diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
index 27c6931a..c34b247f 100644
--- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
+++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
@@ -391,7 +391,7 @@ object GamePacketOpcode extends Enumeration {
case 0x3b => noDecoder(UnknownMessage59)
case 0x3c => noDecoder(GenericCollisionMsg)
case 0x3d => game.QuantityUpdateMessage.decode
- case 0x3e => noDecoder(ArmorChangedMessage)
+ case 0x3e => game.ArmorChangedMessage.decode
case 0x3f => noDecoder(ProjectileStateMessage)
// OPCODES 0x40-4f
diff --git a/common/src/main/scala/net/psforever/packet/game/ArmorChangedMessage.scala b/common/src/main/scala/net/psforever/packet/game/ArmorChangedMessage.scala
new file mode 100644
index 00000000..98d1a57d
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/ArmorChangedMessage.scala
@@ -0,0 +1,46 @@
+// 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._
+
+/**
+ * Force a player model to change its exo-suit.
+ * Set all GUI elements and functional elements to be associated with that type of exo-suit.
+ * Inventory and holster contents are discarded.
+ *
+ * Due to the way armor is handled internally, a player of one faction may not spawn in the exo-suit of another faction.
+ * That style of exo-suit is never available through this packet.
+ * As MAX units do not get their weapon by default, all the MAX values produce the same faction-appropriate mechanized exo-suit body visually.
+ * (The MAX weapons are supplied in subsequent packets.)
+ * `
+ * 0, 0 - Agile
+ * 1, 0 - Reinforced
+ * 2, 0 - MAX
+ * 2, 1 - AI MAX
+ * 2, 2 - AV MAX
+ * 2, 3 - AA MAX
+ * 3, 0 - Infiltration
+ * 4, 0 - Standard
+ * `
+ * @param player_guid the player
+ * @param armor the type of exo-suit
+ * @param subtype the exo-suit subtype, if any
+ */
+final case class ArmorChangedMessage(player_guid : PlanetSideGUID,
+ armor : Int,
+ subtype : Int)
+ extends PlanetSideGamePacket {
+ type Packet = ArmorChangedMessage
+ def opcode = GamePacketOpcode.ArmorChangedMessage
+ def encode = ArmorChangedMessage.encode(this)
+}
+
+object ArmorChangedMessage extends Marshallable[ArmorChangedMessage] {
+ implicit val codec : Codec[ArmorChangedMessage] = (
+ ("player_guid" | PlanetSideGUID.codec) ::
+ ("armor" | uintL(3)) ::
+ ("subtype" | uintL(3))
+ ).as[ArmorChangedMessage]
+}
diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala
index f440da2a..6ecdaf87 100644
--- a/common/src/test/scala/GamePacketTest.scala
+++ b/common/src/test/scala/GamePacketTest.scala
@@ -981,6 +981,28 @@ class GamePacketTest extends Specification {
}
}
+ "ArmorChangedMessage" should {
+ val string = hex"3E 11 01 4C"
+
+ "decode" in {
+ PacketCoding.DecodePacket(string).require match {
+ case ArmorChangedMessage(player_guid, armor, subtype) =>
+ player_guid mustEqual PlanetSideGUID(273)
+ armor mustEqual 2
+ subtype mustEqual 3
+ case default =>
+ ko
+ }
+ }
+
+ "encode" in {
+ val msg = ArmorChangedMessage(PlanetSideGUID(273), 2, 3)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string
+ }
+ }
+
"QuantityDeltaUpdateMessage" should {
val string = hex"C4 5300 FBFFFFFF"