From 5347b78e7ca63f0475db6f498da60270c3c53533 Mon Sep 17 00:00:00 2001 From: Mazo Date: Sat, 2 Jun 2018 14:08:13 +0100 Subject: [PATCH 1/4] Add CargoMountPointStatusMessage / DismountVehicleCargoMsg / MountVehicleCargomsg packets --- .../psforever/packet/GamePacketOpcode.scala | 6 +-- .../game/CargoMountPointStatusMessage.scala | 45 +++++++++++++++++++ .../packet/game/DismountVehicleCargoMsg.scala | 33 ++++++++++++++ .../packet/game/MountVehicleCargoMsg.scala | 31 +++++++++++++ .../net/psforever/types/CargoStatus.scala | 15 +++++++ 5 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 common/src/main/scala/net/psforever/packet/game/CargoMountPointStatusMessage.scala create mode 100644 common/src/main/scala/net/psforever/packet/game/DismountVehicleCargoMsg.scala create mode 100644 common/src/main/scala/net/psforever/packet/game/MountVehicleCargoMsg.scala create mode 100644 common/src/main/scala/net/psforever/types/CargoStatus.scala diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index f1a6ea45..4300e22a 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -395,9 +395,9 @@ object GamePacketOpcode extends Enumeration { case 0x3f => game.ProjectileStateMessage.decode // OPCODES 0x40-4f - case 0x40 => noDecoder(MountVehicleCargoMsg) - case 0x41 => noDecoder(DismountVehicleCargoMsg) - case 0x42 => noDecoder(CargoMountPointStatusMessage) + case 0x40 => game.MountVehicleCargoMsg.decode + case 0x41 => game.DismountVehicleCargoMsg.decode + case 0x42 => game.CargoMountPointStatusMessage.decode case 0x43 => game.BeginZoningMessage.decode case 0x44 => game.ItemTransactionMessage.decode case 0x45 => game.ItemTransactionResultMessage.decode diff --git a/common/src/main/scala/net/psforever/packet/game/CargoMountPointStatusMessage.scala b/common/src/main/scala/net/psforever/packet/game/CargoMountPointStatusMessage.scala new file mode 100644 index 00000000..fa250ead --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/CargoMountPointStatusMessage.scala @@ -0,0 +1,45 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} +import net.psforever.types.CargoStatus +import scodec.Codec +import scodec.codecs._ + +/** + * + * @param cargo_vehicle_guid Cargo vehicle GUID (galaxy / lodestar) + * @param requesting_vehicle Seems to be vehicle that requested mounting (0 after mount) + * @param mounted_vehicle Seems to be vehicle that requested mounting after mount (0 before mount) + * @param dismounted_vehicle Seems to be vehicle that was mounted after disembarking (0 before embark or reset to 0 when MountVehicleCargoMsg received) + * @param slot Mount point for cargo bay 1 = lodestar, 15 = galaxy + * @param mount_status Mount status? 0 = None, 1 = Mount/Dismount in progress, 3 = Mounted + * @param orientation 0 = normal, 1 = sideways (e.g. router in lodestar) + */ +final case class CargoMountPointStatusMessage(cargo_vehicle_guid : PlanetSideGUID, + requesting_vehicle: PlanetSideGUID, + mounted_vehicle: PlanetSideGUID, + dismounted_vehicle: PlanetSideGUID, + slot: Int, + mount_status: CargoStatus.Value, + orientation: Int) + extends PlanetSideGamePacket { + type Packet = CargoMountPointStatusMessage + + def opcode = GamePacketOpcode.CargoMountPointStatusMessage + + def encode = CargoMountPointStatusMessage.encode(this) +} + +object CargoMountPointStatusMessage extends Marshallable[CargoMountPointStatusMessage] { + implicit val codec : Codec[CargoMountPointStatusMessage] = ( + ("cargo_vehicle_guid" | PlanetSideGUID.codec) :: + ("requesting_vehicle" | PlanetSideGUID.codec) :: + ("mounted_vehicle" | PlanetSideGUID.codec) :: + ("dismounted_vehicle" | PlanetSideGUID.codec) :: + ("slot" | uint8L) :: + ("mount_status" | CargoStatus.codec) :: + ("orientation" | uint2L) + ).as[CargoMountPointStatusMessage] +} + diff --git a/common/src/main/scala/net/psforever/packet/game/DismountVehicleCargoMsg.scala b/common/src/main/scala/net/psforever/packet/game/DismountVehicleCargoMsg.scala new file mode 100644 index 00000000..63da8503 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/DismountVehicleCargoMsg.scala @@ -0,0 +1,33 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} +import scodec.Codec +import scodec.codecs._ + +/** + * Note: For some reason this packet does not include the GUID of the vehicle that is being dismounted from. As a workaround vehicle.MountedIn in manually set/removed + * @param player_guid // GUID of the player that is rqeuesting dismount + * @param vehicle_guid GUID of the vehicle that is requesting dismount + * @param bailed If the vehicle bailed out of the cargo vehicle + * @param requestedByPassenger If a passenger of the vehicle in the cargo bay requests dismount this bit will be set + * @param kicked If the vehicle was kicked by the cargo vehicle pilot + */ +final case class DismountVehicleCargoMsg(player_guid : PlanetSideGUID, vehicle_guid: PlanetSideGUID, bailed: Boolean, requestedByPassenger: Boolean, kicked: Boolean) + extends PlanetSideGamePacket { + type Packet = DismountVehicleCargoMsg + + def opcode = GamePacketOpcode.DismountVehicleCargoMsg + + def encode = DismountVehicleCargoMsg.encode(this) +} + +object DismountVehicleCargoMsg extends Marshallable[DismountVehicleCargoMsg] { + implicit val codec : Codec[DismountVehicleCargoMsg] = ( + ("player_guid" | PlanetSideGUID.codec) :: + ("vehicle_guid" | PlanetSideGUID.codec) :: + ("unk3" | bool) :: // bailed? + ("unk4" | bool) :: + ("unk5" | bool) + ).as[DismountVehicleCargoMsg] +} \ No newline at end of file diff --git a/common/src/main/scala/net/psforever/packet/game/MountVehicleCargoMsg.scala b/common/src/main/scala/net/psforever/packet/game/MountVehicleCargoMsg.scala new file mode 100644 index 00000000..2eab06ed --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/MountVehicleCargoMsg.scala @@ -0,0 +1,31 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} +import scodec.Codec +import scodec.codecs._ + +/** + * + * @param player_guid The guid of the player sending the request to board another vehicle with a cargo vehicle + * @param vehicle_guid The guid of the vehicle for the requesting player + * @param target_vehicle The cargo vehicle guid e.g. Galaxy / Lodestar + * @param unk4 + */ +final case class MountVehicleCargoMsg(player_guid : PlanetSideGUID, vehicle_guid: PlanetSideGUID, target_vehicle: PlanetSideGUID, unk4: Int) + extends PlanetSideGamePacket { + type Packet = MountVehicleCargoMsg + + def opcode = GamePacketOpcode.MountVehicleCargoMsg + + def encode = MountVehicleCargoMsg.encode(this) +} + +object MountVehicleCargoMsg extends Marshallable[MountVehicleCargoMsg] { + implicit val codec : Codec[MountVehicleCargoMsg] = ( + ("unk1" | PlanetSideGUID.codec) :: + ("unk2" | PlanetSideGUID.codec):: + ("unk3" | PlanetSideGUID.codec) :: + ("unk4" | uint8L) + ).as[MountVehicleCargoMsg] +} diff --git a/common/src/main/scala/net/psforever/types/CargoStatus.scala b/common/src/main/scala/net/psforever/types/CargoStatus.scala new file mode 100644 index 00000000..9cee94a7 --- /dev/null +++ b/common/src/main/scala/net/psforever/types/CargoStatus.scala @@ -0,0 +1,15 @@ +// Copyright (c) 2017 PSForever +package net.psforever.types + +import net.psforever.packet.PacketHelpers +import scodec.codecs._ + +object CargoStatus extends Enumeration { + type Type = Value + + val Empty = Value(0) + val InProgress = Value(1) + val Occupied = Value(3) + + implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L) +} From 27d8f91cec1a79fc76124304e2182bfcc42cc8ce Mon Sep 17 00:00:00 2001 From: Mazo Date: Sat, 2 Jun 2018 14:09:42 +0100 Subject: [PATCH 2/4] Update documentation --- .../psforever/packet/game/DismountVehicleMsg.scala | 4 ++-- .../packet/game/GenericObjectActionMessage.scala | 4 ++++ .../packet/game/PlanetsideAttributeMessage.scala | 1 + .../packet/game/ServerVehicleOverrideMsg.scala | 13 +++++++++---- .../psforever/packet/game/VehicleStateMessage.scala | 2 +- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/common/src/main/scala/net/psforever/packet/game/DismountVehicleMsg.scala b/common/src/main/scala/net/psforever/packet/game/DismountVehicleMsg.scala index ed72f2e9..2a793146 100644 --- a/common/src/main/scala/net/psforever/packet/game/DismountVehicleMsg.scala +++ b/common/src/main/scala/net/psforever/packet/game/DismountVehicleMsg.scala @@ -10,11 +10,11 @@ import net.psforever.types.BailType * Dispatched by the client when the player wishes to get out of a vehicle. * @param player_guid the player * @param bailType The dismount action e.g. normal dismount, kicked by owner, bailed - * @param unk2 na + * @param wasKickedByDriver Seems to be true if a passenger was manually kicked by the vehicle owner */ final case class DismountVehicleMsg(player_guid : PlanetSideGUID, bailType : BailType.Value, - wasKickedByDriver : Boolean) // Seems to be true if a passenger was manually kicked by the vehicle owner + wasKickedByDriver : Boolean) extends PlanetSideGamePacket { type Packet = DismountVehicleMsg def opcode = GamePacketOpcode.DismountVehicleMsg diff --git a/common/src/main/scala/net/psforever/packet/game/GenericObjectActionMessage.scala b/common/src/main/scala/net/psforever/packet/game/GenericObjectActionMessage.scala index 0c15f526..4bceb566 100644 --- a/common/src/main/scala/net/psforever/packet/game/GenericObjectActionMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/GenericObjectActionMessage.scala @@ -12,6 +12,10 @@ import shapeless.{::, HNil} * (Write more some other time.) * @param object_guid the target object * @param code the action code + * 96, 97, 98, 99 - Makes the vehicle bounce slightly. Have seen this in packet captures after taking a vehicle through a warpgate + * 200, 201, 202, 203 - For aircraft - client shows "THe bailing mechanism failed! To fix the mechanism, land and repair the vehicle!" + * 224 - Sets vehicle or player to be black ops + * 228 - Reverts player from black ops */ final case class GenericObjectActionMessage(object_guid : PlanetSideGUID, code : Int) diff --git a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala index 5d0dc875..636a49b4 100644 --- a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala @@ -129,6 +129,7 @@ import scodec.codecs._ * `21 - Asserts first time event eligibility / makes owner if no owner is assigned`
* `22 - Toggles gunner and passenger mount points (1 = hides, 0 = reveals; this also locks their permissions)`
* `68 - ???`
+ * `79 - ???`
* `80 - Damage vehicle (unknown value)`
* `81 - ???`
* `113 - ???` diff --git a/common/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala b/common/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala index 266396a8..52e788bb 100644 --- a/common/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala +++ b/common/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala @@ -33,14 +33,19 @@ import scodec.codecs._ * @param lock_accelerator driver has no control over vehicle acceleration * @param lock_wheel driver has no control over vehicle turning * @param reverse move in reverse + * 0 = forward + * 1 = reverse * @param unk4 na; * something to do with vehicle bailable speed * @param lock_vthrust pilot has no control over vertical thrust; * asserts a constant positive vertical thrust; * the only valid setting appears to be 1 * @param lock_strafe pilot has no control over strafing thrust; - * the only valid setting appears to be 1 - * @param forward_speed "something like speed" + * 0 = not locked + * 1 = no strafing + * 2 = strafe left automatically + * 3 = strafe right automatically + * @param movement_speed "something like speed" * @param unk8 na; * set `lock_wheel` to `true` to expose this value */ @@ -50,7 +55,7 @@ final case class ServerVehicleOverrideMsg(lock_accelerator : Boolean, unk4 : Boolean, lock_vthrust : Int, lock_strafe : Int, - forward_speed : Int, + movement_speed : Int, unk8 : Option[Long] ) extends PlanetSideGamePacket { type Packet = ServerVehicleOverrideMsg @@ -66,7 +71,7 @@ object ServerVehicleOverrideMsg extends Marshallable[ServerVehicleOverrideMsg] { ("unk4" | bool) :: ("lock_vthrust" | uint2L) :: ("lock_strafe" | uint2L) :: - ("forward_speed" | uintL(9)) :: + ("movement_speed" | uintL(9)) :: conditional(test, "unk8" | uint32L) }) ).as[ServerVehicleOverrideMsg] diff --git a/common/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala b/common/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala index 3dbc037b..b0282d5d 100644 --- a/common/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala @@ -21,7 +21,7 @@ import scodec.codecs._ * 15 for straight; * 0 for hard right; * 30 for hard left - * @param unk5 na + * @param unk5 na - Possibly a flag to indicate the vehicle is attached to something else e.g. is in a galaxy/lodestar cargo bay * @param unk6 na * @see `PlacementData` */ From 447f6de2d6c3198e97ccbf16fc37889cbea01ca9 Mon Sep 17 00:00:00 2001 From: Mazo Date: Sat, 2 Jun 2018 14:11:19 +0100 Subject: [PATCH 3/4] Add definitions/objects for cargo holds and cargo --- .../psforever/objects/GlobalDefinitions.scala | 4 + .../scala/net/psforever/objects/Vehicle.scala | 47 +++++++++- .../objects/definition/CargoDefinition.scala | 33 +++++++ .../definition/VehicleDefinition.scala | 2 + .../psforever/objects/vehicles/Cargo.scala | 91 +++++++++++++++++++ .../vehicles/CargoVehicleRestriction.scala | 18 ++++ .../vehicle/support/DeconstructionActor.scala | 34 +++++-- 7 files changed, 219 insertions(+), 10 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/definition/CargoDefinition.scala create mode 100644 common/src/main/scala/net/psforever/objects/vehicles/Cargo.scala create mode 100644 common/src/main/scala/net/psforever/objects/vehicles/CargoVehicleRestriction.scala diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index d3b9c05f..50a8c8c4 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2864,6 +2864,7 @@ object GlobalDefinitions { dropship.Weapons += 12 -> cannon_dropship_20mm dropship.Weapons += 13 -> cannon_dropship_20mm dropship.Weapons += 14 -> dropship_rear_turret + dropship.Cargo += 15 -> new CargoDefinition() dropship.MountPoints += 1 -> 0 dropship.MountPoints += 2 -> 11 dropship.MountPoints += 3 -> 1 @@ -2876,6 +2877,7 @@ object GlobalDefinitions { dropship.MountPoints += 10 -> 8 dropship.MountPoints += 11 -> 9 dropship.MountPoints += 12 -> 10 + dropship.MountPoints += 13 -> 15 dropship.TrunkSize = InventoryTile.Tile1612 dropship.TrunkOffset = 30 dropship.AutoPilotSpeeds = (0, 4) @@ -2912,6 +2914,8 @@ object GlobalDefinitions { lodestar.Name = "lodestar" lodestar.Seats += 0 -> new SeatDefinition() lodestar.MountPoints += 1 -> 0 + lodestar.MountPoints += 2 -> 1 + lodestar.Cargo += 1 -> new CargoDefinition() lodestar.TrunkSize = InventoryTile.Tile1612 lodestar.TrunkOffset = 30 lodestar.AutoPilotSpeeds = (0, 4) diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala index 6dabd99a..e01ea16b 100644 --- a/common/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala @@ -8,7 +8,7 @@ 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.objects.vehicles._ import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.PlanetSideEmpire @@ -54,10 +54,17 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ */ private val groupPermissions : Array[VehicleLockState.Value] = Array(VehicleLockState.Locked, VehicleLockState.Empire, VehicleLockState.Empire, VehicleLockState.Locked) private var seats : Map[Int, Seat] = Map.empty + private var cargoHolds : Map[Int, Cargo] = Map.empty private var weapons : Map[Int, EquipmentSlot] = Map.empty private var utilities : Map[Int, Utility] = Map() private val trunk : GridInventory = GridInventory() + /** + * Records the GUID of the cargo vehicle (galaxy/lodestar) this vehicle is stored in for DismountVehicleCargoMsg use + * DismountVehicleCargoMsg only passes the player_guid and this vehicle's guid + */ + private var mountedIn : Option[PlanetSideGUID] = None + //init LoadDefinition() @@ -78,6 +85,22 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ faction } + def MountedIn : Option[PlanetSideGUID] = { + this.mountedIn + } + + def MountedIn_=(cargo_vehicle_guid : PlanetSideGUID) : Option[PlanetSideGUID] = MountedIn_=(Some(cargo_vehicle_guid)) + + def MountedIn_=(cargo_vehicle_guid : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = { + cargo_vehicle_guid match { + case Some(_) => + this.mountedIn = cargo_vehicle_guid + case None => + this.mountedIn = None + } + MountedIn + } + def Owner : Option[PlanetSideGUID] = { this.owner } @@ -231,6 +254,19 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ seats } + def CargoHold(cargoNumber : Int) : Option[Cargo] = { + if(cargoNumber >= 0) { + this.cargoHolds.get(cargoNumber) + } + else { + None + } + } + + def CargoHolds : Map[Int, Cargo] = { + cargoHolds + } + def SeatPermissionGroup(seatNumber : Int) : Option[AccessPermissionGroup.Value] = { if(seatNumber == 0) { Some(AccessPermissionGroup.Driver) @@ -247,6 +283,12 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ case None => None } + CargoHold(seatNumber) match { + case Some(_) => + Some(AccessPermissionGroup.Passenger) + case None => + None + } } } @@ -516,6 +558,9 @@ object Vehicle { }).toMap //create seats vehicle.seats = vdef.Seats.map({ case(num, definition) => num -> Seat(definition)}).toMap + // create cargo holds + vehicle.cargoHolds = vdef.Cargo.map({ case(num, definition) => num -> Cargo(definition)}).toMap + //create utilities vehicle.utilities = vdef.Utilities.map({ case(num, util) => num -> Utility(util, vehicle) }).toMap //trunk diff --git a/common/src/main/scala/net/psforever/objects/definition/CargoDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/CargoDefinition.scala new file mode 100644 index 00000000..3f01590e --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/definition/CargoDefinition.scala @@ -0,0 +1,33 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.definition + +import net.psforever.objects.vehicles.CargoVehicleRestriction + +/** + * The definition for a cargo hold. + */ +class CargoDefinition extends BasicDefinition { + /** a restriction on the type of exo-suit a person can wear */ + private var vehicleRestriction : CargoVehicleRestriction.Value = CargoVehicleRestriction.Small + /** the user can escape while the vehicle is moving */ + private var bailable : Boolean = true + Name = "cargo" + + def CargoRestriction : CargoVehicleRestriction.Value = { + this.vehicleRestriction + } + + def CargoRestriction_=(restriction : CargoVehicleRestriction.Value) : CargoVehicleRestriction.Value = { + this.vehicleRestriction = restriction + restriction + } + + def Bailable : Boolean = { + this.bailable + } + + def Bailable_=(canBail : Boolean) : Boolean = { + this.bailable = canBail + canBail + } +} 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 5bb11e89..5742cbd3 100644 --- a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala @@ -16,6 +16,7 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) { private var maxShields : Int = 0 /* key - seat index, value - seat object */ private val seats : mutable.HashMap[Int, SeatDefinition] = mutable.HashMap[Int, SeatDefinition]() + private val cargo : mutable.HashMap[Int, CargoDefinition] = mutable.HashMap[Int, CargoDefinition]() /* key - entry point index, value - seat index */ private val mountPoints : mutable.HashMap[Int, Int] = mutable.HashMap() /* key - seat index (where this weapon attaches during object construction), value - the weapon on an EquipmentSlot */ @@ -47,6 +48,7 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) { } def Seats : mutable.HashMap[Int, SeatDefinition] = seats + def Cargo : mutable.HashMap[Int, CargoDefinition] = cargo def MountPoints : mutable.HashMap[Int, Int] = mountPoints diff --git a/common/src/main/scala/net/psforever/objects/vehicles/Cargo.scala b/common/src/main/scala/net/psforever/objects/vehicles/Cargo.scala new file mode 100644 index 00000000..9a0d779f --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vehicles/Cargo.scala @@ -0,0 +1,91 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vehicles + +import net.psforever.objects.{Player, Vehicle} +import net.psforever.objects.definition.{CargoDefinition} + +/** + * Server-side support for a slot that vehicles can occupy + * @param cargoDef the Definition that constructs this item and maintains some of its unchanging fields + */ +class Cargo(private val cargoDef : CargoDefinition) { + private var occupant : Option[Vehicle] = None + + /** + * Is the cargo hold occupied? + * @return The vehicle in the cargo hold, or `None` if it is left vacant + */ + def Occupant : Option[Vehicle] = { + this.occupant + } + + /** + * A vehicle is trying to board the cargo hold + * Cargo holds are exclusive positions that can only hold one vehicle at a time. + * @param vehicle the vehicle boarding the cargo hold, or `None` if the vehicle is leaving + * @return the vehicle sitting in this seat, or `None` if it is left vacant + */ + def Occupant_=(vehicle : Vehicle) : Option[Vehicle] = Occupant_=(Some(vehicle)) + + def Occupant_=(vehicle : Option[Vehicle]) : Option[Vehicle] = { + if(vehicle.isDefined) { + if(this.occupant.isEmpty) { + this.occupant = vehicle + } + } + else { + this.occupant = None + } + this.occupant + } + + /** + * Is this cargo hold occupied? + * @return `true`, if it is occupied; `false`, otherwise + */ + def isOccupied : Boolean = { + this.occupant.isDefined + } + + def CargoRestriction : CargoVehicleRestriction.Value = { + cargoDef.CargoRestriction + } + + def Bailable : Boolean = { + cargoDef.Bailable + } + + /** + * Override the string representation to provide additional information. + * @return the string output + */ + override def toString : String = { + Cargo.toString(this) + } +} + +object Cargo { + /** + * Overloaded constructor. + * @return a `Cargo` object + */ + def apply(cargoDef : CargoDefinition) : Cargo = { + new Cargo(cargoDef) + } + + /** + * Provide a fixed string representation. + * @return the string output + */ + def toString(obj : Cargo) : String = { + val cargoStr = if(obj.isOccupied) { + s", occupied by vehicle ${obj.Occupant.get.GUID}" + } + else { + "" + } + s"cargo$cargoStr" + } +} + + diff --git a/common/src/main/scala/net/psforever/objects/vehicles/CargoVehicleRestriction.scala b/common/src/main/scala/net/psforever/objects/vehicles/CargoVehicleRestriction.scala new file mode 100644 index 00000000..098d3f9b --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vehicles/CargoVehicleRestriction.scala @@ -0,0 +1,18 @@ +// Copyright (c) 2017 PSForever +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. + * `NoReinforcedOrMax` is next most common. + * `MaxOnly` is a rare seat restriction found in pairs on Galaxies and on the large "Ground Transport" vehicles. + */ +object CargoVehicleRestriction extends Enumeration { + type Type = Value + + val + Small, + Large + = Value +} diff --git a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala index 2a95b8a5..07e43852 100644 --- a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala +++ b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala @@ -6,7 +6,7 @@ import net.psforever.objects.{DefaultCancellable, GlobalDefinitions, Vehicle} import net.psforever.objects.guid.TaskResolver import net.psforever.objects.vehicles.Seat import net.psforever.objects.zones.Zone -import net.psforever.packet.game.PlanetSideGUID +import net.psforever.packet.game.{DismountVehicleCargoMsg, PlanetSideGUID} import net.psforever.types.Vector3 import services.ServiceManager import services.ServiceManager.Lookup @@ -73,16 +73,32 @@ class DeconstructionActor extends Actor { //kick everyone out; this is a no-blocking manual form of MountableBehavior ! Mountable.TryDismount vehicle.Definition.MountPoints.values.foreach(seat_num => { val zone_id : String = zone.Id - val seat : Seat = vehicle.Seat(seat_num).get - seat.Occupant match { - case Some(tplayer) => - seat.Occupant = None - tplayer.VehicleSeated = None - if(tplayer.HasGUID) { - context.parent ! VehicleServiceMessage(zone_id, VehicleAction.KickPassenger(tplayer.GUID, 4, false, vehicle.GUID)) + vehicle.Seat(seat_num) match { + case Some(seat) => + seat.Occupant match { + case Some(tplayer) => + seat.Occupant = None + tplayer.VehicleSeated = None + if(tplayer.HasGUID) { + context.parent ! VehicleServiceMessage(zone_id, VehicleAction.KickPassenger(tplayer.GUID, 4, false, vehicle.GUID)) + } + case None => ; // No player seated } - case None => ; + case None => ; // Not a seat mount point } + vehicle.CargoHold(seat_num) match { + case Some(cargo) => + cargo.Occupant match { + case Some(vehicle) => + //todo: this probably doesn't work for passengers within the cargo vehicle + // Instruct client to start bail dismount procedure + // player_num set to 0 as it's not easily available in the context and currently isn't used by DismountVehicleCargoMsg + context.parent ! DismountVehicleCargoMsg(PlanetSideGUID(0), vehicle.GUID, true, false, false) + case None => ; // No vehicle in cargo + } + case None => ; // Not a cargo mounting point + } + }) if(vehicles.size == 1) { //we were the only entry so the event must be started from scratch From 8dc4a20332add37cbcff6c8f6284d4f4ea700feb Mon Sep 17 00:00:00 2001 From: Mazo Date: Sat, 2 Jun 2018 14:17:22 +0100 Subject: [PATCH 4/4] Initial functionality for vehicle cargo holds & cargo passengers --- .../src/main/scala/WorldSessionActor.scala | 280 +++++++++++++++++- .../scala/services/avatar/AvatarAction.scala | 8 +- .../services/avatar/AvatarResponse.scala | 3 + .../scala/services/avatar/AvatarService.scala | 4 + .../services/vehicle/VehicleAction.scala | 5 +- .../services/vehicle/VehicleResponse.scala | 5 +- .../services/vehicle/VehicleService.scala | 4 + 7 files changed, 296 insertions(+), 13 deletions(-) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 8ec79f16..4491654d 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -33,7 +33,7 @@ import net.psforever.objects.serverobject.structures.{Building, StructureType, W import net.psforever.objects.serverobject.terminals._ import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.objects.serverobject.tube.SpawnTube -import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, VehicleLockState} +import net.psforever.objects.vehicles.{AccessPermissionGroup, Cargo, Utility, VehicleLockState} import net.psforever.objects.zones.{InterstellarCluster, Zone} import net.psforever.packet.game.objectcreate._ import net.psforever.types._ @@ -45,6 +45,7 @@ import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, import scala.annotation.tailrec import scala.util.Success +import scala.concurrent.duration._ class WorldSessionActor extends Actor with MDCContextAware { import WorldSessionActor._ @@ -82,6 +83,9 @@ class WorldSessionActor extends Actor with MDCContextAware { var progressBarUpdate : Cancellable = DefaultCancellable.obj var reviveTimer : Cancellable = DefaultCancellable.obj var respawnTimer : Cancellable = DefaultCancellable.obj + var cargoMountTimer : Cancellable = DefaultCancellable.obj + var cargoDismountTimer : Cancellable = DefaultCancellable.obj + /** * Convert a boolean value into an integer value. @@ -245,9 +249,104 @@ class WorldSessionActor extends Actor with MDCContextAware { case PokeClient() => sendResponse(KeepAliveMessage()) + case CheckCargoDismount(vehicle_guid, cargo_vehicle_guid, cargo_mountpoint, iteration) => + val vehicle = continent.GUID(vehicle_guid.guid).get.asInstanceOf[Vehicle] + val cargo_vehicle = continent.GUID(cargo_vehicle_guid).get.asInstanceOf[Vehicle] + + val distance = Vector3.Distance(vehicle.Position, cargo_vehicle.Position) + + log.info(s"Dismount distance ${distance}") + if(distance > 15 || iteration > 20) { + // Vehicle has moved far enough away - close the cargo door + log.info("Vehicle is far enough away or disembark timed out - closing cargo door and returning full control to driver") + + StartBundlingPackets() + + // Return control of vehicle to driver + DriverVehicleControl(vehicle) + + val cargoStatusMessage = CargoMountPointStatusMessage(cargo_vehicle_guid, PlanetSideGUID(0), PlanetSideGUID(0), vehicle_guid, cargo_mountpoint, CargoStatus.Empty, 0) + log.warn(cargoStatusMessage.toString) + // Do NOT send this packet back to the client directly. If you do and then send it again to all clients in the zone (including the client again) + // The client will get stuck in a state where the player cannot dismount as it thinks it is always trying to remount the cargo hold + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player.GUID, cargoStatusMessage)) + + StopBundlingPackets() + + cargoMountTimer.cancel() + cargoDismountTimer.cancel() + } else { + // Not far enough away - rescheduling check + import scala.concurrent.ExecutionContext.Implicits.global + cargoDismountTimer = context.system.scheduler.scheduleOnce(250 milliseconds, self, CheckCargoDismount(vehicle_guid, cargo_vehicle.GUID, cargo_mountpoint, iteration = iteration + 1)) + } + case CheckCargoMounting(vehicle_guid, cargo_vehicle_guid, cargo_mountpoint, iteration) => + val vehicle = continent.GUID(vehicle_guid.guid).get.asInstanceOf[Vehicle] + val cargo_vehicle = continent.GUID(cargo_vehicle_guid.guid).get.asInstanceOf[Vehicle] + + val distance = Vector3.Distance(vehicle.Position, cargo_vehicle.Position) + log.warn(s"Mount distance ${distance}") + if(distance <= 8) { + // Vehicle is close enough that it should be within the cargo bay. Mount it. + log.info("Mounting vehicle cargo") + cargoMountTimer.cancel() + cargoDismountTimer.cancel() + + val vehicle = continent.GUID(vehicle_guid).get.asInstanceOf[Vehicle] + + StartBundlingPackets() + vehicleService ! VehicleServiceMessage(s"${vehicle.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargo_vehicle_guid, 0, cargo_vehicle.Health))) + vehicleService ! VehicleServiceMessage(s"${vehicle.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargo_vehicle_guid, 68, cargo_vehicle.Shields))) + + val attachMessage = ObjectAttachMessage(cargo_vehicle_guid, vehicle_guid, cargo_mountpoint) + log.warn(attachMessage.toString) + sendResponse(attachMessage) + + // This is required for when DismountVehicleCargoMsg is sent as the cargo_vehicle_guid isn't sent as a parameter + vehicle.MountedIn = cargo_vehicle_guid + cargo_vehicle.CargoHold(cargo_mountpoint).get.Occupant = vehicle + + val orientation = if(vehicle.Definition == GlobalDefinitions.router) { + // mount router "sideways" in a lodestar + //todo: BFRs will likely also need this set + 1 + } else { + 0 + } + + val cargoStatusMessage = CargoMountPointStatusMessage(cargo_vehicle_guid, vehicle_guid, vehicle_guid, PlanetSideGUID(0), cargo_mountpoint, CargoStatus.Occupied, orientation) + log.warn(cargoStatusMessage.toString) + sendResponse(cargoStatusMessage) + + StopBundlingPackets() + + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player.GUID, attachMessage)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player.GUID, cargoStatusMessage)) + } else if (distance > 25 || iteration >= 15) { + // Vehicle is too far away. Abort mounting. + log.info("Vehicle is too far away or didn't mount within allocated time. Aborting cargo mount.") + + val cargoStatusMessage = CargoMountPointStatusMessage(cargo_vehicle_guid, PlanetSideGUID(0), PlanetSideGUID(0), vehicle_guid, cargo_mountpoint, CargoStatus.Empty, 0) + log.warn(cargoStatusMessage.toString) + // Do NOT send this packet back to the client directly. If you do and then send it again to all clients in the zone (including the client again) + // The client will get stuck in a state where the player cannot dismount as it thinks it is always trying to remount the cargo hold + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player.GUID, cargoStatusMessage)) + + + cargoMountTimer.cancel() + cargoDismountTimer.cancel() + } + else { + // Not close enough, far away enough or timeout not exceeded. Reschedule check + import scala.concurrent.ExecutionContext.Implicits.global + cargoMountTimer = context.system.scheduler.scheduleOnce(1 second, self, CheckCargoMounting(vehicle_guid, cargo_vehicle_guid, cargo_mountpoint, iteration = iteration + 1)) + } + case AvatarServiceResponse(_, guid, reply) => val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(-1) } reply match { + case AvatarResponse.SendResponse(msg) => + sendResponse(msg) case AvatarResponse.ArmorChanged(suit, subtype) => if(tplayer_guid != guid) { sendResponse(ArmorChangedMessage(guid, suit, subtype)) @@ -547,6 +646,8 @@ class WorldSessionActor extends Actor with MDCContextAware { player.Position = pos } } + case VehicleResponse.SendResponse(msg) => + sendResponse(msg) case VehicleResponse.UpdateAmsSpawnPoint(list) => if(player.isBackpack) { @@ -1609,6 +1710,97 @@ class WorldSessionActor extends Actor with MDCContextAware { clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient()) log.warn(PacketCoding.DecodePacket(hex"d2327e7b8a972b95113881003710").toString) + case msg @ DismountVehicleCargoMsg(player_guid, vehicle_guid, bailed, requestedByPassenger, kicked) => + log.info(msg.toString) + + // Ignore dismount requests by passengers of the vehicle in the cargo bay for now + // todo: allow passengers of vehicle in cargo bay to bail, but not bail the cargo vehicle itself + if(!requestedByPassenger) { + StartBundlingPackets() + val vehicle = continent.GUID(vehicle_guid).get.asInstanceOf[Vehicle] + val cargo_vehicle = continent.GUID(vehicle.MountedIn.get).get.asInstanceOf[Vehicle] + // todo: change this to work with multiple cargo holds for potential custom vehicles in the future + val cargo_mountpoint = cargo_vehicle.Definition.Cargo.head._1 + + val cargoStatusMessage = CargoMountPointStatusMessage(cargo_vehicle.GUID, PlanetSideGUID(0), PlanetSideGUID(0), vehicle_guid, cargo_mountpoint, CargoStatus.InProgress, 0) + log.info(cargoStatusMessage.toString) + // Dismount vehicle on UI and disable "shield" effect on lodestar + sendResponse(cargoStatusMessage) + + + // Detach vehicle from cargo vehicle + val dismount_position = if (bailed || kicked) { + // If we're bailing drop the vehicle below the cargo vehicle + //todo: once the server has a concept of height from the floor we should probably ensure vehicles aren't dropped below the world + Vector3(cargo_vehicle.Position.x, cargo_vehicle.Position.y, cargo_vehicle.Position.z - 1f) + } else if (cargo_vehicle.Definition == GlobalDefinitions.dropship) { + // As the galaxy cargo bay is offset backwards from the center of the vehicle (unlike the lodestar) we need to set the position backwards slightly + Vector3(cargo_vehicle.Position.x, cargo_vehicle.Position.y - 7f, cargo_vehicle.Position.z + 2f) + } else { + Vector3(cargo_vehicle.Position.x, cargo_vehicle.Position.y, cargo_vehicle.Position.z + 2f) + } + + // Add a flag if the vehicle should mount/dismount sideways + //todo: BFRs will likely also need this set + val sideways = vehicle.Definition == GlobalDefinitions.router + + val rotation = if(sideways) { + // dismount router "sideways" in a lodestar + cargo_vehicle.Orientation.z - 90f + } else { + cargo_vehicle.Orientation.z + } + + val detachMessage = ObjectDetachMessage(cargo_vehicle.GUID, vehicle_guid, dismount_position, cargo_vehicle.Orientation.x, cargo_vehicle.Orientation.y, rotation) + log.info(detachMessage.toString) + sendResponse(detachMessage) + + // Update display to show current vehicle health & shields correctly + log.warn(s"vehicle health: ${vehicle.Health} shields: ${vehicle.Shields}") + vehicleService ! VehicleServiceMessage(s"${vehicle.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(vehicle_guid, 0, vehicle.Health))) + vehicleService ! VehicleServiceMessage(s"${vehicle.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(vehicle_guid, 68, vehicle.Shields))) + + vehicle.MountedIn = None + cargo_vehicle.CargoHold(cargo_mountpoint).get.Occupant = None + + if (!bailed) { + // Automatically drive the vehicle backwards out of the cargo bay + if (!sideways) { + ServerVehicleLockReverse() + } else { + ServerVehicleLockStrafeLeft() + } + } else { + //todo: proper vehicle bailing. It works currently but when collision damage is implemented the vehicle will take damage if not in a bail state. Need to confirm how this is done with further research + } + + import scala.concurrent.duration._ + import scala.concurrent.ExecutionContext.Implicits.global + // Start a timer to check every second if the vehicle has moved far enough away to be considered dismounted, and then close the cargo door + cargoDismountTimer = context.system.scheduler.scheduleOnce(250 milliseconds, self, CheckCargoDismount(vehicle_guid, cargo_vehicle.GUID, cargo_mountpoint, iteration = 0)) + + StopBundlingPackets() + + // Sync to other clients + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player.GUID, cargoStatusMessage)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player.GUID, detachMessage)) + } + case msg @ MountVehicleCargoMsg(player_guid, vehicle_guid, cargo_vehicle_guid, unk4) => + log.info(msg.toString) + + val cargo_vehicle = continent.GUID(cargo_vehicle_guid).get.asInstanceOf[Vehicle] + val cargo_mountpoint = cargo_vehicle.Definition.Cargo.head._1 + + // Begin the mount process - open the cargo door + val reply = CargoMountPointStatusMessage(cargo_vehicle_guid, PlanetSideGUID(0), vehicle_guid, PlanetSideGUID(0), cargo_mountpoint, CargoStatus.InProgress, 0) + log.warn(reply.toString) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player.GUID, reply)) + sendResponse(reply) + + import scala.concurrent.duration._ + import scala.concurrent.ExecutionContext.Implicits.global + // Start timer to check every second if the vehicle is close enough to mount, or far enough away to cancel the mounting + cargoMountTimer = context.system.scheduler.scheduleOnce(1 second, self, CheckCargoMounting(vehicle_guid, cargo_vehicle_guid, cargo_mountpoint, iteration = 0)) case msg @ CharacterCreateRequestMessage(name, head, voice, gender, empire) => log.info("Handling " + msg) sendResponse(ActionResultMessage(true, None)) @@ -1674,16 +1866,38 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ObjectCreateMessage(definition.ObjectId, vehicle.GUID, definition.Packet.ConstructorData(vehicle).get)) //seat vehicle occupants definition.MountPoints.values.foreach(seat_num => { - vehicle.Seat(seat_num).get.Occupant match { + vehicle.Seat(seat_num) match { + case Some(seat) => + seat.Occupant match { case Some(tplayer) => if(tplayer.HasGUID) { sendResponse(ObjectAttachMessage(vehicle.GUID, tplayer.GUID, seat_num)) } - case None => ; + case None => ; // No player seated + } + case None => ; // Not a seat mounting point } }) ReloadVehicleAccessPermissions(vehicle) }) + + // Loop over vehicles again to add cargohold occupants after all vehicles have been created on the local client + continent.Vehicles.foreach(vehicle => { + vehicle.CargoHolds.foreach({ case (cargo_num, cargo) => { + cargo.Occupant match { + case Some(cargo_vehicle) => + if(cargo_vehicle.HasGUID) { + StartBundlingPackets() + sendResponse(ObjectAttachMessage(cargo_vehicle.GUID, vehicle.GUID, cargo_num)) + //todo: attaching the vehicle seems to work, but setting the mount point status doesn't? + sendResponse(CargoMountPointStatusMessage(cargo_vehicle.GUID, vehicle.GUID, vehicle.GUID, PlanetSideGUID(0), cargo_num, CargoStatus.Occupied, 0)) + StopBundlingPackets() + } + case None => ; // No vehicle in cargo + } + }}) + }) + //implant terminals continent.Map.TerminalToInterface.foreach({ case ((terminal_guid, interface_guid)) => val parent_guid = PlanetSideGUID(terminal_guid) @@ -2792,6 +3006,7 @@ class WorldSessionActor extends Actor with MDCContextAware { // Deconstruct the vehicle if the driver has bailed out and the vehicle is capable of flight //todo: implement auto landing procedure if the pilot bails but passengers are still present instead of deconstructing the vehicle //todo: continue flight path until aircraft crashes if no passengers present (or no passenger seats), then deconstruct. + //todo: kick cargo passengers out. To be added after PR #216 is merged if(bailType == BailType.Bailed && seat_num == 0 && GlobalDefinitions.isFlightVehicle(obj.asInstanceOf[Vehicle].Definition)) { vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj.asInstanceOf[Vehicle], continent, 0L) // Immediately deconstruct vehicle } @@ -2874,17 +3089,34 @@ class WorldSessionActor extends Actor with MDCContextAware { vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SeatPermissions(player.GUID, vehicle.GUID, attribute_type, attribute_value)) //kick players who should not be seated in the vehicle due to permission changes if(allow == VehicleLockState.Locked) { //TODO only important permission atm - vehicle.Definition.MountPoints.values.foreach(seat_num => { - val seat = vehicle.Seat(seat_num).get + vehicle.Definition.MountPoints.values.foreach(mountpoint_num => { + vehicle.Seat(mountpoint_num) match { + case Some(seat) => seat.Occupant match { case Some(tplayer) => - if(vehicle.SeatPermissionGroup(seat_num).contains(group) && tplayer != player) { + if(vehicle.SeatPermissionGroup(mountpoint_num).contains(group) && tplayer != player) { seat.Occupant = None tplayer.VehicleSeated = None vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(tplayer.GUID, 4, false, object_guid)) } - case None => ; + case None => ; // No player seated + } + case None => ; // Not a seat mounting point } + vehicle.CargoHold(mountpoint_num) match { + case Some(cargo) => + cargo.Occupant match { + case Some(vehicle) => + if(vehicle.SeatPermissionGroup(mountpoint_num).contains(group)) { + //todo: this probably doesn't work for passengers within the cargo vehicle + // Instruct client to start bail dismount procedure + self ! DismountVehicleCargoMsg(player.GUID, vehicle.GUID, true, false, false) + } + case None => ; // No vehicle in cargo + } + case None => ; // Not a cargo mounting point + } + }) } case None => ; @@ -2926,6 +3158,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case default => log.error(s"Unhandled GamePacket $pkt") } + /** * Iterate over a group of `EquipmentSlot`s, some of which may be occupied with an item. * Remove any encountered items and add them to an output `List`. @@ -4622,6 +4855,36 @@ class WorldSessionActor extends Actor with MDCContextAware { tplayer.Armor == tplayer.MaxArmor } + /** + * 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 + */ + def ServerVehicleLockReverse() : Unit = { + controlled = Some(0) + 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 = { + controlled = Some(0) + 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 = { + controlled = Some(0) + sendResponse(ServerVehicleOverrideMsg(lock_accelerator = true, lock_wheel = true, reverse = false, unk4 = true, lock_vthrust = 0, lock_strafe = 2, movement_speed = 0, unk8 = Some(0))) + } + /** * Lock all applicable controls of the current vehicle. * This includes forward motion, turning, and, if applicable, strafing. @@ -4799,6 +5062,9 @@ object WorldSessionActor { private final case class VehicleLoaded(vehicle : Vehicle) private final case class DelayedProximityUnitStop(unit : Terminal with ProximityUnit) private final case class UnregisterCorpseOnVehicleDisembark(corpse : Player) + private final case class CheckCargoMounting(vehicle_guid : PlanetSideGUID, cargo_vehicle_guid: PlanetSideGUID, cargo_mountpoint: Int, iteration: Int) + private final case class CheckCargoDismount(vehicle_guid : PlanetSideGUID, cargo_vehicle_guid: PlanetSideGUID, cargo_mountpoint: Int, iteration: Int) + /** * A message that indicates the user is using a remote electronics kit to hack some server object. diff --git a/pslogin/src/main/scala/services/avatar/AvatarAction.scala b/pslogin/src/main/scala/services/avatar/AvatarAction.scala index d0701767..1cfaeb8f 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarAction.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarAction.scala @@ -4,7 +4,8 @@ package services.avatar import net.psforever.objects.Player import net.psforever.objects.equipment.Equipment import net.psforever.objects.zones.Zone -import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream} +import net.psforever.packet.PlanetSideGamePacket +import net.psforever.packet.game.{CargoMountPointStatusMessage, PlanetSideGUID, PlayerStateMessageUpstream} import net.psforever.packet.game.objectcreate.ConstructorData import net.psforever.types.{ExoSuitType, Vector3} @@ -30,7 +31,10 @@ object AvatarAction { final case class Reload(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action final case class StowEquipment(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action final case class WeaponDryFire(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action -// final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action + + final case class SendResponse(player_guid: PlanetSideGUID, msg: PlanetSideGamePacket) extends Action + + // final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action // final case class DestroyDisplay(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action // final case class HitHintReturn(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action // final case class ChangeWeapon(unk1 : Int, sessionId : Long) extends Action diff --git a/pslogin/src/main/scala/services/avatar/AvatarResponse.scala b/pslogin/src/main/scala/services/avatar/AvatarResponse.scala index 5932bace..0eeab5fb 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarResponse.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarResponse.scala @@ -3,6 +3,7 @@ package services.avatar import net.psforever.objects.Player import net.psforever.objects.equipment.Equipment +import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream} import net.psforever.packet.game.objectcreate.ConstructorData import net.psforever.types.{ExoSuitType, Vector3} @@ -29,6 +30,8 @@ object AvatarResponse { final case class Reload(weapon_guid : PlanetSideGUID) extends Response final case class StowEquipment(target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Response final case class WeaponDryFire(weapon_guid : PlanetSideGUID) extends Response + + final case class SendResponse(msg: PlanetSideGamePacket) extends Response // final case class PlayerStateShift(itemID : PlanetSideGUID) extends Response // final case class DestroyDisplay(itemID : PlanetSideGUID) extends Response // final case class HitHintReturn(itemID : PlanetSideGUID) extends Response diff --git a/pslogin/src/main/scala/services/avatar/AvatarService.scala b/pslogin/src/main/scala/services/avatar/AvatarService.scala index 17cec7ae..aa0f0aaa 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarService.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarService.scala @@ -110,6 +110,10 @@ class AvatarService extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.WeaponDryFire(weapon_guid)) ) + case AvatarAction.SendResponse(player_guid, msg) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.SendResponse(msg)) + ) case _ => ; } diff --git a/pslogin/src/main/scala/services/vehicle/VehicleAction.scala b/pslogin/src/main/scala/services/vehicle/VehicleAction.scala index 68c9788e..44b1002f 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleAction.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleAction.scala @@ -4,9 +4,10 @@ package services.vehicle import net.psforever.objects.{PlanetSideGameObject, Vehicle} import net.psforever.objects.equipment.Equipment import net.psforever.objects.zones.Zone +import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.objectcreate.ConstructorData -import net.psforever.types.{DriveState, Vector3, BailType} +import net.psforever.types.{BailType, DriveState, Vector3} object VehicleAction { trait Action @@ -25,6 +26,6 @@ object VehicleAction { final case class UnloadVehicle(player_guid : PlanetSideGUID, continent : Zone, vehicle : Vehicle) extends Action final case class UnstowEquipment(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID) extends Action final case class VehicleState(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, unk1 : Int, pos : Vector3, ang : Vector3, vel : Option[Vector3], unk2 : Option[Int], unk3 : Int, unk4 : Int, wheel_direction : Int, unk5 : Boolean, unk6 : Boolean) extends Action - + final case class SendResponse(player_guid: PlanetSideGUID, msg : PlanetSideGamePacket) extends Action final case class UpdateAmsSpawnPoint(zone : Zone) extends Action } diff --git a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala index f3c9f9eb..b0ac6cbc 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala @@ -3,9 +3,10 @@ package services.vehicle import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.{PlanetSideGameObject, Vehicle} +import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.objectcreate.ConstructorData -import net.psforever.types.{DriveState, Vector3, BailType} +import net.psforever.types.{BailType, DriveState, Vector3} object VehicleResponse { trait Response @@ -29,6 +30,6 @@ object VehicleResponse { final case class UnloadVehicle(vehicle_guid : PlanetSideGUID) extends Response final case class UnstowEquipment(item_guid : PlanetSideGUID) extends Response final case class VehicleState(vehicle_guid : PlanetSideGUID, unk1 : Int, pos : Vector3, ang : Vector3, vel : Option[Vector3], unk2 : Option[Int], unk3 : Int, unk4 : Int, wheel_direction : Int, unk5 : Boolean, unk6 : Boolean) extends Response - + final case class SendResponse(msg: PlanetSideGamePacket) extends Response final case class UpdateAmsSpawnPoint(list : List[SpawnTube]) extends Response } diff --git a/pslogin/src/main/scala/services/vehicle/VehicleService.scala b/pslogin/src/main/scala/services/vehicle/VehicleService.scala index 6b16ec2b..5f4faf35 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleService.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleService.scala @@ -95,6 +95,10 @@ class VehicleService extends Actor { VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.VehicleState(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6)) ) + case VehicleAction.SendResponse(player_guid, msg) => + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.SendResponse(msg)) + ) //unlike other messages, just return to sender, don't publish case VehicleAction.UpdateAmsSpawnPoint(zone : Zone) =>