diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
index 566f678f..008dbcb6 100644
--- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
+++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
@@ -513,7 +513,7 @@ object GamePacketOpcode extends Enumeration {
case 0xa1 => noDecoder(FireHintMessage)
case 0xa2 => noDecoder(UplinkRequest)
case 0xa3 => noDecoder(UplinkResponse)
- case 0xa4 => noDecoder(WarpgateRequest)
+ case 0xa4 => game.WarpgateRequest.decode
case 0xa5 => noDecoder(WarpgateResponse)
case 0xa6 => noDecoder(DamageWithPositionMessage)
case 0xa7 => game.GenericActionMessage.decode
diff --git a/common/src/main/scala/net/psforever/packet/game/WarpgateRequest.scala b/common/src/main/scala/net/psforever/packet/game/WarpgateRequest.scala
new file mode 100644
index 00000000..3d80f2d9
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/WarpgateRequest.scala
@@ -0,0 +1,50 @@
+// Copyright (c) 2016 PSForever.net to present
+package net.psforever.packet.game
+
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
+import scodec.Codec
+import scodec.codecs._
+
+/**
+ * Alert the server that a player wishes to engage in warp gate transport.
+ *
+ * This packet is dispatched after a player interacts with the transportation beam in the center of a warp gate.
+ * The player has either chosen a destination from the Interstellar Map or was assigned a fixed destination.
+ * When the the destination is limited to a specific continent and warp gate - "fixed" - the destination fields can be blanked.
+ * Otherwise, they must be specified.
+ * The process of gate transportation should not start until the server responds to this packet.
+ *
+ * Exploration 1:
+ * Does this packet apply to geowarp transport as well?
+ *
+ * Exploration 2:
+ * Those last two fields that are usually blanked do something?
+ * @param continent_guid the continent (zone)
+ * @param building_guid the warp gate
+ * @param dest_building_guid the destination warp gate
+ * @param dest_continent_guid the destination continent (zone)
+ * @param unk1 na; always zero?
+ * @param unk2 na; always zero?
+ */
+final case class WarpgateRequest(continent_guid : PlanetSideGUID,
+ building_guid : PlanetSideGUID,
+ dest_building_guid : PlanetSideGUID,
+ dest_continent_guid : PlanetSideGUID,
+ unk1 : Int,
+ unk2 : Int)
+ extends PlanetSideGamePacket {
+ type Packet = WarpgateRequest
+ def opcode = GamePacketOpcode.WarpgateRequest
+ def encode = WarpgateRequest.encode(this)
+}
+
+object WarpgateRequest extends Marshallable[WarpgateRequest] {
+ implicit val codec : Codec[WarpgateRequest] = (
+ ("continent_guid" | PlanetSideGUID.codec) ::
+ ("building_guid" | PlanetSideGUID.codec) ::
+ ("dest_building_guid" | PlanetSideGUID.codec) ::
+ ("dest_continent_guid" | PlanetSideGUID.codec) ::
+ ("unk1" | uint8L) ::
+ ("unk2" | uint8L)
+ ).as[WarpgateRequest]
+}
diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala
index ffa99724..c35855db 100644
--- a/common/src/test/scala/GamePacketTest.scala
+++ b/common/src/test/scala/GamePacketTest.scala
@@ -1490,6 +1490,31 @@ class GamePacketTest extends Specification {
pkt mustEqual string
}
}
+
+ "WarpgateRequest" should {
+ val string = hex"A4 1D00 1F00 1327 1F00 00 00" // an Extinction warp gate to a Desolation warp gate
+
+ "decode" in {
+ PacketCoding.DecodePacket(string).require match {
+ case WarpgateRequest(continent_guid, building_guid, dest_building_guid, dest_continent_guid, unk1, unk2) =>
+ continent_guid mustEqual PlanetSideGUID(29)
+ building_guid mustEqual PlanetSideGUID(31)
+ dest_building_guid mustEqual PlanetSideGUID(10003)
+ dest_continent_guid mustEqual PlanetSideGUID(31)
+ unk1 mustEqual 0
+ unk2 mustEqual 0
+ case default =>
+ ko
+ }
+ }
+
+ "encode" in {
+ val msg = WarpgateRequest(PlanetSideGUID(29), PlanetSideGUID(31), PlanetSideGUID(10003), PlanetSideGUID(31), 0, 0)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string
+ }
+ }
"ContinentalLockUpdateMessage" should {
val string = hex"A8 16 00 40"
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index 0762f464..71aa12f4 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -340,6 +340,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ AvatarFirstTimeEventMessage(avatar_guid, object_guid, unk1, event_name) =>
log.info("AvatarFirstTimeEvent: " + msg)
+ case msg @ WarpgateRequest(continent_guid, building_guid, dest_building_guid, dest_continent_guid, unk1, unk2) =>
+ log.info("WarpgateRequest: " + msg)
+
case msg @ MountVehicleMsg(player_guid, vehicle_guid, unk) =>
log.info("MounVehicleMsg: "+msg)