Merge pull request #199 from Fate-JH/v-spawn-pad-redo

Vehicle Spawn Pad, Again
This commit is contained in:
Fate-JH 2018-05-05 22:36:52 -04:00 committed by GitHub
commit dd4b586bef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 2712 additions and 416 deletions

View file

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

View file

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

View file

@ -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`.<br>
* 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.<br>
* <br>
* 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:<br>
* 1. the vehicle is attached to a lifting platform that is designed to introduce the vehicle;<br>
* 2. the player is seated in the vehicle's driver seat (seat 0) and is thus declared the owner; <br>
* 3. various properties of the player, the vehicle, and the spawn pad itself are set `PlanetsideAttributesMessage`.<br>
* 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.<br>
* <br>
* 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.<br>
* <br>
* 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)
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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.<br>
* <br>
* 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.<br>
* <br>
* 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.)<br>
* <br>
* "Something like speed:"<br>
* 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]
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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`.<br>
* <br>
* 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.<br>
* <br>
* 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.)<br>
* <br>
* 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 _ => ;
}
}