diff --git a/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala index 72100a61..e7470546 100644 --- a/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala +++ b/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala @@ -3,26 +3,29 @@ package net.psforever.objects.loadouts import net.psforever.types.ExoSuitType +/** + * A blueprint of a player's uniform, their holster items, and their inventory items, saved in a specific state. + * This previous state can be restored on any given player template + * by reconstructing the items (if permitted) and re-assigning the uniform (if available).
+ *
+ * The fifth tab on an `order_terminal` window is occupied by the list of "Favorite" `Loadout` blueprints. + * The ten-long list is initialized with `FavoritesMessage` packets assigned to the "Infantry" list. + * Specific entries are added or removed using `FavoritesRequest` packets, + * re-established using other conventional game packets. + * @param label the name by which this inventory will be known when displayed in a Favorites list; + * field gets inherited + * @param visible_slots simplified representation of the `Equipment` that can see "seen" on the target; + * field gets inherited + * @param inventory simplified representation of the `Equipment` in the target's inventory or trunk; + * field gets inherited + * @param exosuit the exo-suit in which the avatar will be dressed; + * may be restricted + * @param subtype the mechanized assault exo-suit specialization number that indicates whether the MAX performs: + * anti-infantry (1), anti-vehicular (2), or anti-air work (3); + * the default value is 0 + */ final case class InfantryLoadout(label : String, visible_slots : List[Loadout.SimplifiedEntry], inventory : List[Loadout.SimplifiedEntry], exosuit : ExoSuitType.Value, - subtype : Int) extends Loadout(label, visible_slots, inventory) { - /** - * The exo-suit in which the avatar will be dressed. - * Might be restricted and, thus, restrict the rest of the `Equipment` from being constructed and given. - * @return the exo-suit - */ - def ExoSuit : ExoSuitType.Value = exosuit - - /** - * The mechanized assault exo-suit specialization number that indicates whether the MAX performs: - * anti-infantry (1), - * anti-vehicular (2), - * or anti-air work (3). - * The major distinction is the type of arm weapons that MAX is equipped. - * When the blueprint doesn't call for a MAX, the number will be 0. - * @return the specialization number - */ - def Subtype : Int = subtype -} + subtype : Int) extends Loadout(label, visible_slots, inventory) diff --git a/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala index 89d2261c..7035c89d 100644 --- a/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala +++ b/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala @@ -10,27 +10,16 @@ import net.psforever.types.ExoSuitType import scala.annotation.tailrec /** - * From a `Player` their current exo-suit and their `Equipment`, retain a set of instructions to reconstruct this arrangement.
+ * The base of all specific kinds of blueprint containers. + * This previous state can be restored on any appropriate template from which the loadout was copied + * by reconstructing the items (if permitted). + * The three fields are the name assigned to the loadout, + * the visible items that are created (which obey different rules depending on the source), + * and the concealed items that are created and added to the source's `Inventory`.
+ * For example, the `visible_slots` on a `Player`-borne loadout will transform into the form `Array[EquipmentSlot]`; + * `Vehicle`-originating loadouts transform into the form `Map[Int, Equipment]`. *
- * `Loadout` objects are composed of the following information, as if a blueprint:
- * - the avatar's current exo-suit
- * - the type of specialization, called a "subtype" (mechanized assault exo-suits only)
- * - the contents of the avatar's occupied holster slots
- * - the contents of the avatar's occupied inventory
- * `Equipment` contents of the holsters and of the formal inventory region will be condensed into a simplified form. - * These are also "blueprints." - * At its most basic, this simplification will merely comprise the former object's `EquipmentDefinition`. - * For items that are already simple - `Kit` objects and `SimpleItem` objects - this form will not be too far removed. - * For more complicated affairs like `Tool` objects and `AmmoBox` objects, only essential information will be retained.
- *
- * The deconstructed blueprint can be applied to any avatar. - * They are, however, typically tied to unique users and unique characters. - * For reasons of certifications, however, permissions on that avatar may affect what `Equipment` can be distributed. - * Even a whole blueprint can be denied if the user lacks the necessary exo-suit certification. - * A completely new piece of `Equipment` is constructed when the `Loadout` is regurgitated.
- *
- * The fifth tab on an `order_terminal` window is for "Favorite" blueprints for `Loadout` entries. - * The ten-long list is initialized with `FavoritesMessage` packets. + * The lists of user-specific loadouts are initialized with `FavoritesMessage` packets. * Specific entries are loaded or removed using `FavoritesRequest` packets. * @param label the name by which this inventory will be known when displayed in a Favorites list * @param visible_slots simplified representation of the `Equipment` that can see "seen" on the target @@ -38,27 +27,15 @@ import scala.annotation.tailrec */ abstract class Loadout(label : String, visible_slots : List[Loadout.SimplifiedEntry], - inventory : List[Loadout.SimplifiedEntry]) { - /** - * The label by which this `Loadout` is called. - * @return the label - */ - def Label : String = label - - /** - * The `Equipment` in the `Player`'s holster slots when this `Loadout` is created. - * @return a `List` of the holster item blueprints - */ - def VisibleSlots : List[Loadout.SimplifiedEntry] = visible_slots - - /** - * The `Equipment` in the `Player`'s inventory region when this `Loadout` is created. - * @return a `List` of the inventory item blueprints - */ - def Inventory : List[Loadout.SimplifiedEntry] = inventory -} + inventory : List[Loadout.SimplifiedEntry]) object Loadout { + /** + * Produce the blueprint on a player. + * @param player the player + * @param label the name of this loadout + * @return an `InfantryLoadout` object populated with appropriate information about the current state of the player + */ def Create(player : Player, label : String) : Loadout = { InfantryLoadout( label, @@ -69,6 +46,12 @@ object Loadout { ) } + /** + * Produce the blueprint of a vehicle. + * @param vehicle the vehicle + * @param label the name of this loadout + * @return a `VehicleLoadout` object populated with appropriate information about the current state of the vehicle + */ def Create(vehicle : Vehicle, label : String) : Loadout = { VehicleLoadout( label, @@ -128,10 +111,29 @@ object Loadout { */ final case class ShorthandKit(definition : KitDefinition) extends Simplification + /** + * The sub-type of the player's uniform. + * Applicable to mechanized assault units, mainly. + * The subtype is reported as a number but indicates the specialization - anti-infantry, ani-vehicular, anti-air - of the suit + * as indicated by the arm weapon(s). + * @param player the player + * @return the numeric subtype + */ def DetermineSubtype(player : Player) : Int = { DetermineSubtype(player.ExoSuit, player.Slot(0).Equipment) } + /** + * The sub-type of the player's uniform. + * Applicable to mechanized assault units, mainly. + * The subtype is reported as a number but indicates the specialization - anti-infantry, ani-vehicular, anti-air - of the suit + * as indicated by the arm weapon(s). + * @param suit the player's uniform; + * the target is for MAX armors + * @param weapon any weapon the player may have it his "first pistol slot;" + * to a MAX, that is its "primary weapon slot" + * @return the numeric subtype + */ def DetermineSubtype(suit : ExoSuitType.Value, weapon : Option[Equipment]) : Int = { if(suit == ExoSuitType.MAX) { weapon match { diff --git a/common/src/main/scala/net/psforever/objects/loadouts/VehicleLoadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/VehicleLoadout.scala index b4713d64..5f7e0b63 100644 --- a/common/src/main/scala/net/psforever/objects/loadouts/VehicleLoadout.scala +++ b/common/src/main/scala/net/psforever/objects/loadouts/VehicleLoadout.scala @@ -3,9 +3,25 @@ package net.psforever.objects.loadouts import net.psforever.objects.definition._ +/** + * A blueprint of a vehicle's mounted weapons and its inventory items, saved in a specific state. + * This previous state can be restored on an apporpriate vehicle template + * by reconstructing the items (if permitted). + * Mismatched vehicles may produce no loadout or an imperfect loadout depending on specifications.
+ *
+ * The second tab on an `repair_silo` window is occupied by the list of "Favorite" `Loadout` blueprints. + * The five-long list is initialized with `FavoritesMessage` packets assigned to the "Vehicle" list. + * Specific entries are added or removed using `FavoritesRequest` packets, + * re-established using other conventional game packets. + * @param label the name by which this inventory will be known when displayed in a Favorites list; + * field gets inherited + * @param visible_slots simplified representation of the `Equipment` that can see "seen" on the target; + * field gets inherited + * @param inventory simplified representation of the `Equipment` in the target's inventory or trunk; + * field gets inherited + * @param vehicle_definition the original type of vehicle whose state is being populated + */ final case class VehicleLoadout(label : String, visible_slots : List[Loadout.SimplifiedEntry], inventory : List[Loadout.SimplifiedEntry], - vehicle_definition : VehicleDefinition) extends Loadout(label, visible_slots, inventory) { - def Definition : VehicleDefinition = vehicle_definition -} \ No newline at end of file + vehicle_definition : VehicleDefinition) extends Loadout(label, visible_slots, inventory) diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalABDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalABDefinition.scala index 3aaac12a..c90aca31 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalABDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalABDefinition.scala @@ -54,10 +54,10 @@ class OrderTerminalABDefinition(object_id : Int) extends EquipmentTerminalDefini if(msg.item_page == 4) { //Favorites tab player.LoadLoadout(msg.unk1) match { case Some(loadout : InfantryLoadout) => - if(loadout.ExoSuit != ExoSuitType.MAX) { - val holsters = loadout.VisibleSlots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) - val inventory = loadout.Inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) - Terminal.InfantryLoadout(loadout.ExoSuit, loadout.Subtype, holsters, inventory) + if(loadout.exosuit != ExoSuitType.MAX) { + val holsters = loadout.visible_slots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) + val inventory = loadout.inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) + Terminal.InfantryLoadout(loadout.exosuit, loadout.subtype, holsters, inventory) } else { Terminal.NoDeal() diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala index 8a270b34..d9730bd6 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala @@ -39,9 +39,9 @@ class OrderTerminalDefinition extends EquipmentTerminalDefinition(612) { if(msg.item_page == 4) { //Favorites tab player.LoadLoadout(msg.unk1) match { case Some(loadout : InfantryLoadout) => - val holsters = loadout.VisibleSlots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) - val inventory = loadout.Inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) - Terminal.InfantryLoadout(loadout.ExoSuit, loadout.Subtype, holsters, inventory) + val holsters = loadout.visible_slots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) + val inventory = loadout.inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) + Terminal.InfantryLoadout(loadout.exosuit, loadout.subtype, holsters, inventory) case Some(_) | None => Terminal.NoDeal() } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityUnit.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityUnit.scala index 8339e51e..25843fa1 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityUnit.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityUnit.scala @@ -6,26 +6,27 @@ import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.packet.game.PlanetSideGUID /** - * A server object that is a "terminal" that can be accessed for amenities and services, - * triggered when a certain distance from the unit itself (proximity-based).
- *
- * Unlike conventional terminals, this structure is not necessarily structure-owned. + * A server object that provides a service, triggered when a certain distance from the unit itself (proximity-based). + * Unlike conventional terminals, this one is not necessarily structure-owned. * For example, the cavern crystals are considered owner-neutral elements that are not attached to a `Building` object. */ trait ProximityUnit { this : Terminal => - private var users : Set[PlanetSideGUID] = Set.empty + /** + * A list of targets that are currently affected by this proximity unit. + */ + private var targets : Set[PlanetSideGUID] = Set.empty - def NumberUsers : Int = users.size + def NumberUsers : Int = targets.size def AddUser(player_guid : PlanetSideGUID) : Int = { - users += player_guid + targets += player_guid NumberUsers } def RemoveUser(player_guid : PlanetSideGUID) : Int = { - users -= player_guid + targets -= player_guid NumberUsers } } @@ -33,11 +34,15 @@ trait ProximityUnit { object ProximityUnit { import akka.actor.Actor + /** + * A mixin `trait` for an `Actor`'s `PartialFunction` that handles messages, + * in this case handling messages that controls the telegraphed state of the `ProximityUnit` object as the number of users changes. + */ trait Use { this : Actor => def TerminalObject : Terminal with ProximityUnit - + val proximityBehavior : Receive = { case CommonMessages.Use(player) => val hadNoUsers = TerminalObject.NumberUsers == 0 @@ -52,4 +57,4 @@ object ProximityUnit { } } } -} \ No newline at end of file +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmControl.scala index b5514305..2451fbb3 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmControl.scala @@ -4,6 +4,11 @@ package net.psforever.objects.serverobject.terminals import akka.actor.Actor import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} +/** + * An `Actor` that handles messages being dispatched to a specific `IFFLock`. + * @param term the `RepairRearmSilo` object being governed + * @see `CommonMessages` + */ class RepairRearmControl(term : RepairRearmSilo) extends Actor with FactionAffinityBehavior.Check with ProximityUnit.Use { def FactionObject : FactionAffinity = term diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSilo.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSilo.scala index 45a01302..b01cfbae 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSilo.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSilo.scala @@ -2,6 +2,9 @@ package net.psforever.objects.serverobject.terminals /** + * A structure-owned server object for preserving vehicle loadouts, + * obtaining vehicle weapon ammunition, + * and, with proper perks, automatically repairing damage doen to allied vehicles. * A server object that is a "terminal" that can be accessed for amenities and services, * triggered when a certain distance from the unit itself (proximity-based).
*
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala index baf37bc4..2fd82802 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala @@ -7,6 +7,10 @@ import net.psforever.objects.loadouts.VehicleLoadout import net.psforever.objects.serverobject.terminals.EquipmentTerminalDefinition.BuildSimplifiedPattern import net.psforever.packet.game.ItemTransactionMessage +/** + * The `Definition` for any `Terminal` that is of a type "repair_silo." + * Has both proximity-based operation and direct access purchasing power. + */ class RepairRearmSiloDefinition(objectId : Int) extends EquipmentTerminalDefinition(objectId) { Name = "repair_silo" @@ -18,8 +22,8 @@ class RepairRearmSiloDefinition(objectId : Int) extends EquipmentTerminalDefinit if(msg.item_page == 4) { //Favorites tab player.LoadLoadout(msg.unk1 + 10) match { case Some(loadout : VehicleLoadout) => - val weapons = loadout.VisibleSlots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) - val inventory = loadout.Inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) + val weapons = loadout.visible_slots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) + val inventory = loadout.inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) Terminal.VehicleLoadout(loadout.vehicle_definition, weapons, inventory) case _ => Terminal.NoDeal() diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala index bff5fe26..8785ae7a 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala @@ -487,12 +487,12 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition vehicles.get(msg.item_name) match { case Some(vehicle) => val (weapons, inventory) = trunk.get(msg.item_name) match { - case Some(loadout) => + case Some(loadout : VehicleLoadout) => ( - loadout.VisibleSlots.map(entry => { InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index) }), - loadout.Inventory.map(entry => { InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index) }) + loadout.visible_slots.map(entry => { InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index) }), + loadout.inventory.map(entry => { InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index) }) ) - case None => + case _ => (List.empty, List.empty) } Terminal.BuyVehicle(vehicle(), weapons, inventory) diff --git a/common/src/test/scala/objects/AvatarTest.scala b/common/src/test/scala/objects/AvatarTest.scala index 6a45225d..6c331798 100644 --- a/common/src/test/scala/objects/AvatarTest.scala +++ b/common/src/test/scala/objects/AvatarTest.scala @@ -293,12 +293,12 @@ class AvatarTest extends Specification { avatar.LoadLoadout(0) match { case Some(items : InfantryLoadout) => - items.Label mustEqual "test" - items.ExoSuit mustEqual obj.ExoSuit - items.Subtype mustEqual 0 + items.label mustEqual "test" + items.exosuit mustEqual obj.ExoSuit + items.subtype mustEqual 0 - items.VisibleSlots.length mustEqual 3 - val holsters = items.VisibleSlots.sortBy(_.index) + items.visible_slots.length mustEqual 3 + val holsters = items.visible_slots.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 @@ -308,8 +308,8 @@ class AvatarTest extends Specification { 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) + 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 @@ -349,11 +349,11 @@ class AvatarTest extends Specification { avatar.LoadLoadout(0) match { case Some(items : InfantryLoadout) => - items.Label mustEqual "test" - items.ExoSuit mustEqual obj.ExoSuit - items.Subtype mustEqual 0 - items.VisibleSlots.length mustEqual 3 - items.Inventory.length mustEqual 0 //empty + items.label mustEqual "test" + items.exosuit mustEqual obj.ExoSuit + items.subtype mustEqual 0 + items.visible_slots.length mustEqual 3 + items.inventory.length mustEqual 0 //empty case _ => ko } @@ -368,11 +368,11 @@ class AvatarTest extends Specification { avatar.LoadLoadout(0) match { case Some(items : InfantryLoadout) => - items.Label mustEqual "test" - items.ExoSuit mustEqual obj.ExoSuit - items.Subtype mustEqual 0 - items.VisibleSlots.length mustEqual 0 //empty - items.Inventory.length mustEqual 6 + items.label mustEqual "test" + items.exosuit mustEqual obj.ExoSuit + items.subtype mustEqual 0 + items.visible_slots.length mustEqual 0 //empty + items.inventory.length mustEqual 6 case _ => ko } diff --git a/common/src/test/scala/objects/LoadoutTest.scala b/common/src/test/scala/objects/LoadoutTest.scala index 208f5f6d..1f32386e 100644 --- a/common/src/test/scala/objects/LoadoutTest.scala +++ b/common/src/test/scala/objects/LoadoutTest.scala @@ -39,12 +39,12 @@ class LoadoutTest extends Specification { val player = CreatePlayer() val obj = Loadout.Create(player, "test").asInstanceOf[InfantryLoadout] - obj.Label mustEqual "test" - obj.ExoSuit mustEqual obj.ExoSuit - obj.Subtype mustEqual 0 + obj.label mustEqual "test" + obj.exosuit mustEqual ExoSuitType.Standard + obj.subtype mustEqual 0 - obj.VisibleSlots.length mustEqual 3 - val holsters = obj.VisibleSlots.sortBy(_.index) + obj.visible_slots.length mustEqual 3 + val holsters = obj.visible_slots.sortBy(_.index) holsters.head.index mustEqual 0 holsters.head.item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual beamer holsters(1).index mustEqual 2 @@ -52,8 +52,8 @@ class LoadoutTest extends Specification { holsters(2).index mustEqual 4 holsters(2).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual forceblade - obj.Inventory.length mustEqual 5 - val inventory = obj.Inventory.sortBy(_.index) + 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 @@ -66,6 +66,30 @@ class LoadoutTest extends Specification { inventory(4).item.asInstanceOf[Loadout.ShorthandSimpleItem].definition mustEqual remote_electronics_kit } + "create a loadout that contains a vehicle's inventory" in { + val vehicle = Vehicle(mediumtransport) + vehicle.Inventory += 30 -> AmmoBox(bullet_9mm) + vehicle.Inventory += 33 -> AmmoBox(bullet_9mm_AP) + val obj = Loadout.Create(vehicle, "test").asInstanceOf[VehicleLoadout] + + obj.label mustEqual "test" + obj.vehicle_definition mustEqual mediumtransport + + obj.visible_slots.length mustEqual 2 + val holsters = obj.visible_slots.sortBy(_.index) + holsters.head.index mustEqual 5 + holsters.head.item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual mediumtransport_weapon_systemA + holsters(1).index mustEqual 6 + holsters(1).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual mediumtransport_weapon_systemB + + obj.inventory.length mustEqual 2 + val inventory = obj.inventory.sortBy(_.index) + inventory.head.index mustEqual 30 + inventory.head.item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm + inventory(1).index mustEqual 33 + inventory(1).item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm_AP + } + "distinguish MAX subtype information" in { val player = CreatePlayer() val slot = player.Slot(0) @@ -83,9 +107,9 @@ class LoadoutTest extends Specification { slot.Equipment = Tool(trhev_burster) val ldout4 = Loadout.Create(player, "burster").asInstanceOf[InfantryLoadout] - ldout1.Subtype mustEqual 0 - ldout2.Subtype mustEqual 1 - ldout3.Subtype mustEqual 2 - ldout4.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/ServerObjectBuilderTest.scala b/common/src/test/scala/objects/ServerObjectBuilderTest.scala index 49678d5f..1f5b6dcf 100644 --- a/common/src/test/scala/objects/ServerObjectBuilderTest.scala +++ b/common/src/test/scala/objects/ServerObjectBuilderTest.scala @@ -208,6 +208,25 @@ class SpawnTubeObjectBuilderTest extends ActorTest { } } +class RepairRearmSiloObjectBuilderTest extends ActorTest { + import net.psforever.objects.GlobalDefinitions.repair_silo + import net.psforever.objects.serverobject.terminals.RepairRearmSilo + "LockerObjectBuilder" should { + "build" in { + val hub = ServerObjectBuilderTest.NumberPoolHub + val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, + RepairRearmSilo.Constructor(repair_silo)), hub), "silo") + actor ! "!" + + val reply = receiveOne(Duration.create(1000, "ms")) + assert(reply.isInstanceOf[RepairRearmSilo]) + assert(reply.asInstanceOf[RepairRearmSilo].HasGUID) + assert(reply.asInstanceOf[RepairRearmSilo].GUID == PlanetSideGUID(1)) + 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/VehicleTest.scala b/common/src/test/scala/objects/VehicleTest.scala index 0124917d..e0495c4a 100644 --- a/common/src/test/scala/objects/VehicleTest.scala +++ b/common/src/test/scala/objects/VehicleTest.scala @@ -277,6 +277,38 @@ class VehicleTest extends Specification { filteredMap(0).UtilType mustEqual UtilityType.order_terminalb filteredMap(2).UtilType mustEqual UtilityType.order_terminalb } + + "access its mounted weapons by Slot" in { + val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) + harasser_vehicle.Weapons(2).Equipment.get.GUID = PlanetSideGUID(10) + + harasser_vehicle.Slot(2).Equipment.get.GUID mustEqual PlanetSideGUID(10) + } + + "access its trunk by Slot" in { + val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) + val ammobox = AmmoBox(GlobalDefinitions.armor_canister) + ammobox.GUID = PlanetSideGUID(10) + harasser_vehicle.Inventory += 30 -> ammobox + + harasser_vehicle.Slot(30).Equipment.get.GUID mustEqual PlanetSideGUID(10) + } + + "find its mounted weapons by GUID" in { + val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) + harasser_vehicle.Weapons(2).Equipment.get.GUID = PlanetSideGUID(10) + + harasser_vehicle.Find(PlanetSideGUID(10)) mustEqual Some(2) + } + + "find items in its trunk by GUID" in { + val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) + val ammobox = AmmoBox(GlobalDefinitions.armor_canister) + ammobox.GUID = PlanetSideGUID(10) + harasser_vehicle.Inventory += 30 -> ammobox + + harasser_vehicle.Find(PlanetSideGUID(10)) mustEqual Some(30) + } } } diff --git a/common/src/test/scala/objects/terminal/OrderTerminalTest.scala b/common/src/test/scala/objects/terminal/OrderTerminalTest.scala index 024c8794..76e30ba1 100644 --- a/common/src/test/scala/objects/terminal/OrderTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/OrderTerminalTest.scala @@ -72,12 +72,54 @@ class OrderTerminalTest extends Specification { terminal.Request(player, msg) mustEqual Terminal.NoDeal() } - //TODO loudout tests - "player can not buy equipment from the wrong page ('9mmbullet_AP', page 1)" in { val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "9mmbullet_AP", 0, PlanetSideGUID(0)) terminal.Request(player, msg) mustEqual Terminal.NoDeal() } + + "player can retrieve an infantry loadout" in { + val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(avatar) + player2.ExoSuit = ExoSuitType.Agile + player2.Slot(0).Equipment = Tool(GlobalDefinitions.beamer) + player2.Slot(6).Equipment = Tool(GlobalDefinitions.beamer) + avatar.SaveLoadout(player2, "test", 0) + + val msg = terminal.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 4, "", 0, PlanetSideGUID(0))) + msg.isInstanceOf[Terminal.InfantryLoadout] mustEqual true + val loadout = msg.asInstanceOf[Terminal.InfantryLoadout] + loadout.exosuit mustEqual ExoSuitType.Agile + loadout.subtype mustEqual 0 + loadout.holsters.size mustEqual 1 + loadout.holsters.head.obj.Definition mustEqual GlobalDefinitions.beamer + loadout.holsters.head.start mustEqual 0 + loadout.inventory.head.obj.Definition mustEqual GlobalDefinitions.beamer + loadout.inventory.head.start mustEqual 6 + } + + "player can not retrieve an infantry loadout from the wrong page" in { + val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(avatar) + player2.ExoSuit = ExoSuitType.Agile + player2.Slot(0).Equipment = Tool(GlobalDefinitions.beamer) + player2.Slot(6).Equipment = Tool(GlobalDefinitions.beamer) + avatar.SaveLoadout(player2, "test", 0) + + val msg = terminal.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 3, "", 0, PlanetSideGUID(0))) //page 3 + msg.isInstanceOf[Terminal.NoDeal] mustEqual true + } + + "player can not retrieve an infantry loadout from the wrong line" in { + val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(avatar) + player2.ExoSuit = ExoSuitType.Agile + player2.Slot(0).Equipment = Tool(GlobalDefinitions.beamer) + player2.Slot(6).Equipment = Tool(GlobalDefinitions.beamer) + avatar.SaveLoadout(player2, "test", 0) + + val msg = terminal.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 4, "", 1, PlanetSideGUID(0))) + msg.isInstanceOf[Terminal.NoDeal] mustEqual true + } } } diff --git a/common/src/test/scala/objects/terminal/ProximityTerminalControlTest.scala b/common/src/test/scala/objects/terminal/ProximityTerminalControlTest.scala index f817be6a..a28235fe 100644 --- a/common/src/test/scala/objects/terminal/ProximityTerminalControlTest.scala +++ b/common/src/test/scala/objects/terminal/ProximityTerminalControlTest.scala @@ -25,8 +25,7 @@ class ProximityTerminalControl2Test extends ActorTest() { val (_, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.medical_terminal, PlanetSideEmpire.TR) terminal.Actor !"hello" - val reply = receiveOne(Duration.create(500, "ms")) - assert(reply.isInstanceOf[Terminal.NoDeal]) + expectNoMsg(Duration.create(500, "ms")) } } diff --git a/common/src/test/scala/objects/terminal/ProximityTest.scala b/common/src/test/scala/objects/terminal/ProximityTest.scala new file mode 100644 index 00000000..7b799d1d --- /dev/null +++ b/common/src/test/scala/objects/terminal/ProximityTest.scala @@ -0,0 +1,170 @@ +// Copyright (c) 2017 PSForever +package objects.terminal + +import akka.actor.Props +import net.psforever.objects.serverobject.CommonMessages +import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage +import net.psforever.objects.serverobject.terminals.{ProximityTerminal, ProximityTerminalControl, ProximityUnit, Terminal} +import net.psforever.objects.{Avatar, GlobalDefinitions, Player} +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.types.{CharacterGender, PlanetSideEmpire} +import objects.ActorTest +import org.specs2.mutable.Specification + +import scala.concurrent.duration._ + +class ProximityTest extends Specification { + "ProximityUnit" should { + "construct (with a Terminal object)" in { + val obj = new ProximityTest.SampleTerminal() + obj.NumberUsers mustEqual 0 + } + + "keep track of users (add)" in { + val obj = new ProximityTest.SampleTerminal() + obj.NumberUsers mustEqual 0 + obj.AddUser(PlanetSideGUID(10)) mustEqual obj.NumberUsers + obj.NumberUsers mustEqual 1 + obj.AddUser(PlanetSideGUID(20)) mustEqual obj.NumberUsers + obj.NumberUsers mustEqual 2 + } + + "keep track of users (remove)" in { + val obj = new ProximityTest.SampleTerminal() + obj.AddUser(PlanetSideGUID(10)) + obj.AddUser(PlanetSideGUID(20)) + obj.NumberUsers mustEqual 2 + obj.RemoveUser(PlanetSideGUID(10)) mustEqual obj.NumberUsers + obj.NumberUsers mustEqual 1 + obj.RemoveUser(PlanetSideGUID(20)) mustEqual obj.NumberUsers + obj.NumberUsers mustEqual 0 + } + + "can not add a user twice" in { + val obj = new ProximityTest.SampleTerminal() + obj.AddUser(PlanetSideGUID(10)) + obj.NumberUsers mustEqual 1 + obj.AddUser(PlanetSideGUID(10)) + obj.NumberUsers mustEqual 1 + } + + "can not remove a user that was not added" in { + val obj = new ProximityTest.SampleTerminal() + obj.AddUser(PlanetSideGUID(10)) + obj.NumberUsers mustEqual 1 + obj.RemoveUser(PlanetSideGUID(20)) + obj.NumberUsers mustEqual 1 + } + } + + "ProximityTerminal" should { + "construct" in { + ProximityTerminal(GlobalDefinitions.medical_terminal) + ok + } + } +} + +class ProximityTerminalControl1bTest extends ActorTest { + "ProximityTerminalControl" should { + "send out a start message" in { + val obj = ProximityTerminal(GlobalDefinitions.medical_terminal) + obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl") + val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + player.GUID = PlanetSideGUID(10) + + assert(obj.NumberUsers == 0) + obj.Actor ! CommonMessages.Use(player) + val msg = receiveOne(200 milliseconds) + assert(obj.NumberUsers == 1) + assert(msg.isInstanceOf[TerminalMessage]) + val msgout = msg.asInstanceOf[TerminalMessage] + assert(msgout.player == player) + assert(msgout.msg == null) + assert(msgout.response.isInstanceOf[Terminal.StartProximityEffect]) + } + } +} + +class ProximityTerminalControl2bTest extends ActorTest { + "ProximityTerminalControl" should { + "will not send out one start message unless first user" in { + val obj = ProximityTerminal(GlobalDefinitions.medical_terminal) + obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl") + val player1 = Player(Avatar("TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + player1.GUID = PlanetSideGUID(10) + val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + player2.GUID = PlanetSideGUID(11) + assert(obj.NumberUsers == 0) + + obj.Actor ! CommonMessages.Use(player1) + val msg = receiveOne(200 milliseconds) + assert(obj.NumberUsers == 1) + assert(msg.isInstanceOf[TerminalMessage]) + assert(msg.asInstanceOf[TerminalMessage].response.isInstanceOf[Terminal.StartProximityEffect]) + obj.Actor ! CommonMessages.Use(player2) + expectNoMsg(500 milliseconds) + assert(obj.NumberUsers == 2) + } + } +} + +class ProximityTerminalControl3bTest extends ActorTest { + "ProximityTerminalControl" should { + "send out a stop message" in { + val obj = ProximityTerminal(GlobalDefinitions.medical_terminal) + obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl") + val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + player.GUID = PlanetSideGUID(10) + + assert(obj.NumberUsers == 0) + obj.Actor ! CommonMessages.Use(player) + receiveOne(200 milliseconds) + assert(obj.NumberUsers == 1) + obj.Actor ! CommonMessages.Unuse(player) + val msg = receiveOne(200 milliseconds) + assert(obj.NumberUsers == 0) + assert(msg.isInstanceOf[TerminalMessage]) + val msgout = msg.asInstanceOf[TerminalMessage] + assert(msgout.player == player) + assert(msgout.msg == null) + assert(msgout.response.isInstanceOf[Terminal.StopProximityEffect]) + } + } +} + +class ProximityTerminalControl4bTest extends ActorTest { + "ProximityTerminalControl" should { + "will not send out one stop message until last user" in { + val obj = ProximityTerminal(GlobalDefinitions.medical_terminal) + obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl") + val player1 = Player(Avatar("TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + player1.GUID = PlanetSideGUID(10) + val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + player2.GUID = PlanetSideGUID(11) + assert(obj.NumberUsers == 0) + + obj.Actor ! CommonMessages.Use(player1) + receiveOne(200 milliseconds) //StartProximityEffect + assert(obj.NumberUsers == 1) + obj.Actor ! CommonMessages.Use(player2) + expectNoMsg(500 milliseconds) + assert(obj.NumberUsers == 2) + obj.Actor ! CommonMessages.Unuse(player1) + expectNoMsg(500 milliseconds) + assert(obj.NumberUsers == 1) + obj.Actor ! CommonMessages.Unuse(player2) + val msg = receiveOne(200 milliseconds) + assert(obj.NumberUsers == 0) + assert(msg.isInstanceOf[TerminalMessage]) + val msgout = msg.asInstanceOf[TerminalMessage] + assert(msgout.player == player2) + assert(msgout.msg == null) + assert(msgout.response.isInstanceOf[Terminal.StopProximityEffect]) + } + } +} + +object ProximityTest { + class SampleTerminal extends Terminal(GlobalDefinitions.dropship_vehicle_terminal) with ProximityUnit +} diff --git a/common/src/test/scala/objects/terminal/RepairRearmSiloTest.scala b/common/src/test/scala/objects/terminal/RepairRearmSiloTest.scala new file mode 100644 index 00000000..eeb6fa33 --- /dev/null +++ b/common/src/test/scala/objects/terminal/RepairRearmSiloTest.scala @@ -0,0 +1,91 @@ +// Copyright (c) 2017 PSForever +package objects.terminal + +import akka.actor.ActorRef +import net.psforever.objects.serverobject.structures.{Building, StructureType} +import net.psforever.objects._ +import net.psforever.objects.serverobject.terminals.{RepairRearmSilo, Terminal} +import net.psforever.objects.zones.Zone +import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} +import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType} +import org.specs2.mutable.Specification + +class RepairRearmSiloTest extends Specification { + "RepairRearmSilo" should { + val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + val silo = RepairRearmSilo(GlobalDefinitions.repair_silo) + silo.Owner = new Building(0, Zone.Nowhere, StructureType.Building) + silo.Owner.Faction = PlanetSideEmpire.TR + + "define" in { + GlobalDefinitions.repair_silo.ObjectId mustEqual 729 + } + + "construct" in { + val obj = RepairRearmSilo(GlobalDefinitions.repair_silo) + obj.Actor mustEqual ActorRef.noSender + } + + "player can buy a box of ammunition ('bullet_35mm')" in { + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 3, "35mmbullet", 0, PlanetSideGUID(0)) + val reply = silo.Request(player, msg) + reply.isInstanceOf[Terminal.BuyEquipment] mustEqual true + val reply2 = reply.asInstanceOf[Terminal.BuyEquipment] + reply2.item.isInstanceOf[AmmoBox] mustEqual true + reply2.item.asInstanceOf[AmmoBox].Definition mustEqual GlobalDefinitions.bullet_35mm + reply2.item.asInstanceOf[AmmoBox].Capacity mustEqual 100 + } + + "player can not buy fake equipment ('sabot')" in { + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 3, "sabot", 0, PlanetSideGUID(0)) + + silo.Request(player, msg) mustEqual Terminal.NoDeal() + } + + "player can not buy equipment from the wrong page ('35mmbullet', page 1)" in { + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "35mmbullet", 0, PlanetSideGUID(0)) + + silo.Request(player, msg) mustEqual Terminal.NoDeal() + } + + "player can retrieve a vehicle loadout" in { + val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(avatar) + val vehicle = Vehicle(GlobalDefinitions.fury) + vehicle.Slot(30).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm) + avatar.SaveLoadout(vehicle, "test", 10) + + val msg = silo.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 4, "", 0, PlanetSideGUID(0))) + msg.isInstanceOf[Terminal.VehicleLoadout] mustEqual true + val loadout = msg.asInstanceOf[Terminal.VehicleLoadout] + loadout.vehicle_definition mustEqual GlobalDefinitions.fury + loadout.weapons.size mustEqual 1 + loadout.weapons.head.obj.Definition mustEqual GlobalDefinitions.fury_weapon_systema + loadout.weapons.head.start mustEqual 1 + loadout.inventory.head.obj.Definition mustEqual GlobalDefinitions.bullet_9mm + loadout.inventory.head.start mustEqual 30 + } + + "player can not retrieve a vehicle loadout from the wrong line" in { + val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(avatar) + val vehicle = Vehicle(GlobalDefinitions.fury) + vehicle.Slot(30).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm) + avatar.SaveLoadout(vehicle, "test", 10) + + val msg = silo.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 3, "", 0, PlanetSideGUID(0))) //page 3 + msg.isInstanceOf[Terminal.NoDeal] mustEqual true + } + + "player can not retrieve a vehicle loadout from the wrong line" in { + val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(avatar) + val vehicle = Vehicle(GlobalDefinitions.fury) + vehicle.Slot(30).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm) + avatar.SaveLoadout(vehicle, "test", 10) + + val msg = silo.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 4, "", 1, PlanetSideGUID(0))) //line 11 + msg.isInstanceOf[Terminal.NoDeal] mustEqual true + } + } +} diff --git a/common/src/test/scala/objects/terminal/TerminalControlTest.scala b/common/src/test/scala/objects/terminal/TerminalControlTest.scala index 428d8d87..e8432d3d 100644 --- a/common/src/test/scala/objects/terminal/TerminalControlTest.scala +++ b/common/src/test/scala/objects/terminal/TerminalControlTest.scala @@ -26,8 +26,7 @@ class TerminalControl2Test extends ActorTest() { val (_, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.cert_terminal, PlanetSideEmpire.TR) terminal.Actor !"hello" - val reply = receiveOne(Duration.create(500, "ms")) - assert(reply.isInstanceOf[Terminal.NoDeal]) + expectNoMsg(Duration.create(500, "ms")) } } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 9b05f120..ca2fd7ae 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -468,6 +468,11 @@ class WorldSessionActor extends Actor with MDCContextAware { ) } + case VehicleResponse.InventoryState2(obj_guid, parent_guid, value) => + if(tplayer_guid != guid) { + sendResponse(InventoryStateMessage(obj_guid, 0, parent_guid, value)) + } + case VehicleResponse.KickPassenger(unk1, unk2, vehicle_guid) => sendResponse(DismountVehicleMsg(guid, unk1, unk2)) if(tplayer_guid == guid) { @@ -819,7 +824,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) => //TODO optimizations against replacing Equipment with the exact same Equipment and potentially for recycling existing Equipment log.info(s"$tplayer wants to change equipment loadout to their option #${msg.unk1 + 1}") - sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Loadout, true)) + sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Loadout, true)) val dropPred = DropPredicate(tplayer) val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred) val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred) @@ -838,7 +843,7 @@ class WorldSessionActor extends Actor with MDCContextAware { taskResolver ! GUIDTask.UnregisterEquipment(elem.obj)(continent.GUID) }) //report change - sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, 0)) + sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, subtype)) avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype)) sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 4, tplayer.Armor)) avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, tplayer.Armor)) @@ -882,44 +887,37 @@ class WorldSessionActor extends Actor with MDCContextAware { val objDef = obj.Definition avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentOnGround(tplayer.GUID, pos, orient, objDef.ObjectId, obj.GUID, objDef.Packet.ConstructorData(obj).get)) }) - sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Loadout, true)) case Terminal.VehicleLoadout(definition, weapons, inventory) => log.info(s"$tplayer wants to change their vehicle equipment loadout to their option #${msg.unk1 + 1}") - log.info(s"Vehicle: $definition") - log.info(s"Weapons (${weapons.size}): $weapons") - log.info(s"Inventory (${inventory.size}): $inventory") - LocalVehicle match { + FindLocalVehicle match { case Some(vehicle) => sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Loadout, true)) val (_, afterInventory) = inventory.partition( DropPredicate(tplayer) ) //dropped items are lost - //common action - remove old inventory + //remove old inventory val deleteEquipment : (Int,Equipment)=>Unit = DeleteEquipmentFromVehicle(vehicle) - vehicle.Inventory.Clear().foreach({ case InventoryItem(obj, index) => - deleteEquipment(index, obj) - taskResolver ! GUIDTask.UnregisterEquipment(obj)(continent.GUID) - }) + vehicle.Inventory.Clear().foreach({ case InventoryItem(obj, index) => deleteEquipment(index, obj) }) val stowEquipment : (Int,Equipment)=>TaskResolver.GiveTask = StowNewEquipmentInVehicle(vehicle) (if(vehicle.Definition == definition) { - //vehicles are the same type; transfer over weapons - vehicle.Weapons - .filter({ case (_, slot) => slot.Equipment.nonEmpty }) - .foreach({ case (_, slot) => - val equipment = slot.Equipment.get - slot.Equipment = None - val equipment_guid = equipment.GUID - sendResponse(ObjectDeleteMessage(equipment_guid, 0)) - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, equipment_guid)) - taskResolver ! GUIDTask.UnregisterEquipment(equipment)(continent.GUID) - }) + //vehicles are the same type; transfer over weapon ammo + //TODO ammo switching? no vehicle weapon does that currently but ... + //TODO want to completely swap weapons, but holster icon vanishes temporarily after swap + //TODO BFR arms must be swapped properly + val channel = s"${vehicle.Actor}" weapons.foreach({ case InventoryItem(obj, index) => - //create weapons and share with everyone - taskResolver ! PutNewWeaponInVehicleSlot(vehicle, obj.asInstanceOf[Tool], index) + val savedWeapon = obj.asInstanceOf[Tool] + val existingWeapon = vehicle.Weapons(index).Equipment.get.asInstanceOf[Tool] + (0 until existingWeapon.MaxAmmoSlot).foreach({ index => + val existingBox = existingWeapon.AmmoSlots(index).Box + existingBox.Capacity = savedWeapon.AmmoSlots(index).Box.Capacity + //use VehicleAction.InventoryState2; VehicleAction.InventoryState temporarily glitches ammo count in ui + vehicleService ! VehicleServiceMessage(channel, VehicleAction.InventoryState2(PlanetSideGUID(0), existingBox.GUID, existingWeapon.GUID, existingBox.Capacity)) + }) }) afterInventory } else { - //do not transfer over weapons + //do not transfer over weapon ammo if(vehicle.Definition.TrunkSize == definition.TrunkSize && vehicle.Definition.TrunkOffset == definition.TrunkOffset) { afterInventory } @@ -934,6 +932,7 @@ class WorldSessionActor extends Actor with MDCContextAware { }) case None => log.error(s"can not apply the loadout - can not find a vehicle") + sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Loadout, false)) } case Terminal.LearnCertification(cert, cost) => @@ -2123,7 +2122,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case None => None }) - .orElse(LocalVehicle match { + .orElse(FindLocalVehicle match { case Some(parent) => findFunc(parent) case None => @@ -2735,46 +2734,6 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - def PutNewWeaponInVehicleSlot(target : Vehicle, obj : Tool, index : Int) : TaskResolver.GiveTask = { - TaskResolver.GiveTask( - new Task() { - private val localTarget = target - private val localIndex = index - private val localObject = obj - private val localAnnounce = self - private val localAvatarService = avatarService - private val localVehicleService = vehicleService - - override def isComplete : Task.Resolution.Value = { - if(localTarget.Slot(localIndex).Equipment.contains(localObject)) { - Task.Resolution.Success - } - else { - Task.Resolution.Incomplete - } - } - - def Execute(resolver : ActorRef) : Unit = { - localTarget.Slot(localIndex).Equipment = localObject - resolver ! scala.util.Success(this) - } - - override def onSuccess() : Unit = { - val definition = localObject.Definition - if(localTarget.VisibleSlots.contains(localIndex)) { - localAvatarService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(localTarget.GUID, localTarget.GUID, localIndex, localObject)) - } - val channel = s"${localTarget.Actor}" - (0 until localObject.MaxAmmoSlot).foreach({ index => - val box = localObject.AmmoSlots(index).Box - val boxDef = box.Definition - val boxdata = boxDef.Packet.DetailedConstructorData(box).get - localVehicleService ! VehicleServiceMessage(channel, VehicleAction.InventoryState(PlanetSideGUID(0), box, localObject.GUID, index, boxdata)) - }) - } - }, List(GUIDTask.RegisterTool(obj)(continent.GUID))) - } - /** * Construct tasking that coordinates the following:
* 1) Remove a new piece of `Equipment` from where it is currently stored.
@@ -3418,6 +3377,25 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + /** + * Get the current `Vehicle` object that the player is riding/driving. + * The vehicle must be found solely through use of `player.VehicleSeated`. + * @return the vehicle + */ + def FindLocalVehicle : Option[Vehicle] = { + player.VehicleSeated match { + case Some(vehicle_guid) => + continent.GUID(vehicle_guid) match { + case Some(obj : Vehicle) => + Some(obj) + case _ => + None + } + case None => + None + } + } + /** * Given an object that contains an item (`Equipment`) in its `Inventory` at a certain location, * remove it permanently. @@ -3428,7 +3406,8 @@ class WorldSessionActor extends Actor with MDCContextAware { */ private def DeleteEquipment(obj : PlanetSideGameObject with Container)(start : Int, item : Equipment) : Unit = { val item_guid = item.GUID - obj.Inventory -= start + obj.Slot(start).Equipment = None + //obj.Inventory -= start taskResolver ! GUIDTask.UnregisterEquipment(item)(continent.GUID) sendResponse(ObjectDeleteMessage(item_guid, 0)) } @@ -3762,20 +3741,6 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - def LocalVehicle : Option[Vehicle] = { - player.VehicleSeated match { - case Some(vehicle_guid) => - continent.GUID(vehicle_guid) match { - case Some(obj : Vehicle) => - Some(obj) - case _ => - None - } - case None => - None - } - } - /** * Perform specific operations depending on the target of deployment. * @param obj the object that has deployed diff --git a/pslogin/src/main/scala/services/vehicle/VehicleAction.scala b/pslogin/src/main/scala/services/vehicle/VehicleAction.scala index 66afa834..06d3c768 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleAction.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleAction.scala @@ -16,6 +16,7 @@ object VehicleAction { final case class DeployRequest(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, state : DriveState.Value, unk1 : Int, unk2 : Boolean, pos : Vector3) extends Action final case class DismountVehicle(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean) extends Action final case class InventoryState(player_guid : PlanetSideGUID, obj : PlanetSideGameObject, parent_guid : PlanetSideGUID, start : Int, con_data : ConstructorData) extends Action + final case class InventoryState2(player_guid : PlanetSideGUID, obj_guid : PlanetSideGUID, parent_guid : PlanetSideGUID, value : Int) extends Action final case class KickPassenger(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Action final case class LoadVehicle(player_guid : PlanetSideGUID, vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Action final case class MountVehicle(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, seat : Int) extends Action diff --git a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala index 5c2fb5b2..74280a63 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala @@ -17,6 +17,7 @@ object VehicleResponse { final case class DetachFromRails(vehicle_guid : PlanetSideGUID, rails_guid : PlanetSideGUID, rails_pos : Vector3, rails_rot : Float) extends Response final case class DismountVehicle(unk1 : Int, unk2 : Boolean) extends Response final case class InventoryState(obj : PlanetSideGameObject, parent_guid : PlanetSideGUID, start : Int, con_data : ConstructorData) extends Response + final case class InventoryState2(obj_guid : PlanetSideGUID, parent_guid : PlanetSideGUID, value : Int) extends Response final case class KickPassenger(unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Response final case class LoadVehicle(vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Response final case class MountVehicle(object_guid : PlanetSideGUID, seat : Int) extends Response diff --git a/pslogin/src/main/scala/services/vehicle/VehicleService.scala b/pslogin/src/main/scala/services/vehicle/VehicleService.scala index e820db05..2061fdc0 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleService.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleService.scala @@ -60,6 +60,10 @@ class VehicleService extends Actor { VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.InventoryState(obj, parent_guid, start, con_data)) ) + case VehicleAction.InventoryState2(player_guid, obj_guid, parent_guid, value) => + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.InventoryState2(obj_guid, parent_guid, value)) + ) case VehicleAction.KickPassenger(player_guid, unk1, unk2, vehicle_guid) => VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickPassenger(unk1, unk2, vehicle_guid)) diff --git a/pslogin/src/test/scala/VehicleServiceTest.scala b/pslogin/src/test/scala/VehicleServiceTest.scala index d8cd0d4f..d1467ab8 100644 --- a/pslogin/src/test/scala/VehicleServiceTest.scala +++ b/pslogin/src/test/scala/VehicleServiceTest.scala @@ -136,6 +136,22 @@ class InventoryStateTest extends ActorTest { } } +class InventoryState2Test extends ActorTest { + ServiceManager.boot(system) + val tool = Tool(GlobalDefinitions.beamer) + tool.AmmoSlots.head.Box.GUID = PlanetSideGUID(13) + val cdata = tool.Definition.Packet.ConstructorData(tool).get + + "VehicleService" should { + "pass InventoryState2" in { + val service = system.actorOf(Props[VehicleService], "v-service") + service ! Service.Join("test") + service ! VehicleServiceMessage("test", VehicleAction.InventoryState2(PlanetSideGUID(10), PlanetSideGUID(11), PlanetSideGUID(12), 13)) + expectMsg(VehicleServiceResponse("/test/Vehicle", PlanetSideGUID(10), VehicleResponse.InventoryState2(PlanetSideGUID(11), PlanetSideGUID(12), 13))) + } + } +} + class KickPassengerTest extends ActorTest { ServiceManager.boot(system)