A container class for vehicle-installed Amenity objects that are owned by the vehicle.  It not only constructs the internalized Amenity but harbors logic that is required to setup the object at a more appropriate time (at start of VehicleControl).

OrderTerminalABDefinition:
Terminals built into the sides of the advanced mobile spawn, accessible when it deploys.

OCM packet converters, GUID registrtaion, and setup code allows the code to wire into the vehicle.
This commit is contained in:
FateJH 2018-02-09 22:57:04 -05:00
parent fb1c365bc2
commit 860edf9a97
18 changed files with 494 additions and 229 deletions

View file

@ -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

View file

@ -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.<br>
@ -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 => ;

View file

@ -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

View file

@ -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)) }
}

View file

@ -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

View file

@ -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.<br>
* <br>
@ -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)
}
/**

View file

@ -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()
}
}
}

View file

@ -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.<br>
* <br>
* `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}")
}
}
}

View file

@ -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.<br>
* <br>
* `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.

View file

@ -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.

View file

@ -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)
}
}

View file

@ -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.<br>
* <br>
* 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.<br>
* <br>
* 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
}
}

View file

@ -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)

View file

@ -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")

View file

@ -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)

View file

@ -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 ! ""
}
}
}

View file

@ -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()
}
}
}

View file

@ -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)