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