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 * @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) 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)) ObjectCreateDetailedMessage(ObjectCreateBase.streamLen(None, data), objectClass, guid, None, Some(data))
} }
/** // /**
* Take the important information of a game piece and transform it into bit 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. // * 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. // * Generally, the `Exception` messages themselves are not useful here.
* @param objClass the code for the type of object being deconstructed // * @param objClass the code for the type of object being deconstructed
* @param obj the object data // * @param obj the object data
* @return the bitstream data // * @return the bitstream data
* @see ObjectClass.selectDataCodec // * @see ObjectClass.selectDataCodec
*/ // */
def encodeData(objClass : Int, obj : ConstructorData, getCodecFunc : (Int) => Codec[ConstructorData.genericPattern]) : BitVector = { // def encodeData(objClass : Int, obj : ConstructorData, getCodecFunc : (Int) => Codec[ConstructorData.genericPattern]) : BitVector = {
var out = BitVector.empty // var out = BitVector.empty
try { // try {
val outOpt : Option[BitVector] = getCodecFunc(objClass).encode(Some(obj.asInstanceOf[ConstructorData])).toOption // val outOpt : Option[BitVector] = getCodecFunc(objClass).encode(Some(obj.asInstanceOf[ConstructorData])).toOption
if(outOpt.isDefined) // if(outOpt.isDefined)
out = outOpt.get // out = outOpt.get
} // }
catch { // catch {
case _ : Exception => // case _ : Exception =>
//catch and release, any sort of parse error // //catch and release, any sort of parse error
} // }
out // out
} // }
implicit val codec : Codec[ObjectCreateDetailedMessage] = ObjectCreateBase.baseCodec.exmap[ObjectCreateDetailedMessage] ( 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")) Attempt.failure(Err("no data to decode"))
case len :: cls :: guid :: par :: data :: HNil => 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)) Attempt.successful(ObjectCreateDetailedMessage(len, cls, guid, par, obj))
}, },
{ {
@ -109,7 +116,14 @@ object ObjectCreateDetailedMessage extends Marshallable[ObjectCreateDetailedMess
case ObjectCreateDetailedMessage(_, cls, guid, par, Some(obj)) => 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 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) Attempt.successful(len :: cls :: guid :: par :: bitvec :: HNil)
} }
) )

View file

