diff --git a/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala index 51992e015..eb3ba3aac 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala @@ -425,8 +425,7 @@ class SessionAvatarHandlers( sessionData.zoning.zoningStatus = Zoning.Status.None sessionData.zoning.spawn.deadState = DeadState.Dead continent.GUID(mount).collect { case obj: Vehicle => - sessionData.vehicles.ConditionalDriverVehicleControl(obj) - sessionData.vehicles.serverVehicleControlVelocity = None + sessionData.vehicles.DriverVehicleControl(obj) sessionData.unaccessContainer(obj) } sessionData.playerActionsToCancel() diff --git a/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala index 9af1aeeb6..261b1fed9 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala @@ -302,7 +302,7 @@ class SessionMountHandlers( case v: Vehicle if seatNum == 0 && Vector3.MagnitudeSquared(v.Velocity.getOrElse(Vector3.Zero)) > 0f => sessionData.vehicles.serverVehicleControlVelocity.collect { _ => - sessionData.vehicles.ServerVehicleOverrideEnd(v) + sessionData.vehicles.ServerVehicleOverrideStop(v) } v.Velocity = Vector3.Zero continent.VehicleEvents ! VehicleServiceMessage( diff --git a/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala index 651009208..96c5b23fb 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala @@ -202,17 +202,19 @@ class SessionVehicleHandlers( val strafe = 1 + Vehicles.CargoOrientation(vehicle) val reverseSpeed = if (strafe > 1) { 0 } else { speed } //strafe or reverse, not both - sessionData.vehicles.serverVehicleControlVelocity = Some(reverseSpeed) - sendResponse(ServerVehicleOverrideMsg( - lock_accelerator=true, - lock_wheel=true, - reverse=true, - unk4=false, - lock_vthrust=0, - strafe, - reverseSpeed, - unk8=Some(0) - )) + sessionData.vehicles.ServerVehicleOverrideWithPacket( + vehicle, + ServerVehicleOverrideMsg( + lock_accelerator=true, + lock_wheel=true, + reverse=true, + unk4=false, + lock_vthrust=0, + strafe, + reverseSpeed, + unk8=Some(0) + ) + ) import scala.concurrent.ExecutionContext.Implicits.global context.system.scheduler.scheduleOnce( delay milliseconds, @@ -220,25 +222,27 @@ class SessionVehicleHandlers( VehicleServiceResponse(toChannel, PlanetSideGUID(0), VehicleResponse.KickCargo(vehicle, speed=0, delay)) ) - case VehicleResponse.KickCargo(_, _, _) + case VehicleResponse.KickCargo(cargo, _, _) if player.VehicleSeated.nonEmpty && sessionData.zoning.spawn.deadState == DeadState.Alive => - sessionData.vehicles.serverVehicleControlVelocity = None - sendResponse(ServerVehicleOverrideMsg( - lock_accelerator=false, - lock_wheel=false, - reverse=false, - unk4=false, - lock_vthrust=0, - lock_strafe=0, - movement_speed=0, - unk8=None - )) + sessionData.vehicles.TotalDriverVehicleControl(cargo) case VehicleResponse.StartPlayerSeatedInVehicle(vehicle, _) if player.VisibleSlots.contains(player.DrawnSlot) => val vehicle_guid = vehicle.GUID sessionData.playerActionsToCancel() - sessionData.vehicles.serverVehicleControlVelocity = Some(0) + sessionData.vehicles.ServerVehicleOverrideWithPacket( + vehicle, + ServerVehicleOverrideMsg( + lock_accelerator=false, + lock_wheel=false, + reverse=false, + unk4=false, + lock_vthrust=0, + lock_strafe=0, + movement_speed=0, + unk8=None + ) + ) sessionData.terminals.CancelAllProximityUnits() player.DrawnSlot = Player.HandsDownSlot sendResponse(ObjectHeldMessage(player.GUID, Player.HandsDownSlot, unk1=true)) @@ -256,7 +260,6 @@ class SessionVehicleHandlers( val vehicleGuid = vehicle.GUID val playerGuid = player.GUID sessionData.playerActionsToCancel() - sessionData.vehicles.serverVehicleControlVelocity = Some(0) sessionData.terminals.CancelAllProximityUnits() sendResponse(PlanetsideAttributeMessage(vehicleGuid, attribute_type=22, attribute_value=1L)) //mount points off sendResponse(PlanetsideAttributeMessage(playerGuid, attribute_type=21, vehicleGuid)) //ownership @@ -266,18 +269,39 @@ class SessionVehicleHandlers( case VehicleResponse.PlayerSeatedInVehicle(vehicle, _) => Vehicles.ReloadAccessPermissions(vehicle, player.Name) - sessionData.vehicles.ServerVehicleLock(vehicle) + sessionData.vehicles.ServerVehicleOverrideWithPacket( + vehicle, + ServerVehicleOverrideMsg( + lock_accelerator=true, + lock_wheel=true, + reverse=false, + unk4=false, + lock_vthrust=0, + lock_strafe=0, + movement_speed=0, + unk8=Some(0) + ) + ) + sessionData.vehicles.serverVehicleControlVelocity = Some(-1) case VehicleResponse.ServerVehicleOverrideStart(vehicle, _) => val vdef = vehicle.Definition - sessionData.vehicles.ServerVehicleOverride( + sessionData.vehicles.ServerVehicleOverrideWithPacket( vehicle, - vdef.AutoPilotSpeed1, - flight= if (GlobalDefinitions.isFlightVehicle(vdef)) { 1 } else { 0 } + ServerVehicleOverrideMsg( + lock_accelerator=true, + lock_wheel=true, + reverse=false, + unk4=false, + lock_vthrust=if (GlobalDefinitions.isFlightVehicle(vdef)) { 1 } else { 0 }, + lock_strafe=0, + movement_speed=vdef.AutoPilotSpeed1, + unk8=Some(0) + ) ) case VehicleResponse.ServerVehicleOverrideEnd(vehicle, _) => - sessionData.vehicles.ServerVehicleOverrideEnd(vehicle) + sessionData.vehicles.ServerVehicleOverrideStop(vehicle) case VehicleResponse.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, data) => sendResponse(ChatMsg( diff --git a/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala b/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala index e46914eb9..4cce70018 100644 --- a/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala @@ -269,7 +269,7 @@ class VehicleOperations( def handleMountVehicle(pkt: MountVehicleMsg): Unit = { val MountVehicleMsg(_, mountable_guid, entry_point) = pkt sessionData.validObject(mountable_guid, decorator = "MountVehicle").collect { - case obj: Mountable if serverVehicleControlVelocity.isEmpty => + case obj: Mountable => obj.Actor ! Mountable.TryMount(player, entry_point) case _: Mountable => log.warn( @@ -299,10 +299,6 @@ class VehicleOperations( dError(s"DismountVehicleMsg: player ${player.Name} not considered seated in a mountable entity", player) None }) match { - case Some(_) if serverVehicleControlVelocity.nonEmpty => - log.warn( - s"DismountVehicleMsg: ${player.Name} can not dismount from vehicle while server has asserted control; please wait" - ) case Some(obj: Mountable) => obj.PassengerInSeat(player) match { case Some(seat_num) => @@ -535,97 +531,27 @@ class VehicleOperations( } /** - * This function is applied to vehicles that are leaving a cargo vehicle's cargo hold to auto reverse them out - * Lock all applicable controls of the current vehicle - * Set the vehicle to move in reverse + * Place the current vehicle under the control of the driver's commands, + * but leave it in a cancellable auto-drive. + * @param vehicle the vehicle */ - def ServerVehicleLockReverse(): Unit = { - serverVehicleControlVelocity = Some(-1) - sendResponse( - ServerVehicleOverrideMsg( - lock_accelerator = true, - lock_wheel = true, - reverse = true, - unk4 = true, - lock_vthrust = 0, - lock_strafe = 1, - movement_speed = 2, - unk8 = Some(0) - ) - ) - } - - /** - * This function is applied to vehicles that are leaving a cargo vehicle's cargo hold to strafe right out of the cargo hold for vehicles that are mounted sideways e.g. router/BFR - * Lock all applicable controls of the current vehicle - * Set the vehicle to strafe right - */ - def ServerVehicleLockStrafeRight(): Unit = { - serverVehicleControlVelocity = Some(-1) - sendResponse( - ServerVehicleOverrideMsg( - lock_accelerator = true, - lock_wheel = true, - reverse = false, - unk4 = true, - lock_vthrust = 0, - lock_strafe = 3, - movement_speed = 0, - unk8 = Some(0) - ) - ) - } - - /** - * This function is applied to vehicles that are leaving a cargo vehicle's cargo hold to strafe left out of the cargo hold for vehicles that are mounted sideways e.g. router/BFR - * Lock all applicable controls of the current vehicle - * Set the vehicle to strafe left - */ - def ServerVehicleLockStrafeLeft(): Unit = { - serverVehicleControlVelocity = Some(-1) - sendResponse( - ServerVehicleOverrideMsg( - lock_accelerator = true, - lock_wheel = true, - reverse = false, - unk4 = true, - lock_vthrust = 0, - lock_strafe = 2, - movement_speed = 0, - unk8 = Some(0) - ) - ) - } - - /** - * - */ - def ServerVehicleOverrideEnd(vehicle: Vehicle): Unit = { + def ServerVehicleOverrideStop(vehicle: Vehicle): Unit = { val vehicleGuid = vehicle.GUID session = session.copy(avatar = avatar.copy(vehicle = Some(vehicleGuid))) sessionData.vehicles.DriverVehicleControl(vehicle, vehicle.Definition.AutoPilotSpeed2) sendResponse(PlanetsideAttributeMessage(vehicleGuid, attribute_type=22, attribute_value=0L)) //mount points on } - /** - * Lock all applicable controls of the current vehicle. - * This includes forward motion, turning, and, if applicable, strafing. - * @param vehicle the vehicle being controlled - */ - def ServerVehicleLock(vehicle: Vehicle): Unit = { - serverVehicleControlVelocity = Some(-1) - sendResponse(ServerVehicleOverrideMsg(lock_accelerator=true, lock_wheel=true, reverse=false, unk4=false, 0, 1, 0, Some(0))) - } - /** * Place the current vehicle under the control of the server's commands. - * @param vehicle the vehicle - * @param speed how fast the vehicle is moving forward - * @param flight whether the vehicle is ascending or not, if the vehicle is an applicable type + * @param vehicle vehicle to be controlled; + * the client's player who is receiving this packet should be mounted as its driver, but this is not explicitly tested + * @param pkt packet to instigate server control */ - def ServerVehicleOverride(vehicle: Vehicle, speed: Int = 0, flight: Int = 0): Unit = { - serverVehicleControlVelocity = Some(speed) - sendResponse(ServerVehicleOverrideMsg(lock_accelerator=true, lock_wheel=true, reverse=false, unk4=false, flight, 0, speed, Some(0))) + def ServerVehicleOverrideWithPacket(vehicle: Vehicle, pkt: ServerVehicleOverrideMsg): Unit = { + serverVehicleControlVelocity = Some(pkt.movement_speed) + vehicle.DeploymentState = DriveState.AutoPilot + sendResponse(pkt) } /** @@ -636,9 +562,20 @@ class VehicleOperations( * @param flight whether the vehicle is ascending or not, if the vehicle is an applicable type */ def DriverVehicleControl(vehicle: Vehicle, speed: Int = 0, flight: Int = 0): Unit = { - if (serverVehicleControlVelocity.nonEmpty) { - serverVehicleControlVelocity = None - sendResponse(ServerVehicleOverrideMsg(lock_accelerator=false, lock_wheel=false, reverse=false, unk4=true, flight, 0, speed, None)) + if (vehicle.DeploymentState == DriveState.AutoPilot) { + TotalDriverVehicleControlWithPacket( + vehicle, + ServerVehicleOverrideMsg( + lock_accelerator=false, + lock_wheel=false, + reverse=false, + unk4=true, + lock_vthrust=flight, + lock_strafe=0, + movement_speed=speed, + unk8=None + ) + ) } } @@ -649,15 +586,45 @@ class VehicleOperations( * @param vehicle the vehicle */ def ConditionalDriverVehicleControl(vehicle: Vehicle): Unit = { - if (serverVehicleControlVelocity.nonEmpty && !serverVehicleControlVelocity.contains(0)) { + if (vehicle.DeploymentState == DriveState.AutoPilot) { TotalDriverVehicleControl(vehicle) } } - //noinspection ScalaUnusedSymbol + /** + * Place the current vehicle under the control of the driver's commands, + * but leave it in a cancellable auto-drive. + * Stop all movement entirely. + * @param vehicle the vehicle + */ def TotalDriverVehicleControl(vehicle: Vehicle): Unit = { + TotalDriverVehicleControlWithPacket( + vehicle, + ServerVehicleOverrideMsg( + lock_accelerator=false, + lock_wheel=false, + reverse=false, + unk4=false, + lock_vthrust=0, + lock_strafe=0, + movement_speed=0, + unk8=None + ) + ) + } + + /** + * Place the current vehicle under the control of the driver's commands, + * but leave it in a cancellable auto-drive. + * Stop all movement entirely. + * @param vehicle the vehicle; + * the client's player who is receiving this packet should be mounted as its driver, but this is not explicitly tested + * @param pkt packet to instigate cancellable control + */ + def TotalDriverVehicleControlWithPacket(vehicle: Vehicle, pkt: ServerVehicleOverrideMsg): Unit = { serverVehicleControlVelocity = None - sendResponse(ServerVehicleOverrideMsg(lock_accelerator=false, lock_wheel=false, reverse=false, unk4=false, 0, 0, 0, None)) + vehicle.DeploymentState = DriveState.Mobile + sendResponse(pkt) } /** diff --git a/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala index 3a3665bc8..2e76d7314 100644 --- a/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala +++ b/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala @@ -36,7 +36,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() { health, unk4 = false, no_mount_points = false, - obj.DeploymentState, + SterilizedDeploymentState(obj), unk5 = false, unk6 = false, obj.Cloaked, @@ -105,6 +105,13 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() { .toList } + private def SterilizedDeploymentState(obj: Vehicle): DriveState.Value = { + obj.DeploymentState match { + case state if state.id < 0 => DriveState.Mobile + case state => state + } + } + protected def SpecificFormatModifier: VehicleFormat.Value = VehicleFormat.Normal protected def SpecificFormatData(obj: Vehicle): Option[SpecificVehicleData] = None diff --git a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala index 93ee04def..d79b75f8a 100644 --- a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala @@ -121,6 +121,9 @@ class VehicleControl(vehicle: Vehicle) case Vehicle.Ownership(Some(player)) => GainOwnership(player) + case Mountable.TryMount(user, mountPoint) if vehicle.DeploymentState == DriveState.AutoPilot => + sender() ! Mountable.MountMessages(user, Mountable.CanNotMount(vehicle, mountPoint)) + case msg @ Mountable.TryMount(player, mount_point) => mountBehavior.apply(msg) mountCleanup(mount_point, player) diff --git a/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala b/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala index f06a83f52..161dece47 100644 --- a/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala +++ b/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala @@ -31,7 +31,8 @@ import scodec.codecs._ * For flight vehicles, for `n`, the forward air speed for the value in this packet will be at least `1.18 * n`. * This approximation is not always going to be accurate but serves as a good rule of thumb. * @param lock_accelerator driver has no control over vehicle acceleration - * @param lock_wheel driver has no control over vehicle turning + * @param lock_wheel driver has no control over vehicle turning; + * generally, the driver never has turning control * @param reverse move in reverse * 0 = forward * 1 = reverse diff --git a/src/main/scala/net/psforever/types/DriveState.scala b/src/main/scala/net/psforever/types/DriveState.scala index 887d93a6e..be192c369 100644 --- a/src/main/scala/net/psforever/types/DriveState.scala +++ b/src/main/scala/net/psforever/types/DriveState.scala @@ -21,5 +21,7 @@ object DriveState extends Enumeration { val State7 = Value(7) //unknown; not encountered on a vehicle that can deploy; functions like Mobile val State127 = Value(127) //unknown - val Kneeling = Value(-1) //flag bfr kneeling state; should not not encode + //the following values should never be encoded + val Kneeling = Value(-1) //flag bfr kneeling state + val AutoPilot = Value(-2) //when emerging from spawn pad, or being kicked from a ferry, during server guidance }