diff --git a/src/main/scala/net/psforever/packet/game/OutfitMemberEvent.scala b/src/main/scala/net/psforever/packet/game/OutfitMemberEvent.scala index 6f11c730..f40498a4 100644 --- a/src/main/scala/net/psforever/packet/game/OutfitMemberEvent.scala +++ b/src/main/scala/net/psforever/packet/game/OutfitMemberEvent.scala @@ -8,21 +8,11 @@ import scodec.bits.BitVector import scodec.codecs._ import shapeless.{::, HNil} -/* - packet_type is unimplemented! if packet_type == 0 only outfit_id and member_id are sent - action is unimplemented! if action == 0 unk2 will contain one additional uint32L - unk0_padding contains one byte of padding. may contain 4byte of unknown data depending on action - */ final case class OutfitMemberEvent( - packet_type: Int, // only 0 is known // TODO: needs implementation + packet_type: OutfitMemberEvent.PacketType.Type, outfit_id: Long, member_id: Long, - member_name: String, // from here is packet_type == 0 only - rank: Int, // 0-7 - points: Long, // client divides this by 100 - last_login: Long, // seconds ago from current time, 0 if online - action: OutfitMemberEvent.PacketType.Type, // this should always be 1, otherwise there will be actual data in unk0_padding! - unk0_padding: OutfitMemberEventAction // only contains information if action is 0, 1 byte of padding otherwise + action: OutfitMemberEventAction ) extends PlanetSideGamePacket { type Packet = OutfitMemberEvent @@ -34,12 +24,30 @@ final case class OutfitMemberEvent( abstract class OutfitMemberEventAction(val code: Int) object OutfitMemberEventAction { + object PacketType extends Enumeration { + type Type = Value + + val Unk0: PacketType.Value = Value(0) + val Padding: PacketType.Value = Value(1) + + implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uintL(1)) + + } + + /* + action is unimplemented! if action == 0 unk2 will contain one additional uint32L + unk0_padding contains one byte of padding. may contain 4byte of unknown data depending on action + */ final case class Unk0( - unk0: Long + member_name: String, + rank: Int, + points: Long, // client divides this by 100 + last_login: Long, // seconds ago from current time, 0 if online + action: PacketType.Type, // should always be 1, otherwise there will be actual data in padding. not implemented! + padding: Int // should always be 0, 4 bits of padding // only contains data if action is 0 ) extends OutfitMemberEventAction(code = 0) - final case class Padding( - padding: Int + final case class Unk1( ) extends OutfitMemberEventAction(code = 1) final case class Unknown(badCode: Int, data: BitVector) extends OutfitMemberEventAction(badCode) @@ -51,31 +59,25 @@ object OutfitMemberEventAction { object Codecs { private val everFailCondition = conditional(included = false, bool) - val UnkNonPaddingCodec: Codec[Unk0] = ( - ("unk0" | uint32L) + val Unk0Codec: Codec[Unk0] = ( + ("member_name" | PacketHelpers.encodedWideStringAligned(6)) :: // from here is packet_type == 0 only + ("rank" | uint(3)) :: + ("points" | uint32L) :: + ("last_login" | uint32L) :: + ("action" | OutfitMemberEventAction.PacketType.codec) :: + ("padding" | uint4L) ).xmap[Unk0]( { - case u0 => - Unk0(u0) + case member_name :: rank :: points :: last_login :: action :: padding :: HNil => + Unk0(member_name, rank, points, last_login, action, padding) }, { - case Unk0(u0) => - u0 + case Unk0(member_name, rank, points, last_login, action, padding) => + member_name :: rank :: points :: last_login :: action :: padding :: HNil } ) - val PaddingCodec: Codec[Padding] = ( - ("padding" | uint4L) - ).xmap[Padding]( - { - case padding => - Padding(padding) - }, - { - case Padding(padding) => - padding - } - ) + val Unk1Codec: Codec[Unk1] = PacketHelpers.emptyCodec(Unk1()) /** * A common form for known action code indexes with an unknown purpose and transformation is an "Unknown" object. @@ -109,9 +111,9 @@ object OutfitMemberEvent extends Marshallable[OutfitMemberEvent] { type Type = Value val Unk0: PacketType.Value = Value(0) - val Padding: PacketType.Value = Value(1) // Info: Player has been invited / response to OutfitMembershipRequest Unk2 for that player + val Unk1: PacketType.Value = Value(1) // Info: Player has been invited / response to OutfitMembershipRequest Unk2 for that player - implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uintL(1)) + implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uintL(2)) } private def selectFromType(code: Int): Codec[OutfitMemberEventAction] = { @@ -119,34 +121,27 @@ object OutfitMemberEvent extends Marshallable[OutfitMemberEvent] { import scala.annotation.switch ((code: @switch) match { - case 0 => UnkNonPaddingCodec - case 1 => PaddingCodec + case 0 => Unk0Codec + case 1 => Unk1Codec case _ => failureCodec(code) }).asInstanceOf[Codec[OutfitMemberEventAction]] } implicit val codec: Codec[OutfitMemberEvent] = ( - ("packet_type" | uintL(2)) :: // this should selectFromType + ("packet_type" | PacketType.codec) >>:~ { packet_type => ("outfit_id" | uint32L) :: ("member_id" | uint32L) :: - ("member_name" | PacketHelpers.encodedWideStringAligned(6)) :: // from here is packet_type == 0 only - ("rank" | uint(3)) :: - ("points" | uint32L) :: - ("last_login" | uint32L) :: - (("action" | PacketType.codec) >>:~ { action => - ("action_part" | selectFromType(action.id)).hlist - }) + ("action" | selectFromType(packet_type.id)).hlist + } ).xmap[OutfitMemberEvent]( { - case packet_type :: outfit_id :: member_id :: member_name :: rank :: points :: last_login :: action :: unk0_padding :: HNil => - OutfitMemberEvent(packet_type, outfit_id, member_id, member_name, rank, points, last_login, action, unk0_padding) + case packet_type :: outfit_id :: member_id:: action :: HNil => + OutfitMemberEvent(packet_type, outfit_id, member_id, action) }, { - // TODO: remove once implemented - // ensure we send packet_type 0 only - case OutfitMemberEvent(_, outfit_id, member_id, member_name, rank, points, last_login, action, unk0_padding) => - 0 :: outfit_id :: member_id :: member_name :: rank :: points :: last_login :: action :: unk0_padding :: HNil + case OutfitMemberEvent(packet_type, outfit_id, member_id, action) => + packet_type :: outfit_id :: member_id :: action :: HNil } ) } diff --git a/src/test/scala/game/OutfitMemberEventTest.scala b/src/test/scala/game/OutfitMemberEventTest.scala index f51fe952..726d0a78 100644 --- a/src/test/scala/game/OutfitMemberEventTest.scala +++ b/src/test/scala/game/OutfitMemberEventTest.scala @@ -2,8 +2,8 @@ package game import net.psforever.packet._ -import net.psforever.packet.game.OutfitMemberEvent -import net.psforever.packet.game.OutfitMemberEventAction.Padding +import net.psforever.packet.game.{OutfitMemberEvent, OutfitMemberEventAction} +import net.psforever.packet.game.OutfitMemberEventAction._ import org.specs2.mutable._ import scodec.bits._ @@ -18,20 +18,20 @@ class OutfitMemberEventTest extends Specification { val PvtPa = hex"90 0 4864 0000 1e69 e80a 2c 0 500076007400500061006e00630061006b0065007300 705e a080 0a85 e060 10" val Night = hex"90 0 4864 0002 4cf0 3802 28 0 4e006900670068007400770069006e0067003100 b8fb 9a40 0da6 ec80 50" - val Unk1 = hex"90 5 40542002 3f61e808 0" + val unk1 = hex"90 5 40542002 3f61e808 0" "decode Lazer padding" in { PacketCoding.decodePacket(Lazer).require match { - case OutfitMemberEvent(packet_type, outfit_id, member_id, member_name, rank, points, last_login, action, unk0_padding) => - packet_type mustEqual 0 - outfit_id mustEqual 6418 - member_id mustEqual 705344 - member_name mustEqual "Lazer1982" - rank mustEqual 7 - points mustEqual 3134113 - last_login mustEqual 156506 - action mustEqual OutfitMemberEvent.PacketType.Padding - unk0_padding mustEqual Padding(0) + case OutfitMemberEvent(packet_type, outfit_id, member_id, Unk0(member_name, rank, points, last_login, action, padding)) => + packet_type mustEqual OutfitMemberEvent.PacketType.Unk0 + outfit_id mustEqual 6418 + member_id mustEqual 705344 + member_name mustEqual "Lazer1982" + rank mustEqual 7 + points mustEqual 3134113 + last_login mustEqual 156506 + action mustEqual OutfitMemberEventAction.PacketType.Padding + padding mustEqual 0 case _ => ko } @@ -39,15 +39,17 @@ class OutfitMemberEventTest extends Specification { "encode Lazer padding" in { val msg = OutfitMemberEvent( - packet_type = 0, + packet_type = OutfitMemberEvent.PacketType.Unk0, outfit_id = 6418, member_id = 705344, - member_name = "Lazer1982", - rank = 7, - points = 3134113, - last_login = 156506, - action = OutfitMemberEvent.PacketType.Padding, - unk0_padding = Padding(0) + Unk0( + member_name = "Lazer1982", + rank = 7, + points = 3134113, + last_login = 156506, + action = OutfitMemberEventAction.PacketType.Padding, + padding = 0 + ) ) val pkt = PacketCoding.encodePacket(msg).require.toByteVector @@ -56,16 +58,16 @@ class OutfitMemberEventTest extends Specification { "decode OpolE padding" in { PacketCoding.decodePacket(OpolE).require match { - case OutfitMemberEvent(packet_type, outfit_id, member_id, member_name, rank, points, last_login, action, unk0_padding) => - packet_type mustEqual 0 + case OutfitMemberEvent(packet_type, outfit_id, member_id, Unk0(member_name, rank, points, last_login, action, unk0_padding)) => + packet_type mustEqual OutfitMemberEvent.PacketType.Unk0 outfit_id mustEqual 6418 member_id mustEqual 42644970 member_name mustEqual "OpolE" rank mustEqual 6 points mustEqual 461901 last_login mustEqual 137576 - action mustEqual OutfitMemberEvent.PacketType.Padding - unk0_padding mustEqual Padding(0) + action mustEqual OutfitMemberEventAction.PacketType.Padding + unk0_padding mustEqual 0 case _ => ko } @@ -73,44 +75,44 @@ class OutfitMemberEventTest extends Specification { "encode OpolE padding" in { val msg = OutfitMemberEvent( - packet_type = 0, + packet_type = OutfitMemberEvent.PacketType.Unk0, outfit_id = 6418, member_id = 42644970, - member_name = "OpolE", - rank = 6, - points = 461901, - last_login = 137576, - action = OutfitMemberEvent.PacketType.Padding, - unk0_padding = Padding(0) + Unk0( + member_name = "OpolE", + rank = 6, + points = 461901, + last_login = 137576, + action = OutfitMemberEventAction.PacketType.Padding, + padding = 0 + ) + ) val pkt = PacketCoding.encodePacket(msg).require.toByteVector pkt mustEqual OpolE } - // TODO: these are broken because the decoder can only handle packet_type 0 - - /* - "decode Unk0" in { - PacketCoding.decodePacket(Unk1).require match { - case OutfitMemberEvent(packet_type, outfit_id, member_id) => - packet_type mustEqual 1 - outfit_id mustEqual 6418 - member_id mustEqual 42644970 + "decode Unk1" in { + PacketCoding.decodePacket(unk1).require match { + case OutfitMemberEvent(packet_type,outfit_id, member_id, Unk1()) => + packet_type mustEqual OutfitMemberEvent.PacketType.Unk1 + outfit_id mustEqual 529744 + member_id mustEqual 41605263 case _ => ko } } - "encode Unk0" in { + "encode Unk1" in { val msg = OutfitMemberEvent( - packet_type = 1, - outfit_id = 6418, - member_id = 42644970, + packet_type = OutfitMemberEvent.PacketType.Unk1, + outfit_id = 529744, + member_id = 41605263, + Unk1() ) val pkt = PacketCoding.encodePacket(msg).require.toByteVector - pkt mustEqual Unk1 + pkt mustEqual unk1 } - */ }