Merge pull request #188 from Fate-JH/ams-deploy

AMS Deployment
This commit is contained in:
Fate-JH 2018-02-21 22:43:58 -05:00 committed by GitHub
commit 599d767598
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 810 additions and 91 deletions

View file

@ -25,6 +25,7 @@ ignore:
- "common/src/main/scala/net/psforever/types/Angular.scala" - "common/src/main/scala/net/psforever/types/Angular.scala"
- "common/src/main/scala/net/psforever/types/CertificationType.scala" - "common/src/main/scala/net/psforever/types/CertificationType.scala"
- "common/src/main/scala/net/psforever/types/ChatMessageType.scala" - "common/src/main/scala/net/psforever/types/ChatMessageType.scala"
- "common/src/main/scala/net/psforever/types/DriveState.scala"
- "common/src/main/scala/net/psforever/types/EmoteType.scala" - "common/src/main/scala/net/psforever/types/EmoteType.scala"
- "common/src/main/scala/net/psforever/types/ExoSuitType.scala" - "common/src/main/scala/net/psforever/types/ExoSuitType.scala"
- "common/src/main/scala/net/psforever/types/GrenadeState.scala" - "common/src/main/scala/net/psforever/types/GrenadeState.scala"

View file

@ -2349,6 +2349,9 @@ object GlobalDefinitions {
ams.MountPoints += 2 -> 0 ams.MountPoints += 2 -> 0
ams.Utilities += 3 -> UtilityType.order_terminala ams.Utilities += 3 -> UtilityType.order_terminala
ams.Utilities += 4 -> UtilityType.order_terminalb ams.Utilities += 4 -> UtilityType.order_terminalb
ams.Deployment = true
ams.DeployTime = 2000
ams.UndeployTime = 2000
ams.Packet = utilityConverter ams.Packet = utilityConverter
val variantConverter = new VariantVehicleConverter val variantConverter = new VariantVehicleConverter
@ -2356,6 +2359,9 @@ object GlobalDefinitions {
router.MountPoints += 1 -> 0 router.MountPoints += 1 -> 0
router.TrunkSize = InventoryTile.Tile1511 router.TrunkSize = InventoryTile.Tile1511
router.TrunkOffset = 30 router.TrunkOffset = 30
router.Deployment = true
router.DeployTime = 2000
router.UndeployTime = 2000
router.Packet = variantConverter router.Packet = variantConverter
switchblade.Seats += 0 -> new SeatDefinition() switchblade.Seats += 0 -> new SeatDefinition()
@ -2365,6 +2371,9 @@ object GlobalDefinitions {
switchblade.MountPoints += 2 -> 0 switchblade.MountPoints += 2 -> 0
switchblade.TrunkSize = InventoryTile.Tile1511 switchblade.TrunkSize = InventoryTile.Tile1511
switchblade.TrunkOffset = 30 switchblade.TrunkOffset = 30
switchblade.Deployment = true
switchblade.DeployTime = 2000
switchblade.UndeployTime = 2000
switchblade.Packet = variantConverter switchblade.Packet = variantConverter
flail.Seats += 0 -> new SeatDefinition() flail.Seats += 0 -> new SeatDefinition()
@ -2373,6 +2382,9 @@ object GlobalDefinitions {
flail.MountPoints += 1 -> 0 flail.MountPoints += 1 -> 0
flail.TrunkSize = InventoryTile.Tile1511 flail.TrunkSize = InventoryTile.Tile1511
flail.TrunkOffset = 30 flail.TrunkOffset = 30
flail.Deployment = true
flail.DeployTime = 2000
flail.UndeployTime = 2000
flail.Packet = variantConverter flail.Packet = variantConverter
mosquito.Seats += 0 -> new SeatDefinition() mosquito.Seats += 0 -> new SeatDefinition()

View file

@ -7,9 +7,9 @@ import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem,
import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.vehicles.{AccessPermissionGroup, Seat, Utility, VehicleLockState} import net.psforever.objects.vehicles.{AccessPermissionGroup, Seat, Utility, VehicleLockState}
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.DriveState
import net.psforever.types.PlanetSideEmpire import net.psforever.types.PlanetSideEmpire
import scala.annotation.tailrec import scala.annotation.tailrec
@ -20,8 +20,7 @@ import scala.annotation.tailrec
* All infantry seating, all mounted weapons, and the trunk space are considered part of the same index hierarchy. * All infantry seating, all mounted weapons, and the trunk space are considered part of the same index hierarchy.
* Generally, all seating is declared first - the driver and passengers and and gunners. * Generally, all seating is declared first - the driver and passengers and and gunners.
* Following that are the mounted weapons and other utilities. * Following that are the mounted weapons and other utilities.
* Trunk space starts being indexed afterwards.<br> * Trunk space starts being indexed afterwards.
* <br>
* To keep it simple, infantry seating, mounted weapons, and utilities are stored separately.<br> * To keep it simple, infantry seating, mounted weapons, and utilities are stored separately.<br>
* <br> * <br>
* Vehicles maintain a `Map` of `Utility` objects in given index positions. * Vehicles maintain a `Map` of `Utility` objects in given index positions.
@ -38,12 +37,12 @@ import scala.annotation.tailrec
class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServerObject class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServerObject
with FactionAffinity with FactionAffinity
with Mountable with Mountable
with Deployment
with Container { with Container {
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR
private var owner : Option[PlanetSideGUID] = None private var owner : Option[PlanetSideGUID] = None
private var health : Int = 1 private var health : Int = 1
private var shields : Int = 0 private var shields : Int = 0
private var deployed : DriveState.Value = DriveState.Mobile
private var decal : Int = 0 private var decal : Int = 0
private var trunkAccess : Option[PlanetSideGUID] = None private var trunkAccess : Option[PlanetSideGUID] = None
private var jammered : Boolean = false private var jammered : Boolean = false
@ -125,17 +124,6 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
Definition.MaxShields Definition.MaxShields
} }
def Drive : DriveState.Value = {
this.deployed
}
def Drive_=(deploy : DriveState.Value) : DriveState.Value = {
if(Definition.Deployment) {
this.deployed = deploy
}
Drive
}
def Decal : Int = { def Decal : Int = {
this.decal this.decal
} }
@ -353,6 +341,10 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
} }
} }
override def DeployTime = Definition.DeployTime
override def UndeployTime = Definition.UndeployTime
def Inventory : GridInventory = trunk def Inventory : GridInventory = trunk
def Find(obj : Equipment) : Option[Int] = Find(obj.GUID) def Find(obj : Equipment) : Option[Int] = Find(obj.GUID)

View file

@ -22,6 +22,8 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
private val weapons : mutable.HashMap[Int, ToolDefinition] = mutable.HashMap[Int, ToolDefinition]() private val weapons : mutable.HashMap[Int, ToolDefinition] = mutable.HashMap[Int, ToolDefinition]()
private var deployment : Boolean = false private var deployment : Boolean = false
private val utilities : mutable.HashMap[Int, UtilityType.Value] = mutable.HashMap() private val utilities : mutable.HashMap[Int, UtilityType.Value] = mutable.HashMap()
private var deploymentTime_Deploy : Int = 0 //ms
private var deploymentTime_Undeploy : Int = 0 //ms
private var trunkSize : InventoryTile = InventoryTile.None private var trunkSize : InventoryTile = InventoryTile.None
private var trunkOffset : Int = 0 private var trunkOffset : Int = 0
private var canCloak : Boolean = false private var canCloak : Boolean = false
@ -70,8 +72,23 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
Deployment Deployment
} }
def Utilities : mutable.HashMap[Int, UtilityType.Value] = utilities def Utilities : mutable.HashMap[Int, UtilityType.Value] = utilities
def DeployTime : Int = deploymentTime_Deploy
def DeployTime_=(dtime : Int) : Int = {
deploymentTime_Deploy = dtime
DeployTime
}
def UndeployTime : Int = deploymentTime_Undeploy
def UndeployTime_=(dtime : Int) : Int = {
deploymentTime_Undeploy = dtime
UndeployTime
}
def TrunkSize : InventoryTile = trunkSize def TrunkSize : InventoryTile = trunkSize
def TrunkSize_=(tile : InventoryTile) : InventoryTile = { def TrunkSize_=(tile : InventoryTile) : InventoryTile = {

View file

@ -24,7 +24,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
0, 0,
obj.Health / obj.MaxHealth * 255, //TODO not precise obj.Health / obj.MaxHealth * 255, //TODO not precise
false, false, false, false,
obj.Drive, obj.DeploymentState,
false, false,
false, false,
obj.Cloaked, obj.Cloaked,

View file

@ -0,0 +1,106 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.deploy
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.types.DriveState
/**
* A `trait` for the purposes of deploying a server object that supports the operation.
* As a mixin, it provides the local variable used to keep track of the deployment state
* and the logic to change the value of the deployment.
* Initially, the deployment state is `Mobile`.
*/
trait Deployment {
this : PlanetSideServerObject =>
private var deployState : DriveState.Value = DriveState.Mobile
def DeployTime : Int = 0 //ms
def UndeployTime : Int = 0 //ms
def DeploymentState : DriveState.Value = deployState
def DeploymentState_=(to_deploy_state : DriveState.Value) : DriveState.Value = {
deployState = to_deploy_state
DeploymentState
}
}
object Deployment {
/**
* A shorthand `type` for a valid object of `Deployment`.
*/
type DeploymentObject = PlanetSideServerObject with Deployment
/**
* A message for instigating a change in deployment state.
* @param state the new deployment state
*/
final case class TryDeploymentChange(state : DriveState.Value)
/**
* A message for instigating a change to a deploy state.
* @param state the new deploy state
*/
final case class TryDeploy(state : DriveState.Value)
/**
* A message for instigating a change to an undeploy state.
* @param state the new undeploy state
*/
final case class TryUndeploy(state : DriveState.Value)
/**
* A response message to report successful deploy change.
* @param obj the object being deployed
* @param state the new deploy state
*/
final case class CanDeploy(obj : DeploymentObject, state : DriveState.Value)
/**
* A response message to report successful undeploy change.
* @param obj the object being undeployed
* @param state the new undeploy state
*/
final case class CanUndeploy(obj : DeploymentObject, state : DriveState.Value)
/**
* A response message to report an unsuccessful deployment change.
* @param obj the object being changed
* @param to_state the attempted deployment state
* @param reason a string explaining why the state can not or will not change
*/
final case class CanNotChangeDeployment(obj : DeploymentObject, to_state : DriveState.Value, reason : String)
/**
* Given a starting deployment state, provide the next deployment state in a sequence.<br>
* <br>
* Two sequences are defined.
* The more elaborate sequence proceeds from `Mobile --> Deploying --> Deployed --> Undeploying --> Mobile`.
* This is the standard in-game deploy cycle.
* The sequence void of complexity is `State7 --> State7`.
* `State7` is an odd condition possessed mainly by vehicles that do not deploy.
* @param from_state the original deployment state
* @return the deployment state that is being transitioned
*/
def NextState(from_state : DriveState.Value) : DriveState.Value = {
from_state match {
case DriveState.Mobile => DriveState.Deploying
case DriveState.Deploying => DriveState.Deployed
case DriveState.Deployed => DriveState.Undeploying
case DriveState.Undeploying => DriveState.Mobile
case DriveState.State7 => DriveState.State7
}
}
/**
* Is this `state` considered one of "deploy?"
* @param state the state to check
* @return yes, if it is a valid state; otherwise, false
*/
def CheckForDeployState(state : DriveState.Value) : Boolean =
state == DriveState.Deploying || state == DriveState.Deployed
/**
* Is this `state` considered one of "undeploy?"
* @param state the state to check
* @return yes, if it is a valid state; otherwise, false
*/
def CheckForUndeployState(state : DriveState.Value) : Boolean =
state == DriveState.Undeploying || state == DriveState.Mobile || state == DriveState.State7
}

View file

@ -0,0 +1,58 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.deploy
import akka.actor.Actor
/**
* The logic governing `Deployment` objects that use the following messages:
* `TryDeploymentChange`,
* `TryDeploy`,
* and `TryUndeploy`.
* This is a mix-in trait for combining with existing `Receive` logic.
* @see `Deployment`
* @see `DriveState`
*/
trait DeploymentBehavior {
this : Actor =>
def DeploymentObject : Deployment.DeploymentObject
val deployBehavior : Receive = {
case Deployment.TryDeploymentChange(state) =>
val obj = DeploymentObject
if(Deployment.NextState(obj.DeploymentState) == state
&& (obj.DeploymentState = state) == state) {
if(Deployment.CheckForDeployState(state)) {
sender ! Deployment.CanDeploy(obj, state)
}
else { //may need to check in future
sender ! Deployment.CanUndeploy(obj, state)
}
}
else {
sender ! Deployment.CanNotChangeDeployment(obj, state, "incorrect transition state")
}
case Deployment.TryDeploy(state) =>
val obj = DeploymentObject
if(Deployment.CheckForDeployState(state)
&& Deployment.NextState(obj.DeploymentState) == state
&& (obj.DeploymentState = state) == state) {
sender ! Deployment.CanDeploy(obj, state)
}
else {
sender ! Deployment.CanNotChangeDeployment(obj, state, "incorrect deploy transition state")
}
case Deployment.TryUndeploy(state) =>
val obj = DeploymentObject
if(Deployment.CheckForUndeployState(state)
&& Deployment.NextState(obj.DeploymentState) == state
&& (obj.DeploymentState = state) == state) {
sender ! Deployment.CanUndeploy(obj, state)
}
else {
sender ! Deployment.CanNotChangeDeployment(obj, state, "incorrect undeploy transition state")
}
}
}

View file

@ -5,6 +5,7 @@ import akka.actor.Actor
import net.psforever.objects.Vehicle import net.psforever.objects.Vehicle
import net.psforever.objects.serverobject.mount.MountableBehavior import net.psforever.objects.serverobject.mount.MountableBehavior
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.deploy.DeploymentBehavior
/** /**
* An `Actor` that handles messages being dispatched to a specific `Vehicle`.<br> * An `Actor` that handles messages being dispatched to a specific `Vehicle`.<br>
@ -15,18 +16,22 @@ import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffi
*/ */
class VehicleControl(vehicle : Vehicle) extends Actor class VehicleControl(vehicle : Vehicle) extends Actor
with FactionAffinityBehavior.Check with FactionAffinityBehavior.Check
with DeploymentBehavior
with MountableBehavior.Mount with MountableBehavior.Mount
with MountableBehavior.Dismount { with MountableBehavior.Dismount {
//make control actors belonging to utilities when making control actor belonging to vehicle //make control actors belonging to utilities when making control actor belonging to vehicle
vehicle.Utilities.foreach({case (_, util) => util.Setup }) vehicle.Utilities.foreach({case (_, util) => util.Setup })
def MountableObject = vehicle //do not add type! def MountableObject = vehicle
def FactionObject : FactionAffinity = vehicle def FactionObject = vehicle
def DeploymentObject = vehicle
def receive : Receive = Enabled def receive : Receive = Enabled
def Enabled : Receive = checkBehavior def Enabled : Receive = checkBehavior
.orElse(deployBehavior)
.orElse(mountBehavior) .orElse(mountBehavior)
.orElse(dismountBehavior) .orElse(dismountBehavior)
.orElse { .orElse {

View file

@ -2,7 +2,7 @@
package net.psforever.packet.game package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.Vector3 import net.psforever.types.{DriveState, Vector3}
import scodec.Codec import scodec.Codec
import scodec.codecs._ import scodec.codecs._
@ -22,8 +22,7 @@ import scodec.codecs._
* This packet has nothing to do with ACE deployables. * This packet has nothing to do with ACE deployables.
* @param player_guid the player requesting the deployment * @param player_guid the player requesting the deployment
* @param vehicle_guid the vehicle to be deployed * @param vehicle_guid the vehicle to be deployed
* @param unk1 na; * @param deploy_state either requests for a specific deployment state or assignment of the requested state
* usually 2
* @param unk2 na; * @param unk2 na;
* usually 0 * usually 0
* @param unk3 na * @param unk3 na
@ -31,7 +30,7 @@ import scodec.codecs._
*/ */
final case class DeployRequestMessage(player_guid : PlanetSideGUID, final case class DeployRequestMessage(player_guid : PlanetSideGUID,
vehicle_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID,
unk1 : Int, deploy_state : DriveState.Value,
unk2 : Int, unk2 : Int,
unk3 : Boolean, unk3 : Boolean,
pos : Vector3) pos : Vector3)
@ -44,8 +43,8 @@ final case class DeployRequestMessage(player_guid : PlanetSideGUID,
object DeployRequestMessage extends Marshallable[DeployRequestMessage] { object DeployRequestMessage extends Marshallable[DeployRequestMessage] {
implicit val codec : Codec[DeployRequestMessage] = ( implicit val codec : Codec[DeployRequestMessage] = (
("player_guid" | PlanetSideGUID.codec) :: ("player_guid" | PlanetSideGUID.codec) ::
("deploy_guid" | PlanetSideGUID.codec) :: ("vehicle_guid" | PlanetSideGUID.codec) ::
("unk1" | uint(3)) :: ("deploy_state" | DriveState.codec) ::
("unk2" | uint(5)) :: ("unk2" | uint(5)) ::
("unk3" | bool) :: ("unk3" | bool) ::
("pos" | Vector3.codec_pos) ("pos" | Vector3.codec_pos)

View file

@ -2,7 +2,7 @@
package net.psforever.packet.game.objectcreate package net.psforever.packet.game.objectcreate
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.PlanetSideEmpire import net.psforever.types.{DriveState, PlanetSideEmpire}
/** /**
* A compilation of the common `*Data` objects that would be used for stock game objects. * A compilation of the common `*Data` objects that would be used for stock game objects.

View file

@ -1,11 +1,13 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable import net.psforever.packet.{Marshallable, PacketHelpers}
import scodec.Attempt.{Failure, Successful} import scodec.Attempt.{Failure, Successful}
import scodec.{Attempt, Codec, Err} import scodec.{Attempt, Codec, Err}
import scodec.codecs._ import scodec.codecs._
import shapeless.{::, HNil} import shapeless.{::, HNil}
import net.psforever.types.DriveState
/** /**
* An `Enumeration` of the various formats that known structures that the stream of bits for `VehicleData` can assume. * An `Enumeration` of the various formats that known structures that the stream of bits for `VehicleData` can assume.
*/ */
@ -141,6 +143,8 @@ object VehicleData extends Marshallable[VehicleData] {
new VehicleData(basic, unk1, health, unk2>0, false, driveState, unk3, unk5>0, false, Some(unk4), inventory)(VehicleFormat.Variant) new VehicleData(basic, unk1, health, unk2>0, false, driveState, unk3, unk5>0, false, Some(unk4), inventory)(VehicleFormat.Variant)
} }
private val driveState8u = PacketHelpers.createEnumerationCodec(DriveState, uint8L)
/** /**
* `Codec` for the "utility" format. * `Codec` for the "utility" format.
*/ */
@ -192,7 +196,7 @@ object VehicleData extends Marshallable[VehicleData] {
("health" | uint8L) :: ("health" | uint8L) ::
("unk2" | bool) :: //usually 0 ("unk2" | bool) :: //usually 0
("no_mount_points" | bool) :: ("no_mount_points" | bool) ::
("driveState" | DriveState.codec) :: //used for deploy state ("driveState" | driveState8u) :: //used for deploy state
("unk3" | bool) :: //unknown but generally false; can cause stream misalignment if set when unexpectedly ("unk3" | bool) :: //unknown but generally false; can cause stream misalignment if set when unexpectedly
("unk4" | bool) :: ("unk4" | bool) ::
("cloak" | bool) :: //cloak as wraith, phantasm ("cloak" | bool) :: //cloak as wraith, phantasm

View file

@ -1,26 +1,24 @@
// Copyright (c) 2017 PSForever package net.psforever.types
package net.psforever.packet.game.objectcreate
import net.psforever.packet.PacketHelpers import net.psforever.packet.PacketHelpers
import scodec.codecs._ import scodec.codecs.uint
/** /**
* An `Enumeration` of the mobility states of vehicles.<br> * An `Enumeration` of the mobility states of vehicles.<br>
* <br> * <br>
* In general, two important mobility states exist - `Mobile` and "deployed." * In general, two important mobility states exist - `Mobile` and `Deployed`.
* There are three stages of a formal deployment. * There are stages of a formal deployment.
* For any deployment state other than the defined ones, the vehicle assumes it is in one of the transitional states. * For any deployment state other than the defined ones, the vehicle assumes it is in one of the transitional states.
* If the target vehicle has no deployment behavior, a non-`Mobile` value will not affect it. * If the target vehicle has no deployment behavior, a non-`Mobile` value will not affect it.
*/ */
object DriveState extends Enumeration { object DriveState extends Enumeration {
type Type = Value type Type = Value
val Mobile = Value(0) //drivable val Mobile = Value(0)
val Undeployed = Value(1) //stationary val Undeploying = Value(1)
val Unavailable = Value(2) //stationary, partial activation val Deploying = Value(2)
val Deployed = Value(3) //stationary, full activation val Deployed = Value(3)
val State7 = Value(7) //unknown; not encountered on a vehicle that can deploy; functions like Mobile val State7 = Value(7) //unknown; not encountered on a vehicle that can deploy; functions like Mobile
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint8L) implicit val codec = PacketHelpers.createEnumerationCodec(this, uint(3))
} }

View file

@ -4,7 +4,7 @@ package game
import org.specs2.mutable._ import org.specs2.mutable._
import net.psforever.packet._ import net.psforever.packet._
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.types.Vector3 import net.psforever.types.{DriveState, Vector3}
import scodec.bits._ import scodec.bits._
class DeployRequestMessageTest extends Specification { class DeployRequestMessageTest extends Specification {
@ -12,10 +12,10 @@ class DeployRequestMessageTest extends Specification {
"decode" in { "decode" in {
PacketCoding.DecodePacket(string).require match { PacketCoding.DecodePacket(string).require match {
case DeployRequestMessage(player_guid, vehicle_guid, unk1, unk2, unk3, pos) => case DeployRequestMessage(player_guid, vehicle_guid, deploy_state, unk2, unk3, pos) =>
player_guid mustEqual PlanetSideGUID(75) player_guid mustEqual PlanetSideGUID(75)
vehicle_guid mustEqual PlanetSideGUID(380) vehicle_guid mustEqual PlanetSideGUID(380)
unk1 mustEqual 2 deploy_state mustEqual DriveState.Deploying
unk2 mustEqual 0 unk2 mustEqual 0
unk3 mustEqual false unk3 mustEqual false
pos.x mustEqual 4060.1953f pos.x mustEqual 4060.1953f
@ -30,7 +30,8 @@ class DeployRequestMessageTest extends Specification {
val msg = DeployRequestMessage( val msg = DeployRequestMessage(
PlanetSideGUID(75), PlanetSideGUID(75),
PlanetSideGUID(380), PlanetSideGUID(380),
2, 0, false, DriveState.Deploying,
0, false,
Vector3(4060.1953f, 2218.8281f, 155.32812f) Vector3(4060.1953f, 2218.8281f, 155.32812f)
) )
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector

View file

@ -0,0 +1,197 @@
// Copyright (c) 2017 PSForever
package objects
import akka.actor.{Actor, ActorSystem, Props}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.{GlobalDefinitions, Vehicle}
import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior}
import net.psforever.types.{DriveState, PlanetSideEmpire}
import org.specs2.mutable.Specification
import scala.concurrent.duration.Duration
class DeploymentTest extends Specification {
"Deployment" should {
"construct" in {
val obj = new DeploymentTest.DeploymentObject()
obj.DeploymentState mustEqual DriveState.Mobile
obj.DeployTime mustEqual 0
obj.UndeployTime mustEqual 0
}
"change deployment state" in {
val obj = new DeploymentTest.DeploymentObject()
obj.DeploymentState mustEqual DriveState.Mobile
obj.DeploymentState = DriveState.Deployed
obj.DeploymentState mustEqual DriveState.Deployed
obj.DeploymentState = DriveState.Deploying
obj.DeploymentState mustEqual DriveState.Deploying
obj.DeploymentState = DriveState.Undeploying
obj.DeploymentState mustEqual DriveState.Undeploying
obj.DeploymentState = DriveState.State7
obj.DeploymentState mustEqual DriveState.State7
}
"have custom deployment time by object" in {
val ams = Vehicle(GlobalDefinitions.ams)
(ams.DeployTime == 0) mustEqual false //not default
(ams.UndeployTime == 0) mustEqual false //not default
}
}
}
class DeploymentBehavior1Test extends ActorTest {
"Deployment" should {
"construct" in {
val obj = DeploymentTest.SetUpAgent
assert(obj.Actor != Actor.noSender)
assert(obj.DeploymentState == DriveState.Mobile)
}
}
}
class DeploymentBehavior2Test extends ActorTest {
"Deployment" should {
"change following a deployment cycle using TryDeployChange" in {
val obj = DeploymentTest.SetUpAgent
assert(obj.DeploymentState == DriveState.Mobile)
//to Deploying
obj.Actor ! Deployment.TryDeploymentChange(DriveState.Deploying)
val reply1 = receiveOne(Duration.create(100, "ms"))
assert(reply1.isInstanceOf[Deployment.CanDeploy])
assert(reply1.asInstanceOf[Deployment.CanDeploy].obj == obj)
assert(reply1.asInstanceOf[Deployment.CanDeploy].state == DriveState.Deploying)
//to Deployed
obj.Actor ! Deployment.TryDeploymentChange(DriveState.Deployed)
val reply2 = receiveOne(Duration.create(100, "ms"))
assert(reply2.isInstanceOf[Deployment.CanDeploy])
assert(reply2.asInstanceOf[Deployment.CanDeploy].obj == obj)
assert(reply2.asInstanceOf[Deployment.CanDeploy].state == DriveState.Deployed)
//to Deployed
obj.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
val reply3 = receiveOne(Duration.create(100, "ms"))
assert(reply3.isInstanceOf[Deployment.CanUndeploy])
assert(reply3.asInstanceOf[Deployment.CanUndeploy].obj == obj)
assert(reply3.asInstanceOf[Deployment.CanUndeploy].state == DriveState.Undeploying)
//to Deployed
obj.Actor ! Deployment.TryDeploymentChange(DriveState.Mobile)
val reply4 = receiveOne(Duration.create(100, "ms"))
assert(reply4.isInstanceOf[Deployment.CanUndeploy])
assert(reply4.asInstanceOf[Deployment.CanUndeploy].obj == obj)
assert(reply4.asInstanceOf[Deployment.CanUndeploy].state == DriveState.Mobile)
}
}
}
class DeploymentBehavior3Test extends ActorTest {
"Deployment" should {
"change following a deployment cycle using TryDeploy and TryUndeploy" in {
val obj = DeploymentTest.SetUpAgent
assert(obj.DeploymentState == DriveState.Mobile)
//to Deploying
obj.Actor ! Deployment.TryDeploy(DriveState.Deploying)
val reply1 = receiveOne(Duration.create(100, "ms"))
assert(reply1.isInstanceOf[Deployment.CanDeploy])
assert(reply1.asInstanceOf[Deployment.CanDeploy].obj == obj)
assert(reply1.asInstanceOf[Deployment.CanDeploy].state == DriveState.Deploying)
//to Deployed
obj.Actor ! Deployment.TryDeploy(DriveState.Deployed)
val reply2 = receiveOne(Duration.create(100, "ms"))
assert(reply2.isInstanceOf[Deployment.CanDeploy])
assert(reply2.asInstanceOf[Deployment.CanDeploy].obj == obj)
assert(reply2.asInstanceOf[Deployment.CanDeploy].state == DriveState.Deployed)
//to Deployed
obj.Actor ! Deployment.TryUndeploy(DriveState.Undeploying)
val reply3 = receiveOne(Duration.create(100, "ms"))
assert(reply3.isInstanceOf[Deployment.CanUndeploy])
assert(reply3.asInstanceOf[Deployment.CanUndeploy].obj == obj)
assert(reply3.asInstanceOf[Deployment.CanUndeploy].state == DriveState.Undeploying)
//to Deployed
obj.Actor ! Deployment.TryUndeploy(DriveState.Mobile)
val reply4 = receiveOne(Duration.create(100, "ms"))
assert(reply4.isInstanceOf[Deployment.CanUndeploy])
assert(reply4.asInstanceOf[Deployment.CanUndeploy].obj == obj)
assert(reply4.asInstanceOf[Deployment.CanUndeploy].state == DriveState.Mobile)
}
}
}
class DeploymentBehavior4Test extends ActorTest {
"Deployment" should {
"not deploy to an out of order state" in {
val obj = DeploymentTest.SetUpAgent
assert(obj.DeploymentState == DriveState.Mobile)
obj.Actor ! Deployment.TryDeploymentChange(DriveState.Deployed)
val reply1 = receiveOne(Duration.create(100, "ms"))
assert(reply1.isInstanceOf[Deployment.CanNotChangeDeployment])
assert(reply1.asInstanceOf[Deployment.CanNotChangeDeployment].obj == obj)
assert(reply1.asInstanceOf[Deployment.CanNotChangeDeployment].to_state == DriveState.Deployed)
assert(obj.DeploymentState == DriveState.Mobile)
obj.Actor ! Deployment.TryDeploy(DriveState.Deployed)
val reply2 = receiveOne(Duration.create(100, "ms"))
assert(reply2.isInstanceOf[Deployment.CanNotChangeDeployment])
assert(reply2.asInstanceOf[Deployment.CanNotChangeDeployment].obj == obj)
assert(reply2.asInstanceOf[Deployment.CanNotChangeDeployment].to_state == DriveState.Deployed)
assert(obj.DeploymentState == DriveState.Mobile)
}
}
}
class DeploymentBehavior5Test extends ActorTest {
"Deployment" should {
"not deploy to an undeploy state" in {
val obj = DeploymentTest.SetUpAgent
assert(obj.DeploymentState == DriveState.Mobile)
obj.Actor ! Deployment.TryDeploymentChange(DriveState.Deploying)
receiveOne(Duration.create(100, "ms")) //consume
obj.Actor ! Deployment.TryDeploymentChange(DriveState.Deployed)
receiveOne(Duration.create(100, "ms")) //consume
assert(obj.DeploymentState == DriveState.Deployed)
obj.Actor ! Deployment.TryDeploy(DriveState.Undeploying)
val reply = receiveOne(Duration.create(100, "ms"))
assert(reply.isInstanceOf[Deployment.CanNotChangeDeployment])
assert(reply.asInstanceOf[Deployment.CanNotChangeDeployment].obj == obj)
assert(reply.asInstanceOf[Deployment.CanNotChangeDeployment].to_state == DriveState.Undeploying)
assert(obj.DeploymentState == DriveState.Deployed)
}
}
}
class DeploymentBehavior6Test extends ActorTest {
"Deployment" should {
"not undeploy to a deploy state" in {
val obj = DeploymentTest.SetUpAgent
assert(obj.DeploymentState == DriveState.Mobile)
obj.Actor ! Deployment.TryUndeploy(DriveState.Deploying)
val reply = receiveOne(Duration.create(100, "ms"))
assert(reply.isInstanceOf[Deployment.CanNotChangeDeployment])
assert(reply.asInstanceOf[Deployment.CanNotChangeDeployment].obj == obj)
assert(reply.asInstanceOf[Deployment.CanNotChangeDeployment].to_state == DriveState.Deploying)
assert(obj.DeploymentState == DriveState.Mobile)
}
}
}
object DeploymentTest {
class DeploymentObject extends PlanetSideServerObject with Deployment {
def Faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
def Definition = null
}
private class DeploymentControl(obj : Deployment.DeploymentObject) extends Actor
with DeploymentBehavior {
override def DeploymentObject = obj
def receive = deployBehavior.orElse { case _ => }
}
def SetUpAgent(implicit system : ActorSystem) = {
val obj = new DeploymentObject()
obj.Actor = system.actorOf(Props(classOf[DeploymentControl], obj), "test")
obj
}
}

View file

@ -16,6 +16,7 @@ import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech
@ -330,6 +331,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(guid, unk1, unk2))) sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(guid, unk1, unk2)))
} }
case VehicleResponse.DeployRequest(object_guid, state, unk1, unk2, pos) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(guid, object_guid, state, unk1, unk2, pos)))
}
case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) => case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) =>
if(player.GUID != guid) { if(player.GUID != guid) {
//TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly?
@ -399,6 +405,50 @@ class WorldSessionActor extends Actor with MDCContextAware {
case _ => ; case _ => ;
} }
case Deployment.CanDeploy(obj, state) =>
val vehicle_guid = obj.GUID
if(state == DriveState.Deploying) {
log.info(s"DeployRequest: $obj transitioning to deploy state")
sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, obj.Position)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, obj.Position))
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
context.system.scheduler.scheduleOnce(obj.DeployTime milliseconds, obj.Actor, Deployment.TryDeploy(DriveState.Deployed))
}
else if(state == DriveState.Deployed) {
log.info(s"DeployRequest: $obj has been Deployed")
sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, obj.Position)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, obj.Position))
DeploymentActivities(obj)
//...
}
else {
CanNotChangeDeployment(obj, state, "incorrect deploy state")
}
case Deployment.CanUndeploy(obj, state) =>
val vehicle_guid = obj.GUID
if(state == DriveState.Undeploying) {
log.info(s"DeployRequest: $obj transitioning to undeploy state")
sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
context.system.scheduler.scheduleOnce(obj.UndeployTime milliseconds, obj.Actor, Deployment.TryUndeploy(DriveState.Mobile))
}
else if(state == DriveState.Mobile) {
log.info(s"DeployRequest: $obj is Mobile")
sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
//...
}
else {
CanNotChangeDeployment(obj, state, "incorrect undeploy state")
}
case Deployment.CanNotChangeDeployment(obj, state, reason) =>
CanNotChangeDeployment(obj, state, reason)
case Door.DoorMessage(tplayer, msg, order) => case Door.DoorMessage(tplayer, msg, order) =>
val door_guid = msg.object_guid val door_guid = msg.object_guid
order match { order match {
@ -2096,24 +2146,20 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
} }
case msg @ DeployRequestMessage(player_guid, entity, unk1, unk2, unk3, pos) => case msg @ DeployRequestMessage(player_guid, vehicle_guid, deploy_state, unk2, unk3, pos) =>
log.info("DeployRequest: " + msg) log.info(s"DeployRequest: $msg")
//LOCAL FUNCTIONALITY ONLY FOR THIS BRANCH if(player.VehicleOwned == Some(vehicle_guid) && player.VehicleOwned == player.VehicleSeated) {
val player_guid = player.GUID continent.GUID(vehicle_guid) match {
if(unk1 == 2) { // deploy AMS case Some(obj : Vehicle) =>
//sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(entity,49,1))) // TODO : ANT ? With increment when loading NTU ? obj.Actor ! Deployment.TryDeploymentChange(deploy_state)
sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player_guid, entity, unk1, unk2, unk3, Vector3(0f, 0f, 0f))))
Thread.sleep(1000) // 2 seconds case _ =>
sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player_guid, entity, 3, unk2, unk3, Vector3(0f, 0f, 0f)))) log.error(s"DeployRequest: can not find $vehicle_guid in scope; removing ownership to mitigate confusion")
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(entity, 10, 1))) player.VehicleOwned = None
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(entity, 11, 1))) }
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(entity, 12, 1)))
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(entity, 13, 1)))
} }
else if(unk1 == 1) { // undeploy AMS else {
sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player_guid, entity, unk1, unk2, unk3, Vector3(0f, 0f, 0f)))) log.warn(s"DeployRequest: $player does not own the deploying $vehicle_guid object")
Thread.sleep(1000) // 2 seconds
sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player_guid, entity, 0, unk2, unk3, Vector3(0f, 0f, 0f))))
} }
case msg @ AvatarGrenadeStateMessage(player_guid, state) => case msg @ AvatarGrenadeStateMessage(player_guid, state) =>
@ -2607,12 +2653,18 @@ class WorldSessionActor extends Actor with MDCContextAware {
/** /**
* Temporary function that iterates over vehicle permissions and turns them into `PlanetsideAttributeMessage` packets.<br> * Temporary function that iterates over vehicle permissions and turns them into `PlanetsideAttributeMessage` packets.<br>
* <br> * <br>
* 2 November 2017 * 2 November 2017:<br>
* Unexpected behavior causes seat mount points to become blocked when a new driver claims the vehicle. * Unexpected behavior causes seat mount points to become blocked when a new driver claims the vehicle.
* For the purposes of ensuring that other players are always aware of the proper permission state of the trunk and seats, * For the purposes of ensuring that other players are always aware of the proper permission state of the trunk and seats,
* packets are intentionally dispatched to the current client to update the states. * packets are intentionally dispatched to the current client to update the states.
* Perform this action just after any instance where the client would initially gain awareness of the vehicle. * Perform this action just after any instance where the client would initially gain awareness of the vehicle.
* The most important examples include either the player or the vehicle itself spawning in for the first time. * The most important examples include either the player or the vehicle itself spawning in for the first time.<br>
* <br>
* 20 February 2018:<br>
* Occasionally, during deployment, local(?) vehicle seat access permissions may change.
* This results in players being locked into their own vehicle.
* Reloading vehicle permissions supposedly ensures the seats will be properly available.
* This is considered a client issue; but, somehow, it also impacts server operation somehow.
* @param vehicle the `Vehicle` * @param vehicle the `Vehicle`
*/ */
def ReloadVehicleAccessPermissions(vehicle : Vehicle) : Unit = { def ReloadVehicleAccessPermissions(vehicle : Vehicle) : Unit = {
@ -2964,6 +3016,38 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
} }
/**
* Perform specific operations depending on the target of deployment.
* @param obj the object that has deployed
*/
def DeploymentActivities(obj : Deployment.DeploymentObject) : Unit = {
obj match {
case vehicle : Vehicle =>
//TODO we should not have to do this imho
ReloadVehicleAccessPermissions(vehicle)
case _ => ;
}
}
/**
* Common reporting behavior when a `Deployment` object fails to properly transition between states.
* @param obj the game object that could not
* @param state the `DriveState` that could not be promoted
* @param reason a string explaining why the state can not or will not change
*/
def CanNotChangeDeployment(obj : PlanetSideServerObject with Deployment, state : DriveState.Value, reason : String) : Unit = {
val mobileShift : String = if(obj.DeploymentState != DriveState.Mobile) {
obj.DeploymentState = DriveState.Mobile
sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player.GUID, obj.GUID, DriveState.Mobile, 0, false, Vector3.Zero)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, obj.GUID, DriveState.Mobile, 0, false, Vector3.Zero))
"; enforcing Mobile deployment state"
}
else {
""
}
log.error(s"DeployRequest: $obj can not transition to $state - $reason$mobileShift")
}
def failWithError(error : String) = { def failWithError(error : String) = {
log.error(error) log.error(error)
sendResponse(PacketCoding.CreateControlPacket(ConnectionClose())) sendResponse(PacketCoding.CreateControlPacket(ConnectionClose()))

View file

@ -6,13 +6,14 @@ import net.psforever.objects.equipment.Equipment
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.ConstructorData import net.psforever.packet.game.objectcreate.ConstructorData
import net.psforever.types.Vector3 import net.psforever.types.{DriveState, Vector3}
object VehicleAction { object VehicleAction {
trait Action trait Action
final case class Awareness(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID) extends Action final case class Awareness(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID) extends Action
final case class ChildObjectState(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, pitch : Float, yaw : Float) extends Action final case class ChildObjectState(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, pitch : Float, yaw : Float) extends Action
final case class DeployRequest(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, state : DriveState.Value, unk1 : Int, unk2 : Boolean, pos : Vector3) extends Action
final case class DismountVehicle(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean) extends Action final case class DismountVehicle(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean) extends Action
final case class InventoryState(player_guid : PlanetSideGUID, obj : PlanetSideGameObject, parent_guid : PlanetSideGUID, start : Int, con_data : ConstructorData) extends Action final case class InventoryState(player_guid : PlanetSideGUID, obj : PlanetSideGameObject, parent_guid : PlanetSideGUID, start : Int, con_data : ConstructorData) extends Action
final case class KickPassenger(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Action final case class KickPassenger(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Action

View file

@ -4,13 +4,14 @@ package services.vehicle
import net.psforever.objects.{PlanetSideGameObject, Vehicle} import net.psforever.objects.{PlanetSideGameObject, Vehicle}
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.ConstructorData import net.psforever.packet.game.objectcreate.ConstructorData
import net.psforever.types.Vector3 import net.psforever.types.{DriveState, Vector3}
object VehicleResponse { object VehicleResponse {
trait Response trait Response
final case class Awareness(vehicle_guid : PlanetSideGUID) extends Response final case class Awareness(vehicle_guid : PlanetSideGUID) extends Response
final case class ChildObjectState(object_guid : PlanetSideGUID, pitch : Float, yaw : Float) extends Response final case class ChildObjectState(object_guid : PlanetSideGUID, pitch : Float, yaw : Float) extends Response
final case class DeployRequest(object_guid : PlanetSideGUID, state : DriveState.Value, unk1 : Int, unk2 : Boolean, pos : Vector3) extends Response
final case class DismountVehicle(unk1 : Int, unk2 : Boolean) extends Response final case class DismountVehicle(unk1 : Int, unk2 : Boolean) extends Response
final case class InventoryState(obj : PlanetSideGameObject, parent_guid : PlanetSideGUID, start : Int, con_data : ConstructorData) extends Response final case class InventoryState(obj : PlanetSideGameObject, parent_guid : PlanetSideGUID, start : Int, con_data : ConstructorData) extends Response
final case class KickPassenger(unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Response final case class KickPassenger(unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Response

View file

@ -2,8 +2,6 @@
package services.vehicle package services.vehicle
import akka.actor.{Actor, ActorRef, Props} import akka.actor.{Actor, ActorRef, Props}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.ConstructorData
import services.vehicle.support.{DeconstructionActor, DelayedDeconstructionActor, VehicleContextActor} import services.vehicle.support.{DeconstructionActor, DelayedDeconstructionActor, VehicleContextActor}
import services.{GenericEventBus, Service} import services.{GenericEventBus, Service}
@ -49,14 +47,18 @@ class VehicleService extends Actor {
VehicleEvents.publish( VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.ChildObjectState(object_guid, pitch, yaw)) VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.ChildObjectState(object_guid, pitch, yaw))
) )
case VehicleAction.InventoryState(player_guid, obj, parent_guid, start, con_data) => case VehicleAction.DeployRequest(player_guid, object_guid, state, unk1, unk2, pos) =>
VehicleEvents.publish( VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.InventoryState(obj, parent_guid, start, con_data)) VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.DeployRequest(object_guid, state, unk1, unk2, pos))
) )
case VehicleAction.DismountVehicle(player_guid, unk1, unk2) => case VehicleAction.DismountVehicle(player_guid, unk1, unk2) =>
VehicleEvents.publish( VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.DismountVehicle(unk1, unk2)) VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.DismountVehicle(unk1, unk2))
) )
case VehicleAction.InventoryState(player_guid, obj, parent_guid, start, con_data) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.InventoryState(obj, parent_guid, start, con_data))
)
case VehicleAction.KickPassenger(player_guid, unk1, unk2, vehicle_guid) => case VehicleAction.KickPassenger(player_guid, unk1, unk2, vehicle_guid) =>
VehicleEvents.publish( VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickPassenger(unk1, unk2, vehicle_guid)) VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickPassenger(unk1, unk2, vehicle_guid))

