diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala
index 6ad779d7..c5c1ed9a 100644
--- a/common/src/main/scala/net/psforever/objects/Vehicle.scala
+++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala
@@ -16,6 +16,7 @@ import net.psforever.objects.vital.{DamageResistanceModel, StandardResistancePro
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID}
import scala.annotation.tailrec
+import scala.concurrent.duration.FiniteDuration
/**
* The server-side support object that represents a vehicle.
@@ -577,11 +578,12 @@ object Vehicle {
final case class VehicleMessages(player : Player, response : Exchange)
/**
- * The `Vehicle` will become unresponsive to player activity.
- * Usually, it does this to await deconstruction and clean-up.
+ * Initiate vehicle deconstruction.
* @see `VehicleControl`
+ * @param time the delay before deconstruction should initiate;
+ * should initiate instantly when `None`
*/
- final case class PrepareForDeletion()
+ final case class Deconstruct(time : Option[FiniteDuration] = None)
/**
* The `Vehicle` will resume previous unresponsiveness to player activity.
diff --git a/common/src/main/scala/net/psforever/objects/Vehicles.scala b/common/src/main/scala/net/psforever/objects/Vehicles.scala
index 8a9ca35e..4725ed27 100644
--- a/common/src/main/scala/net/psforever/objects/Vehicles.scala
+++ b/common/src/main/scala/net/psforever/objects/Vehicles.scala
@@ -6,7 +6,7 @@ import net.psforever.objects.vehicles.{CargoBehavior, VehicleLockState}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.TriggeredSound
import net.psforever.types.{DriveState, PlanetSideGUID}
-import services.{RemoverActor, Service}
+import services.Service
import services.avatar.{AvatarAction, AvatarServiceMessage}
import services.local.{LocalAction, LocalServiceMessage}
import services.vehicle.{VehicleAction, VehicleServiceMessage}
@@ -191,10 +191,10 @@ object Vehicles {
case Some(cargo : Vehicle) => {
cargo.Seats(0).Occupant match {
case Some(cargoDriver: Player) =>
- CargoBehavior.HandleVehicleCargoDismount(target.Zone, cargoDriver.GUID, cargo.GUID, bailed = target.Flying, requestedByPassenger = false, kicked = true )
+ CargoBehavior.HandleVehicleCargoDismount(target.Zone, cargo.GUID, bailed = target.Flying, requestedByPassenger = false, kicked = true )
case None =>
log.error("FinishHackingVehicle: vehicle in cargo hold missing driver")
- CargoBehavior.HandleVehicleCargoDismount(hacker.GUID, cargo.GUID, cargo, target.GUID, target, false, false, true)
+ CargoBehavior.HandleVehicleCargoDismount(cargo.GUID, cargo, target.GUID, target, false, false, true)
}
}
case None => ;
@@ -215,8 +215,7 @@ object Vehicles {
// If the vehicle can fly and is flying deconstruct it, and well played to whomever managed to hack a plane in mid air. I'm impressed.
if(target.Definition.CanFly && target.Flying) {
// todo: Should this force the vehicle to land in the same way as when a pilot bails with passengers on board?
- zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(target), zone))
- zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(target, zone, Some(0 seconds)))
+ target.Actor ! Vehicle.Deconstruct()
} else { // Otherwise handle ownership transfer as normal
// Remove ownership of our current vehicle, if we have one
hacker.VehicleOwned match {
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala b/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala
index dce43d36..c0b6f2ff 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala
@@ -8,7 +8,7 @@ import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.vital.resolution.ResolutionCalculations
import net.psforever.types.{DriveState, PlanetSideGUID}
-import services.{RemoverActor, Service}
+import services.Service
import services.local.{LocalAction, LocalServiceMessage}
import services.vehicle.{VehicleAction, VehicleService, VehicleServiceMessage}
@@ -180,8 +180,7 @@ object DamageableVehicle {
target.Shields = 0
zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, 0))
}
- zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(target), zone))
- zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(target, zone, Some(1 minute)))
+ target.Actor ! Vehicle.Deconstruct(Some(1 minute))
target.ClearHistory()
}
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala
index 574c9d31..13733f01 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala
@@ -6,6 +6,8 @@ import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffi
import net.psforever.objects.serverobject.pad.process.{VehicleSpawnControlBase, VehicleSpawnControlConcealPlayer}
import net.psforever.objects.zones.Zone
import net.psforever.objects.{DefaultCancellable, Player, Vehicle}
+import services.RemoverActor
+import services.vehicle.VehicleServiceMessage
import scala.annotation.tailrec
import scala.concurrent.ExecutionContext.Implicits.global
@@ -64,9 +66,7 @@ class VehicleSpawnControl(pad : VehicleSpawnPad) extends VehicleSpawnControlBase
handleOrderFunc(VehicleSpawnControl.Order(player, vehicle))
}
catch {
- case _ : AssertionError if vehicle.HasGUID => //same as order being dropped
- VehicleSpawnControl.DisposeSpawnedVehicle(vehicle, pad.Zone)
- case _ : AssertionError => ; //shrug
+ case _ : AssertionError => ; //ehhh
case e : Exception => //something unexpected
e.printStackTrace()
}
@@ -143,7 +143,7 @@ class VehicleSpawnControl(pad : VehicleSpawnPad) extends VehicleSpawnControlBase
pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(name, VehicleSpawnPad.Reminders.Queue, Some(orders.length + 1))
}
else {
- VehicleSpawnControl.DisposeSpawnedVehicle(order, pad.Zone)
+ VehicleSpawnControl.DisposeVehicle(order.vehicle, pad.Zone)
}
}
@@ -282,19 +282,24 @@ object VehicleSpawnControl {
* Properly clean up a vehicle that has been registered and spawned into the game world.
* Call this downstream of "`ConcealPlayer`".
* @param entry the order being cancelled
- * @param zone the continent on which the vehicle was registered
+ * @param zone the zone in which the vehicle is registered (should be located)
*/
def DisposeSpawnedVehicle(entry : VehicleSpawnControl.Order, zone: Zone) : Unit = {
- DisposeSpawnedVehicle(entry.vehicle, zone)
+ DisposeVehicle(entry.vehicle, zone)
zone.VehicleEvents ! VehicleSpawnPad.RevealPlayer(entry.DriverGUID)
}
/**
* Properly clean up a vehicle that has been registered and spawned into the game world.
- * @param vehicle the vehicle being cancelled
- * @param zone the continent on which the vehicle was registered
+ * @param vehicle the vehicle being disposed
+ * @param zone the zone in which the vehicle is registered (should be located)
*/
- def DisposeSpawnedVehicle(vehicle : Vehicle, zone: Zone) : Unit = {
- zone.VehicleEvents ! VehicleSpawnPad.DisposeVehicle(vehicle)
+ def DisposeVehicle(vehicle : Vehicle, zone : Zone) : Unit = {
+ if(zone.Vehicles.exists(_.GUID == vehicle.GUID)) { //already added to zone
+ vehicle.Actor ! Vehicle.Deconstruct()
+ }
+ else { //just registered to zone
+ zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, zone, Some(0 seconds)))
+ }
}
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlConcealPlayer.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlConcealPlayer.scala
index 3663d8e6..fe5350c9 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlConcealPlayer.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlConcealPlayer.scala
@@ -33,7 +33,7 @@ class VehicleSpawnControlConcealPlayer(pad : VehicleSpawnPad) extends VehicleSpa
}
else {
trace(s"integral component lost; abort order fulfillment")
- VehicleSpawnControl.DisposeSpawnedVehicle(order.vehicle, pad.Zone)
+ VehicleSpawnControl.DisposeVehicle(order.vehicle, pad.Zone)
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlDriverControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlDriverControl.scala
index edc0ba13..1fa79b0d 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlDriverControl.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlDriverControl.scala
@@ -21,15 +21,12 @@ class VehicleSpawnControlDriverControl(pad : VehicleSpawnPad) extends VehicleSpa
def receive : Receive = {
case order @ VehicleSpawnControl.Order(driver, vehicle) =>
- if(vehicle.Health == 0) {
- trace(s"vehicle was already destroyed; but, everything is fine")
- }
- if(vehicle.PassengerInSeat(driver).contains(0)) {
+ if(vehicle.Health > 0 && vehicle.PassengerInSeat(driver).contains(0)) {
trace(s"returning control of ${vehicle.Definition.Name} to ${driver.Name}")
pad.Zone.VehicleEvents ! VehicleSpawnPad.ServerVehicleOverrideEnd(driver.Name, vehicle, pad)
}
else {
- trace(s"${driver.Name} is not seated in ${vehicle.Definition.Name}; vehicle controls have been locked")
+ trace(s"${driver.Name} is not seated in ${vehicle.Definition.Name}; vehicle controls might have been locked")
}
finalClear ! order
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlLoadVehicle.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlLoadVehicle.scala
index df8a5505..a2ec9212 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlLoadVehicle.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlLoadVehicle.scala
@@ -4,6 +4,7 @@ package net.psforever.objects.serverobject.pad.process
import akka.actor.Props
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
+import net.psforever.objects.zones.Zone
import net.psforever.types.Vector3
import scala.concurrent.ExecutionContext.Implicits.global
@@ -36,7 +37,7 @@ class VehicleSpawnControlLoadVehicle(pad : VehicleSpawnPad) extends VehicleSpawn
}
else {
trace("owner lost or vehicle in poor condition; abort order fulfillment")
- VehicleSpawnControl.DisposeSpawnedVehicle(order, pad.Zone)
+ VehicleSpawnControl.DisposeVehicle(order.vehicle, pad.Zone)
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala
index 7a0e58d0..b58243ea 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala
@@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.pad.process
import akka.actor.{ActorRef, Props}
+import net.psforever.objects.Vehicle
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
import scala.concurrent.ExecutionContext.Implicits.global
@@ -38,9 +39,12 @@ class VehicleSpawnControlSeatDriver(pad : VehicleSpawnPad) extends VehicleSpawnC
case VehicleSpawnControlSeatDriver.BeginDriverInSeat(entry) =>
val driver = entry.driver
- if(entry.vehicle.Health > 0 && driver.isAlive && driver.Continent == pad.Continent && driver.VehicleSeated.isEmpty) {
+ val vehicle = entry.vehicle
+ //avoid unattended vehicle blocking the pad; user should mount (and does so normally) to reset decon timer
+ vehicle.Actor ! Vehicle.Deconstruct(Some(30 seconds))
+ if(vehicle.Health > 0 && driver.isAlive && driver.Continent == pad.Continent && driver.VehicleSeated.isEmpty) {
trace("driver to be made seated in vehicle")
- pad.Zone.VehicleEvents ! VehicleSpawnPad.StartPlayerSeatedInVehicle(entry.driver.Name, entry.vehicle, pad)
+ pad.Zone.VehicleEvents ! VehicleSpawnPad.StartPlayerSeatedInVehicle(driver.Name, vehicle, pad)
}
else{
trace("driver lost; vehicle stranded on pad")
@@ -48,7 +52,7 @@ class VehicleSpawnControlSeatDriver(pad : VehicleSpawnPad) extends VehicleSpawnC
context.system.scheduler.scheduleOnce(2500 milliseconds, self, VehicleSpawnControlSeatDriver.DriverInSeat(entry))
case VehicleSpawnControlSeatDriver.DriverInSeat(entry) =>
- if(entry.driver.isAlive && entry.vehicle.PassengerInSeat(entry.driver).contains(0)) {
+ if(entry.vehicle.Health > 0 && entry.driver.isAlive && entry.vehicle.PassengerInSeat(entry.driver).contains(0)) {
trace(s"driver ${entry.driver.Name} has taken the wheel")
pad.Zone.VehicleEvents ! VehicleSpawnPad.PlayerSeatedInVehicle(entry.driver.Name, entry.vehicle, pad)
}
diff --git a/common/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala b/common/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala
index 689ffff6..5f7c9795 100644
--- a/common/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala
+++ b/common/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala
@@ -8,7 +8,7 @@ import net.psforever.objects.vehicles.CargoBehavior.{CheckCargoDismount, CheckCa
import net.psforever.packet.game.{CargoMountPointStatusMessage, ObjectAttachMessage, ObjectDetachMessage, PlanetsideAttributeMessage}
import net.psforever.types.{CargoStatus, PlanetSideGUID, Vector3}
import services.avatar.{AvatarAction, AvatarServiceMessage}
-import services.{RemoverActor, Service}
+import services.Service
import services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.duration._
@@ -231,18 +231,18 @@ object CargoBehavior {
/**
* na
- * @param player_guid na
+ * @param zone na
* @param cargo_guid na
* @param bailed na
* @param requestedByPassenger na
* @param kicked na
*/
- def HandleVehicleCargoDismount(zone : Zone, player_guid : PlanetSideGUID, cargo_guid : PlanetSideGUID, bailed : Boolean, requestedByPassenger : Boolean, kicked : Boolean) : Unit = {
+ def HandleVehicleCargoDismount(zone : Zone, cargo_guid : PlanetSideGUID, bailed : Boolean, requestedByPassenger : Boolean, kicked : Boolean) : Unit = {
zone.GUID(cargo_guid) match {
case Some(cargo : Vehicle) =>
zone.GUID(cargo.MountedIn) match {
case Some(ferry : Vehicle) =>
- HandleVehicleCargoDismount(player_guid, cargo_guid, cargo, ferry.GUID, ferry, bailed, requestedByPassenger, kicked)
+ HandleVehicleCargoDismount(cargo_guid, cargo, ferry.GUID, ferry, bailed, requestedByPassenger, kicked)
case _ =>
log.warn(s"DismountVehicleCargo: target ${cargo.Definition.Name}@$cargo_guid does not know what treats it as cargo")
}
@@ -253,7 +253,6 @@ object CargoBehavior {
/**
* na
- * @param player_guid the target player
* @param cargoGUID the globally unique number for the vehicle being ferried
* @param cargo the vehicle being ferried
* @param carrierGUID the globally unique number for the vehicle doing the ferrying
@@ -262,7 +261,7 @@ object CargoBehavior {
* @param requestedByPassenger the ferried vehicle is being politely disembarked from the cargo hold
* @param kicked the ferried vehicle is being kicked out of the cargo hold
*/
- def HandleVehicleCargoDismount(player_guid : PlanetSideGUID, cargoGUID : PlanetSideGUID, cargo : Vehicle, carrierGUID : PlanetSideGUID, carrier : Vehicle, bailed : Boolean, requestedByPassenger : Boolean, kicked : Boolean) : Unit = {
+ def HandleVehicleCargoDismount(cargoGUID : PlanetSideGUID, cargo : Vehicle, carrierGUID : PlanetSideGUID, carrier : Vehicle, bailed : Boolean, requestedByPassenger : Boolean, kicked : Boolean) : Unit = {
val zone = carrier.Zone
carrier.CargoHolds.find({case(_, hold) => hold.Occupant.contains(cargo)}) match {
case Some((mountPoint, hold)) =>
@@ -284,41 +283,43 @@ object CargoBehavior {
//the lodestar's cargo hold is almost the center of the vehicle
carrier.Position
}
- zone.VehicleEvents ! VehicleServiceMessage(s"${cargo.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 0, cargo.Health)))
- zone.VehicleEvents ! VehicleServiceMessage(s"${cargo.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 68, cargo.Shields)))
+ val GUID0 = Service.defaultPlayerGUID
+ val zoneId = zone.Id
+ val events = zone.VehicleEvents
+ val cargoActor = cargo.Actor
+ events ! VehicleServiceMessage(s"$cargoActor", VehicleAction.SendResponse(GUID0, PlanetsideAttributeMessage(cargoGUID, 0, cargo.Health)))
+ events ! VehicleServiceMessage(s"$cargoActor", VehicleAction.SendResponse(GUID0, PlanetsideAttributeMessage(cargoGUID, 68, cargo.Shields)))
if(carrier.Flying) {
//the carrier vehicle is flying; eject the cargo vehicle
- val ejectCargoMsg = CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), PlanetSideGUID(0), cargoGUID, mountPoint, CargoStatus.InProgress, 0)
+ val ejectCargoMsg = CargoMountPointStatusMessage(carrierGUID, GUID0, GUID0, cargoGUID, mountPoint, CargoStatus.InProgress, 0)
val detachCargoMsg = ObjectDetachMessage(carrierGUID, cargoGUID, cargoHoldPosition - Vector3.z(1), rotation)
- val resetCargoMsg = CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), PlanetSideGUID(0), cargoGUID, mountPoint, CargoStatus.Empty, 0)
- zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(Service.defaultPlayerGUID, ejectCargoMsg))
- zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(Service.defaultPlayerGUID, detachCargoMsg))
- zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(Service.defaultPlayerGUID, resetCargoMsg))
+ val resetCargoMsg = CargoMountPointStatusMessage(carrierGUID, GUID0, GUID0, cargoGUID, mountPoint, CargoStatus.Empty, 0)
+ events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, ejectCargoMsg))
+ events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, detachCargoMsg))
+ events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, resetCargoMsg))
log.debug(ejectCargoMsg.toString)
log.debug(detachCargoMsg.toString)
if(driverOpt.isEmpty) {
//TODO cargo should drop like a rock like normal; until then, deconstruct it
- zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(cargo), zone))
- zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(cargo, zone, Some(0 seconds)))
+ cargo.Actor ! Vehicle.Deconstruct()
}
}
else {
//the carrier vehicle is not flying; just open the door and let the cargo vehicle back out; force it out if necessary
- val cargoStatusMessage = CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), cargoGUID, PlanetSideGUID(0), mountPoint, CargoStatus.InProgress, 0)
+ val cargoStatusMessage = CargoMountPointStatusMessage(carrierGUID, GUID0, cargoGUID, GUID0, mountPoint, CargoStatus.InProgress, 0)
val cargoDetachMessage = ObjectDetachMessage(carrierGUID, cargoGUID, cargoHoldPosition + Vector3.z(1f), rotation)
- zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(Service.defaultPlayerGUID, cargoStatusMessage))
- zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(Service.defaultPlayerGUID, cargoDetachMessage))
+ events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, cargoStatusMessage))
+ events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, cargoDetachMessage))
driverOpt match {
case Some(driver) =>
- zone.VehicleEvents ! VehicleServiceMessage(s"${driver.Name}", VehicleAction.KickCargo(player_guid, cargo, cargo.Definition.AutoPilotSpeed2, 2500))
+ events ! VehicleServiceMessage(s"${driver.Name}", VehicleAction.KickCargo(GUID0, cargo, cargo.Definition.AutoPilotSpeed2, 2500))
//check every quarter second if the vehicle has moved far enough away to be considered dismounted
- cargo.Actor ! CheckCargoDismount(carrierGUID, mountPoint, 0)
+ cargoActor ! CheckCargoDismount(carrierGUID, mountPoint, 0)
case None =>
- val resetCargoMsg = CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), PlanetSideGUID(0), cargoGUID, mountPoint, CargoStatus.Empty, 0)
- zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(PlanetSideGUID(0), resetCargoMsg)) //lazy
+ val resetCargoMsg = CargoMountPointStatusMessage(carrierGUID, GUID0, GUID0, cargoGUID, mountPoint, CargoStatus.Empty, 0)
+ events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, resetCargoMsg)) //lazy
//TODO cargo should back out like normal; until then, deconstruct it
- zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(cargo), zone))
- zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(cargo, zone, Some(0 seconds)))
+ cargoActor ! Vehicle.Deconstruct()
}
}
diff --git a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
index 8d3f315d..9f4e0ed9 100644
--- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
+++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
@@ -1,8 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.vehicles
-import akka.actor.{Actor, ActorRef}
-import net.psforever.objects.{GlobalDefinitions, SimpleItem, Vehicle, Vehicles}
+import akka.actor.{Actor, ActorRef, Cancellable}
+import net.psforever.objects.{DefaultCancellable, GlobalDefinitions, SimpleItem, Vehicle, Vehicles}
import net.psforever.objects.ballistics.{ResolvedProjectile, VehicleSource}
import net.psforever.objects.equipment.JammableMountedWeapons
import net.psforever.objects.serverobject.CommonMessages
@@ -13,9 +13,14 @@ import net.psforever.objects.serverobject.deploy.DeploymentBehavior
import net.psforever.objects.serverobject.hackable.GenericHackables
import net.psforever.objects.serverobject.repair.RepairableVehicle
import net.psforever.objects.vital.VehicleShieldCharge
-import net.psforever.types.{ExoSuitType, PlanetSideGUID}
+import net.psforever.objects.zones.Zone
+import net.psforever.types.{DriveState, ExoSuitType, PlanetSideGUID, Vector3}
+import services.{RemoverActor, Service}
import services.vehicle.{VehicleAction, VehicleServiceMessage}
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.duration._
+
/**
* An `Actor` that handles messages being dispatched to a specific `Vehicle`.
*
@@ -44,10 +49,17 @@ class VehicleControl(vehicle : Vehicle) extends Actor
def DamageableObject = vehicle
def RepairableObject = vehicle
+ /** cheap flag for whether the vehicle is decaying */
+ var decaying : Boolean = false
+ /** primary vehicle decay timer */
+ var decayTimer : Cancellable = DefaultCancellable.obj
+
def receive : Receive = Enabled
override def postStop() : Unit = {
super.postStop()
+ decaying = false
+ decayTimer.cancel
vehicle.Utilities.values.foreach { util =>
context.stop(util().Actor)
util().Actor = ActorRef.noSender
@@ -63,9 +75,18 @@ class VehicleControl(vehicle : Vehicle) extends Actor
.orElse {
case msg : Mountable.TryMount =>
tryMountBehavior.apply(msg)
+ if(MountableObject.Seats.values.exists(_.isOccupied)) {
+ decaying = false
+ decayTimer.cancel
+ }
case msg : Mountable.TryDismount =>
dismountBehavior.apply(msg)
+ val obj = MountableObject
+ if(!decaying && obj.Owner.isEmpty && obj.Seats.values.forall(!_.isOccupied)) {
+ decaying = true
+ decayTimer = context.system.scheduler.scheduleOnce(MountableObject.Definition.DeconstructionTime.getOrElse(5 minutes), self, VehicleControl.PrepareForDeletion())
+ }
case Vehicle.ChargeShields(amount) =>
val now : Long = System.nanoTime
@@ -94,10 +115,18 @@ class VehicleControl(vehicle : Vehicle) extends Actor
)
}
- case Vehicle.PrepareForDeletion() =>
- CancelJammeredSound(vehicle)
- CancelJammeredStatus(vehicle)
- context.become(Disabled)
+ case Vehicle.Deconstruct(time) =>
+ time match {
+ case Some(delay) =>
+ decaying = true
+ decayTimer.cancel
+ decayTimer = context.system.scheduler.scheduleOnce(delay, self, VehicleControl.PrepareForDeletion())
+ case _ =>
+ PrepareForDeletion()
+ }
+
+ case VehicleControl.PrepareForDeletion() =>
+ PrepareForDeletion()
case _ => ;
}
@@ -128,14 +157,56 @@ class VehicleControl(vehicle : Vehicle) extends Actor
}
}
+ def PrepareForDeletion() : Unit = {
+ decaying = false
+ val guid = vehicle.GUID
+ val zone = vehicle.Zone
+ val zoneId = zone.Id
+ val events = zone.VehicleEvents
+ //become disabled
+ context.become(Disabled)
+ //cancel jammed behavior
+ CancelJammeredSound(vehicle)
+ CancelJammeredStatus(vehicle)
+ //escape being someone else's cargo
+ vehicle.MountedIn match {
+ case Some(_) =>
+ CargoBehavior.HandleVehicleCargoDismount(zone, guid, bailed = false, requestedByPassenger = false, kicked = false)
+ case _ => ;
+ }
+ //kick all passengers
+ vehicle.Seats.values.foreach(seat => {
+ seat.Occupant match {
+ case Some(player) =>
+ seat.Occupant = None
+ player.VehicleSeated = None
+ if(player.HasGUID) {
+ events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, false, guid))
+ }
+ case None => ;
+ }
+ //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)
+ }
+ })
+ //unregister
+ events ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, zone, Some(0 seconds)))
+ //banished to the shadow realm
+ vehicle.Position = Vector3.Zero
+ vehicle.DeploymentState = DriveState.Mobile
+ //queue final deletion
+ decayTimer = context.system.scheduler.scheduleOnce(5 seconds, self, VehicleControl.Deletion())
+ }
+
def Disabled : Receive = checkBehavior
.orElse {
- case msg : Mountable.TryDismount =>
- dismountBehavior.apply(msg)
-
- case Vehicle.Reactivate() =>
- context.become(Enabled)
-
+ case VehicleControl.Deletion() =>
+ val zone = vehicle.Zone
+ zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.UnloadVehicle(Service.defaultPlayerGUID, zone, vehicle, vehicle.GUID))
+ zone.Transport ! Zone.Vehicle.Despawn(vehicle)
case _ =>
}
@@ -150,6 +221,10 @@ object VehicleControl {
import net.psforever.objects.vital.{DamageFromProjectile, VehicleShieldCharge, VitalsActivity}
import scala.concurrent.duration._
+ private case class PrepareForDeletion()
+
+ private case class Deletion()
+
/**
* Determine if a given activity entry would invalidate the act of charging vehicle shields this tick.
* @param now the current time (in nanoseconds)
diff --git a/common/src/main/scala/services/RemoverActor.scala b/common/src/main/scala/services/RemoverActor.scala
index 1f9f7371..58415aa5 100644
--- a/common/src/main/scala/services/RemoverActor.scala
+++ b/common/src/main/scala/services/RemoverActor.scala
@@ -61,7 +61,7 @@ abstract class RemoverActor extends SupportActor[RemoverActor.Entry] {
*/
override def preStart() : Unit = {
super.preStart()
- self ! Service.Startup()
+ ServiceManager.serviceManager ! ServiceManager.Lookup("taskResolver") //ask for a resolver to deal with the GUID system
}
/**
@@ -84,9 +84,6 @@ abstract class RemoverActor extends SupportActor[RemoverActor.Entry] {
}
def receive : Receive = {
- case Service.Startup() =>
- ServiceManager.serviceManager ! ServiceManager.Lookup("taskResolver") //ask for a resolver to deal with the GUID system
-
case ServiceManager.LookupResult("taskResolver", endpoint) =>
taskResolver = endpoint
context.become(Processing)
diff --git a/common/src/main/scala/services/account/AccountPersistenceService.scala b/common/src/main/scala/services/account/AccountPersistenceService.scala
index 32c2c3d2..bbc261f2 100644
--- a/common/src/main/scala/services/account/AccountPersistenceService.scala
+++ b/common/src/main/scala/services/account/AccountPersistenceService.scala
@@ -348,16 +348,15 @@ class PersistenceMonitor(name : String, squadService : ActorRef, taskResolver :
def DisownVehicle(player : Player) : Unit = {
Vehicles.Disown(player, inZone) match {
case Some(vehicle) if vehicle.Health == 0 || (vehicle.Seats.values.forall(seat => !seat.isOccupied) && vehicle.Owner.isEmpty) =>
- inZone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(vehicle), inZone))
- inZone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, inZone,
+ vehicle.Actor ! Vehicle.Deconstruct(
if(vehicle.Flying) {
//TODO gravity
- Some(0 seconds) //immediate deconstruction
+ None //immediate deconstruction
}
else {
vehicle.Definition.DeconstructionTime //normal deconstruction
}
- ))
+ )
case _ => ;
}
}
diff --git a/common/src/main/scala/services/vehicle/VehicleService.scala b/common/src/main/scala/services/vehicle/VehicleService.scala
index bad2bb0b..d535056e 100644
--- a/common/src/main/scala/services/vehicle/VehicleService.scala
+++ b/common/src/main/scala/services/vehicle/VehicleService.scala
@@ -137,11 +137,6 @@ class VehicleService(zone : Zone) extends Actor {
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.TransferPassengerChannel(old_channel, temp_channel, vehicle, vehicle_to_delete))
)
-
- case VehicleAction.ForceDismountVehicleCargo(player_guid, vehicle_guid, bailed, requestedByPassenger, kicked) =>
- VehicleEvents.publish(
- VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.ForceDismountVehicleCargo(vehicle_guid, bailed, requestedByPassenger, kicked))
- )
case VehicleAction.KickCargo(player_guid, cargo, speed, delay) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickCargo(cargo, speed, delay))
@@ -216,12 +211,6 @@ class VehicleService(zone : Zone) extends Actor {
VehicleEvents.publish(
VehicleServiceResponse(s"/${zone.Id}/Vehicle", Service.defaultPlayerGUID, VehicleResponse.LoadVehicle(vehicle, vtype, vguid, vdata))
)
- //avoid unattended vehicle spawning blocking the pad; user should mount (and does so normally) to reset decon timer
- vehicleDecon forward RemoverActor.AddTask(vehicle, zone, Some(30 seconds))
-
- case VehicleSpawnPad.DisposeVehicle(vehicle) =>
- vehicleDecon forward RemoverActor.AddTask(vehicle, zone, Some(0 seconds))
- vehicleDecon forward RemoverActor.HurrySpecific(List(vehicle), zone)
//correspondence from WorldSessionActor
case VehicleServiceMessage.AMSDeploymentChange(_) =>
diff --git a/common/src/main/scala/services/vehicle/VehicleServiceMessage.scala b/common/src/main/scala/services/vehicle/VehicleServiceMessage.scala
index 931ce8d8..8d101911 100644
--- a/common/src/main/scala/services/vehicle/VehicleServiceMessage.scala
+++ b/common/src/main/scala/services/vehicle/VehicleServiceMessage.scala
@@ -46,6 +46,5 @@ object VehicleAction {
final case class TransferPassengerChannel(player_guid : PlanetSideGUID, temp_channel : String, new_channel : String, vehicle : Vehicle, vehicle_to_delete : PlanetSideGUID) extends Action
- final case class ForceDismountVehicleCargo(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, bailed : Boolean, requestedByPassenger : Boolean, kicked : Boolean) extends Action
final case class KickCargo(player_guid : PlanetSideGUID, cargo : Vehicle, speed : Int, delay : Long) extends Action
}
diff --git a/common/src/main/scala/services/vehicle/VehicleServiceResponse.scala b/common/src/main/scala/services/vehicle/VehicleServiceResponse.scala
index 8075e8c8..68834cc4 100644
--- a/common/src/main/scala/services/vehicle/VehicleServiceResponse.scala
+++ b/common/src/main/scala/services/vehicle/VehicleServiceResponse.scala
@@ -52,6 +52,5 @@ object VehicleResponse {
final case class TransferPassengerChannel(old_channel : String, temp_channel : String, vehicle : Vehicle, vehicle_to_delete : PlanetSideGUID) extends Response
- final case class ForceDismountVehicleCargo(vehicle_guid : PlanetSideGUID, bailed : Boolean, requestedByPassenger : Boolean, kicked : Boolean) extends Response
final case class KickCargo(cargo : Vehicle, speed : Int, delay : Long) extends Response
}
diff --git a/common/src/main/scala/services/vehicle/support/VehicleRemover.scala b/common/src/main/scala/services/vehicle/support/VehicleRemover.scala
index 3637393a..1f57e85f 100644
--- a/common/src/main/scala/services/vehicle/support/VehicleRemover.scala
+++ b/common/src/main/scala/services/vehicle/support/VehicleRemover.scala
@@ -1,88 +1,31 @@
-// Copyright (c) 2017 PSForever
+// Copyright (c) 2017-2020 PSForever
package services.vehicle.support
-import akka.actor.ActorRef
+import akka.actor.{Actor, ActorRef}
import net.psforever.objects.Vehicle
-import net.psforever.objects.guid.{GUIDTask, TaskResolver}
-import net.psforever.objects.zones.Zone
-import net.psforever.types.{DriveState, PlanetSideGUID}
-import services.{RemoverActor, Service}
-import services.vehicle.{VehicleAction, VehicleServiceMessage}
+import net.psforever.objects.guid.GUIDTask.UnregisterVehicle
+import services.{RemoverActor, ServiceManager}
-import scala.concurrent.duration._
+class VehicleRemover extends Actor {
+ var taskResolver : ActorRef = Actor.noSender
-class VehicleRemover extends RemoverActor {
- final val FirstStandardDuration : FiniteDuration = 5 minutes
-
- final val SecondStandardDuration : FiniteDuration = 5 seconds
-
- def InclusionTest(entry : RemoverActor.Entry) : Boolean = {
- entry.obj.isInstanceOf[Vehicle]
+ override def preStart() : Unit = {
+ super.preStart()
+ ServiceManager.serviceManager ! ServiceManager.Lookup("taskResolver") //ask for a resolver to deal with the GUID system
}
- def InitialJob(entry : RemoverActor.Entry) : Unit = { }
+ def receive : Receive = {
+ case ServiceManager.LookupResult("taskResolver", endpoint) =>
+ taskResolver = endpoint
+ context.become(Processing)
- def FirstJob(entry : RemoverActor.Entry) : Unit = {
- val vehicleGUID = entry.obj.GUID
- entry.zone.GUID(vehicleGUID) match {
- case Some(vehicle : Vehicle) if vehicle.HasGUID =>
- val zoneId = entry.zone.Id
- if(vehicle.Actor != ActorRef.noSender) {
- vehicle.Actor ! Vehicle.PrepareForDeletion()
- }
- //escape being someone else's cargo
- (vehicle.MountedIn match {
- case Some(carrierGUID) =>
- entry.zone.Vehicles.find(v => v.GUID == carrierGUID)
- case None =>
- None
- }) match {
- case Some(carrier : Vehicle) =>
- val driverName = carrier.Seats(0).Occupant match {
- case Some(driver) => driver.Name
- case _ => zoneId
- }
- context.parent ! VehicleServiceMessage(s"$driverName", VehicleAction.ForceDismountVehicleCargo(PlanetSideGUID(0), vehicleGUID, true, false, false))
- case _ => ;
- }
- //kick out all passengers
- vehicle.Seats.values.foreach(seat => {
- seat.Occupant match {
- case Some(tplayer) =>
- seat.Occupant = None
- tplayer.VehicleSeated = None
- if(tplayer.HasGUID) {
- context.parent ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(tplayer.GUID, 4, false, vehicleGUID))
- }
- case None => ;
- }
- //abandon all cargo
- vehicle.CargoHolds.values
- .collect { case hold if hold.isOccupied =>
- val cargo = hold.Occupant.get
- context.parent ! VehicleServiceMessage(zoneId, VehicleAction.ForceDismountVehicleCargo(PlanetSideGUID(0), cargo.GUID, true, false, false))
- }
- })
- case _ => ;
- }
+ case _ => ;
}
- override def SecondJob(entry : RemoverActor.Entry) : Unit = {
- val vehicleGUID = entry.obj.GUID
- entry.zone.GUID(vehicleGUID) match {
- case Some(vehicle : Vehicle) if vehicle.HasGUID =>
- val zone = entry.zone
- vehicle.DeploymentState = DriveState.Mobile
- zone.Transport ! Zone.Vehicle.Despawn(vehicle)
- context.parent ! VehicleServiceMessage(zone.Id, VehicleAction.UnloadVehicle(Service.defaultPlayerGUID, zone, vehicle, vehicleGUID))
- super.SecondJob(entry)
- case _ => ;
- }
- }
+ def Processing : Receive = {
+ case RemoverActor.AddTask(obj : Vehicle, zone, _) =>
+ taskResolver ! UnregisterVehicle(obj)(zone.GUID)
- def ClearanceTest(entry : RemoverActor.Entry) : Boolean = entry.obj.asInstanceOf[Vehicle].Seats.values.count(_.isOccupied) == 0
-
- def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = {
- GUIDTask.UnregisterVehicle(entry.obj.asInstanceOf[Vehicle])(entry.zone.GUID)
+ case _ => ;
}
}
diff --git a/common/src/test/scala/base/FreedContextActorTest.scala b/common/src/test/scala/base/FreedContextActorTest.scala
new file mode 100644
index 00000000..759b85fe
--- /dev/null
+++ b/common/src/test/scala/base/FreedContextActorTest.scala
@@ -0,0 +1,54 @@
+// Copyright (c) 2020 PSForever
+package base
+
+import akka.actor.{Actor, ActorContext, ActorRef, Props}
+import akka.pattern.ask
+import akka.util.Timeout
+
+import scala.concurrent.duration._
+import scala.concurrent.Await
+
+/**
+ * Create an `ActorTest` environment that has an `ActorContext` object.
+ */
+abstract class FreedContextActorTest extends ActorTest {
+ /*
+ Never do this in actual production code!
+ ActorSystem and ActorContext offer similar mechanisms for instantiating actors.
+ This is a consequence of their shared inheritance of the ActorRefFactory trait.
+ They are not equivalent enough to be able to pass one as the other as a parameter.
+ Because the ActorSystem has no context of its own,
+ various bizarre mechanisms have to be developed to use any methods that would pass in a context object.
+ We create a middleman Actor whose main purpose is to surrender its context object to the test environment directly
+ and then direct all messages sent to that object to the test environment.
+ */
+ private val _testContextHandler = system.actorOf(Props(classOf[ContextSensitive]), "actor-test-cs")
+ private implicit val timeout = Timeout(5 seconds)
+ private val _testContextHandlerResult = ask(_testContextHandler, message = "", self)
+ implicit val context = Await.result(_testContextHandlerResult, timeout.duration).asInstanceOf[ActorContext]
+}
+
+/**
+ * Surrender your `context` object for a greater good!
+ */
+private class ContextSensitive extends Actor {
+ var output : ActorRef = ActorRef.noSender
+
+ def receive : Receive = {
+ case _ =>
+ context.become(PassThroughBehavior)
+ output = sender
+ sender ! context
+ }
+
+ /**
+ * Once the `context` object has been leased,
+ * this `Actor` becomes transparent.
+ * Calling `context.parent` from whatever `Actor` was spurned by the previously provided `context`,
+ * will now refer to whatever was the contact to gain access to it - the test environment.
+ * @return something to `become`
+ */
+ def PassThroughBehavior : Receive = {
+ case msg => output forward msg
+ }
+}
diff --git a/common/src/test/scala/objects/DamageableTest.scala b/common/src/test/scala/objects/DamageableTest.scala
index b857f001..6facd258 100644
--- a/common/src/test/scala/objects/DamageableTest.scala
+++ b/common/src/test/scala/objects/DamageableTest.scala
@@ -21,7 +21,7 @@ import net.psforever.objects.vital.Vitality
import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game.DamageWithPositionMessage
import net.psforever.types._
-import services.{RemoverActor, Service}
+import services.Service
import services.avatar.{AvatarAction, AvatarServiceMessage}
import services.support.SupportActor
import services.vehicle.support.TurretUpgrader
@@ -1307,7 +1307,6 @@ class DamageableVehicleDestroyTest extends ActorTest {
atv.Actor ! Vitality.Damage(applyDamageTo)
val msg124 = avatarProbe.receiveN(3, 500 milliseconds)
val msg3 = player2Probe.receiveOne(200 milliseconds)
- val msg567 = vehicleProbe.receiveN(2, 200 milliseconds)
assert(
msg124.head match {
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(1), 0, _)) => true
@@ -1332,18 +1331,6 @@ class DamageableVehicleDestroyTest extends ActorTest {
case _ => false
}
)
- assert(
- msg567.head match {
- case VehicleServiceMessage.Decon(SupportActor.ClearSpecific(List(target), _zone)) if (target eq atv) && (_zone eq zone) => true
- case _ => false
- }
- )
- assert(
- msg567(1) match {
- case VehicleServiceMessage.Decon(RemoverActor.AddTask(target, _zone, _)) if (target eq atv) && (_zone eq zone) => true
- case _ => false
- }
- )
assert(atv.Health <= atv.Definition.DamageDestroysAt)
assert(atv.Destroyed)
//
@@ -1467,7 +1454,7 @@ class DamageableVehicleDestroyMountedTest extends ActorTest {
player2Probe.expectNoMsg(10 milliseconds)
val msg_player3 = player3Probe.receiveOne(200 milliseconds)
player3Probe.expectNoMsg(10 milliseconds)
- val msg_vehicle = vehicleProbe.receiveN(6, 200 milliseconds)
+ val msg_vehicle = vehicleProbe.receiveN(2, 200 milliseconds)
vehicleProbe.expectNoMsg(10 milliseconds)
assert(
msg_avatar.exists( {
@@ -1511,30 +1498,6 @@ class DamageableVehicleDestroyMountedTest extends ActorTest {
case _ => false
}
)
- assert(
- msg_vehicle.exists( {
- case VehicleServiceMessage.Decon(SupportActor.ClearSpecific(List(target), _zone)) if (target eq lodestar) && (_zone eq zone) => true
- case _ => false
- })
- )
- assert(
- msg_vehicle.exists( {
- case VehicleServiceMessage.Decon(RemoverActor.AddTask(target, _zone, _)) if (target eq lodestar) && (_zone eq zone) => true
- case _ => false
- })
- )
- assert(
- msg_vehicle.exists( {
- case VehicleServiceMessage.Decon(SupportActor.ClearSpecific(List(target), _zone)) if (target eq atv) && (_zone eq zone) => true
- case _ => false
- })
- )
- assert(
- msg_vehicle.exists( {
- case VehicleServiceMessage.Decon(RemoverActor.AddTask(target, _zone, _)) if (target eq atv) && (_zone eq zone) => true
- case _ => false
- })
- )
assert(
msg_vehicle.exists( {
case VehicleServiceMessage("test", VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, PlanetSideGUID(4), 27, 0))=> true
diff --git a/common/src/test/scala/objects/VehicleTest.scala b/common/src/test/scala/objects/VehicleTest.scala
index 7fc1fbae..d06a0762 100644
--- a/common/src/test/scala/objects/VehicleTest.scala
+++ b/common/src/test/scala/objects/VehicleTest.scala
@@ -3,16 +3,19 @@ package objects
import akka.actor.Props
import akka.testkit.TestProbe
-import base.ActorTest
+import base.{ActorTest, FreedContextActorTest}
import net.psforever.objects._
-import net.psforever.objects.ballistics.{PlayerSource, Projectile, ProjectileResolution, ResolvedProjectile}
import net.psforever.objects.definition.{SeatDefinition, VehicleDefinition}
+import net.psforever.objects.guid.NumberPoolHub
+import net.psforever.objects.guid.source.LimitedNumberSource
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.vehicles._
-import net.psforever.objects.vital.{VehicleShieldCharge, Vitality}
-import net.psforever.objects.zones.{Zone, ZoneMap}
+import net.psforever.objects.vital.VehicleShieldCharge
+import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap}
+import net.psforever.packet.game.{CargoMountPointStatusMessage, ObjectDetachMessage, PlanetsideAttributeMessage}
import net.psforever.types.{PlanetSideGUID, _}
import org.specs2.mutable._
+import services.{RemoverActor, ServiceManager}
import services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.duration._
@@ -30,13 +33,13 @@ class VehicleTest extends Specification {
val t_seat = new SeatDefinition
t_seat.ArmorRestriction mustEqual SeatArmorRestriction.NoMax
t_seat.Bailable mustEqual false
- t_seat.ControlledWeapon mustEqual None
+ t_seat.ControlledWeapon.isEmpty mustEqual true
}
"define (custom)" in {
seat.ArmorRestriction mustEqual SeatArmorRestriction.MaxOnly
seat.Bailable mustEqual true
- seat.ControlledWeapon mustEqual Some(5)
+ seat.ControlledWeapon.contains(5)
}
}
@@ -47,13 +50,13 @@ class VehicleTest extends Specification {
fury.CanCloak mustEqual false
fury.Seats.size mustEqual 1
fury.Seats(0).Bailable mustEqual true
- fury.Seats(0).ControlledWeapon mustEqual Some(1)
+ fury.Seats(0).ControlledWeapon.contains(1)
fury.MountPoints.size mustEqual 2
- fury.MountPoints.get(1) mustEqual Some(0)
- fury.MountPoints.get(2) mustEqual Some(0)
+ fury.MountPoints.get(1).contains(0)
+ fury.MountPoints.get(2).contains(0)
fury.Weapons.size mustEqual 1
- fury.Weapons.get(0) mustEqual None
- fury.Weapons.get(1) mustEqual Some(GlobalDefinitions.fury_weapon_systema)
+ fury.Weapons.get(0).isEmpty mustEqual true
+ fury.Weapons.get(1).contains(GlobalDefinitions.fury_weapon_systema)
fury.TrunkSize.Width mustEqual 11
fury.TrunkSize.Height mustEqual 11
fury.TrunkOffset mustEqual 30
@@ -70,9 +73,9 @@ class VehicleTest extends Specification {
val seat = new Seat(seat_def)
seat.ArmorRestriction mustEqual SeatArmorRestriction.MaxOnly
seat.Bailable mustEqual true
- seat.ControlledWeapon mustEqual Some(5)
+ seat.ControlledWeapon.contains(5)
seat.isOccupied mustEqual false
- seat.Occupant mustEqual None
+ seat.Occupant.isEmpty mustEqual true
}
"player can sit" in {
@@ -136,19 +139,19 @@ class VehicleTest extends Specification {
"construct (detailed)" in {
val fury_vehicle = Vehicle(GlobalDefinitions.fury)
- fury_vehicle.Owner mustEqual None
+ fury_vehicle.Owner.isEmpty mustEqual true
fury_vehicle.Seats.size mustEqual 1
fury_vehicle.Seats(0).ArmorRestriction mustEqual SeatArmorRestriction.NoMax
fury_vehicle.Seats(0).isOccupied mustEqual false
- fury_vehicle.Seats(0).Occupant mustEqual None
+ fury_vehicle.Seats(0).Occupant.isEmpty mustEqual true
fury_vehicle.Seats(0).Bailable mustEqual true
- fury_vehicle.Seats(0).ControlledWeapon mustEqual Some(1)
- fury_vehicle.PermissionGroup(0) mustEqual Some(VehicleLockState.Locked) //driver
- fury_vehicle.PermissionGroup(1) mustEqual Some(VehicleLockState.Empire) //gunner
- fury_vehicle.PermissionGroup(2) mustEqual Some(VehicleLockState.Empire) //passenger
- fury_vehicle.PermissionGroup(3) mustEqual Some(VehicleLockState.Locked) //trunk
+ fury_vehicle.Seats(0).ControlledWeapon.contains(1)
+ fury_vehicle.PermissionGroup(0).contains(VehicleLockState.Locked) //driver
+ fury_vehicle.PermissionGroup(1).contains(VehicleLockState.Empire) //gunner
+ fury_vehicle.PermissionGroup(2).contains(VehicleLockState.Empire) //passenger
+ fury_vehicle.PermissionGroup(3).contains(VehicleLockState.Locked) //trunk
fury_vehicle.Weapons.size mustEqual 1
- fury_vehicle.Weapons.get(0) mustEqual None
+ fury_vehicle.Weapons.get(0).isEmpty mustEqual true
fury_vehicle.Weapons.get(1).isDefined mustEqual true
fury_vehicle.Weapons(1).Equipment.isDefined mustEqual true
fury_vehicle.Weapons(1).Equipment.get.Definition mustEqual GlobalDefinitions.fury.Weapons(1)
@@ -156,8 +159,8 @@ class VehicleTest extends Specification {
fury_vehicle.Trunk.Width mustEqual 11
fury_vehicle.Trunk.Height mustEqual 11
fury_vehicle.Trunk.Offset mustEqual 30
- fury_vehicle.GetSeatFromMountPoint(1) mustEqual Some(0)
- fury_vehicle.GetSeatFromMountPoint(2) mustEqual Some(0)
+ fury_vehicle.GetSeatFromMountPoint(1).contains(0)
+ fury_vehicle.GetSeatFromMountPoint(2).contains(0)
fury_vehicle.Decal mustEqual 0
fury_vehicle.Health mustEqual fury_vehicle.Definition.MaxHealth
}
@@ -192,30 +195,30 @@ class VehicleTest extends Specification {
"can use mount point to get seat number" in {
val fury_vehicle = Vehicle(GlobalDefinitions.fury)
- fury_vehicle.GetSeatFromMountPoint(0) mustEqual None
- fury_vehicle.GetSeatFromMountPoint(1) mustEqual Some(0)
- fury_vehicle.GetSeatFromMountPoint(2) mustEqual Some(0)
- fury_vehicle.GetSeatFromMountPoint(3) mustEqual None
+ fury_vehicle.GetSeatFromMountPoint(0).isEmpty mustEqual true
+ fury_vehicle.GetSeatFromMountPoint(1).contains(0)
+ fury_vehicle.GetSeatFromMountPoint(2).contains(0)
+ fury_vehicle.GetSeatFromMountPoint(3).isEmpty mustEqual true
}
"has four permission groups" in {
val fury_vehicle = Vehicle(GlobalDefinitions.fury)
- fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id) mustEqual Some(VehicleLockState.Locked)
- fury_vehicle.PermissionGroup(AccessPermissionGroup.Gunner.id) mustEqual Some(VehicleLockState.Empire)
- fury_vehicle.PermissionGroup(AccessPermissionGroup.Passenger.id) mustEqual Some(VehicleLockState.Empire)
- fury_vehicle.PermissionGroup(AccessPermissionGroup.Trunk.id) mustEqual Some(VehicleLockState.Locked)
+ fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id).contains(VehicleLockState.Locked)
+ fury_vehicle.PermissionGroup(AccessPermissionGroup.Gunner.id).contains(VehicleLockState.Empire)
+ fury_vehicle.PermissionGroup(AccessPermissionGroup.Passenger.id).contains(VehicleLockState.Empire)
+ fury_vehicle.PermissionGroup(AccessPermissionGroup.Trunk.id).contains(VehicleLockState.Locked)
}
"set new permission level" in {
val fury_vehicle = Vehicle(GlobalDefinitions.fury)
- fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id) mustEqual Some(VehicleLockState.Locked)
- fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id, VehicleLockState.Group.id) mustEqual Some(VehicleLockState.Group)
+ fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id).contains(VehicleLockState.Locked)
+ fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id, VehicleLockState.Group.id).contains(VehicleLockState.Group)
}
"set the same permission level" in {
val fury_vehicle = Vehicle(GlobalDefinitions.fury)
- fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id) mustEqual Some(VehicleLockState.Locked)
- fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id, VehicleLockState.Locked.id) mustEqual None
+ fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id).contains(VehicleLockState.Locked)
+ fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id, VehicleLockState.Locked.id).isEmpty mustEqual true
}
"alternate permission level indices" in {
@@ -226,15 +229,15 @@ class VehicleTest extends Specification {
fury_vehicle.PermissionGroup(AccessPermissionGroup.Trunk.id) mustEqual fury_vehicle.PermissionGroup(13)
(AccessPermissionGroup.Driver.id + 10) mustEqual 10
- fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id, VehicleLockState.Group.id) mustEqual Some(VehicleLockState.Group)
+ fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id, VehicleLockState.Group.id).contains(VehicleLockState.Group)
fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id) mustEqual fury_vehicle.PermissionGroup(10)
}
"can determine permission group from seat" in {
val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
- harasser_vehicle.SeatPermissionGroup(0) mustEqual Some(AccessPermissionGroup.Driver)
- harasser_vehicle.SeatPermissionGroup(1) mustEqual Some(AccessPermissionGroup.Gunner)
- harasser_vehicle.SeatPermissionGroup(2) mustEqual None
+ harasser_vehicle.SeatPermissionGroup(0).contains(AccessPermissionGroup.Driver)
+ harasser_vehicle.SeatPermissionGroup(1).contains(AccessPermissionGroup.Gunner)
+ harasser_vehicle.SeatPermissionGroup(2).isEmpty mustEqual true
//TODO test for AccessPermissionGroup.Passenger later
}
@@ -247,11 +250,11 @@ class VehicleTest extends Specification {
harasser_vehicle.Seat(0).get.Occupant = player1 //don't worry about ownership for now
harasser_vehicle.Seat(1).get.Occupant = player2
- harasser_vehicle.PassengerInSeat(player1) mustEqual Some(0)
- harasser_vehicle.PassengerInSeat(player2) mustEqual Some(1)
+ harasser_vehicle.PassengerInSeat(player1).contains(0)
+ harasser_vehicle.PassengerInSeat(player2).contains(1)
harasser_vehicle.Seat(0).get.Occupant = None
- harasser_vehicle.PassengerInSeat(player1) mustEqual None
- harasser_vehicle.PassengerInSeat(player2) mustEqual Some(1)
+ harasser_vehicle.PassengerInSeat(player1).isEmpty mustEqual true
+ harasser_vehicle.PassengerInSeat(player2).contains(1)
}
"can find a weapon controlled from seat" in {
@@ -259,7 +262,7 @@ class VehicleTest extends Specification {
val chaingun_p = harasser_vehicle.Weapons(2).Equipment
chaingun_p.isDefined mustEqual true
- harasser_vehicle.WeaponControlledFromSeat(0) mustEqual None
+ harasser_vehicle.WeaponControlledFromSeat(0).isEmpty mustEqual true
harasser_vehicle.WeaponControlledFromSeat(1) mustEqual chaingun_p
}
@@ -273,12 +276,12 @@ class VehicleTest extends Specification {
obj.Utilities.size mustEqual 3
obj.Utilities(-1).UtilType mustEqual UtilityType.order_terminala
obj.Utilities(0).UtilType mustEqual UtilityType.order_terminalb
- obj.Utilities.get(1) mustEqual None
+ obj.Utilities.get(1).isEmpty mustEqual true
obj.Utilities(2).UtilType mustEqual UtilityType.order_terminalb
val filteredMap = Vehicle.EquipmentUtilities(obj.Utilities)
filteredMap.size mustEqual 2
- filteredMap.get(-1) mustEqual None
+ filteredMap.get(-1).isEmpty mustEqual true
filteredMap(0).UtilType mustEqual UtilityType.order_terminalb
filteredMap(2).UtilType mustEqual UtilityType.order_terminalb
}
@@ -303,7 +306,7 @@ class VehicleTest extends Specification {
val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
harasser_vehicle.Weapons(2).Equipment.get.GUID = PlanetSideGUID(10)
- harasser_vehicle.Find(PlanetSideGUID(10)) mustEqual Some(2)
+ harasser_vehicle.Find(PlanetSideGUID(10)).contains(2)
}
"find items in its trunk by GUID" in {
@@ -312,101 +315,284 @@ class VehicleTest extends Specification {
ammobox.GUID = PlanetSideGUID(10)
harasser_vehicle.Inventory += 30 -> ammobox
- harasser_vehicle.Find(PlanetSideGUID(10)) mustEqual Some(30)
+ harasser_vehicle.Find(PlanetSideGUID(10)).contains(30)
}
}
}
-class VehicleControlStopMountingTest extends ActorTest {
- "Vehicle Control" should {
- "deactivate and stop handling mount messages" in {
- val player1 = Player(VehicleTest.avatar1)
- player1.GUID = PlanetSideGUID(1)
- val player2 = Player(VehicleTest.avatar2)
- val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
- vehicle.Faction = PlanetSideEmpire.TR
- vehicle.GUID = PlanetSideGUID(3)
- vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
- vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
- VehicleEvents = new TestProbe(system).ref //necessary
- }
- vehicle.Weapons(2).Equipment.get.GUID = PlanetSideGUID(4)
- val probe = new TestProbe(system)
-
- vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
- val reply = probe.receiveOne(Duration.create(200, "ms"))
- assert(reply.isInstanceOf[Mountable.MountMessages])
-
- vehicle.Actor.tell(Vehicle.PrepareForDeletion(), probe.ref)
- vehicle.Actor.tell(Mountable.TryMount(player2, 1), probe.ref)
- probe.expectNoMsg(Duration.create(200, "ms")) //assertion failed: received unexpected message MountMessages(CanMount
- }
- }
-}
-
-class VehicleControlRestartMountingTest extends ActorTest {
- val player1 = Player(VehicleTest.avatar1)
- player1.GUID = PlanetSideGUID(1)
- val player2 = Player(VehicleTest.avatar2)
- player2.GUID = PlanetSideGUID(2)
+class VehicleControlPrepareForDeletionTest extends ActorTest {
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
vehicle.Faction = PlanetSideEmpire.TR
- vehicle.GUID = PlanetSideGUID(3)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
+ val vehicleProbe = new TestProbe(system)
vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
- VehicleEvents = new TestProbe(system).ref
+ VehicleEvents = vehicleProbe.ref
}
- vehicle.Weapons(2).Equipment.get.GUID = PlanetSideGUID(4)
- val probe = new TestProbe(system)
- "Vehicle Control" should {
- "reactivate and resume handling mount messages" in {
- vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
- probe.receiveOne(Duration.create(200, "ms")) //discard
- vehicle.Actor.tell(Vehicle.PrepareForDeletion(), probe.ref)
- vehicle.Actor.tell(Mountable.TryMount(player2, 1), probe.ref)
- probe.expectNoMsg(Duration.create(200, "ms"))
+ vehicle.GUID = PlanetSideGUID(1)
+ expectNoMsg(200 milliseconds)
- vehicle.Actor.tell(Vehicle.Reactivate(), probe.ref)
- vehicle.Actor.tell(Mountable.TryMount(player2, 1), probe.ref)
- val reply = probe.receiveOne(Duration.create(200, "ms"))
- assert(reply.isInstanceOf[Mountable.MountMessages])
+ "VehicleControl" should {
+ "submit for unregistering when marked for deconstruction" in {
+ vehicle.Actor ! Vehicle.Deconstruct()
+
+ val vehicle_msg = vehicleProbe.receiveN(1, 500 milliseconds)
+ assert(
+ vehicle_msg.head match {
+ case VehicleServiceMessage.Decon(RemoverActor.AddTask(v, z, _)) => (v eq vehicle) && (z == vehicle.Zone)
+ case _ => false
+ }
+ )
+
+ val vehicle_msg_final = vehicleProbe.receiveN(1, 6 seconds)
+ assert(
+ vehicle_msg_final.head match {
+ case VehicleServiceMessage("test", VehicleAction.UnloadVehicle(_, z, v, PlanetSideGUID(1))) => (v eq vehicle) && (z == vehicle.Zone)
+ case _ => false
+ }
+ )
}
}
}
-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)
+class VehicleControlPrepareForDeletionPassengerTest extends ActorTest {
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
vehicle.Faction = PlanetSideEmpire.TR
- vehicle.GUID = PlanetSideGUID(3)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
+ val vehicleProbe = new TestProbe(system)
vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
- VehicleEvents = new TestProbe(system).ref
+ VehicleEvents = vehicleProbe.ref
}
- vehicle.Weapons(2).Equipment.get.GUID = PlanetSideGUID(4)
+ val player1 = Player(VehicleTest.avatar1)
- "Vehicle Control" should {
- "always allow dismount messages" in {
- vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
- probe.receiveOne(Duration.create(100, "ms")) //discard
- vehicle.Actor.tell(Mountable.TryMount(player2, 1), probe.ref)
- probe.receiveOne(Duration.create(100, "ms")) //discard
+ vehicle.GUID = PlanetSideGUID(1)
+ player1.GUID = PlanetSideGUID(2)
+ vehicle.Seats(1).Occupant = player1 //passenger seat
+ player1.VehicleSeated = vehicle.GUID
+ expectNoMsg(200 milliseconds)
- vehicle.Actor.tell(Mountable.TryDismount(player2, 1), probe.ref) //player2 requests dismount
- val reply1 = probe.receiveOne(Duration.create(100, "ms"))
- assert(reply1.isInstanceOf[Mountable.MountMessages])
- assert(reply1.asInstanceOf[Mountable.MountMessages].response.isInstanceOf[Mountable.CanDismount]) //player2 dismounts
+ "VehicleControl" should {
+ "kick all players when marked for deconstruction" in {
+ vehicle.Actor ! Vehicle.Deconstruct()
- vehicle.Actor.tell(Vehicle.PrepareForDeletion(), probe.ref)
- vehicle.Actor.tell(Mountable.TryDismount(player1, 0), probe.ref) //player1 requests dismount
- val reply2 = probe.receiveOne(Duration.create(100, "ms"))
- assert(reply2.isInstanceOf[Mountable.MountMessages])
- assert(reply2.asInstanceOf[Mountable.MountMessages].response.isInstanceOf[Mountable.CanDismount]) //player1 dismounts
+ val vehicle_msg = vehicleProbe.receiveN(2, 500 milliseconds)
+ assert(
+ vehicle_msg.head match {
+ case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(2), 4, false, PlanetSideGUID(1))) => true
+ case _ => false
+ }
+ )
+ assert(player1.VehicleSeated.isEmpty)
+ assert(vehicle.Seats(1).Occupant.isEmpty)
+ assert(
+ vehicle_msg(1) match {
+ case VehicleServiceMessage.Decon(RemoverActor.AddTask(v, z, _)) => (v eq vehicle) && (z == vehicle.Zone)
+ case _ => false
+ }
+ )
+ }
+ }
+}
+
+class VehicleControlPrepareForDeletionMountedInTest extends FreedContextActorTest {
+ ServiceManager.boot
+ val guid = new NumberPoolHub(new LimitedNumberSource(10))
+ val zone = new Zone("test", new ZoneMap("test"), 0) {
+ GUID(guid)
+ override def SetupNumberPools() : Unit = { }
+ }
+ zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-zone-actor")
+ zone.Init(context)
+
+ val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
+ vehicle.Faction = PlanetSideEmpire.TR
+ vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test-cargo")
+ vehicle.Zone = zone
+ val lodestar = Vehicle(GlobalDefinitions.lodestar)
+ lodestar.Faction = PlanetSideEmpire.TR
+ val player1 = Player(VehicleTest.avatar1) //name="test1"
+ val player2 = Player(VehicleTest.avatar2) //name="test2"
+
+ guid.register(vehicle, 1)
+ guid.register(lodestar, 2)
+ player1.GUID = PlanetSideGUID(3)
+ var utilityId = 10
+ lodestar.Utilities.values.foreach { util =>
+ util().GUID = PlanetSideGUID(utilityId)
+ utilityId += 1
+ }
+ vehicle.Seats(1).Occupant = player1 //passenger seat
+ player1.VehicleSeated = vehicle.GUID
+ lodestar.Seats(0).Occupant = player2
+ player2.VehicleSeated = lodestar.GUID
+ lodestar.CargoHolds(1).Occupant = vehicle
+ vehicle.MountedIn = lodestar.GUID
+
+ val vehicleProbe = new TestProbe(system)
+ zone.VehicleEvents = vehicleProbe.ref
+ zone.Transport ! Zone.Vehicle.Spawn(lodestar) //can not fake this
+ expectNoMsg(200 milliseconds)
+
+ "VehicleControl" should {
+ "if mounted as cargo, self-eject when marked for deconstruction" in {
+ vehicle.Actor ! Vehicle.Deconstruct()
+
+ val vehicle_msg = vehicleProbe.receiveN(7, 500 milliseconds)
+ //dismounting as cargo messages
+ assert(
+ vehicle_msg.head match {
+ case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))) => true
+ case _ => false
+ }
+ )
+ assert(
+ vehicle_msg(1) match {
+ case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))) => true
+ case _ => false
+ }
+ )
+ assert(
+ vehicle_msg(2) match {
+ case VehicleServiceMessage("test", VehicleAction.SendResponse(_,
+ CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0)
+ )) => true
+ case _ => false
+ }
+ )
+ assert(
+ vehicle_msg(3) match {
+ case VehicleServiceMessage("test", VehicleAction.SendResponse(_,
+ ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _)
+ )) => true
+ case _ => false
+ }
+ )
+ assert(
+ vehicle_msg(4) match {
+ case VehicleServiceMessage("test", VehicleAction.SendResponse(_,
+ CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0)
+ )) => true
+ case _ => false
+ }
+ )
+ //dismounting as cargo messages
+ //TODO: does not actually kick out the cargo, but instigates the process
+ assert(
+ vehicle_msg(5) match {
+ case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(3), 4, false, PlanetSideGUID(1))) => true
+ case _ => false
+ }
+ )
+ assert(player1.VehicleSeated.isEmpty)
+ assert(vehicle.Seats(1).Occupant.isEmpty)
+ assert(
+ vehicle_msg(6) match {
+ case VehicleServiceMessage.Decon(RemoverActor.AddTask(v, z, _)) => (v eq vehicle) && (z == vehicle.Zone)
+ case _ => false
+ }
+ )
+ }
+ }
+}
+
+class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActorTest {
+ val guid = new NumberPoolHub(new LimitedNumberSource(10))
+ ServiceManager.boot
+ val zone = new Zone("test", new ZoneMap("test"), 0) {
+ GUID(guid)
+ override def SetupNumberPools() : Unit = { }
+ }
+ zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-zone-actor")
+ zone.Init(context)
+
+ val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
+ vehicle.Faction = PlanetSideEmpire.TR
+ vehicle.Zone = zone
+ val cargoProbe = new TestProbe(system)
+ vehicle.Actor = cargoProbe.ref
+ val lodestar = Vehicle(GlobalDefinitions.lodestar)
+ lodestar.Faction = PlanetSideEmpire.TR
+ val player1 = Player(VehicleTest.avatar1) //name="test1"
+ val player2 = Player(VehicleTest.avatar2) //name="test2"
+
+ guid.register(vehicle, 1)
+ guid.register(lodestar, 2)
+ player1.GUID = PlanetSideGUID(3)
+ player2.GUID = PlanetSideGUID(4)
+ var utilityId = 10
+ lodestar.Utilities.values.foreach { util =>
+ util().GUID = PlanetSideGUID(utilityId)
+ utilityId += 1
+ }
+ vehicle.Seats(1).Occupant = player1 //passenger seat
+ player1.VehicleSeated = vehicle.GUID
+ lodestar.Seats(0).Occupant = player2
+ player2.VehicleSeated = lodestar.GUID
+ lodestar.CargoHolds(1).Occupant = vehicle
+ vehicle.MountedIn = lodestar.GUID
+
+ val vehicleProbe = new TestProbe(system)
+ zone.VehicleEvents = vehicleProbe.ref
+ zone.Transport ! Zone.Vehicle.Spawn(lodestar) //can not fake this
+ expectNoMsg(200 milliseconds)
+
+ "VehicleControl" should {
+ "if with mounted cargo, eject it when marked for deconstruction" in {
+ lodestar.Actor ! Vehicle.Deconstruct()
+
+ val vehicle_msg = vehicleProbe.receiveN(7, 500 milliseconds)
+ assert(
+ vehicle_msg.head match {
+ case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(4), 4, false, PlanetSideGUID(2))) => true
+ case _ => false
+ }
+ )
+ assert(player2.VehicleSeated.isEmpty)
+ assert(lodestar.Seats(0).Occupant.isEmpty)
+ //cargo dismounting messages
+ assert(
+ vehicle_msg(1) match {
+ case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))) => true
+ case _ => false
+ }
+ )
+ assert(
+ vehicle_msg(2) match {
+ case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))) => true
+ case _ => false
+ }
+ )
+ assert(
+ vehicle_msg(3) match {
+ case VehicleServiceMessage("test", VehicleAction.SendResponse(_,
+ CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0)
+ )) => true
+ case _ => false
+ }
+ )
+ assert(
+ vehicle_msg(4) match {
+ case VehicleServiceMessage("test", VehicleAction.SendResponse(_,
+ ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _)
+ )) => true
+ case _ => false
+ }
+ )
+ assert(
+ vehicle_msg(5) match {
+ case VehicleServiceMessage("test", VehicleAction.SendResponse(_,
+ CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0)
+ )) => true
+ case _ => false
+ }
+ )
+ //cargo dismounting messages
+ assert(
+ vehicle_msg(6) match {
+ case VehicleServiceMessage.Decon(RemoverActor.AddTask(v, z, _)) => (v eq lodestar) && (z == vehicle.Zone)
+ case _ => false
+ }
+ )
}
}
}
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index c132f904..27eabeb2 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -1400,14 +1400,14 @@ class WorldSessionActor extends Actor
else {
inZone.GUID(p.VehicleSeated) match {
case Some(v : Vehicle) if v.Destroyed =>
- inZone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(v), inZone))
- inZone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(v, inZone, if(v.Flying) {
+ v.Actor ! Vehicle.Deconstruct(
+ if(v.Flying) {
//TODO gravity
- Some(0 seconds) //immediate deconstruction
+ None //immediate deconstruction
}
else {
v.Definition.DeconstructionTime //normal deconstruction
- }))
+ })
case _ => ;
}
}
@@ -2283,7 +2283,6 @@ class WorldSessionActor extends Actor
sendResponse(PlanetsideAttributeMessage(obj_guid, 113, capacitor))
}
if(seat_num == 0) {
- continent.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(obj), continent)) //clear timer
//simplistic vehicle ownership management
obj.Owner match {
case Some(owner_guid) =>
@@ -2336,8 +2335,6 @@ class WorldSessionActor extends Actor
case Mountable.CanDismount(obj : Vehicle, seat_num) if obj.Definition == GlobalDefinitions.droppod =>
UnAccessContents(obj)
DismountAction(tplayer, obj, seat_num)
- continent.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(obj), continent))
- continent.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(obj, continent, obj.Definition.DeconstructionTime))
case Mountable.CanDismount(obj : Vehicle, seat_num) =>
val player_guid : PlanetSideGUID = tplayer.GUID
@@ -3007,9 +3004,8 @@ class WorldSessionActor extends Actor
)
}
- case msg@VehicleResponse.KickPassenger(seat_num, wasKickedByDriver, vehicle_guid) =>
+ case VehicleResponse.KickPassenger(seat_num, wasKickedByDriver, vehicle_guid) =>
// seat_num seems to be correct if passenger is kicked manually by driver, but always seems to return 4 if user is kicked by seat permissions
- log.info(s"$msg")
sendResponse(DismountVehicleMsg(guid, BailType.Kicked, wasKickedByDriver))
if(tplayer_guid == guid) {
continent.GUID(vehicle_guid) match {
@@ -3100,9 +3096,6 @@ class WorldSessionActor extends Actor
galaxyService ! Service.Join(temp_channel) //temporary vehicle-specific channel
}
- case VehicleResponse.ForceDismountVehicleCargo(cargo_guid, bailed, requestedByPassenger, kicked) =>
- CargoBehavior.HandleVehicleCargoDismount(continent, tplayer_guid, cargo_guid, bailed, requestedByPassenger, kicked)
-
case VehicleResponse.KickCargo(vehicle, speed, delay) =>
if(player.VehicleSeated.nonEmpty && deadState == DeadState.Alive) {
if(speed > 0) {
@@ -3114,7 +3107,7 @@ class WorldSessionActor extends Actor
controlled = Some(reverseSpeed)
sendResponse(ServerVehicleOverrideMsg(true, true, true, false, 0, strafe, reverseSpeed, Some(0)))
import scala.concurrent.ExecutionContext.Implicits.global
- context.system.scheduler.scheduleOnce(delay milliseconds, self, VehicleServiceResponse(toChannel, tplayer_guid, VehicleResponse.KickCargo(vehicle, 0, delay)))
+ context.system.scheduler.scheduleOnce(delay milliseconds, self, VehicleServiceResponse(toChannel, PlanetSideGUID(0), VehicleResponse.KickCargo(vehicle, 0, delay)))
}
else {
controlled = None
@@ -3650,7 +3643,7 @@ class WorldSessionActor extends Actor
case Some(cargo : Vehicle) if !requestedByPassenger =>
continent.GUID(cargo.MountedIn) match {
case Some(carrier : Vehicle) =>
- CargoBehavior.HandleVehicleCargoDismount(continent, player_guid, cargo_guid, bailed, requestedByPassenger, kicked)
+ CargoBehavior.HandleVehicleCargoDismount(continent, cargo_guid, bailed, requestedByPassenger, kicked)
case _ => ;
}
case _ => ;
@@ -4218,8 +4211,7 @@ class WorldSessionActor extends Actor
case v : Vehicle if seatNum == 0 && v.Flying =>
TotalDriverVehicleControl(v)
UnAccessContents(v)
- continent.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(obj), continent))
- continent.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(obj, continent, Some(0 seconds)))
+ v.Actor ! Vehicle.Deconstruct()
case _ => ;
}
case _ => ; //found no vehicle where one was expected; since we're dead, let's not dwell on it
@@ -5035,8 +5027,7 @@ class WorldSessionActor extends Actor
if((player.VehicleOwned.contains(object_guid) && vehicle.Owner.contains(player.GUID)) ||
(player.Faction == vehicle.Faction &&
((vehicle.Owner.isEmpty || continent.GUID(vehicle.Owner.get).isEmpty) || vehicle.Destroyed))) {
- continent.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(vehicle), continent))
- continent.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent, Some(0 seconds)))
+ vehicle.Actor ! Vehicle.Deconstruct()
log.info(s"RequestDestroy: vehicle $vehicle")
}
else {
@@ -6024,8 +6015,7 @@ class WorldSessionActor extends Actor
//todo: kick cargo passengers out. To be added after PR #216 is merged
obj match {
case v : Vehicle if bailType == BailType.Bailed && seat_num == 0 && v.Flying =>
- continent.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(obj), continent))
- continent.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(obj, continent, Some(0 seconds))) // Immediately deconstruct vehicle
+ v.Actor ! Vehicle.Deconstruct() // Immediately deconstruct vehicle
case _ => ;
}
@@ -9724,7 +9714,7 @@ class WorldSessionActor extends Actor
case ("MISSING_DRIVER", index) =>
val cargo = vehicle.CargoHolds(index).Occupant.get
log.error(s"LoadZoneInVehicleAsDriver: eject cargo in hold $index; vehicle missing driver")
- CargoBehavior.HandleVehicleCargoDismount(pguid, cargo.GUID, cargo, vehicle.GUID, vehicle, false, false, true)
+ CargoBehavior.HandleVehicleCargoDismount(cargo.GUID, cargo, vehicle.GUID, vehicle, false, false, true)
case (name, index) =>
val cargo = vehicle.CargoHolds(index).Occupant.get
continent.VehicleEvents ! VehicleServiceMessage(name, VehicleAction.TransferPassengerChannel(pguid, s"${cargo.Actor}", toChannel, cargo, topLevel))
@@ -10016,8 +10006,6 @@ class WorldSessionActor extends Actor
sendResponse(PlayerStateShiftMessage(ShiftState(0, dest.Position, player.Orientation.z)))
UseRouterTelepadEffect(pguid, sguid, dguid)
StopBundlingPackets()
- // continent.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(router), continent))
- // continent.VehicleEvents p! VehicleServiceMessage.Decon(RemoverActor.AddTask(router, continent, router.Definition.DeconstructionTime))
continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.RouterTelepadTransport(pguid, pguid, sguid, dguid))
}
else {
diff --git a/pslogin/src/test/scala/actor/objects/VehicleSpawnPadTest.scala b/pslogin/src/test/scala/actor/objects/VehicleSpawnPadTest.scala
index e39a7227..03d48ebd 100644
--- a/pslogin/src/test/scala/actor/objects/VehicleSpawnPadTest.scala
+++ b/pslogin/src/test/scala/actor/objects/VehicleSpawnPadTest.scala
@@ -9,6 +9,7 @@ import net.psforever.objects.serverobject.structures.StructureType
import net.psforever.objects.{Avatar, GlobalDefinitions, Player, Vehicle}
import net.psforever.objects.zones.Zone
import net.psforever.types.{PlanetSideGUID, _}
+import services.RemoverActor
import services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.duration._
@@ -107,7 +108,13 @@ class VehicleSpawnControl4Test extends ActorTest {
pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle) //order
- probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.DisposeVehicle])
+ val msg = probe.receiveOne(1 minute)
+ assert(
+ msg match {
+ case VehicleServiceMessage.Decon(RemoverActor.AddTask(v, z , _)) => (v == vehicle) && (z == zone)
+ case _ => false
+ }
+ )
probe.expectNoMsg(5 seconds)
}
}