test Harasser to demonstrate synched vehicle actions: mounting, disembarking, driving, gunning, changing access permissions, changing ownership, kicking passengers, deconstructing

This commit is contained in:
FateJH 2017-10-27 18:44:20 -04:00
parent 211eb838aa
commit 8f658aa688
36 changed files with 1820 additions and 470 deletions

View file

@ -1228,17 +1228,86 @@ object GlobalDefinitions {
fury_weapon_systema.FireModes.head.AmmoSlotIndex = 0 fury_weapon_systema.FireModes.head.AmmoSlotIndex = 0
fury_weapon_systema.FireModes.head.Magazine = 2 fury_weapon_systema.FireModes.head.Magazine = 2
val
quadassault_weapon_system = ToolDefinition(ObjectClass.quadassault_weapon_system)
quadassault_weapon_system.Size = EquipmentSize.VehicleWeapon
quadassault_weapon_system.AmmoTypes += Ammo.bullet_12mm
quadassault_weapon_system.FireModes += new FireModeDefinition
quadassault_weapon_system.FireModes.head.AmmoTypeIndices += 0
quadassault_weapon_system.FireModes.head.AmmoSlotIndex = 0
quadassault_weapon_system.FireModes.head.Magazine = 100
val
chaingun_p = ToolDefinition(ObjectClass.chaingun_p)
chaingun_p.Size = EquipmentSize.VehicleWeapon
chaingun_p.AmmoTypes += Ammo.bullet_12mm
chaingun_p.FireModes += new FireModeDefinition
chaingun_p.FireModes.head.AmmoTypeIndices += 0
chaingun_p.FireModes.head.AmmoSlotIndex = 0
chaingun_p.FireModes.head.Magazine = 150
val val
fury = VehicleDefinition(ObjectClass.fury) fury = VehicleDefinition(ObjectClass.fury)
fury.Seats += 0 -> new SeatDefinition() fury.Seats += 0 -> new SeatDefinition()
fury.Seats(0).Bailable = true fury.Seats(0).Bailable = true
fury.Seats(0).ControlledWeapon = Some(1) fury.Seats(0).ControlledWeapon = 1
fury.MountPoints += 0 -> 0 fury.MountPoints += 0 -> 0
fury.MountPoints += 2 -> 0 fury.MountPoints += 2 -> 0
fury.Weapons += 1 -> fury_weapon_systema fury.Weapons += 1 -> fury_weapon_systema
fury.TrunkSize = InventoryTile(11, 11) fury.TrunkSize = InventoryTile(11, 11)
fury.TrunkOffset = 30 fury.TrunkOffset = 30
val
quadassault = VehicleDefinition(ObjectClass.quadassault)
quadassault.Seats += 0 -> new SeatDefinition()
quadassault.Seats(0).Bailable = true
quadassault.Seats(0).ControlledWeapon = 1
quadassault.MountPoints += 0 -> 0
quadassault.MountPoints += 2 -> 0
quadassault.Weapons += 1 -> quadassault_weapon_system
quadassault.TrunkSize = InventoryTile(11, 11)
quadassault.TrunkOffset = 30
val
quadstealth = VehicleDefinition(ObjectClass.quadstealth)
quadstealth.CanCloak = true
quadstealth.Seats += 0 -> new SeatDefinition()
quadstealth.Seats(0).Bailable = true
quadstealth.MountPoints += 0 -> 0
quadstealth.MountPoints += 2 -> 0
quadstealth.CanCloak = true
quadstealth.TrunkSize = InventoryTile(11, 11)
quadstealth.TrunkOffset = 30
val
two_man_assault_buggy = VehicleDefinition(ObjectClass.two_man_assault_buggy)
two_man_assault_buggy.Seats += 0 -> new SeatDefinition()
two_man_assault_buggy.Seats(0).Bailable = true
two_man_assault_buggy.Seats += 1 -> new SeatDefinition()
two_man_assault_buggy.Seats(1).Bailable = true
two_man_assault_buggy.Seats(1).ControlledWeapon = 2
two_man_assault_buggy.MountPoints += 1 -> 0
two_man_assault_buggy.MountPoints += 2 -> 1
two_man_assault_buggy.Weapons += 2 -> chaingun_p
two_man_assault_buggy.TrunkSize = InventoryTile(11, 11)
two_man_assault_buggy.TrunkOffset = 30
val
phantasm = VehicleDefinition(ObjectClass.phantasm)
phantasm.CanCloak = true
phantasm.Seats += 0 -> new SeatDefinition()
phantasm.Seats += 1 -> new SeatDefinition()
phantasm.Seats(1).Bailable = true
phantasm.Seats += 2 -> new SeatDefinition()
phantasm.Seats(2).Bailable = true
phantasm.Seats += 3 -> new SeatDefinition()
phantasm.Seats(3).Bailable = true
phantasm.Seats += 4 -> new SeatDefinition()
phantasm.Seats(4).Bailable = true
phantasm.MountPoints += 1 -> 0 //TODO add and check all
phantasm.TrunkSize = InventoryTile(11, 8)
phantasm.TrunkOffset = 30 //TODO check
val val
order_terminal = new OrderTerminalDefinition order_terminal = new OrderTerminalDefinition
val val

View file

@ -4,11 +4,13 @@ package net.psforever.objects
import net.psforever.objects.definition.VehicleDefinition import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.equipment.{Equipment, EquipmentSize} import net.psforever.objects.equipment.{Equipment, EquipmentSize}
import net.psforever.objects.inventory.GridInventory import net.psforever.objects.inventory.GridInventory
import net.psforever.objects.vehicles.{Seat, Utility, VehicleLockState} import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.vehicles.{AccessPermissionGroup, Seat, Utility, VehicleLockState}
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.DriveState import net.psforever.packet.game.objectcreate.DriveState
import net.psforever.types.PlanetSideEmpire import net.psforever.types.PlanetSideEmpire
import scala.annotation.tailrec
import scala.collection.mutable import scala.collection.mutable
/** /**
@ -17,29 +19,31 @@ import scala.collection.mutable
* All infantry seating, all mounted weapons, and the trunk space are considered part of the same index hierarchy. * All infantry seating, all mounted weapons, and the trunk space are considered part of the same index hierarchy.
* Generally, all seating is declared first - the driver and passengers and and gunners. * Generally, all seating is declared first - the driver and passengers and and gunners.
* Following that are the mounted weapons and other utilities. * Following that are the mounted weapons and other utilities.
* Trunk space starts being indexed afterwards. * Trunk space starts being indexed afterwards.<br>
* The first seat is always the op;erator (driver/pilot).
* "Passengers" are seats that are not the operator and are not in control of a mounted weapon.
* "Gunners" are seats that are not the operator and ARE in control of a mounted weapon.
* (The operator can be in control of a weapon - that is the whole point of a turret.)<br>
* <br> * <br>
* Having said all that, to keep it simple, infantry seating, mounted weapons, and utilities are stored in separate `Map`s. * To keep it simple, infantry seating, mounted weapons, and utilities are stored separately.
* @param vehicleDef the vehicle's definition entry' * @param vehicleDef the vehicle's definition entry';
* stores and unloads pertinent information about the `Vehicle`'s configuration; * stores and unloads pertinent information about the `Vehicle`'s configuration;
* used in the initialization process (`loadVehicleDefinition`) * used in the initialization process (`loadVehicleDefinition`)
*/ */
class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGameObject { class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServerObject {
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR
private var owner : Option[PlanetSideGUID] = None private var owner : Option[PlanetSideGUID] = None
private var health : Int = 1 private var health : Int = 1
private var shields : Int = 0 private var shields : Int = 0
private var deployed : DriveState.Value = DriveState.Mobile private var deployed : DriveState.Value = DriveState.Mobile
private var decal : Int = 0 private var decal : Int = 0
private var trunkLockState : VehicleLockState.Value = VehicleLockState.Locked
private var trunkAccess : Option[PlanetSideGUID] = None private var trunkAccess : Option[PlanetSideGUID] = None
private var jammered : Boolean = false
private var cloaked : Boolean = false
private val seats : mutable.HashMap[Int, Seat] = mutable.HashMap() /**
private val weapons : mutable.HashMap[Int, EquipmentSlot] = mutable.HashMap() * Permissions control who gets to access different parts of the vehicle;
* the groups are Driver (seat), Gunner (seats), Passenger (seats), and the Trunk
*/
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 weapons : Map[Int, EquipmentSlot] = Map.empty
private val utilities : mutable.ArrayBuffer[Utility] = mutable.ArrayBuffer() private val utilities : mutable.ArrayBuffer[Utility] = mutable.ArrayBuffer()
private val trunk : GridInventory = GridInventory() private val trunk : GridInventory = GridInventory()
@ -68,8 +72,15 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame
} }
def Owner_=(owner : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = { def Owner_=(owner : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = {
this.owner = owner owner match {
owner case Some(_) =>
if(Definition.CanBeOwned) {
this.owner = owner
}
case None =>
this.owner = None
}
Owner
} }
def Health : Int = { def Health : Int = {
@ -98,15 +109,15 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame
vehicleDef.MaxShields vehicleDef.MaxShields
} }
def Configuration : DriveState.Value = { def Drive : DriveState.Value = {
this.deployed this.deployed
} }
def Configuration_=(deploy : DriveState.Value) : DriveState.Value = { def Drive_=(deploy : DriveState.Value) : DriveState.Value = {
if(vehicleDef.Deployment) { if(vehicleDef.Deployment) {
this.deployed = deploy this.deployed = deploy
} }
Configuration Drive
} }
def Decal : Int = { def Decal : Int = {
@ -118,6 +129,20 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame
decal decal
} }
def Jammered : Boolean = jammered
def Jammered_=(jamState : Boolean) : Boolean = {
jammered = jamState
Jammered
}
def Cloaked : Boolean = cloaked
def Cloaked_=(isCloaked : Boolean) : Boolean = {
cloaked = isCloaked
Cloaked
}
/** /**
* Given the index of an entry mounting point, return the infantry-accessible `Seat` associated with it. * Given the index of an entry mounting point, return the infantry-accessible `Seat` associated with it.
* @param mountPoint an index representing the seat position / mounting point * @param mountPoint an index representing the seat position / mounting point
@ -127,6 +152,60 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame
vehicleDef.MountPoints.get(mountPoint) vehicleDef.MountPoints.get(mountPoint)
} }
/**
* What are the access permissions for a position on this vehicle, seats or trunk?
* @param group the group index
* @return what sort of access permission exist for this group
*/
def PermissionGroup(group : Int) : Option[VehicleLockState.Value] = {
reindexPermissionsGroup(group) match {
case Some(index) =>
Some(groupPermissions(index))
case None =>
None
}
}
/**
* Change the access permissions for a position on this vehicle, seats or trunk.
* @param group the group index
* @param level the new permission for this group
* @return the new access permission for this group;
* `None`, if the group does not exist or the level of permission was not changed
*/
def PermissionGroup(group : Int, level : Long) : Option[VehicleLockState.Value] = {
reindexPermissionsGroup(group) match {
case Some(index) =>
val current = groupPermissions(index)
val next = try { VehicleLockState(level.toInt) } catch { case _ : Exception => groupPermissions(index) }
if(current != next) {
groupPermissions(index) = next
PermissionGroup(index)
}
else {
None
}
case None =>
None
}
}
/**
* When the access permission group is communicated via `PlanetsideAttributeMessage`, the index is between 10 and 13.
* Internally, permission groups are stored as an `Array`, so the respective re-indexing plots 10 -> 0 and 13 -> 3.
* @param group the group index
* @return the modified group index
*/
private def reindexPermissionsGroup(group : Int) : Option[Int] = if(group > 9 && group < 14) {
Some(group - 10)
}
else if(group > -1 && group < 4) {
Some(group)
}
else {
None
}
/** /**
* Get the seat at the index. * Get the seat at the index.
* The specified "seat" can only accommodate a player as opposed to weapon mounts which share the same indexing system. * The specified "seat" can only accommodate a player as opposed to weapon mounts which share the same indexing system.
@ -146,7 +225,26 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame
seats.values.toList seats.values.toList
} }
def Weapons : mutable.HashMap[Int, EquipmentSlot] = weapons def SeatPermissionGroup(seatNumber : Int) : Option[AccessPermissionGroup.Value] = {
if(seatNumber == 0) {
Some(AccessPermissionGroup.Driver)
}
else {
Seat(seatNumber) match {
case Some(seat) =>
seat.ControlledWeapon match {
case Some(_) =>
Some(AccessPermissionGroup.Gunner)
case None =>
Some(AccessPermissionGroup.Passenger)
}
case None =>
None
}
}
}
def Weapons : Map[Int, EquipmentSlot] = weapons
/** /**
* Get the weapon at the index. * Get the weapon at the index.
@ -164,20 +262,25 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame
} }
/** /**
* Given a player who may be a passenger, retrieve an index where this player is seated. * Given a player who may be an occupant, retrieve an number of the seat where this player is sat.
* @param player the player * @param player the player
* @return a seat by index, or `None` if the `player` is not actually seated in this `Vehicle` * @return a seat number, or `None` if the `player` is not actually seated in this vehicle
*/ */
def PassengerInSeat(player : Player) : Option[Int] = { def PassengerInSeat(player : Player) : Option[Int] = recursivePassengerInSeat(seats.iterator, player)
var outSeat : Option[Int] = None
val GUID = player.GUID @tailrec private def recursivePassengerInSeat(iter : Iterator[(Int, Seat)], player : Player) : Option[Int] = {
for((seatNumber, seat) <- this.seats) { if(!iter.hasNext) {
val occupant : Option[PlanetSideGUID] = seat.Occupant None
if(occupant.isDefined && occupant.get == GUID) { }
outSeat = Some(seatNumber) else {
val (seatNumber, seat) = iter.next
if(seat.Occupant.contains(player)) {
Some(seatNumber)
}
else {
recursivePassengerInSeat(iter, player)
} }
} }
outSeat
} }
/** /**
@ -267,7 +370,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame
*/ */
def CanAccessTrunk(player : Player) : Boolean = { def CanAccessTrunk(player : Player) : Boolean = {
if(trunkAccess.isEmpty || trunkAccess.contains(player.GUID)) { if(trunkAccess.isEmpty || trunkAccess.contains(player.GUID)) {
trunkLockState match { groupPermissions(3) match {
case VehicleLockState.Locked => //only the owner case VehicleLockState.Locked => //only the owner
owner.isEmpty || (owner.isDefined && player.GUID == owner.get) owner.isEmpty || (owner.isDefined && player.GUID == owner.get)
case VehicleLockState.Group => //anyone in the owner's squad or platoon case VehicleLockState.Group => //anyone in the owner's squad or platoon
@ -285,19 +388,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame
* Check access to the `Trunk`. * Check access to the `Trunk`.
* @return the current access value for the `Vehicle` `Trunk` * @return the current access value for the `Vehicle` `Trunk`
*/ */
def TrunkLockState : VehicleLockState.Value = { def TrunkLockState : VehicleLockState.Value = groupPermissions(3)
this.trunkLockState
}
/**
* Change the access value for the trunk.
* @param lockState the new access value for the `Vehicle` `Trunk`
* @return the current access value for the `Vehicle` `Trunk` after the change
*/
def TrunkLockState_=(lockState : VehicleLockState.Value) : VehicleLockState.Value = {
this.trunkLockState = lockState
lockState
}
/** /**
* This is the definition entry that is used to store and unload pertinent information about the `Vehicle`. * This is the definition entry that is used to store and unload pertinent information about the `Vehicle`.
@ -316,22 +407,52 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame
object Vehicle { object Vehicle {
/** /**
* Overloaded constructor. * A basic `Trait` connecting all of the actionable `Vehicle` response messages.
* @param vehicleDef the vehicle's definition entry
* @return a `Vwehicle` object
*/ */
def apply(vehicleDef : VehicleDefinition) : Vehicle = { sealed trait Exchange
new Vehicle(vehicleDef)
} /**
* Message that carries the result of the processed request message back to the original user (`player`).
* @param player the player who sent this request message
* @param response the result of the processed request
*/
final case class VehicleMessages(player : Player, response : Exchange)
/**
* The `Vehicle` will become unresponsive to player activity.
* Usually, it does this to await deconstruction and clean-up
* @see `VehicleControl`
*/
final case class PrepareForDeletion()
/**
* This player wants to sit down in an available(?) seat.
* @param seat_num the seat where the player is trying to occupy;
* this is NOT the entry mount point index;
* make certain to convert!
* @param player the `Player` object
*/
final case class TrySeatPlayer(seat_num : Int, player : Player)
/**
* The recipient player of this packet is being allowed to sit in the assigned seat.
* @param vehicle the `Vehicle` object that generated this message
* @param seat_num the seat that the player will occupy
*/
final case class CanSeatPlayer(vehicle : Vehicle, seat_num : Int) extends Exchange
/**
* The recipient player of this packet is not allowed to sit in the requested seat.
* @param vehicle the `Vehicle` object that generated this message
* @param seat_num the seat that the player can not occupy
*/
final case class CannotSeatPlayer(vehicle : Vehicle, seat_num : Int) extends Exchange
/** /**
* Overloaded constructor. * Overloaded constructor.
* @param vehicleDef the vehicle's definition entry * @param vehicleDef the vehicle's definition entry
* @return a `Vwehicle` object * @return a `Vehicle` object
*/ */
def apply(guid : PlanetSideGUID, vehicleDef : VehicleDefinition) : Vehicle = { def apply(vehicleDef : VehicleDefinition) : Vehicle = {
val obj = new Vehicle(vehicleDef) new Vehicle(vehicleDef)
obj.GUID = guid
obj
} }
/** /**
@ -344,16 +465,13 @@ object Vehicle {
//general stuff //general stuff
vehicle.Health = vdef.MaxHealth vehicle.Health = vdef.MaxHealth
//create weapons //create weapons
for((num, definition) <- vdef.Weapons) { vehicle.weapons = vdef.Weapons.map({case (num, definition) =>
val slot = EquipmentSlot(EquipmentSize.VehicleWeapon) val slot = EquipmentSlot(EquipmentSize.VehicleWeapon)
slot.Equipment = Tool(definition) slot.Equipment = Tool(definition)
vehicle.weapons += num -> slot num -> slot
vehicle }).toMap
}
//create seats //create seats
for((num, seatDef) <- vdef.Seats) { vehicle.seats = vdef.Seats.map({ case(num, definition) => num -> Seat(definition)}).toMap
vehicle.seats += num -> Seat(seatDef, vehicle)
}
for(i <- vdef.Utilities) { for(i <- vdef.Utilities) {
//TODO utilies must be loaded and wired on a case-by-case basis? //TODO utilies must be loaded and wired on a case-by-case basis?
vehicle.Utilities += Utility.Select(i, vehicle) vehicle.Utilities += Utility.Select(i, vehicle)

View file

@ -37,8 +37,12 @@ class SeatDefinition extends BasicDefinition {
this.weaponMount this.weaponMount
} }
def ControlledWeapon_=(seat : Option[Int]) : Option[Int] = { def ControlledWeapon_=(wep : Int) : Option[Int] = {
this.weaponMount = seat ControlledWeapon_=(Some(wep))
}
def ControlledWeapon_=(wep : Option[Int]) : Option[Int] = {
this.weaponMount = wep
ControlledWeapon ControlledWeapon
} }
} }

View file

@ -22,7 +22,9 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
private var deployment : Boolean = false private var deployment : Boolean = false
private val utilities : mutable.ArrayBuffer[Int] = mutable.ArrayBuffer[Int]() private val utilities : mutable.ArrayBuffer[Int] = mutable.ArrayBuffer[Int]()
private var trunkSize : InventoryTile = InventoryTile.None private var trunkSize : InventoryTile = InventoryTile.None
private var trunkOffset: Int = 0 private var trunkOffset : Int = 0
private var canCloak : Boolean = false
private var canBeOwned : Boolean = true
Name = "vehicle" Name = "vehicle"
Packet = new VehicleConverter Packet = new VehicleConverter
@ -44,6 +46,20 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
def MountPoints : mutable.HashMap[Int, Int] = mountPoints def MountPoints : mutable.HashMap[Int, Int] = mountPoints
def CanBeOwned : Boolean = canBeOwned
def CanBeOwned_=(ownable : Boolean) : Boolean = {
canBeOwned = ownable
CanBeOwned
}
def CanCloak : Boolean = canCloak
def CanCloak_=(cloakable : Boolean) : Boolean = {
canCloak = cloakable
CanCloak
}
def Weapons : mutable.HashMap[Int, ToolDefinition] = weapons def Weapons : mutable.HashMap[Int, ToolDefinition] = weapons
def Deployment : Boolean = deployment def Deployment : Boolean = deployment

View file

@ -2,11 +2,10 @@
package net.psforever.objects.definition.converter package net.psforever.objects.definition.converter
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.objects.{EquipmentSlot, Vehicle} import net.psforever.objects.Vehicle
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.{InventoryItemData, _} import net.psforever.packet.game.objectcreate.{InventoryItemData, _}
import scala.annotation.tailrec
import scala.util.{Failure, Success, Try} import scala.util.{Failure, Success, Try}
class VehicleConverter extends ObjectCreateConverter[Vehicle]() { class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
@ -19,47 +18,66 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
PlacementData(obj.Position, obj.Orientation, obj.Velocity), PlacementData(obj.Position, obj.Orientation, obj.Velocity),
obj.Faction, obj.Faction,
0, 0,
if(obj.Owner.isDefined) { obj.Owner.get } else { PlanetSideGUID(0) } //this is the owner field, right? PlanetSideGUID(0) //if(obj.Owner.isDefined) { obj.Owner.get } else { PlanetSideGUID(0) } //TODO is this really Owner?
), ),
0, 0,
obj.Health / obj.MaxHealth * 255, //TODO not precise obj.Health / obj.MaxHealth * 255, //TODO not precise
false, false, false, false,
DriveState.Mobile, obj.Drive,
false,
false, false,
false, false,
obj.Cloaked,
SpecificFormatData(obj), SpecificFormatData(obj),
Some(InventoryData(MakeMountings(obj).sortBy(_.parentSlot))) Some(InventoryData((MakeMountings(obj) ++ MakeTrunk(obj)).sortBy(_.parentSlot)))
)(SpecificFormatModifier) )(SpecificFormatModifier)
) )
} }
/** /**
* For an object with a list of weapon mountings, convert those weapons into data as if found in an `0x17` packet. * na
* @param obj the Vehicle game object * @param obj the `Player` game object
* @return the converted data * @return a list of all tools that were in the mounted weapon slots in decoded packet form
*/ */
private def MakeMountings(obj : Vehicle) : List[InventoryItemData.InventoryItem] = recursiveMakeMountings(obj.Weapons.iterator) private def MakeMountings(obj : Vehicle) : List[InventoryItemData.InventoryItem] = {
obj.Weapons.map({
@tailrec private def recursiveMakeMountings(iter : Iterator[(Int,EquipmentSlot)], list : List[InventoryItemData.InventoryItem] = Nil) : List[InventoryItemData.InventoryItem] = { case((index, slot)) =>
if(!iter.hasNext) {
list
}
else {
val (index, slot) = iter.next
if(slot.Equipment.isDefined) {
val equip : Equipment = slot.Equipment.get val equip : Equipment = slot.Equipment.get
recursiveMakeMountings( InventoryItemData(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.Packet.ConstructorData(equip).get)
iter, }).toList
list :+ InventoryItemData(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.Packet.ConstructorData(equip).get)
)
}
else {
recursiveMakeMountings(iter, list)
}
}
} }
/**
* na
* @param obj the `Player` game object
* @return a list of all items that were in the inventory in decoded packet form
*/
private def MakeTrunk(obj : Vehicle) : List[InternalSlot] = {
obj.Trunk.Items.map({
case(_, item) =>
val equip : Equipment = item.obj
InventoryItemData(equip.Definition.ObjectId, equip.GUID, item.start, equip.Definition.Packet.ConstructorData(equip).get)
}).toList
}
// @tailrec private def recursiveMakeSeats(iter : Iterator[(Int, Seat)], list : List[InventoryItemData.InventoryItem] = Nil) : List[InventoryItemData.InventoryItem] = {
// if(!iter.hasNext) {
// list
// }
// else {
// val (index, seat) = iter.next
// seat.Occupant match {
// case Some(avatar) =>
// val definition = avatar.Definition
// recursiveMakeSeats(
// iter,
// list :+ InventoryItemData(definition.ObjectId, avatar.GUID, index, definition.Packet.ConstructorData(avatar).get)
// )
// case None =>
// recursiveMakeSeats(iter, list)
// }
// }
// }
protected def SpecificFormatModifier : VehicleFormat.Value = VehicleFormat.Normal protected def SpecificFormatModifier : VehicleFormat.Value = VehicleFormat.Normal
protected def SpecificFormatData(obj : Vehicle) : Option[SpecificVehicleData] = None protected def SpecificFormatData(obj : Vehicle) : Option[SpecificVehicleData] = None

View file

@ -9,6 +9,8 @@ object EquipmentSize extends Enumeration {
Rifle, //6x3 and 9x3 Rifle, //6x3 and 9x3
Max, //max weapon only Max, //max weapon only
VehicleWeapon, //vehicle-mounted weapons VehicleWeapon, //vehicle-mounted weapons
BFRArmWeapon, //duel arm weapons for bfr
BFRGunnerWeapon, //gunner seat for bfr
Inventory, //reserved Inventory, //reserved
Any Any
= Value = Value

View file

@ -0,0 +1,13 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.inventory
import net.psforever.objects.Player
import net.psforever.packet.game.PlanetSideGUID
trait AccessibleInventory {
def Inventory : GridInventory
def CanAccess(who : Player) : Boolean
def Access(who : PlanetSideGUID) : Boolean
def Unaccess : Boolean
}

View file

@ -18,7 +18,7 @@ class CertTerminalDefinition extends TerminalDefinition(171) {
*/ */
private val certificationList : Map[String, (CertificationType.Value, Int)] = Map( private val certificationList : Map[String, (CertificationType.Value, Int)] = Map(
"medium_assault" -> (CertificationType.MediumAssault, 2), "medium_assault" -> (CertificationType.MediumAssault, 2),
"reinforced_armo" -> (CertificationType.ReinforcedExoSuit, 3), "reinforced_armor" -> (CertificationType.ReinforcedExoSuit, 3),
"quad_all" -> (CertificationType.ATV, 1), "quad_all" -> (CertificationType.ATV, 1),
"switchblade" -> (CertificationType.Switchblade, 1), "switchblade" -> (CertificationType.Switchblade, 1),
"harasser" -> (CertificationType.Harasser, 1), "harasser" -> (CertificationType.Harasser, 1),

View file

@ -0,0 +1,22 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.vehicles
/**
* An `Enumeration` of various permission groups that control access to aspects of a vehicle.<br>
* - `Driver` is a seat that is always seat number 0.<br>
* - `Gunner` is a seat that is not the `Driver` and controls a mounted weapon.<br>
* - `Passenger` is a seat that is not the `Driver` and does not have control of a mounted weapon.<br>
* - `Trunk` represnts access to the vehicle's internal storage space.<br>
* Organized to replicate the `PlanetsideAttributeMessage` value used for that given access level.
* In their respective `PlanetsideAttributeMessage` packet, the groups are indexed in the same order as 10 through 13.
*/
object AccessPermissionGroup extends Enumeration {
type Type = Value
val
Driver,
Gunner,
Passenger,
Trunk
= Value
}

View file

@ -2,33 +2,22 @@
package net.psforever.objects.vehicles package net.psforever.objects.vehicles
import net.psforever.objects.definition.SeatDefinition import net.psforever.objects.definition.SeatDefinition
import net.psforever.objects.{Player, Vehicle} import net.psforever.objects.Player
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.PlanetSideEmpire
/** /**
* Server-side support for a slot that infantry players can occupy, ostensibly called a "seat" and treated like a "seat." * Server-side support for a slot that infantry players can occupy, ostensibly called a "seat" and treated like a "seat."
* (Players can sit in it.) * (Players can sit in it.)
* @param seatDef the Definition that constructs this item and maintains some of its immutable fields * @param seatDef the Definition that constructs this item and maintains some of its unchanging fields
* @param vehicle the vehicle where this seat is installed
*/ */
class Seat(private val seatDef : SeatDefinition, private val vehicle : Vehicle) { class Seat(private val seatDef : SeatDefinition) {
private var occupant : Option[PlanetSideGUID] = None private var occupant : Option[Player] = None
private var lockState : VehicleLockState.Value = VehicleLockState.Empire // private var lockState : VehicleLockState.Value = VehicleLockState.Empire
/**
* The faction association of this `Seat` is tied directly to the connected `Vehicle`.
* @return the faction association
*/
def Faction : PlanetSideEmpire.Value = {
vehicle.Faction
}
/** /**
* Is this seat occupied? * Is this seat occupied?
* @return the GUID of the player sitting in this seat, or `None` if it is left vacant * @return the GUID of the player sitting in this seat, or `None` if it is left vacant
*/ */
def Occupant : Option[PlanetSideGUID] = { def Occupant : Option[Player] = {
this.occupant this.occupant
} }
@ -38,10 +27,12 @@ class Seat(private val seatDef : SeatDefinition, private val vehicle : Vehicle)
* @param player the player who wants to sit, or `None` if the occupant is getting up * @param player the player who wants to sit, or `None` if the occupant is getting up
* @return the GUID of the player sitting in this seat, or `None` if it is left vacant * @return the GUID of the player sitting in this seat, or `None` if it is left vacant
*/ */
def Occupant_=(player : Option[Player]) : Option[PlanetSideGUID] = { def Occupant_=(player : Player) : Option[Player] = Occupant_=(Some(player))
def Occupant_=(player : Option[Player]) : Option[Player] = {
if(player.isDefined) { if(player.isDefined) {
if(this.occupant.isEmpty) { if(this.occupant.isEmpty) {
this.occupant = Some(player.get.GUID) this.occupant = player
} }
} }
else { else {
@ -58,14 +49,14 @@ class Seat(private val seatDef : SeatDefinition, private val vehicle : Vehicle)
this.occupant.isDefined this.occupant.isDefined
} }
def SeatLockState : VehicleLockState.Value = { // def SeatLockState : VehicleLockState.Value = {
this.lockState // this.lockState
} // }
//
def SeatLockState_=(lockState : VehicleLockState.Value) : VehicleLockState.Value = { // def SeatLockState_=(lockState : VehicleLockState.Value) : VehicleLockState.Value = {
this.lockState = lockState // this.lockState = lockState
SeatLockState // SeatLockState
} // }
def ArmorRestriction : SeatArmorRestriction.Value = { def ArmorRestriction : SeatArmorRestriction.Value = {
seatDef.ArmorRestriction seatDef.ArmorRestriction
@ -79,25 +70,6 @@ class Seat(private val seatDef : SeatDefinition, private val vehicle : Vehicle)
seatDef.ControlledWeapon seatDef.ControlledWeapon
} }
/**
* Given a player, can they access this `Seat` under its current restrictions and permissions.
* @param player the player who wants to sit
* @return `true` if the player can sit down in this `Seat`; `false`, otherwise
*/
def CanUseSeat(player : Player) : Boolean = {
var access : Boolean = false
val owner : Option[PlanetSideGUID] = vehicle.Owner
lockState match {
case VehicleLockState.Locked =>
access = owner.isEmpty || (owner.isDefined && player.GUID == owner.get)
case VehicleLockState.Group =>
access = Faction == player.Faction //TODO this is not correct
case VehicleLockState.Empire =>
access = Faction == player.Faction
}
access
}
/** /**
* Override the string representation to provide additional information. * Override the string representation to provide additional information.
* @return the string output * @return the string output
@ -110,11 +82,10 @@ class Seat(private val seatDef : SeatDefinition, private val vehicle : Vehicle)
object Seat { object Seat {
/** /**
* Overloaded constructor. * Overloaded constructor.
* @param vehicle the vehicle where this seat is installed
* @return a `Seat` object * @return a `Seat` object
*/ */
def apply(seatDef : SeatDefinition, vehicle : Vehicle) : Seat = { def apply(seatDef : SeatDefinition) : Seat = {
new Seat(seatDef, vehicle) new Seat(seatDef)
} }
/** /**
@ -122,13 +93,12 @@ object Seat {
* @return the string output * @return the string output
*/ */
def toString(obj : Seat) : String = { def toString(obj : Seat) : String = {
val weaponStr = if(obj.ControlledWeapon.isDefined) { " (gunner)" } else { "" }
val seatStr = if(obj.isOccupied) { val seatStr = if(obj.isOccupied) {
"occupied by %d".format(obj.Occupant.get.guid) s", occupied by player ${obj.Occupant.get.GUID}"
} }
else { else {
"unoccupied" ""
} }
s"{Seat$weaponStr: $seatStr}" s"seat$seatStr"
} }
} }

View file

@ -11,7 +11,8 @@ package net.psforever.objects.vehicles
object SeatArmorRestriction extends Enumeration { object SeatArmorRestriction extends Enumeration {
type Type = Value type Type = Value
val MaxOnly, val
MaxOnly,
NoMax, NoMax,
NoReinforcedOrMax NoReinforcedOrMax
= Value = Value

View file

@ -0,0 +1,37 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.vehicles
import akka.actor.Actor
import net.psforever.objects.Vehicle
/**
* An `Actor` that handles messages being dispatched to a specific `Vehicle`.<br>
* <br>
* Vehicle-controlling actors have two behavioral states - responsive and "`Disabled`."
* The latter is applicable only when the specific vehicle is being deconstructed.
* @param vehicle the `Vehicle` object being governed
*/
class VehicleControl(private val vehicle : Vehicle) extends Actor {
def receive : Receive = {
case Vehicle.PrepareForDeletion =>
context.become(Disabled)
case Vehicle.TrySeatPlayer(seat_num, player) =>
vehicle.Seat(seat_num) match {
case Some(seat) =>
if((seat.Occupant = player).contains(player)) {
sender ! Vehicle.VehicleMessages(player, Vehicle.CanSeatPlayer(vehicle, seat_num))
}
else {
sender ! Vehicle.VehicleMessages(player, Vehicle.CannotSeatPlayer(vehicle, seat_num))
}
case None =>
sender ! Vehicle.VehicleMessages(player, Vehicle.CannotSeatPlayer(vehicle, seat_num))
}
case _ => ;
}
def Disabled : Receive = {
case _ => ;
}
}

View file

@ -3,12 +3,12 @@ package net.psforever.objects.vehicles
/** /**
* An `Enumeration` of various access states for vehicle components, such as the seats and the trunk. * An `Enumeration` of various access states for vehicle components, such as the seats and the trunk.
* Organized to replicate the `PlanetsideAttributeMessage` value used for that given access level.
*/ */
object VehicleLockState extends Enumeration { object VehicleLockState extends Enumeration {
type Type = Value type Type = Value
val Empire, //owner's whole faction val Locked = Value(0) //owner only
Group, //owner's squad/platoon only val Group = Value(1) //owner's squad/platoon only
Locked //owner only val Empire = Value(3) //owner's whole faction
= Value
} }

View file

@ -4,7 +4,7 @@ package net.psforever.objects.zones
import akka.actor.{ActorContext, ActorRef, Props} import akka.actor.{ActorContext, ActorRef, Props}
import akka.routing.RandomPool import akka.routing.RandomPool
import net.psforever.objects.serverobject.doors.Base import net.psforever.objects.serverobject.doors.Base
import net.psforever.objects.{PlanetSideGameObject, Player} import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle}
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.objects.guid.NumberPoolHub import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.actor.UniqueNumberSystem import net.psforever.objects.guid.actor.UniqueNumberSystem
@ -14,6 +14,7 @@ import net.psforever.packet.GamePacket
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.Vector3 import net.psforever.types.Vector3
import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
/** /**
@ -49,6 +50,10 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
private val equipmentOnGround : ListBuffer[Equipment] = ListBuffer[Equipment]() private val equipmentOnGround : ListBuffer[Equipment] = ListBuffer[Equipment]()
/** Used by the `Zone` to coordinate `Equipment` dropping and collection requests. */ /** Used by the `Zone` to coordinate `Equipment` dropping and collection requests. */
private var ground : ActorRef = ActorRef.noSender private var ground : ActorRef = ActorRef.noSender
/** */
private var vehicles : List[Vehicle] = List[Vehicle]()
/** */
private var transport : ActorRef = ActorRef.noSender
private var bases : List[Base] = List() private var bases : List[Base] = List()
@ -69,6 +74,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
implicit val guid : NumberPoolHub = this.guid //passed into builderObject.Build implicitly implicit val guid : NumberPoolHub = this.guid //passed into builderObject.Build implicitly
accessor = context.actorOf(RandomPool(25).props(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystem.AllocateNumberPoolActors(guid))), s"$Id-uns") accessor = context.actorOf(RandomPool(25).props(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystem.AllocateNumberPoolActors(guid))), s"$Id-uns")
ground = context.actorOf(Props(classOf[ZoneGroundActor], equipmentOnGround), s"$Id-ground") ground = context.actorOf(Props(classOf[ZoneGroundActor], equipmentOnGround), s"$Id-ground")
transport = context.actorOf(Props(classOf[ZoneVehicleActor], this), s"$Id-vehicles")
Map.LocalObjects.foreach({ builderObject => Map.LocalObjects.foreach({ builderObject =>
builderObject.Build builderObject.Build
@ -165,6 +171,37 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
*/ */
def EquipmentOnGround : List[Equipment] = equipmentOnGround.toList def EquipmentOnGround : List[Equipment] = equipmentOnGround.toList
def Vehicles : List[Vehicle] = vehicles
def AddVehicle(vehicle : Vehicle) : List[Vehicle] = {
vehicles = vehicles :+ vehicle
Vehicles
}
def RemoveVehicle(vehicle : Vehicle) : List[Vehicle] = {
vehicles = recursiveFindVehicle(vehicles.iterator, vehicle) match {
case Some(index) =>
vehicles.take(index) ++ vehicles.drop(index + 1)
case None => ;
vehicles
}
Vehicles
}
@tailrec private def recursiveFindVehicle(iter : Iterator[Vehicle], target : Vehicle, index : Int = 0) : Option[Int] = {
if(!iter.hasNext) {
None
}
else {
if(iter.next.equals(target)) {
Some(index)
}
else {
recursiveFindVehicle(iter, target, index + 1)
}
}
}
/** /**
* Coordinate `Equipment` that has been dropped on the ground or to-be-dropped on the ground. * Coordinate `Equipment` that has been dropped on the ground or to-be-dropped on the ground.
* @return synchronized reference to the ground * @return synchronized reference to the ground
@ -175,6 +212,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
*/ */
def Ground : ActorRef = ground def Ground : ActorRef = ground
def Transport : ActorRef = transport
def MakeBases(num : Int) : List[Base] = { def MakeBases(num : Int) : List[Base] = {
bases = (0 to num).map(id => new Base(id)).toList bases = (0 to num).map(id => new Base(id)).toList
bases bases
@ -250,6 +289,10 @@ object Zone {
*/ */
final case class ItemFromGround(player : Player, item : Equipment) final case class ItemFromGround(player : Player, item : Equipment)
final case class SpawnVehicle(vehicle : Vehicle)
final case class DespawnVehicle(vehicle : Vehicle)
/** /**
* Message to report the packet messages that initialize the client. * Message to report the packet messages that initialize the client.
* @param list a `List` of `GamePacket` messages * @param list a `List` of `GamePacket` messages

View file

@ -0,0 +1,22 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.zones
import akka.actor.Actor
/**
* Synchronize management of the list of `Vehicles` maintained by some `Zone`.
* @param zone the `Zone` object
*/
class ZoneVehicleActor(zone : Zone) extends Actor {
//private[this] val log = org.log4s.getLogger
def receive : Receive = {
case Zone.SpawnVehicle(vehicle) =>
zone.AddVehicle(vehicle)
case Zone.DespawnVehicle(vehicle) =>
zone.RemoveVehicle(vehicle)
case _ => ;
}
}

View file

@ -6,7 +6,7 @@ import scodec.Codec
import scodec.codecs._ import scodec.codecs._
/** /**
* Attribute Type:<br> * Players/General:<br>
* Server to client : <br> * Server to client : <br>
* `0 - health`<br> * `0 - health`<br>
* `1 - healthMax`<br> * `1 - healthMax`<br>
@ -86,7 +86,19 @@ import scodec.codecs._
* `78 - Cavern Kills. Value is the number of kills`<br> * `78 - Cavern Kills. Value is the number of kills`<br>
* `106 - Custom Head` * `106 - Custom Head`
* Client to Server : <br> * Client to Server : <br>
* `106 - Custom Head` * `106 - Custom Head`<br>
* <br>
* Vehicles:<br>
* 0 - Vehicle health<br>
* 10 - Driver seat permissions (0 = Locked, 1 = Group, 3 = Empire)<br>
* 11 - Gunner seat(s) permissions (same)<br>
* 12 - Passenger seat(s) permissions (same) <br>
* 13 - Trunk permissions (same)<br>
* 21 - Asserts first time event eligibility / makes owner if no owner is assigned<br>
* 22 - Toggles gunner and passenger mount points (1 = hides, 0 = reveals; this also locks their permissions)<br>
* 68 - ???<br>
* 80 - Damage vehicle (unknown value)<br>
* 113 - ???
* @param player_guid the player * @param player_guid the player
* @param attribute_type na * @param attribute_type na
* @param attribute_value na * @param attribute_value na

View file

@ -803,6 +803,7 @@ object ObjectClass {
case ObjectClass.battlewagon_weapon_systemd => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.battlewagon_weapon_systemd => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.bolt_driver => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.bolt_driver => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.chainblade => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.chainblade => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.chaingun_p => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.colossus_burster => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.colossus_burster => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.colossus_burster_left => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.colossus_burster_left => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.colossus_burster_right => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.colossus_burster_right => ConstructorData.genericCodec(WeaponData.codec, "weapon")
@ -918,7 +919,6 @@ object ObjectClass {
case ObjectClass.cannon_dropship_20mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.cannon_dropship_20mm => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.chaingun_12mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.chaingun_12mm => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.chaingun_15mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.chaingun_15mm => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.chaingun_p => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.cycler_v2 => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.cycler_v2 => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.cycler_v3 => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.cycler_v3 => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.cycler_v4 => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.cycler_v4 => ConstructorData.genericCodec(WeaponData.codec, "weapon")
@ -1080,6 +1080,7 @@ object ObjectClass {
// case ObjectClass.aphelion_starfire_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") // case ObjectClass.aphelion_starfire_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.bolt_driver => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.bolt_driver => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.chainblade => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.chainblade => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
// case ObjectClass.chaingun_p => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
// case ObjectClass.colossus_burster => DroppedItemData.genericCodec(WeaponData.codec, "weapon") // case ObjectClass.colossus_burster => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
// case ObjectClass.colossus_burster_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") // case ObjectClass.colossus_burster_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
// case ObjectClass.colossus_burster_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") // case ObjectClass.colossus_burster_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
@ -1144,7 +1145,6 @@ object ObjectClass {
case ObjectClass.advanced_missile_launcher_t => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.advanced_missile_launcher_t => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.chaingun_12mm => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.chaingun_12mm => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.chaingun_15mm => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.chaingun_15mm => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.chaingun_p => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.cycler_v2 => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.cycler_v2 => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.cycler_v3 => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.cycler_v3 => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.cycler_v4 => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.cycler_v4 => DroppedItemData.genericCodec(WeaponData.codec, "weapon")

View file

@ -281,7 +281,8 @@ class ConverterTest extends Specification {
val hellfire_ammo_box = AmmoBox(PlanetSideGUID(432), hellfire_ammo) val hellfire_ammo_box = AmmoBox(PlanetSideGUID(432), hellfire_ammo)
val fury = Vehicle(PlanetSideGUID(413), fury_def) val fury = Vehicle(fury_def)
fury.GUID = PlanetSideGUID(413)
fury.Faction = PlanetSideEmpire.VS fury.Faction = PlanetSideEmpire.VS
fury.Position = Vector3(3674.8438f, 2732f, 91.15625f) fury.Position = Vector3(3674.8438f, 2732f, 91.15625f)
fury.Orientation = Vector3(0.0f, 0.0f, 90.0f) fury.Orientation = Vector3(0.0f, 0.0f, 90.0f)

View file

@ -0,0 +1,103 @@
// Copyright (c) 2017 PSForever
package objects
import net.psforever.objects.{GlobalDefinitions, Vehicle}
import net.psforever.objects.definition.SeatDefinition
import net.psforever.objects.vehicles.{Seat, SeatArmorRestriction, VehicleLockState}
import org.specs2.mutable._
class VehicleTest extends Specification {
"SeatDefinition" should {
val seat = new SeatDefinition
seat.ArmorRestriction = SeatArmorRestriction.MaxOnly
seat.Bailable = true
seat.ControlledWeapon = 5
"define (default)" in {
val t_seat = new SeatDefinition
t_seat.ArmorRestriction mustEqual SeatArmorRestriction.NoMax
t_seat.Bailable mustEqual false
t_seat.ControlledWeapon mustEqual None
}
"define (custom)" in {
seat.ArmorRestriction mustEqual SeatArmorRestriction.MaxOnly
seat.Bailable mustEqual true
seat.ControlledWeapon mustEqual Some(5)
}
}
"VehicleDefinition" should {
"define" in {
val fury = GlobalDefinitions.fury
fury.CanBeOwned mustEqual true
fury.CanCloak mustEqual false
fury.Seats.size mustEqual 1
fury.Seats(0).Bailable mustEqual true
fury.Seats(0).ControlledWeapon mustEqual Some(1)
fury.MountPoints.size mustEqual 2
fury.MountPoints.get(0) mustEqual Some(0)
fury.MountPoints.get(1) mustEqual None
fury.MountPoints.get(2) mustEqual Some(0)
fury.Weapons.size mustEqual 1
fury.Weapons.get(0) mustEqual None
fury.Weapons.get(1) mustEqual Some(GlobalDefinitions.fury_weapon_systema)
fury.TrunkSize.width mustEqual 11
fury.TrunkSize.height mustEqual 11
fury.TrunkOffset mustEqual 30
}
}
"Seat" should {
val seat_def = new SeatDefinition
seat_def.ArmorRestriction = SeatArmorRestriction.MaxOnly
seat_def.Bailable = true
seat_def.ControlledWeapon = 5
"construct" in {
val seat = new Seat(seat_def)
seat.ArmorRestriction mustEqual SeatArmorRestriction.MaxOnly
seat.Bailable mustEqual true
seat.ControlledWeapon mustEqual Some(5)
seat.isOccupied mustEqual false
seat.Occupant mustEqual None
}
}
"Vehicle" should {
"construct" in {
Vehicle(GlobalDefinitions.fury)
ok
}
"construct (detailed)" in {
val fury_vehicle = Vehicle(GlobalDefinitions.fury)
fury_vehicle.Owner mustEqual None
fury_vehicle.Seats.size mustEqual 1
fury_vehicle.Seats.head.ArmorRestriction mustEqual SeatArmorRestriction.NoMax
fury_vehicle.Seats.head.isOccupied mustEqual false
fury_vehicle.Seats.head.Occupant mustEqual None
fury_vehicle.Seats.head.Bailable mustEqual true
fury_vehicle.Seats.head.ControlledWeapon mustEqual Some(1)
fury_vehicle.PermissionGroup(0) mustEqual Some(VehicleLockState.Locked) //driver
fury_vehicle.PermissionGroup(1) mustEqual Some(VehicleLockState.Empire) //gunner
fury_vehicle.PermissionGroup(2) mustEqual Some(VehicleLockState.Empire) //passenger
fury_vehicle.PermissionGroup(3) mustEqual Some(VehicleLockState.Locked) //trunk
fury_vehicle.Weapons.size mustEqual 1
fury_vehicle.Weapons.get(0) mustEqual None
fury_vehicle.Weapons.get(1).isDefined mustEqual true
fury_vehicle.Weapons(1).Equipment.isDefined mustEqual true
fury_vehicle.Weapons(1).Equipment.get.Definition mustEqual GlobalDefinitions.fury.Weapons(1)
fury_vehicle.WeaponControlledFromSeat(0) mustEqual fury_vehicle.Weapons(1).Equipment
fury_vehicle.Trunk.Width mustEqual 11
fury_vehicle.Trunk.Height mustEqual 11
fury_vehicle.Trunk.Offset mustEqual 30
fury_vehicle.GetSeatFromMountPoint(0) mustEqual Some(0)
fury_vehicle.GetSeatFromMountPoint(1) mustEqual None
fury_vehicle.GetSeatFromMountPoint(2) mustEqual Some(0)
fury_vehicle.Decal mustEqual 0
fury_vehicle.Health mustEqual fury_vehicle.Definition.MaxHealth
}
}
}

View file

@ -18,8 +18,10 @@ import net.psforever.objects.serverobject.builders.{DoorObjectBuilder, IFFLockOb
import org.slf4j import org.slf4j
import org.fusesource.jansi.Ansi._ import org.fusesource.jansi.Ansi._
import org.fusesource.jansi.Ansi.Color._ import org.fusesource.jansi.Ansi.Color._
import services.ServiceManager
import services.avatar._ import services.avatar._
import services.local._ import services.local._
import services.vehicle.VehicleService
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import scala.concurrent.Await import scala.concurrent.Await
@ -206,6 +208,7 @@ object PsLogin {
serviceManager ! ServiceManager.Register(RandomPool(50).props(Props[TaskResolver]), "taskResolver") serviceManager ! ServiceManager.Register(RandomPool(50).props(Props[TaskResolver]), "taskResolver")
serviceManager ! ServiceManager.Register(Props[AvatarService], "avatar") serviceManager ! ServiceManager.Register(Props[AvatarService], "avatar")
serviceManager ! ServiceManager.Register(Props[LocalService], "local") serviceManager ! ServiceManager.Register(Props[LocalService], "local")
serviceManager ! ServiceManager.Register(Props[VehicleService], "vehicle")
serviceManager ! ServiceManager.Register(Props(classOf[InterstellarCluster], createContinents()), "galaxy") serviceManager ! ServiceManager.Register(Props(classOf[InterstellarCluster], createContinents()), "galaxy")
/** Create two actors for handling the login and world server endpoints */ /** Create two actors for handling the login and world server endpoints */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,282 @@
// Copyright (c) 2017 PSForever
package scripts
/**
* The basic compiled tasks for assigning (registering) and revoking (unregistering) globally unique identifiers.<br>
* <br>
* Almost all of these functions will be invoked from `WorldSessionActor`.
* Some of the "unregistering" functions will invoke on delayed `Service` operations,
* indicating behavior that is not user/observer dependent.
* The object's (current) `Zone` must also be knowable since the GUID systems are tied to individual zones.
* For simplicity, all functions have the same format where the hook into the GUID system is an `implicit` parameter.
* It will get passed from the more complicated functions down into the less complicated functions,
* until it has found the basic number assignment functionality.<br>
* <br>
* All functions produce a `TaskResolver.GiveTask` container object that is expected to be used by a `TaskResolver`.
* These "task containers" can also be unpackaged into their tasks, sorted into other containers,
* and combined with other "task containers" to enact more complicated sequences of operations.
*/
object GUIDTask {
import akka.actor.ActorRef
import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.guid.{Task, TaskResolver}
import net.psforever.objects.{EquipmentSlot, Player, Tool, Vehicle}
import scala.annotation.tailrec
/**
* Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers.<br>
* <br>
* Regardless of the complexity of the object provided to this function, only the current depth will be assigned a GUID.
* This is the most basic operation that all objects that can be assigned a GUID must perform.
* @param obj the object being registered
* @param guid implicit reference to a unique number system
* @return a `TaskResolver.GiveTask` message
*/
def RegisterObjectTask(obj : IdentifiableEntity)(implicit guid : ActorRef) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private val localObject = obj
private val localAccessor = guid
override def isComplete : Task.Resolution.Value = if(localObject.HasGUID) {
Task.Resolution.Success
}
else {
Task.Resolution.Incomplete
}
def Execute(resolver : ActorRef) : Unit = {
import net.psforever.objects.guid.actor.Register
localAccessor ! Register(localObject, "dynamic", resolver) //TODO pool should not be hardcoded
}
})
}
/**
* Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, as a `Tool`.<br>
* <br>
* `Tool` objects are complicated by an internal structure informally called a "magazine feed."
* The objects in the magazine feed are called `AmmoBox` objects.
* Each `AmmoBox` object can be registered to a unique number system much like the `Tool` itself; and,
* each must be registered properly for the whole of the `Tool` to be communicated from the server to the client.
* While the matter has been abstracted for convenience, most `Tool` objects will have only one `AmmoBox` at a time
* and the common outlier will only be two.<br>
* <br>
* Do not invoke this function unless certain the object will be of type `Tool`,
* else use a more general function to differentiate between simple and complex objects.
* @param obj the `Tool` object being registered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterEquipment`
* @return a `TaskResolver.GiveTask` message
*/
def RegisterTool(obj : Tool)(implicit guid : ActorRef) : TaskResolver.GiveTask = {
val ammoTasks : List[TaskResolver.GiveTask] = (0 until obj.MaxAmmoSlot).map(ammoIndex => RegisterObjectTask(obj.AmmoSlots(ammoIndex).Box)).toList
TaskResolver.GiveTask(RegisterObjectTask(obj).task, ammoTasks)
}
/**
* Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers,
* after determining whether the object is complex (`Tool`) or simple.<br>
* <br>
* The objects in this case are specifically `Equipment`, a subclass of the basic register-able `IdentifiableEntity`.
* About five subclasses of `Equipment` exist, but they decompose into two groups - "complex objects" and "simple objects."
* "Simple objects" are most groups of `Equipment` and just their own GUID to be registered.
* "Complex objects" are just the `Tool` category of `Equipment`.
* They have internal objects that must also have their GUID's registered to function.<br>
* <br>
* Using this function when passing unknown `Equipment` is recommended.
* The type will be sorted and the object will be handled according to its complexity level.
* @param obj the `Equipment` object being registered
* @param guid implicit reference to a unique number system
* @return a `TaskResolver.GiveTask` message
*/
def RegisterEquipment(obj : Equipment)(implicit guid : ActorRef) : TaskResolver.GiveTask = {
obj match {
case tool : Tool =>
RegisterTool(tool)
case _ =>
RegisterObjectTask(obj)
}
}
/**
* Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, as a `Player`.<br>
* <br>
* `Player` objects are far more complicated than `Tools` (but they are not `Equipment`).
* A player has an inventory in which it can hold a countable number of `Equipment`; and,
* this inventory holds a sub-inventory with its own countable number of `Equipment`.
* Although a process of completing and inserting `Equipment` into the inventories that looks orderly can be written,
* this function assumes that the player is already fully composed.
* Use this function for an sudden introduction of the player into his environment
* (as defined by the scope of the unique number system).
* For working with processes concerning these "orderly insertions,"
* a task built of lesser registration tasks and supporting tasks should be written instead.
* @param tplayer the `Player` object being registered
* @param guid implicit reference to a unique number system
* @return a `TaskResolver.GiveTask` message
*/
def RegisterAvatar(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = {
import net.psforever.objects.LockerContainer
import net.psforever.objects.inventory.InventoryItem
val holsterTasks = recursiveHolsterTaskBuilding(tplayer.Holsters().iterator, RegisterEquipment)
val fifthHolsterTask = tplayer.Slot(5).Equipment match {
case Some(locker) =>
RegisterObjectTask(locker) :: locker.asInstanceOf[LockerContainer].Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => RegisterEquipment(entry.obj)}).toList
case None =>
List.empty[TaskResolver.GiveTask];
}
val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => RegisterEquipment(entry.obj)})
TaskResolver.GiveTask(RegisterObjectTask(tplayer).task, holsterTasks ++ fifthHolsterTask ++ inventoryTasks)
}
/**
* Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, as a `Vehicle`.<br>
* <br>
* `Vehicle` objects are far more complicated than `Tools` (but they are not `Equipment`).
* A vehicle has an inventory in which it can hold a countable number of `Equipment`; and,
* it may possess weapons (`Tools`, usually) that are firmly mounted on its outside.
* (This is similar to the holsters on a `Player` object but they can not be swapped out for other `Equipment` or for nothing.)
* Although a process of completing and inserting `Equipment` into the inventories that looks orderly can be written,
* this function assumes that the vehicle is already fully composed.
* Use this function for an sudden introduction of the vehicle into its environment
* (as defined by the scope of the unique number system).
* For working with processes concerning these "orderly insertions,"
* a task built of lesser registration tasks and supporting tasks should be written instead.
* @param vehicle the `Vehicle` object being registered
* @param guid implicit reference to a unique number system
* @return a `TaskResolver.GiveTask` message
*/
def RegisterVehicle(vehicle : Vehicle)(implicit guid : ActorRef) : TaskResolver.GiveTask = {
import net.psforever.objects.inventory.InventoryItem
val weaponTasks = vehicle.Weapons.map({ case(_ : Int, entry : EquipmentSlot) => RegisterEquipment(entry.Equipment.get)}).toList
val inventoryTasks = vehicle.Trunk.Items.map({ case((_ : Int, entry : InventoryItem)) => RegisterEquipment(entry.obj)})
TaskResolver.GiveTask(RegisterObjectTask(vehicle).task, weaponTasks ++ inventoryTasks)
}
/**
* Construct tasking that unregisters an object from a globally unique identifier system.<br>
* <br>
* This task performs an operation that reverses the effect of `RegisterObjectTask`.
* It is the most basic operation that all objects that can have their GUIDs revoked must perform.
* @param obj the object being unregistered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterObjectTask`
* @return a `TaskResolver.GiveTask` message
*/
def UnregisterObjectTask(obj : IdentifiableEntity)(implicit guid : ActorRef) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private val localObject = obj
private val localAccessor = guid
override def isComplete : Task.Resolution.Value = if(!localObject.HasGUID) {
Task.Resolution.Success
}
else {
Task.Resolution.Incomplete
}
def Execute(resolver : ActorRef) : Unit = {
import net.psforever.objects.guid.actor.Unregister
localAccessor ! Unregister(localObject, resolver)
}
}
)
}
/**
* Construct tasking that unregisters a `Tool` object from a globally unique identifier system.<br>
* <br>
* This task performs an operation that reverses the effect of `RegisterTool`.
* @param obj the `Tool` object being unregistered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterTool`
* @return a `TaskResolver.GiveTask` message
*/
def UnregisterTool(obj : Tool)(implicit guid : ActorRef) : TaskResolver.GiveTask = {
val ammoTasks : List[TaskResolver.GiveTask] = (0 until obj.MaxAmmoSlot).map(ammoIndex => UnregisterObjectTask(obj.AmmoSlots(ammoIndex).Box)).toList
TaskResolver.GiveTask(UnregisterObjectTask(obj).task, ammoTasks)
}
/**
* Construct tasking that unregisters an object from a globally unique identifier system
* after determining whether the object is complex (`Tool`) or simple.<br>
* <br>
* This task performs an operation that reverses the effect of `RegisterEquipment`.
* @param obj the `Equipment` object being unregistered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterEquipment`
* @return a `TaskResolver.GiveTask` message
*/
def UnregisterEquipment(obj : Equipment)(implicit guid : ActorRef) : TaskResolver.GiveTask = {
obj match {
case tool : Tool =>
UnregisterTool(tool)
case _ =>
UnregisterObjectTask(obj)
}
}
/**
* Construct tasking that unregisters a `Player` object from a globally unique identifier system.<br>
* <br>
* This task performs an operation that reverses the effect of `RegisterAvatar`.
* @param tplayer the `Player` object being unregistered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterAvatar`
* @return a `TaskResolver.GiveTask` message
*/
def UnregisterAvatar(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = {
import net.psforever.objects.LockerContainer
import net.psforever.objects.inventory.InventoryItem
val holsterTasks = recursiveHolsterTaskBuilding(tplayer.Holsters().iterator, UnregisterEquipment)
val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => UnregisterEquipment(entry.obj)})
val fifthHolsterTask = tplayer.Slot(5).Equipment match {
case Some(locker) =>
UnregisterObjectTask(locker) :: locker.asInstanceOf[LockerContainer].Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => UnregisterEquipment(entry.obj)}).toList
case None =>
List.empty[TaskResolver.GiveTask];
}
TaskResolver.GiveTask(UnregisterObjectTask(tplayer).task, holsterTasks ++ fifthHolsterTask ++ inventoryTasks)
}
/**
* Construct tasking that unregisters a `Vehicle` object from a globally unique identifier system.<br>
* <br>
* This task performs an operation that reverses the effect of `RegisterVehicle`.
* @param vehicle the `Vehicle` object being unregistered
* @param guid implicit reference to a unique number system
* @see `GUIDTask.RegisterVehicle`
* @return a `TaskResolver.GiveTask` message
*/
def UnregisterVehicle(vehicle : Vehicle)(implicit guid : ActorRef) : TaskResolver.GiveTask = {
import net.psforever.objects.inventory.InventoryItem
val weaponTasks = vehicle.Weapons.map({ case(_ : Int, entry : EquipmentSlot) => UnregisterTool(entry.Equipment.get.asInstanceOf[Tool]) }).toList
val inventoryTasks = vehicle.Trunk.Items.map({ case((_ : Int, entry : InventoryItem)) => UnregisterEquipment(entry.obj)})
TaskResolver.GiveTask(UnregisterObjectTask(vehicle).task, weaponTasks ++ inventoryTasks)
}
/**
* Iterate over a group of `EquipmentSlot`s, some of which may be occupied with an item.
* Use `func` on any discovered `Equipment` to transform items into tasking, and add the tasking to a `List`.
* @param iter the `Iterator` of `EquipmentSlot`s
* @param func the function used to build tasking from any discovered `Equipment`;
* strictly either `RegisterEquipment` or `UnregisterEquipment`
* @param list a persistent `List` of `Equipment` tasking
* @return a `List` of `Equipment` tasking
*/
@tailrec private def recursiveHolsterTaskBuilding(iter : Iterator[EquipmentSlot], func : ((Equipment)=>TaskResolver.GiveTask), list : List[TaskResolver.GiveTask] = Nil)(implicit guid : ActorRef) : List[TaskResolver.GiveTask] = {
if(!iter.hasNext) {
list
}
else {
iter.next.Equipment match {
case Some(item) =>
recursiveHolsterTaskBuilding(iter, func, list :+ func(item))
case None =>
recursiveHolsterTaskBuilding(iter, func, list)
}
}
}
}

View file

@ -1,3 +1,5 @@
package services
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
import akka.actor.{Actor, ActorIdentity, ActorRef, ActorSystem, Identify, Props} import akka.actor.{Actor, ActorIdentity, ActorRef, ActorSystem, Identify, Props}

View file

@ -0,0 +1,28 @@
// Copyright (c) 2017 PSForever
package services.avatar
import net.psforever.objects.equipment.Equipment
import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream}
import net.psforever.packet.game.objectcreate.ConstructorData
import net.psforever.types.{ExoSuitType, Vector3}
object AvatarResponse {
trait Response
final case class ArmorChanged(suit : ExoSuitType.Value, subtype : Int) extends Response
//final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Response
final case class EquipmentInHand(slot : Int, item : Equipment) extends Response
final case class EquipmentOnGround(pos : Vector3, orient : Vector3, item : Equipment) extends Response
final case class LoadPlayer(pdata : ConstructorData) extends Response
// final case class unLoadMap() extends Response
// final case class LoadMap() extends Response
final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response
final case class ObjectHeld(slot : Int) extends Response
final case class PlanetSideAttribute(attribute_type : Int, attribute_value : Long) extends Response
final case class PlayerState(msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Response
final case class Reload(mag : Int) 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
// final case class ChangeWeapon(facingYaw : Int) extends Response
}

View file

@ -31,39 +31,39 @@ class AvatarService extends Actor {
action match { action match {
case AvatarAction.ArmorChanged(player_guid, suit, subtype) => case AvatarAction.ArmorChanged(player_guid, suit, subtype) =>
AvatarEvents.publish( AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.ArmorChanged(suit, subtype)) AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ArmorChanged(suit, subtype))
) )
case AvatarAction.EquipmentInHand(player_guid, slot, obj) => case AvatarAction.EquipmentInHand(player_guid, slot, obj) =>
AvatarEvents.publish( AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.EquipmentInHand(slot, obj)) AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EquipmentInHand(slot, obj))
) )
case AvatarAction.EquipmentOnGround(player_guid, pos, orient, obj) => case AvatarAction.EquipmentOnGround(player_guid, pos, orient, obj) =>
AvatarEvents.publish( AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.EquipmentOnGround(pos, orient, obj)) AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EquipmentOnGround(pos, orient, obj))
) )
case AvatarAction.LoadPlayer(player_guid, pdata) => case AvatarAction.LoadPlayer(player_guid, pdata) =>
AvatarEvents.publish( AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.LoadPlayer(pdata)) AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadPlayer(pdata))
) )
case AvatarAction.ObjectDelete(player_guid, item_guid, unk) => case AvatarAction.ObjectDelete(player_guid, item_guid, unk) =>
AvatarEvents.publish( AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.ObjectDelete(item_guid, unk)) AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ObjectDelete(item_guid, unk))
) )
case AvatarAction.ObjectHeld(player_guid, slot) => case AvatarAction.ObjectHeld(player_guid, slot) =>
AvatarEvents.publish( AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.ObjectHeld(slot)) AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ObjectHeld(slot))
) )
case AvatarAction.PlanetsideAttribute(guid, attribute_type, attribute_value) => case AvatarAction.PlanetsideAttribute(guid, attribute_type, attribute_value) =>
AvatarEvents.publish( AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarServiceResponse.PlanetSideAttribute(attribute_type, attribute_value)) AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarResponse.PlanetSideAttribute(attribute_type, attribute_value))
) )
case AvatarAction.PlayerState(guid, msg, spectator, weapon) => case AvatarAction.PlayerState(guid, msg, spectator, weapon) =>
AvatarEvents.publish( AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarServiceResponse.PlayerState(msg, spectator, weapon)) AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarResponse.PlayerState(msg, spectator, weapon))
) )
case AvatarAction.Reload(player_guid, mag) => case AvatarAction.Reload(player_guid, mag) =>
AvatarEvents.publish( AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.Reload(mag)) AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.Reload(mag))
) )
case _ => ; case _ => ;
} }

