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