mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-20 02:24:45 +00:00
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:
parent
052a318285
commit
4e41468cd0
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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) ::
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue