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