View file

@ -1,34 +1,10 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package services.avatar package services.avatar
import net.psforever.objects.equipment.Equipment import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream}
import net.psforever.packet.game.objectcreate.ConstructorData
import net.psforever.types.{ExoSuitType, Vector3}
import services.GenericEventBusMsg import services.GenericEventBusMsg
final case class AvatarServiceResponse(toChannel : String, final case class AvatarServiceResponse(toChannel : String,
avatar_guid : PlanetSideGUID, avatar_guid : PlanetSideGUID,
replyMessage : AvatarServiceResponse.Response replyMessage : AvatarResponse.Response
) extends GenericEventBusMsg ) extends GenericEventBusMsg
object AvatarServiceResponse {
trait Response
final case class ArmorChanged(suit : ExoSuitType.Value, subtype : Int) extends Response
//final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Response
final case class EquipmentInHand(slot : Int, item : Equipment) extends Response
final case class EquipmentOnGround(pos : Vector3, orient : Vector3, item : Equipment) extends Response
final case class LoadPlayer(pdata : ConstructorData) extends Response
// final case class unLoadMap() extends Response
// final case class LoadMap() extends Response
final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response
final case class ObjectHeld(slot : Int) extends Response
final case class PlanetSideAttribute(attribute_type : Int, attribute_value : Long) extends Response
final case class PlayerState(msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Response
final case class Reload(mag : Int) 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
// final case class ChangeWeapon(facingYaw : Int) extends Response
}

View file

@ -0,0 +1,15 @@
// Copyright (c) 2017 PSForever
package services.local
import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound}
import net.psforever.types.Vector3
object LocalResponse {
trait Response
final case class DoorOpens(door_guid : PlanetSideGUID) extends Response
final case class DoorCloses(door_guid : PlanetSideGUID) extends Response
final case class HackClear(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long) extends Response
final case class HackObject(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long) extends Response
final case class TriggerSound(sound : TriggeredSound.Value, pos : Vector3, unk : Int, volume : Float) extends Response
}

