diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
index 21d4ba9b..b6ab2040 100644
--- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
+++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
@@ -364,7 +364,7 @@ object GamePacketOpcode extends Enumeration {
case 0x24 => game.SetEmpireMessage.decode
case 0x25 => game.EmoteMsg.decode
case 0x26 => noDecoder(UnuseItemMessage)
- case 0x27 => noDecoder(ObjectDetachMessage)
+ case 0x27 => game.ObjectDetachMessage.decode
// 0x28
case 0x28 => game.CreateShortcutMessage.decode
case 0x29 => game.ChangeShortcutBankMessage.decode
diff --git a/common/src/main/scala/net/psforever/packet/game/DropItemMessage.scala b/common/src/main/scala/net/psforever/packet/game/DropItemMessage.scala
index 5adf3bb9..8e03b546 100644
--- a/common/src/main/scala/net/psforever/packet/game/DropItemMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/DropItemMessage.scala
@@ -5,6 +5,18 @@ import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, Plan
import scodec.Codec
import scodec.codecs._
+/**
+ * Dispatched by the client when the player's intent is to put an item down on the ground.
+ *
+ * When a player drops an item, it normally appears right under their feet (where they are standing).
+ * This part of the ground is chosen because it should be the stable.
+ * Also, those coordinates belonging to the player are the most accessible.
+ * This process, however, is not automatic.
+ * The server determines the exact position where the item gets placed.
+ *
+ * This packet is complemented by an `ObjectDetachMessage` packet from the server that performs the actual "dropping."
+ * @param item_guid the item to be dropped
+ */
final case class DropItemMessage(item_guid : PlanetSideGUID)
extends PlanetSideGamePacket {
type Packet = DropItemMessage
@@ -14,6 +26,6 @@ final case class DropItemMessage(item_guid : PlanetSideGUID)
object DropItemMessage extends Marshallable[DropItemMessage] {
implicit val codec : Codec[DropItemMessage] = (
- ("item_guid" | PlanetSideGUID.codec)
+ "item_guid" | PlanetSideGUID.codec
).as[DropItemMessage]
}
diff --git a/common/src/main/scala/net/psforever/packet/game/ObjectDetachMessage.scala b/common/src/main/scala/net/psforever/packet/game/ObjectDetachMessage.scala
new file mode 100644
index 00000000..8630fc74
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/ObjectDetachMessage.scala
@@ -0,0 +1,60 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game
+
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
+import net.psforever.types.Vector3
+import scodec.Codec
+import scodec.codecs._
+
+/**
+ * Dispatched by the server to cause two associated objects to disentangle from one another.
+ *
+ * `ObjectDetachMessage` is the opposite to `ObjectAttachMessage`.
+ * When detached, the resulting freed object will be placed at the given coordinates.
+ * For some container objects, most often static ones, a default placement point does exist.
+ * This usually matches the position where the original mounting occurred, or is relative to the current position of the container.
+ * Using a position that is not the mounting one, in this case, counts as a temporary teleport of the character.
+ * As soon as available, e.g., the end of an animation, the character will rw-appear at the mounting point.
+ * The object may also have its orientation aspect changed.
+ *
+ * This packet is considered proper response to:
+ * - `DismountVehicleMsg`
+ * - `DropItemMessage`
+ * @param parent_guid the container/connector object
+ * @param child_guid the contained/connected object
+ * @param pos where the contained/connected object will be placed after it has detached
+ * @param roll the roll of the dropped item;
+ * every `0x1` is 2.813 degrees;
+ * every `0x10` is 45-degrees;
+ * it wraps at `0x0` == `0x80` == top facing up
+ * @param pitch the pitch of the dropped item;
+ * every `0x1` is 2.813 degrees;
+ * every `0x10` is 45-degrees;
+ * it wraps at `0x0` == `0x80` == top facing up
+ * @param yaw the yaw of the dropped item;
+ * every `0x1` is 2.813 degrees counter clockwise from East;
+ * every `0x10` is 45-degrees;
+ * it wraps at `0x0` == `0x80` == front facing East
+ */
+final case class ObjectDetachMessage(parent_guid : PlanetSideGUID,
+ child_guid : PlanetSideGUID,
+ pos : Vector3,
+ roll : Int,
+ pitch : Int,
+ yaw : Int)
+ extends PlanetSideGamePacket {
+ type Packet = ObjectDetachMessage
+ def opcode = GamePacketOpcode.ObjectDetachMessage
+ def encode = ObjectDetachMessage.encode(this)
+}
+
+object ObjectDetachMessage extends Marshallable[ObjectDetachMessage] {
+ implicit val codec : Codec[ObjectDetachMessage] = (
+ ("parent_guid" | PlanetSideGUID.codec) ::
+ ("child_guid" | PlanetSideGUID.codec) ::
+ ("pos" | Vector3.codec_pos) ::
+ ("roll" | uint8L) ::
+ ("pitch" | uint8L) ::
+ ("yaw" | uint8L)
+ ).as[ObjectDetachMessage]
+}
diff --git a/common/src/test/scala/game/ObjectDetachMessageTest.scala b/common/src/test/scala/game/ObjectDetachMessageTest.scala
new file mode 100644
index 00000000..0b2b8605
--- /dev/null
+++ b/common/src/test/scala/game/ObjectDetachMessageTest.scala
@@ -0,0 +1,35 @@
+// Copyright (c) 2017 PSForever
+package game
+
+import org.specs2.mutable._
+import net.psforever.packet._
+import net.psforever.packet.game._
+import net.psforever.types.Vector3
+import scodec.bits._
+
+class ObjectDetachMessageTest extends Specification {
+ val string = hex"27 640B C609 92F76 01D65 F611 00 00 40"
+
+ "decode" in {
+ PacketCoding.DecodePacket(string).require match {
+ case ObjectDetachMessage(parent_guid, child_guid, pos, roll, pitch, yaw) =>
+ parent_guid mustEqual PlanetSideGUID(2916)
+ child_guid mustEqual PlanetSideGUID(2502)
+ pos.x mustEqual 3567.1406f
+ pos.y mustEqual 2988.0078f
+ pos.z mustEqual 71.84375f
+ roll mustEqual 0
+ pitch mustEqual 0
+ yaw mustEqual 64
+ case _ =>
+ ko
+ }
+ }
+
+ "encode" in {
+ val msg = ObjectDetachMessage(PlanetSideGUID(2916), PlanetSideGUID(2502), Vector3(3567.1406f, 2988.0078f, 71.84375f), 0, 0, 64)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string
+ }
+}
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index 3e10636f..8648ea3a 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -279,6 +279,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PacketCoding.CreateGamePacket(0, EmoteMsg(avatar_guid, emote)))
case msg @ DropItemMessage(item_guid) =>
+ //item dropped where you spawn in VS Sanctuary
+ sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(PlanetSideGUID(75), item_guid, app.pos, 0, 0, 0)))
log.info("DropItem: " + msg)
case msg @ ReloadMessage(item_guid, ammo_clip, unk1) =>