diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
index 35a4cd10..a502732a 100644
--- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
+++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
@@ -350,7 +350,7 @@ object GamePacketOpcode extends Enumeration {
case 0x18 => game.ObjectCreateDetailedMessage.decode
case 0x19 => game.ObjectDeleteMessage.decode
case 0x1a => game.PingMsg.decode
- case 0x1b => noDecoder(VehicleStateMessage)
+ case 0x1b => game.VehicleStateMessage.decode
case 0x1c => noDecoder(FrameVehicleStateMessage)
case 0x1d => game.GenericObjectStateMsg.decode
case 0x1e => game.ChildObjectStateMessage.decode
diff --git a/common/src/main/scala/net/psforever/packet/game/BuildingInfoUpdateMessage.scala b/common/src/main/scala/net/psforever/packet/game/BuildingInfoUpdateMessage.scala
index 0a9df191..9887e409 100644
--- a/common/src/main/scala/net/psforever/packet/game/BuildingInfoUpdateMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/BuildingInfoUpdateMessage.scala
@@ -200,10 +200,10 @@ object BuildingInfoUpdateMessage extends Marshallable[BuildingInfoUpdateMessage]
Attempt.successful(BuildingInfoUpdateMessage(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u))
},
{
- case BuildingInfoUpdateMessage(_, _, _, _, _, _, _, 0, Some(x), _, _, _, _, _, _, _, _, _, _, _, _) =>
+ case BuildingInfoUpdateMessage(_, _, _, _, _, _, _, 0, Some(_), _, _, _, _, _, _, _, _, _, _, _, _) =>
Attempt.failure(Err("invalid properties when value == 0"))
- case BuildingInfoUpdateMessage(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 8, Some(x), _, _) =>
+ case BuildingInfoUpdateMessage(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 8, Some(_), _, _) =>
Attempt.failure(Err("invalid properties when value == 8"))
case BuildingInfoUpdateMessage(a, b, c, d, e, f, g, h, i, j, k, l, m, n, lst, p, q, r, s, t, u) =>
diff --git a/common/src/main/scala/net/psforever/packet/game/ChangeAmmoMessage.scala b/common/src/main/scala/net/psforever/packet/game/ChangeAmmoMessage.scala
index 6eb915ec..1612987a 100644
--- a/common/src/main/scala/net/psforever/packet/game/ChangeAmmoMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/ChangeAmmoMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
diff --git a/common/src/main/scala/net/psforever/packet/game/ChangeFireModeMessage.scala b/common/src/main/scala/net/psforever/packet/game/ChangeFireModeMessage.scala
index fc52e138..bf9409a8 100644
--- a/common/src/main/scala/net/psforever/packet/game/ChangeFireModeMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/ChangeFireModeMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
diff --git a/common/src/main/scala/net/psforever/packet/game/ChangeFireStateMessage_Start.scala b/common/src/main/scala/net/psforever/packet/game/ChangeFireStateMessage_Start.scala
index 30ec9a90..bd7dbb61 100644
--- a/common/src/main/scala/net/psforever/packet/game/ChangeFireStateMessage_Start.scala
+++ b/common/src/main/scala/net/psforever/packet/game/ChangeFireStateMessage_Start.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
@@ -13,7 +13,5 @@ final case class ChangeFireStateMessage_Start(item_guid : PlanetSideGUID)
}
object ChangeFireStateMessage_Start extends Marshallable[ChangeFireStateMessage_Start] {
- implicit val codec : Codec[ChangeFireStateMessage_Start] = (
- ("item_guid" | PlanetSideGUID.codec)
- ).as[ChangeFireStateMessage_Start]
+ implicit val codec : Codec[ChangeFireStateMessage_Start] = ("item_guid" | PlanetSideGUID.codec).as[ChangeFireStateMessage_Start]
}
diff --git a/common/src/main/scala/net/psforever/packet/game/ChangeFireStateMessage_Stop.scala b/common/src/main/scala/net/psforever/packet/game/ChangeFireStateMessage_Stop.scala
index 16ff5c9e..571860d1 100644
--- a/common/src/main/scala/net/psforever/packet/game/ChangeFireStateMessage_Stop.scala
+++ b/common/src/main/scala/net/psforever/packet/game/ChangeFireStateMessage_Stop.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
@@ -13,7 +13,5 @@ final case class ChangeFireStateMessage_Stop(item_guid : PlanetSideGUID)
}
object ChangeFireStateMessage_Stop extends Marshallable[ChangeFireStateMessage_Stop] {
- implicit val codec : Codec[ChangeFireStateMessage_Stop] = (
- ("item_guid" | PlanetSideGUID.codec)
- ).as[ChangeFireStateMessage_Stop]
+ implicit val codec : Codec[ChangeFireStateMessage_Stop] = ("item_guid" | PlanetSideGUID.codec).as[ChangeFireStateMessage_Stop]
}
diff --git a/common/src/main/scala/net/psforever/packet/game/CharacterInfoMessage.scala b/common/src/main/scala/net/psforever/packet/game/CharacterInfoMessage.scala
index c75fface..29488447 100644
--- a/common/src/main/scala/net/psforever/packet/game/CharacterInfoMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/CharacterInfoMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
diff --git a/common/src/main/scala/net/psforever/packet/game/EmoteMsg.scala b/common/src/main/scala/net/psforever/packet/game/EmoteMsg.scala
index fde47ced..8e911a14 100644
--- a/common/src/main/scala/net/psforever/packet/game/EmoteMsg.scala
+++ b/common/src/main/scala/net/psforever/packet/game/EmoteMsg.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.EmoteType
import scodec.Codec
import scodec.codecs._
diff --git a/common/src/main/scala/net/psforever/packet/game/FireHintMessage.scala b/common/src/main/scala/net/psforever/packet/game/FireHintMessage.scala
index 53bbd519..a724e863 100644
--- a/common/src/main/scala/net/psforever/packet/game/FireHintMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/FireHintMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.Vector3
import scodec.Codec
import scodec.codecs._
diff --git a/common/src/main/scala/net/psforever/packet/game/HitMessage.scala b/common/src/main/scala/net/psforever/packet/game/HitMessage.scala
index ba56f66a..c8b3451c 100644
--- a/common/src/main/scala/net/psforever/packet/game/HitMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/HitMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.Vector3
import scodec.Codec
import scodec.codecs._
diff --git a/common/src/main/scala/net/psforever/packet/game/LoginRespMessage.scala b/common/src/main/scala/net/psforever/packet/game/LoginRespMessage.scala
index 5a4f7835..0d17aefa 100644
--- a/common/src/main/scala/net/psforever/packet/game/LoginRespMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/LoginRespMessage.scala
@@ -77,7 +77,7 @@ object LoginRespMessage extends Marshallable[LoginRespMessage] {
("unknown" | uint32L) ::
("username" | PacketHelpers.encodedString) ::
("privilege" | uint32L)
- .flatZip(priv => bool) // really not so sure about this bool part. client gets just a single bit
+ .flatZip(_ => bool) // really not so sure about this bool part. client gets just a single bit
.xmap[Long]({case (a, _) => a}, priv => (priv, (priv & 1) == 1))
).as[LoginRespMessage]
}
diff --git a/common/src/main/scala/net/psforever/packet/game/ObjectDeleteMessage.scala b/common/src/main/scala/net/psforever/packet/game/ObjectDeleteMessage.scala
index 652f0e7f..00e2e085 100644
--- a/common/src/main/scala/net/psforever/packet/game/ObjectDeleteMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/ObjectDeleteMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
diff --git a/common/src/main/scala/net/psforever/packet/game/ObjectHeldMessage.scala b/common/src/main/scala/net/psforever/packet/game/ObjectHeldMessage.scala
index 2f6015d7..b826d9f4 100644
--- a/common/src/main/scala/net/psforever/packet/game/ObjectHeldMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/ObjectHeldMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
diff --git a/common/src/main/scala/net/psforever/packet/game/PingMsg.scala b/common/src/main/scala/net/psforever/packet/game/PingMsg.scala
index 1f317622..3dca80a7 100644
--- a/common/src/main/scala/net/psforever/packet/game/PingMsg.scala
+++ b/common/src/main/scala/net/psforever/packet/game/PingMsg.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
diff --git a/common/src/main/scala/net/psforever/packet/game/QuantityDeltaUpdateMessage.scala b/common/src/main/scala/net/psforever/packet/game/QuantityDeltaUpdateMessage.scala
index 898beff9..bec65f0a 100644
--- a/common/src/main/scala/net/psforever/packet/game/QuantityDeltaUpdateMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/QuantityDeltaUpdateMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
diff --git a/common/src/main/scala/net/psforever/packet/game/QuantityUpdateMessage.scala b/common/src/main/scala/net/psforever/packet/game/QuantityUpdateMessage.scala
index e2f20cfd..1f112c3f 100644
--- a/common/src/main/scala/net/psforever/packet/game/QuantityUpdateMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/QuantityUpdateMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
diff --git a/common/src/main/scala/net/psforever/packet/game/ReloadMessage.scala b/common/src/main/scala/net/psforever/packet/game/ReloadMessage.scala
index b3045263..02374b05 100644
--- a/common/src/main/scala/net/psforever/packet/game/ReloadMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/ReloadMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
diff --git a/common/src/main/scala/net/psforever/packet/game/SetCurrentAvatarMessage.scala b/common/src/main/scala/net/psforever/packet/game/SetCurrentAvatarMessage.scala
index e40632a2..0abd2c16 100644
--- a/common/src/main/scala/net/psforever/packet/game/SetCurrentAvatarMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/SetCurrentAvatarMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
diff --git a/common/src/main/scala/net/psforever/packet/game/SetEmpireMessage.scala b/common/src/main/scala/net/psforever/packet/game/SetEmpireMessage.scala
index 4b6f643f..8b6d5cde 100644
--- a/common/src/main/scala/net/psforever/packet/game/SetEmpireMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/SetEmpireMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.PlanetSideEmpire
import scodec.Codec
import scodec.codecs._
diff --git a/common/src/main/scala/net/psforever/packet/game/UseItemMessage.scala b/common/src/main/scala/net/psforever/packet/game/UseItemMessage.scala
index 89ca2e2e..0df5c38f 100644
--- a/common/src/main/scala/net/psforever/packet/game/UseItemMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/UseItemMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.Vector3
import scodec.Codec
import scodec.codecs._
diff --git a/common/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala b/common/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala
new file mode 100644
index 00000000..40910c43
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala
@@ -0,0 +1,95 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game
+
+import net.psforever.newcodecs.newcodecs
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
+import net.psforever.types.Vector3
+import scodec.Codec
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+//TODO write more thorough comments later.
+/**
+ * Dispatched to report and update the operational condition of a given vehicle.
+ * @param vehicle_guid the vehicle
+ * @param unk1 na
+ * @param pos the xyz-coordinate location in the world
+ * @param roll the amount of roll that affects orientation;
+ * 0.0f is flat to the ground;
+ * roll-right rotation increases angle
+ * @param pitch the amount of pitch that affects orientation;
+ * 0.0f is flat to the ground;
+ * front-up rotation increases angle
+ * @param yaw the amount of yaw that affects orientation;
+ * 0.0f is North (before the correction, 0.0f is East);
+ * clockwise rotation increases angle
+ * @param vel optional movement data
+ * @param unk2 na
+ * @param unk3 na
+ * @param unk4 na
+ * @param wheel_direction for ground vehicles, whether the wheels are being turned;
+ * 15 for straight;
+ * 0 for hard left;
+ * 30 for hard right
+ * @param unk5 na
+ * @param unk6 na
+ * @see `PlacementData`
+ */
+final case class VehicleStateMessage(vehicle_guid : PlanetSideGUID,
+ unk1 : Int,
+ pos : Vector3,
+ roll : Float,
+ pitch : Float,
+ yaw : Float,
+ vel : Option[Vector3],
+ unk2 : Option[Int],
+ unk3 : Int,
+ unk4 : Int,
+ wheel_direction : Int,
+ unk5 : Boolean,
+ unk6 : Boolean
+ ) extends PlanetSideGamePacket {
+ type Packet = VehicleStateMessage
+ def opcode = GamePacketOpcode.VehicleStateMessage
+ def encode = VehicleStateMessage.encode(this)
+}
+
+object VehicleStateMessage extends Marshallable[VehicleStateMessage] {
+ implicit val codec : Codec[VehicleStateMessage] = (
+ ("vehicle_guid" | PlanetSideGUID.codec) ::
+ ("unk1" | uintL(3)) ::
+ ("pos" | Vector3.codec_pos) ::
+ ("roll" | newcodecs.q_float(0.0f, 360.0f, 10)) ::
+ ("pitch" | newcodecs.q_float(360.0f, 0.0f, 10)) ::
+ ("yaw" | newcodecs.q_float(360.0f, 0.0f, 10)) ::
+ optional(bool, "vel" | Vector3.codec_vel) ::
+ optional(bool, "unk2" | uintL(5)) ::
+ ("unk3" | uintL(7)) ::
+ ("unk4" | uint4L) ::
+ ("wheel_direction" | uintL(5)) ::
+ ("int5" | bool) ::
+ ("int6" | bool)
+ ).xmap[VehicleStateMessage] (
+ {
+ case guid :: u1 :: pos :: roll :: pitch :: yaw :: vel :: u2 :: u3 :: u4 :: wheel :: u5 :: u6 :: HNil =>
+ var northCorrectedYaw : Float = yaw + 90f
+ if(northCorrectedYaw > 360f) {
+ northCorrectedYaw = northCorrectedYaw - 360f
+ }
+ VehicleStateMessage(guid, u1, pos, roll, pitch, northCorrectedYaw, vel, u2, u3, u4, wheel, u5, u6)
+ },
+
+ {
+ case VehicleStateMessage(guid, u1, pos, roll, pitch, yaw, vel, u2, u3, u4, wheel, u5, u6) =>
+ var northCorrectedYaw : Float = yaw - 90f
+ //TODO this invites imprecision
+ while(northCorrectedYaw < 0f) {
+ northCorrectedYaw = 360f + northCorrectedYaw
+ }
+ if(northCorrectedYaw > 360f) {
+ northCorrectedYaw = northCorrectedYaw % 360f
+ }
+ guid :: u1 :: pos :: roll :: pitch :: northCorrectedYaw :: vel :: u2 :: u3 :: u4 :: wheel :: u5 :: u6 :: HNil
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/WeaponDelayFireMessage.scala b/common/src/main/scala/net/psforever/packet/game/WeaponDelayFireMessage.scala
index 74c71be7..2a52ce8d 100644
--- a/common/src/main/scala/net/psforever/packet/game/WeaponDelayFireMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/WeaponDelayFireMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
diff --git a/common/src/main/scala/net/psforever/packet/game/WeaponDryFireMessage.scala b/common/src/main/scala/net/psforever/packet/game/WeaponDryFireMessage.scala
index d24e30a4..34ccf12c 100644
--- a/common/src/main/scala/net/psforever/packet/game/WeaponDryFireMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/WeaponDryFireMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
@@ -21,7 +21,5 @@ final case class WeaponDryFireMessage(weapon_guid : PlanetSideGUID)
}
object WeaponDryFireMessage extends Marshallable[WeaponDryFireMessage] {
- implicit val codec : Codec[WeaponDryFireMessage] = (
- ("weapon_guid" | PlanetSideGUID.codec)
- ).as[WeaponDryFireMessage]
+ implicit val codec : Codec[WeaponDryFireMessage] = ("weapon_guid" | PlanetSideGUID.codec).as[WeaponDryFireMessage]
}
diff --git a/common/src/main/scala/net/psforever/packet/game/WeaponFireMessage.scala b/common/src/main/scala/net/psforever/packet/game/WeaponFireMessage.scala
index 5d4c2bd6..d3a87bdf 100644
--- a/common/src/main/scala/net/psforever/packet/game/WeaponFireMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/WeaponFireMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.Vector3
import scodec.Codec
import scodec.codecs._
@@ -39,7 +39,7 @@ object WeaponFireMessage extends Marshallable[WeaponFireMessage] {
("unk4" | uint16L) ::
("unk5" | uint8L) ::
(("unk6" | uintL(3)) >>:~ { unk6_value =>
- conditional(unk6_value == 3, ("unk7" | optional(bool, Vector3.codec_vel))).hlist
+ conditional(unk6_value == 3, "unk7" | optional(bool, Vector3.codec_vel)).hlist
})
).as[WeaponFireMessage]
}
diff --git a/common/src/main/scala/net/psforever/packet/game/WeaponJammedMessage.scala b/common/src/main/scala/net/psforever/packet/game/WeaponJammedMessage.scala
index 809dbc18..dc96a537 100644
--- a/common/src/main/scala/net/psforever/packet/game/WeaponJammedMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/WeaponJammedMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
@@ -21,7 +21,5 @@ final case class WeaponJammedMessage(weapon_guid : PlanetSideGUID)
}
object WeaponJammedMessage extends Marshallable[WeaponJammedMessage] {
- implicit val codec : Codec[WeaponJammedMessage] = (
- ("weapon_guid" | PlanetSideGUID.codec)
- ).as[WeaponJammedMessage]
+ implicit val codec : Codec[WeaponJammedMessage] = ("weapon_guid" | PlanetSideGUID.codec).as[WeaponJammedMessage]
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ACEData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ACEData.scala
index af93c131..ef6324e2 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ACEData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ACEData.scala
@@ -2,7 +2,6 @@
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
-import net.psforever.packet.game.PlanetSideGUID
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/AMSData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/AMSData.scala
new file mode 100644
index 00000000..54a226cc
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/AMSData.scala
@@ -0,0 +1,99 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import net.psforever.packet.game.PlanetSideGUID
+import net.psforever.types.PlanetSideEmpire
+import scodec.codecs._
+import scodec.{Attempt, Codec, Err}
+import shapeless.{::, HNil}
+
+/**
+ * A representation of a vehicle called the Advanced Mobile Station (AMS).
+ *
+ * The AMS has four utilities associated with its `Deployed` mode.
+ * It has two flanking equipment terminals, a front matrix panel, and a rear deconstruction terminal.
+ * This is consistent from AMS to AMS, regardless of the faction that spawned the vehicle originally.
+ * For that reason, the only thing that changes between different AMS's are the GUIDs used for each terminal.
+ * @param basic data common to objects
+ * @param unk1 na
+ * @param health the amount of health the object has, as a percentage of a filled bar
+ * @param unk2 na
+ * @param driveState the drivable condition
+ * @param unk3 na;
+ * common values are 0 or 63;
+ * usually in a non-`Mobile` state when non-zero
+ * @param matrix_guid the GUID for the spawn matrix panel on the front
+ * @param respawn_guid the GUID for the respawn apparatus on the rear
+ * @param term_a_guid the GUID for the equipment terminal on the AMS on the left side
+ * @param term_b_guid the GUID for the equipment on the AMS on the right side
+ */
+final case class AMSData(basic : CommonFieldData,
+ unk1 : Int,
+ health : Int,
+ unk2 : Int,
+ driveState : DriveState.Value,
+ unk3 : Int,
+ matrix_guid : PlanetSideGUID,
+ respawn_guid : PlanetSideGUID,
+ term_a_guid : PlanetSideGUID,
+ term_b_guid : PlanetSideGUID
+ ) extends ConstructorData {
+ override def bitsize : Long = {
+ val basicSize = basic.bitsize
+ val vehicleSize : Long = VehicleData.baseVehicleSize
+ //the four utilities should all be the same size
+ val utilitySize : Long = 4 * InternalSlot(ObjectClass.matrix_terminalc, matrix_guid, 1, CommonTerminalData(basic.faction)).bitsize
+ 19L + basicSize + vehicleSize + utilitySize
+ }
+}
+
+object AMSData extends Marshallable[AMSData] {
+ /**
+ * Overloaded constructor that ignores all of the unknown fields.
+ * @param basic data common to objects
+ * @param health the amount of health the object has, as a percentage of a filled bar
+ * @param driveState the drivable condition
+ * @param matrix_guid the GUID for the spawn matrix panel on the front
+ * @param respawn_guid the GUID for the respawn apparatus on the rear
+ * @param term_a_guid the GUID for the equipment terminal on the AMS on the left side
+ * @param term_b_guid the GUID for the equipment on the AMS on the right side
+ * @return an `AMSData` object
+ */
+ def apply(basic : CommonFieldData, health : Int, driveState : DriveState.Value, matrix_guid : PlanetSideGUID, respawn_guid : PlanetSideGUID, term_a_guid : PlanetSideGUID, term_b_guid : PlanetSideGUID) : AMSData =
+ new AMSData(basic, 0, health, 0, driveState, 0, matrix_guid, respawn_guid, term_a_guid, term_b_guid)
+
+ implicit val codec : Codec[AMSData] = (
+ VehicleData.basic_vehicle_codec :+
+ uintL(6) :+
+ bool :+
+ uintL(12) :+
+ InternalSlot.codec :+
+ InternalSlot.codec :+
+ InternalSlot.codec :+
+ InternalSlot.codec
+ ).exmap[AMSData] (
+ {
+ case basic :: unk1 :: health :: unk2 :: driveState :: false :: unk3 :: false :: 0x41 ::
+ InternalSlot(ObjectClass.matrix_terminalc, matrix_guid, 1, CommonTerminalData(_, _)) ::
+ InternalSlot(ObjectClass.ams_respawn_tube, respawn_guid,2, CommonTerminalData(_, _)) ::
+ InternalSlot(ObjectClass.order_terminala, terma_guid, 3, CommonTerminalData(_, _)) ::
+ InternalSlot(ObjectClass.order_terminalb, termb_guid, 4, CommonTerminalData(_, _)) :: HNil =>
+ Attempt.successful(AMSData(basic, unk1, health, unk2, driveState, unk3, matrix_guid, respawn_guid, terma_guid, termb_guid))
+
+ case _ =>
+ Attempt.failure(Err("invalid AMS data"))
+ },
+ {
+ case AMSData(basic, unk1, health, unk2, driveState, unk3, matrix_guid, respawn_guid, terma_guid, termb_guid) =>
+ val faction : PlanetSideEmpire.Value = basic.faction
+ Attempt.successful(
+ basic :: unk1 :: health :: unk2 :: driveState :: false :: unk3 :: false :: 0x41 ::
+ InternalSlot(ObjectClass.matrix_terminalc, matrix_guid, 1, CommonTerminalData(faction)) ::
+ InternalSlot(ObjectClass.ams_respawn_tube, respawn_guid,2, CommonTerminalData(faction)) ::
+ InternalSlot(ObjectClass.order_terminala, terma_guid, 3, CommonTerminalData(faction)) ::
+ InternalSlot(ObjectClass.order_terminalb, termb_guid, 4, CommonTerminalData(faction)) :: HNil
+ )
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ANTData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ANTData.scala
new file mode 100644
index 00000000..b75a3df7
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ANTData.scala
@@ -0,0 +1,62 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import scodec.codecs._
+import scodec.{Attempt, Codec, Err}
+import shapeless.{::, HNil}
+
+/**
+ * A representation of a vehicle called the Advanced Nanite Transport (ANT).
+ * @param basic data common to objects
+ * @param unk1 na
+ * @param health the amount of health the object has, as a percentage of a filled bar
+ * @param unk2 na
+ * @param driveState the drivable condition;
+ * defaults to `Mobile`
+ * @param unk3 na;
+ * defaults to 0
+ */
+final case class ANTData(basic : CommonFieldData,
+ unk1 : Int,
+ health : Int,
+ unk2 : Int,
+ driveState : DriveState.Value = DriveState.Mobile,
+ unk3 : Int = 0
+ ) extends ConstructorData {
+ override def bitsize : Long = {
+ val basicSize = basic.bitsize
+ val vehicleBasicSize : Long = VehicleData.baseVehicleSize
+ 9L + basicSize + vehicleBasicSize
+ }
+}
+
+object ANTData extends Marshallable[ANTData] {
+ /**
+ * Overloaded constructor.
+ * @param basic data common to objects
+ * @param health the amount of health the object has, as a percentage of a filled bar
+ * @param driveState the drivable condition
+ * @return an `ANTData` object
+ */
+ def apply(basic : CommonFieldData, health : Int, driveState : DriveState.Value) : ANTData =
+ new ANTData(basic, 0, health, 0, driveState, 0)
+
+ implicit val codec : Codec[ANTData] = (
+ VehicleData.basic_vehicle_codec :+
+ uint8L :+
+ bool //false for vehicle driving control; ditto u4 from above
+ ).exmap[ANTData] (
+ {
+ case basic :: unk1 :: health :: unk2 :: driveState :: false :: unk3 :: false :: HNil =>
+ Attempt.successful(ANTData(basic, unk1, health, unk2, driveState, unk3))
+
+ case _ =>
+ Attempt.failure(Err("invalid ant data format"))
+ },
+ {
+ case ANTData(basic, unk1, health, unk2, driveState, unk3) =>
+ Attempt.successful(basic :: unk1 :: health :: unk2 :: driveState :: false :: unk3 :: false :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/AegisShieldGeneratorData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/AegisShieldGeneratorData.scala
index e94a6348..271d7ca7 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/AegisShieldGeneratorData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/AegisShieldGeneratorData.scala
@@ -11,7 +11,7 @@ import shapeless.{::, HNil}
* @param deploy data common to objects spawned by the (advanced) adaptive construction engine
* @param health the amount of health the object has, as a percentage of a filled bar
*/
-final case class AegisShieldGeneratorData(deploy : ACEDeployableData,
+final case class AegisShieldGeneratorData(deploy : CommonFieldData,
health : Int
) extends ConstructorData {
override def bitsize : Long = {
@@ -21,7 +21,7 @@ final case class AegisShieldGeneratorData(deploy : ACEDeployableData,
object AegisShieldGeneratorData extends Marshallable[AegisShieldGeneratorData] {
implicit val codec : Codec[AegisShieldGeneratorData] = (
- ("deploy" | ACEDeployableData.codec) ::
+ ("deploy" | CommonFieldData.codec) ::
("health" | uint8L) ::
uint32 :: uint32 :: uint32 :: uint4L //100 bits
).exmap[AegisShieldGeneratorData] (
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/AmmoBoxData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/AmmoBoxData.scala
index 865e697a..90f1c281 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/AmmoBoxData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/AmmoBoxData.scala
@@ -36,7 +36,7 @@ object AmmoBoxData extends Marshallable[AmmoBoxData] {
implicit val codec : Codec[AmmoBoxData] = (
uint4L ::
- ("unk" | uint4L) ::
+ ("unk" | uint4L) :: // 8 - common - 4 - safe, 2 - stream misalignment, 1 - safe, 0 - common
uint(16)
).exmap[AmmoBoxData] (
{
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala
index 01f775cd..85a01340 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala
@@ -113,7 +113,7 @@ final case class CharacterAppearanceData(pos : PlacementData,
override def bitsize : Long = {
//factor guard bool values into the base size, not its corresponding optional field
val placementSize : Long = pos.bitsize
- val nameStringSize : Long = StreamBitSize.stringBitSize(basic_appearance.name, 16) + CharacterAppearanceData.namePadding(pos.init_move)
+ val nameStringSize : Long = StreamBitSize.stringBitSize(basic_appearance.name, 16) + CharacterAppearanceData.namePadding(pos.vel)
val outfitStringSize : Long = StreamBitSize.stringBitSize(outfit_name, 16) + CharacterAppearanceData.outfitNamePadding
val altModelSize = if(on_zipline || backpack) { 1L } else { 0L }
335L + placementSize + nameStringSize + outfitStringSize + altModelSize
@@ -153,7 +153,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
("jammered" | bool) ::
bool :: //crashes client
uint(16) :: //unknown, but usually 0
- ("name" | PacketHelpers.encodedWideStringAligned( namePadding(pos.init_move) )) ::
+ ("name" | PacketHelpers.encodedWideStringAligned( namePadding(pos.vel) )) ::
("exosuit" | ExoSuitType.codec) ::
ignore(2) :: //unknown
("sex" | CharacterGender.codec) ::
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ACEDeployableData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonFieldData.scala
similarity index 59%
rename from common/src/main/scala/net/psforever/packet/game/objectcreate/ACEDeployableData.scala
rename to common/src/main/scala/net/psforever/packet/game/objectcreate/CommonFieldData.scala
index af4182c0..f598e4fd 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ACEDeployableData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonFieldData.scala
@@ -3,26 +3,39 @@ package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import net.psforever.packet.game.PlanetSideGUID
+import net.psforever.types.PlanetSideEmpire
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
/**
- * Data that is common to a number of items that are spawned by the adaptive construction engine, or its advanced version.
+ * Data that is common to a number of game object serializations.
* @param pos where and how the object is oriented
+ * @param faction association of the object with
* @param unk na
- * @param player_guid the player who placed this object
+ * @param player_guid the player who placed/leverages/[action]s this object
*/
-final case class ACEDeployableData(pos : PlacementData,
- unk : Int,
- player_guid : PlanetSideGUID
+final case class CommonFieldData(pos : PlacementData,
+ faction : PlanetSideEmpire.Value,
+ unk : Int,
+ player_guid : PlanetSideGUID
) extends StreamBitSize {
override def bitsize : Long = 23L + pos.bitsize
}
-object ACEDeployableData extends Marshallable[ACEDeployableData] {
+object CommonFieldData extends Marshallable[CommonFieldData] {
final val internalWeapon_bitsize : Long = 10
+ /**
+ * Overloaded constructor that eliminates the need to list the fourth, optional, GUID field.
+ * @param pos where and how the object is oriented
+ * @param faction association of the object with
+ * @param unk na
+ * @return a `CommonFieldData` object
+ */
+ def apply(pos : PlacementData, faction : PlanetSideEmpire.Value, unk : Int) : CommonFieldData =
+ CommonFieldData(pos, faction, unk, PlanetSideGUID(0))
+
/**
* `Codec` for transforming reliable `WeaponData` from the internal structure of the turret when it is defined.
* Works for both `SmallTurretData` and `OneMannedFieldTurretData`.
@@ -57,21 +70,22 @@ object ACEDeployableData extends Marshallable[ACEDeployableData] {
}
)
- implicit val codec : Codec[ACEDeployableData] = (
+ implicit val codec : Codec[CommonFieldData] = (
("pos" | PlacementData.codec) ::
- ("unk1" | uint(7)) ::
+ ("faction" | PlanetSideEmpire.codec) ::
+ ("unk" | uint(5)) ::
("player_guid" | PlanetSideGUID.codec)
- ).exmap[ACEDeployableData] (
+ ).exmap[CommonFieldData] (
{
- case pos :: unk :: player :: HNil =>
- Attempt.successful(ACEDeployableData(pos, unk, player))
+ case pos :: fac :: unk :: player :: HNil =>
+ Attempt.successful(CommonFieldData(pos, fac, unk, player))
case _ =>
Attempt.failure(Err("invalid deployable data format"))
},
{
- case ACEDeployableData(pos, unk, player) =>
- Attempt.successful(pos :: unk :: player :: HNil)
+ case CommonFieldData(pos, fac, unk, player) =>
+ Attempt.successful(pos :: fac :: unk :: player :: HNil)
}
)
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonTerminalData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonTerminalData.scala
index dd85ce26..7a5f6d7f 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonTerminalData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/CommonTerminalData.scala
@@ -2,6 +2,8 @@
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
+import net.psforever.packet.game.PlanetSideGUID
+import net.psforever.types.PlanetSideEmpire
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
@@ -9,28 +11,42 @@ import shapeless.{::, HNil}
/**
* A representation of an object that can be interacted with when using a variety of terminals.
* This object is generally invisible.
- * @param pos where and how the object is oriented
+ * @param faction the faction that can access the terminal
+ * @param unk na
*/
-final case class CommonTerminalData(pos : PlacementData) extends ConstructorData {
- override def bitsize : Long = 24L + pos.bitsize
+final case class CommonTerminalData(faction : PlanetSideEmpire.Value,
+ unk : Int = 0
+ ) extends ConstructorData {
+ override def bitsize : Long = 24L
}
object CommonTerminalData extends Marshallable[CommonTerminalData] {
+ /**
+ * Overloaded constructor for a type of common terminal.
+ * @param cls the code for the type of object being constructed
+ * @param guid the GUID this object will be assigned
+ * @param parentSlot a parent-defined slot identifier that explains where the child is to be attached to the parent
+ * @param terminal the `CommonTerminalData`
+ * @return an `InternalSlot` object
+ */
+ def apply(cls : Int, guid : PlanetSideGUID, parentSlot : Int, terminal : CommonTerminalData) : InternalSlot =
+ InternalSlot(cls, guid, parentSlot, terminal)
+
implicit val codec : Codec[CommonTerminalData] = (
- ("pos" | PlacementData.codec) ::
- bool ::
- bool ::
- uint(22)
+ ("faction" | PlanetSideEmpire.codec) ::
+ uint2L ::
+ ("unk" | uint2L) ::
+ uint(18)
).exmap[CommonTerminalData] (
{
- case pos :: false :: true :: 0 :: HNil =>
- Attempt.successful(CommonTerminalData(pos))
+ case fac :: 0 :: unk :: 0 :: HNil =>
+ Attempt.successful(CommonTerminalData(fac, unk))
case _ :: _ :: _ :: _ :: HNil =>
Attempt.failure(Err("invalid terminal data format"))
},
{
- case CommonTerminalData(pos) =>
- Attempt.successful(pos :: false :: true :: 0 :: HNil)
+ case CommonTerminalData(fac, unk) =>
+ Attempt.successful(fac :: 0 :: unk :: 0 :: HNil)
}
)
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ConcurrentFeedWeaponData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ConcurrentFeedWeaponData.scala
deleted file mode 100644
index adfddf2d..00000000
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ConcurrentFeedWeaponData.scala
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (c) 2017 PSForever
-package net.psforever.packet.game.objectcreate
-
-import net.psforever.packet.game.PlanetSideGUID
-import net.psforever.packet.{Marshallable, PacketHelpers}
-import scodec.codecs.{uint, _}
-import scodec.{Attempt, Codec, Err}
-import shapeless.{::, HNil}
-
-/**
- * A representation of a class of weapons that can be created using `ObjectCreateDetailedMessage` packet data.
- * A "concurrent feed weapon" refers to a weapon system that can chamber multiple types of ammunition simultaneously.
- * This data will help construct a "weapon" such as a Punisher.
- *
- * The data for the weapons nests information for the default (current) type and number of ammunition in its magazine.
- * This ammunition data essentially is the weapon's magazines as numbered slots.
- * @param unk1 na
- * @param unk2 na
- * @param fire_mode the current mode of weapon's fire;
- * zero-indexed
- * @param ammo `List` data regarding the currently loaded ammunition types and quantities
- * @see `WeaponData`
- * @see `AmmoBoxData`
- */
-final case class ConcurrentFeedWeaponData(unk1 : Int,
- unk2 : Int,
- fire_mode : Int,
- ammo : List[InternalSlot]) extends ConstructorData {
- override def bitsize : Long = {
- var bitsize : Long = 0L
- for(o <- ammo) {
- bitsize += o.bitsize
- }
- 44L + bitsize
- }
-}
-
-object ConcurrentFeedWeaponData extends Marshallable[ConcurrentFeedWeaponData] {
- /**
- * An abbreviated constructor for creating `ConcurrentFeedWeaponData` while masking use of `InternalSlot` for its `DetailedAmmoBoxData`.
- *
- * Exploration:
- * This class may need to be rewritten later to support objects spawned in the world environment.
- * @param unk1 na
- * @param unk2 na
- * @param fire_mode data regarding the currently loaded ammunition type
- * @param cls the code for the type of object (ammunition) being constructed
- * @param guid the globally unique id assigned to the ammunition
- * @param parentSlot the slot where the ammunition is to be installed in the weapon
- * @param ammo the constructor data for the ammunition
- * @return a DetailedWeaponData object
- */
- def apply(unk1 : Int, unk2 : Int, fire_mode : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : DetailedAmmoBoxData) : ConcurrentFeedWeaponData =
- new ConcurrentFeedWeaponData(unk1, unk2, fire_mode, InternalSlot(cls, guid, parentSlot, ammo) :: Nil)
-
- implicit val codec : Codec[ConcurrentFeedWeaponData] = (
- ("unk1" | uint4L) ::
- ("unk2" | uint4L) ::
- uint(20) ::
- ("fire_mode" | int(3)) ::
- bool ::
- bool ::
- (uint8L >>:~ { size =>
- uint2L ::
- ("ammo" | PacketHelpers.listOfNSized(size, InternalSlot.codec)) ::
- bool
- })
- ).exmap[ConcurrentFeedWeaponData] (
- {
- case unk1 :: unk2 :: 0 :: fmode :: false :: true :: size :: 0 :: ammo :: false :: HNil =>
- if(size != ammo.size)
- Attempt.failure(Err("weapon encodes wrong number of ammunition"))
- else if(size == 0)
- Attempt.failure(Err("weapon needs to encode at least one type of ammunition"))
- else
- Attempt.successful(ConcurrentFeedWeaponData(unk1, unk2, fmode, ammo))
- case _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil =>
- Attempt.failure(Err("invalid weapon data format"))
- },
- {
- case ConcurrentFeedWeaponData(unk1, unk2, fmode, ammo) =>
- val size = ammo.size
- if(size == 0)
- Attempt.failure(Err("weapon needs to encode at least one type of ammunition"))
- else if(size >= 255)
- Attempt.failure(Err("weapon has too much ammunition (255+ types!)"))
- else
- Attempt.successful(unk1 :: unk2 :: 0 :: fmode :: false :: true :: size :: 0 :: ammo :: false :: HNil)
- }
- )
-}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DestroyedVehicleData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DestroyedVehicleData.scala
new file mode 100644
index 00000000..1069c89e
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DestroyedVehicleData.scala
@@ -0,0 +1,21 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import scodec.codecs._
+import scodec.Codec
+
+/**
+ * A representation of the charred husk of a destroyed vehicle.
+ *
+ * This is a hand-crafted `Codec` and was not based on something found on Gemini Live.
+ * @param pos where and how the object is oriented;
+ * `pos.vel` existing is fine
+ */
+final case class DestroyedVehicleData(pos : PlacementData) extends ConstructorData {
+ override def bitsize : Long = pos.bitsize
+}
+
+object DestroyedVehicleData extends Marshallable[DestroyedVehicleData] {
+ implicit val codec : Codec[DestroyedVehicleData] = ("pos" | PlacementData.codec).as[DestroyedVehicleData]
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedAmmoBoxData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedAmmoBoxData.scala
index b3bdf95f..794ccf23 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedAmmoBoxData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedAmmoBoxData.scala
@@ -39,7 +39,7 @@ object DetailedAmmoBoxData extends Marshallable[DetailedAmmoBoxData] {
implicit val codec : Codec[DetailedAmmoBoxData] = (
uint4L ::
- ("unk" | uint4L) ::
+ ("unk" | uint4L) :: // 8 - common - 4 - safe, 2 - stream misalignment, 1 - safe, 0 - common
uint(15) ::
("magazine" | uint16L) ::
bool
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedConcurrentFeedWeaponData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedConcurrentFeedWeaponData.scala
deleted file mode 100644
index b96151cb..00000000
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedConcurrentFeedWeaponData.scala
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) 2017 PSForever
-package net.psforever.packet.game.objectcreate
-
-import net.psforever.packet.game.PlanetSideGUID
-import net.psforever.packet.{Marshallable, PacketHelpers}
-import scodec.codecs._
-import scodec.{Attempt, Codec, Err}
-import shapeless.{::, HNil}
-
-/**
- * A representation of a class of weapons that can be created using `ObjectCreateDetailedMessage` packet data.
- * A "concurrent feed weapon" refers to a weapon system that can chamber multiple types of ammunition simultaneously.
- * This data will help construct a "weapon" such as a Punisher.
- *
- * The data for the weapons nests information for the default (current) type of ammunition in its magazine.
- * This ammunition data essentially is the weapon's magazines as numbered slots.
- * @param unk1 na
- * @param unk2 na
- * @param ammo `List` data regarding the currently loaded ammunition types and quantities
- * @see DetailedWeaponData
- * @see DetailedAmmoBoxData
- */
-final case class DetailedConcurrentFeedWeaponData(unk1 : Int,
- unk2 : Int,
- ammo : List[InternalSlot]) extends ConstructorData {
- override def bitsize : Long = {
- var bitsize : Long = 0L
- for(o <- ammo) {
- bitsize += o.bitsize
- }
- 61L + bitsize
- }
-}
-
-object DetailedConcurrentFeedWeaponData extends Marshallable[DetailedConcurrentFeedWeaponData] {
- /**
- * An abbreviated constructor for creating `DetailedConcurrentFeedWeaponData` while masking use of `InternalSlot` for its `DetailedAmmoBoxData`.
- *
- * Exploration:
- * This class may need to be rewritten later to support objects spawned in the world environment.
- * @param unk1 na
- * @param unk2 na
- * @param cls the code for the type of object (ammunition) being constructed
- * @param guid the globally unique id assigned to the ammunition
- * @param parentSlot the slot where the ammunition is to be installed in the weapon
- * @param ammo the constructor data for the ammunition
- * @return a DetailedWeaponData object
- */
- def apply(unk1 : Int, unk2 : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : DetailedAmmoBoxData) : DetailedConcurrentFeedWeaponData =
- new DetailedConcurrentFeedWeaponData(unk1, unk2, InternalSlot(cls, guid, parentSlot, ammo) :: Nil)
-
- implicit val codec : Codec[DetailedConcurrentFeedWeaponData] = (
- ("unk" | uint4L) ::
- uint4L ::
- uint24 ::
- uint16 ::
- uint2L ::
- (uint8L >>:~ { size =>
- uint2L ::
- ("ammo" | PacketHelpers.listOfNSized(size, InternalSlot.codec_detailed)) ::
- bool
- })
- ).exmap[DetailedConcurrentFeedWeaponData] (
- {
- case unk1 :: unk2 :: 2 :: 0 :: 3 :: size :: 0 :: ammo :: false :: HNil =>
- if(size != ammo.size)
- Attempt.failure(Err("weapon encodes wrong number of ammunition"))
- else if(size == 0)
- Attempt.failure(Err("weapon needs to encode at least one type of ammunition"))
- else
- Attempt.successful(DetailedConcurrentFeedWeaponData(unk1, unk2, ammo))
- case _ =>
- Attempt.failure(Err("invalid weapon data format"))
- },
- {
- case DetailedConcurrentFeedWeaponData(unk1, unk2, ammo) =>
- val size = ammo.size
- if(size == 0)
- Attempt.failure(Err("weapon needs to encode at least one type of ammunition"))
- else if(size >= 255)
- Attempt.failure(Err("weapon has too much ammunition (255+ types!)"))
- else
- Attempt.successful(unk1 :: unk2 :: 2 :: 0 :: 3 :: size :: 0 :: ammo :: false :: HNil)
- }
- )
-}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedWeaponData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedWeaponData.scala
index a02baeda..9e4ecaf8 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedWeaponData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedWeaponData.scala
@@ -13,52 +13,107 @@ import shapeless.{::, HNil}
*
* The data for the weapons nests information for the default (current) type and number of ammunition in its magazine.
* This ammunition data essentially is the weapon's magazines as numbered slots.
- * This format only handles one type of ammunition at a time.
- * Any weapon that has two types of ammunition simultaneously loaded must be handled with another `Codec`.
- * This functionality is unrelated to a weapon that switches ammunition type;
- * a weapon with that behavior is handled perfectly fine using this `case class`.
- * @param unk na
- * @param ammo data regarding the currently loaded ammunition type and quantity
- * @see DetailedAmmoBoxData
+ * An "expected" number of ammunition slot data can be passed into the function.
+ * @param unk1 na
+ * @param unk2 na
+ * @param ammo data regarding the currently loaded ammunition type(s) and quantity(ies)
+ * @param mag_capacity implicit;
+ * the total number of concurrently-loaded ammunition types allowed in this weapon;
+ * concurrent ammunition does not need to be unloaded to be switched;
+ * defaults to 1;
+ * 0 is invalid;
+ * -1 or less ignores the imposed checks
+ * @see `DetailedAmmoBoxData`
+ * @see `WeaponData`
*/
-final case class DetailedWeaponData(unk : Int,
- ammo : InternalSlot) extends ConstructorData {
- override def bitsize : Long = 61L + ammo.bitsize
+final case class DetailedWeaponData(unk1 : Int,
+ unk2 : Int,
+ ammo : List[InternalSlot]
+ )(implicit val mag_capacity : Int = 1) extends ConstructorData {
+ override def bitsize : Long = {
+ var bitsize : Long = 0L
+ for(o <- ammo) {
+ bitsize += o.bitsize
+ }
+ 61L + bitsize
+ }
}
object DetailedWeaponData extends Marshallable[DetailedWeaponData] {
/**
- * An abbreviated constructor for creating `DetailedWeaponData` while masking use of `InternalSlot` for its `DetailedAmmoBoxData`.
- * @param unk na
+ * Overloaded constructor for creating `DetailedWeaponData` while masking use of `InternalSlot` for its `DetailedAmmoBoxData`.
+ * @param unk1 na
+ * @param unk2 na
* @param cls the code for the type of object (ammunition) being constructed
* @param guid the globally unique id assigned to the ammunition
* @param parentSlot the slot where the ammunition is to be installed in the weapon
* @param ammo the constructor data for the ammunition
- * @return a DetailedWeaponData object
+ * @return a `DetailedWeaponData` object
*/
- def apply(unk : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : DetailedAmmoBoxData) : DetailedWeaponData =
- new DetailedWeaponData(unk, InternalSlot(cls, guid, parentSlot, ammo))
+ def apply(unk1 : Int, unk2 : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : DetailedAmmoBoxData) : DetailedWeaponData =
+ new DetailedWeaponData(unk1, unk2, InternalSlot(cls, guid, parentSlot, ammo) :: Nil)
- implicit val codec : Codec[DetailedWeaponData] = (
- ("unk" | uint4L) ::
- uint4L ::
+ /**
+ * A `Codec` for `DetailedWeaponData`
+ * @param mag_capacity the total number of concurrently-loaded ammunition types allowed in this weapon;
+ * defaults to 1
+ * @return a `WeaponData` object or a `BitVector`
+ */
+ def codec(mag_capacity : Int = 1) : Codec[DetailedWeaponData] = (
+ ("unk1" | uintL(3)) ::
+ bool :: //weapon refuses to shoot if set (not weapons lock?)
+ ("unk2" | uint4L) :: //8 - common; 4 - jammers weapons; 2 - weapon breaks; 1, 0 - safe
uint24 ::
- uint16L ::
- uint2 ::
- uint8 :: //size = 1 type of ammunition loaded
- uint2 ::
- ("ammo" | InternalSlot.codec_detailed) ::
+ uint16 ::
+ uint2L ::
+ ("ammo" | InventoryData.codec_detailed) ::
bool
).exmap[DetailedWeaponData] (
{
- case code :: 8 :: 2 :: 0 :: 3 :: 1 :: 0 :: ammo :: false :: HNil =>
- Attempt.successful(DetailedWeaponData(code, ammo))
- case _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil =>
+ case unk1 :: false :: unk2 :: 2 :: 0 :: 3 :: InventoryData(ammo) :: false :: HNil =>
+ val magSize = ammo.size
+ if(mag_capacity == 0 || magSize == 0) {
+ Attempt.failure(Err("weapon must decode some ammunition"))
+ }
+ else if(mag_capacity > 0 && magSize != mag_capacity) {
+ Attempt.failure(Err(s"weapon decodes too much or too little ammunition - actual $magSize, expected $mag_capacity"))
+ }
+ else {
+ Attempt.successful(DetailedWeaponData(unk1, unk2, ammo)(magSize))
+ }
+
+ case _ =>
Attempt.failure(Err("invalid weapon data format"))
},
{
- case DetailedWeaponData(code, ammo) =>
- Attempt.successful(code :: 8 :: 2 :: 0 :: 3 :: 1 :: 0 :: ammo :: false :: HNil)
+ case obj @ DetailedWeaponData(unk1, unk2, ammo) =>
+ val magSize = ammo.size
+ val magCapacity = obj.mag_capacity
+ if(mag_capacity == 0 || magCapacity == 0 || magSize == 0) {
+ Attempt.failure(Err("weapon must encode some ammunition"))
+ }
+ else if(magCapacity < 0 || mag_capacity < 0) {
+ Attempt.successful(unk1 :: false :: unk2 :: 2 :: 0 :: 3 :: InventoryData(ammo) :: false :: HNil)
+ }
+ else {
+ if(magCapacity != mag_capacity) {
+ Attempt.failure(Err(s"different encoding expectations for amount of ammunition - actual $magCapacity, expected $mag_capacity"))
+ }
+ else if(magSize != mag_capacity) {
+ Attempt.failure(Err(s"weapon encodes wrong amount of ammunition - actual $magSize, expected $mag_capacity"))
+ }
+ else if(magSize >= 255) {
+ Attempt.failure(Err("weapon encodes too much ammunition (255+ types!)"))
+ }
+ else {
+ Attempt.successful(unk1 :: false :: unk2 :: 2 :: 0 :: 3 :: InventoryData(ammo) :: false :: HNil)
+ }
+ }
+
+ case _ =>
+ Attempt.failure(Err("invalid weapon data format"))
}
)
+
+ implicit val codec : Codec[DetailedWeaponData] = codec()
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DriveState.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DriveState.scala
new file mode 100644
index 00000000..fd685b73
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DriveState.scala
@@ -0,0 +1,26 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.PacketHelpers
+import scodec.codecs._
+
+/**
+ * An `Enumeration` of the mobility states of vehicles.
+ *
+ * In general, two important mobility states exist - `Mobile` and "deployed."
+ * There are three stages of a formal deployment.
+ * For any deployment state other than the defined ones, the vehicle assumes it is in one of the transitional states.
+ * If the target vehicle has no deployment behavior, a non-`Mobile` value will not affect it.
+ */
+object DriveState extends Enumeration {
+ type Type = Value
+
+ val Mobile = Value(0) //drivable
+ val Undeployed = Value(1) //stationary
+ val Unavailable = Value(2) //stationary, partial activation
+ val Deployed = Value(3) //stationary, full activation
+
+ val State7 = Value(7) //unknown; not encountered on a vehicle that can deploy; functions like Mobile
+
+ implicit val codec = PacketHelpers.createEnumerationCodec(this, uint8L)
+}
\ No newline at end of file
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DroppodData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DroppodData.scala
new file mode 100644
index 00000000..9ad295b0
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DroppodData.scala
@@ -0,0 +1,66 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import scodec.codecs._
+import scodec.{Attempt, Codec}
+import shapeless.{::, HNil}
+
+/**
+ * A representation of a droppod that is dropped from the HART shuttle and ferries the player into battle.
+ * Droppods are also used when a player has activated Instant Action.
+ *
+ * When the server first spawns the droppod, it will be placed at the world ceiling - 1024.0f.
+ * It is placed under control via another packet that sends it hurtling to the ground.
+ * Upon hitting the ground, it opens up, releasing the player, and despawns.
+ *
+ * Although the droppod is not technically a vehicle, it is treated as such by the game.
+ * A spawned and unoccupied droppod can be entered and exited, as expected (the seat is 0).
+ * There is no entry animation.
+ * The exit animation is the droppod flowering open as usual.
+ * Even in its spread open state, the droppod can be re-entered, though it will remain spread open.
+ * The player's character will disappear once "inside."
+ * Upon exiting again, the droppod will snap shut and spread open.
+ *
+ * Exploration:
+ * When `basic.player_guid` is defined, the droppod will not be at the world ceiling anymore and its boosters will be activate.
+ * Does this `basic.player_guid` actually represent the player who is in the pod?
+ * @param basic data common to objects
+ * @param burn whether the boosters are ignited
+ * @param health the amount of health the object has, as a percentage of a filled bar
+ * @see `DroppodLaunchRequestMessage`
+ * @see `DroppodLaunchResponseMessage`
+ */
+final case class DroppodData(basic : CommonFieldData,
+ burn : Boolean = false,
+ health : Int = 255
+ ) extends ConstructorData {
+ override def bitsize : Long = {
+ val basicSize = basic.bitsize
+ 29L + basicSize
+ }
+}
+
+object DroppodData extends Marshallable[DroppodData] {
+ implicit val codec : Codec[DroppodData] = (
+ ("basic" | CommonFieldData.codec) ::
+ bool ::
+ ("health" | uint8L) :: //health
+ uintL(5) :: //0x0
+ uint4L :: //0xF
+ uintL(6) :: //0x0
+ ("boosters" | uint4L) :: //0x9 on standby, 0x0 when burning and occupied (basic.player_guid?)
+ bool
+ ).exmap[DroppodData] (
+ {
+ case basic :: false :: health :: 0 :: 0xF :: 0 :: boosters :: false :: HNil =>
+ val burn : Boolean = boosters == 0
+ Attempt.successful(DroppodData(basic, burn, health))
+ },
+ {
+ case DroppodData(basic, burn, health) =>
+ val boosters : Int = if(burn) { 0 } else { 9 }
+ Attempt.successful(basic :: false :: health :: 0 :: 0xF :: 0 :: boosters :: false :: HNil)
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ImplantInterfaceData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ImplantInterfaceData.scala
deleted file mode 100644
index 411be6f6..00000000
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ImplantInterfaceData.scala
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2017 PSForever
-package net.psforever.packet.game.objectcreate
-
-import net.psforever.packet.Marshallable
-import scodec.{Attempt, Codec, Err}
-import scodec.codecs._
-import shapeless.{::, HNil}
-
-/**
- * A representation of an object that can be interacted with when using an implant terminal.
- * This object is generally invisible.
- */
-final case class ImplantInterfaceData() extends ConstructorData {
- override def bitsize : Long = 24L
-}
-
-object ImplantInterfaceData extends Marshallable[ImplantInterfaceData] {
- implicit val codec : Codec[ImplantInterfaceData] = (
- bool ::
- uint(23)
- ).exmap[ImplantInterfaceData] (
- {
- case true :: 0 :: HNil =>
- Attempt.successful(ImplantInterfaceData())
- case _ :: _ :: HNil =>
- Attempt.failure(Err("invalid interface data format"))
- },
- {
- case ImplantInterfaceData() =>
- Attempt.successful(true :: 0 :: HNil)
- }
- )
-}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/InternalSlot.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/InternalSlot.scala
index 4505e23c..012dacb7 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/InternalSlot.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/InternalSlot.scala
@@ -14,8 +14,8 @@ import shapeless.{::, HNil}
* This prior object will clarify the identity of the "parent" object that owns the given `parentSlot`.
* As the name implies, this should never have to be used in the representation of a non-child object.
*
- * Try to avoid exposing `InternalSlot` in the process of implementing object code.
- * (Provide overrode constructors where applicable.)
+ * Try to avoid exposing this class in the process of implementing common object code.
+ * Provide overrode constructors that mask the creation of `InternalSlot` where applicable.
* @param objectClass the code for the type of object being constructed
* @param guid the GUID this object will be assigned
* @param parentSlot a parent-defined slot identifier that explains where the child is to be attached to the parent
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryData.scala
index 9add3cde..ef4fa92e 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryData.scala
@@ -1,6 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
+import InventoryItem._
import net.psforever.packet.PacketHelpers
import scodec.Codec
import scodec.codecs._
@@ -16,15 +17,11 @@ import shapeless.{::, HNil}
* No values are allowed to be misplaced and no unexpected regions of data can be discovered.
* If there is even a minor failure, the remainder of the inventory will fail to translate.
*
- * Inventories are usually prefaced with a `bin1` value not accounted for here.
- * It can be treated as optional.
+ * Inventories are usually prefaced with a single bit value not accounted for here to switch them "on."
* @param contents the items in the inventory
- * @param unk1 na
- * @param unk2 na
+ * @see `InventoryItem`
*/
-final case class InventoryData(contents : List[InventoryItem] = List.empty,
- unk1 : Boolean = false,
- unk2 : Boolean = false) extends StreamBitSize {
+final case class InventoryData(contents : List[InventoryItem] = List.empty) extends StreamBitSize {
override def bitsize : Long = {
val base : Long = 10L //8u + 1u + 1u
var invSize : Long = 0L //length of all items in inventory
@@ -36,48 +33,34 @@ final case class InventoryData(contents : List[InventoryItem] = List.empty,
}
object InventoryData {
- private def inventoryCodec(itemCodec : Codec[InventoryItem]) : Codec[InventoryData] = (
+ /**
+ * The primary `Codec` that parses the common format for an inventory `List`.
+ * @param itemCodec a `Codec` that describes each of the contents of the list
+ * @return an `InventoryData` object, or a `BitVector`
+ */
+ def codec(itemCodec : Codec[InventoryItem]) : Codec[InventoryData] = (
uint8L >>:~ { len =>
- ("unk1" | bool) ::
- ("unk2" | bool) ::
+ uint2L ::
("contents" | PacketHelpers.listOfNSized(len, itemCodec))
}
).xmap[InventoryData] (
{
- case _ :: a :: b :: c :: HNil =>
- InventoryData(c, a, b)
+ case _ :: 0 :: c :: HNil =>
+ InventoryData(c)
},
{
- case InventoryData(c, a, b) =>
- c.size :: a :: b :: c :: HNil
+ case InventoryData(c) =>
+ c.size :: 0 :: c :: HNil
}
)
/**
* A `Codec` for `0x17` `ObjectCreateMessage` data.
*/
- val codec : Codec[InventoryData] = inventoryCodec(InventoryItem.codec).hlist.xmap[InventoryData] (
- {
- case inventory :: HNil =>
- inventory
- },
- {
- case InventoryData(a, b, c) =>
- InventoryData(a, b, c) :: HNil
- }
- )
+ val codec : Codec[InventoryData] = codec(InventoryItem.codec)
/**
* A `Codec` for `0x18` `ObjectCreateDetailedMessage` data.
*/
- val codec_detailed : Codec[InventoryData] = inventoryCodec(InventoryItem.codec_detailed).hlist.xmap[InventoryData] (
- {
- case inventory :: HNil =>
- inventory
- },
- {
- case InventoryData(a, b, c) =>
- InventoryData(a, b, c) :: HNil
- }
- )
+ val codec_detailed : Codec[InventoryData] = codec(InventoryItem.codec_detailed)
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryItem.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryItem.scala
index 083c78b1..4b6a0191 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryItem.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryItem.scala
@@ -3,42 +3,33 @@ package net.psforever.packet.game.objectcreate
import net.psforever.packet.game.PlanetSideGUID
import scodec.Codec
-import scodec.codecs._
/**
- * A representation of an item in an avatar's inventory.
- * Reliance on `InternalSlot` indicates that this item is applicable to the same implicit parent-child relationship.
- * (That is, its parent object will be clarified by the containing element, e.g., the inventory or its owner.)
- * Unwinding inventory items into individual standard `ObjectCreateMessage` packet data is entirely possible.
- *
- * This intermediary object is primarily intended to mask external use of `InternalSlot`, as specified by the class.
- * @param item the object in inventory
- * @see InternalSlot
+ * Mask the use of `InternalSlot` using a fake class called an `InventoryItem`.
*/
-final case class InventoryItem(item : InternalSlot
- ) extends StreamBitSize {
- override def bitsize : Long = item.bitsize
-}
-
object InventoryItem {
/**
- * An abbreviated constructor for creating an `InventoryItem` without interacting with `InternalSlot` directly.
- * @param objClass the code for the type of object (ammunition) being constructed
- * @param guid the globally unique id assigned to the ammunition
- * @param parentSlot the slot where the ammunition is to be installed in the weapon
- * @param obj the constructor data
- * @return an InventoryItem
+ * Constructor for creating an `InventoryItem`.
+ * @param guid the GUID this object will be assigned
+ * @param slot a parent-defined slot identifier that explains where the child is to be attached to the parent
+ * @param obj the data used as representation of the object to be constructed
+ * @return an `InventoryItem` object
*/
- def apply(objClass : Int, guid : PlanetSideGUID, parentSlot : Int, obj : ConstructorData) : InventoryItem =
- InventoryItem(InternalSlot(objClass, guid, parentSlot, obj))
+ def apply(objClass : Int, guid : PlanetSideGUID, slot : Int, obj : ConstructorData) : InventoryItem =
+ InternalSlot(objClass, guid, slot, obj)
+
+ /**
+ * Alias `InventoryItem` to `InternalSlot`.
+ */
+ type InventoryItem = InternalSlot
/**
* A `Codec` for `0x17` `ObjectCreateMessage` data.
*/
- val codec : Codec[InventoryItem] = ("item" | InternalSlot.codec).as[InventoryItem]
+ val codec : Codec[InventoryItem] = InternalSlot.codec
/**
* A `Codec` for `0x18` `ObjectCreateDetailedMessage` data.
*/
- val codec_detailed : Codec[InventoryItem] = ("item" | InternalSlot.codec_detailed).as[InventoryItem]
+ val codec_detailed : Codec[InventoryItem] = InternalSlot.codec_detailed
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/MountItem.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/MountItem.scala
new file mode 100644
index 00000000..0af87b30
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/MountItem.scala
@@ -0,0 +1,30 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.game.PlanetSideGUID
+import scodec.Codec
+
+/**
+ * Mask the use of `InternalSlot` using a fake class called a `MountItem`.
+ */
+object MountItem {
+ /**
+ * Constructor for creating a `MountItem`.
+ * @param guid the GUID this object will be assigned
+ * @param slot a parent-defined slot identifier that explains where the child is to be attached to the parent
+ * @param obj the data used as representation of the object to be constructed
+ * @return an `InventoryItem` object
+ */
+ def apply(objClass : Int, guid : PlanetSideGUID, slot : Int, obj : ConstructorData) : MountItem =
+ InternalSlot(objClass, guid, slot, obj)
+
+ /**
+ * Alias `MountItem` to `InternalSlot`.
+ */
+ type MountItem = InternalSlot
+
+ /**
+ * A `Codec` for `0x17` `ObjectCreateMessage` data.
+ */
+ val codec : Codec[MountItem] = InternalSlot.codec
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala
index a72d949f..030def3d 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala
@@ -7,15 +7,10 @@ import scodec.codecs._
import scala.annotation.switch
/**
- * A reference between all object class codes and the name of the object they represent.
- *
- * Object classes compose a number between 0 and 2047, always translating into an 11-bit value.
- * In `scodec` terms, that's a `uintL(11)` or a `uintL(0xB)`.
- * The items that can be constructed with packets `ObjectCreateMessage` and `ObjectCreateDetailedMessage` number fewer than 1047.
+ * A reference between all object class codes and the name of the object they represent.
+ * Object class types are defined by an 11-bit (`0xB`) value.
*/
object ObjectClass {
- //character
- final val avatar = 0x79 // 121
//ammunition
final val bullet_105mm = 0
final val bullet_12mm = 3
@@ -304,17 +299,47 @@ object ObjectClass {
final val oicw_projectile = 602
final val starfire_projectile = 831
final val striker_missile_targeting_projectile = 841
+ //vehicles
+ final val ams = 46
+ final val ams_destroyed = 47
+ final val ant = 60
+ final val ant_destroyed = 61
+ final val aurora = 118
+ final val battlewagon = 135 //raider
+ final val droppod = 258
+ final val fury = 335
+ final val lightning = 446
+ final val lightning_destroyed = 447
+ final val mediumtransport = 532
+ final val mediumtransport_destroyed = 533
+ final val orbital_shuttle = 608
+ final val quadassault = 707
+ final val quadassault_destroyed = 708
+ final val quadstealth = 710
+ final val quadstealth_destroyed = 711
+ final val switchblade = 847
+ final val switchblade_destroyed = 848
+ final val threemanheavybuggy = 862 //marauder
+ final val threemanheavybuggy_destroyed = 863
+ final val thunderer = 865
+ final val two_man_assault_buggy = 896 //harasser
+ final val two_man_assault_buggy_destroyed = 897
+ final val twomanheavybuggy = 898 //enforcer
+ final val twomanheavybuggy_destroyed = 899
+ final val twomanhoverbuggy = 900 //thresher
+ final val twomanhoverbuggy_destroyed = 901
//other
- final val locker_container = 456
+ final val ams_respawn_tube = 49
+ final val avatar = 121
+ final val capture_flag = 157
final val implant_terminal_interface = 409
+ final val locker_container = 456
final val matrix_terminala = 517
final val matrix_terminalb = 518
final val matrix_terminalc = 519
final val order_terminal = 612
final val order_terminala = 613
final val order_terminalb = 614
- final val ams_respawn_tube = 49
- final val capture_flag = 157
//TODO refactor the following functions into another object later
/**
@@ -501,7 +526,7 @@ object ObjectClass {
case ObjectClass.hunterseeker => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.ilc9 => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.isp => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
- case ObjectClass.katana => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.katana => ConstructorData.genericCodec(DetailedWeaponData.codec(2), "weapon")
case ObjectClass.lancer => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.lasher => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.liberator_25mm_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
@@ -515,9 +540,9 @@ object ObjectClass {
case ObjectClass.mediumtransport_weapon_systemB => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.mini_chaingun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.oicw => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
- case ObjectClass.nchev_falcon => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
- case ObjectClass.nchev_scattercannon => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
- case ObjectClass.nchev_sparrow => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.nchev_falcon => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
+ case ObjectClass.nchev_scattercannon => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
+ case ObjectClass.nchev_sparrow => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
case ObjectClass.particle_beam_magrider => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.pellet_gun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.peregrine_dual_machine_gun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
@@ -540,7 +565,7 @@ object ObjectClass {
case ObjectClass.prowler_weapon_systemB => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.pulsar => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.pulsed_particle_accelerator => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
- case ObjectClass.punisher => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.punisher => ConstructorData.genericCodec(DetailedWeaponData.codec(2), "weapon")
case ObjectClass.quadassault_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.r_shotgun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.radiator => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
@@ -559,14 +584,14 @@ object ObjectClass {
case ObjectClass.thumper => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.thunderer_weapon_systema => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.thunderer_weapon_systemb => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
- case ObjectClass.trhev_burster => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
- case ObjectClass.trhev_dualcycler => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
- case ObjectClass.trhev_pounder => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.trhev_burster => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
+ case ObjectClass.trhev_dualcycler => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
+ case ObjectClass.trhev_pounder => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
case ObjectClass.vanguard_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.vanu_sentry_turret_weapon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
- case ObjectClass.vshev_comet => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
- case ObjectClass.vshev_starfire => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
- case ObjectClass.vshev_quasar => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.vshev_comet => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
+ case ObjectClass.vshev_starfire => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
+ case ObjectClass.vshev_quasar => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
case ObjectClass.vulture_bomb_bay => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.vulture_nose_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.vulture_tail_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
@@ -740,8 +765,8 @@ object ObjectClass {
case ObjectClass.aphelion_starfire => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.aphelion_starfire_left => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.aphelion_starfire_right => ConstructorData.genericCodec(WeaponData.codec, "weapon")
- case ObjectClass.aurora_weapon_systema => ConstructorData.genericCodec(WeaponData.codec, "weapon")
- case ObjectClass.aurora_weapon_systemb => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.aurora_weapon_systema => ConstructorData.genericCodec(WeaponData.codec(2), "weapon")
+ case ObjectClass.aurora_weapon_systemb => ConstructorData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.battlewagon_weapon_systema => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.battlewagon_weapon_systemb => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.battlewagon_weapon_systemc => ConstructorData.genericCodec(WeaponData.codec, "weapon")
@@ -789,22 +814,22 @@ object ObjectClass {
case ObjectClass.hunterseeker => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.ilc9 => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.isp => ConstructorData.genericCodec(WeaponData.codec, "weapon")
- case ObjectClass.katana => ConstructorData.genericCodec(ConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.katana => ConstructorData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.lancer => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.lasher => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.liberator_25mm_cannon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.liberator_bomb_bay => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.liberator_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.lightgunship_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
- case ObjectClass.lightning_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.lightning_weapon_system => ConstructorData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.maelstrom => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.magcutter => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.mediumtransport_weapon_systemA => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.mediumtransport_weapon_systemB => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.mini_chaingun => ConstructorData.genericCodec(WeaponData.codec, "weapon")
- case ObjectClass.nchev_falcon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
- case ObjectClass.nchev_scattercannon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
- case ObjectClass.nchev_sparrow => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.nchev_falcon => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
+ case ObjectClass.nchev_scattercannon => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
+ case ObjectClass.nchev_sparrow => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
case ObjectClass.oicw => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.particle_beam_magrider => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.pellet_gun => ConstructorData.genericCodec(WeaponData.codec, "weapon")
@@ -828,14 +853,14 @@ object ObjectClass {
case ObjectClass.prowler_weapon_systemB => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.pulsar => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.pulsed_particle_accelerator => ConstructorData.genericCodec(WeaponData.codec, "weapon")
- case ObjectClass.punisher => ConstructorData.genericCodec(ConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.punisher => ConstructorData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.quadassault_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.r_shotgun => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.radiator => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.repeater => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.rocklet => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.rotarychaingun_mosquito => ConstructorData.genericCodec(WeaponData.codec, "weapon")
- case ObjectClass.scythe => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.scythe => ConstructorData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.six_shooter => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.skyguard_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.spiker => ConstructorData.genericCodec(WeaponData.codec, "weapon")
@@ -846,14 +871,14 @@ object ObjectClass {
case ObjectClass.thumper => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.thunderer_weapon_systema => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.thunderer_weapon_systemb => ConstructorData.genericCodec(WeaponData.codec, "weapon")
- case ObjectClass.trhev_burster => ConstructorData.genericCodec(WeaponData.codec, "weapon")
- case ObjectClass.trhev_dualcycler => ConstructorData.genericCodec(WeaponData.codec, "weapon")
- case ObjectClass.trhev_pounder => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.trhev_burster => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
+ case ObjectClass.trhev_dualcycler => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
+ case ObjectClass.trhev_pounder => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
case ObjectClass.vanguard_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.vanu_sentry_turret_weapon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
- case ObjectClass.vshev_comet => ConstructorData.genericCodec(WeaponData.codec, "weapon")
- case ObjectClass.vshev_quasar => ConstructorData.genericCodec(WeaponData.codec, "weapon")
- case ObjectClass.vshev_starfire => ConstructorData.genericCodec(WeaponData.codec, "weapon")
+ case ObjectClass.vshev_comet => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
+ case ObjectClass.vshev_quasar => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
+ case ObjectClass.vshev_starfire => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
case ObjectClass.vulture_bomb_bay => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.vulture_nose_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.vulture_tail_cannon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
@@ -879,8 +904,17 @@ object ObjectClass {
case ObjectClass.ace => ConstructorData.genericCodec(ACEData.codec, "ace")
case ObjectClass.advanced_ace => ConstructorData.genericCodec(CommandDetonaterData.codec, "advanced ace")
case ObjectClass.boomer_trigger => ConstructorData.genericCodec(BoomerTriggerData.codec, "boomer trigger")
+ //vehicles?
+ case ObjectClass.orbital_shuttle => ConstructorData.genericCodec(OrbitalShuttleData.codec, "HART")
//other
- case ObjectClass.implant_terminal_interface => ConstructorData.genericCodec(ImplantInterfaceData.codec, "implant terminal")
+ case ObjectClass.ams_respawn_tube => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
+ case ObjectClass.implant_terminal_interface => ConstructorData.genericCodec(CommonTerminalData.codec, "implant terminal")
+ case ObjectClass.matrix_terminala => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
+ case ObjectClass.matrix_terminalb => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
+ case ObjectClass.matrix_terminalc => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
+ case ObjectClass.order_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
+ case ObjectClass.order_terminala => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
+ case ObjectClass.order_terminalb => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
//failure case
case _ => defaultFailureCodec(objClass)
}
@@ -1034,7 +1068,7 @@ object ObjectClass {
case ObjectClass.hunterseeker => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.ilc9 => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.isp => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
- case ObjectClass.katana => DroppedItemData.genericCodec(ConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.katana => DroppedItemData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.lancer => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.lasher => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.maelstrom => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
@@ -1055,7 +1089,7 @@ object ObjectClass {
case ObjectClass.peregrine_sparrow_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.phoenix => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.pulsar => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
- case ObjectClass.punisher => DroppedItemData.genericCodec(ConcurrentFeedWeaponData.codec, "weapon")
+ case ObjectClass.punisher => DroppedItemData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.r_shotgun => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.radiator => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.repeater => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
@@ -1112,28 +1146,58 @@ object ObjectClass {
case ObjectClass.oicw_projectile => ConstructorData.genericCodec(TrackedProjectileData.codec, "projectile")
case ObjectClass.starfire_projectile => ConstructorData.genericCodec(TrackedProjectileData.codec, "projectile")
case ObjectClass.striker_missile_targeting_projectile => ConstructorData.genericCodec(TrackedProjectileData.codec, "projectile")
+ //vehicles
+ case ObjectClass.ams => ConstructorData.genericCodec(AMSData.codec, "ams")
+ case ObjectClass.ams_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
+ case ObjectClass.ant => ConstructorData.genericCodec(ANTData.codec, "ant")
+ case ObjectClass.ant_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
+ case ObjectClass.aurora => ConstructorData.genericCodec(VehicleData.codec(2)(), "vehicle")
+ case ObjectClass.battlewagon => ConstructorData.genericCodec(VehicleData.codec(4)(), "vehicle")
+ case ObjectClass.droppod => ConstructorData.genericCodec(DroppodData.codec, "droppod")
+ case ObjectClass.fury => ConstructorData.genericCodec(VehicleData.codec, "vehicle")
+ case ObjectClass.lightning => ConstructorData.genericCodec(VehicleData.codec, "vehicle")
+ case ObjectClass.lightning_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
+ case ObjectClass.mediumtransport => ConstructorData.genericCodec(VehicleData.codec(2)(), "vehicle")
+ case ObjectClass.mediumtransport_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
+ case ObjectClass.orbital_shuttle => ConstructorData.genericCodec(OrbitalShuttleData.codec_pos, "HART")
+ case ObjectClass.quadassault => ConstructorData.genericCodec(VehicleData.codec, "vehicle")
+ case ObjectClass.quadassault_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
+ case ObjectClass.quadstealth => ConstructorData.genericCodec(VehicleData.codec(0)(), "vehicle")
+ case ObjectClass.quadstealth_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
+ case ObjectClass.switchblade => ConstructorData.genericCodec(Vehicle2Data.codec, "vehicle")
+ case ObjectClass.switchblade_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
+ case ObjectClass.threemanheavybuggy => ConstructorData.genericCodec(VehicleData.codec(2)(), "vehicle")
+ case ObjectClass.threemanheavybuggy_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
+ case ObjectClass.thunderer => ConstructorData.genericCodec(VehicleData.codec(2)(), "vehicle")
+ case ObjectClass.two_man_assault_buggy => ConstructorData.genericCodec(VehicleData.codec, "vehicle")
+ case ObjectClass.two_man_assault_buggy_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
+ case ObjectClass.twomanheavybuggy => ConstructorData.genericCodec(VehicleData.codec, "vehicle")
+ case ObjectClass.twomanheavybuggy_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
+ case ObjectClass.twomanhoverbuggy => ConstructorData.genericCodec(VehicleData.codec, "vehicle")
+ case ObjectClass.twomanhoverbuggy_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
//other
+ case ObjectClass.ams_respawn_tube => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.avatar => ConstructorData.genericCodec(CharacterData.codec, "avatar")
- case ObjectClass.locker_container => ConstructorData.genericCodec(LockerContainerData.codec, "locker container")
- case ObjectClass.matrix_terminala => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") //todo confirm
- case ObjectClass.matrix_terminalb => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") //todo confirm
- case ObjectClass.matrix_terminalc => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
- case ObjectClass.order_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") //todo confirm
- case ObjectClass.order_terminala => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
- case ObjectClass.order_terminalb => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
- case ObjectClass.ams_respawn_tube => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.capture_flag => ConstructorData.genericCodec(CaptureFlagData.codec, "capture flag")
+ case ObjectClass.implant_terminal_interface => DroppedItemData.genericCodec(CommonTerminalData.codec, "implant terminal")
+ case ObjectClass.locker_container => ConstructorData.genericCodec(LockerContainerData.codec, "locker container")
+ case ObjectClass.matrix_terminala => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
+ case ObjectClass.matrix_terminalb => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
+ case ObjectClass.matrix_terminalc => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
+ case ObjectClass.order_terminal => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
+ case ObjectClass.order_terminala => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
+ case ObjectClass.order_terminalb => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
//failure case
case _ => defaultFailureCodec(objClass)
}
/**
* `Codec` for handling a failure case upon not finding an appropriate object `Codec`.
- * @param cls the object class whose `Codec` we have failed to find
+ * @param cls the object class whose `Codec` we have failed to find;
* @return a failure
*/
private def defaultFailureCodec(cls : Int) : Codec[ConstructorData.genericPattern] = {
- conditional(false, bool).exmap[ConstructorData.genericPattern] (
+ conditional(cls == 0, bool).exmap[ConstructorData.genericPattern] (
{
case None | _ =>
Attempt.failure(Err(s"decoding unknown object class $cls"))
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectCreateBase.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectCreateBase.scala
index dd9369bc..b9c15dfe 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectCreateBase.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectCreateBase.scala
@@ -3,9 +3,8 @@ package net.psforever.packet.game.objectcreate
import net.psforever.packet.PacketHelpers
import net.psforever.packet.game.PlanetSideGUID
-import scodec.{Attempt, Codec, DecodeResult, Err}
+import scodec.{Attempt, Codec, Err}
import scodec.bits.BitVector
-import scodec.codecs.{bool, either, uintL}
import shapeless.{::, HNil}
import scodec.codecs._
@@ -40,6 +39,8 @@ final case class ObjectCreateMessageParent(guid : PlanetSideGUID,
slot : Int)
object ObjectCreateBase {
+ private[this] val log = org.log4s.getLogger("ObjectCreate")
+
private type basePattern = Long :: Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: BitVector :: HNil
private type parentPattern = Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: HNil
@@ -86,13 +87,17 @@ object ObjectCreateBase {
def decodeData(objectClass : Int, data : BitVector, getCodecFunc : (Int) => Codec[ConstructorData.genericPattern]) : Option[ConstructorData] = {
var out : Option[ConstructorData] = None
try {
- val outOpt : Option[DecodeResult[_]] = getCodecFunc(objectClass).decode(data).toOption
- if(outOpt.isDefined)
- out = outOpt.get.value.asInstanceOf[ConstructorData.genericPattern]
+ getCodecFunc(objectClass).decode(data) match {
+ case Attempt.Successful(decode) =>
+ out = decode.value.asInstanceOf[ConstructorData.genericPattern]
+ case Attempt.Failure(err) =>
+ log.error(s"an object $objectClass failed to decode - ${err.toString}")
+ log.debug(s"object type: $objectClass, input: ${data.toString}, problem: ${err.toString}")
+ }
}
catch {
- case _ : Exception =>
- //catch and release, any sort of parse error
+ case ex : Exception =>
+ log.error(s"${ex.getClass.toString} - ${ex.toString}")
}
out
}
@@ -101,22 +106,26 @@ object ObjectCreateBase {
* Take the important information of a game piece and transform it into bit data.
* This function is fail-safe because it catches errors involving bad parsing of the object data.
* Generally, the `Exception` messages themselves are not useful here.
- * @param objClass the code for the type of object being deconstructed
+ * @param objectClass the code for the type of object being deconstructed
* @param obj the object data
* @param getCodecFunc a lookup function that returns a `Codec` for this object class
* @return the bitstream data
* @see `ObjectClass`
*/
- def encodeData(objClass : Int, obj : ConstructorData, getCodecFunc : (Int) => Codec[ConstructorData.genericPattern]) : BitVector = {
+ def encodeData(objectClass : Int, obj : ConstructorData, getCodecFunc : (Int) => Codec[ConstructorData.genericPattern]) : BitVector = {
var out = BitVector.empty
try {
- val outOpt : Option[BitVector] = getCodecFunc(objClass).encode(Some(obj.asInstanceOf[ConstructorData])).toOption
- if(outOpt.isDefined)
- out = outOpt.get
+ getCodecFunc(objectClass).encode(Some(obj.asInstanceOf[ConstructorData])) match {
+ case Attempt.Successful(encode) =>
+ out = encode
+ case Attempt.Failure(err) =>
+ log.error(s"an $objectClass object failed to encode - ${err.toString}")
+ log.debug(s"object type: $objectClass, input: ${obj.toString}, problem: ${err.toString}")
+ }
}
catch {
- case _ : Exception =>
- //catch and release, any sort of parse error
+ case ex : Exception =>
+ log.error(s"${ex.getClass.toString} - ${ex.toString}")
}
out
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/OneMannedFieldTurretData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/OneMannedFieldTurretData.scala
index 565c21d2..5708fd88 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/OneMannedFieldTurretData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/OneMannedFieldTurretData.scala
@@ -18,18 +18,16 @@ import shapeless.{::, HNil}
* If the turret has no internal weapon, it is safest rendered as destroyed.
* Trying to fire a turret with no internal weapon will soft-lock the PlanetSide client.
* @param deploy data common to objects spawned by the (advanced) adaptive construction engine
- * @param player_guid the player who owns this object
* @param health the amount of health the object has, as a percentage of a filled bar
* @param internals data regarding the mountable weapon
*/
-final case class OneMannedFieldTurretData(deploy : ACEDeployableData,
- player_guid : PlanetSideGUID, //might be able to re-package into field above
+final case class OneMannedFieldTurretData(deploy : CommonFieldData,
health : Int,
internals : Option[InternalSlot] = None
) extends ConstructorData {
override def bitsize : Long = {
val deploySize = deploy.bitsize
- val internalSize = if(internals.isDefined) { ACEDeployableData.internalWeapon_bitsize + internals.get.bitsize } else { 0L }
+ val internalSize = if(internals.isDefined) { CommonFieldData.internalWeapon_bitsize + internals.get.bitsize } else { 0L }
38L + deploySize + internalSize //16u + 8u + 8u + 2u + 4u
}
}
@@ -38,13 +36,12 @@ object OneMannedFieldTurretData extends Marshallable[OneMannedFieldTurretData] {
/**
* Overloaded constructor that mandates information about the internal weapon of the field turret.
* @param deploy data common to objects spawned by the (advanced) adaptive construction engine
- * @param player_guid the player who owns this object
* @param health the amount of health the object has, as a percentage of a filled bar
* @param internals data regarding the mountable weapon
* @return a `OneMannedFieldTurretData` object
*/
- def apply(deploy : ACEDeployableData, player_guid : PlanetSideGUID, health : Int, internals : InternalSlot) : OneMannedFieldTurretData =
- new OneMannedFieldTurretData(deploy, player_guid, health, Some(internals))
+ def apply(deploy : CommonFieldData, health : Int, internals : InternalSlot) : OneMannedFieldTurretData =
+ new OneMannedFieldTurretData(deploy, health, Some(internals))
/**
* Prefabricated weapon data for a weaponless field turret mount (`portable_manned_turret`).
@@ -125,15 +122,15 @@ object OneMannedFieldTurretData extends Marshallable[OneMannedFieldTurretData] {
)
implicit val codec : Codec[OneMannedFieldTurretData] = (
- ("deploy" | ACEDeployableData.codec) ::
+ ("deploy" | CommonFieldData.codec) ::
bool ::
- ("player_guid" | PlanetSideGUID.codec) ::
+ PlanetSideGUID.codec :: //hoist/extract with the CommonFieldData above
bool ::
("health" | uint8L) ::
uint2L ::
uint8L ::
bool ::
- optional(bool, "internals" | ACEDeployableData.internalWeaponCodec)
+ optional(bool, "internals" | CommonFieldData.internalWeaponCodec)
).exmap[OneMannedFieldTurretData] (
{
case deploy :: false :: player :: false :: health :: 0 :: 0x1E :: false :: internals :: HNil =>
@@ -143,20 +140,22 @@ object OneMannedFieldTurretData extends Marshallable[OneMannedFieldTurretData] {
newHealth = 0
newInternals = None
}
- Attempt.successful(OneMannedFieldTurretData(deploy, player, newHealth, newInternals))
+ val newDeploy = CommonFieldData(deploy.pos, deploy.faction, deploy.unk, player)
+ Attempt.successful(OneMannedFieldTurretData(newDeploy, newHealth, newInternals))
case _ =>
Attempt.failure(Err("invalid omft data format"))
},
{
- case OneMannedFieldTurretData(deploy, player, health, internals) =>
+ case OneMannedFieldTurretData(deploy, health, internals) =>
var newHealth : Int = health
var newInternals : Option[InternalSlot] = internals
if(health == 0 || internals.isEmpty) {
newHealth = 0
newInternals = None
}
- Attempt.successful(deploy :: false :: player :: false :: newHealth :: 0 :: 0x1E :: false :: newInternals :: HNil)
+ val newDeploy = CommonFieldData(deploy.pos, deploy.faction, deploy.unk)
+ Attempt.successful(newDeploy :: false :: deploy.player_guid :: false :: newHealth :: 0 :: 0x1E :: false :: newInternals :: HNil)
case _ =>
Attempt.failure(Err("invalid omft data format"))
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/OrbitalShuttleData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/OrbitalShuttleData.scala
new file mode 100644
index 00000000..017a8055
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/OrbitalShuttleData.scala
@@ -0,0 +1,107 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import net.psforever.types.PlanetSideEmpire
+import scodec.codecs._
+import scodec.{Attempt, Codec, Err}
+import shapeless.{::, HNil}
+
+/**
+ * A representation of the high altitude rapid transport (HART) shuttle that ferries the player into battle.
+ * This `Codec` is different depending on whether the shuttle is the child of a parent or independent.
+ *
+ * Three HART shuttles dock with the three HART buildings in the three sanctuaries for short periods on a timed schedule.
+ * When one is landed, players may board the shuttle using designated hallways in the lobbies of the HART building.
+ * After the shuttle leaves the sanctuary, it transports those players up into orbit above the continents.
+ * The shuttle docks again, this time with space stations that orbit the planet.
+ * It allows infantry to use droppods to land on the continents by pinpointing locations on that continent's tactical map.
+ *
+ * The previous explanation is smoke and mirrors nonsense.
+ * Lore-wise, the separation of Auraxis during the Bending rendered it impossible for the shuttle to visit all of the continents.
+ * The orbital stations - even if they multiplied one per planet - don't really exist.
+ * (They almost existed but all assets for them were cut from the game.)
+ * The HART shuttle also isn't a traditional vehicle.
+ * It isn't even tangible.
+ * The game just treats it like a vehicle for the purpose of allowing players to access the controllable droppod system.
+ *
+ * When accessible to the player, the shuttle has an access point called a "trunk."
+ * Trying to access it yields the brief message "OSMustBeDockedToMount."
+ * @param faction empire the object is affiliated with
+ * @param pos optional;
+ * where and how the object is oriented
+ * @see `DroppodLaunchRequestMessage`
+ * @see `DroppodLaunchResponseMessage`
+ * @see `OrbitalShuttleTimeMsg`
+ */
+final case class OrbitalShuttleData(faction : PlanetSideEmpire.Value,
+ pos : Option[PlacementData] = None) extends ConstructorData {
+ override def bitsize : Long = if(pos.isDefined) {
+ 54L + pos.get.bitsize
+ }
+ else {
+ 46L
+ }
+}
+
+object OrbitalShuttleData extends Marshallable[OrbitalShuttleData] {
+ /**
+ * Overloaded constructor that requires defining a position.
+ * The fields are arranged in the standard order for most vehicles (position data first).
+ * @param pos where and how the object is oriented
+ * @param faction empire the object is affiliated with
+ * @return an `OrbitalShuttleData` object
+ */
+ def apply(pos : PlacementData, faction : PlanetSideEmpire.Value) : OrbitalShuttleData =
+ OrbitalShuttleData(faction, Some(pos))
+
+ implicit val codec : Codec[OrbitalShuttleData] = (
+ ("faction" | PlanetSideEmpire.codec) ::
+ uintL(25) ::
+ uint8L :: //255
+ uintL(5) ::
+ uint4L :: //7
+ uint2L
+ ).exmap[OrbitalShuttleData] (
+ {
+ case faction :: 0 :: 255 :: 0 :: 7 :: 0 :: HNil =>
+ Attempt.successful(OrbitalShuttleData(faction))
+ },
+ {
+ case OrbitalShuttleData(faction, _) =>
+ Attempt.successful(faction :: 0 :: 255 :: 0 :: 7 :: 0 :: HNil)
+ }
+ )
+ /**
+ * Used when the shuttle is not attached to something else.
+ */
+ val codec_pos : Codec[OrbitalShuttleData] = (
+ ("pos" | PlacementData.codec) ::
+ ("faction" | PlanetSideEmpire.codec) ::
+ uintL(22) ::
+ uint8L :: //255
+ uintL(3) ::
+ uint8L :: //255
+ uintL(6) ::
+ uint4L :: //15
+ bool
+ ).exmap[OrbitalShuttleData] (
+ {
+ case pos :: faction :: 0 :: 255 :: 0 :: 255 :: 0 :: 15 :: false :: HNil =>
+ Attempt.successful(OrbitalShuttleData(faction, Some(pos)))
+
+ case _ =>
+ Attempt.failure(Err("invalid shuttle data format"))
+ },
+ {
+ case OrbitalShuttleData(faction, Some(pos)) =>
+ Attempt.successful(pos :: faction :: 0 :: 255 :: 0 :: 255 :: 0 :: 15 :: false :: HNil)
+
+ case OrbitalShuttleData(_, None) =>
+ Attempt.failure(Err("invalid shuttle data format (needs position)"))
+
+ case _ =>
+ Attempt.failure(Err("invalid shuttle data format"))
+ }
+ )
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/PlacementData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/PlacementData.scala
index 1c707256..51df840a 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/PlacementData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/PlacementData.scala
@@ -12,16 +12,16 @@ import scodec.Codec
* @param roll the amount of roll that affects orientation
* @param pitch the amount of pitch that affects orientation
* @param yaw the amount of yaw that affects orientation
- * @param init_move optional movement data that occurs upon placement
+ * @param vel optional movement data (that occurs upon placement)
*/
final case class PlacementData(coord : Vector3,
roll : Int,
pitch : Int,
yaw : Int,
- init_move : Option[Vector3] = None
+ vel : Option[Vector3] = None
) extends StreamBitSize {
override def bitsize : Long = {
- val moveLength = if(init_move.isDefined) { 42 } else { 0 }
+ val moveLength = if(vel.isDefined) { 42 } else { 0 }
81L + moveLength
}
}
@@ -58,17 +58,17 @@ object PlacementData extends Marshallable[PlacementData] {
* @param roll the amount of roll that affects orientation
* @param pitch the amount of pitch that affects orientation
* @param yaw the amount of yaw that affects orientation
- * @param init_move optional movement data that occurs upon placement
+ * @param vel optional movement data that occurs upon placement
* @return a `PlacementData` object
*/
- def apply(x : Float, y : Float, z : Float, roll : Int, pitch : Int, yaw : Int, init_move : Vector3) : PlacementData =
- new PlacementData(Vector3(x, y, z), roll, pitch, yaw, Some(init_move))
+ def apply(x : Float, y : Float, z : Float, roll : Int, pitch : Int, yaw : Int, vel : Vector3) : PlacementData =
+ new PlacementData(Vector3(x, y, z), roll, pitch, yaw, Some(vel))
implicit val codec : Codec[PlacementData] = (
("coord" | Vector3.codec_pos) ::
("roll" | uint8L) ::
("pitch" | uint8L) ::
("yaw" | uint8L) ::
- optional(bool, "init_move" | Vector3.codec_vel)
+ optional(bool, "vel" | Vector3.codec_vel)
).as[PlacementData]
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala
new file mode 100644
index 00000000..d0d4c669
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala
@@ -0,0 +1,153 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+/**
+ * A compilation of the common `*Data` objects that would be used for stock game objects.
+ * Each function is named after the `ObjectClass` name (internal name) it creates.
+ * No `Prefab` assumes empire allegiance or initial health.
+ */
+object Prefab {
+ import net.psforever.packet.game.PlanetSideGUID
+ import net.psforever.types.PlanetSideEmpire
+
+ object Vehicle {
+ def ams(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, driveState : DriveState.Value, matrix_guid : PlanetSideGUID, respawn_guid : PlanetSideGUID, term_a_guid : PlanetSideGUID, term_b_guid : PlanetSideGUID) : AMSData = {
+ AMSData(CommonFieldData(loc, faction, 0), health, driveState, matrix_guid, respawn_guid, term_a_guid, term_b_guid)
+ }
+
+ def ant(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, driveState : DriveState.Value) : ANTData = {
+ ANTData(CommonFieldData(loc, faction, 0), health, driveState)
+ }
+
+ def aurora(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo11_guid : PlanetSideGUID, ammo12_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo21_guid : PlanetSideGUID, ammo22_guid : PlanetSideGUID) : VehicleData = {
+ VehicleData(CommonFieldData(loc, faction, 0), 0, health, 0, DriveState.State7, true, 0,
+ Some(
+ MountItem(ObjectClass.aurora_weapon_systema, weapon1_guid, 5,
+ WeaponData(0x6, 0x8, 0, ObjectClass.fluxpod_ammo, ammo11_guid, 0, AmmoBoxData(0x8), ObjectClass.fluxpod_ammo, ammo12_guid, 1, AmmoBoxData(0x8))
+ ) ::
+ MountItem(ObjectClass.aurora_weapon_systemb, weapon2_guid, 6,
+ WeaponData(0x6, 0x8, 0, ObjectClass.fluxpod_ammo, ammo21_guid, 0, AmmoBoxData(0x8), ObjectClass.fluxpod_ammo, ammo22_guid, 1, AmmoBoxData(0x8))
+ ) :: Nil
+ )
+ )(2)
+ }
+
+ def battlewagon(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon4_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID) : VehicleData = {
+ VehicleData(CommonFieldData(loc, faction, 0), 0, health, 0, DriveState.State7, true, 0,
+ Some(
+ MountItem(ObjectClass.battlewagon_weapon_systema, weapon1_guid, 5,
+ WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo1_guid, 0, AmmoBoxData(0x8))
+ ) ::
+ MountItem(ObjectClass.battlewagon_weapon_systemb, weapon2_guid, 6,
+ WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo2_guid, 0, AmmoBoxData(0x8))
+ ) ::
+ MountItem(ObjectClass.battlewagon_weapon_systemc, weapon3_guid, 7,
+ WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo3_guid, 0, AmmoBoxData(0x8))
+ ) ::
+ MountItem(ObjectClass.battlewagon_weapon_systemd, weapon4_guid, 8,
+ WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo4_guid, 0, AmmoBoxData(0x8))
+ ) :: Nil
+ )
+ )(4)
+ }
+
+ def fury(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = {
+ VehicleData(CommonFieldData(loc, faction, 0), health,
+ MountItem(ObjectClass.fury_weapon_systema, weapon_guid, 1,
+ WeaponData(0x4, 0x8, ObjectClass.hellfire_ammo, ammo_guid, 0, AmmoBoxData(0x8))
+ )
+ )
+ }
+
+ def lightning(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = {
+ VehicleData(CommonFieldData(loc, faction, 0), health,
+ MountItem(ObjectClass.lightning_weapon_system, weapon_guid, 1,
+ WeaponData(0x4, 0x8, 0, ObjectClass.bullet_75mm, ammo1_guid, 0, AmmoBoxData(0x0), ObjectClass.bullet_25mm, ammo2_guid, 1, AmmoBoxData(0x0))
+ )
+ )
+ }
+
+ def mediumtransport(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID): VehicleData = {
+ VehicleData(CommonFieldData(loc, faction, 0), 0, health, 0, DriveState.State7, true, 0,
+ Some(
+ MountItem(ObjectClass.mediumtransport_weapon_systemA, weapon1_guid, 5,
+ WeaponData(0x6, 0x8, ObjectClass.bullet_20mm, ammo1_guid, 0, AmmoBoxData(0x8))
+ ) ::
+ MountItem(ObjectClass.mediumtransport_weapon_systemB, weapon2_guid, 6,
+ WeaponData(0x6, 0x8, ObjectClass.bullet_20mm, ammo2_guid, 0, AmmoBoxData(0x8))
+ ) :: Nil
+ )
+ )(2)
+ }
+
+ def quadassault(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = {
+ VehicleData(CommonFieldData(loc, faction, 0), health,
+ MountItem(ObjectClass.quadassault_weapon_system, weapon_guid, 1,
+ WeaponData(0x6, 0x8, ObjectClass.bullet_12mm, ammo_guid, 0, AmmoBoxData(0x8))
+ )
+ )
+ }
+
+ def quadstealth(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int) : VehicleData = {
+ VehicleData(CommonFieldData(loc, faction, 0), 0, health, 0, DriveState.State7, false, 0)(0)
+ }
+
+ def switchblade(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, driveState : DriveState.Value, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : Vehicle2Data = {
+ Vehicle2Data(CommonFieldData(loc, faction, 0), health, driveState,
+ MountItem(ObjectClass.scythe, weapon_guid, 1,
+ WeaponData(0x6, 0x8, 0, ObjectClass.ancient_ammo_vehicle, ammo1_guid, 0, AmmoBoxData(0x8), ObjectClass.ancient_ammo_vehicle, ammo2_guid, 1, AmmoBoxData(0x8))
+ )
+ )
+ }
+
+ def threemanheavybuggy(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = {
+ VehicleData(CommonFieldData(loc, faction, 0), 0, health, 0, DriveState.State7, true, 0,
+ Some(
+ MountItem(ObjectClass.chaingun_p, weapon1_guid, 3,
+ WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo1_guid, 0, AmmoBoxData(0x8))
+ ) ::
+ MountItem(ObjectClass.grenade_launcher_marauder, weapon2_guid, 4,
+ WeaponData(0x6, 0x8, 0, ObjectClass.heavy_grenade_mortar, ammo2_guid, 0, AmmoBoxData(0x8))
+ ) :: Nil
+ )
+ )(2)
+ }
+
+ def thunderer(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = {
+ VehicleData(CommonFieldData(loc, faction, 0), 0, health, 0, DriveState.State7, true, 0,
+ Some(
+ MountItem(ObjectClass.thunderer_weapon_systema, weapon1_guid, 5,
+ WeaponData(0x6, 0x8, 0, ObjectClass.gauss_cannon_ammo, ammo1_guid, 0, AmmoBoxData(0x8))
+ ) ::
+ MountItem(ObjectClass.thunderer_weapon_systemb, weapon2_guid, 6,
+ WeaponData(0x6, 0x8, 0, ObjectClass.gauss_cannon_ammo, ammo2_guid, 0, AmmoBoxData(0x8))
+ ) :: Nil
+ )
+ )(2)
+ }
+
+ def two_man_assault_buggy(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = {
+ VehicleData(CommonFieldData(loc, faction, 0), health,
+ MountItem(ObjectClass.chaingun_p, weapon_guid, 2,
+ WeaponData(0x6, 0x8, ObjectClass.bullet_12mm, ammo_guid, 0, AmmoBoxData(0x8))
+ )
+ )
+ }
+
+ def twomanheavybuggy(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = {
+ VehicleData(CommonFieldData(loc, faction, 0), health,
+ MountItem(ObjectClass.advanced_missile_launcher_t, weapon_guid, 2,
+ WeaponData(0x6, 0x8, 0, ObjectClass.firebird_missile, ammo_guid, 0, AmmoBoxData(0x8))
+ )
+ )
+ }
+
+ def twomanhoverbuggy(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = {
+ VehicleData(CommonFieldData(loc, faction, 0), health,
+ MountItem(ObjectClass.flux_cannon_thresher, weapon_guid, 2,
+ WeaponData(0x6, 0x8, 0, ObjectClass.flux_cannon_thresher_battery, ammo_guid, 0, AmmoBoxData(0x8))
+ )
+ )
+ }
+ }
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/SmallDeployableData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/SmallDeployableData.scala
index 5bf06920..14432434 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/SmallDeployableData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/SmallDeployableData.scala
@@ -10,13 +10,13 @@ import shapeless.{::, HNil}
* A representation of simple objects that are spawned by the adaptive construction engine.
* @param deploy data common to objects spawned by the (advanced) adaptive construction engine
*/
-final case class SmallDeployableData(deploy : ACEDeployableData) extends ConstructorData {
+final case class SmallDeployableData(deploy : CommonFieldData) extends ConstructorData {
override def bitsize : Long = deploy.bitsize + 1L
}
object SmallDeployableData extends Marshallable[SmallDeployableData] {
implicit val codec : Codec[SmallDeployableData] = (
- ("deploy" | ACEDeployableData.codec) ::
+ ("deploy" | CommonFieldData.codec) ::
bool
).exmap[SmallDeployableData] (
{
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/SmallTurretData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/SmallTurretData.scala
index 5ab5406b..4edd4800 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/SmallTurretData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/SmallTurretData.scala
@@ -20,13 +20,13 @@ import shapeless.{::, HNil}
* @param health the amount of health the object has, as a percentage of a filled bar
* @param internals data regarding the mounted weapon
*/
-final case class SmallTurretData(deploy : ACEDeployableData,
+final case class SmallTurretData(deploy : CommonFieldData,
health : Int,
internals : Option[InternalSlot] = None
) extends ConstructorData {
override def bitsize : Long = {
val deploySize = deploy.bitsize
- val internalSize = if(internals.isDefined) { ACEDeployableData.internalWeapon_bitsize + internals.get.bitsize } else { 0L }
+ val internalSize = if(internals.isDefined) { CommonFieldData.internalWeapon_bitsize + internals.get.bitsize } else { 0L }
23L + deploySize + internalSize //1u + 8u + 7u + 4u + 2u + 1u
}
}
@@ -39,7 +39,7 @@ object SmallTurretData extends Marshallable[SmallTurretData] {
* @param internals data regarding the mounted weapon
* @return a `SmallTurretData` object
*/
- def apply(deploy : ACEDeployableData, health : Int, internals : InternalSlot) : SmallTurretData =
+ def apply(deploy : CommonFieldData, health : Int, internals : InternalSlot) : SmallTurretData =
new SmallTurretData(deploy, health, Some(internals))
/**
@@ -81,13 +81,13 @@ object SmallTurretData extends Marshallable[SmallTurretData] {
)
implicit val codec : Codec[SmallTurretData] = (
- ("deploy" | ACEDeployableData.codec) ::
+ ("deploy" | CommonFieldData.codec) ::
bool ::
("health" | uint8L) ::
uintL(7) ::
uint4L ::
uint2L ::
- optional(bool, "internals" | ACEDeployableData.internalWeaponCodec)
+ optional(bool, "internals" | CommonFieldData.internalWeaponCodec)
).exmap[SmallTurretData] (
{
case deploy :: false :: health :: 0 :: 0xF :: 0 :: internals :: HNil =>
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/TRAPData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/TRAPData.scala
index 72f0882d..3c5fcba7 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/TRAPData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/TRAPData.scala
@@ -12,7 +12,7 @@ import shapeless.{::, HNil}
* @param deploy data common to objects spawned by the (advanced) adaptive construction engine
* @param health the amount of health the object has, as a percentage of a filled bar
*/
-final case class TRAPData(deploy : ACEDeployableData,
+final case class TRAPData(deploy : CommonFieldData,
health : Int
) extends ConstructorData {
override def bitsize : Long = {
@@ -22,7 +22,7 @@ final case class TRAPData(deploy : ACEDeployableData,
object TRAPData extends Marshallable[TRAPData] {
implicit val codec : Codec[TRAPData] = (
- ("deploy" | ACEDeployableData.codec) ::
+ ("deploy" | CommonFieldData.codec) ::
bool ::
("health" | uint8L) ::
uint(7) ::
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/Vehicle2Data.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/Vehicle2Data.scala
new file mode 100644
index 00000000..c338cc02
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/Vehicle2Data.scala
@@ -0,0 +1,129 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import net.psforever.packet.game.objectcreate.MountItem.MountItem
+import scodec.codecs._
+import scodec.{Attempt, Codec, Err}
+import shapeless.{::, HNil}
+
+/**
+ * A representation of a generic vehicle, with optional mounted weapons.
+ * This data will help construct vehicular options such as the Switchblade and the Mosquito.
+ * @param basic data common to objects
+ * @param unk1 na
+ * @param health the amount of health the vehicle has, as a percentage of a filled bar
+ * @param unk2 na
+ * @param driveState the drivable condition
+ * @param unk4 na
+ * @param unk5 na
+ * @param mountings data regarding the mounted utilities, usually weapons
+ * @param mount_capacity implicit;
+ * the total number of mounted utilities allowed on this vehicle;
+ * defaults to 1;
+ * -1 or less ignores the imposed checks
+ * @see `VehicleData`
+ */
+final case class Vehicle2Data(basic : CommonFieldData,
+ unk1 : Int,
+ health : Int,
+ unk2 : Int,
+ driveState : DriveState.Value,
+ unk4 : Boolean,
+ unk5 : Int,
+ unk6 : Int,
+ mountings : Option[List[MountItem]] = None
+ )(implicit val mount_capacity : Int = 1) extends ConstructorData {
+ override def bitsize : Long = {
+ val basicSize = basic.bitsize
+ val mountSize = if(mountings.isDefined) {
+ var bSize : Long = 0L
+ for(item <- mountings.get) {
+ bSize += item.bitsize
+ }
+ 10 + bSize
+ }
+ else {
+ 0L
+ }
+ 11L + VehicleData.baseVehicleSize + basicSize + mountSize
+ }
+}
+
+object Vehicle2Data extends Marshallable[Vehicle2Data] {
+ /**
+ * Overloaded constructor that mandates information about a single weapon mount.
+ * @param basic data common to objects
+ * @param health the amount of health the object has, as a percentage of a filled bar
+ * @param mount data regarding the mounted weapon
+ * @return a `Vehicle2Data` object
+ */
+ def apply(basic : CommonFieldData, health : Int, mount : MountItem) : Vehicle2Data =
+ Vehicle2Data(basic, 0, health, 0, DriveState.Mobile, false, 0, 0, Some(mount :: Nil))
+
+ /**
+ * Overloaded constructor that mandates information about a single weapon mount and deployment state.
+ * @param basic data common to objects
+ * @param health the amount of health the object has, as a percentage of a filled bar
+ * @param driveState the drivable condition
+ * @param mount data regarding the mounted weapon
+ * @return a `Vehicle2Data` object
+ */
+ def apply(basic : CommonFieldData, health : Int, driveState : DriveState.Value, mount : MountItem) : Vehicle2Data =
+ Vehicle2Data(basic, 0, health, 0, driveState, false, 0, 0, Some(mount :: Nil))
+
+ /**
+ * A `Codec` for `Vehicle2Data`.
+ * @param mount_capacity the total number of mounted weapons that are attached to this vehicle;
+ * defaults to 1
+ * @param mountCheck implicit;
+ * an evaluation of the provided `List` of objects;
+ * a function that takes an object and returns `true` if the object passed its defined test;
+ * defaults to `onlyWeapons`
+ * @return a `VehicleData` object or a `BitVector`
+ */
+ def codec(mount_capacity : Int = 1)(implicit mountCheck : (List[MountItem]) => Boolean = VehicleData.onlyWeapons) : Codec[Vehicle2Data] = (
+ VehicleData.basic_vehicle_codec :+
+ uint8L :+
+ uint2L :+
+ optional(bool, "mountings" | VehicleData.mountedUtilitiesCodec(mountCheck))
+ ).exmap[Vehicle2Data] (
+ {
+ case basic :: u1 :: health :: u2 :: driveState :: u4 :: u5 :: u6 :: mountings :: HNil =>
+ val onboardMountCount : Int = if(mountings.isDefined) { mountings.get.size } else { 0 }
+ if(mount_capacity > -1 && mount_capacity != onboardMountCount) {
+ Attempt.failure(Err(s"vehicle decodes wrong number of mounts - actual $onboardMountCount, expected $mount_capacity"))
+ }
+ else {
+ Attempt.successful(Vehicle2Data(basic, u1, health, u2, driveState, u4, u5, u6, mountings)(onboardMountCount))
+ }
+
+ case _ =>
+ Attempt.failure(Err("invalid vehicle data format"))
+ },
+ {
+ case obj @ Vehicle2Data(basic, u1, health, u2, driveState, u4, u5, u6, mountings) =>
+ val objMountCapacity = obj.mount_capacity
+ if(objMountCapacity < 0 || mount_capacity < 0) {
+ Attempt.successful(basic :: u1 :: health :: u2 :: driveState :: u4 :: u5 :: u6 :: mountings :: HNil)
+ }
+ else {
+ val onboardMountCount : Int = if(mountings.isDefined) { mountings.get.size } else { 0 }
+ if(mount_capacity != objMountCapacity) {
+ Attempt.failure(Err(s"different encoding expectations for amount of mounts - actual $objMountCapacity, expected $mount_capacity"))
+ }
+ else if(mount_capacity != onboardMountCount) {
+ Attempt.failure(Err(s"vehicle encodes wrong number of mounts - actual $onboardMountCount, expected $mount_capacity"))
+ }
+ else {
+ Attempt.successful(basic :: u1 :: health :: u2 :: driveState :: u4 :: u5 :: u6 :: mountings :: HNil)
+ }
+ }
+
+ case _ =>
+ Attempt.failure(Err("invalid vehicle data format"))
+ }
+ )
+
+ implicit val codec : Codec[Vehicle2Data] = codec()
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala
new file mode 100644
index 00000000..950e5e6f
--- /dev/null
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala
@@ -0,0 +1,208 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import net.psforever.packet.game.objectcreate.MountItem.MountItem
+import scodec.codecs._
+import scodec.{Attempt, Codec, Err}
+import shapeless.{::, HNil}
+
+/**
+ * A representation of a generic vehicle, with optional mounted weapons.
+ * This data will help construct most of the game's vehicular options such as the Lightning and the Harasser.
+ *
+ * Vehicles utilize their own packet to communicate position to the server, known as `VehicleStateMessage`.
+ * This takes the place of `PlayerStateMessageUpstream` when the player avatar is in control;
+ * and, it takes the place of `PlayerStateMessage` for other players when they are in control.
+ * If the vehicle is sufficiently complicated, a `ChildObjectStateMessage` will be used.
+ * This packet will control any turret(s) on the vehicle.
+ * For very complicated vehicles, the packets `FrameVehicleStateMessage` and `VehicleSubStateMessage` will also be employed.
+ * The tasks that these packets perform are different based on the vehicle that responds or generates them.
+ *
+ * Vehicles have a variety of features.
+ * They have their own inventory space, seating space for driver and passengers, Infantry mounting positions for the former two, and weapon mounting positions.
+ * Specialized vehicles also have terminals attached to them.
+ * The trunk is little different from player character inventories save for capacity and that it must be manually accessed.
+ * It is usually on the rear of the vehicle if that vehicle has a trunk at all.
+ * Weapons and infantry are allocated mounting slots from the same list.
+ * Weapons are constructed in their given slot with the vehicle itself and Infantry sit aside the weapons.
+ * Certain slots ("seats") allow control of one of the weapons in another slot ("weapon mounting").
+ * ("Seat" and "weapon mounting" do not coincide numerically.)
+ * For trunk and for Infantry slots, various glyphs are projected onto the ground, called "mounting positions."
+ * Standing nearly on top of the glyph and facing the vehicle allows access or seat-taking.
+ * ("Seat" and "mounting positions" will not necessarily coincide numerically either.)
+ *
+ * Outside of managing mounted weaponry, any vehicle with special "utilities" must be handled as a special case.
+ * Utilities are plastered onto the chassis and carried around with the vehicle.
+ * Some vehicles have to go through a sessile physical conversion known as "deploying" to get access to their utilities.
+ *
+ * An "expected" number of mounting data can be passed into the class for the purposes of validating input.
+ * @param basic data common to objects
+ * @param unk1 na
+ * @param health the amount of health the vehicle has, as a percentage of a filled bar
+ * @param unk2 na
+ * @param driveState the drivable condition
+ * @param unk4 na
+ * @param unk5 na;
+ * 1 causes the `quadstealth` (Wraith) to cloak
+ * @param mountings data regarding the mounted utilities, usually weapons
+ * @param mount_capacity implicit;
+ * the total number of mounted utilities allowed on this vehicle;
+ * defaults to 1;
+ * -1 or less ignores the imposed checks
+ * @see `Vehicle2Data`
+ */
+final case class VehicleData(basic : CommonFieldData,
+ unk1 : Int,
+ health : Int,
+ unk2 : Int,
+ driveState : DriveState.Value,
+ unk4 : Boolean,
+ unk5 : Int,
+ mountings : Option[List[MountItem]] = None
+ )(implicit val mount_capacity : Int = 1) extends ConstructorData {
+ override def bitsize : Long = {
+ val basicSize = basic.bitsize
+ val mountSize = if(mountings.isDefined) {
+ var bSize : Long = 0L
+ for(item <- mountings.get) {
+ bSize += item.bitsize
+ }
+ 10 + bSize
+ }
+ else {
+ 0L
+ }
+ 3L + VehicleData.baseVehicleSize + basicSize + mountSize
+ }
+}
+
+object VehicleData extends Marshallable[VehicleData] {
+ val baseVehicleSize : Long = 21L //2u + 8u + 2u + 8u + 1u
+
+ /**
+ * Overloaded constructor that mandates information about a single weapon mount.
+ * @param basic data common to objects
+ * @param health the amount of health the object has, as a percentage of a filled bar
+ * @param mount data regarding the mounted weapon
+ * @return a `VehicleData` object
+ */
+ def apply(basic : CommonFieldData, health : Int, mount : MountItem) : VehicleData =
+ VehicleData(basic, 0, health, 0, DriveState.Mobile, false, 0, Some(mount :: Nil))
+
+ /**
+ * Overloaded constructor that mandates information about a single weapon mount and deployment state.
+ * @param basic data common to objects
+ * @param health the amount of health the object has, as a percentage of a filled bar
+ * @param driveState the drivable condition
+ * @param mount data regarding the mounted weapon
+ * @return a `Vehicle2Data` object
+ */
+ def apply(basic : CommonFieldData, health : Int, driveState : DriveState.Value, mount : MountItem) : VehicleData =
+ VehicleData(basic, 0, health, 0, driveState, false, 0, Some(mount :: Nil))
+
+ /**
+ * A `Codec` for mounted utilities, generally weapons (as `WeaponData`).
+ * @param mountCheck a function that takes a `List` of `InternalSlot` objects and returns `true` if those objects passed its test
+ * @return a `List` of mounted objects or a `BitVector` of the same
+ * @see `InventoryData`
+ */
+ def mountedUtilitiesCodec(mountCheck : (List[MountItem]) => Boolean) : Codec[List[MountItem]] =
+ InventoryData.codec(MountItem.codec).exmap[List[MountItem]] (
+ {
+ case InventoryData(list) =>
+ if(!mountCheck(list)) {
+ Attempt.failure(Err("vehicle mount decoding is disallowed by test failure"))
+ }
+ else {
+ Attempt.successful(list)
+ }
+
+ case _ =>
+ Attempt.failure(Err("invalid mounting data format"))
+ },
+ {
+ case list =>
+ if(list.size > 255) {
+ Attempt.failure(Err("vehicle encodes too many weapon mountings (255+ objects!)"))
+ }
+ else if(!mountCheck(list)) {
+ Attempt.failure(Err("vehicle mount encoding is disallowed by test failure"))
+ }
+ else {
+ Attempt.successful(InventoryData(list))
+ }
+ }
+ )
+
+ /**
+ * These values are parsed by all vehicles.
+ * Comments about the fields are provided where helpful.
+ */
+ val basic_vehicle_codec : Codec[CommonFieldData :: Int :: Int :: Int :: DriveState.Value :: Boolean :: HNil] = (
+ CommonFieldData.codec :: //not certain if player_guid is valid
+ uint2L :: //often paired with the assumed 16u field in previous?
+ uint8L :: //usually "health"
+ uint2L :: //usually 0; second bit turns off vehicle seat entry points
+ DriveState.codec :: //special field (AMS and ANT use for deploy state)
+ bool //unknown but generally false; can cause stream misalignment if set when unexpected
+ ).as[CommonFieldData :: Int :: Int :: Int :: DriveState.Value :: Boolean :: HNil]
+
+ /**
+ * Perform an evaluation of the provided object.
+ * @param list a List of objects to be compared against some criteria
+ * @return `true`, if the objects pass this test; false, otherwise
+ */
+ def onlyWeapons(list : List[MountItem]) : Boolean = !list.exists(!_.obj.isInstanceOf[WeaponData])
+
+ /**
+ * A `Codec` for `VehicleData`.
+ * @param mount_capacity the total number of mounted weapons that are attached to this vehicle;
+ * defaults to 1
+ * @param mountCheck implicit;
+ * an evaluation of the provided `List` of objects;
+ * a function that takes an object and returns `true` if the object passed its defined test;
+ * defaults to `onlyWeapons`
+ * @return a `VehicleData` object or a `BitVector`
+ */
+ def codec(mount_capacity : Int = 1)(implicit mountCheck : (List[MountItem]) => Boolean = onlyWeapons) : Codec[VehicleData] = (
+ basic_vehicle_codec :+
+ uint2L :+
+ optional(bool, "mountings" | mountedUtilitiesCodec(mountCheck))
+ ).exmap[VehicleData] (
+ {
+ case basic :: u1 :: health :: u2 :: driveState :: u4 :: u5 :: mountings :: HNil =>
+ val onboardMountCount : Int = if(mountings.isDefined) { mountings.get.size } else { 0 }
+ if(mount_capacity > -1 && mount_capacity != onboardMountCount) {
+ Attempt.failure(Err(s"vehicle decodes wrong number of mounts - actual $onboardMountCount, expected $mount_capacity"))
+ }
+ else {
+ Attempt.successful(VehicleData(basic, u1, health, u2, driveState, u4, u5, mountings)(onboardMountCount))
+ }
+ },
+ {
+ case obj @ VehicleData(basic, u1, health, u2, driveState, u4, u5, mountings) =>
+ val objMountCapacity = obj.mount_capacity
+ if(objMountCapacity < 0 || mount_capacity < 0) {
+ Attempt.successful(basic :: u1 :: health :: u2 :: driveState :: u4 :: u5 :: mountings :: HNil)
+ }
+ else {
+ val onboardMountCount : Int = if(mountings.isDefined) { mountings.get.size } else { 0 }
+ if(mount_capacity != objMountCapacity) {
+ Attempt.failure(Err(s"different encoding expectations for amount of mounts - actual $objMountCapacity, expected $mount_capacity"))
+ }
+ else if(mount_capacity != onboardMountCount) {
+ Attempt.failure(Err(s"vehicle encodes wrong number of mounts - actual $onboardMountCount, expected $mount_capacity"))
+ }
+ else {
+ Attempt.successful(basic :: u1 :: health :: u2 :: driveState :: u4 :: u5 :: mountings :: HNil)
+ }
+ }
+
+ case _ =>
+ Attempt.failure(Err("invalid vehicle data format"))
+ }
+ )
+
+ implicit val codec : Codec[VehicleData] = codec()
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/WeaponData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/WeaponData.scala
index 0cecc2f2..1bd19eff 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/WeaponData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/WeaponData.scala
@@ -3,34 +3,50 @@ package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import net.psforever.packet.game.PlanetSideGUID
-import scodec.codecs._
import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
import shapeless.{::, HNil}
/**
* A representation of a class of weapons that can be created using `ObjectCreateMessage` packet data.
- * Common uses include items deposited on the ground and items in another player's visible inventory (holsters).
+ * This data will help construct a "loaded weapon" such as a Suppressor or a Gauss.
+ * Common uses include items deposited on the ground and items in another player's visible inventory (holsters).
+ *
+ * The data for the weapons nests information for the default (current) type of ammunition and number of ammunitions in its magazine(s).
+ * This ammunition data essentially is the weapon's magazines as numbered slots.
+ * An "expected" number of ammunition slot data can be passed into the class for the purposes of validating input.
* @param unk1 na;
* commonly 8
* @param unk2 na;
* commonly 12
* @param fire_mode the current mode of weapon's fire;
* zero-indexed
- * @param ammo data regarding the currently loaded ammunition type
- * @see `WeaponData`
+ * @param ammo data regarding the currently loaded ammunition type(s)
+ * @param mag_capacity implicit;
+ * the total number of concurrently-loaded ammunition types allowed in this weapon;
+ * concurrent ammunition does not need to be unloaded to be switched;
+ * defaults to 1;
+ * 0 is invalid;
+ * -1 or less ignores the imposed checks
* @see `AmmoBoxData`
*/
final case class WeaponData(unk1 : Int,
unk2 : Int,
fire_mode : Int,
- ammo : InternalSlot
- ) extends ConstructorData {
- override def bitsize : Long = 44L + ammo.bitsize
+ ammo : List[InternalSlot]
+ )(implicit val mag_capacity : Int = 1) extends ConstructorData {
+ override def bitsize : Long = {
+ var bitsize : Long = 0L
+ for(o <- ammo) {
+ bitsize += o.bitsize
+ }
+ 44L + bitsize
+ }
}
object WeaponData extends Marshallable[WeaponData] {
/**
- * An abbreviated constructor for creating `WeaponData` while masking use of `InternalSlot` for its `AmmoBoxData`.
+ * Overloaded constructor for creating `WeaponData` that mandates information about a single type of ammunition.
* @param unk1 na
* @param unk2 na
* @param cls the code for the type of object (ammunition) being constructed
@@ -40,10 +56,10 @@ object WeaponData extends Marshallable[WeaponData] {
* @return a `WeaponData` object
*/
def apply(unk1 : Int, unk2 : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : AmmoBoxData) : WeaponData =
- new WeaponData(unk1, unk2, 0, InternalSlot(cls, guid, parentSlot, ammo))
+ new WeaponData(unk1, unk2, 0, InternalSlot(cls, guid, parentSlot, ammo) :: Nil)
/**
- * An abbreviated constructor for creating `WeaponData` while masking use of `InternalSlot` for its `AmmoBoxData`.
+ * Overloaded constructor for creating `WeaponData` that mandates information about the firemode and a single type of ammunition.
* @param unk1 na
* @param unk2 na
* @param fire_mode data regarding the currently loaded ammunition type
@@ -54,29 +70,89 @@ object WeaponData extends Marshallable[WeaponData] {
* @return a `WeaponData` object
*/
def apply(unk1 : Int, unk2 : Int, fire_mode : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : AmmoBoxData) : WeaponData =
- new WeaponData(unk1, unk2, fire_mode, InternalSlot(cls, guid, parentSlot, ammo))
+ WeaponData(unk1, unk2, fire_mode, InternalSlot(cls, guid, parentSlot, ammo) :: Nil)
- implicit val codec : Codec[WeaponData] = (
- ("unk1" | uint4L) ::
- ("unk2" | uint4L) ::
+ /**
+ * Overloaded constructor for creating `WeaponData` with two types of ammunition concurrently loaded.
+ * This is a common weapon configuration, especially for vehicle-mounted weaponry.
+ * @param unk1 na
+ * @param unk2 na
+ * @param fire_mode data regarding the currently loaded ammunition type
+ * @param cls1 the code for the first type of object (ammunition) being constructed
+ * @param guid1 the globally unique id assigned to the first type of ammunition
+ * @param slot1 the slot where the first type of ammunition is to be installed in the weapon
+ * @param ammo1 the first ammunition object
+ * @param cls2 the code for the second type of object (ammunition) being constructed
+ * @param guid2 the globally unique id assigned to the second type of ammunition
+ * @param slot2 the slot where the second type of ammunition is to be installed in the weapon
+ * @param ammo2 the second ammunition object
+ * @return a `WeaponData` object
+ */
+ def apply(unk1 : Int, unk2 : Int, fire_mode : Int, cls1 : Int, guid1 : PlanetSideGUID, slot1 : Int, ammo1 : AmmoBoxData, cls2 : Int, guid2 : PlanetSideGUID, slot2 : Int, ammo2 : AmmoBoxData) : WeaponData =
+ WeaponData(unk1, unk2, fire_mode, InternalSlot(cls1, guid1, slot1, ammo1) :: InternalSlot(cls2, guid2, slot2, ammo2) :: Nil)(2)
+
+ /**
+ * A `Codec` for `WeaponData`.
+ * @param mag_capacity the total number of concurrently-loaded ammunition types allowed in this weapon;
+ * defaults to 1
+ * @return a `WeaponData` object or a `BitVector`
+ */
+ def codec(mag_capacity : Int = 1) : Codec[WeaponData] = (
+ ("unk1" | uintL(3)) ::
+ bool :: //weapon refuses to shoot if set (not weapons lock?)
+ ("unk2" | uint4L) :: //8 - common; 4 - jammers weapons; 2 - weapon breaks; 1, 0 - safe
uint(20) ::
("fire_mode" | int(3)) ::
bool ::
bool ::
- uint8L :: //size = 1 type of ammunition loaded
- uint2 ::
- ("ammo" | InternalSlot.codec) ::
+ ("ammo" | InventoryData.codec) ::
bool
).exmap[WeaponData] (
{
- case unk1 :: unk2 :: 0 :: fmode :: false :: true :: 1 :: 0 :: ammo :: false :: HNil =>
- Attempt.successful(WeaponData(unk1, unk2, fmode, ammo))
- case _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil =>
+ case unk1 :: false :: unk2 :: 0 :: fmode :: false :: true :: InventoryData(ammo) :: false :: HNil =>
+ val magSize = ammo.size
+ if(mag_capacity == 0 || magSize == 0) {
+ Attempt.failure(Err("weapon must decode some ammunition"))
+ }
+ else if(mag_capacity > 0 && magSize != mag_capacity) {
+ Attempt.failure(Err(s"weapon decodes too much or too little ammunition - actual $magSize, expected $mag_capacity"))
+ }
+ else {
+ Attempt.successful(WeaponData(unk1, unk2, fmode, ammo)(magSize))
+ }
+
+ case _ =>
Attempt.failure(Err("invalid weapon data format"))
},
{
- case WeaponData(unk1, unk2, fmode, ammo) =>
- Attempt.successful(unk1 :: unk2 :: 0 :: fmode :: false :: true :: 1 :: 0 :: ammo :: false :: HNil)
+ case obj @ WeaponData(unk1, unk2, fmode, ammo) =>
+ val magSize = ammo.size
+ val magCapacity = obj.mag_capacity
+ if(mag_capacity == 0 || magCapacity == 0 || magSize == 0) {
+ Attempt.failure(Err("weapon must encode some ammunition"))
+ }
+ else if(magSize >= 255) {
+ Attempt.failure(Err("weapon encodes too much ammunition (255+ types!)"))
+ }
+ else if(magCapacity < 0 || mag_capacity < 0) {
+ Attempt.successful(unk1 :: false :: unk2 :: 0 :: fmode :: false :: true :: InventoryData(ammo) :: false :: HNil)
+ }
+ else {
+ if(magCapacity != mag_capacity) {
+ Attempt.failure(Err(s"different encoding expectations for amount of ammunition - actual $magCapacity, expected $mag_capacity"))
+ }
+ else if(magSize != mag_capacity) {
+ Attempt.failure(Err(s"weapon encodes wrong amount of ammunition - actual $magSize, expected $mag_capacity"))
+ }
+ else {
+ Attempt.successful(unk1 :: false :: unk2 :: 0 :: fmode :: false :: true :: InventoryData(ammo) :: false :: HNil)
+ }
+ }
+
+ case _ =>
+ Attempt.failure(Err("invalid weapon data format"))
}
)
+
+ implicit val codec : Codec[WeaponData] = codec()
}
diff --git a/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala b/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala
index c27f7064..061db99f 100644
--- a/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala
+++ b/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala
@@ -96,12 +96,13 @@ class ObjectCreateDetailedMessageTest extends Specification {
parent.get.slot mustEqual 2
data.isDefined mustEqual true
val obj_wep = data.get.asInstanceOf[DetailedWeaponData]
- obj_wep.unk mustEqual 4
+ obj_wep.unk1 mustEqual 2
+ obj_wep.unk2 mustEqual 8
val obj_ammo = obj_wep.ammo
- obj_ammo.objectClass mustEqual 28
- obj_ammo.guid mustEqual PlanetSideGUID(1286)
- obj_ammo.parentSlot mustEqual 0
- obj_ammo.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 30
+ obj_ammo.head.objectClass mustEqual 28
+ obj_ammo.head.guid mustEqual PlanetSideGUID(1286)
+ obj_ammo.head.parentSlot mustEqual 0
+ obj_ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 30
case _ =>
ko
}
@@ -117,7 +118,7 @@ class ObjectCreateDetailedMessageTest extends Specification {
parent.get.guid mustEqual PlanetSideGUID(75)
parent.get.slot mustEqual 2
data.isDefined mustEqual true
- val obj_wep = data.get.asInstanceOf[DetailedConcurrentFeedWeaponData]
+ val obj_wep = data.get.asInstanceOf[DetailedWeaponData]
obj_wep.unk1 mustEqual 0
obj_wep.unk2 mustEqual 8
val obj_ammo = obj_wep.ammo
@@ -229,66 +230,66 @@ class ObjectCreateDetailedMessageTest extends Specification {
val inventory = char.inventory.get.contents
inventory.size mustEqual 10
//0
- inventory.head.item.objectClass mustEqual ObjectClass.beamer
- inventory.head.item.guid mustEqual PlanetSideGUID(76)
- inventory.head.item.parentSlot mustEqual 0
- var wep = inventory.head.item.obj.asInstanceOf[DetailedWeaponData]
- wep.ammo.objectClass mustEqual ObjectClass.energy_cell
- wep.ammo.guid mustEqual PlanetSideGUID(77)
- wep.ammo.parentSlot mustEqual 0
- wep.ammo.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 16
+ inventory.head.objectClass mustEqual ObjectClass.beamer
+ inventory.head.guid mustEqual PlanetSideGUID(76)
+ inventory.head.parentSlot mustEqual 0
+ var wep = inventory.head.obj.asInstanceOf[DetailedWeaponData]
+ wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell
+ wep.ammo.head.guid mustEqual PlanetSideGUID(77)
+ wep.ammo.head.parentSlot mustEqual 0
+ wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 16
//1
- inventory(1).item.objectClass mustEqual ObjectClass.suppressor
- inventory(1).item.guid mustEqual PlanetSideGUID(78)
- inventory(1).item.parentSlot mustEqual 2
- wep = inventory(1).item.obj.asInstanceOf[DetailedWeaponData]
- wep.ammo.objectClass mustEqual ObjectClass.bullet_9mm
- wep.ammo.guid mustEqual PlanetSideGUID(79)
- wep.ammo.parentSlot mustEqual 0
- wep.ammo.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 25
+ inventory(1).objectClass mustEqual ObjectClass.suppressor
+ inventory(1).guid mustEqual PlanetSideGUID(78)
+ inventory(1).parentSlot mustEqual 2
+ wep = inventory(1).obj.asInstanceOf[DetailedWeaponData]
+ wep.ammo.head.objectClass mustEqual ObjectClass.bullet_9mm
+ wep.ammo.head.guid mustEqual PlanetSideGUID(79)
+ wep.ammo.head.parentSlot mustEqual 0
+ wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 25
//2
- inventory(2).item.objectClass mustEqual ObjectClass.forceblade
- inventory(2).item.guid mustEqual PlanetSideGUID(80)
- inventory(2).item.parentSlot mustEqual 4
- wep = inventory(2).item.obj.asInstanceOf[DetailedWeaponData]
- wep.ammo.objectClass mustEqual ObjectClass.melee_ammo
- wep.ammo.guid mustEqual PlanetSideGUID(81)
- wep.ammo.parentSlot mustEqual 0
- wep.ammo.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1
+ inventory(2).objectClass mustEqual ObjectClass.forceblade
+ inventory(2).guid mustEqual PlanetSideGUID(80)
+ inventory(2).parentSlot mustEqual 4
+ wep = inventory(2).obj.asInstanceOf[DetailedWeaponData]
+ wep.ammo.head.objectClass mustEqual ObjectClass.melee_ammo
+ wep.ammo.head.guid mustEqual PlanetSideGUID(81)
+ wep.ammo.head.parentSlot mustEqual 0
+ wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1
//3
- inventory(3).item.objectClass mustEqual ObjectClass.locker_container
- inventory(3).item.guid mustEqual PlanetSideGUID(82)
- inventory(3).item.parentSlot mustEqual 5
- inventory(3).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1
+ inventory(3).objectClass mustEqual ObjectClass.locker_container
+ inventory(3).guid mustEqual PlanetSideGUID(82)
+ inventory(3).parentSlot mustEqual 5
+ inventory(3).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1
//4
- inventory(4).item.objectClass mustEqual ObjectClass.bullet_9mm
- inventory(4).item.guid mustEqual PlanetSideGUID(83)
- inventory(4).item.parentSlot mustEqual 6
- inventory(4).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
+ inventory(4).objectClass mustEqual ObjectClass.bullet_9mm
+ inventory(4).guid mustEqual PlanetSideGUID(83)
+ inventory(4).parentSlot mustEqual 6
+ inventory(4).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
//5
- inventory(5).item.objectClass mustEqual ObjectClass.bullet_9mm
- inventory(5).item.guid mustEqual PlanetSideGUID(84)
- inventory(5).item.parentSlot mustEqual 9
- inventory(5).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
+ inventory(5).objectClass mustEqual ObjectClass.bullet_9mm
+ inventory(5).guid mustEqual PlanetSideGUID(84)
+ inventory(5).parentSlot mustEqual 9
+ inventory(5).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
//6
- inventory(6).item.objectClass mustEqual ObjectClass.bullet_9mm
- inventory(6).item.guid mustEqual PlanetSideGUID(85)
- inventory(6).item.parentSlot mustEqual 12
- inventory(6).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
+ inventory(6).objectClass mustEqual ObjectClass.bullet_9mm
+ inventory(6).guid mustEqual PlanetSideGUID(85)
+ inventory(6).parentSlot mustEqual 12
+ inventory(6).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
//7
- inventory(7).item.objectClass mustEqual ObjectClass.bullet_9mm_AP
- inventory(7).item.guid mustEqual PlanetSideGUID(86)
- inventory(7).item.parentSlot mustEqual 33
- inventory(7).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
+ inventory(7).objectClass mustEqual ObjectClass.bullet_9mm_AP
+ inventory(7).guid mustEqual PlanetSideGUID(86)
+ inventory(7).parentSlot mustEqual 33
+ inventory(7).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
//8
- inventory(8).item.objectClass mustEqual ObjectClass.energy_cell
- inventory(8).item.guid mustEqual PlanetSideGUID(87)
- inventory(8).item.parentSlot mustEqual 36
- inventory(8).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
+ inventory(8).objectClass mustEqual ObjectClass.energy_cell
+ inventory(8).guid mustEqual PlanetSideGUID(87)
+ inventory(8).parentSlot mustEqual 36
+ inventory(8).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
//9
- inventory(9).item.objectClass mustEqual ObjectClass.remote_electronics_kit
- inventory(9).item.guid mustEqual PlanetSideGUID(88)
- inventory(9).item.parentSlot mustEqual 39
+ inventory(9).objectClass mustEqual ObjectClass.remote_electronics_kit
+ inventory(9).guid mustEqual PlanetSideGUID(88)
+ inventory(9).parentSlot mustEqual 39
//the rek has data but none worth testing here
char.drawn_slot mustEqual DrawnSlot.Pistol1
case _ =>
@@ -327,7 +328,7 @@ class ObjectCreateDetailedMessageTest extends Specification {
}
"encode (gauss)" in {
- val obj = DetailedWeaponData(4, ObjectClass.bullet_9mm, PlanetSideGUID(1286), 0, DetailedAmmoBoxData(8, 30))
+ val obj = DetailedWeaponData(2, 8, ObjectClass.bullet_9mm, PlanetSideGUID(1286), 0, DetailedAmmoBoxData(8, 30))
val msg = ObjectCreateDetailedMessage(ObjectClass.gauss, PlanetSideGUID(1465), ObjectCreateMessageParent(PlanetSideGUID(75), 2), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@@ -335,7 +336,11 @@ class ObjectCreateDetailedMessageTest extends Specification {
}
"encode (punisher)" in {
- val obj = DetailedConcurrentFeedWeaponData(0, 8, DetailedAmmoBoxData(ObjectClass.bullet_9mm, PlanetSideGUID(1693), 0, DetailedAmmoBoxData(8, 30)) :: DetailedAmmoBoxData(ObjectClass.jammer_cartridge, PlanetSideGUID(1564), 1, DetailedAmmoBoxData(8, 1)) :: Nil)
+ val obj = DetailedWeaponData(0, 8,
+ DetailedAmmoBoxData(ObjectClass.bullet_9mm, PlanetSideGUID(1693), 0, DetailedAmmoBoxData(8, 30)) ::
+ DetailedAmmoBoxData(ObjectClass.jammer_cartridge, PlanetSideGUID(1564), 1, DetailedAmmoBoxData(8, 1)) ::
+ Nil
+ )(2)
val msg = ObjectCreateDetailedMessage(ObjectClass.punisher, PlanetSideGUID(1703), ObjectCreateMessageParent(PlanetSideGUID(75), 2), obj)
var pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@@ -387,9 +392,9 @@ class ObjectCreateDetailedMessageTest extends Specification {
false,
RibbonBars()
)
- val inv = InventoryItem(ObjectClass.beamer, PlanetSideGUID(76), 0, DetailedWeaponData(8, ObjectClass.energy_cell, PlanetSideGUID(77), 0, DetailedAmmoBoxData(8, 16))) ::
- InventoryItem(ObjectClass.suppressor, PlanetSideGUID(78), 2, DetailedWeaponData(8, ObjectClass.bullet_9mm, PlanetSideGUID(79), 0, DetailedAmmoBoxData(8, 25))) ::
- InventoryItem(ObjectClass.forceblade, PlanetSideGUID(80), 4, DetailedWeaponData(8, ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1))) ::
+ val inv = InventoryItem(ObjectClass.beamer, PlanetSideGUID(76), 0, DetailedWeaponData(4, 8, ObjectClass.energy_cell, PlanetSideGUID(77), 0, DetailedAmmoBoxData(8, 16))) ::
+ InventoryItem(ObjectClass.suppressor, PlanetSideGUID(78), 2, DetailedWeaponData(4, 8, ObjectClass.bullet_9mm, PlanetSideGUID(79), 0, DetailedAmmoBoxData(8, 25))) ::
+ InventoryItem(ObjectClass.forceblade, PlanetSideGUID(80), 4, DetailedWeaponData(4, 8, ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1))) ::
InventoryItem(ObjectClass.locker_container, PlanetSideGUID(82), 5, DetailedAmmoBoxData(8, 1)) ::
InventoryItem(ObjectClass.bullet_9mm, PlanetSideGUID(83), 6, DetailedAmmoBoxData(8, 50)) ::
InventoryItem(ObjectClass.bullet_9mm, PlanetSideGUID(84), 9, DetailedAmmoBoxData(8, 50)) ::
diff --git a/common/src/test/scala/game/ObjectCreateMessageTest.scala b/common/src/test/scala/game/ObjectCreateMessageTest.scala
index 5b619687..e3cc4969 100644
--- a/common/src/test/scala/game/ObjectCreateMessageTest.scala
+++ b/common/src/test/scala/game/ObjectCreateMessageTest.scala
@@ -2,7 +2,7 @@
package game
import net.psforever.packet._
-import net.psforever.packet.game._
+import net.psforever.packet.game.{ObjectCreateMessage, _}
import net.psforever.packet.game.objectcreate._
import net.psforever.types._
import org.specs2.mutable._
@@ -35,7 +35,7 @@ class ObjectCreateMessageTest extends Specification {
val string_character = hex"17 73070000 BC8 3E0F 6C2D7 65535 CA16 00 00 09 9741E4F804000000 234530063007200610077006E00790052006F006E006E0069006500 220B7 E67B540404001000000000022B50100 268042006C00610063006B002000420065007200650074002000410072006D006F007500720065006400200043006F00720070007300 1700E0030050040003BC00000234040001A004000 3FFF67A8F A0A5424E0E800000000080952A9C3A03000001081103E040000000A023782F1080C0000016244108200000000808382403A030000014284C3A0C0000000202512F00B80C00000578F80F840000000280838B3C320300000080"
val string_character_backpack = hex"17 9C030000 BC8 340D F20A9 3956C AF0D 00 00 73 480000 87041006E00670065006C006C006F00 4A148 0000000000000000000000005C54200 24404F0072006900670069006E0061006C00200044006900730074007200690063007400 1740180181E8000000C202000042000000D202000000010A3C00"
- "deocde (striker projectile)" in {
+ "decode (striker projectile)" in {
PacketCoding.DecodePacket(string_striker_projectile).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 197
@@ -68,7 +68,8 @@ class ObjectCreateMessageTest extends Specification {
parent.get.guid mustEqual PlanetSideGUID(514)
parent.get.slot mustEqual 1
data.isDefined mustEqual true
- data.get.isInstanceOf[ImplantInterfaceData] mustEqual true
+ data.get.isInstanceOf[CommonTerminalData] mustEqual true
+ data.get.asInstanceOf[CommonTerminalData].faction mustEqual PlanetSideEmpire.VS
case _ =>
ko
}
@@ -82,14 +83,18 @@ class ObjectCreateMessageTest extends Specification {
guid mustEqual PlanetSideGUID(3827)
parent.isDefined mustEqual false
data.isDefined mustEqual true
- val term = data.get.asInstanceOf[CommonTerminalData]
- term.pos.coord.x mustEqual 4579.3438f
- term.pos.coord.y mustEqual 5615.0703f
- term.pos.coord.z mustEqual 72.953125f
- term.pos.pitch mustEqual 0
- term.pos.roll mustEqual 0
- term.pos.yaw mustEqual 125
- ok
+ data.get.isInstanceOf[DroppedItemData[_]] mustEqual true
+ val drop = data.get.asInstanceOf[DroppedItemData[_]]
+ drop.pos.coord.x mustEqual 4579.3438f
+ drop.pos.coord.y mustEqual 5615.0703f
+ drop.pos.coord.z mustEqual 72.953125f
+ drop.pos.pitch mustEqual 0
+ drop.pos.roll mustEqual 0
+ drop.pos.yaw mustEqual 125
+ drop.obj.isInstanceOf[CommonTerminalData] mustEqual true
+ val term = drop.obj.asInstanceOf[CommonTerminalData]
+ term.faction mustEqual PlanetSideEmpire.NC
+ term.unk mustEqual 0
case _ =>
ko
}
@@ -163,14 +168,14 @@ class ObjectCreateMessageTest extends Specification {
data.isDefined mustEqual true
data.get.isInstanceOf[WeaponData] mustEqual true
val wep = data.get.asInstanceOf[WeaponData]
- wep.unk1 mustEqual 8
+ wep.unk1 mustEqual 4
wep.unk2 mustEqual 8
wep.fire_mode mustEqual 0
- wep.ammo.objectClass mustEqual ObjectClass.energy_cell
- wep.ammo.guid mustEqual PlanetSideGUID(3548)
- wep.ammo.parentSlot mustEqual 0
- wep.ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
- val ammo = wep.ammo.obj.asInstanceOf[AmmoBoxData]
+ wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell
+ wep.ammo.head.guid mustEqual PlanetSideGUID(3548)
+ wep.ammo.head.parentSlot mustEqual 0
+ wep.ammo.head.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ val ammo = wep.ammo.head.obj.asInstanceOf[AmmoBoxData]
ammo.unk mustEqual 8
case _ =>
ko
@@ -187,9 +192,9 @@ class ObjectCreateMessageTest extends Specification {
parent.get.guid mustEqual PlanetSideGUID(3092)
parent.get.slot mustEqual 3
data.isDefined mustEqual true
- data.get.isInstanceOf[ConcurrentFeedWeaponData] mustEqual true
- val wep = data.get.asInstanceOf[ConcurrentFeedWeaponData]
- wep.unk1 mustEqual 8
+ data.get.isInstanceOf[WeaponData] mustEqual true
+ val wep = data.get.asInstanceOf[WeaponData]
+ wep.unk1 mustEqual 4
wep.unk2 mustEqual 8
wep.fire_mode mustEqual 0
val ammo = wep.ammo
@@ -345,14 +350,14 @@ class ObjectCreateMessageTest extends Specification {
drop.pos.yaw mustEqual 32
drop.obj.isInstanceOf[WeaponData] mustEqual true
val wep = drop.obj.asInstanceOf[WeaponData]
- wep.unk1 mustEqual 8
+ wep.unk1 mustEqual 4
wep.unk2 mustEqual 0
wep.fire_mode mustEqual 0
- wep.ammo.objectClass mustEqual ObjectClass.energy_cell
- wep.ammo.guid mustEqual PlanetSideGUID(3268)
- wep.ammo.parentSlot mustEqual 0
- wep.ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
- val ammo = wep.ammo.obj.asInstanceOf[AmmoBoxData]
+ wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell
+ wep.ammo.head.guid mustEqual PlanetSideGUID(3268)
+ wep.ammo.head.parentSlot mustEqual 0
+ wep.ammo.head.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ val ammo = wep.ammo.head.obj.asInstanceOf[AmmoBoxData]
ammo.unk mustEqual 0
case _ =>
ko
@@ -375,9 +380,9 @@ class ObjectCreateMessageTest extends Specification {
drop.pos.roll mustEqual 0
drop.pos.pitch mustEqual 0
drop.pos.yaw mustEqual 51
- drop.obj.isInstanceOf[ConcurrentFeedWeaponData] mustEqual true
- val wep = drop.obj.asInstanceOf[ConcurrentFeedWeaponData]
- wep.unk1 mustEqual 4
+ drop.obj.isInstanceOf[WeaponData] mustEqual true
+ val wep = drop.obj.asInstanceOf[WeaponData]
+ wep.unk1 mustEqual 2
wep.unk2 mustEqual 0
wep.fire_mode mustEqual 0
val ammo = wep.ammo
@@ -464,7 +469,8 @@ class ObjectCreateMessageTest extends Specification {
turret.deploy.pos.roll mustEqual 0
turret.deploy.pos.pitch mustEqual 127
turret.deploy.pos.yaw mustEqual 66
- turret.deploy.unk mustEqual 44
+ turret.deploy.faction mustEqual PlanetSideEmpire.NC
+ turret.deploy.unk mustEqual 12
turret.deploy.player_guid mustEqual PlanetSideGUID(3871)
turret.health mustEqual 0
turret.internals.isDefined mustEqual false
@@ -489,7 +495,8 @@ class ObjectCreateMessageTest extends Specification {
turret.deploy.pos.roll mustEqual 0
turret.deploy.pos.pitch mustEqual 0
turret.deploy.pos.yaw mustEqual 105
- turret.deploy.unk mustEqual 68
+ turret.deploy.faction mustEqual PlanetSideEmpire.VS
+ turret.deploy.unk mustEqual 4
turret.deploy.player_guid mustEqual PlanetSideGUID(4232)
turret.health mustEqual 255
turret.internals.isDefined mustEqual true
@@ -499,10 +506,10 @@ class ObjectCreateMessageTest extends Specification {
internals.parentSlot mustEqual 0
internals.obj.isInstanceOf[WeaponData] mustEqual true
val wep = internals.obj.asInstanceOf[WeaponData]
- wep.unk1 mustEqual 0xC
+ wep.unk1 mustEqual 0x6
wep.unk2 mustEqual 0x8
wep.fire_mode mustEqual 0
- val ammo = wep.ammo
+ val ammo = wep.ammo.head
ammo.objectClass mustEqual ObjectClass.spitfire_ammo
ammo.guid mustEqual PlanetSideGUID(3694)
ammo.parentSlot mustEqual 0
@@ -529,7 +536,8 @@ class ObjectCreateMessageTest extends Specification {
trap.deploy.pos.roll mustEqual 0
trap.deploy.pos.pitch mustEqual 0
trap.deploy.pos.yaw mustEqual 0
- trap.deploy.unk mustEqual 68
+ trap.deploy.faction mustEqual PlanetSideEmpire.VS
+ trap.deploy.unk mustEqual 4
trap.health mustEqual 255
trap.deploy.player_guid mustEqual PlanetSideGUID(2502)
case _ =>
@@ -553,7 +561,8 @@ class ObjectCreateMessageTest extends Specification {
aegis.deploy.pos.roll mustEqual 0
aegis.deploy.pos.pitch mustEqual 0
aegis.deploy.pos.yaw mustEqual 0
- aegis.deploy.unk mustEqual 68
+ aegis.deploy.faction mustEqual PlanetSideEmpire.VS
+ aegis.deploy.unk mustEqual 4
aegis.health mustEqual 255
aegis.deploy.player_guid mustEqual PlanetSideGUID(2366)
case _ =>
@@ -577,9 +586,9 @@ class ObjectCreateMessageTest extends Specification {
omft.deploy.pos.roll mustEqual 0
omft.deploy.pos.pitch mustEqual 0
omft.deploy.pos.yaw mustEqual 94
- omft.deploy.unk mustEqual 68
- omft.deploy.player_guid mustEqual PlanetSideGUID(0)
- omft.player_guid mustEqual PlanetSideGUID(2502)
+ omft.deploy.faction mustEqual PlanetSideEmpire.VS
+ omft.deploy.unk mustEqual 4
+ omft.deploy.player_guid mustEqual PlanetSideGUID(2502)
omft.health mustEqual 255
omft.internals.isDefined mustEqual true
val internals = omft.internals.get
@@ -588,10 +597,10 @@ class ObjectCreateMessageTest extends Specification {
internals.parentSlot mustEqual 1
internals.obj.isInstanceOf[WeaponData] mustEqual true
val wep = internals.obj.asInstanceOf[WeaponData]
- wep.unk1 mustEqual 0xC
+ wep.unk1 mustEqual 0x6
wep.unk2 mustEqual 0x8
wep.fire_mode mustEqual 0
- val ammo = wep.ammo
+ val ammo = wep.ammo.head
ammo.objectClass mustEqual ObjectClass.energy_gun_ammo
ammo.guid mustEqual PlanetSideGUID(2510)
ammo.parentSlot mustEqual 0
@@ -612,35 +621,33 @@ class ObjectCreateMessageTest extends Specification {
data.isDefined mustEqual true
data.get.isInstanceOf[LockerContainerData] mustEqual true
val locker = data.get.asInstanceOf[LockerContainerData]
- locker.inventory.unk1 mustEqual false
- locker.inventory.unk2 mustEqual false
val contents = locker.inventory.contents
contents.size mustEqual 3
//0
- contents.head.item.objectClass mustEqual ObjectClass.nano_dispenser
- contents.head.item.guid mustEqual PlanetSideGUID(2935)
- contents.head.item.parentSlot mustEqual 0
- contents.head.item.obj.isInstanceOf[WeaponData] mustEqual true
- val dispenser = contents.head.item.obj.asInstanceOf[WeaponData]
- dispenser.unk1 mustEqual 0xC
+ contents.head.objectClass mustEqual ObjectClass.nano_dispenser
+ contents.head.guid mustEqual PlanetSideGUID(2935)
+ contents.head.parentSlot mustEqual 0
+ contents.head.obj.isInstanceOf[WeaponData] mustEqual true
+ val dispenser = contents.head.obj.asInstanceOf[WeaponData]
+ dispenser.unk1 mustEqual 0x6
dispenser.unk2 mustEqual 0x0
- dispenser.ammo.objectClass mustEqual ObjectClass.armor_canister
- dispenser.ammo.guid mustEqual PlanetSideGUID(3426)
- dispenser.ammo.parentSlot mustEqual 0
- dispenser.ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
- dispenser.ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0
+ dispenser.ammo.head.objectClass mustEqual ObjectClass.armor_canister
+ dispenser.ammo.head.guid mustEqual PlanetSideGUID(3426)
+ dispenser.ammo.head.parentSlot mustEqual 0
+ dispenser.ammo.head.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ dispenser.ammo.head.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0
//1
- contents(1).item.objectClass mustEqual ObjectClass.armor_canister
- contents(1).item.guid mustEqual PlanetSideGUID(4090)
- contents(1).item.parentSlot mustEqual 45
- contents(1).item.obj.isInstanceOf[AmmoBoxData] mustEqual true
- contents(1).item.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0
+ contents(1).objectClass mustEqual ObjectClass.armor_canister
+ contents(1).guid mustEqual PlanetSideGUID(4090)
+ contents(1).parentSlot mustEqual 45
+ contents(1).obj.isInstanceOf[AmmoBoxData] mustEqual true
+ contents(1).obj.asInstanceOf[AmmoBoxData].unk mustEqual 0
//2
- contents(2).item.objectClass mustEqual ObjectClass.armor_canister
- contents(2).item.guid mustEqual PlanetSideGUID(3326)
- contents(2).item.parentSlot mustEqual 78
- contents(2).item.obj.isInstanceOf[AmmoBoxData] mustEqual true
- contents(2).item.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0
+ contents(2).objectClass mustEqual ObjectClass.armor_canister
+ contents(2).guid mustEqual PlanetSideGUID(3326)
+ contents(2).parentSlot mustEqual 78
+ contents(2).obj.isInstanceOf[AmmoBoxData] mustEqual true
+ contents(2).obj.asInstanceOf[AmmoBoxData].unk mustEqual 0
case _ =>
ko
}
@@ -662,10 +669,10 @@ class ObjectCreateMessageTest extends Specification {
pc.appearance.pos.roll mustEqual 0
pc.appearance.pos.pitch mustEqual 0
pc.appearance.pos.yaw mustEqual 9
- pc.appearance.pos.init_move.isDefined mustEqual true
- pc.appearance.pos.init_move.get.x mustEqual 1.4375f
- pc.appearance.pos.init_move.get.y mustEqual -0.4375f
- pc.appearance.pos.init_move.get.z mustEqual 0f
+ pc.appearance.pos.vel.isDefined mustEqual true
+ pc.appearance.pos.vel.get.x mustEqual 1.4375f
+ pc.appearance.pos.vel.get.y mustEqual -0.4375f
+ pc.appearance.pos.vel.get.z mustEqual 0f
pc.appearance.basic_appearance.name mustEqual "ScrawnyRonnie"
pc.appearance.basic_appearance.faction mustEqual PlanetSideEmpire.TR
pc.appearance.basic_appearance.sex mustEqual CharacterGender.Male
@@ -705,40 +712,40 @@ class ObjectCreateMessageTest extends Specification {
val contents = pc.inventory.get.contents
contents.size mustEqual 5
//0
- contents.head.item.objectClass mustEqual ObjectClass.plasma_grenade
- contents.head.item.guid mustEqual PlanetSideGUID(3662)
- contents.head.item.parentSlot mustEqual 0
- contents.head.item.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
- contents.head.item.obj.asInstanceOf[WeaponData].ammo.objectClass mustEqual ObjectClass.plasma_grenade_ammo
- contents.head.item.obj.asInstanceOf[WeaponData].ammo.guid mustEqual PlanetSideGUID(3751)
+ contents.head.objectClass mustEqual ObjectClass.plasma_grenade
+ contents.head.guid mustEqual PlanetSideGUID(3662)
+ contents.head.parentSlot mustEqual 0
+ contents.head.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
+ contents.head.obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.plasma_grenade_ammo
+ contents.head.obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3751)
//1
- contents(1).item.objectClass mustEqual ObjectClass.bank
- contents(1).item.guid mustEqual PlanetSideGUID(3908)
- contents(1).item.parentSlot mustEqual 1
- contents(1).item.obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
- contents(1).item.obj.asInstanceOf[WeaponData].ammo.objectClass mustEqual ObjectClass.armor_canister
- contents(1).item.obj.asInstanceOf[WeaponData].ammo.guid mustEqual PlanetSideGUID(4143)
+ contents(1).objectClass mustEqual ObjectClass.bank
+ contents(1).guid mustEqual PlanetSideGUID(3908)
+ contents(1).parentSlot mustEqual 1
+ contents(1).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
+ contents(1).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.armor_canister
+ contents(1).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(4143)
//2
- contents(2).item.objectClass mustEqual ObjectClass.mini_chaingun
- contents(2).item.guid mustEqual PlanetSideGUID(4164)
- contents(2).item.parentSlot mustEqual 2
- contents(2).item.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
- contents(2).item.obj.asInstanceOf[WeaponData].ammo.objectClass mustEqual ObjectClass.bullet_9mm
- contents(2).item.obj.asInstanceOf[WeaponData].ammo.guid mustEqual PlanetSideGUID(3728)
+ contents(2).objectClass mustEqual ObjectClass.mini_chaingun
+ contents(2).guid mustEqual PlanetSideGUID(4164)
+ contents(2).parentSlot mustEqual 2
+ contents(2).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
+ contents(2).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.bullet_9mm
+ contents(2).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3728)
//3
- contents(3).item.objectClass mustEqual ObjectClass.phoenix //actually, a decimator
- contents(3).item.guid mustEqual PlanetSideGUID(3603)
- contents(3).item.parentSlot mustEqual 3
- contents(3).item.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
- contents(3).item.obj.asInstanceOf[WeaponData].ammo.objectClass mustEqual ObjectClass.phoenix_missile
- contents(3).item.obj.asInstanceOf[WeaponData].ammo.guid mustEqual PlanetSideGUID(3056)
+ contents(3).objectClass mustEqual ObjectClass.phoenix //actually, a decimator
+ contents(3).guid mustEqual PlanetSideGUID(3603)
+ contents(3).parentSlot mustEqual 3
+ contents(3).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
+ contents(3).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.phoenix_missile
+ contents(3).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3056)
//4
- contents(4).item.objectClass mustEqual ObjectClass.chainblade
- contents(4).item.guid mustEqual PlanetSideGUID(4088)
- contents(4).item.parentSlot mustEqual 4
- contents(4).item.obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
- contents(4).item.obj.asInstanceOf[WeaponData].ammo.objectClass mustEqual ObjectClass.melee_ammo
- contents(4).item.obj.asInstanceOf[WeaponData].ammo.guid mustEqual PlanetSideGUID(3279)
+ contents(4).objectClass mustEqual ObjectClass.chainblade
+ contents(4).guid mustEqual PlanetSideGUID(4088)
+ contents(4).parentSlot mustEqual 4
+ contents(4).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
+ contents(4).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.melee_ammo
+ contents(4).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3279)
pc.drawn_slot mustEqual DrawnSlot.Rifle1
case _ =>
ko
@@ -761,7 +768,7 @@ class ObjectCreateMessageTest extends Specification {
pc.appearance.pos.roll mustEqual 0
pc.appearance.pos.pitch mustEqual 0
pc.appearance.pos.yaw mustEqual 115
- pc.appearance.pos.init_move.isDefined mustEqual false
+ pc.appearance.pos.vel.isDefined mustEqual false
pc.appearance.basic_appearance.name mustEqual "Angello"
pc.appearance.basic_appearance.faction mustEqual PlanetSideEmpire.VS
pc.appearance.basic_appearance.sex mustEqual CharacterGender.Male
@@ -814,7 +821,7 @@ class ObjectCreateMessageTest extends Specification {
}
"encode (implant interface)" in {
- val obj = ImplantInterfaceData()
+ val obj = CommonTerminalData(PlanetSideEmpire.VS)
val msg = ObjectCreateMessage(0x199, PlanetSideGUID(1075), ObjectCreateMessageParent(PlanetSideGUID(514), 1), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@@ -822,7 +829,10 @@ class ObjectCreateMessageTest extends Specification {
}
"encode (order terminal a)" in {
- val obj = CommonTerminalData(PlacementData(Vector3(4579.3438f, 5615.0703f, 72.953125f), 0, 0, 125))
+ val obj = DroppedItemData(
+ PlacementData(4579.3438f, 5615.0703f, 72.953125f, 0, 0, 125),
+ CommonTerminalData(PlanetSideEmpire.NC)
+ )
val msg = ObjectCreateMessage(ObjectClass.order_terminala, PlanetSideGUID(3827), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@@ -854,7 +864,7 @@ class ObjectCreateMessageTest extends Specification {
}
"encode (lasher, held)" in {
- val obj = WeaponData(8, 8, ObjectClass.energy_cell, PlanetSideGUID(3548), 0, AmmoBoxData(8))
+ val obj = WeaponData(4, 8, ObjectClass.energy_cell, PlanetSideGUID(3548), 0, AmmoBoxData(8))
val msg = ObjectCreateMessage(ObjectClass.lasher, PlanetSideGUID(3033), ObjectCreateMessageParent(PlanetSideGUID(4141), 3), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@@ -862,11 +872,12 @@ class ObjectCreateMessageTest extends Specification {
}
"encode (punisher, held)" in {
- val obj = ConcurrentFeedWeaponData(8, 8, 0,
- AmmoBoxData(ObjectClass.bullet_9mm, PlanetSideGUID(3918), 0, AmmoBoxData(8)) ::
- AmmoBoxData(ObjectClass.rocket, PlanetSideGUID(3941), 1, AmmoBoxData(8)) ::
- Nil
- )
+ val obj =
+ WeaponData(4, 8, 0,
+ AmmoBoxData(ObjectClass.bullet_9mm, PlanetSideGUID(3918), 0, AmmoBoxData(8)) ::
+ AmmoBoxData(ObjectClass.rocket, PlanetSideGUID(3941), 1, AmmoBoxData(8)) ::
+ Nil
+ )(2)
val msg = ObjectCreateMessage(ObjectClass.punisher, PlanetSideGUID(4147), ObjectCreateMessageParent(PlanetSideGUID(3092), 3), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@@ -925,7 +936,7 @@ class ObjectCreateMessageTest extends Specification {
"encode (lasher, dropped)" in {
val obj = DroppedItemData(
PlacementData(Vector3(4691.1953f, 5537.039f, 65.484375f), 0, 0, 32),
- WeaponData(8, 0, ObjectClass.energy_cell, PlanetSideGUID(3268), 0, AmmoBoxData())
+ WeaponData(4, 0, ObjectClass.energy_cell, PlanetSideGUID(3268), 0, AmmoBoxData())
)
val msg = ObjectCreateMessage(ObjectClass.lasher, PlanetSideGUID(3074), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@@ -936,11 +947,11 @@ class ObjectCreateMessageTest extends Specification {
"encode (punisher, dropped)" in {
val obj = DroppedItemData(
PlacementData(Vector3(4789.133f, 5522.3125f, 72.3125f), 0, 0, 51),
- ConcurrentFeedWeaponData(4, 0, 0,
+ WeaponData(2, 0, 0,
AmmoBoxData(ObjectClass.bullet_9mm, PlanetSideGUID(3528), 0, AmmoBoxData()) ::
AmmoBoxData(ObjectClass.rocket, PlanetSideGUID(3031), 1, AmmoBoxData()) ::
Nil
- )
+ )(2)
)
val msg = ObjectCreateMessage(ObjectClass.punisher, PlanetSideGUID(2978), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@@ -961,9 +972,9 @@ class ObjectCreateMessageTest extends Specification {
"encode (boomer)" in {
val obj = SmallDeployableData(
- ACEDeployableData(
+ CommonFieldData(
PlacementData(Vector3(4704.172f, 5546.4375f, 82.234375f), 0, 0, 63),
- 0, PlanetSideGUID(4145)
+ PlanetSideEmpire.TR, 0, PlanetSideGUID(4145)
)
)
val msg = ObjectCreateMessage(ObjectClass.boomer, PlanetSideGUID(3840), obj)
@@ -974,10 +985,9 @@ class ObjectCreateMessageTest extends Specification {
"encode (spitfire, short)" in {
val obj = SmallTurretData(
- ACEDeployableData(
- PlacementData(Vector3(4577.7812f, 5624.828f, 72.046875f), 0, 127, 66),
- 44,
- PlanetSideGUID(3871)
+ CommonFieldData(
+ PlacementData(Vector3(4577.7812f, 5624.828f, 72.046875f), 0, 127, 66),
+ PlanetSideEmpire.NC, 12, PlanetSideGUID(3871)
),
255 //sets to 0
)
@@ -993,13 +1003,12 @@ class ObjectCreateMessageTest extends Specification {
"encode (spitfire)" in {
val obj = SmallTurretData(
- ACEDeployableData(
+ CommonFieldData(
PlacementData(Vector3(4527.633f, 6271.3594f, 70.265625f), 0, 0, 105),
- 68,
- PlanetSideGUID(4232)
+ PlanetSideEmpire.VS, 4, PlanetSideGUID(4232)
),
255,
- SmallTurretData.spitfire(PlanetSideGUID(3064), 0xC, 0x8, PlanetSideGUID(3694), 8)
+ SmallTurretData.spitfire(PlanetSideGUID(3064), 0x6, 0x8, PlanetSideGUID(3694), 8)
)
val msg = ObjectCreateMessage(ObjectClass.spitfire_turret, PlanetSideGUID(4265), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@@ -1013,10 +1022,9 @@ class ObjectCreateMessageTest extends Specification {
"encode (trap)" in {
val obj = TRAPData(
- ACEDeployableData(
+ CommonFieldData(
PlacementData(Vector3(3572.4453f, 3277.9766f, 114.0f), 0, 0, 0),
- 68,
- PlanetSideGUID(2502)
+ PlanetSideEmpire.VS, 4, PlanetSideGUID(2502)
),
255
)
@@ -1032,10 +1040,9 @@ class ObjectCreateMessageTest extends Specification {
"encode (aegis)" in {
val obj = AegisShieldGeneratorData(
- ACEDeployableData(
+ CommonFieldData(
PlacementData(Vector3(3571.2266f, 3278.0938f, 114.0f), 0, 0, 0),
- 68,
- PlanetSideGUID(2366)
+ PlanetSideEmpire.VS, 4, PlanetSideGUID(2366)
),
255
)
@@ -1047,14 +1054,12 @@ class ObjectCreateMessageTest extends Specification {
"encode (orion)" in {
val obj = OneMannedFieldTurretData(
- ACEDeployableData(
+ CommonFieldData(
PlacementData(Vector3(3567.1406f, 2988.0078f, 71.84375f), 0, 0, 94),
- 68,
- PlanetSideGUID(0)
+ PlanetSideEmpire.VS, 4, PlanetSideGUID(2502)
),
- PlanetSideGUID(2502),
255,
- OneMannedFieldTurretData.orion(PlanetSideGUID(2615), 0xC, 0x8, PlanetSideGUID(2510), 8)
+ OneMannedFieldTurretData.orion(PlanetSideGUID(2615), 0x6, 0x8, PlanetSideGUID(2510), 8)
)
val msg = ObjectCreateMessage(ObjectClass.portable_manned_turret_vs, PlanetSideGUID(2916), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@@ -1069,7 +1074,7 @@ class ObjectCreateMessageTest extends Specification {
"encode (locker container)" in {
val obj = LockerContainerData(
InventoryData(
- InventoryItem(ObjectClass.nano_dispenser, PlanetSideGUID(2935), 0, WeaponData(0xC, 0x0, ObjectClass.armor_canister, PlanetSideGUID(3426), 0, AmmoBoxData())) ::
+ InventoryItem(ObjectClass.nano_dispenser, PlanetSideGUID(2935), 0, WeaponData(0x6, 0x0, ObjectClass.armor_canister, PlanetSideGUID(3426), 0, AmmoBoxData())) ::
InventoryItem(ObjectClass.armor_canister, PlanetSideGUID(4090), 45, AmmoBoxData()) ::
InventoryItem(ObjectClass.armor_canister, PlanetSideGUID(3326), 78, AmmoBoxData()) ::
Nil
diff --git a/common/src/test/scala/game/ObjectCreateMessageVehiclesTest.scala b/common/src/test/scala/game/ObjectCreateMessageVehiclesTest.scala
new file mode 100644
index 00000000..f51aa2d4
--- /dev/null
+++ b/common/src/test/scala/game/ObjectCreateMessageVehiclesTest.scala
@@ -0,0 +1,531 @@
+// Copyright (c) 2017 PSForever
+package game
+
+import net.psforever.packet._
+import net.psforever.packet.game.{ObjectCreateMessage, _}
+import net.psforever.packet.game.objectcreate.{DriveState, _}
+import net.psforever.types._
+import org.specs2.mutable._
+import scodec.bits._
+
+class ObjectCreateMessageVehiclesTest extends Specification {
+ val string_fury = hex"17 50010000 A79 9D01 FBC1C 12A83 2F06 00 00 21 4400003FC00101140C800C0E40000004048F3600301900000"
+ val string_ant = hex"17 C2000000 9E0 7C01 6C2D7 65535 CA16 00 00 00 4400003FC000000"
+ val string_lightning = hex"17 8b010000 df1 5a00 6c2d7 65535 ca16 00 00 00 4400003fc00101300ad8040c4000000408190b801018000002617402070000000"
+ val string_mediumtransport = hex"17 DA010000 8A2 8301 FBC1C 12A83 2F06 00 00 21 2400003FC079020593F80C2E400000040410148030190000017458050D90000001010401F814064000000"
+ val string_ams = hex"17 B8010000 970 3D10 002D765535CA16000000 402285BB0037E4100749E1D03000000620D83A0A00000195798741C00000332E40D84800000"
+ val string_ams_destroyed = hex"17 8D000000 978 3D10 002D765535CA16000000 0"
+ val string_switchblade = hex"17 93010000 A7B A201 FBC1C12A832F06000021 4400003FC00001013AD3180C0E4000000408330DC03019000006620406072000000"
+ val string_droppod = hex"17 C1000000 8110B0E00FA9000ACFFFF000000 4400007F83C0900"
+ val string_orbital_shuttle_1 = hex"17 82000000 0901B026904838000001FE0700"
+ val string_orbital_shuttle_2 = hex"17 C3000000 B02670402F5AA14F88C210000604000007F8FF03C0"
+
+ "decode (fury)" in {
+ PacketCoding.DecodePacket(string_fury).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 336
+ cls mustEqual ObjectClass.fury
+ guid mustEqual PlanetSideGUID(413)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[VehicleData] mustEqual true
+ val fury = data.get.asInstanceOf[VehicleData]
+ fury.basic.pos.coord.x mustEqual 6531.961f
+ fury.basic.pos.coord.y mustEqual 1872.1406f
+ fury.basic.pos.coord.z mustEqual 24.734375f
+ fury.basic.pos.roll mustEqual 0
+ fury.basic.pos.pitch mustEqual 0
+ fury.basic.pos.yaw mustEqual 33
+ fury.basic.pos.vel.isDefined mustEqual false
+ fury.basic.faction mustEqual PlanetSideEmpire.VS
+ fury.basic.unk mustEqual 4
+ fury.basic.player_guid mustEqual PlanetSideGUID(0)
+ fury.health mustEqual 255
+ //
+ fury.mountings.isDefined mustEqual true
+ fury.mountings.get.size mustEqual 1
+ val mounting = fury.mountings.get.head
+ mounting.objectClass mustEqual ObjectClass.fury_weapon_systema
+ mounting.guid mustEqual PlanetSideGUID(400)
+ mounting.parentSlot mustEqual 1
+ mounting.obj.isInstanceOf[WeaponData] mustEqual true
+ val weapon = mounting.obj.asInstanceOf[WeaponData]
+ weapon.unk1 mustEqual 0x6
+ weapon.unk2 mustEqual 0x8
+ weapon.fire_mode mustEqual 0
+ weapon.ammo.size mustEqual 1
+ val ammo = weapon.ammo.head
+ ammo.objectClass mustEqual ObjectClass.hellfire_ammo
+ ammo.guid mustEqual PlanetSideGUID(432)
+ ammo.parentSlot mustEqual 0
+ ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x8
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (ant)" in {
+ PacketCoding.DecodePacket(string_ant).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 194L
+ cls mustEqual ObjectClass.ant
+ guid mustEqual PlanetSideGUID(380)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[ANTData] mustEqual true
+ val ant = data.get.asInstanceOf[ANTData]
+ ant.basic.pos.coord.x mustEqual 3674.8438f
+ ant.basic.pos.coord.y mustEqual 2726.789f
+ ant.basic.pos.coord.z mustEqual 91.15625f
+ ant.basic.pos.roll mustEqual 0
+ ant.basic.pos.pitch mustEqual 0
+ ant.basic.pos.yaw mustEqual 0
+ ant.basic.faction mustEqual PlanetSideEmpire.VS
+ ant.basic.unk mustEqual 4
+ ant.basic.player_guid mustEqual PlanetSideGUID(0)
+ ant.health mustEqual 255
+ ant.driveState mustEqual DriveState.Mobile
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (lightning)" in {
+ PacketCoding.DecodePacket(string_lightning).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 395L
+ cls mustEqual ObjectClass.lightning
+ guid mustEqual PlanetSideGUID(90)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[VehicleData] mustEqual true
+ val lightning = data.get.asInstanceOf[VehicleData]
+ lightning.basic.pos.coord.x mustEqual 3674.8438f
+ lightning.basic.pos.coord.y mustEqual 2726.789f
+ lightning.basic.pos.coord.z mustEqual 91.15625f
+ lightning.basic.pos.roll mustEqual 0
+ lightning.basic.pos.pitch mustEqual 0
+ lightning.basic.pos.yaw mustEqual 0
+ lightning.basic.faction mustEqual PlanetSideEmpire.VS
+ lightning.basic.unk mustEqual 4
+ lightning.basic.player_guid mustEqual PlanetSideGUID(0)
+ lightning.health mustEqual 255
+ lightning.mountings.isDefined mustEqual true
+ lightning.mountings.get.size mustEqual 1
+ val mounting = lightning.mountings.get.head
+ mounting.objectClass mustEqual ObjectClass.lightning_weapon_system
+ mounting.guid mustEqual PlanetSideGUID(91)
+ mounting.parentSlot mustEqual 1
+ mounting.obj.isInstanceOf[WeaponData] mustEqual true
+ val weapon = mounting.obj.asInstanceOf[WeaponData]
+ weapon.unk1 mustEqual 0x4
+ weapon.unk2 mustEqual 0x8
+ weapon.fire_mode mustEqual 0
+ weapon.ammo.size mustEqual 2
+ //0
+ var ammo = weapon.ammo.head
+ ammo.objectClass mustEqual ObjectClass.bullet_75mm
+ ammo.guid mustEqual PlanetSideGUID(92)
+ ammo.parentSlot mustEqual 0
+ ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x0
+ //1
+ ammo = weapon.ammo(1)
+ ammo.objectClass mustEqual ObjectClass.bullet_25mm
+ ammo.guid mustEqual PlanetSideGUID(93)
+ ammo.parentSlot mustEqual 1
+ ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x0
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (medium transport)" in {
+ PacketCoding.DecodePacket(string_mediumtransport).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 474L
+ cls mustEqual ObjectClass.mediumtransport
+ guid mustEqual PlanetSideGUID(387)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[VehicleData] mustEqual true
+ val deliverer = data.get.asInstanceOf[VehicleData]
+ deliverer.basic.pos.coord.x mustEqual 6531.961f
+ deliverer.basic.pos.coord.y mustEqual 1872.1406f
+ deliverer.basic.pos.coord.z mustEqual 24.734375f
+ deliverer.basic.pos.roll mustEqual 0
+ deliverer.basic.pos.pitch mustEqual 0
+ deliverer.basic.pos.yaw mustEqual 33
+ deliverer.basic.faction mustEqual PlanetSideEmpire.NC
+ deliverer.basic.unk mustEqual 4
+ deliverer.basic.player_guid mustEqual PlanetSideGUID(0)
+ deliverer.unk1 mustEqual 0
+ deliverer.health mustEqual 255
+ deliverer.unk2 mustEqual 0
+ deliverer.driveState mustEqual DriveState.State7
+ deliverer.unk4 mustEqual true
+ deliverer.unk5 mustEqual 0
+ deliverer.mountings.isDefined mustEqual true
+ deliverer.mountings.get.size mustEqual 2
+ //0
+ var mounting = deliverer.mountings.get.head
+ mounting.objectClass mustEqual ObjectClass.mediumtransport_weapon_systemA
+ mounting.guid mustEqual PlanetSideGUID(383)
+ mounting.parentSlot mustEqual 5
+ mounting.obj.isInstanceOf[WeaponData] mustEqual true
+ var weapon = mounting.obj.asInstanceOf[WeaponData]
+ weapon.unk1 mustEqual 0x6
+ weapon.unk2 mustEqual 0x8
+ weapon.fire_mode mustEqual 0
+ weapon.ammo.size mustEqual 1
+ var ammo = weapon.ammo.head
+ ammo.objectClass mustEqual ObjectClass.bullet_20mm
+ ammo.guid mustEqual PlanetSideGUID(420)
+ ammo.parentSlot mustEqual 0
+ ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x8
+ //1
+ mounting = deliverer.mountings.get(1)
+ mounting.objectClass mustEqual ObjectClass.mediumtransport_weapon_systemB
+ mounting.guid mustEqual PlanetSideGUID(556)
+ mounting.parentSlot mustEqual 6
+ mounting.obj.isInstanceOf[WeaponData] mustEqual true
+ weapon = mounting.obj.asInstanceOf[WeaponData]
+ weapon.unk1 mustEqual 0x6
+ weapon.unk2 mustEqual 0x8
+ weapon.fire_mode mustEqual 0
+ weapon.ammo.size mustEqual 1
+ ammo = weapon.ammo.head
+ ammo.objectClass mustEqual ObjectClass.bullet_20mm
+ ammo.guid mustEqual PlanetSideGUID(575)
+ ammo.parentSlot mustEqual 0
+ ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
+ ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x8
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (ams)" in {
+ PacketCoding.DecodePacket(string_ams).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 440L
+ cls mustEqual ObjectClass.ams
+ guid mustEqual PlanetSideGUID(4157)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[AMSData] mustEqual true
+ val ams = data.get.asInstanceOf[AMSData]
+ ams.basic.pos.coord.x mustEqual 3674.0f
+ ams.basic.pos.coord.y mustEqual 2726.789f
+ ams.basic.pos.coord.z mustEqual 91.15625f
+ ams.basic.pos.roll mustEqual 0
+ ams.basic.pos.pitch mustEqual 0
+ ams.basic.pos.yaw mustEqual 0
+ ams.basic.faction mustEqual PlanetSideEmpire.VS
+ ams.basic.unk mustEqual 0
+ ams.basic.player_guid mustEqual PlanetSideGUID(34082)
+ ams.unk1 mustEqual 2
+ ams.health mustEqual 236
+ ams.unk2 mustEqual 0
+ ams.driveState mustEqual DriveState.Deployed
+ ams.matrix_guid mustEqual PlanetSideGUID(3663)
+ ams.respawn_guid mustEqual PlanetSideGUID(3638)
+ ams.term_a_guid mustEqual PlanetSideGUID(3827)
+ ams.term_b_guid mustEqual PlanetSideGUID(3556)
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (ams, destroyed)" in {
+ PacketCoding.DecodePacket(string_ams_destroyed).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 141L
+ cls mustEqual ObjectClass.ams_destroyed
+ guid mustEqual PlanetSideGUID(4157)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[DestroyedVehicleData] mustEqual true
+ val dams = data.get.asInstanceOf[DestroyedVehicleData]
+ dams.pos.coord.x mustEqual 3674.0f
+ dams.pos.coord.y mustEqual 2726.789f
+ dams.pos.coord.z mustEqual 91.15625f
+ dams.pos.roll mustEqual 0
+ dams.pos.pitch mustEqual 0
+ dams.pos.yaw mustEqual 0
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (switchblade)" in {
+ PacketCoding.DecodePacket(string_switchblade).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 403L
+ cls mustEqual ObjectClass.switchblade
+ guid mustEqual PlanetSideGUID(418)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[Vehicle2Data] mustEqual true
+ val switchblade = data.get.asInstanceOf[Vehicle2Data]
+ switchblade.basic.pos.coord.x mustEqual 6531.961f
+ switchblade.basic.pos.coord.y mustEqual 1872.1406f
+ switchblade.basic.pos.coord.z mustEqual 24.734375f
+ switchblade.basic.pos.roll mustEqual 0
+ switchblade.basic.pos.pitch mustEqual 0
+ switchblade.basic.pos.yaw mustEqual 33
+ switchblade.basic.faction mustEqual PlanetSideEmpire.VS
+ switchblade.basic.unk mustEqual 4
+ switchblade.health mustEqual 255
+ switchblade.driveState mustEqual DriveState.Mobile
+ switchblade.mountings.isDefined mustEqual true
+ switchblade.mountings.get.size mustEqual 1
+ //0
+ val weapon = switchblade.mountings.get.head
+ weapon.objectClass mustEqual ObjectClass.scythe
+ weapon.guid mustEqual PlanetSideGUID(355)
+ weapon.parentSlot mustEqual 1
+ weapon.obj.asInstanceOf[WeaponData].unk1 mustEqual 0x6
+ weapon.obj.asInstanceOf[WeaponData].unk2 mustEqual 0x8
+ weapon.obj.asInstanceOf[WeaponData].ammo.size mustEqual 2
+ //ammo-0
+ var ammo = weapon.obj.asInstanceOf[WeaponData].ammo.head
+ ammo.objectClass mustEqual ObjectClass.ancient_ammo_vehicle
+ ammo.guid mustEqual PlanetSideGUID(366)
+ ammo.parentSlot mustEqual 0
+ ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x8
+ //ammo-1
+ ammo = weapon.obj.asInstanceOf[WeaponData].ammo(1)
+ ammo.objectClass mustEqual ObjectClass.ancient_ammo_vehicle
+ ammo.guid mustEqual PlanetSideGUID(385)
+ ammo.parentSlot mustEqual 1
+ ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x8
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (droppod)" in {
+ PacketCoding.DecodePacket(string_droppod).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 193L
+ cls mustEqual ObjectClass.droppod
+ guid mustEqual PlanetSideGUID(3595)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[DroppodData] mustEqual true
+ val droppod = data.get.asInstanceOf[DroppodData]
+ droppod.basic.pos.coord.x mustEqual 5108.0f
+ droppod.basic.pos.coord.y mustEqual 6164.0f
+ droppod.basic.pos.coord.z mustEqual 1023.9844f
+ droppod.basic.pos.roll mustEqual 0
+ droppod.basic.pos.pitch mustEqual 0
+ droppod.basic.pos.yaw mustEqual 0
+ droppod.basic.unk mustEqual 4
+ droppod.basic.player_guid mustEqual PlanetSideGUID(0)
+ droppod.burn mustEqual false
+ droppod.health mustEqual 255
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (shuttle 1)" in {
+ PacketCoding.DecodePacket(string_orbital_shuttle_1).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 130
+ cls mustEqual ObjectClass.orbital_shuttle
+ guid mustEqual PlanetSideGUID(1129)
+ parent.isDefined mustEqual true
+ parent.get.guid mustEqual PlanetSideGUID(786)
+ parent.get.slot mustEqual 3
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[OrbitalShuttleData] mustEqual true
+ data.get.asInstanceOf[OrbitalShuttleData].faction mustEqual PlanetSideEmpire.VS
+ data.get.asInstanceOf[OrbitalShuttleData].pos.isDefined mustEqual false
+ case _ =>
+ ko
+ }
+ }
+
+ "decode (shuttle 2)" in {
+ PacketCoding.DecodePacket(string_orbital_shuttle_2).require match {
+ case ObjectCreateMessage(len, cls, guid, parent, data) =>
+ len mustEqual 195
+ cls mustEqual ObjectClass.orbital_shuttle
+ guid mustEqual PlanetSideGUID(1127)
+ parent.isDefined mustEqual false
+ data.isDefined mustEqual true
+ data.get.isInstanceOf[OrbitalShuttleData] mustEqual true
+ val shuttle = data.get.asInstanceOf[OrbitalShuttleData]
+ shuttle.faction mustEqual PlanetSideEmpire.VS
+ shuttle.pos.isDefined mustEqual true
+ shuttle.pos.get.coord.x mustEqual 5610.0156f
+ shuttle.pos.get.coord.y mustEqual 4255.258f
+ shuttle.pos.get.coord.z mustEqual 134.1875f
+ shuttle.pos.get.roll mustEqual 0
+ shuttle.pos.get.pitch mustEqual 0
+ shuttle.pos.get.yaw mustEqual 96
+ case _ =>
+ ko
+ }
+ }
+
+ "encode (fury)" in {
+ val obj = VehicleData(
+ CommonFieldData(
+ PlacementData(6531.961f, 1872.1406f, 24.734375f, 0, 0, 33),
+ PlanetSideEmpire.VS, 4
+ ),
+ 255,
+ MountItem(ObjectClass.fury_weapon_systema, PlanetSideGUID(400), 1,
+ WeaponData(0x6, 0x8, 0, ObjectClass.hellfire_ammo, PlanetSideGUID(432), 0, AmmoBoxData(0x8))
+ )
+ )
+ val msg = ObjectCreateMessage(ObjectClass.fury, PlanetSideGUID(413), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_fury
+ }
+
+ "encode (ant)" in {
+ val obj = ANTData(
+ CommonFieldData(
+ PlacementData(3674.8438f, 2726.789f, 91.15625f),
+ PlanetSideEmpire.VS, 4
+ ),
+ 255,
+ DriveState.Mobile
+ )
+ val msg = ObjectCreateMessage(ObjectClass.ant, PlanetSideGUID(380), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_ant
+ }
+
+ "encode (lightning)" in {
+ val obj = VehicleData(
+ CommonFieldData(
+ PlacementData(3674.8438f, 2726.789f, 91.15625f),
+ PlanetSideEmpire.VS, 4
+ ),
+ 255,
+ MountItem(ObjectClass.lightning_weapon_system, PlanetSideGUID(91), 1,
+ WeaponData(4, 8, 0, ObjectClass.bullet_75mm, PlanetSideGUID(92), 0, AmmoBoxData(), ObjectClass.bullet_25mm, PlanetSideGUID(93), 1, AmmoBoxData())
+ )
+ )
+ val msg = ObjectCreateMessage(ObjectClass.lightning, PlanetSideGUID(90), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_lightning
+ }
+
+ "encode (deliverer)" in {
+ val obj = VehicleData(
+ CommonFieldData(
+ PlacementData(6531.961f, 1872.1406f, 24.734375f, 0, 0, 33),
+ PlanetSideEmpire.NC, 4
+ ),
+ 0,
+ 255,
+ 0,
+ DriveState.State7,
+ true,
+ 0,
+ Some(
+ MountItem(
+ ObjectClass.mediumtransport_weapon_systemA, PlanetSideGUID(383), 5,
+ WeaponData(6, 8, ObjectClass.bullet_20mm, PlanetSideGUID(420), 0, AmmoBoxData(8))
+ ) ::
+ MountItem(
+ ObjectClass.mediumtransport_weapon_systemB, PlanetSideGUID(556), 6,
+ WeaponData(6, 8, ObjectClass.bullet_20mm, PlanetSideGUID(575), 0, AmmoBoxData(8))
+ ) ::
+ Nil
+ )
+ )(2)
+ val msg = ObjectCreateMessage(ObjectClass.mediumtransport, PlanetSideGUID(387), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_mediumtransport
+ }
+
+ "encode (ams)" in {
+ val obj = AMSData(
+ CommonFieldData(PlacementData(3674.0f, 2726.789f, 91.15625f, 0, 0, 0),
+ PlanetSideEmpire.VS, 0,
+ PlanetSideGUID(34082)
+ ),
+ 2,
+ 236,
+ 0,
+ DriveState.Deployed,
+ 63,
+ PlanetSideGUID(3663),
+ PlanetSideGUID(3638),
+ PlanetSideGUID(3827),
+ PlanetSideGUID(3556)
+ )
+ val msg = ObjectCreateMessage(ObjectClass.ams, PlanetSideGUID(4157), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_ams
+ }
+
+ "encode (ams, destroyed)" in {
+ val obj = DestroyedVehicleData(PlacementData(3674.0f, 2726.789f, 91.15625f))
+ val msg = ObjectCreateMessage(ObjectClass.ams_destroyed, PlanetSideGUID(4157), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_ams_destroyed
+ }
+
+ "encode (switchblade(" in {
+ val obj = Vehicle2Data(
+ CommonFieldData(PlacementData(6531.961f, 1872.1406f, 24.734375f ,0 ,0 ,33),
+ PlanetSideEmpire.VS, 4
+ ),
+ 255,
+ DriveState.Mobile,
+ MountItem(ObjectClass.scythe, PlanetSideGUID(355), 1,
+ WeaponData(0x6, 0x8, 0, ObjectClass.ancient_ammo_vehicle, PlanetSideGUID(366), 0, AmmoBoxData(0x8), ObjectClass.ancient_ammo_vehicle, PlanetSideGUID(385), 1, AmmoBoxData(0x8))
+ )
+ )
+ val msg = ObjectCreateMessage(ObjectClass.switchblade, PlanetSideGUID(418), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_switchblade
+ }
+
+ "encode (droppod)" in {
+ val obj = DroppodData(
+ CommonFieldData(
+ PlacementData(5108.0f, 6164.0f, 1023.9844f),
+ PlanetSideEmpire.VS, 4
+ )
+ )
+ val msg = ObjectCreateMessage(ObjectClass.droppod, PlanetSideGUID(3595), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_droppod
+ }
+
+ "encode (shuttle 1)" in {
+ val obj = OrbitalShuttleData(PlanetSideEmpire.VS)
+ val msg = ObjectCreateMessage(ObjectClass.orbital_shuttle, PlanetSideGUID(1129), ObjectCreateMessageParent(PlanetSideGUID(786), 3), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_orbital_shuttle_1
+ }
+
+ "encode (shuttle 2)" in {
+ val obj = OrbitalShuttleData(PlacementData(5610.0156f, 4255.258f, 134.1875f, 0, 0, 96), PlanetSideEmpire.VS)
+ val msg = ObjectCreateMessage(ObjectClass.orbital_shuttle, PlanetSideGUID(1127), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_orbital_shuttle_2
+ }
+}
diff --git a/common/src/test/scala/game/VehicleStateMessageTest.scala b/common/src/test/scala/game/VehicleStateMessageTest.scala
new file mode 100644
index 00000000..d9290863
--- /dev/null
+++ b/common/src/test/scala/game/VehicleStateMessageTest.scala
@@ -0,0 +1,54 @@
+// 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 VehicleStateMessageTest extends Specification {
+ val string = hex"1b 9d010d85aecaa6b8c2dfdfefffc020008006000078"
+
+ "decode" in {
+ PacketCoding.DecodePacket(string).require match {
+ case VehicleStateMessage(guid, unk1, pos, roll, pitch, yaw, vel, unk2, unk3, unk4, wheel, unk5, unk6) =>
+ guid mustEqual PlanetSideGUID(413)
+ unk1 mustEqual 0
+ pos.x mustEqual 3674.8438f
+ pos.y mustEqual 2726.789f
+ pos.z mustEqual 91.09375f
+ roll mustEqual 359.29688f
+ pitch mustEqual 1.0546875f
+ yaw mustEqual 90.35156f
+ vel.isDefined mustEqual true
+ vel.get.x mustEqual 0.0f
+ vel.get.y mustEqual 0.0f
+ vel.get.z mustEqual 0.03125f
+ unk2.isDefined mustEqual false
+ unk3 mustEqual 0
+ unk4 mustEqual 0
+ wheel mustEqual 15
+ unk5 mustEqual false
+ unk6 mustEqual false
+ case _ =>
+ ko
+ }
+ }
+
+ "encode" in {
+ val msg = VehicleStateMessage(
+ PlanetSideGUID(413),
+ 0,
+ Vector3(3674.8438f, 2726.789f, 91.09375f),
+ 359.29688f, 1.0546875f, 90.35156f,
+ Some(Vector3(0.0f, 0.0f, 0.03125f)),
+ None,
+ 0, 0, 15,
+ false, false
+ )
+ 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 74b9616e..d44ce0f3 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -138,9 +138,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
false,
RibbonBars()
)
- val inv = InventoryItem(ObjectClass.beamer, PlanetSideGUID(76), 0, DetailedWeaponData(8, ObjectClass.energy_cell, PlanetSideGUID(77), 0, DetailedAmmoBoxData(8, 16))) ::
- InventoryItem(ObjectClass.suppressor, PlanetSideGUID(78), 2, DetailedWeaponData(8, ObjectClass.bullet_9mm, PlanetSideGUID(79), 0, DetailedAmmoBoxData(8, 25))) ::
- InventoryItem(ObjectClass.forceblade, PlanetSideGUID(80), 4, DetailedWeaponData(8, ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1))) ::
+ val inv = InventoryItem(ObjectClass.beamer, PlanetSideGUID(76), 0, DetailedWeaponData(4, 8, ObjectClass.energy_cell, PlanetSideGUID(77), 0, DetailedAmmoBoxData(8, 16))) ::
+ InventoryItem(ObjectClass.suppressor, PlanetSideGUID(78), 2, DetailedWeaponData(4, 8, ObjectClass.bullet_9mm, PlanetSideGUID(79), 0, DetailedAmmoBoxData(8, 25))) ::
+ InventoryItem(ObjectClass.forceblade, PlanetSideGUID(80), 4, DetailedWeaponData(4, 8, ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1))) ::
InventoryItem(ObjectClass.locker_container, PlanetSideGUID(82), 5, DetailedAmmoBoxData(8, 1)) ::
InventoryItem(ObjectClass.bullet_9mm, PlanetSideGUID(83), 6, DetailedAmmoBoxData(8, 50)) ::
InventoryItem(ObjectClass.bullet_9mm, PlanetSideGUID(84), 9, DetailedAmmoBoxData(8, 50)) ::
@@ -255,6 +255,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ ChildObjectStateMessage(object_guid : PlanetSideGUID, pitch : Int, yaw : Int) =>
//log.info("ChildObjectState: " + msg)
+ case msg @ VehicleStateMessage(vehicle_guid, unk1, pos, roll, pitch, yaw, vel, unk5, unk6, unk7, wheels, unk9, unkA) =>
+ //log.info("VehicleState: " + msg)
+
case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vector, unk1, unk2, unk3, unk4, time_alive) =>
//log.info("ProjectileState: " + msg)