mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-03-24 14:29:08 +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
62 changed files with 2259 additions and 677 deletions
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue