mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-07-15 16:34:39 +00:00
removed Option from InternalSlot
refactored a chunk of CharacterData as an example
This commit is contained in:
parent
9adb077d8c
commit
f98a648db1
12 changed files with 672 additions and 218 deletions
|
|
@ -15,7 +15,7 @@ import shapeless.{::, HNil}
|
||||||
* The parent is a pre-existing object into which the (created) child is attached.<br>
|
* The parent is a pre-existing object into which the (created) child is attached.<br>
|
||||||
* <br>
|
* <br>
|
||||||
* The slot is encoded as a string length integer commonly used by PlanetSide.
|
* The slot is encoded as a string length integer commonly used by PlanetSide.
|
||||||
* It is either a 0-127 eight bit number (0 = 0x80), or a 128-32767 sixteen bit number (128 = 0x0080).
|
* It is either a 0-127 eight bit number (0 = `0x80`), or a 128-32767 sixteen bit number (128 = `0x0080`).
|
||||||
* @param guid the GUID of the parent object
|
* @param guid the GUID of the parent object
|
||||||
* @param slot a parent-defined slot identifier that explains where the child is to be attached to the parent
|
* @param slot a parent-defined slot identifier that explains where the child is to be attached to the parent
|
||||||
*/
|
*/
|
||||||
|
|
@ -59,6 +59,18 @@ case class ObjectCreateMessage(streamLength : Long,
|
||||||
}
|
}
|
||||||
|
|
||||||
object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] {
|
object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] {
|
||||||
|
/**
|
||||||
|
* An abbreviated constructor for creating `ObjectCreateMessages`, ignoring the optional aspect of some fields.
|
||||||
|
* @param streamLength the total length of the data that composes this packet in bits, excluding the opcode and end padding
|
||||||
|
* @param objectClass the code for the type of object being constructed
|
||||||
|
* @param guid the GUID this object will be assigned
|
||||||
|
* @param parentInfo the relationship between this object and another object (its parent)
|
||||||
|
* @param data the data used to construct this type of object
|
||||||
|
* @return an ObjectCreateMessage
|
||||||
|
*/
|
||||||
|
def apply(streamLength : Long, objectClass : Int, guid : PlanetSideGUID, parentInfo : ObjectCreateMessageParent, data : ConstructorData) : ObjectCreateMessage =
|
||||||
|
ObjectCreateMessage(streamLength, objectClass, guid, Some(parentInfo), Some(data))
|
||||||
|
|
||||||
type Pattern = Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: HNil
|
type Pattern = Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: HNil
|
||||||
type outPattern = Long :: Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: Option[ConstructorData] :: HNil
|
type outPattern = Long :: Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: Option[ConstructorData] :: HNil
|
||||||
/**
|
/**
|
||||||
|
|
@ -146,44 +158,7 @@ object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the stream length in number of bits by factoring in the whole message in two portions.
|
* Calculate the stream length in number of bits by factoring in the whole message in two portions.
|
||||||
* @param parentInfo if defined, information about the parent
|
* This process automates for: object encoding.<br>
|
||||||
* @param data the data length is indeterminate until it is read
|
|
||||||
* @return the total length of the stream in bits
|
|
||||||
*/
|
|
||||||
private def streamLen(parentInfo : Option[ObjectCreateMessageParent], data : BitVector) : Long = {
|
|
||||||
//knowable length
|
|
||||||
val first : Long = commonMsgLen(parentInfo)
|
|
||||||
//data length
|
|
||||||
var second : Long = data.size
|
|
||||||
val secondMod4 : Long = second % 4L
|
|
||||||
if(secondMod4 > 0L) {
|
|
||||||
//pad to include last whole nibble
|
|
||||||
second += 4L - secondMod4
|
|
||||||
}
|
|
||||||
first + second
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the stream length in number of bits by factoring in the whole message in two portions.
|
|
||||||
* @param parentInfo if defined, information about the parent
|
|
||||||
* @param data the data length is indeterminate until it is read
|
|
||||||
* @return the total length of the stream in bits
|
|
||||||
*/
|
|
||||||
private def streamLen(parentInfo : Option[ObjectCreateMessageParent], data : ConstructorData) : Long = {
|
|
||||||
//knowable length
|
|
||||||
val first : Long = commonMsgLen(parentInfo)
|
|
||||||
//data length
|
|
||||||
var second : Long = data.bitsize
|
|
||||||
val secondMod4 : Long = second % 4L
|
|
||||||
if(secondMod4 > 0L) {
|
|
||||||
//pad to include last whole nibble
|
|
||||||
second += 4L - secondMod4
|
|
||||||
}
|
|
||||||
first + second
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the length (in number of bits) of the basic packet message region.<br>
|
|
||||||
* <br>
|
* <br>
|
||||||
* Ignoring the parent data, constant field lengths have already been factored into the results.
|
* Ignoring the parent data, constant field lengths have already been factored into the results.
|
||||||
* That includes:
|
* That includes:
|
||||||
|
|
@ -192,17 +167,26 @@ object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] {
|
||||||
* the object's GUID (16u),
|
* the object's GUID (16u),
|
||||||
* and the bit to determine if there will be parent data.
|
* and the bit to determine if there will be parent data.
|
||||||
* In total, these fields form a known fixed length of 60u.
|
* In total, these fields form a known fixed length of 60u.
|
||||||
* @param parentInfo if defined, the parentInfo adds either 24u or 32u
|
* @param parentInfo if defined, information about the parent adds either 24u or 32u
|
||||||
* @return the length, including the optional parent data
|
* @param data the data length is indeterminate until it is walked-through
|
||||||
|
* @return the total length of the stream in bits
|
||||||
*/
|
*/
|
||||||
private def commonMsgLen(parentInfo : Option[ObjectCreateMessageParent]) : Long = {
|
private def streamLen(parentInfo : Option[ObjectCreateMessageParent], data : ConstructorData) : Long = {
|
||||||
if(parentInfo.isDefined) {
|
//knowable length
|
||||||
//(32u + 1u + 11u + 16u) ?+ (16u + (8u | 16u))
|
val first : Long = if(parentInfo.isDefined) {
|
||||||
if(parentInfo.get.slot > 127) 92L else 84L
|
if(parentInfo.get.slot > 127) 92L else 84L //(32u + 1u + 11u + 16u) ?+ (16u + (8u | 16u))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
60L
|
60L
|
||||||
}
|
}
|
||||||
|
//object length
|
||||||
|
var second : Long = data.bitsize
|
||||||
|
val secondMod4 : Long = second % 4L
|
||||||
|
if(secondMod4 > 0L) {
|
||||||
|
//pad to include last whole nibble
|
||||||
|
second += 4L - secondMod4
|
||||||
|
}
|
||||||
|
first + second
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit val codec : Codec[ObjectCreateMessage] = (
|
implicit val codec : Codec[ObjectCreateMessage] = (
|
||||||
|
|
@ -226,25 +210,27 @@ object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] {
|
||||||
}
|
}
|
||||||
) :+
|
) :+
|
||||||
("data" | bits)) //greed is good
|
("data" | bits)) //greed is good
|
||||||
).xmap[outPattern](
|
).exmap[outPattern] (
|
||||||
{
|
{
|
||||||
|
case _ :: _ :: _ :: _ :: BitVector.empty :: HNil =>
|
||||||
|
Attempt.failure(Err("no data to decode"))
|
||||||
case len :: cls :: guid :: par :: data :: HNil =>
|
case len :: cls :: guid :: par :: data :: HNil =>
|
||||||
len :: cls :: guid :: par :: decodeData(cls, data) :: HNil
|
Attempt.successful(len :: cls :: guid :: par :: decodeData(cls, data) :: HNil)
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
|
case _ :: _ :: _ :: _ :: None :: HNil =>
|
||||||
|
Attempt.failure(Err("no object to encode"))
|
||||||
case _ :: cls :: guid :: par :: Some(obj) :: HNil =>
|
case _ :: cls :: guid :: par :: Some(obj) :: HNil =>
|
||||||
streamLen(par, obj) :: cls :: guid :: par :: encodeData(cls, obj) :: HNil
|
Attempt.successful(streamLen(par, obj) :: cls :: guid :: par :: encodeData(cls, obj) :: HNil)
|
||||||
case _ :: cls :: guid :: par :: None :: HNil =>
|
|
||||||
streamLen(par, BitVector.empty) :: cls :: guid :: par :: BitVector.empty :: HNil
|
|
||||||
}
|
}
|
||||||
).exmap[ObjectCreateMessage](
|
).xmap[ObjectCreateMessage] (
|
||||||
{
|
{
|
||||||
case len :: cls :: guid :: par :: obj :: HNil =>
|
case len :: cls :: guid :: par :: obj :: HNil =>
|
||||||
Attempt.successful(ObjectCreateMessage(len, cls, guid, par, obj))
|
ObjectCreateMessage(len, cls, guid, par, obj)
|
||||||
}, {
|
},
|
||||||
case ObjectCreateMessage(_, _, _, _, None) =>
|
{
|
||||||
Attempt.failure(Err("no object to encode"))
|
|
||||||
case ObjectCreateMessage(len, cls, guid, par, obj) =>
|
case ObjectCreateMessage(len, cls, guid, par, obj) =>
|
||||||
Attempt.successful(len :: cls :: guid :: par :: obj :: HNil)
|
len :: cls :: guid :: par :: obj :: HNil
|
||||||
}
|
}
|
||||||
).as[ObjectCreateMessage]
|
).as[ObjectCreateMessage]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,21 @@ import scodec.{Attempt, Codec, Err}
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
import shapeless.{::, HNil}
|
import shapeless.{::, HNil}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of the ammunition portion of `ObjectCreateMessage` packet data.
|
||||||
|
* When alone, this data will help construct a "box" of that type of ammunition, hence the name.<br>
|
||||||
|
* <br>
|
||||||
|
* Exploration:<br>
|
||||||
|
* This class may need to be rewritten later to support objects spawned in the world environment.
|
||||||
|
* @param magazine the number of rounds available
|
||||||
|
*/
|
||||||
case class AmmoBoxData(magazine : Int
|
case class AmmoBoxData(magazine : Int
|
||||||
) extends ConstructorData {
|
) extends ConstructorData {
|
||||||
|
/**
|
||||||
|
* Performs a "sizeof()" analysis of the given object.
|
||||||
|
* @see ConstructorData.bitsize
|
||||||
|
* @return the number of bits necessary to represent this object
|
||||||
|
*/
|
||||||
override def bitsize : Long = 39L
|
override def bitsize : Long = 39L
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,6 +42,9 @@ object AmmoBoxData extends Marshallable[AmmoBoxData] {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform between AmmoBoxData and ConstructorData.
|
||||||
|
*/
|
||||||
val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
|
val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
|
||||||
{
|
{
|
||||||
case x =>
|
case x =>
|
||||||
|
|
|
||||||
|
|
@ -5,73 +5,27 @@ import net.psforever.packet.{Marshallable, PacketHelpers}
|
||||||
import net.psforever.types.Vector3
|
import net.psforever.types.Vector3
|
||||||
import scodec.{Attempt, Codec, Err}
|
import scodec.{Attempt, Codec, Err}
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
|
import shapeless.{::, HNil}
|
||||||
|
|
||||||
case class CharacterData(pos : Vector3,
|
case class CharacterAppearanceData(pos : Vector3,
|
||||||
objYaw : Int,
|
objYaw : Int,
|
||||||
faction : Int,
|
faction : Int,
|
||||||
bops : Boolean,
|
bops : Boolean,
|
||||||
name : String,
|
name : String,
|
||||||
exosuit : Int,
|
exosuit : Int,
|
||||||
sex : Int,
|
sex : Int,
|
||||||
face1 : Int,
|
face1 : Int,
|
||||||
face2 : Int,
|
face2 : Int,
|
||||||
voice : Int,
|
voice : Int,
|
||||||
unk1 : Int, //0x8080
|
unk1 : Int, //0x8080
|
||||||
unk2 : Int, //0xFFFF or 0x0
|
unk2 : Int, //0xFFFF or 0x0
|
||||||
unk3 : Int, //2
|
unk3 : Int, //2
|
||||||
viewPitch : Int,
|
viewPitch : Int,
|
||||||
viewYaw : Int,
|
viewYaw : Int,
|
||||||
ribbons : RibbonBars,
|
ribbons : RibbonBars)
|
||||||
healthMax : Int,
|
|
||||||
health : Int,
|
|
||||||
armor : Int,
|
|
||||||
unk4 : Int, //1
|
|
||||||
unk5 : Int, //7
|
|
||||||
unk6 : Int, //7
|
|
||||||
staminaMax : Int,
|
|
||||||
stamina : Int,
|
|
||||||
unk7 : Int, // 28
|
|
||||||
unk8 : Int, //4
|
|
||||||
unk9 : Int, //44
|
|
||||||
unk10 : Int, //84
|
|
||||||
unk11 : Int, //104
|
|
||||||
unk12 : Int, //1900
|
|
||||||
firstTimeEvent_length : Long,
|
|
||||||
firstEntry : Option[String],
|
|
||||||
firstTimeEvent_list : List[String],
|
|
||||||
tutorial_list : List[String],
|
|
||||||
inventory : InventoryData
|
|
||||||
) extends ConstructorData {
|
|
||||||
override def bitsize : Long = {
|
|
||||||
//represents static fields (includes medals.bitsize)
|
|
||||||
val first : Long = 1194L //TODO due to changing understanding of the bit patterns in this data, this value will change
|
|
||||||
//name
|
|
||||||
val second : Long = CharacterData.stringBitSize(name, 16) + 4L //plus the padding
|
|
||||||
//fte_list
|
|
||||||
var third : Long = 32L
|
|
||||||
if(firstEntry.isDefined) {
|
|
||||||
third += CharacterData.stringBitSize(firstEntry.get) + 5L //plus the padding
|
|
||||||
for(str <- firstTimeEvent_list) {
|
|
||||||
third += CharacterData.stringBitSize(str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//tutorial list
|
|
||||||
var fourth : Long = 32L
|
|
||||||
for(str <- tutorial_list) {
|
|
||||||
fourth += CharacterData.stringBitSize(str)
|
|
||||||
}
|
|
||||||
first + second + third + fourth + inventory.bitsize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object CharacterData extends Marshallable[CharacterData] {
|
object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
||||||
private def stringBitSize(str : String, width : Int = 8) : Long = {
|
implicit val codec : Codec[CharacterAppearanceData] = (
|
||||||
val strlen = str.length
|
|
||||||
val lenSize = if(strlen > 127) 16L else 8L
|
|
||||||
lenSize + strlen * width
|
|
||||||
}
|
|
||||||
|
|
||||||
implicit val codec : Codec[CharacterData] = (
|
|
||||||
("pos" | Vector3.codec_pos) ::
|
("pos" | Vector3.codec_pos) ::
|
||||||
ignore(16) ::
|
ignore(16) ::
|
||||||
("objYaw" | uint8L) ::
|
("objYaw" | uint8L) ::
|
||||||
|
|
@ -91,12 +45,215 @@ object CharacterData extends Marshallable[CharacterData] {
|
||||||
ignore(42) ::
|
ignore(42) ::
|
||||||
("unk2" | uint16L) ::
|
("unk2" | uint16L) ::
|
||||||
ignore(30) ::
|
ignore(30) ::
|
||||||
("unk3" | uintL(4)) ::
|
("unk3" | uint4L) ::
|
||||||
ignore(24) ::
|
ignore(24) ::
|
||||||
("viewPitch" | uint8L) ::
|
("viewPitch" | uint8L) ::
|
||||||
("viewYaw" | uint8L) ::
|
("viewYaw" | uint8L) ::
|
||||||
ignore(10) ::
|
ignore(10) ::
|
||||||
("ribbons" | RibbonBars.codec) ::
|
("ribbons" | RibbonBars.codec)
|
||||||
|
).as[CharacterAppearanceData]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of the avatar portion of `ObjectCreateMessage` packet data.<br>
|
||||||
|
* <br>
|
||||||
|
* This object is huge, representing the quantity of densely-encoded data in its packet.
|
||||||
|
* Although the actual organization is ill-defined, the packet can be divided into seven parts.
|
||||||
|
* The first part maintains information about the avatar as a game object in the game environment.
|
||||||
|
* The second part maintains information as an ongoing representation of the avatar.
|
||||||
|
* This includes fixed details like name and gender, though it also includes mutable aspects like exosuit type.
|
||||||
|
* The third part maintains information about career in the game.
|
||||||
|
* The fourth part maintains miscellaneous status and pose information.
|
||||||
|
* The fifth part maintains part of the statistical information about participation in the game.
|
||||||
|
* The sixth part maintains a stream of typically zero'd unknown information.
|
||||||
|
* The seventh part maintains the inventory.
|
||||||
|
* The fifth and seventh parts can inflate the size of packet significantly due to their encoding.
|
||||||
|
* The fifth, in particular, is string data that can number in the hundreds of strings(!).<br>
|
||||||
|
* <br>
|
||||||
|
* Ignoring the strings, lists of strings, and the inventory, the base length of the packet is currently __1138__ bits.
|
||||||
|
* Some undefined bits in the packet can change the length of the packet by being set or unset.
|
||||||
|
* This will mess with the encoding and the decoding of later fields.
|
||||||
|
* Any data that is padded for byte-alignment will also have its padding adjusted.
|
||||||
|
* Each string adds either 8 or 16, plus an additional 8 or 16 per the number of characters.
|
||||||
|
* For the name, that's 16 per character, a minimum of two characters, plus the (current) padding.
|
||||||
|
* for the first time events and tutorials, that's 8 per character, plus the (current) padding of the first entry.
|
||||||
|
* For the first time events and tutorials, however, the size of the list is always a 32-bit number.
|
||||||
|
* The formal inventory entries are preceded by 1 absolute bit.<br>
|
||||||
|
* <br>
|
||||||
|
* The adjusted base length is therefore __1203__ bits (1138 + 32 + 32 + 1).
|
||||||
|
* Of that, __720__ bits are unknown.
|
||||||
|
* Including the values that are defaulted, __831__ bits are perfectly unknown.
|
||||||
|
* This data is accurate as of 2016-12-07.<br>
|
||||||
|
* <br>
|
||||||
|
* Faction:<br>
|
||||||
|
* `0 - Terran Republic`<br>
|
||||||
|
* `1 - New Comglomerate`<br>
|
||||||
|
* `2 - Vanu Sovereignty`<br>
|
||||||
|
* <br>
|
||||||
|
* Exosuit:<br>
|
||||||
|
* `0 - Agile`<br>
|
||||||
|
* `1 - Refinforced`<br>
|
||||||
|
* `2 - Mechanized Assault`<br>
|
||||||
|
* `3 - Infiltration`<br>
|
||||||
|
* `4 - Standard`<br>
|
||||||
|
* <br>
|
||||||
|
* Sex:<br>
|
||||||
|
* `1 - Male`<br>
|
||||||
|
* `2 - Female`<br>
|
||||||
|
* <br>
|
||||||
|
* Voice:<br>
|
||||||
|
* ` MALE FEMALE`<br>
|
||||||
|
* `0 - No voice No voice`<br>
|
||||||
|
* `1 - Male_1 Female_1`<br>
|
||||||
|
* `2 - Male_2 Female_2`<br>
|
||||||
|
* `3 - Male_3 Female_3`<br>
|
||||||
|
* `4 - Male_4 Female_4`<br>
|
||||||
|
* `5 - Male_5 Female_5`<br>
|
||||||
|
* `6 - Female_1 No voice`<br>
|
||||||
|
* `7 - Female_2 No voice`
|
||||||
|
// * @param pos the position of the character in the world environment (in three coordinates)
|
||||||
|
// * @param objYaw the angle with respect to the horizon towards which the object's front is facing;
|
||||||
|
// * every `0x1` is 2.813 degrees counter clockwise from North;
|
||||||
|
// * every `0x10` is 45-degrees;
|
||||||
|
// * it wraps at `0x80`
|
||||||
|
// * (note: references the avatar as a game object?)
|
||||||
|
// * @param faction the empire to which the avatar belongs;
|
||||||
|
// * the value scale is different from `PlanetSideEmpire`;
|
||||||
|
// * @param bops whether or not this avatar is enrolled in Black OPs
|
||||||
|
// * @param name the wide character name of the avatar
|
||||||
|
// * @param exosuit the type of exosuit the avatar will be depicted in;
|
||||||
|
// * for Black OPs, the agile exosuit and the reinforced exosuit are replaced with the Black OPs exosuits
|
||||||
|
// * @param sex whether the avatar is male or female
|
||||||
|
// * @param face1 the avatar's face, as by column number on the character creation screen
|
||||||
|
// * @param face2 the avatar's face, as by row number on the character creation screen
|
||||||
|
// * @param voice the avatar's voice selection
|
||||||
|
// * @param unk1 na;
|
||||||
|
// * defaults to `0x8080`
|
||||||
|
// * @param unk2 na;
|
||||||
|
// * defaults to `0xFFFF`;
|
||||||
|
// * may be `0x0`
|
||||||
|
// * @param unk3 na;
|
||||||
|
// * defaults to 2
|
||||||
|
// * @param viewPitch the angle with respect to the horizon towards which the avatar is looking;
|
||||||
|
// * only supports downwards view angles;
|
||||||
|
// * `0x0` is forwards-facing;
|
||||||
|
// * `0x20` to `0xFF` is downwards-facing
|
||||||
|
// * @param viewYaw the angle with respect to the ground directions towards which the avatar is looking;
|
||||||
|
// * every `0x1` is 2.813 degrees counter clockwise from North;
|
||||||
|
// * every `0x10` is 45-degrees;
|
||||||
|
// * it wraps at `0x80`
|
||||||
|
// * @param ribbons the four merit commendation ribbon medals displayed on the avatar's left pauldron
|
||||||
|
// * @param healthMax for "x / y" of hitpoints, this is the avatar's 'y' value;
|
||||||
|
// * range is 0-65535
|
||||||
|
// * @param health for "x / y" of hitpoints, this is the avatar's 'x' value;
|
||||||
|
// * range is 0-65535
|
||||||
|
// * @param armor for "x / y" of armor points, this is the avatar's 'x' value;
|
||||||
|
// * range is 0-65535;
|
||||||
|
// * the avatar's "y" armor points is tied to their exosuit type
|
||||||
|
// * @param unk4 na;
|
||||||
|
// * defaults to 1
|
||||||
|
// * @param unk5 na;
|
||||||
|
// * defaults to 7
|
||||||
|
// * @param unk6 na;
|
||||||
|
// * defaults to 7
|
||||||
|
// * @param staminaMax for "x / y" of stamina points, this is the avatar's 'y' value;
|
||||||
|
// * range is 0-65535
|
||||||
|
// * @param stamina for "x / y" of stamina points, this is the avatar's 'x' value;
|
||||||
|
// * range is 0-65535
|
||||||
|
// * @param unk7 na;
|
||||||
|
// * defaults to 28
|
||||||
|
// * @param unk8 na;
|
||||||
|
// * defaults to 4
|
||||||
|
// * @param unk9 na;
|
||||||
|
// * defaults to 44
|
||||||
|
// * @param unk10 na;
|
||||||
|
// * defaults to 84
|
||||||
|
// * @param unk11 na;
|
||||||
|
// * defaults to 104
|
||||||
|
// * @param unk12 na;
|
||||||
|
// * defaults to 1900
|
||||||
|
// * @param firstTimeEvent_length the total number of first time events performed by this avatar
|
||||||
|
// * @param firstTimeEvent_firstEntry the separated "first entry" of the list of first time events performed by this avatar
|
||||||
|
// * @param firstTimeEvent_list the list of first time events performed by this avatar
|
||||||
|
// * @param tutorial_length the total number of tutorials completed by this avatar
|
||||||
|
// * @param tutorial_firstEntry the separated "first entry" of the list of tutorials completed by this avatar
|
||||||
|
// * @param tutorial_list the list of tutorials completed by this avatar
|
||||||
|
// * @param inventory the avatar's inventory
|
||||||
|
*/
|
||||||
|
case class CharacterData(appearance : CharacterAppearanceData,
|
||||||
|
healthMax : Int,
|
||||||
|
health : Int,
|
||||||
|
armor : Int,
|
||||||
|
unk4 : Int, //1
|
||||||
|
unk5 : Int, //7
|
||||||
|
unk6 : Int, //7
|
||||||
|
staminaMax : Int,
|
||||||
|
stamina : Int,
|
||||||
|
unk7 : Int, //28
|
||||||
|
unk8 : Int, //4
|
||||||
|
unk9 : Int, //44
|
||||||
|
unk10 : Int, //84
|
||||||
|
unk11 : Int, //104
|
||||||
|
unk12 : Int, //1900
|
||||||
|
firstTimeEvents : List[String],
|
||||||
|
tutorials : List[String],
|
||||||
|
inventory : InventoryData
|
||||||
|
) extends ConstructorData {
|
||||||
|
/**
|
||||||
|
* Performs a "sizeof()" analysis of the given object.
|
||||||
|
* @see ConstructorData.bitsize
|
||||||
|
* @return the number of bits necessary to represent this object
|
||||||
|
*/
|
||||||
|
override def bitsize : Long = {
|
||||||
|
// //represents static fields (includes medals.bitsize)
|
||||||
|
// val base : Long = 1138L //TODO ongoing analysis, this value will be subject to change
|
||||||
|
// //name
|
||||||
|
// val nameSize : Long = CharacterData.stringBitSize(appearance.name, 16) + 4L //plus the current padding
|
||||||
|
// //fte_list
|
||||||
|
// var eventListSize : Long = 32L
|
||||||
|
// if(firstTimeEvent_firstEntry.isDefined) {
|
||||||
|
// eventListSize += CharacterData.stringBitSize(firstTimeEvent_firstEntry.get) + 5L //plus the current padding
|
||||||
|
// for(str <- firstTimeEvent_list) {
|
||||||
|
// eventListSize += CharacterData.stringBitSize(str)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// //tutorial list
|
||||||
|
// var tutorialListSize : Long = 32L
|
||||||
|
// for(str <- tutorial_list) {
|
||||||
|
// tutorialListSize += CharacterData.stringBitSize(str)
|
||||||
|
// }
|
||||||
|
// base + nameSize + eventListSize + tutorialListSize + inventory.bitsize
|
||||||
|
0L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object CharacterData extends Marshallable[CharacterData] {
|
||||||
|
/**
|
||||||
|
* Calculate the size of a string, including the length of the "string length" field that precedes it.
|
||||||
|
* Do not pass null-terminated strings.
|
||||||
|
* @param str a length-prefixed string
|
||||||
|
* @param width the width of the character encoding;
|
||||||
|
* defaults to the standard 8-bits
|
||||||
|
* @return the size in bits
|
||||||
|
*/
|
||||||
|
private def stringBitSize(str : String, width : Int = 8) : Long = {
|
||||||
|
val strlen = str.length
|
||||||
|
val lenSize = if(strlen > 127) 16L else 8L
|
||||||
|
lenSize + (strlen * width)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def ftePadding(len : Long) : Int = {
|
||||||
|
//TODO determine how this should be padded better
|
||||||
|
5
|
||||||
|
}
|
||||||
|
|
||||||
|
private def tutListPadding(len : Long) : Int = {
|
||||||
|
//TODO determine how this should be padded when len == 0
|
||||||
|
if(len > 0) 0 else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val codec : Codec[CharacterData] = (
|
||||||
|
("appearance" | CharacterAppearanceData.codec) ::
|
||||||
ignore(160) ::
|
ignore(160) ::
|
||||||
("healthMax" | uint16L) ::
|
("healthMax" | uint16L) ::
|
||||||
("health" | uint16L) ::
|
("health" | uint16L) ::
|
||||||
|
|
@ -118,14 +275,45 @@ object CharacterData extends Marshallable[CharacterData] {
|
||||||
("unk12" | uintL(12)) ::
|
("unk12" | uintL(12)) ::
|
||||||
ignore(19) ::
|
ignore(19) ::
|
||||||
(("firstTimeEvent_length" | uint32L) >>:~ { len =>
|
(("firstTimeEvent_length" | uint32L) >>:~ { len =>
|
||||||
conditional(len > 0, "firstEntry" | PacketHelpers.encodedStringAligned(5)) ::
|
conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned( ftePadding(len) )) ::
|
||||||
("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) ::
|
("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) ::
|
||||||
("tutorial_list" | PacketHelpers.listOfNAligned(uint32L, 0, PacketHelpers.encodedString)) ::
|
(("tutorial_length" | uint32L) >>:~ { len2 =>
|
||||||
ignore(207) ::
|
conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned( tutListPadding(len) )) ::
|
||||||
("inventory" | InventoryData.codec)
|
("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) ::
|
||||||
|
ignore(207) ::
|
||||||
|
("inventory" | InventoryData.codec)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
).as[CharacterData]
|
).xmap[CharacterData] (
|
||||||
|
{
|
||||||
|
case app :: _ :: b :: c :: _ :: d :: _ :: e :: _ :: f :: g :: h :: i :: _ :: j :: k :: l :: m :: n :: o :: _ :: p :: q :: r :: s :: t :: u :: _ :: v :: HNil =>
|
||||||
|
//prepend the displaced first elements to their lists
|
||||||
|
val fteList : List[String] = if(q.isDefined) { q.get :: r } else r
|
||||||
|
val tutList : List[String] = if(t.isDefined) { t.get :: u } else u
|
||||||
|
CharacterData(app, b, c, d, e, f, g, h, i, j, k, l, m, n, o, fteList, tutList, v)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case CharacterData(app, b, c, d, e, f, g, h, i, j, k, l, m, n, o, fteList, tutList, p) =>
|
||||||
|
//shift the first elements off their lists
|
||||||
|
var fteListCopy = fteList
|
||||||
|
var firstEvent : Option[String] = None
|
||||||
|
if(fteList.nonEmpty) {
|
||||||
|
firstEvent = Some(fteList.head)
|
||||||
|
fteListCopy = fteList.drop(1)
|
||||||
|
}
|
||||||
|
var tutListCopy = tutList
|
||||||
|
var firstTutorial : Option[String] = None
|
||||||
|
if(tutList.nonEmpty) {
|
||||||
|
firstTutorial = Some(tutList.head)
|
||||||
|
tutListCopy = tutList.drop(1)
|
||||||
|
}
|
||||||
|
app :: () :: b :: c :: () :: d :: () :: e :: () :: f :: g :: h :: i :: () :: j :: k :: l :: m :: n :: o :: () :: fteList.size.toLong :: firstEvent :: fteListCopy :: tutList.size.toLong :: firstTutorial :: tutListCopy :: () :: p :: HNil
|
||||||
|
}
|
||||||
|
).as[CharacterData]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform between CharacterData and ConstructorData.
|
||||||
|
*/
|
||||||
val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
|
val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
|
||||||
{
|
{
|
||||||
case x =>
|
case x =>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,29 @@
|
||||||
// Copyright (c) 2016 PSForever.net to present
|
// Copyright (c) 2016 PSForever.net to present
|
||||||
package net.psforever.packet.game.objectcreate
|
package net.psforever.packet.game.objectcreate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base type for the representation of any data used to produce objects from `ObjectCreateMessage` packet data.
|
||||||
|
* There is no reason to instantiate this class as-is.
|
||||||
|
* Children of this class are expected to be able to translate through `scodec` operations into packet data.<br>
|
||||||
|
* <br>
|
||||||
|
* The object data is uncoupled from the object class as multiple classes use the same format for their data.
|
||||||
|
* For example, both the Suppressor and the Gauss use a weapon data format.
|
||||||
|
* For example, both 9mm Bullets and energy cells use am ammunition data format.
|
||||||
|
*/
|
||||||
abstract class ConstructorData() {
|
abstract class ConstructorData() {
|
||||||
|
/**
|
||||||
|
* Performs a "sizeof()" analysis of the given object.
|
||||||
|
* @return the number of bits necessary to represent this object;
|
||||||
|
* reflects the `Codec` definition rather than the parameter fields;
|
||||||
|
* defaults to `0L`
|
||||||
|
*/
|
||||||
def bitsize : Long = 0L
|
def bitsize : Long = 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
object ConstructorData {
|
object ConstructorData {
|
||||||
|
/**
|
||||||
|
* This pattern is intended to provide common conversion between all of the `Codec`s of the children of this class.
|
||||||
|
* The casting will be performed through use of `exmap`.
|
||||||
|
*/
|
||||||
type genericPattern = Option[ConstructorData]
|
type genericPattern = Option[ConstructorData]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,27 +8,36 @@ import scodec.codecs._
|
||||||
import shapeless.{::, HNil}
|
import shapeless.{::, HNil}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The same kind of data as required for a formal ObjectCreateMessage but with a required and implicit parent relationship.
|
* The same kind of data as required for a formal `ObjectCreateMessage` but with a required and implicit parent relationship.
|
||||||
* Data preceding this entry will define the existence of the parent.
|
* Some data preceding this entry will clarify the existence of the parent.<br>
|
||||||
* @param objectClass na
|
* <br>
|
||||||
* @param guid na
|
* As indicated, an `InternalSlot` object is not a top-level object.
|
||||||
* @param parentSlot na
|
* This is true in relation between one object and another, as well as in how this object is sorted in the `ObjectCreateMessage` data.
|
||||||
* @param obj na
|
* The data outlined by this class encompasses the same kind as the outer-most `ObjectCreateMessage`.
|
||||||
|
* By contrast, this object always has a dedicated parent object and a known slot to be attached to that parent.
|
||||||
|
* It's not optional.
|
||||||
|
* @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
|
||||||
|
* @param obj the data used as representation of the object to be constructed
|
||||||
*/
|
*/
|
||||||
case class InternalSlot(objectClass : Int,
|
case class InternalSlot(objectClass : Int,
|
||||||
guid : PlanetSideGUID,
|
guid : PlanetSideGUID,
|
||||||
parentSlot : Int,
|
parentSlot : Int,
|
||||||
obj : Option[ConstructorData]) {
|
obj : ConstructorData) {
|
||||||
|
/**
|
||||||
|
* Performs a "sizeof()" analysis of the given object.
|
||||||
|
* @see ConstructorData.bitsize
|
||||||
|
* @return the number of bits necessary to represent this object
|
||||||
|
*/
|
||||||
def bitsize : Long = {
|
def bitsize : Long = {
|
||||||
val first : Long = if(parentSlot > 127) 44L else 36L
|
val first : Long = if(parentSlot > 127) 44L else 36L
|
||||||
val second : Long = if(obj.isDefined) obj.get.bitsize else 0L
|
val second : Long = obj.bitsize
|
||||||
first + second
|
first + second
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object InternalSlot extends Marshallable[InternalSlot] {
|
object InternalSlot extends Marshallable[InternalSlot] {
|
||||||
type objPattern = Int :: PlanetSideGUID :: Int :: ConstructorData :: HNil
|
|
||||||
|
|
||||||
implicit val codec : Codec[InternalSlot] = (
|
implicit val codec : Codec[InternalSlot] = (
|
||||||
ignore(1) :: //TODO determine what this bit does
|
ignore(1) :: //TODO determine what this bit does
|
||||||
(("objectClass" | uintL(11)) >>:~ { obj_cls =>
|
(("objectClass" | uintL(11)) >>:~ { obj_cls =>
|
||||||
|
|
@ -36,5 +45,14 @@ object InternalSlot extends Marshallable[InternalSlot] {
|
||||||
("parentSlot" | PacketHelpers.encodedStringSize) ::
|
("parentSlot" | PacketHelpers.encodedStringSize) ::
|
||||||
("obj" | ObjectClass.selectDataCodec(obj_cls))
|
("obj" | ObjectClass.selectDataCodec(obj_cls))
|
||||||
})
|
})
|
||||||
).as[InternalSlot]
|
).xmap[InternalSlot] (
|
||||||
}
|
{
|
||||||
|
case _ :: cls :: guid :: slot :: Some(obj) :: HNil =>
|
||||||
|
InternalSlot(cls, guid, slot, obj)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case InternalSlot(cls, guid, slot, obj) =>
|
||||||
|
() :: cls :: guid :: slot :: Some(obj) :: HNil
|
||||||
|
}
|
||||||
|
).as[InternalSlot]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,59 @@
|
||||||
// Copyright (c) 2016 PSForever.net to present
|
// Copyright (c) 2016 PSForever.net to present
|
||||||
package net.psforever.packet.game.objectcreate
|
package net.psforever.packet.game.objectcreate
|
||||||
|
|
||||||
import net.psforever.packet.{Marshallable, PacketHelpers}
|
import net.psforever.packet.Marshallable
|
||||||
import scodec.Codec
|
import scodec.Codec
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
|
import shapeless.{::,HNil}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of the inventory portion of `ObjectCreateMessage` packet data for avatars.<br>
|
||||||
|
* <br>
|
||||||
|
* Unfortunately, the inventory is a fail-fast greedy thing.
|
||||||
|
* Any format discrepancies will cause it to fail and that will cause character encoding to fail as well.
|
||||||
|
* Care should be taken that all possible item encodings are representable.
|
||||||
|
* @param unk1 na;
|
||||||
|
* always `true` to mark the start of the inventory data?
|
||||||
|
* @param unk2 na
|
||||||
|
* @param contents the actual items in the inventory;
|
||||||
|
* holster slots are 0-4;
|
||||||
|
* an inaccessible slot is 5;
|
||||||
|
* internal capacity is 6-`n`, where `n` is defined by exosuit type and is mapped into a grid
|
||||||
|
*/
|
||||||
case class InventoryData(unk1 : Boolean,
|
case class InventoryData(unk1 : Boolean,
|
||||||
size : Int,
|
unk2 : Boolean,
|
||||||
unk2 : Boolean){//,
|
contents : Vector[InventoryItem]) {
|
||||||
//inv : List[InventoryItem]) {
|
/**
|
||||||
|
* Performs a "sizeof()" analysis of the given object.
|
||||||
|
* @see ConstructorData.bitsize
|
||||||
|
* @return the number of bits necessary to represent this object
|
||||||
|
*/
|
||||||
def bitsize : Long = {
|
def bitsize : Long = {
|
||||||
10L
|
//two booleans and the 8-bit length field
|
||||||
|
val first : Long = 10L
|
||||||
|
//length of all items in inventory
|
||||||
|
var second : Long = 0L
|
||||||
|
for(item <- contents) {
|
||||||
|
second += item.bitsize
|
||||||
|
}
|
||||||
|
first + second
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object InventoryData extends Marshallable[InventoryData] {
|
object InventoryData extends Marshallable[InventoryData] {
|
||||||
implicit val codec : Codec[InventoryData] = (
|
implicit val codec : Codec[InventoryData] = (
|
||||||
("unk1" | bool) ::
|
("unk1" | bool) ::
|
||||||
(("len" | uint8L) >>:~ { len =>
|
("len" | uint8L) ::
|
||||||
("unk2" | bool).hlist// ::
|
("unk2" | bool) ::
|
||||||
//("inv" | PacketHelpers.listOfNSized(len, InventoryItem.codec))
|
("contents" | vector(InventoryItem.codec))
|
||||||
})
|
).xmap[InventoryData] (
|
||||||
).as[InventoryData]
|
{
|
||||||
|
case u1 :: _ :: u2 :: vector :: HNil =>
|
||||||
|
InventoryData(u1, u2, vector)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case InventoryData(u1, u2, vector) =>
|
||||||
|
u1 :: vector.length :: u2 :: vector :: HNil
|
||||||
|
}
|
||||||
|
).as[InventoryData]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,60 @@
|
||||||
package net.psforever.packet.game.objectcreate
|
package net.psforever.packet.game.objectcreate
|
||||||
|
|
||||||
import net.psforever.packet.Marshallable
|
import net.psforever.packet.Marshallable
|
||||||
|
import net.psforever.packet.game.PlanetSideGUID
|
||||||
import scodec.Codec
|
import scodec.Codec
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
|
|
||||||
case class InventoryItem(item : InternalSlot)
|
/**
|
||||||
|
* Represent an item in inventory.<br>
|
||||||
|
* <br>
|
||||||
|
* Note the use of `InternalSlot` to indicate the implicit parent ownership of the resulting item.
|
||||||
|
* Unwinding inventory items into individual standard `ObjectCreateMessage` packet data is entirely possible.
|
||||||
|
* @param item the object in inventory
|
||||||
|
* @param na the user should not have to worry about this potential bit;
|
||||||
|
* it follows after weapon entries, allegedly
|
||||||
|
*/
|
||||||
|
case class InventoryItem(item : InternalSlot,
|
||||||
|
na : Option[Boolean] = None) {
|
||||||
|
/**
|
||||||
|
* Performs a "sizeof()" analysis of the given object.
|
||||||
|
* @see ConstructorData.bitsize
|
||||||
|
* @return the number of bits necessary to represent this object
|
||||||
|
*/
|
||||||
|
def bitsize : Long = {
|
||||||
|
//item
|
||||||
|
val first : Long = item.bitsize
|
||||||
|
//trailing bit
|
||||||
|
val second : Long = if(na.isDefined) 1L else 0L
|
||||||
|
first + second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
object InventoryItem extends Marshallable[InventoryItem] {
|
object InventoryItem extends Marshallable[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
|
||||||
|
*/
|
||||||
|
def apply(objClass : Int, guid : PlanetSideGUID, parentSlot : Int, obj : ConstructorData) : InventoryItem = {
|
||||||
|
val isWep = if(obj.isInstanceOf[WeaponData]) Some(false) else None
|
||||||
|
//TODO is this always Some(false)?
|
||||||
|
InventoryItem(InternalSlot(objClass, guid, parentSlot, obj), isWep)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the allocated item is a weapon.
|
||||||
|
* @param itm the inventory item
|
||||||
|
* @return true, if the item is a weapon; false, otherwise
|
||||||
|
*/
|
||||||
|
def wasWeapon(itm : InternalSlot) : Boolean = itm.obj.isInstanceOf[WeaponData]
|
||||||
|
|
||||||
implicit val codec : Codec[InventoryItem] = (
|
implicit val codec : Codec[InventoryItem] = (
|
||||||
"item" | InternalSlot.codec
|
("item" | InternalSlot.codec) >>:~ { item =>
|
||||||
).as[InventoryItem]
|
conditional(wasWeapon(item), bool).hlist
|
||||||
|
}
|
||||||
|
).as[InventoryItem]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,37 +6,68 @@ import scodec.codecs._
|
||||||
|
|
||||||
import scala.annotation.switch
|
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 (probably) `2047`, always translating into an 11-bit value.
|
||||||
|
* They are recorded as little-endian hexadecimal values here.
|
||||||
|
*/
|
||||||
object ObjectClass {
|
object ObjectClass {
|
||||||
//character
|
//character
|
||||||
final val PLAYER = 0x79
|
final val AVATAR = 0x79
|
||||||
//ammunition
|
//ammunition
|
||||||
final val BULLETS_9MM = 0x1C
|
final val BULLETS_9MM = 0x1C
|
||||||
final val BULLETS_9MM_AP = 0x1D
|
final val BULLETS_9MM_AP = 0x1D
|
||||||
final val ENERGY_CELL = 0x110
|
final val ENERGY_CELL = 0x110
|
||||||
|
final val JAMMER_GRENADE_AMMO = 0x1A1
|
||||||
final val FORCE_BLADE_AMMO = 0x21C
|
final val FORCE_BLADE_AMMO = 0x21C
|
||||||
|
final val PLASMA_GRENADE_AMMO = 0x2A9
|
||||||
|
final val BUCKSHOT = 0x2F3 //TODO apply internal name
|
||||||
//weapons
|
//weapons
|
||||||
|
final val SUPPRESSOR = 0x34D
|
||||||
final val BEAMER = 0x8C
|
final val BEAMER = 0x8C
|
||||||
|
final val SWEEPER = 0x130
|
||||||
final val FORCE_BLADE = 0x144
|
final val FORCE_BLADE = 0x144
|
||||||
final val GAUSS = 0x159
|
final val GAUSS = 0x159
|
||||||
final val SUPPRESSOR = 0x34D
|
final val JAMMER_GRENADE = 0x1A0
|
||||||
|
final val PLASMA_GRENADE = 0x2A8
|
||||||
//tools
|
//tools
|
||||||
|
final val MEDKIT = 0x218
|
||||||
final val REK = 0x2D8
|
final val REK = 0x2D8
|
||||||
//unknown
|
//unknown
|
||||||
final val SLOT_BLOCKER = 0x1C8
|
final val SLOT_BLOCKER = 0x1C8 //strange item found in slot #5, between holsters and inventory
|
||||||
|
|
||||||
|
//TODO refactor this function into another object later
|
||||||
|
/**
|
||||||
|
* Given an object class, retrieve the `Codec` used to parse and translate the constructor data for that type.<br>
|
||||||
|
* <br>
|
||||||
|
* This function serves as a giant `switch` statement that loosely connects object data to object class.
|
||||||
|
* All entries, save the default, merely point to the `Codec` of pattern `ConstructorData.genericPattern`.
|
||||||
|
* This pattern connects all `Codec`s back to the superclass `ConstructorData`.
|
||||||
|
* The default case is a failure case for trying to either decode or encode an unknown class of object.
|
||||||
|
* @param objClass the code for the type of object being constructed
|
||||||
|
* @return the `Codec` that handles the format of data for that particular item class, or a failing codec
|
||||||
|
*/
|
||||||
def selectDataCodec(objClass : Int) : Codec[ConstructorData.genericPattern] = {
|
def selectDataCodec(objClass : Int) : Codec[ConstructorData.genericPattern] = {
|
||||||
(objClass : @switch) match {
|
(objClass : @switch) match {
|
||||||
case ObjectClass.PLAYER => CharacterData.genericCodec
|
case ObjectClass.AVATAR => CharacterData.genericCodec
|
||||||
|
case ObjectClass.BEAMER => WeaponData.genericCodec
|
||||||
|
case ObjectClass.BUCKSHOT => AmmoBoxData.genericCodec
|
||||||
case ObjectClass.BULLETS_9MM => AmmoBoxData.genericCodec
|
case ObjectClass.BULLETS_9MM => AmmoBoxData.genericCodec
|
||||||
case ObjectClass.BULLETS_9MM_AP => AmmoBoxData.genericCodec
|
case ObjectClass.BULLETS_9MM_AP => AmmoBoxData.genericCodec
|
||||||
case ObjectClass.ENERGY_CELL => AmmoBoxData.genericCodec
|
case ObjectClass.ENERGY_CELL => AmmoBoxData.genericCodec
|
||||||
case ObjectClass.FORCE_BLADE_AMMO => AmmoBoxData.genericCodec
|
case ObjectClass.FORCE_BLADE_AMMO => AmmoBoxData.genericCodec
|
||||||
case ObjectClass.BEAMER => WeaponData.genericCodec
|
|
||||||
case ObjectClass.FORCE_BLADE => WeaponData.genericCodec
|
case ObjectClass.FORCE_BLADE => WeaponData.genericCodec
|
||||||
case ObjectClass.GAUSS => WeaponData.genericCodec
|
case ObjectClass.GAUSS => WeaponData.genericCodec
|
||||||
case ObjectClass.SUPPRESSOR => WeaponData.genericCodec
|
case ObjectClass.JAMMER_GRENADE => WeaponData.genericCodec
|
||||||
|
case ObjectClass.JAMMER_GRENADE_AMMO => AmmoBoxData.genericCodec
|
||||||
|
case ObjectClass.MEDKIT => AmmoBoxData.genericCodec
|
||||||
|
case ObjectClass.PLASMA_GRENADE => WeaponData.genericCodec
|
||||||
|
case ObjectClass.PLASMA_GRENADE_AMMO => AmmoBoxData.genericCodec
|
||||||
case ObjectClass.REK => REKData.genericCodec
|
case ObjectClass.REK => REKData.genericCodec
|
||||||
case ObjectClass.SLOT_BLOCKER => AmmoBoxData.genericCodec
|
case ObjectClass.SLOT_BLOCKER => AmmoBoxData.genericCodec
|
||||||
|
case ObjectClass.SUPPRESSOR => WeaponData.genericCodec
|
||||||
|
case ObjectClass.SWEEPER => WeaponData.genericCodec
|
||||||
//failure case
|
//failure case
|
||||||
case _ => conditional(false, bool).exmap[ConstructorData.genericPattern] (
|
case _ => conditional(false, bool).exmap[ConstructorData.genericPattern] (
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,18 @@ import scodec.{Attempt, Codec, Err}
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
import shapeless.{::, HNil}
|
import shapeless.{::, HNil}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of the REK portion of `ObjectCreateMessage` packet data.
|
||||||
|
* When alone, this data will help construct the "tool" called a Remote Electronics Kit.
|
||||||
|
* @param unk na
|
||||||
|
*/
|
||||||
case class REKData(unk : Int
|
case class REKData(unk : Int
|
||||||
) extends ConstructorData {
|
) extends ConstructorData {
|
||||||
|
/**
|
||||||
|
* Performs a "sizeof()" analysis of the given object.
|
||||||
|
* @see ConstructorData.bitsize
|
||||||
|
* @return the number of bits necessary to represent this object
|
||||||
|
*/
|
||||||
override def bitsize : Long = 72L
|
override def bitsize : Long = 72L
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,8 +43,9 @@ object REKData extends Marshallable[REKData] {
|
||||||
}
|
}
|
||||||
).as[REKData]
|
).as[REKData]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform between REKData and ConstructorData.
|
||||||
|
*/
|
||||||
val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
|
val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
|
||||||
{
|
{
|
||||||
case x =>
|
case x =>
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,26 @@ import net.psforever.packet.Marshallable
|
||||||
import scodec.Codec
|
import scodec.Codec
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
|
|
||||||
case class RibbonBars(upper : Long = 0xFFFFFFFFL, //0xFFFFFFFF means no merit (for all ...)
|
/**
|
||||||
|
* Enumerate the player-displayed merit commendation awards granted for excellence (or tenacity) in combat.
|
||||||
|
* These are the medals players wish to brandish on their left pauldron.<br>
|
||||||
|
* <br>
|
||||||
|
* All merit commendation ribbons are represented by a 32-bit signature.
|
||||||
|
* The default "no-ribbon" value is `0xFFFFFFFF`.
|
||||||
|
* @param upper the "top" configurable merit ribbon
|
||||||
|
* @param middle the central configurable merit ribbon
|
||||||
|
* @param lower the lower configurable merit ribbon
|
||||||
|
* @param tos the automatic top-most term of service merit ribbon
|
||||||
|
*/
|
||||||
|
case class RibbonBars(upper : Long = 0xFFFFFFFFL,
|
||||||
middle : Long = 0xFFFFFFFFL,
|
middle : Long = 0xFFFFFFFFL,
|
||||||
lower : Long = 0xFFFFFFFFL,
|
lower : Long = 0xFFFFFFFFL,
|
||||||
tos : Long = 0xFFFFFFFFL) {
|
tos : Long = 0xFFFFFFFFL) {
|
||||||
|
/**
|
||||||
|
* Performs a "sizeof()" analysis of the given object.
|
||||||
|
* @see ConstructorData.bitsize
|
||||||
|
* @return the number of bits necessary to represent this object
|
||||||
|
*/
|
||||||
def bitsize : Long = 128L
|
def bitsize : Long = 128L
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,42 @@ import scodec.{Attempt, Codec, Err}
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
import shapeless.{::, HNil}
|
import shapeless.{::, HNil}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of the weapon portion of `ObjectCreateMessage` packet data.
|
||||||
|
* When alone, this data will help construct a "weapon" such as Suppressor.<br>
|
||||||
|
* <br>
|
||||||
|
* The data for the weapon also nests required default ammunition data.
|
||||||
|
* Where the ammunition is loaded is considered the "first slot."
|
||||||
|
* @param unk na
|
||||||
|
* @param ammo data regarding the currently loaded ammunition type and quantity
|
||||||
|
* @see AmmoBoxData
|
||||||
|
*/
|
||||||
case class WeaponData(unk : Int,
|
case class WeaponData(unk : Int,
|
||||||
ammo : InternalSlot) extends ConstructorData {
|
ammo : InternalSlot) extends ConstructorData {
|
||||||
|
/**
|
||||||
|
* Performs a "sizeof()" analysis of the given object.
|
||||||
|
* @see ConstructorData.bitsize
|
||||||
|
* @see AmmoBoxData.bitsize
|
||||||
|
* @return the number of bits necessary to represent this object
|
||||||
|
*/
|
||||||
override def bitsize : Long = 59L + ammo.bitsize
|
override def bitsize : Long = 59L + ammo.bitsize
|
||||||
}
|
}
|
||||||
|
|
||||||
object WeaponData extends Marshallable[WeaponData] {
|
object WeaponData extends Marshallable[WeaponData] {
|
||||||
|
/**
|
||||||
|
* An abbreviated constructor for creating `WeaponData` while masking use of `InternalSlot` for its `AmmoBoxData`.<br>
|
||||||
|
* <br>
|
||||||
|
* Exploration:<br>
|
||||||
|
* This class may need to be rewritten later to support objects spawned in the world environment.
|
||||||
|
* @param unk 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 WeaponData object
|
||||||
|
*/
|
||||||
def apply(unk : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : AmmoBoxData) : WeaponData =
|
def apply(unk : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : AmmoBoxData) : WeaponData =
|
||||||
new WeaponData(unk, InternalSlot(cls, guid, parentSlot, Some(ammo)))
|
new WeaponData(unk, InternalSlot(cls, guid, parentSlot, ammo))
|
||||||
|
|
||||||
implicit val codec : Codec[WeaponData] = (
|
implicit val codec : Codec[WeaponData] = (
|
||||||
("unk" | uint4L) ::
|
("unk" | uint4L) ::
|
||||||
|
|
@ -37,8 +65,9 @@ object WeaponData extends Marshallable[WeaponData] {
|
||||||
}
|
}
|
||||||
).as[WeaponData]
|
).as[WeaponData]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform between WeaponData and ConstructorData.
|
||||||
|
*/
|
||||||
val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
|
val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
|
||||||
{
|
{
|
||||||
case x =>
|
case x =>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import net.psforever.packet._
|
||||||
import net.psforever.packet.game._
|
import net.psforever.packet.game._
|
||||||
import net.psforever.packet.game.objectcreate._
|
import net.psforever.packet.game.objectcreate._
|
||||||
import net.psforever.types._
|
import net.psforever.types._
|
||||||
import scodec.Attempt
|
import scodec.{Attempt, Err}
|
||||||
import scodec.Attempt.Successful
|
import scodec.Attempt.Successful
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
|
|
||||||
|
|
@ -164,16 +164,16 @@ class GamePacketTest extends Specification {
|
||||||
val invData = InventoryItem.codec.decode(invTestWep.toBitVector.drop(1)).toOption
|
val invData = InventoryItem.codec.decode(invTestWep.toBitVector.drop(1)).toOption
|
||||||
invData.isDefined mustEqual true
|
invData.isDefined mustEqual true
|
||||||
|
|
||||||
InventoryData.codec.decode(invTest.toBitVector.drop(7)).toOption match {
|
// InventoryData.codec.decode(invTest.toBitVector.drop(7)) match {
|
||||||
case Some(x) =>
|
// case Attempt.Successful(x) =>
|
||||||
x.value.unk1 equals true
|
// x.value.unk1 equals true
|
||||||
x.value.size mustEqual 1
|
// x.value.size mustEqual 1
|
||||||
x.value.unk2 mustEqual false
|
// x.value.unk2 mustEqual false
|
||||||
//x.value.inv.head.item.objectClass mustEqual 0x8C
|
// //x.value.inv.head.item.objectClass mustEqual 0x8C
|
||||||
//x.value.inv.head.na mustEqual false
|
// //x.value.inv.head.na mustEqual false
|
||||||
case _ =>
|
// case Attempt.Failure(x) =>
|
||||||
ko
|
// x.message mustEqual ""
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
"decode (2)" in {
|
"decode (2)" in {
|
||||||
|
|
@ -190,7 +190,7 @@ class GamePacketTest extends Specification {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"decode (char)" in {
|
"decode (character)" in {
|
||||||
PacketCoding.DecodePacket(string_testchar).require match {
|
PacketCoding.DecodePacket(string_testchar).require match {
|
||||||
case obj @ ObjectCreateMessage(len, cls, guid, parent, data) =>
|
case obj @ ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||||
len mustEqual 3159
|
len mustEqual 3159
|
||||||
|
|
@ -200,27 +200,27 @@ class GamePacketTest extends Specification {
|
||||||
data.isDefined mustEqual true
|
data.isDefined mustEqual true
|
||||||
|
|
||||||
val char = data.get.asInstanceOf[CharacterData]
|
val char = data.get.asInstanceOf[CharacterData]
|
||||||
char.pos.x mustEqual 3674.8438f
|
char.appearance.pos.x mustEqual 3674.8438f
|
||||||
char.pos.y mustEqual 2726.789f
|
char.appearance.pos.y mustEqual 2726.789f
|
||||||
char.pos.z mustEqual 91.15625f
|
char.appearance.pos.z mustEqual 91.15625f
|
||||||
char.objYaw mustEqual 19
|
char.appearance.objYaw mustEqual 19
|
||||||
char.faction mustEqual 2 //vs
|
char.appearance.faction mustEqual 2 //vs
|
||||||
char.bops mustEqual false
|
char.appearance.bops mustEqual false
|
||||||
char.name mustEqual "IlllIIIlllIlIllIlllIllI"
|
char.appearance.name mustEqual "IlllIIIlllIlIllIlllIllI"
|
||||||
char.exosuit mustEqual 4 //standard
|
char.appearance.exosuit mustEqual 4 //standard
|
||||||
char.sex mustEqual 2 //female
|
char.appearance.sex mustEqual 2 //female
|
||||||
char.face1 mustEqual 2
|
char.appearance.face1 mustEqual 2
|
||||||
char.face2 mustEqual 9
|
char.appearance.face2 mustEqual 9
|
||||||
char.voice mustEqual 1 //female 1
|
char.appearance.voice mustEqual 1 //female 1
|
||||||
char.unk1 mustEqual 0x8080
|
char.appearance.unk1 mustEqual 0x8080
|
||||||
char.unk2 mustEqual 0xFFFF
|
char.appearance.unk2 mustEqual 0xFFFF
|
||||||
char.unk3 mustEqual 2
|
char.appearance.unk3 mustEqual 2
|
||||||
char.viewPitch mustEqual 0xFF
|
char.appearance.viewPitch mustEqual 0xFF
|
||||||
char.viewYaw mustEqual 0x6A
|
char.appearance.viewYaw mustEqual 0x6A
|
||||||
char.ribbons.upper mustEqual 0xFFFFFFFFL //none
|
char.appearance.ribbons.upper mustEqual 0xFFFFFFFFL //none
|
||||||
char.ribbons.middle mustEqual 0xFFFFFFFFL //none
|
char.appearance.ribbons.middle mustEqual 0xFFFFFFFFL //none
|
||||||
char.ribbons.lower mustEqual 0xFFFFFFFFL //none
|
char.appearance.ribbons.lower mustEqual 0xFFFFFFFFL //none
|
||||||
char.ribbons.tos mustEqual 0xFFFFFFFFL //none
|
char.appearance.ribbons.tos mustEqual 0xFFFFFFFFL //none
|
||||||
char.healthMax mustEqual 100
|
char.healthMax mustEqual 100
|
||||||
char.health mustEqual 100
|
char.health mustEqual 100
|
||||||
char.armor mustEqual 50 //standard exosuit value
|
char.armor mustEqual 50 //standard exosuit value
|
||||||
|
|
@ -235,16 +235,78 @@ class GamePacketTest extends Specification {
|
||||||
char.unk10 mustEqual 84
|
char.unk10 mustEqual 84
|
||||||
char.unk11 mustEqual 104
|
char.unk11 mustEqual 104
|
||||||
char.unk12 mustEqual 1900
|
char.unk12 mustEqual 1900
|
||||||
char.firstTimeEvent_length mustEqual 4
|
char.firstTimeEvents.size mustEqual 4
|
||||||
char.firstEntry mustEqual Some("xpe_sanctuary_help")
|
char.firstTimeEvents.head mustEqual "xpe_sanctuary_help"
|
||||||
char.firstTimeEvent_list.size mustEqual 3
|
char.firstTimeEvents(1) mustEqual "xpe_th_firemodes"
|
||||||
char.firstTimeEvent_list.head mustEqual "xpe_th_firemodes"
|
char.firstTimeEvents(2) mustEqual "used_beamer"
|
||||||
char.firstTimeEvent_list(1) mustEqual "used_beamer"
|
char.firstTimeEvents(3) mustEqual "map13"
|
||||||
char.firstTimeEvent_list(2) mustEqual "map13"
|
char.tutorials.size mustEqual 0
|
||||||
char.tutorial_list.size mustEqual 0
|
|
||||||
char.inventory.unk1 mustEqual true
|
char.inventory.unk1 mustEqual true
|
||||||
char.inventory.size mustEqual 10
|
|
||||||
char.inventory.unk2 mustEqual false
|
char.inventory.unk2 mustEqual false
|
||||||
|
char.inventory.contents.length mustEqual 10
|
||||||
|
val inventory = char.inventory.contents
|
||||||
|
//0
|
||||||
|
inventory.head.item.objectClass mustEqual 0x8C //beamer
|
||||||
|
inventory.head.item.guid mustEqual PlanetSideGUID(76)
|
||||||
|
inventory.head.item.parentSlot mustEqual 0
|
||||||
|
var wep = inventory.head.item.obj.asInstanceOf[WeaponData]
|
||||||
|
wep.ammo.objectClass mustEqual 0x110 //plasma
|
||||||
|
wep.ammo.guid mustEqual PlanetSideGUID(77)
|
||||||
|
wep.ammo.parentSlot mustEqual 0
|
||||||
|
wep.ammo.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 16
|
||||||
|
//1
|
||||||
|
inventory(1).item.objectClass mustEqual 0x34D //suppressor
|
||||||
|
inventory(1).item.guid mustEqual PlanetSideGUID(78)
|
||||||
|
inventory(1).item.parentSlot mustEqual 2
|
||||||
|
wep = inventory(1).item.obj.asInstanceOf[WeaponData]
|
||||||
|
wep.ammo.objectClass mustEqual 0x1C //9mm
|
||||||
|
wep.ammo.guid mustEqual PlanetSideGUID(79)
|
||||||
|
wep.ammo.parentSlot mustEqual 0
|
||||||
|
wep.ammo.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 25
|
||||||
|
//2
|
||||||
|
inventory(2).item.objectClass mustEqual 0x144 //force blade
|
||||||
|
inventory(2).item.guid mustEqual PlanetSideGUID(80)
|
||||||
|
inventory(2).item.parentSlot mustEqual 4
|
||||||
|
wep = inventory(2).item.obj.asInstanceOf[WeaponData]
|
||||||
|
wep.ammo.objectClass mustEqual 0x21C //force blade ammo
|
||||||
|
wep.ammo.guid mustEqual PlanetSideGUID(81)
|
||||||
|
wep.ammo.parentSlot mustEqual 0
|
||||||
|
wep.ammo.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 1
|
||||||
|
//3
|
||||||
|
inventory(3).item.objectClass mustEqual 0x1C8 //thing
|
||||||
|
inventory(3).item.guid mustEqual PlanetSideGUID(82)
|
||||||
|
inventory(3).item.parentSlot mustEqual 5
|
||||||
|
inventory(3).item.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 1
|
||||||
|
//4
|
||||||
|
inventory(4).item.objectClass mustEqual 0x1C //9mm
|
||||||
|
inventory(4).item.guid mustEqual PlanetSideGUID(83)
|
||||||
|
inventory(4).item.parentSlot mustEqual 6
|
||||||
|
inventory(4).item.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 50
|
||||||
|
//5
|
||||||
|
inventory(5).item.objectClass mustEqual 0x1C //9mm
|
||||||
|
inventory(5).item.guid mustEqual PlanetSideGUID(84)
|
||||||
|
inventory(5).item.parentSlot mustEqual 9
|
||||||
|
inventory(5).item.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 50
|
||||||
|
//6
|
||||||
|
inventory(6).item.objectClass mustEqual 0x1C //9mm
|
||||||
|
inventory(6).item.guid mustEqual PlanetSideGUID(85)
|
||||||
|
inventory(6).item.parentSlot mustEqual 12
|
||||||
|
inventory(6).item.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 50
|
||||||
|
//7
|
||||||
|
inventory(7).item.objectClass mustEqual 0x1D //9mm ap
|
||||||
|
inventory(7).item.guid mustEqual PlanetSideGUID(86)
|
||||||
|
inventory(7).item.parentSlot mustEqual 33
|
||||||
|
inventory(7).item.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 50
|
||||||
|
//8
|
||||||
|
inventory(8).item.objectClass mustEqual 0x110 //plasma
|
||||||
|
inventory(8).item.guid mustEqual PlanetSideGUID(87)
|
||||||
|
inventory(8).item.parentSlot mustEqual 36
|
||||||
|
inventory(8).item.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 50
|
||||||
|
//9
|
||||||
|
inventory(9).item.objectClass mustEqual 0x2D8 //rek
|
||||||
|
inventory(9).item.guid mustEqual PlanetSideGUID(88)
|
||||||
|
inventory(9).item.parentSlot mustEqual 39
|
||||||
|
//the rek has data but none worth testing here
|
||||||
case default =>
|
case default =>
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
|
|
@ -260,8 +322,7 @@ class GamePacketTest extends Specification {
|
||||||
parent.get.guid mustEqual PlanetSideGUID(75)
|
parent.get.guid mustEqual PlanetSideGUID(75)
|
||||||
parent.get.slot mustEqual 33
|
parent.get.slot mustEqual 33
|
||||||
data.isDefined mustEqual true
|
data.isDefined mustEqual true
|
||||||
val obj = data.get.asInstanceOf[AmmoBoxData]
|
data.get.asInstanceOf[AmmoBoxData].magazine mustEqual 50
|
||||||
obj.magazine mustEqual 50
|
|
||||||
case default =>
|
case default =>
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
|
|
@ -283,9 +344,7 @@ class GamePacketTest extends Specification {
|
||||||
obj_ammo.objectClass mustEqual 28
|
obj_ammo.objectClass mustEqual 28
|
||||||
obj_ammo.guid mustEqual PlanetSideGUID(1286)
|
obj_ammo.guid mustEqual PlanetSideGUID(1286)
|
||||||
obj_ammo.parentSlot mustEqual 0
|
obj_ammo.parentSlot mustEqual 0
|
||||||
obj_ammo.obj.isDefined mustEqual true
|
obj_ammo.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 30
|
||||||
val ammo = obj_ammo.obj.get.asInstanceOf[AmmoBoxData]
|
|
||||||
ammo.magazine mustEqual 30
|
|
||||||
case default =>
|
case default =>
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
|
|
@ -298,16 +357,16 @@ class GamePacketTest extends Specification {
|
||||||
}
|
}
|
||||||
|
|
||||||
"encode (9mm)" in {
|
"encode (9mm)" in {
|
||||||
val obj : ConstructorData = AmmoBoxData(50).asInstanceOf[ConstructorData]
|
val obj = AmmoBoxData(50)
|
||||||
val msg = ObjectCreateMessage(0, 28, PlanetSideGUID(1280), Some(ObjectCreateMessageParent(PlanetSideGUID(75), 33)), Some(obj))
|
val msg = ObjectCreateMessage(0, 28, PlanetSideGUID(1280), ObjectCreateMessageParent(PlanetSideGUID(75), 33), obj)
|
||||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
pkt mustEqual string_9mm
|
pkt mustEqual string_9mm
|
||||||
}
|
}
|
||||||
|
|
||||||
"encode (gauss)" in {
|
"encode (gauss)" in {
|
||||||
val obj : ConstructorData = WeaponData(4, 28, PlanetSideGUID(1286), 0, AmmoBoxData(30)).asInstanceOf[ConstructorData]
|
val obj = WeaponData(4, 28, PlanetSideGUID(1286), 0, AmmoBoxData(30))
|
||||||
val msg = ObjectCreateMessage(0, 345, PlanetSideGUID(1465), Some(ObjectCreateMessageParent(PlanetSideGUID(75), 2)), Some(obj))
|
val msg = ObjectCreateMessage(0, 345, PlanetSideGUID(1465), ObjectCreateMessageParent(PlanetSideGUID(75), 2), obj)
|
||||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
pkt mustEqual string_gauss
|
pkt mustEqual string_gauss
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue