diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
index d22bea55..edc10560 100644
--- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
+++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
@@ -12,7 +12,7 @@ import net.psforever.objects.serverobject.locks.IFFLockDefinition
import net.psforever.objects.serverobject.mblocker.LockerDefinition
import net.psforever.objects.serverobject.pad.VehicleSpawnPadDefinition
import net.psforever.objects.serverobject.terminals._
-import net.psforever.objects.vehicles.SeatArmorRestriction
+import net.psforever.objects.vehicles.{SeatArmorRestriction, UtilityType}
import net.psforever.types.PlanetSideEmpire
object GlobalDefinitions {
@@ -486,6 +486,10 @@ object GlobalDefinitions {
*/
val order_terminal = new OrderTerminalDefinition
+ val order_terminala = new OrderTerminalABDefinition(613)
+
+ val order_terminalb = new OrderTerminalABDefinition(614)
+
val cert_terminal = new CertTerminalDefinition
val implant_terminal_mech = new ImplantTerminalMechDefinition
@@ -2343,6 +2347,8 @@ object GlobalDefinitions {
ams.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
ams.MountPoints += 1 -> 0
ams.MountPoints += 2 -> 0
+ ams.Utilities += 3 -> UtilityType.order_terminala
+ ams.Utilities += 4 -> UtilityType.order_terminalb
ams.Packet = utilityConverter
val variantConverter = new VariantVehicleConverter
diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala
index eb2bd636..22f87d59 100644
--- a/common/src/main/scala/net/psforever/objects/Vehicle.scala
+++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala
@@ -13,7 +13,6 @@ import net.psforever.packet.game.objectcreate.DriveState
import net.psforever.types.PlanetSideEmpire
import scala.annotation.tailrec
-import scala.collection.mutable
/**
* The server-side support object that represents a vehicle.
@@ -49,7 +48,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
private val groupPermissions : Array[VehicleLockState.Value] = Array(VehicleLockState.Locked, VehicleLockState.Empire, VehicleLockState.Empire, VehicleLockState.Locked)
private var seats : Map[Int, Seat] = Map.empty
private var weapons : Map[Int, EquipmentSlot] = Map.empty
- private val utilities : mutable.ArrayBuffer[Utility] = mutable.ArrayBuffer()
+ private var utilities : Map[Int, Utility] = Map()
private val trunk : GridInventory = GridInventory()
//init
@@ -325,16 +324,21 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
}
}
- def Utilities : mutable.ArrayBuffer[Utility] = utilities
+ def Utilities : Map[Int, Utility] = utilities
/**
* Get a referenece ot a certain `Utility` attached to this `Vehicle`.
* @param utilNumber the attachment number of the `Utility`
* @return the `Utility` or `None` (if invalid)
*/
- def Utility(utilNumber : Int) : Option[Utility] = {
+ def Utility(utilNumber : Int) : Option[PlanetSideServerObject] = {
if(utilNumber >= 0 && utilNumber < this.utilities.size) {
- Some(this.utilities(utilNumber))
+ this.utilities.get(utilNumber) match {
+ case Some(util) =>
+ Some(util())
+ case None =>
+ None
+ }
}
else {
None
@@ -506,10 +510,8 @@ object Vehicle {
}).toMap
//create seats
vehicle.seats = vdef.Seats.map({ case(num, definition) => num -> Seat(definition)}).toMap
- for(i <- vdef.Utilities) {
- //TODO utilies must be loaded and wired on a case-by-case basis?
- vehicle.Utilities += Utility.Select(i, vehicle)
- }
+ //create utilities
+ vehicle.utilities = vdef.Utilities.map({ case(num, util) => num -> Utility(util, vehicle) }).toMap
//trunk
vdef.TrunkSize match {
case InventoryTile.None => ;
diff --git a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
index a84e97c6..693a1164 100644
--- a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
@@ -3,6 +3,7 @@ package net.psforever.objects.definition
import net.psforever.objects.definition.converter.VehicleConverter
import net.psforever.objects.inventory.InventoryTile
+import net.psforever.objects.vehicles.UtilityType
import scala.collection.mutable
@@ -20,7 +21,7 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
/* key - seat index (where this weapon attaches during object construction), value - the weapon on an EquipmentSlot */
private val weapons : mutable.HashMap[Int, ToolDefinition] = mutable.HashMap[Int, ToolDefinition]()
private var deployment : Boolean = false
- private val utilities : mutable.ArrayBuffer[Int] = mutable.ArrayBuffer[Int]()
+ private val utilities : mutable.HashMap[Int, UtilityType.Value] = mutable.HashMap()
private var trunkSize : InventoryTile = InventoryTile.None
private var trunkOffset : Int = 0
private var canCloak : Boolean = false
@@ -69,7 +70,7 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
Deployment
}
- def Utilities : mutable.ArrayBuffer[Int] = utilities
+ def Utilities : mutable.HashMap[Int, UtilityType.Value] = utilities
def TrunkSize : InventoryTile = trunkSize
diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/TerminalConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/TerminalConverter.scala
new file mode 100644
index 00000000..440d8329
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/definition/converter/TerminalConverter.scala
@@ -0,0 +1,11 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.definition.converter
+
+import net.psforever.objects.serverobject.terminals.Terminal
+import net.psforever.packet.game.objectcreate.CommonTerminalData
+
+import scala.util.{Success, Try}
+
+class TerminalConverter extends ObjectCreateConverter[Terminal]() {
+ override def ConstructorData(obj : Terminal) : Try[CommonTerminalData] = { Success(CommonTerminalData(obj.Faction)) }
+}
diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
index 14f8b766..6bef162c 100644
--- a/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
@@ -29,55 +29,28 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
false,
obj.Cloaked,
SpecificFormatData(obj),
- Some(InventoryData(MakeMountings(obj).sortBy(_.parentSlot)))
+ Some(InventoryData((MakeUtilities(obj) ++ MakeMountings(obj)).sortBy(_.parentSlot)))
)(SpecificFormatModifier)
)
}
-
- /**
- * na
- * @param obj the `Player` game object
- * @return a list of all tools that were in the mounted weapon slots in decoded packet form
- */
+
private def MakeMountings(obj : Vehicle) : List[InventoryItemData.InventoryItem] = {
obj.Weapons.map({
case((index, slot)) =>
val equip : Equipment = slot.Equipment.get
- InventoryItemData(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.Packet.ConstructorData(equip).get)
+ val equipDef = equip.Definition
+ InventoryItemData(equipDef.ObjectId, equip.GUID, index, equipDef.Packet.ConstructorData(equip).get)
}).toList
}
-// /**
-// * na
-// * @param obj the `Player` game object
-// * @return a list of all items that were in the inventory in decoded packet form
-// */
-// private def MakeTrunk(obj : Vehicle) : List[InternalSlot] = {
-// obj.Trunk.Items.map({
-// case(_, item) =>
-// val equip : Equipment = item.obj
-// InventoryItemData(equip.Definition.ObjectId, equip.GUID, item.start, equip.Definition.Packet.ConstructorData(equip).get)
-// }).toList
-// }
-
-// @tailrec private def recursiveMakeSeats(iter : Iterator[(Int, Seat)], list : List[InventoryItemData.InventoryItem] = Nil) : List[InventoryItemData.InventoryItem] = {
-// if(!iter.hasNext) {
-// list
-// }
-// else {
-// val (index, seat) = iter.next
-// seat.Occupant match {
-// case Some(avatar) =>
-// val definition = avatar.Definition
-// recursiveMakeSeats(
-// iter,
-// list :+ InventoryItemData(definition.ObjectId, avatar.GUID, index, definition.Packet.ConstructorData(avatar).get)
-// )
-// case None =>
-// recursiveMakeSeats(iter, list)
-// }
-// }
-// }
+ protected def MakeUtilities(obj : Vehicle) : List[InventoryItemData.InventoryItem] = {
+ obj.Utilities.map({
+ case(index, utilContainer) =>
+ val util = utilContainer()
+ val utilDef = util.Definition
+ InventoryItemData(utilDef.ObjectId, util.GUID, index, utilDef.Packet.ConstructorData(util).get)
+ }).toList
+ }
protected def SpecificFormatModifier : VehicleFormat.Value = VehicleFormat.Normal
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 7425322b..95b03452 100644
--- a/common/src/main/scala/net/psforever/objects/guid/GUIDTask.scala
+++ b/common/src/main/scala/net/psforever/objects/guid/GUIDTask.scala
@@ -1,6 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.guid
+import net.psforever.objects.vehicles.Utility
+
/**
* The basic compiled tasks for assigning (registering) and revoking (unregistering) globally unique identifiers.
*
@@ -149,8 +151,9 @@ object GUIDTask {
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.Utilities.map({case (_ : Int, util : Utility) => RegisterObjectTask(util())}).toList
val inventoryTasks = vehicle.Trunk.Items.map({ case((_ : Int, entry : InventoryItem)) => RegisterEquipment(entry.obj)})
- TaskResolver.GiveTask(RegisterObjectTask(vehicle).task, weaponTasks ++ inventoryTasks)
+ TaskResolver.GiveTask(RegisterObjectTask(vehicle).task, weaponTasks ++ utilTasks ++ inventoryTasks)
}
/**
@@ -252,8 +255,9 @@ object GUIDTask {
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.Utilities.map({case (_ : Int, util : Utility) => UnregisterObjectTask(util())}).toList
val inventoryTasks = vehicle.Trunk.Items.map({ case((_ : Int, entry : InventoryItem)) => UnregisterEquipment(entry.obj)})
- TaskResolver.GiveTask(UnregisterObjectTask(vehicle).task, weaponTasks ++ inventoryTasks)
+ TaskResolver.GiveTask(UnregisterObjectTask(vehicle).task, weaponTasks ++ utilTasks ++ inventoryTasks)
}
/**
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 5984dd56..94fef0fc 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
@@ -4,10 +4,27 @@ package net.psforever.objects.serverobject.terminals
import net.psforever.objects._
import net.psforever.objects.definition._
import net.psforever.objects.equipment.Equipment
+import net.psforever.packet.game.ItemTransactionMessage
import net.psforever.types.ExoSuitType
+import scala.annotation.switch
+
abstract class EquipmentTerminalDefinition(objId : Int) extends TerminalDefinition(objId) {
Name = "equipment_terminal"
+
+ /**
+ * Process a `TransactionType.Sell` action by the user.
+ * There is no specific tab associated with this action.
+ * It is a common button on the terminal interface window.
+ * Additionally, the equipment to be sold ia almost always in the player's `FreeHand` slot.
+ * Selling `Equipment` is always permitted.
+ * @param player the player
+ * @param msg the original packet carrying the request
+ * @return an actionable message that explains how to process the request
+ */
+ override def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
+ Terminal.SellEquipment()
+ }
}
object EquipmentTerminalDefinition {
@@ -22,7 +39,15 @@ object EquipmentTerminalDefinition {
"standard_issue_armor" -> (ExoSuitType.Standard, 0),
"lite_armor" -> (ExoSuitType.Agile, 0),
"med_armor" -> (ExoSuitType.Reinforced, 0),
- "stealth_armor" -> (ExoSuitType.Infiltration, 0),
+ "stealth_armor" -> (ExoSuitType.Infiltration, 0)
+ )
+
+ /**
+ * A `Map` of information for changing mechanized assault exo-suits.
+ * key - an identification string sent by the client
+ * value - a `Tuple` containing exo-suit specifications
+ */
+ val maxSuits : Map[String, (ExoSuitType.Value, Int)] = Map(
"trhev_antiaircraft" -> (ExoSuitType.MAX, 3),
"trhev_antipersonnel" -> (ExoSuitType.MAX, 1),
"trhev_antivehicular" -> (ExoSuitType.MAX, 2),
@@ -337,4 +362,57 @@ object EquipmentTerminalDefinition {
MakeKit(obj.kdef)
}
}
+
+ /**
+ * Process a `TransactionType.Buy` action by the user.
+ * Either attempt to purchase equipment or attempt to switch directly to a different exo-suit.
+ * @param page0Stock the `Equipment` items and `AmmoBox` items available on the first tab
+ * @param page2Stock the `Equipment` items and `AmmoBox` items available on the third tab
+ * @param exosuits the exo-suit types (and subtypes) available on the second tab
+ * @param player the player
+ * @param msg the original packet carrying the request
+ * @return an actionable message that explains how to process the request
+ */
+ def Buy(page0Stock : Map[String, ()=>Equipment],
+ page2Stock : Map[String, ()=>Equipment],
+ exosuits : Map[String, (ExoSuitType.Value, Int)])
+ (player : Player, msg : ItemTransactionMessage): Terminal.Exchange = {
+ (msg.item_page : @switch) match {
+ case 0 => //Weapon tab
+ page0Stock.get(msg.item_name) match {
+ case Some(item) =>
+ Terminal.BuyEquipment(item())
+ case None =>
+ Terminal.NoDeal()
+ }
+ case 2 => //Support tab
+ page2Stock.get(msg.item_name) match {
+ case Some(item) =>
+ Terminal.BuyEquipment(item())
+ case None =>
+ Terminal.NoDeal()
+ }
+ case 3 => //Vehicle tab
+ vehicleAmmunition.get(msg.item_name) match {
+ case Some(item) =>
+ Terminal.BuyEquipment(item())
+ case None =>
+ Terminal.NoDeal()
+ }
+ case 1 => //Armor tab
+ exosuits.get(msg.item_name) match {
+ case Some((suit, subtype)) =>
+ Terminal.BuyExosuit(suit, subtype)
+ case None =>
+ maxAmmo.get(msg.item_name) match {
+ case Some(item) =>
+ Terminal.BuyEquipment(item())
+ case None =>
+ Terminal.NoDeal()
+ }
+ }
+ case _ =>
+ Terminal.NoDeal()
+ }
+ }
}
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
new file mode 100644
index 00000000..192d87dc
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalABDefinition.scala
@@ -0,0 +1,87 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.serverobject.terminals
+
+import akka.actor.ActorContext
+import net.psforever.objects.Player
+import net.psforever.objects.inventory.InventoryItem
+import net.psforever.objects.serverobject.structures.Amenity
+import net.psforever.packet.game.ItemTransactionMessage
+import net.psforever.objects.serverobject.terminals.EquipmentTerminalDefinition._
+import net.psforever.types.ExoSuitType
+
+/**
+ * The definition for any `Terminal` that is of a type "ams_order_terminal".
+ * As the name indicates, paired on the flanks of an advanced mobile spawn vehicle.
+ *
+ * `Buy` and `Sell` `Equipment` items and `AmmoBox` items.
+ * Change `ExoSuitType` and retrieve `Loadout` entries.
+ * Changing into mechanized assault exo-suits (MAXes) is not permitted.
+ */
+class OrderTerminalABDefinition(object_id : Int) extends EquipmentTerminalDefinition(object_id) {
+ if(object_id == 613) {
+ Name = "order_terminala"
+ }
+ else if(object_id == 614) {
+ Name = "order_terminalb"
+ }
+ else {
+ throw new IllegalArgumentException("terminal must be either object id 613 or object id 614")
+ }
+
+ /**
+ * The `Equipment` available from this `Terminal` on specific pages.
+ */
+ private val buyFunc : (Player, ItemTransactionMessage)=>Terminal.Exchange =
+ EquipmentTerminalDefinition.Buy(
+ infantryAmmunition ++ infantryWeapons,
+ supportAmmunition ++ supportWeapons,
+ suits
+ )
+
+ override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = buyFunc(player, msg)
+
+ /**
+ * Process a `TransactionType.InfantryLoadout` action by the user.
+ * `Loadout` objects are blueprints composed of exo-suit specifications and simplified `Equipment`-to-slot mappings.
+ * If a valid loadout is found, its data is transformed back into actual `Equipment` for return to the user.
+ * Loadouts that would suit the player into a mechanized assault exo-suit are not permitted.
+ * @param player the player
+ * @param msg the original packet carrying the request
+ * @return an actionable message that explains how to process the request
+ */
+ override def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
+ if(msg.item_page == 4) { //Favorites tab
+ player.LoadLoadout(msg.unk1) match {
+ case Some(loadout) =>
+ 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)
+ }
+ else {
+ Terminal.NoDeal()
+ }
+ case None =>
+ Terminal.NoDeal()
+ }
+ }
+ else {
+ Terminal.NoDeal()
+ }
+ }
+}
+
+object OrderTerminalABDefinition {
+ /**
+ * Assemble some logic for a provided object.
+ * @param obj an `Amenity` object;
+ * anticipating a `Terminal` object using this same definition
+ * @param context hook to the local `Actor` system
+ */
+ def Setup(obj : Amenity, context : ActorContext) : Unit = {
+ import akka.actor.{ActorRef, Props}
+ if(obj.Actor == ActorRef.noSender) {
+ obj.Actor = context.actorOf(Props(classOf[TerminalControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}")
+ }
+ }
+}
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 973d7664..a1bfca86 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
@@ -2,15 +2,14 @@
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
-import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.InventoryItem
import net.psforever.packet.game.ItemTransactionMessage
import net.psforever.objects.serverobject.terminals.EquipmentTerminalDefinition._
-import scala.annotation.switch
-
/**
* The definition for any `Terminal` that is of a type "order_terminal".
+ * This kind of "order_terminal" is applicable to facilities.
+ *
* `Buy` and `Sell` `Equipment` items and `AmmoBox` items.
* Change `ExoSuitType` and retrieve `Loadout` entries.
*/
@@ -20,68 +19,12 @@ class OrderTerminalDefinition extends EquipmentTerminalDefinition(612) {
/**
* The `Equipment` available from this `Terminal` on specific pages.
*/
- private val page0Stock : Map[String, ()=>Equipment] = infantryAmmunition ++ infantryWeapons
- private val page2Stock : Map[String, ()=>Equipment] = supportAmmunition ++ supportWeapons
+ private val buyFunc : (Player, ItemTransactionMessage)=>Terminal.Exchange = EquipmentTerminalDefinition.Buy(
+ infantryAmmunition ++ infantryWeapons,
+ supportAmmunition ++ supportWeapons,
+ suits ++ maxSuits)
- /**
- * Process a `TransactionType.Buy` action by the user.
- * Either attempt to purchase equipment or attempt to switch directly to a different exo-suit.
- * @param player the player
- * @param msg the original packet carrying the request
- * @return an actionable message that explains how to process the request
- */
- def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
- (msg.item_page : @switch) match {
- case 0 => //Weapon tab
- page0Stock.get(msg.item_name) match {
- case Some(item) =>
- Terminal.BuyEquipment(item())
- case None =>
- Terminal.NoDeal()
- }
- case 2 => //Support tab
- page2Stock.get(msg.item_name) match {
- case Some(item) =>
- Terminal.BuyEquipment(item())
- case None =>
- Terminal.NoDeal()
- }
- case 3 => //Vehicle tab
- vehicleAmmunition.get(msg.item_name) match {
- case Some(item) =>
- Terminal.BuyEquipment(item())
- case None =>
- Terminal.NoDeal()
- }
- case 1 => //Armor tab
- suits.get(msg.item_name) match {
- case Some((suit, subtype)) =>
- Terminal.BuyExosuit(suit, subtype)
- case None =>
- maxAmmo.get(msg.item_name) match {
- case Some(item) =>
- Terminal.BuyEquipment(item())
- case None =>
- Terminal.NoDeal()
- }
- }
- case _ =>
- Terminal.NoDeal()
- }
- }
-
- /**
- * Process a `TransactionType.Sell` action by the user.
- * There is no specific `order_terminal` tab associated with this action.
- * Additionally, the equipment to be sold ia almost always in the player's `FreeHand` slot.
- * Selling `Equipment` is always permitted.
- * @param player the player
- * @param msg the original packet carrying the request
- * @return an actionable message that explains how to process the request
- */
- override def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
- Terminal.SellEquipment()
- }
+ override def Buy(player: Player, msg : ItemTransactionMessage) : Terminal.Exchange = buyFunc(player, msg)
/**
* Process a `TransactionType.InfantryLoadout` action by the user.
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala
index 02767848..be739f4c 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala
@@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
+import net.psforever.objects.definition.converter.TerminalConverter
import net.psforever.packet.game.ItemTransactionMessage
/**
@@ -10,6 +11,7 @@ import net.psforever.packet.game.ItemTransactionMessage
*/
abstract class TerminalDefinition(objectId : Int) extends net.psforever.objects.definition.ObjectDefinition(objectId) {
Name = "terminal"
+ Packet = new TerminalConverter
/**
* The unimplemented functionality for this `Terminal`'s `TransactionType.Buy` and `TransactionType.Learn` activity.
diff --git a/common/src/main/scala/net/psforever/objects/vehicles/ANTResourceUtility.scala b/common/src/main/scala/net/psforever/objects/vehicles/ANTResourceUtility.scala
deleted file mode 100644
index b7a32caa..00000000
--- a/common/src/main/scala/net/psforever/objects/vehicles/ANTResourceUtility.scala
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2017 PSForever
-package net.psforever.objects.vehicles
-
-import net.psforever.objects.Vehicle
-
-/**
- * A `Utility` designed to simulate the NTU-distributive functions of an ANT.
- * @param objectId the object id that is associated with this sort of `Utility`
- * @param vehicle the `Vehicle` to which this `Utility` is attached
- */
-class ANTResourceUtility(objectId : Int, vehicle : Vehicle) extends Utility(objectId, vehicle) {
- private var currentNTU : Int = 0
-
- def NTU : Int = currentNTU
-
- def NTU_=(ntu : Int) : Int = {
- currentNTU = ntu
- currentNTU = math.max(math.min(currentNTU, MaxNTU), 0)
- NTU
- }
-
- def MaxNTU : Int = ANTResourceUtility.MaxNTU
-}
-
-object ANTResourceUtility {
- private val MaxNTU : Int = 300 //TODO what should this value be?
-
- def apply(objectId : Int, vehicle : Vehicle) : ANTResourceUtility = {
- new ANTResourceUtility(objectId, vehicle)
- }
-}
\ No newline at end of file
diff --git a/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala b/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala
index 172d7ed1..683b4fc8 100644
--- a/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala
+++ b/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala
@@ -1,104 +1,97 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.vehicles
-import net.psforever.objects.entity.IdentifiableEntity
-import net.psforever.objects.Vehicle
-import net.psforever.packet.game.PlanetSideGUID
-import net.psforever.types.PlanetSideEmpire
-
-import scala.annotation.switch
+import akka.actor.ActorContext
+import net.psforever.objects.{GlobalDefinitions, Vehicle}
+import net.psforever.objects.serverobject.structures.Amenity
+import net.psforever.objects.serverobject.terminals.{OrderTerminalABDefinition, Terminal}
/**
- * A `Utility` represents an unknown but functional entity that is attached to a `Vehicle` and is not a weapon or a seat.
- * This is a placeholder solution until a better system is established.
- * @param objectId the object id that is associated with this sort of `Utility`
- * @param vehicle the `Vehicle` to which this `Utility` is attached
+ * An `Enumeration` of the available vehicular utilities.
+ *
+ * These values are used to connect `Amenity` objects and their extra logic encapsulated in this class
+ * with information in the `VehicleDefinition` object for that kind of vehicle.
+ * @see `Vehicle.LoadDefinition`
+ * @see `VehicleDefinition.Utilities`
*/
-class Utility(val objectId : Int, vehicle : Vehicle) extends IdentifiableEntity {
- private var active : Boolean = false
+object UtilityType extends Enumeration {
+ type Type = Value
+ val
+ order_terminala,
+ order_terminalb
+ = Value
+}
+
+/**
+ * Build a specific functional extension that is a component of a certain `Vehicle` object.
+ *
+ * A `Utility` object is a variation of an `Amenity` object that might be found in a `Building` object.
+ * The object itself is stored inside the `Utility` as if it were a container.
+ * `Amenity` objects are required because they are to be owned by the `vehicle` for purposes of faction affinity.
+ * Only specific kinds of objects count for being `Utility` contents/objects.
+ * Additional "setup" logic can be supplied that will be called when the owner vehicle's control `Actor` is created.
+ * Ostensibly, the purpose of the additional logic, when it is called,
+ * is to initialize a control `Actor` for the contained object.
+ * This `Actor` is expected by other logic.
+ * @see `Vehicle.LoadDefinition`
+ * @see `VehicleDefinition.Utilities`
+ * @param util the type of the `Amenity` object to be created
+ * @param vehicle the owner of this object
+ * @see `Amenity.Owner`
+ */
+class Utility(util : UtilityType.Value, vehicle : Vehicle) {
+ private val obj : Amenity = Utility.BuildUtilityFunc(util)
+ obj.Owner = vehicle
+ private val setupFunc : Utility.UtilLogic = Utility.SelectUtilitySetupFunc(util)
/**
- * The faction association of this `Utility` is tied directly to the connected `Vehicle`.
- * @return the faction association
+ * Access the contained object in this `Utility`.
+ * @return the contained `Amenity` object
*/
- def Faction : PlanetSideEmpire.Value = {
- vehicle.Faction
- }
+ def apply() : Amenity = obj
/**
- * An "active" `Utility` can be used by players; an "inactive" one can not be used in its current state.
- * @return whether this `Utility` is active.
+ * Run the setup code that was provided in the object constructor parameters.
+ * While it is expected to construct an `Actor`, that is not required.
+ * @param context an `ActorContext` potentially useful for the function
*/
- def ActiveState : Boolean = {
- this.active
- }
-
- /**
- * Change the "active" state of this `Utility`.
- * @param state the new active state
- * @return the current active state after being changed
- */
- def ActiveState_=(state : Boolean) : Boolean = {
- this.active = state
- state
- }
-
- /**
- * Override the string representation to provide additional information.
- * @return the string output
- */
- override def toString : String = {
- Utility.toString(this)
- }
+ def Setup(implicit context : ActorContext) : Unit = setupFunc(obj, context)
}
object Utility {
+ type UtilLogic = (Amenity, ActorContext)=>Unit
+
/**
- * An overloaded constructor.
- * @param objectId the object id the is associated with this sort of `Utility`
- * @param vehicle the `Vehicle` to which this `Utility` is attached
+ * Overloaded constructor.
+ * @param util the type of the `Amenity` object to be created
+ * @param vehicle the owner of this object
* @return a `Utility` object
*/
- def apply(objectId : Int, vehicle : Vehicle) : Utility = {
- new Utility(objectId, vehicle)
+ def apply(util : UtilityType.Value, vehicle : Vehicle) : Utility = {
+ new Utility(util, vehicle)
}
/**
- * An overloaded constructor.
- * @param objectId the object id the is associated with this sort of `Utility`
- * @param vehicle the `Vehicle` to which this `Utility` is attached
- * @return a `Utility` object
+ * Create the called-out object.
+ * @param util the type of the `Amenity` object
+ * @return the `Amenity` object
*/
- def apply(guid : PlanetSideGUID, objectId : Int, vehicle : Vehicle) : Utility = {
- val obj = new Utility(objectId, vehicle)
- obj.GUID = guid
- obj
+ private def BuildUtilityFunc(util : UtilityType.Value) : Amenity = util match {
+ case UtilityType.order_terminala =>
+ Terminal(GlobalDefinitions.order_terminala)
+ case UtilityType.order_terminalb =>
+ Terminal(GlobalDefinitions.order_terminalb)
}
/**
- * Create one of a specific type of utilities.
- * @param objectId the object id that is associated with this sort of `Utility`
- * @param vehicle the `Vehicle` to which this `Utility` is attached
- * @return a permitted `Utility` object
+ * Provide the called-out object's logic.
+ * @param util the type of the `Amenity` object
+ * @return the `Amenity` object
*/
- def Select(objectId : Int, vehicle : Vehicle) : Utility = {
- (objectId : @switch) match {
- case 60 => //this is the object id of an ANT
- ANTResourceUtility(objectId, vehicle)
-
- case 49 | 519 | 613 | 614 => //ams parts
- Utility(objectId, vehicle)
-
- case _ =>
- throw new IllegalArgumentException(s"the requested objectID #$objectId is not accepted as a valid Utility")
- }
- }
-
- /**
- * Provide a fixed string representation.
- * @return the string output
- */
- def toString(obj : Utility) : String = {
- s"{utility-${obj.objectId}}"
+ private def SelectUtilitySetupFunc(util : UtilityType.Value) : UtilLogic = util match {
+ case UtilityType.order_terminala =>
+ OrderTerminalABDefinition.Setup
+ case UtilityType.order_terminalb =>
+ OrderTerminalABDefinition.Setup
}
}
diff --git a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
index 70639d45..ced4442c 100644
--- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
+++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
@@ -3,7 +3,7 @@ package net.psforever.objects.vehicles
import akka.actor.Actor
import net.psforever.objects.Vehicle
-import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
+import net.psforever.objects.serverobject.mount.MountableBehavior
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
/**
@@ -13,10 +13,13 @@ import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffi
* The latter is applicable only when the specific vehicle is being deconstructed.
* @param vehicle the `Vehicle` object being governed
*/
-class VehicleControl(private val vehicle : Vehicle) extends Actor
+class VehicleControl(vehicle : Vehicle) extends Actor
with FactionAffinityBehavior.Check
with MountableBehavior.Mount
with MountableBehavior.Dismount {
+ //make control actors belonging to utilities when making control actor belonging to vehicle
+ vehicle.Utilities.foreach({case (_, util) => util.Setup })
+
def MountableObject = vehicle //do not add type!
def FactionObject : FactionAffinity = vehicle
@@ -27,6 +30,13 @@ class VehicleControl(private val vehicle : Vehicle) extends Actor
.orElse(mountBehavior)
.orElse(dismountBehavior)
.orElse {
+ case FactionAffinity.ConvertFactionAffinity(faction) =>
+ val originalAffinity = vehicle.Faction
+ if(originalAffinity != (vehicle.Faction = faction)) {
+ vehicle.Utilities.foreach({ case(_ : Int, util : Utility) => util().Actor forward FactionAffinity.ConfirmFactionAffinity() })
+ }
+ sender ! FactionAffinity.AssertFactionAffinity(vehicle, faction)
+
case Vehicle.PrepareForDeletion =>
context.become(Disabled)
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala
index 9ceebca1..40a95b49 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala
@@ -365,6 +365,7 @@ object ObjectClass {
final val vulture = 986
final val wasp = 997
//other
+ final val ams_order_terminal = 48
final val ams_respawn_tube = 49
final val avatar = 121
final val bfr_rearm_terminal = 142
@@ -950,6 +951,7 @@ object ObjectClass {
//vehicles?
case ObjectClass.orbital_shuttle => ConstructorData.genericCodec(OrbitalShuttleData.codec, "HART")
//other
+ case ObjectClass.ams_order_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.ams_respawn_tube => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.bfr_rearm_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.implant_terminal_interface => ConstructorData.genericCodec(CommonTerminalData.codec, "implant terminal")
diff --git a/common/src/test/scala/objects/ConverterTest.scala b/common/src/test/scala/objects/ConverterTest.scala
index b6ea3619..61263740 100644
--- a/common/src/test/scala/objects/ConverterTest.scala
+++ b/common/src/test/scala/objects/ConverterTest.scala
@@ -1,18 +1,20 @@
// Copyright (c) 2017 PSForever
package objects
+import net.psforever.objects.GlobalDefinitions.remote_electronics_kit
import net.psforever.objects.definition.converter.{ACEConverter, CharacterSelectConverter, REKConverter}
import net.psforever.objects._
import net.psforever.objects.definition._
import net.psforever.objects.equipment.CItem.{DeployedItem, Unit}
import net.psforever.objects.equipment._
import net.psforever.objects.inventory.InventoryTile
+import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3}
import org.specs2.mutable.Specification
-import scala.util.Success
+import scala.util.{Failure, Success}
class ConverterTest extends Specification {
"AmmoBox" should {
@@ -275,6 +277,26 @@ class ConverterTest extends Specification {
}
}
+ "Terminal" should {
+ "convert to packet" in {
+ val obj = Terminal(GlobalDefinitions.order_terminala)
+
+ obj.Definition.Packet.DetailedConstructorData(obj) match {
+ case Failure(err) =>
+ err.isInstanceOf[NoSuchMethodException] mustEqual true
+ case _ =>
+ ko
+ }
+
+ obj.Definition.Packet.ConstructorData(obj) match {
+ case Success(pkt) =>
+ pkt mustEqual CommonTerminalData(PlanetSideEmpire.NEUTRAL, 0)
+ case _ =>
+ ko
+ }
+ }
+ }
+
"Vehicle" should {
"convert to packet" in {
val hellfire_ammo = AmmoBoxDefinition(Ammo.hellfire_ammo.id)
diff --git a/common/src/test/scala/objects/UtilityTest.scala b/common/src/test/scala/objects/UtilityTest.scala
new file mode 100644
index 00000000..5ac6740a
--- /dev/null
+++ b/common/src/test/scala/objects/UtilityTest.scala
@@ -0,0 +1,69 @@
+// Copyright (c) 2017 PSForever
+package objects
+
+import akka.actor.{Actor, ActorRef, Props}
+import net.psforever.objects.{GlobalDefinitions, Vehicle}
+import net.psforever.objects.serverobject.terminals.Terminal
+import net.psforever.objects.vehicles._
+import net.psforever.packet.game.PlanetSideGUID
+import org.specs2.mutable._
+
+import scala.concurrent.duration.Duration
+
+class UtilityTest extends Specification {
+ "Utility" should {
+ "create an order_terminala object" in {
+ val obj = Utility(UtilityType.order_terminala, UtilityTest.vehicle)
+ obj().isInstanceOf[Terminal] mustEqual true
+ obj().asInstanceOf[Terminal].Definition.ObjectId mustEqual 613
+ obj().asInstanceOf[Terminal].Actor == ActorRef.noSender
+ }
+
+ "create an order_terminalb object" in {
+ val obj = Utility(UtilityType.order_terminalb, UtilityTest.vehicle)
+ obj().isInstanceOf[Terminal] mustEqual true
+ obj().asInstanceOf[Terminal].Definition.ObjectId mustEqual 614
+ obj().asInstanceOf[Terminal].Actor == ActorRef.noSender
+ }
+ }
+}
+
+class Utility1Test extends ActorTest() {
+ "Utility" should {
+ "wire an order_terminala Actor" in {
+ val obj = Utility(UtilityType.order_terminala, UtilityTest.vehicle)
+ obj().GUID = PlanetSideGUID(1)
+ assert(obj().Actor == ActorRef.noSender)
+
+ system.actorOf(Props(classOf[UtilityTest.SetupControl], obj), "test") ! ""
+ receiveOne(Duration.create(100, "ms")) //consume and discard
+ assert(obj().Actor != ActorRef.noSender)
+ }
+ }
+}
+
+class Utility2Test extends ActorTest() {
+ "Utility" should {
+ "wire an order_terminalb Actor" in {
+ val obj = Utility(UtilityType.order_terminalb, UtilityTest.vehicle)
+ obj().GUID = PlanetSideGUID(1)
+ assert(obj().Actor == ActorRef.noSender)
+
+ system.actorOf(Props(classOf[UtilityTest.SetupControl], obj), "test") ! ""
+ receiveOne(Duration.create(100, "ms")) //consume and discard
+ assert(obj().Actor != ActorRef.noSender)
+ }
+ }
+}
+
+object UtilityTest {
+ val vehicle = Vehicle(GlobalDefinitions.quadstealth)
+
+ class SetupControl(obj : Utility) extends Actor {
+ def receive : Receive = {
+ case _ =>
+ obj.Setup(context)
+ sender ! ""
+ }
+ }
+}
diff --git a/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala b/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala
new file mode 100644
index 00000000..e517624e
--- /dev/null
+++ b/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala
@@ -0,0 +1,77 @@
+// Copyright (c) 2017 PSForever
+package objects.terminal
+
+import akka.actor.ActorRef
+import net.psforever.objects.serverobject.structures.Building
+import net.psforever.objects.serverobject.terminals.{OrderTerminalABDefinition, Terminal}
+import net.psforever.objects.zones.Zone
+import net.psforever.objects.{GlobalDefinitions, Player}
+import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
+import net.psforever.types._
+import org.specs2.mutable.Specification
+
+class OrderTerminalABTest extends Specification {
+ "OrderTerminalAB" should {
+ "define (a)" in {
+ val a = new OrderTerminalABDefinition(613)
+ a.ObjectId mustEqual 613
+ a.Name mustEqual "order_terminala"
+ }
+
+ "define (b)" in {
+ val b = new OrderTerminalABDefinition(614)
+ b.ObjectId mustEqual 614
+ b.Name mustEqual "order_terminalb"
+ }
+
+ "define (invalid)" in {
+ var id : Int = (math.random * Int.MaxValue).toInt
+ if(id == 613) {
+ id += 2
+ }
+ else if(id == 614) {
+ id += 1
+ }
+
+ new OrderTerminalABDefinition(id) must throwA[IllegalArgumentException]
+ }
+ }
+
+ "Order_Terminal" should {
+ val terminal = Terminal(GlobalDefinitions.order_terminala)
+ terminal.Owner = new Building(0, Zone.Nowhere)
+ terminal.Owner.Faction = PlanetSideEmpire.TR
+
+ "construct" in {
+ terminal.Actor mustEqual ActorRef.noSender
+ }
+
+ "player can buy different armor ('lite_armor')" in {
+ val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
+ val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "lite_armor", 0, PlanetSideGUID(0))
+
+ terminal.Request(player, msg) mustEqual Terminal.BuyExosuit(ExoSuitType.Agile)
+ }
+
+ "player can buy max armor ('trhev_antiaircraft')" in {
+ val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
+ val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "trhev_antiaircraft", 0, PlanetSideGUID(0))
+
+ terminal.Request(player, msg) mustEqual Terminal.NoDeal()
+ }
+ //TODO loudout tests
+
+ "player can not load max loadout" in {
+ val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
+ player.SaveLoadout("test1", 0)
+ player.ExoSuit = ExoSuitType.MAX
+ player.SaveLoadout("test2", 1)
+
+ val msg1 = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.InfantryLoadout, 4, "", 0, PlanetSideGUID(0))
+ terminal.Request(player, msg1) mustEqual Terminal.InfantryLoadout(ExoSuitType.Standard, 0, Nil, Nil)
+
+ val msg2 = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.InfantryLoadout, 4, "", 1, PlanetSideGUID(0))
+ terminal.Request(player, msg2) mustEqual Terminal.NoDeal()
+ }
+ }
+}
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index 46b6ef0e..d0e4826e 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -2097,8 +2097,24 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
case msg @ DeployRequestMessage(player_guid, entity, unk1, unk2, unk3, pos) =>
- //if you try to deploy, can not undeploy
log.info("DeployRequest: " + msg)
+ //LOCAL FUNCTIONALITY ONLY FOR THIS BRANCH
+ val player_guid = player.GUID
+ if(unk1 == 2) { // deploy AMS
+ //sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(entity,49,1))) // TODO : ANT ? With increment when loading NTU ?
+ sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player_guid, entity, unk1, unk2, unk3, Vector3(0f, 0f, 0f))))
+ Thread.sleep(1000) // 2 seconds
+ sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player_guid, entity, 3, unk2, unk3, Vector3(0f, 0f, 0f))))
+ sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(entity, 10, 1)))
+ sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(entity, 11, 1)))
+ sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(entity, 12, 1)))
+ sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(entity, 13, 1)))
+ }
+ else if(unk1 == 1) { // undeploy AMS
+ sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player_guid, entity, unk1, unk2, unk3, Vector3(0f, 0f, 0f))))
+ Thread.sleep(1000) // 2 seconds
+ sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player_guid, entity, 0, unk2, unk3, Vector3(0f, 0f, 0f))))
+ }
case msg @ AvatarGrenadeStateMessage(player_guid, state) =>
log.info("AvatarGrenadeStateMessage: " + msg)