View file

@ -6,7 +6,6 @@ import services.local.support.{DoorCloseActor, HackClearActor}
import services.{GenericEventBus, Service} import services.{GenericEventBus, Service}
class LocalService extends Actor { class LocalService extends Actor {
//import LocalService._
private val doorCloser = context.actorOf(Props[DoorCloseActor], "local-door-closer") private val doorCloser = context.actorOf(Props[DoorCloseActor], "local-door-closer")
private val hackClearer = context.actorOf(Props[HackClearActor], "local-hack-clearer") private val hackClearer = context.actorOf(Props[HackClearActor], "local-hack-clearer")
private [this] val log = org.log4s.getLogger private [this] val log = org.log4s.getLogger
@ -19,7 +18,7 @@ class LocalService extends Actor {
def receive = { def receive = {
case Service.Join(channel) => case Service.Join(channel) =>
val path = s"/$channel/LocalEnvironment" val path = s"/$channel/Local"
val who = sender() val who = sender()
log.info(s"$who has joined $path") log.info(s"$who has joined $path")
LocalEvents.subscribe(who, path) LocalEvents.subscribe(who, path)
@ -33,24 +32,24 @@ class LocalService extends Actor {
case LocalAction.DoorOpens(player_guid, zone, door) => case LocalAction.DoorOpens(player_guid, zone, door) =>
doorCloser ! DoorCloseActor.DoorIsOpen(door, zone) doorCloser ! DoorCloseActor.DoorIsOpen(door, zone)
LocalEvents.publish( LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/LocalEnvironment", player_guid, LocalServiceResponse.DoorOpens(door.GUID)) LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.DoorOpens(door.GUID))
) )
case LocalAction.DoorCloses(player_guid, door_guid) => case LocalAction.DoorCloses(player_guid, door_guid) =>
LocalEvents.publish( LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/LocalEnvironment", player_guid, LocalServiceResponse.DoorCloses(door_guid)) LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.DoorCloses(door_guid))
) )
case LocalAction.HackClear(player_guid, target, unk1, unk2) => case LocalAction.HackClear(player_guid, target, unk1, unk2) =>
LocalEvents.publish( LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/LocalEnvironment", player_guid, LocalServiceResponse.HackClear(target.GUID, unk1, unk2)) LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.HackClear(target.GUID, unk1, unk2))
) )
case LocalAction.HackTemporarily(player_guid, zone, target, unk1, unk2) => case LocalAction.HackTemporarily(player_guid, zone, target, unk1, unk2) =>
hackClearer ! HackClearActor.ObjectIsHacked(target, zone, unk1, unk2) hackClearer ! HackClearActor.ObjectIsHacked(target, zone, unk1, unk2)
LocalEvents.publish( LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/LocalEnvironment", player_guid, LocalServiceResponse.HackObject(target.GUID, unk1, unk2)) LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.HackObject(target.GUID, unk1, unk2))
) )
case LocalAction.TriggerSound(player_guid, sound, pos, unk, volume) => case LocalAction.TriggerSound(player_guid, sound, pos, unk, volume) =>
LocalEvents.publish( LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/LocalEnvironment", player_guid, LocalServiceResponse.TriggerSound(sound, pos, unk, volume)) LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.TriggerSound(sound, pos, unk, volume))
) )
case _ => ; case _ => ;
} }
@ -58,13 +57,13 @@ class LocalService extends Actor {
//response from DoorCloseActor //response from DoorCloseActor
case DoorCloseActor.CloseTheDoor(door_guid, zone_id) => case DoorCloseActor.CloseTheDoor(door_guid, zone_id) =>
LocalEvents.publish( LocalEvents.publish(
LocalServiceResponse(s"/$zone_id/LocalEnvironment", Service.defaultPlayerGUID, LocalServiceResponse.DoorCloses(door_guid)) LocalServiceResponse(s"/$zone_id/Local", Service.defaultPlayerGUID, LocalResponse.DoorCloses(door_guid))
) )
//response from HackClearActor //response from HackClearActor
case HackClearActor.ClearTheHack(target_guid, zone_id, unk1, unk2) => case HackClearActor.ClearTheHack(target_guid, zone_id, unk1, unk2) =>
LocalEvents.publish( LocalEvents.publish(
LocalServiceResponse(s"/$zone_id/LocalEnvironment", Service.defaultPlayerGUID, LocalServiceResponse.HackClear(target_guid, unk1, unk2)) LocalServiceResponse(s"/$zone_id/Local", Service.defaultPlayerGUID, LocalResponse.HackClear(target_guid, unk1, unk2))
) )
case msg => case msg =>

View file

@ -1,21 +1,10 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package services.local package services.local
import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound} import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.Vector3
import services.GenericEventBusMsg import services.GenericEventBusMsg
final case class LocalServiceResponse(toChannel : String, final case class LocalServiceResponse(toChannel : String,
avatar_guid : PlanetSideGUID, avatar_guid : PlanetSideGUID,
replyMessage : LocalServiceResponse.Response replyMessage : LocalResponse.Response
) extends GenericEventBusMsg ) extends GenericEventBusMsg
object LocalServiceResponse {
trait Response
final case class DoorOpens(door_guid : PlanetSideGUID) extends Response
final case class DoorCloses(door_guid : PlanetSideGUID) extends Response
final case class HackClear(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long) extends Response
final case class HackObject(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long) extends Response
final case class TriggerSound(sound : TriggeredSound.Value, pos : Vector3, unk : Int, volume : Float) extends Response
}

