meta-stability in terms of the underlying structure that will eventually read off seated passengers in vehicles; the padding offsets just need tuning

This commit is contained in:
FateJH 2018-05-30 08:27:50 -04:00
parent 052a318285
commit 4e41468cd0
9 changed files with 665 additions and 187 deletions

View file

@ -231,6 +231,21 @@ object PacketHelpers {
* @return a codec that works on a List of A but excludes the size from the encoding
*/
def listOfNSized[A](size : Long, codec : Codec[A]) : Codec[List[A]] = PacketHelpers.listOfNAligned(provide(if(size < 0) 0 else size), 0, codec)
/**
* A `peek` that decodes like the normal but encodes nothing.
* Decoding `Codec[A]` from the input vector emits a value but reverts to the prior read position.
* Encoding `Codec[A]` to the input vector appends no new data to the input vector.
* In effect, `peek` is a harmless meta-`Codec` that introduces no changes to the input vector.
* @see `scodec.codecs.peek` or `codecs/package.scala:peek`
* @param target codec that decodes the value
* @return codec that behaves the same as `target` but resets remainder to the input vector
*/
def peek[A](target: Codec[A]): Codec[A] = new Codec[A] {
def sizeBound = target.sizeBound
def encode(a: A) = Attempt.Successful(BitVector.empty)
def decode(b: BitVector) = target.decode(b).map { _.mapRemainder(_ => b) }
}
}
/**

View file

@ -71,28 +71,28 @@ object ObjectCreateDetailedMessage extends Marshallable[ObjectCreateDetailedMess
ObjectCreateDetailedMessage(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
* @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
}
// /**
// * 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] (
{
@ -100,7 +100,14 @@ object ObjectCreateDetailedMessage extends Marshallable[ObjectCreateDetailedMess
Attempt.failure(Err("no data to decode"))
case len :: cls :: guid :: par :: data :: HNil =>
val obj = ObjectCreateBase.decodeData(cls, data, ObjectClass.selectDataDetailedCodec)
val obj = ObjectCreateBase.decodeData(cls, data,
if(par.isDefined) {
ObjectClass.selectDataDetailedCodec
}
else {
ObjectClass.selectDataDroppedDetailedCodec
}
)
Attempt.successful(ObjectCreateDetailedMessage(len, cls, guid, par, obj))
},
{
@ -109,7 +116,14 @@ object ObjectCreateDetailedMessage extends Marshallable[ObjectCreateDetailedMess
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)
val bitvec = ObjectCreateBase.encodeData(cls, obj,
if(par.isDefined) {
ObjectClass.selectDataDetailedCodec
}
else {
ObjectClass.selectDataDroppedDetailedCodec
}
)
Attempt.successful(len :: cls :: guid :: par :: bitvec :: HNil)
}
)

View file

@ -148,7 +148,8 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
*/
def namePaddingRule(pad : Int) : Int =
if(pad == 0) {
1 //normal alignment padding for the string
//note that this counts as 1 (local) + 4 (inherited from PlayerData OCM with parent)
5 //normal alignment padding
}
else {
pad //custom padding value
@ -171,7 +172,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
("jammered" | bool) ::
bool :: //crashes client
uint(16) :: //unknown, but usually 0
("name" | PacketHelpers.encodedWideStringAligned( namePaddingRule(name_padding) )) ::
("name" | PacketHelpers.encodedWideStringAligned(name_padding)) ::
("exosuit" | ExoSuitType.codec) ::
ignore(2) :: //unknown
("sex" | CharacterGender.codec) ::

View file

@ -115,7 +115,7 @@ object CharacterData extends Marshallable[CharacterData] {
{
case health :: armor :: uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: HNil =>
val newHealth = if(is_backpack) { 0 } else { health }
Attempt.Successful(new CharacterData(newHealth, armor, uniform, 0, cr, implant_effects, cosmetics)(is_backpack))
Attempt.Successful(CharacterData(newHealth, armor, uniform, 0, cr, implant_effects, cosmetics)(is_backpack))
case _ =>
Attempt.Failure(Err("invalid character data; can not encode"))
@ -130,5 +130,30 @@ object CharacterData extends Marshallable[CharacterData] {
}
)
def codec_seated(is_backpack : Boolean) : Codec[CharacterData] = (
("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" | Cosmetics.codec)
}
).exmap[CharacterData] (
{
case uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: HNil =>
Attempt.Successful(new CharacterData(100, 0, uniform, 0, cr, implant_effects, cosmetics)(is_backpack))
case _ =>
Attempt.Failure(Err("invalid character data; can not encode"))
},
{
case CharacterData(_, _, uniform, _, cr, implant_effects, cosmetics) =>
Attempt.Successful(uniform :: () :: cr :: false :: implant_effects :: cosmetics :: HNil)
case _ =>
Attempt.Failure(Err("invalid character data; can not decode"))
}
)
implicit val codec : Codec[CharacterData] = codec(false)
}

