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..f0a56c126
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/Avatar.scala
@@ -0,0 +1,206 @@
+// 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
+ * 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 : LockerContainer = 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) || 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) =>
+ 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 => ;
+ }
+ })
+ }
+
+ def SaveLoadout(owner : Player, label : String, line : Int) : Unit = {
+ if(line > -1 && line < 10) {
+ loadouts(line) = Some(Loadout.Create(owner, label))
+ }
+ }
+
+ def LoadLoadout(line : Int) : Option[Loadout] = loadouts.lift(line).getOrElse(None)
+
+ def DeleteLoadout(line : Int) : Unit = {
+ loadouts(line) = None
+ }
+
+ def Locker : LockerContainer = locker
+
+ def FifthSlot : EquipmentSlot = {
+ new OffhandEquipmentSlot(EquipmentSize.Inventory) {
+ Equipment = 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 = Avatar.toString(this)
+}
+
+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)
+ }
+
+ 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 14c8fbfe6..db4c14fef 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,6 +509,12 @@ object GlobalDefinitions {
val vehicle_terminal_combined = new VehicleTerminalCombinedDefinition
+ val spawn_terminal = new MatrixTerminalDefinition(812)
+
+ val respawn_tube = new SpawnTubeDefinition(732)
+
+ val respawn_tube_tower = new SpawnTubeDefinition(733)
+
val spawn_pad = new VehicleSpawnPadDefinition
val mb_locker = new LockerDefinition
@@ -643,7 +649,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
@@ -683,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/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/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 6da1a64c6..1250051b4 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,46 +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 spectator : 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. */
@@ -76,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
@@ -173,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
@@ -189,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 {
@@ -220,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] = {
@@ -243,33 +192,22 @@ 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(Locker.Find(guid).isDefined) {
+ Some(5)
+ }
+ else if(freeHand.Equipment.isDefined && freeHand.Equipment.get.GUID == guid) {
+ Some(Player.FreeHandSlot)
+ }
+ else {
+ None
}
}
}
@@ -349,27 +287,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?
@@ -377,91 +301,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
@@ -524,10 +374,6 @@ class Player(private val name : String,
isBackpack && (backpackAccess.isEmpty || backpackAccess.contains(player.GUID))
}
- def Admin : Boolean = admin
-
- def Spectator : Boolean = spectator
-
def VehicleSeated : Option[PlanetSideGUID] = vehicleSeated
def VehicleSeated_=(guid : PlanetSideGUID) : Option[PlanetSideGUID] = VehicleSeated_=(Some(guid))
@@ -553,52 +399,34 @@ 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 LockerSlot : Int = 5
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
@@ -611,37 +439,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 Spectate(player : Player, isSpectator : Boolean) : Player = {
- player.spectator = isSpectator
- 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.Voice, player.Head)
+ 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 ???
- obj.fifthSlot.Equipment = player.fifthSlot.Equipment
- player.fifthSlot.Equipment = None
obj
}
else {
@@ -650,7 +452,7 @@ 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})]"
+ 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/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/definition/converter/CorpseConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala
new file mode 100644
index 000000000..68b7df90d
--- /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, Vector3}
+
+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, Vector3(0,0, obj.Orientation.z)),
+ 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/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/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..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,14 +6,19 @@ 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 id : 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.
+ */
+ 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,25 +37,44 @@ 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 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/tube/SpawnTube.scala b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala
index 9d9baac9d..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,34 +1,58 @@
// 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
-// 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 = {
+ Constructor(GlobalDefinitions.respawn_tube, pos, orient)(id, context)
+ }
+
+ /**
+ * 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"${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 ddd70cca2..a1b79e7ce 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
@@ -42,16 +41,25 @@ 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 =>
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 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 _ => ;
}
@@ -59,20 +67,20 @@ class InterstellarCluster(zones : List[Zone]) extends Actor {
/**
* 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)
}
}
}
@@ -95,17 +103,41 @@ 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()
}
+
+/*
+// 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 f084443df..5104c7dc7 100644
--- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala
+++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala
@@ -3,17 +3,19 @@ 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
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
import scala.annotation.tailrec
+import scala.collection.concurrent.TrieMap
import scala.collection.mutable.ListBuffer
import scala.collection.immutable.{Map => PairMap}
@@ -44,8 +46,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. */
@@ -54,8 +56,16 @@ 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 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`.
@@ -66,7 +76,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 = {
@@ -75,12 +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, players, corpses), s"$Id-players")
- Map.LocalObjects.foreach({ builderObject =>
- builderObject.Build
- })
+ Map.LocalObjects.foreach({ builderObject => builderObject.Build })
MakeBuildings(context)
AssignAmenities()
+ CreateSpawnGroups()
}
}
@@ -176,6 +191,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.toList
+
def AddVehicle(vehicle : Vehicle) : List[Vehicle] = {
vehicles = vehicles :+ vehicle
Vehicles
@@ -217,6 +238,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] = {
@@ -235,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)
/**
@@ -263,6 +315,97 @@ 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
+ /**
+ * 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(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
+ * @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 af5c06eab..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, 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")
+ 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 {
@@ -85,8 +169,9 @@ 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")
+ errorCounter.incrementAndGet()
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/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..a22e9b431
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala
@@ -0,0 +1,184 @@
+// Copyright (c) 2017 PSForever
+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._
+
+ def receive : Receive = {
+ case Zone.Population.Join(avatar) =>
+ PopulationJoin(avatar, playerMap)
+
+ case Zone.Population.Leave(avatar) =>
+ PopulationLeave(avatar, playerMap) match {
+ case None => ;
+ case player @ Some(_) =>
+ sender ! Zone.Population.PlayerHasLeft(zone, player)
+ }
+
+ case Zone.Population.Spawn(avatar, player) =>
+ PopulationSpawn(avatar, player, playerMap) match {
+ case Some(tplayer) =>
+ if(tplayer ne player) {
+ sender ! Zone.Population.PlayerAlreadySpawned(zone, player)
+ }
+ case None =>
+ sender ! Zone.Population.PlayerCanNotSpawn(zone, player)
+ }
+
+ case Zone.Population.Release(avatar) =>
+ PopulationRelease(avatar, playerMap) match {
+ case Some(_) => ;
+ case None =>
+ sender ! Zone.Population.PlayerHasLeft(zone, None)
+ }
+
+ case Zone.Corpse.Add(player) =>
+ CorpseAdd(player, corpseList)
+
+ case Zone.Corpse.Remove(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/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/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 cbb35a959..c3049546f 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,18 @@ import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacke
import scodec.Codec
import scodec.codecs._
-
+/**
+ * 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 when defined, the continent number
+ */
final case class SpawnRequestMessage(unk1 : Int,
unk2 : Long,
unk3 : Int,
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/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/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/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/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..73e55c00d 100644
--- a/common/src/test/scala/objects/DoorTest.scala
+++ b/common/src/test/scala/objects/DoorTest.scala
@@ -2,9 +2,9 @@
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.serverobject.structures.{Building, StructureType}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{PlanetSideGUID, UseItemMessage}
import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3}
@@ -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 {
@@ -121,8 +121,8 @@ 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("test", faction, CharacterGender.Male, 0, 0), door)
+ (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 05eb1e2fb..5cd66613c 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.serverobject.structures.{Building, StructureType}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
@@ -68,8 +67,8 @@ 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("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..fb01d6963 100644
--- a/common/src/test/scala/objects/LoadoutTest.scala
+++ b/common/src/test/scala/objects/LoadoutTest.scala
@@ -4,193 +4,87 @@ 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 {
+ val avatar = Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1)
+
def CreatePlayer() : Player = {
- val
- player = Player("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1)
- 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
+ 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 : Player = 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().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 : Player = CreatePlayer()
- obj.SaveLoadout("test", 0)
+ obj.Label mustEqual "test"
+ obj.ExoSuit mustEqual obj.ExoSuit
+ obj.Subtype mustEqual 0
- obj.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 : Player = 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)
+ 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
+ }
- obj.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 : Player = CreatePlayer()
- obj.Inventory.Clear()
- obj.SaveLoadout("test", 0)
-
- obj.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 : Player = CreatePlayer()
- obj.Slot(0).Equipment = None
- obj.Slot(2).Equipment = None
- obj.Slot(4).Equipment = None
- obj.SaveLoadout("test", 0)
-
- obj.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 : Player = CreatePlayer()
- obj.Inventory.Clear()
- obj.Slot(6).Equipment = ConstructionItem(advanced_ace)
- obj.SaveLoadout("test", 0)
-
- obj.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 : Player = CreatePlayer()
- obj.Inventory.Clear()
- obj.Slot(6).Equipment = Kit(medkit)
- obj.SaveLoadout("test", 0)
-
- obj.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 : Player = CreatePlayer()
- obj.SaveLoadout("test", 0)
-
- obj.LoadLoadout(0) match {
- case None =>
- ko
- case Some(_) => ; //good; keep going
- }
- obj.DeleteLoadout(0)
- obj.LoadLoadout(0) mustEqual None
- }
-
- "distinguish MAX subtype information" in {
- val obj : Player = 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
- slot.Equipment = None
- slot.Equipment = Tool(trhev_dualcycler)
- obj.SaveLoadout("cycler", 1)
- slot.Equipment = None
- slot.Equipment = Tool(trhev_pounder)
- obj.SaveLoadout("pounder", 2)
- slot.Equipment = None
- slot.Equipment = Tool(trhev_burster)
- obj.SaveLoadout("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
- }
+ ldout1.Subtype mustEqual 0
+ ldout2.Subtype mustEqual 1
+ ldout3.Subtype mustEqual 2
+ ldout4.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 d082fe2f2..dd9771cb5 100644
--- a/common/src/test/scala/objects/PlayerTest.scala
+++ b/common/src/test/scala/objects/PlayerTest.scala
@@ -1,6 +1,7 @@
// Copyright (c) 2017 PSForever
package objects
+import net.psforever.objects.GlobalDefinitions._
import net.psforever.objects._
import net.psforever.objects.definition.{ImplantDefinition, SimpleItemDefinition}
import net.psforever.objects.equipment.EquipmentSize
@@ -8,38 +9,53 @@ import net.psforever.packet.game.PlanetSideGUID
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 = new 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 {
- (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
- }
+ (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) ==
+ TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual true
- "become a backpack" in {
- val obj = new 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 = new 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 = new 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 = new 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 = new 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 = new 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 = new 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 = new 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 = new 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 satore equipment" in {
- val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
- obj.Inventory.Resize(3,3)
+ "search for the smallest available slot in which to store equipment" in {
+ 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)
@@ -176,46 +255,180 @@ class PlayerTest extends Specification {
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
}
- "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)
+ "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
- }
- ok
+ } //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
}
- "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)
+ "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
}
- "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)
+ "command experience point values of the avatar" in {
+ val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
+ val player = Player(avatar)
- obj.UninstallImplant(testplant.Type)
- obj.Implants(0).Installed mustEqual None
+ 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 = new 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))
@@ -224,7 +437,7 @@ class PlayerTest extends Specification {
}
"own in a vehicle" in {
- val obj = new 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))
@@ -233,27 +446,18 @@ class PlayerTest extends Specification {
}
"remember what zone he is in" in {
- val obj = new 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"
}
- "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
- }
+ "toString" in {
+ val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
+ obj.toString mustEqual "TR Chord 0/100 0/50"
- "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
+ 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 f595da65f..d4393b5d2 100644
--- a/common/src/test/scala/objects/VehicleSpawnPadTest.scala
+++ b/common/src/test/scala/objects/VehicleSpawnPadTest.scala
@@ -3,10 +3,10 @@ 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.{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
@@ -109,8 +109,8 @@ 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("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/ZoneTest.scala b/common/src/test/scala/objects/ZoneTest.scala
index 024c8aef6..2c5cfcb69 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._
+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,422 @@ 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)
+ }
+ }
+}
+
+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 = {
+ 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 a96b55dd2..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}
@@ -10,7 +9,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/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/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/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 65436e495..b635ad5de 100644
--- a/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala
+++ b/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala
@@ -2,8 +2,8 @@
package objects.terminal
import akka.actor.ActorRef
-import net.psforever.objects.serverobject.structures.Building
-import net.psforever.objects.{GlobalDefinitions, Player}
+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
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
@@ -12,9 +12,9 @@ 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 = 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 c3d000d0e..c9791c9ec 100644
--- a/common/src/test/scala/objects/terminal/CertTerminalTest.scala
+++ b/common/src/test/scala/objects/terminal/CertTerminalTest.scala
@@ -2,19 +2,19 @@
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.{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 = 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 d498e3e0b..ef6c621b8 100644
--- a/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala
+++ b/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala
@@ -2,8 +2,8 @@
package objects.terminal
import akka.actor.ActorRef
-import net.psforever.objects.serverobject.structures.Building
-import net.psforever.objects.{GlobalDefinitions, Player}
+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
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
@@ -12,9 +12,9 @@ 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 = 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 831bb0b17..ee88b6027 100644
--- a/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala
+++ b/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala
@@ -2,8 +2,8 @@
package objects.terminal
import akka.actor.ActorRef
-import net.psforever.objects.serverobject.structures.Building
-import net.psforever.objects.{GlobalDefinitions, Player}
+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
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
@@ -12,9 +12,9 @@ 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 = 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 d846ab944..272815f78 100644
--- a/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala
+++ b/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala
@@ -2,8 +2,8 @@
package objects.terminal
import akka.actor.ActorRef
-import net.psforever.objects.serverobject.structures.Building
-import net.psforever.objects.{GlobalDefinitions, Player}
+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
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
@@ -12,9 +12,9 @@ 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 = 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 a58f0cf0c..91993769f 100644
--- a/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala
+++ b/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala
@@ -5,8 +5,9 @@ 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.{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 +45,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 +90,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
@@ -160,9 +161,9 @@ 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("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..fbbb4d6e6 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
@@ -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
}
@@ -54,7 +60,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..c4d44e14e 100644
--- a/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala
+++ b/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala
@@ -2,10 +2,10 @@
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.{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
@@ -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 {
@@ -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..024c8794e 100644
--- a/common/src/test/scala/objects/terminal/OrderTerminalTest.scala
+++ b/common/src/test/scala/objects/terminal/OrderTerminalTest.scala
@@ -2,19 +2,19 @@
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, 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 = 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 f3a461f90..428d8d87e 100644
--- a/common/src/test/scala/objects/terminal/TerminalControlTest.scala
+++ b/common/src/test/scala/objects/terminal/TerminalControlTest.scala
@@ -2,10 +2,10 @@
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.{GlobalDefinitions, Player}
+import net.psforever.objects.{Avatar, GlobalDefinitions, Player}
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types._
import objects.ActorTest
@@ -122,8 +122,8 @@ 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("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..0233dc9d6 100644
--- a/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala
+++ b/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala
@@ -2,8 +2,8 @@
package objects.terminal
import akka.actor.ActorRef
-import net.psforever.objects.serverobject.structures.Building
-import net.psforever.objects.{GlobalDefinitions, Player}
+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
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
@@ -12,9 +12,9 @@ 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 = 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 ab19d2dbb..a6f8cd0ae 100644
--- a/pslogin/src/main/scala/Maps.scala
+++ b/pslogin/src/main/scala/Maps.scala
@@ -1,14 +1,14 @@
// 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
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
object Maps {
@@ -23,16 +23,362 @@ 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,
- VehicleSpawnPad.Constructor(Vector3(3962.0f, 4334.0f, 268.0f), Vector3(0f, 0f, 180.0f))
- )) //TODO guid not correct
+ Building2()
+ Building38()
+ Building42()
+ Building48()
+ Building49()
- LocalBuilding(2, FoundationBuilder(Building.Structure))
- ObjectToBuilding(3353, 2)
- ObjectToBuilding(500, 2)
- TerminalToSpawnPad(3353, 500)
+ 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(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(Vector3(3939.6328f, 4232.547f, 279.26562f), Vector3(0, 0, 270))) //3rd floor door
+ LocalObject(385, Door.Constructor) //courtyard
+ 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(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(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)
+ 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(Vector3(3983.9531f, 4299.992f, 249.29688f), Vector3(0, 0, 90))) //generator room door
+ LocalObject(621, Door.Constructor)
+ 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(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)
+ 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, 267.75f), 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(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)
+ 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)
+ 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)
+ }
+
+ def Building38() : Unit = {
+ //Anguta, West Bunker
+ LocalBuilding(38, FoundationBuilder(Building.Structure(StructureType.Bunker)))
+ LocalObject(362, Door.Constructor)
+ ObjectToBuilding(362, 38)
+ }
+
+ 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(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
+ 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(2156, 49)
+ ObjectToBuilding(2157, 49)
+ ObjectToBuilding(2333, 49)
+ ObjectToBuilding(2334, 49)
+ DoorToLock(430, 906)
+ DoorToLock(431, 907)
+ DoorToLock(432, 902)
+ DoorToLock(433, 903)
+ }
}
val map7 = new ZoneMap("map07")
@@ -48,154 +394,241 @@ 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(ServerObjectBuilder(372, Door.Constructor))
- LocalObject(ServerObjectBuilder(373, Door.Constructor))
+ def Building1() : Unit = {
+ //warpgate?
+ LocalBuilding(1, FoundationBuilder(WarpGate.Structure))
+ }
- 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
- TerminalToInterface(520, 1081)
+ def Building3() : Unit = {
+ //warpgate?
+ LocalBuilding(3, FoundationBuilder(WarpGate.Structure))
+ }
- 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
- ObjectToBuilding(186, 2)
- ObjectToBuilding(187, 2)
- ObjectToBuilding(188, 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(2, FoundationBuilder(WarpGate.Structure)) //TODO might be wrong?
- 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))
- 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)
+// LocalObject(520, ImplantTerminalMech.Constructor) //Hart B
+// LocalObject(1081, Terminal.Constructor(implant_terminal_interface)) //tube 520
+// TerminalToInterface(520, 1081)
- LocalBuilding(51, FoundationBuilder(Building.Structure))
- LocalObject(ServerObjectBuilder(304, Terminal.Constructor(dropship_vehicle_terminal)))
- LocalObject(ServerObjectBuilder(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 = {
+ //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))
+ 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))
- LocalObject(ServerObjectBuilder(1063, Terminal.Constructor(ground_vehicle_terminal)))
- LocalObject(ServerObjectBuilder(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 = {
+ //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)))
+ 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)
+ }
- ObjectToBuilding(853, 2) //TODO check building_id
- ObjectToBuilding(855, 2) //TODO check building_id
- ObjectToBuilding(860, 2) //TODO check building_id
+ def Building42() : Unit = {
+ //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
+ 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.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
+ 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 = {
+ //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))
+ )
+ ObjectToBuilding(304, 51)
+ ObjectToBuilding(292, 51)
+ TerminalToSpawnPad(304, 292)
+ }
+
+ def Building77() : Unit = {
+ //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))
+ )
+ ObjectToBuilding(1063, 77)
+ ObjectToBuilding(706, 77)
+ TerminalToSpawnPad(1063, 706)
+ }
}
- 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")
@@ -209,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/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index 509a202e8..d6c597d96 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -9,8 +9,11 @@ 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.definition.ToolDefinition
+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}
@@ -26,8 +29,9 @@ import net.psforever.objects.serverobject.pad.VehicleSpawnPad
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}
import net.psforever.objects.zones.{InterstellarCluster, Zone}
import net.psforever.packet.game.objectcreate._
@@ -52,52 +56,106 @@ 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
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 admin : Boolean = false
var clientKeepAlive : Cancellable = DefaultCancellable.obj
var progressBarUpdate : Cancellable = DefaultCancellable.obj
+ var reviveTimer : 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 => ;
- }
+ clientKeepAlive.cancel
+ reviveTimer.cancel
+ PlayerActionsToCancel()
+ localService ! Service.Leave()
+ vehicleService ! Service.Leave()
+ avatarService ! Service.Leave()
- 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
- }
+ LivePlayerList.Remove(sessionId)
+ if(player != null && player.HasGUID) {
+ val player_guid = player.GUID
+ 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 {
+ 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.Leave(avatar)
}
}
+ /**
+ * 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 = {
@@ -145,18 +203,20 @@ class WorldSessionActor extends Actor with MDCContextAware {
case GamePacket(_, _, pkt) =>
handleGamePkt(pkt)
// temporary hack to keep the client from disconnecting
+ //it's been a "temporary hack" since 2016 :P
case PokeClient() =>
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(
@@ -170,27 +230,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(
@@ -203,7 +263,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,
@@ -214,27 +274,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) {
@@ -274,13 +334,18 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
}
+ case AvatarResponse.Release(tplayer) =>
+ if(tplayer_guid != guid) {
+ TurnPlayerIntoCorpse(tplayer)
+ }
+
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))
}
@@ -288,9 +353,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))
}
@@ -312,28 +378,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))
@@ -349,7 +416,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)
@@ -359,23 +426,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)
@@ -386,13 +453,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
@@ -534,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)
}
@@ -756,9 +823,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))
}
@@ -768,9 +835,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))
}
@@ -787,8 +854,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
@@ -827,7 +894,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)
@@ -907,7 +974,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
@@ -959,33 +1025,16 @@ 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
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))
@@ -998,42 +1047,135 @@ 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 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 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
+ 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, player.Faction, 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
+ 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))
+ }
+ }
+ 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) =>
+ 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
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 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.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))
- 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() //important! the LoadMapMessage must be processed by the client before the avatar is created
+
+ case PlayerLoaded(tplayer) =>
+ log.info(s"Player ${tplayer.Name} will respawn")
+ player = tplayer
+ AvatarCreate()
+ self ! SetCurrentAvatar(tplayer)
+
+ case PlayerFailedToLoad(tplayer) =>
+ player.Continent match {
+ case _ =>
+ failWithError(s"${tplayer.Name} failed to load anywhere")
+ }
case SetCurrentAvatar(tplayer) =>
+ player = tplayer
val guid = tplayer.GUID
- LivePlayerList.Assign(continent.Number, sessionId, 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))
+ }
(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
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 => {
@@ -1125,48 +1267,48 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
}
- var player : Player = 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"
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._
- 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.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
+ import net.psforever.types.CertificationType._
+ 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
//
- 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
- AwardBattleExperiencePoints(player, 1000000L)
+ 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.Orientation = Vector3(0f, 0f, 90f)
+ 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
@@ -1177,7 +1319,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._
@@ -1196,11 +1338,10 @@ 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
- galaxy ! InterstellarCluster.GetWorld("home3")
+ galaxy ! InterstellarCluster.RequestClientInitialization()
case default =>
log.error("Unsupported " + default + " in " + msg)
}
@@ -1210,12 +1351,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ BeginZoningMessage() =>
log.info("Reticulating splines ...")
- //map-specific initializations
- configZone(continent) //todo density
+ configZone(continent)
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)) //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 => {
@@ -1229,23 +1372,17 @@ class WorldSessionActor extends Actor with MDCContextAware {
)
})
//load active players in zone
- LivePlayerList.ZonePopulation(continent.Number, _ => true).foreach(char => {
- sendResponse(
- ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get)
- )
+ 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
- )
- )
+ 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) {
@@ -1262,14 +1399,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 _ => ;
@@ -1295,18 +1430,19 @@ 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
-
- val wepInHand : Boolean = player.Slot(player.DrawnSlot).Equipment match {
- case Some(item) => item.Definition == GlobalDefinitions.bolt_driver
- case None => false
+ 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
+ }
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, msg, spectator, wepInHand))
}
- avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, msg, player.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
@@ -1366,28 +1502,88 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ ReleaseAvatarRequestMessage() =>
log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released")
- sendResponse(PlanetsideAttributeMessage(player.GUID, 6, 1))
- sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, 2, true))
+ reviveTimer.cancel
+ player.Release
+ sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, 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
+ 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
+ //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))
+ 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)))
+ }
case msg @ SpawnRequestMessage(u1, u2, u3, u4, u5) =>
log.info(s"SpawnRequestMessage: $msg")
+ //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)
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)
}
if(messagetype == ChatMessageType.CMT_SUICIDE) {
- val player_guid = player.GUID
- val pos = player.Position
- sendResponse(PlanetsideAttributeMessage(player_guid, 0, 0))
- sendResponse(PlanetsideAttributeMessage(player_guid, 2, 0))
- sendResponse(DestroyMessage(player_guid, player_guid, PlanetSideGUID(0), pos))
- 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) {
@@ -1400,9 +1596,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 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)
+ }
+
// 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.")
@@ -1711,7 +1912,9 @@ 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 || 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")
@@ -1790,9 +1993,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?
@@ -1950,6 +2153,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, player.Faction, 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))
@@ -1971,6 +2181,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
obj.AccessingTrunk = None
UnAccessContents(obj)
}
+ case Some(obj : Player) =>
+ TryDisposeOfLootedCorpse(obj)
case _ =>;
}
@@ -2000,10 +2212,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, ""))
}
}
@@ -2075,7 +2287,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) =>
@@ -2356,6 +2568,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.
@@ -2386,7 +2632,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
override def onFailure(ex : Throwable) : Unit = {
localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WSA
}
- }, List(GUIDTask.RegisterAvatar(tplayer)(continent.GUID))
+ }, List(GUIDTask.RegisterPlayer(tplayer)(continent.GUID))
)
}
@@ -2541,6 +2787,28 @@ 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 = priorTask.task.isComplete
+
+ 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.
@@ -2653,21 +2921,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
@@ -3025,10 +3293,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)
}
}
@@ -3111,18 +3379,226 @@ 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.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))
})
}
+ /**
+ * 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, 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))
+ 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 //TODO temp
+ 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))
+ continent.Population ! Zone.Population.Spawn(avatar, player)
+ log.debug(s"ObjectCreateDetailedMessage: $dcdata")
+ }
+
+ /**
+ * 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.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
+ }
+
+ /**
+ * 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).
+ * @see `CorpseConverter.converter`
+ * @param tplayer the player
+ */
+ def TurnPlayerIntoCorpse(tplayer : Player) : Unit = {
+ sendResponse(
+ ObjectCreateDetailedMessage(ObjectClass.avatar, tplayer.GUID, CorpseConverter.converter.DetailedConstructorData(tplayer).get)
+ )
+ }
+
+ /**
+ * 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,
+ * 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())
@@ -3162,6 +3638,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()
diff --git a/pslogin/src/main/scala/Zones.scala b/pslogin/src/main/scala/Zones.scala
index 352c27294..1944179f9 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)
@@ -19,6 +20,13 @@ 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
+ Building(49).get.Faction = PlanetSideEmpire.VS
+ Building(49).get.ModelId = 69
}
}
@@ -39,7 +47,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
}
}
@@ -81,4 +89,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.NC => "home1"
+ case PlanetSideEmpire.TR => "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.NC => 11
+ case PlanetSideEmpire.TR => 12
+ case PlanetSideEmpire.VS => 13
+ case PlanetSideEmpire.NEUTRAL => 0 //invalid, not black ops
+ }
+ }
}
diff --git a/pslogin/src/main/scala/services/avatar/AvatarAction.scala b/pslogin/src/main/scala/services/avatar/AvatarAction.scala
index 13ece68ee..80d49828d 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, 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/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..2978f1a11 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.CorpseRemovalActor
import services.{GenericEventBus, Service}
class AvatarService extends Actor {
- //import AvatarServiceResponse._
+ private val undertaker : ActorRef = context.actorOf(Props[CorpseRemovalActor], "corpse-removal-agent")
+ undertaker ! "startup"
+
private [this] val log = org.log4s.getLogger
override def preStart = {
@@ -87,6 +90,14 @@ class AvatarService extends Actor {
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarResponse.PlayerState(msg, spectator, weapon))
)
+ 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))
+ )
case AvatarAction.Reload(player_guid, weapon_guid) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.Reload(weapon_guid))
@@ -95,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/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 56e02385f..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)))
@@ -110,14 +124,15 @@ 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
"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()}"
+ }
}