View file

@ -0,0 +1,22 @@
// Copyright (c) 2017 PSForever
package services.vehicle
import net.psforever.objects.Vehicle
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.ConstructorData
import net.psforever.types.Vector3
object VehicleAction {
trait Action
final case class Awareness(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID) extends Action
final case class ChildObjectState(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, pitch : Float, yaw : Float) extends Action
final case class DismountVehicle(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean) extends Action
final case class KickPassenger(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean) extends Action
final case class LoadVehicle(player_guid : PlanetSideGUID, vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Action
final case class MountVehicle(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, seat : Int) extends Action
final case class SeatPermissions(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, seat_group : Int, permission : Long) extends Action
final case class UnloadVehicle(player_guid : PlanetSideGUID, continent : Zone, vehicle : Vehicle) 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
}

View file

@ -0,0 +1,21 @@
// Copyright (c) 2017 PSForever
package services.vehicle
import net.psforever.objects.Vehicle
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.ConstructorData
import net.psforever.types.Vector3
object VehicleResponse {
trait Response
final case class Awareness(vehicle_guid : PlanetSideGUID) extends Response
final case class ChildObjectState(object_guid : PlanetSideGUID, pitch : Float, yaw : Float) extends Response
final case class DismountVehicle(unk1 : Int, unk2 : Boolean) extends Response
final case class KickPassenger(unk1 : Int, unk2 : Boolean) extends Response
final case class LoadVehicle(vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Response
final case class MountVehicle(object_guid : PlanetSideGUID, seat : Int) extends Response
final case class SeatPermissions(vehicle_guid : PlanetSideGUID, seat_group : Int, permission : Long) extends Response
final case class UnloadVehicle(vehicle_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
}

View file

@ -0,0 +1,91 @@
// Copyright (c) 2017 PSForever
package services.vehicle
import akka.actor.{Actor, ActorRef, Props}
import services.vehicle.support.{DeconstructionActor, VehicleContextActor}
import services.{GenericEventBus, Service}
class VehicleService extends Actor {
private val vehicleContext : ActorRef = context.actorOf(Props[VehicleContextActor], "vehicle-context-root")
private val vehicleDecon : ActorRef = context.actorOf(Props[DeconstructionActor], "vehicle-decon-agent")
vehicleDecon ! DeconstructionActor.RequestTaskResolver
private [this] val log = org.log4s.getLogger
override def preStart = {
log.info("Starting...")
}
val VehicleEvents = new GenericEventBus[VehicleServiceResponse]
def receive = {
case Service.Join(channel) =>
val path = s"/$channel/Vehicle"
val who = sender()
log.info(s"$who has joined $path")
VehicleEvents.subscribe(who, path)
case Service.Leave() =>
VehicleEvents.unsubscribe(sender())
case Service.LeaveAll() =>
VehicleEvents.unsubscribe(sender())
case VehicleServiceMessage(forChannel, action) =>
action match {
case VehicleAction.Awareness(player_guid, vehicle_guid) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.Awareness(vehicle_guid))
)
case VehicleAction.ChildObjectState(player_guid, object_guid, pitch, yaw) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.ChildObjectState(object_guid, pitch, yaw))
)
case VehicleAction.DismountVehicle(player_guid, unk1, unk2) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.DismountVehicle(unk1, unk2))
)
case VehicleAction.KickPassenger(player_guid, unk1, unk2) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickPassenger(unk1, unk2))
)
case VehicleAction.LoadVehicle(player_guid, vehicle, vtype, vguid, vdata) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.LoadVehicle(vehicle, vtype, vguid, vdata))
)
case VehicleAction.MountVehicle(player_guid, vehicle_guid, seat) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.MountVehicle(vehicle_guid, seat))
)
case VehicleAction.SeatPermissions(player_guid, vehicle_guid, seat_group, permission) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission))
)
case VehicleAction.VehicleState(player_guid, vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.VehicleState(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6))
)
case _ => ;
}
//message to VehicleContext
case VehicleServiceMessage.GiveActorControl(vehicle, actorName) =>
vehicleContext ! VehicleServiceMessage.GiveActorControl(vehicle, actorName)
//message to VehicleContext
case VehicleServiceMessage.RevokeActorControl(vehicle) =>
vehicleContext ! VehicleServiceMessage.RevokeActorControl(vehicle)
//message to DeconstructionActor
case VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent) =>
vehicleDecon ! DeconstructionActor.RequestDeleteVehicle(vehicle, continent)
//response from DeconstructionActor
case DeconstructionActor.DeleteVehicle(vehicle_guid, zone_id) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$zone_id/Vehicle", Service.defaultPlayerGUID, VehicleResponse.UnloadVehicle(vehicle_guid))
)
case msg =>
log.info(s"Unhandled message $msg from $sender")
}
}

