diff --git a/server/src/test/scala/actor/objects/AutoRepairIntegrationTest.scala b/server/src/test/scala/actor/objects/AutoRepairIntegrationTest.scala
index cc3ea960f..1f646d955 100644
--- a/server/src/test/scala/actor/objects/AutoRepairIntegrationTest.scala
+++ b/server/src/test/scala/actor/objects/AutoRepairIntegrationTest.scala
@@ -14,7 +14,6 @@ import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.resourcesilo.{ResourceSilo, ResourceSiloControl}
import net.psforever.objects.serverobject.structures.{AutoRepairStats, Building, StructureType}
import net.psforever.objects.serverobject.terminals.{OrderTerminalDefinition, Terminal, TerminalControl}
-import net.psforever.objects.vehicles.VehicleControl
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.base.DamageResolution
import net.psforever.objects.vital.damage.DamageProfile
@@ -201,7 +200,7 @@ class AutoRepairFacilityIntegrationAntGiveNtuTest extends FreedContextActorTest
val maxNtuCap = ant.Definition.MaxNtuCapacitor
player.Spawn()
ant.NtuCapacitor = maxNtuCap
- ant.Actor = context.actorOf(Props(classOf[VehicleControl], ant), name = "test-ant")
+ ant.Definition.Initialize(ant, context)
ant.Zone = zone
ant.Seats(0).mount(player)
ant.DeploymentState = DriveState.Deployed
@@ -295,7 +294,7 @@ class AutoRepairFacilityIntegrationTerminalDestroyedTerminalAntTest extends Free
val maxNtuCap = ant.Definition.MaxNtuCapacitor
player.Spawn()
ant.NtuCapacitor = maxNtuCap
- ant.Actor = context.actorOf(Props(classOf[VehicleControl], ant), name = "test-ant")
+ ant.Definition.Initialize(ant, context)
ant.Zone = zone
ant.Seats(0).mount(player)
ant.DeploymentState = DriveState.Deployed
@@ -397,7 +396,7 @@ class AutoRepairFacilityIntegrationTerminalIncompleteRepairTest extends FreedCon
val maxNtuCap = ant.Definition.MaxNtuCapacitor
player.Spawn()
ant.NtuCapacitor = maxNtuCap
- ant.Actor = context.actorOf(Props(classOf[VehicleControl], ant), name = "test-ant")
+ ant.Definition.Initialize(ant, context)
ant.Zone = zone
ant.Seats(0).mount(player)
ant.DeploymentState = DriveState.Deployed
diff --git a/server/src/test/scala/actor/objects/VehicleSpawnPadTest.scala b/server/src/test/scala/actor/objects/VehicleSpawnPadTest.scala
index f2f00f6dd..886b01039 100644
--- a/server/src/test/scala/actor/objects/VehicleSpawnPadTest.scala
+++ b/server/src/test/scala/actor/objects/VehicleSpawnPadTest.scala
@@ -215,7 +215,7 @@ object VehicleSpawnPadControlTest {
import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.structures.Building
- import net.psforever.objects.vehicles.VehicleControl
+ import net.psforever.objects.vehicles.control.VehicleControl
import net.psforever.objects.Tool
import net.psforever.types.CharacterSex
diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala
index 679a766b4..44b3ef51e 100644
--- a/src/main/scala/net/psforever/actors/session/SessionActor.scala
+++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala
@@ -958,8 +958,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
case Deployment.CanNotChangeDeployment(obj, state, reason) =>
- if (Deployment.CheckForDeployState(state) && !VehicleControl.DeploymentAngleCheck(obj)) {
- CanNotChangeDeployment(obj, state, "ground too steep")
+ if (Deployment.CheckForDeployState(state) && !Deployment.AngleCheck(obj)) {
+ CanNotChangeDeployment(obj, state, reason = "ground too steep")
} else {
CanNotChangeDeployment(obj, state, reason)
}
@@ -2407,13 +2407,12 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
val obj_guid: PlanetSideGUID = obj.GUID
CancelAllProximityUnits()
sendResponse(PlanetsideAttributeMessage(obj_guid, 0, obj.Health))
- sendResponse(PlanetsideAttributeMessage(obj_guid, 68, obj.Shields)) //shield health
+ sendResponse(PlanetsideAttributeMessage(obj_guid, 68, obj.Shields))
if (obj.Definition == GlobalDefinitions.ant) {
sendResponse(PlanetsideAttributeMessage(obj_guid, 45, obj.NtuCapacitorScaled))
}
if (obj.Definition.MaxCapacitor > 0) {
- val capacitor = scala.math.ceil((obj.Capacitor.toFloat / obj.Definition.MaxCapacitor.toFloat) * 10).toInt
- sendResponse(PlanetsideAttributeMessage(obj_guid, 113, capacitor))
+ sendResponse(PlanetsideAttributeMessage(obj_guid, 113, obj.Capacitor))
}
if (seat_number == 0) {
if (obj.Definition == GlobalDefinitions.quadstealth) {
@@ -3150,14 +3149,16 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
vehicle.Actor ! JammableUnit.ClearJammeredSound()
}
//positive shield strength
- if (vehicle.Shields > 0) {
+ if (vehicle.Definition.MaxShields > 0) {
sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 68, vehicle.Shields))
}
// ANT capacitor
if (vehicle.Definition == GlobalDefinitions.ant) {
- sendResponse(
- PlanetsideAttributeMessage(vehicle.GUID, 45, vehicle.NtuCapacitorScaled)
- ) // set ntu on vehicle UI
+ sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 45, vehicle.NtuCapacitorScaled)) // set ntu on vehicle UI
+ }
+ // vehicle capacitor
+ if (vehicle.Definition.MaxCapacitor > 0) {
+ sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 113, vehicle.Capacitor))
}
LoadZoneTransferPassengerMessages(
guid,
@@ -4966,7 +4967,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
case msg @ GenericObjectActionMessage(object_guid, code) =>
+ log.debug(s"$msg")
ValidObject(object_guid) match {
+ case Some(vehicle: Vehicle) =>
+ if (code == 55) {
+ //apc emp
+ vehicle.Actor ! SpecialEmp.Burst()
+ }
case Some(tool: Tool) =>
if (tool.Definition == GlobalDefinitions.maelstrom && code == 35) {
//maelstrom primary fire mode effect (no target)
diff --git a/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala b/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
index 30bc205c2..dbad8716f 100644
--- a/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
+++ b/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
@@ -244,7 +244,7 @@ object ExplosiveDeployableControl {
//val scalar = Vector3.ScalarProjection(dir, up)
val point1 = g1.pointOnOutside(dir).asVector3
val point2 = g2.pointOnOutside(Vector3.neg(dir)).asVector3
- val scalar = Vector3.ScalarProjection(point2 - point1, up)
+ val scalar = Vector3.ScalarProjection(point2 - obj1.Position, up)
(scalar >= 0 || Vector3.MagnitudeSquared(up * scalar) < 0.35f) &&
math.min(
Vector3.DistanceSquared(g1.center.asVector3, g2.center.asVector3),
diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
index 7feb758fa..6d6bfa971 100644
--- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
+++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
@@ -897,11 +897,11 @@ object GlobalDefinitions {
val aurora = VehicleDefinition(ObjectClass.aurora)
- val apc_tr = VehicleDefinition(ObjectClass.apc_tr)
+ val apc_tr = VehicleDefinition.Apc(ObjectClass.apc_tr)
- val apc_nc = VehicleDefinition(ObjectClass.apc_nc)
+ val apc_nc = VehicleDefinition.Apc(ObjectClass.apc_nc)
- val apc_vs = VehicleDefinition(ObjectClass.apc_vs)
+ val apc_vs = VehicleDefinition.Apc(ObjectClass.apc_vs)
val lightning = VehicleDefinition(ObjectClass.lightning)
@@ -911,15 +911,15 @@ object GlobalDefinitions {
val magrider = VehicleDefinition(ObjectClass.magrider)
- val ant = VehicleDefinition(ObjectClass.ant)
+ val ant = VehicleDefinition.Ant(ObjectClass.ant)
- val ams = VehicleDefinition(ObjectClass.ams)
+ val ams = VehicleDefinition.Ams(ObjectClass.ams)
- val router = VehicleDefinition(ObjectClass.router)
+ val router = VehicleDefinition.Router(ObjectClass.router)
- val switchblade = VehicleDefinition(ObjectClass.switchblade)
+ val switchblade = VehicleDefinition.Deploying(ObjectClass.switchblade)
- val flail = VehicleDefinition(ObjectClass.flail)
+ val flail = VehicleDefinition.Deploying(ObjectClass.flail)
val mosquito = VehicleDefinition(ObjectClass.mosquito)
@@ -931,11 +931,11 @@ object GlobalDefinitions {
val vulture = VehicleDefinition(ObjectClass.vulture)
- val dropship = VehicleDefinition(ObjectClass.dropship)
+ val dropship = VehicleDefinition.Carrier(ObjectClass.dropship)
val galaxy_gunship = VehicleDefinition(ObjectClass.galaxy_gunship)
- val lodestar = VehicleDefinition(ObjectClass.lodestar)
+ val lodestar = VehicleDefinition.Carrier(ObjectClass.lodestar)
val phantasm = VehicleDefinition(ObjectClass.phantasm)
@@ -6146,6 +6146,7 @@ object GlobalDefinitions {
apc_tr.MaxDepth = 3
apc_tr.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
apc_tr.Geometry = apcForm
+ apc_tr.MaxCapacitor = 300
apc_nc.Name = "apc_nc" // Vindicator
apc_nc.MaxHealth = 6000
@@ -6208,6 +6209,7 @@ object GlobalDefinitions {
apc_nc.MaxDepth = 3
apc_nc.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
apc_nc.Geometry = apcForm
+ apc_nc.MaxCapacitor = 300
apc_vs.Name = "apc_vs" // Leviathan
apc_vs.MaxHealth = 6000
@@ -6270,6 +6272,7 @@ object GlobalDefinitions {
apc_vs.MaxDepth = 3
apc_vs.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
apc_vs.Geometry = apcForm
+ apc_vs.MaxCapacitor = 300
lightning.Name = "lightning"
lightning.MaxHealth = 2000
diff --git a/src/main/scala/net/psforever/objects/SpecialEmp.scala b/src/main/scala/net/psforever/objects/SpecialEmp.scala
index 1f2d1b79c..7fd2da1db 100644
--- a/src/main/scala/net/psforever/objects/SpecialEmp.scala
+++ b/src/main/scala/net/psforever/objects/SpecialEmp.scala
@@ -41,6 +41,11 @@ object SpecialEmp {
innateDamage = emp
}
+ /**
+ * Trigger an electromagnetic pulse.
+ */
+ final case class Burst()
+
/**
* The damage interaction for an electromagnetic pulse effect.
* @param empEffect information about the effect
diff --git a/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala b/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
index 83a41a353..790b23072 100644
--- a/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
+++ b/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
@@ -1,9 +1,11 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition
-import net.psforever.objects.NtuContainerDefinition
+import akka.actor.{ActorContext, Props}
+import net.psforever.objects.{Default, NtuContainerDefinition, Vehicle}
import net.psforever.objects.definition.converter.VehicleConverter
import net.psforever.objects.inventory.InventoryTile
+import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.vehicles.{DestroyedVehicle, MountableWeaponsDefinition, UtilityType}
import net.psforever.objects.vital._
import net.psforever.objects.vital.damage.DamageCalculations
@@ -181,6 +183,19 @@ class VehicleDefinition(objectId: Int)
destroyedModel = model
DestroyedModel
}
+
+ def Initialize(obj: Vehicle, context: ActorContext): Unit = {
+ import net.psforever.objects.vehicles.control.VehicleControl
+ obj.Actor = context.actorOf(
+ Props(classOf[VehicleControl], obj),
+ PlanetSideServerObject.UniqueActorName(obj)
+ )
+ }
+
+ def Uninitialize(obj: Vehicle, context: ActorContext): Unit = {
+ obj.Actor ! akka.actor.PoisonPill
+ obj.Actor = Default.Actor
+ }
}
object VehicleDefinition {
@@ -189,4 +204,94 @@ object VehicleDefinition {
def apply(objectId: Int): VehicleDefinition = {
new VehicleDefinition(objectId)
}
+
+ protected class AmsDefinition(objectId: Int) extends VehicleDefinition(objectId) {
+ import net.psforever.objects.vehicles.control.AmsControl
+ override def Initialize(obj: Vehicle, context: ActorContext): Unit = {
+ obj.Actor = context.actorOf(
+ Props(classOf[AmsControl], obj),
+ PlanetSideServerObject.UniqueActorName(obj)
+ )
+ }
+ }
+ /**
+ * Vehicle definition for the advanced mobile spawn (AMS) vehicle.
+ * @param objectId the object id that is associated with this sort of `Vehicle`
+ */
+ def Ams(objectId: Int): VehicleDefinition = new AmsDefinition(objectId)
+
+ protected class AntDefinition(objectId: Int) extends VehicleDefinition(objectId) {
+ import net.psforever.objects.vehicles.control.AntControl
+ override def Initialize(obj: Vehicle, context: ActorContext): Unit = {
+ obj.Actor = context.actorOf(
+ Props(classOf[AntControl], obj),
+ PlanetSideServerObject.UniqueActorName(obj)
+ )
+ }
+ }
+ /**
+ * Vehicle definition for the advanced nanite transport (ANT) vehicle.
+ * @param objectId the object id that is associated with this sort of `Vehicle`
+ */
+ def Ant(objectId: Int): VehicleDefinition = new AntDefinition(objectId)
+
+ protected class ApcDefinition(objectId: Int) extends VehicleDefinition(objectId) {
+ import net.psforever.objects.vehicles.control.ApcControl
+ override def Initialize(obj: Vehicle, context: ActorContext): Unit = {
+ obj.Actor = context.actorOf(
+ Props(classOf[ApcControl], obj),
+ PlanetSideServerObject.UniqueActorName(obj)
+ )
+ }
+ }
+ /**
+ * Vehicle definition(s) for the armored personnel carrier (`apc*`) vehicles.
+ * @param objectId the object id that is associated with this sort of `Vehicle`
+ */
+ def Apc(objectId: Int): VehicleDefinition = new ApcDefinition(objectId)
+
+ protected class CarrierDefinition(objectId: Int) extends VehicleDefinition(objectId) {
+ import net.psforever.objects.vehicles.control.CargoCarrierControl
+ override def Initialize(obj: Vehicle, context: ActorContext): Unit = {
+ obj.Actor = context.actorOf(
+ Props(classOf[CargoCarrierControl], obj),
+ PlanetSideServerObject.UniqueActorName(obj)
+ )
+ }
+ }
+ /**
+ * Vehicle definition(s) for the vehicles (carriers) that are used to transport other vehicles (cargo).
+ * @param objectId the object id that is associated with this sort of `Vehicle`
+ */
+ def Carrier(objectId: Int): VehicleDefinition = new CarrierDefinition(objectId)
+
+ protected class DeployingDefinition(objectId: Int) extends VehicleDefinition(objectId) {
+ import net.psforever.objects.vehicles.control.DeployingVehicleControl
+ override def Initialize(obj: Vehicle, context: ActorContext): Unit = {
+ obj.Actor = context.actorOf(
+ Props(classOf[DeployingVehicleControl], obj),
+ PlanetSideServerObject.UniqueActorName(obj)
+ )
+ }
+ }
+ /**
+ * Vehicle definition(s) for the vehicles that perform significant mode state transitions.
+ * @param objectId the object id that is associated with this sort of `Vehicle`
+ */
+ def Deploying(objectId: Int): VehicleDefinition = new DeployingDefinition(objectId)
+
+ protected class RouterDefinition(objectId: Int) extends VehicleDefinition(objectId) {
+ import net.psforever.objects.vehicles.control.RouterControl
+ override def Initialize(obj: Vehicle, context: ActorContext): Unit = {
+ obj.Actor = context.actorOf(
+ Props(classOf[RouterControl], obj),
+ PlanetSideServerObject.UniqueActorName(obj)
+ )
+ }
+ }
+ /**
+ * Vehicle definition for the Router.
+ * @param objectId the object id that is associated with this sort of `Vehicle`
+ */
+ def Router(objectId: Int): VehicleDefinition = new RouterDefinition(objectId)
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala
index ebbeb7918..9efe3dc82 100644
--- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala
@@ -28,27 +28,26 @@ trait DamageableVehicle
}
/** whether or not the vehicle has been damaged directly, report that damage has occurred */
- private var reportDamageToVehicle: Boolean = false
+ protected var reportDamageToVehicle: Boolean = false
def DamageableObject: Vehicle
def AggravatedObject : Vehicle = DamageableObject
- override val takesDamage: Receive =
- originalTakesDamage
- .orElse(aggravatedBehavior)
- .orElse {
- case DamageableVehicle.Damage(cause, damage) =>
- //cargo vehicles inherit feedback from carrier
- reportDamageToVehicle = damage > 0
- DamageAwareness(DamageableObject, cause, amount = 0)
+ override val takesDamage: Receive = originalTakesDamage
+ .orElse(aggravatedBehavior)
+ .orElse {
+ case DamageableVehicle.Damage(cause, damage) =>
+ //cargo vehicles inherit feedback from carrier
+ reportDamageToVehicle = damage > 0
+ DamageAwareness(DamageableObject, cause, amount = 0)
- case DamageableVehicle.Destruction(cause) =>
- //cargo vehicles are destroyed when carrier is destroyed
- val obj = DamageableObject
- obj.Health = 0
- obj.History(cause)
- DestructionAwareness(obj, cause)
- }
+ case DamageableVehicle.Destruction(cause) =>
+ //cargo vehicles are destroyed when carrier is destroyed
+ val obj = DamageableObject
+ obj.Health = 0
+ obj.History(cause)
+ DestructionAwareness(obj, cause)
+ }
/**
* Vehicles may have charged shields that absorb damage before the vehicle's own health is affected.
@@ -83,8 +82,6 @@ trait DamageableVehicle
/**
* Most all vehicles and the weapons mounted to them can jam
* if the projectile that strikes (near) them has jammering properties.
- * A damaged carrier alerts its cargo vehicles of the source of the damage,
- * but it will not be affected by the same jammering effect.
* If this vehicle has shields that were affected by previous damage, that is also reported to the clients.
* @see `Service.defaultPlayerGUID`
* @see `Vehicle.CargoHolds`
@@ -156,22 +153,10 @@ trait DamageableVehicle
//alert to damage source
DamageableMountable.DamageAwareness(obj, cause, totalDamage)
}
- //alert cargo occupants to damage source
- obj.CargoHolds.values.foreach(hold => {
- hold.occupant match {
- case Some(cargo) =>
- cargo.Actor ! DamageableVehicle.Damage(cause, totalDamage)
- case None => ;
- }
- })
}
}
/**
- * A destroyed carrier informs its cargo vehicles that they should also be destroyed
- * for reasons of the same cause being inherited as the source of damage.
- * Regardless of the amount of damage they carrier takes or some other target would take,
- * its cargo vehicles die immediately.
* The vehicle's shields are zero'd out if they were previously energized
* so that the vehicle's corpse does not act like it is still protected by vehicle shields.
* Finally, the vehicle is tasked for deconstruction.
@@ -196,14 +181,6 @@ trait DamageableVehicle
EndAllAggravation()
//passengers die with us
DamageableMountable.DestructionAwareness(obj, cause)
- //cargo vehicles die with us
- obj.CargoHolds.values.foreach(hold => {
- hold.occupant match {
- case Some(cargo) =>
- cargo.Actor ! DamageableVehicle.Destruction(cause)
- case None => ;
- }
- })
//things positioned around us can get hurt from us
Zone.serverSideDamage(obj.Zone, target, Zone.explosionDamage(Some(cause)))
//special considerations for certain vehicles
@@ -224,17 +201,16 @@ trait DamageableVehicle
}
object DamageableVehicle {
-
/**
* Message for instructing the target's cargo vehicles about a damage source affecting their carrier.
* @param cause historical information about damage
*/
- private case class Damage(cause: DamageResult, amount: Int)
+ final case class Damage(cause: DamageResult, amount: Int)
/**
* Message for instructing the target's cargo vehicles that their carrier is destroyed,
* and they should be destroyed too.
* @param cause historical information about damage
*/
- private case class Destruction(cause: DamageResult)
+ final case class Destruction(cause: DamageResult)
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/deploy/Deployment.scala b/src/main/scala/net/psforever/objects/serverobject/deploy/Deployment.scala
index 719c421e3..34aa78b2a 100644
--- a/src/main/scala/net/psforever/objects/serverobject/deploy/Deployment.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/deploy/Deployment.scala
@@ -110,4 +110,8 @@ object Deployment {
*/
def CheckForUndeployState(state: DriveState.Value): Boolean =
state == DriveState.Undeploying || state == DriveState.Mobile || state == DriveState.State7
+
+ def AngleCheck(obj: Deployment.DeploymentObject): Boolean = {
+ obj.Orientation.x <= 30 || obj.Orientation.x >= 330
+ }
}
diff --git a/src/main/scala/net/psforever/objects/vehicles/control/AmsControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/AmsControl.scala
new file mode 100644
index 000000000..38109464b
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/vehicles/control/AmsControl.scala
@@ -0,0 +1,58 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.vehicles.control
+
+import net.psforever.objects._
+import net.psforever.services.Service
+import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
+import net.psforever.types.DriveState
+
+/**
+ * A vehicle control agency exclusive to the advanced mobile spawn (AMS).
+ * When deployed, infantry troops may manifest nearby the vehicle
+ * as they switch from being deconstructed (or dead) to being alive.
+ * @param vehicle the AMS
+ */
+class AmsControl(vehicle: Vehicle)
+ extends DeployingVehicleControl(vehicle) {
+
+ /**
+ * React to a deployment state change.
+ * Announce that this AMS is ready to accept troop deployment.
+ * @param state the deployment state
+ */
+ override def specificResponseToDeployment(state: DriveState.Value): Unit = {
+ state match {
+ case DriveState.Deployed =>
+ val zone = vehicle.Zone
+ val driverChannel = vehicle.Seats(0).occupant match {
+ case Some(tplayer) => tplayer.Name
+ case None => ""
+ }
+ val events = zone.VehicleEvents
+ events ! VehicleServiceMessage.AMSDeploymentChange(zone)
+ events ! VehicleServiceMessage(driverChannel, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vehicle.GUID, 81, 1))
+ case _ => ;
+ }
+ }
+
+ /**
+ * React to an undeployment state change.
+ * This AMS is now off the grid.
+ * @param state the deployment state
+ */
+ override def specificResponseToUndeployment(state: DriveState.Value): Unit = {
+ state match {
+ case DriveState.Undeploying =>
+ val zone = vehicle.Zone
+ val driverChannel = vehicle.Seats(0).occupant match {
+ case Some(tplayer) => tplayer.Name
+ case None => ""
+ }
+ val events = zone.VehicleEvents
+ events ! VehicleServiceMessage.AMSDeploymentChange(zone)
+ events ! VehicleServiceMessage(driverChannel, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vehicle.GUID, 81, 0))
+ case _ => ;
+ }
+ }
+}
+
diff --git a/src/main/scala/net/psforever/objects/vehicles/control/AntControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/AntControl.scala
new file mode 100644
index 000000000..d682fafe6
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/vehicles/control/AntControl.scala
@@ -0,0 +1,60 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.vehicles.control
+
+import net.psforever.objects._
+import net.psforever.objects.serverobject.transfer.TransferBehavior
+import net.psforever.objects.vehicles._
+import net.psforever.types.DriveState
+
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.duration._
+
+/**
+ * A vehicle control agency exclusive to the advanced nanite transport (ANT).
+ * When deployed, nanites in the package of nanite transfer units (NTU) are moved around
+ * and may be may be acquired from a Warp Gate structure
+ * or supplied to a nanite resource silo belonging to a mjaor facility.
+ * @param vehicle the ANT
+ */
+class AntControl(vehicle: Vehicle)
+ extends DeployingVehicleControl(vehicle)
+ with AntTransferBehavior {
+ def ChargeTransferObject = vehicle
+
+ findChargeTargetFunc = Vehicles.FindANTChargingSource
+ findDischargeTargetFunc = Vehicles.FindANTDischargingTarget
+
+ override def commonEnabledBehavior: Receive = super.commonEnabledBehavior.orElse(antBehavior)
+
+ /**
+ * React to a deployment state change.
+ * Make ourselves available to nanite charging or discharging.
+ * @param state the deployment state
+ */
+ override def specificResponseToDeployment(state: DriveState.Value): Unit = {
+ state match {
+ case DriveState.Deployed =>
+ // Start ntu regeneration
+ // If vehicle sends UseItemMessage with silo as target NTU regeneration will be disabled and orb particles will be disabled
+ context.system.scheduler.scheduleOnce(
+ delay = 1000 milliseconds,
+ vehicle.Actor,
+ TransferBehavior.Charging(Ntu.Nanites)
+ )
+ case _ => ;
+ }
+ }
+
+ /**
+ * React to an undeployment state change.
+ * Stop charging or discharging and tell the partner entity that it is no longer interacting with this vehicle.
+ * @param state the undeployment state
+ */
+ override def specificResponseToUndeployment(state: DriveState.Value): Unit = {
+ state match {
+ case DriveState.Undeploying =>
+ TryStopChargingEvent(vehicle)
+ case _ => ;
+ }
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/vehicles/control/ApcControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/ApcControl.scala
new file mode 100644
index 000000000..5655ec931
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/vehicles/control/ApcControl.scala
@@ -0,0 +1,116 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.vehicles.control
+
+import net.psforever.objects._
+import net.psforever.objects.serverobject.damage.Damageable.Target
+import net.psforever.objects.vital.interaction.DamageResult
+import net.psforever.objects.zones.Zone
+import net.psforever.packet.game.{TriggerEffectMessage, TriggeredEffectLocation}
+import net.psforever.services.Service
+import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
+
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.duration._
+
+/**
+ * A vehicle control agency exclusive to the armored personnel carrier (APC) ground transport vehicles.
+ * These vehicles include the Juggernaut (`apc_tr`), the Vindicator (`apc_nc`), the and Leviathan (`apc_vs`).
+ * A completely faction-neutral transport in the same sytle (`apc`) does exist but is unused.
+ * A common characteristic of this type of vehicle is the ability to discharge a defensive wide-area electromagnetic pulse.
+ * @param vehicle the APC
+ */
+class ApcControl(vehicle: Vehicle)
+ extends VehicleControl(vehicle) {
+ protected var capacitor = Default.Cancellable
+
+ startCapacitorTimer()
+
+ override def postStop() : Unit = {
+ super.postStop()
+ capacitor.cancel()
+ }
+
+ override def commonEnabledBehavior : Receive =
+ super.commonEnabledBehavior
+ .orElse {
+ case ApcControl.CapacitorCharge(amount) =>
+ if (vehicle.Capacitor < vehicle.Definition.MaxCapacitor) {
+ val capacitance = vehicle.Capacitor += amount
+ vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
+ self.toString(),
+ VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vehicle.GUID, 113, capacitance)
+ )
+ startCapacitorTimer()
+ } else {
+ capacitor = Default.Cancellable
+ }
+
+ case SpecialEmp.Burst() =>
+ if (vehicle.Capacitor == vehicle.Definition.MaxCapacitor) { //only if the capacitor is full
+ val zone = vehicle.Zone
+ val events = zone.VehicleEvents
+ val pos = vehicle.Position
+ val GUID0 = Service.defaultPlayerGUID
+ val emp = vehicle.Definition.innateDamage.getOrElse { SpecialEmp.emp }
+ val faction = vehicle.Faction
+ //drain the capacitor
+ vehicle.Capacitor = 0
+ events ! VehicleServiceMessage(
+ self.toString(),
+ VehicleAction.PlanetsideAttribute(GUID0, vehicle.GUID, 113, 0)
+ )
+ //cause the emp
+ events ! VehicleServiceMessage(
+ zone.id,
+ VehicleAction.SendResponse(
+ GUID0,
+ TriggerEffectMessage(
+ GUID0,
+ s"apc_explosion_emp_${faction.toString.toLowerCase}",
+ None,
+ Some(TriggeredEffectLocation(pos, vehicle.Orientation))
+ )
+ )
+ )
+ //resolve what targets are affected by the emp
+ Zone.serverSideDamage(
+ zone,
+ vehicle,
+ emp,
+ SpecialEmp.createEmpInteraction(emp, pos),
+ ExplosiveDeployableControl.detectionForExplosiveSource(vehicle),
+ Zone.findAllTargets
+ )
+ //start charging again
+ startCapacitorTimer()
+ }
+ }
+
+ override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
+ super.PrepareForDisabled(kickPassengers)
+ capacitor.cancel()
+ }
+
+ override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = {
+ super.DestructionAwareness(target, cause)
+ capacitor.cancel()
+ vehicle.Capacitor = 0
+ }
+
+ //TODO switch from magic numbers to definition numbers?
+ private def startCapacitorTimer(): Unit = {
+ capacitor = context.system.scheduler.scheduleOnce(
+ delay = 1000 millisecond,
+ self,
+ ApcControl.CapacitorCharge(10)
+ )
+ }
+}
+
+object ApcControl {
+ /**
+ * Charge the vehicle's internal capacitor by the given amount during the schedulefd charge event.
+ * @param amount how much energy in the charge
+ */
+ private case class CapacitorCharge(amount: Int)
+}
diff --git a/src/main/scala/net/psforever/objects/vehicles/control/CargoCarrierControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/CargoCarrierControl.scala
new file mode 100644
index 000000000..ccfc6c3dd
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/vehicles/control/CargoCarrierControl.scala
@@ -0,0 +1,88 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.vehicles.control
+
+import net.psforever.objects._
+import net.psforever.objects.serverobject.damage.{Damageable, DamageableVehicle}
+import net.psforever.objects.vehicles.CargoBehavior
+import net.psforever.objects.vital.interaction.DamageResult
+
+/**
+ * A vehicle control agency exclusive to vehicles that can physically transport other vehicles.
+ * This includes the Galaxy (`dropship`) and the Lodestar.
+ * @param vehicle the vehicle
+ */
+class CargoCarrierControl(vehicle: Vehicle)
+ extends VehicleControl(vehicle)
+ with CargoBehavior {
+ def CargoObject = vehicle
+
+ override def commonEnabledBehavior: Receive = super.commonEnabledBehavior.orElse(cargoBehavior)
+
+ /**
+ * If the vehicle becomes disabled, the safety and autonomy of the cargo should be prioritized.
+ * @param kickPassengers passengers need to be ejected "by force"
+ */
+ override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
+ //abandon all cargo
+ vehicle.CargoHolds.values
+ .collect {
+ case hold if hold.isOccupied =>
+ val cargo = hold.occupant.get
+ CargoBehavior.HandleVehicleCargoDismount(
+ cargo.GUID,
+ cargo,
+ vehicle.GUID,
+ vehicle,
+ bailed = false,
+ requestedByPassenger = false,
+ kicked = false
+ )
+ }
+ super.PrepareForDisabled(kickPassengers)
+ }
+
+ /**
+ * A damaged carrier alerts its cargo vehicles of the source of the damage,
+ * but that cargo will not be affected by either damage directly or by other effects applied to the carrier.
+ * @param target the entity being destroyed
+ * @param cause historical information about the damage
+ * @param amount how much damage was performed
+ */
+ override protected def DamageAwareness(target: Damageable.Target, cause: DamageResult, amount: Any): Unit = {
+ val report = amount match {
+ case (a: Int, b: Int) => a + b
+ case a: Int => a
+ case _ => 0
+ }
+ val announceConfrontation: Boolean = reportDamageToVehicle || report > 0
+ super.DamageAwareness(target, cause, amount)
+ if (announceConfrontation) {
+ //alert cargo occupants to damage source
+ vehicle.CargoHolds.values.foreach(hold => {
+ hold.occupant match {
+ case Some(cargo) => cargo.Actor ! DamageableVehicle.Damage(cause, report)
+ case None => ;
+ }
+ })
+ }
+ }
+
+ /**
+ * A destroyed carrier informs its cargo vehicles that they should also be destroyed
+ * for reasons of the same cause being inherited as the source of damage.
+ * Regardless of the amount of damage they carrier takes or some other target would take,
+ * its cargo vehicles die immediately.
+ * @param target the entity being destroyed
+ * @param cause historical information about the damage
+ */
+ override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = {
+ super.DestructionAwareness(target, cause)
+ //cargo vehicles die with us
+ vehicle.CargoHolds.values.foreach { hold =>
+ hold.occupant match {
+ case Some(cargo) => cargo.Actor ! DamageableVehicle.Destruction(cause)
+ case None => ;
+ }
+ }
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/vehicles/control/DeployingVehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/DeployingVehicleControl.scala
new file mode 100644
index 000000000..ef5014eea
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/vehicles/control/DeployingVehicleControl.scala
@@ -0,0 +1,97 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.vehicles.control
+
+import net.psforever.objects._
+import net.psforever.objects.serverobject.deploy.Deployment.DeploymentObject
+import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior}
+import net.psforever.objects.serverobject.mount.Mountable
+import net.psforever.types._
+
+/**
+ * A vehicle control agency exclusive to vehicles that can switch out a navigation mode
+ * and convert to a sessile mode that affords additional functionality.
+ * This includes only the Switchblade and the Flail.
+ * Other vehicles that deploy are handled by specific instances of this control agency.
+ * @see `AmsControl`
+ * @see `AntControl`
+ * @see `RouterControl`
+ * @param vehicle the vehicle
+ */
+class DeployingVehicleControl(vehicle: Vehicle)
+ extends VehicleControl(vehicle)
+ with DeploymentBehavior {
+ def DeploymentObject = vehicle
+
+ override def commonEnabledBehavior : Receive = super.commonEnabledBehavior.orElse(deployBehavior)
+
+ /**
+ * Even when disabled, the vehicle can be made to undeploy.
+ * Even when disabled, passengers can formally dismount from the vehicle.
+ */
+ override def commonDisabledBehavior : Receive =
+ super.commonDisabledBehavior
+ .orElse {
+ case msg : Deployment.TryUndeploy =>
+ deployBehavior.apply(msg)
+
+ case msg @ Mountable.TryDismount(_, seat_num) =>
+ dismountBehavior.apply(msg)
+ dismountCleanup(seat_num)
+ }
+
+ /**
+ * Even when on the verge of deletion, the vehicle can be made to undeploy.
+ */
+ override def commonDeleteBehavior : Receive =
+ super.commonDeleteBehavior
+ .orElse {
+ case msg : Deployment.TryUndeploy =>
+ deployBehavior.apply(msg)
+ }
+
+ /**
+ * Even when disabled, the vehicle can be made to undeploy.
+ */
+ override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
+ vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying)
+ super.PrepareForDisabled(kickPassengers)
+ }
+
+ /**
+ * Even when on the verge of deletion, the vehicle can be made to undeploy.
+ */
+ override def PrepareForDeletion() : Unit = {
+ vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying)
+ super.PrepareForDeletion()
+ }
+
+ override def TryDeploymentChange(obj: Deployment.DeploymentObject, state: DriveState.Value): Boolean = {
+ Deployment.AngleCheck(obj) && super.TryDeploymentChange(obj, state)
+ }
+
+ override def DeploymentAction(
+ obj: DeploymentObject,
+ state: DriveState.Value,
+ prevState: DriveState.Value
+ ): DriveState.Value = {
+ val out = super.DeploymentAction(obj, state, prevState)
+ Vehicles.ReloadAccessPermissions(vehicle, vehicle.Faction.toString)
+ specificResponseToDeployment(state)
+ out
+ }
+
+ def specificResponseToDeployment(state: DriveState.Value): Unit = { }
+
+ override def UndeploymentAction(
+ obj: DeploymentObject,
+ state: DriveState.Value,
+ prevState: DriveState.Value
+ ): DriveState.Value = {
+ val out = if (decaying) state else super.UndeploymentAction(obj, state, prevState)
+ Vehicles.ReloadAccessPermissions(vehicle, vehicle.Faction.toString)
+ specificResponseToUndeployment(state)
+ out
+ }
+
+ def specificResponseToUndeployment(state: DriveState.Value): Unit = { }
+}
diff --git a/src/main/scala/net/psforever/objects/vehicles/control/RouterControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/RouterControl.scala
new file mode 100644
index 000000000..010a44ee0
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/vehicles/control/RouterControl.scala
@@ -0,0 +1,50 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.vehicles.control
+
+import net.psforever.objects._
+import net.psforever.objects.ce.TelepadLike
+import net.psforever.objects.vehicles.{Utility, UtilityType}
+import net.psforever.types.DriveState
+
+/**
+ * A vehicle control agency exclusive to the router.
+ * When deployed, any router telepad that was acquired from this particular router
+ * and then constructed into a router telepad somewhere in the world
+ * may synchronize with the vehicle to establish a short to medium range infantry teleportation system.
+ * @param vehicle the router
+ */
+class RouterControl(vehicle: Vehicle)
+ extends DeployingVehicleControl(vehicle) {
+
+ /**
+ * React to a deployment state change.
+ * Activate the internal telepad mechanism.
+ * @param state the deployment state
+ */
+ override def specificResponseToDeployment(state: DriveState.Value): Unit = {
+ state match {
+ case DriveState.Deployed =>
+ vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
+ case Some(util: Utility.InternalTelepad) => util.Actor ! TelepadLike.Activate(util)
+ case _ => ;
+ }
+ case _ => ;
+ }
+ }
+
+ /**
+ * React to an undeployment state change.
+ * Deactivate the internal telepad mechanism.
+ * @param state the deployment state
+ */
+ override def specificResponseToUndeployment(state: DriveState.Value): Unit = {
+ state match {
+ case DriveState.Undeploying =>
+ vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
+ case Some(util: Utility.InternalTelepad) => util.Actor ! TelepadLike.Deactivate(util)
+ case _ => ;
+ }
+ case _ => ;
+ }
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala
similarity index 61%
rename from src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
rename to src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala
index fff3c7fe6..f001da233 100644
--- a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala
@@ -1,11 +1,10 @@
-// Copyright (c) 2017-2020 PSForever
-package net.psforever.objects.vehicles
+// Copyright (c) 2017-2021 PSForever
+package net.psforever.objects.vehicles.control
import akka.actor.{Actor, Cancellable}
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects._
import net.psforever.objects.ballistics.VehicleSource
-import net.psforever.objects.ce.TelepadLike
import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.equipment.{Equipment, EquipmentSlot, JammableMountedWeapons}
import net.psforever.objects.guid.GUIDTask
@@ -14,14 +13,12 @@ import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObjec
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior}
import net.psforever.objects.serverobject.damage.{AggravatedBehavior, DamageableVehicle}
-import net.psforever.objects.serverobject.deploy.Deployment.DeploymentObject
-import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior}
import net.psforever.objects.serverobject.environment._
import net.psforever.objects.serverobject.hackable.GenericHackables
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.repair.RepairableVehicle
import net.psforever.objects.serverobject.terminals.Terminal
-import net.psforever.objects.serverobject.transfer.TransferBehavior
+import net.psforever.objects.vehicles.{AccessPermissionGroup, CargoBehavior, Utility, VehicleLockState}
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
import net.psforever.objects.vital.VehicleShieldCharge
import net.psforever.objects.vital.environment.EnvironmentReason
@@ -40,45 +37,36 @@ import scala.concurrent.duration._
/**
* An `Actor` that handles messages being dispatched to a specific `Vehicle`.
*
- * Vehicle-controlling actors have two behavioral states - responsive and "`Disabled`."
+ * Vehicle-controlling actors have two important behavioral states - responsive and "`Disabled`."
* The latter is applicable only when the specific vehicle is being deconstructed.
- *
+ * Furthermore, being "ready to delete" is also a behavoral state for the end of life operations of the vehicle.
* @param vehicle the `Vehicle` object being governed
*/
class VehicleControl(vehicle: Vehicle)
- extends Actor
+ extends Actor
with FactionAffinityBehavior.Check
- with DeploymentBehavior
with MountableBehavior
- with CargoBehavior
with DamageableVehicle
with RepairableVehicle
with JammableMountedWeapons
with ContainableBehavior
- with AntTransferBehavior
with AggravatedBehavior
with RespondsToZoneEnvironment {
//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 CargoObject = vehicle
-
def JammableObject = vehicle
def FactionObject = vehicle
- def DeploymentObject = vehicle
-
def DamageableObject = vehicle
def RepairableObject = vehicle
def ContainerObject = vehicle
- def ChargeTransferObject = vehicle
-
def InteractiveObject = vehicle
SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
@@ -87,10 +75,6 @@ class VehicleControl(vehicle: Vehicle)
SetInteractionStop(EnvironmentAttribute.Water, stopInteractingWithWater)
}
- if (vehicle.Definition == GlobalDefinitions.ant) {
- findChargeTargetFunc = Vehicles.FindANTChargingSource
- findDischargeTargetFunc = Vehicles.FindANTDischargingTarget
- }
/** cheap flag for whether the vehicle is decaying */
var decaying : Boolean = false
/** primary vehicle decay timer */
@@ -112,199 +96,197 @@ class VehicleControl(vehicle: Vehicle)
recoverFromEnvironmentInteracting()
}
- def Enabled : Receive =
- checkBehavior
- .orElse(deployBehavior)
- .orElse(cargoBehavior)
- .orElse(jammableBehavior)
- .orElse(takesDamage)
- .orElse(canBeRepairedByNanoDispenser)
- .orElse(containerBehavior)
- .orElse(antBehavior)
- .orElse(environmentBehavior)
- .orElse {
- case Vehicle.Ownership(None) =>
- LoseOwnership()
+ def commonEnabledBehavior: Receive = checkBehavior
+ .orElse(jammableBehavior)
+ .orElse(takesDamage)
+ .orElse(canBeRepairedByNanoDispenser)
+ .orElse(containerBehavior)
+ .orElse(environmentBehavior)
+ .orElse {
+ case Vehicle.Ownership(None) =>
+ LoseOwnership()
- case Vehicle.Ownership(Some(player)) =>
- GainOwnership(player)
+ case Vehicle.Ownership(Some(player)) =>
+ GainOwnership(player)
- case msg @ Mountable.TryMount(player, mount_point) =>
- mountBehavior.apply(msg)
- mountCleanup(mount_point, player)
+ case msg @ Mountable.TryMount(player, mount_point) =>
+ mountBehavior.apply(msg)
+ mountCleanup(mount_point, player)
- case msg @ Mountable.TryDismount(_, seat_num) =>
- dismountBehavior.apply(msg)
- dismountCleanup(seat_num)
+ case msg @ Mountable.TryDismount(_, seat_num) =>
+ dismountBehavior.apply(msg)
+ dismountCleanup(seat_num)
- case Vehicle.ChargeShields(amount) =>
- val now : Long = System.currentTimeMillis()
- //make certain vehicle doesn't charge shields too quickly
- if (
- vehicle.Health > 0 && vehicle.Shields < vehicle.MaxShields &&
- !vehicle.History.exists(VehicleControl.LastShieldChargeOrDamage(now))
- ) {
- vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), amount))
- vehicle.Shields = vehicle.Shields + amount
- vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
- s"${vehicle.Actor}",
- VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), vehicle.GUID, 68, vehicle.Shields)
- )
- }
+ case Vehicle.ChargeShields(amount) =>
+ val now : Long = System.currentTimeMillis()
+ //make certain vehicles don't charge shields too quickly
+ if (
+ vehicle.Health > 0 && vehicle.Shields < vehicle.MaxShields &&
+ !vehicle.History.exists(VehicleControl.LastShieldChargeOrDamage(now))
+ ) {
+ vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), amount))
+ vehicle.Shields = vehicle.Shields + amount
+ vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
+ s"${vehicle.Actor}",
+ VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), vehicle.GUID, 68, vehicle.Shields)
+ )
+ }
- case Vehicle.UpdateZoneInteractionProgressUI(player) =>
- updateZoneInteractionProgressUI(player)
+ case Vehicle.UpdateZoneInteractionProgressUI(player) =>
+ updateZoneInteractionProgressUI(player)
- case FactionAffinity.ConvertFactionAffinity(faction) =>
- val originalAffinity = vehicle.Faction
- if (originalAffinity != (vehicle.Faction = faction)) {
- vehicle.Utilities.foreach({
- case (_ : Int, util : Utility) => util().Actor forward FactionAffinity.ConfirmFactionAffinity()
- })
- }
- sender() ! FactionAffinity.AssertFactionAffinity(vehicle, faction)
+ case FactionAffinity.ConvertFactionAffinity(faction) =>
+ val originalAffinity = vehicle.Faction
+ if (originalAffinity != (vehicle.Faction = faction)) {
+ vehicle.Utilities.foreach({
+ case (_ : Int, util : Utility) => util().Actor forward FactionAffinity.ConfirmFactionAffinity()
+ })
+ }
+ sender() ! FactionAffinity.AssertFactionAffinity(vehicle, faction)
- case CommonMessages.Use(player, Some(item : SimpleItem))
- if item.Definition == GlobalDefinitions.remote_electronics_kit =>
- //TODO setup certifications check
- if (vehicle.Faction != player.Faction) {
- sender() ! CommonMessages.Progress(
- GenericHackables.GetHackSpeed(player, vehicle),
- Vehicles.FinishHackingVehicle(vehicle, player, 3212836864L),
- GenericHackables.HackingTickAction(progressType = 1, player, vehicle, item.GUID)
- )
- }
+ case CommonMessages.Use(player, Some(item : SimpleItem))
+ if item.Definition == GlobalDefinitions.remote_electronics_kit =>
+ //TODO setup certifications check
+ if (vehicle.Faction != player.Faction) {
+ sender() ! CommonMessages.Progress(
+ GenericHackables.GetHackSpeed(player, vehicle),
+ Vehicles.FinishHackingVehicle(vehicle, player, 3212836864L),
+ GenericHackables.HackingTickAction(progressType = 1, player, vehicle, item.GUID)
+ )
+ }
- case Terminal.TerminalMessage(player, msg, reply) =>
- reply match {
- case Terminal.VehicleLoadout(definition, weapons, inventory) =>
- org.log4s
- .getLogger(vehicle.Definition.Name)
- .info(s"changing vehicle equipment loadout to ${player.Name}'s option #${msg.unk1 + 1}")
- //remove old inventory
- val oldInventory = vehicle.Inventory.Clear().map { case InventoryItem(obj, _) => (obj, obj.GUID) }
- //"dropped" items are lost; if it doesn't go in the trunk, it vanishes into the nanite cloud
- val (_, afterInventory) = inventory.partition(ContainableBehavior.DropPredicate(player))
- val (oldWeapons, newWeapons, finalInventory) = if (vehicle.Definition == definition) {
- //vehicles are the same type
- //TODO want to completely swap weapons, but holster icon vanishes temporarily after swap
- //TODO BFR arms must be swapped properly
- // //remove old weapons
- // val oldWeapons = vehicle.Weapons.values.collect { case slot if slot.Equipment.nonEmpty =>
- // val obj = slot.Equipment.get
- // slot.Equipment = None
- // (obj, obj.GUID)
- // }.toList
- // (oldWeapons, weapons, afterInventory)
- //TODO for now, just refill ammo; assume weapons stay the same
- vehicle.Weapons
- .collect { case (_, slot : EquipmentSlot) if slot.Equipment.nonEmpty => slot.Equipment.get }
- .collect {
- case weapon : Tool =>
- weapon.AmmoSlots.foreach { ammo => ammo.Box.Capacity = ammo.Box.Definition.Capacity }
- }
- (Nil, Nil, afterInventory)
+ case Terminal.TerminalMessage(player, msg, reply) =>
+ reply match {
+ case Terminal.VehicleLoadout(definition, weapons, inventory) =>
+ org.log4s
+ .getLogger(vehicle.Definition.Name)
+ .info(s"changing vehicle equipment loadout to ${player.Name}'s option #${msg.unk1 + 1}")
+ //remove old inventory
+ val oldInventory = vehicle.Inventory.Clear().map { case InventoryItem(obj, _) => (obj, obj.GUID) }
+ //"dropped" items are lost; if it doesn't go in the trunk, it vanishes into the nanite cloud
+ val (_, afterInventory) = inventory.partition(ContainableBehavior.DropPredicate(player))
+ val (oldWeapons, newWeapons, finalInventory) = if (vehicle.Definition == definition) {
+ //vehicles are the same type
+ //TODO want to completely swap weapons, but holster icon vanishes temporarily after swap
+ //TODO BFR arms must be swapped properly
+ // //remove old weapons
+ // val oldWeapons = vehicle.Weapons.values.collect { case slot if slot.Equipment.nonEmpty =>
+ // val obj = slot.Equipment.get
+ // slot.Equipment = None
+ // (obj, obj.GUID)
+ // }.toList
+ // (oldWeapons, weapons, afterInventory)
+ //TODO for now, just refill ammo; assume weapons stay the same
+ vehicle.Weapons
+ .collect { case (_, slot : EquipmentSlot) if slot.Equipment.nonEmpty => slot.Equipment.get }
+ .collect {
+ case weapon : Tool =>
+ weapon.AmmoSlots.foreach { ammo => ammo.Box.Capacity = ammo.Box.Definition.Capacity }
+ }
+ (Nil, Nil, afterInventory)
+ }
+ else {
+ //vehicle loadout is not for this vehicle
+ //do not transfer over weapon ammo
+ if (
+ vehicle.Definition.TrunkSize == definition.TrunkSize && vehicle.Definition.TrunkOffset == definition.TrunkOffset
+ ) {
+ (Nil, Nil, afterInventory) //trunk is the same dimensions, however
}
else {
- //vehicle loadout is not for this vehicle
- //do not transfer over weapon ammo
- if (
- vehicle.Definition.TrunkSize == definition.TrunkSize && vehicle.Definition.TrunkOffset == definition.TrunkOffset
- ) {
- (Nil, Nil, afterInventory) //trunk is the same dimensions, however
- }
- else {
- //accommodate as much of inventory as possible
- val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory)
- (Nil, Nil, stow)
- }
+ //accommodate as much of inventory as possible
+ val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory)
+ (Nil, Nil, stow)
}
- finalInventory.foreach {
- _.obj.Faction = vehicle.Faction
- }
- player.Zone.VehicleEvents ! VehicleServiceMessage(
- player.Zone.id,
- VehicleAction.ChangeLoadout(vehicle.GUID, oldWeapons, newWeapons, oldInventory, finalInventory)
- )
- player.Zone.AvatarEvents ! AvatarServiceMessage(
- player.Name,
- AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true)
- )
+ }
+ finalInventory.foreach {
+ _.obj.Faction = vehicle.Faction
+ }
+ player.Zone.VehicleEvents ! VehicleServiceMessage(
+ player.Zone.id,
+ VehicleAction.ChangeLoadout(vehicle.GUID, oldWeapons, newWeapons, oldInventory, finalInventory)
+ )
+ player.Zone.AvatarEvents ! AvatarServiceMessage(
+ player.Name,
+ AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true)
+ )
- case _ => ;
- }
+ case _ => ;
+ }
- case VehicleControl.Disable() =>
- PrepareForDisabled(kickPassengers = false)
- context.become(Disabled)
+ case VehicleControl.Disable() =>
+ PrepareForDisabled(kickPassengers = false)
+ context.become(Disabled)
- case Vehicle.Deconstruct(time) =>
- time match {
- case Some(delay) if vehicle.Definition.undergoesDecay =>
- decaying = true
- decayTimer.cancel()
- decayTimer = context.system.scheduler.scheduleOnce(delay, self, VehicleControl.PrepareForDeletion())
- case _ =>
- PrepareForDisabled(kickPassengers = true)
- PrepareForDeletion()
- context.become(ReadyToDelete)
- }
+ case Vehicle.Deconstruct(time) =>
+ time match {
+ case Some(delay) if vehicle.Definition.undergoesDecay =>
+ decaying = true
+ decayTimer.cancel()
+ decayTimer = context.system.scheduler.scheduleOnce(delay, self, VehicleControl.PrepareForDeletion())
+ case _ =>
+ PrepareForDisabled(kickPassengers = true)
+ PrepareForDeletion()
+ context.become(ReadyToDelete)
+ }
- case VehicleControl.PrepareForDeletion() =>
- PrepareForDisabled(kickPassengers = true)
- PrepareForDeletion()
- context.become(ReadyToDelete)
+ case VehicleControl.PrepareForDeletion() =>
+ PrepareForDisabled(kickPassengers = true)
+ PrepareForDeletion()
+ context.become(ReadyToDelete)
- case VehicleControl.AssignOwnership(player) =>
- vehicle.AssignOwnership(player)
+ case VehicleControl.AssignOwnership(player) =>
+ vehicle.AssignOwnership(player)
+ }
+ final def Enabled: Receive =
+ commonEnabledBehavior
+ .orElse {
case _ => ;
}
- def Disabled : Receive =
- checkBehavior
- .orElse {
- case msg : Deployment.TryUndeploy =>
- deployBehavior.apply(msg)
+ def commonDisabledBehavior: Receive = checkBehavior
+ .orElse {
+ case msg @ Mountable.TryDismount(_, seat_num) =>
+ dismountBehavior.apply(msg)
+ dismountCleanup(seat_num)
- case msg @ Mountable.TryDismount(_, seat_num) =>
- dismountBehavior.apply(msg)
- dismountCleanup(seat_num)
+ case Vehicle.Deconstruct(time) =>
+ time match {
+ case Some(delay) if vehicle.Definition.undergoesDecay =>
+ decaying = true
+ decayTimer.cancel()
+ decayTimer = context.system.scheduler.scheduleOnce(delay, self, VehicleControl.PrepareForDeletion())
+ case _ =>
+ PrepareForDeletion()
+ context.become(ReadyToDelete)
+ }
- case Vehicle.Deconstruct(time) =>
- time match {
- case Some(delay) if vehicle.Definition.undergoesDecay =>
- decaying = true
- decayTimer.cancel()
- decayTimer = context.system.scheduler.scheduleOnce(delay, self, VehicleControl.PrepareForDeletion())
- case _ =>
- PrepareForDeletion()
- context.become(ReadyToDelete)
- }
+ case VehicleControl.PrepareForDeletion() =>
+ PrepareForDeletion()
+ context.become(ReadyToDelete)
+ }
- case VehicleControl.PrepareForDeletion() =>
- PrepareForDeletion()
- context.become(ReadyToDelete)
+ final def Disabled: Receive = commonDisabledBehavior
+ .orElse {
+ case _ => ;
+ }
- case _ =>
- }
+ def commonDeleteBehavior: Receive = checkBehavior
+ .orElse {
+ case VehicleControl.Deletion() =>
+ val zone = vehicle.Zone
+ zone.VehicleEvents ! VehicleServiceMessage(
+ zone.id,
+ VehicleAction.UnloadVehicle(Service.defaultPlayerGUID, vehicle, vehicle.GUID)
+ )
+ zone.Transport.tell(Zone.Vehicle.Despawn(vehicle), zone.Transport)
+ }
- def ReadyToDelete : Receive =
- checkBehavior
- .orElse {
- case msg : Deployment.TryUndeploy =>
- deployBehavior.apply(msg)
-
- case VehicleControl.Deletion() =>
- val zone = vehicle.Zone
- zone.VehicleEvents ! VehicleServiceMessage(
- zone.id,
- VehicleAction.UnloadVehicle(Service.defaultPlayerGUID, vehicle, vehicle.GUID)
- )
- zone.Transport.tell(Zone.Vehicle.Despawn(vehicle), zone.Transport)
-
- case _ =>
- }
+ final def ReadyToDelete: Receive = commonDeleteBehavior
+ .orElse {
+ case _ => ;
+ }
override protected def mountTest(
obj: PlanetSideServerObject with Mountable,
@@ -387,7 +369,6 @@ class VehicleControl(vehicle: Vehicle)
val events = zone.VehicleEvents
//miscellaneous changes
recoverFromEnvironmentInteracting()
- Vehicles.BeforeUnloadVehicle(vehicle, zone)
//escape being someone else's cargo
vehicle.MountedIn match {
case Some(_) =>
@@ -417,28 +398,11 @@ class VehicleControl(vehicle: Vehicle)
}
}
}
- //abandon all cargo
- vehicle.CargoHolds.values
- .collect {
- case hold if hold.isOccupied =>
- val cargo = hold.occupant.get
- CargoBehavior.HandleVehicleCargoDismount(
- cargo.GUID,
- cargo,
- guid,
- vehicle,
- bailed = false,
- requestedByPassenger = false,
- kicked = false
- )
- }
- }
+ }
def PrepareForDeletion() : Unit = {
decaying = false
val zone = vehicle.Zone
- //miscellaneous changes
- Vehicles.BeforeUnloadVehicle(vehicle, zone)
//cancel jammed behavior
CancelJammeredSound(vehicle)
CancelJammeredStatus(vehicle)
@@ -557,119 +521,6 @@ class VehicleControl(vehicle: Vehicle)
)
}
- override def TryDeploymentChange(obj: Deployment.DeploymentObject, state: DriveState.Value): Boolean = {
- VehicleControl.DeploymentAngleCheck(obj) && super.TryDeploymentChange(obj, state)
- }
-
- override def DeploymentAction(
- obj: DeploymentObject,
- state: DriveState.Value,
- prevState: DriveState.Value
- ): DriveState.Value = {
- val out = super.DeploymentAction(obj, state, prevState)
- obj match {
- case vehicle: Vehicle =>
- val guid = vehicle.GUID
- val zone = vehicle.Zone
- val zoneChannel = zone.id
- val GUID0 = Service.defaultPlayerGUID
- val driverChannel = vehicle.Seats(0).occupant match {
- case Some(tplayer) => tplayer.Name
- case None => ""
- }
- Vehicles.ReloadAccessPermissions(vehicle, vehicle.Faction.toString)
- //ams
- if (vehicle.Definition == GlobalDefinitions.ams) {
- val events = zone.VehicleEvents
- state match {
- case DriveState.Deployed =>
- events ! VehicleServiceMessage.AMSDeploymentChange(zone)
- events ! VehicleServiceMessage(driverChannel, VehicleAction.PlanetsideAttribute(GUID0, guid, 81, 1))
- case _ => ;
- }
- }
- //ant
- else if (vehicle.Definition == GlobalDefinitions.ant) {
- state match {
- case DriveState.Deployed =>
- // Start ntu regeneration
- // If vehicle sends UseItemMessage with silo as target NTU regeneration will be disabled and orb particles will be disabled
- context.system.scheduler.scheduleOnce(
- delay = 1000 milliseconds,
- vehicle.Actor,
- TransferBehavior.Charging(Ntu.Nanites)
- )
- case _ => ;
- }
- }
- //router
- else if (vehicle.Definition == GlobalDefinitions.router) {
- val events = zone.LocalEvents
- state match {
- case DriveState.Deploying =>
- vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
- case Some(util: Utility.InternalTelepad) => util.Actor ! TelepadLike.Activate(util)
- case _ => ;
- }
- case _ => ;
- }
- }
- case _ => ;
- }
- out
- }
-
- override def UndeploymentAction(
- obj: DeploymentObject,
- state: DriveState.Value,
- prevState: DriveState.Value
- ): DriveState.Value = {
- val out = if (decaying) state else super.UndeploymentAction(obj, state, prevState)
- obj match {
- case vehicle: Vehicle =>
- val guid = vehicle.GUID
- val zone = vehicle.Zone
- val GUID0 = Service.defaultPlayerGUID
- val driverChannel = vehicle.Seats(0).occupant match {
- case Some(tplayer) => tplayer.Name
- case None => ""
- }
- Vehicles.ReloadAccessPermissions(vehicle, vehicle.Faction.toString)
- //ams
- if (vehicle.Definition == GlobalDefinitions.ams) {
- val events = zone.VehicleEvents
- state match {
- case DriveState.Undeploying =>
- events ! VehicleServiceMessage.AMSDeploymentChange(zone)
- events ! VehicleServiceMessage(driverChannel, VehicleAction.PlanetsideAttribute(GUID0, guid, 81, 0))
- case _ => ;
- }
- }
- //ant
- else if (vehicle.Definition == GlobalDefinitions.ant) {
- state match {
- case DriveState.Undeploying =>
- TryStopChargingEvent(vehicle)
- case _ => ;
- }
- }
- //router
- else if (vehicle.Definition == GlobalDefinitions.router) {
- state match {
- case DriveState.Undeploying =>
- //deactivate internal router before trying to reset the system
- vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
- case Some(util: Utility.InternalTelepad) => util.Actor ! TelepadLike.Deactivate(util)
- case _ => ;
- }
- case _ => ;
- }
- }
- case _ => ;
- }
- out
- }
-
/**
* Water causes vehicles to become disabled if they dive off too far, too deep.
* Flying vehicles do not display progress towards being waterlogged. They just disable outright.
@@ -895,8 +746,4 @@ object VehicleControl {
case _ => false
}
}
-
- def DeploymentAngleCheck(obj: Deployment.DeploymentObject): Boolean = {
- obj.Orientation.x <= 30 || obj.Orientation.x >= 330
- }
}
diff --git a/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala b/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
index c07e827c7..ea474375b 100644
--- a/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
+++ b/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
@@ -1,11 +1,9 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.zones
-import akka.actor.{Actor, Props}
+import akka.actor.Actor
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.{Default, Vehicle}
-import net.psforever.objects.serverobject.PlanetSideServerObject
-import net.psforever.objects.vehicles.VehicleControl
import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer
@@ -42,8 +40,7 @@ class ZoneVehicleActor(zone: Zone, vehicleList: ListBuffer[Vehicle]) extends Act
} else {
vehicleList += vehicle
vehicle.Zone = zone
- vehicle.Actor =
- context.actorOf(Props(classOf[VehicleControl], vehicle), PlanetSideServerObject.UniqueActorName(vehicle))
+ vehicle.Definition.Initialize(vehicle, context)
}
if (vehicle.MountedIn.isEmpty) {
zone.actor ! ZoneActor.AddToBlockMap(vehicle, vehicle.Position)
@@ -54,8 +51,7 @@ class ZoneVehicleActor(zone: Zone, vehicleList: ListBuffer[Vehicle]) extends Act
ZoneVehicleActor.recursiveFindVehicle(vehicleList.iterator, vehicle) match {
case Some(index) =>
vehicleList.remove(index)
- context.stop(vehicle.Actor)
- vehicle.Actor = Default.Actor
+ vehicle.Definition.Uninitialize(vehicle, context)
zone.actor ! ZoneActor.RemoveFromBlockMap(vehicle)
sender() ! Zone.Vehicle.HasDespawned(zone, vehicle)
case None => ;
diff --git a/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala b/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
index f4951b633..a22fd9255 100644
--- a/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
+++ b/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
@@ -145,7 +145,7 @@ import scodec.codecs._
* `45 - NTU charge bar 0-10, 5 = 50% full. Seems to apply to both ANT and NTU Silo (possibly siphons?)`
* `46 - Sends "Generator damage is at a critical level!" message`
* `47 - Sets base NTU level to CRITICAL.`
- * `48 - Set to 1 to send base power loss message & turns on red warning lights throughout base.
+ * `48 - Set to 1 to send base power loss message & turns on red warning lights throughout base.`
* `49 - Vehicle texture effects state? (>0 turns on ANT panel glow or ntu silo panel glow + orbs) (bit?)`
* `52 - Vehicle particle effects? (>0 turns on orbs going towards ANT. Doesn't affect silo) (bit?)`
* `53 - LFS. Value is 1 to flag LFS`
@@ -191,6 +191,7 @@ import scodec.codecs._
* `21 - Declare a player the vehicle's owner, by globally unique identifier`
* `22 - Toggles gunner and passenger mount points (1 = hides, 0 = reveals; this also locks their permissions)`
* `54 - Plays jammed buzzing sound in vicinity of target`
+ * `55 - Trigger APC EMP`
* `68 - Vehicle shield health`
* `79 - ???`
* `80 - Damage vehicle (unknown value)`
diff --git a/src/test/scala/objects/DamageableTest.scala b/src/test/scala/objects/DamageableTest.scala
index 4809d1531..cd0ee5896 100644
--- a/src/test/scala/objects/DamageableTest.scala
+++ b/src/test/scala/objects/DamageableTest.scala
@@ -3,7 +3,8 @@ package objects
import akka.actor.Props
import akka.testkit.TestProbe
-import base.ActorTest
+import base.{ActorTest, FreedContextActorTest}
+import net.psforever.actors.zone.ZoneActor
import net.psforever.objects._
import net.psforever.objects.ballistics._
import net.psforever.objects.equipment.JammableUnit
@@ -15,7 +16,7 @@ import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects.serverobject.terminals.{Terminal, TerminalControl, TerminalDefinition}
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.serverobject.turret.{FacilityTurret, FacilityTurretControl, TurretUpgrade}
-import net.psforever.objects.vehicles.VehicleControl
+import net.psforever.objects.vehicles.control.VehicleControl
import net.psforever.objects.vital.Vitality
import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game.DamageWithPositionMessage
@@ -1222,7 +1223,7 @@ class DamageableVehicleDamageTest extends ActorTest {
}
}
-class DamageableVehicleDamageMountedTest extends ActorTest {
+class DamageableVehicleDamageMountedTest extends FreedContextActorTest {
val guid = new NumberPoolHub(new MaxNumberSource(15))
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
@@ -1234,6 +1235,8 @@ class DamageableVehicleDamageMountedTest extends ActorTest {
zone.Activity = activityProbe.ref
zone.AvatarEvents = avatarProbe.ref
zone.VehicleEvents = vehicleProbe.ref
+ import akka.actor.typed.scaladsl.adapter._
+ zone.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
val lodestar = Vehicle(GlobalDefinitions.lodestar) //guid=1 & 4,5,6,7,8,9
lodestar.Position = Vector3(1, 0, 0)
@@ -1271,7 +1274,7 @@ class DamageableVehicleDamageMountedTest extends ActorTest {
guid.register(atv, 11)
//the lodestar control actor needs to load after the utilities have guid's assigned
- lodestar.Actor = system.actorOf(Props(classOf[VehicleControl], lodestar), "lodestar-control")
+ lodestar.Definition.Initialize(lodestar, context)
lodestar.Zone = zone
lodestar.Seats(0).mount(player2)
player2.VehicleSeated = lodestar.GUID
@@ -1314,50 +1317,38 @@ class DamageableVehicleDamageMountedTest extends ActorTest {
assert(atv.Shields == 1)
lodestar.Actor ! Vitality.Damage(applyDamageTo)
- val msg12 = vehicleProbe.receiveN(2, 200 milliseconds)
- val msg3 = activityProbe.receiveOne(200 milliseconds)
- val msg45 = avatarProbe.receiveN(2,200 milliseconds)
- assert(
- msg12.head match {
- case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _)) => true
- case _ => false
- }
- )
- assert(
- msg12(1) match {
- case VehicleServiceMessage("test", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 0, _)) => true
- case _ => false
- }
- )
- assert(
- msg3 match {
- case activity: Zone.HotSpot.Activity =>
- activity.attacker == pSource &&
- activity.defender == SourceEntry(lodestar) &&
- activity.location == Vector3(1, 0, 0)
- case _ => false
- }
- )
- assert(
- msg45.head match {
- case AvatarServiceMessage(
- "TestCharacter2",
- AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(400, Vector3(2, 0, 0)))
- ) =>
- true
- case _ => false
- }
- )
- assert(
- msg45(1) match {
- case AvatarServiceMessage(
- "TestCharacter3",
- AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(0, Vector3(2, 0, 0)))
- ) =>
- true
- case _ => false
- }
- )
+ val msg12 = vehicleProbe.receiveN(2, 500 milliseconds)
+ val msg3 = activityProbe.receiveOne(500 milliseconds)
+ val msg45 = avatarProbe.receiveN(2,500 milliseconds)
+ msg12.head match {
+ case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _)) => ;
+ case _ => assert(false)
+ }
+ msg12(1) match {
+ case VehicleServiceMessage("test", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 0, _)) => ;
+ case _ => assert(false)
+ }
+ msg3 match {
+ case activity: Zone.HotSpot.Activity =>
+ assert(activity.attacker == pSource &&
+ activity.defender == SourceEntry(lodestar) &&
+ activity.location == Vector3(1, 0, 0))
+ case _ => assert(false)
+ }
+ msg45.head match {
+ case AvatarServiceMessage(
+ "TestCharacter2",
+ AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(400, Vector3(2, 0, 0)))
+ ) => ;
+ case _ => assert(false)
+ }
+ msg45(1) match {
+ case AvatarServiceMessage(
+ "TestCharacter3",
+ AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(0, Vector3(2, 0, 0)))
+ ) => ;
+ case _ => assert(false)
+ }
assert(lodestar.Health < lodestar.Definition.DefaultHealth)
assert(lodestar.Shields == 0)
assert(atv.Health == atv.Definition.DefaultHealth)
@@ -1365,7 +1356,7 @@ class DamageableVehicleDamageMountedTest extends ActorTest {
}
}
-class DamageableVehicleJammeringMountedTest extends ActorTest {
+class DamageableVehicleJammeringMountedTest extends FreedContextActorTest {
val guid = new NumberPoolHub(new MaxNumberSource(15))
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
@@ -1417,7 +1408,7 @@ class DamageableVehicleJammeringMountedTest extends ActorTest {
guid.register(player2, 12)
guid.register(player3, 13)
- lodestar.Actor = system.actorOf(Props(classOf[VehicleControl], lodestar), "lodestar-control")
+ lodestar.Definition.Initialize(lodestar, context)
atv.Zone = zone
lodestar.Zone = zone
atv.Seats(0).mount(player2)
@@ -1585,7 +1576,7 @@ class DamageableVehicleDestroyTest extends ActorTest {
}
}
-class DamageableVehicleDestroyMountedTest extends ActorTest {
+class DamageableVehicleDestroyMountedTest extends FreedContextActorTest {
val atv = Vehicle(GlobalDefinitions.quadassault) //guid=1
atv.Actor = system.actorOf(Props(classOf[VehicleControl], atv), "atv-control")
atv.Position = Vector3(1, 0, 0)
@@ -1611,19 +1602,22 @@ class DamageableVehicleDestroyMountedTest extends ActorTest {
val player3Probe = TestProbe()
player3.Actor = player3Probe.ref
-
+ val activityProbe = TestProbe()
+ val avatarProbe = TestProbe()
+ val vehicleProbe = TestProbe()
+ val catchall = TestProbe()
val guid = new NumberPoolHub(new MaxNumberSource(15))
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
override def LivePlayers = List(player1, player2, player3)
+ override def Activity = activityProbe.ref
+ override def AvatarEvents = avatarProbe.ref
+ override def VehicleEvents = vehicleProbe.ref
+ override def tasks = catchall.ref
+ import akka.actor.typed.scaladsl.adapter._
+ this.actor = catchall.ref.toTyped[ZoneActor.Command]
}
- val activityProbe = TestProbe()
- val avatarProbe = TestProbe()
- val vehicleProbe = TestProbe()
- zone.Activity = activityProbe.ref
- zone.AvatarEvents = avatarProbe.ref
- zone.VehicleEvents = vehicleProbe.ref
guid.register(atv, 1)
guid.register(atvWeapon, 2)
@@ -1639,7 +1633,7 @@ class DamageableVehicleDestroyMountedTest extends ActorTest {
guid.register(player2, 12)
guid.register(player3, 13)
- lodestar.Actor = system.actorOf(Props(classOf[VehicleControl], lodestar), "lodestar-control")
+ lodestar.Definition.Initialize(lodestar, context)
atv.Zone = zone
lodestar.Zone = zone
atv.Seats(0).mount(player2)
diff --git a/src/test/scala/objects/RepairableTest.scala b/src/test/scala/objects/RepairableTest.scala
index 83cde24a1..3d2c6fc38 100644
--- a/src/test/scala/objects/RepairableTest.scala
+++ b/src/test/scala/objects/RepairableTest.scala
@@ -13,7 +13,7 @@ import net.psforever.objects.serverobject.generator.{Generator, GeneratorControl
import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects.serverobject.terminals.{Terminal, TerminalControl}
import net.psforever.objects.serverobject.turret.{FacilityTurret, FacilityTurretControl}
-import net.psforever.objects.vehicles.VehicleControl
+import net.psforever.objects.vehicles.control.VehicleControl
import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game.{InventoryStateMessage, RepairMessage}
import net.psforever.types._
diff --git a/src/test/scala/objects/TelepadRouterTest.scala b/src/test/scala/objects/TelepadRouterTest.scala
index b95c04af2..84f1b5a55 100644
--- a/src/test/scala/objects/TelepadRouterTest.scala
+++ b/src/test/scala/objects/TelepadRouterTest.scala
@@ -4,14 +4,14 @@ package objects
import akka.actor.{ActorRef, Props}
import akka.actor.typed.scaladsl.adapter._
import akka.testkit.TestProbe
-import base.ActorTest
+import base.{ActorTest, FreedContextActorTest}
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects._
import net.psforever.objects.ce.{DeployableCategory, DeployedItem, TelepadLike}
import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.deploy.Deployment
-import net.psforever.objects.vehicles.{Utility, UtilityType, VehicleControl}
+import net.psforever.objects.vehicles.{Utility, UtilityType}
import net.psforever.objects.zones.{Zone, ZoneDeployableActor, ZoneMap}
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
import net.psforever.packet.game._
@@ -214,7 +214,7 @@ class TelepadDeployableAttemptTest extends ActorTest {
}
}
-class TelepadDeployableResponseFromRouterTest extends ActorTest {
+class TelepadDeployableResponseFromRouterTest extends FreedContextActorTest {
val eventsProbe = new TestProbe(system)
val telepad = new TelepadDeployable(TelepadRouterTest.router_telepad_deployable) //guid=1
val router = Vehicle(GlobalDefinitions.router) //guid=2
@@ -242,17 +242,20 @@ class TelepadDeployableResponseFromRouterTest extends ActorTest {
guid.register(internal, number = 3)
guid.register(router.Utility(UtilityType.teleportpad_terminal).get, number = 4) //necessary
router.Zone = zone
- router.Actor = system.actorOf(Props(classOf[VehicleControl], router), "test-router")
+ router.Definition.Initialize(router, context)
telepad.Router = PlanetSideGUID(2) //artificial
"TelepadDeployable" should {
"link with a connected router" in {
assert(!telepad.Active, "link to router test - telepad active earlier than intended (1)")
assert(!internal.Active, "link to router test - router internals active earlier than intended")
- router.Actor.tell(Deployment.TryDeploy(DriveState.Deploying), new TestProbe(system).ref)
+ val deploymentProbe = new TestProbe(system)
+ router.Actor.tell(Deployment.TryDeploy(DriveState.Deploying), deploymentProbe.ref)
eventsProbe.receiveN(10, 10.seconds) //flush all messages related to deployment
+ deploymentProbe.receiveOne(2.seconds) //CanDeploy
+ deploymentProbe.expectNoMessage(2.seconds) //intentional delay
+ assert(internal.Active, "link to router test - router internals not active when expected")
assert(!telepad.Active, "link to router test - telepad active earlier than intended (2)")
- assert(internal.Active, "link to router test - router internals active not active when expected")
assert(deployableList.isEmpty, "link to router test - deployable list is not empty")
zone.Deployables ! Zone.Deployable.Build(telepad)
diff --git a/src/test/scala/objects/VehicleControlTest.scala b/src/test/scala/objects/VehicleControlTest.scala
index 8ce6a880f..8379868ff 100644
--- a/src/test/scala/objects/VehicleControlTest.scala
+++ b/src/test/scala/objects/VehicleControlTest.scala
@@ -7,15 +7,17 @@ import akka.testkit.TestProbe
import base.{ActorTest, FreedContextActorTest}
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.avatar.{Avatar, PlayerControl}
-import net.psforever.objects.{GlobalDefinitions, Player, Vehicle}
+import net.psforever.objects.ce.DeployedItem
+import net.psforever.objects._
import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.environment._
import net.psforever.objects.serverobject.mount.Mountable
-import net.psforever.objects.vehicles.{VehicleControl, VehicleLockState}
-import net.psforever.objects.vital.VehicleShieldCharge
+import net.psforever.objects.vehicles.VehicleLockState
+import net.psforever.objects.vehicles.control.VehicleControl
+import net.psforever.objects.vital.{VehicleShieldCharge, Vitality}
import net.psforever.objects.zones.{Zone, ZoneMap}
-import net.psforever.packet.game.{CargoMountPointStatusMessage, ObjectDetachMessage, PlanetsideAttributeMessage}
+import net.psforever.packet.game._
import net.psforever.services.ServiceManager
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
@@ -201,12 +203,16 @@ class VehicleControlPrepareForDeletionMountedInTest extends FreedContextActorTes
}
class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActorTest {
+ val vehicleProbe = new TestProbe(system)
+ val catchall = new TestProbe(system)
val guid = new NumberPoolHub(new MaxNumberSource(10))
ServiceManager.boot
val zone = new Zone("test", new ZoneMap("test"), 0) {
GUID(guid)
override def SetupNumberPools(): Unit = {}
+ override def VehicleEvents = vehicleProbe.ref
+ override def tasks = catchall.ref
}
zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
// crappy workaround but without it the zone doesn't get initialized in time
@@ -219,6 +225,7 @@ class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActor
vehicle.Actor = cargoProbe.ref
val lodestar = Vehicle(GlobalDefinitions.lodestar)
lodestar.Faction = PlanetSideEmpire.TR
+ lodestar.Zone = zone
val player1 = Player(VehicleTest.avatar1) //name="test1"
val player2 = Player(VehicleTest.avatar2) //name="test2"
@@ -237,85 +244,64 @@ class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActor
player2.VehicleSeated = lodestar.GUID
lodestar.CargoHolds(1).mount(vehicle)
vehicle.MountedIn = lodestar.GUID
-
- val vehicleProbe = new TestProbe(system)
- zone.VehicleEvents = vehicleProbe.ref
- zone.Transport ! Zone.Vehicle.Spawn(lodestar) //can not fake this
+ lodestar.Definition.Initialize(lodestar, context)
"VehicleControl" should {
"if with mounted cargo, eject it when marked for deconstruction" in {
lodestar.Actor ! Vehicle.Deconstruct()
val vehicle_msg = vehicleProbe.receiveN(6, 500 milliseconds)
- assert(
- vehicle_msg.head match {
- case VehicleServiceMessage(
+ vehicle_msg(5) match {
+ case VehicleServiceMessage(
"test",
VehicleAction.KickPassenger(PlanetSideGUID(4), 4, false, PlanetSideGUID(2))
- ) =>
- true
- case _ => false
- }
- )
+ ) => ;
+ case _ => assert(false)
+ }
assert(player2.VehicleSeated.isEmpty)
assert(lodestar.Seats(0).occupant.isEmpty)
//cargo dismounting messages
- assert(
- vehicle_msg(1) match {
- case VehicleServiceMessage(
+ vehicle_msg.head match {
+ case VehicleServiceMessage(
_,
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))
- ) =>
- true
- case _ => false
- }
- )
- assert(
- vehicle_msg(2) match {
- case VehicleServiceMessage(
+ ) => ;
+ case _ => assert(false)
+ }
+ vehicle_msg(1) match {
+ case VehicleServiceMessage(
_,
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))
- ) =>
- true
- case _ => false
- }
- )
- assert(
- vehicle_msg(3) match {
- case VehicleServiceMessage(
+ ) => ;
+ case _ => assert(false)
+ }
+ vehicle_msg(2) match {
+ case VehicleServiceMessage(
"test",
VehicleAction.SendResponse(
- _,
- CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0)
+ _,
+ CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0)
)
- ) =>
- true
- case _ => false
- }
- )
- assert(
- vehicle_msg(4) match {
- case VehicleServiceMessage(
+ ) => ;
+ case _ => assert(false)
+ }
+ vehicle_msg(3) match {
+ case VehicleServiceMessage(
"test",
VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))
- ) =>
- true
- case _ => false
- }
- )
- assert(
- vehicle_msg(5) match {
- case VehicleServiceMessage(
+ ) => ;
+ case _ => assert(false)
+ }
+ vehicle_msg(4) match {
+ case VehicleServiceMessage(
"test",
VehicleAction.SendResponse(
- _,
- CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0)
+ _,
+ CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0)
)
- ) =>
- true
- case _ => false
- }
- )
+ ) => ;
+ case _ => assert(false)
+ }
}
}
}
@@ -945,6 +931,142 @@ class VehicleControlInteractWithDeathTest extends ActorTest {
}
}
+class ApcControlCanChargeCapacitor extends FreedContextActorTest {
+ val apc = Vehicle(GlobalDefinitions.apc_tr) //guid=1, weapons not registered
+
+ val guid = new NumberPoolHub(new MaxNumberSource(max = 5))
+ val localProbe = TestProbe()
+ val vehicleProbe = TestProbe()
+ val catchall = TestProbe()
+ val zone = new Zone(id = "test-zone", new ZoneMap(name = "test-map"), zoneNumber = 0) {
+ override def SetupNumberPools(): Unit = {}
+ GUID(guid)
+ override def Vehicles = List(apc)
+ override def VehicleEvents = vehicleProbe.ref
+ override def LocalEvents = localProbe.ref
+ override def AvatarEvents = catchall.ref
+ override def Activity = catchall.ref
+ }
+
+ guid.register(apc, number = 1)
+ apc.Faction = PlanetSideEmpire.VS
+ apc.Zone = zone
+ //apc.Definition.Initialize(apc, context) //do later ...
+ zone.blockMap.addTo(apc)
+
+ val maxCapacitor = apc.Definition.MaxCapacitor
+
+ "ApcControl" should {
+ "charge its capacitors when initialized" in {
+ assert(apc.Capacitor == 0)
+ apc.Definition.Initialize(apc, context)
+ do {
+ val msg = vehicleProbe.receiveOne(3.seconds)
+ msg match {
+ case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(_, PlanetSideGUID(1), 113, capacitance)) =>
+ assert(capacitance > 0)
+ case _ =>
+ assert(false)
+ }
+ }
+ while(apc.Capacitor < maxCapacitor)
+ vehicleProbe.expectNoMessage(5.seconds)
+ assert(apc.Capacitor == maxCapacitor)
+ }
+ }
+}
+
+class ApcControlCanEmp extends FreedContextActorTest {
+ val apc = Vehicle(GlobalDefinitions.apc_vs) //guid=1, weapons not registered
+ val fury = Vehicle(GlobalDefinitions.fury) //guid=2, weapons not registered
+ val boomer = Deployables.Make(DeployedItem.boomer)() //guid=3, no trigger
+ val boomer2 = Deployables.Make(DeployedItem.boomer)() //guid=4, no trigger
+
+ val guid = new NumberPoolHub(new MaxNumberSource(max = 5))
+ val localProbe = TestProbe()
+ val vehicleProbe = TestProbe()
+ val catchall = TestProbe()
+ val zone = new Zone(id = "test-zone", new ZoneMap(name = "test-map"), zoneNumber = 0) {
+ override def SetupNumberPools(): Unit = {}
+ GUID(guid)
+ override def Vehicles = List(apc, fury)
+ override def DeployableList = List(boomer, boomer2)
+ override def VehicleEvents = vehicleProbe.ref
+ override def LocalEvents = localProbe.ref
+ override def AvatarEvents = catchall.ref
+ override def Activity = catchall.ref
+ }
+
+ guid.register(apc, number = 1)
+ apc.Faction = PlanetSideEmpire.VS
+ apc.Zone = zone
+ apc.Capacitor = apc.Definition.MaxCapacitor
+ apc.Definition.Initialize(apc, context)
+ zone.blockMap.addTo(apc)
+
+ val furyProbe = TestProbe()
+ guid.register(fury, number = 2)
+ fury.Position = Vector3(4, 0, 0) //within 15m of apc
+ fury.Faction = PlanetSideEmpire.TR
+ fury.Zone = zone
+ fury.Actor = furyProbe.ref
+ zone.blockMap.addTo(fury)
+
+ val boomerProbe = TestProbe()
+ guid.register(boomer, number = 3)
+ boomer.Position = Vector3(0, 14, 0) //within 15m of apc
+ boomer.Faction = PlanetSideEmpire.TR
+ boomer.Zone = zone
+ boomer.Actor = boomerProbe.ref
+ zone.blockMap.addTo(boomer)
+
+ val boomer2Probe = TestProbe()
+ guid.register(boomer2, number = 4)
+ boomer2.Position = Vector3(0, 30, 0) //beyond 15m of apc
+ boomer2.Faction = PlanetSideEmpire.TR
+ boomer2.Zone = zone
+ boomer2.Actor = boomer2Probe.ref
+ zone.blockMap.addTo(boomer2)
+
+ "ApcControl" should {
+ "charge its capacitors when initialized" in {
+ assert(apc.Capacitor == apc.Definition.MaxCapacitor)
+ apc.Definition.Initialize(apc, context)
+ vehicleProbe.expectNoMessage(5.seconds) //the capacitor is max, so no charging is needed
+
+ apc.Actor ! SpecialEmp.Burst()
+ val vehicleMsgs = vehicleProbe.receiveN(2, 500.milliseconds)
+ vehicleMsgs.head match {
+ case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(_, PlanetSideGUID(1), 113, 0)) => ;
+ case _ => assert(false)
+ }
+ vehicleMsgs(1) match {
+ case VehicleServiceMessage(
+ "test-zone",
+ VehicleAction.SendResponse(
+ _,
+ TriggerEffectMessage(_, "apc_explosion_emp_vs", None, Some(TriggeredEffectLocation(Vector3.Zero, Vector3.Zero)))
+ )
+ ) => ;
+ case _ => assert(false)
+ }
+ assert(apc.Capacitor == 0)
+
+ val furyMsg = furyProbe.receiveOne(200.milliseconds)
+ furyMsg match {
+ case Vitality.Damage(_) => ;
+ case _ => assert(false)
+ }
+ val boomerMsg = boomerProbe.receiveOne(200.milliseconds)
+ boomerMsg match {
+ case Vitality.Damage(_) => ;
+ case _ => assert(false)
+ }
+ boomer2Probe.expectNoMessage(400.milliseconds) //out of range
+ }
+ }
+}
+
object VehicleControlTest {
import net.psforever.objects.avatar.Avatar
import net.psforever.types.{CharacterSex, PlanetSideEmpire}
diff --git a/src/test/scala/service/LocalServiceTest.scala b/src/test/scala/service/LocalServiceTest.scala
index 774cc2d49..7c96d6ed1 100644
--- a/src/test/scala/service/LocalServiceTest.scala
+++ b/src/test/scala/service/LocalServiceTest.scala
@@ -7,7 +7,7 @@ import base.{ActorTest, FreedContextActorTest}
import net.psforever.objects.{GlobalDefinitions, SensorDeployable, Vehicle}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.terminals.{ProximityTerminal, Terminal}
-import net.psforever.objects.vehicles.VehicleControl
+import net.psforever.objects.vehicles.control.VehicleControl
import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game._
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
diff --git a/src/test/scala/service/VehicleServiceTest.scala b/src/test/scala/service/VehicleServiceTest.scala
index 63deec93a..691780b58 100644
--- a/src/test/scala/service/VehicleServiceTest.scala
+++ b/src/test/scala/service/VehicleServiceTest.scala
@@ -4,7 +4,7 @@ package service
import akka.actor.Props
import base.ActorTest
import net.psforever.objects._
-import net.psforever.objects.vehicles.VehicleControl
+import net.psforever.objects.vehicles.control.VehicleControl
import net.psforever.objects.zones.Zone
import net.psforever.types.{PlanetSideGUID, _}
import net.psforever.services.{Service, ServiceManager}