diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
index cd45fa0e..2146e5d7 100644
--- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
+++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
@@ -535,7 +535,7 @@ object GamePacketOpcode extends Enumeration {
case 0xb4 => game.BattleExperienceMessage.decode
case 0xb5 => noDecoder(TargetingImplantRequest)
case 0xb6 => game.ZonePopulationUpdateMessage.decode
- case 0xb7 => noDecoder(DisconnectMessage)
+ case 0xb7 => game.DisconnectMessage.decode
// 0xb8
case 0xb8 => noDecoder(ExperienceAddedMessage)
case 0xb9 => game.OrbitalStrikeWaypointMessage.decode
diff --git a/common/src/main/scala/net/psforever/packet/game/DisconnectMessage.scala b/common/src/main/scala/net/psforever/packet/game/DisconnectMessage.scala
new file mode 100644
index 00000000..b6ef7539
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/DisconnectMessage.scala
@@ -0,0 +1,36 @@
+// Copyright (c) 2016 PSForever.net to present
+package net.psforever.packet.game
+
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import scodec.Codec
+import scodec.codecs._
+
+/**
+ * Dispatched to the client to force a disconnect.
+ *
+ * The client's render will fade and be centered with a system textbox with the given message.
+ * Using the button on the textbox will drop the current world session and return the player to the world select screen.
+ * Being disconnected like this has no client-based consequences on its own.
+ *
+ * Exploration:
+ * When do the other two messages appear, if at all?
+ * @param msg the displayed message
+ * @param unk2 na
+ * @param unk3 na
+ */
+final case class DisconnectMessage(msg : String,
+ unk2 : String = "",
+ unk3 : String = "")
+ extends PlanetSideGamePacket {
+ type Packet = DisconnectMessage
+ def opcode = GamePacketOpcode.DisconnectMessage
+ def encode = DisconnectMessage.encode(this)
+}
+
+object DisconnectMessage extends Marshallable[DisconnectMessage] {
+ implicit val codec : Codec[DisconnectMessage] = (
+ ("msg" | PacketHelpers.encodedString) ::
+ ("unk2" | PacketHelpers.encodedString) ::
+ ("unk3" | PacketHelpers.encodedString)
+ ).as[DisconnectMessage]
+}
diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala
index ad5c5017..776380ae 100644
--- a/common/src/test/scala/GamePacketTest.scala
+++ b/common/src/test/scala/GamePacketTest.scala
@@ -1492,6 +1492,28 @@ class GamePacketTest extends Specification {
}
}
+ "DisconnectMessage" should {
+ val string = hex"B7 85 46 69 72 73 74 86 53 65 63 6F 6E 64 8E 46 69 72 73 74 20 26 20 73 65 63 6F 6E 64"
+
+ "decode" in {
+ PacketCoding.DecodePacket(string).require match {
+ case DisconnectMessage(unk1, unk2, unk3) =>
+ unk1 mustEqual "First"
+ unk2 mustEqual "Second"
+ unk3 mustEqual "First & second"
+ case default =>
+ ko
+ }
+ }
+
+ "encode" in {
+ val msg = DisconnectMessage("First", "Second", "First & second")
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string
+ }
+ }
+
"OrbitalStrikeWaypointMessage" should {
val string_on = hex"B9 46 0C AA E3 D2 2A 92 00"
val string_off = hex"B9 46 0C 00"