diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
index 0e542f53..d763cbcc 100644
--- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
+++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
@@ -845,6 +845,53 @@ object GlobalDefinitions {
}
}
+ /**
+ * Using the definition for a `Vehicle` determine whether it can fly.
+ * @param vdef the `VehicleDefinition` of the vehicle
+ * @return `true`, if it is; `false`, otherwise
+ */
+ def isFlightVehicle(vdef : VehicleDefinition) : Boolean = {
+ vdef match {
+ case `mosquito` | `lightgunship` | `wasp` | `liberator` | `vulture` | `phantasm` | `lodestar` | `dropship` | `galaxy_gunship` =>
+ true
+ case _ =>
+ false
+ }
+ }
+
+ /**
+ * Using the definition for a `Vehicle` determine whether it hovers.
+ * @param vdef the `VehicleDefinition` of the vehicle
+ * @return `true`, if it can; `false`, otherwise
+ */
+ def isHoverVehicle(vdef : VehicleDefinition) : Boolean = {
+ vdef match {
+ case `twomanhoverbuggy` | `magrider` | `router` | `flail` =>
+ true
+ case _ =>
+ false
+ }
+ }
+
+ /**
+ * Using the definition for a `Vehicle` determine whether it can rotate its body without forward acceleration.
+ * @param vdef the `VehicleDefinition` of the vehicle
+ * @return `true`, if it is; `false`, otherwise
+ */
+ def canStationaryRotate(vdef : VehicleDefinition) : Boolean = {
+ if(isFlightVehicle(vdef) || isHoverVehicle(vdef)) {
+ true
+ }
+ else {
+ vdef match {
+ case `lightning` | `prowler` | `vanguard` =>
+ true
+ case _ =>
+ false
+ }
+ }
+ }
+
/**
* Initialize `AmmoBoxDefinition` globals.
*/
@@ -2034,6 +2081,7 @@ object GlobalDefinitions {
* Initialize `VehicleDefinition` globals.
*/
private def init_vehicles() : Unit = {
+ fury.Name = "fury"
fury.Seats += 0 -> new SeatDefinition()
fury.Seats(0).Bailable = true
fury.Seats(0).ControlledWeapon = 1
@@ -2042,7 +2090,9 @@ object GlobalDefinitions {
fury.MountPoints += 2 -> 0
fury.TrunkSize = InventoryTile.Tile1111
fury.TrunkOffset = 30
+ fury.AutoPilotSpeeds = (24, 10)
+ quadassault.Name = "quadassault"
quadassault.Seats += 0 -> new SeatDefinition()
quadassault.Seats(0).Bailable = true
quadassault.Seats(0).ControlledWeapon = 1
@@ -2051,7 +2101,9 @@ object GlobalDefinitions {
quadassault.MountPoints += 2 -> 0
quadassault.TrunkSize = InventoryTile.Tile1111
quadassault.TrunkOffset = 30
+ quadassault.AutoPilotSpeeds = (24, 10)
+ quadstealth.Name = "quadstealth"
quadstealth.CanCloak = true
quadstealth.Seats += 0 -> new SeatDefinition()
quadstealth.Seats(0).Bailable = true
@@ -2060,7 +2112,9 @@ object GlobalDefinitions {
quadstealth.MountPoints += 2 -> 0
quadstealth.TrunkSize = InventoryTile.Tile1111
quadstealth.TrunkOffset = 30
+ quadstealth.AutoPilotSpeeds = (24, 10)
+ two_man_assault_buggy.Name = "two_man_assault_buggy"
two_man_assault_buggy.Seats += 0 -> new SeatDefinition()
two_man_assault_buggy.Seats(0).Bailable = true
two_man_assault_buggy.Seats += 1 -> new SeatDefinition()
@@ -2071,7 +2125,9 @@ object GlobalDefinitions {
two_man_assault_buggy.MountPoints += 2 -> 1
two_man_assault_buggy.TrunkSize = InventoryTile.Tile1511
two_man_assault_buggy.TrunkOffset = 30
+ two_man_assault_buggy.AutoPilotSpeeds = (22, 8)
+ skyguard.Name = "skyguard"
skyguard.Seats += 0 -> new SeatDefinition()
skyguard.Seats(0).Bailable = true
skyguard.Seats += 1 -> new SeatDefinition()
@@ -2083,7 +2139,9 @@ object GlobalDefinitions {
skyguard.MountPoints += 3 -> 1
skyguard.TrunkSize = InventoryTile.Tile1511
skyguard.TrunkOffset = 30
+ skyguard.AutoPilotSpeeds = (22, 8)
+ threemanheavybuggy.Name = "threemanheavybuggy"
threemanheavybuggy.Seats += 0 -> new SeatDefinition()
threemanheavybuggy.Seats(0).Bailable = true
threemanheavybuggy.Seats += 1 -> new SeatDefinition()
@@ -2099,7 +2157,9 @@ object GlobalDefinitions {
threemanheavybuggy.MountPoints += 3 -> 2
threemanheavybuggy.TrunkSize = InventoryTile.Tile1511
threemanheavybuggy.TrunkOffset = 30
+ threemanheavybuggy.AutoPilotSpeeds = (22, 8)
+ twomanheavybuggy.Name = "twomanheavybuggy"
twomanheavybuggy.Seats += 0 -> new SeatDefinition()
twomanheavybuggy.Seats(0).Bailable = true
twomanheavybuggy.Seats += 1 -> new SeatDefinition()
@@ -2110,7 +2170,9 @@ object GlobalDefinitions {
twomanheavybuggy.MountPoints += 2 -> 1
twomanheavybuggy.TrunkSize = InventoryTile.Tile1511
twomanheavybuggy.TrunkOffset = 30
+ twomanheavybuggy.AutoPilotSpeeds = (22, 8)
+ twomanhoverbuggy.Name = "twomanhoverbuggy"
twomanhoverbuggy.Seats += 0 -> new SeatDefinition()
twomanhoverbuggy.Seats(0).Bailable = true
twomanhoverbuggy.Seats += 1 -> new SeatDefinition()
@@ -2121,7 +2183,9 @@ object GlobalDefinitions {
twomanhoverbuggy.MountPoints += 2 -> 1
twomanhoverbuggy.TrunkSize = InventoryTile.Tile1511
twomanhoverbuggy.TrunkOffset = 30
+ twomanhoverbuggy.AutoPilotSpeeds = (22, 10)
+ mediumtransport.Name = "mediumtransport"
mediumtransport.Seats += 0 -> new SeatDefinition()
mediumtransport.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
mediumtransport.Seats += 1 -> new SeatDefinition()
@@ -2139,7 +2203,9 @@ object GlobalDefinitions {
mediumtransport.MountPoints += 5 -> 4
mediumtransport.TrunkSize = InventoryTile.Tile1515
mediumtransport.TrunkOffset = 30
+ mediumtransport.AutoPilotSpeeds = (18, 6)
+ battlewagon.Name = "battlewagon"
battlewagon.Seats += 0 -> new SeatDefinition()
battlewagon.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
battlewagon.Seats += 1 -> new SeatDefinition()
@@ -2161,7 +2227,9 @@ object GlobalDefinitions {
battlewagon.MountPoints += 5 -> 4
battlewagon.TrunkSize = InventoryTile.Tile1515
battlewagon.TrunkOffset = 30
+ battlewagon.AutoPilotSpeeds = (18, 6)
+ thunderer.Name = "thunderer"
thunderer.Seats += 0 -> new SeatDefinition()
thunderer.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
thunderer.Seats += 1 -> new SeatDefinition()
@@ -2179,7 +2247,9 @@ object GlobalDefinitions {
thunderer.MountPoints += 5 -> 4
thunderer.TrunkSize = InventoryTile.Tile1515
thunderer.TrunkOffset = 30
+ thunderer.AutoPilotSpeeds = (18, 6)
+ aurora.Name = "aurora"
aurora.Seats += 0 -> new SeatDefinition()
aurora.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
aurora.Seats += 1 -> new SeatDefinition()
@@ -2197,7 +2267,9 @@ object GlobalDefinitions {
aurora.MountPoints += 5 -> 4
aurora.TrunkSize = InventoryTile.Tile1515
aurora.TrunkOffset = 30
+ aurora.AutoPilotSpeeds = (18, 6)
+ apc_tr.Name = "apc_tr"
apc_tr.Seats += 0 -> new SeatDefinition()
apc_tr.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
apc_tr.Seats += 1 -> new SeatDefinition()
@@ -2238,7 +2310,9 @@ object GlobalDefinitions {
apc_tr.MountPoints += 12 -> 10
apc_tr.TrunkSize = InventoryTile.Tile2016
apc_tr.TrunkOffset = 30
+ apc_tr.AutoPilotSpeeds = (16, 6)
+ apc_nc.Name = "apc_nc"
apc_nc.Seats += 0 -> new SeatDefinition()
apc_nc.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
apc_nc.Seats += 1 -> new SeatDefinition()
@@ -2279,7 +2353,9 @@ object GlobalDefinitions {
apc_nc.MountPoints += 12 -> 10
apc_nc.TrunkSize = InventoryTile.Tile2016
apc_nc.TrunkOffset = 30
+ apc_nc.AutoPilotSpeeds = (16, 6)
+ apc_vs.Name = "apc_vs"
apc_vs.Seats += 0 -> new SeatDefinition()
apc_vs.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
apc_vs.Seats += 1 -> new SeatDefinition()
@@ -2320,7 +2396,9 @@ object GlobalDefinitions {
apc_vs.MountPoints += 12 -> 10
apc_vs.TrunkSize = InventoryTile.Tile2016
apc_vs.TrunkOffset = 30
+ apc_vs.AutoPilotSpeeds = (16, 6)
+ lightning.Name = "lightning"
lightning.Seats += 0 -> new SeatDefinition()
lightning.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
lightning.Seats(0).ControlledWeapon = 1
@@ -2329,7 +2407,9 @@ object GlobalDefinitions {
lightning.MountPoints += 2 -> 0
lightning.TrunkSize = InventoryTile.Tile1511
lightning.TrunkOffset = 30
+ lightning.AutoPilotSpeeds = (20, 8)
+ prowler.Name = "prowler"
prowler.Seats += 0 -> new SeatDefinition()
prowler.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
prowler.Seats += 1 -> new SeatDefinition()
@@ -2343,7 +2423,9 @@ object GlobalDefinitions {
prowler.MountPoints += 3 -> 2
prowler.TrunkSize = InventoryTile.Tile1511
prowler.TrunkOffset = 30
+ prowler.AutoPilotSpeeds = (14, 6)
+ vanguard.Name = "vanguard"
vanguard.Seats += 0 -> new SeatDefinition()
vanguard.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
vanguard.Seats += 1 -> new SeatDefinition()
@@ -2353,7 +2435,9 @@ object GlobalDefinitions {
vanguard.MountPoints += 2 -> 1
vanguard.TrunkSize = InventoryTile.Tile1511
vanguard.TrunkOffset = 30
+ vanguard.AutoPilotSpeeds = (16, 6)
+ magrider.Name = "magrider"
magrider.Seats += 0 -> new SeatDefinition()
magrider.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
magrider.Seats(0).ControlledWeapon = 2
@@ -2365,14 +2449,18 @@ object GlobalDefinitions {
magrider.MountPoints += 2 -> 1
magrider.TrunkSize = InventoryTile.Tile1511
magrider.TrunkOffset = 30
+ magrider.AutoPilotSpeeds = (18, 6)
val utilityConverter = new UtilityVehicleConverter
+ ant.Name = "ant"
ant.Seats += 0 -> new SeatDefinition()
ant.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
ant.MountPoints += 1 -> 0
ant.MountPoints += 2 -> 0
+ ant.AutoPilotSpeeds = (18, 6)
ant.Packet = utilityConverter
+ ams.Name = "ams"
ams.Seats += 0 -> new SeatDefinition()
ams.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
ams.MountPoints += 1 -> 0
@@ -2384,9 +2472,11 @@ object GlobalDefinitions {
ams.Deployment = true
ams.DeployTime = 2000
ams.UndeployTime = 2000
+ ams.AutoPilotSpeeds = (18, 6)
ams.Packet = utilityConverter
val variantConverter = new VariantVehicleConverter
+ router.Name = "router"
router.Seats += 0 -> new SeatDefinition()
router.MountPoints += 1 -> 0
router.TrunkSize = InventoryTile.Tile1511
@@ -2394,8 +2484,10 @@ object GlobalDefinitions {
router.Deployment = true
router.DeployTime = 2000
router.UndeployTime = 2000
+ router.AutoPilotSpeeds = (16, 6)
router.Packet = variantConverter
+ switchblade.Name = "switchblade"
switchblade.Seats += 0 -> new SeatDefinition()
switchblade.Seats(0).ControlledWeapon = 1
switchblade.Weapons += 1 -> scythe
@@ -2406,8 +2498,10 @@ object GlobalDefinitions {
switchblade.Deployment = true
switchblade.DeployTime = 2000
switchblade.UndeployTime = 2000
+ switchblade.AutoPilotSpeeds = (22, 8)
switchblade.Packet = variantConverter
+ flail.Name = "flail"
flail.Seats += 0 -> new SeatDefinition()
flail.Seats(0).ControlledWeapon = 1
flail.Weapons += 1 -> flail_weapon
@@ -2417,8 +2511,10 @@ object GlobalDefinitions {
flail.Deployment = true
flail.DeployTime = 2000
flail.UndeployTime = 2000
+ flail.AutoPilotSpeeds = (14, 6)
flail.Packet = variantConverter
+ mosquito.Name = "mosquito"
mosquito.Seats += 0 -> new SeatDefinition()
mosquito.Seats(0).Bailable = true
mosquito.Seats(0).ControlledWeapon = 1
@@ -2427,8 +2523,10 @@ object GlobalDefinitions {
mosquito.MountPoints += 2 -> 0
mosquito.TrunkSize = InventoryTile.Tile1111
mosquito.TrunkOffset = 30
+ mosquito.AutoPilotSpeeds = (0, 6)
mosquito.Packet = variantConverter
+ lightgunship.Name = "lightgunship"
lightgunship.Seats += 0 -> new SeatDefinition()
lightgunship.Seats(0).Bailable = true
lightgunship.Seats(0).ControlledWeapon = 1
@@ -2437,8 +2535,10 @@ object GlobalDefinitions {
lightgunship.MountPoints += 2 -> 0
lightgunship.TrunkSize = InventoryTile.Tile1511
lightgunship.TrunkOffset = 30
+ lightgunship.AutoPilotSpeeds = (0, 4)
lightgunship.Packet = variantConverter
+ wasp.Name = "wasp"
wasp.Seats += 0 -> new SeatDefinition()
wasp.Seats(0).Bailable = true
wasp.Seats(0).ControlledWeapon = 1
@@ -2447,8 +2547,10 @@ object GlobalDefinitions {
wasp.MountPoints += 2 -> 0
wasp.TrunkSize = InventoryTile.Tile1111
wasp.TrunkOffset = 30
+ wasp.AutoPilotSpeeds = (0, 6)
wasp.Packet = variantConverter
+ liberator.Name = "liberator"
liberator.Seats += 0 -> new SeatDefinition()
liberator.Seats(0).ControlledWeapon = 3
liberator.Seats += 1 -> new SeatDefinition()
@@ -2464,8 +2566,10 @@ object GlobalDefinitions {
liberator.MountPoints += 4 -> 2
liberator.TrunkSize = InventoryTile.Tile1515
liberator.TrunkOffset = 30
+ liberator.AutoPilotSpeeds = (0, 4)
liberator.Packet = variantConverter
+ vulture.Name = "vulture"
vulture.Seats += 0 -> new SeatDefinition()
vulture.Seats(0).ControlledWeapon = 3
vulture.Seats += 1 -> new SeatDefinition()
@@ -2481,8 +2585,10 @@ object GlobalDefinitions {
vulture.MountPoints += 4 -> 2
vulture.TrunkSize = InventoryTile.Tile1611
vulture.TrunkOffset = 30
+ vulture.AutoPilotSpeeds = (0, 4)
vulture.Packet = variantConverter
+ dropship.Name = "dropship"
dropship.Seats += 0 -> new SeatDefinition()
dropship.Seats += 1 -> new SeatDefinition()
dropship.Seats(1).Bailable = true
@@ -2528,8 +2634,10 @@ object GlobalDefinitions {
dropship.MountPoints += 12 -> 10
dropship.TrunkSize = InventoryTile.Tile1612
dropship.TrunkOffset = 30
+ dropship.AutoPilotSpeeds = (0, 4)
dropship.Packet = variantConverter
+ galaxy_gunship.Name = "galaxy_gunship"
galaxy_gunship.Seats += 0 -> new SeatDefinition()
galaxy_gunship.Seats += 1 -> new SeatDefinition()
galaxy_gunship.Seats(1).ControlledWeapon = 6
@@ -2554,14 +2662,18 @@ object GlobalDefinitions {
galaxy_gunship.MountPoints += 6 -> 5
galaxy_gunship.TrunkSize = InventoryTile.Tile1816
galaxy_gunship.TrunkOffset = 30
+ galaxy_gunship.AutoPilotSpeeds = (0, 4)
galaxy_gunship.Packet = variantConverter
+ lodestar.Name = "lodestar"
lodestar.Seats += 0 -> new SeatDefinition()
lodestar.MountPoints += 1 -> 0
lodestar.TrunkSize = InventoryTile.Tile1612
lodestar.TrunkOffset = 30
+ lodestar.AutoPilotSpeeds = (0, 4)
lodestar.Packet = variantConverter
+ phantasm.Name = "phantasm"
phantasm.CanCloak = true
phantasm.Seats += 0 -> new SeatDefinition()
phantasm.Seats += 1 -> new SeatDefinition()
@@ -2579,6 +2691,7 @@ object GlobalDefinitions {
phantasm.MountPoints += 5 -> 4
phantasm.TrunkSize = InventoryTile.Tile1107
phantasm.TrunkOffset = 30
+ phantasm.AutoPilotSpeeds = (0, 6)
phantasm.Packet = variantConverter
}
}
diff --git a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
index 4e5900d4..5bb11e89 100644
--- a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
@@ -28,6 +28,7 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
private var trunkOffset : Int = 0
private var canCloak : Boolean = false
private var canBeOwned : Boolean = true
+ private var serverVehicleOverrideSpeeds : (Int, Int) = (0, 0)
Name = "vehicle"
Packet = VehicleDefinition.converter
@@ -102,6 +103,17 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
trunkOffset = offset
TrunkOffset
}
+
+ def AutoPilotSpeeds : (Int, Int) = serverVehicleOverrideSpeeds
+
+ def AutoPilotSpeeds_=(speeds : (Int, Int)) : (Int, Int) = {
+ serverVehicleOverrideSpeeds = speeds
+ AutoPilotSpeeds
+ }
+
+ def AutoPilotSpeed1 : Int = serverVehicleOverrideSpeeds._1
+
+ def AutoPilotSpeed2 : Int = serverVehicleOverrideSpeeds._2
}
object VehicleDefinition {
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 8f1e75a4..47f8f119 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
@@ -1,205 +1,267 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.pad
-import akka.actor.{Actor, ActorRef, Cancellable}
+import akka.actor.{ActorContext, ActorRef, Cancellable, Props}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
+import net.psforever.objects.serverobject.pad.process.{VehicleSpawnControlBase, VehicleSpawnControlConcealPlayer}
+import net.psforever.objects.zones.Zone
import net.psforever.objects.{DefaultCancellable, Player, Vehicle}
-import net.psforever.types.Vector3
+import scala.annotation.tailrec
+import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
/**
- * An `Actor` that handles messages being dispatched to a specific `VehicleSpawnPad`.
+ * An `Actor` that handles vehicle spawning orders for a `VehicleSpawnPad`.
+ * The basic `VehicleSpawnControl` is the root of a simple tree of "spawn control" objects that chain to each other.
+ * Each object performs on (or more than one related) actions upon the vehicle order that was submitted.
*
+ * The purpose of the base actor is to serve as the entry point for the spawning process.
* A spawn pad receives vehicle orders from an attached `Terminal` object.
- * At the time when the order is received, the player who submitted the order is completely visible
- * and waiting back by the said `Terminal` from where the order was submitted.
- * Assuming no other orders are currently being processed, the repeated self message will retrieve this as the next order.
- * The player character is first made transparent with a `GenericObjectActionMessage` packet.
- * The vehicle model itself is then introduced to the game and three things happen with the following order, more or less:
- * 1. the vehicle is attached to a lifting platform that is designed to introduce the vehicle;
- * 2. the player is seated in the vehicle's driver seat (seat 0) and is thus declared the owner;
- * 3. various properties of the player, the vehicle, and the spawn pad itself are set `PlanetsideAttributesMessage`.
- * When this step is finished, the lifting platform raises the vehicle and the mounted player into the game world.
- * The vehicle detaches and is made to roll off the spawn pad a certain distance before being released to user control.
- * That is what is supposed to happen within a certain measure of timing.
- *
- * The orders that are submitted to the spawn pad must be composed of at least three elements:
- * 1. a player, specifically the one that submitted the order and will be declared the "owner;"
- * 2. a vehicle;
- * 3. a callback location for sending messages.
+ * The control object accepts orders, enqueues them, and,
+ * whenever prompted by a previous complete order or by an absence of active orders,
+ * will select the first available order to be completed.
+ * This order will be "tracked" and will be given to the first functional "spawn control" object of the process.
+ * If the process is completed, or is ever aborted by any of the subsequent tasks,
+ * control will propagate down back to this control object.
* @param pad the `VehicleSpawnPad` object being governed
*/
-class VehicleSpawnControl(pad : VehicleSpawnPad) extends Actor with FactionAffinityBehavior.Check {
- /** an executor for progressing a vehicle order through the normal spawning logic */
- private var process : Cancellable = DefaultCancellable.obj
+class VehicleSpawnControl(pad : VehicleSpawnPad) extends VehicleSpawnControlBase(pad) with FactionAffinityBehavior.Check {
+ /** a reminder sent to future customers */
+ var periodicReminder : Cancellable = DefaultCancellable.obj
/** a list of vehicle orders that have been submitted for this spawn pad */
- private var orders : List[VehicleSpawnControl.OrderEntry] = List.empty[VehicleSpawnControl.OrderEntry]
- /** the current vehicle order being acted upon */
- private var trackedOrder : Option[VehicleSpawnControl.OrderEntry] = None
- /** how many times a spawned vehicle (spatially) disrupted the next vehicle from being spawned */
- private var blockingViolations : Int = 0
- private[this] val log = org.log4s.getLogger
- private[this] def trace(msg : String) : Unit = log.trace(msg)
+ private var orders : List[VehicleSpawnControl.Order] = List.empty[VehicleSpawnControl.Order]
+ /** the current vehicle order being acted upon;
+ * used as a guard condition to control order processing rate */
+ private var trackedOrder : Option[VehicleSpawnControl.Order] = None
+
+ def LogId = ""
+
+ /**
+ * The first chained action of the vehicle spawning process.
+ */
+ val concealPlayer = context.actorOf(Props(classOf[VehicleSpawnControlConcealPlayer], pad), s"${context.parent.path.name}-conceal")
def FactionObject : FactionAffinity = pad
def receive : Receive = checkBehavior.orElse {
case VehicleSpawnPad.VehicleOrder(player, vehicle) =>
trace(s"order from $player for $vehicle received")
- orders = orders :+ VehicleSpawnControl.OrderEntry(player, vehicle, sender)
+ orders = orders :+ VehicleSpawnControl.Order(player, vehicle, sender)
if(trackedOrder.isEmpty && orders.length == 1) {
- self ! VehicleSpawnControl.Process.GetOrder
+ SelectOrder()
+ }
+ else {
+ sender ! VehicleSpawnControl.RenderOrderRemainderMsg(orders.length + 1)
}
- case VehicleSpawnControl.Process.GetOrder =>
- process.cancel
- blockingViolations = 0
- val (completeOrder, remainingOrders) : (Option[VehicleSpawnControl.OrderEntry], List[VehicleSpawnControl.OrderEntry]) = orders match {
- case x :: Nil =>
- (Some(x), Nil)
- case x :: b =>
- (Some(x), b)
- case Nil =>
- (None, Nil)
+ case VehicleSpawnControl.ProcessControl.GetNewOrder =>
+ if(sender == concealPlayer) {
+ trackedOrder = None //guard off
+ SelectOrder()
}
- orders = remainingOrders
- completeOrder match {
+
+ /*
+ When the vehicle is spawned and added to the pad, it will "occupy" the pad and block it from further action.
+ Normally, the player who wanted to spawn the vehicle will be automatically put into the driver seat.
+ If this is blocked, the vehicle will idle on the pad and must be moved far enough away from the point of origin.
+ During this time, a periodic message about the spawn pad being blocked
+ will be broadcast to all current customers in the order queue.
+ */
+ case VehicleSpawnControl.ProcessControl.Reminder =>
+ trackedOrder match {
case Some(entry) =>
- trace(s"processing order $entry")
- trackedOrder = completeOrder
- import scala.concurrent.ExecutionContext.Implicits.global
- process = context.system.scheduler.scheduleOnce(VehicleSpawnControl.concealPlayerTimeout, self, VehicleSpawnControl.Process.ConcealPlayer)
+ if(periodicReminder.isCancelled) {
+ trace (s"the pad has become blocked by ${entry.vehicle.Definition.Name}")
+ periodicReminder = context.system.scheduler.schedule(
+ VehicleSpawnControl.initialReminderDelay,
+ VehicleSpawnControl.periodicReminderDelay,
+ self, VehicleSpawnControl.ProcessControl.Reminder
+ )
+ }
+ else {
+ VehicleSpawnControl.BlockedReminder(entry, entry +: orders)
+ }
case None => ;
- }
-
- case VehicleSpawnControl.Process.ConcealPlayer =>
- process.cancel
- trackedOrder match {
- case Some(entry) =>
- if(entry.player.isAlive && entry.vehicle.Actor != ActorRef.noSender && entry.sendTo != ActorRef.noSender && entry.player.VehicleSeated.isEmpty) {
- trace(s"hiding player: ${entry.player}")
- entry.sendTo ! VehicleSpawnPad.ConcealPlayer
- import scala.concurrent.ExecutionContext.Implicits.global
- process = context.system.scheduler.scheduleOnce(VehicleSpawnControl.loadVehicleTimeout, self, VehicleSpawnControl.Process.LoadVehicle)
- }
- else {
- trace("integral component lost; abort order fulfillment")
- //TODO Unregister vehicle ... somehow
- trackedOrder = None
- self ! VehicleSpawnControl.Process.GetOrder
- }
- case None =>
- self ! VehicleSpawnControl.Process.GetOrder
- }
-
- case VehicleSpawnControl.Process.LoadVehicle =>
- process.cancel
- trackedOrder match {
- case Some(entry) =>
- if(entry.vehicle.Actor != ActorRef.noSender && entry.sendTo != ActorRef.noSender) {
- trace(s"loading vehicle: ${entry.vehicle} defined in order")
- entry.sendTo ! VehicleSpawnPad.LoadVehicle(entry.vehicle, pad)
- import scala.concurrent.ExecutionContext.Implicits.global
- process = context.system.scheduler.scheduleOnce(VehicleSpawnControl.awaitSeatedTimeout, self, VehicleSpawnControl.Process.AwaitSeated)
- }
- else {
- trace("owner or vehicle lost; abort order fulfillment")
- //TODO Unregister vehicle ... somehow
- trackedOrder = None
- self ! VehicleSpawnControl.Process.GetOrder
- }
-
- case None =>
- self ! VehicleSpawnControl.Process.GetOrder
- }
-
- case VehicleSpawnControl.Process.AwaitSeated =>
- process.cancel
- trackedOrder match {
- case Some(entry) =>
- if(entry.sendTo != ActorRef.noSender) {
- trace("owner seated in vehicle")
- import scala.concurrent.ExecutionContext.Implicits.global
- process = if(entry.player.VehicleOwned.contains(entry.vehicle.GUID)) {
- entry.sendTo ! VehicleSpawnPad.PlayerSeatedInVehicle(entry.vehicle)
- context.system.scheduler.scheduleOnce(VehicleSpawnControl.awaitClearanceTimeout, self, VehicleSpawnControl.Process.AwaitClearance)
- }
- else {
- context.system.scheduler.scheduleOnce(VehicleSpawnControl.awaitSeatedTimeout, self, VehicleSpawnControl.Process.AwaitSeated)
- }
- }
- else {
- trace("owner lost; abort order fulfillment")
- trackedOrder = None
- self ! VehicleSpawnControl.Process.GetOrder
- }
- case None =>
- self ! VehicleSpawnControl.Process.GetOrder
- }
-
- //TODO raise spawn pad rails from ground
-
- //TODO start auto drive away
-
- //TODO release auto drive away
-
- case VehicleSpawnControl.Process.AwaitClearance =>
- process.cancel
- trackedOrder match {
- case Some(entry) =>
- if(entry.sendTo == ActorRef.noSender || entry.vehicle.Actor == ActorRef.noSender) {
- trace("integral component lost, but order fulfilled; process next order")
- trackedOrder = None
- self ! VehicleSpawnControl.Process.GetOrder
- }
- else if(Vector3.DistanceSquared(entry.vehicle.Position, pad.Position) > 100.0f) { //10m away from pad
- trace("pad cleared; process next order")
- trackedOrder = None
- entry.sendTo ! VehicleSpawnPad.SpawnPadUnblocked(entry.vehicle.GUID)
- self ! VehicleSpawnControl.Process.GetOrder
- }
- else {
- trace(s"pad blocked by ${entry.vehicle} ...")
- blockingViolations += 1
- entry.sendTo ! VehicleSpawnPad.SpawnPadBlockedWarning(entry.vehicle, blockingViolations)
- import scala.concurrent.ExecutionContext.Implicits.global
- process = context.system.scheduler.scheduleOnce(VehicleSpawnControl.awaitClearanceTimeout, self, VehicleSpawnControl.Process.AwaitClearance)
- }
- case None =>
- self ! VehicleSpawnControl.Process.GetOrder
+ periodicReminder.cancel
}
case _ => ;
}
+
+ def SelectOrder() : Unit = {
+ trackedOrder match {
+ case None =>
+ periodicReminder.cancel
+ val (completeOrder, remainingOrders) : (Option[VehicleSpawnControl.Order], List[VehicleSpawnControl.Order]) = orders match {
+ case x :: Nil =>
+ (Some(x), Nil)
+ case x :: b =>
+ trace(s"order backlog size: ${b.size}")
+ VehicleSpawnControl.recursiveOrderReminder(b.iterator)
+ (Some(x), b)
+ case Nil =>
+ (None, Nil)
+ }
+ orders = remainingOrders
+ completeOrder match {
+ case Some(entry) =>
+ trace(s"processing next order - a ${entry.vehicle.Definition.Name} for ${entry.driver.Name}")
+ trackedOrder = completeOrder //guard on
+ context.system.scheduler.scheduleOnce(2000 milliseconds, concealPlayer, VehicleSpawnControl.Process.ConcealPlayer(entry))
+ case None =>
+ trackedOrder = None
+ }
+ case Some(_) => ; //do not work on new orders
+ }
+ }
}
object VehicleSpawnControl {
- final val concealPlayerTimeout : FiniteDuration = 2000000000L nanoseconds //2s
- final val loadVehicleTimeout : FiniteDuration = 1000000000L nanoseconds //1s
- final val awaitSeatedTimeout : FiniteDuration = 1000000000L nanoseconds //1s
- final val awaitClearanceTimeout : FiniteDuration = 5000000000L nanoseconds //5s
+ private final val initialReminderDelay : FiniteDuration = 10000 milliseconds
+ private final val periodicReminderDelay : FiniteDuration = 10000 milliseconds
/**
- * An `Enumeration` of the stages of a full vehicle spawning process, associated with certain messages passed.
- * Some stages are currently TEMPORARY.
- * @see VehicleSpawnPad
+ * An `Enumeration` of non-data control messages for the vehicle spawn process.
*/
- object Process extends Enumeration {
+ object ProcessControl extends Enumeration {
val
- GetOrder,
- ConcealPlayer,
- LoadVehicle,
- AwaitSeated,
- AwaitClearance
+ Reminder,
+ GetNewOrder
= Value
}
+ /**
+ * An `Enumeration` of the stages of a full vehicle spawning process, passing the current order being processed.
+ * Messages in this group are used by the `receive` entry points of the multiple child objects
+ * that perform the vehicle spawning operation.
+ */
+ object Process {
+ sealed class Order(entry : VehicleSpawnControl.Order)
+
+ final case class ConcealPlayer(entry : VehicleSpawnControl.Order) extends Order(entry)
+ final case class LoadVehicle(entry : VehicleSpawnControl.Order) extends Order(entry)
+ final case class SeatDriver(entry : VehicleSpawnControl.Order) extends Order(entry)
+ final case class RailJackAction(entry : VehicleSpawnControl.Order) extends Order(entry)
+ final case class ServerVehicleOverride(entry : VehicleSpawnControl.Order) extends Order(entry)
+ final case class StartGuided(entry : VehicleSpawnControl.Order) extends Order(entry)
+ final case class DriverVehicleControl(entry : VehicleSpawnControl.Order) extends Order(entry)
+ final case class FinalClearance(entry : VehicleSpawnControl.Order) extends Order(entry)
+ }
/**
* An entry that stores vehicle spawn pad spawning tasks (called "orders").
- * @param player the player
+ * @param driver the player who wants the vehicle
* @param vehicle the vehicle
* @param sendTo a callback `Actor` associated with the player (in other words, `WorldSessionActor`)
*/
- private final case class OrderEntry(player : Player, vehicle : Vehicle, sendTo : ActorRef)
+ final case class Order(driver : Player, vehicle : Vehicle, sendTo : ActorRef)
+
+ /**
+ * Properly clean up a vehicle that has been registered, but not yet been spawned into the game world.
+ *
+ * Constructs a temporary `TaskResolver` to deal with the vehicle's registration status.
+ * This "temporary" router will persist as if it were a `static` variable in some other language
+ * due to the fact that the `ActorSystem` object will remember it existing.
+ * After the primary task is complete, the router that was created is stopped so that it can be garbage collected.
+ * We could re-use it theoretically, but the `context` might be untrustworthy.
+ * @param entry the order being cancelled
+ * @param zone the continent on which the vehicle was registered
+ * @param context an `ActorContext` object for which to create the `TaskResolver` object
+ */
+ def DisposeVehicle(entry : VehicleSpawnControl.Order, zone: Zone)(implicit context : ActorContext) : Unit = {
+ import akka.actor.{ActorRef, PoisonPill}
+ import akka.routing.SmallestMailboxPool
+ import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
+ import net.psforever.types.Vector3
+ val vehicle = entry.vehicle
+ vehicle.Position = Vector3.Zero
+ zone.VehicleEvents ! VehicleSpawnPad.RevealPlayer(entry.driver.GUID, zone.Id)
+
+ val router = context.actorOf(
+ SmallestMailboxPool(10).props(Props[TaskResolver]),
+ s"vehicle-spawn-control-emergency-decon-resolver-${System.nanoTime}"
+ )
+ router !
+ TaskResolver.GiveTask(
+ new Task() {
+ private val localRouter = router
+
+ override def isComplete = Task.Resolution.Success
+
+ def Execute(resolver : ActorRef) : Unit = {
+ resolver ! scala.util.Success(this)
+ }
+
+ override def Cleanup() : Unit = { localRouter ! PoisonPill } //where the router is stopped
+ }, List(GUIDTask.UnregisterVehicle(vehicle)(zone.GUID))
+ )
+ }
+
+ /**
+ * Properly clean up a vehicle that has been registered and spawned into the game world.
+ * @param entry the order being cancelled
+ * @param zone the continent on which the vehicle was registered
+ */
+ def DisposeSpawnedVehicle(entry : VehicleSpawnControl.Order, zone: Zone) : Unit = {
+ //TODO this cleanup will handle the vehicle; but, the former driver may be thrown into the void
+ zone.VehicleEvents ! VehicleSpawnPad.DisposeVehicle(entry.vehicle, zone)
+ zone.VehicleEvents ! VehicleSpawnPad.RevealPlayer(entry.driver.GUID, zone.Id)
+ }
+
+ /**
+ * Remind a customer how long it will take for their vehicle order to be processed.
+ * @param position position in the queue
+ * @return an index-appropriate `VehicleSpawnPad.PeriodicReminder` object
+ */
+ def RenderOrderRemainderMsg(position : Int) : VehicleSpawnPad.PeriodicReminder = {
+ VehicleSpawnPad.PeriodicReminder(VehicleSpawnPad.Reminders.Queue, Some(s"$position"))
+ }
+
+ /**
+ *
+ * @param blockedOrder the previous order whose vehicle is blocking the spawn pad from operating
+ * @param recipients all of the customers who will be receiving the message
+ */
+ def BlockedReminder(blockedOrder : VehicleSpawnControl.Order, recipients : Seq[VehicleSpawnControl.Order]) : Unit = {
+ val wrecked : Option[Any] = if(blockedOrder.vehicle.Health == 0) {
+ Option("Clear the wreckage.")
+ }
+ else {
+ None
+ }
+ VehicleSpawnControl.recursiveBlockedReminder(recipients.iterator, wrecked)
+ }
+
+// @tailrec private final def recursiveFindOrder(iter : Iterator[VehicleSpawnControl.Order], target : ActorRef, index : Int = 0) : Option[Int] = {
+// if(!iter.hasNext) {
+// None
+// }
+// else {
+// val recipient = iter.next
+// if(recipient.sendTo == target) {
+// Some(index)
+// }
+// else {
+// recursiveFindOrder(iter, target, index + 1)
+// }
+// }
+// }
+
+ @tailrec private final def recursiveBlockedReminder(iter : Iterator[VehicleSpawnControl.Order], cause : Option[Any]) : Unit = {
+ if(iter.hasNext) {
+ val recipient = iter.next
+ recipient.sendTo ! VehicleSpawnPad.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, cause)
+ recursiveBlockedReminder(iter, cause)
+ }
+ }
+
+ @tailrec private final def recursiveOrderReminder(iter : Iterator[VehicleSpawnControl.Order], position : Int = 2) : Unit = {
+ if(iter.hasNext) {
+ val recipient = iter.next
+ if(recipient.sendTo != ActorRef.noSender) {
+ recipient.sendTo ! RenderOrderRemainderMsg(position)
+ }
+ recursiveOrderReminder(iter, position + 1)
+ }
+ }
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala
index 19df456d..7412b7cd 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala
@@ -1,8 +1,10 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.pad
+import net.psforever.objects.serverobject.pad.process.AutoDriveControls
import net.psforever.objects.{Player, Vehicle}
import net.psforever.objects.serverobject.structures.Amenity
+import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
/**
@@ -12,17 +14,43 @@ import net.psforever.packet.game.PlanetSideGUID
* maintain the operative queue that introduces the vehicle into the game world and applies initial activity to it and
* maintain a position and a direction where the vehicle will be made to appear (as a `PlanetSideServerObject`).
* The actual functionality managed by this object is wholly found on its accompanying `Actor`.
- * @param spDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
* @see `VehicleSpawnControl`
+ * @param spDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
class VehicleSpawnPad(spDef : VehicleSpawnPadDefinition) extends Amenity {
+ /**
+ * Use the in-game railed platform to lift the spawned vehicle out of the trench.
+ * When set, the client performs the standard vehicle entry procedure, including lifting platform animations.
+ * When unset, the client depicts the player manually boarding the new vehicle within the trench area.
+ * Eventually, the vehicle is then hoisted out into the open; without this set, that hoisting is abrupt.
+ * The main reason to disable this feature is to avoid an `ObjectAttachMessage` for an incorrect object designation.
+ * Unset if not guaranteed to have the correct globally unique id of the spawn pad.
+ */
+ private var onRails : Boolean = true
+
+ private var guidedPath : List[AutoDriveControls.Configuration] = Nil
+
+ def Railed : Boolean = onRails
+
+ def Railed_=(useRails : Boolean) : Boolean = {
+ onRails = useRails
+ Railed
+ }
+
+ def Guide : List[AutoDriveControls.Configuration] = guidedPath
+
+ def Guide_=(path : List[AutoDriveControls.Configuration]) : List[AutoDriveControls.Configuration] = {
+ guidedPath = path
+ Guide
+ }
+
def Definition : VehicleSpawnPadDefinition = spDef
}
object VehicleSpawnPad {
/**
- * Communicate to the spawn pad that it should enqueue the following vehicle.
+ * Message to the spawn pad to enqueue the following vehicle order.
* This is the entry point to vehicle spawn pad functionality.
* @param player the player who submitted the order (the "owner")
* @param vehicle the vehicle produced from the order
@@ -30,52 +58,114 @@ object VehicleSpawnPad {
final case class VehicleOrder(player : Player, vehicle : Vehicle)
/**
- * The first callback step in spawning the vehicle.
- * An packet `GenericObjectActionMessage(/player/, 36)`, when used on a player character,
- * will cause that player character's model to fade into transparency.
+ * Message to indicate that a certain player should be made transparent.
+ * @see `GenericObjectActionMessage`
+ * @param player_guid the player
+ * @param zone_id the zone in which the spawn pad is located
*/
- final case class ConcealPlayer()
+ final case class ConcealPlayer(player_guid : PlanetSideGUID, zone_id : String)
/**
- * A callback step in spawning the vehicle.
- * The vehicle is properly introduced into the game world.
- * If information about the vehicle itself that is important to its spawning has not yet been set,
- * this callback is the last ideal situation to set that properties without having to adjust the vehicle visually.
- * The primary operation that should occur is a content-appropriate `ObjectCreateMessage` packet and
- * having the player sit down in the driver's seat (seat 0) of the vehicle.
+ * Message is intended to undo the effects of the above message, `ConcealPlayer`.
+ * @see `ConcealPlayer`
+ * @param player_guid the player
+ * @param zone_id the zone in which the spawn pad is located
+ */
+ final case class RevealPlayer(player_guid : PlanetSideGUID, zone_id : String)
+
+ /**
+ * Message to properly introduce the vehicle into the zone.
* @param vehicle the vehicle being spawned
- * @param pad the pad
+ * @param zone the zone in which the spawn pad is located
*/
- final case class LoadVehicle(vehicle : Vehicle, pad : VehicleSpawnPad)
+ final case class LoadVehicle(vehicle : Vehicle, zone : Zone)
/**
- * A TEMPORARY callback step in spawning the vehicle.
- * From a state of transparency, while the vehicle is attached to the lifting platform of the spawn pad,
- * the player designated the "owner" by callback is made to sit in the driver's seat (always seat 0).
- * This message is the next step after that.
+ * Message to attach the vehicle to the spawn pad's lifting platform ("put on rails").
+ * The attachment process (to the third slot) itself begins autonomous operation of the lifting platform.
+ * @see `ObjectAttachMessage`
* @param vehicle the vehicle being spawned
+ * @param pad the spawn pad
+ * @param zone_id the zone in which the spawn pad is located
*/
- final case class PlayerSeatedInVehicle(vehicle : Vehicle)
+ final case class AttachToRails(vehicle : Vehicle, pad : VehicleSpawnPad, zone_id : String)
/**
- * A TEMPORARY callback step in (successfully) spawning the vehicle.
- * While the vehicle is still occupying the pad just after being spawned and its driver seat mounted,
- * that vehicle is considered blocking the pad from being used for further spawning operations.
- * This message allows the user to be made known about this blockage.
+ * Message to detach the vehicle from the spawn pad's lifting platform ("put on rails").
+ * @see `ObjectDetachMessage`
+ * @param vehicle the vehicle being spawned
+ * @param pad the spawn pad
+ * @param zone_id the zone in which the spawn pad is located
+ */
+ final case class DetachFromRails(vehicle : Vehicle, pad : VehicleSpawnPad, zone_id : String)
+
+ /**
+ * Message that resets the spawn pad for its next order fulfillment operation by lowering the lifting platform.
+ * @see `GenericObjectActionMessage`
+ * @param pad the spawn pad
+ * @param zone_id the zone in which the spawn pad is located
+ */
+ final case class ResetSpawnPad(pad : VehicleSpawnPad, zone_id : String)
+
+ /**
+ * Message that acts as callback to the driver that the process of sitting in the driver seat will be initiated soon.
+ * This information should only be communicated to the driver's client only.
+ * @param vehicle the vehicle being spawned
+ * @param pad the spawn pad
+ */
+ final case class StartPlayerSeatedInVehicle(vehicle : Vehicle, pad : VehicleSpawnPad)
+
+ /**
+ * Message that acts as callback to the driver that the process of sitting in the driver seat should be finished.
+ * This information should only be communicated to the driver's client only.
+ * @param vehicle the vehicle being spawned
+ * @param pad the spawn pad
+ */
+ final case class PlayerSeatedInVehicle(vehicle : Vehicle, pad : VehicleSpawnPad) //TODO while using fake rails
+
+ /**
+ * Message that starts the newly-spawned vehicle to begin driving away from the spawn pad.
+ * Information about the driving process is available on the vehicle itself.
+ * This information should only be communicated to the driver's client only.
+ * @see `VehicleDefinition`
* @param vehicle the vehicle
- * @param warning_count the number of times a warning period has occurred
+ * @param pad the spawn pad
*/
- final case class SpawnPadBlockedWarning(vehicle : Vehicle, warning_count : Int)
+ final case class ServerVehicleOverrideStart(vehicle : Vehicle, pad : VehicleSpawnPad)
/**
- * A TEMPORARY callback step in (successfully) spawning the vehicle.
- * While the vehicle is still occupying the pad just after being spawned and its driver seat mounted,
- * that vehicle is considered blocking the pad from being used for further spawning operations.
- * A timeout will begin counting until the vehicle is despawned automatically for its driver's negligence.
- * This message is used to clear the deconstruction countdown, primarily.
- * @param vehicle_guid the vehicle
+ * Message that transitions the newly-spawned vehicle into a cancellable auto-drive state.
+ * Information about the driving process is available on the vehicle itself.
+ * This information should only be communicated to the driver's client only.
+ * @see `VehicleDefinition`
+ * @param vehicle the vehicle
+ * @param pad the spawn pad
*/
- final case class SpawnPadUnblocked(vehicle_guid : PlanetSideGUID)
+ final case class ServerVehicleOverrideEnd(vehicle : Vehicle, pad : VehicleSpawnPad)
+
+ /**
+ * Message to initiate the process of properly disposing of the vehicle that may have been or was spawned into the game world.
+ * @param vehicle the vehicle
+ * @param zone the zone in which the spawn pad is located
+ */
+ final case class DisposeVehicle(vehicle : Vehicle, zone : Zone)
+
+ /**
+ * Message to send targeted messages to the clients of specific users.
+ * @param reason the nature of the message
+ * @param data optional information for rendering the message to the client
+ */
+ final case class PeriodicReminder(reason : Reminders.Value, data : Option[Any] = None)
+
+ /**
+ * An `Enumeration` of reasons for sending a periodic reminder to the user.
+ */
+ object Reminders extends Enumeration {
+ val
+ Queue, //optional data is the numeric position in the queue
+ Blocked //optional data is a message regarding the blockage
+ = Value
+ }
/**
* Overloaded constructor.
@@ -90,7 +180,7 @@ object VehicleSpawnPad {
import net.psforever.types.Vector3
/**
- * Instantiate an configure a `VehicleSpawnPad` object
+ * Instantiate and configure a `VehicleSpawnPad` object
* @param pos the position (used to determine spawn point)
* @param orient the orientation (used to indicate spawn direction)
* @param id the unique id that will be assigned to this entity
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/AutoDriveControls.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/AutoDriveControls.scala
new file mode 100644
index 00000000..5059c21f
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/AutoDriveControls.scala
@@ -0,0 +1,304 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.serverobject.pad.process
+
+import net.psforever.objects.{GlobalDefinitions, Vehicle}
+import net.psforever.types.Vector3
+
+/**
+ * Instructions to be processed by `VehicleSpawnControlGuided`.
+ * These instructions coordinate basic vehicle manipulations such as driving, turning, and stopping.
+ * If defined, they will operate on a newly formed vehicle after it is released from its spawn pad lifting platform
+ * and after it has been issued at least one `ServerVehicleOverrideMsg` packet.
+ */
+object AutoDriveControls {
+
+ /**
+ * A container that translates to a new `Setting` instruction.
+ * Instructions are maintained in this form until they will be used due to the nature of the `Setting` object.
+ * The least that this object needs to do is accept parameters that matches the specific `Setting` that it outputs.
+ */
+ sealed trait Configuration {
+ def Create : Setting
+ }
+
+ /**
+ * An instruction to be consumed by the cyclic operation of `VehicleSpawnControlGuided`
+ * and are created by a `Configuration` object.
+ * They are considered semi-immutable `case class` objects.
+ * Externally, they are immutable by proper Scala standards.
+ * Internally, they will be permitted `private` fields that can be modified the first time the object is used.
+ */
+ sealed trait Setting {
+ /**
+ * The nature of the action being performed.
+ * @return an enumerated value that explains the purpose of the action
+ */
+ def Type : State.Value
+ /**
+ * The delay in between checks to determine if this setting has accomplished its goal or has entered an invalid state.
+ * @return the length of the delay
+ */
+ def Delay : Long = 200L
+ /**
+ * Data that is important for fulfilling the instruction on a user's client.
+ * Highly specific to the implementation.
+ * @return any data deemed important; `None`, if unnecessary
+ */
+ def Data : Option[Any] = None
+ /**
+ * Perform a test to determine if the vehicle is capable of performing the action this instruction requires.
+ * The test is typically simplistic in nature and often boils down to whether o not the vehicle is mobile.
+ * @param vehicle the vehicle being controlled
+ * @return `true`, if the action can (probably) be accomplished under the current conditions; `false`, otherwise
+ */
+ def Validate(vehicle : Vehicle) : Boolean = Vector3.MagnitudeSquared(vehicle.Velocity.getOrElse(Vector3.Zero).xy) > 0
+ /**
+ * Perform a test to determine if the vehicle has reached a set of conditions
+ * where the action performed by the instruction has been fulfilled.
+ * This should count as the "end of this step" and the "beginning of the next step."
+ * @param vehicle the vehicle being controlled
+ * @return `true`, if the action has run to completion; `false`, otherwise
+ */
+ def CompletionTest(vehicle : Vehicle) : Boolean
+ }
+
+ /**
+ * The nature of the action being performed.
+ * Different actions can be divided into types.
+ */
+ object State extends Enumeration {
+ val
+ Cancel,
+ Climb,
+ Drive,
+ Stop,
+ Turn,
+ Wait
+ = Value
+ }
+
+ protected final case class AutoDrive(speed : Int) extends Setting {
+ def Type = State.Drive
+
+ override def Data = Some(speed)
+
+ override def Validate(vehicle : Vehicle) : Boolean = true
+
+ def CompletionTest(vehicle : Vehicle) = Vector3.MagnitudeSquared(vehicle.Velocity.getOrElse(Vector3.Zero).xy) > 0
+ }
+
+ protected final case class AutoDriveDistance(start : Vector3, sqDistance : Float) extends Setting {
+ def Type = State.Wait
+
+ def CompletionTest(vehicle : Vehicle) : Boolean = {
+ Vector3.DistanceSquared(vehicle.Position.xy, start) > sqDistance
+ }
+ }
+
+ protected final case class AutoDriveDistanceFromHere(sqDistance : Float) extends Setting {
+ private var start : Option[Vector3] = None
+
+ def Type = State.Wait
+
+ def CompletionTest(vehicle : Vehicle) : Boolean = {
+ val startLoc = start.getOrElse({
+ start = Some(vehicle.Position.xy)
+ start.get
+ })
+ Vector3.DistanceSquared(vehicle.Position.xy, startLoc) > sqDistance
+ }
+ }
+
+ protected final case class AutoDriveForTime(length : Long) extends Setting {
+ private var start : Option[Long] = None
+
+ def Type = State.Wait
+
+ def CompletionTest(vehicle : Vehicle) : Boolean = {
+ val time : Long = System.currentTimeMillis
+ val startTime = start.getOrElse({
+ start = Some(time)
+ time
+ })
+ time - startTime >= length
+ }
+
+ override def Validate(vehicle : Vehicle) : Boolean = true
+ }
+
+ protected final case class AutoDriveTurnBy(angle : Float, direction : Int) extends Setting {
+ private var end : Option[Float] = None
+ private var currAng : Float = 0f
+
+ def Type = State.Turn
+
+ override def Delay : Long = 100L //increased frequency
+
+ override def Data = Some(direction)
+
+ def CompletionTest(vehicle : Vehicle) : Boolean = {
+ val endAng = end.getOrElse {
+ currAng = vehicle.Orientation.z
+ var ang = (currAng + angle) % 360f
+ if(ang < 0f) {
+ ang += 360f
+ }
+ end = Some(ang)
+ ang
+ }
+ val lastAng = currAng
+ currAng = vehicle.Orientation.z
+ //check that the expected angle is sandwiched between the previous angle and the current angle
+ currAng == endAng || (lastAng < endAng && endAng <= currAng) || (lastAng > endAng && endAng >= currAng)
+ }
+
+ override def Validate(vehicle : Vehicle) : Boolean = direction != 15 && super.Validate(vehicle)
+ }
+
+ protected final case class AutoDriveFirstGear() extends Setting {
+ private var speed : Int = 0
+
+ def Type = State.Drive
+
+ override def Data = Some(speed)
+
+ def CompletionTest(vehicle : Vehicle) = Vector3.MagnitudeSquared(vehicle.Velocity.getOrElse(Vector3.Zero)) > 0
+
+ override def Validate(vehicle : Vehicle) : Boolean = {
+ speed = vehicle.Definition.AutoPilotSpeed1
+ true
+ }
+ }
+
+ protected final case class AutoDriveSecondGear() extends Setting {
+ private var speed : Int = 0
+
+ def Type = State.Drive
+
+ override def Data = Some(speed)
+
+ def CompletionTest(vehicle : Vehicle) = Vector3.MagnitudeSquared(vehicle.Velocity.getOrElse(Vector3.Zero)) > 0
+
+ override def Validate(vehicle : Vehicle) : Boolean = {
+ speed = vehicle.Definition.AutoPilotSpeed2
+ true
+ }
+ }
+
+ protected final case class AutoDriveClimb(altitude : Float) extends Setting {
+ def Type = State.Climb
+
+ override def Data = Some(altitude)
+
+ def CompletionTest(vehicle : Vehicle) = {
+ vehicle.Position.z >= altitude
+ }
+
+ override def Validate(vehicle : Vehicle) : Boolean = GlobalDefinitions.isFlightVehicle(vehicle.Definition)
+ }
+
+ protected final case class AutoDriveCancelEarly(test : (Vehicle) => Boolean) extends Setting {
+ def Type = State.Cancel
+
+ def CompletionTest(vehicle : Vehicle) = true
+
+ override def Validate(vehicle : Vehicle) : Boolean = test(vehicle)
+ }
+
+ protected final case class AutoDriveStop() extends Setting {
+ def Type = State.Stop
+
+ override def Validate(vehicle : Vehicle) : Boolean = true
+
+ def CompletionTest(vehicle : Vehicle) = Validate(vehicle)
+ }
+
+ /**
+ * Use a validation test to determine if the remainder of the instructions should be processed.
+ * @param test the custom valid conditions of the vehicle for continuing
+ */
+ final case class CancelEarly(test : (Vehicle)=>Boolean) extends Configuration {
+ def Create : Setting = AutoDriveControls.AutoDriveCancelEarly(test)
+ }
+
+ /**
+ * Gain altitude with a flying vehicle.
+ * The climb speed is fixed.
+ * @param altitude the vertical distance to ascend
+ */
+ final case class Climb(altitude : Float) extends Configuration {
+ def Create : Setting = AutoDriveControls.AutoDriveClimb(altitude)
+ }
+
+ /**
+ * Drive a certain distance from somewhere.
+ * @param start the fixed coordinates of the origin point
+ * @param distance how far from the origin point the vehicle should travel
+ */
+ final case class Distance(start : Vector3, distance : Float) extends Configuration {
+ def Create : Setting = AutoDriveControls.AutoDriveDistance(start, distance * distance)
+ }
+
+ /**
+ * Drive a certain distance from where the vehicle is at the time that the instruction is called.
+ * The starting position is the current position of the vehicle.
+ * @param distance how far from the origin point the vehicle should travel
+ */
+ final case class DistanceFromHere(distance : Float) extends Configuration {
+ def Create : Setting = AutoDriveControls.AutoDriveDistanceFromHere(distance * distance)
+ }
+
+ /**
+ * Basic drive forward instruction.
+ * @see `ServerVehicleOverrideMsg.forward_speed`
+ * @param speed the speed that the vehicle accelerates to;
+ * scaled in a curious way
+ */
+ final case class Drive(speed : Int) extends Configuration {
+ def Create : Setting = AutoDriveControls.AutoDrive(speed)
+ }
+
+ /**
+ * Special drive forward instruction.
+ * @see `ServerVehicleOverrideMsg.forward_speed`
+ * @see `VehicleDefinition.AutoPilotSpeed1`
+ */
+ final case class FirstGear() extends Configuration {
+ def Create : Setting = AutoDriveControls.AutoDriveFirstGear()
+ }
+
+ /**
+ * Drive or idle for a certain duration.
+ * The starting position is the current position of the vehicle.
+ * @param time how long to contiue driving under the current conditions
+ */
+ final case class ForTime(time : Long) extends Configuration {
+ def Create : Setting = AutoDriveControls.AutoDriveForTime(time)
+ }
+
+ /**
+ * Special drive forward instruction.
+ * @see `ServerVehicleOverrideMsg.forward_speed`
+ * @see `VehicleDefinition.AutoPilotSpeed2`
+ */
+ final case class SecondGear() extends Configuration {
+ def Create : Setting = AutoDriveControls.AutoDriveSecondGear()
+ }
+
+ /**
+ * Stop driving (but do not cancel the server override state).
+ */
+ final case class Stop() extends Configuration {
+ def Create : Setting = AutoDriveControls.AutoDriveStop()
+ }
+
+ /**
+ * Cause the vehicle to turn a certain amount.
+ * @see `VehicleMessage.wheel_direction`
+ * @param angle the angle by which to turn the vehicle
+ * @param direction the wheel direction of the vehicle
+ */
+ final case class TurnBy(angle : Float, direction : Int) extends Configuration {
+ def Create : Setting = AutoDriveControls.AutoDriveTurnBy(angle, direction)
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlBase.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlBase.scala
new file mode 100644
index 00000000..284d0999
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlBase.scala
@@ -0,0 +1,69 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.serverobject.pad.process
+
+import akka.actor.Actor
+import net.psforever.objects.serverobject.pad.VehicleSpawnPad
+import net.psforever.objects.serverobject.structures.Building
+import net.psforever.objects.zones.Zone
+import org.log4s.Logger
+
+/**
+ * Base for all `VehicleSpawnControl`-related `Actor` classes.
+ * Provide a common convention for the logging system's name.
+ * Additional functionality that recovers the `Zone` of the owned amenity `VehicleSpawnPad`.
+ * @param pad a `VehicleSpawnPad` object
+ */
+abstract class VehicleSpawnControlBase(pad : VehicleSpawnPad) extends Actor {
+ /** the log reference */
+ private var baseLogger : Option[Logger] = None
+
+ /**
+ * Initialize, if appropriate, and provide a log-keeping agent for the requested task.
+ * If a consistent logger does not yet exist, initialize one that will be returned this time and for every subsequent request.
+ * If the underlying spawn pad has not been registered yet, however, produce a throw-away logger.
+ * @param logid a special identifier that distinguishes a logger whose name is built of common features
+ * @return a `Logger` object
+ */
+ private def GetLogger(logid : String) : Logger = baseLogger match {
+ case None =>
+ if(!pad.HasGUID || Continent == Zone.Nowhere) {
+ org.log4s.getLogger(s"uninitialized_${pad.Definition.Name}$logid")
+ }
+ else {
+ baseLogger = Some(org.log4s.getLogger(s"${Continent.Id}-${pad.Definition.Name}-${pad.GUID.guid}$logid"))
+ baseLogger.get
+ }
+ case Some(logger) =>
+ logger
+ }
+
+ /**
+ * Implement this to add a suffix to the identifying name of the logger.
+ * @return a special identifier that distinguishes a logger whose name is built of common features
+ */
+ def LogId : String
+
+ /**
+ * Act as if a variable for the logging agent.
+ * @return a `Logger` object
+ */
+ def log : Logger = GetLogger(LogId)
+
+ /**
+ * A common manner of utilizing the logging agent such that all messages have the same logging level.
+ * The default should be `trace`-level comments.
+ * No important messages should processed by this agent; only consume general vehicle spawn status.
+ * @param msg the message
+ */
+ def trace(msg : String) : Unit = log.trace(msg)
+
+ /**
+ * The continent the pad recognizes as a place of installation will change as its `Owner` changes.
+ * Originally, it belongs to a default non-`Building` object that is owned by a default non-`Zone` object.
+ * Eventually, it will belong to an active `Building` object that will belong to an active `Zone` object.
+ * With respect to `GetLogger(String)`, the active `Zone` object will be valid shortly after the object is registered,
+ * but will still be separated from being owned by a valid `Building` object by a few validation checks.
+ * @return the (current) `Zone` object
+ */
+ def Continent : Zone = pad.Owner.asInstanceOf[Building].Zone
+}
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
new file mode 100644
index 00000000..21baed97
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlConcealPlayer.scala
@@ -0,0 +1,46 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.serverobject.pad.process
+
+import akka.actor.{ActorRef, Props}
+import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
+
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.duration._
+
+/**
+ * An `Actor` that handles vehicle spawning orders for a `VehicleSpawnPad`.
+ * The basic `VehicleSpawnControl` is the root of a simple tree of "spawn control" objects that chain to each other.
+ * Each object performs on (or more than one related) actions upon the vehicle order that was submitted.
+ *
+ * This object is the first link in the process chain that spawns the ordered vehicle.
+ * It is devoted to causing the prospective driver to become hidden during the first part of the process
+ * with the goal of appearing to be "teleported" into the driver seat.
+ * It has failure cases should the driver be in an incorrect state.
+ * @param pad the `VehicleSpawnPad` object being governed
+ */
+class VehicleSpawnControlConcealPlayer(pad : VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
+ def LogId = "-concealer"
+
+ val loadVehicle = context.actorOf(Props(classOf[VehicleSpawnControlLoadVehicle], pad), s"${context.parent.path.name}-load")
+
+ def receive : Receive = {
+ case VehicleSpawnControl.Process.ConcealPlayer(entry) =>
+ val driver = entry.driver
+ //TODO how far can the driver get stray from the Terminal before his order is cancelled?
+ if(entry.sendTo != ActorRef.noSender && driver.Continent == Continent.Id && driver.VehicleSeated.isEmpty) {
+ trace(s"hiding ${driver.Name}")
+ Continent.VehicleEvents ! VehicleSpawnPad.ConcealPlayer(driver.GUID, Continent.Id)
+ context.system.scheduler.scheduleOnce(2000 milliseconds, loadVehicle, VehicleSpawnControl.Process.LoadVehicle(entry))
+ }
+ else {
+ trace(s"integral component lost; abort order fulfillment")
+ VehicleSpawnControl.DisposeVehicle(entry, Continent)
+ context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
+ }
+
+ case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
+ context.parent ! msg
+
+ case _ => ;
+ }
+}
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
new file mode 100644
index 00000000..98db2d77
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlDriverControl.scala
@@ -0,0 +1,54 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.serverobject.pad.process
+
+import akka.actor.{ActorRef, Props}
+import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
+
+/**
+ * An `Actor` that handles vehicle spawning orders for a `VehicleSpawnPad`.
+ * The basic `VehicleSpawnControl` is the root of a simple tree of "spawn control" objects that chain to each other.
+ * Each object performs on (or more than one related) actions upon the vehicle order that was submitted.
+ *
+ * A certain amount of time after the server has asserted control over a newly-spawned vehicle,
+ * control of that vehicle is given over to the driver.
+ * It has failure cases should the driver be in an incorrect state.
+ * @param pad the `VehicleSpawnPad` object being governed
+ */
+class VehicleSpawnControlDriverControl(pad : VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
+ def LogId = "-overrider"
+
+ val finalClear = context.actorOf(Props(classOf[VehicleSpawnControlFinalClearance], pad), s"${context.parent.path.name}-final")
+
+ def receive : Receive = {
+ case VehicleSpawnControl.Process.DriverVehicleControl(entry) =>
+ val vehicle = entry.vehicle
+ if(pad.Railed) {
+ Continent.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad, Continent.Id)
+ }
+ if(vehicle.Health == 0) {
+ trace(s"vehicle was already destroyed; but, everything is fine")
+ }
+ if(entry.sendTo != ActorRef.noSender) {
+ val driver = entry.driver
+ entry.sendTo ! VehicleSpawnPad.ServerVehicleOverrideEnd(vehicle, pad)
+ if(driver.VehicleSeated.contains(vehicle.GUID)) {
+ trace(s"returning control of ${vehicle.Definition.Name} to ${driver.Name}")
+ }
+ else {
+ trace(s"${driver.Name} is not seated in ${vehicle.Definition.Name}; vehicle controls have been locked")
+ }
+ }
+ else {
+ trace("can not properly return control to driver")
+ }
+ finalClear ! VehicleSpawnControl.Process.FinalClearance(entry)
+
+ case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
+ context.parent ! msg
+
+ case msg @ VehicleSpawnControl.Process.FinalClearance(_) =>
+ finalClear ! msg
+
+ case _ => ;
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlFinalClearance.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlFinalClearance.scala
new file mode 100644
index 00000000..c5f32288
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlFinalClearance.scala
@@ -0,0 +1,44 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.serverobject.pad.process
+
+import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
+import net.psforever.types.Vector3
+
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.duration._
+
+/**
+ * An `Actor` that handles vehicle spawning orders for a `VehicleSpawnPad`.
+ * The basic `VehicleSpawnControl` is the root of a simple tree of "spawn control" objects that chain to each other.
+ * Each object performs on (or more than one related) actions upon the vehicle order that was submitted.
+ *
+ * There is nothing left to do
+ * except make certain that the vehicle has moved far enough away from the spawn pad
+ * to not block the next order that may be queued.
+ * A long call is made to the root of this `Actor` object chain to start work on any subsequent vehicle order.
+ * @param pad the `VehicleSpawnPad` object being governed
+ */
+class VehicleSpawnControlFinalClearance(pad : VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
+ def LogId = "-clearer"
+
+ def receive : Receive = {
+ case VehicleSpawnControl.Process.FinalClearance(entry) =>
+ context.parent ! VehicleSpawnControl.ProcessControl.Reminder
+ self ! VehicleSpawnControlFinalClearance.Test(entry)
+
+ case VehicleSpawnControlFinalClearance.Test(entry) =>
+ if(Vector3.DistanceSquared(entry.vehicle.Position, pad.Position) > 100.0f) { //10m away from pad
+ trace("pad cleared")
+ context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
+ }
+ else {
+ context.system.scheduler.scheduleOnce(2000 milliseconds, self, VehicleSpawnControlFinalClearance.Test(entry))
+ }
+
+ case _ => ;
+ }
+}
+
+object VehicleSpawnControlFinalClearance {
+ private final case class Test(entry : VehicleSpawnControl.Order)
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlGuided.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlGuided.scala
new file mode 100644
index 00000000..9011082d
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlGuided.scala
@@ -0,0 +1,126 @@
+// Copyright (c) 2017 PSForever
+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
+import scala.concurrent.duration._
+
+/**
+ * An `Actor` that handles vehicle spawning orders for a `VehicleSpawnPad`.
+ * The basic `VehicleSpawnControl` is the root of a simple tree of "spawn control" objects that chain to each other.
+ * Each object performs on (or more than one related) actions upon the vehicle order that was submitted.
+ *
+ * After the vehicle has been released from the spawn pad lifting platform,
+ * it enters into an auto-drive mode that has at least two stages.
+ * An undefined number of stages cane be included, however.
+ * This can lead the newly-spawned vehicle through a rough pre-defined path.
+ *
+ * Throughout this process, the conditions of `ServerVehicleOverrideMsg` are still in effect.
+ * @param pad the `VehicleSpawnPad` object being governed
+ */
+class VehicleSpawnControlGuided(pad : VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
+ def LogId = "-guide"
+
+ val driverControl = context.actorOf(Props(classOf[VehicleSpawnControlDriverControl], pad), s"${context.parent.path.name}-driver")
+
+ def receive : Receive = {
+ case VehicleSpawnControl.Process.StartGuided(entry) =>
+ pad.Guide match {
+ case Nil =>
+ trace("no guided path for this pad")
+ driverControl ! VehicleSpawnControl.Process.DriverVehicleControl(entry)
+ case path =>
+ self ! VehicleSpawnControlGuided.InitialGuided(entry, path.map { _.Create })
+ }
+
+ case VehicleSpawnControlGuided.SelectNextGuided(entry, actions) =>
+ actions match {
+ case Nil | _ :: Nil =>
+ trace("custom vehicle path completed")
+ driverControl ! VehicleSpawnControl.Process.DriverVehicleControl(entry)
+ case _ :: xs =>
+ self ! VehicleSpawnControlGuided.InitialGuided(entry, xs)
+ }
+
+ case VehicleSpawnControlGuided.InitialGuided(entry, actions) =>
+ val vehicle = entry.vehicle
+ if(entry.sendTo != ActorRef.noSender && vehicle.Health != 0 && entry.driver.VehicleSeated.contains(vehicle.GUID) && actions.head.Validate(vehicle)) {
+ trace(s"custom vehicle path plotted - ${actions.head.Type}")
+ entry.sendTo ! VehicleSpawnControlGuided.GuidedControl(actions.head.Type, vehicle, actions.head.Data)
+ self ! VehicleSpawnControlGuided.ContinueGuided(entry, actions)
+ }
+ else {
+ trace(s"projected ${vehicle.Definition.Name} path interruption; exit guided mode")
+ driverControl ! VehicleSpawnControl.Process.DriverVehicleControl(entry)
+ }
+
+ case VehicleSpawnControlGuided.ValidateGuided(entry, actions) =>
+ val vehicle = entry.vehicle
+ if(entry.sendTo != ActorRef.noSender && vehicle.Health != 0 && entry.driver.VehicleSeated.contains(vehicle.GUID) && actions.head.Validate(vehicle)) {
+ self ! VehicleSpawnControlGuided.ContinueGuided(entry, actions)
+ }
+ else {
+ trace(s"plotted ${vehicle.Definition.Name} path interruption; exit guided mode")
+ driverControl ! VehicleSpawnControl.Process.DriverVehicleControl(entry)
+ }
+
+ case VehicleSpawnControlGuided.ContinueGuided(entry, actions) =>
+ if(actions.head.CompletionTest(entry.vehicle)) {
+ trace("step completed")
+ self ! VehicleSpawnControlGuided.SelectNextGuided(entry, actions)
+ }
+ else {
+ context.system.scheduler.scheduleOnce(actions.head.Delay milliseconds, self, VehicleSpawnControlGuided.ValidateGuided(entry, actions))
+ }
+
+ case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
+ context.parent ! msg
+
+ case msg @ VehicleSpawnControl.Process.FinalClearance(_) =>
+ driverControl ! msg
+
+ case _ => ;
+ }
+}
+
+object VehicleSpawnControlGuided {
+ /**
+ * Select the first instruction from the list.
+ * @param entry the vehicle order
+ * @param actions the list of instructions related to this spawn pad
+ */
+ private final case class InitialGuided(entry : VehicleSpawnControl.Order, actions : List[AutoDriveControls.Setting])
+ /**
+ * Swap to the next instruction, if it exists.
+ * @param entry the vehicle order
+ * @param actions the list of instructions related to this spawn pad
+ */
+ private final case class SelectNextGuided(entry : VehicleSpawnControl.Order, actions : List[AutoDriveControls.Setting])
+ /**
+ * The validation test determines whether the vehicle, the driver, and any other important elements
+ * are still in a state where the current instruction can be accomplished.
+ * If the validation test passes, the current instruction can continue to run to completion.
+ * If the validation test fails, the remainder of the instructions are aborted.
+ * @param entry the vehicle order
+ * @param actions the list of instructions related to this spawn pad
+ */
+ private final case class ValidateGuided(entry : VehicleSpawnControl.Order, actions : List[AutoDriveControls.Setting])
+ /**
+ * If the previous validation test passes, the current instruction can continue to run to completion.
+ * Once completed, the next instruction can be selected.
+ * @param entry the vehicle order
+ * @param actions the list of instructions related to this spawn pad
+ */
+ private final case class ContinueGuided(entry : VehicleSpawnControl.Order, actions : List[AutoDriveControls.Setting])
+
+ /**
+ * A message that explains the current instruction in the guided mode to another agency.
+ * @param command the nature of the action being performed
+ * @param vehicle the vehicle being controlled
+ * @param data optional data used to process the instruction
+ */
+ final case class GuidedControl(command : AutoDriveControls.State.Value, vehicle : Vehicle, data : Option[Any])
+}
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
new file mode 100644
index 00000000..2510387d
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlLoadVehicle.scala
@@ -0,0 +1,51 @@
+// Copyright (c) 2017 PSForever
+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.types.Vector3
+
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.duration._
+
+/**
+ * An `Actor` that handles vehicle spawning orders for a `VehicleSpawnPad`.
+ * The basic `VehicleSpawnControl` is the root of a simple tree of "spawn control" objects that chain to each other.
+ * Each object performs on (or more than one related) actions upon the vehicle order that was submitted.
+ *
+ * This object introduces the vehicle into the game environment.
+ * The vehicle must be added to the `Continent`, loaded onto other players' clients, and given an initial timed deconstruction event.
+ * For actual details on this process, please refer to the external source represented by `Continent.VehicleEvents`.
+ * It has failure cases should the driver be in an incorrect state.
+ * @param pad the `VehicleSpawnPad` object being governed
+ */
+class VehicleSpawnControlLoadVehicle(pad : VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
+ def LogId = "-loader"
+
+ val railJack = context.actorOf(Props(classOf[VehicleSpawnControlRailJack], pad), s"${context.parent.path.name}-rails")
+
+ def receive : Receive = {
+ case VehicleSpawnControl.Process.LoadVehicle(entry) =>
+ val vehicle = entry.vehicle
+ if(entry.driver.Continent == Continent.Id) {
+ trace(s"loading the ${vehicle.Definition.Name}")
+ if(pad.Railed) {
+ //load the vehicle in the spawn pad trench, underground, initially
+ vehicle.Position = vehicle.Position - Vector3(0, 0, if(GlobalDefinitions.isFlightVehicle(vehicle.Definition)) 9 else 5)
+ }
+ Continent.VehicleEvents ! VehicleSpawnPad.LoadVehicle(vehicle, Continent)
+ context.system.scheduler.scheduleOnce(100 milliseconds, railJack, VehicleSpawnControl.Process.RailJackAction(entry))
+ }
+ else {
+ trace("owner lost; abort order fulfillment")
+ VehicleSpawnControl.DisposeVehicle(entry, Continent)
+ context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
+ }
+
+ case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
+ context.parent ! msg
+
+ case _ => ;
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlRailJack.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlRailJack.scala
new file mode 100644
index 00000000..bb3bf5bd
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlRailJack.scala
@@ -0,0 +1,43 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.serverobject.pad.process
+
+import akka.actor.Props
+import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
+
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.duration._
+
+/**
+ * An `Actor` that handles vehicle spawning orders for a `VehicleSpawnPad`.
+ * The basic `VehicleSpawnControl` is the root of a simple tree of "spawn control" objects that chain to each other.
+ * Each object performs on (or more than one related) actions upon the vehicle order that was submitted.
+ *
+ * When the vehicle is added into the environment, it is attached to the spawn pad platform.
+ * On cue, the trapdoor of the platform will open, and the vehicle will be raised up into plain sight on a group of rails.
+ * These actions are actually integrated into previous stages and into later stages of the process.
+ * The primary objective to be completed is a specific place to start a frequent message to the other customers.
+ * It has failure cases should the driver be in an incorrect state.
+ * @param pad the `VehicleSpawnPad` object being governed
+ */
+class VehicleSpawnControlRailJack(pad : VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
+ def LogId = "-lifter"
+
+ val seatDriver = context.actorOf(Props(classOf[VehicleSpawnControlSeatDriver], pad), s"${context.parent.path.name}-seat")
+
+ def receive : Receive = {
+ case VehicleSpawnControl.Process.RailJackAction(entry) =>
+ if(pad.Railed) {
+ trace(s"attaching vehicle to railed platform")
+ Continent.VehicleEvents ! VehicleSpawnPad.AttachToRails(entry.vehicle, pad, Continent.Id)
+ }
+ else {
+ trace(s"railed platform skipped; vehicle positioned in pad trench temporarily")
+ }
+ context.system.scheduler.scheduleOnce(10 milliseconds, seatDriver, VehicleSpawnControl.Process.SeatDriver(entry))
+
+ case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
+ context.parent ! msg
+
+ case _ => ;
+ }
+}
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
new file mode 100644
index 00000000..2b67fa47
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala
@@ -0,0 +1,136 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.serverobject.pad.process
+
+import akka.actor.{ActorRef, Props}
+import net.psforever.objects.serverobject.mount.Mountable
+import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
+
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.duration._
+
+/**
+ * An `Actor` that handles vehicle spawning orders for a `VehicleSpawnPad`.
+ * The basic `VehicleSpawnControl` is the root of a simple tree of "spawn control" objects that chain to each other.
+ * Each object performs on (or more than one related) actions upon the vehicle order that was submitted.
+ *
+ * This object forces the prospective driver to take the driver seat.
+ * Multiple separate but sequentially significant steps occur within the scope of this object.
+ * First, this step waits for the vehicle to be completely ready to accept the driver.
+ * Second, this step triggers the player to actually be moved into the driver seat.
+ * Finally, this step waits until the driver is properly in the driver seat.
+ * It has failure cases should the driver or the vehicle be in an incorrect state.
+ * @see `ZonePopulationActor`
+ * @param pad the `VehicleSpawnPad` object being governed
+ */
+class VehicleSpawnControlSeatDriver(pad : VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
+ def LogId = "-usher"
+
+ val vehicleOverride = context.actorOf(Props(classOf[VehicleSpawnControlServerVehicleOverride], pad), s"${context.parent.path.name}-override")
+
+ def receive : Receive = {
+ case VehicleSpawnControl.Process.SeatDriver(entry) =>
+ self ! VehicleSpawnControlSeatDriver.AwaitVehicleReadiness(entry)
+
+ case VehicleSpawnControlSeatDriver.AwaitVehicleReadiness(entry) =>
+ if(entry.vehicle.Actor == ActorRef.noSender) { //wait for a necessary vehicle component to be loaded
+ context.system.scheduler.scheduleOnce(50 milliseconds, self, VehicleSpawnControlSeatDriver.AwaitVehicleReadiness(entry))
+ }
+ else {
+ trace("vehicle ready")
+ self ! VehicleSpawnControlSeatDriver.BeginDriverInSeat(entry)
+ }
+
+ case VehicleSpawnControlSeatDriver.BeginDriverInSeat(entry) =>
+ val driver = entry.driver
+ if(entry.sendTo != ActorRef.noSender && entry.vehicle.Health > 0 && driver.isAlive && driver.Continent == Continent.Id && driver.VehicleSeated.isEmpty) {
+ trace("driver to be made seated in vehicle")
+ entry.sendTo ! VehicleSpawnPad.StartPlayerSeatedInVehicle(entry.vehicle, pad)
+ entry.vehicle.Actor.tell(Mountable.TryMount(driver, 0), entry.sendTo) //entry.sendTo should handle replies to TryMount
+ context.system.scheduler.scheduleOnce(1000 milliseconds, self, VehicleSpawnControlSeatDriver.AwaitDriverInSeat(entry))
+ }
+ else {
+ trace("driver lost; vehicle stranded on pad")
+ context.system.scheduler.scheduleOnce(1000 milliseconds, vehicleOverride, VehicleSpawnControl.Process.ServerVehicleOverride(entry))
+ }
+
+ case VehicleSpawnControlSeatDriver.AwaitDriverInSeat(entry) =>
+ val driver = entry.driver
+ if(entry.sendTo == ActorRef.noSender || driver.Continent != Continent.Id) {
+ trace("driver lost, but operations can continue")
+ vehicleOverride ! VehicleSpawnControl.Process.ServerVehicleOverride(entry)
+ }
+ else if(driver.isAlive && driver.VehicleSeated.isEmpty) {
+ context.system.scheduler.scheduleOnce(100 milliseconds, self, VehicleSpawnControlSeatDriver.AwaitDriverInSeat(entry))
+ }
+ else {
+ trace(s"driver is sitting down")
+ val time = if(pad.Railed) 1000 else VehicleSpawnControlSeatDriver.RaillessSeatAnimationTimes(entry.vehicle.Definition.Name)
+ context.system.scheduler.scheduleOnce(time milliseconds, self, VehicleSpawnControlSeatDriver.DriverInSeat(entry))
+ }
+
+ case VehicleSpawnControlSeatDriver.DriverInSeat(entry) =>
+ if(entry.sendTo != ActorRef.noSender || entry.driver.Continent != Continent.Id) {
+ trace(s"driver ${entry.driver.Name} has taken the wheel")
+ entry.sendTo ! VehicleSpawnPad.PlayerSeatedInVehicle(entry.vehicle, pad)
+ }
+ else {
+ trace("driver lost, but operations can continue")
+ }
+ context.system.scheduler.scheduleOnce(250 milliseconds, vehicleOverride, VehicleSpawnControl.Process.ServerVehicleOverride(entry))
+
+ case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
+ context.parent ! msg
+
+ case _ => ;
+ }
+}
+
+object VehicleSpawnControlSeatDriver {
+ final case class AwaitVehicleReadiness(entry : VehicleSpawnControl.Order)
+
+ final case class BeginDriverInSeat(entry : VehicleSpawnControl.Order)
+
+ final case class AwaitDriverInSeat(entry : VehicleSpawnControl.Order)
+
+ final case class DriverInSeat(entry : VehicleSpawnControl.Order)
+
+ /**
+ * If the spawn pad associated with this `Actor` chain is not `Railed` -
+ * not guaranteed to have the correct ingame globally unique id of the spawn pad -
+ * then the animation of the driver boarding their vehicle will be displayed.
+ * Although the network is finicky, these times should compensate a beneficial visual delay.
+ * The BFRs, the Switchblade, and the Flail are all untested.
+ */
+ private val RaillessSeatAnimationTimes : Map[String, Int] = Map(
+ "fury" -> 600,
+ "quadassault" -> 600,
+ "quadstealth" -> 600,
+ "two_man_assault_buggy" -> 1000,
+ "skyguard" -> 1300,
+ "threemanheavybuggy" -> 1000,
+ "twomanheavybuggy" -> 1800,
+ "twomanhoverbuggy" -> 1800,
+ "mediumtransport" -> 1300,
+ "battlewagon" -> 1300,
+ "thunderer" -> 1300,
+ "aurora" -> 1300,
+ "apc_tr" -> 2300,
+ "apc_nc" -> 2300,
+ "apc_vs" -> 2300,
+ "prowler" -> 1000,
+ "vanguard" -> 2000,
+ "magrider" -> 1800,
+ "ant" -> 2500,
+ "ams" -> 1000,
+ "router" -> 2500,
+ "mosquito" -> 2000,
+ "lightgunship" -> 2000,
+ "wasp" -> 2000,
+ "liberator" -> 1800,
+ "vulture" -> 1800,
+ "dropship" -> 2000,
+ "galaxy_gunship" -> 2000,
+ "lodestar" -> 2000,
+ "phantasm" -> 1800
+ ).withDefaultValue(1000)
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlServerVehicleOverride.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlServerVehicleOverride.scala
new file mode 100644
index 00000000..41194d46
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlServerVehicleOverride.scala
@@ -0,0 +1,60 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.serverobject.pad.process
+
+import akka.actor.{ActorRef, Props}
+import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
+
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.duration._
+
+/**
+ * An `Actor` that handles vehicle spawning orders for a `VehicleSpawnPad`.
+ * The basic `VehicleSpawnControl` is the root of a simple tree of "spawn control" objects that chain to each other.
+ * Each object performs on (or more than one related) actions upon the vehicle order that was submitted.
+ *
+ * This object asserts automated control over the vehicle's motion after it has been released from its lifting platform.
+ * Normally, the vehicle drives forward for a bit under its own power.
+ * After a certain amount of time, control of the vehicle is given over to the driver.
+ * It has failure cases should the driver be in an incorrect state.
+ * @param pad the `VehicleSpawnPad` object being governed
+ */
+class VehicleSpawnControlServerVehicleOverride(pad : VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
+ def LogId = "-overrider"
+
+ val vehicleGuide = context.actorOf(Props(classOf[VehicleSpawnControlGuided], pad), s"${context.parent.path.name}-guide")
+
+ def receive : Receive = {
+ case VehicleSpawnControl.Process.ServerVehicleOverride(entry) =>
+ val vehicle = entry.vehicle
+ val pad_railed = pad.Railed
+ if(pad_railed) {
+ Continent.VehicleEvents ! VehicleSpawnPad.DetachFromRails(vehicle, pad, Continent.Id)
+ }
+ if(vehicle.Health == 0) {
+ trace(s"vehicle was already destroyed; but, everything is fine")
+ if(pad_railed) {
+ Continent.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad, Continent.Id)
+ }
+ vehicleGuide ! VehicleSpawnControl.Process.FinalClearance(entry)
+ }
+ else if(entry.sendTo != ActorRef.noSender && entry.driver.isAlive && entry.driver.Continent == Continent.Id && entry.driver.VehicleSeated.contains(vehicle.GUID)) {
+ trace(s"telling ${entry.driver.Name} that the server is assuming control of the ${vehicle.Definition.Name}")
+ entry.sendTo ! VehicleSpawnPad.ServerVehicleOverrideStart(vehicle, pad)
+ context.system.scheduler.scheduleOnce(3000 milliseconds, vehicleGuide, VehicleSpawnControl.Process.StartGuided(entry))
+ }
+ else {
+ if(pad_railed) {
+ Continent.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad, Continent.Id)
+ }
+ vehicleGuide ! VehicleSpawnControl.Process.FinalClearance(entry)
+ }
+
+ case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
+ context.parent ! msg
+
+ case msg @ VehicleSpawnControl.Process.FinalClearance(_) =>
+ vehicleGuide ! msg
+
+ case _ => ;
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala
index 5104c7dc..87f6cbd1 100644
--- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala
+++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala
@@ -14,7 +14,6 @@ import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.Vector3
-import scala.annotation.tailrec
import scala.collection.concurrent.TrieMap
import scala.collection.mutable.ListBuffer
import scala.collection.immutable.{Map => PairMap}
@@ -50,11 +49,11 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
guid.AddPool("dynamic", (3001 to 10000).toList).Selector = new RandomSelector //TODO unlump pools later; do not make too big
/** A synchronized `List` of items (`Equipment`) dropped by players on the ground and can be collected again. */
private val equipmentOnGround : ListBuffer[Equipment] = ListBuffer[Equipment]()
+ /** */
+ private val vehicles : ListBuffer[Vehicle] = ListBuffer[Vehicle]()
/** Used by the `Zone` to coordinate `Equipment` dropping and collection requests. */
private var ground : ActorRef = ActorRef.noSender
/** */
- private var vehicles : List[Vehicle] = List[Vehicle]()
- /** */
private var transport : ActorRef = ActorRef.noSender
/** */
private val players : TrieMap[Avatar, Option[Player]] = TrieMap[Avatar, Option[Player]]()
@@ -66,6 +65,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
private var buildings : PairMap[Int, Building] = PairMap.empty[Int, Building]
/** key - spawn zone id, value - buildings belonging to spawn zone */
private var spawnGroups : Map[Building, List[SpawnTube]] = PairMap[Building, List[SpawnTube]]()
+ /** */
+ private var vehicleEvents : ActorRef = ActorRef.noSender
/**
* Establish the basic accessible conditions necessary for a functional `Zone`.
@@ -89,7 +90,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
implicit val guid : NumberPoolHub = this.guid //passed into builderObject.Build implicitly
accessor = context.actorOf(RandomPool(25).props(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystem.AllocateNumberPoolActors(guid))), s"$Id-uns")
ground = context.actorOf(Props(classOf[ZoneGroundActor], equipmentOnGround), s"$Id-ground")
- transport = context.actorOf(Props(classOf[ZoneVehicleActor], this), s"$Id-vehicles")
+ transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"$Id-vehicles")
population = context.actorOf(Props(classOf[ZonePopulationActor], this, players, corpses), s"$Id-players")
Map.LocalObjects.foreach({ builderObject => builderObject.Build })
@@ -189,7 +190,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
*/
def EquipmentOnGround : List[Equipment] = equipmentOnGround.toList
- def Vehicles : List[Vehicle] = vehicles
+ def Vehicles : List[Vehicle] = vehicles.toList
def Players : List[Avatar] = players.keys.toList
@@ -197,35 +198,6 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
def Corpses : List[Player] = corpses.toList
- def AddVehicle(vehicle : Vehicle) : List[Vehicle] = {
- vehicles = vehicles :+ vehicle
- Vehicles
- }
-
- def RemoveVehicle(vehicle : Vehicle) : List[Vehicle] = {
- vehicles = recursiveFindVehicle(vehicles.iterator, vehicle) match {
- case Some(index) =>
- vehicles.take(index) ++ vehicles.drop(index + 1)
- case None => ;
- vehicles
- }
- Vehicles
- }
-
- @tailrec private def recursiveFindVehicle(iter : Iterator[Vehicle], target : Vehicle, index : Int = 0) : Option[Int] = {
- if(!iter.hasNext) {
- None
- }
- else {
- if(iter.next.equals(target)) {
- Some(index)
- }
- else {
- recursiveFindVehicle(iter, target, index + 1)
- }
- }
- }
-
/**
* Coordinate `Equipment` that has been dropped on the ground or to-be-dropped on the ground.
* @return synchronized reference to the ground
@@ -303,6 +275,15 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
* @return the `Zone` object
*/
def ClientInitialization() : Zone = this
+
+ def VehicleEvents : ActorRef = vehicleEvents
+
+ def VehicleEvents_=(bus : ActorRef) : ActorRef = {
+ if(vehicleEvents == ActorRef.noSender) {
+ vehicleEvents = bus
+ }
+ VehicleEvents
+ }
}
object Zone {
@@ -428,9 +409,15 @@ object Zone {
*/
final case class ItemFromGround(player : Player, item : Equipment)
- final case class SpawnVehicle(vehicle : Vehicle)
+ object Vehicle {
+ final case class Spawn(vehicle : Vehicle)
- final case class DespawnVehicle(vehicle : Vehicle)
+ final case class Despawn(vehicle : Vehicle)
+
+ final case class CanNotSpawn(zone : Zone, vehicle : Vehicle, reason : String)
+
+ final case class CanNotDespawn(zone : Zone, vehicle : Vehicle, reason : String)
+ }
/**
* Message to report the packet messages that initialize the client.
diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala
index 0f93d918..f14f31bb 100644
--- a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala
+++ b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala
@@ -55,10 +55,10 @@ class ZoneActor(zone : Zone) extends Actor {
zone.Ground forward msg
//frwd to Vehicle Actor
- case msg @ Zone.SpawnVehicle =>
+ case msg @ Zone.Vehicle.Spawn =>
zone.Transport forward msg
- case msg @ Zone.DespawnVehicle =>
+ case msg @ Zone.Vehicle.Despawn =>
zone.Transport forward msg
//own
diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
index b7d3c6a5..bdb2c2c3 100644
--- a/common/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
+++ b/common/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
@@ -1,22 +1,76 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.zones
-import akka.actor.Actor
+import akka.actor.{Actor, ActorRef, Props}
+import net.psforever.objects.Vehicle
+import net.psforever.objects.vehicles.VehicleControl
+
+import scala.annotation.tailrec
+import scala.collection.mutable.ListBuffer
/**
* Synchronize management of the list of `Vehicles` maintained by some `Zone`.
- * @param zone the `Zone` object
*/
-class ZoneVehicleActor(zone : Zone) extends Actor {
+//COMMENTS IMPORTED FROM FORMER VehicleContextActor:
+/**
+ * Provide a context for a `Vehicle` `Actor` - the `VehicleControl`.
+ *
+ * A vehicle can be passed between different zones and, therefore, does not belong to the zone.
+ * A vehicle cna be given to different players and can persist and change though players have gone.
+ * Therefore, also does not belong to `WorldSessionActor`.
+ * A vehicle must anchored to something that exists outside of the `InterstellarCluster` and its agents.
+ *
+ * The only purpose of this `Actor` is to allow vehicles to borrow a context for the purpose of `Actor` creation.
+ * It is also be allowed to be responsible for cleaning up that context.
+ * (In reality, it can be cleaned up anywhere a `PoisonPill` can be sent.)
+ *
+ * This `Actor` is intended to sit on top of the event system that handles broadcast messaging.
+ */
+class ZoneVehicleActor(zone : Zone, vehicleList : ListBuffer[Vehicle]) extends Actor {
//private[this] val log = org.log4s.getLogger
def receive : Receive = {
- case Zone.SpawnVehicle(vehicle) =>
- zone.AddVehicle(vehicle)
+ case Zone.Vehicle.Spawn(vehicle) =>
+ if(!vehicle.HasGUID) {
+ sender ! Zone.Vehicle.CanNotSpawn(zone, vehicle, "not registered yet")
+ }
+ else if(vehicleList.contains(vehicle)) {
+ sender ! Zone.Vehicle.CanNotSpawn(zone, vehicle, "already in zone")
+ }
+ else if(vehicle.Actor != ActorRef.noSender) {
+ sender ! Zone.Vehicle.CanNotSpawn(zone, vehicle, "already in another zone")
+ }
+ else {
+ vehicleList += vehicle
+ vehicle.Actor = context.actorOf(Props(classOf[VehicleControl], vehicle), s"${vehicle.Definition.Name}_${vehicle.GUID.guid}")
+ }
- case Zone.DespawnVehicle(vehicle) =>
- zone.RemoveVehicle(vehicle)
+ case Zone.Vehicle.Despawn(vehicle) =>
+ ZoneVehicleActor.recursiveFindVehicle(vehicleList.iterator, vehicle) match {
+ case Some(index) =>
+ vehicleList.remove(index)
+ vehicle.Actor ! akka.actor.PoisonPill
+ vehicle.Actor = ActorRef.noSender
+ case None => ;
+ sender ! Zone.Vehicle.CanNotDespawn(zone, vehicle, "can not find")
+ }
case _ => ;
}
}
+
+object ZoneVehicleActor {
+ @tailrec final def recursiveFindVehicle(iter : Iterator[Vehicle], target : Vehicle, index : Int = 0) : Option[Int] = {
+ if(!iter.hasNext) {
+ None
+ }
+ else {
+ if(iter.next.equals(target)) {
+ Some(index)
+ }
+ else {
+ recursiveFindVehicle(iter, target, index + 1)
+ }
+ }
+ }
+}
diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
index 1c118547..e7ebd353 100644
--- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
+++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
@@ -410,7 +410,7 @@ object GamePacketOpcode extends Enumeration {
case 0x4b => game.DeployRequestMessage.decode
case 0x4c => noDecoder(UnknownMessage76)
case 0x4d => game.RepairMessage.decode
- case 0x4e => noDecoder(ServerVehicleOverrideMsg)
+ case 0x4e => game.ServerVehicleOverrideMsg.decode
case 0x4f => game.LashMessage.decode
// OPCODES 0x50-5f
diff --git a/common/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala b/common/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala
new file mode 100644
index 00000000..266396a8
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala
@@ -0,0 +1,73 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game
+
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
+import scodec.Codec
+import scodec.codecs._
+
+/**
+ * Dispatched by server to assert control of a player's vehicle, usually temporarily, and to relinquish that control.
+ *
+ * The "vehicle" counts as any mobile platform where the user's character is currently sitting.
+ * If the player is not sitting in what the game considers a "vehicle," the packet is wasted.
+ * Either of the first two parameters - `lock_accelerator` or `lock_wheel` - constitutes any vehicle being overrode.
+ * Either of the latter two parameters - `lock_thrust` or `lock_strafe` - constitutes a flight vehicle being overrode.
+ * No message is displayed if the vehicle is placed under any form of server control.
+ * During server control, this is an acceleration value (?);
+ * during cancellable auto-drive, a constant velocity value.
+ * Vertical thrust control for aircraft is either on or off;
+ * the amount of that thrust can not be controlled.
+ *
+ * After being controlled, when the vehicle is no longer under control,
+ * it will transition into a state of constant speed auto-drive.
+ * The message regarding the vehicle being back in the driver's control will display,
+ * unless one of the aforementioned `lock_*` parameters is still set to `true`.
+ * When dismounting a bailable vehicle while it is under the server's control,
+ * the player will behave like they are bailing from it.
+ * (The vehicle actually has to be "bailable" first, of course.)
+ *
+ * "Something like speed:"
+ * For ground vehicles, for `n`, the calculated in-game speed for the value in this packet will be at least `3.45 x n`.
+ * For flight vehicles, for `n`, the forward air speed for the value in this packet will be at least `1.18 * n`.
+ * This approximation is not always going to be accurate but serves as a good rule of thumb.
+ * @param lock_accelerator driver has no control over vehicle acceleration
+ * @param lock_wheel driver has no control over vehicle turning
+ * @param reverse move in reverse
+ * @param unk4 na;
+ * something to do with vehicle bailable speed
+ * @param lock_vthrust pilot has no control over vertical thrust;
+ * asserts a constant positive vertical thrust;
+ * the only valid setting appears to be 1
+ * @param lock_strafe pilot has no control over strafing thrust;
+ * the only valid setting appears to be 1
+ * @param forward_speed "something like speed"
+ * @param unk8 na;
+ * set `lock_wheel` to `true` to expose this value
+ */
+final case class ServerVehicleOverrideMsg(lock_accelerator : Boolean,
+ lock_wheel : Boolean,
+ reverse : Boolean,
+ unk4 : Boolean,
+ lock_vthrust : Int,
+ lock_strafe : Int,
+ forward_speed : Int,
+ unk8 : Option[Long]
+ ) extends PlanetSideGamePacket {
+ type Packet = ServerVehicleOverrideMsg
+ def opcode = GamePacketOpcode.ServerVehicleOverrideMsg
+ def encode = ServerVehicleOverrideMsg.encode(this)
+}
+
+object ServerVehicleOverrideMsg extends Marshallable[ServerVehicleOverrideMsg] {
+ implicit val codec: Codec[ServerVehicleOverrideMsg] = (
+ ("lock_accelerator" | bool) ::
+ (("lock_wheel" | bool) >>:~ { test =>
+ ("reverse" | bool) ::
+ ("unk4" | bool) ::
+ ("lock_vthrust" | uint2L) ::
+ ("lock_strafe" | uint2L) ::
+ ("forward_speed" | uintL(9)) ::
+ conditional(test, "unk8" | uint32L)
+ })
+ ).as[ServerVehicleOverrideMsg]
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala b/common/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala
index aa71caa2..3dbc037b 100644
--- a/common/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala
@@ -19,8 +19,8 @@ import scodec.codecs._
* @param unk4 na
* @param wheel_direction for ground vehicles, whether the wheels are being turned;
* 15 for straight;
- * 0 for hard left;
- * 30 for hard right
+ * 0 for hard right;
+ * 30 for hard left
* @param unk5 na
* @param unk6 na
* @see `PlacementData`
diff --git a/common/src/test/scala/game/ServerVehicleOverrideMsgTest.scala b/common/src/test/scala/game/ServerVehicleOverrideMsgTest.scala
new file mode 100644
index 00000000..91a10784
--- /dev/null
+++ b/common/src/test/scala/game/ServerVehicleOverrideMsgTest.scala
@@ -0,0 +1,59 @@
+// Copyright (c) 2017 PSForever
+package game
+
+import org.specs2.mutable._
+import net.psforever.packet._
+import net.psforever.packet.game._
+import scodec.bits._
+
+class ServerVehicleOverrideMsgTest extends Specification {
+ val string1 = hex"4E C0 0C0 00000000 0"
+ val string2 = hex"4E 10 050 0"
+
+ "decode (1)" in {
+ PacketCoding.DecodePacket(string1).require match {
+ case ServerVehicleOverrideMsg(u1, u2, u3, u4, u5, u6, u7, u8) =>
+ u1 mustEqual true
+ u2 mustEqual true
+ u3 mustEqual false
+ u4 mustEqual false
+ u5 mustEqual 0
+ u6 mustEqual 0
+ u7 mustEqual 12
+ u8.isDefined mustEqual true
+ u8.get mustEqual 0L
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (2)" in {
+ PacketCoding.DecodePacket(string2).require match {
+ case ServerVehicleOverrideMsg(u1, u2, u3, u4, u5, u6, u7, u8) =>
+ u1 mustEqual false
+ u2 mustEqual false
+ u3 mustEqual false
+ u4 mustEqual true
+ u5 mustEqual 0
+ u6 mustEqual 0
+ u7 mustEqual 5
+ u8.isDefined mustEqual false
+ case _ =>
+ ko
+ }
+ }
+
+ "encode (1)" in {
+ val msg = ServerVehicleOverrideMsg(true, true, false, false, 0, 0, 12, Some(0L))
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string1
+ }
+
+ "encode (2)" in {
+ val msg = ServerVehicleOverrideMsg(false, false, false, true, 0, 0, 5, None)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string2
+ }
+}
diff --git a/common/src/test/scala/objects/AutoDriveControlsTest.scala b/common/src/test/scala/objects/AutoDriveControlsTest.scala
new file mode 100644
index 00000000..e26c7d9b
--- /dev/null
+++ b/common/src/test/scala/objects/AutoDriveControlsTest.scala
@@ -0,0 +1,498 @@
+// Copyright (c) 2017 PSForever
+package objects
+
+import akka.actor.Props
+import akka.testkit.TestProbe
+import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
+import net.psforever.objects.{Avatar, GlobalDefinitions, Player, Vehicle}
+import net.psforever.objects.serverobject.pad.process.{AutoDriveControls, VehicleSpawnControlGuided}
+import net.psforever.packet.game.PlanetSideGUID
+import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3}
+import org.specs2.mutable.Specification
+
+import scala.concurrent.duration._
+
+class AutoDriveControlsTest extends Specification {
+ "CancelEntry" should {
+ val vehicle = Vehicle(GlobalDefinitions.fury)
+ def exampleTest(vehicle : Vehicle) : Boolean = { vehicle.Position == Vector3(1,1,1) }
+
+ "create" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.CancelEarly(exampleTest)
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.Type mustEqual AutoDriveControls.State.Cancel
+ setting.Data mustEqual None
+ setting.Delay mustEqual 200L
+ }
+
+ "validate" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.CancelEarly(exampleTest)
+ val setting : AutoDriveControls.Setting = config.Create
+
+ vehicle.Position mustEqual Vector3.Zero
+ setting.Validate(vehicle) mustEqual false
+ vehicle.Position = Vector3(1,1,1)
+ setting.Validate(vehicle) mustEqual true
+ }
+
+ "completion" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.CancelEarly(exampleTest)
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.CompletionTest(vehicle) mustEqual true //always true
+ }
+ }
+
+ "Climb" should {
+ val vehicle = Vehicle(GlobalDefinitions.mosquito)
+
+ "create" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.Climb(10.5f)
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.Type mustEqual AutoDriveControls.State.Climb
+ setting.Data mustEqual Some(10.5f)
+ setting.Delay mustEqual 200L
+ }
+
+ "validate" in {
+ val vehicle_fury = Vehicle(GlobalDefinitions.fury)
+ val config : AutoDriveControls.Configuration = AutoDriveControls.Climb(10.5f)
+ val setting : AutoDriveControls.Setting = config.Create
+
+ setting.Validate(vehicle) mustEqual true //mosquito is a flying vehicle
+ setting.Validate(vehicle_fury) mustEqual false
+ }
+
+ "completion" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.Climb(10.5f)
+ val setting : AutoDriveControls.Setting = config.Create
+
+ vehicle.Position mustEqual Vector3.Zero
+ setting.CompletionTest(vehicle) mustEqual false
+ vehicle.Position = Vector3(0,0,10.5f)
+ setting.CompletionTest(vehicle) mustEqual true
+ }
+ }
+
+ "Distance" should {
+ val vehicle = Vehicle(GlobalDefinitions.fury)
+
+ "create" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.Distance(Vector3.Zero, 10.5f)
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.Type mustEqual AutoDriveControls.State.Wait
+ setting.Data mustEqual None
+ setting.Delay mustEqual 200L
+ }
+ "validate" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.Distance(Vector3.Zero, 10.5f)
+ val setting : AutoDriveControls.Setting = config.Create
+
+ vehicle.Velocity mustEqual None
+ setting.Validate(vehicle) mustEqual false
+ vehicle.Velocity = Vector3.Zero
+ setting.Validate(vehicle) mustEqual false
+ vehicle.Velocity = Vector3(1,0,0)
+ setting.Validate(vehicle) mustEqual true
+ }
+
+ "completion" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.Distance(Vector3.Zero, 10.5f)
+ val setting : AutoDriveControls.Setting = config.Create
+
+ vehicle.Position = Vector3(0,0,0)
+ setting.CompletionTest(vehicle) mustEqual false
+ vehicle.Position = Vector3(10.5f,0,0)
+ setting.CompletionTest(vehicle) mustEqual false
+ vehicle.Position = Vector3(11,0,0)
+ setting.CompletionTest(vehicle) mustEqual true
+ vehicle.Position = Vector3(0,11,0)
+ setting.CompletionTest(vehicle) mustEqual true
+ vehicle.Position = Vector3(0,0,11)
+ setting.CompletionTest(vehicle) mustEqual false
+ vehicle.Position = Vector3(7.5f,7.5f,0)
+ setting.CompletionTest(vehicle) mustEqual true
+ }
+ }
+
+ "DistanceFromHere" should {
+ val vehicle = Vehicle(GlobalDefinitions.fury)
+
+ "create" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.DistanceFromHere(10.5f)
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.Type mustEqual AutoDriveControls.State.Wait
+ setting.Data mustEqual None
+ setting.Delay mustEqual 200L
+ }
+
+ "validate" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.DistanceFromHere(10.5f)
+ val setting : AutoDriveControls.Setting = config.Create
+
+ vehicle.Velocity mustEqual None
+ setting.Validate(vehicle) mustEqual false
+ vehicle.Velocity = Vector3.Zero
+ setting.Validate(vehicle) mustEqual false
+ vehicle.Velocity = Vector3(1,0,0)
+ setting.Validate(vehicle) mustEqual true
+ }
+
+ "completion" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.DistanceFromHere(10.5f)
+ val setting : AutoDriveControls.Setting = config.Create
+
+ vehicle.Position = Vector3(0,0,0)
+ setting.CompletionTest(vehicle) mustEqual false
+ vehicle.Position = Vector3(10.5f,0,0)
+ setting.CompletionTest(vehicle) mustEqual false
+ vehicle.Position = Vector3(11,0,0)
+ setting.CompletionTest(vehicle) mustEqual true
+ vehicle.Position = Vector3(0,11,0)
+ setting.CompletionTest(vehicle) mustEqual true
+ vehicle.Position = Vector3(0,0,11)
+ setting.CompletionTest(vehicle) mustEqual false
+ vehicle.Position = Vector3(7.5f,7.5f,0)
+ setting.CompletionTest(vehicle) mustEqual true
+ }
+ }
+
+ "Drive" should {
+ val vehicle = Vehicle(GlobalDefinitions.fury)
+
+ "create" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.Drive(3)
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.Type mustEqual AutoDriveControls.State.Drive
+ setting.Data mustEqual Some(3)
+ setting.Delay mustEqual 200L
+ }
+
+ "validate" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.Drive(3)
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.Validate(vehicle) mustEqual true
+ }
+
+ "completion" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.Drive(3)
+ val setting : AutoDriveControls.Setting = config.Create
+ vehicle.Velocity mustEqual None
+ setting.CompletionTest(vehicle) mustEqual false
+
+ vehicle.Velocity = Vector3.Zero
+ vehicle.Velocity mustEqual Some(Vector3.Zero)
+ setting.CompletionTest(vehicle) mustEqual false
+
+ vehicle.Velocity = Vector3(1,0,0)
+ vehicle.Velocity mustEqual Some(Vector3(1,0,0))
+ setting.CompletionTest(vehicle) mustEqual true
+
+ }
+ }
+
+ "FirstGear" should {
+ val veh_def = GlobalDefinitions.mediumtransport
+ val vehicle = Vehicle(veh_def)
+
+ "create" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.FirstGear()
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.Type mustEqual AutoDriveControls.State.Drive
+ setting.Data mustEqual Some(0)
+ setting.Delay mustEqual 200L
+ }
+
+ "validate" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.FirstGear()
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.Validate(vehicle) mustEqual true //always true
+ }
+
+ "completion" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.FirstGear()
+ val setting : AutoDriveControls.Setting = config.Create
+
+ vehicle.Velocity mustEqual None
+ setting.CompletionTest(vehicle) mustEqual false
+ vehicle.Velocity = Vector3.Zero
+ setting.CompletionTest(vehicle) mustEqual false
+ vehicle.Velocity = Vector3(1,0,0)
+ setting.CompletionTest(vehicle) mustEqual true
+ }
+
+ "data" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.FirstGear()
+ val setting : AutoDriveControls.Setting = config.Create
+
+ setting.Data mustEqual Some(0)
+ setting.Validate(vehicle)
+ setting.Data mustEqual Some(veh_def.AutoPilotSpeed1)
+ }
+ }
+
+ "ForTime" should {
+ val veh_def = GlobalDefinitions.mediumtransport
+ val vehicle = Vehicle(veh_def)
+
+ "create" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.ForTime(1200L)
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.Type mustEqual AutoDriveControls.State.Wait
+ setting.Data mustEqual None
+ setting.Delay mustEqual 200L
+ }
+
+ "validate" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.ForTime(1200L)
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.Validate(vehicle) mustEqual true //always true
+ }
+
+ "completion" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.ForTime(1200L)
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.CompletionTest(vehicle) mustEqual false
+
+ Thread.sleep(1100)
+ setting.CompletionTest(vehicle) mustEqual false
+
+ Thread.sleep(200)
+ setting.CompletionTest(vehicle) mustEqual true
+ }
+ }
+
+ "SecondGear" should {
+ val veh_def = GlobalDefinitions.mediumtransport
+ val vehicle = Vehicle(veh_def)
+
+ "create" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.SecondGear()
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.Type mustEqual AutoDriveControls.State.Drive
+ setting.Data mustEqual Some(0)
+ setting.Delay mustEqual 200L
+ }
+
+ "validate" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.SecondGear()
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.Validate(vehicle) mustEqual true //always true
+ }
+
+ "completion" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.SecondGear()
+ val setting : AutoDriveControls.Setting = config.Create
+
+ vehicle.Velocity mustEqual None
+ setting.CompletionTest(vehicle) mustEqual false
+ vehicle.Velocity = Vector3.Zero
+ setting.CompletionTest(vehicle) mustEqual false
+ vehicle.Velocity = Vector3(1,0,0)
+ setting.CompletionTest(vehicle) mustEqual true
+ }
+
+ "data" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.SecondGear()
+ val setting : AutoDriveControls.Setting = config.Create
+
+ setting.Data mustEqual Some(0)
+ setting.Validate(vehicle)
+ setting.Data mustEqual Some(veh_def.AutoPilotSpeed2)
+ }
+ }
+
+ "Stop" should {
+ val vehicle = Vehicle(GlobalDefinitions.mediumtransport)
+
+ "create" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.Stop()
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.Type mustEqual AutoDriveControls.State.Stop
+ setting.Data mustEqual None
+ setting.Delay mustEqual 200L
+ }
+
+ "validate" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.Stop()
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.Validate(vehicle) mustEqual true //always true
+ }
+
+ "completion" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.Stop()
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.CompletionTest(vehicle) mustEqual true //always true
+ }
+ }
+
+ "TurnBy" should {
+ "create" in {
+ val config : AutoDriveControls.Configuration = AutoDriveControls.TurnBy(35.5f, 23)
+ val setting : AutoDriveControls.Setting = config.Create
+ setting.Type mustEqual AutoDriveControls.State.Turn
+ setting.Data mustEqual Some(23)
+ setting.Delay mustEqual 100L
+ }
+
+ "validate (velocity)" in {
+ val vehicle = Vehicle(GlobalDefinitions.mediumtransport)
+ val config : AutoDriveControls.Configuration = AutoDriveControls.TurnBy(35.5f, 23)
+ val setting : AutoDriveControls.Setting = config.Create
+
+ vehicle.Velocity mustEqual None
+ setting.Validate(vehicle) mustEqual false
+ vehicle.Velocity = Vector3(1,1,1)
+ setting.Validate(vehicle) mustEqual true
+ }
+
+ "validate (wheel direction = 15)" in {
+ val vehicle = Vehicle(GlobalDefinitions.mediumtransport)
+ val config : AutoDriveControls.Configuration = AutoDriveControls.TurnBy(35.5f, 15)
+ val setting : AutoDriveControls.Setting = config.Create
+
+ vehicle.Velocity = Vector3(1,1,1)
+ setting.Validate(vehicle) mustEqual false
+ }
+
+ "completion (passing 35.5-up)" in {
+ val vehicle = Vehicle(GlobalDefinitions.mediumtransport)
+ val config : AutoDriveControls.Configuration = AutoDriveControls.TurnBy(35.5f, 25)
+ val setting : AutoDriveControls.Setting = config.Create
+
+ vehicle.Orientation mustEqual Vector3.Zero
+ setting.CompletionTest(vehicle) mustEqual false
+ vehicle.Orientation = Vector3(0,0,34.5f)
+ setting.CompletionTest(vehicle) mustEqual false
+ vehicle.Orientation = Vector3(0,0,35f)
+ setting.CompletionTest(vehicle) mustEqual false
+ vehicle.Orientation = Vector3(0,0,36.0f)
+ setting.CompletionTest(vehicle) mustEqual true
+ }
+
+ "completion (passing 35.5 down)" in {
+ val vehicle = Vehicle(GlobalDefinitions.mediumtransport)
+ val config : AutoDriveControls.Configuration = AutoDriveControls.TurnBy(-35.5f, 25)
+ val setting : AutoDriveControls.Setting = config.Create
+
+ vehicle.Orientation = Vector3(0,0,40f)
+ setting.CompletionTest(vehicle) mustEqual false
+ vehicle.Orientation = Vector3(0,0,5f)
+ setting.CompletionTest(vehicle) mustEqual false
+ vehicle.Orientation = Vector3(0,0,4f)
+ setting.CompletionTest(vehicle) mustEqual true
+ }
+ }
+}
+
+class GuidedControlTest1 extends ActorTest {
+ "VehicleSpawnControlGuided" should {
+ "unguided" in {
+ val vehicle = Vehicle(GlobalDefinitions.mediumtransport)
+ vehicle.GUID = PlanetSideGUID(1)
+ val driver = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0,0))
+ driver.VehicleSeated = vehicle.GUID
+ val sendTo = TestProbe()
+ val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref)
+ val pad = VehicleSpawnPad(GlobalDefinitions.spawn_pad)
+ pad.GUID = PlanetSideGUID(1)
+ pad.Railed = false //suppress certain events
+ val guided = system.actorOf(Props(classOf[VehicleSpawnControlGuided], pad), "pad")
+
+ guided ! VehicleSpawnControl.Process.StartGuided(order)
+ val msg = sendTo.receiveOne(100 milliseconds)
+ assert(msg.isInstanceOf[VehicleSpawnPad.ServerVehicleOverrideEnd])
+ }
+ }
+}
+
+class GuidedControlTest2 extends ActorTest {
+ "VehicleSpawnControlGuided" should {
+ "guided (one)" in {
+ val vehicle = Vehicle(GlobalDefinitions.mediumtransport)
+ vehicle.GUID = PlanetSideGUID(1)
+ vehicle.Velocity = Vector3(1,1,1)
+ val driver = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0,0))
+ driver.VehicleSeated = vehicle.GUID
+ val sendTo = TestProbe()
+ val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref)
+ val pad = VehicleSpawnPad(GlobalDefinitions.spawn_pad)
+ pad.Railed = false //suppress certain events
+ val guided = system.actorOf(Props(classOf[VehicleSpawnControlGuided], pad), "pad")
+
+ pad.Guide = List(AutoDriveControls.FirstGear())
+ guided ! VehicleSpawnControl.Process.StartGuided(order)
+ val msg1 = sendTo.receiveOne(100 milliseconds)
+ assert(msg1.isInstanceOf[VehicleSpawnControlGuided.GuidedControl])
+ assert(msg1.asInstanceOf[VehicleSpawnControlGuided.GuidedControl].command == AutoDriveControls.State.Drive)
+ val msg2 = sendTo.receiveOne(200 milliseconds)
+ assert(msg2.isInstanceOf[VehicleSpawnPad.ServerVehicleOverrideEnd])
+ }
+ }
+}
+
+class GuidedControlTest3 extends ActorTest {
+ "VehicleSpawnControlGuided" should {
+ "guided (three)" in {
+ val vehicle = Vehicle(GlobalDefinitions.mediumtransport)
+ vehicle.GUID = PlanetSideGUID(1)
+ vehicle.Velocity = Vector3(1,1,1)
+ val driver = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0,0))
+ driver.VehicleSeated = vehicle.GUID
+ val sendTo = TestProbe()
+ val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref)
+ val pad = VehicleSpawnPad(GlobalDefinitions.spawn_pad)
+ pad.Railed = false //suppress certain events
+ val guided = system.actorOf(Props(classOf[VehicleSpawnControlGuided], pad), "pad")
+
+ pad.Guide = List(
+ AutoDriveControls.FirstGear(),
+ AutoDriveControls.ForTime(1000L),
+ AutoDriveControls.SecondGear()
+ )
+ guided ! VehicleSpawnControl.Process.StartGuided(order)
+ val msg1 = sendTo.receiveOne(100 milliseconds)
+ assert(msg1.isInstanceOf[VehicleSpawnControlGuided.GuidedControl])
+ assert(msg1.asInstanceOf[VehicleSpawnControlGuided.GuidedControl].command == AutoDriveControls.State.Drive)
+ val msg2 = sendTo.receiveOne(100 milliseconds)
+ assert(msg2.isInstanceOf[VehicleSpawnControlGuided.GuidedControl])
+ assert(msg2.asInstanceOf[VehicleSpawnControlGuided.GuidedControl].command == AutoDriveControls.State.Wait)
+ sendTo.expectNoMsg(1000 milliseconds)
+ val msg3 = sendTo.receiveOne(100 milliseconds)
+ assert(msg3.isInstanceOf[VehicleSpawnControlGuided.GuidedControl])
+ assert(msg3.asInstanceOf[VehicleSpawnControlGuided.GuidedControl].command == AutoDriveControls.State.Drive)
+ val msg4 = sendTo.receiveOne(200 milliseconds)
+ assert(msg4.isInstanceOf[VehicleSpawnPad.ServerVehicleOverrideEnd])
+ }
+ }
+}
+
+class GuidedControlTest4 extends ActorTest {
+ "VehicleSpawnControlGuided" should {
+ "fail validation test" in {
+ def validationFailure(vehicle : Vehicle) : Boolean = false
+
+ val vehicle = Vehicle(GlobalDefinitions.mediumtransport)
+ vehicle.GUID = PlanetSideGUID(1)
+ vehicle.Velocity = Vector3(1,1,1)
+ val driver = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0,0))
+ driver.VehicleSeated = vehicle.GUID
+ val sendTo = TestProbe()
+ val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref)
+ val pad = VehicleSpawnPad(GlobalDefinitions.spawn_pad)
+ pad.Railed = false //suppress certain events
+ val guided = system.actorOf(Props(classOf[VehicleSpawnControlGuided], pad), "pad")
+
+ pad.Guide = List(
+ AutoDriveControls.FirstGear(),
+ AutoDriveControls.CancelEarly(validationFailure),
+ AutoDriveControls.SecondGear()
+ )
+ guided ! VehicleSpawnControl.Process.StartGuided(order)
+ val msg1 = sendTo.receiveOne(100 milliseconds)
+ assert(msg1.isInstanceOf[VehicleSpawnControlGuided.GuidedControl])
+ assert(msg1.asInstanceOf[VehicleSpawnControlGuided.GuidedControl].command == AutoDriveControls.State.Drive)
+ val msg2 = sendTo.receiveOne(200 milliseconds)
+ assert(msg2.isInstanceOf[VehicleSpawnPad.ServerVehicleOverrideEnd])
+ }
+ }
+}
diff --git a/common/src/test/scala/objects/VehicleSpawnPadTest.scala b/common/src/test/scala/objects/VehicleSpawnPadTest.scala
index d4393b5d..2c9e1ba7 100644
--- a/common/src/test/scala/objects/VehicleSpawnPadTest.scala
+++ b/common/src/test/scala/objects/VehicleSpawnPadTest.scala
@@ -2,16 +2,17 @@
package objects
import akka.actor.{ActorRef, ActorSystem, Props}
+import akka.testkit.TestProbe
+import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
-import net.psforever.objects.serverobject.structures.{Building, StructureType}
-import net.psforever.objects.vehicles.VehicleControl
-import net.psforever.objects.zones.Zone
+import net.psforever.objects.serverobject.structures.StructureType
import net.psforever.objects.{Avatar, GlobalDefinitions, Player, Vehicle}
+import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
-import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3}
+import net.psforever.types.{PlanetSideEmpire, Vector3}
import org.specs2.mutable.Specification
-import scala.concurrent.duration.Duration
+import scala.concurrent.duration._
class VehicleSpawnPadTest extends Specification {
"VehicleSpawnPadDefinition" should {
@@ -25,6 +26,14 @@ class VehicleSpawnPadTest extends Specification {
val obj = VehicleSpawnPad(GlobalDefinitions.spawn_pad)
obj.Actor mustEqual ActorRef.noSender
obj.Definition mustEqual GlobalDefinitions.spawn_pad
+ obj.Railed mustEqual true
+ }
+
+ "un-railed" in {
+ val obj = VehicleSpawnPad(GlobalDefinitions.spawn_pad)
+ obj.Railed mustEqual true
+ obj.Railed = false
+ obj.Railed mustEqual false
}
}
}
@@ -39,78 +48,342 @@ class VehicleSpawnControl1Test extends ActorTest() {
}
}
-class VehicleSpawnControl2Test extends ActorTest() {
+class VehicleSpawnControl2aTest extends ActorTest() {
+ // This long runs for a long time.
"VehicleSpawnControl" should {
- "spawn a vehicle" in {
- val (player, pad) = VehicleSpawnPadControl.SetUpAgents(PlanetSideEmpire.TR)
- player.Spawn
- val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
- vehicle.GUID = PlanetSideGUID(1)
- vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle")
+ "complete on a vehicle order (block a second one until the first is done and the spawn pad is cleared)" in {
+ val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
+ //we can recycle the vehicle and the player for each order
+ val probe1 = new TestProbe(system, "first-order")
+ val probe2 = new TestProbe(system, "second-order")
+ val probe3 = new TestProbe(system, "zone-events")
+ zone.VehicleEvents = probe3.ref
- pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle)
- val reply = receiveOne(Duration.create(10000, "ms"))
- assert(reply == VehicleSpawnPad.ConcealPlayer) //explicit: isInstanceOf does not work
+ pad.Actor.tell(VehicleSpawnPad.VehicleOrder(player, vehicle), probe1.ref) //first order
+ pad.Actor.tell(VehicleSpawnPad.VehicleOrder(player, vehicle), probe2.ref) //second order
- val reply2 = receiveOne(Duration.create(10000, "ms"))
- assert(reply2.isInstanceOf[VehicleSpawnPad.LoadVehicle])
- assert(reply2.asInstanceOf[VehicleSpawnPad.LoadVehicle].vehicle == vehicle)
- assert(reply2.asInstanceOf[VehicleSpawnPad.LoadVehicle].pad == pad)
+ val probe2Msg1 = probe2.receiveOne(100 milliseconds)
+ assert(probe2Msg1.isInstanceOf[VehicleSpawnPad.PeriodicReminder])
+ assert(probe2Msg1.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Queue)
+ assert(probe2Msg1.asInstanceOf[VehicleSpawnPad.PeriodicReminder].data.contains("2"))
- player.VehicleOwned = Some(vehicle.GUID)
- val reply3 = receiveOne(Duration.create(10000, "ms"))
- assert(reply3.isInstanceOf[VehicleSpawnPad.PlayerSeatedInVehicle])
- assert(reply3.asInstanceOf[VehicleSpawnPad.PlayerSeatedInVehicle].vehicle == vehicle)
+ val probe3Msg1 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg1.isInstanceOf[VehicleSpawnPad.ConcealPlayer])
- val reply4 = receiveOne(Duration.create(10000, "ms"))
- assert(reply4.isInstanceOf[VehicleSpawnPad.SpawnPadBlockedWarning])
- assert(reply4.asInstanceOf[VehicleSpawnPad.SpawnPadBlockedWarning].vehicle == vehicle)
- assert(reply4.asInstanceOf[VehicleSpawnPad.SpawnPadBlockedWarning].warning_count > 0)
+ val probe3Msg2 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg2.isInstanceOf[VehicleSpawnPad.LoadVehicle])
- vehicle.Position = Vector3(11f, 0f, 0f) //greater than 10m
- val reply5 = receiveOne(Duration.create(10000, "ms"))
- assert(reply5.isInstanceOf[VehicleSpawnPad.SpawnPadUnblocked])
- assert(reply5.asInstanceOf[VehicleSpawnPad.SpawnPadUnblocked].vehicle_guid == vehicle.GUID)
+ val probe3Msg3 = probe3.receiveOne(200 milliseconds)
+ assert(probe3Msg3.isInstanceOf[VehicleSpawnPad.AttachToRails])
+
+ val probe1Msg1 = probe1.receiveOne(200 milliseconds)
+ assert(probe1Msg1.isInstanceOf[VehicleSpawnPad.StartPlayerSeatedInVehicle])
+ val probe1Msg2 = probe1.receiveOne(200 milliseconds)
+ assert(probe1Msg2.isInstanceOf[Mountable.MountMessages])
+ val probe1Msg2Contents = probe1Msg2.asInstanceOf[Mountable.MountMessages]
+ assert(probe1Msg2Contents.response.isInstanceOf[Mountable.CanMount])
+ val probe1Msg3 = probe1.receiveOne(3 seconds)
+ assert(probe1Msg3.isInstanceOf[VehicleSpawnPad.PlayerSeatedInVehicle])
+
+ val probe3Msg4 = probe3.receiveOne(1 seconds)
+ assert(probe3Msg4.isInstanceOf[VehicleSpawnPad.DetachFromRails])
+
+ val probe1Msg4 = probe1.receiveOne(1 seconds)
+ assert(probe1Msg4.isInstanceOf[VehicleSpawnPad.ServerVehicleOverrideStart])
+ val probe1Msg5 = probe1.receiveOne(4 seconds)
+ assert(probe1Msg5.isInstanceOf[VehicleSpawnPad.ServerVehicleOverrideEnd])
+
+ val probe1Msg6 = probe1.receiveOne(11 seconds)
+ assert(probe1Msg6.isInstanceOf[VehicleSpawnPad.PeriodicReminder])
+ assert(probe1Msg6.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked)
+ val probe2Msg2 = probe2.receiveOne(100 milliseconds)
+ assert(probe2Msg2.isInstanceOf[VehicleSpawnPad.PeriodicReminder])
+ assert(probe2Msg2.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked)
+
+ //if we move the vehicle more than 25m away from the pad, we should receive a ResetSpawnPad, and a second ConcealPlayer message
+ //that means that the first order has cleared and the spawn pad is now working on the second order successfully
+ vehicle.Position = Vector3(11,0,0)
+ player.VehicleSeated = None //since shared between orders, is necessary
+ val probe3Msg5 = probe3.receiveOne(4 seconds)
+ assert(probe3Msg5.isInstanceOf[VehicleSpawnPad.ResetSpawnPad])
+ val probe3Msg6 = probe3.receiveOne(5 seconds)
+ assert(probe3Msg6.isInstanceOf[VehicleSpawnPad.ConcealPlayer])
+ }
+ }
+}
+
+class VehicleSpawnControl2bTest extends ActorTest() {
+ // This long runs for a long time.
+ "VehicleSpawnControl" should {
+ "complete on a vehicle order (railless)" in {
+ val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
+ //we can recycle the vehicle and the player for each order
+ val probe1 = new TestProbe(system, "first-order")
+ val probe2 = new TestProbe(system, "second-order")
+ val probe3 = new TestProbe(system, "zone-events")
+ zone.VehicleEvents = probe3.ref
+ pad.Railed = false
+
+ pad.Actor.tell(VehicleSpawnPad.VehicleOrder(player, vehicle), probe1.ref) //first order
+ pad.Actor.tell(VehicleSpawnPad.VehicleOrder(player, vehicle), probe2.ref) //second order
+
+ val probe2Msg1 = probe2.receiveOne(100 milliseconds)
+ assert(probe2Msg1.isInstanceOf[VehicleSpawnPad.PeriodicReminder])
+ assert(probe2Msg1.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Queue)
+ assert(probe2Msg1.asInstanceOf[VehicleSpawnPad.PeriodicReminder].data.contains("2"))
+
+ val probe3Msg1 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg1.isInstanceOf[VehicleSpawnPad.ConcealPlayer])
+
+ val probe3Msg2 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg2.isInstanceOf[VehicleSpawnPad.LoadVehicle])
+
+ val probe1Msg1 = probe1.receiveOne(200 milliseconds)
+ assert(probe1Msg1.isInstanceOf[VehicleSpawnPad.StartPlayerSeatedInVehicle])
+ val probe1Msg2 = probe1.receiveOne(200 milliseconds)
+ assert(probe1Msg2.isInstanceOf[Mountable.MountMessages])
+ val probe1Msg2Contents = probe1Msg2.asInstanceOf[Mountable.MountMessages]
+ assert(probe1Msg2Contents.response.isInstanceOf[Mountable.CanMount])
+ val probe1Msg3 = probe1.receiveOne(3 seconds)
+ assert(probe1Msg3.isInstanceOf[VehicleSpawnPad.PlayerSeatedInVehicle])
+
+ val probe1Msg4 = probe1.receiveOne(1 seconds)
+ assert(probe1Msg4.isInstanceOf[VehicleSpawnPad.ServerVehicleOverrideStart])
+ val probe1Msg5 = probe1.receiveOne(4 seconds)
+ assert(probe1Msg5.isInstanceOf[VehicleSpawnPad.ServerVehicleOverrideEnd])
+
+ val probe1Msg6 = probe1.receiveOne(11 seconds)
+ assert(probe1Msg6.isInstanceOf[VehicleSpawnPad.PeriodicReminder])
+ assert(probe1Msg6.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked)
+ val probe2Msg2 = probe2.receiveOne(100 milliseconds)
+ assert(probe2Msg2.isInstanceOf[VehicleSpawnPad.PeriodicReminder])
+ assert(probe2Msg2.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked)
+
+ //if we move the vehicle more than 10m away from the pad, we should receive a second ConcealPlayer message
+ //that means that the first order has cleared and the spawn pad is now working on the second order successfully
+ vehicle.Position = Vector3(11,0,0)
+ player.VehicleSeated = None //since shared between orders, is necessary
+ val probe3Msg6 = probe3.receiveOne(4 seconds)
+ assert(probe3Msg6.isInstanceOf[VehicleSpawnPad.ConcealPlayer])
}
}
}
class VehicleSpawnControl3Test extends ActorTest() {
"VehicleSpawnControl" should {
- "not spawn a vehicle if player is dead" in {
- val (player, pad) = VehicleSpawnPadControl.SetUpAgents(PlanetSideEmpire.TR)
- val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
- vehicle.GUID = PlanetSideGUID(1)
- vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle")
+ "player is on wrong continent before vehicle can partially load; vehicle is cleaned up" in {
+ val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
+ val probe1 = new TestProbe(system, "first-order")
+ val probe3 = new TestProbe(system, "zone-events")
+ zone.VehicleEvents = probe3.ref
+ player.Continent = "problem" //problem
- pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle)
- val reply = receiveOne(Duration.create(5000, "ms"))
- assert(reply == null)
+ assert(vehicle.HasGUID)
+ pad.Actor.tell(VehicleSpawnPad.VehicleOrder(player, vehicle), probe1.ref)
+
+ val probe3Msg1 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg1.isInstanceOf[VehicleSpawnPad.RevealPlayer])
+ probe3.expectNoMsg(5 seconds)
+ assert(!vehicle.HasGUID) //vehicle has been unregistered
}
}
}
class VehicleSpawnControl4Test extends ActorTest() {
"VehicleSpawnControl" should {
- "not spawn a vehicle if vehicle Actor is missing" in {
- val (player, pad) = VehicleSpawnPadControl.SetUpAgents(PlanetSideEmpire.TR)
- player.Spawn
- val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
- vehicle.GUID = PlanetSideGUID(1)
+ "the player is on wrong continent when the vehicle tries to load; vehicle is cleaned up" in {
+ val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
+ val probe1 = new TestProbe(system, "first-order")
+ val probe3 = new TestProbe(system, "zone-events")
+ zone.VehicleEvents = probe3.ref
- pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle)
- val reply = receiveOne(Duration.create(5000, "ms"))
- assert(reply == null)
+ pad.Actor.tell(VehicleSpawnPad.VehicleOrder(player, vehicle), probe1.ref)
+
+ val probe3Msg1 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg1.isInstanceOf[VehicleSpawnPad.ConcealPlayer])
+ player.Continent = "problem" //problem
+ assert(vehicle.HasGUID)
+
+ val probe3Msg2 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg2.isInstanceOf[VehicleSpawnPad.RevealPlayer])
+ probe3.expectNoMsg(5 seconds)
+ assert(!vehicle.HasGUID) //vehicle has been unregistered
}
}
}
-object VehicleSpawnPadControl {
- def SetUpAgents(faction : PlanetSideEmpire.Value)(implicit system : ActorSystem) : (Player, VehicleSpawnPad) = {
- val pad = VehicleSpawnPad(GlobalDefinitions.spawn_pad)
- pad.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], pad), "test-pad")
- pad.Owner = new Building(0, Zone.Nowhere, StructureType.Building)
- pad.Owner.Faction = faction
- (Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), pad)
+//class VehicleSpawnControl5aTest extends ActorTest() {
+// "VehicleSpawnControl" should {
+// "the vehicle is destroyed before being fully loaded; the vehicle is cleaned up" in {
+// val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
+// //we can recycle the vehicle and the player for each order
+// val probe1 = new TestProbe(system, "first-order")
+// val probe3 = new TestProbe(system, "zone-events")
+// zone.VehicleEvents = probe3.ref
+//
+// pad.Actor.tell(VehicleSpawnPad.VehicleOrder(player, vehicle), probe1.ref)
+//
+// val probe3Msg1 = probe3.receiveOne(3 seconds)
+// assert(probe3Msg1.isInstanceOf[VehicleSpawnPad.ConcealPlayer])
+//
+// val probe3Msg2 = probe3.receiveOne(3 seconds)
+// assert(probe3Msg2.isInstanceOf[VehicleSpawnPad.LoadVehicle])
+// vehicle.Health = 0 //problem
+//
+// val probe3Msg3 = probe3.receiveOne(3 seconds)
+// assert(probe3Msg3.isInstanceOf[VehicleSpawnPad.DisposeVehicle])
+// val probe3Msg4 = probe3.receiveOne(100 milliseconds)
+// assert(probe3Msg4.isInstanceOf[VehicleSpawnPad.RevealPlayer])
+// //note: the vehicle will not be unregistered by this logic alone
+// //since LoadVehicle should introduce it into the game world properly, it has to be handled properly
+// }
+// }
+//}
+
+class VehicleSpawnControl5Test extends ActorTest() {
+ "VehicleSpawnControl" should {
+ "player dies right after vehicle partially loads; the vehicle spawns and blocks the pad" in {
+ val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
+ //we can recycle the vehicle and the player for each order
+ val probe1 = new TestProbe(system, "first-order")
+ val probe3 = new TestProbe(system, "zone-events")
+ zone.VehicleEvents = probe3.ref
+
+ pad.Actor.tell(VehicleSpawnPad.VehicleOrder(player, vehicle), probe1.ref)
+
+ val probe3Msg1 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg1.isInstanceOf[VehicleSpawnPad.ConcealPlayer])
+
+ val probe3Msg2 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg2.isInstanceOf[VehicleSpawnPad.LoadVehicle])
+ player.Die //problem
+
+ val probe3Msg3 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg3.isInstanceOf[VehicleSpawnPad.AttachToRails])
+ val probe3Msg4 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg4.isInstanceOf[VehicleSpawnPad.DetachFromRails])
+
+ val probe1Msg = probe1.receiveOne(12 seconds)
+ assert(probe1Msg.isInstanceOf[VehicleSpawnPad.PeriodicReminder])
+ assert(probe1Msg.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked)
+ }
+ }
+}
+
+class VehicleSpawnControl6Test extends ActorTest() {
+ "VehicleSpawnControl" should {
+ "the player can not sit in vehicle; vehicle spawns and blocks the pad" in {
+ val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
+ //we can recycle the vehicle and the player for each order
+ val probe1 = new TestProbe(system, "first-order")
+ val probe3 = new TestProbe(system, "zone-events")
+ zone.VehicleEvents = probe3.ref
+
+ pad.Actor.tell(VehicleSpawnPad.VehicleOrder(player, vehicle), probe1.ref)
+
+ val probe3Msg1 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg1.isInstanceOf[VehicleSpawnPad.ConcealPlayer])
+
+ val probe3Msg2 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg2.isInstanceOf[VehicleSpawnPad.LoadVehicle])
+
+ val probe3Msg3 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg3.isInstanceOf[VehicleSpawnPad.AttachToRails])
+
+ val probe1Msg1 = probe1.receiveOne(200 milliseconds)
+ assert(probe1Msg1.isInstanceOf[VehicleSpawnPad.StartPlayerSeatedInVehicle])
+ player.Continent = "problem" //problem 1
+ probe1.receiveOne(200 milliseconds) //Mountable.MountMessage
+
+ val probe3Msg4 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg4.isInstanceOf[VehicleSpawnPad.DetachFromRails])
+ val probe3Msg5 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg5.isInstanceOf[VehicleSpawnPad.ResetSpawnPad])
+
+ val probe1Msg2 = probe1.receiveOne(12 seconds)
+ assert(probe1Msg2.isInstanceOf[VehicleSpawnPad.PeriodicReminder])
+ assert(probe1Msg2.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked)
+ }
+ }
+}
+
+class VehicleSpawnControl7Test extends ActorTest() {
+ "VehicleSpawnControl" should {
+ "player dies after getting in driver seat; the vehicle blocks the pad" in {
+ val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
+ //we can recycle the vehicle and the player for each order
+ val probe1 = new TestProbe(system, "first-order")
+ val probe3 = new TestProbe(system, "zone-events")
+ zone.VehicleEvents = probe3.ref
+
+ pad.Actor.tell(VehicleSpawnPad.VehicleOrder(player, vehicle), probe1.ref)
+
+ val probe3Msg1 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg1.isInstanceOf[VehicleSpawnPad.ConcealPlayer])
+
+ val probe3Msg2 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg2.isInstanceOf[VehicleSpawnPad.LoadVehicle])
+
+ val probe3Msg3 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg3.isInstanceOf[VehicleSpawnPad.AttachToRails])
+
+ val probe1Msg1 = probe1.receiveOne(200 milliseconds)
+ assert(probe1Msg1.isInstanceOf[VehicleSpawnPad.StartPlayerSeatedInVehicle])
+ val probe1Msg2 = probe1.receiveOne(200 milliseconds)
+ assert(probe1Msg2.isInstanceOf[Mountable.MountMessages])
+ val probe1Msg2Contents = probe1Msg2.asInstanceOf[Mountable.MountMessages]
+ assert(probe1Msg2Contents.response.isInstanceOf[Mountable.CanMount])
+ val probe1Msg3 = probe1.receiveOne(3 seconds)
+ assert(probe1Msg3.isInstanceOf[VehicleSpawnPad.PlayerSeatedInVehicle])
+ player.Die //problem
+
+ val probe3Msg4 = probe3.receiveOne(3 seconds)
+ assert(probe3Msg4.isInstanceOf[VehicleSpawnPad.DetachFromRails])
+ val probe3Msg5 = probe3.receiveOne(100 milliseconds)
+ assert(probe3Msg5.isInstanceOf[VehicleSpawnPad.ResetSpawnPad])
+
+ val probe1Msg4 = probe1.receiveOne(12 seconds)
+ assert(probe1Msg4.isInstanceOf[VehicleSpawnPad.PeriodicReminder])
+ assert(probe1Msg4.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked)
+ }
+ }
+}
+
+object VehicleSpawnPadControlTest {
+ import net.psforever.objects.zones.ZoneMap
+ private val map = new ZoneMap("test-map")
+
+ def SetUpAgents(faction : PlanetSideEmpire.Value)(implicit system : ActorSystem) : (Vehicle, Player, VehicleSpawnPad, Zone) = {
+ import net.psforever.objects.guid.NumberPoolHub
+ import net.psforever.objects.guid.source.LimitedNumberSource
+ import net.psforever.objects.serverobject.structures.Building
+ import net.psforever.objects.vehicles.VehicleControl
+ import net.psforever.objects.zones.ZoneActor
+ import net.psforever.objects.Tool
+ import net.psforever.types.CharacterGender
+
+ val zone = new Zone("test-zone", map, 0)
+ val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
+ val weapon = vehicle.WeaponControlledFromSeat(1).get.asInstanceOf[Tool]
+ val guid : NumberPoolHub = new NumberPoolHub(LimitedNumberSource(3))
+ guid.AddPool("test-pool", (0 to 2).toList)
+ guid.register(vehicle, "test-pool")
+ guid.register(weapon, "test-pool")
+ guid.register(weapon.AmmoSlot.Box, "test-pool")
+ zone.GUID(guid)
+ zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), s"test-zone-${System.nanoTime()}")
+ zone.Actor ! Zone.Init()
+ vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), s"vehicle-control-${System.nanoTime()}")
+
+ val pad = VehicleSpawnPad(GlobalDefinitions.spawn_pad)
+ pad.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], pad), s"test-pad-${System.nanoTime()}")
+ pad.Owner = new Building(0, zone, StructureType.Building)
+ pad.Owner.Faction = faction
+ val player = Player(Avatar("test", faction, CharacterGender.Male, 0, 0))
+ player.GUID = PlanetSideGUID(10)
+ player.Continent = zone.Id
+ player.Spawn
+ //note: pad and vehicle are both at Vector3(0,0,0) so they count as blocking
+ (vehicle, player, pad, zone)
}
}
diff --git a/common/src/test/scala/objects/ZoneTest.scala b/common/src/test/scala/objects/ZoneTest.scala
index 2c5cfcb6..251694f3 100644
--- a/common/src/test/scala/objects/ZoneTest.scala
+++ b/common/src/test/scala/objects/ZoneTest.scala
@@ -8,13 +8,14 @@ import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.LimitedNumberSource
-import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType}
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.serverobject.tube.SpawnTube
-import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap}
import net.psforever.objects._
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3}
+import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType}
+import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap}
+import net.psforever.objects.Vehicle
import org.specs2.mutable.Specification
import scala.concurrent.duration.Duration
@@ -29,8 +30,6 @@ class ZoneTest extends Specification {
}
"references bases by a positive building id (defaults to 0)" in {
- def test(a: Int, b : Zone, c : ActorContext) : Building = { Building.NoBuilding }
-
val map = new ZoneMap("map13")
map.LocalBuildings mustEqual Map.empty
map.LocalBuilding(10, FoundationBuilder(test))
@@ -110,28 +109,6 @@ class ZoneTest extends Specification {
guid2.AddPool("pool4", (51 to 75).toList)
zone.GUID(guid2) mustEqual false
}
-
- "can keep track of Vehicles" in {
- val zone = new Zone("home3", map13, 13)
- val fury = Vehicle(GlobalDefinitions.fury)
- zone.Vehicles mustEqual List()
- zone.AddVehicle(fury)
- zone.Vehicles mustEqual List(fury)
- }
-
- "can forget specific vehicles" in {
- val zone = new Zone("home3", map13, 13)
- val fury = Vehicle(GlobalDefinitions.fury)
- val wraith = Vehicle(GlobalDefinitions.quadstealth)
- val basilisk = Vehicle(GlobalDefinitions.quadassault)
- zone.AddVehicle(wraith)
- zone.AddVehicle(fury)
- zone.AddVehicle(basilisk)
- zone.Vehicles mustEqual List(wraith, fury, basilisk)
-
- zone.RemoveVehicle(fury)
- zone.Vehicles mustEqual List(wraith, basilisk)
- }
}
}
diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala
index 8b9351b1..4051208c 100644
--- a/pslogin/src/main/scala/Maps.scala
+++ b/pslogin/src/main/scala/Maps.scala
@@ -126,14 +126,15 @@ object Maps {
LocalObject(2323, Door.Constructor) //spawn tube door
LocalObject(2324, Door.Constructor) //spawn tube door
LocalObject(2419, Terminal.Constructor(ground_vehicle_terminal))
- LocalObject(500,
+ LocalObject(1479,
VehicleSpawnPad.Constructor(Vector3(3962.0f, 4334.0f, 267.75f), Vector3(0f, 0f, 180.0f))
- ) //TODO guid not correct
+ )
LocalObject(224, Terminal.Constructor(dropship_vehicle_terminal))
- LocalObject(501,
- VehicleSpawnPad.Constructor(Vector3(4012.3594f, 4364.8047f, 271.90625f), Vector3(0f, 0f, 180.0f))
- ) //TODO guid not correct
+ LocalObject(223,
+ VehicleSpawnPad.Constructor(Vector3(4012.3594f, 4364.8047f, 271.90625f), Vector3(0f, 0f, 0f))
+ )
ObjectToBuilding(222, 2)
+ ObjectToBuilding(223, 2)
ObjectToBuilding(224, 2)
ObjectToBuilding(370, 2)
ObjectToBuilding(371, 2)
@@ -204,6 +205,7 @@ object Maps {
ObjectToBuilding(1188, 2)
ObjectToBuilding(1492, 2)
ObjectToBuilding(1494, 2)
+ ObjectToBuilding(1479, 2)
ObjectToBuilding(1564, 2)
ObjectToBuilding(1568, 2)
ObjectToBuilding(1569, 2)
@@ -228,8 +230,6 @@ object Maps {
ObjectToBuilding(2323, 2)
ObjectToBuilding(2324, 2)
ObjectToBuilding(2419, 2)
- ObjectToBuilding(500, 2)
- ObjectToBuilding(501, 2)
DoorToLock(375, 863)
DoorToLock(376, 860)
DoorToLock(384, 866)
@@ -244,8 +244,8 @@ object Maps {
DoorToLock(638, 882)
DoorToLock(642, 884)
DoorToLock(715, 751)
- TerminalToSpawnPad(224, 501)
- TerminalToSpawnPad(2419, 500)
+ TerminalToSpawnPad(224, 223)
+ TerminalToSpawnPad(2419, 1479)
}
def Building38() : Unit = {
@@ -404,20 +404,23 @@ object Maps {
Building29()
Building42()
Building51()
+ Building52()
Building77()
+ Building79()
+ Building81()
def Building1() : Unit = {
//warpgate?
LocalBuilding(1, FoundationBuilder(WarpGate.Structure))
}
+ // LocalBuilding(2, FoundationBuilder(WarpGate.Structure)) //TODO might be wrong?
+
def Building3() : Unit = {
//warpgate?
LocalBuilding(3, FoundationBuilder(WarpGate.Structure))
}
-// LocalBuilding(2, FoundationBuilder(WarpGate.Structure)) //TODO might be wrong?
-
// LocalObject(520, ImplantTerminalMech.Constructor) //Hart B
// LocalObject(1081, Terminal.Constructor(implant_terminal_interface)) //tube 520
// TerminalToInterface(520, 1081)
@@ -616,24 +619,60 @@ object Maps {
LocalBuilding(51, FoundationBuilder(Building.Structure(StructureType.Platform)))
LocalObject(304, Terminal.Constructor(dropship_vehicle_terminal))
LocalObject(292,
- VehicleSpawnPad.Constructor(Vector3(3508.9844f, 2895.961f, 92.296875f), Vector3(0f, 0f, 270.0f))
+ VehicleSpawnPad.Constructor(Vector3(3508.9844f, 2895.961f, 92.296875f), Vector3(0f, 0f, 90.0f))
)
ObjectToBuilding(304, 51)
ObjectToBuilding(292, 51)
TerminalToSpawnPad(304, 292)
}
+ def Building52() : Unit = {
+ //air terminal southwest of HART C
+ LocalBuilding(52, FoundationBuilder(Building.Structure(StructureType.Platform)))
+ LocalObject(305, Terminal.Constructor(dropship_vehicle_terminal))
+ LocalObject(293,
+ VehicleSpawnPad.Constructor(Vector3(3575.0781f, 2654.9766f, 92.296875f), Vector3(0f, 0f, 45.0f))
+ )
+ ObjectToBuilding(305, 52)
+ ObjectToBuilding(293, 52)
+ TerminalToSpawnPad(305, 293)
+ }
+
def Building77() : Unit = {
//ground terminal west of HART C
LocalBuilding(77, FoundationBuilder(Building.Structure(StructureType.Platform)))
LocalObject(1063, Terminal.Constructor(ground_vehicle_terminal))
LocalObject(706,
- VehicleSpawnPad.Constructor(Vector3(3506.0f, 2820.0f, 92.0f), Vector3(0f, 0f, 270.0f))
+ VehicleSpawnPad.Constructor(Vector3(3506.0f, 2820.0f, 92.0625f), Vector3(0f, 0f, 270.0f))
)
ObjectToBuilding(1063, 77)
ObjectToBuilding(706, 77)
TerminalToSpawnPad(1063, 706)
}
+
+ def Building79() : Unit = {
+ //ground terminal south of HART C
+ LocalBuilding(79, FoundationBuilder(Building.Structure(StructureType.Platform)))
+ LocalObject(1065, Terminal.Constructor(ground_vehicle_terminal))
+ LocalObject(710,
+ VehicleSpawnPad.Constructor(Vector3(3659.836f, 2589.875f, 92.0625f), Vector3(0f, 0f, 180.0f))
+ )
+ ObjectToBuilding(1065, 79)
+ ObjectToBuilding(710, 79)
+ TerminalToSpawnPad(1065, 710)
+ }
+
+ def Building81() : Unit = {
+ //ground terminal south of HART C
+ LocalBuilding(81, FoundationBuilder(Building.Structure(StructureType.Platform)))
+ LocalObject(1067, Terminal.Constructor(ground_vehicle_terminal))
+ LocalObject(712,
+ VehicleSpawnPad.Constructor(Vector3(3724.0156f, 2589.875f, 92.0625f), Vector3(0f, 0f, 180.0f))
+ )
+ ObjectToBuilding(1067, 81)
+ ObjectToBuilding(712, 81)
+ TerminalToSpawnPad(1067, 712)
+ }
}
val map14 = new ZoneMap("map14")
diff --git a/pslogin/src/main/scala/PsLogin.scala b/pslogin/src/main/scala/PsLogin.scala
index 05de1604..e689aa9e 100644
--- a/pslogin/src/main/scala/PsLogin.scala
+++ b/pslogin/src/main/scala/PsLogin.scala
@@ -203,12 +203,29 @@ object PsLogin {
)
*/
+ val continentList = createContinents()
val serviceManager = ServiceManager.boot
serviceManager ! ServiceManager.Register(RandomPool(50).props(Props[TaskResolver]), "taskResolver")
serviceManager ! ServiceManager.Register(Props[AvatarService], "avatar")
serviceManager ! ServiceManager.Register(Props[LocalService], "local")
serviceManager ! ServiceManager.Register(Props[VehicleService], "vehicle")
- serviceManager ! ServiceManager.Register(Props(classOf[InterstellarCluster], createContinents()), "galaxy")
+ serviceManager ! ServiceManager.Register(Props(classOf[InterstellarCluster], continentList), "galaxy")
+
+ //attach event bus entry point to each zone
+ import akka.pattern.ask
+ import akka.util.Timeout
+ import scala.concurrent.ExecutionContext.Implicits.global
+ import scala.concurrent.Future
+ import scala.util.{Failure, Success}
+ implicit val timeout = Timeout(200 milliseconds)
+ val requestVehicleEventBus : Future[ServiceManager.LookupResult] =
+ (ServiceManager.serviceManager ask ServiceManager.Lookup("vehicle")).mapTo[ServiceManager.LookupResult]
+ requestVehicleEventBus.onComplete {
+ case Success(ServiceManager.LookupResult(_, bus)) =>
+ continentList.foreach { _.VehicleEvents = bus }
+ case Failure(_) => ;
+ //TODO how to fail
+ }
/** Create two actors for handling the login and world server endpoints */
loginRouter = Props(new SessionRouter("Login", loginTemplate))
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index 46fe9aef..b7e803aa 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -26,13 +26,13 @@ import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech
import net.psforever.objects.serverobject.locks.IFFLock
import net.psforever.objects.serverobject.mblocker.Locker
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
-import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, ProximityTerminal, Terminal}
-import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
-import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, VehicleLockState}
+import net.psforever.objects.serverobject.pad.process.{AutoDriveControls, VehicleSpawnControlGuided}
import net.psforever.objects.serverobject.structures.{Building, StructureType, WarpGate}
+import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, ProximityTerminal, Terminal}
import net.psforever.objects.serverobject.terminals.Terminal
+import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
import net.psforever.objects.serverobject.tube.SpawnTube
-import net.psforever.objects.vehicles.{AccessPermissionGroup, VehicleLockState}
+import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, VehicleLockState}
import net.psforever.objects.zones.{InterstellarCluster, Zone}
import net.psforever.packet.game.objectcreate._
import net.psforever.types._
@@ -69,11 +69,20 @@ class WorldSessionActor extends Actor with MDCContextAware {
var usingMedicalTerminal : Option[PlanetSideGUID] = None
var usingProximityTerminal : Set[PlanetSideGUID] = Set.empty
var delayedProximityTerminalResets : Map[PlanetSideGUID, Cancellable] = Map.empty
+ var controlled : Option[Int] = None //keep track of avatar's ServerVehicleOverride state
var clientKeepAlive : Cancellable = DefaultCancellable.obj
var progressBarUpdate : Cancellable = DefaultCancellable.obj
var reviveTimer : Cancellable = DefaultCancellable.obj
+ /**
+ * Convert a boolean value into an integer value.
+ * Use: `true:Int` or `false:Int`
+ * @param b `true` or `false` (or `null`)
+ * @return 1 for `true`; 0 for `false`
+ */
+ implicit def boolToInt(b : Boolean) : Int = if(b) 1 else 0
+
override def postStop() = {
clientKeepAlive.cancel
reviveTimer.cancel
@@ -404,11 +413,17 @@ class WorldSessionActor extends Actor with MDCContextAware {
//resets exclamation point fte marker (once)
sendResponse(PlanetsideAttributeMessage(guid, 21, vehicle_guid.guid.toLong))
+ case VehicleResponse.AttachToRails(vehicle_guid, pad_guid) =>
+ sendResponse(ObjectAttachMessage(pad_guid, vehicle_guid, 3))
+
case VehicleResponse.ChildObjectState(object_guid, pitch, yaw) =>
if(tplayer_guid != guid) {
sendResponse(ChildObjectStateMessage(object_guid, pitch, yaw))
}
+ case VehicleResponse.ConcealPlayer(player_guid) =>
+ sendResponse(GenericObjectActionMessage(player_guid, 36))
+
case VehicleResponse.DismountVehicle(unk1, unk2) =>
if(tplayer_guid != guid) {
sendResponse(DismountVehicleMsg(guid, unk1, unk2))
@@ -419,6 +434,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(DeployRequestMessage(guid, object_guid, state, unk1, unk2, pos))
}
+ case VehicleResponse.DetachFromRails(vehicle_guid, pad_guid, pad_position, pad_orientation_z) =>
+ sendResponse(ObjectDetachMessage(pad_guid, vehicle_guid, pad_position + Vector3(0,0,0.5f), 0, 0, pad_orientation_z))
+
case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) =>
if(tplayer_guid != guid) {
//TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly?
@@ -456,6 +474,16 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(ObjectAttachMessage(vehicle_guid, guid, seat))
}
+ case VehicleResponse.ResetSpawnPad(pad_guid) =>
+ sendResponse(GenericObjectActionMessage(pad_guid, 92))
+
+ case VehicleResponse.RevealPlayer(player_guid) =>
+ //TODO any action will cause the player to appear after the effects of ConcealPlayer
+ if(player.GUID == player_guid) {
+ sendResponse(ChatMsg(ChatMessageType.CMT_OPEN, true, "", "You are in a strange situation.", None))
+ KillPlayer(player)
+ }
+
case VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission) =>
if(tplayer_guid != guid) {
sendResponse(PlanetsideAttributeMessage(vehicle_guid, seat_group, permission))
@@ -614,6 +642,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(player_guid == player.GUID) {
//disembarking self
log.info(s"DismountVehicleMsg: $player_guid dismounts $obj @ $seat_num")
+ TotalDriverVehicleControl(obj)
sendResponse(DismountVehicleMsg(player_guid, seat_num, false))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, seat_num, false))
UnAccessContents(obj)
@@ -1000,46 +1029,63 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, msg.transaction_type, false))
}
- case VehicleSpawnPad.ConcealPlayer =>
- sendResponse(GenericObjectActionMessage(player.GUID, 36))
- avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ConcealPlayer(player.GUID))
-
- case VehicleSpawnPad.LoadVehicle(vehicle, _/*pad*/) =>
- val player_guid = player.GUID
- val definition = vehicle.Definition
- val objedtId = definition.ObjectId
+ case VehicleSpawnPad.StartPlayerSeatedInVehicle(vehicle, pad) =>
val vehicle_guid = vehicle.GUID
- val vdata = definition.Packet.ConstructorData(vehicle).get
- sendResponse(ObjectCreateMessage(objedtId, vehicle_guid, vdata))
- continent.Transport ! Zone.SpawnVehicle(vehicle)
- vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.LoadVehicle(player_guid, vehicle, objedtId, vehicle_guid, vdata))
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 1L)) //mount points off?
- sendResponse(PlanetsideAttributeMessage(vehicle_guid, 21, player_guid.guid)) //fte and ownership?
- //sendResponse(ObjectAttachMessage(vehicle_guid, player_guid, 0))
- vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) //cancel queue timeout delay
- vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 21L) //temporary drive away from pad delay
- vehicle.Actor ! Mountable.TryMount(player, 0)
+ sendResponse(PlanetsideAttributeMessage(vehicle_guid, 21, player.GUID.guid)) //fte and ownership?
- case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle) =>
- vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 21L) //sitting in the vehicle clears the drive away delay
+ case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle, pad) =>
val vehicle_guid = vehicle.GUID
+ if(player.VehicleSeated.nonEmpty) {
+ vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid)
+ }
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 0L)) //mount points on?
//sendResponse(PlanetsideAttributeMessage(vehicle_guid, 0, vehicle.Definition.MaxHealth)))
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 68, 0L)) //???
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 113, 0L)) //???
ReloadVehicleAccessPermissions(vehicle)
+ ServerVehicleLock(vehicle)
- case VehicleSpawnPad.SpawnPadBlockedWarning(vehicle, warning_count) =>
- if(warning_count > 2) {
- sendResponse(TriggerSoundMessage(TriggeredSound.Unknown14, vehicle.Position, 20, 1f))
- sendResponse(
- ChatMsg(ChatMessageType.CMT_TELL, true, "", "\\#FYour vehicle is blocking the spawn pad, and will be deconstructed if not moved.", None)
- )
+ case VehicleSpawnPad.ServerVehicleOverrideStart(vehicle, pad) =>
+ val vdef = vehicle.Definition
+ if(vehicle.Seats(0).isOccupied) {
+ sendResponse(ObjectDetachMessage(pad.GUID, vehicle.GUID, pad.Position + Vector3(0, 0, 0.5f), 0, 0, pad.Orientation.z))
+ }
+ ServerVehicleOverride(vehicle, vdef.AutoPilotSpeed1, GlobalDefinitions.isFlightVehicle(vdef):Int)
+
+ case VehicleSpawnControlGuided.GuidedControl(cmd, vehicle, data) =>
+ cmd match {
+ case AutoDriveControls.State.Drive =>
+ val speed : Int = data.getOrElse({ vehicle.Definition.AutoPilotSpeed1 }).asInstanceOf[Int]
+ ServerVehicleOverride(vehicle, speed)
+
+ case AutoDriveControls.State.Climb =>
+ ServerVehicleOverride(vehicle, controlled.getOrElse(0), GlobalDefinitions.isFlightVehicle(vehicle.Definition):Int)
+
+ case AutoDriveControls.State.Turn =>
+ //TODO how to turn hovering/flying vehicle?
+ val direction = data.getOrElse(15).asInstanceOf[Int]
+ sendResponse(VehicleStateMessage(vehicle.GUID, 0, vehicle.Position, vehicle.Orientation, vehicle.Velocity, None, 0, 0, direction, false, false))
+
+
+ case AutoDriveControls.State.Stop =>
+ ServerVehicleOverride(vehicle, 0)
+
+ case _ => ;
}
- case VehicleSpawnPad.SpawnPadUnblocked(vehicle_guid) =>
- //vehicle has moved away from spawn pad after initial spawn
- vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) //cancel temporary drive away from pad delay
+ case VehicleSpawnPad.ServerVehicleOverrideEnd(vehicle, pad) =>
+ sendResponse(GenericObjectActionMessage(pad.GUID, 92)) //reset spawn pad
+ DriverVehicleControl(vehicle, vehicle.Definition.AutoPilotSpeed2)
+
+ case VehicleSpawnPad.PeriodicReminder(cause, data) =>
+ val msg : String = (cause match {
+ case VehicleSpawnPad.Reminders.Blocked =>
+ s"The vehicle spawn where you placed your order is blocked. ${data.getOrElse("")}"
+ case VehicleSpawnPad.Reminders.Queue =>
+ s"Your position in the vehicle spawn queue is ${data.get}."
+ })
+ sendResponse(ChatMsg(ChatMessageType.CMT_OPEN, true, "", msg, None))
case ListAccountCharacters =>
import net.psforever.objects.definition.converter.CharacterSelectConverter
@@ -1539,7 +1585,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case _ =>
log.warn(s"VehicleState: no vehicle $vehicle_guid found in zone")
}
- //log.info("VehicleState: " + msg)
+ //log.info(s"VehicleState: $msg")
case msg @ VehicleSubStateMessage(vehicle_guid, player_guid, vehicle_pos, vehicle_ang, vel, unk1, unk2) =>
//log.info(s"VehicleSubState: $vehicle_guid, $player_guid, $vehicle_pos, $vehicle_ang, $vel, $unk1, $unk2")
@@ -2753,14 +2799,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
new Task() {
private val localVehicle = obj
private val localPad = pad.Actor
- private val localAnnounce = vehicleService
private val localSession : String = sessionId.toString
private val localPlayer = player
private val localVehicleService = vehicleService
private val localZone = continent
override def isComplete : Task.Resolution.Value = {
- if(localVehicle.Actor != ActorRef.noSender) {
+ if(localVehicle.HasGUID) {
Task.Resolution.Success
}
else {
@@ -2769,9 +2814,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
def Execute(resolver : ActorRef) : Unit = {
- localAnnounce ! VehicleServiceMessage.GiveActorControl(obj, localSession)
localPad ! VehicleSpawnPad.VehicleOrder(localPlayer, localVehicle)
- localVehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(localVehicle, localZone, 60L)
resolver ! scala.util.Success(this)
}
}, List(RegisterVehicle(obj)))
@@ -3496,6 +3539,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, pos, player.Faction, true))
if(tplayer.VehicleSeated.nonEmpty) {
//make player invisible (if not, the cadaver sticks out the side in a seated position)
+ TotalDriverVehicleControl(continent.GUID(tplayer.VehicleSeated.get).get.asInstanceOf[Vehicle])
sendResponse(PlanetsideAttributeMessage(player_guid, 29, 1))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 29, 1))
}
@@ -3852,6 +3896,54 @@ class WorldSessionActor extends Actor with MDCContextAware {
tplayer.Armor == tplayer.MaxArmor
}
+ /**
+ * Lock all applicable controls of the current vehicle.
+ * This includes forward motion, turning, and, if applicable, strafing.
+ * @param vehicle the vehicle being controlled
+ */
+ def ServerVehicleLock(vehicle : Vehicle) : Unit = {
+ controlled = Some(0)
+ sendResponse(ServerVehicleOverrideMsg(true, true, false, false, 0, 1, 0, Some(0)))
+ }
+
+ /**
+ * Place the current vehicle under the control of the server's commands.
+ * @param vehicle the vehicle
+ * @param speed how fast the vehicle is moving forward
+ * @param flight whether the vehicle is ascending or not, if the vehicle is an applicable type
+ */
+ def ServerVehicleOverride(vehicle : Vehicle, speed : Int = 0, flight : Int = 0) : Unit = {
+ controlled = Some(speed)
+ sendResponse(ServerVehicleOverrideMsg(true, true, false, false, flight, 0, speed, Some(0)))
+ }
+
+ /**
+ * Place the current vehicle under the control of the driver's commands,
+ * but leave it in a cancellable auto-drive.
+ * @param vehicle the vehicle
+ * @param speed how fast the vehicle is moving forward
+ * @param flight whether the vehicle is ascending or not, if the vehicle is an applicable type
+ */
+ def DriverVehicleControl(vehicle : Vehicle, speed : Int = 0, flight : Int = 0) : Unit = {
+ if(controlled.nonEmpty) {
+ controlled = None
+ sendResponse(ServerVehicleOverrideMsg(false, false, false, true, flight, 0, speed, None))
+ }
+ }
+
+ /**
+ * Place the current vehicle under the control of the driver's commands,
+ * but leave it in a cancellable auto-drive.
+ * Stop all movement entirely.
+ * @param vehicle the vehicle
+ */
+ def TotalDriverVehicleControl(vehicle : Vehicle) : Unit = {
+ if(controlled.nonEmpty) {
+ controlled = None
+ sendResponse(ServerVehicleOverrideMsg(false, false, false, false, 0, 0, 0, None))
+ }
+ }
+
def failWithError(error : String) = {
log.error(error)
sendResponse(ConnectionClose())
diff --git a/pslogin/src/main/scala/Zones.scala b/pslogin/src/main/scala/Zones.scala
index 1944179f..55d18bb9 100644
--- a/pslogin/src/main/scala/Zones.scala
+++ b/pslogin/src/main/scala/Zones.scala
@@ -1,5 +1,7 @@
// Copyright (c) 2017 PSForever
import akka.actor.ActorContext
+import net.psforever.objects.serverobject.pad.VehicleSpawnPad
+import net.psforever.objects.serverobject.pad.process._
import net.psforever.objects.zones.Zone
import net.psforever.types.PlanetSideEmpire
@@ -49,6 +51,10 @@ object Zones {
import net.psforever.types.PlanetSideEmpire
Buildings.values.foreach { _.Faction = PlanetSideEmpire.VS }
Building(29).get.Faction = PlanetSideEmpire.NC //South Villa Gun Tower
+ GUID(293).get.asInstanceOf[VehicleSpawnPad].Railed = false //building 52
+ GUID(706).get.asInstanceOf[VehicleSpawnPad].Guide = List(AutoDriveControls.DistanceFromHere(50f)) //building 77
+ GUID(710).get.asInstanceOf[VehicleSpawnPad].Railed = false //building 79
+ GUID(712).get.asInstanceOf[VehicleSpawnPad].Railed = false //building 81
}
}
diff --git a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala
index a7bed3bc..5c2fb5b2 100644
--- a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala
+++ b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala
@@ -9,14 +9,19 @@ import net.psforever.types.{DriveState, Vector3}
object VehicleResponse {
trait Response
+ final case class AttachToRails(vehicle_guid : PlanetSideGUID, rails_guid : PlanetSideGUID) extends Response
final case class Awareness(vehicle_guid : PlanetSideGUID) extends Response
final case class ChildObjectState(object_guid : PlanetSideGUID, pitch : Float, yaw : Float) extends Response
+ final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Response
final case class DeployRequest(object_guid : PlanetSideGUID, state : DriveState.Value, unk1 : Int, unk2 : Boolean, pos : Vector3) extends Response
+ final case class DetachFromRails(vehicle_guid : PlanetSideGUID, rails_guid : PlanetSideGUID, rails_pos : Vector3, rails_rot : Float) extends Response
final case class DismountVehicle(unk1 : Int, unk2 : Boolean) extends Response
final case class InventoryState(obj : PlanetSideGameObject, parent_guid : PlanetSideGUID, start : Int, con_data : ConstructorData) extends Response
final case class KickPassenger(unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Response
final case class LoadVehicle(vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Response
final case class MountVehicle(object_guid : PlanetSideGUID, seat : Int) extends Response
+ final case class ResetSpawnPad(pad_guid : PlanetSideGUID) extends Response
+ final case class RevealPlayer(player_guid : PlanetSideGUID) extends Response
final case class SeatPermissions(vehicle_guid : PlanetSideGUID, seat_group : Int, permission : Long) extends Response
final case class StowEquipment(vehicle_guid : PlanetSideGUID, slot : Int, itype : Int, iguid : PlanetSideGUID, idata : ConstructorData) extends Response
final case class UnloadVehicle(vehicle_guid : PlanetSideGUID) extends Response
diff --git a/pslogin/src/main/scala/services/vehicle/VehicleService.scala b/pslogin/src/main/scala/services/vehicle/VehicleService.scala
index 26a2412e..e820db05 100644
--- a/pslogin/src/main/scala/services/vehicle/VehicleService.scala
+++ b/pslogin/src/main/scala/services/vehicle/VehicleService.scala
@@ -2,11 +2,12 @@
package services.vehicle
import akka.actor.{Actor, ActorRef, Props}
-import services.vehicle.support.{DeconstructionActor, DelayedDeconstructionActor, VehicleContextActor}
+import net.psforever.objects.serverobject.pad.VehicleSpawnPad
+import net.psforever.objects.zones.Zone
+import services.vehicle.support.{DeconstructionActor, DelayedDeconstructionActor}
import services.{GenericEventBus, Service}
class VehicleService extends Actor {
- private val vehicleContext : ActorRef = context.actorOf(Props[VehicleContextActor], "vehicle-context-root")
private val vehicleDecon : ActorRef = context.actorOf(Props[DeconstructionActor], "vehicle-decon-agent")
private val vehicleDelayedDecon : ActorRef = context.actorOf(Props[DelayedDeconstructionActor], "vehicle-delayed-decon-agent")
vehicleDecon ! DeconstructionActor.RequestTaskResolver
@@ -91,14 +92,6 @@ class VehicleService extends Actor {
case _ => ;
}
- //message to VehicleContext
- case VehicleServiceMessage.GiveActorControl(vehicle, actorName) =>
- vehicleContext ! VehicleServiceMessage.GiveActorControl(vehicle, actorName)
-
- //message to VehicleContext
- case VehicleServiceMessage.RevokeActorControl(vehicle) =>
- vehicleContext ! VehicleServiceMessage.RevokeActorControl(vehicle)
-
//message to DeconstructionActor
case VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent) =>
vehicleDecon ! DeconstructionActor.RequestDeleteVehicle(vehicle, continent)
@@ -117,6 +110,53 @@ class VehicleService extends Actor {
VehicleServiceResponse(s"/$zone_id/Vehicle", Service.defaultPlayerGUID, VehicleResponse.UnloadVehicle(vehicle_guid))
)
+ //from VehicleSpawnControl
+ case VehicleSpawnPad.ConcealPlayer(player_guid, zone_id) =>
+ VehicleEvents.publish(
+ VehicleServiceResponse(s"/$zone_id/Vehicle", Service.defaultPlayerGUID, VehicleResponse.ConcealPlayer(player_guid))
+ )
+
+ //from VehicleSpawnControl
+ case VehicleSpawnPad.AttachToRails(vehicle, pad, zone_id) =>
+ VehicleEvents.publish(
+ VehicleServiceResponse(s"/$zone_id/Vehicle", Service.defaultPlayerGUID, VehicleResponse.AttachToRails(vehicle.GUID, pad.GUID))
+ )
+
+ //from VehicleSpawnControl
+ case VehicleSpawnPad.DetachFromRails(vehicle, pad, zone_id) =>
+ VehicleEvents.publish(
+ VehicleServiceResponse(s"/$zone_id/Vehicle", Service.defaultPlayerGUID, VehicleResponse.DetachFromRails(vehicle.GUID, pad.GUID, pad.Position, pad.Orientation.z))
+ )
+
+ //from VehicleSpawnControl
+ case VehicleSpawnPad.ResetSpawnPad(pad, zone_id) =>
+ VehicleEvents.publish(
+ VehicleServiceResponse(s"/$zone_id/Vehicle", Service.defaultPlayerGUID, VehicleResponse.ResetSpawnPad(pad.GUID))
+ )
+
+ case VehicleSpawnPad.RevealPlayer(player_guid, zone_id) =>
+ VehicleEvents.publish(
+ VehicleServiceResponse(s"/$zone_id/Vehicle", Service.defaultPlayerGUID, VehicleResponse.RevealPlayer(player_guid))
+ )
+
+ //from VehicleSpawnControl
+ case VehicleSpawnPad.LoadVehicle(vehicle, zone) =>
+ val definition = vehicle.Definition
+ val vtype = definition.ObjectId
+ val vguid = vehicle.GUID
+ val vdata = definition.Packet.ConstructorData(vehicle).get
+ zone.Transport ! Zone.Vehicle.Spawn(vehicle)
+ VehicleEvents.publish(
+ VehicleServiceResponse(s"/${zone.Id}/Vehicle", Service.defaultPlayerGUID, VehicleResponse.LoadVehicle(vehicle, vtype, vguid, vdata))
+ )
+ vehicleDelayedDecon ! DelayedDeconstructionActor.UnscheduleDeconstruction(vguid)
+ vehicleDelayedDecon ! DelayedDeconstructionActor.ScheduleDeconstruction(vehicle, zone, 600L) //10min
+
+ //from VehicleSpawnControl
+ case VehicleSpawnPad.DisposeVehicle(vehicle, zone) =>
+ vehicleDelayedDecon ! DelayedDeconstructionActor.UnscheduleDeconstruction(vehicle.GUID)
+ vehicleDecon ! DeconstructionActor.RequestDeleteVehicle(vehicle, zone)
+
case msg =>
log.info(s"Unhandled message $msg from $sender")
}
diff --git a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala
index 2aaefff4..9d7a3efa 100644
--- a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala
+++ b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala
@@ -82,9 +82,8 @@ class DeconstructionActor extends Actor {
val vehicle = entry.vehicle
val zone = entry.zone
vehicle.Position = Vector3.Zero //somewhere it will not disturb anything
- entry.zone.Transport ! Zone.DespawnVehicle(vehicle)
+ entry.zone.Transport ! Zone.Vehicle.Despawn(vehicle)
context.parent ! DeconstructionActor.DeleteVehicle(vehicle.GUID, zone.Id) //call up to the main event system
- context.parent ! VehicleServiceMessage.RevokeActorControl(vehicle) //call up to a sibling manager
taskResolver ! DeconstructionTask(vehicle, zone)
})
diff --git a/pslogin/src/main/scala/services/vehicle/support/VehicleContextActor.scala b/pslogin/src/main/scala/services/vehicle/support/VehicleContextActor.scala
deleted file mode 100644
index ebc954aa..00000000
--- a/pslogin/src/main/scala/services/vehicle/support/VehicleContextActor.scala
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2017 PSForever
-package services.vehicle.support
-
-import akka.actor.{Actor, ActorRef, Props}
-import net.psforever.objects.vehicles.VehicleControl
-import services.vehicle.VehicleServiceMessage
-
-/**
- * Provide a context for a `Vehicle` `Actor` - the `VehicleControl`.
- *
- * A vehicle can be passed between different zones and, therefore, does not belong to the zone.
- * A vehicle cna be given to different players and can persist and change though players have gone.
- * Therefore, also does not belong to `WorldSessionActor`.
- * A vehicle must anchored to something that exists outside of the `InterstellarCluster` and its agents.
- *
- * The only purpose of this `Actor` is to allow vehicles to borrow a context for the purpose of `Actor` creation.
- * It is also be allowed to be responsible for cleaning up that context.
- * (In reality, it can be cleaned up anywhere a `PoisonPill` can be sent.)
- *
- * This `Actor` is intended to sit on top of the event system that handles broadcast messaging.
- */
-class VehicleContextActor() extends Actor {
- def receive : Receive = {
- case VehicleServiceMessage.GiveActorControl(vehicle, actorName) =>
- vehicle.Actor = context.actorOf(Props(classOf[VehicleControl], vehicle), s"${vehicle.Definition.Name}_$actorName.${System.nanoTime()}")
-
- case VehicleServiceMessage.RevokeActorControl(vehicle) =>
- vehicle.Actor ! akka.actor.PoisonPill
- vehicle.Actor = ActorRef.noSender
-
- case _ => ;
- }
-}