From 68422401e5de1c62388bd51a1908e0a57360e810 Mon Sep 17 00:00:00 2001 From: FateJH Date: Sat, 21 Apr 2018 17:50:20 -0400 Subject: [PATCH] Another revamp to vehicle spawn process. Wrote tests. --- .../psforever/objects/GlobalDefinitions.scala | 35 +- .../scala/net/psforever/objects/Vehicle.scala | 10 + .../pad/VehicleSpawnControl.scala | 7 +- .../serverobject/pad/VehicleSpawnPad.scala | 10 + .../pad/process/AutoDriveControls.scala | 304 +++++++++++ .../pad/process/VehicleSpawnControlBase.scala | 14 +- .../VehicleSpawnControlConcealPlayer.scala | 7 +- .../VehicleSpawnControlDriverControl.scala | 54 ++ .../VehicleSpawnControlFinalClearance.scala | 12 +- .../process/VehicleSpawnControlGuided.scala | 126 +++++ .../VehicleSpawnControlLoadVehicle.scala | 12 +- .../process/VehicleSpawnControlRailJack.scala | 22 +- .../VehicleSpawnControlSeatDriver.scala | 87 ++- ...cleSpawnControlServerVehicleOverride.scala | 46 +- .../game/ServerVehicleOverrideMsg.scala | 23 +- .../packet/game/VehicleStateMessage.scala | 4 +- .../game/ServerVehicleOverrideMsgTest.scala | 14 - .../scala/objects/AutoDriveControlsTest.scala | 498 ++++++++++++++++++ .../scala/objects/VehicleSpawnPadTest.scala | 203 ++----- pslogin/src/main/scala/Maps.scala | 63 ++- .../src/main/scala/WorldSessionActor.scala | 54 +- pslogin/src/main/scala/Zones.scala | 2 + 22 files changed, 1267 insertions(+), 340 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/pad/process/AutoDriveControls.scala create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlDriverControl.scala create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlGuided.scala create mode 100644 common/src/test/scala/objects/AutoDriveControlsTest.scala diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index bb2c9f81..d763cbcc 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -846,7 +846,7 @@ object GlobalDefinitions { } /** - * Using the definition for a piece of `Equipment` determine whether it can fly. + * Using the definition for a `Vehicle` determine whether it can fly. * @param vdef the `VehicleDefinition` of the vehicle * @return `true`, if it is; `false`, otherwise */ @@ -859,6 +859,39 @@ object GlobalDefinitions { } } + /** + * Using the definition for a `Vehicle` determine whether it hovers. + * @param vdef the `VehicleDefinition` of the vehicle + * @return `true`, if it can; `false`, otherwise + */ + def isHoverVehicle(vdef : VehicleDefinition) : Boolean = { + vdef match { + case `twomanhoverbuggy` | `magrider` | `router` | `flail` => + true + case _ => + false + } + } + + /** + * Using the definition for a `Vehicle` determine whether it can rotate its body without forward acceleration. + * @param vdef the `VehicleDefinition` of the vehicle + * @return `true`, if it is; `false`, otherwise + */ + def canStationaryRotate(vdef : VehicleDefinition) : Boolean = { + if(isFlightVehicle(vdef) || isHoverVehicle(vdef)) { + true + } + else { + vdef match { + case `lightning` | `prowler` | `vanguard` => + true + case _ => + false + } + } + } + /** * Initialize `AmmoBoxDefinition` globals. */ diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala index 13854707..20dbc2d5 100644 --- a/common/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala @@ -47,6 +47,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ private var trunkAccess : Option[PlanetSideGUID] = None private var jammered : Boolean = false private var cloaked : Boolean = false + private var controlled : Option[Int] = None /** * Permissions control who gets to access different parts of the vehicle; @@ -443,6 +444,15 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ */ def TrunkLockState : VehicleLockState.Value = groupPermissions(3) + def Controlled : Option[Int] = controlled + + def Controlled_=(speed : Int) : Option[Int] = Controlled_=(Some(speed)) + + def Controlled_=(speed : Option[Int]) : Option[Int] = { + controlled = speed + Controlled + } + /** * This is the definition entry that is used to store and unload pertinent information about the `Vehicle`. * @return the vehicle's definition entry diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala index 054ef0f3..47f8f119 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala @@ -141,10 +141,9 @@ object VehicleSpawnControl { 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 ServerVehicleOverride(entry : VehicleSpawnControl.Order) extends Order(entry) + final case class StartGuided(entry : VehicleSpawnControl.Order) extends Order(entry) final case class DriverVehicleControl(entry : VehicleSpawnControl.Order) extends Order(entry) final case class FinalClearance(entry : VehicleSpawnControl.Order) extends Order(entry) } @@ -251,9 +250,7 @@ object VehicleSpawnControl { @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(VehicleSpawnPad.Reminders.Blocked, cause) - } + recipient.sendTo ! VehicleSpawnPad.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, cause) recursiveBlockedReminder(iter, cause) } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala index 9a4c1abe..7412b7cd 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala @@ -1,6 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.pad +import net.psforever.objects.serverobject.pad.process.AutoDriveControls import net.psforever.objects.{Player, Vehicle} import net.psforever.objects.serverobject.structures.Amenity import net.psforever.objects.zones.Zone @@ -27,6 +28,8 @@ class VehicleSpawnPad(spDef : VehicleSpawnPadDefinition) extends Amenity { */ private var onRails : Boolean = true + private var guidedPath : List[AutoDriveControls.Configuration] = Nil + def Railed : Boolean = onRails def Railed_=(useRails : Boolean) : Boolean = { @@ -34,6 +37,13 @@ class VehicleSpawnPad(spDef : VehicleSpawnPadDefinition) extends Amenity { Railed } + def Guide : List[AutoDriveControls.Configuration] = guidedPath + + def Guide_=(path : List[AutoDriveControls.Configuration]) : List[AutoDriveControls.Configuration] = { + guidedPath = path + Guide + } + def Definition : VehicleSpawnPadDefinition = spDef } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/AutoDriveControls.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/AutoDriveControls.scala new file mode 100644 index 00000000..5059c21f --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/AutoDriveControls.scala @@ -0,0 +1,304 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.pad.process + +import net.psforever.objects.{GlobalDefinitions, Vehicle} +import net.psforever.types.Vector3 + +/** + * Instructions to be processed by `VehicleSpawnControlGuided`. + * These instructions coordinate basic vehicle manipulations such as driving, turning, and stopping. + * If defined, they will operate on a newly formed vehicle after it is released from its spawn pad lifting platform + * and after it has been issued at least one `ServerVehicleOverrideMsg` packet. + */ +object AutoDriveControls { + + /** + * A container that translates to a new `Setting` instruction. + * Instructions are maintained in this form until they will be used due to the nature of the `Setting` object. + * The least that this object needs to do is accept parameters that matches the specific `Setting` that it outputs. + */ + sealed trait Configuration { + def Create : Setting + } + + /** + * An instruction to be consumed by the cyclic operation of `VehicleSpawnControlGuided` + * and are created by a `Configuration` object. + * They are considered semi-immutable `case class` objects. + * Externally, they are immutable by proper Scala standards. + * Internally, they will be permitted `private` fields that can be modified the first time the object is used. + */ + sealed trait Setting { + /** + * The nature of the action being performed. + * @return an enumerated value that explains the purpose of the action + */ + def Type : State.Value + /** + * The delay in between checks to determine if this setting has accomplished its goal or has entered an invalid state. + * @return the length of the delay + */ + def Delay : Long = 200L + /** + * Data that is important for fulfilling the instruction on a user's client. + * Highly specific to the implementation. + * @return any data deemed important; `None`, if unnecessary + */ + def Data : Option[Any] = None + /** + * Perform a test to determine if the vehicle is capable of performing the action this instruction requires. + * The test is typically simplistic in nature and often boils down to whether o not the vehicle is mobile. + * @param vehicle the vehicle being controlled + * @return `true`, if the action can (probably) be accomplished under the current conditions; `false`, otherwise + */ + def Validate(vehicle : Vehicle) : Boolean = Vector3.MagnitudeSquared(vehicle.Velocity.getOrElse(Vector3.Zero).xy) > 0 + /** + * Perform a test to determine if the vehicle has reached a set of conditions + * where the action performed by the instruction has been fulfilled. + * This should count as the "end of this step" and the "beginning of the next step." + * @param vehicle the vehicle being controlled + * @return `true`, if the action has run to completion; `false`, otherwise + */ + def CompletionTest(vehicle : Vehicle) : Boolean + } + + /** + * The nature of the action being performed. + * Different actions can be divided into types. + */ + object State extends Enumeration { + val + Cancel, + Climb, + Drive, + Stop, + Turn, + Wait + = Value + } + + protected final case class AutoDrive(speed : Int) extends Setting { + def Type = State.Drive + + override def Data = Some(speed) + + override def Validate(vehicle : Vehicle) : Boolean = true + + def CompletionTest(vehicle : Vehicle) = Vector3.MagnitudeSquared(vehicle.Velocity.getOrElse(Vector3.Zero).xy) > 0 + } + + protected final case class AutoDriveDistance(start : Vector3, sqDistance : Float) extends Setting { + def Type = State.Wait + + def CompletionTest(vehicle : Vehicle) : Boolean = { + Vector3.DistanceSquared(vehicle.Position.xy, start) > sqDistance + } + } + + protected final case class AutoDriveDistanceFromHere(sqDistance : Float) extends Setting { + private var start : Option[Vector3] = None + + def Type = State.Wait + + def CompletionTest(vehicle : Vehicle) : Boolean = { + val startLoc = start.getOrElse({ + start = Some(vehicle.Position.xy) + start.get + }) + Vector3.DistanceSquared(vehicle.Position.xy, startLoc) > sqDistance + } + } + + protected final case class AutoDriveForTime(length : Long) extends Setting { + private var start : Option[Long] = None + + def Type = State.Wait + + def CompletionTest(vehicle : Vehicle) : Boolean = { + val time : Long = System.currentTimeMillis + val startTime = start.getOrElse({ + start = Some(time) + time + }) + time - startTime >= length + } + + override def Validate(vehicle : Vehicle) : Boolean = true + } + + protected final case class AutoDriveTurnBy(angle : Float, direction : Int) extends Setting { + private var end : Option[Float] = None + private var currAng : Float = 0f + + def Type = State.Turn + + override def Delay : Long = 100L //increased frequency + + override def Data = Some(direction) + + def CompletionTest(vehicle : Vehicle) : Boolean = { + val endAng = end.getOrElse { + currAng = vehicle.Orientation.z + var ang = (currAng + angle) % 360f + if(ang < 0f) { + ang += 360f + } + end = Some(ang) + ang + } + val lastAng = currAng + currAng = vehicle.Orientation.z + //check that the expected angle is sandwiched between the previous angle and the current angle + currAng == endAng || (lastAng < endAng && endAng <= currAng) || (lastAng > endAng && endAng >= currAng) + } + + override def Validate(vehicle : Vehicle) : Boolean = direction != 15 && super.Validate(vehicle) + } + + protected final case class AutoDriveFirstGear() extends Setting { + private var speed : Int = 0 + + def Type = State.Drive + + override def Data = Some(speed) + + def CompletionTest(vehicle : Vehicle) = Vector3.MagnitudeSquared(vehicle.Velocity.getOrElse(Vector3.Zero)) > 0 + + override def Validate(vehicle : Vehicle) : Boolean = { + speed = vehicle.Definition.AutoPilotSpeed1 + true + } + } + + protected final case class AutoDriveSecondGear() extends Setting { + private var speed : Int = 0 + + def Type = State.Drive + + override def Data = Some(speed) + + def CompletionTest(vehicle : Vehicle) = Vector3.MagnitudeSquared(vehicle.Velocity.getOrElse(Vector3.Zero)) > 0 + + override def Validate(vehicle : Vehicle) : Boolean = { + speed = vehicle.Definition.AutoPilotSpeed2 + true + } + } + + protected final case class AutoDriveClimb(altitude : Float) extends Setting { + def Type = State.Climb + + override def Data = Some(altitude) + + def CompletionTest(vehicle : Vehicle) = { + vehicle.Position.z >= altitude + } + + override def Validate(vehicle : Vehicle) : Boolean = GlobalDefinitions.isFlightVehicle(vehicle.Definition) + } + + protected final case class AutoDriveCancelEarly(test : (Vehicle) => Boolean) extends Setting { + def Type = State.Cancel + + def CompletionTest(vehicle : Vehicle) = true + + override def Validate(vehicle : Vehicle) : Boolean = test(vehicle) + } + + protected final case class AutoDriveStop() extends Setting { + def Type = State.Stop + + override def Validate(vehicle : Vehicle) : Boolean = true + + def CompletionTest(vehicle : Vehicle) = Validate(vehicle) + } + + /** + * Use a validation test to determine if the remainder of the instructions should be processed. + * @param test the custom valid conditions of the vehicle for continuing + */ + final case class CancelEarly(test : (Vehicle)=>Boolean) extends Configuration { + def Create : Setting = AutoDriveControls.AutoDriveCancelEarly(test) + } + + /** + * Gain altitude with a flying vehicle. + * The climb speed is fixed. + * @param altitude the vertical distance to ascend + */ + final case class Climb(altitude : Float) extends Configuration { + def Create : Setting = AutoDriveControls.AutoDriveClimb(altitude) + } + + /** + * Drive a certain distance from somewhere. + * @param start the fixed coordinates of the origin point + * @param distance how far from the origin point the vehicle should travel + */ + final case class Distance(start : Vector3, distance : Float) extends Configuration { + def Create : Setting = AutoDriveControls.AutoDriveDistance(start, distance * distance) + } + + /** + * Drive a certain distance from where the vehicle is at the time that the instruction is called. + * The starting position is the current position of the vehicle. + * @param distance how far from the origin point the vehicle should travel + */ + final case class DistanceFromHere(distance : Float) extends Configuration { + def Create : Setting = AutoDriveControls.AutoDriveDistanceFromHere(distance * distance) + } + + /** + * Basic drive forward instruction. + * @see `ServerVehicleOverrideMsg.forward_speed` + * @param speed the speed that the vehicle accelerates to; + * scaled in a curious way + */ + final case class Drive(speed : Int) extends Configuration { + def Create : Setting = AutoDriveControls.AutoDrive(speed) + } + + /** + * Special drive forward instruction. + * @see `ServerVehicleOverrideMsg.forward_speed` + * @see `VehicleDefinition.AutoPilotSpeed1` + */ + final case class FirstGear() extends Configuration { + def Create : Setting = AutoDriveControls.AutoDriveFirstGear() + } + + /** + * Drive or idle for a certain duration. + * The starting position is the current position of the vehicle. + * @param time how long to contiue driving under the current conditions + */ + final case class ForTime(time : Long) extends Configuration { + def Create : Setting = AutoDriveControls.AutoDriveForTime(time) + } + + /** + * Special drive forward instruction. + * @see `ServerVehicleOverrideMsg.forward_speed` + * @see `VehicleDefinition.AutoPilotSpeed2` + */ + final case class SecondGear() extends Configuration { + def Create : Setting = AutoDriveControls.AutoDriveSecondGear() + } + + /** + * Stop driving (but do not cancel the server override state). + */ + final case class Stop() extends Configuration { + def Create : Setting = AutoDriveControls.AutoDriveStop() + } + + /** + * Cause the vehicle to turn a certain amount. + * @see `VehicleMessage.wheel_direction` + * @param angle the angle by which to turn the vehicle + * @param direction the wheel direction of the vehicle + */ + final case class TurnBy(angle : Float, direction : Int) extends Configuration { + def Create : Setting = AutoDriveControls.AutoDriveTurnBy(angle, direction) + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlBase.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlBase.scala index 49fdd3bf..284d0999 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlBase.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlBase.scala @@ -9,8 +9,8 @@ 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. + * Provide a common convention for the logging system's name. + * Additional functionality that recovers the `Zone` of the owned amenity `VehicleSpawnPad`. * @param pad a `VehicleSpawnPad` object */ abstract class VehicleSpawnControlBase(pad : VehicleSpawnPad) extends Actor { @@ -51,21 +51,19 @@ abstract class VehicleSpawnControlBase(pad : VehicleSpawnPad) extends Actor { /** * A common manner of utilizing the logging agent such that all messages have the same logging level. - * The default should be set to `trace`. + * The default should be `trace`-level comments. * No important messages should processed by this agent; only consume general vehicle spawn status. * @param msg the message */ def trace(msg : String) : Unit = log.trace(msg) - 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. + * Originally, it belongs to a default non-`Building` object that is owned by a default non-`Zone` object. + * Eventually, it will belong to an active `Building` object that will belong to an active `Zone` object. * With respect to `GetLogger(String)`, the active `Zone` object will be valid shortly after the object is registered, * but will still be separated from being owned by a valid `Building` object by a few validation checks. * @return the (current) `Zone` object */ - def Continent : Zone = Pad.Owner.asInstanceOf[Building].Zone + def Continent : Zone = pad.Owner.asInstanceOf[Building].Zone } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlConcealPlayer.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlConcealPlayer.scala index a7c3f59d..21baed97 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlConcealPlayer.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlConcealPlayer.scala @@ -38,11 +38,8 @@ class VehicleSpawnControlConcealPlayer(pad : VehicleSpawnPad) extends VehicleSpa context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder } - case VehicleSpawnControl.ProcessControl.Reminder => - context.parent ! VehicleSpawnControl.ProcessControl.Reminder - - case VehicleSpawnControl.ProcessControl.GetNewOrder => - context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder + case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) => + context.parent ! msg case _ => ; } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlDriverControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlDriverControl.scala new file mode 100644 index 00000000..98db2d77 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlDriverControl.scala @@ -0,0 +1,54 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.pad.process + +import akka.actor.{ActorRef, Props} +import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} + +/** + * An `Actor` that handles vehicle spawning orders for a `VehicleSpawnPad`. + * The basic `VehicleSpawnControl` is the root of a simple tree of "spawn control" objects that chain to each other. + * Each object performs on (or more than one related) actions upon the vehicle order that was submitted.
+ *
+ * A certain amount of time after the server has asserted control over a newly-spawned vehicle, + * control of that vehicle is given over to the driver. + * It has failure cases should the driver be in an incorrect state. + * @param pad the `VehicleSpawnPad` object being governed + */ +class VehicleSpawnControlDriverControl(pad : VehicleSpawnPad) extends VehicleSpawnControlBase(pad) { + def LogId = "-overrider" + + val finalClear = context.actorOf(Props(classOf[VehicleSpawnControlFinalClearance], pad), s"${context.parent.path.name}-final") + + def receive : Receive = { + case VehicleSpawnControl.Process.DriverVehicleControl(entry) => + val vehicle = entry.vehicle + if(pad.Railed) { + Continent.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad, Continent.Id) + } + if(vehicle.Health == 0) { + trace(s"vehicle was already destroyed; but, everything is fine") + } + if(entry.sendTo != ActorRef.noSender) { + val driver = entry.driver + entry.sendTo ! VehicleSpawnPad.ServerVehicleOverrideEnd(vehicle, pad) + if(driver.VehicleSeated.contains(vehicle.GUID)) { + trace(s"returning control of ${vehicle.Definition.Name} to ${driver.Name}") + } + else { + trace(s"${driver.Name} is not seated in ${vehicle.Definition.Name}; vehicle controls have been locked") + } + } + else { + trace("can not properly return control to driver") + } + finalClear ! VehicleSpawnControl.Process.FinalClearance(entry) + + case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) => + context.parent ! msg + + case msg @ VehicleSpawnControl.Process.FinalClearance(_) => + finalClear ! msg + + case _ => ; + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlFinalClearance.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlFinalClearance.scala index c4014a24..d748307f 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlFinalClearance.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlFinalClearance.scala @@ -23,14 +23,22 @@ class VehicleSpawnControlFinalClearance(pad : VehicleSpawnPad) extends VehicleSp def receive : Receive = { case VehicleSpawnControl.Process.FinalClearance(entry) => - if(Vector3.DistanceSquared(entry.vehicle.Position, pad.Position) > 100.0f) { //10m away from pad + context.parent ! VehicleSpawnControl.ProcessControl.Reminder + self ! VehicleSpawnControlFinalClearance.Test(entry) + + case VehicleSpawnControlFinalClearance.Test(entry) => + if(Vector3.DistanceSquared(entry.vehicle.Position.xy, pad.Position.xy) > 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)) + context.system.scheduler.scheduleOnce(2000 milliseconds, self, VehicleSpawnControlFinalClearance.Test(entry)) } case _ => ; } } + +object VehicleSpawnControlFinalClearance { + private final case class Test(entry : VehicleSpawnControl.Order) +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlGuided.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlGuided.scala new file mode 100644 index 00000000..9011082d --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlGuided.scala @@ -0,0 +1,126 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.pad.process + +import akka.actor.{ActorRef, Props} +import net.psforever.objects.Vehicle +import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration._ + +/** + * An `Actor` that handles vehicle spawning orders for a `VehicleSpawnPad`. + * The basic `VehicleSpawnControl` is the root of a simple tree of "spawn control" objects that chain to each other. + * Each object performs on (or more than one related) actions upon the vehicle order that was submitted.
+ *
+ * After the vehicle has been released from the spawn pad lifting platform, + * it enters into an auto-drive mode that has at least two stages. + * An undefined number of stages cane be included, however. + * This can lead the newly-spawned vehicle through a rough pre-defined path.
+ *
+ * Throughout this process, the conditions of `ServerVehicleOverrideMsg` are still in effect. + * @param pad the `VehicleSpawnPad` object being governed + */ +class VehicleSpawnControlGuided(pad : VehicleSpawnPad) extends VehicleSpawnControlBase(pad) { + def LogId = "-guide" + + val driverControl = context.actorOf(Props(classOf[VehicleSpawnControlDriverControl], pad), s"${context.parent.path.name}-driver") + + def receive : Receive = { + case VehicleSpawnControl.Process.StartGuided(entry) => + pad.Guide match { + case Nil => + trace("no guided path for this pad") + driverControl ! VehicleSpawnControl.Process.DriverVehicleControl(entry) + case path => + self ! VehicleSpawnControlGuided.InitialGuided(entry, path.map { _.Create }) + } + + case VehicleSpawnControlGuided.SelectNextGuided(entry, actions) => + actions match { + case Nil | _ :: Nil => + trace("custom vehicle path completed") + driverControl ! VehicleSpawnControl.Process.DriverVehicleControl(entry) + case _ :: xs => + self ! VehicleSpawnControlGuided.InitialGuided(entry, xs) + } + + case VehicleSpawnControlGuided.InitialGuided(entry, actions) => + val vehicle = entry.vehicle + if(entry.sendTo != ActorRef.noSender && vehicle.Health != 0 && entry.driver.VehicleSeated.contains(vehicle.GUID) && actions.head.Validate(vehicle)) { + trace(s"custom vehicle path plotted - ${actions.head.Type}") + entry.sendTo ! VehicleSpawnControlGuided.GuidedControl(actions.head.Type, vehicle, actions.head.Data) + self ! VehicleSpawnControlGuided.ContinueGuided(entry, actions) + } + else { + trace(s"projected ${vehicle.Definition.Name} path interruption; exit guided mode") + driverControl ! VehicleSpawnControl.Process.DriverVehicleControl(entry) + } + + case VehicleSpawnControlGuided.ValidateGuided(entry, actions) => + val vehicle = entry.vehicle + if(entry.sendTo != ActorRef.noSender && vehicle.Health != 0 && entry.driver.VehicleSeated.contains(vehicle.GUID) && actions.head.Validate(vehicle)) { + self ! VehicleSpawnControlGuided.ContinueGuided(entry, actions) + } + else { + trace(s"plotted ${vehicle.Definition.Name} path interruption; exit guided mode") + driverControl ! VehicleSpawnControl.Process.DriverVehicleControl(entry) + } + + case VehicleSpawnControlGuided.ContinueGuided(entry, actions) => + if(actions.head.CompletionTest(entry.vehicle)) { + trace("step completed") + self ! VehicleSpawnControlGuided.SelectNextGuided(entry, actions) + } + else { + context.system.scheduler.scheduleOnce(actions.head.Delay milliseconds, self, VehicleSpawnControlGuided.ValidateGuided(entry, actions)) + } + + case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) => + context.parent ! msg + + case msg @ VehicleSpawnControl.Process.FinalClearance(_) => + driverControl ! msg + + case _ => ; + } +} + +object VehicleSpawnControlGuided { + /** + * Select the first instruction from the list. + * @param entry the vehicle order + * @param actions the list of instructions related to this spawn pad + */ + private final case class InitialGuided(entry : VehicleSpawnControl.Order, actions : List[AutoDriveControls.Setting]) + /** + * Swap to the next instruction, if it exists. + * @param entry the vehicle order + * @param actions the list of instructions related to this spawn pad + */ + private final case class SelectNextGuided(entry : VehicleSpawnControl.Order, actions : List[AutoDriveControls.Setting]) + /** + * The validation test determines whether the vehicle, the driver, and any other important elements + * are still in a state where the current instruction can be accomplished. + * If the validation test passes, the current instruction can continue to run to completion. + * If the validation test fails, the remainder of the instructions are aborted. + * @param entry the vehicle order + * @param actions the list of instructions related to this spawn pad + */ + private final case class ValidateGuided(entry : VehicleSpawnControl.Order, actions : List[AutoDriveControls.Setting]) + /** + * If the previous validation test passes, the current instruction can continue to run to completion. + * Once completed, the next instruction can be selected. + * @param entry the vehicle order + * @param actions the list of instructions related to this spawn pad + */ + private final case class ContinueGuided(entry : VehicleSpawnControl.Order, actions : List[AutoDriveControls.Setting]) + + /** + * A message that explains the current instruction in the guided mode to another agency. + * @param command the nature of the action being performed + * @param vehicle the vehicle being controlled + * @param data optional data used to process the instruction + */ + final case class GuidedControl(command : AutoDriveControls.State.Value, vehicle : Vehicle, data : Option[Any]) +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlLoadVehicle.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlLoadVehicle.scala index e278f51f..2510387d 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlLoadVehicle.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlLoadVehicle.scala @@ -30,7 +30,10 @@ class VehicleSpawnControlLoadVehicle(pad : VehicleSpawnPad) extends VehicleSpawn val vehicle = entry.vehicle if(entry.driver.Continent == Continent.Id) { trace(s"loading the ${vehicle.Definition.Name}") - vehicle.Position = vehicle.Position - Vector3(0, 0, if(GlobalDefinitions.isFlightVehicle(vehicle.Definition)) 9 else 5) + if(pad.Railed) { + //load the vehicle in the spawn pad trench, underground, initially + vehicle.Position = vehicle.Position - Vector3(0, 0, if(GlobalDefinitions.isFlightVehicle(vehicle.Definition)) 9 else 5) + } Continent.VehicleEvents ! VehicleSpawnPad.LoadVehicle(vehicle, Continent) context.system.scheduler.scheduleOnce(100 milliseconds, railJack, VehicleSpawnControl.Process.RailJackAction(entry)) } @@ -40,11 +43,8 @@ class VehicleSpawnControlLoadVehicle(pad : VehicleSpawnPad) extends VehicleSpawn context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder } - case VehicleSpawnControl.ProcessControl.Reminder => - context.parent ! VehicleSpawnControl.ProcessControl.Reminder - - case VehicleSpawnControl.ProcessControl.GetNewOrder => - context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder + case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) => + context.parent ! msg case _ => ; } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlRailJack.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlRailJack.scala index 7fd19eee..bb3bf5bd 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlRailJack.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlRailJack.scala @@ -26,25 +26,17 @@ class VehicleSpawnControlRailJack(pad : VehicleSpawnPad) extends VehicleSpawnCon def receive : Receive = { case VehicleSpawnControl.Process.RailJackAction(entry) => - if(entry.vehicle.Health == 0) { - trace("vehicle was already destroyed; clean it up") - VehicleSpawnControl.DisposeSpawnedVehicle(entry, Continent) - context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder + if(pad.Railed) { + trace(s"attaching vehicle to railed platform") + Continent.VehicleEvents ! VehicleSpawnPad.AttachToRails(entry.vehicle, pad, Continent.Id) } else { - 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, seatDriver, VehicleSpawnControl.Process.SeatDriver(entry)) + trace(s"railed platform skipped; vehicle positioned in pad trench temporarily") } + context.system.scheduler.scheduleOnce(10 milliseconds, seatDriver, VehicleSpawnControl.Process.SeatDriver(entry)) - case VehicleSpawnControl.ProcessControl.GetNewOrder => - context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder + case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) => + context.parent ! msg case _ => ; } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala index 5c6eb8ef..6c6896c4 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala @@ -14,7 +14,7 @@ import scala.concurrent.duration._ * Each object performs on (or more than one related) actions upon the vehicle order that was submitted.
*
* This object forces the prospective driver to take the driver seat. - * Three separate but sequentially significant steps occur within the scope of this object. + * Multiple separate but sequentially significant steps occur within the scope of this object. * First, this step waits for the vehicle to be completely ready to accept the driver. * Second, this step triggers the player to actually be moved into the driver seat. * Finally, this step waits until the driver is properly in the driver seat. @@ -29,84 +29,71 @@ class VehicleSpawnControlSeatDriver(pad : VehicleSpawnPad) extends VehicleSpawnC 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, self, VehicleSpawnControl.Process.SeatDriver(entry)) + self ! VehicleSpawnControlSeatDriver.AwaitVehicleReadiness(entry) + + case VehicleSpawnControlSeatDriver.AwaitVehicleReadiness(entry) => + if(entry.vehicle.Actor == ActorRef.noSender) { //wait for a necessary vehicle component to be loaded + context.system.scheduler.scheduleOnce(50 milliseconds, self, VehicleSpawnControlSeatDriver.AwaitVehicleReadiness(entry)) } else { - 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 { - trace("driver lost; vehicle stranded on pad") - context.system.scheduler.scheduleOnce(1000 milliseconds, vehicleOverride, VehicleSpawnControl.Process.ServerVehicleOverride(entry)) - } + trace("vehicle ready") + self ! VehicleSpawnControlSeatDriver.BeginDriverInSeat(entry) } - case VehicleSpawnControl.Process.AwaitDriverInSeat(entry) => + case VehicleSpawnControlSeatDriver.BeginDriverInSeat(entry) => 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 + if(entry.sendTo != ActorRef.noSender && entry.vehicle.Health > 0 && driver.isAlive && driver.Continent == Continent.Id && driver.VehicleSeated.isEmpty) { + trace("driver to be made seated in vehicle") + entry.sendTo ! VehicleSpawnPad.StartPlayerSeatedInVehicle(entry.vehicle, pad) + entry.vehicle.Actor.tell(Mountable.TryMount(driver, 0), entry.sendTo) //entry.sendTo should handle replies to TryMount + context.system.scheduler.scheduleOnce(1000 milliseconds, self, VehicleSpawnControlSeatDriver.AwaitDriverInSeat(entry)) } - else if(entry.sendTo == ActorRef.noSender || driver.Continent != Continent.Id) { + else { + trace("driver lost; vehicle stranded on pad") + context.system.scheduler.scheduleOnce(1000 milliseconds, vehicleOverride, VehicleSpawnControl.Process.ServerVehicleOverride(entry)) + } + + case VehicleSpawnControlSeatDriver.AwaitDriverInSeat(entry) => + val driver = entry.driver + if(entry.sendTo == ActorRef.noSender || driver.Continent != Continent.Id) { trace("driver lost, but operations can continue") vehicleOverride ! VehicleSpawnControl.Process.ServerVehicleOverride(entry) } else if(driver.isAlive && driver.VehicleSeated.isEmpty) { - context.system.scheduler.scheduleOnce(100 milliseconds, self, VehicleSpawnControl.Process.AwaitDriverInSeat(entry)) + context.system.scheduler.scheduleOnce(100 milliseconds, self, VehicleSpawnControlSeatDriver.AwaitDriverInSeat(entry)) } else { trace(s"driver is sitting down") val time = if(pad.Railed) 1000 else VehicleSpawnControlSeatDriver.RaillessSeatAnimationTimes(entry.vehicle.Definition.Name) - context.system.scheduler.scheduleOnce(time milliseconds, self, VehicleSpawnControl.Process.DriverInSeat(entry)) + context.system.scheduler.scheduleOnce(time milliseconds, self, VehicleSpawnControlSeatDriver.DriverInSeat(entry)) } - case VehicleSpawnControl.Process.DriverInSeat(entry) => - if(entry.vehicle.Health == 0) { - 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 || entry.driver.Continent != Continent.Id) { + case VehicleSpawnControlSeatDriver.DriverInSeat(entry) => + if(entry.sendTo != ActorRef.noSender || entry.driver.Continent != Continent.Id) { trace(s"driver ${entry.driver.Name} has taken the wheel") entry.sendTo ! VehicleSpawnPad.PlayerSeatedInVehicle(entry.vehicle, pad) - 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, vehicleOverride, VehicleSpawnControl.Process.ServerVehicleOverride(entry)) } + context.system.scheduler.scheduleOnce(800 milliseconds, vehicleOverride, VehicleSpawnControl.Process.ServerVehicleOverride(entry)) - case VehicleSpawnControl.ProcessControl.Reminder => - context.parent ! VehicleSpawnControl.ProcessControl.Reminder - - case VehicleSpawnControl.ProcessControl.GetNewOrder => - context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder + case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) => + context.parent ! msg case _ => ; } } object VehicleSpawnControlSeatDriver { + final case class AwaitVehicleReadiness(entry : VehicleSpawnControl.Order) + + final case class BeginDriverInSeat(entry : VehicleSpawnControl.Order) + + final case class AwaitDriverInSeat(entry : VehicleSpawnControl.Order) + + final case class DriverInSeat(entry : VehicleSpawnControl.Order) + /** * If the spawn pad associated with this `Actor` chain is not `Railed` - * not guaranteed to have the correct ingame globally unique id of the spawn pad - diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlServerVehicleOverride.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlServerVehicleOverride.scala index 7b2c48f4..41194d46 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlServerVehicleOverride.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlServerVehicleOverride.scala @@ -21,59 +21,39 @@ import scala.concurrent.duration._ class VehicleSpawnControlServerVehicleOverride(pad : VehicleSpawnPad) extends VehicleSpawnControlBase(pad) { def LogId = "-overrider" - val finalClear = context.actorOf(Props(classOf[VehicleSpawnControlFinalClearance], pad), s"${context.parent.path.name}-final") + val vehicleGuide = context.actorOf(Props(classOf[VehicleSpawnControlGuided], pad), s"${context.parent.path.name}-guide") def receive : Receive = { case VehicleSpawnControl.Process.ServerVehicleOverride(entry) => val vehicle = entry.vehicle - Continent.VehicleEvents ! VehicleSpawnPad.DetachFromRails(vehicle, pad, Continent.Id) + val pad_railed = pad.Railed + if(pad_railed) { + Continent.VehicleEvents ! VehicleSpawnPad.DetachFromRails(vehicle, pad, Continent.Id) + } if(vehicle.Health == 0) { trace(s"vehicle was already destroyed; but, everything is fine") - if(pad.Railed) { + if(pad_railed) { Continent.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad, Continent.Id) } - finalClear ! VehicleSpawnControl.Process.FinalClearance(entry) + vehicleGuide ! VehicleSpawnControl.Process.FinalClearance(entry) } else if(entry.sendTo != ActorRef.noSender && entry.driver.isAlive && entry.driver.Continent == Continent.Id && entry.driver.VehicleSeated.contains(vehicle.GUID)) { trace(s"telling ${entry.driver.Name} that the server is assuming control of the ${vehicle.Definition.Name}") entry.sendTo ! VehicleSpawnPad.ServerVehicleOverrideStart(vehicle, pad) - context.system.scheduler.scheduleOnce(3000 milliseconds, self, VehicleSpawnControl.Process.DriverVehicleControl(entry)) + context.system.scheduler.scheduleOnce(3000 milliseconds, vehicleGuide, VehicleSpawnControl.Process.StartGuided(entry)) } else { - if(pad.Railed) { + if(pad_railed) { Continent.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad, Continent.Id) } - finalClear ! VehicleSpawnControl.Process.FinalClearance(entry) + vehicleGuide ! 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") - } - if(entry.sendTo != ActorRef.noSender) { - val driver = entry.driver - entry.sendTo ! VehicleSpawnPad.ServerVehicleOverrideEnd(vehicle, pad) - if(driver.VehicleSeated.contains(vehicle.GUID)) { - trace(s"returning control of ${vehicle.Definition.Name} to ${driver.Name}") - } - else { - trace(s"${driver.Name} is not seated in ${vehicle.Definition.Name}; can not properly return control to driver") - } - } - else { - trace("can not properly return control to driver") - } - finalClear ! VehicleSpawnControl.Process.FinalClearance(entry) + case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) => + context.parent ! msg case msg @ VehicleSpawnControl.Process.FinalClearance(_) => - finalClear ! msg - - case VehicleSpawnControl.ProcessControl.GetNewOrder => - context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder + vehicleGuide ! msg case _ => ; } diff --git a/common/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala b/common/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala index c0b41132..266396a8 100644 --- a/common/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala +++ b/common/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala @@ -33,7 +33,8 @@ import scodec.codecs._ * @param lock_accelerator driver has no control over vehicle acceleration * @param lock_wheel driver has no control over vehicle turning * @param reverse move in reverse - * @param unk4 na + * @param unk4 na; + * something to do with vehicle bailable speed * @param lock_vthrust pilot has no control over vertical thrust; * asserts a constant positive vertical thrust; * the only valid setting appears to be 1 @@ -58,26 +59,6 @@ final case class ServerVehicleOverrideMsg(lock_accelerator : Boolean, } object ServerVehicleOverrideMsg extends Marshallable[ServerVehicleOverrideMsg] { - /** - * Common lock control packet format. - * Strafing is always treated as unlocked. - * @param flight vehicle flies and should move vertically - * @param speed "something like speed" - * @return a `ServerVehicleOverrideMsg` packet - */ - def Lock(flight : Int, speed : Int) : ServerVehicleOverrideMsg = { - ServerVehicleOverrideMsg(true, true, false, false, flight, 0, speed, Some(0)) - } - - /** - * Common cancellable auto-drive packet format. - * @param speed "something like speed" - * @return a `ServerVehicleOverrideMsg` packet - */ - def Auto(speed : Int) : ServerVehicleOverrideMsg = { - ServerVehicleOverrideMsg(false, false, false, true, 0, 0, speed, None) - } - implicit val codec: Codec[ServerVehicleOverrideMsg] = ( ("lock_accelerator" | bool) :: (("lock_wheel" | bool) >>:~ { test => diff --git a/common/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala b/common/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala index aa71caa2..3dbc037b 100644 --- a/common/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala @@ -19,8 +19,8 @@ import scodec.codecs._ * @param unk4 na * @param wheel_direction for ground vehicles, whether the wheels are being turned; * 15 for straight; - * 0 for hard left; - * 30 for hard right + * 0 for hard right; + * 30 for hard left * @param unk5 na * @param unk6 na * @see `PlacementData` diff --git a/common/src/test/scala/game/ServerVehicleOverrideMsgTest.scala b/common/src/test/scala/game/ServerVehicleOverrideMsgTest.scala index 44b243ed..91a10784 100644 --- a/common/src/test/scala/game/ServerVehicleOverrideMsgTest.scala +++ b/common/src/test/scala/game/ServerVehicleOverrideMsgTest.scala @@ -56,18 +56,4 @@ class ServerVehicleOverrideMsgTest extends Specification { pkt mustEqual string2 } - - "encode (3)" in { - val msg = ServerVehicleOverrideMsg.Lock(0, 12) - val pkt = PacketCoding.EncodePacket(msg).require.toByteVector - - pkt mustEqual string1 - } - - "encode (4)" in { - val msg = ServerVehicleOverrideMsg.Auto(5) - val pkt = PacketCoding.EncodePacket(msg).require.toByteVector - - pkt mustEqual string2 - } } diff --git a/common/src/test/scala/objects/AutoDriveControlsTest.scala b/common/src/test/scala/objects/AutoDriveControlsTest.scala new file mode 100644 index 00000000..e26c7d9b --- /dev/null +++ b/common/src/test/scala/objects/AutoDriveControlsTest.scala @@ -0,0 +1,498 @@ +// Copyright (c) 2017 PSForever +package objects + +import akka.actor.Props +import akka.testkit.TestProbe +import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} +import net.psforever.objects.{Avatar, GlobalDefinitions, Player, Vehicle} +import net.psforever.objects.serverobject.pad.process.{AutoDriveControls, VehicleSpawnControlGuided} +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3} +import org.specs2.mutable.Specification + +import scala.concurrent.duration._ + +class AutoDriveControlsTest extends Specification { + "CancelEntry" should { + val vehicle = Vehicle(GlobalDefinitions.fury) + def exampleTest(vehicle : Vehicle) : Boolean = { vehicle.Position == Vector3(1,1,1) } + + "create" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.CancelEarly(exampleTest) + val setting : AutoDriveControls.Setting = config.Create + setting.Type mustEqual AutoDriveControls.State.Cancel + setting.Data mustEqual None + setting.Delay mustEqual 200L + } + + "validate" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.CancelEarly(exampleTest) + val setting : AutoDriveControls.Setting = config.Create + + vehicle.Position mustEqual Vector3.Zero + setting.Validate(vehicle) mustEqual false + vehicle.Position = Vector3(1,1,1) + setting.Validate(vehicle) mustEqual true + } + + "completion" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.CancelEarly(exampleTest) + val setting : AutoDriveControls.Setting = config.Create + setting.CompletionTest(vehicle) mustEqual true //always true + } + } + + "Climb" should { + val vehicle = Vehicle(GlobalDefinitions.mosquito) + + "create" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.Climb(10.5f) + val setting : AutoDriveControls.Setting = config.Create + setting.Type mustEqual AutoDriveControls.State.Climb + setting.Data mustEqual Some(10.5f) + setting.Delay mustEqual 200L + } + + "validate" in { + val vehicle_fury = Vehicle(GlobalDefinitions.fury) + val config : AutoDriveControls.Configuration = AutoDriveControls.Climb(10.5f) + val setting : AutoDriveControls.Setting = config.Create + + setting.Validate(vehicle) mustEqual true //mosquito is a flying vehicle + setting.Validate(vehicle_fury) mustEqual false + } + + "completion" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.Climb(10.5f) + val setting : AutoDriveControls.Setting = config.Create + + vehicle.Position mustEqual Vector3.Zero + setting.CompletionTest(vehicle) mustEqual false + vehicle.Position = Vector3(0,0,10.5f) + setting.CompletionTest(vehicle) mustEqual true + } + } + + "Distance" should { + val vehicle = Vehicle(GlobalDefinitions.fury) + + "create" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.Distance(Vector3.Zero, 10.5f) + val setting : AutoDriveControls.Setting = config.Create + setting.Type mustEqual AutoDriveControls.State.Wait + setting.Data mustEqual None + setting.Delay mustEqual 200L + } + "validate" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.Distance(Vector3.Zero, 10.5f) + val setting : AutoDriveControls.Setting = config.Create + + vehicle.Velocity mustEqual None + setting.Validate(vehicle) mustEqual false + vehicle.Velocity = Vector3.Zero + setting.Validate(vehicle) mustEqual false + vehicle.Velocity = Vector3(1,0,0) + setting.Validate(vehicle) mustEqual true + } + + "completion" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.Distance(Vector3.Zero, 10.5f) + val setting : AutoDriveControls.Setting = config.Create + + vehicle.Position = Vector3(0,0,0) + setting.CompletionTest(vehicle) mustEqual false + vehicle.Position = Vector3(10.5f,0,0) + setting.CompletionTest(vehicle) mustEqual false + vehicle.Position = Vector3(11,0,0) + setting.CompletionTest(vehicle) mustEqual true + vehicle.Position = Vector3(0,11,0) + setting.CompletionTest(vehicle) mustEqual true + vehicle.Position = Vector3(0,0,11) + setting.CompletionTest(vehicle) mustEqual false + vehicle.Position = Vector3(7.5f,7.5f,0) + setting.CompletionTest(vehicle) mustEqual true + } + } + + "DistanceFromHere" should { + val vehicle = Vehicle(GlobalDefinitions.fury) + + "create" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.DistanceFromHere(10.5f) + val setting : AutoDriveControls.Setting = config.Create + setting.Type mustEqual AutoDriveControls.State.Wait + setting.Data mustEqual None + setting.Delay mustEqual 200L + } + + "validate" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.DistanceFromHere(10.5f) + val setting : AutoDriveControls.Setting = config.Create + + vehicle.Velocity mustEqual None + setting.Validate(vehicle) mustEqual false + vehicle.Velocity = Vector3.Zero + setting.Validate(vehicle) mustEqual false + vehicle.Velocity = Vector3(1,0,0) + setting.Validate(vehicle) mustEqual true + } + + "completion" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.DistanceFromHere(10.5f) + val setting : AutoDriveControls.Setting = config.Create + + vehicle.Position = Vector3(0,0,0) + setting.CompletionTest(vehicle) mustEqual false + vehicle.Position = Vector3(10.5f,0,0) + setting.CompletionTest(vehicle) mustEqual false + vehicle.Position = Vector3(11,0,0) + setting.CompletionTest(vehicle) mustEqual true + vehicle.Position = Vector3(0,11,0) + setting.CompletionTest(vehicle) mustEqual true + vehicle.Position = Vector3(0,0,11) + setting.CompletionTest(vehicle) mustEqual false + vehicle.Position = Vector3(7.5f,7.5f,0) + setting.CompletionTest(vehicle) mustEqual true + } + } + + "Drive" should { + val vehicle = Vehicle(GlobalDefinitions.fury) + + "create" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.Drive(3) + val setting : AutoDriveControls.Setting = config.Create + setting.Type mustEqual AutoDriveControls.State.Drive + setting.Data mustEqual Some(3) + setting.Delay mustEqual 200L + } + + "validate" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.Drive(3) + val setting : AutoDriveControls.Setting = config.Create + setting.Validate(vehicle) mustEqual true + } + + "completion" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.Drive(3) + val setting : AutoDriveControls.Setting = config.Create + vehicle.Velocity mustEqual None + setting.CompletionTest(vehicle) mustEqual false + + vehicle.Velocity = Vector3.Zero + vehicle.Velocity mustEqual Some(Vector3.Zero) + setting.CompletionTest(vehicle) mustEqual false + + vehicle.Velocity = Vector3(1,0,0) + vehicle.Velocity mustEqual Some(Vector3(1,0,0)) + setting.CompletionTest(vehicle) mustEqual true + + } + } + + "FirstGear" should { + val veh_def = GlobalDefinitions.mediumtransport + val vehicle = Vehicle(veh_def) + + "create" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.FirstGear() + val setting : AutoDriveControls.Setting = config.Create + setting.Type mustEqual AutoDriveControls.State.Drive + setting.Data mustEqual Some(0) + setting.Delay mustEqual 200L + } + + "validate" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.FirstGear() + val setting : AutoDriveControls.Setting = config.Create + setting.Validate(vehicle) mustEqual true //always true + } + + "completion" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.FirstGear() + val setting : AutoDriveControls.Setting = config.Create + + vehicle.Velocity mustEqual None + setting.CompletionTest(vehicle) mustEqual false + vehicle.Velocity = Vector3.Zero + setting.CompletionTest(vehicle) mustEqual false + vehicle.Velocity = Vector3(1,0,0) + setting.CompletionTest(vehicle) mustEqual true + } + + "data" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.FirstGear() + val setting : AutoDriveControls.Setting = config.Create + + setting.Data mustEqual Some(0) + setting.Validate(vehicle) + setting.Data mustEqual Some(veh_def.AutoPilotSpeed1) + } + } + + "ForTime" should { + val veh_def = GlobalDefinitions.mediumtransport + val vehicle = Vehicle(veh_def) + + "create" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.ForTime(1200L) + val setting : AutoDriveControls.Setting = config.Create + setting.Type mustEqual AutoDriveControls.State.Wait + setting.Data mustEqual None + setting.Delay mustEqual 200L + } + + "validate" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.ForTime(1200L) + val setting : AutoDriveControls.Setting = config.Create + setting.Validate(vehicle) mustEqual true //always true + } + + "completion" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.ForTime(1200L) + val setting : AutoDriveControls.Setting = config.Create + setting.CompletionTest(vehicle) mustEqual false + + Thread.sleep(1100) + setting.CompletionTest(vehicle) mustEqual false + + Thread.sleep(200) + setting.CompletionTest(vehicle) mustEqual true + } + } + + "SecondGear" should { + val veh_def = GlobalDefinitions.mediumtransport + val vehicle = Vehicle(veh_def) + + "create" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.SecondGear() + val setting : AutoDriveControls.Setting = config.Create + setting.Type mustEqual AutoDriveControls.State.Drive + setting.Data mustEqual Some(0) + setting.Delay mustEqual 200L + } + + "validate" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.SecondGear() + val setting : AutoDriveControls.Setting = config.Create + setting.Validate(vehicle) mustEqual true //always true + } + + "completion" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.SecondGear() + val setting : AutoDriveControls.Setting = config.Create + + vehicle.Velocity mustEqual None + setting.CompletionTest(vehicle) mustEqual false + vehicle.Velocity = Vector3.Zero + setting.CompletionTest(vehicle) mustEqual false + vehicle.Velocity = Vector3(1,0,0) + setting.CompletionTest(vehicle) mustEqual true + } + + "data" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.SecondGear() + val setting : AutoDriveControls.Setting = config.Create + + setting.Data mustEqual Some(0) + setting.Validate(vehicle) + setting.Data mustEqual Some(veh_def.AutoPilotSpeed2) + } + } + + "Stop" should { + val vehicle = Vehicle(GlobalDefinitions.mediumtransport) + + "create" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.Stop() + val setting : AutoDriveControls.Setting = config.Create + setting.Type mustEqual AutoDriveControls.State.Stop + setting.Data mustEqual None + setting.Delay mustEqual 200L + } + + "validate" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.Stop() + val setting : AutoDriveControls.Setting = config.Create + setting.Validate(vehicle) mustEqual true //always true + } + + "completion" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.Stop() + val setting : AutoDriveControls.Setting = config.Create + setting.CompletionTest(vehicle) mustEqual true //always true + } + } + + "TurnBy" should { + "create" in { + val config : AutoDriveControls.Configuration = AutoDriveControls.TurnBy(35.5f, 23) + val setting : AutoDriveControls.Setting = config.Create + setting.Type mustEqual AutoDriveControls.State.Turn + setting.Data mustEqual Some(23) + setting.Delay mustEqual 100L + } + + "validate (velocity)" in { + val vehicle = Vehicle(GlobalDefinitions.mediumtransport) + val config : AutoDriveControls.Configuration = AutoDriveControls.TurnBy(35.5f, 23) + val setting : AutoDriveControls.Setting = config.Create + + vehicle.Velocity mustEqual None + setting.Validate(vehicle) mustEqual false + vehicle.Velocity = Vector3(1,1,1) + setting.Validate(vehicle) mustEqual true + } + + "validate (wheel direction = 15)" in { + val vehicle = Vehicle(GlobalDefinitions.mediumtransport) + val config : AutoDriveControls.Configuration = AutoDriveControls.TurnBy(35.5f, 15) + val setting : AutoDriveControls.Setting = config.Create + + vehicle.Velocity = Vector3(1,1,1) + setting.Validate(vehicle) mustEqual false + } + + "completion (passing 35.5-up)" in { + val vehicle = Vehicle(GlobalDefinitions.mediumtransport) + val config : AutoDriveControls.Configuration = AutoDriveControls.TurnBy(35.5f, 25) + val setting : AutoDriveControls.Setting = config.Create + + vehicle.Orientation mustEqual Vector3.Zero + setting.CompletionTest(vehicle) mustEqual false + vehicle.Orientation = Vector3(0,0,34.5f) + setting.CompletionTest(vehicle) mustEqual false + vehicle.Orientation = Vector3(0,0,35f) + setting.CompletionTest(vehicle) mustEqual false + vehicle.Orientation = Vector3(0,0,36.0f) + setting.CompletionTest(vehicle) mustEqual true + } + + "completion (passing 35.5 down)" in { + val vehicle = Vehicle(GlobalDefinitions.mediumtransport) + val config : AutoDriveControls.Configuration = AutoDriveControls.TurnBy(-35.5f, 25) + val setting : AutoDriveControls.Setting = config.Create + + vehicle.Orientation = Vector3(0,0,40f) + setting.CompletionTest(vehicle) mustEqual false + vehicle.Orientation = Vector3(0,0,5f) + setting.CompletionTest(vehicle) mustEqual false + vehicle.Orientation = Vector3(0,0,4f) + setting.CompletionTest(vehicle) mustEqual true + } + } +} + +class GuidedControlTest1 extends ActorTest { + "VehicleSpawnControlGuided" should { + "unguided" in { + val vehicle = Vehicle(GlobalDefinitions.mediumtransport) + vehicle.GUID = PlanetSideGUID(1) + val driver = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0,0)) + driver.VehicleSeated = vehicle.GUID + val sendTo = TestProbe() + val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref) + val pad = VehicleSpawnPad(GlobalDefinitions.spawn_pad) + pad.GUID = PlanetSideGUID(1) + pad.Railed = false //suppress certain events + val guided = system.actorOf(Props(classOf[VehicleSpawnControlGuided], pad), "pad") + + guided ! VehicleSpawnControl.Process.StartGuided(order) + val msg = sendTo.receiveOne(100 milliseconds) + assert(msg.isInstanceOf[VehicleSpawnPad.ServerVehicleOverrideEnd]) + } + } +} + +class GuidedControlTest2 extends ActorTest { + "VehicleSpawnControlGuided" should { + "guided (one)" in { + val vehicle = Vehicle(GlobalDefinitions.mediumtransport) + vehicle.GUID = PlanetSideGUID(1) + vehicle.Velocity = Vector3(1,1,1) + val driver = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0,0)) + driver.VehicleSeated = vehicle.GUID + val sendTo = TestProbe() + val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref) + val pad = VehicleSpawnPad(GlobalDefinitions.spawn_pad) + pad.Railed = false //suppress certain events + val guided = system.actorOf(Props(classOf[VehicleSpawnControlGuided], pad), "pad") + + pad.Guide = List(AutoDriveControls.FirstGear()) + guided ! VehicleSpawnControl.Process.StartGuided(order) + val msg1 = sendTo.receiveOne(100 milliseconds) + assert(msg1.isInstanceOf[VehicleSpawnControlGuided.GuidedControl]) + assert(msg1.asInstanceOf[VehicleSpawnControlGuided.GuidedControl].command == AutoDriveControls.State.Drive) + val msg2 = sendTo.receiveOne(200 milliseconds) + assert(msg2.isInstanceOf[VehicleSpawnPad.ServerVehicleOverrideEnd]) + } + } +} + +class GuidedControlTest3 extends ActorTest { + "VehicleSpawnControlGuided" should { + "guided (three)" in { + val vehicle = Vehicle(GlobalDefinitions.mediumtransport) + vehicle.GUID = PlanetSideGUID(1) + vehicle.Velocity = Vector3(1,1,1) + val driver = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0,0)) + driver.VehicleSeated = vehicle.GUID + val sendTo = TestProbe() + val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref) + val pad = VehicleSpawnPad(GlobalDefinitions.spawn_pad) + pad.Railed = false //suppress certain events + val guided = system.actorOf(Props(classOf[VehicleSpawnControlGuided], pad), "pad") + + pad.Guide = List( + AutoDriveControls.FirstGear(), + AutoDriveControls.ForTime(1000L), + AutoDriveControls.SecondGear() + ) + guided ! VehicleSpawnControl.Process.StartGuided(order) + val msg1 = sendTo.receiveOne(100 milliseconds) + assert(msg1.isInstanceOf[VehicleSpawnControlGuided.GuidedControl]) + assert(msg1.asInstanceOf[VehicleSpawnControlGuided.GuidedControl].command == AutoDriveControls.State.Drive) + val msg2 = sendTo.receiveOne(100 milliseconds) + assert(msg2.isInstanceOf[VehicleSpawnControlGuided.GuidedControl]) + assert(msg2.asInstanceOf[VehicleSpawnControlGuided.GuidedControl].command == AutoDriveControls.State.Wait) + sendTo.expectNoMsg(1000 milliseconds) + val msg3 = sendTo.receiveOne(100 milliseconds) + assert(msg3.isInstanceOf[VehicleSpawnControlGuided.GuidedControl]) + assert(msg3.asInstanceOf[VehicleSpawnControlGuided.GuidedControl].command == AutoDriveControls.State.Drive) + val msg4 = sendTo.receiveOne(200 milliseconds) + assert(msg4.isInstanceOf[VehicleSpawnPad.ServerVehicleOverrideEnd]) + } + } +} + +class GuidedControlTest4 extends ActorTest { + "VehicleSpawnControlGuided" should { + "fail validation test" in { + def validationFailure(vehicle : Vehicle) : Boolean = false + + val vehicle = Vehicle(GlobalDefinitions.mediumtransport) + vehicle.GUID = PlanetSideGUID(1) + vehicle.Velocity = Vector3(1,1,1) + val driver = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0,0)) + driver.VehicleSeated = vehicle.GUID + val sendTo = TestProbe() + val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref) + val pad = VehicleSpawnPad(GlobalDefinitions.spawn_pad) + pad.Railed = false //suppress certain events + val guided = system.actorOf(Props(classOf[VehicleSpawnControlGuided], pad), "pad") + + pad.Guide = List( + AutoDriveControls.FirstGear(), + AutoDriveControls.CancelEarly(validationFailure), + AutoDriveControls.SecondGear() + ) + guided ! VehicleSpawnControl.Process.StartGuided(order) + val msg1 = sendTo.receiveOne(100 milliseconds) + assert(msg1.isInstanceOf[VehicleSpawnControlGuided.GuidedControl]) + assert(msg1.asInstanceOf[VehicleSpawnControlGuided.GuidedControl].command == AutoDriveControls.State.Drive) + val msg2 = sendTo.receiveOne(200 milliseconds) + assert(msg2.isInstanceOf[VehicleSpawnPad.ServerVehicleOverrideEnd]) + } + } +} diff --git a/common/src/test/scala/objects/VehicleSpawnPadTest.scala b/common/src/test/scala/objects/VehicleSpawnPadTest.scala index 9ee32ea6..2c9e1ba7 100644 --- a/common/src/test/scala/objects/VehicleSpawnPadTest.scala +++ b/common/src/test/scala/objects/VehicleSpawnPadTest.scala @@ -85,7 +85,7 @@ class VehicleSpawnControl2aTest extends ActorTest() { val probe1Msg3 = probe1.receiveOne(3 seconds) assert(probe1Msg3.isInstanceOf[VehicleSpawnPad.PlayerSeatedInVehicle]) - val probe3Msg4 = probe3.receiveOne(200 milliseconds) + val probe3Msg4 = probe3.receiveOne(1 seconds) assert(probe3Msg4.isInstanceOf[VehicleSpawnPad.DetachFromRails]) val probe1Msg4 = probe1.receiveOne(1 seconds) @@ -93,20 +93,20 @@ class VehicleSpawnControl2aTest extends ActorTest() { val probe1Msg5 = probe1.receiveOne(4 seconds) assert(probe1Msg5.isInstanceOf[VehicleSpawnPad.ServerVehicleOverrideEnd]) - val probe1Msg6 = probe1.receiveOne(10 seconds) + val probe1Msg6 = probe1.receiveOne(11 seconds) assert(probe1Msg6.isInstanceOf[VehicleSpawnPad.PeriodicReminder]) assert(probe1Msg6.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked) val probe2Msg2 = probe2.receiveOne(100 milliseconds) assert(probe2Msg2.isInstanceOf[VehicleSpawnPad.PeriodicReminder]) assert(probe2Msg2.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked) - //if we move the vehicle more than 10m away from the pad, we should receive a ResetSpawnPad, and a second ConcealPlayer message + //if we move the vehicle more than 25m away from the pad, we should receive a ResetSpawnPad, and a second ConcealPlayer message //that means that the first order has cleared and the spawn pad is now working on the second order successfully - vehicle.Position = Vector3(0,0,11) + vehicle.Position = Vector3(11,0,0) player.VehicleSeated = None //since shared between orders, is necessary val probe3Msg5 = probe3.receiveOne(4 seconds) assert(probe3Msg5.isInstanceOf[VehicleSpawnPad.ResetSpawnPad]) - val probe3Msg6 = probe3.receiveOne(4 seconds) + val probe3Msg6 = probe3.receiveOne(5 seconds) assert(probe3Msg6.isInstanceOf[VehicleSpawnPad.ConcealPlayer]) } } @@ -147,15 +147,12 @@ class VehicleSpawnControl2bTest extends ActorTest() { 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) + val probe1Msg6 = probe1.receiveOne(11 seconds) assert(probe1Msg6.isInstanceOf[VehicleSpawnPad.PeriodicReminder]) assert(probe1Msg6.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked) val probe2Msg2 = probe2.receiveOne(100 milliseconds) @@ -164,7 +161,7 @@ class VehicleSpawnControl2bTest extends ActorTest() { //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) + vehicle.Position = Vector3(11,0,0) player.VehicleSeated = None //since shared between orders, is necessary val probe3Msg6 = probe3.receiveOne(4 seconds) assert(probe3Msg6.isInstanceOf[VehicleSpawnPad.ConcealPlayer]) @@ -215,35 +212,35 @@ class VehicleSpawnControl4Test extends ActorTest() { } } -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 +//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 +// } +// } +//} - 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() { +class VehicleSpawnControl5Test extends ActorTest() { "VehicleSpawnControl" should { "player dies right after vehicle partially loads; the vehicle spawns and blocks the pad" in { val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR) @@ -266,91 +263,14 @@ class VehicleSpawnControl5bTest extends ActorTest() { val probe3Msg4 = probe3.receiveOne(3 seconds) assert(probe3Msg4.isInstanceOf[VehicleSpawnPad.DetachFromRails]) - val probe1Msg = probe1.receiveOne(10 seconds) + val probe1Msg = probe1.receiveOne(12 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() { +class VehicleSpawnControl6Test extends ActorTest() { "VehicleSpawnControl" should { "the player can not sit in vehicle; vehicle spawns and blocks the pad" in { val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR) @@ -380,57 +300,14 @@ class VehicleSpawnControl6cTest extends ActorTest() { val probe3Msg5 = probe3.receiveOne(3 seconds) assert(probe3Msg5.isInstanceOf[VehicleSpawnPad.ResetSpawnPad]) - val probe1Msg2 = probe1.receiveOne(10 seconds) + val probe1Msg2 = probe1.receiveOne(12 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() { +class VehicleSpawnControl7Test extends ActorTest() { "VehicleSpawnControl" should { "player dies after getting in driver seat; the vehicle blocks the pad" in { val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR) @@ -465,7 +342,7 @@ class VehicleSpawnControl7bTest extends ActorTest() { val probe3Msg5 = probe3.receiveOne(100 milliseconds) assert(probe3Msg5.isInstanceOf[VehicleSpawnPad.ResetSpawnPad]) - val probe1Msg4 = probe1.receiveOne(10 seconds) + val probe1Msg4 = probe1.receiveOne(12 seconds) assert(probe1Msg4.isInstanceOf[VehicleSpawnPad.PeriodicReminder]) assert(probe1Msg4.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked) } diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala index 8b9351b1..cc0b7723 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/pslogin/src/main/scala/Maps.scala @@ -126,14 +126,15 @@ object Maps { LocalObject(2323, Door.Constructor) //spawn tube door LocalObject(2324, Door.Constructor) //spawn tube door LocalObject(2419, Terminal.Constructor(ground_vehicle_terminal)) - LocalObject(500, + LocalObject(1479, VehicleSpawnPad.Constructor(Vector3(3962.0f, 4334.0f, 267.75f), Vector3(0f, 0f, 180.0f)) - ) //TODO guid not correct + ) LocalObject(224, Terminal.Constructor(dropship_vehicle_terminal)) - LocalObject(501, - VehicleSpawnPad.Constructor(Vector3(4012.3594f, 4364.8047f, 271.90625f), Vector3(0f, 0f, 180.0f)) - ) //TODO guid not correct + LocalObject(223, + VehicleSpawnPad.Constructor(Vector3(4012.3594f, 4364.8047f, 271.90625f), Vector3(0f, 0f, 0f)) + ) ObjectToBuilding(222, 2) + ObjectToBuilding(223, 2) ObjectToBuilding(224, 2) ObjectToBuilding(370, 2) ObjectToBuilding(371, 2) @@ -204,6 +205,7 @@ object Maps { ObjectToBuilding(1188, 2) ObjectToBuilding(1492, 2) ObjectToBuilding(1494, 2) + ObjectToBuilding(1479, 2) ObjectToBuilding(1564, 2) ObjectToBuilding(1568, 2) ObjectToBuilding(1569, 2) @@ -228,8 +230,6 @@ object Maps { ObjectToBuilding(2323, 2) ObjectToBuilding(2324, 2) ObjectToBuilding(2419, 2) - ObjectToBuilding(500, 2) - ObjectToBuilding(501, 2) DoorToLock(375, 863) DoorToLock(376, 860) DoorToLock(384, 866) @@ -244,8 +244,8 @@ object Maps { DoorToLock(638, 882) DoorToLock(642, 884) DoorToLock(715, 751) - TerminalToSpawnPad(224, 501) - TerminalToSpawnPad(2419, 500) + TerminalToSpawnPad(224, 223) + TerminalToSpawnPad(2419, 1479) } def Building38() : Unit = { @@ -404,20 +404,23 @@ object Maps { Building29() Building42() Building51() + Building52() Building77() + Building79() + Building81() def Building1() : Unit = { //warpgate? LocalBuilding(1, FoundationBuilder(WarpGate.Structure)) } + // LocalBuilding(2, FoundationBuilder(WarpGate.Structure)) //TODO might be wrong? + def Building3() : Unit = { //warpgate? LocalBuilding(3, FoundationBuilder(WarpGate.Structure)) } -// LocalBuilding(2, FoundationBuilder(WarpGate.Structure)) //TODO might be wrong? - // LocalObject(520, ImplantTerminalMech.Constructor) //Hart B // LocalObject(1081, Terminal.Constructor(implant_terminal_interface)) //tube 520 // TerminalToInterface(520, 1081) @@ -623,17 +626,53 @@ object Maps { TerminalToSpawnPad(304, 292) } + def Building52() : Unit = { + //air terminal southwest of HART C + LocalBuilding(52, FoundationBuilder(Building.Structure(StructureType.Platform))) + LocalObject(305, Terminal.Constructor(dropship_vehicle_terminal)) + LocalObject(293, + VehicleSpawnPad.Constructor(Vector3(3575.0781f, 2654.9766f, 92.296875f), Vector3(0f, 0f, 225.0f)) + ) + ObjectToBuilding(305, 52) + ObjectToBuilding(293, 52) + TerminalToSpawnPad(305, 293) + } + def Building77() : Unit = { //ground terminal west of HART C LocalBuilding(77, FoundationBuilder(Building.Structure(StructureType.Platform))) LocalObject(1063, Terminal.Constructor(ground_vehicle_terminal)) LocalObject(706, - VehicleSpawnPad.Constructor(Vector3(3506.0f, 2820.0f, 92.0f), Vector3(0f, 0f, 270.0f)) + VehicleSpawnPad.Constructor(Vector3(3506.0f, 2820.0f, 92.0625f), Vector3(0f, 0f, 270.0f)) ) ObjectToBuilding(1063, 77) ObjectToBuilding(706, 77) TerminalToSpawnPad(1063, 706) } + + def Building79() : Unit = { + //ground terminal south of HART C + LocalBuilding(79, FoundationBuilder(Building.Structure(StructureType.Platform))) + LocalObject(1065, Terminal.Constructor(ground_vehicle_terminal)) + LocalObject(710, + VehicleSpawnPad.Constructor(Vector3(3659.836f, 2589.875f, 92.0625f), Vector3(0f, 0f, 180.0f)) + ) + ObjectToBuilding(1065, 79) + ObjectToBuilding(710, 79) + TerminalToSpawnPad(1065, 710) + } + + def Building81() : Unit = { + //ground terminal south of HART C + LocalBuilding(81, FoundationBuilder(Building.Structure(StructureType.Platform))) + LocalObject(1067, Terminal.Constructor(ground_vehicle_terminal)) + LocalObject(712, + VehicleSpawnPad.Constructor(Vector3(3659.836f, 2589.875f, 92.0625f), Vector3(0f, 0f, 270.0f)) + ) + ObjectToBuilding(1067, 81) + ObjectToBuilding(712, 81) + TerminalToSpawnPad(1067, 712) + } } val map14 = new ZoneMap("map14") diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 8df74569..8a80bb26 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -27,6 +27,8 @@ import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.mblocker.Locker import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, ProximityTerminal, Terminal} +import net.psforever.objects.serverobject.pad.process.{AutoDriveControls, VehicleSpawnControlGuided} +import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, Terminal} import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, VehicleLockState} import net.psforever.objects.serverobject.structures.{Building, StructureType, WarpGate} @@ -478,6 +480,10 @@ class WorldSessionActor extends Actor with MDCContextAware { case VehicleResponse.RevealPlayer(player_guid) => //TODO any action will cause the player to appear after the effects of ConcealPlayer + if(player.GUID == player_guid) { + sendResponse(ChatMsg(ChatMessageType.CMT_OPEN, true, "", "You are in a strange situation.", None)) + KillPlayer(player) + } case VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission) => if(tplayer_guid != guid) { @@ -637,6 +643,7 @@ class WorldSessionActor extends Actor with MDCContextAware { if(player_guid == player.GUID) { //disembarking self log.info(s"DismountVehicleMsg: $player_guid dismounts $obj @ $seat_num") + TotalDriverVehicleControl(obj) sendResponse(DismountVehicleMsg(player_guid, seat_num, false)) vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, seat_num, false)) UnAccessContents(obj) @@ -1044,11 +1051,32 @@ class WorldSessionActor extends Actor with MDCContextAware { if(vehicle.Seats(0).isOccupied) { sendResponse(ObjectDetachMessage(pad.GUID, vehicle.GUID, pad.Position + Vector3(0, 0, 0.5f), 0, 0, pad.Orientation.z)) } - sendResponse(ServerVehicleOverrideMsg.Lock(GlobalDefinitions.isFlightVehicle(vdef):Int, vdef.AutoPilotSpeed1)) + ServerVehicleOverride(vehicle, vdef.AutoPilotSpeed1, GlobalDefinitions.isFlightVehicle(vdef):Int) + + case VehicleSpawnControlGuided.GuidedControl(cmd, vehicle, data) => + cmd match { + case AutoDriveControls.State.Drive => + val speed : Int = data.getOrElse({ vehicle.Definition.AutoPilotSpeed1 }).asInstanceOf[Int] + ServerVehicleOverride(vehicle, speed) + + case AutoDriveControls.State.Climb => + ServerVehicleOverride(vehicle, vehicle.Controlled.getOrElse(0), GlobalDefinitions.isFlightVehicle(vehicle.Definition):Int) + + case AutoDriveControls.State.Turn => + //TODO how to turn hovering/flying vehicle? + val direction = data.getOrElse(15).asInstanceOf[Int] + sendResponse(VehicleStateMessage(vehicle.GUID, 0, vehicle.Position, vehicle.Orientation, vehicle.Velocity, None, 0, 0, direction, false, false)) + + + case AutoDriveControls.State.Stop => + ServerVehicleOverride(vehicle, 0) + + case _ => ; + } case VehicleSpawnPad.ServerVehicleOverrideEnd(vehicle, pad) => sendResponse(GenericObjectActionMessage(pad.GUID, 92)) //reset spawn pad - sendResponse(ServerVehicleOverrideMsg.Auto(vehicle.Definition.AutoPilotSpeed2)) + DriverVehicleControl(vehicle, vehicle.Definition.AutoPilotSpeed2) case VehicleSpawnPad.PeriodicReminder(cause, data) => val msg : String = (cause match { @@ -1557,7 +1585,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ => log.warn(s"VehicleState: no vehicle $vehicle_guid found in zone") } - //log.info("VehicleState: " + msg) + //log.info(s"VehicleState: $msg") case msg @ VehicleSubStateMessage(vehicle_guid, player_guid, vehicle_pos, vehicle_ang, vel, unk1, unk2) => //log.info(s"VehicleSubState: $vehicle_guid, $player_guid, $vehicle_pos, $vehicle_ang, $vel, $unk1, $unk2") @@ -3511,6 +3539,7 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, pos, player.Faction, true)) if(tplayer.VehicleSeated.nonEmpty) { //make player invisible (if not, the cadaver sticks out the side in a seated position) + TotalDriverVehicleControl(continent.GUID(tplayer.VehicleSeated.get).get.asInstanceOf[Vehicle]) sendResponse(PlanetsideAttributeMessage(player_guid, 29, 1)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 29, 1)) } @@ -3867,6 +3896,25 @@ class WorldSessionActor extends Actor with MDCContextAware { tplayer.Armor == tplayer.MaxArmor } + def ServerVehicleOverride(vehicle : Vehicle, speed : Int = 0, flight : Int = 0) : Unit = { + vehicle.Controlled = Some(speed) + sendResponse(ServerVehicleOverrideMsg(true, true, false, false, flight, 0, speed, Some(0))) + } + + def DriverVehicleControl(vehicle : Vehicle, speed : Int = 0, flight : Int = 0) : Unit = { + if(vehicle.Controlled.nonEmpty) { + vehicle.Controlled = None + sendResponse(ServerVehicleOverrideMsg(false, false, false, true, flight, 0, speed, None)) + } + } + + def TotalDriverVehicleControl(vehicle : Vehicle) : Unit = { + if(vehicle.Controlled.nonEmpty) { + vehicle.Controlled = None + sendResponse(ServerVehicleOverrideMsg(false, false, false, false, 0, 0, 0, None)) + } + } + def failWithError(error : String) = { log.error(error) sendResponse(ConnectionClose()) diff --git a/pslogin/src/main/scala/Zones.scala b/pslogin/src/main/scala/Zones.scala index b40d3311..55d18bb9 100644 --- a/pslogin/src/main/scala/Zones.scala +++ b/pslogin/src/main/scala/Zones.scala @@ -1,6 +1,7 @@ // Copyright (c) 2017 PSForever import akka.actor.ActorContext import net.psforever.objects.serverobject.pad.VehicleSpawnPad +import net.psforever.objects.serverobject.pad.process._ import net.psforever.objects.zones.Zone import net.psforever.types.PlanetSideEmpire @@ -51,6 +52,7 @@ object Zones { Buildings.values.foreach { _.Faction = PlanetSideEmpire.VS } Building(29).get.Faction = PlanetSideEmpire.NC //South Villa Gun Tower GUID(293).get.asInstanceOf[VehicleSpawnPad].Railed = false //building 52 + GUID(706).get.asInstanceOf[VehicleSpawnPad].Guide = List(AutoDriveControls.DistanceFromHere(50f)) //building 77 GUID(710).get.asInstanceOf[VehicleSpawnPad].Railed = false //building 79 GUID(712).get.asInstanceOf[VehicleSpawnPad].Railed = false //building 81 }