From ddc245054141474543e6f6a265cb3ffcb2279cb7 Mon Sep 17 00:00:00 2001 From: FateJH Date: Sat, 24 Mar 2018 00:28:02 -0400 Subject: [PATCH] Buildings now have simple type distinctions to work using the known output of a SpawnRequestMessage packet. Support for cross-continental respawning (actually a failure case for being unable to spawn). Corpse tuning and testing. --- .../scala/net/psforever/objects/Avatar.scala | 43 +- .../psforever/objects/GlobalDefinitions.scala | 8 +- .../scala/net/psforever/objects/Loadout.scala | 32 +- .../scala/net/psforever/objects/Player.scala | 11 +- .../net/psforever/objects/guid/GUIDTask.scala | 192 +++++--- .../guid/actor/UniqueNumberSystem.scala | 14 +- .../serverobject/structures/Building.scala | 26 +- .../structures/StructureType.scala | 20 + .../serverobject/structures/WarpGate.scala | 2 +- .../EquipmentTerminalDefinition.scala | 12 +- .../terminals/MatrixTerminalDefinition.scala | 5 +- .../terminals/SpawnTerminalDefinition.scala | 15 - .../objects/serverobject/tube/SpawnTube.scala | 40 +- .../tube/SpawnTubeDefinition.scala | 17 + .../objects/zones/InterstellarCluster.scala | 47 +- .../net/psforever/objects/zones/Zone.scala | 197 +++++---- .../psforever/objects/zones/ZoneActor.scala | 97 ++++- .../objects/zones/ZonePopulationActor.scala | 159 ++++++- .../packet/game/DisconnectMessage.scala | 13 +- .../packet/game/SpawnRequestMessage.scala | 12 +- .../scala/net/psforever/types/Vector3.scala | 18 +- common/src/test/scala/Vector3Test.scala | 5 + .../src/test/scala/objects/AvatarTest.scala | 397 +++++++++++++++++ .../src/test/scala/objects/BuildingTest.scala | 18 +- common/src/test/scala/objects/DoorTest.scala | 4 +- .../scala/objects/FactionAffinityTest.scala | 4 +- .../src/test/scala/objects/IFFLockTest.scala | 4 +- .../src/test/scala/objects/LoadoutTest.scala | 244 +++-------- .../src/test/scala/objects/PlayerTest.scala | 409 +++++++++++++----- .../objects/ServerObjectBuilderTest.scala | 24 +- .../test/scala/objects/SpawnTubeTest.scala | 59 +++ .../scala/objects/VehicleSpawnPadTest.scala | 4 +- common/src/test/scala/objects/ZoneTest.scala | 383 +++++++++++++++- .../guidtask/GUIDTaskRegister5Test.scala | 1 - .../guidtask/GUIDTaskRegister6Test.scala | 38 ++ .../guidtask/GUIDTaskUnregister6Test.scala | 44 ++ .../number/UniqueNumberSystemTest.scala | 34 ++ .../terminal/AirVehicleTerminalTest.scala | 4 +- .../objects/terminal/CertTerminalTest.scala | 4 +- .../DropshipVehicleTerminalTest.scala | 4 +- .../terminal/GroundVehicleTerminalTest.scala | 4 +- .../ImplantTerminalInterfaceTest.scala | 4 +- .../terminal/ImplantTerminalMechTest.scala | 3 +- .../objects/terminal/MatrixTerminalTest.scala | 10 +- .../terminal/OrderTerminalABTest.scala | 4 +- .../objects/terminal/OrderTerminalTest.scala | 4 +- .../terminal/TerminalControlTest.scala | 4 +- .../VehicleTerminalCombinedTest.scala | 4 +- pslogin/src/main/scala/Maps.scala | 96 ++-- .../src/main/scala/WorldSessionActor.scala | 374 +++++++++++----- pslogin/src/main/scala/Zones.scala | 29 ++ .../avatar/support/UndertakerActor.scala | 2 +- 52 files changed, 2491 insertions(+), 711 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/structures/StructureType.scala delete mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/SpawnTerminalDefinition.scala create mode 100644 common/src/test/scala/objects/AvatarTest.scala create mode 100644 common/src/test/scala/objects/SpawnTubeTest.scala create mode 100644 common/src/test/scala/objects/guidtask/GUIDTaskRegister6Test.scala create mode 100644 common/src/test/scala/objects/guidtask/GUIDTaskUnregister6Test.scala diff --git a/common/src/main/scala/net/psforever/objects/Avatar.scala b/common/src/main/scala/net/psforever/objects/Avatar.scala index fac940c18..f0a56c126 100644 --- a/common/src/main/scala/net/psforever/objects/Avatar.scala +++ b/common/src/main/scala/net/psforever/objects/Avatar.scala @@ -15,14 +15,19 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : private var cep : Long = 0 /** Certifications */ private val certs : mutable.Set[CertificationType.Value] = mutable.Set[CertificationType.Value]() - /** Implants */ + /** Implants
+ * Unlike other objects, the maximum number of `ImplantSlots` are built into the `Avatar`. + * Additionally, implants do not have tightly-coupled "`Definition` objects" that explain a formal implant object. + * The `ImplantDefinition` objects themselves are moved around as if they were the implants. + * The terms externally used for the states of process is "installed" and "uninstalled." + * @see `ImplantSlot` + * @see `DetailedCharacterData.implants` + */ private val implants : Array[ImplantSlot] = Array.fill[ImplantSlot](3)(new ImplantSlot) /** Loadouts */ private val loadouts : Array[Option[Loadout]] = Array.fill[Option[Loadout]](10)(None) /** Locker (fifth inventory slot) */ - private val locker : EquipmentSlot = new OffhandEquipmentSlot(EquipmentSize.Inventory) { - Equipment = new LockerContainer - } + private val locker : LockerContainer = LockerContainer() def BEP : Long = bep @@ -67,7 +72,7 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : * @return the index of the `ImplantSlot` where the implant was installed */ def InstallImplant(implant : ImplantDefinition) : Option[Int] = { - implants.find({p => p.Installed.contains(implant)}) match { //try to find the installed implant + implants.find({p => p.Installed.contains(implant) || p.Implant == implant.Type}) match { //try to find the installed implant case None => recursiveFindImplantInSlot(implants.iterator, ImplantType.None) match { //install in a free slot case Some(slot) => @@ -138,29 +143,25 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : }) } - /** - * Unlike other objects, the maximum number of `ImplantSlots` are built into the `Player`. - * Additionally, "implants" do not have tightly-coupled "`Definition` objects" that explain a formal implant object. - * The `ImplantDefinition` objects themselves are moved around as if they were the implants. - * The term internally used for this process is "installed" and "uninstalled." - * @see `ImplantSlot` - * @see `DetailedCharacterData.implants` - * @see `AvatarConverter.MakeImplantEntries` - */ - def SaveLoadout(owner : Player, label : String, line : Int) : Unit = { - loadouts(line) = Some(Loadout.Create(owner, label)) + if(line > -1 && line < 10) { + loadouts(line) = Some(Loadout.Create(owner, label)) + } } - def LoadLoadout(line : Int) : Option[Loadout] = loadouts(line) + def LoadLoadout(line : Int) : Option[Loadout] = loadouts.lift(line).getOrElse(None) def DeleteLoadout(line : Int) : Unit = { loadouts(line) = None } - def Locker : LockerContainer = locker.Equipment.get.asInstanceOf[LockerContainer] + def Locker : LockerContainer = locker - def FifthSlot : EquipmentSlot = locker + def FifthSlot : EquipmentSlot = { + new OffhandEquipmentSlot(EquipmentSize.Inventory) { + Equipment = locker + } + } def Definition : AvatarDefinition = Avatar.definition @@ -191,7 +192,7 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b) } - override def toString: String = s"$faction $name" + override def toString: String = Avatar.toString(this) } object Avatar { @@ -200,4 +201,6 @@ object Avatar { def apply(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : Int) : Avatar = { new Avatar(name, faction, sex, head, voice) } + + def toString(avatar : Avatar) : String = s"${avatar.faction} ${avatar.name}" } diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index fd0f8f878..1cc05ec0f 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -487,7 +487,7 @@ object GlobalDefinitions { */ val order_terminal = new OrderTerminalDefinition - val ams_respawn_tube = new SpawnTubeDefinition(49) { Name = "ams_respawn_tube" } + val ams_respawn_tube = new SpawnTubeDefinition(49) val matrix_terminalc = new MatrixTerminalDefinition(519) @@ -509,7 +509,11 @@ object GlobalDefinitions { val vehicle_terminal_combined = new VehicleTerminalCombinedDefinition - val spawn_terminal = new SpawnTerminalDefinition + val spawn_terminal = new MatrixTerminalDefinition(812) + + val respawn_tube = new SpawnTubeDefinition(732) + + val respawn_tube_tower = new SpawnTubeDefinition(733) val spawn_pad = new VehicleSpawnPadDefinition diff --git a/common/src/main/scala/net/psforever/objects/Loadout.scala b/common/src/main/scala/net/psforever/objects/Loadout.scala index 1d2a2d2c9..ec997d0a0 100644 --- a/common/src/main/scala/net/psforever/objects/Loadout.scala +++ b/common/src/main/scala/net/psforever/objects/Loadout.scala @@ -8,14 +8,6 @@ import net.psforever.types.ExoSuitType import scala.annotation.tailrec -//trait Loadout { -// def Label : String -// def VisibleSlots : List[Loadout.SimplifiedEntry] -// def Inventory : List[Loadout.SimplifiedEntry] -// def ExoSuit : ExoSuitType.Value -// def Subtype : Int -//} - /** * From a `Player` their current exo-suit and their `Equipment`, retain a set of instructions to reconstruct this arrangement.
*
@@ -113,7 +105,9 @@ object Loadout { /** * A basic `Trait` connecting all of the `Equipment` blueprints. */ - sealed trait Simplification + sealed trait Simplification { + def definition : ObjectDefinition + } /** * An entry in the `Loadout`, wrapping around a slot index and what is in the slot index. @@ -125,17 +119,17 @@ object Loadout { /** * The simplified form of an `AmmoBox`. - * @param adef the `AmmoBoxDefinition` that describes this future object + * @param definition the `AmmoBoxDefinition` that describes this future object * @param capacity the amount of ammunition, if any, to initialize; * if `None`, then the previous `AmmoBoxDefinition` will be referenced for the amount later */ - final case class ShorthandAmmoBox(adef : AmmoBoxDefinition, capacity : Int) extends Simplification + final case class ShorthandAmmoBox(definition : AmmoBoxDefinition, capacity : Int) extends Simplification /** * The simplified form of a `Tool`. - * @param tdef the `ToolDefinition` that describes this future object + * @param definition the `ToolDefinition` that describes this future object * @param ammo the blueprints to construct the correct number of ammunition slots in the `Tool` */ - final case class ShorthandTool(tdef : ToolDefinition, ammo : List[ShorthandAmmoSlot]) extends Simplification + final case class ShorthandTool(definition : ToolDefinition, ammo : List[ShorthandAmmoSlot]) extends Simplification /** * The simplified form of a `Tool` `FireMode` * @param ammoIndex the index that points to the type of ammunition this slot currently uses @@ -144,19 +138,19 @@ object Loadout { final case class ShorthandAmmoSlot(ammoIndex : Int, ammo : ShorthandAmmoBox) /** * The simplified form of a `ConstructionItem`. - * @param cdef the `ConstructionItemDefinition` that describes this future object + * @param definition the `ConstructionItemDefinition` that describes this future object */ - final case class ShorthandConstructionItem(cdef : ConstructionItemDefinition) extends Simplification + final case class ShorthandConstructionItem(definition : ConstructionItemDefinition) extends Simplification /** * The simplified form of a `SimpleItem`. - * @param sdef the `SimpleItemDefinition` that describes this future object + * @param definition the `SimpleItemDefinition` that describes this future object */ - final case class ShorthandSimpleItem(sdef : SimpleItemDefinition) extends Simplification + final case class ShorthandSimpleItem(definition : SimpleItemDefinition) extends Simplification /** * The simplified form of a `Kit`. - * @param kdef the `KitDefinition` that describes this future object + * @param definition the `KitDefinition` that describes this future object */ - final case class ShorthandKit(kdef : KitDefinition) extends Simplification + final case class ShorthandKit(definition : KitDefinition) extends Simplification def DetermineSubtype(player : Player) : Int = { if(player.ExoSuit == ExoSuitType.MAX) { diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index 30c328211..1250051b4 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -200,7 +200,10 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio case Some(index) => Some(index) case None => - if(freeHand.Equipment.isDefined && freeHand.Equipment.get.GUID == guid) { + if(Locker.Find(guid).isDefined) { + Some(5) + } + else if(freeHand.Equipment.isDefined && freeHand.Equipment.get.GUID == guid) { Some(Player.FreeHandSlot) } else { @@ -416,6 +419,7 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio } object Player { + final val LockerSlot : Int = 5 final val FreeHandSlot : Int = 250 final val HandsDownSlot : Int = 255 @@ -447,5 +451,8 @@ object Player { } } - def toString(obj : Player) : String = s"${obj.core} ${obj.Health}/${obj.MaxHealth} ${obj.Armor}/${obj.MaxArmor}" + def toString(obj : Player) : String = { + val guid = if(obj.HasGUID) { s" ${obj.Continent}-${obj.GUID.guid}" } else { "" } + s"${obj.core}$guid ${obj.Health}/${obj.MaxHealth} ${obj.Armor}/${obj.MaxArmor}" + } } diff --git a/common/src/main/scala/net/psforever/objects/guid/GUIDTask.scala b/common/src/main/scala/net/psforever/objects/guid/GUIDTask.scala index 95d92bced..2ea7fb4a5 100644 --- a/common/src/main/scala/net/psforever/objects/guid/GUIDTask.scala +++ b/common/src/main/scala/net/psforever/objects/guid/GUIDTask.scala @@ -1,7 +1,13 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.guid -import net.psforever.objects.vehicles.Utility +import akka.actor.ActorRef +import net.psforever.objects.entity.IdentifiableEntity +import net.psforever.objects.equipment.Equipment +import net.psforever.objects.{EquipmentSlot, LockerContainer, Player, Tool, Vehicle} +import net.psforever.objects.inventory.Container + +import scala.annotation.tailrec /** * The basic compiled tasks for assigning (registering) and revoking (unregistering) globally unique identifiers.
@@ -14,17 +20,13 @@ import net.psforever.objects.vehicles.Utility * It will get passed from the more complicated functions down into the less complicated functions, * until it has found the basic number assignment functionality.
*
- * All functions produce a `TaskResolver.GiveTask` container object that is expected to be used by a `TaskResolver`. - * These "task containers" can also be unpackaged into their tasks, sorted into other containers, - * and combined with other "task containers" to enact more complicated sequences of operations. + * All functions produce a `TaskResolver.GiveTask` container object + * or a list of `TaskResolver.GiveTask` container objects that is expected to be used by a `TaskResolver` `Actor`. + * These "task containers" can also be unpackaged into their component tasks, sorted into other containers, + * and combined with other tasks to enact more complicated sequences of operations. + * Almost all tasks have an explicit registering and an unregistering activity defined for it. */ object GUIDTask { - import akka.actor.ActorRef - import net.psforever.objects.entity.IdentifiableEntity - import net.psforever.objects.equipment.Equipment - import net.psforever.objects.{EquipmentSlot, Player, Tool, Vehicle} - - import scala.annotation.tailrec /** * Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers.
*
@@ -76,9 +78,34 @@ object GUIDTask { TaskResolver.GiveTask(RegisterObjectTask(obj).task, ammoTasks) } + /** + * Construct tasking that registers a `LockerContainer` object + * with a globally unique identifier selected from a pool of numbers. + * @param obj the object being registered + * @param guid implicit reference to a unique number system + * @see `GUIDTask.UnregisterLocker` + * @return a `TaskResolver.GiveTask` message + */ + def RegisterLocker(obj : LockerContainer)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + TaskResolver.GiveTask(RegisterObjectTask(obj).task, RegisterInventory(obj)) + } + + /** + * Construct tasking that registers the objects that are within the given container's inventory + * with a globally unique identifier selected from a pool of numbers for each object. + * @param container the storage unit in which objects can be found + * @param guid implicit reference to a unique number system + * @see `GUID.UnregisterInventory`
+ * `Container` + * @return a list of `TaskResolver.GiveTask` messages + */ + def RegisterInventory(container : Container)(implicit guid : ActorRef) : List[TaskResolver.GiveTask] = { + container.Inventory.Items.values.map(entry => { RegisterEquipment(entry.obj)}).toList + } + /** * Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, - * after determining whether the object is complex (`Tool`) or simple.
+ * after determining whether the object is complex (`Tool` or `Locker`) or is simple.
*
* The objects in this case are specifically `Equipment`, a subclass of the basic register-able `IdentifiableEntity`. * About five subclasses of `Equipment` exist, but they decompose into two groups - "complex objects" and "simple objects." @@ -96,6 +123,8 @@ object GUIDTask { obj match { case tool : Tool => RegisterTool(tool) + case locker : LockerContainer => + RegisterLocker(locker) case _ => RegisterObjectTask(obj) } @@ -118,17 +147,24 @@ object GUIDTask { * @return a `TaskResolver.GiveTask` message */ def RegisterAvatar(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = { - import net.psforever.objects.LockerContainer - import net.psforever.objects.inventory.InventoryItem - val holsterTasks = recursiveHolsterTaskBuilding(tplayer.Holsters().iterator, RegisterEquipment) - val fifthHolsterTask = tplayer.Slot(5).Equipment match { - case Some(locker) => - RegisterObjectTask(locker) :: locker.asInstanceOf[LockerContainer].Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => RegisterEquipment(entry.obj)}).toList - case None => - List.empty[TaskResolver.GiveTask]; - } - val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => RegisterEquipment(entry.obj)}) - TaskResolver.GiveTask(RegisterObjectTask(tplayer).task, holsterTasks ++ fifthHolsterTask ++ inventoryTasks) + val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), RegisterEquipment) + val lockerTask = List(RegisterLocker(tplayer.Locker)) + val inventoryTasks = RegisterInventory(tplayer) + TaskResolver.GiveTask(RegisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks) + } + + /** + * Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, as a `Player`.
+ *
+ * Similar to `RegisterAvatar` but the locker components are skipped. + * @param tplayer the `Player` object being registered + * @param guid implicit reference to a unique number system + * @return a `TaskResolver.GiveTask` message + */ + def RegisterPlayer(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), RegisterEquipment) + val inventoryTasks = RegisterInventory(tplayer) + TaskResolver.GiveTask(GUIDTask.RegisterObjectTask(tplayer)(guid).task, holsterTasks ++ inventoryTasks) } /** @@ -149,10 +185,9 @@ object GUIDTask { * @return a `TaskResolver.GiveTask` message */ def RegisterVehicle(vehicle : Vehicle)(implicit guid : ActorRef) : TaskResolver.GiveTask = { - import net.psforever.objects.inventory.InventoryItem - val weaponTasks = vehicle.Weapons.map({ case(_ : Int, entry : EquipmentSlot) => RegisterEquipment(entry.Equipment.get)}).toList - val utilTasks = Vehicle.EquipmentUtilities(vehicle.Utilities).map({case (_ : Int, util : Utility) => RegisterObjectTask(util())}).toList - val inventoryTasks = vehicle.Trunk.Items.map({ case((_ : Int, entry : InventoryItem)) => RegisterEquipment(entry.obj)}) + val weaponTasks = VisibleSlotTaskBuilding(vehicle.Weapons.values, RegisterEquipment) + val utilTasks = Vehicle.EquipmentUtilities(vehicle.Utilities).values.map(util => { RegisterObjectTask(util())}).toList + val inventoryTasks = RegisterInventory(vehicle) TaskResolver.GiveTask(RegisterObjectTask(vehicle).task, weaponTasks ++ utilTasks ++ inventoryTasks) } @@ -201,9 +236,33 @@ object GUIDTask { TaskResolver.GiveTask(UnregisterObjectTask(obj).task, ammoTasks) } + /** + * Construct tasking that unregisters a `LockerContainer` object from a globally unique identifier system. + * @param obj the object being unregistered + * @param guid implicit reference to a unique number system + * @see `GUIDTask.RegisterLocker` + * @return a `TaskResolver.GiveTask` message + */ + def UnregisterLocker(obj : LockerContainer)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + TaskResolver.GiveTask(UnregisterObjectTask(obj).task, UnregisterInventory(obj)) + } + + /** + * Construct tasking that unregisters the objects that are within the given container's inventory + * from a globally unique identifier system. + * @param container the storage unit in which objects can be found + * @param guid implicit reference to a unique number system + * @see `GUIDTask.RegisterInventory`
+ * `Container` + * @return a list of `TaskResolver.GiveTask` messages + */ + def UnregisterInventory(container : Container)(implicit guid : ActorRef) : List[TaskResolver.GiveTask] = { + container.Inventory.Items.values.map(entry => { UnregisterEquipment(entry.obj)}).toList + } + /** * Construct tasking that unregisters an object from a globally unique identifier system - * after determining whether the object is complex (`Tool`) or simple.
+ * after determining whether the object is complex (`Tool` or `Locker`) or is simple.
*
* This task performs an operation that reverses the effect of `RegisterEquipment`. * @param obj the `Equipment` object being unregistered @@ -215,6 +274,8 @@ object GUIDTask { obj match { case tool : Tool => UnregisterTool(tool) + case locker : LockerContainer => + UnregisterLocker(locker) case _ => UnregisterObjectTask(obj) } @@ -230,36 +291,58 @@ object GUIDTask { * @return a `TaskResolver.GiveTask` message */ def UnregisterAvatar(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = { - import net.psforever.objects.LockerContainer - import net.psforever.objects.inventory.InventoryItem - val holsterTasks = recursiveHolsterTaskBuilding(tplayer.Holsters().iterator, UnregisterEquipment) - val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => UnregisterEquipment(entry.obj)}) - val fifthHolsterTask = tplayer.Slot(5).Equipment match { - case Some(locker) => - UnregisterObjectTask(locker) :: locker.asInstanceOf[LockerContainer].Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => UnregisterEquipment(entry.obj)}).toList - case None => - List.empty[TaskResolver.GiveTask]; - } - TaskResolver.GiveTask(UnregisterObjectTask(tplayer).task, holsterTasks ++ fifthHolsterTask ++ inventoryTasks) + val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), UnregisterEquipment) + val lockerTask = List(UnregisterLocker(tplayer.Locker)) + val inventoryTasks = UnregisterInventory(tplayer) + TaskResolver.GiveTask(UnregisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks) } -/** - * Construct tasking that unregisters a `Vehicle` object from a globally unique identifier system.
- *
- * This task performs an operation that reverses the effect of `RegisterVehicle`. - * @param vehicle the `Vehicle` object being unregistered - * @param guid implicit reference to a unique number system - * @see `GUIDTask.RegisterVehicle` - * @return a `TaskResolver.GiveTask` message - */ + /** + * Construct tasking that unregisters a portion of a `Player` object from a globally unique identifier system.
+ *
+ * Similar to `UnregisterAvatar` but the locker components are skipped. + * This task performs an operation that reverses the effect of `RegisterPlayer`. + * @param tplayer the `Player` object being unregistered + * @param guid implicit reference to a unique number system + * @see `GUIDTask.RegisterAvatar` + * @return a `TaskResolver.GiveTask` message + */ + def UnregisterPlayer(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), UnregisterEquipment) + val inventoryTasks = UnregisterInventory(tplayer) + TaskResolver.GiveTask(GUIDTask.UnregisterObjectTask(tplayer).task, holsterTasks ++ inventoryTasks) + } + + /** + * Construct tasking that unregisters a `Vehicle` object from a globally unique identifier system.
+ *
+ * This task performs an operation that reverses the effect of `RegisterVehicle`. + * @param vehicle the `Vehicle` object being unregistered + * @param guid implicit reference to a unique number system + * @see `GUIDTask.RegisterVehicle` + * @return a `TaskResolver.GiveTask` message + */ def UnregisterVehicle(vehicle : Vehicle)(implicit guid : ActorRef) : TaskResolver.GiveTask = { - import net.psforever.objects.inventory.InventoryItem - val weaponTasks = vehicle.Weapons.map({ case(_ : Int, entry : EquipmentSlot) => UnregisterTool(entry.Equipment.get.asInstanceOf[Tool]) }).toList - val utilTasks = Vehicle.EquipmentUtilities(vehicle.Utilities).map({case (_ : Int, util : Utility) => UnregisterObjectTask(util())}).toList - val inventoryTasks = vehicle.Trunk.Items.map({ case((_ : Int, entry : InventoryItem)) => UnregisterEquipment(entry.obj)}) + val weaponTasks = VisibleSlotTaskBuilding(vehicle.Weapons.values, UnregisterEquipment) + val utilTasks = Vehicle.EquipmentUtilities(vehicle.Utilities).values.map(util => { UnregisterObjectTask(util())}).toList + val inventoryTasks = UnregisterInventory(vehicle) TaskResolver.GiveTask(UnregisterObjectTask(vehicle).task, weaponTasks ++ utilTasks ++ inventoryTasks) } + /** + * Construct tasking that allocates work upon encountered `Equipment` objects + * in reference to a globally unique identifier system of a pool of numbers. + * "Visible slots" are locations that can be viewed by multiple observers across a number of clients. + * @param list an `Iterable` sequence of `EquipmentSlot` objects that may or may not have equipment + * @param func the function used to build tasking from any discovered `Equipment`; + * strictly either `RegisterEquipment` or `UnregisterEquipment` + * @param guid implicit reference to a unique number system + * @return a list of `TaskResolver.GiveTask` messages + */ + def VisibleSlotTaskBuilding(list : Iterable[EquipmentSlot], func : ((Equipment)=>TaskResolver.GiveTask))(implicit guid : ActorRef) : List[TaskResolver.GiveTask] = { + recursiveVisibleSlotTaskBuilding(list.iterator, func) + } + /** * Iterate over a group of `EquipmentSlot`s, some of which may be occupied with an item. * Use `func` on any discovered `Equipment` to transform items into tasking, and add the tasking to a `List`. @@ -267,18 +350,19 @@ object GUIDTask { * @param func the function used to build tasking from any discovered `Equipment`; * strictly either `RegisterEquipment` or `UnregisterEquipment` * @param list a persistent `List` of `Equipment` tasking + * @see `VisibleSlotTaskBuilding` * @return a `List` of `Equipment` tasking */ - @tailrec private def recursiveHolsterTaskBuilding(iter : Iterator[EquipmentSlot], func : ((Equipment)=>TaskResolver.GiveTask), list : List[TaskResolver.GiveTask] = Nil)(implicit guid : ActorRef) : List[TaskResolver.GiveTask] = { + @tailrec private def recursiveVisibleSlotTaskBuilding(iter : Iterator[EquipmentSlot], func : ((Equipment)=>TaskResolver.GiveTask), list : List[TaskResolver.GiveTask] = Nil)(implicit guid : ActorRef) : List[TaskResolver.GiveTask] = { if(!iter.hasNext) { list } else { iter.next.Equipment match { case Some(item) => - recursiveHolsterTaskBuilding(iter, func, list :+ func(item)) + recursiveVisibleSlotTaskBuilding(iter, func, list :+ func(item)) case None => - recursiveHolsterTaskBuilding(iter, func, list) + recursiveVisibleSlotTaskBuilding(iter, func, list) } } } diff --git a/common/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala b/common/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala index fcd16c173..31699543a 100644 --- a/common/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala +++ b/common/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala @@ -287,12 +287,7 @@ class UniqueNumberSystem(private val guid : NumberPoolHub, private val poolActor * @see `UniqueNumberSystem.UnregistrationProcess(Option[GUIDRequest], Int, Int)` */ private def NoCallbackReturnNumber(number : Int, poolName : String) : Unit = { - poolActors.get(poolName) match { - case Some(pool) => - pool ! NumberPoolActor.ReturnNumber(number, Some(Long.MinValue)) - case None => - log.error(s"critical: tried to return number $number but did not find pool $poolName") - } + poolActors(poolName) ! NumberPoolActor.ReturnNumber(number, Some(Long.MinValue)) } /** @@ -316,12 +311,7 @@ class UniqueNumberSystem(private val guid : NumberPoolHub, private val poolActor * @see `UniqueNumberSystem.RegistrationProcess(Option[GUIDRequest], Int, Int)` */ private def NoCallbackGetSpecificNumber(number : Int, poolName : String) : Unit = { - poolActors.get(poolName) match { - case Some(pool) => - pool ! NumberPoolActor.GetSpecificNumber(number, Some(Long.MinValue)) - case None => - log.error(s"critical: tried to re-register number $number but did not find pool $poolName") - } + poolActors(poolName) ! NumberPoolActor.GetSpecificNumber(number, Some(Long.MinValue)) } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala index 7b078dc97..844da483b 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala @@ -6,9 +6,9 @@ import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.zones.Zone import net.psforever.packet.game.PlanetSideGUID -import net.psforever.types.PlanetSideEmpire +import net.psforever.types.{PlanetSideEmpire, Vector3} -class Building(private val mapId : Int, private val zone : Zone) extends PlanetSideServerObject { +class Building(private val mapId : Int, private val zone : Zone, private val buildingType : StructureType.Value) extends PlanetSideServerObject { /** * The mapId is the identifier number used in BuildingInfoUpdateMessage. * The modelId is the identifier number used in SetEmpireMessage. @@ -46,25 +46,35 @@ class Building(private val mapId : Int, private val zone : Zone) extends PlanetS ModelId } + def BuildingType : StructureType.Value = buildingType + def Definition: ObjectDefinition = Building.BuildingDefinition } object Building { - final val NoBuilding : Building = new Building(0, Zone.Nowhere) { + final val NoBuilding : Building = new Building(0, Zone.Nowhere, StructureType.Platform) { override def Faction_=(faction : PlanetSideEmpire.Value) : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL override def Amenities_=(obj : Amenity) : List[Amenity] = Nil } final val BuildingDefinition : ObjectDefinition = new ObjectDefinition(0) { Name = "building" } - def apply(id : Int, zone : Zone) : Building = { - new Building(id, zone) + def apply(id : Int, zone : Zone, buildingType : StructureType.Value) : Building = { + new Building(id, zone, buildingType) } - def Structure(id : Int, zone : Zone, context : ActorContext) : Building = { + def Structure(buildingType : StructureType.Value, location : Vector3)(id : Int, zone : Zone, context : ActorContext) : Building = { import akka.actor.Props - val obj = new Building(id, zone) - obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-building") + val obj = new Building(id, zone, buildingType) + obj.Position = location + obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building") + obj + } + + def Structure(buildingType : StructureType.Value)(id : Int, zone : Zone, context : ActorContext) : Building = { + import akka.actor.Props + val obj = new Building(id, zone, buildingType) + obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building") obj } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/StructureType.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/StructureType.scala new file mode 100644 index 000000000..7acc1840a --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/StructureType.scala @@ -0,0 +1,20 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.structures + +/** + * An `Enumeration` of the kinds of building structures found in the game. + * This is merely a kludge for more a future, more complicated internal object that handles base operations. + */ +object StructureType extends Enumeration { + type Type = Value + + val + Bridge, + Building, //generic + Bunker, + Facility, + Platform, //outdoor amenities like the spawn pads in sanctuary + Tower, + WarpGate + = Value +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala index 784685377..bf23cc0d1 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala @@ -4,7 +4,7 @@ package net.psforever.objects.serverobject.structures import akka.actor.ActorContext import net.psforever.objects.zones.Zone -class WarpGate(id : Int, zone : Zone) extends Building(id, zone) { +class WarpGate(id : Int, zone : Zone) extends Building(id, zone, StructureType.WarpGate) { //TODO stuff later } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala index 94fef0fcd..2c062abb7 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala @@ -339,8 +339,8 @@ object EquipmentTerminalDefinition { import net.psforever.objects.Loadout._ entry match { case obj : ShorthandTool => - val ammo : List[AmmoBoxDefinition] = obj.ammo.map(fmode => { fmode.ammo.adef }) - val tool = Tool(obj.tdef) + val ammo : List[AmmoBoxDefinition] = obj.ammo.map(fmode => { fmode.ammo.definition }) + val tool = Tool(obj.definition) //makes Tools where an ammo slot may have one of its alternate ammo types (0 until tool.MaxAmmoSlot).foreach(index => { val slot = tool.AmmoSlots(index) @@ -350,16 +350,16 @@ object EquipmentTerminalDefinition { tool case obj : ShorthandAmmoBox => - MakeAmmoBox(obj.adef, Some(obj.capacity)) + MakeAmmoBox(obj.definition, Some(obj.capacity)) case obj : ShorthandConstructionItem => - MakeConstructionItem(obj.cdef) + MakeConstructionItem(obj.definition) case obj : ShorthandSimpleItem => - MakeSimpleItem(obj.sdef) + MakeSimpleItem(obj.definition) case obj : ShorthandKit => - MakeKit(obj.kdef) + MakeKit(obj.definition) } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/MatrixTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/MatrixTerminalDefinition.scala index 047e786d5..292e30b24 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/MatrixTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/MatrixTerminalDefinition.scala @@ -19,8 +19,11 @@ class MatrixTerminalDefinition(object_id : Int) extends TerminalDefinition(objec else if(object_id == 519) { "matrix_terminalc" } + else if(object_id == 812) { + "spawn_terminal" + } else { - throw new IllegalArgumentException("terminal must be object id 517-519") + throw new IllegalArgumentException("terminal must be object id 517-519 or 812") } override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal() diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/SpawnTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/SpawnTerminalDefinition.scala deleted file mode 100644 index 8483a2c37..000000000 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/SpawnTerminalDefinition.scala +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.serverobject.terminals - -import net.psforever.objects.Player -import net.psforever.packet.game.ItemTransactionMessage - -/** - * The definition for any `Terminal` that is of a type "spawn_terminal." - * A "spawn_terminal" is somewhat like the `matrix_terminalc` of an advanced mobile spawn unit, but inside of facilities. - */ -class SpawnTerminalDefinition extends TerminalDefinition(812) { - Name = "spawn_terminal" - - override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal() -} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala index 4bc80f06d..91aff35df 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala @@ -1,16 +1,26 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.tube -import net.psforever.objects.definition.ObjectDefinition +import net.psforever.objects.GlobalDefinitions import net.psforever.objects.serverobject.structures.Amenity -class SpawnTube(tubeDef : ObjectDefinition) extends Amenity { - def Definition : ObjectDefinition = tubeDef +/** + * An owned server object that is used as a placeholder for the position and direction + * that infantry will be arranged upon spawning into the game world. + * @param tDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields + */ +class SpawnTube(tDef : SpawnTubeDefinition) extends Amenity { + def Definition : SpawnTubeDefinition = tDef } object SpawnTube { - def apply(tubeDef : ObjectDefinition) : SpawnTube = { - new SpawnTube(tubeDef) + /** + * Overloaded constructor. + * @param tDef the spawn tube's definition entry + * @return a `SpawnTube` object + */ + def apply(tDef : SpawnTubeDefinition) : SpawnTube = { + new SpawnTube(tDef) } import akka.actor.ActorContext @@ -24,13 +34,25 @@ object SpawnTube { * @return the `SpawnTube` object */ def Constructor(pos : Vector3, orient : Vector3)(id : Int, context : ActorContext) : SpawnTube = { - import akka.actor.Props - import net.psforever.objects.GlobalDefinitions + Constructor(GlobalDefinitions.respawn_tube, pos, orient)(id, context) + } - val obj = SpawnTube(GlobalDefinitions.ams_respawn_tube) + /** + * Instantiate an configure a `SpawnTube` object with the given definition + * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields + * @param pos the position (used to determine spawn point) + * @param orient the orientation (used to indicate spawn direction) + * @param id the unique id that will be assigned to this entity + * @param context a context to allow the object to properly set up `ActorSystem` functionality + * @return the `SpawnTube` object + */ + def Constructor(tdef : SpawnTubeDefinition, pos : Vector3, orient : Vector3)(id : Int, context : ActorContext) : SpawnTube = { + import akka.actor.Props + + val obj = SpawnTube(tdef) obj.Position = pos obj.Orientation = orient - obj.Actor = context.actorOf(Props(classOf[SpawnTubeControl], obj), s"${GlobalDefinitions.ams_respawn_tube.Name}_$id") + obj.Actor = context.actorOf(Props(classOf[SpawnTubeControl], obj), s"${tdef.Name}_$id") obj } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala index 774449c03..1cb1279dd 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala @@ -6,7 +6,24 @@ import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.definition.converter.SpawnTubeConverter import net.psforever.objects.serverobject.structures.Amenity +/** + * The definition for any `VehicleSpawnPad`. + * Currently, all tubes identify as object id 49 - `ams_respawn_tube` - configured manually. + * @see `GlobalDefinitions.ams_respawn_tube` + */ class SpawnTubeDefinition(object_id : Int) extends ObjectDefinition(object_id) { + Name = if(object_id == 49) { + "ams_respawn_tube" + } + else if(object_id == 732) { + "respawn_tube" + } + else if(object_id == 733) { + "respawn_tube_tower" + } + else { + throw new IllegalArgumentException("terminal must be object id 49, 732, or 733") + } Packet = new SpawnTubeConverter } diff --git a/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala b/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala index 88af2e5c2..a1b79e7ce 100644 --- a/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala +++ b/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala @@ -41,7 +41,7 @@ class InterstellarCluster(zones : List[Zone]) extends Actor { def receive : Receive = { case InterstellarCluster.GetWorld(zoneId) => log.info(s"Asked to find $zoneId") - findWorldInCluster(zones.iterator, zoneId) match { + recursiveFindWorldInCluster(zones.iterator, _.Id == zoneId) match { case Some(continent) => sender ! InterstellarCluster.GiveWorld(zoneId, continent) case None => @@ -52,26 +52,35 @@ class InterstellarCluster(zones : List[Zone]) extends Actor { zones.foreach(zone => { sender ! Zone.ClientInitialization(zone.ClientInitialization()) }) sender ! InterstellarCluster.ClientInitializationComplete() //will be processed after all Zones + case msg @ Zone.Lattice.RequestSpawnPoint(zone_number, _, _) => + recursiveFindWorldInCluster(zones.iterator, _.Number == zone_number) match { + case Some(zone) => + zone.Actor forward msg + + case None => //zone_number does not exist + sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None) + } + case _ => ; } /** * Search through the `List` of `Zone` entities and find the one with the matching designation. * @param iter an `Iterator` of `Zone` entities - * @param zoneId the name of the `Zone` + * @param predicate a condition to check against to determine when the appropriate `Zone` is discovered * @return the discovered `Zone` */ - @tailrec private def findWorldInCluster(iter : Iterator[Zone], zoneId : String) : Option[Zone] = { + @tailrec private def recursiveFindWorldInCluster(iter : Iterator[Zone], predicate : Zone=>Boolean) : Option[Zone] = { if(!iter.hasNext) { None } else { val cont = iter.next - if(cont.Id == zoneId) { + if(predicate.apply(cont)) { Some(cont) } else { - findWorldInCluster(iter, zoneId) + recursiveFindWorldInCluster(iter, predicate) } } } @@ -104,3 +113,31 @@ object InterstellarCluster { */ final case class ClientInitializationComplete() } + +/* +// List[Building] --> List[List[(Amenity, Building)]] --> List[(SpawnTube*, Building)] +zone.LocalLattice.Buildings.values + .filter(_.Faction == player.Faction) + .map(building => { building.Amenities.map { _ -> building } }) + .flatMap( _.filter({ case(amenity, _) => amenity.isInstanceOf[SpawnTube] }) ) + */ + +/* +zone.Buildings.values.filter(building => { + ( + if(spawn_zone == 6) { Set(StructureType.Tower) } + else if(spawn_zone == 7) { Set(StructureType.Facility, StructureType.Building) } + else { Set.empty[StructureType.Value] } + ).contains(building.BuildingType) && + building.Amenities.exists(_.isInstanceOf[SpawnTube]) && + building.Faction == player.Faction && + building.Position != Vector3.Zero +}) + .toSeq + .sortBy(building => { + Vector3.DistanceSquared(player.Position, building.Position) < Vector3.DistanceSquared(player.Position, building.Position) + }) + .map(building => { building.Amenities.map { _ -> building } }) + .flatMap( _.filter({ case(amenity, _) => amenity.isInstanceOf[SpawnTube] }) ) +).headOption + */ diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala index 7281891d7..37254fb70 100644 --- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -10,6 +10,7 @@ import net.psforever.objects.guid.actor.UniqueNumberSystem import net.psforever.objects.guid.selector.RandomSelector import net.psforever.objects.guid.source.LimitedNumberSource import net.psforever.objects.serverobject.structures.{Amenity, Building} +import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.Vector3 @@ -58,11 +59,13 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { /** */ private val players : TrieMap[Avatar, Option[Player]] = TrieMap[Avatar, Option[Player]]() /** */ - private var corpses : List[Player] = List[Player]() + private val corpses : ListBuffer[Player] = ListBuffer[Player]() /** */ private var population : ActorRef = ActorRef.noSender private var buildings : PairMap[Int, Building] = PairMap.empty[Int, Building] + /** key - spawn zone id, value - buildings belonging to spawn zone */ + private var spawnGroups : Map[Building, List[SpawnTube]] = PairMap[Building, List[SpawnTube]]() /** * Establish the basic accessible conditions necessary for a functional `Zone`.
@@ -87,11 +90,12 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { accessor = context.actorOf(RandomPool(25).props(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystem.AllocateNumberPoolActors(guid))), s"$Id-uns") ground = context.actorOf(Props(classOf[ZoneGroundActor], equipmentOnGround), s"$Id-ground") transport = context.actorOf(Props(classOf[ZoneVehicleActor], this), s"$Id-vehicles") - population = context.actorOf(Props(classOf[ZonePopulationActor], this), s"$Id-players") + population = context.actorOf(Props(classOf[ZonePopulationActor], this, players, corpses), s"$Id-players") Map.LocalObjects.foreach({ builderObject => builderObject.Build }) MakeBuildings(context) AssignAmenities() + CreateSpawnGroups() } } @@ -191,7 +195,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { def LivePlayers : List[Player] = players.values.collect( { case Some(tplayer) => tplayer }).toList - def Corpses : List[Player] = corpses + def Corpses : List[Player] = corpses.toList def AddVehicle(vehicle : Vehicle) : List[Vehicle] = { vehicles = vehicles :+ vehicle @@ -222,78 +226,6 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { } } - def PopulationJoin(avatar : Avatar) : Boolean = { - players.get(avatar) match { - case Some(_) => - false - case None => - players += avatar -> None - true - } - } - - def PopulationLeave(avatar : Avatar) : Option[Player] = { - players.remove(avatar) match { - case None => - None - case Some(tplayer) => - tplayer - } - } - - def PopulationSpawn(avatar : Avatar, player : Player) : Option[Player] = { - players.get(avatar) match { - case None => - None - case Some(tplayer) => - tplayer match { - case Some(aplayer) => - Some(aplayer) - case None => - players(avatar) = Some(player) - Some(player) - } - } - } - - def PopulationRelease(avatar : Avatar) : Option[Player] = { - players.get(avatar) match { - case None => - None - case Some(tplayer) => - players(avatar) = None - tplayer - } - } - - def CorpseAdd(player : Player) : Unit = { - if(player.isBackpack && recursiveFindCorpse(players.values.filter(_.nonEmpty).map(_.get).iterator, player.GUID).isEmpty) { - corpses = corpses :+ player - } - } - - def CorpseRemove(player : Player) : Unit = { - recursiveFindCorpse(corpses.iterator, player.GUID) match { - case Some(index) => - corpses = corpses.take(index-1) ++ corpses.drop(index) - case None => ; - } - } - - @tailrec final def recursiveFindCorpse(iter : Iterator[Player], guid : PlanetSideGUID, index : Int = 0) : Option[Int] = { - if(!iter.hasNext) { - None - } - else { - if(iter.next.GUID == guid) { - Some(index) - } - else { - recursiveFindCorpse(iter, guid, index + 1) - } - } - } - /** * Coordinate `Equipment` that has been dropped on the ground or to-be-dropped on the ground. * @return synchronized reference to the ground @@ -326,26 +258,55 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { }) } + private def CreateSpawnGroups() : Unit = { + buildings.values + .filterNot { _.Position == Vector3.Zero } + .map(building => { building -> building.Amenities.collect { case(obj : SpawnTube) => obj } }) + .filter( { case((_, spawns)) => spawns.nonEmpty }) + .foreach { SpawnGroups } + } + + def SpawnGroups() : Map[Building, List[SpawnTube]] = spawnGroups + + def SpawnGroups(building : Building) : List[SpawnTube] = SpawnGroups(building.Id) + + def SpawnGroups(buildingId : Int) : List[SpawnTube] = { + spawnGroups.find({ case((building, _)) => building.Id == buildingId }) match { + case Some((_, list)) => + list + case None => + List.empty[SpawnTube] + } + } + + def SpawnGroups(spawns : (Building, List[SpawnTube])) : Map[Building, List[SpawnTube]] = { + val (building, tubes) = spawns + val entry : Map[Building, List[SpawnTube]] = PairMap(building -> tubes) + spawnGroups = spawnGroups ++ entry + entry + } + /** * Provide bulk correspondence on all map entities that can be composed into packet messages and reported to a client. * These messages are sent in this fashion at the time of joining the server:
- * - `BroadcastWarpgateUpdateMessage`
* - `BuildingInfoUpdateMessage`
+ * - `DensityLevelUpdateMessage`
+ * - `BroadcastWarpgateUpdateMessage`
* - `CaptureFlagUpdateMessage`
* - `ContinentalLockUpdateMessage`
- * - `DensityLevelUpdateMessage`
* - `ModuleLimitsMessage`
* - `VanuModuleUpdateMessage`
* - `ZoneForcedCavernConnectionMessage`
* - `ZoneInfoMessage`
* - `ZoneLockInfoMessage`
* - `ZonePopulationUpdateMessage` - * @return a `List` of `GamePacket` messages + * @return the `Zone` object */ def ClientInitialization() : Zone = this } object Zone { + /** Default value, non-zone area. */ final val Nowhere : Zone = new Zone("nowhere", new ZoneMap("nowhere"), 99) /** @@ -355,20 +316,96 @@ object Zone { final case class Init() object Population { + /** + * Message that introduces a user, by their `Avatar`, into a `Zone`. + * That user will be counted as part of that zone's population. + * The `avatar` may associate `Player` objects with itself in the future. + * @param avatar the `Avatar` object + */ final case class Join(avatar : Avatar) + /** + * Message that excuses a user, by their `Avatar`, into a `Zone`. + * That user will not longer be counted as part of that zone's population. + * @see `PlayerHasLeft` + * @param avatar the `Avatar` object + */ final case class Leave(avatar : Avatar) + /** + * Message that instructs the zone to disassociate a `Player` from this `Actor`. + * @see `PlayerAlreadySpawned`
+ * `PlayerCanNotSpawn` + * @param avatar the `Avatar` object + * @param player the `Player` object + */ final case class Spawn(avatar : Avatar, player : Player) + /** + * Message that instructs the zone to disassociate a `Player` from this `Actor`. + * @see `PlayerHasLeft` + * @param avatar the `Avatar` object + */ final case class Release(avatar : Avatar) + /** + * Message that acts in reply to `Leave(avatar)` or `Release(avatar)`. + * In the former case, the avatar will have successfully left the zone, and `player` may be defined. + * In the latter case, the avatar did not initially `Join` the zone, and `player` is `None`. + * This message should not be considered a failure or a success case. + * @see `Release`
+ * `Leave` + * @param zone the `Zone` object + * @param player the `Player` object + */ final case class PlayerHasLeft(zone : Zone, player : Option[Player]) //Leave(avatar), but still has a player - final case class PlayerAlreadySpawned(player : Player) //Spawn(avatar, player), but avatar already has a player - final case class PlayerCanNotSpawn(zone : Zone, player : Player) //Spawn(avatar, player), but avatar did not initially Join(avatar) + /** + * Message that acts in reply to `Spawn(avatar, player)`, but the avatar already has a player. + * @param player the `Player` object + */ + final case class PlayerAlreadySpawned(player : Player) + /** + * Message that acts in reply to `Spawn(avatar, player)`, but the avatar did not initially `Join` this zone. + * @param zone the `Zone` object + * @param player the `Player` object + */ + final case class PlayerCanNotSpawn(zone : Zone, player : Player) } object Corpse { + /** + * Message that reports to the zone of a freshly dead player. + * @param player the dead `Player` + */ final case class Add(player : Player) + /** + * Message that tells the zone to no longer mind the dead player. + * @param player the dead `Player` + */ final case class Remove(player : Player) } + object Lattice { + /** + * Message requesting that the current zone determine where a `player` can spawn. + * @param zone_number this zone's numeric identifier + * @param player the `Player` object + * @param spawn_group the category of spawn points the request wants searched + */ + final case class RequestSpawnPoint(zone_number : Int, player : Player, spawn_group : Int) + /** + * Message that returns a discovered spawn point to a request source. + * @param zone_id the zone's text identifier + * @param building the `Building` in which the spawnpoint is located + * @param spawn_tube the spawn point holding object + */ + final case class SpawnPoint(zone_id : String, building : Building, spawn_tube : SpawnTube) + /** + * Message that informs a request source that a spawn point could not be discovered with the previous criteria. + * @param zone_number this zone's numeric identifier + * @param spawn_group the spawn point holding object; + * if `None`, then the previous `zone_number` could not be found; + * otherwise, no spawn points could be found in the zone + */ + final case class NoValidSpawnPoint(zone_number : Int, spawn_group : Option[Int]) + } + /** * Message to relinguish an item and place in on the ground. * @param item the piece of `Equipment` diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala index 92d36827d..0f93d9183 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala @@ -1,8 +1,12 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.zones +import java.util.concurrent.atomic.AtomicInteger + import akka.actor.Actor import net.psforever.objects.PlanetSideGameObject +import net.psforever.objects.serverobject.structures.StructureType +import net.psforever.types.Vector3 import org.log4s.Logger /** @@ -12,29 +16,108 @@ import org.log4s.Logger class ZoneActor(zone : Zone) extends Actor { private[this] val log = org.log4s.getLogger - def receive : Receive = { + def receive : Receive = Init + + def Init : Receive = { case Zone.Init() => zone.Init ZoneSetupCheck() + context.become(Processing) + + case _ => ; + } + + def Processing : Receive = { + //frwd to Population Actor + case msg @ Zone.Population.Join => + zone.Population forward msg + + case msg @ Zone.Population.Leave => + zone.Population forward msg + + case msg @ Zone.Population.Spawn => + zone.Population forward msg + + case msg @ Zone.Population.Release => + zone.Population forward msg + + case msg @ Zone.Corpse.Add => + zone.Population forward msg + + case msg @ Zone.Corpse.Remove => + zone.Population forward msg + + //frwd to Ground Actor + case msg @ Zone.DropItemOnGround => + zone.Ground forward msg + + case msg @ Zone.GetItemOnGround => + zone.Ground forward msg + + //frwd to Vehicle Actor + case msg @ Zone.SpawnVehicle => + zone.Transport forward msg + + case msg @ Zone.DespawnVehicle => + zone.Transport forward msg + + //own + case Zone.Lattice.RequestSpawnPoint(zone_number, player, spawn_group) => + if(zone_number == zone.Number) { + val buildingTypeSet = if(spawn_group == 6) { + Set(StructureType.Tower) + } + else if(spawn_group == 7) { + Set(StructureType.Facility, StructureType.Building) + } + else { + Set.empty[StructureType.Value] + } + val playerPosition = player.Position.xy + zone.SpawnGroups() + .filter({ case((building, _)) => + building.Faction == player.Faction && buildingTypeSet.contains(building.BuildingType) + }) + .toSeq + .sortBy({ case ((building, _)) => + Vector3.DistanceSquared(playerPosition, building.Position.xy) + }) + .headOption match { + case Some((building, List(tube))) => + sender ! Zone.Lattice.SpawnPoint(zone.Id, building, tube) + + case Some((building, tubes)) => + sender ! Zone.Lattice.SpawnPoint(zone.Id, building, scala.util.Random.shuffle(tubes).head) + + case None => + sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, Some(spawn_group)) + } + } + else { //wrong zone_number + sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None) + } case msg => log.warn(s"Received unexpected message - $msg") } - def ZoneSetupCheck(): Unit = { + def ZoneSetupCheck() : Int = { import ZoneActor._ - def guid(id : Int) = zone.GUID(id) val map = zone.Map + def guid(id : Int) = zone.GUID(id) val slog = org.log4s.getLogger(s"zone/${zone.Id}/sanity") - val validateObject : (Int, (PlanetSideGameObject)=>Boolean, String) => Boolean = ValidateObject(guid, slog) + val errors = new AtomicInteger(0) + val validateObject : (Int, (PlanetSideGameObject)=>Boolean, String) => Boolean = ValidateObject(guid, slog, errors) //check base to object associations map.ObjectToBuilding.foreach({ case((object_guid, building_id)) => if(zone.Building(building_id).isEmpty) { slog.error(s"expected a building at id #$building_id") + errors.incrementAndGet() } if(guid(object_guid).isEmpty) { slog.error(s"expected object id $object_guid to exist, but it did not") + errors.incrementAndGet() } }) @@ -55,11 +138,11 @@ class ZoneActor(zone : Zone) extends Actor { validateObject(mech_guid, ImplantMechCheck, "implant terminal mech") validateObject(interface_guid, TerminalCheck, "implant terminal interface") }) + errors.intValue() } } object ZoneActor { - /** * Recover an object from a collection and perform any number of validating tests upon it. * If the object fails any tests, log an error. @@ -73,11 +156,12 @@ object ZoneActor { * @return `true` if the object was discovered and validates correctly; * `false` if the object failed any tests */ - def ValidateObject(guid : (Int)=>Option[PlanetSideGameObject], elog : Logger) + def ValidateObject(guid : (Int)=>Option[PlanetSideGameObject], elog : Logger, errorCounter : AtomicInteger) (object_guid : Int, test : (PlanetSideGameObject)=>Boolean, description : String) : Boolean = { try { if(!test(guid(object_guid).get)) { elog.error(s"expected id $object_guid to be a $description, but it was not") + errorCounter.incrementAndGet() false } else { @@ -87,6 +171,7 @@ object ZoneActor { catch { case e : Exception => elog.error(s"expected a $description at id $object_guid but no object is initialized - $e") + errorCounter.incrementAndGet() false } } diff --git a/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala index 6787166c4..76840df88 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala @@ -2,42 +2,183 @@ package net.psforever.objects.zones import akka.actor.Actor +import net.psforever.objects.{Avatar, Player} + +import scala.annotation.tailrec +import scala.collection.concurrent.TrieMap +import scala.collection.mutable.ListBuffer + +/** + * A support `Actor` that sequences adding and removing `Avatar` and `Player` objects to mappings and lists. + * The former mapping is considered to represent every user connect to the `zone` (`as Avatar` objects) + * and their current representation (as `Player` objects). + * The latter list keeps track of a group of former user representations. + * @param zone the `Zone` object + * @param playerMap the mapping of `Avatar` objects to `Player` objects + * @param corpseList a list of `Player` objects + */ +class ZonePopulationActor(zone : Zone, playerMap : TrieMap[Avatar, Option[Player]], corpseList : ListBuffer[Player]) extends Actor { + import ZonePopulationActor._ -class ZonePopulationActor(zone : Zone) extends Actor { def receive : Receive = { case Zone.Population.Join(avatar) => - zone.PopulationJoin(avatar) + PopulationJoin(avatar, playerMap) case Zone.Population.Leave(avatar) => - zone.PopulationLeave(avatar) match { + PopulationLeave(avatar, playerMap) match { case None => ; case player @ Some(_) => sender ! Zone.Population.PlayerHasLeft(zone, player) } case Zone.Population.Spawn(avatar, player) => - zone.PopulationSpawn(avatar, player) match { + PopulationSpawn(avatar, player, playerMap) match { case Some(tplayer) => - if(tplayer != player) { - sender ! Zone.Population.PlayerCanNotSpawn(zone, player) + if(tplayer ne player) { + sender ! Zone.Population.PlayerAlreadySpawned(player) } case None => sender ! Zone.Population.PlayerCanNotSpawn(zone, player) } case Zone.Population.Release(avatar) => - zone.PopulationRelease(avatar) match { + PopulationRelease(avatar, playerMap) match { case Some(_) => ; case None => sender ! Zone.Population.PlayerHasLeft(zone, None) } case Zone.Corpse.Add(player) => - zone.CorpseAdd(player) + CorpseAdd(player, corpseList) case Zone.Corpse.Remove(player) => - zone.CorpseRemove(player) + CorpseRemove(player, corpseList) case _ => ; } } + +object ZonePopulationActor { + /** + * Add an `avatar` as the key of an `Avatar` to `Player` object pair in the given collection. + * @param avatar an `Avatar` object + * @param playerMap the mapping of `Avatar` objects to `Player` objects + * @return true, if the mapping is for a new key; + * false, if the key already exists + */ + def PopulationJoin(avatar : Avatar, playerMap : TrieMap[Avatar, Option[Player]]) : Boolean = { + playerMap.get(avatar) match { + case Some(_) => + false + case None => + playerMap += avatar -> None + true + } + } + /** + * Remove an `avatar` from the key of an `Avatar` to `Player` object pair in the given collection. + * If a `Player` object is associated at the time, return it safely. + * @param avatar an `Avatar` object + * @param playerMap the mapping of `Avatar` objects to `Player` objects + * @return any `Player` object that was associated at the time the `avatar` was removed + */ + def PopulationLeave(avatar : Avatar, playerMap : TrieMap[Avatar, Option[Player]]) : Option[Player] = { + playerMap.remove(avatar) match { + case None => + None + case Some(tplayer) => + tplayer + } + } + + /** + * Associate a `Player` object as a value to an existing `Avatar` object that will be its key. + * Do not overwrite players that are already associated. + * @param avatar an `Avatar` object + * @param player a `Player` object + * @param playerMap the mapping of `Avatar` objects to `Player` objects + * @return the `Player` object that is associated with the `Avatar` key + */ + def PopulationSpawn(avatar : Avatar, player : Player, playerMap : TrieMap[Avatar, Option[Player]]) : Option[Player] = { + playerMap.get(avatar) match { + case None => + None + case Some(tplayer) => + tplayer match { + case Some(aplayer) => + Some(aplayer) + case None => + playerMap(avatar) = Some(player) + Some(player) + } + } + } + + /** + * Disassociate a `Player` object from an existing `Avatar` object that was be its key. + * @param avatar an `Avatar` object + * @param playerMap the mapping of `Avatar` objects to `Player` objects + * @return any `Player` object that is associated at the time + */ + def PopulationRelease(avatar : Avatar, playerMap : TrieMap[Avatar, Option[Player]]) : Option[Player] = { + playerMap.get(avatar) match { + case None => + None + case Some(tplayer) => + playerMap(avatar) = None + tplayer + } + } + + /** + * If the given `player` passes a condition check, add it to the list. + * @param player a `Player` object + * @param corpseList a list of `Player` objects + * @return true, if the `player` was added to the list; + * false, otherwise + */ + def CorpseAdd(player : Player, corpseList : ListBuffer[Player]) : Boolean = { + if(player.isBackpack) { + corpseList += player + true + } + else { + false + } + } + + /** + * Remove the given `player` from the list. + * @param player a `Player` object + * @param corpseList a list of `Player` objects + */ + def CorpseRemove(player : Player, corpseList : ListBuffer[Player]) : Unit = { + recursiveFindCorpse(corpseList.iterator, player) match { + case None => ; + case Some(index) => + corpseList.remove(index) + } + } + + /** + * A recursive function that finds and removes a specific player from a list of players. + * @param iter an `Iterator` of `Player` objects + * @param player the target `Player` + * @param index the index of the discovered `Player` object + * @return the index of the `Player` object in the list to be removed; + * `None`, otherwise + */ + @tailrec final def recursiveFindCorpse(iter : Iterator[Player], player : Player, index : Int = 0) : Option[Int] = { + if(!iter.hasNext) { + None + } + else { + if(iter.next == player) { + Some(index) + } + else { + recursiveFindCorpse(iter, player, index + 1) + } + } + } +} diff --git a/common/src/main/scala/net/psforever/packet/game/DisconnectMessage.scala b/common/src/main/scala/net/psforever/packet/game/DisconnectMessage.scala index ddd65b5a0..4c1ec29b1 100644 --- a/common/src/main/scala/net/psforever/packet/game/DisconnectMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/DisconnectMessage.scala @@ -20,8 +20,8 @@ import scodec.codecs._ * @param unk3 na */ final case class DisconnectMessage(msg : String, - unk2 : String = "", - unk3 : String = "") + unk2 : String, + unk3 : String) extends PlanetSideGamePacket { type Packet = DisconnectMessage def opcode = GamePacketOpcode.DisconnectMessage @@ -29,6 +29,15 @@ final case class DisconnectMessage(msg : String, } object DisconnectMessage extends Marshallable[DisconnectMessage] { + /** + * Overloaded constructor that focuses only on the visible disconnection message + * @param msg the displayed message + * @return a `DisconnectMessage` object + */ + def apply(msg : String) : DisconnectMessage = { + new DisconnectMessage(msg, "", "") + } + implicit val codec : Codec[DisconnectMessage] = ( ("msg" | PacketHelpers.encodedString) :: ("unk2" | PacketHelpers.encodedString) :: diff --git a/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala b/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala index 045aa6d0c..c3049546f 100644 --- a/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala @@ -6,12 +6,16 @@ import scodec.Codec import scodec.codecs._ /** - * - * @param unk1 na - * @param unk2 na + * na + * @param unk1 when defined, na; + * non-zero when selecting the sanctuary option from a non-sanctuary continent deployment map + * @param unk2 when defined, indicates type of spawn point by destination; + * 0 is unknown (may refer to all available spawns regardless of last position); + * 6 is towers; + * 7 is facilities * @param unk3 na * @param unk4 na - * @param unk5 continent? + * @param unk5 when defined, the continent number */ final case class SpawnRequestMessage(unk1 : Int, unk2 : Long, diff --git a/common/src/main/scala/net/psforever/types/Vector3.scala b/common/src/main/scala/net/psforever/types/Vector3.scala index 3b2b05833..e501d635f 100644 --- a/common/src/main/scala/net/psforever/types/Vector3.scala +++ b/common/src/main/scala/net/psforever/types/Vector3.scala @@ -9,8 +9,8 @@ final case class Vector3(x : Float, y : Float, z : Float) { /** - * Operator override for vector addition, treating `Vector3` objects as actual mathematical vectors. - * The application of this overload is "vector1 + vector2." + * Operator for vector addition, treating `Vector3` objects as actual mathematical vectors. + * The application of this definition is "vector1 + vector2." * @param vec the other `Vector3` object * @return a new `Vector3` object with the summed values */ @@ -19,8 +19,8 @@ final case class Vector3(x : Float, } /** - * Operator override for vector subtraction, treating `Vector3` objects as actual mathematical vectors. - * The application of this overload is "vector1 - vector2." + * Operator for vector subtraction, treating `Vector3` objects as actual mathematical vectors. + * The application of this definition is "vector1 - vector2." * @param vec the other `Vector3` object * @return a new `Vector3` object with the difference values */ @@ -29,7 +29,7 @@ final case class Vector3(x : Float, } /** - * Operator override for vector scaling, treating `Vector3` objects as actual mathematical vectors. + * Operator for vector scaling, treating `Vector3` objects as actual mathematical vectors. * The application of this overload is "vector * scalar" exclusively. * "scalar * vector" is invalid. * @param scalar the value to multiply this vector @@ -38,6 +38,14 @@ final case class Vector3(x : Float, def *(scalar : Float) : Vector3 = { Vector3(x*scalar, y*scalar, z*scalar) } + + /** + * Operator for returning the ground-planar coordinates + * and ignoring the perpendicular distance from the world floor. + * The application of this definition is "vector.xy" or "vector xy." + * @return a new `Vector3` object with only two of the components of the original + */ + def xy : Vector3 = Vector3(x, y, 0) } object Vector3 { diff --git a/common/src/test/scala/Vector3Test.scala b/common/src/test/scala/Vector3Test.scala index 75f1c2e53..26fcddd51 100644 --- a/common/src/test/scala/Vector3Test.scala +++ b/common/src/test/scala/Vector3Test.scala @@ -64,6 +64,11 @@ class Vector3Test extends Specification { vec * 3f mustEqual Vector3(3.8999999f, -7.7999997f, 11.700001f) } + "separate into x-component and y-component only" in { + val obj = Vector3(1.1f, 2.2f, 3.3f) + obj.xy mustEqual Vector3(1.1f, 2.2f, 0f) + } + "calculate the unit vector (zero)" in { Vector3.Unit(Vector3.Zero) mustEqual Vector3(0,0,0) } diff --git a/common/src/test/scala/objects/AvatarTest.scala b/common/src/test/scala/objects/AvatarTest.scala new file mode 100644 index 000000000..95fb698fe --- /dev/null +++ b/common/src/test/scala/objects/AvatarTest.scala @@ -0,0 +1,397 @@ +// Copyright (c) 2017 PSForever +package objects + +import net.psforever.objects.GlobalDefinitions._ +import net.psforever.objects._ +import net.psforever.objects.definition.ImplantDefinition +import net.psforever.types.{CharacterGender, ImplantType, PlanetSideEmpire} +import org.specs2.mutable._ + +class AvatarTest extends Specification { + def CreatePlayer() : (Player, Avatar) = { + val avatar = Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) + val + player = Player(avatar) + player.Slot(0).Equipment = Tool(beamer) + player.Slot(2).Equipment = Tool(suppressor) + player.Slot(4).Equipment = Tool(forceblade) + player.Slot(6).Equipment = AmmoBox(bullet_9mm) + player.Slot(9).Equipment = AmmoBox(bullet_9mm) + player.Slot(12).Equipment = AmmoBox(bullet_9mm) + player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) + player.Slot(36).Equipment = AmmoBox(energy_cell) + player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) + (player, avatar) + } + + "construct" in { + val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + av.name mustEqual "Chord" + av.faction mustEqual PlanetSideEmpire.TR + av.sex mustEqual CharacterGender.Male + av.head mustEqual 0 + av.voice mustEqual 5 + av.BEP mustEqual 0 + av.CEP mustEqual 0 + av.Certifications mustEqual Set.empty + av.Definition.ObjectId mustEqual 121 + } + + "can maintain cumulative battle experience point values" in { + val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + av.BEP mustEqual 0 + av.BEP = 100 + av.BEP mustEqual 100 + av.BEP = 700 + av.BEP mustEqual 700 + } + + "can maintain battle experience point values up to a maximum (Long.MaxValue)" in { + val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + av.BEP mustEqual 0 + av.BEP = 4294967295L + av.BEP mustEqual 4294967295L + } + + "can not maintain battle experience point values below zero" in { + val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + av.BEP mustEqual 0 + av.BEP = -1 + av.BEP mustEqual 0 + av.BEP = 100 + av.BEP mustEqual 100 + av.BEP = -1 + av.BEP mustEqual 0 + } + + "can maintain cumulative command experience point values" in { + val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + av.CEP mustEqual 0 + av.CEP = 100 + av.CEP mustEqual 100 + av.CEP = 700 + av.CEP mustEqual 700 + } + + "can maintain command experience point values up to a maximum (Long.MaxValue)" in { + val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + av.CEP mustEqual 0 + av.CEP = 4294967295L + av.CEP mustEqual 4294967295L + } + + "can not maintain command experience point values below zero" in { + val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + av.CEP mustEqual 0 + av.CEP = -1 + av.CEP mustEqual 0 + av.CEP = 100 + av.CEP mustEqual 100 + av.CEP = -1 + av.CEP mustEqual 0 + } + + "can tell the difference between avatars" in { + (Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual true + + (Avatar("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + Avatar("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual false + + (Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + Avatar("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, 5)) mustEqual false + + (Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Female, 0, 5)) mustEqual false + + (Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 1, 5)) mustEqual false + + (Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 6)) mustEqual false + } + + //refer to ImplantTest.scala for more tests + "maximum of three implant slots" in { + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants.length mustEqual 3 + obj.Implants(0).Unlocked mustEqual false + obj.Implants(0).Initialized mustEqual false + obj.Implants(0).Active mustEqual false + obj.Implants(0).Implant mustEqual ImplantType.None + obj.Implant(0) mustEqual ImplantType.None + obj.Implants(0).Installed mustEqual None + obj.Implants(1).Unlocked mustEqual false + obj.Implants(1).Initialized mustEqual false + obj.Implants(1).Active mustEqual false + obj.Implants(1).Implant mustEqual ImplantType.None + obj.Implant(1) mustEqual ImplantType.None + obj.Implants(1).Installed mustEqual None + obj.Implants(2).Unlocked mustEqual false + obj.Implants(2).Initialized mustEqual false + obj.Implants(2).Active mustEqual false + obj.Implants(2).Implant mustEqual ImplantType.None + obj.Implant(2) mustEqual ImplantType.None + obj.Implants(2).Installed mustEqual None + + obj.Implant(3) mustEqual ImplantType.None //invalid slots beyond the third always reports as ImplantType.None + } + + "can install an implant" in { + val testplant : ImplantDefinition = ImplantDefinition(1) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.InstallImplant(testplant) mustEqual Some(0) + obj.Implants.find({p => p.Implant == ImplantType(1)}) match { //find the installed implant + case Some(slot) => + slot.Installed mustEqual Some(testplant) + case _ => + ko + } + ok + } + + "can install implants in sequential slots" in { + val testplant1 : ImplantDefinition = ImplantDefinition(1) + val testplant2 : ImplantDefinition = ImplantDefinition(2) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.Implants(1).Unlocked = true + + obj.InstallImplant(testplant1) mustEqual Some(0) + obj.InstallImplant(testplant2) mustEqual Some(1) + } + + "can not install the same type of implant twice" in { + val testplant1 : ImplantDefinition = ImplantDefinition(1) + val testplant2 : ImplantDefinition = ImplantDefinition(1) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.Implants(1).Unlocked = true + + obj.InstallImplant(testplant1) mustEqual Some(0) + obj.InstallImplant(testplant2) mustEqual None + } + + "can not install more implants than slots available (two unlocked)" in { + val testplant1 : ImplantDefinition = ImplantDefinition(1) + val testplant2 : ImplantDefinition = ImplantDefinition(2) + val testplant3 : ImplantDefinition = ImplantDefinition(3) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.Implants(1).Unlocked = true + + obj.InstallImplant(testplant1) mustEqual Some(0) + obj.InstallImplant(testplant2) mustEqual Some(1) + obj.InstallImplant(testplant3) mustEqual None + } + + "can not install more implants than slots available (four implants)" in { + val testplant1 : ImplantDefinition = ImplantDefinition(1) + val testplant2 : ImplantDefinition = ImplantDefinition(2) + val testplant3 : ImplantDefinition = ImplantDefinition(3) + val testplant4 : ImplantDefinition = ImplantDefinition(4) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.Implants(1).Unlocked = true + obj.Implants(2).Unlocked = true + + obj.InstallImplant(testplant1) mustEqual Some(0) + obj.InstallImplant(testplant2) mustEqual Some(1) + obj.InstallImplant(testplant3) mustEqual Some(2) + obj.InstallImplant(testplant4) mustEqual None + } + + "can uninstall an implant" in { + val testplant : ImplantDefinition = ImplantDefinition(1) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.InstallImplant(testplant) mustEqual Some(0) + obj.Implants(0).Installed mustEqual Some(testplant) + + obj.UninstallImplant(testplant.Type) mustEqual Some(0) + obj.Implants(0).Installed mustEqual None + } + + "can uninstall just a specific implant" in { + val testplant1 : ImplantDefinition = ImplantDefinition(1) + val testplant2 : ImplantDefinition = ImplantDefinition(2) + val testplant3 : ImplantDefinition = ImplantDefinition(3) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.Implants(1).Unlocked = true + obj.Implants(2).Unlocked = true + obj.InstallImplant(testplant1) mustEqual Some(0) + obj.InstallImplant(testplant2) mustEqual Some(1) + obj.InstallImplant(testplant3) mustEqual Some(2) + + obj.Implant(0) mustEqual testplant1.Type + obj.Implant(1) mustEqual testplant2.Type + obj.Implant(2) mustEqual testplant3.Type + obj.UninstallImplant(testplant2.Type) mustEqual Some(1) + obj.Implant(0) mustEqual testplant1.Type + obj.Implant(1) mustEqual ImplantType.None + obj.Implant(2) mustEqual testplant3.Type + } + + "can install implants to any available slot" in { + val testplant1 : ImplantDefinition = ImplantDefinition(1) + val testplant2 : ImplantDefinition = ImplantDefinition(2) + val testplant3 : ImplantDefinition = ImplantDefinition(3) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.Implants(1).Unlocked = true + obj.Implants(2).Unlocked = true + obj.InstallImplant(testplant1) mustEqual Some(0) + obj.InstallImplant(testplant2) mustEqual Some(1) + obj.InstallImplant(testplant3) mustEqual Some(2) + obj.UninstallImplant(testplant2.Type) mustEqual Some(1) + obj.Implant(0) mustEqual testplant1.Type + obj.Implant(1) mustEqual ImplantType.None + obj.Implant(2) mustEqual testplant3.Type + + val testplant4 : ImplantDefinition = ImplantDefinition(4) + obj.InstallImplant(testplant4) mustEqual Some(1) + obj.Implant(0) mustEqual testplant1.Type + obj.Implant(1) mustEqual testplant4.Type + obj.Implant(2) mustEqual testplant3.Type + } + + "can reset implants to uninitialized state" in { + val testplant1 : ImplantDefinition = ImplantDefinition(1) + val testplant2 : ImplantDefinition = ImplantDefinition(2) + val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.Implants(1).Unlocked = true + obj.InstallImplant(testplant1) mustEqual Some(0) + obj.InstallImplant(testplant2) mustEqual Some(1) + obj.Implants(0).Initialized = true + obj.Implants(0).Active = true + obj.Implants(1).Initialized = true + + obj.Implants(0).Initialized mustEqual true + obj.Implants(0).Active mustEqual true + obj.Implants(1).Initialized mustEqual true + obj.ResetAllImplants() + obj.Implants(0).Initialized mustEqual false + obj.Implants(0).Active mustEqual false + obj.Implants(1).Initialized mustEqual false + } + + "does not have any loadout specifications by default" in { + val (_, avatar) = CreatePlayer() + (0 to 9).foreach { avatar.LoadLoadout(_) mustEqual None } + ok + } + + "save player's current inventory as a loadout" in { + val (obj, avatar) = CreatePlayer() + obj.Slot(0).Equipment.get.asInstanceOf[Tool].Magazine = 1 //non-standard but legal + obj.Slot(2).Equipment.get.asInstanceOf[Tool].AmmoSlot.Magazine = 100 //non-standard (and out of range, real=25) + avatar.SaveLoadout(obj, "test", 0) + + avatar.LoadLoadout(0) match { + case Some(items) => + items.Label mustEqual "test" + items.ExoSuit mustEqual obj.ExoSuit + items.Subtype mustEqual 0 + + items.VisibleSlots.length mustEqual 3 + val holsters = items.VisibleSlots.sortBy(_.index) + holsters.head.index mustEqual 0 + holsters.head.item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual beamer + holsters.head.item.asInstanceOf[Loadout.ShorthandTool].ammo.head.ammo.capacity mustEqual 1 //we changed this + holsters(1).index mustEqual 2 + holsters(1).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual suppressor + holsters(1).item.asInstanceOf[Loadout.ShorthandTool].ammo.head.ammo.capacity mustEqual 100 //we changed this + holsters(2).index mustEqual 4 + holsters(2).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual forceblade + + items.Inventory.length mustEqual 6 + val inventory = items.Inventory.sortBy(_.index) + inventory.head.index mustEqual 6 + inventory.head.item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm + inventory(1).index mustEqual 9 + inventory(1).item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm + inventory(2).index mustEqual 12 + inventory(2).item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm + inventory(3).index mustEqual 33 + inventory(3).item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm_AP + inventory(4).index mustEqual 36 + inventory(4).item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual energy_cell + inventory(5).index mustEqual 39 + inventory(5).item.asInstanceOf[Loadout.ShorthandSimpleItem].definition mustEqual remote_electronics_kit + case None => + ko + } + } + + "save player's current inventory as a loadout, only found in the called-out slot number" in { + val (obj, avatar) = CreatePlayer() + avatar.SaveLoadout(obj, "test", 0) + + avatar.LoadLoadout(1).isDefined mustEqual false + avatar.LoadLoadout(0).isDefined mustEqual true + } + + "try to save player's current inventory as a loadout, but will not save to an invalid slot" in { + val (obj, avatar) = CreatePlayer() + avatar.SaveLoadout(obj, "test", 10) + + avatar.LoadLoadout(10) mustEqual None + } + + "save player's current inventory as a loadout, without inventory contents" in { + val (obj, avatar) = CreatePlayer() + obj.Inventory.Clear() + avatar.SaveLoadout(obj, "test", 0) + + avatar.LoadLoadout(0) match { + case Some(items) => + items.Label mustEqual "test" + items.ExoSuit mustEqual obj.ExoSuit + items.Subtype mustEqual 0 + items.VisibleSlots.length mustEqual 3 + items.Inventory.length mustEqual 0 //empty + case None => + ko + } + } + + "save player's current inventory as a loadout, without visible slot contents" in { + val (obj, avatar) = CreatePlayer() + obj.Slot(0).Equipment = None + obj.Slot(2).Equipment = None + obj.Slot(4).Equipment = None + avatar.SaveLoadout(obj, "test", 0) + + avatar.LoadLoadout(0) match { + case Some(items) => + items.Label mustEqual "test" + items.ExoSuit mustEqual obj.ExoSuit + items.Subtype mustEqual 0 + items.VisibleSlots.length mustEqual 0 //empty + items.Inventory.length mustEqual 6 + case None => + ko + } + } + + "save, load, delete; rapidly" in { + val (obj, avatar) = CreatePlayer() + avatar.SaveLoadout(obj, "test", 0) + + avatar.LoadLoadout(0).isDefined mustEqual true + avatar.DeleteLoadout(0) + avatar.LoadLoadout(0) mustEqual None + } + + "the fifth slot is the locker wrapped in an EquipmentSlot" in { + val (_, avatar) = CreatePlayer() + avatar.FifthSlot.Equipment.contains(avatar.Locker) + } + + "toString" in { + Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5).toString mustEqual "TR Chord" + } +} diff --git a/common/src/test/scala/objects/BuildingTest.scala b/common/src/test/scala/objects/BuildingTest.scala index dc533b1b8..72da3fab0 100644 --- a/common/src/test/scala/objects/BuildingTest.scala +++ b/common/src/test/scala/objects/BuildingTest.scala @@ -6,7 +6,7 @@ import net.psforever.objects.GlobalDefinitions import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.doors.{Door, DoorControl} -import net.psforever.objects.serverobject.structures.{Amenity, Building, BuildingControl, WarpGate} +import net.psforever.objects.serverobject.structures._ import net.psforever.objects.zones.Zone import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.PlanetSideEmpire @@ -27,7 +27,7 @@ class AmenityTest extends Specification { "can be owned by a building" in { val ao = new AmenityObject() - val bldg = Building(10, Zone.Nowhere) + val bldg = Building(10, Zone.Nowhere, StructureType.Building) ao.Owner = bldg ao.Owner mustEqual bldg @@ -51,7 +51,7 @@ class AmenityTest extends Specification { "confer faction allegiance through ownership" in { //see FactionAffinityTest val ao = new AmenityObject() - val bldg = Building(10, Zone.Nowhere) + val bldg = Building(10, Zone.Nowhere, StructureType.Building) ao.Owner = bldg bldg.Faction mustEqual PlanetSideEmpire.NEUTRAL ao.Faction mustEqual PlanetSideEmpire.NEUTRAL @@ -66,7 +66,7 @@ class AmenityTest extends Specification { class BuildingTest extends Specification { "Building" should { "construct" in { - val bldg = Building(10, Zone.Nowhere) + val bldg = Building(10, Zone.Nowhere, StructureType.Building) bldg.Id mustEqual 10 bldg.Actor mustEqual ActorRef.noSender bldg.Amenities mustEqual Nil @@ -75,7 +75,7 @@ class BuildingTest extends Specification { } "change faction affinity" in { - val bldg = Building(10, Zone.Nowhere) + val bldg = Building(10, Zone.Nowhere, StructureType.Building) bldg.Faction mustEqual PlanetSideEmpire.NEUTRAL bldg.Faction = PlanetSideEmpire.TR @@ -83,7 +83,7 @@ class BuildingTest extends Specification { } "keep track of amenities" in { - val bldg = Building(10, Zone.Nowhere) + val bldg = Building(10, Zone.Nowhere, StructureType.Building) val door1 = Door(GlobalDefinitions.door) val door2 = Door(GlobalDefinitions.door) @@ -114,7 +114,7 @@ class WarpGateTest extends Specification { class BuildingControl1Test extends ActorTest { "Building Control" should { "construct" in { - val bldg = Building(10, Zone.Nowhere) + val bldg = Building(10, Zone.Nowhere, StructureType.Building) bldg.Actor = system.actorOf(Props(classOf[BuildingControl], bldg), "test") assert(bldg.Actor != ActorRef.noSender) } @@ -124,7 +124,7 @@ class BuildingControl1Test extends ActorTest { class BuildingControl2Test extends ActorTest { "Building Control" should { "convert and assert faction affinity on convert request" in { - val bldg = Building(10, Zone.Nowhere) + val bldg = Building(10, Zone.Nowhere, StructureType.Building) bldg.Faction = PlanetSideEmpire.TR bldg.Actor = system.actorOf(Props(classOf[BuildingControl], bldg), "test") assert(bldg.Faction == PlanetSideEmpire.TR) @@ -142,7 +142,7 @@ class BuildingControl2Test extends ActorTest { class BuildingControl3Test extends ActorTest { "Building Control" should { "convert and assert faction affinity on convert request, and for each of its amenities" in { - val bldg = Building(10, Zone.Nowhere) + val bldg = Building(10, Zone.Nowhere, StructureType.Building) bldg.Faction = PlanetSideEmpire.TR bldg.Actor = system.actorOf(Props(classOf[BuildingControl], bldg), "building-test") val door1 = Door(GlobalDefinitions.door) diff --git a/common/src/test/scala/objects/DoorTest.scala b/common/src/test/scala/objects/DoorTest.scala index 95dddace2..73e55c00d 100644 --- a/common/src/test/scala/objects/DoorTest.scala +++ b/common/src/test/scala/objects/DoorTest.scala @@ -4,7 +4,7 @@ package objects import akka.actor.{ActorRef, ActorSystem, Props} import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.doors.{Door, DoorControl} -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.zones.Zone import net.psforever.packet.game.{PlanetSideGUID, UseItemMessage} import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3} @@ -121,7 +121,7 @@ object DoorControlTest { def SetUpAgents(faction : PlanetSideEmpire.Value)(implicit system : ActorSystem) : (Player, Door) = { val door = Door(GlobalDefinitions.door) door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door") - door.Owner = new Building(0, Zone.Nowhere) + door.Owner = new Building(0, Zone.Nowhere, StructureType.Building) door.Owner.Faction = faction (Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), door) } diff --git a/common/src/test/scala/objects/FactionAffinityTest.scala b/common/src/test/scala/objects/FactionAffinityTest.scala index c4598896a..a3163d9f7 100644 --- a/common/src/test/scala/objects/FactionAffinityTest.scala +++ b/common/src/test/scala/objects/FactionAffinityTest.scala @@ -5,7 +5,7 @@ import akka.actor.{Actor, ActorSystem, Props} import net.psforever.objects.{GlobalDefinitions, Vehicle} import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.doors.Door -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.zones.Zone import net.psforever.types.PlanetSideEmpire import org.specs2.mutable.Specification @@ -42,7 +42,7 @@ class FactionAffinityTest extends Specification { "inherits affinity from owner 2" in { val obj = new Door(GlobalDefinitions.door) - val bldg = new Building(1, Zone.Nowhere) + val bldg = new Building(1, Zone.Nowhere, StructureType.Building) obj.Owner = bldg obj.Faction mustEqual PlanetSideEmpire.NEUTRAL diff --git a/common/src/test/scala/objects/IFFLockTest.scala b/common/src/test/scala/objects/IFFLockTest.scala index f31133154..5cd66613c 100644 --- a/common/src/test/scala/objects/IFFLockTest.scala +++ b/common/src/test/scala/objects/IFFLockTest.scala @@ -5,7 +5,7 @@ import akka.actor.{ActorRef, ActorSystem, Props} import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.locks.{IFFLock, IFFLockControl} -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.zones.Zone import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.{CharacterGender, PlanetSideEmpire} @@ -67,7 +67,7 @@ object IFFLockControlTest { def SetUpAgents(faction : PlanetSideEmpire.Value)(implicit system : ActorSystem) : (Player, IFFLock) = { val lock = IFFLock(GlobalDefinitions.lock_external) lock.Actor = system.actorOf(Props(classOf[IFFLockControl], lock), "lock-control") - lock.Owner = new Building(0, Zone.Nowhere) + lock.Owner = new Building(0, Zone.Nowhere, StructureType.Building) lock.Owner.Faction = faction (Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), lock) } diff --git a/common/src/test/scala/objects/LoadoutTest.scala b/common/src/test/scala/objects/LoadoutTest.scala index 84a1f3fd3..fb01d6963 100644 --- a/common/src/test/scala/objects/LoadoutTest.scala +++ b/common/src/test/scala/objects/LoadoutTest.scala @@ -9,190 +9,82 @@ import org.specs2.mutable._ class LoadoutTest extends Specification { val avatar = Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) - def CreatePlayer() : (Player, Avatar) = { - val avatar = Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) - val - player = Player(avatar) - player.Slot(0).Equipment = Tool(beamer) - player.Slot(2).Equipment = Tool(suppressor) - player.Slot(4).Equipment = Tool(forceblade) - player.Slot(6).Equipment = AmmoBox(bullet_9mm) - player.Slot(9).Equipment = AmmoBox(bullet_9mm) - player.Slot(12).Equipment = AmmoBox(bullet_9mm) - player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) - player.Slot(36).Equipment = AmmoBox(energy_cell) - player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) - (player, avatar) + def CreatePlayer() : Player = { + new Player(avatar) { + Slot(0).Equipment = Tool(beamer) + Slot(2).Equipment = Tool(suppressor) + Slot(4).Equipment = Tool(forceblade) + Slot(6).Equipment = ConstructionItem(ace) + Slot(9).Equipment = AmmoBox(bullet_9mm) + Slot(12).Equipment = AmmoBox(bullet_9mm) + Slot(33).Equipment = Kit(medkit) + Slot(39).Equipment = SimpleItem(remote_electronics_kit) + } } - "Player Loadout" should { - "test sample player" in { - val (obj, _) = CreatePlayer() - obj.Holsters()(0).Equipment.get.Definition mustEqual beamer - obj.Holsters()(2).Equipment.get.Definition mustEqual suppressor - obj.Holsters()(4).Equipment.get.Definition mustEqual forceblade - obj.Slot(6).Equipment.get.Definition mustEqual bullet_9mm - obj.Slot(9).Equipment.get.Definition mustEqual bullet_9mm - obj.Slot(12).Equipment.get.Definition mustEqual bullet_9mm - obj.Slot(33).Equipment.get.Definition mustEqual bullet_9mm_AP - obj.Slot(36).Equipment.get.Definition mustEqual energy_cell - obj.Slot(39).Equipment.get.Definition mustEqual remote_electronics_kit - } + "test sample player" in { + val player = CreatePlayer() + player.Holsters()(0).Equipment.get.Definition mustEqual beamer + player.Holsters()(2).Equipment.get.Definition mustEqual suppressor + player.Holsters()(4).Equipment.get.Definition mustEqual forceblade + player.Slot(6).Equipment.get.Definition mustEqual ace + player.Slot(9).Equipment.get.Definition mustEqual bullet_9mm + player.Slot(12).Equipment.get.Definition mustEqual bullet_9mm + player.Slot(33).Equipment.get.Definition mustEqual medkit + player.Slot(39).Equipment.get.Definition mustEqual remote_electronics_kit + } - "do not load, if never saved" in { - CreatePlayer()._2.LoadLoadout(0) mustEqual None - } + "create a loadout that contains a player's inventory" in { + val player = CreatePlayer() + val obj = Loadout.Create(player, "test") - "save but incorrect load" in { - val (obj, avatar) = CreatePlayer() - avatar.SaveLoadout(obj, "test", 0) + obj.Label mustEqual "test" + obj.ExoSuit mustEqual obj.ExoSuit + obj.Subtype mustEqual 0 - avatar.LoadLoadout(1) mustEqual None - } + obj.VisibleSlots.length mustEqual 3 + val holsters = obj.VisibleSlots.sortBy(_.index) + holsters.head.index mustEqual 0 + holsters.head.item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual beamer + holsters(1).index mustEqual 2 + holsters(1).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual suppressor + holsters(2).index mustEqual 4 + holsters(2).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual forceblade - "save and load" in { - val (obj, avatar) = CreatePlayer() - obj.Slot(0).Equipment.get.asInstanceOf[Tool].Magazine = 1 //non-standard but legal - obj.Slot(2).Equipment.get.asInstanceOf[Tool].AmmoSlot.Magazine = 100 //non-standard (and out of range, real=25) - avatar.SaveLoadout(obj, "test", 0) + obj.Inventory.length mustEqual 5 + val inventory = obj.Inventory.sortBy(_.index) + inventory.head.index mustEqual 6 + inventory.head.item.asInstanceOf[Loadout.ShorthandConstructionItem].definition mustEqual ace + inventory(1).index mustEqual 9 + inventory(1).item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm + inventory(2).index mustEqual 12 + inventory(2).item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm + inventory(3).index mustEqual 33 + inventory(3).item.asInstanceOf[Loadout.ShorthandKit].definition mustEqual medkit + inventory(4).index mustEqual 39 + inventory(4).item.asInstanceOf[Loadout.ShorthandSimpleItem].definition mustEqual remote_electronics_kit + } - avatar.LoadLoadout(0) match { - case Some(items) => - items.Label mustEqual "test" - items.ExoSuit mustEqual obj.ExoSuit - items.Subtype mustEqual 0 + "distinguish MAX subtype information" in { + val player = CreatePlayer() + val slot = player.Slot(0) + slot.Equipment = None //only an unequipped slot can have its Equipment Size changed (Rifle -> Max) + Player.SuitSetup(player, ExoSuitType.MAX) - items.VisibleSlots.length mustEqual 3 - val holsters = items.VisibleSlots.sortBy(_.index) - holsters.head.index mustEqual 0 - holsters.head.item.asInstanceOf[Loadout.ShorthandTool].tdef mustEqual beamer - holsters.head.item.asInstanceOf[Loadout.ShorthandTool].ammo.head.ammo.capacity mustEqual 1 - holsters(1).index mustEqual 2 - holsters(1).item.asInstanceOf[Loadout.ShorthandTool].tdef mustEqual suppressor - holsters(1).item.asInstanceOf[Loadout.ShorthandTool].ammo.head.ammo.capacity mustEqual 100 - holsters(2).index mustEqual 4 - holsters(2).item.asInstanceOf[Loadout.ShorthandTool].tdef mustEqual forceblade + val ldout1 = Loadout.Create(player, "weaponless") + slot.Equipment = None + slot.Equipment = Tool(trhev_dualcycler) + val ldout2 = Loadout.Create(player, "cycler") + slot.Equipment = None + slot.Equipment = Tool(trhev_pounder) + val ldout3 = Loadout.Create(player, "pounder") + slot.Equipment = None + slot.Equipment = Tool(trhev_burster) + val ldout4 = Loadout.Create(player, "burster") - items.Inventory.length mustEqual 6 - val inventory = items.Inventory.sortBy(_.index) - inventory.head.index mustEqual 6 - inventory.head.item.asInstanceOf[Loadout.ShorthandAmmoBox].adef mustEqual bullet_9mm - inventory(1).index mustEqual 9 - inventory(1).item.asInstanceOf[Loadout.ShorthandAmmoBox].adef mustEqual bullet_9mm - inventory(2).index mustEqual 12 - inventory(2).item.asInstanceOf[Loadout.ShorthandAmmoBox].adef mustEqual bullet_9mm - inventory(3).index mustEqual 33 - inventory(3).item.asInstanceOf[Loadout.ShorthandAmmoBox].adef mustEqual bullet_9mm_AP - inventory(4).index mustEqual 36 - inventory(4).item.asInstanceOf[Loadout.ShorthandAmmoBox].adef mustEqual energy_cell - inventory(5).index mustEqual 39 - inventory(5).item.asInstanceOf[Loadout.ShorthandSimpleItem].sdef mustEqual remote_electronics_kit - case None => - ko - } - } - - "save without inventory contents" in { - val (obj, avatar) = CreatePlayer() - obj.Inventory.Clear() - avatar.SaveLoadout(obj, "test", 0) - - avatar.LoadLoadout(0) match { - case Some(items) => - items.Label mustEqual "test" - items.ExoSuit mustEqual obj.ExoSuit - items.Subtype mustEqual 0 - items.VisibleSlots.length mustEqual 3 - items.Inventory.length mustEqual 0 //empty - case None => - ko - } - } - - "save without visible slot contents" in { - val (obj, avatar) = CreatePlayer() - obj.Slot(0).Equipment = None - obj.Slot(2).Equipment = None - obj.Slot(4).Equipment = None - avatar.SaveLoadout(obj, "test", 0) - - avatar.LoadLoadout(0) match { - case Some(items) => - items.Label mustEqual "test" - items.ExoSuit mustEqual obj.ExoSuit - items.Subtype mustEqual 0 - items.VisibleSlots.length mustEqual 0 //empty - items.Inventory.length mustEqual 6 - case None => - ko - } - } - - "save (a construction item) and load" in { - val (obj, avatar) = CreatePlayer() - obj.Inventory.Clear() - obj.Slot(6).Equipment = ConstructionItem(advanced_ace) - avatar.SaveLoadout(obj, "test", 0) - - avatar.LoadLoadout(0) match { - case Some(items) => - items.Inventory.length mustEqual 1 - items.Inventory.head.index mustEqual 6 - items.Inventory.head.item.asInstanceOf[Loadout.ShorthandConstructionItem].cdef mustEqual advanced_ace - case None => - ko - } - } - - "save (a kit) and load" in { - val (obj, avatar) = CreatePlayer() - obj.Inventory.Clear() - obj.Slot(6).Equipment = Kit(medkit) - avatar.SaveLoadout(obj, "test", 0) - - avatar.LoadLoadout(0) match { - case Some(items) => - items.Inventory.length mustEqual 1 - items.Inventory.head.index mustEqual 6 - items.Inventory.head.item.asInstanceOf[Loadout.ShorthandKit].kdef mustEqual medkit - case None => - ko - } - } - - "save, load, delete" in { - val (obj, avatar) = CreatePlayer() - avatar.SaveLoadout(obj, "test", 0) - - avatar.LoadLoadout(0) match { - case None => - ko - case Some(_) => ; //good; keep going - } - avatar.DeleteLoadout(0) - avatar.LoadLoadout(0) mustEqual None - } - - "distinguish MAX subtype information" in { - val (obj, avatar) = CreatePlayer() - val slot = obj.Slot(0) - slot.Equipment = None //only an unequipped slot can have its Equipment Size changed (Rifle -> Max) - Player.SuitSetup(obj, ExoSuitType.MAX) - avatar.SaveLoadout(obj, "generic", 0) //weaponless - slot.Equipment = None - slot.Equipment = Tool(trhev_dualcycler) - avatar.SaveLoadout(obj, "cycler", 1) - slot.Equipment = None - slot.Equipment = Tool(trhev_pounder) - avatar.SaveLoadout(obj, "pounder", 2) - slot.Equipment = None - slot.Equipment = Tool(trhev_burster) - avatar.SaveLoadout(obj, "burster", 3) - - avatar.LoadLoadout(0).get.Subtype mustEqual 0 - avatar.LoadLoadout(1).get.Subtype mustEqual 1 - avatar.LoadLoadout(2).get.Subtype mustEqual 2 - avatar.LoadLoadout(3).get.Subtype mustEqual 3 - } + ldout1.Subtype mustEqual 0 + ldout2.Subtype mustEqual 1 + ldout3.Subtype mustEqual 2 + ldout4.Subtype mustEqual 3 } } diff --git a/common/src/test/scala/objects/PlayerTest.scala b/common/src/test/scala/objects/PlayerTest.scala index 6a4de73f1..dd9771cb5 100644 --- a/common/src/test/scala/objects/PlayerTest.scala +++ b/common/src/test/scala/objects/PlayerTest.scala @@ -1,45 +1,61 @@ // Copyright (c) 2017 PSForever package objects +import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects._ -import net.psforever.objects.definition.SimpleItemDefinition +import net.psforever.objects.definition.{ImplantDefinition, SimpleItemDefinition} import net.psforever.objects.equipment.EquipmentSize import net.psforever.packet.game.PlanetSideGUID -import net.psforever.types.{CharacterGender, ExoSuitType, PlanetSideEmpire} +import net.psforever.types.{CharacterGender, ExoSuitType, ImplantType, PlanetSideEmpire} import org.specs2.mutable._ +import scala.util.Success + class PlayerTest extends Specification { + def TestPlayer(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : Int) : Player = { + new Player(Avatar(name, faction, sex, head, voice)) + } + "construct" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.isAlive mustEqual false + obj.FacingYawUpper mustEqual 0 + obj.Jumping mustEqual false + obj.Crouching mustEqual false + obj.Cloaked mustEqual false + + obj.FacingYawUpper = 1.3f + obj.Jumping = true + obj.Crouching = true + obj.Cloaked = true + obj.FacingYawUpper mustEqual 1.3f + obj.Jumping mustEqual true + obj.Crouching mustEqual true + obj.Cloaked mustEqual true } "different players" in { - (PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual true - (PlayerTest.Player("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - PlayerTest.Player("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual false - (PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - PlayerTest.Player("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, 5)) mustEqual false - (PlayerTest.Player("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - PlayerTest.Player("Chord2", PlanetSideEmpire.TR, CharacterGender.Female, 0, 5)) mustEqual false - (PlayerTest.Player("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - PlayerTest.Player("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 1, 5)) mustEqual false - (PlayerTest.Player("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - PlayerTest.Player("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 6)) mustEqual false - } + (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual true - "become a backpack" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.isAlive mustEqual false - obj.isBackpack mustEqual false - obj.Release - obj.isAlive mustEqual false - obj.isBackpack mustEqual true + (TestPlayer("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual false + + (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, 5)) mustEqual false + + (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Female, 0, 5)) mustEqual false + + (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 1, 5)) mustEqual false + + (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 6)) mustEqual false } "(re)spawn" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.isAlive mustEqual false obj.Health mustEqual 0 obj.Stamina mustEqual 0 @@ -54,32 +70,24 @@ class PlayerTest extends Specification { obj.Armor mustEqual 50 } - "set new maximum values (health, stamina)" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.MaxHealth mustEqual 100 - obj.MaxStamina mustEqual 100 - obj.MaxHealth = 123 - obj.MaxStamina = 456 + "will not (re)spawn if not dead" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Spawn - obj.Health mustEqual 123 - obj.Stamina mustEqual 456 + obj.Health mustEqual 100 + obj.Armor mustEqual 50 + obj.isAlive mustEqual true + + obj.Health = 10 + obj.Armor = 10 + obj.Health mustEqual 10 + obj.Armor mustEqual 10 + obj.Spawn + obj.Health mustEqual 10 + obj.Armor mustEqual 10 } - "init (Standard Exo-Suit)" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.ExoSuit mustEqual ExoSuitType.Standard - obj.Slot(0).Size mustEqual EquipmentSize.Pistol - obj.Slot(1).Size mustEqual EquipmentSize.Blocked - obj.Slot(2).Size mustEqual EquipmentSize.Rifle - obj.Slot(3).Size mustEqual EquipmentSize.Blocked - obj.Slot(4).Size mustEqual EquipmentSize.Melee - obj.Inventory.Width mustEqual 9 - obj.Inventory.Height mustEqual 6 - obj.Inventory.Offset mustEqual 6 - } - - "die" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + "can die" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Spawn obj.Armor = 35 //50 -> 35 obj.isAlive mustEqual true @@ -93,9 +101,80 @@ class PlayerTest extends Specification { obj.Armor mustEqual 35 } + "can not become a backpack if alive" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Spawn + obj.isAlive mustEqual true + obj.isBackpack mustEqual false + obj.Release + obj.isAlive mustEqual true + obj.isBackpack mustEqual false + } + + "can become a backpack" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.isAlive mustEqual false + obj.isBackpack mustEqual false + obj.Release + obj.isAlive mustEqual false + obj.isBackpack mustEqual true + } + + "set new maximum values (health, stamina)" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.MaxHealth mustEqual 100 + obj.MaxStamina mustEqual 100 + obj.MaxHealth = 123 + obj.MaxStamina = 456 + obj.Spawn + obj.Health mustEqual 123 + obj.Stamina mustEqual 456 + } + + "set new values (health, armor, stamina) but only when alive" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Health = 23 + obj.Armor = 34 + obj.Stamina = 45 + obj.Health mustEqual 0 + obj.Armor mustEqual 0 + obj.Stamina mustEqual 0 + + obj.Spawn + obj.Health mustEqual obj.MaxHealth + obj.Armor mustEqual obj.MaxArmor + obj.Stamina mustEqual obj.MaxStamina + obj.Health = 23 + obj.Armor = 34 + obj.Stamina = 45 + obj.Health mustEqual 23 + obj.Armor mustEqual 34 + obj.Stamina mustEqual 45 + } + + "has visible slots" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.VisibleSlots mustEqual Set(0,1,2,3,4) + obj.ExoSuit = ExoSuitType.MAX + obj.VisibleSlots mustEqual Set(0) + } + + "init (Standard Exo-Suit)" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.ExoSuit mustEqual ExoSuitType.Standard + obj.Slot(0).Size mustEqual EquipmentSize.Pistol + obj.Slot(1).Size mustEqual EquipmentSize.Blocked + obj.Slot(2).Size mustEqual EquipmentSize.Rifle + obj.Slot(3).Size mustEqual EquipmentSize.Blocked + obj.Slot(4).Size mustEqual EquipmentSize.Melee + obj.Inventory.Width mustEqual 9 + obj.Inventory.Height mustEqual 6 + obj.Inventory.Offset mustEqual 6 + } + "draw equipped holsters only" in { val wep = SimpleItem(SimpleItemDefinition(149)) - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Slot(1).Size = EquipmentSize.Pistol obj.Slot(1).Equipment = wep obj.DrawnSlot mustEqual Player.HandsDownSlot @@ -108,7 +187,7 @@ class PlayerTest extends Specification { "remember the last drawn holster" in { val wep1 = SimpleItem(SimpleItemDefinition(149)) val wep2 = SimpleItem(SimpleItemDefinition(149)) - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Slot(0).Size = EquipmentSize.Pistol obj.Slot(0).Equipment = wep1 obj.Slot(1).Size = EquipmentSize.Pistol @@ -147,7 +226,7 @@ class PlayerTest extends Specification { "hold something in their free hand" in { val wep = SimpleItem(SimpleItemDefinition(149)) - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Slot(Player.FreeHandSlot).Equipment = wep obj.Slot(Player.FreeHandSlot).Equipment.get.Definition.ObjectId mustEqual 149 @@ -155,15 +234,15 @@ class PlayerTest extends Specification { "provide an invalid hand that can not hold anything" in { val wep = SimpleItem(SimpleItemDefinition(149)) - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Slot(-1).Equipment = wep obj.Slot(-1).Equipment mustEqual None } "search for the smallest available slot in which to store equipment" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Inventory.Resize(3,3) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Inventory.Resize(3,3) //fits one item obj.Fit(Tool(GlobalDefinitions.beamer)) mustEqual Some(0) @@ -171,52 +250,185 @@ class PlayerTest extends Specification { val ammo = AmmoBox(GlobalDefinitions.bullet_9mm) val ammo2 = AmmoBox(GlobalDefinitions.bullet_9mm) - //val ammo3 = AmmoBox(GlobalDefinitions.bullet_9mm) + val ammo3 = AmmoBox(GlobalDefinitions.bullet_9mm) obj.Fit(ammo) mustEqual Some(6) obj.Slot(6).Equipment = ammo obj.Fit(ammo2) mustEqual Some(Player.FreeHandSlot) obj.Slot(Player.FreeHandSlot).Equipment = ammo2 - obj.Fit(ammo2) mustEqual None + obj.Fit(ammo3) mustEqual None } - //TODO move to Avatar tests -// "install an implant" in { -// val testplant : ImplantDefinition = ImplantDefinition(1) -// val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) -// obj.Implants(0).Unlocked = true -// obj.InstallImplant(testplant) mustEqual Some(0) -// obj.Implants.find({p => p.Implant == ImplantType(1)}) match { //find the installed implant -// case Some(slot) => -// slot.Installed mustEqual Some(testplant) -// case _ => -// ko -// } -// ok -// } -// -// "can not install the same type of implant twice" in { -// val testplant1 : ImplantDefinition = ImplantDefinition(1) -// val testplant2 : ImplantDefinition = ImplantDefinition(1) -// val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) -// obj.Implants(0).Unlocked = true -// obj.Implants(1).Unlocked = true -// obj.InstallImplant(testplant1) mustEqual Some(0) -// obj.InstallImplant(testplant2) mustEqual Some(1) -// } -// -// "uninstall implants" in { -// val testplant : ImplantDefinition = ImplantDefinition(1) -// val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) -// obj.Implants(0).Unlocked = true -// obj.InstallImplant(testplant) mustEqual Some(0) -// obj.Implants(0).Installed mustEqual Some(testplant) -// -// obj.UninstallImplant(testplant.Type) -// obj.Implants(0).Installed mustEqual None -// } + "can use their free hand to hold things" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val ammo = AmmoBox(GlobalDefinitions.bullet_9mm) + obj.FreeHand.Equipment mustEqual None + + obj.FreeHand.Equipment = ammo + obj.FreeHand.Equipment mustEqual Some(ammo) + } + + "can access the player's locker-space" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Slot(5).Equipment.get.isInstanceOf[LockerContainer] mustEqual true + } + + "can find equipment" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Slot(0).Equipment = { + val item = Tool(beamer) + item.GUID = PlanetSideGUID(1) + item + } + obj.Slot(4).Equipment = { + val item = Tool(forceblade) + item.GUID = PlanetSideGUID(2) + item + } + obj.Slot(6).Equipment = { + val item = ConstructionItem(ace) + item.GUID = PlanetSideGUID(3) + item + } + obj.Locker.Slot(6).Equipment = { + val item = Kit(medkit) + item.GUID = PlanetSideGUID(4) + item + } + obj.FreeHand.Equipment = { + val item = SimpleItem(remote_electronics_kit) + item.GUID = PlanetSideGUID(5) + item + } + + obj.Find(PlanetSideGUID(1)) mustEqual Some(0) //holsters + obj.Find(PlanetSideGUID(2)) mustEqual Some(4) //holsters, melee + obj.Find(PlanetSideGUID(3)) mustEqual Some(6) //inventory + obj.Find(PlanetSideGUID(4)) mustEqual Some(Player.LockerSlot) //locker-space + obj.Find(PlanetSideGUID(5)) mustEqual Some(Player.FreeHandSlot) //free hand + obj.Find(PlanetSideGUID(6)) mustEqual None //not here + } + + "does equipment collision checking (are we already holding something there?)" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val item1 = Tool(beamer) + val item2 = Kit(medkit) + val item3 = AmmoBox(GlobalDefinitions.bullet_9mm) + obj.Slot(0).Equipment = item1 + obj.Slot(6).Equipment = item2 + obj.FreeHand.Equipment = item3 + + obj.Collisions(0, 1, 1) match { + case Success(List(item)) => + item.obj mustEqual item1 + item.start mustEqual 0 + case _ => + ko + } //holsters + + obj.Collisions(1, 1, 1) match { + case Success(List()) => ; + case _ => + ko + } //holsters, nothing + + obj.Collisions(6, 1, 1)match { + case Success(List(item)) => + item.obj mustEqual item2 + item.start mustEqual 6 + case _ => + ko + } //inventory + + obj.Collisions(Player.FreeHandSlot, 1, 1)match { + case Success(List(item)) => + item.obj mustEqual item3 + item.start mustEqual Player.FreeHandSlot + case _ => + ko + } //free hand + } + + "battle experience point values of the avatar" in { + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + + player.BEP mustEqual avatar.BEP + avatar.BEP = 1002 + player.BEP mustEqual avatar.BEP + } + + "command experience point values of the avatar" in { + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + + player.CEP mustEqual avatar.CEP + avatar.CEP = 1002 + player.CEP mustEqual avatar.CEP + } + + "can get a quick summary of implant slots (default)" in { + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + + player.Implants mustEqual Array.empty + } + + "can get a quick summary of implant slots (two unlocked, one installed)" in { + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + val temp = new ImplantDefinition(1) + avatar.Implants(0).Unlocked = true + avatar.InstallImplant(new ImplantDefinition(1)) + avatar.Implants(1).Unlocked = true + avatar.InstallImplant(new ImplantDefinition(2)) + avatar.UninstallImplant(temp.Type) + + val list = player.Implants + //slot 0 + val (implant1, init1, active1) = list(0) + implant1 mustEqual ImplantType.None + init1 mustEqual -1 + active1 mustEqual false + //slot 1 + val (implant2, init2, active2) = list(1) + implant2 mustEqual ImplantType(2) + init2 mustEqual 0 + active2 mustEqual false + } + + "can get a quick summary of implant slots (all unlocked, first two installed)" in { + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + avatar.Implants(0).Unlocked = true + avatar.InstallImplant(new ImplantDefinition(1)) + avatar.Implants(0).Initialized = true + avatar.Implants(0).Active = true + avatar.Implants(1).Unlocked = true + avatar.InstallImplant(new ImplantDefinition(2)) + avatar.Implants(1).Initialized = true + avatar.Implants(1).Active = false + avatar.Implants(2).Unlocked = true + + val list = player.Implants + //slot 0 + val (implant1, init1, active1) = list(0) + implant1 mustEqual ImplantType(1) + init1 mustEqual 0 + active1 mustEqual true + //slot 1 + val (implant2, init2, active2) = list(1) + implant2 mustEqual ImplantType(2) + init2 mustEqual 0 + active2 mustEqual false + //slot 2 + val (implant3, init3, active3) = list(2) + implant3 mustEqual ImplantType.None + init3 mustEqual -1 + active3 mustEqual false + } "seat in a vehicle" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.VehicleSeated mustEqual None obj.VehicleSeated = PlanetSideGUID(65) obj.VehicleSeated mustEqual Some(PlanetSideGUID(65)) @@ -225,7 +437,7 @@ class PlayerTest extends Specification { } "own in a vehicle" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.VehicleOwned mustEqual None obj.VehicleOwned = PlanetSideGUID(65) obj.VehicleOwned mustEqual Some(PlanetSideGUID(65)) @@ -234,15 +446,18 @@ class PlayerTest extends Specification { } "remember what zone he is in" in { - val obj = PlayerTest.Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Continent mustEqual "home2" obj.Continent = "ugd01" obj.Continent mustEqual "ugd01" } -} -object PlayerTest { - def Player(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : Int) : Player = { - new Player(Avatar(name, faction, sex, head, voice)) + "toString" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.toString mustEqual "TR Chord 0/100 0/50" + + obj.GUID = PlanetSideGUID(455) + obj.Continent = "z3" + obj.toString mustEqual "TR Chord z3-455 0/100 0/50" } } diff --git a/common/src/test/scala/objects/ServerObjectBuilderTest.scala b/common/src/test/scala/objects/ServerObjectBuilderTest.scala index 903e2e50d..813dbe36c 100644 --- a/common/src/test/scala/objects/ServerObjectBuilderTest.scala +++ b/common/src/test/scala/objects/ServerObjectBuilderTest.scala @@ -5,7 +5,7 @@ import akka.actor.{Actor, ActorContext, Props} import net.psforever.objects.guid.NumberPoolHub import net.psforever.packet.game.PlanetSideGUID import net.psforever.objects.serverobject.ServerObjectBuilder -import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, WarpGate} +import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType, WarpGate} import net.psforever.objects.zones.Zone import net.psforever.types.Vector3 @@ -14,7 +14,7 @@ import scala.concurrent.duration.Duration class BuildingBuilderTest extends ActorTest { "Building object" should { "build" in { - val structure : (Int,Zone,ActorContext)=>Building = Building.Structure + val structure : (Int,Zone,ActorContext)=>Building = Building.Structure(StructureType.Building) val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuildingTestActor], structure, 10, Zone.Nowhere), "building") actor ! "!" @@ -169,6 +169,26 @@ class LockerObjectBuilderTest extends ActorTest { } } +class SpawnTubeObjectBuilderTest extends ActorTest { + import net.psforever.objects.serverobject.tube.SpawnTube + "LockerObjectBuilder" should { + "build" in { + val hub = ServerObjectBuilderTest.NumberPoolHub + val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, + SpawnTube.Constructor(Vector3(3980.4062f, 4267.3047f, 257.5625f), Vector3(0, 0, 90))), hub), "spawn-tube") + actor ! "!" + + val reply = receiveOne(Duration.create(1000, "ms")) + assert(reply.isInstanceOf[SpawnTube]) + assert(reply.asInstanceOf[SpawnTube].HasGUID) + assert(reply.asInstanceOf[SpawnTube].GUID == PlanetSideGUID(1)) + assert(reply.asInstanceOf[SpawnTube].Position == Vector3(3980.4062f, 4267.3047f, 257.5625f)) + assert(reply.asInstanceOf[SpawnTube].Orientation == Vector3(0, 0, 90)) + assert(reply == hub(1).get) + } + } +} + object ServerObjectBuilderTest { import net.psforever.objects.guid.source.LimitedNumberSource def NumberPoolHub : NumberPoolHub = { diff --git a/common/src/test/scala/objects/SpawnTubeTest.scala b/common/src/test/scala/objects/SpawnTubeTest.scala new file mode 100644 index 000000000..7ef8e86b7 --- /dev/null +++ b/common/src/test/scala/objects/SpawnTubeTest.scala @@ -0,0 +1,59 @@ +// Copyright (c) 2017 PSForever +package objects + +import akka.actor.{ActorRef, Props} +import net.psforever.objects.GlobalDefinitions +import net.psforever.objects.serverobject.tube.{SpawnTube, SpawnTubeControl, SpawnTubeDefinition} +import org.specs2.mutable.Specification + +class SpawnTubeTest extends Specification { + "SpawnTubeDefinition" should { + "define (ams_respawn_tube)" in { + val obj = new SpawnTubeDefinition(49) + obj.ObjectId mustEqual 49 + obj.Name mustEqual "ams_respawn_tube" + } + + "define (respawn_tube)" in { + val obj = new SpawnTubeDefinition(732) + obj.ObjectId mustEqual 732 + obj.Name mustEqual "respawn_tube" + } + + "define (respawn_tube_tower)" in { + val obj = new SpawnTubeDefinition(733) + obj.ObjectId mustEqual 733 + obj.Name mustEqual "respawn_tube_tower" + } + + "define (invalid)" in { + var id : Int = (math.random * Int.MaxValue).toInt + if(id == 49 || id == 733) { + id += 1 + } + else if(id == 732) { + id += 2 + } + + new SpawnTubeDefinition(id) must throwA[IllegalArgumentException] + } + } + + "SpawnTube" should { + "construct" in { + val obj = SpawnTube(GlobalDefinitions.ams_respawn_tube) + obj.Actor mustEqual ActorRef.noSender + obj.Definition mustEqual GlobalDefinitions.ams_respawn_tube + } + } +} + +class SpawnTubeControlTest extends ActorTest() { + "SpawnTubeControl" should { + "construct" in { + val obj = SpawnTube(GlobalDefinitions.ams_respawn_tube) + obj.Actor = system.actorOf(Props(classOf[SpawnTubeControl], obj), "spawn-tube") + assert(obj.Actor != ActorRef.noSender) + } + } +} diff --git a/common/src/test/scala/objects/VehicleSpawnPadTest.scala b/common/src/test/scala/objects/VehicleSpawnPadTest.scala index 1332fed32..d4393b5d2 100644 --- a/common/src/test/scala/objects/VehicleSpawnPadTest.scala +++ b/common/src/test/scala/objects/VehicleSpawnPadTest.scala @@ -3,7 +3,7 @@ package objects import akka.actor.{ActorRef, ActorSystem, Props} import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.vehicles.VehicleControl import net.psforever.objects.zones.Zone import net.psforever.objects.{Avatar, GlobalDefinitions, Player, Vehicle} @@ -109,7 +109,7 @@ object VehicleSpawnPadControl { def SetUpAgents(faction : PlanetSideEmpire.Value)(implicit system : ActorSystem) : (Player, VehicleSpawnPad) = { val pad = VehicleSpawnPad(GlobalDefinitions.spawn_pad) pad.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], pad), "test-pad") - pad.Owner = new Building(0, Zone.Nowhere) + pad.Owner = new Building(0, Zone.Nowhere, StructureType.Building) pad.Owner.Faction = faction (Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), pad) } diff --git a/common/src/test/scala/objects/ZoneTest.scala b/common/src/test/scala/objects/ZoneTest.scala index 024c8aef6..49fe866be 100644 --- a/common/src/test/scala/objects/ZoneTest.scala +++ b/common/src/test/scala/objects/ZoneTest.scala @@ -1,16 +1,24 @@ // Copyright (c) 2017 PSForever package objects -import akka.actor.{ActorContext, ActorRef} +import java.util.concurrent.atomic.AtomicInteger + +import akka.actor.{Actor, ActorContext, ActorRef, Props} import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.equipment.Equipment import net.psforever.objects.guid.NumberPoolHub import net.psforever.objects.guid.source.LimitedNumberSource -import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder} -import net.psforever.objects.zones.{Zone, ZoneMap} -import net.psforever.objects.{GlobalDefinitions, Vehicle} +import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType} +import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.serverobject.tube.SpawnTube +import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap} +import net.psforever.objects.{Avatar, GlobalDefinitions, Player, Vehicle} +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3} import org.specs2.mutable.Specification +import scala.concurrent.duration.Duration + class ZoneTest extends Specification { def test(a: Int, b : Zone, c : ActorContext) : Building = { Building.NoBuilding } @@ -82,6 +90,8 @@ class ZoneTest extends Specification { //zone also has a unique identifier system but it can't be accessed without its the Actor GUID being initialized zone.EquipmentOnGround mustEqual List.empty[Equipment] zone.Vehicles mustEqual List.empty[Vehicle] + zone.Players mustEqual List.empty[Player] + zone.Corpses mustEqual List.empty[Player] } "can have its unique identifier system changed if no objects were added to it" in { @@ -124,3 +134,368 @@ class ZoneTest extends Specification { } } } + +class ZoneActorTest extends ActorTest { + "Zone" should { + "have an Actor" in { + val zone = new Zone("test", new ZoneMap("map6"), 1) + zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-actor") + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Actor != ActorRef.noSender) + } + + "set up spawn groups based on buildings" in { + val map6 = new ZoneMap("map6") { + LocalBuilding(1, FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1,1,1)))) + LocalObject(1, SpawnTube.Constructor(Vector3.Zero, Vector3.Zero)) + LocalObject(2, Terminal.Constructor(GlobalDefinitions.dropship_vehicle_terminal)) + LocalObject(3, SpawnTube.Constructor(Vector3.Zero, Vector3.Zero)) + ObjectToBuilding(1, 1) + ObjectToBuilding(2, 1) + ObjectToBuilding(3, 1) + + LocalBuilding(2, FoundationBuilder(Building.Structure(StructureType.Building))) + LocalObject(7, SpawnTube.Constructor(Vector3.Zero, Vector3.Zero)) + ObjectToBuilding(7, 2) + + LocalBuilding(3, FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1,1,1)))) + LocalObject(4, Terminal.Constructor(GlobalDefinitions.dropship_vehicle_terminal)) + LocalObject(5, SpawnTube.Constructor(Vector3.Zero, Vector3.Zero)) + LocalObject(6, Terminal.Constructor(GlobalDefinitions.dropship_vehicle_terminal)) + ObjectToBuilding(4, 3) + ObjectToBuilding(5, 3) + ObjectToBuilding(6, 3) + } + val zone = new Zone("test", map6, 1) + zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-init") + zone.Actor ! Zone.Init() + expectNoMsg(Duration.create(300, "ms")) + + val groups = zone.SpawnGroups() + assert(groups.size == 2) + zone.SpawnGroups().foreach({ case(building, tubes) => + if(building.Id == 1) { + val building1 = zone.SpawnGroups(building) + assert(tubes.length == 2) + assert(tubes.head == building1.head) + assert(tubes.head.GUID == PlanetSideGUID(1)) + assert(tubes(1) == building1(1)) + assert(tubes(1).GUID == PlanetSideGUID(3)) + } + else if(building.Id == 3) { + val building2 = zone.SpawnGroups(building) + assert(tubes.length == 1) + assert(tubes.head == building2.head) + assert(tubes.head.GUID == PlanetSideGUID(5)) + } + else { + assert(false) + } + }) + } + + "select spawn points based on the position of the player in reference to buildings" in { + val map6 = new ZoneMap("map6") { + LocalBuilding(1, FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1,1,1)))) + LocalObject(1, SpawnTube.Constructor(Vector3.Zero, Vector3.Zero)) + ObjectToBuilding(1, 1) + + LocalBuilding(3, FoundationBuilder(Building.Structure(StructureType.Building, Vector3(4,4,4)))) + LocalObject(5, SpawnTube.Constructor(Vector3.Zero, Vector3.Zero)) + ObjectToBuilding(5, 3) + } + val zone = new Zone("test", map6, 1) + zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-spawn") + zone.Actor ! Zone.Init() + expectNoMsg(Duration.create(300, "ms")) + val player = Player(Avatar("Chord", PlanetSideEmpire.NEUTRAL, CharacterGender.Male, 0, 5)) + + val bldg1 = zone.Building(1).get + val bldg3 = zone.Building(3).get + player.Position = Vector3(1,1,1) //closer to bldg1 + zone.Actor ! Zone.Lattice.RequestSpawnPoint(1, player, 7) + val reply1 = receiveOne(Duration.create(200, "ms")) + assert(reply1.isInstanceOf[Zone.Lattice.SpawnPoint]) + assert(reply1.asInstanceOf[Zone.Lattice.SpawnPoint].zone_id == "test") + assert(reply1.asInstanceOf[Zone.Lattice.SpawnPoint].building == bldg1) + assert(reply1.asInstanceOf[Zone.Lattice.SpawnPoint].spawn_tube.Owner == bldg1) + + player.Position = Vector3(3,3,3) //closer to bldg3 + zone.Actor ! Zone.Lattice.RequestSpawnPoint(1, player, 7) + val reply3 = receiveOne(Duration.create(200, "ms")) + assert(reply3.isInstanceOf[Zone.Lattice.SpawnPoint]) + assert(reply3.asInstanceOf[Zone.Lattice.SpawnPoint].zone_id == "test") + assert(reply3.asInstanceOf[Zone.Lattice.SpawnPoint].building == bldg3) + assert(reply3.asInstanceOf[Zone.Lattice.SpawnPoint].spawn_tube.Owner == bldg3) + } + + "will report if no spawn points have been found in a zone" in { + val map6 = new ZoneMap("map6") { + LocalBuilding(1, FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1,1,1)))) + + LocalBuilding(3, FoundationBuilder(Building.Structure(StructureType.Tower, Vector3(4,4,4)))) + LocalObject(5, SpawnTube.Constructor(Vector3.Zero, Vector3.Zero)) + ObjectToBuilding(5, 3) + } + val zone = new Zone("test", map6, 1) + zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-no-spawn") + zone.Actor ! Zone.Init() + expectNoMsg(Duration.create(300, "ms")) + val player = Player(Avatar("Chord", PlanetSideEmpire.NEUTRAL, CharacterGender.Male, 0, 5)) + + zone.Actor ! Zone.Lattice.RequestSpawnPoint(1, player, 7) + val reply = receiveOne(Duration.create(200, "ms")) + assert(reply.isInstanceOf[Zone.Lattice.NoValidSpawnPoint]) + assert(reply.asInstanceOf[Zone.Lattice.NoValidSpawnPoint].zone_number == 1) + assert(reply.asInstanceOf[Zone.Lattice.NoValidSpawnPoint].spawn_group.contains(7)) + } + } +} + +class ZonePopulationTest extends ActorTest { + val testNum = new AtomicInteger(1) + def TestName : String = s"test${testNum.getAndIncrement()}" + + "ZonePopulationActor" should { + "add new user to zones" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + + assert(zone.Players.isEmpty) + assert(zone.LivePlayers.isEmpty) + zone.Population ! Zone.Population.Join(avatar) + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.isEmpty) + } + + "remove user from zones" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + zone.Population ! Zone.Population.Join(avatar) + expectNoMsg(Duration.create(100, "ms")) + + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + zone.Population ! Zone.Population.Leave(avatar) + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Players.isEmpty) + } + + "associate user with a character" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + zone.Population ! Zone.Population.Join(avatar) + expectNoMsg(Duration.create(100, "ms")) + + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.isEmpty) + zone.Population ! Zone.Population.Spawn(avatar, player) + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.size == 1) + assert(zone.LivePlayers.head == player) + } + + "disassociate character from a user" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + zone.Population ! Zone.Population.Join(avatar) + expectNoMsg(Duration.create(100, "ms")) + zone.Population ! Zone.Population.Spawn(avatar, player) + expectNoMsg(Duration.create(100, "ms")) + + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.size == 1) + assert(zone.LivePlayers.head == player) + zone.Population ! Zone.Population.Release(avatar) + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.isEmpty) + } + + "user tries to Leave, but still has an associated character" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(500, "ms")) //consume + zone.Population ! Zone.Population.Join(avatar) + expectNoMsg(Duration.create(100, "ms")) + zone.Population ! Zone.Population.Spawn(avatar, player) + expectNoMsg(Duration.create(100, "ms")) + + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.size == 1) + assert(zone.LivePlayers.head == player) + zone.Population ! Zone.Population.Leave(avatar) + val reply = receiveOne(Duration.create(100, "ms")) + assert(zone.Players.isEmpty) + assert(zone.LivePlayers.isEmpty) + assert(reply.isInstanceOf[Zone.Population.PlayerHasLeft]) + assert(reply.asInstanceOf[Zone.Population.PlayerHasLeft].zone == zone) + assert(reply.asInstanceOf[Zone.Population.PlayerHasLeft].player.contains(player)) + } + + "user tries to Spawn a character, but an associated character already exists" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player1 = Player(avatar) + val player2 = Player(avatar) + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + zone.Population ! Zone.Population.Join(avatar) + expectNoMsg(Duration.create(100, "ms")) + zone.Population ! Zone.Population.Spawn(avatar, player1) + expectNoMsg(Duration.create(100, "ms")) + + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.size == 1) + assert(zone.LivePlayers.head == player1) + zone.Population ! Zone.Population.Spawn(avatar, player2) + val reply = receiveOne(Duration.create(100, "ms")) + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.size == 1) + assert(zone.LivePlayers.head == player1) + assert(reply.isInstanceOf[Zone.Population.PlayerAlreadySpawned]) + assert(reply.asInstanceOf[Zone.Population.PlayerAlreadySpawned].player == player1) + } + + "user tries to Spawn a character, but did not Join first" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + + assert(zone.Players.isEmpty) + assert(zone.LivePlayers.isEmpty) + zone.Population ! Zone.Population.Spawn(avatar, player) + val reply = receiveOne(Duration.create(100, "ms")) + assert(zone.Players.isEmpty) + assert(zone.LivePlayers.isEmpty) + assert(reply.isInstanceOf[Zone.Population.PlayerCanNotSpawn]) + assert(reply.asInstanceOf[Zone.Population.PlayerCanNotSpawn].zone == zone) + assert(reply.asInstanceOf[Zone.Population.PlayerCanNotSpawn].player == player) + } + + "user tries to Release a character, but did not Spawn a character first" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + zone.Population ! Zone.Population.Join(avatar) + expectNoMsg(Duration.create(100, "ms")) + + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.isEmpty) + zone.Population ! Zone.Population.Release(avatar) + val reply = receiveOne(Duration.create(100, "ms")) + assert(zone.Players.size == 1) + assert(zone.Players.head == avatar) + assert(zone.LivePlayers.isEmpty) + assert(reply.isInstanceOf[Zone.Population.PlayerHasLeft]) + assert(reply.asInstanceOf[Zone.Population.PlayerHasLeft].zone == zone) + assert(reply.asInstanceOf[Zone.Population.PlayerHasLeft].player.isEmpty) + } + + "user adds character to list of retired characters" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) + player.Release + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + + assert(zone.Corpses.isEmpty) + zone.Population ! Zone.Corpse.Add(player) + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Corpses.size == 1) + assert(zone.Corpses.head == player) + } + + "user removes character from the list of retired characters" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) + player.Release + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + zone.Population ! Zone.Corpse.Add(player) + expectNoMsg(Duration.create(100, "ms")) + + assert(zone.Corpses.size == 1) + assert(zone.Corpses.head == player) + zone.Population ! Zone.Corpse.Remove(player) + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Corpses.isEmpty) + } + + "user removes THE CORRECT character from the list of retired characters" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val player1 = Player(Avatar("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) + player1.Release + val player2 = Player(Avatar("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) + player2.Release + val player3 = Player(Avatar("Chord3", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) + player3.Release + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!" + receiveOne(Duration.create(200, "ms")) //consume + zone.Population ! Zone.Corpse.Add(player1) + zone.Population ! Zone.Corpse.Add(player2) + zone.Population ! Zone.Corpse.Add(player3) + expectNoMsg(Duration.create(100, "ms")) + + assert(zone.Corpses.size == 3) + assert(zone.Corpses.head == player1) + assert(zone.Corpses(1) == player2) + assert(zone.Corpses(2) == player3) + zone.Population ! Zone.Corpse.Remove(player2) + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Corpses.size == 2) + assert(zone.Corpses.head == player1) + assert(zone.Corpses(1) == player3) + } + + "user tries to add character to list of retired characters, but is not in correct state" in { + val zone = new Zone("test", new ZoneMap(""), 0) + val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) + //player.Release !!important + system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "testC") ! "!" + receiveOne(Duration.create(500, "ms")) //consume + + assert(zone.Corpses.isEmpty) + zone.Population ! Zone.Corpse.Add(player) + expectNoMsg(Duration.create(100, "ms")) + assert(zone.Corpses.isEmpty) + } + } +} + +object ZoneTest { + class ZoneInitActor(zone : Zone) extends Actor { + def receive : Receive = { + case "!" => + zone.Init(context) + sender ! "!" + case _ => ; + } + } +} diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskRegister5Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskRegister5Test.scala index 2c08c06df..0a19c67df 100644 --- a/common/src/test/scala/objects/guidtask/GUIDTaskRegister5Test.scala +++ b/common/src/test/scala/objects/guidtask/GUIDTaskRegister5Test.scala @@ -1,7 +1,6 @@ // Copyright (c) 2017 PSForever package objects.guidtask - import net.psforever.objects._ import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.types.{CharacterGender, PlanetSideEmpire} diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskRegister6Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskRegister6Test.scala new file mode 100644 index 000000000..091ea62fc --- /dev/null +++ b/common/src/test/scala/objects/guidtask/GUIDTaskRegister6Test.scala @@ -0,0 +1,38 @@ +// Copyright (c) 2017 PSForever +package objects.guidtask + +import net.psforever.objects._ +import net.psforever.objects.guid.{GUIDTask, TaskResolver} +import net.psforever.types.{CharacterGender, PlanetSideEmpire} +import objects.ActorTest + +class GUIDTaskRegister6Test extends ActorTest() { + "RegisterPlayer" in { + val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup + val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + val obj_wep = Tool(GlobalDefinitions.beamer) + obj.Slot(0).Equipment = obj_wep + val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj_wep.AmmoSlots.head.Box = obj_wep_ammo + val obj_inv_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj.Slot(6).Equipment = obj_inv_ammo + val obj_locker = obj.Slot(5).Equipment.get + val obj_locker_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj_locker.asInstanceOf[LockerContainer].Inventory += 0 -> obj_locker_ammo + + assert(!obj.HasGUID) + assert(!obj_wep.HasGUID) + assert(!obj_wep_ammo.HasGUID) + assert(!obj_inv_ammo.HasGUID) + assert(!obj_locker.HasGUID) + assert(!obj_locker_ammo.HasGUID) + taskResolver ! TaskResolver.GiveTask(new GUIDTaskTest.RegisterTestTask(probe.ref), List(GUIDTask.RegisterPlayer(obj)(uns))) + probe.expectMsg(scala.util.Success) + assert(obj.HasGUID) + assert(obj_wep.HasGUID) + assert(obj_wep_ammo.HasGUID) + assert(obj_inv_ammo.HasGUID) + assert(!obj_locker.HasGUID) + assert(!obj_locker_ammo.HasGUID) + } +} diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskUnregister6Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister6Test.scala new file mode 100644 index 000000000..718f460f0 --- /dev/null +++ b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister6Test.scala @@ -0,0 +1,44 @@ +// Copyright (c) 2017 PSForever +package objects.guidtask + +import net.psforever.objects._ +import net.psforever.objects.guid.{GUIDTask, TaskResolver} +import net.psforever.types.{CharacterGender, PlanetSideEmpire} +import objects.ActorTest + +class GUIDTaskUnregister6Test extends ActorTest() { + "UnregisterPlayer" in { + val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup + val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + val obj_wep = Tool(GlobalDefinitions.beamer) + obj.Slot(0).Equipment = obj_wep + val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj_wep.AmmoSlots.head.Box = obj_wep_ammo + val obj_inv_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj.Slot(6).Equipment = obj_inv_ammo + val obj_locker = obj.Slot(5).Equipment.get + val obj_locker_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj_locker.asInstanceOf[LockerContainer].Inventory += 0 -> obj_locker_ammo + guid.register(obj, "dynamic") + guid.register(obj_wep, "dynamic") + guid.register(obj_wep_ammo, "dynamic") + guid.register(obj_inv_ammo, "dynamic") + guid.register(obj_locker, "dynamic") + guid.register(obj_locker_ammo, "dynamic") + + assert(obj.HasGUID) + assert(obj_wep.HasGUID) + assert(obj_wep_ammo.HasGUID) + assert(obj_inv_ammo.HasGUID) + assert(obj_locker.HasGUID) + assert(obj_locker_ammo.HasGUID) + taskResolver ! TaskResolver.GiveTask(new GUIDTaskTest.RegisterTestTask(probe.ref), List(GUIDTask.UnregisterPlayer(obj)(uns))) + probe.expectMsg(scala.util.Success) + assert(!obj.HasGUID) + assert(!obj_wep.HasGUID) + assert(!obj_wep_ammo.HasGUID) + assert(!obj_inv_ammo.HasGUID) + assert(obj_locker.HasGUID) + assert(obj_locker_ammo.HasGUID) + } +} diff --git a/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala b/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala index be8d3e7fa..954a5837e 100644 --- a/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala +++ b/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala @@ -275,6 +275,40 @@ class UniqueNumberSystemTest8 extends ActorTest() { } } +class UniqueNumberSystemTest9 extends ActorTest() { + class EntityTestClass extends IdentifiableEntity + + "UniqueNumberSystem" should { + "Failures (manually walking the failure cases)" in { + val src : LimitedNumberSource = LimitedNumberSource(6000) + val guid : NumberPoolHub = new NumberPoolHub(src) + guid.AddPool("pool1", (1001 to 2000).toList).Selector = new RandomSelector + guid.AddPool("pool2", (3001 to 4000).toList).Selector = new RandomSelector + guid.AddPool("pool3", (5001 to 6000).toList).Selector = new RandomSelector + val uns = system.actorOf(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), "uns") + val excp = new Exception("EXCEPTION MESSAGE") + expectNoMsg(Duration.create(200, "ms")) + + //GiveNumber + uns ! NumberPoolActor.GiveNumber(1001, Some("test")) //no task associated with id="test" + uns ! NumberPoolActor.GiveNumber(1000, Some("test")) //no task associated with id="test" and number is not pooled + uns ! NumberPoolActor.GiveNumber(1000, Some(1)) //the task could theoretically exist, but does not + //NoNumber + uns ! NumberPoolActor.NoNumber(excp, Some(1)) + uns ! NumberPoolActor.NoNumber(excp, None) + uns ! NumberPoolActor.NoNumber(excp, Some("test")) + //ReturnNumberResult A + uns ! NumberPoolActor.ReturnNumberResult(1001, None, Some("test")) + uns ! NumberPoolActor.ReturnNumberResult(1000, None, Some("test")) + uns ! NumberPoolActor.ReturnNumberResult(1001, None, Some(1)) + uns ! NumberPoolActor.ReturnNumberResult(1000, None, Some(1)) + //ReturnNumberResult B + uns ! NumberPoolActor.ReturnNumberResult(1001, Some(excp), Some("test")) + uns ! NumberPoolActor.ReturnNumberResult(1001, Some(excp), Some(1)) + } + } +} + object UniqueNumberSystemTest { /** * @see `UniqueNumberSystem.AllocateNumberPoolActors(NumberPoolHub)(implicit ActorContext)` diff --git a/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala b/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala index 2201f6195..b635ad5de 100644 --- a/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.ActorRef -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone @@ -14,7 +14,7 @@ class AirVehicleTerminalTest extends Specification { "Air_Vehicle_Terminal" should { val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.air_vehicle_terminal) - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { diff --git a/common/src/test/scala/objects/terminal/CertTerminalTest.scala b/common/src/test/scala/objects/terminal/CertTerminalTest.scala index 8a9785d81..c9791c9ec 100644 --- a/common/src/test/scala/objects/terminal/CertTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/CertTerminalTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.ActorRef -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone import net.psforever.objects.{Avatar, GlobalDefinitions, Player} @@ -14,7 +14,7 @@ class CertTerminalTest extends Specification { "Cert_Terminal" should { val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.cert_terminal) - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { diff --git a/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala b/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala index 2b16b69cb..ef6c621b8 100644 --- a/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.ActorRef -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone @@ -14,7 +14,7 @@ class DropshipVehicleTerminalTest extends Specification { "Dropship_Vehicle_Terminal" should { val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.dropship_vehicle_terminal) - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { diff --git a/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala b/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala index 554d492f7..ee88b6027 100644 --- a/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.ActorRef -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone @@ -14,7 +14,7 @@ class GroundVehicleTerminalTest extends Specification { "Ground_Vehicle_Terminal" should { val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.ground_vehicle_terminal) - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { diff --git a/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala b/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala index 5a1d2c9dd..272815f78 100644 --- a/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala +++ b/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.ActorRef -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone @@ -14,7 +14,7 @@ class ImplantTerminalInterfaceTest extends Specification { "Implant_Terminal_Interface" should { val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.implant_terminal_interface) - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { diff --git a/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala b/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala index 7c333b861..91993769f 100644 --- a/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala +++ b/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala @@ -5,6 +5,7 @@ import akka.actor.{ActorRef, ActorSystem, Props} import net.psforever.objects.definition.SeatDefinition import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.implantmech.{ImplantTerminalMech, ImplantTerminalMechControl} +import net.psforever.objects.serverobject.structures.StructureType import net.psforever.objects.vehicles.Seat import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3} @@ -160,7 +161,7 @@ object ImplantTerminalMechTest { val terminal = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech) terminal.Actor = system.actorOf(Props(classOf[ImplantTerminalMechControl], terminal), "mech") - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = faction terminal.GUID = PlanetSideGUID(1) (Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), terminal) diff --git a/common/src/test/scala/objects/terminal/MatrixTerminalTest.scala b/common/src/test/scala/objects/terminal/MatrixTerminalTest.scala index 5fe8c3167..fbbb4d6e6 100644 --- a/common/src/test/scala/objects/terminal/MatrixTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/MatrixTerminalTest.scala @@ -22,12 +22,18 @@ class MatrixTerminalTest extends Specification { b.Name mustEqual "matrix_terminalb" } - "define (b)" in { + "define (c)" in { val b = new MatrixTerminalDefinition(519) b.ObjectId mustEqual 519 b.Name mustEqual "matrix_terminalc" } + "define (d)" in { + val b = new MatrixTerminalDefinition(812) + b.ObjectId mustEqual 812 + b.Name mustEqual "spawn_terminal" + } + "define (invalid)" in { var id : Int = (math.random * Int.MaxValue).toInt if(id == 517) { @@ -36,7 +42,7 @@ class MatrixTerminalTest extends Specification { else if(id == 518) { id += 2 } - else if(id == 519) { + else if(id == 519 | id == 812) { id += 1 } diff --git a/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala b/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala index d94b867bf..c4d44e14e 100644 --- a/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala +++ b/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.ActorRef -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.serverobject.terminals.{OrderTerminalABDefinition, Terminal} import net.psforever.objects.zones.Zone import net.psforever.objects.{Avatar, GlobalDefinitions, Player} @@ -39,7 +39,7 @@ class OrderTerminalABTest extends Specification { "Order_Terminal" should { val terminal = Terminal(GlobalDefinitions.order_terminala) - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { diff --git a/common/src/test/scala/objects/terminal/OrderTerminalTest.scala b/common/src/test/scala/objects/terminal/OrderTerminalTest.scala index e6e8c2c48..024c8794e 100644 --- a/common/src/test/scala/objects/terminal/OrderTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/OrderTerminalTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.ActorRef -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone import net.psforever.objects.{AmmoBox, Avatar, GlobalDefinitions, Player, Tool} @@ -14,7 +14,7 @@ class OrderTerminalTest extends Specification { "Order_Terminal" should { val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.order_terminal) - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { diff --git a/common/src/test/scala/objects/terminal/TerminalControlTest.scala b/common/src/test/scala/objects/terminal/TerminalControlTest.scala index e77bbd738..428d8d87e 100644 --- a/common/src/test/scala/objects/terminal/TerminalControlTest.scala +++ b/common/src/test/scala/objects/terminal/TerminalControlTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.{ActorSystem, Props} -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.serverobject.terminals.{Terminal, TerminalControl, TerminalDefinition} import net.psforever.objects.zones.Zone import net.psforever.objects.{Avatar, GlobalDefinitions, Player} @@ -122,7 +122,7 @@ object TerminalControlTest { def SetUpAgents(tdef : TerminalDefinition, faction : PlanetSideEmpire.Value)(implicit system : ActorSystem) : (Player, Terminal) = { val terminal = Terminal(tdef) terminal.Actor = system.actorOf(Props(classOf[TerminalControl], terminal), "test-term") - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = faction (Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), terminal) } diff --git a/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala b/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala index 85fb96ef7..0233dc9d6 100644 --- a/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala +++ b/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.ActorRef -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone @@ -14,7 +14,7 @@ class VehicleTerminalCombinedTest extends Specification { "Ground_Vehicle_Terminal" should { val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) val terminal = Terminal(GlobalDefinitions.vehicle_terminal_combined) - terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building) terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala index 72a17cc7b..6625e55f9 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/pslogin/src/main/scala/Maps.scala @@ -6,7 +6,7 @@ import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.mblocker.Locker import net.psforever.objects.serverobject.pad.VehicleSpawnPad -import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, WarpGate} +import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType, WarpGate} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.types.Vector3 @@ -23,7 +23,7 @@ object Maps { val map5 = new ZoneMap("map05") val map6 = new ZoneMap("map06") { - LocalBuilding(2, FoundationBuilder(Building.Structure)) //Anguta + LocalBuilding(2, FoundationBuilder(Building.Structure(StructureType.Facility, Vector3(3974.2344f, 4287.914f, 0)))) //Anguta LocalObject(222, Door.Constructor) //air term building, bay door LocalObject(370, Door.Constructor) //courtyard LocalObject(371, Door.Constructor) //courtyard @@ -193,17 +193,17 @@ object Maps { TerminalToSpawnPad(224, 501) TerminalToSpawnPad(2419, 500) - LocalBuilding(38, FoundationBuilder(Building.Structure)) //Anguta, West Bunker + LocalBuilding(38, FoundationBuilder(Building.Structure(StructureType.Bunker))) //Anguta, West Bunker LocalObject(362, Door.Constructor) ObjectToBuilding(362, 38) - LocalBuilding(42, FoundationBuilder(Building.Structure)) //Anguta, East Bunker(s) + LocalBuilding(42, FoundationBuilder(Building.Structure(StructureType.Bunker))) //Anguta, East Bunker(s) LocalObject(407, Door.Constructor) LocalObject(408, Door.Constructor) ObjectToBuilding(407, 42) ObjectToBuilding(408, 42) - LocalBuilding(48, FoundationBuilder(Building.Structure)) //North Anguta Watchtower + LocalBuilding(48, FoundationBuilder(Building.Structure(StructureType.Tower, Vector3(3864.2266f, 4518.0234f, 0)))) //North Anguta Watchtower LocalObject(364, Door.Constructor(Vector3(3871.9688f, 4509.992f, 269.65625f), Vector3(0f, 0f, 180f))) //s1 LocalObject(365, Door.Constructor(Vector3(3871.9688f, 4509.992f, 279.57812f), Vector3(0f, 0f, 180f))) //s2 LocalObject(366, Door.Constructor(Vector3(3871.9688f, 4509.992f, 299.57812f), Vector3(0f, 0f, 180f))) //s3 @@ -227,8 +227,8 @@ object Maps { LocalObject(1561, Terminal.Constructor(order_terminal)) LocalObject(1562, Terminal.Constructor(order_terminal)) LocalObject(1563, Terminal.Constructor(order_terminal)) - LocalObject(2138, SpawnTube.Constructor(Vector3(3870.9688f, 4505.7266f, 259.875f), Vector3(0, 0, 90))) - LocalObject(2139, SpawnTube.Constructor(Vector3(3870.9688f, 4522.1562f, 259.875f), Vector3(0, 0, 90))) + LocalObject(2138, SpawnTube.Constructor(respawn_tube_tower, Vector3(3870.9688f, 4505.7266f, 259.875f), Vector3(0, 0, 90))) + LocalObject(2139, SpawnTube.Constructor(respawn_tube_tower, Vector3(3870.9688f, 4522.1562f, 259.875f), Vector3(0, 0, 90))) LocalObject(2315, Door.Constructor) //spawn tube door LocalObject(2316, Door.Constructor) //spawn tube door ObjectToBuilding(364, 48) @@ -287,15 +287,13 @@ object Maps { LocalObject(1081, Terminal.Constructor(implant_terminal_interface)) //tube 520 TerminalToInterface(520, 1081) - LocalBuilding(2, FoundationBuilder(Building.Structure)) //HART building C + LocalBuilding(2, FoundationBuilder(Building.Structure(StructureType.Building))) //HART building C LocalObject(186, Terminal.Constructor(cert_terminal)) LocalObject(187, Terminal.Constructor(cert_terminal)) LocalObject(188, Terminal.Constructor(cert_terminal)) LocalObject(362, Door.Constructor) LocalObject(370, Door.Constructor) LocalObject(371, Door.Constructor) - LocalObject(372, Door.Constructor) - LocalObject(373, Door.Constructor) LocalObject(374, Door.Constructor) LocalObject(375, Door.Constructor) LocalObject(394, Door.Constructor) @@ -303,6 +301,8 @@ object Maps { LocalObject(396, Door.Constructor) LocalObject(397, Door.Constructor) LocalObject(398, Door.Constructor) + LocalObject(462, Door.Constructor) + LocalObject(463, Door.Constructor) LocalObject(522, ImplantTerminalMech.Constructor) LocalObject(523, ImplantTerminalMech.Constructor) LocalObject(524, ImplantTerminalMech.Constructor) @@ -337,8 +337,6 @@ object Maps { ObjectToBuilding(362, 2) ObjectToBuilding(370, 2) ObjectToBuilding(371, 2) - ObjectToBuilding(372, 2) - ObjectToBuilding(373, 2) ObjectToBuilding(374, 2) ObjectToBuilding(375, 2) ObjectToBuilding(394, 2) @@ -346,6 +344,8 @@ object Maps { ObjectToBuilding(396, 2) ObjectToBuilding(397, 2) ObjectToBuilding(398, 2) + ObjectToBuilding(462, 2) + ObjectToBuilding(463, 2) ObjectToBuilding(522, 2) ObjectToBuilding(523, 2) ObjectToBuilding(524, 2) @@ -383,7 +383,7 @@ object Maps { TerminalToInterface(528, 1088) TerminalToInterface(529, 1089) - LocalBuilding(29, FoundationBuilder(Building.Structure)) //South Villa Gun Tower + LocalBuilding(29, FoundationBuilder(Building.Structure(StructureType.Tower))) //South Villa Gun Tower LocalObject(330, Door.Constructor(Vector3(3979.9219f, 2592.0547f, 91.140625f), Vector3(0, 0, 180))) LocalObject(331, Door.Constructor(Vector3(3979.9219f, 2592.0547f, 111.140625f), Vector3(0, 0, 180))) LocalObject(332, Door.Constructor(Vector3(3979.9688f, 2608.0625f, 91.140625f), Vector3(0, 0, 0))) @@ -405,7 +405,61 @@ object Maps { DoorToLock(332, 556) DoorToLock(333, 557) - LocalBuilding(51, FoundationBuilder(Building.Structure)) + LocalBuilding(42, FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1, 0, 0)))) //spawn building south of HART C + LocalObject(258, Door.Constructor) //spawn tube door + LocalObject(259, Door.Constructor) //spawn tube door + LocalObject(260, Door.Constructor) //spawn tube door + LocalObject(261, Door.Constructor) //spawn tube door + LocalObject(262, Door.Constructor) //spawn tube door + LocalObject(263, Door.Constructor) //spawn tube door + LocalObject(372, Door.Constructor) //entrance + LocalObject(373, Door.Constructor) //entrance + LocalObject(430, Door.Constructor) //vr door + LocalObject(431, Door.Constructor) //vr door + LocalObject(432, Door.Constructor) //vr door + LocalObject(433, Door.Constructor) //vr door + LocalObject(434, Door.Constructor) //vr door + LocalObject(435, Door.Constructor) //vr door + LocalObject(744, SpawnTube.Constructor(Vector3(3684.336f, 2709.0469f, 91.859375f), Vector3(0, 0, 180))) + LocalObject(745, SpawnTube.Constructor(Vector3(3684.336f, 2713.2344f, 91.859375f), Vector3(0, 0, 0))) + LocalObject(746, SpawnTube.Constructor(Vector3(3691.0703f, 2709.0469f, 91.859375f), Vector3(0, 0, 180))) + LocalObject(747, SpawnTube.Constructor(Vector3(3691.0703f, 2713.2344f, 91.859375f), Vector3(0, 0, 0))) + LocalObject(748, SpawnTube.Constructor(Vector3(3697.711f, 2709.0469f, 91.859375f), Vector3(0, 0, 180))) + LocalObject(749, SpawnTube.Constructor(Vector3(3697.711f, 2713.2344f, 91.859375f), Vector3(0, 0, 0))) + LocalObject(852, Terminal.Constructor(order_terminal)) //s. wall + LocalObject(853, Terminal.Constructor(order_terminal)) //n. wall + LocalObject(854, Terminal.Constructor(order_terminal)) //s. wall + LocalObject(855, Terminal.Constructor(order_terminal)) //n. wall + LocalObject(859, Terminal.Constructor(order_terminal)) //s. wall + LocalObject(860, Terminal.Constructor(order_terminal)) //n. wall + ObjectToBuilding(258, 42) + ObjectToBuilding(259, 42) + ObjectToBuilding(260, 42) + ObjectToBuilding(261, 42) + ObjectToBuilding(262, 42) + ObjectToBuilding(263, 42) + ObjectToBuilding(372, 42) + ObjectToBuilding(373, 42) + ObjectToBuilding(430, 42) + ObjectToBuilding(431, 42) + ObjectToBuilding(432, 42) + ObjectToBuilding(433, 42) + ObjectToBuilding(434, 42) + ObjectToBuilding(435, 42) + ObjectToBuilding(744, 42) + ObjectToBuilding(745, 42) + ObjectToBuilding(746, 42) + ObjectToBuilding(747, 42) + ObjectToBuilding(748, 42) + ObjectToBuilding(749, 42) + ObjectToBuilding(852, 42) + ObjectToBuilding(853, 42) + ObjectToBuilding(854, 42) + ObjectToBuilding(855, 42) + ObjectToBuilding(859, 42) + ObjectToBuilding(860, 42) + + LocalBuilding(51, FoundationBuilder(Building.Structure(StructureType.Platform))) //air terminal west of HART C LocalObject(304, Terminal.Constructor(dropship_vehicle_terminal)) LocalObject(292, VehicleSpawnPad.Constructor(Vector3(3508.9844f, 2895.961f, 92.296875f), Vector3(0f, 0f, 270.0f)) @@ -414,7 +468,7 @@ object Maps { ObjectToBuilding(292, 51) TerminalToSpawnPad(304, 292) - LocalBuilding(77, FoundationBuilder(Building.Structure)) + LocalBuilding(77, FoundationBuilder(Building.Structure(StructureType.Platform))) //ground terminal west of HART C LocalObject(1063, Terminal.Constructor(ground_vehicle_terminal)) LocalObject(706, VehicleSpawnPad.Constructor(Vector3(3506.0f, 2820.0f, 92.0f), Vector3(0f, 0f, 270.0f)) @@ -422,18 +476,6 @@ object Maps { ObjectToBuilding(1063, 77) ObjectToBuilding(706, 77) TerminalToSpawnPad(1063, 706) - - //TODO check building id: these belong to a spawn building in HART C campus - LocalObject(462, Door.Constructor) - LocalObject(463, Door.Constructor) - LocalObject(853, Terminal.Constructor(order_terminal)) - LocalObject(855, Terminal.Constructor(order_terminal)) - LocalObject(860, Terminal.Constructor(order_terminal)) - ObjectToBuilding(462, 2) - ObjectToBuilding(463, 2) - ObjectToBuilding(853, 2) - ObjectToBuilding(855, 2) - ObjectToBuilding(860, 2) } val map14 = new ZoneMap("map13") diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 7ce17fe86..129afc99b 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -25,10 +25,10 @@ import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.mblocker.Locker import net.psforever.objects.serverobject.pad.VehicleSpawnPad -import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, SpawnTerminalDefinition, Terminal} +import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, Terminal} import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, VehicleLockState} -import net.psforever.objects.serverobject.structures.{Building, WarpGate} +import net.psforever.objects.serverobject.structures.{Building, StructureType, WarpGate} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.vehicles.{AccessPermissionGroup, VehicleLockState} @@ -56,6 +56,8 @@ class WorldSessionActor extends Actor with MDCContextAware { var taskResolver : ActorRef = Actor.noSender var galaxy : ActorRef = Actor.noSender var continent : Zone = null + var player : Player = null + var avatar : Avatar = null var progressBarValue : Option[Float] = None var shooting : Option[PlanetSideGUID] = None var accessedContainer : Option[PlanetSideGameObject with Container] = None @@ -66,9 +68,12 @@ class WorldSessionActor extends Actor with MDCContextAware { var clientKeepAlive : Cancellable = DefaultCancellable.obj var progressBarUpdate : Cancellable = DefaultCancellable.obj + var reviveTimer : Cancellable = DefaultCancellable.obj override def postStop() = { - clientKeepAlive.cancel() + clientKeepAlive.cancel + progressBarUpdate.cancel + reviveTimer.cancel localService ! Service.Leave() vehicleService ! Service.Leave() avatarService ! Service.Leave() @@ -282,7 +287,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case AvatarResponse.Release(tplayer) => if(tplayer_guid != guid) { - turnPlayerIntoCorpse(tplayer) + TurnPlayerIntoCorpse(tplayer) } case AvatarResponse.Reload(item_guid) => @@ -920,7 +925,6 @@ class WorldSessionActor extends Actor with MDCContextAware { continent.Transport ! Zone.SpawnVehicle(vehicle) vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.LoadVehicle(player_guid, vehicle, objedtId, vehicle_guid, vdata)) sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 1L)) //mount points off? - sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 1L)) //mount points off? sendResponse(PlanetsideAttributeMessage(vehicle_guid, 21, player_guid.guid)) //fte and ownership? //sendResponse(ObjectAttachMessage(vehicle_guid, player_guid, 0)) vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) //cancel queue timeout delay @@ -1002,6 +1006,64 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info(s"$tplayer has left zone ${zone.Id}") } + case Zone.Lattice.SpawnPoint(zone_id, building, spawn_tube) => + log.info(s"Zone.Lattice.SpawnPoint: spawn point on $zone_id in ${building.Id} @ ${spawn_tube.GUID.guid} selected") + reviveTimer.cancel + val sameZone = zone_id == continent.Id + val backpack = player.isBackpack + val respawnTime : Long = if(sameZone) { 10 } else { 0 } //s + val respawnTimeMillis = respawnTime * 1000 //ms + sendResponse(AvatarDeadStateMessage(DeadState.RespawnTime, respawnTimeMillis, respawnTimeMillis, Vector3.Zero, 2, true)) + val tplayer = if(backpack) { + RespawnClone(player) //new player + } + else { + val player_guid = player.GUID + sendResponse(ObjectDeleteMessage(player_guid, 4)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid, 4)) + player //player is deconstructing self + } + + tplayer.Position = spawn_tube.Position + tplayer.Orientation = spawn_tube.Orientation + import scala.concurrent.duration._ + import scala.concurrent.ExecutionContext.Implicits.global + val (target, msg) : (ActorRef, Any) = if(sameZone) { + if(backpack) { + //respawning from unregistered player + (taskResolver, RegisterAvatar(tplayer)) + } + else { + //move existing player + (self, PlayerLoaded(tplayer)) + } + } + else { + continent.Population ! Zone.Population.Leave(avatar) + val original = player + //TODO check player orientation upon spawn not polluted + if(backpack) { + //unregister avatar locker + GiveWorld + player = tplayer + (taskResolver, TaskBeforeZoneChange(GUIDTask.UnregisterLocker(original.Locker)(continent.GUID), zone_id)) + } + else { + //unregister avatar whole + GiveWorld + (taskResolver, TaskBeforeZoneChange(GUIDTask.UnregisterAvatar(original)(continent.GUID), zone_id)) + } + } + context.system.scheduler.scheduleOnce(respawnTime seconds, target, msg) + + case Zone.Lattice.NoValidSpawnPoint(zone_number, None) => + log.warn(s"Zone.Lattice.SpawnPoint: zone $zone_number could not be accessed as requested") + reviveTimer.cancel + RequestSanctuaryZoneSpawn(player, zone_number) + + case Zone.Lattice.NoValidSpawnPoint(zone_number, Some(spawn_group)) => + log.warn(s"Zone.Lattice.SpawnPoint: zone $zone_number has no available ${player.Faction} targets in spawn group $spawn_group") + reviveTimer.cancel + RequestSanctuaryZoneSpawn(player, zone_number) + case InterstellarCluster.ClientInitializationComplete() => LivePlayerList.Add(sessionId, avatar) //PropertyOverrideMessage @@ -1012,21 +1074,21 @@ class WorldSessionActor extends Actor with MDCContextAware { galaxy ! InterstellarCluster.GetWorld("z6") case InterstellarCluster.GiveWorld(zoneId, zone) => - log.info(s"Zone $zoneId has been loaded") + log.info(s"Zone $zoneId will now load") player.Continent = zoneId continent = zone continent.Population ! Zone.Population.Join(avatar) taskResolver ! RegisterNewAvatar(player) case NewPlayerLoaded(tplayer) => - log.info(s"Player $tplayer has been loaded") + log.info(s"Player ${tplayer.Name} has been loaded") player = tplayer //LoadMapMessage will cause the client to send back a BeginZoningMessage packet (see below) sendResponse(LoadMapMessage(continent.Map.Name, continent.Id, 40100,25,true,3770441820L)) AvatarCreate() //important! the LoadMapMessage must be processed by the client before the avatar is created case PlayerLoaded(tplayer) => - log.info(s"Player $tplayer has been loaded") + log.info(s"Player ${tplayer.Name} will respawn") player = tplayer AvatarCreate() self ! SetCurrentAvatar(tplayer) @@ -1034,22 +1096,25 @@ class WorldSessionActor extends Actor with MDCContextAware { case PlayerFailedToLoad(tplayer) => player.Continent match { case _ => - failWithError(s"$tplayer failed to load anywhere") + failWithError(s"${tplayer.Name} failed to load anywhere") } case SetCurrentAvatar(tplayer) => player = tplayer val guid = tplayer.GUID sendResponse(SetCurrentAvatarMessage(guid,0,0)) + if(spectator) { + sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, false, "", "on", None)) + } (0 until DetailedCharacterData.numberOfImplantSlots(tplayer.BEP)).foreach(slot => { sendResponse(AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 1)) //init implant slot sendResponse(AvatarImplantMessage(guid, ImplantAction.Activation, slot, 0)) //deactivate implant - //TODO: if this implant is Installed but does not have shortcut, add to a free slot or write over slot 61/62/63 + //TODO if this implant is Installed but does not have shortcut, add to a free slot or write over slot 61/62/63 }) sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0)) - //TODO: if Medkit does not have shortcut, add to a free slot or write over slot 64 + //TODO if Medkit does not have shortcut, add to a free slot or write over slot 64 sendResponse(CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT)) sendResponse(ChangeShortcutBankMessage(guid, 0)) //FavoritesMessage @@ -1195,10 +1260,6 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - var player : Player = null - var avatar : Avatar = null - var spawnZones : Map[Int, Building] = null - def handleGamePkt(pkt : PlanetSideGamePacket) = pkt match { case ConnectToWorldRequestMessage(server, token, majorVersion, minorVersion, revision, buildDate, unk) => val clientVersion = s"Client Version: $majorVersion.$minorVersion.$revision, $buildDate" @@ -1272,7 +1333,6 @@ class WorldSessionActor extends Actor with MDCContextAware { //TODO check if can spawn on last continent/location from player? //TODO if yes, get continent guid accessors //TODO if no, get sanctuary guid accessors and reset the player's expectations - //galaxy ! InterstellarCluster.GetWorld("z6") galaxy ! InterstellarCluster.RequestClientInitialization() case default => log.error("Unsupported " + default + " in " + msg) @@ -1283,18 +1343,14 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ BeginZoningMessage() => log.info("Reticulating splines ...") - configZone(continent) //todo density + configZone(continent) sendResponse(TimeOfDayMessage(1191182336)) - /** WIP */ - spawnZones = Map( - 7 -> continent.Building(2).get, - 6 -> continent.Building(48).get - ) //custom sendResponse(ContinentalLockUpdateMessage(13, PlanetSideEmpire.VS)) // "The VS have captured the VS Sanctuary." sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list - sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1)) + sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1)) //common + (0 to 255).foreach(i => { sendResponse(SetEmpireMessage(PlanetSideGUID(i), PlanetSideEmpire.VS)) }) //render Equipment that was dropped into zone before the player arrived continent.EquipmentOnGround.foreach(item => { @@ -1309,20 +1365,16 @@ class WorldSessionActor extends Actor with MDCContextAware { }) //load active players in zone continent.LivePlayers.filterNot(_.GUID == player.GUID).foreach(char => { - sendResponse( - ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get) - ) + sendResponse(ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get)) }) //load corpses in zone - continent.Corpses.foreach( turnPlayerIntoCorpse(_) ) + continent.Corpses.foreach { TurnPlayerIntoCorpse } //load active vehicles in zone continent.Vehicles.foreach(vehicle => { val definition = vehicle.Definition - sendResponse( - ObjectCreateMessage(definition.ObjectId, vehicle.GUID, definition.Packet.ConstructorData(vehicle).get) - ) + sendResponse(ObjectCreateMessage(definition.ObjectId, vehicle.GUID, definition.Packet.ConstructorData(vehicle).get)) //seat vehicle occupants - vehicle.Definition.MountPoints.values.foreach(seat_num => { + definition.MountPoints.values.foreach(seat_num => { vehicle.Seat(seat_num).get.Occupant match { case Some(tplayer) => if(tplayer.HasGUID) { @@ -1339,14 +1391,12 @@ class WorldSessionActor extends Actor with MDCContextAware { continent.GUID(interface_guid) match { case Some(obj : Terminal) => val objDef = obj.Definition - val obj_uid = objDef.ObjectId - val obj_data = objDef.Packet.ConstructorData(obj).get sendResponse( ObjectCreateMessage( - obj_uid, + ObjectClass.implant_terminal_interface, PlanetSideGUID(interface_guid), ObjectCreateMessageParent(parent_guid, 1), - obj_data + objDef.Packet.ConstructorData(obj).get ) ) case _ => ; @@ -1444,36 +1494,35 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ReleaseAvatarRequestMessage() => log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released") - //TODO is it easier to delete the player, then re-create them as a corpse? + reviveTimer.cancel player.Release - continent.Population ! Zone.Population.Release(avatar) - continent.Population ! Zone.Corpse.Add(player) - val knife = player.Slot(4).Equipment.get - taskResolver ! RemoveEquipmentFromSlot(player, knife, 4) - turnPlayerIntoCorpse(player) - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.Release(player, continent)) sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, 2, true)) + continent.Population ! Zone.Population.Release(avatar) + player.VehicleSeated match { + case None => + continent.Population ! Zone.Corpse.Add(player) //TODO move back out of this match case when changing below issue + val knife = player.Slot(4).Equipment.get + player.Slot(4).Equipment = None + taskResolver ! RemoveEquipmentFromSlot(player, knife, 4) + TurnPlayerIntoCorpse(player) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.Release(player, continent)) + + case Some(_) => + //TODO we do not want to delete the player if he is seated in a vehicle when releasing + //TODO it is necessary for now until we know how to juggle ownership properly + val player_guid = player.GUID + sendResponse(ObjectDeleteMessage(player_guid, 0)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid, 0)) + self ! PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, 0, true)) //let vehicle try to clean up its fields + taskResolver ! GUIDTask.UnregisterPlayer(player)(continent.GUID) + //sendResponse(ObjectDetachMessage(vehicle_guid, player.GUID, Vector3.Zero, 0, 0, 0)) + //sendResponse(PlayerStateShiftMessage(ShiftState(1, Vector3.Zero, 0))) + } case msg @ SpawnRequestMessage(u1, u2, u3, u4, u5) => log.info(s"SpawnRequestMessage: $msg") - spawnZones.get(u2.toInt) match { - case Some(building) => - scala.util.Random.shuffle(building.Amenities.filter(_.isInstanceOf[SpawnTube])).headOption match { //TODO temporary shuffle - case Some(tube) => - val tplayer = SpawnRequest(player) //new player - tplayer.Position = tube.Position - tplayer.Orientation = tube.Orientation - log.info(s"SpawnRequestMessage: new player will spawn in ${building.Id} @ ${tube.GUID.guid}") - sendResponse(AvatarDeadStateMessage(DeadState.RespawnTime, 10000, 10000, Vector3.Zero, 2, true)) - import scala.concurrent.duration._ - import scala.concurrent.ExecutionContext.Implicits.global - context.system.scheduler.scheduleOnce(10 seconds, taskResolver, RegisterAvatar(tplayer)) - case None => - log.warn(s"SpawnRequestMessage: can not find a spawn point in this spawn group - $u2") - } - case None => - log.warn(s"SpawnRequestMessage: can not find somewhere to spawn on ${continent.Id}") - } + //TODO just focus on u5 and u2 for now + galaxy ! Zone.Lattice.RequestSpawnPoint(u5.toInt, player, u2.toInt) case msg @ SetChatFilterMessage(send_channel, origin, whitelist) => log.info("SetChatFilters: " + msg) @@ -1516,15 +1565,11 @@ class WorldSessionActor extends Actor with MDCContextAware { } if(messagetype == ChatMessageType.CMT_SUICIDE) { - val player_guid = player.GUID - val pos = player.Position - player.Die - sendResponse(PlanetsideAttributeMessage(player_guid, 0, 0)) - sendResponse(PlanetsideAttributeMessage(player_guid, 2, 0)) - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 0, 0)) - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 2, 0)) - sendResponse(DestroyMessage(player_guid, player_guid, PlanetSideGUID(0), pos)) //how many players get this message? - sendResponse(AvatarDeadStateMessage(DeadState.Dead, 300000, 300000, pos, 2, true)) + KillPlayer(player) + } + + if(messagetype == ChatMessageType.CMT_DESTROY) { + self ! PacketCoding.CreateGamePacket(0, RequestDestroyMessage(PlanetSideGUID(contents.toInt))) } if (messagetype == ChatMessageType.CMT_VOICE) { @@ -1853,7 +1898,8 @@ class WorldSessionActor extends Actor with MDCContextAware { // TODO: Make sure this is the correct response for all cases continent.GUID(object_guid) match { case Some(vehicle : Vehicle) => - if(player.VehicleOwned.contains(object_guid) && vehicle.Owner.contains(player.GUID)) { + if((player.VehicleOwned.contains(object_guid) && vehicle.Owner.contains(player.GUID)) + || (player.Faction == vehicle.Faction && (vehicle.Owner.isEmpty || vehicle.Health == 0))) { vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(object_guid) vehicleService ! VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent) log.info(s"RequestDestroy: vehicle $object_guid") @@ -2084,7 +2130,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } case Some(obj : Terminal) => - if(obj.Definition.isInstanceOf[MatrixTerminalDefinition] || obj.Definition.isInstanceOf[SpawnTerminalDefinition]) { + if(obj.Definition.isInstanceOf[MatrixTerminalDefinition]) { //TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks) sendResponse(BindPlayerMessage(1, "@ams", true, true, 0, 0, 0, obj.Position)) } @@ -2092,6 +2138,13 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) } + case Some(obj : SpawnTube) => + //deconstruction + PlayerActionsToCancel() + player.Release + sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, 2, true)) + continent.Population ! Zone.Population.Release(avatar) + case Some(obj : PlanetSideGameObject) => if(itemType != 121) { sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) @@ -2217,7 +2270,7 @@ class WorldSessionActor extends Actor with MDCContextAware { def dismountWarning(msg : String) : Unit = { log.warn(s"$msg; some vehicle might not know that a player is no longer sitting in it") } - if(player.GUID == player_guid) { + if(player.HasGUID && player.GUID == player_guid) { //normally disembarking from a seat player.VehicleSeated match { case Some(obj_guid) => @@ -2562,32 +2615,10 @@ class WorldSessionActor extends Actor with MDCContextAware { override def onFailure(ex : Throwable) : Unit = { localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WSA } - }, List(RegisterLightweightAvatar(tplayer)(continent.GUID)) + }, List(GUIDTask.RegisterPlayer(tplayer)(continent.GUID)) ) } - //TODO temporary function for registering avatar without locker contents - def RegisterLightweightAvatar(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = { - import net.psforever.objects.LockerContainer - import net.psforever.objects.inventory.InventoryItem - val holsterTasks = tplayer.Holsters().filter(_.Equipment.isDefined).map(slot =>{ - GUIDTask.RegisterEquipment(slot.Equipment.get)(guid) - }).toList - val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => GUIDTask.RegisterEquipment(entry.obj)(guid)}) - TaskResolver.GiveTask(GUIDTask.RegisterObjectTask(tplayer)(guid).task, holsterTasks ++ inventoryTasks) - } - - //TODO temporary function for unregistering avatar without locker contents - def UnregisterLightweightAvatar(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = { - import net.psforever.objects.LockerContainer - import net.psforever.objects.inventory.InventoryItem - val holsterTasks = tplayer.Holsters().filter(_.Equipment.isDefined).map(slot =>{ - GUIDTask.UnregisterEquipment(slot.Equipment.get)(guid) - }).toList - val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => GUIDTask.UnregisterEquipment(entry.obj)(guid)}) - TaskResolver.GiveTask(GUIDTask.UnregisterObjectTask(tplayer)(guid).task, holsterTasks ++ inventoryTasks) - } - /** * Construct tasking that adds a completed and registered vehicle into the scene. * Use this function to renew the globally unique identifiers on a vehicle that has already been added to the scene once. @@ -2739,6 +2770,22 @@ class WorldSessionActor extends Actor with MDCContextAware { ) } + def TaskBeforeZoneChange(priorTask : TaskResolver.GiveTask, zoneId : String) : TaskResolver.GiveTask = { + TaskResolver.GiveTask( + new Task() { + private val localService = galaxy + private val localMsg = InterstellarCluster.GetWorld(zoneId) + + override def isComplete : Task.Resolution.Value = Task.Resolution.Success + + def Execute(resolver : ActorRef) : Unit = { + localService ! localMsg + resolver ! scala.util.Success(this) + } + }, List(priorTask) + ) + } + /** * After a client has connected to the server, their account is used to generate a list of characters. * On the character selection screen, each of these characters is made to exist temporarily when one is selected. @@ -3223,10 +3270,10 @@ class WorldSessionActor extends Actor with MDCContextAware { * @param building the building object */ def initBuilding(continentNumber : Int, buildingNumber : Int, building : Building) : Unit = { - building match { - case _ : WarpGate => + building.BuildingType match { + case StructureType.WarpGate => initGate(continentNumber, buildingNumber, building) - case _ : Building => + case _ => initFacility(continentNumber, buildingNumber, building) } } @@ -3309,6 +3356,14 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(BroadcastWarpgateUpdateMessage(continentNumber, buildingNumber, false, false, true)) } + /** + * Configure the buildings and each specific amenity for that building in a given zone by sending the client packets. + * These actions are performed during the loading of a zone. + * @see `SetEmpireMessage`
+ * `PlanetsideAttributeMessage`
+ * `HackMessage` + * @param zone the zone being loaded + */ def configZone(zone : Zone) : Unit = { zone.Buildings.values.foreach(building => { sendResponse(SetEmpireMessage(PlanetSideGUID(building.ModelId), building.Faction)) @@ -3322,11 +3377,90 @@ class WorldSessionActor extends Actor with MDCContextAware { } /** - * TODO write + * The player has lost all his vitality and must be killed.
+ *
+ * Shift directly into a state of being dead on the client by setting health to zero points, + * whereupon the player will perform a dramatic death animation. + * Stamina is also set to zero points. + * If the player was in a vehicle at the time of demise, special conditions apply and + * the model must be manipulated so it behaves correctly. + * Do not move or completely destroy the `Player` object as its coordinates of death will be important.
+ *
+ * A maximum revive waiting timer is started. + * When this timer reaches zero, the avatar will attempt to spawn back on its faction-specific sanctuary continent. + * @pararm tplayer the player to be killed + */ + def KillPlayer(tplayer : Player) : Unit = { + val player_guid = tplayer.GUID + val pos = tplayer.Position + val respawnTimer = 300000 //milliseconds + tplayer.Die + sendResponse(PlanetsideAttributeMessage(player_guid, 0, 0)) + sendResponse(PlanetsideAttributeMessage(player_guid, 2, 0)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 0, 0)) + sendResponse(DestroyMessage(player_guid, player_guid, PlanetSideGUID(0), pos)) //how many players get this message? + sendResponse(AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, pos, 2, true)) + if(tplayer.VehicleSeated.nonEmpty) { + //make player invisible (if not, the cadaver sticks out the side in a seated position) + sendResponse(PlanetsideAttributeMessage(player_guid, 29, 1)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 29, 1)) + } + PlayerActionsToCancel() + + import scala.concurrent.duration._ + import scala.concurrent.ExecutionContext.Implicits.global + reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer milliseconds, galaxy, Zone.Lattice.RequestSpawnPoint(Zones.SanctuaryZoneNumber(tplayer.Faction), tplayer, 7)) + } + + /** + * An event has occurred that would cause the player character to stop certain stateful activities. + * These activities include shooting, hacking, accessing (a container), flying, and running. + * Other players in the same zone must be made aware that the player has stopped as well.
+ *
+ * Things whose configuration should not be changed:
+ * - if the player is seated + */ + def PlayerActionsToCancel() : Unit = { + progressBarUpdate.cancel + progressBarValue = None + accessedContainer match { + case Some(obj : Vehicle) => + if(obj.AccessingTrunk.contains(player.GUID)) { + obj.AccessingTrunk = None + UnAccessContents(obj) + } + accessedContainer = None + + case Some(_) => + accessedContainer = None + + case None => ; + } + shooting match { + case Some(guid) => + sendResponse(ChangeFireStateMessage_Stop(guid)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, guid)) + shooting = None + case None => ; + } + if(flying) { + sendResponse(ChatMsg(ChatMessageType.CMT_FLY, false, "", "off", None)) + flying = false + } + if(speed > 1) { + sendResponse(ChatMsg(ChatMessageType.CMT_SPEED, false, "", "1.000", None)) + speed = 1f + } + } + + /** + * A part of the process of spawning the player into the game world. + * The function should work regardless of whether the player is alive or dead - it will make them alive. + * It adds the `WSA`-current `Player` to the current zone and sends out the expected packets. */ def AvatarCreate() : Unit = { player.Spawn - player.Health = 50 + player.Health = 50 //TODO temp val packet = player.Definition.Packet val dcdata = packet.DetailedConstructorData(player).get sendResponse(ObjectCreateDetailedMessage(ObjectClass.avatar, player.GUID, dcdata)) @@ -3335,11 +3469,16 @@ class WorldSessionActor extends Actor with MDCContextAware { log.debug(s"ObjectCreateDetailedMessage: $dcdata") } - def SpawnRequest(tplayer : Player) : Player = { + /** + * Produce a clone of the player that is equipped with the default infantry loadout. + * The loadout is hardcoded. + * The player is expected to be in a Standard Exo-Suit. + * @param tplayer the original player + * @return the duplication of the player, in Standard Exo-Suit and with default equipment loadout + */ + def RespawnClone(tplayer : Player) : Player = { val faction = tplayer.Faction val obj = Player.Respawn(tplayer) - //obj.VehicleOwned = tplayer.VehicleOwned - //obj.Continent = tplayer.Continent obj.Slot(0).Equipment = Tool(StandardPistol(faction)) obj.Slot(2).Equipment = Tool(suppressor) obj.Slot(4).Equipment = Tool(StandardMelee(faction)) @@ -3352,13 +3491,38 @@ class WorldSessionActor extends Actor with MDCContextAware { obj } - def turnPlayerIntoCorpse(tplayer : Player) : Unit = { - //sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 6, 1)) + /** + * Creates a player that has the characteristics of a corpse. + * To the game, that is a backpack (or some pastry, festive graphical modification allowing). + * @see `CorpseConverter.converter` + * @param tplayer the player + */ + def TurnPlayerIntoCorpse(tplayer : Player) : Unit = { sendResponse( ObjectCreateDetailedMessage(ObjectClass.avatar, tplayer.GUID, CorpseConverter.converter.DetailedConstructorData(tplayer).get) ) } + /** + * Attempt to tranfer to the player's faction-specific sanctuary continent. + * If the server thinks the player is already on his sanctuary continent, + * it will disconnect the player under the assumption that an error has occurred. + * Eventually, this functionality should support better error-handling before it jumps to the conclusion: + * "Disconnecting the client is the safest option." + * @see `Zones.SanctuaryZoneNumber` + * @param tplayer the player + * @param currentZone the current cone number + */ + def RequestSanctuaryZoneSpawn(tplayer : Player, currentZone : Int) : Unit = { + val sanctNumber = Zones.SanctuaryZoneNumber(tplayer.Faction) + if(currentZone == sanctNumber) { + sendResponse(DisconnectMessage("Player failed to load on faction's sanctuary continent. Please relog.")) + } + else { + galaxy ! Zone.Lattice.RequestSpawnPoint(sanctNumber, tplayer, 7) + } + } + def failWithError(error : String) = { log.error(error) sendResponse(ConnectionClose()) diff --git a/pslogin/src/main/scala/Zones.scala b/pslogin/src/main/scala/Zones.scala index 051fcf5ad..6cf61e332 100644 --- a/pslogin/src/main/scala/Zones.scala +++ b/pslogin/src/main/scala/Zones.scala @@ -1,6 +1,7 @@ // Copyright (c) 2017 PSForever import akka.actor.ActorContext import net.psforever.objects.zones.Zone +import net.psforever.types.PlanetSideEmpire object Zones { val z1 = new Zone("z1", Maps.map1, 1) @@ -86,4 +87,32 @@ object Zones { val i3 = new Zone("i3", Maps.map98, 31) val i4 = new Zone("i4", Maps.map99, 32) + + /** + * Get the zone identifier name for the sanctuary continent of a given empire. + * @param faction the empire + * @return the zone id, with a blank string as an invalidating result + */ + def SanctuaryZoneId(faction : PlanetSideEmpire.Value) : String = { + faction match { + case PlanetSideEmpire.TR => "home1" + case PlanetSideEmpire.NC => "home2" + case PlanetSideEmpire.VS => "home3" + case PlanetSideEmpire.NEUTRAL => "" //invalid, not black ops + } + } + + /** + * Get the zone number for the sanctuary continent of a given empire. + * @param faction the empire + * @return the zone number, within the sequence 1-32, and with 0 as an invalidating result + */ + def SanctuaryZoneNumber(faction : PlanetSideEmpire.Value) : Int = { + faction match { + case PlanetSideEmpire.TR => 11 + case PlanetSideEmpire.NC => 12 + case PlanetSideEmpire.VS => 13 + case PlanetSideEmpire.NEUTRAL => 0 //invalid, not black ops + } + } } diff --git a/pslogin/src/main/scala/services/avatar/support/UndertakerActor.scala b/pslogin/src/main/scala/services/avatar/support/UndertakerActor.scala index bc9e81220..83788a174 100644 --- a/pslogin/src/main/scala/services/avatar/support/UndertakerActor.scala +++ b/pslogin/src/main/scala/services/avatar/support/UndertakerActor.scala @@ -92,7 +92,7 @@ class UndertakerActor extends Actor { override def onFailure(ex : Throwable): Unit = { localAnnounce ! UndertakerActor.FailureToWork(localCorpse, localZone, ex) } - }, List(GUIDTask.UnregisterAvatar(corpse)(zone.GUID)) + }, List(GUIDTask.UnregisterPlayer(corpse)(zone.GUID)) ) }