View file

@ -0,0 +1,13 @@
// Copyright (c) 2017 PSForever
package services.vehicle
import net.psforever.objects.Vehicle
import net.psforever.objects.zones.Zone
final case class VehicleServiceMessage(forChannel : String, actionMessage : VehicleAction.Action)
object VehicleServiceMessage {
final case class GiveActorControl(vehicle : Vehicle, actorName : String)
final case class RevokeActorControl(vehicle : Vehicle)
final case class RequestDeleteVehicle(vehicle : Vehicle, continent : Zone)
}

View file

@ -0,0 +1,11 @@
// Copyright (c) 2017 PSForever
package services.vehicle
import net.psforever.packet.game.PlanetSideGUID
import services.GenericEventBusMsg
final case class VehicleServiceResponse(toChannel : String,
avatar_guid : PlanetSideGUID,
replyMessage : VehicleResponse.Response
) extends GenericEventBusMsg

View file

@ -0,0 +1,181 @@
// Copyright (c) 2017 PSForever
package services.vehicle.support
import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects.Vehicle
import net.psforever.objects.vehicles.Seat
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
import scripts.GUIDTask
import services.ServiceManager
import services.ServiceManager.Lookup
import services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.annotation.tailrec
import scala.concurrent.duration._
/**
* Manage a previously-functioning vehicle as it is being deconstructed.<br>
* <br>
* A reference to a vehicle should be passed to this object as soon as it is going to be cleaned-up from the game world.
* Once accepted, only a few seconds will remain before the vehicle is deleted.
* To ensure that no players are lost in the deletion, all occupants of the vehicle are kicked out.
* Furthermore, the vehicle is rendered "dead" and inaccessible right up to the point where it is removed.
*/
class DeconstructionActor extends Actor {
/** The periodic `Executor` that scraps the next vehicle on the list */
private var scrappingProcess : Cancellable = DeconstructionActor.DefaultProcess
/** A `List` of currently doomed vehicles */
private var vehicles : List[DeconstructionActor.VehicleEntry] = Nil
/** The manager that helps unregister the vehicle from its current GUID scope */
private var taskResolver : ActorRef = Actor.noSender
//private[this] val log = org.log4s.getLogger
def receive : Receive = {
/*
ask for a resolver to deal with the GUID system
when the TaskResolver is finally delivered, switch over to a behavior that actually deals with submitted vehicles
*/
case DeconstructionActor.RequestTaskResolver =>
ServiceManager.serviceManager ! Lookup("taskResolver")
case ServiceManager.LookupResult("taskResolver", endpoint) =>
taskResolver = endpoint
context.become(Processing)
case _ => ;
}
def Processing : Receive = {
case DeconstructionActor.RequestDeleteVehicle(vehicle, zone, time) =>
vehicles = vehicles :+ DeconstructionActor.VehicleEntry(vehicle, zone, time)
vehicle.Actor ! Vehicle.PrepareForDeletion
//kick everyone out
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
context.parent ! VehicleServiceMessage(zone_id, VehicleAction.KickPassenger(tplayer.GUID, 4, false))
case None => ;
}
})
if(vehicles.size == 1) { //we were the only entry so the event must be started from scratch
import scala.concurrent.ExecutionContext.Implicits.global
scrappingProcess = context.system.scheduler.scheduleOnce(DeconstructionActor.timeout, self, DeconstructionActor.TryDeleteVehicle())
}
case DeconstructionActor.TryDeleteVehicle() =>
scrappingProcess.cancel
val now : Long = System.nanoTime
val (vehiclesToScrap, vehiclesRemain) = PartitionEntries(vehicles, now)
vehicles = vehiclesRemain
vehiclesToScrap.foreach(entry => {
val vehicle = entry.vehicle
val zone = entry.zone
entry.zone.Transport ! Zone.DespawnVehicle(vehicle)
context.parent ! DeconstructionActor.DeleteVehicle(vehicle.GUID, zone.Id) //call up to the main event system
context.parent ! VehicleServiceMessage.RevokeActorControl(vehicle) //call up to a sibling manager
taskResolver ! GUIDTask.UnregisterVehicle(vehicle)(zone.GUID)
})
if(vehiclesRemain.nonEmpty) {
val short_timeout : FiniteDuration = math.max(1, DeconstructionActor.timeout_time - (now - vehiclesRemain.head.time)) nanoseconds
import scala.concurrent.ExecutionContext.Implicits.global
scrappingProcess = context.system.scheduler.scheduleOnce(short_timeout, self, DeconstructionActor.TryDeleteVehicle())
}
case _ => ;
}
/**
* Iterate over entries in a `List` until an entry that does not exceed the time limit is discovered.
* Separate the original `List` into two:
* a `List` of elements that have exceeded the time limit,
* and a `List` of elements that still satisfy the time limit.
* As newer entries to the `List` will always resolve later than old ones,
* and newer entries are always added to the end of the main `List`,
* processing in order is always correct.
* @param list the `List` of entries to divide
* @param now the time right now (in nanoseconds)
* @see `List.partition`
* @return a `Tuple` of two `Lists`, whose qualifications are explained above
*/
private def PartitionEntries(list : List[DeconstructionActor.VehicleEntry], now : Long) : (List[DeconstructionActor.VehicleEntry], List[DeconstructionActor.VehicleEntry]) = {
val n : Int = recursivePartitionEntries(list.iterator, now)
(list.take(n), list.drop(n)) //take and drop so to always return new lists
}
/**
* Mark the index where the `List` of elements can be divided into two:
* a `List` of elements that have exceeded the time limit,
* and a `List` of elements that still satisfy the time limit.
* @param iter the `Iterator` of entries to divide
* @param now the time right now (in nanoseconds)
* @param index a persistent record of the index where list division should occur;
* defaults to 0
* @return the index where division will occur
*/
@tailrec private def recursivePartitionEntries(iter : Iterator[DeconstructionActor.VehicleEntry], now : Long, index : Int = 0) : Int = {
if(!iter.hasNext) {
index
}
else {
val entry = iter.next()
if(now - entry.time >= DeconstructionActor.timeout_time) {
recursivePartitionEntries(iter, now, index + 1)
}
else {
index
}
}
}
}
object DeconstructionActor {
/** The wait before completely deleting a vehicle; as a Long for calculation simplicity */
private final val timeout_time : Long = 5000000000L //nanoseconds (5s)
/** The wait before completely deleting a vehicle; as a `FiniteDuration` for `Executor` simplicity */
private final val timeout : FiniteDuration = timeout_time nanoseconds
private final val DefaultProcess : Cancellable = new Cancellable() {
override def cancel : Boolean = true
override def isCancelled : Boolean = true
}
final case class RequestTaskResolver()
/**
* Message that carries information about a vehicle to be deconstructed.
* @param vehicle the `Vehicle` object
* @param zone the `Zone` in which the vehicle resides
* @param time when the vehicle was doomed
* @see `VehicleEntry`
*/
final case class RequestDeleteVehicle(vehicle : Vehicle, zone : Zone, time : Long = System.nanoTime())
/**
* Message that carries information about a vehicle to be deconstructed.
* Prompting, as compared to `RequestDeleteVehicle` which is reactionary.
* @param vehicle_guid the vehicle
* @param zone_id the `Zone` in which the vehicle resides
*/
final case class DeleteVehicle(vehicle_guid : PlanetSideGUID, zone_id : String)
/**
* Internal message used to signal a test of the queued vehicle information.
*/
private final case class TryDeleteVehicle()
/**
* Entry of vehicle information.
* The `zone` is maintained separately as a necessity, required to complete the deletion of the vehicle
* via unregistering of the vehicle and all related, registered objects.
* @param vehicle the `Vehicle` object
* @param zone the `Zone` in which the vehicle resides
* @param time when the vehicle was doomed
* @see `RequestDeleteVehicle`
*/
private final case class VehicleEntry(vehicle : Vehicle, zone : Zone, time : Long)
}

