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