diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
index 14d667f9..055c2235 100644
--- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
+++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
@@ -345,9 +345,9 @@ object GamePacketOpcode extends Enumeration {
case 0x14 => game.CharacterInfoMessage.decode
case 0x15 => noDecoder(UnknownMessage21)
case 0x16 => game.BindPlayerMessage.decode
- case 0x17 => noDecoder(ObjectCreateMessage_Duplicate)
+ case 0x17 => game.ObjectCreateMessage.decode
// 0x18
- case 0x18 => game.ObjectCreateMessage.decode
+ case 0x18 => game.ObjectCreateDetailedMessage.decode
case 0x19 => game.ObjectDeleteMessage.decode
case 0x1a => game.PingMsg.decode
case 0x1b => noDecoder(VehicleStateMessage)
diff --git a/common/src/main/scala/net/psforever/packet/game/ArmorChangedMessage.scala b/common/src/main/scala/net/psforever/packet/game/ArmorChangedMessage.scala
index 28e87064..48c127f4 100644
--- a/common/src/main/scala/net/psforever/packet/game/ArmorChangedMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/ArmorChangedMessage.scala
@@ -2,6 +2,7 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
+import net.psforever.types.ExoSuitType
import scodec.Codec
import scodec.codecs._
@@ -13,23 +14,21 @@ import scodec.codecs._
* Due to the way armor is handled internally, a player of one faction may not spawn in the exo-suit of another faction.
* That style of exo-suit is never available through this packet.
* As MAX units do not get their weapon by default, all the MAX values produce the same faction-appropriate mechanized exo-suit body visually.
- * (The MAX weapons are supplied in subsequent packets.)
+ * (The MAX weapons are supplied in subsequent packets.)
+ *
+ * Mechanized Assault Subtypes:
* `
- * 0, 0 - Agile
- * 1, 0 - Reinforced
- * 2, 0 - MAX
- * 2, 1 - AI MAX
- * 2, 2 - AV MAX
- * 2, 3 - AA MAX
- * 3, 0 - Infiltration
- * 4, 0 - Standard
+ * 0 - na
+ * 1 - AI MAX
+ * 2 - AV MAX
+ * 3 - AA MAX
* `
* @param player_guid the player
* @param armor the type of exo-suit
* @param subtype the exo-suit subtype, if any
*/
final case class ArmorChangedMessage(player_guid : PlanetSideGUID,
- armor : Int,
+ armor : ExoSuitType.Value,
subtype : Int)
extends PlanetSideGamePacket {
type Packet = ArmorChangedMessage
@@ -40,7 +39,7 @@ final case class ArmorChangedMessage(player_guid : PlanetSideGUID,
object ArmorChangedMessage extends Marshallable[ArmorChangedMessage] {
implicit val codec : Codec[ArmorChangedMessage] = (
("player_guid" | PlanetSideGUID.codec) ::
- ("armor" | uintL(3)) ::
+ ("armor" | ExoSuitType.codec) ::
("subtype" | uintL(3))
).as[ArmorChangedMessage]
}
diff --git a/common/src/main/scala/net/psforever/packet/game/AvatarGrenadeStateMessage.scala b/common/src/main/scala/net/psforever/packet/game/AvatarGrenadeStateMessage.scala
index e2cd6f34..4096f800 100644
--- a/common/src/main/scala/net/psforever/packet/game/AvatarGrenadeStateMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/AvatarGrenadeStateMessage.scala
@@ -1,23 +1,11 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
+import net.psforever.types.GrenadeState
import scodec.Codec
import scodec.codecs._
-/**
- * An `Enumeration` of the kinds of states applicable to the grenade animation.
- */
-object GrenadeState extends Enumeration {
- type Type = Value
- val UNK0,
- PRIMED, //avatars and other depicted player characters
- THROWN //avatars only
- = Value
-
- implicit val codec = PacketHelpers.createEnumerationCodec(this, uint8L)
-}
-
/**
* Report the state of the grenade throw animation for this player.
* The default state is "held at side," though the client's avatar never has to announce this.
@@ -25,12 +13,12 @@ object GrenadeState extends Enumeration {
* The throwing animation has a minor timing glitch.
* Causing another player to raise his arm will always result in that arm being lowered a few seconds later.
* This is as opposed to the client's avatar, who can seem to hold a grenade in the "prepare to throw" state indefinitely.
- * If the avatar looks away from a player whose grenade arm is up ("prepare to throw"), however, when they look back at the player
+ * If the avatar looks away from a player whose grenade arm is up ("prepare to throw"), however, when they look back at the player,
* his grenade arm will occasionally have been lowered ("held at side") again before it would normally be lowered.
*
- * A client will dispatch state '1' and state '2' for the avatar's actions.
- * A client will only react temporarily for another character other than the avatar when the given a state '1'.
- * If that internal state is not changed, however, that other character will not respond to any subsequent '1' state.
+ * A client will dispatch state 'Primed' and state 'Thrown' for the avatar's actions.
+ * A client will only react temporarily for another character other than the avatar when the given a state 'Primed'.
+ * If that internal state is not changed, however, that other character will not respond to any subsequent 'Primed' state.
* (This may also be a glitch.)
*
* States:
diff --git a/common/src/main/scala/net/psforever/packet/game/CharacterCreateRequestMessage.scala b/common/src/main/scala/net/psforever/packet/game/CharacterCreateRequestMessage.scala
index 9b6c8482..29724f0c 100644
--- a/common/src/main/scala/net/psforever/packet/game/CharacterCreateRequestMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/CharacterCreateRequestMessage.scala
@@ -2,19 +2,11 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
-import net.psforever.types.PlanetSideEmpire
+import net.psforever.types.{CharacterGender, PlanetSideEmpire}
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
-object CharacterGender extends Enumeration(1) {
- type Type = Value
-
- val Male, Female = Value
-
- implicit val codec = PacketHelpers.createEnumerationCodec(this, uint2L)
-}
-
/**
* Is sent by the PlanetSide client on character selection completion.
*/
diff --git a/common/src/main/scala/net/psforever/packet/game/ObjectCreateDetailedMessage.scala b/common/src/main/scala/net/psforever/packet/game/ObjectCreateDetailedMessage.scala
new file mode 100644
index 00000000..2f2ab416
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/ObjectCreateDetailedMessage.scala
@@ -0,0 +1,113 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game
+
+import net.psforever.packet.game.objectcreate.{ConstructorData, ObjectClass, ObjectCreateBase, ObjectCreateMessageParent}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
+import scodec.bits.BitVector
+import scodec.{Attempt, Codec, Err}
+import shapeless.{::, HNil}
+
+/**
+ * Communicate with the client that a certain object with certain properties is to be created.
+ * In general, `ObjectCreateMessage` and its counterpart `ObjectCreateDetailedMessage` should look similar.
+ *
+ * In normal packet data order, the parent object is specified before the actual object is specified.
+ * This is most likely a method of early correction.
+ * "Does this parent object exist?"
+ * "Is this new object something that can be attached to this parent?"
+ * "Does the parent have the appropriate attachment slot?"
+ * There is no fail-safe method for any of these circumstances being false, however, and the object will simply not be created.
+ * In instance where the parent data does not exist, the object-specific data is immediately encountered.
+ *
+ * The object's GUID is assigned by the server.
+ * The clients are required to adhere to this new GUID referring to the object.
+ * There is no fail-safe for a conflict between what the server thinks is a new GUID and what any client thinks is an already-assigned GUID.
+ * Likewise, there is no fail-safe between a client failing or refusing to create an object and the server thinking an object has been created.
+ * (The GM-level command `/sync` tests for objects that "do not match" between the server and the client.
+ * It's implementation and scope are undefined.)
+ *
+ * Knowing the object's type is essential for parsing the specific information passed by the `data` parameter.
+ * If the object does not have encoding information or is unknown, it will not translate between byte data and a game object.
+ * @param streamLength the total length of the data that composes this packet in bits, excluding the opcode and end padding
+ * @param objectClass the code for the type of object being constructed
+ * @param guid the GUID this object will be assigned
+ * @param parentInfo if defined, the relationship between this object and another object (its parent)
+ * @param data the data used to construct this type of object;
+ * on decoding, set to `None` if the process failed
+ */
+final case class ObjectCreateDetailedMessage(streamLength : Long,
+ objectClass : Int,
+ guid : PlanetSideGUID,
+ parentInfo : Option[ObjectCreateMessageParent],
+ data : Option[ConstructorData])
+ extends PlanetSideGamePacket {
+ type Packet = ObjectCreateDetailedMessage
+ def opcode = GamePacketOpcode.ObjectCreateMessage
+ def encode = ObjectCreateDetailedMessage.encode(this)
+}
+
+object ObjectCreateDetailedMessage extends Marshallable[ObjectCreateDetailedMessage] {
+ /**
+ * An abbreviated constructor for creating `ObjectCreateMessages`, ignoring the optional aspect of some fields.
+ * @param objectClass the code for the type of object being constructed
+ * @param guid the GUID this object will be assigned
+ * @param parentInfo the relationship between this object and another object (its parent)
+ * @param data the data used to construct this type of object
+ * @return an ObjectCreateMessage
+ */
+ def apply(objectClass : Int, guid : PlanetSideGUID, parentInfo : ObjectCreateMessageParent, data : ConstructorData) : ObjectCreateDetailedMessage =
+ ObjectCreateDetailedMessage(0L, objectClass, guid, Some(parentInfo), Some(data))
+
+ /**
+ * An abbreviated constructor for creating `ObjectCreateMessages`, ignoring `parentInfo`.
+ * @param objectClass the code for the type of object being constructed
+ * @param guid the GUID this object will be assigned
+ * @param data the data used to construct this type of object
+ * @return an ObjectCreateMessage
+ */
+ def apply(objectClass : Int, guid : PlanetSideGUID, data : ConstructorData) : ObjectCreateDetailedMessage =
+ ObjectCreateDetailedMessage(0L, objectClass, guid, None, Some(data))
+
+ /**
+ * Take the important information of a game piece and transform it into bit data.
+ * This function is fail-safe because it catches errors involving bad parsing of the object data.
+ * Generally, the `Exception` messages themselves are not useful here.
+ * @param objClass the code for the type of object being deconstructed
+ * @param obj the object data
+ * @return the bitstream data
+ * @see ObjectClass.selectDataCodec
+ */
+ def encodeData(objClass : Int, obj : ConstructorData, getCodecFunc : (Int) => Codec[ConstructorData.genericPattern]) : BitVector = {
+ var out = BitVector.empty
+ try {
+ val outOpt : Option[BitVector] = getCodecFunc(objClass).encode(Some(obj.asInstanceOf[ConstructorData])).toOption
+ if(outOpt.isDefined)
+ out = outOpt.get
+ }
+ catch {
+ case _ : Exception =>
+ //catch and release, any sort of parse error
+ }
+ out
+ }
+
+ implicit val codec : Codec[ObjectCreateDetailedMessage] = ObjectCreateBase.baseCodec.exmap[ObjectCreateDetailedMessage] (
+ {
+ case _ :: _ :: _ :: _ :: BitVector.empty :: HNil =>
+ Attempt.failure(Err("no data to decode"))
+
+ case len :: cls :: guid :: par :: data :: HNil =>
+ val obj = ObjectCreateBase.decodeData(cls, data, ObjectClass.selectDataDetailedCodec)
+ Attempt.successful(ObjectCreateDetailedMessage(len, cls, guid, par, obj))
+ },
+ {
+ case ObjectCreateDetailedMessage(_ , _ , _, _, None) =>
+ Attempt.failure(Err("no object to encode"))
+
+ case ObjectCreateDetailedMessage(_, cls, guid, par, Some(obj)) =>
+ val len = ObjectCreateBase.streamLen(par, obj) //even if a stream length has been assigned, it can not be trusted during encoding
+ val bitvec = ObjectCreateBase.encodeData(cls, obj, ObjectClass.selectDataDetailedCodec)
+ Attempt.successful(len :: cls :: guid :: par :: bitvec :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/ObjectCreateMessage.scala b/common/src/main/scala/net/psforever/packet/game/ObjectCreateMessage.scala
index 5bdceb2b..bbe9108f 100644
--- a/common/src/main/scala/net/psforever/packet/game/ObjectCreateMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/ObjectCreateMessage.scala
@@ -1,53 +1,50 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.game.objectcreate.{ConstructorData, ObjectClass, StreamBitSize}
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
+import net.psforever.packet.game.objectcreate._
+import scodec.{Attempt, Codec, Err}
import scodec.bits.BitVector
-import scodec.{Attempt, Codec, DecodeResult, Err}
-import scodec.codecs._
import shapeless.{::, HNil}
-/**
- * The parent information of a created object.
- *
- * Rather than a created-parent with a created-child relationship, the whole of the packet still only creates the child.
- * The parent is a pre-existing object into which the (created) child is attached.
- * The slot is encoded as a string length integer, following PlanetSide Classic convention for slot numbering.
- * It is either a 0-127 eight bit number, or a 128-32767 sixteen bit number.
- * @param guid the GUID of the parent object
- * @param slot a parent-defined slot identifier that explains where the child is to be attached to the parent
- */
-final case class ObjectCreateMessageParent(guid : PlanetSideGUID,
- slot : Int)
-
/**
* Communicate with the client that a certain object with certain properties is to be created.
- * The object may also have primitive assignment (attachment) properties.
+ * In general, `ObjectCreateMessage` and its counterpart `ObjectCreateDetailedMessage` should look similar.
*
- * In normal packet data order, the parent object is specified before the actual object is specified.
- * This is most likely a method of early correction.
- * "Does this parent object exist?"
- * "Is this new object something that can be attached to this parent?"
- * "Does the parent have the appropriate attachment slot?"
- * There is no fail-safe method for any of these circumstances being false, however, and the object will simply not be created.
- * In instance where the parent data does not exist, the object-specific data is immediately encountered.
+ * `ObjectCreateMessage` is capable of creating every non-environmental object in the game through the use of encoding patterns.
+ * The objects produced by this packet generally do not always fully express all the complexities of the object class.
+ * With respect to a client's avatar, all of the items in his inventory are given thorough detail so that the client can account for their interaction.
+ * The "shallow" objects produced by this packet are not like that.
+ * They express only the essential information necessary for client interaction when the client interacts with them.
+ * For example, a weapon defined by this packet may not care internally what fire mode it is in or how much ammunition it has.
+ * Such a weapon is not in the client's player's holster or inventory.
+ * It is imperceptive information to which he would not currently have access.
+ * An `0x17` game object is, therefore, a game object with only the essential data exposed.
*
- * The object's GUID is assigned by the server.
- * The clients are required to adhere to this new GUID referring to the object.
- * There is no fail-safe for a conflict between what the server thinks is a new GUID and what any client thinks is an already-assigned GUID.
- * Likewise, there is no fail-safe between a client failing or refusing to create an object and the server thinking an object has been created.
- * (The GM-level command `/sync` tests for objects that "do not match" between the server and the client.
- * It's implementation and scope are undefined.)
+ * When interacting with an `0x17` game object, the server will swap back and forth between it and an `0x18` object.
+ * (Or it will be removed when it is placed somewhere a given client will no longer be able to see it.)
+ * The purpose of this conversion is to control network traffic and object agency.
+ * It is not necessary to keep track of all objects on every player on every client individually.
+ * This relates to the goal of this packet exposing only "essential data."
+ * One player does not need to know how much ammunition remains in a weapon belonging to another player normally.
+ * One player also does not need to know how much ammunition is used up when another player reloads their weapon.
+ * The only way the first player will know is when the weapon is transferred into his own inventory.
+ * All other clients are spared micromanagement of the hypothetical other player's weapon.
+ * Updated information is only made available when and where it is needed.
*
- * Knowing the object's class is essential for parsing the specific information passed by the `data` parameter.
- * @param streamLength the total length of the data that composes this packet in bits, excluding the opcode and end padding
- * @param objectClass the code for the type of object being constructed
+ * Knowing the object's type is necessary for proper parsing.
+ * If the object does not have encoding information or is unknown, it will not translate between byte data and a game object.
+ * @param streamLength the total length of the data that composes this packet in bits;
+ * exclude the opcode (1 byte) and end padding (0-7 bits);
+ * when encoding, it will be calculated automatically
+ * @param objectClass the code for the type of object being constructed;
+ * always an 11-bit LE value
* @param guid the GUID this object will be assigned
* @param parentInfo if defined, the relationship between this object and another object (its parent)
* @param data the data used to construct this type of object;
* on decoding, set to `None` if the process failed
- * @see ObjectClass.selectDataCodec
+ * @see ObjectCreateDetailedMessage
+ * @see ObjectCreateMessageParent
*/
final case class ObjectCreateMessage(streamLength : Long,
objectClass : Int,
@@ -55,185 +52,67 @@ final case class ObjectCreateMessage(streamLength : Long,
parentInfo : Option[ObjectCreateMessageParent],
data : Option[ConstructorData])
extends PlanetSideGamePacket {
- def opcode = GamePacketOpcode.ObjectCreateMessage
+ type Packet = ObjectCreateMessage
+ def opcode = GamePacketOpcode.ObjectCreateMessage_Duplicate
def encode = ObjectCreateMessage.encode(this)
}
object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] {
/**
- * An abbreviated constructor for creating `ObjectCreateMessages`, ignoring the optional aspect of some fields.
- * @param streamLength the total length of the data that composes this packet in bits, excluding the opcode and end padding
+ * An abbreviated constructor for creating `ObjectCreateMessage`s, ignoring the optional aspect of some fields.
* @param objectClass the code for the type of object being constructed
* @param guid the GUID this object will be assigned
* @param parentInfo the relationship between this object and another object (its parent)
* @param data the data used to construct this type of object
- * @return an ObjectCreateMessage
+ * @return an `ObjectCreateMessage`
*/
- def apply(streamLength : Long, objectClass : Int, guid : PlanetSideGUID, parentInfo : ObjectCreateMessageParent, data : ConstructorData) : ObjectCreateMessage =
- ObjectCreateMessage(streamLength, objectClass, guid, Some(parentInfo), Some(data))
+ def apply(objectClass : Int, guid : PlanetSideGUID, parentInfo : ObjectCreateMessageParent, data : ConstructorData) : ObjectCreateMessage = {
+ val parentInfoOpt : Option[ObjectCreateMessageParent] = Some(parentInfo)
+ ObjectCreateMessage(ObjectCreateBase.streamLen(parentInfoOpt, data), objectClass, guid, parentInfoOpt, Some(data))
+ }
/**
- * An abbreviated constructor for creating `ObjectCreateMessages`, ignoring `parentInfo`.
- * @param streamLength the total length of the data that composes this packet in bits, excluding the opcode and end padding
+ * An abbreviated constructor for creating `ObjectCreateMessage`s, calculating `streamLen` and ignoring `parentInfo`.
* @param objectClass the code for the type of object being constructed
* @param guid the GUID this object will be assigned
* @param data the data used to construct this type of object
- * @return an ObjectCreateMessage
+ * @return an `ObjectCreateMessage`
*/
- def apply(streamLength : Long, objectClass : Int, guid : PlanetSideGUID, data : ConstructorData) : ObjectCreateMessage =
- ObjectCreateMessage(streamLength, objectClass, guid, None, Some(data))
-
- type Pattern = Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: HNil
- type outPattern = Long :: Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: Option[ConstructorData] :: HNil
- /**
- * Codec for formatting around the lack of parent data in the stream.
- */
- private val noParent : Codec[Pattern] = (
- ("objectClass" | uintL(0xb)) :: //11u
- ("guid" | PlanetSideGUID.codec) //16u
- ).xmap[Pattern](
- {
- case cls :: guid :: HNil =>
- cls :: guid :: None :: HNil
- }, {
- case cls :: guid :: None :: HNil =>
- cls :: guid :: HNil
- }
- )
- /**
- * Codec for reading and formatting parent data from the stream.
- */
- private val parent : Codec[Pattern] = (
- ("parentGuid" | PlanetSideGUID.codec) :: //16u
- ("objectClass" | uintL(0xb)) :: //11u
- ("guid" | PlanetSideGUID.codec) :: //16u
- ("parentSlotIndex" | PacketHelpers.encodedStringSize) //8u or 16u
- ).xmap[Pattern](
- {
- case pguid :: cls :: guid :: slot :: HNil =>
- cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil
- }, {
- case cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil =>
- pguid :: cls :: guid :: slot :: HNil
- }
- )
-
- /**
- * Take bit data and transform it into an object that expresses the important information of a game piece.
- * This function is fail-safe because it catches errors involving bad parsing of the bitstream data.
- * Generally, the `Exception` messages themselves are not useful here.
- * The important parts are what the packet thought the object class should be and what it actually processed.
- * @param objectClass the code for the type of object being constructed
- * @param data the bitstream data
- * @return the optional constructed object
- */
- private def decodeData(objectClass : Int, data : BitVector) : Option[ConstructorData] = {
- var out : Option[ConstructorData] = None
- try {
- val outOpt : Option[DecodeResult[_]] = ObjectClass.selectDataCodec(objectClass).decode(data).toOption
- if(outOpt.isDefined)
- out = outOpt.get.value.asInstanceOf[ConstructorData.genericPattern]
- }
- catch {
- case ex : Exception =>
- //catch and release, any sort of parse error
- }
- out
+ def apply(objectClass : Int, guid : PlanetSideGUID, data : ConstructorData) : ObjectCreateMessage = {
+ ObjectCreateMessage(ObjectCreateBase.streamLen(None, data), objectClass, guid, None, Some(data))
}
- /**
- * Take the important information of a game piece and transform it into bit data.
- * This function is fail-safe because it catches errors involving bad parsing of the object data.
- * Generally, the `Exception` messages themselves are not useful here.
- * @param objClass the code for the type of object being deconstructed
- * @param obj the object data
- * @return the bitstream data
- */
- private def encodeData(objClass : Int, obj : ConstructorData) : BitVector = {
- var out = BitVector.empty
- try {
- val outOpt : Option[BitVector] = ObjectClass.selectDataCodec(objClass).encode(Some(obj.asInstanceOf[ConstructorData])).toOption
- if(outOpt.isDefined)
- out = outOpt.get
- }
- catch {
- case ex : Exception =>
- //catch and release, any sort of parse error
- }
- out
- }
-
- /**
- * Calculate the stream length in number of bits by factoring in the whole message in two portions.
- * This process automates for: object encoding.
- *
- * Ignoring the parent data, constant field lengths have already been factored into the results.
- * That includes:
- * the length of the stream length field (32u),
- * the object's class (11u),
- * the object's GUID (16u),
- * and the bit to determine if there will be parent data.
- * In total, these fields form a known fixed length of 60u.
- * @param parentInfo if defined, the relationship between this object and another object (its parent);
- * information about the parent adds either 24u or 32u
- * @param data if defined, the data used to construct this type of object;
- * the data length is indeterminate until it is walked-through;
- * note: the type is `StreamBitSize` as opposed to `ConstructorData`
- * @return the total length of the resulting data stream in bits
- */
- private def streamLen(parentInfo : Option[ObjectCreateMessageParent], data : StreamBitSize) : Long = {
- //knowable length
- val base : Long = if(parentInfo.isDefined) {
- if(parentInfo.get.slot > 127) 92L else 84L //(32u + 1u + 11u + 16u) ?+ (16u + (8u | 16u))
- }
- else {
- 60L
- }
- base + data.bitsize
- }
-
- implicit val codec : Codec[ObjectCreateMessage] = (
- ("streamLength" | uint32L) ::
- (either(bool, parent, noParent).exmap[Pattern] (
- {
- case Left(a :: b :: Some(c) :: HNil) =>
- Attempt.successful(a :: b :: Some(c) :: HNil) //true, _, _, Some(c)
- case Right(a :: b :: None :: HNil) =>
- Attempt.successful(a :: b :: None :: HNil) //false, _, _, None
- // failure cases
- case Left(a :: b :: None :: HNil) =>
- Attempt.failure(Err("missing parent structure")) //true, _, _, None
- case Right(a :: b :: Some(c) :: HNil) =>
- Attempt.failure(Err("unexpected parent structure")) //false, _, _, Some(c)
- }, {
- case a :: b :: Some(c) :: HNil =>
- Attempt.successful(Left(a :: b :: Some(c) :: HNil))
- case a :: b :: None :: HNil =>
- Attempt.successful(Right(a :: b :: None :: HNil))
- }
- ) :+
- ("data" | bits)) //greed is good
- ).exmap[outPattern] (
+ implicit val codec : Codec[ObjectCreateMessage] = ObjectCreateBase.baseCodec.exmap[ObjectCreateMessage] (
{
case _ :: _ :: _ :: _ :: BitVector.empty :: HNil =>
Attempt.failure(Err("no data to decode"))
+
case len :: cls :: guid :: par :: data :: HNil =>
- Attempt.successful(len :: cls :: guid :: par :: decodeData(cls, data) :: HNil)
+ val obj = ObjectCreateBase.decodeData(cls, data,
+ if(par.isDefined) {
+ ObjectClass.selectDataCodec
+ }
+ else {
+ ObjectClass.selectDataDroppedCodec
+ }
+ )
+ Attempt.successful(ObjectCreateMessage(len, cls, guid, par, obj))
},
{
- case _ :: _ :: _ :: _ :: None :: HNil =>
+ case ObjectCreateMessage(_ , _ , _, _, None) =>
Attempt.failure(Err("no object to encode"))
- case _ :: cls :: guid :: par :: Some(obj) :: HNil =>
- Attempt.successful(streamLen(par, obj) :: cls :: guid :: par :: encodeData(cls, obj) :: HNil)
+
+ case ObjectCreateMessage(_, cls, guid, par, Some(obj)) =>
+ val len = ObjectCreateBase.streamLen(par, obj) //even if a stream length has been assigned, it can not be trusted during encoding
+ val bitvec = ObjectCreateBase.encodeData(cls, obj,
+ if(par.isDefined) {
+ ObjectClass.selectDataCodec
+ }
+ else {
+ ObjectClass.selectDataDroppedCodec
+ }
+ )
+ Attempt.successful(len :: cls :: guid :: par :: bitvec :: HNil)
}
- ).xmap[ObjectCreateMessage] (
- {
- case len :: cls :: guid :: par :: obj :: HNil =>
- ObjectCreateMessage(len, cls, guid, par, obj)
- },
- {
- case ObjectCreateMessage(len, cls, guid, par, obj) =>
- len :: cls :: guid :: par :: obj :: HNil
- }
- ).as[ObjectCreateMessage]
+ )
}
diff --git a/common/src/main/scala/net/psforever/packet/game/ReplicationStreamMessage.scala b/common/src/main/scala/net/psforever/packet/game/ReplicationStreamMessage.scala
index bf588b08..0f4aafc3 100644
--- a/common/src/main/scala/net/psforever/packet/game/ReplicationStreamMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/ReplicationStreamMessage.scala
@@ -104,7 +104,7 @@ final case class SquadListing(index : Int = 255,
* `behavior behavior2`
* `1 X `Update where initial entry removes a squad from the list
* `5 6 `Clear squad list and initialize new squad list
- * `5 6 `Clear squad list (ransitions directly into 255-entry)
+ * `5 6 `Clear squad list (transitions directly into 255-entry)
* `6 X `Update a squad in the list
* @param behavior a code that suggests the primary purpose of the data in this packet
* @param behavior2 during initialization, this code is read;
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ACEData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ACEData.scala
new file mode 100644
index 00000000..af93c131
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ACEData.scala
@@ -0,0 +1,44 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import net.psforever.packet.game.PlanetSideGUID
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * A representation of an adaptive construction engine (ACE).
+ * This one-time-use item deploys a variety of utilities into the game environment.
+ * Has an advanced version internally called an `advanced_ace` and commonly called a Field Deployment Unit (FDU).
+ * @param unk1 na
+ * @param unk2 na
+ * @param unk3 na
+ */
+final case class ACEData(unk1 : Int,
+ unk2 : Int,
+ unk3 : Int = 0
+ ) extends ConstructorData {
+ override def bitsize : Long = 34L
+}
+
+object ACEData extends Marshallable[ACEData] {
+ implicit val codec : Codec[ACEData] = (
+ ("unk1" | uint4L) ::
+ ("unk2" | uint4L) ::
+ uint(20) ::
+ ("unk3" | uint4L) ::
+ uint2L
+ ).exmap[ACEData] (
+ {
+ case unk1 :: unk2 :: 0 :: unk3 :: 0 :: HNil =>
+ Attempt.successful(ACEData(unk1, unk2, unk3))
+ case _ :: _ :: _ :: _ :: _ :: HNil =>
+ Attempt.failure(Err("invalid ace data format"))
+ },
+ {
+ case ACEData(unk1, unk2, unk3) =>
+ Attempt.successful(unk1 :: unk2 :: 0 :: unk3 :: 0 :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ACEDeployableData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ACEDeployableData.scala
new file mode 100644
index 00000000..af4182c0
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ACEDeployableData.scala
@@ -0,0 +1,77 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import net.psforever.packet.game.PlanetSideGUID
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * Data that is common to a number of items that are spawned by the adaptive construction engine, or its advanced version.
+ * @param pos where and how the object is oriented
+ * @param unk na
+ * @param player_guid the player who placed this object
+ */
+final case class ACEDeployableData(pos : PlacementData,
+ unk : Int,
+ player_guid : PlanetSideGUID
+ ) extends StreamBitSize {
+ override def bitsize : Long = 23L + pos.bitsize
+}
+
+object ACEDeployableData extends Marshallable[ACEDeployableData] {
+ final val internalWeapon_bitsize : Long = 10
+
+ /**
+ * `Codec` for transforming reliable `WeaponData` from the internal structure of the turret when it is defined.
+ * Works for both `SmallTurretData` and `OneMannedFieldTurretData`.
+ */
+ val internalWeaponCodec : Codec[InternalSlot] = (
+ uint8L :: //number of internal weapons (should be 1)?
+ uint2L ::
+ InternalSlot.codec
+ ).exmap[InternalSlot] (
+ {
+ case 1 :: 0 :: InternalSlot(a1, b1, c1, WeaponData(a2, b2, c2, d)) :: HNil =>
+ Attempt.successful(InternalSlot(a1, b1, c1, WeaponData(a2, b2, c2, d)))
+
+ case 1 :: 0 :: InternalSlot(_, _, _, _) :: HNil =>
+ Attempt.failure(Err(s"turret internals must contain weapon data"))
+
+ case n :: 0 :: _ :: HNil =>
+ Attempt.failure(Err(s"turret internals can not have $n weapons"))
+
+ case _ =>
+ Attempt.failure(Err("invalid turret internals data format"))
+ },
+ {
+ case InternalSlot(a1, b1, c1, WeaponData(a2, b2, c2, d)) =>
+ Attempt.successful(1 :: 0 :: InternalSlot(a1, b1, c1, WeaponData(a2, b2, c2, d)) :: HNil)
+
+ case InternalSlot(_, _, _, _) =>
+ Attempt.failure(Err(s"turret internals must contain weapon data"))
+
+ case _ =>
+ Attempt.failure(Err("invalid turret internals data format"))
+ }
+ )
+
+ implicit val codec : Codec[ACEDeployableData] = (
+ ("pos" | PlacementData.codec) ::
+ ("unk1" | uint(7)) ::
+ ("player_guid" | PlanetSideGUID.codec)
+ ).exmap[ACEDeployableData] (
+ {
+ case pos :: unk :: player :: HNil =>
+ Attempt.successful(ACEDeployableData(pos, unk, player))
+
+ case _ =>
+ Attempt.failure(Err("invalid deployable data format"))
+ },
+ {
+ case ACEDeployableData(pos, unk, player) =>
+ Attempt.successful(pos :: unk :: player :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/AegisShieldGeneratorData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/AegisShieldGeneratorData.scala
new file mode 100644
index 00000000..e94a6348
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/AegisShieldGeneratorData.scala
@@ -0,0 +1,39 @@
+// 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 the aegis shield generator deployed using an advanced adaptive construction engine.
+ * @param deploy data common to objects spawned by the (advanced) adaptive construction engine
+ * @param health the amount of health the object has, as a percentage of a filled bar
+ */
+final case class AegisShieldGeneratorData(deploy : ACEDeployableData,
+ health : Int
+ ) extends ConstructorData {
+ override def bitsize : Long = {
+ 108 + deploy.bitsize //8u + 100u
+ }
+}
+
+object AegisShieldGeneratorData extends Marshallable[AegisShieldGeneratorData] {
+ implicit val codec : Codec[AegisShieldGeneratorData] = (
+ ("deploy" | ACEDeployableData.codec) ::
+ ("health" | uint8L) ::
+ uint32 :: uint32 :: uint32 :: uint4L //100 bits
+ ).exmap[AegisShieldGeneratorData] (
+ {
+ case deploy :: health :: 0 :: 0 :: 0 :: 0 :: HNil =>
+ Attempt.successful(AegisShieldGeneratorData(deploy, health))
+ case _ =>
+ Attempt.failure(Err("invalid aegis data format"))
+ },
+ {
+ case AegisShieldGeneratorData(deploy, health) =>
+ Attempt.successful(deploy :: health :: 0L :: 0L :: 0L :: 0 :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/AmmoBoxData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/AmmoBoxData.scala
index a0157824..865e697a 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/AmmoBoxData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/AmmoBoxData.scala
@@ -8,68 +8,46 @@ import scodec.codecs._
import shapeless.{::, HNil}
/**
- * A representation of the ammunition portion of `ObjectCreateMessage` packet data.
+ * A representation of ammunition that can be created using `ObjectCreateMessage` packet data.
* This data will help construct a "box" of that type of ammunition when standalone.
* It can also be constructed directly inside a weapon as its magazine.
*
- * The maximum amount of ammunition that can be stored in a single box is 65535 units.
- * Regardless of the interface, however, the number will never be fully visible.
- * Only the first three digits or the first four digits may be represented.
- * @param magazine the number of rounds available
- * @see WeaponData
+ * This ammunition object ompletely ignores thr capacity field, normal to detailed ammunition objects.
+ * Creating an object of this type directly and picking it up or observing it (in a weapon) reveals a single round.
+ * @param unk na;
+ * defaults to 0
+ * @see `DetailedAmmoBoxData`
*/
-final case class AmmoBoxData(magazine : Int) extends ConstructorData {
- /**
- * Performs a "sizeof()" analysis of the given object.
- * @see ConstructorData.bitsize
- * @return the number of bits necessary to represent this object
- */
- override def bitsize : Long = 40L
+final case class AmmoBoxData(unk : Int = 0) extends ConstructorData {
+ override def bitsize : Long = 24L
}
object AmmoBoxData extends Marshallable[AmmoBoxData] {
/**
- * An abbreviated constructor for creating `WeaponData` while masking use of `InternalSlot`.
+ * An abbreviated constructor for creating `AmmoBoxData` while masking use of `InternalSlot`.
* @param cls the code for the type of object being constructed
* @param guid the GUID this object will be assigned
* @param parentSlot a parent-defined slot identifier that explains where the child is to be attached to the parent
- * @param ammo the `AmmoBoxData`
+ * @param ammo the ammunition object
* @return an `InternalSlot` object that encapsulates `AmmoBoxData`
*/
def apply(cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : AmmoBoxData) : InternalSlot =
new InternalSlot(cls, guid, parentSlot, ammo)
implicit val codec : Codec[AmmoBoxData] = (
- uint8L ::
- uint(15) ::
- ("magazine" | uint16L) ::
- bool
- ).exmap[AmmoBoxData] (
+ uint4L ::
+ ("unk" | uint4L) ::
+ uint(16)
+ ).exmap[AmmoBoxData] (
{
- case 0xC8 :: 0 :: mag :: false :: HNil =>
- Attempt.successful(AmmoBoxData(mag))
- case a :: b :: _ :: d :: HNil =>
+ case 0xC :: unk :: 0 :: HNil =>
+ Attempt.successful(AmmoBoxData(unk))
+ case _ :: _ :: _ :: HNil =>
Attempt.failure(Err("invalid ammunition data format"))
},
{
- case AmmoBoxData(mag) =>
- Attempt.successful(0xC8 :: 0 :: mag :: false:: HNil)
- }
- )
-
- /**
- * Transform between AmmoBoxData and ConstructorData.
- */
- val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
- {
- case x =>
- Attempt.successful(Some(x.asInstanceOf[ConstructorData]))
- },
- {
- case Some(x) =>
- Attempt.successful(x.asInstanceOf[AmmoBoxData])
- case _ =>
- Attempt.failure(Err("can not encode ammo box data"))
+ case AmmoBoxData(unk) =>
+ Attempt.successful(0xC :: unk :: 0 :: HNil)
}
)
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/BoomerTriggerData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/BoomerTriggerData.scala
new file mode 100644
index 00000000..f5e704b7
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/BoomerTriggerData.scala
@@ -0,0 +1,34 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * A representation of the detonator utility that is created when putting down a Boomer with an ACE.
+ * @param unk na
+ */
+final case class BoomerTriggerData(unk : Int = 0x8) extends ConstructorData {
+ override def bitsize : Long = 34L
+}
+
+object BoomerTriggerData extends Marshallable[BoomerTriggerData] {
+ implicit val codec : Codec[BoomerTriggerData] = (
+ uint4L ::
+ uint4L ::
+ uint(26)
+ ).exmap[BoomerTriggerData] (
+ {
+ case 0xC :: unk :: 0 :: HNil =>
+ Attempt.successful(BoomerTriggerData(unk))
+ case _ =>
+ Attempt.failure(Err("invalid command detonater format"))
+ },
+ {
+ case BoomerTriggerData(unk) =>
+ Attempt.successful(0xC :: unk :: 0 :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/CaptureFlagData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/CaptureFlagData.scala
new file mode 100644
index 00000000..44b32560
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/CaptureFlagData.scala
@@ -0,0 +1,61 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import net.psforever.types.PlanetSideEmpire
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * A representation of the capture flag portion of `ObjectCreateDetailedMessage` packet data.
+ * This creates what is known as a lattice logic unit, or LLU.
+ * It is originally spawned in the base object called the lattice link socket during certain base captures.
+ *
+ * Players can not directly interact with the capture flag.
+ * Whenever an applicable player is nearby, that client will rapidly fire off `ItemUseMessage` packets to the server.
+ * The capture flag will be picked-up by the player and stored in a special slot that is not part of their inventory.
+ * A special dropping keybind has been prepared to relinquish the capture flag back to the game world.
+ * @param faction the empire whose players may interact with this capture flag
+ * @param unk1 na
+ * @param unk2 na
+ * @param unk3 na
+ * @param unk4 na
+ */
+final case class CaptureFlagData(pos : PlacementData,
+ faction : PlanetSideEmpire.Value,
+ unk1 : Int,
+ unk2 : Int,
+ unk3 : Int,
+ unk4 : Int
+ ) extends ConstructorData {
+ override def bitsize : Long = 88L + pos.bitsize
+}
+
+object CaptureFlagData extends Marshallable[CaptureFlagData] {
+ implicit val codec : Codec[CaptureFlagData] = (
+ ("pos" | PlacementData.codec) ::
+ ("faction" | PlanetSideEmpire.codec) ::
+ bool ::
+ uint4L ::
+ uint16L ::
+ ("unk1" | uint8L) ::
+ uint8L ::
+ ("unk2" | uint8L) ::
+ uint8L ::
+ ("unk3" | uint16L) :: //probably a PlanetSideGUID
+ ("unk4" | uint8L) ::
+ uint(9)
+ ).exmap[CaptureFlagData] (
+ {
+ case pos :: fac :: false :: 4 :: 0 :: unk1 :: 0 :: unk2 :: 0 :: unk3 :: unk4 :: 0 :: HNil =>
+ Attempt.Successful(CaptureFlagData(pos, fac, unk1, unk2, unk3, unk4))
+ case _ =>
+ Attempt.failure(Err("invalid capture flag data"))
+ },
+ {
+ case CaptureFlagData(pos, fac, unk1, unk2, unk3, unk4) =>
+ Attempt.successful(pos :: fac :: false :: 4 :: 0 :: unk1 :: 0 :: unk2 :: 0 :: unk3 :: unk4 :: 0 :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala
new file mode 100644
index 00000000..01f775cd
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala
@@ -0,0 +1,220 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.{Marshallable, PacketHelpers}
+import net.psforever.types.{CharacterGender, ExoSuitType, GrenadeState, PlanetSideEmpire}
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * A part of a representation of the avatar portion of `ObjectCreateMessage` packet data.
+ *
+ * This partition of the data stream contains information used to represent how the player's avatar is presented.
+ * This appearance coincides with the data available from the `CharacterCreateRequestMessage` packet.
+ *
+ * Voice:
+ * ` MALE FEMALE`
+ * `0 - no voice no voice`
+ * `1 - male_1 female_1`
+ * `2 - male_2 female_2`
+ * `3 - male_3 female_3`
+ * `4 - male_4 female_4`
+ * `5 - male_5 female_5`
+ * `6 - female_1 no voice`
+ * `7 - female_2 no voice`
+ * @param name the unique name of the avatar;
+ * minimum of two characters
+ * @param faction the empire to which the avatar belongs
+ * @param sex whether the avatar is `Male` or `Female`
+ * @param head the avatar's face and hair;
+ * by row and column on the character creation screen, the high nibble is the row and the low nibble is the column
+ * @param voice the avatar's voice selection
+ * @see `PlanetSideEmpire`
+ * @see `CharacaterGender`
+ */
+final case class BasicCharacterData(name : String,
+ faction : PlanetSideEmpire.Value,
+ sex : CharacterGender.Value,
+ head : Int,
+ voice : Int)
+
+/**
+ * A part of a representation of the avatar portion of `ObjectCreateDetailedMessage` packet data.
+ *
+ * This is a shared partition of the data used to represent how the player's avatar is presented.
+ * It is utilized by both `0x17 ObjectCreateMessage CharacterData` and `0x18 ObjectCreateDetailedMessage DetailedCharacterData`.
+ * This can be considered the data that goes into creating the player's model.
+ *
+ * Only a few changes would occur depending on which packet would deal with the data.
+ * One example is `facingYawUpper` which, when depicting avatars, can be set to represent non-trivial turning angles.
+ * When depicting other players, it is limited to a small range of angles in the direction of that model's forward-facing.
+ * Another example is the outfit information: not usually represented for avatars; but, always represented for other players.
+ *
+ * One way the player's model can be changed dramatically involves being depicted as "released."
+ * In this form, their body appears as a backpack (or pumpkin or pastry) that can be looted for the equipment carried while alive.
+ * Companion data will describe how the player is represented while he is "dead," usually a requirement for being "released."
+ * Without that requirement here, it is possible to depicte the player as a "living backpack."
+ * The said equipment is also defined elsewhere.
+ * Another dramatic change replaces the player's model with a ball of plasma that masks the player while riding zip lines.
+ *
+ * Exploration:
+ * How do I crouch?
+ * @param pos the position of the character in the world environment (in three coordinates)
+ * @param basic_appearance the player's cardinal appearance settings
+ * @param voice2 na;
+ * affects the frequency by which the character's voice is heard (somehow);
+ * commonly 3 for best results
+ * @param black_ops whether or not this avatar is enrolled in Black OPs
+ * @param jammered the player has been caught in an EMP blast recently;
+ * creates a jammered sound effect that follows the player around and can be heard by others
+ * @param exosuit the type of exo-suit the avatar will be depicted in;
+ * for Black OPs, the agile exo-suit and the reinforced exo-suit are replaced with the Black OPs exo-suits
+ * @param outfit_name the name of the outfit to which this player belongs;
+ * if the option is selected, allies with see either "[`outfit_name`]" or "{No Outfit}" under the player's name
+ * @param outfit_logo the decal seen on the player's exo-suit (and beret and cap) associated with the player's outfit;
+ * if there is a variable color for that decal, the faction-appropriate one is selected
+ * @param facingPitch the angle with respect to the sky and the ground towards which the avatar is looking
+ * @param facingYawUpper the angle of the avatar's upper body with respect to its forward-facing direction
+ * @param lfs this player is looking for a squad;
+ * all allies will see the phrase "[Looking for Squad]" under the player's name
+ * @param is_cloaking avatar is cloaked by virtue of an Infiltration Suit
+ * @param grenade_state if the player has a grenade `Primed`;
+ * should be `GrenadeStateState.None` if nothing special
+ * @param charging_pose animation pose for both charging modules and BFR imprinting
+ * @param on_zipline player's model is changed into a faction-color ball of energy, as if on a zip line
+ * @param ribbons the four merit commendation ribbon medals
+ * @see `CharacterData`
+ * @see `DetailedCharacterData`
+ * @see `PlacementData`
+ * @see `ExoSuitType`
+ * @see `GrenadeState`
+ * @see `RibbonBars`
+ * @see `http://wiki.planetsidesyndicate.com/index.php?title=Outfit_Logo` for a list of outfit decals
+ */
+final case class CharacterAppearanceData(pos : PlacementData,
+ basic_appearance : BasicCharacterData,
+ voice2 : Int,
+ black_ops : Boolean,
+ jammered : Boolean,
+ exosuit : ExoSuitType.Value,
+ outfit_name : String,
+ outfit_logo : Int,
+ backpack : Boolean,
+ facingPitch : Int,
+ facingYawUpper : Int,
+ lfs : Boolean,
+ grenade_state : GrenadeState.Value,
+ is_cloaking : Boolean,
+ charging_pose : Boolean,
+ on_zipline : Boolean,
+ ribbons : RibbonBars) extends StreamBitSize {
+
+ override def bitsize : Long = {
+ //factor guard bool values into the base size, not its corresponding optional field
+ val placementSize : Long = pos.bitsize
+ val nameStringSize : Long = StreamBitSize.stringBitSize(basic_appearance.name, 16) + CharacterAppearanceData.namePadding(pos.init_move)
+ val outfitStringSize : Long = StreamBitSize.stringBitSize(outfit_name, 16) + CharacterAppearanceData.outfitNamePadding
+ val altModelSize = if(on_zipline || backpack) { 1L } else { 0L }
+ 335L + placementSize + nameStringSize + outfitStringSize + altModelSize
+ }
+}
+
+object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
+ /**
+ * Get the padding of the player's name.
+ * The padding will always be a number 0-7.
+ * @return the pad length in bits
+ */
+ def namePadding(move : Option[_]) : Int = {
+ if(move.isDefined) {
+ 2
+ }
+ else {
+ 4
+ }
+ }
+
+ /**
+ * Get the padding of the outfit's name.
+ * The padding will always be a number 0-7.
+ * @return the pad length in bits
+ */
+ def outfitNamePadding : Int = {
+ 6
+ }
+
+ implicit val codec : Codec[CharacterAppearanceData] = (
+ ("pos" | PlacementData.codec) >>:~ { pos =>
+ ("faction" | PlanetSideEmpire.codec) ::
+ ("black_ops" | bool) ::
+ (("alt_model" | bool) >>:~ { alt_model => //modifies stream format (to display alternate player models)
+ ignore(1) :: //unknown
+ ("jammered" | bool) ::
+ bool :: //crashes client
+ uint(16) :: //unknown, but usually 0
+ ("name" | PacketHelpers.encodedWideStringAligned( namePadding(pos.init_move) )) ::
+ ("exosuit" | ExoSuitType.codec) ::
+ ignore(2) :: //unknown
+ ("sex" | CharacterGender.codec) ::
+ ("head" | uint8L) ::
+ ("voice" | uint(3)) ::
+ ("voice2" | uint2L) ::
+ ignore(78) :: //unknown
+ uint16L :: //usually either 0 or 65535
+ uint32L :: //for outfit_name (below) to be visible in-game, this value should be non-zero
+ ("outfit_name" | PacketHelpers.encodedWideStringAligned( outfitNamePadding )) ::
+ ("outfit_logo" | uint8L) ::
+ ignore(1) :: //unknown
+ ("backpack" | bool) :: //requires alt_model flag (does NOT require health == 0)
+ bool :: //stream misalignment when set
+ ("facingPitch" | uint8L) ::
+ ("facingYawUpper" | uint8L) ::
+ ignore(1) :: //unknown
+ conditional(alt_model, bool) :: //alt_model flag adds a bit before lfs
+ ignore(1) :: //an alternate lfs?
+ ("lfs" | bool) ::
+ ("grenade_state" | GrenadeState.codec_2u) :: //note: bin10 and bin11 are neutral (bin00 is not defined)
+ ("is_cloaking" | bool) ::
+ ignore(1) :: //unknown
+ bool :: //stream misalignment when set
+ ("charging_pose" | bool) ::
+ ignore(1) :: //alternate charging pose?
+ ("on_zipline" | bool) :: //requires alt_model flag
+ ("ribbons" | RibbonBars.codec)
+ })
+ }).exmap[CharacterAppearanceData] (
+ {
+ case _ :: _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil |
+ _ :: _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: HNil =>
+ Attempt.Failure(Err("invalid character appearance data; can not encode alternate model without required bit set"))
+
+ case pos :: faction :: bops :: _ :: _ :: jamd :: false :: 0 :: name :: suit :: _ :: sex :: head :: v1 :: v2 :: _ :: _ :: _/*has_outfit_name*/ :: outfit :: logo :: _ :: bpack :: false :: facingPitch :: facingYawUpper :: _ :: _ :: _ :: lfs :: gstate :: cloaking :: _ :: false :: charging :: _ :: zipline :: ribbons :: HNil =>
+ Attempt.successful(
+ CharacterAppearanceData(pos, BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons)
+ )
+
+ case _ =>
+ Attempt.Failure(Err("invalid character appearance data; can not encode"))
+ },
+ {
+ case CharacterAppearanceData(_, BasicCharacterData(name, PlanetSideEmpire.NEUTRAL, _, _, _), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
+ Attempt.failure(Err(s"character $name's faction can not declare as neutral"))
+
+ case CharacterAppearanceData(pos, BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons) =>
+ val has_outfit_name : Long = outfit.length.toLong //todo this is a kludge
+ var alt_model : Boolean = false
+ var alt_model_extrabit : Option[Boolean] = None
+ if(zipline || bpack) {
+ alt_model = true
+ alt_model_extrabit = Some(false)
+ }
+ Attempt.successful(
+ pos :: faction :: bops :: alt_model :: () :: jamd :: false :: 0 :: name :: suit :: () :: sex :: head :: v1 :: v2 :: () :: 0 :: has_outfit_name :: outfit :: logo :: () :: bpack :: false :: facingPitch :: facingYawUpper :: () :: alt_model_extrabit :: () :: lfs :: gstate :: cloaking :: () :: false :: charging :: () :: zipline :: ribbons :: HNil
+ )
+
+ case _ =>
+ Attempt.Failure(Err("invalid character appearance data; can not decode"))
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterData.scala
index 3a6284b4..a3aa0009 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterData.scala
@@ -2,402 +2,194 @@
package net.psforever.packet.game.objectcreate
import net.psforever.packet.{Marshallable, PacketHelpers}
-import net.psforever.types.{PlanetSideEmpire, Vector3}
-import scodec.{Attempt, Codec, Err}
import scodec.codecs._
+import scodec.{Attempt, Codec, Err}
import shapeless.{::, HNil}
/**
- * A part of a representation of the avatar portion of `ObjectCreateMessage` packet data.
+ * Values for the implant effects on a character model.
+ * The effects can not be activated simultaneously.
+ * In at least one case, attempting to activate multiple effects will cause the PlanetSide client to crash.
*
- * This partition of the data stream contains information used to represent how the player's avatar is presented.
- * This appearance can be considered the avatar's obvious points beyond experience levels.
- * It does not include passive exo-suit upgrades, battle rank 24 cosmetics, special postures, or current equipment.
- * Those will occur later back in the main data stream.
- *
- * This base length of this stream is __430__ known bits, excluding the length of the name and the padding on that name.
- * Of that, __203__ bits are perfectly unknown in significance.
- *
- * Exo-suit:
- * `0 - Agile`
- * `1 - Refinforced`
- * `2 - Mechanized Assault`
- * `3 - Infiltration`
- * `4 - Standard`
- *
- * Sex:
- * `0 - invalid`
- * `1 - Male`
- * `2 - Female`
- * `3 - invalid`
- *
- * Voice:
- * ` MALE FEMALE`
- * `0 - no voice no voice`
- * `1 - male_1 female_1`
- * `2 - male_2 female_2`
- * `3 - male_3 female_3`
- * `4 - male_4 female_4`
- * `5 - male_5 female_5`
- * `6 - female_1 no voice`
- * `7 - female_2 no voice`
- * @param pos the position of the character in the world environment (in three coordinates)
- * @param objYaw the angle with respect to the horizon towards which the object's front is facing;
- * every `0x1` is 2.813 degrees counter clockwise from North;
- * every `0x10` is 45-degrees;
- * it wraps at `0x0` == `0x80` == North
- * (note: references the avatar as a game object?)
- * @param faction the empire to which the avatar belongs;
- * the value scale is different from `PlanetSideEmpire`
- * @param bops whether or not this avatar is enrolled in Black OPs
- * @param unk1 na;
- * defaults to 4
- * @param name the wide character name of the avatar, minimum of two characters
- * @param exosuit the type of exosuit the avatar will be depicted in;
- * for Black OPs, the agile exo-suit and the reinforced exo-suit are replaced with the Black OPs exo-suits
- * @param sex whether the avatar is male or female
- * @param face1 the avatar's face, as by column number on the character creation screen
- * @param face2 the avatar's face, as by row number on the character creation screen
- * @param voice the avatar's voice selection
- * @param unk2 na
- * @param unk3 na;
- * can be missing from the stream under certain conditions;
- * see next
- * @param unk4 na;
- * can be missing from the stream under certain conditions;
- * see previous
- * @param unk5 na;
- * defaults to `0x8080`
- * @param unk6 na;
- * defaults to `0xFFFF`;
- * may be `0x0`
- * @param unk7 na;
- * defaults to 2
- * @param viewPitch the angle with respect to the sky and the ground towards which the avatar is looking;
- * only supports downwards view angles;
- * `0x0` is forwards-facing;
- * `0x20` to `0xFF` is downwards-facing
- * @param viewYaw the angle with respect to the horizon towards which the avatar is looking;
- * every `0x1` is 2.813 degrees counter clockwise from North;
- * every `0x10` is 45-degrees;
- * it wraps at `0x0` == `0x80` == North
- * @param unk8 na
- * @param ribbons the four merit commendation ribbon medals
+ * `RegenEffects` is a reverse-flagged item - inactive when the corresponding bit is set.
+ * For that reason, every other effect is `n`+1, while `NoEffects` is 1 and `RegenEffects` is 0.
*/
-final case class CharacterAppearanceData(pos : Vector3,
- objYaw : Int,
- faction : PlanetSideEmpire.Value,
- bops : Boolean,
- unk1 : Int,
- name : String,
- exosuit : Int,
- sex : Int,
- face1 : Int,
- face2 : Int,
- voice : Int,
- unk2 : Int,
- unk3 : Int,
- unk4 : Int,
- unk5 : Int,
- unk6 : Int,
- unk7 : Int,
- viewPitch : Int,
- viewYaw : Int,
- unk8 : Int,
- ribbons : RibbonBars) extends StreamBitSize {
- /**
- * Performs a "sizeof()" analysis of the given object.
- * @see ConstructorData.bitsize
- * @return the number of bits necessary to represent this object
- */
- override def bitsize : Long = {
- //TODO ongoing analysis, this value will be subject to change
- 430L + CharacterData.stringBitSize(name, 16) + CharacterAppearanceData.namePadding
- }
-}
+object ImplantEffects extends Enumeration {
+ type Type = Value
-object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
- /**
- * Get the padding of the avatar's name.
- * The padding will always be a number 0-7.
- * @return the pad length in bits
- */
- private def namePadding : Int = {
- //TODO the parameters for this function are not correct
- //TODO the proper padding length should reflect all variability in the substream prior to this point
- 4
- }
+ val SurgeEffects = Value(9)
+ val PersonalShieldEffects = Value(5)
+ val DarklightEffects = Value(3)
+ val RegenEffects = Value(0)
+ val NoEffects = Value(1)
- implicit val codec : Codec[CharacterAppearanceData] = (
- ("pos" | Vector3.codec_pos) ::
- ignore(16) ::
- ("objYaw" | uint8L) ::
- ignore(1) ::
- ("faction" | PlanetSideEmpire.codec) ::
- ("bops" | bool) ::
- ("unk1" | uint4L) ::
- ignore(16) ::
- ("name" | PacketHelpers.encodedWideStringAligned( namePadding )) ::
- ("exosuit" | uintL(3)) ::
- ignore(2) ::
- ("sex" | uint2L) ::
- ("face1" | uint4L) ::
- ("face2" | uint4L) ::
- ("voice" | uintL(3)) ::
- ("unk2" | uint2L) ::
- ignore(4) ::
- ("unk3" | uint8L) ::
- ("unk4" | uint8L) ::
- ("unk5" | uint16L) ::
- ignore(42) ::
- ("unk6" | uint16L) ::
- ignore(30) ::
- ("unk7" | uint4L) ::
- ignore(24) ::
- ("viewPitch" | uint8L) ::
- ("viewYaw" | uint8L) ::
- ("unk8" | uint4L) ::
- ignore(6) ::
- ("ribbons" | RibbonBars.codec)
- ).exmap[CharacterAppearanceData] (
- {
- case a :: _ :: b :: _ :: c :: d :: e :: _ :: f :: g :: _ :: h :: i :: j :: k :: l :: _ :: m :: n :: o :: _ :: p :: _ :: q :: _ :: r :: s :: t :: _ :: u :: HNil =>
- Attempt.successful(
- CharacterAppearanceData(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u)
- )
- },
- {
- case CharacterAppearanceData(_, _, PlanetSideEmpire.NEUTRAL, _, _, name, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
- Attempt.failure(Err(s"character $name's faction can not declare as neutral"))
-
- case CharacterAppearanceData(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u) =>
- Attempt.successful(
- a :: () :: b :: () :: c :: d :: e :: () :: f :: g :: () :: h :: i :: j :: k :: l :: () :: m :: n :: o :: () :: p :: () :: q :: () :: r :: s :: t :: () :: u :: HNil
- )
- }
- )
+ implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L)
}
/**
- * A representation of the avatar portion of `ObjectCreateMessage` packet data.
+ * Values for the four different color designs that impact a player's uniform.
+ * Exo-suits get minor graphical updates at the following battle rank levels: seven, fourteen, and twenty-five.
+ */
+object UniformStyle extends Enumeration {
+ type Type = Value
+
+ val Normal = Value(0)
+ val FirstUpgrade = Value(1)
+ val SecondUpgrade = Value(2)
+ val ThirdUpgrade = Value(4)
+
+ implicit val codec = PacketHelpers.createEnumerationCodec(this, uintL(3))
+}
+
+/**
+ * The different cosmetics that a player can apply to their model's head.
*
- * This object is huge, representing the quantity of densely-encoded data in its packet.
- * Certain bits, when set or unset, introduce or remove other bits from the packet data as well.
- * (As in: flipping a bit may create room or negate other bits from somewhere else in the data stream.
- * Not accounting for this new pattern of bits will break decoding and encoding.)
- * Due to the very real concern that bloating the constructor for this object with parameters could break the `apply` method,
- * parameters will often be composed of nested case objects that contain a group of formal parameters.
- * There are lists of byte-aligned `Strings` later-on in the packet data that will need access to these objects to calculate padding length.
+ * The player gets the ability to apply these minor modifications at battle rank twenty-four, just one rank before the third uniform upgrade.
+ * @param no_helmet removes the current helmet on the reinforced exo-suit and the agile exo-suit;
+ * all other cosmetics require `no_helmet` to be `true` before they can be seen
+ * @param beret player dons a beret
+ * @param sunglasses player dons sunglasses
+ * @param earpiece player dons an earpiece on the left
+ * @param brimmed_cap player dons a cap;
+ * the cap overrides the beret, if both are selected
+ */
+final case class Cosmetics(no_helmet : Boolean,
+ beret : Boolean,
+ sunglasses : Boolean,
+ earpiece : Boolean,
+ brimmed_cap : Boolean)
+
+/**
+ * A part of a representation of the avatar portion of `ObjectCreateMessage` packet data.
+ * This densely-packed information outlines most of the specifics of depicting some other character.
*
- * The first subdivision of parameters concerns the avatar's basic aesthetics, mostly.
- * (No other parts of the data divided up yet.)
- * The final sections include two lists of accredited activity performed/completed by the player.
- * The remainder of the data, following after that, can be read straight, up to and through the inventory.
+ * The character created by this data is treated like an NPC from the perspective of the server.
+ * Someone else decides how that character is behaving and the server tells each client how to depict that behavior.
+ * For that reason, the character is mostly for presentation purposes, rather than really being fleshed-out.
+ * (As far as the client is concerned, nothing stops this character from being declared an "avatar."
+ * A player would find such a client-controlled character lacking many important details and have poor equipment.
+ * They would also be competing with some other player for input control, if they could control the character at all.)
*
- * The base length of the stream is currently __1138__ bits, excluding `List`s and `String`s and inventory.
- * Of that, __831__ bits are perfectly unknown.
- * @param appearance data about the avatar's basic aesthetics
- * @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value;
- * range is 0-65535
- * @param health for `x / y` of hitpoints, this is the avatar's `x` value;
- * range is 0-65535
- * @param armor for `x / y` of armor points, this is the avatar's `x` value;
- * range is 0-65535;
- * the avatar's `y` armor points is tied to their exo-suit type
- * @param unk1 na;
- * defaults to 1
- * @param unk2 na;
- * defaults to 7
- * @param unk3 na;
- * defaults to 7
- * @param staminaMax for `x / y` of stamina points, this is the avatar's `y` value;
- * range is 0-65535
- * @param stamina for `x / y` of stamina points, this is the avatar's `x` value;
- * range is 0-65535
- * @param unk4 na;
- * defaults to 28
- * @param unk5 na;
- * defaults to 4
- * @param unk6 na;
- * defaults to 44
- * @param unk7 na;
- * defaults to 84
- * @param unk8 na;
- * defaults to 104
- * @param unk9 na;
- * defaults to 1900
- * @param firstTimeEvents the list of first time events performed by this avatar;
- * the size field is a 32-bit number;
- * the first entry may be padded
- * @param tutorials the list of tutorials completed by this avatar;
- * the size field is a 32-bit number;
- * the first entry may be padded
- * @param inventory the avatar's inventory
+ * Divisions exist to make the data more manageable.
+ * The first division of data only manages the general appearance of the player's in-game model.
+ * The second division (currently, the fields actually in this class) manages the status of the character.
+ * In general, it passes more simplified data about the character, the minimum that is necessary to explain status to some other player.
+ * For example, health and armor are percentages, and are depicted as bars over the player's head near the nameplate.
+ * The third is the inventory (composed of normal-type objects).
+ * Rather than equipment other players would never interact with, it only comprises the contents of the five holster slots.
+ *
+ * If this player is spawned as dead - with their `health` at 0% - he will start standing and then immediately fall into a lying pose.
+ * The death pose selected is randomized, can not be influenced, and is not be shared across clients.
+ * @param appearance the player's cardinal appearance settings
+ * @param health the amount of health the player has, as a percentage of a filled bar;
+ * the bar has 85 states, with 3 points for each state;
+ * when 0% (less than 3 of 255), the player will collapse into a death pose on the ground
+ * @param armor the amount of armor the player has, as a percentage of a filled bar;
+ * the bar has 85 states, with 3 points for each state
+ * @param uniform_upgrade the level of upgrade to apply to the player's base uniform
+ * @param command_rank the player's command rank as a number from 0 to 5;
+ * cosmetic armor associated with the command rank will be applied automatically
+ * @param implant_effects the effects of implants that can be seen on a player's character;
+ * though many implants can be used simultaneously, only one implant effect can be applied here
+ * @param cosmetics optional decorative features that are added to the player's head model by console/chat commands;
+ * they become available at battle rank 24, but here they require the third uniform upgrade (rank 25);
+ * these flags do not exist if they are not applicable
+ * @param inventory the avatar's inventory;
+ * typically, only the tools and weapons in the equipment holster slots
+ * @param drawn_slot the holster that is initially drawn;
+ * defaults to `DrawnSlot.None`
+ * @see `CharacterAppearanceData`
+ * @see `DetailedCharacterData`
+ * @see `InventoryData`
+ * @see `DrawnSlot`
*/
final case class CharacterData(appearance : CharacterAppearanceData,
- healthMax : Int,
health : Int,
armor : Int,
- unk1 : Int, //1
- unk2 : Int, //7
- unk3 : Int, //7
- staminaMax : Int,
- stamina : Int,
- unk4 : Int, //28
- unk5 : Int, //4
- unk6 : Int, //44
- unk7 : Int, //84
- unk8 : Int, //104
- unk9 : Int, //1900
- firstTimeEvents : List[String],
- tutorials : List[String],
- inventory : InventoryData
- ) extends ConstructorData {
- /**
- * Performs a "sizeof()" analysis of the given object.
- * @see ConstructorData.bitsize
- * @return the number of bits necessary to represent this object
- */
+ uniform_upgrade : UniformStyle.Value,
+ command_rank : Int,
+ implant_effects : Option[ImplantEffects.Value],
+ cosmetics : Option[Cosmetics],
+ inventory : Option[InventoryData],
+ drawn_slot : DrawnSlot.Value = DrawnSlot.None
+ ) extends ConstructorData {
+
override def bitsize : Long = {
- //TODO ongoing analysis, this value will be subject to change
- //fte list
- val fteLen = firstTimeEvents.size
- var eventListSize : Long = 32L + CharacterData.ftePadding(fteLen)
- for(str <- firstTimeEvents) {
- eventListSize += CharacterData.stringBitSize(str)
- }
- //tutorial list
- val tutLen = tutorials.size
- var tutorialListSize : Long = 32L + CharacterData.tutPadding(fteLen, tutLen)
- for(str <- tutorials) {
- tutorialListSize += CharacterData.stringBitSize(str)
- }
- 708L + appearance.bitsize + eventListSize + tutorialListSize + inventory.bitsize
+ //factor guard bool values into the base size, not its corresponding optional field
+ val appearanceSize : Long = appearance.bitsize
+ val effectsSize : Long = if(implant_effects.isDefined) { 4L } else { 0L }
+ val cosmeticsSize : Long = if(cosmetics.isDefined) { 5L } else { 0L }
+ val inventorySize : Long = if(inventory.isDefined) { inventory.get.bitsize } else { 0L }
+ 32L + appearanceSize + effectsSize + cosmeticsSize + inventorySize
}
}
object CharacterData extends Marshallable[CharacterData] {
/**
- * Calculate the size of a string, including the length of the "string length" field that precedes it.
- * Do not pass null-terminated strings.
- * @param str a length-prefixed string
- * @param width the width of the character encoding;
- * defaults to the standard 8-bits
- * @return the size in bits
+ * An overloaded constructor for `CharacterData` that allows for a not-optional inventory.
+ * @param appearance the player's cardinal appearance settings
+ * @param health the amount of health the player has, as a percentage of a filled bar
+ * @param armor the amount of armor the player has, as a percentage of a filled bar
+ * @param uniform the level of upgrade to apply to the player's base uniform
+ * @param cr the player's command rank as a number from 0 to 5
+ * @param implant_effects the effects of implants that can be seen on a player's character
+ * @param cosmetics optional decorative features that are added to the player's head model by console/chat commands
+ * @param inv the avatar's inventory
+ * @param drawn_slot the holster that is initially drawn
+ * @return a `CharacterData` object
*/
- def stringBitSize(str : String, width : Int = 8) : Long = {
- val strlen = str.length
- val lenSize = if(strlen > 127) 16L else 8L
- lenSize + (strlen * width)
- }
+ def apply(appearance : CharacterAppearanceData, health : Int, armor : Int, uniform : UniformStyle.Value, cr : Int, implant_effects : Option[ImplantEffects.Value], cosmetics : Option[Cosmetics], inv : InventoryData, drawn_slot : DrawnSlot.Value) : CharacterData =
+ new CharacterData(appearance, health, armor, uniform, cr, implant_effects, cosmetics, Some(inv), drawn_slot)
/**
- * Get the padding of the first entry in the first time events list.
- * The padding will always be a number 0-7.
- * @param len the length of the list
- * @return the pad length in bits
+ * Check for the bit flags for the cosmetic items.
+ * These flags are only valid if the player has acquired their third uniform upgrade.
+ * @see `UniformStyle.ThirdUpgrade`
*/
- private def ftePadding(len : Long) : Int = {
- //TODO the parameters for this function are not correct
- //TODO the proper padding length should reflect all variability in the stream prior to this point
- if(len > 0) {
- 5
- }
- else
- 0
- }
-
- /**
- * Get the padding of the first entry in the completed tutorials list.
- * The padding will always be a number 0-7.
- *
- * The tutorials list follows the first time event list and that contains byte-aligned strings too.
- * While there will be more to the padding, this other list is important.
- * Any elements in that list causes the automatic byte-alignment of this list's first entry.
- * @param len the length of the list
- * @return the pad length in bits
- */
- private def tutPadding(len : Long, len2 : Long) : Int = {
- if(len > 0) //automatic alignment from previous List
- 0
- else if(len2 > 0) //need to align for elements
- 5
- else //both lists are empty
- 0
- }
+ private val cosmeticsCodec : Codec[Cosmetics] = (
+ ("no_helmet" | bool) ::
+ ("beret" | bool) ::
+ ("sunglasses" | bool) ::
+ ("earpiece" | bool) ::
+ ("brimmed_cap" | bool)
+ ).as[Cosmetics]
implicit val codec : Codec[CharacterData] = (
- ("appearance" | CharacterAppearanceData.codec) ::
- ignore(160) ::
- ("healthMax" | uint16L) ::
- ("health" | uint16L) ::
- ignore(1) ::
- ("armor" | uint16L) ::
- ignore(9) ::
- ("unk1" | uint8L) ::
- ignore(8) ::
- ("unk2" | uint4L) ::
- ("unk3" | uintL(3)) ::
- ("staminaMax" | uint16L) ::
- ("stamina" | uint16L) ::
- ignore(149) ::
- ("unk4" | uint16L) ::
- ("unk5" | uint8L) ::
- ("unk6" | uint8L) ::
- ("unk7" | uint8L) ::
- ("unk8" | uint8L) ::
- ("unk9" | uintL(12)) ::
- ignore(19) ::
- (("firstTimeEvent_length" | uint32L) >>:~ { len =>
- conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned( ftePadding(len) )) ::
- ("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) ::
- (("tutorial_length" | uint32L) >>:~ { len2 =>
- conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned( tutPadding(len, len2) )) ::
- ("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) ::
- ignore(207) ::
- ("inventory" | InventoryData.codec)
- })
+ ("app" | CharacterAppearanceData.codec) ::
+ ("health" | uint8L) :: //dead state when health == 0
+ ("armor" | uint8L) ::
+ (("uniform_upgrade" | UniformStyle.codec) >>:~ { style =>
+ ignore(3) :: //unknown
+ ("command_rank" | uintL(3)) ::
+ bool :: //stream misalignment when != 1
+ optional(bool, "implant_effects" | ImplantEffects.codec) ::
+ conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | cosmeticsCodec) ::
+ optional(bool, "inventory" | InventoryData.codec) ::
+ ("drawn_slot" | DrawnSlot.codec) ::
+ bool //usually false
})
- ).xmap[CharacterData] (
+ ).exmap[CharacterData] (
{
- case app :: _ :: b :: c :: _ :: d :: _ :: e :: _ :: f :: g :: h :: i :: _ :: j :: k :: l :: m :: n :: o :: _ :: p :: q :: r :: s :: t :: u :: _ :: v :: HNil =>
- //prepend the displaced first elements to their lists
- val fteList : List[String] = if(q.isDefined) { q.get :: r } else r
- val tutList : List[String] = if(t.isDefined) { t.get :: u } else u
- CharacterData(app, b, c, d, e, f, g, h, i, j, k, l, m, n, o, fteList, tutList, v)
- },
- {
- case CharacterData(app, b, c, d, e, f, g, h, i, j, k, l, m, n, o, fteList, tutList, p) =>
- //shift the first elements off their lists
- var fteListCopy = fteList
- var firstEvent : Option[String] = None
- if(fteList.nonEmpty) {
- firstEvent = Some(fteList.head)
- fteListCopy = fteList.drop(1)
+ case app :: health :: armor :: uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: inv :: drawn_slot :: false :: HNil =>
+ var newHealth = health
+ if(app.backpack) {
+ newHealth = 0
}
- var tutListCopy = tutList
- var firstTutorial : Option[String] = None
- if(tutList.nonEmpty) {
- firstTutorial = Some(tutList.head)
- tutListCopy = tutList.drop(1)
- }
- app :: () :: b :: c :: () :: d :: () :: e :: () :: f :: g :: h :: i :: () :: j :: k :: l :: m :: n :: o :: () :: fteList.size.toLong :: firstEvent :: fteListCopy :: tutList.size.toLong :: firstTutorial :: tutListCopy :: () :: p :: HNil
- }
- ).as[CharacterData]
+ Attempt.Successful(CharacterData(app, newHealth, armor, uniform, cr, implant_effects, cosmetics, inv, drawn_slot))
- /**
- * Transform between CharacterData and ConstructorData.
- */
- val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
- {
- case x =>
- Attempt.successful(Some(x.asInstanceOf[ConstructorData]))
+ case _ =>
+ Attempt.Failure(Err("invalid character data; can not encode"))
},
{
- case Some(x) =>
- Attempt.successful(x.asInstanceOf[CharacterData])
+ case CharacterData(app, health, armor, uniform, cr, implant_effects, cosmetics, inv, drawn_slot) =>
+ var newHealth = health
+ if(app.backpack) {
+ newHealth = 0
+ }
+ Attempt.Successful(app :: newHealth :: armor :: uniform :: () :: cr :: false :: implant_effects :: cosmetics :: inv :: drawn_slot :: false :: HNil)
+
case _ =>
- Attempt.failure(Err("can not encode character data"))
+ Attempt.Failure(Err("invalid character data; can not decode"))
}
)
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/CommandDetonaterData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/CommandDetonaterData.scala
new file mode 100644
index 00000000..0d5d46a2
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/CommandDetonaterData.scala
@@ -0,0 +1,35 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * A representation of the command uplink device.
+ * I don't know much about the command uplink device so someone else has to provide this commentary.
+ */
+final case class CommandDetonaterData(unk1 : Int = 0,
+ unk2 : Int = 0) extends ConstructorData {
+ override def bitsize : Long = 34L
+}
+
+object CommandDetonaterData extends Marshallable[CommandDetonaterData] {
+ implicit val codec : Codec[CommandDetonaterData] = (
+ ("unk1" | uint4L) ::
+ ("unk2" | uint4L) ::
+ uint(26)
+ ).exmap[CommandDetonaterData] (
+ {
+ case unk1 :: unk2 :: 0 :: HNil =>
+ Attempt.successful(CommandDetonaterData(unk1, unk2))
+ case _ :: _ :: _ :: HNil =>
+ Attempt.failure(Err("invalid command detonator data format"))
+ },
+ {
+ case CommandDetonaterData(unk1, unk2) =>
+ Attempt.successful(unk1 :: unk2 :: 0 :: 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
new file mode 100644
index 00000000..dd85ce26
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonTerminalData.scala
@@ -0,0 +1,36 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * A representation of an object that can be interacted with when using a variety of terminals.
+ * This object is generally invisible.
+ * @param pos where and how the object is oriented
+ */
+final case class CommonTerminalData(pos : PlacementData) extends ConstructorData {
+ override def bitsize : Long = 24L + pos.bitsize
+}
+
+object CommonTerminalData extends Marshallable[CommonTerminalData] {
+ implicit val codec : Codec[CommonTerminalData] = (
+ ("pos" | PlacementData.codec) ::
+ bool ::
+ bool ::
+ uint(22)
+ ).exmap[CommonTerminalData] (
+ {
+ case pos :: false :: true :: 0 :: HNil =>
+ Attempt.successful(CommonTerminalData(pos))
+ case _ :: _ :: _ :: _ :: HNil =>
+ Attempt.failure(Err("invalid terminal data format"))
+ },
+ {
+ case CommonTerminalData(pos) =>
+ Attempt.successful(pos :: false :: true :: 0 :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ConcurrentFeedWeaponData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ConcurrentFeedWeaponData.scala
index d5c8c4af..adfddf2d 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ConcurrentFeedWeaponData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ConcurrentFeedWeaponData.scala
@@ -3,62 +3,63 @@ package net.psforever.packet.game.objectcreate
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.{Marshallable, PacketHelpers}
-import scodec.codecs._
+import scodec.codecs.{uint, _}
import scodec.{Attempt, Codec, Err}
import shapeless.{::, HNil}
/**
- * A representation of a class of weapons that can be created using `ObjectCreateMessage` packet data.
+ * A representation of a class of weapons that can be created using `ObjectCreateDetailedMessage` packet data.
* A "concurrent feed weapon" refers to a weapon system that can chamber multiple types of ammunition simultaneously.
* This data will help construct a "weapon" such as a Punisher.
*
* The data for the weapons nests information for the default (current) type and number of ammunition in its magazine.
* This ammunition data essentially is the weapon's magazines as numbered slots.
- * @param unk na
+ * @param unk1 na
+ * @param unk2 na
+ * @param fire_mode the current mode of weapon's fire;
+ * zero-indexed
* @param ammo `List` data regarding the currently loaded ammunition types and quantities
- * @see WeaponData
- * @see AmmoBoxData
+ * @see `WeaponData`
+ * @see `AmmoBoxData`
*/
-final case class ConcurrentFeedWeaponData(unk : Int,
+final case class ConcurrentFeedWeaponData(unk1 : Int,
+ unk2 : Int,
+ fire_mode : Int,
ammo : List[InternalSlot]) extends ConstructorData {
- /**
- * Performs a "sizeof()" analysis of the given object.
- * @see ConstructorData.bitsize
- * @see InternalSlot.bitsize
- * @see AmmoBoxData.bitsize
- * @return the number of bits necessary to represent this object
- */
override def bitsize : Long = {
var bitsize : Long = 0L
for(o <- ammo) {
bitsize += o.bitsize
}
- 61L + bitsize
+ 44L + bitsize
}
}
object ConcurrentFeedWeaponData extends Marshallable[ConcurrentFeedWeaponData] {
/**
- * An abbreviated constructor for creating `ConcurrentFeedWeaponData` while masking use of `InternalSlot` for its `AmmoBoxData`.
+ * An abbreviated constructor for creating `ConcurrentFeedWeaponData` while masking use of `InternalSlot` for its `DetailedAmmoBoxData`.
*
* Exploration:
* This class may need to be rewritten later to support objects spawned in the world environment.
- * @param unk na
+ * @param unk1 na
+ * @param unk2 na
+ * @param fire_mode data regarding the currently loaded ammunition type
* @param cls the code for the type of object (ammunition) being constructed
* @param guid the globally unique id assigned to the ammunition
* @param parentSlot the slot where the ammunition is to be installed in the weapon
* @param ammo the constructor data for the ammunition
- * @return a WeaponData object
+ * @return a DetailedWeaponData object
*/
- def apply(unk : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : AmmoBoxData) : ConcurrentFeedWeaponData =
- new ConcurrentFeedWeaponData(unk, InternalSlot(cls, guid, parentSlot, ammo) :: Nil)
+ def apply(unk1 : Int, unk2 : Int, fire_mode : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : DetailedAmmoBoxData) : ConcurrentFeedWeaponData =
+ new ConcurrentFeedWeaponData(unk1, unk2, fire_mode, InternalSlot(cls, guid, parentSlot, ammo) :: Nil)
implicit val codec : Codec[ConcurrentFeedWeaponData] = (
- ("unk" | uint4L) ::
- uint4L ::
- uint24 ::
- uint16 ::
- uint2L ::
+ ("unk1" | uint4L) ::
+ ("unk2" | uint4L) ::
+ uint(20) ::
+ ("fire_mode" | int(3)) ::
+ bool ::
+ bool ::
(uint8L >>:~ { size =>
uint2L ::
("ammo" | PacketHelpers.listOfNSized(size, InternalSlot.codec)) ::
@@ -66,41 +67,25 @@ object ConcurrentFeedWeaponData extends Marshallable[ConcurrentFeedWeaponData] {
})
).exmap[ConcurrentFeedWeaponData] (
{
- case code :: 8 :: 2 :: 0 :: 3 :: size :: 0 :: ammo :: false :: HNil =>
+ case unk1 :: unk2 :: 0 :: fmode :: false :: true :: size :: 0 :: ammo :: false :: HNil =>
if(size != ammo.size)
Attempt.failure(Err("weapon encodes wrong number of ammunition"))
else if(size == 0)
Attempt.failure(Err("weapon needs to encode at least one type of ammunition"))
else
- Attempt.successful(ConcurrentFeedWeaponData(code, ammo))
- case code :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil =>
+ Attempt.successful(ConcurrentFeedWeaponData(unk1, unk2, fmode, ammo))
+ case _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil =>
Attempt.failure(Err("invalid weapon data format"))
},
{
- case ConcurrentFeedWeaponData(code, ammo) =>
+ case ConcurrentFeedWeaponData(unk1, unk2, fmode, ammo) =>
val size = ammo.size
if(size == 0)
Attempt.failure(Err("weapon needs to encode at least one type of ammunition"))
else if(size >= 255)
Attempt.failure(Err("weapon has too much ammunition (255+ types!)"))
else
- Attempt.successful(code :: 8 :: 2 :: 0 :: 3 :: size :: 0 :: ammo :: false :: HNil)
- }
- ).as[ConcurrentFeedWeaponData]
-
- /**
- * Transform between WeaponData and ConstructorData.
- */
- val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
- {
- case x =>
- Attempt.successful(Some(x.asInstanceOf[ConstructorData]))
- },
- {
- case Some(x) =>
- Attempt.successful(x.asInstanceOf[ConcurrentFeedWeaponData])
- case _ =>
- Attempt.failure(Err("can not encode weapon data"))
+ Attempt.successful(unk1 :: unk2 :: 0 :: fmode :: false :: true :: size :: 0 :: ammo :: false :: HNil)
}
)
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ConstructorData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ConstructorData.scala
index 94ceca53..6b91eb74 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ConstructorData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ConstructorData.scala
@@ -1,6 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
+import scodec.{Attempt, Codec, Err}
+
/**
* The base type for the representation of any data used to produce objects from `ObjectCreateMessage` packet data.
* There is no reason to instantiate this class as-is.
@@ -18,4 +20,26 @@ object ConstructorData {
* The casting will be performed through use of `exmap` in the child class.
*/
type genericPattern = Option[ConstructorData]
+
+ /**
+ * Transform a `Codec[T]` for object type `T` into `ConstructorData.genericPattern`.
+ * @param objCodec a `Codec` that satisfies the transformation `Codec[T] -> T`
+ * @param objType a `String` that explains what the object should be identified as in the `Err` message;
+ * defaults to "object"
+ * @tparam T a subclass of `ConstructorData` that indicates what type the object is
+ * @return `ConstructorData.genericPattern`
+ */
+ def genericCodec[T <: ConstructorData](objCodec : Codec[T], objType : String = "object") : Codec[ConstructorData.genericPattern] =
+ objCodec.exmap[ConstructorData.genericPattern] (
+ {
+ case x =>
+ Attempt.successful(Some(x.asInstanceOf[ConstructorData]))
+ },
+ {
+ case Some(x) =>
+ Attempt.successful(x.asInstanceOf[T]) //why does this work? shouldn't type erasure be a problem?
+ case _ =>
+ Attempt.failure(Err(s"can not encode as $objType data"))
+ }
+ )
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedACEData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedACEData.scala
new file mode 100644
index 00000000..0c1ad166
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedACEData.scala
@@ -0,0 +1,39 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * A representation of an adaptive construction engine (ACE).
+ * This one-time-use item deploys a variety of utilities into the game environment.
+ * Has an advanced version internally called an `advanced_ace` and commonly called a Field Deployment Unit (FDU).
+ * @param unk na
+ */
+final case class DetailedACEData(unk : Int) extends ConstructorData {
+ override def bitsize : Long = 51L
+}
+
+object DetailedACEData extends Marshallable[DetailedACEData] {
+ implicit val codec : Codec[DetailedACEData] = (
+ ("unk" | uint4L) ::
+ uint4L ::
+ uintL(20) ::
+ uint4L ::
+ uint16L ::
+ uint(3)
+ ).exmap[DetailedACEData] (
+ {
+ case code :: 8 :: 0 :: 2 :: 0 :: 4 :: HNil =>
+ Attempt.successful(DetailedACEData(code))
+ case _ =>
+ Attempt.failure(Err("invalid ace data format"))
+ },
+ {
+ case DetailedACEData(code) =>
+ Attempt.successful(code :: 8 :: 0 :: 2 :: 0 :: 4 :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedAmmoBoxData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedAmmoBoxData.scala
new file mode 100644
index 00000000..b3bdf95f
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedAmmoBoxData.scala
@@ -0,0 +1,58 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import net.psforever.packet.game.PlanetSideGUID
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * A representation of the ammunition portion of `ObjectCreateDetailedMessage` packet data.
+ * This data will help construct a "box" of that type of ammunition when standalone.
+ * It can also be constructed directly inside a weapon as its magazine.
+ *
+ * The maximum amount of ammunition that can be stored in a single box is 65535 units.
+ * Regardless of the interface, however, the number will never be fully visible.
+ * Only the first three digits or the first four digits may be represented.
+ * @param unk na
+ * @param magazine the number of rounds available
+ * @see DetailedWeaponData
+ */
+final case class DetailedAmmoBoxData(unk : Int,
+ magazine : Int
+ ) extends ConstructorData {
+ override def bitsize : Long = 40L
+}
+
+object DetailedAmmoBoxData extends Marshallable[DetailedAmmoBoxData] {
+ /**
+ * An abbreviated constructor for creating `DetailedWeaponData` while masking use of `InternalSlot`.
+ * @param cls the code for the type of object being constructed
+ * @param guid the GUID this object will be assigned
+ * @param parentSlot a parent-defined slot identifier that explains where the child is to be attached to the parent
+ * @param ammo the `DetailedAmmoBoxData`
+ * @return an `InternalSlot` object that encapsulates `DetailedAmmoBoxData`
+ */
+ def apply(cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : DetailedAmmoBoxData) : InternalSlot =
+ new InternalSlot(cls, guid, parentSlot, ammo)
+
+ implicit val codec : Codec[DetailedAmmoBoxData] = (
+ uint4L ::
+ ("unk" | uint4L) ::
+ uint(15) ::
+ ("magazine" | uint16L) ::
+ bool
+ ).exmap[DetailedAmmoBoxData] (
+ {
+ case 0xC :: unk :: 0 :: mag :: false :: HNil =>
+ Attempt.successful(DetailedAmmoBoxData(unk, mag))
+ case _ =>
+ Attempt.failure(Err("invalid ammunition data format"))
+ },
+ {
+ case DetailedAmmoBoxData(unk, mag) =>
+ Attempt.successful(0xC :: unk :: 0 :: mag :: false:: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedBoomerTriggerData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedBoomerTriggerData.scala
new file mode 100644
index 00000000..3e4aebf7
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedBoomerTriggerData.scala
@@ -0,0 +1,36 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * A representation of the detonater utility that is created when putting down a Boomer with an ACE.
+ */
+final case class DetailedBoomerTriggerData() extends ConstructorData {
+ override def bitsize : Long = 51L
+}
+
+object DetailedBoomerTriggerData extends Marshallable[DetailedBoomerTriggerData] {
+ implicit val codec : Codec[DetailedBoomerTriggerData] = (
+ uint8L ::
+ uint(22) ::
+ bool :: //true
+ uint(17) ::
+ bool :: //true
+ uint2L
+ ).exmap[DetailedBoomerTriggerData] (
+ {
+ case 0xC8 :: 0 :: true :: 0 :: true :: 0 :: HNil =>
+ Attempt.successful(DetailedBoomerTriggerData())
+ case _ :: _ :: _ :: _ :: _ :: _ :: HNil =>
+ Attempt.failure(Err("invalid command detonater format"))
+ },
+ {
+ case DetailedBoomerTriggerData() =>
+ Attempt.successful(0xC8 :: 0 :: true :: 0 :: true :: 0 :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala
new file mode 100644
index 00000000..34e6a6db
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala
@@ -0,0 +1,252 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.{Marshallable, PacketHelpers}
+import scodec.{Attempt, Codec}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * A representation of the avatar portion of `ObjectCreateDetailedMessage` packet data.
+ * This densely-packed information outlines most of the specifics required to depict a character as an avatar.
+ *
+ * As an avatar, the character created by this data is expected to be controllable by the client that gets sent this data.
+ * It goes into depth about information related to the given character in-game career that is not revealed to other players.
+ *
+ * Divisions exist to make the data more manageable.
+ * The first division of data only manages the general appearance of the player's in-game model.
+ * The second division (currently, the fields actually in this class) manages the status of the character as an avatar.
+ * In general, it passes more thorough data about the character that the client can display to the owner of the client.
+ * For example, health is a full number, rather than a percentage.
+ * Just as prominent is the list of first time events and the list of completed tutorials.
+ * The third subdivision is also exclusive to avatar-prepared characters and contains (omitted).
+ * The fourth is the inventory (composed of `Direct`-type objects).
+ *
+ * Exploration:
+ * Lots of analysis needed for the remainder of the byte data.
+ * @param appearance data about the avatar's basic aesthetics
+ * @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value;
+ * range is 0-65535
+ * @param health for `x / y` of hitpoints, this is the avatar's `x` value;
+ * range is 0-65535
+ * @param armor for `x / y` of armor points, this is the avatar's `x` value;
+ * range is 0-65535;
+ * the avatar's `y` armor points is tied to their exo-suit type
+ * @param unk1 na;
+ * defaults to 1
+ * @param unk2 na;
+ * defaults to 7
+ * @param unk3 na;
+ * defaults to 7
+ * @param staminaMax for `x / y` of stamina points, this is the avatar's `y` value;
+ * range is 0-65535
+ * @param stamina for `x / y` of stamina points, this is the avatar's `x` value;
+ * range is 0-65535
+ * @param unk4 na;
+ * defaults to 28
+ * @param unk5 na;
+ * defaults to 4
+ * @param unk6 na;
+ * defaults to 44
+ * @param unk7 na;
+ * defaults to 84
+ * @param unk8 na;
+ * defaults to 104
+ * @param unk9 na;
+ * defaults to 1900
+ * @param firstTimeEvents the list of first time events performed by this avatar;
+ * the size field is a 32-bit number;
+ * the first entry may be padded
+ * @param tutorials the list of tutorials completed by this avatar;
+ * the size field is a 32-bit number;
+ * the first entry may be padded
+ * @param inventory the avatar's inventory
+ * @param drawn_slot the holster that is initially drawn
+ * @see `CharacterAppearanceData`
+ * @see `CharacterData`
+ * @see `InventoryData`
+ * @see `DrawnSlot`
+ */
+final case class DetailedCharacterData(appearance : CharacterAppearanceData,
+ healthMax : Int,
+ health : Int,
+ armor : Int,
+ unk1 : Int, //1
+ unk2 : Int, //7
+ unk3 : Int, //7
+ staminaMax : Int,
+ stamina : Int,
+ unk4 : Int, //28
+ unk5 : Int, //4
+ unk6 : Int, //44
+ unk7 : Int, //84
+ unk8 : Int, //104
+ unk9 : Int, //1900
+ firstTimeEvents : List[String],
+ tutorials : List[String],
+ inventory : Option[InventoryData],
+ drawn_slot : DrawnSlot.Value = DrawnSlot.None
+ ) extends ConstructorData {
+
+ override def bitsize : Long = {
+ //factor guard bool values into the base size, not its corresponding optional field
+ val appearanceSize = appearance.bitsize
+ val fteLen = firstTimeEvents.size //fte list
+ var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen)
+ for(str <- firstTimeEvents) {
+ eventListSize += StreamBitSize.stringBitSize(str)
+ }
+ val tutLen = tutorials.size //tutorial list
+ var tutorialListSize : Long = 32L + DetailedCharacterData.tutPadding(fteLen, tutLen)
+ for(str <- tutorials) {
+ tutorialListSize += StreamBitSize.stringBitSize(str)
+ }
+ var inventorySize : Long = 0L //inventory
+ if(inventory.isDefined) {
+ inventorySize = inventory.get.bitsize
+ }
+ 713L + appearanceSize + eventListSize + tutorialListSize + inventorySize
+ }
+}
+
+object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
+ /**
+ * Overloaded constructor for `DetailedCharacterData` that skips all the unknowns by assigning defaulted values.
+ * It also allows for a not-optional inventory.
+ * @param appearance data about the avatar's basic aesthetics
+ * @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value
+ * @param health for `x / y` of hitpoints, this is the avatar's `x` value
+ * @param armor for `x / y` of armor points, this is the avatar's `x` value
+ * @param staminaMax for `x / y` of stamina points, this is the avatar's `y` value
+ * @param stamina for `x / y` of stamina points, this is the avatar's `x` value
+ * @param firstTimeEvents the list of first time events performed by this avatar
+ * @param tutorials the list of tutorials completed by this avatar
+ * @param inventory the avatar's inventory
+ * @param drawn_slot the holster that is initially drawn
+ * @return a `DetailedCharacterData` object
+ */
+ def apply(appearance : CharacterAppearanceData, healthMax : Int, health : Int, armor : Int, staminaMax : Int, stamina : Int, firstTimeEvents : List[String], tutorials : List[String], inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedCharacterData =
+ new DetailedCharacterData(appearance, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, 28, 4, 44, 84, 104, 1900, firstTimeEvents, tutorials, Some(inventory), drawn_slot)
+
+ /**
+ * Overloaded constructor for `DetailedCharacterData` that allows for a not-optional inventory.
+ * @param appearance data about the avatar's basic aesthetics
+ * @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value
+ * @param health for `x / y` of hitpoints, this is the avatar's `x` value
+ * @param armor for `x / y` of armor points, this is the avatar's `x` value
+ * @param unk1 na
+ * @param unk2 na
+ * @param unk3 na
+ * @param staminaMax for `x / y` of stamina points, this is the avatar's `y` value
+ * @param stamina for `x / y` of stamina points, this is the avatar's `x` value
+ * @param unk4 na
+ * @param unk5 na
+ * @param unk6 na
+ * @param unk7 na
+ * @param unk8 na
+ * @param unk9 na
+ * @param firstTimeEvents the list of first time events performed by this avatar
+ * @param tutorials the list of tutorials completed by this avatar
+ * @param inventory the avatar's inventory
+ * @param drawn_slot the holster that is initially drawn
+ * @return a `DetailedCharacterData` object
+ */
+ def apply(appearance : CharacterAppearanceData, healthMax : Int, health : Int, armor : Int, unk1 : Int, unk2 : Int, unk3 : Int, staminaMax : Int, stamina : Int, unk4 : Int, unk5 : Int, unk6 : Int, unk7 : Int, unk8 : Int, unk9 : Int, firstTimeEvents : List[String], tutorials : List[String], inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedCharacterData =
+ new DetailedCharacterData(appearance, healthMax, health, armor, unk1, unk2, unk3, staminaMax, stamina, unk4, unk5, unk6, unk7, unk8, unk9, firstTimeEvents, tutorials, Some(inventory), drawn_slot)
+
+ /**
+ * Get the padding of the first entry in the first time events list.
+ * The padding will always be a number 0-7.
+ * @param len the length of the list
+ * @return the pad length in bits
+ */
+ private def ftePadding(len : Long) : Int = {
+ //TODO the parameters for this function are not correct
+ //TODO the proper padding length should reflect all variability in the stream prior to this point
+ if(len > 0) {
+ 5
+ }
+ else
+ 0
+ }
+
+ /**
+ * Get the padding of the first entry in the completed tutorials list.
+ * The padding will always be a number 0-7.
+ *
+ * The tutorials list follows the first time event list and that contains byte-aligned strings too.
+ * While there will be more to the padding, this other list is important.
+ * Any elements in that list causes the automatic byte-alignment of this list's first entry.
+ * @param len the length of the list
+ * @return the pad length in bits
+ */
+ private def tutPadding(len : Long, len2 : Long) : Int = {
+ if(len > 0) //automatic alignment from previous List
+ 0
+ else if(len2 > 0) //need to align for elements
+ 5
+ else //both lists are empty
+ 0
+ }
+
+ implicit val codec : Codec[DetailedCharacterData] = (
+ ("appearance" | CharacterAppearanceData.codec) ::
+ ignore(160) ::
+ ("healthMax" | uint16L) ::
+ ("health" | uint16L) ::
+ ignore(1) ::
+ ("armor" | uint16L) ::
+ ignore(9) ::
+ ("unk1" | uint8L) ::
+ ignore(8) ::
+ ("unk2" | uint4L) ::
+ ("unk3" | uintL(3)) ::
+ ("staminaMax" | uint16L) ::
+ ("stamina" | uint16L) ::
+ ignore(149) ::
+ ("unk4" | uint16L) ::
+ ("unk5" | uint8L) ::
+ ("unk6" | uint8L) ::
+ ("unk7" | uint8L) ::
+ ("unk8" | uint8L) ::
+ ("unk9" | uintL(12)) ::
+ ignore(19) ::
+ (("firstTimeEvent_length" | uint32L) >>:~ { len =>
+ conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned( ftePadding(len) )) ::
+ ("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) ::
+ (("tutorial_length" | uint32L) >>:~ { len2 =>
+ conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned( tutPadding(len, len2) )) ::
+ ("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) ::
+ ignore(207) ::
+ optional(bool, "inventory" | InventoryData.codec_detailed) ::
+ ("drawn_slot" | DrawnSlot.codec) ::
+ bool //usually false
+ })
+ })
+ ).exmap[DetailedCharacterData] (
+ {
+ case app :: _ :: b :: c :: _ :: d :: _ :: e :: _ :: f :: g :: h :: i :: _ :: j :: k :: l :: m :: n :: o :: _ :: _ :: q :: r :: _ :: t :: u :: _ :: v :: w :: false :: HNil =>
+ //prepend the displaced first elements to their lists
+ val fteList : List[String] = if(q.isDefined) { q.get :: r } else r
+ val tutList : List[String] = if(t.isDefined) { t.get :: u } else u
+ Attempt.successful(DetailedCharacterData(app, b, c, d, e, f, g, h, i, j, k, l, m, n, o, fteList, tutList, v, w))
+ },
+ {
+ case DetailedCharacterData(app, b, c, d, e, f, g, h, i, j, k, l, m, n, o, fteList, tutList, p, q) =>
+ //shift the first elements off their lists
+ var fteListCopy = fteList
+ var firstEvent : Option[String] = None
+ if(fteList.nonEmpty) {
+ firstEvent = Some(fteList.head)
+ fteListCopy = fteList.drop(1)
+ }
+ var tutListCopy = tutList
+ var firstTutorial : Option[String] = None
+ if(tutList.nonEmpty) {
+ firstTutorial = Some(tutList.head)
+ tutListCopy = tutList.drop(1)
+ }
+ Attempt.successful(app :: () :: b :: c :: () :: d :: () :: e :: () :: f :: g :: h :: i :: () :: j :: k :: l :: m :: n :: o :: () :: fteList.size.toLong :: firstEvent :: fteListCopy :: tutList.size.toLong :: firstTutorial :: tutListCopy :: () :: p :: q :: false :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCommandDetonaterData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCommandDetonaterData.scala
new file mode 100644
index 00000000..cd5c684b
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCommandDetonaterData.scala
@@ -0,0 +1,38 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * A representation of the command uplink device.
+ * I don't know much about the command uplink device so someone else has to provide this commentary.
+ */
+final case class DetailedCommandDetonaterData(unk1 : Int = 8,
+ unk2 : Int = 0) extends ConstructorData {
+ override def bitsize : Long = 51L
+}
+
+object DetailedCommandDetonaterData extends Marshallable[DetailedCommandDetonaterData] {
+ implicit val codec : Codec[DetailedCommandDetonaterData] = (
+ ("unk1" | uint4L) ::
+ ("unk2" | uint4L) ::
+ uint(20) ::
+ uint4L ::
+ uint16 ::
+ uint(3)
+ ).exmap[DetailedCommandDetonaterData] (
+ {
+ case unk1 :: unk2 :: 0 :: 2 :: 0 :: 4 :: HNil =>
+ Attempt.successful(DetailedCommandDetonaterData(unk1, unk2))
+ case _ =>
+ Attempt.failure(Err("invalid command detonator data format"))
+ },
+ {
+ case DetailedCommandDetonaterData(unk1, unk2) =>
+ Attempt.successful(unk1 :: unk2 :: 0 :: 2 :: 0 :: 4 :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedConcurrentFeedWeaponData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedConcurrentFeedWeaponData.scala
new file mode 100644
index 00000000..b96151cb
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedConcurrentFeedWeaponData.scala
@@ -0,0 +1,86 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.game.PlanetSideGUID
+import net.psforever.packet.{Marshallable, PacketHelpers}
+import scodec.codecs._
+import scodec.{Attempt, Codec, Err}
+import shapeless.{::, HNil}
+
+/**
+ * A representation of a class of weapons that can be created using `ObjectCreateDetailedMessage` packet data.
+ * A "concurrent feed weapon" refers to a weapon system that can chamber multiple types of ammunition simultaneously.
+ * This data will help construct a "weapon" such as a Punisher.
+ *
+ * The data for the weapons nests information for the default (current) type of ammunition in its magazine.
+ * This ammunition data essentially is the weapon's magazines as numbered slots.
+ * @param unk1 na
+ * @param unk2 na
+ * @param ammo `List` data regarding the currently loaded ammunition types and quantities
+ * @see DetailedWeaponData
+ * @see DetailedAmmoBoxData
+ */
+final case class DetailedConcurrentFeedWeaponData(unk1 : Int,
+ unk2 : Int,
+ ammo : List[InternalSlot]) extends ConstructorData {
+ override def bitsize : Long = {
+ var bitsize : Long = 0L
+ for(o <- ammo) {
+ bitsize += o.bitsize
+ }
+ 61L + bitsize
+ }
+}
+
+object DetailedConcurrentFeedWeaponData extends Marshallable[DetailedConcurrentFeedWeaponData] {
+ /**
+ * An abbreviated constructor for creating `DetailedConcurrentFeedWeaponData` while masking use of `InternalSlot` for its `DetailedAmmoBoxData`.
+ *
+ * Exploration:
+ * This class may need to be rewritten later to support objects spawned in the world environment.
+ * @param unk1 na
+ * @param unk2 na
+ * @param cls the code for the type of object (ammunition) being constructed
+ * @param guid the globally unique id assigned to the ammunition
+ * @param parentSlot the slot where the ammunition is to be installed in the weapon
+ * @param ammo the constructor data for the ammunition
+ * @return a DetailedWeaponData object
+ */
+ def apply(unk1 : Int, unk2 : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : DetailedAmmoBoxData) : DetailedConcurrentFeedWeaponData =
+ new DetailedConcurrentFeedWeaponData(unk1, unk2, InternalSlot(cls, guid, parentSlot, ammo) :: Nil)
+
+ implicit val codec : Codec[DetailedConcurrentFeedWeaponData] = (
+ ("unk" | uint4L) ::
+ uint4L ::
+ uint24 ::
+ uint16 ::
+ uint2L ::
+ (uint8L >>:~ { size =>
+ uint2L ::
+ ("ammo" | PacketHelpers.listOfNSized(size, InternalSlot.codec_detailed)) ::
+ bool
+ })
+ ).exmap[DetailedConcurrentFeedWeaponData] (
+ {
+ case unk1 :: unk2 :: 2 :: 0 :: 3 :: size :: 0 :: ammo :: false :: HNil =>
+ if(size != ammo.size)
+ Attempt.failure(Err("weapon encodes wrong number of ammunition"))
+ else if(size == 0)
+ Attempt.failure(Err("weapon needs to encode at least one type of ammunition"))
+ else
+ Attempt.successful(DetailedConcurrentFeedWeaponData(unk1, unk2, ammo))
+ case _ =>
+ Attempt.failure(Err("invalid weapon data format"))
+ },
+ {
+ case DetailedConcurrentFeedWeaponData(unk1, unk2, ammo) =>
+ val size = ammo.size
+ if(size == 0)
+ Attempt.failure(Err("weapon needs to encode at least one type of ammunition"))
+ else if(size >= 255)
+ Attempt.failure(Err("weapon has too much ammunition (255+ types!)"))
+ else
+ Attempt.successful(unk1 :: unk2 :: 2 :: 0 :: 3 :: size :: 0 :: ammo :: false :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedREKData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedREKData.scala
new file mode 100644
index 00000000..de0912c7
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedREKData.scala
@@ -0,0 +1,41 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * A representation of the REK portion of `ObjectCreateDetailedMessage` packet data.
+ * This data will help construct the "tool" called a Remote Electronics Kit.
+ *
+ * Of note is the first portion of the data which resembles the `DetailedWeaponData` format.
+ * @param unk na
+ */
+final case class DetailedREKData(unk : Int) extends ConstructorData {
+ override def bitsize : Long = 67L
+}
+
+object DetailedREKData extends Marshallable[DetailedREKData] {
+ implicit val codec : Codec[DetailedREKData] = (
+ ("unk" | uint4L) ::
+ uint4L ::
+ uintL(20) ::
+ uint4L ::
+ uint16L ::
+ uint4L ::
+ uintL(15)
+ ).exmap[DetailedREKData] (
+ {
+ case code :: 8 :: 0 :: 2 :: 0 :: 8 :: 0 :: HNil =>
+ Attempt.successful(DetailedREKData(code))
+ case _ =>
+ Attempt.failure(Err("invalid rek data format"))
+ },
+ {
+ case DetailedREKData(code) =>
+ Attempt.successful(code :: 8 :: 0 :: 2 :: 0 :: 8 :: 0 :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedWeaponData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedWeaponData.scala
new file mode 100644
index 00000000..a02baeda
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedWeaponData.scala
@@ -0,0 +1,64 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import net.psforever.packet.game.PlanetSideGUID
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * A representation of a class of weapons that can be created using `ObjectCreateDetailedMessage` packet data.
+ * This data will help construct a "loaded weapon" such as a Suppressor or a Gauss.
+ *
+ * The data for the weapons nests information for the default (current) type and number of ammunition in its magazine.
+ * This ammunition data essentially is the weapon's magazines as numbered slots.
+ * This format only handles one type of ammunition at a time.
+ * Any weapon that has two types of ammunition simultaneously loaded must be handled with another `Codec`.
+ * This functionality is unrelated to a weapon that switches ammunition type;
+ * a weapon with that behavior is handled perfectly fine using this `case class`.
+ * @param unk na
+ * @param ammo data regarding the currently loaded ammunition type and quantity
+ * @see DetailedAmmoBoxData
+ */
+final case class DetailedWeaponData(unk : Int,
+ ammo : InternalSlot) extends ConstructorData {
+ override def bitsize : Long = 61L + ammo.bitsize
+}
+
+object DetailedWeaponData extends Marshallable[DetailedWeaponData] {
+ /**
+ * An abbreviated constructor for creating `DetailedWeaponData` while masking use of `InternalSlot` for its `DetailedAmmoBoxData`.
+ * @param unk na
+ * @param cls the code for the type of object (ammunition) being constructed
+ * @param guid the globally unique id assigned to the ammunition
+ * @param parentSlot the slot where the ammunition is to be installed in the weapon
+ * @param ammo the constructor data for the ammunition
+ * @return a DetailedWeaponData object
+ */
+ def apply(unk : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : DetailedAmmoBoxData) : DetailedWeaponData =
+ new DetailedWeaponData(unk, InternalSlot(cls, guid, parentSlot, ammo))
+
+ implicit val codec : Codec[DetailedWeaponData] = (
+ ("unk" | uint4L) ::
+ uint4L ::
+ uint24 ::
+ uint16L ::
+ uint2 ::
+ uint8 :: //size = 1 type of ammunition loaded
+ uint2 ::
+ ("ammo" | InternalSlot.codec_detailed) ::
+ bool
+ ).exmap[DetailedWeaponData] (
+ {
+ case code :: 8 :: 2 :: 0 :: 3 :: 1 :: 0 :: ammo :: false :: HNil =>
+ Attempt.successful(DetailedWeaponData(code, ammo))
+ case _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil =>
+ Attempt.failure(Err("invalid weapon data format"))
+ },
+ {
+ case DetailedWeaponData(code, ammo) =>
+ Attempt.successful(code :: 8 :: 2 :: 0 :: 3 :: 1 :: 0 :: ammo :: false :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DrawnSlot.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DrawnSlot.scala
new file mode 100644
index 00000000..9ea17ed1
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DrawnSlot.scala
@@ -0,0 +1,25 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+/**
+ * Values for the equipment holster slot whose contained ("held") equipment can be drawn.
+ * The values for these Enums match the slot number by index for Infantry weapons.
+ *
+ * `None` is not a kludge.
+ * While any "not a holster" number can be used to indicate "no weapon drawn," seven is the value PlanetSide is looking for.
+ * Using five or six delays the first weapon draw while the client corrects its internal state.
+ */
+object DrawnSlot extends Enumeration {
+ type Type = Value
+
+ val Pistol1 = Value(0)
+ val Pistol2 = Value(1)
+ val Rifle1 = Value(2)
+ val Rifle2 = Value(3)
+ val Melee = Value(4)
+ val None = Value(7)
+
+ import net.psforever.packet.PacketHelpers
+ import scodec.codecs._
+ implicit val codec = PacketHelpers.createEnumerationCodec(this, uint(3))
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DroppedItemData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DroppedItemData.scala
new file mode 100644
index 00000000..e65cab16
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DroppedItemData.scala
@@ -0,0 +1,62 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * Provide information that positions a given object on the ground in the game world.
+ * @param pos where and how the object is oriented
+ * @param obj the object on the ground
+ * @tparam T a subclass of `ConstructorData` that indicates what type the object is
+ */
+final case class DroppedItemData[T <: ConstructorData](pos : PlacementData, obj : T) extends ConstructorData {
+ override def bitsize : Long = pos.bitsize + obj.bitsize
+}
+
+object DroppedItemData {
+ /**
+ * Transform `DroppedItemData[T]` for object type `T` into `ConstructorData.genericPattern`.
+ *
+ * This function eliminates the need to have a separate "DroppedFooData" class for every object "Foo."
+ * Two functions normally perform this transformation: an `implicit` `codec` used in a `genericCodec`.
+ * Since actual Generics are utilized, combining the processes eliminates defining to the type data multiple times.
+ * (If that is even possible here.)
+ * Knowledge of the object type is still necessary to recover the original object's data through casting.
+ * Not having to explicitly cast would have been the main upside of having specialized "DroppedFooData" classes.
+ *
+ * Use:
+ * `DroppedItemCodec.genericCodec(T.codec)`
+ * @param objCodec a `Codec` that satisfies the transformation `Codec[T] -> T`
+ * @param objType a `String` that explains what the object should be identified as in the log;
+ * defaults to "object"
+ * @tparam T a subclass of `ConstructorData` that indicates what type the object is
+ * @return `ConstructorData.genericPattern`
+ * @see `ConstructorData.genericPattern` (function)
+ */
+ def genericCodec[T <: ConstructorData](objCodec : Codec[T], objType : String = "object") : Codec[ConstructorData.genericPattern] = (
+ ("pos" | PlacementData.codec) ::
+ ("obj" | objCodec)
+ ).xmap[DroppedItemData[T]] (
+ {
+ case pos :: obj :: HNil =>
+ DroppedItemData[T](pos, obj)
+ },
+ {
+ case DroppedItemData(pos, obj) =>
+ pos :: obj :: HNil
+ }
+ ).exmap[ConstructorData.genericPattern] (
+ {
+ case x =>
+ Attempt.successful(Some(x.asInstanceOf[ConstructorData]))
+ },
+ {
+ case Some(x) =>
+ Attempt.successful(x.asInstanceOf[DroppedItemData[T]])
+ case _ =>
+ Attempt.failure(Err(s"can not encode dropped $objType data"))
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ImplantInterfaceData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ImplantInterfaceData.scala
new file mode 100644
index 00000000..411be6f6
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ImplantInterfaceData.scala
@@ -0,0 +1,33 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * A representation of an object that can be interacted with when using an implant terminal.
+ * This object is generally invisible.
+ */
+final case class ImplantInterfaceData() extends ConstructorData {
+ override def bitsize : Long = 24L
+}
+
+object ImplantInterfaceData extends Marshallable[ImplantInterfaceData] {
+ implicit val codec : Codec[ImplantInterfaceData] = (
+ bool ::
+ uint(23)
+ ).exmap[ImplantInterfaceData] (
+ {
+ case true :: 0 :: HNil =>
+ Attempt.successful(ImplantInterfaceData())
+ case _ :: _ :: HNil =>
+ Attempt.failure(Err("invalid interface data format"))
+ },
+ {
+ case ImplantInterfaceData() =>
+ Attempt.successful(true :: 0 :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/InternalSlot.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/InternalSlot.scala
index d4efb1e0..4505e23c 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/InternalSlot.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/InternalSlot.scala
@@ -1,46 +1,47 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
-import net.psforever.packet.{Marshallable, PacketHelpers}
+import net.psforever.packet.PacketHelpers
import net.psforever.packet.game.PlanetSideGUID
import scodec.Codec
import scodec.codecs._
import shapeless.{::, HNil}
/**
- * An intermediate class for the primary fields of `ObjectCreateMessage` with an implicit parent-child relationship.
+ * An intermediate class for the primary fields of `ObjectCreate*Message` with an implicit parent-child relationship.
*
* Any object that is contained in a "slot" of another object will use `InternalSlot` to hold the anchoring data.
- * This prior object will clarify the identity of the "parent" object that owns the given `parentSlot`.
+ * This prior object will clarify the identity of the "parent" object that owns the given `parentSlot`.
+ * As the name implies, this should never have to be used in the representation of a non-child object.
*
- * Try to avoid exposing `InternalSlot` in the process of implementing code.
+ * Try to avoid exposing `InternalSlot` in the process of implementing object code.
+ * (Provide overrode constructors where applicable.)
* @param objectClass the code for the type of object being constructed
* @param guid the GUID this object will be assigned
* @param parentSlot a parent-defined slot identifier that explains where the child is to be attached to the parent
* @param obj the data used as representation of the object to be constructed
- * @see ObjectClass.selectDataCodec
+ * @see `ObjectClass.selectDataCodec`
+ * @see `ObjectClass.selectDataDetailedCodec`
*/
final case class InternalSlot(objectClass : Int,
guid : PlanetSideGUID,
parentSlot : Int,
obj : ConstructorData) extends StreamBitSize {
- /**
- * Performs a "sizeof()" analysis of the given object.
- * @see ConstructorData.bitsize
- * @return the number of bits necessary to represent this object
- */
override def bitsize : Long = {
val base : Long = if(parentSlot > 127) 43L else 35L
base + obj.bitsize
}
}
-object InternalSlot extends Marshallable[InternalSlot] {
- implicit val codec : Codec[InternalSlot] = (
+object InternalSlot {
+ /**
+ * Used for `0x18` `ObjectCreateDetailedMessage` packets
+ */
+ val codec_detailed : Codec[InternalSlot] = (
("objectClass" | uintL(11)) >>:~ { obj_cls =>
("guid" | PlanetSideGUID.codec) ::
("parentSlot" | PacketHelpers.encodedStringSize) ::
- ("obj" | ObjectClass.selectDataCodec(obj_cls)) //it's fine for this call to fail
+ ("obj" | ObjectClass.selectDataDetailedCodec(obj_cls)) //it's fine for this call to fail
}
).xmap[InternalSlot] (
{
@@ -51,5 +52,25 @@ object InternalSlot extends Marshallable[InternalSlot] {
case InternalSlot(cls, guid, slot, obj) =>
cls :: guid :: slot :: Some(obj) :: HNil
}
- ).as[InternalSlot]
+ )
+
+ /**
+ * Used for `0x17` `ObjectCreateMessage` packets
+ */
+ val codec : Codec[InternalSlot] = (
+ ("objectClass" | uintL(11)) >>:~ { obj_cls =>
+ ("guid" | PlanetSideGUID.codec) ::
+ ("parentSlot" | PacketHelpers.encodedStringSize) ::
+ ("obj" | ObjectClass.selectDataCodec(obj_cls)) //it's fine for this call to fail
+ }
+ ).xmap[InternalSlot] (
+ {
+ case cls :: guid :: slot :: Some(obj) :: HNil =>
+ InternalSlot(cls, guid, slot, obj)
+ },
+ {
+ case InternalSlot(cls, guid, slot, obj) =>
+ cls :: guid :: slot :: Some(obj) :: HNil
+ }
+ )
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryData.scala
index cbc75235..9add3cde 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryData.scala
@@ -1,52 +1,33 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
-import net.psforever.packet.{Marshallable, PacketHelpers}
+import net.psforever.packet.PacketHelpers
import scodec.Codec
import scodec.codecs._
import shapeless.{::, HNil}
/**
- * A representation of the inventory portion of `ObjectCreateMessage` packet data for avatars.
+ * A representation of the inventory portion of `ObjectCreate*Message` packet data for avatars.
*
* The inventory is a temperamental thing.
* Items placed into the inventory must follow their proper encoding schematics to the letter.
+ * The slot number refers to the position occupied by the item.
+ * In icon format, all-encompassing slots are absolute positions; and, grid-distributed icons use the upper-left corner.
* No values are allowed to be misplaced and no unexpected regions of data can be discovered.
- * If there is even a minor failure, the whole of the inventory will fail to translate.
+ * If there is even a minor failure, the remainder of the inventory will fail to translate.
*
- * Under the official servers, when a new character was generated, the inventory encoded as `0x1C`.
- * This inventory had no size field, no contents, and an indeterminate number of values.
- * This format is no longer supported.
- * Going forward, an empty inventory - approximately `0x10000` - should be used as substitute.
- *
- * Exploration:
- * 4u of ignored bits have been added to the end of the inventory to make up for missing stream length.
- * They do not actually seem to be part of the inventory.
- * Are these bits always at the end of the packet data and what is the significance?
- * @param unk1 na;
- * `true` to mark the start of the inventory data?
- * is explicitly declaring the bit necessary when it always seems to be `true`?
+ * Inventories are usually prefaced with a `bin1` value not accounted for here.
+ * It can be treated as optional.
+ * @param contents the items in the inventory
+ * @param unk1 na
* @param unk2 na
- * @param unk3 na
- * @param contents the actual items in the inventory;
- * holster slots are 0-4;
- * an inaccessible slot is 5;
- * internal capacity is 6-`n`, where `n` is defined by exosuit type and is mapped into a grid
*/
-final case class InventoryData(unk1 : Boolean,
- unk2 : Boolean,
- unk3 : Boolean,
- contents : List[InventoryItem]) extends StreamBitSize {
- /**
- * Performs a "sizeof()" analysis of the given object.
- * @see ConstructorData.bitsize
- * @return the number of bits necessary to represent this object
- */
+final case class InventoryData(contents : List[InventoryItem] = List.empty,
+ unk1 : Boolean = false,
+ unk2 : Boolean = false) extends StreamBitSize {
override def bitsize : Long = {
- //three booleans, the 4u extra, and the 8u length field
- val base : Long = 15L
- //length of all items in inventory
- var invSize : Long = 0L
+ val base : Long = 10L //8u + 1u + 1u
+ var invSize : Long = 0L //length of all items in inventory
for(item <- contents) {
invSize += item.bitsize
}
@@ -54,23 +35,49 @@ final case class InventoryData(unk1 : Boolean,
}
}
-object InventoryData extends Marshallable[InventoryData] {
- implicit val codec : Codec[InventoryData] = (
- ("unk1" | bool) ::
- (("len" | uint8L) >>:~ { len =>
+object InventoryData {
+ private def inventoryCodec(itemCodec : Codec[InventoryItem]) : Codec[InventoryData] = (
+ uint8L >>:~ { len =>
+ ("unk1" | bool) ::
("unk2" | bool) ::
- ("unk3" | bool) ::
- ("contents" | PacketHelpers.listOfNSized(len, InventoryItem.codec)) ::
- ignore(4)
- })
- ).xmap[InventoryData] (
+ ("contents" | PacketHelpers.listOfNSized(len, itemCodec))
+ }
+ ).xmap[InventoryData] (
{
- case u1 :: _ :: a :: b :: ctnt :: _ :: HNil =>
- InventoryData(u1, a, b, ctnt)
+ case _ :: a :: b :: c :: HNil =>
+ InventoryData(c, a, b)
},
{
- case InventoryData(u1, a, b, ctnt) =>
- u1 :: ctnt.size :: a :: b :: ctnt :: () :: HNil
+ case InventoryData(c, a, b) =>
+ c.size :: a :: b :: c :: HNil
}
- ).as[InventoryData]
+ )
+
+ /**
+ * A `Codec` for `0x17` `ObjectCreateMessage` data.
+ */
+ val codec : Codec[InventoryData] = inventoryCodec(InventoryItem.codec).hlist.xmap[InventoryData] (
+ {
+ case inventory :: HNil =>
+ inventory
+ },
+ {
+ case InventoryData(a, b, c) =>
+ InventoryData(a, b, c) :: HNil
+ }
+ )
+
+ /**
+ * A `Codec` for `0x18` `ObjectCreateDetailedMessage` data.
+ */
+ val codec_detailed : Codec[InventoryData] = inventoryCodec(InventoryItem.codec_detailed).hlist.xmap[InventoryData] (
+ {
+ case inventory :: HNil =>
+ inventory
+ },
+ {
+ case InventoryData(a, b, c) =>
+ InventoryData(a, b, c) :: HNil
+ }
+ )
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryItem.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryItem.scala
index f9ce41b0..083c78b1 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryItem.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryItem.scala
@@ -1,7 +1,6 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
-import net.psforever.packet.Marshallable
import net.psforever.packet.game.PlanetSideGUID
import scodec.Codec
import scodec.codecs._
@@ -16,16 +15,12 @@ import scodec.codecs._
* @param item the object in inventory
* @see InternalSlot
*/
-final case class InventoryItem(item : InternalSlot) extends StreamBitSize {
- /**
- * Performs a "sizeof()" analysis of the given object.
- * @see ConstructorData.bitsize
- * @return the number of bits necessary to represent this object
- */
+final case class InventoryItem(item : InternalSlot
+ ) extends StreamBitSize {
override def bitsize : Long = item.bitsize
}
-object InventoryItem extends Marshallable[InventoryItem] {
+object InventoryItem {
/**
* An abbreviated constructor for creating an `InventoryItem` without interacting with `InternalSlot` directly.
* @param objClass the code for the type of object (ammunition) being constructed
@@ -37,7 +32,13 @@ object InventoryItem extends Marshallable[InventoryItem] {
def apply(objClass : Int, guid : PlanetSideGUID, parentSlot : Int, obj : ConstructorData) : InventoryItem =
InventoryItem(InternalSlot(objClass, guid, parentSlot, obj))
- implicit val codec : Codec[InventoryItem] = (
- "item" | InternalSlot.codec
- ).as[InventoryItem]
+ /**
+ * A `Codec` for `0x17` `ObjectCreateMessage` data.
+ */
+ val codec : Codec[InventoryItem] = ("item" | InternalSlot.codec).as[InventoryItem]
+
+ /**
+ * A `Codec` for `0x18` `ObjectCreateDetailedMessage` data.
+ */
+ val codec_detailed : Codec[InventoryItem] = ("item" | InternalSlot.codec_detailed).as[InventoryItem]
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/LockerContainerData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/LockerContainerData.scala
new file mode 100644
index 00000000..abcc2150
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/LockerContainerData.scala
@@ -0,0 +1,38 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * A representation for a game object that can contain items.
+ *
+ * For whatever reason, these "lockers" are typically placed at the origin coordinates.
+ * @param inventory the items inside his locker
+ */
+final case class LockerContainerData(inventory : InventoryData) extends ConstructorData {
+ override def bitsize : Long = 105L + inventory.bitsize //81u + 2u + 21u + 1u
+}
+
+object LockerContainerData extends Marshallable[LockerContainerData] {
+ implicit val codec : Codec[LockerContainerData] = (
+ uint32 :: uint32 :: uint(17) :: //can substitute with PlacementData, if ever necessary
+ uint2L ::
+ uint(21) ::
+ bool ::
+ InventoryData.codec
+ ).exmap[LockerContainerData] (
+ {
+ case 0 :: 0 :: 0 :: 3 :: 0 :: true :: inv :: HNil =>
+ Attempt.successful(LockerContainerData(inv))
+ case _ =>
+ Attempt.failure(Err("invalid locker container format"))
+ },
+ {
+ case LockerContainerData(inv) =>
+ Attempt.successful(0L :: 0L :: 0 :: 3 :: 0 :: true :: inv :: HNil)
+ }
+ )
+}
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 333704b4..a72d949f 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
@@ -9,9 +9,9 @@ import scala.annotation.switch
/**
* A reference between all object class codes and the name of the object they represent.
*
- * Object classes compose a number between 0 and (probably) 2047, always translating into an 11-bit value.
- * They are recorded as little-endian hexadecimal values here.
- * In `scodec` terms, that's a `uintL(11)` or `uintL(0xB)`.
+ * Object classes compose a number between 0 and 2047, always translating into an 11-bit value.
+ * In `scodec` terms, that's a `uintL(11)` or a `uintL(0xB)`.
+ * The items that can be constructed with packets `ObjectCreateMessage` and `ObjectCreateDetailedMessage` number fewer than 1047.
*/
object ObjectClass {
//character
@@ -111,9 +111,7 @@ object ObjectClass {
final val cannon_dropship_l_20mm = 15
final val cannon_75mm = 23
final val lightning_75mm = 24
- final val ace = 32
final val ace_deployable = 33
- final val advanced_ace = 39
final val advanced_missile_launcher_t = 40
final val anniversary_gun = 55
final val anniversary_guna = 56
@@ -204,6 +202,9 @@ object ObjectClass {
final val mediumtransport_weapon_systemA = 534
final val mediumtransport_weapon_systemB = 535
final val mini_chaingun = 556
+ final val nchev_falcon = 587
+ final val nchev_scattercannon = 588
+ final val nchev_sparrow = 589
final val oicw = 599
final val particle_beam_magrider = 628
final val pellet_gun = 629
@@ -235,6 +236,7 @@ object ObjectClass {
final val rocklet = 737
final val rotarychaingun_mosquito = 740
final val router_telepad = 743
+ final val router_telepad_deployable = 744
final val scythe = 747
final val six_shooter = 761
final val skyguard_weapon_system = 788
@@ -246,8 +248,14 @@ object ObjectClass {
final val thumper = 864
final val thunderer_weapon_systema = 866
final val thunderer_weapon_systemb = 867
+ final val trhev_burster = 888
+ final val trhev_dualcycler = 889
+ final val trhev_pounder = 890
final val vanguard_weapon_system = 927
final val vanu_sentry_turret_weapon = 945
+ final val vshev_comet = 968
+ final val vshev_quasar = 969
+ final val vshev_starfire = 970
final val vulture_bomb_bay = 987
final val vulture_nose_weapon_system = 990
final val vulture_tail_cannon = 992
@@ -259,11 +267,12 @@ object ObjectClass {
final val jammer_grenade = 416
final val mine_sweeper = 552
final val plasma_grenade = 680
- //tools - medkits
+ //medkits
final val medkit = 536
final val super_armorkit = 842
final val super_medkit = 843
final val super_staminakit = 844
+ //tools
final val remote_electronics_kit = 728
final val trek = 876
final val applicator = 110
@@ -272,297 +281,867 @@ object ObjectClass {
final val nano_dispenser = 577
final val command_detonater = 213
final val laze_pointer = 297
- //unknown
- final val locker_container = 456 //strange item found in inventory slot #5, between holsters and grid
+ //ace deployables
+ final val ace = 32
+ final val advanced_ace = 39
+ final val boomer = 148
+ final val boomer_trigger = 149
+ final val he_mine = 388
+ final val jammer_mine = 420
+ final val motionalarmsensor = 575
+ final val sensor_shield = 752
+ final val spitfire_aa = 819
+ final val spitfire_cloaked = 825
+ final val spitfire_turret = 826
+ final val tank_traps = 849
+ final val deployable_shield_generator = 240
+ final val portable_manned_turret = 685
+ final val portable_manned_turret_nc = 686
+ final val portable_manned_turret_tr = 687
+ final val portable_manned_turret_vs = 688
+ //projectiles
+ final val hunter_seeker_missile_projectile = 405
+ final val oicw_projectile = 602
+ final val starfire_projectile = 831
+ final val striker_missile_targeting_projectile = 841
+ //other
+ final val locker_container = 456
+ final val implant_terminal_interface = 409
+ final val matrix_terminala = 517
+ final val matrix_terminalb = 518
+ final val matrix_terminalc = 519
+ final val order_terminal = 612
+ final val order_terminala = 613
+ final val order_terminalb = 614
+ final val ams_respawn_tube = 49
+ final val capture_flag = 157
-
-
- //TODO refactor this function into another object later
+ //TODO refactor the following functions into another object later
/**
- * Given an object class, retrieve the `Codec` used to parse and translate the constructor data for that type.
+ * Given an object class, retrieve the `Codec` used to parse and translate the constructor data for that type.
+ * This function services `0x18` `ObjectCreateDetailedMessage` packet data.
*
- * This function serves as a giant `switch` statement that loosely connects object data to object class.
- * All entries, save the default, merely point to the `Codec` of pattern `ConstructorData.genericPattern`.
- * This pattern connects all `Codec`s back to the superclass `ConstructorData`.
- * The default case is a failure case for trying to either decode or encode an unknown class of object.
+ * All `Codec`s accessible by this function assume the object is perfectly visible from the perspective of the avatar.
* @param objClass the code for the type of object being constructed
* @return the `Codec` that handles the format of data for that particular item class, or a failing `Codec`
+ * @see `ConstructorData`
*/
- def selectDataCodec(objClass : Int) : Codec[ConstructorData.genericPattern] = {
+ def selectDataDetailedCodec(objClass : Int) : Codec[ConstructorData.genericPattern] =
(objClass : @switch) match {
//ammunition
- case ObjectClass.bullet_105mm => AmmoBoxData.genericCodec
- case ObjectClass.bullet_12mm => AmmoBoxData.genericCodec
- case ObjectClass.bullet_150mm => AmmoBoxData.genericCodec
- case ObjectClass.bullet_15mm => AmmoBoxData.genericCodec
- case ObjectClass.bullet_20mm => AmmoBoxData.genericCodec
- case ObjectClass.bullet_25mm => AmmoBoxData.genericCodec
- case ObjectClass.bullet_35mm => AmmoBoxData.genericCodec
- case ObjectClass.bullet_75mm => AmmoBoxData.genericCodec
- case ObjectClass.bullet_9mm => AmmoBoxData.genericCodec
- case ObjectClass.bullet_9mm_AP => AmmoBoxData.genericCodec
- case ObjectClass.ancient_ammo_combo => AmmoBoxData.genericCodec
- case ObjectClass.ancient_ammo_vehicle => AmmoBoxData.genericCodec
- case ObjectClass.anniversary_ammo => AmmoBoxData.genericCodec
- case ObjectClass.aphelion_immolation_cannon_ammo => AmmoBoxData.genericCodec
- case ObjectClass.aphelion_laser_ammo => AmmoBoxData.genericCodec
- case ObjectClass.aphelion_plasma_rocket_ammo => AmmoBoxData.genericCodec
- case ObjectClass.aphelion_ppa_ammo => AmmoBoxData.genericCodec
- case ObjectClass.aphelion_starfire_ammo => AmmoBoxData.genericCodec
- case ObjectClass.armor_canister => AmmoBoxData.genericCodec
- case ObjectClass.armor_siphon_ammo => AmmoBoxData.genericCodec
- case ObjectClass.bolt => AmmoBoxData.genericCodec
- case ObjectClass.burster_ammo => AmmoBoxData.genericCodec
- case ObjectClass.colossus_100mm_cannon_ammo => AmmoBoxData.genericCodec
- case ObjectClass.colossus_burster_ammo => AmmoBoxData.genericCodec
- case ObjectClass.colossus_chaingun_ammo => AmmoBoxData.genericCodec
- case ObjectClass.colossus_cluster_bomb_ammo => AmmoBoxData.genericCodec
- case ObjectClass.colossus_tank_cannon_ammo => AmmoBoxData.genericCodec
- case ObjectClass.comet_ammo => AmmoBoxData.genericCodec
- case ObjectClass.dualcycler_ammo => AmmoBoxData.genericCodec
- case ObjectClass.energy_cell => AmmoBoxData.genericCodec
- case ObjectClass.energy_gun_ammo => AmmoBoxData.genericCodec
- case ObjectClass.falcon_ammo => AmmoBoxData.genericCodec
- case ObjectClass.firebird_missile => AmmoBoxData.genericCodec
- case ObjectClass.flamethrower_ammo => AmmoBoxData.genericCodec
- case ObjectClass.flux_cannon_thresher_battery => AmmoBoxData.genericCodec
- case ObjectClass.fluxpod_ammo => AmmoBoxData.genericCodec
- case ObjectClass.frag_cartridge => AmmoBoxData.genericCodec
- case ObjectClass.frag_grenade_ammo => AmmoBoxData.genericCodec
- case ObjectClass.gauss_cannon_ammo => AmmoBoxData.genericCodec
- case ObjectClass.grenade => AmmoBoxData.genericCodec
- case ObjectClass.health_canister => AmmoBoxData.genericCodec
- case ObjectClass.heavy_grenade_mortar => AmmoBoxData.genericCodec
- case ObjectClass.heavy_rail_beam_battery => AmmoBoxData.genericCodec
- case ObjectClass.hellfire_ammo => AmmoBoxData.genericCodec
- case ObjectClass.hunter_seeker_missile => AmmoBoxData.genericCodec
- case ObjectClass.jammer_cartridge => AmmoBoxData.genericCodec
- case ObjectClass.jammer_grenade_ammo => AmmoBoxData.genericCodec
- case ObjectClass.lancer_cartridge => AmmoBoxData.genericCodec
- case ObjectClass.liberator_bomb => AmmoBoxData.genericCodec
- case ObjectClass.maelstrom_ammo => AmmoBoxData.genericCodec
- case ObjectClass.melee_ammo => AmmoBoxData.genericCodec
- case ObjectClass.mine => AmmoBoxData.genericCodec
- case ObjectClass.mine_sweeper_ammo => AmmoBoxData.genericCodec
- case ObjectClass.ntu_siphon_ammo => AmmoBoxData.genericCodec
- case ObjectClass.oicw_ammo => AmmoBoxData.genericCodec
- case ObjectClass.pellet_gun_ammo => AmmoBoxData.genericCodec
- case ObjectClass.peregrine_dual_machine_gun_ammo => AmmoBoxData.genericCodec
- case ObjectClass.peregrine_mechhammer_ammo => AmmoBoxData.genericCodec
- case ObjectClass.peregrine_particle_cannon_ammo => AmmoBoxData.genericCodec
- case ObjectClass.peregrine_rocket_pod_ammo => AmmoBoxData.genericCodec
- case ObjectClass.peregrine_sparrow_ammo => AmmoBoxData.genericCodec
- case ObjectClass.phalanx_ammo => AmmoBoxData.genericCodec
- case ObjectClass.phoenix_missile => AmmoBoxData.genericCodec
- case ObjectClass.plasma_cartridge => AmmoBoxData.genericCodec
- case ObjectClass.plasma_grenade_ammo => AmmoBoxData.genericCodec
- case ObjectClass.pounder_ammo => AmmoBoxData.genericCodec
- case ObjectClass.pulse_battery => AmmoBoxData.genericCodec
- case ObjectClass.quasar_ammo => AmmoBoxData.genericCodec
- case ObjectClass.reaver_rocket => AmmoBoxData.genericCodec
- case ObjectClass.rocket => AmmoBoxData.genericCodec
- case ObjectClass.scattercannon_ammo => AmmoBoxData.genericCodec
- case ObjectClass.shotgun_shell => AmmoBoxData.genericCodec
- case ObjectClass.shotgun_shell_AP => AmmoBoxData.genericCodec
- case ObjectClass.six_shooter_ammo => AmmoBoxData.genericCodec
- case ObjectClass.skyguard_flak_cannon_ammo => AmmoBoxData.genericCodec
- case ObjectClass.sparrow_ammo => AmmoBoxData.genericCodec
- case ObjectClass.spitfire_aa_ammo => AmmoBoxData.genericCodec
- case ObjectClass.spitfire_ammo => AmmoBoxData.genericCodec
- case ObjectClass.starfire_ammo => AmmoBoxData.genericCodec
- case ObjectClass.striker_missile_ammo => AmmoBoxData.genericCodec
- case ObjectClass.trek_ammo => AmmoBoxData.genericCodec
- case ObjectClass.upgrade_canister => AmmoBoxData.genericCodec
- case ObjectClass.wasp_gun_ammo => AmmoBoxData.genericCodec
- case ObjectClass.wasp_rocket_ammo => AmmoBoxData.genericCodec
- case ObjectClass.winchester_ammo => AmmoBoxData.genericCodec
- //weapons (have a look on punisher)
- case ObjectClass.beamer => WeaponData.genericCodec
- case ObjectClass.chaingun_12mm => WeaponData.genericCodec
- case ObjectClass.chaingun_15mm => WeaponData.genericCodec
- case ObjectClass.cannon_20mm => WeaponData.genericCodec
- case ObjectClass.cannon_deliverer_20mm => WeaponData.genericCodec
- case ObjectClass.cannon_dropship_20mm => WeaponData.genericCodec
- case ObjectClass.cannon_dropship_l_20mm => WeaponData.genericCodec
- case ObjectClass.cannon_75mm => WeaponData.genericCodec
- case ObjectClass.lightning_75mm => WeaponData.genericCodec
- case ObjectClass.ace => WeaponData.genericCodec
- case ObjectClass.ace_deployable => WeaponData.genericCodec
- case ObjectClass.advanced_ace => WeaponData.genericCodec
- case ObjectClass.advanced_missile_launcher_t => WeaponData.genericCodec
- case ObjectClass.anniversary_gun => WeaponData.genericCodec
- case ObjectClass.anniversary_guna => WeaponData.genericCodec
- case ObjectClass.anniversary_gunb => WeaponData.genericCodec
- case ObjectClass.apc_ballgun_l => WeaponData.genericCodec
- case ObjectClass.apc_ballgun_r => WeaponData.genericCodec
- case ObjectClass.apc_weapon_systema => WeaponData.genericCodec
- case ObjectClass.apc_weapon_systemb => WeaponData.genericCodec
- case ObjectClass.apc_weapon_systemc => WeaponData.genericCodec
- case ObjectClass.apc_weapon_systemc_nc => WeaponData.genericCodec
- case ObjectClass.apc_weapon_systemc_tr => WeaponData.genericCodec
- case ObjectClass.apc_weapon_systemc_vs => WeaponData.genericCodec
- case ObjectClass.apc_weapon_systemd => WeaponData.genericCodec
- case ObjectClass.apc_weapon_systemd_nc => WeaponData.genericCodec
- case ObjectClass.apc_weapon_systemd_tr => WeaponData.genericCodec
- case ObjectClass.apc_weapon_systemd_vs => WeaponData.genericCodec
- case ObjectClass.aphelion_immolation_cannon => WeaponData.genericCodec
- case ObjectClass.aphelion_laser => WeaponData.genericCodec
- case ObjectClass.aphelion_laser_left => WeaponData.genericCodec
- case ObjectClass.aphelion_laser_right => WeaponData.genericCodec
- case ObjectClass.aphelion_plasma_rocket_pod => WeaponData.genericCodec
- case ObjectClass.aphelion_ppa => WeaponData.genericCodec
- case ObjectClass.aphelion_ppa_left => WeaponData.genericCodec
- case ObjectClass.aphelion_ppa_right => WeaponData.genericCodec
- case ObjectClass.aphelion_starfire => WeaponData.genericCodec
- case ObjectClass.aphelion_starfire_left => WeaponData.genericCodec
- case ObjectClass.aphelion_starfire_right => WeaponData.genericCodec
- case ObjectClass.aurora_weapon_systema => WeaponData.genericCodec
- case ObjectClass.aurora_weapon_systemb => WeaponData.genericCodec
- case ObjectClass.battlewagon_weapon_systema => WeaponData.genericCodec
- case ObjectClass.battlewagon_weapon_systemb => WeaponData.genericCodec
- case ObjectClass.battlewagon_weapon_systemc => WeaponData.genericCodec
- case ObjectClass.battlewagon_weapon_systemd => WeaponData.genericCodec
- case ObjectClass.bolt_driver => WeaponData.genericCodec
- case ObjectClass.chainblade => WeaponData.genericCodec
- case ObjectClass.chaingun_p => WeaponData.genericCodec
- case ObjectClass.colossus_burster => WeaponData.genericCodec
- case ObjectClass.colossus_burster_left => WeaponData.genericCodec
- case ObjectClass.colossus_burster_right => WeaponData.genericCodec
- case ObjectClass.colossus_chaingun => WeaponData.genericCodec
- case ObjectClass.colossus_chaingun_left => WeaponData.genericCodec
- case ObjectClass.colossus_chaingun_right => WeaponData.genericCodec
- case ObjectClass.colossus_cluster_bomb_pod => WeaponData.genericCodec
- case ObjectClass.colossus_dual_100mm_cannons => WeaponData.genericCodec
- case ObjectClass.colossus_tank_cannon => WeaponData.genericCodec
- case ObjectClass.colossus_tank_cannon_left => WeaponData.genericCodec
- case ObjectClass.colossus_tank_cannon_right => WeaponData.genericCodec
- case ObjectClass.cycler => WeaponData.genericCodec
- case ObjectClass.cycler_v2 => WeaponData.genericCodec
- case ObjectClass.cycler_v3 => WeaponData.genericCodec
- case ObjectClass.cycler_v4 => WeaponData.genericCodec
- case ObjectClass.dropship_rear_turret => WeaponData.genericCodec
- case ObjectClass.energy_gun => WeaponData.genericCodec
- case ObjectClass.energy_gun_nc => WeaponData.genericCodec
- case ObjectClass.energy_gun_tr => WeaponData.genericCodec
- case ObjectClass.energy_gun_vs => WeaponData.genericCodec
- case ObjectClass.flail_weapon => WeaponData.genericCodec
- case ObjectClass.flamethrower => WeaponData.genericCodec
- case ObjectClass.flechette => WeaponData.genericCodec
- case ObjectClass.flux_cannon_thresher => WeaponData.genericCodec
- case ObjectClass.fluxpod => WeaponData.genericCodec
- case ObjectClass.forceblade => WeaponData.genericCodec
- case ObjectClass.fragmentation_grenade => WeaponData.genericCodec
- case ObjectClass.fury_weapon_systema => WeaponData.genericCodec
- case ObjectClass.galaxy_gunship_cannon => WeaponData.genericCodec
- case ObjectClass.galaxy_gunship_gun => WeaponData.genericCodec
- case ObjectClass.galaxy_gunship_tailgun => WeaponData.genericCodec
- case ObjectClass.gauss => WeaponData.genericCodec
- case ObjectClass.gauss_cannon => WeaponData.genericCodec
- case ObjectClass.grenade_launcher_marauder => WeaponData.genericCodec
- case ObjectClass.heavy_rail_beam_magrider => WeaponData.genericCodec
- case ObjectClass.heavy_sniper => WeaponData.genericCodec
- case ObjectClass.hellfire => WeaponData.genericCodec
- case ObjectClass.hunterseeker => WeaponData.genericCodec
- case ObjectClass.ilc9 => WeaponData.genericCodec
- case ObjectClass.isp => WeaponData.genericCodec
- case ObjectClass.katana => WeaponData.genericCodec
- case ObjectClass.lancer => WeaponData.genericCodec
- case ObjectClass.lasher => WeaponData.genericCodec
- case ObjectClass.liberator_25mm_cannon => WeaponData.genericCodec
- case ObjectClass.liberator_bomb_bay => WeaponData.genericCodec
- case ObjectClass.liberator_weapon_system => WeaponData.genericCodec
- case ObjectClass.lightgunship_weapon_system => WeaponData.genericCodec
- case ObjectClass.lightning_weapon_system => WeaponData.genericCodec
- case ObjectClass.maelstrom => WeaponData.genericCodec
- case ObjectClass.magcutter => WeaponData.genericCodec
- case ObjectClass.mediumtransport_weapon_systemA => WeaponData.genericCodec
- case ObjectClass.mediumtransport_weapon_systemB => WeaponData.genericCodec
- case ObjectClass.mini_chaingun => WeaponData.genericCodec
- case ObjectClass.oicw => WeaponData.genericCodec
- case ObjectClass.particle_beam_magrider => WeaponData.genericCodec
- case ObjectClass.pellet_gun => WeaponData.genericCodec
- case ObjectClass.peregrine_dual_machine_gun => WeaponData.genericCodec
- case ObjectClass.peregrine_dual_machine_gun_left => WeaponData.genericCodec
- case ObjectClass.peregrine_dual_machine_gun_right => WeaponData.genericCodec
- case ObjectClass.peregrine_dual_rocket_pods => WeaponData.genericCodec
- case ObjectClass.peregrine_mechhammer => WeaponData.genericCodec
- case ObjectClass.peregrine_mechhammer_left => WeaponData.genericCodec
- case ObjectClass.peregrine_mechhammer_right => WeaponData.genericCodec
- case ObjectClass.peregrine_particle_cannon => WeaponData.genericCodec
- case ObjectClass.peregrine_sparrow => WeaponData.genericCodec
- case ObjectClass.peregrine_sparrow_left => WeaponData.genericCodec
- case ObjectClass.peregrine_sparrow_right => WeaponData.genericCodec
- case ObjectClass.phalanx_avcombo => WeaponData.genericCodec
- case ObjectClass.phalanx_flakcombo => WeaponData.genericCodec
- case ObjectClass.phalanx_sgl_hevgatcan => WeaponData.genericCodec
- case ObjectClass.phantasm_12mm_machinegun => WeaponData.genericCodec
- case ObjectClass.phoenix => WeaponData.genericCodec
- case ObjectClass.prowler_weapon_systemA => WeaponData.genericCodec
- case ObjectClass.prowler_weapon_systemB => WeaponData.genericCodec
- case ObjectClass.pulsar => WeaponData.genericCodec
- case ObjectClass.pulsed_particle_accelerator => WeaponData.genericCodec
- case ObjectClass.punisher => ConcurrentFeedWeaponData.genericCodec
- case ObjectClass.quadassault_weapon_system => WeaponData.genericCodec
- case ObjectClass.r_shotgun => WeaponData.genericCodec
- case ObjectClass.radiator => WeaponData.genericCodec
- case ObjectClass.repeater => WeaponData.genericCodec
- case ObjectClass.rocklet => WeaponData.genericCodec
- case ObjectClass.rotarychaingun_mosquito => WeaponData.genericCodec
- case ObjectClass.router_telepad => WeaponData.genericCodec
- case ObjectClass.scythe => WeaponData.genericCodec
- case ObjectClass.six_shooter => WeaponData.genericCodec
- case ObjectClass.skyguard_weapon_system => WeaponData.genericCodec
- case ObjectClass.spiker => WeaponData.genericCodec
- case ObjectClass.spitfire_aa_weapon => WeaponData.genericCodec
- case ObjectClass.spitfire_weapon => WeaponData.genericCodec
- case ObjectClass.striker => WeaponData.genericCodec
- case ObjectClass.suppressor => WeaponData.genericCodec
- case ObjectClass.thumper => WeaponData.genericCodec
- case ObjectClass.thunderer_weapon_systema => WeaponData.genericCodec
- case ObjectClass.thunderer_weapon_systemb => WeaponData.genericCodec
- case ObjectClass.vanguard_weapon_system => WeaponData.genericCodec
- case ObjectClass.vanu_sentry_turret_weapon => WeaponData.genericCodec
- case ObjectClass.vulture_bomb_bay => WeaponData.genericCodec
- case ObjectClass.vulture_nose_weapon_system => WeaponData.genericCodec
- case ObjectClass.vulture_tail_cannon => WeaponData.genericCodec
- case ObjectClass.wasp_weapon_system => WeaponData.genericCodec
- case ObjectClass.winchester => WeaponData.genericCodec
- case ObjectClass.dynomite => WeaponData.genericCodec
- case ObjectClass.frag_grenade => WeaponData.genericCodec
- case ObjectClass.generic_grenade => WeaponData.genericCodec
- case ObjectClass.jammer_grenade => WeaponData.genericCodec
- case ObjectClass.mine_sweeper => WeaponData.genericCodec
- case ObjectClass.plasma_grenade => WeaponData.genericCodec
- //tools - medkits
- case ObjectClass.avatar => CharacterData.genericCodec
- case ObjectClass.locker_container => AmmoBoxData.genericCodec
+ case ObjectClass.bullet_105mm => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_12mm => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_150mm => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_15mm => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_20mm => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_25mm => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_35mm => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_75mm => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_9mm => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_9mm_AP => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.ancient_ammo_combo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.ancient_ammo_vehicle => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.anniversary_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.aphelion_immolation_cannon_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.aphelion_laser_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.aphelion_plasma_rocket_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.aphelion_ppa_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.aphelion_starfire_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.armor_canister => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.armor_siphon_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.bolt => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.burster_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.colossus_100mm_cannon_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.colossus_burster_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.colossus_chaingun_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.colossus_cluster_bomb_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.colossus_tank_cannon_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.comet_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.dualcycler_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.energy_cell => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.energy_gun_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.falcon_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.firebird_missile => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.flamethrower_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.flux_cannon_thresher_battery => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.fluxpod_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.frag_cartridge => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.frag_grenade_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.gauss_cannon_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.grenade => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.health_canister => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.heavy_grenade_mortar => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.heavy_rail_beam_battery => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.hellfire_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.hunter_seeker_missile => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.jammer_cartridge => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.jammer_grenade_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.lancer_cartridge => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.liberator_bomb => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.maelstrom_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.melee_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.mine => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.mine_sweeper_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.ntu_siphon_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.oicw_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.pellet_gun_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.peregrine_dual_machine_gun_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.peregrine_mechhammer_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.peregrine_particle_cannon_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.peregrine_rocket_pod_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.peregrine_sparrow_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.phalanx_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.phoenix_missile => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.plasma_cartridge => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.plasma_grenade_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.pounder_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.pulse_battery => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.quasar_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.reaver_rocket => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.rocket => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.scattercannon_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.shotgun_shell => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.shotgun_shell_AP => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.six_shooter_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.skyguard_flak_cannon_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.sparrow_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.spitfire_aa_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.spitfire_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.starfire_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.striker_missile_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.trek_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.upgrade_canister => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.wasp_gun_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.wasp_rocket_ammo => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ 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.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.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.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.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.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.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.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.katana => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "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.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.mini_chaingun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
+ case ObjectClass.oicw => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
+ case ObjectClass.nchev_falcon => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.nchev_scattercannon => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.nchev_sparrow => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "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.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.pulsar => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
+ case ObjectClass.pulsed_particle_accelerator => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
+ case ObjectClass.punisher => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.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.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.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.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.trhev_burster => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.trhev_dualcycler => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.trhev_pounder => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.vanguard_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
+ case ObjectClass.vanu_sentry_turret_weapon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
+ case ObjectClass.vshev_comet => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.vshev_starfire => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.vshev_quasar => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.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")
+ case ObjectClass.winchester => 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.mine_sweeper => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
+ case ObjectClass.plasma_grenade => 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")
+ case ObjectClass.super_medkit => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ case ObjectClass.super_staminakit => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "ammo box")
+ //tools
+ case ObjectClass.applicator => ConstructorData.genericCodec(DetailedWeaponData.codec, "tool")
+ case ObjectClass.bank => ConstructorData.genericCodec(DetailedWeaponData.codec, "tool")
+ case ObjectClass.command_detonater => ConstructorData.genericCodec(DetailedCommandDetonaterData.codec, "tool")
+ case ObjectClass.laze_pointer => ConstructorData.genericCodec(DetailedWeaponData.codec, "tool")
+ case ObjectClass.medicalapplicator => ConstructorData.genericCodec(DetailedWeaponData.codec, "tool")
+ case ObjectClass.nano_dispenser => ConstructorData.genericCodec(DetailedWeaponData.codec, "tool")
+ case ObjectClass.remote_electronics_kit => ConstructorData.genericCodec(DetailedREKData.codec, "tool")
+ //case ObjectClass.router_telepad => ConstructorData.genericCodec(*.codec, "tool") //TODO
+ case ObjectClass.trek => ConstructorData.genericCodec(DetailedWeaponData.codec, "tool")
+ //ace deployable
+ case ObjectClass.ace => ConstructorData.genericCodec(DetailedACEData.codec, "ace")
+ case ObjectClass.advanced_ace => ConstructorData.genericCodec(DetailedACEData.codec, "advanced ace")
+ case ObjectClass.boomer_trigger => ConstructorData.genericCodec(DetailedBoomerTriggerData.codec, "boomer trigger")
+ //other
+ case ObjectClass.avatar => ConstructorData.genericCodec(DetailedCharacterData.codec, "avatar")
+ case ObjectClass.locker_container => ConstructorData.genericCodec(DetailedAmmoBoxData.codec, "locker container")
- case ObjectClass.remote_electronics_kit => REKData.genericCodec
- case ObjectClass.trek => WeaponData.genericCodec
- case ObjectClass.medkit => AmmoBoxData.genericCodec
- case ObjectClass.super_armorkit => AmmoBoxData.genericCodec
- case ObjectClass.super_medkit => AmmoBoxData.genericCodec
- case ObjectClass.super_staminakit => AmmoBoxData.genericCodec
- case ObjectClass.applicator => WeaponData.genericCodec
- case ObjectClass.medicalapplicator => WeaponData.genericCodec
- case ObjectClass.bank => WeaponData.genericCodec
- case ObjectClass.nano_dispenser => WeaponData.genericCodec
- case ObjectClass.command_detonater => WeaponData.genericCodec
- case ObjectClass.laze_pointer => WeaponData.genericCodec
-
-
-
- //failure case
- case _ => conditional(false, bool).exmap[ConstructorData.genericPattern] (
- {
- case None | _ =>
- Attempt.failure(Err("decoding unknown object class"))
- },
- {
- case None | _ =>
- Attempt.failure(Err("encoding unknown object class"))
- }
- )
+ //failure case
+ case _ => defaultFailureCodec(objClass)
}
+
+ /**
+ * Given an object class, retrieve the `Codec` used to parse and translate the constructor data for that type.
+ * This function services `0x17` `ObjectCreateMessage` packet data.
+ *
+ * All `Codec`s accessible by this function assume the object has parent data.
+ * @param objClass the code for the type of object being constructed
+ * @return the `Codec` that handles the format of data for that particular item class, or a failing `Codec`
+ * @see `ConstructorData`
+ */
+ def selectDataCodec(objClass : Int) : Codec[ConstructorData.genericPattern] =
+ (objClass : @switch) match {
+ //ammunition
+ case ObjectClass.bullet_105mm => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_12mm => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_150mm => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_15mm => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_20mm => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_25mm => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_35mm => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_75mm => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_9mm => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_9mm_AP => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.ancient_ammo_combo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.ancient_ammo_vehicle => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.anniversary_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.aphelion_immolation_cannon_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.aphelion_laser_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.aphelion_plasma_rocket_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.aphelion_ppa_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.aphelion_starfire_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.armor_canister => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.armor_siphon_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bolt => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.burster_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.colossus_100mm_cannon_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.colossus_burster_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.colossus_chaingun_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.colossus_cluster_bomb_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.colossus_tank_cannon_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.comet_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.dualcycler_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.energy_cell => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.energy_gun_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.falcon_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.firebird_missile => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.flamethrower_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.flux_cannon_thresher_battery => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.fluxpod_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.frag_cartridge => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.frag_grenade_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.gauss_cannon_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.grenade => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.health_canister => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.heavy_grenade_mortar => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.heavy_rail_beam_battery => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.hellfire_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.hunter_seeker_missile => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.jammer_cartridge => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.jammer_grenade_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.lancer_cartridge => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.liberator_bomb => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.maelstrom_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.melee_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.mine => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.mine_sweeper_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.ntu_siphon_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.oicw_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.pellet_gun_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.peregrine_dual_machine_gun_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.peregrine_mechhammer_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.peregrine_particle_cannon_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.peregrine_rocket_pod_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.peregrine_sparrow_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.phalanx_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.phoenix_missile => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.plasma_cartridge => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.plasma_grenade_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.pounder_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.pulse_battery => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.quasar_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.reaver_rocket => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.rocket => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.scattercannon_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.shotgun_shell => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.shotgun_shell_AP => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.six_shooter_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.skyguard_flak_cannon_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.sparrow_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.spitfire_aa_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.spitfire_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.starfire_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.striker_missile_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.trek_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.upgrade_canister => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.wasp_gun_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.wasp_rocket_ammo => ConstructorData.genericCodec(AmmoBoxData.codec, "ammo box")
+ 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")
+ case ObjectClass.apc_ballgun_l => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.apc_ballgun_r => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.apc_weapon_systema => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.apc_weapon_systemb => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.apc_weapon_systemc => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.apc_weapon_systemc_nc => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.apc_weapon_systemc_tr => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.apc_weapon_systemc_vs => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.apc_weapon_systemd => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.apc_weapon_systemd_nc => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.apc_weapon_systemd_tr => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.apc_weapon_systemd_vs => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.aphelion_immolation_cannon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.aphelion_laser => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.aphelion_laser_left => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.aphelion_laser_right => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.aphelion_plasma_rocket_pod => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.aphelion_ppa => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.aphelion_ppa_left => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.aphelion_ppa_right => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.aphelion_starfire => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.aphelion_starfire_left => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.aphelion_starfire_right => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.aurora_weapon_systema => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.aurora_weapon_systemb => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.battlewagon_weapon_systema => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.battlewagon_weapon_systemb => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.battlewagon_weapon_systemc => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ 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")
+ case ObjectClass.colossus_chaingun => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.colossus_chaingun_left => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.colossus_chaingun_right => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.colossus_cluster_bomb_pod => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.colossus_dual_100mm_cannons => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.colossus_tank_cannon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ 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")
+ case ObjectClass.flail_weapon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.flamethrower => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.flechette => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.flux_cannon_thresher => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.fluxpod => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.forceblade => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.fragmentation_grenade => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.fury_weapon_systema => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.galaxy_gunship_cannon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.galaxy_gunship_gun => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.galaxy_gunship_tailgun => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.gauss => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.gauss_cannon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.grenade_launcher_marauder => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.heavy_rail_beam_magrider => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.heavy_sniper => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.hellfire => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ 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.katana => ConstructorData.genericCodec(ConcurrentFeedWeaponData.codec, "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_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.lightgunship_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.lightning_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.maelstrom => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.magcutter => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.mediumtransport_weapon_systemA => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.mediumtransport_weapon_systemB => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.mini_chaingun => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.nchev_falcon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.nchev_scattercannon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.nchev_sparrow => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.oicw => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.particle_beam_magrider => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.pellet_gun => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.peregrine_dual_machine_gun => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.peregrine_dual_machine_gun_left => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.peregrine_dual_machine_gun_right => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.peregrine_dual_rocket_pods => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.peregrine_mechhammer => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.peregrine_mechhammer_left => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.peregrine_mechhammer_right => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.peregrine_particle_cannon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.peregrine_sparrow => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.peregrine_sparrow_left => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.peregrine_sparrow_right => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ 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.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")
+ case ObjectClass.pulsed_particle_accelerator => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.punisher => ConstructorData.genericCodec(ConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.quadassault_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.r_shotgun => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.radiator => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.repeater => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.rocklet => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.rotarychaingun_mosquito => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.scythe => ConstructorData.genericCodec(WeaponData.codec, "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")
+ case ObjectClass.spitfire_weapon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.striker => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.suppressor => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.thumper => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.thunderer_weapon_systema => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.thunderer_weapon_systemb => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.trhev_burster => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.trhev_dualcycler => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.trhev_pounder => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.vanguard_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.vanu_sentry_turret_weapon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.vshev_comet => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.vshev_quasar => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.vshev_starfire => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ 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.dynomite => 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.mine_sweeper => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.plasma_grenade => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ //tools
+ case ObjectClass.applicator => ConstructorData.genericCodec(WeaponData.codec, "tool")
+ case ObjectClass.bank => ConstructorData.genericCodec(WeaponData.codec, "tool")
+ case ObjectClass.command_detonater => ConstructorData.genericCodec(CommandDetonaterData.codec, "tool")
+ case ObjectClass.laze_pointer => ConstructorData.genericCodec(WeaponData.codec, "tool")
+ case ObjectClass.medicalapplicator => ConstructorData.genericCodec(WeaponData.codec, "tool")
+ case ObjectClass.nano_dispenser => ConstructorData.genericCodec(WeaponData.codec, "tool")
+ case ObjectClass.remote_electronics_kit => ConstructorData.genericCodec(REKData.codec, "tool")
+ //case ObjectClass.router_telepad => ConstructorData.genericCodec(WeaponData.codec, "tool") //TODO
+ case ObjectClass.trek => ConstructorData.genericCodec(WeaponData.codec, "tool")
+ //ace deployables
+ case ObjectClass.ace => ConstructorData.genericCodec(ACEData.codec, "ace")
+ case ObjectClass.advanced_ace => ConstructorData.genericCodec(CommandDetonaterData.codec, "advanced ace")
+ case ObjectClass.boomer_trigger => ConstructorData.genericCodec(BoomerTriggerData.codec, "boomer trigger")
+ //other
+ case ObjectClass.implant_terminal_interface => ConstructorData.genericCodec(ImplantInterfaceData.codec, "implant terminal")
+ //failure case
+ case _ => defaultFailureCodec(objClass)
+ }
+
+ /**
+ * Given an object class, retrieve the `Codec` used to parse and translate the constructor data for that type.
+ * This function services `0x17` `ObjectCreateMessage` packet data.
+ *
+ * All `Codec`s accessible by this function assume the object has no parent data and is on the ground.
+ * @param objClass the code for the type of object being constructed
+ * @return the `Codec` that handles the format of data for that particular item class, or a failing `Codec`
+ * @see `ConstructorData`
+ */
+ def selectDataDroppedCodec(objClass : Int) : Codec[ConstructorData.genericPattern] =
+ (objClass : @switch) match {
+ //ammunition
+ case ObjectClass.bullet_105mm => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_12mm => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_150mm => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_15mm => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_20mm => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_25mm => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_35mm => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_75mm => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_9mm => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bullet_9mm_AP => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.ancient_ammo_combo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.ancient_ammo_vehicle => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.anniversary_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.aphelion_immolation_cannon_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.aphelion_laser_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.aphelion_plasma_rocket_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.aphelion_ppa_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.aphelion_starfire_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.armor_canister => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.armor_siphon_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.bolt => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.burster_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.colossus_100mm_cannon_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.colossus_burster_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.colossus_chaingun_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.colossus_cluster_bomb_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.colossus_tank_cannon_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.comet_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.dualcycler_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.energy_cell => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.energy_gun_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.falcon_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.firebird_missile => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.flamethrower_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ 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.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")
+ case ObjectClass.heavy_grenade_mortar => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.heavy_rail_beam_battery => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ 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.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")
+ case ObjectClass.melee_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.mine => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.mine_sweeper_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.ntu_siphon_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.oicw_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.pellet_gun_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.peregrine_dual_machine_gun_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.peregrine_mechhammer_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.peregrine_particle_cannon_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.peregrine_rocket_pod_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.peregrine_sparrow_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ 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.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")
+ case ObjectClass.reaver_rocket => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.rocket => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.scattercannon_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.shotgun_shell => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.shotgun_shell_AP => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.six_shooter_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.skyguard_flak_cannon_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.sparrow_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ case ObjectClass.spitfire_aa_ammo => DroppedItemData.genericCodec(AmmoBoxData.codec, "ammo box")
+ 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.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.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.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.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.hunterseeker => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.ilc9 => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.isp => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.katana => DroppedItemData.genericCodec(ConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.lancer => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.lasher => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.maelstrom => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
+ 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.phoenix => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.pulsar => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.punisher => DroppedItemData.genericCodec(ConcurrentFeedWeaponData.codec, "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.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")
+ 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")
+ //medkits
+ case ObjectClass.medkit => DroppedItemData.genericCodec(AmmoBoxData.codec, "medkit")
+ case ObjectClass.super_armorkit => DroppedItemData.genericCodec(AmmoBoxData.codec, "repair kit")
+ case ObjectClass.super_medkit => DroppedItemData.genericCodec(AmmoBoxData.codec, "medkit")
+ case ObjectClass.super_staminakit => DroppedItemData.genericCodec(AmmoBoxData.codec, "stamina kit")
+ //tools
+ case ObjectClass.applicator => DroppedItemData.genericCodec(WeaponData.codec, "tool")
+ case ObjectClass.bank => DroppedItemData.genericCodec(WeaponData.codec, "tool")
+ case ObjectClass.command_detonater => DroppedItemData.genericCodec(CommandDetonaterData.codec, "tool")
+ case ObjectClass.laze_pointer => DroppedItemData.genericCodec(WeaponData.codec, "tool")
+ case ObjectClass.medicalapplicator => DroppedItemData.genericCodec(WeaponData.codec, "tool")
+ case ObjectClass.nano_dispenser => DroppedItemData.genericCodec(WeaponData.codec, "tool")
+ case ObjectClass.remote_electronics_kit => DroppedItemData.genericCodec(REKData.codec, " tool")
+ //case ObjectClass.router_telepad => DroppedItemData.genericCodec(WeaponData.codec, "tool") //TODO
+ case ObjectClass.trek => DroppedItemData.genericCodec(WeaponData.codec, "tool")
+ //ace deployables
+ case ObjectClass.ace => DroppedItemData.genericCodec(ACEData.codec, "ace")
+ case ObjectClass.advanced_ace => DroppedItemData.genericCodec(CommandDetonaterData.codec, "advanced ace") //todo temporary?
+ case ObjectClass.boomer_trigger => DroppedItemData.genericCodec(BoomerTriggerData.codec, "boomer trigger")
+ case ObjectClass.boomer => ConstructorData.genericCodec(SmallDeployableData.codec, "ace deployable")
+ case ObjectClass.he_mine => ConstructorData.genericCodec(SmallDeployableData.codec, "ace deployable")
+ case ObjectClass.jammer_mine => ConstructorData.genericCodec(SmallDeployableData.codec, "ace deployable")
+ case ObjectClass.motionalarmsensor => ConstructorData.genericCodec(SmallDeployableData.codec, "ace deployable")
+ case ObjectClass.sensor_shield => ConstructorData.genericCodec(SmallDeployableData.codec, "ace deployable")
+ case ObjectClass.spitfire_aa => ConstructorData.genericCodec(SmallTurretData.codec, "small turret")
+ case ObjectClass.spitfire_cloaked => ConstructorData.genericCodec(SmallTurretData.codec, "small turret")
+ case ObjectClass.spitfire_turret => ConstructorData.genericCodec(SmallTurretData.codec, "small turret")
+ case ObjectClass.tank_traps => ConstructorData.genericCodec(TRAPData.codec, "trap")
+ case ObjectClass.deployable_shield_generator => ConstructorData.genericCodec(AegisShieldGeneratorData.codec, "shield generator")
+ case ObjectClass.portable_manned_turret => ConstructorData.genericCodec(OneMannedFieldTurretData.codec, "field turret")
+ case ObjectClass.portable_manned_turret_nc => ConstructorData.genericCodec(OneMannedFieldTurretData.codec, "field turret")
+ case ObjectClass.portable_manned_turret_tr => ConstructorData.genericCodec(OneMannedFieldTurretData.codec, "field turret")
+ case ObjectClass.portable_manned_turret_vs => ConstructorData.genericCodec(OneMannedFieldTurretData.codec, "field turret")
+ //projectiles
+ case ObjectClass.hunter_seeker_missile_projectile => ConstructorData.genericCodec(TrackedProjectileData.codec, "projectile")
+ case ObjectClass.oicw_projectile => ConstructorData.genericCodec(TrackedProjectileData.codec, "projectile")
+ case ObjectClass.starfire_projectile => ConstructorData.genericCodec(TrackedProjectileData.codec, "projectile")
+ case ObjectClass.striker_missile_targeting_projectile => ConstructorData.genericCodec(TrackedProjectileData.codec, "projectile")
+ //other
+ case ObjectClass.avatar => ConstructorData.genericCodec(CharacterData.codec, "avatar")
+ case ObjectClass.locker_container => ConstructorData.genericCodec(LockerContainerData.codec, "locker container")
+ case ObjectClass.matrix_terminala => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") //todo confirm
+ case ObjectClass.matrix_terminalb => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") //todo confirm
+ case ObjectClass.matrix_terminalc => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
+ case ObjectClass.order_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") //todo confirm
+ case ObjectClass.order_terminala => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
+ case ObjectClass.order_terminalb => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
+ case ObjectClass.ams_respawn_tube => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
+ case ObjectClass.capture_flag => ConstructorData.genericCodec(CaptureFlagData.codec, "capture flag")
+ //failure case
+ case _ => defaultFailureCodec(objClass)
+ }
+
+ /**
+ * `Codec` for handling a failure case upon not finding an appropriate object `Codec`.
+ * @param cls the object class whose `Codec` we have failed to find
+ * @return a failure
+ */
+ private def defaultFailureCodec(cls : Int) : Codec[ConstructorData.genericPattern] = {
+ conditional(false, bool).exmap[ConstructorData.genericPattern] (
+ {
+ case None | _ =>
+ Attempt.failure(Err(s"decoding unknown object class $cls"))
+ },
+ {
+ case None | _ =>
+ Attempt.failure(Err(s"encoding unknown object class $cls"))
+ }
+ )
}
-}
\ No newline at end of file
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectCreateBase.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectCreateBase.scala
new file mode 100644
index 00000000..dd9369bc
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectCreateBase.scala
@@ -0,0 +1,181 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.PacketHelpers
+import net.psforever.packet.game.PlanetSideGUID
+import scodec.{Attempt, Codec, DecodeResult, Err}
+import scodec.bits.BitVector
+import scodec.codecs.{bool, either, uintL}
+import shapeless.{::, HNil}
+import scodec.codecs._
+
+/**
+ * The parent information of a created object.
+ *
+ * In normal packet data order, there are two ways the parent object can be assigned.
+ * The first is an implicit association between a parent object and a child object that are both created at the same time.
+ * A player character object, for example, is initialized in the same breath as the objects in his inventory are initialized.
+ * A weapon object is constructed with an ammunition object already included within itself.
+ * The second is an explicit association between the child and the parent where the parent exists before the child is created.
+ * When a new inventory object is produced, it is usually assigned to some other existing object's inventory.
+ * That is the relationship to the role of "parent" that this object defines.
+ * As such, only its current unique identifier needs to be provided.
+ * If the parent can not be found, the child object is not created.
+ *
+ * A third form of parent object to child object association involves the impromptu assignment of an existing child to an existing parent.
+ * Since no objects are being created, that is unrelated to `ObjectCreateMessage`.
+ * Refer to `ObjectAttachMessage`, `MountVehicleMsg`, and `MountVehicleCargoMsg`.
+ *
+ * When associated, the child object is "attached" to the parent object at a specific location called a "slot."
+ * "Slots" are internal to the object and are (typically) invisible to the player.
+ * Any game object can possess any number of "slots" that serve specific purposes.
+ * Player objects have equipment holsters and grid inventory capacity.
+ * Weapon objects have magazine feed positions.
+ * Vehicle objects have seating for players and trunk inventory capacity.
+ * @param guid the GUID of the parent object
+ * @param slot a parent-defined slot identifier that explains where the child is to be attached to the parent;
+ * encoded as the length field of a Pascal string
+ */
+final case class ObjectCreateMessageParent(guid : PlanetSideGUID,
+ slot : Int)
+
+object ObjectCreateBase {
+ private type basePattern = Long :: Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: BitVector :: HNil
+ private type parentPattern = Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: HNil
+
+ /**
+ * Calculate the stream length in number of bits by factoring in the whole message in two portions.
+ * This process automates for: object encoding.
+ *
+ * Ignoring the parent data, constant field lengths have already been factored into the results.
+ * That includes:
+ * the length of the stream length field (32u),
+ * the object's class (11u),
+ * the object's GUID (16u),
+ * and the bit to determine if there will be parent data.
+ * In total, these fields form a known fixed length of 60u.
+ * @param parentInfo if defined, the relationship between this object and another object (its parent);
+ * information about the parent adds either 24u or 32u
+ * @param data if defined, the data used to construct this type of object;
+ * the data length is indeterminate until it is walked-through;
+ * note: the type is `StreamBitSize` as opposed to `ConstructorData`
+ * @return the total length of the resulting data stream in bits
+ */
+ def streamLen(parentInfo : Option[ObjectCreateMessageParent], data : StreamBitSize) : Long = {
+ //knowable length
+ val base : Long = if(parentInfo.isDefined) {
+ if(parentInfo.get.slot > 127) 92L else 84L //(32u + 1u + 11u + 16u) ?+ (16u + (8u | 16u))
+ }
+ else {
+ 60L
+ }
+ base + data.bitsize
+ }
+
+ /**
+ * Take bit data and transform it into an object that expresses the important information of a game piece.
+ * This function is fail-safe because it catches errors involving bad parsing of the bitstream data.
+ * Generally, the `Exception` messages themselves are not useful here.
+ * The important parts are what the packet thought the object class should be and what it actually processed.
+ * @param objectClass the code for the type of object being constructed
+ * @param data the bitstream data
+ * @param getCodecFunc a lookup function that returns a `Codec` for this object class
+ * @return the optional constructed object
+ * @see `ObjectClass`
+ */
+ def decodeData(objectClass : Int, data : BitVector, getCodecFunc : (Int) => Codec[ConstructorData.genericPattern]) : Option[ConstructorData] = {
+ var out : Option[ConstructorData] = None
+ try {
+ val outOpt : Option[DecodeResult[_]] = getCodecFunc(objectClass).decode(data).toOption
+ if(outOpt.isDefined)
+ out = outOpt.get.value.asInstanceOf[ConstructorData.genericPattern]
+ }
+ catch {
+ case _ : Exception =>
+ //catch and release, any sort of parse error
+ }
+ out
+ }
+
+ /**
+ * Take the important information of a game piece and transform it into bit data.
+ * This function is fail-safe because it catches errors involving bad parsing of the object data.
+ * Generally, the `Exception` messages themselves are not useful here.
+ * @param objClass the code for the type of object being deconstructed
+ * @param obj the object data
+ * @param getCodecFunc a lookup function that returns a `Codec` for this object class
+ * @return the bitstream data
+ * @see `ObjectClass`
+ */
+ def encodeData(objClass : Int, obj : ConstructorData, getCodecFunc : (Int) => Codec[ConstructorData.genericPattern]) : BitVector = {
+ var out = BitVector.empty
+ try {
+ val outOpt : Option[BitVector] = getCodecFunc(objClass).encode(Some(obj.asInstanceOf[ConstructorData])).toOption
+ if(outOpt.isDefined)
+ out = outOpt.get
+ }
+ catch {
+ case _ : Exception =>
+ //catch and release, any sort of parse error
+ }
+ out
+ }
+
+ /**
+ * `Codec` for formatting around the lack of parent data in the stream.
+ */
+ private val noParent : Codec[parentPattern] = (
+ ("objectClass" | uintL(0xb)) :: //11u
+ ("guid" | PlanetSideGUID.codec) //16u
+ ).xmap[parentPattern](
+ {
+ case cls :: guid :: HNil =>
+ cls :: guid :: None :: HNil
+ }, {
+ case cls :: guid :: None :: HNil =>
+ cls :: guid :: HNil
+ }
+ )
+ /**
+ * `Codec` for reading and formatting parent data from the stream.
+ */
+ private val parent : Codec[parentPattern] = (
+ ("parentGuid" | PlanetSideGUID.codec) :: //16u
+ ("objectClass" | uintL(0xb)) :: //11u
+ ("guid" | PlanetSideGUID.codec) :: //16u
+ ("parentSlotIndex" | PacketHelpers.encodedStringSize) //8u or 16u
+ ).xmap[parentPattern](
+ {
+ case pguid :: cls :: guid :: slot :: HNil =>
+ cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil
+ }, {
+ case cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil =>
+ pguid :: cls :: guid :: slot :: HNil
+ }
+ )
+
+ /**
+ * `Codec` for handling the primary fields of both `ObjectCreateMessage` packets and `ObjectCreateDetailedMessage` packets.
+ */
+ val baseCodec : Codec[basePattern] =
+ ("streamLength" | uint32L) ::
+ (either(bool, parent, noParent).exmap[parentPattern] (
+ {
+ case Left(a :: b :: Some(c) :: HNil) =>
+ Attempt.successful(a :: b :: Some(c) :: HNil) //true, _, _, Some(c)
+ case Right(a :: b :: None :: HNil) =>
+ Attempt.successful(a :: b :: None :: HNil) //false, _, _, None
+ // failure cases
+ case Left(_ :: _ :: None :: HNil) =>
+ Attempt.failure(Err("missing parent structure")) //true, _, _, None
+ case Right(_ :: _ :: Some(_) :: HNil) =>
+ Attempt.failure(Err("unexpected parent structure")) //false, _, _, Some(c)
+ }, {
+ case a :: b :: Some(c) :: HNil =>
+ Attempt.successful(Left(a :: b :: Some(c) :: HNil))
+ case a :: b :: None :: HNil =>
+ Attempt.successful(Right(a :: b :: None :: HNil))
+ }
+ ) :+
+ ("data" | bits)) //greed is good
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/OneMannedFieldTurretData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/OneMannedFieldTurretData.scala
new file mode 100644
index 00000000..565c21d2
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/OneMannedFieldTurretData.scala
@@ -0,0 +1,165 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import net.psforever.packet.game.PlanetSideGUID
+import scodec.codecs._
+import scodec.{Attempt, Codec, Err}
+import shapeless.{::, HNil}
+
+/**
+ * A representation of the player-mountable large field turrets deployed using an advanced adaptive construction engine.
+ *
+ * Field turrets are divided into the turret base, the mounted turret weapon, and the turret's ammunition.
+ * The ammunition is always the same regardless of which faction owns the turret.
+ * Turret bases and turret weapons are generally paired by the faction.
+ *
+ * If the turret has no `health`, it is rendered as destroyed.
+ * If the turret has no internal weapon, it is safest rendered as destroyed.
+ * Trying to fire a turret with no internal weapon will soft-lock the PlanetSide client.
+ * @param deploy data common to objects spawned by the (advanced) adaptive construction engine
+ * @param player_guid the player who owns this object
+ * @param health the amount of health the object has, as a percentage of a filled bar
+ * @param internals data regarding the mountable weapon
+ */
+final case class OneMannedFieldTurretData(deploy : ACEDeployableData,
+ player_guid : PlanetSideGUID, //might be able to re-package into field above
+ health : Int,
+ internals : Option[InternalSlot] = None
+ ) extends ConstructorData {
+ override def bitsize : Long = {
+ val deploySize = deploy.bitsize
+ val internalSize = if(internals.isDefined) { ACEDeployableData.internalWeapon_bitsize + internals.get.bitsize } else { 0L }
+ 38L + deploySize + internalSize //16u + 8u + 8u + 2u + 4u
+ }
+}
+
+object OneMannedFieldTurretData extends Marshallable[OneMannedFieldTurretData] {
+ /**
+ * Overloaded constructor that mandates information about the internal weapon of the field turret.
+ * @param deploy data common to objects spawned by the (advanced) adaptive construction engine
+ * @param player_guid the player who owns this object
+ * @param health the amount of health the object has, as a percentage of a filled bar
+ * @param internals data regarding the mountable weapon
+ * @return a `OneMannedFieldTurretData` object
+ */
+ def apply(deploy : ACEDeployableData, player_guid : PlanetSideGUID, health : Int, internals : InternalSlot) : OneMannedFieldTurretData =
+ new OneMannedFieldTurretData(deploy, player_guid, health, Some(internals))
+
+ /**
+ * Prefabricated weapon data for a weaponless field turret mount (`portable_manned_turret`).
+ * @param wep_guid the uid to assign to the weapon
+ * @param wep_unk1 na;
+ * used by `WeaponData`
+ *
+ * @param wep_unk2 na;
+ * used by `WeaponData`
+ * @param ammo_guid the uid to assign to the ammo
+ * @param ammo_unk na;
+ * used by `AmmoBoxData`
+ * @return an `InternalSlot` object
+ */
+ def generic(wep_guid : PlanetSideGUID, wep_unk1 : Int, wep_unk2 : Int, ammo_guid : PlanetSideGUID, ammo_unk : Int) : InternalSlot =
+ InternalSlot(ObjectClass.energy_gun, wep_guid, 1,
+ WeaponData(wep_unk1, wep_unk2, ObjectClass.energy_gun_ammo, ammo_guid, 0,
+ AmmoBoxData(ammo_unk)
+ )
+ )
+
+ /**
+ * Prefabricated weapon data for the Terran Republic field turret, the Avenger (`portable_manned_turret_tr`).
+ * @param wep_guid the uid to assign to the weapon
+ * @param wep_unk1 na;
+ * used by `WeaponData`
+ *
+ * @param wep_unk2 na;
+ * used by `WeaponData`
+ * @param ammo_guid the uid to assign to the ammo
+ * @param ammo_unk na;
+ * used by `AmmoBoxData`
+ * @return an `InternalSlot` object
+ */
+ def avenger(wep_guid : PlanetSideGUID, wep_unk1 : Int, wep_unk2 : Int, ammo_guid : PlanetSideGUID, ammo_unk : Int) : InternalSlot =
+ InternalSlot(ObjectClass.energy_gun_tr, wep_guid, 1,
+ WeaponData(wep_unk1, wep_unk2, ObjectClass.energy_gun_ammo, ammo_guid, 0,
+ AmmoBoxData(ammo_unk)
+ )
+ )
+
+ /**
+ * Prefabricated weapon data for the New Conglomerate field turret, the Osprey (`portable_manned_turret_vnc`).
+ * @param wep_guid the uid to assign to the weapon
+ * @param wep_unk1 na;
+ * used by `WeaponData`
+ * @param wep_unk2 na;
+ * used by `WeaponData`
+ * @param ammo_guid the uid to assign to the ammo
+ * @param ammo_unk na;
+ * used by `AmmoBoxData`
+ * @return an `InternalSlot` object
+ */
+ def osprey(wep_guid : PlanetSideGUID, wep_unk1 : Int, wep_unk2 : Int, ammo_guid : PlanetSideGUID, ammo_unk : Int) : InternalSlot =
+ InternalSlot(ObjectClass.energy_gun_nc, wep_guid, 1,
+ WeaponData(wep_unk1, wep_unk2, ObjectClass.energy_gun_ammo, ammo_guid, 0,
+ AmmoBoxData(ammo_unk)
+ )
+ )
+
+ /**
+ * Prefabricated weapon data for the Vanu Soveriegnty field turret, the Orion (`portable_manned_turret_vs`).
+ * @param wep_guid the uid to assign to the weapon
+ * @param wep_unk1 na;
+ * used by `WeaponData`
+ * @param wep_unk2 na;
+ * used by `WeaponData`
+ * @param ammo_guid the uid to assign to the ammo
+ * @param ammo_unk na;
+ * used by `AmmoBoxData`
+ * @return an `InternalSlot` object
+ */
+ def orion(wep_guid : PlanetSideGUID, wep_unk1 : Int, wep_unk2 : Int, ammo_guid : PlanetSideGUID, ammo_unk : Int) : InternalSlot =
+ InternalSlot(ObjectClass.energy_gun_vs, wep_guid, 1,
+ WeaponData(wep_unk1, wep_unk2, ObjectClass.energy_gun_ammo, ammo_guid, 0,
+ AmmoBoxData(ammo_unk)
+ )
+ )
+
+ implicit val codec : Codec[OneMannedFieldTurretData] = (
+ ("deploy" | ACEDeployableData.codec) ::
+ bool ::
+ ("player_guid" | PlanetSideGUID.codec) ::
+ bool ::
+ ("health" | uint8L) ::
+ uint2L ::
+ uint8L ::
+ bool ::
+ optional(bool, "internals" | ACEDeployableData.internalWeaponCodec)
+ ).exmap[OneMannedFieldTurretData] (
+ {
+ case deploy :: false :: player :: false :: health :: 0 :: 0x1E :: false :: internals :: HNil =>
+ var newHealth : Int = health
+ var newInternals : Option[InternalSlot] = internals
+ if(health == 0 || internals.isEmpty) {
+ newHealth = 0
+ newInternals = None
+ }
+ Attempt.successful(OneMannedFieldTurretData(deploy, player, newHealth, newInternals))
+
+ case _ =>
+ Attempt.failure(Err("invalid omft data format"))
+ },
+ {
+ case OneMannedFieldTurretData(deploy, player, health, internals) =>
+ var newHealth : Int = health
+ var newInternals : Option[InternalSlot] = internals
+ if(health == 0 || internals.isEmpty) {
+ newHealth = 0
+ newInternals = None
+ }
+ Attempt.successful(deploy :: false :: player :: false :: newHealth :: 0 :: 0x1E :: false :: newInternals :: HNil)
+
+ case _ =>
+ Attempt.failure(Err("invalid omft data format"))
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/PlacementData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/PlacementData.scala
new file mode 100644
index 00000000..1c707256
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/PlacementData.scala
@@ -0,0 +1,74 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import net.psforever.types.Vector3
+import scodec.codecs._
+import scodec.Codec
+
+/**
+ * A specific location and heading in game world coordinates and game world measurements.
+ * @param coord the xyz-coordinate location in the world
+ * @param roll the amount of roll that affects orientation
+ * @param pitch the amount of pitch that affects orientation
+ * @param yaw the amount of yaw that affects orientation
+ * @param init_move optional movement data that occurs upon placement
+ */
+final case class PlacementData(coord : Vector3,
+ roll : Int,
+ pitch : Int,
+ yaw : Int,
+ init_move : Option[Vector3] = None
+ ) extends StreamBitSize {
+ override def bitsize : Long = {
+ val moveLength = if(init_move.isDefined) { 42 } else { 0 }
+ 81L + moveLength
+ }
+}
+
+object PlacementData extends Marshallable[PlacementData] {
+ /**
+ * An abbreviated constructor for creating `PlacementData`, ignoring the `Vector3` for position data.
+ * @param x the x-coordinate location in the world
+ * @param y the y-coordinate location in the world
+ * @param z the z-coordinate location in the world
+ * @return a `PlacementData` object
+ */
+ def apply(x : Float, y : Float, z : Float) : PlacementData =
+ new PlacementData(Vector3(x, y, z), 0, 0, 0)
+
+ /**
+ * An abbreviated constructor for creating `PlacementData`, ignoring the `Vector3` for position data, supplying other important fields.
+ * @param x the x-coordinate location in the world
+ * @param y the y-coordinate location in the world
+ * @param z the z-coordinate location in the world
+ * @param roll the amount of roll that affects orientation
+ * @param pitch the amount of pitch that affects orientation
+ * @param yaw the amount of yaw that affects orientation
+ * @return a `PlacementData` object
+ */
+ def apply(x : Float, y : Float, z : Float, roll : Int, pitch : Int, yaw : Int) : PlacementData =
+ new PlacementData(Vector3(x, y, z), roll, pitch, yaw)
+
+ /**
+ * An abbreviated constructor for creating `PlacementData`, ignoring the `Vector3` for position data, supplying all other fields.
+ * @param x the x-coordinate location in the world
+ * @param y the y-coordinate location in the world
+ * @param z the z-coordinate location in the world
+ * @param roll the amount of roll that affects orientation
+ * @param pitch the amount of pitch that affects orientation
+ * @param yaw the amount of yaw that affects orientation
+ * @param init_move optional movement data that occurs upon placement
+ * @return a `PlacementData` object
+ */
+ def apply(x : Float, y : Float, z : Float, roll : Int, pitch : Int, yaw : Int, init_move : Vector3) : PlacementData =
+ new PlacementData(Vector3(x, y, z), roll, pitch, yaw, Some(init_move))
+
+ implicit val codec : Codec[PlacementData] = (
+ ("coord" | Vector3.codec_pos) ::
+ ("roll" | uint8L) ::
+ ("pitch" | uint8L) ::
+ ("yaw" | uint8L) ::
+ optional(bool, "init_move" | Vector3.codec_vel)
+ ).as[PlacementData]
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/REKData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/REKData.scala
index e595b7c8..eabf66cb 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/REKData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/REKData.scala
@@ -7,56 +7,36 @@ import scodec.codecs._
import shapeless.{::, HNil}
/**
- * A representation of the REK portion of `ObjectCreateMessage` packet data.
- * This data will help construct the "tool" called a Remote Electronics Kit.
- *
- * Of note is the first portion of the data which resembles the `WeaponData` format.
- * @param unk na
+ * na
+ * @param unk1 na
+ * @param unk2 na;
+ * defaults to 0
+ * @see `DetailedREKData`
*/
-final case class REKData(unk : Int) extends ConstructorData {
- /**
- * Performs a "sizeof()" analysis of the given object.
- * @see ConstructorData.bitsize
- * @return the number of bits necessary to represent this object
- */
- override def bitsize : Long = 67L
+final case class REKData(unk1 : Int,
+ unk2 : Int,
+ unk3 : Int = 0
+ ) extends ConstructorData {
+ override def bitsize : Long = 50L
}
object REKData extends Marshallable[REKData] {
implicit val codec : Codec[REKData] = (
- ("unk" | uint4L) ::
- uint4L ::
- uintL(20) ::
- uint4L ::
- uint16L ::
- uint4L ::
- uintL(15)
+ ("unk1" | uint4L) ::
+ ("unk2" | uint4L) ::
+ uint(28) ::
+ ("unk3" | uint4L) ::
+ uint(10)
).exmap[REKData] (
{
- case code :: 8 :: 0 :: 2 :: 0 :: 8 :: 0 :: HNil =>
- Attempt.successful(REKData(code))
- case code :: _ :: _ :: _ :: _ :: _ :: _ :: HNil =>
+ case unk1 :: unk2 :: 0 :: unk3 :: 0 :: HNil =>
+ Attempt.successful(REKData(unk1, unk2, unk3))
+ case _ :: _ :: _ :: _ :: _ :: HNil =>
Attempt.failure(Err("invalid rek data format"))
},
{
- case REKData(code) =>
- Attempt.successful(code :: 8 :: 0 :: 2 :: 0 :: 8 :: 0 :: HNil)
- }
- ).as[REKData]
-
- /**
- * Transform between REKData and ConstructorData.
- */
- val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
- {
- case x =>
- Attempt.successful(Some(x.asInstanceOf[ConstructorData]))
- },
- {
- case Some(x) =>
- Attempt.successful(x.asInstanceOf[REKData])
- case _ =>
- Attempt.failure(Err("can not encode rek data"))
+ case REKData(unk1, unk2, unk3) =>
+ Attempt.successful(unk1 :: unk2 :: 0 :: unk3 :: 0 :: HNil)
}
)
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/RibbonBars.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/RibbonBars.scala
index 2acb0b63..305d9a48 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/RibbonBars.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/RibbonBars.scala
@@ -17,19 +17,16 @@ import scodec.codecs._
* @param lower the lower configurable merit ribbon
* @param tos the top-most term of service merit ribbon
*/
-final case class RibbonBars(upper : Long = 0xFFFFFFFFL,
- middle : Long = 0xFFFFFFFFL,
- lower : Long = 0xFFFFFFFFL,
- tos : Long = 0xFFFFFFFFL) extends StreamBitSize {
- /**
- * Performs a "sizeof()" analysis of the given object.
- * @see ConstructorData.bitsize
- * @return the number of bits necessary to represent this object
- */
+final case class RibbonBars(upper : Long = RibbonBars.noRibbon,
+ middle : Long = RibbonBars.noRibbon,
+ lower : Long = RibbonBars.noRibbon,
+ tos : Long = RibbonBars.noRibbon) extends StreamBitSize {
override def bitsize : Long = 128L
}
object RibbonBars extends Marshallable[RibbonBars] {
+ val noRibbon : Long = 0xFFFFFFFFL
+
implicit val codec : Codec[RibbonBars] = (
("upper" | uint32L) ::
("middle" | uint32L) ::
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/SmallDeployableData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/SmallDeployableData.scala
new file mode 100644
index 00000000..5bf06920
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/SmallDeployableData.scala
@@ -0,0 +1,34 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * A representation of simple objects that are spawned by the adaptive construction engine.
+ * @param deploy data common to objects spawned by the (advanced) adaptive construction engine
+ */
+final case class SmallDeployableData(deploy : ACEDeployableData) extends ConstructorData {
+ override def bitsize : Long = deploy.bitsize + 1L
+}
+
+object SmallDeployableData extends Marshallable[SmallDeployableData] {
+ implicit val codec : Codec[SmallDeployableData] = (
+ ("deploy" | ACEDeployableData.codec) ::
+ bool
+ ).exmap[SmallDeployableData] (
+ {
+ case deploy :: false :: HNil =>
+ Attempt.successful(SmallDeployableData(deploy))
+
+ case _ =>
+ Attempt.failure(Err("invalid small deployable data format"))
+ },
+ {
+ case SmallDeployableData(deploy) =>
+ Attempt.successful(deploy :: false :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/SmallTurretData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/SmallTurretData.scala
new file mode 100644
index 00000000..5ab5406b
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/SmallTurretData.scala
@@ -0,0 +1,116 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import net.psforever.packet.game.PlanetSideGUID
+import scodec.codecs._
+import scodec.{Attempt, Codec, Err}
+import shapeless.{::, HNil}
+
+/**
+ * A representation of the Spitfire-based small turrets deployed using an adaptive construction engine.
+ *
+ * The turret may contain substructure defining a weapon is a turret weapon contained within the turret itself.
+ * Furthermore, that turret-like weapon is loaded with turret-like ammunition.
+ * In other words, this outer turret can be considered a weapons platform for the inner turret weapon.
+ *
+ * If the turret has no `health`, it is rendered as destroyed.
+ * If the turret has no internal weapon, it is safest rendered as destroyed.
+ * @param deploy data common to objects spawned by the (advanced) adaptive construction engine
+ * @param health the amount of health the object has, as a percentage of a filled bar
+ * @param internals data regarding the mounted weapon
+ */
+final case class SmallTurretData(deploy : ACEDeployableData,
+ health : Int,
+ internals : Option[InternalSlot] = None
+ ) extends ConstructorData {
+ override def bitsize : Long = {
+ val deploySize = deploy.bitsize
+ val internalSize = if(internals.isDefined) { ACEDeployableData.internalWeapon_bitsize + internals.get.bitsize } else { 0L }
+ 23L + deploySize + internalSize //1u + 8u + 7u + 4u + 2u + 1u
+ }
+}
+
+object SmallTurretData extends Marshallable[SmallTurretData] {
+ /**
+ * Overloaded constructor that mandates information about the internal weapon of the small turret.
+ * @param deploy data common to objects spawned by the (advanced) adaptive construction engine
+ * @param health the amount of health the object has, as a percentage of a filled bar
+ * @param internals data regarding the mounted weapon
+ * @return a `SmallTurretData` object
+ */
+ def apply(deploy : ACEDeployableData, health : Int, internals : InternalSlot) : SmallTurretData =
+ new SmallTurretData(deploy, health, Some(internals))
+
+ /**
+ * Prefabricated weapon data for both Spitfires (`spitfire_turret`) and Shadow Turrets (`spitfire_cloaked`).
+ * @param wep_guid the uid to assign to the weapon
+ * @param wep_unk1 na;
+ * used by `WeaponData`
+ * @param wep_unk2 na;
+ * used by `WeaponData`
+ * @param ammo_guid the uid to assign to the ammo
+ * @param ammo_unk na;
+ * used by `AmmoBoxData`
+ * @return an `InternalSlot` object
+ */
+ def spitfire(wep_guid : PlanetSideGUID, wep_unk1 : Int, wep_unk2 : Int, ammo_guid : PlanetSideGUID, ammo_unk : Int) : InternalSlot =
+ InternalSlot(ObjectClass.spitfire_weapon, wep_guid, 0,
+ WeaponData(wep_unk1, wep_unk2, ObjectClass.spitfire_ammo, ammo_guid, 0,
+ AmmoBoxData(ammo_unk)
+ )
+ )
+
+ /**
+ * Prefabricated weapon data for Cerebus turrets (`spitfire_aa`).
+ * @param wep_guid the uid to assign to the weapon
+ * @param wep_unk1 na;
+ * used by `WeaponData`
+ * @param ammo_guid the uid to assign to the ammo
+ * @param wep_unk2 na;
+ * used by `WeaponData`
+ * @param ammo_unk na;
+ * used by `AmmoBoxData`
+ * @return an `InternalSlot` object
+ */
+ def cerebus(wep_guid : PlanetSideGUID, wep_unk1 : Int, wep_unk2 : Int, ammo_guid : PlanetSideGUID, ammo_unk : Int) : InternalSlot =
+ InternalSlot(ObjectClass.spitfire_aa_weapon, wep_guid, 0,
+ WeaponData(wep_unk1, wep_unk2, ObjectClass.spitfire_aa_ammo, ammo_guid, 0,
+ AmmoBoxData(ammo_unk)
+ )
+ )
+
+ implicit val codec : Codec[SmallTurretData] = (
+ ("deploy" | ACEDeployableData.codec) ::
+ bool ::
+ ("health" | uint8L) ::
+ uintL(7) ::
+ uint4L ::
+ uint2L ::
+ optional(bool, "internals" | ACEDeployableData.internalWeaponCodec)
+ ).exmap[SmallTurretData] (
+ {
+ case deploy :: false :: health :: 0 :: 0xF :: 0 :: internals :: HNil =>
+ var newHealth : Int = health
+ var newInternals : Option[InternalSlot] = internals
+ if(health == 0 || internals.isEmpty) {
+ newHealth = 0
+ newInternals = None
+ }
+ Attempt.successful(SmallTurretData(deploy, newHealth, newInternals))
+
+ case _ =>
+ Attempt.failure(Err("invalid small turret data format"))
+ },
+ {
+ case SmallTurretData(deploy, health, internals) =>
+ var newHealth : Int = health
+ var newInternals : Option[InternalSlot] = internals
+ if(health == 0 || internals.isEmpty) {
+ newHealth = 0
+ newInternals = None
+ }
+ Attempt.successful(deploy :: false :: newHealth :: 0 :: 0xF :: 0 :: newInternals :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/StreamBitSize.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/StreamBitSize.scala
index 495b341b..141e5eb7 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/StreamBitSize.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/StreamBitSize.scala
@@ -2,17 +2,33 @@
package net.psforever.packet.game.objectcreate
/**
- * Apply this trait to a class that needs to have its size in bits calculated.
+ * Apply this `trait` to a class that needs to have its size in bits calculated.
*/
trait StreamBitSize {
/**
- * Performs a "sizeof()" analysis of the given object.
+ * Performs a "sizeof()" analysis of the given object.
+ *
* The calculation reflects the `scodec Codec` definition rather than the explicit parameter fields.
- * For example, an `Int` is normally a 32u number;
- * when parsed with a `uintL(7)`, it's length will be considered 7u.
- * (Note: being permanently signed, an `scodec` 32u value must fit into a `Long` type.)
- * @return the number of bits necessary to represent this object;
+ * For example, a traditional `Int` is normally a 32-bit number, often rendered as a `32u` number.
+ * When parsed with a `uintL(7)`, it's length will be considered 7 bits (`7u`).
+ * (Note: being permanently signed, an `scodec` value of `32u` or longer must fit into a `Long` type.)
+ * @return the number of bits necessary to measure an object of this class;
* defaults to `0L`
*/
def bitsize : Long = 0L
}
+
+object StreamBitSize {
+ /**
+ * Calculate the bit size of a Pascal string.
+ * @param str a length-prefixed string
+ * @param width the width of the character encoding;
+ * defaults to 8 bits
+ * @return the size in bits
+ */
+ def stringBitSize(str : String, width : Int = 8) : Long = {
+ val strlen = str.length
+ val lenSize = if(strlen > 127) 16L else 8L
+ lenSize + (strlen * width)
+ }
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/TRAPData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/TRAPData.scala
new file mode 100644
index 00000000..72f0882d
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/TRAPData.scala
@@ -0,0 +1,44 @@
+// 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 the tactical resonance area protection unit deployed using an advanced adaptive construction engine.
+ * Three metal beams, erect and tangled, block passage to enemies and their vehicles.
+ * @param deploy data common to objects spawned by the (advanced) adaptive construction engine
+ * @param health the amount of health the object has, as a percentage of a filled bar
+ */
+final case class TRAPData(deploy : ACEDeployableData,
+ health : Int
+ ) extends ConstructorData {
+ override def bitsize : Long = {
+ 23L + deploy.bitsize //8u + 7u + 4u + 3u + 1u
+ }
+}
+
+object TRAPData extends Marshallable[TRAPData] {
+ implicit val codec : Codec[TRAPData] = (
+ ("deploy" | ACEDeployableData.codec) ::
+ bool ::
+ ("health" | uint8L) ::
+ uint(7) ::
+ uint4L ::
+ uint(3)
+ ).exmap[TRAPData] (
+ {
+ case deploy :: false :: health :: 0 :: 15 :: 0 :: HNil =>
+ Attempt.successful(TRAPData(deploy, health))
+
+ case _ =>
+ Attempt.failure(Err("invalid trap data format"))
+ },
+ {
+ case TRAPData(deploy, health) =>
+ Attempt.successful(deploy :: false :: health :: 0 :: 15 :: 0 :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala
new file mode 100644
index 00000000..b2420ca4
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala
@@ -0,0 +1,86 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * A representation of a projectile that the server must intentionally convey to players other than the shooter.
+ * @param pos where and how the projectile is oriented
+ * @param unk1 na
+ * @param unk2 na;
+ * data specific to the type of projectile(?)
+ */
+final case class TrackedProjectileData(pos : PlacementData,
+ unk1 : Int,
+ unk2 : Int
+ ) extends ConstructorData {
+ override def bitsize : Long = 56L + pos.bitsize
+}
+
+object TrackedProjectileData extends Marshallable[TrackedProjectileData] {
+ final val oicw_projectile_data = 3355587
+ final val striker_missile_targetting_projectile_data = 6710918
+ final val hunter_seeker_missile_projectile_data = 10131913
+ final val starfire_projectile_data = 10131961
+
+ /**
+ * Overloaded constructor specifically for OICW projectiles.
+ * @param pos where and how the projectile is oriented
+ * @param unk na
+ * @return a `TrackedProjectileData` object
+ */
+ def oicw(pos : PlacementData, unk : Int) : TrackedProjectileData =
+ new TrackedProjectileData(pos, unk, oicw_projectile_data)
+
+ /**
+ * Overloaded constructor specifically for Striker projectiles.
+ * @param pos where and how the projectile is oriented
+ * @param unk na
+ * @return a `TrackedProjectileData` object
+ */
+ def striker(pos : PlacementData, unk : Int) : TrackedProjectileData =
+ new TrackedProjectileData(pos, unk, striker_missile_targetting_projectile_data)
+
+ /**
+ * Overloaded constructor specifically for Hunter Seeker (Phoenix) projectiles.
+ * @param pos where and how the projectile is oriented
+ * @param unk na
+ * @return a `TrackedProjectileData` object
+ */
+ def hunter_seeker(pos : PlacementData, unk : Int) : TrackedProjectileData =
+ new TrackedProjectileData(pos, unk, hunter_seeker_missile_projectile_data)
+
+ /**
+ * Overloaded constructor specifically for Starfire projectiles.
+ * @param pos where and how the projectile is oriented
+ * @param unk na
+ * @return a `TrackedProjectileData` object
+ */
+ def starfire(pos : PlacementData, unk : Int) : TrackedProjectileData =
+ new TrackedProjectileData(pos, unk, starfire_projectile_data)
+
+ implicit val codec : Codec[TrackedProjectileData] = (
+ ("pos" | PlacementData.codec) ::
+ ("unk1" | uint(3)) ::
+ uint4L ::
+ uint16L ::
+ ("unk2" | uint24) ::
+ uint4L ::
+ uint(5)
+ ).exmap[TrackedProjectileData] (
+ {
+ case pos :: unk1 :: 4 :: 0 :: unk2 :: 4 :: 0 :: HNil =>
+ Attempt.successful(TrackedProjectileData(pos, unk1, unk2))
+
+ case _ =>
+ Attempt.failure(Err("invalid projectile data format"))
+ },
+ {
+ case TrackedProjectileData(pos, unk1, unk2) =>
+ Attempt.successful(pos :: unk1 :: 4 :: 0 :: unk2 :: 4 :: 0 :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/WeaponData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/WeaponData.scala
index 2a1d5852..0cecc2f2 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/WeaponData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/WeaponData.scala
@@ -3,87 +3,80 @@ package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import net.psforever.packet.game.PlanetSideGUID
-import scodec.{Attempt, Codec, Err}
import scodec.codecs._
+import scodec.{Attempt, Codec, Err}
import shapeless.{::, HNil}
/**
* A representation of a class of weapons that can be created using `ObjectCreateMessage` packet data.
- * This data will help construct a "loaded weapon" such as a Suppressor or a Gauss.
- *
- * The data for the weapons nests information for the default (current) type and number of ammunition in its magazine.
- * This ammunition data essentially is the weapon's magazines as numbered slots.
- * Having said that, this format only handles one type of ammunition at a time.
- * Any weapon that has two types of ammunition simultaneously loaded, e.g., a Punisher, must be handled with another `Codec`.
- * This functionality is unrelated to a weapon that switches ammunition type;
- * a weapon with that behavior is handled perfectly fine using this `case class`.
- * @param unk na
- * @param ammo data regarding the currently loaded ammunition type and quantity
- * @see AmmoBoxData
+ * Common uses include items deposited on the ground and items in another player's visible inventory (holsters).
+ * @param unk1 na;
+ * commonly 8
+ * @param unk2 na;
+ * commonly 12
+ * @param fire_mode the current mode of weapon's fire;
+ * zero-indexed
+ * @param ammo data regarding the currently loaded ammunition type
+ * @see `WeaponData`
+ * @see `AmmoBoxData`
*/
-final case class WeaponData(unk : Int,
- ammo : InternalSlot) extends ConstructorData {
- /**
- * Performs a "sizeof()" analysis of the given object.
- * @see ConstructorData.bitsize
- * @see AmmoBoxData.bitsize
- * @return the number of bits necessary to represent this object
- */
- override def bitsize : Long = 61L + ammo.bitsize
+final case class WeaponData(unk1 : Int,
+ unk2 : Int,
+ fire_mode : Int,
+ ammo : InternalSlot
+ ) extends ConstructorData {
+ override def bitsize : Long = 44L + ammo.bitsize
}
object WeaponData extends Marshallable[WeaponData] {
/**
- * An abbreviated constructor for creating `WeaponData` while masking use of `InternalSlot` for its `AmmoBoxData`.
- *
- * Exploration:
- * This class may need to be rewritten later to support objects spawned in the world environment.
- * @param unk na
+ * An abbreviated constructor for creating `WeaponData` while masking use of `InternalSlot` for its `AmmoBoxData`.
+ * @param unk1 na
+ * @param unk2 na
* @param cls the code for the type of object (ammunition) being constructed
* @param guid the globally unique id assigned to the ammunition
* @param parentSlot the slot where the ammunition is to be installed in the weapon
- * @param ammo the constructor data for the ammunition
- * @return a WeaponData object
+ * @param ammo the ammunition object
+ * @return a `WeaponData` object
*/
- def apply(unk : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : AmmoBoxData) : WeaponData =
- new WeaponData(unk, InternalSlot(cls, guid, parentSlot, ammo))
+ def apply(unk1 : Int, unk2 : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : AmmoBoxData) : WeaponData =
+ new WeaponData(unk1, unk2, 0, InternalSlot(cls, guid, parentSlot, ammo))
+
+ /**
+ * An abbreviated constructor for creating `WeaponData` while masking use of `InternalSlot` for its `AmmoBoxData`.
+ * @param unk1 na
+ * @param unk2 na
+ * @param fire_mode data regarding the currently loaded ammunition type
+ * @param cls the code for the type of object (ammunition) being constructed
+ * @param guid the globally unique id assigned to the ammunition
+ * @param parentSlot the slot where the ammunition is to be installed in the weapon
+ * @param ammo the ammunition object
+ * @return a `WeaponData` object
+ */
+ def apply(unk1 : Int, unk2 : Int, fire_mode : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : AmmoBoxData) : WeaponData =
+ new WeaponData(unk1, unk2, fire_mode, InternalSlot(cls, guid, parentSlot, ammo))
implicit val codec : Codec[WeaponData] = (
- ("unk" | uint4L) ::
- uint4L ::
- uint24 ::
- uint16L ::
- uint2 ::
- uint8 :: //size = 1 type of ammunition loaded
+ ("unk1" | uint4L) ::
+ ("unk2" | uint4L) ::
+ uint(20) ::
+ ("fire_mode" | int(3)) ::
+ bool ::
+ bool ::
+ uint8L :: //size = 1 type of ammunition loaded
uint2 ::
("ammo" | InternalSlot.codec) ::
bool
).exmap[WeaponData] (
{
- case code :: 8 :: 2 :: 0 :: 3 :: 1 :: 0 :: ammo :: false :: HNil =>
- Attempt.successful(WeaponData(code, ammo))
- case code :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil =>
+ case unk1 :: unk2 :: 0 :: fmode :: false :: true :: 1 :: 0 :: ammo :: false :: HNil =>
+ Attempt.successful(WeaponData(unk1, unk2, fmode, ammo))
+ case _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil =>
Attempt.failure(Err("invalid weapon data format"))
},
{
- case WeaponData(code, ammo) =>
- Attempt.successful(code :: 8 :: 2 :: 0 :: 3 :: 1 :: 0 :: ammo :: false :: HNil)
- }
- ).as[WeaponData]
-
- /**
- * Transform between WeaponData and ConstructorData.
- */
- val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
- {
- case x =>
- Attempt.successful(Some(x.asInstanceOf[ConstructorData]))
- },
- {
- case Some(x) =>
- Attempt.successful(x.asInstanceOf[WeaponData])
- case _ =>
- Attempt.failure(Err("can not encode weapon data"))
+ case WeaponData(unk1, unk2, fmode, ammo) =>
+ Attempt.successful(unk1 :: unk2 :: 0 :: fmode :: false :: true :: 1 :: 0 :: ammo :: false :: HNil)
}
)
}
diff --git a/common/src/main/scala/net/psforever/types/CharacterGender.scala b/common/src/main/scala/net/psforever/types/CharacterGender.scala
new file mode 100644
index 00000000..8f05bca9
--- /dev/null
+++ b/common/src/main/scala/net/psforever/types/CharacterGender.scala
@@ -0,0 +1,16 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.types
+
+import net.psforever.packet.PacketHelpers
+import scodec.codecs.uint2L
+
+/**
+ * Values for two genders, Male and Female, starting at 1 = Male.
+ */
+object CharacterGender extends Enumeration(1) {
+ type Type = Value
+
+ val Male, Female = Value
+
+ implicit val codec = PacketHelpers.createEnumerationCodec(this, uint2L)
+}
diff --git a/common/src/main/scala/net/psforever/types/ExoSuitType.scala b/common/src/main/scala/net/psforever/types/ExoSuitType.scala
new file mode 100644
index 00000000..dab8b437
--- /dev/null
+++ b/common/src/main/scala/net/psforever/types/ExoSuitType.scala
@@ -0,0 +1,19 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.types
+
+import net.psforever.packet.PacketHelpers
+import scodec.codecs._
+
+/**
+ * Values for the the different types of exo-suits that players can wear.
+ */
+object ExoSuitType extends Enumeration {
+ type Type = Value
+ val Agile,
+ Reinforced,
+ MAX,
+ Infiltration,
+ Standard= Value
+
+ implicit val codec = PacketHelpers.createEnumerationCodec(this, uint(3))
+}
diff --git a/common/src/main/scala/net/psforever/types/GrenadeState.scala b/common/src/main/scala/net/psforever/types/GrenadeState.scala
new file mode 100644
index 00000000..6874b240
--- /dev/null
+++ b/common/src/main/scala/net/psforever/types/GrenadeState.scala
@@ -0,0 +1,21 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.types
+
+import net.psforever.packet.PacketHelpers
+import scodec.codecs._
+
+/**
+ * An `Enumeration` of the kinds of states applicable to the grenade animation.
+ */
+object GrenadeState extends Enumeration(1) {
+ type Type = Value
+
+ val Primed, //avatars and other depicted player characters
+ Thrown, //avatars only
+ None //non-actionable state of rest
+ = Value
+
+ implicit val codec = PacketHelpers.createEnumerationCodec(this, uint8L)
+
+ val codec_2u = PacketHelpers.createEnumerationCodec(this, uint2L)
+}
diff --git a/common/src/test/scala/game/ArmorChangedMessageTest.scala b/common/src/test/scala/game/ArmorChangedMessageTest.scala
index 30c097a4..e587e1f8 100644
--- a/common/src/test/scala/game/ArmorChangedMessageTest.scala
+++ b/common/src/test/scala/game/ArmorChangedMessageTest.scala
@@ -4,6 +4,7 @@ package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
+import net.psforever.types.ExoSuitType
import scodec.bits._
class ArmorChangedMessageTest extends Specification {
@@ -13,7 +14,7 @@ class ArmorChangedMessageTest extends Specification {
PacketCoding.DecodePacket(string).require match {
case ArmorChangedMessage(player_guid, armor, subtype) =>
player_guid mustEqual PlanetSideGUID(273)
- armor mustEqual 2
+ armor mustEqual ExoSuitType.MAX
subtype mustEqual 3
case _ =>
ko
@@ -21,7 +22,7 @@ class ArmorChangedMessageTest extends Specification {
}
"encode" in {
- val msg = ArmorChangedMessage(PlanetSideGUID(273), 2, 3)
+ val msg = ArmorChangedMessage(PlanetSideGUID(273), ExoSuitType.MAX, 3)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
diff --git a/common/src/test/scala/game/AvatarGrenadeStateMessageTest.scala b/common/src/test/scala/game/AvatarGrenadeStateMessageTest.scala
index a2122948..a153af90 100644
--- a/common/src/test/scala/game/AvatarGrenadeStateMessageTest.scala
+++ b/common/src/test/scala/game/AvatarGrenadeStateMessageTest.scala
@@ -4,6 +4,7 @@ package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
+import net.psforever.types.GrenadeState
import scodec.bits._
class AvatarGrenadeStateMessageTest extends Specification {
@@ -13,14 +14,14 @@ class AvatarGrenadeStateMessageTest extends Specification {
PacketCoding.DecodePacket(string).require match {
case AvatarGrenadeStateMessage(player_guid, state) =>
player_guid mustEqual PlanetSideGUID(4570)
- state mustEqual GrenadeState.PRIMED
+ state mustEqual GrenadeState.Primed
case _ =>
ko
}
}
"encode" in {
- val msg = AvatarGrenadeStateMessage(PlanetSideGUID(4570), GrenadeState.PRIMED)
+ val msg = AvatarGrenadeStateMessage(PlanetSideGUID(4570), GrenadeState.Primed)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
diff --git a/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala b/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala
new file mode 100644
index 00000000..4bcebf31
--- /dev/null
+++ b/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala
@@ -0,0 +1,423 @@
+// Copyright (c) 2017 PSForever
+package game
+
+import org.specs2.mutable._
+import net.psforever.packet._
+import net.psforever.packet.game._
+import net.psforever.packet.game.objectcreate._
+import net.psforever.types._
+import scodec.bits._
+
+class ObjectCreateDetailedMessageTest extends Specification {
+ val packet = hex"18 CF 13 00 00 BC 87 00 0A F0 16 C3 43 A1 30 90 00 02 C0 40 00 08 70 43 00 68 00 6F 00 72 00 64 00 54 00 52 00 82 65 1F F5 9E 80 80 00 00 00 00 00 3F FF C0 00 00 00 20 00 00 00 20 27 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FC CC 10 00 03 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 00 00 00 01 00 7E C8 00 C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 C0 00 42 C5 46 86 C7 00 00 02 A0 00 00 12 60 78 70 65 5F 77 61 72 70 5F 67 61 74 65 5F 75 73 61 67 65 92 78 70 65 5F 69 6E 73 74 61 6E 74 5F 61 63 74 69 6F 6E 92 78 70 65 5F 73 61 6E 63 74 75 61 72 79 5F 68 65 6C 70 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 32 8E 78 70 65 5F 66 6F 72 6D 5F 73 71 75 61 64 8E 78 70 65 5F 74 68 5F 6E 6F 6E 73 61 6E 63 8B 78 70 65 5F 74 68 5F 61 6D 6D 6F 90 78 70 65 5F 74 68 5F 66 69 72 65 6D 6F 64 65 73 8F 75 73 65 64 5F 63 68 61 69 6E 62 6C 61 64 65 9A 76 69 73 69 74 65 64 5F 62 72 6F 61 64 63 61 73 74 5F 77 61 72 70 67 61 74 65 8E 76 69 73 69 74 65 64 5F 6C 6F 63 6B 65 72 8D 75 73 65 64 5F 70 75 6E 69 73 68 65 72 88 75 73 65 64 5F 72 65 6B 8D 75 73 65 64 5F 72 65 70 65 61 74 65 72 9F 76 69 73 69 74 65 64 5F 64 65 63 6F 6E 73 74 72 75 63 74 69 6F 6E 5F 74 65 72 6D 69 6E 61 6C 8F 75 73 65 64 5F 73 75 70 70 72 65 73 73 6F 72 96 76 69 73 69 74 65 64 5F 6F 72 64 65 72 5F 74 65 72 6D 69 6E 61 6C 85 6D 61 70 31 35 85 6D 61 70 31 34 85 6D 61 70 31 32 85 6D 61 70 30 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 0A 36 13 88 04 00 40 00 00 10 00 04 00 00 4D 6E 40 10 41 00 00 00 40 00 18 08 38 1C C0 20 32 00 00 07 80 15 E1 D0 02 10 20 00 00 08 00 03 01 07 13 A8 04 06 40 00 00 10 03 20 BB 00 42 E4 00 00 01 00 0E 07 70 08 6C 80 00 06 40 01 C0 F0 01 13 90 00 00 C8 00 38 1E 40 23 32 00 00 19 00 07 03 D0 05 0E 40 00 03 20 00 E8 7B 00 A4 C8 00 00 64 00 DA 4F 80 14 E1 00 00 00 40 00 18 08 38 1F 40 20 32 00 00 0A 00 08 " //fake data?
+ val packet2 = hex"18 F8 00 00 00 BC 8C 10 90 3B 45 C6 FA 94 00 9F F0 00 00 40 00 08 C0 44 00 69 00 66 00 66 00 45" //fake data
+ //val packet2Rest = packet2.bits.drop(8 + 32 + 1 + 11 + 16)
+ var string_inventoryItem = hex"46 04 C0 08 08 80 00 00 20 00 0C 04 10 29 A0 10 19 00 00 04 00 00"
+ val string_detonater = hex"18 87000000 6506 EA8 7420 80 8000000200008"
+ val string_ace = hex"18 87000000 1006 100 C70B 80 8800000200008"
+ val string_9mm = hex"18 7C000000 2580 0E0 0005 A1 C8000064000"
+ val string_gauss = hex"18 DC000000 2580 2C9 B905 82 480000020000C04 1C00C0B0190000078000"
+ val string_punisher = hex"18 27010000 2580 612 a706 82 080000020000c08 1c13a0d01900000780 13a4701a072000000800"
+ val string_rek = hex"18 97000000 2580 6C2 9F05 81 48000002000080000"
+ val string_boomer_trigger = hex"18 87000000 6304CA8760B 80 C800000200008"
+ val string_testchar = hex"18 570C0000 BC8 4B00 6C2D7 65535 CA16 0 00 01 34 40 00 0970 49006C006C006C004900490049006C006C006C0049006C0049006C006C0049006C006C006C0049006C006C004900 84 52 70 76 1E 80 80 00 00 00 00 00 3FFFC 0 00 00 00 20 00 00 0F F6 A7 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 64 00 00 01 00 7E C8 00 C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 C0 00 42 C5 46 86 C7 00 00 00 80 00 00 12 40 78 70 65 5F 73 61 6E 63 74 75 61 72 79 5F 68 65 6C 70 90 78 70 65 5F 74 68 5F 66 69 72 65 6D 6F 64 65 73 8B 75 73 65 64 5F 62 65 61 6D 65 72 85 6D 61 70 31 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 0A 23 02 60 04 04 40 00 00 10 00 06 02 08 14 D0 08 0C 80 00 02 00 02 6B 4E 00 82 88 00 00 02 00 00 C0 41 C0 9E 01 01 90 00 00 64 00 44 2A 00 10 91 00 00 00 40 00 18 08 38 94 40 20 32 00 00 00 80 19 05 48 02 17 20 00 00 08 00 70 29 80 43 64 00 00 32 00 0E 05 40 08 9C 80 00 06 40 01 C0 AA 01 19 90 00 00 C8 00 3A 15 80 28 72 00 00 19 00 04 0A B8 05 26 40 00 03 20 06 C2 58 00 A7 88 00 00 02 00 00 80 00 00"
+
+ "decode (2)" in {
+ //an invalid bit representation will fail to turn into an object
+ PacketCoding.DecodePacket(packet2).require match {
+ case ObjectCreateDetailedMessage(len, cls, guid, parent, data) =>
+ len mustEqual 248
+ cls mustEqual ObjectClass.avatar
+ guid mustEqual PlanetSideGUID(2497)
+ parent mustEqual None
+ data.isDefined mustEqual false
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (detonater)" in {
+ PacketCoding.DecodePacket(string_detonater).require match {
+ case ObjectCreateDetailedMessage(len, cls, guid, parent, data) =>
+ len mustEqual 135
+ cls mustEqual ObjectClass.command_detonater
+ guid mustEqual PlanetSideGUID(8308)
+ parent.isDefined mustEqual true
+ parent.get.guid mustEqual PlanetSideGUID(3530)
+ parent.get.slot mustEqual 0
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[DetailedCommandDetonaterData] mustEqual true
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (ace)" in {
+ PacketCoding.DecodePacket(string_ace).require match {
+ case ObjectCreateDetailedMessage(len, cls, guid, parent, data) =>
+ len mustEqual 135
+ cls mustEqual ObjectClass.ace
+ guid mustEqual PlanetSideGUID(3015)
+ parent.isDefined mustEqual true
+ parent.get.guid mustEqual PlanetSideGUID(3104)
+ parent.get.slot mustEqual 0
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[DetailedACEData] mustEqual true
+ data.get.asInstanceOf[DetailedACEData].unk mustEqual 8
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (9mm)" in {
+ PacketCoding.DecodePacket(string_9mm).require match {
+ case ObjectCreateDetailedMessage(len, cls, guid, parent, data) =>
+ len mustEqual 124
+ cls mustEqual ObjectClass.bullet_9mm
+ guid mustEqual PlanetSideGUID(1280)
+ parent.isDefined mustEqual true
+ parent.get.guid mustEqual PlanetSideGUID(75)
+ parent.get.slot mustEqual 33
+ data.isDefined mustEqual true
+ data.get.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (gauss)" in {
+ PacketCoding.DecodePacket(string_gauss).require match {
+ case ObjectCreateDetailedMessage(len, cls, guid, parent, data) =>
+ len mustEqual 220
+ cls mustEqual ObjectClass.gauss
+ guid mustEqual PlanetSideGUID(1465)
+ parent.isDefined mustEqual true
+ parent.get.guid mustEqual PlanetSideGUID(75)
+ parent.get.slot mustEqual 2
+ data.isDefined mustEqual true
+ val obj_wep = data.get.asInstanceOf[DetailedWeaponData]
+ obj_wep.unk mustEqual 4
+ val obj_ammo = obj_wep.ammo
+ obj_ammo.objectClass mustEqual 28
+ obj_ammo.guid mustEqual PlanetSideGUID(1286)
+ obj_ammo.parentSlot mustEqual 0
+ obj_ammo.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 30
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (punisher)" in {
+ PacketCoding.DecodePacket(string_punisher).require match {
+ case ObjectCreateDetailedMessage(len, cls, guid, parent, data) =>
+ len mustEqual 295
+ cls mustEqual ObjectClass.punisher
+ guid mustEqual PlanetSideGUID(1703)
+ parent.isDefined mustEqual true
+ parent.get.guid mustEqual PlanetSideGUID(75)
+ parent.get.slot mustEqual 2
+ data.isDefined mustEqual true
+ val obj_wep = data.get.asInstanceOf[DetailedConcurrentFeedWeaponData]
+ obj_wep.unk1 mustEqual 0
+ obj_wep.unk2 mustEqual 8
+ val obj_ammo = obj_wep.ammo
+ obj_ammo.size mustEqual 2
+ obj_ammo.head.objectClass mustEqual ObjectClass.bullet_9mm
+ obj_ammo.head.guid mustEqual PlanetSideGUID(1693)
+ obj_ammo.head.parentSlot mustEqual 0
+ obj_ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 30
+ obj_ammo(1).objectClass mustEqual ObjectClass.jammer_cartridge
+ obj_ammo(1).guid mustEqual PlanetSideGUID(1564)
+ obj_ammo(1).parentSlot mustEqual 1
+ obj_ammo(1).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (rek)" in {
+ PacketCoding.DecodePacket(string_rek).require match {
+ case ObjectCreateDetailedMessage(len, cls, guid, parent, data) =>
+ len mustEqual 151
+ cls mustEqual ObjectClass.remote_electronics_kit
+ guid mustEqual PlanetSideGUID(1439)
+ parent.isDefined mustEqual true
+ parent.get.guid mustEqual PlanetSideGUID(75)
+ parent.get.slot mustEqual 1
+ data.isDefined mustEqual true
+ data.get.asInstanceOf[DetailedREKData].unk mustEqual 4
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (boomer trigger)" in {
+ PacketCoding.DecodePacket(string_boomer_trigger).require match {
+ case ObjectCreateDetailedMessage(len, cls, guid, parent, data) =>
+ len mustEqual 135
+ cls mustEqual ObjectClass.boomer_trigger
+ guid mustEqual PlanetSideGUID(2934)
+ parent.isDefined mustEqual true
+ parent.get.guid mustEqual PlanetSideGUID(2502)
+ parent.get.slot mustEqual 0
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[DetailedBoomerTriggerData] mustEqual true
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (character)" in {
+ PacketCoding.DecodePacket(string_testchar).require match {
+ case ObjectCreateDetailedMessage(len, cls, guid, parent, data) =>
+ len mustEqual 3159
+ cls mustEqual ObjectClass.avatar
+ guid mustEqual PlanetSideGUID(75)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ val char = data.get.asInstanceOf[DetailedCharacterData]
+ char.appearance.pos.coord.x mustEqual 3674.8438f
+ char.appearance.pos.coord.y mustEqual 2726.789f
+ char.appearance.pos.coord.z mustEqual 91.15625f
+ char.appearance.pos.roll mustEqual 0
+ char.appearance.pos.pitch mustEqual 0
+ char.appearance.pos.yaw mustEqual 19
+ char.appearance.basic_appearance.name mustEqual "IlllIIIlllIlIllIlllIllI"
+ char.appearance.basic_appearance.faction mustEqual PlanetSideEmpire.VS
+ char.appearance.basic_appearance.sex mustEqual CharacterGender.Female
+ char.appearance.basic_appearance.head mustEqual 41
+ char.appearance.basic_appearance.voice mustEqual 1 //female 1
+ char.appearance.voice2 mustEqual 3
+ char.appearance.black_ops mustEqual false
+ char.appearance.jammered mustEqual false
+ char.appearance.exosuit mustEqual ExoSuitType.Standard
+ char.appearance.outfit_name mustEqual ""
+ char.appearance.outfit_logo mustEqual 0
+ char.appearance.backpack mustEqual false
+ char.appearance.facingPitch mustEqual 127
+ char.appearance.facingYawUpper mustEqual 181
+ char.appearance.lfs mustEqual true
+ char.appearance.grenade_state mustEqual GrenadeState.None
+ char.appearance.is_cloaking mustEqual false
+ char.appearance.charging_pose mustEqual false
+ char.appearance.on_zipline mustEqual false
+ char.appearance.ribbons.upper mustEqual 0xFFFFFFFFL //none
+ char.appearance.ribbons.middle mustEqual 0xFFFFFFFFL //none
+ char.appearance.ribbons.lower mustEqual 0xFFFFFFFFL //none
+ char.appearance.ribbons.tos mustEqual 0xFFFFFFFFL //none
+ char.healthMax mustEqual 100
+ char.health mustEqual 100
+ char.armor mustEqual 50 //standard exosuit value
+ char.unk1 mustEqual 1
+ char.unk2 mustEqual 7
+ char.unk3 mustEqual 7
+ char.staminaMax mustEqual 100
+ char.stamina mustEqual 100
+ char.unk4 mustEqual 28
+ char.unk5 mustEqual 4
+ char.unk6 mustEqual 44
+ char.unk7 mustEqual 84
+ char.unk8 mustEqual 104
+ char.unk9 mustEqual 1900
+ char.firstTimeEvents.size mustEqual 4
+ char.firstTimeEvents.head mustEqual "xpe_sanctuary_help"
+ char.firstTimeEvents(1) mustEqual "xpe_th_firemodes"
+ char.firstTimeEvents(2) mustEqual "used_beamer"
+ char.firstTimeEvents(3) mustEqual "map13"
+ char.tutorials.size mustEqual 0
+ char.inventory.isDefined mustEqual true
+ val inventory = char.inventory.get.contents
+ inventory.size mustEqual 10
+ //0
+ inventory.head.item.objectClass mustEqual ObjectClass.beamer
+ inventory.head.item.guid mustEqual PlanetSideGUID(76)
+ inventory.head.item.parentSlot mustEqual 0
+ var wep = inventory.head.item.obj.asInstanceOf[DetailedWeaponData]
+ wep.ammo.objectClass mustEqual ObjectClass.energy_cell
+ wep.ammo.guid mustEqual PlanetSideGUID(77)
+ wep.ammo.parentSlot mustEqual 0
+ wep.ammo.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 16
+ //1
+ inventory(1).item.objectClass mustEqual ObjectClass.suppressor
+ inventory(1).item.guid mustEqual PlanetSideGUID(78)
+ inventory(1).item.parentSlot mustEqual 2
+ wep = inventory(1).item.obj.asInstanceOf[DetailedWeaponData]
+ wep.ammo.objectClass mustEqual ObjectClass.bullet_9mm
+ wep.ammo.guid mustEqual PlanetSideGUID(79)
+ wep.ammo.parentSlot mustEqual 0
+ wep.ammo.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 25
+ //2
+ inventory(2).item.objectClass mustEqual ObjectClass.forceblade
+ inventory(2).item.guid mustEqual PlanetSideGUID(80)
+ inventory(2).item.parentSlot mustEqual 4
+ wep = inventory(2).item.obj.asInstanceOf[DetailedWeaponData]
+ wep.ammo.objectClass mustEqual ObjectClass.melee_ammo
+ wep.ammo.guid mustEqual PlanetSideGUID(81)
+ wep.ammo.parentSlot mustEqual 0
+ wep.ammo.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1
+ //3
+ inventory(3).item.objectClass mustEqual ObjectClass.locker_container
+ inventory(3).item.guid mustEqual PlanetSideGUID(82)
+ inventory(3).item.parentSlot mustEqual 5
+ inventory(3).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1
+ //4
+ inventory(4).item.objectClass mustEqual ObjectClass.bullet_9mm
+ inventory(4).item.guid mustEqual PlanetSideGUID(83)
+ inventory(4).item.parentSlot mustEqual 6
+ inventory(4).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
+ //5
+ inventory(5).item.objectClass mustEqual ObjectClass.bullet_9mm
+ inventory(5).item.guid mustEqual PlanetSideGUID(84)
+ inventory(5).item.parentSlot mustEqual 9
+ inventory(5).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
+ //6
+ inventory(6).item.objectClass mustEqual ObjectClass.bullet_9mm
+ inventory(6).item.guid mustEqual PlanetSideGUID(85)
+ inventory(6).item.parentSlot mustEqual 12
+ inventory(6).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
+ //7
+ inventory(7).item.objectClass mustEqual ObjectClass.bullet_9mm_AP
+ inventory(7).item.guid mustEqual PlanetSideGUID(86)
+ inventory(7).item.parentSlot mustEqual 33
+ inventory(7).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
+ //8
+ inventory(8).item.objectClass mustEqual ObjectClass.energy_cell
+ inventory(8).item.guid mustEqual PlanetSideGUID(87)
+ inventory(8).item.parentSlot mustEqual 36
+ inventory(8).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
+ //9
+ inventory(9).item.objectClass mustEqual ObjectClass.remote_electronics_kit
+ inventory(9).item.guid mustEqual PlanetSideGUID(88)
+ inventory(9).item.parentSlot mustEqual 39
+ //the rek has data but none worth testing here
+ char.drawn_slot mustEqual DrawnSlot.Pistol1
+ case _ =>
+ ko
+ }
+ }
+
+ "encode (2)" in {
+ //the lack of an object will fail to turn into a bad bitstream
+ val msg = ObjectCreateDetailedMessage(0L, ObjectClass.avatar, PlanetSideGUID(2497), None, None)
+ PacketCoding.EncodePacket(msg).isFailure mustEqual true
+ }
+
+ "encode (detonater)" in {
+ val obj = DetailedCommandDetonaterData()
+ val msg = ObjectCreateDetailedMessage(ObjectClass.command_detonater, PlanetSideGUID(8308), ObjectCreateMessageParent(PlanetSideGUID(3530), 0), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_detonater
+ }
+
+ "encode (ace)" in {
+ val obj = DetailedACEData(8)
+ val msg = ObjectCreateDetailedMessage(ObjectClass.ace, PlanetSideGUID(3015), ObjectCreateMessageParent(PlanetSideGUID(3104), 0), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_ace
+ }
+
+ "encode (9mm)" in {
+ val obj = DetailedAmmoBoxData(8, 50)
+ val msg = ObjectCreateDetailedMessage(ObjectClass.bullet_9mm, PlanetSideGUID(1280), ObjectCreateMessageParent(PlanetSideGUID(75), 33), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_9mm
+ }
+
+ "encode (gauss)" in {
+ val obj = DetailedWeaponData(4, ObjectClass.bullet_9mm, PlanetSideGUID(1286), 0, DetailedAmmoBoxData(8, 30))
+ val msg = ObjectCreateDetailedMessage(ObjectClass.gauss, PlanetSideGUID(1465), ObjectCreateMessageParent(PlanetSideGUID(75), 2), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_gauss
+ }
+
+ "encode (punisher)" in {
+ val obj = DetailedConcurrentFeedWeaponData(0, 8, DetailedAmmoBoxData(ObjectClass.bullet_9mm, PlanetSideGUID(1693), 0, DetailedAmmoBoxData(8, 30)) :: DetailedAmmoBoxData(ObjectClass.jammer_cartridge, PlanetSideGUID(1564), 1, DetailedAmmoBoxData(8, 1)) :: Nil)
+ val msg = ObjectCreateDetailedMessage(ObjectClass.punisher, PlanetSideGUID(1703), ObjectCreateMessageParent(PlanetSideGUID(75), 2), obj)
+ var pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_punisher
+ }
+
+ "encode (rek)" in {
+ val obj = DetailedREKData(4)
+ val msg = ObjectCreateDetailedMessage(ObjectClass.remote_electronics_kit, PlanetSideGUID(1439), ObjectCreateMessageParent(PlanetSideGUID(75), 1), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_rek
+ }
+
+ "encode (boomer trigger)" in {
+ val obj = DetailedBoomerTriggerData()
+ val msg = ObjectCreateDetailedMessage(ObjectClass.boomer_trigger, PlanetSideGUID(2934), ObjectCreateMessageParent(PlanetSideGUID(2502), 0), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_boomer_trigger
+ }
+
+ "encode (character)" in {
+ val app = CharacterAppearanceData(
+ PlacementData(
+ Vector3(3674.8438f, 2726.789f, 91.15625f),
+ 0, 0,
+ 19
+ ),
+ BasicCharacterData(
+ "IlllIIIlllIlIllIlllIllI",
+ PlanetSideEmpire.VS,
+ CharacterGender.Female,
+ 41,
+ 1
+ ),
+ 3,
+ false,
+ false,
+ ExoSuitType.Standard,
+ "",
+ 0,
+ false,
+ 127, 181,
+ true,
+ GrenadeState.None,
+ false,
+ false,
+ false,
+ RibbonBars()
+ )
+ val inv = InventoryItem(ObjectClass.beamer, PlanetSideGUID(76), 0, DetailedWeaponData(8, ObjectClass.energy_cell, PlanetSideGUID(77), 0, DetailedAmmoBoxData(8, 16))) ::
+ InventoryItem(ObjectClass.suppressor, PlanetSideGUID(78), 2, DetailedWeaponData(8, ObjectClass.bullet_9mm, PlanetSideGUID(79), 0, DetailedAmmoBoxData(8, 25))) ::
+ InventoryItem(ObjectClass.forceblade, PlanetSideGUID(80), 4, DetailedWeaponData(8, ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1))) ::
+ InventoryItem(ObjectClass.locker_container, PlanetSideGUID(82), 5, DetailedAmmoBoxData(8, 1)) ::
+ InventoryItem(ObjectClass.bullet_9mm, PlanetSideGUID(83), 6, DetailedAmmoBoxData(8, 50)) ::
+ InventoryItem(ObjectClass.bullet_9mm, PlanetSideGUID(84), 9, DetailedAmmoBoxData(8, 50)) ::
+ InventoryItem(ObjectClass.bullet_9mm, PlanetSideGUID(85), 12, DetailedAmmoBoxData(8, 50)) ::
+ InventoryItem(ObjectClass.bullet_9mm_AP, PlanetSideGUID(86), 33, DetailedAmmoBoxData(8, 50)) ::
+ InventoryItem(ObjectClass.energy_cell, PlanetSideGUID(87), 36, DetailedAmmoBoxData(8, 50)) ::
+ InventoryItem(ObjectClass.remote_electronics_kit, PlanetSideGUID(88), 39, DetailedREKData(8)) ::
+ Nil
+ val obj = DetailedCharacterData(
+ app,
+ 100, 100,
+ 50,
+ 1, 7, 7,
+ 100, 100,
+ 28, 4, 44, 84, 104, 1900,
+ "xpe_sanctuary_help" :: "xpe_th_firemodes" :: "used_beamer" :: "map13" :: Nil,
+ List.empty,
+ InventoryData(inv),
+ DrawnSlot.Pistol1
+ )
+ val msg = ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ val pkt_bitv = pkt.toBitVector
+ val ori_bitv = string_testchar.toBitVector
+ pkt_bitv.take(153) mustEqual ori_bitv.take(153) //skip 1
+ pkt_bitv.drop(154).take(422) mustEqual ori_bitv.drop(154).take(422) //skip 126
+ pkt_bitv.drop(702) mustEqual ori_bitv.drop(702)
+ //TODO work on DetailedCharacterData to make this pass as a single stream
+ }
+}
diff --git a/common/src/test/scala/game/ObjectCreateMessageTest.scala b/common/src/test/scala/game/ObjectCreateMessageTest.scala
index 6fff42b3..b60bb811 100644
--- a/common/src/test/scala/game/ObjectCreateMessageTest.scala
+++ b/common/src/test/scala/game/ObjectCreateMessageTest.scala
@@ -1,327 +1,1184 @@
// Copyright (c) 2017 PSForever
package game
-import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.packet.game.objectcreate._
-import net.psforever.types.{PlanetSideEmpire, Vector3}
+import net.psforever.types.{CharacterGender, ExoSuitType, GrenadeState, PlanetSideEmpire, Vector3}
+import org.specs2.mutable._
import scodec.bits._
class ObjectCreateMessageTest extends Specification {
- val packet = hex"18 CF 13 00 00 BC 87 00 0A F0 16 C3 43 A1 30 90 00 02 C0 40 00 08 70 43 00 68 00 6F 00 72 00 64 00 54 00 52 00 82 65 1F F5 9E 80 80 00 00 00 00 00 3F FF C0 00 00 00 20 00 00 00 20 27 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FC CC 10 00 03 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 00 00 00 01 00 7E C8 00 C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 C0 00 42 C5 46 86 C7 00 00 02 A0 00 00 12 60 78 70 65 5F 77 61 72 70 5F 67 61 74 65 5F 75 73 61 67 65 92 78 70 65 5F 69 6E 73 74 61 6E 74 5F 61 63 74 69 6F 6E 92 78 70 65 5F 73 61 6E 63 74 75 61 72 79 5F 68 65 6C 70 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 32 8E 78 70 65 5F 66 6F 72 6D 5F 73 71 75 61 64 8E 78 70 65 5F 74 68 5F 6E 6F 6E 73 61 6E 63 8B 78 70 65 5F 74 68 5F 61 6D 6D 6F 90 78 70 65 5F 74 68 5F 66 69 72 65 6D 6F 64 65 73 8F 75 73 65 64 5F 63 68 61 69 6E 62 6C 61 64 65 9A 76 69 73 69 74 65 64 5F 62 72 6F 61 64 63 61 73 74 5F 77 61 72 70 67 61 74 65 8E 76 69 73 69 74 65 64 5F 6C 6F 63 6B 65 72 8D 75 73 65 64 5F 70 75 6E 69 73 68 65 72 88 75 73 65 64 5F 72 65 6B 8D 75 73 65 64 5F 72 65 70 65 61 74 65 72 9F 76 69 73 69 74 65 64 5F 64 65 63 6F 6E 73 74 72 75 63 74 69 6F 6E 5F 74 65 72 6D 69 6E 61 6C 8F 75 73 65 64 5F 73 75 70 70 72 65 73 73 6F 72 96 76 69 73 69 74 65 64 5F 6F 72 64 65 72 5F 74 65 72 6D 69 6E 61 6C 85 6D 61 70 31 35 85 6D 61 70 31 34 85 6D 61 70 31 32 85 6D 61 70 30 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 0A 36 13 88 04 00 40 00 00 10 00 04 00 00 4D 6E 40 10 41 00 00 00 40 00 18 08 38 1C C0 20 32 00 00 07 80 15 E1 D0 02 10 20 00 00 08 00 03 01 07 13 A8 04 06 40 00 00 10 03 20 BB 00 42 E4 00 00 01 00 0E 07 70 08 6C 80 00 06 40 01 C0 F0 01 13 90 00 00 C8 00 38 1E 40 23 32 00 00 19 00 07 03 D0 05 0E 40 00 03 20 00 E8 7B 00 A4 C8 00 00 64 00 DA 4F 80 14 E1 00 00 00 40 00 18 08 38 1F 40 20 32 00 00 0A 00 08 " //fake data?
- val packet2 = hex"18 F8 00 00 00 BC 8C 10 90 3B 45 C6 FA 94 00 9F F0 00 00 40 00 08 C0 44 00 69 00 66 00 66 00 45" //fake data
- //val packet2Rest = packet2.bits.drop(8 + 32 + 1 + 11 + 16)
- var string_inventoryItem = hex"46 04 C0 08 08 80 00 00 20 00 0C 04 10 29 A0 10 19 00 00 04 00 00"
- val string_9mm = hex"18 7C000000 2580 0E0 0005 A1 C8000064000"
- val string_gauss = hex"18 DC000000 2580 2C9 B905 82 480000020000C04 1C00C0B0190000078000"
- val string_punisher = hex"18 27010000 2580 612 a706 82 080000020000c08 1c13a0d01900000780 13a4701a072000000800"
- val string_rek = hex"18 97000000 2580 6C2 9F05 81 48000002000080000"
- val string_testchar = hex"18 570C0000 BC8 4B00 6C2D7 65535 CA16 0 00 01 34 40 00 0970 49006C006C006C004900490049006C006C006C0049006C0049006C006C0049006C006C006C0049006C006C004900 84 52 70 76 1E 80 80 00 00 00 00 00 3FFFC 0 00 00 00 20 00 00 0F F6 A7 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 64 00 00 01 00 7E C8 00 C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 C0 00 42 C5 46 86 C7 00 00 00 80 00 00 12 40 78 70 65 5F 73 61 6E 63 74 75 61 72 79 5F 68 65 6C 70 90 78 70 65 5F 74 68 5F 66 69 72 65 6D 6F 64 65 73 8B 75 73 65 64 5F 62 65 61 6D 65 72 85 6D 61 70 31 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 0A 23 02 60 04 04 40 00 00 10 00 06 02 08 14 D0 08 0C 80 00 02 00 02 6B 4E 00 82 88 00 00 02 00 00 C0 41 C0 9E 01 01 90 00 00 64 00 44 2A 00 10 91 00 00 00 40 00 18 08 38 94 40 20 32 00 00 00 80 19 05 48 02 17 20 00 00 08 00 70 29 80 43 64 00 00 32 00 0E 05 40 08 9C 80 00 06 40 01 C0 AA 01 19 90 00 00 C8 00 3A 15 80 28 72 00 00 19 00 04 0A B8 05 26 40 00 03 20 06 C2 58 00 A7 88 00 00 02 00 00 80 00 00"
+ val string_striker_projectile = hex"17 C5000000 A4B 009D 4C129 0CB0A 9814 00 F5 E3 040000666686400"
+ val string_implant_interface = hex"17 6C000000 01014C93304818000000"
+ val string_order_terminala = hex"17 A5000000 B2AF30EACF1889F7A3D1200007D2000000"
+ val string_ace_held = hex"17 76000000 0406900650C80480000000"
+ val string_boomertrigger = hex"17 76000000 58084A8100E80C00000000" //reconstructed from an inventory entry
+ val string_detonater_held = hex"17 76000000 1A886A8421080400000000"
+ val string_lasher_held = hex"17 BB000000 1688569D90B83 880000008082077036032000000"
+ val string_punisher_held = hex"17 F6000000 0A06612331083 88000000810381383E03200003793287C0E400000"
+ val string_rek_held = hex"17 86000000 27086C2350F800800000000000"
+ val string_captureflag = hex"17 E5000000 CE8EA10 04A47 B818A FE0E 00 00 0F 24000015000400160B09000" //LLU for Qumu on Amerish
+ val string_ace_dropped = hex"17 AF000000 90024113B329C5D5A2D1200005B440000000"
+ val string_detonater_dropped = hex"17 AF000000 EA8620ED1549B4B6A741500001B000000000"
+ val string_shotgunshell_dropped = hex"17 A5000000 F9A7D0D 5E269 BED5A F114 0000596000000"
+ val string_lasher_dropped = hex"17 F4000000 D69020C 99299 85D0A 5F10 00 00 20 400000004041038819018000000"
+ val string_punisher_dropped = hex"17 2F010000 E12A20B 915A9 28C9A 1412 00 00 33 200000004081C1901B01800001BCB5C2E07000000"
+ val string_rek_dropped = hex"17 BF000000 EC20311 85219 7AC1A 2D12 00 00 4E 4000000001800"
+ val string_boomer = hex"17 A5000000 CA0000F1630938D5A8F1400003F0031100"
+ val string_spitfire_short = hex"17 BB000000 9D37010 E4F08 6AFCA 0312 00 7F 42 2C1F0F0000F00"
+ val string_spitfire = hex"17 4F010000 9D3A910 D1D78 AE3FC 9111 00 00 69 4488107F80F2021DBF80B80C80000008086EDB83A03200000"
+ val string_trap = hex"17 BB000000 A8B630A 39FA6 FD666 801C 00 00 00 44C6097F80F00"
+ val string_aegis = hex"17 10010000 F80FC09 9DF96 0C676 801C 00 00 00 443E09FF0000000000000000000000000"
+ val string_orion = hex"17 5E010000 D82640B 92F76 01D65 F611 00 00 5E 4400006304BFC1E4041826E1503900000010104CE704C06400000"
+ val string_locker_container = hex"17 AF010000 E414C0C00000000000000000000600000818829DC2E030000000202378620D80C00000378FA0FADC000006F1FC199D800000"
+ val string_character = hex"17 73070000 BC8 3E0F 6C2D7 65535 CA16 00 00 09 9741E4F804000000 234530063007200610077006E00790052006F006E006E0069006500 220B7 E67B540404001000000000022B50100 268042006C00610063006B002000420065007200650074002000410072006D006F007500720065006400200043006F00720070007300 1700E0030050040003BC00000234040001A004000 3FFF67A8F A0A5424E0E800000000080952A9C3A03000001081103E040000000A023782F1080C0000016244108200000000808382403A030000014284C3A0C0000000202512F00B80C00000578F80F840000000280838B3C320300000080"
+ val string_character_backpack = hex"17 9C030000 BC8 340D F20A9 3956C AF0D 00 00 73 480000 87041006E00670065006C006C006F00 4A148 0000000000000000000000005C54200 24404F0072006900670069006E0061006C00200044006900730074007200690063007400 1740180181E8000000C202000042000000D202000000010A3C00"
- "decode (2)" in {
- //an invalid bit representation will fail to turn into an object
- PacketCoding.DecodePacket(packet2).require match {
+ "deocde (striker projectile)" in {
+ PacketCoding.DecodePacket(string_striker_projectile).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
- len mustEqual 248
- cls mustEqual 121
- guid mustEqual PlanetSideGUID(2497)
- parent mustEqual None
- data.isDefined mustEqual false
- case _ =>
- ko
- }
- }
-
- "decode (9mm)" in {
- PacketCoding.DecodePacket(string_9mm).require match {
- case ObjectCreateMessage(len, cls, guid, parent, data) =>
- len mustEqual 124
- cls mustEqual 28
- guid mustEqual PlanetSideGUID(1280)
- parent.isDefined mustEqual true
- parent.get.guid mustEqual PlanetSideGUID(75)
- parent.get.slot mustEqual 33
- data.isDefined mustEqual true
- data.get.asInstanceOf[AmmoBoxData].magazine mustEqual 50
- case _ =>
- ko
- }
- }
-
- "decode (gauss)" in {
- PacketCoding.DecodePacket(string_gauss).require match {
- case ObjectCreateMessage(len, cls, guid, parent, data) =>
- len mustEqual 220
- cls mustEqual 345
- guid mustEqual PlanetSideGUID(1465)
- parent.isDefined mustEqual true
- parent.get.guid mustEqual PlanetSideGUID(75)
- parent.get.slot mustEqual 2
- data.isDefined mustEqual true
- val obj_wep = data.get.asInstanceOf[WeaponData]
- obj_wep.unk mustEqual 4
- val obj_ammo = obj_wep.ammo
- obj_ammo.objectClass mustEqual 28
- obj_ammo.guid mustEqual PlanetSideGUID(1286)
- obj_ammo.parentSlot mustEqual 0
- obj_ammo.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 30
- case _ =>
- ko
- }
- }
-
- "decode (punisher)" in {
- PacketCoding.DecodePacket(string_punisher).require match {
- case ObjectCreateMessage(len, cls, guid, parent, data) =>
- len mustEqual 295
- cls mustEqual 706
- guid mustEqual PlanetSideGUID(1703)
- parent.isDefined mustEqual true
- parent.get.guid mustEqual PlanetSideGUID(75)
- parent.get.slot mustEqual 2
- data.isDefined mustEqual true
- val obj_wep = data.get.asInstanceOf[ConcurrentFeedWeaponData]
- obj_wep.unk mustEqual 0
- val obj_ammo = obj_wep.ammo
- obj_ammo.size mustEqual 2
- obj_ammo.head.objectClass mustEqual 28
- obj_ammo.head.guid mustEqual PlanetSideGUID(1693)
- obj_ammo.head.parentSlot mustEqual 0
- obj_ammo.head.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 30
- obj_ammo(1).objectClass mustEqual 413
- obj_ammo(1).guid mustEqual PlanetSideGUID(1564)
- obj_ammo(1).parentSlot mustEqual 1
- obj_ammo(1).obj.asInstanceOf[AmmoBoxData].magazine mustEqual 1
- case _ =>
- ko
- }
- }
-
- "decode (rek)" in {
- PacketCoding.DecodePacket(string_rek).require match {
- case ObjectCreateMessage(len, cls, guid, parent, data) =>
- len mustEqual 151
- cls mustEqual 0x2D8
- guid mustEqual PlanetSideGUID(1439)
- parent.isDefined mustEqual true
- parent.get.guid mustEqual PlanetSideGUID(75)
- parent.get.slot mustEqual 1
- data.isDefined mustEqual true
- data.get.asInstanceOf[REKData].unk mustEqual 4
- case _ =>
- ko
- }
- }
-
- "decode (character)" in {
- PacketCoding.DecodePacket(string_testchar).require match {
- case ObjectCreateMessage(len, cls, guid, parent, data) =>
- len mustEqual 3159
- cls mustEqual 0x79
- guid mustEqual PlanetSideGUID(75)
+ len mustEqual 197
+ cls mustEqual ObjectClass.striker_missile_targeting_projectile
+ guid mustEqual PlanetSideGUID(40192)
parent.isDefined mustEqual false
data.isDefined mustEqual true
-
- val char = data.get.asInstanceOf[CharacterData]
- char.appearance.pos.x mustEqual 3674.8438f
- char.appearance.pos.y mustEqual 2726.789f
- char.appearance.pos.z mustEqual 91.15625f
- char.appearance.objYaw mustEqual 19
- char.appearance.faction mustEqual PlanetSideEmpire.VS
- char.appearance.bops mustEqual false
- char.appearance.unk1 mustEqual 4
- char.appearance.name mustEqual "IlllIIIlllIlIllIlllIllI"
- char.appearance.exosuit mustEqual 4 //standard
- char.appearance.sex mustEqual 2 //female
- char.appearance.face1 mustEqual 2
- char.appearance.face2 mustEqual 9
- char.appearance.voice mustEqual 1 //female 1
- char.appearance.unk2 mustEqual 3
- char.appearance.unk3 mustEqual 118
- char.appearance.unk4 mustEqual 30
- char.appearance.unk5 mustEqual 0x8080
- char.appearance.unk6 mustEqual 0xFFFF
- char.appearance.unk7 mustEqual 2
- char.appearance.viewPitch mustEqual 0xFF
- char.appearance.viewYaw mustEqual 0x6A
- char.appearance.unk8 mustEqual 7
- char.appearance.ribbons.upper mustEqual 0xFFFFFFFFL //none
- char.appearance.ribbons.middle mustEqual 0xFFFFFFFFL //none
- char.appearance.ribbons.lower mustEqual 0xFFFFFFFFL //none
- char.appearance.ribbons.tos mustEqual 0xFFFFFFFFL //none
- char.healthMax mustEqual 100
- char.health mustEqual 100
- char.armor mustEqual 50 //standard exosuit value
- char.unk1 mustEqual 1
- char.unk2 mustEqual 7
- char.unk3 mustEqual 7
- char.staminaMax mustEqual 100
- char.stamina mustEqual 100
- char.unk4 mustEqual 28
- char.unk5 mustEqual 4
- char.unk6 mustEqual 44
- char.unk7 mustEqual 84
- char.unk8 mustEqual 104
- char.unk9 mustEqual 1900
- char.firstTimeEvents.size mustEqual 4
- char.firstTimeEvents.head mustEqual "xpe_sanctuary_help"
- char.firstTimeEvents(1) mustEqual "xpe_th_firemodes"
- char.firstTimeEvents(2) mustEqual "used_beamer"
- char.firstTimeEvents(3) mustEqual "map13"
- char.tutorials.size mustEqual 0
- char.inventory.unk1 mustEqual true
- char.inventory.unk2 mustEqual false
- char.inventory.contents.size mustEqual 10
- val inventory = char.inventory.contents
- //0
- inventory.head.item.objectClass mustEqual 0x8C //beamer
- inventory.head.item.guid mustEqual PlanetSideGUID(76)
- inventory.head.item.parentSlot mustEqual 0
- var wep = inventory.head.item.obj.asInstanceOf[WeaponData]
- wep.ammo.objectClass mustEqual 0x110 //plasma
- wep.ammo.guid mustEqual PlanetSideGUID(77)
- wep.ammo.parentSlot mustEqual 0
- wep.ammo.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 16
- //1
- inventory(1).item.objectClass mustEqual 0x34D //suppressor
- inventory(1).item.guid mustEqual PlanetSideGUID(78)
- inventory(1).item.parentSlot mustEqual 2
- wep = inventory(1).item.obj.asInstanceOf[WeaponData]
- wep.ammo.objectClass mustEqual 0x1C //9mm
- wep.ammo.guid mustEqual PlanetSideGUID(79)
- wep.ammo.parentSlot mustEqual 0
- wep.ammo.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 25
- //2
- inventory(2).item.objectClass mustEqual 0x144 //force blade
- inventory(2).item.guid mustEqual PlanetSideGUID(80)
- inventory(2).item.parentSlot mustEqual 4
- wep = inventory(2).item.obj.asInstanceOf[WeaponData]
- wep.ammo.objectClass mustEqual 0x21C //force blade ammo
- wep.ammo.guid mustEqual PlanetSideGUID(81)
- wep.ammo.parentSlot mustEqual 0
- wep.ammo.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 1
- //3
- inventory(3).item.objectClass mustEqual 0x1C8 //thing
- inventory(3).item.guid mustEqual PlanetSideGUID(82)
- inventory(3).item.parentSlot mustEqual 5
- inventory(3).item.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 1
- //4
- inventory(4).item.objectClass mustEqual 0x1C //9mm
- inventory(4).item.guid mustEqual PlanetSideGUID(83)
- inventory(4).item.parentSlot mustEqual 6
- inventory(4).item.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 50
- //5
- inventory(5).item.objectClass mustEqual 0x1C //9mm
- inventory(5).item.guid mustEqual PlanetSideGUID(84)
- inventory(5).item.parentSlot mustEqual 9
- inventory(5).item.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 50
- //6
- inventory(6).item.objectClass mustEqual 0x1C //9mm
- inventory(6).item.guid mustEqual PlanetSideGUID(85)
- inventory(6).item.parentSlot mustEqual 12
- inventory(6).item.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 50
- //7
- inventory(7).item.objectClass mustEqual 0x1D //9mm ap
- inventory(7).item.guid mustEqual PlanetSideGUID(86)
- inventory(7).item.parentSlot mustEqual 33
- inventory(7).item.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 50
- //8
- inventory(8).item.objectClass mustEqual 0x110 //plasma
- inventory(8).item.guid mustEqual PlanetSideGUID(87)
- inventory(8).item.parentSlot mustEqual 36
- inventory(8).item.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 50
- //9
- inventory(9).item.objectClass mustEqual 0x2D8 //rek
- inventory(9).item.guid mustEqual PlanetSideGUID(88)
- inventory(9).item.parentSlot mustEqual 39
- //the rek has data but none worth testing here
+ data.get.isInstanceOf[TrackedProjectileData] mustEqual true
+ val projectile = data.get.asInstanceOf[TrackedProjectileData]
+ projectile.pos.coord.x mustEqual 4644.5938f
+ projectile.pos.coord.y mustEqual 5472.0938f
+ projectile.pos.coord.z mustEqual 82.375f
+ projectile.pos.roll mustEqual 0
+ projectile.pos.pitch mustEqual 245
+ projectile.pos.yaw mustEqual 227
+ projectile.unk1 mustEqual 0
+ projectile.unk2 mustEqual TrackedProjectileData.striker_missile_targetting_projectile_data
case _ =>
ko
}
}
- "encode (2)" in {
- //the lack of an object will fail to turn into a bad bitstream
- val msg = ObjectCreateMessage(0, 121, PlanetSideGUID(2497), None, None)
- PacketCoding.EncodePacket(msg).isFailure mustEqual true
+ "decode (implant interface)" in {
+ PacketCoding.DecodePacket(string_implant_interface).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 108
+ cls mustEqual 0x199
+ guid mustEqual PlanetSideGUID(1075)
+ parent.isDefined mustEqual true
+ parent.get.guid mustEqual PlanetSideGUID(514)
+ parent.get.slot mustEqual 1
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[ImplantInterfaceData] mustEqual true
+ case _ =>
+ ko
+ }
}
- "encode (9mm)" in {
- val obj = AmmoBoxData(50)
- val msg = ObjectCreateMessage(0, 28, PlanetSideGUID(1280), ObjectCreateMessageParent(PlanetSideGUID(75), 33), obj)
- val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
-
- pkt mustEqual string_9mm
+ "decode (order terminal a)" in {
+ PacketCoding.DecodePacket(string_order_terminala).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 165
+ cls mustEqual ObjectClass.order_terminala
+ guid mustEqual PlanetSideGUID(3827)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ val term = data.get.asInstanceOf[CommonTerminalData]
+ term.pos.coord.x mustEqual 4579.3438f
+ term.pos.coord.y mustEqual 5615.0703f
+ term.pos.coord.z mustEqual 72.953125f
+ term.pos.pitch mustEqual 0
+ term.pos.roll mustEqual 0
+ term.pos.yaw mustEqual 125
+ ok
+ case _ =>
+ ko
+ }
}
- "encode (gauss)" in {
- val obj = WeaponData(4, 28, PlanetSideGUID(1286), 0, AmmoBoxData(30))
- val msg = ObjectCreateMessage(0, 345, PlanetSideGUID(1465), ObjectCreateMessageParent(PlanetSideGUID(75), 2), obj)
- val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
-
- pkt mustEqual string_gauss
+ "decode (ace, held)" in {
+ PacketCoding.DecodePacket(string_ace_held).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 118
+ cls mustEqual ObjectClass.ace
+ guid mustEqual PlanetSideGUID(3173)
+ parent.isDefined mustEqual true
+ parent.get.guid mustEqual PlanetSideGUID(3336)
+ parent.get.slot mustEqual 0
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[ACEData] mustEqual true
+ val ace = data.get.asInstanceOf[ACEData]
+ ace.unk1 mustEqual 4
+ ace.unk2 mustEqual 8
+ ace.unk3 mustEqual 0
+ case _ =>
+ ko
+ }
}
- "encode (punisher)" in {
- val obj = ConcurrentFeedWeaponData(0, AmmoBoxData(28, PlanetSideGUID(1693), 0, AmmoBoxData(30)) :: AmmoBoxData(413, PlanetSideGUID(1564), 1, AmmoBoxData(1)) :: Nil)
- val msg = ObjectCreateMessage(0, 706, PlanetSideGUID(1703), ObjectCreateMessageParent(PlanetSideGUID(75), 2), obj)
- var pkt = PacketCoding.EncodePacket(msg).require.toByteVector
-
- pkt mustEqual string_punisher
+ "decode (boomer trigger, held)" in {
+ PacketCoding.DecodePacket(string_boomertrigger).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 118
+ cls mustEqual ObjectClass.boomer_trigger
+ guid mustEqual PlanetSideGUID(3600)
+ parent.isDefined mustEqual true
+ parent.get.guid mustEqual PlanetSideGUID(4272)
+ parent.get.slot mustEqual 0
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[BoomerTriggerData] mustEqual true
+ data.get.asInstanceOf[BoomerTriggerData].unk mustEqual 0
+ case _ =>
+ ko
+ }
}
- "encode (rek)" in {
- val obj = REKData(4)
- val msg = ObjectCreateMessage(0, 0x2D8, PlanetSideGUID(1439), ObjectCreateMessageParent(PlanetSideGUID(75), 1), obj)
- val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
-
- pkt mustEqual string_rek
+ "decode (detonator, held)" in {
+ PacketCoding.DecodePacket(string_detonater_held).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 118
+ cls mustEqual ObjectClass.command_detonater
+ guid mustEqual PlanetSideGUID(4162)
+ parent.isDefined mustEqual true
+ parent.get.guid mustEqual PlanetSideGUID(4149)
+ parent.get.slot mustEqual 0
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[CommandDetonaterData] mustEqual true
+ val cud = data.get.asInstanceOf[CommandDetonaterData]
+ cud.unk1 mustEqual 4
+ cud.unk2 mustEqual 0
+ case _ =>
+ ko
+ }
}
- "encode (character)" in {
- val app = CharacterAppearanceData(
- Vector3(3674.8438f, 2726.789f, 91.15625f),
- 19,
- PlanetSideEmpire.VS,
- false,
- 4,
- "IlllIIIlllIlIllIlllIllI",
- 4,
- 2,
- 2,9,
- 1,
- 3, 118,30, 0x8080, 0xFFFF, 2,
- 255, 106, 7,
- RibbonBars()
+ "decode (lasher, held)" in {
+ PacketCoding.DecodePacket(string_lasher_held).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 187
+ cls mustEqual ObjectClass.lasher
+ guid mustEqual PlanetSideGUID(3033)
+ parent.isDefined mustEqual true
+ parent.get.guid mustEqual PlanetSideGUID(4141)
+ parent.get.slot mustEqual 3
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[WeaponData] mustEqual true
+ val wep = data.get.asInstanceOf[WeaponData]
+ wep.unk1 mustEqual 8
+ wep.unk2 mustEqual 8
+ wep.fire_mode mustEqual 0
+ wep.ammo.objectClass mustEqual ObjectClass.energy_cell
+ wep.ammo.guid mustEqual PlanetSideGUID(3548)
+ wep.ammo.parentSlot mustEqual 0
+ wep.ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ val ammo = wep.ammo.obj.asInstanceOf[AmmoBoxData]
+ ammo.unk mustEqual 8
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (punisher, held)" in {
+ PacketCoding.DecodePacket(string_punisher_held).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 246
+ cls mustEqual ObjectClass.punisher
+ guid mustEqual PlanetSideGUID(4147)
+ parent.isDefined mustEqual true
+ parent.get.guid mustEqual PlanetSideGUID(3092)
+ parent.get.slot mustEqual 3
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[ConcurrentFeedWeaponData] mustEqual true
+ val wep = data.get.asInstanceOf[ConcurrentFeedWeaponData]
+ wep.unk1 mustEqual 8
+ wep.unk2 mustEqual 8
+ wep.fire_mode mustEqual 0
+ val ammo = wep.ammo
+ ammo.size mustEqual 2
+ //0
+ ammo.head.objectClass mustEqual ObjectClass.bullet_9mm
+ ammo.head.guid mustEqual PlanetSideGUID(3918)
+ ammo.head.parentSlot mustEqual 0
+ ammo.head.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ ammo.head.obj.asInstanceOf[AmmoBoxData].unk mustEqual 8
+ //1
+ ammo(1).objectClass mustEqual ObjectClass.rocket
+ ammo(1).guid mustEqual PlanetSideGUID(3941)
+ ammo(1).parentSlot mustEqual 1
+ ammo(1).obj.isInstanceOf[AmmoBoxData] mustEqual true
+ ammo(1).obj.asInstanceOf[AmmoBoxData].unk mustEqual 8
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (REK, held)" in {
+ PacketCoding.DecodePacket(string_rek_held).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 134
+ cls mustEqual ObjectClass.remote_electronics_kit
+ guid mustEqual PlanetSideGUID(3893)
+ parent.isDefined mustEqual true
+ parent.get.guid mustEqual PlanetSideGUID(4174)
+ parent.get.slot mustEqual 0
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[REKData] mustEqual true
+ val rek = data.get.asInstanceOf[REKData]
+ rek.unk1 mustEqual 0
+ rek.unk2 mustEqual 8
+ rek.unk3 mustEqual 0
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (capture flag)" in {
+ PacketCoding.DecodePacket(string_captureflag).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 229
+ cls mustEqual ObjectClass.capture_flag
+ guid mustEqual PlanetSideGUID(4330)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[CaptureFlagData] mustEqual true
+ val flag = data.get.asInstanceOf[CaptureFlagData]
+ flag.pos.coord.x mustEqual 3912.0312f
+ flag.pos.coord.y mustEqual 5169.4375f
+ flag.pos.coord.z mustEqual 59.96875f
+ flag.pos.roll mustEqual 0
+ flag.pos.pitch mustEqual 0
+ flag.pos.yaw mustEqual 15
+ flag.faction mustEqual PlanetSideEmpire.NC
+ flag.unk1 mustEqual 21
+ flag.unk2 mustEqual 4
+ flag.unk3 mustEqual 2838
+ flag.unk4 mustEqual 9
+ case _ =>
+ ko
+ }
+ }
+ "decode (ace, dropped)" in {
+ PacketCoding.DecodePacket(string_ace_dropped).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 175
+ cls mustEqual ObjectClass.ace
+ guid mustEqual PlanetSideGUID(4388)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[DroppedItemData[_]] mustEqual true
+ val drop = data.get.asInstanceOf[DroppedItemData[_]]
+ drop.pos.coord.x mustEqual 4708.461f
+ drop.pos.coord.y mustEqual 5547.539f
+ drop.pos.coord.z mustEqual 72.703125f
+ drop.pos.roll mustEqual 0
+ drop.pos.pitch mustEqual 0
+ drop.pos.yaw mustEqual 91
+ drop.obj.isInstanceOf[ACEData] mustEqual true
+ val ace = drop.obj.asInstanceOf[ACEData]
+ ace.unk1 mustEqual 8
+ ace.unk2 mustEqual 8
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (detonator, dropped)" in {
+ PacketCoding.DecodePacket(string_detonater_dropped).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 175
+ cls mustEqual ObjectClass.command_detonater
+ guid mustEqual PlanetSideGUID(3682)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[DroppedItemData[_]] mustEqual true
+ val drop = data.get.asInstanceOf[DroppedItemData[_]]
+ drop.pos.coord.x mustEqual 4777.633f
+ drop.pos.coord.y mustEqual 5485.4062f
+ drop.pos.coord.z mustEqual 85.8125f
+ drop.pos.roll mustEqual 0
+ drop.pos.pitch mustEqual 0
+ drop.pos.yaw mustEqual 27
+ drop.obj.isInstanceOf[CommandDetonaterData] mustEqual true
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (shotgun shells, dropped)" in {
+ PacketCoding.DecodePacket(string_shotgunshell_dropped).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 165
+ cls mustEqual ObjectClass.shotgun_shell
+ guid mustEqual PlanetSideGUID(3453)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[DroppedItemData[_]] mustEqual true
+ val drop = data.get.asInstanceOf[DroppedItemData[_]]
+ drop.pos.coord.x mustEqual 4684.7344f
+ drop.pos.coord.y mustEqual 5547.4844f
+ drop.pos.coord.z mustEqual 83.765625f
+ drop.pos.roll mustEqual 0
+ drop.pos.pitch mustEqual 0
+ drop.pos.yaw mustEqual 89
+ drop.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ val box = drop.obj.asInstanceOf[AmmoBoxData]
+ box.unk mustEqual 0
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (lasher, dropped)" in {
+ PacketCoding.DecodePacket(string_lasher_dropped).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 244
+ cls mustEqual ObjectClass.lasher
+ guid mustEqual PlanetSideGUID(3074)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[DroppedItemData[_]] mustEqual true
+ val drop = data.get.asInstanceOf[DroppedItemData[_]]
+ drop.pos.coord.x mustEqual 4691.1953f
+ drop.pos.coord.y mustEqual 5537.039f
+ drop.pos.coord.z mustEqual 65.484375f
+ drop.pos.roll mustEqual 0
+ drop.pos.pitch mustEqual 0
+ drop.pos.yaw mustEqual 32
+ drop.obj.isInstanceOf[WeaponData] mustEqual true
+ val wep = drop.obj.asInstanceOf[WeaponData]
+ wep.unk1 mustEqual 8
+ wep.unk2 mustEqual 0
+ wep.fire_mode mustEqual 0
+ wep.ammo.objectClass mustEqual ObjectClass.energy_cell
+ wep.ammo.guid mustEqual PlanetSideGUID(3268)
+ wep.ammo.parentSlot mustEqual 0
+ wep.ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ val ammo = wep.ammo.obj.asInstanceOf[AmmoBoxData]
+ ammo.unk mustEqual 0
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (punisher, dropped)" in {
+ PacketCoding.DecodePacket(string_punisher_dropped).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 303
+ cls mustEqual ObjectClass.punisher
+ guid mustEqual PlanetSideGUID(2978)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[DroppedItemData[_]] mustEqual true
+ val drop = data.get.asInstanceOf[DroppedItemData[_]]
+ drop.pos.coord.x mustEqual 4789.133f
+ drop.pos.coord.y mustEqual 5522.3125f
+ drop.pos.coord.z mustEqual 72.3125f
+ drop.pos.roll mustEqual 0
+ drop.pos.pitch mustEqual 0
+ drop.pos.yaw mustEqual 51
+ drop.obj.isInstanceOf[ConcurrentFeedWeaponData] mustEqual true
+ val wep = drop.obj.asInstanceOf[ConcurrentFeedWeaponData]
+ wep.unk1 mustEqual 4
+ wep.unk2 mustEqual 0
+ wep.fire_mode mustEqual 0
+ val ammo = wep.ammo
+ ammo.size mustEqual 2
+ //0
+ ammo.head.objectClass mustEqual ObjectClass.bullet_9mm
+ ammo.head.guid mustEqual PlanetSideGUID(3528)
+ ammo.head.parentSlot mustEqual 0
+ ammo.head.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ ammo.head.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0
+ //1
+ ammo(1).objectClass mustEqual ObjectClass.rocket
+ ammo(1).guid mustEqual PlanetSideGUID(3031)
+ ammo(1).parentSlot mustEqual 1
+ ammo(1).obj.isInstanceOf[AmmoBoxData] mustEqual true
+ ammo(1).obj.asInstanceOf[AmmoBoxData].unk mustEqual 0
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (REK, dropped)" in {
+ PacketCoding.DecodePacket(string_rek_dropped).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 191
+ cls mustEqual ObjectClass.remote_electronics_kit
+ guid mustEqual PlanetSideGUID(4355)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[DroppedItemData[_]] mustEqual true
+ val dropped = data.get.asInstanceOf[DroppedItemData[_]]
+ dropped.pos.coord.x mustEqual 4675.039f
+ dropped.pos.coord.y mustEqual 5506.953f
+ dropped.pos.coord.z mustEqual 72.703125f
+ dropped.pos.roll mustEqual 0
+ dropped.pos.pitch mustEqual 0
+ dropped.pos.yaw mustEqual 78
+ dropped.obj.isInstanceOf[REKData] mustEqual true
+ val rek = dropped.obj.asInstanceOf[REKData]
+ rek.unk1 mustEqual 8
+ rek.unk2 mustEqual 0
+ rek.unk3 mustEqual 3
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (boomer)" in {
+ PacketCoding.DecodePacket(string_boomer).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 165
+ cls mustEqual ObjectClass.boomer
+ guid mustEqual PlanetSideGUID(3840)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[SmallDeployableData] mustEqual true
+ val boomer = data.get.asInstanceOf[SmallDeployableData]
+ boomer.deploy.pos.coord.x mustEqual 4704.172f
+ boomer.deploy.pos.coord.y mustEqual 5546.4375f
+ boomer.deploy.pos.coord.z mustEqual 82.234375f
+ boomer.deploy.pos.roll mustEqual 0
+ boomer.deploy.pos.pitch mustEqual 0
+ boomer.deploy.pos.yaw mustEqual 63
+ boomer.deploy.unk mustEqual 0
+ boomer.deploy.player_guid mustEqual PlanetSideGUID(4145)
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (spitfire, short)" in {
+ PacketCoding.DecodePacket(string_spitfire_short).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 187
+ cls mustEqual ObjectClass.spitfire_turret
+ guid mustEqual PlanetSideGUID(4208)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[SmallTurretData] mustEqual true
+ val turret = data.get.asInstanceOf[SmallTurretData]
+ turret.deploy.pos.coord.x mustEqual 4577.7812f
+ turret.deploy.pos.coord.y mustEqual 5624.828f
+ turret.deploy.pos.coord.z mustEqual 72.046875f
+ turret.deploy.pos.roll mustEqual 0
+ turret.deploy.pos.pitch mustEqual 127
+ turret.deploy.pos.yaw mustEqual 66
+ turret.deploy.unk mustEqual 44
+ turret.deploy.player_guid mustEqual PlanetSideGUID(3871)
+ turret.health mustEqual 0
+ turret.internals.isDefined mustEqual false
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (spitfire)" in {
+ PacketCoding.DecodePacket(string_spitfire).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 335
+ cls mustEqual ObjectClass.spitfire_turret
+ guid mustEqual PlanetSideGUID(4265)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[SmallTurretData] mustEqual true
+ val turret = data.get.asInstanceOf[SmallTurretData]
+ turret.deploy.pos.coord.x mustEqual 4527.633f
+ turret.deploy.pos.coord.y mustEqual 6271.3594f
+ turret.deploy.pos.coord.z mustEqual 70.265625f
+ turret.deploy.pos.roll mustEqual 0
+ turret.deploy.pos.pitch mustEqual 0
+ turret.deploy.pos.yaw mustEqual 105
+ turret.deploy.unk mustEqual 68
+ turret.deploy.player_guid mustEqual PlanetSideGUID(4232)
+ turret.health mustEqual 255
+ turret.internals.isDefined mustEqual true
+ val internals = turret.internals.get
+ internals.objectClass mustEqual ObjectClass.spitfire_weapon
+ internals.guid mustEqual PlanetSideGUID(3064)
+ internals.parentSlot mustEqual 0
+ internals.obj.isInstanceOf[WeaponData] mustEqual true
+ val wep = internals.obj.asInstanceOf[WeaponData]
+ wep.unk1 mustEqual 0xC
+ wep.unk2 mustEqual 0x8
+ wep.fire_mode mustEqual 0
+ val ammo = wep.ammo
+ ammo.objectClass mustEqual ObjectClass.spitfire_ammo
+ ammo.guid mustEqual PlanetSideGUID(3694)
+ ammo.parentSlot mustEqual 0
+ ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 8
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (trap)" in {
+ PacketCoding.DecodePacket(string_trap).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 187
+ cls mustEqual ObjectClass.tank_traps
+ guid mustEqual PlanetSideGUID(2659)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[TRAPData] mustEqual true
+ val trap = data.get.asInstanceOf[TRAPData]
+ trap.deploy.pos.coord.x mustEqual 3572.4453f
+ trap.deploy.pos.coord.y mustEqual 3277.9766f
+ trap.deploy.pos.coord.z mustEqual 114.0f
+ trap.deploy.pos.roll mustEqual 0
+ trap.deploy.pos.pitch mustEqual 0
+ trap.deploy.pos.yaw mustEqual 0
+ trap.deploy.unk mustEqual 68
+ trap.health mustEqual 255
+ trap.deploy.player_guid mustEqual PlanetSideGUID(2502)
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (aegis)" in {
+ PacketCoding.DecodePacket(string_aegis).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 272
+ cls mustEqual ObjectClass.deployable_shield_generator
+ guid mustEqual PlanetSideGUID(2556)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[AegisShieldGeneratorData] mustEqual true
+ val aegis = data.get.asInstanceOf[AegisShieldGeneratorData]
+ aegis.deploy.pos.coord.x mustEqual 3571.2266f
+ aegis.deploy.pos.coord.y mustEqual 3278.0938f
+ aegis.deploy.pos.coord.z mustEqual 114.0f
+ aegis.deploy.pos.roll mustEqual 0
+ aegis.deploy.pos.pitch mustEqual 0
+ aegis.deploy.pos.yaw mustEqual 0
+ aegis.deploy.unk mustEqual 68
+ aegis.health mustEqual 255
+ aegis.deploy.player_guid mustEqual PlanetSideGUID(2366)
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (orion)" in {
+ PacketCoding.DecodePacket(string_orion).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 350
+ cls mustEqual ObjectClass.portable_manned_turret_vs
+ guid mustEqual PlanetSideGUID(2916)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[OneMannedFieldTurretData] mustEqual true
+ val omft = data.get.asInstanceOf[OneMannedFieldTurretData]
+ omft.deploy.pos.coord.x mustEqual 3567.1406f
+ omft.deploy.pos.coord.y mustEqual 2988.0078f
+ omft.deploy.pos.coord.z mustEqual 71.84375f
+ omft.deploy.pos.roll mustEqual 0
+ omft.deploy.pos.pitch mustEqual 0
+ omft.deploy.pos.yaw mustEqual 94
+ omft.deploy.unk mustEqual 68
+ omft.deploy.player_guid mustEqual PlanetSideGUID(0)
+ omft.player_guid mustEqual PlanetSideGUID(2502)
+ omft.health mustEqual 255
+ omft.internals.isDefined mustEqual true
+ val internals = omft.internals.get
+ internals.objectClass mustEqual ObjectClass.energy_gun_vs
+ internals.guid mustEqual PlanetSideGUID(2615)
+ internals.parentSlot mustEqual 1
+ internals.obj.isInstanceOf[WeaponData] mustEqual true
+ val wep = internals.obj.asInstanceOf[WeaponData]
+ wep.unk1 mustEqual 0xC
+ wep.unk2 mustEqual 0x8
+ wep.fire_mode mustEqual 0
+ val ammo = wep.ammo
+ ammo.objectClass mustEqual ObjectClass.energy_gun_ammo
+ ammo.guid mustEqual PlanetSideGUID(2510)
+ ammo.parentSlot mustEqual 0
+ ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 8
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (locker container)" in {
+ PacketCoding.DecodePacket(string_locker_container).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 431
+ cls mustEqual ObjectClass.locker_container
+ guid mustEqual PlanetSideGUID(3148)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[LockerContainerData] mustEqual true
+ val locker = data.get.asInstanceOf[LockerContainerData]
+ locker.inventory.unk1 mustEqual false
+ locker.inventory.unk2 mustEqual false
+ val contents = locker.inventory.contents
+ contents.size mustEqual 3
+ //0
+ contents.head.item.objectClass mustEqual ObjectClass.nano_dispenser
+ contents.head.item.guid mustEqual PlanetSideGUID(2935)
+ contents.head.item.parentSlot mustEqual 0
+ contents.head.item.obj.isInstanceOf[WeaponData] mustEqual true
+ val dispenser = contents.head.item.obj.asInstanceOf[WeaponData]
+ dispenser.unk1 mustEqual 0xC
+ dispenser.unk2 mustEqual 0x0
+ dispenser.ammo.objectClass mustEqual ObjectClass.armor_canister
+ dispenser.ammo.guid mustEqual PlanetSideGUID(3426)
+ dispenser.ammo.parentSlot mustEqual 0
+ dispenser.ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ dispenser.ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0
+ //1
+ contents(1).item.objectClass mustEqual ObjectClass.armor_canister
+ contents(1).item.guid mustEqual PlanetSideGUID(4090)
+ contents(1).item.parentSlot mustEqual 45
+ contents(1).item.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ contents(1).item.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0
+ //2
+ contents(2).item.objectClass mustEqual ObjectClass.armor_canister
+ contents(2).item.guid mustEqual PlanetSideGUID(3326)
+ contents(2).item.parentSlot mustEqual 78
+ contents(2).item.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ contents(2).item.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (character, alive)" in {
+ PacketCoding.DecodePacket(string_character).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 1907
+ cls mustEqual ObjectClass.avatar
+ guid mustEqual PlanetSideGUID(3902)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[CharacterData] mustEqual true
+ val pc = data.get.asInstanceOf[CharacterData]
+ pc.appearance.pos.coord.x mustEqual 3674.8438f
+ pc.appearance.pos.coord.y mustEqual 2726.789f
+ pc.appearance.pos.coord.z mustEqual 91.15625f
+ pc.appearance.pos.roll mustEqual 0
+ pc.appearance.pos.pitch mustEqual 0
+ pc.appearance.pos.yaw mustEqual 9
+ pc.appearance.pos.init_move.isDefined mustEqual true
+ pc.appearance.pos.init_move.get.x mustEqual 1.4375f
+ pc.appearance.pos.init_move.get.y mustEqual -0.4375f
+ pc.appearance.pos.init_move.get.z mustEqual 0f
+ pc.appearance.basic_appearance.name mustEqual "ScrawnyRonnie"
+ pc.appearance.basic_appearance.faction mustEqual PlanetSideEmpire.TR
+ pc.appearance.basic_appearance.sex mustEqual CharacterGender.Male
+ pc.appearance.basic_appearance.head mustEqual 5
+ pc.appearance.basic_appearance.voice mustEqual 5
+ pc.appearance.voice2 mustEqual 3
+ pc.appearance.black_ops mustEqual false
+ pc.appearance.jammered mustEqual false
+ pc.appearance.exosuit mustEqual ExoSuitType.Reinforced
+ pc.appearance.outfit_name mustEqual "Black Beret Armoured Corps"
+ pc.appearance.outfit_logo mustEqual 23
+ pc.appearance.facingPitch mustEqual 7
+ pc.appearance.facingYawUpper mustEqual 0
+ pc.appearance.lfs mustEqual false
+ pc.appearance.grenade_state mustEqual GrenadeState.None
+ pc.appearance.is_cloaking mustEqual false
+ pc.appearance.charging_pose mustEqual false
+ pc.appearance.on_zipline mustEqual false
+ pc.appearance.ribbons.upper mustEqual 276L
+ pc.appearance.ribbons.middle mustEqual 239L
+ pc.appearance.ribbons.lower mustEqual 397L
+ pc.appearance.ribbons.tos mustEqual 360L
+ pc.health mustEqual 255
+ pc.armor mustEqual 253
+ pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
+ pc.command_rank mustEqual 5
+ pc.implant_effects.isDefined mustEqual true
+ pc.implant_effects.get mustEqual ImplantEffects.NoEffects
+ pc.cosmetics.isDefined mustEqual true
+ pc.cosmetics.get.no_helmet mustEqual true
+ pc.cosmetics.get.beret mustEqual true
+ pc.cosmetics.get.sunglasses mustEqual true
+ pc.cosmetics.get.earpiece mustEqual true
+ pc.cosmetics.get.brimmed_cap mustEqual false
+ //short test of inventory items
+ pc.inventory.isDefined mustEqual true
+ val contents = pc.inventory.get.contents
+ contents.size mustEqual 5
+ //0
+ contents.head.item.objectClass mustEqual ObjectClass.plasma_grenade
+ contents.head.item.guid mustEqual PlanetSideGUID(3662)
+ contents.head.item.parentSlot mustEqual 0
+ contents.head.item.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
+ contents.head.item.obj.asInstanceOf[WeaponData].ammo.objectClass mustEqual ObjectClass.plasma_grenade_ammo
+ contents.head.item.obj.asInstanceOf[WeaponData].ammo.guid mustEqual PlanetSideGUID(3751)
+ //1
+ contents(1).item.objectClass mustEqual ObjectClass.bank
+ contents(1).item.guid mustEqual PlanetSideGUID(3908)
+ contents(1).item.parentSlot mustEqual 1
+ contents(1).item.obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
+ contents(1).item.obj.asInstanceOf[WeaponData].ammo.objectClass mustEqual ObjectClass.armor_canister
+ contents(1).item.obj.asInstanceOf[WeaponData].ammo.guid mustEqual PlanetSideGUID(4143)
+ //2
+ contents(2).item.objectClass mustEqual ObjectClass.mini_chaingun
+ contents(2).item.guid mustEqual PlanetSideGUID(4164)
+ contents(2).item.parentSlot mustEqual 2
+ contents(2).item.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
+ contents(2).item.obj.asInstanceOf[WeaponData].ammo.objectClass mustEqual ObjectClass.bullet_9mm
+ contents(2).item.obj.asInstanceOf[WeaponData].ammo.guid mustEqual PlanetSideGUID(3728)
+ //3
+ contents(3).item.objectClass mustEqual ObjectClass.phoenix //actually, a decimator
+ contents(3).item.guid mustEqual PlanetSideGUID(3603)
+ contents(3).item.parentSlot mustEqual 3
+ contents(3).item.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
+ contents(3).item.obj.asInstanceOf[WeaponData].ammo.objectClass mustEqual ObjectClass.phoenix_missile
+ contents(3).item.obj.asInstanceOf[WeaponData].ammo.guid mustEqual PlanetSideGUID(3056)
+ //4
+ contents(4).item.objectClass mustEqual ObjectClass.chainblade
+ contents(4).item.guid mustEqual PlanetSideGUID(4088)
+ contents(4).item.parentSlot mustEqual 4
+ contents(4).item.obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
+ contents(4).item.obj.asInstanceOf[WeaponData].ammo.objectClass mustEqual ObjectClass.melee_ammo
+ contents(4).item.obj.asInstanceOf[WeaponData].ammo.guid mustEqual PlanetSideGUID(3279)
+ pc.drawn_slot mustEqual DrawnSlot.Rifle1
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (character, backpack)" in {
+ PacketCoding.DecodePacket(string_character_backpack).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 924L
+ cls mustEqual ObjectClass.avatar
+ guid mustEqual PlanetSideGUID(3380)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[CharacterData] mustEqual true
+ val pc = data.get.asInstanceOf[CharacterData]
+ pc.appearance.pos.coord.x mustEqual 4629.8906f
+ pc.appearance.pos.coord.y mustEqual 6316.4453f
+ pc.appearance.pos.coord.z mustEqual 54.734375f
+ pc.appearance.pos.roll mustEqual 0
+ pc.appearance.pos.pitch mustEqual 0
+ pc.appearance.pos.yaw mustEqual 115
+ pc.appearance.pos.init_move.isDefined mustEqual false
+ pc.appearance.basic_appearance.name mustEqual "Angello"
+ pc.appearance.basic_appearance.faction mustEqual PlanetSideEmpire.VS
+ pc.appearance.basic_appearance.sex mustEqual CharacterGender.Male
+ pc.appearance.basic_appearance.head mustEqual 10
+ pc.appearance.basic_appearance.voice mustEqual 2
+ pc.appearance.voice2 mustEqual 0
+ pc.appearance.black_ops mustEqual false
+ pc.appearance.jammered mustEqual false
+ pc.appearance.exosuit mustEqual ExoSuitType.MAX
+ pc.appearance.outfit_name mustEqual "Original District"
+ pc.appearance.outfit_logo mustEqual 23
+ pc.appearance.facingPitch mustEqual 0
+ pc.appearance.facingYawUpper mustEqual 192
+ pc.appearance.lfs mustEqual false
+ pc.appearance.grenade_state mustEqual GrenadeState.None
+ pc.appearance.is_cloaking mustEqual false
+ pc.appearance.charging_pose mustEqual false
+ pc.appearance.on_zipline mustEqual false
+ pc.appearance.ribbons.upper mustEqual 244L
+ pc.appearance.ribbons.middle mustEqual 353L
+ pc.appearance.ribbons.lower mustEqual 33L
+ pc.appearance.ribbons.tos mustEqual 361L
+ pc.health mustEqual 0
+ pc.armor mustEqual 0
+ pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
+ pc.command_rank mustEqual 2
+ pc.implant_effects.isDefined mustEqual false
+ pc.cosmetics.isDefined mustEqual true
+ pc.cosmetics.get.no_helmet mustEqual true
+ pc.cosmetics.get.beret mustEqual true
+ pc.cosmetics.get.sunglasses mustEqual true
+ pc.cosmetics.get.earpiece mustEqual true
+ pc.cosmetics.get.brimmed_cap mustEqual false
+ pc.inventory.isDefined mustEqual false
+ pc.drawn_slot mustEqual DrawnSlot.Pistol1
+ case _ =>
+ ko
+ }
+ }
+
+ "encode (striker projectile)" in {
+ val obj = TrackedProjectileData.striker(
+ PlacementData(4644.5938f, 5472.0938f, 82.375f, 0, 245, 227),
+ 0
)
- val inv = InventoryItem(0x8C, PlanetSideGUID(76), 0, WeaponData(8, 0x110, PlanetSideGUID(77), 0, AmmoBoxData(16))) ::
- InventoryItem(0x34D, PlanetSideGUID(78), 2, WeaponData(8, 0x1C, PlanetSideGUID(79), 0, AmmoBoxData(25))) ::
- InventoryItem(0x144, PlanetSideGUID(80), 4, WeaponData(8, 0x21C, PlanetSideGUID(81), 0, AmmoBoxData(1))) ::
- InventoryItem(0x1C8, PlanetSideGUID(82), 5, AmmoBoxData(1)) ::
- InventoryItem(0x1C, PlanetSideGUID(83), 6, AmmoBoxData(50)) ::
- InventoryItem(0x1C, PlanetSideGUID(84), 9, AmmoBoxData(50)) ::
- InventoryItem(0x1C, PlanetSideGUID(85), 12, AmmoBoxData(50)) ::
- InventoryItem(0x1D, PlanetSideGUID(86), 33, AmmoBoxData(50)) ::
- InventoryItem(0x110, PlanetSideGUID(87), 36, AmmoBoxData(50)) ::
- InventoryItem(0x2D8, PlanetSideGUID(88), 39, REKData(8)) ::
- Nil
- val obj = CharacterData(
- app,
- 100, 100,
- 50,
- 1, 7, 7,
- 100, 100,
- 28, 4, 44, 84, 104, 1900,
- "xpe_sanctuary_help" :: "xpe_th_firemodes" :: "used_beamer" :: "map13" :: Nil,
- List.empty,
- InventoryData(
- true, false, false, inv
+ val msg = ObjectCreateMessage(ObjectClass.striker_missile_targeting_projectile, PlanetSideGUID(40192), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_striker_projectile
+ }
+
+ "encode (implant interface)" in {
+ val obj = ImplantInterfaceData()
+ val msg = ObjectCreateMessage(0x199, PlanetSideGUID(1075), ObjectCreateMessageParent(PlanetSideGUID(514), 1), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_implant_interface
+ }
+
+ "encode (order terminal a)" in {
+ val obj = CommonTerminalData(PlacementData(Vector3(4579.3438f, 5615.0703f, 72.953125f), 0, 0, 125))
+ val msg = ObjectCreateMessage(ObjectClass.order_terminala, PlanetSideGUID(3827), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_order_terminala
+ }
+
+ "encode (ace, held)" in {
+ val obj = ACEData(4, 8)
+ val msg = ObjectCreateMessage(ObjectClass.ace, PlanetSideGUID(3173), ObjectCreateMessageParent(PlanetSideGUID(3336), 0), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_ace_held
+ }
+
+ "encode (boomer trigger, held)" in {
+ val obj = BoomerTriggerData(0)
+ val msg = ObjectCreateMessage(ObjectClass.boomer_trigger, PlanetSideGUID(3600), ObjectCreateMessageParent(PlanetSideGUID(4272), 0), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_boomertrigger
+ }
+
+ "encode (detonater, held)" in {
+ val obj = CommandDetonaterData(4)
+ val msg = ObjectCreateMessage(ObjectClass.command_detonater, PlanetSideGUID(4162), ObjectCreateMessageParent(PlanetSideGUID(4149), 0), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_detonater_held
+ }
+
+ "encode (lasher, held)" in {
+ val obj = WeaponData(8, 8, ObjectClass.energy_cell, PlanetSideGUID(3548), 0, AmmoBoxData(8))
+ val msg = ObjectCreateMessage(ObjectClass.lasher, PlanetSideGUID(3033), ObjectCreateMessageParent(PlanetSideGUID(4141), 3), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_lasher_held
+ }
+
+ "encode (punisher, held)" in {
+ val obj = ConcurrentFeedWeaponData(8, 8, 0,
+ AmmoBoxData(ObjectClass.bullet_9mm, PlanetSideGUID(3918), 0, AmmoBoxData(8)) ::
+ AmmoBoxData(ObjectClass.rocket, PlanetSideGUID(3941), 1, AmmoBoxData(8)) ::
+ Nil
+ )
+ val msg = ObjectCreateMessage(ObjectClass.punisher, PlanetSideGUID(4147), ObjectCreateMessageParent(PlanetSideGUID(3092), 3), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_punisher_held
+ }
+
+ "encode (REK, held)" in {
+ val obj = REKData(0, 8)
+ val msg = ObjectCreateMessage(ObjectClass.remote_electronics_kit, PlanetSideGUID(3893), ObjectCreateMessageParent(PlanetSideGUID(4174), 0), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_rek_held
+ }
+
+ "encode (capture flag)" in {
+ val obj = CaptureFlagData(PlacementData(3912.0312f, 5169.4375f, 59.96875f, 0, 0, 15), PlanetSideEmpire.NC, 21, 4, 2838, 9)
+ val msg = ObjectCreateMessage(ObjectClass.capture_flag, PlanetSideGUID(4330), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_captureflag
+ }
+
+ "encode (ace, dropped)" in {
+ val obj = DroppedItemData(
+ PlacementData(Vector3(4708.461f, 5547.539f, 72.703125f), 0, 0, 91),
+ ACEData(8, 8)
+ )
+ val msg = ObjectCreateMessage(ObjectClass.ace, PlanetSideGUID(4388), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_ace_dropped
+ }
+
+ "encode (detonator, dropped)" in {
+ val obj = DroppedItemData(
+ PlacementData(Vector3(4777.633f, 5485.4062f, 85.8125f), 0, 0, 27),
+ CommandDetonaterData()
+ )
+ val msg = ObjectCreateMessage(ObjectClass.command_detonater, PlanetSideGUID(3682), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_detonater_dropped
+ }
+
+ "encode (shotgun shells, dropped)" in {
+ val obj = DroppedItemData(
+ PlacementData(Vector3(4684.7344f, 5547.4844f, 83.765625f), 0, 0, 89),
+ AmmoBoxData()
+ )
+ val msg = ObjectCreateMessage(ObjectClass.shotgun_shell, PlanetSideGUID(3453), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_shotgunshell_dropped
+ }
+
+ "encode (lasher, dropped)" in {
+ val obj = DroppedItemData(
+ PlacementData(Vector3(4691.1953f, 5537.039f, 65.484375f), 0, 0, 32),
+ WeaponData(8, 0, ObjectClass.energy_cell, PlanetSideGUID(3268), 0, AmmoBoxData())
+ )
+ val msg = ObjectCreateMessage(ObjectClass.lasher, PlanetSideGUID(3074), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_lasher_dropped
+ }
+
+ "encode (punisher, dropped)" in {
+ val obj = DroppedItemData(
+ PlacementData(Vector3(4789.133f, 5522.3125f, 72.3125f), 0, 0, 51),
+ ConcurrentFeedWeaponData(4, 0, 0,
+ AmmoBoxData(ObjectClass.bullet_9mm, PlanetSideGUID(3528), 0, AmmoBoxData()) ::
+ AmmoBoxData(ObjectClass.rocket, PlanetSideGUID(3031), 1, AmmoBoxData()) ::
+ Nil
)
)
- val msg = ObjectCreateMessage(0, 0x79, PlanetSideGUID(75), obj)
+ val msg = ObjectCreateMessage(ObjectClass.punisher, PlanetSideGUID(2978), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
- pkt mustEqual string_testchar
+ pkt mustEqual string_punisher_dropped
+ }
+
+ "encode (REK, dropped)" in {
+ val obj = DroppedItemData(
+ PlacementData(Vector3(4675.039f, 5506.953f, 72.703125f), 0, 0, 78),
+ REKData(8, 0, 3)
+ )
+ val msg = ObjectCreateMessage(ObjectClass.remote_electronics_kit, PlanetSideGUID(4355), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_rek_dropped
+ }
+
+ "encode (boomer)" in {
+ val obj = SmallDeployableData(
+ ACEDeployableData(
+ PlacementData(Vector3(4704.172f, 5546.4375f, 82.234375f), 0, 0, 63),
+ 0, PlanetSideGUID(4145)
+ )
+ )
+ val msg = ObjectCreateMessage(ObjectClass.boomer, PlanetSideGUID(3840), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_boomer
+ }
+
+ "encode (spitfire, short)" in {
+ val obj = SmallTurretData(
+ ACEDeployableData(
+ PlacementData(Vector3(4577.7812f, 5624.828f, 72.046875f), 0, 127, 66),
+ 44,
+ PlanetSideGUID(3871)
+ ),
+ 255 //sets to 0
+ )
+ val msg = ObjectCreateMessage(ObjectClass.spitfire_turret, PlanetSideGUID(4208), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ val pkt_bitv = pkt.toBitVector
+ val ori_bitv = string_spitfire_short.toBitVector
+ pkt_bitv.take(173) mustEqual ori_bitv.take(173)
+ pkt_bitv.drop(185) mustEqual ori_bitv.drop(185)
+ //TODO work on SmallTurretData to make this pass as a single stream
+ }
+
+ "encode (spitfire)" in {
+ val obj = SmallTurretData(
+ ACEDeployableData(
+ PlacementData(Vector3(4527.633f, 6271.3594f, 70.265625f), 0, 0, 105),
+ 68,
+ PlanetSideGUID(4232)
+ ),
+ 255,
+ SmallTurretData.spitfire(PlanetSideGUID(3064), 0xC, 0x8, PlanetSideGUID(3694), 8)
+ )
+ val msg = ObjectCreateMessage(ObjectClass.spitfire_turret, PlanetSideGUID(4265), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ val pkt_bitv = pkt.toBitVector
+ val ori_bitv = string_spitfire.toBitVector
+ pkt_bitv.take(173) mustEqual ori_bitv.take(173)
+ pkt_bitv.drop(185) mustEqual ori_bitv.drop(185)
+ //TODO work on SmallTurretData to make this pass as a single stream
+ }
+
+ "encode (trap)" in {
+ val obj = TRAPData(
+ ACEDeployableData(
+ PlacementData(Vector3(3572.4453f, 3277.9766f, 114.0f), 0, 0, 0),
+ 68,
+ PlanetSideGUID(2502)
+ ),
+ 255
+ )
+ val msg = ObjectCreateMessage(ObjectClass.tank_traps, PlanetSideGUID(2659), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ val pkt_bitv = pkt.toBitVector
+ val ori_bitv = string_trap.toBitVector
+ pkt_bitv.take(173) mustEqual ori_bitv.take(173)
+ pkt_bitv.drop(185) mustEqual ori_bitv.drop(185)
+ //TODO work on TRAPData to make this pass as a single stream
+ }
+
+ "encode (aegis)" in {
+ val obj = AegisShieldGeneratorData(
+ ACEDeployableData(
+ PlacementData(Vector3(3571.2266f, 3278.0938f, 114.0f), 0, 0, 0),
+ 68,
+ PlanetSideGUID(2366)
+ ),
+ 255
+ )
+ val msg = ObjectCreateMessage(ObjectClass.deployable_shield_generator, PlanetSideGUID(2556), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_aegis
+ }
+
+ "encode (orion)" in {
+ val obj = OneMannedFieldTurretData(
+ ACEDeployableData(
+ PlacementData(Vector3(3567.1406f, 2988.0078f, 71.84375f), 0, 0, 94),
+ 68,
+ PlanetSideGUID(0)
+ ),
+ PlanetSideGUID(2502),
+ 255,
+ OneMannedFieldTurretData.orion(PlanetSideGUID(2615), 0xC, 0x8, PlanetSideGUID(2510), 8)
+ )
+ val msg = ObjectCreateMessage(ObjectClass.portable_manned_turret_vs, PlanetSideGUID(2916), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ val pkt_bitv = pkt.toBitVector
+ val ori_bitv = string_orion.toBitVector
+ pkt_bitv.take(189) mustEqual ori_bitv.take(189)
+ pkt_bitv.drop(200) mustEqual ori_bitv.drop(200)
+ //TODO work on OneMannedFieldTurretData to make this pass as a single stream
+ }
+
+ "encode (locker container)" in {
+ val obj = LockerContainerData(
+ InventoryData(
+ InventoryItem(ObjectClass.nano_dispenser, PlanetSideGUID(2935), 0, WeaponData(0xC, 0x0, ObjectClass.armor_canister, PlanetSideGUID(3426), 0, AmmoBoxData())) ::
+ InventoryItem(ObjectClass.armor_canister, PlanetSideGUID(4090), 45, AmmoBoxData()) ::
+ InventoryItem(ObjectClass.armor_canister, PlanetSideGUID(3326), 78, AmmoBoxData()) ::
+ Nil
+ )
+ )
+ val msg = ObjectCreateMessage(ObjectClass.locker_container, PlanetSideGUID(3148), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_locker_container
+ }
+
+ "encode (character, alive)" in {
+ val obj = CharacterData(
+ CharacterAppearanceData(
+ PlacementData(
+ Vector3(3674.8438f, 2726.789f, 91.15625f),
+ 0, 0, 9,
+ Some(Vector3(1.4375f, -0.4375f, 0f))
+ ),
+ BasicCharacterData(
+ "ScrawnyRonnie",
+ PlanetSideEmpire.TR,
+ CharacterGender.Male,
+ 5,
+ 5
+ ),
+ 3,
+ false,
+ false,
+ ExoSuitType.Reinforced,
+ "Black Beret Armoured Corps",
+ 23,
+ false,
+ 7, 0,
+ false,
+ GrenadeState.None,
+ false, false, false,
+ RibbonBars(276L, 239L, 397L, 360L)
+ ),
+ 255, 253,
+ UniformStyle.ThirdUpgrade,
+ 5,
+ Some(ImplantEffects.NoEffects),
+ Some(Cosmetics(true, true, true, true, false)),
+ InventoryData(
+ InventoryItem(ObjectClass.plasma_grenade, PlanetSideGUID(3662), 0, WeaponData(0, 0, ObjectClass.plasma_grenade_ammo, PlanetSideGUID(3751), 0, AmmoBoxData())) ::
+ InventoryItem(ObjectClass.bank, PlanetSideGUID(3908), 1, WeaponData(0, 0, 1, ObjectClass.armor_canister, PlanetSideGUID(4143), 0, AmmoBoxData())) ::
+ InventoryItem(ObjectClass.mini_chaingun, PlanetSideGUID(4164), 2, WeaponData(0, 0, ObjectClass.bullet_9mm, PlanetSideGUID(3728), 0, AmmoBoxData())) ::
+ InventoryItem(ObjectClass.phoenix, PlanetSideGUID(3603), 3, WeaponData(0, 0, ObjectClass.phoenix_missile, PlanetSideGUID(3056), 0, AmmoBoxData())) ::
+ InventoryItem(ObjectClass.chainblade, PlanetSideGUID(4088), 4, WeaponData(0, 0, 1, ObjectClass.melee_ammo, PlanetSideGUID(3279), 0, AmmoBoxData())) ::
+ Nil
+ ),
+ DrawnSlot.Rifle1
+ )
+ val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3902), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ val pkt_bitv = pkt.toBitVector
+ val ori_bitv = string_character.toBitVector
+ pkt_bitv.take(452) mustEqual ori_bitv.take(452) //skip 126
+ pkt_bitv.drop(578).take(438) mustEqual ori_bitv.drop(578).take(438) //skip 2
+ pkt_bitv.drop(1018).take(17) mustEqual ori_bitv.drop(1018).take(17) //skip 11
+ pkt_bitv.drop(1046).take(147) mustEqual ori_bitv.drop(1046).take(147) //skip 3
+ pkt_bitv.drop(1196) mustEqual ori_bitv.drop(1196)
+ //TODO work on CharacterData to make this pass as a single stream
+ }
+
+ "encode (character, backpack)" in {
+ val obj = CharacterData(
+ CharacterAppearanceData(
+ PlacementData(4629.8906f, 6316.4453f, 54.734375f, 0, 0, 115),
+ BasicCharacterData(
+ "Angello",
+ PlanetSideEmpire.VS,
+ CharacterGender.Male,
+ 10,
+ 2
+ ),
+ 0,
+ false,
+ false,
+ ExoSuitType.MAX,
+ "Original District",
+ 23,
+ true, //backpack
+ 0, 192,
+ false,
+ GrenadeState.None,
+ false, false, false,
+ RibbonBars(244L, 353L, 33L, 361L)
+ ),
+ 0, 0,
+ UniformStyle.ThirdUpgrade,
+ 2,
+ None,
+ Some(Cosmetics(true, true, true, true, false)),
+ None,
+ DrawnSlot.Pistol1
+ )
+ val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3380), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ val pkt_bitv = pkt.toBitVector
+ val ori_bitv = string_character_backpack.toBitVector
+ pkt_bitv.take(300) mustEqual ori_bitv.take(300) //skip 2
+ pkt_bitv.drop(302).take(14) mustEqual ori_bitv.drop(302).take(14) //skip 126
+ pkt_bitv.drop(442).take(317) mustEqual ori_bitv.drop(442).take(317) //skip 2
+ pkt_bitv.drop(761).take(155) mustEqual ori_bitv.drop(761).take(155) //skip 1
+ pkt_bitv.drop(917) mustEqual ori_bitv.drop(917)
+ //TODO work on CharacterData to make this pass as a single stream
}
}
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index 56b00e01..091a5035 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -10,7 +10,7 @@ import scodec.bits._
import org.log4s.MDC
import MDCContextAware.Implicits._
import net.psforever.packet.game.objectcreate._
-import net.psforever.types.{ChatMessageType, TransactionType, PlanetSideEmpire, Vector3}
+import net.psforever.types._
class WorldSessionActor extends Actor with MDCContextAware {
private[this] val log = org.log4s.getLogger
@@ -111,33 +111,45 @@ class WorldSessionActor extends Actor with MDCContextAware {
//val objectHex = hex"18 57 0C 00 00 BC 84 B0 06 C2 D7 65 53 5C A1 60 00 01 34 40 00 09 70 49 00 6C 00 6C 00 6C 00 49 00 49 00 49 00 6C 00 6C 00 6C 00 49 00 6C 00 49 00 6C 00 6C 00 49 00 6C 00 6C 00 6C 00 49 00 6C 00 6C 00 49 00 84 52 70 76 1E 80 80 00 00 00 00 00 3F FF C0 00 00 00 20 00 00 0F F6 A7 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FD 90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 64 00 00 01 00 7E C8 00 C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 C0 00 42 C5 46 86 C7 00 00 00 80 00 00 12 40 78 70 65 5F 73 61 6E 63 74 75 61 72 79 5F 68 65 6C 70 90 78 70 65 5F 74 68 5F 66 69 72 65 6D 6F 64 65 73 8B 75 73 65 64 5F 62 65 61 6D 65 72 85 6D 61 70 31 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 0A 23 02 60 04 04 40 00 00 10 00 06 02 08 14 D0 08 0C 80 00 02 00 02 6B 4E 00 82 88 00 00 02 00 00 C0 41 C0 9E 01 01 90 00 00 64 00 44 2A 00 10 91 00 00 00 40 00 18 08 38 94 40 20 32 00 00 00 80 19 05 48 02 17 20 00 00 08 00 70 29 80 43 64 00 00 32 00 0E 05 40 08 9C 80 00 06 40 01 C0 AA 01 19 90 00 00 C8 00 3A 15 80 28 72 00 00 19 00 04 0A B8 05 26 40 00 03 20 06 C2 58 00 A7 88 00 00 02 00 00 80 00 00"
//currently, the character's starting BEP is discarded due to unknown bit format
val app = CharacterAppearanceData(
- Vector3(3674.8438f, 2726.789f, 91.15625f),
- 19,
- PlanetSideEmpire.VS,
+ PlacementData(
+ Vector3(3674.8438f, 2726.789f, 91.15625f),
+ 0, 0,
+ 19
+ ),
+ BasicCharacterData(
+ "IlllIIIlllIlIllIlllIllI",
+ PlanetSideEmpire.VS,
+ CharacterGender.Female,
+ 41,
+ 1
+ ),
+ 3,
+ false,
+ false,
+ ExoSuitType.Standard,
+ "",
+ 0,
+ false,
+ 0, 181,
+ true,
+ GrenadeState.None,
+ false,
+ false,
false,
- 4,
- "IlllIIIlllIlIllIlllIllI",
- 4,
- 2,
- 2, 9,
- 1,
- 3, 118, 30, 0x8080, 0xFFFF, 2,
- 255, 106, 7,
RibbonBars()
)
- val inv =
- InventoryItem(ObjectClass.beamer, PlanetSideGUID(76), 0, WeaponData(8, ObjectClass.energy_cell, PlanetSideGUID(77), 0, AmmoBoxData(16))) ::
- InventoryItem(ObjectClass.suppressor, PlanetSideGUID(78), 2, WeaponData(8, ObjectClass.bullet_9mm, PlanetSideGUID(79), 0, AmmoBoxData(25))) ::
- InventoryItem(ObjectClass.forceblade, PlanetSideGUID(80), 4, WeaponData(8, ObjectClass.melee_ammo, PlanetSideGUID(81), 0, AmmoBoxData(1))) ::
- InventoryItem(ObjectClass.locker_container, PlanetSideGUID(82), 5, AmmoBoxData(1)) ::
- InventoryItem(ObjectClass.bullet_9mm, PlanetSideGUID(83), 6, AmmoBoxData(50)) ::
- InventoryItem(ObjectClass.bullet_9mm, PlanetSideGUID(84), 9, AmmoBoxData(50)) ::
- InventoryItem(ObjectClass.bullet_9mm, PlanetSideGUID(85), 12, AmmoBoxData(50)) ::
- InventoryItem(ObjectClass.bullet_9mm_AP, PlanetSideGUID(86), 33, AmmoBoxData(50)) ::
- InventoryItem(ObjectClass.energy_cell, PlanetSideGUID(87), 36, AmmoBoxData(50)) ::
- InventoryItem(ObjectClass.remote_electronics_kit, PlanetSideGUID(88), 39, REKData(8)) ::
+ val inv = InventoryItem(ObjectClass.beamer, PlanetSideGUID(76), 0, DetailedWeaponData(8, ObjectClass.energy_cell, PlanetSideGUID(77), 0, DetailedAmmoBoxData(8, 16))) ::
+ InventoryItem(ObjectClass.suppressor, PlanetSideGUID(78), 2, DetailedWeaponData(8, ObjectClass.bullet_9mm, PlanetSideGUID(79), 0, DetailedAmmoBoxData(8, 25))) ::
+ InventoryItem(ObjectClass.forceblade, PlanetSideGUID(80), 4, DetailedWeaponData(8, ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1))) ::
+ InventoryItem(ObjectClass.locker_container, PlanetSideGUID(82), 5, DetailedAmmoBoxData(8, 1)) ::
+ InventoryItem(ObjectClass.bullet_9mm, PlanetSideGUID(83), 6, DetailedAmmoBoxData(8, 50)) ::
+ InventoryItem(ObjectClass.bullet_9mm, PlanetSideGUID(84), 9, DetailedAmmoBoxData(8, 50)) ::
+ InventoryItem(ObjectClass.bullet_9mm, PlanetSideGUID(85), 12, DetailedAmmoBoxData(8, 50)) ::
+ InventoryItem(ObjectClass.bullet_9mm_AP, PlanetSideGUID(86), 33, DetailedAmmoBoxData(8, 50)) ::
+ InventoryItem(ObjectClass.energy_cell, PlanetSideGUID(87), 36, DetailedAmmoBoxData(8, 50)) ::
+ InventoryItem(ObjectClass.remote_electronics_kit, PlanetSideGUID(88), 39, DetailedREKData(8)) ::
Nil
- val obj = CharacterData(
+ val obj = DetailedCharacterData(
app,
100, 100,
50,
@@ -146,11 +158,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
28, 4, 44, 84, 104, 1900,
"xpe_sanctuary_help" :: "xpe_th_firemodes" :: "used_beamer" :: "map13" :: Nil,
List.empty,
- InventoryData(
- true, false, false, inv
- )
+ InventoryData(inv),
+ DrawnSlot.None
)
- val objectHex = ObjectCreateMessage(0, ObjectClass.avatar, PlanetSideGUID(75), obj)
+ val objectHex = ObjectCreateDetailedMessage(ObjectClass.avatar, PlanetSideGUID(75), obj)
def handleGamePkt(pkt : PlanetSideGamePacket) = pkt match {
case ConnectToWorldRequestMessage(server, token, majorVersion, minorVersion, revision, buildDate, unk) =>
@@ -175,7 +186,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PacketCoding.CreateGamePacket(0, ActionResultMessage(false, Some(1))))
case CharacterRequestAction.Select =>
objectHex match {
- case obj @ ObjectCreateMessage(len, cls, guid, _, _) =>
+ case obj @ ObjectCreateDetailedMessage(len, cls, guid, _, _) =>
log.debug("Object: " + obj)
// LoadMapMessage 13714 in mossy .gcap
// XXX: hardcoded shit
@@ -216,7 +227,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PacketCoding.CreateGamePacket(0, SetCurrentAvatarMessage(guid,0,0)))
sendResponse(PacketCoding.CreateGamePacket(0, CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT)))
- sendResponse(PacketCoding.CreateGamePacket(0, ReplicationStreamMessage(5, Some(6), Vector(SquadListing(255))))) //clear squad list
+ sendResponse(PacketCoding.CreateGamePacket(0, ReplicationStreamMessage(5, Some(6), Vector(SquadListing())))) //clear squad list
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
@@ -286,7 +297,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ DropItemMessage(item_guid) =>
//item dropped where you spawn in VS Sanctuary
- sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(PlanetSideGUID(75), item_guid, app.pos, 0, 0, 0)))
+ sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(PlanetSideGUID(75), item_guid, app.pos.coord, 0, 0, 0)))
log.info("DropItem: " + msg)
case msg @ PickupItemMessage(item_guid, player_guid, unk1, unk2) =>
@@ -402,6 +413,18 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info("PlanetsideAttributeMessage: "+msg)
sendResponse(PacketCoding.CreateGamePacket(0,PlanetsideAttributeMessage(avatar_guid, attribute_type, attribute_value)))
+ case msg @ CreateShortcutMessage(player_guid, slot, unk, add, shortcut) =>
+ log.info("CreateShortcutMessage: "+msg)
+
+ case msg @ FriendsRequest(action, friend) =>
+ log.info("FriendsRequest: "+msg)
+
+ case msg @ HitHint(source, player) =>
+ log.info("HitHint: "+msg)
+
+ case msg @ WeaponDryFireMessage(weapon) =>
+ log.info("WeaponDryFireMessage: "+msg)
+
case default => log.error(s"Unhandled GamePacket ${pkt}")
}