Reorganized vehicle spawning process and wrote tests for process.

This commit is contained in:
FateJH 2018-04-15 22:37:35 -04:00
parent c87273c351
commit 5d5c609a2f
11 changed files with 709 additions and 237 deletions

View file

@ -49,97 +49,85 @@ class VehicleSpawnControl(pad : VehicleSpawnPad) extends VehicleSpawnControlBase
trace(s"order from $player for $vehicle received")
orders = orders :+ VehicleSpawnControl.Order(player, vehicle, sender)
if(trackedOrder.isEmpty && orders.length == 1) {
self ! VehicleSpawnControl.ProcessControl.GetOrder
SelectOrder()
}
else {
sender ! VehicleSpawnControl.RenderOrderRemainderMsg(orders.length + 1)
}
case VehicleSpawnControl.ProcessControl.GetOrder =>
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
}
case VehicleSpawnControl.ProcessControl.CancelOrder =>
VehicleSpawnControl.recursiveFindOrder(orders.iterator, sender) match {
case None => ;
case Some(index) =>
val dequeuedOrder = orders(index)
orders = orders.take(index - 1) ++ orders.drop(index + 1)
trace(s"${dequeuedOrder.driver}'s vehicle order has been cancelled")
}
case VehicleSpawnControl.ProcessControl.GetNewOrder =>
if(sender == concealPlayer) {
trackedOrder = None //guard off
self ! VehicleSpawnControl.ProcessControl.GetOrder
SelectOrder()
}
/*
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 =>
/*
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.
*/
if(periodicReminder.isCancelled) {
trace(s"the pad has become blocked by ${trackedOrder.get.vehicle.Definition.Name}")
periodicReminder = context.system.scheduler.schedule(
VehicleSpawnControl.initialReminderDelay,
VehicleSpawnControl.periodicReminderDelay,
self, VehicleSpawnControl.ProcessControl.Reminder
)
}
else {
VehicleSpawnControl.BlockedReminder(trackedOrder, trackedOrder.get +: orders)
trackedOrder match {
case Some(entry) =>
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 => ;
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 {
private final val initialReminderDelay : FiniteDuration = 10000 milliseconds
private final val periodicReminderDelay : FiniteDuration = 10000 milliseconds
/**
* A `TaskResolver` to assist with the deconstruction of vehicles.
* Treated like a `lazy val`, this only gets defined once and then keeps getting reused.
* Since the use case is "if something goes wrong," a limited implementation should be fine.
*/
private var emergencyResolver : Option[ActorRef] = None
/**
* An `Enumeration` of non-data control messages for the vehicle spawn process.
*/
object ProcessControl extends Enumeration {
val
Reminder,
GetOrder,
GetNewOrder,
CancelOrder
GetNewOrder
= Value
}
/**
@ -170,23 +158,46 @@ object VehicleSpawnControl {
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.
* @see `VehicleSpawnControl.emergencyResolver`
* 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 net.psforever.objects.guid.GUIDTask
emergencyResolver.getOrElse({
import akka.routing.SmallestMailboxPool
import net.psforever.objects.guid.TaskResolver
val resolver = context.actorOf(SmallestMailboxPool(10).props(Props[TaskResolver]), "vehicle-spawn-control-emergency-decon-resolver")
emergencyResolver = Some(resolver)
resolver
}) ! GUIDTask.UnregisterVehicle(entry.vehicle)(zone.GUID)
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
@ -204,7 +215,7 @@ object VehicleSpawnControl {
* @return an index-appropriate `VehicleSpawnPad.PeriodicReminder` object
*/
def RenderOrderRemainderMsg(position : Int) : VehicleSpawnPad.PeriodicReminder = {
VehicleSpawnPad.PeriodicReminder(s"Your position in the vehicle spawn queue is $position.")
VehicleSpawnPad.PeriodicReminder(VehicleSpawnPad.Reminders.Queue, Some(s"$position"))
}
/**
@ -212,42 +223,38 @@ object VehicleSpawnControl {
* @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 : Option[VehicleSpawnControl.Order], recipients : Seq[VehicleSpawnControl.Order]) : Unit = {
blockedOrder match {
case Some(entry) =>
val msg : String = if(entry.vehicle.Health == 0) {
"The vehicle spawn where you placed your order is blocked by wreckage."
}
else {
"The vehicle spawn where you placed your order is blocked."
}
VehicleSpawnControl.recursiveBlockedReminder(recipients.iterator, msg)
case None => ;
}
}
@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)
def BlockedReminder(blockedOrder : VehicleSpawnControl.Order, recipients : Seq[VehicleSpawnControl.Order]) : Unit = {
val wrecked : Option[Any] = if(blockedOrder.vehicle.Health == 0) {
Option("Clear the wreckage.")
}
else {
recursiveFindOrder(iter, target, index + 1)
None
}
}
VehicleSpawnControl.recursiveBlockedReminder(recipients.iterator, wrecked)
}
@tailrec private final def recursiveBlockedReminder(iter : Iterator[VehicleSpawnControl.Order], msg : String) : Unit = {
// @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
if(recipient.sendTo != ActorRef.noSender) {
recipient.sendTo ! VehicleSpawnPad.PeriodicReminder(msg)
recipient.sendTo ! VehicleSpawnPad.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, cause)
}
recursiveBlockedReminder(iter, msg)
recursiveBlockedReminder(iter, cause)
}
}

View file

@ -18,13 +18,12 @@ import net.psforever.packet.game.PlanetSideGUID
*/
class VehicleSpawnPad(spDef : VehicleSpawnPadDefinition) extends Amenity {
/**
* USE THIS BOOLEAN FOR DEVELOPMENT PURPOSES!<br>
* Purpose: use the ingame railed platform to lift the spawned vehicle out of the trench.
* When set, the client performs the standard vehicle entry procedure, including rail animations.
* 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.
* The main reason to disable this feature is to avoid an `ObjectAttachMessage` that may be dispatched for an incorrect object.
* Unset if not guaranteed to have the correct ingame globally unique id of the spawn pad.
* 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
@ -41,7 +40,7 @@ class VehicleSpawnPad(spDef : VehicleSpawnPadDefinition) extends Amenity {
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
@ -49,53 +48,115 @@ 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(player_guid : PlanetSideGUID, zone_id : String)
/**
* Undoes the above message.
* 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)
/**
* 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 to properly introduce the vehicle into the zone.
* @param vehicle the vehicle being spawned
* @param zone the zone in which the spawn pad is located
*/
final case class LoadVehicle(vehicle : Vehicle, zone : Zone)
/**
* 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 AttachToRails(vehicle : Vehicle, pad : VehicleSpawnPad, zone_id : String)
/**
* 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)
/**
* 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 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 pad the spawn pad
*/
final case class ServerVehicleOverrideStart(vehicle : Vehicle, pad : VehicleSpawnPad)
/**
* 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 ServerVehicleOverrideEnd(vehicle : Vehicle, pad : VehicleSpawnPad)
final case class ResetSpawnPad(pad : VehicleSpawnPad, zone_id : String)
final case class PeriodicReminder(msg : String)
/**
* 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.
* @param spDef the spawn pad's definition entry
@ -109,7 +170,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

@ -27,7 +27,7 @@ class VehicleSpawnControlConcealPlayer(pad : VehicleSpawnPad) extends VehicleSpa
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.isAlive && driver.Continent == Continent.Id && driver.VehicleSeated.isEmpty) {
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))

View file

@ -23,7 +23,7 @@ import scala.concurrent.duration._
class VehicleSpawnControlLoadVehicle(pad : VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
def LogId = "-loader"
val seatDriver = context.actorOf(Props(classOf[VehicleSpawnControlSeatDriver], pad), s"${context.parent.path.name}-seat")
val railJack = context.actorOf(Props(classOf[VehicleSpawnControlRailJack], pad), s"${context.parent.path.name}-rails")
def receive : Receive = {
case VehicleSpawnControl.Process.LoadVehicle(entry) =>
@ -32,12 +32,12 @@ class VehicleSpawnControlLoadVehicle(pad : VehicleSpawnPad) extends VehicleSpawn
trace(s"loading the ${vehicle.Definition.Name}")
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, seatDriver, VehicleSpawnControl.Process.SeatDriver(entry))
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.GetOrder
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
}
case VehicleSpawnControl.ProcessControl.Reminder =>

View file

@ -22,20 +22,25 @@ import scala.concurrent.duration._
class VehicleSpawnControlRailJack(pad : VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
def LogId = "-lifter"
val vehicleOverride = context.actorOf(Props(classOf[VehicleSpawnControlServerVehicleOverride], pad), s"${context.parent.path.name}-override")
val seatDriver = context.actorOf(Props(classOf[VehicleSpawnControlSeatDriver], pad), s"${context.parent.path.name}-seat")
def receive : Receive = {
case VehicleSpawnControl.Process.RailJackAction(entry) =>
if(entry.vehicle.Health == 0) {
//TODO detach vehicle from pad rails if necessary
trace(s"vehicle was already destroyed")
trace("vehicle was already destroyed; clean it up")
VehicleSpawnControl.DisposeSpawnedVehicle(entry, Continent)
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
}
else {
trace(s"extending rails with vehicle attached")
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 temporarily in pad trench")
}
context.parent ! VehicleSpawnControl.ProcessControl.Reminder
context.system.scheduler.scheduleOnce(10 milliseconds, vehicleOverride, VehicleSpawnControl.Process.ServerVehicleOverride(entry))
context.system.scheduler.scheduleOnce(10 milliseconds, seatDriver, VehicleSpawnControl.Process.SeatDriver(entry))
}
case VehicleSpawnControl.ProcessControl.GetNewOrder =>

View file

@ -25,34 +25,32 @@ import scala.concurrent.duration._
class VehicleSpawnControlSeatDriver(pad : VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
def LogId = "-usher"
val railJack = context.actorOf(Props(classOf[VehicleSpawnControlRailJack], pad), s"${context.parent.path.name}-rails")
val vehicleOverride = context.actorOf(Props(classOf[VehicleSpawnControlServerVehicleOverride], pad), s"${context.parent.path.name}-override")
def receive : Receive = {
case VehicleSpawnControl.Process.SeatDriver(entry) =>
if(entry.vehicle.Actor == ActorRef.noSender) { //wait for the component of the vehicle needed for seating to be loaded
context.system.scheduler.scheduleOnce(50 milliseconds, railJack, VehicleSpawnControl.Process.SeatDriver(entry))
context.system.scheduler.scheduleOnce(50 milliseconds, self, VehicleSpawnControl.Process.SeatDriver(entry))
}
else {
val driver = entry.driver
if(entry.vehicle.Health == 0) {
trace("vehicle was already destroyed; clean it up")
if(pad.Railed) {
Continent.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad, Continent.Id)
}
VehicleSpawnControl.DisposeSpawnedVehicle(entry, Continent)
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
}
else if(entry.sendTo != ActorRef.noSender && 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, VehicleSpawnControl.Process.AwaitDriverInSeat(entry))
}
else {
if(entry.sendTo != ActorRef.noSender && 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, VehicleSpawnControl.Process.AwaitDriverInSeat(entry))
}
else {
if(pad.Railed) {
Continent.VehicleEvents ! VehicleSpawnPad.AttachToRails(entry.vehicle, pad, Continent.Id)
}
trace("driver lost; vehicle stranded on pad")
context.system.scheduler.scheduleOnce(1000 milliseconds, railJack, VehicleSpawnControl.Process.RailJackAction(entry))
}
trace("driver lost; vehicle stranded on pad")
context.system.scheduler.scheduleOnce(1000 milliseconds, vehicleOverride, VehicleSpawnControl.Process.ServerVehicleOverride(entry))
}
}
@ -60,40 +58,42 @@ class VehicleSpawnControlSeatDriver(pad : VehicleSpawnPad) extends VehicleSpawnC
val driver = entry.driver
if(entry.vehicle.Health == 0) {
trace("vehicle was already destroyed; clean it up")
if(pad.Railed) {
Continent.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad, Continent.Id)
}
VehicleSpawnControl.DisposeSpawnedVehicle(entry, Continent)
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
}
else if(entry.sendTo == ActorRef.noSender) {
else if(entry.sendTo == ActorRef.noSender || driver.Continent != Continent.Id) {
trace("driver lost, but operations can continue")
self ! VehicleSpawnControl.Process.RailJackAction(entry)
vehicleOverride ! VehicleSpawnControl.Process.ServerVehicleOverride(entry)
}
else if(driver.isAlive && driver.Continent == Continent.Id && driver.VehicleSeated.isEmpty) {
else if(driver.isAlive && driver.VehicleSeated.isEmpty) {
context.system.scheduler.scheduleOnce(100 milliseconds, self, VehicleSpawnControl.Process.AwaitDriverInSeat(entry))
}
else {
trace(s"driver is sitting down")
context.system.scheduler.scheduleOnce(
VehicleSpawnControlSeatDriver.RaillessSeatAnimationTimes(entry.vehicle.Definition.Name) milliseconds,
self,
VehicleSpawnControl.Process.DriverInSeat(entry)
)
val time = if(pad.Railed) 1000 else VehicleSpawnControlSeatDriver.RaillessSeatAnimationTimes(entry.vehicle.Definition.Name)
context.system.scheduler.scheduleOnce(time milliseconds, self, VehicleSpawnControl.Process.DriverInSeat(entry))
}
case VehicleSpawnControl.Process.DriverInSeat(entry) =>
if(entry.vehicle.Health == 0) {
//TODO detach vehicle from pad rails if necessary
trace(s"vehicle was already destroyed; clean it up")
if(pad.Railed) {
Continent.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad, Continent.Id)
}
VehicleSpawnControl.DisposeSpawnedVehicle(entry, Continent)
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
}
else if(entry.sendTo != ActorRef.noSender) {
else 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)
context.system.scheduler.scheduleOnce(10 milliseconds, railJack, VehicleSpawnControl.Process.RailJackAction(entry))
context.system.scheduler.scheduleOnce(10 milliseconds, vehicleOverride, VehicleSpawnControl.Process.ServerVehicleOverride(entry))
}
else {
trace("driver lost, but operations can continue")
context.system.scheduler.scheduleOnce(10 milliseconds, railJack, VehicleSpawnControl.Process.RailJackAction(entry))
context.system.scheduler.scheduleOnce(10 milliseconds, vehicleOverride, VehicleSpawnControl.Process.ServerVehicleOverride(entry))
}
case VehicleSpawnControl.ProcessControl.Reminder =>
@ -118,6 +118,7 @@ object VehicleSpawnControlSeatDriver {
"fury" -> 600,
"quadassault" -> 600,
"quadstealth" -> 600,
"two_man_assault_buggy" -> 1000,
"skyguard" -> 1300,
"threemanheavybuggy" -> 1000,
"twomanheavybuggy" -> 1800,

View file

@ -26,25 +26,31 @@ class VehicleSpawnControlServerVehicleOverride(pad : VehicleSpawnPad) extends Ve
def receive : Receive = {
case VehicleSpawnControl.Process.ServerVehicleOverride(entry) =>
val vehicle = entry.vehicle
//TODO detach vehicle from pad rails
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)
}
finalClear ! VehicleSpawnControl.Process.FinalClearance(entry)
}
else if(entry.sendTo != ActorRef.noSender && entry.driver.VehicleSeated.contains(vehicle.GUID)) {
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, self, VehicleSpawnControl.Process.DriverVehicleControl(entry))
}
else {
if(pad.Railed) {
Continent.VehicleEvents ! VehicleSpawnPad.DetachFromRails(vehicle, pad, Continent.Id)
Continent.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad, Continent.Id)
}
finalClear ! VehicleSpawnControl.Process.FinalClearance(entry)
}
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")
}
@ -59,9 +65,6 @@ class VehicleSpawnControlServerVehicleOverride(pad : VehicleSpawnPad) extends Ve
}
}
else {
if(pad.Railed) {
Continent.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad, Continent.Id)
}
trace("can not properly return control to driver")
}
finalClear ! VehicleSpawnControl.Process.FinalClearance(entry)

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

@ -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,465 @@ 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)
//
// 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 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)
//
// 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 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 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(200 milliseconds)
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(10 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 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(0,0,11)
player.VehicleSeated = None //since shared between orders, is necessary
val probe3Msg5 = probe3.receiveOne(4 seconds)
assert(probe3Msg5.isInstanceOf[VehicleSpawnPad.ResetSpawnPad])
val probe3Msg6 = probe3.receiveOne(4 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 probe3Msg4 = probe3.receiveOne(200 milliseconds)
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(10 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(0,0,11)
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 VehicleSpawnControl5bTest 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(10 seconds)
assert(probe1Msg.isInstanceOf[VehicleSpawnPad.PeriodicReminder])
assert(probe1Msg.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked)
}
}
}
class VehicleSpawnControl6aTest extends ActorTest() {
"VehicleSpawnControl" should {
"the vehicle is destroyed while the player is sitting down; 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])
val probe3Msg3 = probe3.receiveOne(3 seconds)
assert(probe3Msg3.isInstanceOf[VehicleSpawnPad.AttachToRails])
val probe1Msg1 = probe1.receiveOne(200 milliseconds)
assert(probe1Msg1.isInstanceOf[VehicleSpawnPad.StartPlayerSeatedInVehicle])
vehicle.Health = 0 //problem
val probe3Msg5 = probe3.receiveOne(4 seconds)
assert(probe3Msg5.isInstanceOf[VehicleSpawnPad.ResetSpawnPad])
val probe3Msg6 = probe3.receiveOne(100 milliseconds)
assert(probe3Msg6.isInstanceOf[VehicleSpawnPad.DisposeVehicle])
val probe3Msg7 = probe3.receiveOne(100 milliseconds)
assert(probe3Msg7.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
}
}
}
//TODO test was poor; attempting to check the "if(vehicle.Health ..." cases in VehicleSpawnControlSeatDriver
//class VehicleSpawnControl6bTest extends ActorTest() {
// "VehicleSpawnControl" should {
// "player on wrong continent; the vehicle is then destroyed after being partially spawned, but 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])
//
// 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
//
// vehicle.Health = 0 //problem 2
// val probe3Msg3b = probe3.receiveOne(3 seconds)
// assert(probe3Msg3b.isInstanceOf[VehicleSpawnPad.DetachFromRails])
//
// val probe3Msg4 = probe3.receiveOne(3 seconds)
// assert(probe3Msg4.isInstanceOf[VehicleSpawnPad.ResetSpawnPad])
// val probe3Msg5 = probe3.receiveOne(1 seconds)
// assert(probe3Msg5.isInstanceOf[VehicleSpawnPad.DisposeVehicle])
// val probe3Msg6 = probe3.receiveOne(1 seconds)
// assert(probe3Msg6.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 VehicleSpawnControl6cTest 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(10 seconds)
assert(probe1Msg2.isInstanceOf[VehicleSpawnPad.PeriodicReminder])
assert(probe1Msg2.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked)
}
}
}
class VehicleSpawnControl7aTest extends ActorTest() {
"VehicleSpawnControl" should {
"the vehicle is destroyed while attached to the rails; it 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])
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])
vehicle.Health = 0 //problem
val probe3Msg4 = probe3.receiveOne(200 milliseconds)
assert(probe3Msg4.isInstanceOf[VehicleSpawnPad.DetachFromRails])
val probe3Msg5 = probe3.receiveOne(200 milliseconds)
assert(probe3Msg5.isInstanceOf[VehicleSpawnPad.ResetSpawnPad])
probe1.receiveOne(200 milliseconds) //Mountable.MountMessage
val probe1Msg4 = probe1.receiveOne(10 seconds)
assert(probe1Msg4.isInstanceOf[VehicleSpawnPad.PeriodicReminder])
assert(probe1Msg4.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked)
}
}
}
class VehicleSpawnControl7bTest 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(10 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

@ -1025,9 +1025,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
case VehicleSpawnPad.StartPlayerSeatedInVehicle(vehicle, pad) =>
val vehicle_guid = vehicle.GUID
if(pad.Railed) {
sendResponse(ObjectAttachMessage(pad.GUID, vehicle_guid, 3))
}
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 1L)) //mount points off?
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 21, player.GUID.guid)) //fte and ownership?
@ -1053,7 +1050,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(GenericObjectActionMessage(pad.GUID, 92)) //reset spawn pad
sendResponse(ServerVehicleOverrideMsg.Auto(vehicle.Definition.AutoPilotSpeed2))
case VehicleSpawnPad.PeriodicReminder(msg) =>
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 =>

View file

@ -81,11 +81,7 @@ class DeconstructionActor extends Actor {
vehiclesToScrap.foreach(entry => {
val vehicle = entry.vehicle
val zone = entry.zone
<<<<<<< 18a068c10272376450c412425118c86612af7397
vehicle.Position = Vector3.Zero //somewhere it will not disturb anything
=======
vehicle.Position = Vector3.Zero
>>>>>>> Splitting single vehicle spawn control process into several subtasks with their own control Actor objects. Importing SouNourS copy of ServerVehicleOverrideMessage packet and tests.
entry.zone.Transport ! Zone.Vehicle.Despawn(vehicle)
context.parent ! DeconstructionActor.DeleteVehicle(vehicle.GUID, zone.Id) //call up to the main event system
taskResolver ! DeconstructionTask(vehicle, zone)