View file

@ -666,13 +666,21 @@ object ObjectClass {
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(DetailedPlayerData.codec(true), "avatar")
case ObjectClass.avatar => ConstructorData.genericCodec(DetailedPlayerData.codec(false), "avatar")
case ObjectClass.locker_container => ConstructorData.genericCodec(DetailedLockerContainerData.codec, "locker container")
//failure case
case _ => defaultFailureCodec(objClass)
}
def selectDataDroppedDetailedCodec(objClass : Int) : Codec[ConstructorData.genericPattern] =
(objClass : @switch) match {
//special cases
case ObjectClass.avatar => ConstructorData.genericCodec(DetailedPlayerData.codec(true), "avatar")
//defer to other codec selection
case _ => selectDataDetailedCodec(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.<br>

View file

@ -1,6 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.newcodecs._
import net.psforever.packet.Marshallable
import scodec.codecs._
import scodec.Codec
@ -94,21 +95,43 @@ object PlayerData extends Marshallable[PlayerData] {
* @return the pad length in bits
*/
def placementOffset(pos : Option[PlacementData]) : Int = {
if(pos.isEmpty) {
0
}
else if(pos.get.vel.isDefined) {
2
}
else {
4
pos match {
case Some(place) =>
if(place.vel.isDefined) { 2 } else { 4 }
case None =>
0
}
}
def codec(position_defined : Boolean) : Codec[PlayerData] = (
conditional(position_defined, "pos" | PlacementData.codec) >>:~ { pos =>
("basic_appearance" | CharacterAppearanceData.codec(placementOffset(pos))) >>:~ { app =>
("character_data" | CharacterData.codec(app.backpack)) ::
("character_data" | newcodecs.binary_choice(position_defined,
CharacterData.codec(app.backpack),
CharacterData.codec_seated(app.backpack))) ::
optional(bool, "inventory" | InventoryData.codec) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false
}
}).xmap[PlayerData] (
{
case pos :: app :: data :: inv :: hand :: _ :: HNil =>
PlayerData(pos, app, data, inv, hand)(pos.isDefined)
},
{
case PlayerData(pos, app, data, inv, hand) =>
pos :: app :: data :: inv :: hand :: false :: HNil
}
)
def codec(position_defined : Boolean, offset : Int) : Codec[PlayerData] = (
conditional(position_defined, "pos" | PlacementData.codec) >>:~ { pos =>
("basic_appearance" | CharacterAppearanceData.codec(offset)) >>:~ { app =>
("character_data" | newcodecs.binary_choice(position_defined,
CharacterData.codec(app.backpack),
CharacterData.codec_seated(app.backpack))) ::
optional(bool, "inventory" | InventoryData.codec) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false

View file

@ -1,13 +1,16 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.{Marshallable, PacketHelpers}
import scodec.Attempt.{Failure, Successful}
import scodec.{Attempt, Codec, Err}
import shapeless.HNil
import scodec.codecs._
import shapeless.{::, HNil}
import net.psforever.types.DriveState
import scala.collection.mutable.ListBuffer
/**
* An `Enumeration` of the various formats that known structures that the stream of bits for `VehicleData` can assume.
*/
@ -148,33 +151,39 @@ object VehicleData extends Marshallable[VehicleData] {
/**
* `Codec` for the "utility" format.
*/
private val utility_data_codec : Codec[SpecificVehicleData] = uintL(6).hlist.exmap[SpecificVehicleData] (
{
case n :: HNil =>
Successful(UtilityVehicleData(n).asInstanceOf[SpecificVehicleData])
},
{
case UtilityVehicleData(n) =>
Successful(n :: HNil)
case _ =>
Failure(Err("wrong kind of vehicle data object (wants 'Utility')"))
}
)
private val utility_data_codec : Codec[SpecificVehicleData] = {
import shapeless.::
uintL(6).hlist.exmap[SpecificVehicleData] (
{
case n :: HNil =>
Successful(UtilityVehicleData(n).asInstanceOf[SpecificVehicleData])
},
{
case UtilityVehicleData(n) =>
Successful(n :: HNil)
case _ =>
Failure(Err("wrong kind of vehicle data object (wants 'Utility')"))
}
)
}
/**
* `Codec` for the "variant" format.
*/
private val variant_data_codec : Codec[SpecificVehicleData] = uint8L.hlist.exmap[SpecificVehicleData] (
{
case n :: HNil =>
Successful(VariantVehicleData(n).asInstanceOf[SpecificVehicleData])
},
{
case VariantVehicleData(n) =>
Successful(n :: HNil)
case _ =>
Failure(Err("wrong kind of vehicle data object (wants 'Variant')"))
}
)
private val variant_data_codec : Codec[SpecificVehicleData] = {
import shapeless.::
uint8L.hlist.exmap[SpecificVehicleData] (
{
case n :: HNil =>
Successful(VariantVehicleData(n).asInstanceOf[SpecificVehicleData])
},
{
case VariantVehicleData(n) =>
Successful(n :: HNil)
case _ =>
Failure(Err("wrong kind of vehicle data object (wants 'Variant')"))
}
)
}
/**
* Select an appropriate `Codec` in response to the requested stream format
@ -190,47 +199,202 @@ object VehicleData extends Marshallable[VehicleData] {
Failure(Err(s"$vehicleFormat is not a valid vehicle format for parsing data")).asInstanceOf[Codec[SpecificVehicleData]]
}
def codec(vehicle_type : VehicleFormat.Value) : Codec[VehicleData] = (
("basic" | CommonFieldData.codec) ::
("unk1" | uint2L) ::
("health" | uint8L) ::
("unk2" | bool) :: //usually 0
("no_mount_points" | bool) ::
("driveState" | driveState8u) :: //used for deploy state
("unk3" | bool) :: //unknown but generally false; can cause stream misalignment if set when unexpectedly
("unk4" | bool) ::
("cloak" | bool) :: //cloak as wraith, phantasm
conditional(vehicle_type != VehicleFormat.Normal, "unk5" | selectFormatReader(vehicle_type)) :: //padding?
optional(bool, "inventory" | InventoryData.codec)
).exmap[VehicleData] (
{
case basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: u5 :: cloak :: inv :: HNil =>
Attempt.successful(new VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, u5, cloak, inv)(vehicle_type))
def codec(vehicle_type : VehicleFormat.Value) : Codec[VehicleData] = {
import shapeless.::
(
("basic" | CommonFieldData.codec) >>:~ { com =>
("unk1" | uint2L) ::
("health" | uint8L) ::
("unk2" | bool) :: //usually 0
("no_mount_points" | bool) ::
("driveState" | driveState8u) :: //used for deploy state
("unk3" | bool) :: //unknown but generally false; can cause stream misalignment if set when unexpectedly
("unk4" | bool) ::
("cloak" | bool) :: //cloak as wraith, phantasm
conditional(vehicle_type != VehicleFormat.Normal, "unk5" | selectFormatReader(vehicle_type)) :: //padding?
optional(bool, "inventory" | custom_inventory_codec(InitialStreamLengthToSeatEntries(com, vehicle_type)))
}
).exmap[VehicleData] (
{
case basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: u5 :: cloak :: inv :: HNil =>
Attempt.successful(new VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, u5, cloak, inv)(vehicle_type))
case _ =>
Attempt.failure(Err("invalid vehicle data format"))
},
{
case obj @ VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, cloak, Some(u5), inv) =>
if(obj.vehicle_type == VehicleFormat.Normal) {
Attempt.failure(Err("invalid vehicle data format; variable bits not expected; will ignore ..."))
}
else {
Attempt.successful(basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: cloak :: Some(u5) :: inv :: HNil)
}
case _ =>
Attempt.failure(Err("invalid vehicle data format"))
},
{
case obj @ VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, cloak, Some(u5), inv) =>
if(obj.vehicle_type == VehicleFormat.Normal) {
Attempt.failure(Err("invalid vehicle data format; variable bits not expected; will ignore ..."))
}
else {
Attempt.successful(basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: cloak :: Some(u5) :: inv :: HNil)
}
case obj @ VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, cloak, None, inv) =>
if(obj.vehicle_type != VehicleFormat.Normal) {
Attempt.failure(Err("invalid vehicle data format; variable bits expected"))
}
else {
Attempt.successful(basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: cloak :: None :: inv :: HNil)
}
case obj @ VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, cloak, None, inv) =>
if(obj.vehicle_type != VehicleFormat.Normal) {
Attempt.failure(Err("invalid vehicle data format; variable bits expected"))
}
else {
Attempt.successful(basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: cloak :: None :: inv :: HNil)
}
case _ =>
Attempt.failure(Err("invalid vehicle data format"))
case _ =>
Attempt.failure(Err("invalid vehicle data format"))
}
)
}
private def InitialStreamLengthToSeatEntries(com : CommonFieldData, format : VehicleFormat.Type) : Long = {
198 +
(if(com.pos.vel.isDefined) { 42 } else { 0 }) +
(format match {
case VehicleFormat.Utility => 6
case VehicleFormat.Variant => 8
case _ => 0
})
}
private def custom_inventory_codec(length : Long) : Codec[InventoryData] = {
import shapeless.::
(
uint8 >>:~ { size =>
uint2 ::
(inventory_seat_codec(
length, //length of stream until current seat
{ //calculated offset of name field in next seat
val next = length + 23 + 35 //in bits: InternalSlot lead + length of CharacterAppearanceData~>name
val pad =((next - math.floor(next / 8) * 8) % 8).toInt
if(pad > 0) {
8 - pad
}
else {
0
}
}
) >>:~ { seats =>
PacketHelpers.listOfNSized(size - countSeats(seats), InternalSlot.codec).hlist
})
}
).xmap[InventoryData] (
{
case _ :: _ :: None :: inv :: HNil =>
InventoryData(inv)
case _ :: _ :: seats :: inv :: HNil =>
InventoryData(unlinkSeats(seats) ++ inv)
},
{
case InventoryData(inv) =>
val (seats, slots) = inv.partition(entry => entry.objectClass == ObjectClass.avatar)
inv.size :: 0 :: chainSeats(seats) :: slots :: HNil
}
)
}
private case class InventorySeat(seat : Option[InternalSlot], next : Option[InventorySeat])
private def inventory_seat_codec(length : Long, offset : Int) : Codec[Option[InventorySeat]] = {
import shapeless.::
(
PacketHelpers.peek(uintL(11)) >>:~ { objClass =>
conditional(objClass == ObjectClass.avatar, seat_codec(offset)) >>:~ { seat =>
conditional(objClass == ObjectClass.avatar, inventory_seat_codec(
{ //length of stream until next seat
length + (seat match {
case Some(o) => o.bitsize
case None => 0
})
},
{ //calculated offset of name field in next seat
val next = length + 23 + 35 + (seat match {
case Some(o) => o.bitsize
case None => 0
})
val pad =((next - math.floor(next / 8) * 8) % 8).toInt
if(pad > 0) {
8 - pad
}
else {
0
}
})).hlist
}
}
).exmap[Option[InventorySeat]] (
{
case _ :: None :: None :: HNil =>
Successful(None)
case _ :: slot :: Some(next) :: HNil =>
Successful(Some(InventorySeat(slot, next)))
},
{
case None =>
Successful(0 :: None :: None :: HNil)
case Some(InventorySeat(slot, None)) =>
Successful(ObjectClass.avatar :: slot :: None :: HNil)
case Some(InventorySeat(slot, next)) =>
Successful(ObjectClass.avatar :: slot :: Some(next) :: HNil)
}
)
}
private def seat_codec(pad : Int) : Codec[InternalSlot] = {
import shapeless.::
(
("objectClass" | uintL(11)) ::
("guid" | PlanetSideGUID.codec) ::
("parentSlot" | PacketHelpers.encodedStringSize) ::
("obj" | PlayerData.codec(false, pad))
).xmap[InternalSlot] (
{
case objectClass :: guid :: parentSlot :: obj :: HNil =>
InternalSlot(objectClass, guid, parentSlot, obj)
},
{
case InternalSlot(objectClass, guid, parentSlot, obj) =>
objectClass :: guid :: parentSlot :: obj.asInstanceOf[PlayerData] :: HNil
}
)
}
private def countSeats(chain : Option[InventorySeat]) : Int = {
chain match {
case None =>
0
case Some(link) =>
if(link.seat.isDefined) { 1 } else { 0 } + countSeats(link.next)
}
)
}
private def unlinkSeats(chain : Option[InventorySeat]) : List[InternalSlot] = {
var curr = chain
val out = new ListBuffer[InternalSlot]
while(curr.isDefined) {
curr.get.seat match {
case None =>
curr = None
case Some(seat) =>
out += seat
curr = curr.get.next
}
}
out.toList
}
private def chainSeats(list : List[InternalSlot]) : Option[InventorySeat] = {
list match {
case Nil =>
None
case x :: Nil =>
Some(InventorySeat(Some(x), None))
case x :: xs =>
Some(InventorySeat(Some(x), chainSeats(xs)))
}
}
implicit val codec : Codec[VehicleData] = codec(VehicleFormat.Normal)
}

File diff suppressed because one or more lines are too long

View file

@ -11,100 +11,111 @@ import scodec.bits._
class UtilityVehiclesTest extends Specification {
val string_ant = hex"17 C2000000 9E0 7C01 6C2D7 65535 CA16 00 00 00 4400003FC000000"
val string_ams = hex"17 B8010000 970 3D10 002D765535CA16000000 402285BB0037E4100749E1D03000000620D83A0A00000195798741C00000332E40D84800000"
val string_ams_seated =
hex"17ec060000970fe0f030898abda28127f007ff9c1f2f80c0001e18ff00001051e40786400000008c50004c0041006d0069006e006700790075006500540052007c00000304217c859e8080000000000000002503420022c02a002a002a002a0050004c0041002a002a002a002a00010027e300940000016c0400023c040002285a086c2f00c80000000000300210288740800000004046f17423018000002c4d6190400000001010704a86406000002bc770842000000004041c5f21d01800000e075821902000000623e84208000001950588c1800000332ea0f840000000"
"Utility vehicles" should {
"decode (ant)" in {
PacketCoding.DecodePacket(string_ant).require match {
// "decode (ant)" in {
// PacketCoding.DecodePacket(string_ant).require match {
// case ObjectCreateMessage(len, cls, guid, parent, data) =>
// len mustEqual 194L
// cls mustEqual ObjectClass.ant
// guid mustEqual PlanetSideGUID(380)
// parent.isDefined mustEqual false
// data.isDefined mustEqual true
// data.get.isInstanceOf[VehicleData] mustEqual true
// val ant = data.get.asInstanceOf[VehicleData]
// ant.basic.pos.coord.x mustEqual 3674.8438f
// ant.basic.pos.coord.y mustEqual 2726.789f
// ant.basic.pos.coord.z mustEqual 91.15625f
// ant.basic.pos.orient.x mustEqual 0f
// ant.basic.pos.orient.y mustEqual 0f
// ant.basic.pos.orient.z mustEqual 90.0f
// ant.basic.faction mustEqual PlanetSideEmpire.VS
// ant.basic.unk mustEqual 2
// ant.basic.player_guid mustEqual PlanetSideGUID(0)
// ant.health mustEqual 255
// ant.driveState mustEqual DriveState.Mobile
// case _ =>
// ko
// }
// }
//
// "decode (ams)" in {
// PacketCoding.DecodePacket(string_ams).require match {
// case ObjectCreateMessage(len, cls, guid, parent, data) =>
// len mustEqual 440L
// cls mustEqual ObjectClass.ams
// guid mustEqual PlanetSideGUID(4157)
// parent.isDefined mustEqual false
// data.isDefined mustEqual true
// data.get.isInstanceOf[VehicleData] mustEqual true
// val ams = data.get.asInstanceOf[VehicleData]
// ams.basic.pos.coord.x mustEqual 3674.0f
// ams.basic.pos.coord.y mustEqual 2726.789f
// ams.basic.pos.coord.z mustEqual 91.15625f
// ams.basic.pos.orient.x mustEqual 0f
// ams.basic.pos.orient.y mustEqual 0f
// ams.basic.pos.orient.z mustEqual 90.0f
// ams.basic.faction mustEqual PlanetSideEmpire.VS
// ams.basic.unk mustEqual 0
// ams.basic.player_guid mustEqual PlanetSideGUID(34082)
// ams.unk1 mustEqual 2
// ams.health mustEqual 236
// ams.unk2 mustEqual false
// ams.driveState mustEqual DriveState.Deployed
//
// ams.inventory.isDefined mustEqual true
// val inv = ams.inventory.get.contents
// inv.head.objectClass mustEqual ObjectClass.matrix_terminalc
// inv.head.guid mustEqual PlanetSideGUID(3663)
// inv.head.parentSlot mustEqual 1
// inv.head.obj.isInstanceOf[CommonTerminalData] mustEqual true
// inv(1).objectClass mustEqual ObjectClass.ams_respawn_tube
// inv(1).guid mustEqual PlanetSideGUID(3638)
// inv(1).parentSlot mustEqual 2
// inv(1).obj.isInstanceOf[CommonTerminalData] mustEqual true
// inv(2).objectClass mustEqual ObjectClass.order_terminala
// inv(2).guid mustEqual PlanetSideGUID(3827)
// inv(2).parentSlot mustEqual 3
// inv(2).obj.isInstanceOf[CommonTerminalData] mustEqual true
// inv(3).objectClass mustEqual ObjectClass.order_terminalb
// inv(3).guid mustEqual PlanetSideGUID(3556)
// inv(3).parentSlot mustEqual 4
// inv(3).obj.isInstanceOf[CommonTerminalData] mustEqual true
// case _ =>
// ko
// }
// }
//
"decode (ams, seated)" in {
PacketCoding.DecodePacket(string_ams_seated).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 194L
cls mustEqual ObjectClass.ant
guid mustEqual PlanetSideGUID(380)
parent.isDefined mustEqual false
data.isDefined mustEqual true
data.get.isInstanceOf[VehicleData] mustEqual true
val ant = data.get.asInstanceOf[VehicleData]
ant.basic.pos.coord.x mustEqual 3674.8438f
ant.basic.pos.coord.y mustEqual 2726.789f
ant.basic.pos.coord.z mustEqual 91.15625f
ant.basic.pos.orient.x mustEqual 0f
ant.basic.pos.orient.y mustEqual 0f
ant.basic.pos.orient.z mustEqual 90.0f
ant.basic.faction mustEqual PlanetSideEmpire.VS
ant.basic.unk mustEqual 2
ant.basic.player_guid mustEqual PlanetSideGUID(0)
ant.health mustEqual 255
ant.driveState mustEqual DriveState.Mobile
case _ =>
ko
}
}
"decode (ams)" in {
PacketCoding.DecodePacket(string_ams).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 440L
cls mustEqual ObjectClass.ams
guid mustEqual PlanetSideGUID(4157)
parent.isDefined mustEqual false
data.isDefined mustEqual true
data.get.isInstanceOf[VehicleData] mustEqual true
val ams = data.get.asInstanceOf[VehicleData]
ams.basic.pos.coord.x mustEqual 3674.0f
ams.basic.pos.coord.y mustEqual 2726.789f
ams.basic.pos.coord.z mustEqual 91.15625f
ams.basic.pos.orient.x mustEqual 0f
ams.basic.pos.orient.y mustEqual 0f
ams.basic.pos.orient.z mustEqual 90.0f
ams.basic.faction mustEqual PlanetSideEmpire.VS
ams.basic.unk mustEqual 0
ams.basic.player_guid mustEqual PlanetSideGUID(34082)
ams.unk1 mustEqual 2
ams.health mustEqual 236
ams.unk2 mustEqual false
ams.driveState mustEqual DriveState.Deployed
ams.inventory.isDefined mustEqual true
val inv = ams.inventory.get.contents
inv.head.objectClass mustEqual ObjectClass.matrix_terminalc
inv.head.guid mustEqual PlanetSideGUID(3663)
inv.head.parentSlot mustEqual 1
inv.head.obj.isInstanceOf[CommonTerminalData] mustEqual true
inv(1).objectClass mustEqual ObjectClass.ams_respawn_tube
inv(1).guid mustEqual PlanetSideGUID(3638)
inv(1).parentSlot mustEqual 2
inv(1).obj.isInstanceOf[CommonTerminalData] mustEqual true
inv(2).objectClass mustEqual ObjectClass.order_terminala
inv(2).guid mustEqual PlanetSideGUID(3827)
inv(2).parentSlot mustEqual 3
inv(2).obj.isInstanceOf[CommonTerminalData] mustEqual true
inv(3).objectClass mustEqual ObjectClass.order_terminalb
inv(3).guid mustEqual PlanetSideGUID(3556)
inv(3).parentSlot mustEqual 4
inv(3).obj.isInstanceOf[CommonTerminalData] mustEqual true
case _ =>
ko
}
}
"encode (ant)" in {
val obj = VehicleData(
CommonFieldData(
PlacementData(3674.8438f, 2726.789f, 91.15625f, 0f, 0f, 90.0f),
PlanetSideEmpire.VS, 2
),
0,
255,
false, false,
DriveState.Mobile,
false, false, false,
Some(UtilityVehicleData(0)),
None
)(VehicleFormat.Utility)
val msg = ObjectCreateMessage(ObjectClass.ant, PlanetSideGUID(380), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_ant
}
//
// "encode (ant)" in {
// val obj = VehicleData(
// CommonFieldData(
// PlacementData(3674.8438f, 2726.789f, 91.15625f, 0f, 0f, 90.0f),
// PlanetSideEmpire.VS, 2
// ),
// 0,
// 255,
// false, false,
// DriveState.Mobile,
// false, false, false,
// Some(UtilityVehicleData(0)),
// None
// )(VehicleFormat.Utility)
// val msg = ObjectCreateMessage(ObjectClass.ant, PlanetSideGUID(380), obj)
// val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
//
// pkt mustEqual string_ant
// }
"encode (ams)" in {
val obj = VehicleData(