Refactored CharacterData and DetailedCharacterData to be a component of PlayerData and DetailedPlayerData, respectively. Position information is now optional; inventory data and exposed hand data are also removed into PlayerData and DetailedPlayerData. String padding management has been preserved. Tests and packet converters have been repaired.

This commit is contained in:
FateJH 2018-05-28 20:39:35 -04:00
parent a9db4b5820
commit 052a318285
13 changed files with 1134 additions and 940 deletions

View file

@ -3,47 +3,52 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.{EquipmentSlot, Player}
import net.psforever.objects.equipment.Equipment
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, Cosmetics, DetailedCharacterData, DrawnSlot, ImplantEffects, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle}
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{GrenadeState, ImplantType}
import scala.annotation.tailrec
import scala.util.{Success, Try}
class AvatarConverter extends ObjectCreateConverter[Player]() {
override def ConstructorData(obj : Player) : Try[CharacterData] = {
override def ConstructorData(obj : Player) : Try[PlayerData] = {
val MaxArmor = obj.MaxArmor
Success(
CharacterData(
Success(
PlayerData.apply(
PlacementData(obj.Position, obj.Orientation, obj.Velocity),
MakeAppearanceData(obj),
255 * obj.Health / obj.MaxHealth, //TODO not precise
if(MaxArmor == 0) { 0 } else { 255 * obj.Armor / MaxArmor }, //TODO not precise
DressBattleRank(obj),
DressCommandRank(obj),
recursiveMakeImplantEffects(obj.Implants.iterator),
MakeCosmetics(obj.BEP),
CharacterData(
255 * obj.Health / obj.MaxHealth, //TODO not precise
if(MaxArmor == 0) { 0 } else { 255 * obj.Armor / MaxArmor }, //TODO not precise
DressBattleRank(obj),
DressCommandRank(obj),
recursiveMakeImplantEffects(obj.Implants.iterator),
MakeCosmetics(obj.BEP)
),
InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot)), //TODO is sorting necessary?
GetDrawnSlot(obj)
)
)
//TODO tidy this mess up
}
override def DetailedConstructorData(obj : Player) : Try[DetailedCharacterData] = {
override def DetailedConstructorData(obj : Player) : Try[DetailedPlayerData] = {
Success(
DetailedCharacterData(
DetailedPlayerData.apply(
PlacementData(obj.Position, obj.Orientation, obj.Velocity),
MakeAppearanceData(obj),
obj.BEP,
obj.CEP,
obj.MaxHealth,
obj.Health,
obj.Armor,
obj.MaxStamina,
obj.Stamina,
obj.Certifications.toList.sortBy(_.id), //TODO is sorting necessary?
MakeImplantEntries(obj),
List.empty[String], //TODO fte list
List.empty[String], //TODO tutorial list
MakeCosmetics(obj.BEP),
DetailedCharacterData(
obj.BEP,
obj.CEP,
obj.MaxHealth,
obj.Health,
obj.Armor,
obj.MaxStamina,
obj.Stamina,
obj.Certifications.toList.sortBy(_.id), //TODO is sorting necessary?
MakeImplantEntries(obj),
List.empty[String], //TODO fte list
List.empty[String], //TODO tutorial list
MakeCosmetics(obj.BEP)
),
InventoryData((MakeHolsters(obj, BuildDetailedEquipment) ++ MakeFifthSlot(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)),
GetDrawnSlot(obj)
)
@ -55,9 +60,8 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
* @param obj the `Player` game object
* @return the resulting `CharacterAppearanceData`
*/
private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = {
private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
CharacterAppearanceData(
PlacementData(obj.Position, obj.Orientation, obj.Velocity),
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, obj.Voice),
0,
false,

View file

@ -3,7 +3,7 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.{EquipmentSlot, Player}
import net.psforever.objects.equipment.Equipment
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars}
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{GrenadeState, ImplantType}
import scala.annotation.tailrec
@ -15,19 +15,22 @@ import scala.util.{Failure, Success, Try}
* Details that would not be apparent on that screen such as implants or certifications are ignored.
*/
class CharacterSelectConverter extends AvatarConverter {
override def ConstructorData(obj : Player) : Try[CharacterData] = Failure(new Exception("CharacterSelectConverter should not be used to generate CharacterData"))
override def ConstructorData(obj : Player) : Try[PlayerData] = Failure(new Exception("CharacterSelectConverter should not be used to generate CharacterData"))
override def DetailedConstructorData(obj : Player) : Try[DetailedCharacterData] = {
override def DetailedConstructorData(obj : Player) : Try[DetailedPlayerData] = {
Success(
DetailedCharacterData(
DetailedPlayerData.apply(
PlacementData(0, 0, 0),
MakeAppearanceData(obj),
obj.BEP,
obj.CEP,
1, 1, 0, 1, 1,
Nil,
MakeImplantEntries(obj), //necessary for correct stream length
Nil, Nil,
MakeCosmetics(obj.BEP),
DetailedCharacterData(
obj.BEP,
obj.CEP,
1, 1, 0, 1, 1,
Nil,
MakeImplantEntries(obj), //necessary for correct stream length
Nil, Nil,
MakeCosmetics(obj.BEP)
),
InventoryData(recursiveMakeHolsters(obj.Holsters().iterator)),
GetDrawnSlot(obj)
)
@ -40,9 +43,8 @@ class CharacterSelectConverter extends AvatarConverter {
* @see `AvatarConverter.MakeAppearanceData`
* @return the resulting `CharacterAppearanceData`
*/
private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = {
private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
CharacterAppearanceData(
PlacementData(0f, 0f, 0f),
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, 1),
0,
false,

View file

@ -3,23 +3,26 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.{EquipmentSlot, Player}
import net.psforever.objects.equipment.Equipment
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, InternalSlot, InventoryData, PlacementData, RibbonBars}
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{CharacterGender, GrenadeState, Vector3}
import scala.annotation.tailrec
import scala.util.{Failure, Success, Try}
class CorpseConverter extends AvatarConverter {
override def ConstructorData(obj : Player) : Try[CharacterData] =
override def ConstructorData(obj : Player) : Try[PlayerData] =
Failure(new Exception("CorpseConverter should not be used to generate CharacterData"))
override def DetailedConstructorData(obj : Player) : Try[DetailedCharacterData] = {
override def DetailedConstructorData(obj : Player) : Try[DetailedPlayerData] = {
Success(
DetailedCharacterData(
DetailedPlayerData.apply(
PlacementData(obj.Position, Vector3(0,0, obj.Orientation.z)),
MakeAppearanceData(obj),
0, 0, 0, 0, 0, 0, 0,
Nil, Nil, Nil, Nil,
None,
DetailedCharacterData(
0, 0, 0, 0, 0, 0, 0,
Nil, Nil, Nil, Nil,
None
),
InventoryData((MakeHolsters(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)),
DrawnSlot.None
)
@ -31,9 +34,8 @@ class CorpseConverter extends AvatarConverter {
* @param obj the `Player` game object
* @return the resulting `CharacterAppearanceData`
*/
private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = {
private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
CharacterAppearanceData(
PlacementData(obj.Position, Vector3(0,0, obj.Orientation.z)),
BasicCharacterData(obj.Name, obj.Faction, CharacterGender.Male, 0, 0),
0,
false,

View file

@ -23,6 +23,8 @@ import shapeless.{::, HNil}
* `5 - male_5 &nbsp;&nbsp; female_5`<br>
* `6 - female_1 &nbsp;no voice`<br>
* `7 - female_2 &nbsp;no voice`
* @see `PlanetSideEmpire`<br>
* `CharacaterGender`
* @param name the unique name of the avatar;
* minimum of two characters
* @param faction the empire to which the avatar belongs
@ -30,8 +32,6 @@ import shapeless.{::, HNil}
* @param head the avatar's face and hair;
* by row and column on the character creation screen, the high nibble is the row and the low nibble is the column
* @param voice the avatar's voice selection
* @see `PlanetSideEmpire`
* @see `CharacaterGender`
*/
final case class BasicCharacterData(name : String,
faction : PlanetSideEmpire.Value,
@ -60,8 +60,12 @@ final case class BasicCharacterData(name : String,
* <br>
* Exploration:<br>
* How do I crouch?
* @param pos the position of the character in the world environment (in three coordinates)
* @param basic_appearance the player's cardinal appearance settings
* @see `CharacterData`<br>
* `DetailedCharacterData`<br>
* `ExoSuitType`<br>
* `GrenadeState`<br>
* `RibbonBars`
* @param app the player's cardinal appearance settings
* @param voice2 na;
* affects the frequency by which the character's voice is heard (somehow);
* commonly 3 for best results
@ -86,16 +90,8 @@ final case class BasicCharacterData(name : String,
* @param charging_pose animation pose for both charging modules and BFR imprinting
* @param on_zipline player's model is changed into a faction-color ball of energy, as if on a zip line
* @param ribbons the four merit commendation ribbon medals
* @see `CharacterData`
* @see `DetailedCharacterData`
* @see `PlacementData`
* @see `ExoSuitType`
* @see `GrenadeState`
* @see `RibbonBars`
* @see `http://wiki.planetsidesyndicate.com/index.php?title=Outfit_Logo` for a list of outfit decals
*/
final case class CharacterAppearanceData(pos : PlacementData,
basic_appearance : BasicCharacterData,
final case class CharacterAppearanceData(app : BasicCharacterData,
voice2 : Int,
black_ops : Boolean,
jammered : Boolean,
@ -110,22 +106,29 @@ final case class CharacterAppearanceData(pos : PlacementData,
is_cloaking : Boolean,
charging_pose : Boolean,
on_zipline : Boolean,
ribbons : RibbonBars) extends StreamBitSize {
ribbons : RibbonBars)
(name_padding : Int) extends StreamBitSize {
override def bitsize : Long = {
//factor guard bool values into the base size, not its corresponding optional field
val placementSize : Long = pos.bitsize
val nameStringSize : Long = StreamBitSize.stringBitSize(basic_appearance.name, 16) + CharacterAppearanceData.namePadding(pos.vel)
val nameStringSize : Long = StreamBitSize.stringBitSize(app.name, 16) + CharacterAppearanceData.namePaddingRule(name_padding)
val outfitStringSize : Long = StreamBitSize.stringBitSize(outfit_name, 16) + CharacterAppearanceData.outfitNamePadding
val altModelSize = CharacterAppearanceData.altModelBit(this).getOrElse(0)
335L + placementSize + nameStringSize + outfitStringSize + altModelSize
335L + nameStringSize + outfitStringSize + altModelSize
}
/**
* When a player is released-dead or attached to a zipline, their basic infantry model is replaced with a different one.
* @return the length of the variable field that exists when using alternate models
*/
def altModelBit : Option[Int] = CharacterAppearanceData.altModelBit(this)
}
object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
/**
* When a player is released-dead or attached to a zipline, their basic infantry model is replaced with a different one.
* In the former casde, a backpack.
* In the former case, a backpack.
* In the latter case, a ball of colored energy.
* In this state, the length of the stream of data is modified.
* @param app the appearance
@ -143,14 +146,13 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
* The padding will always be a number 0-7.
* @return the pad length in bits
*/
def namePadding(move : Option[_]) : Int = {
if(move.isDefined) {
2
def namePaddingRule(pad : Int) : Int =
if(pad == 0) {
1 //normal alignment padding for the string
}
else {
4
pad //custom padding value
}
}
/**
* Get the padding of the outfit's name.
@ -161,77 +163,78 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
6
}
implicit val codec : Codec[CharacterAppearanceData] = (
("pos" | PlacementData.codec) >>:~ { pos =>
("faction" | PlanetSideEmpire.codec) ::
("black_ops" | bool) ::
(("alt_model" | bool) >>:~ { alt_model => //modifies stream format (to display alternate player models)
def codec(name_padding : Int) : Codec[CharacterAppearanceData] = (
("faction" | PlanetSideEmpire.codec) ::
("black_ops" | bool) ::
(("alt_model" | bool) >>:~ { alt_model => //modifies stream format (to display alternate player models)
ignore(1) :: //unknown
("jammered" | bool) ::
bool :: //crashes client
uint(16) :: //unknown, but usually 0
("name" | PacketHelpers.encodedWideStringAligned( namePaddingRule(name_padding) )) ::
("exosuit" | ExoSuitType.codec) ::
ignore(2) :: //unknown
("sex" | CharacterGender.codec) ::
("head" | uint8L) ::
("voice" | uint(3)) ::
("voice2" | uint2L) ::
ignore(78) :: //unknown
uint16L :: //usually either 0 or 65535
uint32L :: //for outfit_name (below) to be visible in-game, this value should be non-zero
("outfit_name" | PacketHelpers.encodedWideStringAligned( outfitNamePadding )) ::
("outfit_logo" | uint8L) ::
ignore(1) :: //unknown
("jammered" | bool) ::
bool :: //crashes client
uint(16) :: //unknown, but usually 0
("name" | PacketHelpers.encodedWideStringAligned( namePadding(pos.vel) )) ::
("exosuit" | ExoSuitType.codec) ::
ignore(2) :: //unknown
("sex" | CharacterGender.codec) ::
("head" | uint8L) ::
("voice" | uint(3)) ::
("voice2" | uint2L) ::
ignore(78) :: //unknown
uint16L :: //usually either 0 or 65535
uint32L :: //for outfit_name (below) to be visible in-game, this value should be non-zero
("outfit_name" | PacketHelpers.encodedWideStringAligned( outfitNamePadding )) ::
("outfit_logo" | uint8L) ::
ignore(1) :: //unknown
("backpack" | bool) :: //requires alt_model flag (does NOT require health == 0)
bool :: //stream misalignment when set
("facingPitch" | Angular.codec_pitch) ::
("facingYawUpper" | Angular.codec_yaw(0f)) ::
ignore(1) :: //unknown
conditional(alt_model, bool) :: //alt_model flag adds a bit before lfs
ignore(1) :: //an alternate lfs?
("lfs" | bool) ::
("grenade_state" | GrenadeState.codec_2u) :: //note: bin10 and bin11 are neutral (bin00 is not defined)
("is_cloaking" | bool) ::
ignore(1) :: //unknown
bool :: //stream misalignment when set
("charging_pose" | bool) ::
ignore(1) :: //alternate charging pose?
("on_zipline" | bool) :: //requires alt_model flag
("ribbons" | RibbonBars.codec)
})
}).exmap[CharacterAppearanceData] (
("backpack" | bool) :: //requires alt_model flag (does NOT require health == 0)
bool :: //stream misalignment when set
("facingPitch" | Angular.codec_pitch) ::
("facingYawUpper" | Angular.codec_yaw(0f)) ::
ignore(1) :: //unknown
conditional(alt_model, bool) :: //alt_model flag adds a bit before lfs
ignore(1) :: //an alternate lfs?
("lfs" | bool) ::
("grenade_state" | GrenadeState.codec_2u) :: //note: bin10 and bin11 are neutral (bin00 is not defined)
("is_cloaking" | bool) ::
ignore(1) :: //unknown
bool :: //stream misalignment when set
("charging_pose" | bool) ::
ignore(1) :: //alternate charging pose?
("on_zipline" | bool) :: //requires alt_model flag
("ribbons" | RibbonBars.codec)
})
).exmap[CharacterAppearanceData] (
{
case _ :: _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil |
_ :: _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: HNil =>
case _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil |
_ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: HNil =>
Attempt.Failure(Err("invalid character appearance data; can not encode alternate model without required bit set"))
case pos :: faction :: bops :: _ :: _ :: jamd :: false :: 0 :: name :: suit :: _ :: sex :: head :: v1 :: v2 :: _ :: _ :: _/*has_outfit_name*/ :: outfit :: logo :: _ :: bpack :: false :: facingPitch :: facingYawUpper :: _ :: _ :: _ :: lfs :: gstate :: cloaking :: _ :: false :: charging :: _ :: zipline :: ribbons :: HNil =>
case faction :: bops :: _ :: _ :: jamd :: false :: 0 :: name :: suit :: _ :: sex :: head :: v1 :: v2 :: _ :: _ :: _/*has_outfit_name*/ :: outfit :: logo :: _ :: bpack :: false :: facingPitch :: facingYawUpper :: _ :: _ :: _ :: lfs :: gstate :: cloaking :: _ :: false :: charging :: _ :: zipline :: ribbons :: HNil =>
Attempt.successful(
CharacterAppearanceData(pos, BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons)
CharacterAppearanceData(BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons)(name_padding)
)
case _ =>
Attempt.Failure(Err("invalid character appearance data; can not encode"))
},
{
case CharacterAppearanceData(_, BasicCharacterData(name, PlanetSideEmpire.NEUTRAL, _, _, _), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
case CharacterAppearanceData(BasicCharacterData(name, PlanetSideEmpire.NEUTRAL, _, _, _), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
Attempt.failure(Err(s"character $name's faction can not declare as neutral"))
case CharacterAppearanceData(pos, BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons) =>
case CharacterAppearanceData(BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons) =>
val has_outfit_name : Long = outfit.length.toLong //TODO this is a kludge
var alt_model : Boolean = false
var alt_model : Boolean = false
var alt_model_extrabit : Option[Boolean] = None
if(zipline || bpack) {
alt_model = true
alt_model_extrabit = Some(false)
}
Attempt.successful(
pos :: faction :: bops :: alt_model :: () :: jamd :: false :: 0 :: name :: suit :: () :: sex :: head :: v1 :: v2 :: () :: 0 :: has_outfit_name :: outfit :: logo :: () :: bpack :: false :: facingPitch :: facingYawUpper :: () :: alt_model_extrabit :: () :: lfs :: gstate :: cloaking :: () :: false :: charging :: () :: zipline :: ribbons :: HNil
faction :: bops :: alt_model :: () :: jamd :: false :: 0 :: name :: suit :: () :: sex :: head :: v1 :: v2 :: () :: 0 :: has_outfit_name :: outfit :: logo :: () :: bpack :: false :: facingPitch :: facingYawUpper :: () :: alt_model_extrabit :: () :: lfs :: gstate :: cloaking :: () :: false :: charging :: () :: zipline :: ribbons :: HNil
)
case _ =>
Attempt.Failure(Err("invalid character appearance data; can not decode"))
}
)
implicit val codec : Codec[CharacterAppearanceData] = codec(0)
}

View file

@ -42,27 +42,17 @@ object UniformStyle extends Enumeration {
}
/**
* A part of a representation of the avatar portion of `ObjectCreateMessage` packet data.
* This densely-packed information outlines most of the specifics of depicting some other character.<br>
* A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.<br>
* <br>
* The character created by this data is treated like an NPC from the perspective of the server.
* This densely-packed information outlines most of the specifics required to depict some other player's character.
* Someone else decides how that character is behaving and the server tells each client how to depict that behavior.
* For that reason, the character is mostly for presentation purposes, rather than really being fleshed-out.
* (As far as the client is concerned, nothing stops this character from being declared an "avatar."
* A player would find such a client-controlled character lacking many important details and have poor equipment.
* They would also be competing with some other player for input control, if they could control the character at all.)<br>
* Of the inventory for this character, only the initial five weapon slots are defined.<br>
* <br>
* Divisions exist to make the data more manageable.
* The first division of data only manages the general appearance of the player's in-game model.
* The second division (currently, the fields actually in this class) manages the status of the character.
* In general, it passes more simplified data about the character, the minimum that is necessary to explain status to some other player.
* For example, health and armor are percentages, and are depicted as bars over the player's head near the nameplate.
* The third is the inventory (composed of normal-type objects).
* Rather than equipment other players would never interact with, it only comprises the contents of the five holster slots.<br>
* <br>
* If this player is spawned as dead - with their `health` at 0% - he will start standing and then immediately fall into a lying pose.
* The death pose selected is randomized, can not be influenced, and is not be shared across clients.
* @param appearance the player's cardinal appearance settings
* In the "backend of the client," the character produced by this data is no different
* from the kind of character that could be declared a given player's avatar.
* In terms of equipment and complicated features common to an avatar character, however,
* any user would find this character ill-equipped.
* @param health the amount of health the player has, as a percentage of a filled bar;
* the bar has 85 states, with 3 points for each state;
* when 0% (less than 3 of 255), the player will collapse into a death pose on the ground
@ -76,89 +66,69 @@ object UniformStyle extends Enumeration {
* @param cosmetics optional decorative features that are added to the player's head model by console/chat commands;
* they become available at battle rank 24, but here they require the third uniform upgrade (rank 25);
* these flags do not exist if they are not applicable
* @param inventory the avatar's inventory;
* typically, only the tools and weapons in the equipment holster slots
* @param drawn_slot the holster that is initially drawn;
* defaults to `DrawnSlot.None`
* @see `CharacterAppearanceData`
* @see `DetailedCharacterData`
* @see `InventoryData`
* @see `DrawnSlot`
*/
final case class CharacterData(appearance : CharacterAppearanceData,
health : Int,
final case class CharacterData(health : Int,
armor : Int,
uniform_upgrade : UniformStyle.Value,
unk : Int,
command_rank : Int,
implant_effects : Option[ImplantEffects.Value],
cosmetics : Option[Cosmetics],
inventory : Option[InventoryData],
drawn_slot : DrawnSlot.Value = DrawnSlot.None
) extends ConstructorData {
cosmetics : Option[Cosmetics])
(is_backpack : Boolean) extends ConstructorData {
override def bitsize : Long = {
//factor guard bool values into the base size, not its corresponding optional field
val appearanceSize : Long = appearance.bitsize
val effectsSize : Long = if(implant_effects.isDefined) { 4L } else { 0L }
val cosmeticsSize : Long = if(cosmetics.isDefined) { cosmetics.get.bitsize } else { 0L }
val inventorySize : Long = if(inventory.isDefined) { inventory.get.bitsize } else { 0L }
32L + appearanceSize + effectsSize + cosmeticsSize + inventorySize
27L + effectsSize + cosmeticsSize
}
}
object CharacterData extends Marshallable[CharacterData] {
/**
* An overloaded constructor for `CharacterData` that allows for a not-optional inventory.
* @param appearance the player's cardinal appearance settings
* @param health the amount of health the player has, as a percentage of a filled bar
* @param armor the amount of armor the player has, as a percentage of a filled bar
* @param uniform the level of upgrade to apply to the player's base uniform
* @param cr the player's command rank as a number from 0 to 5
* @param implant_effects the effects of implants that can be seen on a player's character
* @param cosmetics optional decorative features that are added to the player's head model by console/chat commands
* @param inv the avatar's inventory
* @param drawn_slot the holster that is initially drawn
* //@param inv the avatar's inventory
* //@param drawn_slot the holster that is initially drawn
* @return a `CharacterData` object
*/
def apply(appearance : CharacterAppearanceData, health : Int, armor : Int, uniform : UniformStyle.Value, cr : Int, implant_effects : Option[ImplantEffects.Value], cosmetics : Option[Cosmetics], inv : InventoryData, drawn_slot : DrawnSlot.Value) : CharacterData =
new CharacterData(appearance, health, armor, uniform, cr, implant_effects, cosmetics, Some(inv), drawn_slot)
def apply(health : Int, armor : Int, uniform : UniformStyle.Value, cr : Int, implant_effects : Option[ImplantEffects.Value], cosmetics : Option[Cosmetics]) : (Boolean)=>CharacterData =
CharacterData(health, armor, uniform, 0, cr, implant_effects, cosmetics)
implicit val codec : Codec[CharacterData] = (
("app" | CharacterAppearanceData.codec) ::
("health" | uint8L) :: //dead state when health == 0
def codec(is_backpack : Boolean) : Codec[CharacterData] = (
("health" | uint8L) :: //dead state when health == 0
("armor" | uint8L) ::
(("uniform_upgrade" | UniformStyle.codec) >>:~ { style =>
ignore(3) :: //unknown
("command_rank" | uintL(3)) ::
bool :: //stream misalignment when != 1
optional(bool, "implant_effects" | ImplantEffects.codec) ::
conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec) ::
optional(bool, "inventory" | InventoryData.codec) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false
conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec)
})
).exmap[CharacterData] (
).exmap[CharacterData] (
{
case app :: health :: armor :: uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: inv :: drawn_slot :: false :: HNil =>
var newHealth = health
if(app.backpack) {
newHealth = 0
}
Attempt.Successful(CharacterData(app, newHealth, armor, uniform, cr, implant_effects, cosmetics, inv, drawn_slot))
case health :: armor :: uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: HNil =>
val newHealth = if(is_backpack) { 0 } else { health }
Attempt.Successful(new CharacterData(newHealth, armor, uniform, 0, cr, implant_effects, cosmetics)(is_backpack))
case _ =>
Attempt.Failure(Err("invalid character data; can not encode"))
},
{
case CharacterData(app, health, armor, uniform, cr, implant_effects, cosmetics, inv, drawn_slot) =>
var newHealth = health
if(app.backpack) {
newHealth = 0
}
Attempt.Successful(app :: newHealth :: armor :: uniform :: () :: cr :: false :: implant_effects :: cosmetics :: inv :: drawn_slot :: false :: HNil)
case CharacterData(health, armor, uniform, _, cr, implant_effects, cosmetics) =>
val newHealth = if(is_backpack) { 0 } else { health }
Attempt.Successful(newHealth :: armor :: uniform :: () :: cr :: false :: implant_effects :: cosmetics :: HNil)
case _ =>
Attempt.Failure(Err("invalid character data; can not decode"))
}
)
implicit val codec : Codec[CharacterData] = codec(false)
}

View file

@ -27,22 +27,14 @@ final case class ImplantEntry(implant : ImplantType.Value,
}
/**
* A representation of the avatar portion of `ObjectCreateDetailedMessage` packet data.
* This densely-packed information outlines most of the specifics required to depict a character as an avatar.<br>
* A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.<br>
* <br>
* As an avatar, the character created by this data is expected to be controllable by the client that gets sent this data.
* It goes into depth about information related to the given character in-game career that is not revealed to other players.<br>
* <br>
* Divisions exist to make the data more manageable.
* The first division of data only manages the general appearance of the player's in-game model.
* It is shared between `DetailedCharacterData` avatars and `CharacterData` player characters.
* The second division (of fields) manages the status of the character as an avatar.
* In general, it passes more thorough data about the character that the client can display to the owner of the client.
* This densely-packed information outlines most of the specifics required to depict a character as an avatar.
* It goes into depth about information related to the given character in-game career that is not revealed to other players.
* To be specific, it passes more thorough data about the character that the client can display to the owner of the client.
* For example, health is a full number, rather than a percentage.
* Just as prominent is the list of first time events and the list of completed tutorials.
* The third subdivision is also exclusive to avatar-prepared characters and contains (omitted).
* The fourth is the inventory (composed of `Direct`-type objects).
* @param appearance data about the avatar's basic aesthetics
* Additionally, a full inventory, as opposed to the initial five weapon slots.
* @param bep the avatar's battle experience points, which determines his Battle Rank
* @param cep the avatar's command experience points, which determines his Command Rank
* @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value;
@ -73,16 +65,10 @@ final case class ImplantEntry(implant : ImplantType.Value,
* @param cosmetics optional decorative features that are added to the player's head model by console/chat commands;
* they become available at battle rank 24;
* these flags do not exist if they are not applicable
* @param inventory the avatar's inventory
* @param drawn_slot the holster that is initially drawn
* @see `CharacterAppearanceData`<br>
* `CharacterData`<br>
* `CertificationType`<br>
* `InventoryData`<br>
* `DrawnSlot`
* @see `CharacterData`<br>
* `CertificationType`
*/
final case class DetailedCharacterData(appearance : CharacterAppearanceData,
bep : Long,
final case class DetailedCharacterData(bep : Long,
cep : Long,
healthMax : Int,
health : Int,
@ -96,20 +82,17 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData,
implants : List[ImplantEntry],
firstTimeEvents : List[String],
tutorials : List[String],
cosmetics : Option[Cosmetics],
inventory : Option[InventoryData],
drawn_slot : DrawnSlot.Value = DrawnSlot.None
) extends ConstructorData {
cosmetics : Option[Cosmetics])
(pad_length : Option[Int]) extends ConstructorData {
override def bitsize : Long = {
//factor guard bool values into the base size, not corresponding optional fields, unless contained or enumerated
val appearanceSize = appearance.bitsize
val certSize = (certs.length + 1) * 8 //cert list
var implantSize : Long = 0L //implant list
for(entry <- implants) {
implantSize += entry.bitsize
}
val implantPadding = DetailedCharacterData.implantFieldPadding(implants, CharacterAppearanceData.altModelBit(appearance))
val implantPadding = DetailedCharacterData.implantFieldPadding(implants, pad_length)
val fteLen = firstTimeEvents.size //fte list
var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen, implantPadding)
for(str <- firstTimeEvents) {
@ -123,20 +106,13 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData,
val br24 = DetailedCharacterData.isBR24(bep) //character is at least BR24
val extraBitSize : Long = if(br24) { 33L } else { 46L }
val cosmeticsSize : Long = if(br24) { cosmetics.get.bitsize } else { 0L }
val inventorySize : Long = if(inventory.isDefined) { //inventory
inventory.get.bitsize
}
else {
0L
}
603L + appearanceSize + certSize + implantSize + eventListSize + extraBitSize + cosmeticsSize + tutorialListSize + inventorySize
598L + certSize + implantSize + eventListSize + extraBitSize + cosmeticsSize + tutorialListSize
}
}
object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
/**
* Overloaded constructor for `DetailedCharacterData` that requires an inventory and drops unknown values.
* @param appearance data about the avatar's basic aesthetics
* @param bep the avatar's battle experience points, which determines his Battle Rank
* @param cep the avatar's command experience points, which determines his Command Rank
* @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value
@ -148,12 +124,10 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
* @param implants the `List` of implant slots currently possessed by this avatar
* @param firstTimeEvents the list of first time events performed by this avatar
* @param tutorials the list of tutorials completed by this avatar
* @param inventory the avatar's inventory
* @param drawn_slot the holster that is initially drawn
* @return a `DetailedCharacterData` object
*/
def apply(appearance : CharacterAppearanceData, bep : Long, cep : Long, healthMax : Int, health : Int, armor : Int, staminaMax : Int, stamina : Int, certs : List[CertificationType.Value], implants : List[ImplantEntry], firstTimeEvents : List[String], tutorials : List[String], cosmetics : Option[Cosmetics], inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedCharacterData =
new DetailedCharacterData(appearance, bep, cep, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, certs, implants, firstTimeEvents, tutorials, cosmetics, Some(inventory), drawn_slot)
def apply(bep : Long, cep : Long, healthMax : Int, health : Int, armor : Int, staminaMax : Int, stamina : Int, certs : List[CertificationType.Value], implants : List[ImplantEntry], firstTimeEvents : List[String], tutorials : List[String], cosmetics : Option[Cosmetics]) : (Option[Int])=>DetailedCharacterData =
DetailedCharacterData(bep, cep, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, certs, implants, firstTimeEvents, tutorials, cosmetics)
/**
* `Codec` for entries in the `List` of implants.
@ -278,57 +252,52 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
def isBR24(bep : Long) : Boolean = bep > 2286230
implicit val codec : Codec[DetailedCharacterData] = (
("appearance" | CharacterAppearanceData.codec) >>:~ { app =>
("bep" | uint32L) >>:~ { bep =>
("cep" | uint32L) ::
ignore(96) ::
("healthMax" | uint16L) ::
("health" | uint16L) ::
ignore(1) ::
("armor" | uint16L) ::
ignore(9) ::
("unk1" | uint8L) ::
ignore(8) ::
("unk2" | uint4L) ::
("unk3" | uintL(3)) ::
("staminaMax" | uint16L) ::
("stamina" | uint16L) ::
ignore(147) ::
("certs" | listOfN(uint8L, CertificationType.codec)) ::
optional(bool, uint32L) :: //ask about sample CCRIDER
ignore(4) ::
(("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants =>
ignore(12) ::
(("firstTimeEvent_length" | uint32L) >>:~ { len =>
conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned(ftePadding(len, implantFieldPadding(implants, CharacterAppearanceData.altModelBit(app))))) ::
("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) ::
(("tutorial_length" | uint32L) >>:~ { len2 =>
conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned(tutPadding(len, len2, implantFieldPadding(implants, CharacterAppearanceData.altModelBit(app))))) ::
("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) ::
ignore(160) ::
(bool >>:~ { br24 => //BR24+
newcodecs.binary_choice(br24, ignore(33), ignore(46)) ::
conditional(br24, Cosmetics.codec) ::
optional(bool, "inventory" | InventoryData.codec_detailed) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false
})
})
})
})
}
def codec(pad_length : Option[Int]) : Codec[DetailedCharacterData] = (
("bep" | uint32L) >>:~ { bep =>
("cep" | uint32L) ::
ignore(96) ::
("healthMax" | uint16L) ::
("health" | uint16L) ::
ignore(1) ::
("armor" | uint16L) ::
ignore(9) ::
("unk1" | uint8L) ::
ignore(8) ::
("unk2" | uint4L) ::
("unk3" | uintL(3)) ::
("staminaMax" | uint16L) ::
("stamina" | uint16L) ::
ignore(147) ::
("certs" | listOfN(uint8L, CertificationType.codec)) ::
optional(bool, uint32L) :: //ask about sample CCRIDER
ignore(4) ::
(("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants =>
ignore(12) ::
(("firstTimeEvent_length" | uint32L) >>:~ { len =>
conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned(ftePadding(len, implantFieldPadding(implants, pad_length)))) ::
("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) ::
(("tutorial_length" | uint32L) >>:~ { len2 =>
conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned(tutPadding(len, len2, implantFieldPadding(implants, pad_length)))) ::
("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) ::
ignore(160) ::
(bool >>:~ { br24 => //BR24+
newcodecs.binary_choice(br24, ignore(33), ignore(46)) ::
conditional(br24, Cosmetics.codec)
})
})
})
})
}
).exmap[DetailedCharacterData] (
{
case app :: bep :: cep :: _ :: hpmax :: hp :: _ :: armor :: _ :: u1 :: _ :: u2 :: u3 :: stamax :: stam :: _ :: certs :: _ :: _ :: implants :: _ :: _ :: fte0 :: fte1 :: _ :: tut0 :: tut1 :: _ :: _ :: _ :: cosmetics :: inv :: drawn :: false :: HNil =>
case bep :: cep :: _ :: hpmax :: hp :: _ :: armor :: _ :: u1 :: _ :: u2 :: u3 :: stamax :: stam :: _ :: certs :: _ :: _ :: implants :: _ :: _ :: fte0 :: fte1 :: _ :: tut0 :: tut1 :: _ :: _ :: _ :: cosmetics :: HNil =>
//prepend the displaced first elements to their lists
val fteList : List[String] = if(fte0.isDefined) { fte0.get +: fte1 } else { fte1 }
val tutList : List[String] = if(tut0.isDefined) { tut0.get +: tut1 } else { tut1 }
Attempt.successful(DetailedCharacterData(app, bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cosmetics, inv, drawn))
Attempt.successful(DetailedCharacterData(bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cosmetics)(pad_length))
},
{
case DetailedCharacterData(app, bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cos, inv, drawn) =>
case DetailedCharacterData(bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cos) =>
val implantCapacity : Int = numberOfImplantSlots(bep)
val implantList = if(implants.length > implantCapacity) {
implants.slice(0, implantCapacity)
@ -349,7 +318,9 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
}
val br24 : Boolean = isBR24(bep)
val cosmetics : Option[Cosmetics] = if(br24) { cos } else { None }
Attempt.successful(app :: bep :: cep :: () :: hpmax :: hp :: () :: armor :: () :: u1 :: () :: u2 :: u3 :: stamax :: stam :: () :: certs :: None :: () :: implantList :: () :: fteList.size.toLong :: firstEvent :: fteListCopy :: tutList.size.toLong :: firstTutorial :: tutListCopy :: () :: br24 :: () :: cosmetics :: inv :: drawn :: false :: HNil)
Attempt.successful(bep :: cep :: () :: hpmax :: hp :: () :: armor :: () :: u1 :: () :: u2 :: u3 :: stamax :: stam :: () :: certs :: None :: () :: implantList :: () :: fteList.size.toLong :: firstEvent :: fteListCopy :: tutList.size.toLong :: firstTutorial :: tutListCopy :: () :: br24 :: () :: cosmetics :: HNil)
}
)
implicit val codec : Codec[DetailedCharacterData] = codec(None)
}

View file

@ -0,0 +1,103 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import scodec.codecs._
import scodec.Codec
import shapeless.{::, HNil}
/**
* A representation of an `avatar` player for the `ObjectCreateDetailedMessage` packet.
* As an avatar, the character created by this data is expected to be controllable by the client that gets sent this data.<br>
* <br>
* Divisions exist to make the data more manageable.
* The first division defines the player's location within the game coordinate system.
* The second division defines features of the `avatar`
* that are shared by both the `ObjectCreateDetailedMessage` version of a controlled player character (this)
* and the `ObjectCreateMessage` version of a player character.
* The third field expands on the nature of the character and this avatar's campaign.
* Expansive information about previous interactions, the contents of their inventory, and equipment permissions are included.<br>
* <br>
* The presence or absence of position data as the first division creates a cascading effect
* causing all of fields in the other two divisions to gain offsets.
* These offsets exist in the form of `String` and `List` padding.
* @see `DetailedCharacterData`<br>
* `InventoryData`<br>
* `DrawnSlot`
* @param pos the optional position of the character in the world environment
* @param basic_appearance common fields regarding the the character's appearance
* @param character_data the class-specific data that explains about the character
* @param position_defined used by the `Codec` to seed the state of the optional `pos` field
* @param inventory the player's full inventory
* @param drawn_slot the holster that is initially drawn
*/
final case class DetailedPlayerData(pos : Option[PlacementData],
basic_appearance : CharacterAppearanceData,
character_data : DetailedCharacterData,
inventory : Option[InventoryData],
drawn_slot : DrawnSlot.Value)
(position_defined : Boolean) extends ConstructorData {
override def bitsize : Long = {
val posSize : Long = if(pos.isDefined) { pos.get.bitsize } else { 0 }
val appSize : Long = basic_appearance.bitsize
val charSize = character_data.bitsize
val inventorySize : Long = if(inventory.isDefined) { inventory.get.bitsize } else { 0L }
5L + posSize + appSize + charSize + inventorySize
}
}
object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
/**
* Overloaded constructor that ignores the coordinate information.
* It passes information between the three major divisions for the purposes of offset calculations.
* @param basic_appearance a curried function for the common fields regarding the the character's appearance
* @param character_data a curried function for the class-specific data that explains about the character
* @return a `DetailedPlayerData` object
*/
def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
val appearance = basic_appearance(0)
DetailedPlayerData(None, appearance, character_data(appearance.altModelBit), Some(inventory), drawn_slot)(false)
}
/** */
def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
val appearance = basic_appearance(0)
DetailedPlayerData(None, appearance, character_data(appearance.altModelBit), None, drawn_slot)(false)
}
/**
* Overloaded constructor that includes the coordinate information.
* It passes information between the three major divisions for the purposes of offset calculations.
* @param basic_appearance a curried function for the common fields regarding the the character's appearance
* @param character_data a curried function for the class-specific data that explains about the character
* @return a `DetailedPlayerData` object
*/
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
val appearance = basic_appearance(PlayerData.placementOffset(Some(pos)))
DetailedPlayerData(Some(pos), appearance, character_data(appearance.altModelBit), Some(inventory), drawn_slot)(true)
}
/** */
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
val appearance = basic_appearance(PlayerData.placementOffset(Some(pos)))
DetailedPlayerData(Some(pos), appearance, character_data(appearance.altModelBit), None, drawn_slot)(true)
}
def codec(position_defined : Boolean) : Codec[DetailedPlayerData] = (
conditional(position_defined, "pos" | PlacementData.codec) >>:~ { pos =>
("basic_appearance" | CharacterAppearanceData.codec(PlayerData.placementOffset(pos))) >>:~ { app =>
("character_data" | DetailedCharacterData.codec(app.altModelBit)) ::
optional(bool, "inventory" | InventoryData.codec_detailed) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false
}
}).xmap[DetailedPlayerData] (
{
case pos :: app :: data :: inv :: hand :: _ :: HNil =>
DetailedPlayerData(pos, app, data, inv, hand)(pos.isDefined)
},
{
case DetailedPlayerData(pos, app, data, inv, hand) =>
pos :: app :: data :: inv :: hand :: false :: HNil
}
)
implicit val codec : Codec[DetailedPlayerData] = codec(false)
}

View file

@ -666,7 +666,7 @@ object ObjectClass {
case ObjectClass.advanced_ace => ConstructorData.genericCodec(DetailedACEData.codec, "advanced ace")
case ObjectClass.boomer_trigger => ConstructorData.genericCodec(DetailedBoomerTriggerData.codec, "boomer trigger")
//other
case ObjectClass.avatar => ConstructorData.genericCodec(DetailedCharacterData.codec, "avatar")
case ObjectClass.avatar => ConstructorData.genericCodec(DetailedPlayerData.codec(true), "avatar")
case ObjectClass.locker_container => ConstructorData.genericCodec(DetailedLockerContainerData.codec, "locker container")
//failure case
@ -953,6 +953,7 @@ object ObjectClass {
//other
case ObjectClass.ams_order_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.ams_respawn_tube => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.avatar => ConstructorData.genericCodec(PlayerData.codec(false), "avatar")
case ObjectClass.bfr_rearm_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.implant_terminal_interface => ConstructorData.genericCodec(CommonTerminalData.codec, "implant terminal")
case ObjectClass.lodestar_repair_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
@ -1268,7 +1269,7 @@ object ObjectClass {
case ObjectClass.wasp => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle")
//other
case ObjectClass.ams_respawn_tube => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.avatar => ConstructorData.genericCodec(CharacterData.codec, "avatar")
case ObjectClass.avatar => ConstructorData.genericCodec(PlayerData.codec(true), "avatar")
case ObjectClass.capture_flag => ConstructorData.genericCodec(CaptureFlagData.codec, "capture flag")
case ObjectClass.implant_terminal_interface => ConstructorData.genericCodec(CommonTerminalData.codec, "implant terminal")
case ObjectClass.locker_container => ConstructorData.genericCodec(LockerContainerData.codec, "locker container")

View file

@ -0,0 +1,128 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import scodec.codecs._
import scodec.Codec
import shapeless.{::, HNil}
/**
* A representation of another player's character for the `ObjectCreateDetailedMessage` packet.
* In general, this packet is used to describe other players.<br>
* <br>
* Divisions exist to make the data more manageable.
* The first division defines the player's location within the game coordinate system.
* The second division defines features of the character
* that are shared by both the `ObjectCreateDetailedMessage` version of a controlled player character
* and the `ObjectCreateMessage` version of a player character (this).
* The third field provides further information on the appearance of the player character, albeit condensed.
* One of the most compact forms of a player character description is transcribed using this information.<br>
* <br>
* The presence or absence of position data as the first division creates a cascading effect
* causing all of fields in the other two divisions to gain offsets.
* These offsets exist in the form of `String` and `List` padding.
* @see `CharacterData`<br>
* `InventoryData`<br>
* `DrawnSlot`
* @param pos the optional position of the character in the world environment
* @param basic_appearance common fields regarding the the character's appearance
* @param character_data the class-specific data that explains about the character
* @param inventory the player's inventory;
* typically, only the tools and weapons in the equipment holster slots
* @param drawn_slot the holster that is initially drawn
* @param position_defined used by the `Codec` to seed the state of the optional `pos` field
*/
final case class PlayerData(pos : Option[PlacementData],
basic_appearance : CharacterAppearanceData,
character_data : CharacterData,
inventory : Option[InventoryData],
drawn_slot : DrawnSlot.Value)
(position_defined : Boolean) extends ConstructorData {
override def bitsize : Long = {
val posSize : Long = if(pos.isDefined) { pos.get.bitsize } else { 0 }
val appSize : Long = basic_appearance.bitsize
val charSize = character_data.bitsize
val inventorySize : Long = if(inventory.isDefined) { inventory.get.bitsize } else { 0L }
5L + posSize + appSize + charSize + inventorySize
}
}
object PlayerData extends Marshallable[PlayerData] {
/**
* Overloaded constructor that ignores the coordinate information.
* It passes information between the three major divisions for the purposes of offset calculations.
* @param basic_appearance a curried function for the common fields regarding the the character's appearance
* @param character_data a curried function for the class-specific data that explains about the character
* @param inventory the player's inventory
* @param drawn_slot the holster that is initially drawn
* @return a `PlayerData` object
*/
def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type) : PlayerData = {
val appearance = basic_appearance(0)
PlayerData(None, appearance, character_data(appearance.backpack), Some(inventory), drawn_slot)(false)
}
/** */
def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean)=>CharacterData, hand_held : DrawnSlot.Type) : PlayerData = {
val appearance = basic_appearance(0)
PlayerData(None, appearance, character_data(appearance.backpack), None, hand_held)(false)
}
/**
* Overloaded constructor that includes the coordinate information.
* It passes information between the three major divisions for the purposes of offset calculations.
* @param pos the optional position of the character in the world environment
* @param basic_appearance a curried function for the common fields regarding the the character's appearance
* @param character_data a curried function for the class-specific data that explains about the character
* @param inventory the player's inventory
* @param drawn_slot the holster that is initially drawn
* @return a `PlayerData` object
*/
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type) : PlayerData = {
val appearance = basic_appearance( placementOffset(Some(pos)) )
PlayerData(Some(pos), appearance, character_data(appearance.backpack), Some(inventory), drawn_slot)(true)
}
/** */
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean)=>CharacterData, hand_held : DrawnSlot.Type) : PlayerData = {
val appearance = basic_appearance( placementOffset(Some(pos)) )
PlayerData(Some(pos), appearance, character_data(appearance.backpack), None, hand_held)(true)
}
/**
* Determine the padding offset for a subsequent field given the existence of `PlacementData`.
* The padding will always be a number 0-7.
* @see `PlacemtnData`
* @param pos the optional `PlacementData` object that creates the shift in bits
* @return the pad length in bits
*/
def placementOffset(pos : Option[PlacementData]) : Int = {
if(pos.isEmpty) {
0
}
else if(pos.get.vel.isDefined) {
2
}
else {
4
}
}
def codec(position_defined : Boolean) : Codec[PlayerData] = (
conditional(position_defined, "pos" | PlacementData.codec) >>:~ { pos =>
("basic_appearance" | CharacterAppearanceData.codec(placementOffset(pos))) >>:~ { app =>
("character_data" | CharacterData.codec(app.backpack)) ::
optional(bool, "inventory" | InventoryData.codec) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false
}
}).xmap[PlayerData] (
{
case pos :: app :: data :: inv :: hand :: _ :: HNil =>
PlayerData(pos, app, data, inv, hand)(pos.isDefined)
},
{
case PlayerData(pos, app, data, inv, hand) =>
pos :: app :: data :: inv :: hand :: false :: HNil
}
)
implicit val codec : Codec[PlayerData] = codec(false)
}

View file

@ -20,93 +20,92 @@ class CharacterDataTest extends Specification {
cls mustEqual ObjectClass.avatar
guid mustEqual PlanetSideGUID(3902)
parent.isDefined mustEqual false
data.isDefined mustEqual true
data.get.isInstanceOf[CharacterData] mustEqual true
val pc = data.get.asInstanceOf[CharacterData]
pc.appearance.pos.coord.x mustEqual 3674.8438f
pc.appearance.pos.coord.y mustEqual 2726.789f
pc.appearance.pos.coord.z mustEqual 91.15625f
pc.appearance.pos.orient.x mustEqual 0f
pc.appearance.pos.orient.y mustEqual 0f
pc.appearance.pos.orient.z mustEqual 64.6875f
pc.appearance.pos.vel.isDefined mustEqual true
pc.appearance.pos.vel.get.x mustEqual 1.4375f
pc.appearance.pos.vel.get.y mustEqual -0.4375f
pc.appearance.pos.vel.get.z mustEqual 0f
pc.appearance.basic_appearance.name mustEqual "ScrawnyRonnie"
pc.appearance.basic_appearance.faction mustEqual PlanetSideEmpire.TR
pc.appearance.basic_appearance.sex mustEqual CharacterGender.Male
pc.appearance.basic_appearance.head mustEqual 5
pc.appearance.basic_appearance.voice mustEqual 5
pc.appearance.voice2 mustEqual 3
pc.appearance.black_ops mustEqual false
pc.appearance.jammered mustEqual false
pc.appearance.exosuit mustEqual ExoSuitType.Reinforced
pc.appearance.outfit_name mustEqual "Black Beret Armoured Corps"
pc.appearance.outfit_logo mustEqual 23
pc.appearance.facingPitch mustEqual 340.3125f
pc.appearance.facingYawUpper mustEqual 0
pc.appearance.lfs mustEqual false
pc.appearance.grenade_state mustEqual GrenadeState.None
pc.appearance.is_cloaking mustEqual false
pc.appearance.charging_pose mustEqual false
pc.appearance.on_zipline mustEqual false
pc.appearance.ribbons.upper mustEqual MeritCommendation.MarkovVeteran
pc.appearance.ribbons.middle mustEqual MeritCommendation.HeavyInfantry4
pc.appearance.ribbons.lower mustEqual MeritCommendation.TankBuster7
pc.appearance.ribbons.tos mustEqual MeritCommendation.SixYearTR
pc.health mustEqual 255
pc.armor mustEqual 253
pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
pc.command_rank mustEqual 5
pc.implant_effects.isDefined mustEqual true
pc.implant_effects.get mustEqual ImplantEffects.NoEffects
pc.cosmetics.isDefined mustEqual true
pc.cosmetics.get.no_helmet mustEqual true
pc.cosmetics.get.beret mustEqual true
pc.cosmetics.get.sunglasses mustEqual true
pc.cosmetics.get.earpiece mustEqual true
pc.cosmetics.get.brimmed_cap mustEqual false
//short test of inventory items
pc.inventory.isDefined mustEqual true
val contents = pc.inventory.get.contents
contents.size mustEqual 5
//0
contents.head.objectClass mustEqual ObjectClass.plasma_grenade
contents.head.guid mustEqual PlanetSideGUID(3662)
contents.head.parentSlot mustEqual 0
contents.head.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
contents.head.obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.plasma_grenade_ammo
contents.head.obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3751)
//1
contents(1).objectClass mustEqual ObjectClass.bank
contents(1).guid mustEqual PlanetSideGUID(3908)
contents(1).parentSlot mustEqual 1
contents(1).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
contents(1).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.armor_canister
contents(1).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(4143)
//2
contents(2).objectClass mustEqual ObjectClass.mini_chaingun
contents(2).guid mustEqual PlanetSideGUID(4164)
contents(2).parentSlot mustEqual 2
contents(2).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
contents(2).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.bullet_9mm
contents(2).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3728)
//3
contents(3).objectClass mustEqual ObjectClass.phoenix //actually, a decimator
contents(3).guid mustEqual PlanetSideGUID(3603)
contents(3).parentSlot mustEqual 3
contents(3).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
contents(3).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.phoenix_missile
contents(3).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3056)
//4
contents(4).objectClass mustEqual ObjectClass.chainblade
contents(4).guid mustEqual PlanetSideGUID(4088)
contents(4).parentSlot mustEqual 4
contents(4).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
contents(4).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.melee_ammo
contents(4).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3279)
pc.drawn_slot mustEqual DrawnSlot.Rifle1
data match {
case Some(PlayerData(Some(pos), basic, char, inv, hand)) =>
pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f)
pos.orient mustEqual Vector3(0f, 0f, 64.6875f)
pos.vel.isDefined mustEqual true
pos.vel.get mustEqual Vector3(1.4375f, -0.4375f, 0f)
basic.app.name mustEqual "ScrawnyRonnie"
basic.app.faction mustEqual PlanetSideEmpire.TR
basic.app.sex mustEqual CharacterGender.Male
basic.app.head mustEqual 5
basic.app.voice mustEqual 5
basic.voice2 mustEqual 3
basic.black_ops mustEqual false
basic.jammered mustEqual false
basic.exosuit mustEqual ExoSuitType.Reinforced
basic.outfit_name mustEqual "Black Beret Armoured Corps"
basic.outfit_logo mustEqual 23
basic.facingPitch mustEqual 340.3125f
basic.facingYawUpper mustEqual 0
basic.lfs mustEqual false
basic.grenade_state mustEqual GrenadeState.None
basic.is_cloaking mustEqual false
basic.charging_pose mustEqual false
basic.on_zipline mustEqual false
basic.ribbons.upper mustEqual MeritCommendation.MarkovVeteran
basic.ribbons.middle mustEqual MeritCommendation.HeavyInfantry4
basic.ribbons.lower mustEqual MeritCommendation.TankBuster7
basic.ribbons.tos mustEqual MeritCommendation.SixYearTR
char.health mustEqual 255
char.armor mustEqual 253
char.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
char.command_rank mustEqual 5
char.implant_effects.isDefined mustEqual true
char.implant_effects.get mustEqual ImplantEffects.NoEffects
char.cosmetics.isDefined mustEqual true
char.cosmetics.get.no_helmet mustEqual true
char.cosmetics.get.beret mustEqual true
char.cosmetics.get.sunglasses mustEqual true
char.cosmetics.get.earpiece mustEqual true
char.cosmetics.get.brimmed_cap mustEqual false
//short test of inventory items
inv.isDefined mustEqual true
val contents = inv.get.contents
contents.size mustEqual 5
//0
contents.head.objectClass mustEqual ObjectClass.plasma_grenade
contents.head.guid mustEqual PlanetSideGUID(3662)
contents.head.parentSlot mustEqual 0
contents.head.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
contents.head.obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.plasma_grenade_ammo
contents.head.obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3751)
//1
contents(1).objectClass mustEqual ObjectClass.bank
contents(1).guid mustEqual PlanetSideGUID(3908)
contents(1).parentSlot mustEqual 1
contents(1).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
contents(1).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.armor_canister
contents(1).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(4143)
//2
contents(2).objectClass mustEqual ObjectClass.mini_chaingun
contents(2).guid mustEqual PlanetSideGUID(4164)
contents(2).parentSlot mustEqual 2
contents(2).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
contents(2).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.bullet_9mm
contents(2).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3728)
//3
contents(3).objectClass mustEqual ObjectClass.phoenix //actually, a decimator
contents(3).guid mustEqual PlanetSideGUID(3603)
contents(3).parentSlot mustEqual 3
contents(3).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
contents(3).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.phoenix_missile
contents(3).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3056)
//4
contents(4).objectClass mustEqual ObjectClass.chainblade
contents(4).guid mustEqual PlanetSideGUID(4088)
contents(4).parentSlot mustEqual 4
contents(4).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
contents(4).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.melee_ammo
contents(4).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3279)
hand mustEqual DrawnSlot.Rifle1
case _ =>
ko
}
case _ =>
ko
}
@ -119,104 +118,105 @@ class CharacterDataTest extends Specification {
cls mustEqual ObjectClass.avatar
guid mustEqual PlanetSideGUID(3380)
parent.isDefined mustEqual false
data.isDefined mustEqual true
data.get.isInstanceOf[CharacterData] mustEqual true
val pc = data.get.asInstanceOf[CharacterData]
pc.appearance.pos.coord.x mustEqual 4629.8906f
pc.appearance.pos.coord.y mustEqual 6316.4453f
pc.appearance.pos.coord.z mustEqual 54.734375f
pc.appearance.pos.orient.x mustEqual 0f
pc.appearance.pos.orient.y mustEqual 0f
pc.appearance.pos.orient.z mustEqual 126.5625f
pc.appearance.pos.vel.isDefined mustEqual false
pc.appearance.basic_appearance.name mustEqual "Angello"
pc.appearance.basic_appearance.faction mustEqual PlanetSideEmpire.VS
pc.appearance.basic_appearance.sex mustEqual CharacterGender.Male
pc.appearance.basic_appearance.head mustEqual 10
pc.appearance.basic_appearance.voice mustEqual 2
pc.appearance.voice2 mustEqual 0
pc.appearance.black_ops mustEqual false
pc.appearance.jammered mustEqual false
pc.appearance.exosuit mustEqual ExoSuitType.MAX
pc.appearance.outfit_name mustEqual "Original District"
pc.appearance.outfit_logo mustEqual 23
pc.appearance.facingPitch mustEqual 0
pc.appearance.facingYawUpper mustEqual 180.0f
pc.appearance.lfs mustEqual false
pc.appearance.grenade_state mustEqual GrenadeState.None
pc.appearance.is_cloaking mustEqual false
pc.appearance.charging_pose mustEqual false
pc.appearance.on_zipline mustEqual false
pc.appearance.ribbons.upper mustEqual MeritCommendation.Jacking2
pc.appearance.ribbons.middle mustEqual MeritCommendation.ScavengerVS1
pc.appearance.ribbons.lower mustEqual MeritCommendation.AMSSupport4
pc.appearance.ribbons.tos mustEqual MeritCommendation.SixYearVS
pc.health mustEqual 0
pc.armor mustEqual 0
pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
pc.command_rank mustEqual 2
pc.implant_effects.isDefined mustEqual false
pc.cosmetics.isDefined mustEqual true
pc.cosmetics.get.no_helmet mustEqual true
pc.cosmetics.get.beret mustEqual true
pc.cosmetics.get.sunglasses mustEqual true
pc.cosmetics.get.earpiece mustEqual true
pc.cosmetics.get.brimmed_cap mustEqual false
pc.inventory.isDefined mustEqual false
pc.drawn_slot mustEqual DrawnSlot.Pistol1
data match {
case Some(PlayerData(Some(pos), basic, char, None, hand)) =>
pos.coord mustEqual Vector3(4629.8906f, 6316.4453f, 54.734375f)
pos.orient mustEqual Vector3(0, 0, 126.5625f)
pos.vel.isDefined mustEqual false
basic.app.name mustEqual "Angello"
basic.app.faction mustEqual PlanetSideEmpire.VS
basic.app.sex mustEqual CharacterGender.Male
basic.app.head mustEqual 10
basic.app.voice mustEqual 2
basic.voice2 mustEqual 0
basic.black_ops mustEqual false
basic.jammered mustEqual false
basic.exosuit mustEqual ExoSuitType.MAX
basic.outfit_name mustEqual "Original District"
basic.outfit_logo mustEqual 23
basic.facingPitch mustEqual 0
basic.facingYawUpper mustEqual 180.0f
basic.lfs mustEqual false
basic.grenade_state mustEqual GrenadeState.None
basic.is_cloaking mustEqual false
basic.charging_pose mustEqual false
basic.on_zipline mustEqual false
basic.ribbons.upper mustEqual MeritCommendation.Jacking2
basic.ribbons.middle mustEqual MeritCommendation.ScavengerVS1
basic.ribbons.lower mustEqual MeritCommendation.AMSSupport4
basic.ribbons.tos mustEqual MeritCommendation.SixYearVS
char.health mustEqual 0
char.armor mustEqual 0
char.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
char.command_rank mustEqual 2
char.implant_effects.isDefined mustEqual false
char.cosmetics.isDefined mustEqual true
char.cosmetics.get.no_helmet mustEqual true
char.cosmetics.get.beret mustEqual true
char.cosmetics.get.sunglasses mustEqual true
char.cosmetics.get.earpiece mustEqual true
char.cosmetics.get.brimmed_cap mustEqual false
hand mustEqual DrawnSlot.Pistol1
case _ =>
ko
}
case _ =>
ko
}
}
"encode" in {
val obj = CharacterData(
CharacterAppearanceData(
PlacementData(
Vector3(3674.8438f, 2726.789f, 91.15625f),
Vector3(0f, 0f, 64.6875f),
Some(Vector3(1.4375f, -0.4375f, 0f))
),
BasicCharacterData(
"ScrawnyRonnie",
PlanetSideEmpire.TR,
CharacterGender.Male,
5,
5
),
3,
false,
false,
ExoSuitType.Reinforced,
"Black Beret Armoured Corps",
23,
false,
340.3125f, 0f,
false,
GrenadeState.None,
false, false, false,
RibbonBars(
MeritCommendation.MarkovVeteran,
MeritCommendation.HeavyInfantry4,
MeritCommendation.TankBuster7,
MeritCommendation.SixYearTR
)
val pos : PlacementData = PlacementData(
Vector3(3674.8438f, 2726.789f, 91.15625f),
Vector3(0f, 0f, 64.6875f),
Some(Vector3(1.4375f, -0.4375f, 0f))
)
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
BasicCharacterData(
"ScrawnyRonnie",
PlanetSideEmpire.TR,
CharacterGender.Male,
5,
5
),
3,
false,
false,
ExoSuitType.Reinforced,
"Black Beret Armoured Corps",
23,
false,
340.3125f, 0f,
false,
GrenadeState.None,
false, false, false,
RibbonBars(
MeritCommendation.MarkovVeteran,
MeritCommendation.HeavyInfantry4,
MeritCommendation.TankBuster7,
MeritCommendation.SixYearTR
)
)
val char : (Boolean)=>CharacterData = CharacterData(
255, 253,
UniformStyle.ThirdUpgrade,
5,
Some(ImplantEffects.NoEffects),
Some(Cosmetics(true, true, true, true, false)),
InventoryData(
InventoryItemData(ObjectClass.plasma_grenade, PlanetSideGUID(3662), 0, WeaponData(0, 0, ObjectClass.plasma_grenade_ammo, PlanetSideGUID(3751), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.bank, PlanetSideGUID(3908), 1, WeaponData(0, 0, 1, ObjectClass.armor_canister, PlanetSideGUID(4143), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.mini_chaingun, PlanetSideGUID(4164), 2, WeaponData(0, 0, ObjectClass.bullet_9mm, PlanetSideGUID(3728), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.phoenix, PlanetSideGUID(3603), 3, WeaponData(0, 0, ObjectClass.phoenix_missile, PlanetSideGUID(3056), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.chainblade, PlanetSideGUID(4088), 4, WeaponData(0, 0, 1, ObjectClass.melee_ammo, PlanetSideGUID(3279), 0, AmmoBoxData())) ::
Nil
),
DrawnSlot.Rifle1
Some(Cosmetics(true, true, true, true, false))
)
val inv = InventoryData(
InventoryItemData(ObjectClass.plasma_grenade, PlanetSideGUID(3662), 0, WeaponData(0, 0, ObjectClass.plasma_grenade_ammo, PlanetSideGUID(3751), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.bank, PlanetSideGUID(3908), 1, WeaponData(0, 0, 1, ObjectClass.armor_canister, PlanetSideGUID(4143), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.mini_chaingun, PlanetSideGUID(4164), 2, WeaponData(0, 0, ObjectClass.bullet_9mm, PlanetSideGUID(3728), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.phoenix, PlanetSideGUID(3603), 3, WeaponData(0, 0, ObjectClass.phoenix_missile, PlanetSideGUID(3056), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.chainblade, PlanetSideGUID(4088), 4, WeaponData(0, 0, 1, ObjectClass.melee_ammo, PlanetSideGUID(3279), 0, AmmoBoxData())) ::
Nil
)
val obj = PlayerData.apply(pos, app, char, inv, DrawnSlot.Rifle1)
val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3902), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
val pkt_bitv = pkt.toBitVector
@ -230,42 +230,45 @@ class CharacterDataTest extends Specification {
}
"encode (backpack)" in {
val obj = CharacterData(
CharacterAppearanceData(
PlacementData(4629.8906f, 6316.4453f, 54.734375f, 0f, 0f, 126.5625f),
BasicCharacterData(
"Angello",
PlanetSideEmpire.VS,
CharacterGender.Male,
10,
2
),
0,
false,
false,
ExoSuitType.MAX,
"Original District",
23,
true, //backpack
0f, 180.0f,
false,
GrenadeState.None,
false, false, false,
RibbonBars(
MeritCommendation.Jacking2,
MeritCommendation.ScavengerVS1,
MeritCommendation.AMSSupport4,
MeritCommendation.SixYearVS
)
val pos = PlacementData(
Vector3(4629.8906f, 6316.4453f, 54.734375f),
Vector3(0, 0, 126.5625f)
)
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
BasicCharacterData(
"Angello",
PlanetSideEmpire.VS,
CharacterGender.Male,
10,
2
),
0,
false,
false,
ExoSuitType.MAX,
"Original District",
23,
true, //backpack
0f, 180.0f,
false,
GrenadeState.None,
false, false, false,
RibbonBars(
MeritCommendation.Jacking2,
MeritCommendation.ScavengerVS1,
MeritCommendation.AMSSupport4,
MeritCommendation.SixYearVS
)
)
val char : (Boolean)=>CharacterData = CharacterData(
0, 0,
UniformStyle.ThirdUpgrade,
2,
None,
Some(Cosmetics(true, true, true, true, false)),
None,
DrawnSlot.Pistol1
Some(Cosmetics(true, true, true, true, false))
)
val obj = PlayerData.apply(pos, app, char, DrawnSlot.Pistol1)
val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3380), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
val pkt_bitv = pkt.toBitVector

View file

@ -20,130 +20,132 @@ class DetailedCharacterDataTest extends Specification {
cls mustEqual ObjectClass.avatar
guid mustEqual PlanetSideGUID(75)
parent.isDefined mustEqual false
data.isDefined mustEqual true
val char = data.get.asInstanceOf[DetailedCharacterData]
char.appearance.pos.coord.x mustEqual 3674.8438f
char.appearance.pos.coord.y mustEqual 2726.789f
char.appearance.pos.coord.z mustEqual 91.15625f
char.appearance.pos.orient.x mustEqual 0
char.appearance.pos.orient.y mustEqual 0f
char.appearance.pos.orient.z mustEqual 36.5625f
char.appearance.basic_appearance.name mustEqual "IlllIIIlllIlIllIlllIllI"
char.appearance.basic_appearance.faction mustEqual PlanetSideEmpire.VS
char.appearance.basic_appearance.sex mustEqual CharacterGender.Female
char.appearance.basic_appearance.head mustEqual 41
char.appearance.basic_appearance.voice mustEqual 1 //female 1
char.appearance.voice2 mustEqual 3
char.appearance.black_ops mustEqual false
char.appearance.jammered mustEqual false
char.appearance.exosuit mustEqual ExoSuitType.Standard
char.appearance.outfit_name mustEqual ""
char.appearance.outfit_logo mustEqual 0
char.appearance.backpack mustEqual false
char.appearance.facingPitch mustEqual 2.8125f
char.appearance.facingYawUpper mustEqual 210.9375f
char.appearance.lfs mustEqual true
char.appearance.grenade_state mustEqual GrenadeState.None
char.appearance.is_cloaking mustEqual false
char.appearance.charging_pose mustEqual false
char.appearance.on_zipline mustEqual false
char.appearance.ribbons.upper mustEqual MeritCommendation.None
char.appearance.ribbons.middle mustEqual MeritCommendation.None
char.appearance.ribbons.lower mustEqual MeritCommendation.None
char.appearance.ribbons.tos mustEqual MeritCommendation.None
char.bep mustEqual 0
char.cep mustEqual 0
char.healthMax mustEqual 100
char.health mustEqual 100
char.armor mustEqual 50 //standard exosuit value
char.unk1 mustEqual 1
char.unk2 mustEqual 7
char.unk3 mustEqual 7
char.staminaMax mustEqual 100
char.stamina mustEqual 100
char.certs.length mustEqual 7
char.certs.head mustEqual CertificationType.StandardAssault
char.certs(1) mustEqual CertificationType.MediumAssault
char.certs(2) mustEqual CertificationType.ATV
char.certs(3) mustEqual CertificationType.Harasser
char.certs(4) mustEqual CertificationType.StandardExoSuit
char.certs(5) mustEqual CertificationType.AgileExoSuit
char.certs(6) mustEqual CertificationType.ReinforcedExoSuit
char.implants.length 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.cosmetics.isDefined mustEqual false
char.inventory.isDefined mustEqual true
val inventory = char.inventory.get.contents
inventory.size mustEqual 10
//0
inventory.head.objectClass mustEqual ObjectClass.beamer
inventory.head.guid mustEqual PlanetSideGUID(76)
inventory.head.parentSlot mustEqual 0
var wep = inventory.head.obj.asInstanceOf[DetailedWeaponData]
wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell
wep.ammo.head.guid mustEqual PlanetSideGUID(77)
wep.ammo.head.parentSlot mustEqual 0
wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 16
//1
inventory(1).objectClass mustEqual ObjectClass.suppressor
inventory(1).guid mustEqual PlanetSideGUID(78)
inventory(1).parentSlot mustEqual 2
wep = inventory(1).obj.asInstanceOf[DetailedWeaponData]
wep.ammo.head.objectClass mustEqual ObjectClass.bullet_9mm
wep.ammo.head.guid mustEqual PlanetSideGUID(79)
wep.ammo.head.parentSlot mustEqual 0
wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 25
//2
inventory(2).objectClass mustEqual ObjectClass.forceblade
inventory(2).guid mustEqual PlanetSideGUID(80)
inventory(2).parentSlot mustEqual 4
wep = inventory(2).obj.asInstanceOf[DetailedWeaponData]
wep.ammo.head.objectClass mustEqual ObjectClass.melee_ammo
wep.ammo.head.guid mustEqual PlanetSideGUID(81)
wep.ammo.head.parentSlot mustEqual 0
wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1
//3
inventory(3).objectClass mustEqual ObjectClass.locker_container
inventory(3).guid mustEqual PlanetSideGUID(82)
inventory(3).parentSlot mustEqual 5
inventory(3).obj.isInstanceOf[DetailedLockerContainerData] mustEqual true
inventory(3).obj.asInstanceOf[DetailedLockerContainerData].inventory.isDefined mustEqual false
//4
inventory(4).objectClass mustEqual ObjectClass.bullet_9mm
inventory(4).guid mustEqual PlanetSideGUID(83)
inventory(4).parentSlot mustEqual 6
inventory(4).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
//5
inventory(5).objectClass mustEqual ObjectClass.bullet_9mm
inventory(5).guid mustEqual PlanetSideGUID(84)
inventory(5).parentSlot mustEqual 9
inventory(5).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
//6
inventory(6).objectClass mustEqual ObjectClass.bullet_9mm
inventory(6).guid mustEqual PlanetSideGUID(85)
inventory(6).parentSlot mustEqual 12
inventory(6).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
//7
inventory(7).objectClass mustEqual ObjectClass.bullet_9mm_AP
inventory(7).guid mustEqual PlanetSideGUID(86)
inventory(7).parentSlot mustEqual 33
inventory(7).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
//8
inventory(8).objectClass mustEqual ObjectClass.energy_cell
inventory(8).guid mustEqual PlanetSideGUID(87)
inventory(8).parentSlot mustEqual 36
inventory(8).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
//9
inventory(9).objectClass mustEqual ObjectClass.remote_electronics_kit
inventory(9).guid mustEqual PlanetSideGUID(88)
inventory(9).parentSlot mustEqual 39
//the rek has data but none worth testing here
char.drawn_slot mustEqual DrawnSlot.Pistol1
data match {
case Some(DetailedPlayerData(Some(pos), basic, char, inv, hand)) =>
pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f)
pos.orient mustEqual Vector3(0, 0, 36.5625f)
pos.vel.isDefined mustEqual false
basic.app.name mustEqual "IlllIIIlllIlIllIlllIllI"
basic.app.faction mustEqual PlanetSideEmpire.VS
basic.app.sex mustEqual CharacterGender.Female
basic.app.head mustEqual 41
basic.app.voice mustEqual 1 //female 1
basic.voice2 mustEqual 3
basic.black_ops mustEqual false
basic.jammered mustEqual false
basic.exosuit mustEqual ExoSuitType.Standard
basic.outfit_name mustEqual ""
basic.outfit_logo mustEqual 0
basic.backpack mustEqual false
basic.facingPitch mustEqual 2.8125f
basic.facingYawUpper mustEqual 210.9375f
basic.lfs mustEqual true
basic.grenade_state mustEqual GrenadeState.None
basic.is_cloaking mustEqual false
basic.charging_pose mustEqual false
basic.on_zipline mustEqual false
basic.ribbons.upper mustEqual MeritCommendation.None
basic.ribbons.middle mustEqual MeritCommendation.None
basic.ribbons.lower mustEqual MeritCommendation.None
basic.ribbons.tos mustEqual MeritCommendation.None
char.bep mustEqual 0
char.cep mustEqual 0
char.healthMax mustEqual 100
char.health mustEqual 100
char.armor mustEqual 50 //standard exosuit value
char.unk1 mustEqual 1
char.unk2 mustEqual 7
char.unk3 mustEqual 7
char.staminaMax mustEqual 100
char.stamina mustEqual 100
char.certs.length mustEqual 7
char.certs.head mustEqual CertificationType.StandardAssault
char.certs(1) mustEqual CertificationType.MediumAssault
char.certs(2) mustEqual CertificationType.ATV
char.certs(3) mustEqual CertificationType.Harasser
char.certs(4) mustEqual CertificationType.StandardExoSuit
char.certs(5) mustEqual CertificationType.AgileExoSuit
char.certs(6) mustEqual CertificationType.ReinforcedExoSuit
char.implants.length 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.cosmetics.isDefined mustEqual false
inv.isDefined mustEqual true
val inventory = inv.get.contents
inventory.size mustEqual 10
//0
inventory.head.objectClass mustEqual ObjectClass.beamer
inventory.head.guid mustEqual PlanetSideGUID(76)
inventory.head.parentSlot mustEqual 0
var wep = inventory.head.obj.asInstanceOf[DetailedWeaponData]
wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell
wep.ammo.head.guid mustEqual PlanetSideGUID(77)
wep.ammo.head.parentSlot mustEqual 0
wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 16
//1
inventory(1).objectClass mustEqual ObjectClass.suppressor
inventory(1).guid mustEqual PlanetSideGUID(78)
inventory(1).parentSlot mustEqual 2
wep = inventory(1).obj.asInstanceOf[DetailedWeaponData]
wep.ammo.head.objectClass mustEqual ObjectClass.bullet_9mm
wep.ammo.head.guid mustEqual PlanetSideGUID(79)
wep.ammo.head.parentSlot mustEqual 0
wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 25
//2
inventory(2).objectClass mustEqual ObjectClass.forceblade
inventory(2).guid mustEqual PlanetSideGUID(80)
inventory(2).parentSlot mustEqual 4
wep = inventory(2).obj.asInstanceOf[DetailedWeaponData]
wep.ammo.head.objectClass mustEqual ObjectClass.melee_ammo
wep.ammo.head.guid mustEqual PlanetSideGUID(81)
wep.ammo.head.parentSlot mustEqual 0
wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1
//3
inventory(3).objectClass mustEqual ObjectClass.locker_container
inventory(3).guid mustEqual PlanetSideGUID(82)
inventory(3).parentSlot mustEqual 5
inventory(3).obj.isInstanceOf[DetailedLockerContainerData] mustEqual true
inventory(3).obj.asInstanceOf[DetailedLockerContainerData].inventory.isDefined mustEqual false
//4
inventory(4).objectClass mustEqual ObjectClass.bullet_9mm
inventory(4).guid mustEqual PlanetSideGUID(83)
inventory(4).parentSlot mustEqual 6
inventory(4).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
//5
inventory(5).objectClass mustEqual ObjectClass.bullet_9mm
inventory(5).guid mustEqual PlanetSideGUID(84)
inventory(5).parentSlot mustEqual 9
inventory(5).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
//6
inventory(6).objectClass mustEqual ObjectClass.bullet_9mm
inventory(6).guid mustEqual PlanetSideGUID(85)
inventory(6).parentSlot mustEqual 12
inventory(6).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
//7
inventory(7).objectClass mustEqual ObjectClass.bullet_9mm_AP
inventory(7).guid mustEqual PlanetSideGUID(86)
inventory(7).parentSlot mustEqual 33
inventory(7).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
//8
inventory(8).objectClass mustEqual ObjectClass.energy_cell
inventory(8).guid mustEqual PlanetSideGUID(87)
inventory(8).parentSlot mustEqual 36
inventory(8).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50
//9
inventory(9).objectClass mustEqual ObjectClass.remote_electronics_kit
inventory(9).guid mustEqual PlanetSideGUID(88)
inventory(9).parentSlot mustEqual 39
//the rek has data but none worth testing here
hand mustEqual DrawnSlot.Pistol1
case _ =>
ko
}
case _ =>
ko
}
@ -155,76 +157,80 @@ class DetailedCharacterDataTest extends Specification {
//this test is mainly for an alternate bitstream parsing order
//the object produced is massive and most of it is already covered in other tests
//only certain details towards the end of the stream will be checked
data.isDefined mustEqual true
val char = data.get.asInstanceOf[DetailedCharacterData]
DetailedCharacterData.isBR24(char.bep) mustEqual true
char.certs.size mustEqual 15
char.certs.head mustEqual CertificationType.StandardAssault
char.certs(14) mustEqual CertificationType.CombatEngineering
char.implants.size mustEqual 3
char.implants.head.implant mustEqual ImplantType.AudioAmplifier
char.implants.head.activation mustEqual None
char.implants(1).implant mustEqual ImplantType.Targeting
char.implants(1).activation mustEqual None
char.implants(2).implant mustEqual ImplantType.Surge
char.implants(2).activation mustEqual None
char.firstTimeEvents.size mustEqual 298
char.firstTimeEvents.head mustEqual "xpe_overhead_map"
char.firstTimeEvents(297) mustEqual "map10"
char.tutorials.size mustEqual 3
char.tutorials.head mustEqual "training_start_nc"
char.tutorials(1) mustEqual "training_ui"
char.tutorials(2) mustEqual "training_map"
char.cosmetics.isDefined mustEqual true
char.cosmetics.get.no_helmet mustEqual true
char.cosmetics.get.beret mustEqual true
char.cosmetics.get.earpiece mustEqual true
char.cosmetics.get.sunglasses mustEqual true
char.cosmetics.get.brimmed_cap mustEqual false
//inventory
char.inventory.isDefined mustEqual true
char.inventory.get.contents.size mustEqual 12
//0
char.inventory.get.contents.head.objectClass mustEqual 531
char.inventory.get.contents.head.guid mustEqual PlanetSideGUID(4202)
char.inventory.get.contents.head.parentSlot mustEqual 0
val wep1 = char.inventory.get.contents.head.obj.asInstanceOf[DetailedWeaponData]
wep1.unk1 mustEqual 2
wep1.unk2 mustEqual 8
wep1.ammo.head.objectClass mustEqual 389
wep1.ammo.head.guid mustEqual PlanetSideGUID(3942)
wep1.ammo.head.parentSlot mustEqual 0
wep1.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].unk mustEqual 8
wep1.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 100
//4
char.inventory.get.contents(4).objectClass mustEqual 456
char.inventory.get.contents(4).guid mustEqual PlanetSideGUID(5374)
char.inventory.get.contents(4).parentSlot mustEqual 5
char.inventory.get.contents(4).obj.asInstanceOf[DetailedLockerContainerData].inventory.get.contents.size mustEqual 61
//11
char.inventory.get.contents(11).objectClass mustEqual 673
char.inventory.get.contents(11).guid mustEqual PlanetSideGUID(3661)
char.inventory.get.contents(11).parentSlot mustEqual 60
val wep2 = char.inventory.get.contents(11).obj.asInstanceOf[DetailedWeaponData]
wep2.unk1 mustEqual 2
wep2.unk2 mustEqual 8
wep2.ammo.head.objectClass mustEqual 674
wep2.ammo.head.guid mustEqual PlanetSideGUID(8542)
wep2.ammo.head.parentSlot mustEqual 0
wep2.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].unk mustEqual 8
wep2.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 3
char.drawn_slot mustEqual DrawnSlot.None
data match {
case Some(DetailedPlayerData(Some(_), _, char, inv, hand)) =>
DetailedCharacterData.isBR24(char.bep) mustEqual true
char.certs.size mustEqual 15
char.certs.head mustEqual CertificationType.StandardAssault
char.certs(14) mustEqual CertificationType.CombatEngineering
char.implants.size mustEqual 3
char.implants.head.implant mustEqual ImplantType.AudioAmplifier
char.implants.head.activation mustEqual None
char.implants(1).implant mustEqual ImplantType.Targeting
char.implants(1).activation mustEqual None
char.implants(2).implant mustEqual ImplantType.Surge
char.implants(2).activation mustEqual None
char.firstTimeEvents.size mustEqual 298
char.firstTimeEvents.head mustEqual "xpe_overhead_map"
char.firstTimeEvents(297) mustEqual "map10"
char.tutorials.size mustEqual 3
char.tutorials.head mustEqual "training_start_nc"
char.tutorials(1) mustEqual "training_ui"
char.tutorials(2) mustEqual "training_map"
char.cosmetics.isDefined mustEqual true
char.cosmetics.get.no_helmet mustEqual true
char.cosmetics.get.beret mustEqual true
char.cosmetics.get.earpiece mustEqual true
char.cosmetics.get.sunglasses mustEqual true
char.cosmetics.get.brimmed_cap mustEqual false
//inventory
inv.isDefined mustEqual true
inv.get.contents.size mustEqual 12
//0
inv.get.contents.head.objectClass mustEqual 531
inv.get.contents.head.guid mustEqual PlanetSideGUID(4202)
inv.get.contents.head.parentSlot mustEqual 0
val wep1 = inv.get.contents.head.obj.asInstanceOf[DetailedWeaponData]
wep1.unk1 mustEqual 2
wep1.unk2 mustEqual 8
wep1.ammo.head.objectClass mustEqual 389
wep1.ammo.head.guid mustEqual PlanetSideGUID(3942)
wep1.ammo.head.parentSlot mustEqual 0
wep1.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].unk mustEqual 8
wep1.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 100
//4
inv.get.contents(4).objectClass mustEqual 456
inv.get.contents(4).guid mustEqual PlanetSideGUID(5374)
inv.get.contents(4).parentSlot mustEqual 5
inv.get.contents(4).obj.asInstanceOf[DetailedLockerContainerData].inventory.get.contents.size mustEqual 61
//11
inv.get.contents(11).objectClass mustEqual 673
inv.get.contents(11).guid mustEqual PlanetSideGUID(3661)
inv.get.contents(11).parentSlot mustEqual 60
val wep2 = inv.get.contents(11).obj.asInstanceOf[DetailedWeaponData]
wep2.unk1 mustEqual 2
wep2.unk2 mustEqual 8
wep2.ammo.head.objectClass mustEqual 674
wep2.ammo.head.guid mustEqual PlanetSideGUID(8542)
wep2.ammo.head.parentSlot mustEqual 0
wep2.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].unk mustEqual 8
wep2.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 3
hand mustEqual DrawnSlot.None
case _ =>
ko
}
case _ =>
ko
}
}
"encode (character)" in {
val app = CharacterAppearanceData(
PlacementData(
Vector3(3674.8438f, 2726.789f, 91.15625f),
Vector3(0f, 0f, 36.5625f)
),
val pos : PlacementData = PlacementData(
3674.8438f, 2726.789f, 91.15625f,
0, 0, 36.5625f
)
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
BasicCharacterData(
"IlllIIIlllIlIllIlllIllI",
PlanetSideEmpire.VS,
@ -247,19 +253,7 @@ class DetailedCharacterDataTest extends Specification {
false,
RibbonBars()
)
val inv = InventoryItemData(ObjectClass.beamer, PlanetSideGUID(76), 0, DetailedWeaponData(4, 8, ObjectClass.energy_cell, PlanetSideGUID(77), 0, DetailedAmmoBoxData(8, 16))) ::
InventoryItemData(ObjectClass.suppressor, PlanetSideGUID(78), 2, DetailedWeaponData(4, 8, ObjectClass.bullet_9mm, PlanetSideGUID(79), 0, DetailedAmmoBoxData(8, 25))) ::
InventoryItemData(ObjectClass.forceblade, PlanetSideGUID(80), 4, DetailedWeaponData(4, 8, ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1))) ::
InventoryItemData(ObjectClass.locker_container, PlanetSideGUID(82), 5, DetailedLockerContainerData(8)) ::
InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(83), 6, DetailedAmmoBoxData(8, 50)) ::
InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(84), 9, DetailedAmmoBoxData(8, 50)) ::
InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(85), 12, DetailedAmmoBoxData(8, 50)) ::
InventoryItemData(ObjectClass.bullet_9mm_AP, PlanetSideGUID(86), 33, DetailedAmmoBoxData(8, 50)) ::
InventoryItemData(ObjectClass.energy_cell, PlanetSideGUID(87), 36, DetailedAmmoBoxData(8, 50)) ::
InventoryItemData(ObjectClass.remote_electronics_kit, PlanetSideGUID(88), 39, DetailedREKData(8)) ::
Nil
val obj = DetailedCharacterData(
app,
val char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData(
0,
0,
100, 100,
@ -278,13 +272,25 @@ class DetailedCharacterDataTest extends Specification {
List(),
"xpe_sanctuary_help" :: "xpe_th_firemodes" :: "used_beamer" :: "map13" :: Nil,
List.empty,
None,
Some(InventoryData(inv)),
DrawnSlot.Pistol1
None
)
val inv = InventoryData(
InventoryItemData(ObjectClass.beamer, PlanetSideGUID(76), 0, DetailedWeaponData(4, 8, ObjectClass.energy_cell, PlanetSideGUID(77), 0, DetailedAmmoBoxData(8, 16))) ::
InventoryItemData(ObjectClass.suppressor, PlanetSideGUID(78), 2, DetailedWeaponData(4, 8, ObjectClass.bullet_9mm, PlanetSideGUID(79), 0, DetailedAmmoBoxData(8, 25))) ::
InventoryItemData(ObjectClass.forceblade, PlanetSideGUID(80), 4, DetailedWeaponData(4, 8, ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1))) ::
InventoryItemData(ObjectClass.locker_container, PlanetSideGUID(82), 5, DetailedLockerContainerData(8)) ::
InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(83), 6, DetailedAmmoBoxData(8, 50)) ::
InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(84), 9, DetailedAmmoBoxData(8, 50)) ::
InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(85), 12, DetailedAmmoBoxData(8, 50)) ::
InventoryItemData(ObjectClass.bullet_9mm_AP, PlanetSideGUID(86), 33, DetailedAmmoBoxData(8, 50)) ::
InventoryItemData(ObjectClass.energy_cell, PlanetSideGUID(87), 36, DetailedAmmoBoxData(8, 50)) ::
InventoryItemData(ObjectClass.remote_electronics_kit, PlanetSideGUID(88), 39, DetailedREKData(8)) ::
Nil
)
val obj = DetailedPlayerData.apply(pos, app, char, inv, DrawnSlot.Pistol1)
val msg = ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
val pkt_bitv = pkt.toBitVector
val ori_bitv = string_testchar.toBitVector
pkt_bitv.take(153) mustEqual ori_bitv.take(153) //skip 1
@ -295,26 +301,31 @@ class DetailedCharacterDataTest extends Specification {
}
"encode (character, br32)" in {
val obj = DetailedCharacterData(
CharacterAppearanceData(
PlacementData(
Vector3(5500.0f, 3800.0f, 71.484375f),
Vector3(0.0f, 0.0f, 90.0f),
None
),
BasicCharacterData("KiCkJr", PlanetSideEmpire.NC, CharacterGender.Male, 24, 4),
3,
false, false,
ExoSuitType.Agile,
"",
14,
false,
354.375f, 354.375f,
false,
GrenadeState.None,
false, false, false,
RibbonBars(MeritCommendation.Loser4, MeritCommendation.EventNCElite, MeritCommendation.HeavyAssault6, MeritCommendation.SixYearNC)
),
val pos : PlacementData = PlacementData(
Vector3(5500.0f, 3800.0f, 71.484375f),
Vector3(0, 0, 90.0f),
None
)
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
BasicCharacterData("KiCkJr", PlanetSideEmpire.NC, CharacterGender.Male, 24, 4),
3,
false, false,
ExoSuitType.Agile,
"",
14,
false,
354.375f, 354.375f,
false,
GrenadeState.None,
false, false, false,
RibbonBars(
MeritCommendation.Loser4,
MeritCommendation.EventNCElite,
MeritCommendation.HeavyAssault6,
MeritCommendation.SixYearNC
)
)
val char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData(
6366766,
694787,
100, 100, 100,
@ -647,198 +658,196 @@ class DetailedCharacterDataTest extends Specification {
"training_ui",
"training_map"
),
Some(Cosmetics(true, true, true, true, false)),
Some(
InventoryData(
List(
InternalSlot(531, PlanetSideGUID(4202), 0,
DetailedWeaponData(2, 8, 0, List(InternalSlot(389, PlanetSideGUID(3942), 0,DetailedAmmoBoxData(8, 100))))
),
InternalSlot(132, PlanetSideGUID(6924), 1,
DetailedWeaponData(2, 8, 0, List(InternalSlot(111, PlanetSideGUID(9157), 0, DetailedAmmoBoxData(8, 100))))
),
InternalSlot(714, PlanetSideGUID(8498), 2,
DetailedWeaponData(2, 8, 0, List(InternalSlot(755, PlanetSideGUID(5356), 0, DetailedAmmoBoxData(8, 16))))
),
InternalSlot(468, PlanetSideGUID(7198), 4,
DetailedWeaponData(2, 8, 0, List(InternalSlot(540, PlanetSideGUID(5009), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(456, PlanetSideGUID(5374), 5,
DetailedLockerContainerData(8, Some(InventoryData(List(
InternalSlot(429, PlanetSideGUID(3021), 0,
DetailedWeaponData(6, 8, 0, List(InternalSlot(272, PlanetSideGUID(8729), 0, DetailedAmmoBoxData(8, 0))))
),
InternalSlot(838, PlanetSideGUID(8467), 9,
DetailedWeaponData(6, 8, 0, List(InternalSlot(839, PlanetSideGUID(8603), 0, DetailedAmmoBoxData(8, 5))))
),
InternalSlot(272, PlanetSideGUID(3266), 18, DetailedAmmoBoxData(8, 27)),
InternalSlot(577, PlanetSideGUID(2934), 22,
DetailedWeaponData(6, 8, 0, List(InternalSlot(111, PlanetSideGUID(4682), 0, DetailedAmmoBoxData(8, 100))))
),
InternalSlot(839, PlanetSideGUID(3271), 90, DetailedAmmoBoxData(8, 15)),
InternalSlot(839, PlanetSideGUID(7174), 94, DetailedAmmoBoxData(8, 6)),
InternalSlot(429, PlanetSideGUID(6084), 98,
DetailedWeaponData(6, 8, 0, List(InternalSlot(272, PlanetSideGUID(5928), 0, DetailedAmmoBoxData(8, 35))))
),
InternalSlot(462, PlanetSideGUID(5000), 108,
DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(6277), 0, DetailedAmmoBoxData(8, 150))))
),
InternalSlot(429, PlanetSideGUID(4341), 189,
DetailedWeaponData(6, 8, 0, List(InternalSlot(272, PlanetSideGUID(7043), 0, DetailedAmmoBoxData(8, 35))))
),
InternalSlot(556, PlanetSideGUID(4168), 198,
DetailedWeaponData(6, 8, 0, List(InternalSlot(28, PlanetSideGUID(8937), 0, DetailedAmmoBoxData(8, 100))))
),
InternalSlot(272, PlanetSideGUID(3173), 207, DetailedAmmoBoxData(8, 50)),
InternalSlot(462, PlanetSideGUID(3221), 210,
DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(4031), 0, DetailedAmmoBoxData(8, 150))))
),
InternalSlot(556, PlanetSideGUID(6853), 280,
DetailedWeaponData(6, 8, 0, List(InternalSlot(29, PlanetSideGUID(8524), 0, DetailedAmmoBoxData(8, 67))))
),
InternalSlot(556, PlanetSideGUID(4569), 290,
DetailedWeaponData(6, 8, 0, List(InternalSlot(28, PlanetSideGUID(5584), 0, DetailedAmmoBoxData(8, 100))))
),
InternalSlot(462, PlanetSideGUID(9294), 300,
DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(3118), 0, DetailedAmmoBoxData(8, 150))))
),
InternalSlot(272, PlanetSideGUID(4759), 387, DetailedAmmoBoxData(8, 50)),
InternalSlot(462, PlanetSideGUID(7377), 390,
DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(8155), 0, DetailedAmmoBoxData(8, 150))))
),
InternalSlot(843, PlanetSideGUID(6709), 480, DetailedAmmoBoxData(8, 1)),
InternalSlot(843, PlanetSideGUID(5276), 484, DetailedAmmoBoxData(8, 1)),
InternalSlot(843, PlanetSideGUID(7769), 488, DetailedAmmoBoxData(8, 1)),
InternalSlot(844, PlanetSideGUID(5334), 492, DetailedAmmoBoxData(8, 1)),
InternalSlot(844, PlanetSideGUID(6219), 496, DetailedAmmoBoxData(8, 1)),
InternalSlot(842, PlanetSideGUID(7279), 500, DetailedAmmoBoxData(8, 1)),
InternalSlot(842, PlanetSideGUID(5415), 504, DetailedAmmoBoxData(8, 1)),
InternalSlot(175, PlanetSideGUID(5741), 540,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5183), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(6208), 541,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5029), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(8589), 542,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(9217), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(8901), 543,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7633), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(8419), 544,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6546), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(4715), 545,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(8453), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(3577), 546,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(9202), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(6003), 547,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(3260), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(9140), 548,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(3815),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(4913), 549,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(7222),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(6954), 550,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(2953),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(6405), 551,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(4676),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(8915), 552,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(4018),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(4993), 553,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(6775),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(5053), 554,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(6418),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(9244), 555,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(3327),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(468, PlanetSideGUID(6292), 556,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(6918),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(842, PlanetSideGUID(5357), 558, DetailedAmmoBoxData(8, 1)),
InternalSlot(844, PlanetSideGUID(4435), 562, DetailedAmmoBoxData(8, 1)),
InternalSlot(843, PlanetSideGUID(7242), 566, DetailedAmmoBoxData(8, 1)),
InternalSlot(175, PlanetSideGUID(7330), 570,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4786), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(468, PlanetSideGUID(7415), 571,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6536), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(3949), 572,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7526), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(3805), 573,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7358), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(4493), 574,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6852), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(5762), 575,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(3463), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(3315), 576,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7619), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(6263), 577,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5912), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(468, PlanetSideGUID(4028), 578,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(8021), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(2843), 579,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7250), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(9143), 580,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5195), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(468, PlanetSideGUID(5024), 581,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4287), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(468, PlanetSideGUID(6582), 582,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4915), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(468, PlanetSideGUID(6425), 583,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(8872), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(468, PlanetSideGUID(4431), 584,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4191), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(8339), 585,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7317), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(3277), 586,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6469), 0, DetailedAmmoBoxData(8, 1))))
)
))))
),
InternalSlot(213, PlanetSideGUID(6877), 6, DetailedCommandDetonaterData(4, 8)),
InternalSlot(755, PlanetSideGUID(6227), 9, DetailedAmmoBoxData(8, 16)),
InternalSlot(728, PlanetSideGUID(7181), 12, DetailedREKData(4, 16)),
InternalSlot(536, PlanetSideGUID(4077), 33, DetailedAmmoBoxData(8, 1)),
InternalSlot(680, PlanetSideGUID(4377), 37,
DetailedWeaponData(2, 8, 0, List(InternalSlot(681, PlanetSideGUID(8905), 0, DetailedAmmoBoxData(8, 3))))
),
InternalSlot(32, PlanetSideGUID(5523), 39, DetailedACEData(4)),
InternalSlot(673, PlanetSideGUID(3661), 60,
DetailedWeaponData(2, 8, 0, List(InternalSlot(674, PlanetSideGUID(8542), 0, DetailedAmmoBoxData(8, 3))))
)
)
)
),
DrawnSlot.None
Some(Cosmetics(true, true, true, true, false))
)
val inv = InventoryData(
List(
InternalSlot(531, PlanetSideGUID(4202), 0,
DetailedWeaponData(2, 8, 0, List(InternalSlot(389, PlanetSideGUID(3942), 0,DetailedAmmoBoxData(8, 100))))
),
InternalSlot(132, PlanetSideGUID(6924), 1,
DetailedWeaponData(2, 8, 0, List(InternalSlot(111, PlanetSideGUID(9157), 0, DetailedAmmoBoxData(8, 100))))
),
InternalSlot(714, PlanetSideGUID(8498), 2,
DetailedWeaponData(2, 8, 0, List(InternalSlot(755, PlanetSideGUID(5356), 0, DetailedAmmoBoxData(8, 16))))
),
InternalSlot(468, PlanetSideGUID(7198), 4,
DetailedWeaponData(2, 8, 0, List(InternalSlot(540, PlanetSideGUID(5009), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(456, PlanetSideGUID(5374), 5,
DetailedLockerContainerData(8, Some(InventoryData(List(
InternalSlot(429, PlanetSideGUID(3021), 0,
DetailedWeaponData(6, 8, 0, List(InternalSlot(272, PlanetSideGUID(8729), 0, DetailedAmmoBoxData(8, 0))))
),
InternalSlot(838, PlanetSideGUID(8467), 9,
DetailedWeaponData(6, 8, 0, List(InternalSlot(839, PlanetSideGUID(8603), 0, DetailedAmmoBoxData(8, 5))))
),
InternalSlot(272, PlanetSideGUID(3266), 18, DetailedAmmoBoxData(8, 27)),
InternalSlot(577, PlanetSideGUID(2934), 22,
DetailedWeaponData(6, 8, 0, List(InternalSlot(111, PlanetSideGUID(4682), 0, DetailedAmmoBoxData(8, 100))))
),
InternalSlot(839, PlanetSideGUID(3271), 90, DetailedAmmoBoxData(8, 15)),
InternalSlot(839, PlanetSideGUID(7174), 94, DetailedAmmoBoxData(8, 6)),
InternalSlot(429, PlanetSideGUID(6084), 98,
DetailedWeaponData(6, 8, 0, List(InternalSlot(272, PlanetSideGUID(5928), 0, DetailedAmmoBoxData(8, 35))))
),
InternalSlot(462, PlanetSideGUID(5000), 108,
DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(6277), 0, DetailedAmmoBoxData(8, 150))))
),
InternalSlot(429, PlanetSideGUID(4341), 189,
DetailedWeaponData(6, 8, 0, List(InternalSlot(272, PlanetSideGUID(7043), 0, DetailedAmmoBoxData(8, 35))))
),
InternalSlot(556, PlanetSideGUID(4168), 198,
DetailedWeaponData(6, 8, 0, List(InternalSlot(28, PlanetSideGUID(8937), 0, DetailedAmmoBoxData(8, 100))))
),
InternalSlot(272, PlanetSideGUID(3173), 207, DetailedAmmoBoxData(8, 50)),
InternalSlot(462, PlanetSideGUID(3221), 210,
DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(4031), 0, DetailedAmmoBoxData(8, 150))))
),
InternalSlot(556, PlanetSideGUID(6853), 280,
DetailedWeaponData(6, 8, 0, List(InternalSlot(29, PlanetSideGUID(8524), 0, DetailedAmmoBoxData(8, 67))))
),
InternalSlot(556, PlanetSideGUID(4569), 290,
DetailedWeaponData(6, 8, 0, List(InternalSlot(28, PlanetSideGUID(5584), 0, DetailedAmmoBoxData(8, 100))))
),
InternalSlot(462, PlanetSideGUID(9294), 300,
DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(3118), 0, DetailedAmmoBoxData(8, 150))))
),
InternalSlot(272, PlanetSideGUID(4759), 387, DetailedAmmoBoxData(8, 50)),
InternalSlot(462, PlanetSideGUID(7377), 390,
DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(8155), 0, DetailedAmmoBoxData(8, 150))))
),
InternalSlot(843, PlanetSideGUID(6709), 480, DetailedAmmoBoxData(8, 1)),
InternalSlot(843, PlanetSideGUID(5276), 484, DetailedAmmoBoxData(8, 1)),
InternalSlot(843, PlanetSideGUID(7769), 488, DetailedAmmoBoxData(8, 1)),
InternalSlot(844, PlanetSideGUID(5334), 492, DetailedAmmoBoxData(8, 1)),
InternalSlot(844, PlanetSideGUID(6219), 496, DetailedAmmoBoxData(8, 1)),
InternalSlot(842, PlanetSideGUID(7279), 500, DetailedAmmoBoxData(8, 1)),
InternalSlot(842, PlanetSideGUID(5415), 504, DetailedAmmoBoxData(8, 1)),
InternalSlot(175, PlanetSideGUID(5741), 540,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5183), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(6208), 541,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5029), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(8589), 542,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(9217), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(8901), 543,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7633), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(8419), 544,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6546), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(4715), 545,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(8453), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(3577), 546,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(9202), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(6003), 547,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(3260), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(9140), 548,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(3815),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(4913), 549,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(7222),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(6954), 550,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(2953),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(6405), 551,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(4676),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(8915), 552,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(4018),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(4993), 553,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(6775),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(5053), 554,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(6418),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(9244), 555,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(3327),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(468, PlanetSideGUID(6292), 556,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(6918),0,DetailedAmmoBoxData(8, 1))))
),
InternalSlot(842, PlanetSideGUID(5357), 558, DetailedAmmoBoxData(8, 1)),
InternalSlot(844, PlanetSideGUID(4435), 562, DetailedAmmoBoxData(8, 1)),
InternalSlot(843, PlanetSideGUID(7242), 566, DetailedAmmoBoxData(8, 1)),
InternalSlot(175, PlanetSideGUID(7330), 570,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4786), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(468, PlanetSideGUID(7415), 571,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6536), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(3949), 572,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7526), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(3805), 573,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7358), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(4493), 574,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6852), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(5762), 575,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(3463), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(3315), 576,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7619), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(324, PlanetSideGUID(6263), 577,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5912), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(468, PlanetSideGUID(4028), 578,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(8021), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(2843), 579,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7250), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(9143), 580,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5195), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(468, PlanetSideGUID(5024), 581,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4287), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(468, PlanetSideGUID(6582), 582,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4915), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(468, PlanetSideGUID(6425), 583,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(8872), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(468, PlanetSideGUID(4431), 584,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4191), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(8339), 585,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7317), 0, DetailedAmmoBoxData(8, 1))))
),
InternalSlot(175, PlanetSideGUID(3277), 586,
DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6469), 0, DetailedAmmoBoxData(8, 1))))
)
))))
),
InternalSlot(213, PlanetSideGUID(6877), 6, DetailedCommandDetonaterData(4, 8)),
InternalSlot(755, PlanetSideGUID(6227), 9, DetailedAmmoBoxData(8, 16)),
InternalSlot(728, PlanetSideGUID(7181), 12, DetailedREKData(4, 16)),
InternalSlot(536, PlanetSideGUID(4077), 33, DetailedAmmoBoxData(8, 1)),
InternalSlot(680, PlanetSideGUID(4377), 37,
DetailedWeaponData(2, 8, 0, List(InternalSlot(681, PlanetSideGUID(8905), 0, DetailedAmmoBoxData(8, 3))))
),
InternalSlot(32, PlanetSideGUID(5523), 39, DetailedACEData(4)),
InternalSlot(673, PlanetSideGUID(3661), 60,
DetailedWeaponData(2, 8, 0, List(InternalSlot(674, PlanetSideGUID(8542), 0, DetailedAmmoBoxData(8, 3))))
)
)
)
val obj = DetailedPlayerData(pos, app, char, inv, DrawnSlot.None)
val msg = ObjectCreateDetailedMessage(ObjectClass.avatar, PlanetSideGUID(75), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
val pkt_bitv = pkt.toBitVector
val ori_bitv = string_testchar_br32.toBitVector
pkt_bitv.take(153) mustEqual ori_bitv.take(153) //skip 1

View file

@ -1828,7 +1828,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
galaxy ! Zone.Lattice.RequestSpawnPoint(u5.toInt, player, u2.toInt)
case msg @ SetChatFilterMessage(send_channel, origin, whitelist) =>
log.info("SetChatFilters: " + msg)
//log.info("SetChatFilters: " + msg)
case msg @ ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents) =>
var makeReply : Boolean = true

View file

@ -452,45 +452,44 @@ class PacketCodingActorHTest extends ActorTest {
}
class PacketCodingActorITest extends ActorTest {
import net.psforever.packet.game.objectcreate._
val pos : PlacementData = PlacementData(Vector3.Zero, Vector3.Zero)
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1),
3,
false,
false,
ExoSuitType.Standard,
"",
0,
false,
2.8125f, 210.9375f,
true,
GrenadeState.None,
false,
false,
false,
RibbonBars()
)
var char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData(
0,
0,
100, 100,
50,
1, 7, 7,
100, 100,
List(CertificationType.StandardAssault, CertificationType.MediumAssault, CertificationType.ATV, CertificationType.Harasser, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit, CertificationType.ReinforcedExoSuit),
List(),
List(),
List.empty,
None
)
val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None)
val pkt = MultiPacketBundle(List(ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj)))
val string_hex = hex"000900001879060000bc84b000000000000000000002040000097049006c006c006c004900490049006c006c006c0049006c0049006c006c0049006c006c006c0049006c006c0049008452700000000000000000000000000000002000000fe6a703fffffffffffffffffffffffffffffffc00000000000000000000000000000000000000019001900064000001007ec800c80000000000000000000000000000000000000001c00042c54686c7000000000000000000000000000000000000000000000000000000000000000000000000200700"
"PacketCodingActor" should {
"bundle an r-originating packet into an l-facing SlottedMetaPacket byte stream data (SlottedMetaPacket)" in {
import net.psforever.packet.game.objectcreate._
val obj = DetailedCharacterData(
CharacterAppearanceData(
PlacementData(Vector3.Zero, Vector3.Zero),
BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1),
3,
false,
false,
ExoSuitType.Standard,
"",
0,
false,
2.8125f, 210.9375f,
true,
GrenadeState.None,
false,
false,
false,
RibbonBars()
),
0,
0,
100, 100,
50,
1, 7, 7,
100, 100,
List(CertificationType.StandardAssault,CertificationType.MediumAssault,CertificationType.ATV,CertificationType.Harasser,CertificationType.StandardExoSuit,CertificationType.AgileExoSuit,CertificationType.ReinforcedExoSuit),
List(),
List(),
List.empty,
None,
Some(InventoryData(Nil)),
DrawnSlot.None
)
val pkt = MultiPacketBundle(List(ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj)))
val string_hex = hex"000900001879060000bc84b000000000000000000002040000097049006c006c006c004900490049006c006c006c0049006c0049006c006c0049006c006c006c0049006c006c0049008452700000000000000000000000000000002000000fe6a703fffffffffffffffffffffffffffffffc00000000000000000000000000000000000000019001900064000001007ec800c80000000000000000000000000000000000000001c00042c54686c7000000000000000000000000000000000000000000000000000000000000000000000000200700"
val probe1 = TestProbe()
val probe2 = system.actorOf(Props(classOf[ActorTest.MDCTestProbe], probe1), "mdc-probe")
val pca : ActorRef = system.actorOf(Props[PacketCodingActor], "pca")
@ -547,25 +546,25 @@ class PacketCodingActorJTest extends ActorTest {
class PacketCodingActorKTest extends ActorTest {
import net.psforever.packet.game.objectcreate._
val obj = DetailedCharacterData(
CharacterAppearanceData(
PlacementData(Vector3.Zero, Vector3.Zero),
BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1),
3,
false,
false,
ExoSuitType.Standard,
"",
0,
false,
2.8125f, 210.9375f,
true,
GrenadeState.None,
false,
false,
false,
RibbonBars()
),
val pos : PlacementData = PlacementData(Vector3.Zero, Vector3.Zero)
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1),
3,
false,
false,
ExoSuitType.Standard,
"",
0,
false,
2.8125f, 210.9375f,
true,
GrenadeState.None,
false,
false,
false,
RibbonBars()
)
var char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData(
0,
0,
100, 100,
@ -576,10 +575,9 @@ class PacketCodingActorKTest extends ActorTest {
List(),
List("xpe_sanctuary_help", "xpe_th_firemodes", "used_beamer", "map13"),
List.empty,
None,
Some(InventoryData(Nil)),
DrawnSlot.None
None
)
val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None)
val list = List(
ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj),
ObjectDeleteMessage(PlanetSideGUID(1103), 2),