Merge pull request #151 from Fate-JH/object-create-updates

Object Create Message Update #2.5
This commit is contained in:
Fate-JH 2017-06-03 13:44:14 -04:00 committed by GitHub
commit 986594de87
62 changed files with 2259 additions and 677 deletions

View file

@ -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

View file

@ -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) =>

View file

@ -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._

View file

@ -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._

View file

@ -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]
}

View file

@ -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]
}

View file

@ -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._

View file

@ -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._

View file

@ -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._

View file

@ -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._

View file

@ -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]
}

View file

@ -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._

View file

@ -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._

View file

@ -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._

View file

@ -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._

View file

@ -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._

View file

@ -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._

View file

@ -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._

View file

@ -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._

View file

@ -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._

View file

@ -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
}
)
}

View file

@ -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._

View file

@ -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]
}

View file

@ -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]
}

View file

@ -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]
}

View file

@ -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}

View file

@ -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).<br>
* <br>
* 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
)
}
)
}

View file

@ -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)
}
)
}

View file

@ -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] (

View file

@ -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] (
{

View file

@ -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) ::

View file

@ -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)
}
)
}

View file

@ -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)
}
)
}

View file

@ -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.<br>
* <br>
* 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`.<br>
* <br>
* Exploration:<br>
* 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)
}
)
}

View file

@ -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.<br>
* <br>
* 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]
}

View file

@ -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

View file

@ -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.<br>
* <br>
* 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`.<br>
* <br>
* Exploration:<br>
* 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)
}
)
}

View file

@ -13,52 +13,107 @@ import shapeless.{::, HNil}
* <br>
* 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()
}

View file

@ -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.<br>
* <br>
* 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)
}

View file

@ -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.<br>
* <br>
* 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.<br>
* <br>
* 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.<br>
* <br>
* 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)
}
)
}

View file

@ -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)
}
)
}

View file

@ -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.<br>
* <br>
* 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

View file

@ -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.<br>
* <br>
* 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)
}

View file

@ -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.<br>
* <br>
* 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
}

View file

@ -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
}

View file

@ -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.<br>
* <br>
* Object classes compose a number between 0 and 2047, always translating into an 11-bit value.
* In `scodec` terms, that's a `uintL(11)` or a `uintL(0xB)`.
* The items that can be constructed with packets `ObjectCreateMessage` and `ObjectCreateDetailedMessage` number fewer than 1047.
* A reference between all object class codes and the name of the object they represent.
* Object class types are defined by an 11-bit (`0xB`) value.
*/
object ObjectClass {
//character
final val avatar = 0x79 // 121
//ammunition
final val bullet_105mm = 0
final val bullet_12mm = 3
@ -304,17 +299,47 @@ object ObjectClass {
final val oicw_projectile = 602
final val starfire_projectile = 831
final val striker_missile_targeting_projectile = 841
//vehicles
final val ams = 46
final val ams_destroyed = 47
final val ant = 60
final val ant_destroyed = 61
final val aurora = 118
final val battlewagon = 135 //raider
final val droppod = 258
final val fury = 335
final val lightning = 446
final val lightning_destroyed = 447
final val mediumtransport = 532
final val mediumtransport_destroyed = 533
final val orbital_shuttle = 608
final val quadassault = 707
final val quadassault_destroyed = 708
final val quadstealth = 710
final val quadstealth_destroyed = 711
final val switchblade = 847
final val switchblade_destroyed = 848
final val threemanheavybuggy = 862 //marauder
final val threemanheavybuggy_destroyed = 863
final val thunderer = 865
final val two_man_assault_buggy = 896 //harasser
final val two_man_assault_buggy_destroyed = 897
final val twomanheavybuggy = 898 //enforcer
final val twomanheavybuggy_destroyed = 899
final val twomanhoverbuggy = 900 //thresher
final val twomanhoverbuggy_destroyed = 901
//other
final val locker_container = 456
final val ams_respawn_tube = 49
final val avatar = 121
final val capture_flag = 157
final val implant_terminal_interface = 409
final val locker_container = 456
final val matrix_terminala = 517
final val matrix_terminalb = 518
final val matrix_terminalc = 519
final val order_terminal = 612
final val order_terminala = 613
final val order_terminalb = 614
final val ams_respawn_tube = 49
final val capture_flag = 157
//TODO refactor the following functions into another object later
/**
@ -501,7 +526,7 @@ object ObjectClass {
case ObjectClass.hunterseeker => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.ilc9 => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.isp => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.katana => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
case ObjectClass.katana => ConstructorData.genericCodec(DetailedWeaponData.codec(2), "weapon")
case ObjectClass.lancer => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.lasher => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.liberator_25mm_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
@ -515,9 +540,9 @@ object ObjectClass {
case ObjectClass.mediumtransport_weapon_systemB => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.mini_chaingun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.oicw => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.nchev_falcon => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
case ObjectClass.nchev_scattercannon => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
case ObjectClass.nchev_sparrow => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
case ObjectClass.nchev_falcon => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
case ObjectClass.nchev_scattercannon => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
case ObjectClass.nchev_sparrow => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
case ObjectClass.particle_beam_magrider => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.pellet_gun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.peregrine_dual_machine_gun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
@ -540,7 +565,7 @@ object ObjectClass {
case ObjectClass.prowler_weapon_systemB => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.pulsar => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.pulsed_particle_accelerator => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.punisher => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
case ObjectClass.punisher => ConstructorData.genericCodec(DetailedWeaponData.codec(2), "weapon")
case ObjectClass.quadassault_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.r_shotgun => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.radiator => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
@ -559,14 +584,14 @@ object ObjectClass {
case ObjectClass.thumper => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.thunderer_weapon_systema => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.thunderer_weapon_systemb => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.trhev_burster => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
case ObjectClass.trhev_dualcycler => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
case ObjectClass.trhev_pounder => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
case ObjectClass.trhev_burster => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
case ObjectClass.trhev_dualcycler => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
case ObjectClass.trhev_pounder => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
case ObjectClass.vanguard_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.vanu_sentry_turret_weapon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.vshev_comet => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
case ObjectClass.vshev_starfire => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
case ObjectClass.vshev_quasar => ConstructorData.genericCodec(DetailedConcurrentFeedWeaponData.codec, "weapon")
case ObjectClass.vshev_comet => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
case ObjectClass.vshev_starfire => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
case ObjectClass.vshev_quasar => ConstructorData.genericCodec(DetailedWeaponData.codec(-1), "weapon")
case ObjectClass.vulture_bomb_bay => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.vulture_nose_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.vulture_tail_cannon => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
@ -740,8 +765,8 @@ object ObjectClass {
case ObjectClass.aphelion_starfire => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.aphelion_starfire_left => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.aphelion_starfire_right => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.aurora_weapon_systema => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.aurora_weapon_systemb => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.aurora_weapon_systema => ConstructorData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.aurora_weapon_systemb => ConstructorData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.battlewagon_weapon_systema => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.battlewagon_weapon_systemb => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.battlewagon_weapon_systemc => ConstructorData.genericCodec(WeaponData.codec, "weapon")
@ -789,22 +814,22 @@ object ObjectClass {
case ObjectClass.hunterseeker => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.ilc9 => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.isp => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.katana => ConstructorData.genericCodec(ConcurrentFeedWeaponData.codec, "weapon")
case ObjectClass.katana => ConstructorData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.lancer => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.lasher => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.liberator_25mm_cannon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.liberator_bomb_bay => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.liberator_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.lightgunship_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.lightning_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.lightning_weapon_system => ConstructorData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.maelstrom => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.magcutter => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.mediumtransport_weapon_systemA => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.mediumtransport_weapon_systemB => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.mini_chaingun => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.nchev_falcon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.nchev_scattercannon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.nchev_sparrow => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.nchev_falcon => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
case ObjectClass.nchev_scattercannon => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
case ObjectClass.nchev_sparrow => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
case ObjectClass.oicw => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.particle_beam_magrider => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.pellet_gun => ConstructorData.genericCodec(WeaponData.codec, "weapon")
@ -828,14 +853,14 @@ object ObjectClass {
case ObjectClass.prowler_weapon_systemB => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.pulsar => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.pulsed_particle_accelerator => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.punisher => ConstructorData.genericCodec(ConcurrentFeedWeaponData.codec, "weapon")
case ObjectClass.punisher => ConstructorData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.quadassault_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.r_shotgun => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.radiator => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.repeater => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.rocklet => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.rotarychaingun_mosquito => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.scythe => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.scythe => ConstructorData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.six_shooter => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.skyguard_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.spiker => ConstructorData.genericCodec(WeaponData.codec, "weapon")
@ -846,14 +871,14 @@ object ObjectClass {
case ObjectClass.thumper => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.thunderer_weapon_systema => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.thunderer_weapon_systemb => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.trhev_burster => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.trhev_dualcycler => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.trhev_pounder => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.trhev_burster => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
case ObjectClass.trhev_dualcycler => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
case ObjectClass.trhev_pounder => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
case ObjectClass.vanguard_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.vanu_sentry_turret_weapon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.vshev_comet => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.vshev_quasar => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.vshev_starfire => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.vshev_comet => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
case ObjectClass.vshev_quasar => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
case ObjectClass.vshev_starfire => ConstructorData.genericCodec(WeaponData.codec(-1), "weapon")
case ObjectClass.vulture_bomb_bay => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.vulture_nose_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.vulture_tail_cannon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
@ -879,8 +904,17 @@ object ObjectClass {
case ObjectClass.ace => ConstructorData.genericCodec(ACEData.codec, "ace")
case ObjectClass.advanced_ace => ConstructorData.genericCodec(CommandDetonaterData.codec, "advanced ace")
case ObjectClass.boomer_trigger => ConstructorData.genericCodec(BoomerTriggerData.codec, "boomer trigger")
//vehicles?
case ObjectClass.orbital_shuttle => ConstructorData.genericCodec(OrbitalShuttleData.codec, "HART")
//other
case ObjectClass.implant_terminal_interface => ConstructorData.genericCodec(ImplantInterfaceData.codec, "implant terminal")
case ObjectClass.ams_respawn_tube => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.implant_terminal_interface => ConstructorData.genericCodec(CommonTerminalData.codec, "implant terminal")
case ObjectClass.matrix_terminala => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.matrix_terminalb => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.matrix_terminalc => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.order_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.order_terminala => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.order_terminalb => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
//failure case
case _ => defaultFailureCodec(objClass)
}
@ -1034,7 +1068,7 @@ object ObjectClass {
case ObjectClass.hunterseeker => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.ilc9 => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.isp => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.katana => DroppedItemData.genericCodec(ConcurrentFeedWeaponData.codec, "weapon")
case ObjectClass.katana => DroppedItemData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.lancer => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.lasher => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.maelstrom => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
@ -1055,7 +1089,7 @@ object ObjectClass {
case ObjectClass.peregrine_sparrow_right => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.phoenix => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.pulsar => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.punisher => DroppedItemData.genericCodec(ConcurrentFeedWeaponData.codec, "weapon")
case ObjectClass.punisher => DroppedItemData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.r_shotgun => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.radiator => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.repeater => DroppedItemData.genericCodec(WeaponData.codec, "weapon")
@ -1112,28 +1146,58 @@ object ObjectClass {
case ObjectClass.oicw_projectile => ConstructorData.genericCodec(TrackedProjectileData.codec, "projectile")
case ObjectClass.starfire_projectile => ConstructorData.genericCodec(TrackedProjectileData.codec, "projectile")
case ObjectClass.striker_missile_targeting_projectile => ConstructorData.genericCodec(TrackedProjectileData.codec, "projectile")
//vehicles
case ObjectClass.ams => ConstructorData.genericCodec(AMSData.codec, "ams")
case ObjectClass.ams_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
case ObjectClass.ant => ConstructorData.genericCodec(ANTData.codec, "ant")
case ObjectClass.ant_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
case ObjectClass.aurora => ConstructorData.genericCodec(VehicleData.codec(2)(), "vehicle")
case ObjectClass.battlewagon => ConstructorData.genericCodec(VehicleData.codec(4)(), "vehicle")
case ObjectClass.droppod => ConstructorData.genericCodec(DroppodData.codec, "droppod")
case ObjectClass.fury => ConstructorData.genericCodec(VehicleData.codec, "vehicle")
case ObjectClass.lightning => ConstructorData.genericCodec(VehicleData.codec, "vehicle")
case ObjectClass.lightning_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
case ObjectClass.mediumtransport => ConstructorData.genericCodec(VehicleData.codec(2)(), "vehicle")
case ObjectClass.mediumtransport_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
case ObjectClass.orbital_shuttle => ConstructorData.genericCodec(OrbitalShuttleData.codec_pos, "HART")
case ObjectClass.quadassault => ConstructorData.genericCodec(VehicleData.codec, "vehicle")
case ObjectClass.quadassault_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
case ObjectClass.quadstealth => ConstructorData.genericCodec(VehicleData.codec(0)(), "vehicle")
case ObjectClass.quadstealth_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
case ObjectClass.switchblade => ConstructorData.genericCodec(Vehicle2Data.codec, "vehicle")
case ObjectClass.switchblade_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
case ObjectClass.threemanheavybuggy => ConstructorData.genericCodec(VehicleData.codec(2)(), "vehicle")
case ObjectClass.threemanheavybuggy_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
case ObjectClass.thunderer => ConstructorData.genericCodec(VehicleData.codec(2)(), "vehicle")
case ObjectClass.two_man_assault_buggy => ConstructorData.genericCodec(VehicleData.codec, "vehicle")
case ObjectClass.two_man_assault_buggy_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
case ObjectClass.twomanheavybuggy => ConstructorData.genericCodec(VehicleData.codec, "vehicle")
case ObjectClass.twomanheavybuggy_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
case ObjectClass.twomanhoverbuggy => ConstructorData.genericCodec(VehicleData.codec, "vehicle")
case ObjectClass.twomanhoverbuggy_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
//other
case ObjectClass.ams_respawn_tube => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.avatar => ConstructorData.genericCodec(CharacterData.codec, "avatar")
case ObjectClass.locker_container => ConstructorData.genericCodec(LockerContainerData.codec, "locker container")
case ObjectClass.matrix_terminala => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") //todo confirm
case ObjectClass.matrix_terminalb => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") //todo confirm
case ObjectClass.matrix_terminalc => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.order_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") //todo confirm
case ObjectClass.order_terminala => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.order_terminalb => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.ams_respawn_tube => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.capture_flag => ConstructorData.genericCodec(CaptureFlagData.codec, "capture flag")
case ObjectClass.implant_terminal_interface => DroppedItemData.genericCodec(CommonTerminalData.codec, "implant terminal")
case ObjectClass.locker_container => ConstructorData.genericCodec(LockerContainerData.codec, "locker container")
case ObjectClass.matrix_terminala => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.matrix_terminalb => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.matrix_terminalc => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.order_terminal => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.order_terminala => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.order_terminalb => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
//failure case
case _ => defaultFailureCodec(objClass)
}
/**
* `Codec` for handling a failure case upon not finding an appropriate object `Codec`.
* @param cls the object class whose `Codec` we have failed to find
* @param cls the object class whose `Codec` we have failed to find;
* @return a failure
*/
private def defaultFailureCodec(cls : Int) : Codec[ConstructorData.genericPattern] = {
conditional(false, bool).exmap[ConstructorData.genericPattern] (
conditional(cls == 0, bool).exmap[ConstructorData.genericPattern] (
{
case None | _ =>
Attempt.failure(Err(s"decoding unknown object class $cls"))

View file

@ -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
}

View file

@ -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"))

View file

@ -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.<br>
* <br>
* 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.<br>
* <br>
* 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.<br>
* <br>
* 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"))
}
)
}

View file

@ -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]
}

