From 434171bcc4c263db81184d4f9c396d996c8c1e68 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 9 May 2017 20:30:58 -0400 Subject: [PATCH 1/3] This is a combination of 32 commits for object-create logic. --- .../psforever/packet/GamePacketOpcode.scala | 2 +- .../game/BuildingInfoUpdateMessage.scala | 4 +- .../packet/game/ChangeAmmoMessage.scala | 2 +- .../packet/game/ChangeFireModeMessage.scala | 2 +- .../game/ChangeFireStateMessage_Start.scala | 6 +- .../game/ChangeFireStateMessage_Stop.scala | 6 +- .../packet/game/CharacterInfoMessage.scala | 2 +- .../net/psforever/packet/game/EmoteMsg.scala | 2 +- .../packet/game/FireHintMessage.scala | 2 +- .../psforever/packet/game/HitMessage.scala | 2 +- .../packet/game/LoginRespMessage.scala | 2 +- .../packet/game/ObjectDeleteMessage.scala | 2 +- .../packet/game/ObjectHeldMessage.scala | 2 +- .../net/psforever/packet/game/PingMsg.scala | 2 +- .../game/QuantityDeltaUpdateMessage.scala | 2 +- .../packet/game/QuantityUpdateMessage.scala | 2 +- .../psforever/packet/game/ReloadMessage.scala | 2 +- .../packet/game/SetCurrentAvatarMessage.scala | 2 +- .../packet/game/SetEmpireMessage.scala | 2 +- .../packet/game/UseItemMessage.scala | 2 +- .../packet/game/VehicleStateMessage.scala | 95 ++++ .../packet/game/WeaponDelayFireMessage.scala | 2 +- .../packet/game/WeaponDryFireMessage.scala | 6 +- .../packet/game/WeaponFireMessage.scala | 4 +- .../packet/game/WeaponJammedMessage.scala | 6 +- .../packet/game/objectcreate/ACEData.scala | 1 - .../packet/game/objectcreate/AMSData.scala | 99 ++++ .../packet/game/objectcreate/ANTData.scala | 62 +++ .../AegisShieldGeneratorData.scala | 4 +- .../game/objectcreate/AmmoBoxData.scala | 2 +- .../CharacterAppearanceData.scala | 4 +- ...oyableData.scala => CommonFieldData.scala} | 40 +- .../objectcreate/CommonTerminalData.scala | 38 +- .../ConcurrentFeedWeaponData.scala | 91 ---- .../objectcreate/DestroyedVehicleData.scala | 21 + .../objectcreate/DetailedAmmoBoxData.scala | 2 +- .../DetailedConcurrentFeedWeaponData.scala | 86 --- .../objectcreate/DetailedWeaponData.scala | 111 +++- .../packet/game/objectcreate/DriveState.scala | 26 + .../game/objectcreate/DroppodData.scala | 66 +++ .../objectcreate/ImplantInterfaceData.scala | 33 -- .../game/objectcreate/InternalSlot.scala | 4 +- .../game/objectcreate/InventoryData.scala | 51 +- .../game/objectcreate/InventoryItem.scala | 39 +- .../packet/game/objectcreate/MountItem.scala | 30 ++ .../game/objectcreate/ObjectClass.scala | 161 ++++-- .../game/objectcreate/ObjectCreateBase.scala | 37 +- .../OneMannedFieldTurretData.scala | 25 +- .../objectcreate/OrbitalShuttleData.scala | 107 ++++ .../game/objectcreate/PlacementData.scala | 14 +- .../packet/game/objectcreate/Prefab.scala | 153 ++++++ .../objectcreate/SmallDeployableData.scala | 4 +- .../game/objectcreate/SmallTurretData.scala | 10 +- .../packet/game/objectcreate/TRAPData.scala | 4 +- .../game/objectcreate/Vehicle2Data.scala | 129 +++++ .../game/objectcreate/VehicleData.scala | 208 ++++++++ .../packet/game/objectcreate/WeaponData.scala | 120 ++++- .../ObjectCreateDetailedMessageTest.scala | 129 ++--- .../scala/game/ObjectCreateMessageTest.scala | 267 +++++----- .../ObjectCreateMessageVehiclesTest.scala | 498 ++++++++++++++++++ .../scala/game/VehicleStateMessageTest.scala | 54 ++ .../src/main/scala/WorldSessionActor.scala | 9 +- 62 files changed, 2225 insertions(+), 677 deletions(-) create mode 100644 common/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala create mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/AMSData.scala create mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/ANTData.scala rename common/src/main/scala/net/psforever/packet/game/objectcreate/{ACEDeployableData.scala => CommonFieldData.scala} (59%) delete mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/ConcurrentFeedWeaponData.scala create mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/DestroyedVehicleData.scala delete mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedConcurrentFeedWeaponData.scala create mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/DriveState.scala create mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/DroppodData.scala delete mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/ImplantInterfaceData.scala create mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/MountItem.scala create mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/OrbitalShuttleData.scala create mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala create mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/Vehicle2Data.scala create mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala create mode 100644 common/src/test/scala/game/ObjectCreateMessageVehiclesTest.scala create mode 100644 common/src/test/scala/game/VehicleStateMessageTest.scala diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index 69629b54..6fdec417 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..970c6492 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(0), "weapon") + case ObjectClass.nchev_scattercannon => ConstructorData.genericCodec(DetailedWeaponData.codec(0), "weapon") + case ObjectClass.nchev_sparrow => ConstructorData.genericCodec(DetailedWeaponData.codec(0), "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(0), "weapon") + case ObjectClass.trhev_dualcycler => ConstructorData.genericCodec(DetailedWeaponData.codec(0), "weapon") + case ObjectClass.trhev_pounder => ConstructorData.genericCodec(DetailedWeaponData.codec(0), "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(0), "weapon") + case ObjectClass.vshev_starfire => ConstructorData.genericCodec(DetailedWeaponData.codec(0), "weapon") + case ObjectClass.vshev_quasar => ConstructorData.genericCodec(DetailedWeaponData.codec(0), "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(0), "weapon") + case ObjectClass.nchev_scattercannon => ConstructorData.genericCodec(WeaponData.codec(0), "weapon") + case ObjectClass.nchev_sparrow => ConstructorData.genericCodec(WeaponData.codec(0), "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(0), "weapon") + case ObjectClass.trhev_dualcycler => ConstructorData.genericCodec(WeaponData.codec(0), "weapon") + case ObjectClass.trhev_pounder => ConstructorData.genericCodec(WeaponData.codec(0), "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(0), "weapon") + case ObjectClass.vshev_quasar => ConstructorData.genericCodec(WeaponData.codec(0), "weapon") + case ObjectClass.vshev_starfire => ConstructorData.genericCodec(WeaponData.codec(0), "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,57 @@ 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.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..c8e6dbf6 --- /dev/null +++ b/common/src/test/scala/game/ObjectCreateMessageVehiclesTest.scala @@ -0,0 +1,498 @@ +// 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 = hex"17 82000000 0901B026904838000001FE0700" + + "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)" in { + + PacketCoding.DecodePacket(string_orbital_shuttle).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 + 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)" in { + val obj = OrbitalShuttleData() + 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 + } +} 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) From 41acd32914a9a5800dd2efeb376584e2a1fd4e16 Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 22 May 2017 00:26:38 -0400 Subject: [PATCH 2/3] set the MAX arms to ignore the ammunition checks because I have conflicted expectations for them right now --- .../game/objectcreate/ObjectClass.scala | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) 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 970c6492..217505b6 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 @@ -540,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(DetailedWeaponData.codec(0), "weapon") - case ObjectClass.nchev_scattercannon => ConstructorData.genericCodec(DetailedWeaponData.codec(0), "weapon") - case ObjectClass.nchev_sparrow => ConstructorData.genericCodec(DetailedWeaponData.codec(0), "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") @@ -584,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(DetailedWeaponData.codec(0), "weapon") - case ObjectClass.trhev_dualcycler => ConstructorData.genericCodec(DetailedWeaponData.codec(0), "weapon") - case ObjectClass.trhev_pounder => ConstructorData.genericCodec(DetailedWeaponData.codec(0), "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(DetailedWeaponData.codec(0), "weapon") - case ObjectClass.vshev_starfire => ConstructorData.genericCodec(DetailedWeaponData.codec(0), "weapon") - case ObjectClass.vshev_quasar => ConstructorData.genericCodec(DetailedWeaponData.codec(0), "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") @@ -827,9 +827,9 @@ object ObjectClass { 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(0), "weapon") - case ObjectClass.nchev_scattercannon => ConstructorData.genericCodec(WeaponData.codec(0), "weapon") - case ObjectClass.nchev_sparrow => ConstructorData.genericCodec(WeaponData.codec(0), "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") @@ -871,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(0), "weapon") - case ObjectClass.trhev_dualcycler => ConstructorData.genericCodec(WeaponData.codec(0), "weapon") - case ObjectClass.trhev_pounder => ConstructorData.genericCodec(WeaponData.codec(0), "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(0), "weapon") - case ObjectClass.vshev_quasar => ConstructorData.genericCodec(WeaponData.codec(0), "weapon") - case ObjectClass.vshev_starfire => ConstructorData.genericCodec(WeaponData.codec(0), "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") From bf91dd833315b8fe0f3275e6fe97c1c988fb6d46 Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 22 May 2017 08:33:46 -0400 Subject: [PATCH 3/3] corrections to OrtibalShuttleData Codec and testing --- .../game/objectcreate/ObjectClass.scala | 1 + .../ObjectCreateMessageVehiclesTest.scala | 47 ++++++++++++++++--- 2 files changed, 41 insertions(+), 7 deletions(-) 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 217505b6..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 @@ -1159,6 +1159,7 @@ object ObjectClass { 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") diff --git a/common/src/test/scala/game/ObjectCreateMessageVehiclesTest.scala b/common/src/test/scala/game/ObjectCreateMessageVehiclesTest.scala index c8e6dbf6..f51aa2d4 100644 --- a/common/src/test/scala/game/ObjectCreateMessageVehiclesTest.scala +++ b/common/src/test/scala/game/ObjectCreateMessageVehiclesTest.scala @@ -17,7 +17,8 @@ class ObjectCreateMessageVehiclesTest extends Specification { 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 = hex"17 82000000 0901B026904838000001FE0700" + 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 { @@ -332,9 +333,8 @@ class ObjectCreateMessageVehiclesTest extends Specification { } } - "decode (shuttle)" in { - - PacketCoding.DecodePacket(string_orbital_shuttle).require match { + "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 @@ -344,6 +344,31 @@ class ObjectCreateMessageVehiclesTest extends Specification { 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 } @@ -488,11 +513,19 @@ class ObjectCreateMessageVehiclesTest extends Specification { pkt mustEqual string_droppod } - "encode (shuttle)" in { - val obj = OrbitalShuttleData() + "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 + 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 } }