diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala
index c1193cc8a..b15d26947 100644
--- a/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala
@@ -64,58 +64,92 @@ object AvatarConverter {
* @return the resulting `CharacterAppearanceData`
*/
def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
- CharacterAppearanceData(
+ val alt_model_flag : Boolean = obj.isBackpack
+ val aa : Int=>CharacterAppearanceA = CharacterAppearanceA(
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, obj.Voice),
- voice2 = 0,
black_ops = false,
+ alt_model_flag,
+ false,
+ None,
jammered = false,
obj.ExoSuit,
+ None,
+ 0,
+ 0,
+ 0L,
+ 0,
+ 0,
+ 0,
+ 0
+ )
+ val ab : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
+ 0L,
outfit_name = "",
outfit_logo = 0,
+ false,
obj.isBackpack,
+ false,
+ false,
+ false,
facingPitch = obj.Orientation.y,
facingYawUpper = obj.FacingYawUpper,
lfs = true,
GrenadeState.None,
- is_cloaking = false,
+ obj.Cloaked,
+ false,
+ false,
charging_pose = false,
- on_zipline = false,
- RibbonBars()
+ false,
+ on_zipline = None
)
+ CharacterAppearanceData(aa, ab, RibbonBars())
}
def MakeCharacterData(obj : Player) : (Boolean,Boolean)=>CharacterData = {
val MaxArmor = obj.MaxArmor
CharacterData(
- 255 * obj.Health / obj.MaxHealth, //TODO not precise
+ 255 * obj.Health / obj.MaxHealth,
if(MaxArmor == 0) {
0
}
else {
255 * obj.Armor / MaxArmor
- }, //TODO not precise
+ },
DressBattleRank(obj),
+ 0,
DressCommandRank(obj),
- recursiveMakeImplantEffects(obj.Implants.iterator),
+ MakeImplantEffectList(obj.Implants),
MakeCosmetics(obj.BEP)
)
}
def MakeDetailedCharacterData(obj : Player) : (Option[Int])=>DetailedCharacterData = {
- DetailedCharacterData(
- obj.BEP,
+ val bep = obj.BEP
+ val ba : DetailedCharacterA = DetailedCharacterA(
+ bep,
obj.CEP,
- obj.MaxHealth,
- obj.Health,
+ 0L, 0L, 0L,
+ obj.MaxHealth, obj.Health,
+ false,
obj.Armor,
- obj.MaxStamina,
- obj.Stamina,
- obj.Certifications.toList.sortBy(_.id), //TODO is sorting necessary?
+ 0L,
+ obj.MaxStamina, obj.Stamina,
+ 0, 0, 0L,
+ List(0, 0, 0, 0, 0, 0),
+ obj.Certifications.toList.sortBy(_.id) //TODO is sorting necessary?
+ )
+ val bb : (Long, Option[Int])=>DetailedCharacterB = DetailedCharacterB(
+ None,
MakeImplantEntries(obj),
+ Nil, Nil,
firstTimeEvents = List.empty[String], //TODO fte list
tutorials = List.empty[String], //TODO tutorial list
- MakeCosmetics(obj.BEP)
+ 0L, 0L, 0L, 0L, 0L,
+ Some(DCDExtra2(0, 0)),
+ Nil, Nil, false,
+ MakeCosmetics(bep)
)
+ (pad_length : Option[Int]) => DetailedCharacterData(ba, bb(bep, pad_length))(pad_length)
}
def MakeInventoryData(obj : Player) : InventoryData = {
@@ -185,7 +219,7 @@ object AvatarConverter {
private def MakeImplantEntries(obj : Player) : List[ImplantEntry] = {
val numImplants : Int = DetailedCharacterData.numberOfImplantSlots(obj.BEP)
val implants = obj.Implants
- obj.Implants.map({ case(implant, initialization, active) =>
+ obj.Implants.map({ case(implant, initialization, _) =>
if(initialization == 0) {
ImplantEntry(implant, None)
}
@@ -196,34 +230,24 @@ object AvatarConverter {
}
/**
- * Find an active implant whose effect will be displayed on this player.
- * @param iter an `Iterator` of `ImplantSlot` objects
+ * Find and encode implants whose effect will be displayed on this player.
+ * @param implants a `Sequence` of `ImplantSlot` objects
* @return the effect of an active implant
*/
- @tailrec private def recursiveMakeImplantEffects(iter : Iterator[(ImplantType.Value, Long, Boolean)]) : Option[ImplantEffects.Value] = {
- if(!iter.hasNext) {
- None
- }
- else {
- val(implant, _, active) = iter.next
- if(active) {
+ private def MakeImplantEffectList(implants : Seq[(ImplantType.Value, Long, Boolean)]) : List[ImplantEffects.Value] = {
+ implants.collect {
+ case ((implant,_,true)) =>
implant match {
case ImplantType.AdvancedRegen =>
- Some(ImplantEffects.RegenEffects)
+ ImplantEffects.RegenEffects
case ImplantType.DarklightVision =>
- Some(ImplantEffects.DarklightEffects)
+ ImplantEffects.DarklightEffects
case ImplantType.PersonalShield =>
- Some(ImplantEffects.PersonalShieldEffects)
+ ImplantEffects.PersonalShieldEffects
case ImplantType.Surge =>
- Some(ImplantEffects.SurgeEffects)
- case _ =>
- recursiveMakeImplantEffects(iter)
+ ImplantEffects.SurgeEffects
}
- }
- else {
- recursiveMakeImplantEffects(iter)
- }
- }
+ }.toList
}
/**
diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/CharacterSelectConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/CharacterSelectConverter.scala
index 4a432fea8..8930da40a 100644
--- a/common/src/main/scala/net/psforever/objects/definition/converter/CharacterSelectConverter.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/converter/CharacterSelectConverter.scala
@@ -4,7 +4,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._
-import net.psforever.types.{CharacterVoice, GrenadeState, ImplantType}
+import net.psforever.types.{CertificationType, CharacterVoice, GrenadeState, ImplantType}
import scala.annotation.tailrec
import scala.util.{Failure, Success, Try}
@@ -22,20 +22,7 @@ class CharacterSelectConverter extends AvatarConverter {
DetailedPlayerData.apply(
PlacementData(0, 0, 0),
MakeAppearanceData(obj),
- DetailedCharacterData(
- obj.BEP,
- obj.CEP,
- healthMax = 1,
- health = 1,
- armor = 0,
- staminaMax = 1,
- stamina = 1,
- certs = Nil,
- MakeImplantEntries(obj), //necessary for correct stream length
- firstTimeEvents = Nil,
- tutorials = Nil,
- AvatarConverter.MakeCosmetics(obj.BEP)
- ),
+ MakeDetailedCharacterData(obj),
InventoryData(recursiveMakeHolsters(obj.Holsters().iterator)),
AvatarConverter.GetDrawnSlot(obj)
)
@@ -49,24 +36,73 @@ class CharacterSelectConverter extends AvatarConverter {
* @return the resulting `CharacterAppearanceData`
*/
private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
- CharacterAppearanceData(
+ val aa : Int=>CharacterAppearanceA = CharacterAppearanceA(
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, CharacterVoice.Mute),
- voice2 = 0,
black_ops = false,
+ false,
+ false,
+ None,
jammered = false,
obj.ExoSuit,
+ None,
+ 0,
+ 0,
+ 0L,
+ 0,
+ 0,
+ 0,
+ 0
+ )
+ val ab : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
+ 0L,
outfit_name = "",
outfit_logo = 0,
+ false,
backpack = false,
+ false,
+ false,
+ false,
facingPitch = 0,
facingYawUpper = 0,
- lfs = true,
+ lfs = false,
GrenadeState.None,
- is_cloaking = false,
+ obj.Cloaked,
+ false,
+ false,
charging_pose = false,
- on_zipline = false,
- RibbonBars()
+ false,
+ on_zipline = None
)
+ CharacterAppearanceData(aa, ab, RibbonBars())
+ }
+
+ private def MakeDetailedCharacterData(obj : Player) : (Option[Int]=>DetailedCharacterData) = {
+ val bep = obj.BEP
+ val ba : DetailedCharacterA = DetailedCharacterA(
+ bep,
+ obj.CEP,
+ 0L, 0L, 0L,
+ 1, 1,
+ false,
+ 0,
+ 0L,
+ 1, 1,
+ 0, 0, 0L,
+ List(0, 0, 0, 0, 0, 0),
+ certs = List.empty[CertificationType.Value]
+ )
+ val bb : (Long, Option[Int])=>DetailedCharacterB = DetailedCharacterB(
+ None,
+ MakeImplantEntries(obj), //necessary for correct stream length
+ Nil, Nil,
+ firstTimeEvents = List.empty[String],
+ tutorials = List.empty[String],
+ 0L, 0L, 0L, 0L, 0L,
+ Some(DCDExtra2(0, 0)),
+ Nil, Nil, false,
+ AvatarConverter.MakeCosmetics(bep)
+ )
+ (pad_length : Option[Int]) => DetailedCharacterData(ba, bb(bep, pad_length))(pad_length)
}
/**
diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala
index 762b48da4..7993d51e3 100644
--- a/common/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala
@@ -4,7 +4,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._
-import net.psforever.types.{CharacterGender, CharacterVoice, GrenadeState, Vector3}
+import net.psforever.types._
import scala.annotation.tailrec
import scala.util.{Failure, Success, Try}
@@ -18,20 +18,7 @@ class CorpseConverter extends AvatarConverter {
DetailedPlayerData.apply(
PlacementData(obj.Position, Vector3(0,0, obj.Orientation.z)),
MakeAppearanceData(obj),
- DetailedCharacterData(
- bep = 0,
- cep = 0,
- healthMax = 0,
- health = 0,
- armor = 0,
- staminaMax = 0,
- stamina = 0,
- certs = Nil,
- implants = Nil,
- firstTimeEvents = Nil,
- tutorials = Nil,
- cosmetics = None
- ),
+ MakeDetailedCharacterData(obj),
InventoryData((MakeHolsters(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)),
DrawnSlot.None
)
@@ -44,24 +31,72 @@ class CorpseConverter extends AvatarConverter {
* @return the resulting `CharacterAppearanceData`
*/
private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
- CharacterAppearanceData(
+ val aa : Int=>CharacterAppearanceA = CharacterAppearanceA(
BasicCharacterData(obj.Name, obj.Faction, CharacterGender.Male, 0, CharacterVoice.Mute),
- voice2 = 0,
black_ops = false,
+ altModel = true,
+ false,
+ None,
jammered = false,
obj.ExoSuit,
+ None,
+ 0,
+ 0,
+ 0L,
+ 0,
+ 0,
+ 0,
+ 0
+ )
+ val ab : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
+ 0L,
outfit_name = "",
outfit_logo = 0,
+ false,
backpack = true,
- facingPitch = obj.Orientation.y, //TODO is this important?
+ false,
+ false,
+ false,
+ facingPitch = 0,
facingYawUpper = 0,
- lfs = true,
+ lfs = false,
GrenadeState.None,
is_cloaking = false,
+ false,
+ false,
charging_pose = false,
- on_zipline = false,
- RibbonBars()
+ false,
+ on_zipline = None
)
+ CharacterAppearanceData(aa, ab, RibbonBars())
+ }
+
+ private def MakeDetailedCharacterData(obj : Player) : (Option[Int]=>DetailedCharacterData) = {
+ val ba : DetailedCharacterA = DetailedCharacterA(
+ bep = 0L,
+ cep = 0L,
+ 0L, 0L, 0L,
+ 0, 0,
+ false,
+ 0,
+ 0L,
+ 0, 0,
+ 0, 0, 0L,
+ List(0, 0, 0, 0, 0, 0),
+ certs = List.empty[CertificationType.Value]
+ )
+ val bb : (Long, Option[Int])=>DetailedCharacterB = DetailedCharacterB(
+ None,
+ implants = List.empty[ImplantEntry],
+ Nil, Nil,
+ firstTimeEvents = List.empty[String],
+ tutorials = List.empty[String],
+ 0L, 0L, 0L, 0L, 0L,
+ Some(DCDExtra2(0, 0)),
+ Nil, Nil, false,
+ cosmetics = None
+ )
+ (pad_length : Option[Int]) => DetailedCharacterData(ba, bb(0, pad_length))(pad_length)
}
/**
diff --git a/common/src/main/scala/net/psforever/objects/entity/IdentifiableEntity.scala b/common/src/main/scala/net/psforever/objects/entity/IdentifiableEntity.scala
index de158182d..3531f3906 100644
--- a/common/src/main/scala/net/psforever/objects/entity/IdentifiableEntity.scala
+++ b/common/src/main/scala/net/psforever/objects/entity/IdentifiableEntity.scala
@@ -16,16 +16,7 @@ abstract class IdentifiableEntity extends Identifiable {
private val container : GUIDContainable = GUIDContainer()
private var current : GUIDContainable = IdentifiableEntity.noGUIDContainer
- def HasGUID : Boolean = {
- try {
- GUID
- true
- }
- catch {
- case _ : NoGUIDException =>
- false
- }
- }
+ def HasGUID : Boolean = current ne IdentifiableEntity.noGUIDContainer
def GUID : PlanetSideGUID = current.GUID
diff --git a/common/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala b/common/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala
index 31699543a..c90c529b9 100644
--- a/common/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala
+++ b/common/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala
@@ -44,17 +44,15 @@ class UniqueNumberSystem(private val guid : NumberPoolHub, private val poolActor
def receive : Receive = {
case Register(obj, Some(pname), None, call) =>
val callback = call.getOrElse(sender())
- try {
- obj.GUID //stop if object already has a GUID; sometimes this happens
+ if(obj.HasGUID) {
AlreadyRegistered(obj, pname)
callback ! Success(obj)
}
- catch {
- case _ : Exception =>
- val id : Long = index
- index += 1
- requestQueue += id -> UniqueNumberSystem.GUIDRequest(obj, pname, callback)
- RegistrationProcess(pname, id)
+ else {
+ val id : Long = index
+ index += 1
+ requestQueue += id -> UniqueNumberSystem.GUIDRequest(obj, pname, callback)
+ RegistrationProcess(pname, id)
}
//this message is automatically sent by NumberPoolActor
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala
index fb6eee6ed..2bc98a369 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala
@@ -7,6 +7,96 @@ import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
+/**
+ * A part of a representation of the avatar portion of `ObjectCreateDetailedMessage` packet data.
+ * @see `CharacterData`
+ * @see `DetailedCharacterData`
+ * @see `ExoSuitType`
+ * @param app the player's cardinal appearance settings
+ * @param black_ops whether or not this avatar is enrolled in Black OPs
+ * @param jammered the player has been caught in an EMP blast recently;
+ * creates a jammered sound effect that follows the player around and can be heard by others
+ * @param exosuit the type of exo-suit the avatar will be depicted in;
+ * for Black OPs, the agile exo-suit and the reinforced exo-suit are replaced with the Black OPs exo-suits
+ */
+final case class CharacterAppearanceA(app : BasicCharacterData,
+ black_ops : Boolean,
+ altModel : Boolean,
+ unk1 : Boolean,
+ unk2 : Option[CharacterAppearanceData.ExtraData],
+ jammered : Boolean,
+ exosuit : ExoSuitType.Value,
+ unk3 : Option[Int],
+ unk4 : Int,
+ unk5 : Int,
+ unk6 : Long,
+ unk7 : Int,
+ unk8 : Int,
+ unk9 : Int,
+ unkA : Int)
+ (name_padding : Int) extends StreamBitSize {
+ override def bitsize : Long = {
+ //factor guard bool values into the base size, not its corresponding optional field
+ val unk2Size : Long = unk2 match { case Some(n) => n.bitsize ; case None => 0L }
+ val nameStringSize : Long = StreamBitSize.stringBitSize(app.name, 16) + name_padding
+ val unk3Size : Long = unk3 match { case Some(_) => 32L ; case None => 0L }
+ 137L + unk2Size + nameStringSize + unk3Size
+ }
+}
+
+/**
+ * A part of a representation of the avatar portion of `ObjectCreateDetailedMessage` packet data.
+ * @see `CharacterData`
+ * @see `DetailedCharacterData`
+ * @see `ExoSuitType`
+ * @see `GrenadeState`
+ * @see `RibbonBars`
+ * @see `http://www.planetside-universe.com/p-outfit-decals-31.htm`
+ * @param outfit_name the name of the outfit to which this player belongs;
+ * if the option is selected, allies with see either "[`outfit_name`]" or "{No Outfit}" under the player's name
+ * @param outfit_logo the decal seen on the player's exo-suit (and beret and cap) associated with the player's outfit;
+ * if there is a variable color for that decal, the faction-appropriate one is selected
+ * @param facingPitch a "pitch" angle
+ * @param facingYawUpper a "yaw" angle that represents the angle of the avatar's upper body with respect to its forward-facing direction;
+ * this number is normally 0 for forward facing;
+ * the range is limited between approximately 61 degrees of center turned to left or right
+ * @param lfs this player is looking for a squad;
+ * all allies will see the phrase "[Looking for Squad]" under the player's name
+ * @param is_cloaking avatar is cloaked by virtue of an Infiltration Suit
+ * @param grenade_state if the player has a grenade `Primed`;
+ * should be `GrenadeStateState.None` if nothing special
+ * @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
+ */
+final case class CharacterAppearanceB(unk0 : Long,
+ outfit_name : String,
+ outfit_logo : Int,
+ unk1 : Boolean,
+ backpack : Boolean,
+ unk2 : Boolean,
+ unk3 : Boolean,
+ unk4 : Boolean,
+ facingPitch : Float,
+ facingYawUpper : Float,
+ lfs : Boolean,
+ grenade_state : GrenadeState.Value,
+ is_cloaking : Boolean,
+ unk5 : Boolean,
+ unk6 : Boolean,
+ charging_pose : Boolean,
+ unk7 : Boolean,
+ on_zipline : Option[CharacterAppearanceData.ZiplineData])
+ (alt_model : Boolean, name_padding : Int) extends StreamBitSize {
+ override def bitsize : Long = {
+ //factor guard bool values into the base size, not its corresponding optional field
+ val outfitStringSize : Long = StreamBitSize.stringBitSize(outfit_name, 16) +
+ CharacterAppearanceData.outfitNamePadding //even if the outfit_name is blank, string is always padded
+ val backpackSize = if(backpack) { 1L } else { 0L }
+ val onZiplineSize : Long = on_zipline match { case Some(n) => n.bitsize; case None => 0 }
+ 70L + outfitStringSize + backpackSize + onZiplineSize
+ }
+}
+
/**
* A part of a representation of the avatar portion of `ObjectCreateDetailedMessage` packet data.
*
@@ -28,64 +118,16 @@ import shapeless.{::, HNil}
*
* Exploration:
* How do I crouch?
- * @see `CharacterData`
- * `DetailedCharacterData`
- * `ExoSuitType`
- * `GrenadeState`
- * `RibbonBars`
- * @see `http://www.planetside-universe.com/p-outfit-decals-31.htm`
- * @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
- * @param black_ops whether or not this avatar is enrolled in Black OPs
- * @param jammered the player has been caught in an EMP blast recently;
- * creates a jammered sound effect that follows the player around and can be heard by others
- * @param exosuit the type of exo-suit the avatar will be depicted in;
- * for Black OPs, the agile exo-suit and the reinforced exo-suit are replaced with the Black OPs exo-suits
- * @param outfit_name the name of the outfit to which this player belongs;
- * if the option is selected, allies with see either "[`outfit_name`]" or "{No Outfit}" under the player's name
- * @param outfit_logo the decal seen on the player's exo-suit (and beret and cap) associated with the player's outfit;
- * if there is a variable color for that decal, the faction-appropriate one is selected
- * @param facingPitch a "pitch" angle
- * @param facingYawUpper a "yaw" angle that represents the angle of the avatar's upper body with respect to its forward-facing direction;
- * this number is normally 0 for forward facing;
- * the range is limited between approximately 61 degrees of center turned to left or right
- * @param lfs this player is looking for a squad;
- * all allies will see the phrase "[Looking for Squad]" under the player's name
- * @param is_cloaking avatar is cloaked by virtue of an Infiltration Suit
- * @param grenade_state if the player has a grenade `Primed`;
- * should be `GrenadeStateState.None` if nothing special
- * @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
+ * @see `CharacterData`
+ * @see `DetailedCharacterData`
* @param ribbons the four merit commendation ribbon medals
*/
-final case class CharacterAppearanceData(app : BasicCharacterData,
- voice2 : Int,
- black_ops : Boolean,
- jammered : Boolean,
- exosuit : ExoSuitType.Value,
- outfit_name : String,
- outfit_logo : Int,
- backpack : Boolean,
- facingPitch : Float,
- facingYawUpper : Float,
- lfs : Boolean,
- grenade_state : GrenadeState.Value,
- is_cloaking : Boolean,
- charging_pose : Boolean,
- on_zipline : Boolean,
+final case class CharacterAppearanceData(a : CharacterAppearanceA,
+ b : CharacterAppearanceB,
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 nameStringSize : Long = StreamBitSize.stringBitSize(app.name, 16) + name_padding
- val outfitStringSize : Long = StreamBitSize.stringBitSize(outfit_name, 16) +
- CharacterAppearanceData.outfitNamePadding //even if the outfit_name is blank, string is always padded
- val altModelSize = CharacterAppearanceData.altModelBit(this).getOrElse(0)
- 335L + nameStringSize + outfitStringSize + altModelSize //base value includes the ribbons
- }
+ override def bitsize : Long = 128L + a.bitsize + b.bitsize
/**
* External access to the value padding on the name field.
@@ -102,6 +144,91 @@ final case class CharacterAppearanceData(app : BasicCharacterData,
}
object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
+ def apply(app : BasicCharacterData,
+ black_ops : Boolean,
+ jammered : Boolean,
+ exosuit : ExoSuitType.Value,
+ outfit_name : String,
+ outfit_logo : Int,
+ backpack : Boolean,
+ facingPitch : Float,
+ facingYawUpper : Float,
+ lfs : Boolean,
+ grenade_state : GrenadeState.Value,
+ is_cloaking : Boolean,
+ charging_pose : Boolean,
+ on_zipline : Option[ZiplineData],
+ ribbons : RibbonBars)(name_padding : Int) : CharacterAppearanceData = {
+ val altModel : Boolean = backpack || on_zipline.isDefined
+ val a = CharacterAppearanceA(
+ app,
+ black_ops,
+ altModel,
+ false,
+ None,
+ jammered,
+ exosuit,
+ None,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ )(name_padding)
+ val b = CharacterAppearanceB(
+ outfit_name.length,
+ outfit_name : String,
+ outfit_logo : Int,
+ false,
+ backpack,
+ false,
+ false,
+ false,
+ facingPitch : Float,
+ facingYawUpper : Float,
+ lfs : Boolean,
+ grenade_state : GrenadeState.Value,
+ is_cloaking : Boolean,
+ false,
+ false,
+ charging_pose : Boolean,
+ false,
+ on_zipline
+ )(altModel, name_padding)
+ new CharacterAppearanceData(
+ a,
+ b,
+ ribbons
+ )(name_padding)
+ }
+
+ def apply(a : Int=>CharacterAppearanceA, b : (Boolean,Int)=>CharacterAppearanceB, ribbons : RibbonBars)(name_padding : Int) : CharacterAppearanceData = {
+ val first = a(name_padding)
+ CharacterAppearanceData(a(name_padding), b(first.altModel, name_padding), ribbons)(name_padding)
+ }
+
+ /**
+ * na
+ * @param unk1 na
+ * @param unk2 na
+ */
+ final case class ExtraData(unk1 : Boolean,
+ unk2 : Boolean) extends StreamBitSize {
+ override def bitsize : Long = 2L
+ }
+
+ /**
+ * na
+ * @param unk1 na
+ * @param unk2 na
+ */
+ final case class ZiplineData(unk1 : Long,
+ unk2 : Boolean) extends StreamBitSize {
+ override def bitsize : Long = 33L
+ }
+
/**
* When a player is released-dead or attached to a zipline, their basic infantry model is replaced with a different one.
* In the former case, a backpack.
@@ -110,13 +237,26 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
* @param app the appearance
* @return the length of the variable field that exists when using alternate models
*/
- def altModelBit(app : CharacterAppearanceData) : Option[Int] = if(app.backpack || app.on_zipline) {
+ def altModelBit(app : CharacterAppearanceData) : Option[Int] = if(app.b.backpack || app.b.on_zipline.isDefined) {
Some(1)
}
else {
None
}
+ def namePadding(inheritPad : Int, pad : Option[ExtraData]) : Int = {
+ pad match {
+ case Some(n) =>
+ val bitsize = n.bitsize.toInt % 8
+ if(inheritPad > bitsize)
+ inheritPad - bitsize
+ else
+ 8 - bitsize
+ case None =>
+ inheritPad
+ }
+ }
+
/**
* Get the padding of the outfit's name.
* The padding will always be a number 0-7.
@@ -126,72 +266,60 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
6
}
- def codec(name_padding : Int) : Codec[CharacterAppearanceData] = (
+ private val extra_codec : Codec[ExtraData] = (
+ ("unk1" | bool) ::
+ ("unk2" | bool)
+ ).as[ExtraData]
+
+ private val zipline_codec : Codec[ZiplineData] = (
+ ("unk1" | uint32L) ::
+ ("unk2" | bool)
+ ).as[ZiplineData]
+
+ /**
+ * na
+ * @param name_padding na
+ * @return na
+ */
+ def a_codec(name_padding : Int) : Codec[CharacterAppearanceA] = (
("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(name_padding)) ::
- ("exosuit" | ExoSuitType.codec) ::
- ignore(2) :: //unknown
- ("sex" | CharacterGender.codec) ::
- ("head" | uint8L) ::
- ("voice" | CharacterVoice.codec) ::
- ("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)
+ ("unk1" | bool) :: //serves a different internal purpose depending on the state of alt_model
+ (conditional(false, "unk2" | extra_codec) >>:~ { extra => //TODO not sure what causes this branch
+ ("jammered" | bool) ::
+ optional(bool, "unk3" | uint16L) ::
+ ("unk4" | uint16L) ::
+ ("name" | PacketHelpers.encodedWideStringAligned(namePadding(name_padding, extra))) ::
+ ("exosuit" | ExoSuitType.codec) ::
+ ("unk5" | uint2) :: //unknown
+ ("sex" | CharacterGender.codec) ::
+ ("head" | uint8L) ::
+ ("voice" | CharacterVoice.codec) ::
+ ("unk6" | uint32L) ::
+ ("unk7" | uint16L) ::
+ ("unk8" | uint16L) ::
+ ("unk9" | uint16L) ::
+ ("unkA" | uint16L) //usually either 0 or 65535
+ })
})
- ).exmap[CharacterAppearanceData] (
+ ).exmap[CharacterAppearanceA] (
{
- case _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil |
- _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: HNil =>
- Attempt.Failure(Err("invalid character appearance data; can not encode alternate model without required bit set"))
-
- 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 =>
+ case faction :: bops :: alt :: u1 :: u2 :: jamd :: u3 :: u4 :: name :: suit :: u5 :: sex :: head :: v1 :: u6 :: u7 :: u8 :: u9 :: uA :: HNil =>
Attempt.successful(
- CharacterAppearanceData(BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons)(name_padding)
+ CharacterAppearanceA(BasicCharacterData(name, faction, sex, head, v1), bops, alt, u1, u2, jamd, suit, u3, u4, u5, u6, u7, u8, u9, uA)(name_padding)
)
case _ =>
Attempt.Failure(Err("invalid character appearance data; can not encode"))
},
{
- case CharacterAppearanceData(BasicCharacterData(name, PlanetSideEmpire.NEUTRAL, _, _, _), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
+ case CharacterAppearanceA(BasicCharacterData(name, PlanetSideEmpire.NEUTRAL, _, _, _), _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
Attempt.failure(Err(s"character $name's faction can not declare as neutral"))
- 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_extrabit : Option[Boolean] = None
- if(zipline || bpack) {
- alt_model = true
- alt_model_extrabit = Some(false)
- }
+ case CharacterAppearanceA(BasicCharacterData(name, faction, sex, head, v1), bops, alt, u1, u2, jamd, suit, u3, u4, u5, u6, u7, u8, u9, uA) =>
Attempt.successful(
- 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 :: u1 :: u2 :: jamd :: u3 :: u4 :: name :: suit :: u5 :: sex :: head :: v1 :: u6 :: u7 :: u8 :: u9 :: uA :: HNil
)
case _ =>
@@ -199,5 +327,77 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
}
)
+ /**
+ * na
+ * @param alt_model na
+ * @param name_padding na
+ * @return na
+ */
+ def b_codec(alt_model : Boolean, name_padding : Int) : Codec[CharacterAppearanceB] = (
+ ("unk0" | uint32L) :: //for outfit_name (below) to be visible in-game, this value should be non-zero
+ ("outfit_name" | PacketHelpers.encodedWideStringAligned(outfitNamePadding)) ::
+ ("outfit_logo" | uint8L) ::
+ ("unk1" | bool) :: //unknown
+ conditional(alt_model, "backpack" | bool) :: //alt_model flag adds this bit; see ps.c:line#1069587
+ ("unk2" | bool) :: //requires alt_model flag (does NOT require health == 0)
+ ("unk3" | bool) :: //stream misalignment when set
+ ("unk4" | bool) :: //unknown
+ ("facingPitch" | Angular.codec_pitch) ::
+ ("facingYawUpper" | Angular.codec_yaw(0f)) ::
+ ("lfs" | uint2) ::
+ ("grenade_state" | GrenadeState.codec_2u) :: //note: bin10 and bin11 are neutral (bin00 is not defined)
+ ("is_cloaking" | bool) ::
+ ("unk5" | bool) :: //unknown
+ ("unk6" | bool) :: //stream misalignment when set
+ ("charging_pose" | bool) ::
+ ("unk7" | bool) :: //alternate charging pose?
+ optional(bool, "on_zipline" | zipline_codec)
+ ).exmap[CharacterAppearanceB] (
+ {
+ case u0 :: outfit :: logo :: u1 :: bpack :: u2 :: u3 :: u4 :: facingPitch :: facingYawUpper :: lfs :: gstate :: cloaking :: u5 :: u6 :: charging :: u7 :: zipline :: HNil =>
+ val lfsBool = if(lfs == 0) false else true
+ val bpackBool = bpack match { case Some(_) => alt_model ; case None => false }
+ Attempt.successful(
+ CharacterAppearanceB(u0, outfit, logo, u1, bpackBool, u2, u3, u4, facingPitch, facingYawUpper, lfsBool, gstate, cloaking, u5, u6, charging, u7, zipline)(alt_model, name_padding)
+ )
+ },
+ {
+ case CharacterAppearanceB(u0, outfit, logo, u1, bpack, u2, u3, u4, facingPitch, facingYawUpper, lfs, gstate, cloaking, u5, u6, charging, u7, zipline) =>
+ val u0Long = if(u0 == 0 && outfit.nonEmpty) {
+ outfit.length.toLong
+ }
+ else {
+ u0
+ } //TODO this is a kludge; unk0 must be (some) non-zero if outfit_name is defined
+ val (bpackOpt, zipOpt) = if(alt_model) {
+ val bpackOpt = if(bpack) { Some(true) } else { None }
+ (bpackOpt, zipline)
+ }
+ else {
+ (None, None)
+ } //alt_model must be set for either of the other two to be valid
+ val lfsInt = if(lfs) { 1 } else { 0 }
+ Attempt.successful(
+ u0Long :: outfit :: logo :: u1 :: bpackOpt :: u2 :: u3 :: u4 :: facingPitch :: facingYawUpper :: lfsInt :: gstate :: cloaking :: u5 :: u6 :: charging :: u7 :: zipOpt :: HNil
+ )
+ }
+ )
+
+ def codec(name_padding : Int) : Codec[CharacterAppearanceData] = (
+ ("a" | a_codec(name_padding)) >>:~ { a =>
+ ("b" | b_codec(a.altModel, name_padding)) ::
+ ("ribbons" | RibbonBars.codec)
+ }
+ ).xmap[CharacterAppearanceData] (
+ {
+ case a :: b :: ribbons :: HNil =>
+ CharacterAppearanceData(a, b, ribbons)(name_padding)
+ },
+ {
+ case CharacterAppearanceData(a, b, ribbons) =>
+ a :: b :: ribbons :: HNil
+ }
+ )
+
implicit val codec : Codec[CharacterAppearanceData] = codec(0)
}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterData.scala
index d89e6d379..a913b9010 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterData.scala
@@ -8,11 +8,10 @@ import shapeless.{::, HNil}
/**
* Values for the implant effects on a character model.
- * The effects can not be activated simultaneously.
- * In at least one case, attempting to activate multiple effects will cause the PlanetSide client to crash.
+ * The effects are not additive and this value is not a bitmask.
*
* `RegenEffects` is a reverse-flagged item - inactive when the corresponding bit is set.
- * For that reason, every other effect is `n`+1, while `NoEffects` is 1 and `RegenEffects` is 0.
+ * For that reason, every other effect is `n + 1`, while `NoEffects` is `1` and `RegenEffects` is `0`.
*/
object ImplantEffects extends Enumeration {
type Type = Value
@@ -44,19 +43,20 @@ object UniformStyle extends Enumeration {
/**
* A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.
*
- * This densely-packed information outlines most of the specifics required to depict some other player's character.
+ * This 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.
- * Of the inventory for this character, only the initial five weapon slots are defined.
- *
+ * Of the inventory for this character, only the initial five weapon slots are defined.
* 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.
+ * @see `DetailedCharacterData`
+ * @see `Cosmetics`
* @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;
- * while `is_corpse == true`, `health` will always report as 0;
+ * while `is_backpack == true`, `health` will always report as 0;
* while `is_seated == true`, `health` will (try to) report as 100
* @param armor the amount of armor the player has, as a percentage of a filled bar;
* the bar has 85 states, with 3 points for each state;
@@ -65,7 +65,8 @@ object UniformStyle extends Enumeration {
* @param command_rank the player's command rank as a number from 0 to 5;
* cosmetic armor associated with the command rank will be applied automatically
* @param implant_effects the effects of implants that can be seen on a player's character;
- * though many implants can be used simultaneously, only one implant effect can be applied here
+ * the number of entries equates to the number of effects applied;
+ * the maximu number of effects is three
* @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
@@ -74,23 +75,20 @@ object UniformStyle extends Enumeration {
* the alternate model bit should be flipped
* @param is_seated this player character is seated in a vehicle or mounted to some other object;
* alternate format for data parsing applies
- * @see `DetailedCharacterData`
- * `CharacterAppearanceData`
*/
final case class CharacterData(health : Int,
armor : Int,
uniform_upgrade : UniformStyle.Value,
unk : Int,
command_rank : Int,
- implant_effects : Option[ImplantEffects.Value],
+ implant_effects : List[ImplantEffects.Value],
cosmetics : Option[Cosmetics])
(is_backpack : Boolean,
is_seated : Boolean) extends ConstructorData {
override def bitsize : Long = {
- //factor guard bool values into the base size, not its corresponding optional field
val seatedSize = if(is_seated) { 0 } else { 16 }
- val effectsSize : Long = if(implant_effects.isDefined) { 4L } else { 0L }
+ val effectsSize : Long = implant_effects.length * 4L
val cosmeticsSize : Long = if(cosmetics.isDefined) { cosmetics.get.bitsize } else { 0L }
11L + seatedSize + effectsSize + cosmeticsSize
}
@@ -98,7 +96,7 @@ final case class CharacterData(health : Int,
object CharacterData extends Marshallable[CharacterData] {
/**
- * An overloaded constructor for `CharacterData` that allows for a not-optional inventory.
+ * An overloaded constructor for `CharacterData`.
* @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
@@ -107,32 +105,31 @@ object CharacterData extends Marshallable[CharacterData] {
* @param cosmetics optional decorative features that are added to the player's head model by console/chat commands
* @return a `CharacterData` object
*/
- def apply(health : Int, armor : Int, uniform : UniformStyle.Value, cr : Int, implant_effects : Option[ImplantEffects.Value], cosmetics : Option[Cosmetics]) : (Boolean,Boolean)=>CharacterData =
+ def apply(health : Int, armor : Int, uniform : UniformStyle.Value, cr : Int, implant_effects : List[ImplantEffects.Value], cosmetics : Option[Cosmetics]) : (Boolean,Boolean)=>CharacterData =
CharacterData(health, armor, uniform, 0, cr, implant_effects, cosmetics)
def codec(is_backpack : Boolean) : Codec[CharacterData] = (
("health" | uint8L) :: //dead state when health == 0
("armor" | uint8L) ::
(("uniform_upgrade" | UniformStyle.codec) >>:~ { style =>
- ignore(3) :: //unknown
+ uint(3) :: //uniform_upgrade is actually interpreted as a 6u field, but the lower 3u seems to be discarded
("command_rank" | uintL(3)) ::
- bool :: //misalignment when == 1
- optional(bool, "implant_effects" | ImplantEffects.codec) ::
+ listOfN(uint2, "implant_effects" | ImplantEffects.codec) ::
conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec)
})
).exmap[CharacterData] (
{
- case health :: armor :: uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: HNil =>
+ case health :: armor :: uniform :: unk :: cr :: implant_effects :: cosmetics :: HNil =>
val newHealth = if(is_backpack) { 0 } else { health }
- Attempt.Successful(CharacterData(newHealth, armor, uniform, 0, cr, implant_effects, cosmetics)(is_backpack, false))
+ Attempt.Successful(CharacterData(newHealth, armor, uniform, unk, cr, implant_effects, cosmetics)(is_backpack, false))
case _ =>
Attempt.Failure(Err("invalid character data; can not encode"))
},
{
- case CharacterData(health, armor, uniform, _, cr, implant_effects, cosmetics) =>
+ case CharacterData(health, armor, uniform, unk, cr, implant_effects, cosmetics) =>
val newHealth = if(is_backpack) { 0 } else { health }
- Attempt.Successful(newHealth :: armor :: uniform :: () :: cr :: false :: implant_effects :: cosmetics :: HNil)
+ Attempt.Successful(newHealth :: armor :: uniform :: unk :: cr :: implant_effects :: cosmetics :: HNil)
case _ =>
Attempt.Failure(Err("invalid character data; can not decode"))
@@ -141,23 +138,22 @@ object CharacterData extends Marshallable[CharacterData] {
def codec_seated(is_backpack : Boolean) : Codec[CharacterData] = (
("uniform_upgrade" | UniformStyle.codec) >>:~ { style =>
- ignore(3) :: //unknown
+ uint(3) :: //uniform_upgrade is actually interpreted as a 6u field, but the lower 3u seems to be discarded
("command_rank" | uintL(3)) ::
- bool :: //stream misalignment when != 1
- optional(bool, "implant_effects" | ImplantEffects.codec) ::
+ listOfN(uint2, "implant_effects" | ImplantEffects.codec) ::
conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec)
}
).exmap[CharacterData] (
{
- case uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: HNil =>
- Attempt.Successful(new CharacterData(100, 0, uniform, 0, cr, implant_effects, cosmetics)(is_backpack, true))
+ case uniform :: unk :: cr :: implant_effects :: cosmetics :: HNil =>
+ Attempt.Successful(new CharacterData(100, 0, uniform, unk, cr, implant_effects, cosmetics)(is_backpack, true))
case _ =>
Attempt.Failure(Err("invalid character data; can not encode"))
},
{
- case obj @ CharacterData(_, _, uniform, _, cr, implant_effects, cosmetics) =>
- Attempt.Successful(uniform :: () :: cr :: false :: implant_effects :: cosmetics :: HNil)
+ case CharacterData(_, _, uniform, unk, cr, implant_effects, cosmetics) =>
+ Attempt.Successful(uniform :: unk :: cr :: implant_effects :: cosmetics :: HNil)
case _ =>
Attempt.Failure(Err("invalid character data; can not decode"))
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala
index fa7f7f933..7d86cc58b 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala
@@ -12,29 +12,52 @@ import scala.annotation.tailrec
/**
* An entry in the `List` of valid implant slots in `DetailedCharacterData`.
- * `activation`, if defined, indicates the time remaining (in seconds?) before an implant becomes usable.
* @param implant the type of implant
- * @param activation the activation timer;
- * technically, this is "unconfirmed"
+ * @param initialization the amount of time necessary until this implant is ready to be activated;
+ * technically, this is unconfirmed
+ * @param active whether this implant is turned on;
+ * technically, this is unconfirmed
* @see `ImplantType`
*/
final case class ImplantEntry(implant : ImplantType.Value,
- activation : Option[Int]) extends StreamBitSize {
+ initialization : Option[Int],
+ active : Boolean) extends StreamBitSize {
override def bitsize : Long = {
- val activationSize = if(activation.isDefined) { 12L } else { 5L }
- 5L + activationSize
+ val timerSize = initialization match { case Some(_) => 8L ; case None => 1L }
+ 9L + timerSize
+ }
+}
+
+object ImplantEntry {
+ def apply(implant : ImplantType.Value, initialization : Option[Int]) : ImplantEntry = {
+ ImplantEntry(implant, initialization, false)
}
}
/**
- * A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.
- *
- * 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.
- * Additionally, a full inventory, as opposed to the initial five weapon slots.
+ * na
+ * @param unk1 na
+ * @param unk2 na
+ */
+final case class DCDExtra1(unk1 : String,
+ unk2 : Int) extends StreamBitSize {
+ override def bitsize : Long = 16L + StreamBitSize.stringBitSize(unk1)
+}
+
+/**
+ * na
+ * @param unk1 an
+ * @param unk2 na
+ */
+final case class DCDExtra2(unk1 : Int,
+ unk2 : Int) extends StreamBitSize {
+ override def bitsize : Long = 13L
+}
+
+/**
+ * A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.
+ * @see `CharacterData`
+ * @see `CertificationType`
* @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;
@@ -44,17 +67,39 @@ final case class ImplantEntry(implant : ImplantType.Value,
* @param armor for `x / y` of armor points, this is the avatar's `x` value;
* range is 0-65535;
* the avatar's `y` armor points is tied to their exo-suit type
- * @param unk1 na;
- * defaults to 1
- * @param unk2 na;
- * defaults to 7
- * @param unk3 na;
- * defaults to 7
* @param staminaMax for `x / y` of stamina points, this is the avatar's `y` value;
* range is 0-65535
* @param stamina for `x / y` of stamina points, this is the avatar's `x` value;
* range is 0-65535
- * @param certs the `List` of active certifications
+ * @param certs the `List` of certifications
+ */
+final case class DetailedCharacterA(bep : Long,
+ cep : Long,
+ unk1 : Long,
+ unk2 : Long,
+ unk3 : Long,
+ healthMax : Int,
+ health : Int,
+ unk4 : Boolean,
+ armor : Int,
+ unk5 : Long,
+ staminaMax : Int,
+ stamina : Int,
+ unk6 : Int,
+ unk7 : Int,
+ unk8 : Long,
+ unk9 : List[Int],
+ certs : List[CertificationType.Value]) extends StreamBitSize {
+ override def bitsize : Long = {
+ val certSize : Long = certs.length * 8
+ 428L + certSize
+ }
+}
+
+/**
+ * A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.
+ * @see `CharacterData`
+ * @see `Cosmetics`
* @param implants the `List` of implant slots currently possessed by this avatar
* @param firstTimeEvents the list of first time events performed by this avatar;
* the size field is a 32-bit number;
@@ -65,92 +110,160 @@ 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
- * @see `CharacterData`
- * `CertificationType`
*/
-final case class DetailedCharacterData(bep : Long,
- cep : Long,
- healthMax : Int,
- health : Int,
- armor : Int,
- unk1 : Int, //1
- unk2 : Int, //7
- unk3 : Int, //7
- staminaMax : Int,
- stamina : Int,
- certs : List[CertificationType.Value],
- implants : List[ImplantEntry],
- firstTimeEvents : List[String],
- tutorials : List[String],
- cosmetics : Option[Cosmetics])
- (pad_length : Option[Int]) extends ConstructorData {
-
+final case class DetailedCharacterB(unk1 : Option[Long],
+ implants : List[ImplantEntry],
+ unk2 : List[DCDExtra1],
+ unk3 : List[DCDExtra1],
+ firstTimeEvents : List[String],
+ tutorials : List[String],
+ unk4 : Long,
+ unk5 : Long,
+ unk6 : Long,
+ unk7 : Long,
+ unk8 : Long,
+ unk9 : Option[DCDExtra2],
+ unkA : List[Long],
+ unkB : List[String],
+ unkC : Boolean,
+ cosmetics : Option[Cosmetics])
+ (bep : Long,
+ pad_length : Option[Int]) extends StreamBitSize {
override def bitsize : Long = {
- //factor guard bool values into the base size, not corresponding optional fields, unless contained or enumerated
- val certSize = (certs.length + 1) * 8 //cert list
- var implantSize : Long = 0L //implant list
- for(entry <- implants) {
- implantSize += entry.bitsize
+ //unk1
+ val unk1Size = unk1 match { case Some(_) => 32L ; case None => 0L }
+ //implant list
+ val implantSize : Long = implants.foldLeft(0L)(_ + _.bitsize)
+ //fte list
+ val eventListSize : Long = firstTimeEvents.foldLeft(0L)(_ + StreamBitSize.stringBitSize(_))
+ //tutorial list
+ val tutorialListSize : Long = tutorials.foldLeft(0L)(_ + StreamBitSize.stringBitSize(_))
+ val unk2Len = unk2.size
+ val unk3Len = unk3.size
+ val unkAllLen = unk2Len + unk3Len
+ val unk2_3ListSize : Long = if(unk2Len > 0) {
+ unkAllLen * unk2.head.bitsize
}
- val implantPadding = DetailedCharacterData.implantFieldPadding(implants, pad_length)
- val fteLen = firstTimeEvents.size //fte list
- var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen, implantPadding)
- for(str <- firstTimeEvents) {
- eventListSize += StreamBitSize.stringBitSize(str)
+ else if(unk3Len > 0) {
+ unkAllLen * unk3.head.bitsize
}
- val tutLen = tutorials.size //tutorial list
- var tutorialListSize : Long = 32L + DetailedCharacterData.tutPadding(fteLen, tutLen, implantPadding)
- for(str <- tutorials) {
- tutorialListSize += StreamBitSize.stringBitSize(str)
+ else {
+ 0L
}
- val br24 = DetailedCharacterData.isBR24(bep) //character is at least BR24
- val extraBitSize : Long = if(br24) { 33L } else { 46L }
+ //character is at least BR24
+ val br24 = DetailedCharacterData.isBR24(bep)
+ val unk9Size : Long = if(br24) { 0L } else { 13L }
+ val unkASize : Long = unkA.length * 32L
+ val unkBSize : Long = unkB.foldLeft(0L)(_ + StreamBitSize.stringBitSize(_))
val cosmeticsSize : Long = if(br24) { cosmetics.get.bitsize } else { 0L }
- 598L + certSize + implantSize + eventListSize + extraBitSize + cosmeticsSize + tutorialListSize
+
+ val paddingSize : Int =
+ DetailedCharacterData.paddingCalculations(pad_length, implants, Nil)(unk2Len) + /* unk2 */
+ DetailedCharacterData.paddingCalculations(pad_length, implants, List(unk2))(unk3Len) + /* unk3 */
+ DetailedCharacterData.paddingCalculations(pad_length, implants, List(unk3, unk2))(firstTimeEvents.length) + /* firstTimeEvents */
+ DetailedCharacterData.paddingCalculations(pad_length, implants, List(firstTimeEvents, unk3, unk2))(tutorials.size) + /* tutorials */
+ DetailedCharacterData.paddingCalculations(
+ DetailedCharacterData.displaceByUnk9(pad_length, unk9, 5),
+ implants,
+ List(DetailedCharacterData.optToList(unk9), tutorials, firstTimeEvents, unk3, unk2)
+ )(unkB.length) /* unkB */
+ 275L + unk1Size + implantSize + eventListSize + unk2_3ListSize + tutorialListSize + unk9Size + unkASize + unkBSize + cosmeticsSize + paddingSize
}
}
+/**
+ * A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.
+ *
+ * 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, as is the case with `CharacterData`.
+ * Just as prominent is the list of first time events and the list of completed tutorials.
+ * Additionally, a full inventory, as opposed to the initial five weapon slots.
+ * @see `CharacterData`
+ */
+final case class DetailedCharacterData(a : DetailedCharacterA,
+ b : DetailedCharacterB)
+ (pad_length : Option[Int]) extends ConstructorData {
+
+ override def bitsize : Long = a.bitsize + b.bitsize
+}
+
object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
- /**
- * Overloaded constructor for `DetailedCharacterData` that requires an inventory and drops unknown values.
- * @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
- * @param health for `x / y` of hitpoints, this is the avatar's `x` value
- * @param armor for `x / y` of armor points, this is the avatar's `x` value
- * @param staminaMax for `x / y` of stamina points, this is the avatar's `y` value
- * @param stamina for `x / y` of stamina points, this is the avatar's `x` value
- * @param certs the `List` of active certifications
- * @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
- * @return a `DetailedCharacterData` object
- */
- 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)
+ 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 = {
+ val a = DetailedCharacterA(
+ bep,
+ cep,
+ 0L,
+ 0L,
+ 0L,
+ healthMax, health,
+ false,
+ armor,
+ 0L,
+ staminaMax, stamina,
+ 0,
+ 0,
+ 0L,
+ List(0,0,0,0,0,0),
+ certs
+ )
+ val b : (Long, Option[Int]) => DetailedCharacterB = DetailedCharacterB(
+ None,
+ implants,
+ Nil,
+ Nil,
+ firstTimeEvents,
+ tutorials,
+ 0L,
+ 0L,
+ 0L,
+ 0L,
+ 0L,
+ None,
+ Nil,
+ Nil,
+ false,
+ cosmetics
+ )
+ (pad_length : Option[Int]) => DetailedCharacterData(a, b(a.bep, pad_length))(pad_length)
+ }
/**
* `Codec` for entries in the `List` of implants.
*/
private val implant_entry_codec : Codec[ImplantEntry] = (
- ("implant" | ImplantType.codec) ::
+ ("implant" | uint8L) ::
(bool >>:~ { guard =>
- newcodecs.binary_choice(guard, uintL(5), uintL(12)).hlist
+ newcodecs.binary_choice(guard, uint(1), uint8L).hlist
})
).xmap[ImplantEntry] (
{
- case implant :: true :: _ :: HNil =>
- ImplantEntry(implant, None)
+ case implant :: true :: n :: HNil => //initialized (no timer), active/inactive?
+ val activeBool : Boolean = n != 0
+ ImplantEntry(ImplantType(implant), None, activeBool) //TODO catch potential NoSuchElementException?
- case implant :: false :: extra :: HNil =>
- ImplantEntry(implant, Some(extra))
+ case implant :: false :: extra :: HNil => //uninitialized (timer), inactive
+ ImplantEntry(ImplantType(implant), Some(extra), false) //TODO catch potential NoSuchElementException?
},
{
- case ImplantEntry(implant, None) =>
- implant :: true :: 0 :: HNil
+ case ImplantEntry(implant, None, n) => //initialized (no timer), active/inactive?
+ val activeInt : Int = if(n) { 1 } else { 0 }
+ implant.id :: true :: activeInt :: HNil
- case ImplantEntry(implant, Some(extra)) =>
- implant :: false :: extra :: HNil
+ case ImplantEntry(implant, Some(extra), _) => //uninitialized (timer), inactive
+ implant.id :: false :: extra :: HNil
}
)
@@ -176,21 +289,215 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
}
/**
- * The padding value of the first entry in either of two byte-aligned `List` structures.
- * @param implants implant entries
- * @return the pad length in bits `0 <= n < 8`
+ * `Codec` for a `List` of `DCDExtra1` objects.
+ * The first entry contains a padded `String` so it must be processed different from the remainder.
*/
- private def implantFieldPadding(implants : List[ImplantEntry], varBit : Option[Int] = None) : Int = {
- val base : Int = 5 //the offset with no implant entries
- val baseOffset : Int = base - varBit.getOrElse(0)
- val resultA = if(baseOffset < 0) { 8 - baseOffset } else { baseOffset % 8 }
+ private def dcd_list_codec(padFunc : (Long)=>Int) : Codec[List[DCDExtra1]] = (
+ uint8 >>:~ { size =>
+ conditional(size > 0, dcd_extra1_codec(padFunc(size))) ::
+ PacketHelpers.listOfNSized(size - 1, dcd_extra1_codec(0))
+ }
+ ).xmap[List[DCDExtra1]] (
+ {
+ case _ :: Some(first) :: Nil :: HNil =>
+ List(first)
+ case _ :: Some(first) :: rest :: HNil =>
+ first +: rest
+ case _ :: None :: _ :: HNil =>
+ List()
+ },
+ {
+ case List() =>
+ 0 :: None :: Nil :: HNil
+ case contents =>
+ contents.length :: contents.headOption :: contents.tail :: HNil
+ }
+ )
- var implantOffset : Int = 0
- implants.foreach({entry =>
- implantOffset += entry.bitsize.toInt
- })
- val resultB : Int = resultA - (implantOffset % 8)
- if(resultB < 0) { 8 + resultB } else { resultB }
+ /**
+ * `Codec` for entries in the `List` of `DCDExtra1` objects.
+ * The first entry's size of 80 characters is hard-set by the client.
+ */
+ private def dcd_extra1_codec(pad : Int) : Codec[DCDExtra1] = (
+ ("unk1" | PacketHelpers.encodedStringAligned(pad)) ::
+ ("unk2" | uint16L)
+ ).xmap[DCDExtra1] (
+ {
+ case unk1 :: unk2 :: HNil =>
+ DCDExtra1(unk1, unk2)
+ },
+ {
+ case DCDExtra1(unk1, unk2) =>
+ unk1.slice(0, 80) :: unk2 :: HNil //max 80 characters
+ }
+ )
+
+ /**
+ * A common `Codec` for a `List` of `String` objects
+ * used for first time events list and for the tutorials list.
+ * The first entry contains a padded `String` so it must be processed different from the remainder.
+ * @param padFunc a curried function awaiting the extracted length of the current `List`
+ */
+ private def eventsListCodec(padFunc : (Long)=>Int) : Codec[List[String]] = (
+ uint32L >>:~ { size =>
+ conditional(size > 0, PacketHelpers.encodedStringAligned(padFunc(size))) ::
+ PacketHelpers.listOfNSized(size - 1, PacketHelpers.encodedString)
+ }
+ ).xmap[List[String]] (
+ {
+ case _ :: Some(first) :: Nil :: HNil =>
+ List(first)
+ case _ :: Some(first) :: rest :: HNil =>
+ first +: rest
+ case _ :: None :: _ :: HNil =>
+ List()
+ },
+ {
+ case List() =>
+ 0 :: None :: Nil :: HNil
+ case contents =>
+ contents.length :: contents.headOption :: contents.tail :: HNil
+ }
+ )
+
+ /**
+ * `Codec` for a `DCDExtra2` object.
+ */
+ private val dcd_extra2_codec : Codec[DCDExtra2] = (
+ uint(5) ::
+ uint8L
+ ).as[DCDExtra2]
+
+ /**
+ * `Codec` for a `List` of `String` objects.
+ * The first entry contains a padded `String` so it must be processed different from the remainder.
+ * The padding length is the conclusion of the summation of all the bits up until the point of this `String` object.
+ * Additionally, the length of this current string is also a necessary consideration.
+ * @see `paddingCalculations`
+ * @param padFunc a curried function awaiting the extracted length of the current `List` and will count the padding bits
+ */
+ private def unkBCodec(padFunc : (Long)=>Int) : Codec[List[String]] = (
+ uint16L >>:~ { size =>
+ conditional(size > 0, PacketHelpers.encodedStringAligned(padFunc(size))) ::
+ PacketHelpers.listOfNSized(size - 1, PacketHelpers.encodedString)
+ }
+ ).xmap[List[String]] (
+ {
+ case _ :: Some(first) :: Nil :: HNil =>
+ List(first)
+ case _ :: Some(first) :: rest :: HNil =>
+ first +: rest
+ case _ :: None :: _ :: HNil =>
+ List()
+ },
+ {
+ case List() =>
+ 0 :: None :: Nil :: HNil
+ case contents =>
+ contents.length :: contents.headOption :: contents.tail :: HNil
+ }
+ )
+
+ /**
+ * Suport function that obtains the "absolute list value" of an `Option` object.
+ * @param opt the `Option` object
+ * @return if defined, returns a `List` of the `Option` object's contents;
+ * if undefined (`None`), returns an empty list
+ */
+ def optToList(opt : Option[Any]) : List[Any] = opt match {
+ case Some(o) => List(o)
+ case None => Nil
+ }
+
+ /**
+ * A very specific `Option` object addition function.
+ * If a condition is met, the current `Optional` value is incremented by a specific amount.
+ * @param start the original amount
+ * @param test the test on whether to add to `start`
+ * @param value how much to add to `start`
+ * @return the amount after testing
+ */
+ def displaceByUnk9(start : Option[Int], test : Option[Any], value : Int) : Option[Int] = test match {
+ case Some(_) =>
+ Some(start.getOrElse(0) + value)
+ case None =>
+ start
+ }
+
+ /**
+ * A `List` of bit distances between different sets of `String` objects in the `DetailedCharacterData` `Codec`
+ * in reverse order of encountered `String` fields (later to earlier).
+ * The distances are not the actual lengths but are modulo eight.
+ * Specific strings include (the contents of):
+ * - `unk9` (as a `List` object)
+ * - `tutorials`
+ * - `firstTimeEvents`
+ * - `unk3`
+ * - `unk2`
+ */
+ private val displacementPerEntry : List[Int] = List(7, 0, 0, 0, 0)
+
+ /**
+ * A curried function to calculate a cumulative padding value
+ * for whichever of the groups of `List` objects of `String` objects are found in a `DetailedCharacterData` object.
+ * Defines the expected base value - the starting value for determining the padding.
+ * The specific `String` object being considered is determined by the number of input lists.
+ * @see `paddingCalculations(Int, Option[Int], List[ImplantEntry], List[List[Any]])(Long)`
+ * @param contextOffset an inherited modification of the `base` padding value
+ * @param implants the list of implants in the stream
+ * @param prevLists all of the important previous lists
+ * @param currListLen the length of the current list
+ * @return the padding value for the target list
+ */
+ def paddingCalculations(contextOffset : Option[Int], implants : List[ImplantEntry], prevLists : List[List[Any]])(currListLen : Long) : Int = {
+ paddingCalculations(3, contextOffset, implants, prevLists)(currListLen)
+ }
+
+ /**
+ * A curried function to calculate a cumulative padding value
+ * for whichever of the groups of `List` objects of `String` objects are found in a `DetailedCharacterData` object.
+ * The specific `String` object being considered is determined by the number of input lists.
+ * @see `paddingCalculations(Option[Int], List[ImplantEntry], List[List[Any/]/])(Long)`
+ * @param base the starting value with no implant entries, or bits from context
+ * @param contextOffset an inherited modification of the `base` padding value
+ * @param implants the list of implants in the stream
+ * @param prevLists all of the important previous lists
+ * @param currListLen the length of the current list
+ * @throws Exception if the number of input lists (`prevLists`) exceeds the number of expected bit distances between known lists
+ * @return the padding value for the target list;
+ * a value clamped between 0 and 7
+ */
+ def paddingCalculations(base : Int, contextOffset : Option[Int], implants : List[ImplantEntry], prevLists : List[List[Any]])(currListLen : Long) : Int = {
+ if(prevLists.length > displacementPerEntry.length) {
+ throw new Exception("mismatched number of input lists compared to bit distances")
+ }
+ else if(currListLen > 0) {
+ //displacement into next byte of the content field of the first relevant string without padding
+ val baseResult : Int = base + contextOffset.getOrElse(0) + implants.foldLeft(0L)(_ + _.bitsize).toInt
+ val displacementResult : Int = (if(prevLists.isEmpty) {
+ baseResult
+ }
+ else {
+ //isolate the displacements that are important
+ val sequentialEmptyLists : List[List[Any]] = prevLists.takeWhile(_.isEmpty)
+ val offsetSlice : List[Int] = displacementPerEntry.drop(displacementPerEntry.length - sequentialEmptyLists.length)
+ if(prevLists.length == sequentialEmptyLists.length) { //if all lists are empty, factor in the base displacement
+ baseResult + offsetSlice.sum
+ }
+ else {
+ offsetSlice.sum
+ }
+ }) % 8
+ if(displacementResult != 0) {
+ 8 - displacementResult
+ }
+ else {
+ 0
+ }
+ }
+ else {
+ 0 //if the current list has no length, there's no need to pad it
+ }
}
/**
@@ -212,92 +519,86 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
}
/**
- * Get the padding of the first entry in the first time events list.
- * @param len the length of the first time events list
- * @param implantPadding the padding that resulted from implant entries
- * @return the pad length in bits `0 <= n < 8`
+ * By comparing the battle experience points to a fixed number of points,
+ * determine if the player is at least battle rank 24.
+ * Important things happen if the player is at least battle rank 24 ...
+ * @param bep the battle experience points being compared
+ * @return `true`, if the battle experience points are enough to be a player of the esteemed battle rank
*/
- private def ftePadding(len : Long, implantPadding : Int) : Int = {
- //TODO the proper padding length should reflect all variability in the stream prior to this point
- if(len > 0) {
- implantPadding
- }
- else {
- 0
- }
- }
-
- /**
- * Get the padding of the first entry in the completed tutorials list.
- *
- * The tutorials list follows the first time event list and also contains byte-aligned strings.
- * If the both lists are populated or empty at the same time, the first entry will not need padding.
- * If the first time events list is unpopulated, but this list is populated, the first entry will need padding bits.
- * @param len the length of the first time events list
- * @param len2 the length of the tutorial list
- * @param implantPadding the padding that resulted from implant entries
- * @return the pad length in bits `n < 8`
- */
- private def tutPadding(len : Long, len2 : Long, implantPadding : Int) : Int = {
- if(len > 0) {
- 0 //automatic alignment from previous List
- }
- else if(len2 > 0) {
- implantPadding //need to align for elements
- }
- else {
- 0 //both lists are empty
- }
- }
-
def isBR24(bep : Long) : Boolean = bep > 2286230
- def codec(pad_length : Option[Int]) : Codec[DetailedCharacterData] = (
- ("bep" | uint32L) >>:~ { bep =>
+ val a_codec : Codec[DetailedCharacterA] = (
+ ("bep" | uint32L) ::
("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] (
+ ("unk1" | uint32L) ::
+ ("unk2" | uint32L) ::
+ ("unk3" | uint32L) ::
+ ("healthMax" | uint16L) ::
+ ("health" | uint16L) ::
+ ("unk4" | bool) ::
+ ("armor" | uint16L) ::
+ ("unk5" | uint32) :: //endianness?
+ ("staminaMax" | uint16L) ::
+ ("stamina" | uint16L) ::
+ conditional(false, uint32L) :: //see ps.c: sub_901150, line#1070692
+ ("unk6" | uint16L) ::
+ ("unk7" | uint(3)) ::
+ ("unk8" | uint32L) ::
+ ("unk9" | PacketHelpers.listOfNSized(6, uint16L)) :: //always length of 6
+ ("certs" | listOfN(uint8L, CertificationType.codec))
+ ).exmap[DetailedCharacterA] (
{
- 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(bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cosmetics)(pad_length))
+ case bep :: cep :: u1 :: u2 :: u3 :: healthMax :: health :: u4 :: armor :: u5 :: staminaMax :: stamina :: None :: u6 :: u7 :: u8 :: u9 :: certs :: HNil =>
+ Attempt.successful(DetailedCharacterA(bep, cep, u1, u2, u3, healthMax, health, u4, armor, u5, staminaMax, stamina, u6, u7, u8, u9, certs))
},
{
- case DetailedCharacterData(bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cos) =>
+ case DetailedCharacterA(bep, cep, u1, u2, u3, healthMax, health, u4, armor, u5, staminaMax, stamina, u6, u7, u8, u9, certs) =>
+ Attempt.successful(
+ bep :: cep :: u1 :: u2 :: u3 :: healthMax :: health :: u4 :: armor :: u5 :: staminaMax :: stamina :: None :: u6 :: u7 :: u8 :: u9 :: certs :: HNil
+ )
+ }
+ )
+
+ def b_codec(bep : Long, pad_length : Option[Int]) : Codec[DetailedCharacterB] = (
+ optional(bool, "unk1" | uint32L) ::
+ (("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants =>
+ ("unk2" | dcd_list_codec(paddingCalculations(pad_length, implants, Nil))) >>:~ { unk2 =>
+ ("unk3" | dcd_list_codec(paddingCalculations(pad_length, implants, List(unk2)))) >>:~ { unk3 =>
+ ("firstTimeEvents" | eventsListCodec(paddingCalculations(pad_length, implants, List(unk3, unk2)))) >>:~ { fte =>
+ ("tutorials" | eventsListCodec(paddingCalculations(pad_length, implants, List(fte, unk3, unk2)))) >>:~ { tut =>
+ ("unk4" | uint32L) ::
+ ("unk5" | uint32L) ::
+ ("unk6" | uint32L) ::
+ ("unk7" | uint32L) ::
+ ("unk8" | uint32L) ::
+ (bool >>:~ { br24 => //BR24+
+ conditional(!br24, "unk9" | dcd_extra2_codec) >>:~ { unk9 =>
+ ("unkA" | listOfN(uint16L, uint32L)) ::
+ ("unkB" | unkBCodec(
+ paddingCalculations(
+ displaceByUnk9(pad_length, unk9, 5),
+ implants,
+ List(optToList(unk9), tut, fte, unk3, unk2)
+ )
+ )) ::
+ ("unkC" | bool) ::
+ conditional(br24, "cosmetics" | Cosmetics.codec)
+ }
+ })
+ }
+ }
+ }
+ }
+ })
+ ).exmap[DetailedCharacterB] (
+ {
+ case u1 :: implants :: u2 :: u3 :: fte :: tut :: u4 :: u5 :: u6 :: u7 :: u8 :: _ :: u9 :: uA :: uB :: uC :: cosmetics :: HNil =>
+ Attempt.successful(
+ DetailedCharacterB(u1, implants, u2, u3, fte, tut, u4, u5, u6, u7, u8, u9, uA, uB, uC, cosmetics)(bep, pad_length)
+ )
+ },
+ {
+ case DetailedCharacterB(u1, implants, u2, u3, fte, tut, u4, u5, u6, u7, u8, u9, uA, uB, uC, cosmetics) =>
val implantCapacity : Int = numberOfImplantSlots(bep)
val implantList = if(implants.length > implantCapacity) {
implants.slice(0, implantCapacity)
@@ -305,20 +606,26 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
else {
recursiveEnsureImplantSlots(implantCapacity, implants)
}
- //shift the first elements off their lists
- val (firstEvent, fteListCopy) = fteList match {
- case (f : String) +: Nil => (Some(f), Nil)
- case ((f : String) +: (rest : List[String])) => (Some(f), rest)
- case Nil => (None, Nil)
- }
- val (firstTutorial, tutListCopy) = tutList match {
- case (f : String) +: Nil => (Some(f), Nil)
- case ((f : String) +: (rest : List[String])) => (Some(f), rest)
- case Nil => (None, Nil)
- }
val br24 : Boolean = isBR24(bep)
- val cosmetics : Option[Cosmetics] = if(br24) { cos } else { None }
- 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)
+ val cos : Option[Cosmetics] = if(br24) { cosmetics } else { None }
+ Attempt.successful(
+ u1 :: implantList :: u2 :: u3 :: fte :: tut :: u4 :: u5 :: u6 :: u7 :: u8 :: br24 :: u9 :: uA :: uB :: uC :: cos :: HNil
+ )
+ }
+ )
+
+ def codec(pad_length : Option[Int]) : Codec[DetailedCharacterData] = (
+ ("a" | a_codec) >>:~ { a =>
+ ("b" | b_codec(a.bep, pad_length)).hlist
+ }
+ ).exmap[DetailedCharacterData] (
+ {
+ case a :: b :: HNil =>
+ Attempt.successful(DetailedCharacterData(a, b)(pad_length))
+ },
+ {
+ case DetailedCharacterData(a, b) =>
+ Attempt.successful(a :: b :: HNil)
}
)
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedPlayerData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedPlayerData.scala
index 20b6f3e49..071674822 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedPlayerData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedPlayerData.scala
@@ -21,15 +21,16 @@ import shapeless.{::, HNil}
* 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`
- * `InventoryData`
- * `DrawnSlot`
+ * @see `CharacterAppearanceData`
+ * @see `DetailedCharacterData`
+ * @see `InventoryData`
+ * @see `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
+ * @param character_data the class-specific data that discusses the character
+ * @param position_defined used to seed the state of the optional position fields
+ * @param inventory the player's full or partial (holsters-only) inventory
+ * @param drawn_slot the holster that is depicted as exposed, or "drawn"
*/
final case class DetailedPlayerData(pos : Option[PlacementData],
basic_appearance : CharacterAppearanceData,
@@ -54,8 +55,8 @@ object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
* This constructor should be used for players that are mounted.
* @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;
+ * @param inventory the player's full or partial (holsters-only) inventory
+ * @param drawn_slot the holster that is depicted as exposed, or "drawn";
* technically, always `DrawnSlot.None`, but the field is preserved to maintain similarity
* @return a `DetailedPlayerData` object
*/
@@ -70,7 +71,7 @@ object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
* This constructor should be used for players that are mounted.
* @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 drawn_slot the holster that is initially drawn;
+ * @param drawn_slot the holster that is depicted as exposed, or "drawn;"
* technically, always `DrawnSlot.None`, but the field is preserved to maintain similarity
* @return a `DetailedPlayerData` object
*/
@@ -86,8 +87,8 @@ object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
* @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
+ * @param inventory the player's full or partial (holsters-only) inventory
+ * @param drawn_slot the holster that is depicted as exposed, or "drawn"
* @return a `DetailedPlayerData` object
*/
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
@@ -102,7 +103,7 @@ object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
* @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 drawn_slot the holster that is initially drawn
+ * @param drawn_slot the holster that is depicted as exposed, or "drawn"
* @return a `DetailedPlayerData` object
*/
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/PlayerData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/PlayerData.scala
index 7452d1be1..12b398d8a 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/PlayerData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/PlayerData.scala
@@ -65,7 +65,7 @@ object PlayerData extends Marshallable[PlayerData] {
*/
def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type) : PlayerData = {
val appearance = basic_appearance(5)
- PlayerData(None, appearance, character_data(appearance.backpack, true), Some(inventory), drawn_slot)(false)
+ PlayerData(None, appearance, character_data(appearance.a.altModel, true), Some(inventory), drawn_slot)(false)
}
/**
* Overloaded constructor that ignores the coordinate information and the inventory.
@@ -79,7 +79,7 @@ object PlayerData extends Marshallable[PlayerData] {
*/
def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, drawn_slot : DrawnSlot.Type) : PlayerData = {
val appearance = basic_appearance(5)
- PlayerData(None, appearance, character_data(appearance.backpack, true), None, drawn_slot)(false)
+ PlayerData(None, appearance, character_data(appearance.a.altModel, true), None, drawn_slot)(false)
}
/**
@@ -95,7 +95,7 @@ object PlayerData extends Marshallable[PlayerData] {
*/
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type) : PlayerData = {
val appearance = basic_appearance( PaddingOffset(Some(pos)) )
- PlayerData(Some(pos), appearance, character_data(appearance.backpack, false), Some(inventory), drawn_slot)(true)
+ PlayerData(Some(pos), appearance, character_data(appearance.a.altModel, false), Some(inventory), drawn_slot)(true)
}
/**
* Overloaded constructor that includes the coordinate information but ignores the inventory.
@@ -109,7 +109,7 @@ object PlayerData extends Marshallable[PlayerData] {
*/
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, drawn_slot : DrawnSlot.Type) : PlayerData = {
val appearance = basic_appearance( PaddingOffset(Some(pos)) )
- PlayerData(Some(pos), appearance, character_data(appearance.backpack, false), None, drawn_slot)(true)
+ PlayerData(Some(pos), appearance, character_data(appearance.a.altModel, false), None, drawn_slot)(true)
}
/**
@@ -166,8 +166,8 @@ object PlayerData extends Marshallable[PlayerData] {
conditional(position_defined, "pos" | PlacementData.codec) >>:~ { pos =>
("basic_appearance" | CharacterAppearanceData.codec(PaddingOffset(pos))) >>:~ { app =>
("character_data" | newcodecs.binary_choice(position_defined,
- CharacterData.codec(app.backpack),
- CharacterData.codec_seated(app.backpack))) ::
+ CharacterData.codec(app.b.backpack),
+ CharacterData.codec_seated(app.b.backpack))) ::
optional(bool, "inventory" | InventoryData.codec) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false
@@ -193,7 +193,7 @@ object PlayerData extends Marshallable[PlayerData] {
*/
def codec(offset : Int) : Codec[PlayerData] = (
("basic_appearance" | CharacterAppearanceData.codec(offset)) >>:~ { app =>
- ("character_data" | CharacterData.codec_seated(app.backpack)) ::
+ ("character_data" | CharacterData.codec_seated(app.b.backpack)) ::
optional(bool, "inventory" | InventoryData.codec) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala
index 773e9bb14..a3d271c89 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala
@@ -165,7 +165,7 @@ object VehicleData extends Marshallable[VehicleData] {
*/
def PlayerData(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type, accumulative : Long) : Player_Data = {
val appearance = basic_appearance(CumulativeSeatedPlayerNamePadding(accumulative))
- Player_Data(None, appearance, character_data(appearance.backpack, true), Some(inventory), drawn_slot)(false)
+ Player_Data(None, appearance, character_data(appearance.b.backpack, true), Some(inventory), drawn_slot)(false)
}
/**
* Constructor for `PlayerData` that ignores the coordinate information and the inventory
@@ -181,7 +181,7 @@ object VehicleData extends Marshallable[VehicleData] {
*/
def PlayerData(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, drawn_slot : DrawnSlot.Type, accumulative : Long) : Player_Data = {
val appearance = basic_appearance(CumulativeSeatedPlayerNamePadding(accumulative))
- Player_Data.apply(None, appearance, character_data(appearance.backpack, true), None, drawn_slot)(false)
+ Player_Data.apply(None, appearance, character_data(appearance.b.backpack, true), None, drawn_slot)(false)
}
private val driveState8u = PacketHelpers.createEnumerationCodec(DriveState, uint8L)
diff --git a/common/src/test/scala/game/objectcreate/CharacterDataTest.scala b/common/src/test/scala/game/objectcreate/CharacterDataTest.scala
index 6115f2311..d0d14d1b6 100644
--- a/common/src/test/scala/game/objectcreate/CharacterDataTest.scala
+++ b/common/src/test/scala/game/objectcreate/CharacterDataTest.scala
@@ -33,41 +33,67 @@ class CharacterDataTest extends Specification {
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 CharacterVoice.Voice5
- 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
+ basic match {
+ case CharacterAppearanceData(a, b, ribbons) =>
+ a.app.name mustEqual "ScrawnyRonnie"
+ a.app.faction mustEqual PlanetSideEmpire.TR
+ a.app.sex mustEqual CharacterGender.Male
+ a.app.head mustEqual 5
+ a.app.voice mustEqual CharacterVoice.Voice5
+ a.black_ops mustEqual false
+ a.jammered mustEqual false
+ a.exosuit mustEqual ExoSuitType.Reinforced
+ a.unk1 mustEqual false
+ a.unk2 mustEqual None
+ a.unk3 mustEqual None
+ a.unk4 mustEqual 0
+ a.unk5 mustEqual 0
+ a.unk6 mustEqual 30777081L
+ a.unk7 mustEqual 1
+ a.unk8 mustEqual 4
+ a.unk9 mustEqual 0
+ a.unkA mustEqual 0
+
+ b.outfit_name mustEqual "Black Beret Armoured Corps"
+ b.outfit_logo mustEqual 23
+ b.backpack mustEqual false
+ b.facingPitch mustEqual 320.625f
+ b.facingYawUpper mustEqual 0
+ b.lfs mustEqual false
+ b.grenade_state mustEqual GrenadeState.None
+ b.is_cloaking mustEqual false
+ b.charging_pose mustEqual false
+ b.on_zipline mustEqual None
+ b.unk0 mustEqual 316554L
+ b.unk1 mustEqual false
+ b.unk2 mustEqual false
+ b.unk3 mustEqual false
+ b.unk4 mustEqual false
+ b.unk5 mustEqual false
+ b.unk6 mustEqual false
+ b.unk7 mustEqual false
+
+ ribbons.upper mustEqual MeritCommendation.MarkovVeteran
+ ribbons.middle mustEqual MeritCommendation.HeavyInfantry4
+ ribbons.lower mustEqual MeritCommendation.TankBuster7
+ ribbons.tos mustEqual MeritCommendation.SixYearTR
+ case _ =>
+ ko
+ }
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.implant_effects.length mustEqual 1
+ char.implant_effects.head 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
+ char.unk mustEqual 7
//short test of inventory items
inv.isDefined mustEqual true
val contents = inv.get.contents
@@ -116,7 +142,7 @@ class CharacterDataTest extends Specification {
ko
}
}
-
+//
"decode (seated)" in {
PacketCoding.DecodePacket(string_seated).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
@@ -126,29 +152,54 @@ class CharacterDataTest extends Specification {
parent mustEqual Some(ObjectCreateMessageParent(PlanetSideGUID(1234), 0))
data match {
case Some(PlayerData(None, basic, char, inv, hand)) =>
- 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 CharacterVoice.Voice5
- 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
- //etc..
+ basic match {
+ case CharacterAppearanceData(a, b, ribbons) =>
+ a.app.name mustEqual "ScrawnyRonnie"
+ a.app.faction mustEqual PlanetSideEmpire.TR
+ a.app.sex mustEqual CharacterGender.Male
+ a.app.head mustEqual 5
+ a.app.voice mustEqual CharacterVoice.Voice5
+ a.black_ops mustEqual false
+ a.jammered mustEqual false
+ a.exosuit mustEqual ExoSuitType.Reinforced
+ a.unk1 mustEqual false
+ a.unk2 mustEqual None
+ a.unk3 mustEqual None
+ a.unk4 mustEqual 0
+ a.unk5 mustEqual 0
+ a.unk6 mustEqual 192L
+ a.unk7 mustEqual 0
+ a.unk8 mustEqual 0
+ a.unk9 mustEqual 0
+ a.unkA mustEqual 0
+
+ b.outfit_name mustEqual "Black Beret Armoured Corps"
+ b.outfit_logo mustEqual 23
+ b.backpack mustEqual false
+ b.facingPitch mustEqual 320.625f
+ b.facingYawUpper mustEqual 0
+ b.lfs mustEqual false
+ b.grenade_state mustEqual GrenadeState.None
+ b.is_cloaking mustEqual false
+ b.charging_pose mustEqual false
+ b.on_zipline mustEqual None
+ b.unk0 mustEqual 26L
+ b.unk1 mustEqual false
+ b.unk2 mustEqual false
+ b.unk3 mustEqual false
+ b.unk4 mustEqual false
+ b.unk5 mustEqual false
+ b.unk6 mustEqual false
+ b.unk7 mustEqual false
+
+ ribbons.upper mustEqual MeritCommendation.MarkovVeteran
+ ribbons.middle mustEqual MeritCommendation.HeavyInfantry4
+ ribbons.lower mustEqual MeritCommendation.TankBuster7
+ ribbons.tos mustEqual MeritCommendation.SixYearTR
+ //etc..
+ case _ =>
+ ko
+ }
case _ =>
ko
}
@@ -170,40 +221,67 @@ class CharacterDataTest extends Specification {
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 CharacterVoice.Voice2
- 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
+ basic match {
+ case CharacterAppearanceData(a, b, ribbons) =>
+ a.app.name mustEqual "Angello"
+ a.app.faction mustEqual PlanetSideEmpire.VS
+ a.app.sex mustEqual CharacterGender.Male
+ a.app.head mustEqual 10
+ a.app.voice mustEqual CharacterVoice.Voice2
+ a.black_ops mustEqual false
+ a.jammered mustEqual false
+ a.exosuit mustEqual ExoSuitType.MAX
+ a.unk1 mustEqual false
+ a.unk2 mustEqual None
+ a.unk3 mustEqual None
+ a.unk4 mustEqual 0
+ a.unk5 mustEqual 1
+ a.unk6 mustEqual 0L
+ a.unk7 mustEqual 0
+ a.unk8 mustEqual 0
+ a.unk9 mustEqual 0
+ a.unkA mustEqual 0
+
+ b.outfit_name mustEqual "Original District"
+ b.outfit_logo mustEqual 23
+ b.backpack mustEqual true
+ b.facingPitch mustEqual 351.5625f
+ b.facingYawUpper mustEqual 0
+ b.lfs mustEqual false
+ b.grenade_state mustEqual GrenadeState.None
+ b.is_cloaking mustEqual false
+ b.charging_pose mustEqual false
+ b.on_zipline mustEqual None
+ b.unk0 mustEqual 529687L
+ b.unk1 mustEqual false
+ b.unk2 mustEqual false
+ b.unk3 mustEqual false
+ b.unk4 mustEqual false
+ b.unk5 mustEqual false
+ b.unk6 mustEqual false
+ b.unk7 mustEqual false
+
+ ribbons.upper mustEqual MeritCommendation.Jacking2
+ ribbons.middle mustEqual MeritCommendation.ScavengerVS1
+ ribbons.lower mustEqual MeritCommendation.AMSSupport4
+ ribbons.tos mustEqual MeritCommendation.SixYearVS
+ //etc..
+ case _ =>
+ ko
+ }
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.implant_effects.isEmpty mustEqual true
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
+ char.unk mustEqual 1
hand mustEqual DrawnSlot.Pistol1
case _ =>
@@ -220,7 +298,7 @@ class CharacterDataTest extends Specification {
Vector3(0f, 0f, 64.6875f),
Some(Vector3(1.4375f, -0.4375f, 0f))
)
- val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
+ val a : Int=>CharacterAppearanceA = CharacterAppearanceA(
BasicCharacterData(
"ScrawnyRonnie",
PlanetSideEmpire.TR,
@@ -228,17 +306,43 @@ class CharacterDataTest extends Specification {
5,
CharacterVoice.Voice5
),
- 3,
false,
false,
+ false,
+ None,
+ false,
ExoSuitType.Reinforced,
+ None,
+ 0,
+ 0,
+ 30777081L,
+ 1,
+ 4,
+ 0,
+ 0
+ )
+ val b : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
+ 316554L,
"Black Beret Armoured Corps",
23,
false,
- 340.3125f, 0f,
+ false,
+ false,
+ false,
+ false,
+ 320.625f, 0f,
false,
GrenadeState.None,
- false, false, false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ None
+ )
+
+ val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
+ a, b,
RibbonBars(
MeritCommendation.MarkovVeteran,
MeritCommendation.HeavyInfantry4,
@@ -249,8 +353,9 @@ class CharacterDataTest extends Specification {
val char : (Boolean,Boolean)=>CharacterData = CharacterData(
255, 253,
UniformStyle.ThirdUpgrade,
+ 7,
5,
- Some(ImplantEffects.NoEffects),
+ List(ImplantEffects.NoEffects),
Some(Cosmetics(true, true, true, true, false))
)
val inv = InventoryData(
@@ -265,18 +370,11 @@ class CharacterDataTest extends Specification {
val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3902), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
- val pkt_bitv = pkt.toBitVector
- val ori_bitv = string.toBitVector
- pkt_bitv.take(452) mustEqual ori_bitv.take(452) //skip 126
- pkt_bitv.drop(578).take(438) mustEqual ori_bitv.drop(578).take(438) //skip 2
- pkt_bitv.drop(1018).take(17) mustEqual ori_bitv.drop(1018).take(17) //skip 11
- pkt_bitv.drop(1046).take(147) mustEqual ori_bitv.drop(1046).take(147) //skip 3
- pkt_bitv.drop(1196) mustEqual ori_bitv.drop(1196)
- //TODO work on CharacterData to make this pass as a single stream
+ pkt mustEqual string
}
"encode (seated)" in {
- val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
+ val a : Int=>CharacterAppearanceA = CharacterAppearanceA(
BasicCharacterData(
"ScrawnyRonnie",
PlanetSideEmpire.TR,
@@ -284,17 +382,43 @@ class CharacterDataTest extends Specification {
5,
CharacterVoice.Voice5
),
- 3,
false,
false,
+ false,
+ None,
+ false,
ExoSuitType.Reinforced,
+ None,
+ 0,
+ 0,
+ 192L,
+ 0,
+ 0,
+ 0,
+ 0
+ )
+ val b : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
+ 26L,
"Black Beret Armoured Corps",
23,
false,
- 340.3125f, 0f,
+ false,
+ false,
+ false,
+ false,
+ 320.625f, 0f,
false,
GrenadeState.None,
- false, false, false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ None
+ )
+
+ val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
+ a, b,
RibbonBars(
MeritCommendation.MarkovVeteran,
MeritCommendation.HeavyInfantry4,
@@ -306,7 +430,7 @@ class CharacterDataTest extends Specification {
255, 253,
UniformStyle.ThirdUpgrade,
5,
- Some(ImplantEffects.NoEffects),
+ List(ImplantEffects.NoEffects),
Some(Cosmetics(true, true, true, true, false))
)
val inv = InventoryData(
@@ -329,7 +453,7 @@ class CharacterDataTest extends Specification {
Vector3(4629.8906f, 6316.4453f, 54.734375f),
Vector3(0, 0, 126.5625f)
)
- val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
+ val a : Int=>CharacterAppearanceA = CharacterAppearanceA(
BasicCharacterData(
"Angello",
PlanetSideEmpire.VS,
@@ -337,17 +461,43 @@ class CharacterDataTest extends Specification {
10,
CharacterVoice.Voice2
),
- 0,
false,
+ true,
+ false,
+ None,
false,
ExoSuitType.MAX,
+ None,
+ 0,
+ 1,
+ 0L,
+ 0,
+ 0,
+ 0,
+ 0
+ )
+ val b : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
+ 529687L,
"Original District",
23,
+ false, //unk1
true, //backpack
- 0f, 180.0f,
- false,
+ false, //unk2
+ false, //unk3
+ false, //unk4
+ 351.5625f, 0f,
+ false, //lfs
GrenadeState.None,
- false, false, false,
+ false, //is_cloaking
+ false, //unk5
+ false, //unk6
+ false, //charging_pose
+ false, //unk7
+ None
+ )
+
+ val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
+ a, b,
RibbonBars(
MeritCommendation.Jacking2,
MeritCommendation.ScavengerVS1,
@@ -358,22 +508,19 @@ class CharacterDataTest extends Specification {
val char : (Boolean,Boolean)=>CharacterData = CharacterData(
0, 0,
UniformStyle.ThirdUpgrade,
- 2,
- None,
+ 1,
+ List(),
Some(Cosmetics(true, true, true, true, false))
)
val obj = PlayerData(pos, app, char, DrawnSlot.Pistol1)
val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3380), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+ //granular test
val pkt_bitv = pkt.toBitVector
val ori_bitv = string_backpack.toBitVector
- pkt_bitv.take(300) mustEqual ori_bitv.take(300) //skip 2
- pkt_bitv.drop(302).take(14) mustEqual ori_bitv.drop(302).take(14) //skip 126
- pkt_bitv.drop(442).take(305) mustEqual ori_bitv.drop(442).take(305) //skip 1
- pkt_bitv.drop(748).take(9) mustEqual ori_bitv.drop(748).take(9) // skip 2
- pkt_bitv.drop(759).take(157) mustEqual ori_bitv.drop(759).take(157) //skip 1
- pkt_bitv.drop(917) mustEqual ori_bitv.drop(917)
+ pkt_bitv.take(916) mustEqual pkt_bitv.take(916) //skip 4
+ pkt_bitv.drop(920) mustEqual pkt_bitv.drop(920)
//TODO work on CharacterData to make this pass as a single stream
}
}
diff --git a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala
index b571749be..34f8999d1 100644
--- a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala
+++ b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala
@@ -19,6 +19,7 @@ class DetailedCharacterDataTest extends Specification {
hex"82880000020000c041c09e01019000006400442a001091000000400018083894402032000000801905480217200000080070298043640000" ++
hex"32000e0540089c8000064001c0aa0119900000c8003a1580287200001900040ab805264000032006c25800a7880000020000800000"
val string_br32 = hex"18 2c e0 00 00 bc 84 B0 00 0b ea 00 6c 7d f1 10 00 00 02 40 00 08 60 4b 00 69 00 43 00 6b 00 4a 00 72 00 02 31 3a cc 82 c0 00 00 00 00 00 00 00 00 3e df 42 00 20 00 0e 00 40 43 40 4c 04 00 02 e8 00 00 03 a8 00 00 01 9c 04 00 00 b8 99 84 00 0e 68 28 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 c8 00 00 01 00 7e c8 00 5c 00 00 01 29 c1 cc 80 00 00 00 00 00 00 00 00 00 00 00 00 03 c0 00 40 81 01 c4 45 46 86 c8 88 c9 09 4a 4a 80 50 0c 13 00 00 15 00 80 00 48 00 7870655f6f766572686561645f6d6170 8d7870655f776172705f676174658f7870655f666f726d5f6f75746669748c7870655f626c61636b6f7073927870655f636f6d6d616e645f72616e6b5f35927870655f636f6d6d616e645f72616e6b5f33927870655f73616e6374756172795f68656c70927870655f626174746c655f72616e6b5f3133927870655f626174746c655f72616e6b5f3132927870655f626174746c655f72616e6b5f3130927870655f626174746c655f72616e6b5f3134927870655f626174746c655f72616e6b5f3135937870655f6f72626974616c5f73687574746c658c7870655f64726f705f706f64917870655f62696e645f666163696c697479917870655f626174746c655f72616e6b5f33917870655f626174746c655f72616e6b5f35917870655f626174746c655f72616e6b5f348e7870655f6a6f696e5f73717561648e7870655f666f726d5f7371756164927870655f696e7374616e745f616374696f6e917870655f626174746c655f72616e6b5f32937870655f776172705f676174655f7573616765917870655f626174746c655f72616e6b5f38927870655f626174746c655f72616e6b5f3131917870655f626174746c655f72616e6b5f368e7870655f6d61696c5f616c657274927870655f636f6d6d616e645f72616e6b5f31927870655f626174746c655f72616e6b5f3230927870655f626174746c655f72616e6b5f3138927870655f626174746c655f72616e6b5f3139907870655f6a6f696e5f706c61746f6f6e927870655f626174746c655f72616e6b5f3137927870655f626174746c655f72616e6b5f31368f7870655f6a6f696e5f6f7574666974927870655f626174746c655f72616e6b5f3235927870655f626174746c655f72616e6b5f3234927870655f636f6d6d616e645f72616e6b5f34907870655f666f726d5f706c61746f6f6e8c7870655f62696e645f616d73917870655f626174746c655f72616e6b5f39917870655f626174746c655f72616e6b5f378d7870655f74685f726f757465728c7870655f74685f666c61696c8a7870655f74685f616e748a7870655f74685f616d738f7870655f74685f67726f756e645f708c7870655f74685f6169725f708c7870655f74685f686f7665728d7870655f74685f67726f756e648a7870655f74685f626672927870655f74685f61667465726275726e65728a7870655f74685f6169728c7870655f74685f636c6f616b89757365645f6f69637791757365645f616476616e6365645f61636597766973697465645f73706974666972655f74757272657498766973697465645f73706974666972655f636c6f616b656493766973697465645f73706974666972655f616192766973697465645f74616e6b5f7472617073a1766973697465645f706f727461626c655f6d616e6e65645f7475727265745f6e63a1766973697465645f706f727461626c655f6d616e6e65645f7475727265745f74728e757365645f6d61676375747465728f757365645f636861696e626c6164658f757365645f666f726365626c61646593766973697465645f77616c6c5f74757272657498766973697465645f616e6369656e745f7465726d696e616c8b766973697465645f616d738b766973697465645f616e7490766973697465645f64726f707368697091766973697465645f6c6962657261746f7294766973697465645f6c6967687467756e7368697091766973697465645f6c696768746e696e6790766973697465645f6d616772696465728f766973697465645f70726f776c657293766973697465645f71756164737465616c746890766973697465645f736b7967756172649a766973697465645f74687265656d616e686561767962756767799d766973697465645f74776f5f6d616e5f61737361756c745f627567677998766973697465645f74776f6d616e6865617679627567677998766973697465645f74776f6d616e686f766572627567677990766973697465645f76616e67756172648d766973697465645f666c61696c8e766973697465645f726f7574657293766973697465645f737769746368626c6164658e766973697465645f6175726f726193766973697465645f626174746c657761676f6e8c766973697465645f6675727993766973697465645f7175616461737361756c7496766973697465645f67616c6178795f67756e736869708e766973697465645f6170635f74728e766973697465645f6170635f767390766973697465645f6c6f64657374617290766973697465645f7068616e7461736d91766973697465645f7468756e64657265728e766973697465645f6170635f6e638f766973697465645f76756c747572658c766973697465645f7761737090766973697465645f6d6f73717569746f97766973697465645f617068656c696f6e5f666c6967687497766973697465645f617068656c696f6e5f67756e6e657297766973697465645f636f6c6f737375735f666c6967687497766973697465645f636f6c6f737375735f67756e6e657298766973697465645f706572656772696e655f666c6967687498766973697465645f706572656772696e655f67756e6e657289757365645f62616e6b95766973697465645f7265736f757263655f73696c6f9e766973697465645f63657274696669636174696f6e5f7465726d696e616c94766973697465645f6d65645f7465726d696e616c93757365645f6e616e6f5f64697370656e73657295766973697465645f73656e736f725f736869656c649a766973697465645f62726f6164636173745f77617270676174658c757365645f7068616c616e7894757365645f7068616c616e785f6176636f6d626f96757365645f7068616c616e785f666c616b636f6d626f96766973697465645f77617270676174655f736d616c6c91757365645f666c616d657468726f7765729a757365645f616e6369656e745f7475727265745f776561706f6e92766973697465645f4c4c555f736f636b657492757365645f656e657267795f67756e5f6e6397766973697465645f6d656469756d7472616e73706f72749f757365645f617068656c696f6e5f696d6d6f6c6174696f6e5f63616e6e6f6e93757365645f6772656e6164655f706c61736d6193757365645f6772656e6164655f6a616d6d657298766973697465645f736869656c645f67656e657261746f7295766973697465645f6d6f74696f6e5f73656e736f7296766973697465645f6865616c74685f6372797374616c96766973697465645f7265706169725f6372797374616c97766973697465645f76656869636c655f6372797374616c91757365645f6772656e6164655f6672616788757365645f61636598766973697465645f6164765f6d65645f7465726d696e616c8b757365645f6265616d657290757365645f626f6c745f6472697665728b757365645f6379636c65728a757365645f676175737391757365645f68756e7465727365656b657288757365645f6973708b757365645f6c616e6365728b757365645f6c61736865728e757365645f6d61656c7374726f6d8c757365645f70686f656e69788b757365645f70756c7361728d757365645f70756e69736865728e757365645f725f73686f7467756e8d757365645f7261646961746f7288757365645f72656b8d757365645f72657065617465728c757365645f726f636b6c65748c757365645f737472696b65728f757365645f73757070726573736f728c757365645f7468756d7065729c766973697465645f76616e755f636f6e74726f6c5f636f6e736f6c6598766973697465645f636170747572655f7465726d696e616c92757365645f6d696e695f636861696e67756e91757365645f6c617a655f706f696e7465728c757365645f74656c657061648b757365645f7370696b657291757365645f68656176795f736e6970657293757365645f636f6d6d616e645f75706c696e6b8d757365645f66697265626972648e757365645f666c6563686574746594757365645f68656176795f7261696c5f6265616d89757365645f696c63399a766973697465645f67656e657261746f725f7465726d696e616c8e766973697465645f6c6f636b65729a766973697465645f65787465726e616c5f646f6f725f6c6f636b9c766973697465645f6169725f76656869636c655f7465726d696e616c97766973697465645f67616c6178795f7465726d696e616c98766973697465645f696d706c616e745f7465726d696e616c99766973697465645f7365636f6e646172795f6361707475726590757365645f32356d6d5f63616e6e6f6e99757365645f6c6962657261746f725f626f6d6261726469657293766973697465645f7265706169725f73696c6f93766973697465645f76616e755f6d6f64756c6591757365645f666c61696c5f776561706f6e8b757365645f73637974686598766973697465645f7265737061776e5f7465726d696e616c8c757365645f62616c6c67756e92757365645f656e657267795f67756e5f747295757365645f616e6e69766572736172795f67756e6195757365645f616e6e69766572736172795f67756e6294757365645f616e6e69766572736172795f67756e90757365645f37356d6d5f63616e6e6f6e92757365645f6170635f6e635f776561706f6e92757365645f6170635f74725f776561706f6e92757365645f6170635f76735f776561706f6e90757365645f666c75785f63616e6e6f6e9f757365645f617068656c696f6e5f706c61736d615f726f636b65745f706f6491757365645f617068656c696f6e5f7070618c757365645f666c7578706f6494766973697465645f6266725f7465726d696e616c9e757365645f636f6c6f737375735f636c75737465725f626f6d625f706f64a0757365645f636f6c6f737375735f6475616c5f3130306d6d5f63616e6e6f6e7399757365645f636f6c6f737375735f74616e6b5f63616e6e6f6e96766973697465645f656e657267795f6372797374616c9b757365645f68656176795f6772656e6164655f6c61756e6368657298757365645f33356d6d5f726f74617279636861696e67756e8b757365645f6b6174616e6190757365645f33356d6d5f63616e6e6f6e93757365645f7265617665725f776561706f6e7396757365645f6c696768746e696e675f776561706f6e738c757365645f6d65645f61707090757365645f32306d6d5f63616e6e6f6e98766973697465645f6d6f6e6f6c6974685f616d657269736899766973697465645f6d6f6e6f6c6974685f636572797368656e97766973697465645f6d6f6e6f6c6974685f637973736f7297766973697465645f6d6f6e6f6c6974685f6573616d697299766973697465645f6d6f6e6f6c6974685f666f72736572616c99766973697465645f6d6f6e6f6c6974685f697368756e64617298766973697465645f6d6f6e6f6c6974685f7365617268757397766973697465645f6d6f6e6f6c6974685f736f6c73617292757365645f6e635f6865765f66616c636f6e99757365645f6e635f6865765f7363617474657263616e6e6f6e93757365645f6e635f6865765f73706172726f7791757365645f61726d6f725f736970686f6e9f757365645f706572656772696e655f6475616c5f6d616368696e655f67756e9f757365645f706572656772696e655f6475616c5f726f636b65745f706f647399757365645f706572656772696e655f6d65636868616d6d65729e757365645f706572656772696e655f7061727469636c655f63616e6e6f6e96757365645f706572656772696e655f73706172726f7791757365645f3130356d6d5f63616e6e6f6e92757365645f31356d6d5f636861696e67756ea0757365645f70756c7365645f7061727469636c655f616363656c657261746f7293757365645f726f74617279636861696e67756e9f766973697465645f6465636f6e737472756374696f6e5f7465726d696e616c95757365645f736b7967756172645f776561706f6e7391766973697465645f67656e657261746f7291757365645f67617573735f63616e6e6f6e89757365645f7472656b95757365645f76616e67756172645f776561706f6e73a4766973697465645f616e6369656e745f6169725f76656869636c655f7465726d696e616ca2766973697465645f616e6369656e745f65717569706d656e745f7465726d696e616c96766973697465645f6f726465725f7465726d696e616ca7766973697465645f616e6369656e745f67726f756e645f76656869636c655f7465726d696e616c9f766973697465645f67726f756e645f76656869636c655f7465726d696e616c97757365645f76756c747572655f626f6d6261726469657298757365645f76756c747572655f6e6f73655f63616e6e6f6e98757365645f76756c747572655f7461696c5f63616e6e6f6e97757365645f776173705f776561706f6e5f73797374656d91766973697465645f636861726c6965303191766973697465645f636861726c6965303291766973697465645f636861726c6965303391766973697465645f636861726c6965303491766973697465645f636861726c6965303591766973697465645f636861726c6965303691766973697465645f636861726c6965303791766973697465645f636861726c6965303891766973697465645f636861726c6965303996766973697465645f67696e6765726d616e5f6174617298766973697465645f67696e6765726d616e5f646168616b6196766973697465645f67696e6765726d616e5f6876617296766973697465645f67696e6765726d616e5f697a686199766973697465645f67696e6765726d616e5f6a616d7368696498766973697465645f67696e6765726d616e5f6d697468726198766973697465645f67696e6765726d616e5f726173686e7599766973697465645f67696e6765726d616e5f7372616f73686198766973697465645f67696e6765726d616e5f79617a61746195766973697465645f67696e6765726d616e5f7a616c8e766973697465645f736c656430318e766973697465645f736c656430328e766973697465645f736c656430348e766973697465645f736c656430358e766973697465645f736c656430368e766973697465645f736c656430378e766973697465645f736c6564303897766973697465645f736e6f776d616e5f616d657269736898766973697465645f736e6f776d616e5f636572797368656e96766973697465645f736e6f776d616e5f637973736f7296766973697465645f736e6f776d616e5f6573616d697298766973697465645f736e6f776d616e5f666f72736572616c96766973697465645f736e6f776d616e5f686f7373696e98766973697465645f736e6f776d616e5f697368756e64617297766973697465645f736e6f776d616e5f7365617268757396766973697465645f736e6f776d616e5f736f6c736172857567643036857567643035857567643034857567643033857567643032857567643031856d61703939856d61703938856d61703937856d61703936856d61703135856d61703134856d61703131856d61703038856d61703034856d61703035856d61703033856d61703031856d61703036856d61703032856d61703039856d61703037856d617031300300000091747261696e696e675f73746172745f6e638b747261696e696e675f75698c747261696e696e675f6d61700000000000000000000000000000000000000000800000003d0c04d350840240000010000602429660f80c80000c8004200c1b81480000020000c046f18a47019000019000ca4644304900000040001809e6bb052032000008001a84787211200000080003010714889c06400000100320ff0a42e4000001009e95a7342e03200000080003010408c914064000000001198990c4e4000001000060223b9b2180c800000a00081c20c92c800003600414ec172d900000040001808de1284a0320000320008ef1c336b20000078011d830e6f6400000600569c417e2c80000020000c04102502f019000008c00ce31027d99000000400018099e6146203200004b0015a7d44002f720000008000301040c18dc064000023000b1240800636400000100006020e0e92280c80000c800081650c00cfc800006400ce32a1801a59000000400018099e6fc3e03200004b00058b14680463200000080003010742610c064000043000b16c8880916400000100006020e0d01580c80000c8006714e24012cc80000020000c04cf25c190190000258001032e240307900000c8019c74470061b2000000800030133ced8fc0640000960012d9a8d00f0640000010025b9c1401e4c8000002004b6b23c03d1900000040098f585007b3200000080131a58c00f864000001002536f1c01f4c8000002004a64e2a03f190000004015e1b4580873200000080003010711f8a406400000100110a00c010ee400000100006020e2a51380c8000002002218d21021ec80000020000c041c40249019000000400af18a44043f90000004000180838b44760320000008015e38c80088320000008000301071490cc064000001002bc35890110e400000100006020e2052180c800000200221f90d0222c80000020000c041c5e447019000000400442e62e044790000004000180838af032032000000800886d08c089320000008000301071738740640000010011098898112e400000100006020e2361c80c8000002002212a1b0226c80000020000c041c512170190000004004420a32044f900000040001808389104a0320000008008874c8808a3200000080003010715907c06400000100110c0898114e400000100006020e2771a80c800000200578bd13022ac80000020000c041c424330190000004004423848045790000004000180838bfc32032000000801a86506008b320000008000301071030dc06400000100129f68a0117640000010026353110232c8000002004b69438046d90000004015e2887008eb200000080003010715909406400000100350fb8e011de400000100006020e2881980c8000002005786d0f023cc80000020000c041c4cc3b019000000400af1ba1c047b90000004000180838af872032000000800886344408fb20000008000301071620d406400000100110c10b011fe400000100006020e2870d80c800000200578f30c0240c80000020000c041c5863b019000000400442ee300483900000040001808388605e032000000801a86f03c090b200000080003010712a8fc064000001002bc0d858121e400000100006020e2521c80c800000200578b7230244c80000020000c041c49629019000000400d434026048b90000004000180838afc42032000000801a86d864091b200000080003010711989c064000001003508c8c8123e400000100006020e2a82280c8000002006a14f110248c80000020000c041c4be21019000000400af12640049390000004000180838a54720320000008015e33430092b20000008000301071228cc064000001003546e8d432400000100004f34a631139000004001b0834723120000008000204000c2ed0fa1c800000200a8432234a90000004000180952b248a0320000018004024c569d20000008000250a4d0ebc480000020000c04a24bc43019000000c00e0"
+ val string_ccrider = hex"18 4a c7 00 00 bc 84 B0 0c 0b 95 59 9a 84 40 b0 00 01 32 00 00 08 70 43 00 43 00 52 00 49 00 44 00 45 00 52 00 82 28 c9 3d 04 40 03 c0 01 40 02 80 00 40 35 18 40 00 25 40 42 00 6c 00 61 00 63 00 6b 00 20 00 41 00 72 00 6d 00 6f 00 72 00 65 00 64 00 20 00 52 00 65 00 61 00 70 00 65 00 72 00 73 00 0f 00 00 03 02 0c 00 00 03 88 00 00 00 d4 00 00 01 9c 04 00 00 09 19 90 02 04 3c 28 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 64 00 00 01 00 7e c8 00 c8 00 00 01 b9 53 4c 00 00 00 00 00 00 00 00 00 00 00 00 00 03 40 00 40 81 c6 86 c8 48 88 c9 09 49 8a 67 86 e0 00 01 25 e0 32 d8 09 6c 00 00 3c 04 00 02 30 7870655f626c61636b6f7073917870655f626174746c655f72616e6b5f39927870655f626174746c655f72616e6b5f3233927870655f636f6d6d616e645f72616e6b5f318f7870655f666f726d5f6f75746669748e7870655f6d61696c5f616c657274927870655f626174746c655f72616e6b5f3232927870655f636f6d6d616e645f72616e6b5f33907870655f666f726d5f706c61746f6f6e927870655f626174746c655f72616e6b5f3133927870655f626174746c655f72616e6b5f3132927870655f626174746c655f72616e6b5f3130927870655f626174746c655f72616e6b5f3134927870655f626174746c655f72616e6b5f3135917870655f626174746c655f72616e6b5f38927870655f626174746c655f72616e6b5f3131927870655f626174746c655f72616e6b5f3231927870655f626174746c655f72616e6b5f3230927870655f626174746c655f72616e6b5f3138927870655f626174746c655f72616e6b5f3139907870655f6a6f696e5f706c61746f6f6e927870655f626174746c655f72616e6b5f3137927870655f626174746c655f72616e6b5f31368f7870655f6a6f696e5f6f7574666974927870655f626174746c655f72616e6b5f3235927870655f626174746c655f72616e6b5f3234917870655f626174746c655f72616e6b5f37917870655f62696e645f666163696c6974798c7870655f62696e645f616d73927870655f636f6d6d616e645f72616e6b5f35927870655f636f6d6d616e645f72616e6b5f32927870655f696e7374616e745f616374696f6e8e7870655f666f726d5f7371756164917870655f626174746c655f72616e6b5f34937870655f776172705f676174655f7573616765917870655f626174746c655f72616e6b5f32917870655f626174746c655f72616e6b5f33927870655f73616e6374756172795f68656c708c7870655f64726f705f706f64937870655f6f72626974616c5f73687574746c65917870655f626174746c655f72616e6b5f368e7870655f6a6f696e5f7371756164917870655f626174746c655f72616e6b5f35927870655f74685f737769746368626c6164658d7870655f74685f726f757465728c7870655f74685f666c61696c8a7870655f74685f616e748a7870655f74685f616d738a7870655f74685f62667289757365645f6f69637791757365645f616476616e6365645f61636597766973697465645f73706974666972655f74757272657498766973697465645f73706974666972655f636c6f616b656493766973697465645f73706974666972655f616192766973697465645f74616e6b5f7472617073a1766973697465645f706f727461626c655f6d616e6e65645f7475727265745f6e638e757365645f6d616763757474657293766973697465645f77616c6c5f7475727265748b766973697465645f616d738b766973697465645f616e7490766973697465645f64726f707368697091766973697465645f6c6962657261746f7294766973697465645f6c6967687467756e7368697091766973697465645f6c696768746e696e6790766973697465645f6d616772696465728f766973697465645f70726f776c657293766973697465645f71756164737465616c746890766973697465645f736b7967756172649a766973697465645f74687265656d616e686561767962756767799d766973697465645f74776f5f6d616e5f61737361756c745f627567677998766973697465645f74776f6d616e6865617679627567677998766973697465645f74776f6d616e686f766572627567677990766973697465645f76616e67756172648d766973697465645f666c61696c8e766973697465645f726f7574657293766973697465645f737769746368626c6164658e766973697465645f6175726f726193766973697465645f626174746c657761676f6e8c766973697465645f6675727993766973697465645f7175616461737361756c7496766973697465645f67616c6178795f67756e736869708e766973697465645f6170635f74728e766973697465645f6170635f767390766973697465645f6c6f64657374617290766973697465645f7068616e7461736d91766973697465645f7468756e64657265728e766973697465645f6170635f6e638f766973697465645f76756c747572658c766973697465645f7761737090766973697465645f6d6f73717569746f97766973697465645f617068656c696f6e5f666c6967687497766973697465645f617068656c696f6e5f67756e6e657297766973697465645f636f6c6f737375735f666c6967687497766973697465645f636f6c6f737375735f67756e6e657298766973697465645f706572656772696e655f666c6967687498766973697465645f706572656772696e655f67756e6e657289757365645f62616e6b95766973697465645f7265736f757263655f73696c6f9e766973697465645f63657274696669636174696f6e5f7465726d696e616c94766973697465645f6d65645f7465726d696e616c93757365645f6e616e6f5f64697370656e73657295766973697465645f73656e736f725f736869656c649a766973697465645f62726f6164636173745f77617270676174658c757365645f7068616c616e7894757365645f7068616c616e785f6176636f6d626f96757365645f7068616c616e785f666c616b636f6d626f96766973697465645f77617270676174655f736d616c6c91757365645f666c616d657468726f7765729a757365645f616e6369656e745f7475727265745f776561706f6e92766973697465645f4c4c555f736f636b657492757365645f656e657267795f67756e5f6e6397766973697465645f6d656469756d7472616e73706f72749f757365645f617068656c696f6e5f696d6d6f6c6174696f6e5f63616e6e6f6e93757365645f6772656e6164655f706c61736d6193757365645f6772656e6164655f6a616d6d657298766973697465645f736869656c645f67656e657261746f7295766973697465645f6d6f74696f6e5f73656e736f7296766973697465645f6865616c74685f6372797374616c96766973697465645f7265706169725f6372797374616c97766973697465645f76656869636c655f6372797374616c91757365645f6772656e6164655f6672616788757365645f61636598766973697465645f6164765f6d65645f7465726d696e616c8b757365645f6265616d657290757365645f626f6c745f6472697665728b757365645f6379636c65728a757365645f676175737391757365645f68756e7465727365656b657288757365645f6973708b757365645f6c616e6365728b757365645f6c61736865728e757365645f6d61656c7374726f6d8c757365645f70686f656e69788b757365645f70756c7361728d757365645f70756e69736865728e757365645f725f73686f7467756e8d757365645f7261646961746f7288757365645f72656b8d757365645f72657065617465728c757365645f726f636b6c65748c757365645f737472696b65728f757365645f73757070726573736f728c757365645f7468756d7065729c766973697465645f76616e755f636f6e74726f6c5f636f6e736f6c6598766973697465645f636170747572655f7465726d696e616c92757365645f6d696e695f636861696e67756e91757365645f6c617a655f706f696e7465728c757365645f74656c657061648b757365645f7370696b657291757365645f68656176795f736e6970657293757365645f636f6d6d616e645f75706c696e6b8d757365645f66697265626972648e757365645f666c6563686574746594757365645f68656176795f7261696c5f6265616d89757365645f696c63399a766973697465645f67656e657261746f725f7465726d696e616c8e766973697465645f6c6f636b65729a766973697465645f65787465726e616c5f646f6f725f6c6f636b9c766973697465645f6169725f76656869636c655f7465726d696e616c97766973697465645f67616c6178795f7465726d696e616c98766973697465645f696d706c616e745f7465726d696e616c99766973697465645f7365636f6e646172795f6361707475726590757365645f32356d6d5f63616e6e6f6e99757365645f6c6962657261746f725f626f6d6261726469657293766973697465645f7265706169725f73696c6f93766973697465645f76616e755f6d6f64756c6591757365645f666c61696c5f776561706f6e8b757365645f73637974686598766973697465645f7265737061776e5f7465726d696e616c8c757365645f62616c6c67756e95757365645f616e6e69766572736172795f67756e6195757365645f616e6e69766572736172795f67756e6294757365645f616e6e69766572736172795f67756e90757365645f37356d6d5f63616e6e6f6e92757365645f6170635f6e635f776561706f6e92757365645f6170635f74725f776561706f6e92757365645f6170635f76735f776561706f6e90757365645f666c75785f63616e6e6f6e93757365645f617068656c696f6e5f6c617365729f757365645f617068656c696f6e5f706c61736d615f726f636b65745f706f6491757365645f617068656c696f6e5f70706196757365645f617068656c696f6e5f73746172666972658c757365645f666c7578706f6494766973697465645f6266725f7465726d696e616c95757365645f636f6c6f737375735f6275727374657296757365645f636f6c6f737375735f636861696e67756e9e757365645f636f6c6f737375735f636c75737465725f626f6d625f706f64a0757365645f636f6c6f737375735f6475616c5f3130306d6d5f63616e6e6f6e7399757365645f636f6c6f737375735f74616e6b5f63616e6e6f6e96766973697465645f64616d6167655f6372797374616c96766973697465645f656e657267795f6372797374616c9b757365645f68656176795f6772656e6164655f6c61756e6368657298757365645f33356d6d5f726f74617279636861696e67756e8b757365645f6b6174616e6190757365645f33356d6d5f63616e6e6f6e93757365645f7265617665725f776561706f6e7396757365645f6c696768746e696e675f776561706f6e738c757365645f6d65645f61707090757365645f32306d6d5f63616e6e6f6e98766973697465645f6d6f6e6f6c6974685f616d657269736899766973697465645f6d6f6e6f6c6974685f636572797368656e97766973697465645f6d6f6e6f6c6974685f637973736f7297766973697465645f6d6f6e6f6c6974685f6573616d697299766973697465645f6d6f6e6f6c6974685f666f72736572616c97766973697465645f6d6f6e6f6c6974685f686f7373696e99766973697465645f6d6f6e6f6c6974685f697368756e64617298766973697465645f6d6f6e6f6c6974685f7365617268757397766973697465645f6d6f6e6f6c6974685f736f6c73617292757365645f6e635f6865765f66616c636f6e99757365645f6e635f6865765f7363617474657263616e6e6f6e93757365645f6e635f6865765f73706172726f7791757365645f61726d6f725f736970686f6e9f757365645f706572656772696e655f6475616c5f6d616368696e655f67756e9f757365645f706572656772696e655f6475616c5f726f636b65745f706f647399757365645f706572656772696e655f6d65636868616d6d65728f757365645f6e74755f736970686f6e9e757365645f706572656772696e655f7061727469636c655f63616e6e6f6e96757365645f706572656772696e655f73706172726f7791757365645f3130356d6d5f63616e6e6f6e92757365645f31356d6d5f636861696e67756ea0757365645f70756c7365645f7061727469636c655f616363656c657261746f7293757365645f726f74617279636861696e67756e9f766973697465645f6465636f6e737472756374696f6e5f7465726d696e616c95757365645f736b7967756172645f776561706f6e7391766973697465645f67656e657261746f7291757365645f67617573735f63616e6e6f6e89757365645f7472656b95757365645f76616e67756172645f776561706f6e73a4766973697465645f616e6369656e745f6169725f76656869636c655f7465726d696e616ca2766973697465645f616e6369656e745f65717569706d656e745f7465726d696e616c96766973697465645f6f726465725f7465726d696e616ca7766973697465645f616e6369656e745f67726f756e645f76656869636c655f7465726d696e616c9f766973697465645f67726f756e645f76656869636c655f7465726d696e616c97757365645f76756c747572655f626f6d6261726469657298757365645f76756c747572655f6e6f73655f63616e6e6f6e98757365645f76756c747572655f7461696c5f63616e6e6f6e97757365645f776173705f776561706f6e5f73797374656d91766973697465645f636861726c6965303591766973697465645f636861726c6965303691766973697465645f636861726c6965303791766973697465645f636861726c6965303891766973697465645f636861726c696530398e766973697465645f736c656430348e766973697465645f736c656430358e766973697465645f736c656430368e766973697465645f736c656430378e766973697465645f736c65643038857567643036857567643035857567643034857567643033857567643032857567643031856d61703939856d61703938856d61703937856d61703936856d61703135856d61703134856d61703133856d61703132856d61703131856d61703034856d61703035856d61703033856d61703031856d61703036856d61703032856d61703039856d61703037856d617031300800000092747261696e696e675f776561706f6e73303192747261696e696e675f7761727067617465738c747261696e696e675f6d617092747261696e696e675f696e76656e746f727990747261696e696e675f6865616c696e678d747261696e696e675f686172748f747261696e696e675f61726d6f727390747261696e696e675f77656c636f6d650000000000000000000000000000000000000000ab000480000005000000058000000800000008800000090000000f0000000f8000001000000012800000130000001a0000001a8000001c800000228000002e00000031800000320000003580000036000000368000003700000039000000398000003a0000003a8000003b0000003f800000400000004080000041000000418000004e000000518000005580000059000000598000005a0000005a800000610000006180000063000000648000006b8000006c0000006c8000006f0000006f800000700000007080000071000000718000007280000073000000798000007a0000000080800004808000050080000a8080000b0080000d8080000e0080000e8080000f0080001080800011008000140080001c808000270080002a8080002b00800033808000388080003c0080003c8080004000800043808000440080004480800045008000470080004c0080004f808000520080005400800000002f0a26c9313c024000001000060279a272780c80000100026b271682480000020000c041c0464f019000006400d43541d08900000040001808388946a0320000008019041c821700000008046ce915631018000000400018083822c6a030000019001d2338421b00000008000301070438ac060000032003a4630c466000000100006020e06c1e80c000006400748e01c92c00000020000c041c11e4101800000c800e90a02331800000040001808381b06e030000019001989ec436b000000080003010c81d924060000019003310e87f0600000010000602190d31780c000003200662fa22e6c00000020000c04320a82f018000006400e90683bd9800000040001808381205a030000019000205808bcb000001900040e4908058e0000032006629c1d00b4c00000020000c043209c35018000006400cc480320175800000040001808642084a03000000c80198880540303000000080003010c87d86c060000019000b17e9100636000000100006020e01f2080c00000c800081580f010bc000006400414961a022f800000040001808de0004603000003200082818840477000000080003011bc0a11406000006400040a28680b2e000003200569271a017ac00000020000c041037435018000008c00ce2e04003498000000400018099e6c46e03000004b0019c4483406b70000000800030133cc28ec06000009600040ad8e80dfe0000032006710e1201fec00000020000c04cf2a837018000025800ce37432040f8000000400018099e7586a03000004b00058b18480843000000080003010702f11406000006400040f690010ce0000032000e0222786c8000064001c1d82b13900000c800380205233200001900074559050e40000320079afa0fa4c800002000d84b64f4e9000000400010300380"
"DetailedCharacterData" should {
"decode" in {
@@ -34,56 +35,107 @@ class DetailedCharacterDataTest extends Specification {
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 CharacterVoice.Voice1
- 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
+ basic match {
+ case CharacterAppearanceData(a, b, ribbons) =>
+ a.app.name mustEqual "IlllIIIlllIlIllIlllIllI"
+ a.app.faction mustEqual PlanetSideEmpire.VS
+ a.app.sex mustEqual CharacterGender.Female
+ a.app.head mustEqual 41
+ a.app.voice mustEqual CharacterVoice.Voice1
+ a.black_ops mustEqual false
+ a.jammered mustEqual false
+ a.exosuit mustEqual ExoSuitType.Standard
+ a.unk1 mustEqual true
+ a.unk2 mustEqual None
+ a.unk3 mustEqual None
+ a.unk4 mustEqual 0
+ a.unk5 mustEqual 0
+ a.unk6 mustEqual 41605313L
+ a.unk7 mustEqual 0
+ a.unk8 mustEqual 0
+ a.unk9 mustEqual 0
+ a.unkA mustEqual 65535
+
+ b.outfit_name mustEqual ""
+ b.outfit_logo mustEqual 0
+ b.backpack mustEqual false
+ b.facingPitch mustEqual 2.8125f
+ b.facingYawUpper mustEqual 61.875f
+ b.lfs mustEqual true
+ b.grenade_state mustEqual GrenadeState.None
+ b.is_cloaking mustEqual false
+ b.charging_pose mustEqual false
+ b.on_zipline mustEqual None
+ b.unk0 mustEqual 0L
+ b.unk1 mustEqual false
+ b.unk2 mustEqual false
+ b.unk3 mustEqual false
+ b.unk4 mustEqual false
+ b.unk5 mustEqual false
+ b.unk6 mustEqual false
+ b.unk7 mustEqual false
+
+ ribbons.upper mustEqual MeritCommendation.None
+ ribbons.middle mustEqual MeritCommendation.None
+ ribbons.lower mustEqual MeritCommendation.None
+ ribbons.tos mustEqual MeritCommendation.None
+ case _ =>
+ ko
+ }
+
+ char match {
+ case DetailedCharacterData(a, b) =>
+ a.bep mustEqual 0L
+ a.cep mustEqual 0L
+ a.healthMax mustEqual 100
+ a.health mustEqual 100
+ a.armor mustEqual 50 //standard exosuit value
+ a.staminaMax mustEqual 100
+ a.stamina mustEqual 100
+ a.certs mustEqual List(
+ CertificationType.StandardAssault,
+ CertificationType.MediumAssault,
+ CertificationType.ATV,
+ CertificationType.Harasser,
+ CertificationType.StandardExoSuit,
+ CertificationType.AgileExoSuit,
+ CertificationType.ReinforcedExoSuit
+ )
+ a.unk1 mustEqual 0L
+ a.unk2 mustEqual 0L
+ a.unk3 mustEqual 0L
+ a.unk4 mustEqual false
+ a.unk5 mustEqual 32831L
+ a.unk6 mustEqual 0
+ a.unk7 mustEqual 0
+ a.unk8 mustEqual 0L
+ a.unk9 mustEqual List(0, 0, 0, 0, 0, 0)
+
+ b.implants mustEqual List()
+ b.firstTimeEvents mustEqual List(
+ "xpe_sanctuary_help",
+ "xpe_th_firemodes",
+ "used_beamer",
+ "map13"
+ )
+ b.tutorials mustEqual Nil
+ b.cosmetics mustEqual None
+ b.unk1 mustEqual None
+ b.unk2 mustEqual Nil
+ b.unk3 mustEqual Nil
+ b.unk4 mustEqual 0L
+ b.unk5 mustEqual 0L
+ b.unk6 mustEqual 0L
+ b.unk7 mustEqual 0L
+ b.unk8 mustEqual 0L
+ b.unk9 mustEqual Some(DCDExtra2(0, 0))
+ b.unkA mustEqual Nil
+ b.unkB mustEqual Nil
+ b.unkC mustEqual false
+ case _ =>
+ ko
+ }
- 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
@@ -170,56 +222,107 @@ class DetailedCharacterDataTest extends Specification {
parent.get.slot mustEqual 0
data match {
case Some(DetailedPlayerData(None, basic, char, inv, hand)) =>
- 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 CharacterVoice.Voice1
- 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
+ basic match {
+ case CharacterAppearanceData(a, b, ribbons) =>
+ a.app.name mustEqual "IlllIIIlllIlIllIlllIllI"
+ a.app.faction mustEqual PlanetSideEmpire.VS
+ a.app.sex mustEqual CharacterGender.Female
+ a.app.head mustEqual 41
+ a.app.voice mustEqual CharacterVoice.Voice1
+ a.black_ops mustEqual false
+ a.jammered mustEqual false
+ a.exosuit mustEqual ExoSuitType.Standard
+ a.unk1 mustEqual false
+ a.unk2 mustEqual None
+ a.unk3 mustEqual None
+ a.unk4 mustEqual 0
+ a.unk5 mustEqual 0
+ a.unk6 mustEqual 192L
+ a.unk7 mustEqual 0
+ a.unk8 mustEqual 0
+ a.unk9 mustEqual 0
+ a.unkA mustEqual 0
+
+ b.outfit_name mustEqual ""
+ b.outfit_logo mustEqual 0
+ b.backpack mustEqual false
+ b.facingPitch mustEqual 5.625f
+ b.facingYawUpper mustEqual 61.875f
+ b.lfs mustEqual true
+ b.grenade_state mustEqual GrenadeState.None
+ b.is_cloaking mustEqual false
+ b.charging_pose mustEqual false
+ b.on_zipline mustEqual None
+ b.unk0 mustEqual 0L
+ b.unk1 mustEqual false
+ b.unk2 mustEqual false
+ b.unk3 mustEqual false
+ b.unk4 mustEqual false
+ b.unk5 mustEqual false
+ b.unk6 mustEqual false
+ b.unk7 mustEqual false
+
+ ribbons.upper mustEqual MeritCommendation.None
+ ribbons.middle mustEqual MeritCommendation.None
+ ribbons.lower mustEqual MeritCommendation.None
+ ribbons.tos mustEqual MeritCommendation.None
+ case _ =>
+ ko
+ }
+
+ char match {
+ case DetailedCharacterData(a, b) =>
+ a.bep mustEqual 0L
+ a.cep mustEqual 0L
+ a.healthMax mustEqual 100
+ a.health mustEqual 100
+ a.armor mustEqual 50 //standard exosuit value
+ a.staminaMax mustEqual 100
+ a.stamina mustEqual 100
+ a.certs mustEqual List(
+ CertificationType.StandardAssault,
+ CertificationType.MediumAssault,
+ CertificationType.ATV,
+ CertificationType.Harasser,
+ CertificationType.StandardExoSuit,
+ CertificationType.AgileExoSuit,
+ CertificationType.ReinforcedExoSuit
+ )
+ a.unk1 mustEqual 0L
+ a.unk2 mustEqual 0L
+ a.unk3 mustEqual 0L
+ a.unk4 mustEqual false
+ a.unk5 mustEqual 32831L
+ a.unk6 mustEqual 0
+ a.unk7 mustEqual 0
+ a.unk8 mustEqual 0L
+ a.unk9 mustEqual List(0, 0, 0, 0, 0, 0)
+
+ b.implants mustEqual List()
+ b.firstTimeEvents mustEqual List(
+ "xpe_sanctuary_help",
+ "xpe_th_firemodes",
+ "used_beamer",
+ "map13"
+ )
+ b.tutorials mustEqual Nil
+ b.cosmetics mustEqual None
+ b.unk1 mustEqual None
+ b.unk2 mustEqual Nil
+ b.unk3 mustEqual Nil
+ b.unk4 mustEqual 0L
+ b.unk5 mustEqual 0L
+ b.unk6 mustEqual 0L
+ b.unk7 mustEqual 0L
+ b.unk8 mustEqual 0L
+ b.unk9 mustEqual Some(DCDExtra2(0, 0))
+ b.unkA mustEqual Nil
+ b.unkB mustEqual Nil
+ b.unkC mustEqual false
+ case _ =>
+ ko
+ }
- 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
@@ -297,37 +400,426 @@ class DetailedCharacterDataTest extends Specification {
"decode (BR32)" in {
PacketCoding.DecodePacket(string_br32).require match {
- case ObjectCreateDetailedMessage(len, cls, guid, parent, data) =>
+ case ObjectCreateDetailedMessage(_, _, _, _, data) =>
//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 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
+ case Some(DetailedPlayerData(Some(_), basic, char, inv, hand)) =>
+ basic match {
+ case CharacterAppearanceData(a, b, ribbons) =>
+ a.app.name mustEqual "KiCkJr"
+ a.app.faction mustEqual PlanetSideEmpire.NC
+ a.app.sex mustEqual CharacterGender.Male
+ a.app.head mustEqual 24
+ a.app.voice mustEqual CharacterVoice.Voice4
+ a.black_ops mustEqual false
+ a.jammered mustEqual false
+ a.exosuit mustEqual ExoSuitType.Agile
+ a.unk1 mustEqual true
+ a.unk2 mustEqual None
+ a.unk3 mustEqual None
+ a.unk4 mustEqual 0
+ a.unk5 mustEqual 0
+ a.unk6 mustEqual 733931L
+ a.unk7 mustEqual 0
+ a.unk8 mustEqual 0
+ a.unk9 mustEqual 0
+ a.unkA mustEqual 0
+
+ b.outfit_name mustEqual ""
+ b.outfit_logo mustEqual 14
+ b.backpack mustEqual false
+ b.facingPitch mustEqual 348.75f
+ b.facingYawUpper mustEqual 348.75f
+ b.lfs mustEqual false
+ b.grenade_state mustEqual GrenadeState.None
+ b.is_cloaking mustEqual false
+ b.charging_pose mustEqual false
+ b.on_zipline mustEqual None
+ b.unk0 mustEqual 556539L
+ b.unk1 mustEqual false
+ b.unk2 mustEqual false
+ b.unk3 mustEqual false
+ b.unk4 mustEqual false
+ b.unk5 mustEqual true
+ b.unk6 mustEqual false
+ b.unk7 mustEqual false
+
+ ribbons.upper mustEqual MeritCommendation.Loser4
+ ribbons.middle mustEqual MeritCommendation.EventNCElite
+ ribbons.lower mustEqual MeritCommendation.HeavyAssault6
+ ribbons.tos mustEqual MeritCommendation.SixYearNC
+ case _ =>
+ ko
+ }
+
+ DetailedCharacterData.isBR24(char.a.bep) mustEqual true
+ char match {
+ case DetailedCharacterData(a, b) =>
+ a.bep mustEqual 6366766L
+ a.cep mustEqual 694787L
+ a.healthMax mustEqual 100
+ a.health mustEqual 100
+ a.armor mustEqual 100 //standard exosuit value
+ a.staminaMax mustEqual 100
+ a.stamina mustEqual 46
+ a.certs mustEqual List(
+ CertificationType.StandardAssault,
+ CertificationType.MediumAssault,
+ CertificationType.HeavyAssault,
+ CertificationType.AntiVehicular,
+ CertificationType.AirCavalryScout,
+ CertificationType.GroundSupport,
+ CertificationType.Harasser,
+ CertificationType.StandardExoSuit,
+ CertificationType.AgileExoSuit,
+ CertificationType.Medical,
+ CertificationType.AdvancedMedical,
+ CertificationType.Hacking,
+ CertificationType.AdvancedHacking,
+ CertificationType.Engineering,
+ CertificationType.CombatEngineering
+ )
+ a.unk1 mustEqual 0L
+ a.unk2 mustEqual 0L
+ a.unk3 mustEqual 0L
+ a.unk4 mustEqual false
+ a.unk5 mustEqual 32831L
+ a.unk6 mustEqual 0
+ a.unk7 mustEqual 4
+ a.unk8 mustEqual 3278759L
+ a.unk9 mustEqual List(0, 0, 0, 0, 0, 0)
+
+ b.implants mustEqual List(
+ ImplantEntry(ImplantType.AudioAmplifier, None, false),
+ ImplantEntry(ImplantType.Targeting, None, false),
+ ImplantEntry(ImplantType.Surge, None, false)
+ )
+ b.firstTimeEvents mustEqual List(
+ "xpe_overhead_map",
+ "xpe_warp_gate",
+ "xpe_form_outfit",
+ "xpe_blackops",
+ "xpe_command_rank_5",
+ "xpe_command_rank_3",
+ "xpe_sanctuary_help",
+ "xpe_battle_rank_13",
+ "xpe_battle_rank_12",
+ "xpe_battle_rank_10",
+ "xpe_battle_rank_14",
+ "xpe_battle_rank_15",
+ "xpe_orbital_shuttle",
+ "xpe_drop_pod",
+ "xpe_bind_facility",
+ "xpe_battle_rank_3",
+ "xpe_battle_rank_5",
+ "xpe_battle_rank_4",
+ "xpe_join_squad",
+ "xpe_form_squad",
+ "xpe_instant_action",
+ "xpe_battle_rank_2",
+ "xpe_warp_gate_usage",
+ "xpe_battle_rank_8",
+ "xpe_battle_rank_11",
+ "xpe_battle_rank_6",
+ "xpe_mail_alert",
+ "xpe_command_rank_1",
+ "xpe_battle_rank_20",
+ "xpe_battle_rank_18",
+ "xpe_battle_rank_19",
+ "xpe_join_platoon",
+ "xpe_battle_rank_17",
+ "xpe_battle_rank_16",
+ "xpe_join_outfit",
+ "xpe_battle_rank_25",
+ "xpe_battle_rank_24",
+ "xpe_command_rank_4",
+ "xpe_form_platoon",
+ "xpe_bind_ams",
+ "xpe_battle_rank_9",
+ "xpe_battle_rank_7",
+ "xpe_th_router",
+ "xpe_th_flail",
+ "xpe_th_ant",
+ "xpe_th_ams",
+ "xpe_th_ground_p",
+ "xpe_th_air_p",
+ "xpe_th_hover",
+ "xpe_th_ground",
+ "xpe_th_bfr",
+ "xpe_th_afterburner",
+ "xpe_th_air",
+ "xpe_th_cloak",
+ "used_oicw",
+ "used_advanced_ace",
+ "visited_spitfire_turret",
+ "visited_spitfire_cloaked",
+ "visited_spitfire_aa",
+ "visited_tank_traps",
+ "visited_portable_manned_turret_nc",
+ "visited_portable_manned_turret_tr",
+ "used_magcutter",
+ "used_chainblade",
+ "used_forceblade",
+ "visited_wall_turret",
+ "visited_ancient_terminal",
+ "visited_ams",
+ "visited_ant",
+ "visited_dropship",
+ "visited_liberator",
+ "visited_lightgunship",
+ "visited_lightning",
+ "visited_magrider",
+ "visited_prowler",
+ "visited_quadstealth",
+ "visited_skyguard",
+ "visited_threemanheavybuggy",
+ "visited_two_man_assault_buggy",
+ "visited_twomanheavybuggy",
+ "visited_twomanhoverbuggy",
+ "visited_vanguard",
+ "visited_flail",
+ "visited_router",
+ "visited_switchblade",
+ "visited_aurora",
+ "visited_battlewagon",
+ "visited_fury",
+ "visited_quadassault",
+ "visited_galaxy_gunship",
+ "visited_apc_tr",
+ "visited_apc_vs",
+ "visited_lodestar",
+ "visited_phantasm",
+ "visited_thunderer",
+ "visited_apc_nc",
+ "visited_vulture",
+ "visited_wasp",
+ "visited_mosquito",
+ "visited_aphelion_flight",
+ "visited_aphelion_gunner",
+ "visited_colossus_flight",
+ "visited_colossus_gunner",
+ "visited_peregrine_flight",
+ "visited_peregrine_gunner",
+ "used_bank",
+ "visited_resource_silo",
+ "visited_certification_terminal",
+ "visited_med_terminal",
+ "used_nano_dispenser",
+ "visited_sensor_shield",
+ "visited_broadcast_warpgate",
+ "used_phalanx",
+ "used_phalanx_avcombo",
+ "used_phalanx_flakcombo",
+ "visited_warpgate_small",
+ "used_flamethrower",
+ "used_ancient_turret_weapon",
+ "visited_LLU_socket",
+ "used_energy_gun_nc",
+ "visited_mediumtransport",
+ "used_aphelion_immolation_cannon",
+ "used_grenade_plasma",
+ "used_grenade_jammer",
+ "visited_shield_generator",
+ "visited_motion_sensor",
+ "visited_health_crystal",
+ "visited_repair_crystal",
+ "visited_vehicle_crystal",
+ "used_grenade_frag",
+ "used_ace",
+ "visited_adv_med_terminal",
+ "used_beamer",
+ "used_bolt_driver",
+ "used_cycler",
+ "used_gauss",
+ "used_hunterseeker",
+ "used_isp",
+ "used_lancer",
+ "used_lasher",
+ "used_maelstrom",
+ "used_phoenix",
+ "used_pulsar",
+ "used_punisher",
+ "used_r_shotgun",
+ "used_radiator",
+ "used_rek",
+ "used_repeater",
+ "used_rocklet",
+ "used_striker",
+ "used_suppressor",
+ "used_thumper",
+ "visited_vanu_control_console",
+ "visited_capture_terminal",
+ "used_mini_chaingun",
+ "used_laze_pointer",
+ "used_telepad",
+ "used_spiker",
+ "used_heavy_sniper",
+ "used_command_uplink",
+ "used_firebird",
+ "used_flechette",
+ "used_heavy_rail_beam",
+ "used_ilc9",
+ "visited_generator_terminal",
+ "visited_locker",
+ "visited_external_door_lock",
+ "visited_air_vehicle_terminal",
+ "visited_galaxy_terminal",
+ "visited_implant_terminal",
+ "visited_secondary_capture",
+ "used_25mm_cannon",
+ "used_liberator_bombardier",
+ "visited_repair_silo",
+ "visited_vanu_module",
+ "used_flail_weapon",
+ "used_scythe",
+ "visited_respawn_terminal",
+ "used_ballgun",
+ "used_energy_gun_tr",
+ "used_anniversary_guna",
+ "used_anniversary_gunb",
+ "used_anniversary_gun",
+ "used_75mm_cannon",
+ "used_apc_nc_weapon",
+ "used_apc_tr_weapon",
+ "used_apc_vs_weapon",
+ "used_flux_cannon",
+ "used_aphelion_plasma_rocket_pod",
+ "used_aphelion_ppa",
+ "used_fluxpod",
+ "visited_bfr_terminal",
+ "used_colossus_cluster_bomb_pod",
+ "used_colossus_dual_100mm_cannons",
+ "used_colossus_tank_cannon",
+ "visited_energy_crystal",
+ "used_heavy_grenade_launcher",
+ "used_35mm_rotarychaingun",
+ "used_katana",
+ "used_35mm_cannon",
+ "used_reaver_weapons",
+ "used_lightning_weapons",
+ "used_med_app",
+ "used_20mm_cannon",
+ "visited_monolith_amerish",
+ "visited_monolith_ceryshen",
+ "visited_monolith_cyssor",
+ "visited_monolith_esamir",
+ "visited_monolith_forseral",
+ "visited_monolith_ishundar",
+ "visited_monolith_searhus",
+ "visited_monolith_solsar",
+ "used_nc_hev_falcon",
+ "used_nc_hev_scattercannon",
+ "used_nc_hev_sparrow",
+ "used_armor_siphon",
+ "used_peregrine_dual_machine_gun",
+ "used_peregrine_dual_rocket_pods",
+ "used_peregrine_mechhammer",
+ "used_peregrine_particle_cannon",
+ "used_peregrine_sparrow",
+ "used_105mm_cannon",
+ "used_15mm_chaingun",
+ "used_pulsed_particle_accelerator",
+ "used_rotarychaingun",
+ "visited_deconstruction_terminal",
+ "used_skyguard_weapons",
+ "visited_generator",
+ "used_gauss_cannon",
+ "used_trek",
+ "used_vanguard_weapons",
+ "visited_ancient_air_vehicle_terminal",
+ "visited_ancient_equipment_terminal",
+ "visited_order_terminal",
+ "visited_ancient_ground_vehicle_terminal",
+ "visited_ground_vehicle_terminal",
+ "used_vulture_bombardier",
+ "used_vulture_nose_cannon",
+ "used_vulture_tail_cannon",
+ "used_wasp_weapon_system",
+ "visited_charlie01",
+ "visited_charlie02",
+ "visited_charlie03",
+ "visited_charlie04",
+ "visited_charlie05",
+ "visited_charlie06",
+ "visited_charlie07",
+ "visited_charlie08",
+ "visited_charlie09",
+ "visited_gingerman_atar",
+ "visited_gingerman_dahaka",
+ "visited_gingerman_hvar",
+ "visited_gingerman_izha",
+ "visited_gingerman_jamshid",
+ "visited_gingerman_mithra",
+ "visited_gingerman_rashnu",
+ "visited_gingerman_sraosha",
+ "visited_gingerman_yazata",
+ "visited_gingerman_zal",
+ "visited_sled01",
+ "visited_sled02",
+ "visited_sled04",
+ "visited_sled05",
+ "visited_sled06",
+ "visited_sled07",
+ "visited_sled08",
+ "visited_snowman_amerish",
+ "visited_snowman_ceryshen",
+ "visited_snowman_cyssor",
+ "visited_snowman_esamir",
+ "visited_snowman_forseral",
+ "visited_snowman_hossin",
+ "visited_snowman_ishundar",
+ "visited_snowman_searhus",
+ "visited_snowman_solsar",
+ "ugd06",
+ "ugd05",
+ "ugd04",
+ "ugd03",
+ "ugd02",
+ "ugd01",
+ "map99",
+ "map98",
+ "map97",
+ "map96",
+ "map15",
+ "map14",
+ "map11",
+ "map08",
+ "map04",
+ "map05",
+ "map03",
+ "map01",
+ "map06",
+ "map02",
+ "map09",
+ "map07",
+ "map10"
+ )
+ b.tutorials mustEqual List(
+ "training_start_nc",
+ "training_ui",
+ "training_map"
+ )
+ b.cosmetics mustEqual Some(
+ Cosmetics(true, true, true, true, false)
+ )
+ b.unk1 mustEqual None
+ b.unk2 mustEqual Nil
+ b.unk3 mustEqual Nil
+ b.unk4 mustEqual 0L
+ b.unk5 mustEqual 0L
+ b.unk6 mustEqual 0L
+ b.unk7 mustEqual 0L
+ b.unk8 mustEqual 0L
+ b.unk9 mustEqual None
+ b.unkA mustEqual Nil
+ b.unkB mustEqual Nil
+ b.unkC mustEqual false
+ case _ =>
+ ko
+ }
+ //inventory (but just briefly)
inv.isDefined mustEqual true
inv.get.contents.size mustEqual 12
//0
@@ -369,12 +861,149 @@ class DetailedCharacterDataTest extends Specification {
}
}
+ "decode (ccrider)" in {
+ PacketCoding.DecodePacket(string_ccrider).require match {
+ case ObjectCreateDetailedMessage(len, _, _, None, data) =>
+ len mustEqual 51018L
+ data match {
+ case Some(DetailedPlayerData(_, basic, char, _, _)) =>
+ basic match {
+ case CharacterAppearanceData(a, b, ribbons) =>
+ a.app mustEqual BasicCharacterData("CCRIDER", PlanetSideEmpire.NC, CharacterGender.Male, 20, CharacterVoice.Voice3)
+ a.black_ops mustEqual false
+ a.altModel mustEqual false
+ a.unk1 mustEqual false
+ a.unk2 mustEqual None
+ a.jammered mustEqual false
+ a.exosuit mustEqual ExoSuitType.Standard
+ a.unk3 mustEqual None
+ a.unk4 mustEqual 0
+ a.unk5 mustEqual 0
+ a.unk6 mustEqual 1176612L
+ a.unk7 mustEqual 15
+ a.unk8 mustEqual 5
+ a.unk9 mustEqual 10
+ a.unkA mustEqual 1
+
+ b.unk0 mustEqual 25044L
+ b.outfit_name mustEqual "Black Armored Reapers"
+ b.outfit_logo mustEqual 15
+ b.unk1 mustEqual false
+ b.backpack mustEqual false
+ b.unk2 mustEqual false
+ b.unk3 mustEqual false
+ b.unk4 mustEqual false
+ b.facingPitch mustEqual 0
+ b.facingYawUpper mustEqual 0
+ b.lfs mustEqual false
+ b.grenade_state mustEqual GrenadeState.None
+ b.is_cloaking mustEqual false
+ b.unk5 mustEqual false
+ b.unk6 mustEqual false
+ b.charging_pose mustEqual false
+ b.unk7 mustEqual false
+ b.on_zipline mustEqual None
+
+ ribbons.upper mustEqual MeritCommendation.DefenseNC5
+ ribbons.middle mustEqual MeritCommendation.HackingSupport5
+ ribbons.lower mustEqual MeritCommendation.BFRAdvanced2
+ ribbons.tos mustEqual MeritCommendation.SixYearNC
+ case _ =>
+ ko
+ }
+
+ char match {
+ case DetailedCharacterData(a, b) =>
+ a.bep mustEqual 6571522L
+ a.cep mustEqual 659329L
+ a.unk1 mustEqual 0L
+ a.unk2 mustEqual 0L
+ a.unk3 mustEqual 0L
+ a.healthMax mustEqual 100
+ a.health mustEqual 100
+ a.unk4 mustEqual false
+ a.armor mustEqual 50
+ a.unk5 mustEqual 32831L
+ a.staminaMax mustEqual 100
+ a.stamina mustEqual 100
+ a.unk6 mustEqual 0
+ a.unk7 mustEqual 6
+ a.unk8 mustEqual 3165669L
+ a.unk9 mustEqual List(0, 0, 0, 0, 0, 0)
+ a.certs mustEqual List(
+ CertificationType.StandardAssault,
+ CertificationType.MediumAssault,
+ CertificationType.HeavyAssault,
+ CertificationType.AirCavalryScout,
+ CertificationType.StandardExoSuit,
+ CertificationType.AgileExoSuit,
+ CertificationType.UniMAX,
+ CertificationType.Medical,
+ CertificationType.AdvancedMedical,
+ CertificationType.Hacking,
+ CertificationType.AdvancedHacking,
+ CertificationType.ExpertHacking,
+ CertificationType.Engineering
+ )
+
+ b.unk1 mustEqual Some(14140)
+ b.implants mustEqual List(
+ ImplantEntry(ImplantType.Surge, Some(94), false),
+ ImplantEntry(ImplantType.DarklightVision, Some(91), false),
+ ImplantEntry(ImplantType.Targeting, Some(91), false)
+ )
+ b.unk2 mustEqual List()
+ b.unk3 mustEqual List()
+ b.firstTimeEvents.size mustEqual 271 //too many to bother listing; see encoding test
+ b.tutorials mustEqual List(
+ "training_weapons01",
+ "training_warpgates",
+ "training_map",
+ "training_inventory",
+ "training_healing",
+ "training_hart",
+ "training_armors",
+ "training_welcome"
+ )
+ b.unk4 mustEqual 0L
+ b.unk5 mustEqual 0L
+ b.unk6 mustEqual 0L
+ b.unk7 mustEqual 0L
+ b.unk8 mustEqual 0L
+ b.unk9 mustEqual None
+ b.unkA.size mustEqual 86
+ b.unkA mustEqual List(
+ 9,
+ 10, 11, 16, 17, 18, 30, 31, 32,
+ 37, 38, 52, 53, 57, 69, 92, 99,
+ 100, 107, 108, 109, 110, 114, 115, 116, 117, 118,
+ 127, 128, 129, 130, 131, 156, 163, 171, 178, 179,
+ 180, 181, 194, 195, 198, 201, 215, 216, 217, 222,
+ 223, 224, 225, 226, 227, 229, 230, 243, 244, 257,
+ 265, 266, 277, 278, 283, 284, 285, 286, 289, 290,
+ 296, 313, 334, 341, 342, 359, 369, 376, 377, 384,
+ 391, 392, 393, 394, 398, 408, 415, 420, 424
+ )
+ b.unkB mustEqual List()
+ b.unkC mustEqual false
+ b.cosmetics mustEqual Some(Cosmetics(true, false, true, true, true))
+ case _ =>
+ ko
+ }
+ case _ =>
+ ko
+ }
+ case _ =>
+ ko
+ }
+ }
+
"encode" in {
val pos : PlacementData = PlacementData(
3674.8438f, 2726.789f, 91.15625f,
0, 0, 36.5625f
)
- val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
+ val aa : Int=>CharacterAppearanceA = CharacterAppearanceA(
BasicCharacterData(
"IlllIIIlllIlIllIlllIllI",
PlanetSideEmpire.VS,
@@ -382,28 +1011,61 @@ class DetailedCharacterDataTest extends Specification {
41,
CharacterVoice.Voice1
),
- 3,
false,
false,
+ true,
+ None,
+ false,
ExoSuitType.Standard,
+ None,
+ 0,
+ 0,
+ 41605313L,
+ 0,
+ 0,
+ 0,
+ 65535
+ )
+ val ab : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
+ 0L,
"",
0,
false,
- 2.8125f, 210.9375f,
+ false,
+ false,
+ false,
+ false,
+ 2.8125f, 61.875f,
true,
GrenadeState.None,
false,
false,
false,
- RibbonBars()
+ false,
+ false,
+ None
)
- val char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData(
- 0,
- 0,
+
+ val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
+ aa, ab,
+ RibbonBars(
+ MeritCommendation.None,
+ MeritCommendation.None,
+ MeritCommendation.None,
+ MeritCommendation.None
+ )
+ )
+ val ba : DetailedCharacterA = DetailedCharacterA(
+ 0L,
+ 0L,
+ 0L, 0L, 0L,
100, 100,
+ false,
50,
- 1, 7, 7,
+ 32831L,
100, 100,
+ 0, 0, 0L,
+ List(0, 0, 0, 0, 0, 0),
List(
CertificationType.StandardAssault,
CertificationType.MediumAssault,
@@ -412,16 +1074,34 @@ class DetailedCharacterDataTest extends Specification {
CertificationType.StandardExoSuit,
CertificationType.AgileExoSuit,
CertificationType.ReinforcedExoSuit
+ )
+ )
+ val bb : (Long, Option[Int])=>DetailedCharacterB = DetailedCharacterB(
+ None,
+ Nil,
+ Nil, Nil,
+ List(
+ "xpe_sanctuary_help",
+ "xpe_th_firemodes",
+ "used_beamer",
+ "map13"
),
- List(),
- "xpe_sanctuary_help" :: "xpe_th_firemodes" :: "used_beamer" :: "map13" :: Nil,
- List.empty,
+ Nil,
+ 0L, 0L, 0L, 0L, 0L,
+ Some(DCDExtra2(0, 0)),
+ Nil, Nil, false,
None
)
+ val char : (Option[Int])=>DetailedCharacterData =
+ (pad_length : Option[Int]) => DetailedCharacterData(ba, bb(ba.bep, pad_length))(pad_length)
+
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.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)) ::
@@ -437,15 +1117,13 @@ class DetailedCharacterDataTest extends Specification {
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
val pkt_bitv = pkt.toBitVector
val ori_bitv = string.toBitVector
- pkt_bitv.take(153) mustEqual ori_bitv.take(153) //skip 1
- pkt_bitv.drop(154).take(422) mustEqual ori_bitv.drop(154).take(422) //skip 126
- pkt_bitv.drop(702).take(29) mustEqual ori_bitv.drop(702).take(29) //skip 1
- pkt_bitv.drop(732) mustEqual ori_bitv.drop(732)
+ pkt_bitv.take(724) mustEqual ori_bitv.take(724) //skip 1; this is the highest bit of facingPitch
+ pkt_bitv.drop(725) mustEqual ori_bitv.drop(725)
//TODO work on DetailedCharacterData to make this pass as a single stream
}
"encode (character, seated)" in {
- val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
+ val aa : Int=>CharacterAppearanceA = CharacterAppearanceA(
BasicCharacterData(
"IlllIIIlllIlIllIlllIllI",
PlanetSideEmpire.VS,
@@ -453,28 +1131,61 @@ class DetailedCharacterDataTest extends Specification {
41,
CharacterVoice.Voice1
),
- 3,
false,
false,
+ false,
+ None,
+ false,
ExoSuitType.Standard,
+ None,
+ 0,
+ 0,
+ 192L,
+ 0,
+ 0,
+ 0,
+ 0
+ )
+ val ab : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
+ 0L,
"",
0,
false,
- 2.8125f, 210.9375f,
+ false,
+ false,
+ false,
+ false,
+ 5.625f, 61.875f,
true,
GrenadeState.None,
false,
false,
false,
- RibbonBars()
+ false,
+ false,
+ None
)
- val char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData(
- 0,
- 0,
+
+ val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
+ aa, ab,
+ RibbonBars(
+ MeritCommendation.None,
+ MeritCommendation.None,
+ MeritCommendation.None,
+ MeritCommendation.None
+ )
+ )
+ val ba : DetailedCharacterA = DetailedCharacterA(
+ 0L,
+ 0L,
+ 0L, 0L, 0L,
100, 100,
+ false,
50,
- 1, 7, 7,
+ 32831L,
100, 100,
+ 0, 0, 0L,
+ List(0, 0, 0, 0, 0, 0),
List(
CertificationType.StandardAssault,
CertificationType.MediumAssault,
@@ -483,12 +1194,27 @@ class DetailedCharacterDataTest extends Specification {
CertificationType.StandardExoSuit,
CertificationType.AgileExoSuit,
CertificationType.ReinforcedExoSuit
+ )
+ )
+ val bb : (Long, Option[Int])=>DetailedCharacterB = DetailedCharacterB(
+ None,
+ Nil,
+ Nil, Nil,
+ List(
+ "xpe_sanctuary_help",
+ "xpe_th_firemodes",
+ "used_beamer",
+ "map13"
),
- List(),
- "xpe_sanctuary_help" :: "xpe_th_firemodes" :: "used_beamer" :: "map13" :: Nil,
- List.empty,
+ Nil,
+ 0L, 0L, 0L, 0L, 0L,
+ Some(DCDExtra2(0, 0)),
+ Nil, Nil, false,
None
)
+ val char : (Option[Int])=>DetailedCharacterData =
+ (pad_length : Option[Int]) => DetailedCharacterData(ba, bb(ba.bep, pad_length))(pad_length)
+
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))) ::
@@ -509,7 +1235,9 @@ class DetailedCharacterDataTest extends Specification {
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
val pkt_bitv = pkt.toBitVector
val ori_bitv = string_seated.toBitVector
- pkt_bitv mustEqual ori_bitv
+ pkt_bitv.take(668) mustEqual ori_bitv.take(668) //skip 1; this is the highest bit of facingPitch
+ pkt_bitv.drop(670) mustEqual ori_bitv.drop(670)
+ //TODO work on DetailedCharacterData to make this pass as a single stream
}
"encode (character, br32)" in {
@@ -518,18 +1246,51 @@ class DetailedCharacterDataTest extends Specification {
Vector3(0, 0, 90.0f),
None
)
- val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
- BasicCharacterData("KiCkJr", PlanetSideEmpire.NC, CharacterGender.Male, 24, CharacterVoice.Voice4),
- 3,
- false, false,
+ val aa : Int=>CharacterAppearanceA = CharacterAppearanceA(
+ BasicCharacterData(
+ "KiCkJr",
+ PlanetSideEmpire.NC,
+ CharacterGender.Male,
+ 24,
+ CharacterVoice.Voice4
+ ),
+ false,
+ false,
+ true,
+ None,
+ false,
ExoSuitType.Agile,
+ None,
+ 0,
+ 0,
+ 733931L,
+ 0,
+ 0,
+ 0,
+ 0
+ )
+ val ab : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
+ 556539L,
"",
14,
false,
- 354.375f, 354.375f,
+ false,
+ false,
+ false,
+ false,
+ 348.75f, 348.75f,
false,
GrenadeState.None,
- false, false, false,
+ false,
+ true,
+ false,
+ false,
+ false,
+ None
+ )
+
+ val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
+ aa, ab,
RibbonBars(
MeritCommendation.Loser4,
MeritCommendation.EventNCElite,
@@ -537,12 +1298,17 @@ class DetailedCharacterDataTest extends Specification {
MeritCommendation.SixYearNC
)
)
- val char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData(
- 6366766,
- 694787,
- 100, 100, 100,
- 1, 7, 7,
+ val ba : DetailedCharacterA = DetailedCharacterA(
+ 6366766L,
+ 694787L,
+ 0L, 0L, 0L,
+ 100, 100,
+ false,
+ 100,
+ 32831L,
100, 46,
+ 0, 4, 3278759L,
+ List(0, 0, 0, 0, 0, 0),
List(
CertificationType.StandardAssault,
CertificationType.MediumAssault,
@@ -559,12 +1325,16 @@ class DetailedCharacterDataTest extends Specification {
CertificationType.AdvancedHacking,
CertificationType.Engineering,
CertificationType.CombatEngineering
- ),
+ )
+ )
+ val bb : (Long, Option[Int])=>DetailedCharacterB = DetailedCharacterB(
+ None,
List(
ImplantEntry(ImplantType.AudioAmplifier, None),
ImplantEntry(ImplantType.Targeting, None),
ImplantEntry(ImplantType.Surge, None)
),
+ Nil, Nil,
List(
"xpe_overhead_map",
"xpe_warp_gate",
@@ -870,8 +1640,14 @@ class DetailedCharacterDataTest extends Specification {
"training_ui",
"training_map"
),
+ 0L, 0L, 0L, 0L, 0L,
+ None,
+ Nil, Nil, false,
Some(Cosmetics(true, true, true, true, false))
)
+ val char : (Option[Int])=>DetailedCharacterData =
+ (pad_length : Option[Int]) => DetailedCharacterData(ba, bb(ba.bep, pad_length))(pad_length)
+
val inv = InventoryData(
List(
InternalSlot(531, PlanetSideGUID(4202), 0,
@@ -1060,16 +1836,507 @@ class DetailedCharacterDataTest extends Specification {
val msg = ObjectCreateDetailedMessage(ObjectClass.avatar, PlanetSideGUID(75), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
- val pkt_bitv = pkt.toBitVector
- val ori_bitv = string_br32.toBitVector
- //pkt_bitv mustEqual ori_bitv
- pkt_bitv.take(153) mustEqual ori_bitv.take(153) //skip 1
- pkt_bitv.drop(154).take(144) mustEqual ori_bitv.drop(154).take(144) //skip 24
- pkt_bitv.drop(322).take(72) mustEqual ori_bitv.drop(322).take(72) //skip 24
- pkt_bitv.drop(418).take(55) mustEqual ori_bitv.drop(418).take(55) //skip 1
- pkt_bitv.drop(474).take(102) mustEqual ori_bitv.drop(474).take(102) //skip 126
- pkt_bitv.drop(702).take(192) mustEqual ori_bitv.drop(702).take(192) //skip 36
- pkt_bitv.drop(930) mustEqual ori_bitv.drop(930) //to end
+ pkt mustEqual string_br32
+ }
+
+ "encode (ccrider)" in {
+ val pos : PlacementData = PlacementData(
+ Vector3(2931.5f, 4404.6953f, 45.0625f),
+ Vector3.z(36.5625f),
+ None
+ )
+ val aa : Int=>CharacterAppearanceA = CharacterAppearanceA(
+ BasicCharacterData(
+ "CCRIDER",
+ PlanetSideEmpire.NC,
+ CharacterGender.Male,
+ 20,
+ CharacterVoice.Voice3
+ ),
+ false,
+ false,
+ false,
+ None,
+ false,
+ ExoSuitType.Standard,
+ None,
+ 0,
+ 0,
+ 1176612L,
+ 15,
+ 5,
+ 10,
+ 1
+ )
+ val ab : (Boolean, Int)=>CharacterAppearanceB = CharacterAppearanceB(
+ 25044L,
+ "Black Armored Reapers",
+ 15,
+ false,
+ false,
+ false,
+ false,
+ false,
+ 0, 0,
+ false,
+ GrenadeState.None,
+ false,
+ false,
+ false,
+ false,
+ false,
+ None
+ )
+
+ val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
+ aa, ab,
+ RibbonBars(
+ MeritCommendation.DefenseNC5,
+ MeritCommendation.HackingSupport5,
+ MeritCommendation.BFRAdvanced2,
+ MeritCommendation.SixYearNC
+ )
+ )
+ val ba : DetailedCharacterA = DetailedCharacterA(
+ 6571522L,
+ 659329L,
+ 0L, 0L, 0L,
+ 100, 100,
+ false,
+ 50,
+ 32831,
+ 100, 100,
+ 0,
+ 6,
+ 3165669,
+ List(0, 0, 0, 0, 0, 0),
+ List(
+ CertificationType.StandardAssault,
+ CertificationType.MediumAssault,
+ CertificationType.HeavyAssault,
+ CertificationType.AirCavalryScout,
+ CertificationType.StandardExoSuit,
+ CertificationType.AgileExoSuit,
+ CertificationType.UniMAX,
+ CertificationType.Medical,
+ CertificationType.AdvancedMedical,
+ CertificationType.Hacking,
+ CertificationType.AdvancedHacking,
+ CertificationType.ExpertHacking,
+ CertificationType.Engineering
+ )
+ )
+ val bb : (Long, Option[Int])=>DetailedCharacterB = DetailedCharacterB(
+ Some(14140),
+ List(
+ ImplantEntry(ImplantType.Surge, Some(94), false),
+ ImplantEntry(ImplantType.DarklightVision, Some(91), false),
+ ImplantEntry(ImplantType.Targeting, Some(91), false)
+ ),
+ List(), List(),
+ List(
+ "xpe_blackops",
+ "xpe_battle_rank_9",
+ "xpe_battle_rank_23",
+ "xpe_command_rank_1",
+ "xpe_form_outfit",
+ "xpe_mail_alert",
+ "xpe_battle_rank_22",
+ "xpe_command_rank_3",
+ "xpe_form_platoon",
+ "xpe_battle_rank_13",
+ "xpe_battle_rank_12",
+ "xpe_battle_rank_10",
+ "xpe_battle_rank_14",
+ "xpe_battle_rank_15",
+ "xpe_battle_rank_8",
+ "xpe_battle_rank_11",
+ "xpe_battle_rank_21",
+ "xpe_battle_rank_20",
+ "xpe_battle_rank_18",
+ "xpe_battle_rank_19",
+ "xpe_join_platoon",
+ "xpe_battle_rank_17",
+ "xpe_battle_rank_16",
+ "xpe_join_outfit",
+ "xpe_battle_rank_25",
+ "xpe_battle_rank_24",
+ "xpe_battle_rank_7",
+ "xpe_bind_facility",
+ "xpe_bind_ams",
+ "xpe_command_rank_5",
+ "xpe_command_rank_2",
+ "xpe_instant_action",
+ "xpe_form_squad",
+ "xpe_battle_rank_4",
+ "xpe_warp_gate_usage",
+ "xpe_battle_rank_2",
+ "xpe_battle_rank_3",
+ "xpe_sanctuary_help",
+ "xpe_drop_pod",
+ "xpe_orbital_shuttle",
+ "xpe_battle_rank_6",
+ "xpe_join_squad",
+ "xpe_battle_rank_5",
+ "xpe_th_switchblade",
+ "xpe_th_router",
+ "xpe_th_flail",
+ "xpe_th_ant",
+ "xpe_th_ams",
+ "xpe_th_bfr",
+ "used_oicw",
+ "used_advanced_ace",
+ "visited_spitfire_turret",
+ "visited_spitfire_cloaked",
+ "visited_spitfire_aa",
+ "visited_tank_traps",
+ "visited_portable_manned_turret_nc",
+ "used_magcutter",
+ "visited_wall_turret",
+ "visited_ams",
+ "visited_ant",
+ "visited_dropship",
+ "visited_liberator",
+ "visited_lightgunship",
+ "visited_lightning",
+ "visited_magrider",
+ "visited_prowler",
+ "visited_quadstealth",
+ "visited_skyguard",
+ "visited_threemanheavybuggy",
+ "visited_two_man_assault_buggy",
+ "visited_twomanheavybuggy",
+ "visited_twomanhoverbuggy",
+ "visited_vanguard",
+ "visited_flail",
+ "visited_router",
+ "visited_switchblade",
+ "visited_aurora",
+ "visited_battlewagon",
+ "visited_fury",
+ "visited_quadassault",
+ "visited_galaxy_gunship",
+ "visited_apc_tr",
+ "visited_apc_vs",
+ "visited_lodestar",
+ "visited_phantasm",
+ "visited_thunderer",
+ "visited_apc_nc",
+ "visited_vulture",
+ "visited_wasp",
+ "visited_mosquito",
+ "visited_aphelion_flight",
+ "visited_aphelion_gunner",
+ "visited_colossus_flight",
+ "visited_colossus_gunner",
+ "visited_peregrine_flight",
+ "visited_peregrine_gunner",
+ "used_bank",
+ "visited_resource_silo",
+ "visited_certification_terminal",
+ "visited_med_terminal",
+ "used_nano_dispenser",
+ "visited_sensor_shield",
+ "visited_broadcast_warpgate",
+ "used_phalanx",
+ "used_phalanx_avcombo",
+ "used_phalanx_flakcombo",
+ "visited_warpgate_small",
+ "used_flamethrower",
+ "used_ancient_turret_weapon",
+ "visited_LLU_socket",
+ "used_energy_gun_nc",
+ "visited_mediumtransport",
+ "used_aphelion_immolation_cannon",
+ "used_grenade_plasma",
+ "used_grenade_jammer",
+ "visited_shield_generator",
+ "visited_motion_sensor",
+ "visited_health_crystal",
+ "visited_repair_crystal",
+ "visited_vehicle_crystal",
+ "used_grenade_frag",
+ "used_ace",
+ "visited_adv_med_terminal",
+ "used_beamer",
+ "used_bolt_driver",
+ "used_cycler",
+ "used_gauss",
+ "used_hunterseeker",
+ "used_isp",
+ "used_lancer",
+ "used_lasher",
+ "used_maelstrom",
+ "used_phoenix",
+ "used_pulsar",
+ "used_punisher",
+ "used_r_shotgun",
+ "used_radiator",
+ "used_rek",
+ "used_repeater",
+ "used_rocklet",
+ "used_striker",
+ "used_suppressor",
+ "used_thumper",
+ "visited_vanu_control_console",
+ "visited_capture_terminal",
+ "used_mini_chaingun",
+ "used_laze_pointer",
+ "used_telepad",
+ "used_spiker",
+ "used_heavy_sniper",
+ "used_command_uplink",
+ "used_firebird",
+ "used_flechette",
+ "used_heavy_rail_beam",
+ "used_ilc9",
+ "visited_generator_terminal",
+ "visited_locker",
+ "visited_external_door_lock",
+ "visited_air_vehicle_terminal",
+ "visited_galaxy_terminal",
+ "visited_implant_terminal",
+ "visited_secondary_capture",
+ "used_25mm_cannon",
+ "used_liberator_bombardier",
+ "visited_repair_silo",
+ "visited_vanu_module",
+ "used_flail_weapon",
+ "used_scythe",
+ "visited_respawn_terminal",
+ "used_ballgun",
+ "used_anniversary_guna",
+ "used_anniversary_gunb",
+ "used_anniversary_gun",
+ "used_75mm_cannon",
+ "used_apc_nc_weapon",
+ "used_apc_tr_weapon",
+ "used_apc_vs_weapon",
+ "used_flux_cannon",
+ "used_aphelion_laser",
+ "used_aphelion_plasma_rocket_pod",
+ "used_aphelion_ppa",
+ "used_aphelion_starfire",
+ "used_fluxpod",
+ "visited_bfr_terminal",
+ "used_colossus_burster",
+ "used_colossus_chaingun",
+ "used_colossus_cluster_bomb_pod",
+ "used_colossus_dual_100mm_cannons",
+ "used_colossus_tank_cannon",
+ "visited_damage_crystal",
+ "visited_energy_crystal",
+ "used_heavy_grenade_launcher",
+ "used_35mm_rotarychaingun",
+ "used_katana",
+ "used_35mm_cannon",
+ "used_reaver_weapons",
+ "used_lightning_weapons",
+ "used_med_app",
+ "used_20mm_cannon",
+ "visited_monolith_amerish",
+ "visited_monolith_ceryshen",
+ "visited_monolith_cyssor",
+ "visited_monolith_esamir",
+ "visited_monolith_forseral",
+ "visited_monolith_hossin",
+ "visited_monolith_ishundar",
+ "visited_monolith_searhus",
+ "visited_monolith_solsar",
+ "used_nc_hev_falcon",
+ "used_nc_hev_scattercannon",
+ "used_nc_hev_sparrow",
+ "used_armor_siphon",
+ "used_peregrine_dual_machine_gun",
+ "used_peregrine_dual_rocket_pods",
+ "used_peregrine_mechhammer",
+ "used_ntu_siphon",
+ "used_peregrine_particle_cannon",
+ "used_peregrine_sparrow",
+ "used_105mm_cannon",
+ "used_15mm_chaingun",
+ "used_pulsed_particle_accelerator",
+ "used_rotarychaingun",
+ "visited_deconstruction_terminal",
+ "used_skyguard_weapons",
+ "visited_generator",
+ "used_gauss_cannon",
+ "used_trek",
+ "used_vanguard_weapons",
+ "visited_ancient_air_vehicle_terminal",
+ "visited_ancient_equipment_terminal",
+ "visited_order_terminal",
+ "visited_ancient_ground_vehicle_terminal",
+ "visited_ground_vehicle_terminal",
+ "used_vulture_bombardier",
+ "used_vulture_nose_cannon",
+ "used_vulture_tail_cannon",
+ "used_wasp_weapon_system",
+ "visited_charlie05",
+ "visited_charlie06",
+ "visited_charlie07",
+ "visited_charlie08",
+ "visited_charlie09",
+ "visited_sled04",
+ "visited_sled05",
+ "visited_sled06",
+ "visited_sled07",
+ "visited_sled08",
+ "ugd06",
+ "ugd05",
+ "ugd04",
+ "ugd03",
+ "ugd02",
+ "ugd01",
+ "map99",
+ "map98",
+ "map97",
+ "map96",
+ "map15",
+ "map14",
+ "map13",
+ "map12",
+ "map11",
+ "map04",
+ "map05",
+ "map03",
+ "map01",
+ "map06",
+ "map02",
+ "map09",
+ "map07",
+ "map10"
+ ),
+ List(
+ "training_weapons01",
+ "training_warpgates",
+ "training_map",
+ "training_inventory",
+ "training_healing",
+ "training_hart",
+ "training_armors",
+ "training_welcome"
+ ),
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ None,
+ List(
+ 9,
+ 10, 11, 16, 17, 18, 30, 31, 32,
+ 37, 38, 52, 53, 57, 69, 92, 99,
+ 100, 107, 108, 109, 110, 114, 115, 116, 117, 118,
+ 127, 128, 129, 130, 131, 156, 163, 171, 178, 179,
+ 180, 181, 194, 195, 198, 201, 215, 216, 217, 222,
+ 223, 224, 225, 226, 227, 229, 230, 243, 244, 257,
+ 265, 266, 277, 278, 283, 284, 285, 286, 289, 290,
+ 296, 313, 334, 341, 342, 359, 369, 376, 377, 384,
+ 391, 392, 393, 394, 398, 408, 415, 420, 424
+ ),
+ List(),
+ false,
+ Some(Cosmetics(true, false, true, true, true))
+ )
+ val char : (Option[Int])=>DetailedCharacterData =
+ (pad_length : Option[Int]) => DetailedCharacterData(ba, bb(ba.bep, pad_length))(pad_length)
+
+ val inv : InventoryData = InventoryData(List(
+ InternalSlot(411, PlanetSideGUID(10022), 0,
+ DetailedWeaponData(2, 8, 0, List(InternalSlot(755, PlanetSideGUID(10023), 0, DetailedAmmoBoxData(8, 8))))
+ ),
+ InternalSlot(845, PlanetSideGUID(5671), 2,
+ DetailedWeaponData(2, 8, 0, List(InternalSlot(28, PlanetSideGUID(10019), 0, DetailedAmmoBoxData(8, 25))))
+ ),
+ InternalSlot(468, PlanetSideGUID(3754), 4,
+ DetailedWeaponData(2, 8, 0, List(InternalSlot(540, PlanetSideGUID(6693), 0, DetailedAmmoBoxData(8, 1))))
+ ),
+ InternalSlot(456, PlanetSideGUID(8199), 5,
+ DetailedLockerContainerData(0, Some(InventoryData(List(
+ InternalSlot(233, PlanetSideGUID(6315), 0,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(28, PlanetSideGUID(6795), 0, DetailedAmmoBoxData(0, 50))))
+ ),
+ InternalSlot(233, PlanetSideGUID(4302), 6,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(28, PlanetSideGUID(5511), 0, DetailedAmmoBoxData(0, 50))))
+ ),
+ InternalSlot(233, PlanetSideGUID(6342), 12,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(28, PlanetSideGUID(7788), 0, DetailedAmmoBoxData(0, 50))))
+ ),
+ InternalSlot(233, PlanetSideGUID(7392), 18,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(28, PlanetSideGUID(8335), 0, DetailedAmmoBoxData(0, 50))))
+ ),
+ InternalSlot(233, PlanetSideGUID(4432), 24,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(28, PlanetSideGUID(7020), 0, DetailedAmmoBoxData(0,50))))
+ ),
+ InternalSlot(716, PlanetSideGUID(4219), 90,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(50, PlanetSideGUID(9275), 0, DetailedAmmoBoxData(0, 25))))
+ ),
+ InternalSlot(716, PlanetSideGUID(3869), 96,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(50, PlanetSideGUID(6099), 0, DetailedAmmoBoxData(0, 25))))
+ ),
+ InternalSlot(716, PlanetSideGUID(8954), 102,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(50, PlanetSideGUID(5972), 0, DetailedAmmoBoxData(0, 25))))
+ ),
+ InternalSlot(233, PlanetSideGUID(7476), 108,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(28, PlanetSideGUID(5704), 0, DetailedAmmoBoxData(0, 50))))
+ ),
+ InternalSlot(272, PlanetSideGUID(8800), 114, DetailedAmmoBoxData(0, 50)),
+ InternalSlot(272, PlanetSideGUID(8649), 177, DetailedAmmoBoxData(0, 50)),
+ InternalSlot(716, PlanetSideGUID(7580), 180,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(50, PlanetSideGUID(6734), 0, DetailedAmmoBoxData(0, 25))))
+ ),
+ InternalSlot(716, PlanetSideGUID(6464), 186,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(50, PlanetSideGUID(4738), 0, DetailedAmmoBoxData(0, 25))))
+ ),
+ InternalSlot(716, PlanetSideGUID(5408), 192,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(50, PlanetSideGUID(3579), 0, DetailedAmmoBoxData(0, 25))))
+ ),
+ InternalSlot(556, PlanetSideGUID(8957), 198,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(28, PlanetSideGUID(8223), 0, DetailedAmmoBoxData(0, 100))))
+ ),
+ InternalSlot(272, PlanetSideGUID(3928), 267, DetailedAmmoBoxData(0, 50)),
+ InternalSlot(577, PlanetSideGUID(3403), 279,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(111, PlanetSideGUID(4352), 0, DetailedAmmoBoxData(0, 100))))
+ ),
+ InternalSlot(577, PlanetSideGUID(8454), 285,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(111, PlanetSideGUID(8724), 0, DetailedAmmoBoxData(0,100))))
+ ),
+ InternalSlot(272, PlanetSideGUID(3397), 357, DetailedAmmoBoxData(0, 50)),
+ InternalSlot(429, PlanetSideGUID(6695), 378,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(272, PlanetSideGUID(6842), 0, DetailedAmmoBoxData(0, 35))))
+ ),
+ InternalSlot(462, PlanetSideGUID(8304), 420,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(463, PlanetSideGUID(7089), 0, DetailedAmmoBoxData(0, 150))))
+ ),
+ InternalSlot(462, PlanetSideGUID(3346), 429,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(463, PlanetSideGUID(7557), 0, DetailedAmmoBoxData(0, 150))))
+ ),
+ InternalSlot(272, PlanetSideGUID(7515), 447, DetailedAmmoBoxData(0, 50)),
+ InternalSlot(462, PlanetSideGUID(4622), 510,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(463, PlanetSideGUID(6996), 0, DetailedAmmoBoxData(0, 150))))
+ ),
+ InternalSlot(462, PlanetSideGUID(6586), 519,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(463, PlanetSideGUID(6870), 0, DetailedAmmoBoxData(0, 150))))
+ ),
+ InternalSlot(556, PlanetSideGUID(4806), 528,
+ DetailedWeaponData(6, 0, 0, List(InternalSlot(28, PlanetSideGUID(8798), 0, DetailedAmmoBoxData(0, 100))))
+ ),
+ InternalSlot(272, PlanetSideGUID(8429), 537, DetailedAmmoBoxData(0, 50))
+ ))))
+ ),
+ InternalSlot(28, PlanetSideGUID(10018), 6, DetailedAmmoBoxData(8, 50)),
+ InternalSlot(28, PlanetSideGUID(5612), 9, DetailedAmmoBoxData(8, 50)),
+ InternalSlot(28, PlanetSideGUID(5128), 12, DetailedAmmoBoxData(8, 50)),
+ InternalSlot(29, PlanetSideGUID(8363), 33, DetailedAmmoBoxData(8, 50)),
+ InternalSlot(755, PlanetSideGUID(4090), 36, DetailedAmmoBoxData(8, 16)),
+ InternalSlot(728, PlanetSideGUID(10075), 39, DetailedREKData(4, 24))
+ ))
+ val obj = DetailedPlayerData.apply(pos, app, char, inv, DrawnSlot.None)
+
+ val msg = ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj)
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+ pkt mustEqual string_ccrider
}
}
}
diff --git a/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala b/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala
index ca939ebea..cbeccc0c9 100644
--- a/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala
+++ b/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala
@@ -50,34 +50,60 @@ class MountedVehiclesTest extends Specification {
list.head.guid mustEqual PlanetSideGUID(3776)
list.head.parentSlot mustEqual 0
list.head.obj match {
- case PlayerData(pos, app, char, Some(InventoryData(inv)), hand) =>
- pos mustEqual None
- app.app.name mustEqual "ScrawnyRonnie"
- app.app.faction mustEqual PlanetSideEmpire.TR
- app.app.sex mustEqual CharacterGender.Male
- app.app.head mustEqual 5
- app.app.voice mustEqual CharacterVoice.Voice5
- app.voice2 mustEqual 3
- app.black_ops mustEqual false
- app.lfs mustEqual false
- app.outfit_name mustEqual "Black Beret Armoured Corps"
- app.outfit_logo mustEqual 23
- app.facingPitch mustEqual 354.375f
- app.facingYawUpper mustEqual 0.0f
- app.altModelBit mustEqual None
- app.charging_pose mustEqual false
- app.on_zipline mustEqual false
- app.backpack mustEqual false
- app.ribbons.upper mustEqual MeritCommendation.MarkovVeteran
- app.ribbons.middle mustEqual MeritCommendation.HeavyInfantry4
- app.ribbons.lower mustEqual MeritCommendation.TankBuster7
- app.ribbons.tos mustEqual MeritCommendation.SixYearTR
- char.health mustEqual 100
- char.armor mustEqual 0
- char.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
- char.command_rank mustEqual 5
- char.implant_effects mustEqual None
- char.cosmetics mustEqual Some(Cosmetics(true, true, true, true, false))
+ case PlayerData(None, app, char, Some(InventoryData(inv)), DrawnSlot.None) =>
+ app match {
+ case CharacterAppearanceData(a, b, ribbons) =>
+ a.app mustEqual BasicCharacterData("ScrawnyRonnie", PlanetSideEmpire.TR, CharacterGender.Male, 5, CharacterVoice.Voice5)
+ a.black_ops mustEqual false
+ a.jammered mustEqual false
+ a.exosuit mustEqual ExoSuitType.Agile
+ a.unk1 mustEqual false
+ a.unk2 mustEqual None
+ a.unk3 mustEqual None
+ a.unk4 mustEqual 0
+ a.unk5 mustEqual 0
+ a.unk6 mustEqual 30777081L
+ a.unk7 mustEqual 1
+ a.unk8 mustEqual 4
+ a.unk9 mustEqual 0
+ a.unkA mustEqual 0
+
+ b.outfit_name mustEqual "Black Beret Armoured Corps"
+ b.outfit_logo mustEqual 23
+ b.backpack mustEqual false
+ b.facingPitch mustEqual 348.75f
+ b.facingYawUpper mustEqual 0
+ b.lfs mustEqual false
+ b.grenade_state mustEqual GrenadeState.None
+ b.is_cloaking mustEqual false
+ b.charging_pose mustEqual false
+ b.on_zipline mustEqual None
+ b.unk0 mustEqual 316554L
+ b.unk1 mustEqual false
+ b.unk2 mustEqual false
+ b.unk3 mustEqual false
+ b.unk4 mustEqual false
+ b.unk5 mustEqual false
+ b.unk6 mustEqual false
+ b.unk7 mustEqual false
+
+ case _ =>
+ ko
+ }
+
+ char match {
+ case CharacterData(health, armor, uniform, unk, cr, implants, cosmetics) =>
+ health mustEqual 100
+ armor mustEqual 0
+ uniform mustEqual UniformStyle.ThirdUpgrade
+ unk mustEqual 7
+ cr mustEqual 5
+ implants mustEqual Nil
+ cosmetics mustEqual Some(Cosmetics(true, true, true, true, false))
+ case _ =>
+ ko
+ }
+ //briefly ...
inv.size mustEqual 4
inv.head.objectClass mustEqual ObjectClass.medicalapplicator
inv.head.parentSlot mustEqual 0
@@ -87,10 +113,10 @@ class MountedVehiclesTest extends Specification {
inv(2).parentSlot mustEqual 2
inv(3).objectClass mustEqual ObjectClass.chainblade
inv(3).parentSlot mustEqual 4
- hand mustEqual DrawnSlot.None
case _ =>
ko
}
+ //back to mosquito inventory
list(1).objectClass mustEqual ObjectClass.rotarychaingun_mosquito
list(1).parentSlot mustEqual 1
case None =>
@@ -105,17 +131,51 @@ class MountedVehiclesTest extends Specification {
}
"encode (Scrawny Ronnie's mosquito)" in {
- val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
- BasicCharacterData("ScrawnyRonnie", PlanetSideEmpire.TR, CharacterGender.Male, 5, CharacterVoice.Voice5),
- 3,
- false, false,
+ val a : Int=>CharacterAppearanceA = CharacterAppearanceA(
+ BasicCharacterData(
+ "ScrawnyRonnie",
+ PlanetSideEmpire.TR,
+ CharacterGender.Male,
+ 5,
+ CharacterVoice.Voice5
+ ),
+ false,
+ false,
+ false,
+ None,
+ false,
ExoSuitType.Agile,
+ None,
+ 0,
+ 0,
+ 30777081L,
+ 1,
+ 4,
+ 0,
+ 0
+ )
+ val b : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
+ 316554L,
"Black Beret Armoured Corps",
23,
false,
- 354.375f, 0.0f,
false,
- GrenadeState.None, false, false, false,
+ false,
+ false,
+ false,
+ 348.75f, 0,
+ false,
+ GrenadeState.None,
+ false,
+ false,
+ false,
+ false,
+ false,
+ None
+ )
+
+ val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
+ a, b,
RibbonBars(
MeritCommendation.MarkovVeteran,
MeritCommendation.HeavyInfantry4,
@@ -126,9 +186,9 @@ class MountedVehiclesTest extends Specification {
val char : (Boolean,Boolean)=>CharacterData = CharacterData(
100, 0,
UniformStyle.ThirdUpgrade,
- 0,
+ 7,
5,
- None,
+ Nil,
Some(Cosmetics(true, true, true, true, false))
)
val inv : InventoryData = InventoryData(
@@ -178,15 +238,7 @@ class MountedVehiclesTest extends Specification {
)(VehicleFormat.Variant)
val msg = ObjectCreateMessage(ObjectClass.mosquito, PlanetSideGUID(4308), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
-
- val pkt_bitv = pkt.toBitVector
- val ori_bitv = string_mosquito_seated.toBitVector
- pkt_bitv.take(555) mustEqual ori_bitv.take(555) //skip 126
- pkt_bitv.drop(681).take(512) mustEqual ori_bitv.drop(681).take(512) //renew
- pkt_bitv.drop(1193).take(88) mustEqual ori_bitv.drop(1193).take(88) //skip 3
- pkt_bitv.drop(1284).take(512) mustEqual ori_bitv.drop(1284).take(512) //renew
- pkt_bitv.drop(1796) mustEqual ori_bitv.drop(1796)
- //TODO work on CharacterData to make this pass as a single stream
+ pkt mustEqual string_mosquito_seated
}
}
diff --git a/pslogin/src/test/scala/PacketCodingActorTest.scala b/pslogin/src/test/scala/PacketCodingActorTest.scala
index 9922f93fa..dc446d0dd 100644
--- a/pslogin/src/test/scala/PacketCodingActorTest.scala
+++ b/pslogin/src/test/scala/PacketCodingActorTest.scala
@@ -456,7 +456,6 @@ class PacketCodingActorITest extends ActorTest {
val pos : PlacementData = PlacementData(Vector3.Zero, Vector3.Zero)
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1),
- 3,
false,
false,
ExoSuitType.Standard,
@@ -468,7 +467,7 @@ class PacketCodingActorITest extends ActorTest {
GrenadeState.None,
false,
false,
- false,
+ None,
RibbonBars()
)
var char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData(
@@ -476,7 +475,6 @@ class PacketCodingActorITest extends ActorTest {
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(),
@@ -486,7 +484,7 @@ class PacketCodingActorITest extends ActorTest {
)
val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None)
val pkt = MultiPacketBundle(List(ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj)))
- val string_hex = hex"000900001879060000bc84b000000000000000000002040000097049006c006c006c004900490049006c006c006c0049006c0049006c006c0049006c006c006c0049006c006c0049008452700000000000000000000000000000002000000fe6a703fffffffffffffffffffffffffffffffc00000000000000000000000000000000000000019001900064000001007ec800c80000000000000000000000000000000000000001c00042c54686c7000000000000000000000000000000000000000000000000000000000000000000000000200700"
+ val string_hex = hex"000900001879060000bc84b000000000000000000002040000097049006c006c006c004900490049006c006c006c0049006c0049006c006c0049006c006c006c0049006c006c00490084524000000000000000000000000000000020000007f35703fffffffffffffffffffffffffffffffc000000000000000000000000000000000000000190019000640000000000c800c80000000000000000000000000000000000000001c00042c54686c7000000000000000000000000000000000000000000000000000000000000000000000400e0"
"PacketCodingActor" should {
"bundle an r-originating packet into an l-facing SlottedMetaPacket byte stream data (SlottedMetaPacket)" in {
@@ -547,36 +545,92 @@ class PacketCodingActorJTest extends ActorTest {
class PacketCodingActorKTest 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, CharacterVoice.Voice1),
- 3,
+ val aa : Int=>CharacterAppearanceA = CharacterAppearanceA(
+ BasicCharacterData(
+ "IlllIIIlllIlIllIlllIllI",
+ PlanetSideEmpire.VS,
+ CharacterGender.Female,
+ 41,
+ CharacterVoice.Voice1
+ ),
false,
false,
+ true,
+ None,
+ false,
ExoSuitType.Standard,
+ None,
+ 0,
+ 0,
+ 41605313L,
+ 0,
+ 0,
+ 0,
+ 65535
+ )
+ val ab : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
+ 0L,
"",
0,
false,
+ false,
+ false,
+ false,
+ false,
2.8125f, 210.9375f,
- true,
+ false,
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("xpe_sanctuary_help", "xpe_th_firemodes", "used_beamer", "map13"),
- List.empty,
+ false,
+ false,
None
)
+
+ val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
+ aa, ab,
+ RibbonBars()
+ )
+ val ba : DetailedCharacterA = DetailedCharacterA(
+ 0L,
+ 0L,
+ 0L, 0L, 0L,
+ 100, 100,
+ false,
+ 50,
+ 32831L,
+ 100, 100,
+ 0, 0, 0L,
+ List(0, 0, 0, 0, 0, 0),
+ List(
+ CertificationType.StandardAssault,
+ CertificationType.MediumAssault,
+ CertificationType.ATV,
+ CertificationType.Harasser,
+ CertificationType.StandardExoSuit,
+ CertificationType.AgileExoSuit,
+ CertificationType.ReinforcedExoSuit
+ )
+ )
+ val bb : (Long, Option[Int])=>DetailedCharacterB = DetailedCharacterB(
+ None,
+ Nil,
+ Nil, Nil,
+ List(
+ "xpe_sanctuary_help",
+ "xpe_th_firemodes",
+ "used_beamer",
+ "map13"
+ ),
+ Nil,
+ 0L, 0L, 0L, 0L, 0L,
+ Some(DCDExtra2(0, 0)),
+ Nil, Nil, false,
+ None
+ )
+ val char : (Option[Int])=>DetailedCharacterData =
+ (pad_length : Option[Int]) => DetailedCharacterData(ba, bb(ba.bep, pad_length))(pad_length)
val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None)
val list = List(
ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj),