mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-19 18:14:44 +00:00
removed Option from InternalSlot
refactored a chunk of CharacterData as an example
This commit is contained in:
parent
9adb077d8c
commit
f98a648db1
|
|
@ -15,7 +15,7 @@ import shapeless.{::, HNil}
|
|||
* The parent is a pre-existing object into which the (created) child is attached.<br>
|
||||
* <br>
|
||||
* 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 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] {
|
||||
/**
|
||||
* 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 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.
|
||||
* @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 : 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>
|
||||
* This process automates for: object encoding.<br>
|
||||
* <br>
|
||||
* Ignoring the parent data, constant field lengths have already been factored into the results.
|
||||
* That includes:
|
||||
|
|
@ -192,17 +167,26 @@ object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] {
|
|||
* the object's GUID (16u),
|
||||
* and the bit to determine if there will be parent data.
|
||||
* In total, these fields form a known fixed length of 60u.
|
||||
* @param parentInfo if defined, the parentInfo adds either 24u or 32u
|
||||
* @return the length, including the optional parent data
|
||||
* @param parentInfo if defined, information about the parent adds either 24u or 32u
|
||||
* @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 = {
|
||||
if(parentInfo.isDefined) {
|
||||
//(32u + 1u + 11u + 16u) ?+ (16u + (8u | 16u))
|
||||
if(parentInfo.get.slot > 127) 92L else 84L
|
||||
private def streamLen(parentInfo : Option[ObjectCreateMessageParent], data : ConstructorData) : Long = {
|
||||
//knowable length
|
||||
val first : Long = if(parentInfo.isDefined) {
|
||||
if(parentInfo.get.slot > 127) 92L else 84L //(32u + 1u + 11u + 16u) ?+ (16u + (8u | 16u))
|
||||
}
|
||||
else {
|
||||
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] = (
|
||||
|
|
@ -226,25 +210,27 @@ object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] {
|
|||
}
|
||||
) :+
|
||||
("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 =>
|
||||
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 =>
|
||||
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
|
||||
Attempt.successful(streamLen(par, obj) :: cls :: guid :: par :: encodeData(cls, obj) :: HNil)
|
||||
}
|
||||
).exmap[ObjectCreateMessage](
|
||||
).xmap[ObjectCreateMessage] (
|
||||
{
|
||||
case len :: cls :: guid :: par :: obj :: HNil =>
|
||||
Attempt.successful(ObjectCreateMessage(len, cls, guid, par, obj))
|
||||
}, {
|
||||
case ObjectCreateMessage(_, _, _, _, None) =>
|
||||
Attempt.failure(Err("no object to encode"))
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,21 @@ import scodec.{Attempt, Codec, Err}
|
|||
import scodec.codecs._
|
||||
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
|
||||
) 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
|
||||
}
|
||||
|
||||
|
|
@ -29,6 +42,9 @@ object AmmoBoxData extends Marshallable[AmmoBoxData] {
|
|||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Transform between AmmoBoxData and ConstructorData.
|
||||
*/
|
||||
val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
|
||||
{
|
||||
case x =>
|
||||
|
|
|
|||
|
|
@ -5,73 +5,27 @@ import net.psforever.packet.{Marshallable, PacketHelpers}
|
|||
import net.psforever.types.Vector3
|
||||
import scodec.{Attempt, Codec, Err}
|
||||
import scodec.codecs._
|
||||
import shapeless.{::, HNil}
|
||||
|
||||
case class CharacterData(pos : Vector3,
|
||||
objYaw : Int,
|
||||
faction : Int,
|
||||
bops : Boolean,
|
||||
name : String,
|
||||
exosuit : Int,
|
||||
sex : Int,
|
||||
face1 : Int,
|
||||
face2 : Int,
|
||||
voice : Int,
|
||||
unk1 : Int, //0x8080
|
||||
unk2 : Int, //0xFFFF or 0x0
|
||||
unk3 : Int, //2
|
||||
viewPitch : Int,
|
||||
viewYaw : Int,
|
||||
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
|
||||
}
|
||||
}
|
||||
case class CharacterAppearanceData(pos : Vector3,
|
||||
objYaw : Int,
|
||||
faction : Int,
|
||||
bops : Boolean,
|
||||
name : String,
|
||||
exosuit : Int,
|
||||
sex : Int,
|
||||
face1 : Int,
|
||||
face2 : Int,
|
||||
voice : Int,
|
||||
unk1 : Int, //0x8080
|
||||
unk2 : Int, //0xFFFF or 0x0
|
||||
unk3 : Int, //2
|
||||
viewPitch : Int,
|
||||
viewYaw : Int,
|
||||
ribbons : RibbonBars)
|
||||
|
||||
object CharacterData extends Marshallable[CharacterData] {
|
||||
private def stringBitSize(str : String, width : Int = 8) : Long = {
|
||||
val strlen = str.length
|
||||
val lenSize = if(strlen > 127) 16L else 8L
|
||||
lenSize + strlen * width
|
||||
}
|
||||
|
||||
implicit val codec : Codec[CharacterData] = (
|
||||
object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
||||
implicit val codec : Codec[CharacterAppearanceData] = (
|
||||
("pos" | Vector3.codec_pos) ::
|
||||
ignore(16) ::
|
||||
("objYaw" | uint8L) ::
|
||||
|
|
@ -91,12 +45,215 @@ object CharacterData extends Marshallable[CharacterData] {
|
|||
ignore(42) ::
|
||||
("unk2" | uint16L) ::
|
||||
ignore(30) ::
|
||||
("unk3" | uintL(4)) ::
|
||||
("unk3" | uint4L) ::
|
||||
ignore(24) ::
|
||||
("viewPitch" | uint8L) ::
|
||||
("viewYaw" | uint8L) ::
|
||||
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) ::
|
||||
("healthMax" | uint16L) ::
|
||||
("health" | uint16L) ::
|
||||
|
|
@ -118,14 +275,45 @@ object CharacterData extends Marshallable[CharacterData] {
|
|||
("unk12" | uintL(12)) ::
|
||||
ignore(19) ::
|
||||
(("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)) ::
|
||||
("tutorial_list" | PacketHelpers.listOfNAligned(uint32L, 0, PacketHelpers.encodedString)) ::
|
||||
ignore(207) ::
|
||||
("inventory" | InventoryData.codec)
|
||||
(("tutorial_length" | uint32L) >>:~ { len2 =>
|
||||
conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned( tutListPadding(len) )) ::
|
||||
("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] (
|
||||
{
|
||||
case x =>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,29 @@
|
|||
// Copyright (c) 2016 PSForever.net to present
|
||||
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() {
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,27 +8,36 @@ import scodec.codecs._
|
|||
import shapeless.{::, HNil}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param objectClass na
|
||||
* @param guid na
|
||||
* @param parentSlot na
|
||||
* @param obj na
|
||||
* The same kind of data as required for a formal `ObjectCreateMessage` but with a required and implicit parent relationship.
|
||||
* Some data preceding this entry will clarify the existence of the parent.<br>
|
||||
* <br>
|
||||
* As indicated, an `InternalSlot` object is not a top-level object.
|
||||
* This is true in relation between one object and another, as well as in how this object is sorted in the `ObjectCreateMessage` data.
|
||||
* 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,
|
||||
guid : PlanetSideGUID,
|
||||
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 = {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
object InternalSlot extends Marshallable[InternalSlot] {
|
||||
type objPattern = Int :: PlanetSideGUID :: Int :: ConstructorData :: HNil
|
||||
|
||||
implicit val codec : Codec[InternalSlot] = (
|
||||
ignore(1) :: //TODO determine what this bit does
|
||||
(("objectClass" | uintL(11)) >>:~ { obj_cls =>
|
||||
|
|
@ -36,5 +45,14 @@ object InternalSlot extends Marshallable[InternalSlot] {
|
|||
("parentSlot" | PacketHelpers.encodedStringSize) ::
|
||||
("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
|
||||
package net.psforever.packet.game.objectcreate
|
||||
|
||||
import net.psforever.packet.{Marshallable, PacketHelpers}
|
||||
import net.psforever.packet.Marshallable
|
||||
import scodec.Codec
|
||||
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,
|
||||
size : Int,
|
||||
unk2 : Boolean){//,
|
||||
//inv : List[InventoryItem]) {
|
||||
unk2 : Boolean,
|
||||
contents : Vector[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 = {
|
||||
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] {
|
||||
implicit val codec : Codec[InventoryData] = (
|
||||
("unk1" | bool) ::
|
||||
(("len" | uint8L) >>:~ { len =>
|
||||
("unk2" | bool).hlist// ::
|
||||
//("inv" | PacketHelpers.listOfNSized(len, InventoryItem.codec))
|
||||
})
|
||||
).as[InventoryData]
|
||||
("len" | uint8L) ::
|
||||
("unk2" | bool) ::
|
||||
("contents" | vector(InventoryItem.codec))
|
||||
).xmap[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
|
||||
|
||||
import net.psforever.packet.Marshallable
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import scodec.Codec
|
||||
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] {
|
||||
/**
|
||||
* 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] = (
|
||||
"item" | InternalSlot.codec
|
||||
).as[InventoryItem]
|
||||
("item" | InternalSlot.codec) >>:~ { item =>
|
||||
conditional(wasWeapon(item), bool).hlist
|
||||
}
|
||||
).as[InventoryItem]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,37 +6,68 @@ 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 (probably) `2047`, always translating into an 11-bit value.
|
||||
* They are recorded as little-endian hexadecimal values here.
|
||||
*/
|
||||
object ObjectClass {
|
||||
//character
|
||||
final val PLAYER = 0x79
|
||||
final val AVATAR = 0x79
|
||||
//ammunition
|
||||
final val BULLETS_9MM = 0x1C
|
||||
final val BULLETS_9MM_AP = 0x1D
|
||||
final val ENERGY_CELL = 0x110
|
||||
final val JAMMER_GRENADE_AMMO = 0x1A1
|
||||
final val FORCE_BLADE_AMMO = 0x21C
|
||||
final val PLASMA_GRENADE_AMMO = 0x2A9
|
||||
final val BUCKSHOT = 0x2F3 //TODO apply internal name
|
||||
//weapons
|
||||
final val SUPPRESSOR = 0x34D
|
||||
final val BEAMER = 0x8C
|
||||
final val SWEEPER = 0x130
|
||||
final val FORCE_BLADE = 0x144
|
||||
final val GAUSS = 0x159
|
||||
final val SUPPRESSOR = 0x34D
|
||||
final val JAMMER_GRENADE = 0x1A0
|
||||
final val PLASMA_GRENADE = 0x2A8
|
||||
//tools
|
||||
final val MEDKIT = 0x218
|
||||
final val REK = 0x2D8
|
||||
//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] = {
|
||||
(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_AP => AmmoBoxData.genericCodec
|
||||
case ObjectClass.ENERGY_CELL => AmmoBoxData.genericCodec
|
||||
case ObjectClass.FORCE_BLADE_AMMO => AmmoBoxData.genericCodec
|
||||
case ObjectClass.BEAMER => WeaponData.genericCodec
|
||||
case ObjectClass.FORCE_BLADE => 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.SLOT_BLOCKER => AmmoBoxData.genericCodec
|
||||
case ObjectClass.SUPPRESSOR => WeaponData.genericCodec
|
||||
case ObjectClass.SWEEPER => WeaponData.genericCodec
|
||||
//failure case
|
||||
case _ => conditional(false, bool).exmap[ConstructorData.genericPattern] (
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,8 +6,18 @@ import scodec.{Attempt, Codec, Err}
|
|||
import scodec.codecs._
|
||||
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
|
||||
) 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
|
||||
}
|
||||
|
||||
|
|
@ -33,8 +43,9 @@ object REKData extends Marshallable[REKData] {
|
|||
}
|
||||
).as[REKData]
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Transform between REKData and ConstructorData.
|
||||
*/
|
||||
val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
|
||||
{
|
||||
case x =>
|
||||
|
|
|
|||
|
|
@ -5,10 +5,26 @@ import net.psforever.packet.Marshallable
|
|||
import scodec.Codec
|
||||
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,
|
||||
lower : 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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,14 +7,42 @@ import scodec.{Attempt, Codec, Err}
|
|||
import scodec.codecs._
|
||||
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,
|
||||
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
|
||||
}
|
||||
|
||||
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 =
|
||||
new WeaponData(unk, InternalSlot(cls, guid, parentSlot, Some(ammo)))
|
||||
new WeaponData(unk, InternalSlot(cls, guid, parentSlot, ammo))
|
||||
|
||||
implicit val codec : Codec[WeaponData] = (
|
||||
("unk" | uint4L) ::
|
||||
|
|
@ -37,8 +65,9 @@ object WeaponData extends Marshallable[WeaponData] {
|
|||
}
|
||||
).as[WeaponData]
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Transform between WeaponData and ConstructorData.
|
||||
*/
|
||||
val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
|
||||
{
|
||||
case x =>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import net.psforever.packet._
|
|||
import net.psforever.packet.game._
|
||||
import net.psforever.packet.game.objectcreate._
|
||||
import net.psforever.types._
|
||||
import scodec.Attempt
|
||||
import scodec.{Attempt, Err}
|
||||
import scodec.Attempt.Successful
|
||||
import scodec.bits._
|
||||
|
||||
|
|
@ -164,16 +164,16 @@ class GamePacketTest extends Specification {
|
|||
val invData = InventoryItem.codec.decode(invTestWep.toBitVector.drop(1)).toOption
|
||||
invData.isDefined mustEqual true
|
||||
|
||||
InventoryData.codec.decode(invTest.toBitVector.drop(7)).toOption match {
|
||||
case Some(x) =>
|
||||
x.value.unk1 equals true
|
||||
x.value.size mustEqual 1
|
||||
x.value.unk2 mustEqual false
|
||||
//x.value.inv.head.item.objectClass mustEqual 0x8C
|
||||
//x.value.inv.head.na mustEqual false
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
// InventoryData.codec.decode(invTest.toBitVector.drop(7)) match {
|
||||
// case Attempt.Successful(x) =>
|
||||
// x.value.unk1 equals true
|
||||
// x.value.size mustEqual 1
|
||||
// x.value.unk2 mustEqual false
|
||||
// //x.value.inv.head.item.objectClass mustEqual 0x8C
|
||||
// //x.value.inv.head.na mustEqual false
|
||||
// case Attempt.Failure(x) =>
|
||||
// x.message mustEqual ""
|
||||
// }
|
||||
}
|
||||
|
||||
"decode (2)" in {
|
||||
|
|
@ -190,7 +190,7 @@ class GamePacketTest extends Specification {
|
|||
}
|
||||
}
|
||||
|
||||
"decode (char)" in {
|
||||
"decode (character)" in {
|
||||
PacketCoding.DecodePacket(string_testchar).require match {
|
||||
case obj @ ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
len mustEqual 3159
|
||||
|
|
@ -200,27 +200,27 @@ class GamePacketTest extends Specification {
|
|||
data.isDefined mustEqual true
|
||||
|
||||
val char = data.get.asInstanceOf[CharacterData]
|
||||
char.pos.x mustEqual 3674.8438f
|
||||
char.pos.y mustEqual 2726.789f
|
||||
char.pos.z mustEqual 91.15625f
|
||||
char.objYaw mustEqual 19
|
||||
char.faction mustEqual 2 //vs
|
||||
char.bops mustEqual false
|
||||
char.name mustEqual "IlllIIIlllIlIllIlllIllI"
|
||||
char.exosuit mustEqual 4 //standard
|
||||
char.sex mustEqual 2 //female
|
||||
char.face1 mustEqual 2
|
||||
char.face2 mustEqual 9
|
||||
char.voice mustEqual 1 //female 1
|
||||
char.unk1 mustEqual 0x8080
|
||||
char.unk2 mustEqual 0xFFFF
|
||||
char.unk3 mustEqual 2
|
||||
char.viewPitch mustEqual 0xFF
|
||||
char.viewYaw mustEqual 0x6A
|
||||
char.ribbons.upper mustEqual 0xFFFFFFFFL //none
|
||||
char.ribbons.middle mustEqual 0xFFFFFFFFL //none
|
||||
char.ribbons.lower mustEqual 0xFFFFFFFFL //none
|
||||
char.ribbons.tos mustEqual 0xFFFFFFFFL //none
|
||||
char.appearance.pos.x mustEqual 3674.8438f
|
||||
char.appearance.pos.y mustEqual 2726.789f
|
||||
char.appearance.pos.z mustEqual 91.15625f
|
||||
char.appearance.objYaw mustEqual 19
|
||||
char.appearance.faction mustEqual 2 //vs
|
||||
char.appearance.bops mustEqual false
|
||||
char.appearance.name mustEqual "IlllIIIlllIlIllIlllIllI"
|
||||
char.appearance.exosuit mustEqual 4 //standard
|
||||
char.appearance.sex mustEqual 2 //female
|
||||
char.appearance.face1 mustEqual 2
|
||||
char.appearance.face2 mustEqual 9
|
||||
char.appearance.voice mustEqual 1 //female 1
|
||||
char.appearance.unk1 mustEqual 0x8080
|
||||
char.appearance.unk2 mustEqual 0xFFFF
|
||||
char.appearance.unk3 mustEqual 2
|
||||
char.appearance.viewPitch mustEqual 0xFF
|
||||
char.appearance.viewYaw mustEqual 0x6A
|
||||
char.appearance.ribbons.upper mustEqual 0xFFFFFFFFL //none
|
||||
char.appearance.ribbons.middle mustEqual 0xFFFFFFFFL //none
|
||||
char.appearance.ribbons.lower mustEqual 0xFFFFFFFFL //none
|
||||
char.appearance.ribbons.tos mustEqual 0xFFFFFFFFL //none
|
||||
char.healthMax mustEqual 100
|
||||
char.health mustEqual 100
|
||||
char.armor mustEqual 50 //standard exosuit value
|
||||
|
|
@ -235,16 +235,78 @@ class GamePacketTest extends Specification {
|
|||
char.unk10 mustEqual 84
|
||||
char.unk11 mustEqual 104
|
||||
char.unk12 mustEqual 1900
|
||||
char.firstTimeEvent_length mustEqual 4
|
||||
char.firstEntry mustEqual Some("xpe_sanctuary_help")
|
||||
char.firstTimeEvent_list.size mustEqual 3
|
||||
char.firstTimeEvent_list.head mustEqual "xpe_th_firemodes"
|
||||
char.firstTimeEvent_list(1) mustEqual "used_beamer"
|
||||
char.firstTimeEvent_list(2) mustEqual "map13"
|
||||
char.tutorial_list.size mustEqual 0
|
||||
char.firstTimeEvents.size mustEqual 4
|
||||
char.firstTimeEvents.head mustEqual "xpe_sanctuary_help"
|
||||
char.firstTimeEvents(1) mustEqual "xpe_th_firemodes"
|
||||
char.firstTimeEvents(2) mustEqual "used_beamer"
|
||||
char.firstTimeEvents(3) mustEqual "map13"
|
||||
char.tutorials.size mustEqual 0
|
||||
char.inventory.unk1 mustEqual true
|
||||
char.inventory.size mustEqual 10
|
||||
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 =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -260,8 +322,7 @@ class GamePacketTest extends Specification {
|
|||
parent.get.guid mustEqual PlanetSideGUID(75)
|
||||
parent.get.slot mustEqual 33
|
||||
data.isDefined mustEqual true
|
||||
val obj = data.get.asInstanceOf[AmmoBoxData]
|
||||
obj.magazine mustEqual 50
|
||||
data.get.asInstanceOf[AmmoBoxData].magazine mustEqual 50
|
||||
case default =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -283,9 +344,7 @@ class GamePacketTest extends Specification {
|
|||
obj_ammo.objectClass mustEqual 28
|
||||
obj_ammo.guid mustEqual PlanetSideGUID(1286)
|
||||
obj_ammo.parentSlot mustEqual 0
|
||||
obj_ammo.obj.isDefined mustEqual true
|
||||
val ammo = obj_ammo.obj.get.asInstanceOf[AmmoBoxData]
|
||||
ammo.magazine mustEqual 30
|
||||
obj_ammo.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 30
|
||||
case default =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -298,16 +357,16 @@ class GamePacketTest extends Specification {
|
|||
}
|
||||
|
||||
"encode (9mm)" in {
|
||||
val obj : ConstructorData = AmmoBoxData(50).asInstanceOf[ConstructorData]
|
||||
val msg = ObjectCreateMessage(0, 28, PlanetSideGUID(1280), Some(ObjectCreateMessageParent(PlanetSideGUID(75), 33)), Some(obj))
|
||||
val obj = AmmoBoxData(50)
|
||||
val msg = ObjectCreateMessage(0, 28, PlanetSideGUID(1280), ObjectCreateMessageParent(PlanetSideGUID(75), 33), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_9mm
|
||||
}
|
||||
|
||||
"encode (gauss)" in {
|
||||
val obj : ConstructorData = WeaponData(4, 28, PlanetSideGUID(1286), 0, AmmoBoxData(30)).asInstanceOf[ConstructorData]
|
||||
val msg = ObjectCreateMessage(0, 345, PlanetSideGUID(1465), Some(ObjectCreateMessageParent(PlanetSideGUID(75), 2)), Some(obj))
|
||||
val obj = WeaponData(4, 28, PlanetSideGUID(1286), 0, AmmoBoxData(30))
|
||||
val msg = ObjectCreateMessage(0, 345, PlanetSideGUID(1465), ObjectCreateMessageParent(PlanetSideGUID(75), 2), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_gauss
|
||||
|
|
|
|||
Loading…
Reference in a new issue