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)