View file

@ -0,0 +1,30 @@
// Copyright (c) 2017 PSForever
package services.vehicle.support
import akka.actor.{Actor, Props}
import net.psforever.objects.vehicles.VehicleControl
import services.vehicle.VehicleServiceMessage
/**
* Provide a context for a `Vehicle` `Actor` - the `VehicleControl`.<br>
* <br>
* A vehicle can be passed between different zones and, therefore, does not belong to the zone.
* A vehicle cna be given to different players and can persist and change though players have gone.
* Therefore, also does not belong to `WorldSessionActor`.
* A vehicle must anchored to something that exists outside of the `InterstellarCluster` and its agents.<br>
* <br>
* The only purpose of this `Actor` is to allow vehicles to borrow a context for the purpose of `Actor` creation.
* It is also be allowed to be responsible for cleaning up that context.
* (In reality, it can be cleaned up anywhere a `PoisonPill` can be sent.)
*/
class VehicleContextActor() extends Actor {
def receive : Receive = {
case VehicleServiceMessage.GiveActorControl(vehicle, actorName) =>
vehicle.Actor = context.actorOf(Props(classOf[VehicleControl], vehicle), s"${vehicle.Definition.Name}_$actorName")
case VehicleServiceMessage.RevokeActorControl(vehicle) =>
vehicle.Actor ! akka.actor.PoisonPill
case _ => ;
}
}