From 211eb838aac727e8a2df5c5a826950438166b424 Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 18 Oct 2017 20:26:41 -0400 Subject: [PATCH 1/4] standardizing VehicleData Codec; clarifying field data for CommonFieldData; added various Vehicles that were not part of the earlier updates, leaving only the BFRs untouched; fixing PacketConverters for Vehicles; cleaning-up ObjectClass --- .../definition/converter/AMSConverter.scala | 44 -- .../definition/converter/ANTConverter.scala | 29 -- .../converter/PacketConverter.scala | 38 -- .../converter/UtilityVehicleConverter.scala | 11 + .../converter/VariantVehicleConverter.scala | 11 + .../converter/VehicleConverter.scala | 28 +- .../packet/game/objectcreate/AMSData.scala | 99 ---- .../packet/game/objectcreate/ANTData.scala | 62 --- .../game/objectcreate/CommonFieldData.scala | 30 +- .../objectcreate/CommonTerminalData.scala | 2 +- .../game/objectcreate/ObjectClass.scala | 479 ++++++++++-------- .../packet/game/objectcreate/Prefab.scala | 419 ++++++++++++--- .../game/objectcreate/Vehicle2Data.scala | 129 ----- .../game/objectcreate/VehicleData.scala | 328 ++++++------ .../scala/game/ObjectCreateMessageTest.scala | 21 +- .../ObjectCreateMessageVehiclesTest.scala | 191 ++++--- .../src/main/scala/WorldSessionActor.scala | 1 - 17 files changed, 982 insertions(+), 940 deletions(-) delete mode 100644 common/src/main/scala/net/psforever/objects/definition/converter/AMSConverter.scala delete mode 100644 common/src/main/scala/net/psforever/objects/definition/converter/ANTConverter.scala create mode 100644 common/src/main/scala/net/psforever/objects/definition/converter/UtilityVehicleConverter.scala create mode 100644 common/src/main/scala/net/psforever/objects/definition/converter/VariantVehicleConverter.scala delete mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/AMSData.scala delete mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/ANTData.scala delete mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/Vehicle2Data.scala diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/AMSConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/AMSConverter.scala deleted file mode 100644 index ee4cfc8e..00000000 --- a/common/src/main/scala/net/psforever/objects/definition/converter/AMSConverter.scala +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.definition.converter - -import net.psforever.objects.Vehicle -import net.psforever.packet.game.PlanetSideGUID -import net.psforever.packet.game.objectcreate.{AMSData, CommonFieldData, ObjectClass, PlacementData} - -import scala.util.{Success, Try} - -class AMSConverter extends ObjectCreateConverter[Vehicle] { - /* Vehicles do not have a conversion for `0x18` packet data. */ - - override def ConstructorData(obj : Vehicle) : Try[AMSData] = { - Success( - AMSData( - CommonFieldData( - PlacementData(obj.Position, obj.Orientation, obj.Velocity), - obj.Faction, - 0, - if(obj.Owner.isDefined) { obj.Owner.get } else { PlanetSideGUID(0) } //this is the owner field, right? - ), - 0, - obj.Health, - 0, - obj.Configuration, - 0, - ReferenceUtility(obj, ObjectClass.matrix_terminalc), - ReferenceUtility(obj, ObjectClass.ams_respawn_tube), - ReferenceUtility(obj, ObjectClass.order_terminala), - ReferenceUtility(obj, ObjectClass.order_terminalb) - ) - ) - } - - /** - * For an object with a list of utilities, find a specific kind of utility. - * @param obj the game object - * @param objectId the utility being sought - * @return the global unique identifier of the utility - */ - private def ReferenceUtility(obj : Vehicle, objectId : Int) : PlanetSideGUID = { - obj.Utilities.find(util => util.objectId == objectId).head.GUID - } -} diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/ANTConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/ANTConverter.scala deleted file mode 100644 index 19c8c729..00000000 --- a/common/src/main/scala/net/psforever/objects/definition/converter/ANTConverter.scala +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.definition.converter - -import net.psforever.objects.Vehicle -import net.psforever.packet.game.PlanetSideGUID -import net.psforever.packet.game.objectcreate.{ANTData, CommonFieldData, PlacementData} - -import scala.util.{Success, Try} - -class ANTConverter extends ObjectCreateConverter[Vehicle] { - /* Vehicles do not have a conversion for `0x18` packet data. */ - - override def ConstructorData(obj : Vehicle) : Try[ANTData] = { - Success( - ANTData( - CommonFieldData( - PlacementData(obj.Position, obj.Orientation,obj.Velocity), - obj.Faction, - 0, - if(obj.Owner.isDefined) { obj.Owner.get } else { PlanetSideGUID(0) } //this is the owner field, right? - ), - 0, - obj.Health, - 0, - obj.Configuration - ) - ) - } -} diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/PacketConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/PacketConverter.scala index 663b1a2a..0a7fec60 100644 --- a/common/src/main/scala/net/psforever/objects/definition/converter/PacketConverter.scala +++ b/common/src/main/scala/net/psforever/objects/definition/converter/PacketConverter.scala @@ -17,44 +17,6 @@ sealed trait PacketConverter * @tparam A the type of game object */ abstract class ObjectCreateConverter[A <: PlanetSideGameObject] extends PacketConverter { -// def ObjectCreate(obj : A) : Try[ObjectCreateMessage] = { -// Success( -// ObjectCreateMessage(obj.Definition.ObjectId, obj.GUID, -// DroppedItemData( -// PlacementData(obj.Position, obj.Orientation.x.toInt, obj.Orientation.y.toInt, obj.Orientation.z.toInt, Some(obj.Velocity)), -// ConstructorData(obj).get -// ) -// ) -// ) -// } -// -// def ObjectCreate(obj : A, info : PlacementData) : Try[ObjectCreateMessage] = { -// Success(ObjectCreateMessage(obj.Definition.ObjectId, obj.GUID, DroppedItemData(info, ConstructorData(obj).get))) -// } -// -// def ObjectCreate(obj : A, info : ObjectCreateMessageParent) : Try[ObjectCreateMessage] = { -// Success(ObjectCreateMessage(obj.Definition.ObjectId, obj.GUID, info, ConstructorData(obj).get)) -// } -// -// def ObjectCreateDetailed(obj : A) : Try[ObjectCreateDetailedMessage] = { -// Success( -// ObjectCreateDetailedMessage(obj.Definition.ObjectId, obj.GUID, -// DroppedItemData( -// PlacementData(obj.Position, obj.Orientation.x.toInt, obj.Orientation.y.toInt, obj.Orientation.z.toInt, Some(obj.Velocity)), -// DetailedConstructorData(obj).get -// ) -// ) -// ) -// } -// -// def ObjectCreateDetailed(obj : A, info : PlacementData) : Try[ObjectCreateDetailedMessage] = { -// Success(ObjectCreateDetailedMessage(obj.Definition.ObjectId, obj.GUID, DroppedItemData(info, DetailedConstructorData(obj).get))) -// } -// -// def ObjectCreateDetailed(obj : A, info : ObjectCreateMessageParent) : Try[ObjectCreateDetailedMessage] = { -// Success(ObjectCreateDetailedMessage(obj.Definition.ObjectId, obj.GUID, info, DetailedConstructorData(obj).get)) -// } - /** * Take a game object and transform it into its equivalent data for an `0x17` packet. * @param obj the game object diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/UtilityVehicleConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/UtilityVehicleConverter.scala new file mode 100644 index 00000000..c39f3965 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/definition/converter/UtilityVehicleConverter.scala @@ -0,0 +1,11 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.definition.converter + +import net.psforever.objects.Vehicle +import net.psforever.packet.game.objectcreate.{UtilityVehicleData, VehicleFormat} + +class UtilityVehicleConverter extends VehicleConverter { + override protected def SpecificFormatModifier : VehicleFormat.Value = VehicleFormat.Utility + + override protected def SpecificFormatData(obj : Vehicle) = Some(UtilityVehicleData(0)) +} diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/VariantVehicleConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/VariantVehicleConverter.scala new file mode 100644 index 00000000..12758a05 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/definition/converter/VariantVehicleConverter.scala @@ -0,0 +1,11 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.definition.converter + +import net.psforever.objects.Vehicle +import net.psforever.packet.game.objectcreate.{VariantVehicleData, VehicleFormat} + +class VariantVehicleConverter extends VehicleConverter { + override protected def SpecificFormatModifier : VehicleFormat.Value = VehicleFormat.Variant + + override protected def SpecificFormatData(obj : Vehicle) = Some(VariantVehicleData(0)) +} 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 39ea3403..06409367 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 @@ -4,14 +4,13 @@ package net.psforever.objects.definition.converter import net.psforever.objects.equipment.Equipment import net.psforever.objects.{EquipmentSlot, Vehicle} import net.psforever.packet.game.PlanetSideGUID -import net.psforever.packet.game.objectcreate.MountItem.MountItem -import net.psforever.packet.game.objectcreate.{CommonFieldData, DriveState, MountItem, PlacementData, VehicleData} +import net.psforever.packet.game.objectcreate.{InventoryItemData, _} import scala.annotation.tailrec -import scala.util.{Success, Try} +import scala.util.{Failure, Success, Try} class VehicleConverter extends ObjectCreateConverter[Vehicle]() { - /* Vehicles do not have a conversion for `0x18` packet data. */ + override def DetailedConstructorData(obj : Vehicle) : Try[VehicleData] = Failure(new Exception("VehicleConverter should not be used to generate detailed VehicleData")) override def ConstructorData(obj : Vehicle) : Try[VehicleData] = { Success( @@ -24,14 +23,15 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() { ), 0, obj.Health / obj.MaxHealth * 255, //TODO not precise - 0, + false, false, DriveState.Mobile, false, - 0, - Some(MakeMountings(obj).sortBy(_.parentSlot)) - ) + false, + false, + SpecificFormatData(obj), + Some(InventoryData(MakeMountings(obj).sortBy(_.parentSlot))) + )(SpecificFormatModifier) ) - //TODO work utilities into this mess? } /** @@ -39,9 +39,9 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() { * @param obj the Vehicle game object * @return the converted data */ - private def MakeMountings(obj : Vehicle) : List[MountItem] = recursiveMakeMountings(obj.Weapons.iterator) + private def MakeMountings(obj : Vehicle) : List[InventoryItemData.InventoryItem] = recursiveMakeMountings(obj.Weapons.iterator) - @tailrec private def recursiveMakeMountings(iter : Iterator[(Int,EquipmentSlot)], list : List[MountItem] = Nil) : List[MountItem] = { + @tailrec private def recursiveMakeMountings(iter : Iterator[(Int,EquipmentSlot)], list : List[InventoryItemData.InventoryItem] = Nil) : List[InventoryItemData.InventoryItem] = { if(!iter.hasNext) { list } @@ -51,7 +51,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() { val equip : Equipment = slot.Equipment.get recursiveMakeMountings( iter, - list :+ MountItem(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.Packet.ConstructorData(equip).get) + list :+ InventoryItemData(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.Packet.ConstructorData(equip).get) ) } else { @@ -59,4 +59,8 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() { } } } + + protected def SpecificFormatModifier : VehicleFormat.Value = VehicleFormat.Normal + + protected def SpecificFormatData(obj : Vehicle) : Option[SpecificVehicleData] = None } diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/AMSData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/AMSData.scala deleted file mode 100644 index 54a226cc..00000000 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/AMSData.scala +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.packet.game.objectcreate - -import net.psforever.packet.Marshallable -import net.psforever.packet.game.PlanetSideGUID -import net.psforever.types.PlanetSideEmpire -import scodec.codecs._ -import scodec.{Attempt, Codec, Err} -import shapeless.{::, HNil} - -/** - * A representation of a vehicle called the Advanced Mobile Station (AMS).
- *
- * The AMS has four utilities associated with its `Deployed` mode. - * It has two flanking equipment terminals, a front matrix panel, and a rear deconstruction terminal. - * This is consistent from AMS to AMS, regardless of the faction that spawned the vehicle originally. - * For that reason, the only thing that changes between different AMS's are the GUIDs used for each terminal. - * @param basic data common to objects - * @param unk1 na - * @param health the amount of health the object has, as a percentage of a filled bar - * @param unk2 na - * @param driveState the drivable condition - * @param unk3 na; - * common values are 0 or 63; - * usually in a non-`Mobile` state when non-zero - * @param matrix_guid the GUID for the spawn matrix panel on the front - * @param respawn_guid the GUID for the respawn apparatus on the rear - * @param term_a_guid the GUID for the equipment terminal on the AMS on the left side - * @param term_b_guid the GUID for the equipment on the AMS on the right side - */ -final case class AMSData(basic : CommonFieldData, - unk1 : Int, - health : Int, - unk2 : Int, - driveState : DriveState.Value, - unk3 : Int, - matrix_guid : PlanetSideGUID, - respawn_guid : PlanetSideGUID, - term_a_guid : PlanetSideGUID, - term_b_guid : PlanetSideGUID - ) extends ConstructorData { - override def bitsize : Long = { - val basicSize = basic.bitsize - val vehicleSize : Long = VehicleData.baseVehicleSize - //the four utilities should all be the same size - val utilitySize : Long = 4 * InternalSlot(ObjectClass.matrix_terminalc, matrix_guid, 1, CommonTerminalData(basic.faction)).bitsize - 19L + basicSize + vehicleSize + utilitySize - } -} - -object AMSData extends Marshallable[AMSData] { - /** - * Overloaded constructor that ignores all of the unknown fields. - * @param basic data common to objects - * @param health the amount of health the object has, as a percentage of a filled bar - * @param driveState the drivable condition - * @param matrix_guid the GUID for the spawn matrix panel on the front - * @param respawn_guid the GUID for the respawn apparatus on the rear - * @param term_a_guid the GUID for the equipment terminal on the AMS on the left side - * @param term_b_guid the GUID for the equipment on the AMS on the right side - * @return an `AMSData` object - */ - def apply(basic : CommonFieldData, health : Int, driveState : DriveState.Value, matrix_guid : PlanetSideGUID, respawn_guid : PlanetSideGUID, term_a_guid : PlanetSideGUID, term_b_guid : PlanetSideGUID) : AMSData = - new AMSData(basic, 0, health, 0, driveState, 0, matrix_guid, respawn_guid, term_a_guid, term_b_guid) - - implicit val codec : Codec[AMSData] = ( - VehicleData.basic_vehicle_codec :+ - uintL(6) :+ - bool :+ - uintL(12) :+ - InternalSlot.codec :+ - InternalSlot.codec :+ - InternalSlot.codec :+ - InternalSlot.codec - ).exmap[AMSData] ( - { - case basic :: unk1 :: health :: unk2 :: driveState :: false :: unk3 :: false :: 0x41 :: - InternalSlot(ObjectClass.matrix_terminalc, matrix_guid, 1, CommonTerminalData(_, _)) :: - InternalSlot(ObjectClass.ams_respawn_tube, respawn_guid,2, CommonTerminalData(_, _)) :: - InternalSlot(ObjectClass.order_terminala, terma_guid, 3, CommonTerminalData(_, _)) :: - InternalSlot(ObjectClass.order_terminalb, termb_guid, 4, CommonTerminalData(_, _)) :: HNil => - Attempt.successful(AMSData(basic, unk1, health, unk2, driveState, unk3, matrix_guid, respawn_guid, terma_guid, termb_guid)) - - case _ => - Attempt.failure(Err("invalid AMS data")) - }, - { - case AMSData(basic, unk1, health, unk2, driveState, unk3, matrix_guid, respawn_guid, terma_guid, termb_guid) => - val faction : PlanetSideEmpire.Value = basic.faction - Attempt.successful( - basic :: unk1 :: health :: unk2 :: driveState :: false :: unk3 :: false :: 0x41 :: - InternalSlot(ObjectClass.matrix_terminalc, matrix_guid, 1, CommonTerminalData(faction)) :: - InternalSlot(ObjectClass.ams_respawn_tube, respawn_guid,2, CommonTerminalData(faction)) :: - InternalSlot(ObjectClass.order_terminala, terma_guid, 3, CommonTerminalData(faction)) :: - InternalSlot(ObjectClass.order_terminalb, termb_guid, 4, CommonTerminalData(faction)) :: HNil - ) - } - ) -} diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ANTData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ANTData.scala deleted file mode 100644 index b75a3df7..00000000 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ANTData.scala +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.packet.game.objectcreate - -import net.psforever.packet.Marshallable -import scodec.codecs._ -import scodec.{Attempt, Codec, Err} -import shapeless.{::, HNil} - -/** - * A representation of a vehicle called the Advanced Nanite Transport (ANT). - * @param basic data common to objects - * @param unk1 na - * @param health the amount of health the object has, as a percentage of a filled bar - * @param unk2 na - * @param driveState the drivable condition; - * defaults to `Mobile` - * @param unk3 na; - * defaults to 0 - */ -final case class ANTData(basic : CommonFieldData, - unk1 : Int, - health : Int, - unk2 : Int, - driveState : DriveState.Value = DriveState.Mobile, - unk3 : Int = 0 - ) extends ConstructorData { - override def bitsize : Long = { - val basicSize = basic.bitsize - val vehicleBasicSize : Long = VehicleData.baseVehicleSize - 9L + basicSize + vehicleBasicSize - } -} - -object ANTData extends Marshallable[ANTData] { - /** - * Overloaded constructor. - * @param basic data common to objects - * @param health the amount of health the object has, as a percentage of a filled bar - * @param driveState the drivable condition - * @return an `ANTData` object - */ - def apply(basic : CommonFieldData, health : Int, driveState : DriveState.Value) : ANTData = - new ANTData(basic, 0, health, 0, driveState, 0) - - implicit val codec : Codec[ANTData] = ( - VehicleData.basic_vehicle_codec :+ - uint8L :+ - bool //false for vehicle driving control; ditto u4 from above - ).exmap[ANTData] ( - { - case basic :: unk1 :: health :: unk2 :: driveState :: false :: unk3 :: false :: HNil => - Attempt.successful(ANTData(basic, unk1, health, unk2, driveState, unk3)) - - case _ => - Attempt.failure(Err("invalid ant data format")) - }, - { - case ANTData(basic, unk1, health, unk2, driveState, unk3) => - Attempt.successful(basic :: unk1 :: health :: unk2 :: driveState :: false :: unk3 :: false :: HNil) - } - ) -} diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonFieldData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonFieldData.scala index f598e4fd..37ec4167 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonFieldData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonFieldData.scala @@ -17,7 +17,10 @@ import shapeless.{::, HNil} */ final case class CommonFieldData(pos : PlacementData, faction : PlanetSideEmpire.Value, + bops : Boolean, + destroyed : Boolean, unk : Int, + jammered : Boolean, player_guid : PlanetSideGUID ) extends StreamBitSize { override def bitsize : Long = 23L + pos.bitsize @@ -34,7 +37,16 @@ object CommonFieldData extends Marshallable[CommonFieldData] { * @return a `CommonFieldData` object */ def apply(pos : PlacementData, faction : PlanetSideEmpire.Value, unk : Int) : CommonFieldData = - CommonFieldData(pos, faction, unk, PlanetSideGUID(0)) + CommonFieldData(pos, faction, false, false, unk, false, PlanetSideGUID(0)) + + def apply(pos : PlacementData, faction : PlanetSideEmpire.Value, unk : Int, player_guid : PlanetSideGUID) : CommonFieldData = + CommonFieldData(pos, faction, false, false, unk, false, player_guid) + + def apply(pos : PlacementData, faction : PlanetSideEmpire.Value, destroyed : Boolean, unk : Int) : CommonFieldData = + CommonFieldData(pos, faction, false, destroyed, unk, false, PlanetSideGUID(0)) + + def apply(pos : PlacementData, faction : PlanetSideEmpire.Value, destroyed : Boolean, unk : Int, player_guid : PlanetSideGUID) : CommonFieldData = + CommonFieldData(pos, faction, false, destroyed, unk, false, player_guid) /** * `Codec` for transforming reliable `WeaponData` from the internal structure of the turret when it is defined. @@ -73,19 +85,19 @@ object CommonFieldData extends Marshallable[CommonFieldData] { implicit val codec : Codec[CommonFieldData] = ( ("pos" | PlacementData.codec) :: ("faction" | PlanetSideEmpire.codec) :: - ("unk" | uint(5)) :: + ("bops" | bool) :: + ("destroyed" | bool) :: + ("unk" | uint2L) :: //3 - na, 2 - common, 1 - na, 0 - common? + ("jammered" | bool) :: ("player_guid" | PlanetSideGUID.codec) ).exmap[CommonFieldData] ( { - case pos :: fac :: unk :: player :: HNil => - Attempt.successful(CommonFieldData(pos, fac, unk, player)) - - case _ => - Attempt.failure(Err("invalid deployable data format")) + case pos :: fac :: bops :: wrecked :: unk :: jammered :: player :: HNil => + Attempt.successful(CommonFieldData(pos, fac, bops, wrecked, unk,jammered, player)) }, { - case CommonFieldData(pos, fac, unk, player) => - Attempt.successful(pos :: fac :: unk :: player :: HNil) + case CommonFieldData(pos, fac, bops, wrecked, unk, jammered, player) => + Attempt.successful(pos :: fac :: bops :: wrecked :: unk :: jammered :: player :: HNil) } ) } diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonTerminalData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonTerminalData.scala index 7a5f6d7f..39dc42a1 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonTerminalData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonTerminalData.scala @@ -41,7 +41,7 @@ object CommonTerminalData extends Marshallable[CommonTerminalData] { { case fac :: 0 :: unk :: 0 :: HNil => Attempt.successful(CommonTerminalData(fac, unk)) - case _ :: _ :: _ :: _ :: HNil => + case _ => Attempt.failure(Err("invalid terminal data format")) }, { 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 f88a38e7..f396aba1 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 @@ -300,6 +300,10 @@ object ObjectClass { final val starfire_projectile = 831 final val striker_missile_targeting_projectile = 841 //vehicles + final val apc_destroyed = 65 + final val apc_tr = 67 //juggernaut + final val apc_nc = 66 //vindicator + final val apc_vs = 68 //leviathan final val ams = 46 final val ams_destroyed = 47 final val ant = 60 @@ -311,24 +315,38 @@ object ObjectClass { final val colossus_flight = 199 final val colossus_gunner = 200 final val droppod = 258 + final val dropship = 259 + final val dropship_destroyed = 260 final val flail = 294 + final val flail_destroyed = 295 final val fury = 335 final val galaxy_gunship = 338 final val liberator = 432 + final val liberator_destroyed = 439 final val lightgunship = 441 + final val lightgunship_destroyed = 442 final val lightning = 446 final val lightning_destroyed = 447 + final val lodestar = 459 + final val lodestar_destroyed = 460 final val magrider = 470 + final val magrider_destroyed = 471 + final val mosquito = 572 + final val mosquito_destroyed = 573 final val mediumtransport = 532 final val mediumtransport_destroyed = 533 final val orbital_shuttle = 608 final val peregrine_flight = 642 final val peregrine_gunner = 643 + final val phantasm = 671 final val prowler = 697 + final val prowler_destroyed = 698 final val quadassault = 707 final val quadassault_destroyed = 708 final val quadstealth = 710 final val quadstealth_destroyed = 711 + final val router = 741 + final val router_destroyed = 742 final val switchblade = 847 final val switchblade_destroyed = 848 final val threemanheavybuggy = 862 //marauder @@ -341,19 +359,26 @@ object ObjectClass { final val twomanhoverbuggy = 900 //thresher final val twomanhoverbuggy_destroyed = 901 final val vanguard = 923 + final val vanguard_destroyed = 924 final val vulture = 986 + final val wasp = 997 //other final val ams_respawn_tube = 49 final val avatar = 121 + final val bfr_rearm_terminal = 142 final val capture_flag = 157 final val implant_terminal_interface = 409 final val locker_container = 456 + final val lodestar_repair_terminal = 461 final val matrix_terminala = 517 final val matrix_terminalb = 518 final val matrix_terminalc = 519 + final val multivehicle_rearm_terminal = 576 final val order_terminal = 612 final val order_terminala = 613 final val order_terminalb = 614 + final val targeting_laser_dispenser = 851 + final val teleportpad_terminal = 853 //TODO refactor the following functions into another object later /** @@ -455,168 +480,169 @@ object ObjectClass { case ObjectClass.winchester_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box") //weapons case ObjectClass.beamer => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.chaingun_12mm => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.chaingun_15mm => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.cannon_20mm => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.cannon_deliverer_20mm => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.cannon_dropship_20mm => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.cannon_dropship_l_20mm => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.cannon_75mm => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.lightning_75mm => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.ace_deployable => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.advanced_missile_launcher_t => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.cannon_dropship_20mm => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.anniversary_gun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.anniversary_guna => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.anniversary_gunb => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.apc_ballgun_l => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.apc_ballgun_r => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.apc_weapon_systema => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.apc_weapon_systemb => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.apc_weapon_systemc => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.apc_weapon_systemc_nc => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.apc_weapon_systemc_tr => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.apc_weapon_systemc_vs => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.apc_weapon_systemd => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.apc_weapon_systemd_nc => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.apc_weapon_systemd_tr => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.apc_weapon_systemd_vs => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.aphelion_immolation_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.aphelion_laser => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.aphelion_laser_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.aphelion_laser_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.aphelion_plasma_rocket_pod => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.aphelion_ppa => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.aphelion_ppa_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.aphelion_ppa_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.aphelion_starfire => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.aphelion_starfire_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.aphelion_starfire_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.aurora_weapon_systema => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.aurora_weapon_systemb => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.battlewagon_weapon_systema => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.battlewagon_weapon_systemb => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.battlewagon_weapon_systemc => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.battlewagon_weapon_systemd => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.apc_ballgun_l => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.apc_ballgun_r => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.apc_weapon_systema => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.apc_weapon_systemb => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.apc_weapon_systemc => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.apc_weapon_systemc_nc => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.apc_weapon_systemc_tr => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.apc_weapon_systemc_vs => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.apc_weapon_systemd => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.apc_weapon_systemd_nc => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.apc_weapon_systemd_tr => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.apc_weapon_systemd_vs => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.aphelion_immolation_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.aphelion_laser => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.aphelion_laser_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.aphelion_laser_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.aphelion_plasma_rocket_pod => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.aphelion_ppa => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.aphelion_ppa_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.aphelion_ppa_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.aphelion_starfire => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.aphelion_starfire_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.aphelion_starfire_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.aurora_weapon_systema => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.aurora_weapon_systemb => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.battlewagon_weapon_systema => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.battlewagon_weapon_systemb => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.battlewagon_weapon_systemc => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.battlewagon_weapon_systemd => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.bolt_driver => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.chainblade => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.chaingun_p => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.colossus_burster => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.colossus_burster_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.colossus_burster_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.colossus_chaingun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.colossus_chaingun_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.colossus_chaingun_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.colossus_cluster_bomb_pod => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.colossus_dual_100mm_cannons => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.colossus_tank_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.colossus_tank_cannon_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.colossus_tank_cannon_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.colossus_burster => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.colossus_burster_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.colossus_burster_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.colossus_chaingun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.colossus_chaingun_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.colossus_chaingun_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.colossus_cluster_bomb_pod => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.colossus_dual_100mm_cannons => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.colossus_tank_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.colossus_tank_cannon_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.colossus_tank_cannon_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.cycler => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.cycler_v2 => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.cycler_v3 => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.cycler_v4 => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.dropship_rear_turret => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.energy_gun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.energy_gun_nc => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.energy_gun_tr => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.energy_gun_vs => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.flail_weapon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.dropship_rear_turret => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.energy_gun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.energy_gun_nc => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.energy_gun_tr => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.energy_gun_vs => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.flail_weapon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.flamethrower => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.flechette => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.flux_cannon_thresher => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.fluxpod => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.flux_cannon_thresher => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.fluxpod => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.forceblade => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.fragmentation_grenade => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.fury_weapon_systema => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.galaxy_gunship_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.galaxy_gunship_gun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.galaxy_gunship_tailgun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.fury_weapon_systema => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.galaxy_gunship_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.galaxy_gunship_gun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.galaxy_gunship_tailgun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.gauss => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.gauss_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.grenade_launcher_marauder => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.heavy_rail_beam_magrider => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.gauss_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.grenade_launcher_marauder => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.heavy_rail_beam_magrider => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.heavy_sniper => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.hellfire => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.hunterseeker => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.ilc9 => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.isp => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.jammer_grenade => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.katana => ConstructorData.genericCodec(DetailedWeaponData.codec(2), "weapon") case ObjectClass.lancer => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.lasher => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.liberator_25mm_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.liberator_bomb_bay => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.liberator_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.lightgunship_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.lightning_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.liberator_25mm_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.liberator_bomb_bay => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.liberator_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.lightgunship_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.lightning_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.maelstrom => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.magcutter => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.mediumtransport_weapon_systemA => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.mediumtransport_weapon_systemB => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.mediumtransport_weapon_systemA => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.mediumtransport_weapon_systemB => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.mini_chaingun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.oicw => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.nchev_falcon => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon") case ObjectClass.nchev_scattercannon => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon") case ObjectClass.nchev_sparrow => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon") - case ObjectClass.particle_beam_magrider => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.pellet_gun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.peregrine_dual_machine_gun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.peregrine_dual_machine_gun_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.peregrine_dual_machine_gun_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.peregrine_dual_rocket_pods => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.peregrine_mechhammer => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.peregrine_mechhammer_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.peregrine_mechhammer_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.peregrine_particle_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.peregrine_sparrow => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.peregrine_sparrow_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.peregrine_sparrow_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.phalanx_avcombo => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.phalanx_flakcombo => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.phalanx_sgl_hevgatcan => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.phantasm_12mm_machinegun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.particle_beam_magrider => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.peregrine_dual_machine_gun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.peregrine_dual_machine_gun_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.peregrine_dual_machine_gun_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.peregrine_dual_rocket_pods => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.peregrine_mechhammer => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.peregrine_mechhammer_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.peregrine_mechhammer_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.peregrine_particle_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.peregrine_sparrow => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.peregrine_sparrow_left => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.peregrine_sparrow_right => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.phalanx_avcombo => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.phalanx_flakcombo => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.phalanx_sgl_hevgatcan => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.phoenix => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.prowler_weapon_systemA => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.prowler_weapon_systemB => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.prowler_weapon_systemA => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.prowler_weapon_systemB => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.plasma_grenade => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.pulsar => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.pulsed_particle_accelerator => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.pulsed_particle_accelerator => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.punisher => ConstructorData.genericCodec(DetailedWeaponData.codec(2), "weapon") - case ObjectClass.quadassault_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.quadassault_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.r_shotgun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.radiator => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.repeater => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.rocklet => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.rotarychaingun_mosquito => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.router_telepad => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.rotarychaingun_mosquito => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.router_telepad => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") //TODO belongs here? case ObjectClass.scythe => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.six_shooter => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.skyguard_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.skyguard_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.spiker => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.spitfire_aa_weapon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.spitfire_weapon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.spitfire_aa_weapon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.spitfire_weapon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.striker => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.suppressor => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.thumper => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.thunderer_weapon_systema => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.thunderer_weapon_systemb => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.thunderer_weapon_systema => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.thunderer_weapon_systemb => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.trhev_burster => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon") case ObjectClass.trhev_dualcycler => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon") case ObjectClass.trhev_pounder => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon") - case ObjectClass.vanguard_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.vanu_sentry_turret_weapon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.vanguard_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec(2), "weapon") +// case ObjectClass.vanu_sentry_turret_weapon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.vshev_comet => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon") case ObjectClass.vshev_starfire => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon") case ObjectClass.vshev_quasar => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon") - case ObjectClass.vulture_bomb_bay => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.vulture_nose_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.vulture_tail_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.wasp_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.winchester => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.vulture_bomb_bay => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.vulture_nose_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.vulture_tail_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.wasp_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + //other weapons + case ObjectClass.ace_deployable => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.advanced_missile_launcher_t => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.cannon_20mm => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.cannon_75mm => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.cannon_deliverer_20mm => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.cannon_dropship_l_20mm => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.chaingun_12mm => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.chaingun_15mm => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.chaingun_p => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.cycler_v2 => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.cycler_v3 => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.cycler_v4 => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.dynomite => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.frag_grenade => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.generic_grenade => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.jammer_grenade => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.lightning_75mm => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") case ObjectClass.mine_sweeper => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") - case ObjectClass.plasma_grenade => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.pellet_gun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") +// case ObjectClass.phantasm_12mm_machinegun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.six_shooter => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") + case ObjectClass.winchester => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") //medkits case ObjectClass.medkit => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box") case ObjectClass.super_armorkit => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box") @@ -743,16 +769,6 @@ object ObjectClass { case ObjectClass.winchester_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box") //weapons case ObjectClass.beamer => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.chaingun_12mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.chaingun_15mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.cannon_20mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.cannon_deliverer_20mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.cannon_dropship_20mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.cannon_dropship_l_20mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.cannon_75mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.lightning_75mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.ace_deployable => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.advanced_missile_launcher_t => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.anniversary_gun => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.anniversary_guna => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.anniversary_gunb => ConstructorData.genericCodec(WeaponData.codec, "weapon") @@ -787,7 +803,6 @@ object ObjectClass { case ObjectClass.battlewagon_weapon_systemd => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.bolt_driver => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.chainblade => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.chaingun_p => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.colossus_burster => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.colossus_burster_left => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.colossus_burster_right => ConstructorData.genericCodec(WeaponData.codec, "weapon") @@ -800,11 +815,7 @@ object ObjectClass { case ObjectClass.colossus_tank_cannon_left => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.colossus_tank_cannon_right => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.cycler => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.cycler_v2 => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.cycler_v3 => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.cycler_v4 => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.dropship_rear_turret => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.energy_gun => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.energy_gun_nc => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.energy_gun_tr => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.energy_gun_vs => ConstructorData.genericCodec(WeaponData.codec, "weapon") @@ -828,13 +839,14 @@ object ObjectClass { case ObjectClass.hunterseeker => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.ilc9 => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.isp => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.jammer_grenade => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.katana => ConstructorData.genericCodec(WeaponData.codec(2), "weapon") case ObjectClass.lancer => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.lasher => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.liberator_25mm_cannon => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.liberator_bomb_bay => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.liberator_bomb_bay => ConstructorData.genericCodec(WeaponData.codec(2), "weapon") case ObjectClass.liberator_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.lightgunship_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.lightgunship_weapon_system => ConstructorData.genericCodec(WeaponData.codec(2), "weapon") case ObjectClass.lightning_weapon_system => ConstructorData.genericCodec(WeaponData.codec(2), "weapon") case ObjectClass.maelstrom => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.magcutter => ConstructorData.genericCodec(WeaponData.codec, "weapon") @@ -861,8 +873,8 @@ object ObjectClass { case ObjectClass.phalanx_avcombo => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.phalanx_flakcombo => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.phalanx_sgl_hevgatcan => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.phantasm_12mm_machinegun => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.phoenix => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.plasma_grenade => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.prowler_weapon_systemA => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.prowler_weapon_systemB => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.pulsar => ConstructorData.genericCodec(WeaponData.codec, "weapon") @@ -875,7 +887,6 @@ object ObjectClass { case ObjectClass.rocklet => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.rotarychaingun_mosquito => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.scythe => ConstructorData.genericCodec(WeaponData.codec(2), "weapon") - case ObjectClass.six_shooter => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.skyguard_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.spiker => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.spitfire_aa_weapon => ConstructorData.genericCodec(WeaponData.codec, "weapon") @@ -888,7 +899,7 @@ object ObjectClass { case ObjectClass.trhev_burster => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon") case ObjectClass.trhev_dualcycler => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon") case ObjectClass.trhev_pounder => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon") - case ObjectClass.vanguard_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.vanguard_weapon_system => ConstructorData.genericCodec(WeaponData.codec(2), "weapon") case ObjectClass.vanu_sentry_turret_weapon => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.vshev_comet => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon") case ObjectClass.vshev_quasar => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon") @@ -896,14 +907,30 @@ object ObjectClass { case ObjectClass.vulture_bomb_bay => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.vulture_nose_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.vulture_tail_cannon => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.wasp_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.winchester => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.wasp_weapon_system => ConstructorData.genericCodec(WeaponData.codec(2), "weapon") + //other weapons + case ObjectClass.ace_deployable => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.advanced_missile_launcher_t => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.cannon_20mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.cannon_75mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.cannon_deliverer_20mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.cannon_dropship_l_20mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.cannon_dropship_20mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.chaingun_12mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.chaingun_15mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.chaingun_p => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.cycler_v2 => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.cycler_v3 => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.cycler_v4 => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.dynomite => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.energy_gun => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.frag_grenade => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.generic_grenade => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.jammer_grenade => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.lightning_75mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.mine_sweeper => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.plasma_grenade => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.phantasm_12mm_machinegun => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.six_shooter => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.winchester => ConstructorData.genericCodec(WeaponData.codec, "weapon") //tools case ObjectClass.applicator => ConstructorData.genericCodec(WeaponData.codec, "tool") case ObjectClass.bank => ConstructorData.genericCodec(WeaponData.codec, "tool") @@ -922,13 +949,18 @@ object ObjectClass { case ObjectClass.orbital_shuttle => ConstructorData.genericCodec(OrbitalShuttleData.codec, "HART") //other 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") + case ObjectClass.lodestar_repair_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") case ObjectClass.matrix_terminala => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") case ObjectClass.matrix_terminalb => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") case ObjectClass.matrix_terminalc => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") + case ObjectClass.multivehicle_rearm_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") case ObjectClass.order_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") case ObjectClass.order_terminala => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") case ObjectClass.order_terminalb => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") + case ObjectClass.targeting_laser_dispenser => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") + case ObjectClass.teleportpad_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") //failure case case _ => defaultFailureCodec(objClass) } @@ -982,7 +1014,7 @@ object ObjectClass { case ObjectClass.flux_cannon_thresher_battery => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.fluxpod_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.frag_cartridge => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") - case ObjectClass.frag_grenade_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") +// case ObjectClass.frag_grenade_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.gauss_cannon_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.grenade => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.health_canister => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") @@ -991,7 +1023,7 @@ object ObjectClass { case ObjectClass.hellfire_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.hunter_seeker_missile => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.jammer_cartridge => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") - case ObjectClass.jammer_grenade_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") +// case ObjectClass.jammer_grenade_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.lancer_cartridge => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.liberator_bomb => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.maelstrom_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") @@ -1009,7 +1041,7 @@ object ObjectClass { case ObjectClass.phalanx_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.phoenix_missile => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.plasma_cartridge => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") - case ObjectClass.plasma_grenade_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") +// case ObjectClass.plasma_grenade_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.pounder_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.pulse_battery => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.quasar_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") @@ -1025,63 +1057,56 @@ object ObjectClass { case ObjectClass.spitfire_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.starfire_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.striker_missile_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") - case ObjectClass.trek_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") +// case ObjectClass.trek_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.upgrade_canister => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.wasp_gun_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.wasp_rocket_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") case ObjectClass.winchester_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box") //weapons case ObjectClass.beamer => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.chaingun_12mm => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.chaingun_15mm => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.ace_deployable => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.advanced_missile_launcher_t => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.anniversary_gun => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.anniversary_guna => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.anniversary_gunb => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.aphelion_immolation_cannon => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.aphelion_laser => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.aphelion_laser_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.aphelion_laser_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.aphelion_plasma_rocket_pod => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.aphelion_ppa => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.aphelion_ppa_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.aphelion_ppa_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.aphelion_starfire => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.aphelion_starfire_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.aphelion_starfire_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.aphelion_immolation_cannon => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.aphelion_laser => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.aphelion_laser_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.aphelion_laser_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.aphelion_plasma_rocket_pod => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.aphelion_ppa => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.aphelion_ppa_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.aphelion_ppa_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.aphelion_starfire => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.aphelion_starfire_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.aphelion_starfire_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.bolt_driver => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.chainblade => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.chaingun_p => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.colossus_burster => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.colossus_burster_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.colossus_burster_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.colossus_chaingun => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.colossus_chaingun_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.colossus_chaingun_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.colossus_cluster_bomb_pod => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.colossus_dual_100mm_cannons => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.colossus_tank_cannon => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.colossus_tank_cannon_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.colossus_tank_cannon_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.colossus_burster => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.colossus_burster_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.colossus_burster_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.colossus_chaingun => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.colossus_chaingun_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.colossus_chaingun_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.colossus_cluster_bomb_pod => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.colossus_dual_100mm_cannons => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.colossus_tank_cannon => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.colossus_tank_cannon_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.colossus_tank_cannon_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.cycler => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.cycler_v2 => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.cycler_v3 => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.cycler_v4 => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.energy_gun => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.energy_gun_nc => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.energy_gun_tr => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.energy_gun_vs => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.energy_gun => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.energy_gun_nc => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.energy_gun_tr => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.energy_gun_vs => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.flamethrower => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.flechette => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.forceblade => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.fragmentation_grenade => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.gauss => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.heavy_sniper => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.hellfire => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.hellfire => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.hunterseeker => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.ilc9 => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.isp => DroppedItemData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.jammer_grenade => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.katana => DroppedItemData.genericCodec(WeaponData.codec(2), "weapon") case ObjectClass.lancer => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.lasher => DroppedItemData.genericCodec(WeaponData.codec, "weapon") @@ -1089,39 +1114,47 @@ object ObjectClass { case ObjectClass.magcutter => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.mini_chaingun => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.oicw => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.pellet_gun => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.peregrine_dual_machine_gun => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.peregrine_dual_machine_gun_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.peregrine_dual_machine_gun_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.peregrine_dual_rocket_pods => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.peregrine_mechhammer => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.peregrine_mechhammer_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.peregrine_mechhammer_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.peregrine_particle_cannon => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.peregrine_sparrow => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.peregrine_sparrow_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.peregrine_sparrow_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.peregrine_dual_machine_gun => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.peregrine_dual_machine_gun_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.peregrine_dual_machine_gun_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.peregrine_dual_rocket_pods => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.peregrine_mechhammer => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.peregrine_mechhammer_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.peregrine_mechhammer_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.peregrine_particle_cannon => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.peregrine_sparrow => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.peregrine_sparrow_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.peregrine_sparrow_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.phoenix => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.pulsar => DroppedItemData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.plasma_grenade => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.punisher => DroppedItemData.genericCodec(WeaponData.codec(2), "weapon") case ObjectClass.r_shotgun => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.radiator => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.repeater => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.rocklet => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.six_shooter => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.spiker => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.spitfire_aa_weapon => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.spitfire_weapon => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.spitfire_aa_weapon => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.spitfire_weapon => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.striker => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.suppressor => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.thumper => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.winchester => DroppedItemData.genericCodec(WeaponData.codec, "weapon") + //other weapons + case ObjectClass.ace_deployable => DroppedItemData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.advanced_missile_launcher_t => DroppedItemData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.chaingun_12mm => DroppedItemData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.chaingun_15mm => DroppedItemData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.chaingun_p => DroppedItemData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.cycler_v2 => DroppedItemData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.cycler_v3 => DroppedItemData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.cycler_v4 => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.dynomite => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.frag_grenade => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.generic_grenade => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.jammer_grenade => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.mine_sweeper => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.plasma_grenade => DroppedItemData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.pellet_gun => DroppedItemData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.six_shooter => DroppedItemData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.winchester => DroppedItemData.genericCodec(WeaponData.codec, "weapon") //medkits case ObjectClass.medkit => DroppedItemData.genericCodec(AmmoBoxData.codec, "medkit") case ObjectClass.super_armorkit => DroppedItemData.genericCodec(AmmoBoxData.codec, "repair kit") @@ -1161,34 +1194,72 @@ object ObjectClass { case ObjectClass.starfire_projectile => ConstructorData.genericCodec(TrackedProjectileData.codec, "projectile") case ObjectClass.striker_missile_targeting_projectile => ConstructorData.genericCodec(TrackedProjectileData.codec, "projectile") //vehicles - case ObjectClass.ams => ConstructorData.genericCodec(AMSData.codec, "ams") + case ObjectClass.ams => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Utility), "ams") case ObjectClass.ams_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") - case ObjectClass.ant => ConstructorData.genericCodec(ANTData.codec, "ant") + case ObjectClass.ant => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Utility), "ant") case ObjectClass.ant_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") - case ObjectClass.aurora => ConstructorData.genericCodec(VehicleData.codec(2)(), "vehicle") - case ObjectClass.battlewagon => ConstructorData.genericCodec(VehicleData.codec(4)(), "vehicle") + case ObjectClass.apc_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") + case ObjectClass.apc_nc => ConstructorData.genericCodec(VehicleData.codec, "vehicle") + case ObjectClass.apc_tr => ConstructorData.genericCodec(VehicleData.codec, "vehicle") + case ObjectClass.apc_vs => ConstructorData.genericCodec(VehicleData.codec, "vehicle") + //case ObjectClass.aphelion_destroyed => normal @ 0 health + case ObjectClass.aphelion_flight => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Battleframe), "vehicle") + case ObjectClass.aphelion_gunner => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Battleframe), "vehicle") + case ObjectClass.aurora => ConstructorData.genericCodec(VehicleData.codec, "vehicle") + case ObjectClass.battlewagon => ConstructorData.genericCodec(VehicleData.codec, "vehicle") + //case ObjectClass.colossus_destroyed => normal @ 0 health + case ObjectClass.colossus_flight => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Battleframe), "vehicle") + case ObjectClass.colossus_gunner => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Battleframe), "vehicle") case ObjectClass.droppod => ConstructorData.genericCodec(DroppodData.codec, "droppod") + case ObjectClass.dropship => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle") + case ObjectClass.dropship_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") + case ObjectClass.flail => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle") + case ObjectClass.flail_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") case ObjectClass.fury => ConstructorData.genericCodec(VehicleData.codec, "vehicle") + case ObjectClass.galaxy_gunship => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle") + case ObjectClass.liberator => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle") + case ObjectClass.liberator_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") + case ObjectClass.lightgunship => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle") + case ObjectClass.lightgunship_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") case ObjectClass.lightning => ConstructorData.genericCodec(VehicleData.codec, "vehicle") case ObjectClass.lightning_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") - case ObjectClass.mediumtransport => ConstructorData.genericCodec(VehicleData.codec(2)(), "vehicle") + case ObjectClass.lodestar => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle") + case ObjectClass.lodestar_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") + case ObjectClass.magrider => ConstructorData.genericCodec(VehicleData.codec, "vehicle") + case ObjectClass.magrider_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") + case ObjectClass.mediumtransport => ConstructorData.genericCodec(VehicleData.codec, "vehicle") case ObjectClass.mediumtransport_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") + case ObjectClass.mosquito => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle") + case ObjectClass.mosquito_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") case ObjectClass.orbital_shuttle => ConstructorData.genericCodec(OrbitalShuttleData.codec_pos, "HART") + //case ObjectClass.peregrine_destroyed => normal @ 0 health + case ObjectClass.peregrine_flight => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Battleframe), "vehicle") + case ObjectClass.peregrine_gunner => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Battleframe), "vehicle") + case ObjectClass.phantasm => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle") + //case ObjectClass.phantasm_destroyed => normal @ 0 health + case ObjectClass.prowler => ConstructorData.genericCodec(VehicleData.codec, "vehicle") + case ObjectClass.prowler_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") case ObjectClass.quadassault => ConstructorData.genericCodec(VehicleData.codec, "vehicle") case ObjectClass.quadassault_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") - case ObjectClass.quadstealth => ConstructorData.genericCodec(VehicleData.codec(0)(), "vehicle") + case ObjectClass.quadstealth => ConstructorData.genericCodec(VehicleData.codec, "vehicle") case ObjectClass.quadstealth_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") - case ObjectClass.switchblade => ConstructorData.genericCodec(Vehicle2Data.codec, "vehicle") + case ObjectClass.router => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle") + case ObjectClass.router_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") + case ObjectClass.switchblade => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle") case ObjectClass.switchblade_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") - case ObjectClass.threemanheavybuggy => ConstructorData.genericCodec(VehicleData.codec(2)(), "vehicle") + case ObjectClass.threemanheavybuggy => ConstructorData.genericCodec(VehicleData.codec, "vehicle") case ObjectClass.threemanheavybuggy_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") - case ObjectClass.thunderer => ConstructorData.genericCodec(VehicleData.codec(2)(), "vehicle") + case ObjectClass.thunderer => ConstructorData.genericCodec(VehicleData.codec, "vehicle") case ObjectClass.two_man_assault_buggy => ConstructorData.genericCodec(VehicleData.codec, "vehicle") case ObjectClass.two_man_assault_buggy_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") case ObjectClass.twomanheavybuggy => ConstructorData.genericCodec(VehicleData.codec, "vehicle") case ObjectClass.twomanheavybuggy_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") case ObjectClass.twomanhoverbuggy => ConstructorData.genericCodec(VehicleData.codec, "vehicle") case ObjectClass.twomanhoverbuggy_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") + case ObjectClass.vanguard => ConstructorData.genericCodec(VehicleData.codec, "vehicle") + case ObjectClass.vanguard_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") + case ObjectClass.vulture => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle") + case ObjectClass.wasp => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle") //other case ObjectClass.ams_respawn_tube => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal") case ObjectClass.avatar => ConstructorData.genericCodec(CharacterData.codec, "avatar") diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala index d0d4c669..8d3a6065 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala @@ -1,153 +1,422 @@ // Copyright (c) 2017 PSForever package net.psforever.packet.game.objectcreate +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.types.PlanetSideEmpire + /** * A compilation of the common `*Data` objects that would be used for stock game objects. * Each function is named after the `ObjectClass` name (internal name) it creates. * No `Prefab` assumes empire allegiance or initial health. + * This file is more useful for reference, rather than application. */ object Prefab { - import net.psforever.packet.game.PlanetSideGUID - import net.psforever.types.PlanetSideEmpire - object Vehicle { - def ams(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, driveState : DriveState.Value, matrix_guid : PlanetSideGUID, respawn_guid : PlanetSideGUID, term_a_guid : PlanetSideGUID, term_b_guid : PlanetSideGUID) : AMSData = { - AMSData(CommonFieldData(loc, faction, 0), health, driveState, matrix_guid, respawn_guid, term_a_guid, term_b_guid) + def ams(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, driveState : DriveState.Value, matrix_guid : PlanetSideGUID, respawn_guid : PlanetSideGUID, term_a_guid : PlanetSideGUID, term_b_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, driveState, false, false, false, Some(UtilityVehicleData(0)), + Some(InventoryData(List( + InternalSlot(ObjectClass.matrix_terminalc, matrix_guid, 1, CommonTerminalData(faction)), + InternalSlot(ObjectClass.ams_respawn_tube, respawn_guid, 2, CommonTerminalData(faction)), + InternalSlot(ObjectClass.order_terminala, term_a_guid, 3, CommonTerminalData(faction)), + InternalSlot(ObjectClass.order_terminalb, term_b_guid, 4, CommonTerminalData(faction)) + ))) + )(VehicleFormat.Utility) } - def ant(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, driveState : DriveState.Value) : ANTData = { - ANTData(CommonFieldData(loc, faction, 0), health, driveState) + def ant(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, driveState : DriveState.Value) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, driveState, false, false, false, Some(UtilityVehicleData(0)), None)(VehicleFormat.Utility) } def aurora(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo11_guid : PlanetSideGUID, ammo12_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo21_guid : PlanetSideGUID, ammo22_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, 0, DriveState.State7, true, 0, - Some( - MountItem(ObjectClass.aurora_weapon_systema, weapon1_guid, 5, + VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + Some(InventoryData( + InventoryItemData(ObjectClass.aurora_weapon_systema, weapon1_guid, 5, WeaponData(0x6, 0x8, 0, ObjectClass.fluxpod_ammo, ammo11_guid, 0, AmmoBoxData(0x8), ObjectClass.fluxpod_ammo, ammo12_guid, 1, AmmoBoxData(0x8)) ) :: - MountItem(ObjectClass.aurora_weapon_systemb, weapon2_guid, 6, + InventoryItemData(ObjectClass.aurora_weapon_systemb, weapon2_guid, 6, WeaponData(0x6, 0x8, 0, ObjectClass.fluxpod_ammo, ammo21_guid, 0, AmmoBoxData(0x8), ObjectClass.fluxpod_ammo, ammo22_guid, 1, AmmoBoxData(0x8)) ) :: Nil - ) - )(2) + )) + )(VehicleFormat.Normal) } def battlewagon(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon4_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, 0, DriveState.State7, true, 0, - Some( - MountItem(ObjectClass.battlewagon_weapon_systema, weapon1_guid, 5, + VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + Some(InventoryData( + InventoryItemData(ObjectClass.battlewagon_weapon_systema, weapon1_guid, 5, WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo1_guid, 0, AmmoBoxData(0x8)) ) :: - MountItem(ObjectClass.battlewagon_weapon_systemb, weapon2_guid, 6, + InventoryItemData(ObjectClass.battlewagon_weapon_systemb, weapon2_guid, 6, WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo2_guid, 0, AmmoBoxData(0x8)) ) :: - MountItem(ObjectClass.battlewagon_weapon_systemc, weapon3_guid, 7, + InventoryItemData(ObjectClass.battlewagon_weapon_systemc, weapon3_guid, 7, WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo3_guid, 0, AmmoBoxData(0x8)) ) :: - MountItem(ObjectClass.battlewagon_weapon_systemd, weapon4_guid, 8, + InventoryItemData(ObjectClass.battlewagon_weapon_systemd, weapon4_guid, 8, WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo4_guid, 0, AmmoBoxData(0x8)) ) :: Nil - ) - )(4) + )) + )(VehicleFormat.Normal) + } + + def dropship(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + Some(InventoryData( + InventoryItemData(ObjectClass.cannon_dropship_20mm, weapon1_guid, 12, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo1_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.cannon_dropship_20mm, weapon2_guid, 13, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo2_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.dropship_rear_turret, weapon3_guid, 14, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo3_guid, 0, AmmoBoxData(8)) + ) :: Nil + )) + )(VehicleFormat.Variant) + } + + def flail(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID, terminal_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.Mobile, false, false, false, Some(VariantVehicleData(0)), + Some(InventoryData( + InventoryItemData(ObjectClass.flail_weapon, weapon_guid, 1, + WeaponData(0x6, 0x8, 0, ObjectClass.ancient_ammo_vehicle, ammo_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.targeting_laser_dispenser, terminal_guid, 2, CommonTerminalData(faction, 2)) :: Nil + )) + )(VehicleFormat.Variant) } def fury(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), health, - MountItem(ObjectClass.fury_weapon_systema, weapon_guid, 1, - WeaponData(0x4, 0x8, ObjectClass.hellfire_ammo, ammo_guid, 0, AmmoBoxData(0x8)) - ) - ) + VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + Some(InventoryData( + InventoryItemData(ObjectClass.fury_weapon_systema, weapon_guid, 1, + WeaponData(0x4, 0x8, ObjectClass.hellfire_ammo, ammo_guid, 0, AmmoBoxData(0x8)) + ) :: Nil + )) + )(VehicleFormat.Normal) + } + + def galaxy_gunship(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon4_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID, weapon5_guid : PlanetSideGUID, ammo5_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + Some(InventoryData( + InventoryItemData(ObjectClass.galaxy_gunship_cannon, weapon1_guid, 6, + WeaponData(0x6, 0x8, 0, ObjectClass.heavy_grenade_mortar, ammo1_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.galaxy_gunship_cannon, weapon2_guid, 7, + WeaponData(0x6, 0x8, 0, ObjectClass.heavy_grenade_mortar, ammo2_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.galaxy_gunship_tailgun, weapon3_guid, 8, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_35mm, ammo3_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.galaxy_gunship_gun, weapon4_guid, 9, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_35mm, ammo4_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.galaxy_gunship_gun, weapon5_guid, 10, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_35mm, ammo5_guid, 0, AmmoBoxData(8)) + ) :: Nil + )) + )(VehicleFormat.Variant) + } + + def juggernaut(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon4_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID, weapon5_guid : PlanetSideGUID, ammo5_guid : PlanetSideGUID, weapon6_guid : PlanetSideGUID, ammo6_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + Some(InventoryData( + InventoryItemData(ObjectClass.apc_weapon_systemc_tr, weapon1_guid, 11, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo1_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.apc_weapon_systemb, weapon2_guid, 12, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo2_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.apc_weapon_systema, weapon3_guid, 13, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo3_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.apc_weapon_systemd_tr, weapon4_guid, 14, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo4_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.apc_ballgun_r, weapon5_guid, 15, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo5_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.apc_ballgun_l, weapon6_guid, 16, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo6_guid, 0, AmmoBoxData(8)) + ) :: Nil + )) + )(VehicleFormat.Normal) + } + + def leviathan(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon4_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID, weapon5_guid : PlanetSideGUID, ammo5_guid : PlanetSideGUID, weapon6_guid : PlanetSideGUID, ammo6_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + Some(InventoryData( + InventoryItemData(ObjectClass.apc_weapon_systemc_vs, weapon1_guid, 11, + WeaponData(0x6, 0x8, 0, ObjectClass.flux_cannon_thresher_battery, ammo1_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.apc_weapon_systemb, weapon2_guid, 12, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo2_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.apc_weapon_systema, weapon3_guid, 13, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo3_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.apc_weapon_systemd_vs, weapon4_guid, 14, + WeaponData(0x6, 0x8, 0, ObjectClass.flux_cannon_thresher_battery, ammo4_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.apc_ballgun_r, weapon5_guid, 15, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo5_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.apc_ballgun_l, weapon6_guid, 16, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo6_guid, 0, AmmoBoxData(8)) + ) :: Nil + )) + )(VehicleFormat.Normal) + } + + def liberator(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + Some(InventoryData( + InventoryItemData(ObjectClass.liberator_weapon_system, weapon1_guid, 3, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_35mm, ammo1_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.liberator_bomb_bay, weapon2_guid, 4, + WeaponData(0x6, 0x8, 0, ObjectClass.liberator_bomb, ammo2_guid, 0, AmmoBoxData(8), ObjectClass.liberator_bomb, ammo3_guid, 1, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.liberator_25mm_cannon, weapon3_guid, 5, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_25mm, ammo4_guid, 0 ,AmmoBoxData(8)) + ) :: Nil + )) + )(VehicleFormat.Variant) + } + + def lightgunship(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.Mobile, false, false, false, Some(VariantVehicleData(0)), + Some(InventoryData( + InventoryItemData(445, weapon_guid, 1, + WeaponData(0x6, 0x8, 0, 16, ammo1_guid, 0, AmmoBoxData(8), 722, ammo2_guid,1, AmmoBoxData(8)) + ) :: Nil + )) + )(VehicleFormat.Variant) } def lightning(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), health, - MountItem(ObjectClass.lightning_weapon_system, weapon_guid, 1, - WeaponData(0x4, 0x8, 0, ObjectClass.bullet_75mm, ammo1_guid, 0, AmmoBoxData(0x0), ObjectClass.bullet_25mm, ammo2_guid, 1, AmmoBoxData(0x0)) + VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + Some(InventoryData( + InventoryItemData(ObjectClass.lightning_weapon_system, weapon_guid, 1, + WeaponData(0x4, 0x8, 0, ObjectClass.bullet_75mm, ammo1_guid, 0, AmmoBoxData(0x0), ObjectClass.bullet_25mm, ammo2_guid, 1, AmmoBoxData(0x0)) + ) :: Nil) ) - ) + )(VehicleFormat.Normal) + } + + def lodestar(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, repair1_guid : PlanetSideGUID, repair2_guid : PlanetSideGUID, veh_rearm1_guid : PlanetSideGUID, veh_rearm2_guid : PlanetSideGUID, bfr_rearm1_guid : PlanetSideGUID, bfr_rearm2_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + Some(InventoryData(List( + InternalSlot(ObjectClass.lodestar_repair_terminal, repair1_guid, 2, CommonTerminalData(faction, 2)), + InternalSlot(ObjectClass.lodestar_repair_terminal, repair2_guid, 3, CommonTerminalData(faction, 2)), + InternalSlot(ObjectClass.multivehicle_rearm_terminal, veh_rearm1_guid, 4, CommonTerminalData(faction, 2)), + InternalSlot(ObjectClass.multivehicle_rearm_terminal, veh_rearm2_guid, 5, CommonTerminalData(faction, 2)), + InternalSlot(ObjectClass.bfr_rearm_terminal, bfr_rearm1_guid, 6, CommonTerminalData(faction, 2)), + InternalSlot(ObjectClass.bfr_rearm_terminal, bfr_rearm2_guid, 7, CommonTerminalData(faction, 2)) + ))) + )(VehicleFormat.Variant) + } + + def magrider(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + Some(InventoryData( + InventoryItemData(ObjectClass.particle_beam_magrider, weapon1_guid, 2, + WeaponData(0x6, 0x8, 0, ObjectClass.pulse_battery, ammo1_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.heavy_rail_beam_magrider, weapon2_guid, 3, + WeaponData(0x6, 0x8, 0, ObjectClass.heavy_rail_beam_battery, ammo2_guid, 0, AmmoBoxData(8)) + ) :: Nil + )) + )(VehicleFormat.Normal) } def mediumtransport(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID): VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, 0, DriveState.State7, true, 0, - Some( - MountItem(ObjectClass.mediumtransport_weapon_systemA, weapon1_guid, 5, + VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + Some(InventoryData( + InventoryItemData(ObjectClass.mediumtransport_weapon_systemA, weapon1_guid, 5, WeaponData(0x6, 0x8, ObjectClass.bullet_20mm, ammo1_guid, 0, AmmoBoxData(0x8)) ) :: - MountItem(ObjectClass.mediumtransport_weapon_systemB, weapon2_guid, 6, + InventoryItemData(ObjectClass.mediumtransport_weapon_systemB, weapon2_guid, 6, WeaponData(0x6, 0x8, ObjectClass.bullet_20mm, ammo2_guid, 0, AmmoBoxData(0x8)) ) :: Nil - ) - )(2) + )) + )(VehicleFormat.Normal) + } + + def mosquito(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + Some(InventoryData( + InventoryItemData(ObjectClass.rotarychaingun_mosquito, weapon_guid, 1, + WeaponData(0x6, 0x8, ObjectClass.bullet_12mm, ammo_guid, 0, AmmoBoxData(8)) + ) :: Nil + )) + )(VehicleFormat.Variant) + } + + def phantasm(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), None)(VehicleFormat.Variant) + } + + def prowler(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + Some(InventoryData( + InventoryItemData(ObjectClass.prowler_weapon_systemA, weapon1_guid, 3, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_105mm, ammo1_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.prowler_weapon_systemB, weapon2_guid, 4, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo2_guid, 0, AmmoBoxData(8)) + ) :: Nil + )) + )(VehicleFormat.Normal) + } + + def router(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, terminal_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.Mobile, false, false, false, Some(VariantVehicleData(0)), + Some(InventoryData( + InventoryItemData(ObjectClass.teleportpad_terminal, terminal_guid, 1, CommonTerminalData(faction, 2)) :: Nil + )) + )(VehicleFormat.Variant) } def quadassault(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), health, - MountItem(ObjectClass.quadassault_weapon_system, weapon_guid, 1, - WeaponData(0x6, 0x8, ObjectClass.bullet_12mm, ammo_guid, 0, AmmoBoxData(0x8)) - ) - ) + VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + Some(InventoryData( + InventoryItemData(ObjectClass.quadassault_weapon_system, weapon_guid, 1, + WeaponData(0x6, 0x8, ObjectClass.bullet_12mm, ammo_guid, 0, AmmoBoxData(0x8)) + ) :: Nil + )) + )(VehicleFormat.Normal) } def quadstealth(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, 0, DriveState.State7, false, 0)(0) + VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, false, false, false, None, None)(VehicleFormat.Normal) } - def switchblade(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, driveState : DriveState.Value, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : Vehicle2Data = { - Vehicle2Data(CommonFieldData(loc, faction, 0), health, driveState, - MountItem(ObjectClass.scythe, weapon_guid, 1, - WeaponData(0x6, 0x8, 0, ObjectClass.ancient_ammo_vehicle, ammo1_guid, 0, AmmoBoxData(0x8), ObjectClass.ancient_ammo_vehicle, ammo2_guid, 1, AmmoBoxData(0x8)) - ) - ) + def switchblade(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, driveState : DriveState.Value, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + Some(InventoryData( + InventoryItemData(ObjectClass.scythe, weapon_guid, 1, + WeaponData(0x6, 0x8, 0, ObjectClass.ancient_ammo_vehicle, ammo1_guid, 0, AmmoBoxData(0x8), ObjectClass.ancient_ammo_vehicle, ammo2_guid, 1, AmmoBoxData(0x8)) + ) :: Nil + )) + )(VehicleFormat.Variant) } def threemanheavybuggy(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, 0, DriveState.State7, true, 0, - Some( - MountItem(ObjectClass.chaingun_p, weapon1_guid, 3, + VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + Some(InventoryData( + InventoryItemData(ObjectClass.chaingun_p, weapon1_guid, 3, WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo1_guid, 0, AmmoBoxData(0x8)) ) :: - MountItem(ObjectClass.grenade_launcher_marauder, weapon2_guid, 4, + InventoryItemData(ObjectClass.grenade_launcher_marauder, weapon2_guid, 4, WeaponData(0x6, 0x8, 0, ObjectClass.heavy_grenade_mortar, ammo2_guid, 0, AmmoBoxData(0x8)) ) :: Nil - ) - )(2) + )) + )(VehicleFormat.Normal) } def thunderer(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, 0, DriveState.State7, true, 0, - Some( - MountItem(ObjectClass.thunderer_weapon_systema, weapon1_guid, 5, + VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + Some(InventoryData( + InventoryItemData(ObjectClass.thunderer_weapon_systema, weapon1_guid, 5, WeaponData(0x6, 0x8, 0, ObjectClass.gauss_cannon_ammo, ammo1_guid, 0, AmmoBoxData(0x8)) ) :: - MountItem(ObjectClass.thunderer_weapon_systemb, weapon2_guid, 6, + InventoryItemData(ObjectClass.thunderer_weapon_systemb, weapon2_guid, 6, WeaponData(0x6, 0x8, 0, ObjectClass.gauss_cannon_ammo, ammo2_guid, 0, AmmoBoxData(0x8)) ) :: Nil - ) - )(2) + )) + )(VehicleFormat.Normal) } def two_man_assault_buggy(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), health, - MountItem(ObjectClass.chaingun_p, weapon_guid, 2, - WeaponData(0x6, 0x8, ObjectClass.bullet_12mm, ammo_guid, 0, AmmoBoxData(0x8)) - ) - ) + VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + Some(InventoryData( + InventoryItemData(ObjectClass.chaingun_p, weapon_guid, 2, + WeaponData(0x6, 0x8, ObjectClass.bullet_12mm, ammo_guid, 0, AmmoBoxData(0x8)) + ) :: Nil + )) + )(VehicleFormat.Normal) } def twomanheavybuggy(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), health, - MountItem(ObjectClass.advanced_missile_launcher_t, weapon_guid, 2, - WeaponData(0x6, 0x8, 0, ObjectClass.firebird_missile, ammo_guid, 0, AmmoBoxData(0x8)) - ) - ) + VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + Some(InventoryData( + InventoryItemData(ObjectClass.advanced_missile_launcher_t, weapon_guid, 2, + WeaponData(0x6, 0x8, 0, ObjectClass.firebird_missile, ammo_guid, 0, AmmoBoxData(0x8)) + ) :: Nil + )) + )(VehicleFormat.Normal) } def twomanhoverbuggy(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), health, - MountItem(ObjectClass.flux_cannon_thresher, weapon_guid, 2, - WeaponData(0x6, 0x8, 0, ObjectClass.flux_cannon_thresher_battery, ammo_guid, 0, AmmoBoxData(0x8)) - ) - ) + VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + Some(InventoryData( + InventoryItemData(ObjectClass.flux_cannon_thresher, weapon_guid, 2, + WeaponData(0x6, 0x8, 0, ObjectClass.flux_cannon_thresher_battery, ammo_guid, 0, AmmoBoxData(0x8)) + ) :: Nil + )) + )(VehicleFormat.Normal) + } + + def vanguard(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + Some(InventoryData( + InventoryItemData(ObjectClass.vanguard_weapon_system, weapon_guid, 2, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_150mm, ammo1_guid, 0, AmmoBoxData(8), ObjectClass.bullet_20mm, ammo2_guid, 1, AmmoBoxData(8)) + ) :: Nil + )) + )(VehicleFormat.Normal) + } + + def vindicator(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon4_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID, weapon5_guid : PlanetSideGUID, ammo5_guid : PlanetSideGUID, weapon6_guid : PlanetSideGUID, ammo6_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + Some(InventoryData( + InventoryItemData(ObjectClass.apc_weapon_systemc_nc, weapon1_guid, 11, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo1_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.apc_weapon_systemb, weapon2_guid, 12, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo2_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.apc_weapon_systema, weapon3_guid, 13, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo3_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.apc_weapon_systemd_nc, weapon4_guid, 14, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo4_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.apc_ballgun_r, weapon5_guid, 15, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo5_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.apc_ballgun_l, weapon6_guid, 16, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo6_guid, 0, AmmoBoxData(8)) + ) :: Nil + )) + )(VehicleFormat.Normal) + } + + def vulture(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + Some(InventoryData( + InventoryItemData(ObjectClass.vulture_nose_weapon_system, weapon1_guid, 3, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_35mm, ammo1_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.vulture_bomb_bay, weapon2_guid, 4, + WeaponData(0x6, 0x8, 0, ObjectClass.liberator_bomb, ammo2_guid, 0, AmmoBoxData(8)) + ) :: + InventoryItemData(ObjectClass.vulture_tail_cannon, weapon3_guid, 5, + WeaponData(0x6, 0x8, 0, ObjectClass.bullet_25mm, ammo3_guid, 0, AmmoBoxData(8)) + ) :: Nil + )) + )(VehicleFormat.Variant) + } + + def wasp(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { + VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.Mobile, false, false, false, Some(VariantVehicleData(0)), + Some(InventoryData( + InventoryItemData(ObjectClass.wasp_weapon_system, weapon_guid, 1, + WeaponData(0x6, 0x8, 0, ObjectClass.wasp_gun_ammo, ammo1_guid, 0, AmmoBoxData(8), ObjectClass.wasp_rocket_ammo, ammo2_guid, 0, AmmoBoxData(8)) + ) :: Nil + )) + )(VehicleFormat.Variant) } } } diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/Vehicle2Data.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/Vehicle2Data.scala deleted file mode 100644 index c338cc02..00000000 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/Vehicle2Data.scala +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.packet.game.objectcreate - -import net.psforever.packet.Marshallable -import net.psforever.packet.game.objectcreate.MountItem.MountItem -import scodec.codecs._ -import scodec.{Attempt, Codec, Err} -import shapeless.{::, HNil} - -/** - * A representation of a generic vehicle, with optional mounted weapons. - * This data will help construct vehicular options such as the Switchblade and the Mosquito. - * @param basic data common to objects - * @param unk1 na - * @param health the amount of health the vehicle has, as a percentage of a filled bar - * @param unk2 na - * @param driveState the drivable condition - * @param unk4 na - * @param unk5 na - * @param mountings data regarding the mounted utilities, usually weapons - * @param mount_capacity implicit; - * the total number of mounted utilities allowed on this vehicle; - * defaults to 1; - * -1 or less ignores the imposed checks - * @see `VehicleData` - */ -final case class Vehicle2Data(basic : CommonFieldData, - unk1 : Int, - health : Int, - unk2 : Int, - driveState : DriveState.Value, - unk4 : Boolean, - unk5 : Int, - unk6 : Int, - mountings : Option[List[MountItem]] = None - )(implicit val mount_capacity : Int = 1) extends ConstructorData { - override def bitsize : Long = { - val basicSize = basic.bitsize - val mountSize = if(mountings.isDefined) { - var bSize : Long = 0L - for(item <- mountings.get) { - bSize += item.bitsize - } - 10 + bSize - } - else { - 0L - } - 11L + VehicleData.baseVehicleSize + basicSize + mountSize - } -} - -object Vehicle2Data extends Marshallable[Vehicle2Data] { - /** - * Overloaded constructor that mandates information about a single weapon mount. - * @param basic data common to objects - * @param health the amount of health the object has, as a percentage of a filled bar - * @param mount data regarding the mounted weapon - * @return a `Vehicle2Data` object - */ - def apply(basic : CommonFieldData, health : Int, mount : MountItem) : Vehicle2Data = - Vehicle2Data(basic, 0, health, 0, DriveState.Mobile, false, 0, 0, Some(mount :: Nil)) - - /** - * Overloaded constructor that mandates information about a single weapon mount and deployment state. - * @param basic data common to objects - * @param health the amount of health the object has, as a percentage of a filled bar - * @param driveState the drivable condition - * @param mount data regarding the mounted weapon - * @return a `Vehicle2Data` object - */ - def apply(basic : CommonFieldData, health : Int, driveState : DriveState.Value, mount : MountItem) : Vehicle2Data = - Vehicle2Data(basic, 0, health, 0, driveState, false, 0, 0, Some(mount :: Nil)) - - /** - * A `Codec` for `Vehicle2Data`. - * @param mount_capacity the total number of mounted weapons that are attached to this vehicle; - * defaults to 1 - * @param mountCheck implicit; - * an evaluation of the provided `List` of objects; - * a function that takes an object and returns `true` if the object passed its defined test; - * defaults to `onlyWeapons` - * @return a `VehicleData` object or a `BitVector` - */ - def codec(mount_capacity : Int = 1)(implicit mountCheck : (List[MountItem]) => Boolean = VehicleData.onlyWeapons) : Codec[Vehicle2Data] = ( - VehicleData.basic_vehicle_codec :+ - uint8L :+ - uint2L :+ - optional(bool, "mountings" | VehicleData.mountedUtilitiesCodec(mountCheck)) - ).exmap[Vehicle2Data] ( - { - case basic :: u1 :: health :: u2 :: driveState :: u4 :: u5 :: u6 :: mountings :: HNil => - val onboardMountCount : Int = if(mountings.isDefined) { mountings.get.size } else { 0 } - if(mount_capacity > -1 && mount_capacity != onboardMountCount) { - Attempt.failure(Err(s"vehicle decodes wrong number of mounts - actual $onboardMountCount, expected $mount_capacity")) - } - else { - Attempt.successful(Vehicle2Data(basic, u1, health, u2, driveState, u4, u5, u6, mountings)(onboardMountCount)) - } - - case _ => - Attempt.failure(Err("invalid vehicle data format")) - }, - { - case obj @ Vehicle2Data(basic, u1, health, u2, driveState, u4, u5, u6, mountings) => - val objMountCapacity = obj.mount_capacity - if(objMountCapacity < 0 || mount_capacity < 0) { - Attempt.successful(basic :: u1 :: health :: u2 :: driveState :: u4 :: u5 :: u6 :: mountings :: HNil) - } - else { - val onboardMountCount : Int = if(mountings.isDefined) { mountings.get.size } else { 0 } - if(mount_capacity != objMountCapacity) { - Attempt.failure(Err(s"different encoding expectations for amount of mounts - actual $objMountCapacity, expected $mount_capacity")) - } - else if(mount_capacity != onboardMountCount) { - Attempt.failure(Err(s"vehicle encodes wrong number of mounts - actual $onboardMountCount, expected $mount_capacity")) - } - else { - Attempt.successful(basic :: u1 :: health :: u2 :: driveState :: u4 :: u5 :: u6 :: mountings :: HNil) - } - } - - case _ => - Attempt.failure(Err("invalid vehicle data format")) - } - ) - - implicit val codec : Codec[Vehicle2Data] = codec() -} diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala index 950e5e6f..8292a5b2 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala @@ -1,15 +1,51 @@ // Copyright (c) 2017 PSForever package net.psforever.packet.game.objectcreate - import net.psforever.packet.Marshallable -import net.psforever.packet.game.objectcreate.MountItem.MountItem -import scodec.codecs._ +import scodec.Attempt.{Failure, Successful} import scodec.{Attempt, Codec, Err} +import scodec.codecs._ import shapeless.{::, HNil} /** - * A representation of a generic vehicle, with optional mounted weapons. - * This data will help construct most of the game's vehicular options such as the Lightning and the Harasser.
+ * An `Enumeration` of the various formats that known structures that the stream of bits for `VehicleData` can assume. + */ +object VehicleFormat extends Enumeration { + type Type = Value + + val + Battleframe, //future expansion? + Normal, + Utility, + Variant = Value +} + +/** + * A basic `Trait` connecting all of the vehicle data formats (excepting `Normal`/`None`). + */ +sealed trait SpecificVehicleData extends StreamBitSize + +/** + * The format of vehicle data for the type of vehicles that are considered "utility." + * The vehicles in this category are two: + * the advanced nanite transport, and + * the advanced mobile station. + * @param unk na + */ +final case class UtilityVehicleData(unk : Int) extends SpecificVehicleData { + override def bitsize : Long = 6L +} + +/** + * A common format variant of vehicle data. + * This category includes all flying vehicles and the ancient cavern vehicles. + * @param unk na + */ +final case class VariantVehicleData(unk : Int) extends SpecificVehicleData { + override def bitsize : Long = 8L +} + +/** + * A representation of a generic vehicle.
*
* Vehicles utilize their own packet to communicate position to the server, known as `VehicleStateMessage`. * This takes the place of `PlayerStateMessageUpstream` when the player avatar is in control; @@ -17,186 +53,174 @@ import shapeless.{::, HNil} * If the vehicle is sufficiently complicated, a `ChildObjectStateMessage` will be used. * This packet will control any turret(s) on the vehicle. * For very complicated vehicles, the packets `FrameVehicleStateMessage` and `VehicleSubStateMessage` will also be employed. - * The tasks that these packets perform are different based on the vehicle that responds or generates them.
- *
- * Vehicles have a variety of features. - * They have their own inventory space, seating space for driver and passengers, Infantry mounting positions for the former two, and weapon mounting positions. - * Specialized vehicles also have terminals attached to them. - * The trunk is little different from player character inventories save for capacity and that it must be manually accessed. - * It is usually on the rear of the vehicle if that vehicle has a trunk at all. - * Weapons and infantry are allocated mounting slots from the same list. - * Weapons are constructed in their given slot with the vehicle itself and Infantry sit aside the weapons. - * Certain slots ("seats") allow control of one of the weapons in another slot ("weapon mounting"). - * ("Seat" and "weapon mounting" do not coincide numerically.) - * For trunk and for Infantry slots, various glyphs are projected onto the ground, called "mounting positions." - * Standing nearly on top of the glyph and facing the vehicle allows access or seat-taking. - * ("Seat" and "mounting positions" will not necessarily coincide numerically either.)
- *
- * Outside of managing mounted weaponry, any vehicle with special "utilities" must be handled as a special case. - * Utilities are plastered onto the chassis and carried around with the vehicle. - * Some vehicles have to go through a sessile physical conversion known as "deploying" to get access to their utilities.
- *
- * An "expected" number of mounting data can be passed into the class for the purposes of validating input. + * The tasks that these packets perform are different based on the vehicle that responds or generates them. * @param basic data common to objects * @param unk1 na - * @param health the amount of health the vehicle has, as a percentage of a filled bar + * @param health the amount of health the vehicle has, as a percentage of a filled bar (255) * @param unk2 na - * @param driveState the drivable condition + * @param no_mount_points do not display entry points for the seats + * @param driveState a representation for the current mobility state; + * various vehicles also use this field to indicate "deployment," e.g., AMS + * @param unk3 na + * @param unk5 na + * @param cloak if a cloakable vehicle is cloaked * @param unk4 na - * @param unk5 na; - * 1 causes the `quadstealth` (Wraith) to cloak - * @param mountings data regarding the mounted utilities, usually weapons - * @param mount_capacity implicit; - * the total number of mounted utilities allowed on this vehicle; - * defaults to 1; - * -1 or less ignores the imposed checks - * @see `Vehicle2Data` + * @param inventory the seats, mounted weapons, and utilities (such as terminals) that are currently included; + * will also include trunk contents + * @param vehicle_type a modifier for parsing the vehicle data format differently; + * defaults to `Normal` */ final case class VehicleData(basic : CommonFieldData, unk1 : Int, health : Int, - unk2 : Int, + unk2 : Boolean, + no_mount_points : Boolean, driveState : DriveState.Value, - unk4 : Boolean, - unk5 : Int, - mountings : Option[List[MountItem]] = None - )(implicit val mount_capacity : Int = 1) extends ConstructorData { + unk3 : Boolean, + unk5 : Boolean, + cloak : Boolean, + unk4 : Option[SpecificVehicleData], + inventory : Option[InventoryData] = None + )(val vehicle_type : VehicleFormat.Value = VehicleFormat.Normal) extends ConstructorData { override def bitsize : Long = { val basicSize = basic.bitsize - val mountSize = if(mountings.isDefined) { - var bSize : Long = 0L - for(item <- mountings.get) { - bSize += item.bitsize - } - 10 + bSize - } - else { - 0L - } - 3L + VehicleData.baseVehicleSize + basicSize + mountSize + val extraBitsSize : Long = if(unk4.isDefined) { unk4.get.bitsize } else { 0L } + val inventorySize = if(inventory.isDefined) { inventory.get.bitsize } else { 0L } + 24L + basicSize + extraBitsSize + inventorySize } } object VehicleData extends Marshallable[VehicleData] { - val baseVehicleSize : Long = 21L //2u + 8u + 2u + 8u + 1u - /** - * Overloaded constructor that mandates information about a single weapon mount. + * Overloaded constructor for specifically handling `Normal` vehicle format. * @param basic data common to objects - * @param health the amount of health the object has, as a percentage of a filled bar - * @param mount data regarding the mounted weapon + * @param unk1 na + * @param health the amount of health the vehicle has, as a percentage of a filled bar (255) + * @param unk2 na + * @param driveState a representation for the current mobility state; + * @param unk3 na + * @param unk4 na + * @param inventory the seats, mounted weapons, and utilities (such as terminals) that are currently included * @return a `VehicleData` object */ - def apply(basic : CommonFieldData, health : Int, mount : MountItem) : VehicleData = - VehicleData(basic, 0, health, 0, DriveState.Mobile, false, 0, Some(mount :: Nil)) + def apply(basic : CommonFieldData, unk1 : Int, health : Int, unk2 : Int, driveState : DriveState.Value, unk3 : Boolean, unk4 : Int, inventory : Option[InventoryData]) : VehicleData = { + new VehicleData(basic, unk1, health, unk2>0, false, driveState, unk3, unk4>0, false, None, inventory)(VehicleFormat.Normal) + } /** - * Overloaded constructor that mandates information about a single weapon mount and deployment state. + * Overloaded constructor for specifically handling `Utility` vehicle format. * @param basic data common to objects - * @param health the amount of health the object has, as a percentage of a filled bar - * @param driveState the drivable condition - * @param mount data regarding the mounted weapon - * @return a `Vehicle2Data` object + * @param unk1 na + * @param health the amount of health the vehicle has, as a percentage of a filled bar (255) + * @param unk2 na + * @param driveState a representation for the current mobility state; + * @param unk3 na + * @param unk4 utility-specific field + * @param unk5 na + * @param inventory the seats, mounted weapons, and utilities (such as terminals) that are currently included + * @return a `VehicleData` object */ - def apply(basic : CommonFieldData, health : Int, driveState : DriveState.Value, mount : MountItem) : VehicleData = - VehicleData(basic, 0, health, 0, driveState, false, 0, Some(mount :: Nil)) + def apply(basic : CommonFieldData, unk1 : Int, health : Int, unk2 : Int, driveState : DriveState.Value, unk3 : Boolean, unk4 : UtilityVehicleData, unk5 : Int, inventory : Option[InventoryData]) : VehicleData = { + new VehicleData(basic, unk1, health, unk2>0, false, driveState, unk3, unk5>0, false, Some(unk4), inventory)(VehicleFormat.Utility) + } /** - * A `Codec` for mounted utilities, generally weapons (as `WeaponData`). - * @param mountCheck a function that takes a `List` of `InternalSlot` objects and returns `true` if those objects passed its test - * @return a `List` of mounted objects or a `BitVector` of the same - * @see `InventoryData` + * Overloaded constructor for specifically handling `Variant` vehicle format. + * @param basic data common to objects + * @param unk1 na + * @param health the amount of health the vehicle has, as a percentage of a filled bar (255) + * @param unk2 na + * @param driveState a representation for the current mobility state; + * @param unk3 na + * @param unk4 variant-specific field + * @param unk5 na + * @param inventory the seats, mounted weapons, and utilities (such as terminals) that are currently included + * @return a `VehicleData` object */ - def mountedUtilitiesCodec(mountCheck : (List[MountItem]) => Boolean) : Codec[List[MountItem]] = - InventoryData.codec(MountItem.codec).exmap[List[MountItem]] ( - { - case InventoryData(list) => - if(!mountCheck(list)) { - Attempt.failure(Err("vehicle mount decoding is disallowed by test failure")) - } - else { - Attempt.successful(list) - } - - case _ => - Attempt.failure(Err("invalid mounting data format")) - }, - { - case list => - if(list.size > 255) { - Attempt.failure(Err("vehicle encodes too many weapon mountings (255+ objects!)")) - } - else if(!mountCheck(list)) { - Attempt.failure(Err("vehicle mount encoding is disallowed by test failure")) - } - else { - Attempt.successful(InventoryData(list)) - } - } - ) + def apply(basic : CommonFieldData, unk1 : Int, health : Int, unk2 : Int, driveState : DriveState.Value, unk3 : Boolean, unk4 : VariantVehicleData, unk5 : Int, inventory : Option[InventoryData]) : VehicleData = { + new VehicleData(basic, unk1, health, unk2>0, false, driveState, unk3, unk5>0, false, Some(unk4), inventory)(VehicleFormat.Variant) + } /** - * These values are parsed by all vehicles. - * Comments about the fields are provided where helpful. + * `Codec` for the "utility" format. */ - val basic_vehicle_codec : Codec[CommonFieldData :: Int :: Int :: Int :: DriveState.Value :: Boolean :: HNil] = ( - CommonFieldData.codec :: //not certain if player_guid is valid - uint2L :: //often paired with the assumed 16u field in previous? - uint8L :: //usually "health" - uint2L :: //usually 0; second bit turns off vehicle seat entry points - DriveState.codec :: //special field (AMS and ANT use for deploy state) - bool //unknown but generally false; can cause stream misalignment if set when unexpected - ).as[CommonFieldData :: Int :: Int :: Int :: DriveState.Value :: Boolean :: HNil] - - /** - * Perform an evaluation of the provided object. - * @param list a List of objects to be compared against some criteria - * @return `true`, if the objects pass this test; false, otherwise - */ - def onlyWeapons(list : List[MountItem]) : Boolean = !list.exists(!_.obj.isInstanceOf[WeaponData]) - - /** - * A `Codec` for `VehicleData`. - * @param mount_capacity the total number of mounted weapons that are attached to this vehicle; - * defaults to 1 - * @param mountCheck implicit; - * an evaluation of the provided `List` of objects; - * a function that takes an object and returns `true` if the object passed its defined test; - * defaults to `onlyWeapons` - * @return a `VehicleData` object or a `BitVector` - */ - def codec(mount_capacity : Int = 1)(implicit mountCheck : (List[MountItem]) => Boolean = onlyWeapons) : Codec[VehicleData] = ( - basic_vehicle_codec :+ - uint2L :+ - optional(bool, "mountings" | mountedUtilitiesCodec(mountCheck)) - ).exmap[VehicleData] ( + private val utility_data_codec : Codec[SpecificVehicleData] = uintL(6).hlist.exmap[SpecificVehicleData] ( { - case basic :: u1 :: health :: u2 :: driveState :: u4 :: u5 :: mountings :: HNil => - val onboardMountCount : Int = if(mountings.isDefined) { mountings.get.size } else { 0 } - if(mount_capacity > -1 && mount_capacity != onboardMountCount) { - Attempt.failure(Err(s"vehicle decodes wrong number of mounts - actual $onboardMountCount, expected $mount_capacity")) - } - else { - Attempt.successful(VehicleData(basic, u1, health, u2, driveState, u4, u5, mountings)(onboardMountCount)) - } + case n :: HNil => + Successful(UtilityVehicleData(n).asInstanceOf[SpecificVehicleData]) }, { - case obj @ VehicleData(basic, u1, health, u2, driveState, u4, u5, mountings) => - val objMountCapacity = obj.mount_capacity - if(objMountCapacity < 0 || mount_capacity < 0) { - Attempt.successful(basic :: u1 :: health :: u2 :: driveState :: u4 :: u5 :: mountings :: HNil) + case UtilityVehicleData(n) => + Successful(n :: HNil) + case _ => + Failure(Err("wrong kind of vehicle data object (wants 'Utility')")) + } + ) + /** + * `Codec` for the "variant" format. + */ + private val variant_data_codec : Codec[SpecificVehicleData] = uint8L.hlist.exmap[SpecificVehicleData] ( + { + case n :: HNil => + Successful(VariantVehicleData(n).asInstanceOf[SpecificVehicleData]) + }, + { + case VariantVehicleData(n) => + Successful(n :: HNil) + case _ => + Failure(Err("wrong kind of vehicle data object (wants 'Variant')")) + } + ) + + /** + * Select an appropriate `Codec` in response to the requested stream format + * @param vehicleFormat the requested format + * @return the appropriate `Codec` for parsing that format + */ + private def selectFormatReader(vehicleFormat : VehicleFormat.Value) : Codec[SpecificVehicleData] = vehicleFormat match { + case VehicleFormat.Utility => + utility_data_codec + case VehicleFormat.Variant => + variant_data_codec + case _ => + Failure(Err(s"$vehicleFormat is not a valid vehicle format for parsing data")).asInstanceOf[Codec[SpecificVehicleData]] + } + + def codec(vehicle_type : VehicleFormat.Value) : Codec[VehicleData] = ( + ("basic" | CommonFieldData.codec) :: + ("unk1" | uint2L) :: + ("health" | uint8L) :: + ("unk2" | bool) :: //usually 0 + ("no_mount_points" | bool) :: + ("driveState" | DriveState.codec) :: //used for deploy state + ("unk3" | bool) :: //unknown but generally false; can cause stream misalignment if set when unexpectedly + ("unk4" | bool) :: + ("cloak" | bool) :: //cloak as wraith, phantasm + conditional(vehicle_type != VehicleFormat.Normal, "unk5" | selectFormatReader(vehicle_type)) :: //padding? + optional(bool, "inventory" | InventoryData.codec) + ).exmap[VehicleData] ( + { + case basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: u5 :: cloak :: inv :: HNil => + Attempt.successful(new VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, u5, cloak, inv)(vehicle_type)) + + case _ => + Attempt.failure(Err("invalid vehicle data format")) + }, + { + case obj @ VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, cloak, Some(u5), inv) => + if(obj.vehicle_type == VehicleFormat.Normal) { + Attempt.failure(Err("invalid vehicle data format; variable bits not expected; will ignore ...")) } else { - val onboardMountCount : Int = if(mountings.isDefined) { mountings.get.size } else { 0 } - if(mount_capacity != objMountCapacity) { - Attempt.failure(Err(s"different encoding expectations for amount of mounts - actual $objMountCapacity, expected $mount_capacity")) - } - else if(mount_capacity != onboardMountCount) { - Attempt.failure(Err(s"vehicle encodes wrong number of mounts - actual $onboardMountCount, expected $mount_capacity")) - } - else { - Attempt.successful(basic :: u1 :: health :: u2 :: driveState :: u4 :: u5 :: mountings :: HNil) - } + Attempt.successful(basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: cloak :: Some(u5) :: inv :: HNil) + } + + case obj @ VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, cloak, None, inv) => + if(obj.vehicle_type != VehicleFormat.Normal) { + Attempt.failure(Err("invalid vehicle data format; variable bits expected")) + } + else { + Attempt.successful(basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: cloak :: None :: inv :: HNil) } case _ => @@ -204,5 +228,5 @@ object VehicleData extends Marshallable[VehicleData] { } ) - implicit val codec : Codec[VehicleData] = codec() + implicit val codec : Codec[VehicleData] = codec(VehicleFormat.Normal) } diff --git a/common/src/test/scala/game/ObjectCreateMessageTest.scala b/common/src/test/scala/game/ObjectCreateMessageTest.scala index 76b8669e..b7795746 100644 --- a/common/src/test/scala/game/ObjectCreateMessageTest.scala +++ b/common/src/test/scala/game/ObjectCreateMessageTest.scala @@ -470,7 +470,8 @@ class ObjectCreateMessageTest extends Specification { turret.deploy.pos.orient.y mustEqual 2.8125f turret.deploy.pos.orient.z mustEqual 264.375f turret.deploy.faction mustEqual PlanetSideEmpire.NC - turret.deploy.unk mustEqual 12 + turret.deploy.destroyed mustEqual true + turret.deploy.unk mustEqual 2 turret.deploy.player_guid mustEqual PlanetSideGUID(3871) turret.health mustEqual 0 turret.internals.isDefined mustEqual false @@ -496,7 +497,7 @@ class ObjectCreateMessageTest extends Specification { turret.deploy.pos.orient.y mustEqual 0f turret.deploy.pos.orient.z mustEqual 154.6875f turret.deploy.faction mustEqual PlanetSideEmpire.VS - turret.deploy.unk mustEqual 4 + turret.deploy.unk mustEqual 2 turret.deploy.player_guid mustEqual PlanetSideGUID(4232) turret.health mustEqual 255 turret.internals.isDefined mustEqual true @@ -537,7 +538,7 @@ class ObjectCreateMessageTest extends Specification { trap.deploy.pos.orient.y mustEqual 0f trap.deploy.pos.orient.z mustEqual 90.0f trap.deploy.faction mustEqual PlanetSideEmpire.VS - trap.deploy.unk mustEqual 4 + trap.deploy.unk mustEqual 2 trap.health mustEqual 255 trap.deploy.player_guid mustEqual PlanetSideGUID(2502) case _ => @@ -562,7 +563,7 @@ class ObjectCreateMessageTest extends Specification { aegis.deploy.pos.orient.y mustEqual 0f aegis.deploy.pos.orient.z mustEqual 90.0f aegis.deploy.faction mustEqual PlanetSideEmpire.VS - aegis.deploy.unk mustEqual 4 + aegis.deploy.unk mustEqual 2 aegis.health mustEqual 255 aegis.deploy.player_guid mustEqual PlanetSideGUID(2366) case _ => @@ -587,7 +588,7 @@ class ObjectCreateMessageTest extends Specification { omft.deploy.pos.orient.y mustEqual 0f omft.deploy.pos.orient.z mustEqual 185.625f omft.deploy.faction mustEqual PlanetSideEmpire.VS - omft.deploy.unk mustEqual 4 + omft.deploy.unk mustEqual 2 omft.deploy.player_guid mustEqual PlanetSideGUID(2502) omft.health mustEqual 255 omft.internals.isDefined mustEqual true @@ -989,7 +990,7 @@ class ObjectCreateMessageTest extends Specification { val obj = SmallTurretData( CommonFieldData( PlacementData(4577.7812f, 5624.828f, 72.046875f, 0f, 2.8125f, 264.375f), - PlanetSideEmpire.NC, 12, PlanetSideGUID(3871) + PlanetSideEmpire.NC, true, 2, PlanetSideGUID(3871) ), 255 //sets to 0 ) @@ -1007,7 +1008,7 @@ class ObjectCreateMessageTest extends Specification { val obj = SmallTurretData( CommonFieldData( PlacementData(4527.633f, 6271.3594f, 70.265625f, 0f, 0f, 154.6875f), - PlanetSideEmpire.VS, 4, PlanetSideGUID(4232) + PlanetSideEmpire.VS, 2, PlanetSideGUID(4232) ), 255, SmallTurretData.spitfire(PlanetSideGUID(3064), 0x6, 0x8, PlanetSideGUID(3694), 8) @@ -1026,7 +1027,7 @@ class ObjectCreateMessageTest extends Specification { val obj = TRAPData( CommonFieldData( PlacementData(3572.4453f, 3277.9766f, 114.0f, 0f, 0f, 90.0f), - PlanetSideEmpire.VS, 4, PlanetSideGUID(2502) + PlanetSideEmpire.VS, 2, PlanetSideGUID(2502) ), 255 ) @@ -1044,7 +1045,7 @@ class ObjectCreateMessageTest extends Specification { val obj = AegisShieldGeneratorData( CommonFieldData( PlacementData(3571.2266f, 3278.0938f, 114.0f, 0f, 0f, 90.0f), - PlanetSideEmpire.VS, 4, PlanetSideGUID(2366) + PlanetSideEmpire.VS, 2, PlanetSideGUID(2366) ), 255 ) @@ -1058,7 +1059,7 @@ class ObjectCreateMessageTest extends Specification { val obj = OneMannedFieldTurretData( CommonFieldData( PlacementData(3567.1406f, 2988.0078f, 71.84375f, 0f, 0f, 185.625f), - PlanetSideEmpire.VS, 4, PlanetSideGUID(2502) + PlanetSideEmpire.VS, 2, PlanetSideGUID(2502) ), 255, OneMannedFieldTurretData.orion(PlanetSideGUID(2615), 0x6, 0x8, PlanetSideGUID(2510), 8) diff --git a/common/src/test/scala/game/ObjectCreateMessageVehiclesTest.scala b/common/src/test/scala/game/ObjectCreateMessageVehiclesTest.scala index cd66e598..11cca6b2 100644 --- a/common/src/test/scala/game/ObjectCreateMessageVehiclesTest.scala +++ b/common/src/test/scala/game/ObjectCreateMessageVehiclesTest.scala @@ -38,13 +38,13 @@ class ObjectCreateMessageVehiclesTest extends Specification { fury.basic.pos.orient.z mustEqual 357.1875f fury.basic.pos.vel.isDefined mustEqual false fury.basic.faction mustEqual PlanetSideEmpire.VS - fury.basic.unk mustEqual 4 + fury.basic.unk mustEqual 2 fury.basic.player_guid mustEqual PlanetSideGUID(0) fury.health mustEqual 255 // - fury.mountings.isDefined mustEqual true - fury.mountings.get.size mustEqual 1 - val mounting = fury.mountings.get.head + fury.inventory.isDefined mustEqual true + fury.inventory.get.contents.size mustEqual 1 + val mounting = fury.inventory.get.contents.head mounting.objectClass mustEqual ObjectClass.fury_weapon_systema mounting.guid mustEqual PlanetSideGUID(400) mounting.parentSlot mustEqual 1 @@ -73,8 +73,8 @@ class ObjectCreateMessageVehiclesTest extends Specification { guid mustEqual PlanetSideGUID(380) parent.isDefined mustEqual false data.isDefined mustEqual true - data.get.isInstanceOf[ANTData] mustEqual true - val ant = data.get.asInstanceOf[ANTData] + data.get.isInstanceOf[VehicleData] mustEqual true + val ant = data.get.asInstanceOf[VehicleData] ant.basic.pos.coord.x mustEqual 3674.8438f ant.basic.pos.coord.y mustEqual 2726.789f ant.basic.pos.coord.z mustEqual 91.15625f @@ -82,7 +82,7 @@ class ObjectCreateMessageVehiclesTest extends Specification { ant.basic.pos.orient.y mustEqual 0f ant.basic.pos.orient.z mustEqual 90.0f ant.basic.faction mustEqual PlanetSideEmpire.VS - ant.basic.unk mustEqual 4 + ant.basic.unk mustEqual 2 ant.basic.player_guid mustEqual PlanetSideGUID(0) ant.health mustEqual 255 ant.driveState mustEqual DriveState.Mobile @@ -108,12 +108,12 @@ class ObjectCreateMessageVehiclesTest extends Specification { lightning.basic.pos.orient.y mustEqual 0f lightning.basic.pos.orient.z mustEqual 90.0f lightning.basic.faction mustEqual PlanetSideEmpire.VS - lightning.basic.unk mustEqual 4 + lightning.basic.unk mustEqual 2 lightning.basic.player_guid mustEqual PlanetSideGUID(0) lightning.health mustEqual 255 - lightning.mountings.isDefined mustEqual true - lightning.mountings.get.size mustEqual 1 - val mounting = lightning.mountings.get.head + lightning.inventory.isDefined mustEqual true + lightning.inventory.get.contents.size mustEqual 1 + val mounting = lightning.inventory.get.contents.head mounting.objectClass mustEqual ObjectClass.lightning_weapon_system mounting.guid mustEqual PlanetSideGUID(91) mounting.parentSlot mustEqual 1 @@ -159,18 +159,19 @@ class ObjectCreateMessageVehiclesTest extends Specification { deliverer.basic.pos.orient.y mustEqual 0f deliverer.basic.pos.orient.z mustEqual 357.1875f deliverer.basic.faction mustEqual PlanetSideEmpire.NC - deliverer.basic.unk mustEqual 4 + deliverer.basic.unk mustEqual 2 deliverer.basic.player_guid mustEqual PlanetSideGUID(0) deliverer.unk1 mustEqual 0 deliverer.health mustEqual 255 - deliverer.unk2 mustEqual 0 + deliverer.unk2 mustEqual false deliverer.driveState mustEqual DriveState.State7 - deliverer.unk4 mustEqual true - deliverer.unk5 mustEqual 0 - deliverer.mountings.isDefined mustEqual true - deliverer.mountings.get.size mustEqual 2 + deliverer.unk3 mustEqual true + deliverer.unk4 mustEqual None + deliverer.unk5 mustEqual false + deliverer.inventory.isDefined mustEqual true + deliverer.inventory.get.contents.size mustEqual 2 //0 - var mounting = deliverer.mountings.get.head + var mounting = deliverer.inventory.get.contents.head mounting.objectClass mustEqual ObjectClass.mediumtransport_weapon_systemA mounting.guid mustEqual PlanetSideGUID(383) mounting.parentSlot mustEqual 5 @@ -187,7 +188,7 @@ class ObjectCreateMessageVehiclesTest extends Specification { ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x8 //1 - mounting = deliverer.mountings.get(1) + mounting = deliverer.inventory.get.contents(1) mounting.objectClass mustEqual ObjectClass.mediumtransport_weapon_systemB mounting.guid mustEqual PlanetSideGUID(556) mounting.parentSlot mustEqual 6 @@ -216,8 +217,8 @@ class ObjectCreateMessageVehiclesTest extends Specification { guid mustEqual PlanetSideGUID(4157) parent.isDefined mustEqual false data.isDefined mustEqual true - data.get.isInstanceOf[AMSData] mustEqual true - val ams = data.get.asInstanceOf[AMSData] + data.get.isInstanceOf[VehicleData] mustEqual true + val ams = data.get.asInstanceOf[VehicleData] ams.basic.pos.coord.x mustEqual 3674.0f ams.basic.pos.coord.y mustEqual 2726.789f ams.basic.pos.coord.z mustEqual 91.15625f @@ -229,12 +230,27 @@ class ObjectCreateMessageVehiclesTest extends Specification { ams.basic.player_guid mustEqual PlanetSideGUID(34082) ams.unk1 mustEqual 2 ams.health mustEqual 236 - ams.unk2 mustEqual 0 + ams.unk2 mustEqual false ams.driveState mustEqual DriveState.Deployed - ams.matrix_guid mustEqual PlanetSideGUID(3663) - ams.respawn_guid mustEqual PlanetSideGUID(3638) - ams.term_a_guid mustEqual PlanetSideGUID(3827) - ams.term_b_guid mustEqual PlanetSideGUID(3556) + + ams.inventory.isDefined mustEqual true + val inv = ams.inventory.get.contents + inv.head.objectClass mustEqual ObjectClass.matrix_terminalc + inv.head.guid mustEqual PlanetSideGUID(3663) + inv.head.parentSlot mustEqual 1 + inv.head.obj.isInstanceOf[CommonTerminalData] mustEqual true + inv(1).objectClass mustEqual ObjectClass.ams_respawn_tube + inv(1).guid mustEqual PlanetSideGUID(3638) + inv(1).parentSlot mustEqual 2 + inv(1).obj.isInstanceOf[CommonTerminalData] mustEqual true + inv(2).objectClass mustEqual ObjectClass.order_terminala + inv(2).guid mustEqual PlanetSideGUID(3827) + inv(2).parentSlot mustEqual 3 + inv(2).obj.isInstanceOf[CommonTerminalData] mustEqual true + inv(3).objectClass mustEqual ObjectClass.order_terminalb + inv(3).guid mustEqual PlanetSideGUID(3556) + inv(3).parentSlot mustEqual 4 + inv(3).obj.isInstanceOf[CommonTerminalData] mustEqual true case _ => ko } @@ -269,8 +285,8 @@ class ObjectCreateMessageVehiclesTest extends Specification { guid mustEqual PlanetSideGUID(418) parent.isDefined mustEqual false data.isDefined mustEqual true - data.get.isInstanceOf[Vehicle2Data] mustEqual true - val switchblade = data.get.asInstanceOf[Vehicle2Data] + data.get.isInstanceOf[VehicleData] mustEqual true + val switchblade = data.get.asInstanceOf[VehicleData] switchblade.basic.pos.coord.x mustEqual 6531.961f switchblade.basic.pos.coord.y mustEqual 1872.1406f switchblade.basic.pos.coord.z mustEqual 24.734375f @@ -278,13 +294,13 @@ class ObjectCreateMessageVehiclesTest extends Specification { switchblade.basic.pos.orient.y mustEqual 0f switchblade.basic.pos.orient.z mustEqual 357.1875f switchblade.basic.faction mustEqual PlanetSideEmpire.VS - switchblade.basic.unk mustEqual 4 + switchblade.basic.unk mustEqual 2 switchblade.health mustEqual 255 switchblade.driveState mustEqual DriveState.Mobile - switchblade.mountings.isDefined mustEqual true - switchblade.mountings.get.size mustEqual 1 + switchblade.inventory.isDefined mustEqual true + switchblade.inventory.get.contents.size mustEqual 1 //0 - val weapon = switchblade.mountings.get.head + val weapon = switchblade.inventory.get.contents.head weapon.objectClass mustEqual ObjectClass.scythe weapon.guid mustEqual PlanetSideGUID(355) weapon.parentSlot mustEqual 1 @@ -324,7 +340,7 @@ class ObjectCreateMessageVehiclesTest extends Specification { droppod.basic.pos.orient.x mustEqual 0f droppod.basic.pos.orient.y mustEqual 0f droppod.basic.pos.orient.z mustEqual 90.0f - droppod.basic.unk mustEqual 4 + droppod.basic.unk mustEqual 2 droppod.basic.player_guid mustEqual PlanetSideGUID(0) droppod.burn mustEqual false droppod.health mustEqual 255 @@ -378,13 +394,20 @@ class ObjectCreateMessageVehiclesTest extends Specification { val obj = VehicleData( CommonFieldData( PlacementData(6531.961f, 1872.1406f, 24.734375f, 0f, 0f, 357.1875f), - PlanetSideEmpire.VS, 4 + PlanetSideEmpire.VS, 2 ), + 0, 255, - MountItem(ObjectClass.fury_weapon_systema, PlanetSideGUID(400), 1, - WeaponData(0x6, 0x8, 0, ObjectClass.hellfire_ammo, PlanetSideGUID(432), 0, AmmoBoxData(0x8)) - ) - ) + false, false, + DriveState.Mobile, + false, false, false, + None, + Some(InventoryData( + InventoryItemData(ObjectClass.fury_weapon_systema, PlanetSideGUID(400), 1, + WeaponData(0x6, 0x8, 0, ObjectClass.hellfire_ammo, PlanetSideGUID(432), 0, AmmoBoxData(0x8)) + ) :: Nil + )) + )(VehicleFormat.Normal) val msg = ObjectCreateMessage(ObjectClass.fury, PlanetSideGUID(413), obj) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector @@ -392,14 +415,19 @@ class ObjectCreateMessageVehiclesTest extends Specification { } "encode (ant)" in { - val obj = ANTData( + val obj = VehicleData( CommonFieldData( PlacementData(3674.8438f, 2726.789f, 91.15625f, 0f, 0f, 90.0f), - PlanetSideEmpire.VS, 4 + PlanetSideEmpire.VS, 2 ), + 0, 255, - DriveState.Mobile - ) + false, false, + DriveState.Mobile, + false, false, false, + Some(UtilityVehicleData(0)), + None + )(VehicleFormat.Utility) val msg = ObjectCreateMessage(ObjectClass.ant, PlanetSideGUID(380), obj) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector @@ -410,13 +438,20 @@ class ObjectCreateMessageVehiclesTest extends Specification { val obj = VehicleData( CommonFieldData( PlacementData(3674.8438f, 2726.789f, 91.15625f, 0f, 0f, 90.0f), - PlanetSideEmpire.VS, 4 + PlanetSideEmpire.VS, 2 ), + 0, 255, - MountItem(ObjectClass.lightning_weapon_system, PlanetSideGUID(91), 1, - WeaponData(4, 8, 0, ObjectClass.bullet_75mm, PlanetSideGUID(92), 0, AmmoBoxData(), ObjectClass.bullet_25mm, PlanetSideGUID(93), 1, AmmoBoxData()) - ) - ) + false, false, + DriveState.Mobile, + false, false, false, + None, + Some(InventoryData( + InventoryItemData(ObjectClass.lightning_weapon_system, PlanetSideGUID(91), 1, + WeaponData(4, 8, 0, ObjectClass.bullet_75mm, PlanetSideGUID(92), 0, AmmoBoxData(), ObjectClass.bullet_25mm, PlanetSideGUID(93), 1, AmmoBoxData()) + ) :: Nil + )) + )(VehicleFormat.Normal) val msg = ObjectCreateMessage(ObjectClass.lightning, PlanetSideGUID(90), obj) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector @@ -427,26 +462,23 @@ class ObjectCreateMessageVehiclesTest extends Specification { val obj = VehicleData( CommonFieldData( PlacementData(6531.961f, 1872.1406f, 24.734375f, 0f, 0f, 357.1875f), - PlanetSideEmpire.NC, 4 + PlanetSideEmpire.NC, 2 ), 0, 255, - 0, + false, false, DriveState.State7, - true, - 0, - Some( - MountItem( - ObjectClass.mediumtransport_weapon_systemA, PlanetSideGUID(383), 5, + true, false, false, + None, + Some(InventoryData( + InventoryItemData(ObjectClass.mediumtransport_weapon_systemA, PlanetSideGUID(383), 5, WeaponData(6, 8, ObjectClass.bullet_20mm, PlanetSideGUID(420), 0, AmmoBoxData(8)) ) :: - MountItem( - ObjectClass.mediumtransport_weapon_systemB, PlanetSideGUID(556), 6, + InventoryItemData(ObjectClass.mediumtransport_weapon_systemB, PlanetSideGUID(556), 6, WeaponData(6, 8, ObjectClass.bullet_20mm, PlanetSideGUID(575), 0, AmmoBoxData(8)) - ) :: - Nil - ) - )(2) + ) :: Nil + )) + )(VehicleFormat.Normal) val msg = ObjectCreateMessage(ObjectClass.mediumtransport, PlanetSideGUID(387), obj) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector @@ -454,7 +486,7 @@ class ObjectCreateMessageVehiclesTest extends Specification { } "encode (ams)" in { - val obj = AMSData( + val obj = VehicleData( CommonFieldData( PlacementData(3674.0f, 2726.789f, 91.15625f, 0f, 0f, 90.0f), PlanetSideEmpire.VS, 0, @@ -462,14 +494,17 @@ class ObjectCreateMessageVehiclesTest extends Specification { ), 2, 236, - 0, + false, false, DriveState.Deployed, - 63, - PlanetSideGUID(3663), - PlanetSideGUID(3638), - PlanetSideGUID(3827), - PlanetSideGUID(3556) - ) + false, true, true, + Some(UtilityVehicleData(60)), //what does this mean? + Some(InventoryData(List( + InternalSlot(ObjectClass.matrix_terminalc, PlanetSideGUID(3663), 1, CommonTerminalData(PlanetSideEmpire.VS)), + InternalSlot(ObjectClass.ams_respawn_tube, PlanetSideGUID(3638), 2, CommonTerminalData(PlanetSideEmpire.VS)), + InternalSlot(ObjectClass.order_terminala, PlanetSideGUID(3827), 3, CommonTerminalData(PlanetSideEmpire.VS)), + InternalSlot(ObjectClass.order_terminalb, PlanetSideGUID(3556), 4, CommonTerminalData(PlanetSideEmpire.VS)) + ))) + )(VehicleFormat.Utility) val msg = ObjectCreateMessage(ObjectClass.ams, PlanetSideGUID(4157), obj) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector @@ -485,18 +520,24 @@ class ObjectCreateMessageVehiclesTest extends Specification { } "encode (switchblade)" in { - val obj = Vehicle2Data( + val obj = VehicleData( CommonFieldData( PlacementData(6531.961f, 1872.1406f, 24.734375f, 0f, 0f, 357.1875f), PlanetSideEmpire.VS, - 4 + 2 ), + 0, 255, + false, false, DriveState.Mobile, - MountItem(ObjectClass.scythe, PlanetSideGUID(355), 1, - WeaponData(0x6, 0x8, 0, ObjectClass.ancient_ammo_vehicle, PlanetSideGUID(366), 0, AmmoBoxData(0x8), ObjectClass.ancient_ammo_vehicle, PlanetSideGUID(385), 1, AmmoBoxData(0x8)) - ) - ) + false, false, false, + Some(VariantVehicleData(0)), + Some(InventoryData( + InventoryItemData(ObjectClass.scythe, PlanetSideGUID(355), 1, + WeaponData(0x6, 0x8, 0, ObjectClass.ancient_ammo_vehicle, PlanetSideGUID(366), 0, AmmoBoxData(0x8), ObjectClass.ancient_ammo_vehicle, PlanetSideGUID(385), 1, AmmoBoxData(0x8)) + ) :: Nil + )) + )(VehicleFormat.Variant) val msg = ObjectCreateMessage(ObjectClass.switchblade, PlanetSideGUID(418), obj) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector @@ -508,7 +549,7 @@ class ObjectCreateMessageVehiclesTest extends Specification { CommonFieldData( PlacementData(5108.0f, 6164.0f, 1023.9844f, 0f, 0f, 90.0f), PlanetSideEmpire.VS, - 4 + 2 ) ) val msg = ObjectCreateMessage(ObjectClass.droppod, PlanetSideGUID(3595), obj) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 1c9cc53d..2d9dfadb 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -799,7 +799,6 @@ class WorldSessionActor extends Actor with MDCContextAware { ) ) }) - avatarService ! Service.Join(player.Continent) localService ! Service.Join(player.Continent) self ! SetCurrentAvatar(player) From 8f658aa68852b7d0c6e56e8cc65ebc05c182fffc Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 27 Oct 2017 18:44:20 -0400 Subject: [PATCH 2/4] test Harasser to demonstrate synched vehicle actions: mounting, disembarking, driving, gunning, changing access permissions, changing ownership, kicking passengers, deconstructing --- .../psforever/objects/GlobalDefinitions.scala | 71 +- .../scala/net/psforever/objects/Vehicle.scala | 238 ++++-- .../objects/definition/SeatDefinition.scala | 8 +- .../definition/VehicleDefinition.scala | 18 +- .../converter/VehicleConverter.scala | 72 +- .../objects/equipment/EquipmentSize.scala | 2 + .../inventory/AccessibleInventory.scala | 13 + .../terminals/CertTerminalDefinition.scala | 2 +- .../vehicles/AccessPermissionGroup.scala | 22 + .../net/psforever/objects/vehicles/Seat.scala | 76 +- .../vehicles/SeatArmorRestriction.scala | 3 +- .../objects/vehicles/VehicleControl.scala | 37 + .../objects/vehicles/VehicleLockState.scala | 8 +- .../net/psforever/objects/zones/Zone.scala | 45 +- .../objects/zones/ZoneVehicleActor.scala | 22 + .../game/PlanetsideAttributeMessage.scala | 16 +- .../game/objectcreate/ObjectClass.scala | 4 +- .../test/scala/objects/ConverterTest.scala | 3 +- .../src/test/scala/objects/VehicleTest.scala | 103 +++ pslogin/src/main/scala/PsLogin.scala | 3 + .../src/main/scala/WorldSessionActor.scala | 750 ++++++++++++------ pslogin/src/main/scala/scripts/GUIDTask.scala | 282 +++++++ .../scala/{ => services}/ServiceManager.scala | 2 + .../services/avatar/AvatarResponse.scala | 28 + .../scala/services/avatar/AvatarService.scala | 18 +- .../avatar/AvatarServiceResponse.scala | 28 +- .../scala/services/local/LocalResponse.scala | 15 + .../scala/services/local/LocalService.scala | 17 +- .../services/local/LocalServiceResponse.scala | 15 +- .../services/vehicle/VehicleAction.scala | 22 + .../services/vehicle/VehicleResponse.scala | 21 + .../services/vehicle/VehicleService.scala | 91 +++ .../vehicle/VehicleServiceMessage.scala | 13 + .../vehicle/VehicleServiceResponse.scala | 11 + .../vehicle/support/DeconstructionActor.scala | 181 +++++ .../vehicle/support/VehicleContextActor.scala | 30 + 36 files changed, 1820 insertions(+), 470 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/inventory/AccessibleInventory.scala create mode 100644 common/src/main/scala/net/psforever/objects/vehicles/AccessPermissionGroup.scala create mode 100644 common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala create mode 100644 common/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala create mode 100644 common/src/test/scala/objects/VehicleTest.scala create mode 100644 pslogin/src/main/scala/scripts/GUIDTask.scala rename pslogin/src/main/scala/{ => services}/ServiceManager.scala (99%) create mode 100644 pslogin/src/main/scala/services/avatar/AvatarResponse.scala create mode 100644 pslogin/src/main/scala/services/local/LocalResponse.scala create mode 100644 pslogin/src/main/scala/services/vehicle/VehicleAction.scala create mode 100644 pslogin/src/main/scala/services/vehicle/VehicleResponse.scala create mode 100644 pslogin/src/main/scala/services/vehicle/VehicleService.scala create mode 100644 pslogin/src/main/scala/services/vehicle/VehicleServiceMessage.scala create mode 100644 pslogin/src/main/scala/services/vehicle/VehicleServiceResponse.scala create mode 100644 pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala create mode 100644 pslogin/src/main/scala/services/vehicle/support/VehicleContextActor.scala diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 6d961e8f..917fc452 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -1228,17 +1228,86 @@ object GlobalDefinitions { fury_weapon_systema.FireModes.head.AmmoSlotIndex = 0 fury_weapon_systema.FireModes.head.Magazine = 2 + val + quadassault_weapon_system = ToolDefinition(ObjectClass.quadassault_weapon_system) + quadassault_weapon_system.Size = EquipmentSize.VehicleWeapon + quadassault_weapon_system.AmmoTypes += Ammo.bullet_12mm + quadassault_weapon_system.FireModes += new FireModeDefinition + quadassault_weapon_system.FireModes.head.AmmoTypeIndices += 0 + quadassault_weapon_system.FireModes.head.AmmoSlotIndex = 0 + quadassault_weapon_system.FireModes.head.Magazine = 100 + + val + chaingun_p = ToolDefinition(ObjectClass.chaingun_p) + chaingun_p.Size = EquipmentSize.VehicleWeapon + chaingun_p.AmmoTypes += Ammo.bullet_12mm + chaingun_p.FireModes += new FireModeDefinition + chaingun_p.FireModes.head.AmmoTypeIndices += 0 + chaingun_p.FireModes.head.AmmoSlotIndex = 0 + chaingun_p.FireModes.head.Magazine = 150 + val fury = VehicleDefinition(ObjectClass.fury) fury.Seats += 0 -> new SeatDefinition() fury.Seats(0).Bailable = true - fury.Seats(0).ControlledWeapon = Some(1) + fury.Seats(0).ControlledWeapon = 1 fury.MountPoints += 0 -> 0 fury.MountPoints += 2 -> 0 fury.Weapons += 1 -> fury_weapon_systema fury.TrunkSize = InventoryTile(11, 11) fury.TrunkOffset = 30 + val + quadassault = VehicleDefinition(ObjectClass.quadassault) + quadassault.Seats += 0 -> new SeatDefinition() + quadassault.Seats(0).Bailable = true + quadassault.Seats(0).ControlledWeapon = 1 + quadassault.MountPoints += 0 -> 0 + quadassault.MountPoints += 2 -> 0 + quadassault.Weapons += 1 -> quadassault_weapon_system + quadassault.TrunkSize = InventoryTile(11, 11) + quadassault.TrunkOffset = 30 + + val + quadstealth = VehicleDefinition(ObjectClass.quadstealth) + quadstealth.CanCloak = true + quadstealth.Seats += 0 -> new SeatDefinition() + quadstealth.Seats(0).Bailable = true + quadstealth.MountPoints += 0 -> 0 + quadstealth.MountPoints += 2 -> 0 + quadstealth.CanCloak = true + quadstealth.TrunkSize = InventoryTile(11, 11) + quadstealth.TrunkOffset = 30 + + val + two_man_assault_buggy = VehicleDefinition(ObjectClass.two_man_assault_buggy) + two_man_assault_buggy.Seats += 0 -> new SeatDefinition() + two_man_assault_buggy.Seats(0).Bailable = true + two_man_assault_buggy.Seats += 1 -> new SeatDefinition() + two_man_assault_buggy.Seats(1).Bailable = true + two_man_assault_buggy.Seats(1).ControlledWeapon = 2 + two_man_assault_buggy.MountPoints += 1 -> 0 + two_man_assault_buggy.MountPoints += 2 -> 1 + two_man_assault_buggy.Weapons += 2 -> chaingun_p + two_man_assault_buggy.TrunkSize = InventoryTile(11, 11) + two_man_assault_buggy.TrunkOffset = 30 + + val + phantasm = VehicleDefinition(ObjectClass.phantasm) + phantasm.CanCloak = true + phantasm.Seats += 0 -> new SeatDefinition() + phantasm.Seats += 1 -> new SeatDefinition() + phantasm.Seats(1).Bailable = true + phantasm.Seats += 2 -> new SeatDefinition() + phantasm.Seats(2).Bailable = true + phantasm.Seats += 3 -> new SeatDefinition() + phantasm.Seats(3).Bailable = true + phantasm.Seats += 4 -> new SeatDefinition() + phantasm.Seats(4).Bailable = true + phantasm.MountPoints += 1 -> 0 //TODO add and check all + phantasm.TrunkSize = InventoryTile(11, 8) + phantasm.TrunkOffset = 30 //TODO check + val order_terminal = new OrderTerminalDefinition val diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala index 206fc78f..ab1608b2 100644 --- a/common/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala @@ -4,11 +4,13 @@ package net.psforever.objects import net.psforever.objects.definition.VehicleDefinition import net.psforever.objects.equipment.{Equipment, EquipmentSize} import net.psforever.objects.inventory.GridInventory -import net.psforever.objects.vehicles.{Seat, Utility, VehicleLockState} +import net.psforever.objects.serverobject.PlanetSideServerObject +import net.psforever.objects.vehicles.{AccessPermissionGroup, Seat, Utility, VehicleLockState} import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.objectcreate.DriveState import net.psforever.types.PlanetSideEmpire +import scala.annotation.tailrec import scala.collection.mutable /** @@ -17,29 +19,31 @@ import scala.collection.mutable * All infantry seating, all mounted weapons, and the trunk space are considered part of the same index hierarchy. * Generally, all seating is declared first - the driver and passengers and and gunners. * Following that are the mounted weapons and other utilities. - * Trunk space starts being indexed afterwards. - * The first seat is always the op;erator (driver/pilot). - * "Passengers" are seats that are not the operator and are not in control of a mounted weapon. - * "Gunners" are seats that are not the operator and ARE in control of a mounted weapon. - * (The operator can be in control of a weapon - that is the whole point of a turret.)
+ * Trunk space starts being indexed afterwards.
*
- * Having said all that, to keep it simple, infantry seating, mounted weapons, and utilities are stored in separate `Map`s. - * @param vehicleDef the vehicle's definition entry' + * To keep it simple, infantry seating, mounted weapons, and utilities are stored separately. + * @param vehicleDef the vehicle's definition entry'; * stores and unloads pertinent information about the `Vehicle`'s configuration; * used in the initialization process (`loadVehicleDefinition`) */ -class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGameObject { +class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServerObject { private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR private var owner : Option[PlanetSideGUID] = None private var health : Int = 1 private var shields : Int = 0 private var deployed : DriveState.Value = DriveState.Mobile private var decal : Int = 0 - private var trunkLockState : VehicleLockState.Value = VehicleLockState.Locked private var trunkAccess : Option[PlanetSideGUID] = None + private var jammered : Boolean = false + private var cloaked : Boolean = false - private val seats : mutable.HashMap[Int, Seat] = mutable.HashMap() - private val weapons : mutable.HashMap[Int, EquipmentSlot] = mutable.HashMap() + /** + * Permissions control who gets to access different parts of the vehicle; + * the groups are Driver (seat), Gunner (seats), Passenger (seats), and the Trunk + */ + 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 val trunk : GridInventory = GridInventory() @@ -68,8 +72,15 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame } def Owner_=(owner : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = { - this.owner = owner - owner + owner match { + case Some(_) => + if(Definition.CanBeOwned) { + this.owner = owner + } + case None => + this.owner = None + } + Owner } def Health : Int = { @@ -98,15 +109,15 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame vehicleDef.MaxShields } - def Configuration : DriveState.Value = { + def Drive : DriveState.Value = { this.deployed } - def Configuration_=(deploy : DriveState.Value) : DriveState.Value = { + def Drive_=(deploy : DriveState.Value) : DriveState.Value = { if(vehicleDef.Deployment) { this.deployed = deploy } - Configuration + Drive } def Decal : Int = { @@ -118,6 +129,20 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame decal } + def Jammered : Boolean = jammered + + def Jammered_=(jamState : Boolean) : Boolean = { + jammered = jamState + Jammered + } + + def Cloaked : Boolean = cloaked + + def Cloaked_=(isCloaked : Boolean) : Boolean = { + cloaked = isCloaked + Cloaked + } + /** * Given the index of an entry mounting point, return the infantry-accessible `Seat` associated with it. * @param mountPoint an index representing the seat position / mounting point @@ -127,6 +152,60 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame vehicleDef.MountPoints.get(mountPoint) } + /** + * What are the access permissions for a position on this vehicle, seats or trunk? + * @param group the group index + * @return what sort of access permission exist for this group + */ + def PermissionGroup(group : Int) : Option[VehicleLockState.Value] = { + reindexPermissionsGroup(group) match { + case Some(index) => + Some(groupPermissions(index)) + case None => + None + } + } + + /** + * Change the access permissions for a position on this vehicle, seats or trunk. + * @param group the group index + * @param level the new permission for this group + * @return the new access permission for this group; + * `None`, if the group does not exist or the level of permission was not changed + */ + def PermissionGroup(group : Int, level : Long) : Option[VehicleLockState.Value] = { + reindexPermissionsGroup(group) match { + case Some(index) => + val current = groupPermissions(index) + val next = try { VehicleLockState(level.toInt) } catch { case _ : Exception => groupPermissions(index) } + if(current != next) { + groupPermissions(index) = next + PermissionGroup(index) + } + else { + None + } + case None => + None + } + } + + /** + * When the access permission group is communicated via `PlanetsideAttributeMessage`, the index is between 10 and 13. + * Internally, permission groups are stored as an `Array`, so the respective re-indexing plots 10 -> 0 and 13 -> 3. + * @param group the group index + * @return the modified group index + */ + private def reindexPermissionsGroup(group : Int) : Option[Int] = if(group > 9 && group < 14) { + Some(group - 10) + } + else if(group > -1 && group < 4) { + Some(group) + } + else { + None + } + /** * Get the seat at the index. * The specified "seat" can only accommodate a player as opposed to weapon mounts which share the same indexing system. @@ -146,7 +225,26 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame seats.values.toList } - def Weapons : mutable.HashMap[Int, EquipmentSlot] = weapons + def SeatPermissionGroup(seatNumber : Int) : Option[AccessPermissionGroup.Value] = { + if(seatNumber == 0) { + Some(AccessPermissionGroup.Driver) + } + else { + Seat(seatNumber) match { + case Some(seat) => + seat.ControlledWeapon match { + case Some(_) => + Some(AccessPermissionGroup.Gunner) + case None => + Some(AccessPermissionGroup.Passenger) + } + case None => + None + } + } + } + + def Weapons : Map[Int, EquipmentSlot] = weapons /** * Get the weapon at the index. @@ -164,20 +262,25 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame } /** - * Given a player who may be a passenger, retrieve an index where this player is seated. + * Given a player who may be an occupant, retrieve an number of the seat where this player is sat. * @param player the player - * @return a seat by index, or `None` if the `player` is not actually seated in this `Vehicle` + * @return a seat number, or `None` if the `player` is not actually seated in this vehicle */ - def PassengerInSeat(player : Player) : Option[Int] = { - var outSeat : Option[Int] = None - val GUID = player.GUID - for((seatNumber, seat) <- this.seats) { - val occupant : Option[PlanetSideGUID] = seat.Occupant - if(occupant.isDefined && occupant.get == GUID) { - outSeat = Some(seatNumber) + def PassengerInSeat(player : Player) : Option[Int] = recursivePassengerInSeat(seats.iterator, player) + + @tailrec private def recursivePassengerInSeat(iter : Iterator[(Int, Seat)], player : Player) : Option[Int] = { + if(!iter.hasNext) { + None + } + else { + val (seatNumber, seat) = iter.next + if(seat.Occupant.contains(player)) { + Some(seatNumber) + } + else { + recursivePassengerInSeat(iter, player) } } - outSeat } /** @@ -267,7 +370,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame */ def CanAccessTrunk(player : Player) : Boolean = { if(trunkAccess.isEmpty || trunkAccess.contains(player.GUID)) { - trunkLockState match { + groupPermissions(3) match { case VehicleLockState.Locked => //only the owner owner.isEmpty || (owner.isDefined && player.GUID == owner.get) case VehicleLockState.Group => //anyone in the owner's squad or platoon @@ -285,19 +388,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame * Check access to the `Trunk`. * @return the current access value for the `Vehicle` `Trunk` */ - def TrunkLockState : VehicleLockState.Value = { - this.trunkLockState - } - - /** - * Change the access value for the trunk. - * @param lockState the new access value for the `Vehicle` `Trunk` - * @return the current access value for the `Vehicle` `Trunk` after the change - */ - def TrunkLockState_=(lockState : VehicleLockState.Value) : VehicleLockState.Value = { - this.trunkLockState = lockState - lockState - } + def TrunkLockState : VehicleLockState.Value = groupPermissions(3) /** * This is the definition entry that is used to store and unload pertinent information about the `Vehicle`. @@ -316,22 +407,52 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideGame object Vehicle { /** - * Overloaded constructor. - * @param vehicleDef the vehicle's definition entry - * @return a `Vwehicle` object + * A basic `Trait` connecting all of the actionable `Vehicle` response messages. */ - def apply(vehicleDef : VehicleDefinition) : Vehicle = { - new Vehicle(vehicleDef) - } + sealed trait Exchange + + /** + * Message that carries the result of the processed request message back to the original user (`player`). + * @param player the player who sent this request message + * @param response the result of the processed request + */ + final case class VehicleMessages(player : Player, response : Exchange) + + /** + * The `Vehicle` will become unresponsive to player activity. + * Usually, it does this to await deconstruction and clean-up + * @see `VehicleControl` + */ + final case class PrepareForDeletion() + + /** + * This player wants to sit down in an available(?) seat. + * @param seat_num the seat where the player is trying to occupy; + * this is NOT the entry mount point index; + * make certain to convert! + * @param player the `Player` object + */ + final case class TrySeatPlayer(seat_num : Int, player : Player) + /** + * The recipient player of this packet is being allowed to sit in the assigned seat. + * @param vehicle the `Vehicle` object that generated this message + * @param seat_num the seat that the player will occupy + */ + final case class CanSeatPlayer(vehicle : Vehicle, seat_num : Int) extends Exchange + /** + * The recipient player of this packet is not allowed to sit in the requested seat. + * @param vehicle the `Vehicle` object that generated this message + * @param seat_num the seat that the player can not occupy + */ + final case class CannotSeatPlayer(vehicle : Vehicle, seat_num : Int) extends Exchange + /** * Overloaded constructor. * @param vehicleDef the vehicle's definition entry - * @return a `Vwehicle` object + * @return a `Vehicle` object */ - def apply(guid : PlanetSideGUID, vehicleDef : VehicleDefinition) : Vehicle = { - val obj = new Vehicle(vehicleDef) - obj.GUID = guid - obj + def apply(vehicleDef : VehicleDefinition) : Vehicle = { + new Vehicle(vehicleDef) } /** @@ -344,16 +465,13 @@ object Vehicle { //general stuff vehicle.Health = vdef.MaxHealth //create weapons - for((num, definition) <- vdef.Weapons) { + vehicle.weapons = vdef.Weapons.map({case (num, definition) => val slot = EquipmentSlot(EquipmentSize.VehicleWeapon) slot.Equipment = Tool(definition) - vehicle.weapons += num -> slot - vehicle - } + num -> slot + }).toMap //create seats - for((num, seatDef) <- vdef.Seats) { - vehicle.seats += num -> Seat(seatDef, vehicle) - } + 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) diff --git a/common/src/main/scala/net/psforever/objects/definition/SeatDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/SeatDefinition.scala index 5535274e..f1b12915 100644 --- a/common/src/main/scala/net/psforever/objects/definition/SeatDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/SeatDefinition.scala @@ -37,8 +37,12 @@ class SeatDefinition extends BasicDefinition { this.weaponMount } - def ControlledWeapon_=(seat : Option[Int]) : Option[Int] = { - this.weaponMount = seat + def ControlledWeapon_=(wep : Int) : Option[Int] = { + ControlledWeapon_=(Some(wep)) + } + + def ControlledWeapon_=(wep : Option[Int]) : Option[Int] = { + this.weaponMount = wep ControlledWeapon } } 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 d7c67fe6..708741ef 100644 --- a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala @@ -22,7 +22,9 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) { private var deployment : Boolean = false private val utilities : mutable.ArrayBuffer[Int] = mutable.ArrayBuffer[Int]() private var trunkSize : InventoryTile = InventoryTile.None - private var trunkOffset: Int = 0 + private var trunkOffset : Int = 0 + private var canCloak : Boolean = false + private var canBeOwned : Boolean = true Name = "vehicle" Packet = new VehicleConverter @@ -44,6 +46,20 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) { def MountPoints : mutable.HashMap[Int, Int] = mountPoints + def CanBeOwned : Boolean = canBeOwned + + def CanBeOwned_=(ownable : Boolean) : Boolean = { + canBeOwned = ownable + CanBeOwned + } + + def CanCloak : Boolean = canCloak + + def CanCloak_=(cloakable : Boolean) : Boolean = { + canCloak = cloakable + CanCloak + } + def Weapons : mutable.HashMap[Int, ToolDefinition] = weapons def Deployment : Boolean = deployment 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 06409367..2517e8c8 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 @@ -2,11 +2,10 @@ package net.psforever.objects.definition.converter import net.psforever.objects.equipment.Equipment -import net.psforever.objects.{EquipmentSlot, Vehicle} +import net.psforever.objects.Vehicle import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.objectcreate.{InventoryItemData, _} -import scala.annotation.tailrec import scala.util.{Failure, Success, Try} class VehicleConverter extends ObjectCreateConverter[Vehicle]() { @@ -19,47 +18,66 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() { PlacementData(obj.Position, obj.Orientation, obj.Velocity), obj.Faction, 0, - if(obj.Owner.isDefined) { obj.Owner.get } else { PlanetSideGUID(0) } //this is the owner field, right? + PlanetSideGUID(0) //if(obj.Owner.isDefined) { obj.Owner.get } else { PlanetSideGUID(0) } //TODO is this really Owner? ), 0, obj.Health / obj.MaxHealth * 255, //TODO not precise false, false, - DriveState.Mobile, - false, + obj.Drive, false, false, + obj.Cloaked, SpecificFormatData(obj), - Some(InventoryData(MakeMountings(obj).sortBy(_.parentSlot))) + Some(InventoryData((MakeMountings(obj) ++ MakeTrunk(obj)).sortBy(_.parentSlot))) )(SpecificFormatModifier) ) } /** - * For an object with a list of weapon mountings, convert those weapons into data as if found in an `0x17` packet. - * @param obj the Vehicle game object - * @return the converted data + * 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] = recursiveMakeMountings(obj.Weapons.iterator) - - @tailrec private def recursiveMakeMountings(iter : Iterator[(Int,EquipmentSlot)], list : List[InventoryItemData.InventoryItem] = Nil) : List[InventoryItemData.InventoryItem] = { - if(!iter.hasNext) { - list - } - else { - val (index, slot) = iter.next - if(slot.Equipment.isDefined) { + private def MakeMountings(obj : Vehicle) : List[InventoryItemData.InventoryItem] = { + obj.Weapons.map({ + case((index, slot)) => val equip : Equipment = slot.Equipment.get - recursiveMakeMountings( - iter, - list :+ InventoryItemData(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.Packet.ConstructorData(equip).get) - ) - } - else { - recursiveMakeMountings(iter, list) - } - } + InventoryItemData(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.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 SpecificFormatModifier : VehicleFormat.Value = VehicleFormat.Normal protected def SpecificFormatData(obj : Vehicle) : Option[SpecificVehicleData] = None diff --git a/common/src/main/scala/net/psforever/objects/equipment/EquipmentSize.scala b/common/src/main/scala/net/psforever/objects/equipment/EquipmentSize.scala index 484d9160..16ffebb9 100644 --- a/common/src/main/scala/net/psforever/objects/equipment/EquipmentSize.scala +++ b/common/src/main/scala/net/psforever/objects/equipment/EquipmentSize.scala @@ -9,6 +9,8 @@ object EquipmentSize extends Enumeration { Rifle, //6x3 and 9x3 Max, //max weapon only VehicleWeapon, //vehicle-mounted weapons + BFRArmWeapon, //duel arm weapons for bfr + BFRGunnerWeapon, //gunner seat for bfr Inventory, //reserved Any = Value diff --git a/common/src/main/scala/net/psforever/objects/inventory/AccessibleInventory.scala b/common/src/main/scala/net/psforever/objects/inventory/AccessibleInventory.scala new file mode 100644 index 00000000..d0d5e15e --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/inventory/AccessibleInventory.scala @@ -0,0 +1,13 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.inventory + +import net.psforever.objects.Player +import net.psforever.packet.game.PlanetSideGUID + +trait AccessibleInventory { + def Inventory : GridInventory + + def CanAccess(who : Player) : Boolean + def Access(who : PlanetSideGUID) : Boolean + def Unaccess : Boolean +} \ No newline at end of file diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/CertTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/CertTerminalDefinition.scala index dbab72d7..8f990196 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/CertTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/CertTerminalDefinition.scala @@ -18,7 +18,7 @@ class CertTerminalDefinition extends TerminalDefinition(171) { */ private val certificationList : Map[String, (CertificationType.Value, Int)] = Map( "medium_assault" -> (CertificationType.MediumAssault, 2), - "reinforced_armo" -> (CertificationType.ReinforcedExoSuit, 3), + "reinforced_armor" -> (CertificationType.ReinforcedExoSuit, 3), "quad_all" -> (CertificationType.ATV, 1), "switchblade" -> (CertificationType.Switchblade, 1), "harasser" -> (CertificationType.Harasser, 1), diff --git a/common/src/main/scala/net/psforever/objects/vehicles/AccessPermissionGroup.scala b/common/src/main/scala/net/psforever/objects/vehicles/AccessPermissionGroup.scala new file mode 100644 index 00000000..5a99956e --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vehicles/AccessPermissionGroup.scala @@ -0,0 +1,22 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vehicles + +/** + * An `Enumeration` of various permission groups that control access to aspects of a vehicle.
+ * - `Driver` is a seat that is always seat number 0.
+ * - `Gunner` is a seat that is not the `Driver` and controls a mounted weapon.
+ * - `Passenger` is a seat that is not the `Driver` and does not have control of a mounted weapon.
+ * - `Trunk` represnts access to the vehicle's internal storage space.
+ * Organized to replicate the `PlanetsideAttributeMessage` value used for that given access level. + * In their respective `PlanetsideAttributeMessage` packet, the groups are indexed in the same order as 10 through 13. + */ +object AccessPermissionGroup extends Enumeration { + type Type = Value + + val + Driver, + Gunner, + Passenger, + Trunk + = Value +} \ No newline at end of file diff --git a/common/src/main/scala/net/psforever/objects/vehicles/Seat.scala b/common/src/main/scala/net/psforever/objects/vehicles/Seat.scala index 263fa2e2..f69f927d 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/Seat.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/Seat.scala @@ -2,33 +2,22 @@ package net.psforever.objects.vehicles import net.psforever.objects.definition.SeatDefinition -import net.psforever.objects.{Player, Vehicle} -import net.psforever.packet.game.PlanetSideGUID -import net.psforever.types.PlanetSideEmpire +import net.psforever.objects.Player /** * Server-side support for a slot that infantry players can occupy, ostensibly called a "seat" and treated like a "seat." * (Players can sit in it.) - * @param seatDef the Definition that constructs this item and maintains some of its immutable fields - * @param vehicle the vehicle where this seat is installed + * @param seatDef the Definition that constructs this item and maintains some of its unchanging fields */ -class Seat(private val seatDef : SeatDefinition, private val vehicle : Vehicle) { - private var occupant : Option[PlanetSideGUID] = None - private var lockState : VehicleLockState.Value = VehicleLockState.Empire - - /** - * The faction association of this `Seat` is tied directly to the connected `Vehicle`. - * @return the faction association - */ - def Faction : PlanetSideEmpire.Value = { - vehicle.Faction - } +class Seat(private val seatDef : SeatDefinition) { + private var occupant : Option[Player] = None +// private var lockState : VehicleLockState.Value = VehicleLockState.Empire /** * Is this seat occupied? * @return the GUID of the player sitting in this seat, or `None` if it is left vacant */ - def Occupant : Option[PlanetSideGUID] = { + def Occupant : Option[Player] = { this.occupant } @@ -38,10 +27,12 @@ class Seat(private val seatDef : SeatDefinition, private val vehicle : Vehicle) * @param player the player who wants to sit, or `None` if the occupant is getting up * @return the GUID of the player sitting in this seat, or `None` if it is left vacant */ - def Occupant_=(player : Option[Player]) : Option[PlanetSideGUID] = { + def Occupant_=(player : Player) : Option[Player] = Occupant_=(Some(player)) + + def Occupant_=(player : Option[Player]) : Option[Player] = { if(player.isDefined) { if(this.occupant.isEmpty) { - this.occupant = Some(player.get.GUID) + this.occupant = player } } else { @@ -58,14 +49,14 @@ class Seat(private val seatDef : SeatDefinition, private val vehicle : Vehicle) this.occupant.isDefined } - def SeatLockState : VehicleLockState.Value = { - this.lockState - } - - def SeatLockState_=(lockState : VehicleLockState.Value) : VehicleLockState.Value = { - this.lockState = lockState - SeatLockState - } +// def SeatLockState : VehicleLockState.Value = { +// this.lockState +// } +// +// def SeatLockState_=(lockState : VehicleLockState.Value) : VehicleLockState.Value = { +// this.lockState = lockState +// SeatLockState +// } def ArmorRestriction : SeatArmorRestriction.Value = { seatDef.ArmorRestriction @@ -79,25 +70,6 @@ class Seat(private val seatDef : SeatDefinition, private val vehicle : Vehicle) seatDef.ControlledWeapon } - /** - * Given a player, can they access this `Seat` under its current restrictions and permissions. - * @param player the player who wants to sit - * @return `true` if the player can sit down in this `Seat`; `false`, otherwise - */ - def CanUseSeat(player : Player) : Boolean = { - var access : Boolean = false - val owner : Option[PlanetSideGUID] = vehicle.Owner - lockState match { - case VehicleLockState.Locked => - access = owner.isEmpty || (owner.isDefined && player.GUID == owner.get) - case VehicleLockState.Group => - access = Faction == player.Faction //TODO this is not correct - case VehicleLockState.Empire => - access = Faction == player.Faction - } - access - } - /** * Override the string representation to provide additional information. * @return the string output @@ -110,11 +82,10 @@ class Seat(private val seatDef : SeatDefinition, private val vehicle : Vehicle) object Seat { /** * Overloaded constructor. - * @param vehicle the vehicle where this seat is installed * @return a `Seat` object */ - def apply(seatDef : SeatDefinition, vehicle : Vehicle) : Seat = { - new Seat(seatDef, vehicle) + def apply(seatDef : SeatDefinition) : Seat = { + new Seat(seatDef) } /** @@ -122,13 +93,12 @@ object Seat { * @return the string output */ def toString(obj : Seat) : String = { - val weaponStr = if(obj.ControlledWeapon.isDefined) { " (gunner)" } else { "" } val seatStr = if(obj.isOccupied) { - "occupied by %d".format(obj.Occupant.get.guid) + s", occupied by player ${obj.Occupant.get.GUID}" } else { - "unoccupied" + "" } - s"{Seat$weaponStr: $seatStr}" + s"seat$seatStr" } } diff --git a/common/src/main/scala/net/psforever/objects/vehicles/SeatArmorRestriction.scala b/common/src/main/scala/net/psforever/objects/vehicles/SeatArmorRestriction.scala index 391b3d86..8558b386 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/SeatArmorRestriction.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/SeatArmorRestriction.scala @@ -11,7 +11,8 @@ package net.psforever.objects.vehicles object SeatArmorRestriction extends Enumeration { type Type = Value - val MaxOnly, + val + MaxOnly, NoMax, NoReinforcedOrMax = Value diff --git a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala new file mode 100644 index 00000000..5f6be0e3 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -0,0 +1,37 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vehicles + +import akka.actor.Actor +import net.psforever.objects.Vehicle + +/** + * An `Actor` that handles messages being dispatched to a specific `Vehicle`.
+ *
+ * Vehicle-controlling actors have two behavioral states - responsive and "`Disabled`." + * 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 { + def receive : Receive = { + case Vehicle.PrepareForDeletion => + context.become(Disabled) + + case Vehicle.TrySeatPlayer(seat_num, player) => + vehicle.Seat(seat_num) match { + case Some(seat) => + if((seat.Occupant = player).contains(player)) { + sender ! Vehicle.VehicleMessages(player, Vehicle.CanSeatPlayer(vehicle, seat_num)) + } + else { + sender ! Vehicle.VehicleMessages(player, Vehicle.CannotSeatPlayer(vehicle, seat_num)) + } + case None => + sender ! Vehicle.VehicleMessages(player, Vehicle.CannotSeatPlayer(vehicle, seat_num)) + } + case _ => ; + } + + def Disabled : Receive = { + case _ => ; + } +} diff --git a/common/src/main/scala/net/psforever/objects/vehicles/VehicleLockState.scala b/common/src/main/scala/net/psforever/objects/vehicles/VehicleLockState.scala index 66156701..748f370c 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleLockState.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleLockState.scala @@ -3,12 +3,12 @@ package net.psforever.objects.vehicles /** * An `Enumeration` of various access states for vehicle components, such as the seats and the trunk. + * Organized to replicate the `PlanetsideAttributeMessage` value used for that given access level. */ object VehicleLockState extends Enumeration { type Type = Value - val Empire, //owner's whole faction - Group, //owner's squad/platoon only - Locked //owner only - = Value + val Locked = Value(0) //owner only + val Group = Value(1) //owner's squad/platoon only + val Empire = Value(3) //owner's whole faction } diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala index 94c30d49..7bf7d22c 100644 --- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -4,7 +4,7 @@ package net.psforever.objects.zones import akka.actor.{ActorContext, ActorRef, Props} import akka.routing.RandomPool import net.psforever.objects.serverobject.doors.Base -import net.psforever.objects.{PlanetSideGameObject, Player} +import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle} import net.psforever.objects.equipment.Equipment import net.psforever.objects.guid.NumberPoolHub import net.psforever.objects.guid.actor.UniqueNumberSystem @@ -14,6 +14,7 @@ import net.psforever.packet.GamePacket import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.Vector3 +import scala.annotation.tailrec import scala.collection.mutable.ListBuffer /** @@ -49,6 +50,10 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { private val equipmentOnGround : ListBuffer[Equipment] = ListBuffer[Equipment]() /** Used by the `Zone` to coordinate `Equipment` dropping and collection requests. */ private var ground : ActorRef = ActorRef.noSender + /** */ + private var vehicles : List[Vehicle] = List[Vehicle]() + /** */ + private var transport : ActorRef = ActorRef.noSender private var bases : List[Base] = List() @@ -69,6 +74,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { implicit val guid : NumberPoolHub = this.guid //passed into builderObject.Build implicitly accessor = context.actorOf(RandomPool(25).props(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystem.AllocateNumberPoolActors(guid))), s"$Id-uns") ground = context.actorOf(Props(classOf[ZoneGroundActor], equipmentOnGround), s"$Id-ground") + transport = context.actorOf(Props(classOf[ZoneVehicleActor], this), s"$Id-vehicles") Map.LocalObjects.foreach({ builderObject => builderObject.Build @@ -165,6 +171,37 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { */ def EquipmentOnGround : List[Equipment] = equipmentOnGround.toList + def Vehicles : List[Vehicle] = vehicles + + def AddVehicle(vehicle : Vehicle) : List[Vehicle] = { + vehicles = vehicles :+ vehicle + Vehicles + } + + def RemoveVehicle(vehicle : Vehicle) : List[Vehicle] = { + vehicles = recursiveFindVehicle(vehicles.iterator, vehicle) match { + case Some(index) => + vehicles.take(index) ++ vehicles.drop(index + 1) + case None => ; + vehicles + } + Vehicles + } + + @tailrec private def recursiveFindVehicle(iter : Iterator[Vehicle], target : Vehicle, index : Int = 0) : Option[Int] = { + if(!iter.hasNext) { + None + } + else { + if(iter.next.equals(target)) { + Some(index) + } + else { + recursiveFindVehicle(iter, target, index + 1) + } + } + } + /** * Coordinate `Equipment` that has been dropped on the ground or to-be-dropped on the ground. * @return synchronized reference to the ground @@ -175,6 +212,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { */ def Ground : ActorRef = ground + def Transport : ActorRef = transport + def MakeBases(num : Int) : List[Base] = { bases = (0 to num).map(id => new Base(id)).toList bases @@ -250,6 +289,10 @@ object Zone { */ final case class ItemFromGround(player : Player, item : Equipment) + final case class SpawnVehicle(vehicle : Vehicle) + + final case class DespawnVehicle(vehicle : Vehicle) + /** * Message to report the packet messages that initialize the client. * @param list a `List` of `GamePacket` messages diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala new file mode 100644 index 00000000..b7d3c6a5 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala @@ -0,0 +1,22 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.zones + +import akka.actor.Actor + +/** + * Synchronize management of the list of `Vehicles` maintained by some `Zone`. + * @param zone the `Zone` object + */ +class ZoneVehicleActor(zone : Zone) extends Actor { + //private[this] val log = org.log4s.getLogger + + def receive : Receive = { + case Zone.SpawnVehicle(vehicle) => + zone.AddVehicle(vehicle) + + case Zone.DespawnVehicle(vehicle) => + zone.RemoveVehicle(vehicle) + + case _ => ; + } +} diff --git a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala index 0b640c68..c61b03e7 100644 --- a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala @@ -6,7 +6,7 @@ import scodec.Codec import scodec.codecs._ /** - * Attribute Type:
+ * Players/General:
* Server to client :
* `0 - health`
* `1 - healthMax`
@@ -86,7 +86,19 @@ import scodec.codecs._ * `78 - Cavern Kills. Value is the number of kills`
* `106 - Custom Head` * Client to Server :
- * `106 - Custom Head` + * `106 - Custom Head`
+ *
+ * Vehicles:
+ * 0 - Vehicle health
+ * 10 - Driver seat permissions (0 = Locked, 1 = Group, 3 = Empire)
+ * 11 - Gunner seat(s) permissions (same)
+ * 12 - Passenger seat(s) permissions (same)
+ * 13 - Trunk permissions (same)
+ * 21 - Asserts first time event eligibility / makes owner if no owner is assigned
+ * 22 - Toggles gunner and passenger mount points (1 = hides, 0 = reveals; this also locks their permissions)
+ * 68 - ???
+ * 80 - Damage vehicle (unknown value)
+ * 113 - ??? * @param player_guid the player * @param attribute_type na * @param attribute_value na 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 f396aba1..37b85c5a 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 @@ -803,6 +803,7 @@ object ObjectClass { case ObjectClass.battlewagon_weapon_systemd => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.bolt_driver => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.chainblade => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.chaingun_p => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.colossus_burster => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.colossus_burster_left => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.colossus_burster_right => ConstructorData.genericCodec(WeaponData.codec, "weapon") @@ -918,7 +919,6 @@ object ObjectClass { case ObjectClass.cannon_dropship_20mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.chaingun_12mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.chaingun_15mm => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.chaingun_p => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.cycler_v2 => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.cycler_v3 => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.cycler_v4 => ConstructorData.genericCodec(WeaponData.codec, "weapon") @@ -1080,6 +1080,7 @@ object ObjectClass { // case ObjectClass.aphelion_starfire_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.bolt_driver => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.chainblade => DroppedItemData.genericCodec(WeaponData.codec, "weapon") +// case ObjectClass.chaingun_p => DroppedItemData.genericCodec(WeaponData.codec, "weapon") // case ObjectClass.colossus_burster => DroppedItemData.genericCodec(WeaponData.codec, "weapon") // case ObjectClass.colossus_burster_left => DroppedItemData.genericCodec(WeaponData.codec, "weapon") // case ObjectClass.colossus_burster_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon") @@ -1144,7 +1145,6 @@ object ObjectClass { case ObjectClass.advanced_missile_launcher_t => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.chaingun_12mm => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.chaingun_15mm => DroppedItemData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.chaingun_p => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.cycler_v2 => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.cycler_v3 => DroppedItemData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.cycler_v4 => DroppedItemData.genericCodec(WeaponData.codec, "weapon") diff --git a/common/src/test/scala/objects/ConverterTest.scala b/common/src/test/scala/objects/ConverterTest.scala index 9f53443c..3b946fba 100644 --- a/common/src/test/scala/objects/ConverterTest.scala +++ b/common/src/test/scala/objects/ConverterTest.scala @@ -281,7 +281,8 @@ class ConverterTest extends Specification { val hellfire_ammo_box = AmmoBox(PlanetSideGUID(432), hellfire_ammo) - val fury = Vehicle(PlanetSideGUID(413), fury_def) + val fury = Vehicle(fury_def) + fury.GUID = PlanetSideGUID(413) fury.Faction = PlanetSideEmpire.VS fury.Position = Vector3(3674.8438f, 2732f, 91.15625f) fury.Orientation = Vector3(0.0f, 0.0f, 90.0f) diff --git a/common/src/test/scala/objects/VehicleTest.scala b/common/src/test/scala/objects/VehicleTest.scala new file mode 100644 index 00000000..11b1d65a --- /dev/null +++ b/common/src/test/scala/objects/VehicleTest.scala @@ -0,0 +1,103 @@ +// Copyright (c) 2017 PSForever +package objects + +import net.psforever.objects.{GlobalDefinitions, Vehicle} +import net.psforever.objects.definition.SeatDefinition +import net.psforever.objects.vehicles.{Seat, SeatArmorRestriction, VehicleLockState} +import org.specs2.mutable._ + +class VehicleTest extends Specification { + + "SeatDefinition" should { + val seat = new SeatDefinition + seat.ArmorRestriction = SeatArmorRestriction.MaxOnly + seat.Bailable = true + seat.ControlledWeapon = 5 + + "define (default)" in { + val t_seat = new SeatDefinition + t_seat.ArmorRestriction mustEqual SeatArmorRestriction.NoMax + t_seat.Bailable mustEqual false + t_seat.ControlledWeapon mustEqual None + } + + "define (custom)" in { + seat.ArmorRestriction mustEqual SeatArmorRestriction.MaxOnly + seat.Bailable mustEqual true + seat.ControlledWeapon mustEqual Some(5) + } + } + + "VehicleDefinition" should { + "define" in { + val fury = GlobalDefinitions.fury + fury.CanBeOwned mustEqual true + fury.CanCloak mustEqual false + fury.Seats.size mustEqual 1 + fury.Seats(0).Bailable mustEqual true + fury.Seats(0).ControlledWeapon mustEqual Some(1) + fury.MountPoints.size mustEqual 2 + fury.MountPoints.get(0) mustEqual Some(0) + fury.MountPoints.get(1) mustEqual None + fury.MountPoints.get(2) mustEqual Some(0) + fury.Weapons.size mustEqual 1 + fury.Weapons.get(0) mustEqual None + fury.Weapons.get(1) mustEqual Some(GlobalDefinitions.fury_weapon_systema) + fury.TrunkSize.width mustEqual 11 + fury.TrunkSize.height mustEqual 11 + fury.TrunkOffset mustEqual 30 + } + } + + "Seat" should { + val seat_def = new SeatDefinition + seat_def.ArmorRestriction = SeatArmorRestriction.MaxOnly + seat_def.Bailable = true + seat_def.ControlledWeapon = 5 + + "construct" in { + val seat = new Seat(seat_def) + seat.ArmorRestriction mustEqual SeatArmorRestriction.MaxOnly + seat.Bailable mustEqual true + seat.ControlledWeapon mustEqual Some(5) + seat.isOccupied mustEqual false + seat.Occupant mustEqual None + } + } + + "Vehicle" should { + "construct" in { + Vehicle(GlobalDefinitions.fury) + ok + } + + "construct (detailed)" in { + val fury_vehicle = Vehicle(GlobalDefinitions.fury) + fury_vehicle.Owner mustEqual None + fury_vehicle.Seats.size mustEqual 1 + fury_vehicle.Seats.head.ArmorRestriction mustEqual SeatArmorRestriction.NoMax + fury_vehicle.Seats.head.isOccupied mustEqual false + fury_vehicle.Seats.head.Occupant mustEqual None + fury_vehicle.Seats.head.Bailable mustEqual true + fury_vehicle.Seats.head.ControlledWeapon mustEqual Some(1) + fury_vehicle.PermissionGroup(0) mustEqual Some(VehicleLockState.Locked) //driver + fury_vehicle.PermissionGroup(1) mustEqual Some(VehicleLockState.Empire) //gunner + fury_vehicle.PermissionGroup(2) mustEqual Some(VehicleLockState.Empire) //passenger + fury_vehicle.PermissionGroup(3) mustEqual Some(VehicleLockState.Locked) //trunk + fury_vehicle.Weapons.size mustEqual 1 + fury_vehicle.Weapons.get(0) mustEqual None + fury_vehicle.Weapons.get(1).isDefined mustEqual true + fury_vehicle.Weapons(1).Equipment.isDefined mustEqual true + fury_vehicle.Weapons(1).Equipment.get.Definition mustEqual GlobalDefinitions.fury.Weapons(1) + fury_vehicle.WeaponControlledFromSeat(0) mustEqual fury_vehicle.Weapons(1).Equipment + fury_vehicle.Trunk.Width mustEqual 11 + fury_vehicle.Trunk.Height mustEqual 11 + fury_vehicle.Trunk.Offset mustEqual 30 + fury_vehicle.GetSeatFromMountPoint(0) mustEqual Some(0) + fury_vehicle.GetSeatFromMountPoint(1) mustEqual None + fury_vehicle.GetSeatFromMountPoint(2) mustEqual Some(0) + fury_vehicle.Decal mustEqual 0 + fury_vehicle.Health mustEqual fury_vehicle.Definition.MaxHealth + } + } +} diff --git a/pslogin/src/main/scala/PsLogin.scala b/pslogin/src/main/scala/PsLogin.scala index e3e4bc1b..870f402e 100644 --- a/pslogin/src/main/scala/PsLogin.scala +++ b/pslogin/src/main/scala/PsLogin.scala @@ -18,8 +18,10 @@ import net.psforever.objects.serverobject.builders.{DoorObjectBuilder, IFFLockOb import org.slf4j import org.fusesource.jansi.Ansi._ import org.fusesource.jansi.Ansi.Color._ +import services.ServiceManager import services.avatar._ import services.local._ +import services.vehicle.VehicleService import scala.collection.JavaConverters._ import scala.concurrent.Await @@ -206,6 +208,7 @@ object PsLogin { serviceManager ! ServiceManager.Register(RandomPool(50).props(Props[TaskResolver]), "taskResolver") serviceManager ! ServiceManager.Register(Props[AvatarService], "avatar") serviceManager ! ServiceManager.Register(Props[LocalService], "local") + serviceManager ! ServiceManager.Register(Props[VehicleService], "vehicle") serviceManager ! ServiceManager.Register(Props(classOf[InterstellarCluster], createContinents()), "galaxy") /** Create two actors for handling the login and world server endpoints */ diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 2d9dfadb..9948e6d7 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -9,23 +9,24 @@ import scodec.Attempt.{Failure, Successful} import scodec.bits._ import org.log4s.MDC import MDCContextAware.Implicits._ -import ServiceManager.Lookup +import services.ServiceManager.Lookup import net.psforever.objects._ -import net.psforever.objects.serverobject.doors.Door -import net.psforever.objects.zones.{InterstellarCluster, Zone} -import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.equipment._ import net.psforever.objects.guid.{Task, TaskResolver} -import net.psforever.objects.guid.actor.{Register, Unregister} import net.psforever.objects.inventory.{GridInventory, InventoryItem} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} +import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.vehicles.{AccessPermissionGroup, Seat, VehicleLockState} +import net.psforever.objects.zones.{InterstellarCluster, Zone} import net.psforever.packet.game.objectcreate._ import net.psforever.types._ +import scripts.GUIDTask import services._ import services.avatar._ import services.local._ +import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse} import scala.annotation.tailrec import scala.util.Success @@ -39,6 +40,7 @@ class WorldSessionActor extends Actor with MDCContextAware { var rightRef : ActorRef = ActorRef.noSender var avatarService : ActorRef = ActorRef.noSender var localService : ActorRef = ActorRef.noSender + var vehicleService : ActorRef = ActorRef.noSender var taskResolver : ActorRef = Actor.noSender var galaxy : ActorRef = Actor.noSender var continent : Zone = null @@ -53,12 +55,13 @@ class WorldSessionActor extends Actor with MDCContextAware { avatarService ! Service.Leave() localService ! Service.Leave() + vehicleService ! Service.Leave() LivePlayerList.Remove(sessionId) match { case Some(tplayer) => if(tplayer.HasGUID) { val guid = tplayer.GUID avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(guid, guid)) - taskResolver ! UnregisterAvatar(tplayer) + taskResolver ! GUIDTask.UnregisterAvatar(tplayer)(continent.GUID) //TODO normally, the actual player avatar persists a minute or so after the user disconnects } case None => ; @@ -81,6 +84,7 @@ class WorldSessionActor extends Actor with MDCContextAware { context.become(Started) ServiceManager.serviceManager ! Lookup("avatar") ServiceManager.serviceManager ! Lookup("local") + ServiceManager.serviceManager ! Lookup("vehicle") ServiceManager.serviceManager ! Lookup("taskResolver") ServiceManager.serviceManager ! Lookup("galaxy") @@ -96,6 +100,9 @@ class WorldSessionActor extends Actor with MDCContextAware { case ServiceManager.LookupResult("local", endpoint) => localService = endpoint log.info("ID: " + sessionId + " Got local service " + endpoint) + case ServiceManager.LookupResult("vehicle", endpoint) => + vehicleService = endpoint + log.info("ID: " + sessionId + " Got vehicle service " + endpoint) case ServiceManager.LookupResult("taskResolver", endpoint) => taskResolver = endpoint log.info("ID: " + sessionId + " Got task resolver service " + endpoint) @@ -113,12 +120,12 @@ class WorldSessionActor extends Actor with MDCContextAware { case AvatarServiceResponse(_, guid, reply) => reply match { - case AvatarServiceResponse.ArmorChanged(suit, subtype) => + case AvatarResponse.ArmorChanged(suit, subtype) => if(player.GUID != guid) { sendResponse(PacketCoding.CreateGamePacket(0, ArmorChangedMessage(guid, suit, subtype))) } - case AvatarServiceResponse.EquipmentInHand(slot, item) => + case AvatarResponse.EquipmentInHand(slot, item) => if(player.GUID != guid) { val definition = item.Definition sendResponse( @@ -133,7 +140,7 @@ class WorldSessionActor extends Actor with MDCContextAware { ) } - case AvatarServiceResponse.EquipmentOnGround(pos, orient, item) => + case AvatarResponse.EquipmentOnGround(pos, orient, item) => if(player.GUID != guid) { val definition = item.Definition sendResponse( @@ -147,7 +154,7 @@ class WorldSessionActor extends Actor with MDCContextAware { ) } - case AvatarServiceResponse.LoadPlayer(pdata) => + case AvatarResponse.LoadPlayer(pdata) => if(player.GUID != guid) { sendResponse( PacketCoding.CreateGamePacket( @@ -157,22 +164,22 @@ class WorldSessionActor extends Actor with MDCContextAware { ) } - case AvatarServiceResponse.ObjectDelete(item_guid, unk) => + case AvatarResponse.ObjectDelete(item_guid, unk) => if(player.GUID != guid) { sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(item_guid, unk))) } - case AvatarServiceResponse.ObjectHeld(slot) => + case AvatarResponse.ObjectHeld(slot) => if(player.GUID != guid) { sendResponse(PacketCoding.CreateGamePacket(0, ObjectHeldMessage(guid, slot, true))) } - case AvatarServiceResponse.PlanetSideAttribute(attribute_type, attribute_value) => + case AvatarResponse.PlanetSideAttribute(attribute_type, attribute_value) => if(player.GUID != guid) { sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(guid, attribute_type, attribute_value))) } - case AvatarServiceResponse.PlayerState(msg, spectating, weaponInHand) => + case AvatarResponse.PlayerState(msg, spectating, weaponInHand) => if(player.GUID != guid) { val now = System.currentTimeMillis() val (location, time, distanceSq) : (Vector3, Long, Float) = if(spectating) { @@ -213,7 +220,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - case AvatarServiceResponse.Reload(mag) => + case AvatarResponse.Reload(mag) => if(player.GUID != guid) { sendResponse(PacketCoding.CreateGamePacket(0, ReloadMessage(guid, mag, 0))) } @@ -223,24 +230,76 @@ class WorldSessionActor extends Actor with MDCContextAware { case LocalServiceResponse(_, guid, reply) => reply match { - case LocalServiceResponse.DoorOpens(door_guid) => + case LocalResponse.DoorOpens(door_guid) => if(player.GUID != guid) { sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(door_guid, 16))) } - case LocalServiceResponse.DoorCloses(door_guid) => //door closes for everyone + case LocalResponse.DoorCloses(door_guid) => //door closes for everyone sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(door_guid, 17))) - case LocalServiceResponse.HackClear(target_guid, unk1, unk2) => + case LocalResponse.HackClear(target_guid, unk1, unk2) => sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target_guid, guid, 0, unk1, HackState.HackCleared, unk2))) - case LocalServiceResponse.HackObject(target_guid, unk1, unk2) => + case LocalResponse.HackObject(target_guid, unk1, unk2) => if(player.GUID != guid) { sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target_guid, guid, 100, unk1, HackState.Hacked, unk2))) } - case LocalServiceResponse.TriggerSound(sound, pos, unk, volume) => + case LocalResponse.TriggerSound(sound, pos, unk, volume) => sendResponse(PacketCoding.CreateGamePacket(0, TriggerSoundMessage(sound, pos, unk, volume))) + + case _ => ; + } + + case VehicleServiceResponse(_, guid, reply) => + reply match { + case VehicleResponse.Awareness(vehicle_guid) => + //resets exclamation point fte marker (once) + sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(guid, 21, vehicle_guid.guid.toLong))) + + case VehicleResponse.ChildObjectState(object_guid, pitch, yaw) => + if(player.GUID != guid) { + sendResponse(PacketCoding.CreateGamePacket(0, ChildObjectStateMessage(object_guid, pitch, yaw))) + } + + case VehicleResponse.DismountVehicle(unk1, unk2) => + if(player.GUID != guid) { + sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(guid, unk1, unk2))) + } + + case VehicleResponse.KickPassenger(unk1, unk2) => + sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(guid, unk1, unk2))) + + case VehicleResponse.LoadVehicle(vehicle, vtype, vguid, vdata) => + //this is not be suitable for vehicles with people who are seated in it before it spawns (if that is possible) + if(player.GUID != guid) { + sendResponse(PacketCoding.CreateGamePacket(0, ObjectCreateMessage(vtype, vguid, vdata))) + ReloadVehicleAccessPermissions(vehicle) + } + + case VehicleResponse.MountVehicle(vehicle_guid, seat) => + if(player.GUID != guid) { + sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle_guid, guid, seat))) + if(player.VehicleOwned.contains(vehicle_guid)) { //simplistic vehicle ownership management + player.VehicleOwned = None + } + } + + case VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission) => + if(player.GUID != guid) { + sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, seat_group, permission))) + } + + case VehicleResponse.UnloadVehicle(vehicle_guid) => + sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(vehicle_guid, 0))) + + case VehicleResponse.VehicleState(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6) => + if(player.GUID != guid) { + sendResponse(PacketCoding.CreateGamePacket(0, VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6))) + } + + case _ => ; } case Door.DoorMessage(tplayer, msg, order) => @@ -375,7 +434,7 @@ class WorldSessionActor extends Actor with MDCContextAware { tplayer.Fit(item) match { case Some(index) => sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Buy, true))) - PutEquipmentInSlot(tplayer, item, index) + taskResolver ! PutEquipmentInSlot(tplayer, item, index) case None => sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Buy, false))) } @@ -385,7 +444,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(item) => if(item.GUID == msg.item_guid) { sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Sell, true))) - RemoveEquipmentFromSlot(tplayer, item, Player.FreeHandSlot) + taskResolver ! RemoveEquipmentFromSlot(tplayer, item, Player.FreeHandSlot) } case None => sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Sell, false))) @@ -407,7 +466,7 @@ class WorldSessionActor extends Actor with MDCContextAware { }) (beforeHolsters ++ beforeInventory).foreach({ elem => sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(elem.obj.GUID, 0))) - taskResolver ! UnregisterEquipment(elem.obj) + taskResolver ! GUIDTask.UnregisterEquipment(elem.obj)(continent.GUID) }) //report change sendResponse(PacketCoding.CreateGamePacket(0, ArmorChangedMessage(tplayer.GUID, exosuit, 0))) @@ -433,11 +492,11 @@ class WorldSessionActor extends Actor with MDCContextAware { } //draw holsters holsters.foreach(entry => { - PutEquipmentInSlot(tplayer, entry.obj, entry.start) + taskResolver ! PutEquipmentInSlot(tplayer, entry.obj, entry.start) }) //put items into inventory inventory.foreach(entry => { - PutEquipmentInSlot(tplayer, entry.obj, entry.start) + taskResolver ! PutEquipmentInSlot(tplayer, entry.obj, entry.start) }) //TODO drop items on ground sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.InfantryLoadout, true))) @@ -471,6 +530,41 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, msg.transaction_type, false))) } + case Vehicle.VehicleMessages(tplayer, reply) => + reply match { + case Vehicle.CanSeatPlayer(vehicle, seat_num) => + log.info(s"MountVehicleMsg: ${player.GUID} mounts ${vehicle.GUID} @ $seat_num") + val vehicle_guid : PlanetSideGUID = vehicle.GUID + tplayer.VehicleSeated = Some(vehicle_guid) + if(seat_num == 0) { //simplistic vehicle ownership management + player.VehicleOwned = Some(vehicle_guid) + vehicle.Owner = Some(player.GUID) + } + vehicle.WeaponControlledFromSeat(seat_num) match { + case Some(weapon : Tool) => + //update mounted weapon belonging to seat + val magazine = weapon.AmmoSlots(weapon.FireModeIndex).Box //update the magazine in the weapon, specifically + sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(magazine.GUID, 0, weapon.GUID, weapon.Magazine.toLong))) + //update all related ammunition objects in trunk + vehicle.Trunk.Items + .filter({ case ((_, item)) => item.obj.isInstanceOf[AmmoBox] && item.obj.asInstanceOf[AmmoBox].AmmoType == weapon.AmmoType }) + .foreach({ case ((_, item)) => + val box = item.obj.asInstanceOf[AmmoBox] + sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(box.GUID, 0, vehicle_guid, box.Capacity.toLong))) + }) + case None => ; //no weapons to update + } + val player_guid : PlanetSideGUID = tplayer.GUID + sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle_guid, player_guid, seat_num))) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, vehicle_guid, seat_num)) + + case Vehicle.CannotSeatPlayer(vehicle, seat_num) => + val seat : Seat = vehicle.Seat(seat_num).get + log.warn(s"MountVehicleMsg: player $tplayer attempted to board vehicle ${vehicle.GUID}'s seat $seat_num, but was not allowed") + + case _ => ; + } + case ListAccountCharacters => import net.psforever.objects.definition.converter.CharacterSelectConverter val gen : AtomicInteger = new AtomicInteger(1) @@ -512,6 +606,16 @@ class WorldSessionActor extends Actor with MDCContextAware { failWithError(s"$tplayer failed to load anywhere") } + case VehicleLoaded(vehicle) => + val definition = vehicle.Definition + val objedtId = definition.ObjectId + val vehicle_guid = vehicle.GUID + val vdata = definition.Packet.ConstructorData(vehicle).get + sendResponse(PacketCoding.CreateGamePacket(0, ObjectCreateMessage(objedtId, vehicle_guid, vdata))) + ReloadVehicleAccessPermissions(vehicle) + continent.Transport ! Zone.SpawnVehicle(vehicle) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.LoadVehicle(player.GUID, vehicle, objedtId, vehicle_guid, vdata)) + case Zone.ClientInitialization(/*initList*/_) => //TODO iterate over initList; for now, just do this sendResponse( @@ -691,53 +795,46 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - import net.psforever.objects.GlobalDefinitions._ - //this part is created by WSA based on the database query (should be in case of ConnectToWorldRequestMessage, maybe) - val energy_cell_box1 = AmmoBox(energy_cell) - val energy_cell_box2 = AmmoBox(energy_cell, 16) - val bullet_9mm_box1 = AmmoBox(bullet_9mm) - val bullet_9mm_box2 = AmmoBox(bullet_9mm) - val bullet_9mm_box3 = AmmoBox(bullet_9mm) - val bullet_9mm_box4 = AmmoBox(bullet_9mm, 25) - val bullet_9mm_AP_box = AmmoBox(bullet_9mm_AP) - val melee_ammo_box = AmmoBox(melee_ammo) - val - beamer1 = Tool(beamer) - beamer1.AmmoSlots.head.Box = energy_cell_box2 - val - suppressor1 = Tool(suppressor) - suppressor1.AmmoSlots.head.Box = bullet_9mm_box4 - val - forceblade1 = Tool(forceblade) - forceblade1.AmmoSlots.head.Box = melee_ammo_box - val rek = SimpleItem(remote_electronics_kit) - val extra_rek = SimpleItem(remote_electronics_kit) - val - player = Player("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) - player.Position = Vector3(3674.8438f, 2726.789f, 91.15625f) - player.Orientation = Vector3(0f, 0f, 90f) - player.Certifications += CertificationType.StandardAssault - player.Certifications += CertificationType.MediumAssault - player.Certifications += CertificationType.StandardExoSuit - player.Certifications += CertificationType.AgileExoSuit - player.Certifications += CertificationType.ReinforcedExoSuit - player.Certifications += CertificationType.ATV - player.Certifications += CertificationType.Harasser - player.Slot(0).Equipment = beamer1 - player.Slot(2).Equipment = suppressor1 - player.Slot(4).Equipment = forceblade1 - player.Slot(6).Equipment = bullet_9mm_box1 - player.Slot(9).Equipment = bullet_9mm_box2 - player.Slot(12).Equipment = bullet_9mm_box3 - player.Slot(33).Equipment = bullet_9mm_AP_box - player.Slot(36).Equipment = energy_cell_box1 - player.Slot(39).Equipment = rek - player.Slot(5).Equipment.get.asInstanceOf[LockerContainer].Inventory += 0 -> extra_rek + var player : Player = null + var harasser : Vehicle = null //TODO used in testing def handleGamePkt(pkt : PlanetSideGamePacket) = pkt match { case ConnectToWorldRequestMessage(server, token, majorVersion, minorVersion, revision, buildDate, unk) => val clientVersion = s"Client Version: $majorVersion.$minorVersion.$revision, $buildDate" log.info(s"New world login to $server with Token:$token. $clientVersion") + //TODO begin temp player character auto-loading; remove later + import net.psforever.objects.GlobalDefinitions._ + val + beamer1 = Tool(beamer) + beamer1.AmmoSlots.head.Box = AmmoBox(energy_cell, 16) + val + suppressor1 = Tool(suppressor) + suppressor1.AmmoSlots.head.Box = AmmoBox(bullet_9mm, 25) + val + forceblade1 = Tool(forceblade) + forceblade1.AmmoSlots.head.Box = AmmoBox(melee_ammo) + + player = Player("TestCharacter"+sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) + player.Position = Vector3(3674.8438f, 2726.789f, 91.15625f) + player.Orientation = Vector3(0f, 0f, 90f) + player.Certifications += CertificationType.StandardAssault + player.Certifications += CertificationType.MediumAssault + player.Certifications += CertificationType.StandardExoSuit + player.Certifications += CertificationType.AgileExoSuit + player.Certifications += CertificationType.ReinforcedExoSuit + player.Certifications += CertificationType.ATV + player.Certifications += CertificationType.Harasser + player.Slot(0).Equipment = beamer1 + player.Slot(2).Equipment = suppressor1 + player.Slot(4).Equipment = forceblade1 + player.Slot(6).Equipment = AmmoBox(bullet_9mm) + player.Slot(9).Equipment = AmmoBox(bullet_9mm) + player.Slot(12).Equipment = AmmoBox(bullet_9mm) + player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) + player.Slot(36).Equipment = AmmoBox(energy_cell) + player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) + player.Slot(5).Equipment.get.asInstanceOf[LockerContainer].Inventory += 0 -> SimpleItem(remote_electronics_kit) + //TODO end temp player character auto-loading self ! ListAccountCharacters import scala.concurrent.duration._ @@ -778,14 +875,6 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, TimeOfDayMessage(1191182336))) sendResponse(PacketCoding.CreateGamePacket(0, ReplicationStreamMessage(5, Some(6), Vector(SquadListing())))) //clear squad list - //load active players in zone - LivePlayerList.ZonePopulation(continent.Number, _ => true).foreach(char => { - sendResponse( - PacketCoding.CreateGamePacket(0, - ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get) - ) - ) - }) //render Equipment that was dropped into zone before the player arrived continent.EquipmentOnGround.foreach(item => { val definition = item.Definition @@ -799,8 +888,54 @@ class WorldSessionActor extends Actor with MDCContextAware { ) ) }) + //load active players in zone + LivePlayerList.ZonePopulation(continent.Number, _ => true).foreach(char => { + sendResponse( + PacketCoding.CreateGamePacket(0, + ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get) + ) + ) + }) + //load active vehicles in zone + continent.Vehicles.foreach(vehicle => { + val definition = vehicle.Definition + sendResponse( + PacketCoding.CreateGamePacket(0, + ObjectCreateMessage( + definition.ObjectId, + vehicle.GUID, + definition.Packet.ConstructorData(vehicle).get + ) + ) + ) + //seat vehicle occupants + vehicle.Definition.MountPoints.values.foreach(seat_num => { + vehicle.Seat(seat_num).get.Occupant match { + case Some(tplayer) => + sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle.GUID, tplayer.GUID, seat_num))) + case None => ; + } + }) + ReloadVehicleAccessPermissions(vehicle) + }) + //TODO begin temp vehicle auto-loading + import net.psforever.objects.GlobalDefinitions._ + if(continent.Vehicles.isEmpty) { + harasser = Vehicle(two_man_assault_buggy) + harasser.Position = Vector3(3674.8438f, 2730.789f, 91.15625f) + harasser.Faction = PlanetSideEmpire.VS + harasser.Orientation = Vector3(0f, 0f, 90f) + harasser.Weapons(2).Equipment.get.asInstanceOf[Tool].AmmoSlots.head.Box = AmmoBox(bullet_12mm, 150) + harasser.Trunk += 30 -> AmmoBox(bullet_12mm, 100) + taskResolver ! RegisterNewVehicle(harasser) + } + else { + harasser = continent.Vehicles.head //subsequent players after first + } + //TODO end temp vehicle auto-loading avatarService ! Service.Join(player.Continent) localService ! Service.Join(player.Continent) + vehicleService ! Service.Join(player.Continent) self ! SetCurrentAvatar(player) case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) => @@ -812,16 +947,53 @@ class WorldSessionActor extends Actor with MDCContextAware { player.Jumping = is_jumping val wepInHand : Boolean = player.Slot(player.DrawnSlot).Equipment match { - case Some(item) => item.Definition == bolt_driver + case Some(item) => item.Definition == GlobalDefinitions.bolt_driver case None => false } - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlayerState(avatar_guid, msg, player.Spectator, wepInHand)) - //log.info("PlayerState: " + msg) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, msg, player.Spectator, wepInHand)) case msg @ ChildObjectStateMessage(object_guid, pitch, yaw) => + //the majority of the following check retrieves information to determine if we are in control of the child + player.VehicleSeated match { + case Some(vehicle_guid) => + continent.GUID(vehicle_guid) match { + case Some(obj : Vehicle) => + obj.PassengerInSeat(player) match { + case Some(seat_num) => + obj.WeaponControlledFromSeat(seat_num) match { + case Some(tool) => + if(tool.GUID == object_guid) { + //TODO set tool orientation? + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.ChildObjectState(player.GUID, object_guid, pitch, yaw)) + } + case None => + log.warn(s"ChildObjectState: player $player is not using stated controllable agent") + } + case None => + log.warn(s"ChildObjectState: player ${player.GUID} is not in a position to use controllable agent") + } + case _ => + log.warn(s"ChildObjectState: player $player's controllable agent not available in scope") + } + case None => + //TODO status condition of "playing getting out of vehicle to allow for late packets without warning + //log.warn(s"ChildObjectState: player $player not related to anything with a controllable agent") + } //log.info("ChildObjectState: " + msg) case msg @ VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk5, unk6, unk7, wheels, unk9, unkA) => + continent.GUID(vehicle_guid) match { + case Some(obj : Vehicle) => + if(obj.Seat(0).get.Occupant.contains(player)) { //we're driving the vehicle + obj.Position = pos + obj.Orientation = ang + obj.Velocity = vel + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, pos, ang, vel, unk5, unk6, unk7, wheels, unk9, unkA)) + } + //TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle + case _ => + log.warn(s"VehicleState: no vehicle $vehicle_guid found in zone") + } //log.info("VehicleState: " + msg) case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vector, unk1, unk2, unk3, unk4, time_alive) => @@ -892,19 +1064,34 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ReloadMessage(item_guid, ammo_clip, unk1) => log.info("Reload: " + msg) - val reloadValue = player.Slot(player.DrawnSlot).Equipment match { - case Some(item) => - item match { - case tool : Tool => - tool.FireMode.Magazine - case _ => + val reloadValue : Int = player.VehicleSeated match { + case Some(vehicle_guid) => //weapon is vehicle turret? + continent.GUID(vehicle_guid) match { + case Some(vehicle : Vehicle) => + vehicle.PassengerInSeat(player) match { + case Some(seat_num) => + vehicle.WeaponControlledFromSeat(seat_num) match { + case Some(item : Tool) => + item.FireMode.Magazine + case _ => ; + 0 + } + case None => ; + 0 + } + case _ => ; + 0 + } + case None => //not in vehicle; weapon in hand? + player.Slot(player.DrawnSlot).Equipment match { //TODO check that item in hand is item_guid? + case Some(item : Tool) => + item.FireMode.Magazine + case Some(_) | None => ; 0 } - case None => - 0 } - //TODO hunt for ammunition in inventory if(reloadValue > 0) { + //TODO hunt for ammunition in backpack/trunk sendResponse(PacketCoding.CreateGamePacket(0, ReloadMessage(item_guid, reloadValue, unk1))) } @@ -939,14 +1126,32 @@ class WorldSessionActor extends Actor with MDCContextAware { } case msg @ RequestDestroyMessage(object_guid) => - // TODO: Make sure this is the correct response in all cases - player.Find(object_guid) match { - case Some(slot) => - taskResolver ! RemoveEquipmentFromSlot(player, player.Slot(slot).Equipment.get, slot) - log.info("RequestDestroy: " + msg) + // TODO: Make sure this is the correct response for all cases + continent.GUID(object_guid) match { + case Some(vehicle : Vehicle) => + if(player.VehicleOwned.contains(object_guid) && vehicle.Owner.contains(player.GUID)) { + vehicleService ! VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent) + log.info(s"RequestDestroy: vehicle $object_guid") + } + else { + log.info(s"RequestDestroy: must own vehicle $object_guid in order to deconstruct it") + } + + case Some(obj : Equipment) => + player.Find(object_guid) match { //player should be holding it + case Some(slot) => + taskResolver ! RemoveEquipmentFromSlot(player, player.Slot(slot).Equipment.get, slot) + log.info(s"RequestDestroy: equipment $object_guid") + case None => + sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(object_guid, 0))) + log.warn(s"RequestDestroy: object $object_guid not found in player hands") + } + case None => - sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(object_guid, 0))) log.warn(s"RequestDestroy: object $object_guid not found") + + case _ => + log.warn(s"RequestDestroy: not allowed to delete object $object_guid") } case msg @ ObjectDeleteMessage(object_guid, unk1) => @@ -1068,6 +1273,19 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ => ; } + case Some(obj : Vehicle) => + if(obj.Faction == player.Faction) { + player.Slot(player.DrawnSlot).Equipment match { + case Some(tool : Tool) => + if(tool.Definition == GlobalDefinitions.nano_dispenser) { + //TODO repairing behavior + } + case Some(_) | None => + //TODO trunk access + sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))) + } + } + case Some(obj : PlanetSideGameObject) => if(itemType != 121) { sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))) @@ -1080,7 +1298,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case None => ; } - + case msg @ UnuseItemMessage(player_guid, item) => log.info("UnuseItem: " + msg) @@ -1136,12 +1354,79 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info("WarpgateRequest: " + msg) case msg @ MountVehicleMsg(player_guid, vehicle_guid, unk) => - sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle_guid,player_guid,0))) - log.info("MounVehicleMsg: "+msg) + //log.info("MountVehicleMsg: "+msg) + continent.GUID(vehicle_guid) match { + case Some(obj : Vehicle) => + obj.GetSeatFromMountPoint(unk) match { + case Some(seat_num) => + obj.Actor ! Vehicle.TrySeatPlayer(seat_num, player) + case None => + log.warn(s"MountVehicleMsg: attempted to board vehicle $vehicle_guid's seat $unk, but no seat exists there") + } + case None | Some(_) => + log.warn(s"MountVehicleMsg: not a vehicle") + } case msg @ DismountVehicleMsg(player_guid, unk1, unk2) => - sendResponse(PacketCoding.CreateGamePacket(0, msg)) //should be safe; replace with ObjectDetachMessage later - log.info("DismountVehicleMsg: " + msg) + //TODO optimize this later + log.info(s"DismountVehicleMsg: $msg") + if(player.GUID == player_guid) { + //normally disembarking from a seat + player.VehicleSeated match { + case Some(vehicle_guid) => + continent.GUID(vehicle_guid) match { + case Some(obj : Vehicle) => + obj.Seats.find(seat => seat.Occupant.contains(player)) match { + case Some(seat) => + val vel = obj.Velocity.getOrElse(new Vector3(0f, 0f, 0f)) + val total_vel : Int = math.abs(vel.x * vel.y * vel.z).toInt + if(seat.Bailable || obj.Velocity.isEmpty || total_vel == 0) { + seat.Occupant = None + player.VehicleSeated = None + sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, unk1, unk2))) //should be safe; replace with ObjectDetachMessage later + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, unk1, unk2)) + } + case None => + log.warn(s"DismountVehicleMsg: can not find where player $player_guid is seated in vehicle $vehicle_guid") + } + case _ => + log.warn(s"DismountVehicleMsg: can not find vehicle $vehicle_guid") + } + case None => + log.warn(s"DismountVehicleMsg: player $player_guid not considered seated in a vehicle") + } + } + else { + //kicking someone else out of a seat; need to own that seat + player.VehicleOwned match { + case Some(vehicle_guid) => + continent.GUID(player_guid) match { + case Some(tplayer : Player) => + if(tplayer.VehicleSeated.contains(vehicle_guid)) { + continent.GUID(vehicle_guid) match { + case Some(obj : Vehicle) => + obj.Seats.find(seat => seat.Occupant.contains(tplayer)) match { + case Some(seat) => + seat.Occupant = None + tplayer.VehicleSeated = None + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, unk1, unk2)) + case None => + log.warn(s"DismountVehicleMsg: can not find where player $player_guid is seated in vehicle $vehicle_guid") + } + case _ => + log.warn(s"DismountVehicleMsg: can not find vehicle $vehicle_guid") + } + } + else { + log.warn(s"DismountVehicleMsg: non-owner player $player trying to kick player $tplayer out of his seat") + } + case _ => + log.warn(s"DismountVehicleMsg: player $player_guid could not be found to kick") + } + case None => + log.warn(s"DismountVehicleMsg: $player does not own a vehicle") + } + } case msg @ DeployRequestMessage(player_guid, entity, unk1, unk2, unk3, pos) => //if you try to deploy, can not undeploy @@ -1162,9 +1447,46 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ BindPlayerMessage(action, bindDesc, unk1, logging, unk2, unk3, unk4, pos) => log.info("BindPlayerMessage: " + msg) - case msg @ PlanetsideAttributeMessage(avatar_guid, attribute_type, attribute_value) => + case msg @ PlanetsideAttributeMessage(object_guid, attribute_type, attribute_value) => log.info("PlanetsideAttributeMessage: "+msg) - sendResponse(PacketCoding.CreateGamePacket(0,PlanetsideAttributeMessage(avatar_guid, attribute_type, attribute_value))) + continent.GUID(object_guid) match { + case Some(vehicle : Vehicle) => + if(player.VehicleOwned.contains(vehicle.GUID)) { + if(9 < attribute_type && attribute_type < 14) { + vehicle.PermissionGroup(attribute_type, attribute_value) match { + case Some(allow) => + val group = AccessPermissionGroup(attribute_type - 10) + log.info(s"Vehicle attributes: vehicle ${vehicle.GUID} access permission $group changed to $allow") + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SeatPermissions(player.GUID, vehicle.GUID, attribute_type, attribute_value)) + //kick players who should not be seated in the vehicle due to permission changes + if(allow == VehicleLockState.Locked) { //TODO only important permission atm + vehicle.Definition.MountPoints.values.foreach(seat_num => { + val seat = vehicle.Seat(seat_num).get + seat.Occupant match { + case Some(tplayer) => + if(vehicle.SeatPermissionGroup(seat_num).contains(group) && tplayer != player) { + seat.Occupant = None + tplayer.VehicleSeated = None + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(tplayer.GUID, 4, false)) + } + case None => ; + } + }) + } + case None => ; + } + } + else { + log.warn(s"Vehicle attributes: unsupported change on vehicle $object_guid - $attribute_type") + } + } + else { + log.warn(s"Vehicle attributes: $player does not own vehicle ${vehicle.GUID} and can not change it") + } + case _ => + log.warn(s"echo unknown attributes behavior") + sendResponse(PacketCoding.CreateGamePacket(0,PlanetsideAttributeMessage(object_guid, attribute_type, attribute_value))) + } case msg @ BattleplanMessage(char_id, player_name, zonr_id, diagrams) => log.info("Battleplan: "+msg) @@ -1244,28 +1566,6 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - /** - * Iterate over a group of `EquipmentSlot`s, some of which may be occupied with an item. - * Use `func` on any discovered `Equipment` to transform items into tasking, and add the tasking to a `List`. - * @param iter the `Iterator` of `EquipmentSlot`s - * @param func the function used to build tasking from any discovered `Equipment` - * @param list a persistent `List` of `Equipment` tasking - * @return a `List` of `Equipment` tasking - */ - @tailrec private def recursiveHolsterTaskBuilding(iter : Iterator[EquipmentSlot], func : ((Equipment)=>TaskResolver.GiveTask), list : List[TaskResolver.GiveTask] = Nil) : List[TaskResolver.GiveTask] = { - if(!iter.hasNext) { - list - } - else { - iter.next.Equipment match { - case Some(item) => - recursiveHolsterTaskBuilding(iter, func, list :+ func(item)) - case None => - recursiveHolsterTaskBuilding(iter, func, list) - } - } - } - /** * Construct tasking that coordinates the following:
* 1) Accept a new piece of `Equipment` and register it with a globally unique identifier.
@@ -1273,17 +1573,18 @@ class WorldSessionActor extends Actor with MDCContextAware { * @param target what object will accept the new `Equipment` * @param obj the new `Equipment` * @param index the slot where the new `Equipment` will be placed - * @see `RegisterEquipment` + * @see `GUIDTask.RegisterEquipment` * @see `PutInSlot` + * @return a `TaskResolver.GiveTask` message */ - private def PutEquipmentInSlot(target : Player, obj : Equipment, index : Int) : Unit = { - val regTask = RegisterEquipment(obj) + private def PutEquipmentInSlot(target : Player, obj : Equipment, index : Int) : TaskResolver.GiveTask = { + val regTask = GUIDTask.RegisterEquipment(obj)(continent.GUID) obj match { case tool : Tool => val linearToolTask = TaskResolver.GiveTask(regTask.task) +: regTask.subs - taskResolver ! TaskResolver.GiveTask(PutInSlot(target, tool, index).task, linearToolTask) + TaskResolver.GiveTask(PutInSlot(target, tool, index).task, linearToolTask) case _ => - taskResolver ! TaskResolver.GiveTask(PutInSlot(target, obj, index).task, List(regTask)) + TaskResolver.GiveTask(PutInSlot(target, obj, index).task, List(regTask)) } } @@ -1294,72 +1595,18 @@ class WorldSessionActor extends Actor with MDCContextAware { * @param target the object that currently possesses the `Equipment` * @param obj the `Equipment` * @param index the slot from where the `Equipment` will be removed - * @see `UnregisterEquipment` + * @see `GUIDTask.UnregisterEquipment` * @see `RemoveFromSlot` + * @return a `TaskResolver.GiveTask` message */ - private def RemoveEquipmentFromSlot(target : Player, obj : Equipment, index : Int) : Unit = { - val regTask = UnregisterEquipment(obj) + private def RemoveEquipmentFromSlot(target : Player, obj : Equipment, index : Int) : TaskResolver.GiveTask = { + val regTask = GUIDTask.UnregisterEquipment(obj)(continent.GUID) //to avoid an error from a GUID-less object from being searchable, it is removed from the inventory first obj match { case _ : Tool => - taskResolver ! TaskResolver.GiveTask(regTask.task, RemoveFromSlot(target, obj, index) +: regTask.subs) + TaskResolver.GiveTask(regTask.task, RemoveFromSlot(target, obj, index) +: regTask.subs) case _ => - taskResolver ! TaskResolver.GiveTask(regTask.task, RemoveFromSlot(target, obj, index) :: Nil) - } - } - - /** - * Construct tasking that registers an object with the a globally unique identifier selected from a pool of numbers. - * The object in question is not considered to have any form of internal complexity. - * @param obj the object being registered - * @return a `TaskResolver.GiveTask` message - */ - private def RegisterObjectTask(obj : IdentifiableEntity) : TaskResolver.GiveTask = { - TaskResolver.GiveTask( - new Task() { - private val localObject = obj - private val localAccessor = continent.GUID - - override def isComplete : Task.Resolution.Value = { - try { - localObject.GUID - Task.Resolution.Success - } - catch { - case _ : Exception => - Task.Resolution.Incomplete - } - } - - def Execute(resolver : ActorRef) : Unit = { - localAccessor ! Register(localObject, "dynamic", resolver) - } - }) - } - - /** - * Construct tasking that registers an object that is an object of type `Tool`. - * `Tool` objects have internal structures called "ammo slots;" - * each ammo slot contains a register-able `AmmoBox` object. - * @param obj the object being registered - * @return a `TaskResolver.GiveTask` message - */ - private def RegisterTool(obj : Tool) : TaskResolver.GiveTask = { - val ammoTasks : List[TaskResolver.GiveTask] = (0 until obj.MaxAmmoSlot).map(ammoIndex => RegisterObjectTask(obj.AmmoSlots(ammoIndex).Box)).toList - TaskResolver.GiveTask(RegisterObjectTask(obj).task, ammoTasks) - } - - /** - * Construct tasking that registers an object, determining whether it is a complex object of type `Tool` or a more simple object type. - * @param obj the object being registered - * @return a `TaskResolver.GiveTask` message - */ - private def RegisterEquipment(obj : Equipment) : TaskResolver.GiveTask = { - obj match { - case tool : Tool => - RegisterTool(tool) - case _ => - RegisterObjectTask(obj) + TaskResolver.GiveTask(regTask.task, List(RemoveFromSlot(target, obj, index))) } } @@ -1377,6 +1624,7 @@ class WorldSessionActor extends Actor with MDCContextAware { private val localIndex = index private val localObject = obj private val localAnnounce = self + private val localService = avatarService override def isComplete : Task.Resolution.Value = { if(localTarget.Slot(localIndex).Equipment.contains(localObject)) { @@ -1405,7 +1653,7 @@ class WorldSessionActor extends Actor with MDCContextAware { ) ) if(0 <= localIndex && localIndex < 5) { - avatarService ! AvatarServiceMessage(localTarget.Continent, AvatarAction.EquipmentInHand(localTarget.GUID, localIndex, localObject)) + localService ! AvatarServiceMessage(localTarget.Continent, AvatarAction.EquipmentInHand(localTarget.GUID, localIndex, localObject)) } } }) @@ -1418,14 +1666,6 @@ class WorldSessionActor extends Actor with MDCContextAware { * @return a `TaskResolver.GiveTask` message */ private def RegisterAvatar(tplayer : Player) : TaskResolver.GiveTask = { - val holsterTasks = recursiveHolsterTaskBuilding(tplayer.Holsters().iterator, RegisterEquipment) - val fifthHolsterTask = tplayer.Slot(5).Equipment match { - case Some(locker) => - RegisterObjectTask(locker) :: locker.asInstanceOf[LockerContainer].Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => RegisterEquipment(entry.obj)}).toList - case None => - List.empty[TaskResolver.GiveTask]; - } - val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => RegisterEquipment(entry.obj)}) TaskResolver.GiveTask( new Task() { private val localPlayer = tplayer @@ -1449,64 +1689,55 @@ class WorldSessionActor extends Actor with MDCContextAware { override def onFailure(ex : Throwable) : Unit = { localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WSA } - }, RegisterObjectTask(tplayer) +: (holsterTasks ++ fifthHolsterTask ++ inventoryTasks) + }, List(GUIDTask.RegisterAvatar(tplayer)(continent.GUID)) ) } - /** - * Construct tasking that un-registers an object. - * The object in question is not considered to have any form of internal complexity. - * @param obj the object being un-registered - * @return a `TaskResolver.GiveTask` message - */ - private def UnregisterObjectTask(obj : IdentifiableEntity) : TaskResolver.GiveTask = { + def RegisterVehicle(vehicle : Vehicle) : TaskResolver.GiveTask = { TaskResolver.GiveTask( new Task() { - private val localObject = obj - private val localAccessor = continent.GUID + private val localVehicle = vehicle + private val localAnnounce = self override def isComplete : Task.Resolution.Value = { - try { - localObject.GUID - Task.Resolution.Incomplete + if(localVehicle.HasGUID) { + Task.Resolution.Success } - catch { - case _ : Exception => - Task.Resolution.Success + else { + Task.Resolution.Incomplete } } def Execute(resolver : ActorRef) : Unit = { - localAccessor ! Unregister(localObject, resolver) + log.info(s"Vehicle $localVehicle is registered") + resolver ! scala.util.Success(this) + localAnnounce ! VehicleLoaded(localVehicle) //alerts WSA } - } + }, List(GUIDTask.RegisterVehicle(vehicle)(continent.GUID)) ) } - /** - * Construct tasking that un-registers an object that is an object of type `Tool`. - * `Tool` objects have internal structures called "ammo slots;" - * each ammo slot contains a register-able `AmmoBox` object. - * @param obj the object being un-registered - * @return a `TaskResolver.GiveTask` message - */ - private def UnregisterTool(obj : Tool) : TaskResolver.GiveTask = { - val ammoTasks : List[TaskResolver.GiveTask] = (0 until obj.MaxAmmoSlot).map(ammoIndex => UnregisterObjectTask(obj.AmmoSlots(ammoIndex).Box)).toList - TaskResolver.GiveTask(UnregisterObjectTask(obj).task, ammoTasks) - } + def RegisterNewVehicle(obj : Vehicle) : TaskResolver.GiveTask = { + TaskResolver.GiveTask( + new Task() { + private val localVehicle = obj + private val localAnnounce = vehicleService + private val localSession : String = sessionId.toString - /** - * Construct tasking that un-registers an object, determining whether it is a complex object of type `Tool` or a more simple object type. - * @param obj the object being registered - * @return a `TaskResolver.GiveTask` message - */ - private def UnregisterEquipment(obj : Equipment) : TaskResolver.GiveTask = { - obj match { - case tool : Tool => - UnregisterTool(tool) - case _ => - UnregisterObjectTask(obj) - } + override def isComplete : Task.Resolution.Value = { + if(localVehicle.Actor != ActorRef.noSender) { + Task.Resolution.Success + } + else { + Task.Resolution.Incomplete + } + } + + def Execute(resolver : ActorRef) : Unit = { + localAnnounce ! VehicleServiceMessage.GiveActorControl(obj, localSession) + resolver ! scala.util.Success(this) + } + }, List(RegisterVehicle(obj))) } /** @@ -1524,6 +1755,7 @@ class WorldSessionActor extends Actor with MDCContextAware { private val localObject = obj private val localObjectGUID = obj.GUID private val localAnnounce = self //self may not be the same when it executes + private val localService = avatarService override def isComplete : Task.Resolution.Value = { if(localTarget.Slot(localIndex).Equipment.contains(localObject)) { @@ -1542,28 +1774,11 @@ class WorldSessionActor extends Actor with MDCContextAware { override def onSuccess() : Unit = { localAnnounce ! ResponseToSelf(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(localObjectGUID, 0))) if(0 <= localIndex && localIndex < 5) { - avatarService ! AvatarServiceMessage(localTarget.Continent, AvatarAction.ObjectDelete(localTarget.GUID, localObjectGUID)) + localService ! AvatarServiceMessage(localTarget.Continent, AvatarAction.ObjectDelete(localTarget.GUID, localObjectGUID)) } } - }) - } - - /** - * Construct tasking that un-registers all aspects of a `Player` avatar. - * `Players` are complex objects that contain a variety of other register-able objects and each of these objects much be handled. - * @param tplayer the avatar `Player` - * @return a `TaskResolver.GiveTask` message - */ - private def UnregisterAvatar(tplayer : Player) : TaskResolver.GiveTask = { - val holsterTasks = recursiveHolsterTaskBuilding(tplayer.Holsters().iterator, UnregisterEquipment) - val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => UnregisterEquipment(entry.obj)}) - val fifthHolsterTask = tplayer.Slot(5).Equipment match { - case Some(locker) => - UnregisterObjectTask(locker) :: locker.asInstanceOf[LockerContainer].Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => UnregisterEquipment(entry.obj)}).toList - case None => - List.empty[TaskResolver.GiveTask]; - } - TaskResolver.GiveTask(UnregisterObjectTask(tplayer).task, holsterTasks ++ fifthHolsterTask ++ inventoryTasks) + } + ) } /** @@ -1645,6 +1860,26 @@ class WorldSessionActor extends Actor with MDCContextAware { localService ! LocalServiceMessage(continent.Id, LocalAction.HackTemporarily(player.GUID, continent, target, unk)) } + /** + * Temporary function that iterates over vehicle permissions and turns them into `PlanetsideAttributeMessage` packets.
+ *
+ * 2 November 2017 + * Unexpected behavior causes seat mount points to become blocked when a new driver claims the vehicle. + * For the purposes of ensuring that other players are always aware of the proper permission state of the trunk and seats, + * packets are intentionally dispatched to the current client to update the states. + * Perform this action just after any instance where the client would initially gain awareness of the vehicle. + * The most important examples include either the player or the vehicle itself spawning in for the first time. + * @param vehicle the `Vehicle` + */ + def ReloadVehicleAccessPermissions(vehicle : Vehicle) : Unit = { + val vehicle_guid = vehicle.GUID + (0 to 3).foreach(group => { + sendResponse(PacketCoding.CreateGamePacket(0, + PlanetsideAttributeMessage(vehicle_guid, group + 10, vehicle.PermissionGroup(group).get.id.toLong) + )) + }) + } + def failWithError(error : String) = { log.error(error) sendResponse(PacketCoding.CreateControlPacket(ConnectionClose())) @@ -1675,6 +1910,7 @@ object WorldSessionActor { private final case class PlayerFailedToLoad(tplayer : Player) private final case class ListAccountCharacters() private final case class SetCurrentAvatar(tplayer : Player) + private final case class VehicleLoaded(vehicle : Vehicle) /** * A message that indicates the user is using a remote electronics kit to hack some server object. diff --git a/pslogin/src/main/scala/scripts/GUIDTask.scala b/pslogin/src/main/scala/scripts/GUIDTask.scala new file mode 100644 index 00000000..0d04585b --- /dev/null +++ b/pslogin/src/main/scala/scripts/GUIDTask.scala @@ -0,0 +1,282 @@ +// Copyright (c) 2017 PSForever +package scripts + +/** + * The basic compiled tasks for assigning (registering) and revoking (unregistering) globally unique identifiers.
+ *
+ * Almost all of these functions will be invoked from `WorldSessionActor`. + * Some of the "unregistering" functions will invoke on delayed `Service` operations, + * indicating behavior that is not user/observer dependent. + * The object's (current) `Zone` must also be knowable since the GUID systems are tied to individual zones. + * For simplicity, all functions have the same format where the hook into the GUID system is an `implicit` parameter. + * It will get passed from the more complicated functions down into the less complicated functions, + * until it has found the basic number assignment functionality.
+ *
+ * All functions produce a `TaskResolver.GiveTask` container object that is expected to be used by a `TaskResolver`. + * These "task containers" can also be unpackaged into their tasks, sorted into other containers, + * and combined with other "task containers" to enact more complicated sequences of operations. + */ +object GUIDTask { + import akka.actor.ActorRef + import net.psforever.objects.entity.IdentifiableEntity + import net.psforever.objects.equipment.Equipment + import net.psforever.objects.guid.{Task, TaskResolver} + import net.psforever.objects.{EquipmentSlot, Player, Tool, Vehicle} + + import scala.annotation.tailrec + /** + * Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers.
+ *
+ * Regardless of the complexity of the object provided to this function, only the current depth will be assigned a GUID. + * This is the most basic operation that all objects that can be assigned a GUID must perform. + * @param obj the object being registered + * @param guid implicit reference to a unique number system + * @return a `TaskResolver.GiveTask` message + */ + def RegisterObjectTask(obj : IdentifiableEntity)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + TaskResolver.GiveTask( + new Task() { + private val localObject = obj + private val localAccessor = guid + + override def isComplete : Task.Resolution.Value = if(localObject.HasGUID) { + Task.Resolution.Success + } + else { + Task.Resolution.Incomplete + } + + def Execute(resolver : ActorRef) : Unit = { + import net.psforever.objects.guid.actor.Register + localAccessor ! Register(localObject, "dynamic", resolver) //TODO pool should not be hardcoded + } + }) + } + + /** + * Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, as a `Tool`.
+ *
+ * `Tool` objects are complicated by an internal structure informally called a "magazine feed." + * The objects in the magazine feed are called `AmmoBox` objects. + * Each `AmmoBox` object can be registered to a unique number system much like the `Tool` itself; and, + * each must be registered properly for the whole of the `Tool` to be communicated from the server to the client. + * While the matter has been abstracted for convenience, most `Tool` objects will have only one `AmmoBox` at a time + * and the common outlier will only be two.
+ *
+ * Do not invoke this function unless certain the object will be of type `Tool`, + * else use a more general function to differentiate between simple and complex objects. + * @param obj the `Tool` object being registered + * @param guid implicit reference to a unique number system + * @see `GUIDTask.RegisterEquipment` + * @return a `TaskResolver.GiveTask` message + */ + def RegisterTool(obj : Tool)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + val ammoTasks : List[TaskResolver.GiveTask] = (0 until obj.MaxAmmoSlot).map(ammoIndex => RegisterObjectTask(obj.AmmoSlots(ammoIndex).Box)).toList + TaskResolver.GiveTask(RegisterObjectTask(obj).task, ammoTasks) + } + + /** + * Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, + * after determining whether the object is complex (`Tool`) or simple.
+ *
+ * The objects in this case are specifically `Equipment`, a subclass of the basic register-able `IdentifiableEntity`. + * About five subclasses of `Equipment` exist, but they decompose into two groups - "complex objects" and "simple objects." + * "Simple objects" are most groups of `Equipment` and just their own GUID to be registered. + * "Complex objects" are just the `Tool` category of `Equipment`. + * They have internal objects that must also have their GUID's registered to function.
+ *
+ * Using this function when passing unknown `Equipment` is recommended. + * The type will be sorted and the object will be handled according to its complexity level. + * @param obj the `Equipment` object being registered + * @param guid implicit reference to a unique number system + * @return a `TaskResolver.GiveTask` message + */ + def RegisterEquipment(obj : Equipment)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + obj match { + case tool : Tool => + RegisterTool(tool) + case _ => + RegisterObjectTask(obj) + } + } + + /** + * Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, as a `Player`.
+ *
+ * `Player` objects are far more complicated than `Tools` (but they are not `Equipment`). + * A player has an inventory in which it can hold a countable number of `Equipment`; and, + * this inventory holds a sub-inventory with its own countable number of `Equipment`. + * Although a process of completing and inserting `Equipment` into the inventories that looks orderly can be written, + * this function assumes that the player is already fully composed. + * Use this function for an sudden introduction of the player into his environment + * (as defined by the scope of the unique number system). + * For working with processes concerning these "orderly insertions," + * a task built of lesser registration tasks and supporting tasks should be written instead. + * @param tplayer the `Player` object being registered + * @param guid implicit reference to a unique number system + * @return a `TaskResolver.GiveTask` message + */ + def RegisterAvatar(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + import net.psforever.objects.LockerContainer + import net.psforever.objects.inventory.InventoryItem + val holsterTasks = recursiveHolsterTaskBuilding(tplayer.Holsters().iterator, RegisterEquipment) + val fifthHolsterTask = tplayer.Slot(5).Equipment match { + case Some(locker) => + RegisterObjectTask(locker) :: locker.asInstanceOf[LockerContainer].Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => RegisterEquipment(entry.obj)}).toList + case None => + List.empty[TaskResolver.GiveTask]; + } + val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => RegisterEquipment(entry.obj)}) + TaskResolver.GiveTask(RegisterObjectTask(tplayer).task, holsterTasks ++ fifthHolsterTask ++ inventoryTasks) + } + + /** + * Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, as a `Vehicle`.
+ *
+ * `Vehicle` objects are far more complicated than `Tools` (but they are not `Equipment`). + * A vehicle has an inventory in which it can hold a countable number of `Equipment`; and, + * it may possess weapons (`Tools`, usually) that are firmly mounted on its outside. + * (This is similar to the holsters on a `Player` object but they can not be swapped out for other `Equipment` or for nothing.) + * Although a process of completing and inserting `Equipment` into the inventories that looks orderly can be written, + * this function assumes that the vehicle is already fully composed. + * Use this function for an sudden introduction of the vehicle into its environment + * (as defined by the scope of the unique number system). + * For working with processes concerning these "orderly insertions," + * a task built of lesser registration tasks and supporting tasks should be written instead. + * @param vehicle the `Vehicle` object being registered + * @param guid implicit reference to a unique number system + * @return a `TaskResolver.GiveTask` message + */ + 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 inventoryTasks = vehicle.Trunk.Items.map({ case((_ : Int, entry : InventoryItem)) => RegisterEquipment(entry.obj)}) + TaskResolver.GiveTask(RegisterObjectTask(vehicle).task, weaponTasks ++ inventoryTasks) + } + + /** + * Construct tasking that unregisters an object from a globally unique identifier system.
+ *
+ * This task performs an operation that reverses the effect of `RegisterObjectTask`. + * It is the most basic operation that all objects that can have their GUIDs revoked must perform. + * @param obj the object being unregistered + * @param guid implicit reference to a unique number system + * @see `GUIDTask.RegisterObjectTask` + * @return a `TaskResolver.GiveTask` message + */ + def UnregisterObjectTask(obj : IdentifiableEntity)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + TaskResolver.GiveTask( + new Task() { + private val localObject = obj + private val localAccessor = guid + + override def isComplete : Task.Resolution.Value = if(!localObject.HasGUID) { + Task.Resolution.Success + } + else { + Task.Resolution.Incomplete + } + + def Execute(resolver : ActorRef) : Unit = { + import net.psforever.objects.guid.actor.Unregister + localAccessor ! Unregister(localObject, resolver) + } + } + ) + } + + /** + * Construct tasking that unregisters a `Tool` object from a globally unique identifier system.
+ *
+ * This task performs an operation that reverses the effect of `RegisterTool`. + * @param obj the `Tool` object being unregistered + * @param guid implicit reference to a unique number system + * @see `GUIDTask.RegisterTool` + * @return a `TaskResolver.GiveTask` message + */ + def UnregisterTool(obj : Tool)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + val ammoTasks : List[TaskResolver.GiveTask] = (0 until obj.MaxAmmoSlot).map(ammoIndex => UnregisterObjectTask(obj.AmmoSlots(ammoIndex).Box)).toList + TaskResolver.GiveTask(UnregisterObjectTask(obj).task, ammoTasks) + } + + /** + * Construct tasking that unregisters an object from a globally unique identifier system + * after determining whether the object is complex (`Tool`) or simple.
+ *
+ * This task performs an operation that reverses the effect of `RegisterEquipment`. + * @param obj the `Equipment` object being unregistered + * @param guid implicit reference to a unique number system + * @see `GUIDTask.RegisterEquipment` + * @return a `TaskResolver.GiveTask` message + */ + def UnregisterEquipment(obj : Equipment)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + obj match { + case tool : Tool => + UnregisterTool(tool) + case _ => + UnregisterObjectTask(obj) + } + } + + /** + * Construct tasking that unregisters a `Player` object from a globally unique identifier system.
+ *
+ * This task performs an operation that reverses the effect of `RegisterAvatar`. + * @param tplayer the `Player` object being unregistered + * @param guid implicit reference to a unique number system + * @see `GUIDTask.RegisterAvatar` + * @return a `TaskResolver.GiveTask` message + */ + def UnregisterAvatar(tplayer : Player)(implicit guid : ActorRef) : TaskResolver.GiveTask = { + import net.psforever.objects.LockerContainer + import net.psforever.objects.inventory.InventoryItem + val holsterTasks = recursiveHolsterTaskBuilding(tplayer.Holsters().iterator, UnregisterEquipment) + val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => UnregisterEquipment(entry.obj)}) + val fifthHolsterTask = tplayer.Slot(5).Equipment match { + case Some(locker) => + UnregisterObjectTask(locker) :: locker.asInstanceOf[LockerContainer].Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => UnregisterEquipment(entry.obj)}).toList + case None => + List.empty[TaskResolver.GiveTask]; + } + TaskResolver.GiveTask(UnregisterObjectTask(tplayer).task, holsterTasks ++ fifthHolsterTask ++ inventoryTasks) + } + +/** + * Construct tasking that unregisters a `Vehicle` object from a globally unique identifier system.
+ *
+ * This task performs an operation that reverses the effect of `RegisterVehicle`. + * @param vehicle the `Vehicle` object being unregistered + * @param guid implicit reference to a unique number system + * @see `GUIDTask.RegisterVehicle` + * @return a `TaskResolver.GiveTask` message + */ + 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 inventoryTasks = vehicle.Trunk.Items.map({ case((_ : Int, entry : InventoryItem)) => UnregisterEquipment(entry.obj)}) + TaskResolver.GiveTask(UnregisterObjectTask(vehicle).task, weaponTasks ++ inventoryTasks) + } + + /** + * Iterate over a group of `EquipmentSlot`s, some of which may be occupied with an item. + * Use `func` on any discovered `Equipment` to transform items into tasking, and add the tasking to a `List`. + * @param iter the `Iterator` of `EquipmentSlot`s + * @param func the function used to build tasking from any discovered `Equipment`; + * strictly either `RegisterEquipment` or `UnregisterEquipment` + * @param list a persistent `List` of `Equipment` tasking + * @return a `List` of `Equipment` tasking + */ + @tailrec private def recursiveHolsterTaskBuilding(iter : Iterator[EquipmentSlot], func : ((Equipment)=>TaskResolver.GiveTask), list : List[TaskResolver.GiveTask] = Nil)(implicit guid : ActorRef) : List[TaskResolver.GiveTask] = { + if(!iter.hasNext) { + list + } + else { + iter.next.Equipment match { + case Some(item) => + recursiveHolsterTaskBuilding(iter, func, list :+ func(item)) + case None => + recursiveHolsterTaskBuilding(iter, func, list) + } + } + } +} \ No newline at end of file diff --git a/pslogin/src/main/scala/ServiceManager.scala b/pslogin/src/main/scala/services/ServiceManager.scala similarity index 99% rename from pslogin/src/main/scala/ServiceManager.scala rename to pslogin/src/main/scala/services/ServiceManager.scala index 40f2c4c0..91dcb449 100644 --- a/pslogin/src/main/scala/ServiceManager.scala +++ b/pslogin/src/main/scala/services/ServiceManager.scala @@ -1,3 +1,5 @@ +package services + // Copyright (c) 2017 PSForever import akka.actor.{Actor, ActorIdentity, ActorRef, ActorSystem, Identify, Props} diff --git a/pslogin/src/main/scala/services/avatar/AvatarResponse.scala b/pslogin/src/main/scala/services/avatar/AvatarResponse.scala new file mode 100644 index 00000000..87f37e84 --- /dev/null +++ b/pslogin/src/main/scala/services/avatar/AvatarResponse.scala @@ -0,0 +1,28 @@ +// Copyright (c) 2017 PSForever +package services.avatar + +import net.psforever.objects.equipment.Equipment +import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream} +import net.psforever.packet.game.objectcreate.ConstructorData +import net.psforever.types.{ExoSuitType, Vector3} + +object AvatarResponse { + trait Response + + final case class ArmorChanged(suit : ExoSuitType.Value, subtype : Int) extends Response + //final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Response + final case class EquipmentInHand(slot : Int, item : Equipment) extends Response + final case class EquipmentOnGround(pos : Vector3, orient : Vector3, item : Equipment) extends Response + final case class LoadPlayer(pdata : ConstructorData) extends Response + // final case class unLoadMap() extends Response + // final case class LoadMap() extends Response + final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response + final case class ObjectHeld(slot : Int) extends Response + final case class PlanetSideAttribute(attribute_type : Int, attribute_value : Long) extends Response + final case class PlayerState(msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Response + final case class Reload(mag : Int) extends Response + // final case class PlayerStateShift(itemID : PlanetSideGUID) extends Response + // final case class DestroyDisplay(itemID : PlanetSideGUID) extends Response + // final case class HitHintReturn(itemID : PlanetSideGUID) extends Response + // final case class ChangeWeapon(facingYaw : Int) extends Response +} diff --git a/pslogin/src/main/scala/services/avatar/AvatarService.scala b/pslogin/src/main/scala/services/avatar/AvatarService.scala index 5b656c23..0e3c6f16 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarService.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarService.scala @@ -31,39 +31,39 @@ class AvatarService extends Actor { action match { case AvatarAction.ArmorChanged(player_guid, suit, subtype) => AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.ArmorChanged(suit, subtype)) + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ArmorChanged(suit, subtype)) ) case AvatarAction.EquipmentInHand(player_guid, slot, obj) => AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.EquipmentInHand(slot, obj)) + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EquipmentInHand(slot, obj)) ) case AvatarAction.EquipmentOnGround(player_guid, pos, orient, obj) => AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.EquipmentOnGround(pos, orient, obj)) + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EquipmentOnGround(pos, orient, obj)) ) case AvatarAction.LoadPlayer(player_guid, pdata) => AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.LoadPlayer(pdata)) + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadPlayer(pdata)) ) case AvatarAction.ObjectDelete(player_guid, item_guid, unk) => AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.ObjectDelete(item_guid, unk)) + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ObjectDelete(item_guid, unk)) ) case AvatarAction.ObjectHeld(player_guid, slot) => AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.ObjectHeld(slot)) + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ObjectHeld(slot)) ) case AvatarAction.PlanetsideAttribute(guid, attribute_type, attribute_value) => AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarServiceResponse.PlanetSideAttribute(attribute_type, attribute_value)) + AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarResponse.PlanetSideAttribute(attribute_type, attribute_value)) ) case AvatarAction.PlayerState(guid, msg, spectator, weapon) => AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarServiceResponse.PlayerState(msg, spectator, weapon)) + AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarResponse.PlayerState(msg, spectator, weapon)) ) case AvatarAction.Reload(player_guid, mag) => AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.Reload(mag)) + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.Reload(mag)) ) case _ => ; } diff --git a/pslogin/src/main/scala/services/avatar/AvatarServiceResponse.scala b/pslogin/src/main/scala/services/avatar/AvatarServiceResponse.scala index 29e05ed6..0d31425e 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarServiceResponse.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarServiceResponse.scala @@ -1,34 +1,10 @@ // Copyright (c) 2017 PSForever package services.avatar -import net.psforever.objects.equipment.Equipment -import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream} -import net.psforever.packet.game.objectcreate.ConstructorData -import net.psforever.types.{ExoSuitType, Vector3} +import net.psforever.packet.game.PlanetSideGUID import services.GenericEventBusMsg final case class AvatarServiceResponse(toChannel : String, avatar_guid : PlanetSideGUID, - replyMessage : AvatarServiceResponse.Response + replyMessage : AvatarResponse.Response ) extends GenericEventBusMsg - -object AvatarServiceResponse { - trait Response - - final case class ArmorChanged(suit : ExoSuitType.Value, subtype : Int) extends Response - //final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Response - final case class EquipmentInHand(slot : Int, item : Equipment) extends Response - final case class EquipmentOnGround(pos : Vector3, orient : Vector3, item : Equipment) extends Response - final case class LoadPlayer(pdata : ConstructorData) extends Response -// final case class unLoadMap() extends Response -// final case class LoadMap() extends Response - final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response - final case class ObjectHeld(slot : Int) extends Response - final case class PlanetSideAttribute(attribute_type : Int, attribute_value : Long) extends Response - final case class PlayerState(msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Response - final case class Reload(mag : Int) extends Response -// final case class PlayerStateShift(itemID : PlanetSideGUID) extends Response -// final case class DestroyDisplay(itemID : PlanetSideGUID) extends Response -// final case class HitHintReturn(itemID : PlanetSideGUID) extends Response -// final case class ChangeWeapon(facingYaw : Int) extends Response -} diff --git a/pslogin/src/main/scala/services/local/LocalResponse.scala b/pslogin/src/main/scala/services/local/LocalResponse.scala new file mode 100644 index 00000000..72bd523f --- /dev/null +++ b/pslogin/src/main/scala/services/local/LocalResponse.scala @@ -0,0 +1,15 @@ +// Copyright (c) 2017 PSForever +package services.local + +import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound} +import net.psforever.types.Vector3 + +object LocalResponse { + trait Response + + final case class DoorOpens(door_guid : PlanetSideGUID) extends Response + final case class DoorCloses(door_guid : PlanetSideGUID) extends Response + final case class HackClear(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long) extends Response + final case class HackObject(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long) extends Response + final case class TriggerSound(sound : TriggeredSound.Value, pos : Vector3, unk : Int, volume : Float) extends Response +} diff --git a/pslogin/src/main/scala/services/local/LocalService.scala b/pslogin/src/main/scala/services/local/LocalService.scala index 56acf25d..bf5b109a 100644 --- a/pslogin/src/main/scala/services/local/LocalService.scala +++ b/pslogin/src/main/scala/services/local/LocalService.scala @@ -6,7 +6,6 @@ import services.local.support.{DoorCloseActor, HackClearActor} import services.{GenericEventBus, Service} class LocalService extends Actor { - //import LocalService._ private val doorCloser = context.actorOf(Props[DoorCloseActor], "local-door-closer") private val hackClearer = context.actorOf(Props[HackClearActor], "local-hack-clearer") private [this] val log = org.log4s.getLogger @@ -19,7 +18,7 @@ class LocalService extends Actor { def receive = { case Service.Join(channel) => - val path = s"/$channel/LocalEnvironment" + val path = s"/$channel/Local" val who = sender() log.info(s"$who has joined $path") LocalEvents.subscribe(who, path) @@ -33,24 +32,24 @@ class LocalService extends Actor { case LocalAction.DoorOpens(player_guid, zone, door) => doorCloser ! DoorCloseActor.DoorIsOpen(door, zone) LocalEvents.publish( - LocalServiceResponse(s"/$forChannel/LocalEnvironment", player_guid, LocalServiceResponse.DoorOpens(door.GUID)) + LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.DoorOpens(door.GUID)) ) case LocalAction.DoorCloses(player_guid, door_guid) => LocalEvents.publish( - LocalServiceResponse(s"/$forChannel/LocalEnvironment", player_guid, LocalServiceResponse.DoorCloses(door_guid)) + LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.DoorCloses(door_guid)) ) case LocalAction.HackClear(player_guid, target, unk1, unk2) => LocalEvents.publish( - LocalServiceResponse(s"/$forChannel/LocalEnvironment", player_guid, LocalServiceResponse.HackClear(target.GUID, unk1, unk2)) + LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.HackClear(target.GUID, unk1, unk2)) ) case LocalAction.HackTemporarily(player_guid, zone, target, unk1, unk2) => hackClearer ! HackClearActor.ObjectIsHacked(target, zone, unk1, unk2) LocalEvents.publish( - LocalServiceResponse(s"/$forChannel/LocalEnvironment", player_guid, LocalServiceResponse.HackObject(target.GUID, unk1, unk2)) + LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.HackObject(target.GUID, unk1, unk2)) ) case LocalAction.TriggerSound(player_guid, sound, pos, unk, volume) => LocalEvents.publish( - LocalServiceResponse(s"/$forChannel/LocalEnvironment", player_guid, LocalServiceResponse.TriggerSound(sound, pos, unk, volume)) + LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.TriggerSound(sound, pos, unk, volume)) ) case _ => ; } @@ -58,13 +57,13 @@ class LocalService extends Actor { //response from DoorCloseActor case DoorCloseActor.CloseTheDoor(door_guid, zone_id) => LocalEvents.publish( - LocalServiceResponse(s"/$zone_id/LocalEnvironment", Service.defaultPlayerGUID, LocalServiceResponse.DoorCloses(door_guid)) + LocalServiceResponse(s"/$zone_id/Local", Service.defaultPlayerGUID, LocalResponse.DoorCloses(door_guid)) ) //response from HackClearActor case HackClearActor.ClearTheHack(target_guid, zone_id, unk1, unk2) => LocalEvents.publish( - LocalServiceResponse(s"/$zone_id/LocalEnvironment", Service.defaultPlayerGUID, LocalServiceResponse.HackClear(target_guid, unk1, unk2)) + LocalServiceResponse(s"/$zone_id/Local", Service.defaultPlayerGUID, LocalResponse.HackClear(target_guid, unk1, unk2)) ) case msg => diff --git a/pslogin/src/main/scala/services/local/LocalServiceResponse.scala b/pslogin/src/main/scala/services/local/LocalServiceResponse.scala index 736732bc..4708feb5 100644 --- a/pslogin/src/main/scala/services/local/LocalServiceResponse.scala +++ b/pslogin/src/main/scala/services/local/LocalServiceResponse.scala @@ -1,21 +1,10 @@ // Copyright (c) 2017 PSForever package services.local -import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound} -import net.psforever.types.Vector3 +import net.psforever.packet.game.PlanetSideGUID import services.GenericEventBusMsg final case class LocalServiceResponse(toChannel : String, avatar_guid : PlanetSideGUID, - replyMessage : LocalServiceResponse.Response + replyMessage : LocalResponse.Response ) extends GenericEventBusMsg - -object LocalServiceResponse { - trait Response - - final case class DoorOpens(door_guid : PlanetSideGUID) extends Response - final case class DoorCloses(door_guid : PlanetSideGUID) extends Response - final case class HackClear(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long) extends Response - final case class HackObject(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long) extends Response - final case class TriggerSound(sound : TriggeredSound.Value, pos : Vector3, unk : Int, volume : Float) extends Response -} diff --git a/pslogin/src/main/scala/services/vehicle/VehicleAction.scala b/pslogin/src/main/scala/services/vehicle/VehicleAction.scala new file mode 100644 index 00000000..03d700f4 --- /dev/null +++ b/pslogin/src/main/scala/services/vehicle/VehicleAction.scala @@ -0,0 +1,22 @@ +// Copyright (c) 2017 PSForever +package services.vehicle + +import net.psforever.objects.Vehicle +import net.psforever.objects.zones.Zone +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.packet.game.objectcreate.ConstructorData +import net.psforever.types.Vector3 + +object VehicleAction { + trait Action + + final case class Awareness(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID) extends Action + final case class ChildObjectState(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, pitch : Float, yaw : Float) extends Action + final case class DismountVehicle(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean) extends Action + final case class KickPassenger(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean) extends Action + final case class LoadVehicle(player_guid : PlanetSideGUID, vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Action + final case class MountVehicle(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, seat : Int) extends Action + final case class SeatPermissions(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, seat_group : Int, permission : Long) extends Action + final case class UnloadVehicle(player_guid : PlanetSideGUID, continent : Zone, vehicle : Vehicle) extends Action + final case class VehicleState(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, unk1 : Int, pos : Vector3, ang : Vector3, vel : Option[Vector3], unk2 : Option[Int], unk3 : Int, unk4 : Int, wheel_direction : Int, unk5 : Boolean, unk6 : Boolean) extends Action +} diff --git a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala new file mode 100644 index 00000000..8806e192 --- /dev/null +++ b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala @@ -0,0 +1,21 @@ +// Copyright (c) 2017 PSForever +package services.vehicle + +import net.psforever.objects.Vehicle +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.packet.game.objectcreate.ConstructorData +import net.psforever.types.Vector3 + +object VehicleResponse { + trait Response + + final case class Awareness(vehicle_guid : PlanetSideGUID) extends Response + final case class ChildObjectState(object_guid : PlanetSideGUID, pitch : Float, yaw : Float) extends Response + final case class DismountVehicle(unk1 : Int, unk2 : Boolean) extends Response + final case class KickPassenger(unk1 : Int, unk2 : Boolean) extends Response + final case class LoadVehicle(vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Response + final case class MountVehicle(object_guid : PlanetSideGUID, seat : Int) extends Response + final case class SeatPermissions(vehicle_guid : PlanetSideGUID, seat_group : Int, permission : Long) extends Response + final case class UnloadVehicle(vehicle_guid : PlanetSideGUID) extends Response + final case class VehicleState(vehicle_guid : PlanetSideGUID, unk1 : Int, pos : Vector3, ang : Vector3, vel : Option[Vector3], unk2 : Option[Int], unk3 : Int, unk4 : Int, wheel_direction : Int, unk5 : Boolean, unk6 : Boolean) extends Response +} diff --git a/pslogin/src/main/scala/services/vehicle/VehicleService.scala b/pslogin/src/main/scala/services/vehicle/VehicleService.scala new file mode 100644 index 00000000..12842a95 --- /dev/null +++ b/pslogin/src/main/scala/services/vehicle/VehicleService.scala @@ -0,0 +1,91 @@ +// Copyright (c) 2017 PSForever +package services.vehicle + +import akka.actor.{Actor, ActorRef, Props} +import services.vehicle.support.{DeconstructionActor, VehicleContextActor} +import services.{GenericEventBus, Service} + +class VehicleService extends Actor { + private val vehicleContext : ActorRef = context.actorOf(Props[VehicleContextActor], "vehicle-context-root") + private val vehicleDecon : ActorRef = context.actorOf(Props[DeconstructionActor], "vehicle-decon-agent") + vehicleDecon ! DeconstructionActor.RequestTaskResolver + private [this] val log = org.log4s.getLogger + + override def preStart = { + log.info("Starting...") + } + + val VehicleEvents = new GenericEventBus[VehicleServiceResponse] + + def receive = { + case Service.Join(channel) => + val path = s"/$channel/Vehicle" + val who = sender() + + log.info(s"$who has joined $path") + + VehicleEvents.subscribe(who, path) + case Service.Leave() => + VehicleEvents.unsubscribe(sender()) + case Service.LeaveAll() => + VehicleEvents.unsubscribe(sender()) + + case VehicleServiceMessage(forChannel, action) => + action match { + case VehicleAction.Awareness(player_guid, vehicle_guid) => + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.Awareness(vehicle_guid)) + ) + case VehicleAction.ChildObjectState(player_guid, object_guid, pitch, yaw) => + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.ChildObjectState(object_guid, pitch, yaw)) + ) + case VehicleAction.DismountVehicle(player_guid, unk1, unk2) => + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.DismountVehicle(unk1, unk2)) + ) + case VehicleAction.KickPassenger(player_guid, unk1, unk2) => + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickPassenger(unk1, unk2)) + ) + case VehicleAction.LoadVehicle(player_guid, vehicle, vtype, vguid, vdata) => + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.LoadVehicle(vehicle, vtype, vguid, vdata)) + ) + case VehicleAction.MountVehicle(player_guid, vehicle_guid, seat) => + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.MountVehicle(vehicle_guid, seat)) + ) + case VehicleAction.SeatPermissions(player_guid, vehicle_guid, seat_group, permission) => + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission)) + ) + case VehicleAction.VehicleState(player_guid, vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6) => + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.VehicleState(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6)) + ) + case _ => ; + } + + //message to VehicleContext + case VehicleServiceMessage.GiveActorControl(vehicle, actorName) => + vehicleContext ! VehicleServiceMessage.GiveActorControl(vehicle, actorName) + + //message to VehicleContext + case VehicleServiceMessage.RevokeActorControl(vehicle) => + vehicleContext ! VehicleServiceMessage.RevokeActorControl(vehicle) + + //message to DeconstructionActor + case VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent) => + vehicleDecon ! DeconstructionActor.RequestDeleteVehicle(vehicle, continent) + + //response from DeconstructionActor + case DeconstructionActor.DeleteVehicle(vehicle_guid, zone_id) => + VehicleEvents.publish( + VehicleServiceResponse(s"/$zone_id/Vehicle", Service.defaultPlayerGUID, VehicleResponse.UnloadVehicle(vehicle_guid)) + ) + + case msg => + log.info(s"Unhandled message $msg from $sender") + } +} diff --git a/pslogin/src/main/scala/services/vehicle/VehicleServiceMessage.scala b/pslogin/src/main/scala/services/vehicle/VehicleServiceMessage.scala new file mode 100644 index 00000000..c3e1c480 --- /dev/null +++ b/pslogin/src/main/scala/services/vehicle/VehicleServiceMessage.scala @@ -0,0 +1,13 @@ +// Copyright (c) 2017 PSForever +package services.vehicle + +import net.psforever.objects.Vehicle +import net.psforever.objects.zones.Zone + +final case class VehicleServiceMessage(forChannel : String, actionMessage : VehicleAction.Action) + +object VehicleServiceMessage { + final case class GiveActorControl(vehicle : Vehicle, actorName : String) + final case class RevokeActorControl(vehicle : Vehicle) + final case class RequestDeleteVehicle(vehicle : Vehicle, continent : Zone) +} diff --git a/pslogin/src/main/scala/services/vehicle/VehicleServiceResponse.scala b/pslogin/src/main/scala/services/vehicle/VehicleServiceResponse.scala new file mode 100644 index 00000000..c8012873 --- /dev/null +++ b/pslogin/src/main/scala/services/vehicle/VehicleServiceResponse.scala @@ -0,0 +1,11 @@ +// Copyright (c) 2017 PSForever +package services.vehicle + +import net.psforever.packet.game.PlanetSideGUID +import services.GenericEventBusMsg + +final case class VehicleServiceResponse(toChannel : String, + avatar_guid : PlanetSideGUID, + replyMessage : VehicleResponse.Response + ) extends GenericEventBusMsg + diff --git a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala new file mode 100644 index 00000000..6ad68310 --- /dev/null +++ b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala @@ -0,0 +1,181 @@ +// Copyright (c) 2017 PSForever +package services.vehicle.support + +import akka.actor.{Actor, ActorRef, Cancellable} +import net.psforever.objects.Vehicle +import net.psforever.objects.vehicles.Seat +import net.psforever.objects.zones.Zone +import net.psforever.packet.game.PlanetSideGUID +import scripts.GUIDTask +import services.ServiceManager +import services.ServiceManager.Lookup +import services.vehicle.{VehicleAction, VehicleServiceMessage} + +import scala.annotation.tailrec +import scala.concurrent.duration._ + +/** + * Manage a previously-functioning vehicle as it is being deconstructed.
+ *
+ * A reference to a vehicle should be passed to this object as soon as it is going to be cleaned-up from the game world. + * Once accepted, only a few seconds will remain before the vehicle is deleted. + * To ensure that no players are lost in the deletion, all occupants of the vehicle are kicked out. + * Furthermore, the vehicle is rendered "dead" and inaccessible right up to the point where it is removed. + */ +class DeconstructionActor extends Actor { + /** The periodic `Executor` that scraps the next vehicle on the list */ + private var scrappingProcess : Cancellable = DeconstructionActor.DefaultProcess + /** A `List` of currently doomed vehicles */ + private var vehicles : List[DeconstructionActor.VehicleEntry] = Nil + /** The manager that helps unregister the vehicle from its current GUID scope */ + private var taskResolver : ActorRef = Actor.noSender + //private[this] val log = org.log4s.getLogger + + + def receive : Receive = { + /* + ask for a resolver to deal with the GUID system + when the TaskResolver is finally delivered, switch over to a behavior that actually deals with submitted vehicles + */ + case DeconstructionActor.RequestTaskResolver => + ServiceManager.serviceManager ! Lookup("taskResolver") + + case ServiceManager.LookupResult("taskResolver", endpoint) => + taskResolver = endpoint + context.become(Processing) + + case _ => ; + } + + def Processing : Receive = { + case DeconstructionActor.RequestDeleteVehicle(vehicle, zone, time) => + vehicles = vehicles :+ DeconstructionActor.VehicleEntry(vehicle, zone, time) + vehicle.Actor ! Vehicle.PrepareForDeletion + //kick everyone out + vehicle.Definition.MountPoints.values.foreach(seat_num => { + val zone_id : String = zone.Id + val seat : Seat = vehicle.Seat(seat_num).get + seat.Occupant match { + case Some(tplayer) => + seat.Occupant = None + tplayer.VehicleSeated = None + context.parent ! VehicleServiceMessage(zone_id, VehicleAction.KickPassenger(tplayer.GUID, 4, false)) + case None => ; + } + }) + if(vehicles.size == 1) { //we were the only entry so the event must be started from scratch + import scala.concurrent.ExecutionContext.Implicits.global + scrappingProcess = context.system.scheduler.scheduleOnce(DeconstructionActor.timeout, self, DeconstructionActor.TryDeleteVehicle()) + } + + case DeconstructionActor.TryDeleteVehicle() => + scrappingProcess.cancel + val now : Long = System.nanoTime + val (vehiclesToScrap, vehiclesRemain) = PartitionEntries(vehicles, now) + vehicles = vehiclesRemain + vehiclesToScrap.foreach(entry => { + val vehicle = entry.vehicle + val zone = entry.zone + entry.zone.Transport ! Zone.DespawnVehicle(vehicle) + context.parent ! DeconstructionActor.DeleteVehicle(vehicle.GUID, zone.Id) //call up to the main event system + context.parent ! VehicleServiceMessage.RevokeActorControl(vehicle) //call up to a sibling manager + taskResolver ! GUIDTask.UnregisterVehicle(vehicle)(zone.GUID) + }) + + if(vehiclesRemain.nonEmpty) { + val short_timeout : FiniteDuration = math.max(1, DeconstructionActor.timeout_time - (now - vehiclesRemain.head.time)) nanoseconds + import scala.concurrent.ExecutionContext.Implicits.global + scrappingProcess = context.system.scheduler.scheduleOnce(short_timeout, self, DeconstructionActor.TryDeleteVehicle()) + } + + case _ => ; + } + + /** + * Iterate over entries in a `List` until an entry that does not exceed the time limit is discovered. + * Separate the original `List` into two: + * a `List` of elements that have exceeded the time limit, + * and a `List` of elements that still satisfy the time limit. + * As newer entries to the `List` will always resolve later than old ones, + * and newer entries are always added to the end of the main `List`, + * processing in order is always correct. + * @param list the `List` of entries to divide + * @param now the time right now (in nanoseconds) + * @see `List.partition` + * @return a `Tuple` of two `Lists`, whose qualifications are explained above + */ + private def PartitionEntries(list : List[DeconstructionActor.VehicleEntry], now : Long) : (List[DeconstructionActor.VehicleEntry], List[DeconstructionActor.VehicleEntry]) = { + val n : Int = recursivePartitionEntries(list.iterator, now) + (list.take(n), list.drop(n)) //take and drop so to always return new lists + } + + /** + * Mark the index where the `List` of elements can be divided into two: + * a `List` of elements that have exceeded the time limit, + * and a `List` of elements that still satisfy the time limit. + * @param iter the `Iterator` of entries to divide + * @param now the time right now (in nanoseconds) + * @param index a persistent record of the index where list division should occur; + * defaults to 0 + * @return the index where division will occur + */ + @tailrec private def recursivePartitionEntries(iter : Iterator[DeconstructionActor.VehicleEntry], now : Long, index : Int = 0) : Int = { + if(!iter.hasNext) { + index + } + else { + val entry = iter.next() + if(now - entry.time >= DeconstructionActor.timeout_time) { + recursivePartitionEntries(iter, now, index + 1) + } + else { + index + } + } + } +} + +object DeconstructionActor { + /** The wait before completely deleting a vehicle; as a Long for calculation simplicity */ + private final val timeout_time : Long = 5000000000L //nanoseconds (5s) + /** The wait before completely deleting a vehicle; as a `FiniteDuration` for `Executor` simplicity */ + private final val timeout : FiniteDuration = timeout_time nanoseconds + + private final val DefaultProcess : Cancellable = new Cancellable() { + override def cancel : Boolean = true + override def isCancelled : Boolean = true + } + + final case class RequestTaskResolver() + + /** + * Message that carries information about a vehicle to be deconstructed. + * @param vehicle the `Vehicle` object + * @param zone the `Zone` in which the vehicle resides + * @param time when the vehicle was doomed + * @see `VehicleEntry` + */ + final case class RequestDeleteVehicle(vehicle : Vehicle, zone : Zone, time : Long = System.nanoTime()) + /** + * Message that carries information about a vehicle to be deconstructed. + * Prompting, as compared to `RequestDeleteVehicle` which is reactionary. + * @param vehicle_guid the vehicle + * @param zone_id the `Zone` in which the vehicle resides + */ + final case class DeleteVehicle(vehicle_guid : PlanetSideGUID, zone_id : String) + /** + * Internal message used to signal a test of the queued vehicle information. + */ + private final case class TryDeleteVehicle() + + /** + * Entry of vehicle information. + * The `zone` is maintained separately as a necessity, required to complete the deletion of the vehicle + * via unregistering of the vehicle and all related, registered objects. + * @param vehicle the `Vehicle` object + * @param zone the `Zone` in which the vehicle resides + * @param time when the vehicle was doomed + * @see `RequestDeleteVehicle` + */ + private final case class VehicleEntry(vehicle : Vehicle, zone : Zone, time : Long) +} diff --git a/pslogin/src/main/scala/services/vehicle/support/VehicleContextActor.scala b/pslogin/src/main/scala/services/vehicle/support/VehicleContextActor.scala new file mode 100644 index 00000000..756a31a3 --- /dev/null +++ b/pslogin/src/main/scala/services/vehicle/support/VehicleContextActor.scala @@ -0,0 +1,30 @@ +// Copyright (c) 2017 PSForever +package services.vehicle.support + +import akka.actor.{Actor, Props} +import net.psforever.objects.vehicles.VehicleControl +import services.vehicle.VehicleServiceMessage + +/** + * Provide a context for a `Vehicle` `Actor` - the `VehicleControl`.
+ *
+ * A vehicle can be passed between different zones and, therefore, does not belong to the zone. + * A vehicle cna be given to different players and can persist and change though players have gone. + * Therefore, also does not belong to `WorldSessionActor`. + * A vehicle must anchored to something that exists outside of the `InterstellarCluster` and its agents.
+ *
+ * The only purpose of this `Actor` is to allow vehicles to borrow a context for the purpose of `Actor` creation. + * It is also be allowed to be responsible for cleaning up that context. + * (In reality, it can be cleaned up anywhere a `PoisonPill` can be sent.) + */ +class VehicleContextActor() extends Actor { + def receive : Receive = { + case VehicleServiceMessage.GiveActorControl(vehicle, actorName) => + vehicle.Actor = context.actorOf(Props(classOf[VehicleControl], vehicle), s"${vehicle.Definition.Name}_$actorName") + + case VehicleServiceMessage.RevokeActorControl(vehicle) => + vehicle.Actor ! akka.actor.PoisonPill + + case _ => ; + } +} From f24911cde8cf9d16c9e1db6f76e1e0ef4f37cd91 Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 6 Nov 2017 10:24:36 -0500 Subject: [PATCH 3/4] tests for a variety of objects and scripts (currently 810); minor changes to align with proper object behavior --- .../psforever/objects/GlobalDefinitions.scala | 6 +- .../scala/net/psforever/objects/Vehicle.scala | 29 ++- .../psforever/objects/guid}/GUIDTask.scala | 5 +- .../objects/guid/pool/ExclusivePool.scala | 2 +- .../objects/guid/pool/SimplePool.scala | 2 +- .../inventory/AccessibleInventory.scala | 13 - .../objects/serverobject/doors/Door.scala | 15 +- .../serverobject/terminals/Terminal.scala | 4 +- .../vehicles/AccessPermissionGroup.scala | 2 +- .../net/psforever/objects/zones/Zone.scala | 9 +- .../net/psforever/objects/zones/ZoneMap.scala | 4 +- common/src/test/scala/objects/ActorTest.scala | 2 +- .../test/scala/objects/ConverterTest.scala | 2 +- common/src/test/scala/objects/DoorTest.scala | 91 +++++++ .../src/test/scala/objects/GUIDTaskTest.scala | 245 ++++++++++++++++++ .../src/test/scala/objects/IFFLockTest.scala | 66 +++++ .../src/test/scala/objects/VehicleTest.scala | 165 +++++++++++- common/src/test/scala/objects/ZoneTest.scala | 104 ++++++++ .../{ => number}/NumberPoolActorTest.scala | 7 +- .../{ => number}/NumberPoolHubTest.scala | 2 +- .../objects/{ => number}/NumberPoolTest.scala | 3 +- .../{ => number}/NumberSelectorTest.scala | 3 +- .../{ => number}/NumberSourceTest.scala | 4 +- .../{ => number}/UniqueNumberSystemTest.scala | 51 ++-- .../objects/terminal/CertTerminalTest.scala | 47 ++++ .../objects/terminal/OrderTerminalTest.scala | 85 ++++++ .../terminal/TerminalControlTest.scala | 73 ++++++ .../src/main/scala/WorldSessionActor.scala | 5 +- .../vehicle/VehicleServiceResponse.scala | 1 - .../vehicle/support/DeconstructionActor.scala | 2 +- 30 files changed, 945 insertions(+), 104 deletions(-) rename {pslogin/src/main/scala/scripts => common/src/main/scala/net/psforever/objects/guid}/GUIDTask.scala (99%) delete mode 100644 common/src/main/scala/net/psforever/objects/inventory/AccessibleInventory.scala create mode 100644 common/src/test/scala/objects/DoorTest.scala create mode 100644 common/src/test/scala/objects/GUIDTaskTest.scala create mode 100644 common/src/test/scala/objects/IFFLockTest.scala create mode 100644 common/src/test/scala/objects/ZoneTest.scala rename common/src/test/scala/objects/{ => number}/NumberPoolActorTest.scala (91%) rename common/src/test/scala/objects/{ => number}/NumberPoolHubTest.scala (99%) rename common/src/test/scala/objects/{ => number}/NumberPoolTest.scala (99%) rename common/src/test/scala/objects/{ => number}/NumberSelectorTest.scala (99%) rename common/src/test/scala/objects/{ => number}/NumberSourceTest.scala (99%) rename common/src/test/scala/objects/{ => number}/UniqueNumberSystemTest.scala (87%) create mode 100644 common/src/test/scala/objects/terminal/CertTerminalTest.scala create mode 100644 common/src/test/scala/objects/terminal/OrderTerminalTest.scala create mode 100644 common/src/test/scala/objects/terminal/TerminalControlTest.scala diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 917fc452..dba9b469 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -1251,7 +1251,7 @@ object GlobalDefinitions { fury.Seats += 0 -> new SeatDefinition() fury.Seats(0).Bailable = true fury.Seats(0).ControlledWeapon = 1 - fury.MountPoints += 0 -> 0 + fury.MountPoints += 1 -> 0 fury.MountPoints += 2 -> 0 fury.Weapons += 1 -> fury_weapon_systema fury.TrunkSize = InventoryTile(11, 11) @@ -1262,7 +1262,7 @@ object GlobalDefinitions { quadassault.Seats += 0 -> new SeatDefinition() quadassault.Seats(0).Bailable = true quadassault.Seats(0).ControlledWeapon = 1 - quadassault.MountPoints += 0 -> 0 + quadassault.MountPoints += 1 -> 0 quadassault.MountPoints += 2 -> 0 quadassault.Weapons += 1 -> quadassault_weapon_system quadassault.TrunkSize = InventoryTile(11, 11) @@ -1273,7 +1273,7 @@ object GlobalDefinitions { quadstealth.CanCloak = true quadstealth.Seats += 0 -> new SeatDefinition() quadstealth.Seats(0).Bailable = true - quadstealth.MountPoints += 0 -> 0 + quadstealth.MountPoints += 1 -> 0 quadstealth.MountPoints += 2 -> 0 quadstealth.CanCloak = true quadstealth.TrunkSize = InventoryTile(11, 11) diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala index ab1608b2..19f0101d 100644 --- a/common/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala @@ -71,10 +71,14 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ this.owner } + def Owner_=(owner : PlanetSideGUID) : Option[PlanetSideGUID] = Owner_=(Some(owner)) + + def Owner_=(owner : Player) : Option[PlanetSideGUID] = Owner_=(Some(owner.GUID)) + def Owner_=(owner : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = { owner match { case Some(_) => - if(Definition.CanBeOwned) { + if(Definition.CanBeOwned) { //e.g., base turrets this.owner = owner } case None => @@ -102,7 +106,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ def Shields_=(strength : Int) : Int = { this.shields = strength - strength + Shields } def MaxShields : Int = { @@ -252,12 +256,11 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ * @return a weapon, or `None` */ def ControlledWeapon(wepNumber : Int) : Option[Equipment] = { - val slot = this.weapons.get(wepNumber) - if(slot.isDefined) { - slot.get.Equipment - } - else { - None + weapons.get(wepNumber) match { + case Some(mount) => + mount.Equipment + case None => + None } } @@ -284,11 +287,11 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ } /** - * Given a valid seat number, retrieve an index where a weapon controlled from this seat is attached. + * Given a valid seat number, retrieve an index where the weapon controlled from this seat is mounted. * @param seatNumber the seat number * @return a mounted weapon by index, or `None` if either the seat doesn't exist or there is no controlled weapon */ - def WeaponControlledFromSeat(seatNumber : Int) : Option[Tool] = { + def WeaponControlledFromSeat(seatNumber : Int) : Option[Equipment] = { Seat(seatNumber) match { case Some(seat) => wepFromSeat(seat) @@ -297,7 +300,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ } } - private def wepFromSeat(seat : Seat) : Option[Tool] = { + private def wepFromSeat(seat : Seat) : Option[Equipment] = { seat.ControlledWeapon match { case Some(index) => wepFromSeat(index) @@ -306,10 +309,10 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ } } - private def wepFromSeat(wepIndex : Int) : Option[Tool] = { + private def wepFromSeat(wepIndex : Int) : Option[Equipment] = { weapons.get(wepIndex) match { case Some(wep) => - wep.Equipment.asInstanceOf[Option[Tool]] + wep.Equipment case None => None } diff --git a/pslogin/src/main/scala/scripts/GUIDTask.scala b/common/src/main/scala/net/psforever/objects/guid/GUIDTask.scala similarity index 99% rename from pslogin/src/main/scala/scripts/GUIDTask.scala rename to common/src/main/scala/net/psforever/objects/guid/GUIDTask.scala index 0d04585b..7425322b 100644 --- a/pslogin/src/main/scala/scripts/GUIDTask.scala +++ b/common/src/main/scala/net/psforever/objects/guid/GUIDTask.scala @@ -1,5 +1,5 @@ // Copyright (c) 2017 PSForever -package scripts +package net.psforever.objects.guid /** * The basic compiled tasks for assigning (registering) and revoking (unregistering) globally unique identifiers.
@@ -20,7 +20,6 @@ object GUIDTask { import akka.actor.ActorRef import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.equipment.Equipment - import net.psforever.objects.guid.{Task, TaskResolver} import net.psforever.objects.{EquipmentSlot, Player, Tool, Vehicle} import scala.annotation.tailrec @@ -279,4 +278,4 @@ object GUIDTask { } } } -} \ No newline at end of file +} diff --git a/common/src/main/scala/net/psforever/objects/guid/pool/ExclusivePool.scala b/common/src/main/scala/net/psforever/objects/guid/pool/ExclusivePool.scala index 93081c54..87f1050d 100644 --- a/common/src/main/scala/net/psforever/objects/guid/pool/ExclusivePool.scala +++ b/common/src/main/scala/net/psforever/objects/guid/pool/ExclusivePool.scala @@ -5,7 +5,7 @@ import net.psforever.objects.guid.selector.NumberSelector import scala.util.{Failure, Success, Try} -class ExclusivePool(numbers : List[Int]) extends SimplePool(numbers) { +class ExclusivePool(override val numbers : List[Int]) extends SimplePool(numbers) { private val pool : Array[Int] = Array.ofDim[Int](numbers.length) numbers.indices.foreach(i => { pool(i) = i }) diff --git a/common/src/main/scala/net/psforever/objects/guid/pool/SimplePool.scala b/common/src/main/scala/net/psforever/objects/guid/pool/SimplePool.scala index 1b30ed4a..fa2f1fb7 100644 --- a/common/src/main/scala/net/psforever/objects/guid/pool/SimplePool.scala +++ b/common/src/main/scala/net/psforever/objects/guid/pool/SimplePool.scala @@ -5,7 +5,7 @@ import net.psforever.objects.guid.selector.{NumberSelector, StrictInOrderSelecto import scala.util.{Success, Try} -class SimplePool(private val numbers : List[Int]) extends NumberPool { +class SimplePool(val numbers : List[Int]) extends NumberPool { if(numbers.count(_ < 0) > 0) { throw new IllegalArgumentException("negative numbers not allowed in number pool") } diff --git a/common/src/main/scala/net/psforever/objects/inventory/AccessibleInventory.scala b/common/src/main/scala/net/psforever/objects/inventory/AccessibleInventory.scala deleted file mode 100644 index d0d5e15e..00000000 --- a/common/src/main/scala/net/psforever/objects/inventory/AccessibleInventory.scala +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.inventory - -import net.psforever.objects.Player -import net.psforever.packet.game.PlanetSideGUID - -trait AccessibleInventory { - def Inventory : GridInventory - - def CanAccess(who : Player) : Boolean - def Access(who : PlanetSideGUID) : Boolean - def Unaccess : Boolean -} \ No newline at end of file diff --git a/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala b/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala index ae63affd..6ba5abc5 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala @@ -11,7 +11,6 @@ import net.psforever.packet.game.UseItemMessage */ class Door(private val ddef : DoorDefinition) extends PlanetSideServerObject { private var openState : Boolean = false - private var lockState : Boolean = false def Open : Boolean = openState @@ -20,25 +19,15 @@ class Door(private val ddef : DoorDefinition) extends PlanetSideServerObject { Open } - def Locked : Boolean = lockState - - def Locked_=(lock : Boolean) : Boolean = { - lockState = lock - Locked - } - def Use(player : Player, msg : UseItemMessage) : Door.Exchange = { - if(!lockState && !openState) { + if(!openState) { openState = true Door.OpenEvent() } - else if(openState) { + else { openState = false Door.CloseEvent() } - else { - Door.NoEvent() - } } def Definition : DoorDefinition = ddef diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala index 57532c54..c6cba9f0 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala @@ -13,9 +13,7 @@ import net.psforever.types.{ExoSuitType, TransactionType, Vector3} * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields */ class Terminal(tdef : TerminalDefinition) extends PlanetSideServerObject { - /** - * An entry that maintains a reference to the `Player`, and the player's GUID and location when the message was received. - */ + /** An entry that maintains a reference to the `Player`, and the player's GUID and location when the message was received. */ private var hackedBy : Option[(Player, PlanetSideGUID, Vector3)] = None def HackedBy : Option[(Player, PlanetSideGUID, Vector3)] = hackedBy diff --git a/common/src/main/scala/net/psforever/objects/vehicles/AccessPermissionGroup.scala b/common/src/main/scala/net/psforever/objects/vehicles/AccessPermissionGroup.scala index 5a99956e..70165977 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/AccessPermissionGroup.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/AccessPermissionGroup.scala @@ -19,4 +19,4 @@ object AccessPermissionGroup extends Enumeration { Passenger, Trunk = Value -} \ No newline at end of file +} diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala index 7bf7d22c..dbd2501d 100644 --- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -136,11 +136,14 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { * The replacement will not occur if the current system is populated or if its synchronized reference has been created. * @return synchronized reference to the globally unique identifier system */ - def GUID(hub : NumberPoolHub) : ActorRef = { + def GUID(hub : NumberPoolHub) : Boolean = { if(actor == ActorRef.noSender && guid.Pools.map({case ((_, pool)) => pool.Count}).sum == 0) { guid = hub + true + } + else { + false } - Actor } /** @@ -215,7 +218,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { def Transport : ActorRef = transport def MakeBases(num : Int) : List[Base] = { - bases = (0 to num).map(id => new Base(id)).toList + bases = (1 to num).map(id => new Base(id)).toList bases } diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala index 96cc0928..dbec207c 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala @@ -50,7 +50,9 @@ class ZoneMap(private val name : String) { def LocalBases : Int = numBases def LocalBases_=(num : Int) : Int = { - numBases = math.max(0, num) + if(num > 0) { + numBases = num + } LocalBases } diff --git a/common/src/test/scala/objects/ActorTest.scala b/common/src/test/scala/objects/ActorTest.scala index 5b08b920..cdb7ca00 100644 --- a/common/src/test/scala/objects/ActorTest.scala +++ b/common/src/test/scala/objects/ActorTest.scala @@ -6,7 +6,7 @@ import akka.testkit.{ImplicitSender, TestKit} import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.specs2.specification.Scope -abstract class ActorTest(sys : ActorSystem) extends TestKit(sys) with Scope with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll { +abstract class ActorTest(sys : ActorSystem = ActorSystem("system")) extends TestKit(sys) with Scope with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll { override def afterAll { TestKit.shutdownActorSystem(system) } diff --git a/common/src/test/scala/objects/ConverterTest.scala b/common/src/test/scala/objects/ConverterTest.scala index 3b946fba..90e92a44 100644 --- a/common/src/test/scala/objects/ConverterTest.scala +++ b/common/src/test/scala/objects/ConverterTest.scala @@ -287,7 +287,7 @@ class ConverterTest extends Specification { fury.Position = Vector3(3674.8438f, 2732f, 91.15625f) fury.Orientation = Vector3(0.0f, 0.0f, 90.0f) fury.WeaponControlledFromSeat(0).get.GUID = PlanetSideGUID(400) - fury.WeaponControlledFromSeat(0).get.AmmoSlots.head.Box = hellfire_ammo_box + fury.WeaponControlledFromSeat(0).get.asInstanceOf[Tool].AmmoSlots.head.Box = hellfire_ammo_box fury.Definition.Packet.ConstructorData(fury).isSuccess mustEqual true ok //TODO write more of this test diff --git a/common/src/test/scala/objects/DoorTest.scala b/common/src/test/scala/objects/DoorTest.scala new file mode 100644 index 00000000..d969e229 --- /dev/null +++ b/common/src/test/scala/objects/DoorTest.scala @@ -0,0 +1,91 @@ +// Copyright (c) 2017 PSForever +package objects + +import akka.actor.{ActorRef, Props} +import net.psforever.objects.{GlobalDefinitions, Player} +import net.psforever.objects.serverobject.doors.{Door, DoorControl} +import net.psforever.packet.game.{PlanetSideGUID, UseItemMessage} +import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3} +import org.specs2.mutable.Specification + +import scala.concurrent.duration.Duration + +class DoorTest extends Specification { + "Door" should { + "construct" in { + Door(GlobalDefinitions.door) + ok + } + + "starts as closed (false)" in { + val door = Door(GlobalDefinitions.door) + door.Open mustEqual false + } + + "can be opened and closed (1; manual)" in { + val door = Door(GlobalDefinitions.door) + door.Open mustEqual false + door.Open = true + door.Open mustEqual true + door.Open = false + door.Open mustEqual false + } + + "can beopened and closed (2; toggle)" in { + val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val msg = UseItemMessage(PlanetSideGUID(6585), 0, PlanetSideGUID(372), 4294967295L, false, Vector3(5.0f,0.0f,0.0f), Vector3(0.0f,0.0f,0.0f), 11, 25, 0, 364) + val door = Door(GlobalDefinitions.door) + door.Open mustEqual false + door.Use(player, msg) + door.Open mustEqual true + door.Use(player, msg) + door.Open mustEqual false + } + } +} + +class DoorControl1Test extends ActorTest() { + "DoorControl" should { + "construct" in { + val door = Door(GlobalDefinitions.door) + door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door") + assert(door.Actor != ActorRef.noSender) + } + } +} + +class DoorControl2Test extends ActorTest() { + "DoorControl" should { + "open on use" in { + val door = Door(GlobalDefinitions.door) + door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door") + val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val msg = UseItemMessage(PlanetSideGUID(1), 0, PlanetSideGUID(2), 0L, false, Vector3(0f,0f,0f),Vector3(0f,0f,0f),0,0,0,0L) //faked + assert(!door.Open) + + door.Actor ! Door.Use(player, msg) + val reply = receiveOne(Duration.create(500, "ms")) + assert(reply.isInstanceOf[Door.DoorMessage]) + val reply2 = reply.asInstanceOf[Door.DoorMessage] + assert(reply2.player == player) + assert(reply2.msg == msg) + assert(reply2.response == Door.OpenEvent()) + assert(door.Open) + } + } +} + +class DoorControl3Test extends ActorTest() { + "DoorControl" should { + "do nothing if given garbage" in { + val door = Door(GlobalDefinitions.door) + door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door") + assert(!door.Open) + + door.Actor ! "trash" + val reply = receiveOne(Duration.create(500, "ms")) + assert(reply.isInstanceOf[Door.NoEvent]) + assert(!door.Open) + } + } +} diff --git a/common/src/test/scala/objects/GUIDTaskTest.scala b/common/src/test/scala/objects/GUIDTaskTest.scala new file mode 100644 index 00000000..1ea187de --- /dev/null +++ b/common/src/test/scala/objects/GUIDTaskTest.scala @@ -0,0 +1,245 @@ +// Copyright (c) 2017 PSForever +package objects + +import java.util.logging.LogManager + +import akka.actor.{ActorRef, ActorSystem, Props} +import akka.testkit.TestProbe +import net.psforever.objects._ +import net.psforever.objects.entity.IdentifiableEntity +import net.psforever.objects.guid.actor.{NumberPoolActor, UniqueNumberSystem} +import net.psforever.objects.guid.selector.RandomSelector +import net.psforever.objects.guid.source.LimitedNumberSource +import net.psforever.objects.guid.{GUIDTask, NumberPoolHub, Task, TaskResolver} +import net.psforever.types.{CharacterGender, PlanetSideEmpire} + +class GUIDTaskRegister1Test extends ActorTest() { + "RegisterObjectTask" in { + val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup + val obj = new GUIDTaskTest.TestObject + + assert(!obj.HasGUID) + taskResolver ! TaskResolver.GiveTask(new GUIDTaskTest.RegisterTestTask(probe.ref), List(GUIDTask.RegisterObjectTask(obj)(uns))) + probe.expectMsg(scala.util.Success) + assert(obj.HasGUID) + } +} + +class GUIDTaskRegister2Test extends ActorTest() { + "RegisterEquipment -> RegisterObjectTask" in { + val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup + val obj = AmmoBox(GlobalDefinitions.energy_cell) + + assert(!obj.HasGUID) + taskResolver ! TaskResolver.GiveTask(new GUIDTaskTest.RegisterTestTask(probe.ref), List(GUIDTask.RegisterEquipment(obj)(uns))) + probe.expectMsg(scala.util.Success) + assert(obj.HasGUID) + } +} + +class GUIDTaskRegister3Test extends ActorTest() { + "RegisterEquipment -> RegisterTool" in { + val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup + val obj = Tool(GlobalDefinitions.beamer) + obj.AmmoSlots.head.Box = AmmoBox(GlobalDefinitions.energy_cell) + + assert(!obj.HasGUID) + assert(!obj.AmmoSlots.head.Box.HasGUID) + taskResolver ! TaskResolver.GiveTask(new GUIDTaskTest.RegisterTestTask(probe.ref), List(GUIDTask.RegisterEquipment(obj)(uns))) + probe.expectMsg(scala.util.Success) + assert(obj.HasGUID) + assert(obj.AmmoSlots.head.Box.HasGUID) + } +} + +class GUIDTaskRegister4Test extends ActorTest() { + "RegisterVehicle" in { + val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup + val obj = Vehicle(GlobalDefinitions.fury) + val obj_wep = obj.WeaponControlledFromSeat(0).get + val obj_wep_ammo = (obj.WeaponControlledFromSeat(0).get.asInstanceOf[Tool].AmmoSlots.head.Box = AmmoBox(GlobalDefinitions.hellfire_ammo)).get + obj.Trunk += 30 -> AmmoBox(GlobalDefinitions.hellfire_ammo) + val obj_trunk_ammo = obj.Trunk.Items(0).obj + + assert(!obj.HasGUID) + assert(!obj_wep.HasGUID) + assert(!obj_wep_ammo.HasGUID) + assert(!obj_trunk_ammo.HasGUID) + taskResolver ! TaskResolver.GiveTask(new GUIDTaskTest.RegisterTestTask(probe.ref), List(GUIDTask.RegisterVehicle(obj)(uns))) + probe.expectMsg(scala.util.Success) + assert(obj.HasGUID) + assert(obj_wep.HasGUID) + assert(obj_wep_ammo.HasGUID) + assert(obj_trunk_ammo.HasGUID) + } +} + +class GUIDTaskRegister5Test extends ActorTest() { + "RegisterAvatar" in { + val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup + val obj = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val obj_wep = Tool(GlobalDefinitions.beamer) + obj.Slot(0).Equipment = obj_wep + val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj_wep.AmmoSlots.head.Box = obj_wep_ammo + val obj_inv_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj.Slot(6).Equipment = obj_inv_ammo + val obj_locker = obj.Slot(5).Equipment.get + val obj_locker_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj_locker.asInstanceOf[LockerContainer].Inventory += 0 -> obj_locker_ammo + + assert(!obj.HasGUID) + assert(!obj_wep.HasGUID) + assert(!obj_wep_ammo.HasGUID) + assert(!obj_inv_ammo.HasGUID) + assert(!obj_locker.HasGUID) + assert(!obj_locker_ammo.HasGUID) + taskResolver ! TaskResolver.GiveTask(new GUIDTaskTest.RegisterTestTask(probe.ref), List(GUIDTask.RegisterAvatar(obj)(uns))) + probe.expectMsg(scala.util.Success) + assert(obj.HasGUID) + assert(obj_wep.HasGUID) + assert(obj_wep_ammo.HasGUID) + assert(obj_inv_ammo.HasGUID) + assert(obj_locker.HasGUID) + assert(obj_locker_ammo.HasGUID) + } +} + +class GUIDTaskUnregister1Test extends ActorTest() { + "UnregisterObjectTask" in { + val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup + val obj = new GUIDTaskTest.TestObject + guid.register(obj, "dynamic") + + assert(obj.HasGUID) + taskResolver ! TaskResolver.GiveTask(new GUIDTaskTest.RegisterTestTask(probe.ref), List(GUIDTask.UnregisterObjectTask(obj)(uns))) + probe.expectMsg(scala.util.Success) + assert(!obj.HasGUID) + } +} + +class GUIDTaskUnregister2Test extends ActorTest() { + "UnregisterEquipment -> UnregisterObjectTask" in { + val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup + val obj = AmmoBox(GlobalDefinitions.energy_cell) + guid.register(obj, "dynamic") + + assert(obj.HasGUID) + taskResolver ! TaskResolver.GiveTask(new GUIDTaskTest.RegisterTestTask(probe.ref), List(GUIDTask.UnregisterEquipment(obj)(uns))) + probe.expectMsg(scala.util.Success) + assert(!obj.HasGUID) + } +} + +class GUIDTaskUnregister3Test extends ActorTest() { + "UnregisterEquipment -> UnregisterTool" in { + val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup + val obj = Tool(GlobalDefinitions.beamer) + obj.AmmoSlots.head.Box = AmmoBox(GlobalDefinitions.energy_cell) + guid.register(obj, "dynamic") + guid.register(obj.AmmoSlots.head.Box, "dynamic") + + assert(obj.HasGUID) + assert(obj.AmmoSlots.head.Box.HasGUID) + taskResolver ! TaskResolver.GiveTask(new GUIDTaskTest.RegisterTestTask(probe.ref), List(GUIDTask.UnregisterEquipment(obj)(uns))) + probe.expectMsg(scala.util.Success) + assert(!obj.HasGUID) + assert(!obj.AmmoSlots.head.Box.HasGUID) + } +} + +class GUIDTaskUnregister4Test extends ActorTest() { + "RegisterVehicle" in { + val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup + val obj = Vehicle(GlobalDefinitions.fury) + val obj_wep = obj.WeaponControlledFromSeat(0).get + val obj_wep_ammo = (obj.WeaponControlledFromSeat(0).get.asInstanceOf[Tool].AmmoSlots.head.Box = AmmoBox(GlobalDefinitions.hellfire_ammo)).get + obj.Trunk += 30 -> AmmoBox(GlobalDefinitions.hellfire_ammo) + val obj_trunk_ammo = obj.Trunk.Items(0).obj + guid.register(obj, "dynamic") + guid.register(obj_wep, "dynamic") + guid.register(obj_wep_ammo, "dynamic") + guid.register(obj_trunk_ammo, "dynamic") + + assert(obj.HasGUID) + assert(obj_wep.HasGUID) + assert(obj_wep_ammo.HasGUID) + assert(obj_trunk_ammo.HasGUID) + taskResolver ! TaskResolver.GiveTask(new GUIDTaskTest.RegisterTestTask(probe.ref), List(GUIDTask.UnregisterVehicle(obj)(uns))) + probe.expectMsg(scala.util.Success) + assert(!obj.HasGUID) + assert(!obj_wep.HasGUID) + assert(!obj_wep_ammo.HasGUID) + assert(!obj_trunk_ammo.HasGUID) + } +} + +class GUIDTaskUnregister5Test extends ActorTest() { + "UnregisterAvatar" in { + val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup + val obj = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val obj_wep = Tool(GlobalDefinitions.beamer) + obj.Slot(0).Equipment = obj_wep + val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj_wep.AmmoSlots.head.Box = obj_wep_ammo + val obj_inv_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj.Slot(6).Equipment = obj_inv_ammo + val obj_locker = obj.Slot(5).Equipment.get + val obj_locker_ammo = AmmoBox(GlobalDefinitions.energy_cell) + obj_locker.asInstanceOf[LockerContainer].Inventory += 0 -> obj_locker_ammo + guid.register(obj, "dynamic") + guid.register(obj_wep, "dynamic") + guid.register(obj_wep_ammo, "dynamic") + guid.register(obj_inv_ammo, "dynamic") + guid.register(obj_locker, "dynamic") + guid.register(obj_locker_ammo, "dynamic") + + assert(obj.HasGUID) + assert(obj_wep.HasGUID) + assert(obj_wep_ammo.HasGUID) + assert(obj_inv_ammo.HasGUID) + assert(obj_locker.HasGUID) + assert(obj_locker_ammo.HasGUID) + taskResolver ! TaskResolver.GiveTask(new GUIDTaskTest.RegisterTestTask(probe.ref), List(GUIDTask.UnregisterAvatar(obj)(uns))) + probe.expectMsg(scala.util.Success) + assert(!obj.HasGUID) + assert(!obj_wep.HasGUID) + assert(!obj_wep_ammo.HasGUID) + assert(!obj_inv_ammo.HasGUID) + assert(!obj_locker.HasGUID) + assert(!obj_locker_ammo.HasGUID) + } +} + +object GUIDTaskTest { + class TestObject extends IdentifiableEntity + + class RegisterTestTask(probe : ActorRef) extends Task { + def Execute(resolver : ActorRef) : Unit = { + probe ! scala.util.Success + resolver ! scala.util.Success(this) + } + } + + def CommonTestSetup(implicit system : ActorSystem) : (NumberPoolHub, ActorRef, ActorRef, TestProbe) = { + import akka.actor.Props + import akka.routing.RandomPool + import akka.testkit.TestProbe + + val guid : NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(110)) + guid.AddPool("dynamic", (1 to 100).toList).Selector = new RandomSelector //TODO name is hardcoded for now + val uns = system.actorOf(RandomPool(25).props(Props(classOf[UniqueNumberSystem], guid, GUIDTaskTest.AllocateNumberPoolActors(guid))), "uns") + val taskResolver = system.actorOf(RandomPool(15).props(Props[TaskResolver]), "resolver") + LogManager.getLogManager.reset() //suppresses any internal loggers created by the above elements + (guid, uns, taskResolver, TestProbe()) + } + + /** + * @see `UniqueNumberSystem.AllocateNumberPoolActors(NumberPoolHub)(implicit ActorContext)` + */ + def AllocateNumberPoolActors(poolSource : NumberPoolHub)(implicit system : ActorSystem) : Map[String, ActorRef] = { + poolSource.Pools.map({ case ((pname, pool)) => + pname -> system.actorOf(Props(classOf[NumberPoolActor], pool), pname) + }).toMap + } +} diff --git a/common/src/test/scala/objects/IFFLockTest.scala b/common/src/test/scala/objects/IFFLockTest.scala new file mode 100644 index 00000000..54be04d1 --- /dev/null +++ b/common/src/test/scala/objects/IFFLockTest.scala @@ -0,0 +1,66 @@ +// Copyright (c) 2017 PSForever +package objects + +import akka.actor.{ActorRef, Props} +import net.psforever.objects.serverobject.CommonMessages +import net.psforever.objects.{GlobalDefinitions, Player} +import net.psforever.objects.serverobject.locks.{IFFLock, IFFLockControl} +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.types.{CharacterGender, PlanetSideEmpire} +import org.specs2.mutable.Specification + +class IFFLockTest extends Specification { + "IFFLock" should { + "construct" in { + IFFLock(GlobalDefinitions.lock_external) + ok + } + + //TODO internal hacking logic will be re-written later + } +} + +class IFFLockControl1Test extends ActorTest() { + "IFFLockControl" should { + "construct" in { + val lock = IFFLock(GlobalDefinitions.lock_external) + lock.Actor = system.actorOf(Props(classOf[IFFLockControl], lock), "lock-control") + assert(lock.Actor != ActorRef.noSender) + } + } +} + +class IFFLockControl2Test extends ActorTest() { + "IFFLockControl" should { + "can hack" in { + val lock = IFFLock(GlobalDefinitions.lock_external) + lock.Actor = system.actorOf(Props(classOf[IFFLockControl], lock), "lock-control") + val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + player.GUID = PlanetSideGUID(1) + assert(lock.HackedBy.isEmpty) + + lock.Actor ! CommonMessages.Hack(player) + Thread.sleep(500L) //blocking + assert(lock.HackedBy.nonEmpty) //TODO rewrite later + } + } +} + +class IFFLockControl3Test extends ActorTest() { + "IFFLockControl" should { + "can hack" in { + val lock = IFFLock(GlobalDefinitions.lock_external) + lock.Actor = system.actorOf(Props(classOf[IFFLockControl], lock), "lock-control") + val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + player.GUID = PlanetSideGUID(1) + assert(lock.HackedBy.isEmpty) + + lock.Actor ! CommonMessages.Hack(player) + Thread.sleep(500L) //blocking + assert(lock.HackedBy.nonEmpty) //TODO rewrite later + lock.Actor ! CommonMessages.ClearHack() + Thread.sleep(500L) //blocking + assert(lock.HackedBy.isEmpty) //TODO rewrite + } + } +} diff --git a/common/src/test/scala/objects/VehicleTest.scala b/common/src/test/scala/objects/VehicleTest.scala index 11b1d65a..d898a66e 100644 --- a/common/src/test/scala/objects/VehicleTest.scala +++ b/common/src/test/scala/objects/VehicleTest.scala @@ -1,9 +1,11 @@ // Copyright (c) 2017 PSForever package objects -import net.psforever.objects.{GlobalDefinitions, Vehicle} +import net.psforever.objects.{GlobalDefinitions, Player, Vehicle} import net.psforever.objects.definition.SeatDefinition -import net.psforever.objects.vehicles.{Seat, SeatArmorRestriction, VehicleLockState} +import net.psforever.objects.vehicles.{AccessPermissionGroup, Seat, SeatArmorRestriction, VehicleLockState} +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.types.{CharacterGender, ExoSuitType, PlanetSideEmpire} import org.specs2.mutable._ class VehicleTest extends Specification { @@ -37,8 +39,7 @@ class VehicleTest extends Specification { fury.Seats(0).Bailable mustEqual true fury.Seats(0).ControlledWeapon mustEqual Some(1) fury.MountPoints.size mustEqual 2 - fury.MountPoints.get(0) mustEqual Some(0) - fury.MountPoints.get(1) mustEqual None + fury.MountPoints.get(1) mustEqual Some(0) fury.MountPoints.get(2) mustEqual Some(0) fury.Weapons.size mustEqual 1 fury.Weapons.get(0) mustEqual None @@ -63,6 +64,58 @@ class VehicleTest extends Specification { seat.isOccupied mustEqual false seat.Occupant mustEqual None } + + "player can sit" in { + val seat = new Seat(seat_def) + seat.Occupant.isDefined mustEqual false + + val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + player1.ExoSuit = ExoSuitType.MAX + seat.Occupant = player1 + seat.Occupant.isDefined mustEqual true + seat.Occupant.contains(player1) mustEqual true + } + + "one occupant at a time" in { + val seat = new Seat(seat_def) + seat.Occupant.isDefined mustEqual false + + val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + player1.ExoSuit = ExoSuitType.MAX + seat.Occupant = player1 + seat.Occupant.isDefined mustEqual true + seat.Occupant.contains(player1) mustEqual true + + val player2 = Player("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + player2.ExoSuit = ExoSuitType.MAX + seat.Occupant = player2 + seat.Occupant.isDefined mustEqual true + seat.Occupant.contains(player1) mustEqual true + } + + "one player must get out of seat before other can get in" in { + val seat = new Seat(seat_def) + seat.Occupant.isDefined mustEqual false + + val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + player1.ExoSuit = ExoSuitType.MAX + seat.Occupant = player1 + seat.Occupant.isDefined mustEqual true + seat.Occupant.contains(player1) mustEqual true + + val player2 = Player("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + player2.ExoSuit = ExoSuitType.MAX + seat.Occupant = player2 + seat.Occupant.isDefined mustEqual true + seat.Occupant.contains(player2) mustEqual false + seat.Occupant.contains(player1) mustEqual true + + seat.Occupant = None + seat.Occupant.isDefined mustEqual false + seat.Occupant = player2 + seat.Occupant.isDefined mustEqual true + seat.Occupant.contains(player2) mustEqual true + } } "Vehicle" should { @@ -93,11 +146,111 @@ class VehicleTest extends Specification { fury_vehicle.Trunk.Width mustEqual 11 fury_vehicle.Trunk.Height mustEqual 11 fury_vehicle.Trunk.Offset mustEqual 30 - fury_vehicle.GetSeatFromMountPoint(0) mustEqual Some(0) - fury_vehicle.GetSeatFromMountPoint(1) mustEqual None + fury_vehicle.GetSeatFromMountPoint(1) mustEqual Some(0) fury_vehicle.GetSeatFromMountPoint(2) mustEqual Some(0) fury_vehicle.Decal mustEqual 0 fury_vehicle.Health mustEqual fury_vehicle.Definition.MaxHealth } + + "can be owned by a player" in { + val fury_vehicle = Vehicle(GlobalDefinitions.fury) + fury_vehicle.Owner.isDefined mustEqual false + + val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + player1.GUID = PlanetSideGUID(1) + fury_vehicle.Owner = player1 + fury_vehicle.Owner.isDefined mustEqual true + fury_vehicle.Owner.contains(PlanetSideGUID(1)) mustEqual true + } + + "ownership depends on who last was granted it" in { + val fury_vehicle = Vehicle(GlobalDefinitions.fury) + fury_vehicle.Owner.isDefined mustEqual false + + val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + player1.GUID = PlanetSideGUID(1) + fury_vehicle.Owner = player1 + fury_vehicle.Owner.isDefined mustEqual true + fury_vehicle.Owner.contains(PlanetSideGUID(1)) mustEqual true + + val player2 = Player("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + player2.GUID = PlanetSideGUID(2) + fury_vehicle.Owner = player2 + fury_vehicle.Owner.isDefined mustEqual true + fury_vehicle.Owner.contains(PlanetSideGUID(2)) mustEqual true + } + + "can use mount point to get seat number" in { + val fury_vehicle = Vehicle(GlobalDefinitions.fury) + fury_vehicle.GetSeatFromMountPoint(0) mustEqual None + fury_vehicle.GetSeatFromMountPoint(1) mustEqual Some(0) + fury_vehicle.GetSeatFromMountPoint(2) mustEqual Some(0) + fury_vehicle.GetSeatFromMountPoint(3) mustEqual None + } + + "has four permission groups" in { + val fury_vehicle = Vehicle(GlobalDefinitions.fury) + fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id) mustEqual Some(VehicleLockState.Locked) + fury_vehicle.PermissionGroup(AccessPermissionGroup.Gunner.id) mustEqual Some(VehicleLockState.Empire) + fury_vehicle.PermissionGroup(AccessPermissionGroup.Passenger.id) mustEqual Some(VehicleLockState.Empire) + fury_vehicle.PermissionGroup(AccessPermissionGroup.Trunk.id) mustEqual Some(VehicleLockState.Locked) + } + + "set new permission level" in { + val fury_vehicle = Vehicle(GlobalDefinitions.fury) + fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id) mustEqual Some(VehicleLockState.Locked) + fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id, VehicleLockState.Group.id) mustEqual Some(VehicleLockState.Group) + } + + "set the same permission level" in { + val fury_vehicle = Vehicle(GlobalDefinitions.fury) + fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id) mustEqual Some(VehicleLockState.Locked) + fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id, VehicleLockState.Locked.id) mustEqual None + } + + "alternate permission level indices" in { + val fury_vehicle = Vehicle(GlobalDefinitions.fury) + fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id) mustEqual fury_vehicle.PermissionGroup(10) + fury_vehicle.PermissionGroup(AccessPermissionGroup.Gunner.id) mustEqual fury_vehicle.PermissionGroup(11) + fury_vehicle.PermissionGroup(AccessPermissionGroup.Passenger.id) mustEqual fury_vehicle.PermissionGroup(12) + fury_vehicle.PermissionGroup(AccessPermissionGroup.Trunk.id) mustEqual fury_vehicle.PermissionGroup(13) + + (AccessPermissionGroup.Driver.id + 10) mustEqual 10 + fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id, VehicleLockState.Group.id) mustEqual Some(VehicleLockState.Group) + fury_vehicle.PermissionGroup(AccessPermissionGroup.Driver.id) mustEqual fury_vehicle.PermissionGroup(10) + } + + "can determine permission group from seat" in { + val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) + harasser_vehicle.SeatPermissionGroup(0) mustEqual Some(AccessPermissionGroup.Driver) + harasser_vehicle.SeatPermissionGroup(1) mustEqual Some(AccessPermissionGroup.Gunner) + harasser_vehicle.SeatPermissionGroup(2) mustEqual None + //TODO test for AccessPermissionGroup.Passenger later + } + + "can find a passenger in a seat" in { + val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) + val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + player1.GUID = PlanetSideGUID(1) + val player2 = Player("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + player2.GUID = PlanetSideGUID(2) + harasser_vehicle.Seat(0).get.Occupant = player1 //don't worry about ownership for now + harasser_vehicle.Seat(1).get.Occupant = player2 + + harasser_vehicle.PassengerInSeat(player1) mustEqual Some(0) + harasser_vehicle.PassengerInSeat(player2) mustEqual Some(1) + harasser_vehicle.Seat(0).get.Occupant = None + harasser_vehicle.PassengerInSeat(player1) mustEqual None + harasser_vehicle.PassengerInSeat(player2) mustEqual Some(1) + } + + "can find a weapon controlled from seat" in { + val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) + val chaingun_p = harasser_vehicle.Weapons(2).Equipment + chaingun_p.isDefined mustEqual true + + harasser_vehicle.WeaponControlledFromSeat(0) mustEqual None + harasser_vehicle.WeaponControlledFromSeat(1) mustEqual chaingun_p + } } } diff --git a/common/src/test/scala/objects/ZoneTest.scala b/common/src/test/scala/objects/ZoneTest.scala new file mode 100644 index 00000000..268f99da --- /dev/null +++ b/common/src/test/scala/objects/ZoneTest.scala @@ -0,0 +1,104 @@ +// Copyright (c) 2017 PSForever +package objects + +import akka.actor.ActorRef +import net.psforever.objects.entity.IdentifiableEntity +import net.psforever.objects.equipment.Equipment +import net.psforever.objects.guid.NumberPoolHub +import net.psforever.objects.guid.source.LimitedNumberSource +import net.psforever.objects.zones.{Zone, ZoneMap} +import net.psforever.objects.{GlobalDefinitions, Vehicle} +import org.specs2.mutable.Specification + +class ZoneTest extends Specification { + "ZoneMap" should { + //TODO these are temporary tests as the current ZoneMap is a kludge + "construct" in { + new ZoneMap("map13") + ok + } + + "references bases by a positive building id (defaults to 0)" in { + val map = new ZoneMap("map13") + map.LocalBases mustEqual 0 + map.LocalBases = 10 + map.LocalBases mustEqual 10 + map.LocalBases = -1 + map.LocalBases mustEqual 10 + } + + "associates objects to bases (doesn't check numbers)" in { + val map = new ZoneMap("map13") + map.ObjectToBase mustEqual Nil + map.ObjectToBase(1, 2) + map.ObjectToBase mustEqual List((1, 2)) + map.ObjectToBase(3, 4) + map.ObjectToBase mustEqual List((1, 2), (3, 4)) + } + + "associates doors to door locks (doesn't check numbers)" in { + val map = new ZoneMap("map13") + map.DoorToLock mustEqual Map.empty + map.DoorToLock(1, 2) + map.DoorToLock mustEqual Map(1 -> 2) + map.DoorToLock(3, 4) + map.DoorToLock mustEqual Map(1 -> 2, 3 -> 4) + } + } + + val map13 = new ZoneMap("map13") + map13.LocalBases = 10 + class TestObject extends IdentifiableEntity + + "Zone" should { + //TODO these are temporary tests as the current Zone is a kludge + "construct" in { + val zone = new Zone("home3", map13, 13) + zone.GUID mustEqual ActorRef.noSender + zone.Ground mustEqual ActorRef.noSender + zone.Transport mustEqual ActorRef.noSender + //zone also has a unique identifier system but it can't be accessed without its the Actor GUID being initialized + zone.EquipmentOnGround mustEqual List.empty[Equipment] + zone.Vehicles mustEqual List.empty[Vehicle] + } + + "can have its unique identifier system changed if no objects were added to it" in { + val zone = new Zone("home3", map13, 13) + val guid1 : NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(100)) + guid1.AddPool("pool1", (0 to 50).toList) + guid1.AddPool("pool2", (51 to 75).toList) + zone.GUID(guid1) mustEqual true + + val obj = new TestObject() + guid1.register(obj, "pool2").isSuccess mustEqual true + guid1.WhichPool(obj) mustEqual Some("pool2") + + val guid2 : NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(150)) + guid2.AddPool("pool3", (0 to 50).toList) + guid2.AddPool("pool4", (51 to 75).toList) + zone.GUID(guid2) mustEqual false + } + + "can keep track of Vehicles" in { + val zone = new Zone("home3", map13, 13) + val fury = Vehicle(GlobalDefinitions.fury) + zone.Vehicles mustEqual List() + zone.AddVehicle(fury) + zone.Vehicles mustEqual List(fury) + } + + "can forget specific vehicles" in { + val zone = new Zone("home3", map13, 13) + val fury = Vehicle(GlobalDefinitions.fury) + val wraith = Vehicle(GlobalDefinitions.quadstealth) + val basilisk = Vehicle(GlobalDefinitions.quadassault) + zone.AddVehicle(wraith) + zone.AddVehicle(fury) + zone.AddVehicle(basilisk) + zone.Vehicles mustEqual List(wraith, fury, basilisk) + + zone.RemoveVehicle(fury) + zone.Vehicles mustEqual List(wraith, basilisk) + } + } +} diff --git a/common/src/test/scala/objects/NumberPoolActorTest.scala b/common/src/test/scala/objects/number/NumberPoolActorTest.scala similarity index 91% rename from common/src/test/scala/objects/NumberPoolActorTest.scala rename to common/src/test/scala/objects/number/NumberPoolActorTest.scala index f40eac43..e9596a0d 100644 --- a/common/src/test/scala/objects/NumberPoolActorTest.scala +++ b/common/src/test/scala/objects/number/NumberPoolActorTest.scala @@ -1,10 +1,11 @@ // Copyright (c) 2017 PSForever -package objects +package objects.number import akka.actor.{ActorSystem, Props} import net.psforever.objects.guid.actor.NumberPoolActor import net.psforever.objects.guid.pool.ExclusivePool import net.psforever.objects.guid.selector.RandomSelector +import objects.ActorTest import scala.concurrent.duration.Duration @@ -15,7 +16,7 @@ class NumberPoolActorTest extends ActorTest(ActorSystem("test")) { pool.Selector = new RandomSelector val poolActor = system.actorOf(Props(classOf[NumberPoolActor], pool), name = "poolActor1") poolActor ! NumberPoolActor.GetAnyNumber() - val msg = receiveOne(Duration.create(100, "ms")) + val msg = receiveOne(Duration.create(500, "ms")) assert(msg.isInstanceOf[NumberPoolActor.GiveNumber]) } } @@ -43,7 +44,7 @@ class NumberPoolActorTest2 extends ActorTest(ActorSystem("test")) { expectMsg(NumberPoolActor.GiveNumber(25, None)) poolActor ! NumberPoolActor.GetAnyNumber() - val msg = receiveOne(Duration.create(100, "ms")) + val msg = receiveOne(Duration.create(500, "ms")) assert(msg.isInstanceOf[NumberPoolActor.NoNumber]) } } diff --git a/common/src/test/scala/objects/NumberPoolHubTest.scala b/common/src/test/scala/objects/number/NumberPoolHubTest.scala similarity index 99% rename from common/src/test/scala/objects/NumberPoolHubTest.scala rename to common/src/test/scala/objects/number/NumberPoolHubTest.scala index 10738a18..551d0909 100644 --- a/common/src/test/scala/objects/NumberPoolHubTest.scala +++ b/common/src/test/scala/objects/number/NumberPoolHubTest.scala @@ -1,5 +1,5 @@ // Copyright (c) 2017 PSForever -package objects +package objects.number import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.guid.NumberPoolHub diff --git a/common/src/test/scala/objects/NumberPoolTest.scala b/common/src/test/scala/objects/number/NumberPoolTest.scala similarity index 99% rename from common/src/test/scala/objects/NumberPoolTest.scala rename to common/src/test/scala/objects/number/NumberPoolTest.scala index a8bbda2b..f29bbc0a 100644 --- a/common/src/test/scala/objects/NumberPoolTest.scala +++ b/common/src/test/scala/objects/number/NumberPoolTest.scala @@ -1,5 +1,5 @@ // Copyright (c) 2017 PSForever -package objects +package objects.number import net.psforever.objects.guid.pool.{ExclusivePool, GenericPool, SimplePool} import net.psforever.objects.guid.selector.SpecificSelector @@ -191,4 +191,3 @@ class NumberPoolTest extends Specification { } } } - diff --git a/common/src/test/scala/objects/NumberSelectorTest.scala b/common/src/test/scala/objects/number/NumberSelectorTest.scala similarity index 99% rename from common/src/test/scala/objects/NumberSelectorTest.scala rename to common/src/test/scala/objects/number/NumberSelectorTest.scala index 463f9654..e9a9fe2a 100644 --- a/common/src/test/scala/objects/NumberSelectorTest.scala +++ b/common/src/test/scala/objects/number/NumberSelectorTest.scala @@ -1,5 +1,5 @@ // Copyright (c) 2017 PSForever -package objects +package objects.number import net.psforever.objects.guid.selector.{RandomSequenceSelector, _} import org.specs2.mutable.Specification @@ -323,4 +323,3 @@ class NumberSelectorTest extends Specification { } } } - diff --git a/common/src/test/scala/objects/NumberSourceTest.scala b/common/src/test/scala/objects/number/NumberSourceTest.scala similarity index 99% rename from common/src/test/scala/objects/NumberSourceTest.scala rename to common/src/test/scala/objects/number/NumberSourceTest.scala index 08c9f48a..dac0df17 100644 --- a/common/src/test/scala/objects/NumberSourceTest.scala +++ b/common/src/test/scala/objects/number/NumberSourceTest.scala @@ -1,8 +1,8 @@ // Copyright (c) 2017 PSForever -package objects +package objects.number -import net.psforever.objects.guid.key.{LoanedKey, SecureKey} import net.psforever.objects.guid.AvailabilityPolicy +import net.psforever.objects.guid.key.{LoanedKey, SecureKey} import org.specs2.mutable.Specification class NumberSourceTest extends Specification { diff --git a/common/src/test/scala/objects/UniqueNumberSystemTest.scala b/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala similarity index 87% rename from common/src/test/scala/objects/UniqueNumberSystemTest.scala rename to common/src/test/scala/objects/number/UniqueNumberSystemTest.scala index a685b035..be8d3e7f 100644 --- a/common/src/test/scala/objects/UniqueNumberSystemTest.scala +++ b/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala @@ -1,5 +1,5 @@ // Copyright (c) 2017 PSForever -package objects +package objects.number import akka.actor.{ActorRef, ActorSystem, Props} import net.psforever.objects.entity.IdentifiableEntity @@ -7,11 +7,12 @@ import net.psforever.objects.guid.NumberPoolHub import net.psforever.objects.guid.actor.{NumberPoolActor, Register, UniqueNumberSystem, Unregister} import net.psforever.objects.guid.selector.RandomSelector import net.psforever.objects.guid.source.LimitedNumberSource +import objects.ActorTest import scala.concurrent.duration.Duration import scala.util.{Failure, Success} -class AllocateNumberPoolActors extends ActorTest(ActorSystem("test")) { +class AllocateNumberPoolActors extends ActorTest() { "AllocateNumberPoolActors" in { val src : LimitedNumberSource = LimitedNumberSource(6000) val guid : NumberPoolHub = new NumberPoolHub(src) @@ -27,7 +28,7 @@ class AllocateNumberPoolActors extends ActorTest(ActorSystem("test")) { } } -class UniqueNumberSystemTest extends ActorTest(ActorSystem("test")) { +class UniqueNumberSystemTest extends ActorTest() { "UniqueNumberSystem" should { "constructor" in { val src : LimitedNumberSource = LimitedNumberSource(6000) @@ -37,12 +38,11 @@ class UniqueNumberSystemTest extends ActorTest(ActorSystem("test")) { guid.AddPool("pool3", (5001 to 6000).toList) system.actorOf(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), "uns") //as long as it constructs ... - } } } -class UniqueNumberSystemTest1 extends ActorTest(ActorSystem("test")) { +class UniqueNumberSystemTest1 extends ActorTest() { class EntityTestClass extends IdentifiableEntity "UniqueNumberSystem" should { @@ -61,7 +61,7 @@ class UniqueNumberSystemTest1 extends ActorTest(ActorSystem("test")) { for(_ <- 1 to 100) { val testObj = new EntityTestClass() uns ! Register(testObj, "pool1") - val msg = receiveOne(Duration.create(100, "ms")) + val msg = receiveOne(Duration.create(500, "ms")) assert(msg.isInstanceOf[Success[_]]) assert(pool1.contains(testObj.GUID.guid)) } @@ -69,7 +69,7 @@ class UniqueNumberSystemTest1 extends ActorTest(ActorSystem("test")) { for(_ <- 1 to 100) { val testObj = new EntityTestClass() uns ! Register(testObj, "pool2") - val msg = receiveOne(Duration.create(100, "ms")) + val msg = receiveOne(Duration.create(500, "ms")) assert(msg.isInstanceOf[Success[_]]) assert(pool2.contains(testObj.GUID.guid)) } @@ -77,7 +77,7 @@ class UniqueNumberSystemTest1 extends ActorTest(ActorSystem("test")) { for(_ <- 1 to 100) { val testObj = new EntityTestClass() uns ! Register(testObj, "pool3") - val msg = receiveOne(Duration.create(100, "ms")) + val msg = receiveOne(Duration.create(500, "ms")) assert(msg.isInstanceOf[Success[_]]) assert(pool3.contains(testObj.GUID.guid)) } @@ -86,7 +86,7 @@ class UniqueNumberSystemTest1 extends ActorTest(ActorSystem("test")) { } } -class UniqueNumberSystemTest2 extends ActorTest(ActorSystem("test")) { +class UniqueNumberSystemTest2 extends ActorTest() { class EntityTestClass extends IdentifiableEntity "UniqueNumberSystem" should { @@ -102,14 +102,14 @@ class UniqueNumberSystemTest2 extends ActorTest(ActorSystem("test")) { assert(src.CountUsed == 0) uns ! Register(testObj, "pool1") - val msg1 = receiveOne(Duration.create(100, "ms")) + val msg1 = receiveOne(Duration.create(500, "ms")) assert(msg1.isInstanceOf[Success[_]]) assert(testObj.HasGUID) assert(src.CountUsed == 1) val id = testObj.GUID.guid uns ! Register(testObj, "pool2") //different pool; makes no difference - val msg2 = receiveOne(Duration.create(100, "ms")) + val msg2 = receiveOne(Duration.create(500, "ms")) assert(msg2.isInstanceOf[Success[_]]) assert(testObj.HasGUID) assert(src.CountUsed == 1) @@ -119,7 +119,7 @@ class UniqueNumberSystemTest2 extends ActorTest(ActorSystem("test")) { //a log.warn should have been generated during this test } -class UniqueNumberSystemTest3 extends ActorTest(ActorSystem("test")) { +class UniqueNumberSystemTest3 extends ActorTest() { class EntityTestClass extends IdentifiableEntity "UniqueNumberSystem" should { @@ -135,7 +135,7 @@ class UniqueNumberSystemTest3 extends ActorTest(ActorSystem("test")) { assert(src.CountUsed == 0) uns ! Register(testObj, "pool4") - val msg1 = receiveOne(Duration.create(100, "ms")) + val msg1 = receiveOne(Duration.create(500, "ms")) assert(msg1.isInstanceOf[Failure[_]]) assert(!testObj.HasGUID) assert(src.CountUsed == 0) @@ -143,7 +143,7 @@ class UniqueNumberSystemTest3 extends ActorTest(ActorSystem("test")) { } } -class UniqueNumberSystemTest4 extends ActorTest(ActorSystem("test")) { +class UniqueNumberSystemTest4 extends ActorTest() { class EntityTestClass extends IdentifiableEntity "UniqueNumberSystem" should { @@ -158,18 +158,18 @@ class UniqueNumberSystemTest4 extends ActorTest(ActorSystem("test")) { val testObj1 = new EntityTestClass() uns ! Register(testObj1, "pool4") - val msg1 = receiveOne(Duration.create(100, "ms")) + val msg1 = receiveOne(Duration.create(500, "ms")) assert(msg1.isInstanceOf[Success[_]]) //pool4 is now empty val testObj2 = new EntityTestClass() uns ! Register(testObj2, "pool4") - val msg2 = receiveOne(Duration.create(100, "ms")) + val msg2 = receiveOne(Duration.create(500, "ms")) assert(msg2.isInstanceOf[Failure[_]]) } } } -class UniqueNumberSystemTest5 extends ActorTest(ActorSystem("test")) { +class UniqueNumberSystemTest5 extends ActorTest() { class EntityTestClass extends IdentifiableEntity "UniqueNumberSystem" should { @@ -186,14 +186,14 @@ class UniqueNumberSystemTest5 extends ActorTest(ActorSystem("test")) { assert(src.CountUsed == 0) uns ! Register(testObj, "pool2") - val msg1 = receiveOne(Duration.create(100, "ms")) + val msg1 = receiveOne(Duration.create(500, "ms")) assert(msg1.isInstanceOf[Success[_]]) assert(testObj.HasGUID) assert(pool2.contains(testObj.GUID.guid)) assert(src.CountUsed == 1) uns ! Unregister(testObj) - val msg2 = receiveOne(Duration.create(100, "ms")) + val msg2 = receiveOne(Duration.create(500, "ms")) assert(msg2.isInstanceOf[Success[_]]) assert(!testObj.HasGUID) assert(src.CountUsed == 0) @@ -201,7 +201,7 @@ class UniqueNumberSystemTest5 extends ActorTest(ActorSystem("test")) { } } -class UniqueNumberSystemTest6 extends ActorTest(ActorSystem("test")) { +class UniqueNumberSystemTest6 extends ActorTest() { class EntityTestClass extends IdentifiableEntity "UniqueNumberSystem" should { @@ -217,7 +217,7 @@ class UniqueNumberSystemTest6 extends ActorTest(ActorSystem("test")) { assert(src.CountUsed == 0) uns ! Unregister(testObj) - val msg1 = receiveOne(Duration.create(100, "ms")) + val msg1 = receiveOne(Duration.create(500, "ms")) assert(msg1.isInstanceOf[Success[_]]) assert(!testObj.HasGUID) assert(src.CountUsed == 0) @@ -225,7 +225,7 @@ class UniqueNumberSystemTest6 extends ActorTest(ActorSystem("test")) { } } -class UniqueNumberSystemTest7 extends ActorTest(ActorSystem("test")) { +class UniqueNumberSystemTest7 extends ActorTest() { class EntityTestClass extends IdentifiableEntity "UniqueNumberSystem" should { @@ -242,7 +242,7 @@ class UniqueNumberSystemTest7 extends ActorTest(ActorSystem("test")) { assert(src.CountUsed == 0) uns ! Unregister(testObj) - val msg1 = receiveOne(Duration.create(100, "ms")) + val msg1 = receiveOne(Duration.create(500, "ms")) assert(msg1.isInstanceOf[Failure[_]]) assert(testObj.HasGUID) assert(src.CountUsed == 0) @@ -250,7 +250,7 @@ class UniqueNumberSystemTest7 extends ActorTest(ActorSystem("test")) { } } -class UniqueNumberSystemTest8 extends ActorTest(ActorSystem("test")) { +class UniqueNumberSystemTest8 extends ActorTest() { class EntityTestClass extends IdentifiableEntity "UniqueNumberSystem" should { @@ -267,7 +267,7 @@ class UniqueNumberSystemTest8 extends ActorTest(ActorSystem("test")) { assert(src.CountUsed == 0) uns ! Unregister(testObj) - val msg1 = receiveOne(Duration.create(100, "ms")) + val msg1 = receiveOne(Duration.create(500, "ms")) assert(msg1.isInstanceOf[Failure[_]]) assert(testObj.HasGUID) assert(src.CountUsed == 0) @@ -285,4 +285,3 @@ object UniqueNumberSystemTest { }).toMap } } - diff --git a/common/src/test/scala/objects/terminal/CertTerminalTest.scala b/common/src/test/scala/objects/terminal/CertTerminalTest.scala new file mode 100644 index 00000000..96b83668 --- /dev/null +++ b/common/src/test/scala/objects/terminal/CertTerminalTest.scala @@ -0,0 +1,47 @@ +// Copyright (c) 2017 PSForever +package objects.terminal + +import akka.actor.ActorRef +import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.{GlobalDefinitions, Player} +import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} +import net.psforever.types._ +import org.specs2.mutable.Specification + +class CertTerminalTest extends Specification { + "Cert_Terminal" should { + val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + + "construct" in { + val terminal = Terminal(GlobalDefinitions.cert_terminal) + terminal.Actor mustEqual ActorRef.noSender + } + + "player can learn a certification ('medium_assault')" in { + val terminal = Terminal(GlobalDefinitions.cert_terminal) + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Learn, 0, "medium_assault", 0, PlanetSideGUID(0)) + terminal.Request(player, msg) mustEqual Terminal.LearnCertification(CertificationType.MediumAssault, 2) + } + + "player can not learn a fake certification ('juggling')" in { + val terminal = Terminal(GlobalDefinitions.cert_terminal) + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Learn, 0, "juggling", 0, PlanetSideGUID(0)) + + terminal.Request(player, msg) mustEqual Terminal.NoDeal() + } + + "player can forget a certification ('medium_assault')" in { + val terminal = Terminal(GlobalDefinitions.cert_terminal) + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Sell, 0, "medium_assault", 0, PlanetSideGUID(0)) + + terminal.Request(player, msg) mustEqual Terminal.SellCertification(CertificationType.MediumAssault, 2) + } + + "player can not forget a fake certification ('juggling')" in { + val terminal = Terminal(GlobalDefinitions.cert_terminal) + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Sell, 0, "juggling", 0, PlanetSideGUID(0)) + + terminal.Request(player, msg) mustEqual Terminal.NoDeal() + } + } +} diff --git a/common/src/test/scala/objects/terminal/OrderTerminalTest.scala b/common/src/test/scala/objects/terminal/OrderTerminalTest.scala new file mode 100644 index 00000000..c5e41100 --- /dev/null +++ b/common/src/test/scala/objects/terminal/OrderTerminalTest.scala @@ -0,0 +1,85 @@ +// Copyright (c) 2017 PSForever +package objects.terminal + +import akka.actor.ActorRef +import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.{AmmoBox, GlobalDefinitions, Player, Tool} +import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} +import net.psforever.types._ +import org.specs2.mutable.Specification + +class OrderTerminalTest extends Specification { + "Order_Terminal" should { + val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + + "construct" in { + val terminal = Terminal(GlobalDefinitions.order_terminal) + terminal.Actor mustEqual ActorRef.noSender + } + + "player can buy a box of ammunition ('9mmbullet_AP')" in { + val terminal = Terminal(GlobalDefinitions.order_terminal) + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "9mmbullet_AP", 0, PlanetSideGUID(0)) + val reply = terminal.Request(player, msg) + reply.isInstanceOf[Terminal.BuyEquipment] mustEqual true + val reply2 = reply.asInstanceOf[Terminal.BuyEquipment] + reply2.item.isInstanceOf[AmmoBox] mustEqual true + reply2.item.asInstanceOf[AmmoBox].Definition mustEqual GlobalDefinitions.bullet_9mm_AP + reply2.item.asInstanceOf[AmmoBox].Capacity mustEqual 50 + } + + "player can buy a weapon ('suppressor')" in { + val terminal = Terminal(GlobalDefinitions.order_terminal) + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "suppressor", 0, PlanetSideGUID(0)) + val reply = terminal.Request(player, msg) + reply.isInstanceOf[Terminal.BuyEquipment] mustEqual true + val reply2 = reply.asInstanceOf[Terminal.BuyEquipment] + reply2.item.isInstanceOf[Tool] mustEqual true + reply2.item.asInstanceOf[Tool].Definition mustEqual GlobalDefinitions.suppressor + } + + "player can buy a box of vehicle ammunition ('105mmbullet')" in { + val terminal = Terminal(GlobalDefinitions.order_terminal) + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 3, "105mmbullet", 0, PlanetSideGUID(0)) + val reply = terminal.Request(player, msg) + reply.isInstanceOf[Terminal.BuyEquipment] mustEqual true + val reply2 = reply.asInstanceOf[Terminal.BuyEquipment] + reply2.item.isInstanceOf[AmmoBox] mustEqual true + reply2.item.asInstanceOf[AmmoBox].Definition mustEqual GlobalDefinitions.bullet_105mm + reply2.item.asInstanceOf[AmmoBox].Capacity mustEqual 100 + } + + "player can buy a support tool ('bank')" in { + val terminal = Terminal(GlobalDefinitions.order_terminal) + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 2, "bank", 0, PlanetSideGUID(0)) + val reply = terminal.Request(player, msg) + reply.isInstanceOf[Terminal.BuyEquipment] mustEqual true + val reply2 = reply.asInstanceOf[Terminal.BuyEquipment] + reply2.item.isInstanceOf[Tool] mustEqual true + reply2.item.asInstanceOf[Tool].Definition mustEqual GlobalDefinitions.bank + } + + "player can buy different armor ('lite_armor')" in { + val terminal = Terminal(GlobalDefinitions.order_terminal) + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "lite_armor", 0, PlanetSideGUID(0)) + + terminal.Request(player, msg) mustEqual Terminal.BuyExosuit(ExoSuitType.Agile) + } + + "player can not buy fake equipment ('sabot')" in { + val terminal = Terminal(GlobalDefinitions.order_terminal) + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "sabot", 0, PlanetSideGUID(0)) + + terminal.Request(player, msg) mustEqual Terminal.NoDeal() + } + + //TODO loudout tests + + "player can not buy equipment from the wrong page ('9mmbullet_AP', page 1)" in { + val terminal = Terminal(GlobalDefinitions.order_terminal) + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "9mmbullet_AP", 0, PlanetSideGUID(0)) + + terminal.Request(player, msg) mustEqual Terminal.NoDeal() + } + } +} diff --git a/common/src/test/scala/objects/terminal/TerminalControlTest.scala b/common/src/test/scala/objects/terminal/TerminalControlTest.scala new file mode 100644 index 00000000..c5d8b062 --- /dev/null +++ b/common/src/test/scala/objects/terminal/TerminalControlTest.scala @@ -0,0 +1,73 @@ +// Copyright (c) 2017 PSForever +package objects.terminal + +import akka.actor.Props +import net.psforever.objects.serverobject.terminals.{Terminal, TerminalControl} +import net.psforever.objects.{GlobalDefinitions, Player} +import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} +import net.psforever.types._ +import objects.ActorTest + +import scala.concurrent.duration.Duration + +class TerminalControlTest extends ActorTest() { + "TerminalControl" should { + "construct (cert terminal)" in { + val terminal = Terminal(GlobalDefinitions.cert_terminal) + terminal.Actor = system.actorOf(Props(classOf[TerminalControl], terminal), "test-cert-term") + } + } +} + +//terminal control is mostly a pass-through actor for Terminal.Exchange messages, wrapped in Terminal.TerminalMessage protocol +//test for Cert_Terminal messages (see CertTerminalTest) +class CertTerminalControl1Test extends ActorTest() { + "TerminalControl can be used to learn a certification ('medium_assault')" in { + val terminal = Terminal(GlobalDefinitions.cert_terminal) + terminal.Actor = system.actorOf(Props(classOf[TerminalControl], terminal), "test-cert-term") + val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Learn, 0, "medium_assault", 0, PlanetSideGUID(0)) + + terminal.Actor ! Terminal.Request(player, msg) + val reply = receiveOne(Duration.create(500, "ms")) + assert(reply.isInstanceOf[Terminal.TerminalMessage]) + val reply2 = reply.asInstanceOf[Terminal.TerminalMessage] + assert(reply2.player == player) + assert(reply2.msg == msg) + assert(reply2.response == Terminal.LearnCertification(CertificationType.MediumAssault, 2)) + } +} + +class CertTerminalControl2Test extends ActorTest() { + "TerminalControl can be used to warn about not learning a fake certification ('juggling')" in { + val terminal = Terminal(GlobalDefinitions.cert_terminal) + terminal.Actor = system.actorOf(Props(classOf[TerminalControl], terminal), "test-cert-term") + val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Learn, 0, "juggling", 0, PlanetSideGUID(0)) + + terminal.Actor ! Terminal.Request(player, msg) + val reply = receiveOne(Duration.create(500, "ms")) + assert(reply.isInstanceOf[Terminal.TerminalMessage]) + val reply2 = reply.asInstanceOf[Terminal.TerminalMessage] + assert(reply2.player == player) + assert(reply2.msg == msg) + assert(reply2.response == Terminal.NoDeal()) + } +} + +class CertTerminalControl3Test extends ActorTest() { + "TerminalControl can be used to forget a certification ('medium_assault')" in { + val terminal = Terminal(GlobalDefinitions.cert_terminal) + terminal.Actor = system.actorOf(Props(classOf[TerminalControl], terminal), "test-cert-term") + val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Sell, 0, "medium_assault", 0, PlanetSideGUID(0)) + + terminal.Actor ! Terminal.Request(player, msg) + val reply = receiveOne(Duration.create(500, "ms")) + assert(reply.isInstanceOf[Terminal.TerminalMessage]) + val reply2 = reply.asInstanceOf[Terminal.TerminalMessage] + assert(reply2.player == player) + assert(reply2.msg == msg) + assert(reply2.response == Terminal.SellCertification(CertificationType.MediumAssault, 2)) + } +} diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 9948e6d7..45345fa2 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -12,7 +12,7 @@ import MDCContextAware.Implicits._ import services.ServiceManager.Lookup import net.psforever.objects._ import net.psforever.objects.equipment._ -import net.psforever.objects.guid.{Task, TaskResolver} +import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} import net.psforever.objects.inventory.{GridInventory, InventoryItem} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.doors.Door @@ -22,7 +22,6 @@ import net.psforever.objects.vehicles.{AccessPermissionGroup, Seat, VehicleLockS import net.psforever.objects.zones.{InterstellarCluster, Zone} import net.psforever.packet.game.objectcreate._ import net.psforever.types._ -import scripts.GUIDTask import services._ import services.avatar._ import services.local._ @@ -552,7 +551,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val box = item.obj.asInstanceOf[AmmoBox] sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(box.GUID, 0, vehicle_guid, box.Capacity.toLong))) }) - case None => ; //no weapons to update + case _ => ; //no weapons to update } val player_guid : PlanetSideGUID = tplayer.GUID sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle_guid, player_guid, seat_num))) diff --git a/pslogin/src/main/scala/services/vehicle/VehicleServiceResponse.scala b/pslogin/src/main/scala/services/vehicle/VehicleServiceResponse.scala index c8012873..6decd460 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleServiceResponse.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleServiceResponse.scala @@ -8,4 +8,3 @@ final case class VehicleServiceResponse(toChannel : String, avatar_guid : PlanetSideGUID, replyMessage : VehicleResponse.Response ) extends GenericEventBusMsg - diff --git a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala index 6ad68310..441da912 100644 --- a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala +++ b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala @@ -3,10 +3,10 @@ package services.vehicle.support import akka.actor.{Actor, ActorRef, Cancellable} import net.psforever.objects.Vehicle +import net.psforever.objects.guid.GUIDTask import net.psforever.objects.vehicles.Seat import net.psforever.objects.zones.Zone import net.psforever.packet.game.PlanetSideGUID -import scripts.GUIDTask import services.ServiceManager import services.ServiceManager.Lookup import services.vehicle.{VehicleAction, VehicleServiceMessage} From 73d0553b2cb2c7e0307257a70b828929fb0c9aad Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 6 Nov 2017 18:16:23 -0500 Subject: [PATCH 4/4] added error messaging for vehicle-decon Actor, should something go wrong with the unregistering process --- .../src/main/scala/WorldSessionActor.scala | 30 +++++++++++++ .../vehicle/support/DeconstructionActor.scala | 42 ++++++++++++++++++- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 45345fa2..9c40a280 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1692,6 +1692,13 @@ class WorldSessionActor extends Actor with MDCContextAware { ) } + /** + * Construct tasking that adds a completed and registered vehicle into the scene. + * Use this function to renew the globally unique identifiers on a vehicle that has already been added to the scene once. + * @param vehicle the `Vehicle` object + * @see `RegisterNewVehicle` + * @return a `TaskResolver.GiveTask` message + */ def RegisterVehicle(vehicle : Vehicle) : TaskResolver.GiveTask = { TaskResolver.GiveTask( new Task() { @@ -1716,6 +1723,16 @@ class WorldSessionActor extends Actor with MDCContextAware { ) } + /** + * Construct tasking that adds a completed and registered vehicle into the scene. + * The major difference between `RegisterVehicle` and `RegisterNewVehicle` is the assumption that this vehicle lacks an internal `Actor`. + * Before being finished, that vehicle is supplied an `Actor` such that it may function properly. + * This function wraps around `RegisterVehicle` and is used in case, prior to this event, + * the vehicle is being brought into existence from scratch and was never a member of any `Zone`. + * @param obj the `Vehicle` object + * @see `RegisterVehicle` + * @return a `TaskResolver.GiveTask` message + */ def RegisterNewVehicle(obj : Vehicle) : TaskResolver.GiveTask = { TaskResolver.GiveTask( new Task() { @@ -1936,10 +1953,23 @@ object WorldSessionActor { def isCancelled() : Boolean = true } + /** + * Calculate the actual distance between two points. + * @param pos1 the first point + * @param pos2 the second point + * @return the distance + */ def Distance(pos1 : Vector3, pos2 : Vector3) : Float = { math.sqrt(DistanceSquared(pos1, pos2)).toFloat } + /** + * Calculate the squared distance between two points. + * Though some time is saved care must be taken that any comparative distance is also squared. + * @param pos1 the first point + * @param pos2 the second point + * @return the distance + */ def DistanceSquared(pos1 : Vector3, pos2 : Vector3) : Float = { val dx : Float = pos1.x - pos2.x val dy : Float = pos1.y - pos2.y diff --git a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala index 441da912..4d09b927 100644 --- a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala +++ b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala @@ -3,7 +3,7 @@ package services.vehicle.support import akka.actor.{Actor, ActorRef, Cancellable} import net.psforever.objects.Vehicle -import net.psforever.objects.guid.GUIDTask +import net.psforever.objects.guid.TaskResolver import net.psforever.objects.vehicles.Seat import net.psforever.objects.zones.Zone import net.psforever.packet.game.PlanetSideGUID @@ -79,7 +79,7 @@ class DeconstructionActor extends Actor { entry.zone.Transport ! Zone.DespawnVehicle(vehicle) context.parent ! DeconstructionActor.DeleteVehicle(vehicle.GUID, zone.Id) //call up to the main event system context.parent ! VehicleServiceMessage.RevokeActorControl(vehicle) //call up to a sibling manager - taskResolver ! GUIDTask.UnregisterVehicle(vehicle)(zone.GUID) + taskResolver ! DeconstructionTask(vehicle, zone) }) if(vehiclesRemain.nonEmpty) { @@ -88,9 +88,39 @@ class DeconstructionActor extends Actor { scrappingProcess = context.system.scheduler.scheduleOnce(short_timeout, self, DeconstructionActor.TryDeleteVehicle()) } + case DeconstructionActor.FailureToDeleteVehicle(localVehicle, localZone, ex) => + org.log4s.getLogger.error(s"vehicle deconstruction: $localVehicle failed to be properly cleaned up from zone $localZone - $ex") + case _ => ; } + /** + * Construct a middleman `Task` intended to return error messages to the `DeconstructionActor`. + * @param vehicle the `Vehicle` object + * @param zone the `Zone` in which the vehicle resides + * @return a `TaskResolver.GiveTask` message + */ + def DeconstructionTask(vehicle : Vehicle, zone : Zone) : TaskResolver.GiveTask = { + import net.psforever.objects.guid.{GUIDTask, Task} + TaskResolver.GiveTask ( + new Task() { + private val localVehicle = vehicle + private val localZone = zone + private val localAnnounce = self + + override def isComplete : Task.Resolution.Value = Task.Resolution.Success + + def Execute(resolver : ActorRef) : Unit = { + resolver ! scala.util.Success(this) + } + + override def onFailure(ex : Throwable): Unit = { + localAnnounce ! DeconstructionActor.FailureToDeleteVehicle(localVehicle, localZone, ex) + } + }, List(GUIDTask.UnregisterVehicle(vehicle)(zone.GUID)) + ) + } + /** * Iterate over entries in a `List` until an entry that does not exceed the time limit is discovered. * Separate the original `List` into two: @@ -168,6 +198,14 @@ object DeconstructionActor { */ private final case class TryDeleteVehicle() + /** + * Error-passing message carrying information out of the final deconstruction GUID unregistering task. + * @param vehicle the `Vehicle` object + * @param zone the `Zone` in which the vehicle may or may not reside + * @param ex information regarding what happened + */ + private final case class FailureToDeleteVehicle(vehicle : Vehicle, zone : Zone, ex : Throwable) + /** * Entry of vehicle information. * The `zone` is maintained separately as a necessity, required to complete the deletion of the vehicle