Changes to AvatarService packets and support actors in regards to corpse management and tests regarding corpse management. Seriously, those tests.

This commit is contained in:
FateJH 2018-03-31 19:31:17 -04:00
parent 7f40b31a34
commit f444a35785
12 changed files with 672 additions and 220 deletions

View file

@ -689,6 +689,17 @@ object GlobalDefinitions {
}
}
def isMaxArms(tdef : ToolDefinition) : Boolean = {
tdef match {
case `trhev_dualcycler` | `nchev_scattercannon` | `vshev_quasar`
| `trhev_pounder` | `nchev_falcon` | `vshev_comet`
| `trhev_burster` | `nchev_sparrow` | `vshev_starfire` =>
true
case _ =>
false
}
}
def AIMAX(faction : PlanetSideEmpire.Value) : ToolDefinition = {
faction match {
case PlanetSideEmpire.TR => trhev_dualcycler

View file

@ -4,7 +4,7 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.{EquipmentSlot, Player}
import net.psforever.objects.equipment.Equipment
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, InternalSlot, InventoryData, PlacementData, RibbonBars}
import net.psforever.types.{CharacterGender, GrenadeState}
import net.psforever.types.{CharacterGender, GrenadeState, Vector3}
import scala.annotation.tailrec
import scala.util.{Failure, Success, Try}
@ -33,7 +33,7 @@ class CorpseConverter extends AvatarConverter {
*/
private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = {
CharacterAppearanceData(
PlacementData(obj.Position, obj.Orientation),
PlacementData(obj.Position, Vector3(0,0, obj.Orientation.z)),
BasicCharacterData(obj.Name, obj.Faction, CharacterGender.Male, 0, 0),
0,
false,

View file

@ -12,7 +12,7 @@ import net.psforever.objects.serverobject.structures.{Building, FoundationBuilde
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap}
import net.psforever.objects.{Avatar, GlobalDefinitions, Player, Vehicle}
import net.psforever.objects._
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3}
import org.specs2.mutable.Specification
@ -489,6 +489,60 @@ class ZonePopulationTest extends ActorTest {
}
}
class ZoneGroundTest extends ActorTest {
val item = AmmoBox(GlobalDefinitions.bullet_9mm)
item.GUID = PlanetSideGUID(10)
"ZoneGroundActor" 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
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))
}
"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"))
assert(zone.EquipmentOnGround == List(item))
zone.Ground ! Zone.GetItemOnGround(player, PlanetSideGUID(10))
val reply = receiveOne(Duration.create(100, "ms"))
assert(zone.EquipmentOnGround.isEmpty)
assert(reply.isInstanceOf[Zone.ItemFromGround])
assert(reply.asInstanceOf[Zone.ItemFromGround].player == player)
assert(reply.asInstanceOf[Zone.ItemFromGround].item == 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"))
assert(zone.EquipmentOnGround == List(item))
zone.Ground ! Zone.GetItemOnGround(player, PlanetSideGUID(11)) //wrong guid
expectNoMsg(Duration.create(500, "ms"))
}
}
}
object ZoneTest {
class ZoneInitActor(zone : Zone) extends Actor {
def receive : Receive = {

View file

@ -37,21 +37,21 @@ object Maps {
LocalObject(371, Door.Constructor) //courtyard
LocalObject(372, Door.Constructor) //courtyard
LocalObject(373, Door.Constructor) //courtyard
LocalObject(375, Door.Constructor) //2nd level door
LocalObject(376, Door.Constructor) //2nd level door
LocalObject(375, Door.Constructor(Vector3(3924.0f, 4231.2656f, 271.82812f), Vector3(0, 0, 180))) //2nd level door, south
LocalObject(376, Door.Constructor(Vector3(3924.0f, 4240.2656f, 271.82812f), Vector3(0, 0, 0))) //2nd level door, north
LocalObject(383, Door.Constructor) //courtyard
LocalObject(384, Door.Constructor) //3rd floor door
LocalObject(384, Door.Constructor(Vector3(3939.6328f, 4232.547f, 279.26562f), Vector3(0, 0, 270))) //3rd floor door
LocalObject(385, Door.Constructor) //courtyard
LocalObject(387, Door.Constructor) //2nd level door
LocalObject(387, Door.Constructor(Vector3(3951.9531f, 4260.008f, 271.82812f), Vector3(0, 0, 270))) //2nd level door, stairwell
LocalObject(391, Door.Constructor) //courtyard
LocalObject(393, Door.Constructor) //air term building, upstairs door
LocalObject(394, Door.Constructor) //air term building, f.door
LocalObject(393, Door.Constructor(Vector3(3997.8984f, 4344.3203f, 271.8125f), Vector3(0, 0, 0))) //air term building, upstairs door
LocalObject(394, Door.Constructor(Vector3(3999.9766f, 4314.3203f, 266.82812f), Vector3(0, 0, 270))) //air term building, f.door
LocalObject(396, Door.Constructor) //courtyard
LocalObject(398, Door.Constructor) //courtyard
LocalObject(399, Door.Constructor) //courtyard
LocalObject(402, Door.Constructor) //courtyard
LocalObject(403, Door.Constructor) //courtyard
LocalObject(404, Door.Constructor) //b.door
LocalObject(404, Door.Constructor(Vector3(4060.0078f, 4319.9766f, 266.8125f), Vector3(0, 0, 0))) //b.door
LocalObject(603, Door.Constructor)
LocalObject(604, Door.Constructor)
LocalObject(605, Door.Constructor)
@ -61,19 +61,33 @@ object Maps {
LocalObject(611, Door.Constructor)
LocalObject(614, Door.Constructor)
LocalObject(619, Door.Constructor)
LocalObject(620, Door.Constructor) //generator room door
LocalObject(620, Door.Constructor(Vector3(3983.9531f, 4299.992f, 249.29688f), Vector3(0, 0, 90))) //generator room door
LocalObject(621, Door.Constructor)
LocalObject(622, Door.Constructor) //spawn room door
LocalObject(623, Door.Constructor) //spawn room door
LocalObject(630, Door.Constructor) //spawn room door
LocalObject(622, Door.Constructor(Vector3(3988.0078f, 4248.0156f, 256.82812f), Vector3(0, 0, 180))) //spawn room door
LocalObject(623, Door.Constructor(Vector3(3988.0078f, 4271.9766f, 256.79688f), Vector3(0, 0, 0))) //spawn room door
LocalObject(630, Door.Constructor(Vector3(4000.0078f, 4252.0f, 249.29688f), Vector3(0, 0, 270))) //spawn room door
LocalObject(631, Door.Constructor) //spawn room door, kitchen
LocalObject(634, Door.Constructor) //air term building, interior
LocalObject(638, Door.Constructor) //cc door
LocalObject(642, Door.Constructor) //cc door, interior
LocalObject(643, Door.Constructor) //cc door
LocalObject(645, Door.Constructor) //b.door interior
LocalObject(646, Door.Constructor) //b.door interior
LocalObject(715, Door.Constructor) //f.door
LocalObject(638, Door.Constructor(Vector3(4016.0078f, 4212.008f, 249.29688f), Vector3(0, 0, 270))) //cc door
LocalObject(642, Door.Constructor(Vector3(4023.9844f, 4212.008f, 249.32812f), Vector3(0, 0, 90))) //cc door, interior
LocalObject(643, Door.Constructor) //cc door, exterior
LocalObject(645, Door.Constructor) //b.door, interior
LocalObject(646, Door.Constructor) //b.door, interior
LocalObject(715, Door.Constructor(Vector3(3961.5938f ,4235.8125f, 266.84375f), Vector3(0, 0, 90))) //f.door
LocalObject(751, IFFLock.Constructor)
LocalObject(860, IFFLock.Constructor)
LocalObject(863, IFFLock.Constructor)
LocalObject(866, IFFLock.Constructor)
LocalObject(868, IFFLock.Constructor)
LocalObject(873, IFFLock.Constructor)
LocalObject(874, IFFLock.Constructor)
LocalObject(875, IFFLock.Constructor)
LocalObject(876, IFFLock.Constructor)
LocalObject(878, IFFLock.Constructor)
LocalObject(879, IFFLock.Constructor)
LocalObject(882, IFFLock.Constructor)
LocalObject(884, IFFLock.Constructor)
LocalObject(885, IFFLock.Constructor)
LocalObject(1177, Locker.Constructor)
LocalObject(1178, Locker.Constructor)
LocalObject(1179, Locker.Constructor)
@ -111,7 +125,7 @@ object Maps {
LocalObject(2324, Door.Constructor) //spawn tube door
LocalObject(2419, Terminal.Constructor(ground_vehicle_terminal))
LocalObject(500,
VehicleSpawnPad.Constructor(Vector3(3962.0f, 4334.0f, 268.0f), Vector3(0f, 0f, 180.0f))
VehicleSpawnPad.Constructor(Vector3(3962.0f, 4334.0f, 267.75f), Vector3(0f, 0f, 180.0f))
) //TODO guid not correct
LocalObject(224, Terminal.Constructor(dropship_vehicle_terminal))
LocalObject(501,
@ -160,6 +174,20 @@ object Maps {
ObjectToBuilding(645, 2)
ObjectToBuilding(646, 2)
ObjectToBuilding(715, 2)
ObjectToBuilding(751, 2)
ObjectToBuilding(860, 2)
ObjectToBuilding(863, 2)
ObjectToBuilding(866, 2)
ObjectToBuilding(868, 2)
ObjectToBuilding(873, 2)
ObjectToBuilding(874, 2)
ObjectToBuilding(875, 2)
ObjectToBuilding(876, 2)
ObjectToBuilding(878, 2)
ObjectToBuilding(879, 2)
ObjectToBuilding(882, 2)
ObjectToBuilding(884, 2)
ObjectToBuilding(885, 2)
ObjectToBuilding(1177, 2)
ObjectToBuilding(1178, 2)
ObjectToBuilding(1179, 2)
@ -198,6 +226,20 @@ object Maps {
ObjectToBuilding(2419, 2)
ObjectToBuilding(500, 2)
ObjectToBuilding(501, 2)
DoorToLock(375, 863)
DoorToLock(376, 860)
DoorToLock(384, 866)
DoorToLock(387, 868)
DoorToLock(393, 876)
DoorToLock(394, 879)
DoorToLock(404, 885)
DoorToLock(620, 873)
DoorToLock(622, 876)
DoorToLock(623, 874)
DoorToLock(630, 878)
DoorToLock(638, 882)
DoorToLock(642, 884)
DoorToLock(715, 751)
TerminalToSpawnPad(224, 501)
TerminalToSpawnPad(2419, 500)
}
@ -285,7 +327,7 @@ object Maps {
def Building49() : Unit = {
//North Akna Air Tower
LocalBuilding(49, FoundationBuilder(Building.Structure(StructureType.Tower, Vector3(3864.2266f, 4518.0234f, 0))))
LocalBuilding(49, FoundationBuilder(Building.Structure(StructureType.Tower, Vector3(4358.3203f, 3989.5625f, 0))))
LocalObject(430, Door.Constructor(Vector3(4366.0156f, 3981.9922f, 237.96875f), Vector3(0f, 0f, 180f))) //s1
LocalObject(431, Door.Constructor(Vector3(4366.0156f, 3981.9922f, 257.89062f), Vector3(0f, 0f, 180f))) //s2
LocalObject(432, Door.Constructor(Vector3(4366.0156f, 3997.9297f, 237.96875f), Vector3(0f, 0f, 0f))) //n1
@ -328,6 +370,8 @@ object Maps {
ObjectToBuilding(1591, 49)
ObjectToBuilding(1592, 49)
ObjectToBuilding(1593, 49)
ObjectToBuilding(2156, 49)
ObjectToBuilding(2157, 49)
ObjectToBuilding(2333, 49)
ObjectToBuilding(2334, 49)
DoorToLock(430, 906)
@ -359,10 +403,12 @@ object Maps {
Building77()
def Building1() : Unit = {
//warpgate?
LocalBuilding(1, FoundationBuilder(WarpGate.Structure))
}
def Building3() : Unit = {
//warpgate?
LocalBuilding(3, FoundationBuilder(WarpGate.Structure))
}
@ -373,7 +419,8 @@ object Maps {
// TerminalToInterface(520, 1081)
def Building2() : Unit = {
LocalBuilding(2, FoundationBuilder(Building.Structure(StructureType.Building))) //HART building C
//HART building C
LocalBuilding(2, FoundationBuilder(Building.Structure(StructureType.Building)))
LocalObject(186, Terminal.Constructor(cert_terminal))
LocalObject(187, Terminal.Constructor(cert_terminal))
LocalObject(188, Terminal.Constructor(cert_terminal))
@ -471,7 +518,8 @@ object Maps {
}
def Building29() : Unit = {
LocalBuilding(29, FoundationBuilder(Building.Structure(StructureType.Tower))) //South Villa Gun Tower
//South Villa Gun Tower
LocalBuilding(29, FoundationBuilder(Building.Structure(StructureType.Tower)))
LocalObject(330, Door.Constructor(Vector3(3979.9219f, 2592.0547f, 91.140625f), Vector3(0, 0, 180)))
LocalObject(331, Door.Constructor(Vector3(3979.9219f, 2592.0547f, 111.140625f), Vector3(0, 0, 180)))
LocalObject(332, Door.Constructor(Vector3(3979.9688f, 2608.0625f, 91.140625f), Vector3(0, 0, 0)))
@ -495,7 +543,8 @@ object Maps {
}
def Building42() : Unit = {
LocalBuilding(42, FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1, 0, 0)))) //spawn building south of HART C
//spawn building south of HART C
LocalBuilding(42, FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1, 0, 0))))
LocalObject(258, Door.Constructor) //spawn tube door
LocalObject(259, Door.Constructor) //spawn tube door
LocalObject(260, Door.Constructor) //spawn tube door
@ -510,12 +559,12 @@ object Maps {
LocalObject(433, Door.Constructor) //vr door
LocalObject(434, Door.Constructor) //vr door
LocalObject(435, Door.Constructor) //vr door
LocalObject(744, SpawnTube.Constructor(Vector3(3684.336f, 2709.0469f, 91.859375f), Vector3(0, 0, 180)))
LocalObject(745, SpawnTube.Constructor(Vector3(3684.336f, 2713.2344f, 91.859375f), Vector3(0, 0, 0)))
LocalObject(746, SpawnTube.Constructor(Vector3(3691.0703f, 2709.0469f, 91.859375f), Vector3(0, 0, 180)))
LocalObject(747, SpawnTube.Constructor(Vector3(3691.0703f, 2713.2344f, 91.859375f), Vector3(0, 0, 0)))
LocalObject(748, SpawnTube.Constructor(Vector3(3697.711f, 2709.0469f, 91.859375f), Vector3(0, 0, 180)))
LocalObject(749, SpawnTube.Constructor(Vector3(3697.711f, 2713.2344f, 91.859375f), Vector3(0, 0, 0)))
LocalObject(744, SpawnTube.Constructor(Vector3(3684.336f, 2709.0469f, 91.9f), Vector3(0, 0, 180)))
LocalObject(745, SpawnTube.Constructor(Vector3(3684.336f, 2713.75f, 91.9f), Vector3(0, 0, 0)))
LocalObject(746, SpawnTube.Constructor(Vector3(3690.9062f, 2708.4219f, 91.9f), Vector3(0, 0, 180)))
LocalObject(747, SpawnTube.Constructor(Vector3(3691.0703f, 2713.8672f, 91.9f), Vector3(0, 0, 0)))
LocalObject(748, SpawnTube.Constructor(Vector3(3697.664f, 2708.3984f, 91.9f), Vector3(0, 0, 180)))
LocalObject(749, SpawnTube.Constructor(Vector3(3697.711f, 2713.2344f, 91.9f), Vector3(0, 0, 0)))
LocalObject(852, Terminal.Constructor(order_terminal)) //s. wall
LocalObject(853, Terminal.Constructor(order_terminal)) //n. wall
LocalObject(854, Terminal.Constructor(order_terminal)) //s. wall
@ -551,7 +600,8 @@ object Maps {
}
def Building51() : Unit = {
LocalBuilding(51, FoundationBuilder(Building.Structure(StructureType.Platform))) //air terminal west of HART C
//air terminal west of HART C
LocalBuilding(51, FoundationBuilder(Building.Structure(StructureType.Platform)))
LocalObject(304, Terminal.Constructor(dropship_vehicle_terminal))
LocalObject(292,
VehicleSpawnPad.Constructor(Vector3(3508.9844f, 2895.961f, 92.296875f), Vector3(0f, 0f, 270.0f))
@ -562,7 +612,8 @@ object Maps {
}
def Building77() : Unit = {
LocalBuilding(77, FoundationBuilder(Building.Structure(StructureType.Platform))) //ground terminal west of HART C
//ground terminal west of HART C
LocalBuilding(77, FoundationBuilder(Building.Structure(StructureType.Platform)))
LocalObject(1063, Terminal.Constructor(ground_vehicle_terminal))
LocalObject(706,
VehicleSpawnPad.Constructor(Vector3(3506.0f, 2820.0f, 92.0f), Vector3(0f, 0f, 270.0f))

View file

@ -12,6 +12,7 @@ import MDCContextAware.Implicits._
import net.psforever.objects.GlobalDefinitions._
import services.ServiceManager.Lookup
import net.psforever.objects._
import net.psforever.objects.definition.ToolDefinition
import net.psforever.objects.definition.converter.CorpseConverter
import net.psforever.objects.equipment._
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
@ -101,8 +102,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
continent.Population ! Zone.Population.Release(avatar)
continent.Population ! Zone.Population.Leave(avatar)
player.Position = Vector3.Zero //save character before doing this
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ObjectDelete(player_guid, player_guid))
taskResolver ! GUIDTask. UnregisterAvatar(player)(continent.GUID)
taskResolver ! GUIDTask.UnregisterAvatar(player)(continent.GUID)
//TODO normally, the actual player avatar persists a minute or so after the user disconnects
}
}
@ -1026,8 +1028,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
tplayer.Position = spawn_tube.Position
tplayer.Orientation = spawn_tube.Orientation
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
val (target, msg) : (ActorRef, Any) = if(sameZone) {
if(backpack) {
//respawning from unregistered player
@ -1052,6 +1052,8 @@ 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
context.system.scheduler.scheduleOnce(respawnTime seconds, target, msg)
case Zone.Lattice.NoValidSpawnPoint(zone_number, None) =>
@ -1103,6 +1105,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
player = tplayer
val guid = tplayer.GUID
sendResponse(SetCurrentAvatarMessage(guid,0,0))
sendResponse(PlayerStateShiftMessage(ShiftState(1, tplayer.Position, tplayer.Orientation.z)))
if(spectator) {
sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, false, "", "on", None))
}
@ -1300,8 +1303,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
player = new Player(avatar)
//player.Position = Vector3(3561.0f, 2854.0f, 90.859375f) //home3, HART C
//player.Orientation = Vector3(0f, 0f, 90f)
player.Position = Vector3(4266.0547f, 4046.4844f, 250.23438f) //z6, Akna.tower
player.Orientation = Vector3(0f, 0f, 320f)
player.Position = Vector3(4262.211f ,4067.0625f ,262.35938f) //z6, Akna.tower
player.Orientation = Vector3(0f, 0f, 132.1875f)
// player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting
player.Slot(0).Equipment = SimpleItem(remote_electronics_kit) //Tool(GlobalDefinitions.StandardPistol(player.Faction))
player.Slot(2).Equipment = Tool(punisher) //suppressor
@ -1502,11 +1505,17 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.VehicleSeated match {
case None =>
continent.Population ! Zone.Corpse.Add(player) //TODO move back out of this match case when changing below issue
val knife = player.Slot(4).Equipment.get
player.Slot(4).Equipment = None
taskResolver ! RemoveEquipmentFromSlot(player, knife, 4)
TurnPlayerIntoCorpse(player)
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.Release(player, continent))
FriskCorpse(player)
if(!WellLootedCorpse(player)) {
TurnPlayerIntoCorpse(player)
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.Release(player, continent))
}
else { //no items in inventory; leave no corpse
val player_guid = player.GUID
sendResponse(ObjectDeleteMessage(player_guid, 0))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid, 0))
taskResolver ! GUIDTask.UnregisterPlayer(player)(continent.GUID)
}
case Some(_) =>
//TODO we do not want to delete the player if he is seated in a vehicle when releasing
@ -1514,8 +1523,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
val player_guid = player.GUID
sendResponse(ObjectDeleteMessage(player_guid, 0))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid, 0))
self ! PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, 0, true)) //let vehicle try to clean up its fields
taskResolver ! GUIDTask.UnregisterPlayer(player)(continent.GUID)
self ! PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, 0, true)) //let vehicle try to clean up its fields
//sendResponse(ObjectDetachMessage(vehicle_guid, player.GUID, Vector3.Zero, 0, 0, 0))
//sendResponse(PlayerStateShiftMessage(ShiftState(1, Vector3.Zero, 0)))
}
@ -1980,9 +1989,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item_guid))
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player_guid, source_guid, index, item2))
//TODO visible slot verification, in the case of BFR arms
case (_ : Player) =>
case (obj : Player) =>
if(source.VisibleSlots.contains(index)) {
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(source_guid, index, item2))
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(source_guid, index, item2))
}
case _ => ;
//TODO something?
@ -2168,6 +2177,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
obj.AccessingTrunk = None
UnAccessContents(obj)
}
case Some(obj : Player) =>
TryDisposeOfLootedCorpse(obj)
case _ =>;
}
@ -2772,13 +2783,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
)
}
/**
* Before calling `Interstellar.GetWorld` to change zones, perform the following task (which can be a nesting of subtasks).
* @param priorTask the tasks to perform
* @param zoneId the zone to load afterwards
* @return a `TaskResolver.GiveTask` message
*/
def TaskBeforeZoneChange(priorTask : TaskResolver.GiveTask, zoneId : String) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private val localService = galaxy
private val localMsg = InterstellarCluster.GetWorld(zoneId)
override def isComplete : Task.Resolution.Value = Task.Resolution.Success
override def isComplete : Task.Resolution.Value = priorTask.task.isComplete
def Execute(resolver : ActorRef) : Unit = {
localService ! localMsg
@ -3493,6 +3510,31 @@ class WorldSessionActor extends Actor with MDCContextAware {
obj
}
/**
* Remove items from a deceased player that is not expected to be found on a corpse.
* Most all players have their melee slot knife (which can not be un-equipped normally) removed.
* MAX's have their primary weapon in the designated slot removed.
* @param obj the player to be turned into a corpse
*/
def FriskCorpse(obj : Player) : Unit = {
if(obj.isBackpack) {
obj.Slot(4).Equipment match {
case None => ;
case Some(knife) =>
obj.Slot(4).Equipment = None
taskResolver ! RemoveEquipmentFromSlot(obj, knife, 4)
}
obj.Slot(0).Equipment match {
case Some(arms : Tool) =>
if(GlobalDefinitions.isMaxArms(arms.Definition)) {
obj.Slot(0).Equipment = None
taskResolver ! RemoveEquipmentFromSlot(obj, arms, 0)
}
case _ => ;
}
}
}
/**
* Creates a player that has the characteristics of a corpse.
* To the game, that is a backpack (or some pastry, festive graphical modification allowing).
@ -3505,6 +3547,34 @@ class WorldSessionActor extends Actor with MDCContextAware {
)
}
/**
* If the corpse has been well-looted, it has no items in its primary holsters nor any items in its inventory.
* @param obj the corpse
* @return `true`, if the `obj` is actually a corpse and has no objects in its holsters or backpack;
* `false`, otherwise
*/
def WellLootedCorpse(obj : Player) : Boolean = {
obj.isBackpack && obj.Holsters().count(_.Equipment.nonEmpty) == 0 && obj.Inventory.Size == 0
}
/**
* If the corpse has been well-looted, remove it from the ground.
* @param obj the corpse
* @return `true`, if the `obj` is actually a corpse and has no objects in its holsters or backpack;
* `false`, otherwise
*/
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)))
true
}
else {
false
}
}
/**
* Attempt to tranfer to the player's faction-specific sanctuary continent.
* If the server thinks the player is already on his sanctuary continent,

View file

@ -26,7 +26,7 @@ object AvatarAction {
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) extends Action
final case class Release(player : Player, zone : Zone, time : Option[Long] = None) extends Action
final case class Reload(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
final case class WeaponDryFire(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
// final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action

View file

@ -2,11 +2,11 @@
package services.avatar
import akka.actor.{Actor, ActorRef, Props}
import services.avatar.support.UndertakerActor
import services.avatar.support.CorpseRemovalActor
import services.{GenericEventBus, Service}
class AvatarService extends Actor {
private val undertaker : ActorRef = context.actorOf(Props[UndertakerActor], "corpse-removal-agent")
private val undertaker : ActorRef = context.actorOf(Props[CorpseRemovalActor], "corpse-removal-agent")
undertaker ! "startup"
private [this] val log = org.log4s.getLogger
@ -90,8 +90,11 @@ class AvatarService extends Actor {
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarResponse.PlayerState(msg, spectator, weapon))
)
case AvatarAction.Release(player, zone) =>
undertaker ! UndertakerActor.AddCorpse(player, zone)
case AvatarAction.Release(player, zone, time) =>
undertaker ! (time match {
case Some(t) => CorpseRemovalActor.AddCorpse(player, zone, t)
case None => CorpseRemovalActor.AddCorpse(player, zone)
})
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player.GUID, AvatarResponse.Release(player))
)
@ -103,9 +106,14 @@ class AvatarService extends Actor {
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.WeaponDryFire(weapon_guid))
)
case _ => ;
}
//message to Undertaker
case AvatarServiceMessage.RemoveSpecificCorpse(corpses) =>
undertaker ! AvatarServiceMessage.RemoveSpecificCorpse( corpses.filter(corpse => {corpse.HasGUID && corpse.isBackpack}) )
/*
case AvatarService.PlayerStateMessage(msg) =>
// log.info(s"NEW: ${m}")

View file

@ -1,4 +1,10 @@
// 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])
}

View file

@ -0,0 +1,199 @@
// 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 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()
private var taskResolver : ActorRef = Actor.noSender
private[this] val log = org.log4s.getLogger
override def postStop() = {
//Cart Master: See you on Thursday.
corpses.foreach { BurialTask }
corpses = Nil
}
def receive : Receive = {
case "startup" =>
ServiceManager.serviceManager ! Lookup("taskResolver") //ask for a resolver to deal with the GUID system
case ServiceManager.LookupResult("taskResolver", endpoint) =>
//Cart Master: Bring out your dead!
taskResolver = endpoint
context.become(Processing)
case _ => ;
}
def Processing : Receive = {
case CorpseRemovalActor.AddCorpse(corpse, zone, time) =>
if(corpse.isBackpack) {
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
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")
}
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) =>
BurialTask(corpses(index))
corpses = corpses.take(index) ++ corpses.drop(index+1)
}
}
else {
log.debug(s"multiple target corpses submitted for early cleanup: $targets")
//cumbersome partition
//a - find targets from corpses
(for {
a <- targets
b <- corpses
if b.corpse == a &&
b.corpse.Continent.equals(a.Continent) &&
b.corpse.HasGUID && a.HasGUID && b.corpse.GUID == a.GUID
} yield b).foreach { BurialTask }
//b - corpses after the found targets are
//removed (note: cull any non-GUID entries while at it)
corpses = (for {
a <- targets
b <- corpses
if b.corpse.HasGUID && a.HasGUID &&
(b.corpse != a ||
!b.corpse.Continent.equals(a.Continent) ||
!b.corpse.HasGUID || !a.HasGUID || b.corpse.GUID != a.GUID)
} yield b).sortBy(_.timeAlive)
}
RetimeFirstTask()
}
case CorpseRemovalActor.Dispose() =>
burial.cancel
val now : Long = System.nanoTime
val (buried, rotting) = corpses.partition(entry => { now - entry.time >= entry.timeAlive })
corpses = rotting
buried.foreach { BurialTask }
RetimeFirstTask()
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.Dispose())
}
}
def BurialTask(entry : CorpseRemovalActor.Entry) : Unit = {
//Cart master: Nine pence.
val target = entry.corpse
val zone = entry.zone
target.Position = Vector3.Zero //somewhere it will not disturb anything
entry.zone.Population ! Zone.Corpse.Remove(target)
context.parent ! AvatarServiceMessage(zone.Id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, target.GUID))
taskResolver ! BurialTask(target, zone)
}
def BurialTask(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())
final case class FailureToWork(corpse : Player, zone : Zone, ex : Throwable)
final case class Dispose()
/**
* 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 == player && corpse.Continent.equals(player.Continent) && corpse.GUID == player.GUID) {
Some(index)
}
else {
recursiveFindCorpse(iter, player, index + 1)
}
}
}
}

View file

@ -1,145 +0,0 @@
// 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 services.{Service, ServiceManager}
import services.ServiceManager.Lookup
import services.avatar.{AvatarAction, AvatarServiceMessage}
import scala.annotation.tailrec
import scala.concurrent.duration._
class UndertakerActor extends Actor {
private var burial : Cancellable = DefaultCancellable.obj
private var corpses : List[UndertakerActor.Entry] = List()
private var taskResolver : ActorRef = Actor.noSender
private[this] val log = org.log4s.getLogger("Cart Master")
override def postStop() = {
corpses.foreach { BurialTask }
}
def receive : Receive = {
case "startup" =>
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 _ => ;
}
def Processing : Receive = {
case UndertakerActor.AddCorpse(corpse, zone, time) =>
if(corpse.isBackpack) {
corpses = corpses :+ UndertakerActor.Entry(corpse, zone, time)
if(corpses.size == 1) { //we were the only entry so the event must be started from scratch
import scala.concurrent.ExecutionContext.Implicits.global
burial = context.system.scheduler.scheduleOnce(UndertakerActor.timeout, self, UndertakerActor.Dispose())
}
}
else {
log.warn(s"he's not dead yet - $corpse")
}
case UndertakerActor.Dispose() =>
burial.cancel
val now : Long = System.nanoTime
val (buried, rotting) = PartitionEntries(corpses, now)
corpses = rotting
buried.foreach { BurialTask }
if(rotting.nonEmpty) {
val short_timeout : FiniteDuration = math.max(1, UndertakerActor.timeout_time - (now - rotting.head.time)) nanoseconds
import scala.concurrent.ExecutionContext.Implicits.global
burial = context.system.scheduler.scheduleOnce(short_timeout, self, UndertakerActor.Dispose())
}
case UndertakerActor.FailureToWork(target, zone, ex) =>
log.error(s"$target failed to be properly cleaned up from $zone - $ex")
case _ => ;
}
def BurialTask(entry : UndertakerActor.Entry) : Unit = {
val target = entry.corpse
val zone = entry.zone
entry.zone.Population ! Zone.Corpse.Remove(target)
context.parent ! AvatarServiceMessage(zone.Id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, target.GUID)) //call up to the main event system
taskResolver ! BurialTask(target, zone)
}
def BurialTask(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 = Task.Resolution.Success
def Execute(resolver : ActorRef) : Unit = {
resolver ! scala.util.Success(this)
}
override def onFailure(ex : Throwable): Unit = {
localAnnounce ! UndertakerActor.FailureToWork(localCorpse, localZone, ex)
}
}, List(GUIDTask.UnregisterPlayer(corpse)(zone.GUID))
)
}
private def PartitionEntries(list : List[UndertakerActor.Entry], now : Long) : (List[UndertakerActor.Entry], List[UndertakerActor.Entry]) = {
val n : Int = recursivePartitionEntries(list.iterator, now, UndertakerActor.timeout_time)
(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[UndertakerActor.Entry], now : Long, duration : Long, index : Int = 0) : Int = {
if(!iter.hasNext) {
index
}
else {
val entry = iter.next()
if(now - entry.time >= duration) {
recursivePartitionEntries(iter, now, duration, index + 1)
}
else {
index
}
}
}
}
object UndertakerActor {
/** A `Long` for calculation simplicity */
private final val timeout_time : Long = 180000000000L //3 min (180s)
/** A `FiniteDuration` for `Executor` simplicity */
private final val timeout : FiniteDuration = timeout_time nanoseconds
final case class AddCorpse(corpse : Player, zone : Zone, time : Long = System.nanoTime())
final case class Entry(corpse : Player, zone : Zone, time : Long = System.nanoTime())
final case class FailureToWork(corpse : Player, zone : Zone, ex : Throwable)
final case class Dispose()
//TODO design mass disposal cases
}

View file

@ -7,6 +7,7 @@ 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}
@ -80,6 +81,7 @@ class DeconstructionActor extends Actor {
vehiclesToScrap.foreach(entry => {
val vehicle = entry.vehicle
val zone = entry.zone
vehicle.Position = Vector3.Zero //somewhere it will not disturb anything
entry.zone.Transport ! Zone.DespawnVehicle(vehicle)
context.parent ! DeconstructionActor.DeleteVehicle(vehicle.GUID, zone.Id) //call up to the main event system
context.parent ! VehicleServiceMessage.RevokeActorControl(vehicle) //call up to a sibling manager

View file

@ -1,15 +1,21 @@
// Copyright (c) 2017 PSForever
import akka.actor.Props
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.types.{CharacterGender, ExoSuitType, PlanetSideEmpire, Vector3}
import services.Service
import services.{Service, ServiceManager}
import services.avatar._
import scala.concurrent.duration._
class AvatarService1Test extends ActorTest {
"AvatarService" should {
"construct" in {
system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
assert(true)
}
}
@ -18,7 +24,8 @@ class AvatarService1Test extends ActorTest {
class AvatarService2Test extends ActorTest {
"AvatarService" should {
"subscribe" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
assert(true)
}
@ -27,8 +34,9 @@ class AvatarService2Test extends ActorTest {
class AvatarService3Test extends ActorTest {
"AvatarService" should {
ServiceManager.boot(system)
"subscribe to a specific channel" in {
val service = system.actorOf(Props[AvatarService], "service")
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! Service.Leave()
assert(true)
@ -39,7 +47,8 @@ class AvatarService3Test extends ActorTest {
class AvatarService4Test extends ActorTest {
"AvatarService" should {
"subscribe" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! Service.LeaveAll()
assert(true)
@ -50,7 +59,8 @@ class AvatarService4Test extends ActorTest {
class AvatarService5Test extends ActorTest {
"AvatarService" should {
"pass an unhandled message" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! "hello"
expectNoMsg()
@ -61,7 +71,8 @@ class AvatarService5Test extends ActorTest {
class ArmorChangedTest extends ActorTest {
"AvatarService" should {
"pass ArmorChanged" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.ArmorChanged(PlanetSideGUID(10), ExoSuitType.Reinforced, 0))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.ArmorChanged(ExoSuitType.Reinforced, 0)))
@ -72,7 +83,8 @@ class ArmorChangedTest extends ActorTest {
class ConcealPlayerTest extends ActorTest {
"AvatarService" should {
"pass ConcealPlayer" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.ConcealPlayer(PlanetSideGUID(10)))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.ConcealPlayer()))
@ -85,7 +97,8 @@ class EquipmentInHandTest extends ActorTest {
"AvatarService" should {
"pass EquipmentInHand" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.EquipmentInHand(PlanetSideGUID(10), 2, tool))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.EquipmentInHand(2, tool)))
@ -101,7 +114,8 @@ class EquipmentOnGroundTest extends ActorTest {
"AvatarService" should {
"pass EquipmentOnGround" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
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)))
@ -117,7 +131,8 @@ class LoadPlayerTest extends ActorTest {
"AvatarService" should {
"pass LoadPlayer" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.LoadPlayer(PlanetSideGUID(10), pdata))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.LoadPlayer(pdata)))
@ -128,7 +143,8 @@ class LoadPlayerTest extends ActorTest {
class ObjectDeleteTest extends ActorTest {
"AvatarService" should {
"pass ObjectDelete" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.ObjectDelete(PlanetSideGUID(10), PlanetSideGUID(11)))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.ObjectDelete(PlanetSideGUID(11), 0)))
@ -142,7 +158,8 @@ class ObjectDeleteTest extends ActorTest {
class ObjectHeldTest extends ActorTest {
"AvatarService" should {
"pass ObjectHeld" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.ObjectHeld(PlanetSideGUID(10), 1))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.ObjectHeld(1)))
@ -153,7 +170,8 @@ class ObjectHeldTest extends ActorTest {
class PlanetsideAttributeTest extends ActorTest {
"AvatarService" should {
"pass PlanetsideAttribute" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.PlanetsideAttribute(PlanetSideGUID(10), 5, 1200L))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.PlanetsideAttribute(5, 1200L)))
@ -166,7 +184,8 @@ class PlayerStateTest extends ActorTest {
"AvatarService" should {
"pass PlayerState" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.PlayerState(PlanetSideGUID(10), msg, false, false))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.PlayerState(msg, false, false)))
@ -177,7 +196,8 @@ class PlayerStateTest extends ActorTest {
class ReloadTest extends ActorTest {
"AvatarService" should {
"pass Reload" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.Reload(PlanetSideGUID(10), PlanetSideGUID(40)))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.Reload(PlanetSideGUID(40))))
@ -191,7 +211,8 @@ class ChangeAmmoTest extends ActorTest {
"AvatarService" should {
"pass ChangeAmmo" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.ChangeAmmo(PlanetSideGUID(10), PlanetSideGUID(40), 0, PlanetSideGUID(40), ammoDef.ObjectId, PlanetSideGUID(41), ammoDef.Packet.ConstructorData(ammoBox).get))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.ChangeAmmo(PlanetSideGUID(40), 0, PlanetSideGUID(40), ammoDef.ObjectId, PlanetSideGUID(41), ammoDef.Packet.ConstructorData(ammoBox).get)))
@ -205,7 +226,8 @@ class ChangeFireModeTest extends ActorTest {
"AvatarService" should {
"pass ChangeFireMode" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.ChangeFireMode(PlanetSideGUID(10), PlanetSideGUID(40), 0))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.ChangeFireMode(PlanetSideGUID(40), 0)))
@ -216,7 +238,8 @@ class ChangeFireModeTest extends ActorTest {
class ChangeFireStateStartTest extends ActorTest {
"AvatarService" should {
"pass ChangeFireState_Start" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.ChangeFireState_Start(PlanetSideGUID(10), PlanetSideGUID(40)))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.ChangeFireState_Start(PlanetSideGUID(40))))
@ -227,7 +250,8 @@ class ChangeFireStateStartTest extends ActorTest {
class ChangeFireStateStopTest extends ActorTest {
"AvatarService" should {
"pass ChangeFireState_Stop" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.ChangeFireState_Stop(PlanetSideGUID(10), PlanetSideGUID(40)))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.ChangeFireState_Stop(PlanetSideGUID(40))))
@ -238,7 +262,8 @@ class ChangeFireStateStopTest extends ActorTest {
class WeaponDryFireTest extends ActorTest {
"AvatarService" should {
"pass WeaponDryFire" in {
val service = system.actorOf(Props[AvatarService], "service")
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.WeaponDryFire(PlanetSideGUID(10), PlanetSideGUID(40)))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.WeaponDryFire(PlanetSideGUID(40))))
@ -246,6 +271,177 @@ class WeaponDryFireTest extends ActorTest {
}
}
object AvatarServiceTest {
//decoy
/*
Preparation for these three Release tests is involved.
The ServiceManager must not only be set up correctly, but must be given a TaskResolver.
The AvatarService is started and that starts CorpseRemovalActor, an essential part of this test.
The CorpseRemovalActor needs that TaskResolver created by the ServiceManager;
but, another independent TaskResolver will be needed for manual parts of the test.
(The ServiceManager's TaskResolver can be "borrowed" but that requires writing code to intercept it.)
The Zone needs to be set up and initialized properly with a ZoneActor.
The ZoneActor builds the GUID Actor and the ZonePopulationActor.
ALL of these Actors will talk to each other.
The lines of communication can short circuit if the next Actor does not have the correct information.
Putting Actor startup in the main class, outside of the body of the test, helps.
Frequent pauses to allow everything to sort their messages also helps.
Even with all this work, the tests have a high chance of failure just due to being asynchronous.
*/
class AvatarReleaseTest 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], "release-test-resolver")
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "release-test-zone")
zone.Actor ! Zone.Init()
val obj = Player(Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, 1))
obj.Continent = "test"
obj.Release
"AvatarService" should {
"pass Release" in {
expectNoMsg(100 milliseconds) //spacer
service ! Service.Join("test")
taskResolver ! GUIDTask.RegisterObjectTask(obj)(zone.GUID)
assert(zone.Corpses.isEmpty)
zone.Population ! Zone.Corpse.Add(obj)
expectNoMsg(100 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
val reply1 = receiveOne(100 milliseconds)
assert(reply1.isInstanceOf[AvatarServiceResponse])
val reply1msg = reply1.asInstanceOf[AvatarServiceResponse]
assert(reply1msg.toChannel == "/test/Avatar")
assert(reply1msg.avatar_guid == guid)
assert(reply1msg.replyMessage.isInstanceOf[AvatarResponse.Release])
assert(reply1msg.replyMessage.asInstanceOf[AvatarResponse.Release].player == obj)
val reply2 = receiveOne(2 seconds)
assert(reply2.isInstanceOf[AvatarServiceResponse])
val reply2msg = reply2.asInstanceOf[AvatarServiceResponse]
assert(reply2msg.toChannel.equals("/test/Avatar"))
assert(reply2msg.avatar_guid == Service.defaultPlayerGUID)
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
expectNoMsg(200 milliseconds)
assert(zone.Corpses.isEmpty)
assert(!obj.HasGUID)
}
}
}
class AvatarReleaseEarly1Test 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], "release-test-resolver")
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "release-test-zone")
zone.Actor ! Zone.Init()
val obj = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, 1))
obj.Continent = "test"
obj.Release
"AvatarService" should {
"pass Release" in {
expectNoMsg(100 milliseconds) //spacer
service ! Service.Join("test")
taskResolver ! GUIDTask.RegisterObjectTask(obj)(zone.GUID)
assert(zone.Corpses.isEmpty)
zone.Population ! Zone.Corpse.Add(obj)
expectNoMsg(100 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)
assert(reply1.isInstanceOf[AvatarServiceResponse])
val reply1msg = reply1.asInstanceOf[AvatarServiceResponse]
assert(reply1msg.toChannel == "/test/Avatar")
assert(reply1msg.avatar_guid == guid)
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)
assert(reply2.isInstanceOf[AvatarServiceResponse])
val reply2msg = reply2.asInstanceOf[AvatarServiceResponse]
assert(reply2msg.toChannel.equals("/test/Avatar"))
assert(reply2msg.avatar_guid == Service.defaultPlayerGUID)
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
expectNoMsg(200 milliseconds)
assert(zone.Corpses.isEmpty)
assert(!obj.HasGUID)
}
}
}
class AvatarReleaseEarly2Test 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], "release-test-resolver")
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "release-test-zone")
zone.Actor ! Zone.Init()
val objAlt = Player(Avatar("TestCharacter2", PlanetSideEmpire.NC, CharacterGender.Male, 1, 1)) //necessary clutter
val obj = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, 1))
obj.Continent = "test"
obj.Release
"AvatarService" should {
"pass Release" in {
expectNoMsg(100 milliseconds) //spacer
service ! Service.Join("test")
taskResolver ! GUIDTask.RegisterObjectTask(obj)(zone.GUID)
assert(zone.Corpses.isEmpty)
zone.Population ! Zone.Corpse.Add(obj)
expectNoMsg(100 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)
assert(reply1.isInstanceOf[AvatarServiceResponse])
val reply1msg = reply1.asInstanceOf[AvatarServiceResponse]
assert(reply1msg.toChannel == "/test/Avatar")
assert(reply1msg.avatar_guid == guid)
assert(reply1msg.replyMessage.isInstanceOf[AvatarResponse.Release])
assert(reply1msg.replyMessage.asInstanceOf[AvatarResponse.Release].player == obj)
service ! AvatarServiceMessage.RemoveSpecificCorpse(List(objAlt, obj)) //IMPORTANT: TWO ENTRIES
val reply2 = receiveOne(100 milliseconds)
assert(reply2.isInstanceOf[AvatarServiceResponse])
val reply2msg = reply2.asInstanceOf[AvatarServiceResponse]
assert(reply2msg.toChannel.equals("/test/Avatar"))
assert(reply2msg.avatar_guid == Service.defaultPlayerGUID)
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
expectNoMsg(200 milliseconds)
assert(zone.Corpses.isEmpty)
assert(!obj.HasGUID)
}
}
}
object AvatarServiceTest {
import java.util.concurrent.atomic.AtomicInteger
private val number = new AtomicInteger(1)
def TestName : String = {
s"service${number.getAndIncrement()}"
}
}