From 001f9a40e9ad0519024fd900b734bb8e2b80bd72 Mon Sep 17 00:00:00 2001 From: FateJH Date: Sat, 10 Mar 2018 21:18:27 -0500 Subject: [PATCH 1/9] Anguta, Ceryshen, the Anguta Watchower, and Anguta's two bunkers have had their amenities wired together, excluding the door locks being connected to doors. Modified how things like spectator status, fly status and speed persist. --- .../psforever/objects/GlobalDefinitions.scala | 4 +- .../scala/net/psforever/objects/Player.scala | 8 - .../serverobject/ServerObjectBuilder.scala | 8 +- .../serverobject/structures/Building.scala | 18 +- .../terminals/SpawnTerminalDefinition.scala | 15 + .../objects/serverobject/tube/SpawnTube.scala | 38 +- .../net/psforever/objects/zones/Zone.scala | 15 +- .../psforever/objects/zones/ZoneActor.scala | 10 +- .../net/psforever/objects/zones/ZoneMap.scala | 13 +- .../src/test/scala/objects/PlayerTest.scala | 9 - pslogin/src/main/scala/Maps.scala | 399 ++++++++++++++---- .../src/main/scala/WorldSessionActor.scala | 121 ++++-- pslogin/src/main/scala/Zones.scala | 7 +- 13 files changed, 490 insertions(+), 175 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/SpawnTerminalDefinition.scala diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 14c8fbfe6..fd0f8f878 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -509,6 +509,8 @@ object GlobalDefinitions { val vehicle_terminal_combined = new VehicleTerminalCombinedDefinition + val spawn_terminal = new SpawnTerminalDefinition + val spawn_pad = new VehicleSpawnPadDefinition val mb_locker = new LockerDefinition @@ -643,7 +645,7 @@ object GlobalDefinitions { * @param faction the faction * @return the `ToolDefinition` for the launcher */ - def AntiVehicular(faction : PlanetSideEmpire.Value) : ToolDefinition = { + def AntiVehicularLauncher(faction : PlanetSideEmpire.Value) : ToolDefinition = { faction match { case PlanetSideEmpire.TR => striker case PlanetSideEmpire.NC => hunterseeker diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index 6da1a64c6..f2a01282c 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -62,7 +62,6 @@ class Player(private val name : String, private var backpackAccess : Option[PlanetSideGUID] = None private var admin : Boolean = false - private var spectator : Boolean = false private var vehicleSeated : Option[PlanetSideGUID] = None private var vehicleOwned : Option[PlanetSideGUID] = None @@ -526,8 +525,6 @@ class Player(private val name : String, def Admin : Boolean = admin - def Spectator : Boolean = spectator - def VehicleSeated : Option[PlanetSideGUID] = vehicleSeated def VehicleSeated_=(guid : PlanetSideGUID) : Option[PlanetSideGUID] = VehicleSeated_=(Some(guid)) @@ -616,11 +613,6 @@ object Player { player } - def Spectate(player : Player, isSpectator : Boolean) : Player = { - player.spectator = isSpectator - player - } - def Release(player : Player) : Player = { if(player.Release) { val obj = new Player(player.Name, player.Faction, player.Sex, player.Voice, player.Head) diff --git a/common/src/main/scala/net/psforever/objects/serverobject/ServerObjectBuilder.scala b/common/src/main/scala/net/psforever/objects/serverobject/ServerObjectBuilder.scala index abc00e160..de9d15c84 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/ServerObjectBuilder.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/ServerObjectBuilder.scala @@ -18,7 +18,7 @@ import net.psforever.objects.guid.NumberPoolHub * a closed number space, which is also the `Zone`. * It utilizes those qualities of the enclosing region to construct the entity within that region.
*
- * Example: `ServerObjectBuilder(n, function)` + * Example: `ServerObjectBuilder(n, function)`
* Example: `new ServerBuilderObject[A](n, function)`, where `function` is a `(Int,Context)=>A` * @see `ZoneMap` * @see `Zone.Init` @@ -28,7 +28,7 @@ import net.psforever.objects.guid.NumberPoolHub * can be inferred from the output of `constructor` */ class ServerObjectBuilder[A <: PlanetSideServerObject](private val id : Int, - private val constructor : (Int, ActorContext) => A + private val constructor : ServerObjectBuilder.ConstructorType[A] ) { /** * Instantiate and configure the given server object. @@ -49,6 +49,8 @@ class ServerObjectBuilder[A <: PlanetSideServerObject](private val id : Int, } object ServerObjectBuilder { + type ConstructorType[A <: PlanetSideServerObject] = (Int, ActorContext)=>A + /** * Overloaded constructor. * @param id the unqiue id that will be assigned to this entity @@ -56,7 +58,7 @@ object ServerObjectBuilder { * @tparam A any object that extends from PlanetSideServerObject that will be produced by this class * @return a `ServerObjectBuilder` object */ - def apply[A <: PlanetSideServerObject](id : Int, constructor : (Int, ActorContext) => A) : ServerObjectBuilder[A] = { + def apply[A <: PlanetSideServerObject](id : Int, constructor : ConstructorType[A]) : ServerObjectBuilder[A] = { new ServerObjectBuilder[A](id, constructor) } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala index 84a14c2c0..7b078dc97 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala @@ -8,12 +8,17 @@ import net.psforever.objects.zones.Zone import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.PlanetSideEmpire -class Building(private val id : Int, private val zone : Zone) extends PlanetSideServerObject { +class Building(private val mapId : Int, private val zone : Zone) extends PlanetSideServerObject { + /** + * The mapId is the identifier number used in BuildingInfoUpdateMessage. + * The modelId is the identifier number used in SetEmpireMessage. + */ + private var modelId : Option[Int] = None private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL private var amenities : List[Amenity] = List.empty GUID = PlanetSideGUID(0) - def Id : Int = id + def Id : Int = mapId def Faction : PlanetSideEmpire.Value = faction @@ -32,6 +37,15 @@ class Building(private val id : Int, private val zone : Zone) extends PlanetSide def Zone : Zone = zone + def ModelId : Int = modelId.getOrElse(Id) + + def ModelId_=(id : Int) : Int = ModelId_=(Some(id)) + + def ModelId_=(id : Option[Int]) : Int = { + modelId = id + ModelId + } + def Definition: ObjectDefinition = Building.BuildingDefinition } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/SpawnTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/SpawnTerminalDefinition.scala new file mode 100644 index 000000000..8483a2c37 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/SpawnTerminalDefinition.scala @@ -0,0 +1,15 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.terminals + +import net.psforever.objects.Player +import net.psforever.packet.game.ItemTransactionMessage + +/** + * The definition for any `Terminal` that is of a type "spawn_terminal." + * A "spawn_terminal" is somewhat like the `matrix_terminalc` of an advanced mobile spawn unit, but inside of facilities. + */ +class SpawnTerminalDefinition extends TerminalDefinition(812) { + Name = "spawn_terminal" + + override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal() +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala index 9d9baac9d..4bc80f06d 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala @@ -13,22 +13,24 @@ object SpawnTube { new SpawnTube(tubeDef) } -// import akka.actor.ActorContext -// import net.psforever.types.Vector3 -// /** -// * Instantiate an configure a `SpawnTube` object -// * @param pos the position (used to determine spawn point) -// * @param orient the orientation (used to indicate spawn direction) -// * @param id the unique id that will be assigned to this entity -// * @param context a context to allow the object to properly set up `ActorSystem` functionality -// * @return the `SpawnTube` object -// */ -// def Constructor(pos : Vector3, orient : Vector3)(id : Int, context : ActorContext) : SpawnTube = { -// import net.psforever.objects.GlobalDefinitions -// -// val obj = SpawnTube(GlobalDefinitions.ams_respawn_tube) -// obj.Position = pos -// obj.Orientation = orient -// obj -// } + import akka.actor.ActorContext + import net.psforever.types.Vector3 + /** + * Instantiate an configure a `SpawnTube` object + * @param pos the position (used to determine spawn point) + * @param orient the orientation (used to indicate spawn direction) + * @param id the unique id that will be assigned to this entity + * @param context a context to allow the object to properly set up `ActorSystem` functionality + * @return the `SpawnTube` object + */ + def Constructor(pos : Vector3, orient : Vector3)(id : Int, context : ActorContext) : SpawnTube = { + import akka.actor.Props + import net.psforever.objects.GlobalDefinitions + + val obj = SpawnTube(GlobalDefinitions.ams_respawn_tube) + obj.Position = pos + obj.Orientation = orient + obj.Actor = context.actorOf(Props(classOf[SpawnTubeControl], obj), s"${GlobalDefinitions.ams_respawn_tube.Name}_$id") + obj + } } diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala index f084443df..859aafa2d 100644 --- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -44,8 +44,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { private var accessor : ActorRef = ActorRef.noSender /** The basic support structure for the globally unique number system used by this `Zone`. */ private var guid : NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(65536)) - guid.AddPool("environment", (0 to 2000).toList) - guid.AddPool("dynamic", (2001 to 10000).toList).Selector = new RandomSelector //TODO unlump pools later; do not make too big + guid.AddPool("environment", (0 to 3000).toList) //TODO tailer ro suit requirements of zone + guid.AddPool("dynamic", (3001 to 10000).toList).Selector = new RandomSelector //TODO unlump pools later; do not make too big /** A synchronized `List` of items (`Equipment`) dropped by players on the ground and can be collected again. */ private val equipmentOnGround : ListBuffer[Equipment] = ListBuffer[Equipment]() /** Used by the `Zone` to coordinate `Equipment` dropping and collection requests. */ @@ -66,7 +66,12 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { * First, the `Actor`-driven aspect of the globally unique identifier system for this `Zone` is finalized. * Second, all supporting `Actor` agents are created, e.g., `ground`. * Third, the `ZoneMap` server objects are loaded and constructed within that aforementioned system. - * To avoid being called more than once, there is a test whether the `accessor` for the globally unique identifier system has been changed. + * To avoid being called more than once, there is a test whether the `accessor` for the globally unique identifier system has been changed.
+ *
+ * Execution of this operation should be fail-safe. + * The chances of failure should be mitigated or skipped. + * An testing routine should be run after the fact on the results of the process. + * @see `ZoneActor.ZoneSetupCheck` * @param context a reference to an `ActorContext` necessary for `Props` */ def Init(implicit context : ActorContext) : Unit = { @@ -76,9 +81,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { ground = context.actorOf(Props(classOf[ZoneGroundActor], equipmentOnGround), s"$Id-ground") transport = context.actorOf(Props(classOf[ZoneVehicleActor], this), s"$Id-vehicles") - Map.LocalObjects.foreach({ builderObject => - builderObject.Build - }) + Map.LocalObjects.foreach({ builderObject => builderObject.Build }) MakeBuildings(context) AssignAmenities() } diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala index af5c06eab..92d36827d 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala @@ -29,9 +29,9 @@ class ZoneActor(zone : Zone) extends Actor { val validateObject : (Int, (PlanetSideGameObject)=>Boolean, String) => Boolean = ValidateObject(guid, slog) //check base to object associations - map.ObjectToBuilding.foreach({ case((object_guid, base_id)) => - if(zone.Building(base_id).isEmpty) { - slog.error(s"expected a building at id #$base_id") + map.ObjectToBuilding.foreach({ case((object_guid, building_id)) => + if(zone.Building(building_id).isEmpty) { + slog.error(s"expected a building at id #$building_id") } if(guid(object_guid).isEmpty) { slog.error(s"expected object id $object_guid to exist, but it did not") @@ -85,8 +85,8 @@ object ZoneActor { } } catch { - case _ : Exception => - elog.error(s"expected a $description at id $object_guid but no object is initialized") + case e : Exception => + elog.error(s"expected a $description at id $object_guid but no object is initialized - $e") false } } diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala index d80192743..51ebeafa4 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala @@ -2,7 +2,7 @@ package net.psforever.objects.zones import net.psforever.objects.serverobject.structures.FoundationBuilder -import net.psforever.objects.serverobject.ServerObjectBuilder +import net.psforever.objects.serverobject.{PlanetSideServerObject, ServerObjectBuilder} /** * The fixed instantiation and relation of a series of server objects.
@@ -44,10 +44,15 @@ class ZoneMap(private val name : String) { /** * Append the builder for a server object to the list of builders known to this `ZoneMap`. - * @param obj the builder for a server object + * @param id the unique id that will be assigned to this entity + * @param constructor the logic that initializes the emitted entity + * @return the current number of builders */ - def LocalObject(obj : ServerObjectBuilder[_]) : Unit = { - localObjects = localObjects :+ obj + def LocalObject[A <: PlanetSideServerObject](id : Int, constructor : ServerObjectBuilder.ConstructorType[A]) : Int = { + if(id > 0) { + localObjects = localObjects :+ ServerObjectBuilder[A](id, constructor) + } + localObjects.size } def LocalBuildings : Map[Int, FoundationBuilder] = buildings diff --git a/common/src/test/scala/objects/PlayerTest.scala b/common/src/test/scala/objects/PlayerTest.scala index d082fe2f2..facf231ca 100644 --- a/common/src/test/scala/objects/PlayerTest.scala +++ b/common/src/test/scala/objects/PlayerTest.scala @@ -247,13 +247,4 @@ class PlayerTest extends Specification { Player.Administrate(obj, false) obj.Admin mustEqual false } - - "spectate" in { - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Spectator mustEqual false - Player.Spectate(obj, true) - obj.Spectator mustEqual true - Player.Spectate(obj, false) - obj.Spectator mustEqual false - } } diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala index ab19d2dbb..72a17cc7b 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/pslogin/src/main/scala/Maps.scala @@ -1,7 +1,6 @@ // Copyright (c) 2017 PSForever import net.psforever.objects.zones.ZoneMap import net.psforever.objects.GlobalDefinitions._ -import net.psforever.objects.serverobject.ServerObjectBuilder import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech import net.psforever.objects.serverobject.locks.IFFLock @@ -9,6 +8,7 @@ import net.psforever.objects.serverobject.mblocker.Locker import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, WarpGate} import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.types.Vector3 object Maps { @@ -23,16 +23,247 @@ object Maps { val map5 = new ZoneMap("map05") val map6 = new ZoneMap("map06") { - //TODO TEST ceryshen - LocalObject(ServerObjectBuilder(3353, Terminal.Constructor(ground_vehicle_terminal))) - LocalObject(ServerObjectBuilder(500, + LocalBuilding(2, FoundationBuilder(Building.Structure)) //Anguta + LocalObject(222, Door.Constructor) //air term building, bay door + LocalObject(370, Door.Constructor) //courtyard + 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(383, Door.Constructor) //courtyard + LocalObject(384, Door.Constructor) //3rd floor door + LocalObject(385, Door.Constructor) //courtyard + LocalObject(387, Door.Constructor) //2nd level door + LocalObject(391, Door.Constructor) //courtyard + LocalObject(393, Door.Constructor) //air term building, upstairs door + LocalObject(394, Door.Constructor) //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(603, Door.Constructor) + LocalObject(604, Door.Constructor) + LocalObject(605, Door.Constructor) + LocalObject(606, Door.Constructor) + LocalObject(607, Door.Constructor) + LocalObject(610, Door.Constructor) + LocalObject(611, Door.Constructor) + LocalObject(614, Door.Constructor) + LocalObject(619, Door.Constructor) + LocalObject(620, Door.Constructor) //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(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(1177, Locker.Constructor) + LocalObject(1178, Locker.Constructor) + LocalObject(1179, Locker.Constructor) + LocalObject(1180, Locker.Constructor) + LocalObject(1181, Locker.Constructor) + LocalObject(1182, Locker.Constructor) + LocalObject(1183, Locker.Constructor) + LocalObject(1184, Locker.Constructor) + LocalObject(1185, Locker.Constructor) + LocalObject(1186, Locker.Constructor) + LocalObject(1187, Locker.Constructor) + LocalObject(1188, Locker.Constructor) + LocalObject(1564, Terminal.Constructor(order_terminal)) + LocalObject(1568, Terminal.Constructor(order_terminal)) + LocalObject(1569, Terminal.Constructor(order_terminal)) + LocalObject(1570, Terminal.Constructor(order_terminal)) + LocalObject(1571, Terminal.Constructor(order_terminal)) + LocalObject(1576, Terminal.Constructor(order_terminal)) + LocalObject(1577, Terminal.Constructor(order_terminal)) + LocalObject(1578, Terminal.Constructor(order_terminal)) + LocalObject(2145, SpawnTube.Constructor(Vector3(3980.4062f, 4252.7656f, 257.5625f), Vector3(0, 0, 90))) + LocalObject(2146, SpawnTube.Constructor(Vector3(3980.4062f, 4259.992f, 257.5625f), Vector3(0, 0, 90))) + LocalObject(2147, SpawnTube.Constructor(Vector3(3980.4062f, 4267.3047f, 257.5625f), Vector3(0, 0, 90))) + LocalObject(2239, Terminal.Constructor(spawn_terminal)) + LocalObject(2244, Terminal.Constructor(spawn_terminal)) + LocalObject(2245, Terminal.Constructor(spawn_terminal)) + LocalObject(2246, Terminal.Constructor(spawn_terminal)) + LocalObject(2248, Terminal.Constructor(spawn_terminal)) + LocalObject(2250, Terminal.Constructor(spawn_terminal)) + LocalObject(2251, Terminal.Constructor(spawn_terminal)) + LocalObject(2253, Terminal.Constructor(spawn_terminal)) + LocalObject(2254, Terminal.Constructor(spawn_terminal)) + LocalObject(2322, Door.Constructor) //spawn tube door + LocalObject(2323, Door.Constructor) //spawn tube door + 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)) - )) //TODO guid not correct - - LocalBuilding(2, FoundationBuilder(Building.Structure)) - ObjectToBuilding(3353, 2) + ) //TODO guid not correct + LocalObject(224, Terminal.Constructor(dropship_vehicle_terminal)) + LocalObject(501, + VehicleSpawnPad.Constructor(Vector3(4012.3594f, 4364.8047f, 271.90625f), Vector3(0f, 0f, 180.0f)) + ) //TODO guid not correct + ObjectToBuilding(222, 2) + ObjectToBuilding(224, 2) + ObjectToBuilding(370, 2) + ObjectToBuilding(371, 2) + ObjectToBuilding(372, 2) + ObjectToBuilding(373, 2) + ObjectToBuilding(375, 2) + ObjectToBuilding(376, 2) + ObjectToBuilding(383, 2) + ObjectToBuilding(384, 2) + ObjectToBuilding(385, 2) + ObjectToBuilding(387, 2) + ObjectToBuilding(391, 2) + ObjectToBuilding(393, 2) + ObjectToBuilding(394, 2) + ObjectToBuilding(396, 2) + ObjectToBuilding(398, 2) + ObjectToBuilding(399, 2) + ObjectToBuilding(402, 2) + ObjectToBuilding(403, 2) + ObjectToBuilding(404, 2) + ObjectToBuilding(603, 2) + ObjectToBuilding(604, 2) + ObjectToBuilding(605, 2) + ObjectToBuilding(606, 2) + ObjectToBuilding(607, 2) + ObjectToBuilding(610, 2) + ObjectToBuilding(611, 2) + ObjectToBuilding(614, 2) + ObjectToBuilding(619, 2) + ObjectToBuilding(620, 2) + ObjectToBuilding(621, 2) + ObjectToBuilding(622, 2) + ObjectToBuilding(623, 2) + ObjectToBuilding(630, 2) + ObjectToBuilding(631, 2) + ObjectToBuilding(634, 2) + ObjectToBuilding(638, 2) + ObjectToBuilding(642, 2) + ObjectToBuilding(643, 2) + ObjectToBuilding(645, 2) + ObjectToBuilding(646, 2) + ObjectToBuilding(715, 2) + ObjectToBuilding(1177, 2) + ObjectToBuilding(1178, 2) + ObjectToBuilding(1179, 2) + ObjectToBuilding(1180, 2) + ObjectToBuilding(1181, 2) + ObjectToBuilding(1182, 2) + ObjectToBuilding(1183, 2) + ObjectToBuilding(1184, 2) + ObjectToBuilding(1185, 2) + ObjectToBuilding(1186, 2) + ObjectToBuilding(1187, 2) + ObjectToBuilding(1188, 2) + ObjectToBuilding(1564, 2) + ObjectToBuilding(1568, 2) + ObjectToBuilding(1569, 2) + ObjectToBuilding(1570, 2) + ObjectToBuilding(1571, 2) + ObjectToBuilding(1576, 2) + ObjectToBuilding(1577, 2) + ObjectToBuilding(1578, 2) + ObjectToBuilding(2145, 2) + ObjectToBuilding(2146, 2) + ObjectToBuilding(2147, 2) + ObjectToBuilding(2239, 2) + ObjectToBuilding(2244, 2) + ObjectToBuilding(2245, 2) + ObjectToBuilding(2246, 2) + ObjectToBuilding(2248, 2) + ObjectToBuilding(2250, 2) + ObjectToBuilding(2251, 2) + ObjectToBuilding(2253, 2) + ObjectToBuilding(2254, 2) + ObjectToBuilding(2322, 2) + ObjectToBuilding(2323, 2) + ObjectToBuilding(2324, 2) + ObjectToBuilding(2419, 2) ObjectToBuilding(500, 2) - TerminalToSpawnPad(3353, 500) + ObjectToBuilding(501, 2) + TerminalToSpawnPad(224, 501) + TerminalToSpawnPad(2419, 500) + + LocalBuilding(38, FoundationBuilder(Building.Structure)) //Anguta, West Bunker + LocalObject(362, Door.Constructor) + ObjectToBuilding(362, 38) + + LocalBuilding(42, FoundationBuilder(Building.Structure)) //Anguta, East Bunker(s) + LocalObject(407, Door.Constructor) + LocalObject(408, Door.Constructor) + ObjectToBuilding(407, 42) + ObjectToBuilding(408, 42) + + LocalBuilding(48, FoundationBuilder(Building.Structure)) //North Anguta Watchtower + LocalObject(364, Door.Constructor(Vector3(3871.9688f, 4509.992f, 269.65625f), Vector3(0f, 0f, 180f))) //s1 + LocalObject(365, Door.Constructor(Vector3(3871.9688f, 4509.992f, 279.57812f), Vector3(0f, 0f, 180f))) //s2 + LocalObject(366, Door.Constructor(Vector3(3871.9688f, 4509.992f, 299.57812f), Vector3(0f, 0f, 180f))) //s3 + LocalObject(367, Door.Constructor(Vector3(3871.9688f, 4525.9844f, 269.65625f), Vector3(0f, 0f, 0f))) //n1 + LocalObject(368, Door.Constructor(Vector3(3871.9688f, 4525.9844f, 279.57812f), Vector3(0f, 0f, 0f))) //n2 + LocalObject(369, Door.Constructor(Vector3(3871.9688f, 4525.9844f, 299.57812f), Vector3(0f, 0f, 0f))) //n3 + LocalObject(854, IFFLock.Constructor) + LocalObject(855, IFFLock.Constructor) + LocalObject(856, IFFLock.Constructor) + LocalObject(857, IFFLock.Constructor) + LocalObject(858, IFFLock.Constructor) + LocalObject(859, IFFLock.Constructor) + LocalObject(1140, Locker.Constructor) + LocalObject(1141, Locker.Constructor) + LocalObject(1142, Locker.Constructor) + LocalObject(1143, Locker.Constructor) + LocalObject(1144, Locker.Constructor) + LocalObject(1145, Locker.Constructor) + LocalObject(1146, Locker.Constructor) + LocalObject(1147, Locker.Constructor) + LocalObject(1561, Terminal.Constructor(order_terminal)) + LocalObject(1562, Terminal.Constructor(order_terminal)) + LocalObject(1563, Terminal.Constructor(order_terminal)) + LocalObject(2138, SpawnTube.Constructor(Vector3(3870.9688f, 4505.7266f, 259.875f), Vector3(0, 0, 90))) + LocalObject(2139, SpawnTube.Constructor(Vector3(3870.9688f, 4522.1562f, 259.875f), Vector3(0, 0, 90))) + LocalObject(2315, Door.Constructor) //spawn tube door + LocalObject(2316, Door.Constructor) //spawn tube door + ObjectToBuilding(364, 48) + ObjectToBuilding(365, 48) + ObjectToBuilding(366, 48) + ObjectToBuilding(367, 48) + ObjectToBuilding(368, 48) + ObjectToBuilding(369, 48) + ObjectToBuilding(854, 48) + ObjectToBuilding(855, 48) + ObjectToBuilding(856, 48) + ObjectToBuilding(857, 48) + ObjectToBuilding(858, 48) + ObjectToBuilding(859, 48) + ObjectToBuilding(1140, 48) + ObjectToBuilding(1141, 48) + ObjectToBuilding(1142, 48) + ObjectToBuilding(1143, 48) + ObjectToBuilding(1144, 48) + ObjectToBuilding(1145, 48) + ObjectToBuilding(1146, 48) + ObjectToBuilding(1147, 48) + ObjectToBuilding(1561, 48) + ObjectToBuilding(1562, 48) + ObjectToBuilding(1563, 48) + ObjectToBuilding(2138, 48) + ObjectToBuilding(2139, 48) + ObjectToBuilding(2315, 48) + ObjectToBuilding(2316, 48) + DoorToLock(364, 857) + DoorToLock(365, 858) + DoorToLock(366, 859) + DoorToLock(367, 854) + DoorToLock(368, 855) + DoorToLock(369, 856) } val map7 = new ZoneMap("map07") @@ -52,63 +283,69 @@ object Maps { LocalBuilding(2, FoundationBuilder(WarpGate.Structure)) LocalBuilding(3, FoundationBuilder(WarpGate.Structure)) - LocalObject(ServerObjectBuilder(372, Door.Constructor)) - LocalObject(ServerObjectBuilder(373, Door.Constructor)) - - LocalObject(ServerObjectBuilder(520, ImplantTerminalMech.Constructor)) //Hart B - LocalObject(ServerObjectBuilder(853, Terminal.Constructor(order_terminal))) - LocalObject(ServerObjectBuilder(855, Terminal.Constructor(order_terminal))) - LocalObject(ServerObjectBuilder(860, Terminal.Constructor(order_terminal))) - LocalObject(ServerObjectBuilder(1081, Terminal.Constructor(implant_terminal_interface))) //tube 520 + LocalObject(520, ImplantTerminalMech.Constructor) //Hart B + LocalObject(1081, Terminal.Constructor(implant_terminal_interface)) //tube 520 TerminalToInterface(520, 1081) LocalBuilding(2, FoundationBuilder(Building.Structure)) //HART building C - LocalObject(ServerObjectBuilder(186, Terminal.Constructor(cert_terminal))) - LocalObject(ServerObjectBuilder(187, Terminal.Constructor(cert_terminal))) - LocalObject(ServerObjectBuilder(188, Terminal.Constructor(cert_terminal))) - LocalObject(ServerObjectBuilder(362, Door.Constructor)) - LocalObject(ServerObjectBuilder(370, Door.Constructor)) - LocalObject(ServerObjectBuilder(371, Door.Constructor)) - LocalObject(ServerObjectBuilder(374, Door.Constructor)) - LocalObject(ServerObjectBuilder(375, Door.Constructor)) - LocalObject(ServerObjectBuilder(394, Door.Constructor)) - LocalObject(ServerObjectBuilder(395, Door.Constructor)) - LocalObject(ServerObjectBuilder(396, Door.Constructor)) - LocalObject(ServerObjectBuilder(397, Door.Constructor)) - LocalObject(ServerObjectBuilder(398, Door.Constructor)) - LocalObject(ServerObjectBuilder(462, Door.Constructor)) - LocalObject(ServerObjectBuilder(463, Door.Constructor)) - LocalObject(ServerObjectBuilder(522, ImplantTerminalMech.Constructor)) //Hart C - LocalObject(ServerObjectBuilder(523, ImplantTerminalMech.Constructor)) //Hart C - LocalObject(ServerObjectBuilder(524, ImplantTerminalMech.Constructor)) //Hart C - LocalObject(ServerObjectBuilder(525, ImplantTerminalMech.Constructor)) //Hart C - LocalObject(ServerObjectBuilder(526, ImplantTerminalMech.Constructor)) //Hart C - LocalObject(ServerObjectBuilder(527, ImplantTerminalMech.Constructor)) //Hart C - LocalObject(ServerObjectBuilder(528, ImplantTerminalMech.Constructor)) //Hart C - LocalObject(ServerObjectBuilder(529, ImplantTerminalMech.Constructor)) //Hart C - LocalObject(ServerObjectBuilder(686, Locker.Constructor)) - LocalObject(ServerObjectBuilder(687, Locker.Constructor)) - LocalObject(ServerObjectBuilder(688, Locker.Constructor)) - LocalObject(ServerObjectBuilder(689, Locker.Constructor)) - LocalObject(ServerObjectBuilder(690, Locker.Constructor)) - LocalObject(ServerObjectBuilder(691, Locker.Constructor)) - LocalObject(ServerObjectBuilder(692, Locker.Constructor)) - LocalObject(ServerObjectBuilder(693, Locker.Constructor)) - LocalObject(ServerObjectBuilder(842, Terminal.Constructor(order_terminal))) - LocalObject(ServerObjectBuilder(843, Terminal.Constructor(order_terminal))) - LocalObject(ServerObjectBuilder(844, Terminal.Constructor(order_terminal))) - LocalObject(ServerObjectBuilder(845, Terminal.Constructor(order_terminal))) - LocalObject(ServerObjectBuilder(1082, Terminal.Constructor(implant_terminal_interface))) //TODO guid not correct - LocalObject(ServerObjectBuilder(1083, Terminal.Constructor(implant_terminal_interface))) //TODO guid not correct - LocalObject(ServerObjectBuilder(1084, Terminal.Constructor(implant_terminal_interface))) //TODO guid not correct - LocalObject(ServerObjectBuilder(1085, Terminal.Constructor(implant_terminal_interface))) //TODO guid not correct - LocalObject(ServerObjectBuilder(1086, Terminal.Constructor(implant_terminal_interface))) //TODO guid not correct - LocalObject(ServerObjectBuilder(1087, Terminal.Constructor(implant_terminal_interface))) //TODO guid not correct - LocalObject(ServerObjectBuilder(1088, Terminal.Constructor(implant_terminal_interface))) //TODO guid not correct - LocalObject(ServerObjectBuilder(1089, Terminal.Constructor(implant_terminal_interface))) //TODO guid not correct + LocalObject(186, Terminal.Constructor(cert_terminal)) + LocalObject(187, Terminal.Constructor(cert_terminal)) + LocalObject(188, Terminal.Constructor(cert_terminal)) + LocalObject(362, Door.Constructor) + LocalObject(370, Door.Constructor) + LocalObject(371, Door.Constructor) + LocalObject(372, Door.Constructor) + LocalObject(373, Door.Constructor) + LocalObject(374, Door.Constructor) + LocalObject(375, Door.Constructor) + LocalObject(394, Door.Constructor) + LocalObject(395, Door.Constructor) + LocalObject(396, Door.Constructor) + LocalObject(397, Door.Constructor) + LocalObject(398, Door.Constructor) + LocalObject(522, ImplantTerminalMech.Constructor) + LocalObject(523, ImplantTerminalMech.Constructor) + LocalObject(524, ImplantTerminalMech.Constructor) + LocalObject(525, ImplantTerminalMech.Constructor) + LocalObject(526, ImplantTerminalMech.Constructor) + LocalObject(527, ImplantTerminalMech.Constructor) + LocalObject(528, ImplantTerminalMech.Constructor) + LocalObject(529, ImplantTerminalMech.Constructor) + LocalObject(686, Locker.Constructor) + LocalObject(687, Locker.Constructor) + LocalObject(688, Locker.Constructor) + LocalObject(689, Locker.Constructor) + LocalObject(690, Locker.Constructor) + LocalObject(691, Locker.Constructor) + LocalObject(692, Locker.Constructor) + LocalObject(693, Locker.Constructor) + LocalObject(842, Terminal.Constructor(order_terminal)) + LocalObject(843, Terminal.Constructor(order_terminal)) + LocalObject(844, Terminal.Constructor(order_terminal)) + LocalObject(845, Terminal.Constructor(order_terminal)) + LocalObject(1082, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct + LocalObject(1083, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct + LocalObject(1084, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct + LocalObject(1085, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct + LocalObject(1086, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct + LocalObject(1087, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct + LocalObject(1088, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct + LocalObject(1089, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct ObjectToBuilding(186, 2) ObjectToBuilding(187, 2) ObjectToBuilding(188, 2) + ObjectToBuilding(362, 2) + ObjectToBuilding(370, 2) + ObjectToBuilding(371, 2) + ObjectToBuilding(372, 2) + ObjectToBuilding(373, 2) + ObjectToBuilding(374, 2) + ObjectToBuilding(375, 2) + ObjectToBuilding(394, 2) + ObjectToBuilding(395, 2) + ObjectToBuilding(396, 2) + ObjectToBuilding(397, 2) + ObjectToBuilding(398, 2) ObjectToBuilding(522, 2) ObjectToBuilding(523, 2) ObjectToBuilding(524, 2) @@ -147,14 +384,14 @@ object Maps { TerminalToInterface(529, 1089) LocalBuilding(29, FoundationBuilder(Building.Structure)) //South Villa Gun Tower - LocalObject(ServerObjectBuilder(330, Door.Constructor(Vector3(3979.9219f, 2592.0547f, 91.140625f), Vector3(0, 0, 180)))) - LocalObject(ServerObjectBuilder(331, Door.Constructor(Vector3(3979.9219f, 2592.0547f, 111.140625f), Vector3(0, 0, 180)))) - LocalObject(ServerObjectBuilder(332, Door.Constructor(Vector3(3979.9688f, 2608.0625f, 91.140625f), Vector3(0, 0, 0)))) - LocalObject(ServerObjectBuilder(333, Door.Constructor(Vector3(3979.9688f, 2608.0625f, 111.140625f), Vector3(0, 0, 0)))) - LocalObject(ServerObjectBuilder(556, IFFLock.Constructor)) - LocalObject(ServerObjectBuilder(557, IFFLock.Constructor)) - LocalObject(ServerObjectBuilder(558, IFFLock.Constructor)) - LocalObject(ServerObjectBuilder(559, IFFLock.Constructor)) + 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))) + LocalObject(333, Door.Constructor(Vector3(3979.9688f, 2608.0625f, 111.140625f), Vector3(0, 0, 0))) + LocalObject(556, IFFLock.Constructor) + LocalObject(557, IFFLock.Constructor) + LocalObject(558, IFFLock.Constructor) + LocalObject(559, IFFLock.Constructor) ObjectToBuilding(330, 29) ObjectToBuilding(331, 29) ObjectToBuilding(332, 29) @@ -169,26 +406,34 @@ object Maps { DoorToLock(333, 557) LocalBuilding(51, FoundationBuilder(Building.Structure)) - LocalObject(ServerObjectBuilder(304, Terminal.Constructor(dropship_vehicle_terminal))) - LocalObject(ServerObjectBuilder(292, + LocalObject(304, Terminal.Constructor(dropship_vehicle_terminal)) + LocalObject(292, VehicleSpawnPad.Constructor(Vector3(3508.9844f, 2895.961f, 92.296875f), Vector3(0f, 0f, 270.0f)) - )) + ) ObjectToBuilding(304, 51) ObjectToBuilding(292, 51) TerminalToSpawnPad(304, 292) LocalBuilding(77, FoundationBuilder(Building.Structure)) - LocalObject(ServerObjectBuilder(1063, Terminal.Constructor(ground_vehicle_terminal))) - LocalObject(ServerObjectBuilder(706, + LocalObject(1063, Terminal.Constructor(ground_vehicle_terminal)) + LocalObject(706, VehicleSpawnPad.Constructor(Vector3(3506.0f, 2820.0f, 92.0f), Vector3(0f, 0f, 270.0f)) - )) + ) ObjectToBuilding(1063, 77) ObjectToBuilding(706, 77) TerminalToSpawnPad(1063, 706) - ObjectToBuilding(853, 2) //TODO check building_id - ObjectToBuilding(855, 2) //TODO check building_id - ObjectToBuilding(860, 2) //TODO check building_id + //TODO check building id: these belong to a spawn building in HART C campus + LocalObject(462, Door.Constructor) + LocalObject(463, Door.Constructor) + LocalObject(853, Terminal.Constructor(order_terminal)) + LocalObject(855, Terminal.Constructor(order_terminal)) + LocalObject(860, Terminal.Constructor(order_terminal)) + ObjectToBuilding(462, 2) + ObjectToBuilding(463, 2) + ObjectToBuilding(853, 2) + ObjectToBuilding(855, 2) + ObjectToBuilding(860, 2) } val map14 = new ZoneMap("map13") diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index cbd3698d0..414057249 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -23,7 +23,7 @@ import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.mblocker.Locker import net.psforever.objects.serverobject.pad.VehicleSpawnPad -import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, Terminal} +import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, SpawnTerminalDefinition, Terminal} import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, VehicleLockState} import net.psforever.objects.serverobject.structures.{Building, WarpGate} @@ -56,6 +56,9 @@ class WorldSessionActor extends Actor with MDCContextAware { var progressBarValue : Option[Float] = None var shooting : Option[PlanetSideGUID] = None var accessedContainer : Option[PlanetSideGameObject with Container] = None + var flying : Boolean = false + var speed : Float = 1.0f + var spectator : Boolean = false var clientKeepAlive : Cancellable = DefaultCancellable.obj var progressBarUpdate : Cancellable = DefaultCancellable.obj @@ -1182,39 +1185,39 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info(s"New world login to $server with Token:$token. $clientVersion") //TODO begin temp player character auto-loading; remove later import net.psforever.objects.GlobalDefinitions._ + import net.psforever.types.CertificationType._ player = Player("TestCharacter"+sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) - //player.Position = Vector3(3674.8438f, 2726.789f, 91.15625f) - //player.Position = Vector3(3523.039f, 2855.5078f, 90.859375f) - player.Position = Vector3(3561.0f, 2854.0f, 90.859375f) + //player.Position = Vector3(3561.0f, 2854.0f, 90.859375f) //home3, HART C + player.Position = Vector3(3881.9688f, 4432.008f, 267.0f) //z6, Anguta / n.tower player.Orientation = Vector3(0f, 0f, 90f) - player.Certifications += CertificationType.StandardAssault - player.Certifications += CertificationType.MediumAssault - player.Certifications += CertificationType.StandardExoSuit - player.Certifications += CertificationType.AgileExoSuit - player.Certifications += CertificationType.ReinforcedExoSuit - player.Certifications += CertificationType.ATV - player.Certifications += CertificationType.Harasser + player.Certifications += StandardAssault + player.Certifications += MediumAssault + player.Certifications += StandardExoSuit + player.Certifications += AgileExoSuit + player.Certifications += ReinforcedExoSuit + player.Certifications += ATV + player.Certifications += Harasser // - player.Certifications += CertificationType.InfiltrationSuit - player.Certifications += CertificationType.Sniping - player.Certifications += CertificationType.AntiVehicular - player.Certifications += CertificationType.HeavyAssault - player.Certifications += CertificationType.SpecialAssault - player.Certifications += CertificationType.EliteAssault - player.Certifications += CertificationType.GroundSupport - player.Certifications += CertificationType.GroundTransport - player.Certifications += CertificationType.Flail - player.Certifications += CertificationType.Switchblade - player.Certifications += CertificationType.AssaultBuggy - player.Certifications += CertificationType.ArmoredAssault1 - player.Certifications += CertificationType.ArmoredAssault2 - player.Certifications += CertificationType.AirCavalryScout - player.Certifications += CertificationType.AirCavalryAssault - player.Certifications += CertificationType.AirCavalryInterceptor - player.Certifications += CertificationType.AirSupport - player.Certifications += CertificationType.GalaxyGunship - player.Certifications += CertificationType.Phantasm - player.Certifications += CertificationType.UniMAX + player.Certifications += InfiltrationSuit + player.Certifications += Sniping + player.Certifications += AntiVehicular + player.Certifications += HeavyAssault + player.Certifications += SpecialAssault + player.Certifications += EliteAssault + player.Certifications += GroundSupport + player.Certifications += GroundTransport + player.Certifications += Flail + player.Certifications += Switchblade + player.Certifications += AssaultBuggy + player.Certifications += ArmoredAssault1 + player.Certifications += ArmoredAssault2 + player.Certifications += AirCavalryScout + player.Certifications += AirCavalryAssault + player.Certifications += AirCavalryInterceptor + player.Certifications += AirSupport + player.Certifications += GalaxyGunship + player.Certifications += Phantasm + player.Certifications += UniMAX AwardBattleExperiencePoints(player, 1000000L) // 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)) @@ -1226,7 +1229,7 @@ class WorldSessionActor extends Actor with MDCContextAware { player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction)) player.Slot(39).Equipment = AmmoBox(plasma_cartridge) //SimpleItem(remote_electronics_kit) - player.Slot(5).Equipment.get.asInstanceOf[LockerContainer].Inventory += 0 -> SimpleItem(remote_electronics_kit) + player.Locker.Inventory += 0 -> SimpleItem(remote_electronics_kit) //TODO end temp player character auto-loading self ! ListAccountCharacters import scala.concurrent.duration._ @@ -1249,7 +1252,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //TODO check if can spawn on last continent/location from player? //TODO if yes, get continent guid accessors //TODO if no, get sanctuary guid accessors and reset the player's expectations - galaxy ! InterstellarCluster.GetWorld("home3") + galaxy ! InterstellarCluster.GetWorld("z6") case default => log.error("Unsupported " + default + " in " + msg) } @@ -1259,12 +1262,12 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ BeginZoningMessage() => log.info("Reticulating splines ...") - //map-specific initializations configZone(continent) //todo density sendResponse(TimeOfDayMessage(1191182336)) //custom sendResponse(ContinentalLockUpdateMessage(13, PlanetSideEmpire.VS)) // "The VS have captured the VS Sanctuary." - (1 to 255).foreach(i => { sendResponse(SetEmpireMessage(PlanetSideGUID(i), PlanetSideEmpire.VS)) }) + sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list + sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1)) //render Equipment that was dropped into zone before the player arrived continent.EquipmentOnGround.foreach(item => { @@ -1355,7 +1358,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(item) => item.Definition == GlobalDefinitions.bolt_driver case None => false } - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, msg, player.Spectator, wepInHand)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, msg, spectator, wepInHand)) case msg @ ChildObjectStateMessage(object_guid, pitch, yaw) => //the majority of the following check retrieves information to determine if we are in control of the child @@ -1425,6 +1428,37 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info("SetChatFilters: " + msg) case msg @ ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents) => + var echoContents : String = contents + //TODO messy on/off strings may work + if(messagetype == ChatMessageType.CMT_FLY) { + if(contents.trim.equals("on")) { + flying = true + } + else if(contents.trim.equals("off")) { + flying = false + } + } + else if(messagetype == ChatMessageType.CMT_SPEED) { + speed = { + try { + contents.trim.toFloat + } + catch { + case _ : Exception => + echoContents = "1.000" + 1f + } + } + } + else if(messagetype == ChatMessageType.CMT_TOGGLESPECTATORMODE) { + if(contents.trim.equals("on")) { + spectator = true + } + else if(contents.trim.equals("off")) { + spectator = false + } + } + // TODO: Prevents log spam, but should be handled correctly if (messagetype != ChatMessageType.CMT_TOGGLE_GM) { log.info("Chat: " + msg) @@ -1449,9 +1483,14 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(DropSession(sessionId, "user quit")) } + if(contents.trim.equals("!loc")) { //dev hack; consider bang-commands to complement slash-commands + echoContents = s"pos=${player.Position.x}, ${player.Position.y}, ${player.Position.z}; ori=${player.Orientation.x}, ${player.Orientation.y}, ${player.Orientation.z}" + log.info(echoContents) + } + // TODO: Depending on messagetype, may need to prepend sender's name to contents with proper spacing // TODO: Just replays the packet straight back to sender; actually needs to be routed to recipients! - sendResponse(ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents)) + sendResponse(ChatMsg(messagetype, has_wide_contents, recipient, echoContents, note_contents)) case msg @ VoiceHostRequest(unk, PlanetSideGUID(player_guid), data) => log.info("Player "+player_guid+" requested in-game voice chat.") @@ -1991,7 +2030,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } case Some(obj : Terminal) => - if(obj.Definition.isInstanceOf[MatrixTerminalDefinition]) { + if(obj.Definition.isInstanceOf[MatrixTerminalDefinition] || obj.Definition.isInstanceOf[SpawnTerminalDefinition]) { //TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks) sendResponse(BindPlayerMessage(1, "@ams", true, true, 0, 0, 0, obj.Position)) } @@ -3161,14 +3200,14 @@ class WorldSessionActor extends Actor with MDCContextAware { } def configZone(zone : Zone) : Unit = { - zone.Buildings.foreach({case (id, building) => - sendResponse(SetEmpireMessage(PlanetSideGUID(id), building.Faction)) + zone.Buildings.values.foreach(building => { + sendResponse(SetEmpireMessage(PlanetSideGUID(building.ModelId), building.Faction)) building.Amenities.foreach(amenity => { val amenityId = amenity.GUID sendResponse(PlanetsideAttributeMessage(amenityId, 50, 0)) sendResponse(PlanetsideAttributeMessage(amenityId, 51, 0)) }) - sendResponse(HackMessage(3, PlanetSideGUID(id), PlanetSideGUID(0), 0, 3212836864L, HackState.HackCleared, 8)) + sendResponse(HackMessage(3, PlanetSideGUID(building.ModelId), PlanetSideGUID(0), 0, 3212836864L, HackState.HackCleared, 8)) }) } diff --git a/pslogin/src/main/scala/Zones.scala b/pslogin/src/main/scala/Zones.scala index 352c27294..051fcf5ad 100644 --- a/pslogin/src/main/scala/Zones.scala +++ b/pslogin/src/main/scala/Zones.scala @@ -19,6 +19,11 @@ object Zones { import net.psforever.types.PlanetSideEmpire Building(2).get.Faction = PlanetSideEmpire.VS + Building(2).get.ModelId = 20 + Building(38).get.ModelId = 0 + Building(42).get.ModelId = 0 + Building(48).get.Faction = PlanetSideEmpire.VS + Building(48).get.ModelId = 59 } } @@ -39,7 +44,7 @@ object Zones { super.Init(context) import net.psforever.types.PlanetSideEmpire - Buildings.values.foreach(building => { building.Faction = PlanetSideEmpire.VS }) + Buildings.values.foreach { _.Faction = PlanetSideEmpire.VS } Building(29).get.Faction = PlanetSideEmpire.NC //South Villa Gun Tower } } From 3e9e3df0fab3a24e5c030b39ffd30f84d50ed89d Mon Sep 17 00:00:00 2001 From: FateJH Date: Sat, 17 Mar 2018 23:52:58 -0400 Subject: [PATCH 2/9] Simple player spawning proof of concept using buildings mapped to their spawn tube amenities in a "spawn group." --- .../scala/net/psforever/objects/Player.scala | 12 +- .../objects/zones/InterstellarCluster.scala | 12 +- .../packet/game/SpawnRequestMessage.scala | 9 +- .../src/main/scala/WorldSessionActor.scala | 208 ++++++++++++------ 4 files changed, 162 insertions(+), 79 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index f2a01282c..505fcd5c9 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -615,7 +615,7 @@ object Player { def Release(player : Player) : Player = { if(player.Release) { - val obj = new Player(player.Name, player.Faction, player.Sex, player.Voice, player.Head) + val obj = new Player(player.Name, player.Faction, player.Sex, player.Head, player.Voice) obj.VehicleOwned = player.VehicleOwned obj.Continent = player.Continent //hand over loadouts @@ -629,11 +629,11 @@ object Player { } }) //hand over knife - obj.Slot(4).Equipment = player.Slot(4).Equipment - player.Slot(4).Equipment = None - //hand over ??? - obj.fifthSlot.Equipment = player.fifthSlot.Equipment - player.fifthSlot.Equipment = None +// obj.Slot(4).Equipment = player.Slot(4).Equipment +// player.Slot(4).Equipment = None + //hand over locker contents +// obj.fifthSlot.Equipment = player.fifthSlot.Equipment +// player.fifthSlot.Equipment = None obj } else { diff --git a/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala b/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala index ddd70cca2..ec5d0d008 100644 --- a/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala +++ b/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala @@ -49,9 +49,9 @@ class InterstellarCluster(zones : List[Zone]) extends Actor { log.error(s"Requested zone $zoneId could not be found") } - case InterstellarCluster.RequestClientInitialization(tplayer) => + case InterstellarCluster.RequestClientInitialization() => zones.foreach(zone => { sender ! Zone.ClientInitialization(zone.ClientInitialization()) }) - sender ! InterstellarCluster.ClientInitializationComplete(tplayer) //will be processed after all Zones + sender ! InterstellarCluster.ClientInitializationComplete() //will be processed after all Zones case _ => ; } @@ -95,17 +95,13 @@ object InterstellarCluster { /** * Signal to the cluster that a new client needs to be initialized for all listed `Zone` destinations. - * @param tplayer the `Player` belonging to the client; - * may be superfluous * @see `Zone` */ - final case class RequestClientInitialization(tplayer : Player) + final case class RequestClientInitialization() /** * Return signal intended to inform the original sender that all `Zone`s have finished being initialized. - * @param tplayer the `Player` belonging to the client; - * may be superfluous * @see `WorldSessionActor` */ - final case class ClientInitializationComplete(tplayer : Player) + final case class ClientInitializationComplete() } diff --git a/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala b/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala index cbb35a959..045aa6d0c 100644 --- a/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala @@ -5,7 +5,14 @@ import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacke import scodec.Codec import scodec.codecs._ - +/** + * + * @param unk1 na + * @param unk2 na + * @param unk3 na + * @param unk4 na + * @param unk5 continent? + */ final case class SpawnRequestMessage(unk1 : Int, unk2 : Long, unk3 : Int, diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 414057249..ce97439fb 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -28,6 +28,7 @@ import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, VehicleLockState} import net.psforever.objects.serverobject.structures.{Building, WarpGate} import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.vehicles.{AccessPermissionGroup, VehicleLockState} import net.psforever.objects.zones.{InterstellarCluster, Zone} import net.psforever.packet.game.objectcreate._ @@ -152,14 +153,15 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(KeepAliveMessage()) case AvatarServiceResponse(_, guid, reply) => + val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(0) } reply match { case AvatarResponse.ArmorChanged(suit, subtype) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ArmorChangedMessage(guid, suit, subtype)) } case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ObjectDetachMessage(weapon_guid, previous_guid, Vector3(0,0,0), 0f, 0f, 0f)) sendResponse( ObjectCreateMessage( @@ -173,27 +175,27 @@ class WorldSessionActor extends Actor with MDCContextAware { } case AvatarResponse.ChangeFireMode(item_guid, mode) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ChangeFireModeMessage(item_guid, mode)) } case AvatarResponse.ChangeFireState_Start(weapon_guid) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ChangeFireStateMessage_Start(weapon_guid)) } case AvatarResponse.ChangeFireState_Stop(weapon_guid) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ChangeFireStateMessage_Stop(weapon_guid)) } case AvatarResponse.ConcealPlayer() => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(GenericObjectActionMessage(guid, 36)) } case AvatarResponse.EquipmentInHand(slot, item) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { val definition = item.Definition sendResponse( ObjectCreateMessage( @@ -206,7 +208,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } case AvatarResponse.EquipmentOnGround(pos, orient, item_id, item_guid, item_data) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse( ObjectCreateMessage( item_id, @@ -217,27 +219,27 @@ class WorldSessionActor extends Actor with MDCContextAware { } case AvatarResponse.LoadPlayer(pdata) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ObjectCreateMessage(ObjectClass.avatar, guid, pdata)) } case AvatarResponse.ObjectDelete(item_guid, unk) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ObjectDeleteMessage(item_guid, unk)) } case AvatarResponse.ObjectHeld(slot) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ObjectHeldMessage(guid, slot, false)) } case AvatarResponse.PlanetsideAttribute(attribute_type, attribute_value) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(PlanetsideAttributeMessage(guid, attribute_type, attribute_value)) } case AvatarResponse.PlayerState(msg, spectating, weaponInHand) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { val now = System.currentTimeMillis() val (location, time, distanceSq) : (Vector3, Long, Float) = if(spectating) { @@ -278,12 +280,12 @@ class WorldSessionActor extends Actor with MDCContextAware { } case AvatarResponse.Reload(item_guid) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ReloadMessage(item_guid, 1, 0)) } case AvatarResponse.WeaponDryFire(weapon_guid) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(WeaponDryFireMessage(weapon_guid)) } @@ -291,9 +293,10 @@ class WorldSessionActor extends Actor with MDCContextAware { } case LocalServiceResponse(_, guid, reply) => + val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(0) } reply match { case LocalResponse.DoorOpens(door_guid) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(GenericObjectStateMsg(door_guid, 16)) } @@ -315,28 +318,29 @@ class WorldSessionActor extends Actor with MDCContextAware { } case VehicleServiceResponse(_, guid, reply) => + val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(0) } reply match { case VehicleResponse.Awareness(vehicle_guid) => //resets exclamation point fte marker (once) sendResponse(PlanetsideAttributeMessage(guid, 21, vehicle_guid.guid.toLong)) case VehicleResponse.ChildObjectState(object_guid, pitch, yaw) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ChildObjectStateMessage(object_guid, pitch, yaw)) } case VehicleResponse.DismountVehicle(unk1, unk2) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(DismountVehicleMsg(guid, unk1, unk2)) } case VehicleResponse.DeployRequest(object_guid, state, unk1, unk2, pos) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(DeployRequestMessage(guid, object_guid, state, unk1, unk2, pos)) } case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? val obj_guid = obj.GUID sendResponse(ObjectDeleteMessage(obj_guid, 0)) @@ -352,7 +356,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case VehicleResponse.KickPassenger(unk1, unk2, vehicle_guid) => sendResponse(DismountVehicleMsg(guid, unk1, unk2)) - if(guid == player.GUID) { + if(tplayer_guid == guid) { continent.GUID(vehicle_guid) match { case Some(obj : Vehicle) => UnAccessContents(obj) @@ -362,23 +366,23 @@ class WorldSessionActor extends Actor with MDCContextAware { case VehicleResponse.LoadVehicle(vehicle, vtype, vguid, vdata) => //this is not be suitable for vehicles with people who are seated in it before it spawns (if that is possible) - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ObjectCreateMessage(vtype, vguid, vdata)) ReloadVehicleAccessPermissions(vehicle) } case VehicleResponse.MountVehicle(vehicle_guid, seat) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ObjectAttachMessage(vehicle_guid, guid, seat)) } case VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(PlanetsideAttributeMessage(vehicle_guid, seat_group, permission)) } case VehicleResponse.StowEquipment(vehicle_guid, slot, item_type, item_guid, item_data) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { //TODO prefer ObjectAttachMessage, but how to force ammo pools to update properly? sendResponse( ObjectCreateDetailedMessage(item_type, item_guid, ObjectCreateMessageParent(vehicle_guid, slot), item_data) @@ -389,13 +393,13 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ObjectDeleteMessage(vehicle_guid, 0)) case VehicleResponse.UnstowEquipment(item_guid) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? sendResponse(ObjectDeleteMessage(item_guid, 0)) } case VehicleResponse.VehicleState(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6)) if(player.VehicleSeated.contains(vehicle_guid)) { player.Position = pos @@ -962,23 +966,6 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0)) - case InterstellarCluster.GiveWorld(zoneId, zone) => - log.info(s"Zone $zoneId has been loaded") - player.Continent = zoneId - continent = zone - taskResolver ! RegisterAvatar(player) - - case PlayerLoaded(tplayer) => - log.info(s"Player $tplayer has been loaded") - //init for whole server - galaxy ! InterstellarCluster.RequestClientInitialization(tplayer) - - case PlayerFailedToLoad(tplayer) => - player.Continent match { - case _ => - failWithError(s"$tplayer failed to load anywhere") - } - case VehicleLoaded(_/*vehicle*/) => ; //currently being handled by VehicleSpawnPad.LoadVehicle during testing phase @@ -1001,23 +988,36 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ZoneForcedCavernConnectionsMessage(continentNumber, 0)) sendResponse(HotSpotUpdateMessage(continentNumber, 1, Nil)) //normally set in bulk; should be fine doing per continent - case InterstellarCluster.ClientInitializationComplete(tplayer)=> + case InterstellarCluster.ClientInitializationComplete() => //PropertyOverrideMessage sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1)) sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list sendResponse(FriendsResponse(FriendAction.InitializeFriendList, 0, true, true, Nil)) sendResponse(FriendsResponse(FriendAction.InitializeIgnoreList, 0, true, true, Nil)) + galaxy ! InterstellarCluster.GetWorld("z6") + case InterstellarCluster.GiveWorld(zoneId, zone) => + log.info(s"Zone $zoneId has been loaded") + player.Continent = zoneId + continent = zone + taskResolver ! RegisterNewAvatar(player) + + case NewPlayerLoaded(tplayer) => + log.info(s"Player $tplayer has been loaded") //LoadMapMessage will cause the client to send back a BeginZoningMessage packet (see below) sendResponse(LoadMapMessage(continent.Map.Name, continent.Id, 40100,25,true,3770441820L)) - log.info("Load the now-registered player") - //load the now-registered player - tplayer.Spawn - tplayer.Health = 50 - val dcdata = tplayer.Definition.Packet.DetailedConstructorData(tplayer).get - sendResponse(ObjectCreateDetailedMessage(ObjectClass.avatar, tplayer.GUID, dcdata)) - avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.LoadPlayer(tplayer.GUID, tplayer.Definition.Packet.ConstructorData(tplayer).get)) - log.debug(s"ObjectCreateDetailedMessage: $dcdata") + AvatarCreate() + + case PlayerLoaded(tplayer) => + log.info(s"Player $tplayer has been loaded") + AvatarCreate() + self ! SetCurrentAvatar(tplayer) + + case PlayerFailedToLoad(tplayer) => + player.Continent match { + case _ => + failWithError(s"$tplayer failed to load anywhere") + } case SetCurrentAvatar(tplayer) => val guid = tplayer.GUID @@ -1178,6 +1178,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } var player : Player = null + var spawnZones : Map[Int, Building] = null def handleGamePkt(pkt : PlanetSideGamePacket) = pkt match { case ConnectToWorldRequestMessage(server, token, majorVersion, minorVersion, revision, buildDate, unk) => @@ -1252,7 +1253,8 @@ class WorldSessionActor extends Actor with MDCContextAware { //TODO check if can spawn on last continent/location from player? //TODO if yes, get continent guid accessors //TODO if no, get sanctuary guid accessors and reset the player's expectations - galaxy ! InterstellarCluster.GetWorld("z6") + //galaxy ! InterstellarCluster.GetWorld("z6") + galaxy ! InterstellarCluster.RequestClientInitialization() case default => log.error("Unsupported " + default + " in " + msg) } @@ -1264,6 +1266,12 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info("Reticulating splines ...") configZone(continent) //todo density sendResponse(TimeOfDayMessage(1191182336)) + /** WIP */ + spawnZones = Map( + 7 -> continent.Building(2).get, + 6 -> continent.Building(48).get + ) + //custom sendResponse(ContinentalLockUpdateMessage(13, PlanetSideEmpire.VS)) // "The VS have captured the VS Sanctuary." sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list @@ -1347,18 +1355,20 @@ class WorldSessionActor extends Actor with MDCContextAware { self ! SetCurrentAvatar(player) case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) => - player.Position = pos - player.Velocity = vel - player.Orientation = Vector3(player.Orientation.x, pitch, yaw) - player.FacingYawUpper = yaw_upper - player.Crouching = is_crouching - player.Jumping = is_jumping + if(player.isAlive && player.GUID == avatar_guid) { + player.Position = pos + player.Velocity = vel + player.Orientation = Vector3(player.Orientation.x, pitch, yaw) + player.FacingYawUpper = yaw_upper + player.Crouching = is_crouching + player.Jumping = is_jumping - val wepInHand : Boolean = player.Slot(player.DrawnSlot).Equipment match { - case Some(item) => item.Definition == GlobalDefinitions.bolt_driver - case None => false + val wepInHand : Boolean = player.Slot(player.DrawnSlot).Equipment match { + case Some(item) => item.Definition == GlobalDefinitions.bolt_driver + case None => false + } + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, msg, spectator, wepInHand)) } - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, msg, spectator, wepInHand)) case msg @ ChildObjectStateMessage(object_guid, pitch, yaw) => //the majority of the following check retrieves information to determine if we are in control of the child @@ -1418,11 +1428,32 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ReleaseAvatarRequestMessage() => log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released") + player.Release sendResponse(PlanetsideAttributeMessage(player.GUID, 6, 1)) sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, 2, true)) + player = Player.Release(player) //swap new player case msg @ SpawnRequestMessage(u1, u2, u3, u4, u5) => log.info(s"SpawnRequestMessage: $msg") + spawnZones.get(u2.toInt) match { + case Some(building) => + scala.util.Random.shuffle(building.Amenities.filter(_.isInstanceOf[SpawnTube])).headOption match { //TODO temporary shuffle + case Some(tube) => + log.info(s"SpawnRequestMessage: new player was at position ${player.Position}") + player.Position = tube.Position + player.Orientation = tube.Orientation + player.FacingYawUpper = 0 + log.info(s"SpawnRequestMessage: new player moved to position ${player.Position}") + sendResponse(AvatarDeadStateMessage(DeadState.RespawnTime, 10000, 10000, Vector3.Zero, 2, true)) + import scala.concurrent.duration._ + import scala.concurrent.ExecutionContext.Implicits.global + context.system.scheduler.scheduleOnce(10 seconds, taskResolver, RegisterAvatar(player)) + case None => + log.warn(s"SpawnRequestMessage: can not find a spawn point in this spawn group - $u2") + } + case None => + log.warn(s"SpawnRequestMessage: can not find somewhere to spawn in ${continent.Id}") + } case msg @ SetChatFilterMessage(send_channel, origin, whitelist) => log.info("SetChatFilters: " + msg) @@ -1467,6 +1498,7 @@ class WorldSessionActor extends Actor with MDCContextAware { if(messagetype == ChatMessageType.CMT_SUICIDE) { val player_guid = player.GUID val pos = player.Position + player.Die sendResponse(PlanetsideAttributeMessage(player_guid, 0, 0)) sendResponse(PlanetsideAttributeMessage(player_guid, 2, 0)) sendResponse(DestroyMessage(player_guid, player_guid, PlanetSideGUID(0), pos)) @@ -2444,6 +2476,40 @@ class WorldSessionActor extends Actor with MDCContextAware { }) } + /** + * Construct tasking that registers all aspects of a `Player` avatar. + * `Players` are complex objects that contain a variety of other register-able objects and each of these objects much be handled. + * @param tplayer the avatar `Player` + * @return a `TaskResolver.GiveTask` message + */ + private def RegisterNewAvatar(tplayer : Player) : TaskResolver.GiveTask = { + TaskResolver.GiveTask( + new Task() { + private val localPlayer = tplayer + private val localAnnounce = self + + override def isComplete : Task.Resolution.Value = { + if(localPlayer.HasGUID) { + Task.Resolution.Success + } + else { + Task.Resolution.Incomplete + } + } + + def Execute(resolver : ActorRef) : Unit = { + log.info(s"Player $localPlayer is registered") + resolver ! scala.util.Success(this) + localAnnounce ! NewPlayerLoaded(localPlayer) //alerts WSA + } + + override def onFailure(ex : Throwable) : Unit = { + localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WSA + } + }, List(GUIDTask.RegisterAvatar(tplayer)(continent.GUID)) + ) + } + /** * Construct tasking that registers all aspects of a `Player` avatar. * `Players` are complex objects that contain a variety of other register-able objects and each of these objects much be handled. @@ -3211,6 +3277,19 @@ class WorldSessionActor extends Actor with MDCContextAware { }) } + /** + * TODO write + */ + def AvatarCreate() : Unit = { + player.Spawn + player.Health = 50 + val packet = player.Definition.Packet + val dcdata = packet.DetailedConstructorData(player).get + sendResponse(ObjectCreateDetailedMessage(ObjectClass.avatar, player.GUID, dcdata)) + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.LoadPlayer(player.GUID, packet.ConstructorData(player).get)) + log.debug(s"ObjectCreateDetailedMessage: $dcdata") + } + def failWithError(error : String) = { log.error(error) sendResponse(ConnectionClose()) @@ -3245,6 +3324,7 @@ object WorldSessionActor { private final case class PokeClient() private final case class ServerLoaded() + private final case class NewPlayerLoaded(tplayer : Player) private final case class PlayerLoaded(tplayer : Player) private final case class PlayerFailedToLoad(tplayer : Player) private final case class ListAccountCharacters() From 8a21df429be05ebdaced92d89c743c068ea2dffe Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 19 Mar 2018 20:13:39 -0400 Subject: [PATCH 3/9] The former Player class (former-former PlaterAvatar class) has been split into a persisting Avatar class and a transitory Player class. --- .../scala/net/psforever/objects/Avatar.scala | 203 +++++++++++++ .../scala/net/psforever/objects/Player.scala | 269 +++--------------- .../converter/AvatarConverter.scala | 28 +- .../objects/zones/InterstellarCluster.scala | 1 - .../test/scala/objects/ConverterTest.scala | 11 +- common/src/test/scala/objects/DoorTest.scala | 6 +- .../src/test/scala/objects/IFFLockTest.scala | 5 +- .../src/test/scala/objects/LoadoutTest.scala | 78 ++--- .../test/scala/objects/MountableTest.scala | 8 +- .../src/test/scala/objects/PlayerTest.scala | 140 +++++---- .../scala/objects/VehicleSpawnPadTest.scala | 4 +- .../src/test/scala/objects/VehicleTest.scala | 40 +-- .../guidtask/GUIDTaskRegister5Test.scala | 2 +- .../guidtask/GUIDTaskUnregister5Test.scala | 2 +- .../terminal/AirVehicleTerminalTest.scala | 4 +- .../objects/terminal/CertTerminalTest.scala | 4 +- .../DropshipVehicleTerminalTest.scala | 4 +- .../terminal/GroundVehicleTerminalTest.scala | 4 +- .../ImplantTerminalInterfaceTest.scala | 4 +- .../terminal/ImplantTerminalMechTest.scala | 8 +- .../objects/terminal/MatrixTerminalTest.scala | 4 +- .../terminal/OrderTerminalABTest.scala | 13 +- .../objects/terminal/OrderTerminalTest.scala | 4 +- .../terminal/TerminalControlTest.scala | 4 +- .../VehicleTerminalCombinedTest.scala | 4 +- .../src/main/scala/WorldSessionActor.scala | 157 ++++++---- .../src/test/scala/AvatarServiceTest.scala | 2 +- 27 files changed, 533 insertions(+), 480 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/Avatar.scala diff --git a/common/src/main/scala/net/psforever/objects/Avatar.scala b/common/src/main/scala/net/psforever/objects/Avatar.scala new file mode 100644 index 000000000..fac940c18 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/Avatar.scala @@ -0,0 +1,203 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects + +import net.psforever.objects.definition.{AvatarDefinition, ImplantDefinition} +import net.psforever.objects.equipment.EquipmentSize +import net.psforever.types.{CertificationType, CharacterGender, ImplantType, PlanetSideEmpire} + +import scala.annotation.tailrec +import scala.collection.mutable + +class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : CharacterGender.Value, val head : Int, val voice : Int) { + /** Battle Experience Points */ + private var bep : Long = 0 + /** Command Experience Points */ + private var cep : Long = 0 + /** Certifications */ + private val certs : mutable.Set[CertificationType.Value] = mutable.Set[CertificationType.Value]() + /** Implants */ + private val implants : Array[ImplantSlot] = Array.fill[ImplantSlot](3)(new ImplantSlot) + /** Loadouts */ + private val loadouts : Array[Option[Loadout]] = Array.fill[Option[Loadout]](10)(None) + /** Locker (fifth inventory slot) */ + private val locker : EquipmentSlot = new OffhandEquipmentSlot(EquipmentSize.Inventory) { + Equipment = new LockerContainer + } + + def BEP : Long = bep + + def BEP_=(battleExperiencePoints : Long) : Long = { + bep = math.max(0L, math.min(battleExperiencePoints, 4294967295L)) + BEP + } + + def Certifications : mutable.Set[CertificationType.Value] = certs + + def CEP : Long = cep + + def CEP_=(commandExperiencePoints : Long) : Long = { + cep = math.max(0L, math.min(commandExperiencePoints, 4294967295L)) + CEP + } + + /** + * Retrieve the three implant slots for this player. + * @return an `Array` of `ImplantSlot` objects + */ + def Implants : Array[ImplantSlot] = implants + + /** + * What kind of implant is installed into the given slot number? + * @see `ImplantType` + * @param slot the slot number + * @return the tye of implant + */ + def Implant(slot : Int) : ImplantType.Value = { + if(-1 < slot && slot < implants.length) { implants(slot).Implant } else { ImplantType.None } + } + + /** + * Given a new implant, assign it into a vacant implant slot on this player.
+ *
+ * The implant must be unique in terms of which implants have already been assigned to this player. + * Multiple of a type of implant being assigned at once is not supported. + * Additionally, the implant is inserted into the earliest yet-unknown but vacant slot. + * Implant slots are vacant by just being unlocked or by having their previous implant uninstalled. + * @param implant the implant being installed + * @return the index of the `ImplantSlot` where the implant was installed + */ + def InstallImplant(implant : ImplantDefinition) : Option[Int] = { + implants.find({p => p.Installed.contains(implant)}) match { //try to find the installed implant + case None => + recursiveFindImplantInSlot(implants.iterator, ImplantType.None) match { //install in a free slot + case Some(slot) => + implants(slot).Implant = implant + Some(slot) + case None => + None + } + case Some(_) => + None + } + } + + /** + * Remove a specific implant from a player's allocated installed implants.
+ *
+ * Due to the exclusiveness of installed implants, + * any implant slot with a matching `Definition` can be uninstalled safely. + * (There will never be any doubles.) + * This operation can lead to an irregular pattern of installed and uninstalled `ImplantSlot` objects. + * Despite that breach of pattern, the logic here is consistent as demonstrated by the client and by packets. + * The client also assigns and removes implants based on slot numbers that only express availability of a "slot." + * @see `AvatarImplantMessage.implantSlot` + * @param implantType the type of implant being uninstalled + * @return the index of the `ImplantSlot` where the implant was found and uninstalled + */ + def UninstallImplant(implantType : ImplantType.Value) : Option[Int] = { + recursiveFindImplantInSlot(implants.iterator, implantType) match { + case Some(slot) => + implants(slot).Implant = None + Some(slot) + case None => + None + } + } + + /** + * Locate the index of the encountered implant type. + * Functional implants may be exclusive in as far as the input `Iterator`'s source is concerned, + * but any number of `ImplantType.None` values are alway allowed in the source in any order. + * @param iter an `Iterator` of `ImplantSlot` objects + * @param implantType the target implant being sought + * @param index a defaulted index value representing the structure underlying the `Iterator` param + * @return the index where the target implant is installed + */ + @tailrec private def recursiveFindImplantInSlot(iter : Iterator[ImplantSlot], implantType : ImplantType.Value, index : Int = 0) : Option[Int] = { + if(!iter.hasNext) { + None + } + else { + val slot = iter.next + if(slot.Unlocked && slot.Implant == implantType) { + Some(index) + } + else { + recursiveFindImplantInSlot(iter, implantType, index + 1) + } + } + } + + def ResetAllImplants() : Unit = { + implants.foreach(slot => { + slot.Installed match { + case Some(_) => + slot.Initialized = false + case None => ; + } + }) + } + + /** + * Unlike other objects, the maximum number of `ImplantSlots` are built into the `Player`. + * Additionally, "implants" do not have tightly-coupled "`Definition` objects" that explain a formal implant object. + * The `ImplantDefinition` objects themselves are moved around as if they were the implants. + * The term internally used for this process is "installed" and "uninstalled." + * @see `ImplantSlot` + * @see `DetailedCharacterData.implants` + * @see `AvatarConverter.MakeImplantEntries` + */ + + def SaveLoadout(owner : Player, label : String, line : Int) : Unit = { + loadouts(line) = Some(Loadout.Create(owner, label)) + } + + def LoadLoadout(line : Int) : Option[Loadout] = loadouts(line) + + def DeleteLoadout(line : Int) : Unit = { + loadouts(line) = None + } + + def Locker : LockerContainer = locker.Equipment.get.asInstanceOf[LockerContainer] + + def FifthSlot : EquipmentSlot = locker + + def Definition : AvatarDefinition = Avatar.definition + + /* + Merit Commendations and Ribbons + */ +// private var tosRibbon : MeritCommendation.Value = MeritCommendation.None +// private var upperRibbon : MeritCommendation.Value = MeritCommendation.None +// private var middleRibbon : MeritCommendation.Value = MeritCommendation.None +// private var lowerRibbon : MeritCommendation.Value = MeritCommendation.None + + def canEqual(other: Any): Boolean = other.isInstanceOf[Avatar] + + override def equals(other : Any) : Boolean = other match { + case that: Avatar => + (that canEqual this) && + name == that.name && + faction == that.faction && + sex == that.sex && + head == that.head && + voice == that.voice + case _ => + false + } + + override def hashCode() : Int = { + val state = Seq(name, faction, sex, head, voice) + state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b) + } + + override def toString: String = s"$faction $name" +} + +object Avatar { + final private val definition : AvatarDefinition = new AvatarDefinition(121) + + def apply(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : Int) : Avatar = { + new Avatar(name, faction, sex, head, voice) + } +} diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index 505fcd5c9..30c328211 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects -import net.psforever.objects.definition.{AvatarDefinition, ImplantDefinition} +import net.psforever.objects.definition.AvatarDefinition import net.psforever.objects.equipment.{Equipment, EquipmentSize} import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} import net.psforever.objects.serverobject.affinity.FactionAffinity @@ -9,15 +9,9 @@ import net.psforever.packet.game.PlanetSideGUID import net.psforever.types._ import scala.annotation.tailrec -import scala.collection.mutable import scala.util.{Success, Try} -class Player(private val name : String, - private val faction : PlanetSideEmpire.Value, - private val sex : CharacterGender.Value, - private val head : Int, - private val voice : Int - ) extends PlanetSideGameObject with FactionAffinity with Container { +class Player(private val core : Avatar) extends PlanetSideGameObject with FactionAffinity with Container { private var alive : Boolean = false private var backpack : Boolean = false private var health : Int = 0 @@ -29,45 +23,20 @@ class Player(private val name : String, private var exosuit : ExoSuitType.Value = ExoSuitType.Standard private val freeHand : EquipmentSlot = new OffhandEquipmentSlot(EquipmentSize.Inventory) private val holsters : Array[EquipmentSlot] = Array.fill[EquipmentSlot](5)(new EquipmentSlot) - private val fifthSlot : EquipmentSlot = new OffhandEquipmentSlot(EquipmentSize.Inventory) private val inventory : GridInventory = GridInventory() private var drawnSlot : Int = Player.HandsDownSlot private var lastDrawnSlot : Int = Player.HandsDownSlot - private val loadouts : Array[Option[Loadout]] = Array.fill[Option[Loadout]](10)(None) - - private var bep : Long = 0 - private var cep : Long = 0 - private val certifications : mutable.Set[CertificationType.Value] = mutable.Set[CertificationType.Value]() - /** - * Unlike other objects, the maximum number of `ImplantSlots` are built into the `Player`. - * Additionally, "implants" do not have tightly-coupled "`Definition` objects" that explain a formal implant object. - * The `ImplantDefinition` objects themselves are moved around as if they were the implants. - * The term internally used for this process is "installed" and "uninstalled." - * @see `ImplantSlot` - * @see `DetailedCharacterData.implants` - * @see `AvatarConverter.MakeImplantEntries` - */ - private val implants : Array[ImplantSlot] = Array.fill[ImplantSlot](3)(new ImplantSlot) - -// private var tosRibbon : MeritCommendation.Value = MeritCommendation.None -// private var upperRibbon : MeritCommendation.Value = MeritCommendation.None -// private var middleRibbon : MeritCommendation.Value = MeritCommendation.None -// private var lowerRibbon : MeritCommendation.Value = MeritCommendation.None - private var facingYawUpper : Float = 0f private var crouching : Boolean = false private var jumping : Boolean = false private var cloaked : Boolean = false private var backpackAccess : Option[PlanetSideGUID] = None - private var admin : Boolean = false - private var vehicleSeated : Option[PlanetSideGUID] = None private var vehicleOwned : Option[PlanetSideGUID] = None private var continent : String = "home2" //the zone id - private val playerDef : AvatarDefinition = Player.definition //TODO could be a var //SouNourS things /** Last medkituse. */ @@ -75,23 +44,20 @@ class Player(private val name : String, var death_by : Int = 0 var lastSeenStreamMessage : Array[Long] = Array.fill[Long](65535)(0L) var lastShotSeq_time : Int = -1 - /** The player is shooting. */ - var shooting : Boolean = false /** From PlanetsideAttributeMessage */ var PlanetsideAttribute : Array[Long] = Array.ofDim(120) Player.SuitSetup(this, ExoSuit) - fifthSlot.Equipment = new LockerContainer //the fifth slot is the player's "locker" - def Name : String = name + def Name : String = core.name - def Faction : PlanetSideEmpire.Value = faction + def Faction : PlanetSideEmpire.Value = core.faction - def Sex : CharacterGender.Value = sex + def Sex : CharacterGender.Value = core.sex - def Voice : Int = voice + def Head : Int = core.head - def Head : Int = head + def Voice : Int = core.voice def isAlive : Boolean = alive @@ -172,9 +138,7 @@ class Player(private val name : String, holsters(slot) } else if(slot == 5) { - new OffhandEquipmentSlot(EquipmentSize.Inventory) { - Equipment = fifthSlot.Equipment - } + core.FifthSlot } else if(slot == Player.FreeHandSlot) { freeHand @@ -188,7 +152,7 @@ class Player(private val name : String, def Inventory : GridInventory = inventory - def Locker : LockerContainer = fifthSlot.Equipment.get.asInstanceOf[LockerContainer] + def Locker : LockerContainer = core.Locker def Fit(obj : Equipment) : Option[Int] = { recursiveHolsterFit(holsters.iterator, obj.Size) match { @@ -219,20 +183,6 @@ class Player(private val name : String, } } - def Equip(slot : Int, obj : Equipment) : Boolean = { - if(-1 < slot && slot < 5) { - holsters(slot).Equipment = obj - true - } - else if(slot == Player.FreeHandSlot) { - freeHand.Equipment = obj - true - } - else { - inventory += slot -> obj - } - } - def FreeHand = freeHand def FreeHand_=(item : Option[Equipment]) : Option[Equipment] = { @@ -242,33 +192,19 @@ class Player(private val name : String, FreeHand.Equipment } - def SaveLoadout(label : String, line : Int) : Unit = { - loadouts(line) = Some(Loadout.Create(this, label)) - } - - def LoadLoadout(line : Int) : Option[Loadout] = loadouts(line) - - def DeleteLoadout(line : Int) : Unit = { - loadouts(line) = None - } - def Find(obj : Equipment) : Option[Int] = Find(obj.GUID) def Find(guid : PlanetSideGUID) : Option[Int] = { - findInHolsters(holsters.iterator, guid) match { + findInHolsters(holsters.iterator, guid) + .orElse(findInInventory(inventory.Items.values.iterator, guid)) match { case Some(index) => Some(index) case None => - findInInventory(inventory.Items.values.iterator, guid) match { - case Some(index) => - Some(index) - case None => - if(freeHand.Equipment.isDefined && freeHand.Equipment.get.GUID == guid) { - Some(Player.FreeHandSlot) - } - else { - None - } + if(freeHand.Equipment.isDefined && freeHand.Equipment.get.GUID == guid) { + Some(Player.FreeHandSlot) + } + else { + None } } } @@ -348,27 +284,13 @@ class Player(private val name : String, exosuit = suit } - def BEP : Long = bep + def LoadLoadout(line : Int) : Option[Loadout] = core.LoadLoadout(line) - def BEP_=(battleExperiencePoints : Long) : Long = { - bep = math.max(0L, math.min(battleExperiencePoints, 4294967295L)) - BEP - } + def BEP : Long = core.BEP - def CEP : Long = cep + def CEP : Long = core.CEP - def CEP_=(commandExperiencePoints : Long) : Long = { - cep = math.max(0L, math.min(commandExperiencePoints, 4294967295L)) - CEP - } - - def Certifications : mutable.Set[CertificationType.Value] = certifications - - /** - * Retrieve the three implant slots for this player. - * @return an `Array` of `ImplantSlot` objects - */ - def Implants : Array[ImplantSlot] = implants + def Certifications : Set[CertificationType.Value] = core.Certifications.toSet /** * What kind of implant is installed into the given slot number? @@ -376,91 +298,17 @@ class Player(private val name : String, * @param slot the slot number * @return the tye of implant */ - def Implant(slot : Int) : ImplantType.Value = { - if(-1 < slot && slot < implants.length) { implants(slot).Implant } else { ImplantType.None } - } + def Implant(slot : Int) : ImplantType.Value = core.Implant(slot) /** - * Given a new implant, assign it into a vacant implant slot on this player.
- *
- * The implant must be unique in terms of which implants have already been assigned to this player. - * Multiple of a type of implant being assigned at once is not supported. - * Additionally, the implant is inserted into the earliest yet-unknown but vacant slot. - * Implant slots are vacant by just being unlocked or by having their previous implant uninstalled. - * @param implant the implant being installed - * @return the index of the `ImplantSlot` where the implant was installed + * A read-only `Array` of tuples representing important information about all unlocked implant slots. + * @return a maximum of three implant types, initialization times, and active flags */ - def InstallImplant(implant : ImplantDefinition) : Option[Int] = { - implants.find({p => p.Installed.contains(implant)}) match { //try to find the installed implant - case None => - recursiveFindImplantInSlot(implants.iterator, ImplantType.None) match { //install in a free slot - case Some(slot) => - implants(slot).Implant = implant - Some(slot) - case None => - None - } - case Some(_) => - None - } + def Implants : Array[(ImplantType.Value, Long, Boolean)] = { + core.Implants.takeWhile(_.Unlocked).map( implant => { (implant.Implant, implant.MaxTimer, implant.Active) }) } - /** - * Remove a specific implant from a player's allocated installed implants.
- *
- * Due to the exclusiveness of installed implants, - * any implant slot with a matching `Definition` can be uninstalled safely. - * (There will never be any doubles.) - * This operation can lead to an irregular pattern of installed and uninstalled `ImplantSlot` objects. - * Despite that breach of pattern, the logic here is consistent as demonstrated by the client and by packets. - * The client also assigns and removes implants based on slot numbers that only express availability of a "slot." - * @see `AvatarImplantMessage.implantSlot` - * @param implantType the type of implant being uninstalled - * @return the index of the `ImplantSlot` where the implant was found and uninstalled - */ - def UninstallImplant(implantType : ImplantType.Value) : Option[Int] = { - recursiveFindImplantInSlot(implants.iterator, implantType) match { - case Some(slot) => - implants(slot).Implant = None - Some(slot) - case None => - None - } - } - - /** - * Locate the index of the encountered implant type. - * Functional implants may be exclusive in as far as the input `Iterator`'s source is concerned, - * but any number of `ImplantType.None` values are alway allowed in the source in any order. - * @param iter an `Iterator` of `ImplantSlot` objects - * @param implantType the target implant being sought - * @param index a defaulted index value representing the structure underlying the `Iterator` param - * @return the index where the target implant is installed - */ - @tailrec private def recursiveFindImplantInSlot(iter : Iterator[ImplantSlot], implantType : ImplantType.Value, index : Int = 0) : Option[Int] = { - if(!iter.hasNext) { - None - } - else { - val slot = iter.next - if(slot.Unlocked && slot.Implant == implantType) { - Some(index) - } - else { - recursiveFindImplantInSlot(iter, implantType, index + 1) - } - } - } - - def ResetAllImplants() : Unit = { - implants.foreach(slot => { - slot.Installed match { - case Some(_) => - slot.Initialized = false - case None => ; - } - }) - } + def ResetAllImplants() : Unit = core.ResetAllImplants() def FacingYawUpper : Float = facingYawUpper @@ -523,8 +371,6 @@ class Player(private val name : String, isBackpack && (backpackAccess.isEmpty || backpackAccess.contains(player.GUID)) } - def Admin : Boolean = admin - def VehicleSeated : Option[PlanetSideGUID] = vehicleSeated def VehicleSeated_=(guid : PlanetSideGUID) : Option[PlanetSideGUID] = VehicleSeated_=(Some(guid)) @@ -550,52 +396,33 @@ class Player(private val name : String, Continent } - def Definition : AvatarDefinition = playerDef - - override def toString : String = { - Player.toString(this) - } + def Definition : AvatarDefinition = core.Definition def canEqual(other: Any): Boolean = other.isInstanceOf[Player] override def equals(other : Any) : Boolean = other match { case that: Player => (that canEqual this) && - name == that.name && - faction == that.faction && - sex == that.sex && - voice == that.voice && - head == that.head + core == that.core case _ => false } override def hashCode() : Int = { - val state = Seq(name, faction, sex, voice, head) - state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b) + core.hashCode() } + + override def toString : String = Player.toString(this) } object Player { - final private val definition : AvatarDefinition = new AvatarDefinition(121) final val FreeHandSlot : Int = 250 final val HandsDownSlot : Int = 255 - def apply(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : Int) : Player = { - new Player(name, faction, sex, head, voice) + def apply(core : Avatar) : Player = { + new Player(core) } -// /** -// * Change the type of `AvatarDefinition` is used to define the player. -// * @param player the player -// * @param avatarDef the player's new definition entry -// * @return the changed player -// */ -// def apply(player : Player, avatarDef : AvatarDefinition) : Player = { -// player.playerDef = avatarDef -// player -// } - def SuitSetup(player : Player, eSuit : ExoSuitType.Value) : Unit = { val esuitDef : ExoSuitDefinition = ExoSuitDefinition.Select(eSuit) //exosuit @@ -608,32 +435,11 @@ object Player { (0 until 5).foreach(index => { player.Slot(index).Size = esuitDef.Holster(index) }) } - def Administrate(player : Player, isAdmin : Boolean) : Player = { - player.admin = isAdmin - player - } - - def Release(player : Player) : Player = { + def Respawn(player : Player) : Player = { if(player.Release) { - val obj = new Player(player.Name, player.Faction, player.Sex, player.Head, player.Voice) + val obj = new Player(player.core) obj.VehicleOwned = player.VehicleOwned obj.Continent = player.Continent - //hand over loadouts - (0 until 10).foreach(index => { - obj.loadouts(index) = player.loadouts(index) - }) - //hand over implants - (0 until 3).foreach(index => { - if(obj.Implants(index).Unlocked = player.Implants(index).Unlocked) { - obj.Implants(index).Implant = player.Implants(index).Installed - } - }) - //hand over knife -// obj.Slot(4).Equipment = player.Slot(4).Equipment -// player.Slot(4).Equipment = None - //hand over locker contents -// obj.fifthSlot.Equipment = player.fifthSlot.Equipment -// player.fifthSlot.Equipment = None obj } else { @@ -641,8 +447,5 @@ object Player { } } - def toString(obj : Player) : String = { - val name : String = if(obj.VehicleSeated.isDefined) { s"[${obj.name}, ${obj.VehicleSeated.get.guid}]" } else { obj.Name } - s"[player $name, ${obj.Faction} (${obj.Health}/${obj.MaxHealth})(${obj.Armor}/${obj.MaxArmor})]" - } + def toString(obj : Player) : String = s"${obj.core} ${obj.Health}/${obj.MaxHealth} ${obj.Armor}/${obj.MaxArmor}" } diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala index 3d8bda7e2..094980fc7 100644 --- a/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala +++ b/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.definition.converter -import net.psforever.objects.{EquipmentSlot, ImplantSlot, Player} +import net.psforever.objects.{EquipmentSlot, Player} import net.psforever.objects.equipment.Equipment import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, Cosmetics, DetailedCharacterData, DrawnSlot, ImplantEffects, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle} import net.psforever.types.{GrenadeState, ImplantType} @@ -136,18 +136,12 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { private def MakeImplantEntries(obj : Player) : List[ImplantEntry] = { val numImplants : Int = DetailedCharacterData.numberOfImplantSlots(obj.BEP) val implants = obj.Implants - (0 until numImplants).map(index => { - val slot = implants(index) - slot.Installed match { - case Some(implant) => - if(slot.Initialized) { - ImplantEntry(slot.Implant, None) - } - else { - ImplantEntry(slot.Implant, Some(implant.Initialization.toInt)) - } - case None => - ImplantEntry(ImplantType.None, None) + obj.Implants.map({ case(implant, initialization, active) => + if(initialization == 0) { + ImplantEntry(implant, None) + } + else { + ImplantEntry(implant, Some(math.max(0,initialization).toInt)) } }).toList } @@ -157,14 +151,14 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { * @param iter an `Iterator` of `ImplantSlot` objects * @return the effect of an active implant */ - @tailrec private def recursiveMakeImplantEffects(iter : Iterator[ImplantSlot]) : Option[ImplantEffects.Value] = { + @tailrec private def recursiveMakeImplantEffects(iter : Iterator[(ImplantType.Value, Long, Boolean)]) : Option[ImplantEffects.Value] = { if(!iter.hasNext) { None } else { - val slot = iter.next - if(slot.Active) { - slot.Implant match { + val(implant, _, active) = iter.next + if(active) { + implant match { case ImplantType.AdvancedRegen => Some(ImplantEffects.RegenEffects) case ImplantType.DarklightVision => diff --git a/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala b/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala index ec5d0d008..88af2e5c2 100644 --- a/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala +++ b/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala @@ -2,7 +2,6 @@ package net.psforever.objects.zones import akka.actor.{Actor, Props} -import net.psforever.objects.Player import scala.annotation.tailrec diff --git a/common/src/test/scala/objects/ConverterTest.scala b/common/src/test/scala/objects/ConverterTest.scala index 57d8fc863..ccc1f7ae4 100644 --- a/common/src/test/scala/objects/ConverterTest.scala +++ b/common/src/test/scala/objects/ConverterTest.scala @@ -151,6 +151,7 @@ class ConverterTest extends Specification { } "Player" should { + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) val obj : Player = { /* Create an AmmoBoxDefinition with which to build two AmmoBoxes @@ -171,7 +172,7 @@ class ConverterTest extends Specification { val tool = Tool(tdef) tool.GUID = PlanetSideGUID(92) tool.AmmoSlot.Box.GUID = PlanetSideGUID(90) - val obj = Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = Player(avatar) obj.GUID = PlanetSideGUID(93) obj.Slot(2).Equipment = tool obj.Slot(5).Equipment.get.GUID = PlanetSideGUID(94) @@ -182,7 +183,7 @@ class ConverterTest extends Specification { val converter = new CharacterSelectConverter "convert to packet (BR < 24)" in { - obj.BEP = 0 + avatar.BEP = 0 obj.Definition.Packet.DetailedConstructorData(obj) match { case Success(_) => ok @@ -198,7 +199,7 @@ class ConverterTest extends Specification { } "convert to packet (BR >= 24)" in { - obj.BEP = 10000000 + avatar.BEP = 10000000 obj.Definition.Packet.DetailedConstructorData(obj) match { case Success(_) => ok @@ -214,7 +215,7 @@ class ConverterTest extends Specification { } "convert to simple packet (BR < 24)" in { - obj.BEP = 0 + avatar.BEP = 0 converter.DetailedConstructorData(obj) match { case Success(_) => ok @@ -226,7 +227,7 @@ class ConverterTest extends Specification { } "convert to simple packet (BR >= 24)" in { - obj.BEP = 10000000 + avatar.BEP = 10000000 converter.DetailedConstructorData(obj) match { case Success(_) => ok diff --git a/common/src/test/scala/objects/DoorTest.scala b/common/src/test/scala/objects/DoorTest.scala index 228d44ab5..95dddace2 100644 --- a/common/src/test/scala/objects/DoorTest.scala +++ b/common/src/test/scala/objects/DoorTest.scala @@ -2,7 +2,7 @@ package objects import akka.actor.{ActorRef, ActorSystem, Props} -import net.psforever.objects.{GlobalDefinitions, Player} +import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.doors.{Door, DoorControl} import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.zones.Zone @@ -13,7 +13,7 @@ import org.specs2.mutable.Specification import scala.concurrent.duration.Duration class DoorTest extends Specification { - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) "Door" should { "construct" in { @@ -123,6 +123,6 @@ object DoorControlTest { door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door") door.Owner = new Building(0, Zone.Nowhere) door.Owner.Faction = faction - (Player("test", faction, CharacterGender.Male, 0, 0), door) + (Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), door) } } diff --git a/common/src/test/scala/objects/IFFLockTest.scala b/common/src/test/scala/objects/IFFLockTest.scala index 05eb1e2fb..f31133154 100644 --- a/common/src/test/scala/objects/IFFLockTest.scala +++ b/common/src/test/scala/objects/IFFLockTest.scala @@ -3,10 +3,9 @@ package objects import akka.actor.{ActorRef, ActorSystem, Props} import net.psforever.objects.serverobject.CommonMessages -import net.psforever.objects.{GlobalDefinitions, Player} +import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.locks.{IFFLock, IFFLockControl} import net.psforever.objects.serverobject.structures.Building -import net.psforever.objects.serverobject.terminals.{Terminal, TerminalControl, TerminalDefinition} import net.psforever.objects.zones.Zone import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.{CharacterGender, PlanetSideEmpire} @@ -70,6 +69,6 @@ object IFFLockControlTest { lock.Actor = system.actorOf(Props(classOf[IFFLockControl], lock), "lock-control") lock.Owner = new Building(0, Zone.Nowhere) lock.Owner.Faction = faction - (Player("test", faction, CharacterGender.Male, 0, 0), lock) + (Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), lock) } } diff --git a/common/src/test/scala/objects/LoadoutTest.scala b/common/src/test/scala/objects/LoadoutTest.scala index db0bd0f05..84a1f3fd3 100644 --- a/common/src/test/scala/objects/LoadoutTest.scala +++ b/common/src/test/scala/objects/LoadoutTest.scala @@ -4,13 +4,15 @@ package objects import net.psforever.objects._ import net.psforever.types.{CharacterGender, ExoSuitType, PlanetSideEmpire} import net.psforever.objects.GlobalDefinitions._ -import net.psforever.objects.equipment.{Equipment, EquipmentSize} import org.specs2.mutable._ class LoadoutTest extends Specification { - def CreatePlayer() : Player = { + val avatar = Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) + + def CreatePlayer() : (Player, Avatar) = { + val avatar = Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) val - player = Player("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) + player = Player(avatar) player.Slot(0).Equipment = Tool(beamer) player.Slot(2).Equipment = Tool(suppressor) player.Slot(4).Equipment = Tool(forceblade) @@ -20,12 +22,12 @@ class LoadoutTest extends Specification { player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) player.Slot(36).Equipment = AmmoBox(energy_cell) player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) - player + (player, avatar) } "Player Loadout" should { "test sample player" in { - val obj : Player = CreatePlayer() + val (obj, _) = CreatePlayer() obj.Holsters()(0).Equipment.get.Definition mustEqual beamer obj.Holsters()(2).Equipment.get.Definition mustEqual suppressor obj.Holsters()(4).Equipment.get.Definition mustEqual forceblade @@ -38,23 +40,23 @@ class LoadoutTest extends Specification { } "do not load, if never saved" in { - CreatePlayer().LoadLoadout(0) mustEqual None + CreatePlayer()._2.LoadLoadout(0) mustEqual None } "save but incorrect load" in { - val obj : Player = CreatePlayer() - obj.SaveLoadout("test", 0) + val (obj, avatar) = CreatePlayer() + avatar.SaveLoadout(obj, "test", 0) - obj.LoadLoadout(1) mustEqual None + avatar.LoadLoadout(1) mustEqual None } "save and load" in { - val obj : Player = CreatePlayer() + val (obj, avatar) = CreatePlayer() obj.Slot(0).Equipment.get.asInstanceOf[Tool].Magazine = 1 //non-standard but legal obj.Slot(2).Equipment.get.asInstanceOf[Tool].AmmoSlot.Magazine = 100 //non-standard (and out of range, real=25) - obj.SaveLoadout("test", 0) + avatar.SaveLoadout(obj, "test", 0) - obj.LoadLoadout(0) match { + avatar.LoadLoadout(0) match { case Some(items) => items.Label mustEqual "test" items.ExoSuit mustEqual obj.ExoSuit @@ -91,11 +93,11 @@ class LoadoutTest extends Specification { } "save without inventory contents" in { - val obj : Player = CreatePlayer() + val (obj, avatar) = CreatePlayer() obj.Inventory.Clear() - obj.SaveLoadout("test", 0) + avatar.SaveLoadout(obj, "test", 0) - obj.LoadLoadout(0) match { + avatar.LoadLoadout(0) match { case Some(items) => items.Label mustEqual "test" items.ExoSuit mustEqual obj.ExoSuit @@ -108,13 +110,13 @@ class LoadoutTest extends Specification { } "save without visible slot contents" in { - val obj : Player = CreatePlayer() + val (obj, avatar) = CreatePlayer() obj.Slot(0).Equipment = None obj.Slot(2).Equipment = None obj.Slot(4).Equipment = None - obj.SaveLoadout("test", 0) + avatar.SaveLoadout(obj, "test", 0) - obj.LoadLoadout(0) match { + avatar.LoadLoadout(0) match { case Some(items) => items.Label mustEqual "test" items.ExoSuit mustEqual obj.ExoSuit @@ -127,12 +129,12 @@ class LoadoutTest extends Specification { } "save (a construction item) and load" in { - val obj : Player = CreatePlayer() + val (obj, avatar) = CreatePlayer() obj.Inventory.Clear() obj.Slot(6).Equipment = ConstructionItem(advanced_ace) - obj.SaveLoadout("test", 0) + avatar.SaveLoadout(obj, "test", 0) - obj.LoadLoadout(0) match { + avatar.LoadLoadout(0) match { case Some(items) => items.Inventory.length mustEqual 1 items.Inventory.head.index mustEqual 6 @@ -143,12 +145,12 @@ class LoadoutTest extends Specification { } "save (a kit) and load" in { - val obj : Player = CreatePlayer() + val (obj, avatar) = CreatePlayer() obj.Inventory.Clear() obj.Slot(6).Equipment = Kit(medkit) - obj.SaveLoadout("test", 0) + avatar.SaveLoadout(obj, "test", 0) - obj.LoadLoadout(0) match { + avatar.LoadLoadout(0) match { case Some(items) => items.Inventory.length mustEqual 1 items.Inventory.head.index mustEqual 6 @@ -159,38 +161,38 @@ class LoadoutTest extends Specification { } "save, load, delete" in { - val obj : Player = CreatePlayer() - obj.SaveLoadout("test", 0) + val (obj, avatar) = CreatePlayer() + avatar.SaveLoadout(obj, "test", 0) - obj.LoadLoadout(0) match { + avatar.LoadLoadout(0) match { case None => ko case Some(_) => ; //good; keep going } - obj.DeleteLoadout(0) - obj.LoadLoadout(0) mustEqual None + avatar.DeleteLoadout(0) + avatar.LoadLoadout(0) mustEqual None } "distinguish MAX subtype information" in { - val obj : Player = CreatePlayer() + val (obj, avatar) = CreatePlayer() val slot = obj.Slot(0) slot.Equipment = None //only an unequipped slot can have its Equipment Size changed (Rifle -> Max) Player.SuitSetup(obj, ExoSuitType.MAX) - obj.SaveLoadout("generic", 0) //weaponless + avatar.SaveLoadout(obj, "generic", 0) //weaponless slot.Equipment = None slot.Equipment = Tool(trhev_dualcycler) - obj.SaveLoadout("cycler", 1) + avatar.SaveLoadout(obj, "cycler", 1) slot.Equipment = None slot.Equipment = Tool(trhev_pounder) - obj.SaveLoadout("pounder", 2) + avatar.SaveLoadout(obj, "pounder", 2) slot.Equipment = None slot.Equipment = Tool(trhev_burster) - obj.SaveLoadout("burster", 3) + avatar.SaveLoadout(obj, "burster", 3) - obj.LoadLoadout(0).get.Subtype mustEqual 0 - obj.LoadLoadout(1).get.Subtype mustEqual 1 - obj.LoadLoadout(2).get.Subtype mustEqual 2 - obj.LoadLoadout(3).get.Subtype mustEqual 3 + avatar.LoadLoadout(0).get.Subtype mustEqual 0 + avatar.LoadLoadout(1).get.Subtype mustEqual 1 + avatar.LoadLoadout(2).get.Subtype mustEqual 2 + avatar.LoadLoadout(3).get.Subtype mustEqual 3 } } } diff --git a/common/src/test/scala/objects/MountableTest.scala b/common/src/test/scala/objects/MountableTest.scala index 83ba5babd..56fe1f831 100644 --- a/common/src/test/scala/objects/MountableTest.scala +++ b/common/src/test/scala/objects/MountableTest.scala @@ -2,7 +2,7 @@ package objects import akka.actor.{Actor, ActorRef, Props} -import net.psforever.objects.Player +import net.psforever.objects.{Avatar, Player} import net.psforever.objects.definition.{ObjectDefinition, SeatDefinition} import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} import net.psforever.objects.serverobject.PlanetSideServerObject @@ -25,7 +25,7 @@ class MountableControl1Test extends ActorTest() { class MountableControl2Test extends ActorTest() { "MountableControl" should { "let a player mount" in { - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val obj = new MountableTest.MountableTestObject obj.Actor = system.actorOf(Props(classOf[MountableTest.MountableTestControl], obj), "mountable") val msg = Mountable.TryMount(player, 0) @@ -46,8 +46,8 @@ class MountableControl2Test extends ActorTest() { class MountableControl3Test extends ActorTest() { "MountableControl" should { "block a player from mounting" in { - val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) - val player2 = Player("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player1 = Player(Avatar("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + val player2 = Player(Avatar("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val obj = new MountableTest.MountableTestObject obj.Actor = system.actorOf(Props(classOf[MountableTest.MountableTestControl], obj), "mountable") obj.Actor ! Mountable.TryMount(player1, 0) diff --git a/common/src/test/scala/objects/PlayerTest.scala b/common/src/test/scala/objects/PlayerTest.scala index facf231ca..6a4de73f1 100644 --- a/common/src/test/scala/objects/PlayerTest.scala +++ b/common/src/test/scala/objects/PlayerTest.scala @@ -2,35 +2,35 @@ package objects import net.psforever.objects._ -import net.psforever.objects.definition.{ImplantDefinition, SimpleItemDefinition} +import net.psforever.objects.definition.SimpleItemDefinition import net.psforever.objects.equipment.EquipmentSize import net.psforever.packet.game.PlanetSideGUID -import net.psforever.types.{CharacterGender, ExoSuitType, ImplantType, PlanetSideEmpire} +import net.psforever.types.{CharacterGender, ExoSuitType, PlanetSideEmpire} import org.specs2.mutable._ class PlayerTest extends Specification { "construct" in { - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.isAlive mustEqual false } "different players" in { - (Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual true - (Player("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - Player("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual false - (Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - Player("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, 5)) mustEqual false - (Player("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - Player("Chord2", PlanetSideEmpire.TR, CharacterGender.Female, 0, 5)) mustEqual false - (Player("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - Player("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 1, 5)) mustEqual false - (Player("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - Player("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 6)) mustEqual false + (PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual true + (PlayerTest.Player("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + PlayerTest.Player("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual false + (PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + PlayerTest.Player("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, 5)) mustEqual false + (PlayerTest.Player("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + PlayerTest.Player("Chord2", PlanetSideEmpire.TR, CharacterGender.Female, 0, 5)) mustEqual false + (PlayerTest.Player("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + PlayerTest.Player("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 1, 5)) mustEqual false + (PlayerTest.Player("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + PlayerTest.Player("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 6)) mustEqual false } "become a backpack" in { - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.isAlive mustEqual false obj.isBackpack mustEqual false obj.Release @@ -39,7 +39,7 @@ class PlayerTest extends Specification { } "(re)spawn" in { - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.isAlive mustEqual false obj.Health mustEqual 0 obj.Stamina mustEqual 0 @@ -55,7 +55,7 @@ class PlayerTest extends Specification { } "set new maximum values (health, stamina)" in { - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.MaxHealth mustEqual 100 obj.MaxStamina mustEqual 100 obj.MaxHealth = 123 @@ -66,7 +66,7 @@ class PlayerTest extends Specification { } "init (Standard Exo-Suit)" in { - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.ExoSuit mustEqual ExoSuitType.Standard obj.Slot(0).Size mustEqual EquipmentSize.Pistol obj.Slot(1).Size mustEqual EquipmentSize.Blocked @@ -79,7 +79,7 @@ class PlayerTest extends Specification { } "die" in { - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Spawn obj.Armor = 35 //50 -> 35 obj.isAlive mustEqual true @@ -95,7 +95,7 @@ class PlayerTest extends Specification { "draw equipped holsters only" in { val wep = SimpleItem(SimpleItemDefinition(149)) - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Slot(1).Size = EquipmentSize.Pistol obj.Slot(1).Equipment = wep obj.DrawnSlot mustEqual Player.HandsDownSlot @@ -108,7 +108,7 @@ class PlayerTest extends Specification { "remember the last drawn holster" in { val wep1 = SimpleItem(SimpleItemDefinition(149)) val wep2 = SimpleItem(SimpleItemDefinition(149)) - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Slot(0).Size = EquipmentSize.Pistol obj.Slot(0).Equipment = wep1 obj.Slot(1).Size = EquipmentSize.Pistol @@ -147,7 +147,7 @@ class PlayerTest extends Specification { "hold something in their free hand" in { val wep = SimpleItem(SimpleItemDefinition(149)) - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Slot(Player.FreeHandSlot).Equipment = wep obj.Slot(Player.FreeHandSlot).Equipment.get.Definition.ObjectId mustEqual 149 @@ -155,14 +155,14 @@ class PlayerTest extends Specification { "provide an invalid hand that can not hold anything" in { val wep = SimpleItem(SimpleItemDefinition(149)) - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Slot(-1).Equipment = wep obj.Slot(-1).Equipment mustEqual None } - "search for the smallest available slot in which to satore equipment" in { - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + "search for the smallest available slot in which to store equipment" in { + val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Inventory.Resize(3,3) obj.Fit(Tool(GlobalDefinitions.beamer)) mustEqual Some(0) @@ -171,7 +171,7 @@ class PlayerTest extends Specification { val ammo = AmmoBox(GlobalDefinitions.bullet_9mm) val ammo2 = AmmoBox(GlobalDefinitions.bullet_9mm) - val ammo3 = AmmoBox(GlobalDefinitions.bullet_9mm) + //val ammo3 = AmmoBox(GlobalDefinitions.bullet_9mm) obj.Fit(ammo) mustEqual Some(6) obj.Slot(6).Equipment = ammo obj.Fit(ammo2) mustEqual Some(Player.FreeHandSlot) @@ -179,43 +179,44 @@ class PlayerTest extends Specification { obj.Fit(ammo2) mustEqual None } - "install an implant" in { - val testplant : ImplantDefinition = ImplantDefinition(1) - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Implants(0).Unlocked = true - obj.InstallImplant(testplant) mustEqual Some(0) - obj.Implants.find({p => p.Implant == ImplantType(1)}) match { //find the installed implant - case Some(slot) => - slot.Installed mustEqual Some(testplant) - case _ => - ko - } - ok - } - - "can not install the same type of implant twice" in { - val testplant1 : ImplantDefinition = ImplantDefinition(1) - val testplant2 : ImplantDefinition = ImplantDefinition(1) - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Implants(0).Unlocked = true - obj.Implants(1).Unlocked = true - obj.InstallImplant(testplant1) mustEqual Some(0) - obj.InstallImplant(testplant2) mustEqual Some(1) - } - - "uninstall implants" in { - val testplant : ImplantDefinition = ImplantDefinition(1) - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Implants(0).Unlocked = true - obj.InstallImplant(testplant) mustEqual Some(0) - obj.Implants(0).Installed mustEqual Some(testplant) - - obj.UninstallImplant(testplant.Type) - obj.Implants(0).Installed mustEqual None - } + //TODO move to Avatar tests +// "install an implant" in { +// val testplant : ImplantDefinition = ImplantDefinition(1) +// val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) +// obj.Implants(0).Unlocked = true +// obj.InstallImplant(testplant) mustEqual Some(0) +// obj.Implants.find({p => p.Implant == ImplantType(1)}) match { //find the installed implant +// case Some(slot) => +// slot.Installed mustEqual Some(testplant) +// case _ => +// ko +// } +// ok +// } +// +// "can not install the same type of implant twice" in { +// val testplant1 : ImplantDefinition = ImplantDefinition(1) +// val testplant2 : ImplantDefinition = ImplantDefinition(1) +// val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) +// obj.Implants(0).Unlocked = true +// obj.Implants(1).Unlocked = true +// obj.InstallImplant(testplant1) mustEqual Some(0) +// obj.InstallImplant(testplant2) mustEqual Some(1) +// } +// +// "uninstall implants" in { +// val testplant : ImplantDefinition = ImplantDefinition(1) +// val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) +// obj.Implants(0).Unlocked = true +// obj.InstallImplant(testplant) mustEqual Some(0) +// obj.Implants(0).Installed mustEqual Some(testplant) +// +// obj.UninstallImplant(testplant.Type) +// obj.Implants(0).Installed mustEqual None +// } "seat in a vehicle" in { - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.VehicleSeated mustEqual None obj.VehicleSeated = PlanetSideGUID(65) obj.VehicleSeated mustEqual Some(PlanetSideGUID(65)) @@ -224,7 +225,7 @@ class PlayerTest extends Specification { } "own in a vehicle" in { - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.VehicleOwned mustEqual None obj.VehicleOwned = PlanetSideGUID(65) obj.VehicleOwned mustEqual Some(PlanetSideGUID(65)) @@ -233,18 +234,15 @@ class PlayerTest extends Specification { } "remember what zone he is in" in { - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Continent mustEqual "home2" obj.Continent = "ugd01" obj.Continent mustEqual "ugd01" } +} - "administrate" in { - val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Admin mustEqual false - Player.Administrate(obj, true) - obj.Admin mustEqual true - Player.Administrate(obj, false) - obj.Admin mustEqual false +object PlayerTest { + def Player(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : Int) : Player = { + new Player(Avatar(name, faction, sex, head, voice)) } } diff --git a/common/src/test/scala/objects/VehicleSpawnPadTest.scala b/common/src/test/scala/objects/VehicleSpawnPadTest.scala index f595da65f..1332fed32 100644 --- a/common/src/test/scala/objects/VehicleSpawnPadTest.scala +++ b/common/src/test/scala/objects/VehicleSpawnPadTest.scala @@ -6,7 +6,7 @@ import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawn import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.vehicles.VehicleControl import net.psforever.objects.zones.Zone -import net.psforever.objects.{GlobalDefinitions, Player, Vehicle} +import net.psforever.objects.{Avatar, GlobalDefinitions, Player, Vehicle} import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3} import org.specs2.mutable.Specification @@ -111,6 +111,6 @@ object VehicleSpawnPadControl { pad.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], pad), "test-pad") pad.Owner = new Building(0, Zone.Nowhere) pad.Owner.Faction = faction - (Player("test", faction, CharacterGender.Male, 0, 0), pad) + (Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), pad) } } diff --git a/common/src/test/scala/objects/VehicleTest.scala b/common/src/test/scala/objects/VehicleTest.scala index e7e6f2e39..0124917dc 100644 --- a/common/src/test/scala/objects/VehicleTest.scala +++ b/common/src/test/scala/objects/VehicleTest.scala @@ -2,17 +2,18 @@ package objects import akka.actor.Props -import net.psforever.objects.{GlobalDefinitions, Player, Vehicle} +import net.psforever.objects._ import net.psforever.objects.definition.{SeatDefinition, VehicleDefinition} import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.vehicles._ import net.psforever.packet.game.PlanetSideGUID -import net.psforever.types.{CharacterGender, ExoSuitType, PlanetSideEmpire} +import net.psforever.types.ExoSuitType import org.specs2.mutable._ import scala.concurrent.duration.Duration class VehicleTest extends Specification { + import VehicleTest._ "SeatDefinition" should { val seat = new SeatDefinition @@ -73,7 +74,7 @@ class VehicleTest extends Specification { val seat = new Seat(seat_def) seat.Occupant.isDefined mustEqual false - val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player1 = Player(avatar1) player1.ExoSuit = ExoSuitType.MAX seat.Occupant = player1 seat.Occupant.isDefined mustEqual true @@ -84,13 +85,13 @@ class VehicleTest extends Specification { val seat = new Seat(seat_def) seat.Occupant.isDefined mustEqual false - val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player1 = Player(avatar1) player1.ExoSuit = ExoSuitType.MAX seat.Occupant = player1 seat.Occupant.isDefined mustEqual true seat.Occupant.contains(player1) mustEqual true - val player2 = Player("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(avatar1) player2.ExoSuit = ExoSuitType.MAX seat.Occupant = player2 seat.Occupant.isDefined mustEqual true @@ -101,13 +102,13 @@ class VehicleTest extends Specification { val seat = new Seat(seat_def) seat.Occupant.isDefined mustEqual false - val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player1 = Player(avatar1) player1.ExoSuit = ExoSuitType.MAX seat.Occupant = player1 seat.Occupant.isDefined mustEqual true seat.Occupant.contains(player1) mustEqual true - val player2 = Player("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(avatar2) player2.ExoSuit = ExoSuitType.MAX seat.Occupant = player2 seat.Occupant.isDefined mustEqual true @@ -160,7 +161,7 @@ class VehicleTest extends Specification { val fury_vehicle = Vehicle(GlobalDefinitions.fury) fury_vehicle.Owner.isDefined mustEqual false - val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player1 = Player(avatar1) player1.GUID = PlanetSideGUID(1) fury_vehicle.Owner = player1 fury_vehicle.Owner.isDefined mustEqual true @@ -171,13 +172,13 @@ class VehicleTest extends Specification { val fury_vehicle = Vehicle(GlobalDefinitions.fury) fury_vehicle.Owner.isDefined mustEqual false - val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player1 = Player(avatar1) player1.GUID = PlanetSideGUID(1) fury_vehicle.Owner = player1 fury_vehicle.Owner.isDefined mustEqual true fury_vehicle.Owner.contains(PlanetSideGUID(1)) mustEqual true - val player2 = Player("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(avatar2) player2.GUID = PlanetSideGUID(2) fury_vehicle.Owner = player2 fury_vehicle.Owner.isDefined mustEqual true @@ -234,9 +235,9 @@ class VehicleTest extends Specification { "can find a passenger in a seat" in { val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) - val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player1 = Player(avatar1) player1.GUID = PlanetSideGUID(1) - val player2 = Player("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(avatar2) player2.GUID = PlanetSideGUID(2) harasser_vehicle.Seat(0).get.Occupant = player1 //don't worry about ownership for now harasser_vehicle.Seat(1).get.Occupant = player2 @@ -282,9 +283,9 @@ class VehicleTest extends Specification { class VehicleControl1Test extends ActorTest { "Vehicle Control" should { "deactivate and stop handling mount messages" in { - val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player1 = Player(VehicleTest.avatar1) player1.GUID = PlanetSideGUID(1) - val player2 = Player("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(VehicleTest.avatar2) val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) vehicle.GUID = PlanetSideGUID(3) vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") @@ -303,9 +304,9 @@ class VehicleControl1Test extends ActorTest { class VehicleControl2Test extends ActorTest { "Vehicle Control" should { "reactivate and resume handling mount messages" in { - val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player1 = Player(VehicleTest.avatar1) player1.GUID = PlanetSideGUID(1) - val player2 = Player("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(VehicleTest.avatar2) player2.GUID = PlanetSideGUID(2) val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) vehicle.GUID = PlanetSideGUID(3) @@ -324,3 +325,10 @@ class VehicleControl2Test extends ActorTest { } } } + +object VehicleTest { + import net.psforever.objects.Avatar + import net.psforever.types.{CharacterGender, PlanetSideEmpire} + val avatar1 = Avatar("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val avatar2 = Avatar("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) +} diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskRegister5Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskRegister5Test.scala index a96b55dd2..2c08c06df 100644 --- a/common/src/test/scala/objects/guidtask/GUIDTaskRegister5Test.scala +++ b/common/src/test/scala/objects/guidtask/GUIDTaskRegister5Test.scala @@ -10,7 +10,7 @@ import objects.ActorTest class GUIDTaskRegister5Test extends ActorTest() { "RegisterAvatar" in { val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup - val obj = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val obj_wep = Tool(GlobalDefinitions.beamer) obj.Slot(0).Equipment = obj_wep val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell) diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskUnregister5Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister5Test.scala index b4d28ab47..15cef74e2 100644 --- a/common/src/test/scala/objects/guidtask/GUIDTaskUnregister5Test.scala +++ b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister5Test.scala @@ -9,7 +9,7 @@ import objects.ActorTest class GUIDTaskUnregister5Test extends ActorTest() { "UnregisterAvatar" in { val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup - val obj = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val obj_wep = Tool(GlobalDefinitions.beamer) obj.Slot(0).Equipment = obj_wep val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell) diff --git a/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala b/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala index 65436e495..2201f6195 100644 --- a/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala @@ -3,7 +3,7 @@ package objects.terminal import akka.actor.ActorRef import net.psforever.objects.serverobject.structures.Building -import net.psforever.objects.{GlobalDefinitions, Player} +import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} @@ -12,7 +12,7 @@ import org.specs2.mutable.Specification class AirVehicleTerminalTest extends Specification { "Air_Vehicle_Terminal" should { - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.air_vehicle_terminal) terminal.Owner = new Building(0, Zone.Nowhere) terminal.Owner.Faction = PlanetSideEmpire.TR diff --git a/common/src/test/scala/objects/terminal/CertTerminalTest.scala b/common/src/test/scala/objects/terminal/CertTerminalTest.scala index c3d000d0e..8a9785d81 100644 --- a/common/src/test/scala/objects/terminal/CertTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/CertTerminalTest.scala @@ -5,14 +5,14 @@ import akka.actor.ActorRef import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone -import net.psforever.objects.{GlobalDefinitions, Player} +import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types._ import org.specs2.mutable.Specification class CertTerminalTest extends Specification { "Cert_Terminal" should { - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.cert_terminal) terminal.Owner = new Building(0, Zone.Nowhere) terminal.Owner.Faction = PlanetSideEmpire.TR diff --git a/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala b/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala index d498e3e0b..2b16b69cb 100644 --- a/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala @@ -3,7 +3,7 @@ package objects.terminal import akka.actor.ActorRef import net.psforever.objects.serverobject.structures.Building -import net.psforever.objects.{GlobalDefinitions, Player} +import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} @@ -12,7 +12,7 @@ import org.specs2.mutable.Specification class DropshipVehicleTerminalTest extends Specification { "Dropship_Vehicle_Terminal" should { - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.dropship_vehicle_terminal) terminal.Owner = new Building(0, Zone.Nowhere) terminal.Owner.Faction = PlanetSideEmpire.TR diff --git a/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala b/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala index 831bb0b17..554d492f7 100644 --- a/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala @@ -3,7 +3,7 @@ package objects.terminal import akka.actor.ActorRef import net.psforever.objects.serverobject.structures.Building -import net.psforever.objects.{GlobalDefinitions, Player} +import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} @@ -12,7 +12,7 @@ import org.specs2.mutable.Specification class GroundVehicleTerminalTest extends Specification { "Ground_Vehicle_Terminal" should { - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.ground_vehicle_terminal) terminal.Owner = new Building(0, Zone.Nowhere) terminal.Owner.Faction = PlanetSideEmpire.TR diff --git a/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala b/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala index d846ab944..5a1d2c9dd 100644 --- a/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala +++ b/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala @@ -3,7 +3,7 @@ package objects.terminal import akka.actor.ActorRef import net.psforever.objects.serverobject.structures.Building -import net.psforever.objects.{GlobalDefinitions, Player} +import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} @@ -12,7 +12,7 @@ import org.specs2.mutable.Specification class ImplantTerminalInterfaceTest extends Specification { "Implant_Terminal_Interface" should { - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.implant_terminal_interface) terminal.Owner = new Building(0, Zone.Nowhere) terminal.Owner.Faction = PlanetSideEmpire.TR diff --git a/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala b/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala index a58f0cf0c..7c333b861 100644 --- a/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala +++ b/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala @@ -6,7 +6,7 @@ import net.psforever.objects.definition.SeatDefinition import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.implantmech.{ImplantTerminalMech, ImplantTerminalMechControl} import net.psforever.objects.vehicles.Seat -import net.psforever.objects.{GlobalDefinitions, Player} +import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3} import objects.ActorTest import org.specs2.mutable.Specification @@ -44,7 +44,7 @@ class ImplantTerminalMechTest extends Specification { } "get passenger in a seat" in { - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val obj = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech) obj.PassengerInSeat(player) mustEqual None obj.Seats(0).Occupant = player @@ -89,7 +89,7 @@ class ImplantTerminalMechControl3Test extends ActorTest() { "ImplantTerminalMechControl" should { "block a player from mounting" in { val (player1, mech) = ImplantTerminalMechTest.SetUpAgents(PlanetSideEmpire.TR) - val player2 = Player("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(Avatar("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) mech.Actor ! Mountable.TryMount(player1, 0) receiveOne(Duration.create(100, "ms")) //consume reply @@ -163,6 +163,6 @@ object ImplantTerminalMechTest { terminal.Owner = new Building(0, Zone.Nowhere) terminal.Owner.Faction = faction terminal.GUID = PlanetSideGUID(1) - (Player("test", faction, CharacterGender.Male, 0, 0), terminal) + (Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), terminal) } } diff --git a/common/src/test/scala/objects/terminal/MatrixTerminalTest.scala b/common/src/test/scala/objects/terminal/MatrixTerminalTest.scala index a574c79e1..5fe8c3167 100644 --- a/common/src/test/scala/objects/terminal/MatrixTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/MatrixTerminalTest.scala @@ -3,7 +3,7 @@ package objects.terminal import akka.actor.ActorRef import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, Terminal} -import net.psforever.objects.{GlobalDefinitions, Player, Vehicle} +import net.psforever.objects.{Avatar, GlobalDefinitions, Player, Vehicle} import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types._ import org.specs2.mutable.Specification @@ -54,7 +54,7 @@ class MatrixTerminalTest extends Specification { } "player can not buy (anything)" in { - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "lite_armor", 0, PlanetSideGUID(0)) terminal.Request(player, msg) mustEqual Terminal.NoDeal() diff --git a/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala b/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala index e517624ed..d94b867bf 100644 --- a/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala +++ b/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala @@ -5,7 +5,7 @@ import akka.actor.ActorRef import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.serverobject.terminals.{OrderTerminalABDefinition, Terminal} import net.psforever.objects.zones.Zone -import net.psforever.objects.{GlobalDefinitions, Player} +import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types._ import org.specs2.mutable.Specification @@ -47,14 +47,14 @@ class OrderTerminalABTest extends Specification { } "player can buy different armor ('lite_armor')" in { - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "lite_armor", 0, PlanetSideGUID(0)) terminal.Request(player, msg) mustEqual Terminal.BuyExosuit(ExoSuitType.Agile) } "player can buy max armor ('trhev_antiaircraft')" in { - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "trhev_antiaircraft", 0, PlanetSideGUID(0)) terminal.Request(player, msg) mustEqual Terminal.NoDeal() @@ -62,10 +62,11 @@ class OrderTerminalABTest extends Specification { //TODO loudout tests "player can not load max loadout" in { - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) - player.SaveLoadout("test1", 0) + val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player = Player(avatar) + avatar.SaveLoadout(player, "test1", 0) player.ExoSuit = ExoSuitType.MAX - player.SaveLoadout("test2", 1) + avatar.SaveLoadout(player, "test2", 1) val msg1 = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.InfantryLoadout, 4, "", 0, PlanetSideGUID(0)) terminal.Request(player, msg1) mustEqual Terminal.InfantryLoadout(ExoSuitType.Standard, 0, Nil, Nil) diff --git a/common/src/test/scala/objects/terminal/OrderTerminalTest.scala b/common/src/test/scala/objects/terminal/OrderTerminalTest.scala index 1998d7d75..e6e8c2c48 100644 --- a/common/src/test/scala/objects/terminal/OrderTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/OrderTerminalTest.scala @@ -5,14 +5,14 @@ import akka.actor.ActorRef import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone -import net.psforever.objects.{AmmoBox, GlobalDefinitions, Player, Tool} +import net.psforever.objects.{AmmoBox, Avatar, GlobalDefinitions, Player, Tool} import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types._ import org.specs2.mutable.Specification class OrderTerminalTest extends Specification { "Order_Terminal" should { - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.order_terminal) terminal.Owner = new Building(0, Zone.Nowhere) terminal.Owner.Faction = PlanetSideEmpire.TR diff --git a/common/src/test/scala/objects/terminal/TerminalControlTest.scala b/common/src/test/scala/objects/terminal/TerminalControlTest.scala index f3a461f90..e77bbd738 100644 --- a/common/src/test/scala/objects/terminal/TerminalControlTest.scala +++ b/common/src/test/scala/objects/terminal/TerminalControlTest.scala @@ -5,7 +5,7 @@ import akka.actor.{ActorSystem, Props} import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.serverobject.terminals.{Terminal, TerminalControl, TerminalDefinition} import net.psforever.objects.zones.Zone -import net.psforever.objects.{GlobalDefinitions, Player} +import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types._ import objects.ActorTest @@ -124,6 +124,6 @@ object TerminalControlTest { terminal.Actor = system.actorOf(Props(classOf[TerminalControl], terminal), "test-term") terminal.Owner = new Building(0, Zone.Nowhere) terminal.Owner.Faction = faction - (Player("test", faction, CharacterGender.Male, 0, 0), terminal) + (Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), terminal) } } diff --git a/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala b/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala index 6a390432b..85fb96ef7 100644 --- a/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala +++ b/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala @@ -3,7 +3,7 @@ package objects.terminal import akka.actor.ActorRef import net.psforever.objects.serverobject.structures.Building -import net.psforever.objects.{GlobalDefinitions, Player} +import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} @@ -12,7 +12,7 @@ import org.specs2.mutable.Specification class VehicleTerminalCombinedTest extends Specification { "Ground_Vehicle_Terminal" should { - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.vehicle_terminal_combined) terminal.Owner = new Building(0, Zone.Nowhere) terminal.Owner.Faction = PlanetSideEmpire.TR diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index ce97439fb..fc24d57e2 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -9,6 +9,7 @@ import scodec.Attempt.{Failure, Successful} import scodec.bits._ import org.log4s.MDC import MDCContextAware.Implicits._ +import net.psforever.objects.GlobalDefinitions._ import services.ServiceManager.Lookup import net.psforever.objects._ import net.psforever.objects.equipment._ @@ -60,6 +61,7 @@ class WorldSessionActor extends Actor with MDCContextAware { var flying : Boolean = false var speed : Float = 1.0f var spectator : Boolean = false + var admin : Boolean = false var clientKeepAlive : Cancellable = DefaultCancellable.obj var progressBarUpdate : Cancellable = DefaultCancellable.obj @@ -763,9 +765,9 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.InfantryLoadout, true)) case Terminal.LearnCertification(cert, cost) => - if(!player.Certifications.contains(cert)) { + if(!tplayer.Certifications.contains(cert)) { log.info(s"$tplayer is learning the $cert certification for $cost points") - tplayer.Certifications += cert + avatar.Certifications += cert sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 24, cert.id.toLong)) sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, true)) } @@ -775,9 +777,9 @@ class WorldSessionActor extends Actor with MDCContextAware { } case Terminal.SellCertification(cert, cost) => - if(player.Certifications.contains(cert)) { + if(tplayer.Certifications.contains(cert)) { log.info(s"$tplayer is forgetting the $cert certification for $cost points") - tplayer.Certifications -= cert + avatar.Certifications -= cert sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 25, cert.id.toLong)) sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Sell, true)) } @@ -794,8 +796,8 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(mech_guid) => ( continent.Map.TerminalToInterface.get(mech_guid.guid), - if(!tplayer.Implants.exists({slot => slot.Implant == implant_type})) { //no duplicates - tplayer.InstallImplant(implant) + if(!avatar.Implants.exists({slot => slot.Implant == implant_type})) { //no duplicates + avatar.InstallImplant(implant) } else { None @@ -834,7 +836,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(mech_guid) => ( continent.Map.TerminalToInterface.get(mech_guid.guid), - tplayer.UninstallImplant(implant_type) + avatar.UninstallImplant(implant_type) ) case None => (None, None) @@ -1004,12 +1006,14 @@ class WorldSessionActor extends Actor with MDCContextAware { case NewPlayerLoaded(tplayer) => log.info(s"Player $tplayer has been loaded") + player = tplayer //LoadMapMessage will cause the client to send back a BeginZoningMessage packet (see below) sendResponse(LoadMapMessage(continent.Map.Name, continent.Id, 40100,25,true,3770441820L)) AvatarCreate() case PlayerLoaded(tplayer) => log.info(s"Player $tplayer has been loaded") + player = tplayer AvatarCreate() self ! SetCurrentAvatar(tplayer) @@ -1020,6 +1024,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } case SetCurrentAvatar(tplayer) => + player = tplayer val guid = tplayer.GUID LivePlayerList.Assign(continent.Number, sessionId, guid) sendResponse(SetCurrentAvatarMessage(guid,0,0)) @@ -1178,6 +1183,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } var player : Player = null + var avatar : Avatar = null var spawnZones : Map[Int, Building] = null def handleGamePkt(pkt : PlanetSideGamePacket) = pkt match { @@ -1187,39 +1193,40 @@ class WorldSessionActor extends Actor with MDCContextAware { //TODO begin temp player character auto-loading; remove later import net.psforever.objects.GlobalDefinitions._ import net.psforever.types.CertificationType._ - player = Player("TestCharacter"+sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) + avatar = Avatar("TestCharacter"+sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) + avatar.Certifications += StandardAssault + avatar.Certifications += MediumAssault + avatar.Certifications += StandardExoSuit + avatar.Certifications += AgileExoSuit + avatar.Certifications += ReinforcedExoSuit + avatar.Certifications += ATV + avatar.Certifications += Harasser + // + avatar.Certifications += InfiltrationSuit + avatar.Certifications += Sniping + avatar.Certifications += AntiVehicular + avatar.Certifications += HeavyAssault + avatar.Certifications += SpecialAssault + avatar.Certifications += EliteAssault + avatar.Certifications += GroundSupport + avatar.Certifications += GroundTransport + avatar.Certifications += Flail + avatar.Certifications += Switchblade + avatar.Certifications += AssaultBuggy + avatar.Certifications += ArmoredAssault1 + avatar.Certifications += ArmoredAssault2 + avatar.Certifications += AirCavalryScout + avatar.Certifications += AirCavalryAssault + avatar.Certifications += AirCavalryInterceptor + avatar.Certifications += AirSupport + avatar.Certifications += GalaxyGunship + avatar.Certifications += Phantasm + avatar.Certifications += UniMAX + AwardBattleExperiencePoints(avatar, 1000000L) + player = new Player(avatar) //player.Position = Vector3(3561.0f, 2854.0f, 90.859375f) //home3, HART C player.Position = Vector3(3881.9688f, 4432.008f, 267.0f) //z6, Anguta / n.tower player.Orientation = Vector3(0f, 0f, 90f) - player.Certifications += StandardAssault - player.Certifications += MediumAssault - player.Certifications += StandardExoSuit - player.Certifications += AgileExoSuit - player.Certifications += ReinforcedExoSuit - player.Certifications += ATV - player.Certifications += Harasser - // - player.Certifications += InfiltrationSuit - player.Certifications += Sniping - player.Certifications += AntiVehicular - player.Certifications += HeavyAssault - player.Certifications += SpecialAssault - player.Certifications += EliteAssault - player.Certifications += GroundSupport - player.Certifications += GroundTransport - player.Certifications += Flail - player.Certifications += Switchblade - player.Certifications += AssaultBuggy - player.Certifications += ArmoredAssault1 - player.Certifications += ArmoredAssault2 - player.Certifications += AirCavalryScout - player.Certifications += AirCavalryAssault - player.Certifications += AirCavalryInterceptor - player.Certifications += AirSupport - player.Certifications += GalaxyGunship - player.Certifications += Phantasm - player.Certifications += UniMAX - AwardBattleExperiencePoints(player, 1000000L) // 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 @@ -1355,14 +1362,13 @@ class WorldSessionActor extends Actor with MDCContextAware { self ! SetCurrentAvatar(player) case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) => - if(player.isAlive && player.GUID == avatar_guid) { + if(!player.isAlive) { player.Position = pos player.Velocity = vel player.Orientation = Vector3(player.Orientation.x, pitch, yaw) player.FacingYawUpper = yaw_upper player.Crouching = is_crouching player.Jumping = is_jumping - val wepInHand : Boolean = player.Slot(player.DrawnSlot).Equipment match { case Some(item) => item.Definition == GlobalDefinitions.bolt_driver case None => false @@ -1429,9 +1435,10 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ReleaseAvatarRequestMessage() => log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released") player.Release + val knife = player.Slot(4).Equipment.get + taskResolver ! RemoveEquipmentFromSlot(player, knife, 4) sendResponse(PlanetsideAttributeMessage(player.GUID, 6, 1)) sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, 2, true)) - player = Player.Release(player) //swap new player case msg @ SpawnRequestMessage(u1, u2, u3, u4, u5) => log.info(s"SpawnRequestMessage: $msg") @@ -1439,20 +1446,19 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(building) => scala.util.Random.shuffle(building.Amenities.filter(_.isInstanceOf[SpawnTube])).headOption match { //TODO temporary shuffle case Some(tube) => - log.info(s"SpawnRequestMessage: new player was at position ${player.Position}") - player.Position = tube.Position - player.Orientation = tube.Orientation - player.FacingYawUpper = 0 - log.info(s"SpawnRequestMessage: new player moved to position ${player.Position}") + val tplayer = SpawnRequest(player) //new player + tplayer.Position = tube.Position + tplayer.Orientation = tube.Orientation + log.info(s"SpawnRequestMessage: new player will spawn in ${building.Id} @ tube ${tube.GUID.guid}") sendResponse(AvatarDeadStateMessage(DeadState.RespawnTime, 10000, 10000, Vector3.Zero, 2, true)) import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global - context.system.scheduler.scheduleOnce(10 seconds, taskResolver, RegisterAvatar(player)) + context.system.scheduler.scheduleOnce(10 seconds, taskResolver, RegisterAvatar(tplayer)) case None => log.warn(s"SpawnRequestMessage: can not find a spawn point in this spawn group - $u2") } case None => - log.warn(s"SpawnRequestMessage: can not find somewhere to spawn in ${continent.Id}") + log.warn(s"SpawnRequestMessage: can not find somewhere to spawn on ${continent.Id}") } case msg @ SetChatFilterMessage(send_channel, origin, whitelist) => @@ -2120,10 +2126,10 @@ class WorldSessionActor extends Actor with MDCContextAware { action match { case FavoritesAction.Unknown => ; case FavoritesAction.Save => - player.SaveLoadout(name, line) + avatar.SaveLoadout(player, name, line) sendResponse(FavoritesMessage(0, player_guid, line, name)) case FavoritesAction.Delete => - player.DeleteLoadout(line) + avatar.DeleteLoadout(line) sendResponse(FavoritesMessage(0, player_guid, line, "")) } } @@ -2540,10 +2546,32 @@ class WorldSessionActor extends Actor with MDCContextAware { override def onFailure(ex : Throwable) : Unit = { localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WSA } - }, List(GUIDTask.RegisterAvatar(tplayer)(continent.GUID)) + }, List(RegisterLightweightAvatar(tplayer)(continent.GUID)) ) } + //TODO temporary function for registering avatar without locker contents + def RegisterLightweightAvatar(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + import net.psforever.objects.LockerContainer + import net.psforever.objects.inventory.InventoryItem + val holsterTasks = tplayer.Holsters().filter(_.Equipment.isDefined).map(slot =>{ + GUIDTask.RegisterEquipment(slot.Equipment.get)(guid) + }).toList + val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => GUIDTask.RegisterEquipment(entry.obj)(guid)}) + TaskResolver.GiveTask(GUIDTask.RegisterObjectTask(tplayer)(guid).task, holsterTasks ++ inventoryTasks) + } + + //TODO temporary function for unregistering avatar without locker contents + def UnregisterLightweightAvatar(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + import net.psforever.objects.LockerContainer + import net.psforever.objects.inventory.InventoryItem + val holsterTasks = tplayer.Holsters().filter(_.Equipment.isDefined).map(slot =>{ + GUIDTask.UnregisterEquipment(slot.Equipment.get)(guid) + }).toList + val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => GUIDTask.UnregisterEquipment(entry.obj)(guid)}) + TaskResolver.GiveTask(GUIDTask.UnregisterObjectTask(tplayer)(guid).task, holsterTasks ++ inventoryTasks) + } + /** * Construct tasking that adds a completed and registered vehicle into the scene. * Use this function to renew the globally unique identifiers on a vehicle that has already been added to the scene once. @@ -2807,21 +2835,21 @@ class WorldSessionActor extends Actor with MDCContextAware { * @param bep the change in experience points, positive by assertion * @return the player's current battle experience points */ - def AwardBattleExperiencePoints(tplayer : Player, bep : Long) : Long = { - val oldBep = tplayer.BEP + def AwardBattleExperiencePoints(avatar : Avatar, bep : Long) : Long = { + val oldBep = avatar.BEP if(bep <= 0) { - log.error(s"trying to set $bep battle experience points on $tplayer; value can not be negative") + log.error(s"trying to set $bep battle experience points on $avatar; value can not be negative") oldBep } else { val oldSlots = DetailedCharacterData.numberOfImplantSlots(oldBep) val newBep = oldBep + bep val newSlots = DetailedCharacterData.numberOfImplantSlots(newBep) - tplayer.BEP = newBep + avatar.BEP = newBep if(newSlots > oldSlots) { (oldSlots until newSlots).foreach(slotNumber => { - tplayer.Implants(slotNumber).Unlocked = true - log.info(s"unlocking implant slot $slotNumber for $tplayer") + avatar.Implants(slotNumber).Unlocked = true + log.info(s"unlocking implant slot $slotNumber for $avatar") }) } newBep @@ -3290,6 +3318,23 @@ class WorldSessionActor extends Actor with MDCContextAware { log.debug(s"ObjectCreateDetailedMessage: $dcdata") } + def SpawnRequest(tplayer : Player) : Player = { + val faction = tplayer.Faction + val obj = Player.Respawn(tplayer) + //obj.VehicleOwned = tplayer.VehicleOwned + //obj.Continent = tplayer.Continent + obj.Slot(0).Equipment = Tool(StandardPistol(faction)) + obj.Slot(2).Equipment = Tool(suppressor) + obj.Slot(4).Equipment = Tool(StandardMelee(faction)) + obj.Slot(6).Equipment = AmmoBox(bullet_9mm) + obj.Slot(9).Equipment = AmmoBox(bullet_9mm) + obj.Slot(12).Equipment = AmmoBox(bullet_9mm) + obj.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) + obj.Slot(36).Equipment = AmmoBox(StandardPistolAmmo(faction)) + obj.Slot(39).Equipment = SimpleItem(remote_electronics_kit) + obj + } + def failWithError(error : String) = { log.error(error) sendResponse(ConnectionClose()) diff --git a/pslogin/src/test/scala/AvatarServiceTest.scala b/pslogin/src/test/scala/AvatarServiceTest.scala index 56e02385f..94d5a1470 100644 --- a/pslogin/src/test/scala/AvatarServiceTest.scala +++ b/pslogin/src/test/scala/AvatarServiceTest.scala @@ -110,7 +110,7 @@ class EquipmentOnGroundTest extends ActorTest { } class LoadPlayerTest extends ActorTest { - val obj = Player("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, 1) + val obj = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, 1)) obj.GUID = PlanetSideGUID(10) obj.Slot(5).Equipment.get.GUID = PlanetSideGUID(11) val pdata = obj.Definition.Packet.DetailedConstructorData(obj).get From 20b7726653d9c09dbe73bcfb45eef90e7f7657a3 Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 21 Mar 2018 08:50:42 -0400 Subject: [PATCH 4/9] Stripped down LivePlayerList functionality, moving player lists onto the appropriate corresponding zones. Corpses are now created, stored, and deleted. --- .../psforever/objects/LivePlayerList.scala | 228 ++---------------- .../converter/CorpseConverter.scala | 124 ++++++++++ .../converter/VehicleConverter.scala | 2 +- .../net/psforever/objects/zones/Zone.scala | 105 +++++++- .../objects/zones/ZonePopulationActor.scala | 43 ++++ .../src/main/scala/WorldSessionActor.scala | 126 ++++++---- .../scala/services/avatar/AvatarAction.scala | 3 + .../services/avatar/AvatarResponse.scala | 2 + .../scala/services/avatar/AvatarService.scala | 12 +- .../avatar/support/UndertakerActor.scala | 145 +++++++++++ 10 files changed, 526 insertions(+), 264 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala create mode 100644 common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala create mode 100644 pslogin/src/main/scala/services/avatar/support/UndertakerActor.scala diff --git a/common/src/main/scala/net/psforever/objects/LivePlayerList.scala b/common/src/main/scala/net/psforever/objects/LivePlayerList.scala index c0e398568..af1ab40ed 100644 --- a/common/src/main/scala/net/psforever/objects/LivePlayerList.scala +++ b/common/src/main/scala/net/psforever/objects/LivePlayerList.scala @@ -1,9 +1,6 @@ // Copyright (c) 2017 PSForever package net.psforever.objects -import net.psforever.packet.game.PlanetSideGUID - -import scala.annotation.tailrec import scala.collection.concurrent.{Map, TrieMap} /** @@ -12,157 +9,42 @@ import scala.collection.concurrent.{Map, TrieMap} */ private class LivePlayerList { /** key - the session id; value - a `Player` object */ - private val sessionMap : Map[Long, Player] = new TrieMap[Long, Player] - /** the index of the List corresponds to zone number 1-32 with 0 being "Nowhere" */ - /** each mapping: key - the global unique identifier; value - the session id */ - private val zoneMap : List[Map[Int, Long]] = List.fill(33)(new TrieMap[Int,Long]) + private val sessionMap : Map[Long, Avatar] = new TrieMap[Long, Avatar] - def WorldPopulation(predicate : ((_, Player)) => Boolean) : List[Player] = { + def WorldPopulation(predicate : ((_, Avatar)) => Boolean) : List[Avatar] = { sessionMap.filter(predicate).values.toList } - def ZonePopulation(zone : Int, predicate : ((_, Player)) => Boolean) : List[Player] = { - zoneMap.lift(zone) match { - case Some(map) => - val list = map.values.toList - sessionMap.filter({ case ((sess, _)) => list.contains(sess) }).filter(predicate).values.toList + def Add(sessionId : Long, avatar : Avatar) : Boolean = { + sessionMap.values.find(char => char.equals(avatar)) match { case None => - Nil - } - } - - def Add(sessionId : Long, player : Player) : Boolean = { - sessionMap.values.find(char => char.equals(player)) match { - case None => - sessionMap.putIfAbsent(sessionId, player).isEmpty + sessionMap.putIfAbsent(sessionId, avatar).isEmpty case Some(_) => false } } - def Remove(sessionId : Long) : Option[Player] = { - sessionMap.remove(sessionId) match { - case Some(char) => - zoneMap.foreach(zone => { - recursiveRemoveSession(zone.iterator, sessionId) match { - case Some(guid) => - zone.remove(guid) - case None => ; - } - }) - Some(char) - case None => - None - } + def Remove(sessionId : Long) : Option[Avatar] = { + sessionMap.remove(sessionId) } - @tailrec private def recursiveRemoveSession(iter : Iterator[(Int, Long)], sessionId : Long) : Option[Int] = { - if(!iter.hasNext) { - None - } - else { - val (guid : Int, sess : Long) = iter.next - if(sess == sessionId) { - Some(guid) - } - else { - recursiveRemoveSession(iter, sessionId) - } - } - } - - def Get(zone : Int, guid : PlanetSideGUID) : Option[Player] = { - Get(zone, guid.guid) - } - - def Get(zone : Int, guid : Int) : Option[Player] = { - zoneMap.lift(zone) match { - case Some(map) => - map.get(guid) match { - case Some(sessionId) => - sessionMap.get(sessionId) - case _ => - None - } - case None => - None - } - } - - def Assign(zone: Int, sessionId : Long, guid : PlanetSideGUID) : Boolean = Assign(zone, sessionId, guid.guid) - - def Assign(zone : Int, sessionId : Long, guid : Int) : Boolean = { - sessionMap.get(sessionId) match { - case Some(_) => - zoneMap.lift(zone) match { - case Some(zn) => - AssignToZone(zn, sessionId, guid) - case None => - false - } - - case None => - false - } - } - - private def AssignToZone(zone : Map[Int, Long], sessionId : Long, guid : Int) : Boolean = { - zone.get(guid) match { - case Some(_) => - false - case None => - zone(guid) = sessionId - true - } - } - - def Drop(zone : Int, guid : PlanetSideGUID) : Option[Player] = Drop(zone, guid.guid) - - def Drop(zone : Int, guid : Int) : Option[Player] = { - zoneMap.lift(zone) match { - case Some(map) => - map.remove(guid) match { - case Some(sessionId) => - sessionMap.get(sessionId) - case None => - None - } - case None => - None - } - } - - def Shutdown : List[Player] = { + def Shutdown : List[Avatar] = { val list = sessionMap.values.toList sessionMap.clear - zoneMap.foreach(map => map.clear()) list } } /** * A class for storing `Player` mappings for users that are currently online. - * The mapping system is tightly coupled between the `Player` class and to an instance of `WorldSessionActor`. - * Looser couplings exist between the instance of `WorldSessionActor` and a given `Player`'s globally unique id. - * These looser couplings are zone-specific. - * Though the user may have local knowledge of the zone they inhabit on their `Player` object, - * it should not be trusted.
+ * The mapping system is tightly coupled between the `Avatar` class and to an instance of `WorldSessionActor`. *
* Use:
- * 1) When a users logs in during `WorldSessionActor`, associate that user's session id and the character.
- *        `LivePlayerList.Add(session, player)`
- * 2) When that user's chosen character is declared his avatar using `SetCurrentAvatarMessage`, - * also associate the user's session with their current GUID.
- *        `LivePlayerList.Assign(zone, session, guid)`
- * 3) Repeat the previous step for as many times the user's GUID changes, especially during the aforementioned condition.
- * 4a) In between the previous two steps, a user's character may be referenced by their current GUID.
- *        `LivePlayerList.Get(zone, guid)`
- * 4b) Also in between those same previous steps, a range of characters may be queried based on provided statistics.
+ * 1) When a users logs in during `WorldSessionActor`, associate that user's session id and their character (avatar).
+ *        `LivePlayerList.Add(session, avatar)`
+ * 2) In between the previous two steps, a range of characters may be queried based on provided statistics.
*        `LivePlayerList.WorldPopulation(...)`
- *        `LivePlayerList.ZonePopulation(zone, ...)`
- * 5) When the user navigates away from a region completely, their entry is forgotten.
- *        `LivePlayerList.Drop(zone, guid)`
- * 6) When the user leaves the game entirely, his character's entries are removed from the mappings.
+ * 3) When the user leaves the game entirely, his character's entry is removed from the mapping.
*        `LivePlayerList.Remove(session)` */ object LivePlayerList { @@ -179,100 +61,28 @@ object LivePlayerList { * @param predicate the conditions for filtering the live `Player`s * @return a list of users's `Player`s that fit the criteria */ - def WorldPopulation(predicate : ((_, Player)) => Boolean) : List[Player] = Instance.WorldPopulation(predicate) - - /** - * Given some criteria, examine the mapping of user characters for a zone and find the ones that fulfill the requirements.
- *
- * Note the signature carefully. - * A two-element tuple is checked, but only the second element of that tuple - a `Player` - is eligible for being queried. - * The first element is ignored. - * Even a predicate as simple as `{ case ((x : Long, _)) => x > 0 }` will not work for that reason. - * @param zone the number of the zone - * @param predicate the conditions for filtering the live `Player`s - * @return a list of users's `Player`s that fit the criteria - */ - def ZonePopulation(zone : Int, predicate : ((_, Player)) => Boolean) : List[Player] = Instance.ZonePopulation(zone, predicate) + def WorldPopulation(predicate : ((_, Avatar)) => Boolean) : List[Avatar] = Instance.WorldPopulation(predicate) /** * Create a mapped entry between the user's session and a user's character. * Neither the player nor the session may exist in the current mappings if this is to work. * @param sessionId the session - * @param player the character + * @param avatar the character * @return `true`, if the session was association was made; `false`, otherwise */ - def Add(sessionId : Long, player : Player) : Boolean = Instance.Add(sessionId, player) + def Add(sessionId : Long, avatar : Avatar) : Boolean = Instance.Add(sessionId, avatar) /** * Remove all entries related to the given session identifier from the mappings. - * The player no longer counts as "online." - * This function cleans up __all__ associations - those created by `Add`, and those created by `Assign`. + * The character no longer counts as "online." * @param sessionId the session * @return any character that was afffected by the mapping removal */ - def Remove(sessionId : Long) : Option[Player] = Instance.Remove(sessionId) - - /** - * Get a user's character from the mappings. - * @param zone the number of the zone - * @param guid the current GUID of the character - * @return the character, if it can be found using the GUID - */ - def Get(zone : Int, guid : PlanetSideGUID) : Option[Player] = Instance.Get(zone, guid) - - /** - * Get a user's character from the mappings. - * @param zone the number of the zone - * @param guid the current GUID of the character - * @return the character, if it can be found using the GUID - */ - def Get(zone : Int, guid : Int) : Option[Player] = Instance.Get(zone, guid) - - /** - * Given a session that maps to a user's character, create a mapping between the character's current GUID and the session. - * If the user already has a GUID in the mappings, remove it and assert the new one. - * @param zone the number of the zone - * @param sessionId the session - * @param guid the GUID to associate with the character; - * technically, it has already been assigned and should be findable using `{character}.GUID.guid` - * @return `true`, if the mapping was created; - * `false`, if the session can not be found or if the character's GUID doesn't match the one provided - */ - def Assign(zone : Int, sessionId : Long, guid : PlanetSideGUID) : Boolean = Instance.Assign(zone, sessionId, guid) - - /** - * Given a session that maps to a user's character, create a mapping between the character's current GUID and the session. - * If the user already has a GUID in the mappings, remove it and assert the new one. - * @param zone the number of the zone - * @param sessionId the session - * @param guid the GUID to associate with the character; - * technically, it has already been assigned and should be findable using `{character}.GUID.guid` - * @return `true`, if the mapping was created; - * `false`, if the session can not be found or if the character's GUID doesn't match the one provided - */ - def Assign(zone : Int, sessionId : Long, guid : Int) : Boolean = Instance.Assign(zone, sessionId, guid) - - /** - * Given a GUID, remove any record of it. - * @param zone the number of the zone - * @param guid a GUID associated with the character; - * it does not have to be findable using `{character}.GUID.guid` - * @return any `Player` that may have been associated with this GUID - */ - def Drop(zone : Int, guid : PlanetSideGUID) : Option[Player] = Instance.Drop(zone, guid) - - /** - * Given a GUID, remove any record of it. - * @param zone the number of the zone - * @param guid a GUID associated with the character; - * it does not have to be findable using `{character}.GUID.guid` - * @return any `Player` that may have been associated with this GUID - */ - def Drop(zone : Int, guid : Int) : Option[Player] = Instance.Drop(zone, guid) + def Remove(sessionId : Long) : Option[Avatar] = Instance.Remove(sessionId) /** * Hastily remove all mappings and ids. * @return an unsorted list of the characters that were still online */ - def Shutdown : List[Player] = Instance.Shutdown + def Shutdown : List[Avatar] = Instance.Shutdown } diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala new file mode 100644 index 000000000..73699a10b --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala @@ -0,0 +1,124 @@ +// Copyright (c) 2017 PSForever +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 scala.annotation.tailrec +import scala.util.{Failure, Success, Try} + +class CorpseConverter extends AvatarConverter { + override def ConstructorData(obj : Player) : Try[CharacterData] = + Failure(new Exception("CorpseConverter should not be used to generate CharacterData")) + + override def DetailedConstructorData(obj : Player) : Try[DetailedCharacterData] = { + Success( + DetailedCharacterData( + MakeAppearanceData(obj), + 0, 0, 0, 0, 0, 0, 0, + Nil, Nil, Nil, Nil, + None, + InventoryData((MakeHolsters(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)), + DrawnSlot.None + ) + ) + } + + /** + * Compose some data from a `Player` into a representation common to both `CharacterData` and `DetailedCharacterData`. + * @param obj the `Player` game object + * @return the resulting `CharacterAppearanceData` + */ + private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = { + CharacterAppearanceData( + PlacementData(obj.Position, obj.Orientation), + BasicCharacterData(obj.Name, obj.Faction, CharacterGender.Male, 0, 0), + 0, + false, + false, + obj.ExoSuit, + "", + 0, + true, + obj.Orientation.y, //TODO is this important? + 0, + true, + GrenadeState.None, + false, + false, + false, + RibbonBars() + ) + } + + /** + * Given a player with an inventory, convert the contents of that inventory into converted-decoded packet data. + * The inventory is not represented in a `0x17` `Player`, so the conversion is only valid for `0x18` avatars. + * It will always be "`Detailed`". + * @param obj the `Player` game object + * @return a list of all items that were in the inventory in decoded packet form + */ + private def MakeInventory(obj : Player) : List[InternalSlot] = { + obj.Inventory.Items + .map({ + case(_, item) => + val equip : Equipment = item.obj + BuildEquipment(item.start, equip) + }).toList + } + + /** + * Given a player with equipment holsters, convert the contents of those holsters into converted-decoded packet data. + * The decoded packet form is determined by the function in the parameters as both `0x17` and `0x18` conversions are available, + * with exception to the contents of the fifth slot. + * The fifth slot is only represented if the `Player` is an `0x18` type. + * @param obj the `Player` game object + * @return a list of all items that were in the holsters in decoded packet form + */ + private def MakeHolsters(obj : Player) : List[InternalSlot] = { + recursiveMakeHolsters(obj.Holsters().iterator) + } + + /** + * Given some equipment holsters, convert the contents of those holsters into converted-decoded packet data. + * @param iter an `Iterator` of `EquipmentSlot` objects that are a part of the player's holsters + * @param list the current `List` of transformed data + * @param index which holster is currently being explored + * @return the `List` of inventory data created from the holsters + */ + @tailrec private def recursiveMakeHolsters(iter : Iterator[EquipmentSlot], list : List[InternalSlot] = Nil, index : Int = 0) : List[InternalSlot] = { + if(!iter.hasNext) { + list + } + else { + val slot : EquipmentSlot = iter.next + if(slot.Equipment.isDefined) { + val equip : Equipment = slot.Equipment.get + recursiveMakeHolsters( + iter, + list :+ BuildEquipment(index, equip), + index + 1 + ) + } + else { + recursiveMakeHolsters(iter, list, index + 1) + } + } + } + + /** + * A builder method for turning an object into `0x17` decoded packet form. + * @param index the position of the object + * @param equip the game object + * @return the game object in decoded packet form + */ + private def BuildEquipment(index : Int, equip : Equipment) : InternalSlot = { + InternalSlot(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.Packet.DetailedConstructorData(equip).get) + } +} + +object CorpseConverter { + val converter = new CorpseConverter +} diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala index 5a0ba6300..81f3f2b65 100644 --- a/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala +++ b/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala @@ -22,7 +22,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() { PlanetSideGUID(0) //if(obj.Owner.isDefined) { obj.Owner.get } else { PlanetSideGUID(0) } //TODO is this really Owner? ), 0, - obj.Health / obj.MaxHealth * 255, //TODO not precise + 255 * obj.Health / obj.MaxHealth, //TODO not precise false, false, obj.DeploymentState, false, diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala index 859aafa2d..7281891d7 100644 --- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -3,7 +3,7 @@ package net.psforever.objects.zones import akka.actor.{ActorContext, ActorRef, Props} import akka.routing.RandomPool -import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle} +import net.psforever.objects.{Avatar, PlanetSideGameObject, Player, Vehicle} import net.psforever.objects.equipment.Equipment import net.psforever.objects.guid.NumberPoolHub import net.psforever.objects.guid.actor.UniqueNumberSystem @@ -14,6 +14,7 @@ import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.Vector3 import scala.annotation.tailrec +import scala.collection.concurrent.TrieMap import scala.collection.mutable.ListBuffer import scala.collection.immutable.{Map => PairMap} @@ -54,6 +55,12 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { private var vehicles : List[Vehicle] = List[Vehicle]() /** */ private var transport : ActorRef = ActorRef.noSender + /** */ + private val players : TrieMap[Avatar, Option[Player]] = TrieMap[Avatar, Option[Player]]() + /** */ + private var corpses : List[Player] = List[Player]() + /** */ + private var population : ActorRef = ActorRef.noSender private var buildings : PairMap[Int, Building] = PairMap.empty[Int, Building] @@ -80,6 +87,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { 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") transport = context.actorOf(Props(classOf[ZoneVehicleActor], this), s"$Id-vehicles") + population = context.actorOf(Props(classOf[ZonePopulationActor], this), s"$Id-players") Map.LocalObjects.foreach({ builderObject => builderObject.Build }) MakeBuildings(context) @@ -179,6 +187,12 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { def Vehicles : List[Vehicle] = vehicles + def Players : List[Avatar] = players.keys.toList + + def LivePlayers : List[Player] = players.values.collect( { case Some(tplayer) => tplayer }).toList + + def Corpses : List[Player] = corpses + def AddVehicle(vehicle : Vehicle) : List[Vehicle] = { vehicles = vehicles :+ vehicle Vehicles @@ -208,6 +222,78 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { } } + def PopulationJoin(avatar : Avatar) : Boolean = { + players.get(avatar) match { + case Some(_) => + false + case None => + players += avatar -> None + true + } + } + + def PopulationLeave(avatar : Avatar) : Option[Player] = { + players.remove(avatar) match { + case None => + None + case Some(tplayer) => + tplayer + } + } + + def PopulationSpawn(avatar : Avatar, player : Player) : Option[Player] = { + players.get(avatar) match { + case None => + None + case Some(tplayer) => + tplayer match { + case Some(aplayer) => + Some(aplayer) + case None => + players(avatar) = Some(player) + Some(player) + } + } + } + + def PopulationRelease(avatar : Avatar) : Option[Player] = { + players.get(avatar) match { + case None => + None + case Some(tplayer) => + players(avatar) = None + tplayer + } + } + + def CorpseAdd(player : Player) : Unit = { + if(player.isBackpack && recursiveFindCorpse(players.values.filter(_.nonEmpty).map(_.get).iterator, player.GUID).isEmpty) { + corpses = corpses :+ player + } + } + + def CorpseRemove(player : Player) : Unit = { + recursiveFindCorpse(corpses.iterator, player.GUID) match { + case Some(index) => + corpses = corpses.take(index-1) ++ corpses.drop(index) + case None => ; + } + } + + @tailrec final def recursiveFindCorpse(iter : Iterator[Player], guid : PlanetSideGUID, index : Int = 0) : Option[Int] = { + if(!iter.hasNext) { + None + } + else { + if(iter.next.GUID == guid) { + Some(index) + } + else { + recursiveFindCorpse(iter, guid, index + 1) + } + } + } + /** * Coordinate `Equipment` that has been dropped on the ground or to-be-dropped on the ground. * @return synchronized reference to the ground @@ -220,6 +306,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { def Transport : ActorRef = transport + def Population : ActorRef = population + def Buildings : Map[Int, Building] = buildings def Building(id : Int) : Option[Building] = { @@ -266,6 +354,21 @@ object Zone { */ final case class Init() + object Population { + final case class Join(avatar : Avatar) + final case class Leave(avatar : Avatar) + final case class Spawn(avatar : Avatar, player : Player) + final case class Release(avatar : Avatar) + final case class PlayerHasLeft(zone : Zone, player : Option[Player]) //Leave(avatar), but still has a player + final case class PlayerAlreadySpawned(player : Player) //Spawn(avatar, player), but avatar already has a player + final case class PlayerCanNotSpawn(zone : Zone, player : Player) //Spawn(avatar, player), but avatar did not initially Join(avatar) + } + + object Corpse { + final case class Add(player : Player) + final case class Remove(player : Player) + } + /** * Message to relinguish an item and place in on the ground. * @param item the piece of `Equipment` diff --git a/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala new file mode 100644 index 000000000..6787166c4 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala @@ -0,0 +1,43 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.zones + +import akka.actor.Actor + +class ZonePopulationActor(zone : Zone) extends Actor { + def receive : Receive = { + case Zone.Population.Join(avatar) => + zone.PopulationJoin(avatar) + + case Zone.Population.Leave(avatar) => + zone.PopulationLeave(avatar) match { + case None => ; + case player @ Some(_) => + sender ! Zone.Population.PlayerHasLeft(zone, player) + } + + case Zone.Population.Spawn(avatar, player) => + zone.PopulationSpawn(avatar, player) match { + case Some(tplayer) => + if(tplayer != player) { + sender ! Zone.Population.PlayerCanNotSpawn(zone, player) + } + case None => + sender ! Zone.Population.PlayerCanNotSpawn(zone, player) + } + + case Zone.Population.Release(avatar) => + zone.PopulationRelease(avatar) match { + case Some(_) => ; + case None => + sender ! Zone.Population.PlayerHasLeft(zone, None) + } + + case Zone.Corpse.Add(player) => + zone.CorpseAdd(player) + + case Zone.Corpse.Remove(player) => + zone.CorpseRemove(player) + + case _ => ; + } +} diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index fc24d57e2..7ce17fe86 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -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.converter.CorpseConverter import net.psforever.objects.equipment._ import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} @@ -67,40 +68,37 @@ class WorldSessionActor extends Actor with MDCContextAware { var progressBarUpdate : Cancellable = DefaultCancellable.obj override def postStop() = { - if(clientKeepAlive != null) - clientKeepAlive.cancel() - localService ! Service.Leave() - vehicleService ! Service.Leave() - avatarService ! Service.Leave() - LivePlayerList.Remove(sessionId) match { - case Some(tplayer) => - tplayer.VehicleSeated match { - case Some(vehicle_guid) => - //TODO do this at some other time - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(tplayer.GUID, 0, true, vehicle_guid)) - case None => ; - } - tplayer.VehicleOwned match { - case Some(vehicle_guid) => - continent.GUID(vehicle_guid) match { - case Some(vehicle : Vehicle) => - vehicle.Owner = None - //TODO temporary solution; to un-own, permit driver seat to Empire access level - vehicle.PermissionGroup(10, VehicleLockState.Empire.id) - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SeatPermissions(tplayer.GUID, vehicle_guid, 10, VehicleLockState.Empire.id)) - case _ => ; - } - case None => ; - } - - if(tplayer.HasGUID) { - val guid = tplayer.GUID - avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(guid, guid)) - taskResolver ! GUIDTask.UnregisterAvatar(tplayer)(continent.GUID) - //TODO normally, the actual player avatar persists a minute or so after the user disconnects - } + clientKeepAlive.cancel() + localService ! Service.Leave() + vehicleService ! Service.Leave() + avatarService ! Service.Leave() + LivePlayerList.Remove(sessionId) + if(player != null && player.HasGUID) { + val player_guid = player.GUID + player.VehicleSeated match { + case Some(vehicle_guid) => + //TODO do this at some other time + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, 0, true, vehicle_guid)) case None => ; + } + player.VehicleOwned match { + case Some(vehicle_guid) => + continent.GUID(vehicle_guid) match { + case Some(vehicle : Vehicle) => + vehicle.Owner = None + //TODO temporary solution; to un-own, permit driver seat to Empire access level + vehicle.PermissionGroup(10, VehicleLockState.Empire.id) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SeatPermissions(player_guid, vehicle_guid, 10, VehicleLockState.Empire.id)) + case _ => ; + } + case None => ; + } + continent.Population ! Zone.Population.Release(avatar) + continent.Population ! Zone.Population.Leave(avatar) + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ObjectDelete(player_guid, player_guid)) + taskResolver ! GUIDTask.UnregisterAvatar(player)(continent.GUID) + //TODO normally, the actual player avatar persists a minute or so after the user disconnects } } @@ -151,6 +149,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case game @ GamePacket(_, _, _) => handlePktContainer(game) // temporary hack to keep the client from disconnecting + //it's been a "temporary hack" since 2016 :P case PokeClient() => sendResponse(KeepAliveMessage()) @@ -281,6 +280,11 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + case AvatarResponse.Release(tplayer) => + if(tplayer_guid != guid) { + turnPlayerIntoCorpse(tplayer) + } + case AvatarResponse.Reload(item_guid) => if(tplayer_guid != guid) { sendResponse(ReloadMessage(item_guid, 1, 0)) @@ -973,11 +977,11 @@ class WorldSessionActor extends Actor with MDCContextAware { case Zone.ClientInitialization(zone) => val continentNumber = zone.Number - val poplist = LivePlayerList.ZonePopulation(continentNumber, _ => true) + val poplist = zone.Players val popBO = 0 //TODO black ops test (partition) - val popTR = poplist.count(_.Faction == PlanetSideEmpire.TR) - val popNC = poplist.count(_.Faction == PlanetSideEmpire.NC) - val popVS = poplist.count(_.Faction == PlanetSideEmpire.VS) + val popTR = poplist.count(_.faction == PlanetSideEmpire.TR) + val popNC = poplist.count(_.faction == PlanetSideEmpire.NC) + val popVS = poplist.count(_.faction == PlanetSideEmpire.VS) zone.Buildings.foreach({ case(id, building) => initBuilding(continentNumber, id, building) }) sendResponse(ZonePopulationUpdateMessage(continentNumber, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO)) @@ -990,7 +994,16 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ZoneForcedCavernConnectionsMessage(continentNumber, 0)) sendResponse(HotSpotUpdateMessage(continentNumber, 1, Nil)) //normally set in bulk; should be fine doing per continent + case Zone.Population.PlayerHasLeft(zone, None) => + log.info(s"$avatar does not have a body on ${zone.Id}") + + case Zone.Population.PlayerHasLeft(zone, Some(tplayer)) => + if(tplayer.isAlive) { + log.info(s"$tplayer has left zone ${zone.Id}") + } + case InterstellarCluster.ClientInitializationComplete() => + LivePlayerList.Add(sessionId, avatar) //PropertyOverrideMessage sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1)) sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list @@ -1002,6 +1015,7 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info(s"Zone $zoneId has been loaded") player.Continent = zoneId continent = zone + continent.Population ! Zone.Population.Join(avatar) taskResolver ! RegisterNewAvatar(player) case NewPlayerLoaded(tplayer) => @@ -1009,7 +1023,7 @@ class WorldSessionActor extends Actor with MDCContextAware { player = tplayer //LoadMapMessage will cause the client to send back a BeginZoningMessage packet (see below) sendResponse(LoadMapMessage(continent.Map.Name, continent.Id, 40100,25,true,3770441820L)) - AvatarCreate() + AvatarCreate() //important! the LoadMapMessage must be processed by the client before the avatar is created case PlayerLoaded(tplayer) => log.info(s"Player $tplayer has been loaded") @@ -1026,7 +1040,6 @@ class WorldSessionActor extends Actor with MDCContextAware { case SetCurrentAvatar(tplayer) => player = tplayer val guid = tplayer.GUID - LivePlayerList.Assign(continent.Number, sessionId, guid) sendResponse(SetCurrentAvatarMessage(guid,0,0)) (0 until DetailedCharacterData.numberOfImplantSlots(tplayer.BEP)).foreach(slot => { @@ -1256,7 +1269,6 @@ class WorldSessionActor extends Actor with MDCContextAware { case CharacterRequestAction.Delete => sendResponse(ActionResultMessage(false, Some(1))) case CharacterRequestAction.Select => - LivePlayerList.Add(sessionId, player) //TODO check if can spawn on last continent/location from player? //TODO if yes, get continent guid accessors //TODO if no, get sanctuary guid accessors and reset the player's expectations @@ -1296,20 +1308,18 @@ class WorldSessionActor extends Actor with MDCContextAware { ) }) //load active players in zone - LivePlayerList.ZonePopulation(continent.Number, _ => true).foreach(char => { + continent.LivePlayers.filterNot(_.GUID == player.GUID).foreach(char => { sendResponse( ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get) ) }) + //load corpses in zone + continent.Corpses.foreach( turnPlayerIntoCorpse(_) ) //load active vehicles in zone continent.Vehicles.foreach(vehicle => { val definition = vehicle.Definition sendResponse( - ObjectCreateMessage( - definition.ObjectId, - vehicle.GUID, - definition.Packet.ConstructorData(vehicle).get - ) + ObjectCreateMessage(definition.ObjectId, vehicle.GUID, definition.Packet.ConstructorData(vehicle).get) ) //seat vehicle occupants vehicle.Definition.MountPoints.values.foreach(seat_num => { @@ -1362,7 +1372,7 @@ class WorldSessionActor extends Actor with MDCContextAware { self ! SetCurrentAvatar(player) case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) => - if(!player.isAlive) { + if(player.isAlive) { player.Position = pos player.Velocity = vel player.Orientation = Vector3(player.Orientation.x, pitch, yaw) @@ -1434,10 +1444,14 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ReleaseAvatarRequestMessage() => log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released") + //TODO is it easier to delete the player, then re-create them as a corpse? player.Release + continent.Population ! Zone.Population.Release(avatar) + continent.Population ! Zone.Corpse.Add(player) val knife = player.Slot(4).Equipment.get taskResolver ! RemoveEquipmentFromSlot(player, knife, 4) - sendResponse(PlanetsideAttributeMessage(player.GUID, 6, 1)) + turnPlayerIntoCorpse(player) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.Release(player, continent)) sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, 2, true)) case msg @ SpawnRequestMessage(u1, u2, u3, u4, u5) => @@ -1449,7 +1463,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val tplayer = SpawnRequest(player) //new player tplayer.Position = tube.Position tplayer.Orientation = tube.Orientation - log.info(s"SpawnRequestMessage: new player will spawn in ${building.Id} @ tube ${tube.GUID.guid}") + log.info(s"SpawnRequestMessage: new player will spawn in ${building.Id} @ ${tube.GUID.guid}") sendResponse(AvatarDeadStateMessage(DeadState.RespawnTime, 10000, 10000, Vector3.Zero, 2, true)) import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global @@ -1507,7 +1521,9 @@ class WorldSessionActor extends Actor with MDCContextAware { player.Die sendResponse(PlanetsideAttributeMessage(player_guid, 0, 0)) sendResponse(PlanetsideAttributeMessage(player_guid, 2, 0)) - sendResponse(DestroyMessage(player_guid, player_guid, PlanetSideGUID(0), pos)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 0, 0)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 2, 0)) + sendResponse(DestroyMessage(player_guid, player_guid, PlanetSideGUID(0), pos)) //how many players get this message? sendResponse(AvatarDeadStateMessage(DeadState.Dead, 300000, 300000, pos, 2, true)) } @@ -1521,8 +1537,8 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(DropSession(sessionId, "user quit")) } - if(contents.trim.equals("!loc")) { //dev hack; consider bang-commands to complement slash-commands - echoContents = s"pos=${player.Position.x}, ${player.Position.y}, ${player.Position.z}; ori=${player.Orientation.x}, ${player.Orientation.y}, ${player.Orientation.z}" + if(contents.trim.equals("!loc")) { //dev hack; consider bang-commands to complement slash-commands in future + echoContents = s"zone=${continent.Id} pos=${player.Position.x},${player.Position.y},${player.Position.z}; ori=${player.Orientation.x},${player.Orientation.y},${player.Orientation.z}" log.info(echoContents) } @@ -3315,6 +3331,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val dcdata = packet.DetailedConstructorData(player).get sendResponse(ObjectCreateDetailedMessage(ObjectClass.avatar, player.GUID, dcdata)) avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.LoadPlayer(player.GUID, packet.ConstructorData(player).get)) + continent.Population ! Zone.Population.Spawn(avatar, player) log.debug(s"ObjectCreateDetailedMessage: $dcdata") } @@ -3335,6 +3352,13 @@ class WorldSessionActor extends Actor with MDCContextAware { obj } + def turnPlayerIntoCorpse(tplayer : Player) : Unit = { + //sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 6, 1)) + sendResponse( + ObjectCreateDetailedMessage(ObjectClass.avatar, tplayer.GUID, CorpseConverter.converter.DetailedConstructorData(tplayer).get) + ) + } + def failWithError(error : String) = { log.error(error) sendResponse(ConnectionClose()) diff --git a/pslogin/src/main/scala/services/avatar/AvatarAction.scala b/pslogin/src/main/scala/services/avatar/AvatarAction.scala index 13ece68ee..54c5725f4 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarAction.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarAction.scala @@ -1,7 +1,9 @@ // Copyright (c) 2017 PSForever package services.avatar +import net.psforever.objects.Player import net.psforever.objects.equipment.Equipment +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} @@ -24,6 +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 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 diff --git a/pslogin/src/main/scala/services/avatar/AvatarResponse.scala b/pslogin/src/main/scala/services/avatar/AvatarResponse.scala index 0ffe562f8..f3c605ed6 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarResponse.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarResponse.scala @@ -1,6 +1,7 @@ // Copyright (c) 2017 PSForever 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.objectcreate.ConstructorData @@ -24,6 +25,7 @@ object AvatarResponse { final case class ObjectHeld(slot : Int) extends Response final case class PlanetsideAttribute(attribute_type : Int, attribute_value : Long) extends Response final case class PlayerState(msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Response + final case class Release(player : Player) extends Response final case class Reload(weapon_guid : PlanetSideGUID) extends Response final case class WeaponDryFire(weapon_guid : PlanetSideGUID) extends Response // final case class PlayerStateShift(itemID : PlanetSideGUID) extends Response diff --git a/pslogin/src/main/scala/services/avatar/AvatarService.scala b/pslogin/src/main/scala/services/avatar/AvatarService.scala index dcf249020..2d8f0f8c9 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarService.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarService.scala @@ -1,11 +1,14 @@ // Copyright (c) 2017 PSForever package services.avatar -import akka.actor.Actor +import akka.actor.{Actor, ActorRef, Props} +import services.avatar.support.UndertakerActor import services.{GenericEventBus, Service} class AvatarService extends Actor { - //import AvatarServiceResponse._ + private val undertaker : ActorRef = context.actorOf(Props[UndertakerActor], "corpse-removal-agent") + undertaker ! "startup" + private [this] val log = org.log4s.getLogger override def preStart = { @@ -87,6 +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) + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", player.GUID, AvatarResponse.Release(player)) + ) case AvatarAction.Reload(player_guid, weapon_guid) => AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.Reload(weapon_guid)) diff --git a/pslogin/src/main/scala/services/avatar/support/UndertakerActor.scala b/pslogin/src/main/scala/services/avatar/support/UndertakerActor.scala new file mode 100644 index 000000000..bc9e81220 --- /dev/null +++ b/pslogin/src/main/scala/services/avatar/support/UndertakerActor.scala @@ -0,0 +1,145 @@ +// 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.UnregisterAvatar(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 +} From ddc245054141474543e6f6a265cb3ffcb2279cb7 Mon Sep 17 00:00:00 2001 From: FateJH Date: Sat, 24 Mar 2018 00:28:02 -0400 Subject: [PATCH 5/9] Buildings now have simple type distinctions to work using the known output of a SpawnRequestMessage packet. Support for cross-continental respawning (actually a failure case for being unable to spawn). Corpse tuning and testing. --- .../scala/net/psforever/objects/Avatar.scala | 43 +- .../psforever/objects/GlobalDefinitions.scala | 8 +- .../scala/net/psforever/objects/Loadout.scala | 32 +- .../scala/net/psforever/objects/Player.scala | 11 +- .../net/psforever/objects/guid/GUIDTask.scala | 192 +++++--- .../guid/actor/UniqueNumberSystem.scala | 14 +- .../serverobject/structures/Building.scala | 26 +- .../structures/StructureType.scala | 20 + .../serverobject/structures/WarpGate.scala | 2 +- .../EquipmentTerminalDefinition.scala | 12 +- .../terminals/MatrixTerminalDefinition.scala | 5 +- .../terminals/SpawnTerminalDefinition.scala | 15 - .../objects/serverobject/tube/SpawnTube.scala | 40 +- .../tube/SpawnTubeDefinition.scala | 17 + .../objects/zones/InterstellarCluster.scala | 47 +- .../net/psforever/objects/zones/Zone.scala | 197 +++++---- .../psforever/objects/zones/ZoneActor.scala | 97 ++++- .../objects/zones/ZonePopulationActor.scala | 159 ++++++- .../packet/game/DisconnectMessage.scala | 13 +- .../packet/game/SpawnRequestMessage.scala | 12 +- .../scala/net/psforever/types/Vector3.scala | 18 +- common/src/test/scala/Vector3Test.scala | 5 + .../src/test/scala/objects/AvatarTest.scala | 397 +++++++++++++++++ .../src/test/scala/objects/BuildingTest.scala | 18 +- common/src/test/scala/objects/DoorTest.scala | 4 +- .../scala/objects/FactionAffinityTest.scala | 4 +- .../src/test/scala/objects/IFFLockTest.scala | 4 +- .../src/test/scala/objects/LoadoutTest.scala | 244 +++-------- .../src/test/scala/objects/PlayerTest.scala | 409 +++++++++++++----- .../objects/ServerObjectBuilderTest.scala | 24 +- .../test/scala/objects/SpawnTubeTest.scala | 59 +++ .../scala/objects/VehicleSpawnPadTest.scala | 4 +- common/src/test/scala/objects/ZoneTest.scala | 383 +++++++++++++++- .../guidtask/GUIDTaskRegister5Test.scala | 1 - .../guidtask/GUIDTaskRegister6Test.scala | 38 ++ .../guidtask/GUIDTaskUnregister6Test.scala | 44 ++ .../number/UniqueNumberSystemTest.scala | 34 ++ .../terminal/AirVehicleTerminalTest.scala | 4 +- .../objects/terminal/CertTerminalTest.scala | 4 +- .../DropshipVehicleTerminalTest.scala | 4 +- .../terminal/GroundVehicleTerminalTest.scala | 4 +- .../ImplantTerminalInterfaceTest.scala | 4 +- .../terminal/ImplantTerminalMechTest.scala | 3 +- .../objects/terminal/MatrixTerminalTest.scala | 10 +- .../terminal/OrderTerminalABTest.scala | 4 +- .../objects/terminal/OrderTerminalTest.scala | 4 +- .../terminal/TerminalControlTest.scala | 4 +- .../VehicleTerminalCombinedTest.scala | 4 +- pslogin/src/main/scala/Maps.scala | 96 ++-- .../src/main/scala/WorldSessionActor.scala | 374 +++++++++++----- pslogin/src/main/scala/Zones.scala | 29 ++ .../avatar/support/UndertakerActor.scala | 2 +- 52 files changed, 2491 insertions(+), 711 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/structures/StructureType.scala delete mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/SpawnTerminalDefinition.scala create mode 100644 common/src/test/scala/objects/AvatarTest.scala create mode 100644 common/src/test/scala/objects/SpawnTubeTest.scala create mode 100644 common/src/test/scala/objects/guidtask/GUIDTaskRegister6Test.scala create mode 100644 common/src/test/scala/objects/guidtask/GUIDTaskUnregister6Test.scala diff --git a/common/src/main/scala/net/psforever/objects/Avatar.scala b/common/src/main/scala/net/psforever/objects/Avatar.scala index fac940c18..f0a56c126 100644 --- a/common/src/main/scala/net/psforever/objects/Avatar.scala +++ b/common/src/main/scala/net/psforever/objects/Avatar.scala @@ -15,14 +15,19 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : private var cep : Long = 0 /** Certifications */ private val certs : mutable.Set[CertificationType.Value] = mutable.Set[CertificationType.Value]() - /** Implants */ + /** Implants
+ * Unlike other objects, the maximum number of `ImplantSlots` are built into the `Avatar`. + * Additionally, implants do not have tightly-coupled "`Definition` objects" that explain a formal implant object. + * The `ImplantDefinition` objects themselves are moved around as if they were the implants. + * The terms externally used for the states of process is "installed" and "uninstalled." + * @see `ImplantSlot` + * @see `DetailedCharacterData.implants` + */ private val implants : Array[ImplantSlot] = Array.fill[ImplantSlot](3)(new ImplantSlot) /** Loadouts */ private val loadouts : Array[Option[Loadout]] = Array.fill[Option[Loadout]](10)(None) /** Locker (fifth inventory slot) */ - private val locker : EquipmentSlot = new OffhandEquipmentSlot(EquipmentSize.Inventory) { - Equipment = new LockerContainer - } + private val locker : LockerContainer = LockerContainer() def BEP : Long = bep @@ -67,7 +72,7 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : * @return the index of the `ImplantSlot` where the implant was installed */ def InstallImplant(implant : ImplantDefinition) : Option[Int] = { - implants.find({p => p.Installed.contains(implant)}) match { //try to find the installed implant + implants.find({p => p.Installed.contains(implant) || p.Implant == implant.Type}) match { //try to find the installed implant case None => recursiveFindImplantInSlot(implants.iterator, ImplantType.None) match { //install in a free slot case Some(slot) => @@ -138,29 +143,25 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : }) } - /** - * Unlike other objects, the maximum number of `ImplantSlots` are built into the `Player`. - * Additionally, "implants" do not have tightly-coupled "`Definition` objects" that explain a formal implant object. - * The `ImplantDefinition` objects themselves are moved around as if they were the implants. - * The term internally used for this process is "installed" and "uninstalled." - * @see `ImplantSlot` - * @see `DetailedCharacterData.implants` - * @see `AvatarConverter.MakeImplantEntries` - */ - def SaveLoadout(owner : Player, label : String, line : Int) : Unit = { - loadouts(line) = Some(Loadout.Create(owner, label)) + if(line > -1 && line < 10) { + loadouts(line) = Some(Loadout.Create(owner, label)) + } } - def LoadLoadout(line : Int) : Option[Loadout] = loadouts(line) + def LoadLoadout(line : Int) : Option[Loadout] = loadouts.lift(line).getOrElse(None) def DeleteLoadout(line : Int) : Unit = { loadouts(line) = None } - def Locker : LockerContainer = locker.Equipment.get.asInstanceOf[LockerContainer] + def Locker : LockerContainer = locker - def FifthSlot : EquipmentSlot = locker + def FifthSlot : EquipmentSlot = { + new OffhandEquipmentSlot(EquipmentSize.Inventory) { + Equipment = locker + } + } def Definition : AvatarDefinition = Avatar.definition @@ -191,7 +192,7 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b) } - override def toString: String = s"$faction $name" + override def toString: String = Avatar.toString(this) } object Avatar { @@ -200,4 +201,6 @@ object Avatar { def apply(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : Int) : Avatar = { new Avatar(name, faction, sex, head, voice) } + + def toString(avatar : Avatar) : String = s"${avatar.faction} ${avatar.name}" } diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index fd0f8f878..1cc05ec0f 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -487,7 +487,7 @@ object GlobalDefinitions { */ val order_terminal = new OrderTerminalDefinition - val ams_respawn_tube = new SpawnTubeDefinition(49) { Name = "ams_respawn_tube" } + val ams_respawn_tube = new SpawnTubeDefinition(49) val matrix_terminalc = new MatrixTerminalDefinition(519) @@ -509,7 +509,11 @@ object GlobalDefinitions { val vehicle_terminal_combined = new VehicleTerminalCombinedDefinition - val spawn_terminal = new SpawnTerminalDefinition + val spawn_terminal = new MatrixTerminalDefinition(812) + + val respawn_tube = new SpawnTubeDefinition(732) + + val respawn_tube_tower = new SpawnTubeDefinition(733) val spawn_pad = new VehicleSpawnPadDefinition diff --git a/common/src/main/scala/net/psforever/objects/Loadout.scala b/common/src/main/scala/net/psforever/objects/Loadout.scala index 1d2a2d2c9..ec997d0a0 100644 --- a/common/src/main/scala/net/psforever/objects/Loadout.scala +++ b/common/src/main/scala/net/psforever/objects/Loadout.scala @@ -8,14 +8,6 @@ import net.psforever.types.ExoSuitType import scala.annotation.tailrec -//trait Loadout { -// def Label : String -// def VisibleSlots : List[Loadout.SimplifiedEntry] -// def Inventory : List[Loadout.SimplifiedEntry] -// def ExoSuit : ExoSuitType.Value -// def Subtype : Int -//} - /** * From a `Player` their current exo-suit and their `Equipment`, retain a set of instructions to reconstruct this arrangement.
*
@@ -113,7 +105,9 @@ object Loadout { /** * A basic `Trait` connecting all of the `Equipment` blueprints. */ - sealed trait Simplification + sealed trait Simplification { + def definition : ObjectDefinition + } /** * An entry in the `Loadout`, wrapping around a slot index and what is in the slot index. @@ -125,17 +119,17 @@ object Loadout { /** * The simplified form of an `AmmoBox`. - * @param adef the `AmmoBoxDefinition` that describes this future object + * @param definition the `AmmoBoxDefinition` that describes this future object * @param capacity the amount of ammunition, if any, to initialize; * if `None`, then the previous `AmmoBoxDefinition` will be referenced for the amount later */ - final case class ShorthandAmmoBox(adef : AmmoBoxDefinition, capacity : Int) extends Simplification + final case class ShorthandAmmoBox(definition : AmmoBoxDefinition, capacity : Int) extends Simplification /** * The simplified form of a `Tool`. - * @param tdef the `ToolDefinition` that describes this future object + * @param definition the `ToolDefinition` that describes this future object * @param ammo the blueprints to construct the correct number of ammunition slots in the `Tool` */ - final case class ShorthandTool(tdef : ToolDefinition, ammo : List[ShorthandAmmoSlot]) extends Simplification + final case class ShorthandTool(definition : ToolDefinition, ammo : List[ShorthandAmmoSlot]) extends Simplification /** * The simplified form of a `Tool` `FireMode` * @param ammoIndex the index that points to the type of ammunition this slot currently uses @@ -144,19 +138,19 @@ object Loadout { final case class ShorthandAmmoSlot(ammoIndex : Int, ammo : ShorthandAmmoBox) /** * The simplified form of a `ConstructionItem`. - * @param cdef the `ConstructionItemDefinition` that describes this future object + * @param definition the `ConstructionItemDefinition` that describes this future object */ - final case class ShorthandConstructionItem(cdef : ConstructionItemDefinition) extends Simplification + final case class ShorthandConstructionItem(definition : ConstructionItemDefinition) extends Simplification /** * The simplified form of a `SimpleItem`. - * @param sdef the `SimpleItemDefinition` that describes this future object + * @param definition the `SimpleItemDefinition` that describes this future object */ - final case class ShorthandSimpleItem(sdef : SimpleItemDefinition) extends Simplification + final case class ShorthandSimpleItem(definition : SimpleItemDefinition) extends Simplification /** * The simplified form of a `Kit`. - * @param kdef the `KitDefinition` that describes this future object + * @param definition the `KitDefinition` that describes this future object */ - final case class ShorthandKit(kdef : KitDefinition) extends Simplification + final case class ShorthandKit(definition : KitDefinition) extends Simplification def DetermineSubtype(player : Player) : Int = { if(player.ExoSuit == ExoSuitType.MAX) { diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index 30c328211..1250051b4 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -200,7 +200,10 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio case Some(index) => Some(index) case None => - if(freeHand.Equipment.isDefined && freeHand.Equipment.get.GUID == guid) { + if(Locker.Find(guid).isDefined) { + Some(5) + } + else if(freeHand.Equipment.isDefined && freeHand.Equipment.get.GUID == guid) { Some(Player.FreeHandSlot) } else { @@ -416,6 +419,7 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio } object Player { + final val LockerSlot : Int = 5 final val FreeHandSlot : Int = 250 final val HandsDownSlot : Int = 255 @@ -447,5 +451,8 @@ object Player { } } - def toString(obj : Player) : String = s"${obj.core} ${obj.Health}/${obj.MaxHealth} ${obj.Armor}/${obj.MaxArmor}" + def toString(obj : Player) : String = { + val guid = if(obj.HasGUID) { s" ${obj.Continent}-${obj.GUID.guid}" } else { "" } + s"${obj.core}$guid ${obj.Health}/${obj.MaxHealth} ${obj.Armor}/${obj.MaxArmor}" + } } diff --git a/common/src/main/scala/net/psforever/objects/guid/GUIDTask.scala b/common/src/main/scala/net/psforever/objects/guid/GUIDTask.scala index 95d92bced..2ea7fb4a5 100644 --- a/common/src/main/scala/net/psforever/objects/guid/GUIDTask.scala +++ b/common/src/main/scala/net/psforever/objects/guid/GUIDTask.scala @@ -1,7 +1,13 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.guid -import net.psforever.objects.vehicles.Utility +import akka.actor.ActorRef +import net.psforever.objects.entity.IdentifiableEntity +import net.psforever.objects.equipment.Equipment +import net.psforever.objects.{EquipmentSlot, LockerContainer, Player, Tool, Vehicle} +import net.psforever.objects.inventory.Container + +import scala.annotation.tailrec /** * The basic compiled tasks for assigning (registering) and revoking (unregistering) globally unique identifiers.
@@ -14,17 +20,13 @@ import net.psforever.objects.vehicles.Utility * It will get passed from the more complicated functions down into the less complicated functions, * until it has found the basic number assignment functionality.
*
- * All functions produce a `TaskResolver.GiveTask` container object that is expected to be used by a `TaskResolver`. - * These "task containers" can also be unpackaged into their tasks, sorted into other containers, - * and combined with other "task containers" to enact more complicated sequences of operations. + * All functions produce a `TaskResolver.GiveTask` container object + * or a list of `TaskResolver.GiveTask` container objects that is expected to be used by a `TaskResolver` `Actor`. + * These "task containers" can also be unpackaged into their component tasks, sorted into other containers, + * and combined with other tasks to enact more complicated sequences of operations. + * Almost all tasks have an explicit registering and an unregistering activity defined for it. */ object GUIDTask { - import akka.actor.ActorRef - import net.psforever.objects.entity.IdentifiableEntity - import net.psforever.objects.equipment.Equipment - import net.psforever.objects.{EquipmentSlot, Player, Tool, Vehicle} - - import scala.annotation.tailrec /** * Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers.
*
@@ -76,9 +78,34 @@ object GUIDTask { TaskResolver.GiveTask(RegisterObjectTask(obj).task, ammoTasks) } + /** + * Construct tasking that registers a `LockerContainer` object + * with a globally unique identifier selected from a pool of numbers. + * @param obj the object being registered + * @param guid implicit reference to a unique number system + * @see `GUIDTask.UnregisterLocker` + * @return a `TaskResolver.GiveTask` message + */ + def RegisterLocker(obj : LockerContainer)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + TaskResolver.GiveTask(RegisterObjectTask(obj).task, RegisterInventory(obj)) + } + + /** + * Construct tasking that registers the objects that are within the given container's inventory + * with a globally unique identifier selected from a pool of numbers for each object. + * @param container the storage unit in which objects can be found + * @param guid implicit reference to a unique number system + * @see `GUID.UnregisterInventory`
+ * `Container` + * @return a list of `TaskResolver.GiveTask` messages + */ + def RegisterInventory(container : Container)(implicit guid : ActorRef) : List[TaskResolver.GiveTask] = { + container.Inventory.Items.values.map(entry => { RegisterEquipment(entry.obj)}).toList + } + /** * Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, - * after determining whether the object is complex (`Tool`) or simple.
+ * after determining whether the object is complex (`Tool` or `Locker`) or is simple.
*
* The objects in this case are specifically `Equipment`, a subclass of the basic register-able `IdentifiableEntity`. * About five subclasses of `Equipment` exist, but they decompose into two groups - "complex objects" and "simple objects." @@ -96,6 +123,8 @@ object GUIDTask { obj match { case tool : Tool => RegisterTool(tool) + case locker : LockerContainer => + RegisterLocker(locker) case _ => RegisterObjectTask(obj) } @@ -118,17 +147,24 @@ object GUIDTask { * @return a `TaskResolver.GiveTask` message */ def RegisterAvatar(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = { - import net.psforever.objects.LockerContainer - import net.psforever.objects.inventory.InventoryItem - val holsterTasks = recursiveHolsterTaskBuilding(tplayer.Holsters().iterator, RegisterEquipment) - val fifthHolsterTask = tplayer.Slot(5).Equipment match { - case Some(locker) => - RegisterObjectTask(locker) :: locker.asInstanceOf[LockerContainer].Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => RegisterEquipment(entry.obj)}).toList - case None => - List.empty[TaskResolver.GiveTask]; - } - val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => RegisterEquipment(entry.obj)}) - TaskResolver.GiveTask(RegisterObjectTask(tplayer).task, holsterTasks ++ fifthHolsterTask ++ inventoryTasks) + val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), RegisterEquipment) + val lockerTask = List(RegisterLocker(tplayer.Locker)) + val inventoryTasks = RegisterInventory(tplayer) + TaskResolver.GiveTask(RegisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks) + } + + /** + * Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, as a `Player`.
+ *
+ * Similar to `RegisterAvatar` but the locker components are skipped. + * @param tplayer the `Player` object being registered + * @param guid implicit reference to a unique number system + * @return a `TaskResolver.GiveTask` message + */ + def RegisterPlayer(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), RegisterEquipment) + val inventoryTasks = RegisterInventory(tplayer) + TaskResolver.GiveTask(GUIDTask.RegisterObjectTask(tplayer)(guid).task, holsterTasks ++ inventoryTasks) } /** @@ -149,10 +185,9 @@ object GUIDTask { * @return a `TaskResolver.GiveTask` message */ def RegisterVehicle(vehicle : Vehicle)(implicit guid : ActorRef) : TaskResolver.GiveTask = { - import net.psforever.objects.inventory.InventoryItem - val weaponTasks = vehicle.Weapons.map({ case(_ : Int, entry : EquipmentSlot) => RegisterEquipment(entry.Equipment.get)}).toList - val utilTasks = Vehicle.EquipmentUtilities(vehicle.Utilities).map({case (_ : Int, util : Utility) => RegisterObjectTask(util())}).toList - val inventoryTasks = vehicle.Trunk.Items.map({ case((_ : Int, entry : InventoryItem)) => RegisterEquipment(entry.obj)}) + val weaponTasks = VisibleSlotTaskBuilding(vehicle.Weapons.values, RegisterEquipment) + val utilTasks = Vehicle.EquipmentUtilities(vehicle.Utilities).values.map(util => { RegisterObjectTask(util())}).toList + val inventoryTasks = RegisterInventory(vehicle) TaskResolver.GiveTask(RegisterObjectTask(vehicle).task, weaponTasks ++ utilTasks ++ inventoryTasks) } @@ -201,9 +236,33 @@ object GUIDTask { TaskResolver.GiveTask(UnregisterObjectTask(obj).task, ammoTasks) } + /** + * Construct tasking that unregisters a `LockerContainer` object from a globally unique identifier system. + * @param obj the object being unregistered + * @param guid implicit reference to a unique number system + * @see `GUIDTask.RegisterLocker` + * @return a `TaskResolver.GiveTask` message + */ + def UnregisterLocker(obj : LockerContainer)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + TaskResolver.GiveTask(UnregisterObjectTask(obj).task, UnregisterInventory(obj)) + } + + /** + * Construct tasking that unregisters the objects that are within the given container's inventory + * from a globally unique identifier system. + * @param container the storage unit in which objects can be found + * @param guid implicit reference to a unique number system + * @see `GUIDTask.RegisterInventory`
+ * `Container` + * @return a list of `TaskResolver.GiveTask` messages + */ + def UnregisterInventory(container : Container)(implicit guid : ActorRef) : List[TaskResolver.GiveTask] = { + container.Inventory.Items.values.map(entry => { UnregisterEquipment(entry.obj)}).toList + } + /** * Construct tasking that unregisters an object from a globally unique identifier system - * after determining whether the object is complex (`Tool`) or simple.
+ * after determining whether the object is complex (`Tool` or `Locker`) or is simple.
*
* This task performs an operation that reverses the effect of `RegisterEquipment`. * @param obj the `Equipment` object being unregistered @@ -215,6 +274,8 @@ object GUIDTask { obj match { case tool : Tool => UnregisterTool(tool) + case locker : LockerContainer => + UnregisterLocker(locker) case _ => UnregisterObjectTask(obj) } @@ -230,36 +291,58 @@ object GUIDTask { * @return a `TaskResolver.GiveTask` message */ def UnregisterAvatar(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = { - import net.psforever.objects.LockerContainer - import net.psforever.objects.inventory.InventoryItem - val holsterTasks = recursiveHolsterTaskBuilding(tplayer.Holsters().iterator, UnregisterEquipment) - val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => UnregisterEquipment(entry.obj)}) - val fifthHolsterTask = tplayer.Slot(5).Equipment match { - case Some(locker) => - UnregisterObjectTask(locker) :: locker.asInstanceOf[LockerContainer].Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => UnregisterEquipment(entry.obj)}).toList - case None => - List.empty[TaskResolver.GiveTask]; - } - TaskResolver.GiveTask(UnregisterObjectTask(tplayer).task, holsterTasks ++ fifthHolsterTask ++ inventoryTasks) + val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), UnregisterEquipment) + val lockerTask = List(UnregisterLocker(tplayer.Locker)) + val inventoryTasks = UnregisterInventory(tplayer) + TaskResolver.GiveTask(UnregisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks) } -/** - * Construct tasking that unregisters a `Vehicle` object from a globally unique identifier system.
- *
- * This task performs an operation that reverses the effect of `RegisterVehicle`. - * @param vehicle the `Vehicle` object being unregistered - * @param guid implicit reference to a unique number system - * @see `GUIDTask.RegisterVehicle` - * @return a `TaskResolver.GiveTask` message - */ + /** + * Construct tasking that unregisters a portion of a `Player` object from a globally unique identifier system.
+ *
+ * Similar to `UnregisterAvatar` but the locker components are skipped. + * This task performs an operation that reverses the effect of `RegisterPlayer`. + * @param tplayer the `Player` object being unregistered + * @param guid implicit reference to a unique number system + * @see `GUIDTask.RegisterAvatar` + * @return a `TaskResolver.GiveTask` message + */ + def UnregisterPlayer(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), UnregisterEquipment) + val inventoryTasks = UnregisterInventory(tplayer) + TaskResolver.GiveTask(GUIDTask.UnregisterObjectTask(tplayer).task, holsterTasks ++ inventoryTasks) + } + + /** + * Construct tasking that unregisters a `Vehicle` object from a globally unique identifier system.
+ *
+ * This task performs an operation that reverses the effect of `RegisterVehicle`. + * @param vehicle the `Vehicle` object being unregistered + * @param guid implicit reference to a unique number system + * @see `GUIDTask.RegisterVehicle` + * @return a `TaskResolver.GiveTask` message + */ def UnregisterVehicle(vehicle : Vehicle)(implicit guid : ActorRef) : TaskResolver.GiveTask = { - import net.psforever.objects.inventory.InventoryItem - val weaponTasks = vehicle.Weapons.map({ case(_ : Int, entry : EquipmentSlot) => UnregisterTool(entry.Equipment.get.asInstanceOf[Tool]) }).toList - val utilTasks = Vehicle.EquipmentUtilities(vehicle.Utilities).map({case (_ : Int, util : Utility) => UnregisterObjectTask(util())}).toList - val inventoryTasks = vehicle.Trunk.Items.map({ case((_ : Int, entry : InventoryItem)) => UnregisterEquipment(entry.obj)}) + val weaponTasks = VisibleSlotTaskBuilding(vehicle.Weapons.values, UnregisterEquipment) + val utilTasks = Vehicle.EquipmentUtilities(vehicle.Utilities).values.map(util => { UnregisterObjectTask(util())}).toList + val inventoryTasks = UnregisterInventory(vehicle) TaskResolver.GiveTask(UnregisterObjectTask(vehicle).task, weaponTasks ++ utilTasks ++ inventoryTasks) } + /** + * Construct tasking that allocates work upon encountered `Equipment` objects + * in reference to a globally unique identifier system of a pool of numbers. + * "Visible slots" are locations that can be viewed by multiple observers across a number of clients. + * @param list an `Iterable` sequence of `EquipmentSlot` objects that may or may not have equipment + * @param func the function used to build tasking from any discovered `Equipment`; + * strictly either `RegisterEquipment` or `UnregisterEquipment` + * @param guid implicit reference to a unique number system + * @return a list of `TaskResolver.GiveTask` messages + */ + def VisibleSlotTaskBuilding(list : Iterable[EquipmentSlot], func : ((Equipment)=>TaskResolver.GiveTask))(implicit guid : ActorRef) : List[TaskResolver.GiveTask] = { + recursiveVisibleSlotTaskBuilding(list.iterator, func) + } + /** * Iterate over a group of `EquipmentSlot`s, some of which may be occupied with an item. * Use `func` on any discovered `Equipment` to transform items into tasking, and add the tasking to a `List`. @@ -267,18 +350,19 @@ object GUIDTask { * @param func the function used to build tasking from any discovered `Equipment`; * strictly either `RegisterEquipment` or `UnregisterEquipment` * @param list a persistent `List` of `Equipment` tasking + * @see `VisibleSlotTaskBuilding` * @return a `List` of `Equipment` tasking */ - @tailrec private def recursiveHolsterTaskBuilding(iter : Iterator[EquipmentSlot], func : ((Equipment)=>TaskResolver.GiveTask), list : List[TaskResolver.GiveTask] = Nil)(implicit guid : ActorRef) : List[TaskResolver.GiveTask] = { + @tailrec private def recursiveVisibleSlotTaskBuilding(iter : Iterator[EquipmentSlot], func : ((Equipment)=>TaskResolver.GiveTask), list : List[TaskResolver.GiveTask] = Nil)(implicit guid : ActorRef) : List[TaskResolver.GiveTask] = { if(!iter.hasNext) { list } else { iter.next.Equipment match { case Some(item) => - recursiveHolsterTaskBuilding(iter, func, list :+ func(item)) + recursiveVisibleSlotTaskBuilding(iter, func, list :+ func(item)) case None => - recursiveHolsterTaskBuilding(iter, func, list) + recursiveVisibleSlotTaskBuilding(iter, func, list) } } } diff --git a/common/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala b/common/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala index fcd16c173..31699543a 100644 --- a/common/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala +++ b/common/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala @@ -287,12 +287,7 @@ class UniqueNumberSystem(private val guid : NumberPoolHub, private val poolActor * @see `UniqueNumberSystem.UnregistrationProcess(Option[GUIDRequest], Int, Int)` */ private def NoCallbackReturnNumber(number : Int, poolName : String) : Unit = { - poolActors.get(poolName) match { - case Some(pool) => - pool ! NumberPoolActor.ReturnNumber(number, Some(Long.MinValue)) - case None => - log.error(s"critical: tried to return number $number but did not find pool $poolName") - } + poolActors(poolName) ! NumberPoolActor.ReturnNumber(number, Some(Long.MinValue)) } /** @@ -316,12 +311,7 @@ class UniqueNumberSystem(private val guid : NumberPoolHub, private val poolActor * @see `UniqueNumberSystem.RegistrationProcess(Option[GUIDRequest], Int, Int)` */ private def NoCallbackGetSpecificNumber(number : Int, poolName : String) : Unit = { - poolActors.get(poolName) match { - case Some(pool) => - pool ! NumberPoolActor.GetSpecificNumber(number, Some(Long.MinValue)) - case None => - log.error(s"critical: tried to re-register number $number but did not find pool $poolName") - } + poolActors(poolName) ! NumberPoolActor.GetSpecificNumber(number, Some(Long.MinValue)) } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala index 7b078dc97..844da483b 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala @@ -6,9 +6,9 @@ import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.zones.Zone import net.psforever.packet.game.PlanetSideGUID -import net.psforever.types.PlanetSideEmpire +import net.psforever.types.{PlanetSideEmpire, Vector3} -class Building(private val mapId : Int, private val zone : Zone) extends PlanetSideServerObject { +class Building(private val mapId : Int, private val zone : Zone, private val buildingType : StructureType.Value) extends PlanetSideServerObject { /** * The mapId is the identifier number used in BuildingInfoUpdateMessage. * The modelId is the identifier number used in SetEmpireMessage. @@ -46,25 +46,35 @@ class Building(private val mapId : Int, private val zone : Zone) extends PlanetS ModelId } + def BuildingType : StructureType.Value = buildingType + def Definition: ObjectDefinition = Building.BuildingDefinition } object Building { - final val NoBuilding : Building = new Building(0, Zone.Nowhere) { + final val NoBuilding : Building = new Building(0, Zone.Nowhere, StructureType.Platform) { override def Faction_=(faction : PlanetSideEmpire.Value) : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL override def Amenities_=(obj : Amenity) : List[Amenity] = Nil } final val BuildingDefinition : ObjectDefinition = new ObjectDefinition(0) { Name = "building" } - def apply(id : Int, zone : Zone) : Building = { - new Building(id, zone) + def apply(id : Int, zone : Zone, buildingType : StructureType.Value) : Building = { + new Building(id, zone, buildingType) } - def Structure(id : Int, zone : Zone, context : ActorContext) : Building = { + def Structure(buildingType : StructureType.Value, location : Vector3)(id : Int, zone : Zone, context : ActorContext) : Building = { import akka.actor.Props - val obj = new Building(id, zone) - obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-building") + val obj = new Building(id, zone, buildingType) + obj.Position = location + obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building") + obj + } + + def Structure(buildingType : StructureType.Value)(id : Int, zone : Zone, context : ActorContext) : Building = { + import akka.actor.Props + val obj = new Building(id, zone, buildingType) + obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building") obj } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/StructureType.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/StructureType.scala new file mode 100644 index 000000000..7acc1840a --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/StructureType.scala @@ -0,0 +1,20 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.structures + +/** + * An `Enumeration` of the kinds of building structures found in the game. + * This is merely a kludge for more a future, more complicated internal object that handles base operations. + */ +object StructureType extends Enumeration { + type Type = Value + + val + Bridge, + Building, //generic + Bunker, + Facility, + Platform, //outdoor amenities like the spawn pads in sanctuary + Tower, + WarpGate + = Value +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala index 784685377..bf23cc0d1 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala @@ -4,7 +4,7 @@ package net.psforever.objects.serverobject.structures import akka.actor.ActorContext import net.psforever.objects.zones.Zone -class WarpGate(id : Int, zone : Zone) extends Building(id, zone) { +class WarpGate(id : Int, zone : Zone) extends Building(id, zone, StructureType.WarpGate) { //TODO stuff later } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala index 94fef0fcd..2c062abb7 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala @@ -339,8 +339,8 @@ object EquipmentTerminalDefinition { import net.psforever.objects.Loadout._ entry match { case obj : ShorthandTool => - val ammo : List[AmmoBoxDefinition] = obj.ammo.map(fmode => { fmode.ammo.adef }) - val tool = Tool(obj.tdef) + val ammo : List[AmmoBoxDefinition] = obj.ammo.map(fmode => { fmode.ammo.definition }) + val tool = Tool(obj.definition) //makes Tools where an ammo slot may have one of its alternate ammo types (0 until tool.MaxAmmoSlot).foreach(index => { val slot = tool.AmmoSlots(index) @@ -350,16 +350,16 @@ object EquipmentTerminalDefinition { tool case obj : ShorthandAmmoBox => - MakeAmmoBox(obj.adef, Some(obj.capacity)) + MakeAmmoBox(obj.definition, Some(obj.capacity)) case obj : ShorthandConstructionItem => - MakeConstructionItem(obj.cdef) + MakeConstructionItem(obj.definition) case obj : ShorthandSimpleItem => - MakeSimpleItem(obj.sdef) + MakeSimpleItem(obj.definition) case obj : ShorthandKit => - MakeKit(obj.kdef) + MakeKit(obj.definition) } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/MatrixTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/MatrixTerminalDefinition.scala index 047e786d5..292e30b24 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/MatrixTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/MatrixTerminalDefinition.scala @@ -19,8 +19,11 @@ class MatrixTerminalDefinition(object_id : Int) extends TerminalDefinition(objec else if(object_id == 519) { "matrix_terminalc" } + else if(object_id == 812) { + "spawn_terminal" + } else { - throw new IllegalArgumentException("terminal must be object id 517-519") + throw new IllegalArgumentException("terminal must be object id 517-519 or 812") } override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal() diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/SpawnTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/SpawnTerminalDefinition.scala deleted file mode 100644 index 8483a2c37..000000000 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/SpawnTerminalDefinition.scala +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.serverobject.terminals - -import net.psforever.objects.Player -import net.psforever.packet.game.ItemTransactionMessage - -/** - * The definition for any `Terminal` that is of a type "spawn_terminal." - * A "spawn_terminal" is somewhat like the `matrix_terminalc` of an advanced mobile spawn unit, but inside of facilities. - */ -class SpawnTerminalDefinition extends TerminalDefinition(812) { - Name = "spawn_terminal" - - override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal() -} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala index 4bc80f06d..91aff35df 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala @@ -1,16 +1,26 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.tube -import net.psforever.objects.definition.ObjectDefinition +import net.psforever.objects.GlobalDefinitions import net.psforever.objects.serverobject.structures.Amenity -class SpawnTube(tubeDef : ObjectDefinition) extends Amenity { - def Definition : ObjectDefinition = tubeDef +/** + * An owned server object that is used as a placeholder for the position and direction + * that infantry will be arranged upon spawning into the game world. + * @param tDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields + */ +class SpawnTube(tDef : SpawnTubeDefinition) extends Amenity { + def Definition : SpawnTubeDefinition = tDef } object SpawnTube { - def apply(tubeDef : ObjectDefinition) : SpawnTube = { - new SpawnTube(tubeDef) + /** + * Overloaded constructor. + * @param tDef the spawn tube's definition entry + * @return a `SpawnTube` object + */ + def apply(tDef : SpawnTubeDefinition) : SpawnTube = { + new SpawnTube(tDef) } import akka.actor.ActorContext @@ -24,13 +34,25 @@ object SpawnTube { * @return the `SpawnTube` object */ def Constructor(pos : Vector3, orient : Vector3)(id : Int, context : ActorContext) : SpawnTube = { - import akka.actor.Props - import net.psforever.objects.GlobalDefinitions + Constructor(GlobalDefinitions.respawn_tube, pos, orient)(id, context) + } - val obj = SpawnTube(GlobalDefinitions.ams_respawn_tube) + /** + * Instantiate an configure a `SpawnTube` object with the given definition + * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields + * @param pos the position (used to determine spawn point) + * @param orient the orientation (used to indicate spawn direction) + * @param id the unique id that will be assigned to this entity + * @param context a context to allow the object to properly set up `ActorSystem` functionality + * @return the `SpawnTube` object + */ + def Constructor(tdef : SpawnTubeDefinition, pos : Vector3, orient : Vector3)(id : Int, context : ActorContext) : SpawnTube = { + import akka.actor.Props + + val obj = SpawnTube(tdef) obj.Position = pos obj.Orientation = orient - obj.Actor = context.actorOf(Props(classOf[SpawnTubeControl], obj), s"${GlobalDefinitions.ams_respawn_tube.Name}_$id") + obj.Actor = context.actorOf(Props(classOf[SpawnTubeControl], obj), s"${tdef.Name}_$id") obj } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala index 774449c03..1cb1279dd 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala @@ -6,7 +6,24 @@ import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.definition.converter.SpawnTubeConverter import net.psforever.objects.serverobject.structures.Amenity +/** + * The definition for any `VehicleSpawnPad`. + * Currently, all tubes identify as object id 49 - `ams_respawn_tube` - configured manually. + * @see `GlobalDefinitions.ams_respawn_tube` + */ class SpawnTubeDefinition(object_id : Int) extends ObjectDefinition(object_id) { + Name = if(object_id == 49) { + "ams_respawn_tube" + } + else if(object_id == 732) { + "respawn_tube" + } + else if(object_id == 733) { + "respawn_tube_tower" + } + else { + throw new IllegalArgumentException("terminal must be object id 49, 732, or 733") + } Packet = new SpawnTubeConverter } diff --git a/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala b/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala index 88af2e5c2..a1b79e7ce 100644 --- a/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala +++ b/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala @@ -41,7 +41,7 @@ class InterstellarCluster(zones : List[Zone]) extends Actor { def receive : Receive = { case InterstellarCluster.GetWorld(zoneId) => log.info(s"Asked to find $zoneId") - findWorldInCluster(zones.iterator, zoneId) match { + recursiveFindWorldInCluster(zones.iterator, _.Id == zoneId) match { case Some(continent) => sender ! InterstellarCluster.GiveWorld(zoneId, continent) case None => @@ -52,26 +52,35 @@ class InterstellarCluster(zones : List[Zone]) extends Actor { zones.foreach(zone => { sender ! Zone.ClientInitialization(zone.ClientInitialization()) }) sender ! InterstellarCluster.ClientInitializationComplete() //will be processed after all Zones + case msg @ Zone.Lattice.RequestSpawnPoint(zone_number, _, _) => + recursiveFindWorldInCluster(zones.iterator, _.Number == zone_number) match { + case Some(zone) => + zone.Actor forward msg + + case None => //zone_number does not exist + sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None) + } + case _ => ; } /** * Search through the `List` of `Zone` entities and find the one with the matching designation. * @param iter an `Iterator` of `Zone` entities - * @param zoneId the name of the `Zone` + * @param predicate a condition to check against to determine when the appropriate `Zone` is discovered * @return the discovered `Zone` */ - @tailrec private def findWorldInCluster(iter : Iterator[Zone], zoneId : String) : Option[Zone] = { + @tailrec private def recursiveFindWorldInCluster(iter : Iterator[Zone], predicate : Zone=>Boolean) : Option[Zone] = { if(!iter.hasNext) { None } else { val cont = iter.next - if(cont.Id == zoneId) { + if(predicate.apply(cont)) { Some(cont) } else { - findWorldInCluster(iter, zoneId) + recursiveFindWorldInCluster(iter, predicate) } } } @@ -104,3 +113,31 @@ object InterstellarCluster { */ final case class ClientInitializationComplete() } + +/* +// List[Building] --> List[List[(Amenity, Building)]] --> List[(SpawnTube*, Building)] +zone.LocalLattice.Buildings.values + .filter(_.Faction == player.Faction) + .map(building => { building.Amenities.map { _ -> building } }) + .flatMap( _.filter({ case(amenity, _) => amenity.isInstanceOf[SpawnTube] }) ) + */ + +/* +zone.Buildings.values.filter(building => { + ( + if(spawn_zone == 6) { Set(StructureType.Tower) } + else if(spawn_zone == 7) { Set(StructureType.Facility, StructureType.Building) } + else { Set.empty[StructureType.Value] } + ).contains(building.BuildingType) && + building.Amenities.exists(_.isInstanceOf[SpawnTube]) && + building.Faction == player.Faction && + building.Position != Vector3.Zero +}) + .toSeq + .sortBy(building => { + Vector3.DistanceSquared(player.Position, building.Position) < Vector3.DistanceSquared(player.Position, building.Position) + }) + .map(building => { building.Amenities.map { _ -> building } }) + .flatMap( _.filter({ case(amenity, _) => amenity.isInstanceOf[SpawnTube] }) ) +).headOption + */ diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala index 7281891d7..37254fb70 100644 --- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -10,6 +10,7 @@ import net.psforever.objects.guid.actor.UniqueNumberSystem import net.psforever.objects.guid.selector.RandomSelector import net.psforever.objects.guid.source.LimitedNumberSource import net.psforever.objects.serverobject.structures.{Amenity, Building} +import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.Vector3 @@ -58,11 +59,13 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { /** */ private val players : TrieMap[Avatar, Option[Player]] = TrieMap[Avatar, Option[Player]]() /** */ - private var corpses : List[Player] = List[Player]() + private val corpses : ListBuffer[Player] = ListBuffer[Player]() /** */ private var population : ActorRef = ActorRef.noSender private var buildings : PairMap[Int, Building] = PairMap.empty[Int, Building] + /** key - spawn zone id, value - buildings belonging to spawn zone */ + private var spawnGroups : Map[Building, List[SpawnTube]] = PairMap[Building, List[SpawnTube]]() /** * Establish the basic accessible conditions necessary for a functional `Zone`.
@@ -87,11 +90,12 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { 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") transport = context.actorOf(Props(classOf[ZoneVehicleActor], this), s"$Id-vehicles") - population = context.actorOf(Props(classOf[ZonePopulationActor], this), s"$Id-players") + population = context.actorOf(Props(classOf[ZonePopulationActor], this, players, corpses), s"$Id-players") Map.LocalObjects.foreach({ builderObject => builderObject.Build }) MakeBuildings(context) AssignAmenities() + CreateSpawnGroups() } } @@ -191,7 +195,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { def LivePlayers : List[Player] = players.values.collect( { case Some(tplayer) => tplayer }).toList - def Corpses : List[Player] = corpses + def Corpses : List[Player] = corpses.toList def AddVehicle(vehicle : Vehicle) : List[Vehicle] = { vehicles = vehicles :+ vehicle @@ -222,78 +226,6 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { } } - def PopulationJoin(avatar : Avatar) : Boolean = { - players.get(avatar) match { - case Some(_) => - false - case None => - players += avatar -> None - true - } - } - - def PopulationLeave(avatar : Avatar) : Option[Player] = { - players.remove(avatar) match { - case None => - None - case Some(tplayer) => - tplayer - } - } - - def PopulationSpawn(avatar : Avatar, player : Player) : Option[Player] = { - players.get(avatar) match { - case None => - None - case Some(tplayer) => - tplayer match { - case Some(aplayer) => - Some(aplayer) - case None => - players(avatar) = Some(player) - Some(player) - } - } - } - - def PopulationRelease(avatar : Avatar) : Option[Player] = { - players.get(avatar) match { - case None => - None - case Some(tplayer) => - players(avatar) = None - tplayer - } - } - - def CorpseAdd(player : Player) : Unit = { - if(player.isBackpack && recursiveFindCorpse(players.values.filter(_.nonEmpty).map(_.get).iterator, player.GUID).isEmpty) { - corpses = corpses :+ player - } - } - - def CorpseRemove(player : Player) : Unit = { - recursiveFindCorpse(corpses.iterator, player.GUID) match { - case Some(index) => - corpses = corpses.take(index-1) ++ corpses.drop(index) - case None => ; - } - } - - @tailrec final def recursiveFindCorpse(iter : Iterator[Player], guid : PlanetSideGUID, index : Int = 0) : Option[Int] = { - if(!iter.hasNext) { - None - } - else { - if(iter.next.GUID == guid) { - Some(index) - } - else { - recursiveFindCorpse(iter, guid, index + 1) - } - } - } - /** * Coordinate `Equipment` that has been dropped on the ground or to-be-dropped on the ground. * @return synchronized reference to the ground @@ -326,26 +258,55 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { }) } + private def CreateSpawnGroups() : Unit = { + buildings.values + .filterNot { _.Position == Vector3.Zero } + .map(building => { building -> building.Amenities.collect { case(obj : SpawnTube) => obj } }) + .filter( { case((_, spawns)) => spawns.nonEmpty }) + .foreach { SpawnGroups } + } + + def SpawnGroups() : Map[Building, List[SpawnTube]] = spawnGroups + + def SpawnGroups(building : Building) : List[SpawnTube] = SpawnGroups(building.Id) + + def SpawnGroups(buildingId : Int) : List[SpawnTube] = { + spawnGroups.find({ case((building, _)) => building.Id == buildingId }) match { + case Some((_, list)) => + list + case None => + List.empty[SpawnTube] + } + } + + def SpawnGroups(spawns : (Building, List[SpawnTube])) : Map[Building, List[SpawnTube]] = { + val (building, tubes) = spawns + val entry : Map[Building, List[SpawnTube]] = PairMap(building -> tubes) + spawnGroups = spawnGroups ++ entry + entry + } + /** * Provide bulk correspondence on all map entities that can be composed into packet messages and reported to a client. * These messages are sent in this fashion at the time of joining the server:
- * - `BroadcastWarpgateUpdateMessage`
* - `BuildingInfoUpdateMessage`
+ * - `DensityLevelUpdateMessage`
+ * - `BroadcastWarpgateUpdateMessage`
* - `CaptureFlagUpdateMessage`
* - `ContinentalLockUpdateMessage`
- * - `DensityLevelUpdateMessage`
* - `ModuleLimitsMessage`
* - `VanuModuleUpdateMessage`
* - `ZoneForcedCavernConnectionMessage`
* - `ZoneInfoMessage`
* - `ZoneLockInfoMessage`
* - `ZonePopulationUpdateMessage` - * @return a `List` of `GamePacket` messages + * @return the `Zone` object */ def ClientInitialization() : Zone = this } object Zone { + /** Default value, non-zone area. */ final val Nowhere : Zone = new Zone("nowhere", new ZoneMap("nowhere"), 99) /** @@ -355,20 +316,96 @@ object Zone { final case class Init() object Population { + /** + * Message that introduces a user, by their `Avatar`, into a `Zone`. + * That user will be counted as part of that zone's population. + * The `avatar` may associate `Player` objects with itself in the future. + * @param avatar the `Avatar` object + */ final case class Join(avatar : Avatar) + /** + * Message that excuses a user, by their `Avatar`, into a `Zone`. + * That user will not longer be counted as part of that zone's population. + * @see `PlayerHasLeft` + * @param avatar the `Avatar` object + */ final case class Leave(avatar : Avatar) + /** + * Message that instructs the zone to disassociate a `Player` from this `Actor`. + * @see `PlayerAlreadySpawned`
+ * `PlayerCanNotSpawn` + * @param avatar the `Avatar` object + * @param player the `Player` object + */ final case class Spawn(avatar : Avatar, player : Player) + /** + * Message that instructs the zone to disassociate a `Player` from this `Actor`. + * @see `PlayerHasLeft` + * @param avatar the `Avatar` object + */ final case class Release(avatar : Avatar) + /** + * Message that acts in reply to `Leave(avatar)` or `Release(avatar)`. + * In the former case, the avatar will have successfully left the zone, and `player` may be defined. + * In the latter case, the avatar did not initially `Join` the zone, and `player` is `None`. + * This message should not be considered a failure or a success case. + * @see `Release`
+ * `Leave` + * @param zone the `Zone` object + * @param player the `Player` object + */ final case class PlayerHasLeft(zone : Zone, player : Option[Player]) //Leave(avatar), but still has a player - final case class PlayerAlreadySpawned(player : Player) //Spawn(avatar, player), but avatar already has a player - final case class PlayerCanNotSpawn(zone : Zone, player : Player) //Spawn(avatar, player), but avatar did not initially Join(avatar) + /** + * Message that acts in reply to `Spawn(avatar, player)`, but the avatar already has a player. + * @param player the `Player` object + */ + final case class PlayerAlreadySpawned(player : Player) + /** + * Message that acts in reply to `Spawn(avatar, player)`, but the avatar did not initially `Join` this zone. + * @param zone the `Zone` object + * @param player the `Player` object + */ + final case class PlayerCanNotSpawn(zone : Zone, player : Player) } object Corpse { + /** + * Message that reports to the zone of a freshly dead player. + * @param player the dead `Player` + */ final case class Add(player : Player) + /** + * Message that tells the zone to no longer mind the dead player. + * @param player the dead `Player` + */ final case class Remove(player : Player) } + object Lattice { + /** + * Message requesting that the current zone determine where a `player` can spawn. + * @param zone_number this zone's numeric identifier + * @param player the `Player` object + * @param spawn_group the category of spawn points the request wants searched + */ + final case class RequestSpawnPoint(zone_number : Int, player : Player, spawn_group : Int) + /** + * Message that returns a discovered spawn point to a request source. + * @param zone_id the zone's text identifier + * @param building the `Building` in which the spawnpoint is located + * @param spawn_tube the spawn point holding object + */ + final case class SpawnPoint(zone_id : String, building : Building, spawn_tube : SpawnTube) + /** + * Message that informs a request source that a spawn point could not be discovered with the previous criteria. + * @param zone_number this zone's numeric identifier + * @param spawn_group the spawn point holding object; + * if `None`, then the previous `zone_number` could not be found; + * otherwise, no spawn points could be found in the 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` diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala index 92d36827d..0f93d9183 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala @@ -1,8 +1,12 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.zones +import java.util.concurrent.atomic.AtomicInteger + import akka.actor.Actor import net.psforever.objects.PlanetSideGameObject +import net.psforever.objects.serverobject.structures.StructureType +import net.psforever.types.Vector3 import org.log4s.Logger /** @@ -12,29 +16,108 @@ import org.log4s.Logger class ZoneActor(zone : Zone) extends Actor { private[this] val log = org.log4s.getLogger - def receive : Receive = { + def receive : Receive = Init + + def Init : Receive = { case Zone.Init() => zone.Init ZoneSetupCheck() + context.become(Processing) + + case _ => ; + } + + def Processing : Receive = { + //frwd to Population Actor + case msg @ Zone.Population.Join => + zone.Population forward msg + + case msg @ Zone.Population.Leave => + zone.Population forward msg + + case msg @ Zone.Population.Spawn => + zone.Population forward msg + + case msg @ Zone.Population.Release => + zone.Population forward msg + + case msg @ Zone.Corpse.Add => + zone.Population forward msg + + case msg @ Zone.Corpse.Remove => + zone.Population forward msg + + //frwd to Ground Actor + case msg @ Zone.DropItemOnGround => + zone.Ground forward msg + + case msg @ Zone.GetItemOnGround => + zone.Ground forward msg + + //frwd to Vehicle Actor + case msg @ Zone.SpawnVehicle => + zone.Transport forward msg + + case msg @ Zone.DespawnVehicle => + zone.Transport forward msg + + //own + case Zone.Lattice.RequestSpawnPoint(zone_number, player, spawn_group) => + if(zone_number == zone.Number) { + val buildingTypeSet = if(spawn_group == 6) { + Set(StructureType.Tower) + } + else if(spawn_group == 7) { + Set(StructureType.Facility, StructureType.Building) + } + else { + Set.empty[StructureType.Value] + } + val playerPosition = player.Position.xy + zone.SpawnGroups() + .filter({ case((building, _)) => + building.Faction == player.Faction && buildingTypeSet.contains(building.BuildingType) + }) + .toSeq + .sortBy({ case ((building, _)) => + Vector3.DistanceSquared(playerPosition, building.Position.xy) + }) + .headOption match { + case Some((building, List(tube))) => + sender ! Zone.Lattice.SpawnPoint(zone.Id, building, tube) + + case Some((building, tubes)) => + sender ! Zone.Lattice.SpawnPoint(zone.Id, building, scala.util.Random.shuffle(tubes).head) + + case None => + sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, Some(spawn_group)) + } + } + else { //wrong zone_number + sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None) + } case msg => log.warn(s"Received unexpected message - $msg") } - def ZoneSetupCheck(): Unit = { + def ZoneSetupCheck() : Int = { import ZoneActor._ - def guid(id : Int) = zone.GUID(id) val map = zone.Map + def guid(id : Int) = zone.GUID(id) val slog = org.log4s.getLogger(s"zone/${zone.Id}/sanity") - val validateObject : (Int, (PlanetSideGameObject)=>Boolean, String) => Boolean = ValidateObject(guid, slog) + val errors = new AtomicInteger(0) + val validateObject : (Int, (PlanetSideGameObject)=>Boolean, String) => Boolean = ValidateObject(guid, slog, errors) //check base to object associations map.ObjectToBuilding.foreach({ case((object_guid, building_id)) => if(zone.Building(building_id).isEmpty) { slog.error(s"expected a building at id #$building_id") + errors.incrementAndGet() } if(guid(object_guid).isEmpty) { slog.error(s"expected object id $object_guid to exist, but it did not") + errors.incrementAndGet() } }) @@ -55,11 +138,11 @@ class ZoneActor(zone : Zone) extends Actor { validateObject(mech_guid, ImplantMechCheck, "implant terminal mech") validateObject(interface_guid, TerminalCheck, "implant terminal interface") }) + errors.intValue() } } object ZoneActor { - /** * Recover an object from a collection and perform any number of validating tests upon it. * If the object fails any tests, log an error. @@ -73,11 +156,12 @@ object ZoneActor { * @return `true` if the object was discovered and validates correctly; * `false` if the object failed any tests */ - def ValidateObject(guid : (Int)=>Option[PlanetSideGameObject], elog : Logger) + def ValidateObject(guid : (Int)=>Option[PlanetSideGameObject], elog : Logger, errorCounter : AtomicInteger) (object_guid : Int, test : (PlanetSideGameObject)=>Boolean, description : String) : Boolean = { try { if(!test(guid(object_guid).get)) { elog.error(s"expected id $object_guid to be a $description, but it was not") + errorCounter.incrementAndGet() false } else { @@ -87,6 +171,7 @@ object ZoneActor { catch { case e : Exception => elog.error(s"expected a $description at id $object_guid but no object is initialized - $e") + errorCounter.incrementAndGet() false } } diff --git a/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala index 6787166c4..76840df88 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala @@ -2,42 +2,183 @@ package net.psforever.objects.zones import akka.actor.Actor +import net.psforever.objects.{Avatar, Player} + +import scala.annotation.tailrec +import scala.collection.concurrent.TrieMap +import scala.collection.mutable.ListBuffer + +/** + * A support `Actor` that sequences adding and removing `Avatar` and `Player` objects to mappings and lists. + * The former mapping is considered to represent every user connect to the `zone` (`as Avatar` objects) + * and their current representation (as `Player` objects). + * The latter list keeps track of a group of former user representations. + * @param zone the `Zone` object + * @param playerMap the mapping of `Avatar` objects to `Player` objects + * @param corpseList a list of `Player` objects + */ +class ZonePopulationActor(zone : Zone, playerMap : TrieMap[Avatar, Option[Player]], corpseList : ListBuffer[Player]) extends Actor { + import ZonePopulationActor._ -class ZonePopulationActor(zone : Zone) extends Actor { def receive : Receive = { case Zone.Population.Join(avatar) => - zone.PopulationJoin(avatar) + PopulationJoin(avatar, playerMap) case Zone.Population.Leave(avatar) => - zone.PopulationLeave(avatar) match { + PopulationLeave(avatar, playerMap) match { case None => ; case player @ Some(_) => sender ! Zone.Population.PlayerHasLeft(zone, player) } case Zone.Population.Spawn(avatar, player) => - zone.PopulationSpawn(avatar, player) match { + PopulationSpawn(avatar, player, playerMap) match { case Some(tplayer) => - if(tplayer != player) { - sender ! Zone.Population.PlayerCanNotSpawn(zone, player) + if(tplayer ne player) { + sender ! Zone.Population.PlayerAlreadySpawned(player) } case None => sender ! Zone.Population.PlayerCanNotSpawn(zone, player) } case Zone.Population.Release(avatar) => - zone.PopulationRelease(avatar) match { + PopulationRelease(avatar, playerMap) match { case Some(_) => ; case None => sender ! Zone.Population.PlayerHasLeft(zone, None) } case Zone.Corpse.Add(player) => - zone.CorpseAdd(player) + CorpseAdd(player, corpseList) case Zone.Corpse.Remove(player) => - zone.CorpseRemove(player) + CorpseRemove(player, corpseList) case _ => ; } } + +object ZonePopulationActor { + /** + * Add an `avatar` as the key of an `Avatar` to `Player` object pair in the given collection. + * @param avatar an `Avatar` object + * @param playerMap the mapping of `Avatar` objects to `Player` objects + * @return true, if the mapping is for a new key; + * false, if the key already exists + */ + def PopulationJoin(avatar : Avatar, playerMap : TrieMap[Avatar, Option[Player]]) : Boolean = { + playerMap.get(avatar) match { + case Some(_) => + false + case None => + playerMap += avatar -> None + true + } + } + /** + * Remove an `avatar` from the key of an `Avatar` to `Player` object pair in the given collection. + * If a `Player` object is associated at the time, return it safely. + * @param avatar an `Avatar` object + * @param playerMap the mapping of `Avatar` objects to `Player` objects + * @return any `Player` object that was associated at the time the `avatar` was removed + */ + def PopulationLeave(avatar : Avatar, playerMap : TrieMap[Avatar, Option[Player]]) : Option[Player] = { + playerMap.remove(avatar) match { + case None => + None + case Some(tplayer) => + tplayer + } + } + + /** + * Associate a `Player` object as a value to an existing `Avatar` object that will be its key. + * Do not overwrite players that are already associated. + * @param avatar an `Avatar` object + * @param player a `Player` object + * @param playerMap the mapping of `Avatar` objects to `Player` objects + * @return the `Player` object that is associated with the `Avatar` key + */ + def PopulationSpawn(avatar : Avatar, player : Player, playerMap : TrieMap[Avatar, Option[Player]]) : Option[Player] = { + playerMap.get(avatar) match { + case None => + None + case Some(tplayer) => + tplayer match { + case Some(aplayer) => + Some(aplayer) + case None => + playerMap(avatar) = Some(player) + Some(player) + } + } + } + + /** + * Disassociate a `Player` object from an existing `Avatar` object that was be its key. + * @param avatar an `Avatar` object + * @param playerMap the mapping of `Avatar` objects to `Player` objects + * @return any `Player` object that is associated at the time + */ + def PopulationRelease(avatar : Avatar, playerMap : TrieMap[Avatar, Option[Player]]) : Option[Player] = { + playerMap.get(avatar) match { + case None => + None + case Some(tplayer) => + playerMap(avatar) = None + tplayer + } + } + + /** + * If the given `player` passes a condition check, add it to the list. + * @param player a `Player` object + * @param corpseList a list of `Player` objects + * @return true, if the `player` was added to the list; + * false, otherwise + */ + def CorpseAdd(player : Player, corpseList : ListBuffer[Player]) : Boolean = { + if(player.isBackpack) { + corpseList += player + true + } + else { + false + } + } + + /** + * Remove the given `player` from the list. + * @param player a `Player` object + * @param corpseList a list of `Player` objects + */ + def CorpseRemove(player : Player, corpseList : ListBuffer[Player]) : Unit = { + recursiveFindCorpse(corpseList.iterator, player) match { + case None => ; + case Some(index) => + corpseList.remove(index) + } + } + + /** + * A recursive function that finds and removes a specific player from a list of players. + * @param iter an `Iterator` of `Player` 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[Player], player : Player, index : Int = 0) : Option[Int] = { + if(!iter.hasNext) { + None + } + else { + if(iter.next == player) { + Some(index) + } + else { + recursiveFindCorpse(iter, player, index + 1) + } + } + } +} diff --git a/common/src/main/scala/net/psforever/packet/game/DisconnectMessage.scala b/common/src/main/scala/net/psforever/packet/game/DisconnectMessage.scala index ddd65b5a0..4c1ec29b1 100644 --- a/common/src/main/scala/net/psforever/packet/game/DisconnectMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/DisconnectMessage.scala @@ -20,8 +20,8 @@ import scodec.codecs._ * @param unk3 na */ final case class DisconnectMessage(msg : String, - unk2 : String = "", - unk3 : String = "") + unk2 : String, + unk3 : String) extends PlanetSideGamePacket { type Packet = DisconnectMessage def opcode = GamePacketOpcode.DisconnectMessage @@ -29,6 +29,15 @@ final case class DisconnectMessage(msg : String, } object DisconnectMessage extends Marshallable[DisconnectMessage] { + /** + * Overloaded constructor that focuses only on the visible disconnection message + * @param msg the displayed message + * @return a `DisconnectMessage` object + */ + def apply(msg : String) : DisconnectMessage = { + new DisconnectMessage(msg, "", "") + } + implicit val codec : Codec[DisconnectMessage] = ( ("msg" | PacketHelpers.encodedString) :: ("unk2" | PacketHelpers.encodedString) :: diff --git a/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala b/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala index 045aa6d0c..c3049546f 100644 --- a/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala @@ -6,12 +6,16 @@ import scodec.Codec import scodec.codecs._ /** - * - * @param unk1 na - * @param unk2 na + * na + * @param unk1 when defined, na; + * non-zero when selecting the sanctuary option from a non-sanctuary continent deployment map + * @param unk2 when defined, indicates type of spawn point by destination; + * 0 is unknown (may refer to all available spawns regardless of last position); + * 6 is towers; + * 7 is facilities * @param unk3 na * @param unk4 na - * @param unk5 continent? + * @param unk5 when defined, the continent number */ final case class SpawnRequestMessage(unk1 : Int, unk2 : Long, diff --git a/common/src/main/scala/net/psforever/types/Vector3.scala b/common/src/main/scala/net/psforever/types/Vector3.scala index 3b2b05833..e501d635f 100644 --- a/common/src/main/scala/net/psforever/types/Vector3.scala +++ b/common/src/main/scala/net/psforever/types/Vector3.scala @@ -9,8 +9,8 @@ final case class Vector3(x : Float, y : Float, z : Float) { /** - * Operator override for vector addition, treating `Vector3` objects as actual mathematical vectors. - * The application of this overload is "vector1 + vector2." + * Operator for vector addition, treating `Vector3` objects as actual mathematical vectors. + * The application of this definition is "vector1 + vector2." * @param vec the other `Vector3` object * @return a new `Vector3` object with the summed values */ @@ -19,8 +19,8 @@ final case class Vector3(x : Float, } /** - * Operator override for vector subtraction, treating `Vector3` objects as actual mathematical vectors. - * The application of this overload is "vector1 - vector2." + * Operator for vector subtraction, treating `Vector3` objects as actual mathematical vectors. + * The application of this definition is "vector1 - vector2." * @param vec the other `Vector3` object * @return a new `Vector3` object with the difference values */ @@ -29,7 +29,7 @@ final case class Vector3(x : Float, } /** - * Operator override for vector scaling, treating `Vector3` objects as actual mathematical vectors. + * Operator for vector scaling, treating `Vector3` objects as actual mathematical vectors. * The application of this overload is "vector * scalar" exclusively. * "scalar * vector" is invalid. * @param scalar the value to multiply this vector @@ -38,6 +38,14 @@ final case class Vector3(x : Float, def *(scalar : Float) : Vector3 = { Vector3(x*scalar, y*scalar, z*scalar) } + + /** + * Operator for returning the ground-planar coordinates + * and ignoring the perpendicular distance from the world floor. + * The application of this definition is "vector.xy" or "vector xy." + * @return a new `Vector3` object with only two of the components of the original + */ + def xy : Vector3 = Vector3(x, y, 0) } object Vector3 { diff --git a/common/src/test/scala/Vector3Test.scala b/common/src/test/scala/Vector3Test.scala index 75f1c2e53..26fcddd51 100644 --- a/common/src/test/scala/Vector3Test.scala +++ b/common/src/test/scala/Vector3Test.scala @@ -64,6 +64,11 @@ class Vector3Test extends Specification { vec * 3f mustEqual Vector3(3.8999999f, -7.7999997f, 11.700001f) } + "separate into x-component and y-component only" in { + val obj = Vector3(1.1f, 2.2f, 3.3f) + obj.xy mustEqual Vector3(1.1f, 2.2f, 0f) + } + "calculate the unit vector (zero)" in { Vector3.Unit(Vector3.Zero) mustEqual Vector3(0,0,0) } diff --git a/common/src/test/scala/objects/AvatarTest.scala b/common/src/test/scala/objects/AvatarTest.scala new file mode 100644 index 000000000..95fb698fe --- /dev/null +++ b/common/src/test/scala/objects/AvatarTest.scala @@ -0,0 +1,397 @@ +// Copyright (c) 2017 PSForever +package objects + +import net.psforever.objects.GlobalDefinitions._ +import net.psforever.objects._ +import net.psforever.objects.definition.ImplantDefinition +import net.psforever.types.{CharacterGender, ImplantType, PlanetSideEmpire} +import org.specs2.mutable._ + +class AvatarTest extends Specification { + def CreatePlayer() : (Player, Avatar) = { + val avatar = Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) + val + player = Player(avatar) + player.Slot(0).Equipment = Tool(beamer) + player.Slot(2).Equipment = Tool(suppressor) + player.Slot(4).Equipment = Tool(forceblade) + player.Slot(6).Equipment = AmmoBox(bullet_9mm) + player.Slot(9).Equipment = AmmoBox(bullet_9mm) + player.Slot(12).Equipment = AmmoBox(bullet_9mm) + player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) + player.Slot(36).Equipment = AmmoBox(energy_cell) + player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) + (player, avatar) + } + + "construct" in { + val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + av.name mustEqual "Chord" + av.faction mustEqual PlanetSideEmpire.TR + av.sex mustEqual CharacterGender.Male + av.head mustEqual 0 + av.voice mustEqual 5 + av.BEP mustEqual 0 + av.CEP mustEqual 0 + av.Certifications mustEqual Set.empty + av.Definition.ObjectId mustEqual 121 + } + + "can maintain cumulative battle experience point values" in { + val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + av.BEP mustEqual 0 + av.BEP = 100 + av.BEP mustEqual 100 + av.BEP = 700 + av.BEP mustEqual 700 + } + + "can maintain battle experience point values up to a maximum (Long.MaxValue)" in { + val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + av.BEP mustEqual 0 + av.BEP = 4294967295L + av.BEP mustEqual 4294967295L + } + + "can not maintain battle experience point values below zero" in { + val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + av.BEP mustEqual 0 + av.BEP = -1 + av.BEP mustEqual 0 + av.BEP = 100 + av.BEP mustEqual 100 + av.BEP = -1 + av.BEP mustEqual 0 + } + + "can maintain cumulative command experience point values" in { + val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + av.CEP mustEqual 0 + av.CEP = 100 + av.CEP mustEqual 100 + av.CEP = 700 + av.CEP mustEqual 700 + } + + "can maintain command experience point values up to a maximum (Long.MaxValue)" in { + val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + av.CEP mustEqual 0 + av.CEP = 4294967295L + av.CEP mustEqual 4294967295L + } + + "can not maintain command experience point values below zero" in { + val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + av.CEP mustEqual 0 + av.CEP = -1 + av.CEP mustEqual 0 + av.CEP = 100 + av.CEP mustEqual 100 + av.CEP = -1 + av.CEP mustEqual 0 + } + + "can tell the difference between avatars" in { + (Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual true + + (Avatar("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + Avatar("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual false + + (Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + Avatar("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, 5)) mustEqual false + + (Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Female, 0, 5)) mustEqual false + + (Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 1, 5)) mustEqual false + + (Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 6)) mustEqual false + } + + //refer to ImplantTest.scala for more tests + "maximum of three implant slots" in { + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants.length mustEqual 3 + obj.Implants(0).Unlocked mustEqual false + obj.Implants(0).Initialized mustEqual false + obj.Implants(0).Active mustEqual false + obj.Implants(0).Implant mustEqual ImplantType.None + obj.Implant(0) mustEqual ImplantType.None + obj.Implants(0).Installed mustEqual None + obj.Implants(1).Unlocked mustEqual false + obj.Implants(1).Initialized mustEqual false + obj.Implants(1).Active mustEqual false + obj.Implants(1).Implant mustEqual ImplantType.None + obj.Implant(1) mustEqual ImplantType.None + obj.Implants(1).Installed mustEqual None + obj.Implants(2).Unlocked mustEqual false + obj.Implants(2).Initialized mustEqual false + obj.Implants(2).Active mustEqual false + obj.Implants(2).Implant mustEqual ImplantType.None + obj.Implant(2) mustEqual ImplantType.None + obj.Implants(2).Installed mustEqual None + + obj.Implant(3) mustEqual ImplantType.None //invalid slots beyond the third always reports as ImplantType.None + } + + "can install an implant" in { + val testplant : ImplantDefinition = ImplantDefinition(1) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.InstallImplant(testplant) mustEqual Some(0) + obj.Implants.find({p => p.Implant == ImplantType(1)}) match { //find the installed implant + case Some(slot) => + slot.Installed mustEqual Some(testplant) + case _ => + ko + } + ok + } + + "can install implants in sequential slots" in { + val testplant1 : ImplantDefinition = ImplantDefinition(1) + val testplant2 : ImplantDefinition = ImplantDefinition(2) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.Implants(1).Unlocked = true + + obj.InstallImplant(testplant1) mustEqual Some(0) + obj.InstallImplant(testplant2) mustEqual Some(1) + } + + "can not install the same type of implant twice" in { + val testplant1 : ImplantDefinition = ImplantDefinition(1) + val testplant2 : ImplantDefinition = ImplantDefinition(1) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.Implants(1).Unlocked = true + + obj.InstallImplant(testplant1) mustEqual Some(0) + obj.InstallImplant(testplant2) mustEqual None + } + + "can not install more implants than slots available (two unlocked)" in { + val testplant1 : ImplantDefinition = ImplantDefinition(1) + val testplant2 : ImplantDefinition = ImplantDefinition(2) + val testplant3 : ImplantDefinition = ImplantDefinition(3) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.Implants(1).Unlocked = true + + obj.InstallImplant(testplant1) mustEqual Some(0) + obj.InstallImplant(testplant2) mustEqual Some(1) + obj.InstallImplant(testplant3) mustEqual None + } + + "can not install more implants than slots available (four implants)" in { + val testplant1 : ImplantDefinition = ImplantDefinition(1) + val testplant2 : ImplantDefinition = ImplantDefinition(2) + val testplant3 : ImplantDefinition = ImplantDefinition(3) + val testplant4 : ImplantDefinition = ImplantDefinition(4) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.Implants(1).Unlocked = true + obj.Implants(2).Unlocked = true + + obj.InstallImplant(testplant1) mustEqual Some(0) + obj.InstallImplant(testplant2) mustEqual Some(1) + obj.InstallImplant(testplant3) mustEqual Some(2) + obj.InstallImplant(testplant4) mustEqual None + } + + "can uninstall an implant" in { + val testplant : ImplantDefinition = ImplantDefinition(1) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.InstallImplant(testplant) mustEqual Some(0) + obj.Implants(0).Installed mustEqual Some(testplant) + + obj.UninstallImplant(testplant.Type) mustEqual Some(0) + obj.Implants(0).Installed mustEqual None + } + + "can uninstall just a specific implant" in { + val testplant1 : ImplantDefinition = ImplantDefinition(1) + val testplant2 : ImplantDefinition = ImplantDefinition(2) + val testplant3 : ImplantDefinition = ImplantDefinition(3) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.Implants(1).Unlocked = true + obj.Implants(2).Unlocked = true + obj.InstallImplant(testplant1) mustEqual Some(0) + obj.InstallImplant(testplant2) mustEqual Some(1) + obj.InstallImplant(testplant3) mustEqual Some(2) + + obj.Implant(0) mustEqual testplant1.Type + obj.Implant(1) mustEqual testplant2.Type + obj.Implant(2) mustEqual testplant3.Type + obj.UninstallImplant(testplant2.Type) mustEqual Some(1) + obj.Implant(0) mustEqual testplant1.Type + obj.Implant(1) mustEqual ImplantType.None + obj.Implant(2) mustEqual testplant3.Type + } + + "can install implants to any available slot" in { + val testplant1 : ImplantDefinition = ImplantDefinition(1) + val testplant2 : ImplantDefinition = ImplantDefinition(2) + val testplant3 : ImplantDefinition = ImplantDefinition(3) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.Implants(1).Unlocked = true + obj.Implants(2).Unlocked = true + obj.InstallImplant(testplant1) mustEqual Some(0) + obj.InstallImplant(testplant2) mustEqual Some(1) + obj.InstallImplant(testplant3) mustEqual Some(2) + obj.UninstallImplant(testplant2.Type) mustEqual Some(1) + obj.Implant(0) mustEqual testplant1.Type + obj.Implant(1) mustEqual ImplantType.None + obj.Implant(2) mustEqual testplant3.Type + + val testplant4 : ImplantDefinition = ImplantDefinition(4) + obj.InstallImplant(testplant4) mustEqual Some(1) + obj.Implant(0) mustEqual testplant1.Type + obj.Implant(1) mustEqual testplant4.Type + obj.Implant(2) mustEqual testplant3.Type + } + + "can reset implants to uninitialized state" in { + val testplant1 : ImplantDefinition = ImplantDefinition(1) + val testplant2 : ImplantDefinition = ImplantDefinition(2) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.Implants(1).Unlocked = true + obj.InstallImplant(testplant1) mustEqual Some(0) + obj.InstallImplant(testplant2) mustEqual Some(1) + obj.Implants(0).Initialized = true + obj.Implants(0).Active = true + obj.Implants(1).Initialized = true + + obj.Implants(0).Initialized mustEqual true + obj.Implants(0).Active mustEqual true + obj.Implants(1).Initialized mustEqual true + obj.ResetAllImplants() + obj.Implants(0).Initialized mustEqual false + obj.Implants(0).Active mustEqual false + obj.Implants(1).Initialized mustEqual false + } + + "does not have any loadout specifications by default" in { + val (_, avatar) = CreatePlayer() + (0 to 9).foreach { avatar.LoadLoadout(_) mustEqual None } + ok + } + + "save player's current inventory as a loadout" in { + val (obj, avatar) = CreatePlayer() + obj.Slot(0).Equipment.get.asInstanceOf[Tool].Magazine = 1 //non-standard but legal + obj.Slot(2).Equipment.get.asInstanceOf[Tool].AmmoSlot.Magazine = 100 //non-standard (and out of range, real=25) + avatar.SaveLoadout(obj, "test", 0) + + avatar.LoadLoadout(0) match { + case Some(items) => + items.Label mustEqual "test" + items.ExoSuit mustEqual obj.ExoSuit + items.Subtype mustEqual 0 + + items.VisibleSlots.length mustEqual 3 + val holsters = items.VisibleSlots.sortBy(_.index) + holsters.head.index mustEqual 0 + holsters.head.item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual beamer + holsters.head.item.asInstanceOf[Loadout.ShorthandTool].ammo.head.ammo.capacity mustEqual 1 //we changed this + holsters(1).index mustEqual 2 + holsters(1).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual suppressor + holsters(1).item.asInstanceOf[Loadout.ShorthandTool].ammo.head.ammo.capacity mustEqual 100 //we changed this + holsters(2).index mustEqual 4 + holsters(2).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual forceblade + + items.Inventory.length mustEqual 6 + val inventory = items.Inventory.sortBy(_.index) + inventory.head.index mustEqual 6 + inventory.head.item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm + inventory(1).index mustEqual 9 + inventory(1).item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm + inventory(2).index mustEqual 12 + inventory(2).item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm + inventory(3).index mustEqual 33 + inventory(3).item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm_AP + inventory(4).index mustEqual 36 + inventory(4).item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual energy_cell + inventory(5).index mustEqual 39 + inventory(5).item.asInstanceOf[Loadout.ShorthandSimpleItem].definition mustEqual remote_electronics_kit + case None => + ko + } + } + + "save player's current inventory as a loadout, only found in the called-out slot number" in { + val (obj, avatar) = CreatePlayer() + avatar.SaveLoadout(obj, "test", 0) + + avatar.LoadLoadout(1).isDefined mustEqual false + avatar.LoadLoadout(0).isDefined mustEqual true + } + + "try to save player's current inventory as a loadout, but will not save to an invalid slot" in { + val (obj, avatar) = CreatePlayer() + avatar.SaveLoadout(obj, "test", 10) + + avatar.LoadLoadout(10) mustEqual None + } + + "save player's current inventory as a loadout, without inventory contents" in { + val (obj, avatar) = CreatePlayer() + obj.Inventory.Clear() + avatar.SaveLoadout(obj, "test", 0) + + avatar.LoadLoadout(0) match { + case Some(items) => + items.Label mustEqual "test" + items.ExoSuit mustEqual obj.ExoSuit + items.Subtype mustEqual 0 + items.VisibleSlots.length mustEqual 3 + items.Inventory.length mustEqual 0 //empty + case None => + ko + } + } + + "save player's current inventory as a loadout, without visible slot contents" in { + val (obj, avatar) = CreatePlayer() + obj.Slot(0).Equipment = None + obj.Slot(2).Equipment = None + obj.Slot(4).Equipment = None + avatar.SaveLoadout(obj, "test", 0) + + avatar.LoadLoadout(0) match { + case Some(items) => + items.Label mustEqual "test" + items.ExoSuit mustEqual obj.ExoSuit + items.Subtype mustEqual 0 + items.VisibleSlots.length mustEqual 0 //empty + items.Inventory.length mustEqual 6 + case None => + ko + } + } + + "save, load, delete; rapidly" in { + val (obj, avatar) = CreatePlayer() + avatar.SaveLoadout(obj, "test", 0) + + avatar.LoadLoadout(0).isDefined mustEqual true + avatar.DeleteLoadout(0) + avatar.LoadLoadout(0) mustEqual None + } + + "the fifth slot is the locker wrapped in an EquipmentSlot" in { + val (_, avatar) = CreatePlayer() + avatar.FifthSlot.Equipment.contains(avatar.Locker) + } + + "toString" in { + Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5).toString mustEqual "TR Chord" + } +} diff --git a/common/src/test/scala/objects/BuildingTest.scala b/common/src/test/scala/objects/BuildingTest.scala index dc533b1b8..72da3fab0 100644 --- a/common/src/test/scala/objects/BuildingTest.scala +++ b/common/src/test/scala/objects/BuildingTest.scala @@ -6,7 +6,7 @@ import net.psforever.objects.GlobalDefinitions import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.doors.{Door, DoorControl} -import net.psforever.objects.serverobject.structures.{Amenity, Building, BuildingControl, WarpGate} +import net.psforever.objects.serverobject.structures._ import net.psforever.objects.zones.Zone import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.PlanetSideEmpire @@ -27,7 +27,7 @@ class AmenityTest extends Specification { "can be owned by a building" in { val ao = new AmenityObject() - val bldg = Building(10, Zone.Nowhere) + val bldg = Building(10, Zone.Nowhere, StructureType.Building) ao.Owner = bldg ao.Owner mustEqual bldg @@ -51,7 +51,7 @@ class AmenityTest extends Specification { "confer faction allegiance through ownership" in { //see FactionAffinityTest val ao = new AmenityObject() - val bldg = Building(10, Zone.Nowhere) + val bldg = Building(10, Zone.Nowhere, StructureType.Building) ao.Owner = bldg bldg.Faction mustEqual PlanetSideEmpire.NEUTRAL ao.Faction mustEqual PlanetSideEmpire.NEUTRAL @@ -66,7 +66,7 @@ class AmenityTest extends Specification { class BuildingTest extends Specification { "Building" should { "construct" in { - val bldg = Building(10, Zone.Nowhere) + val bldg = Building(10, Zone.Nowhere, StructureType.Building) bldg.Id mustEqual 10 bldg.Actor mustEqual ActorRef.noSender bldg.Amenities mustEqual Nil @@ -75,7 +75,7 @@ class BuildingTest extends Specification { } "change faction affinity" in { - val bldg = Building(10, Zone.Nowhere) + val bldg = Building(10, Zone.Nowhere, StructureType.Building) bldg.Faction mustEqual PlanetSideEmpire.NEUTRAL bldg.Faction = PlanetSideEmpire.TR @@ -83,7 +83,7 @@ class BuildingTest extends Specification { } "keep track of amenities" in { - val bldg = Building(10, Zone.Nowhere) + val bldg = Building(10, Zone.Nowhere, StructureType.Building) val door1 = Door(GlobalDefinitions.door) val door2 = Door(GlobalDefinitions.door) @@ -114,7 +114,7 @@ class WarpGateTest extends Specification { class BuildingControl1Test extends ActorTest { "Building Control" should { "construct" in { - val bldg = Building(10, Zone.Nowhere) + val bldg = Building(10, Zone.Nowhere, StructureType.Building) bldg.Actor = system.actorOf(Props(classOf[BuildingControl], bldg), "test") assert(bldg.Actor != ActorRef.noSender) } @@ -124,7 +124,7 @@ class BuildingControl1Test extends ActorTest { class BuildingControl2Test extends ActorTest { "Building Control" should { "convert and assert faction affinity on convert request" in { - val bldg = Building(10, Zone.Nowhere) + val bldg = Building(10, Zone.Nowhere, StructureType.Building) bldg.Faction = PlanetSideEmpire.TR bldg.Actor = system.actorOf(Props(classOf[BuildingControl], bldg), "test") assert(bldg.Faction == PlanetSideEmpire.TR) @@ -142,7 +142,7 @@ class BuildingControl2Test extends ActorTest { class BuildingControl3Test extends ActorTest { "Building Control" should { "convert and assert faction affinity on convert request, and for each of its amenities" in { - val bldg = Building(10, Zone.Nowhere) + val bldg = Building(10, Zone.Nowhere, StructureType.Building) bldg.Faction = PlanetSideEmpire.TR bldg.Actor = system.actorOf(Props(classOf[BuildingControl], bldg), "building-test") val door1 = Door(GlobalDefinitions.door) diff --git a/common/src/test/scala/objects/DoorTest.scala b/common/src/test/scala/objects/DoorTest.scala index 95dddace2..73e55c00d 100644 --- a/common/src/test/scala/objects/DoorTest.scala +++ b/common/src/test/scala/objects/DoorTest.scala @@ -4,7 +4,7 @@ package objects import akka.actor.{ActorRef, ActorSystem, Props} import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.doors.{Door, DoorControl} -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.zones.Zone import net.psforever.packet.game.{PlanetSideGUID, UseItemMessage} import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3} @@ -121,7 +121,7 @@ object DoorControlTest { def SetUpAgents(faction : PlanetSideEmpire.Value)(implicit system : ActorSystem) : (Player, Door) = { val door = Door(GlobalDefinitions.door) door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door") - door.Owner = new Building(0, Zone.Nowhere) + door.Owner = new Building(0, Zone.Nowhere, StructureType.Building) door.Owner.Faction = faction (Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), door) } diff --git a/common/src/test/scala/objects/FactionAffinityTest.scala b/common/src/test/scala/objects/FactionAffinityTest.scala index c4598896a..a3163d9f7 100644 --- a/common/src/test/scala/objects/FactionAffinityTest.scala +++ b/common/src/test/scala/objects/FactionAffinityTest.scala @@ -5,7 +5,7 @@ import akka.actor.{Actor, ActorSystem, Props} import net.psforever.objects.{GlobalDefinitions, Vehicle} import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.doors.Door -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.zones.Zone import net.psforever.types.PlanetSideEmpire import org.specs2.mutable.Specification @@ -42,7 +42,7 @@ class FactionAffinityTest extends Specification { "inherits affinity from owner 2" in { val obj = new Door(GlobalDefinitions.door) - val bldg = new Building(1, Zone.Nowhere) + val bldg = new Building(1, Zone.Nowhere, StructureType.Building) obj.Owner = bldg obj.Faction mustEqual PlanetSideEmpire.NEUTRAL diff --git a/common/src/test/scala/objects/IFFLockTest.scala b/common/src/test/scala/objects/IFFLockTest.scala index f31133154..5cd66613c 100644 --- a/common/src/test/scala/objects/IFFLockTest.scala +++ b/common/src/test/scala/objects/IFFLockTest.scala @@ -5,7 +5,7 @@ import akka.actor.{ActorRef, ActorSystem, Props} import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.locks.{IFFLock, IFFLockControl} -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.zones.Zone import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.{CharacterGender, PlanetSideEmpire} @@ -67,7 +67,7 @@ object IFFLockControlTest { def SetUpAgents(faction : PlanetSideEmpire.Value)(implicit system : ActorSystem) : (Player, IFFLock) = { val lock = IFFLock(GlobalDefinitions.lock_external) lock.Actor = system.actorOf(Props(classOf[IFFLockControl], lock), "lock-control") - lock.Owner = new Building(0, Zone.Nowhere) + lock.Owner = new Building(0, Zone.Nowhere, StructureType.Building) lock.Owner.Faction = faction (Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), lock) } diff --git a/common/src/test/scala/objects/LoadoutTest.scala b/common/src/test/scala/objects/LoadoutTest.scala index 84a1f3fd3..fb01d6963 100644 --- a/common/src/test/scala/objects/LoadoutTest.scala +++ b/common/src/test/scala/objects/LoadoutTest.scala @@ -9,190 +9,82 @@ import org.specs2.mutable._ class LoadoutTest extends Specification { val avatar = Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) - def CreatePlayer() : (Player, Avatar) = { - val avatar = Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) - val - player = Player(avatar) - player.Slot(0).Equipment = Tool(beamer) - player.Slot(2).Equipment = Tool(suppressor) - player.Slot(4).Equipment = Tool(forceblade) - player.Slot(6).Equipment = AmmoBox(bullet_9mm) - player.Slot(9).Equipment = AmmoBox(bullet_9mm) - player.Slot(12).Equipment = AmmoBox(bullet_9mm) - player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) - player.Slot(36).Equipment = AmmoBox(energy_cell) - player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) - (player, avatar) + def CreatePlayer() : Player = { + new Player(avatar) { + Slot(0).Equipment = Tool(beamer) + Slot(2).Equipment = Tool(suppressor) + Slot(4).Equipment = Tool(forceblade) + Slot(6).Equipment = ConstructionItem(ace) + Slot(9).Equipment = AmmoBox(bullet_9mm) + Slot(12).Equipment = AmmoBox(bullet_9mm) + Slot(33).Equipment = Kit(medkit) + Slot(39).Equipment = SimpleItem(remote_electronics_kit) + } } - "Player Loadout" should { - "test sample player" in { - val (obj, _) = CreatePlayer() - obj.Holsters()(0).Equipment.get.Definition mustEqual beamer - obj.Holsters()(2).Equipment.get.Definition mustEqual suppressor - obj.Holsters()(4).Equipment.get.Definition mustEqual forceblade - obj.Slot(6).Equipment.get.Definition mustEqual bullet_9mm - obj.Slot(9).Equipment.get.Definition mustEqual bullet_9mm - obj.Slot(12).Equipment.get.Definition mustEqual bullet_9mm - obj.Slot(33).Equipment.get.Definition mustEqual bullet_9mm_AP - obj.Slot(36).Equipment.get.Definition mustEqual energy_cell - obj.Slot(39).Equipment.get.Definition mustEqual remote_electronics_kit - } + "test sample player" in { + val player = CreatePlayer() + player.Holsters()(0).Equipment.get.Definition mustEqual beamer + player.Holsters()(2).Equipment.get.Definition mustEqual suppressor + player.Holsters()(4).Equipment.get.Definition mustEqual forceblade + player.Slot(6).Equipment.get.Definition mustEqual ace + player.Slot(9).Equipment.get.Definition mustEqual bullet_9mm + player.Slot(12).Equipment.get.Definition mustEqual bullet_9mm + player.Slot(33).Equipment.get.Definition mustEqual medkit + player.Slot(39).Equipment.get.Definition mustEqual remote_electronics_kit + } - "do not load, if never saved" in { - CreatePlayer()._2.LoadLoadout(0) mustEqual None - } + "create a loadout that contains a player's inventory" in { + val player = CreatePlayer() + val obj = Loadout.Create(player, "test") - "save but incorrect load" in { - val (obj, avatar) = CreatePlayer() - avatar.SaveLoadout(obj, "test", 0) + obj.Label mustEqual "test" + obj.ExoSuit mustEqual obj.ExoSuit + obj.Subtype mustEqual 0 - avatar.LoadLoadout(1) mustEqual None - } + obj.VisibleSlots.length mustEqual 3 + val holsters = obj.VisibleSlots.sortBy(_.index) + holsters.head.index mustEqual 0 + holsters.head.item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual beamer + holsters(1).index mustEqual 2 + holsters(1).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual suppressor + holsters(2).index mustEqual 4 + holsters(2).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual forceblade - "save and load" in { - val (obj, avatar) = CreatePlayer() - obj.Slot(0).Equipment.get.asInstanceOf[Tool].Magazine = 1 //non-standard but legal - obj.Slot(2).Equipment.get.asInstanceOf[Tool].AmmoSlot.Magazine = 100 //non-standard (and out of range, real=25) - avatar.SaveLoadout(obj, "test", 0) + obj.Inventory.length mustEqual 5 + val inventory = obj.Inventory.sortBy(_.index) + inventory.head.index mustEqual 6 + inventory.head.item.asInstanceOf[Loadout.ShorthandConstructionItem].definition mustEqual ace + inventory(1).index mustEqual 9 + inventory(1).item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm + inventory(2).index mustEqual 12 + inventory(2).item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm + inventory(3).index mustEqual 33 + inventory(3).item.asInstanceOf[Loadout.ShorthandKit].definition mustEqual medkit + inventory(4).index mustEqual 39 + inventory(4).item.asInstanceOf[Loadout.ShorthandSimpleItem].definition mustEqual remote_electronics_kit + } - avatar.LoadLoadout(0) match { - case Some(items) => - items.Label mustEqual "test" - items.ExoSuit mustEqual obj.ExoSuit - items.Subtype mustEqual 0 + "distinguish MAX subtype information" in { + val player = CreatePlayer() + val slot = player.Slot(0) + slot.Equipment = None //only an unequipped slot can have its Equipment Size changed (Rifle -> Max) + Player.SuitSetup(player, ExoSuitType.MAX) - items.VisibleSlots.length mustEqual 3 - val holsters = items.VisibleSlots.sortBy(_.index) - holsters.head.index mustEqual 0 - holsters.head.item.asInstanceOf[Loadout.ShorthandTool].tdef mustEqual beamer - holsters.head.item.asInstanceOf[Loadout.ShorthandTool].ammo.head.ammo.capacity mustEqual 1 - holsters(1).index mustEqual 2 - holsters(1).item.asInstanceOf[Loadout.ShorthandTool].tdef mustEqual suppressor - holsters(1).item.asInstanceOf[Loadout.ShorthandTool].ammo.head.ammo.capacity mustEqual 100 - holsters(2).index mustEqual 4 - holsters(2).item.asInstanceOf[Loadout.ShorthandTool].tdef mustEqual forceblade + val ldout1 = Loadout.Create(player, "weaponless") + slot.Equipment = None + slot.Equipment = Tool(trhev_dualcycler) + val ldout2 = Loadout.Create(player, "cycler") + slot.Equipment = None + slot.Equipment = Tool(trhev_pounder) + val ldout3 = Loadout.Create(player, "pounder") + slot.Equipment = None + slot.Equipment = Tool(trhev_burster) + val ldout4 = Loadout.Create(player, "burster") - items.Inventory.length mustEqual 6 - val inventory = items.Inventory.sortBy(_.index) - inventory.head.index mustEqual 6 - inventory.head.item.asInstanceOf[Loadout.ShorthandAmmoBox].adef mustEqual bullet_9mm - inventory(1).index mustEqual 9 - inventory(1).item.asInstanceOf[Loadout.ShorthandAmmoBox].adef mustEqual bullet_9mm - inventory(2).index mustEqual 12 - inventory(2).item.asInstanceOf[Loadout.ShorthandAmmoBox].adef mustEqual bullet_9mm - inventory(3).index mustEqual 33 - inventory(3).item.asInstanceOf[Loadout.ShorthandAmmoBox].adef mustEqual bullet_9mm_AP - inventory(4).index mustEqual 36 - inventory(4).item.asInstanceOf[Loadout.ShorthandAmmoBox].adef mustEqual energy_cell - inventory(5).index mustEqual 39 - inventory(5).item.asInstanceOf[Loadout.ShorthandSimpleItem].sdef mustEqual remote_electronics_kit - case None => - ko - } - } - - "save without inventory contents" in { - val (obj, avatar) = CreatePlayer() - obj.Inventory.Clear() - avatar.SaveLoadout(obj, "test", 0) - - avatar.LoadLoadout(0) match { - case Some(items) => - items.Label mustEqual "test" - items.ExoSuit mustEqual obj.ExoSuit - items.Subtype mustEqual 0 - items.VisibleSlots.length mustEqual 3 - items.Inventory.length mustEqual 0 //empty - case None => - ko - } - } - - "save without visible slot contents" in { - val (obj, avatar) = CreatePlayer() - obj.Slot(0).Equipment = None - obj.Slot(2).Equipment = None - obj.Slot(4).Equipment = None - avatar.SaveLoadout(obj, "test", 0) - - avatar.LoadLoadout(0) match { - case Some(items) => - items.Label mustEqual "test" - items.ExoSuit mustEqual obj.ExoSuit - items.Subtype mustEqual 0 - items.VisibleSlots.length mustEqual 0 //empty - items.Inventory.length mustEqual 6 - case None => - ko - } - } - - "save (a construction item) and load" in { - val (obj, avatar) = CreatePlayer() - obj.Inventory.Clear() - obj.Slot(6).Equipment = ConstructionItem(advanced_ace) - avatar.SaveLoadout(obj, "test", 0) - - avatar.LoadLoadout(0) match { - case Some(items) => - items.Inventory.length mustEqual 1 - items.Inventory.head.index mustEqual 6 - items.Inventory.head.item.asInstanceOf[Loadout.ShorthandConstructionItem].cdef mustEqual advanced_ace - case None => - ko - } - } - - "save (a kit) and load" in { - val (obj, avatar) = CreatePlayer() - obj.Inventory.Clear() - obj.Slot(6).Equipment = Kit(medkit) - avatar.SaveLoadout(obj, "test", 0) - - avatar.LoadLoadout(0) match { - case Some(items) => - items.Inventory.length mustEqual 1 - items.Inventory.head.index mustEqual 6 - items.Inventory.head.item.asInstanceOf[Loadout.ShorthandKit].kdef mustEqual medkit - case None => - ko - } - } - - "save, load, delete" in { - val (obj, avatar) = CreatePlayer() - avatar.SaveLoadout(obj, "test", 0) - - avatar.LoadLoadout(0) match { - case None => - ko - case Some(_) => ; //good; keep going - } - avatar.DeleteLoadout(0) - avatar.LoadLoadout(0) mustEqual None - } - - "distinguish MAX subtype information" in { - val (obj, avatar) = CreatePlayer() - val slot = obj.Slot(0) - slot.Equipment = None //only an unequipped slot can have its Equipment Size changed (Rifle -> Max) - Player.SuitSetup(obj, ExoSuitType.MAX) - avatar.SaveLoadout(obj, "generic", 0) //weaponless - slot.Equipment = None - slot.Equipment = Tool(trhev_dualcycler) - avatar.SaveLoadout(obj, "cycler", 1) - slot.Equipment = None - slot.Equipment = Tool(trhev_pounder) - avatar.SaveLoadout(obj, "pounder", 2) - slot.Equipment = None - slot.Equipment = Tool(trhev_burster) - avatar.SaveLoadout(obj, "burster", 3) - - avatar.LoadLoadout(0).get.Subtype mustEqual 0 - avatar.LoadLoadout(1).get.Subtype mustEqual 1 - avatar.LoadLoadout(2).get.Subtype mustEqual 2 - avatar.LoadLoadout(3).get.Subtype mustEqual 3 - } + ldout1.Subtype mustEqual 0 + ldout2.Subtype mustEqual 1 + ldout3.Subtype mustEqual 2 + ldout4.Subtype mustEqual 3 } } diff --git a/common/src/test/scala/objects/PlayerTest.scala b/common/src/test/scala/objects/PlayerTest.scala index 6a4de73f1..dd9771cb5 100644 --- a/common/src/test/scala/objects/PlayerTest.scala +++ b/common/src/test/scala/objects/PlayerTest.scala @@ -1,45 +1,61 @@ // Copyright (c) 2017 PSForever package objects +import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects._ -import net.psforever.objects.definition.SimpleItemDefinition +import net.psforever.objects.definition.{ImplantDefinition, SimpleItemDefinition} import net.psforever.objects.equipment.EquipmentSize import net.psforever.packet.game.PlanetSideGUID -import net.psforever.types.{CharacterGender, ExoSuitType, PlanetSideEmpire} +import net.psforever.types.{CharacterGender, ExoSuitType, ImplantType, PlanetSideEmpire} import org.specs2.mutable._ +import scala.util.Success + class PlayerTest extends Specification { + def TestPlayer(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : Int) : Player = { + new Player(Avatar(name, faction, sex, head, voice)) + } + "construct" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.isAlive mustEqual false + obj.FacingYawUpper mustEqual 0 + obj.Jumping mustEqual false + obj.Crouching mustEqual false + obj.Cloaked mustEqual false + + obj.FacingYawUpper = 1.3f + obj.Jumping = true + obj.Crouching = true + obj.Cloaked = true + obj.FacingYawUpper mustEqual 1.3f + obj.Jumping mustEqual true + obj.Crouching mustEqual true + obj.Cloaked mustEqual true } "different players" in { - (PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual true - (PlayerTest.Player("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - PlayerTest.Player("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual false - (PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - PlayerTest.Player("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, 5)) mustEqual false - (PlayerTest.Player("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - PlayerTest.Player("Chord2", PlanetSideEmpire.TR, CharacterGender.Female, 0, 5)) mustEqual false - (PlayerTest.Player("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - PlayerTest.Player("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 1, 5)) mustEqual false - (PlayerTest.Player("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - PlayerTest.Player("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 6)) mustEqual false - } + (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual true - "become a backpack" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.isAlive mustEqual false - obj.isBackpack mustEqual false - obj.Release - obj.isAlive mustEqual false - obj.isBackpack mustEqual true + (TestPlayer("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual false + + (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, 5)) mustEqual false + + (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Female, 0, 5)) mustEqual false + + (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 1, 5)) mustEqual false + + (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 6)) mustEqual false } "(re)spawn" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.isAlive mustEqual false obj.Health mustEqual 0 obj.Stamina mustEqual 0 @@ -54,32 +70,24 @@ class PlayerTest extends Specification { obj.Armor mustEqual 50 } - "set new maximum values (health, stamina)" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.MaxHealth mustEqual 100 - obj.MaxStamina mustEqual 100 - obj.MaxHealth = 123 - obj.MaxStamina = 456 + "will not (re)spawn if not dead" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Spawn - obj.Health mustEqual 123 - obj.Stamina mustEqual 456 + obj.Health mustEqual 100 + obj.Armor mustEqual 50 + obj.isAlive mustEqual true + + obj.Health = 10 + obj.Armor = 10 + obj.Health mustEqual 10 + obj.Armor mustEqual 10 + obj.Spawn + obj.Health mustEqual 10 + obj.Armor mustEqual 10 } - "init (Standard Exo-Suit)" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.ExoSuit mustEqual ExoSuitType.Standard - obj.Slot(0).Size mustEqual EquipmentSize.Pistol - obj.Slot(1).Size mustEqual EquipmentSize.Blocked - obj.Slot(2).Size mustEqual EquipmentSize.Rifle - obj.Slot(3).Size mustEqual EquipmentSize.Blocked - obj.Slot(4).Size mustEqual EquipmentSize.Melee - obj.Inventory.Width mustEqual 9 - obj.Inventory.Height mustEqual 6 - obj.Inventory.Offset mustEqual 6 - } - - "die" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + "can die" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Spawn obj.Armor = 35 //50 -> 35 obj.isAlive mustEqual true @@ -93,9 +101,80 @@ class PlayerTest extends Specification { obj.Armor mustEqual 35 } + "can not become a backpack if alive" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Spawn + obj.isAlive mustEqual true + obj.isBackpack mustEqual false + obj.Release + obj.isAlive mustEqual true + obj.isBackpack mustEqual false + } + + "can become a backpack" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.isAlive mustEqual false + obj.isBackpack mustEqual false + obj.Release + obj.isAlive mustEqual false + obj.isBackpack mustEqual true + } + + "set new maximum values (health, stamina)" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.MaxHealth mustEqual 100 + obj.MaxStamina mustEqual 100 + obj.MaxHealth = 123 + obj.MaxStamina = 456 + obj.Spawn + obj.Health mustEqual 123 + obj.Stamina mustEqual 456 + } + + "set new values (health, armor, stamina) but only when alive" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Health = 23 + obj.Armor = 34 + obj.Stamina = 45 + obj.Health mustEqual 0 + obj.Armor mustEqual 0 + obj.Stamina mustEqual 0 + + obj.Spawn + obj.Health mustEqual obj.MaxHealth + obj.Armor mustEqual obj.MaxArmor + obj.Stamina mustEqual obj.MaxStamina + obj.Health = 23 + obj.Armor = 34 + obj.Stamina = 45 + obj.Health mustEqual 23 + obj.Armor mustEqual 34 + obj.Stamina mustEqual 45 + } + + "has visible slots" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.VisibleSlots mustEqual Set(0,1,2,3,4) + obj.ExoSuit = ExoSuitType.MAX + obj.VisibleSlots mustEqual Set(0) + } + + "init (Standard Exo-Suit)" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.ExoSuit mustEqual ExoSuitType.Standard + obj.Slot(0).Size mustEqual EquipmentSize.Pistol + obj.Slot(1).Size mustEqual EquipmentSize.Blocked + obj.Slot(2).Size mustEqual EquipmentSize.Rifle + obj.Slot(3).Size mustEqual EquipmentSize.Blocked + obj.Slot(4).Size mustEqual EquipmentSize.Melee + obj.Inventory.Width mustEqual 9 + obj.Inventory.Height mustEqual 6 + obj.Inventory.Offset mustEqual 6 + } + "draw equipped holsters only" in { val wep = SimpleItem(SimpleItemDefinition(149)) - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Slot(1).Size = EquipmentSize.Pistol obj.Slot(1).Equipment = wep obj.DrawnSlot mustEqual Player.HandsDownSlot @@ -108,7 +187,7 @@ class PlayerTest extends Specification { "remember the last drawn holster" in { val wep1 = SimpleItem(SimpleItemDefinition(149)) val wep2 = SimpleItem(SimpleItemDefinition(149)) - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Slot(0).Size = EquipmentSize.Pistol obj.Slot(0).Equipment = wep1 obj.Slot(1).Size = EquipmentSize.Pistol @@ -147,7 +226,7 @@ class PlayerTest extends Specification { "hold something in their free hand" in { val wep = SimpleItem(SimpleItemDefinition(149)) - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Slot(Player.FreeHandSlot).Equipment = wep obj.Slot(Player.FreeHandSlot).Equipment.get.Definition.ObjectId mustEqual 149 @@ -155,15 +234,15 @@ class PlayerTest extends Specification { "provide an invalid hand that can not hold anything" in { val wep = SimpleItem(SimpleItemDefinition(149)) - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Slot(-1).Equipment = wep obj.Slot(-1).Equipment mustEqual None } "search for the smallest available slot in which to store equipment" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Inventory.Resize(3,3) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Inventory.Resize(3,3) //fits one item obj.Fit(Tool(GlobalDefinitions.beamer)) mustEqual Some(0) @@ -171,52 +250,185 @@ class PlayerTest extends Specification { val ammo = AmmoBox(GlobalDefinitions.bullet_9mm) val ammo2 = AmmoBox(GlobalDefinitions.bullet_9mm) - //val ammo3 = AmmoBox(GlobalDefinitions.bullet_9mm) + val ammo3 = AmmoBox(GlobalDefinitions.bullet_9mm) obj.Fit(ammo) mustEqual Some(6) obj.Slot(6).Equipment = ammo obj.Fit(ammo2) mustEqual Some(Player.FreeHandSlot) obj.Slot(Player.FreeHandSlot).Equipment = ammo2 - obj.Fit(ammo2) mustEqual None + obj.Fit(ammo3) mustEqual None } - //TODO move to Avatar tests -// "install an implant" in { -// val testplant : ImplantDefinition = ImplantDefinition(1) -// val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) -// obj.Implants(0).Unlocked = true -// obj.InstallImplant(testplant) mustEqual Some(0) -// obj.Implants.find({p => p.Implant == ImplantType(1)}) match { //find the installed implant -// case Some(slot) => -// slot.Installed mustEqual Some(testplant) -// case _ => -// ko -// } -// ok -// } -// -// "can not install the same type of implant twice" in { -// val testplant1 : ImplantDefinition = ImplantDefinition(1) -// val testplant2 : ImplantDefinition = ImplantDefinition(1) -// val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) -// obj.Implants(0).Unlocked = true -// obj.Implants(1).Unlocked = true -// obj.InstallImplant(testplant1) mustEqual Some(0) -// obj.InstallImplant(testplant2) mustEqual Some(1) -// } -// -// "uninstall implants" in { -// val testplant : ImplantDefinition = ImplantDefinition(1) -// val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) -// obj.Implants(0).Unlocked = true -// obj.InstallImplant(testplant) mustEqual Some(0) -// obj.Implants(0).Installed mustEqual Some(testplant) -// -// obj.UninstallImplant(testplant.Type) -// obj.Implants(0).Installed mustEqual None -// } + "can use their free hand to hold things" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val ammo = AmmoBox(GlobalDefinitions.bullet_9mm) + obj.FreeHand.Equipment mustEqual None + + obj.FreeHand.Equipment = ammo + obj.FreeHand.Equipment mustEqual Some(ammo) + } + + "can access the player's locker-space" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Slot(5).Equipment.get.isInstanceOf[LockerContainer] mustEqual true + } + + "can find equipment" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Slot(0).Equipment = { + val item = Tool(beamer) + item.GUID = PlanetSideGUID(1) + item + } + obj.Slot(4).Equipment = { + val item = Tool(forceblade) + item.GUID = PlanetSideGUID(2) + item + } + obj.Slot(6).Equipment = { + val item = ConstructionItem(ace) + item.GUID = PlanetSideGUID(3) + item + } + obj.Locker.Slot(6).Equipment = { + val item = Kit(medkit) + item.GUID = PlanetSideGUID(4) + item + } + obj.FreeHand.Equipment = { + val item = SimpleItem(remote_electronics_kit) + item.GUID = PlanetSideGUID(5) + item + } + + obj.Find(PlanetSideGUID(1)) mustEqual Some(0) //holsters + obj.Find(PlanetSideGUID(2)) mustEqual Some(4) //holsters, melee + obj.Find(PlanetSideGUID(3)) mustEqual Some(6) //inventory + obj.Find(PlanetSideGUID(4)) mustEqual Some(Player.LockerSlot) //locker-space + obj.Find(PlanetSideGUID(5)) mustEqual Some(Player.FreeHandSlot) //free hand + obj.Find(PlanetSideGUID(6)) mustEqual None //not here + } + + "does equipment collision checking (are we already holding something there?)" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val item1 = Tool(beamer) + val item2 = Kit(medkit) + val item3 = AmmoBox(GlobalDefinitions.bullet_9mm) + obj.Slot(0).Equipment = item1 + obj.Slot(6).Equipment = item2 + obj.FreeHand.Equipment = item3 + + obj.Collisions(0, 1, 1) match { + case Success(List(item)) => + item.obj mustEqual item1 + item.start mustEqual 0 + case _ => + ko + } //holsters + + obj.Collisions(1, 1, 1) match { + case Success(List()) => ; + case _ => + ko + } //holsters, nothing + + obj.Collisions(6, 1, 1)match { + case Success(List(item)) => + item.obj mustEqual item2 + item.start mustEqual 6 + case _ => + ko + } //inventory + + obj.Collisions(Player.FreeHandSlot, 1, 1)match { + case Success(List(item)) => + item.obj mustEqual item3 + item.start mustEqual Player.FreeHandSlot + case _ => + ko + } //free hand + } + + "battle experience point values of the avatar" in { + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + + player.BEP mustEqual avatar.BEP + avatar.BEP = 1002 + player.BEP mustEqual avatar.BEP + } + + "command experience point values of the avatar" in { + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + + player.CEP mustEqual avatar.CEP + avatar.CEP = 1002 + player.CEP mustEqual avatar.CEP + } + + "can get a quick summary of implant slots (default)" in { + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + + player.Implants mustEqual Array.empty + } + + "can get a quick summary of implant slots (two unlocked, one installed)" in { + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + val temp = new ImplantDefinition(1) + avatar.Implants(0).Unlocked = true + avatar.InstallImplant(new ImplantDefinition(1)) + avatar.Implants(1).Unlocked = true + avatar.InstallImplant(new ImplantDefinition(2)) + avatar.UninstallImplant(temp.Type) + + val list = player.Implants + //slot 0 + val (implant1, init1, active1) = list(0) + implant1 mustEqual ImplantType.None + init1 mustEqual -1 + active1 mustEqual false + //slot 1 + val (implant2, init2, active2) = list(1) + implant2 mustEqual ImplantType(2) + init2 mustEqual 0 + active2 mustEqual false + } + + "can get a quick summary of implant slots (all unlocked, first two installed)" in { + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + avatar.Implants(0).Unlocked = true + avatar.InstallImplant(new ImplantDefinition(1)) + avatar.Implants(0).Initialized = true + avatar.Implants(0).Active = true + avatar.Implants(1).Unlocked = true + avatar.InstallImplant(new ImplantDefinition(2)) + avatar.Implants(1).Initialized = true + avatar.Implants(1).Active = false + avatar.Implants(2).Unlocked = true + + val list = player.Implants + //slot 0 + val (implant1, init1, active1) = list(0) + implant1 mustEqual ImplantType(1) + init1 mustEqual 0 + active1 mustEqual true + //slot 1 + val (implant2, init2, active2) = list(1) + implant2 mustEqual ImplantType(2) + init2 mustEqual 0 + active2 mustEqual false + //slot 2 + val (implant3, init3, active3) = list(2) + implant3 mustEqual ImplantType.None + init3 mustEqual -1 + active3 mustEqual false + } "seat in a vehicle" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.VehicleSeated mustEqual None obj.VehicleSeated = PlanetSideGUID(65) obj.VehicleSeated mustEqual Some(PlanetSideGUID(65)) @@ -225,7 +437,7 @@ class PlayerTest extends Specification { } "own in a vehicle" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.VehicleOwned mustEqual None obj.VehicleOwned = PlanetSideGUID(65) obj.VehicleOwned mustEqual Some(PlanetSideGUID(65)) @@ -234,15 +446,18 @@ class PlayerTest extends Specification { } "remember what zone he is in" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Continent mustEqual "home2" obj.Continent = "ugd01" obj.Continent mustEqual "ugd01" } -} -object PlayerTest { - def Player(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : Int) : Player = { - new Player(Avatar(name, faction, sex, head, voice)) + "toString" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.toString mustEqual "TR Chord 0/100 0/50" + + obj.GUID = PlanetSideGUID(455) + obj.Continent = "z3" + obj.toString mustEqual "TR Chord z3-455 0/100 0/50" } } diff --git a/common/src/test/scala/objects/ServerObjectBuilderTest.scala b/common/src/test/scala/objects/ServerObjectBuilderTest.scala index 903e2e50d..813dbe36c 100644 --- a/common/src/test/scala/objects/ServerObjectBuilderTest.scala +++ b/common/src/test/scala/objects/ServerObjectBuilderTest.scala @@ -5,7 +5,7 @@ import akka.actor.{Actor, ActorContext, Props} import net.psforever.objects.guid.NumberPoolHub import net.psforever.packet.game.PlanetSideGUID import net.psforever.objects.serverobject.ServerObjectBuilder -import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, WarpGate} +import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType, WarpGate} import net.psforever.objects.zones.Zone import net.psforever.types.Vector3 @@ -14,7 +14,7 @@ import scala.concurrent.duration.Duration class BuildingBuilderTest extends ActorTest { "Building object" should { "build" in { - val structure : (Int,Zone,ActorContext)=>Building = Building.Structure + val structure : (Int,Zone,ActorContext)=>Building = Building.Structure(StructureType.Building) val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuildingTestActor], structure, 10, Zone.Nowhere), "building") actor ! "!" @@ -169,6 +169,26 @@ class LockerObjectBuilderTest extends ActorTest { } } +class SpawnTubeObjectBuilderTest extends ActorTest { + import net.psforever.objects.serverobject.tube.SpawnTube + "LockerObjectBuilder" should { + "build" in { + val hub = ServerObjectBuilderTest.NumberPoolHub + val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, + SpawnTube.Constructor(Vector3(3980.4062f, 4267.3047f, 257.5625f), Vector3(0, 0, 90))), hub), "spawn-tube") + actor ! "!" + + val reply = receiveOne(Duration.create(1000, "ms")) + assert(reply.isInstanceOf[SpawnTube]) + assert(reply.asInstanceOf[SpawnTube].HasGUID) + assert(reply.asInstanceOf[SpawnTube].GUID == PlanetSideGUID(1)) + assert(reply.asInstanceOf[SpawnTube].Position == Vector3(3980.4062f, 4267.3047f, 257.5625f)) + assert(reply.asInstanceOf[SpawnTube].Orientation == Vector3(0, 0, 90)) + assert(reply == hub(1).get) + } + } +} + object ServerObjectBuilderTest { import net.psforever.objects.guid.source.LimitedNumberSource def NumberPoolHub : NumberPoolHub = { diff --git a/common/src/test/scala/objects/SpawnTubeTest.scala b/common/src/test/scala/objects/SpawnTubeTest.scala new file mode 100644 index 000000000..7ef8e86b7 --- /dev/null +++ b/common/src/test/scala/objects/SpawnTubeTest.scala @@ -0,0 +1,59 @@ +// Copyright (c) 2017 PSForever +package objects + +import akka.actor.{ActorRef, Props} +import net.psforever.objects.GlobalDefinitions +import net.psforever.objects.serverobject.tube.{SpawnTube, SpawnTubeControl, SpawnTubeDefinition} +import org.specs2.mutable.Specification + +class SpawnTubeTest extends Specification { + "SpawnTubeDefinition" should { + "define (ams_respawn_tube)" in { + val obj = new SpawnTubeDefinition(49) + obj.ObjectId mustEqual 49 + obj.Name mustEqual "ams_respawn_tube" + } + + "define (respawn_tube)" in { + val obj = new SpawnTubeDefinition(732) + obj.ObjectId mustEqual 732 + obj.Name mustEqual "respawn_tube" + } + + "define (respawn_tube_tower)" in { + val obj = new SpawnTubeDefinition(733) + obj.ObjectId mustEqual 733 + obj.Name mustEqual "respawn_tube_tower" + } + + "define (invalid)" in { + var id : Int = (math.random * Int.MaxValue).toInt + if(id == 49 || id == 733) { + id += 1 + } + else if(id == 732) { + id += 2 + } + + new SpawnTubeDefinition(id) must throwA[IllegalArgumentException] + } + } + + "SpawnTube" should { + "construct" in { + val obj = SpawnTube(GlobalDefinitions.ams_respawn_tube) + obj.Actor mustEqual ActorRef.noSender + obj.Definition mustEqual GlobalDefinitions.ams_respawn_tube + } + } +} + +class SpawnTubeControlTest extends ActorTest() { + "SpawnTubeControl" should { + "construct" in { + val obj = SpawnTube(GlobalDefinitions.ams_respawn_tube) + obj.Actor = system.actorOf(Props(classOf[SpawnTubeControl], obj), "spawn-tube") + assert(obj.Actor != ActorRef.noSender) + } + } +} diff --git a/common/src/test/scala/objects/VehicleSpawnPadTest.scala b/common/src/test/scala/objects/VehicleSpawnPadTest.scala index 1332fed32..d4393b5d2 100644 --- a/common/src/test/scala/objects/VehicleSpawnPadTest.scala +++ b/common/src/test/scala/objects/VehicleSpawnPadTest.scala @@ -3,7 +3,7 @@ package objects import akka.actor.{ActorRef, ActorSystem, Props} import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.vehicles.VehicleControl import net.psforever.objects.zones.Zone import net.psforever.objects.{Avatar, GlobalDefinitions, Player, Vehicle} @@ -109,7 +109,7 @@ object VehicleSpawnPadControl { def SetUpAgents(faction : PlanetSideEmpire.Value)(implicit system : ActorSystem) : (Player, VehicleSpawnPad) = { val pad = VehicleSpawnPad(GlobalDefinitions.spawn_pad) pad.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], pad), "test-pad") - pad.Owner = new Building(0, Zone.Nowhere) + pad.Owner = new Building(0, Zone.Nowhere, StructureType.Building) pad.Owner.Faction = faction (Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), pad) } diff --git a/common/src/test/scala/objects/ZoneTest.scala b/common/src/test/scala/objects/ZoneTest.scala index 024c8aef6..49fe866be 100644 --- a/common/src/test/scala/objects/ZoneTest.scala +++ b/common/src/test/scala/objects/ZoneTest.scala @@ -1,16 +1,24 @@ // Copyright (c) 2017 PSForever package objects -import akka.actor.{ActorContext, ActorRef} +import java.util.concurrent.atomic.AtomicInteger + +import akka.actor.{Actor, ActorContext, ActorRef, Props} import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.equipment.Equipment import net.psforever.objects.guid.NumberPoolHub import net.psforever.objects.guid.source.LimitedNumberSource -import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder} -import net.psforever.objects.zones.{Zone, ZoneMap} -import net.psforever.objects.{GlobalDefinitions, Vehicle} +import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType} +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.packet.game.PlanetSideGUID +import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3} import org.specs2.mutable.Specification +import scala.concurrent.duration.Duration + class ZoneTest extends Specification { def test(a: Int, b : Zone, c : ActorContext) : Building = { Building.NoBuilding } @@ -82,6 +90,8 @@ class ZoneTest extends Specification { //zone also has a unique identifier system but it can't be accessed without its the Actor GUID being initialized zone.EquipmentOnGround mustEqual List.empty[Equipment] zone.Vehicles mustEqual List.empty[Vehicle] + zone.Players mustEqual List.empty[Player] + zone.Corpses mustEqual List.empty[Player] } "can have its unique identifier system changed if no objects were added to it" in { @@ -124,3 +134,368 @@ class ZoneTest extends Specification { } } } + +class ZoneActorTest extends ActorTest { + "Zone" should { + "have an Actor" in { + val zone = new Zone("test", new ZoneMap("map6"), 1) + zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-actor") + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Actor != ActorRef.noSender) + } + + "set up spawn groups based on buildings" in { + val map6 = new ZoneMap("map6") { + LocalBuilding(1, FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1,1,1)))) + LocalObject(1, SpawnTube.Constructor(Vector3.Zero, Vector3.Zero)) + LocalObject(2, Terminal.Constructor(GlobalDefinitions.dropship_vehicle_terminal)) + LocalObject(3, SpawnTube.Constructor(Vector3.Zero, Vector3.Zero)) + ObjectToBuilding(1, 1) + ObjectToBuilding(2, 1) + ObjectToBuilding(3, 1) + + LocalBuilding(2, FoundationBuilder(Building.Structure(StructureType.Building))) + LocalObject(7, SpawnTube.Constructor(Vector3.Zero, Vector3.Zero)) + ObjectToBuilding(7, 2) + + LocalBuilding(3, FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1,1,1)))) + LocalObject(4, Terminal.Constructor(GlobalDefinitions.dropship_vehicle_terminal)) + LocalObject(5, SpawnTube.Constructor(Vector3.Zero, Vector3.Zero)) + LocalObject(6, Terminal.Constructor(GlobalDefinitions.dropship_vehicle_terminal)) + ObjectToBuilding(4, 3) + ObjectToBuilding(5, 3) + ObjectToBuilding(6, 3) + } + val zone = new Zone("test", map6, 1) + zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-init") + zone.Actor ! Zone.Init() + expectNoMsg(Duration.create(300, "ms")) + + val groups = zone.SpawnGroups() + assert(groups.size == 2) + zone.SpawnGroups().foreach({ case(building, tubes) => + if(building.Id == 1) { + val building1 = zone.SpawnGroups(building) + assert(tubes.length == 2) + assert(tubes.head == building1.head) + assert(tubes.head.GUID == PlanetSideGUID(1)) + assert(tubes(1) == building1(1)) + assert(tubes(1).GUID == PlanetSideGUID(3)) + } + else if(building.Id == 3) { + val building2 = zone.SpawnGroups(building) + assert(tubes.length == 1) + assert(tubes.head == building2.head) + assert(tubes.head.GUID == PlanetSideGUID(5)) + } + else { + assert(false) + } + }) + } + + "select spawn points based on the position of the player in reference to buildings" in { + val map6 = new ZoneMap("map6") { + LocalBuilding(1, FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1,1,1)))) + LocalObject(1, SpawnTube.Constructor(Vector3.Zero, Vector3.Zero)) + ObjectToBuilding(1, 1) + + LocalBuilding(3, FoundationBuilder(Building.Structure(StructureType.Building, Vector3(4,4,4)))) + LocalObject(5, SpawnTube.Constructor(Vector3.Zero, Vector3.Zero)) + ObjectToBuilding(5, 3) + } + val zone = new Zone("test", map6, 1) + zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-spawn") + zone.Actor ! Zone.Init() + expectNoMsg(Duration.create(300, "ms")) + val player = Player(Avatar("Chord", PlanetSideEmpire.NEUTRAL, CharacterGender.Male, 0, 5)) + + val bldg1 = zone.Building(1).get + val bldg3 = zone.Building(3).get + player.Position = Vector3(1,1,1) //closer to bldg1 + zone.Actor ! Zone.Lattice.RequestSpawnPoint(1, player, 7) + val reply1 = receiveOne(Duration.create(200, "ms")) + assert(reply1.isInstanceOf[Zone.Lattice.SpawnPoint]) + assert(reply1.asInstanceOf[Zone.Lattice.SpawnPoint].zone_id == "test") + assert(reply1.asInstanceOf[Zone.Lattice.SpawnPoint].building == bldg1) + assert(reply1.asInstanceOf[Zone.Lattice.SpawnPoint].spawn_tube.Owner == bldg1) + + player.Position = Vector3(3,3,3) //closer to bldg3 + zone.Actor ! Zone.Lattice.RequestSpawnPoint(1, player, 7) + val reply3 = receiveOne(Duration.create(200, "ms")) + assert(reply3.isInstanceOf[Zone.Lattice.SpawnPoint]) + assert(reply3.asInstanceOf[Zone.Lattice.SpawnPoint].zone_id == "test") + assert(reply3.asInstanceOf[Zone.Lattice.SpawnPoint].building == bldg3) + assert(reply3.asInstanceOf[Zone.Lattice.SpawnPoint].spawn_tube.Owner == bldg3) + } + + "will report if no spawn points have been found in a zone" in { + val map6 = new ZoneMap("map6") { + LocalBuilding(1, FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1,1,1)))) + + LocalBuilding(3, FoundationBuilder(Building.Structure(StructureType.Tower, Vector3(4,4,4)))) + LocalObject(5, SpawnTube.Constructor(Vector3.Zero, Vector3.Zero)) + ObjectToBuilding(5, 3) + } + val zone = new Zone("test", map6, 1) + zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-no-spawn") + zone.Actor ! Zone.Init() + expectNoMsg(Duration.create(300, "ms")) + val player = Player(Avatar("Chord", PlanetSideEmpire.NEUTRAL, CharacterGender.Male, 0, 5)) + + zone.Actor ! Zone.Lattice.RequestSpawnPoint(1, player, 7) + val reply = receiveOne(Duration.create(200, "ms")) + assert(reply.isInstanceOf[Zone.Lattice.NoValidSpawnPoint]) + assert(reply.asInstanceOf[Zone.Lattice.NoValidSpawnPoint].zone_number == 1) + assert(reply.asInstanceOf[Zone.Lattice.NoValidSpawnPoint].spawn_group.contains(7)) + } + } +} + +class ZonePopulationTest extends ActorTest { + val testNum = new AtomicInteger(1) + def TestName : String = s"test${testNum.getAndIncrement()}" + + "ZonePopulationActor" should { + "add new user to zones" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + + assert(zone.Players.isEmpty) + assert(zone.LivePlayers.isEmpty) + zone.Population ! Zone.Population.Join(avatar) + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.isEmpty) + } + + "remove user from zones" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + zone.Population ! Zone.Population.Join(avatar) + expectNoMsg(Duration.create(100, "ms")) + + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + zone.Population ! Zone.Population.Leave(avatar) + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Players.isEmpty) + } + + "associate user with a character" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + zone.Population ! Zone.Population.Join(avatar) + expectNoMsg(Duration.create(100, "ms")) + + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.isEmpty) + zone.Population ! Zone.Population.Spawn(avatar, player) + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.size == 1) + assert(zone.LivePlayers.head == player) + } + + "disassociate character from a user" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + zone.Population ! Zone.Population.Join(avatar) + expectNoMsg(Duration.create(100, "ms")) + zone.Population ! Zone.Population.Spawn(avatar, player) + expectNoMsg(Duration.create(100, "ms")) + + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.size == 1) + assert(zone.LivePlayers.head == player) + zone.Population ! Zone.Population.Release(avatar) + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.isEmpty) + } + + "user tries to Leave, but still has an associated character" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(500, "ms")) //consume + zone.Population ! Zone.Population.Join(avatar) + expectNoMsg(Duration.create(100, "ms")) + zone.Population ! Zone.Population.Spawn(avatar, player) + expectNoMsg(Duration.create(100, "ms")) + + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.size == 1) + assert(zone.LivePlayers.head == player) + zone.Population ! Zone.Population.Leave(avatar) + val reply = receiveOne(Duration.create(100, "ms")) + assert(zone.Players.isEmpty) + assert(zone.LivePlayers.isEmpty) + assert(reply.isInstanceOf[Zone.Population.PlayerHasLeft]) + assert(reply.asInstanceOf[Zone.Population.PlayerHasLeft].zone == zone) + assert(reply.asInstanceOf[Zone.Population.PlayerHasLeft].player.contains(player)) + } + + "user tries to Spawn a character, but an associated character already exists" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player1 = Player(avatar) + val player2 = Player(avatar) + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + zone.Population ! Zone.Population.Join(avatar) + expectNoMsg(Duration.create(100, "ms")) + zone.Population ! Zone.Population.Spawn(avatar, player1) + expectNoMsg(Duration.create(100, "ms")) + + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.size == 1) + assert(zone.LivePlayers.head == player1) + zone.Population ! Zone.Population.Spawn(avatar, player2) + val reply = receiveOne(Duration.create(100, "ms")) + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.size == 1) + assert(zone.LivePlayers.head == player1) + assert(reply.isInstanceOf[Zone.Population.PlayerAlreadySpawned]) + assert(reply.asInstanceOf[Zone.Population.PlayerAlreadySpawned].player == player1) + } + + "user tries to Spawn a character, but did not Join first" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + + assert(zone.Players.isEmpty) + assert(zone.LivePlayers.isEmpty) + zone.Population ! Zone.Population.Spawn(avatar, player) + val reply = receiveOne(Duration.create(100, "ms")) + assert(zone.Players.isEmpty) + assert(zone.LivePlayers.isEmpty) + assert(reply.isInstanceOf[Zone.Population.PlayerCanNotSpawn]) + assert(reply.asInstanceOf[Zone.Population.PlayerCanNotSpawn].zone == zone) + assert(reply.asInstanceOf[Zone.Population.PlayerCanNotSpawn].player == player) + } + + "user tries to Release a character, but did not Spawn a character first" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + zone.Population ! Zone.Population.Join(avatar) + expectNoMsg(Duration.create(100, "ms")) + + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.isEmpty) + zone.Population ! Zone.Population.Release(avatar) + val reply = receiveOne(Duration.create(100, "ms")) + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.isEmpty) + assert(reply.isInstanceOf[Zone.Population.PlayerHasLeft]) + assert(reply.asInstanceOf[Zone.Population.PlayerHasLeft].zone == zone) + assert(reply.asInstanceOf[Zone.Population.PlayerHasLeft].player.isEmpty) + } + + "user adds character to list of retired characters" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) + player.Release + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + + assert(zone.Corpses.isEmpty) + zone.Population ! Zone.Corpse.Add(player) + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Corpses.size == 1) + assert(zone.Corpses.head == player) + } + + "user removes character from the list of retired characters" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) + player.Release + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + zone.Population ! Zone.Corpse.Add(player) + expectNoMsg(Duration.create(100, "ms")) + + assert(zone.Corpses.size == 1) + assert(zone.Corpses.head == player) + zone.Population ! Zone.Corpse.Remove(player) + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Corpses.isEmpty) + } + + "user removes THE CORRECT character from the list of retired characters" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val player1 = Player(Avatar("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) + player1.Release + val player2 = Player(Avatar("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) + player2.Release + val player3 = Player(Avatar("Chord3", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) + player3.Release + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + zone.Population ! Zone.Corpse.Add(player1) + zone.Population ! Zone.Corpse.Add(player2) + zone.Population ! Zone.Corpse.Add(player3) + expectNoMsg(Duration.create(100, "ms")) + + assert(zone.Corpses.size == 3) + assert(zone.Corpses.head == player1) + assert(zone.Corpses(1) == player2) + assert(zone.Corpses(2) == player3) + zone.Population ! Zone.Corpse.Remove(player2) + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Corpses.size == 2) + assert(zone.Corpses.head == player1) + assert(zone.Corpses(1) == player3) + } + + "user tries to add character to list of retired characters, but is not in correct state" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) + //player.Release !!important + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "testC") ! "!" + receiveOne(Duration.create(500, "ms")) //consume + + assert(zone.Corpses.isEmpty) + zone.Population ! Zone.Corpse.Add(player) + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Corpses.isEmpty) + } + } +} + +object ZoneTest { + class ZoneInitActor(zone : Zone) extends Actor { + def receive : Receive = { + case "!" => + zone.Init(context) + sender ! "!" + case _ => ; + } + } +} diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskRegister5Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskRegister5Test.scala index 2c08c06df..0a19c67df 100644 --- a/common/src/test/scala/objects/guidtask/GUIDTaskRegister5Test.scala +++ b/common/src/test/scala/objects/guidtask/GUIDTaskRegister5Test.scala @@ -1,7 +1,6 @@ // Copyright (c) 2017 PSForever package objects.guidtask - import net.psforever.objects._ import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.types.{CharacterGender, PlanetSideEmpire} diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskRegister6Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskRegister6Test.scala new file mode 100644 index 000000000..091ea62fc --- /dev/null +++ b/common/src/test/scala/objects/guidtask/GUIDTaskRegister6Test.scala @@ -0,0 +1,38 @@ +// Copyright (c) 2017 PSForever +package objects.guidtask + +import net.psforever.objects._ +import net.psforever.objects.guid.{GUIDTask, TaskResolver} +import net.psforever.types.{CharacterGender, PlanetSideEmpire} +import objects.ActorTest + +class GUIDTaskRegister6Test extends ActorTest() { + "RegisterPlayer" in { + val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup + val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + val obj_wep = Tool(GlobalDefinitions.beamer) + obj.Slot(0).Equipment = obj_wep + val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj_wep.AmmoSlots.head.Box = obj_wep_ammo + val obj_inv_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj.Slot(6).Equipment = obj_inv_ammo + val obj_locker = obj.Slot(5).Equipment.get + val obj_locker_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj_locker.asInstanceOf[LockerContainer].Inventory += 0 -> obj_locker_ammo + + assert(!obj.HasGUID) + assert(!obj_wep.HasGUID) + assert(!obj_wep_ammo.HasGUID) + assert(!obj_inv_ammo.HasGUID) + assert(!obj_locker.HasGUID) + assert(!obj_locker_ammo.HasGUID) + taskResolver ! TaskResolver.GiveTask(new GUIDTaskTest.RegisterTestTask(probe.ref), List(GUIDTask.RegisterPlayer(obj)(uns))) + probe.expectMsg(scala.util.Success) + assert(obj.HasGUID) + assert(obj_wep.HasGUID) + assert(obj_wep_ammo.HasGUID) + assert(obj_inv_ammo.HasGUID) + assert(!obj_locker.HasGUID) + assert(!obj_locker_ammo.HasGUID) + } +} diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskUnregister6Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister6Test.scala new file mode 100644 index 000000000..718f460f0 --- /dev/null +++ b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister6Test.scala @@ -0,0 +1,44 @@ +// Copyright (c) 2017 PSForever +package objects.guidtask + +import net.psforever.objects._ +import net.psforever.objects.guid.{GUIDTask, TaskResolver} +import net.psforever.types.{CharacterGender, PlanetSideEmpire} +import objects.ActorTest + +class GUIDTaskUnregister6Test extends ActorTest() { + "UnregisterPlayer" in { + val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup + val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + val obj_wep = Tool(GlobalDefinitions.beamer) + obj.Slot(0).Equipment = obj_wep + val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj_wep.AmmoSlots.head.Box = obj_wep_ammo + val obj_inv_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj.Slot(6).Equipment = obj_inv_ammo + val obj_locker = obj.Slot(5).Equipment.get + val obj_locker_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj_locker.asInstanceOf[LockerContainer].Inventory += 0 -> obj_locker_ammo + guid.register(obj, "dynamic") + guid.register(obj_wep, "dynamic") + guid.register(obj_wep_ammo, "dynamic") + guid.register(obj_inv_ammo, "dynamic") + guid.register(obj_locker, "dynamic") + guid.register(obj_locker_ammo, "dynamic") + + assert(obj.HasGUID) + assert(obj_wep.HasGUID) + assert(obj_wep_ammo.HasGUID) + assert(obj_inv_ammo.HasGUID) + assert(obj_locker.HasGUID) + assert(obj_locker_ammo.HasGUID) + taskResolver ! TaskResolver.GiveTask(new GUIDTaskTest.RegisterTestTask(probe.ref), List(GUIDTask.UnregisterPlayer(obj)(uns))) + probe.expectMsg(scala.util.Success) + assert(!obj.HasGUID) + assert(!obj_wep.HasGUID) + assert(!obj_wep_ammo.HasGUID) + assert(!obj_inv_ammo.HasGUID) + assert(obj_locker.HasGUID) + assert(obj_locker_ammo.HasGUID) + } +} diff --git a/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala b/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala index be8d3e7fa..954a5837e 100644 --- a/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala +++ b/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala @@ -275,6 +275,40 @@ class UniqueNumberSystemTest8 extends ActorTest() { } } +class UniqueNumberSystemTest9 extends ActorTest() { + class EntityTestClass extends IdentifiableEntity + + "UniqueNumberSystem" should { + "Failures (manually walking the failure cases)" in { + val src : LimitedNumberSource = LimitedNumberSource(6000) + val guid : NumberPoolHub = new NumberPoolHub(src) + guid.AddPool("pool1", (1001 to 2000).toList).Selector = new RandomSelector + guid.AddPool("pool2", (3001 to 4000).toList).Selector = new RandomSelector + guid.AddPool("pool3", (5001 to 6000).toList).Selector = new RandomSelector + val uns = system.actorOf(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), "uns") + val excp = new Exception("EXCEPTION MESSAGE") + expectNoMsg(Duration.create(200, "ms")) + + //GiveNumber + uns ! NumberPoolActor.GiveNumber(1001, Some("test")) //no task associated with id="test" + uns ! NumberPoolActor.GiveNumber(1000, Some("test")) //no task associated with id="test" and number is not pooled + uns ! NumberPoolActor.GiveNumber(1000, Some(1)) //the task could theoretically exist, but does not + //NoNumber + uns ! NumberPoolActor.NoNumber(excp, Some(1)) + uns ! NumberPoolActor.NoNumber(excp, None) + uns ! NumberPoolActor.NoNumber(excp, Some("test")) + //ReturnNumberResult A + uns ! NumberPoolActor.ReturnNumberResult(1001, None, Some("test")) + uns ! NumberPoolActor.ReturnNumberResult(1000, None, Some("test")) + uns ! NumberPoolActor.ReturnNumberResult(1001, None, Some(1)) + uns ! NumberPoolActor.ReturnNumberResult(1000, None, Some(1)) + //ReturnNumberResult B + uns ! NumberPoolActor.ReturnNumberResult(1001, Some(excp), Some("test")) + uns ! NumberPoolActor.ReturnNumberResult(1001, Some(excp), Some(1)) + } + } +} + object UniqueNumberSystemTest { /** * @see `UniqueNumberSystem.AllocateNumberPoolActors(NumberPoolHub)(implicit ActorContext)` diff --git a/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala b/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala index 2201f6195..b635ad5de 100644 --- a/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.ActorRef -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone @@ -14,7 +14,7 @@ class AirVehicleTerminalTest extends Specification { "Air_Vehicle_Terminal" should { val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.air_vehicle_terminal) - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { diff --git a/common/src/test/scala/objects/terminal/CertTerminalTest.scala b/common/src/test/scala/objects/terminal/CertTerminalTest.scala index 8a9785d81..c9791c9ec 100644 --- a/common/src/test/scala/objects/terminal/CertTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/CertTerminalTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.ActorRef -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone import net.psforever.objects.{Avatar, GlobalDefinitions, Player} @@ -14,7 +14,7 @@ class CertTerminalTest extends Specification { "Cert_Terminal" should { val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.cert_terminal) - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { diff --git a/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala b/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala index 2b16b69cb..ef6c621b8 100644 --- a/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.ActorRef -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone @@ -14,7 +14,7 @@ class DropshipVehicleTerminalTest extends Specification { "Dropship_Vehicle_Terminal" should { val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.dropship_vehicle_terminal) - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { diff --git a/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala b/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala index 554d492f7..ee88b6027 100644 --- a/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.ActorRef -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone @@ -14,7 +14,7 @@ class GroundVehicleTerminalTest extends Specification { "Ground_Vehicle_Terminal" should { val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.ground_vehicle_terminal) - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { diff --git a/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala b/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala index 5a1d2c9dd..272815f78 100644 --- a/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala +++ b/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.ActorRef -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone @@ -14,7 +14,7 @@ class ImplantTerminalInterfaceTest extends Specification { "Implant_Terminal_Interface" should { val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.implant_terminal_interface) - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { diff --git a/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala b/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala index 7c333b861..91993769f 100644 --- a/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala +++ b/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala @@ -5,6 +5,7 @@ import akka.actor.{ActorRef, ActorSystem, Props} import net.psforever.objects.definition.SeatDefinition import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.implantmech.{ImplantTerminalMech, ImplantTerminalMechControl} +import net.psforever.objects.serverobject.structures.StructureType import net.psforever.objects.vehicles.Seat import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3} @@ -160,7 +161,7 @@ object ImplantTerminalMechTest { val terminal = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech) terminal.Actor = system.actorOf(Props(classOf[ImplantTerminalMechControl], terminal), "mech") - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = faction terminal.GUID = PlanetSideGUID(1) (Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), terminal) diff --git a/common/src/test/scala/objects/terminal/MatrixTerminalTest.scala b/common/src/test/scala/objects/terminal/MatrixTerminalTest.scala index 5fe8c3167..fbbb4d6e6 100644 --- a/common/src/test/scala/objects/terminal/MatrixTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/MatrixTerminalTest.scala @@ -22,12 +22,18 @@ class MatrixTerminalTest extends Specification { b.Name mustEqual "matrix_terminalb" } - "define (b)" in { + "define (c)" in { val b = new MatrixTerminalDefinition(519) b.ObjectId mustEqual 519 b.Name mustEqual "matrix_terminalc" } + "define (d)" in { + val b = new MatrixTerminalDefinition(812) + b.ObjectId mustEqual 812 + b.Name mustEqual "spawn_terminal" + } + "define (invalid)" in { var id : Int = (math.random * Int.MaxValue).toInt if(id == 517) { @@ -36,7 +42,7 @@ class MatrixTerminalTest extends Specification { else if(id == 518) { id += 2 } - else if(id == 519) { + else if(id == 519 | id == 812) { id += 1 } diff --git a/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala b/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala index d94b867bf..c4d44e14e 100644 --- a/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala +++ b/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.ActorRef -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.serverobject.terminals.{OrderTerminalABDefinition, Terminal} import net.psforever.objects.zones.Zone import net.psforever.objects.{Avatar, GlobalDefinitions, Player} @@ -39,7 +39,7 @@ class OrderTerminalABTest extends Specification { "Order_Terminal" should { val terminal = Terminal(GlobalDefinitions.order_terminala) - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { diff --git a/common/src/test/scala/objects/terminal/OrderTerminalTest.scala b/common/src/test/scala/objects/terminal/OrderTerminalTest.scala index e6e8c2c48..024c8794e 100644 --- a/common/src/test/scala/objects/terminal/OrderTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/OrderTerminalTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.ActorRef -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone import net.psforever.objects.{AmmoBox, Avatar, GlobalDefinitions, Player, Tool} @@ -14,7 +14,7 @@ class OrderTerminalTest extends Specification { "Order_Terminal" should { val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.order_terminal) - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { diff --git a/common/src/test/scala/objects/terminal/TerminalControlTest.scala b/common/src/test/scala/objects/terminal/TerminalControlTest.scala index e77bbd738..428d8d87e 100644 --- a/common/src/test/scala/objects/terminal/TerminalControlTest.scala +++ b/common/src/test/scala/objects/terminal/TerminalControlTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.{ActorSystem, Props} -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.serverobject.terminals.{Terminal, TerminalControl, TerminalDefinition} import net.psforever.objects.zones.Zone import net.psforever.objects.{Avatar, GlobalDefinitions, Player} @@ -122,7 +122,7 @@ object TerminalControlTest { def SetUpAgents(tdef : TerminalDefinition, faction : PlanetSideEmpire.Value)(implicit system : ActorSystem) : (Player, Terminal) = { val terminal = Terminal(tdef) terminal.Actor = system.actorOf(Props(classOf[TerminalControl], terminal), "test-term") - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = faction (Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), terminal) } diff --git a/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala b/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala index 85fb96ef7..0233dc9d6 100644 --- a/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala +++ b/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.ActorRef -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone @@ -14,7 +14,7 @@ class VehicleTerminalCombinedTest extends Specification { "Ground_Vehicle_Terminal" should { val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.vehicle_terminal_combined) - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala index 72a17cc7b..6625e55f9 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/pslogin/src/main/scala/Maps.scala @@ -6,7 +6,7 @@ import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.mblocker.Locker import net.psforever.objects.serverobject.pad.VehicleSpawnPad -import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, WarpGate} +import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType, WarpGate} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.types.Vector3 @@ -23,7 +23,7 @@ object Maps { val map5 = new ZoneMap("map05") val map6 = new ZoneMap("map06") { - LocalBuilding(2, FoundationBuilder(Building.Structure)) //Anguta + LocalBuilding(2, FoundationBuilder(Building.Structure(StructureType.Facility, Vector3(3974.2344f, 4287.914f, 0)))) //Anguta LocalObject(222, Door.Constructor) //air term building, bay door LocalObject(370, Door.Constructor) //courtyard LocalObject(371, Door.Constructor) //courtyard @@ -193,17 +193,17 @@ object Maps { TerminalToSpawnPad(224, 501) TerminalToSpawnPad(2419, 500) - LocalBuilding(38, FoundationBuilder(Building.Structure)) //Anguta, West Bunker + LocalBuilding(38, FoundationBuilder(Building.Structure(StructureType.Bunker))) //Anguta, West Bunker LocalObject(362, Door.Constructor) ObjectToBuilding(362, 38) - LocalBuilding(42, FoundationBuilder(Building.Structure)) //Anguta, East Bunker(s) + LocalBuilding(42, FoundationBuilder(Building.Structure(StructureType.Bunker))) //Anguta, East Bunker(s) LocalObject(407, Door.Constructor) LocalObject(408, Door.Constructor) ObjectToBuilding(407, 42) ObjectToBuilding(408, 42) - LocalBuilding(48, FoundationBuilder(Building.Structure)) //North Anguta Watchtower + LocalBuilding(48, FoundationBuilder(Building.Structure(StructureType.Tower, Vector3(3864.2266f, 4518.0234f, 0)))) //North Anguta Watchtower LocalObject(364, Door.Constructor(Vector3(3871.9688f, 4509.992f, 269.65625f), Vector3(0f, 0f, 180f))) //s1 LocalObject(365, Door.Constructor(Vector3(3871.9688f, 4509.992f, 279.57812f), Vector3(0f, 0f, 180f))) //s2 LocalObject(366, Door.Constructor(Vector3(3871.9688f, 4509.992f, 299.57812f), Vector3(0f, 0f, 180f))) //s3 @@ -227,8 +227,8 @@ object Maps { LocalObject(1561, Terminal.Constructor(order_terminal)) LocalObject(1562, Terminal.Constructor(order_terminal)) LocalObject(1563, Terminal.Constructor(order_terminal)) - LocalObject(2138, SpawnTube.Constructor(Vector3(3870.9688f, 4505.7266f, 259.875f), Vector3(0, 0, 90))) - LocalObject(2139, SpawnTube.Constructor(Vector3(3870.9688f, 4522.1562f, 259.875f), Vector3(0, 0, 90))) + LocalObject(2138, SpawnTube.Constructor(respawn_tube_tower, Vector3(3870.9688f, 4505.7266f, 259.875f), Vector3(0, 0, 90))) + LocalObject(2139, SpawnTube.Constructor(respawn_tube_tower, Vector3(3870.9688f, 4522.1562f, 259.875f), Vector3(0, 0, 90))) LocalObject(2315, Door.Constructor) //spawn tube door LocalObject(2316, Door.Constructor) //spawn tube door ObjectToBuilding(364, 48) @@ -287,15 +287,13 @@ object Maps { LocalObject(1081, Terminal.Constructor(implant_terminal_interface)) //tube 520 TerminalToInterface(520, 1081) - LocalBuilding(2, FoundationBuilder(Building.Structure)) //HART building C + LocalBuilding(2, FoundationBuilder(Building.Structure(StructureType.Building))) //HART building C LocalObject(186, Terminal.Constructor(cert_terminal)) LocalObject(187, Terminal.Constructor(cert_terminal)) LocalObject(188, Terminal.Constructor(cert_terminal)) LocalObject(362, Door.Constructor) LocalObject(370, Door.Constructor) LocalObject(371, Door.Constructor) - LocalObject(372, Door.Constructor) - LocalObject(373, Door.Constructor) LocalObject(374, Door.Constructor) LocalObject(375, Door.Constructor) LocalObject(394, Door.Constructor) @@ -303,6 +301,8 @@ object Maps { LocalObject(396, Door.Constructor) LocalObject(397, Door.Constructor) LocalObject(398, Door.Constructor) + LocalObject(462, Door.Constructor) + LocalObject(463, Door.Constructor) LocalObject(522, ImplantTerminalMech.Constructor) LocalObject(523, ImplantTerminalMech.Constructor) LocalObject(524, ImplantTerminalMech.Constructor) @@ -337,8 +337,6 @@ object Maps { ObjectToBuilding(362, 2) ObjectToBuilding(370, 2) ObjectToBuilding(371, 2) - ObjectToBuilding(372, 2) - ObjectToBuilding(373, 2) ObjectToBuilding(374, 2) ObjectToBuilding(375, 2) ObjectToBuilding(394, 2) @@ -346,6 +344,8 @@ object Maps { ObjectToBuilding(396, 2) ObjectToBuilding(397, 2) ObjectToBuilding(398, 2) + ObjectToBuilding(462, 2) + ObjectToBuilding(463, 2) ObjectToBuilding(522, 2) ObjectToBuilding(523, 2) ObjectToBuilding(524, 2) @@ -383,7 +383,7 @@ object Maps { TerminalToInterface(528, 1088) TerminalToInterface(529, 1089) - LocalBuilding(29, FoundationBuilder(Building.Structure)) //South Villa Gun Tower + LocalBuilding(29, FoundationBuilder(Building.Structure(StructureType.Tower))) //South Villa Gun 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))) @@ -405,7 +405,61 @@ object Maps { DoorToLock(332, 556) DoorToLock(333, 557) - LocalBuilding(51, FoundationBuilder(Building.Structure)) + LocalBuilding(42, FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1, 0, 0)))) //spawn building south of HART C + LocalObject(258, Door.Constructor) //spawn tube door + LocalObject(259, Door.Constructor) //spawn tube door + LocalObject(260, Door.Constructor) //spawn tube door + LocalObject(261, Door.Constructor) //spawn tube door + LocalObject(262, Door.Constructor) //spawn tube door + LocalObject(263, Door.Constructor) //spawn tube door + LocalObject(372, Door.Constructor) //entrance + LocalObject(373, Door.Constructor) //entrance + LocalObject(430, Door.Constructor) //vr door + LocalObject(431, Door.Constructor) //vr door + LocalObject(432, Door.Constructor) //vr door + 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(852, Terminal.Constructor(order_terminal)) //s. wall + LocalObject(853, Terminal.Constructor(order_terminal)) //n. wall + LocalObject(854, Terminal.Constructor(order_terminal)) //s. wall + LocalObject(855, Terminal.Constructor(order_terminal)) //n. wall + LocalObject(859, Terminal.Constructor(order_terminal)) //s. wall + LocalObject(860, Terminal.Constructor(order_terminal)) //n. wall + ObjectToBuilding(258, 42) + ObjectToBuilding(259, 42) + ObjectToBuilding(260, 42) + ObjectToBuilding(261, 42) + ObjectToBuilding(262, 42) + ObjectToBuilding(263, 42) + ObjectToBuilding(372, 42) + ObjectToBuilding(373, 42) + ObjectToBuilding(430, 42) + ObjectToBuilding(431, 42) + ObjectToBuilding(432, 42) + ObjectToBuilding(433, 42) + ObjectToBuilding(434, 42) + ObjectToBuilding(435, 42) + ObjectToBuilding(744, 42) + ObjectToBuilding(745, 42) + ObjectToBuilding(746, 42) + ObjectToBuilding(747, 42) + ObjectToBuilding(748, 42) + ObjectToBuilding(749, 42) + ObjectToBuilding(852, 42) + ObjectToBuilding(853, 42) + ObjectToBuilding(854, 42) + ObjectToBuilding(855, 42) + ObjectToBuilding(859, 42) + ObjectToBuilding(860, 42) + + LocalBuilding(51, FoundationBuilder(Building.Structure(StructureType.Platform))) //air terminal west of HART C LocalObject(304, Terminal.Constructor(dropship_vehicle_terminal)) LocalObject(292, VehicleSpawnPad.Constructor(Vector3(3508.9844f, 2895.961f, 92.296875f), Vector3(0f, 0f, 270.0f)) @@ -414,7 +468,7 @@ object Maps { ObjectToBuilding(292, 51) TerminalToSpawnPad(304, 292) - LocalBuilding(77, FoundationBuilder(Building.Structure)) + LocalBuilding(77, FoundationBuilder(Building.Structure(StructureType.Platform))) //ground terminal west of HART C LocalObject(1063, Terminal.Constructor(ground_vehicle_terminal)) LocalObject(706, VehicleSpawnPad.Constructor(Vector3(3506.0f, 2820.0f, 92.0f), Vector3(0f, 0f, 270.0f)) @@ -422,18 +476,6 @@ object Maps { ObjectToBuilding(1063, 77) ObjectToBuilding(706, 77) TerminalToSpawnPad(1063, 706) - - //TODO check building id: these belong to a spawn building in HART C campus - LocalObject(462, Door.Constructor) - LocalObject(463, Door.Constructor) - LocalObject(853, Terminal.Constructor(order_terminal)) - LocalObject(855, Terminal.Constructor(order_terminal)) - LocalObject(860, Terminal.Constructor(order_terminal)) - ObjectToBuilding(462, 2) - ObjectToBuilding(463, 2) - ObjectToBuilding(853, 2) - ObjectToBuilding(855, 2) - ObjectToBuilding(860, 2) } val map14 = new ZoneMap("map13") diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 7ce17fe86..129afc99b 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -25,10 +25,10 @@ import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.mblocker.Locker import net.psforever.objects.serverobject.pad.VehicleSpawnPad -import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, SpawnTerminalDefinition, Terminal} +import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, Terminal} import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, VehicleLockState} -import net.psforever.objects.serverobject.structures.{Building, WarpGate} +import net.psforever.objects.serverobject.structures.{Building, StructureType, WarpGate} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.vehicles.{AccessPermissionGroup, VehicleLockState} @@ -56,6 +56,8 @@ class WorldSessionActor extends Actor with MDCContextAware { var taskResolver : ActorRef = Actor.noSender var galaxy : ActorRef = Actor.noSender var continent : Zone = null + var player : Player = null + var avatar : Avatar = null var progressBarValue : Option[Float] = None var shooting : Option[PlanetSideGUID] = None var accessedContainer : Option[PlanetSideGameObject with Container] = None @@ -66,9 +68,12 @@ class WorldSessionActor extends Actor with MDCContextAware { var clientKeepAlive : Cancellable = DefaultCancellable.obj var progressBarUpdate : Cancellable = DefaultCancellable.obj + var reviveTimer : Cancellable = DefaultCancellable.obj override def postStop() = { - clientKeepAlive.cancel() + clientKeepAlive.cancel + progressBarUpdate.cancel + reviveTimer.cancel localService ! Service.Leave() vehicleService ! Service.Leave() avatarService ! Service.Leave() @@ -282,7 +287,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case AvatarResponse.Release(tplayer) => if(tplayer_guid != guid) { - turnPlayerIntoCorpse(tplayer) + TurnPlayerIntoCorpse(tplayer) } case AvatarResponse.Reload(item_guid) => @@ -920,7 +925,6 @@ class WorldSessionActor extends Actor with MDCContextAware { continent.Transport ! Zone.SpawnVehicle(vehicle) vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.LoadVehicle(player_guid, vehicle, objedtId, vehicle_guid, vdata)) sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 1L)) //mount points off? - sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 1L)) //mount points off? sendResponse(PlanetsideAttributeMessage(vehicle_guid, 21, player_guid.guid)) //fte and ownership? //sendResponse(ObjectAttachMessage(vehicle_guid, player_guid, 0)) vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) //cancel queue timeout delay @@ -1002,6 +1006,64 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info(s"$tplayer has left zone ${zone.Id}") } + case Zone.Lattice.SpawnPoint(zone_id, building, spawn_tube) => + log.info(s"Zone.Lattice.SpawnPoint: spawn point on $zone_id in ${building.Id} @ ${spawn_tube.GUID.guid} selected") + reviveTimer.cancel + val sameZone = zone_id == continent.Id + val backpack = player.isBackpack + val respawnTime : Long = if(sameZone) { 10 } else { 0 } //s + val respawnTimeMillis = respawnTime * 1000 //ms + sendResponse(AvatarDeadStateMessage(DeadState.RespawnTime, respawnTimeMillis, respawnTimeMillis, Vector3.Zero, 2, true)) + val tplayer = if(backpack) { + RespawnClone(player) //new player + } + else { + val player_guid = player.GUID + sendResponse(ObjectDeleteMessage(player_guid, 4)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid, 4)) + player //player is deconstructing self + } + + 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 + (taskResolver, RegisterAvatar(tplayer)) + } + else { + //move existing player + (self, PlayerLoaded(tplayer)) + } + } + else { + continent.Population ! Zone.Population.Leave(avatar) + val original = player + //TODO check player orientation upon spawn not polluted + if(backpack) { + //unregister avatar locker + GiveWorld + player = tplayer + (taskResolver, TaskBeforeZoneChange(GUIDTask.UnregisterLocker(original.Locker)(continent.GUID), zone_id)) + } + else { + //unregister avatar whole + GiveWorld + (taskResolver, TaskBeforeZoneChange(GUIDTask.UnregisterAvatar(original)(continent.GUID), zone_id)) + } + } + context.system.scheduler.scheduleOnce(respawnTime seconds, target, msg) + + case Zone.Lattice.NoValidSpawnPoint(zone_number, None) => + log.warn(s"Zone.Lattice.SpawnPoint: zone $zone_number could not be accessed as requested") + reviveTimer.cancel + RequestSanctuaryZoneSpawn(player, zone_number) + + case Zone.Lattice.NoValidSpawnPoint(zone_number, Some(spawn_group)) => + log.warn(s"Zone.Lattice.SpawnPoint: zone $zone_number has no available ${player.Faction} targets in spawn group $spawn_group") + reviveTimer.cancel + RequestSanctuaryZoneSpawn(player, zone_number) + case InterstellarCluster.ClientInitializationComplete() => LivePlayerList.Add(sessionId, avatar) //PropertyOverrideMessage @@ -1012,21 +1074,21 @@ class WorldSessionActor extends Actor with MDCContextAware { galaxy ! InterstellarCluster.GetWorld("z6") case InterstellarCluster.GiveWorld(zoneId, zone) => - log.info(s"Zone $zoneId has been loaded") + log.info(s"Zone $zoneId will now load") player.Continent = zoneId continent = zone continent.Population ! Zone.Population.Join(avatar) taskResolver ! RegisterNewAvatar(player) case NewPlayerLoaded(tplayer) => - log.info(s"Player $tplayer has been loaded") + log.info(s"Player ${tplayer.Name} has been loaded") player = tplayer //LoadMapMessage will cause the client to send back a BeginZoningMessage packet (see below) sendResponse(LoadMapMessage(continent.Map.Name, continent.Id, 40100,25,true,3770441820L)) AvatarCreate() //important! the LoadMapMessage must be processed by the client before the avatar is created case PlayerLoaded(tplayer) => - log.info(s"Player $tplayer has been loaded") + log.info(s"Player ${tplayer.Name} will respawn") player = tplayer AvatarCreate() self ! SetCurrentAvatar(tplayer) @@ -1034,22 +1096,25 @@ class WorldSessionActor extends Actor with MDCContextAware { case PlayerFailedToLoad(tplayer) => player.Continent match { case _ => - failWithError(s"$tplayer failed to load anywhere") + failWithError(s"${tplayer.Name} failed to load anywhere") } case SetCurrentAvatar(tplayer) => player = tplayer val guid = tplayer.GUID sendResponse(SetCurrentAvatarMessage(guid,0,0)) + if(spectator) { + sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, false, "", "on", None)) + } (0 until DetailedCharacterData.numberOfImplantSlots(tplayer.BEP)).foreach(slot => { sendResponse(AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 1)) //init implant slot sendResponse(AvatarImplantMessage(guid, ImplantAction.Activation, slot, 0)) //deactivate implant - //TODO: if this implant is Installed but does not have shortcut, add to a free slot or write over slot 61/62/63 + //TODO if this implant is Installed but does not have shortcut, add to a free slot or write over slot 61/62/63 }) sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0)) - //TODO: if Medkit does not have shortcut, add to a free slot or write over slot 64 + //TODO if Medkit does not have shortcut, add to a free slot or write over slot 64 sendResponse(CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT)) sendResponse(ChangeShortcutBankMessage(guid, 0)) //FavoritesMessage @@ -1195,10 +1260,6 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - var player : Player = null - var avatar : Avatar = null - var spawnZones : Map[Int, Building] = null - def handleGamePkt(pkt : PlanetSideGamePacket) = pkt match { case ConnectToWorldRequestMessage(server, token, majorVersion, minorVersion, revision, buildDate, unk) => val clientVersion = s"Client Version: $majorVersion.$minorVersion.$revision, $buildDate" @@ -1272,7 +1333,6 @@ class WorldSessionActor extends Actor with MDCContextAware { //TODO check if can spawn on last continent/location from player? //TODO if yes, get continent guid accessors //TODO if no, get sanctuary guid accessors and reset the player's expectations - //galaxy ! InterstellarCluster.GetWorld("z6") galaxy ! InterstellarCluster.RequestClientInitialization() case default => log.error("Unsupported " + default + " in " + msg) @@ -1283,18 +1343,14 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ BeginZoningMessage() => log.info("Reticulating splines ...") - configZone(continent) //todo density + configZone(continent) sendResponse(TimeOfDayMessage(1191182336)) - /** WIP */ - spawnZones = Map( - 7 -> continent.Building(2).get, - 6 -> continent.Building(48).get - ) //custom sendResponse(ContinentalLockUpdateMessage(13, PlanetSideEmpire.VS)) // "The VS have captured the VS Sanctuary." sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list - sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1)) + sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1)) //common + (0 to 255).foreach(i => { sendResponse(SetEmpireMessage(PlanetSideGUID(i), PlanetSideEmpire.VS)) }) //render Equipment that was dropped into zone before the player arrived continent.EquipmentOnGround.foreach(item => { @@ -1309,20 +1365,16 @@ class WorldSessionActor extends Actor with MDCContextAware { }) //load active players in zone continent.LivePlayers.filterNot(_.GUID == player.GUID).foreach(char => { - sendResponse( - ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get) - ) + sendResponse(ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get)) }) //load corpses in zone - continent.Corpses.foreach( turnPlayerIntoCorpse(_) ) + continent.Corpses.foreach { TurnPlayerIntoCorpse } //load active vehicles in zone continent.Vehicles.foreach(vehicle => { val definition = vehicle.Definition - sendResponse( - ObjectCreateMessage(definition.ObjectId, vehicle.GUID, definition.Packet.ConstructorData(vehicle).get) - ) + sendResponse(ObjectCreateMessage(definition.ObjectId, vehicle.GUID, definition.Packet.ConstructorData(vehicle).get)) //seat vehicle occupants - vehicle.Definition.MountPoints.values.foreach(seat_num => { + definition.MountPoints.values.foreach(seat_num => { vehicle.Seat(seat_num).get.Occupant match { case Some(tplayer) => if(tplayer.HasGUID) { @@ -1339,14 +1391,12 @@ class WorldSessionActor extends Actor with MDCContextAware { continent.GUID(interface_guid) match { case Some(obj : Terminal) => val objDef = obj.Definition - val obj_uid = objDef.ObjectId - val obj_data = objDef.Packet.ConstructorData(obj).get sendResponse( ObjectCreateMessage( - obj_uid, + ObjectClass.implant_terminal_interface, PlanetSideGUID(interface_guid), ObjectCreateMessageParent(parent_guid, 1), - obj_data + objDef.Packet.ConstructorData(obj).get ) ) case _ => ; @@ -1444,36 +1494,35 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ReleaseAvatarRequestMessage() => log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released") - //TODO is it easier to delete the player, then re-create them as a corpse? + reviveTimer.cancel player.Release - continent.Population ! Zone.Population.Release(avatar) - continent.Population ! Zone.Corpse.Add(player) - val knife = player.Slot(4).Equipment.get - taskResolver ! RemoveEquipmentFromSlot(player, knife, 4) - turnPlayerIntoCorpse(player) - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.Release(player, continent)) sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, 2, true)) + continent.Population ! Zone.Population.Release(avatar) + 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)) + + case Some(_) => + //TODO we do not want to delete the player if he is seated in a vehicle when releasing + //TODO it is necessary for now until we know how to juggle ownership properly + 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) + //sendResponse(ObjectDetachMessage(vehicle_guid, player.GUID, Vector3.Zero, 0, 0, 0)) + //sendResponse(PlayerStateShiftMessage(ShiftState(1, Vector3.Zero, 0))) + } case msg @ SpawnRequestMessage(u1, u2, u3, u4, u5) => log.info(s"SpawnRequestMessage: $msg") - spawnZones.get(u2.toInt) match { - case Some(building) => - scala.util.Random.shuffle(building.Amenities.filter(_.isInstanceOf[SpawnTube])).headOption match { //TODO temporary shuffle - case Some(tube) => - val tplayer = SpawnRequest(player) //new player - tplayer.Position = tube.Position - tplayer.Orientation = tube.Orientation - log.info(s"SpawnRequestMessage: new player will spawn in ${building.Id} @ ${tube.GUID.guid}") - sendResponse(AvatarDeadStateMessage(DeadState.RespawnTime, 10000, 10000, Vector3.Zero, 2, true)) - import scala.concurrent.duration._ - import scala.concurrent.ExecutionContext.Implicits.global - context.system.scheduler.scheduleOnce(10 seconds, taskResolver, RegisterAvatar(tplayer)) - case None => - log.warn(s"SpawnRequestMessage: can not find a spawn point in this spawn group - $u2") - } - case None => - log.warn(s"SpawnRequestMessage: can not find somewhere to spawn on ${continent.Id}") - } + //TODO just focus on u5 and u2 for now + galaxy ! Zone.Lattice.RequestSpawnPoint(u5.toInt, player, u2.toInt) case msg @ SetChatFilterMessage(send_channel, origin, whitelist) => log.info("SetChatFilters: " + msg) @@ -1516,15 +1565,11 @@ class WorldSessionActor extends Actor with MDCContextAware { } if(messagetype == ChatMessageType.CMT_SUICIDE) { - val player_guid = player.GUID - val pos = player.Position - player.Die - sendResponse(PlanetsideAttributeMessage(player_guid, 0, 0)) - sendResponse(PlanetsideAttributeMessage(player_guid, 2, 0)) - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 0, 0)) - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 2, 0)) - sendResponse(DestroyMessage(player_guid, player_guid, PlanetSideGUID(0), pos)) //how many players get this message? - sendResponse(AvatarDeadStateMessage(DeadState.Dead, 300000, 300000, pos, 2, true)) + KillPlayer(player) + } + + if(messagetype == ChatMessageType.CMT_DESTROY) { + self ! PacketCoding.CreateGamePacket(0, RequestDestroyMessage(PlanetSideGUID(contents.toInt))) } if (messagetype == ChatMessageType.CMT_VOICE) { @@ -1853,7 +1898,8 @@ class WorldSessionActor extends Actor with MDCContextAware { // TODO: Make sure this is the correct response for all cases continent.GUID(object_guid) match { case Some(vehicle : Vehicle) => - if(player.VehicleOwned.contains(object_guid) && vehicle.Owner.contains(player.GUID)) { + if((player.VehicleOwned.contains(object_guid) && vehicle.Owner.contains(player.GUID)) + || (player.Faction == vehicle.Faction && (vehicle.Owner.isEmpty || vehicle.Health == 0))) { vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(object_guid) vehicleService ! VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent) log.info(s"RequestDestroy: vehicle $object_guid") @@ -2084,7 +2130,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } case Some(obj : Terminal) => - if(obj.Definition.isInstanceOf[MatrixTerminalDefinition] || obj.Definition.isInstanceOf[SpawnTerminalDefinition]) { + if(obj.Definition.isInstanceOf[MatrixTerminalDefinition]) { //TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks) sendResponse(BindPlayerMessage(1, "@ams", true, true, 0, 0, 0, obj.Position)) } @@ -2092,6 +2138,13 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) } + case Some(obj : SpawnTube) => + //deconstruction + PlayerActionsToCancel() + player.Release + sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, 2, true)) + continent.Population ! Zone.Population.Release(avatar) + case Some(obj : PlanetSideGameObject) => if(itemType != 121) { sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) @@ -2217,7 +2270,7 @@ class WorldSessionActor extends Actor with MDCContextAware { def dismountWarning(msg : String) : Unit = { log.warn(s"$msg; some vehicle might not know that a player is no longer sitting in it") } - if(player.GUID == player_guid) { + if(player.HasGUID && player.GUID == player_guid) { //normally disembarking from a seat player.VehicleSeated match { case Some(obj_guid) => @@ -2562,32 +2615,10 @@ class WorldSessionActor extends Actor with MDCContextAware { override def onFailure(ex : Throwable) : Unit = { localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WSA } - }, List(RegisterLightweightAvatar(tplayer)(continent.GUID)) + }, List(GUIDTask.RegisterPlayer(tplayer)(continent.GUID)) ) } - //TODO temporary function for registering avatar without locker contents - def RegisterLightweightAvatar(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = { - import net.psforever.objects.LockerContainer - import net.psforever.objects.inventory.InventoryItem - val holsterTasks = tplayer.Holsters().filter(_.Equipment.isDefined).map(slot =>{ - GUIDTask.RegisterEquipment(slot.Equipment.get)(guid) - }).toList - val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => GUIDTask.RegisterEquipment(entry.obj)(guid)}) - TaskResolver.GiveTask(GUIDTask.RegisterObjectTask(tplayer)(guid).task, holsterTasks ++ inventoryTasks) - } - - //TODO temporary function for unregistering avatar without locker contents - def UnregisterLightweightAvatar(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = { - import net.psforever.objects.LockerContainer - import net.psforever.objects.inventory.InventoryItem - val holsterTasks = tplayer.Holsters().filter(_.Equipment.isDefined).map(slot =>{ - GUIDTask.UnregisterEquipment(slot.Equipment.get)(guid) - }).toList - val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => GUIDTask.UnregisterEquipment(entry.obj)(guid)}) - TaskResolver.GiveTask(GUIDTask.UnregisterObjectTask(tplayer)(guid).task, holsterTasks ++ inventoryTasks) - } - /** * Construct tasking that adds a completed and registered vehicle into the scene. * Use this function to renew the globally unique identifiers on a vehicle that has already been added to the scene once. @@ -2739,6 +2770,22 @@ class WorldSessionActor extends Actor with MDCContextAware { ) } + 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 + + def Execute(resolver : ActorRef) : Unit = { + localService ! localMsg + resolver ! scala.util.Success(this) + } + }, List(priorTask) + ) + } + /** * After a client has connected to the server, their account is used to generate a list of characters. * On the character selection screen, each of these characters is made to exist temporarily when one is selected. @@ -3223,10 +3270,10 @@ class WorldSessionActor extends Actor with MDCContextAware { * @param building the building object */ def initBuilding(continentNumber : Int, buildingNumber : Int, building : Building) : Unit = { - building match { - case _ : WarpGate => + building.BuildingType match { + case StructureType.WarpGate => initGate(continentNumber, buildingNumber, building) - case _ : Building => + case _ => initFacility(continentNumber, buildingNumber, building) } } @@ -3309,6 +3356,14 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(BroadcastWarpgateUpdateMessage(continentNumber, buildingNumber, false, false, true)) } + /** + * Configure the buildings and each specific amenity for that building in a given zone by sending the client packets. + * These actions are performed during the loading of a zone. + * @see `SetEmpireMessage`
+ * `PlanetsideAttributeMessage`
+ * `HackMessage` + * @param zone the zone being loaded + */ def configZone(zone : Zone) : Unit = { zone.Buildings.values.foreach(building => { sendResponse(SetEmpireMessage(PlanetSideGUID(building.ModelId), building.Faction)) @@ -3322,11 +3377,90 @@ class WorldSessionActor extends Actor with MDCContextAware { } /** - * TODO write + * The player has lost all his vitality and must be killed.
+ *
+ * Shift directly into a state of being dead on the client by setting health to zero points, + * whereupon the player will perform a dramatic death animation. + * Stamina is also set to zero points. + * If the player was in a vehicle at the time of demise, special conditions apply and + * the model must be manipulated so it behaves correctly. + * Do not move or completely destroy the `Player` object as its coordinates of death will be important.
+ *
+ * A maximum revive waiting timer is started. + * When this timer reaches zero, the avatar will attempt to spawn back on its faction-specific sanctuary continent. + * @pararm tplayer the player to be killed + */ + def KillPlayer(tplayer : Player) : Unit = { + val player_guid = tplayer.GUID + val pos = tplayer.Position + val respawnTimer = 300000 //milliseconds + tplayer.Die + sendResponse(PlanetsideAttributeMessage(player_guid, 0, 0)) + sendResponse(PlanetsideAttributeMessage(player_guid, 2, 0)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 0, 0)) + sendResponse(DestroyMessage(player_guid, player_guid, PlanetSideGUID(0), pos)) //how many players get this message? + sendResponse(AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, pos, 2, true)) + if(tplayer.VehicleSeated.nonEmpty) { + //make player invisible (if not, the cadaver sticks out the side in a seated position) + sendResponse(PlanetsideAttributeMessage(player_guid, 29, 1)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 29, 1)) + } + PlayerActionsToCancel() + + 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)) + } + + /** + * An event has occurred that would cause the player character to stop certain stateful activities. + * These activities include shooting, hacking, accessing (a container), flying, and running. + * Other players in the same zone must be made aware that the player has stopped as well.
+ *
+ * Things whose configuration should not be changed:
+ * - if the player is seated + */ + def PlayerActionsToCancel() : Unit = { + progressBarUpdate.cancel + progressBarValue = None + accessedContainer match { + case Some(obj : Vehicle) => + if(obj.AccessingTrunk.contains(player.GUID)) { + obj.AccessingTrunk = None + UnAccessContents(obj) + } + accessedContainer = None + + case Some(_) => + accessedContainer = None + + case None => ; + } + shooting match { + case Some(guid) => + sendResponse(ChangeFireStateMessage_Stop(guid)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, guid)) + shooting = None + case None => ; + } + if(flying) { + sendResponse(ChatMsg(ChatMessageType.CMT_FLY, false, "", "off", None)) + flying = false + } + if(speed > 1) { + sendResponse(ChatMsg(ChatMessageType.CMT_SPEED, false, "", "1.000", None)) + speed = 1f + } + } + + /** + * A part of the process of spawning the player into the game world. + * The function should work regardless of whether the player is alive or dead - it will make them alive. + * It adds the `WSA`-current `Player` to the current zone and sends out the expected packets. */ def AvatarCreate() : Unit = { player.Spawn - player.Health = 50 + player.Health = 50 //TODO temp val packet = player.Definition.Packet val dcdata = packet.DetailedConstructorData(player).get sendResponse(ObjectCreateDetailedMessage(ObjectClass.avatar, player.GUID, dcdata)) @@ -3335,11 +3469,16 @@ class WorldSessionActor extends Actor with MDCContextAware { log.debug(s"ObjectCreateDetailedMessage: $dcdata") } - def SpawnRequest(tplayer : Player) : Player = { + /** + * Produce a clone of the player that is equipped with the default infantry loadout. + * The loadout is hardcoded. + * The player is expected to be in a Standard Exo-Suit. + * @param tplayer the original player + * @return the duplication of the player, in Standard Exo-Suit and with default equipment loadout + */ + def RespawnClone(tplayer : Player) : Player = { val faction = tplayer.Faction val obj = Player.Respawn(tplayer) - //obj.VehicleOwned = tplayer.VehicleOwned - //obj.Continent = tplayer.Continent obj.Slot(0).Equipment = Tool(StandardPistol(faction)) obj.Slot(2).Equipment = Tool(suppressor) obj.Slot(4).Equipment = Tool(StandardMelee(faction)) @@ -3352,13 +3491,38 @@ class WorldSessionActor extends Actor with MDCContextAware { obj } - def turnPlayerIntoCorpse(tplayer : Player) : Unit = { - //sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 6, 1)) + /** + * Creates a player that has the characteristics of a corpse. + * To the game, that is a backpack (or some pastry, festive graphical modification allowing). + * @see `CorpseConverter.converter` + * @param tplayer the player + */ + def TurnPlayerIntoCorpse(tplayer : Player) : Unit = { sendResponse( ObjectCreateDetailedMessage(ObjectClass.avatar, tplayer.GUID, CorpseConverter.converter.DetailedConstructorData(tplayer).get) ) } + /** + * Attempt to tranfer to the player's faction-specific sanctuary continent. + * If the server thinks the player is already on his sanctuary continent, + * it will disconnect the player under the assumption that an error has occurred. + * Eventually, this functionality should support better error-handling before it jumps to the conclusion: + * "Disconnecting the client is the safest option." + * @see `Zones.SanctuaryZoneNumber` + * @param tplayer the player + * @param currentZone the current cone number + */ + def RequestSanctuaryZoneSpawn(tplayer : Player, currentZone : Int) : Unit = { + val sanctNumber = Zones.SanctuaryZoneNumber(tplayer.Faction) + if(currentZone == sanctNumber) { + sendResponse(DisconnectMessage("Player failed to load on faction's sanctuary continent. Please relog.")) + } + else { + galaxy ! Zone.Lattice.RequestSpawnPoint(sanctNumber, tplayer, 7) + } + } + def failWithError(error : String) = { log.error(error) sendResponse(ConnectionClose()) diff --git a/pslogin/src/main/scala/Zones.scala b/pslogin/src/main/scala/Zones.scala index 051fcf5ad..6cf61e332 100644 --- a/pslogin/src/main/scala/Zones.scala +++ b/pslogin/src/main/scala/Zones.scala @@ -1,6 +1,7 @@ // Copyright (c) 2017 PSForever import akka.actor.ActorContext import net.psforever.objects.zones.Zone +import net.psforever.types.PlanetSideEmpire object Zones { val z1 = new Zone("z1", Maps.map1, 1) @@ -86,4 +87,32 @@ object Zones { val i3 = new Zone("i3", Maps.map98, 31) val i4 = new Zone("i4", Maps.map99, 32) + + /** + * Get the zone identifier name for the sanctuary continent of a given empire. + * @param faction the empire + * @return the zone id, with a blank string as an invalidating result + */ + def SanctuaryZoneId(faction : PlanetSideEmpire.Value) : String = { + faction match { + case PlanetSideEmpire.TR => "home1" + case PlanetSideEmpire.NC => "home2" + case PlanetSideEmpire.VS => "home3" + case PlanetSideEmpire.NEUTRAL => "" //invalid, not black ops + } + } + + /** + * Get the zone number for the sanctuary continent of a given empire. + * @param faction the empire + * @return the zone number, within the sequence 1-32, and with 0 as an invalidating result + */ + def SanctuaryZoneNumber(faction : PlanetSideEmpire.Value) : Int = { + faction match { + case PlanetSideEmpire.TR => 11 + case PlanetSideEmpire.NC => 12 + case PlanetSideEmpire.VS => 13 + case PlanetSideEmpire.NEUTRAL => 0 //invalid, not black ops + } + } } diff --git a/pslogin/src/main/scala/services/avatar/support/UndertakerActor.scala b/pslogin/src/main/scala/services/avatar/support/UndertakerActor.scala index bc9e81220..83788a174 100644 --- a/pslogin/src/main/scala/services/avatar/support/UndertakerActor.scala +++ b/pslogin/src/main/scala/services/avatar/support/UndertakerActor.scala @@ -92,7 +92,7 @@ class UndertakerActor extends Actor { override def onFailure(ex : Throwable): Unit = { localAnnounce ! UndertakerActor.FailureToWork(localCorpse, localZone, ex) } - }, List(GUIDTask.UnregisterAvatar(corpse)(zone.GUID)) + }, List(GUIDTask.UnregisterPlayer(corpse)(zone.GUID)) ) } From 7f40b31a34bc1c8b5989c8fc54c78e15f81ade8f Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 29 Mar 2018 23:45:11 -0400 Subject: [PATCH 6/9] Akna Air Tower, Ceryshen, has its amenities wired. The default spawn point has been moved to allow for the tower at Akna to interfere with the tower at Anguta. --- .../scala/game/DisconnectMessageTest.scala | 4 + pslogin/src/main/scala/Maps.scala | 953 ++++++++++-------- .../src/main/scala/WorldSessionActor.scala | 14 +- pslogin/src/main/scala/Zones.scala | 2 + 4 files changed, 538 insertions(+), 435 deletions(-) diff --git a/common/src/test/scala/game/DisconnectMessageTest.scala b/common/src/test/scala/game/DisconnectMessageTest.scala index 008ee149b..6e4f4c3a7 100644 --- a/common/src/test/scala/game/DisconnectMessageTest.scala +++ b/common/src/test/scala/game/DisconnectMessageTest.scala @@ -26,4 +26,8 @@ class DisconnectMessageTest extends Specification { pkt mustEqual string } + + "comparison" in { + DisconnectMessage("First") mustEqual DisconnectMessage("First", "", "") + } } diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala index 6625e55f9..9b64ad924 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/pslogin/src/main/scala/Maps.scala @@ -23,247 +23,318 @@ object Maps { val map5 = new ZoneMap("map05") val map6 = new ZoneMap("map06") { - LocalBuilding(2, FoundationBuilder(Building.Structure(StructureType.Facility, Vector3(3974.2344f, 4287.914f, 0)))) //Anguta - LocalObject(222, Door.Constructor) //air term building, bay door - LocalObject(370, Door.Constructor) //courtyard - 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(383, Door.Constructor) //courtyard - LocalObject(384, Door.Constructor) //3rd floor door - LocalObject(385, Door.Constructor) //courtyard - LocalObject(387, Door.Constructor) //2nd level door - LocalObject(391, Door.Constructor) //courtyard - LocalObject(393, Door.Constructor) //air term building, upstairs door - LocalObject(394, Door.Constructor) //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(603, Door.Constructor) - LocalObject(604, Door.Constructor) - LocalObject(605, Door.Constructor) - LocalObject(606, Door.Constructor) - LocalObject(607, Door.Constructor) - LocalObject(610, Door.Constructor) - LocalObject(611, Door.Constructor) - LocalObject(614, Door.Constructor) - LocalObject(619, Door.Constructor) - LocalObject(620, Door.Constructor) //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(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(1177, Locker.Constructor) - LocalObject(1178, Locker.Constructor) - LocalObject(1179, Locker.Constructor) - LocalObject(1180, Locker.Constructor) - LocalObject(1181, Locker.Constructor) - LocalObject(1182, Locker.Constructor) - LocalObject(1183, Locker.Constructor) - LocalObject(1184, Locker.Constructor) - LocalObject(1185, Locker.Constructor) - LocalObject(1186, Locker.Constructor) - LocalObject(1187, Locker.Constructor) - LocalObject(1188, Locker.Constructor) - LocalObject(1564, Terminal.Constructor(order_terminal)) - LocalObject(1568, Terminal.Constructor(order_terminal)) - LocalObject(1569, Terminal.Constructor(order_terminal)) - LocalObject(1570, Terminal.Constructor(order_terminal)) - LocalObject(1571, Terminal.Constructor(order_terminal)) - LocalObject(1576, Terminal.Constructor(order_terminal)) - LocalObject(1577, Terminal.Constructor(order_terminal)) - LocalObject(1578, Terminal.Constructor(order_terminal)) - LocalObject(2145, SpawnTube.Constructor(Vector3(3980.4062f, 4252.7656f, 257.5625f), Vector3(0, 0, 90))) - LocalObject(2146, SpawnTube.Constructor(Vector3(3980.4062f, 4259.992f, 257.5625f), Vector3(0, 0, 90))) - LocalObject(2147, SpawnTube.Constructor(Vector3(3980.4062f, 4267.3047f, 257.5625f), Vector3(0, 0, 90))) - LocalObject(2239, Terminal.Constructor(spawn_terminal)) - LocalObject(2244, Terminal.Constructor(spawn_terminal)) - LocalObject(2245, Terminal.Constructor(spawn_terminal)) - LocalObject(2246, Terminal.Constructor(spawn_terminal)) - LocalObject(2248, Terminal.Constructor(spawn_terminal)) - LocalObject(2250, Terminal.Constructor(spawn_terminal)) - LocalObject(2251, Terminal.Constructor(spawn_terminal)) - LocalObject(2253, Terminal.Constructor(spawn_terminal)) - LocalObject(2254, Terminal.Constructor(spawn_terminal)) - LocalObject(2322, Door.Constructor) //spawn tube door - LocalObject(2323, Door.Constructor) //spawn tube door - 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)) - ) //TODO guid not correct - LocalObject(224, Terminal.Constructor(dropship_vehicle_terminal)) - LocalObject(501, - VehicleSpawnPad.Constructor(Vector3(4012.3594f, 4364.8047f, 271.90625f), Vector3(0f, 0f, 180.0f)) - ) //TODO guid not correct - ObjectToBuilding(222, 2) - ObjectToBuilding(224, 2) - ObjectToBuilding(370, 2) - ObjectToBuilding(371, 2) - ObjectToBuilding(372, 2) - ObjectToBuilding(373, 2) - ObjectToBuilding(375, 2) - ObjectToBuilding(376, 2) - ObjectToBuilding(383, 2) - ObjectToBuilding(384, 2) - ObjectToBuilding(385, 2) - ObjectToBuilding(387, 2) - ObjectToBuilding(391, 2) - ObjectToBuilding(393, 2) - ObjectToBuilding(394, 2) - ObjectToBuilding(396, 2) - ObjectToBuilding(398, 2) - ObjectToBuilding(399, 2) - ObjectToBuilding(402, 2) - ObjectToBuilding(403, 2) - ObjectToBuilding(404, 2) - ObjectToBuilding(603, 2) - ObjectToBuilding(604, 2) - ObjectToBuilding(605, 2) - ObjectToBuilding(606, 2) - ObjectToBuilding(607, 2) - ObjectToBuilding(610, 2) - ObjectToBuilding(611, 2) - ObjectToBuilding(614, 2) - ObjectToBuilding(619, 2) - ObjectToBuilding(620, 2) - ObjectToBuilding(621, 2) - ObjectToBuilding(622, 2) - ObjectToBuilding(623, 2) - ObjectToBuilding(630, 2) - ObjectToBuilding(631, 2) - ObjectToBuilding(634, 2) - ObjectToBuilding(638, 2) - ObjectToBuilding(642, 2) - ObjectToBuilding(643, 2) - ObjectToBuilding(645, 2) - ObjectToBuilding(646, 2) - ObjectToBuilding(715, 2) - ObjectToBuilding(1177, 2) - ObjectToBuilding(1178, 2) - ObjectToBuilding(1179, 2) - ObjectToBuilding(1180, 2) - ObjectToBuilding(1181, 2) - ObjectToBuilding(1182, 2) - ObjectToBuilding(1183, 2) - ObjectToBuilding(1184, 2) - ObjectToBuilding(1185, 2) - ObjectToBuilding(1186, 2) - ObjectToBuilding(1187, 2) - ObjectToBuilding(1188, 2) - ObjectToBuilding(1564, 2) - ObjectToBuilding(1568, 2) - ObjectToBuilding(1569, 2) - ObjectToBuilding(1570, 2) - ObjectToBuilding(1571, 2) - ObjectToBuilding(1576, 2) - ObjectToBuilding(1577, 2) - ObjectToBuilding(1578, 2) - ObjectToBuilding(2145, 2) - ObjectToBuilding(2146, 2) - ObjectToBuilding(2147, 2) - ObjectToBuilding(2239, 2) - ObjectToBuilding(2244, 2) - ObjectToBuilding(2245, 2) - ObjectToBuilding(2246, 2) - ObjectToBuilding(2248, 2) - ObjectToBuilding(2250, 2) - ObjectToBuilding(2251, 2) - ObjectToBuilding(2253, 2) - ObjectToBuilding(2254, 2) - ObjectToBuilding(2322, 2) - ObjectToBuilding(2323, 2) - ObjectToBuilding(2324, 2) - ObjectToBuilding(2419, 2) - ObjectToBuilding(500, 2) - ObjectToBuilding(501, 2) - TerminalToSpawnPad(224, 501) - TerminalToSpawnPad(2419, 500) + Building2() + Building38() + Building42() + Building48() + Building49() - LocalBuilding(38, FoundationBuilder(Building.Structure(StructureType.Bunker))) //Anguta, West Bunker - LocalObject(362, Door.Constructor) - ObjectToBuilding(362, 38) + def Building2() : Unit = { + //Anguta + LocalBuilding(2, FoundationBuilder(Building.Structure(StructureType.Facility, Vector3(3974.2344f, 4287.914f, 0)))) + LocalObject(222, Door.Constructor) //air term building, bay door + LocalObject(370, Door.Constructor) //courtyard + 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(383, Door.Constructor) //courtyard + LocalObject(384, Door.Constructor) //3rd floor door + LocalObject(385, Door.Constructor) //courtyard + LocalObject(387, Door.Constructor) //2nd level door + LocalObject(391, Door.Constructor) //courtyard + LocalObject(393, Door.Constructor) //air term building, upstairs door + LocalObject(394, Door.Constructor) //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(603, Door.Constructor) + LocalObject(604, Door.Constructor) + LocalObject(605, Door.Constructor) + LocalObject(606, Door.Constructor) + LocalObject(607, Door.Constructor) + LocalObject(610, Door.Constructor) + LocalObject(611, Door.Constructor) + LocalObject(614, Door.Constructor) + LocalObject(619, Door.Constructor) + LocalObject(620, Door.Constructor) //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(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(1177, Locker.Constructor) + LocalObject(1178, Locker.Constructor) + LocalObject(1179, Locker.Constructor) + LocalObject(1180, Locker.Constructor) + LocalObject(1181, Locker.Constructor) + LocalObject(1182, Locker.Constructor) + LocalObject(1183, Locker.Constructor) + LocalObject(1184, Locker.Constructor) + LocalObject(1185, Locker.Constructor) + LocalObject(1186, Locker.Constructor) + LocalObject(1187, Locker.Constructor) + LocalObject(1188, Locker.Constructor) + LocalObject(1564, Terminal.Constructor(order_terminal)) + LocalObject(1568, Terminal.Constructor(order_terminal)) + LocalObject(1569, Terminal.Constructor(order_terminal)) + LocalObject(1570, Terminal.Constructor(order_terminal)) + LocalObject(1571, Terminal.Constructor(order_terminal)) + LocalObject(1576, Terminal.Constructor(order_terminal)) + LocalObject(1577, Terminal.Constructor(order_terminal)) + LocalObject(1578, Terminal.Constructor(order_terminal)) + LocalObject(2145, SpawnTube.Constructor(Vector3(3980.4062f, 4252.7656f, 257.5625f), Vector3(0, 0, 90))) + LocalObject(2146, SpawnTube.Constructor(Vector3(3980.4062f, 4259.992f, 257.5625f), Vector3(0, 0, 90))) + LocalObject(2147, SpawnTube.Constructor(Vector3(3980.4062f, 4267.3047f, 257.5625f), Vector3(0, 0, 90))) + LocalObject(2239, Terminal.Constructor(spawn_terminal)) + LocalObject(2244, Terminal.Constructor(spawn_terminal)) + LocalObject(2245, Terminal.Constructor(spawn_terminal)) + LocalObject(2246, Terminal.Constructor(spawn_terminal)) + LocalObject(2248, Terminal.Constructor(spawn_terminal)) + LocalObject(2250, Terminal.Constructor(spawn_terminal)) + LocalObject(2251, Terminal.Constructor(spawn_terminal)) + LocalObject(2253, Terminal.Constructor(spawn_terminal)) + LocalObject(2254, Terminal.Constructor(spawn_terminal)) + LocalObject(2322, Door.Constructor) //spawn tube door + LocalObject(2323, Door.Constructor) //spawn tube door + 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)) + ) //TODO guid not correct + LocalObject(224, Terminal.Constructor(dropship_vehicle_terminal)) + LocalObject(501, + VehicleSpawnPad.Constructor(Vector3(4012.3594f, 4364.8047f, 271.90625f), Vector3(0f, 0f, 180.0f)) + ) //TODO guid not correct + ObjectToBuilding(222, 2) + ObjectToBuilding(224, 2) + ObjectToBuilding(370, 2) + ObjectToBuilding(371, 2) + ObjectToBuilding(372, 2) + ObjectToBuilding(373, 2) + ObjectToBuilding(375, 2) + ObjectToBuilding(376, 2) + ObjectToBuilding(383, 2) + ObjectToBuilding(384, 2) + ObjectToBuilding(385, 2) + ObjectToBuilding(387, 2) + ObjectToBuilding(391, 2) + ObjectToBuilding(393, 2) + ObjectToBuilding(394, 2) + ObjectToBuilding(396, 2) + ObjectToBuilding(398, 2) + ObjectToBuilding(399, 2) + ObjectToBuilding(402, 2) + ObjectToBuilding(403, 2) + ObjectToBuilding(404, 2) + ObjectToBuilding(603, 2) + ObjectToBuilding(604, 2) + ObjectToBuilding(605, 2) + ObjectToBuilding(606, 2) + ObjectToBuilding(607, 2) + ObjectToBuilding(610, 2) + ObjectToBuilding(611, 2) + ObjectToBuilding(614, 2) + ObjectToBuilding(619, 2) + ObjectToBuilding(620, 2) + ObjectToBuilding(621, 2) + ObjectToBuilding(622, 2) + ObjectToBuilding(623, 2) + ObjectToBuilding(630, 2) + ObjectToBuilding(631, 2) + ObjectToBuilding(634, 2) + ObjectToBuilding(638, 2) + ObjectToBuilding(642, 2) + ObjectToBuilding(643, 2) + ObjectToBuilding(645, 2) + ObjectToBuilding(646, 2) + ObjectToBuilding(715, 2) + ObjectToBuilding(1177, 2) + ObjectToBuilding(1178, 2) + ObjectToBuilding(1179, 2) + ObjectToBuilding(1180, 2) + ObjectToBuilding(1181, 2) + ObjectToBuilding(1182, 2) + ObjectToBuilding(1183, 2) + ObjectToBuilding(1184, 2) + ObjectToBuilding(1185, 2) + ObjectToBuilding(1186, 2) + ObjectToBuilding(1187, 2) + ObjectToBuilding(1188, 2) + ObjectToBuilding(1564, 2) + ObjectToBuilding(1568, 2) + ObjectToBuilding(1569, 2) + ObjectToBuilding(1570, 2) + ObjectToBuilding(1571, 2) + ObjectToBuilding(1576, 2) + ObjectToBuilding(1577, 2) + ObjectToBuilding(1578, 2) + ObjectToBuilding(2145, 2) + ObjectToBuilding(2146, 2) + ObjectToBuilding(2147, 2) + ObjectToBuilding(2239, 2) + ObjectToBuilding(2244, 2) + ObjectToBuilding(2245, 2) + ObjectToBuilding(2246, 2) + ObjectToBuilding(2248, 2) + ObjectToBuilding(2250, 2) + ObjectToBuilding(2251, 2) + ObjectToBuilding(2253, 2) + ObjectToBuilding(2254, 2) + ObjectToBuilding(2322, 2) + ObjectToBuilding(2323, 2) + ObjectToBuilding(2324, 2) + ObjectToBuilding(2419, 2) + ObjectToBuilding(500, 2) + ObjectToBuilding(501, 2) + TerminalToSpawnPad(224, 501) + TerminalToSpawnPad(2419, 500) + } - LocalBuilding(42, FoundationBuilder(Building.Structure(StructureType.Bunker))) //Anguta, East Bunker(s) - LocalObject(407, Door.Constructor) - LocalObject(408, Door.Constructor) - ObjectToBuilding(407, 42) - ObjectToBuilding(408, 42) + def Building38() : Unit = { + //Anguta, West Bunker + LocalBuilding(38, FoundationBuilder(Building.Structure(StructureType.Bunker))) + LocalObject(362, Door.Constructor) + ObjectToBuilding(362, 38) + } - LocalBuilding(48, FoundationBuilder(Building.Structure(StructureType.Tower, Vector3(3864.2266f, 4518.0234f, 0)))) //North Anguta Watchtower - LocalObject(364, Door.Constructor(Vector3(3871.9688f, 4509.992f, 269.65625f), Vector3(0f, 0f, 180f))) //s1 - LocalObject(365, Door.Constructor(Vector3(3871.9688f, 4509.992f, 279.57812f), Vector3(0f, 0f, 180f))) //s2 - LocalObject(366, Door.Constructor(Vector3(3871.9688f, 4509.992f, 299.57812f), Vector3(0f, 0f, 180f))) //s3 - LocalObject(367, Door.Constructor(Vector3(3871.9688f, 4525.9844f, 269.65625f), Vector3(0f, 0f, 0f))) //n1 - LocalObject(368, Door.Constructor(Vector3(3871.9688f, 4525.9844f, 279.57812f), Vector3(0f, 0f, 0f))) //n2 - LocalObject(369, Door.Constructor(Vector3(3871.9688f, 4525.9844f, 299.57812f), Vector3(0f, 0f, 0f))) //n3 - LocalObject(854, IFFLock.Constructor) - LocalObject(855, IFFLock.Constructor) - LocalObject(856, IFFLock.Constructor) - LocalObject(857, IFFLock.Constructor) - LocalObject(858, IFFLock.Constructor) - LocalObject(859, IFFLock.Constructor) - LocalObject(1140, Locker.Constructor) - LocalObject(1141, Locker.Constructor) - LocalObject(1142, Locker.Constructor) - LocalObject(1143, Locker.Constructor) - LocalObject(1144, Locker.Constructor) - LocalObject(1145, Locker.Constructor) - LocalObject(1146, Locker.Constructor) - LocalObject(1147, Locker.Constructor) - LocalObject(1561, Terminal.Constructor(order_terminal)) - LocalObject(1562, Terminal.Constructor(order_terminal)) - LocalObject(1563, Terminal.Constructor(order_terminal)) - LocalObject(2138, SpawnTube.Constructor(respawn_tube_tower, Vector3(3870.9688f, 4505.7266f, 259.875f), Vector3(0, 0, 90))) - LocalObject(2139, SpawnTube.Constructor(respawn_tube_tower, Vector3(3870.9688f, 4522.1562f, 259.875f), Vector3(0, 0, 90))) - LocalObject(2315, Door.Constructor) //spawn tube door - LocalObject(2316, Door.Constructor) //spawn tube door - ObjectToBuilding(364, 48) - ObjectToBuilding(365, 48) - ObjectToBuilding(366, 48) - ObjectToBuilding(367, 48) - ObjectToBuilding(368, 48) - ObjectToBuilding(369, 48) - ObjectToBuilding(854, 48) - ObjectToBuilding(855, 48) - ObjectToBuilding(856, 48) - ObjectToBuilding(857, 48) - ObjectToBuilding(858, 48) - ObjectToBuilding(859, 48) - ObjectToBuilding(1140, 48) - ObjectToBuilding(1141, 48) - ObjectToBuilding(1142, 48) - ObjectToBuilding(1143, 48) - ObjectToBuilding(1144, 48) - ObjectToBuilding(1145, 48) - ObjectToBuilding(1146, 48) - ObjectToBuilding(1147, 48) - ObjectToBuilding(1561, 48) - ObjectToBuilding(1562, 48) - ObjectToBuilding(1563, 48) - ObjectToBuilding(2138, 48) - ObjectToBuilding(2139, 48) - ObjectToBuilding(2315, 48) - ObjectToBuilding(2316, 48) - DoorToLock(364, 857) - DoorToLock(365, 858) - DoorToLock(366, 859) - DoorToLock(367, 854) - DoorToLock(368, 855) - DoorToLock(369, 856) + def Building42() : Unit = { + //Anguta, East Bunker(s) + LocalBuilding(42, FoundationBuilder(Building.Structure(StructureType.Bunker))) + LocalObject(407, Door.Constructor) + LocalObject(408, Door.Constructor) + ObjectToBuilding(407, 42) + ObjectToBuilding(408, 42) + } + + def Building48() : Unit = { + //North Anguta Watchtower + LocalBuilding(48, FoundationBuilder(Building.Structure(StructureType.Tower, Vector3(3864.2266f, 4518.0234f, 0)))) + LocalObject(364, Door.Constructor(Vector3(3871.9688f, 4509.992f, 269.65625f), Vector3(0f, 0f, 180f))) //s1 + LocalObject(365, Door.Constructor(Vector3(3871.9688f, 4509.992f, 279.57812f), Vector3(0f, 0f, 180f))) //s2 + LocalObject(366, Door.Constructor(Vector3(3871.9688f, 4509.992f, 299.57812f), Vector3(0f, 0f, 180f))) //s3 + LocalObject(367, Door.Constructor(Vector3(3871.9688f, 4525.9844f, 269.65625f), Vector3(0f, 0f, 0f))) //n1 + LocalObject(368, Door.Constructor(Vector3(3871.9688f, 4525.9844f, 279.57812f), Vector3(0f, 0f, 0f))) //n2 + LocalObject(369, Door.Constructor(Vector3(3871.9688f, 4525.9844f, 299.57812f), Vector3(0f, 0f, 0f))) //n3 + LocalObject(854, IFFLock.Constructor) + LocalObject(855, IFFLock.Constructor) + LocalObject(856, IFFLock.Constructor) + LocalObject(857, IFFLock.Constructor) + LocalObject(858, IFFLock.Constructor) + LocalObject(859, IFFLock.Constructor) + LocalObject(1140, Locker.Constructor) + LocalObject(1141, Locker.Constructor) + LocalObject(1142, Locker.Constructor) + LocalObject(1143, Locker.Constructor) + LocalObject(1144, Locker.Constructor) + LocalObject(1145, Locker.Constructor) + LocalObject(1146, Locker.Constructor) + LocalObject(1147, Locker.Constructor) + LocalObject(1561, Terminal.Constructor(order_terminal)) + LocalObject(1562, Terminal.Constructor(order_terminal)) + LocalObject(1563, Terminal.Constructor(order_terminal)) + LocalObject(2138, SpawnTube.Constructor(respawn_tube_tower, Vector3(3870.9688f, 4505.7266f, 259.875f), Vector3(0, 0, 90))) + LocalObject(2139, SpawnTube.Constructor(respawn_tube_tower, Vector3(3870.9688f, 4522.1562f, 259.875f), Vector3(0, 0, 90))) + LocalObject(2315, Door.Constructor) //spawn tube door + LocalObject(2316, Door.Constructor) //spawn tube door + ObjectToBuilding(364, 48) + ObjectToBuilding(365, 48) + ObjectToBuilding(366, 48) + ObjectToBuilding(367, 48) + ObjectToBuilding(368, 48) + ObjectToBuilding(369, 48) + ObjectToBuilding(854, 48) + ObjectToBuilding(855, 48) + ObjectToBuilding(856, 48) + ObjectToBuilding(857, 48) + ObjectToBuilding(858, 48) + ObjectToBuilding(859, 48) + ObjectToBuilding(1140, 48) + ObjectToBuilding(1141, 48) + ObjectToBuilding(1142, 48) + ObjectToBuilding(1143, 48) + ObjectToBuilding(1144, 48) + ObjectToBuilding(1145, 48) + ObjectToBuilding(1146, 48) + ObjectToBuilding(1147, 48) + ObjectToBuilding(1561, 48) + ObjectToBuilding(1562, 48) + ObjectToBuilding(1563, 48) + ObjectToBuilding(2138, 48) + ObjectToBuilding(2139, 48) + ObjectToBuilding(2315, 48) + ObjectToBuilding(2316, 48) + DoorToLock(364, 857) + DoorToLock(365, 858) + DoorToLock(366, 859) + DoorToLock(367, 854) + DoorToLock(368, 855) + DoorToLock(369, 856) + } + + def Building49() : Unit = { + //North Akna Air Tower + LocalBuilding(49, FoundationBuilder(Building.Structure(StructureType.Tower, Vector3(3864.2266f, 4518.0234f, 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 + LocalObject(433, Door.Constructor(Vector3(4366.0156f, 3997.9297f, 257.89062f), Vector3(0f, 0f, 0f))) //n2 + LocalObject(902, IFFLock.Constructor) + LocalObject(903, IFFLock.Constructor) + LocalObject(906, IFFLock.Constructor) + LocalObject(907, IFFLock.Constructor) + LocalObject(1217, Locker.Constructor) + LocalObject(1218, Locker.Constructor) + LocalObject(1219, Locker.Constructor) + LocalObject(1220, Locker.Constructor) + LocalObject(1225, Locker.Constructor) + LocalObject(1226, Locker.Constructor) + LocalObject(1227, Locker.Constructor) + LocalObject(1228, Locker.Constructor) + LocalObject(1591, Terminal.Constructor(order_terminal)) + LocalObject(1592, Terminal.Constructor(order_terminal)) + LocalObject(1593, Terminal.Constructor(order_terminal)) + LocalObject(2156, SpawnTube.Constructor(respawn_tube_tower, Vector3(4364.633f, 3994.125f, 228.1875f), Vector3(0, 0, 90))) + LocalObject(2157, SpawnTube.Constructor(respawn_tube_tower, Vector3(4364.633f, 3977.7266f, 228.1875f), Vector3(0, 0, 90))) + LocalObject(2333, Door.Constructor) //spawn tube door + LocalObject(2334, Door.Constructor) //spawn tube door + ObjectToBuilding(430, 49) + ObjectToBuilding(431, 49) + ObjectToBuilding(432, 49) + ObjectToBuilding(433, 49) + ObjectToBuilding(902, 49) + ObjectToBuilding(903, 49) + ObjectToBuilding(906, 49) + ObjectToBuilding(907, 49) + ObjectToBuilding(1217, 49) + ObjectToBuilding(1218, 49) + ObjectToBuilding(1219, 49) + ObjectToBuilding(1220, 49) + ObjectToBuilding(1225, 49) + ObjectToBuilding(1226, 49) + ObjectToBuilding(1227, 49) + ObjectToBuilding(1228, 49) + ObjectToBuilding(1591, 49) + ObjectToBuilding(1592, 49) + ObjectToBuilding(1593, 49) + ObjectToBuilding(2333, 49) + ObjectToBuilding(2334, 49) + DoorToLock(430, 906) + DoorToLock(431, 907) + DoorToLock(432, 902) + DoorToLock(433, 903) + } } val map7 = new ZoneMap("map07") @@ -279,203 +350,227 @@ object Maps { val map12 = new ZoneMap("map12") val map13 = new ZoneMap("map13") { - LocalBuilding(1, FoundationBuilder(WarpGate.Structure)) - LocalBuilding(2, FoundationBuilder(WarpGate.Structure)) - LocalBuilding(3, FoundationBuilder(WarpGate.Structure)) + Building1() + Building2() + Building3() + Building29() + Building42() + Building51() + Building77() - LocalObject(520, ImplantTerminalMech.Constructor) //Hart B - LocalObject(1081, Terminal.Constructor(implant_terminal_interface)) //tube 520 - TerminalToInterface(520, 1081) + def Building1() : Unit = { + LocalBuilding(1, FoundationBuilder(WarpGate.Structure)) + } - LocalBuilding(2, FoundationBuilder(Building.Structure(StructureType.Building))) //HART building C - LocalObject(186, Terminal.Constructor(cert_terminal)) - LocalObject(187, Terminal.Constructor(cert_terminal)) - LocalObject(188, Terminal.Constructor(cert_terminal)) - LocalObject(362, Door.Constructor) - LocalObject(370, Door.Constructor) - LocalObject(371, Door.Constructor) - LocalObject(374, Door.Constructor) - LocalObject(375, Door.Constructor) - LocalObject(394, Door.Constructor) - LocalObject(395, Door.Constructor) - LocalObject(396, Door.Constructor) - LocalObject(397, Door.Constructor) - LocalObject(398, Door.Constructor) - LocalObject(462, Door.Constructor) - LocalObject(463, Door.Constructor) - LocalObject(522, ImplantTerminalMech.Constructor) - LocalObject(523, ImplantTerminalMech.Constructor) - LocalObject(524, ImplantTerminalMech.Constructor) - LocalObject(525, ImplantTerminalMech.Constructor) - LocalObject(526, ImplantTerminalMech.Constructor) - LocalObject(527, ImplantTerminalMech.Constructor) - LocalObject(528, ImplantTerminalMech.Constructor) - LocalObject(529, ImplantTerminalMech.Constructor) - LocalObject(686, Locker.Constructor) - LocalObject(687, Locker.Constructor) - LocalObject(688, Locker.Constructor) - LocalObject(689, Locker.Constructor) - LocalObject(690, Locker.Constructor) - LocalObject(691, Locker.Constructor) - LocalObject(692, Locker.Constructor) - LocalObject(693, Locker.Constructor) - LocalObject(842, Terminal.Constructor(order_terminal)) - LocalObject(843, Terminal.Constructor(order_terminal)) - LocalObject(844, Terminal.Constructor(order_terminal)) - LocalObject(845, Terminal.Constructor(order_terminal)) - LocalObject(1082, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct - LocalObject(1083, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct - LocalObject(1084, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct - LocalObject(1085, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct - LocalObject(1086, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct - LocalObject(1087, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct - LocalObject(1088, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct - LocalObject(1089, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct - ObjectToBuilding(186, 2) - ObjectToBuilding(187, 2) - ObjectToBuilding(188, 2) - ObjectToBuilding(362, 2) - ObjectToBuilding(370, 2) - ObjectToBuilding(371, 2) - ObjectToBuilding(374, 2) - ObjectToBuilding(375, 2) - ObjectToBuilding(394, 2) - ObjectToBuilding(395, 2) - ObjectToBuilding(396, 2) - ObjectToBuilding(397, 2) - ObjectToBuilding(398, 2) - ObjectToBuilding(462, 2) - ObjectToBuilding(463, 2) - ObjectToBuilding(522, 2) - ObjectToBuilding(523, 2) - ObjectToBuilding(524, 2) - ObjectToBuilding(525, 2) - ObjectToBuilding(526, 2) - ObjectToBuilding(527, 2) - ObjectToBuilding(528, 2) - ObjectToBuilding(529, 2) - ObjectToBuilding(686, 2) - ObjectToBuilding(687, 2) - ObjectToBuilding(688, 2) - ObjectToBuilding(689, 2) - ObjectToBuilding(690, 2) - ObjectToBuilding(691, 2) - ObjectToBuilding(692, 2) - ObjectToBuilding(693, 2) - ObjectToBuilding(842, 2) - ObjectToBuilding(843, 2) - ObjectToBuilding(844, 2) - ObjectToBuilding(845, 2) - ObjectToBuilding(1082, 2) - ObjectToBuilding(1083, 2) - ObjectToBuilding(1084, 2) - ObjectToBuilding(1085, 2) - ObjectToBuilding(1086, 2) - ObjectToBuilding(1087, 2) - ObjectToBuilding(1088, 2) - ObjectToBuilding(1089, 2) - TerminalToInterface(522, 1082) - TerminalToInterface(523, 1083) - TerminalToInterface(524, 1084) - TerminalToInterface(525, 1085) - TerminalToInterface(526, 1086) - TerminalToInterface(527, 1087) - TerminalToInterface(528, 1088) - TerminalToInterface(529, 1089) + def Building3() : Unit = { + LocalBuilding(3, FoundationBuilder(WarpGate.Structure)) + } - LocalBuilding(29, FoundationBuilder(Building.Structure(StructureType.Tower))) //South Villa Gun 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))) - LocalObject(333, Door.Constructor(Vector3(3979.9688f, 2608.0625f, 111.140625f), Vector3(0, 0, 0))) - LocalObject(556, IFFLock.Constructor) - LocalObject(557, IFFLock.Constructor) - LocalObject(558, IFFLock.Constructor) - LocalObject(559, IFFLock.Constructor) - ObjectToBuilding(330, 29) - ObjectToBuilding(331, 29) - ObjectToBuilding(332, 29) - ObjectToBuilding(333, 29) - ObjectToBuilding(556, 29) - ObjectToBuilding(557, 29) - ObjectToBuilding(558, 29) - ObjectToBuilding(559, 29) - DoorToLock(330, 558) - DoorToLock(331, 559) - DoorToLock(332, 556) - DoorToLock(333, 557) +// LocalBuilding(2, FoundationBuilder(WarpGate.Structure)) //TODO might be wrong? - LocalBuilding(42, FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1, 0, 0)))) //spawn building south of HART C - LocalObject(258, Door.Constructor) //spawn tube door - LocalObject(259, Door.Constructor) //spawn tube door - LocalObject(260, Door.Constructor) //spawn tube door - LocalObject(261, Door.Constructor) //spawn tube door - LocalObject(262, Door.Constructor) //spawn tube door - LocalObject(263, Door.Constructor) //spawn tube door - LocalObject(372, Door.Constructor) //entrance - LocalObject(373, Door.Constructor) //entrance - LocalObject(430, Door.Constructor) //vr door - LocalObject(431, Door.Constructor) //vr door - LocalObject(432, Door.Constructor) //vr door - 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(852, Terminal.Constructor(order_terminal)) //s. wall - LocalObject(853, Terminal.Constructor(order_terminal)) //n. wall - LocalObject(854, Terminal.Constructor(order_terminal)) //s. wall - LocalObject(855, Terminal.Constructor(order_terminal)) //n. wall - LocalObject(859, Terminal.Constructor(order_terminal)) //s. wall - LocalObject(860, Terminal.Constructor(order_terminal)) //n. wall - ObjectToBuilding(258, 42) - ObjectToBuilding(259, 42) - ObjectToBuilding(260, 42) - ObjectToBuilding(261, 42) - ObjectToBuilding(262, 42) - ObjectToBuilding(263, 42) - ObjectToBuilding(372, 42) - ObjectToBuilding(373, 42) - ObjectToBuilding(430, 42) - ObjectToBuilding(431, 42) - ObjectToBuilding(432, 42) - ObjectToBuilding(433, 42) - ObjectToBuilding(434, 42) - ObjectToBuilding(435, 42) - ObjectToBuilding(744, 42) - ObjectToBuilding(745, 42) - ObjectToBuilding(746, 42) - ObjectToBuilding(747, 42) - ObjectToBuilding(748, 42) - ObjectToBuilding(749, 42) - ObjectToBuilding(852, 42) - ObjectToBuilding(853, 42) - ObjectToBuilding(854, 42) - ObjectToBuilding(855, 42) - ObjectToBuilding(859, 42) - ObjectToBuilding(860, 42) +// LocalObject(520, ImplantTerminalMech.Constructor) //Hart B +// LocalObject(1081, Terminal.Constructor(implant_terminal_interface)) //tube 520 +// TerminalToInterface(520, 1081) - LocalBuilding(51, FoundationBuilder(Building.Structure(StructureType.Platform))) //air terminal west of HART C - LocalObject(304, Terminal.Constructor(dropship_vehicle_terminal)) - LocalObject(292, - VehicleSpawnPad.Constructor(Vector3(3508.9844f, 2895.961f, 92.296875f), Vector3(0f, 0f, 270.0f)) - ) - ObjectToBuilding(304, 51) - ObjectToBuilding(292, 51) - TerminalToSpawnPad(304, 292) + def Building2() : Unit = { + LocalBuilding(2, FoundationBuilder(Building.Structure(StructureType.Building))) //HART building C + LocalObject(186, Terminal.Constructor(cert_terminal)) + LocalObject(187, Terminal.Constructor(cert_terminal)) + LocalObject(188, Terminal.Constructor(cert_terminal)) + LocalObject(362, Door.Constructor) + LocalObject(370, Door.Constructor) + LocalObject(371, Door.Constructor) + LocalObject(374, Door.Constructor) + LocalObject(375, Door.Constructor) + LocalObject(394, Door.Constructor) + LocalObject(395, Door.Constructor) + LocalObject(396, Door.Constructor) + LocalObject(397, Door.Constructor) + LocalObject(398, Door.Constructor) + LocalObject(462, Door.Constructor) + LocalObject(463, Door.Constructor) + LocalObject(522, ImplantTerminalMech.Constructor) + LocalObject(523, ImplantTerminalMech.Constructor) + LocalObject(524, ImplantTerminalMech.Constructor) + LocalObject(525, ImplantTerminalMech.Constructor) + LocalObject(526, ImplantTerminalMech.Constructor) + LocalObject(527, ImplantTerminalMech.Constructor) + LocalObject(528, ImplantTerminalMech.Constructor) + LocalObject(529, ImplantTerminalMech.Constructor) + LocalObject(686, Locker.Constructor) + LocalObject(687, Locker.Constructor) + LocalObject(688, Locker.Constructor) + LocalObject(689, Locker.Constructor) + LocalObject(690, Locker.Constructor) + LocalObject(691, Locker.Constructor) + LocalObject(692, Locker.Constructor) + LocalObject(693, Locker.Constructor) + LocalObject(842, Terminal.Constructor(order_terminal)) + LocalObject(843, Terminal.Constructor(order_terminal)) + LocalObject(844, Terminal.Constructor(order_terminal)) + LocalObject(845, Terminal.Constructor(order_terminal)) + LocalObject(1082, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct + LocalObject(1083, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct + LocalObject(1084, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct + LocalObject(1085, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct + LocalObject(1086, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct + LocalObject(1087, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct + LocalObject(1088, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct + LocalObject(1089, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct + ObjectToBuilding(186, 2) + ObjectToBuilding(187, 2) + ObjectToBuilding(188, 2) + ObjectToBuilding(362, 2) + ObjectToBuilding(370, 2) + ObjectToBuilding(371, 2) + ObjectToBuilding(374, 2) + ObjectToBuilding(375, 2) + ObjectToBuilding(394, 2) + ObjectToBuilding(395, 2) + ObjectToBuilding(396, 2) + ObjectToBuilding(397, 2) + ObjectToBuilding(398, 2) + ObjectToBuilding(462, 2) + ObjectToBuilding(463, 2) + ObjectToBuilding(522, 2) + ObjectToBuilding(523, 2) + ObjectToBuilding(524, 2) + ObjectToBuilding(525, 2) + ObjectToBuilding(526, 2) + ObjectToBuilding(527, 2) + ObjectToBuilding(528, 2) + ObjectToBuilding(529, 2) + ObjectToBuilding(686, 2) + ObjectToBuilding(687, 2) + ObjectToBuilding(688, 2) + ObjectToBuilding(689, 2) + ObjectToBuilding(690, 2) + ObjectToBuilding(691, 2) + ObjectToBuilding(692, 2) + ObjectToBuilding(693, 2) + ObjectToBuilding(842, 2) + ObjectToBuilding(843, 2) + ObjectToBuilding(844, 2) + ObjectToBuilding(845, 2) + ObjectToBuilding(1082, 2) + ObjectToBuilding(1083, 2) + ObjectToBuilding(1084, 2) + ObjectToBuilding(1085, 2) + ObjectToBuilding(1086, 2) + ObjectToBuilding(1087, 2) + ObjectToBuilding(1088, 2) + ObjectToBuilding(1089, 2) + TerminalToInterface(522, 1082) + TerminalToInterface(523, 1083) + TerminalToInterface(524, 1084) + TerminalToInterface(525, 1085) + TerminalToInterface(526, 1086) + TerminalToInterface(527, 1087) + TerminalToInterface(528, 1088) + TerminalToInterface(529, 1089) + } - LocalBuilding(77, FoundationBuilder(Building.Structure(StructureType.Platform))) //ground terminal west of HART C - LocalObject(1063, Terminal.Constructor(ground_vehicle_terminal)) - LocalObject(706, - VehicleSpawnPad.Constructor(Vector3(3506.0f, 2820.0f, 92.0f), Vector3(0f, 0f, 270.0f)) - ) - ObjectToBuilding(1063, 77) - ObjectToBuilding(706, 77) - TerminalToSpawnPad(1063, 706) + def Building29() : Unit = { + LocalBuilding(29, FoundationBuilder(Building.Structure(StructureType.Tower))) //South Villa Gun 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))) + LocalObject(333, Door.Constructor(Vector3(3979.9688f, 2608.0625f, 111.140625f), Vector3(0, 0, 0))) + LocalObject(556, IFFLock.Constructor) + LocalObject(557, IFFLock.Constructor) + LocalObject(558, IFFLock.Constructor) + LocalObject(559, IFFLock.Constructor) + ObjectToBuilding(330, 29) + ObjectToBuilding(331, 29) + ObjectToBuilding(332, 29) + ObjectToBuilding(333, 29) + ObjectToBuilding(556, 29) + ObjectToBuilding(557, 29) + ObjectToBuilding(558, 29) + ObjectToBuilding(559, 29) + DoorToLock(330, 558) + DoorToLock(331, 559) + DoorToLock(332, 556) + DoorToLock(333, 557) + } + + def Building42() : Unit = { + LocalBuilding(42, FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1, 0, 0)))) //spawn building south of HART C + LocalObject(258, Door.Constructor) //spawn tube door + LocalObject(259, Door.Constructor) //spawn tube door + LocalObject(260, Door.Constructor) //spawn tube door + LocalObject(261, Door.Constructor) //spawn tube door + LocalObject(262, Door.Constructor) //spawn tube door + LocalObject(263, Door.Constructor) //spawn tube door + LocalObject(372, Door.Constructor) //entrance + LocalObject(373, Door.Constructor) //entrance + LocalObject(430, Door.Constructor) //vr door + LocalObject(431, Door.Constructor) //vr door + LocalObject(432, Door.Constructor) //vr door + 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(852, Terminal.Constructor(order_terminal)) //s. wall + LocalObject(853, Terminal.Constructor(order_terminal)) //n. wall + LocalObject(854, Terminal.Constructor(order_terminal)) //s. wall + LocalObject(855, Terminal.Constructor(order_terminal)) //n. wall + LocalObject(859, Terminal.Constructor(order_terminal)) //s. wall + LocalObject(860, Terminal.Constructor(order_terminal)) //n. wall + ObjectToBuilding(258, 42) + ObjectToBuilding(259, 42) + ObjectToBuilding(260, 42) + ObjectToBuilding(261, 42) + ObjectToBuilding(262, 42) + ObjectToBuilding(263, 42) + ObjectToBuilding(372, 42) + ObjectToBuilding(373, 42) + ObjectToBuilding(430, 42) + ObjectToBuilding(431, 42) + ObjectToBuilding(432, 42) + ObjectToBuilding(433, 42) + ObjectToBuilding(434, 42) + ObjectToBuilding(435, 42) + ObjectToBuilding(744, 42) + ObjectToBuilding(745, 42) + ObjectToBuilding(746, 42) + ObjectToBuilding(747, 42) + ObjectToBuilding(748, 42) + ObjectToBuilding(749, 42) + ObjectToBuilding(852, 42) + ObjectToBuilding(853, 42) + ObjectToBuilding(854, 42) + ObjectToBuilding(855, 42) + ObjectToBuilding(859, 42) + ObjectToBuilding(860, 42) + } + + def Building51() : Unit = { + LocalBuilding(51, FoundationBuilder(Building.Structure(StructureType.Platform))) //air terminal west of HART C + LocalObject(304, Terminal.Constructor(dropship_vehicle_terminal)) + LocalObject(292, + VehicleSpawnPad.Constructor(Vector3(3508.9844f, 2895.961f, 92.296875f), Vector3(0f, 0f, 270.0f)) + ) + ObjectToBuilding(304, 51) + ObjectToBuilding(292, 51) + TerminalToSpawnPad(304, 292) + } + + def Building77() : Unit = { + LocalBuilding(77, FoundationBuilder(Building.Structure(StructureType.Platform))) //ground terminal west of HART C + LocalObject(1063, Terminal.Constructor(ground_vehicle_terminal)) + LocalObject(706, + VehicleSpawnPad.Constructor(Vector3(3506.0f, 2820.0f, 92.0f), Vector3(0f, 0f, 270.0f)) + ) + ObjectToBuilding(1063, 77) + ObjectToBuilding(706, 77) + TerminalToSpawnPad(1063, 706) + } } val map14 = new ZoneMap("map13") diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 129afc99b..6d24c17c7 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -72,8 +72,8 @@ class WorldSessionActor extends Actor with MDCContextAware { override def postStop() = { clientKeepAlive.cancel - progressBarUpdate.cancel reviveTimer.cancel + PlayerActionsToCancel() //progressBarUpdate.cancel localService ! Service.Leave() vehicleService ! Service.Leave() avatarService ! Service.Leave() @@ -102,7 +102,7 @@ class WorldSessionActor extends Actor with MDCContextAware { continent.Population ! Zone.Population.Release(avatar) continent.Population ! Zone.Population.Leave(avatar) 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 } } @@ -1299,8 +1299,9 @@ class WorldSessionActor extends Actor with MDCContextAware { AwardBattleExperiencePoints(avatar, 1000000L) player = new Player(avatar) //player.Position = Vector3(3561.0f, 2854.0f, 90.859375f) //home3, HART C - player.Position = Vector3(3881.9688f, 4432.008f, 267.0f) //z6, Anguta / n.tower - player.Orientation = Vector3(0f, 0f, 90f) + //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.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 @@ -1350,7 +1351,7 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ContinentalLockUpdateMessage(13, PlanetSideEmpire.VS)) // "The VS have captured the VS Sanctuary." sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1)) //common - (0 to 255).foreach(i => { sendResponse(SetEmpireMessage(PlanetSideGUID(i), PlanetSideEmpire.VS)) }) + //(0 to 255).foreach(i => { sendResponse(SetEmpireMessage(PlanetSideGUID(i), PlanetSideEmpire.VS)) }) //render Equipment that was dropped into zone before the player arrived continent.EquipmentOnGround.foreach(item => { @@ -1899,7 +1900,8 @@ class WorldSessionActor extends Actor with MDCContextAware { continent.GUID(object_guid) match { case Some(vehicle : Vehicle) => if((player.VehicleOwned.contains(object_guid) && vehicle.Owner.contains(player.GUID)) - || (player.Faction == vehicle.Faction && (vehicle.Owner.isEmpty || vehicle.Health == 0))) { + || (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") diff --git a/pslogin/src/main/scala/Zones.scala b/pslogin/src/main/scala/Zones.scala index 6cf61e332..a59db3586 100644 --- a/pslogin/src/main/scala/Zones.scala +++ b/pslogin/src/main/scala/Zones.scala @@ -25,6 +25,8 @@ object Zones { Building(42).get.ModelId = 0 Building(48).get.Faction = PlanetSideEmpire.VS Building(48).get.ModelId = 59 + Building(49).get.Faction = PlanetSideEmpire.VS + Building(49).get.ModelId = 69 } } From f444a35785d16df2d9f39080c4dcfed2a89d0693 Mon Sep 17 00:00:00 2001 From: FateJH Date: Sat, 31 Mar 2018 19:31:17 -0400 Subject: [PATCH 7/9] Changes to AvatarService packets and support actors in regards to corpse management and tests regarding corpse management. Seriously, those tests. --- .../psforever/objects/GlobalDefinitions.scala | 11 + .../converter/CorpseConverter.scala | 4 +- common/src/test/scala/objects/ZoneTest.scala | 56 +++- pslogin/src/main/scala/Maps.scala | 111 +++++--- .../src/main/scala/WorldSessionActor.scala | 98 ++++++- .../scala/services/avatar/AvatarAction.scala | 2 +- .../scala/services/avatar/AvatarService.scala | 16 +- .../avatar/AvatarServiceMessage.scala | 6 + .../avatar/support/CorpseRemovalActor.scala | 199 ++++++++++++++ .../avatar/support/UndertakerActor.scala | 145 ----------- .../vehicle/support/DeconstructionActor.scala | 2 + .../src/test/scala/AvatarServiceTest.scala | 242 ++++++++++++++++-- 12 files changed, 672 insertions(+), 220 deletions(-) create mode 100644 pslogin/src/main/scala/services/avatar/support/CorpseRemovalActor.scala delete mode 100644 pslogin/src/main/scala/services/avatar/support/UndertakerActor.scala diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 1cc05ec0f..db4c14fef 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -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 diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala index 73699a10b..68b7df90d 100644 --- a/common/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala +++ b/common/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala @@ -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, diff --git a/common/src/test/scala/objects/ZoneTest.scala b/common/src/test/scala/objects/ZoneTest.scala index 49fe866be..2c5cfcb69 100644 --- a/common/src/test/scala/objects/ZoneTest.scala +++ b/common/src/test/scala/objects/ZoneTest.scala @@ -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 = { diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala index 9b64ad924..ea3cfc2de 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/pslogin/src/main/scala/Maps.scala @@ -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)) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 6d24c17c7..ddf16df90 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -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, diff --git a/pslogin/src/main/scala/services/avatar/AvatarAction.scala b/pslogin/src/main/scala/services/avatar/AvatarAction.scala index 54c5725f4..80d49828d 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarAction.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarAction.scala @@ -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 diff --git a/pslogin/src/main/scala/services/avatar/AvatarService.scala b/pslogin/src/main/scala/services/avatar/AvatarService.scala index 2d8f0f8c9..2978f1a11 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarService.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarService.scala @@ -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}") diff --git a/pslogin/src/main/scala/services/avatar/AvatarServiceMessage.scala b/pslogin/src/main/scala/services/avatar/AvatarServiceMessage.scala index e3e35cd38..04b96a901 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarServiceMessage.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarServiceMessage.scala @@ -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]) +} diff --git a/pslogin/src/main/scala/services/avatar/support/CorpseRemovalActor.scala b/pslogin/src/main/scala/services/avatar/support/CorpseRemovalActor.scala new file mode 100644 index 000000000..ace1fc920 --- /dev/null +++ b/pslogin/src/main/scala/services/avatar/support/CorpseRemovalActor.scala @@ -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) + } + } + } +} diff --git a/pslogin/src/main/scala/services/avatar/support/UndertakerActor.scala b/pslogin/src/main/scala/services/avatar/support/UndertakerActor.scala deleted file mode 100644 index 83788a174..000000000 --- a/pslogin/src/main/scala/services/avatar/support/UndertakerActor.scala +++ /dev/null @@ -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 -} diff --git a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala index adc9668a2..2aaefff4d 100644 --- a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala +++ b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala @@ -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 diff --git a/pslogin/src/test/scala/AvatarServiceTest.scala b/pslogin/src/test/scala/AvatarServiceTest.scala index 94d5a1470..deda78b11 100644 --- a/pslogin/src/test/scala/AvatarServiceTest.scala +++ b/pslogin/src/test/scala/AvatarServiceTest.scala @@ -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()}" + } } From 82c68ebedba6aa8e291b759b6830632fcf90ffc9 Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 9 Apr 2018 19:12:23 -0400 Subject: [PATCH 8/9] Modifying AvatarDeadStateMessage to manipulate visible respawn points on deployment mpa; improving postStop conditions on WSA to consider player in different situations; added basic Zone.Population.(message) case statements --- .../net/psforever/objects/zones/Zone.scala | 2 +- .../objects/zones/ZonePopulationActor.scala | 2 +- .../packet/game/AvatarDeadStateMessage.scala | 64 +++++++++++-- .../game/AvatarDeadStateMessageTest.scala | 11 ++- .../src/main/scala/WorldSessionActor.scala | 89 +++++++++++++++---- 5 files changed, 136 insertions(+), 32 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala index 37254fb70..5104c7dc7 100644 --- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -359,7 +359,7 @@ object Zone { * Message that acts in reply to `Spawn(avatar, player)`, but the avatar already has a player. * @param player the `Player` object */ - final case class PlayerAlreadySpawned(player : Player) + final case class PlayerAlreadySpawned(zone : Zone, player : Player) /** * Message that acts in reply to `Spawn(avatar, player)`, but the avatar did not initially `Join` this zone. * @param zone the `Zone` object diff --git a/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala index 76840df88..a22e9b431 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala @@ -35,7 +35,7 @@ class ZonePopulationActor(zone : Zone, playerMap : TrieMap[Avatar, Option[Player PopulationSpawn(avatar, player, playerMap) match { case Some(tplayer) => if(tplayer ne player) { - sender ! Zone.Population.PlayerAlreadySpawned(player) + sender ! Zone.Population.PlayerAlreadySpawned(zone, player) } case None => sender ! Zone.Population.PlayerCanNotSpawn(zone, player) diff --git a/common/src/main/scala/net/psforever/packet/game/AvatarDeadStateMessage.scala b/common/src/main/scala/net/psforever/packet/game/AvatarDeadStateMessage.scala index 4b7ad326d..5e17f544a 100644 --- a/common/src/main/scala/net/psforever/packet/game/AvatarDeadStateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/AvatarDeadStateMessage.scala @@ -2,15 +2,19 @@ package net.psforever.packet.game import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} -import net.psforever.types.Vector3 -import scodec.Codec +import net.psforever.types.{PlanetSideEmpire, Vector3} +import scodec.Attempt.{Failure, Successful} +import scodec.{Codec, Err} import scodec.codecs._ +/** + * An `Enumeration` of the various states a `Player` may possess in the cycle of nanite life and death. + */ object DeadState extends Enumeration { type Type = Value val - Nothing, + Alive, Dead, Release, RespawnTime @@ -20,19 +24,41 @@ object DeadState extends Enumeration { } /** - * na - * @param state avatar's relationship with the world + * Dispatched by the server to manipulate the client's management of the `Player` object owned by the user as his "avatar."
+ *
+ * The cycle of a player is generally `Alive` to `Dead` and `Dead` to `Release` and `Release` to `RespawnTimer` to `Alive`. + * When deconstructing oneself, the user makes a jump between `Alive` and `Release`; + * and, he may make a further jump from `Release` to `Alive` depending on spawning choices. + * Being `Alive` is the most common state. + * (Despite what anyone says.) + * Being `Dead` is just a technical requirement to initialize the revive timer. + * The player should be sufficiently "dead" by having his health points decreased to zero. + * If the timer is reduced to zero, the player is sent back to their faction-appropriate sanctuary continent.
+ *
+ * `Release` causes a "dead" player to have its character model converted into a backpack or a form of pastry. + * This cancels the revival timer - the player may no longer be revived - and brings the user to the deployment map. + * From the deployment map, the user may select a place where they may respawn a new character. + * The options available form this spawn are not only related to the faction affinity of the bases compared to the user's player(s) + * but also to the field `faction` as is provided in the packet. + * If the player is converted to a state of `Release` while being alive, the deployment map is still displayed. + * Their character model is not replaced by a backpack or pastry.
+ *
+ * `RespawnTimer` is like `Dead` as it is just a formal distinction to cause the client to display a timer. + * The state indicates that the player is being resurrected at a previously-selected location in the state `Alive`. + * @param state avatar's mortal relationship with the world; + * the following timers are applicable during `Death` and `RespawnTimer`; + * `faction` is applicable mainly during `Release` * @param timer_max total length of respawn countdown, in milliseconds * @param timer initial length of the respawn timer, in milliseconds - * @param pos last position - * @param unk4 na + * @param pos player's last position + * @param faction spawn points available for this faction on redeployment map * @param unk5 na */ final case class AvatarDeadStateMessage(state : DeadState.Value, timer_max : Long, timer : Long, pos : Vector3, - unk4 : Long, + faction : PlanetSideEmpire.Value, unk5 : Boolean) extends PlanetSideGamePacket { type Packet = AvatarDeadStateMessage @@ -41,12 +67,32 @@ final case class AvatarDeadStateMessage(state : DeadState.Value, } object AvatarDeadStateMessage extends Marshallable[AvatarDeadStateMessage] { + /** + * allocate all values from the `PlanetSideEmpire` `Enumeration` + */ + private val factionLongValues = PlanetSideEmpire.values map { _.id.toLong } + + /** + * `Codec` for converting between the limited `PlanetSideEmpire` `Enumeration` and a `Long` value. + */ + private val factionLongCodec = uint32L.exmap[PlanetSideEmpire.Value] ( + fv => + if(factionLongValues.contains(fv)) { + Successful(PlanetSideEmpire(fv.toInt)) + } + else { + Failure(Err(s"$fv is not mapped to a PlanetSideEmpire value")) + }, + f => + Successful(f.id.toLong) + ) + implicit val codec : Codec[AvatarDeadStateMessage] = ( ("state" | DeadState.codec) :: ("timer_max" | uint32L) :: ("timer" | uint32L) :: ("pos" | Vector3.codec_pos) :: - ("unk4" | uint32L) :: + ("unk4" | factionLongCodec) :: ("unk5" | bool) ).as[AvatarDeadStateMessage] } diff --git a/common/src/test/scala/game/AvatarDeadStateMessageTest.scala b/common/src/test/scala/game/AvatarDeadStateMessageTest.scala index ef8747279..f1db1008f 100644 --- a/common/src/test/scala/game/AvatarDeadStateMessageTest.scala +++ b/common/src/test/scala/game/AvatarDeadStateMessageTest.scala @@ -4,11 +4,12 @@ package game import org.specs2.mutable._ import net.psforever.packet._ import net.psforever.packet.game._ -import net.psforever.types.Vector3 +import net.psforever.types.{PlanetSideEmpire, Vector3} import scodec.bits._ class AvatarDeadStateMessageTest extends Specification { val string = hex"ad3c1260801c12608009f99861fb0741e040000010" + val string_invalid = hex"ad3c1260801c12608009f99861fb0741e0400000F0" "decode" in { PacketCoding.DecodePacket(string).require match { @@ -17,15 +18,19 @@ class AvatarDeadStateMessageTest extends Specification { unk2 mustEqual 300000 unk3 mustEqual 300000 pos mustEqual Vector3(6552.617f,4602.375f,60.90625f) - unk4 mustEqual 2 + unk4 mustEqual PlanetSideEmpire.VS unk5 mustEqual true case _ => ko } } + "decode (failure)" in { + PacketCoding.DecodePacket(string_invalid).isFailure mustEqual true + } + "encode" in { - val msg = AvatarDeadStateMessage(DeadState.Dead, 300000, 300000, Vector3(6552.617f,4602.375f,60.90625f), 2, true) + val msg = AvatarDeadStateMessage(DeadState.Dead, 300000, 300000, Vector3(6552.617f,4602.375f,60.90625f), PlanetSideEmpire.VS, true) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index ddf16df90..28a66dd80 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -56,7 +56,7 @@ class WorldSessionActor extends Actor with MDCContextAware { var vehicleService : ActorRef = ActorRef.noSender var taskResolver : ActorRef = Actor.noSender var galaxy : ActorRef = Actor.noSender - var continent : Zone = null + var continent : Zone = Zone.Nowhere var player : Player = null var avatar : Avatar = null var progressBarValue : Option[Float] = None @@ -74,7 +74,7 @@ class WorldSessionActor extends Actor with MDCContextAware { override def postStop() = { clientKeepAlive.cancel reviveTimer.cancel - PlayerActionsToCancel() //progressBarUpdate.cancel + PlayerActionsToCancel() localService ! Service.Leave() vehicleService ! Service.Leave() avatarService ! Service.Leave() @@ -82,12 +82,49 @@ class WorldSessionActor extends Actor with MDCContextAware { LivePlayerList.Remove(sessionId) if(player != null && player.HasGUID) { val player_guid = player.GUID - player.VehicleSeated match { - case Some(vehicle_guid) => - //TODO do this at some other time - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, 0, true, vehicle_guid)) - case None => ; + if(player.isAlive) { + //actually being alive or manually deconstructing + player.VehicleSeated match { + case Some(vehicle_guid) => + DismountVehicleOnLogOut(vehicle_guid, player_guid) + case None => ; + } + + continent.Population ! Zone.Population.Release(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) + //TODO normally, the actual player avatar persists a minute or so after the user disconnects } + else if(continent.LivePlayers.contains(player) && !continent.Corpses.contains(player)) { + //player disconnected while waiting for a revive + //similar to handling ReleaseAvatarRequestMessage + player.Release + continent.Population ! Zone.Population.Release(avatar) + player.VehicleSeated match { + case None => + continent.Population ! Zone.Corpse.Add(player) + FriskCorpse(player) //TODO eliminate dead letters + if(!WellLootedCorpse(player)) { + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.Release(player, continent)) + taskResolver ! GUIDTask.UnregisterLocker(player.Locker)(continent.GUID) //rest of player will be cleaned up with corpses + } + 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)) + 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)) + taskResolver ! GUIDTask.UnregisterAvatar(player)(continent.GUID) + DismountVehicleOnLogOut(vehicle_guid, player_guid) + } + } + player.VehicleOwned match { case Some(vehicle_guid) => continent.GUID(vehicle_guid) match { @@ -100,15 +137,25 @@ class WorldSessionActor extends Actor with MDCContextAware { } case None => ; } - 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) - //TODO normally, the actual player avatar persists a minute or so after the user disconnects } } + /** + * Vehicle cleanup that is specific to log out behavior. + * @param vehicle_guid the vehicle being occupied + * @param player_guid the player + */ + def DismountVehicleOnLogOut(vehicle_guid : PlanetSideGUID, player_guid : PlanetSideGUID) : Unit = { + val vehicle = continent.GUID(vehicle_guid).get.asInstanceOf[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 ! Service.Leave(Some(s"${vehicle.Actor}")) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, 0, true, vehicle_guid)) + } + def receive = Initializing def Initializing : Receive = { @@ -554,7 +601,7 @@ class WorldSessionActor extends Actor with MDCContextAware { else { vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, seat_num, true, obj.GUID)) } - if(obj.Seats.values.count(seat => seat.isOccupied) == 0) { + if(obj.Seats.values.count(_.isOccupied) == 0) { vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj, continent, 600L) //start vehicle decay (10m) } @@ -1008,6 +1055,12 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info(s"$tplayer has left zone ${zone.Id}") } + case Zone.Population.PlayerCanNotSpawn(zone, tplayer) => + log.warn(s"$tplayer can not spawn in zone ${zone.Id}; why?") + + case Zone.Population.PlayerAlreadySpawned(zone, tplayer) => + log.warn(s"$tplayer is already spawned on zone ${zone.Id}; a clerical error?") + case Zone.Lattice.SpawnPoint(zone_id, building, spawn_tube) => log.info(s"Zone.Lattice.SpawnPoint: spawn point on $zone_id in ${building.Id} @ ${spawn_tube.GUID.guid} selected") reviveTimer.cancel @@ -1015,7 +1068,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val backpack = player.isBackpack val respawnTime : Long = if(sameZone) { 10 } else { 0 } //s val respawnTimeMillis = respawnTime * 1000 //ms - sendResponse(AvatarDeadStateMessage(DeadState.RespawnTime, respawnTimeMillis, respawnTimeMillis, Vector3.Zero, 2, true)) + sendResponse(AvatarDeadStateMessage(DeadState.RespawnTime, respawnTimeMillis, respawnTimeMillis, Vector3.Zero, player.Faction, true)) val tplayer = if(backpack) { RespawnClone(player) //new player } @@ -1122,7 +1175,7 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ChangeShortcutBankMessage(guid, 0)) //FavoritesMessage sendResponse(SetChatFilterMessage(ChatChannel.Local, false, ChatChannel.values.toList)) //TODO will not always be "on" like this - sendResponse(AvatarDeadStateMessage(DeadState.Nothing, 0,0, tplayer.Position, 0, true)) + sendResponse(AvatarDeadStateMessage(DeadState.Alive, 0,0, tplayer.Position, player.Faction, true)) sendResponse(PlanetsideAttributeMessage(guid, 53, 1)) sendResponse(AvatarSearchCriteriaMessage(guid, List(0,0,0,0,0,0))) (1 to 73).foreach(i => { @@ -1500,7 +1553,7 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released") reviveTimer.cancel player.Release - sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, 2, true)) + sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, true)) continent.Population ! Zone.Population.Release(avatar) player.VehicleSeated match { case None => @@ -2153,7 +2206,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //deconstruction PlayerActionsToCancel() player.Release - sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, 2, true)) + sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, true)) continent.Population ! Zone.Population.Release(avatar) case Some(obj : PlanetSideGameObject) => @@ -3418,7 +3471,7 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PlanetsideAttributeMessage(player_guid, 2, 0)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 0, 0)) sendResponse(DestroyMessage(player_guid, player_guid, PlanetSideGUID(0), pos)) //how many players get this message? - sendResponse(AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, pos, 2, true)) + sendResponse(AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, pos, player.Faction, true)) if(tplayer.VehicleSeated.nonEmpty) { //make player invisible (if not, the cadaver sticks out the side in a seated position) sendResponse(PlanetsideAttributeMessage(player_guid, 29, 1)) From 88ddab5e0af88090631f3d593d4c6a9b0732c25b Mon Sep 17 00:00:00 2001 From: FateJH Date: Sun, 15 Apr 2018 23:34:51 -0400 Subject: [PATCH 9/9] Fixing misidentified sanctuary continents and ZoneMap names --- pslogin/src/main/scala/Maps.scala | 8 ++++---- pslogin/src/main/scala/Zones.scala | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala index ea3cfc2de..a6f8cd0ae 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/pslogin/src/main/scala/Maps.scala @@ -624,11 +624,11 @@ object Maps { } } - val map14 = new ZoneMap("map13") + val map14 = new ZoneMap("map14") - val map15 = new ZoneMap("map13") + val map15 = new ZoneMap("map15") - val map16 = new ZoneMap("map13") + val map16 = new ZoneMap("map16") val ugd01 = new ZoneMap("ugd01") @@ -642,7 +642,7 @@ object Maps { val ugd06 = new ZoneMap("ugd06") - val map96 = new ZoneMap("ugd06") + val map96 = new ZoneMap("map96") val map97 = new ZoneMap("map97") diff --git a/pslogin/src/main/scala/Zones.scala b/pslogin/src/main/scala/Zones.scala index a59db3586..1944179f9 100644 --- a/pslogin/src/main/scala/Zones.scala +++ b/pslogin/src/main/scala/Zones.scala @@ -97,8 +97,8 @@ object Zones { */ def SanctuaryZoneId(faction : PlanetSideEmpire.Value) : String = { faction match { - case PlanetSideEmpire.TR => "home1" - case PlanetSideEmpire.NC => "home2" + case PlanetSideEmpire.NC => "home1" + case PlanetSideEmpire.TR => "home2" case PlanetSideEmpire.VS => "home3" case PlanetSideEmpire.NEUTRAL => "" //invalid, not black ops } @@ -111,8 +111,8 @@ object Zones { */ def SanctuaryZoneNumber(faction : PlanetSideEmpire.Value) : Int = { faction match { - case PlanetSideEmpire.TR => 11 - case PlanetSideEmpire.NC => 12 + case PlanetSideEmpire.NC => 11 + case PlanetSideEmpire.TR => 12 case PlanetSideEmpire.VS => 13 case PlanetSideEmpire.NEUTRAL => 0 //invalid, not black ops }