View file

@ -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))
)
)
}
}
}

View file

@ -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] (
{

View file

@ -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 =>

View file

@ -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) ::

View file

@ -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()
}

View file

@ -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.<br>
* <br>
* 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.<br>
* <br>
* 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.)<br>
* <br>
* 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.<br>
* <br>
* 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()
}

View file

@ -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).<br>
* <br>
* 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()
}

View file

@ -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)) ::

View file

@ -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

View file

@ -0,0 +1,531 @@
// Copyright (c) 2017 PSForever
package game
import net.psforever.packet._
import net.psforever.packet.game.{ObjectCreateMessage, _}
import net.psforever.packet.game.objectcreate.{DriveState, _}
import net.psforever.types._
import org.specs2.mutable._
import scodec.bits._
class ObjectCreateMessageVehiclesTest extends Specification {
val string_fury = hex"17 50010000 A79 9D01 FBC1C 12A83 2F06 00 00 21 4400003FC00101140C800C0E40000004048F3600301900000"
val string_ant = hex"17 C2000000 9E0 7C01 6C2D7 65535 CA16 00 00 00 4400003FC000000"
val string_lightning = hex"17 8b010000 df1 5a00 6c2d7 65535 ca16 00 00 00 4400003fc00101300ad8040c4000000408190b801018000002617402070000000"
val string_mediumtransport = hex"17 DA010000 8A2 8301 FBC1C 12A83 2F06 00 00 21 2400003FC079020593F80C2E400000040410148030190000017458050D90000001010401F814064000000"
val string_ams = hex"17 B8010000 970 3D10 002D765535CA16000000 402285BB0037E4100749E1D03000000620D83A0A00000195798741C00000332E40D84800000"
val string_ams_destroyed = hex"17 8D000000 978 3D10 002D765535CA16000000 0"
val string_switchblade = hex"17 93010000 A7B A201 FBC1C12A832F06000021 4400003FC00001013AD3180C0E4000000408330DC03019000006620406072000000"
val string_droppod = hex"17 C1000000 8110B0E00FA9000ACFFFF000000 4400007F83C0900"
val string_orbital_shuttle_1 = hex"17 82000000 0901B026904838000001FE0700"
val string_orbital_shuttle_2 = hex"17 C3000000 B02670402F5AA14F88C210000604000007F8FF03C0"
"decode (fury)" in {
PacketCoding.DecodePacket(string_fury).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 336
cls mustEqual ObjectClass.fury
guid mustEqual PlanetSideGUID(413)
parent.isDefined mustEqual false
data.isDefined mustEqual true
data.get.isInstanceOf[VehicleData] mustEqual true
val fury = data.get.asInstanceOf[VehicleData]
fury.basic.pos.coord.x mustEqual 6531.961f
fury.basic.pos.coord.y mustEqual 1872.1406f
fury.basic.pos.coord.z mustEqual 24.734375f
fury.basic.pos.roll mustEqual 0
fury.basic.pos.pitch mustEqual 0
fury.basic.pos.yaw mustEqual 33
fury.basic.pos.vel.isDefined mustEqual false
fury.basic.faction mustEqual PlanetSideEmpire.VS
fury.basic.unk mustEqual 4
fury.basic.player_guid mustEqual PlanetSideGUID(0)
fury.health mustEqual 255
//
fury.mountings.isDefined mustEqual true
fury.mountings.get.size mustEqual 1
val mounting = fury.mountings.get.head
mounting.objectClass mustEqual ObjectClass.fury_weapon_systema
mounting.guid mustEqual PlanetSideGUID(400)
mounting.parentSlot mustEqual 1
mounting.obj.isInstanceOf[WeaponData] mustEqual true
val weapon = mounting.obj.asInstanceOf[WeaponData]
weapon.unk1 mustEqual 0x6
weapon.unk2 mustEqual 0x8
weapon.fire_mode mustEqual 0
weapon.ammo.size mustEqual 1
val ammo = weapon.ammo.head
ammo.objectClass mustEqual ObjectClass.hellfire_ammo
ammo.guid mustEqual PlanetSideGUID(432)
ammo.parentSlot mustEqual 0
ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x8
case _ =>
ko
}
}
"decode (ant)" in {
PacketCoding.DecodePacket(string_ant).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 194L
cls mustEqual ObjectClass.ant
guid mustEqual PlanetSideGUID(380)
parent.isDefined mustEqual false
data.isDefined mustEqual true
data.get.isInstanceOf[ANTData] mustEqual true
val ant = data.get.asInstanceOf[ANTData]
ant.basic.pos.coord.x mustEqual 3674.8438f
ant.basic.pos.coord.y mustEqual 2726.789f
ant.basic.pos.coord.z mustEqual 91.15625f
ant.basic.pos.roll mustEqual 0
ant.basic.pos.pitch mustEqual 0
ant.basic.pos.yaw mustEqual 0
ant.basic.faction mustEqual PlanetSideEmpire.VS
ant.basic.unk mustEqual 4
ant.basic.player_guid mustEqual PlanetSideGUID(0)
ant.health mustEqual 255
ant.driveState mustEqual DriveState.Mobile
case _ =>
ko
}
}
"decode (lightning)" in {
PacketCoding.DecodePacket(string_lightning).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 395L
cls mustEqual ObjectClass.lightning
guid mustEqual PlanetSideGUID(90)
parent.isDefined mustEqual false
data.isDefined mustEqual true
data.get.isInstanceOf[VehicleData] mustEqual true
val lightning = data.get.asInstanceOf[VehicleData]
lightning.basic.pos.coord.x mustEqual 3674.8438f
lightning.basic.pos.coord.y mustEqual 2726.789f
lightning.basic.pos.coord.z mustEqual 91.15625f
lightning.basic.pos.roll mustEqual 0
lightning.basic.pos.pitch mustEqual 0
lightning.basic.pos.yaw mustEqual 0
lightning.basic.faction mustEqual PlanetSideEmpire.VS
lightning.basic.unk mustEqual 4
lightning.basic.player_guid mustEqual PlanetSideGUID(0)
lightning.health mustEqual 255
lightning.mountings.isDefined mustEqual true
lightning.mountings.get.size mustEqual 1
val mounting = lightning.mountings.get.head
mounting.objectClass mustEqual ObjectClass.lightning_weapon_system
mounting.guid mustEqual PlanetSideGUID(91)
mounting.parentSlot mustEqual 1
mounting.obj.isInstanceOf[WeaponData] mustEqual true
val weapon = mounting.obj.asInstanceOf[WeaponData]
weapon.unk1 mustEqual 0x4
weapon.unk2 mustEqual 0x8
weapon.fire_mode mustEqual 0
weapon.ammo.size mustEqual 2
//0
var ammo = weapon.ammo.head
ammo.objectClass mustEqual ObjectClass.bullet_75mm
ammo.guid mustEqual PlanetSideGUID(92)
ammo.parentSlot mustEqual 0
ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x0
//1
ammo = weapon.ammo(1)
ammo.objectClass mustEqual ObjectClass.bullet_25mm
ammo.guid mustEqual PlanetSideGUID(93)
ammo.parentSlot mustEqual 1
ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x0
case _ =>
ko
}
}
"decode (medium transport)" in {
PacketCoding.DecodePacket(string_mediumtransport).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 474L
cls mustEqual ObjectClass.mediumtransport
guid mustEqual PlanetSideGUID(387)
parent.isDefined mustEqual false
data.isDefined mustEqual true
data.get.isInstanceOf[VehicleData] mustEqual true
val deliverer = data.get.asInstanceOf[VehicleData]
deliverer.basic.pos.coord.x mustEqual 6531.961f
deliverer.basic.pos.coord.y mustEqual 1872.1406f
deliverer.basic.pos.coord.z mustEqual 24.734375f
deliverer.basic.pos.roll mustEqual 0
deliverer.basic.pos.pitch mustEqual 0
deliverer.basic.pos.yaw mustEqual 33
deliverer.basic.faction mustEqual PlanetSideEmpire.NC
deliverer.basic.unk mustEqual 4
deliverer.basic.player_guid mustEqual PlanetSideGUID(0)
deliverer.unk1 mustEqual 0
deliverer.health mustEqual 255
deliverer.unk2 mustEqual 0
deliverer.driveState mustEqual DriveState.State7
deliverer.unk4 mustEqual true
deliverer.unk5 mustEqual 0
deliverer.mountings.isDefined mustEqual true
deliverer.mountings.get.size mustEqual 2
//0
var mounting = deliverer.mountings.get.head
mounting.objectClass mustEqual ObjectClass.mediumtransport_weapon_systemA
mounting.guid mustEqual PlanetSideGUID(383)
mounting.parentSlot mustEqual 5
mounting.obj.isInstanceOf[WeaponData] mustEqual true
var weapon = mounting.obj.asInstanceOf[WeaponData]
weapon.unk1 mustEqual 0x6
weapon.unk2 mustEqual 0x8
weapon.fire_mode mustEqual 0
weapon.ammo.size mustEqual 1
var ammo = weapon.ammo.head
ammo.objectClass mustEqual ObjectClass.bullet_20mm
ammo.guid mustEqual PlanetSideGUID(420)
ammo.parentSlot mustEqual 0
ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x8
//1
mounting = deliverer.mountings.get(1)
mounting.objectClass mustEqual ObjectClass.mediumtransport_weapon_systemB
mounting.guid mustEqual PlanetSideGUID(556)
mounting.parentSlot mustEqual 6
mounting.obj.isInstanceOf[WeaponData] mustEqual true
weapon = mounting.obj.asInstanceOf[WeaponData]
weapon.unk1 mustEqual 0x6
weapon.unk2 mustEqual 0x8
weapon.fire_mode mustEqual 0
weapon.ammo.size mustEqual 1
ammo = weapon.ammo.head
ammo.objectClass mustEqual ObjectClass.bullet_20mm
ammo.guid mustEqual PlanetSideGUID(575)
ammo.parentSlot mustEqual 0
ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x8
case _ =>
ko
}
}
"decode (ams)" in {
PacketCoding.DecodePacket(string_ams).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 440L
cls mustEqual ObjectClass.ams
guid mustEqual PlanetSideGUID(4157)
parent.isDefined mustEqual false
data.isDefined mustEqual true
data.get.isInstanceOf[AMSData] mustEqual true
val ams = data.get.asInstanceOf[AMSData]
ams.basic.pos.coord.x mustEqual 3674.0f
ams.basic.pos.coord.y mustEqual 2726.789f
ams.basic.pos.coord.z mustEqual 91.15625f
ams.basic.pos.roll mustEqual 0
ams.basic.pos.pitch mustEqual 0
ams.basic.pos.yaw mustEqual 0
ams.basic.faction mustEqual PlanetSideEmpire.VS
ams.basic.unk mustEqual 0
ams.basic.player_guid mustEqual PlanetSideGUID(34082)
ams.unk1 mustEqual 2
ams.health mustEqual 236
ams.unk2 mustEqual 0
ams.driveState mustEqual DriveState.Deployed
ams.matrix_guid mustEqual PlanetSideGUID(3663)
ams.respawn_guid mustEqual PlanetSideGUID(3638)
ams.term_a_guid mustEqual PlanetSideGUID(3827)
ams.term_b_guid mustEqual PlanetSideGUID(3556)
case _ =>
ko
}
}
"decode (ams, destroyed)" in {
PacketCoding.DecodePacket(string_ams_destroyed).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 141L
cls mustEqual ObjectClass.ams_destroyed
guid mustEqual PlanetSideGUID(4157)
parent.isDefined mustEqual false
data.isDefined mustEqual true
data.get.isInstanceOf[DestroyedVehicleData] mustEqual true
val dams = data.get.asInstanceOf[DestroyedVehicleData]
dams.pos.coord.x mustEqual 3674.0f
dams.pos.coord.y mustEqual 2726.789f
dams.pos.coord.z mustEqual 91.15625f
dams.pos.roll mustEqual 0
dams.pos.pitch mustEqual 0
dams.pos.yaw mustEqual 0
case _ =>
ko
}
}
"decode (switchblade)" in {
PacketCoding.DecodePacket(string_switchblade).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 403L
cls mustEqual ObjectClass.switchblade
guid mustEqual PlanetSideGUID(418)
parent.isDefined mustEqual false
data.isDefined mustEqual true
data.get.isInstanceOf[Vehicle2Data] mustEqual true
val switchblade = data.get.asInstanceOf[Vehicle2Data]
switchblade.basic.pos.coord.x mustEqual 6531.961f
switchblade.basic.pos.coord.y mustEqual 1872.1406f
switchblade.basic.pos.coord.z mustEqual 24.734375f
switchblade.basic.pos.roll mustEqual 0
switchblade.basic.pos.pitch mustEqual 0
switchblade.basic.pos.yaw mustEqual 33
switchblade.basic.faction mustEqual PlanetSideEmpire.VS
switchblade.basic.unk mustEqual 4
switchblade.health mustEqual 255
switchblade.driveState mustEqual DriveState.Mobile
switchblade.mountings.isDefined mustEqual true
switchblade.mountings.get.size mustEqual 1
//0
val weapon = switchblade.mountings.get.head
weapon.objectClass mustEqual ObjectClass.scythe
weapon.guid mustEqual PlanetSideGUID(355)
weapon.parentSlot mustEqual 1
weapon.obj.asInstanceOf[WeaponData].unk1 mustEqual 0x6
weapon.obj.asInstanceOf[WeaponData].unk2 mustEqual 0x8
weapon.obj.asInstanceOf[WeaponData].ammo.size mustEqual 2
//ammo-0
var ammo = weapon.obj.asInstanceOf[WeaponData].ammo.head
ammo.objectClass mustEqual ObjectClass.ancient_ammo_vehicle
ammo.guid mustEqual PlanetSideGUID(366)
ammo.parentSlot mustEqual 0
ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x8
//ammo-1
ammo = weapon.obj.asInstanceOf[WeaponData].ammo(1)
ammo.objectClass mustEqual ObjectClass.ancient_ammo_vehicle
ammo.guid mustEqual PlanetSideGUID(385)
ammo.parentSlot mustEqual 1
ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x8
case _ =>
ko
}
}
"decode (droppod)" in {
PacketCoding.DecodePacket(string_droppod).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 193L
cls mustEqual ObjectClass.droppod
guid mustEqual PlanetSideGUID(3595)
parent.isDefined mustEqual false
data.isDefined mustEqual true
data.get.isInstanceOf[DroppodData] mustEqual true
val droppod = data.get.asInstanceOf[DroppodData]
droppod.basic.pos.coord.x mustEqual 5108.0f
droppod.basic.pos.coord.y mustEqual 6164.0f
droppod.basic.pos.coord.z mustEqual 1023.9844f
droppod.basic.pos.roll mustEqual 0
droppod.basic.pos.pitch mustEqual 0
droppod.basic.pos.yaw mustEqual 0
droppod.basic.unk mustEqual 4
droppod.basic.player_guid mustEqual PlanetSideGUID(0)
droppod.burn mustEqual false
droppod.health mustEqual 255
case _ =>
ko
}
}
"decode (shuttle 1)" in {
PacketCoding.DecodePacket(string_orbital_shuttle_1).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 130
cls mustEqual ObjectClass.orbital_shuttle
guid mustEqual PlanetSideGUID(1129)
parent.isDefined mustEqual true
parent.get.guid mustEqual PlanetSideGUID(786)
parent.get.slot mustEqual 3
data.isDefined mustEqual true
data.get.isInstanceOf[OrbitalShuttleData] mustEqual true
data.get.asInstanceOf[OrbitalShuttleData].faction mustEqual PlanetSideEmpire.VS
data.get.asInstanceOf[OrbitalShuttleData].pos.isDefined mustEqual false
case _ =>
ko
}
}
"decode (shuttle 2)" in {
PacketCoding.DecodePacket(string_orbital_shuttle_2).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 195
cls mustEqual ObjectClass.orbital_shuttle
guid mustEqual PlanetSideGUID(1127)
parent.isDefined mustEqual false
data.isDefined mustEqual true
data.get.isInstanceOf[OrbitalShuttleData] mustEqual true
val shuttle = data.get.asInstanceOf[OrbitalShuttleData]
shuttle.faction mustEqual PlanetSideEmpire.VS
shuttle.pos.isDefined mustEqual true
shuttle.pos.get.coord.x mustEqual 5610.0156f
shuttle.pos.get.coord.y mustEqual 4255.258f
shuttle.pos.get.coord.z mustEqual 134.1875f
shuttle.pos.get.roll mustEqual 0
shuttle.pos.get.pitch mustEqual 0
shuttle.pos.get.yaw mustEqual 96
case _ =>
ko
}
}
"encode (fury)" in {
val obj = VehicleData(
CommonFieldData(
PlacementData(6531.961f, 1872.1406f, 24.734375f, 0, 0, 33),
PlanetSideEmpire.VS, 4
),
255,
MountItem(ObjectClass.fury_weapon_systema, PlanetSideGUID(400), 1,
WeaponData(0x6, 0x8, 0, ObjectClass.hellfire_ammo, PlanetSideGUID(432), 0, AmmoBoxData(0x8))
)
)
val msg = ObjectCreateMessage(ObjectClass.fury, PlanetSideGUID(413), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_fury
}
"encode (ant)" in {
val obj = ANTData(
CommonFieldData(
PlacementData(3674.8438f, 2726.789f, 91.15625f),
PlanetSideEmpire.VS, 4
),
255,
DriveState.Mobile
)
val msg = ObjectCreateMessage(ObjectClass.ant, PlanetSideGUID(380), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_ant
}
"encode (lightning)" in {
val obj = VehicleData(
CommonFieldData(
PlacementData(3674.8438f, 2726.789f, 91.15625f),
PlanetSideEmpire.VS, 4
),
255,
MountItem(ObjectClass.lightning_weapon_system, PlanetSideGUID(91), 1,
WeaponData(4, 8, 0, ObjectClass.bullet_75mm, PlanetSideGUID(92), 0, AmmoBoxData(), ObjectClass.bullet_25mm, PlanetSideGUID(93), 1, AmmoBoxData())
)
)
val msg = ObjectCreateMessage(ObjectClass.lightning, PlanetSideGUID(90), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_lightning
}
"encode (deliverer)" in {
val obj = VehicleData(
CommonFieldData(
PlacementData(6531.961f, 1872.1406f, 24.734375f, 0, 0, 33),
PlanetSideEmpire.NC, 4
),
0,
255,
0,
DriveState.State7,
true,
0,
Some(
MountItem(
ObjectClass.mediumtransport_weapon_systemA, PlanetSideGUID(383), 5,
WeaponData(6, 8, ObjectClass.bullet_20mm, PlanetSideGUID(420), 0, AmmoBoxData(8))
) ::
MountItem(
ObjectClass.mediumtransport_weapon_systemB, PlanetSideGUID(556), 6,
WeaponData(6, 8, ObjectClass.bullet_20mm, PlanetSideGUID(575), 0, AmmoBoxData(8))
) ::
Nil
)
)(2)
val msg = ObjectCreateMessage(ObjectClass.mediumtransport, PlanetSideGUID(387), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_mediumtransport
}
"encode (ams)" in {
val obj = AMSData(
CommonFieldData(PlacementData(3674.0f, 2726.789f, 91.15625f, 0, 0, 0),
PlanetSideEmpire.VS, 0,
PlanetSideGUID(34082)
),
2,
236,
0,
DriveState.Deployed,
63,
PlanetSideGUID(3663),
PlanetSideGUID(3638),
PlanetSideGUID(3827),
PlanetSideGUID(3556)
)
val msg = ObjectCreateMessage(ObjectClass.ams, PlanetSideGUID(4157), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_ams
}
"encode (ams, destroyed)" in {
val obj = DestroyedVehicleData(PlacementData(3674.0f, 2726.789f, 91.15625f))
val msg = ObjectCreateMessage(ObjectClass.ams_destroyed, PlanetSideGUID(4157), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_ams_destroyed
}
"encode (switchblade(" in {
val obj = Vehicle2Data(
CommonFieldData(PlacementData(6531.961f, 1872.1406f, 24.734375f ,0 ,0 ,33),
PlanetSideEmpire.VS, 4
),
255,
DriveState.Mobile,
MountItem(ObjectClass.scythe, PlanetSideGUID(355), 1,
WeaponData(0x6, 0x8, 0, ObjectClass.ancient_ammo_vehicle, PlanetSideGUID(366), 0, AmmoBoxData(0x8), ObjectClass.ancient_ammo_vehicle, PlanetSideGUID(385), 1, AmmoBoxData(0x8))
)
)
val msg = ObjectCreateMessage(ObjectClass.switchblade, PlanetSideGUID(418), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_switchblade
}
"encode (droppod)" in {
val obj = DroppodData(
CommonFieldData(
PlacementData(5108.0f, 6164.0f, 1023.9844f),
PlanetSideEmpire.VS, 4
)
)
val msg = ObjectCreateMessage(ObjectClass.droppod, PlanetSideGUID(3595), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_droppod
}
"encode (shuttle 1)" in {
val obj = OrbitalShuttleData(PlanetSideEmpire.VS)
val msg = ObjectCreateMessage(ObjectClass.orbital_shuttle, PlanetSideGUID(1129), ObjectCreateMessageParent(PlanetSideGUID(786), 3), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_orbital_shuttle_1
}
"encode (shuttle 2)" in {
val obj = OrbitalShuttleData(PlacementData(5610.0156f, 4255.258f, 134.1875f, 0, 0, 96), PlanetSideEmpire.VS)
val msg = ObjectCreateMessage(ObjectClass.orbital_shuttle, PlanetSideGUID(1127), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_orbital_shuttle_2
}
}

View file

@ -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
}
}

View file

@ -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)