mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-02-24 00:53:35 +00:00
test Harasser to demonstrate synched vehicle actions: mounting, disembarking, driving, gunning, changing access permissions, changing ownership, kicking passengers, deconstructing
This commit is contained in:
parent
211eb838aa
commit
8f658aa688
36 changed files with 1820 additions and 470 deletions
|
|
@ -18,8 +18,10 @@ import net.psforever.objects.serverobject.builders.{DoorObjectBuilder, IFFLockOb
|
|||
import org.slf4j
|
||||
import org.fusesource.jansi.Ansi._
|
||||
import org.fusesource.jansi.Ansi.Color._
|
||||
import services.ServiceManager
|
||||
import services.avatar._
|
||||
import services.local._
|
||||
import services.vehicle.VehicleService
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.concurrent.Await
|
||||
|
|
@ -206,6 +208,7 @@ object PsLogin {
|
|||
serviceManager ! ServiceManager.Register(RandomPool(50).props(Props[TaskResolver]), "taskResolver")
|
||||
serviceManager ! ServiceManager.Register(Props[AvatarService], "avatar")
|
||||
serviceManager ! ServiceManager.Register(Props[LocalService], "local")
|
||||
serviceManager ! ServiceManager.Register(Props[VehicleService], "vehicle")
|
||||
serviceManager ! ServiceManager.Register(Props(classOf[InterstellarCluster], createContinents()), "galaxy")
|
||||
|
||||
/** Create two actors for handling the login and world server endpoints */
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
282
pslogin/src/main/scala/scripts/GUIDTask.scala
Normal file
282
pslogin/src/main/scala/scripts/GUIDTask.scala
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
package services
|
||||
|
||||
// Copyright (c) 2017 PSForever
|
||||
import akka.actor.{Actor, ActorIdentity, ActorRef, ActorSystem, Identify, Props}
|
||||
|
||||
28
pslogin/src/main/scala/services/avatar/AvatarResponse.scala
Normal file
28
pslogin/src/main/scala/services/avatar/AvatarResponse.scala
Normal 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
|
||||
}
|
||||
|
|
@ -31,39 +31,39 @@ class AvatarService extends Actor {
|
|||
action match {
|
||||
case AvatarAction.ArmorChanged(player_guid, suit, subtype) =>
|
||||
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) =>
|
||||
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) =>
|
||||
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) =>
|
||||
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) =>
|
||||
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) =>
|
||||
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) =>
|
||||
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) =>
|
||||
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) =>
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.Reload(mag))
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.Reload(mag))
|
||||
)
|
||||
case _ => ;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,10 @@
|
|||
// 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}
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import services.GenericEventBusMsg
|
||||
|
||||
final case class AvatarServiceResponse(toChannel : String,
|
||||
avatar_guid : PlanetSideGUID,
|
||||
replyMessage : AvatarServiceResponse.Response
|
||||
replyMessage : AvatarResponse.Response
|
||||
) 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
|
||||
}
|
||||
|
|
|
|||
15
pslogin/src/main/scala/services/local/LocalResponse.scala
Normal file
15
pslogin/src/main/scala/services/local/LocalResponse.scala
Normal 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
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import services.local.support.{DoorCloseActor, HackClearActor}
|
|||
import services.{GenericEventBus, Service}
|
||||
|
||||
class LocalService extends Actor {
|
||||
//import LocalService._
|
||||
private val doorCloser = context.actorOf(Props[DoorCloseActor], "local-door-closer")
|
||||
private val hackClearer = context.actorOf(Props[HackClearActor], "local-hack-clearer")
|
||||
private [this] val log = org.log4s.getLogger
|
||||
|
|
@ -19,7 +18,7 @@ class LocalService extends Actor {
|
|||
|
||||
def receive = {
|
||||
case Service.Join(channel) =>
|
||||
val path = s"/$channel/LocalEnvironment"
|
||||
val path = s"/$channel/Local"
|
||||
val who = sender()
|
||||
log.info(s"$who has joined $path")
|
||||
LocalEvents.subscribe(who, path)
|
||||
|
|
@ -33,24 +32,24 @@ class LocalService extends Actor {
|
|||
case LocalAction.DoorOpens(player_guid, zone, door) =>
|
||||
doorCloser ! DoorCloseActor.DoorIsOpen(door, zone)
|
||||
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) =>
|
||||
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) =>
|
||||
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) =>
|
||||
hackClearer ! HackClearActor.ObjectIsHacked(target, zone, unk1, unk2)
|
||||
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) =>
|
||||
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 _ => ;
|
||||
}
|
||||
|
|
@ -58,13 +57,13 @@ class LocalService extends Actor {
|
|||
//response from DoorCloseActor
|
||||
case DoorCloseActor.CloseTheDoor(door_guid, zone_id) =>
|
||||
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
|
||||
case HackClearActor.ClearTheHack(target_guid, zone_id, unk1, unk2) =>
|
||||
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 =>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,10 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.local
|
||||
|
||||
import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound}
|
||||
import net.psforever.types.Vector3
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import services.GenericEventBusMsg
|
||||
|
||||
final case class LocalServiceResponse(toChannel : String,
|
||||
avatar_guid : PlanetSideGUID,
|
||||
replyMessage : LocalServiceResponse.Response
|
||||
replyMessage : LocalResponse.Response
|
||||
) 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
|
||||
}
|
||||
|
|
|
|||
22
pslogin/src/main/scala/services/vehicle/VehicleAction.scala
Normal file
22
pslogin/src/main/scala/services/vehicle/VehicleAction.scala
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
91
pslogin/src/main/scala/services/vehicle/VehicleService.scala
Normal file
91
pslogin/src/main/scala/services/vehicle/VehicleService.scala
Normal 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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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 _ => ;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue