diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
index 97f58318..b697b677 100644
--- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
+++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
@@ -472,7 +472,7 @@ object GamePacketOpcode extends Enumeration {
// OPCODES 0x80-8f
case 0x80 => noDecoder(GenericObjectAction2Message)
- case 0x81 => noDecoder(DestroyDisplayMessage)
+ case 0x81 => game.DestroyDisplayMessage.decode
case 0x82 => noDecoder(TriggerBotAction)
case 0x83 => noDecoder(SquadWaypointRequest)
case 0x84 => noDecoder(SquadWaypointEvent)
diff --git a/common/src/main/scala/net/psforever/packet/game/DestroyDisplayMessage.scala b/common/src/main/scala/net/psforever/packet/game/DestroyDisplayMessage.scala
new file mode 100644
index 00000000..bbd14756
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/DestroyDisplayMessage.scala
@@ -0,0 +1,80 @@
+// 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._
+
+/**
+ * Display a message in the event window that informs of a player death.
+ *
+ * The message is composed of three parts:
+ * 1) killer information
+ * 2) method information
+ * 3) victim information
+ * In the case of a player kill, the player's name will be attributed directly.
+ * In the case of an absentee kill, a description of the method of death will be attributed.
+ * In the case of a suicide, the player attributed is the player who was killed (message format displays only the victim).
+ * The victim's name is byte-aligned with a 5-bit buffer.
+ *
+ * The four bytes that follow each name seems to be important to the identification of the associated player.
+ * The same value will be seen in every `DestroyDisplayMessage` that includes the player, with respect to whether they are listed as the "killer" or as the "victim."
+ * This holds true for every entry within thie same login session, at least.
+ * Blanking these values out does not change anything about the format of the event message.
+ * In the case of absentee kills, for example, where there is no killer listed, this field has been zero'd (`00000000`).
+ *
+ * The faction affiliation is different from the normal way `PlanetSideEmpire` values are recorded.
+ * The higher nibble will reflect the first part of the `PlanetSideEmpire` value - `0` for TR, `4` for NC `8` for TR, `C` for Neutral/BOPs.
+ * An extra `20` will be added if the player is in a vehicle or turret at the time - `2` for TR, `6` for NC, `A` for VS, `E` for Neutral/BOPs.
+ * When marked as being in a vehicle or turret, the player's name will be enclosed within square brackets.
+ * The length of the player's name found at the start of the wide character string does not reflect whether or not there will be square brackets (fortunately).
+ *
+ * The two bytes in between the killer section and the victim section are the method of homicide or suicide.
+ * The color of the resulting icon is borrowed from the attributed killer's faction affiliation if it can be determined.
+ * An unidentified method defaults to a skull and crossbones icon.
+ * The exact range of unique and valid icon values for this parameter is currently unknown.
+ * It is also unknown what the two bytes preceding `method` specify, as changing them does nothing to the displayed message.
+ * @param killer the name of the player who did the killing
+ * @param killer_unk See above
+ * @param killer_empire the empire affiliation of the killer:
+ * 0 - TR, 1 - NC, 2 - VS, 3 - Neutral/BOPs
+ * @param killer_inVehicle true, if the killer was in a vehicle at the time of the kill; false, otherwise
+ * @param unk na; but does not like being set to 0
+ * @param method modifies the icon in the message, related to the way the victim was killed
+ * @param victim the name of the player who was killed
+ * @param victim_unk See above
+ * @param victim_empire the empire affiliation of the victim:
+ * 0 - TR, 1 - NC, 2 - VS, 3 - Neutral/BOPs
+ * @param victim_inVehicle true, if the victim was in a vehicle when he was killed; false, otherwise
+ */
+final case class DestroyDisplayMessage(killer : String,
+ killer_unk : Long,
+ killer_empire : Int,
+ killer_inVehicle : Boolean,
+ unk : PlanetSideGUID,
+ method : PlanetSideGUID,
+ victim : String,
+ victim_unk : Long,
+ victim_empire : Int,
+ victim_inVehicle : Boolean
+)
+ extends PlanetSideGamePacket {
+ type Packet = DestroyDisplayMessage
+ def opcode = GamePacketOpcode.DestroyDisplayMessage
+ def encode = DestroyDisplayMessage.encode(this)
+}
+
+object DestroyDisplayMessage extends Marshallable[DestroyDisplayMessage] {
+ implicit val codec : Codec[DestroyDisplayMessage] = (
+ ("killer" | PacketHelpers.encodedWideString) ::
+ ("killer_unk" | ulongL(32)) ::
+ ("killer_empire" | uintL(2)) ::
+ ("killer_inVehicle" | bool) ::
+ ("unk" | PlanetSideGUID.codec) ::
+ ("method" | PlanetSideGUID.codec) ::
+ ("victim" | PacketHelpers.encodedWideStringAligned(5)) ::
+ ("victim_unk" | ulongL(32)) ::
+ ("victim_empire" | uintL(2)) ::
+ ("victim_inVehicle" | bool)
+ ).as[DestroyDisplayMessage]
+}
diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala
index eb833722..9bcf21bb 100644
--- a/common/src/test/scala/GamePacketTest.scala
+++ b/common/src/test/scala/GamePacketTest.scala
@@ -1324,6 +1324,34 @@ class GamePacketTest extends Specification {
}
}
+ "DestroyDisplayMessage" should {
+ val string = hex"81 87 41006E00670065006C006C006F00 35BCD801 8 F201 9207 0A 0 48004D00460049004300 B18ED901 00" // Angello-VS (???) HMFIC-TR
+
+ "decode" in {
+ PacketCoding.DecodePacket(string).require match {
+ case DestroyDisplayMessage(killer, killer_unk, killer_empire, killer_inVehicle, unk, method, victim, victim_unk, victim_empire, victim_inVehicle) =>
+ killer mustEqual "Angello"
+ killer_unk mustEqual 30981173
+ killer_empire mustEqual 2
+ killer_inVehicle mustEqual false
+ unk mustEqual PlanetSideGUID(121)
+ method mustEqual PlanetSideGUID(969)
+ victim mustEqual "HMFIC"
+ victim_unk mustEqual 31035057
+ victim_empire mustEqual 0
+ victim_inVehicle mustEqual false
+ case default =>
+ ko
+ }
+ }
+
+ "encode" in {
+ val msg = DestroyDisplayMessage("Angello", 30981173, 2, false, PlanetSideGUID(121), PlanetSideGUID(969), "HMFIC", 31035057, 0, false)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+ pkt mustEqual string
+ }
+ }
+
"WeaponDelayFireMessage" should {
val string = hex"88 A3140000"