@ -148,7 +148,8 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
*/ */
def namePaddingRule(pad : Int) : Int = def namePaddingRule(pad : Int) : Int =
if(pad == 0) { 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 { else {
pad //custom padding value pad //custom padding value
@ -171,7 +172,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
("jammered" | bool) :: ("jammered" | bool) ::
bool :: //crashes client bool :: //crashes client
uint(16) :: //unknown, but usually 0 uint(16) :: //unknown, but usually 0
("name" | PacketHelpers.encodedWideStringAligned( namePaddingRule(name_padding) )) :: ("name" | PacketHelpers.encodedWideStringAligned(name_padding)) ::
("exosuit" | ExoSuitType.codec) :: ("exosuit" | ExoSuitType.codec) ::
ignore(2) :: //unknown ignore(2) :: //unknown
("sex" | CharacterGender.codec) :: ("sex" | CharacterGender.codec) ::

View file

@ -115,7 +115,7 @@ object CharacterData extends Marshallable[CharacterData] {
{ {
case health :: armor :: uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: HNil => case health :: armor :: uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: HNil =>
val newHealth = if(is_backpack) { 0 } else { health } 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 _ => case _ =>
Attempt.Failure(Err("invalid character data; can not encode")) 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) 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.advanced_ace => ConstructorData.genericCodec(DetailedACEData.codec, "advanced ace")
case ObjectClass.boomer_trigger => ConstructorData.genericCodec(DetailedBoomerTriggerData.codec, "boomer trigger") case ObjectClass.boomer_trigger => ConstructorData.genericCodec(DetailedBoomerTriggerData.codec, "boomer trigger")
//other //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") case ObjectClass.locker_container => ConstructorData.genericCodec(DetailedLockerContainerData.codec, "locker container")
//failure case //failure case
case _ => defaultFailureCodec(objClass) 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. * 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> * This function services `0x17` `ObjectCreateMessage` packet data.<br>

View file

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

View file

@ -1,13 +1,16 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate package net.psforever.packet.game.objectcreate
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.{Marshallable, PacketHelpers} import net.psforever.packet.{Marshallable, PacketHelpers}
import scodec.Attempt.{Failure, Successful} import scodec.Attempt.{Failure, Successful}
import scodec.{Attempt, Codec, Err} import scodec.{Attempt, Codec, Err}
import shapeless.HNil
import scodec.codecs._ import scodec.codecs._
import shapeless.{::, HNil}
import net.psforever.types.DriveState 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. * 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. * `Codec` for the "utility" format.
*/ */
private val utility_data_codec : Codec[SpecificVehicleData] = uintL(6).hlist.exmap[SpecificVehicleData] ( private val utility_data_codec : Codec[SpecificVehicleData] = {
{ import shapeless.::
case n :: HNil => uintL(6).hlist.exmap[SpecificVehicleData] (
Successful(UtilityVehicleData(n).asInstanceOf[SpecificVehicleData]) {
}, case n :: HNil =>
{ Successful(UtilityVehicleData(n).asInstanceOf[SpecificVehicleData])
case UtilityVehicleData(n) => },
Successful(n :: HNil) {
case _ => case UtilityVehicleData(n) =>
Failure(Err("wrong kind of vehicle data object (wants 'Utility')")) Successful(n :: HNil)
} case _ =>
) Failure(Err("wrong kind of vehicle data object (wants 'Utility')"))
}
)
}
/** /**
* `Codec` for the "variant" format. * `Codec` for the "variant" format.
*/ */
private val variant_data_codec : Codec[SpecificVehicleData] = uint8L.hlist.exmap[SpecificVehicleData] ( private val variant_data_codec : Codec[SpecificVehicleData] = {
{ import shapeless.::
case n :: HNil => uint8L.hlist.exmap[SpecificVehicleData] (
Successful(VariantVehicleData(n).asInstanceOf[SpecificVehicleData]) {
}, case n :: HNil =>
{ Successful(VariantVehicleData(n).asInstanceOf[SpecificVehicleData])
case VariantVehicleData(n) => },
Successful(n :: HNil) {
case _ => case VariantVehicleData(n) =>
Failure(Err("wrong kind of vehicle data object (wants 'Variant')")) 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 * 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]] Failure(Err(s"$vehicleFormat is not a valid vehicle format for parsing data")).asInstanceOf[Codec[SpecificVehicleData]]
} }
def codec(vehicle_type : VehicleFormat.Value) : Codec[VehicleData] = ( def codec(vehicle_type : VehicleFormat.Value) : Codec[VehicleData] = {
("basic" | CommonFieldData.codec) :: import shapeless.::
("unk1" | uint2L) :: (
("health" | uint8L) :: ("basic" | CommonFieldData.codec) >>:~ { com =>
("unk2" | bool) :: //usually 0 ("unk1" | uint2L) ::
("no_mount_points" | bool) :: ("health" | uint8L) ::
("driveState" | driveState8u) :: //used for deploy state ("unk2" | bool) :: //usually 0
("unk3" | bool) :: //unknown but generally false; can cause stream misalignment if set when unexpectedly ("no_mount_points" | bool) ::
("unk4" | bool) :: ("driveState" | driveState8u) :: //used for deploy state
("cloak" | bool) :: //cloak as wraith, phantasm ("unk3" | bool) :: //unknown but generally false; can cause stream misalignment if set when unexpectedly
conditional(vehicle_type != VehicleFormat.Normal, "unk5" | selectFormatReader(vehicle_type)) :: //padding? ("unk4" | bool) ::
optional(bool, "inventory" | InventoryData.codec) ("cloak" | bool) :: //cloak as wraith, phantasm
).exmap[VehicleData] ( conditional(vehicle_type != VehicleFormat.Normal, "unk5" | selectFormatReader(vehicle_type)) :: //padding?
{ optional(bool, "inventory" | custom_inventory_codec(InitialStreamLengthToSeatEntries(com, vehicle_type)))
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)) ).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 _ => case _ =>
Attempt.failure(Err("invalid vehicle data format")) Attempt.failure(Err("invalid vehicle data format"))
}, },
{ {
case obj @ VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, cloak, Some(u5), inv) => case obj @ VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, cloak, Some(u5), inv) =>
if(obj.vehicle_type == VehicleFormat.Normal) { if(obj.vehicle_type == VehicleFormat.Normal) {
Attempt.failure(Err("invalid vehicle data format; variable bits not expected; will ignore ...")) Attempt.failure(Err("invalid vehicle data format; variable bits not expected; will ignore ..."))
} }
else { else {
Attempt.successful(basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: cloak :: Some(u5) :: inv :: HNil) Attempt.successful(basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: cloak :: Some(u5) :: inv :: HNil)
} }
case obj @ VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, cloak, None, inv) => case obj @ VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, cloak, None, inv) =>
if(obj.vehicle_type != VehicleFormat.Normal) { if(obj.vehicle_type != VehicleFormat.Normal) {
Attempt.failure(Err("invalid vehicle data format; variable bits expected")) Attempt.failure(Err("invalid vehicle data format; variable bits expected"))
} }
else { else {
Attempt.successful(basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: cloak :: None :: inv :: HNil) Attempt.successful(basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: cloak :: None :: inv :: HNil)
} }
case _ => case _ =>
Attempt.failure(Err("invalid vehicle data format")) 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) 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 { class UtilityVehiclesTest extends Specification {
val string_ant = hex"17 C2000000 9E0 7C01 6C2D7 65535 CA16 00 00 00 4400003FC000000" 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 = hex"17 B8010000 970 3D10 002D765535CA16000000 402285BB0037E4100749E1D03000000620D83A0A00000195798741C00000332E40D84800000"
val string_ams_seated =
hex"17ec060000970fe0f030898abda28127f007ff9c1f2f80c0001e18ff00001051e40786400000008c50004c0041006d0069006e006700790075006500540052007c00000304217c859e8080000000000000002503420022c02a002a002a002a0050004c0041002a002a002a002a00010027e300940000016c0400023c040002285a086c2f00c80000000000300210288740800000004046f17423018000002c4d6190400000001010704a86406000002bc770842000000004041c5f21d01800000e075821902000000623e84208000001950588c1800000332ea0f840000000"
"Utility vehicles" should { "Utility vehicles" should {
"decode (ant)" in { // "decode (ant)" in {
PacketCoding.DecodePacket(string_ant).require match { // 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) => 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.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 _ => case _ =>
ko ko
} }
} }
//
"decode (ams)" in { // "encode (ant)" in {
PacketCoding.DecodePacket(string_ams).require match { // val obj = VehicleData(
case ObjectCreateMessage(len, cls, guid, parent, data) => // CommonFieldData(
len mustEqual 440L // PlacementData(3674.8438f, 2726.789f, 91.15625f, 0f, 0f, 90.0f),
cls mustEqual ObjectClass.ams // PlanetSideEmpire.VS, 2
guid mustEqual PlanetSideGUID(4157) // ),
parent.isDefined mustEqual false // 0,
data.isDefined mustEqual true // 255,
data.get.isInstanceOf[VehicleData] mustEqual true // false, false,
val ams = data.get.asInstanceOf[VehicleData] // DriveState.Mobile,
ams.basic.pos.coord.x mustEqual 3674.0f // false, false, false,
ams.basic.pos.coord.y mustEqual 2726.789f // Some(UtilityVehicleData(0)),
ams.basic.pos.coord.z mustEqual 91.15625f // None
ams.basic.pos.orient.x mustEqual 0f // )(VehicleFormat.Utility)
ams.basic.pos.orient.y mustEqual 0f // val msg = ObjectCreateMessage(ObjectClass.ant, PlanetSideGUID(380), obj)
ams.basic.pos.orient.z mustEqual 90.0f // val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
ams.basic.faction mustEqual PlanetSideEmpire.VS //
ams.basic.unk mustEqual 0 // pkt mustEqual string_ant
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 (ams)" in { "encode (ams)" in {
val obj = VehicleData( val obj = VehicleData(