From bf72d553e17e52b80cd70458d93b260ff291dfa1 Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 4 Oct 2018 23:12:07 -0400 Subject: [PATCH 1/4] enhancing the three primary character codecs thanks to new analysis --- .../converter/AvatarConverter.scala | 33 +++++--------- .../converter/CharacterSelectConverter.scala | 1 - .../converter/CorpseConverter.scala | 1 - .../CharacterAppearanceData.scala | 30 +++++++------ .../game/objectcreate/CharacterData.scala | 32 +++++++------ .../objectcreate/DetailedCharacterData.scala | 45 ++++--------------- .../game/objectcreate/CharacterDataTest.scala | 18 +++----- .../DetailedCharacterDataTest.scala | 14 ------ .../MountedVehiclesTest.scala | 6 +-- 9 files changed, 59 insertions(+), 121 deletions(-) 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..553aac71a 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 @@ -66,7 +66,6 @@ object AvatarConverter { def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = { CharacterAppearanceData( BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, obj.Voice), - voice2 = 0, black_ops = false, jammered = false, obj.ExoSuit, @@ -96,7 +95,7 @@ object AvatarConverter { }, //TODO not precise DressBattleRank(obj), DressCommandRank(obj), - recursiveMakeImplantEffects(obj.Implants.iterator), + MakeImplantEffectList(obj.Implants), MakeCosmetics(obj.BEP) ) } @@ -196,34 +195,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..96029e144 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 @@ -51,7 +51,6 @@ class CharacterSelectConverter extends AvatarConverter { private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = { CharacterAppearanceData( BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, CharacterVoice.Mute), - voice2 = 0, black_ops = false, jammered = false, obj.ExoSuit, 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..359b5c5be 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 @@ -46,7 +46,6 @@ class CorpseConverter extends AvatarConverter { private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = { CharacterAppearanceData( BasicCharacterData(obj.Name, obj.Faction, CharacterGender.Male, 0, CharacterVoice.Mute), - voice2 = 0, black_ops = false, jammered = false, obj.ExoSuit, 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..e512b944b 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 @@ -35,9 +35,9 @@ import shapeless.{::, HNil} * `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 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 @@ -61,7 +61,6 @@ import shapeless.{::, HNil} * @param ribbons the four merit commendation ribbon medals */ final case class CharacterAppearanceData(app : BasicCharacterData, - voice2 : Int, black_ops : Boolean, jammered : Boolean, exosuit : ExoSuitType.Value, @@ -140,13 +139,16 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] { ("sex" | CharacterGender.codec) :: ("head" | uint8L) :: ("voice" | CharacterVoice.codec) :: - ("voice2" | uint2L) :: - ignore(78) :: //unknown + uint32L :: + uint16L :: + uint16L :: + uint16L :: 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 + //TODO bool :: //ps.c 1069587 ("backpack" | bool) :: //requires alt_model flag (does NOT require health == 0) bool :: //stream misalignment when set ("facingPitch" | Angular.codec_pitch) :: @@ -166,32 +168,34 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] { }) ).exmap[CharacterAppearanceData] ( { - case _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil | - _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: HNil => + case _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil | + _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: HNil => Attempt.Failure(Err("invalid character appearance data; can not encode alternate model without required bit set")) - case faction :: bops :: _ :: _ :: jamd :: false :: 0 :: name :: suit :: _ :: sex :: head :: v1 :: v2 :: _ :: _ :: _/*has_outfit_name*/ :: outfit :: logo :: _ :: bpack :: false :: facingPitch :: facingYawUpper :: _ :: _ :: _ :: lfs :: gstate :: cloaking :: _ :: false :: charging :: _ :: zipline :: ribbons :: HNil => + case faction :: bops :: _ :: _ :: jamd :: false :: 0 :: name :: suit :: _ :: sex :: head :: v1 :: _ :: _ :: _ :: _ :: _ :: _/*has_outfit_name*/ :: outfit :: logo :: _ :: bpack :: false :: facingPitch :: facingYawUpper :: _ :: _ :: _ :: lfs :: gstate :: cloaking :: _ :: false :: charging :: _ :: zipline :: ribbons :: 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) + CharacterAppearanceData(BasicCharacterData(name, faction, sex, head, v1), bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons)(name_padding) ) case _ => Attempt.Failure(Err("invalid character appearance data; can not encode")) }, { - case CharacterAppearanceData(BasicCharacterData(name, PlanetSideEmpire.NEUTRAL, _, _, _), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => + case CharacterAppearanceData(BasicCharacterData(name, PlanetSideEmpire.NEUTRAL, _, _, _), _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Attempt.failure(Err(s"character $name's faction can not declare as neutral")) - case CharacterAppearanceData(BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons) => + case CharacterAppearanceData(BasicCharacterData(name, faction, sex, head, voice), 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 + var volume : Long = 192L if(zipline || bpack) { alt_model = true alt_model_extrabit = Some(false) + volume = 0L } 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_model :: () :: jamd :: false :: 0 :: name :: suit :: () :: sex :: head :: voice :: volume :: 0 :: 0 :: 0 :: 0 :: has_outfit_name :: outfit :: logo :: () :: bpack :: false :: facingPitch :: facingYawUpper :: () :: alt_model_extrabit :: () :: lfs :: gstate :: cloaking :: () :: false :: charging :: () :: zipline :: ribbons :: HNil ) case _ => 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..446ceee4b 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,8 +8,7 @@ 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. @@ -65,7 +64,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 @@ -82,7 +82,7 @@ final case class CharacterData(health : 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 { @@ -90,7 +90,7 @@ final case class CharacterData(health : Int, 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 } @@ -107,22 +107,21 @@ 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 + ignore(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 :: _ :: 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)) @@ -132,7 +131,7 @@ object CharacterData extends Marshallable[CharacterData] { { case CharacterData(health, armor, uniform, _, cr, implant_effects, cosmetics) => val newHealth = if(is_backpack) { 0 } else { health } - Attempt.Successful(newHealth :: armor :: uniform :: () :: cr :: false :: implant_effects :: cosmetics :: HNil) + Attempt.Successful(newHealth :: armor :: uniform :: () :: cr :: implant_effects :: cosmetics :: HNil) case _ => Attempt.Failure(Err("invalid character data; can not decode")) @@ -141,23 +140,22 @@ object CharacterData extends Marshallable[CharacterData] { def codec_seated(is_backpack : Boolean) : Codec[CharacterData] = ( ("uniform_upgrade" | UniformStyle.codec) >>:~ { style => - ignore(3) :: //unknown + ignore(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 => + case uniform :: _ :: cr :: implant_effects :: cosmetics :: HNil => Attempt.Successful(new CharacterData(100, 0, uniform, 0, 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, _, cr, implant_effects, cosmetics) => + Attempt.Successful(uniform :: () :: 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..cac2796b5 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 @@ -44,12 +44,6 @@ 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; @@ -73,9 +67,6 @@ final case class DetailedCharacterData(bep : Long, healthMax : Int, health : Int, armor : Int, - unk1 : Int, //1 - unk2 : Int, //7 - unk3 : Int, //7 staminaMax : Int, stamina : Int, certs : List[CertificationType.Value], @@ -111,24 +102,6 @@ final case class DetailedCharacterData(bep : Long, } 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) - /** * `Codec` for entries in the `List` of implants. */ @@ -255,16 +228,14 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { def codec(pad_length : Option[Int]) : Codec[DetailedCharacterData] = ( ("bep" | uint32L) >>:~ { bep => ("cep" | uint32L) :: - ignore(96) :: + uint32L :: + uint32L :: + uint32L :: ("healthMax" | uint16L) :: ("health" | uint16L) :: ignore(1) :: ("armor" | uint16L) :: - ignore(9) :: - ("unk1" | uint8L) :: - ignore(8) :: - ("unk2" | uint4L) :: - ("unk3" | uintL(3)) :: + uint32 :: //TODO switch endianness ("staminaMax" | uint16L) :: ("stamina" | uint16L) :: ignore(147) :: @@ -290,14 +261,14 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { } ).exmap[DetailedCharacterData] ( { - case bep :: cep :: _ :: hpmax :: hp :: _ :: armor :: _ :: u1 :: _ :: u2 :: u3 :: stamax :: stam :: _ :: certs :: _ :: _ :: implants :: _ :: _ :: fte0 :: fte1 :: _ :: tut0 :: tut1 :: _ :: _ :: _ :: cosmetics :: HNil => + case bep :: cep :: 0 :: 0 :: 0 :: hpmax :: hp :: _ :: armor :: 32831L :: 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)) + Attempt.successful(new DetailedCharacterData(bep, cep, hpmax, hp, armor, stamax, stam, certs, implants, fteList, tutList, cosmetics)(pad_length)) }, { - case DetailedCharacterData(bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cos) => + case DetailedCharacterData(bep, cep, hpmax, hp, armor, stamax, stam, certs, implants, fteList, tutList, cos) => val implantCapacity : Int = numberOfImplantSlots(bep) val implantList = if(implants.length > implantCapacity) { implants.slice(0, implantCapacity) @@ -318,7 +289,7 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { } 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) + Attempt.successful(bep :: cep :: 0L :: 0L :: 0L :: hpmax :: hp :: () :: armor :: 32831L :: stamax :: stam :: () :: certs :: None :: () :: implantList :: () :: fteList.size.toLong :: firstEvent :: fteListCopy :: tutList.size.toLong :: firstTutorial :: tutListCopy :: () :: br24 :: () :: cosmetics :: HNil) } ) diff --git a/common/src/test/scala/game/objectcreate/CharacterDataTest.scala b/common/src/test/scala/game/objectcreate/CharacterDataTest.scala index 6115f2311..e1c557324 100644 --- a/common/src/test/scala/game/objectcreate/CharacterDataTest.scala +++ b/common/src/test/scala/game/objectcreate/CharacterDataTest.scala @@ -38,7 +38,6 @@ class CharacterDataTest extends Specification { 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 @@ -60,8 +59,8 @@ class CharacterDataTest extends Specification { 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 @@ -131,7 +130,6 @@ class CharacterDataTest extends Specification { 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 @@ -175,7 +173,6 @@ class CharacterDataTest extends Specification { 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 @@ -197,7 +194,7 @@ class CharacterDataTest extends Specification { 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 @@ -228,7 +225,6 @@ class CharacterDataTest extends Specification { 5, CharacterVoice.Voice5 ), - 3, false, false, ExoSuitType.Reinforced, @@ -250,7 +246,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( @@ -284,7 +280,6 @@ class CharacterDataTest extends Specification { 5, CharacterVoice.Voice5 ), - 3, false, false, ExoSuitType.Reinforced, @@ -306,7 +301,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( @@ -337,7 +332,6 @@ class CharacterDataTest extends Specification { 10, CharacterVoice.Voice2 ), - 0, false, false, ExoSuitType.MAX, @@ -359,7 +353,7 @@ class CharacterDataTest extends Specification { 0, 0, UniformStyle.ThirdUpgrade, 2, - None, + List(), Some(Cosmetics(true, true, true, true, false)) ) val obj = PlayerData(pos, app, char, DrawnSlot.Pistol1) diff --git a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala index b571749be..2ce1c8c05 100644 --- a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala +++ b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala @@ -39,7 +39,6 @@ class DetailedCharacterDataTest extends Specification { 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 @@ -63,9 +62,6 @@ class DetailedCharacterDataTest extends Specification { 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 @@ -175,7 +171,6 @@ class DetailedCharacterDataTest extends Specification { 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 @@ -199,9 +194,6 @@ class DetailedCharacterDataTest extends Specification { 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 @@ -382,7 +374,6 @@ class DetailedCharacterDataTest extends Specification { 41, CharacterVoice.Voice1 ), - 3, false, false, ExoSuitType.Standard, @@ -402,7 +393,6 @@ class DetailedCharacterDataTest extends Specification { 0, 100, 100, 50, - 1, 7, 7, 100, 100, List( CertificationType.StandardAssault, @@ -453,7 +443,6 @@ class DetailedCharacterDataTest extends Specification { 41, CharacterVoice.Voice1 ), - 3, false, false, ExoSuitType.Standard, @@ -473,7 +462,6 @@ class DetailedCharacterDataTest extends Specification { 0, 100, 100, 50, - 1, 7, 7, 100, 100, List( CertificationType.StandardAssault, @@ -520,7 +508,6 @@ class DetailedCharacterDataTest extends Specification { ) val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData( BasicCharacterData("KiCkJr", PlanetSideEmpire.NC, CharacterGender.Male, 24, CharacterVoice.Voice4), - 3, false, false, ExoSuitType.Agile, "", @@ -541,7 +528,6 @@ class DetailedCharacterDataTest extends Specification { 6366766, 694787, 100, 100, 100, - 1, 7, 7, 100, 46, List( CertificationType.StandardAssault, diff --git a/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala b/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala index ca939ebea..72653dda3 100644 --- a/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala +++ b/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala @@ -57,7 +57,6 @@ class MountedVehiclesTest extends Specification { 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" @@ -76,7 +75,7 @@ class MountedVehiclesTest extends Specification { char.armor mustEqual 0 char.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade char.command_rank mustEqual 5 - char.implant_effects mustEqual None + char.implant_effects.isEmpty mustEqual true char.cosmetics mustEqual Some(Cosmetics(true, true, true, true, false)) inv.size mustEqual 4 inv.head.objectClass mustEqual ObjectClass.medicalapplicator @@ -107,7 +106,6 @@ 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, ExoSuitType.Agile, "Black Beret Armoured Corps", @@ -128,7 +126,7 @@ class MountedVehiclesTest extends Specification { UniformStyle.ThirdUpgrade, 0, 5, - None, + Nil, Some(Cosmetics(true, true, true, true, false)) ) val inv : InventoryData = InventoryData( From 59569f1a7d662fb5157f775cb135fb57f7b3c9c2 Mon Sep 17 00:00:00 2001 From: FateJH Date: Sun, 7 Oct 2018 23:34:06 -0400 Subject: [PATCH 2/4] added params for all CharacterAppearanceData codec regions; added a param for the three bits of CharacterData not previously supported; repaired CharacterData tests; over-all: padding calculations are not yet adequate --- .../converter/AvatarConverter.scala | 2 +- .../converter/CharacterSelectConverter.scala | 2 +- .../converter/CorpseConverter.scala | 2 +- .../CharacterAppearanceData.scala | 424 ++++++++--- .../game/objectcreate/CharacterData.scala | 24 +- .../objectcreate/DetailedCharacterData.scala | 239 ++++-- .../packet/game/objectcreate/PlayerData.scala | 14 +- .../game/objectcreate/VehicleData.scala | 4 +- .../game/objectcreate/CharacterDataTest.scala | 333 +++++--- .../DetailedCharacterDataTest.scala | 709 +++++++++--------- .../MountedVehiclesTest.scala | 354 ++++----- .../test/scala/PacketCodingActorTest.scala | 8 +- 12 files changed, 1305 insertions(+), 810 deletions(-) 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 553aac71a..2b4e511ab 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 @@ -78,7 +78,7 @@ object AvatarConverter { GrenadeState.None, is_cloaking = false, charging_pose = false, - on_zipline = false, + on_zipline = None, RibbonBars() ) } 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 96029e144..2a9954dfa 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 @@ -63,7 +63,7 @@ class CharacterSelectConverter extends AvatarConverter { GrenadeState.None, is_cloaking = false, charging_pose = false, - on_zipline = false, + on_zipline = None, RibbonBars() ) } 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 359b5c5be..3c2b3c1ff 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 @@ -58,7 +58,7 @@ class CorpseConverter extends AvatarConverter { GrenadeState.None, is_cloaking = false, charging_pose = false, - on_zipline = false, + on_zipline = None, RibbonBars() ) } 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 e512b944b..7a60972d3 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,63 +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, - 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. @@ -101,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. @@ -109,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. @@ -125,77 +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) :: - uint32L :: - uint16L :: - uint16L :: - uint16L :: - 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 - //TODO bool :: //ps.c 1069587 - ("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) :: //TODO factor 16u into bitsize + ("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 :: _ :: _ :: _ :: _ :: _ :: _/*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), 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, voice), 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 - var volume : Long = 192L - if(zipline || bpack) { - alt_model = true - alt_model_extrabit = Some(false) - volume = 0L - } + 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 :: voice :: volume :: 0 :: 0 :: 0 :: 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 _ => @@ -203,5 +327,87 @@ 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) { + if(outfit.length == 0) { + u0 + } + else { + outfit.length.toLong + } + } + else { + if(outfit.length == 0) { + 0L + } + else { + u0 + } + } //TODO this is a kludge; unk0 must be non-zero if outfit_name is defined, and zero if empty + 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 446ceee4b..db1ef9679 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 @@ -11,7 +11,7 @@ import shapeless.{::, HNil} * 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 @@ -55,7 +55,7 @@ object UniformStyle extends Enumeration { * @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; @@ -114,24 +114,24 @@ object CharacterData extends Marshallable[CharacterData] { ("health" | uint8L) :: //dead state when health == 0 ("armor" | uint8L) :: (("uniform_upgrade" | UniformStyle.codec) >>:~ { style => - ignore(3) :: //uniform_upgrade is actually interpreted as a 6u field, but the lower 3u seems to be discarded + uint(3) :: //uniform_upgrade is actually interpreted as a 6u field, but the lower 3u seems to be discarded ("command_rank" | uintL(3)) :: listOfN(uint2, "implant_effects" | ImplantEffects.codec) :: conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec) }) ).exmap[CharacterData] ( { - case health :: armor :: uniform :: _ :: cr :: 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 :: 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")) @@ -140,22 +140,22 @@ object CharacterData extends Marshallable[CharacterData] { def codec_seated(is_backpack : Boolean) : Codec[CharacterData] = ( ("uniform_upgrade" | UniformStyle.codec) >>:~ { style => - ignore(3) :: //uniform_upgrade is actually interpreted as a 6u field, but the lower 3u seems to be discarded + uint(3) :: //uniform_upgrade is actually interpreted as a 6u field, but the lower 3u seems to be discarded ("command_rank" | uintL(3)) :: listOfN(uint2, "implant_effects" | ImplantEffects.codec) :: conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec) } ).exmap[CharacterData] ( { - case uniform :: _ :: cr :: 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 CharacterData(_, _, uniform, _, cr, implant_effects, cosmetics) => - Attempt.Successful(uniform :: () :: cr :: 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 cac2796b5..5ef554e37 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 @@ -21,18 +21,28 @@ import scala.annotation.tailrec final case class ImplantEntry(implant : ImplantType.Value, activation : Option[Int]) extends StreamBitSize { override def bitsize : Long = { - val activationSize = if(activation.isDefined) { 12L } else { 5L } - 5L + activationSize + val activationSize = if(activation.isDefined) { 8L } else { 1L } + 9L + activationSize } } +final case class DCDExtra1(unk1 : String, + unk2 : Int) extends StreamBitSize { + override def bitsize : Long = 16L + StreamBitSize.stringBitSize(unk1) +} + +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.
*
* 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. + * 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. * @param bep the avatar's battle experience points, which determines his Battle Rank @@ -70,7 +80,10 @@ final case class DetailedCharacterData(bep : Long, staminaMax : Int, stamina : Int, certs : List[CertificationType.Value], + unk1 : Option[Long], implants : List[ImplantEntry], + unk2 : List[DCDExtra1], + unk3 : List[DCDExtra1], firstTimeEvents : List[String], tutorials : List[String], cosmetics : Option[Cosmetics]) @@ -78,52 +91,89 @@ final case class DetailedCharacterData(bep : Long, 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 + //cert list + val certSize = (certs.length + 1) * 8 + //unk1 + val unk1Size = if(unk1.isDefined) { 32L } else { 0L } + //implant list + var implantSize : Long = 0L for(entry <- implants) { implantSize += entry.bitsize } val implantPadding = DetailedCharacterData.implantFieldPadding(implants, pad_length) - val fteLen = firstTimeEvents.size //fte list + //fte list + val fteLen = firstTimeEvents.size var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen, implantPadding) for(str <- firstTimeEvents) { eventListSize += StreamBitSize.stringBitSize(str) } - val tutLen = tutorials.size //tutorial list + //unk2, unk3, TODO padding + val unk2Len = unk2.size + val unk3Len = unk3.size + val unkAllLen = unk2Len + unk3Len + val unk2_3ListSize : Long = 16L + (if(unk2Len > 0) { + unkAllLen * unk2.head.bitsize + } + else if(unk3Len > 0) { + unkAllLen * unk3.head.bitsize + } + else { + 0 + }) + //tutorial list + val tutLen = tutorials.size var tutorialListSize : Long = 32L + DetailedCharacterData.tutPadding(fteLen, tutLen, implantPadding) for(str <- tutorials) { tutorialListSize += StreamBitSize.stringBitSize(str) } - 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 extraBitSize : Long = if(br24) { 0L } else { 13L } + //TODO DCDExtra2 + //TODO last List of String values, and padding val cosmeticsSize : Long = if(br24) { cosmetics.get.bitsize } else { 0L } - 598L + certSize + implantSize + eventListSize + extraBitSize + cosmeticsSize + tutorialListSize + 615L + certSize + unk1Size + implantSize + eventListSize + unk2_3ListSize + tutorialListSize + extraBitSize + cosmeticsSize } } object DetailedCharacterData extends Marshallable[DetailedCharacterData] { + 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, staminaMax, stamina, certs, None, implants, Nil, Nil, firstTimeEvents, tutorials, cosmetics) + } + /** * `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) + ImplantEntry(ImplantType(implant), None) //TODO catch potential NoSuchElementException? case implant :: false :: extra :: HNil => - ImplantEntry(implant, Some(extra)) + ImplantEntry(ImplantType(implant), Some(extra)) //TODO catch potential NoSuchElementException? }, { case ImplantEntry(implant, None) => - implant :: true :: 0 :: HNil + implant.id :: true :: 0 :: HNil case ImplantEntry(implant, Some(extra)) => - implant :: false :: extra :: HNil + implant.id :: false :: extra :: HNil } ) @@ -148,6 +198,64 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { } } + private def dcd_list_codec(pad : Int) : Codec[List[DCDExtra1]] = ( + uint8 >>:~ { size => + conditional(size > 0, dcd_extra1_codec(pad)) :: + 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 + } + ) + + 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 + } + ) + + 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 + } + ) + /** * The padding value of the first entry in either of two byte-aligned `List` structures. * @param implants implant entries @@ -184,13 +292,23 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { } } + /** + * A variant of `ftePadding` where the length of the list has been uncurried. + * @see `ftePadding(Int)(Long)` + */ + private def ftePadding(len : Long, implantPadding : Int) : Int = { + //TODO the proper padding length should reflect all variability in the stream prior to this point + ftePadding(implantPadding)(len) + } + /** * Get the padding of the first entry in the first time events list. + * @see `ftePadding(Long, Int)` * @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` */ - private def ftePadding(len : Long, implantPadding : Int) : Int = { + private def ftePadding(implantPadding : Int)(len : Long) : Int = { //TODO the proper padding length should reflect all variability in the stream prior to this point if(len > 0) { implantPadding @@ -206,12 +324,13 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { * 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. + * @see `tutPadding(Long, Long, Int)` * @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 + * @param len2 the length of the tutorial list, curried * @return the pad length in bits `n < 8` */ - private def tutPadding(len : Long, len2 : Long, implantPadding : Int) : Int = { + private def tutPadding(len : Long, implantPadding : Int)(len2 : Long) : Int = { if(len > 0) { 0 //automatic alignment from previous List } @@ -223,8 +342,19 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { } } + /** + * A variant of `tutPadding` where the length of the second list has been uncurried. + * @see `tutPadding(Long, Int)(Long)` + */ + private def tutPadding(len : Long, len2 : Long, implantPadding : Int) : Int = tutPadding(len, implantPadding)(len2) + def isBR24(bep : Long) : Boolean = bep > 2286230 + private val dcd_extra2_codec : Codec[DCDExtra2] = ( + uint(5) :: + uint8L + ).as[DCDExtra2] + def codec(pad_length : Option[Int]) : Codec[DetailedCharacterData] = ( ("bep" | uint32L) >>:~ { bep => ("cep" | uint32L) :: @@ -233,42 +363,46 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { uint32L :: ("healthMax" | uint16L) :: ("health" | uint16L) :: - ignore(1) :: + bool :: ("armor" | uint16L) :: - uint32 :: //TODO switch endianness + uint32 :: //endianness is important here ("staminaMax" | uint16L) :: ("stamina" | uint16L) :: - ignore(147) :: + uint16L :: + uint(3) :: + uint32L :: + PacketHelpers.listOfNSized(6, uint16L) :: ("certs" | listOfN(uint8L, CertificationType.codec)) :: - optional(bool, uint32L) :: //ask about sample CCRIDER - ignore(4) :: + optional(bool, "unk1" | uint32L) :: //ask about sample CCRIDER (("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) - }) - }) + ("unk2" | dcd_list_codec(0)) :: //TODO pad value + ("unk3" | dcd_list_codec(0)) :: //TODO pad value + (("firstTimeEvents" | eventsListCodec(ftePadding(implantFieldPadding(implants, pad_length)))) >>:~ { fte => + ("tutorials" | eventsListCodec(tutPadding(fte.length, implantFieldPadding(implants, pad_length)))) >>:~ { _ => + uint32L :: + uint32L :: + uint32L :: + uint32L :: + uint32L :: + (bool >>:~ { br24 => //BR24+ + conditional(!br24, dcd_extra2_codec) :: + listOfN(uint16L, uint32L) :: + listOfN(uint16L, PacketHelpers.encodedString) :: //TODO pad value + bool :: + conditional(br24, Cosmetics.codec) + }) + } }) }) } ).exmap[DetailedCharacterData] ( { - case bep :: cep :: 0 :: 0 :: 0 :: hpmax :: hp :: _ :: armor :: 32831L :: 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(new DetailedCharacterData(bep, cep, hpmax, hp, armor, stamax, stam, certs, implants, fteList, tutList, cosmetics)(pad_length)) + case o @ (bep :: cep :: 0 :: 0 :: 0 :: hpmax :: hp :: _ :: armor :: 32831L :: stamax :: stam :: 0 :: _ :: _ :: _ :: certs :: unk1 :: implants :: unk2 :: unk3 :: fteList :: tutList :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: cosmetics :: HNil) => + println(o) + Attempt.successful(DetailedCharacterData(bep, cep, hpmax, hp, armor, stamax, stam, certs, unk1, implants, unk2, unk3, fteList, tutList, cosmetics)(pad_length)) }, { - case DetailedCharacterData(bep, cep, hpmax, hp, armor, stamax, stam, certs, implants, fteList, tutList, cos) => + case DetailedCharacterData(bep, cep, hpmax, hp, armor, stamax, stam, certs, unk1, implants, unk2, unk3, fteList, tutList, cos) => val implantCapacity : Int = numberOfImplantSlots(bep) val implantList = if(implants.length > implantCapacity) { implants.slice(0, implantCapacity) @@ -276,20 +410,15 @@ 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 dcdExtra2Field : Option[DCDExtra2] = if(!br24) { + Some(DCDExtra2(0, 0)) + } + else { + None + } val cosmetics : Option[Cosmetics] = if(br24) { cos } else { None } - Attempt.successful(bep :: cep :: 0L :: 0L :: 0L :: hpmax :: hp :: () :: armor :: 32831L :: stamax :: stam :: () :: certs :: None :: () :: implantList :: () :: fteList.size.toLong :: firstEvent :: fteListCopy :: tutList.size.toLong :: firstTutorial :: tutListCopy :: () :: br24 :: () :: cosmetics :: HNil) + Attempt.successful(bep :: cep :: 0L :: 0L :: 0L :: hpmax :: hp :: false :: armor :: 32831L :: stamax :: stam :: 0 :: 0 :: 0L :: List(0, 0, 0, 0, 0, 0) :: certs :: unk1 :: implantList :: unk2 :: unk3 :: fteList :: tutList :: 0L :: 0L :: 0L :: 0L :: 0L :: br24 :: dcdExtra2Field :: Nil :: Nil :: false :: cosmetics :: HNil) } ) 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 e1c557324..d0d14d1b6 100644 --- a/common/src/test/scala/game/objectcreate/CharacterDataTest.scala +++ b/common/src/test/scala/game/objectcreate/CharacterDataTest.scala @@ -33,27 +33,53 @@ 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.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 @@ -67,6 +93,7 @@ class CharacterDataTest extends Specification { 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 @@ -115,7 +142,7 @@ class CharacterDataTest extends Specification { ko } } - +// "decode (seated)" in { PacketCoding.DecodePacket(string_seated).require match { case ObjectCreateMessage(len, cls, guid, parent, data) => @@ -125,28 +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.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 } @@ -168,27 +221,54 @@ 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.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 @@ -201,6 +281,7 @@ class CharacterDataTest extends Specification { 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 _ => @@ -217,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, @@ -227,14 +308,41 @@ class CharacterDataTest extends Specification { ), 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, @@ -245,6 +353,7 @@ class CharacterDataTest extends Specification { val char : (Boolean,Boolean)=>CharacterData = CharacterData( 255, 253, UniformStyle.ThirdUpgrade, + 7, 5, List(ImplantEffects.NoEffects), Some(Cosmetics(true, true, true, true, false)) @@ -261,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, @@ -282,14 +384,41 @@ class CharacterDataTest extends Specification { ), 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, @@ -324,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, @@ -333,15 +462,42 @@ class CharacterDataTest extends Specification { CharacterVoice.Voice2 ), 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, @@ -352,7 +508,7 @@ class CharacterDataTest extends Specification { val char : (Boolean,Boolean)=>CharacterData = CharacterData( 0, 0, UniformStyle.ThirdUpgrade, - 2, + 1, List(), Some(Cosmetics(true, true, true, true, false)) ) @@ -360,14 +516,11 @@ class CharacterDataTest extends Specification { 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 2ce1c8c05..ce63610cb 100644 --- a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala +++ b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala @@ -21,345 +21,345 @@ class DetailedCharacterDataTest extends Specification { 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" "DetailedCharacterData" should { - "decode" in { - PacketCoding.DecodePacket(string).require match { - case ObjectCreateDetailedMessage(len, cls, guid, parent, data) => - len mustEqual 3159 - cls mustEqual ObjectClass.avatar - guid mustEqual PlanetSideGUID(75) - parent.isDefined mustEqual false - data match { - case Some(DetailedPlayerData(Some(pos), basic, char, inv, hand)) => - pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f) - pos.orient mustEqual Vector3(0, 0, 36.5625f) - pos.vel.isDefined mustEqual false - - basic.app.name mustEqual "IlllIIIlllIlIllIlllIllI" - basic.app.faction mustEqual PlanetSideEmpire.VS - basic.app.sex mustEqual CharacterGender.Female - basic.app.head mustEqual 41 - basic.app.voice mustEqual CharacterVoice.Voice1 - basic.black_ops mustEqual false - basic.jammered mustEqual false - basic.exosuit mustEqual ExoSuitType.Standard - basic.outfit_name mustEqual "" - basic.outfit_logo mustEqual 0 - basic.backpack mustEqual false - basic.facingPitch mustEqual 2.8125f - basic.facingYawUpper mustEqual 210.9375f - basic.lfs mustEqual true - basic.grenade_state mustEqual GrenadeState.None - basic.is_cloaking mustEqual false - basic.charging_pose mustEqual false - basic.on_zipline mustEqual false - basic.ribbons.upper mustEqual MeritCommendation.None - basic.ribbons.middle mustEqual MeritCommendation.None - basic.ribbons.lower mustEqual MeritCommendation.None - basic.ribbons.tos mustEqual MeritCommendation.None - - char.bep mustEqual 0 - char.cep mustEqual 0 - char.healthMax mustEqual 100 - char.health mustEqual 100 - char.armor mustEqual 50 //standard exosuit value - char.staminaMax mustEqual 100 - char.stamina mustEqual 100 - char.certs.length mustEqual 7 - char.certs.head mustEqual CertificationType.StandardAssault - char.certs(1) mustEqual CertificationType.MediumAssault - char.certs(2) mustEqual CertificationType.ATV - char.certs(3) mustEqual CertificationType.Harasser - char.certs(4) mustEqual CertificationType.StandardExoSuit - char.certs(5) mustEqual CertificationType.AgileExoSuit - char.certs(6) mustEqual CertificationType.ReinforcedExoSuit - char.implants.length mustEqual 0 - char.firstTimeEvents.size mustEqual 4 - char.firstTimeEvents.head mustEqual "xpe_sanctuary_help" - char.firstTimeEvents(1) mustEqual "xpe_th_firemodes" - char.firstTimeEvents(2) mustEqual "used_beamer" - char.firstTimeEvents(3) mustEqual "map13" - char.tutorials.size mustEqual 0 - char.cosmetics.isDefined mustEqual false - inv.isDefined mustEqual true - val inventory = inv.get.contents - inventory.size mustEqual 10 - //0 - inventory.head.objectClass mustEqual ObjectClass.beamer - inventory.head.guid mustEqual PlanetSideGUID(76) - inventory.head.parentSlot mustEqual 0 - var wep = inventory.head.obj.asInstanceOf[DetailedWeaponData] - wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell - wep.ammo.head.guid mustEqual PlanetSideGUID(77) - wep.ammo.head.parentSlot mustEqual 0 - wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 16 - //1 - inventory(1).objectClass mustEqual ObjectClass.suppressor - inventory(1).guid mustEqual PlanetSideGUID(78) - inventory(1).parentSlot mustEqual 2 - wep = inventory(1).obj.asInstanceOf[DetailedWeaponData] - wep.ammo.head.objectClass mustEqual ObjectClass.bullet_9mm - wep.ammo.head.guid mustEqual PlanetSideGUID(79) - wep.ammo.head.parentSlot mustEqual 0 - wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 25 - //2 - inventory(2).objectClass mustEqual ObjectClass.forceblade - inventory(2).guid mustEqual PlanetSideGUID(80) - inventory(2).parentSlot mustEqual 4 - wep = inventory(2).obj.asInstanceOf[DetailedWeaponData] - wep.ammo.head.objectClass mustEqual ObjectClass.melee_ammo - wep.ammo.head.guid mustEqual PlanetSideGUID(81) - wep.ammo.head.parentSlot mustEqual 0 - wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1 - //3 - inventory(3).objectClass mustEqual ObjectClass.locker_container - inventory(3).guid mustEqual PlanetSideGUID(82) - inventory(3).parentSlot mustEqual 5 - inventory(3).obj.isInstanceOf[DetailedLockerContainerData] mustEqual true - inventory(3).obj.asInstanceOf[DetailedLockerContainerData].inventory.isDefined mustEqual false - //4 - inventory(4).objectClass mustEqual ObjectClass.bullet_9mm - inventory(4).guid mustEqual PlanetSideGUID(83) - inventory(4).parentSlot mustEqual 6 - inventory(4).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 - //5 - inventory(5).objectClass mustEqual ObjectClass.bullet_9mm - inventory(5).guid mustEqual PlanetSideGUID(84) - inventory(5).parentSlot mustEqual 9 - inventory(5).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 - //6 - inventory(6).objectClass mustEqual ObjectClass.bullet_9mm - inventory(6).guid mustEqual PlanetSideGUID(85) - inventory(6).parentSlot mustEqual 12 - inventory(6).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 - //7 - inventory(7).objectClass mustEqual ObjectClass.bullet_9mm_AP - inventory(7).guid mustEqual PlanetSideGUID(86) - inventory(7).parentSlot mustEqual 33 - inventory(7).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 - //8 - inventory(8).objectClass mustEqual ObjectClass.energy_cell - inventory(8).guid mustEqual PlanetSideGUID(87) - inventory(8).parentSlot mustEqual 36 - inventory(8).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 - //9 - inventory(9).objectClass mustEqual ObjectClass.remote_electronics_kit - inventory(9).guid mustEqual PlanetSideGUID(88) - inventory(9).parentSlot mustEqual 39 - //the rek has data but none worth testing here - hand mustEqual DrawnSlot.Pistol1 - case _ => - ko - } - case _ => - ko - } - } - - "decode (character, seated)" in { - PacketCoding.DecodePacket(string_seated).require match { - case ObjectCreateDetailedMessage(len, cls, guid, parent, data) => - len mustEqual 3103 - cls mustEqual ObjectClass.avatar - guid mustEqual PlanetSideGUID(75) - parent.isDefined mustEqual true - parent.get.guid mustEqual PlanetSideGUID(43981) - 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.black_ops mustEqual false - basic.jammered mustEqual false - basic.exosuit mustEqual ExoSuitType.Standard - basic.outfit_name mustEqual "" - basic.outfit_logo mustEqual 0 - basic.backpack mustEqual false - basic.facingPitch mustEqual 2.8125f - basic.facingYawUpper mustEqual 210.9375f - basic.lfs mustEqual true - basic.grenade_state mustEqual GrenadeState.None - basic.is_cloaking mustEqual false - basic.charging_pose mustEqual false - basic.on_zipline mustEqual false - basic.ribbons.upper mustEqual MeritCommendation.None - basic.ribbons.middle mustEqual MeritCommendation.None - basic.ribbons.lower mustEqual MeritCommendation.None - basic.ribbons.tos mustEqual MeritCommendation.None - - char.bep mustEqual 0 - char.cep mustEqual 0 - char.healthMax mustEqual 100 - char.health mustEqual 100 - char.armor mustEqual 50 //standard exosuit value - char.staminaMax mustEqual 100 - char.stamina mustEqual 100 - char.certs.length mustEqual 7 - char.certs.head mustEqual CertificationType.StandardAssault - char.certs(1) mustEqual CertificationType.MediumAssault - char.certs(2) mustEqual CertificationType.ATV - char.certs(3) mustEqual CertificationType.Harasser - char.certs(4) mustEqual CertificationType.StandardExoSuit - char.certs(5) mustEqual CertificationType.AgileExoSuit - char.certs(6) mustEqual CertificationType.ReinforcedExoSuit - char.implants.length mustEqual 0 - char.firstTimeEvents.size mustEqual 4 - char.firstTimeEvents.head mustEqual "xpe_sanctuary_help" - char.firstTimeEvents(1) mustEqual "xpe_th_firemodes" - char.firstTimeEvents(2) mustEqual "used_beamer" - char.firstTimeEvents(3) mustEqual "map13" - char.tutorials.size mustEqual 0 - char.cosmetics.isDefined mustEqual false - inv.isDefined mustEqual true - val inventory = inv.get.contents - inventory.size mustEqual 10 - //0 - inventory.head.objectClass mustEqual ObjectClass.beamer - inventory.head.guid mustEqual PlanetSideGUID(76) - inventory.head.parentSlot mustEqual 0 - var wep = inventory.head.obj.asInstanceOf[DetailedWeaponData] - wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell - wep.ammo.head.guid mustEqual PlanetSideGUID(77) - wep.ammo.head.parentSlot mustEqual 0 - wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 16 - //1 - inventory(1).objectClass mustEqual ObjectClass.suppressor - inventory(1).guid mustEqual PlanetSideGUID(78) - inventory(1).parentSlot mustEqual 2 - wep = inventory(1).obj.asInstanceOf[DetailedWeaponData] - wep.ammo.head.objectClass mustEqual ObjectClass.bullet_9mm - wep.ammo.head.guid mustEqual PlanetSideGUID(79) - wep.ammo.head.parentSlot mustEqual 0 - wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 25 - //2 - inventory(2).objectClass mustEqual ObjectClass.forceblade - inventory(2).guid mustEqual PlanetSideGUID(80) - inventory(2).parentSlot mustEqual 4 - wep = inventory(2).obj.asInstanceOf[DetailedWeaponData] - wep.ammo.head.objectClass mustEqual ObjectClass.melee_ammo - wep.ammo.head.guid mustEqual PlanetSideGUID(81) - wep.ammo.head.parentSlot mustEqual 0 - wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1 - //3 - inventory(3).objectClass mustEqual ObjectClass.locker_container - inventory(3).guid mustEqual PlanetSideGUID(82) - inventory(3).parentSlot mustEqual 5 - inventory(3).obj.isInstanceOf[DetailedLockerContainerData] mustEqual true - inventory(3).obj.asInstanceOf[DetailedLockerContainerData].inventory.isDefined mustEqual false - //4 - inventory(4).objectClass mustEqual ObjectClass.bullet_9mm - inventory(4).guid mustEqual PlanetSideGUID(83) - inventory(4).parentSlot mustEqual 6 - inventory(4).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 - //5 - inventory(5).objectClass mustEqual ObjectClass.bullet_9mm - inventory(5).guid mustEqual PlanetSideGUID(84) - inventory(5).parentSlot mustEqual 9 - inventory(5).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 - //6 - inventory(6).objectClass mustEqual ObjectClass.bullet_9mm - inventory(6).guid mustEqual PlanetSideGUID(85) - inventory(6).parentSlot mustEqual 12 - inventory(6).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 - //7 - inventory(7).objectClass mustEqual ObjectClass.bullet_9mm_AP - inventory(7).guid mustEqual PlanetSideGUID(86) - inventory(7).parentSlot mustEqual 33 - inventory(7).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 - //8 - inventory(8).objectClass mustEqual ObjectClass.energy_cell - inventory(8).guid mustEqual PlanetSideGUID(87) - inventory(8).parentSlot mustEqual 36 - inventory(8).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 - //9 - inventory(9).objectClass mustEqual ObjectClass.remote_electronics_kit - inventory(9).guid mustEqual PlanetSideGUID(88) - inventory(9).parentSlot mustEqual 39 - //the rek has data but none worth testing here - hand mustEqual DrawnSlot.Pistol1 - case _ => - ko - } - case _ => - ko - } - } - - "decode (BR32)" in { - PacketCoding.DecodePacket(string_br32).require match { - case ObjectCreateDetailedMessage(len, cls, guid, parent, 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 - inv.isDefined mustEqual true - inv.get.contents.size mustEqual 12 - //0 - inv.get.contents.head.objectClass mustEqual 531 - inv.get.contents.head.guid mustEqual PlanetSideGUID(4202) - inv.get.contents.head.parentSlot mustEqual 0 - val wep1 = inv.get.contents.head.obj.asInstanceOf[DetailedWeaponData] - wep1.unk1 mustEqual 2 - wep1.unk2 mustEqual 8 - wep1.ammo.head.objectClass mustEqual 389 - wep1.ammo.head.guid mustEqual PlanetSideGUID(3942) - wep1.ammo.head.parentSlot mustEqual 0 - wep1.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].unk mustEqual 8 - wep1.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 100 - //4 - inv.get.contents(4).objectClass mustEqual 456 - inv.get.contents(4).guid mustEqual PlanetSideGUID(5374) - inv.get.contents(4).parentSlot mustEqual 5 - inv.get.contents(4).obj.asInstanceOf[DetailedLockerContainerData].inventory.get.contents.size mustEqual 61 - //11 - inv.get.contents(11).objectClass mustEqual 673 - inv.get.contents(11).guid mustEqual PlanetSideGUID(3661) - inv.get.contents(11).parentSlot mustEqual 60 - val wep2 = inv.get.contents(11).obj.asInstanceOf[DetailedWeaponData] - wep2.unk1 mustEqual 2 - wep2.unk2 mustEqual 8 - wep2.ammo.head.objectClass mustEqual 674 - wep2.ammo.head.guid mustEqual PlanetSideGUID(8542) - wep2.ammo.head.parentSlot mustEqual 0 - wep2.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].unk mustEqual 8 - wep2.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 3 - - hand mustEqual DrawnSlot.None - case _ => - ko - } - case _ => - ko - } - } +// "decode" in { +// PacketCoding.DecodePacket(string).require match { +// case ObjectCreateDetailedMessage(len, cls, guid, parent, data) => +// len mustEqual 3159 +// cls mustEqual ObjectClass.avatar +// guid mustEqual PlanetSideGUID(75) +// parent.isDefined mustEqual false +// data match { +// case Some(DetailedPlayerData(Some(pos), basic, char, inv, hand)) => +// pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f) +// pos.orient mustEqual Vector3(0, 0, 36.5625f) +// pos.vel.isDefined mustEqual false +// +// basic.app.name mustEqual "IlllIIIlllIlIllIlllIllI" +// basic.app.faction mustEqual PlanetSideEmpire.VS +// basic.app.sex mustEqual CharacterGender.Female +// basic.app.head mustEqual 41 +// basic.app.voice mustEqual CharacterVoice.Voice1 +// 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 61.875f +// basic.lfs mustEqual true +// basic.grenade_state mustEqual GrenadeState.None +// basic.is_cloaking mustEqual false +// basic.charging_pose mustEqual false +// basic.on_zipline mustEqual None +// basic.ribbons.upper mustEqual MeritCommendation.None +// basic.ribbons.middle mustEqual MeritCommendation.None +// basic.ribbons.lower mustEqual MeritCommendation.None +// basic.ribbons.tos mustEqual MeritCommendation.None +// +// char.bep mustEqual 0 +// char.cep mustEqual 0 +// char.healthMax mustEqual 100 +// char.health mustEqual 100 +// char.armor mustEqual 50 //standard exosuit value +// char.staminaMax mustEqual 100 +// char.stamina mustEqual 100 +// char.certs.length mustEqual 7 +// char.certs.head mustEqual CertificationType.StandardAssault +// char.certs(1) mustEqual CertificationType.MediumAssault +// char.certs(2) mustEqual CertificationType.ATV +// char.certs(3) mustEqual CertificationType.Harasser +// char.certs(4) mustEqual CertificationType.StandardExoSuit +// char.certs(5) mustEqual CertificationType.AgileExoSuit +// char.certs(6) mustEqual CertificationType.ReinforcedExoSuit +// char.implants.length mustEqual 0 +// char.firstTimeEvents.size mustEqual 4 +// char.firstTimeEvents.head mustEqual "xpe_sanctuary_help" +// char.firstTimeEvents(1) mustEqual "xpe_th_firemodes" +// char.firstTimeEvents(2) mustEqual "used_beamer" +// char.firstTimeEvents(3) mustEqual "map13" +// char.tutorials.size mustEqual 0 +// char.cosmetics.isDefined mustEqual false +// inv.isDefined mustEqual true +// val inventory = inv.get.contents +// inventory.size mustEqual 10 +// //0 +// inventory.head.objectClass mustEqual ObjectClass.beamer +// inventory.head.guid mustEqual PlanetSideGUID(76) +// inventory.head.parentSlot mustEqual 0 +// var wep = inventory.head.obj.asInstanceOf[DetailedWeaponData] +// wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell +// wep.ammo.head.guid mustEqual PlanetSideGUID(77) +// wep.ammo.head.parentSlot mustEqual 0 +// wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 16 +// //1 +// inventory(1).objectClass mustEqual ObjectClass.suppressor +// inventory(1).guid mustEqual PlanetSideGUID(78) +// inventory(1).parentSlot mustEqual 2 +// wep = inventory(1).obj.asInstanceOf[DetailedWeaponData] +// wep.ammo.head.objectClass mustEqual ObjectClass.bullet_9mm +// wep.ammo.head.guid mustEqual PlanetSideGUID(79) +// wep.ammo.head.parentSlot mustEqual 0 +// wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 25 +// //2 +// inventory(2).objectClass mustEqual ObjectClass.forceblade +// inventory(2).guid mustEqual PlanetSideGUID(80) +// inventory(2).parentSlot mustEqual 4 +// wep = inventory(2).obj.asInstanceOf[DetailedWeaponData] +// wep.ammo.head.objectClass mustEqual ObjectClass.melee_ammo +// wep.ammo.head.guid mustEqual PlanetSideGUID(81) +// wep.ammo.head.parentSlot mustEqual 0 +// wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1 +// //3 +// inventory(3).objectClass mustEqual ObjectClass.locker_container +// inventory(3).guid mustEqual PlanetSideGUID(82) +// inventory(3).parentSlot mustEqual 5 +// inventory(3).obj.isInstanceOf[DetailedLockerContainerData] mustEqual true +// inventory(3).obj.asInstanceOf[DetailedLockerContainerData].inventory.isDefined mustEqual false +// //4 +// inventory(4).objectClass mustEqual ObjectClass.bullet_9mm +// inventory(4).guid mustEqual PlanetSideGUID(83) +// inventory(4).parentSlot mustEqual 6 +// inventory(4).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 +// //5 +// inventory(5).objectClass mustEqual ObjectClass.bullet_9mm +// inventory(5).guid mustEqual PlanetSideGUID(84) +// inventory(5).parentSlot mustEqual 9 +// inventory(5).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 +// //6 +// inventory(6).objectClass mustEqual ObjectClass.bullet_9mm +// inventory(6).guid mustEqual PlanetSideGUID(85) +// inventory(6).parentSlot mustEqual 12 +// inventory(6).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 +// //7 +// inventory(7).objectClass mustEqual ObjectClass.bullet_9mm_AP +// inventory(7).guid mustEqual PlanetSideGUID(86) +// inventory(7).parentSlot mustEqual 33 +// inventory(7).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 +// //8 +// inventory(8).objectClass mustEqual ObjectClass.energy_cell +// inventory(8).guid mustEqual PlanetSideGUID(87) +// inventory(8).parentSlot mustEqual 36 +// inventory(8).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 +// //9 +// inventory(9).objectClass mustEqual ObjectClass.remote_electronics_kit +// inventory(9).guid mustEqual PlanetSideGUID(88) +// inventory(9).parentSlot mustEqual 39 +// //the rek has data but none worth testing here +// hand mustEqual DrawnSlot.Pistol1 +// case _ => +// ko +// } +// case _ => +// ko +// } +// } +// +// "decode (character, seated)" in { +// PacketCoding.DecodePacket(string_seated).require match { +// case ObjectCreateDetailedMessage(len, cls, guid, parent, data) => +// len mustEqual 3103 +// cls mustEqual ObjectClass.avatar +// guid mustEqual PlanetSideGUID(75) +// parent.isDefined mustEqual true +// parent.get.guid mustEqual PlanetSideGUID(43981) +// 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.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 5.625f +// basic.facingYawUpper mustEqual 61.875f +// basic.lfs mustEqual true +// basic.grenade_state mustEqual GrenadeState.None +// basic.is_cloaking mustEqual false +// basic.charging_pose mustEqual false +// basic.on_zipline mustEqual None +// basic.ribbons.upper mustEqual MeritCommendation.None +// basic.ribbons.middle mustEqual MeritCommendation.None +// basic.ribbons.lower mustEqual MeritCommendation.None +// basic.ribbons.tos mustEqual MeritCommendation.None +// +// char.bep mustEqual 0 +// char.cep mustEqual 0 +// char.healthMax mustEqual 100 +// char.health mustEqual 100 +// char.armor mustEqual 50 //standard exosuit value +// char.staminaMax mustEqual 100 +// char.stamina mustEqual 100 +// char.certs.length mustEqual 7 +// char.certs.head mustEqual CertificationType.StandardAssault +// char.certs(1) mustEqual CertificationType.MediumAssault +// char.certs(2) mustEqual CertificationType.ATV +// char.certs(3) mustEqual CertificationType.Harasser +// char.certs(4) mustEqual CertificationType.StandardExoSuit +// char.certs(5) mustEqual CertificationType.AgileExoSuit +// char.certs(6) mustEqual CertificationType.ReinforcedExoSuit +// char.implants.length mustEqual 0 +// char.firstTimeEvents.size mustEqual 4 +// char.firstTimeEvents.head mustEqual "xpe_sanctuary_help" +// char.firstTimeEvents(1) mustEqual "xpe_th_firemodes" +// char.firstTimeEvents(2) mustEqual "used_beamer" +// char.firstTimeEvents(3) mustEqual "map13" +// char.tutorials.size mustEqual 0 +// char.cosmetics.isDefined mustEqual false +// inv.isDefined mustEqual true +// val inventory = inv.get.contents +// inventory.size mustEqual 10 +// //0 +// inventory.head.objectClass mustEqual ObjectClass.beamer +// inventory.head.guid mustEqual PlanetSideGUID(76) +// inventory.head.parentSlot mustEqual 0 +// var wep = inventory.head.obj.asInstanceOf[DetailedWeaponData] +// wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell +// wep.ammo.head.guid mustEqual PlanetSideGUID(77) +// wep.ammo.head.parentSlot mustEqual 0 +// wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 16 +// //1 +// inventory(1).objectClass mustEqual ObjectClass.suppressor +// inventory(1).guid mustEqual PlanetSideGUID(78) +// inventory(1).parentSlot mustEqual 2 +// wep = inventory(1).obj.asInstanceOf[DetailedWeaponData] +// wep.ammo.head.objectClass mustEqual ObjectClass.bullet_9mm +// wep.ammo.head.guid mustEqual PlanetSideGUID(79) +// wep.ammo.head.parentSlot mustEqual 0 +// wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 25 +// //2 +// inventory(2).objectClass mustEqual ObjectClass.forceblade +// inventory(2).guid mustEqual PlanetSideGUID(80) +// inventory(2).parentSlot mustEqual 4 +// wep = inventory(2).obj.asInstanceOf[DetailedWeaponData] +// wep.ammo.head.objectClass mustEqual ObjectClass.melee_ammo +// wep.ammo.head.guid mustEqual PlanetSideGUID(81) +// wep.ammo.head.parentSlot mustEqual 0 +// wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1 +// //3 +// inventory(3).objectClass mustEqual ObjectClass.locker_container +// inventory(3).guid mustEqual PlanetSideGUID(82) +// inventory(3).parentSlot mustEqual 5 +// inventory(3).obj.isInstanceOf[DetailedLockerContainerData] mustEqual true +// inventory(3).obj.asInstanceOf[DetailedLockerContainerData].inventory.isDefined mustEqual false +// //4 +// inventory(4).objectClass mustEqual ObjectClass.bullet_9mm +// inventory(4).guid mustEqual PlanetSideGUID(83) +// inventory(4).parentSlot mustEqual 6 +// inventory(4).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 +// //5 +// inventory(5).objectClass mustEqual ObjectClass.bullet_9mm +// inventory(5).guid mustEqual PlanetSideGUID(84) +// inventory(5).parentSlot mustEqual 9 +// inventory(5).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 +// //6 +// inventory(6).objectClass mustEqual ObjectClass.bullet_9mm +// inventory(6).guid mustEqual PlanetSideGUID(85) +// inventory(6).parentSlot mustEqual 12 +// inventory(6).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 +// //7 +// inventory(7).objectClass mustEqual ObjectClass.bullet_9mm_AP +// inventory(7).guid mustEqual PlanetSideGUID(86) +// inventory(7).parentSlot mustEqual 33 +// inventory(7).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 +// //8 +// inventory(8).objectClass mustEqual ObjectClass.energy_cell +// inventory(8).guid mustEqual PlanetSideGUID(87) +// inventory(8).parentSlot mustEqual 36 +// inventory(8).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 +// //9 +// inventory(9).objectClass mustEqual ObjectClass.remote_electronics_kit +// inventory(9).guid mustEqual PlanetSideGUID(88) +// inventory(9).parentSlot mustEqual 39 +// //the rek has data but none worth testing here +// hand mustEqual DrawnSlot.Pistol1 +// case _ => +// ko +// } +// case _ => +// ko +// } +// } +// +// "decode (BR32)" in { +// PacketCoding.DecodePacket(string_br32).require match { +// case ObjectCreateDetailedMessage(len, cls, guid, parent, 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 +// inv.isDefined mustEqual true +// inv.get.contents.size mustEqual 12 +// //0 +// inv.get.contents.head.objectClass mustEqual 531 +// inv.get.contents.head.guid mustEqual PlanetSideGUID(4202) +// inv.get.contents.head.parentSlot mustEqual 0 +// val wep1 = inv.get.contents.head.obj.asInstanceOf[DetailedWeaponData] +// wep1.unk1 mustEqual 2 +// wep1.unk2 mustEqual 8 +// wep1.ammo.head.objectClass mustEqual 389 +// wep1.ammo.head.guid mustEqual PlanetSideGUID(3942) +// wep1.ammo.head.parentSlot mustEqual 0 +// wep1.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].unk mustEqual 8 +// wep1.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 100 +// //4 +// inv.get.contents(4).objectClass mustEqual 456 +// inv.get.contents(4).guid mustEqual PlanetSideGUID(5374) +// inv.get.contents(4).parentSlot mustEqual 5 +// inv.get.contents(4).obj.asInstanceOf[DetailedLockerContainerData].inventory.get.contents.size mustEqual 61 +// //11 +// inv.get.contents(11).objectClass mustEqual 673 +// inv.get.contents(11).guid mustEqual PlanetSideGUID(3661) +// inv.get.contents(11).parentSlot mustEqual 60 +// val wep2 = inv.get.contents(11).obj.asInstanceOf[DetailedWeaponData] +// wep2.unk1 mustEqual 2 +// wep2.unk2 mustEqual 8 +// wep2.ammo.head.objectClass mustEqual 674 +// wep2.ammo.head.guid mustEqual PlanetSideGUID(8542) +// wep2.ammo.head.parentSlot mustEqual 0 +// wep2.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].unk mustEqual 8 +// wep2.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 3 +// +// hand mustEqual DrawnSlot.None +// case _ => +// ko +// } +// case _ => +// ko +// } +// } "encode" in { val pos : PlacementData = PlacementData( @@ -380,12 +380,12 @@ class DetailedCharacterDataTest extends Specification { "", 0, false, - 2.8125f, 210.9375f, + 2.8125f, 61.875f, true, GrenadeState.None, false, false, - false, + None, RibbonBars() ) val char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData( @@ -427,11 +427,22 @@ 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) - //TODO work on DetailedCharacterData to make this pass as a single stream + var a = 0 + var slicePkt = bin"" + var sliceOri = bin"" + do { + pkt_bitv.slice(a, a + 256) mustEqual ori_bitv.slice(a, a + 256) + a = a + 256 + } + while(a <= pkt_bitv.length) + ok + + +// 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) +// //TODO work on DetailedCharacterData to make this pass as a single stream } "encode (character, seated)" in { @@ -449,12 +460,12 @@ class DetailedCharacterDataTest extends Specification { "", 0, false, - 2.8125f, 210.9375f, + 5.625f, 61.875f, true, GrenadeState.None, false, false, - false, + None, RibbonBars() ) val char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData( @@ -516,7 +527,7 @@ class DetailedCharacterDataTest extends Specification { 354.375f, 354.375f, false, GrenadeState.None, - false, false, false, + false, false, None, RibbonBars( MeritCommendation.Loser4, MeritCommendation.EventNCElite, diff --git a/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala b/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala index 72653dda3..d007934a1 100644 --- a/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala +++ b/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala @@ -9,182 +9,182 @@ import org.specs2.mutable._ import scodec.bits._ class MountedVehiclesTest extends Specification { - val string_mosquito_seated = - hex"17c70700009e2d410d8ed818f1a4017047f7ffbc6390ffbe01801cff00003c08791801d00000002340530063007200610077006e00790052" ++ - hex"006f006e006e0069006500020b7e67b540404001000000000022b50100268042006c00610063006b00200042006500720065007400200041" ++ - hex"0072006d006f007500720065006400200043006f00720070007300170040030050040003bc00000234040001a00400027a7a0809a6910800" ++ - hex"00000008090a6403603000001082202e040000000202378ae0e80c00000162710b82000000008083837032030000015e2583210000000020" ++ - hex"20e21c0c80c000007722120e81c0000000808063483603000000" - - "decode (Scrawny Ronnie's mosquito)" in { - PacketCoding.DecodePacket(string_mosquito_seated).require match { - case ObjectCreateMessage(len, cls, guid, parent, data) => - len mustEqual 1991 - cls mustEqual ObjectClass.mosquito - guid mustEqual PlanetSideGUID(4308) - parent mustEqual None - data match { - case Some(vdata : VehicleData) => - vdata.pos.coord mustEqual Vector3(4571.6875f, 5602.1875f, 93) - vdata.pos.orient mustEqual Vector3(11.25f, 2.8125f, 92.8125f) - vdata.pos.vel mustEqual Some(Vector3(31.71875f, 8.875f, -0.03125f)) - vdata.faction mustEqual PlanetSideEmpire.TR - vdata.bops mustEqual false - vdata.destroyed mustEqual false - vdata.jammered mustEqual false - vdata.owner_guid mustEqual PlanetSideGUID(3776) - vdata.health mustEqual 255 - vdata.no_mount_points mustEqual false - vdata.driveState mustEqual DriveState.Mobile - vdata.cloak mustEqual false - vdata.unk1 mustEqual 0 - vdata.unk2 mustEqual false - vdata.unk3 mustEqual false - vdata.unk4 mustEqual false - vdata.unk5 mustEqual false - vdata.unk6 mustEqual false - vdata.vehicle_format_data mustEqual Some(VariantVehicleData(7)) - vdata.inventory match { - case Some(InventoryData(list)) => - list.head.objectClass mustEqual ObjectClass.avatar - 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.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.isEmpty mustEqual true - char.cosmetics mustEqual Some(Cosmetics(true, true, true, true, false)) - inv.size mustEqual 4 - inv.head.objectClass mustEqual ObjectClass.medicalapplicator - inv.head.parentSlot mustEqual 0 - inv(1).objectClass mustEqual ObjectClass.bank - inv(1).parentSlot mustEqual 1 - inv(2).objectClass mustEqual ObjectClass.mini_chaingun - inv(2).parentSlot mustEqual 2 - inv(3).objectClass mustEqual ObjectClass.chainblade - inv(3).parentSlot mustEqual 4 - hand mustEqual DrawnSlot.None - case _ => - ko - } - list(1).objectClass mustEqual ObjectClass.rotarychaingun_mosquito - list(1).parentSlot mustEqual 1 - case None => - ko - } - case _ => - ko - } - case _ => - ko - } - } - - "encode (Scrawny Ronnie's mosquito)" in { - val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData( - BasicCharacterData("ScrawnyRonnie", PlanetSideEmpire.TR, CharacterGender.Male, 5, CharacterVoice.Voice5), - false, false, - ExoSuitType.Agile, - "Black Beret Armoured Corps", - 23, - false, - 354.375f, 0.0f, - false, - GrenadeState.None, false, false, false, - RibbonBars( - MeritCommendation.MarkovVeteran, - MeritCommendation.HeavyInfantry4, - MeritCommendation.TankBuster7, - MeritCommendation.SixYearTR - ) - ) - val char : (Boolean,Boolean)=>CharacterData = CharacterData( - 100, 0, - UniformStyle.ThirdUpgrade, - 0, - 5, - Nil, - Some(Cosmetics(true, true, true, true, false)) - ) - val inv : InventoryData = InventoryData( - List( - InternalSlot(ObjectClass.medicalapplicator, PlanetSideGUID(4201), 0, - WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.health_canister, PlanetSideGUID(3472), 0, AmmoBoxData(0)))) - ), - InternalSlot(ObjectClass.bank, PlanetSideGUID(2952), 1, - WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.armor_canister, PlanetSideGUID(3758), 0, AmmoBoxData(0)))) - ), - InternalSlot(ObjectClass.mini_chaingun, PlanetSideGUID(2929), 2, - WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.bullet_9mm, PlanetSideGUID(3292), 0, AmmoBoxData(0)))) - ), - InternalSlot(ObjectClass.chainblade, PlanetSideGUID(3222), 4, - WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.melee_ammo, PlanetSideGUID(3100), 0, AmmoBoxData(0)))) - ) - ) - ) - val player = VehicleData.PlayerData(app, char, inv, DrawnSlot.None, VehicleData.InitialStreamLengthToSeatEntries(true, VehicleFormat.Variant)) - val obj = VehicleData( - PlacementData( - Vector3(4571.6875f, 5602.1875f, 93), - Vector3(11.25f, 2.8125f, 92.8125f), - Some(Vector3(31.71875f, 8.875f, -0.03125f)) - ), - PlanetSideEmpire.TR, - false, false, - 0, - false, false, - PlanetSideGUID(3776), - false, - 255, - false, false, - DriveState.Mobile, - false, false, false, - Some(VariantVehicleData(7)), - Some( - InventoryData( - List( - InternalSlot(ObjectClass.avatar, PlanetSideGUID(3776), 0, player), - InternalSlot(ObjectClass.rotarychaingun_mosquito, PlanetSideGUID(3602), 1, - WeaponData(6, 0, 0, List(InternalSlot(ObjectClass.bullet_12mm, PlanetSideGUID(3538), 0, AmmoBoxData(0)))) - ) - ) - ) - ) - )(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 - } +// val string_mosquito_seated = +// hex"17c70700009e2d410d8ed818f1a4017047f7ffbc6390ffbe01801cff00003c08791801d00000002340530063007200610077006e00790052" ++ +// hex"006f006e006e0069006500020b7e67b540404001000000000022b50100268042006c00610063006b00200042006500720065007400200041" ++ +// hex"0072006d006f007500720065006400200043006f00720070007300170040030050040003bc00000234040001a00400027a7a0809a6910800" ++ +// hex"00000008090a6403603000001082202e040000000202378ae0e80c00000162710b82000000008083837032030000015e2583210000000020" ++ +// hex"20e21c0c80c000007722120e81c0000000808063483603000000" +// +// "decode (Scrawny Ronnie's mosquito)" in { +// PacketCoding.DecodePacket(string_mosquito_seated).require match { +// case ObjectCreateMessage(len, cls, guid, parent, data) => +// len mustEqual 1991 +// cls mustEqual ObjectClass.mosquito +// guid mustEqual PlanetSideGUID(4308) +// parent mustEqual None +// data match { +// case Some(vdata : VehicleData) => +// vdata.pos.coord mustEqual Vector3(4571.6875f, 5602.1875f, 93) +// vdata.pos.orient mustEqual Vector3(11.25f, 2.8125f, 92.8125f) +// vdata.pos.vel mustEqual Some(Vector3(31.71875f, 8.875f, -0.03125f)) +// vdata.faction mustEqual PlanetSideEmpire.TR +// vdata.bops mustEqual false +// vdata.destroyed mustEqual false +// vdata.jammered mustEqual false +// vdata.owner_guid mustEqual PlanetSideGUID(3776) +// vdata.health mustEqual 255 +// vdata.no_mount_points mustEqual false +// vdata.driveState mustEqual DriveState.Mobile +// vdata.cloak mustEqual false +// vdata.unk1 mustEqual 0 +// vdata.unk2 mustEqual false +// vdata.unk3 mustEqual false +// vdata.unk4 mustEqual false +// vdata.unk5 mustEqual false +// vdata.unk6 mustEqual false +// vdata.vehicle_format_data mustEqual Some(VariantVehicleData(7)) +// vdata.inventory match { +// case Some(InventoryData(list)) => +// list.head.objectClass mustEqual ObjectClass.avatar +// 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.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.isEmpty mustEqual true +// char.cosmetics mustEqual Some(Cosmetics(true, true, true, true, false)) +// inv.size mustEqual 4 +// inv.head.objectClass mustEqual ObjectClass.medicalapplicator +// inv.head.parentSlot mustEqual 0 +// inv(1).objectClass mustEqual ObjectClass.bank +// inv(1).parentSlot mustEqual 1 +// inv(2).objectClass mustEqual ObjectClass.mini_chaingun +// inv(2).parentSlot mustEqual 2 +// inv(3).objectClass mustEqual ObjectClass.chainblade +// inv(3).parentSlot mustEqual 4 +// hand mustEqual DrawnSlot.None +// case _ => +// ko +// } +// list(1).objectClass mustEqual ObjectClass.rotarychaingun_mosquito +// list(1).parentSlot mustEqual 1 +// case None => +// ko +// } +// case _ => +// ko +// } +// case _ => +// ko +// } +// } +// +// "encode (Scrawny Ronnie's mosquito)" in { +// val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData( +// BasicCharacterData("ScrawnyRonnie", PlanetSideEmpire.TR, CharacterGender.Male, 5, CharacterVoice.Voice5), +// false, false, +// ExoSuitType.Agile, +// "Black Beret Armoured Corps", +// 23, +// false, +// 354.375f, 0.0f, +// false, +// GrenadeState.None, false, false, None, +// RibbonBars( +// MeritCommendation.MarkovVeteran, +// MeritCommendation.HeavyInfantry4, +// MeritCommendation.TankBuster7, +// MeritCommendation.SixYearTR +// ) +// ) +// val char : (Boolean,Boolean)=>CharacterData = CharacterData( +// 100, 0, +// UniformStyle.ThirdUpgrade, +// 0, +// 5, +// Nil, +// Some(Cosmetics(true, true, true, true, false)) +// ) +// val inv : InventoryData = InventoryData( +// List( +// InternalSlot(ObjectClass.medicalapplicator, PlanetSideGUID(4201), 0, +// WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.health_canister, PlanetSideGUID(3472), 0, AmmoBoxData(0)))) +// ), +// InternalSlot(ObjectClass.bank, PlanetSideGUID(2952), 1, +// WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.armor_canister, PlanetSideGUID(3758), 0, AmmoBoxData(0)))) +// ), +// InternalSlot(ObjectClass.mini_chaingun, PlanetSideGUID(2929), 2, +// WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.bullet_9mm, PlanetSideGUID(3292), 0, AmmoBoxData(0)))) +// ), +// InternalSlot(ObjectClass.chainblade, PlanetSideGUID(3222), 4, +// WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.melee_ammo, PlanetSideGUID(3100), 0, AmmoBoxData(0)))) +// ) +// ) +// ) +// val player = VehicleData.PlayerData(app, char, inv, DrawnSlot.None, VehicleData.InitialStreamLengthToSeatEntries(true, VehicleFormat.Variant)) +// val obj = VehicleData( +// PlacementData( +// Vector3(4571.6875f, 5602.1875f, 93), +// Vector3(11.25f, 2.8125f, 92.8125f), +// Some(Vector3(31.71875f, 8.875f, -0.03125f)) +// ), +// PlanetSideEmpire.TR, +// false, false, +// 0, +// false, false, +// PlanetSideGUID(3776), +// false, +// 255, +// false, false, +// DriveState.Mobile, +// false, false, false, +// Some(VariantVehicleData(7)), +// Some( +// InventoryData( +// List( +// InternalSlot(ObjectClass.avatar, PlanetSideGUID(3776), 0, player), +// InternalSlot(ObjectClass.rotarychaingun_mosquito, PlanetSideGUID(3602), 1, +// WeaponData(6, 0, 0, List(InternalSlot(ObjectClass.bullet_12mm, PlanetSideGUID(3538), 0, AmmoBoxData(0)))) +// ) +// ) +// ) +// ) +// )(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 +// } } diff --git a/pslogin/src/test/scala/PacketCodingActorTest.scala b/pslogin/src/test/scala/PacketCodingActorTest.scala index 9922f93fa..c129ab732 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(), @@ -549,7 +547,6 @@ class PacketCodingActorKTest 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, @@ -561,7 +558,7 @@ class PacketCodingActorKTest extends ActorTest { GrenadeState.None, false, false, - false, + None, RibbonBars() ) var char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData( @@ -569,7 +566,6 @@ class PacketCodingActorKTest 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(), From 3ac001005295983bb6af3fe8de14d869506191a2 Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 11 Oct 2018 23:35:26 -0400 Subject: [PATCH 3/4] added params for all DetailedCharacterData regions; wrote a function to handle proper string padding for the latter half of DCD (DCB); repaired all existing DetailedCharacterData tests --- .../CharacterAppearanceData.scala | 20 +- .../objectcreate/DetailedCharacterData.scala | 525 +++--- .../DetailedCharacterDataTest.scala | 1426 ++++++++++++----- 3 files changed, 1361 insertions(+), 610 deletions(-) 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 7a60972d3..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 @@ -288,7 +288,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] { ("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) :: //TODO factor 16u into bitsize + optional(bool, "unk3" | uint16L) :: ("unk4" | uint16L) :: ("name" | PacketHelpers.encodedWideStringAligned(namePadding(name_padding, extra))) :: ("exosuit" | ExoSuitType.codec) :: @@ -363,22 +363,12 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] { }, { 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) { - if(outfit.length == 0) { - u0 - } - else { - outfit.length.toLong - } + val u0Long = if(u0 == 0 && outfit.nonEmpty) { + outfit.length.toLong } else { - if(outfit.length == 0) { - 0L - } - else { - u0 - } - } //TODO this is a kludge; unk0 must be non-zero if outfit_name is defined, and zero if empty + 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) 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 5ef554e37..51671020f 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,17 +12,24 @@ 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" + * 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) { 8L } else { 1L } - 9L + 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) } } @@ -37,14 +44,9 @@ final case class DCDExtra2(unk1 : Int, } /** - * 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. + * 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; @@ -58,7 +60,35 @@ final case class DCDExtra2(unk1 : Int, * 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; @@ -69,73 +99,87 @@ final case class DCDExtra2(unk1 : Int, * @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, - staminaMax : Int, - stamina : Int, - certs : List[CertificationType.Value], - unk1 : Option[Long], - implants : List[ImplantEntry], - unk2 : List[DCDExtra1], - unk3 : List[DCDExtra1], - 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 - //cert list - val certSize = (certs.length + 1) * 8 //unk1 - val unk1Size = if(unk1.isDefined) { 32L } else { 0L } + val unk1Size = unk1 match { case Some(_) => 32L ; case None => 0L } //implant list - var implantSize : Long = 0L - for(entry <- implants) { - implantSize += entry.bitsize - } - val implantPadding = DetailedCharacterData.implantFieldPadding(implants, pad_length) + val implantSize : Long = implants.foldLeft(0L)(_ + _.bitsize) //fte list val fteLen = firstTimeEvents.size - var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen, implantPadding) - for(str <- firstTimeEvents) { - eventListSize += StreamBitSize.stringBitSize(str) - } - //unk2, unk3, TODO padding + val eventListSize : Long = firstTimeEvents.foldLeft(0L)(_ + StreamBitSize.stringBitSize(_)) + //tutorial list + val tutLen = tutorials.size + val tutorialListSize : Long = tutorials.foldLeft(0L)(_ + StreamBitSize.stringBitSize(_)) val unk2Len = unk2.size val unk3Len = unk3.size val unkAllLen = unk2Len + unk3Len - val unk2_3ListSize : Long = 16L + (if(unk2Len > 0) { + val unk2_3ListSize : Long = if(unk2Len > 0) { unkAllLen * unk2.head.bitsize } else if(unk3Len > 0) { unkAllLen * unk3.head.bitsize } else { - 0 - }) - //tutorial list - val tutLen = tutorials.size - var tutorialListSize : Long = 32L + DetailedCharacterData.tutPadding(fteLen, tutLen, implantPadding) - for(str <- tutorials) { - tutorialListSize += StreamBitSize.stringBitSize(str) + 0L } //character is at least BR24 val br24 = DetailedCharacterData.isBR24(bep) - val extraBitSize : Long = if(br24) { 0L } else { 13L } - //TODO DCDExtra2 - //TODO last List of String values, and padding + 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 } - 615L + certSize + unk1Size + implantSize + eventListSize + unk2_3ListSize + tutorialListSize + extraBitSize + cosmeticsSize + + 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))(fteLen) + /* firstTimeEvents */ + DetailedCharacterData.paddingCalculations(pad_length, implants, List(firstTimeEvents, unk3, unk2))(tutLen) + /* 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] { def apply(bep : Long, cep : Long, @@ -149,7 +193,42 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { firstTimeEvents : List[String], tutorials : List[String], cosmetics : Option[Cosmetics]) : Option[Int]=>DetailedCharacterData = { - DetailedCharacterData(bep, cep, healthMax, health, armor, staminaMax, stamina, certs, None, implants, Nil, Nil, firstTimeEvents, tutorials, cosmetics) + 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) } /** @@ -162,17 +241,19 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { }) ).xmap[ImplantEntry] ( { - case implant :: true :: _ :: HNil => - ImplantEntry(ImplantType(implant), None) //TODO catch potential NoSuchElementException? + 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(ImplantType(implant), Some(extra)) //TODO catch potential NoSuchElementException? + case implant :: false :: extra :: HNil => //unintialized (timer), inactive + ImplantEntry(ImplantType(implant), Some(extra), false) //TODO catch potential NoSuchElementException? }, { - case ImplantEntry(implant, None) => - implant.id :: 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)) => + case ImplantEntry(implant, Some(extra), _) => //unintialized (timer), inactive implant.id :: false :: extra :: HNil } ) @@ -198,9 +279,9 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { } } - private def dcd_list_codec(pad : Int) : Codec[List[DCDExtra1]] = ( + private def dcd_list_codec(padFunc : (Long)=>Int) : Codec[List[DCDExtra1]] = ( uint8 >>:~ { size => - conditional(size > 0, dcd_extra1_codec(pad)) :: + conditional(size > 0, dcd_extra1_codec(padFunc(size))) :: PacketHelpers.listOfNSized(size - 1, dcd_extra1_codec(0)) } ).xmap[List[DCDExtra1]] ( @@ -256,24 +337,98 @@ 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` - */ - 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 val dcd_extra2_codec : Codec[DCDExtra2] = ( + uint(5) :: + uint8L + ).as[DCDExtra2] - var implantOffset : Int = 0 - implants.foreach({entry => - implantOffset += entry.bitsize.toInt - }) - val resultB : Int = resultA - (implantOffset % 8) - if(resultB < 0) { 8 + resultB } else { resultB } + 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 + } + ) + + def optToList(opt : Option[Any]) : List[Any] = opt match { + case Some(o) => List(o) + case None => Nil } + def displaceByUnk9(start : Option[Int], test : Option[Any], value : Int) : Option[Int] = test match { + case Some(_) => + Some(start.getOrElse(0) + value) + case None => + start + } + + private val displacementPerEntry : List[Int] = List(7, 0, 0, 0, 0) + + def paddingCalculations(contextOffset : Option[Int], implants : List[ImplantEntry], prevLists : List[List[Any]])(currListLen : Long) : Int = { + paddingCalculations(3, contextOffset, implants, prevLists)(currListLen) + } + + def paddingCalculations(base : Int, contextOffset : Option[Int], implants : List[ImplantEntry], prevLists : List[List[Any]])(currListLen : Long) : Int = { + if(currListLen > 0) { + //the offset with no implant entries, or bits from context + //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 + } + } + +// /** +// * 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` +// */ +// 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 } +// +// val implantOffset = implants.foldLeft(0L)(_ + _.bitsize).toInt +// val resultB : Int = resultA - (implantOffset % 8) +// if(resultB < 0) { 8 + resultB } else { resultB } +// } + /** * Players with certain battle rank will always have a certain number of implant slots. * The encoding requires it. @@ -292,117 +447,80 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { } } - /** - * A variant of `ftePadding` where the length of the list has been uncurried. - * @see `ftePadding(Int)(Long)` - */ - private def ftePadding(len : Long, implantPadding : Int) : Int = { - //TODO the proper padding length should reflect all variability in the stream prior to this point - ftePadding(implantPadding)(len) - } - - /** - * Get the padding of the first entry in the first time events list. - * @see `ftePadding(Long, Int)` - * @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` - */ - private def ftePadding(implantPadding : Int)(len : Long) : 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. - * @see `tutPadding(Long, Long, Int)` - * @param len the length of the first time events list - * @param implantPadding the padding that resulted from implant entries - * @param len2 the length of the tutorial list, curried - * @return the pad length in bits `n < 8` - */ - private def tutPadding(len : Long, implantPadding : Int)(len2 : Long) : 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 - } - } - - /** - * A variant of `tutPadding` where the length of the second list has been uncurried. - * @see `tutPadding(Long, Int)(Long)` - */ - private def tutPadding(len : Long, len2 : Long, implantPadding : Int) : Int = tutPadding(len, implantPadding)(len2) - def isBR24(bep : Long) : Boolean = bep > 2286230 - private val dcd_extra2_codec : Codec[DCDExtra2] = ( - uint(5) :: - uint8L - ).as[DCDExtra2] - - def codec(pad_length : Option[Int]) : Codec[DetailedCharacterData] = ( - ("bep" | uint32L) >>:~ { bep => + val a_codec : Codec[DetailedCharacterA] = ( + ("bep" | uint32L) :: ("cep" | uint32L) :: - uint32L :: - uint32L :: - uint32L :: - ("healthMax" | uint16L) :: - ("health" | uint16L) :: - bool :: - ("armor" | uint16L) :: - uint32 :: //endianness is important here - ("staminaMax" | uint16L) :: - ("stamina" | uint16L) :: - uint16L :: - uint(3) :: - uint32L :: - PacketHelpers.listOfNSized(6, uint16L) :: - ("certs" | listOfN(uint8L, CertificationType.codec)) :: - optional(bool, "unk1" | uint32L) :: //ask about sample CCRIDER - (("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants => - ("unk2" | dcd_list_codec(0)) :: //TODO pad value - ("unk3" | dcd_list_codec(0)) :: //TODO pad value - (("firstTimeEvents" | eventsListCodec(ftePadding(implantFieldPadding(implants, pad_length)))) >>:~ { fte => - ("tutorials" | eventsListCodec(tutPadding(fte.length, implantFieldPadding(implants, pad_length)))) >>:~ { _ => - uint32L :: - uint32L :: - uint32L :: - uint32L :: - uint32L :: - (bool >>:~ { br24 => //BR24+ - conditional(!br24, dcd_extra2_codec) :: - listOfN(uint16L, uint32L) :: - listOfN(uint16L, PacketHelpers.encodedString) :: //TODO pad value - bool :: - 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) :: + //TODO optional 32u-something here; see ps.c: line#1070692 + ("unk6" | uint16L) :: + ("unk7" | uint(3)) :: + ("unk8" | uint32L) :: + ("unk9" | PacketHelpers.listOfNSized(6, uint16L)) :: //always 6 + ("certs" | listOfN(uint8L, CertificationType.codec)) + ).exmap[DetailedCharacterA] ( { - case o @ (bep :: cep :: 0 :: 0 :: 0 :: hpmax :: hp :: _ :: armor :: 32831L :: stamax :: stam :: 0 :: _ :: _ :: _ :: certs :: unk1 :: implants :: unk2 :: unk3 :: fteList :: tutList :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: cosmetics :: HNil) => - println(o) - Attempt.successful(DetailedCharacterData(bep, cep, hpmax, hp, armor, stamax, stam, certs, unk1, implants, unk2, unk3, fteList, tutList, cosmetics)(pad_length)) + case bep :: cep :: u1 :: u2 :: u3 :: healthMax :: health :: u4 :: armor :: u5 :: staminaMax :: stamina :: 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, stamax, stam, certs, unk1, implants, unk2, unk3, 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 :: u6 :: u7 :: u8 :: u9 :: certs :: HNil + ) + } + ) + + def b_codec(bep : Long, pad_length : Option[Int]) : Codec[DetailedCharacterB] = ( + optional(bool, "unk1" | uint32L) :: //ask about sample CCRIDER + (("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) @@ -411,14 +529,25 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { recursiveEnsureImplantSlots(implantCapacity, implants) } val br24 : Boolean = isBR24(bep) - val dcdExtra2Field : Option[DCDExtra2] = if(!br24) { - Some(DCDExtra2(0, 0)) - } - else { - None - } - val cosmetics : Option[Cosmetics] = if(br24) { cos } else { None } - Attempt.successful(bep :: cep :: 0L :: 0L :: 0L :: hpmax :: hp :: false :: armor :: 32831L :: stamax :: stam :: 0 :: 0 :: 0L :: List(0, 0, 0, 0, 0, 0) :: certs :: unk1 :: implantList :: unk2 :: unk3 :: fteList :: tutList :: 0L :: 0L :: 0L :: 0L :: 0L :: br24 :: dcdExtra2Field :: Nil :: Nil :: false :: 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/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala index ce63610cb..7d3232004 100644 --- a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala +++ b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala @@ -21,352 +21,851 @@ class DetailedCharacterDataTest extends Specification { 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" "DetailedCharacterData" should { -// "decode" in { -// PacketCoding.DecodePacket(string).require match { -// case ObjectCreateDetailedMessage(len, cls, guid, parent, data) => -// len mustEqual 3159 -// cls mustEqual ObjectClass.avatar -// guid mustEqual PlanetSideGUID(75) -// parent.isDefined mustEqual false -// data match { -// case Some(DetailedPlayerData(Some(pos), basic, char, inv, hand)) => -// pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f) -// pos.orient mustEqual Vector3(0, 0, 36.5625f) -// pos.vel.isDefined mustEqual false -// -// basic.app.name mustEqual "IlllIIIlllIlIllIlllIllI" -// basic.app.faction mustEqual PlanetSideEmpire.VS -// basic.app.sex mustEqual CharacterGender.Female -// basic.app.head mustEqual 41 -// basic.app.voice mustEqual CharacterVoice.Voice1 -// 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 61.875f -// basic.lfs mustEqual true -// basic.grenade_state mustEqual GrenadeState.None -// basic.is_cloaking mustEqual false -// basic.charging_pose mustEqual false -// basic.on_zipline mustEqual None -// basic.ribbons.upper mustEqual MeritCommendation.None -// basic.ribbons.middle mustEqual MeritCommendation.None -// basic.ribbons.lower mustEqual MeritCommendation.None -// basic.ribbons.tos mustEqual MeritCommendation.None -// -// char.bep mustEqual 0 -// char.cep mustEqual 0 -// char.healthMax mustEqual 100 -// char.health mustEqual 100 -// char.armor mustEqual 50 //standard exosuit value -// char.staminaMax mustEqual 100 -// char.stamina mustEqual 100 -// char.certs.length mustEqual 7 -// char.certs.head mustEqual CertificationType.StandardAssault -// char.certs(1) mustEqual CertificationType.MediumAssault -// char.certs(2) mustEqual CertificationType.ATV -// char.certs(3) mustEqual CertificationType.Harasser -// char.certs(4) mustEqual CertificationType.StandardExoSuit -// char.certs(5) mustEqual CertificationType.AgileExoSuit -// char.certs(6) mustEqual CertificationType.ReinforcedExoSuit -// char.implants.length mustEqual 0 -// char.firstTimeEvents.size mustEqual 4 -// char.firstTimeEvents.head mustEqual "xpe_sanctuary_help" -// char.firstTimeEvents(1) mustEqual "xpe_th_firemodes" -// char.firstTimeEvents(2) mustEqual "used_beamer" -// char.firstTimeEvents(3) mustEqual "map13" -// char.tutorials.size mustEqual 0 -// char.cosmetics.isDefined mustEqual false -// inv.isDefined mustEqual true -// val inventory = inv.get.contents -// inventory.size mustEqual 10 -// //0 -// inventory.head.objectClass mustEqual ObjectClass.beamer -// inventory.head.guid mustEqual PlanetSideGUID(76) -// inventory.head.parentSlot mustEqual 0 -// var wep = inventory.head.obj.asInstanceOf[DetailedWeaponData] -// wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell -// wep.ammo.head.guid mustEqual PlanetSideGUID(77) -// wep.ammo.head.parentSlot mustEqual 0 -// wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 16 -// //1 -// inventory(1).objectClass mustEqual ObjectClass.suppressor -// inventory(1).guid mustEqual PlanetSideGUID(78) -// inventory(1).parentSlot mustEqual 2 -// wep = inventory(1).obj.asInstanceOf[DetailedWeaponData] -// wep.ammo.head.objectClass mustEqual ObjectClass.bullet_9mm -// wep.ammo.head.guid mustEqual PlanetSideGUID(79) -// wep.ammo.head.parentSlot mustEqual 0 -// wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 25 -// //2 -// inventory(2).objectClass mustEqual ObjectClass.forceblade -// inventory(2).guid mustEqual PlanetSideGUID(80) -// inventory(2).parentSlot mustEqual 4 -// wep = inventory(2).obj.asInstanceOf[DetailedWeaponData] -// wep.ammo.head.objectClass mustEqual ObjectClass.melee_ammo -// wep.ammo.head.guid mustEqual PlanetSideGUID(81) -// wep.ammo.head.parentSlot mustEqual 0 -// wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1 -// //3 -// inventory(3).objectClass mustEqual ObjectClass.locker_container -// inventory(3).guid mustEqual PlanetSideGUID(82) -// inventory(3).parentSlot mustEqual 5 -// inventory(3).obj.isInstanceOf[DetailedLockerContainerData] mustEqual true -// inventory(3).obj.asInstanceOf[DetailedLockerContainerData].inventory.isDefined mustEqual false -// //4 -// inventory(4).objectClass mustEqual ObjectClass.bullet_9mm -// inventory(4).guid mustEqual PlanetSideGUID(83) -// inventory(4).parentSlot mustEqual 6 -// inventory(4).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 -// //5 -// inventory(5).objectClass mustEqual ObjectClass.bullet_9mm -// inventory(5).guid mustEqual PlanetSideGUID(84) -// inventory(5).parentSlot mustEqual 9 -// inventory(5).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 -// //6 -// inventory(6).objectClass mustEqual ObjectClass.bullet_9mm -// inventory(6).guid mustEqual PlanetSideGUID(85) -// inventory(6).parentSlot mustEqual 12 -// inventory(6).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 -// //7 -// inventory(7).objectClass mustEqual ObjectClass.bullet_9mm_AP -// inventory(7).guid mustEqual PlanetSideGUID(86) -// inventory(7).parentSlot mustEqual 33 -// inventory(7).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 -// //8 -// inventory(8).objectClass mustEqual ObjectClass.energy_cell -// inventory(8).guid mustEqual PlanetSideGUID(87) -// inventory(8).parentSlot mustEqual 36 -// inventory(8).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 -// //9 -// inventory(9).objectClass mustEqual ObjectClass.remote_electronics_kit -// inventory(9).guid mustEqual PlanetSideGUID(88) -// inventory(9).parentSlot mustEqual 39 -// //the rek has data but none worth testing here -// hand mustEqual DrawnSlot.Pistol1 -// case _ => -// ko -// } -// case _ => -// ko -// } -// } -// -// "decode (character, seated)" in { -// PacketCoding.DecodePacket(string_seated).require match { -// case ObjectCreateDetailedMessage(len, cls, guid, parent, data) => -// len mustEqual 3103 -// cls mustEqual ObjectClass.avatar -// guid mustEqual PlanetSideGUID(75) -// parent.isDefined mustEqual true -// parent.get.guid mustEqual PlanetSideGUID(43981) -// 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.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 5.625f -// basic.facingYawUpper mustEqual 61.875f -// basic.lfs mustEqual true -// basic.grenade_state mustEqual GrenadeState.None -// basic.is_cloaking mustEqual false -// basic.charging_pose mustEqual false -// basic.on_zipline mustEqual None -// basic.ribbons.upper mustEqual MeritCommendation.None -// basic.ribbons.middle mustEqual MeritCommendation.None -// basic.ribbons.lower mustEqual MeritCommendation.None -// basic.ribbons.tos mustEqual MeritCommendation.None -// -// char.bep mustEqual 0 -// char.cep mustEqual 0 -// char.healthMax mustEqual 100 -// char.health mustEqual 100 -// char.armor mustEqual 50 //standard exosuit value -// char.staminaMax mustEqual 100 -// char.stamina mustEqual 100 -// char.certs.length mustEqual 7 -// char.certs.head mustEqual CertificationType.StandardAssault -// char.certs(1) mustEqual CertificationType.MediumAssault -// char.certs(2) mustEqual CertificationType.ATV -// char.certs(3) mustEqual CertificationType.Harasser -// char.certs(4) mustEqual CertificationType.StandardExoSuit -// char.certs(5) mustEqual CertificationType.AgileExoSuit -// char.certs(6) mustEqual CertificationType.ReinforcedExoSuit -// char.implants.length mustEqual 0 -// char.firstTimeEvents.size mustEqual 4 -// char.firstTimeEvents.head mustEqual "xpe_sanctuary_help" -// char.firstTimeEvents(1) mustEqual "xpe_th_firemodes" -// char.firstTimeEvents(2) mustEqual "used_beamer" -// char.firstTimeEvents(3) mustEqual "map13" -// char.tutorials.size mustEqual 0 -// char.cosmetics.isDefined mustEqual false -// inv.isDefined mustEqual true -// val inventory = inv.get.contents -// inventory.size mustEqual 10 -// //0 -// inventory.head.objectClass mustEqual ObjectClass.beamer -// inventory.head.guid mustEqual PlanetSideGUID(76) -// inventory.head.parentSlot mustEqual 0 -// var wep = inventory.head.obj.asInstanceOf[DetailedWeaponData] -// wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell -// wep.ammo.head.guid mustEqual PlanetSideGUID(77) -// wep.ammo.head.parentSlot mustEqual 0 -// wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 16 -// //1 -// inventory(1).objectClass mustEqual ObjectClass.suppressor -// inventory(1).guid mustEqual PlanetSideGUID(78) -// inventory(1).parentSlot mustEqual 2 -// wep = inventory(1).obj.asInstanceOf[DetailedWeaponData] -// wep.ammo.head.objectClass mustEqual ObjectClass.bullet_9mm -// wep.ammo.head.guid mustEqual PlanetSideGUID(79) -// wep.ammo.head.parentSlot mustEqual 0 -// wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 25 -// //2 -// inventory(2).objectClass mustEqual ObjectClass.forceblade -// inventory(2).guid mustEqual PlanetSideGUID(80) -// inventory(2).parentSlot mustEqual 4 -// wep = inventory(2).obj.asInstanceOf[DetailedWeaponData] -// wep.ammo.head.objectClass mustEqual ObjectClass.melee_ammo -// wep.ammo.head.guid mustEqual PlanetSideGUID(81) -// wep.ammo.head.parentSlot mustEqual 0 -// wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1 -// //3 -// inventory(3).objectClass mustEqual ObjectClass.locker_container -// inventory(3).guid mustEqual PlanetSideGUID(82) -// inventory(3).parentSlot mustEqual 5 -// inventory(3).obj.isInstanceOf[DetailedLockerContainerData] mustEqual true -// inventory(3).obj.asInstanceOf[DetailedLockerContainerData].inventory.isDefined mustEqual false -// //4 -// inventory(4).objectClass mustEqual ObjectClass.bullet_9mm -// inventory(4).guid mustEqual PlanetSideGUID(83) -// inventory(4).parentSlot mustEqual 6 -// inventory(4).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 -// //5 -// inventory(5).objectClass mustEqual ObjectClass.bullet_9mm -// inventory(5).guid mustEqual PlanetSideGUID(84) -// inventory(5).parentSlot mustEqual 9 -// inventory(5).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 -// //6 -// inventory(6).objectClass mustEqual ObjectClass.bullet_9mm -// inventory(6).guid mustEqual PlanetSideGUID(85) -// inventory(6).parentSlot mustEqual 12 -// inventory(6).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 -// //7 -// inventory(7).objectClass mustEqual ObjectClass.bullet_9mm_AP -// inventory(7).guid mustEqual PlanetSideGUID(86) -// inventory(7).parentSlot mustEqual 33 -// inventory(7).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 -// //8 -// inventory(8).objectClass mustEqual ObjectClass.energy_cell -// inventory(8).guid mustEqual PlanetSideGUID(87) -// inventory(8).parentSlot mustEqual 36 -// inventory(8).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 -// //9 -// inventory(9).objectClass mustEqual ObjectClass.remote_electronics_kit -// inventory(9).guid mustEqual PlanetSideGUID(88) -// inventory(9).parentSlot mustEqual 39 -// //the rek has data but none worth testing here -// hand mustEqual DrawnSlot.Pistol1 -// case _ => -// ko -// } -// case _ => -// ko -// } -// } -// -// "decode (BR32)" in { -// PacketCoding.DecodePacket(string_br32).require match { -// case ObjectCreateDetailedMessage(len, cls, guid, parent, 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 -// inv.isDefined mustEqual true -// inv.get.contents.size mustEqual 12 -// //0 -// inv.get.contents.head.objectClass mustEqual 531 -// inv.get.contents.head.guid mustEqual PlanetSideGUID(4202) -// inv.get.contents.head.parentSlot mustEqual 0 -// val wep1 = inv.get.contents.head.obj.asInstanceOf[DetailedWeaponData] -// wep1.unk1 mustEqual 2 -// wep1.unk2 mustEqual 8 -// wep1.ammo.head.objectClass mustEqual 389 -// wep1.ammo.head.guid mustEqual PlanetSideGUID(3942) -// wep1.ammo.head.parentSlot mustEqual 0 -// wep1.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].unk mustEqual 8 -// wep1.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 100 -// //4 -// inv.get.contents(4).objectClass mustEqual 456 -// inv.get.contents(4).guid mustEqual PlanetSideGUID(5374) -// inv.get.contents(4).parentSlot mustEqual 5 -// inv.get.contents(4).obj.asInstanceOf[DetailedLockerContainerData].inventory.get.contents.size mustEqual 61 -// //11 -// inv.get.contents(11).objectClass mustEqual 673 -// inv.get.contents(11).guid mustEqual PlanetSideGUID(3661) -// inv.get.contents(11).parentSlot mustEqual 60 -// val wep2 = inv.get.contents(11).obj.asInstanceOf[DetailedWeaponData] -// wep2.unk1 mustEqual 2 -// wep2.unk2 mustEqual 8 -// wep2.ammo.head.objectClass mustEqual 674 -// wep2.ammo.head.guid mustEqual PlanetSideGUID(8542) -// wep2.ammo.head.parentSlot mustEqual 0 -// wep2.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].unk mustEqual 8 -// wep2.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 3 -// -// hand mustEqual DrawnSlot.None -// case _ => -// ko -// } -// case _ => -// ko -// } -// } + "decode" in { + PacketCoding.DecodePacket(string).require match { + case ObjectCreateDetailedMessage(len, cls, guid, parent, data) => + len mustEqual 3159 + cls mustEqual ObjectClass.avatar + guid mustEqual PlanetSideGUID(75) + parent.isDefined mustEqual false + data match { + case Some(DetailedPlayerData(Some(pos), basic, char, inv, hand)) => + pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f) + pos.orient mustEqual Vector3(0, 0, 36.5625f) + pos.vel.isDefined mustEqual false + + basic 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 + } + + inv.isDefined mustEqual true + val inventory = inv.get.contents + inventory.size mustEqual 10 + //0 + inventory.head.objectClass mustEqual ObjectClass.beamer + inventory.head.guid mustEqual PlanetSideGUID(76) + inventory.head.parentSlot mustEqual 0 + var wep = inventory.head.obj.asInstanceOf[DetailedWeaponData] + wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell + wep.ammo.head.guid mustEqual PlanetSideGUID(77) + wep.ammo.head.parentSlot mustEqual 0 + wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 16 + //1 + inventory(1).objectClass mustEqual ObjectClass.suppressor + inventory(1).guid mustEqual PlanetSideGUID(78) + inventory(1).parentSlot mustEqual 2 + wep = inventory(1).obj.asInstanceOf[DetailedWeaponData] + wep.ammo.head.objectClass mustEqual ObjectClass.bullet_9mm + wep.ammo.head.guid mustEqual PlanetSideGUID(79) + wep.ammo.head.parentSlot mustEqual 0 + wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 25 + //2 + inventory(2).objectClass mustEqual ObjectClass.forceblade + inventory(2).guid mustEqual PlanetSideGUID(80) + inventory(2).parentSlot mustEqual 4 + wep = inventory(2).obj.asInstanceOf[DetailedWeaponData] + wep.ammo.head.objectClass mustEqual ObjectClass.melee_ammo + wep.ammo.head.guid mustEqual PlanetSideGUID(81) + wep.ammo.head.parentSlot mustEqual 0 + wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1 + //3 + inventory(3).objectClass mustEqual ObjectClass.locker_container + inventory(3).guid mustEqual PlanetSideGUID(82) + inventory(3).parentSlot mustEqual 5 + inventory(3).obj.isInstanceOf[DetailedLockerContainerData] mustEqual true + inventory(3).obj.asInstanceOf[DetailedLockerContainerData].inventory.isDefined mustEqual false + //4 + inventory(4).objectClass mustEqual ObjectClass.bullet_9mm + inventory(4).guid mustEqual PlanetSideGUID(83) + inventory(4).parentSlot mustEqual 6 + inventory(4).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 + //5 + inventory(5).objectClass mustEqual ObjectClass.bullet_9mm + inventory(5).guid mustEqual PlanetSideGUID(84) + inventory(5).parentSlot mustEqual 9 + inventory(5).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 + //6 + inventory(6).objectClass mustEqual ObjectClass.bullet_9mm + inventory(6).guid mustEqual PlanetSideGUID(85) + inventory(6).parentSlot mustEqual 12 + inventory(6).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 + //7 + inventory(7).objectClass mustEqual ObjectClass.bullet_9mm_AP + inventory(7).guid mustEqual PlanetSideGUID(86) + inventory(7).parentSlot mustEqual 33 + inventory(7).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 + //8 + inventory(8).objectClass mustEqual ObjectClass.energy_cell + inventory(8).guid mustEqual PlanetSideGUID(87) + inventory(8).parentSlot mustEqual 36 + inventory(8).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 + //9 + inventory(9).objectClass mustEqual ObjectClass.remote_electronics_kit + inventory(9).guid mustEqual PlanetSideGUID(88) + inventory(9).parentSlot mustEqual 39 + //the rek has data but none worth testing here + hand mustEqual DrawnSlot.Pistol1 + case _ => + ko + } + case _ => + ko + } + } + + "decode (character, seated)" in { + PacketCoding.DecodePacket(string_seated).require match { + case ObjectCreateDetailedMessage(len, cls, guid, parent, data) => + len mustEqual 3103 + cls mustEqual ObjectClass.avatar + guid mustEqual PlanetSideGUID(75) + parent.isDefined mustEqual true + parent.get.guid mustEqual PlanetSideGUID(43981) + parent.get.slot mustEqual 0 + data match { + case Some(DetailedPlayerData(None, basic, char, inv, hand)) => + 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 + } + + inv.isDefined mustEqual true + val inventory = inv.get.contents + inventory.size mustEqual 10 + //0 + inventory.head.objectClass mustEqual ObjectClass.beamer + inventory.head.guid mustEqual PlanetSideGUID(76) + inventory.head.parentSlot mustEqual 0 + var wep = inventory.head.obj.asInstanceOf[DetailedWeaponData] + wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell + wep.ammo.head.guid mustEqual PlanetSideGUID(77) + wep.ammo.head.parentSlot mustEqual 0 + wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 16 + //1 + inventory(1).objectClass mustEqual ObjectClass.suppressor + inventory(1).guid mustEqual PlanetSideGUID(78) + inventory(1).parentSlot mustEqual 2 + wep = inventory(1).obj.asInstanceOf[DetailedWeaponData] + wep.ammo.head.objectClass mustEqual ObjectClass.bullet_9mm + wep.ammo.head.guid mustEqual PlanetSideGUID(79) + wep.ammo.head.parentSlot mustEqual 0 + wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 25 + //2 + inventory(2).objectClass mustEqual ObjectClass.forceblade + inventory(2).guid mustEqual PlanetSideGUID(80) + inventory(2).parentSlot mustEqual 4 + wep = inventory(2).obj.asInstanceOf[DetailedWeaponData] + wep.ammo.head.objectClass mustEqual ObjectClass.melee_ammo + wep.ammo.head.guid mustEqual PlanetSideGUID(81) + wep.ammo.head.parentSlot mustEqual 0 + wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1 + //3 + inventory(3).objectClass mustEqual ObjectClass.locker_container + inventory(3).guid mustEqual PlanetSideGUID(82) + inventory(3).parentSlot mustEqual 5 + inventory(3).obj.isInstanceOf[DetailedLockerContainerData] mustEqual true + inventory(3).obj.asInstanceOf[DetailedLockerContainerData].inventory.isDefined mustEqual false + //4 + inventory(4).objectClass mustEqual ObjectClass.bullet_9mm + inventory(4).guid mustEqual PlanetSideGUID(83) + inventory(4).parentSlot mustEqual 6 + inventory(4).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 + //5 + inventory(5).objectClass mustEqual ObjectClass.bullet_9mm + inventory(5).guid mustEqual PlanetSideGUID(84) + inventory(5).parentSlot mustEqual 9 + inventory(5).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 + //6 + inventory(6).objectClass mustEqual ObjectClass.bullet_9mm + inventory(6).guid mustEqual PlanetSideGUID(85) + inventory(6).parentSlot mustEqual 12 + inventory(6).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 + //7 + inventory(7).objectClass mustEqual ObjectClass.bullet_9mm_AP + inventory(7).guid mustEqual PlanetSideGUID(86) + inventory(7).parentSlot mustEqual 33 + inventory(7).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 + //8 + inventory(8).objectClass mustEqual ObjectClass.energy_cell + inventory(8).guid mustEqual PlanetSideGUID(87) + inventory(8).parentSlot mustEqual 36 + inventory(8).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 + //9 + inventory(9).objectClass mustEqual ObjectClass.remote_electronics_kit + inventory(9).guid mustEqual PlanetSideGUID(88) + inventory(9).parentSlot mustEqual 39 + //the rek has data but none worth testing here + hand mustEqual DrawnSlot.Pistol1 + case _ => + ko + } + case _ => + ko + } + } + + "decode (BR32)" in { + PacketCoding.DecodePacket(string_br32).require match { + 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(_), 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 + inv.get.contents.head.objectClass mustEqual 531 + inv.get.contents.head.guid mustEqual PlanetSideGUID(4202) + inv.get.contents.head.parentSlot mustEqual 0 + val wep1 = inv.get.contents.head.obj.asInstanceOf[DetailedWeaponData] + wep1.unk1 mustEqual 2 + wep1.unk2 mustEqual 8 + wep1.ammo.head.objectClass mustEqual 389 + wep1.ammo.head.guid mustEqual PlanetSideGUID(3942) + wep1.ammo.head.parentSlot mustEqual 0 + wep1.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].unk mustEqual 8 + wep1.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 100 + //4 + inv.get.contents(4).objectClass mustEqual 456 + inv.get.contents(4).guid mustEqual PlanetSideGUID(5374) + inv.get.contents(4).parentSlot mustEqual 5 + inv.get.contents(4).obj.asInstanceOf[DetailedLockerContainerData].inventory.get.contents.size mustEqual 61 + //11 + inv.get.contents(11).objectClass mustEqual 673 + inv.get.contents(11).guid mustEqual PlanetSideGUID(3661) + inv.get.contents(11).parentSlot mustEqual 60 + val wep2 = inv.get.contents(11).obj.asInstanceOf[DetailedWeaponData] + wep2.unk1 mustEqual 2 + wep2.unk2 mustEqual 8 + wep2.ammo.head.objectClass mustEqual 674 + wep2.ammo.head.guid mustEqual PlanetSideGUID(8542) + wep2.ammo.head.parentSlot mustEqual 0 + wep2.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].unk mustEqual 8 + wep2.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 3 + + hand mustEqual DrawnSlot.None + case _ => + ko + } + case _ => + ko + } + } "encode" 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, @@ -376,24 +875,59 @@ class DetailedCharacterDataTest extends Specification { ), 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, 61.875f, true, GrenadeState.None, false, false, - None, - RibbonBars() + false, + 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, + 32831L, 100, 100, + 0, 0, 0L, + List(0, 0, 0, 0, 0, 0), List( CertificationType.StandardAssault, CertificationType.MediumAssault, @@ -402,16 +936,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)) :: @@ -427,26 +979,13 @@ class DetailedCharacterDataTest extends Specification { val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt_bitv = pkt.toBitVector val ori_bitv = string.toBitVector - var a = 0 - var slicePkt = bin"" - var sliceOri = bin"" - do { - pkt_bitv.slice(a, a + 256) mustEqual ori_bitv.slice(a, a + 256) - a = a + 256 - } - while(a <= pkt_bitv.length) - ok - - -// 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) -// //TODO work on DetailedCharacterData to make this pass as a single stream + 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, @@ -456,24 +995,59 @@ class DetailedCharacterDataTest extends Specification { ), false, false, + false, + None, + false, ExoSuitType.Standard, + None, + 0, + 0, + 192L, + 0, + 0, + 0, + 0 + ) + val ab : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB( + 0L, "", 0, false, + false, + false, + false, + false, 5.625f, 61.875f, true, GrenadeState.None, false, false, - None, - RibbonBars() + false, + 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, + 32831L, 100, 100, + 0, 0, 0L, + List(0, 0, 0, 0, 0, 0), List( CertificationType.StandardAssault, CertificationType.MediumAssault, @@ -482,12 +1056,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))) :: @@ -508,7 +1097,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 { @@ -517,17 +1108,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), - 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, None, + false, + true, + false, + false, + false, + None + ) + + val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData( + aa, ab, RibbonBars( MeritCommendation.Loser4, MeritCommendation.EventNCElite, @@ -535,11 +1160,17 @@ class DetailedCharacterDataTest extends Specification { MeritCommendation.SixYearNC ) ) - val char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData( - 6366766, - 694787, - 100, 100, 100, + 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, @@ -556,12 +1187,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", @@ -867,8 +1502,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, @@ -1057,16 +1698,7 @@ 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 } } } From 19f77fc9b56a2937c56a936457976d5b688412e7 Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 17 Oct 2018 12:26:25 -0400 Subject: [PATCH 4/4] adding comments to OC*M Codec files; modifying converters to take advantage of the new packet field extensions; fixing two pieces of GUID code that relied on throwing Exceptions to function correctly --- .../converter/AvatarConverter.scala | 65 +- .../converter/CharacterSelectConverter.scala | 77 ++- .../converter/CorpseConverter.scala | 76 ++- .../objects/entity/IdentifiableEntity.scala | 11 +- .../guid/actor/UniqueNumberSystem.scala | 14 +- .../game/objectcreate/CharacterData.scala | 12 +- .../objectcreate/DetailedCharacterData.scala | 140 +++- .../objectcreate/DetailedPlayerData.scala | 27 +- .../DetailedCharacterDataTest.scala | 638 ++++++++++++++++++ .../MountedVehiclesTest.scala | 408 ++++++----- .../test/scala/PacketCodingActorTest.scala | 92 ++- 11 files changed, 1242 insertions(+), 318 deletions(-) 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 2b4e511ab..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,36 +64,59 @@ 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), 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 = None, - 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), MakeImplantEffectList(obj.Implants), MakeCosmetics(obj.BEP) @@ -101,20 +124,32 @@ object AvatarConverter { } 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 = { @@ -184,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) } 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 2a9954dfa..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,23 +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), 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 = None, - 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 3c2b3c1ff..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,23 +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), 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 = None, - 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/CharacterData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterData.scala index db1ef9679..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 @@ -43,15 +43,16 @@ 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; @@ -74,8 +75,6 @@ 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, @@ -88,7 +87,6 @@ final case class CharacterData(health : Int, 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 = implant_effects.length * 4L val cosmeticsSize : Long = if(cosmetics.isDefined) { cosmetics.get.bitsize } else { 0L } @@ -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 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 51671020f..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 @@ -13,7 +13,8 @@ import scala.annotation.tailrec /** * An entry in the `List` of valid implant slots in `DetailedCharacterData`. * @param implant the type of implant - * 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` @@ -33,11 +34,21 @@ object ImplantEntry { } } +/** + * 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 @@ -124,10 +135,8 @@ final case class DetailedCharacterB(unk1 : Option[Long], //implant list val implantSize : Long = implants.foldLeft(0L)(_ + _.bitsize) //fte list - val fteLen = firstTimeEvents.size val eventListSize : Long = firstTimeEvents.foldLeft(0L)(_ + StreamBitSize.stringBitSize(_)) //tutorial list - val tutLen = tutorials.size val tutorialListSize : Long = tutorials.foldLeft(0L)(_ + StreamBitSize.stringBitSize(_)) val unk2Len = unk2.size val unk3Len = unk3.size @@ -151,8 +160,8 @@ final case class DetailedCharacterB(unk1 : Option[Long], 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))(fteLen) + /* firstTimeEvents */ - DetailedCharacterData.paddingCalculations(pad_length, implants, List(firstTimeEvents, unk3, unk2))(tutLen) + /* tutorials */ + 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, @@ -245,15 +254,15 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { val activeBool : Boolean = n != 0 ImplantEntry(ImplantType(implant), None, activeBool) //TODO catch potential NoSuchElementException? - case implant :: false :: extra :: HNil => //unintialized (timer), inactive + case implant :: false :: extra :: HNil => //uninitialized (timer), inactive ImplantEntry(ImplantType(implant), Some(extra), false) //TODO catch potential NoSuchElementException? }, { case ImplantEntry(implant, None, n) => //initialized (no timer), active/inactive? - val activeInt : Int = if(n) { 1 } else { 0 } + val activeInt : Int = if(n) { 1 } else { 0 } implant.id :: true :: activeInt :: HNil - case ImplantEntry(implant, Some(extra), _) => //unintialized (timer), inactive + case ImplantEntry(implant, Some(extra), _) => //uninitialized (timer), inactive implant.id :: false :: extra :: HNil } ) @@ -279,6 +288,10 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { } } + /** + * `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 dcd_list_codec(padFunc : (Long)=>Int) : Codec[List[DCDExtra1]] = ( uint8 >>:~ { size => conditional(size > 0, dcd_extra1_codec(padFunc(size))) :: @@ -301,6 +314,10 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { } ) + /** + * `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) @@ -315,6 +332,12 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { } ) + /** + * 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))) :: @@ -337,11 +360,22 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { } ) + /** + * `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))) :: @@ -364,11 +398,25 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { } ) + /** + * 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) @@ -376,18 +424,56 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { 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(currListLen > 0) { - //the offset with no implant entries, or bits from context + 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 } @@ -414,21 +500,6 @@ 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` -// */ -// 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 } -// -// val implantOffset = implants.foldLeft(0L)(_ + _.bitsize).toInt -// val resultB : Int = resultA - (implantOffset % 8) -// if(resultB < 0) { 8 + resultB } else { resultB } -// } - /** * Players with certain battle rank will always have a certain number of implant slots. * The encoding requires it. @@ -447,6 +518,13 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { } } + /** + * 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 + */ def isBR24(bep : Long) : Boolean = bep > 2286230 val a_codec : Codec[DetailedCharacterA] = ( @@ -462,27 +540,27 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { ("unk5" | uint32) :: //endianness? ("staminaMax" | uint16L) :: ("stamina" | uint16L) :: - //TODO optional 32u-something here; see ps.c: line#1070692 + conditional(false, uint32L) :: //see ps.c: sub_901150, line#1070692 ("unk6" | uint16L) :: ("unk7" | uint(3)) :: ("unk8" | uint32L) :: - ("unk9" | PacketHelpers.listOfNSized(6, uint16L)) :: //always 6 + ("unk9" | PacketHelpers.listOfNSized(6, uint16L)) :: //always length of 6 ("certs" | listOfN(uint8L, CertificationType.codec)) ).exmap[DetailedCharacterA] ( { - case bep :: cep :: u1 :: u2 :: u3 :: healthMax :: health :: u4 :: armor :: u5 :: staminaMax :: stamina :: u6 :: u7 :: u8 :: u9 :: certs :: HNil => + 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 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 :: u6 :: u7 :: u8 :: u9 :: certs :: HNil + 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) :: //ask about sample CCRIDER + 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 => 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/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala index 7d3232004..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 { @@ -860,6 +861,143 @@ 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, @@ -1700,5 +1838,505 @@ class DetailedCharacterDataTest extends Specification { val pkt = PacketCoding.EncodePacket(msg).require.toByteVector 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 d007934a1..cbeccc0c9 100644 --- a/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala +++ b/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala @@ -9,182 +9,236 @@ import org.specs2.mutable._ import scodec.bits._ class MountedVehiclesTest extends Specification { -// val string_mosquito_seated = -// hex"17c70700009e2d410d8ed818f1a4017047f7ffbc6390ffbe01801cff00003c08791801d00000002340530063007200610077006e00790052" ++ -// hex"006f006e006e0069006500020b7e67b540404001000000000022b50100268042006c00610063006b00200042006500720065007400200041" ++ -// hex"0072006d006f007500720065006400200043006f00720070007300170040030050040003bc00000234040001a00400027a7a0809a6910800" ++ -// hex"00000008090a6403603000001082202e040000000202378ae0e80c00000162710b82000000008083837032030000015e2583210000000020" ++ -// hex"20e21c0c80c000007722120e81c0000000808063483603000000" -// -// "decode (Scrawny Ronnie's mosquito)" in { -// PacketCoding.DecodePacket(string_mosquito_seated).require match { -// case ObjectCreateMessage(len, cls, guid, parent, data) => -// len mustEqual 1991 -// cls mustEqual ObjectClass.mosquito -// guid mustEqual PlanetSideGUID(4308) -// parent mustEqual None -// data match { -// case Some(vdata : VehicleData) => -// vdata.pos.coord mustEqual Vector3(4571.6875f, 5602.1875f, 93) -// vdata.pos.orient mustEqual Vector3(11.25f, 2.8125f, 92.8125f) -// vdata.pos.vel mustEqual Some(Vector3(31.71875f, 8.875f, -0.03125f)) -// vdata.faction mustEqual PlanetSideEmpire.TR -// vdata.bops mustEqual false -// vdata.destroyed mustEqual false -// vdata.jammered mustEqual false -// vdata.owner_guid mustEqual PlanetSideGUID(3776) -// vdata.health mustEqual 255 -// vdata.no_mount_points mustEqual false -// vdata.driveState mustEqual DriveState.Mobile -// vdata.cloak mustEqual false -// vdata.unk1 mustEqual 0 -// vdata.unk2 mustEqual false -// vdata.unk3 mustEqual false -// vdata.unk4 mustEqual false -// vdata.unk5 mustEqual false -// vdata.unk6 mustEqual false -// vdata.vehicle_format_data mustEqual Some(VariantVehicleData(7)) -// vdata.inventory match { -// case Some(InventoryData(list)) => -// list.head.objectClass mustEqual ObjectClass.avatar -// 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.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.isEmpty mustEqual true -// char.cosmetics mustEqual Some(Cosmetics(true, true, true, true, false)) -// inv.size mustEqual 4 -// inv.head.objectClass mustEqual ObjectClass.medicalapplicator -// inv.head.parentSlot mustEqual 0 -// inv(1).objectClass mustEqual ObjectClass.bank -// inv(1).parentSlot mustEqual 1 -// inv(2).objectClass mustEqual ObjectClass.mini_chaingun -// inv(2).parentSlot mustEqual 2 -// inv(3).objectClass mustEqual ObjectClass.chainblade -// inv(3).parentSlot mustEqual 4 -// hand mustEqual DrawnSlot.None -// case _ => -// ko -// } -// list(1).objectClass mustEqual ObjectClass.rotarychaingun_mosquito -// list(1).parentSlot mustEqual 1 -// case None => -// ko -// } -// case _ => -// ko -// } -// case _ => -// ko -// } -// } -// -// "encode (Scrawny Ronnie's mosquito)" in { -// val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData( -// BasicCharacterData("ScrawnyRonnie", PlanetSideEmpire.TR, CharacterGender.Male, 5, CharacterVoice.Voice5), -// false, false, -// ExoSuitType.Agile, -// "Black Beret Armoured Corps", -// 23, -// false, -// 354.375f, 0.0f, -// false, -// GrenadeState.None, false, false, None, -// RibbonBars( -// MeritCommendation.MarkovVeteran, -// MeritCommendation.HeavyInfantry4, -// MeritCommendation.TankBuster7, -// MeritCommendation.SixYearTR -// ) -// ) -// val char : (Boolean,Boolean)=>CharacterData = CharacterData( -// 100, 0, -// UniformStyle.ThirdUpgrade, -// 0, -// 5, -// Nil, -// Some(Cosmetics(true, true, true, true, false)) -// ) -// val inv : InventoryData = InventoryData( -// List( -// InternalSlot(ObjectClass.medicalapplicator, PlanetSideGUID(4201), 0, -// WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.health_canister, PlanetSideGUID(3472), 0, AmmoBoxData(0)))) -// ), -// InternalSlot(ObjectClass.bank, PlanetSideGUID(2952), 1, -// WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.armor_canister, PlanetSideGUID(3758), 0, AmmoBoxData(0)))) -// ), -// InternalSlot(ObjectClass.mini_chaingun, PlanetSideGUID(2929), 2, -// WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.bullet_9mm, PlanetSideGUID(3292), 0, AmmoBoxData(0)))) -// ), -// InternalSlot(ObjectClass.chainblade, PlanetSideGUID(3222), 4, -// WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.melee_ammo, PlanetSideGUID(3100), 0, AmmoBoxData(0)))) -// ) -// ) -// ) -// val player = VehicleData.PlayerData(app, char, inv, DrawnSlot.None, VehicleData.InitialStreamLengthToSeatEntries(true, VehicleFormat.Variant)) -// val obj = VehicleData( -// PlacementData( -// Vector3(4571.6875f, 5602.1875f, 93), -// Vector3(11.25f, 2.8125f, 92.8125f), -// Some(Vector3(31.71875f, 8.875f, -0.03125f)) -// ), -// PlanetSideEmpire.TR, -// false, false, -// 0, -// false, false, -// PlanetSideGUID(3776), -// false, -// 255, -// false, false, -// DriveState.Mobile, -// false, false, false, -// Some(VariantVehicleData(7)), -// Some( -// InventoryData( -// List( -// InternalSlot(ObjectClass.avatar, PlanetSideGUID(3776), 0, player), -// InternalSlot(ObjectClass.rotarychaingun_mosquito, PlanetSideGUID(3602), 1, -// WeaponData(6, 0, 0, List(InternalSlot(ObjectClass.bullet_12mm, PlanetSideGUID(3538), 0, AmmoBoxData(0)))) -// ) -// ) -// ) -// ) -// )(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 -// } + val string_mosquito_seated = + hex"17c70700009e2d410d8ed818f1a4017047f7ffbc6390ffbe01801cff00003c08791801d00000002340530063007200610077006e00790052" ++ + hex"006f006e006e0069006500020b7e67b540404001000000000022b50100268042006c00610063006b00200042006500720065007400200041" ++ + hex"0072006d006f007500720065006400200043006f00720070007300170040030050040003bc00000234040001a00400027a7a0809a6910800" ++ + hex"00000008090a6403603000001082202e040000000202378ae0e80c00000162710b82000000008083837032030000015e2583210000000020" ++ + hex"20e21c0c80c000007722120e81c0000000808063483603000000" + + "decode (Scrawny Ronnie's mosquito)" in { + PacketCoding.DecodePacket(string_mosquito_seated).require match { + case ObjectCreateMessage(len, cls, guid, parent, data) => + len mustEqual 1991 + cls mustEqual ObjectClass.mosquito + guid mustEqual PlanetSideGUID(4308) + parent mustEqual None + data match { + case Some(vdata : VehicleData) => + vdata.pos.coord mustEqual Vector3(4571.6875f, 5602.1875f, 93) + vdata.pos.orient mustEqual Vector3(11.25f, 2.8125f, 92.8125f) + vdata.pos.vel mustEqual Some(Vector3(31.71875f, 8.875f, -0.03125f)) + vdata.faction mustEqual PlanetSideEmpire.TR + vdata.bops mustEqual false + vdata.destroyed mustEqual false + vdata.jammered mustEqual false + vdata.owner_guid mustEqual PlanetSideGUID(3776) + vdata.health mustEqual 255 + vdata.no_mount_points mustEqual false + vdata.driveState mustEqual DriveState.Mobile + vdata.cloak mustEqual false + vdata.unk1 mustEqual 0 + vdata.unk2 mustEqual false + vdata.unk3 mustEqual false + vdata.unk4 mustEqual false + vdata.unk5 mustEqual false + vdata.unk6 mustEqual false + vdata.vehicle_format_data mustEqual Some(VariantVehicleData(7)) + vdata.inventory match { + case Some(InventoryData(list)) => + list.head.objectClass mustEqual ObjectClass.avatar + list.head.guid mustEqual PlanetSideGUID(3776) + list.head.parentSlot mustEqual 0 + list.head.obj match { + 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 + inv(1).objectClass mustEqual ObjectClass.bank + inv(1).parentSlot mustEqual 1 + inv(2).objectClass mustEqual ObjectClass.mini_chaingun + inv(2).parentSlot mustEqual 2 + inv(3).objectClass mustEqual ObjectClass.chainblade + inv(3).parentSlot mustEqual 4 + case _ => + ko + } + //back to mosquito inventory + list(1).objectClass mustEqual ObjectClass.rotarychaingun_mosquito + list(1).parentSlot mustEqual 1 + case None => + ko + } + case _ => + ko + } + case _ => + ko + } + } + + "encode (Scrawny Ronnie's mosquito)" in { + 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, + 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, + MeritCommendation.TankBuster7, + MeritCommendation.SixYearTR + ) + ) + val char : (Boolean,Boolean)=>CharacterData = CharacterData( + 100, 0, + UniformStyle.ThirdUpgrade, + 7, + 5, + Nil, + Some(Cosmetics(true, true, true, true, false)) + ) + val inv : InventoryData = InventoryData( + List( + InternalSlot(ObjectClass.medicalapplicator, PlanetSideGUID(4201), 0, + WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.health_canister, PlanetSideGUID(3472), 0, AmmoBoxData(0)))) + ), + InternalSlot(ObjectClass.bank, PlanetSideGUID(2952), 1, + WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.armor_canister, PlanetSideGUID(3758), 0, AmmoBoxData(0)))) + ), + InternalSlot(ObjectClass.mini_chaingun, PlanetSideGUID(2929), 2, + WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.bullet_9mm, PlanetSideGUID(3292), 0, AmmoBoxData(0)))) + ), + InternalSlot(ObjectClass.chainblade, PlanetSideGUID(3222), 4, + WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.melee_ammo, PlanetSideGUID(3100), 0, AmmoBoxData(0)))) + ) + ) + ) + val player = VehicleData.PlayerData(app, char, inv, DrawnSlot.None, VehicleData.InitialStreamLengthToSeatEntries(true, VehicleFormat.Variant)) + val obj = VehicleData( + PlacementData( + Vector3(4571.6875f, 5602.1875f, 93), + Vector3(11.25f, 2.8125f, 92.8125f), + Some(Vector3(31.71875f, 8.875f, -0.03125f)) + ), + PlanetSideEmpire.TR, + false, false, + 0, + false, false, + PlanetSideGUID(3776), + false, + 255, + false, false, + DriveState.Mobile, + false, false, false, + Some(VariantVehicleData(7)), + Some( + InventoryData( + List( + InternalSlot(ObjectClass.avatar, PlanetSideGUID(3776), 0, player), + InternalSlot(ObjectClass.rotarychaingun_mosquito, PlanetSideGUID(3602), 1, + WeaponData(6, 0, 0, List(InternalSlot(ObjectClass.bullet_12mm, PlanetSideGUID(3538), 0, AmmoBoxData(0)))) + ) + ) + ) + ) + )(VehicleFormat.Variant) + val msg = ObjectCreateMessage(ObjectClass.mosquito, PlanetSideGUID(4308), obj) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + pkt mustEqual string_mosquito_seated + } } diff --git a/pslogin/src/test/scala/PacketCodingActorTest.scala b/pslogin/src/test/scala/PacketCodingActorTest.scala index c129ab732..dc446d0dd 100644 --- a/pslogin/src/test/scala/PacketCodingActorTest.scala +++ b/pslogin/src/test/scala/PacketCodingActorTest.scala @@ -484,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 { @@ -545,34 +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), + 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, - None, - RibbonBars() - ) - var char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData( - 0, - 0, - 100, 100, - 50, - 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, + 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),