From 0fe46311adc43705bc04081c4d093c0729c890e1 Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 9 Feb 2018 19:05:16 -0500 Subject: [PATCH] Deployment: Class and Actor mixins for Deployment state. The logic is surprisingly self-contained, mostly. DriveState: This is not the former DriveState Enumeration of /packet/game/objectcreate/. This is now a /types/ Enumeration shared across /common/ objects and serves the functionality of both, at least to the extent that it is understood. Functions are includes that define the logic order or state changes and divides states into (two) groups. VehicleService: The directory /pslogin/src/test has been created and tests have been migrated there. Originally, the tests were located in the wrong place and were being skipped when not executed manually. They should now appear in coverage reports and be run as a part of continuous integration. --- .codecov.yml | 1 + .../psforever/objects/GlobalDefinitions.scala | 15 ++ .../scala/net/psforever/objects/Vehicle.scala | 24 +- .../definition/VehicleDefinition.scala | 24 ++ .../converter/VehicleConverter.scala | 2 +- .../serverobject/deploy/Deployment.scala | 106 +++++++++ .../deploy/DeploymentBehavior.scala | 58 +++++ .../objects/vehicles/VehicleControl.scala | 15 +- .../packet/game/DeployRequestMessage.scala | 11 +- .../packet/game/objectcreate/Prefab.scala | 2 +- .../game/objectcreate/VehicleData.scala | 8 +- .../objectcreate => types}/DriveState.scala | 22 +- .../scala/game/DeployRequestMessageTest.scala | 9 +- .../test/scala/objects/DeploymentTest.scala | 197 +++++++++++++++++ .../src/main/scala/WorldSessionActor.scala | 98 +++++++-- .../services/vehicle/VehicleAction.scala | 3 +- .../services/vehicle/VehicleResponse.scala | 3 +- .../services/vehicle/VehicleService.scala | 10 +- .../src/{main => }/test/scala/ActorTest.scala | 0 .../test/scala/AvatarServiceTest.scala | 48 ++-- .../test/scala/PacketCodingActorTest.scala | 0 .../src/test/scala/VehicleServiceTest.scala | 208 ++++++++++++++++++ 22 files changed, 778 insertions(+), 86 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/deploy/Deployment.scala create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/deploy/DeploymentBehavior.scala rename common/src/main/scala/net/psforever/{packet/game/objectcreate => types}/DriveState.scala (62%) create mode 100644 common/src/test/scala/objects/DeploymentTest.scala rename pslogin/src/{main => }/test/scala/ActorTest.scala (100%) rename pslogin/src/{main => }/test/scala/AvatarServiceTest.scala (90%) rename pslogin/src/{main => }/test/scala/PacketCodingActorTest.scala (100%) create mode 100644 pslogin/src/test/scala/VehicleServiceTest.scala diff --git a/.codecov.yml b/.codecov.yml index 75bed4f2..959d9a66 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -25,6 +25,7 @@ ignore: - "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/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/ExoSuitType.scala" - "common/src/main/scala/net/psforever/types/GrenadeState.scala" diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index edc10560..e16159b5 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2347,8 +2347,14 @@ object GlobalDefinitions { ams.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax ams.MountPoints += 1 -> 0 ams.MountPoints += 2 -> 0 +<<<<<<< 27d86af015d5a835f7d594aed9ccdd1de4048c53 ams.Utilities += 3 -> UtilityType.order_terminala ams.Utilities += 4 -> UtilityType.order_terminalb +======= + ams.Deployment = true + ams.DeployTime = 2000 + ams.UndeployTime = 2000 +>>>>>>> Deployment: ams.Packet = utilityConverter val variantConverter = new VariantVehicleConverter @@ -2356,6 +2362,9 @@ object GlobalDefinitions { router.MountPoints += 1 -> 0 router.TrunkSize = InventoryTile.Tile1511 router.TrunkOffset = 30 + router.Deployment = true + router.DeployTime = 2000 + router.UndeployTime = 2000 router.Packet = variantConverter switchblade.Seats += 0 -> new SeatDefinition() @@ -2365,6 +2374,9 @@ object GlobalDefinitions { switchblade.MountPoints += 2 -> 0 switchblade.TrunkSize = InventoryTile.Tile1511 switchblade.TrunkOffset = 30 + switchblade.Deployment = true + switchblade.DeployTime = 2000 + switchblade.UndeployTime = 2000 switchblade.Packet = variantConverter flail.Seats += 0 -> new SeatDefinition() @@ -2373,6 +2385,9 @@ object GlobalDefinitions { flail.MountPoints += 1 -> 0 flail.TrunkSize = InventoryTile.Tile1511 flail.TrunkOffset = 30 + flail.Deployment = true + flail.DeployTime = 2000 + flail.UndeployTime = 2000 flail.Packet = variantConverter mosquito.Seats += 0 -> new SeatDefinition() diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala index a5ce3bbd..b15590e2 100644 --- a/common/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala @@ -7,9 +7,9 @@ import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.PlanetSideServerObject 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.packet.game.PlanetSideGUID -import net.psforever.packet.game.objectcreate.DriveState import net.psforever.types.PlanetSideEmpire import scala.annotation.tailrec @@ -22,6 +22,7 @@ import scala.annotation.tailrec * Following that are the mounted weapons and other utilities. * Trunk space starts being indexed afterwards.
*
+<<<<<<< 27d86af015d5a835f7d594aed9ccdd1de4048c53 * To keep it simple, infantry seating, mounted weapons, and utilities are stored separately.
*
* Vehicles maintain a `Map` of `Utility` objects in given index positions. @@ -32,18 +33,22 @@ import scala.annotation.tailrec * The value of the negative index does not have a specific meaning. * @see `Vehicle.EquipmentUtilities` * @param vehicleDef the vehicle's definition entry'; +======= + * To keep it simple, infantry seating, mounted weapons, and utilities are stored separately. + * @param vehicleDef the vehicle's definition entry; +>>>>>>> Deployment: * stores and unloads pertinent information about the `Vehicle`'s configuration; * used in the initialization process (`loadVehicleDefinition`) */ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServerObject with FactionAffinity with Mountable + with Deployment with Container { private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR private var owner : Option[PlanetSideGUID] = None private var health : Int = 1 private var shields : Int = 0 - private var deployed : DriveState.Value = DriveState.Mobile private var decal : Int = 0 private var trunkAccess : Option[PlanetSideGUID] = None private var jammered : Boolean = false @@ -125,17 +130,6 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ 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 = { this.decal } @@ -353,6 +347,10 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ } } + override def DeployTime = Definition.DeployTime + + override def UndeployTime = Definition.UndeployTime + def Inventory : GridInventory = trunk def Find(obj : Equipment) : Option[Int] = Find(obj.GUID) diff --git a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala index 693a1164..09d8136d 100644 --- a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala @@ -21,7 +21,13 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) { /* key - seat index (where this weapon attaches during object construction), value - the weapon on an EquipmentSlot */ private val weapons : mutable.HashMap[Int, ToolDefinition] = mutable.HashMap[Int, ToolDefinition]() private var deployment : Boolean = false +<<<<<<< 27d86af015d5a835f7d594aed9ccdd1de4048c53 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 val utilities : mutable.ArrayBuffer[Int] = mutable.ArrayBuffer[Int]() +>>>>>>> Deployment: private var trunkSize : InventoryTile = InventoryTile.None private var trunkOffset : Int = 0 private var canCloak : Boolean = false @@ -70,7 +76,25 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) { Deployment } +<<<<<<< 27d86af015d5a835f7d594aed9ccdd1de4048c53 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 Utilities : mutable.ArrayBuffer[Int] = utilities +>>>>>>> Deployment: def TrunkSize : InventoryTile = trunkSize diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala index 137ad6c0..5a0ba630 100644 --- a/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala +++ b/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala @@ -24,7 +24,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() { 0, obj.Health / obj.MaxHealth * 255, //TODO not precise false, false, - obj.Drive, + obj.DeploymentState, false, false, obj.Cloaked, diff --git a/common/src/main/scala/net/psforever/objects/serverobject/deploy/Deployment.scala b/common/src/main/scala/net/psforever/objects/serverobject/deploy/Deployment.scala new file mode 100644 index 00000000..fe2bb7c8 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/deploy/Deployment.scala @@ -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.
+ *
+ * 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 +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/deploy/DeploymentBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/deploy/DeploymentBehavior.scala new file mode 100644 index 00000000..94c1702e --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/deploy/DeploymentBehavior.scala @@ -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") + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala index ced4442c..cffb9030 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -4,7 +4,12 @@ package net.psforever.objects.vehicles import akka.actor.Actor import net.psforever.objects.Vehicle import net.psforever.objects.serverobject.mount.MountableBehavior +<<<<<<< 27d86af015d5a835f7d594aed9ccdd1de4048c53 import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} +======= +import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior +import net.psforever.objects.serverobject.deploy.DeploymentBehavior +>>>>>>> Deployment: /** * An `Actor` that handles messages being dispatched to a specific `Vehicle`.
@@ -15,18 +20,26 @@ import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffi */ class VehicleControl(vehicle : Vehicle) extends Actor with FactionAffinityBehavior.Check + with DeploymentBehavior with MountableBehavior.Mount with MountableBehavior.Dismount { +<<<<<<< 27d86af015d5a835f7d594aed9ccdd1de4048c53 //make control actors belonging to utilities when making control actor belonging to vehicle vehicle.Utilities.foreach({case (_, util) => util.Setup }) def MountableObject = vehicle //do not add type! +======= + def MountableObject = vehicle +>>>>>>> Deployment: - def FactionObject : FactionAffinity = vehicle + def FactionObject = vehicle + + def DeploymentObject = vehicle def receive : Receive = Enabled def Enabled : Receive = checkBehavior + .orElse(deployBehavior) .orElse(mountBehavior) .orElse(dismountBehavior) .orElse { diff --git a/common/src/main/scala/net/psforever/packet/game/DeployRequestMessage.scala b/common/src/main/scala/net/psforever/packet/game/DeployRequestMessage.scala index 008f96f7..0a38816a 100644 --- a/common/src/main/scala/net/psforever/packet/game/DeployRequestMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/DeployRequestMessage.scala @@ -2,7 +2,7 @@ package net.psforever.packet.game import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} -import net.psforever.types.Vector3 +import net.psforever.types.{DriveState, Vector3} import scodec.Codec import scodec.codecs._ @@ -22,8 +22,7 @@ import scodec.codecs._ * This packet has nothing to do with ACE deployables. * @param player_guid the player requesting the deployment * @param vehicle_guid the vehicle to be deployed - * @param unk1 na; - * usually 2 + * @param deploy_state either requests for a specific deployment state or assignment of the requested state * @param unk2 na; * usually 0 * @param unk3 na @@ -31,7 +30,7 @@ import scodec.codecs._ */ final case class DeployRequestMessage(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, - unk1 : Int, + deploy_state : DriveState.Value, unk2 : Int, unk3 : Boolean, pos : Vector3) @@ -44,8 +43,8 @@ final case class DeployRequestMessage(player_guid : PlanetSideGUID, object DeployRequestMessage extends Marshallable[DeployRequestMessage] { implicit val codec : Codec[DeployRequestMessage] = ( ("player_guid" | PlanetSideGUID.codec) :: - ("deploy_guid" | PlanetSideGUID.codec) :: - ("unk1" | uint(3)) :: + ("vehicle_guid" | PlanetSideGUID.codec) :: + ("deploy_state" | DriveState.codec) :: ("unk2" | uint(5)) :: ("unk3" | bool) :: ("pos" | Vector3.codec_pos) diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala index 54a6fff4..a424d02b 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala @@ -2,7 +2,7 @@ package net.psforever.packet.game.objectcreate 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. diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala index 8292a5b2..34eac81d 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala @@ -1,11 +1,13 @@ // Copyright (c) 2017 PSForever 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, Codec, Err} import scodec.codecs._ 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. */ @@ -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) } + private val driveState8u = PacketHelpers.createEnumerationCodec(DriveState, uint8L) + /** * `Codec` for the "utility" format. */ @@ -192,7 +196,7 @@ object VehicleData extends Marshallable[VehicleData] { ("health" | uint8L) :: ("unk2" | bool) :: //usually 0 ("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 ("unk4" | bool) :: ("cloak" | bool) :: //cloak as wraith, phantasm diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DriveState.scala b/common/src/main/scala/net/psforever/types/DriveState.scala similarity index 62% rename from common/src/main/scala/net/psforever/packet/game/objectcreate/DriveState.scala rename to common/src/main/scala/net/psforever/types/DriveState.scala index fd685b73..e32c774a 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/DriveState.scala +++ b/common/src/main/scala/net/psforever/types/DriveState.scala @@ -1,26 +1,24 @@ -// Copyright (c) 2017 PSForever -package net.psforever.packet.game.objectcreate +package net.psforever.types import net.psforever.packet.PacketHelpers -import scodec.codecs._ +import scodec.codecs.uint /** * An `Enumeration` of the mobility states of vehicles.
*
- * In general, two important mobility states exist - `Mobile` and "deployed." - * There are three stages of a formal deployment. + * In general, two important mobility states exist - `Mobile` and `Deployed`. + * 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. * If the target vehicle has no deployment behavior, a non-`Mobile` value will not affect it. */ object DriveState extends Enumeration { type Type = Value - val Mobile = Value(0) //drivable - val Undeployed = Value(1) //stationary - val Unavailable = Value(2) //stationary, partial activation - val Deployed = Value(3) //stationary, full activation - + val Mobile = Value(0) + val Undeploying = Value(1) + val Deploying = Value(2) + val Deployed = Value(3) val State7 = Value(7) //unknown; not encountered on a vehicle that can deploy; functions like Mobile - implicit val codec = PacketHelpers.createEnumerationCodec(this, uint8L) -} \ No newline at end of file + implicit val codec = PacketHelpers.createEnumerationCodec(this, uint(3)) +} diff --git a/common/src/test/scala/game/DeployRequestMessageTest.scala b/common/src/test/scala/game/DeployRequestMessageTest.scala index 6d10e911..69139812 100644 --- a/common/src/test/scala/game/DeployRequestMessageTest.scala +++ b/common/src/test/scala/game/DeployRequestMessageTest.scala @@ -4,7 +4,7 @@ package game import org.specs2.mutable._ import net.psforever.packet._ import net.psforever.packet.game._ -import net.psforever.types.Vector3 +import net.psforever.types.{DriveState, Vector3} import scodec.bits._ class DeployRequestMessageTest extends Specification { @@ -12,10 +12,10 @@ class DeployRequestMessageTest extends Specification { "decode" in { 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) vehicle_guid mustEqual PlanetSideGUID(380) - unk1 mustEqual 2 + deploy_state mustEqual DriveState.Deploying unk2 mustEqual 0 unk3 mustEqual false pos.x mustEqual 4060.1953f @@ -30,7 +30,8 @@ class DeployRequestMessageTest extends Specification { val msg = DeployRequestMessage( PlanetSideGUID(75), PlanetSideGUID(380), - 2, 0, false, + DriveState.Deploying, + 0, false, Vector3(4060.1953f, 2218.8281f, 155.32812f) ) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector diff --git a/common/src/test/scala/objects/DeploymentTest.scala b/common/src/test/scala/objects/DeploymentTest.scala new file mode 100644 index 00000000..8321149e --- /dev/null +++ b/common/src/test/scala/objects/DeploymentTest.scala @@ -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 + } +} diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index d0e4826e..9cb0057c 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -16,6 +16,7 @@ import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} import net.psforever.objects.serverobject.mount.Mountable 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.doors.Door 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))) } + 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) => if(player.GUID != guid) { //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? @@ -399,6 +405,49 @@ class WorldSessionActor extends Actor with MDCContextAware { 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)) + //... + } + 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) => val door_guid = msg.object_guid order match { @@ -2096,24 +2145,20 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - case msg @ DeployRequestMessage(player_guid, entity, unk1, unk2, unk3, pos) => - log.info("DeployRequest: " + msg) - //LOCAL FUNCTIONALITY ONLY FOR THIS BRANCH - val player_guid = player.GUID - if(unk1 == 2) { // deploy AMS - //sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(entity,49,1))) // TODO : ANT ? With increment when loading NTU ? - sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player_guid, entity, unk1, unk2, unk3, Vector3(0f, 0f, 0f)))) - Thread.sleep(1000) // 2 seconds - sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player_guid, entity, 3, unk2, unk3, Vector3(0f, 0f, 0f)))) - sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(entity, 10, 1))) - sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(entity, 11, 1))) - sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(entity, 12, 1))) - sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(entity, 13, 1))) + case msg @ DeployRequestMessage(player_guid, vehicle_guid, deploy_state, unk2, unk3, pos) => + log.info(s"DeployRequest: $msg") + if(player.VehicleOwned == Some(vehicle_guid) && player.VehicleOwned == player.VehicleSeated) { + continent.GUID(vehicle_guid) match { + case Some(obj : Vehicle) => + obj.Actor ! Deployment.TryDeploymentChange(deploy_state) + + case _ => + log.error(s"DeployRequest: can not find $vehicle_guid in scope; removing ownership to mitigate confusion") + player.VehicleOwned = None + } } - else if(unk1 == 1) { // undeploy AMS - sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player_guid, entity, unk1, unk2, unk3, Vector3(0f, 0f, 0f)))) - Thread.sleep(1000) // 2 seconds - sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player_guid, entity, 0, unk2, unk3, Vector3(0f, 0f, 0f)))) + else { + log.warn(s"DeployRequest: $player does not own the deploying $vehicle_guid object") } case msg @ AvatarGrenadeStateMessage(player_guid, state) => @@ -2964,6 +3009,25 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + /** + * 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) = { log.error(error) sendResponse(PacketCoding.CreateControlPacket(ConnectionClose())) diff --git a/pslogin/src/main/scala/services/vehicle/VehicleAction.scala b/pslogin/src/main/scala/services/vehicle/VehicleAction.scala index 5bf6a5d4..66afa834 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleAction.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleAction.scala @@ -6,13 +6,14 @@ import net.psforever.objects.equipment.Equipment import net.psforever.objects.zones.Zone import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.objectcreate.ConstructorData -import net.psforever.types.Vector3 +import net.psforever.types.{DriveState, Vector3} object VehicleAction { trait 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 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 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 diff --git a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala index 32c08412..a7bed3bc 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala @@ -4,13 +4,14 @@ package services.vehicle import net.psforever.objects.{PlanetSideGameObject, Vehicle} import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.objectcreate.ConstructorData -import net.psforever.types.Vector3 +import net.psforever.types.{DriveState, Vector3} object VehicleResponse { trait 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 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 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 diff --git a/pslogin/src/main/scala/services/vehicle/VehicleService.scala b/pslogin/src/main/scala/services/vehicle/VehicleService.scala index 9fa88ee3..26a2412e 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleService.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleService.scala @@ -2,8 +2,6 @@ package services.vehicle 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.{GenericEventBus, Service} @@ -49,14 +47,18 @@ class VehicleService extends Actor { VehicleEvents.publish( 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( - 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) => VehicleEvents.publish( 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) => VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickPassenger(unk1, unk2, vehicle_guid)) diff --git a/pslogin/src/main/test/scala/ActorTest.scala b/pslogin/src/test/scala/ActorTest.scala similarity index 100% rename from pslogin/src/main/test/scala/ActorTest.scala rename to pslogin/src/test/scala/ActorTest.scala diff --git a/pslogin/src/main/test/scala/AvatarServiceTest.scala b/pslogin/src/test/scala/AvatarServiceTest.scala similarity index 90% rename from pslogin/src/main/test/scala/AvatarServiceTest.scala rename to pslogin/src/test/scala/AvatarServiceTest.scala index d4b01f89..56e02385 100644 --- a/pslogin/src/main/test/scala/AvatarServiceTest.scala +++ b/pslogin/src/test/scala/AvatarServiceTest.scala @@ -6,7 +6,7 @@ import net.psforever.types.{CharacterGender, ExoSuitType, PlanetSideEmpire, Vect import services.Service import services.avatar._ -class AvatarService0Test extends ActorTest { +class AvatarService1Test extends ActorTest { "AvatarService" should { "construct" in { system.actorOf(Props[AvatarService], "service") @@ -15,7 +15,7 @@ class AvatarService0Test extends ActorTest { } } -class AvatarService1_1Test extends ActorTest { +class AvatarService2Test extends ActorTest { "AvatarService" should { "subscribe" in { 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 { - "subscribe" in { + "subscribe to a specific channel" in { val service = system.actorOf(Props[AvatarService], "service") service ! Service.Join("test") service ! Service.Leave() @@ -36,7 +36,7 @@ class AvatarService1_2Test extends ActorTest { } } -class AvatarService1_3Test extends ActorTest { +class AvatarService4Test extends ActorTest { "AvatarService" should { "subscribe" in { 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 { "pass an unhandled message" in { 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 { "pass ArmorChanged" in { 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 { "pass ConcealPlayer" in { 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) "AvatarService" should { @@ -93,21 +93,23 @@ class AvatarService5Test extends ActorTest { } } -class AvatarService6Test extends ActorTest { +class EquipmentOnGroundTest extends ActorTest { val toolDef = GlobalDefinitions.beamer val tool = Tool(toolDef) + tool.AmmoSlots.head.Box.GUID = PlanetSideGUID(1) + val cdata = toolDef.Packet.ConstructorData(tool).get "AvatarService" should { "pass EquipmentOnGround" in { val service = system.actorOf(Props[AvatarService], "service") 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)) - 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))) + 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), cdata))) } } } -class AvatarService7Test extends ActorTest { +class LoadPlayerTest extends ActorTest { val obj = Player("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, 1) obj.GUID = PlanetSideGUID(10) 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 { "pass ObjectDelete" in { 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 { "pass ObjectHeld" in { 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 { "pass PlanetsideAttribute" in { 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) "AvatarService" should { @@ -172,7 +174,7 @@ class AvatarServiceBTest extends ActorTest { } } -class AvatarServiceCTest extends ActorTest { +class ReloadTest extends ActorTest { "AvatarService" should { "pass Reload" in { 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 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 ammoBox = AmmoBox(ammoDef) @@ -211,7 +213,7 @@ class AvatarServiceETest extends ActorTest { } } -class AvatarServiceF_1Test extends ActorTest { +class ChangeFireStateStartTest extends ActorTest { "AvatarService" should { "pass ChangeFireState_Start" in { 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 { "pass ChangeFireState_Stop" in { 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 { "pass WeaponDryFire" in { val service = system.actorOf(Props[AvatarService], "service") diff --git a/pslogin/src/main/test/scala/PacketCodingActorTest.scala b/pslogin/src/test/scala/PacketCodingActorTest.scala similarity index 100% rename from pslogin/src/main/test/scala/PacketCodingActorTest.scala rename to pslogin/src/test/scala/PacketCodingActorTest.scala diff --git a/pslogin/src/test/scala/VehicleServiceTest.scala b/pslogin/src/test/scala/VehicleServiceTest.scala new file mode 100644 index 00000000..8803db51 --- /dev/null +++ b/pslogin/src/test/scala/VehicleServiceTest.scala @@ -0,0 +1,208 @@ +// 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 +import services.vehicle._ + +class VehicleService1Test extends ActorTest { + "VehicleService" should { + "construct" in { + system.actorOf(Props[VehicleService], "service") + assert(true) + } + } +} + +class VehicleService2Test extends ActorTest { + "VehicleService" should { + "subscribe" in { + val service = system.actorOf(Props[VehicleService], "service") + service ! Service.Join("test") + assert(true) + } + } +} + +class VehicleService3Test extends ActorTest { + "VehicleService" should { + "subscribe to a specific channel" in { + val service = system.actorOf(Props[VehicleService], "service") + service ! Service.Join("test") + service ! Service.Leave() + assert(true) + } + } +} + +class VehicleService4Test extends ActorTest { + "VehicleService" should { + "subscribe" in { + val service = system.actorOf(Props[VehicleService], "service") + service ! Service.Join("test") + service ! Service.LeaveAll() + assert(true) + } + } +} + +class VehicleService5Test extends ActorTest { + "VehicleService" should { + "pass an unhandled message" in { + val service = system.actorOf(Props[VehicleService], "service") + service ! Service.Join("test") + service ! "hello" + expectNoMsg() + } + } +} + +class AwarenessTest extends ActorTest { + "VehicleService" should { + "pass Awareness" in { + val service = system.actorOf(Props[VehicleService], "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 { + "VehicleService" should { + "pass ChildObjectState" in { + val service = system.actorOf(Props[VehicleService], "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 { + "VehicleService" should { + "pass DeployRequest" in { + val service = system.actorOf(Props[VehicleService], "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 { + "VehicleService" should { + "pass DismountVehicle" in { + val service = system.actorOf(Props[VehicleService], "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 { + 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], "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 { + "VehicleService" should { + "pass KickPassenger" in { + val service = system.actorOf(Props[VehicleService], "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 { + val vehicle = Vehicle(GlobalDefinitions.quadstealth) + val cdata = vehicle.Definition.Packet.ConstructorData(vehicle).get + + "VehicleService" should { + "pass LoadVehicle" in { + val service = system.actorOf(Props[VehicleService], "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 { + "VehicleService" should { + "pass MountVehicle" in { + val service = system.actorOf(Props[VehicleService], "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 { + "VehicleService" should { + "pass SeatPermissions" in { + val service = system.actorOf(Props[VehicleService], "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 { + 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], "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 { + "VehicleService" should { + "pass UnstowEquipment" in { + val service = system.actorOf(Props[VehicleService], "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 { + "VehicleService" should { + "pass VehicleState" in { + val service = system.actorOf(Props[VehicleService], "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 +}