mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-19 18:14:44 +00:00
working encoding and decoding of some objects for ObjectCreateMessage; check tests and WSActor
This commit is contained in:
parent
3b9f3a6f33
commit
509ab8ec57
|
|
@ -18,8 +18,8 @@ import shapeless.{::, HNil}
|
|||
* @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
|
||||
*/
|
||||
case class ObjectCreateMessageParent(guid : PlanetSideGUID,
|
||||
slot : Int)
|
||||
final case class ObjectCreateMessageParent(guid : PlanetSideGUID,
|
||||
slot : Int)
|
||||
|
||||
/**
|
||||
* Communicate with the client that a certain object with certain properties is to be created.
|
||||
|
|
@ -45,14 +45,15 @@ case class ObjectCreateMessageParent(guid : PlanetSideGUID,
|
|||
* @param objectClass the code for the type of object being constructed
|
||||
* @param guid the GUID this object will be assigned
|
||||
* @param parentInfo if defined, the relationship between this object and another object (its parent)
|
||||
* @param data if defined, the data used to construct this type of object
|
||||
* @param data the data used to construct this type of object;
|
||||
* on decoding, set to `None` if the process failed
|
||||
* @see ObjectClass.selectDataCodec
|
||||
*/
|
||||
case class ObjectCreateMessage(streamLength : Long,
|
||||
objectClass : Int,
|
||||
guid : PlanetSideGUID,
|
||||
parentInfo : Option[ObjectCreateMessageParent],
|
||||
data : Option[ConstructorData])
|
||||
final case class ObjectCreateMessage(streamLength : Long,
|
||||
objectClass : Int,
|
||||
guid : PlanetSideGUID,
|
||||
parentInfo : Option[ObjectCreateMessageParent],
|
||||
data : Option[ConstructorData])
|
||||
extends PlanetSideGamePacket {
|
||||
def opcode = GamePacketOpcode.ObjectCreateMessage
|
||||
def encode = ObjectCreateMessage.encode(this)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
package net.psforever.packet.game.objectcreate
|
||||
|
||||
import net.psforever.packet.Marshallable
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import scodec.{Attempt, Codec, Err}
|
||||
import scodec.codecs._
|
||||
import shapeless.{::, HNil}
|
||||
|
|
@ -13,12 +14,11 @@ import shapeless.{::, HNil}
|
|||
* <br>
|
||||
* The maximum amount of ammunition that can be stored in a single box is 65535 units.
|
||||
* Regardless of the interface, however, the number will never be fully visible.
|
||||
* Only the first three digits or first four digits may be represented.
|
||||
* Only the first three digits or the first four digits may be represented.
|
||||
* @param magazine the number of rounds available
|
||||
* @see WeaponData
|
||||
*/
|
||||
case class AmmoBoxData(magazine : Int
|
||||
) extends ConstructorData {
|
||||
final case class AmmoBoxData(magazine : Int) extends ConstructorData {
|
||||
/**
|
||||
* Performs a "sizeof()" analysis of the given object.
|
||||
* @see ConstructorData.bitsize
|
||||
|
|
@ -28,9 +28,20 @@ case class AmmoBoxData(magazine : Int
|
|||
}
|
||||
|
||||
object AmmoBoxData extends Marshallable[AmmoBoxData] {
|
||||
/**
|
||||
* An abbreviated constructor for creating `WeaponData` while masking use of `InternalSlot`.
|
||||
* @param cls the code for the type of object being constructed
|
||||
* @param guid the GUID this object will be assigned
|
||||
* @param parentSlot a parent-defined slot identifier that explains where the child is to be attached to the parent
|
||||
* @param ammo the `AmmoBoxData`
|
||||
* @return an `InternalSlot` object that encapsulates `AmmoBoxData`
|
||||
*/
|
||||
def apply(cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : AmmoBoxData) : InternalSlot =
|
||||
new InternalSlot(cls, guid, parentSlot, ammo)
|
||||
|
||||
implicit val codec : Codec[AmmoBoxData] = (
|
||||
uintL(8) ::
|
||||
uintL(15) ::
|
||||
uint8L ::
|
||||
uint(15) ::
|
||||
("magazine" | uint16L) ::
|
||||
bool
|
||||
).exmap[AmmoBoxData] (
|
||||
|
|
@ -38,7 +49,7 @@ object AmmoBoxData extends Marshallable[AmmoBoxData] {
|
|||
case 0xC8 :: 0 :: mag :: false :: HNil =>
|
||||
Attempt.successful(AmmoBoxData(mag))
|
||||
case a :: b :: _ :: d :: HNil =>
|
||||
Attempt.failure(Err("illegal ammunition data format"))
|
||||
Attempt.failure(Err("invalid ammunition data format"))
|
||||
},
|
||||
{
|
||||
case AmmoBoxData(mag) =>
|
||||
|
|
|
|||
|
|
@ -89,27 +89,27 @@ import shapeless.{::, HNil}
|
|||
* @param unk8 na
|
||||
* @param ribbons the four merit commendation ribbon medals
|
||||
*/
|
||||
case class CharacterAppearanceData(pos : Vector3,
|
||||
objYaw : Int,
|
||||
faction : Int,
|
||||
bops : Boolean,
|
||||
unk1 : Int,
|
||||
name : String,
|
||||
exosuit : Int,
|
||||
sex : Int,
|
||||
face1 : Int,
|
||||
face2 : Int,
|
||||
voice : Int,
|
||||
unk2 : Int,
|
||||
unk3 : Int,
|
||||
unk4 : Int,
|
||||
unk5 : Int,
|
||||
unk6 : Int,
|
||||
unk7 : Int,
|
||||
viewPitch : Int,
|
||||
viewYaw : Int,
|
||||
unk8 : Int,
|
||||
ribbons : RibbonBars) extends StreamBitSize {
|
||||
final case class CharacterAppearanceData(pos : Vector3,
|
||||
objYaw : Int,
|
||||
faction : Int,
|
||||
bops : Boolean,
|
||||
unk1 : Int,
|
||||
name : String,
|
||||
exosuit : Int,
|
||||
sex : Int,
|
||||
face1 : Int,
|
||||
face2 : Int,
|
||||
voice : Int,
|
||||
unk2 : Int,
|
||||
unk3 : Int,
|
||||
unk4 : Int,
|
||||
unk5 : Int,
|
||||
unk6 : Int,
|
||||
unk7 : Int,
|
||||
viewPitch : Int,
|
||||
viewYaw : Int,
|
||||
unk8 : Int,
|
||||
ribbons : RibbonBars) extends StreamBitSize {
|
||||
/**
|
||||
* Performs a "sizeof()" analysis of the given object.
|
||||
* @see ConstructorData.bitsize
|
||||
|
|
@ -138,18 +138,18 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
|||
ignore(16) ::
|
||||
("objYaw" | uint8L) ::
|
||||
ignore(1) ::
|
||||
("faction" | uintL(2)) ::
|
||||
("faction" | uint2L) ::
|
||||
("bops" | bool) ::
|
||||
("unk1" | uint4L) ::
|
||||
ignore(16) ::
|
||||
("name" | PacketHelpers.encodedWideStringAligned( namePadding )) ::
|
||||
("exosuit" | uintL(3)) ::
|
||||
ignore(2) ::
|
||||
("sex" | uintL(2)) ::
|
||||
("sex" | uint2L) ::
|
||||
("face1" | uint4L) ::
|
||||
("face2" | uint4L) ::
|
||||
("voice" | uintL(3)) ::
|
||||
("unk2" | uintL(2)) ::
|
||||
("unk2" | uint2L) ::
|
||||
ignore(4) ::
|
||||
("unk3" | uint8L) ::
|
||||
("unk4" | uint8L) ::
|
||||
|
|
@ -219,29 +219,29 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
|||
* the size field is a 32-bit number;
|
||||
* the first entry may be padded
|
||||
* @param tutorials the list of tutorials completed by this avatar;
|
||||
* the size field is a 32-bit number;
|
||||
* the first entry may be padded
|
||||
* the size field is a 32-bit number;
|
||||
* the first entry may be padded
|
||||
* @param inventory the avatar's inventory
|
||||
*/
|
||||
case class CharacterData(appearance : CharacterAppearanceData,
|
||||
healthMax : Int,
|
||||
health : Int,
|
||||
armor : Int,
|
||||
unk1 : Int, //1
|
||||
unk2 : Int, //7
|
||||
unk3 : Int, //7
|
||||
staminaMax : Int,
|
||||
stamina : Int,
|
||||
unk4 : Int, //28
|
||||
unk5 : Int, //4
|
||||
unk6 : Int, //44
|
||||
unk7 : Int, //84
|
||||
unk8 : Int, //104
|
||||
unk9 : Int, //1900
|
||||
firstTimeEvents : List[String],
|
||||
tutorials : List[String],
|
||||
inventory : InventoryData
|
||||
) extends ConstructorData {
|
||||
final case class CharacterData(appearance : CharacterAppearanceData,
|
||||
healthMax : Int,
|
||||
health : Int,
|
||||
armor : Int,
|
||||
unk1 : Int, //1
|
||||
unk2 : Int, //7
|
||||
unk3 : Int, //7
|
||||
staminaMax : Int,
|
||||
stamina : Int,
|
||||
unk4 : Int, //28
|
||||
unk5 : Int, //4
|
||||
unk6 : Int, //44
|
||||
unk7 : Int, //84
|
||||
unk8 : Int, //104
|
||||
unk9 : Int, //1900
|
||||
firstTimeEvents : List[String],
|
||||
tutorials : List[String],
|
||||
inventory : InventoryData
|
||||
) extends ConstructorData {
|
||||
/**
|
||||
* Performs a "sizeof()" analysis of the given object.
|
||||
* @see ConstructorData.bitsize
|
||||
|
|
@ -310,7 +310,7 @@ object CharacterData extends Marshallable[CharacterData] {
|
|||
if(len > 0) //automatic alignment from previous List
|
||||
0
|
||||
else if(len2 > 0) //need to align for elements
|
||||
1
|
||||
5
|
||||
else //both lists are empty
|
||||
0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
// Copyright (c) 2016 PSForever.net to present
|
||||
package net.psforever.packet.game.objectcreate
|
||||
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import net.psforever.packet.{Marshallable, PacketHelpers}
|
||||
import scodec.codecs._
|
||||
import scodec.{Attempt, Codec, Err}
|
||||
import shapeless.{::, HNil}
|
||||
|
||||
/**
|
||||
* A representation of a class of weapons that can be created using `ObjectCreateMessage` packet data.
|
||||
* A "concurrent feed weapon" refers to a weapon system that can chamber multiple types of ammunition simultaneously.
|
||||
* This data will help construct a "weapon" such as a Punisher.<br>
|
||||
* <br>
|
||||
* The data for the weapons nests information for the default (current) type and number of ammunition in its magazine.
|
||||
* This ammunition data essentially is the weapon's magazines as numbered slots.
|
||||
* @param unk na
|
||||
* @param ammo `List` data regarding the currently loaded ammunition types and quantities
|
||||
* @see WeaponData
|
||||
* @see AmmoBoxData
|
||||
*/
|
||||
final case class ConcurrentFeedWeaponData(unk : Int,
|
||||
ammo : List[InternalSlot]) extends ConstructorData {
|
||||
/**
|
||||
* Performs a "sizeof()" analysis of the given object.
|
||||
* @see ConstructorData.bitsize
|
||||
* @see InternalSlot.bitsize
|
||||
* @see AmmoBoxData.bitsize
|
||||
* @return the number of bits necessary to represent this object
|
||||
*/
|
||||
override def bitsize : Long = {
|
||||
var bitsize : Long = 0L
|
||||
for(o <- ammo) {
|
||||
bitsize += o.bitsize
|
||||
}
|
||||
61L + bitsize
|
||||
}
|
||||
}
|
||||
|
||||
object ConcurrentFeedWeaponData extends Marshallable[ConcurrentFeedWeaponData] {
|
||||
/**
|
||||
* An abbreviated constructor for creating `ConcurrentFeedWeaponData` 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) : ConcurrentFeedWeaponData =
|
||||
new ConcurrentFeedWeaponData(unk, InternalSlot(cls, guid, parentSlot, ammo) :: Nil)
|
||||
|
||||
implicit val codec : Codec[ConcurrentFeedWeaponData] = (
|
||||
("unk" | uint4L) ::
|
||||
uint4L ::
|
||||
uint24 ::
|
||||
uint16 ::
|
||||
uint2L ::
|
||||
(uint8L >>:~ { size =>
|
||||
uint2L ::
|
||||
("ammo" | PacketHelpers.listOfNSized(size, InternalSlot.codec)) ::
|
||||
bool
|
||||
})
|
||||
).exmap[ConcurrentFeedWeaponData] (
|
||||
{
|
||||
case code :: 8 :: 2 :: 0 :: 3 :: size :: 0 :: ammo :: false :: HNil =>
|
||||
if(size != ammo.size)
|
||||
Attempt.failure(Err("weapon encodes wrong number of ammunition"))
|
||||
else if(size == 0)
|
||||
Attempt.failure(Err("weapon needs to encode at least one type of ammunition"))
|
||||
else
|
||||
Attempt.successful(ConcurrentFeedWeaponData(code, ammo))
|
||||
case code :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil =>
|
||||
Attempt.failure(Err("invalid weapon data format"))
|
||||
},
|
||||
{
|
||||
case ConcurrentFeedWeaponData(code, ammo) =>
|
||||
val size = ammo.size
|
||||
if(size == 0)
|
||||
Attempt.failure(Err("weapon needs to encode at least one type of ammunition"))
|
||||
else if(size >= 255)
|
||||
Attempt.failure(Err("weapon has too much ammunition (255+ types!)"))
|
||||
else
|
||||
Attempt.successful(code :: 8 :: 2 :: 0 :: 3 :: size :: 0 :: ammo :: false :: HNil)
|
||||
}
|
||||
).as[ConcurrentFeedWeaponData]
|
||||
|
||||
/**
|
||||
* Transform between WeaponData and ConstructorData.
|
||||
*/
|
||||
val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] (
|
||||
{
|
||||
case x =>
|
||||
Attempt.successful(Some(x.asInstanceOf[ConstructorData]))
|
||||
},
|
||||
{
|
||||
case Some(x) =>
|
||||
Attempt.successful(x.asInstanceOf[ConcurrentFeedWeaponData])
|
||||
case _ =>
|
||||
Attempt.failure(Err("can not encode weapon data"))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -8,22 +8,22 @@ import scodec.codecs._
|
|||
import shapeless.{::, HNil}
|
||||
|
||||
/**
|
||||
* Similar fields as required for a formal `ObjectCreateMessage` but with a required but implicit parent relationship.
|
||||
* Specifically, the purpose of the packet is to start to define a new object within the definition of a previous object.
|
||||
* This prior object will clarify the identity of the parent object that owns the given `parentSlot`.<br>
|
||||
* An intermediate class for the primary fields of `ObjectCreateMessage` with an implicit parent-child relationship.<br>
|
||||
* <br>
|
||||
* An `InternalSlot` object is not a top-level object.
|
||||
* Extra effort should be made to ensure the user does not have to directly construct an `InternalSlot`.
|
||||
* Any object that is contained in a "slot" of another object will use `InternalSlot` to hold the anchoring data.
|
||||
* This prior object will clarify the identity of the "parent" object that owns the given `parentSlot`.<br>
|
||||
* <br>
|
||||
* Try to avoid exposing `InternalSlot` in the process of implementing code.
|
||||
* @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
|
||||
* @see ObjectClass.selectDataCodec
|
||||
*/
|
||||
case class InternalSlot(objectClass : Int,
|
||||
guid : PlanetSideGUID,
|
||||
parentSlot : Int,
|
||||
obj : ConstructorData) extends StreamBitSize {
|
||||
final case class InternalSlot(objectClass : Int,
|
||||
guid : PlanetSideGUID,
|
||||
parentSlot : Int,
|
||||
obj : ConstructorData) extends StreamBitSize {
|
||||
/**
|
||||
* Performs a "sizeof()" analysis of the given object.
|
||||
* @see ConstructorData.bitsize
|
||||
|
|
|
|||
|
|
@ -14,12 +14,18 @@ import shapeless.{::, HNil}
|
|||
* No values are allowed to be misplaced and no unexpected regions of data can be discovered.
|
||||
* If there is even a minor failure, the whole of the inventory will fail to translate.<br>
|
||||
* <br>
|
||||
* Under the official servers, when a new character was generated, the inventory encoded as `0x1C`.
|
||||
* This inventory had no size field, no contents, and an indeterminate number of values.
|
||||
* This format is no longer supported.
|
||||
* Going forward, an empty inventory - approximately `0x10000` - should be used as substitute.<br>
|
||||
* <br>
|
||||
* Exploration:<br>
|
||||
* 4u of ignored bits are tagged onto the end of this field for purposes of finding four missing bits of stream length.
|
||||
* The rest of the encoding is valid.
|
||||
* Conditions must certainly decide whether these bits are present or not.
|
||||
* 4u of ignored bits have been added to the end of the inventory to make up for missing stream length.
|
||||
* They do not actually seem to be part of the inventory.
|
||||
* Are these bits always at the end of the packet data and what is the significance?
|
||||
* @param unk1 na;
|
||||
* `true` to mark the start of the inventory data?
|
||||
* is explicitly declaring the bit necessary when it always seems to be `true`?
|
||||
* @param unk2 na
|
||||
* @param unk3 na
|
||||
* @param contents the actual items in the inventory;
|
||||
|
|
@ -27,17 +33,17 @@ import shapeless.{::, HNil}
|
|||
* 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,
|
||||
unk2 : Boolean,
|
||||
unk3 : Boolean,
|
||||
contents : List[InventoryItem]) extends StreamBitSize {
|
||||
final case class InventoryData(unk1 : Boolean,
|
||||
unk2 : Boolean,
|
||||
unk3 : Boolean,
|
||||
contents : List[InventoryItem]) extends StreamBitSize {
|
||||
/**
|
||||
* 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 = {
|
||||
//three booleans, the 4u and the 8u length field
|
||||
//three booleans, the 4u extra, and the 8u length field
|
||||
val base : Long = 15L
|
||||
//length of all items in inventory
|
||||
var invSize : Long = 0L
|
||||
|
|
@ -57,14 +63,14 @@ object InventoryData extends Marshallable[InventoryData] {
|
|||
("contents" | PacketHelpers.listOfNSized(len, InventoryItem.codec)) ::
|
||||
ignore(4)
|
||||
})
|
||||
).xmap[InventoryData] (
|
||||
).xmap[InventoryData] (
|
||||
{
|
||||
case u1 :: _ :: u2 :: u3 :: ctnt :: _ :: HNil =>
|
||||
InventoryData(u1, u2, u3, ctnt)
|
||||
case u1 :: _ :: a :: b :: ctnt :: _ :: HNil =>
|
||||
InventoryData(u1, a, b, ctnt)
|
||||
},
|
||||
{
|
||||
case InventoryData(u1, u2, u3, ctnt) =>
|
||||
u1 :: ctnt.size :: u2 :: u3 :: ctnt :: () :: HNil
|
||||
case InventoryData(u1, a, b, ctnt) =>
|
||||
u1 :: ctnt.size :: a :: b :: ctnt :: () :: HNil
|
||||
}
|
||||
).as[InventoryData]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@ import scodec.codecs._
|
|||
|
||||
/**
|
||||
* A representation of an item in an avatar's inventory.
|
||||
* Reliance on `InternalSlot` indicates that this item is applicable to the same implicit parental relationship.
|
||||
* (That is, its parent object will be clarified earlier on in the data stream.)
|
||||
* Reliance on `InternalSlot` indicates that this item is applicable to the same implicit parent-child relationship.
|
||||
* (That is, its parent object will be clarified by the containing element, e.g., the inventory or its owner.)
|
||||
* Unwinding inventory items into individual standard `ObjectCreateMessage` packet data is entirely possible.<br>
|
||||
* <br>
|
||||
* This intermediary object is primarily intended to mask external use of `InternalSlot`.
|
||||
* This intermediary object is primarily intended to mask external use of `InternalSlot`, as specified by the class.
|
||||
* @param item the object in inventory
|
||||
* @see InternalSlot
|
||||
*/
|
||||
case class InventoryItem(item : InternalSlot) extends StreamBitSize {
|
||||
final case class InventoryItem(item : InternalSlot) extends StreamBitSize {
|
||||
/**
|
||||
* Performs a "sizeof()" analysis of the given object.
|
||||
* @see ConstructorData.bitsize
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ object ObjectClass {
|
|||
final val BULLETS_9MM = 0x1C
|
||||
final val BULLETS_9MM_AP = 0x1D
|
||||
final val ENERGY_CELL = 0x110
|
||||
final val JAMMER_GRENADE_PACK = 0x19D
|
||||
final val JAMMER_GRENADE_AMMO = 0x1A1
|
||||
final val FORCE_BLADE_AMMO = 0x21C
|
||||
final val PLASMA_GRENADE_AMMO = 0x2A9
|
||||
|
|
@ -32,6 +33,7 @@ object ObjectClass {
|
|||
final val GAUSS = 0x159
|
||||
final val JAMMER_GRENADE = 0x1A0
|
||||
final val PLASMA_GRENADE = 0x2A8
|
||||
final val PUNISHER = 0x2C2
|
||||
//tools
|
||||
final val MEDKIT = 0x218
|
||||
final val REK = 0x2D8
|
||||
|
|
@ -62,9 +64,11 @@ object ObjectClass {
|
|||
case ObjectClass.GAUSS => WeaponData.genericCodec
|
||||
case ObjectClass.JAMMER_GRENADE => WeaponData.genericCodec
|
||||
case ObjectClass.JAMMER_GRENADE_AMMO => AmmoBoxData.genericCodec
|
||||
case ObjectClass.JAMMER_GRENADE_PACK => AmmoBoxData.genericCodec
|
||||
case ObjectClass.MEDKIT => AmmoBoxData.genericCodec
|
||||
case ObjectClass.PLASMA_GRENADE => WeaponData.genericCodec
|
||||
case ObjectClass.PLASMA_GRENADE_AMMO => AmmoBoxData.genericCodec
|
||||
case ObjectClass.PUNISHER => ConcurrentFeedWeaponData.genericCodec
|
||||
case ObjectClass.REK => REKData.genericCodec
|
||||
case ObjectClass.SLOT_BLOCKER => AmmoBoxData.genericCodec
|
||||
case ObjectClass.SUPPRESSOR => WeaponData.genericCodec
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@ import shapeless.{::, HNil}
|
|||
* Of note is the first portion of the data which resembles the `WeaponData` format.
|
||||
* @param unk na
|
||||
*/
|
||||
case class REKData(unk : Int
|
||||
) extends ConstructorData {
|
||||
final case class REKData(unk : Int) extends ConstructorData {
|
||||
/**
|
||||
* Performs a "sizeof()" analysis of the given object.
|
||||
* @see ConstructorData.bitsize
|
||||
|
|
@ -29,7 +28,7 @@ object REKData extends Marshallable[REKData] {
|
|||
uint4L ::
|
||||
uintL(20) ::
|
||||
uint4L ::
|
||||
uintL(16) ::
|
||||
uint16L ::
|
||||
uint4L ::
|
||||
uintL(15)
|
||||
).exmap[REKData] (
|
||||
|
|
@ -37,7 +36,7 @@ object REKData extends Marshallable[REKData] {
|
|||
case code :: 8 :: 0 :: 2 :: 0 :: 8 :: 0 :: HNil =>
|
||||
Attempt.successful(REKData(code))
|
||||
case code :: _ :: _ :: _ :: _ :: _ :: _ :: HNil =>
|
||||
Attempt.failure(Err("illegal rek data format"))
|
||||
Attempt.failure(Err("invalid rek data format"))
|
||||
},
|
||||
{
|
||||
case REKData(code) =>
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ import scodec.codecs._
|
|||
* @param lower the lower configurable merit ribbon
|
||||
* @param tos the top-most term of service merit ribbon
|
||||
*/
|
||||
case class RibbonBars(upper : Long = 0xFFFFFFFFL,
|
||||
middle : Long = 0xFFFFFFFFL,
|
||||
lower : Long = 0xFFFFFFFFL,
|
||||
tos : Long = 0xFFFFFFFFL) extends StreamBitSize {
|
||||
final case class RibbonBars(upper : Long = 0xFFFFFFFFL,
|
||||
middle : Long = 0xFFFFFFFFL,
|
||||
lower : Long = 0xFFFFFFFFL,
|
||||
tos : Long = 0xFFFFFFFFL) extends StreamBitSize {
|
||||
/**
|
||||
* Performs a "sizeof()" analysis of the given object.
|
||||
* @see ConstructorData.bitsize
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ trait StreamBitSize {
|
|||
/**
|
||||
* Performs a "sizeof()" analysis of the given object.
|
||||
* The calculation reflects the `scodec Codec` definition rather than the explicit parameter fields.
|
||||
* For example, an `Int` is normally a 32-bit number;
|
||||
* For example, an `Int` is normally a 32u number;
|
||||
* when parsed with a `uintL(7)`, it's length will be considered 7u.
|
||||
* (Note: being permanently signed, an `scodec` 32u value must fit into a `Long` type.)
|
||||
* @return the number of bits necessary to represent this object;
|
||||
* defaults to `0L`
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import scodec.codecs._
|
|||
import shapeless.{::, HNil}
|
||||
|
||||
/**
|
||||
* A representation of the weapon portion of `ObjectCreateMessage` packet data.
|
||||
* This data will help construct a "weapon" such as a Suppressor or a Gauss.<br>
|
||||
* A representation of a class of weapons that can be created using `ObjectCreateMessage` packet data.
|
||||
* This data will help construct a "loaded weapon" such as a Suppressor or a Gauss.<br>
|
||||
* <br>
|
||||
* The data for the weapons nests information for the default (current) type and number of ammunition in its magazine.
|
||||
* This ammunition data essentially is the weapon's magazine as numbered slots.
|
||||
* This ammunition data essentially is the weapon's magazines as numbered slots.
|
||||
* Having said that, this format only handles one type of ammunition at a time.
|
||||
* Any weapon that has two types of ammunition simultaneously loaded, e.g., a Punisher, must be handled with another `Codec`.
|
||||
* This functionality is unrelated to a weapon that switches ammunition type;
|
||||
|
|
@ -21,8 +21,8 @@ import shapeless.{::, HNil}
|
|||
* @param ammo data regarding the currently loaded ammunition type and quantity
|
||||
* @see AmmoBoxData
|
||||
*/
|
||||
case class WeaponData(unk : Int,
|
||||
ammo : InternalSlot) extends ConstructorData {
|
||||
final case class WeaponData(unk : Int,
|
||||
ammo : InternalSlot) extends ConstructorData {
|
||||
/**
|
||||
* Performs a "sizeof()" analysis of the given object.
|
||||
* @see ConstructorData.bitsize
|
||||
|
|
@ -51,23 +51,23 @@ object WeaponData extends Marshallable[WeaponData] {
|
|||
implicit val codec : Codec[WeaponData] = (
|
||||
("unk" | uint4L) ::
|
||||
uint4L ::
|
||||
uintL(20) ::
|
||||
uint4L ::
|
||||
uintL(16) ::
|
||||
uintL(11) ::
|
||||
bool ::
|
||||
uint24 ::
|
||||
uint16L ::
|
||||
uint2 ::
|
||||
uint8 :: //size = 1 type of ammunition loaded
|
||||
uint2 ::
|
||||
("ammo" | InternalSlot.codec) ::
|
||||
bool
|
||||
).exmap[WeaponData] (
|
||||
{
|
||||
case code :: 8 :: 0 :: 2 :: 0 :: 0x2C0 :: false :: ammo :: false :: HNil =>
|
||||
case code :: 8 :: 2 :: 0 :: 3 :: 1 :: 0 :: ammo :: false :: HNil =>
|
||||
Attempt.successful(WeaponData(code, ammo))
|
||||
case code :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil =>
|
||||
Attempt.failure(Err("illegal weapon data format"))
|
||||
Attempt.failure(Err("invalid weapon data format"))
|
||||
},
|
||||
{
|
||||
case WeaponData(code, ammo) =>
|
||||
Attempt.successful(code :: 8 :: 0 :: 2 :: 0 :: 0x2C0 :: false :: ammo :: false :: HNil)
|
||||
Attempt.successful(code :: 8 :: 2 :: 0 :: 3 :: 1 :: 0 :: ammo :: false :: HNil)
|
||||
}
|
||||
).as[WeaponData]
|
||||
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ class GamePacketTest extends Specification {
|
|||
var string_inventoryItem = hex"46 04 C0 08 08 80 00 00 20 00 0C 04 10 29 A0 10 19 00 00 04 00 00"
|
||||
val string_9mm = hex"18 7C000000 2580 0E0 0005 A1 C8000064000"
|
||||
val string_gauss = hex"18 DC000000 2580 2C9 B905 82 480000020000C04 1C00C0B0190000078000"
|
||||
val string_punisher = hex"18 27010000 2580 612 a706 82 080000020000c08 1c13a0d01900000780 13a4701a072000000800"
|
||||
val string_rek = hex"18 97000000 2580 6C2 9F05 81 48000002000080000"
|
||||
val string_testchar = hex"18 570C0000 BC8 4B00 6C2D7 65535 CA16 0 00 01 34 40 00 0970 49006C006C006C004900490049006C006C006C0049006C0049006C006C0049006C006C006C0049006C006C004900 84 52 70 76 1E 80 80 00 00 00 00 00 3FFFC 0 00 00 00 20 00 00 0F F6 A7 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 64 00 00 01 00 7E C8 00 C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 C0 00 42 C5 46 86 C7 00 00 00 80 00 00 12 40 78 70 65 5F 73 61 6E 63 74 75 61 72 79 5F 68 65 6C 70 90 78 70 65 5F 74 68 5F 66 69 72 65 6D 6F 64 65 73 8B 75 73 65 64 5F 62 65 61 6D 65 72 85 6D 61 70 31 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 0A 23 02 60 04 04 40 00 00 10 00 06 02 08 14 D0 08 0C 80 00 02 00 02 6B 4E 00 82 88 00 00 02 00 00 C0 41 C0 9E 01 01 90 00 00 64 00 44 2A 00 10 91 00 00 00 40 00 18 08 38 94 40 20 32 00 00 00 80 19 05 48 02 17 20 00 00 08 00 70 29 80 43 64 00 00 32 00 0E 05 40 08 9C 80 00 06 40 01 C0 AA 01 19 90 00 00 C8 00 3A 15 80 28 72 00 00 19 00 04 0A B8 05 26 40 00 03 20 06 C2 58 00 A7 88 00 00 02 00 00 80 00 00"
|
||||
|
||||
|
|
@ -208,6 +209,33 @@ class GamePacketTest extends Specification {
|
|||
}
|
||||
}
|
||||
|
||||
"decode (punisher)" in {
|
||||
PacketCoding.DecodePacket(string_punisher).require match {
|
||||
case obj @ ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
len mustEqual 295
|
||||
cls mustEqual 706
|
||||
guid mustEqual PlanetSideGUID(1703)
|
||||
parent.isDefined mustEqual true
|
||||
parent.get.guid mustEqual PlanetSideGUID(75)
|
||||
parent.get.slot mustEqual 2
|
||||
data.isDefined mustEqual true
|
||||
val obj_wep = data.get.asInstanceOf[ConcurrentFeedWeaponData]
|
||||
obj_wep.unk mustEqual 0
|
||||
val obj_ammo = obj_wep.ammo
|
||||
obj_ammo.size mustEqual 2
|
||||
obj_ammo.head.objectClass mustEqual 28
|
||||
obj_ammo.head.guid mustEqual PlanetSideGUID(1693)
|
||||
obj_ammo.head.parentSlot mustEqual 0
|
||||
obj_ammo.head.obj.asInstanceOf[AmmoBoxData].magazine mustEqual 30
|
||||
obj_ammo(1).objectClass mustEqual 413
|
||||
obj_ammo(1).guid mustEqual PlanetSideGUID(1564)
|
||||
obj_ammo(1).parentSlot mustEqual 1
|
||||
obj_ammo(1).obj.asInstanceOf[AmmoBoxData].magazine mustEqual 1
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (rek)" in {
|
||||
PacketCoding.DecodePacket(string_rek).require match {
|
||||
case obj @ ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
|
|
@ -373,6 +401,14 @@ class GamePacketTest extends Specification {
|
|||
pkt mustEqual string_gauss
|
||||
}
|
||||
|
||||
"encode (punisher)" in {
|
||||
val obj = ConcurrentFeedWeaponData(0, AmmoBoxData(28, PlanetSideGUID(1693), 0, AmmoBoxData(30)) :: AmmoBoxData(413, PlanetSideGUID(1564), 1, AmmoBoxData(1)) :: Nil)
|
||||
val msg = ObjectCreateMessage(0, 706, PlanetSideGUID(1703), ObjectCreateMessageParent(PlanetSideGUID(75), 2), obj)
|
||||
var pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_punisher
|
||||
}
|
||||
|
||||
"encode (rek)" in {
|
||||
val obj = REKData(4)
|
||||
val msg = ObjectCreateMessage(0, 0x2D8, PlanetSideGUID(1439), ObjectCreateMessageParent(PlanetSideGUID(75), 1), obj)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ import scodec.Attempt.{Failure, Successful}
|
|||
import scodec.bits._
|
||||
import org.log4s.MDC
|
||||
import MDCContextAware.Implicits._
|
||||
import net.psforever.types.ChatMessageType
|
||||
import net.psforever.packet.game.objectcreate._
|
||||
import net.psforever.types.{ChatMessageType, Vector3}
|
||||
|
||||
class WorldSessionActor extends Actor with MDCContextAware {
|
||||
private[this] val log = org.log4s.getLogger
|
||||
|
|
@ -107,8 +108,49 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
}
|
||||
|
||||
// XXX: hard coded ObjectCreateMessage
|
||||
val objectHex = hex"18 57 0C 00 00 BC 84 B0 06 C2 D7 65 53 5C A1 60 00 01 34 40 00 09 70 49 00 6C 00 6C 00 6C 00 49 00 49 00 49 00 6C 00 6C 00 6C 00 49 00 6C 00 49 00 6C 00 6C 00 49 00 6C 00 6C 00 6C 00 49 00 6C 00 6C 00 49 00 84 52 70 76 1E 80 80 00 00 00 00 00 3F FF C0 00 00 00 20 00 00 0F F6 A7 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FD 90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 64 00 00 01 00 7E C8 00 C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 C0 00 42 C5 46 86 C7 00 00 00 80 00 00 12 40 78 70 65 5F 73 61 6E 63 74 75 61 72 79 5F 68 65 6C 70 90 78 70 65 5F 74 68 5F 66 69 72 65 6D 6F 64 65 73 8B 75 73 65 64 5F 62 65 61 6D 65 72 85 6D 61 70 31 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 0A 23 02 60 04 04 40 00 00 10 00 06 02 08 14 D0 08 0C 80 00 02 00 02 6B 4E 00 82 88 00 00 02 00 00 C0 41 C0 9E 01 01 90 00 00 64 00 44 2A 00 10 91 00 00 00 40 00 18 08 38 94 40 20 32 00 00 00 80 19 05 48 02 17 20 00 00 08 00 70 29 80 43 64 00 00 32 00 0E 05 40 08 9C 80 00 06 40 01 C0 AA 01 19 90 00 00 C8 00 3A 15 80 28 72 00 00 19 00 04 0A B8 05 26 40 00 03 20 06 C2 58 00 A7 88 00 00 02 00 00 80 00 00 "
|
||||
//val objectHex = hex"18 57 0C 00 00 BC 84 B0 06 C2 D7 65 53 5C A1 60 00 01 34 40 00 09 70 49 00 6C 00 6C 00 6C 00 49 00 49 00 49 00 6C 00 6C 00 6C 00 49 00 6C 00 49 00 6C 00 6C 00 49 00 6C 00 6C 00 6C 00 49 00 6C 00 6C 00 49 00 84 52 70 76 1E 80 80 00 00 00 00 00 3F FF C0 00 00 00 20 00 00 0F F6 A7 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FD 90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 64 00 00 01 00 7E C8 00 C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 C0 00 42 C5 46 86 C7 00 00 00 80 00 00 12 40 78 70 65 5F 73 61 6E 63 74 75 61 72 79 5F 68 65 6C 70 90 78 70 65 5F 74 68 5F 66 69 72 65 6D 6F 64 65 73 8B 75 73 65 64 5F 62 65 61 6D 65 72 85 6D 61 70 31 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 0A 23 02 60 04 04 40 00 00 10 00 06 02 08 14 D0 08 0C 80 00 02 00 02 6B 4E 00 82 88 00 00 02 00 00 C0 41 C0 9E 01 01 90 00 00 64 00 44 2A 00 10 91 00 00 00 40 00 18 08 38 94 40 20 32 00 00 00 80 19 05 48 02 17 20 00 00 08 00 70 29 80 43 64 00 00 32 00 0E 05 40 08 9C 80 00 06 40 01 C0 AA 01 19 90 00 00 C8 00 3A 15 80 28 72 00 00 19 00 04 0A B8 05 26 40 00 03 20 06 C2 58 00 A7 88 00 00 02 00 00 80 00 00"
|
||||
//currently, the character's starting BEP is discarded due to unknown bit format
|
||||
val app = CharacterAppearanceData(
|
||||
Vector3(3674.8438f, 2726.789f, 91.15625f),
|
||||
19,
|
||||
2,
|
||||
false,
|
||||
4,
|
||||
"IlllIIIlllIlIllIlllIllI",
|
||||
4,
|
||||
2,
|
||||
2, 9,
|
||||
1,
|
||||
3, 118, 30, 0x8080, 0xFFFF, 2,
|
||||
255, 106, 7,
|
||||
RibbonBars()
|
||||
)
|
||||
val inv =
|
||||
InventoryItem(ObjectClass.BEAMER, PlanetSideGUID(76), 0, WeaponData(8, ObjectClass.ENERGY_CELL, PlanetSideGUID(77), 0, AmmoBoxData(16))) ::
|
||||
InventoryItem(ObjectClass.SUPPRESSOR, PlanetSideGUID(78), 2, WeaponData(8, ObjectClass.BULLETS_9MM, PlanetSideGUID(79), 0, AmmoBoxData(25))) ::
|
||||
InventoryItem(ObjectClass.FORCE_BLADE, PlanetSideGUID(80), 4, WeaponData(8, ObjectClass.FORCE_BLADE_AMMO, PlanetSideGUID(81), 0, AmmoBoxData(1))) ::
|
||||
InventoryItem(ObjectClass.SLOT_BLOCKER, PlanetSideGUID(82), 5, AmmoBoxData(1)) ::
|
||||
InventoryItem(ObjectClass.BULLETS_9MM, PlanetSideGUID(83), 6, AmmoBoxData(50)) ::
|
||||
InventoryItem(ObjectClass.BULLETS_9MM, PlanetSideGUID(84), 9, AmmoBoxData(50)) ::
|
||||
InventoryItem(ObjectClass.BULLETS_9MM, PlanetSideGUID(85), 12, AmmoBoxData(50)) ::
|
||||
InventoryItem(ObjectClass.BULLETS_9MM_AP, PlanetSideGUID(86), 33, AmmoBoxData(50)) ::
|
||||
InventoryItem(ObjectClass.ENERGY_CELL, PlanetSideGUID(87), 36, AmmoBoxData(50)) ::
|
||||
InventoryItem(ObjectClass.REK, PlanetSideGUID(88), 39, REKData(8)) ::
|
||||
Nil
|
||||
val obj = CharacterData(
|
||||
app,
|
||||
100, 100,
|
||||
50,
|
||||
1, 7, 7,
|
||||
100, 100,
|
||||
28, 4, 44, 84, 104, 1900,
|
||||
"xpe_sanctuary_help" :: "xpe_th_firemodes" :: "used_beamer" :: "map13" :: Nil,
|
||||
List.empty,
|
||||
InventoryData(
|
||||
true, false, false, inv
|
||||
)
|
||||
)
|
||||
val objectHex = ObjectCreateMessage(0, ObjectClass.AVATAR, PlanetSideGUID(75), obj)
|
||||
|
||||
def handleGamePkt(pkt : PlanetSideGamePacket) = pkt match {
|
||||
case ConnectToWorldRequestMessage(server, token, majorVersion, minorVersion, revision, buildDate, unk) =>
|
||||
|
|
@ -118,7 +160,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
log.info(s"New world login to ${server} with Token:${token}. ${clientVersion}")
|
||||
|
||||
// ObjectCreateMessage
|
||||
sendRawResponse(objectHex)
|
||||
sendResponse(PacketCoding.CreateGamePacket(0, objectHex))
|
||||
// XXX: hard coded message
|
||||
sendRawResponse(hex"14 0F 00 00 00 10 27 00 00 C1 D8 7A 02 4B 00 26 5C B0 80 00 ")
|
||||
|
||||
|
|
@ -132,14 +174,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case CharacterRequestAction.Delete =>
|
||||
sendResponse(PacketCoding.CreateGamePacket(0, ActionResultMessage(false, Some(1))))
|
||||
case CharacterRequestAction.Select =>
|
||||
PacketCoding.DecodeGamePacket(objectHex).require match {
|
||||
objectHex match {
|
||||
case obj @ ObjectCreateMessage(len, cls, guid, _, _) =>
|
||||
log.debug("Object: " + obj)
|
||||
// LoadMapMessage 13714 in mossy .gcap
|
||||
// XXX: hardcoded shit
|
||||
sendResponse(PacketCoding.CreateGamePacket(0, LoadMapMessage("map13","home3",40100,25,true,3770441820L))) //VS Sanctuary
|
||||
sendResponse(PacketCoding.CreateGamePacket(0, ZonePopulationUpdateMessage(PlanetSideGUID(13), 414, 138, 0, 138, 0, 138, 0, 138, 0)))
|
||||
sendRawResponse(objectHex)
|
||||
sendResponse(PacketCoding.CreateGamePacket(0, objectHex))
|
||||
|
||||
// These object_guids are specfic to VS Sanc
|
||||
sendResponse(PacketCoding.CreateGamePacket(0, SetEmpireMessage(PlanetSideGUID(2), PlanetSideEmpire.VS))) //HART building C
|
||||
|
|
|
|||
Loading…
Reference in a new issue