From f45420b51156d82a9689024461db819b328988f1 Mon Sep 17 00:00:00 2001
From: FateJH <Jason_DiDonato@yahoo.com>
Date: Fri, 17 May 2019 14:26:11 -0400
Subject: [PATCH] ; moved from brnach squad-req, due to branch incompatibilities

---
 .../psforever/packet/GamePacketOpcode.scala   |  2 +-
 .../packet/game/SquadMembershipRequest.scala  | 55 +++++++++++++++
 .../psforever/types/SquadRequestType.scala    | 28 ++++++++
 .../game/SquadMembershipRequestTest.scala     | 67 +++++++++++++++++++
 4 files changed, 151 insertions(+), 1 deletion(-)
 create mode 100644 common/src/main/scala/net/psforever/packet/game/SquadMembershipRequest.scala
 create mode 100644 common/src/main/scala/net/psforever/types/SquadRequestType.scala
 create mode 100644 common/src/test/scala/game/SquadMembershipRequestTest.scala

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]
+  }
+}
