diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala index 1291ca07e..229560e72 100644 --- a/common/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala @@ -31,7 +31,7 @@ import scala.annotation.tailrec *
* The importance of a vehicle's owner can not be overlooked. * The owner is someone who can control who can sit in the vehicle's seats - * either through broad categorization or discriminating sleection ("kicking") + * either through broad categorization or discriminating selection ("kicking") * and who has access to and can allow access to the vehicle's trunk capacity. * The driver is the only player that can access a vehicle's saved loadouts through a repair/rearm silo * and can procure equipment from the said silo. @@ -58,7 +58,7 @@ import scala.annotation.tailrec * and may also use their lack of visibility to express state. * In terms of individual access, each seat can have its current occupant ejected, save for the driver's seat. * @see `Vehicle.EquipmentUtilities` - * @param vehicleDef the vehicle's definition entry'; + * @param vehicleDef the vehicle's definition entry; * stores and unloads pertinent information about the `Vehicle`'s configuration; * used in the initialization process (`loadVehicleDefinition`) */ diff --git a/common/src/main/scala/net/psforever/objects/vehicles/SeatArmorRestriction.scala b/common/src/main/scala/net/psforever/objects/vehicles/SeatArmorRestriction.scala index 8558b3866..af01fb1e7 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/SeatArmorRestriction.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/SeatArmorRestriction.scala @@ -4,7 +4,7 @@ package net.psforever.objects.vehicles /** * An `Enumeration` of exo-suit-based seat access restrictions.
*
- * The default value is `NoMax` as that is the most common seat. + * The default value is `NoMax` as that is the most common seat type. * `NoReinforcedOrMax` is next most common. * `MaxOnly` is a rare seat restriction found in pairs on Galaxies and on the large "Ground Transport" vehicles. */ 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 0518cfc7d..dc114e68c 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -49,8 +49,8 @@ class VehicleControl(vehicle : Vehicle) extends Actor }) && (exosuit match { case ExoSuitType.MAX => restriction == SeatArmorRestriction.MaxOnly - case ExoSuitType.Reinforced => restriction != SeatArmorRestriction.NoReinforcedOrMax - case _ => true + case ExoSuitType.Reinforced => restriction == SeatArmorRestriction.NoMax + case _ => restriction != SeatArmorRestriction.MaxOnly }) ) { mountBehavior.apply(Mountable.TryMount(user, seat_num)) diff --git a/common/src/main/scala/net/psforever/packet/game/ObjectCreateDetailedMessage.scala b/common/src/main/scala/net/psforever/packet/game/ObjectCreateDetailedMessage.scala index 36766fbf1..7bbd21e88 100644 --- a/common/src/main/scala/net/psforever/packet/game/ObjectCreateDetailedMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/ObjectCreateDetailedMessage.scala @@ -71,29 +71,6 @@ object ObjectCreateDetailedMessage extends Marshallable[ObjectCreateDetailedMess ObjectCreateDetailedMessage(ObjectCreateBase.streamLen(None, data), objectClass, guid, None, Some(data)) } -// /** -// * Take the important information of a game piece and transform it into bit data. -// * This function is fail-safe because it catches errors involving bad parsing of the object data. -// * Generally, the `Exception` messages themselves are not useful here. -// * @param objClass the code for the type of object being deconstructed -// * @param obj the object data -// * @return the bitstream data -// * @see ObjectClass.selectDataCodec -// */ -// def encodeData(objClass : Int, obj : ConstructorData, getCodecFunc : (Int) => Codec[ConstructorData.genericPattern]) : BitVector = { -// var out = BitVector.empty -// try { -// val outOpt : Option[BitVector] = getCodecFunc(objClass).encode(Some(obj.asInstanceOf[ConstructorData])).toOption -// if(outOpt.isDefined) -// out = outOpt.get -// } -// catch { -// case _ : Exception => -// //catch and release, any sort of parse error -// } -// out -// } - implicit val codec : Codec[ObjectCreateDetailedMessage] = ObjectCreateBase.baseCodec.exmap[ObjectCreateDetailedMessage] ( { case _ :: _ :: _ :: _ :: BitVector.empty :: HNil => diff --git a/common/src/test/scala/objects/VehicleTest.scala b/common/src/test/scala/objects/VehicleTest.scala index 1467b4ae6..42a581acf 100644 --- a/common/src/test/scala/objects/VehicleTest.scala +++ b/common/src/test/scala/objects/VehicleTest.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package objects -import akka.actor.Props +import akka.actor.{ActorSystem, Props} import net.psforever.objects._ import net.psforever.objects.definition.{SeatDefinition, VehicleDefinition} import net.psforever.objects.serverobject.mount.Mountable @@ -312,7 +312,7 @@ class VehicleTest extends Specification { } } -class VehicleControl1Test extends ActorTest { +class VehicleControlStopMountingTest extends ActorTest { "Vehicle Control" should { "deactivate and stop handling mount messages" in { val player1 = Player(VehicleTest.avatar1) @@ -333,7 +333,7 @@ class VehicleControl1Test extends ActorTest { } } -class VehicleControl2Test extends ActorTest { +class VehicleControlRestartMountingTest extends ActorTest { "Vehicle Control" should { "reactivate and resume handling mount messages" in { val player1 = Player(VehicleTest.avatar1) @@ -358,6 +358,258 @@ class VehicleControl2Test extends ActorTest { } } +class VehicleControlAlwaysDismountTest extends ActorTest { + "Vehicle Control" should { + "always allow dismount messages" in { + val player1 = Player(VehicleTest.avatar1) + player1.GUID = PlanetSideGUID(1) + val player2 = Player(VehicleTest.avatar2) + player2.GUID = PlanetSideGUID(2) + val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) + vehicle.GUID = PlanetSideGUID(3) + vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") + vehicle.Actor ! Mountable.TryMount(player1, 0) + receiveOne(Duration.create(100, "ms")) //discard + vehicle.Actor ! Mountable.TryMount(player2, 1) + receiveOne(Duration.create(100, "ms")) //discard + + vehicle.Actor ! Mountable.TryDismount(player2, 1) //player2 requests dismount + val reply1 = receiveOne(Duration.create(100, "ms")) + assert(reply1.isInstanceOf[Mountable.MountMessages]) + assert(reply1.asInstanceOf[Mountable.MountMessages].response.isInstanceOf[Mountable.CanDismount]) //player2 dismounts + vehicle.Actor ! Vehicle.PrepareForDeletion + + vehicle.Actor ! Mountable.TryDismount(player1, 0) //player1 requests dismount + val reply2 = receiveOne(Duration.create(100, "ms")) + assert(reply2.isInstanceOf[Mountable.MountMessages]) + assert(reply2.asInstanceOf[Mountable.MountMessages].response.isInstanceOf[Mountable.CanDismount]) //player1 dismounts + } + } +} + +class VehicleControlMountingBlockedExosuitTest extends ActorTest { + def checkCanNotMount() : Unit = { + val reply = receiveOne(Duration.create(100, "ms")) + reply match { + case msg : Mountable.MountMessages => + assert(msg.response.isInstanceOf[Mountable.CanNotMount]) + case _ => + assert(false) + } + } + + def checkCanMount() : Unit = { + val reply = receiveOne(Duration.create(100, "ms")) + reply match { + case msg : Mountable.MountMessages => + assert(msg.response.isInstanceOf[Mountable.CanMount]) + case _ => + assert(false) + } + } + + "Vehicle Control" should { + "block players from sitting if their exo-suit is not allowed by the seat" in { + val vehicle = Vehicle(GlobalDefinitions.apc_tr) + vehicle.GUID = PlanetSideGUID(10) + vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") + + val player1 = Player(VehicleTest.avatar1) + player1.ExoSuit = ExoSuitType.Reinforced + player1.GUID = PlanetSideGUID(1) + val player2 = Player(VehicleTest.avatar1) + player2.ExoSuit = ExoSuitType.MAX + player2.GUID = PlanetSideGUID(2) + val player3 = Player(VehicleTest.avatar1) + player3.ExoSuit = ExoSuitType.Agile + player3.GUID = PlanetSideGUID(3) + + //disallow + vehicle.Actor ! Mountable.TryMount(player1, 0) //Reinforced in non-MAX seat + checkCanNotMount() + vehicle.Actor ! Mountable.TryMount(player2, 0) //MAX in non-Reinforced seat + checkCanNotMount() + vehicle.Actor ! Mountable.TryMount(player2, 1) //MAX in non-MAX seat + checkCanNotMount() + vehicle.Actor ! Mountable.TryMount(player1, 9) //Reinforced in MAX-only seat + checkCanNotMount() + vehicle.Actor ! Mountable.TryMount(player3, 9) //Agile in MAX-only seat + checkCanNotMount() + + //allow + vehicle.Actor ! Mountable.TryMount(player1, 1) + checkCanMount() + vehicle.Actor ! Mountable.TryMount(player2, 9) + checkCanMount() + vehicle.Actor ! Mountable.TryMount(player3, 0) + checkCanMount() + } + } +} + +class VehicleControlMountingBlockedSeatPermissionTest extends ActorTest { + def checkCanNotMount() : Unit = { + val reply = receiveOne(Duration.create(100, "ms")) + reply match { + case msg : Mountable.MountMessages => + assert(msg.response.isInstanceOf[Mountable.CanNotMount]) + case _ => + assert(false) + } + } + + def checkCanMount() : Unit = { + val reply = receiveOne(Duration.create(100, "ms")) + reply match { + case msg : Mountable.MountMessages => + assert(msg.response.isInstanceOf[Mountable.CanMount]) + case _ => + assert(false) + } + } + + "Vehicle Control" should { + //11 June 2018: Group is not supported yet so do not bother testing it + "block players from sitting if the seat does not allow it" in { + val vehicle = Vehicle(GlobalDefinitions.apc_tr) + vehicle.GUID = PlanetSideGUID(10) + vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") + + val player1 = Player(VehicleTest.avatar1) + player1.GUID = PlanetSideGUID(1) + val player2 = Player(VehicleTest.avatar1) + player2.GUID = PlanetSideGUID(2) + + vehicle.PermissionGroup(2,3) //passenger group -> empire + vehicle.Actor ! Mountable.TryMount(player1, 3) //passenger seat + checkCanMount() + vehicle.PermissionGroup(2,0) //passenger group -> locked + vehicle.Actor ! Mountable.TryMount(player2, 4) //passenger seat + checkCanNotMount() + } + } +} + +class VehicleControlMountingDriverSeatTest extends ActorTest { + def checkCanMount() : Unit = { + val reply = receiveOne(Duration.create(100, "ms")) + reply match { + case msg : Mountable.MountMessages => + assert(msg.response.isInstanceOf[Mountable.CanMount]) + case _ => + assert(false) + } + } + + "Vehicle Control" should { + "allow players to sit in the driver seat, even if it is locked, if the vehicle is unowned" in { + val vehicle = Vehicle(GlobalDefinitions.apc_tr) + vehicle.GUID = PlanetSideGUID(10) + vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") + val player1 = Player(VehicleTest.avatar1) + player1.GUID = PlanetSideGUID(1) + + assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Locked)) //driver group -> locked + assert(vehicle.Seats(0).Occupant.isEmpty) + assert(vehicle.Owner.isEmpty) + vehicle.Actor ! Mountable.TryMount(player1, 0) + checkCanMount() + assert(vehicle.Seats(0).Occupant.nonEmpty) + } + } +} + +class VehicleControlMountingOwnedLockedDriverSeatTest extends ActorTest { + def checkCanNotMount() : Unit = { + val reply = receiveOne(Duration.create(100, "ms")) + reply match { + case msg : Mountable.MountMessages => + assert(msg.response.isInstanceOf[Mountable.CanNotMount]) + case _ => + assert(false) + } + } + + def checkCanMount() : Unit = { + val reply = receiveOne(Duration.create(100, "ms")) + reply match { + case msg : Mountable.MountMessages => + assert(msg.response.isInstanceOf[Mountable.CanMount]) + case _ => + assert(false) + } + } + + "Vehicle Control" should { + "block players that are not the current owner from sitting in the driver seat (locked)" in { + val vehicle = Vehicle(GlobalDefinitions.apc_tr) + vehicle.GUID = PlanetSideGUID(10) + vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") + + val player1 = Player(VehicleTest.avatar1) + player1.GUID = PlanetSideGUID(1) + val player2 = Player(VehicleTest.avatar1) + player2.GUID = PlanetSideGUID(2) + + assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Locked)) //driver group -> locked + assert(vehicle.Seats(0).Occupant.isEmpty) + vehicle.Owner = player1.GUID + + vehicle.Actor ! Mountable.TryMount(player1, 0) + checkCanMount() + assert(vehicle.Seats(0).Occupant.nonEmpty) + vehicle.Actor ! Mountable.TryDismount(player1, 0) + receiveOne(Duration.create(100, "ms")) //discard + assert(vehicle.Seats(0).Occupant.isEmpty) + + vehicle.Actor ! Mountable.TryMount(player2, 0) + checkCanNotMount() + assert(vehicle.Seats(0).Occupant.isEmpty) + } + } +} + +class VehicleControlMountingOwnedUnlockedDriverSeatTest extends ActorTest { + def checkCanMount() : Unit = { + val reply = receiveOne(Duration.create(100, "ms")) + reply match { + case msg : Mountable.MountMessages => + assert(msg.response.isInstanceOf[Mountable.CanMount]) + case _ => + assert(false) + } + } + + "Vehicle Control" should { + "allow players that are not the current owner to sit in the driver seat (empire)" in { + val vehicle = Vehicle(GlobalDefinitions.apc_tr) + vehicle.GUID = PlanetSideGUID(10) + vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") + + val player1 = Player(VehicleTest.avatar1) + player1.GUID = PlanetSideGUID(1) + val player2 = Player(VehicleTest.avatar1) + player2.GUID = PlanetSideGUID(2) + + vehicle.PermissionGroup(0,3) //passenger group -> empire + assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Empire)) //driver group -> empire + assert(vehicle.Seats(0).Occupant.isEmpty) + vehicle.Owner = player1.GUID //owner set + + vehicle.Actor ! Mountable.TryMount(player1, 0) + checkCanMount() + assert(vehicle.Seats(0).Occupant.nonEmpty) + vehicle.Actor ! Mountable.TryDismount(player1, 0) + receiveOne(Duration.create(100, "ms")) //discard + assert(vehicle.Seats(0).Occupant.isEmpty) + + vehicle.Actor ! Mountable.TryMount(player2, 0) + checkCanMount() + assert(vehicle.Seats(0).Occupant.nonEmpty) + } + } +} + object VehicleTest { import net.psforever.objects.Avatar import net.psforever.types.{CharacterGender, PlanetSideEmpire} diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index ffb0188d0..742b24a7d 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -694,7 +694,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Mountable.CanNotMount(obj : Vehicle, seat_num) => log.warn(s"MountVehicleMsg: $tplayer attempted to mount $obj's seat $seat_num, but was not allowed") - if(obj.SeatPermissionGroup(seat_num) == Some(AccessPermissionGroup.Driver)) { + if(obj.SeatPermissionGroup(seat_num).contains(AccessPermissionGroup.Driver)) { sendResponse(ChatMsg(ChatMessageType.CMT_OPEN, false, "", "You are not the driver of this vehicle.", None)) } @@ -1132,12 +1132,12 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ObjectHeldMessage(player.GUID, Player.HandsDownSlot, true)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectHeld(player.GUID, player.LastDrawnSlot)) } - sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 1L)) //mount points off? - sendResponse(PlanetsideAttributeMessage(vehicle_guid, 21, player.GUID.guid)) + sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 1L)) //mount points off + sendResponse(PlanetsideAttributeMessage(vehicle_guid, 21, player.GUID.guid)) //ownership case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle, pad) => val vehicle_guid = vehicle.GUID - sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 0L)) //mount points on? + sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 0L)) //mount points on //sendResponse(PlanetsideAttributeMessage(vehicle_guid, 0, 10))//vehicle.Definition.MaxHealth)) sendResponse(PlanetsideAttributeMessage(vehicle_guid, 68, 0L)) //??? sendResponse(PlanetsideAttributeMessage(vehicle_guid, 113, 0L)) //??? @@ -3483,11 +3483,10 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(vehicle_guid) => continent.GUID(vehicle_guid) match { case Some(vehicle : Vehicle) => - tplayer.VehicleOwned = None DisownVehicle(tplayer, vehicle) - case _ => - tplayer.VehicleOwned = None + case _ => ; } + tplayer.VehicleOwned = None case None => ; } } @@ -3503,8 +3502,6 @@ class WorldSessionActor extends Actor with MDCContextAware { private def DisownVehicle(tplayer : Player, vehicle : Vehicle) : Unit = { if(vehicle.Owner.contains(tplayer.GUID)) { vehicle.Owner = None -// vehicle.PermissionGroup(10, VehicleLockState.Empire.id) -// vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SeatPermissions(tplayer.GUID, vehicle.GUID, 10, VehicleLockState.Empire.id)) } }