Merge pull request #187 from Fate-JH/ams-terminal

AMS Terminals
This commit is contained in:
Fate-JH 2018-02-17 17:34:02 -05:00 committed by GitHub
commit 27d86af015
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 549 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>
@ -23,7 +22,15 @@ import scala.collection.mutable
* Following that are the mounted weapons and other utilities.
* Trunk space starts being indexed afterwards.<br>
* <br>
* To keep it simple, infantry seating, mounted weapons, and utilities are stored separately.
* To keep it simple, infantry seating, mounted weapons, and utilities are stored separately.<br>
* <br>
* Vehicles maintain a `Map` of `Utility` objects in given index positions.
* Positive indices and zero are considered "represented" and must be assigned a globally unique identifier
* and must be present in the containing vehicle's `ObjectCreateMessage` packet.
* The index is the seat position, reflecting the position in the zero-index inventory.
* Negative indices are expected to be excluded from this conversion.
* The value of the negative index does not have a specific meaning.
* @see `Vehicle.EquipmentUtilities`
* @param vehicleDef the vehicle's definition entry';
* stores and unloads pertinent information about the `Vehicle`'s configuration;
* used in the initialization process (`loadVehicleDefinition`)
@ -49,7 +56,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 +332,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
@ -489,6 +501,14 @@ object Vehicle {
new Vehicle(vehicleDef)
}
/**
* Given a `Map` of `Utility` objects, only return the objects with a positive or zero-index position.
* @return a map of applicable utilities
*/
def EquipmentUtilities(utilities : Map[Int, Utility]) : Map[Int, Utility] = {
utilities.filter({ case(index : Int, _ : Utility) => index > -1 })
}
/**
* Use the `*Definition` that was provided to this object to initialize its fields and settings.
* @param vehicle the `Vehicle` being initialized
@ -506,10 +526,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] = {
Vehicle.EquipmentUtilities(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.EquipmentUtilities(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.EquipmentUtilities(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,103 @@
// 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
}
def Setup(implicit context : ActorContext) : Unit = setupFunc(obj, context)
/**
* Change the "active" state of this `Utility`.
* @param state the new active state
* @return the current active state after being changed
* Recover the original value used to initialize this object.
* @return the type of the `Amenity` object that was created
*/
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 UtilType : UtilityType.Value = util
}
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,8 +277,28 @@ class ConverterTest extends Specification {
}
}
"Vehicle" should {
"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 (1)" in {
val hellfire_ammo = AmmoBoxDefinition(Ammo.hellfire_ammo.id)
val fury_weapon_systema_def = ToolDefinition(ObjectClass.fury_weapon_systema)
@ -311,5 +333,16 @@ class ConverterTest extends Specification {
fury.Definition.Packet.ConstructorData(fury).isSuccess mustEqual true
ok //TODO write more of this test
}
"convert to packet (2)" in {
val
ams = Vehicle(GlobalDefinitions.ams)
ams.GUID = PlanetSideGUID(413)
ams.Utilities(3)().GUID = PlanetSideGUID(414)
ams.Utilities(4)().GUID = PlanetSideGUID(415)
ams.Definition.Packet.ConstructorData(ams).isSuccess mustEqual true
ok //TODO write more of this test
}
}
}

View file

@ -0,0 +1,71 @@
// 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.UtilType mustEqual UtilityType.order_terminala
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.UtilType mustEqual UtilityType.order_terminalb
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

@ -3,7 +3,7 @@ package objects
import akka.actor.Props
import net.psforever.objects.{GlobalDefinitions, Player, Vehicle}
import net.psforever.objects.definition.SeatDefinition
import net.psforever.objects.definition.{SeatDefinition, VehicleDefinition}
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.vehicles._
import net.psforever.packet.game.PlanetSideGUID
@ -256,6 +256,26 @@ class VehicleTest extends Specification {
harasser_vehicle.WeaponControlledFromSeat(0) mustEqual None
harasser_vehicle.WeaponControlledFromSeat(1) mustEqual chaingun_p
}
"can filter utilities with indices that are natural numbers" in {
val objDef = VehicleDefinition(1)
objDef.Utilities += -1 -> UtilityType.order_terminala
objDef.Utilities += 0 -> UtilityType.order_terminalb
objDef.Utilities += 2 -> UtilityType.order_terminalb
val obj = Vehicle(objDef)
obj.Utilities.size mustEqual 3
obj.Utilities(-1).UtilType mustEqual UtilityType.order_terminala
obj.Utilities(0).UtilType mustEqual UtilityType.order_terminalb
obj.Utilities.get(1) mustEqual None
obj.Utilities(2).UtilType mustEqual UtilityType.order_terminalb
val filteredMap = Vehicle.EquipmentUtilities(obj.Utilities)
filteredMap.size mustEqual 2
filteredMap.get(-1) mustEqual None
filteredMap(0).UtilType mustEqual UtilityType.order_terminalb
filteredMap(2).UtilType mustEqual UtilityType.order_terminalb
}
}
}

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)