View file

@ -6,7 +6,7 @@ import net.psforever.types.{CharacterGender, ExoSuitType, PlanetSideEmpire, Vect
import services.Service import services.Service
import services.avatar._ import services.avatar._
class AvatarService0Test extends ActorTest { class AvatarService1Test extends ActorTest {
"AvatarService" should { "AvatarService" should {
"construct" in { "construct" in {
system.actorOf(Props[AvatarService], "service") system.actorOf(Props[AvatarService], "service")
@ -15,7 +15,7 @@ class AvatarService0Test extends ActorTest {
} }
} }
class AvatarService1_1Test extends ActorTest { class AvatarService2Test extends ActorTest {
"AvatarService" should { "AvatarService" should {
"subscribe" in { "subscribe" in {
val service = system.actorOf(Props[AvatarService], "service") val service = system.actorOf(Props[AvatarService], "service")
@ -25,9 +25,9 @@ class AvatarService1_1Test extends ActorTest {
} }
} }
class AvatarService1_2Test extends ActorTest { class AvatarService3Test extends ActorTest {
"AvatarService" should { "AvatarService" should {
"subscribe" in { "subscribe to a specific channel" in {
val service = system.actorOf(Props[AvatarService], "service") val service = system.actorOf(Props[AvatarService], "service")
service ! Service.Join("test") service ! Service.Join("test")
service ! Service.Leave() service ! Service.Leave()
@ -36,7 +36,7 @@ class AvatarService1_2Test extends ActorTest {
} }
} }
class AvatarService1_3Test extends ActorTest { class AvatarService4Test extends ActorTest {
"AvatarService" should { "AvatarService" should {
"subscribe" in { "subscribe" in {
val service = system.actorOf(Props[AvatarService], "service") val service = system.actorOf(Props[AvatarService], "service")
@ -47,7 +47,7 @@ class AvatarService1_3Test extends ActorTest {
} }
} }
class AvatarService2Test extends ActorTest { class AvatarService5Test extends ActorTest {
"AvatarService" should { "AvatarService" should {
"pass an unhandled message" in { "pass an unhandled message" in {
val service = system.actorOf(Props[AvatarService], "service") val service = system.actorOf(Props[AvatarService], "service")
@ -58,7 +58,7 @@ class AvatarService2Test extends ActorTest {
} }
} }
class AvatarService3Test extends ActorTest { class ArmorChangedTest extends ActorTest {
"AvatarService" should { "AvatarService" should {
"pass ArmorChanged" in { "pass ArmorChanged" in {
val service = system.actorOf(Props[AvatarService], "service") val service = system.actorOf(Props[AvatarService], "service")
@ -69,7 +69,7 @@ class AvatarService3Test extends ActorTest {
} }
} }
class AvatarService4Test extends ActorTest { class ConcealPlayerTest extends ActorTest {
"AvatarService" should { "AvatarService" should {
"pass ConcealPlayer" in { "pass ConcealPlayer" in {
val service = system.actorOf(Props[AvatarService], "service") val service = system.actorOf(Props[AvatarService], "service")
@ -80,7 +80,7 @@ class AvatarService4Test extends ActorTest {
} }
} }
class AvatarService5Test extends ActorTest { class EquipmentInHandTest extends ActorTest {
val tool = Tool(GlobalDefinitions.beamer) val tool = Tool(GlobalDefinitions.beamer)
"AvatarService" should { "AvatarService" should {
@ -93,21 +93,23 @@ class AvatarService5Test extends ActorTest {
} }
} }
class AvatarService6Test extends ActorTest { class EquipmentOnGroundTest extends ActorTest {
val toolDef = GlobalDefinitions.beamer val toolDef = GlobalDefinitions.beamer
val tool = Tool(toolDef) val tool = Tool(toolDef)
tool.AmmoSlots.head.Box.GUID = PlanetSideGUID(1)
val cdata = toolDef.Packet.ConstructorData(tool).get
"AvatarService" should { "AvatarService" should {
"pass EquipmentOnGround" in { "pass EquipmentOnGround" in {
val service = system.actorOf(Props[AvatarService], "service") val service = system.actorOf(Props[AvatarService], "service")
service ! Service.Join("test") service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.EquipmentOnGround(PlanetSideGUID(10), Vector3(300f, 200f, 100f), Vector3(450f, 300f, 150f), toolDef.ObjectId, PlanetSideGUID(11), toolDef.Packet.ConstructorData(tool).get)) service ! AvatarServiceMessage("test", AvatarAction.EquipmentOnGround(PlanetSideGUID(10), Vector3(300f, 200f, 100f), Vector3(450f, 300f, 150f), toolDef.ObjectId, PlanetSideGUID(11), cdata))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.EquipmentOnGround(Vector3(300f, 200f, 100f), Vector3(450f, 300f, 150f), toolDef.ObjectId, PlanetSideGUID(11), toolDef.Packet.ConstructorData(tool).get))) expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.EquipmentOnGround(Vector3(300f, 200f, 100f), Vector3(450f, 300f, 150f), toolDef.ObjectId, PlanetSideGUID(11), cdata)))
} }
} }
} }
class AvatarService7Test extends ActorTest { class LoadPlayerTest extends ActorTest {
val obj = Player("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, 1) val obj = Player("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, 1)
obj.GUID = PlanetSideGUID(10) obj.GUID = PlanetSideGUID(10)
obj.Slot(5).Equipment.get.GUID = PlanetSideGUID(11) obj.Slot(5).Equipment.get.GUID = PlanetSideGUID(11)
@ -123,7 +125,7 @@ class AvatarService7Test extends ActorTest {
} }
} }
class AvatarService8Test extends ActorTest { class ObjectDeleteTest extends ActorTest {
"AvatarService" should { "AvatarService" should {
"pass ObjectDelete" in { "pass ObjectDelete" in {
val service = system.actorOf(Props[AvatarService], "service") val service = system.actorOf(Props[AvatarService], "service")
@ -137,7 +139,7 @@ class AvatarService8Test extends ActorTest {
} }
} }
class AvatarService9Test extends ActorTest { class ObjectHeldTest extends ActorTest {
"AvatarService" should { "AvatarService" should {
"pass ObjectHeld" in { "pass ObjectHeld" in {
val service = system.actorOf(Props[AvatarService], "service") val service = system.actorOf(Props[AvatarService], "service")
@ -148,7 +150,7 @@ class AvatarService9Test extends ActorTest {
} }
} }
class AvatarServiceATest extends ActorTest { class PlanetsideAttributeTest extends ActorTest {
"AvatarService" should { "AvatarService" should {
"pass PlanetsideAttribute" in { "pass PlanetsideAttribute" in {
val service = system.actorOf(Props[AvatarService], "service") val service = system.actorOf(Props[AvatarService], "service")
@ -159,7 +161,7 @@ class AvatarServiceATest extends ActorTest {
} }
} }
class AvatarServiceBTest extends ActorTest { class PlayerStateTest extends ActorTest {
val msg = PlayerStateMessageUpstream(PlanetSideGUID(75), Vector3(3694.1094f, 2735.4531f, 90.84375f), Some(Vector3(4.375f, 2.59375f, 0.0f)), 61.875f, 351.5625f, 0.0f, 136, 0, false, false, false, false, 112, 0) val msg = PlayerStateMessageUpstream(PlanetSideGUID(75), Vector3(3694.1094f, 2735.4531f, 90.84375f), Some(Vector3(4.375f, 2.59375f, 0.0f)), 61.875f, 351.5625f, 0.0f, 136, 0, false, false, false, false, 112, 0)
"AvatarService" should { "AvatarService" should {
@ -172,7 +174,7 @@ class AvatarServiceBTest extends ActorTest {
} }
} }
class AvatarServiceCTest extends ActorTest { class ReloadTest extends ActorTest {
"AvatarService" should { "AvatarService" should {
"pass Reload" in { "pass Reload" in {
val service = system.actorOf(Props[AvatarService], "service") val service = system.actorOf(Props[AvatarService], "service")
@ -183,7 +185,7 @@ class AvatarServiceCTest extends ActorTest {
} }
} }
class AvatarServiceDTest extends ActorTest { class ChangeAmmoTest extends ActorTest {
val ammoDef = GlobalDefinitions.energy_cell val ammoDef = GlobalDefinitions.energy_cell
val ammoBox = AmmoBox(ammoDef) val ammoBox = AmmoBox(ammoDef)
@ -197,7 +199,7 @@ class AvatarServiceDTest extends ActorTest {
} }
} }
class AvatarServiceETest extends ActorTest { class ChangeFireModeTest extends ActorTest {
val ammoDef = GlobalDefinitions.energy_cell val ammoDef = GlobalDefinitions.energy_cell
val ammoBox = AmmoBox(ammoDef) val ammoBox = AmmoBox(ammoDef)
@ -211,7 +213,7 @@ class AvatarServiceETest extends ActorTest {
} }
} }
class AvatarServiceF_1Test extends ActorTest { class ChangeFireStateStartTest extends ActorTest {
"AvatarService" should { "AvatarService" should {
"pass ChangeFireState_Start" in { "pass ChangeFireState_Start" in {
val service = system.actorOf(Props[AvatarService], "service") val service = system.actorOf(Props[AvatarService], "service")
@ -222,7 +224,7 @@ class AvatarServiceF_1Test extends ActorTest {
} }
} }
class AvatarServiceF_2Test extends ActorTest { class ChangeFireStateStopTest extends ActorTest {
"AvatarService" should { "AvatarService" should {
"pass ChangeFireState_Stop" in { "pass ChangeFireState_Stop" in {
val service = system.actorOf(Props[AvatarService], "service") val service = system.actorOf(Props[AvatarService], "service")
@ -233,7 +235,7 @@ class AvatarServiceF_2Test extends ActorTest {
} }
} }
class AvatarService01Test extends ActorTest { class WeaponDryFireTest extends ActorTest {
"AvatarService" should { "AvatarService" should {
"pass WeaponDryFire" in { "pass WeaponDryFire" in {
val service = system.actorOf(Props[AvatarService], "service") val service = system.actorOf(Props[AvatarService], "service")

View file

@ -0,0 +1,239 @@
// Copyright (c) 2017 PSForever
import akka.actor.Props
import net.psforever.objects._
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types._
import services.{Service, ServiceManager}
import services.vehicle._
class VehicleService1Test extends ActorTest {
ServiceManager.boot(system)
"VehicleService" should {
"construct" in {
system.actorOf(Props[VehicleService], "v-service")
assert(true)
}
}
}
class VehicleService2Test extends ActorTest {
ServiceManager.boot(system)
"VehicleService" should {
"subscribe" in {
val service = system.actorOf(Props[VehicleService], "v-service")
service ! Service.Join("test")
assert(true)
}
}
}
class VehicleService3Test extends ActorTest {
ServiceManager.boot(system)
"VehicleService" should {
"subscribe to a specific channel" in {
val service = system.actorOf(Props[VehicleService], "v-service")
service ! Service.Join("test")
service ! Service.Leave()
assert(true)
}
}
}
class VehicleService4Test extends ActorTest {
ServiceManager.boot(system)
"VehicleService" should {
"subscribe" in {
val service = system.actorOf(Props[VehicleService], "v-service")
service ! Service.Join("test")
service ! Service.LeaveAll()
assert(true)
}
}
}
class VehicleService5Test extends ActorTest {
ServiceManager.boot(system)
"VehicleService" should {
"pass an unhandled message" in {
val service = system.actorOf(Props[VehicleService], "v-service")
service ! Service.Join("test")
service ! "hello"
expectNoMsg()
}
}
}
class AwarenessTest extends ActorTest {
ServiceManager.boot(system)
"VehicleService" should {
"pass Awareness" in {
val service = system.actorOf(Props[VehicleService], "v-service")
service ! Service.Join("test")
service ! VehicleServiceMessage("test", VehicleAction.Awareness(PlanetSideGUID(10), PlanetSideGUID(11)))
expectMsg(VehicleServiceResponse("/test/Vehicle", PlanetSideGUID(10), VehicleResponse.Awareness(PlanetSideGUID(11))))
}
}
}
class ChildObjectStateTest extends ActorTest {
ServiceManager.boot(system)
"VehicleService" should {
"pass ChildObjectState" in {
val service = system.actorOf(Props[VehicleService], "v-service")
service ! Service.Join("test")
service ! VehicleServiceMessage("test", VehicleAction.ChildObjectState(PlanetSideGUID(10), PlanetSideGUID(11), 1.2f, 3.4f))
expectMsg(VehicleServiceResponse("/test/Vehicle", PlanetSideGUID(10), VehicleResponse.ChildObjectState(PlanetSideGUID(11), 1.2f, 3.4f)))
}
}
}
class DeployRequestTest extends ActorTest {
ServiceManager.boot(system)
"VehicleService" should {
"pass DeployRequest" in {
val service = system.actorOf(Props[VehicleService], "v-service")
service ! Service.Join("test")
service ! VehicleServiceMessage("test", VehicleAction.DeployRequest(PlanetSideGUID(10), PlanetSideGUID(11), DriveState.Mobile, 0, false, Vector3(1.2f, 3.4f, 5.6f)))
expectMsg(VehicleServiceResponse("/test/Vehicle", PlanetSideGUID(10), VehicleResponse.DeployRequest(PlanetSideGUID(11), DriveState.Mobile, 0, false, Vector3(1.2f, 3.4f, 5.6f))))
}
}
}
class DismountVehicleTest extends ActorTest {
ServiceManager.boot(system)
"VehicleService" should {
"pass DismountVehicle" in {
val service = system.actorOf(Props[VehicleService], "v-service")
service ! Service.Join("test")
service ! VehicleServiceMessage("test", VehicleAction.DismountVehicle(PlanetSideGUID(10), 0, false))
expectMsg(VehicleServiceResponse("/test/Vehicle", PlanetSideGUID(10), VehicleResponse.DismountVehicle(0, false)))
}
}
}
class InventoryStateTest extends ActorTest {
ServiceManager.boot(system)
val tool = Tool(GlobalDefinitions.beamer)
tool.AmmoSlots.head.Box.GUID = PlanetSideGUID(13)
val cdata = tool.Definition.Packet.ConstructorData(tool).get
"VehicleService" should {
"pass InventoryState" in {
val service = system.actorOf(Props[VehicleService], "v-service")
service ! Service.Join("test")
service ! VehicleServiceMessage("test", VehicleAction.InventoryState(PlanetSideGUID(10), tool, PlanetSideGUID(11), 0, cdata))
expectMsg(VehicleServiceResponse("/test/Vehicle", PlanetSideGUID(10), VehicleResponse.InventoryState(tool, PlanetSideGUID(11), 0, cdata)))
}
}
}
class KickPassengerTest extends ActorTest {
ServiceManager.boot(system)
"VehicleService" should {
"pass KickPassenger" in {
val service = system.actorOf(Props[VehicleService], "v-service")
service ! Service.Join("test")
service ! VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(10), 0, false, PlanetSideGUID(11)))
expectMsg(VehicleServiceResponse("/test/Vehicle", PlanetSideGUID(10), VehicleResponse.KickPassenger(0, false, PlanetSideGUID(11))))
}
}
}
class LoadVehicleTest extends ActorTest {
ServiceManager.boot(system)
val vehicle = Vehicle(GlobalDefinitions.quadstealth)
val cdata = vehicle.Definition.Packet.ConstructorData(vehicle).get
"VehicleService" should {
"pass LoadVehicle" in {
val service = system.actorOf(Props[VehicleService], "v-service")
service ! Service.Join("test")
service ! VehicleServiceMessage("test", VehicleAction.LoadVehicle(PlanetSideGUID(10), vehicle, 12, PlanetSideGUID(11), cdata))
expectMsg(VehicleServiceResponse("/test/Vehicle", PlanetSideGUID(10), VehicleResponse.LoadVehicle(vehicle, 12, PlanetSideGUID(11), cdata)))
}
}
}
class MountVehicleTest extends ActorTest {
ServiceManager.boot(system)
"VehicleService" should {
"pass MountVehicle" in {
val service = system.actorOf(Props[VehicleService], "v-service")
service ! Service.Join("test")
service ! VehicleServiceMessage("test", VehicleAction.MountVehicle(PlanetSideGUID(10), PlanetSideGUID(11), 0))
expectMsg(VehicleServiceResponse("/test/Vehicle", PlanetSideGUID(10), VehicleResponse.MountVehicle(PlanetSideGUID(11), 0)))
}
}
}
class SeatPermissionsTest extends ActorTest {
ServiceManager.boot(system)
"VehicleService" should {
"pass SeatPermissions" in {
val service = system.actorOf(Props[VehicleService], "v-service")
service ! Service.Join("test")
service ! VehicleServiceMessage("test", VehicleAction.SeatPermissions(PlanetSideGUID(10), PlanetSideGUID(11), 0, 12L))
expectMsg(VehicleServiceResponse("/test/Vehicle", PlanetSideGUID(10), VehicleResponse.SeatPermissions(PlanetSideGUID(11), 0, 12L)))
}
}
}
class StowEquipmentTest extends ActorTest {
ServiceManager.boot(system)
val tool = Tool(GlobalDefinitions.beamer)
tool.GUID = PlanetSideGUID(12)
tool.AmmoSlots.head.Box.GUID = PlanetSideGUID(13)
val toolDef = tool.Definition
val cdata = tool.Definition.Packet.DetailedConstructorData(tool).get
"StowEquipment" should {
"pass StowEquipment" in {
val service = system.actorOf(Props[VehicleService], "v-service")
service ! Service.Join("test")
service ! VehicleServiceMessage("test", VehicleAction.StowEquipment(PlanetSideGUID(10), PlanetSideGUID(11), 0, tool))
expectMsg(VehicleServiceResponse("/test/Vehicle", PlanetSideGUID(10), VehicleResponse.StowEquipment(PlanetSideGUID(11), 0, toolDef.ObjectId, tool.GUID, cdata)))
}
}
}
class UnstowEquipmentTest extends ActorTest {
ServiceManager.boot(system)
"VehicleService" should {
"pass UnstowEquipment" in {
val service = system.actorOf(Props[VehicleService], "v-service")
service ! Service.Join("test")
service ! VehicleServiceMessage("test", VehicleAction.UnstowEquipment(PlanetSideGUID(10), PlanetSideGUID(11)))
expectMsg(VehicleServiceResponse("/test/Vehicle", PlanetSideGUID(10), VehicleResponse.UnstowEquipment(PlanetSideGUID(11))))
}
}
}
class VehicleStateTest extends ActorTest {
ServiceManager.boot(system)
"VehicleService" should {
"pass VehicleState" in {
val service = system.actorOf(Props[VehicleService], "v-service")
service ! Service.Join("test")
service ! VehicleServiceMessage("test", VehicleAction.VehicleState(PlanetSideGUID(10), PlanetSideGUID(11), 0, Vector3(1.2f, 3.4f, 5.6f), Vector3(7.8f, 9.1f, 2.3f), Some(Vector3(4.5f, 6.7f, 8.9f)), Option(1), 2, 3, 4, false, true))
expectMsg(VehicleServiceResponse("/test/Vehicle", PlanetSideGUID(10), VehicleResponse.VehicleState(PlanetSideGUID(11), 0, Vector3(1.2f, 3.4f, 5.6f), Vector3(7.8f, 9.1f, 2.3f), Some(Vector3(4.5f, 6.7f, 8.9f)), Option(1), 2, 3, 4, false, true)))
}
}
}
object VehicleServiceTest {
//decoy
}