mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-02-28 02:53:42 +00:00
Merge pull request #216 from Fate-JH/drop-item
Dropping Items, Picking up Items, Cleaning up Items
This commit is contained in:
commit
a9db4b5820
28 changed files with 1713 additions and 972 deletions
|
|
@ -16,6 +16,8 @@ import net.psforever.objects.serverobject.tube.SpawnTubeDefinition
|
|||
import net.psforever.objects.vehicles.{SeatArmorRestriction, UtilityType}
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
object GlobalDefinitions {
|
||||
/*
|
||||
Implants
|
||||
|
|
@ -2716,6 +2718,7 @@ object GlobalDefinitions {
|
|||
ams.Deployment = true
|
||||
ams.DeployTime = 2000
|
||||
ams.UndeployTime = 2000
|
||||
ams.DeconstructionTime = Some(20 minutes)
|
||||
ams.AutoPilotSpeeds = (18, 6)
|
||||
ams.Packet = utilityConverter
|
||||
|
||||
|
|
@ -2728,6 +2731,7 @@ object GlobalDefinitions {
|
|||
router.Deployment = true
|
||||
router.DeployTime = 2000
|
||||
router.UndeployTime = 2000
|
||||
router.DeconstructionTime = Duration(20, "minutes")
|
||||
router.AutoPilotSpeeds = (16, 6)
|
||||
router.Packet = variantConverter
|
||||
|
||||
|
|
|
|||
|
|
@ -129,7 +129,12 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio
|
|||
|
||||
def MaxArmor : Int = exosuit.MaxArmor
|
||||
|
||||
def VisibleSlots : Set[Int] = if(exosuit.SuitType == ExoSuitType.MAX) { Set(0) } else { Set(0,1,2,3,4) }
|
||||
def VisibleSlots : Set[Int] = if(exosuit.SuitType == ExoSuitType.MAX) {
|
||||
Set(0)
|
||||
}
|
||||
else {
|
||||
(0 to 4).filterNot(index => holsters(index).Size == EquipmentSize.Blocked).toSet
|
||||
}
|
||||
|
||||
override def Slot(slot : Int) : EquipmentSlot = {
|
||||
if(inventory.Offset <= slot && slot <= inventory.LastIndex) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import net.psforever.objects.inventory.InventoryTile
|
|||
import net.psforever.objects.vehicles.UtilityType
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* An object definition system used to construct and retain the parameters of various vehicles.
|
||||
|
|
@ -29,6 +30,7 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
|
|||
private var canCloak : Boolean = false
|
||||
private var canBeOwned : Boolean = true
|
||||
private var serverVehicleOverrideSpeeds : (Int, Int) = (0, 0)
|
||||
private var deconTime : Option[FiniteDuration] = None
|
||||
Name = "vehicle"
|
||||
Packet = VehicleDefinition.converter
|
||||
|
||||
|
|
@ -83,6 +85,18 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
|
|||
DeployTime
|
||||
}
|
||||
|
||||
def DeconstructionTime : Option[FiniteDuration] = deconTime
|
||||
|
||||
def DeconstructionTime_=(time : FiniteDuration) : Option[FiniteDuration] = {
|
||||
deconTime_=(Some(time))
|
||||
DeconstructionTime
|
||||
}
|
||||
|
||||
def DeconstructionTime_=(time : Option[FiniteDuration]) : Option[FiniteDuration] = {
|
||||
deconTime = time
|
||||
DeconstructionTime
|
||||
}
|
||||
|
||||
def UndeployTime : Int = deploymentTime_Undeploy
|
||||
|
||||
def UndeployTime_=(dtime : Int) : Int = {
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
|
|||
if(accessor == ActorRef.noSender) {
|
||||
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")
|
||||
ground = context.actorOf(Props(classOf[ZoneGroundActor], equipmentOnGround), s"$Id-ground")
|
||||
ground = context.actorOf(Props(classOf[ZoneGroundActor], this, equipmentOnGround), s"$Id-ground")
|
||||
transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"$Id-vehicles")
|
||||
population = context.actorOf(Props(classOf[ZonePopulationActor], this, players, corpses), s"$Id-players")
|
||||
|
||||
|
|
@ -386,27 +386,17 @@ object Zone {
|
|||
final case class NoValidSpawnPoint(zone_number : Int, spawn_group : Option[Int])
|
||||
}
|
||||
|
||||
/**
|
||||
* Message to relinguish an item and place in on the ground.
|
||||
* @param item the piece of `Equipment`
|
||||
* @param pos where it is dropped
|
||||
* @param orient in which direction it is facing when dropped
|
||||
*/
|
||||
final case class DropItemOnGround(item : Equipment, pos : Vector3, orient : Vector3)
|
||||
object Ground {
|
||||
final case class DropItem(item : Equipment, pos : Vector3, orient : Vector3)
|
||||
final case class ItemOnGround(item : Equipment, pos : Vector3, orient : Vector3)
|
||||
final case class CanNotDropItem(zone : Zone, item : Equipment, reason : String)
|
||||
|
||||
/**
|
||||
* Message to attempt to acquire an item from the ground (before somoene else?).
|
||||
* @param player who wants the piece of `Equipment`
|
||||
* @param item_guid the unique identifier of the piece of `Equipment`
|
||||
*/
|
||||
final case class GetItemOnGround(player : Player, item_guid : PlanetSideGUID)
|
||||
final case class PickupItem(item_guid : PlanetSideGUID)
|
||||
final case class ItemInHand(item : Equipment)
|
||||
final case class CanNotPickupItem(zone : Zone, item_guid : PlanetSideGUID, reason : String)
|
||||
|
||||
/**
|
||||
* Message to give an item from the ground to a specific user.
|
||||
* @param player who wants the piece of `Equipment`
|
||||
* @param item the piece of `Equipment`
|
||||
*/
|
||||
final case class ItemFromGround(player : Player, item : Equipment)
|
||||
final case class RemoveItem(item_guid : PlanetSideGUID)
|
||||
}
|
||||
|
||||
object Vehicle {
|
||||
final case class Spawn(vehicle : Vehicle)
|
||||
|
|
|
|||
|
|
@ -50,10 +50,10 @@ class ZoneActor(zone : Zone) extends Actor {
|
|||
zone.Population forward msg
|
||||
|
||||
//frwd to Ground Actor
|
||||
case msg @ Zone.DropItemOnGround =>
|
||||
case msg @ Zone.Ground.DropItem =>
|
||||
zone.Ground forward msg
|
||||
|
||||
case msg @ Zone.GetItemOnGround =>
|
||||
case msg @ Zone.Ground.PickupItem =>
|
||||
zone.Ground forward msg
|
||||
|
||||
//frwd to Vehicle Actor
|
||||
|
|
|
|||
|
|
@ -12,22 +12,35 @@ import scala.collection.mutable.ListBuffer
|
|||
* na
|
||||
* @param equipmentOnGround a `List` of items (`Equipment`) dropped by players on the ground and can be collected again
|
||||
*/
|
||||
class ZoneGroundActor(equipmentOnGround : ListBuffer[Equipment]) extends Actor {
|
||||
class ZoneGroundActor(zone : Zone, equipmentOnGround : ListBuffer[Equipment]) extends Actor {
|
||||
//private[this] val log = org.log4s.getLogger
|
||||
|
||||
def receive : Receive = {
|
||||
case Zone.DropItemOnGround(item, pos, orient) =>
|
||||
item.Position = pos
|
||||
item.Orientation = orient
|
||||
equipmentOnGround += item
|
||||
|
||||
case Zone.GetItemOnGround(player, item_guid) =>
|
||||
FindItemOnGround(item_guid) match {
|
||||
case Some(item) =>
|
||||
sender ! Zone.ItemFromGround(player, item)
|
||||
case None =>
|
||||
org.log4s.getLogger.warn(s"item on ground $item_guid was requested by $player for pickup but was not found")
|
||||
case Zone.Ground.DropItem(item, pos, orient) =>
|
||||
sender ! (if(!item.HasGUID) {
|
||||
Zone.Ground.CanNotDropItem(zone, item, "not registered yet")
|
||||
}
|
||||
else if(zone.GUID(item.GUID).isEmpty) {
|
||||
Zone.Ground.CanNotDropItem(zone, item, "registered to some other zone")
|
||||
}
|
||||
else if(equipmentOnGround.contains(item)) {
|
||||
Zone.Ground.CanNotDropItem(zone, item, "already dropped")
|
||||
}
|
||||
else {
|
||||
equipmentOnGround += item
|
||||
Zone.Ground.ItemOnGround(item, pos, orient)
|
||||
})
|
||||
|
||||
case Zone.Ground.PickupItem(item_guid) =>
|
||||
sender ! (FindItemOnGround(item_guid) match {
|
||||
case Some(item) =>
|
||||
Zone.Ground.ItemInHand(item)
|
||||
case None =>
|
||||
Zone.Ground.CanNotPickupItem(zone, item_guid, "can not find")
|
||||
})
|
||||
|
||||
case Zone.Ground.RemoveItem(item_guid) =>
|
||||
FindItemOnGround(item_guid) //intentionally no callback
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,13 +19,18 @@ final case class ActionResultMessage(successful : Boolean,
|
|||
}
|
||||
|
||||
object ActionResultMessage extends Marshallable[ActionResultMessage] {
|
||||
def apply() : ActionResultMessage = {
|
||||
ActionResultMessage(true, None)
|
||||
}
|
||||
/**
|
||||
* A message where the result is always a pass.
|
||||
* @return an `ActionResultMessage` object
|
||||
*/
|
||||
def Pass : ActionResultMessage = ActionResultMessage(true, None)
|
||||
|
||||
def apply(error : Long) : ActionResultMessage = {
|
||||
ActionResultMessage(false, Some(error))
|
||||
}
|
||||
/**
|
||||
* A message where the result is always a failure.
|
||||
* @param error the error code
|
||||
* @return an `ActionResultMessage` object
|
||||
*/
|
||||
def Fail(error : Long) : ActionResultMessage = ActionResultMessage(false, Some(error))
|
||||
|
||||
implicit val codec : Codec[ActionResultMessage] = (
|
||||
("successful" | bool) >>:~ { res =>
|
||||
|
|
|
|||
|
|
@ -40,6 +40,14 @@ final case class ObjectDetachMessage(parent_guid : PlanetSideGUID,
|
|||
}
|
||||
|
||||
object ObjectDetachMessage extends Marshallable[ObjectDetachMessage] {
|
||||
def apply(parent_guid : PlanetSideGUID, child_guid : PlanetSideGUID, pos : Vector3, orient : Vector3) : ObjectDetachMessage = {
|
||||
ObjectDetachMessage(parent_guid, child_guid, pos, orient.x, orient.y, orient.z)
|
||||
}
|
||||
|
||||
def apply(parent_guid : PlanetSideGUID, child_guid : PlanetSideGUID, pos : Vector3, orient_z : Float) : ObjectDetachMessage = {
|
||||
ObjectDetachMessage(parent_guid, child_guid, pos, 0, 0, orient_z)
|
||||
}
|
||||
|
||||
implicit val codec : Codec[ObjectDetachMessage] = (
|
||||
("parent_guid" | PlanetSideGUID.codec) ::
|
||||
("child_guid" | PlanetSideGUID.codec) ::
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class ActionResultMessageTest extends Specification {
|
|||
}
|
||||
|
||||
"encode (pass, minimal)" in {
|
||||
val msg = ActionResultMessage()
|
||||
val msg = ActionResultMessage.Pass
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_pass
|
||||
|
|
@ -52,7 +52,7 @@ class ActionResultMessageTest extends Specification {
|
|||
}
|
||||
|
||||
"encode (fail, minimal)" in {
|
||||
val msg = ActionResultMessage(1)
|
||||
val msg = ActionResultMessage.Fail(1)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_fail
|
||||
|
|
|
|||
|
|
@ -26,10 +26,24 @@ class ObjectDetachMessageTest extends Specification {
|
|||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
"encode (1)" in {
|
||||
val msg = ObjectDetachMessage(PlanetSideGUID(2916), PlanetSideGUID(2502), Vector3(3567.1406f, 2988.0078f, 71.84375f), 0f, 0f, 270f)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string
|
||||
}
|
||||
|
||||
"encode (2)" in {
|
||||
val msg = ObjectDetachMessage(PlanetSideGUID(2916), PlanetSideGUID(2502), Vector3(3567.1406f, 2988.0078f, 71.84375f), Vector3(0f, 0f, 270f))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string
|
||||
}
|
||||
|
||||
"encode (3)" in {
|
||||
val msg = ObjectDetachMessage(PlanetSideGUID(2916), PlanetSideGUID(2502), Vector3(3567.1406f, 2988.0078f, 71.84375f), 270f)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,7 +155,13 @@ class PlayerTest extends Specification {
|
|||
|
||||
"has visible slots" in {
|
||||
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
|
||||
obj.VisibleSlots mustEqual Set(0,2,4) //Standard
|
||||
obj.ExoSuit = ExoSuitType.Agile
|
||||
obj.VisibleSlots mustEqual Set(0,1,2,4)
|
||||
obj.ExoSuit = ExoSuitType.Reinforced
|
||||
obj.VisibleSlots mustEqual Set(0,1,2,3,4)
|
||||
obj.ExoSuit = ExoSuitType.Infiltration
|
||||
obj.VisibleSlots mustEqual Set(0,4)
|
||||
obj.ExoSuit = ExoSuitType.MAX
|
||||
obj.VisibleSlots mustEqual Set(0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap}
|
|||
import net.psforever.objects.Vehicle
|
||||
import org.specs2.mutable.Specification
|
||||
|
||||
import scala.concurrent.duration.Duration
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class ZoneTest extends Specification {
|
||||
def test(a: Int, b : Zone, c : ActorContext) : Building = { Building.NoBuilding }
|
||||
|
|
@ -464,56 +464,183 @@ class ZonePopulationTest extends ActorTest {
|
|||
}
|
||||
}
|
||||
|
||||
class ZoneGroundTest extends ActorTest {
|
||||
class ZoneGroundDropItemTest extends ActorTest {
|
||||
val item = AmmoBox(GlobalDefinitions.bullet_9mm)
|
||||
item.GUID = PlanetSideGUID(10)
|
||||
val hub = new NumberPoolHub(new LimitedNumberSource(20))
|
||||
hub.register(item, 10)
|
||||
val zone = new Zone("test", new ZoneMap("test-map"), 0)
|
||||
zone.GUID(hub)
|
||||
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "drop-test-zone") ! "!"
|
||||
|
||||
"ZoneGroundActor" should {
|
||||
"DropItem" should {
|
||||
"drop item on ground" in {
|
||||
val zone = new Zone("test", new ZoneMap(""), 0)
|
||||
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "drop-item-test") ! "!"
|
||||
receiveOne(Duration.create(200, "ms")) //consume
|
||||
receiveOne(1 second) //consume
|
||||
assert(!zone.EquipmentOnGround.contains(item))
|
||||
zone.Ground ! Zone.Ground.DropItem(item, Vector3(1.1f, 2.2f, 3.3f), Vector3(4.4f, 5.5f, 6.6f))
|
||||
|
||||
assert(zone.EquipmentOnGround.isEmpty)
|
||||
assert(item.Position == Vector3.Zero)
|
||||
assert(item.Orientation == Vector3.Zero)
|
||||
zone.Ground ! Zone.DropItemOnGround(item, Vector3(1.1f, 2.2f, 3.3f), Vector3(4.4f, 5.5f, 6.6f))
|
||||
expectNoMsg(Duration.create(100, "ms"))
|
||||
|
||||
assert(zone.EquipmentOnGround == List(item))
|
||||
assert(item.Position == Vector3(1.1f, 2.2f, 3.3f))
|
||||
assert(item.Orientation == Vector3(4.4f, 5.5f, 6.6f))
|
||||
val reply = receiveOne(200 milliseconds)
|
||||
assert(reply.isInstanceOf[Zone.Ground.ItemOnGround])
|
||||
assert(reply.asInstanceOf[Zone.Ground.ItemOnGround].item == item)
|
||||
assert(reply.asInstanceOf[Zone.Ground.ItemOnGround].pos == Vector3(1.1f, 2.2f, 3.3f))
|
||||
assert(reply.asInstanceOf[Zone.Ground.ItemOnGround].orient == Vector3(4.4f, 5.5f, 6.6f))
|
||||
assert(zone.EquipmentOnGround.contains(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"get item from ground (success)" in {
|
||||
val zone = new Zone("test", new ZoneMap(""), 0)
|
||||
val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5))
|
||||
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "get-item-test-good") ! "!"
|
||||
receiveOne(Duration.create(200, "ms")) //consume
|
||||
zone.Ground ! Zone.DropItemOnGround(item, Vector3.Zero, Vector3.Zero)
|
||||
expectNoMsg(Duration.create(100, "ms"))
|
||||
class ZoneGroundCanNotDropItem1Test extends ActorTest {
|
||||
val item = AmmoBox(GlobalDefinitions.bullet_9mm)
|
||||
val hub = new NumberPoolHub(new LimitedNumberSource(20))
|
||||
//hub.register(item, 10) //!important
|
||||
val zone = new Zone("test", new ZoneMap("test-map"), 0)
|
||||
zone.GUID(hub)
|
||||
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "drop-test-zone") ! "!"
|
||||
|
||||
assert(zone.EquipmentOnGround == List(item))
|
||||
zone.Ground ! Zone.GetItemOnGround(player, PlanetSideGUID(10))
|
||||
val reply = receiveOne(Duration.create(100, "ms"))
|
||||
"DropItem" should {
|
||||
"not drop an item that is not registered" in {
|
||||
receiveOne(1 second) //consume
|
||||
assert(!zone.EquipmentOnGround.contains(item))
|
||||
zone.Ground ! Zone.Ground.DropItem(item, Vector3.Zero, Vector3.Zero)
|
||||
|
||||
assert(zone.EquipmentOnGround.isEmpty)
|
||||
assert(reply.isInstanceOf[Zone.ItemFromGround])
|
||||
assert(reply.asInstanceOf[Zone.ItemFromGround].player == player)
|
||||
assert(reply.asInstanceOf[Zone.ItemFromGround].item == item)
|
||||
val reply = receiveOne(300 milliseconds)
|
||||
assert(reply.isInstanceOf[Zone.Ground.CanNotDropItem])
|
||||
assert(reply.asInstanceOf[Zone.Ground.CanNotDropItem].item == item)
|
||||
assert(reply.asInstanceOf[Zone.Ground.CanNotDropItem].zone == zone)
|
||||
assert(reply.asInstanceOf[Zone.Ground.CanNotDropItem].reason == "not registered yet")
|
||||
assert(!zone.EquipmentOnGround.contains(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"get item from ground (failure)" in {
|
||||
val zone = new Zone("test", new ZoneMap(""), 0)
|
||||
val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5))
|
||||
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "get-item-test-fail") ! "!"
|
||||
receiveOne(Duration.create(200, "ms")) //consume
|
||||
zone.Ground ! Zone.DropItemOnGround(item, Vector3.Zero, Vector3.Zero)
|
||||
expectNoMsg(Duration.create(100, "ms"))
|
||||
class ZoneGroundCanNotDropItem2Test extends ActorTest {
|
||||
val item = AmmoBox(GlobalDefinitions.bullet_9mm)
|
||||
val hub = new NumberPoolHub(new LimitedNumberSource(20))
|
||||
hub.register(item, 10) //!important
|
||||
val zone = new Zone("test", new ZoneMap("test-map"), 0)
|
||||
//zone.GUID(hub) //!important
|
||||
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "drop-test-zone") ! "!"
|
||||
|
||||
assert(zone.EquipmentOnGround == List(item))
|
||||
zone.Ground ! Zone.GetItemOnGround(player, PlanetSideGUID(11)) //wrong guid
|
||||
expectNoMsg(Duration.create(500, "ms"))
|
||||
"DropItem" should {
|
||||
"not drop an item that is not registered to the zone" in {
|
||||
receiveOne(1 second) //consume
|
||||
assert(!zone.EquipmentOnGround.contains(item))
|
||||
zone.Ground ! Zone.Ground.DropItem(item, Vector3.Zero, Vector3.Zero)
|
||||
|
||||
val reply = receiveOne(300 milliseconds)
|
||||
assert(reply.isInstanceOf[Zone.Ground.CanNotDropItem])
|
||||
assert(reply.asInstanceOf[Zone.Ground.CanNotDropItem].item == item)
|
||||
assert(reply.asInstanceOf[Zone.Ground.CanNotDropItem].zone == zone)
|
||||
assert(reply.asInstanceOf[Zone.Ground.CanNotDropItem].reason == "registered to some other zone")
|
||||
assert(!zone.EquipmentOnGround.contains(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ZoneGroundCanNotDropItem3Test extends ActorTest {
|
||||
val item = AmmoBox(GlobalDefinitions.bullet_9mm)
|
||||
val hub = new NumberPoolHub(new LimitedNumberSource(20))
|
||||
hub.register(item, 10) //!important
|
||||
val zone = new Zone("test", new ZoneMap("test-map"), 0)
|
||||
zone.GUID(hub) //!important
|
||||
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "drop-test-zone") ! "!"
|
||||
|
||||
"DropItem" should {
|
||||
"not drop an item that has already been dropped" in {
|
||||
receiveOne(1 second) //consume
|
||||
assert(!zone.EquipmentOnGround.contains(item))
|
||||
assert(zone.EquipmentOnGround.isEmpty)
|
||||
zone.Ground ! Zone.Ground.DropItem(item, Vector3.Zero, Vector3.Zero)
|
||||
|
||||
val reply1 = receiveOne(300 milliseconds)
|
||||
assert(reply1.isInstanceOf[Zone.Ground.ItemOnGround])
|
||||
assert(reply1.asInstanceOf[Zone.Ground.ItemOnGround].item == item)
|
||||
assert(zone.EquipmentOnGround.contains(item))
|
||||
assert(zone.EquipmentOnGround.size == 1)
|
||||
zone.Ground ! Zone.Ground.DropItem(item, Vector3.Zero, Vector3.Zero)
|
||||
|
||||
val reply2 = receiveOne(300 milliseconds)
|
||||
assert(reply2.isInstanceOf[Zone.Ground.CanNotDropItem])
|
||||
assert(reply2.asInstanceOf[Zone.Ground.CanNotDropItem].item == item)
|
||||
assert(reply2.asInstanceOf[Zone.Ground.CanNotDropItem].zone == zone)
|
||||
assert(reply2.asInstanceOf[Zone.Ground.CanNotDropItem].reason == "already dropped")
|
||||
assert(zone.EquipmentOnGround.size == 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ZoneGroundPickupItemTest extends ActorTest {
|
||||
val item = AmmoBox(GlobalDefinitions.bullet_9mm)
|
||||
val hub = new NumberPoolHub(new LimitedNumberSource(20))
|
||||
hub.register(item, 10)
|
||||
val zone = new Zone("test", new ZoneMap("test-map"), 0)
|
||||
zone.GUID(hub)
|
||||
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "drop-test-zone") ! "!"
|
||||
|
||||
"PickupItem" should {
|
||||
"pickup an item from ground" in {
|
||||
receiveOne(1 second) //consume
|
||||
assert(!zone.EquipmentOnGround.contains(item))
|
||||
zone.Ground ! Zone.Ground.DropItem(item, Vector3.Zero, Vector3.Zero)
|
||||
|
||||
val reply1 = receiveOne(200 milliseconds)
|
||||
assert(reply1.isInstanceOf[Zone.Ground.ItemOnGround])
|
||||
assert(zone.EquipmentOnGround.contains(item))
|
||||
zone.Ground ! Zone.Ground.PickupItem(item.GUID)
|
||||
|
||||
val reply2 = receiveOne(200 milliseconds)
|
||||
assert(reply2.isInstanceOf[Zone.Ground.ItemInHand])
|
||||
assert(reply2.asInstanceOf[Zone.Ground.ItemInHand].item == item)
|
||||
assert(!zone.EquipmentOnGround.contains(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ZoneGroundCanNotPickupItemTest extends ActorTest {
|
||||
val item = AmmoBox(GlobalDefinitions.bullet_9mm)
|
||||
val hub = new NumberPoolHub(new LimitedNumberSource(20))
|
||||
hub.register(item, 10)
|
||||
val zone = new Zone("test", new ZoneMap("test-map"), 0)
|
||||
zone.GUID(hub) //still registered to this zone
|
||||
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "drop-test-zone") ! "!"
|
||||
|
||||
"PickupItem" should {
|
||||
"not pickup an item if it can not be found" in {
|
||||
receiveOne(1 second) //consume
|
||||
assert(!zone.EquipmentOnGround.contains(item))
|
||||
zone.Ground ! Zone.Ground.PickupItem(item.GUID)
|
||||
|
||||
val reply2 = receiveOne(200 milliseconds)
|
||||
assert(reply2.isInstanceOf[Zone.Ground.CanNotPickupItem])
|
||||
assert(reply2.asInstanceOf[Zone.Ground.CanNotPickupItem].item_guid == item.GUID)
|
||||
assert(reply2.asInstanceOf[Zone.Ground.CanNotPickupItem].zone == zone)
|
||||
assert(reply2.asInstanceOf[Zone.Ground.CanNotPickupItem].reason == "can not find")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ZoneGroundRemoveItemTest extends ActorTest {
|
||||
val item = AmmoBox(GlobalDefinitions.bullet_9mm)
|
||||
val hub = new NumberPoolHub(new LimitedNumberSource(20))
|
||||
hub.register(item, 10)
|
||||
val zone = new Zone("test", new ZoneMap("test-map"), 0)
|
||||
zone.GUID(hub) //still registered to this zone
|
||||
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "drop-test-zone") ! "!"
|
||||
|
||||
"RemoveItem" should {
|
||||
"remove an item from the ground without callback (even if the item is not found)" in {
|
||||
receiveOne(1 second)
|
||||
assert(!zone.EquipmentOnGround.contains(item))
|
||||
zone.Ground ! Zone.Ground.DropItem(item, Vector3.Zero, Vector3.Zero)
|
||||
receiveOne(200 milliseconds)
|
||||
assert(zone.EquipmentOnGround.contains(item)) //dropped
|
||||
|
||||
zone.Ground ! Zone.Ground.RemoveItem(item.GUID)
|
||||
expectNoMsg(500 milliseconds)
|
||||
assert(!zone.EquipmentOnGround.contains(item))
|
||||
|
||||
zone.Ground ! Zone.Ground.RemoveItem(item.GUID) //repeat
|
||||
expectNoMsg(500 milliseconds)
|
||||
assert(!zone.EquipmentOnGround.contains(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ object PsLogin {
|
|||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
import scala.util.{Failure, Success}
|
||||
implicit val timeout = Timeout(200 milliseconds)
|
||||
implicit val timeout = Timeout(500 milliseconds)
|
||||
val requestVehicleEventBus : Future[ServiceManager.LookupResult] =
|
||||
(ServiceManager.serviceManager ask ServiceManager.Lookup("vehicle")).mapTo[ServiceManager.LookupResult]
|
||||
requestVehicleEventBus.onComplete {
|
||||
|
|
|
|||
|
|
@ -37,13 +37,14 @@ import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, VehicleLo
|
|||
import net.psforever.objects.zones.{InterstellarCluster, Zone}
|
||||
import net.psforever.packet.game.objectcreate._
|
||||
import net.psforever.types._
|
||||
import services._
|
||||
import services.{RemoverActor, _}
|
||||
import services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage, AvatarServiceResponse}
|
||||
import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse}
|
||||
import services.vehicle.VehicleAction.UnstowEquipment
|
||||
import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Success
|
||||
|
||||
class WorldSessionActor extends Actor with MDCContextAware {
|
||||
|
|
@ -140,14 +141,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
else { //no items in inventory; leave no corpse
|
||||
val player_guid = player.GUID
|
||||
player.Position = Vector3.Zero //save character before doing this
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid, 0))
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid))
|
||||
taskResolver ! GUIDTask.UnregisterAvatar(player)(continent.GUID)
|
||||
}
|
||||
|
||||
case Some(vehicle_guid) =>
|
||||
val player_guid = player.GUID
|
||||
player.Position = Vector3.Zero //save character before doing this
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid, 0))
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid))
|
||||
taskResolver ! GUIDTask.UnregisterAvatar(player)(continent.GUID)
|
||||
DismountVehicleOnLogOut()
|
||||
}
|
||||
|
|
@ -183,7 +184,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case Some(vehicle : Vehicle) =>
|
||||
vehicle.Seat(vehicle.PassengerInSeat(player).get).get.Occupant = None
|
||||
if(vehicle.Seats.values.count(_.isOccupied) == 0) {
|
||||
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 600L) //start vehicle decay (10m)
|
||||
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent), vehicle.Definition.DeconstructionTime) //start vehicle decay
|
||||
}
|
||||
vehicleService ! Service.Leave(Some(s"${vehicle.Actor}"))
|
||||
|
||||
|
|
@ -255,7 +256,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
|
||||
case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) =>
|
||||
if(tplayer_guid != guid) {
|
||||
sendResponse(ObjectDetachMessage(weapon_guid, previous_guid, Vector3(0,0,0), 0f, 0f, 0f))
|
||||
sendResponse(ObjectDetachMessage(weapon_guid, previous_guid, Vector3.Zero, 0))
|
||||
sendResponse(
|
||||
ObjectCreateMessage(
|
||||
ammo_id,
|
||||
|
|
@ -287,29 +288,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(GenericObjectActionMessage(guid, 36))
|
||||
}
|
||||
|
||||
case AvatarResponse.EquipmentInHand(target, slot, item) =>
|
||||
case msg @ AvatarResponse.DropItem(pkt) =>
|
||||
if(tplayer_guid != guid) {
|
||||
val definition = item.Definition
|
||||
sendResponse(
|
||||
ObjectCreateMessage(
|
||||
definition.ObjectId,
|
||||
item.GUID,
|
||||
ObjectCreateMessageParent(target, slot),
|
||||
definition.Packet.ConstructorData(item).get
|
||||
)
|
||||
)
|
||||
sendResponse(pkt)
|
||||
}
|
||||
|
||||
case msg @ AvatarResponse.EquipmentOnGround(pos, orient, item_id, item_guid, item_data) =>
|
||||
case AvatarResponse.EquipmentInHand(pkt) =>
|
||||
if(tplayer_guid != guid) {
|
||||
log.info(s"now dropping a $msg")
|
||||
sendResponse(
|
||||
ObjectCreateMessage(
|
||||
item_id,
|
||||
item_guid,
|
||||
DroppedItemData(PlacementData(pos, Vector3(0f, 0f, orient.z)), item_data)
|
||||
)
|
||||
)
|
||||
sendResponse(pkt)
|
||||
}
|
||||
|
||||
case AvatarResponse.LoadPlayer(pdata) =>
|
||||
|
|
@ -405,7 +391,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
|
||||
case LocalServiceResponse(_, guid, reply) =>
|
||||
val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(0) }
|
||||
val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(-1) }
|
||||
reply match {
|
||||
case LocalResponse.DoorOpens(door_guid) =>
|
||||
if(tplayer_guid != guid) {
|
||||
|
|
@ -419,12 +405,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(HackMessage(0, target_guid, guid, 0, unk1, HackState.HackCleared, unk2))
|
||||
|
||||
case LocalResponse.HackObject(target_guid, unk1, unk2) =>
|
||||
if(player.GUID != guid) {
|
||||
if(tplayer_guid != guid) {
|
||||
sendResponse(HackMessage(0, target_guid, guid, 100, unk1, HackState.Hacked, unk2))
|
||||
}
|
||||
|
||||
case LocalResponse.ProximityTerminalEffect(object_guid, effectState) =>
|
||||
if(player.GUID != guid) {
|
||||
if(tplayer_guid != guid) {
|
||||
sendResponse(ProximityTerminalUseMessage(PlanetSideGUID(0), object_guid, effectState))
|
||||
}
|
||||
|
||||
|
|
@ -465,7 +451,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
|
||||
case VehicleResponse.DetachFromRails(vehicle_guid, pad_guid, pad_position, pad_orientation_z) =>
|
||||
sendResponse(ObjectDetachMessage(pad_guid, vehicle_guid, pad_position + Vector3(0,0,0.5f), 0, 0, pad_orientation_z))
|
||||
sendResponse(ObjectDetachMessage(pad_guid, vehicle_guid, pad_position + Vector3(0,0,0.5f), pad_orientation_z))
|
||||
|
||||
case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) =>
|
||||
if(tplayer_guid != guid) {
|
||||
|
|
@ -585,7 +571,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
|
||||
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
|
||||
DeploymentActivities(obj)
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
context.system.scheduler.scheduleOnce(obj.DeployTime milliseconds, obj.Actor, Deployment.TryDeploy(DriveState.Deployed))
|
||||
}
|
||||
|
|
@ -607,7 +592,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
|
||||
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
|
||||
DeploymentActivities(obj)
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
context.system.scheduler.scheduleOnce(obj.UndeployTime milliseconds, obj.Actor, Deployment.TryUndeploy(DriveState.Mobile))
|
||||
}
|
||||
|
|
@ -660,7 +644,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
val obj_guid : PlanetSideGUID = obj.GUID
|
||||
val player_guid : PlanetSideGUID = tplayer.GUID
|
||||
log.info(s"MountVehicleMsg: $player_guid mounts $obj_guid @ $seat_num")
|
||||
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(obj_guid) //clear all deconstruction timers
|
||||
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(obj), continent)) //clear timer
|
||||
PlayerActionsToCancel()
|
||||
if(seat_num == 0) { //simplistic vehicle ownership management
|
||||
obj.Owner match {
|
||||
|
|
@ -714,7 +698,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, seat_num, true, obj.GUID))
|
||||
}
|
||||
if(obj.Seats.values.count(_.isOccupied) == 0) {
|
||||
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj, continent, 600L) //start vehicle decay (10m)
|
||||
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(obj, continent, obj.Definition.DeconstructionTime)) //start vehicle decay
|
||||
}
|
||||
|
||||
case Mountable.CanDismount(obj : Mountable, _) =>
|
||||
|
|
@ -844,12 +828,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
})
|
||||
//drop items on ground
|
||||
val pos = tplayer.Position
|
||||
val orient = tplayer.Orientation
|
||||
val orient = Vector3(0,0, tplayer.Orientation.z)
|
||||
((dropHolsters ++ dropInventory).map(_.obj) ++ drop).foreach(obj => {
|
||||
continent.Ground ! Zone.DropItemOnGround(obj, pos, Vector3(0f, 0f, orient.z))
|
||||
sendResponse(ObjectDetachMessage(tplayer.GUID, obj.GUID, pos, 0f, 0f, orient.z))
|
||||
val objDef = obj.Definition
|
||||
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentOnGround(tplayer.GUID, pos, orient, objDef.ObjectId, obj.GUID, objDef.Packet.ConstructorData(obj).get))
|
||||
//TODO make a sound when dropping stuff
|
||||
continent.Ground ! Zone.Ground.DropItem(obj, pos, orient)
|
||||
})
|
||||
sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Buy, true))
|
||||
}
|
||||
|
|
@ -938,12 +920,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
})
|
||||
//drop stuff on ground
|
||||
val pos = tplayer.Position
|
||||
val orient = tplayer.Orientation
|
||||
val orient = Vector3(0,0, tplayer.Orientation.z)
|
||||
((dropHolsters ++ dropInventory).map(_.obj)).foreach(obj => {
|
||||
continent.Ground ! Zone.DropItemOnGround(obj, pos, Vector3(0f, 0f, orient.z))
|
||||
sendResponse(ObjectDetachMessage(tplayer.GUID, obj.GUID, pos, 0f, 0f, orient.z))
|
||||
val objDef = obj.Definition
|
||||
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentOnGround(tplayer.GUID, pos, orient, objDef.ObjectId, obj.GUID, objDef.Packet.ConstructorData(obj).get))
|
||||
continent.Ground ! Zone.Ground.DropItem(obj, pos, orient)
|
||||
})
|
||||
|
||||
case Terminal.VehicleLoadout(definition, weapons, inventory) =>
|
||||
|
|
@ -1164,9 +1143,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
|
||||
case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle, pad) =>
|
||||
val vehicle_guid = vehicle.GUID
|
||||
if(player.VehicleSeated.nonEmpty) {
|
||||
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid)
|
||||
}
|
||||
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 0L)) //mount points on?
|
||||
//sendResponse(PlanetsideAttributeMessage(vehicle_guid, 0, 10))//vehicle.Definition.MaxHealth))
|
||||
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 68, 0L)) //???
|
||||
|
|
@ -1177,7 +1153,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case VehicleSpawnPad.ServerVehicleOverrideStart(vehicle, pad) =>
|
||||
val vdef = vehicle.Definition
|
||||
if(vehicle.Seats(0).isOccupied) {
|
||||
sendResponse(ObjectDetachMessage(pad.GUID, vehicle.GUID, pad.Position + Vector3(0, 0, 0.5f), 0, 0, pad.Orientation.z))
|
||||
sendResponse(ObjectDetachMessage(pad.GUID, vehicle.GUID, pad.Position + Vector3(0, 0, 0.5f), pad.Orientation.z))
|
||||
}
|
||||
ServerVehicleOverride(vehicle, vdef.AutoPilotSpeed1, GlobalDefinitions.isFlightVehicle(vdef):Int)
|
||||
|
||||
|
|
@ -1355,7 +1331,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
(taskResolver, TaskBeforeZoneChange(GUIDTask.UnregisterAvatar(original)(continent.GUID), zone_id))
|
||||
}
|
||||
}
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
respawnTimer = context.system.scheduler.scheduleOnce(respawnTime seconds, target, msg)
|
||||
|
||||
|
|
@ -1375,12 +1350,56 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
RequestSanctuaryZoneSpawn(player, zone_number)
|
||||
}
|
||||
|
||||
case Zone.Ground.ItemOnGround(item, pos, orient) =>
|
||||
item.Position = pos
|
||||
item.Orientation = Vector3(0,0, orient.z) //dropped items rotate towards the user's standing direction
|
||||
val exclusionId = player.Find(item) match {
|
||||
case Some(slotNum) =>
|
||||
player.Slot(slotNum).Equipment = None
|
||||
sendResponse(ObjectDetachMessage(player.GUID, item.GUID, pos, orient.z))
|
||||
sendResponse(ActionResultMessage.Pass)
|
||||
player.GUID //we're dropping it; don't need to see it dropped again
|
||||
case None =>
|
||||
PlanetSideGUID(0) //object is being introduced into the world upon drop
|
||||
}
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.DropItem(exclusionId, item, continent))
|
||||
|
||||
case Zone.Ground.CanNotDropItem(zone, item, reason) =>
|
||||
log.warn(s"DropItem: $player tried to drop a $item on the ground, but $reason")
|
||||
|
||||
case Zone.Ground.ItemInHand(item) =>
|
||||
player.Fit(item) match {
|
||||
case Some(slotNum) =>
|
||||
val item_guid = item.GUID
|
||||
val player_guid = player.GUID
|
||||
player.Slot(slotNum).Equipment = item
|
||||
val definition = item.Definition
|
||||
sendResponse(
|
||||
ObjectCreateDetailedMessage(
|
||||
definition.ObjectId,
|
||||
item_guid,
|
||||
ObjectCreateMessageParent(player_guid, slotNum),
|
||||
definition.Packet.DetailedConstructorData(item).get
|
||||
)
|
||||
)
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PickupItem(player_guid, continent, player, slotNum, item))
|
||||
case None =>
|
||||
continent.Ground ! Zone.Ground.DropItem(item, item.Position, item.Orientation) //restore previous state
|
||||
}
|
||||
|
||||
case Zone.Ground.CanNotPickupItem(zone, item_guid, _) =>
|
||||
zone.GUID(item_guid) match {
|
||||
case Some(item) =>
|
||||
log.warn(s"DropItem: finding a $item on the ground was suggested, but $player can not reach it")
|
||||
case None =>
|
||||
log.warn(s"DropItem: finding an item ($item_guid) on the ground was suggested, but $player can not see it")
|
||||
}
|
||||
|
||||
case InterstellarCluster.ClientInitializationComplete() =>
|
||||
StopBundlingPackets()
|
||||
LivePlayerList.Add(sessionId, avatar)
|
||||
traveler = new Traveler(self, continent.Id)
|
||||
//PropertyOverrideMessage
|
||||
sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on
|
||||
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1))
|
||||
sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list
|
||||
sendResponse(FriendsResponse(FriendAction.InitializeFriendList, 0, true, true, Nil))
|
||||
|
|
@ -1420,7 +1439,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
if(!corpse.isAlive && corpse.HasGUID) {
|
||||
corpse.VehicleSeated match {
|
||||
case Some(_) =>
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
context.system.scheduler.scheduleOnce(50 milliseconds, self, UnregisterCorpseOnVehicleDisembark(corpse))
|
||||
case None =>
|
||||
|
|
@ -1433,6 +1451,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
val guid = tplayer.GUID
|
||||
StartBundlingPackets()
|
||||
sendResponse(SetCurrentAvatarMessage(guid,0,0))
|
||||
sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on //TODO once per respawn?
|
||||
sendResponse(PlayerStateShiftMessage(ShiftState(1, tplayer.Position, tplayer.Orientation.z)))
|
||||
if(spectator) {
|
||||
sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, false, "", "on", None))
|
||||
|
|
@ -1467,31 +1486,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
//TacticsMessage
|
||||
StopBundlingPackets()
|
||||
|
||||
|
||||
case Zone.ItemFromGround(tplayer, item) =>
|
||||
val obj_guid = item.GUID
|
||||
val player_guid = tplayer.GUID
|
||||
tplayer.Fit(item) match {
|
||||
case Some(slot) =>
|
||||
tplayer.Slot(slot).Equipment = item
|
||||
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(player_guid, obj_guid))
|
||||
val definition = item.Definition
|
||||
sendResponse(
|
||||
ObjectCreateDetailedMessage(
|
||||
definition.ObjectId,
|
||||
obj_guid,
|
||||
ObjectCreateMessageParent(player_guid, slot),
|
||||
definition.Packet.DetailedConstructorData(item).get
|
||||
)
|
||||
)
|
||||
sendResponse(ActionResultMessage())
|
||||
if(tplayer.VisibleSlots.contains(slot)) {
|
||||
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentInHand(player_guid, player_guid, slot, item))
|
||||
}
|
||||
case None =>
|
||||
continent.Ground ! Zone.DropItemOnGround(item, item.Position, item.Orientation) //restore
|
||||
}
|
||||
|
||||
case ItemHacking(tplayer, target, tool_guid, delta, completeAction, tickAction) =>
|
||||
progressBarUpdate.cancel
|
||||
if(progressBarValue.isDefined) {
|
||||
|
|
@ -1515,7 +1509,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
else { //continue next tick
|
||||
tickAction.getOrElse(() => Unit)()
|
||||
progressBarValue = Some(progressBarVal)
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
progressBarUpdate = context.system.scheduler.scheduleOnce(250 milliseconds, self, ItemHacking(tplayer, target, tool_guid, delta, completeAction))
|
||||
}
|
||||
|
|
@ -1603,22 +1596,20 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
player.Locker.Inventory += 0 -> SimpleItem(remote_electronics_kit)
|
||||
//TODO end temp player character auto-loading
|
||||
self ! ListAccountCharacters
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
clientKeepAlive.cancel
|
||||
clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient())
|
||||
log.warn(PacketCoding.DecodePacket(hex"d2327e7b8a972b95113881003710").toString)
|
||||
|
||||
case msg @ CharacterCreateRequestMessage(name, head, voice, gender, empire) =>
|
||||
log.info("Handling " + msg)
|
||||
sendResponse(ActionResultMessage(true, None))
|
||||
sendResponse(ActionResultMessage.Pass)
|
||||
self ! ListAccountCharacters
|
||||
|
||||
case msg @ CharacterRequestMessage(charId, action) =>
|
||||
log.info("Handling " + msg)
|
||||
action match {
|
||||
case CharacterRequestAction.Delete =>
|
||||
sendResponse(ActionResultMessage(false, Some(1)))
|
||||
sendResponse(ActionResultMessage.Fail(1))
|
||||
case CharacterRequestAction.Select =>
|
||||
//TODO check if can spawn on last continent/location from player?
|
||||
//TODO if yes, get continent guid accessors
|
||||
|
|
@ -1825,10 +1816,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid, 0))
|
||||
self ! PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, BailType.Normal, true)) //let vehicle try to clean up its fields
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
context.system.scheduler.scheduleOnce(50 milliseconds, self, UnregisterCorpseOnVehicleDisembark(player))
|
||||
//sendResponse(ObjectDetachMessage(vehicle_guid, player.GUID, Vector3.Zero, 0, 0, 0))
|
||||
//sendResponse(ObjectDetachMessage(vehicle_guid, player.GUID, Vector3.Zero, 0))
|
||||
//sendResponse(PlayerStateShiftMessage(ShiftState(1, Vector3.Zero, 0)))
|
||||
}
|
||||
|
||||
|
|
@ -1989,8 +1979,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
val tailReloadValue : Int = if(xs.isEmpty) { 0 } else { xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).reduceLeft(_ + _) }
|
||||
val sumReloadValue : Int = originalBoxCapacity + tailReloadValue
|
||||
val previousBox = tool.AmmoSlot.Box //current magazine in tool
|
||||
sendResponse(ObjectDetachMessage(tool.GUID, previousBox.GUID, Vector3(0f, 0f, 0f), 0f, 0f, 0f))
|
||||
sendResponse(ObjectDetachMessage(player.GUID, box.GUID, Vector3(0f, 0f, 0f), 0f, 0f, 0f))
|
||||
sendResponse(ObjectDetachMessage(tool.GUID, previousBox.GUID, Vector3.Zero, 0f))
|
||||
sendResponse(ObjectDetachMessage(player.GUID, box.GUID, Vector3.Zero, 0f))
|
||||
obj.Inventory -= x.start //remove replacement ammo from inventory
|
||||
val ammoSlotIndex = tool.FireMode.AmmoSlotIndex
|
||||
tool.AmmoSlots(ammoSlotIndex).Box = box //put replacement ammo in tool
|
||||
|
|
@ -2158,27 +2148,37 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(EmoteMsg(avatar_guid, emote))
|
||||
|
||||
case msg @ DropItemMessage(item_guid) =>
|
||||
log.info("DropItem: " + msg)
|
||||
player.FreeHand.Equipment match {
|
||||
case Some(item) =>
|
||||
if(item.GUID == item_guid) {
|
||||
val orient : Vector3 = Vector3(0f, 0f, player.Orientation.z)
|
||||
player.FreeHand.Equipment = None
|
||||
continent.Ground ! Zone.DropItemOnGround(item, player.Position, orient)
|
||||
sendResponse(ObjectDetachMessage(player.GUID, item.GUID, player.Position, 0f, 0f, player.Orientation.z))
|
||||
val objDef = item.Definition
|
||||
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, player.Position, orient, objDef.ObjectId, item.GUID, objDef.Packet.ConstructorData(item).get))
|
||||
}
|
||||
else {
|
||||
log.warn(s"item in hand was ${item.GUID} but trying to drop $item_guid; nothing will be dropped")
|
||||
log.info(s"DropItem: $msg")
|
||||
continent.GUID(item_guid) match {
|
||||
case Some(item : Equipment) =>
|
||||
player.FreeHand.Equipment match {
|
||||
case Some(_) =>
|
||||
if(item.GUID == item_guid) {
|
||||
continent.Ground ! Zone.Ground.DropItem(item, player.Position, player.Orientation)
|
||||
}
|
||||
case None =>
|
||||
log.warn(s"DropItem: $player wanted to drop a $item, but it wasn't at hand")
|
||||
}
|
||||
case Some(obj) => //TODO LLU
|
||||
log.warn(s"DropItem: $player wanted to drop a $obj, but that isn't possible")
|
||||
case None =>
|
||||
log.error(s"$player wanted to drop an item, but it was not in hand")
|
||||
log.warn(s"DropItem: $player wanted to drop an item ($item_guid), but it was nowhere to be found")
|
||||
}
|
||||
|
||||
case msg @ PickupItemMessage(item_guid, player_guid, unk1, unk2) =>
|
||||
log.info("PickupItem: " + msg)
|
||||
continent.Ground ! Zone.GetItemOnGround(player, item_guid)
|
||||
log.info(s"PickupItem: $msg")
|
||||
continent.GUID(item_guid) match {
|
||||
case Some(item : Equipment) =>
|
||||
player.Fit(item) match {
|
||||
case Some(_) =>
|
||||
continent.Ground ! Zone.Ground.PickupItem(item_guid)
|
||||
case None => //skip
|
||||
sendResponse(ActionResultMessage.Fail(16)) //error code?
|
||||
}
|
||||
case _ =>
|
||||
log.warn(s"PickupItem: $player requested an item that doesn't exist in this zone; assume client-side garbage data")
|
||||
sendResponse(ObjectDeleteMessage(item_guid, 0))
|
||||
}
|
||||
|
||||
case msg @ ReloadMessage(item_guid, ammo_clip, unk1) =>
|
||||
log.info("Reload: " + msg)
|
||||
|
|
@ -2276,12 +2276,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
if((player.VehicleOwned.contains(object_guid) && vehicle.Owner.contains(player.GUID))
|
||||
|| (player.Faction == vehicle.Faction
|
||||
&& ((vehicle.Owner.isEmpty || continent.GUID(vehicle.Owner.get).isEmpty) || vehicle.Health == 0))) {
|
||||
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(object_guid)
|
||||
vehicleService ! VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent)
|
||||
log.info(s"RequestDestroy: vehicle $object_guid")
|
||||
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(vehicle), continent))
|
||||
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent, Some(0 seconds)))
|
||||
log.info(s"RequestDestroy: vehicle $vehicle")
|
||||
}
|
||||
else {
|
||||
log.info(s"RequestDestroy: must own vehicle $object_guid in order to deconstruct it")
|
||||
log.info(s"RequestDestroy: must own vehicle in order to deconstruct it")
|
||||
}
|
||||
|
||||
case Some(obj : Equipment) =>
|
||||
|
|
@ -2303,20 +2303,28 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
})
|
||||
match {
|
||||
case Some((parent, Some(slot))) =>
|
||||
obj.Position = Vector3.Zero
|
||||
taskResolver ! RemoveEquipmentFromSlot(parent, obj, slot)
|
||||
log.info(s"RequestDestroy: equipment $object_guid")
|
||||
log.info(s"RequestDestroy: equipment $obj")
|
||||
|
||||
case _ =>
|
||||
//TODO search for item on ground
|
||||
sendResponse(ObjectDeleteMessage(object_guid, 0))
|
||||
log.warn(s"RequestDestroy: object $object_guid not found")
|
||||
if(continent.EquipmentOnGround.contains(obj)) {
|
||||
obj.Position = Vector3.Zero
|
||||
continent.Ground ! Zone.Ground.RemoveItem(object_guid)
|
||||
avatarService ! AvatarServiceMessage.Ground(RemoverActor.ClearSpecific(List(obj), continent))
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(PlanetSideGUID(0), object_guid))
|
||||
log.info(s"RequestDestroy: equipment $obj on ground")
|
||||
}
|
||||
else {
|
||||
log.warn(s"RequestDestroy: equipment $obj exists, but can not be reached")
|
||||
}
|
||||
}
|
||||
|
||||
case Some(thing) =>
|
||||
log.warn(s"RequestDestroy: not allowed to delete object $thing")
|
||||
|
||||
case None =>
|
||||
log.warn(s"RequestDestroy: object $object_guid not found")
|
||||
|
||||
case _ =>
|
||||
log.warn(s"RequestDestroy: not allowed to delete object $object_guid")
|
||||
}
|
||||
|
||||
case msg @ ObjectDeleteMessage(object_guid, unk1) =>
|
||||
|
|
@ -2793,7 +2801,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
//todo: implement auto landing procedure if the pilot bails but passengers are still present instead of deconstructing the vehicle
|
||||
//todo: continue flight path until aircraft crashes if no passengers present (or no passenger seats), then deconstruct.
|
||||
if(bailType == BailType.Bailed && seat_num == 0 && GlobalDefinitions.isFlightVehicle(obj.asInstanceOf[Vehicle].Definition)) {
|
||||
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj.asInstanceOf[Vehicle], continent, 0L) // Immediately deconstruct vehicle
|
||||
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(obj, continent, Some(0 seconds))) // Immediately deconstruct vehicle
|
||||
}
|
||||
|
||||
case None =>
|
||||
|
|
@ -3847,14 +3855,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case Some(InventoryItem(item2, destIndex)) => //yes, swap
|
||||
//cleanly shuffle items around to avoid losing icons
|
||||
//the next ObjectDetachMessage is necessary to avoid icons being lost, but only as part of this swap
|
||||
sendResponse(ObjectDetachMessage(source_guid, item_guid, Vector3.Zero, 0f, 0f, 0f))
|
||||
sendResponse(ObjectDetachMessage(source_guid, item_guid, Vector3.Zero, 0f))
|
||||
val item2_guid = item2.GUID
|
||||
destination.Slot(destIndex).Equipment = None //remove the swap item from destination
|
||||
(indexSlot.Equipment = item2) match {
|
||||
case Some(_) => //item and item2 swapped places successfully
|
||||
log.info(s"MoveItem: $item2 swapped to $source @ $index")
|
||||
//remove item2 from destination
|
||||
sendResponse(ObjectDetachMessage(destination_guid, item2_guid, Vector3.Zero, 0f, 0f, 0f))
|
||||
sendResponse(ObjectDetachMessage(destination_guid, item2_guid, Vector3.Zero, 0f))
|
||||
destination match {
|
||||
case obj : Vehicle =>
|
||||
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item2_guid))
|
||||
|
|
@ -3897,8 +3905,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
val pos = source.Position
|
||||
val sourceOrientZ = source.Orientation.z
|
||||
val orient : Vector3 = Vector3(0f, 0f, sourceOrientZ)
|
||||
continent.Actor ! Zone.DropItemOnGround(item2, pos, orient)
|
||||
sendResponse(ObjectDetachMessage(destination_guid, item2_guid, pos, 0f, 0f, sourceOrientZ)) //ground
|
||||
continent.Ground ! Zone.Ground.DropItem(item2, pos, orient)
|
||||
sendResponse(ObjectDetachMessage(destination_guid, item2_guid, pos, sourceOrientZ)) //ground
|
||||
val objDef = item2.Definition
|
||||
destination match {
|
||||
case obj : Vehicle =>
|
||||
|
|
@ -3906,7 +3914,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case _ => ;
|
||||
//Player does not require special case; the act of dropping forces the item and icon to change
|
||||
}
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentOnGround(player_guid, pos, orient, objDef.ObjectId, item2_guid, objDef.Packet.ConstructorData(item2).get))
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.DropItem(player_guid, item2, continent))
|
||||
}
|
||||
|
||||
case None => ;
|
||||
|
|
@ -3956,15 +3964,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
* @param item the item
|
||||
*/
|
||||
def NormalItemDrop(obj : PlanetSideGameObject with Container, zone : Zone, service : ActorRef)(item : Equipment) : Unit = {
|
||||
val itemGUID = item.GUID
|
||||
val ang = obj.Orientation.z
|
||||
val pos = obj.Position
|
||||
val orient = Vector3(0f, 0f, ang)
|
||||
item.Position = pos
|
||||
item.Orientation = orient
|
||||
zone.Ground ! Zone.DropItemOnGround(item, pos, orient)
|
||||
val itemDef = item.Definition
|
||||
service ! AvatarServiceMessage(zone.Id, AvatarAction.EquipmentOnGround(Service.defaultPlayerGUID, pos, orient, itemDef.ObjectId, itemGUID, itemDef.Packet.ConstructorData(item).get))
|
||||
continent.Ground ! Zone.Ground.DropItem(item, obj.Position, Vector3(0f, 0f, obj.Orientation.z))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -4267,7 +4267,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
PlayerActionsToCancel()
|
||||
CancelAllProximityUnits()
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer milliseconds, galaxy, Zone.Lattice.RequestSpawnPoint(Zones.SanctuaryZoneNumber(tplayer.Faction), tplayer, 7))
|
||||
}
|
||||
|
|
@ -4408,9 +4407,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
*/
|
||||
def TryDisposeOfLootedCorpse(obj : Player) : Boolean = {
|
||||
if(WellLootedCorpse(obj)) {
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
context.system.scheduler.scheduleOnce(1 second, avatarService, AvatarServiceMessage.RemoveSpecificCorpse(List(obj)))
|
||||
avatarService ! AvatarServiceMessage.Corpse(RemoverActor.HurrySpecific(List(obj), continent))
|
||||
true
|
||||
}
|
||||
else {
|
||||
|
|
@ -4486,7 +4483,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
def SetDelayedProximityUnitReset(terminal : Terminal with ProximityUnit) : Unit = {
|
||||
val terminal_guid = terminal.GUID
|
||||
ClearDelayedProximityUnitReset(terminal_guid)
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
delayedProximityTerminalResets += terminal_guid ->
|
||||
context.system.scheduler.scheduleOnce(3000 milliseconds, self, DelayedProximityUnitStop(terminal))
|
||||
|
|
|
|||
502
pslogin/src/main/scala/services/RemoverActor.scala
Normal file
502
pslogin/src/main/scala/services/RemoverActor.scala
Normal file
|
|
@ -0,0 +1,502 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Cancellable}
|
||||
import net.psforever.objects.guid.TaskResolver
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.objects.{DefaultCancellable, PlanetSideGameObject}
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* The base class for a type of "destruction `Actor`" intended to be used for delaying object cleanup activity.
|
||||
* Objects submitted to this process should be registered to a global unique identified system for a given region
|
||||
* as is specified in their submission.<br>
|
||||
* <br>
|
||||
* Two waiting lists are used to pool the objects being removed.
|
||||
* The first list is a basic pooling list that precludes any proper removal actions
|
||||
* and is almost expressly for delaying the process.
|
||||
* Previously-submitted tasks can be removed from this list so long as a matching object can be found.
|
||||
* Tasks in this list can also be expedited into the second list without having to consider delays.
|
||||
* After being migrated to the secondary list, the object is considered beyond the point of no return.
|
||||
* Followup activity will lead to its inevitable unregistering and removal.<br>
|
||||
* <br>
|
||||
* Functions have been provided for `override` in order to interject the appropriate cleanup operations.
|
||||
* The activity itself is typically removing the object in question from a certain list,
|
||||
* dismissing it with a mass distribution of `ObjectDeleteMessage` packets,
|
||||
* and finally unregistering it.
|
||||
* Some types of object have (de-)implementation variations which should be made explicit through the overrides.
|
||||
*/
|
||||
abstract class RemoverActor extends Actor {
|
||||
/**
|
||||
* The timer that checks whether entries in the first pool are still eligible for that pool.
|
||||
*/
|
||||
var firstTask : Cancellable = DefaultCancellable.obj
|
||||
/**
|
||||
* The first pool of objects waiting to be processed for removal.
|
||||
*/
|
||||
var firstHeap : List[RemoverActor.Entry] = List()
|
||||
|
||||
/**
|
||||
* The timer that checks whether entries in the second pool are still eligible for that pool.
|
||||
*/
|
||||
var secondTask : Cancellable = DefaultCancellable.obj
|
||||
/**
|
||||
* The second pool of objects waiting to be processed for removal.
|
||||
*/
|
||||
var secondHeap : List[RemoverActor.Entry] = List()
|
||||
|
||||
private var taskResolver : ActorRef = Actor.noSender
|
||||
|
||||
private[this] val log = org.log4s.getLogger
|
||||
def trace(msg : String) : Unit = log.trace(msg)
|
||||
def debug(msg : String) : Unit = log.debug(msg)
|
||||
|
||||
/**
|
||||
* Send the initial message that requests a task resolver for assisting in the removal process.
|
||||
*/
|
||||
override def preStart() : Unit = {
|
||||
super.preStart()
|
||||
self ! RemoverActor.Startup()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sufficiently clean up the current contents of these waiting removal jobs.
|
||||
* Cancel all timers, rush all entries in the lists through their individual steps, then empty the lists.
|
||||
* This is an improved `HurryAll`, but still faster since it also railroads entries through the second queue as well.
|
||||
*/
|
||||
override def postStop() = {
|
||||
super.postStop()
|
||||
firstTask.cancel
|
||||
secondTask.cancel
|
||||
firstHeap.foreach(entry => {
|
||||
FirstJob(entry)
|
||||
SecondJob(entry)
|
||||
})
|
||||
secondHeap.foreach { SecondJob }
|
||||
firstHeap = Nil
|
||||
secondHeap = Nil
|
||||
taskResolver = ActorRef.noSender
|
||||
}
|
||||
|
||||
def receive : Receive = {
|
||||
case RemoverActor.Startup() =>
|
||||
ServiceManager.serviceManager ! ServiceManager.Lookup("taskResolver") //ask for a resolver to deal with the GUID system
|
||||
|
||||
case ServiceManager.LookupResult("taskResolver", endpoint) =>
|
||||
taskResolver = endpoint
|
||||
context.become(Processing)
|
||||
|
||||
case msg =>
|
||||
log.error(s"received message $msg before being properly initialized")
|
||||
}
|
||||
|
||||
def Processing : Receive = {
|
||||
case RemoverActor.AddTask(obj, zone, duration) =>
|
||||
val entry = RemoverActor.Entry(obj, zone, duration.getOrElse(FirstStandardDuration).toNanos)
|
||||
if(InclusionTest(entry) && !secondHeap.exists(test => RemoverActor.Similarity(test, entry) )) {
|
||||
InitialJob(entry)
|
||||
if(firstHeap.isEmpty) {
|
||||
//we were the only entry so the event must be started from scratch
|
||||
firstHeap = List(entry)
|
||||
trace(s"a remover task has been added: $entry")
|
||||
RetimeFirstTask()
|
||||
}
|
||||
else {
|
||||
//unknown number of entries; append, sort, then re-time tasking
|
||||
val oldHead = firstHeap.head
|
||||
if(!firstHeap.exists(test => RemoverActor.Similarity(test, entry))) {
|
||||
firstHeap = (firstHeap :+ entry).sortBy(_.duration)
|
||||
trace(s"a remover task has been added: $entry")
|
||||
if(oldHead != firstHeap.head) {
|
||||
RetimeFirstTask()
|
||||
}
|
||||
}
|
||||
else {
|
||||
trace(s"$obj is already queued for removal")
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
trace(s"$obj either does not qualify for this Remover or is already queued")
|
||||
}
|
||||
|
||||
case RemoverActor.HurrySpecific(targets, zone) =>
|
||||
HurrySpecific(targets, zone)
|
||||
|
||||
case RemoverActor.HurryAll() =>
|
||||
HurryAll()
|
||||
|
||||
case RemoverActor.ClearSpecific(targets, zone) =>
|
||||
ClearSpecific(targets, zone)
|
||||
|
||||
case RemoverActor.ClearAll() =>
|
||||
ClearAll()
|
||||
|
||||
//private messages from RemoverActor to RemoverActor
|
||||
case RemoverActor.StartDelete() =>
|
||||
firstTask.cancel
|
||||
secondTask.cancel
|
||||
val now : Long = System.nanoTime
|
||||
val (in, out) = firstHeap.partition(entry => { now - entry.time >= entry.duration })
|
||||
firstHeap = out
|
||||
secondHeap = secondHeap ++ in.map { RepackageEntry }
|
||||
in.foreach { FirstJob }
|
||||
RetimeFirstTask()
|
||||
if(secondHeap.nonEmpty) {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
secondTask = context.system.scheduler.scheduleOnce(SecondStandardDuration, self, RemoverActor.TryDelete())
|
||||
}
|
||||
trace(s"item removal task has found ${in.size} items to remove")
|
||||
|
||||
case RemoverActor.TryDelete() =>
|
||||
secondTask.cancel
|
||||
val (in, out) = secondHeap.partition { ClearanceTest }
|
||||
secondHeap = out
|
||||
in.foreach { SecondJob }
|
||||
if(out.nonEmpty) {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
secondTask = context.system.scheduler.scheduleOnce(SecondStandardDuration, self, RemoverActor.TryDelete())
|
||||
}
|
||||
trace(s"item removal task has removed ${in.size} items")
|
||||
|
||||
case RemoverActor.FailureToWork(entry, ex) =>
|
||||
log.error(s"${entry.obj} from ${entry.zone} not properly deleted - $ex")
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expedite some entries from the first pool into the second.
|
||||
* @param targets a list of objects to pick
|
||||
* @param zone the zone in which these objects must be discovered;
|
||||
* all targets must be in this zone, with the assumption that this is the zone where they were registered
|
||||
*/
|
||||
def HurrySpecific(targets : List[PlanetSideGameObject], zone : Zone) : Unit = {
|
||||
CullTargetsFromFirstHeap(targets, zone) match {
|
||||
case Nil =>
|
||||
debug(s"no tasks matching the targets $targets have been hurried")
|
||||
case list =>
|
||||
debug(s"the following tasks have been hurried: $list")
|
||||
secondTask.cancel
|
||||
list.foreach { FirstJob }
|
||||
secondHeap = secondHeap ++ list.map { RepackageEntry }
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
secondTask = context.system.scheduler.scheduleOnce(SecondStandardDuration, self, RemoverActor.TryDelete())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expedite all entries from the first pool into the second.
|
||||
*/
|
||||
def HurryAll() : Unit = {
|
||||
trace("all tasks have been hurried")
|
||||
firstTask.cancel
|
||||
firstHeap.foreach { FirstJob }
|
||||
secondHeap = secondHeap ++ firstHeap.map { RepackageEntry }
|
||||
firstHeap = Nil
|
||||
secondTask.cancel
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
secondTask = context.system.scheduler.scheduleOnce(SecondStandardDuration, self, RemoverActor.TryDelete())
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove specific entries from the first pool.
|
||||
*/
|
||||
def ClearSpecific(targets : List[PlanetSideGameObject], zone : Zone) : Unit = {
|
||||
CullTargetsFromFirstHeap(targets, zone) match {
|
||||
case Nil =>
|
||||
debug(s"no tasks matching the targets $targets have been cleared")
|
||||
case list =>
|
||||
debug(s"the following tasks have been cleared: $list")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* No entries in the first pool.
|
||||
*/
|
||||
def ClearAll() : Unit = {
|
||||
firstTask.cancel
|
||||
firstHeap = Nil
|
||||
}
|
||||
|
||||
/**
|
||||
* Retime an individual entry by recreating it.
|
||||
* @param entry an existing entry
|
||||
* @return a new entry, containing the same object and zone information;
|
||||
* this new entry is always set to last for the duration of the second pool
|
||||
*/
|
||||
private def RepackageEntry(entry : RemoverActor.Entry) : RemoverActor.Entry = {
|
||||
RemoverActor.Entry(entry.obj, entry.zone, SecondStandardDuration.toNanos)
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the first pool of entries awaiting removal processing.
|
||||
* If any entry has the same object as one of the targets and belongs to the same zone, remove it from the first pool.
|
||||
* If no targets are selected (an empty list), all discovered targets within the appropriate zone are removed.
|
||||
* @param targets a list of objects to pick
|
||||
* @param zone the zone in which these objects must be discovered;
|
||||
* all targets must be in this zone, with the assumption that this is the zone where they were registered
|
||||
* @return all of the discovered entries
|
||||
*/
|
||||
private def CullTargetsFromFirstHeap(targets : List[PlanetSideGameObject], zone : Zone) : List[RemoverActor.Entry] = {
|
||||
val culledEntries = if(targets.nonEmpty) {
|
||||
if(targets.size == 1) {
|
||||
debug(s"a target submitted: ${targets.head}")
|
||||
//simple selection
|
||||
RemoverActor.recursiveFind(firstHeap.iterator, RemoverActor.Entry(targets.head, zone, 0)) match {
|
||||
case None => ;
|
||||
Nil
|
||||
case Some(index) =>
|
||||
val entry = firstHeap(index)
|
||||
firstHeap = (firstHeap.take(index) ++ firstHeap.drop(index + 1)).sortBy(_.duration)
|
||||
List(entry)
|
||||
}
|
||||
}
|
||||
else {
|
||||
debug(s"multiple targets submitted: $targets")
|
||||
//cumbersome partition
|
||||
//a - find targets from entries
|
||||
val locatedTargets = for {
|
||||
a <- targets.map(RemoverActor.Entry(_, zone, 0))
|
||||
b <- firstHeap//.filter(entry => entry.zone == zone)
|
||||
if b.obj.HasGUID && a.obj.HasGUID && RemoverActor.Similarity(b, a)
|
||||
} yield b
|
||||
if(locatedTargets.nonEmpty) {
|
||||
//b - entries, after the found targets are removed (cull any non-GUID entries while at it)
|
||||
firstHeap = (for {
|
||||
a <- locatedTargets
|
||||
b <- firstHeap
|
||||
if b.obj.HasGUID && a.obj.HasGUID && !RemoverActor.Similarity(b, a)
|
||||
} yield b).sortBy(_.duration)
|
||||
locatedTargets
|
||||
}
|
||||
else {
|
||||
Nil
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
debug(s"all targets within the specified zone $zone will be submitted")
|
||||
//no specific targets; split on all targets in the given zone instead
|
||||
val (in, out) = firstHeap.partition(entry => entry.zone == zone)
|
||||
firstHeap = out.sortBy(_.duration)
|
||||
in
|
||||
}
|
||||
if(culledEntries.nonEmpty) {
|
||||
RetimeFirstTask()
|
||||
culledEntries
|
||||
}
|
||||
else {
|
||||
Nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common function to reset the first task's delayed execution.
|
||||
* Cancels the scheduled timer and will only restart the timer if there is at least one entry in the first pool.
|
||||
* @param now the time (in nanoseconds);
|
||||
* defaults to the current time (in nanoseconds)
|
||||
*/
|
||||
def RetimeFirstTask(now : Long = System.nanoTime) : Unit = {
|
||||
firstTask.cancel
|
||||
if(firstHeap.nonEmpty) {
|
||||
val short_timeout : FiniteDuration = math.max(1, firstHeap.head.duration - (now - firstHeap.head.time)) nanoseconds
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
firstTask = context.system.scheduler.scheduleOnce(short_timeout, self, RemoverActor.StartDelete())
|
||||
}
|
||||
}
|
||||
|
||||
def SecondJob(entry : RemoverActor.Entry) : Unit = {
|
||||
entry.obj.Position = Vector3.Zero //somewhere it will not disturb anything
|
||||
taskResolver ! FinalTask(entry)
|
||||
}
|
||||
|
||||
def FinalTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = {
|
||||
import net.psforever.objects.guid.Task
|
||||
TaskResolver.GiveTask (
|
||||
new Task() {
|
||||
private val localEntry = entry
|
||||
private val localAnnounce = self
|
||||
|
||||
override def isComplete : Task.Resolution.Value = if(!localEntry.obj.HasGUID) {
|
||||
Task.Resolution.Success
|
||||
}
|
||||
else {
|
||||
Task.Resolution.Incomplete
|
||||
}
|
||||
|
||||
def Execute(resolver : ActorRef) : Unit = {
|
||||
resolver ! scala.util.Success(this)
|
||||
}
|
||||
|
||||
override def onFailure(ex : Throwable): Unit = {
|
||||
localAnnounce ! RemoverActor.FailureToWork(localEntry, ex)
|
||||
}
|
||||
}, List(DeletionTask(entry))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Default time for entries waiting in the first list.
|
||||
* Override.
|
||||
* @return the time as a `FiniteDuration` object (to be later transformed into nanoseconds)
|
||||
*/
|
||||
def FirstStandardDuration : FiniteDuration
|
||||
|
||||
/**
|
||||
* Default time for entries waiting in the second list.
|
||||
* Override.
|
||||
* @return the time as a `FiniteDuration` object (to be later transformed into nanoseconds)
|
||||
*/
|
||||
def SecondStandardDuration : FiniteDuration
|
||||
|
||||
/**
|
||||
* Determine whether or not the resulting entry is valid for this removal process.
|
||||
* The primary purpose of this function should be to determine if the appropriate type of object is being submitted.
|
||||
* Override.
|
||||
* @param entry the entry
|
||||
* @return `true`, if it can be processed; `false`, otherwise
|
||||
*/
|
||||
def InclusionTest(entry : RemoverActor.Entry) : Boolean
|
||||
|
||||
/**
|
||||
* Performed when the entry is initially added to the first list.
|
||||
* Override.
|
||||
* @param entry the entry
|
||||
*/
|
||||
def InitialJob(entry : RemoverActor.Entry) : Unit
|
||||
|
||||
/**
|
||||
* Performed when the entry is shifted from the first list to the second list.
|
||||
* Override.
|
||||
* @param entry the entry
|
||||
*/
|
||||
def FirstJob(entry : RemoverActor.Entry) : Unit
|
||||
|
||||
/**
|
||||
* Performed to determine when an entry can be shifted off from the second list.
|
||||
* Override.
|
||||
* @param entry the entry
|
||||
*/
|
||||
def ClearanceTest(entry : RemoverActor.Entry) : Boolean
|
||||
|
||||
/**
|
||||
* The specific action that is necessary to complete the removal process.
|
||||
* Override.
|
||||
* @see `GUIDTask`
|
||||
* @param entry the entry
|
||||
*/
|
||||
def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask
|
||||
}
|
||||
|
||||
object RemoverActor {
|
||||
/**
|
||||
* All information necessary to apply to the removal process to produce an effect.
|
||||
* Internally, all entries have a "time created" field.
|
||||
* @param obj the target
|
||||
* @param zone the zone in which this target is registered
|
||||
* @param duration how much longer the target will exist in its current state (in nanoseconds)
|
||||
*/
|
||||
case class Entry(obj : PlanetSideGameObject, zone : Zone, duration : Long) {
|
||||
/** The time when this entry was created (in nanoseconds) */
|
||||
val time : Long = System.nanoTime
|
||||
}
|
||||
|
||||
/**
|
||||
* A message that prompts the retrieval of a `TaskResolver` for us in the removal process.
|
||||
*/
|
||||
case class Startup()
|
||||
|
||||
/**
|
||||
* Message to submit an object to the removal process.
|
||||
* @see `FirstStandardDuration`
|
||||
* @param obj the target
|
||||
* @param zone the zone in which this target is registered
|
||||
* @param duration how much longer the target will exist in its current state (in nanoseconds);
|
||||
* a default time duration is provided by implementation
|
||||
*/
|
||||
case class AddTask(obj : PlanetSideGameObject, zone : Zone, duration : Option[FiniteDuration] = None)
|
||||
|
||||
/**
|
||||
* "Hurrying" shifts entries with the discovered objects (in the same `zone`)
|
||||
* through their first task and into the second pool.
|
||||
* If the list of targets is empty, all discovered objects in the given zone will be considered targets.
|
||||
* @param targets a list of objects to match
|
||||
* @param zone the zone in which these objects exist;
|
||||
* the assumption is that all these target objects are registered to this zone
|
||||
*/
|
||||
case class HurrySpecific(targets : List[PlanetSideGameObject], zone : Zone)
|
||||
/**
|
||||
* "Hurrying" shifts all entries through their first task and into the second pool.
|
||||
*/
|
||||
case class HurryAll()
|
||||
|
||||
/**
|
||||
* "Clearing" cancels entries with the discovered objects (in the same `zone`)
|
||||
* if they are discovered in the first pool of objects.
|
||||
* Those entries will no longer be affected by any actions performed by the removal process until re-submitted.
|
||||
* If the list of targets is empty, all discovered objects in the given zone will be considered targets.
|
||||
* @param targets a list of objects to match
|
||||
* @param zone the zone in which these objects exist;
|
||||
* the assumption is that all these target objects are registered to this zone
|
||||
*/
|
||||
case class ClearSpecific(targets : List[PlanetSideGameObject], zone : Zone)
|
||||
/**
|
||||
* "Clearing" cancels all entries if they are discovered in the first pool of objects.
|
||||
* Those entries will no longer be affected by any actions performed by the removal process until re-submitted.
|
||||
*/
|
||||
case class ClearAll()
|
||||
|
||||
/**
|
||||
* Message that indicates that the final stage of the remover process has failed.
|
||||
* Since the last step is generally unregistering the object, it could be a critical error.
|
||||
* @param entry the entry that was not properly removed
|
||||
* @param ex the reason the last entry was not properly removed
|
||||
*/
|
||||
protected final case class FailureToWork(entry : RemoverActor.Entry, ex : Throwable)
|
||||
|
||||
/**
|
||||
* Internal message to flag operations by data in the first list if it has been in that list long enough.
|
||||
*/
|
||||
private final case class StartDelete()
|
||||
|
||||
/**
|
||||
* Internal message to flag operations by data in the second list if it has been in that list long enough.
|
||||
*/
|
||||
private final case class TryDelete()
|
||||
|
||||
/**
|
||||
* Match two entries by object and by zone information.
|
||||
* @param entry1 the first entry
|
||||
* @param entry2 the second entry
|
||||
* @return if they match
|
||||
*/
|
||||
private def Similarity(entry1 : RemoverActor.Entry, entry2 : RemoverActor.Entry) : Boolean = {
|
||||
entry1.obj == entry2.obj && entry1.zone == entry2.zone && entry1.obj.GUID == entry2.obj.GUID
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of an entry in the list of entries.
|
||||
* @param iter an `Iterator` of entries
|
||||
* @param target the specific entry to be found
|
||||
* @param index the incrementing index value
|
||||
* @return the index of the entry in the list, if a match to the target is found
|
||||
*/
|
||||
@tailrec private def recursiveFind(iter : Iterator[RemoverActor.Entry], target : RemoverActor.Entry, index : Int = 0) : Option[Int] = {
|
||||
if(!iter.hasNext) {
|
||||
None
|
||||
}
|
||||
else {
|
||||
val entry = iter.next
|
||||
if(entry.obj.HasGUID && target.obj.HasGUID && Similarity(entry, target)) {
|
||||
Some(index)
|
||||
}
|
||||
else {
|
||||
recursiveFind(iter, target, index + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,15 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.avatar
|
||||
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.{PlanetSideGameObject, Player}
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.inventory.Container
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream}
|
||||
import net.psforever.packet.game.objectcreate.ConstructorData
|
||||
import net.psforever.types.{ExoSuitType, Vector3}
|
||||
import net.psforever.types.ExoSuitType
|
||||
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
|
||||
object AvatarAction {
|
||||
trait Action
|
||||
|
|
@ -17,21 +20,19 @@ object AvatarAction {
|
|||
final case class ChangeFireState_Start(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
|
||||
final case class ChangeFireState_Stop(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
|
||||
final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Action
|
||||
final case class DropItem(player_guid : PlanetSideGUID, item : Equipment, zone : Zone) extends Action
|
||||
final case class EquipmentInHand(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
|
||||
final case class EquipmentOnGround(player_guid : PlanetSideGUID, pos : Vector3, orient : Vector3, item_id : Int, item_guid : PlanetSideGUID, item_data : ConstructorData) extends Action
|
||||
final case class LoadPlayer(player_guid : PlanetSideGUID, pdata : ConstructorData) extends Action
|
||||
// final case class LoadMap(msg : PlanetSideGUID) extends Action
|
||||
// final case class unLoadMap(msg : PlanetSideGUID) extends Action
|
||||
final case class ObjectDelete(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID, unk : Int = 0) extends Action
|
||||
final case class ObjectHeld(player_guid : PlanetSideGUID, slot : Int) extends Action
|
||||
final case class PlanetsideAttribute(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action
|
||||
final case class PlayerState(player_guid : PlanetSideGUID, msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Action
|
||||
final case class Release(player : Player, zone : Zone, time : Option[Long] = None) extends Action
|
||||
final case class PickupItem(player_guid : PlanetSideGUID, zone : Zone, target : PlanetSideGameObject with Container, slot : Int, item : Equipment, unk : Int = 0) extends Action
|
||||
final case class Release(player : Player, zone : Zone, time : Option[FiniteDuration] = None) extends Action
|
||||
final case class Reload(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
|
||||
final case class StowEquipment(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
|
||||
final case class WeaponDryFire(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
|
||||
// final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
|
||||
// final case class DestroyDisplay(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
|
||||
// final case class HitHintReturn(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
|
||||
// final case class ChangeWeapon(unk1 : Int, sessionId : Long) extends Action
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ package services.avatar
|
|||
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream}
|
||||
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID, PlayerStateMessageUpstream}
|
||||
import net.psforever.packet.game.objectcreate.ConstructorData
|
||||
import net.psforever.types.{ExoSuitType, Vector3}
|
||||
import net.psforever.types.ExoSuitType
|
||||
|
||||
object AvatarResponse {
|
||||
trait Response
|
||||
|
|
@ -16,11 +16,9 @@ object AvatarResponse {
|
|||
final case class ChangeFireState_Start(weapon_guid : PlanetSideGUID) extends Response
|
||||
final case class ChangeFireState_Stop(weapon_guid : PlanetSideGUID) extends Response
|
||||
final case class ConcealPlayer() extends Response
|
||||
final case class EquipmentInHand(target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Response
|
||||
final case class EquipmentOnGround(pos : Vector3, orient : Vector3, item_id : Int, item_guid : PlanetSideGUID, item_data : ConstructorData) extends Response
|
||||
final case class EquipmentInHand(pkt : ObjectCreateMessage) extends Response
|
||||
final case class DropItem(pkt : ObjectCreateMessage) 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
|
||||
|
|
@ -32,5 +30,4 @@ object AvatarResponse {
|
|||
// 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,15 @@
|
|||
package services.avatar
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Props}
|
||||
import services.avatar.support.CorpseRemovalActor
|
||||
import services.{GenericEventBus, Service}
|
||||
import net.psforever.packet.game.ObjectCreateMessage
|
||||
import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectCreateMessageParent, PlacementData}
|
||||
import services.avatar.support.{CorpseRemovalActor, DroppedItemRemover}
|
||||
import services.{GenericEventBus, RemoverActor, Service}
|
||||
|
||||
class AvatarService extends Actor {
|
||||
private val undertaker : ActorRef = context.actorOf(Props[CorpseRemovalActor], "corpse-removal-agent")
|
||||
undertaker ! "startup"
|
||||
private val janitor = context.actorOf(Props[DroppedItemRemover], "item-remover-agent")
|
||||
//undertaker ! "startup"
|
||||
|
||||
private [this] val log = org.log4s.getLogger
|
||||
|
||||
|
|
@ -62,13 +65,26 @@ class AvatarService extends Actor {
|
|||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ConcealPlayer())
|
||||
)
|
||||
case AvatarAction.EquipmentInHand(player_guid, target_guid, slot, obj) =>
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EquipmentInHand(target_guid, slot, obj))
|
||||
case AvatarAction.DropItem(player_guid, item, zone) =>
|
||||
val definition = item.Definition
|
||||
val objectData = DroppedItemData(
|
||||
PlacementData(item.Position, item.Orientation),
|
||||
definition.Packet.ConstructorData(item).get
|
||||
)
|
||||
case AvatarAction.EquipmentOnGround(player_guid, pos, orient, item_id, item_guid, item_data) =>
|
||||
janitor forward RemoverActor.AddTask(item, zone)
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EquipmentOnGround(pos, orient, item_id, item_guid, item_data))
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid,
|
||||
AvatarResponse.DropItem(ObjectCreateMessage(definition.ObjectId, item.GUID, objectData))
|
||||
)
|
||||
)
|
||||
case AvatarAction.EquipmentInHand(player_guid, target_guid, slot, item) =>
|
||||
val definition = item.Definition
|
||||
val containerData = ObjectCreateMessageParent(target_guid, slot)
|
||||
val objectData = definition.Packet.ConstructorData(item).get
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid,
|
||||
AvatarResponse.EquipmentInHand(ObjectCreateMessage(definition.ObjectId, item.GUID, containerData, objectData))
|
||||
)
|
||||
)
|
||||
case AvatarAction.LoadPlayer(player_guid, pdata) =>
|
||||
AvatarEvents.publish(
|
||||
|
|
@ -90,11 +106,24 @@ class AvatarService extends Actor {
|
|||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarResponse.PlayerState(msg, spectator, weapon))
|
||||
)
|
||||
case AvatarAction.PickupItem(player_guid, zone, target, slot, item, unk) =>
|
||||
janitor forward RemoverActor.ClearSpecific(List(item), zone)
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, {
|
||||
val itemGUID = item.GUID
|
||||
if(target.VisibleSlots.contains(slot)) {
|
||||
val definition = item.Definition
|
||||
val containerData = ObjectCreateMessageParent(target.GUID, slot)
|
||||
val objectData = definition.Packet.ConstructorData(item).get
|
||||
AvatarResponse.EquipmentInHand(ObjectCreateMessage(definition.ObjectId, itemGUID, containerData, objectData))
|
||||
}
|
||||
else {
|
||||
AvatarResponse.ObjectDelete(itemGUID, unk)
|
||||
}
|
||||
})
|
||||
)
|
||||
case AvatarAction.Release(player, zone, time) =>
|
||||
undertaker ! (time match {
|
||||
case Some(t) => CorpseRemovalActor.AddCorpse(player, zone, t)
|
||||
case None => CorpseRemovalActor.AddCorpse(player, zone)
|
||||
})
|
||||
undertaker forward RemoverActor.AddTask(player, zone, time)
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player.GUID, AvatarResponse.Release(player))
|
||||
)
|
||||
|
|
@ -115,52 +144,13 @@ class AvatarService extends Actor {
|
|||
}
|
||||
|
||||
//message to Undertaker
|
||||
case AvatarServiceMessage.RemoveSpecificCorpse(corpses) =>
|
||||
undertaker ! AvatarServiceMessage.RemoveSpecificCorpse( corpses.filter(corpse => {corpse.HasGUID && corpse.isBackpack}) )
|
||||
case AvatarServiceMessage.Corpse(msg) =>
|
||||
undertaker forward msg
|
||||
|
||||
case AvatarServiceMessage.Ground(msg) =>
|
||||
janitor forward msg
|
||||
|
||||
/*
|
||||
case AvatarService.PlayerStateMessage(msg) =>
|
||||
// log.info(s"NEW: ${m}")
|
||||
val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(msg.avatar_guid)
|
||||
if (playerOpt.isDefined) {
|
||||
val player: PlayerAvatar = playerOpt.get
|
||||
AvatarEvents.publish(AvatarMessage("/Avatar/" + player.continent, msg.avatar_guid,
|
||||
AvatarServiceReply.PlayerStateMessage(msg.pos, msg.vel, msg.facingYaw, msg.facingPitch, msg.facingYawUpper, msg.is_crouching, msg.is_jumping, msg.jump_thrust, msg.is_cloaked)
|
||||
))
|
||||
|
||||
}
|
||||
case AvatarService.LoadMap(msg) =>
|
||||
val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(msg.guid)
|
||||
if (playerOpt.isDefined) {
|
||||
val player: PlayerAvatar = playerOpt.get
|
||||
AvatarEvents.publish(AvatarMessage("/Avatar/" + player.continent, PlanetSideGUID(msg.guid),
|
||||
AvatarServiceReply.LoadMap()
|
||||
))
|
||||
}
|
||||
case AvatarService.unLoadMap(msg) =>
|
||||
val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(msg.guid)
|
||||
if (playerOpt.isDefined) {
|
||||
val player: PlayerAvatar = playerOpt.get
|
||||
AvatarEvents.publish(AvatarMessage("/Avatar/" + player.continent, PlanetSideGUID(msg.guid),
|
||||
AvatarServiceReply.unLoadMap()
|
||||
))
|
||||
}
|
||||
case AvatarService.ObjectHeld(msg) =>
|
||||
val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(msg.guid)
|
||||
if (playerOpt.isDefined) {
|
||||
val player: PlayerAvatar = playerOpt.get
|
||||
AvatarEvents.publish(AvatarMessage("/Avatar/" + player.continent, PlanetSideGUID(msg.guid),
|
||||
AvatarServiceReply.ObjectHeld()
|
||||
))
|
||||
}
|
||||
case AvatarService.PlanetsideAttribute(guid, attribute_type, attribute_value) =>
|
||||
val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(guid)
|
||||
if (playerOpt.isDefined) {
|
||||
val player: PlayerAvatar = playerOpt.get
|
||||
AvatarEvents.publish(AvatarMessage("/Avatar/" + player.continent, guid,
|
||||
AvatarServiceReply.PlanetSideAttribute(attribute_type, attribute_value)
|
||||
))
|
||||
}
|
||||
case AvatarService.PlayerStateShift(killer, guid) =>
|
||||
val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(guid)
|
||||
if (playerOpt.isDefined) {
|
||||
|
|
@ -185,16 +175,8 @@ class AvatarService extends Actor {
|
|||
AvatarServiceReply.DestroyDisplay(source_guid)
|
||||
))
|
||||
}
|
||||
case AvatarService.ChangeWeapon(unk1, sessionId) =>
|
||||
val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(sessionId)
|
||||
if (playerOpt.isDefined) {
|
||||
val player: PlayerAvatar = playerOpt.get
|
||||
AvatarEvents.publish(AvatarMessage("/Avatar/" + player.continent, PlanetSideGUID(player.guid),
|
||||
AvatarServiceReply.ChangeWeapon(unk1)
|
||||
))
|
||||
}
|
||||
*/
|
||||
case msg =>
|
||||
log.info(s"Unhandled message $msg from $sender")
|
||||
log.warn(s"Unhandled message $msg from $sender")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.avatar
|
||||
|
||||
import net.psforever.objects.Player
|
||||
|
||||
final case class AvatarServiceMessage(forChannel : String, actionMessage : AvatarAction.Action)
|
||||
|
||||
object AvatarServiceMessage {
|
||||
final case class RemoveSpecificCorpse(corpse : List[Player])
|
||||
final case class Corpse(msg : Any)
|
||||
final case class Ground(msg : Any)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,245 +1,33 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.avatar.support
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Cancellable}
|
||||
import net.psforever.objects.guid.TaskResolver
|
||||
import net.psforever.objects.{DefaultCancellable, Player}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.types.Vector3
|
||||
import services.{Service, ServiceManager}
|
||||
import services.ServiceManager.Lookup
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
|
||||
import net.psforever.objects.Player
|
||||
import services.{RemoverActor, Service}
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class CorpseRemovalActor extends Actor {
|
||||
private var burial : Cancellable = DefaultCancellable.obj
|
||||
private var corpses : List[CorpseRemovalActor.Entry] = List()
|
||||
class CorpseRemovalActor extends RemoverActor {
|
||||
final val FirstStandardDuration : FiniteDuration = 3 minutes
|
||||
|
||||
private var decomposition : Cancellable = DefaultCancellable.obj
|
||||
private var buriedCorpses : List[CorpseRemovalActor.Entry] = List()
|
||||
final val SecondStandardDuration : FiniteDuration = 500 milliseconds
|
||||
|
||||
private var taskResolver : ActorRef = Actor.noSender
|
||||
|
||||
private[this] val log = org.log4s.getLogger
|
||||
|
||||
override def postStop() = {
|
||||
//Cart Master: See you on Thursday.
|
||||
super.postStop()
|
||||
burial.cancel
|
||||
decomposition.cancel
|
||||
|
||||
corpses.foreach(corpse => {
|
||||
BurialTask(corpse)
|
||||
LastRitesTask(corpse)
|
||||
})
|
||||
buriedCorpses.foreach { LastRitesTask }
|
||||
def InclusionTest(entry : RemoverActor.Entry) : Boolean = {
|
||||
entry.obj.isInstanceOf[Player] && entry.obj.asInstanceOf[Player].isBackpack
|
||||
}
|
||||
|
||||
def receive : Receive = {
|
||||
case "startup" =>
|
||||
ServiceManager.serviceManager ! Lookup("taskResolver") //ask for a resolver to deal with the GUID system
|
||||
def InitialJob(entry : RemoverActor.Entry) : Unit = { }
|
||||
|
||||
case ServiceManager.LookupResult("taskResolver", endpoint) =>
|
||||
//Cart Master: Bring out your dead!
|
||||
taskResolver = endpoint
|
||||
context.become(Processing)
|
||||
|
||||
case _ => ;
|
||||
def FirstJob(entry : RemoverActor.Entry) : Unit = {
|
||||
import net.psforever.objects.zones.Zone
|
||||
entry.zone.Population ! Zone.Corpse.Remove(entry.obj.asInstanceOf[Player])
|
||||
context.parent ! AvatarServiceMessage(entry.zone.Id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, entry.obj.GUID))
|
||||
}
|
||||
|
||||
def Processing : Receive = {
|
||||
case CorpseRemovalActor.AddCorpse(corpse, zone, time) =>
|
||||
import CorpseRemovalActor.SimilarCorpses
|
||||
if(corpse.isBackpack && !buriedCorpses.exists(entry => SimilarCorpses(entry.corpse, corpse) )) {
|
||||
if(corpses.isEmpty) {
|
||||
//we were the only entry so the event must be started from scratch
|
||||
corpses = List(CorpseRemovalActor.Entry(corpse, zone, time))
|
||||
RetimeFirstTask()
|
||||
}
|
||||
else {
|
||||
//unknown number of entries; append, sort, then re-time tasking
|
||||
val oldHead = corpses.head
|
||||
if(!corpses.exists(entry => SimilarCorpses(entry.corpse, corpse))) {
|
||||
corpses = (corpses :+ CorpseRemovalActor.Entry(corpse, zone, time)).sortBy(_.timeAlive)
|
||||
if(oldHead != corpses.head) {
|
||||
RetimeFirstTask()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
//Cart Master: 'Ere. He says he's not dead!
|
||||
log.warn(s"$corpse does not qualify as a corpse; ignored queueing request")
|
||||
}
|
||||
def ClearanceTest(entry : RemoverActor.Entry) : Boolean = !entry.zone.Corpses.contains(entry.obj)
|
||||
|
||||
case AvatarServiceMessage.RemoveSpecificCorpse(targets) =>
|
||||
if(targets.nonEmpty) {
|
||||
//Cart Master: No, I've got to go to the Robinsons'. They've lost nine today.
|
||||
burial.cancel
|
||||
if(targets.size == 1) {
|
||||
log.debug(s"a target corpse submitted for early cleanup: ${targets.head}")
|
||||
//simple selection
|
||||
CorpseRemovalActor.recursiveFindCorpse(corpses.iterator, targets.head) match {
|
||||
case None => ;
|
||||
case Some(index) =>
|
||||
decomposition.cancel
|
||||
BurialTask(corpses(index))
|
||||
buriedCorpses = buriedCorpses :+ corpses(index)
|
||||
corpses = (corpses.take(index) ++ corpses.drop(index + 1)).sortBy(_.timeAlive)
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
decomposition = context.system.scheduler.scheduleOnce(500 milliseconds, self, CorpseRemovalActor.TryDelete())
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.debug(s"multiple target corpses submitted for early cleanup: $targets")
|
||||
import CorpseRemovalActor.SimilarCorpses
|
||||
decomposition.cancel
|
||||
//cumbersome partition
|
||||
//a - find targets from corpses
|
||||
val locatedTargets = for {
|
||||
a <- targets
|
||||
b <- corpses
|
||||
if b.corpse.HasGUID && a.HasGUID && SimilarCorpses(b.corpse, a)
|
||||
} yield b
|
||||
if(locatedTargets.nonEmpty) {
|
||||
decomposition.cancel
|
||||
locatedTargets.foreach { BurialTask }
|
||||
buriedCorpses = locatedTargets ++ buriedCorpses
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
decomposition = context.system.scheduler.scheduleOnce(500 milliseconds, self, CorpseRemovalActor.TryDelete())
|
||||
//b - corpses, after the found targets are removed (cull any non-GUID entries while at it)
|
||||
corpses = (for {
|
||||
a <- locatedTargets
|
||||
b <- corpses
|
||||
if b.corpse.HasGUID && a.corpse.HasGUID && !SimilarCorpses(b.corpse, a.corpse)
|
||||
} yield b).sortBy(_.timeAlive)
|
||||
}
|
||||
}
|
||||
RetimeFirstTask()
|
||||
}
|
||||
|
||||
case CorpseRemovalActor.StartDelete() =>
|
||||
burial.cancel
|
||||
decomposition.cancel
|
||||
val now : Long = System.nanoTime
|
||||
val (buried, rotting) = corpses.partition(entry => { now - entry.time >= entry.timeAlive })
|
||||
corpses = rotting
|
||||
buriedCorpses = buriedCorpses ++ buried
|
||||
buried.foreach { BurialTask }
|
||||
RetimeFirstTask()
|
||||
if(buriedCorpses.nonEmpty) {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
burial = context.system.scheduler.scheduleOnce(500 milliseconds, self, CorpseRemovalActor.TryDelete())
|
||||
}
|
||||
|
||||
case CorpseRemovalActor.TryDelete() =>
|
||||
decomposition.cancel
|
||||
val (decomposed, rotting) = buriedCorpses.partition(entry => {
|
||||
!entry.zone.Corpses.contains(entry.corpse)
|
||||
})
|
||||
buriedCorpses = rotting
|
||||
decomposed.foreach { LastRitesTask }
|
||||
if(rotting.nonEmpty) {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
decomposition = context.system.scheduler.scheduleOnce(500 milliseconds, self, CorpseRemovalActor.TryDelete())
|
||||
}
|
||||
|
||||
case CorpseRemovalActor.FailureToWork(target, zone, ex) =>
|
||||
//Cart Master: Oh, I can't take him like that. It's against regulations.
|
||||
log.error(s"corpse $target from $zone not properly unregistered - $ex")
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
def RetimeFirstTask(now : Long = System.nanoTime) : Unit = {
|
||||
//Cart Master: Thursday.
|
||||
burial.cancel
|
||||
if(corpses.nonEmpty) {
|
||||
val short_timeout : FiniteDuration = math.max(1, corpses.head.timeAlive - (now - corpses.head.time)) nanoseconds
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
burial = context.system.scheduler.scheduleOnce(short_timeout, self, CorpseRemovalActor.StartDelete())
|
||||
}
|
||||
}
|
||||
|
||||
def BurialTask(entry : CorpseRemovalActor.Entry) : Unit = {
|
||||
val target = entry.corpse
|
||||
entry.zone.Population ! Zone.Corpse.Remove(target)
|
||||
context.parent ! AvatarServiceMessage(entry.zone.Id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, target.GUID))
|
||||
}
|
||||
|
||||
def LastRitesTask(entry : CorpseRemovalActor.Entry) : Unit = {
|
||||
//Cart master: Nine pence.
|
||||
val target = entry.corpse
|
||||
target.Position = Vector3.Zero //somewhere it will not disturb anything
|
||||
taskResolver ! LastRitesTask(target, entry.zone)
|
||||
}
|
||||
|
||||
def LastRitesTask(corpse : Player, zone : Zone) : TaskResolver.GiveTask = {
|
||||
import net.psforever.objects.guid.{GUIDTask, Task}
|
||||
TaskResolver.GiveTask (
|
||||
new Task() {
|
||||
private val localCorpse = corpse
|
||||
private val localZone = zone
|
||||
private val localAnnounce = self
|
||||
|
||||
override def isComplete : Task.Resolution.Value = if(!localCorpse.HasGUID) {
|
||||
Task.Resolution.Success
|
||||
}
|
||||
else {
|
||||
Task.Resolution.Incomplete
|
||||
}
|
||||
|
||||
def Execute(resolver : ActorRef) : Unit = {
|
||||
resolver ! scala.util.Success(this)
|
||||
}
|
||||
|
||||
override def onFailure(ex : Throwable): Unit = {
|
||||
localAnnounce ! CorpseRemovalActor.FailureToWork(localCorpse, localZone, ex)
|
||||
}
|
||||
}, List(GUIDTask.UnregisterPlayer(corpse)(zone.GUID))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object CorpseRemovalActor {
|
||||
final val time : Long = 180000000000L //3 min (180s)
|
||||
|
||||
final case class AddCorpse(corpse : Player, zone : Zone, time : Long = CorpseRemovalActor.time)
|
||||
|
||||
final case class Entry(corpse : Player, zone : Zone, timeAlive : Long = CorpseRemovalActor.time, time : Long = System.nanoTime())
|
||||
|
||||
private final case class FailureToWork(corpse : Player, zone : Zone, ex : Throwable)
|
||||
|
||||
private final case class StartDelete()
|
||||
|
||||
private final case class TryDelete()
|
||||
|
||||
private def SimilarCorpses(obj1 : Player, obj2 : Player) : Boolean = {
|
||||
obj1 == obj2 && obj1.Continent.equals(obj2.Continent) && obj1.GUID == obj2.GUID
|
||||
}
|
||||
|
||||
/**
|
||||
* A recursive function that finds and removes a specific player from a list of players.
|
||||
* @param iter an `Iterator` of `CorpseRemovalActor.Entry` objects
|
||||
* @param player the target `Player`
|
||||
* @param index the index of the discovered `Player` object
|
||||
* @return the index of the `Player` object in the list to be removed;
|
||||
* `None`, otherwise
|
||||
*/
|
||||
@tailrec final def recursiveFindCorpse(iter : Iterator[CorpseRemovalActor.Entry], player : Player, index : Int = 0) : Option[Int] = {
|
||||
if(!iter.hasNext) {
|
||||
None
|
||||
}
|
||||
else {
|
||||
val corpse = iter.next.corpse
|
||||
if(corpse.HasGUID && player.HasGUID && SimilarCorpses(corpse, player)) {
|
||||
Some(index)
|
||||
}
|
||||
else {
|
||||
recursiveFindCorpse(iter, player, index + 1)
|
||||
}
|
||||
}
|
||||
def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = {
|
||||
GUIDTask.UnregisterPlayer(entry.obj.asInstanceOf[Player])(entry.zone.GUID)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.avatar.support
|
||||
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import services.{RemoverActor, Service}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class DroppedItemRemover extends RemoverActor {
|
||||
final val FirstStandardDuration : FiniteDuration = 3 minutes
|
||||
|
||||
final val SecondStandardDuration : FiniteDuration = 500 milliseconds
|
||||
|
||||
def InclusionTest(entry : RemoverActor.Entry) : Boolean = {
|
||||
entry.obj.isInstanceOf[Equipment]
|
||||
}
|
||||
|
||||
def InitialJob(entry : RemoverActor.Entry) : Unit = { }
|
||||
|
||||
def FirstJob(entry : RemoverActor.Entry) : Unit = {
|
||||
import net.psforever.objects.zones.Zone
|
||||
entry.zone.Ground ! Zone.Ground.PickupItem(entry.obj.GUID)
|
||||
context.parent ! AvatarServiceMessage(entry.zone.Id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, entry.obj.GUID))
|
||||
}
|
||||
|
||||
def ClearanceTest(entry : RemoverActor.Entry) : Boolean = !entry.zone.EquipmentOnGround.contains(entry.obj)
|
||||
|
||||
def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = {
|
||||
GUIDTask.UnregisterEquipment(entry.obj.asInstanceOf[Equipment])(entry.zone.GUID)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,15 +4,14 @@ package services.vehicle
|
|||
import akka.actor.{Actor, ActorRef, Props}
|
||||
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
|
||||
import net.psforever.objects.zones.Zone
|
||||
import services.vehicle.support.{DeconstructionActor, DelayedDeconstructionActor}
|
||||
import services.vehicle.support.VehicleRemover
|
||||
import net.psforever.types.DriveState
|
||||
import services.{GenericEventBus, RemoverActor, Service}
|
||||
|
||||
import services.{GenericEventBus, Service}
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class VehicleService extends Actor {
|
||||
private val vehicleDecon : ActorRef = context.actorOf(Props[DeconstructionActor], "vehicle-decon-agent")
|
||||
private val vehicleDelayedDecon : ActorRef = context.actorOf(Props[DelayedDeconstructionActor], "vehicle-delayed-decon-agent")
|
||||
vehicleDecon ! DeconstructionActor.RequestTaskResolver
|
||||
private val vehicleDecon : ActorRef = context.actorOf(Props[VehicleRemover], "vehicle-decon-agent")
|
||||
private [this] val log = org.log4s.getLogger
|
||||
|
||||
override def preStart = {
|
||||
|
|
@ -87,6 +86,11 @@ class VehicleService extends Actor {
|
|||
VehicleEvents.publish(
|
||||
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.StowEquipment(vehicle_guid, slot, definition.ObjectId, item.GUID, definition.Packet.DetailedConstructorData(item).get))
|
||||
)
|
||||
case VehicleAction.UnloadVehicle(player_guid, continent, vehicle) =>
|
||||
vehicleDecon ! RemoverActor.ClearSpecific(List(vehicle), continent) //precaution
|
||||
VehicleEvents.publish(
|
||||
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.UnloadVehicle(vehicle.GUID))
|
||||
)
|
||||
case VehicleAction.UnstowEquipment(player_guid, item_guid) =>
|
||||
VehicleEvents.publish(
|
||||
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.UnstowEquipment(item_guid))
|
||||
|
|
@ -102,23 +106,9 @@ class VehicleService extends Actor {
|
|||
case _ => ;
|
||||
}
|
||||
|
||||
//message to DeconstructionActor
|
||||
case VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent) =>
|
||||
vehicleDecon ! DeconstructionActor.RequestDeleteVehicle(vehicle, continent)
|
||||
|
||||
//message to DelayedDeconstructionActor
|
||||
case VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, zone, timeAlive) =>
|
||||
vehicleDelayedDecon ! DelayedDeconstructionActor.ScheduleDeconstruction(vehicle, zone, timeAlive)
|
||||
|
||||
//message to DelayedDeconstructionActor
|
||||
case VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) =>
|
||||
vehicleDelayedDecon ! DelayedDeconstructionActor.UnscheduleDeconstruction(vehicle_guid)
|
||||
|
||||
//response from DeconstructionActor
|
||||
case DeconstructionActor.DeleteVehicle(vehicle_guid, zone_id) =>
|
||||
VehicleEvents.publish(
|
||||
VehicleServiceResponse(s"/$zone_id/Vehicle", Service.defaultPlayerGUID, VehicleResponse.UnloadVehicle(vehicle_guid))
|
||||
)
|
||||
//message to VehicleRemover
|
||||
case VehicleServiceMessage.Decon(msg) =>
|
||||
vehicleDecon forward msg
|
||||
|
||||
//from VehicleSpawnControl
|
||||
case VehicleSpawnPad.ConcealPlayer(player_guid, zone_id) =>
|
||||
|
|
@ -159,13 +149,12 @@ class VehicleService extends Actor {
|
|||
VehicleEvents.publish(
|
||||
VehicleServiceResponse(s"/${zone.Id}/Vehicle", Service.defaultPlayerGUID, VehicleResponse.LoadVehicle(vehicle, vtype, vguid, vdata))
|
||||
)
|
||||
vehicleDelayedDecon ! DelayedDeconstructionActor.UnscheduleDeconstruction(vguid)
|
||||
vehicleDelayedDecon ! DelayedDeconstructionActor.ScheduleDeconstruction(vehicle, zone, 600L) //10min
|
||||
//avoid unattended vehicle spawning blocking the pad; user should mount (and does so normally) to reset decon timer
|
||||
vehicleDecon forward RemoverActor.AddTask(vehicle, zone, Some(30 seconds))
|
||||
|
||||
//from VehicleSpawnControl
|
||||
case VehicleSpawnPad.DisposeVehicle(vehicle, zone) =>
|
||||
vehicleDelayedDecon ! DelayedDeconstructionActor.UnscheduleDeconstruction(vehicle.GUID)
|
||||
vehicleDecon ! DeconstructionActor.RequestDeleteVehicle(vehicle, zone)
|
||||
vehicleDecon forward RemoverActor.HurrySpecific(List(vehicle), zone)
|
||||
|
||||
//correspondence from WorldSessionActor
|
||||
case VehicleServiceMessage.AMSDeploymentChange(zone) =>
|
||||
|
|
|
|||
|
|
@ -3,16 +3,14 @@ package services.vehicle
|
|||
|
||||
import net.psforever.objects.Vehicle
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
|
||||
final case class VehicleServiceMessage(forChannel : String, actionMessage : VehicleAction.Action)
|
||||
|
||||
object VehicleServiceMessage {
|
||||
final case class DelayedVehicleDeconstruction(vehicle : Vehicle, continent : Zone, timeAlive : Long)
|
||||
final case class GiveActorControl(vehicle : Vehicle, actorName : String)
|
||||
final case class RevokeActorControl(vehicle : Vehicle)
|
||||
final case class RequestDeleteVehicle(vehicle : Vehicle, continent : Zone)
|
||||
final case class UnscheduleDeconstruction(vehicle_guid : PlanetSideGUID)
|
||||
|
||||
final case class Decon(msg : Any)
|
||||
|
||||
final case class AMSDeploymentChange(zone : Zone)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,276 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.vehicle.support
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Cancellable}
|
||||
import net.psforever.objects.{DefaultCancellable, GlobalDefinitions, Vehicle}
|
||||
import net.psforever.objects.guid.TaskResolver
|
||||
import net.psforever.objects.vehicles.Seat
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import net.psforever.types.Vector3
|
||||
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.<br>
|
||||
* <br>
|
||||
* This `Actor` is intended to sit on top of the event system that handles broadcast messaging.
|
||||
*/
|
||||
class DeconstructionActor extends Actor {
|
||||
/** The periodic `Executor` that scraps the next vehicle on the list */
|
||||
private var scrappingProcess : Cancellable = DefaultCancellable.obj
|
||||
/** A `List` of currently doomed vehicles */
|
||||
private var vehicles : List[DeconstructionActor.VehicleEntry] = Nil
|
||||
/** The periodic `Executor` that cleans up the next vehicle on the list */
|
||||
private var heapEmptyProcess : Cancellable = DefaultCancellable.obj
|
||||
/** A `List` of vehicles that have been removed from the game world and are awaiting deconstruction. */
|
||||
private var vehicleScrapHeap : 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
|
||||
|
||||
override def postStop() : Unit = {
|
||||
super.postStop()
|
||||
scrappingProcess.cancel
|
||||
heapEmptyProcess.cancel
|
||||
|
||||
vehicles.foreach(entry => {
|
||||
RetirementTask(entry)
|
||||
DestructionTask(entry)
|
||||
})
|
||||
vehicleScrapHeap.foreach { DestructionTask }
|
||||
}
|
||||
|
||||
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) =>
|
||||
if(!vehicles.exists(_.vehicle == vehicle) && !vehicleScrapHeap.exists(_.vehicle == vehicle)) {
|
||||
vehicles = vehicles :+ DeconstructionActor.VehicleEntry(vehicle, zone, time)
|
||||
vehicle.Actor ! Vehicle.PrepareForDeletion
|
||||
//kick everyone out; this is a no-blocking manual form of MountableBehavior ! Mountable.TryDismount
|
||||
vehicle.Definition.MountPoints.values.foreach(seat_num => {
|
||||
val zone_id : String = zone.Id
|
||||
val seat : Seat = vehicle.Seat(seat_num).get
|
||||
seat.Occupant match {
|
||||
case Some(tplayer) =>
|
||||
seat.Occupant = None
|
||||
tplayer.VehicleSeated = None
|
||||
if(tplayer.HasGUID) {
|
||||
context.parent ! VehicleServiceMessage(zone_id, VehicleAction.KickPassenger(tplayer.GUID, 4, false, vehicle.GUID))
|
||||
}
|
||||
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.StartDeleteVehicle())
|
||||
}
|
||||
}
|
||||
|
||||
case DeconstructionActor.StartDeleteVehicle() =>
|
||||
scrappingProcess.cancel
|
||||
heapEmptyProcess.cancel
|
||||
val now : Long = System.nanoTime
|
||||
val (vehiclesToScrap, vehiclesRemain) = PartitionEntries(vehicles, now)
|
||||
vehicles = vehiclesRemain
|
||||
vehicleScrapHeap = vehicleScrapHeap ++ vehiclesToScrap //may include existing entries
|
||||
vehiclesToScrap.foreach(entry => {
|
||||
val vehicle = entry.vehicle
|
||||
val zone = entry.zone
|
||||
RetirementTask(entry)
|
||||
if(vehicle.Definition == GlobalDefinitions.ams) {
|
||||
import net.psforever.types.DriveState
|
||||
vehicle.DeploymentState = DriveState.Mobile //internally undeployed //TODO this should be temporary?
|
||||
context.parent ! VehicleServiceMessage.AMSDeploymentChange(zone)
|
||||
}
|
||||
taskResolver ! DeconstructionTask(vehicle, zone)
|
||||
})
|
||||
|
||||
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.StartDeleteVehicle())
|
||||
}
|
||||
if(vehicleScrapHeap.nonEmpty) {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
heapEmptyProcess = context.system.scheduler.scheduleOnce(500 milliseconds, self, DeconstructionActor.TryDeleteVehicle())
|
||||
}
|
||||
|
||||
case DeconstructionActor.TryDeleteVehicle() =>
|
||||
heapEmptyProcess.cancel
|
||||
val (vehiclesToScrap, vehiclesRemain) = vehicleScrapHeap.partition(entry => !entry.zone.Vehicles.contains(entry.vehicle))
|
||||
vehicleScrapHeap = vehiclesRemain
|
||||
vehiclesToScrap.foreach { DestructionTask }
|
||||
if(vehiclesRemain.nonEmpty) {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
heapEmptyProcess = context.system.scheduler.scheduleOnce(500 milliseconds, self, DeconstructionActor.TryDeleteVehicle())
|
||||
}
|
||||
|
||||
case DeconstructionActor.FailureToDeleteVehicle(localVehicle, localZone, ex) =>
|
||||
org.log4s.getLogger.error(s"vehicle deconstruction: $localVehicle failed to be properly cleaned up from zone $localZone - $ex")
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
def RetirementTask(entry : DeconstructionActor.VehicleEntry) : Unit = {
|
||||
val vehicle = entry.vehicle
|
||||
val zone = entry.zone
|
||||
vehicle.Position = Vector3.Zero //somewhere it will not disturb anything
|
||||
zone.Transport ! Zone.Vehicle.Despawn(vehicle)
|
||||
context.parent ! DeconstructionActor.DeleteVehicle(vehicle.GUID, zone.Id) //call up to the main event system
|
||||
}
|
||||
|
||||
def DestructionTask(entry : DeconstructionActor.VehicleEntry) : Unit = {
|
||||
val vehicle = entry.vehicle
|
||||
val zone = entry.zone
|
||||
taskResolver ! DeconstructionTask(vehicle, zone)
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a middleman `Task` intended to return error messages to the `DeconstructionActor`.
|
||||
* @param vehicle the `Vehicle` object
|
||||
* @param zone the `Zone` in which the vehicle resides
|
||||
* @return a `TaskResolver.GiveTask` message
|
||||
*/
|
||||
def DeconstructionTask(vehicle : Vehicle, zone : Zone) : TaskResolver.GiveTask = {
|
||||
import net.psforever.objects.guid.{GUIDTask, Task}
|
||||
TaskResolver.GiveTask (
|
||||
new Task() {
|
||||
private val localVehicle = vehicle
|
||||
private val localZone = zone
|
||||
private val localAnnounce = self
|
||||
|
||||
override def isComplete : Task.Resolution.Value = Task.Resolution.Success
|
||||
|
||||
def Execute(resolver : ActorRef) : Unit = {
|
||||
resolver ! scala.util.Success(this)
|
||||
}
|
||||
|
||||
override def onFailure(ex : Throwable): Unit = {
|
||||
localAnnounce ! DeconstructionActor.FailureToDeleteVehicle(localVehicle, localZone, ex)
|
||||
}
|
||||
}, List(GUIDTask.UnregisterVehicle(vehicle)(zone.GUID))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
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.
|
||||
* Remove all deconstructing vehicles from the game world.
|
||||
*/
|
||||
private final case class StartDeleteVehicle()
|
||||
/**
|
||||
* Internal message used to signal a test of the queued vehicle information.
|
||||
* Remove all deconstructing vehicles from the zone's globally unique identifier system.
|
||||
*/
|
||||
private final case class TryDeleteVehicle()
|
||||
|
||||
/**
|
||||
* Error-passing message carrying information out of the final deconstruction GUID unregistering task.
|
||||
* @param vehicle the `Vehicle` object
|
||||
* @param zone the `Zone` in which the vehicle may or may not reside
|
||||
* @param ex information regarding what happened
|
||||
*/
|
||||
private final case class FailureToDeleteVehicle(vehicle : Vehicle, zone : Zone, ex : Throwable)
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.vehicle.support
|
||||
|
||||
import akka.actor.{Actor, Cancellable}
|
||||
import net.psforever.objects.{DefaultCancellable, Vehicle}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import services.vehicle.VehicleServiceMessage
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* Maintain and curate a list of timed `vehicle` object deconstruction tasks.<br>
|
||||
* <br>
|
||||
* These tasks are queued or dismissed by player activity but they are executed independent of player activity.
|
||||
* A common disconnected cause of deconstruction is neglect for an extended period of time.
|
||||
* At that point, the original owner of the vehicle no longer matters.
|
||||
* Deconstruction neglect, however, is averted by having someone become seated.
|
||||
* A realized deconstruction is entirely based on a fixed interval after an unresolved request has been received.
|
||||
* The actual process of deconstructing the vehicle and cleaning up its resources is performed by an external agent.<br>
|
||||
* <br>
|
||||
* This `Actor` is intended to sit on top of the event system that handles broadcast messaging.
|
||||
*/
|
||||
class DelayedDeconstructionActor extends Actor {
|
||||
/** The periodic `Executor` that scraps the next vehicle on the list */
|
||||
private var monitor : Cancellable = DefaultCancellable.obj
|
||||
/** A `List` of currently doomed vehicles */
|
||||
private var vehicles : List[DelayedDeconstructionActor.VehicleEntry] = Nil
|
||||
private[this] val log = org.log4s.getLogger
|
||||
private[this] def trace(msg : String) : Unit = log.trace(msg)
|
||||
|
||||
|
||||
def receive : Receive = {
|
||||
case DelayedDeconstructionActor.ScheduleDeconstruction(vehicle, zone, timeAlive) =>
|
||||
trace(s"delayed deconstruction order for $vehicle in $timeAlive")
|
||||
val oldHead = vehicles.headOption
|
||||
val now : Long = System.nanoTime
|
||||
vehicles = (vehicles :+ DelayedDeconstructionActor.VehicleEntry(vehicle, zone, timeAlive * 1000000000L))
|
||||
.sortBy(entry => entry.survivalTime - (now - entry.logTime))
|
||||
if(vehicles.size == 1 || oldHead != vehicles.headOption) { //we were the only entry so the event must be started from scratch
|
||||
RetimePeriodicTest()
|
||||
}
|
||||
|
||||
case DelayedDeconstructionActor.UnscheduleDeconstruction(vehicle_guid) =>
|
||||
//all tasks for this vehicle are cleared from the queue
|
||||
//clear any task that is no longer valid by determination of unregistered GUID
|
||||
val before = vehicles.length
|
||||
val now : Long = System.nanoTime
|
||||
vehicles = vehicles.filter(entry => { entry.vehicle.HasGUID && entry.vehicle.GUID != vehicle_guid })
|
||||
.sortBy(entry => entry.survivalTime - (now - entry.logTime))
|
||||
trace(s"attempting to clear deconstruction order for vehicle $vehicle_guid, found ${before - vehicles.length}")
|
||||
RetimePeriodicTest()
|
||||
|
||||
case DelayedDeconstructionActor.PeriodicTaskCulling =>
|
||||
//filter the list of deconstruction tasks for any that are need to be triggered
|
||||
monitor.cancel
|
||||
val now : Long = System.nanoTime
|
||||
val (vehiclesToDecon, vehiclesRemain) = vehicles.partition(entry => { now - entry.logTime >= entry.survivalTime })
|
||||
vehicles = vehiclesRemain.sortBy(_.survivalTime)
|
||||
trace(s"vehicle culling - ${vehiclesToDecon.length} deconstruction tasks found; ${vehiclesRemain.length} tasks remain")
|
||||
vehiclesToDecon.foreach(entry => { context.parent ! VehicleServiceMessage.RequestDeleteVehicle(entry.vehicle, entry.zone) })
|
||||
RetimePeriodicTest()
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
def RetimePeriodicTest() : Unit = {
|
||||
monitor.cancel
|
||||
vehicles.headOption match {
|
||||
case None => ;
|
||||
case Some(entry) =>
|
||||
val retime = math.max(1, entry.survivalTime - (System.nanoTime - entry.logTime)) nanoseconds
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
monitor = context.system.scheduler.scheduleOnce(retime, self, DelayedDeconstructionActor.PeriodicTaskCulling)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object DelayedDeconstructionActor {
|
||||
/**
|
||||
* Timer for the repeating executor.
|
||||
*/
|
||||
private final val periodicTest : FiniteDuration = 5000000000L nanoseconds //5s
|
||||
|
||||
/**
|
||||
* Queue a future vehicle deconstruction action.
|
||||
* @param vehicle the `Vehicle` object
|
||||
* @param zone the `Zone` that the vehicle currently occupies
|
||||
* @param survivalTime how long until the vehicle will be deconstructed in seconds
|
||||
*/
|
||||
final case class ScheduleDeconstruction(vehicle : Vehicle, zone : Zone, survivalTime : Long)
|
||||
|
||||
/**
|
||||
* Dequeue a vehicle from being deconstructed.
|
||||
* @param vehicle_guid the vehicle
|
||||
*/
|
||||
final case class UnscheduleDeconstruction(vehicle_guid : PlanetSideGUID)
|
||||
|
||||
/**
|
||||
* A message the `Actor` sends to itself.
|
||||
* The trigger for the periodic deconstruction task.
|
||||
*/
|
||||
private final case class PeriodicTaskCulling()
|
||||
|
||||
/**
|
||||
* An entry that stores vehicle deconstruction tasks.
|
||||
* @param vehicle the `Vehicle` object
|
||||
* @param zone the `Zone` that the vehicle currently occupies
|
||||
* @param survivalTime how long until the vehicle will be deconstructed in nanoseconds
|
||||
* @param logTime when this deconstruction request was initially created in nanoseconds;
|
||||
* initialized by default to a "now"
|
||||
*/
|
||||
private final case class VehicleEntry(vehicle : Vehicle, zone : Zone, survivalTime : Long, logTime : Long = System.nanoTime())
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.vehicle.support
|
||||
|
||||
import net.psforever.objects.Vehicle
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import services.{RemoverActor, Service}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class VehicleRemover extends RemoverActor {
|
||||
final val FirstStandardDuration : FiniteDuration = 5 minutes
|
||||
|
||||
final val SecondStandardDuration : FiniteDuration = 5 seconds
|
||||
|
||||
def InclusionTest(entry : RemoverActor.Entry) : Boolean = {
|
||||
entry.obj.isInstanceOf[Vehicle]
|
||||
}
|
||||
|
||||
def InitialJob(entry : RemoverActor.Entry) : Unit = { }
|
||||
|
||||
def FirstJob(entry : RemoverActor.Entry) : Unit = {
|
||||
val vehicle = entry.obj.asInstanceOf[Vehicle]
|
||||
val vehicleGUID = vehicle.GUID
|
||||
val zoneId = entry.zone.Id
|
||||
vehicle.Actor ! Vehicle.PrepareForDeletion
|
||||
//kick out all passengers
|
||||
vehicle.Definition.MountPoints.values.foreach(mount => {
|
||||
val seat = vehicle.Seat(mount).get
|
||||
seat.Occupant match {
|
||||
case Some(tplayer) =>
|
||||
seat.Occupant = None
|
||||
tplayer.VehicleSeated = None
|
||||
if(tplayer.HasGUID) {
|
||||
context.parent ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(tplayer.GUID, 4, false, vehicleGUID))
|
||||
}
|
||||
case None => ;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override def SecondJob(entry : RemoverActor.Entry) : Unit = {
|
||||
val vehicle = entry.obj.asInstanceOf[Vehicle]
|
||||
val zone = entry.zone
|
||||
zone.Transport ! Zone.Vehicle.Despawn(vehicle)
|
||||
context.parent ! VehicleServiceMessage(zone.Id, VehicleAction.UnloadVehicle(Service.defaultPlayerGUID, zone, vehicle))
|
||||
super.SecondJob(entry)
|
||||
}
|
||||
|
||||
def ClearanceTest(entry : RemoverActor.Entry) : Boolean = entry.obj.asInstanceOf[Vehicle].Seats.values.count(_.isOccupied) == 0
|
||||
|
||||
def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = {
|
||||
GUIDTask.UnregisterVehicle(entry.obj.asInstanceOf[Vehicle])(entry.zone.GUID)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,10 @@ import akka.routing.RandomPool
|
|||
import net.psforever.objects._
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
|
||||
import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap}
|
||||
import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream}
|
||||
import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectCreateMessageParent, PlacementData}
|
||||
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID, PlayerStateMessageUpstream}
|
||||
import net.psforever.types.{CharacterGender, ExoSuitType, PlanetSideEmpire, Vector3}
|
||||
import services.{Service, ServiceManager}
|
||||
import services.{RemoverActor, Service, ServiceManager}
|
||||
import services.avatar._
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
|
@ -93,32 +94,59 @@ class ConcealPlayerTest extends ActorTest {
|
|||
}
|
||||
|
||||
class EquipmentInHandTest extends ActorTest {
|
||||
val tool = Tool(GlobalDefinitions.beamer)
|
||||
ServiceManager.boot(system) ! ServiceManager.Register(RandomPool(1).props(Props[TaskResolver]), "taskResolver")
|
||||
val service = system.actorOf(Props[AvatarService], "release-test-service")
|
||||
val zone = new Zone("test", new ZoneMap("test-map"), 0)
|
||||
val taskResolver = system.actorOf(Props[TaskResolver], "release-test-resolver")
|
||||
|
||||
val toolDef = GlobalDefinitions.beamer
|
||||
val tool = Tool(toolDef)
|
||||
tool.GUID = PlanetSideGUID(40)
|
||||
tool.AmmoSlots.head.Box.GUID = PlanetSideGUID(41)
|
||||
val pkt = ObjectCreateMessage(
|
||||
toolDef.ObjectId,
|
||||
tool.GUID,
|
||||
ObjectCreateMessageParent(PlanetSideGUID(11), 2),
|
||||
toolDef.Packet.ConstructorData(tool).get
|
||||
)
|
||||
|
||||
"AvatarService" should {
|
||||
"pass EquipmentInHand" in {
|
||||
ServiceManager.boot(system)
|
||||
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
|
||||
service ! Service.Join("test")
|
||||
service ! AvatarServiceMessage("test", AvatarAction.EquipmentInHand(PlanetSideGUID(10), PlanetSideGUID(11), 2, tool))
|
||||
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.EquipmentInHand(PlanetSideGUID(11), 2, tool)))
|
||||
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.EquipmentInHand(pkt)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EquipmentOnGroundTest extends ActorTest {
|
||||
class DroptItemTest extends ActorTest {
|
||||
ServiceManager.boot(system) ! ServiceManager.Register(RandomPool(1).props(Props[TaskResolver]), "taskResolver")
|
||||
val service = system.actorOf(Props[AvatarService], "release-test-service")
|
||||
val zone = new Zone("test", new ZoneMap("test-map"), 0)
|
||||
val taskResolver = system.actorOf(Props[TaskResolver], "drop-item-test-resolver")
|
||||
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "drop-item-test-zone")
|
||||
zone.Actor ! Zone.Init()
|
||||
|
||||
val toolDef = GlobalDefinitions.beamer
|
||||
val tool = Tool(toolDef)
|
||||
tool.AmmoSlots.head.Box.GUID = PlanetSideGUID(1)
|
||||
val cdata = toolDef.Packet.ConstructorData(tool).get
|
||||
tool.Position = Vector3(1,2,3)
|
||||
tool.Orientation = Vector3(4,5,6)
|
||||
tool.GUID = PlanetSideGUID(40)
|
||||
tool.AmmoSlots.head.Box.GUID = PlanetSideGUID(41)
|
||||
val pkt = ObjectCreateMessage(
|
||||
toolDef.ObjectId,
|
||||
tool.GUID,
|
||||
DroppedItemData(
|
||||
PlacementData(tool.Position, tool.Orientation),
|
||||
toolDef.Packet.ConstructorData(tool).get
|
||||
)
|
||||
)
|
||||
|
||||
"AvatarService" should {
|
||||
"pass EquipmentOnGround" in {
|
||||
ServiceManager.boot(system)
|
||||
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
|
||||
"pass DropItem" in {
|
||||
service ! Service.Join("test")
|
||||
service ! AvatarServiceMessage("test", AvatarAction.EquipmentOnGround(PlanetSideGUID(10), Vector3(300f, 200f, 100f), Vector3(450f, 300f, 150f), toolDef.ObjectId, PlanetSideGUID(11), cdata))
|
||||
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.EquipmentOnGround(Vector3(300f, 200f, 100f), Vector3(450f, 300f, 150f), toolDef.ObjectId, PlanetSideGUID(11), cdata)))
|
||||
service ! AvatarServiceMessage("test", AvatarAction.DropItem(PlanetSideGUID(10), tool, zone))
|
||||
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.DropItem(pkt)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -193,6 +221,45 @@ class PlayerStateTest extends ActorTest {
|
|||
}
|
||||
}
|
||||
|
||||
class PickupItemATest extends ActorTest {
|
||||
val obj = Player(Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, 1))
|
||||
obj.GUID = PlanetSideGUID(10)
|
||||
obj.Slot(5).Equipment.get.GUID = PlanetSideGUID(11)
|
||||
|
||||
val toolDef = GlobalDefinitions.beamer
|
||||
val tool = Tool(toolDef)
|
||||
tool.GUID = PlanetSideGUID(40)
|
||||
tool.AmmoSlots.head.Box.GUID = PlanetSideGUID(41)
|
||||
val pkt = ObjectCreateMessage(
|
||||
toolDef.ObjectId,
|
||||
tool.GUID,
|
||||
ObjectCreateMessageParent(PlanetSideGUID(10), 0),
|
||||
toolDef.Packet.ConstructorData(tool).get
|
||||
)
|
||||
|
||||
"pass PickUpItem as EquipmentInHand (visible pistol slot)" in {
|
||||
ServiceManager.boot(system)
|
||||
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
|
||||
service ! Service.Join("test")
|
||||
service ! AvatarServiceMessage("test", AvatarAction.PickupItem(PlanetSideGUID(10), Zone.Nowhere, obj, 0, tool))
|
||||
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.EquipmentInHand(pkt)))
|
||||
}
|
||||
}
|
||||
|
||||
class PickupItemBTest extends ActorTest {
|
||||
val obj = Player(Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, 1))
|
||||
val tool = Tool(GlobalDefinitions.beamer)
|
||||
tool.GUID = PlanetSideGUID(40)
|
||||
|
||||
"pass PickUpItem as ObjectDelete (not visible inventory space)" in {
|
||||
ServiceManager.boot(system)
|
||||
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
|
||||
service ! Service.Join("test")
|
||||
service ! AvatarServiceMessage("test", AvatarAction.PickupItem(PlanetSideGUID(10), Zone.Nowhere, obj, 6, tool))
|
||||
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.ObjectDelete(tool.GUID, 0)))
|
||||
}
|
||||
}
|
||||
|
||||
class ReloadTest extends ActorTest {
|
||||
"AvatarService" should {
|
||||
"pass Reload" in {
|
||||
|
|
@ -320,14 +387,14 @@ class AvatarReleaseTest extends ActorTest {
|
|||
taskResolver ! GUIDTask.RegisterObjectTask(obj)(zone.GUID)
|
||||
assert(zone.Corpses.isEmpty)
|
||||
zone.Population ! Zone.Corpse.Add(obj)
|
||||
expectNoMsg(100 milliseconds) //spacer
|
||||
expectNoMsg(200 milliseconds) //spacer
|
||||
|
||||
assert(zone.Corpses.size == 1)
|
||||
assert(obj.HasGUID)
|
||||
val guid = obj.GUID
|
||||
service ! AvatarServiceMessage("test", AvatarAction.Release(obj, zone, Some(1000000000))) //alive for one second
|
||||
service ! AvatarServiceMessage("test", AvatarAction.Release(obj, zone, Some(1 second))) //alive for one second
|
||||
|
||||
val reply1 = receiveOne(100 milliseconds)
|
||||
val reply1 = receiveOne(200 milliseconds)
|
||||
assert(reply1.isInstanceOf[AvatarServiceResponse])
|
||||
val reply1msg = reply1.asInstanceOf[AvatarServiceResponse]
|
||||
assert(reply1msg.toChannel == "/test/Avatar")
|
||||
|
|
@ -343,7 +410,7 @@ class AvatarReleaseTest extends ActorTest {
|
|||
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
|
||||
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
|
||||
|
||||
expectNoMsg(1000 milliseconds)
|
||||
expectNoMsg(1 seconds)
|
||||
assert(zone.Corpses.isEmpty)
|
||||
assert(!obj.HasGUID)
|
||||
}
|
||||
|
|
@ -369,14 +436,14 @@ class AvatarReleaseEarly1Test extends ActorTest {
|
|||
taskResolver ! GUIDTask.RegisterObjectTask(obj)(zone.GUID)
|
||||
assert(zone.Corpses.isEmpty)
|
||||
zone.Population ! Zone.Corpse.Add(obj)
|
||||
expectNoMsg(100 milliseconds) //spacer
|
||||
expectNoMsg(200 milliseconds) //spacer
|
||||
|
||||
assert(zone.Corpses.size == 1)
|
||||
assert(obj.HasGUID)
|
||||
val guid = obj.GUID
|
||||
service ! AvatarServiceMessage("test", AvatarAction.Release(obj, zone)) //3+ minutes!
|
||||
|
||||
val reply1 = receiveOne(100 milliseconds)
|
||||
val reply1 = receiveOne(200 milliseconds)
|
||||
assert(reply1.isInstanceOf[AvatarServiceResponse])
|
||||
val reply1msg = reply1.asInstanceOf[AvatarServiceResponse]
|
||||
assert(reply1msg.toChannel == "/test/Avatar")
|
||||
|
|
@ -384,8 +451,8 @@ class AvatarReleaseEarly1Test extends ActorTest {
|
|||
assert(reply1msg.replyMessage.isInstanceOf[AvatarResponse.Release])
|
||||
assert(reply1msg.replyMessage.asInstanceOf[AvatarResponse.Release].player == obj)
|
||||
|
||||
service ! AvatarServiceMessage.RemoveSpecificCorpse(List(obj)) //IMPORTANT: ONE ENTRY
|
||||
val reply2 = receiveOne(100 milliseconds)
|
||||
service ! AvatarServiceMessage.Corpse(RemoverActor.HurrySpecific(List(obj), zone)) //IMPORTANT: ONE ENTRY
|
||||
val reply2 = receiveOne(200 milliseconds)
|
||||
assert(reply2.isInstanceOf[AvatarServiceResponse])
|
||||
val reply2msg = reply2.asInstanceOf[AvatarServiceResponse]
|
||||
assert(reply2msg.toChannel.equals("/test/Avatar"))
|
||||
|
|
@ -393,7 +460,7 @@ class AvatarReleaseEarly1Test extends ActorTest {
|
|||
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
|
||||
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
|
||||
|
||||
expectNoMsg(600 milliseconds)
|
||||
expectNoMsg(1 seconds)
|
||||
assert(zone.Corpses.isEmpty)
|
||||
assert(!obj.HasGUID)
|
||||
}
|
||||
|
|
@ -420,14 +487,14 @@ class AvatarReleaseEarly2Test extends ActorTest {
|
|||
taskResolver ! GUIDTask.RegisterObjectTask(obj)(zone.GUID)
|
||||
assert(zone.Corpses.isEmpty)
|
||||
zone.Population ! Zone.Corpse.Add(obj)
|
||||
expectNoMsg(100 milliseconds) //spacer
|
||||
expectNoMsg(200 milliseconds) //spacer
|
||||
|
||||
assert(zone.Corpses.size == 1)
|
||||
assert(obj.HasGUID)
|
||||
val guid = obj.GUID
|
||||
service ! AvatarServiceMessage("test", AvatarAction.Release(obj, zone)) //3+ minutes!
|
||||
|
||||
val reply1 = receiveOne(100 milliseconds)
|
||||
val reply1 = receiveOne(200 milliseconds)
|
||||
assert(reply1.isInstanceOf[AvatarServiceResponse])
|
||||
val reply1msg = reply1.asInstanceOf[AvatarServiceResponse]
|
||||
assert(reply1msg.toChannel == "/test/Avatar")
|
||||
|
|
@ -435,7 +502,7 @@ class AvatarReleaseEarly2Test extends ActorTest {
|
|||
assert(reply1msg.replyMessage.isInstanceOf[AvatarResponse.Release])
|
||||
assert(reply1msg.replyMessage.asInstanceOf[AvatarResponse.Release].player == obj)
|
||||
|
||||
service ! AvatarServiceMessage.RemoveSpecificCorpse(List(objAlt, obj)) //IMPORTANT: TWO ENTRIES
|
||||
service ! AvatarServiceMessage.Corpse(RemoverActor.HurrySpecific(List(objAlt, obj), zone)) //IMPORTANT: TWO ENTRIES
|
||||
val reply2 = receiveOne(100 milliseconds)
|
||||
assert(reply2.isInstanceOf[AvatarServiceResponse])
|
||||
val reply2msg = reply2.asInstanceOf[AvatarServiceResponse]
|
||||
|
|
@ -444,7 +511,7 @@ class AvatarReleaseEarly2Test extends ActorTest {
|
|||
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
|
||||
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
|
||||
|
||||
expectNoMsg(600 milliseconds)
|
||||
expectNoMsg(1 seconds)
|
||||
assert(zone.Corpses.isEmpty)
|
||||
assert(!obj.HasGUID)
|
||||
}
|
||||
|
|
|
|||
537
pslogin/src/test/scala/RemoverActorTest.scala
Normal file
537
pslogin/src/test/scala/RemoverActorTest.scala
Normal file
|
|
@ -0,0 +1,537 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
import akka.actor.{ActorRef, Props}
|
||||
import akka.routing.RandomPool
|
||||
import akka.testkit.TestProbe
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.definition.{EquipmentDefinition, ObjectDefinition}
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.guid.TaskResolver
|
||||
import net.psforever.objects.zones.{Zone, ZoneMap}
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import services.{RemoverActor, ServiceManager}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class StandardRemoverActorTest extends ActorTest {
|
||||
ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
||||
|
||||
"RemoverActor" should {
|
||||
"handle a simple task" in {
|
||||
expectNoMsg(500 milliseconds)
|
||||
val probe = TestProbe()
|
||||
val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
|
||||
remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere)
|
||||
|
||||
val reply1 = probe.receiveOne(200 milliseconds)
|
||||
assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert])
|
||||
val reply2 = probe.receiveOne(200 milliseconds)
|
||||
assert(reply2.isInstanceOf[RemoverActorTest.InitialJobAlert])
|
||||
probe.expectNoMsg(1 seconds) //delay
|
||||
val reply3 = probe.receiveOne(300 milliseconds)
|
||||
assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert])
|
||||
val reply4 = probe.receiveOne(300 milliseconds)
|
||||
assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert])
|
||||
val reply5 = probe.receiveOne(300 milliseconds)
|
||||
assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert])
|
||||
val reply6 = probe.receiveOne(500 milliseconds)
|
||||
assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert])
|
||||
val reply7 = probe.receiveOne(500 milliseconds)
|
||||
assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DelayedRemoverActorTest extends ActorTest {
|
||||
ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
||||
|
||||
"RemoverActor" should {
|
||||
"handle a simple task (timed)" in {
|
||||
expectNoMsg(500 milliseconds)
|
||||
val probe = TestProbe()
|
||||
val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
|
||||
remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(100 milliseconds))
|
||||
|
||||
val reply1 = probe.receiveOne(200 milliseconds)
|
||||
assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert])
|
||||
val reply2 = probe.receiveOne(200 milliseconds)
|
||||
assert(reply2.isInstanceOf[RemoverActorTest.InitialJobAlert])
|
||||
//no delay
|
||||
val reply3 = probe.receiveOne(300 milliseconds)
|
||||
assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert])
|
||||
val reply4 = probe.receiveOne(300 milliseconds)
|
||||
assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert])
|
||||
val reply5 = probe.receiveOne(300 milliseconds)
|
||||
assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert])
|
||||
val reply6 = probe.receiveOne(300 milliseconds)
|
||||
assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert])
|
||||
val reply7 = probe.receiveOne(300 milliseconds)
|
||||
assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExcludedRemoverActorTest extends ActorTest {
|
||||
ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
||||
val AlternateTestObject = new PlanetSideGameObject() { def Definition = new ObjectDefinition(0) { } }
|
||||
|
||||
"RemoverActor" should {
|
||||
"allow only specific objects" in {
|
||||
expectNoMsg(500 milliseconds)
|
||||
val probe = TestProbe()
|
||||
val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
|
||||
remover ! RemoverActor.AddTask(AlternateTestObject, Zone.Nowhere)
|
||||
|
||||
val reply1 = probe.receiveOne(200 milliseconds)
|
||||
assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert])
|
||||
expectNoMsg(2 seconds)
|
||||
//RemoverActor is stalled because it received an object that it was not allowed to act upon
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MultipleRemoverActorTest extends ActorTest {
|
||||
ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
||||
final val TestObject2 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(2) } }
|
||||
|
||||
"RemoverActor" should {
|
||||
"work on parallel tasks" in {
|
||||
expectNoMsg(500 milliseconds)
|
||||
val probe = TestProbe()
|
||||
val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
|
||||
remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere)
|
||||
remover ! RemoverActor.AddTask(TestObject2, Zone.Nowhere)
|
||||
|
||||
val replies = probe.receiveN(14, 5 seconds)
|
||||
var ita : Int = 0
|
||||
var ija : Int = 0
|
||||
var fja : Int = 0
|
||||
var cta : Int = 0
|
||||
var sja : Int = 0
|
||||
var dta : Int = 0
|
||||
var dtr : Int = 0
|
||||
replies.collect {
|
||||
case RemoverActorTest.InclusionTestAlert() => ita += 1
|
||||
case RemoverActorTest.InitialJobAlert() => ija += 1
|
||||
case RemoverActorTest.FirstJobAlert() => fja += 1
|
||||
case RemoverActorTest.ClearanceTestAlert() => cta += 1
|
||||
case RemoverActorTest.SecondJobAlert() => sja += 1
|
||||
case RemoverActorTest.DeletionTaskAlert() => dta += 1
|
||||
case RemoverActorTest.DeletionTaskRunAlert() => dtr += 1
|
||||
case msg => assert(false, s"$msg")
|
||||
}
|
||||
assert(ita == 2 && ija == 2 && fja == 2 && cta == 2 && sja == 2 && dta == 2 && dtr == 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HurrySpecificRemoverActorTest extends ActorTest {
|
||||
ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
||||
|
||||
"RemoverActor" should {
|
||||
"be able to hurry certain tasks" in {
|
||||
expectNoMsg(500 milliseconds)
|
||||
val probe = TestProbe()
|
||||
val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
|
||||
remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(10 minutes)) //TEN MINUTE WAIT
|
||||
|
||||
val reply1 = probe.receiveOne(200 milliseconds)
|
||||
assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert])
|
||||
val reply2 = probe.receiveOne(200 milliseconds)
|
||||
assert(reply2.isInstanceOf[RemoverActorTest.InitialJobAlert])
|
||||
probe.expectNoMsg(3 seconds) //long delay, longer than standard but not yet 10 minutes
|
||||
remover ! RemoverActor.HurrySpecific(List(RemoverActorTest.TestObject), Zone.Nowhere) //hurried
|
||||
val reply3 = probe.receiveOne(300 milliseconds)
|
||||
assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert])
|
||||
val reply4 = probe.receiveOne(300 milliseconds)
|
||||
assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert])
|
||||
val reply5 = probe.receiveOne(300 milliseconds)
|
||||
assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert])
|
||||
val reply6 = probe.receiveOne(500 milliseconds)
|
||||
assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert])
|
||||
val reply7 = probe.receiveOne(500 milliseconds)
|
||||
assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HurrySelectionRemoverActorTest extends ActorTest {
|
||||
ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
||||
final val TestObject2 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(2) } }
|
||||
|
||||
"RemoverActor" should {
|
||||
"be able to hurry certain tasks, but let others finish normally" in {
|
||||
expectNoMsg(500 milliseconds)
|
||||
val probe = TestProbe()
|
||||
val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
|
||||
remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(5 seconds))
|
||||
remover ! RemoverActor.AddTask(TestObject2, Zone.Nowhere, Some(10 seconds))
|
||||
|
||||
val replies = probe.receiveN(4, 5 seconds)
|
||||
var ita : Int = 0
|
||||
var ija : Int = 0
|
||||
replies.collect {
|
||||
case RemoverActorTest.InclusionTestAlert() => ita += 1
|
||||
case RemoverActorTest.InitialJobAlert() => ija += 1
|
||||
case msg => assert(false, s"$msg")
|
||||
}
|
||||
assert(ita == 2 && ija == 2)
|
||||
probe.expectNoMsg(3 seconds) //long delay, longer than standard but not yet 5 seconds
|
||||
remover ! RemoverActor.HurrySpecific(List(RemoverActorTest.TestObject), Zone.Nowhere) //hurried
|
||||
//first
|
||||
val reply3a = probe.receiveOne(300 milliseconds)
|
||||
assert(reply3a.isInstanceOf[RemoverActorTest.FirstJobAlert])
|
||||
val reply4a = probe.receiveOne(300 milliseconds)
|
||||
assert(reply4a.isInstanceOf[RemoverActorTest.ClearanceTestAlert])
|
||||
val reply5a = probe.receiveOne(300 milliseconds)
|
||||
assert(reply5a.isInstanceOf[RemoverActorTest.SecondJobAlert])
|
||||
val reply6a = probe.receiveOne(500 milliseconds)
|
||||
assert(reply6a.isInstanceOf[RemoverActorTest.DeletionTaskAlert])
|
||||
val reply7a = probe.receiveOne(500 milliseconds)
|
||||
assert(reply7a.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert])
|
||||
//second
|
||||
remover ! RemoverActor.HurrySpecific(List(TestObject2), Zone.Nowhere) //hurried
|
||||
val reply3b = probe.receiveOne(300 milliseconds)
|
||||
assert(reply3b.isInstanceOf[RemoverActorTest.FirstJobAlert])
|
||||
val reply4b = probe.receiveOne(300 milliseconds)
|
||||
assert(reply4b.isInstanceOf[RemoverActorTest.ClearanceTestAlert])
|
||||
val reply5b = probe.receiveOne(300 milliseconds)
|
||||
assert(reply5b.isInstanceOf[RemoverActorTest.SecondJobAlert])
|
||||
val reply6b = probe.receiveOne(500 milliseconds)
|
||||
assert(reply6b.isInstanceOf[RemoverActorTest.DeletionTaskAlert])
|
||||
val reply7b = probe.receiveOne(500 milliseconds)
|
||||
assert(reply7b.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HurryMultipleRemoverActorTest extends ActorTest {
|
||||
ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
||||
final val TestObject2 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(2) } }
|
||||
final val TestObject3 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(3) } }
|
||||
|
||||
"RemoverActor" should {
|
||||
"be able to hurry certain tasks, but only valid ones" in {
|
||||
expectNoMsg(500 milliseconds)
|
||||
val probe = TestProbe()
|
||||
val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
|
||||
remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(5 seconds))
|
||||
remover ! RemoverActor.AddTask(TestObject2, Zone.Nowhere, Some(5 seconds))
|
||||
|
||||
val replies = probe.receiveN(4, 5 seconds)
|
||||
var ita : Int = 0
|
||||
var ija : Int = 0
|
||||
replies.collect {
|
||||
case RemoverActorTest.InclusionTestAlert() => ita += 1
|
||||
case RemoverActorTest.InitialJobAlert() => ija += 1
|
||||
case msg => assert(false, s"$msg")
|
||||
}
|
||||
assert(ita == 2 && ija == 2)
|
||||
probe.expectNoMsg(3 seconds) //long delay, longer than standard but not yet 5 seconds
|
||||
remover ! RemoverActor.HurrySpecific(List(RemoverActorTest.TestObject, TestObject3), Zone.Nowhere) //multiple hurried, only one valid
|
||||
//first
|
||||
val reply3a = probe.receiveOne(300 milliseconds)
|
||||
assert(reply3a.isInstanceOf[RemoverActorTest.FirstJobAlert])
|
||||
val reply4a = probe.receiveOne(300 milliseconds)
|
||||
assert(reply4a.isInstanceOf[RemoverActorTest.ClearanceTestAlert])
|
||||
val reply5a = probe.receiveOne(300 milliseconds)
|
||||
assert(reply5a.isInstanceOf[RemoverActorTest.SecondJobAlert])
|
||||
val reply6a = probe.receiveOne(500 milliseconds)
|
||||
assert(reply6a.isInstanceOf[RemoverActorTest.DeletionTaskAlert])
|
||||
val reply7a = probe.receiveOne(500 milliseconds)
|
||||
assert(reply7a.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert])
|
||||
//second
|
||||
remover ! RemoverActor.HurrySpecific(List(TestObject2), Zone.Nowhere) //hurried
|
||||
val reply3b = probe.receiveOne(300 milliseconds)
|
||||
assert(reply3b.isInstanceOf[RemoverActorTest.FirstJobAlert])
|
||||
val reply4b = probe.receiveOne(300 milliseconds)
|
||||
assert(reply4b.isInstanceOf[RemoverActorTest.ClearanceTestAlert])
|
||||
val reply5b = probe.receiveOne(300 milliseconds)
|
||||
assert(reply5b.isInstanceOf[RemoverActorTest.SecondJobAlert])
|
||||
val reply6b = probe.receiveOne(500 milliseconds)
|
||||
assert(reply6b.isInstanceOf[RemoverActorTest.DeletionTaskAlert])
|
||||
val reply7b = probe.receiveOne(500 milliseconds)
|
||||
assert(reply7b.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HurryByZoneRemoverActorTest extends ActorTest {
|
||||
ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
||||
final val TestObject2 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(2) } }
|
||||
final val TestObject3 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(3) } }
|
||||
final val zone = new Zone("test", new ZoneMap("test-map"), 11)
|
||||
|
||||
"RemoverActor" should {
|
||||
"be able to hurry certain tasks by their zone" in {
|
||||
expectNoMsg(500 milliseconds)
|
||||
val probe = TestProbe()
|
||||
val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
|
||||
remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(5 seconds))
|
||||
remover ! RemoverActor.AddTask(TestObject2, zone, Some(5 seconds))
|
||||
remover ! RemoverActor.AddTask(TestObject3, Zone.Nowhere, Some(5 seconds))
|
||||
|
||||
val replies1 = probe.receiveN(6, 5 seconds)
|
||||
var ita : Int = 0
|
||||
var ija : Int = 0
|
||||
replies1.collect {
|
||||
case RemoverActorTest.InclusionTestAlert() => ita += 1
|
||||
case RemoverActorTest.InitialJobAlert() => ija += 1
|
||||
case msg => assert(false, s"$msg")
|
||||
}
|
||||
assert(ita == 3 && ija == 3)
|
||||
probe.expectNoMsg(3 seconds) //long delay, longer than standard but not yet 5 seconds
|
||||
remover ! RemoverActor.HurrySpecific(List(), Zone.Nowhere) //multiple hurried, only the two entries with Zone.Nowhere
|
||||
//
|
||||
val replies2 = probe.receiveN(10, 5 seconds)
|
||||
var fja : Int = 0
|
||||
var cta : Int = 0
|
||||
var sja : Int = 0
|
||||
var dta : Int = 0
|
||||
var dtr : Int = 0
|
||||
replies2.collect {
|
||||
case RemoverActorTest.InclusionTestAlert() => ita += 1
|
||||
case RemoverActorTest.InitialJobAlert() => ija += 1
|
||||
case RemoverActorTest.FirstJobAlert() => fja += 1
|
||||
case RemoverActorTest.ClearanceTestAlert() => cta += 1
|
||||
case RemoverActorTest.SecondJobAlert() => sja += 1
|
||||
case RemoverActorTest.DeletionTaskAlert() => dta += 1
|
||||
case RemoverActorTest.DeletionTaskRunAlert() => dtr += 1
|
||||
case msg => assert(false, s"$msg")
|
||||
}
|
||||
assert(fja == 2 && cta == 2 && sja == 2 && dta == 2 && dtr == 2)
|
||||
//final
|
||||
remover ! RemoverActor.HurrySpecific(List(), zone) //hurried
|
||||
val reply3b = probe.receiveOne(300 milliseconds)
|
||||
assert(reply3b.isInstanceOf[RemoverActorTest.FirstJobAlert])
|
||||
val reply4b = probe.receiveOne(300 milliseconds)
|
||||
assert(reply4b.isInstanceOf[RemoverActorTest.ClearanceTestAlert])
|
||||
val reply5b = probe.receiveOne(300 milliseconds)
|
||||
assert(reply5b.isInstanceOf[RemoverActorTest.SecondJobAlert])
|
||||
val reply6b = probe.receiveOne(500 milliseconds)
|
||||
assert(reply6b.isInstanceOf[RemoverActorTest.DeletionTaskAlert])
|
||||
val reply7b = probe.receiveOne(500 milliseconds)
|
||||
assert(reply7b.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HurryAllRemoverActorTest extends ActorTest {
|
||||
ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
||||
final val TestObject2 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(2) } }
|
||||
final val TestObject3 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(3) } }
|
||||
|
||||
"RemoverActor" should {
|
||||
"be able to hurry all tasks to completion" in {
|
||||
expectNoMsg(500 milliseconds)
|
||||
val probe = TestProbe()
|
||||
val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
|
||||
remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(20 seconds))
|
||||
remover ! RemoverActor.AddTask(TestObject2, Zone.Nowhere, Some(15 seconds))
|
||||
remover ! RemoverActor.AddTask(TestObject3, Zone.Nowhere, Some(10 seconds))
|
||||
|
||||
val replies1 = probe.receiveN(6, 5 seconds)
|
||||
var ita : Int = 0
|
||||
var ija : Int = 0
|
||||
replies1.collect {
|
||||
case RemoverActorTest.InclusionTestAlert() => ita += 1
|
||||
case RemoverActorTest.InitialJobAlert() => ija += 1
|
||||
case msg => assert(false, s"$msg")
|
||||
}
|
||||
assert(ita == 3 && ija == 3)
|
||||
probe.expectNoMsg(3 seconds) //long delay, longer than standard but not yet longer than any of the tasks
|
||||
remover ! RemoverActor.HurryAll() //all hurried
|
||||
//
|
||||
val replies2 = probe.receiveN(15, 5 seconds)
|
||||
var fja : Int = 0
|
||||
var cta : Int = 0
|
||||
var sja : Int = 0
|
||||
var dta : Int = 0
|
||||
var dtr : Int = 0
|
||||
replies2.collect {
|
||||
case RemoverActorTest.InclusionTestAlert() => ita += 1
|
||||
case RemoverActorTest.InitialJobAlert() => ija += 1
|
||||
case RemoverActorTest.FirstJobAlert() => fja += 1
|
||||
case RemoverActorTest.ClearanceTestAlert() => cta += 1
|
||||
case RemoverActorTest.SecondJobAlert() => sja += 1
|
||||
case RemoverActorTest.DeletionTaskAlert() => dta += 1
|
||||
case RemoverActorTest.DeletionTaskRunAlert() => dtr += 1
|
||||
case msg => assert(false, s"$msg")
|
||||
}
|
||||
assert(fja == 3 && cta == 3 && sja == 3 && dta == 3 && dtr == 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ClearSelectionRemoverActorTest extends ActorTest {
|
||||
ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
||||
final val TestObject2 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(2) } }
|
||||
|
||||
"RemoverActor" should {
|
||||
"be able to clear certain tasks" in {
|
||||
expectNoMsg(500 milliseconds)
|
||||
val probe = TestProbe()
|
||||
val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
|
||||
remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(5 seconds))
|
||||
remover ! RemoverActor.AddTask(TestObject2, Zone.Nowhere, Some(5 seconds))
|
||||
|
||||
val replies = probe.receiveN(4, 5 seconds)
|
||||
var ita : Int = 0
|
||||
var ija : Int = 0
|
||||
replies.collect {
|
||||
case RemoverActorTest.InclusionTestAlert() => ita += 1
|
||||
case RemoverActorTest.InitialJobAlert() => ija += 1
|
||||
case msg => assert(false, s"$msg")
|
||||
}
|
||||
assert(ita == 2 && ija == 2)
|
||||
probe.expectNoMsg(4 seconds) //long delay, longer than standard but not yet 5 seconds
|
||||
remover ! RemoverActor.ClearSpecific(List(RemoverActorTest.TestObject), Zone.Nowhere) //cleared
|
||||
//
|
||||
val reply3 = probe.receiveOne(2 seconds)
|
||||
assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert])
|
||||
val reply4 = probe.receiveOne(300 milliseconds)
|
||||
assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert])
|
||||
val reply5 = probe.receiveOne(300 milliseconds)
|
||||
assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert])
|
||||
val reply6 = probe.receiveOne(500 milliseconds)
|
||||
assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert])
|
||||
val reply7 = probe.receiveOne(500 milliseconds)
|
||||
assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert])
|
||||
//wait
|
||||
probe.expectNoMsg(2 seconds) //nothing more to do
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ClearAllRemoverActorTest extends ActorTest {
|
||||
ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
||||
final val TestObject2 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(2) } }
|
||||
|
||||
"RemoverActor" should {
|
||||
"be able to clear all tasks, with no more work on them" in {
|
||||
expectNoMsg(500 milliseconds)
|
||||
val probe = TestProbe()
|
||||
val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
|
||||
remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(5 seconds))
|
||||
remover ! RemoverActor.AddTask(TestObject2, Zone.Nowhere, Some(5 seconds))
|
||||
|
||||
val replies = probe.receiveN(4, 5 seconds)
|
||||
var ita : Int = 0
|
||||
var ija : Int = 0
|
||||
replies.collect {
|
||||
case RemoverActorTest.InclusionTestAlert() => ita += 1
|
||||
case RemoverActorTest.InitialJobAlert() => ija += 1
|
||||
case msg => assert(false, s"$msg")
|
||||
}
|
||||
assert(ita == 2 && ija == 2)
|
||||
probe.expectNoMsg(4 seconds) //long delay, longer than standard but not yet 5 seconds
|
||||
remover ! RemoverActor.ClearAll() //cleared
|
||||
//wait
|
||||
probe.expectNoMsg(3 seconds) //nothing more to do
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EarlyDeathRemoverActorTest extends ActorTest {
|
||||
ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
||||
final val TestObject2 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(2) } }
|
||||
|
||||
"RemoverActor" should {
|
||||
"be able to hurry certain tasks" in {
|
||||
expectNoMsg(500 milliseconds)
|
||||
val probe = TestProbe()
|
||||
val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
|
||||
remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(5 seconds))
|
||||
remover ! RemoverActor.AddTask(TestObject2, Zone.Nowhere, Some(5 seconds))
|
||||
|
||||
val replies = probe.receiveN(4, 5 seconds)
|
||||
var ita : Int = 0
|
||||
var ija : Int = 0
|
||||
replies.collect {
|
||||
case RemoverActorTest.InclusionTestAlert() => ita += 1
|
||||
case RemoverActorTest.InitialJobAlert() => ija += 1
|
||||
case msg => assert(false, s"$msg")
|
||||
}
|
||||
assert(ita == 2 && ija == 2)
|
||||
probe.expectNoMsg(2 seconds)
|
||||
remover ! akka.actor.PoisonPill
|
||||
//
|
||||
val replies2 = probe.receiveN(8, 5 seconds)
|
||||
var fja : Int = 0
|
||||
var cta : Int = 0
|
||||
var sja : Int = 0
|
||||
var dta : Int = 0
|
||||
var dtr : Int = 0
|
||||
replies2.collect {
|
||||
case RemoverActorTest.FirstJobAlert() => fja += 1
|
||||
case RemoverActorTest.ClearanceTestAlert() => cta += 1
|
||||
case RemoverActorTest.SecondJobAlert() => sja += 1
|
||||
case RemoverActorTest.DeletionTaskAlert() => dta += 1
|
||||
case RemoverActorTest.DeletionTaskRunAlert() => dtr += 1
|
||||
case msg => assert(false, s"$msg")
|
||||
}
|
||||
assert(fja == 2 && cta == 0 && sja == 2 && dta == 2 && dtr == 2) //no clearance tests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object RemoverActorTest {
|
||||
final val TestObject = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(1) } }
|
||||
|
||||
final case class InclusionTestAlert()
|
||||
|
||||
final case class InitialJobAlert()
|
||||
|
||||
final case class FirstJobAlert()
|
||||
|
||||
final case class SecondJobAlert()
|
||||
|
||||
final case class ClearanceTestAlert()
|
||||
|
||||
final case class DeletionTaskAlert()
|
||||
|
||||
final case class DeletionTaskRunAlert()
|
||||
|
||||
class TestRemover(probe : TestProbe) extends RemoverActor {
|
||||
import net.psforever.objects.guid.{Task, TaskResolver}
|
||||
val FirstStandardDuration = 1 seconds
|
||||
|
||||
val SecondStandardDuration = 100 milliseconds
|
||||
|
||||
def InclusionTest(entry : RemoverActor.Entry) : Boolean = {
|
||||
probe.ref ! InclusionTestAlert()
|
||||
entry.obj.isInstanceOf[Equipment]
|
||||
}
|
||||
|
||||
def InitialJob(entry : RemoverActor.Entry) : Unit = {
|
||||
probe.ref ! InitialJobAlert()
|
||||
}
|
||||
|
||||
def FirstJob(entry : RemoverActor.Entry) : Unit = {
|
||||
probe.ref ! FirstJobAlert()
|
||||
}
|
||||
|
||||
override def SecondJob(entry : RemoverActor.Entry) : Unit = {
|
||||
probe.ref ! SecondJobAlert()
|
||||
super.SecondJob(entry)
|
||||
}
|
||||
|
||||
def ClearanceTest(entry : RemoverActor.Entry) : Boolean = {
|
||||
probe.ref ! ClearanceTestAlert()
|
||||
true
|
||||
}
|
||||
|
||||
def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = {
|
||||
probe.ref ! DeletionTaskAlert()
|
||||
TaskResolver.GiveTask(new Task() {
|
||||
private val localProbe = probe
|
||||
|
||||
override def isComplete = Task.Resolution.Success
|
||||
|
||||
def Execute(resolver : ActorRef) : Unit = {
|
||||
localProbe.ref ! DeletionTaskRunAlert()
|
||||
resolver ! scala.util.Success(this)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue