mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-20 02:24:45 +00:00
Merge pull request #151 from Fate-JH/object-create-updates
Object Create Message Update #2.5
This commit is contained in:
commit
986594de87
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -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._
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -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] (
|
||||
|
|
|
|||
|
|
@ -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] (
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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) ::
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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] (
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 =>
|
||||
|
|
|
|||
|
|
@ -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) ::
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,12 +96,13 @@ class ObjectCreateDetailedMessageTest extends Specification {
|
|||
parent.get.slot mustEqual 2
|
||||
data.isDefined mustEqual true
|
||||
val obj_wep = data.get.asInstanceOf[DetailedWeaponData]
|
||||
obj_wep.unk mustEqual 4
|
||||
obj_wep.unk1 mustEqual 2
|
||||
obj_wep.unk2 mustEqual 8
|
||||
val obj_ammo = obj_wep.ammo
|
||||
obj_ammo.objectClass mustEqual 28
|
||||
obj_ammo.guid mustEqual PlanetSideGUID(1286)
|
||||
obj_ammo.parentSlot mustEqual 0
|
||||
obj_ammo.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 30
|
||||
obj_ammo.head.objectClass mustEqual 28
|
||||
obj_ammo.head.guid mustEqual PlanetSideGUID(1286)
|
||||
obj_ammo.head.parentSlot mustEqual 0
|
||||
obj_ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 30
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -117,7 +118,7 @@ class ObjectCreateDetailedMessageTest extends Specification {
|
|||
parent.get.guid mustEqual PlanetSideGUID(75)
|
||||
parent.get.slot mustEqual 2
|
||||
data.isDefined mustEqual true
|
||||
val obj_wep = data.get.asInstanceOf[DetailedConcurrentFeedWeaponData]
|
||||
val obj_wep = data.get.asInstanceOf[DetailedWeaponData]
|
||||
obj_wep.unk1 mustEqual 0
|
||||
obj_wep.unk2 mustEqual 8
|
||||
val obj_ammo = obj_wep.ammo
|
||||
|
|
@ -229,66 +230,66 @@ class ObjectCreateDetailedMessageTest extends Specification {
|
|||
val inventory = char.inventory.get.contents
|
||||
inventory.size mustEqual 10
|
||||
//0
|
||||
inventory.head.item.objectClass mustEqual ObjectClass.beamer
|
||||
inventory.head.item.guid mustEqual PlanetSideGUID(76)
|
||||
inventory.head.item.parentSlot mustEqual 0
|
||||
var wep = inventory.head.item.obj.asInstanceOf[DetailedWeaponData]
|
||||
wep.ammo.objectClass mustEqual ObjectClass.energy_cell
|
||||
wep.ammo.guid mustEqual PlanetSideGUID(77)
|
||||
wep.ammo.parentSlot mustEqual 0
|
||||
wep.ammo.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 16
|
||||
inventory.head.objectClass mustEqual ObjectClass.beamer
|
||||
inventory.head.guid mustEqual PlanetSideGUID(76)
|
||||
inventory.head.parentSlot mustEqual 0
|
||||
var wep = inventory.head.obj.asInstanceOf[DetailedWeaponData]
|
||||
wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell
|
||||
wep.ammo.head.guid mustEqual PlanetSideGUID(77)
|
||||
wep.ammo.head.parentSlot mustEqual 0
|
||||
wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 16
|
||||
//1
|
||||
inventory(1).item.objectClass mustEqual ObjectClass.suppressor
|
||||
inventory(1).item.guid mustEqual PlanetSideGUID(78)
|
||||
inventory(1).item.parentSlot mustEqual 2
|
||||
wep = inventory(1).item.obj.asInstanceOf[DetailedWeaponData]
|
||||
wep.ammo.objectClass mustEqual ObjectClass.bullet_9mm
|
||||
wep.ammo.guid mustEqual PlanetSideGUID(79)
|
||||
wep.ammo.parentSlot mustEqual 0
|
||||
wep.ammo.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 25
|
||||
inventory(1).objectClass mustEqual ObjectClass.suppressor
|
||||
inventory(1).guid mustEqual PlanetSideGUID(78)
|
||||
inventory(1).parentSlot mustEqual 2
|
||||
wep = inventory(1).obj.asInstanceOf[DetailedWeaponData]
|
||||
wep.ammo.head.objectClass mustEqual ObjectClass.bullet_9mm
|
||||
wep.ammo.head.guid mustEqual PlanetSideGUID(79)
|
||||
wep.ammo.head.parentSlot mustEqual 0
|
||||
wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 25
|
||||
//2
|
||||
inventory(2).item.objectClass mustEqual ObjectClass.forceblade
|
||||
inventory(2).item.guid mustEqual PlanetSideGUID(80)
|
||||
inventory(2).item.parentSlot mustEqual 4
|
||||
wep = inventory(2).item.obj.asInstanceOf[DetailedWeaponData]
|
||||
wep.ammo.objectClass mustEqual ObjectClass.melee_ammo
|
||||
wep.ammo.guid mustEqual PlanetSideGUID(81)
|
||||
wep.ammo.parentSlot mustEqual 0
|
||||
wep.ammo.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1
|
||||
inventory(2).objectClass mustEqual ObjectClass.forceblade
|
||||
inventory(2).guid mustEqual PlanetSideGUID(80)
|
||||
inventory(2).parentSlot mustEqual 4
|
||||
wep = inventory(2).obj.asInstanceOf[DetailedWeaponData]
|
||||
wep.ammo.head.objectClass mustEqual ObjectClass.melee_ammo
|
||||
wep.ammo.head.guid mustEqual PlanetSideGUID(81)
|
||||
wep.ammo.head.parentSlot mustEqual 0
|
||||
wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1
|
||||
//3
|
||||
inventory(3).item.objectClass mustEqual ObjectClass.locker_container
|
||||
inventory(3).item.guid mustEqual PlanetSideGUID(82)
|
||||
inventory(3).item.parentSlot mustEqual 5
|
||||
inventory(3).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1
|
||||
inventory(3).objectClass mustEqual ObjectClass.locker_container
|
||||
inventory(3).guid mustEqual PlanetSideGUID(82)
|
||||
inventory(3).parentSlot mustEqual 5
|
||||
inventory(3).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1
|
||||
//4
|
||||
inventory(4).item.objectClass mustEqual ObjectClass.bullet_9mm
|
||||
inventory(4).item.guid mustEqual PlanetSideGUID(83)
|
||||
inventory(4).item.parentSlot mustEqual 6
|
||||
inventory(4).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
|
||||
inventory(4).objectClass mustEqual ObjectClass.bullet_9mm
|
||||
inventory(4).guid mustEqual PlanetSideGUID(83)
|
||||
inventory(4).parentSlot mustEqual 6
|
||||
inventory(4).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
|
||||
//5
|
||||
inventory(5).item.objectClass mustEqual ObjectClass.bullet_9mm
|
||||
inventory(5).item.guid mustEqual PlanetSideGUID(84)
|
||||
inventory(5).item.parentSlot mustEqual 9
|
||||
inventory(5).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
|
||||
inventory(5).objectClass mustEqual ObjectClass.bullet_9mm
|
||||
inventory(5).guid mustEqual PlanetSideGUID(84)
|
||||
inventory(5).parentSlot mustEqual 9
|
||||
inventory(5).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
|
||||
//6
|
||||
inventory(6).item.objectClass mustEqual ObjectClass.bullet_9mm
|
||||
inventory(6).item.guid mustEqual PlanetSideGUID(85)
|
||||
inventory(6).item.parentSlot mustEqual 12
|
||||
inventory(6).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
|
||||
inventory(6).objectClass mustEqual ObjectClass.bullet_9mm
|
||||
inventory(6).guid mustEqual PlanetSideGUID(85)
|
||||
inventory(6).parentSlot mustEqual 12
|
||||
inventory(6).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
|
||||
//7
|
||||
inventory(7).item.objectClass mustEqual ObjectClass.bullet_9mm_AP
|
||||
inventory(7).item.guid mustEqual PlanetSideGUID(86)
|
||||
inventory(7).item.parentSlot mustEqual 33
|
||||
inventory(7).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
|
||||
inventory(7).objectClass mustEqual ObjectClass.bullet_9mm_AP
|
||||
inventory(7).guid mustEqual PlanetSideGUID(86)
|
||||
inventory(7).parentSlot mustEqual 33
|
||||
inventory(7).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
|
||||
//8
|
||||
inventory(8).item.objectClass mustEqual ObjectClass.energy_cell
|
||||
inventory(8).item.guid mustEqual PlanetSideGUID(87)
|
||||
inventory(8).item.parentSlot mustEqual 36
|
||||
inventory(8).item.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
|
||||
inventory(8).objectClass mustEqual ObjectClass.energy_cell
|
||||
inventory(8).guid mustEqual PlanetSideGUID(87)
|
||||
inventory(8).parentSlot mustEqual 36
|
||||
inventory(8).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
|
||||
//9
|
||||
inventory(9).item.objectClass mustEqual ObjectClass.remote_electronics_kit
|
||||
inventory(9).item.guid mustEqual PlanetSideGUID(88)
|
||||
inventory(9).item.parentSlot mustEqual 39
|
||||
inventory(9).objectClass mustEqual ObjectClass.remote_electronics_kit
|
||||
inventory(9).guid mustEqual PlanetSideGUID(88)
|
||||
inventory(9).parentSlot mustEqual 39
|
||||
//the rek has data but none worth testing here
|
||||
char.drawn_slot mustEqual DrawnSlot.Pistol1
|
||||
case _ =>
|
||||
|
|
@ -327,7 +328,7 @@ class ObjectCreateDetailedMessageTest extends Specification {
|
|||
}
|
||||
|
||||
"encode (gauss)" in {
|
||||
val obj = DetailedWeaponData(4, ObjectClass.bullet_9mm, PlanetSideGUID(1286), 0, DetailedAmmoBoxData(8, 30))
|
||||
val obj = DetailedWeaponData(2, 8, ObjectClass.bullet_9mm, PlanetSideGUID(1286), 0, DetailedAmmoBoxData(8, 30))
|
||||
val msg = ObjectCreateDetailedMessage(ObjectClass.gauss, PlanetSideGUID(1465), ObjectCreateMessageParent(PlanetSideGUID(75), 2), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
|
|
@ -335,7 +336,11 @@ class ObjectCreateDetailedMessageTest extends Specification {
|
|||
}
|
||||
|
||||
"encode (punisher)" in {
|
||||
val obj = DetailedConcurrentFeedWeaponData(0, 8, DetailedAmmoBoxData(ObjectClass.bullet_9mm, PlanetSideGUID(1693), 0, DetailedAmmoBoxData(8, 30)) :: DetailedAmmoBoxData(ObjectClass.jammer_cartridge, PlanetSideGUID(1564), 1, DetailedAmmoBoxData(8, 1)) :: Nil)
|
||||
val obj = DetailedWeaponData(0, 8,
|
||||
DetailedAmmoBoxData(ObjectClass.bullet_9mm, PlanetSideGUID(1693), 0, DetailedAmmoBoxData(8, 30)) ::
|
||||
DetailedAmmoBoxData(ObjectClass.jammer_cartridge, PlanetSideGUID(1564), 1, DetailedAmmoBoxData(8, 1)) ::
|
||||
Nil
|
||||
)(2)
|
||||
val msg = ObjectCreateDetailedMessage(ObjectClass.punisher, PlanetSideGUID(1703), ObjectCreateMessageParent(PlanetSideGUID(75), 2), obj)
|
||||
var pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
|
|
@ -387,9 +392,9 @@ class ObjectCreateDetailedMessageTest extends Specification {
|
|||
false,
|
||||
RibbonBars()
|
||||
)
|
||||
val inv = InventoryItem(ObjectClass.beamer, PlanetSideGUID(76), 0, DetailedWeaponData(8, ObjectClass.energy_cell, PlanetSideGUID(77), 0, DetailedAmmoBoxData(8, 16))) ::
|
||||
InventoryItem(ObjectClass.suppressor, PlanetSideGUID(78), 2, DetailedWeaponData(8, ObjectClass.bullet_9mm, PlanetSideGUID(79), 0, DetailedAmmoBoxData(8, 25))) ::
|
||||
InventoryItem(ObjectClass.forceblade, PlanetSideGUID(80), 4, DetailedWeaponData(8, ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1))) ::
|
||||
val inv = InventoryItem(ObjectClass.beamer, PlanetSideGUID(76), 0, DetailedWeaponData(4, 8, ObjectClass.energy_cell, PlanetSideGUID(77), 0, DetailedAmmoBoxData(8, 16))) ::
|
||||
InventoryItem(ObjectClass.suppressor, PlanetSideGUID(78), 2, DetailedWeaponData(4, 8, ObjectClass.bullet_9mm, PlanetSideGUID(79), 0, DetailedAmmoBoxData(8, 25))) ::
|
||||
InventoryItem(ObjectClass.forceblade, PlanetSideGUID(80), 4, DetailedWeaponData(4, 8, ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1))) ::
|
||||
InventoryItem(ObjectClass.locker_container, PlanetSideGUID(82), 5, DetailedAmmoBoxData(8, 1)) ::
|
||||
InventoryItem(ObjectClass.bullet_9mm, PlanetSideGUID(83), 6, DetailedAmmoBoxData(8, 50)) ::
|
||||
InventoryItem(ObjectClass.bullet_9mm, PlanetSideGUID(84), 9, DetailedAmmoBoxData(8, 50)) ::
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
package game
|
||||
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.packet.game.{ObjectCreateMessage, _}
|
||||
import net.psforever.packet.game.objectcreate._
|
||||
import net.psforever.types._
|
||||
import org.specs2.mutable._
|
||||
|
|
@ -35,7 +35,7 @@ class ObjectCreateMessageTest extends Specification {
|
|||
val string_character = hex"17 73070000 BC8 3E0F 6C2D7 65535 CA16 00 00 09 9741E4F804000000 234530063007200610077006E00790052006F006E006E0069006500 220B7 E67B540404001000000000022B50100 268042006C00610063006B002000420065007200650074002000410072006D006F007500720065006400200043006F00720070007300 1700E0030050040003BC00000234040001A004000 3FFF67A8F A0A5424E0E800000000080952A9C3A03000001081103E040000000A023782F1080C0000016244108200000000808382403A030000014284C3A0C0000000202512F00B80C00000578F80F840000000280838B3C320300000080"
|
||||
val string_character_backpack = hex"17 9C030000 BC8 340D F20A9 3956C AF0D 00 00 73 480000 87041006E00670065006C006C006F00 4A148 0000000000000000000000005C54200 24404F0072006900670069006E0061006C00200044006900730074007200690063007400 1740180181E8000000C202000042000000D202000000010A3C00"
|
||||
|
||||
"deocde (striker projectile)" in {
|
||||
"decode (striker projectile)" in {
|
||||
PacketCoding.DecodePacket(string_striker_projectile).require match {
|
||||
case ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
len mustEqual 197
|
||||
|
|
@ -68,7 +68,8 @@ class ObjectCreateMessageTest extends Specification {
|
|||
parent.get.guid mustEqual PlanetSideGUID(514)
|
||||
parent.get.slot mustEqual 1
|
||||
data.isDefined mustEqual true
|
||||
data.get.isInstanceOf[ImplantInterfaceData] mustEqual true
|
||||
data.get.isInstanceOf[CommonTerminalData] mustEqual true
|
||||
data.get.asInstanceOf[CommonTerminalData].faction mustEqual PlanetSideEmpire.VS
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -82,14 +83,18 @@ class ObjectCreateMessageTest extends Specification {
|
|||
guid mustEqual PlanetSideGUID(3827)
|
||||
parent.isDefined mustEqual false
|
||||
data.isDefined mustEqual true
|
||||
val term = data.get.asInstanceOf[CommonTerminalData]
|
||||
term.pos.coord.x mustEqual 4579.3438f
|
||||
term.pos.coord.y mustEqual 5615.0703f
|
||||
term.pos.coord.z mustEqual 72.953125f
|
||||
term.pos.pitch mustEqual 0
|
||||
term.pos.roll mustEqual 0
|
||||
term.pos.yaw mustEqual 125
|
||||
ok
|
||||
data.get.isInstanceOf[DroppedItemData[_]] mustEqual true
|
||||
val drop = data.get.asInstanceOf[DroppedItemData[_]]
|
||||
drop.pos.coord.x mustEqual 4579.3438f
|
||||
drop.pos.coord.y mustEqual 5615.0703f
|
||||
drop.pos.coord.z mustEqual 72.953125f
|
||||
drop.pos.pitch mustEqual 0
|
||||
drop.pos.roll mustEqual 0
|
||||
drop.pos.yaw mustEqual 125
|
||||
drop.obj.isInstanceOf[CommonTerminalData] mustEqual true
|
||||
val term = drop.obj.asInstanceOf[CommonTerminalData]
|
||||
term.faction mustEqual PlanetSideEmpire.NC
|
||||
term.unk mustEqual 0
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -163,14 +168,14 @@ class ObjectCreateMessageTest extends Specification {
|
|||
data.isDefined mustEqual true
|
||||
data.get.isInstanceOf[WeaponData] mustEqual true
|
||||
val wep = data.get.asInstanceOf[WeaponData]
|
||||
wep.unk1 mustEqual 8
|
||||
wep.unk1 mustEqual 4
|
||||
wep.unk2 mustEqual 8
|
||||
wep.fire_mode mustEqual 0
|
||||
wep.ammo.objectClass mustEqual ObjectClass.energy_cell
|
||||
wep.ammo.guid mustEqual PlanetSideGUID(3548)
|
||||
wep.ammo.parentSlot mustEqual 0
|
||||
wep.ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
|
||||
val ammo = wep.ammo.obj.asInstanceOf[AmmoBoxData]
|
||||
wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell
|
||||
wep.ammo.head.guid mustEqual PlanetSideGUID(3548)
|
||||
wep.ammo.head.parentSlot mustEqual 0
|
||||
wep.ammo.head.obj.isInstanceOf[AmmoBoxData] mustEqual true
|
||||
val ammo = wep.ammo.head.obj.asInstanceOf[AmmoBoxData]
|
||||
ammo.unk mustEqual 8
|
||||
case _ =>
|
||||
ko
|
||||
|
|
@ -187,9 +192,9 @@ class ObjectCreateMessageTest extends Specification {
|
|||
parent.get.guid mustEqual PlanetSideGUID(3092)
|
||||
parent.get.slot mustEqual 3
|
||||
data.isDefined mustEqual true
|
||||
data.get.isInstanceOf[ConcurrentFeedWeaponData] mustEqual true
|
||||
val wep = data.get.asInstanceOf[ConcurrentFeedWeaponData]
|
||||
wep.unk1 mustEqual 8
|
||||
data.get.isInstanceOf[WeaponData] mustEqual true
|
||||
val wep = data.get.asInstanceOf[WeaponData]
|
||||
wep.unk1 mustEqual 4
|
||||
wep.unk2 mustEqual 8
|
||||
wep.fire_mode mustEqual 0
|
||||
val ammo = wep.ammo
|
||||
|
|
@ -345,14 +350,14 @@ class ObjectCreateMessageTest extends Specification {
|
|||
drop.pos.yaw mustEqual 32
|
||||
drop.obj.isInstanceOf[WeaponData] mustEqual true
|
||||
val wep = drop.obj.asInstanceOf[WeaponData]
|
||||
wep.unk1 mustEqual 8
|
||||
wep.unk1 mustEqual 4
|
||||
wep.unk2 mustEqual 0
|
||||
wep.fire_mode mustEqual 0
|
||||
wep.ammo.objectClass mustEqual ObjectClass.energy_cell
|
||||
wep.ammo.guid mustEqual PlanetSideGUID(3268)
|
||||
wep.ammo.parentSlot mustEqual 0
|
||||
wep.ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
|
||||
val ammo = wep.ammo.obj.asInstanceOf[AmmoBoxData]
|
||||
wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell
|
||||
wep.ammo.head.guid mustEqual PlanetSideGUID(3268)
|
||||
wep.ammo.head.parentSlot mustEqual 0
|
||||
wep.ammo.head.obj.isInstanceOf[AmmoBoxData] mustEqual true
|
||||
val ammo = wep.ammo.head.obj.asInstanceOf[AmmoBoxData]
|
||||
ammo.unk mustEqual 0
|
||||
case _ =>
|
||||
ko
|
||||
|
|
@ -375,9 +380,9 @@ class ObjectCreateMessageTest extends Specification {
|
|||
drop.pos.roll mustEqual 0
|
||||
drop.pos.pitch mustEqual 0
|
||||
drop.pos.yaw mustEqual 51
|
||||
drop.obj.isInstanceOf[ConcurrentFeedWeaponData] mustEqual true
|
||||
val wep = drop.obj.asInstanceOf[ConcurrentFeedWeaponData]
|
||||
wep.unk1 mustEqual 4
|
||||
drop.obj.isInstanceOf[WeaponData] mustEqual true
|
||||
val wep = drop.obj.asInstanceOf[WeaponData]
|
||||
wep.unk1 mustEqual 2
|
||||
wep.unk2 mustEqual 0
|
||||
wep.fire_mode mustEqual 0
|
||||
val ammo = wep.ammo
|
||||
|
|
@ -464,7 +469,8 @@ class ObjectCreateMessageTest extends Specification {
|
|||
turret.deploy.pos.roll mustEqual 0
|
||||
turret.deploy.pos.pitch mustEqual 127
|
||||
turret.deploy.pos.yaw mustEqual 66
|
||||
turret.deploy.unk mustEqual 44
|
||||
turret.deploy.faction mustEqual PlanetSideEmpire.NC
|
||||
turret.deploy.unk mustEqual 12
|
||||
turret.deploy.player_guid mustEqual PlanetSideGUID(3871)
|
||||
turret.health mustEqual 0
|
||||
turret.internals.isDefined mustEqual false
|
||||
|
|
@ -489,7 +495,8 @@ class ObjectCreateMessageTest extends Specification {
|
|||
turret.deploy.pos.roll mustEqual 0
|
||||
turret.deploy.pos.pitch mustEqual 0
|
||||
turret.deploy.pos.yaw mustEqual 105
|
||||
turret.deploy.unk mustEqual 68
|
||||
turret.deploy.faction mustEqual PlanetSideEmpire.VS
|
||||
turret.deploy.unk mustEqual 4
|
||||
turret.deploy.player_guid mustEqual PlanetSideGUID(4232)
|
||||
turret.health mustEqual 255
|
||||
turret.internals.isDefined mustEqual true
|
||||
|
|
@ -499,10 +506,10 @@ class ObjectCreateMessageTest extends Specification {
|
|||
internals.parentSlot mustEqual 0
|
||||
internals.obj.isInstanceOf[WeaponData] mustEqual true
|
||||
val wep = internals.obj.asInstanceOf[WeaponData]
|
||||
wep.unk1 mustEqual 0xC
|
||||
wep.unk1 mustEqual 0x6
|
||||
wep.unk2 mustEqual 0x8
|
||||
wep.fire_mode mustEqual 0
|
||||
val ammo = wep.ammo
|
||||
val ammo = wep.ammo.head
|
||||
ammo.objectClass mustEqual ObjectClass.spitfire_ammo
|
||||
ammo.guid mustEqual PlanetSideGUID(3694)
|
||||
ammo.parentSlot mustEqual 0
|
||||
|
|
@ -529,7 +536,8 @@ class ObjectCreateMessageTest extends Specification {
|
|||
trap.deploy.pos.roll mustEqual 0
|
||||
trap.deploy.pos.pitch mustEqual 0
|
||||
trap.deploy.pos.yaw mustEqual 0
|
||||
trap.deploy.unk mustEqual 68
|
||||
trap.deploy.faction mustEqual PlanetSideEmpire.VS
|
||||
trap.deploy.unk mustEqual 4
|
||||
trap.health mustEqual 255
|
||||
trap.deploy.player_guid mustEqual PlanetSideGUID(2502)
|
||||
case _ =>
|
||||
|
|
@ -553,7 +561,8 @@ class ObjectCreateMessageTest extends Specification {
|
|||
aegis.deploy.pos.roll mustEqual 0
|
||||
aegis.deploy.pos.pitch mustEqual 0
|
||||
aegis.deploy.pos.yaw mustEqual 0
|
||||
aegis.deploy.unk mustEqual 68
|
||||
aegis.deploy.faction mustEqual PlanetSideEmpire.VS
|
||||
aegis.deploy.unk mustEqual 4
|
||||
aegis.health mustEqual 255
|
||||
aegis.deploy.player_guid mustEqual PlanetSideGUID(2366)
|
||||
case _ =>
|
||||
|
|
@ -577,9 +586,9 @@ class ObjectCreateMessageTest extends Specification {
|
|||
omft.deploy.pos.roll mustEqual 0
|
||||
omft.deploy.pos.pitch mustEqual 0
|
||||
omft.deploy.pos.yaw mustEqual 94
|
||||
omft.deploy.unk mustEqual 68
|
||||
omft.deploy.player_guid mustEqual PlanetSideGUID(0)
|
||||
omft.player_guid mustEqual PlanetSideGUID(2502)
|
||||
omft.deploy.faction mustEqual PlanetSideEmpire.VS
|
||||
omft.deploy.unk mustEqual 4
|
||||
omft.deploy.player_guid mustEqual PlanetSideGUID(2502)
|
||||
omft.health mustEqual 255
|
||||
omft.internals.isDefined mustEqual true
|
||||
val internals = omft.internals.get
|
||||
|
|
@ -588,10 +597,10 @@ class ObjectCreateMessageTest extends Specification {
|
|||
internals.parentSlot mustEqual 1
|
||||
internals.obj.isInstanceOf[WeaponData] mustEqual true
|
||||
val wep = internals.obj.asInstanceOf[WeaponData]
|
||||
wep.unk1 mustEqual 0xC
|
||||
wep.unk1 mustEqual 0x6
|
||||
wep.unk2 mustEqual 0x8
|
||||
wep.fire_mode mustEqual 0
|
||||
val ammo = wep.ammo
|
||||
val ammo = wep.ammo.head
|
||||
ammo.objectClass mustEqual ObjectClass.energy_gun_ammo
|
||||
ammo.guid mustEqual PlanetSideGUID(2510)
|
||||
ammo.parentSlot mustEqual 0
|
||||
|
|
@ -612,35 +621,33 @@ class ObjectCreateMessageTest extends Specification {
|
|||
data.isDefined mustEqual true
|
||||
data.get.isInstanceOf[LockerContainerData] mustEqual true
|
||||
val locker = data.get.asInstanceOf[LockerContainerData]
|
||||
locker.inventory.unk1 mustEqual false
|
||||
locker.inventory.unk2 mustEqual false
|
||||
val contents = locker.inventory.contents
|
||||
contents.size mustEqual 3
|
||||
//0
|
||||
contents.head.item.objectClass mustEqual ObjectClass.nano_dispenser
|
||||
contents.head.item.guid mustEqual PlanetSideGUID(2935)
|
||||
contents.head.item.parentSlot mustEqual 0
|
||||
contents.head.item.obj.isInstanceOf[WeaponData] mustEqual true
|
||||
val dispenser = contents.head.item.obj.asInstanceOf[WeaponData]
|
||||
dispenser.unk1 mustEqual 0xC
|
||||
contents.head.objectClass mustEqual ObjectClass.nano_dispenser
|
||||
contents.head.guid mustEqual PlanetSideGUID(2935)
|
||||
contents.head.parentSlot mustEqual 0
|
||||
contents.head.obj.isInstanceOf[WeaponData] mustEqual true
|
||||
val dispenser = contents.head.obj.asInstanceOf[WeaponData]
|
||||
dispenser.unk1 mustEqual 0x6
|
||||
dispenser.unk2 mustEqual 0x0
|
||||
dispenser.ammo.objectClass mustEqual ObjectClass.armor_canister
|
||||
dispenser.ammo.guid mustEqual PlanetSideGUID(3426)
|
||||
dispenser.ammo.parentSlot mustEqual 0
|
||||
dispenser.ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
|
||||
dispenser.ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0
|
||||
dispenser.ammo.head.objectClass mustEqual ObjectClass.armor_canister
|
||||
dispenser.ammo.head.guid mustEqual PlanetSideGUID(3426)
|
||||
dispenser.ammo.head.parentSlot mustEqual 0
|
||||
dispenser.ammo.head.obj.isInstanceOf[AmmoBoxData] mustEqual true
|
||||
dispenser.ammo.head.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0
|
||||
//1
|
||||
contents(1).item.objectClass mustEqual ObjectClass.armor_canister
|
||||
contents(1).item.guid mustEqual PlanetSideGUID(4090)
|
||||
contents(1).item.parentSlot mustEqual 45
|
||||
contents(1).item.obj.isInstanceOf[AmmoBoxData] mustEqual true
|
||||
contents(1).item.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0
|
||||
contents(1).objectClass mustEqual ObjectClass.armor_canister
|
||||
contents(1).guid mustEqual PlanetSideGUID(4090)
|
||||
contents(1).parentSlot mustEqual 45
|
||||
contents(1).obj.isInstanceOf[AmmoBoxData] mustEqual true
|
||||
contents(1).obj.asInstanceOf[AmmoBoxData].unk mustEqual 0
|
||||
//2
|
||||
contents(2).item.objectClass mustEqual ObjectClass.armor_canister
|
||||
contents(2).item.guid mustEqual PlanetSideGUID(3326)
|
||||
contents(2).item.parentSlot mustEqual 78
|
||||
contents(2).item.obj.isInstanceOf[AmmoBoxData] mustEqual true
|
||||
contents(2).item.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0
|
||||
contents(2).objectClass mustEqual ObjectClass.armor_canister
|
||||
contents(2).guid mustEqual PlanetSideGUID(3326)
|
||||
contents(2).parentSlot mustEqual 78
|
||||
contents(2).obj.isInstanceOf[AmmoBoxData] mustEqual true
|
||||
contents(2).obj.asInstanceOf[AmmoBoxData].unk mustEqual 0
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -662,10 +669,10 @@ class ObjectCreateMessageTest extends Specification {
|
|||
pc.appearance.pos.roll mustEqual 0
|
||||
pc.appearance.pos.pitch mustEqual 0
|
||||
pc.appearance.pos.yaw mustEqual 9
|
||||
pc.appearance.pos.init_move.isDefined mustEqual true
|
||||
pc.appearance.pos.init_move.get.x mustEqual 1.4375f
|
||||
pc.appearance.pos.init_move.get.y mustEqual -0.4375f
|
||||
pc.appearance.pos.init_move.get.z mustEqual 0f
|
||||
pc.appearance.pos.vel.isDefined mustEqual true
|
||||
pc.appearance.pos.vel.get.x mustEqual 1.4375f
|
||||
pc.appearance.pos.vel.get.y mustEqual -0.4375f
|
||||
pc.appearance.pos.vel.get.z mustEqual 0f
|
||||
pc.appearance.basic_appearance.name mustEqual "ScrawnyRonnie"
|
||||
pc.appearance.basic_appearance.faction mustEqual PlanetSideEmpire.TR
|
||||
pc.appearance.basic_appearance.sex mustEqual CharacterGender.Male
|
||||
|
|
@ -705,40 +712,40 @@ class ObjectCreateMessageTest extends Specification {
|
|||
val contents = pc.inventory.get.contents
|
||||
contents.size mustEqual 5
|
||||
//0
|
||||
contents.head.item.objectClass mustEqual ObjectClass.plasma_grenade
|
||||
contents.head.item.guid mustEqual PlanetSideGUID(3662)
|
||||
contents.head.item.parentSlot mustEqual 0
|
||||
contents.head.item.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
|
||||
contents.head.item.obj.asInstanceOf[WeaponData].ammo.objectClass mustEqual ObjectClass.plasma_grenade_ammo
|
||||
contents.head.item.obj.asInstanceOf[WeaponData].ammo.guid mustEqual PlanetSideGUID(3751)
|
||||
contents.head.objectClass mustEqual ObjectClass.plasma_grenade
|
||||
contents.head.guid mustEqual PlanetSideGUID(3662)
|
||||
contents.head.parentSlot mustEqual 0
|
||||
contents.head.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
|
||||
contents.head.obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.plasma_grenade_ammo
|
||||
contents.head.obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3751)
|
||||
//1
|
||||
contents(1).item.objectClass mustEqual ObjectClass.bank
|
||||
contents(1).item.guid mustEqual PlanetSideGUID(3908)
|
||||
contents(1).item.parentSlot mustEqual 1
|
||||
contents(1).item.obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
|
||||
contents(1).item.obj.asInstanceOf[WeaponData].ammo.objectClass mustEqual ObjectClass.armor_canister
|
||||
contents(1).item.obj.asInstanceOf[WeaponData].ammo.guid mustEqual PlanetSideGUID(4143)
|
||||
contents(1).objectClass mustEqual ObjectClass.bank
|
||||
contents(1).guid mustEqual PlanetSideGUID(3908)
|
||||
contents(1).parentSlot mustEqual 1
|
||||
contents(1).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
|
||||
contents(1).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.armor_canister
|
||||
contents(1).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(4143)
|
||||
//2
|
||||
contents(2).item.objectClass mustEqual ObjectClass.mini_chaingun
|
||||
contents(2).item.guid mustEqual PlanetSideGUID(4164)
|
||||
contents(2).item.parentSlot mustEqual 2
|
||||
contents(2).item.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
|
||||
contents(2).item.obj.asInstanceOf[WeaponData].ammo.objectClass mustEqual ObjectClass.bullet_9mm
|
||||
contents(2).item.obj.asInstanceOf[WeaponData].ammo.guid mustEqual PlanetSideGUID(3728)
|
||||
contents(2).objectClass mustEqual ObjectClass.mini_chaingun
|
||||
contents(2).guid mustEqual PlanetSideGUID(4164)
|
||||
contents(2).parentSlot mustEqual 2
|
||||
contents(2).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
|
||||
contents(2).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.bullet_9mm
|
||||
contents(2).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3728)
|
||||
//3
|
||||
contents(3).item.objectClass mustEqual ObjectClass.phoenix //actually, a decimator
|
||||
contents(3).item.guid mustEqual PlanetSideGUID(3603)
|
||||
contents(3).item.parentSlot mustEqual 3
|
||||
contents(3).item.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
|
||||
contents(3).item.obj.asInstanceOf[WeaponData].ammo.objectClass mustEqual ObjectClass.phoenix_missile
|
||||
contents(3).item.obj.asInstanceOf[WeaponData].ammo.guid mustEqual PlanetSideGUID(3056)
|
||||
contents(3).objectClass mustEqual ObjectClass.phoenix //actually, a decimator
|
||||
contents(3).guid mustEqual PlanetSideGUID(3603)
|
||||
contents(3).parentSlot mustEqual 3
|
||||
contents(3).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
|
||||
contents(3).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.phoenix_missile
|
||||
contents(3).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3056)
|
||||
//4
|
||||
contents(4).item.objectClass mustEqual ObjectClass.chainblade
|
||||
contents(4).item.guid mustEqual PlanetSideGUID(4088)
|
||||
contents(4).item.parentSlot mustEqual 4
|
||||
contents(4).item.obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
|
||||
contents(4).item.obj.asInstanceOf[WeaponData].ammo.objectClass mustEqual ObjectClass.melee_ammo
|
||||
contents(4).item.obj.asInstanceOf[WeaponData].ammo.guid mustEqual PlanetSideGUID(3279)
|
||||
contents(4).objectClass mustEqual ObjectClass.chainblade
|
||||
contents(4).guid mustEqual PlanetSideGUID(4088)
|
||||
contents(4).parentSlot mustEqual 4
|
||||
contents(4).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
|
||||
contents(4).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.melee_ammo
|
||||
contents(4).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3279)
|
||||
pc.drawn_slot mustEqual DrawnSlot.Rifle1
|
||||
case _ =>
|
||||
ko
|
||||
|
|
@ -761,7 +768,7 @@ class ObjectCreateMessageTest extends Specification {
|
|||
pc.appearance.pos.roll mustEqual 0
|
||||
pc.appearance.pos.pitch mustEqual 0
|
||||
pc.appearance.pos.yaw mustEqual 115
|
||||
pc.appearance.pos.init_move.isDefined mustEqual false
|
||||
pc.appearance.pos.vel.isDefined mustEqual false
|
||||
pc.appearance.basic_appearance.name mustEqual "Angello"
|
||||
pc.appearance.basic_appearance.faction mustEqual PlanetSideEmpire.VS
|
||||
pc.appearance.basic_appearance.sex mustEqual CharacterGender.Male
|
||||
|
|
@ -814,7 +821,7 @@ class ObjectCreateMessageTest extends Specification {
|
|||
}
|
||||
|
||||
"encode (implant interface)" in {
|
||||
val obj = ImplantInterfaceData()
|
||||
val obj = CommonTerminalData(PlanetSideEmpire.VS)
|
||||
val msg = ObjectCreateMessage(0x199, PlanetSideGUID(1075), ObjectCreateMessageParent(PlanetSideGUID(514), 1), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
|
|
@ -822,7 +829,10 @@ class ObjectCreateMessageTest extends Specification {
|
|||
}
|
||||
|
||||
"encode (order terminal a)" in {
|
||||
val obj = CommonTerminalData(PlacementData(Vector3(4579.3438f, 5615.0703f, 72.953125f), 0, 0, 125))
|
||||
val obj = DroppedItemData(
|
||||
PlacementData(4579.3438f, 5615.0703f, 72.953125f, 0, 0, 125),
|
||||
CommonTerminalData(PlanetSideEmpire.NC)
|
||||
)
|
||||
val msg = ObjectCreateMessage(ObjectClass.order_terminala, PlanetSideGUID(3827), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
|
|
@ -854,7 +864,7 @@ class ObjectCreateMessageTest extends Specification {
|
|||
}
|
||||
|
||||
"encode (lasher, held)" in {
|
||||
val obj = WeaponData(8, 8, ObjectClass.energy_cell, PlanetSideGUID(3548), 0, AmmoBoxData(8))
|
||||
val obj = WeaponData(4, 8, ObjectClass.energy_cell, PlanetSideGUID(3548), 0, AmmoBoxData(8))
|
||||
val msg = ObjectCreateMessage(ObjectClass.lasher, PlanetSideGUID(3033), ObjectCreateMessageParent(PlanetSideGUID(4141), 3), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
|
|
@ -862,11 +872,12 @@ class ObjectCreateMessageTest extends Specification {
|
|||
}
|
||||
|
||||
"encode (punisher, held)" in {
|
||||
val obj = ConcurrentFeedWeaponData(8, 8, 0,
|
||||
AmmoBoxData(ObjectClass.bullet_9mm, PlanetSideGUID(3918), 0, AmmoBoxData(8)) ::
|
||||
AmmoBoxData(ObjectClass.rocket, PlanetSideGUID(3941), 1, AmmoBoxData(8)) ::
|
||||
Nil
|
||||
)
|
||||
val obj =
|
||||
WeaponData(4, 8, 0,
|
||||
AmmoBoxData(ObjectClass.bullet_9mm, PlanetSideGUID(3918), 0, AmmoBoxData(8)) ::
|
||||
AmmoBoxData(ObjectClass.rocket, PlanetSideGUID(3941), 1, AmmoBoxData(8)) ::
|
||||
Nil
|
||||
)(2)
|
||||
val msg = ObjectCreateMessage(ObjectClass.punisher, PlanetSideGUID(4147), ObjectCreateMessageParent(PlanetSideGUID(3092), 3), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
|
|
@ -925,7 +936,7 @@ class ObjectCreateMessageTest extends Specification {
|
|||
"encode (lasher, dropped)" in {
|
||||
val obj = DroppedItemData(
|
||||
PlacementData(Vector3(4691.1953f, 5537.039f, 65.484375f), 0, 0, 32),
|
||||
WeaponData(8, 0, ObjectClass.energy_cell, PlanetSideGUID(3268), 0, AmmoBoxData())
|
||||
WeaponData(4, 0, ObjectClass.energy_cell, PlanetSideGUID(3268), 0, AmmoBoxData())
|
||||
)
|
||||
val msg = ObjectCreateMessage(ObjectClass.lasher, PlanetSideGUID(3074), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
|
@ -936,11 +947,11 @@ class ObjectCreateMessageTest extends Specification {
|
|||
"encode (punisher, dropped)" in {
|
||||
val obj = DroppedItemData(
|
||||
PlacementData(Vector3(4789.133f, 5522.3125f, 72.3125f), 0, 0, 51),
|
||||
ConcurrentFeedWeaponData(4, 0, 0,
|
||||
WeaponData(2, 0, 0,
|
||||
AmmoBoxData(ObjectClass.bullet_9mm, PlanetSideGUID(3528), 0, AmmoBoxData()) ::
|
||||
AmmoBoxData(ObjectClass.rocket, PlanetSideGUID(3031), 1, AmmoBoxData()) ::
|
||||
Nil
|
||||
)
|
||||
)(2)
|
||||
)
|
||||
val msg = ObjectCreateMessage(ObjectClass.punisher, PlanetSideGUID(2978), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
|
@ -961,9 +972,9 @@ class ObjectCreateMessageTest extends Specification {
|
|||
|
||||
"encode (boomer)" in {
|
||||
val obj = SmallDeployableData(
|
||||
ACEDeployableData(
|
||||
CommonFieldData(
|
||||
PlacementData(Vector3(4704.172f, 5546.4375f, 82.234375f), 0, 0, 63),
|
||||
0, PlanetSideGUID(4145)
|
||||
PlanetSideEmpire.TR, 0, PlanetSideGUID(4145)
|
||||
)
|
||||
)
|
||||
val msg = ObjectCreateMessage(ObjectClass.boomer, PlanetSideGUID(3840), obj)
|
||||
|
|
@ -974,10 +985,9 @@ class ObjectCreateMessageTest extends Specification {
|
|||
|
||||
"encode (spitfire, short)" in {
|
||||
val obj = SmallTurretData(
|
||||
ACEDeployableData(
|
||||
PlacementData(Vector3(4577.7812f, 5624.828f, 72.046875f), 0, 127, 66),
|
||||
44,
|
||||
PlanetSideGUID(3871)
|
||||
CommonFieldData(
|
||||
PlacementData(Vector3(4577.7812f, 5624.828f, 72.046875f), 0, 127, 66),
|
||||
PlanetSideEmpire.NC, 12, PlanetSideGUID(3871)
|
||||
),
|
||||
255 //sets to 0
|
||||
)
|
||||
|
|
@ -993,13 +1003,12 @@ class ObjectCreateMessageTest extends Specification {
|
|||
|
||||
"encode (spitfire)" in {
|
||||
val obj = SmallTurretData(
|
||||
ACEDeployableData(
|
||||
CommonFieldData(
|
||||
PlacementData(Vector3(4527.633f, 6271.3594f, 70.265625f), 0, 0, 105),
|
||||
68,
|
||||
PlanetSideGUID(4232)
|
||||
PlanetSideEmpire.VS, 4, PlanetSideGUID(4232)
|
||||
),
|
||||
255,
|
||||
SmallTurretData.spitfire(PlanetSideGUID(3064), 0xC, 0x8, PlanetSideGUID(3694), 8)
|
||||
SmallTurretData.spitfire(PlanetSideGUID(3064), 0x6, 0x8, PlanetSideGUID(3694), 8)
|
||||
)
|
||||
val msg = ObjectCreateMessage(ObjectClass.spitfire_turret, PlanetSideGUID(4265), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
|
@ -1013,10 +1022,9 @@ class ObjectCreateMessageTest extends Specification {
|
|||
|
||||
"encode (trap)" in {
|
||||
val obj = TRAPData(
|
||||
ACEDeployableData(
|
||||
CommonFieldData(
|
||||
PlacementData(Vector3(3572.4453f, 3277.9766f, 114.0f), 0, 0, 0),
|
||||
68,
|
||||
PlanetSideGUID(2502)
|
||||
PlanetSideEmpire.VS, 4, PlanetSideGUID(2502)
|
||||
),
|
||||
255
|
||||
)
|
||||
|
|
@ -1032,10 +1040,9 @@ class ObjectCreateMessageTest extends Specification {
|
|||
|
||||
"encode (aegis)" in {
|
||||
val obj = AegisShieldGeneratorData(
|
||||
ACEDeployableData(
|
||||
CommonFieldData(
|
||||
PlacementData(Vector3(3571.2266f, 3278.0938f, 114.0f), 0, 0, 0),
|
||||
68,
|
||||
PlanetSideGUID(2366)
|
||||
PlanetSideEmpire.VS, 4, PlanetSideGUID(2366)
|
||||
),
|
||||
255
|
||||
)
|
||||
|
|
@ -1047,14 +1054,12 @@ class ObjectCreateMessageTest extends Specification {
|
|||
|
||||
"encode (orion)" in {
|
||||
val obj = OneMannedFieldTurretData(
|
||||
ACEDeployableData(
|
||||
CommonFieldData(
|
||||
PlacementData(Vector3(3567.1406f, 2988.0078f, 71.84375f), 0, 0, 94),
|
||||
68,
|
||||
PlanetSideGUID(0)
|
||||
PlanetSideEmpire.VS, 4, PlanetSideGUID(2502)
|
||||
),
|
||||
PlanetSideGUID(2502),
|
||||
255,
|
||||
OneMannedFieldTurretData.orion(PlanetSideGUID(2615), 0xC, 0x8, PlanetSideGUID(2510), 8)
|
||||
OneMannedFieldTurretData.orion(PlanetSideGUID(2615), 0x6, 0x8, PlanetSideGUID(2510), 8)
|
||||
)
|
||||
val msg = ObjectCreateMessage(ObjectClass.portable_manned_turret_vs, PlanetSideGUID(2916), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
|
@ -1069,7 +1074,7 @@ class ObjectCreateMessageTest extends Specification {
|
|||
"encode (locker container)" in {
|
||||
val obj = LockerContainerData(
|
||||
InventoryData(
|
||||
InventoryItem(ObjectClass.nano_dispenser, PlanetSideGUID(2935), 0, WeaponData(0xC, 0x0, ObjectClass.armor_canister, PlanetSideGUID(3426), 0, AmmoBoxData())) ::
|
||||
InventoryItem(ObjectClass.nano_dispenser, PlanetSideGUID(2935), 0, WeaponData(0x6, 0x0, ObjectClass.armor_canister, PlanetSideGUID(3426), 0, AmmoBoxData())) ::
|
||||
InventoryItem(ObjectClass.armor_canister, PlanetSideGUID(4090), 45, AmmoBoxData()) ::
|
||||
InventoryItem(ObjectClass.armor_canister, PlanetSideGUID(3326), 78, AmmoBoxData()) ::
|
||||
Nil
|
||||
|
|
|
|||
531
common/src/test/scala/game/ObjectCreateMessageVehiclesTest.scala
Normal file
531
common/src/test/scala/game/ObjectCreateMessageVehiclesTest.scala
Normal file
|
|
@ -0,0 +1,531 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package game
|
||||
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game.{ObjectCreateMessage, _}
|
||||
import net.psforever.packet.game.objectcreate.{DriveState, _}
|
||||
import net.psforever.types._
|
||||
import org.specs2.mutable._
|
||||
import scodec.bits._
|
||||
|
||||
class ObjectCreateMessageVehiclesTest extends Specification {
|
||||
val string_fury = hex"17 50010000 A79 9D01 FBC1C 12A83 2F06 00 00 21 4400003FC00101140C800C0E40000004048F3600301900000"
|
||||
val string_ant = hex"17 C2000000 9E0 7C01 6C2D7 65535 CA16 00 00 00 4400003FC000000"
|
||||
val string_lightning = hex"17 8b010000 df1 5a00 6c2d7 65535 ca16 00 00 00 4400003fc00101300ad8040c4000000408190b801018000002617402070000000"
|
||||
val string_mediumtransport = hex"17 DA010000 8A2 8301 FBC1C 12A83 2F06 00 00 21 2400003FC079020593F80C2E400000040410148030190000017458050D90000001010401F814064000000"
|
||||
val string_ams = hex"17 B8010000 970 3D10 002D765535CA16000000 402285BB0037E4100749E1D03000000620D83A0A00000195798741C00000332E40D84800000"
|
||||
val string_ams_destroyed = hex"17 8D000000 978 3D10 002D765535CA16000000 0"
|
||||
val string_switchblade = hex"17 93010000 A7B A201 FBC1C12A832F06000021 4400003FC00001013AD3180C0E4000000408330DC03019000006620406072000000"
|
||||
val string_droppod = hex"17 C1000000 8110B0E00FA9000ACFFFF000000 4400007F83C0900"
|
||||
val string_orbital_shuttle_1 = hex"17 82000000 0901B026904838000001FE0700"
|
||||
val string_orbital_shuttle_2 = hex"17 C3000000 B02670402F5AA14F88C210000604000007F8FF03C0"
|
||||
|
||||
"decode (fury)" in {
|
||||
PacketCoding.DecodePacket(string_fury).require match {
|
||||
case ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
len mustEqual 336
|
||||
cls mustEqual ObjectClass.fury
|
||||
guid mustEqual PlanetSideGUID(413)
|
||||
parent.isDefined mustEqual false
|
||||
data.isDefined mustEqual true
|
||||
data.get.isInstanceOf[VehicleData] mustEqual true
|
||||
val fury = data.get.asInstanceOf[VehicleData]
|
||||
fury.basic.pos.coord.x mustEqual 6531.961f
|
||||
fury.basic.pos.coord.y mustEqual 1872.1406f
|
||||
fury.basic.pos.coord.z mustEqual 24.734375f
|
||||
fury.basic.pos.roll mustEqual 0
|
||||
fury.basic.pos.pitch mustEqual 0
|
||||
fury.basic.pos.yaw mustEqual 33
|
||||
fury.basic.pos.vel.isDefined mustEqual false
|
||||
fury.basic.faction mustEqual PlanetSideEmpire.VS
|
||||
fury.basic.unk mustEqual 4
|
||||
fury.basic.player_guid mustEqual PlanetSideGUID(0)
|
||||
fury.health mustEqual 255
|
||||
//
|
||||
fury.mountings.isDefined mustEqual true
|
||||
fury.mountings.get.size mustEqual 1
|
||||
val mounting = fury.mountings.get.head
|
||||
mounting.objectClass mustEqual ObjectClass.fury_weapon_systema
|
||||
mounting.guid mustEqual PlanetSideGUID(400)
|
||||
mounting.parentSlot mustEqual 1
|
||||
mounting.obj.isInstanceOf[WeaponData] mustEqual true
|
||||
val weapon = mounting.obj.asInstanceOf[WeaponData]
|
||||
weapon.unk1 mustEqual 0x6
|
||||
weapon.unk2 mustEqual 0x8
|
||||
weapon.fire_mode mustEqual 0
|
||||
weapon.ammo.size mustEqual 1
|
||||
val ammo = weapon.ammo.head
|
||||
ammo.objectClass mustEqual ObjectClass.hellfire_ammo
|
||||
ammo.guid mustEqual PlanetSideGUID(432)
|
||||
ammo.parentSlot mustEqual 0
|
||||
ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
|
||||
ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x8
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (ant)" in {
|
||||
PacketCoding.DecodePacket(string_ant).require match {
|
||||
case ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
len mustEqual 194L
|
||||
cls mustEqual ObjectClass.ant
|
||||
guid mustEqual PlanetSideGUID(380)
|
||||
parent.isDefined mustEqual false
|
||||
data.isDefined mustEqual true
|
||||
data.get.isInstanceOf[ANTData] mustEqual true
|
||||
val ant = data.get.asInstanceOf[ANTData]
|
||||
ant.basic.pos.coord.x mustEqual 3674.8438f
|
||||
ant.basic.pos.coord.y mustEqual 2726.789f
|
||||
ant.basic.pos.coord.z mustEqual 91.15625f
|
||||
ant.basic.pos.roll mustEqual 0
|
||||
ant.basic.pos.pitch mustEqual 0
|
||||
ant.basic.pos.yaw mustEqual 0
|
||||
ant.basic.faction mustEqual PlanetSideEmpire.VS
|
||||
ant.basic.unk mustEqual 4
|
||||
ant.basic.player_guid mustEqual PlanetSideGUID(0)
|
||||
ant.health mustEqual 255
|
||||
ant.driveState mustEqual DriveState.Mobile
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (lightning)" in {
|
||||
PacketCoding.DecodePacket(string_lightning).require match {
|
||||
case ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
len mustEqual 395L
|
||||
cls mustEqual ObjectClass.lightning
|
||||
guid mustEqual PlanetSideGUID(90)
|
||||
parent.isDefined mustEqual false
|
||||
data.isDefined mustEqual true
|
||||
data.get.isInstanceOf[VehicleData] mustEqual true
|
||||
val lightning = data.get.asInstanceOf[VehicleData]
|
||||
lightning.basic.pos.coord.x mustEqual 3674.8438f
|
||||
lightning.basic.pos.coord.y mustEqual 2726.789f
|
||||
lightning.basic.pos.coord.z mustEqual 91.15625f
|
||||
lightning.basic.pos.roll mustEqual 0
|
||||
lightning.basic.pos.pitch mustEqual 0
|
||||
lightning.basic.pos.yaw mustEqual 0
|
||||
lightning.basic.faction mustEqual PlanetSideEmpire.VS
|
||||
lightning.basic.unk mustEqual 4
|
||||
lightning.basic.player_guid mustEqual PlanetSideGUID(0)
|
||||
lightning.health mustEqual 255
|
||||
lightning.mountings.isDefined mustEqual true
|
||||
lightning.mountings.get.size mustEqual 1
|
||||
val mounting = lightning.mountings.get.head
|
||||
mounting.objectClass mustEqual ObjectClass.lightning_weapon_system
|
||||
mounting.guid mustEqual PlanetSideGUID(91)
|
||||
mounting.parentSlot mustEqual 1
|
||||
mounting.obj.isInstanceOf[WeaponData] mustEqual true
|
||||
val weapon = mounting.obj.asInstanceOf[WeaponData]
|
||||
weapon.unk1 mustEqual 0x4
|
||||
weapon.unk2 mustEqual 0x8
|
||||
weapon.fire_mode mustEqual 0
|
||||
weapon.ammo.size mustEqual 2
|
||||
//0
|
||||
var ammo = weapon.ammo.head
|
||||
ammo.objectClass mustEqual ObjectClass.bullet_75mm
|
||||
ammo.guid mustEqual PlanetSideGUID(92)
|
||||
ammo.parentSlot mustEqual 0
|
||||
ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
|
||||
ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x0
|
||||
//1
|
||||
ammo = weapon.ammo(1)
|
||||
ammo.objectClass mustEqual ObjectClass.bullet_25mm
|
||||
ammo.guid mustEqual PlanetSideGUID(93)
|
||||
ammo.parentSlot mustEqual 1
|
||||
ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
|
||||
ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x0
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (medium transport)" in {
|
||||
PacketCoding.DecodePacket(string_mediumtransport).require match {
|
||||
case ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
len mustEqual 474L
|
||||
cls mustEqual ObjectClass.mediumtransport
|
||||
guid mustEqual PlanetSideGUID(387)
|
||||
parent.isDefined mustEqual false
|
||||
data.isDefined mustEqual true
|
||||
data.get.isInstanceOf[VehicleData] mustEqual true
|
||||
val deliverer = data.get.asInstanceOf[VehicleData]
|
||||
deliverer.basic.pos.coord.x mustEqual 6531.961f
|
||||
deliverer.basic.pos.coord.y mustEqual 1872.1406f
|
||||
deliverer.basic.pos.coord.z mustEqual 24.734375f
|
||||
deliverer.basic.pos.roll mustEqual 0
|
||||
deliverer.basic.pos.pitch mustEqual 0
|
||||
deliverer.basic.pos.yaw mustEqual 33
|
||||
deliverer.basic.faction mustEqual PlanetSideEmpire.NC
|
||||
deliverer.basic.unk mustEqual 4
|
||||
deliverer.basic.player_guid mustEqual PlanetSideGUID(0)
|
||||
deliverer.unk1 mustEqual 0
|
||||
deliverer.health mustEqual 255
|
||||
deliverer.unk2 mustEqual 0
|
||||
deliverer.driveState mustEqual DriveState.State7
|
||||
deliverer.unk4 mustEqual true
|
||||
deliverer.unk5 mustEqual 0
|
||||
deliverer.mountings.isDefined mustEqual true
|
||||
deliverer.mountings.get.size mustEqual 2
|
||||
//0
|
||||
var mounting = deliverer.mountings.get.head
|
||||
mounting.objectClass mustEqual ObjectClass.mediumtransport_weapon_systemA
|
||||
mounting.guid mustEqual PlanetSideGUID(383)
|
||||
mounting.parentSlot mustEqual 5
|
||||
mounting.obj.isInstanceOf[WeaponData] mustEqual true
|
||||
var weapon = mounting.obj.asInstanceOf[WeaponData]
|
||||
weapon.unk1 mustEqual 0x6
|
||||
weapon.unk2 mustEqual 0x8
|
||||
weapon.fire_mode mustEqual 0
|
||||
weapon.ammo.size mustEqual 1
|
||||
var ammo = weapon.ammo.head
|
||||
ammo.objectClass mustEqual ObjectClass.bullet_20mm
|
||||
ammo.guid mustEqual PlanetSideGUID(420)
|
||||
ammo.parentSlot mustEqual 0
|
||||
ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
|
||||
ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x8
|
||||
//1
|
||||
mounting = deliverer.mountings.get(1)
|
||||
mounting.objectClass mustEqual ObjectClass.mediumtransport_weapon_systemB
|
||||
mounting.guid mustEqual PlanetSideGUID(556)
|
||||
mounting.parentSlot mustEqual 6
|
||||
mounting.obj.isInstanceOf[WeaponData] mustEqual true
|
||||
weapon = mounting.obj.asInstanceOf[WeaponData]
|
||||
weapon.unk1 mustEqual 0x6
|
||||
weapon.unk2 mustEqual 0x8
|
||||
weapon.fire_mode mustEqual 0
|
||||
weapon.ammo.size mustEqual 1
|
||||
ammo = weapon.ammo.head
|
||||
ammo.objectClass mustEqual ObjectClass.bullet_20mm
|
||||
ammo.guid mustEqual PlanetSideGUID(575)
|
||||
ammo.parentSlot mustEqual 0
|
||||
ammo.obj.isInstanceOf[AmmoBoxData] mustEqual true
|
||||
ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x8
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (ams)" in {
|
||||
PacketCoding.DecodePacket(string_ams).require match {
|
||||
case ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
len mustEqual 440L
|
||||
cls mustEqual ObjectClass.ams
|
||||
guid mustEqual PlanetSideGUID(4157)
|
||||
parent.isDefined mustEqual false
|
||||
data.isDefined mustEqual true
|
||||
data.get.isInstanceOf[AMSData] mustEqual true
|
||||
val ams = data.get.asInstanceOf[AMSData]
|
||||
ams.basic.pos.coord.x mustEqual 3674.0f
|
||||
ams.basic.pos.coord.y mustEqual 2726.789f
|
||||
ams.basic.pos.coord.z mustEqual 91.15625f
|
||||
ams.basic.pos.roll mustEqual 0
|
||||
ams.basic.pos.pitch mustEqual 0
|
||||
ams.basic.pos.yaw mustEqual 0
|
||||
ams.basic.faction mustEqual PlanetSideEmpire.VS
|
||||
ams.basic.unk mustEqual 0
|
||||
ams.basic.player_guid mustEqual PlanetSideGUID(34082)
|
||||
ams.unk1 mustEqual 2
|
||||
ams.health mustEqual 236
|
||||
ams.unk2 mustEqual 0
|
||||
ams.driveState mustEqual DriveState.Deployed
|
||||
ams.matrix_guid mustEqual PlanetSideGUID(3663)
|
||||
ams.respawn_guid mustEqual PlanetSideGUID(3638)
|
||||
ams.term_a_guid mustEqual PlanetSideGUID(3827)
|
||||
ams.term_b_guid mustEqual PlanetSideGUID(3556)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (ams, destroyed)" in {
|
||||
PacketCoding.DecodePacket(string_ams_destroyed).require match {
|
||||
case ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
len mustEqual 141L
|
||||
cls mustEqual ObjectClass.ams_destroyed
|
||||
guid mustEqual PlanetSideGUID(4157)
|
||||
parent.isDefined mustEqual false
|
||||
data.isDefined mustEqual true
|
||||
data.get.isInstanceOf[DestroyedVehicleData] mustEqual true
|
||||
val dams = data.get.asInstanceOf[DestroyedVehicleData]
|
||||
dams.pos.coord.x mustEqual 3674.0f
|
||||
dams.pos.coord.y mustEqual 2726.789f
|
||||
dams.pos.coord.z mustEqual 91.15625f
|
||||
dams.pos.roll mustEqual 0
|
||||
dams.pos.pitch mustEqual 0
|
||||
dams.pos.yaw mustEqual 0
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (switchblade)" in {
|
||||
PacketCoding.DecodePacket(string_switchblade).require match {
|
||||
case ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
len mustEqual 403L
|
||||
cls mustEqual ObjectClass.switchblade
|
||||
guid mustEqual PlanetSideGUID(418)
|
||||
parent.isDefined mustEqual false
|
||||
data.isDefined mustEqual true
|
||||
data.get.isInstanceOf[Vehicle2Data] mustEqual true
|
||||
val switchblade = data.get.asInstanceOf[Vehicle2Data]
|
||||
switchblade.basic.pos.coord.x mustEqual 6531.961f
|
||||
switchblade.basic.pos.coord.y mustEqual 1872.1406f
|
||||
switchblade.basic.pos.coord.z mustEqual 24.734375f
|
||||
switchblade.basic.pos.roll mustEqual 0
|
||||
switchblade.basic.pos.pitch mustEqual 0
|
||||
switchblade.basic.pos.yaw mustEqual 33
|
||||
switchblade.basic.faction mustEqual PlanetSideEmpire.VS
|
||||
switchblade.basic.unk mustEqual 4
|
||||
switchblade.health mustEqual 255
|
||||
switchblade.driveState mustEqual DriveState.Mobile
|
||||
switchblade.mountings.isDefined mustEqual true
|
||||
switchblade.mountings.get.size mustEqual 1
|
||||
//0
|
||||
val weapon = switchblade.mountings.get.head
|
||||
weapon.objectClass mustEqual ObjectClass.scythe
|
||||
weapon.guid mustEqual PlanetSideGUID(355)
|
||||
weapon.parentSlot mustEqual 1
|
||||
weapon.obj.asInstanceOf[WeaponData].unk1 mustEqual 0x6
|
||||
weapon.obj.asInstanceOf[WeaponData].unk2 mustEqual 0x8
|
||||
weapon.obj.asInstanceOf[WeaponData].ammo.size mustEqual 2
|
||||
//ammo-0
|
||||
var ammo = weapon.obj.asInstanceOf[WeaponData].ammo.head
|
||||
ammo.objectClass mustEqual ObjectClass.ancient_ammo_vehicle
|
||||
ammo.guid mustEqual PlanetSideGUID(366)
|
||||
ammo.parentSlot mustEqual 0
|
||||
ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x8
|
||||
//ammo-1
|
||||
ammo = weapon.obj.asInstanceOf[WeaponData].ammo(1)
|
||||
ammo.objectClass mustEqual ObjectClass.ancient_ammo_vehicle
|
||||
ammo.guid mustEqual PlanetSideGUID(385)
|
||||
ammo.parentSlot mustEqual 1
|
||||
ammo.obj.asInstanceOf[AmmoBoxData].unk mustEqual 0x8
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (droppod)" in {
|
||||
PacketCoding.DecodePacket(string_droppod).require match {
|
||||
case ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
len mustEqual 193L
|
||||
cls mustEqual ObjectClass.droppod
|
||||
guid mustEqual PlanetSideGUID(3595)
|
||||
parent.isDefined mustEqual false
|
||||
data.isDefined mustEqual true
|
||||
data.get.isInstanceOf[DroppodData] mustEqual true
|
||||
val droppod = data.get.asInstanceOf[DroppodData]
|
||||
droppod.basic.pos.coord.x mustEqual 5108.0f
|
||||
droppod.basic.pos.coord.y mustEqual 6164.0f
|
||||
droppod.basic.pos.coord.z mustEqual 1023.9844f
|
||||
droppod.basic.pos.roll mustEqual 0
|
||||
droppod.basic.pos.pitch mustEqual 0
|
||||
droppod.basic.pos.yaw mustEqual 0
|
||||
droppod.basic.unk mustEqual 4
|
||||
droppod.basic.player_guid mustEqual PlanetSideGUID(0)
|
||||
droppod.burn mustEqual false
|
||||
droppod.health mustEqual 255
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (shuttle 1)" in {
|
||||
PacketCoding.DecodePacket(string_orbital_shuttle_1).require match {
|
||||
case ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
len mustEqual 130
|
||||
cls mustEqual ObjectClass.orbital_shuttle
|
||||
guid mustEqual PlanetSideGUID(1129)
|
||||
parent.isDefined mustEqual true
|
||||
parent.get.guid mustEqual PlanetSideGUID(786)
|
||||
parent.get.slot mustEqual 3
|
||||
data.isDefined mustEqual true
|
||||
data.get.isInstanceOf[OrbitalShuttleData] mustEqual true
|
||||
data.get.asInstanceOf[OrbitalShuttleData].faction mustEqual PlanetSideEmpire.VS
|
||||
data.get.asInstanceOf[OrbitalShuttleData].pos.isDefined mustEqual false
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (shuttle 2)" in {
|
||||
PacketCoding.DecodePacket(string_orbital_shuttle_2).require match {
|
||||
case ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
len mustEqual 195
|
||||
cls mustEqual ObjectClass.orbital_shuttle
|
||||
guid mustEqual PlanetSideGUID(1127)
|
||||
parent.isDefined mustEqual false
|
||||
data.isDefined mustEqual true
|
||||
data.get.isInstanceOf[OrbitalShuttleData] mustEqual true
|
||||
val shuttle = data.get.asInstanceOf[OrbitalShuttleData]
|
||||
shuttle.faction mustEqual PlanetSideEmpire.VS
|
||||
shuttle.pos.isDefined mustEqual true
|
||||
shuttle.pos.get.coord.x mustEqual 5610.0156f
|
||||
shuttle.pos.get.coord.y mustEqual 4255.258f
|
||||
shuttle.pos.get.coord.z mustEqual 134.1875f
|
||||
shuttle.pos.get.roll mustEqual 0
|
||||
shuttle.pos.get.pitch mustEqual 0
|
||||
shuttle.pos.get.yaw mustEqual 96
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode (fury)" in {
|
||||
val obj = VehicleData(
|
||||
CommonFieldData(
|
||||
PlacementData(6531.961f, 1872.1406f, 24.734375f, 0, 0, 33),
|
||||
PlanetSideEmpire.VS, 4
|
||||
),
|
||||
255,
|
||||
MountItem(ObjectClass.fury_weapon_systema, PlanetSideGUID(400), 1,
|
||||
WeaponData(0x6, 0x8, 0, ObjectClass.hellfire_ammo, PlanetSideGUID(432), 0, AmmoBoxData(0x8))
|
||||
)
|
||||
)
|
||||
val msg = ObjectCreateMessage(ObjectClass.fury, PlanetSideGUID(413), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_fury
|
||||
}
|
||||
|
||||
"encode (ant)" in {
|
||||
val obj = ANTData(
|
||||
CommonFieldData(
|
||||
PlacementData(3674.8438f, 2726.789f, 91.15625f),
|
||||
PlanetSideEmpire.VS, 4
|
||||
),
|
||||
255,
|
||||
DriveState.Mobile
|
||||
)
|
||||
val msg = ObjectCreateMessage(ObjectClass.ant, PlanetSideGUID(380), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_ant
|
||||
}
|
||||
|
||||
"encode (lightning)" in {
|
||||
val obj = VehicleData(
|
||||
CommonFieldData(
|
||||
PlacementData(3674.8438f, 2726.789f, 91.15625f),
|
||||
PlanetSideEmpire.VS, 4
|
||||
),
|
||||
255,
|
||||
MountItem(ObjectClass.lightning_weapon_system, PlanetSideGUID(91), 1,
|
||||
WeaponData(4, 8, 0, ObjectClass.bullet_75mm, PlanetSideGUID(92), 0, AmmoBoxData(), ObjectClass.bullet_25mm, PlanetSideGUID(93), 1, AmmoBoxData())
|
||||
)
|
||||
)
|
||||
val msg = ObjectCreateMessage(ObjectClass.lightning, PlanetSideGUID(90), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_lightning
|
||||
}
|
||||
|
||||
"encode (deliverer)" in {
|
||||
val obj = VehicleData(
|
||||
CommonFieldData(
|
||||
PlacementData(6531.961f, 1872.1406f, 24.734375f, 0, 0, 33),
|
||||
PlanetSideEmpire.NC, 4
|
||||
),
|
||||
0,
|
||||
255,
|
||||
0,
|
||||
DriveState.State7,
|
||||
true,
|
||||
0,
|
||||
Some(
|
||||
MountItem(
|
||||
ObjectClass.mediumtransport_weapon_systemA, PlanetSideGUID(383), 5,
|
||||
WeaponData(6, 8, ObjectClass.bullet_20mm, PlanetSideGUID(420), 0, AmmoBoxData(8))
|
||||
) ::
|
||||
MountItem(
|
||||
ObjectClass.mediumtransport_weapon_systemB, PlanetSideGUID(556), 6,
|
||||
WeaponData(6, 8, ObjectClass.bullet_20mm, PlanetSideGUID(575), 0, AmmoBoxData(8))
|
||||
) ::
|
||||
Nil
|
||||
)
|
||||
)(2)
|
||||
val msg = ObjectCreateMessage(ObjectClass.mediumtransport, PlanetSideGUID(387), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_mediumtransport
|
||||
}
|
||||
|
||||
"encode (ams)" in {
|
||||
val obj = AMSData(
|
||||
CommonFieldData(PlacementData(3674.0f, 2726.789f, 91.15625f, 0, 0, 0),
|
||||
PlanetSideEmpire.VS, 0,
|
||||
PlanetSideGUID(34082)
|
||||
),
|
||||
2,
|
||||
236,
|
||||
0,
|
||||
DriveState.Deployed,
|
||||
63,
|
||||
PlanetSideGUID(3663),
|
||||
PlanetSideGUID(3638),
|
||||
PlanetSideGUID(3827),
|
||||
PlanetSideGUID(3556)
|
||||
)
|
||||
val msg = ObjectCreateMessage(ObjectClass.ams, PlanetSideGUID(4157), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_ams
|
||||
}
|
||||
|
||||
"encode (ams, destroyed)" in {
|
||||
val obj = DestroyedVehicleData(PlacementData(3674.0f, 2726.789f, 91.15625f))
|
||||
val msg = ObjectCreateMessage(ObjectClass.ams_destroyed, PlanetSideGUID(4157), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_ams_destroyed
|
||||
}
|
||||
|
||||
"encode (switchblade(" in {
|
||||
val obj = Vehicle2Data(
|
||||
CommonFieldData(PlacementData(6531.961f, 1872.1406f, 24.734375f ,0 ,0 ,33),
|
||||
PlanetSideEmpire.VS, 4
|
||||
),
|
||||
255,
|
||||
DriveState.Mobile,
|
||||
MountItem(ObjectClass.scythe, PlanetSideGUID(355), 1,
|
||||
WeaponData(0x6, 0x8, 0, ObjectClass.ancient_ammo_vehicle, PlanetSideGUID(366), 0, AmmoBoxData(0x8), ObjectClass.ancient_ammo_vehicle, PlanetSideGUID(385), 1, AmmoBoxData(0x8))
|
||||
)
|
||||
)
|
||||
val msg = ObjectCreateMessage(ObjectClass.switchblade, PlanetSideGUID(418), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_switchblade
|
||||
}
|
||||
|
||||
"encode (droppod)" in {
|
||||
val obj = DroppodData(
|
||||
CommonFieldData(
|
||||
PlacementData(5108.0f, 6164.0f, 1023.9844f),
|
||||
PlanetSideEmpire.VS, 4
|
||||
)
|
||||
)
|
||||
val msg = ObjectCreateMessage(ObjectClass.droppod, PlanetSideGUID(3595), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_droppod
|
||||
}
|
||||
|
||||
"encode (shuttle 1)" in {
|
||||
val obj = OrbitalShuttleData(PlanetSideEmpire.VS)
|
||||
val msg = ObjectCreateMessage(ObjectClass.orbital_shuttle, PlanetSideGUID(1129), ObjectCreateMessageParent(PlanetSideGUID(786), 3), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_orbital_shuttle_1
|
||||
}
|
||||
|
||||
"encode (shuttle 2)" in {
|
||||
val obj = OrbitalShuttleData(PlacementData(5610.0156f, 4255.258f, 134.1875f, 0, 0, 96), PlanetSideEmpire.VS)
|
||||
val msg = ObjectCreateMessage(ObjectClass.orbital_shuttle, PlanetSideGUID(1127), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_orbital_shuttle_2
|
||||
}
|
||||
}
|
||||
54
common/src/test/scala/game/VehicleStateMessageTest.scala
Normal file
54
common/src/test/scala/game/VehicleStateMessageTest.scala
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package game
|
||||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.Vector3
|
||||
import scodec.bits._
|
||||
|
||||
class VehicleStateMessageTest extends Specification {
|
||||
val string = hex"1b 9d010d85aecaa6b8c2dfdfefffc020008006000078"
|
||||
|
||||
"decode" in {
|
||||
PacketCoding.DecodePacket(string).require match {
|
||||
case VehicleStateMessage(guid, unk1, pos, roll, pitch, yaw, vel, unk2, unk3, unk4, wheel, unk5, unk6) =>
|
||||
guid mustEqual PlanetSideGUID(413)
|
||||
unk1 mustEqual 0
|
||||
pos.x mustEqual 3674.8438f
|
||||
pos.y mustEqual 2726.789f
|
||||
pos.z mustEqual 91.09375f
|
||||
roll mustEqual 359.29688f
|
||||
pitch mustEqual 1.0546875f
|
||||
yaw mustEqual 90.35156f
|
||||
vel.isDefined mustEqual true
|
||||
vel.get.x mustEqual 0.0f
|
||||
vel.get.y mustEqual 0.0f
|
||||
vel.get.z mustEqual 0.03125f
|
||||
unk2.isDefined mustEqual false
|
||||
unk3 mustEqual 0
|
||||
unk4 mustEqual 0
|
||||
wheel mustEqual 15
|
||||
unk5 mustEqual false
|
||||
unk6 mustEqual false
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
val msg = VehicleStateMessage(
|
||||
PlanetSideGUID(413),
|
||||
0,
|
||||
Vector3(3674.8438f, 2726.789f, 91.09375f),
|
||||
359.29688f, 1.0546875f, 90.35156f,
|
||||
Some(Vector3(0.0f, 0.0f, 0.03125f)),
|
||||
None,
|
||||
0, 0, 15,
|
||||
false, false
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string
|
||||
}
|
||||
}
|
||||
|
|
@ -138,9 +138,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
false,
|
||||
RibbonBars()
|
||||
)
|
||||
val inv = InventoryItem(ObjectClass.beamer, PlanetSideGUID(76), 0, DetailedWeaponData(8, ObjectClass.energy_cell, PlanetSideGUID(77), 0, DetailedAmmoBoxData(8, 16))) ::
|
||||
InventoryItem(ObjectClass.suppressor, PlanetSideGUID(78), 2, DetailedWeaponData(8, ObjectClass.bullet_9mm, PlanetSideGUID(79), 0, DetailedAmmoBoxData(8, 25))) ::
|
||||
InventoryItem(ObjectClass.forceblade, PlanetSideGUID(80), 4, DetailedWeaponData(8, ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1))) ::
|
||||
val inv = InventoryItem(ObjectClass.beamer, PlanetSideGUID(76), 0, DetailedWeaponData(4, 8, ObjectClass.energy_cell, PlanetSideGUID(77), 0, DetailedAmmoBoxData(8, 16))) ::
|
||||
InventoryItem(ObjectClass.suppressor, PlanetSideGUID(78), 2, DetailedWeaponData(4, 8, ObjectClass.bullet_9mm, PlanetSideGUID(79), 0, DetailedAmmoBoxData(8, 25))) ::
|
||||
InventoryItem(ObjectClass.forceblade, PlanetSideGUID(80), 4, DetailedWeaponData(4, 8, ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1))) ::
|
||||
InventoryItem(ObjectClass.locker_container, PlanetSideGUID(82), 5, DetailedAmmoBoxData(8, 1)) ::
|
||||
InventoryItem(ObjectClass.bullet_9mm, PlanetSideGUID(83), 6, DetailedAmmoBoxData(8, 50)) ::
|
||||
InventoryItem(ObjectClass.bullet_9mm, PlanetSideGUID(84), 9, DetailedAmmoBoxData(8, 50)) ::
|
||||
|
|
@ -255,6 +255,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case msg @ ChildObjectStateMessage(object_guid : PlanetSideGUID, pitch : Int, yaw : Int) =>
|
||||
//log.info("ChildObjectState: " + msg)
|
||||
|
||||
case msg @ VehicleStateMessage(vehicle_guid, unk1, pos, roll, pitch, yaw, vel, unk5, unk6, unk7, wheels, unk9, unkA) =>
|
||||
//log.info("VehicleState: " + msg)
|
||||
|
||||
case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vector, unk1, unk2, unk3, unk4, time_alive) =>
|
||||
//log.info("ProjectileState: " + msg)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue