diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index b26a9834..61979bc8 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -446,7 +446,7 @@ object GamePacketOpcode extends Enumeration { case 0x6b => game.TriggerSoundMessage.decode case 0x6c => game.LootItemMessage.decode case 0x6d => game.VehicleSubStateMessage.decode - case 0x6e => noDecoder(SquadMembershipRequest) + case 0x6e => game.SquadMembershipRequest.decode case 0x6f => noDecoder(SquadMembershipResponse) // OPCODES 0x70-7f diff --git a/common/src/main/scala/net/psforever/packet/game/SquadMembershipRequest.scala b/common/src/main/scala/net/psforever/packet/game/SquadMembershipRequest.scala new file mode 100644 index 00000000..ee55360d --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/SquadMembershipRequest.scala @@ -0,0 +1,55 @@ +// Copyright (c) 2019 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import net.psforever.types.SquadRequestType +import scodec.Codec +import scodec.codecs._ + +/** + * SquadMembershipRequest is a Client->Server message for manipulating squad and platoon members. + * @param request_type na + * @param unk2 na + * @param unk3 na + * @param player_name na + * @param unk5 na + */ +final case class SquadMembershipRequest(request_type : SquadRequestType.Value, + unk2 : Long, + unk3 : Option[Long], + player_name : String, + unk5 : Option[Option[String]]) + extends PlanetSideGamePacket { + request_type match { + case SquadRequestType.Accept | SquadRequestType.Reject | SquadRequestType.Disband | + SquadRequestType.PAccept | SquadRequestType.PReject | SquadRequestType.PDisband => + assert(unk3.isEmpty, s"a $request_type request requires the unk3 field be undefined") + case _ => + assert(unk3.nonEmpty, s"a $request_type request requires the unk3 field be defined") + } + if(request_type == SquadRequestType.Invite) { + assert(unk5.nonEmpty, "an Invite request requires the unk5 field be undefined") + } + + type Packet = SquadMembershipRequest + def opcode = GamePacketOpcode.SquadMembershipRequest + def encode = SquadMembershipRequest.encode(this) +} + +object SquadMembershipRequest extends Marshallable[SquadMembershipRequest] { + implicit val codec : Codec[SquadMembershipRequest] = ( + ("request_type" | SquadRequestType.codec) >>:~ { request_type => + ("unk2" | uint32L) :: + conditional(request_type != SquadRequestType.Accept && + request_type != SquadRequestType.Reject && + request_type != SquadRequestType.Disband && + request_type != SquadRequestType.PAccept && + request_type != SquadRequestType.PReject && + request_type != SquadRequestType.PDisband, "unk3" | uint32L) :: + (("player_name" | PacketHelpers.encodedWideStringAligned(4)) >>:~ { pname => + conditional(request_type == SquadRequestType.Invite, + "unk5" | optional(bool, PacketHelpers.encodedWideStringAligned({if(pname.length == 0) 3 else 7})) + ).hlist + }) + }).as[SquadMembershipRequest] +} diff --git a/common/src/main/scala/net/psforever/types/SquadRequestType.scala b/common/src/main/scala/net/psforever/types/SquadRequestType.scala new file mode 100644 index 00000000..6b104c68 --- /dev/null +++ b/common/src/main/scala/net/psforever/types/SquadRequestType.scala @@ -0,0 +1,28 @@ +// Copyright (c) 2019 PSForever +package net.psforever.types + +import net.psforever.packet.PacketHelpers +import scodec.codecs._ + +object SquadRequestType extends Enumeration { + type Type = Value + val Invite, //00 + Unk01, //01 + Accept, //02 + Reject, //03 + Cancel, //04 + Leave, //05 + Promote, //06 + Disband, //07 + PInvite, //08 + PAccept, //09 + PReject, //10 + PCancel, //11 + PLeave, //12 + PDisband, //13 + Unk14, //14 + Unk15 //15 + = Value + + implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L) +} diff --git a/common/src/test/scala/game/SquadMembershipRequestTest.scala b/common/src/test/scala/game/SquadMembershipRequestTest.scala new file mode 100644 index 00000000..79cb9319 --- /dev/null +++ b/common/src/test/scala/game/SquadMembershipRequestTest.scala @@ -0,0 +1,67 @@ +// Copyright (c) 2019 PSForever +package game + +import net.psforever.packet._ +import net.psforever.packet.game._ +import net.psforever.types.SquadRequestType +import org.specs2.mutable._ +import scodec.bits._ + +class SquadMembershipRequestTest extends Specification { + //481c897e-b47b-41cc-b7ad-c604606d985e / PSCap-2016-03-18_12-48-12-PM.gcap / Game record 5521 at 662.786844s + val string1 = hex"6e015aa7a0224d87a0280000" + //... / PSCap-2016-06-29_07-49-26-PM (last).gcap / Game record 77 at 9.732430 (found in MultiPacket) + val string2 = hex"6E265DD7A02800" + //TODO find example where player_name field is defined + //TODO find example where unk field is defined and is a string + + "decode (1)" in { + PacketCoding.DecodePacket(string1).require match { + case SquadMembershipRequest(req_type, unk2, unk3, p_name, unk5) => + req_type mustEqual SquadRequestType.Invite + unk2 mustEqual 41593365L + unk3.contains(41605156L) mustEqual true + p_name mustEqual "" + unk5.contains(None) mustEqual true + case _ => + ko + } + } + + "decode (2)" in { + PacketCoding.DecodePacket(string2).require match { + case SquadMembershipRequest(req_type, unk2, unk3, p_name, unk5) => + req_type mustEqual SquadRequestType.Accept + unk2 mustEqual 41606501L + unk3.isEmpty mustEqual true + p_name mustEqual "" + unk5.isEmpty mustEqual true + case _ => + ko + } + } + + "encode (1)" in { + val msg = SquadMembershipRequest(SquadRequestType.Invite, 41593365, Some(41605156L), "", Some(None)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + pkt mustEqual string1 + } + + "encode (1; failure 1)" in { + SquadMembershipRequest(SquadRequestType.Invite, 41593365, None, "", Some(None)) must throwA[AssertionError] + } + + "encode (1; failure 2)" in { + SquadMembershipRequest(SquadRequestType.Invite, 41593365, Some(41605156L), "", None) must throwA[AssertionError] + } + + "encode (2)" in { + val msg = SquadMembershipRequest(SquadRequestType.Accept, 41606501, None, "", None) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + pkt mustEqual string2 + } + + "encode (2; failure)" in { + SquadMembershipRequest(SquadRequestType.Accept, 41606501, Some(41606501), "", None) must throwA[AssertionError] + } +}