mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-03-04 04:30:21 +00:00
Splitting single vehicle spawn control process into several subtasks. Vehicle event bus attached to zone objects, especially for interacting with VehicleSpawnControl. Importing SouNourS copy of ServerVehicleOverrideMessage packet and tests.
This commit is contained in:
parent
dbc2ea8084
commit
fde49773cd
22 changed files with 993 additions and 339 deletions
|
|
@ -1,173 +1,119 @@
|
|||
// 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
|
||||
self ! VehicleSpawnControl.ProcessControl.GetOrder
|
||||
}
|
||||
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.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
|
||||
}
|
||||
orders = remainingOrders
|
||||
completeOrder 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)
|
||||
|
||||
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.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.ProcessControl.GetNewOrder =>
|
||||
if(sender == concealPlayer) {
|
||||
trackedOrder = None //guard off
|
||||
self ! VehicleSpawnControl.ProcessControl.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.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
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
else {
|
||||
VehicleSpawnControl.BlockedReminder(trackedOrder, trackedOrder.get +: orders)
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
|
|
@ -175,31 +121,144 @@ class VehicleSpawnControl(pad : VehicleSpawnPad) extends Actor with FactionAffin
|
|||
}
|
||||
|
||||
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
|
||||
* 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.
|
||||
*/
|
||||
object Process extends Enumeration {
|
||||
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,
|
||||
ConcealPlayer,
|
||||
LoadVehicle,
|
||||
AwaitSeated,
|
||||
AwaitClearance
|
||||
GetNewOrder,
|
||||
CancelOrder
|
||||
= 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 AwaitDriverInSeat(entry : VehicleSpawnControl.Order) extends Order(entry)
|
||||
final case class DriverInSeat(entry : VehicleSpawnControl.Order) extends Order(entry)
|
||||
final case class RailJackAction(entry : VehicleSpawnControl.Order) extends Order(entry)
|
||||
final case class RailJackRelease(entry : VehicleSpawnControl.Order) extends Order(entry)
|
||||
final case class ServerVehicleOverride(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.
|
||||
* @param vehicle the vehicle
|
||||
* @param player the driver
|
||||
* @param zone the continent on which the vehicle was registered
|
||||
* @param context an `ActorContext` object for which to create the `TaskResolver` object
|
||||
*/
|
||||
def DisposeVehicle(vehicle : Vehicle, player : Player, 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(vehicle)(zone.GUID)
|
||||
zone.VehicleEvents ! VehicleSpawnPad.RevealPlayer(player.GUID, zone.Id)
|
||||
}
|
||||
/**
|
||||
* Properly clean up a vehicle that has been registered and spawned into the game world.
|
||||
* @param vehicle the vehicle
|
||||
* @param player the driver
|
||||
* @param zone the continent on which the vehicle was registered
|
||||
*/
|
||||
def DisposeSpawnedVehicle(vehicle : Vehicle, player : Player, zone: Zone) : Unit = {
|
||||
zone.VehicleEvents ! VehicleSpawnPad.DisposeVehicle(vehicle, zone)
|
||||
zone.VehicleEvents ! VehicleSpawnPad.RevealPlayer(player.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(s"Your position in the vehicle spawn queue is $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 : 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)
|
||||
}
|
||||
else {
|
||||
recursiveFindOrder(iter, target, index + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@tailrec private final def recursiveBlockedReminder(iter : Iterator[VehicleSpawnControl.Order], msg : String) : Unit = {
|
||||
if(iter.hasNext) {
|
||||
val recipient = iter.next
|
||||
if(recipient.sendTo != ActorRef.noSender) {
|
||||
recipient.sendTo ! VehicleSpawnPad.PeriodicReminder(msg)
|
||||
}
|
||||
recursiveBlockedReminder(iter, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.pad
|
|||
|
||||
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
|
||||
|
||||
/**
|
||||
|
|
@ -34,7 +35,12 @@ object VehicleSpawnPad {
|
|||
* An packet `GenericObjectActionMessage(/player/, 36)`, when used on a player character,
|
||||
* will cause that player character's model to fade into transparency.
|
||||
*/
|
||||
final case class ConcealPlayer()
|
||||
final case class ConcealPlayer(player_guid : PlanetSideGUID, zone_id : String)
|
||||
|
||||
/**
|
||||
* Undoes the above message.
|
||||
*/
|
||||
final case class RevealPlayer(player_guid : PlanetSideGUID, zone_id : String)
|
||||
|
||||
/**
|
||||
* A callback step in spawning the vehicle.
|
||||
|
|
@ -44,9 +50,10 @@ object VehicleSpawnPad {
|
|||
* 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.
|
||||
* @param vehicle the vehicle being spawned
|
||||
* @param pad the pad
|
||||
*/
|
||||
final case class LoadVehicle(vehicle : Vehicle, pad : VehicleSpawnPad)
|
||||
final case class LoadVehicle(vehicle : Vehicle, zone : Zone)
|
||||
|
||||
final case class StartPlayerSeatedInVehicle(vehicle : Vehicle)
|
||||
|
||||
/**
|
||||
* A TEMPORARY callback step in spawning the vehicle.
|
||||
|
|
@ -57,25 +64,13 @@ object VehicleSpawnPad {
|
|||
*/
|
||||
final case class PlayerSeatedInVehicle(vehicle : Vehicle)
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param vehicle the vehicle
|
||||
* @param warning_count the number of times a warning period has occurred
|
||||
*/
|
||||
final case class SpawnPadBlockedWarning(vehicle : Vehicle, warning_count : Int)
|
||||
final case class ServerVehicleOverrideStart(speed : Int)
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
final case class SpawnPadUnblocked(vehicle_guid : PlanetSideGUID)
|
||||
final case class ServerVehicleOverrideEnd(speed : Int)
|
||||
|
||||
final case class PeriodicReminder(msg : String)
|
||||
|
||||
final case class DisposeVehicle(vehicle : Vehicle, zone : Zone)
|
||||
|
||||
/**
|
||||
* Overloaded constructor.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
// 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.
|
||||
* The primary purpose of this superclass is to provide a common convention for the logging system's name.
|
||||
* Additional functionality that ewcovers the `Zone` of the owned amenity `VehcileSpawnPad` is also included.
|
||||
* @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 set to `trace`.
|
||||
* No important messages should processed by this agent; only consume general vehicle spawn status.
|
||||
* @param msg the message
|
||||
*/
|
||||
def trace(msg : String) : Unit = log.info(msg)
|
||||
|
||||
protected def Pad : VehicleSpawnPad = pad
|
||||
|
||||
/**
|
||||
* 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 called "nowhere."
|
||||
* Eventually, it will belong to an active `Building` object that belongs to an active `Zone` object with an identifier.
|
||||
* 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
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// 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.isAlive && 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.vehicle, driver, Continent)
|
||||
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
|
||||
}
|
||||
|
||||
case VehicleSpawnControl.ProcessControl.Reminder =>
|
||||
context.parent ! VehicleSpawnControl.ProcessControl.Reminder
|
||||
|
||||
case VehicleSpawnControl.ProcessControl.GetNewOrder =>
|
||||
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// 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) =>
|
||||
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, VehicleSpawnControl.Process.FinalClearance(entry))
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// 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>
|
||||
* 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 seatDriver = context.actorOf(Props(classOf[VehicleSpawnControlSeatDriver], pad), s"${context.parent.path.name}-seat")
|
||||
|
||||
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}")
|
||||
Continent.VehicleEvents ! VehicleSpawnPad.LoadVehicle(vehicle, Continent)
|
||||
context.system.scheduler.scheduleOnce(100 milliseconds, seatDriver, VehicleSpawnControl.Process.SeatDriver(entry))
|
||||
}
|
||||
else {
|
||||
trace("owner lost; abort order fulfillment")
|
||||
VehicleSpawnControl.DisposeVehicle(vehicle, entry.driver, Continent)
|
||||
context.parent ! VehicleSpawnControl.ProcessControl.GetOrder
|
||||
}
|
||||
|
||||
case VehicleSpawnControl.ProcessControl.Reminder =>
|
||||
context.parent ! VehicleSpawnControl.ProcessControl.Reminder
|
||||
|
||||
case VehicleSpawnControl.ProcessControl.GetNewOrder =>
|
||||
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// 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.
|
||||
* It has failure cases should the driver be in an incorrect state.<br>
|
||||
* __It currently does not work__.
|
||||
* @param pad the `VehicleSpawnPad` object being governed
|
||||
*/
|
||||
class VehicleSpawnControlRailJack(pad : VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
|
||||
def LogId = "-jacker"
|
||||
|
||||
val vehicleOverride = context.actorOf(Props(classOf[VehicleSpawnControlServerVehicleOverride], pad), s"${context.parent.path.name}-override")
|
||||
|
||||
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; clean it up")
|
||||
VehicleSpawnControl.DisposeSpawnedVehicle(entry.vehicle, entry.driver, Continent)
|
||||
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
|
||||
}
|
||||
else {
|
||||
trace(s"extending rails with vehicle attached")
|
||||
context.parent ! VehicleSpawnControl.ProcessControl.Reminder
|
||||
context.system.scheduler.scheduleOnce(10 milliseconds, vehicleOverride, VehicleSpawnControl.Process.ServerVehicleOverride(entry))
|
||||
}
|
||||
|
||||
case VehicleSpawnControl.ProcessControl.GetNewOrder =>
|
||||
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
// 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.
|
||||
* Three 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 railJack = context.actorOf(Props(classOf[VehicleSpawnControlRailJack], pad), s"${context.parent.path.name}-rails")
|
||||
|
||||
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))
|
||||
}
|
||||
else {
|
||||
val driver = entry.driver
|
||||
if(entry.vehicle.Health == 0) {
|
||||
//TODO detach vehicle from pad rails if necessary
|
||||
trace("vehicle was already destroyed; clean it up")
|
||||
VehicleSpawnControl.DisposeSpawnedVehicle(entry.vehicle, driver, 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)
|
||||
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 {
|
||||
trace("driver lost; vehicle stranded on pad")
|
||||
context.system.scheduler.scheduleOnce(1000 milliseconds, railJack, VehicleSpawnControl.Process.RailJackAction(entry))
|
||||
}
|
||||
}
|
||||
|
||||
case VehicleSpawnControl.Process.AwaitDriverInSeat(entry) =>
|
||||
val driver = entry.driver
|
||||
if(entry.vehicle.Health == 0) {
|
||||
//TODO detach vehicle from pad rails if necessary
|
||||
trace("vehicle was already destroyed; clean it up")
|
||||
VehicleSpawnControl.DisposeSpawnedVehicle(entry.vehicle, driver, Continent)
|
||||
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
|
||||
}
|
||||
else if(entry.sendTo == ActorRef.noSender) {
|
||||
trace("driver lost, but operations can continue")
|
||||
self ! VehicleSpawnControl.Process.RailJackAction(entry)
|
||||
}
|
||||
else if(driver.isAlive && driver.Continent == Continent.Id && driver.VehicleSeated.isEmpty) {
|
||||
context.system.scheduler.scheduleOnce(1000 milliseconds, self, VehicleSpawnControl.Process.AwaitDriverInSeat(entry))
|
||||
}
|
||||
else {
|
||||
trace(s"driver is sitting down")
|
||||
context.system.scheduler.scheduleOnce(1000 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")
|
||||
VehicleSpawnControl.DisposeSpawnedVehicle(entry.vehicle, entry.driver, Continent)
|
||||
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
|
||||
}
|
||||
else if(entry.sendTo != ActorRef.noSender) {
|
||||
trace(s"driver ${entry.driver.Name} has taken the wheel")
|
||||
entry.sendTo ! VehicleSpawnPad.PlayerSeatedInVehicle(entry.vehicle)
|
||||
context.system.scheduler.scheduleOnce(10 milliseconds, railJack, VehicleSpawnControl.Process.RailJackAction(entry))
|
||||
}
|
||||
else {
|
||||
trace("driver lost, but operations can continue")
|
||||
context.system.scheduler.scheduleOnce(10 milliseconds, railJack, VehicleSpawnControl.Process.RailJackAction(entry))
|
||||
}
|
||||
|
||||
case VehicleSpawnControl.ProcessControl.Reminder =>
|
||||
context.parent ! VehicleSpawnControl.ProcessControl.Reminder
|
||||
|
||||
case VehicleSpawnControl.ProcessControl.GetNewOrder =>
|
||||
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
// 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 finalClear = context.actorOf(Props(classOf[VehicleSpawnControlFinalClearance], pad), s"${context.parent.path.name}-final")
|
||||
|
||||
def receive : Receive = {
|
||||
case VehicleSpawnControl.Process.ServerVehicleOverride(entry) =>
|
||||
val vehicle = entry.vehicle
|
||||
//TODO detach vehicle from pad rails
|
||||
if(vehicle.Health == 0) {
|
||||
trace(s"vehicle was already destroyed; but, everything is fine")
|
||||
finalClear ! VehicleSpawnControl.Process.FinalClearance(entry)
|
||||
}
|
||||
else if(entry.sendTo != ActorRef.noSender && 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(22)
|
||||
context.system.scheduler.scheduleOnce(3000 milliseconds, self, VehicleSpawnControl.Process.DriverVehicleControl(entry))
|
||||
}
|
||||
else {
|
||||
finalClear ! VehicleSpawnControl.Process.FinalClearance(entry)
|
||||
}
|
||||
|
||||
case VehicleSpawnControl.Process.DriverVehicleControl(entry) =>
|
||||
val vehicle = entry.vehicle
|
||||
if(entry.sendTo != ActorRef.noSender) {
|
||||
if(vehicle.Health == 0) {
|
||||
trace(s"vehicle was already destroyed; but, everything is fine")
|
||||
}
|
||||
else {
|
||||
val driver = entry.driver
|
||||
entry.sendTo ! VehicleSpawnPad.ServerVehicleOverrideEnd(8)
|
||||
if(driver.VehicleSeated.contains(vehicle.GUID)) {
|
||||
trace(s"returning control of ${vehicle.Definition.Name} to ${driver.Name}")
|
||||
}
|
||||
else {
|
||||
trace(s"${driver.Name} is no longer seated in new ${vehicle.Definition.Name}; can not properly return control to driver")
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
trace("can not properly return control to driver")
|
||||
}
|
||||
finalClear ! VehicleSpawnControl.Process.FinalClearance(entry)
|
||||
|
||||
case msg @ VehicleSpawnControl.Process.FinalClearance(_) =>
|
||||
finalClear ! msg
|
||||
|
||||
case VehicleSpawnControl.ProcessControl.GetNewOrder =>
|
||||
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
// 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 the vehicle being overrode.
|
||||
* No message is displayed if the vehicle is placed under server control.
|
||||
* The vehicle will operate as if accelerating.<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>
|
||||
* Speed samples follow (from AMS):<br>
|
||||
* 1 -> 3<br>
|
||||
* 2 -> 7<br>
|
||||
* 3 -> 10<br>
|
||||
* 10 -> 35<br>
|
||||
* 15 -> 52<br>
|
||||
* 20 -> 68
|
||||
* @param lock_accelerator driver has no control over whether vehicle accelerates
|
||||
* @param lock_wheel driver has no control over whether the vehicle turns
|
||||
* @param reverse drive in reverse
|
||||
* @param unk4 na
|
||||
* @param unk5 na
|
||||
* @param unk6 na
|
||||
* @param speed "something like speed;"
|
||||
* for `n`, the pattern to calculate a constant in-game speed is `floor(3.5 x n)`;
|
||||
* during server control, an acceleration value (?);
|
||||
* during auto-drive, a velocity value
|
||||
* @param unk8 na;
|
||||
* set `lock_wheel` to `true` to expose value
|
||||
*/
|
||||
final case class ServerVehicleOverrideMsg(lock_accelerator : Boolean,
|
||||
lock_wheel : Boolean,
|
||||
reverse : Boolean,
|
||||
unk4 : Boolean,
|
||||
unk5 : Int,
|
||||
unk6 : Int,
|
||||
speed : Int,
|
||||
unk8 : Option[Long]
|
||||
) extends PlanetSideGamePacket {
|
||||
type Packet = ServerVehicleOverrideMsg
|
||||
def opcode = GamePacketOpcode.ServerVehicleOverrideMsg
|
||||
def encode = ServerVehicleOverrideMsg.encode(this)
|
||||
}
|
||||
|
||||
object ServerVehicleOverrideMsg extends Marshallable[ServerVehicleOverrideMsg] {
|
||||
/**
|
||||
* Common assert control packet format.
|
||||
* @param speed "something like speed"
|
||||
* @return a `ServerVehicleOverrideMsg` packet
|
||||
*/
|
||||
def On(speed : Int) : ServerVehicleOverrideMsg = {
|
||||
ServerVehicleOverrideMsg(true, true, false, false, 0, 0, speed, Some(0))
|
||||
}
|
||||
|
||||
/**
|
||||
* Common relinquish control packet format.
|
||||
* @param speed "something like speed"
|
||||
* @return a `ServerVehicleOverrideMsg` packet
|
||||
*/
|
||||
def Off(speed : Int) : ServerVehicleOverrideMsg = {
|
||||
ServerVehicleOverrideMsg(false, false, false, true, 0, 0, speed, None)
|
||||
}
|
||||
|
||||
implicit val codec: Codec[ServerVehicleOverrideMsg] = (
|
||||
("lock_accelerator" | bool) ::
|
||||
(("lock_wheel" | bool) >>:~ { test =>
|
||||
("reverse" | bool) ::
|
||||
("unk4" | bool) ::
|
||||
("unk5" | uint2L) ::
|
||||
("unk6" | uint2L) ::
|
||||
("speed" | uintL(9)) ::
|
||||
conditional(test, "unk8" | uint32L)
|
||||
})
|
||||
).as[ServerVehicleOverrideMsg]
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
// 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
|
||||
}
|
||||
|
||||
"encode (3)" in {
|
||||
val msg = ServerVehicleOverrideMsg.On(12)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string1
|
||||
}
|
||||
|
||||
"encode (4)" in {
|
||||
val msg = ServerVehicleOverrideMsg.Off(5)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string2
|
||||
}
|
||||
}
|
||||
|
|
@ -55,22 +55,22 @@ class VehicleSpawnControl2Test extends ActorTest() {
|
|||
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)
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue