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 094980fc7..10a332b52 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 @@ -3,47 +3,52 @@ package net.psforever.objects.definition.converter import net.psforever.objects.{EquipmentSlot, Player} import net.psforever.objects.equipment.Equipment -import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, Cosmetics, DetailedCharacterData, DrawnSlot, ImplantEffects, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle} +import net.psforever.packet.game.objectcreate._ import net.psforever.types.{GrenadeState, ImplantType} import scala.annotation.tailrec import scala.util.{Success, Try} class AvatarConverter extends ObjectCreateConverter[Player]() { - override def ConstructorData(obj : Player) : Try[CharacterData] = { + override def ConstructorData(obj : Player) : Try[PlayerData] = { val MaxArmor = obj.MaxArmor - Success( - CharacterData( + Success( + PlayerData.apply( + PlacementData(obj.Position, obj.Orientation, obj.Velocity), MakeAppearanceData(obj), - 255 * obj.Health / obj.MaxHealth, //TODO not precise - if(MaxArmor == 0) { 0 } else { 255 * obj.Armor / MaxArmor }, //TODO not precise - DressBattleRank(obj), - DressCommandRank(obj), - recursiveMakeImplantEffects(obj.Implants.iterator), - MakeCosmetics(obj.BEP), + CharacterData( + 255 * obj.Health / obj.MaxHealth, //TODO not precise + if(MaxArmor == 0) { 0 } else { 255 * obj.Armor / MaxArmor }, //TODO not precise + DressBattleRank(obj), + DressCommandRank(obj), + recursiveMakeImplantEffects(obj.Implants.iterator), + MakeCosmetics(obj.BEP) + ), InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot)), //TODO is sorting necessary? GetDrawnSlot(obj) ) ) - //TODO tidy this mess up } - override def DetailedConstructorData(obj : Player) : Try[DetailedCharacterData] = { + override def DetailedConstructorData(obj : Player) : Try[DetailedPlayerData] = { Success( - DetailedCharacterData( + DetailedPlayerData.apply( + PlacementData(obj.Position, obj.Orientation, obj.Velocity), MakeAppearanceData(obj), - obj.BEP, - obj.CEP, - obj.MaxHealth, - obj.Health, - obj.Armor, - obj.MaxStamina, - obj.Stamina, - obj.Certifications.toList.sortBy(_.id), //TODO is sorting necessary? - MakeImplantEntries(obj), - List.empty[String], //TODO fte list - List.empty[String], //TODO tutorial list - MakeCosmetics(obj.BEP), + DetailedCharacterData( + obj.BEP, + obj.CEP, + obj.MaxHealth, + obj.Health, + obj.Armor, + obj.MaxStamina, + obj.Stamina, + obj.Certifications.toList.sortBy(_.id), //TODO is sorting necessary? + MakeImplantEntries(obj), + List.empty[String], //TODO fte list + List.empty[String], //TODO tutorial list + MakeCosmetics(obj.BEP) + ), InventoryData((MakeHolsters(obj, BuildDetailedEquipment) ++ MakeFifthSlot(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)), GetDrawnSlot(obj) ) @@ -55,9 +60,8 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { * @param obj the `Player` game object * @return the resulting `CharacterAppearanceData` */ - private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = { + private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = { CharacterAppearanceData( - PlacementData(obj.Position, obj.Orientation, obj.Velocity), BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, obj.Voice), 0, false, 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 293c71ced..bb00d7dc3 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 @@ -3,7 +3,7 @@ package net.psforever.objects.definition.converter import net.psforever.objects.{EquipmentSlot, Player} import net.psforever.objects.equipment.Equipment -import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars} +import net.psforever.packet.game.objectcreate._ import net.psforever.types.{GrenadeState, ImplantType} import scala.annotation.tailrec @@ -15,19 +15,22 @@ import scala.util.{Failure, Success, Try} * Details that would not be apparent on that screen such as implants or certifications are ignored. */ class CharacterSelectConverter extends AvatarConverter { - override def ConstructorData(obj : Player) : Try[CharacterData] = Failure(new Exception("CharacterSelectConverter should not be used to generate CharacterData")) + override def ConstructorData(obj : Player) : Try[PlayerData] = Failure(new Exception("CharacterSelectConverter should not be used to generate CharacterData")) - override def DetailedConstructorData(obj : Player) : Try[DetailedCharacterData] = { + override def DetailedConstructorData(obj : Player) : Try[DetailedPlayerData] = { Success( - DetailedCharacterData( + DetailedPlayerData.apply( + PlacementData(0, 0, 0), MakeAppearanceData(obj), - obj.BEP, - obj.CEP, - 1, 1, 0, 1, 1, - Nil, - MakeImplantEntries(obj), //necessary for correct stream length - Nil, Nil, - MakeCosmetics(obj.BEP), + DetailedCharacterData( + obj.BEP, + obj.CEP, + 1, 1, 0, 1, 1, + Nil, + MakeImplantEntries(obj), //necessary for correct stream length + Nil, Nil, + MakeCosmetics(obj.BEP) + ), InventoryData(recursiveMakeHolsters(obj.Holsters().iterator)), GetDrawnSlot(obj) ) @@ -40,9 +43,8 @@ class CharacterSelectConverter extends AvatarConverter { * @see `AvatarConverter.MakeAppearanceData` * @return the resulting `CharacterAppearanceData` */ - private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = { + private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = { CharacterAppearanceData( - PlacementData(0f, 0f, 0f), BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, 1), 0, false, 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 68b7df90d..b32a0a822 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 @@ -3,23 +3,26 @@ package net.psforever.objects.definition.converter import net.psforever.objects.{EquipmentSlot, Player} import net.psforever.objects.equipment.Equipment -import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, InternalSlot, InventoryData, PlacementData, RibbonBars} +import net.psforever.packet.game.objectcreate._ import net.psforever.types.{CharacterGender, GrenadeState, Vector3} import scala.annotation.tailrec import scala.util.{Failure, Success, Try} class CorpseConverter extends AvatarConverter { - override def ConstructorData(obj : Player) : Try[CharacterData] = + override def ConstructorData(obj : Player) : Try[PlayerData] = Failure(new Exception("CorpseConverter should not be used to generate CharacterData")) - override def DetailedConstructorData(obj : Player) : Try[DetailedCharacterData] = { + override def DetailedConstructorData(obj : Player) : Try[DetailedPlayerData] = { Success( - DetailedCharacterData( + DetailedPlayerData.apply( + PlacementData(obj.Position, Vector3(0,0, obj.Orientation.z)), MakeAppearanceData(obj), - 0, 0, 0, 0, 0, 0, 0, - Nil, Nil, Nil, Nil, - None, + DetailedCharacterData( + 0, 0, 0, 0, 0, 0, 0, + Nil, Nil, Nil, Nil, + None + ), InventoryData((MakeHolsters(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)), DrawnSlot.None ) @@ -31,9 +34,8 @@ class CorpseConverter extends AvatarConverter { * @param obj the `Player` game object * @return the resulting `CharacterAppearanceData` */ - private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = { + private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = { CharacterAppearanceData( - PlacementData(obj.Position, Vector3(0,0, obj.Orientation.z)), BasicCharacterData(obj.Name, obj.Faction, CharacterGender.Male, 0, 0), 0, false, 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 92dd6e6b1..581b81a22 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 @@ -23,6 +23,8 @@ import shapeless.{::, HNil} * `5 - male_5    female_5`
* `6 - female_1  no voice`
* `7 - female_2  no voice` + * @see `PlanetSideEmpire`
+ * `CharacaterGender` * @param name the unique name of the avatar; * minimum of two characters * @param faction the empire to which the avatar belongs @@ -30,8 +32,6 @@ import shapeless.{::, HNil} * @param head the avatar's face and hair; * by row and column on the character creation screen, the high nibble is the row and the low nibble is the column * @param voice the avatar's voice selection - * @see `PlanetSideEmpire` - * @see `CharacaterGender` */ final case class BasicCharacterData(name : String, faction : PlanetSideEmpire.Value, @@ -60,8 +60,12 @@ final case class BasicCharacterData(name : String, *
* Exploration:
* How do I crouch? - * @param pos the position of the character in the world environment (in three coordinates) - * @param basic_appearance the player's cardinal appearance settings + * @see `CharacterData`
+ * `DetailedCharacterData`
+ * `ExoSuitType`
+ * `GrenadeState`
+ * `RibbonBars` + * @param app the player's cardinal appearance settings * @param voice2 na; * affects the frequency by which the character's voice is heard (somehow); * commonly 3 for best results @@ -86,16 +90,8 @@ final case class BasicCharacterData(name : String, * @param charging_pose animation pose for both charging modules and BFR imprinting * @param on_zipline player's model is changed into a faction-color ball of energy, as if on a zip line * @param ribbons the four merit commendation ribbon medals - * @see `CharacterData` - * @see `DetailedCharacterData` - * @see `PlacementData` - * @see `ExoSuitType` - * @see `GrenadeState` - * @see `RibbonBars` - * @see `http://wiki.planetsidesyndicate.com/index.php?title=Outfit_Logo` for a list of outfit decals */ -final case class CharacterAppearanceData(pos : PlacementData, - basic_appearance : BasicCharacterData, +final case class CharacterAppearanceData(app : BasicCharacterData, voice2 : Int, black_ops : Boolean, jammered : Boolean, @@ -110,22 +106,29 @@ final case class CharacterAppearanceData(pos : PlacementData, is_cloaking : Boolean, charging_pose : Boolean, on_zipline : Boolean, - ribbons : RibbonBars) extends StreamBitSize { + ribbons : RibbonBars) + (name_padding : Int) extends StreamBitSize { override def bitsize : Long = { //factor guard bool values into the base size, not its corresponding optional field - val placementSize : Long = pos.bitsize - val nameStringSize : Long = StreamBitSize.stringBitSize(basic_appearance.name, 16) + CharacterAppearanceData.namePadding(pos.vel) + val nameStringSize : Long = StreamBitSize.stringBitSize(app.name, 16) + CharacterAppearanceData.namePaddingRule(name_padding) val outfitStringSize : Long = StreamBitSize.stringBitSize(outfit_name, 16) + CharacterAppearanceData.outfitNamePadding val altModelSize = CharacterAppearanceData.altModelBit(this).getOrElse(0) - 335L + placementSize + nameStringSize + outfitStringSize + altModelSize + 335L + nameStringSize + outfitStringSize + altModelSize } + + + /** + * When a player is released-dead or attached to a zipline, their basic infantry model is replaced with a different one. + * @return the length of the variable field that exists when using alternate models + */ + def altModelBit : Option[Int] = CharacterAppearanceData.altModelBit(this) } object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] { /** * When a player is released-dead or attached to a zipline, their basic infantry model is replaced with a different one. - * In the former casde, a backpack. + * In the former case, a backpack. * In the latter case, a ball of colored energy. * In this state, the length of the stream of data is modified. * @param app the appearance @@ -143,14 +146,13 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] { * The padding will always be a number 0-7. * @return the pad length in bits */ - def namePadding(move : Option[_]) : Int = { - if(move.isDefined) { - 2 + def namePaddingRule(pad : Int) : Int = + if(pad == 0) { + 1 //normal alignment padding for the string } else { - 4 + pad //custom padding value } - } /** * Get the padding of the outfit's name. @@ -161,77 +163,78 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] { 6 } - implicit val codec : Codec[CharacterAppearanceData] = ( - ("pos" | PlacementData.codec) >>:~ { pos => - ("faction" | PlanetSideEmpire.codec) :: - ("black_ops" | bool) :: - (("alt_model" | bool) >>:~ { alt_model => //modifies stream format (to display alternate player models) + def codec(name_padding : Int) : Codec[CharacterAppearanceData] = ( + ("faction" | PlanetSideEmpire.codec) :: + ("black_ops" | bool) :: + (("alt_model" | bool) >>:~ { alt_model => //modifies stream format (to display alternate player models) + ignore(1) :: //unknown + ("jammered" | bool) :: + bool :: //crashes client + uint(16) :: //unknown, but usually 0 + ("name" | PacketHelpers.encodedWideStringAligned( namePaddingRule(name_padding) )) :: + ("exosuit" | ExoSuitType.codec) :: + ignore(2) :: //unknown + ("sex" | CharacterGender.codec) :: + ("head" | uint8L) :: + ("voice" | uint(3)) :: + ("voice2" | uint2L) :: + ignore(78) :: //unknown + uint16L :: //usually either 0 or 65535 + uint32L :: //for outfit_name (below) to be visible in-game, this value should be non-zero + ("outfit_name" | PacketHelpers.encodedWideStringAligned( outfitNamePadding )) :: + ("outfit_logo" | uint8L) :: ignore(1) :: //unknown - ("jammered" | bool) :: - bool :: //crashes client - uint(16) :: //unknown, but usually 0 - ("name" | PacketHelpers.encodedWideStringAligned( namePadding(pos.vel) )) :: - ("exosuit" | ExoSuitType.codec) :: - ignore(2) :: //unknown - ("sex" | CharacterGender.codec) :: - ("head" | uint8L) :: - ("voice" | uint(3)) :: - ("voice2" | uint2L) :: - ignore(78) :: //unknown - uint16L :: //usually either 0 or 65535 - uint32L :: //for outfit_name (below) to be visible in-game, this value should be non-zero - ("outfit_name" | PacketHelpers.encodedWideStringAligned( outfitNamePadding )) :: - ("outfit_logo" | uint8L) :: - ignore(1) :: //unknown - ("backpack" | bool) :: //requires alt_model flag (does NOT require health == 0) - bool :: //stream misalignment when set - ("facingPitch" | Angular.codec_pitch) :: - ("facingYawUpper" | Angular.codec_yaw(0f)) :: - ignore(1) :: //unknown - conditional(alt_model, bool) :: //alt_model flag adds a bit before lfs - ignore(1) :: //an alternate lfs? - ("lfs" | bool) :: - ("grenade_state" | GrenadeState.codec_2u) :: //note: bin10 and bin11 are neutral (bin00 is not defined) - ("is_cloaking" | bool) :: - ignore(1) :: //unknown - bool :: //stream misalignment when set - ("charging_pose" | bool) :: - ignore(1) :: //alternate charging pose? - ("on_zipline" | bool) :: //requires alt_model flag - ("ribbons" | RibbonBars.codec) - }) - }).exmap[CharacterAppearanceData] ( + ("backpack" | bool) :: //requires alt_model flag (does NOT require health == 0) + bool :: //stream misalignment when set + ("facingPitch" | Angular.codec_pitch) :: + ("facingYawUpper" | Angular.codec_yaw(0f)) :: + ignore(1) :: //unknown + conditional(alt_model, bool) :: //alt_model flag adds a bit before lfs + ignore(1) :: //an alternate lfs? + ("lfs" | bool) :: + ("grenade_state" | GrenadeState.codec_2u) :: //note: bin10 and bin11 are neutral (bin00 is not defined) + ("is_cloaking" | bool) :: + ignore(1) :: //unknown + bool :: //stream misalignment when set + ("charging_pose" | bool) :: + ignore(1) :: //alternate charging pose? + ("on_zipline" | bool) :: //requires alt_model flag + ("ribbons" | RibbonBars.codec) + }) + ).exmap[CharacterAppearanceData] ( { - case _ :: _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil | - _ :: _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: HNil => + case _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil | + _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: HNil => Attempt.Failure(Err("invalid character appearance data; can not encode alternate model without required bit set")) - case pos :: faction :: bops :: _ :: _ :: jamd :: false :: 0 :: name :: suit :: _ :: sex :: head :: v1 :: v2 :: _ :: _ :: _/*has_outfit_name*/ :: outfit :: logo :: _ :: bpack :: false :: facingPitch :: facingYawUpper :: _ :: _ :: _ :: lfs :: gstate :: cloaking :: _ :: false :: charging :: _ :: zipline :: ribbons :: HNil => + case faction :: bops :: _ :: _ :: jamd :: false :: 0 :: name :: suit :: _ :: sex :: head :: v1 :: v2 :: _ :: _ :: _/*has_outfit_name*/ :: outfit :: logo :: _ :: bpack :: false :: facingPitch :: facingYawUpper :: _ :: _ :: _ :: lfs :: gstate :: cloaking :: _ :: false :: charging :: _ :: zipline :: ribbons :: HNil => Attempt.successful( - CharacterAppearanceData(pos, BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons) + CharacterAppearanceData(BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons)(name_padding) ) case _ => Attempt.Failure(Err("invalid character appearance data; can not encode")) }, { - case CharacterAppearanceData(_, BasicCharacterData(name, PlanetSideEmpire.NEUTRAL, _, _, _), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => + case CharacterAppearanceData(BasicCharacterData(name, PlanetSideEmpire.NEUTRAL, _, _, _), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Attempt.failure(Err(s"character $name's faction can not declare as neutral")) - case CharacterAppearanceData(pos, BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons) => + case CharacterAppearanceData(BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons) => val has_outfit_name : Long = outfit.length.toLong //TODO this is a kludge - var alt_model : Boolean = false + var alt_model : Boolean = false var alt_model_extrabit : Option[Boolean] = None if(zipline || bpack) { alt_model = true alt_model_extrabit = Some(false) } Attempt.successful( - pos :: faction :: bops :: alt_model :: () :: jamd :: false :: 0 :: name :: suit :: () :: sex :: head :: v1 :: v2 :: () :: 0 :: has_outfit_name :: outfit :: logo :: () :: bpack :: false :: facingPitch :: facingYawUpper :: () :: alt_model_extrabit :: () :: lfs :: gstate :: cloaking :: () :: false :: charging :: () :: zipline :: ribbons :: HNil + faction :: bops :: alt_model :: () :: jamd :: false :: 0 :: name :: suit :: () :: sex :: head :: v1 :: v2 :: () :: 0 :: has_outfit_name :: outfit :: logo :: () :: bpack :: false :: facingPitch :: facingYawUpper :: () :: alt_model_extrabit :: () :: lfs :: gstate :: cloaking :: () :: false :: charging :: () :: zipline :: ribbons :: HNil ) case _ => Attempt.Failure(Err("invalid character appearance data; can not decode")) } ) + + implicit val codec : Codec[CharacterAppearanceData] = codec(0) } 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 51d08d63c..9ce35e9b8 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 @@ -42,27 +42,17 @@ object UniformStyle extends Enumeration { } /** - * A part of a representation of the avatar portion of `ObjectCreateMessage` packet data. - * This densely-packed information outlines most of the specifics of depicting some other character.
+ * A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.
*
- * The character created by this data is treated like an NPC from the perspective of the server. + * This densely-packed information outlines most of the specifics required to depict some other player's character. * Someone else decides how that character is behaving and the server tells each client how to depict that behavior. * For that reason, the character is mostly for presentation purposes, rather than really being fleshed-out. - * (As far as the client is concerned, nothing stops this character from being declared an "avatar." - * A player would find such a client-controlled character lacking many important details and have poor equipment. - * They would also be competing with some other player for input control, if they could control the character at all.)
+ * Of the inventory for this character, only the initial five weapon slots are defined.
*
- * Divisions exist to make the data more manageable. - * The first division of data only manages the general appearance of the player's in-game model. - * The second division (currently, the fields actually in this class) manages the status of the character. - * In general, it passes more simplified data about the character, the minimum that is necessary to explain status to some other player. - * For example, health and armor are percentages, and are depicted as bars over the player's head near the nameplate. - * The third is the inventory (composed of normal-type objects). - * Rather than equipment other players would never interact with, it only comprises the contents of the five holster slots.
- *
- * If this player is spawned as dead - with their `health` at 0% - he will start standing and then immediately fall into a lying pose. - * The death pose selected is randomized, can not be influenced, and is not be shared across clients. - * @param appearance the player's cardinal appearance settings + * In the "backend of the client," the character produced by this data is no different + * from the kind of character that could be declared a given player's avatar. + * In terms of equipment and complicated features common to an avatar character, however, + * any user would find this character ill-equipped. * @param health the amount of health the player has, as a percentage of a filled bar; * the bar has 85 states, with 3 points for each state; * when 0% (less than 3 of 255), the player will collapse into a death pose on the ground @@ -76,89 +66,69 @@ object UniformStyle extends Enumeration { * @param cosmetics optional decorative features that are added to the player's head model by console/chat commands; * they become available at battle rank 24, but here they require the third uniform upgrade (rank 25); * these flags do not exist if they are not applicable - * @param inventory the avatar's inventory; - * typically, only the tools and weapons in the equipment holster slots - * @param drawn_slot the holster that is initially drawn; - * defaults to `DrawnSlot.None` - * @see `CharacterAppearanceData` * @see `DetailedCharacterData` - * @see `InventoryData` - * @see `DrawnSlot` */ -final case class CharacterData(appearance : CharacterAppearanceData, - health : Int, +final case class CharacterData(health : Int, armor : Int, uniform_upgrade : UniformStyle.Value, + unk : Int, command_rank : Int, implant_effects : Option[ImplantEffects.Value], - cosmetics : Option[Cosmetics], - inventory : Option[InventoryData], - drawn_slot : DrawnSlot.Value = DrawnSlot.None - ) extends ConstructorData { + cosmetics : Option[Cosmetics]) + (is_backpack : Boolean) extends ConstructorData { override def bitsize : Long = { //factor guard bool values into the base size, not its corresponding optional field - val appearanceSize : Long = appearance.bitsize val effectsSize : Long = if(implant_effects.isDefined) { 4L } else { 0L } val cosmeticsSize : Long = if(cosmetics.isDefined) { cosmetics.get.bitsize } else { 0L } - val inventorySize : Long = if(inventory.isDefined) { inventory.get.bitsize } else { 0L } - 32L + appearanceSize + effectsSize + cosmeticsSize + inventorySize + 27L + effectsSize + cosmeticsSize } } object CharacterData extends Marshallable[CharacterData] { /** * An overloaded constructor for `CharacterData` that allows for a not-optional inventory. - * @param appearance the player's cardinal appearance settings * @param health the amount of health the player has, as a percentage of a filled bar * @param armor the amount of armor the player has, as a percentage of a filled bar * @param uniform the level of upgrade to apply to the player's base uniform * @param cr the player's command rank as a number from 0 to 5 * @param implant_effects the effects of implants that can be seen on a player's character * @param cosmetics optional decorative features that are added to the player's head model by console/chat commands - * @param inv the avatar's inventory - * @param drawn_slot the holster that is initially drawn + * //@param inv the avatar's inventory + * //@param drawn_slot the holster that is initially drawn * @return a `CharacterData` object */ - def apply(appearance : CharacterAppearanceData, health : Int, armor : Int, uniform : UniformStyle.Value, cr : Int, implant_effects : Option[ImplantEffects.Value], cosmetics : Option[Cosmetics], inv : InventoryData, drawn_slot : DrawnSlot.Value) : CharacterData = - new CharacterData(appearance, health, armor, uniform, cr, implant_effects, cosmetics, Some(inv), drawn_slot) + def apply(health : Int, armor : Int, uniform : UniformStyle.Value, cr : Int, implant_effects : Option[ImplantEffects.Value], cosmetics : Option[Cosmetics]) : (Boolean)=>CharacterData = + CharacterData(health, armor, uniform, 0, cr, implant_effects, cosmetics) - implicit val codec : Codec[CharacterData] = ( - ("app" | CharacterAppearanceData.codec) :: - ("health" | uint8L) :: //dead state when health == 0 + def codec(is_backpack : Boolean) : Codec[CharacterData] = ( + ("health" | uint8L) :: //dead state when health == 0 ("armor" | uint8L) :: (("uniform_upgrade" | UniformStyle.codec) >>:~ { style => ignore(3) :: //unknown ("command_rank" | uintL(3)) :: bool :: //stream misalignment when != 1 optional(bool, "implant_effects" | ImplantEffects.codec) :: - conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec) :: - optional(bool, "inventory" | InventoryData.codec) :: - ("drawn_slot" | DrawnSlot.codec) :: - bool //usually false + conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec) }) - ).exmap[CharacterData] ( + ).exmap[CharacterData] ( { - case app :: health :: armor :: uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: inv :: drawn_slot :: false :: HNil => - var newHealth = health - if(app.backpack) { - newHealth = 0 - } - Attempt.Successful(CharacterData(app, newHealth, armor, uniform, cr, implant_effects, cosmetics, inv, drawn_slot)) + case health :: armor :: uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: HNil => + val newHealth = if(is_backpack) { 0 } else { health } + Attempt.Successful(new CharacterData(newHealth, armor, uniform, 0, cr, implant_effects, cosmetics)(is_backpack)) case _ => Attempt.Failure(Err("invalid character data; can not encode")) }, { - case CharacterData(app, health, armor, uniform, cr, implant_effects, cosmetics, inv, drawn_slot) => - var newHealth = health - if(app.backpack) { - newHealth = 0 - } - Attempt.Successful(app :: newHealth :: armor :: uniform :: () :: cr :: false :: implant_effects :: cosmetics :: inv :: drawn_slot :: false :: HNil) + case CharacterData(health, armor, uniform, _, cr, implant_effects, cosmetics) => + val newHealth = if(is_backpack) { 0 } else { health } + Attempt.Successful(newHealth :: armor :: uniform :: () :: cr :: false :: implant_effects :: cosmetics :: HNil) case _ => Attempt.Failure(Err("invalid character data; can not decode")) } ) + + implicit val codec : Codec[CharacterData] = codec(false) } 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 c2fa2d747..fa7f7f933 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 @@ -27,22 +27,14 @@ final case class ImplantEntry(implant : ImplantType.Value, } /** - * A representation of the avatar portion of `ObjectCreateDetailedMessage` packet data. - * This densely-packed information outlines most of the specifics required to depict a character as an avatar.
+ * A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.
*
- * As an avatar, the character created by this data is expected to be controllable by the client that gets sent this data. - * It goes into depth about information related to the given character in-game career that is not revealed to other players.
- *
- * Divisions exist to make the data more manageable. - * The first division of data only manages the general appearance of the player's in-game model. - * It is shared between `DetailedCharacterData` avatars and `CharacterData` player characters. - * The second division (of fields) manages the status of the character as an avatar. - * In general, it passes more thorough data about the character that the client can display to the owner of the client. + * This densely-packed information outlines most of the specifics required to depict a character as an avatar. + * It goes into depth about information related to the given character in-game career that is not revealed to other players. + * To be specific, it passes more thorough data about the character that the client can display to the owner of the client. * For example, health is a full number, rather than a percentage. * Just as prominent is the list of first time events and the list of completed tutorials. - * The third subdivision is also exclusive to avatar-prepared characters and contains (omitted). - * The fourth is the inventory (composed of `Direct`-type objects). - * @param appearance data about the avatar's basic aesthetics + * Additionally, a full inventory, as opposed to the initial five weapon slots. * @param bep the avatar's battle experience points, which determines his Battle Rank * @param cep the avatar's command experience points, which determines his Command Rank * @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value; @@ -73,16 +65,10 @@ final case class ImplantEntry(implant : ImplantType.Value, * @param cosmetics optional decorative features that are added to the player's head model by console/chat commands; * they become available at battle rank 24; * these flags do not exist if they are not applicable - * @param inventory the avatar's inventory - * @param drawn_slot the holster that is initially drawn - * @see `CharacterAppearanceData`
- * `CharacterData`
- * `CertificationType`
- * `InventoryData`
- * `DrawnSlot` + * @see `CharacterData`
+ * `CertificationType` */ -final case class DetailedCharacterData(appearance : CharacterAppearanceData, - bep : Long, +final case class DetailedCharacterData(bep : Long, cep : Long, healthMax : Int, health : Int, @@ -96,20 +82,17 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData, implants : List[ImplantEntry], firstTimeEvents : List[String], tutorials : List[String], - cosmetics : Option[Cosmetics], - inventory : Option[InventoryData], - drawn_slot : DrawnSlot.Value = DrawnSlot.None - ) extends ConstructorData { + cosmetics : Option[Cosmetics]) + (pad_length : Option[Int]) extends ConstructorData { override def bitsize : Long = { //factor guard bool values into the base size, not corresponding optional fields, unless contained or enumerated - val appearanceSize = appearance.bitsize val certSize = (certs.length + 1) * 8 //cert list var implantSize : Long = 0L //implant list for(entry <- implants) { implantSize += entry.bitsize } - val implantPadding = DetailedCharacterData.implantFieldPadding(implants, CharacterAppearanceData.altModelBit(appearance)) + val implantPadding = DetailedCharacterData.implantFieldPadding(implants, pad_length) val fteLen = firstTimeEvents.size //fte list var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen, implantPadding) for(str <- firstTimeEvents) { @@ -123,20 +106,13 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData, val br24 = DetailedCharacterData.isBR24(bep) //character is at least BR24 val extraBitSize : Long = if(br24) { 33L } else { 46L } val cosmeticsSize : Long = if(br24) { cosmetics.get.bitsize } else { 0L } - val inventorySize : Long = if(inventory.isDefined) { //inventory - inventory.get.bitsize - } - else { - 0L - } - 603L + appearanceSize + certSize + implantSize + eventListSize + extraBitSize + cosmeticsSize + tutorialListSize + inventorySize + 598L + certSize + implantSize + eventListSize + extraBitSize + cosmeticsSize + tutorialListSize } } object DetailedCharacterData extends Marshallable[DetailedCharacterData] { /** * Overloaded constructor for `DetailedCharacterData` that requires an inventory and drops unknown values. - * @param appearance data about the avatar's basic aesthetics * @param bep the avatar's battle experience points, which determines his Battle Rank * @param cep the avatar's command experience points, which determines his Command Rank * @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value @@ -148,12 +124,10 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { * @param implants the `List` of implant slots currently possessed by this avatar * @param firstTimeEvents the list of first time events performed by this avatar * @param tutorials the list of tutorials completed by this avatar - * @param inventory the avatar's inventory - * @param drawn_slot the holster that is initially drawn * @return a `DetailedCharacterData` object */ - def apply(appearance : CharacterAppearanceData, bep : Long, cep : Long, healthMax : Int, health : Int, armor : Int, staminaMax : Int, stamina : Int, certs : List[CertificationType.Value], implants : List[ImplantEntry], firstTimeEvents : List[String], tutorials : List[String], cosmetics : Option[Cosmetics], inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedCharacterData = - new DetailedCharacterData(appearance, bep, cep, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, certs, implants, firstTimeEvents, tutorials, cosmetics, Some(inventory), drawn_slot) + def apply(bep : Long, cep : Long, healthMax : Int, health : Int, armor : Int, staminaMax : Int, stamina : Int, certs : List[CertificationType.Value], implants : List[ImplantEntry], firstTimeEvents : List[String], tutorials : List[String], cosmetics : Option[Cosmetics]) : (Option[Int])=>DetailedCharacterData = + DetailedCharacterData(bep, cep, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, certs, implants, firstTimeEvents, tutorials, cosmetics) /** * `Codec` for entries in the `List` of implants. @@ -278,57 +252,52 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { def isBR24(bep : Long) : Boolean = bep > 2286230 - implicit val codec : Codec[DetailedCharacterData] = ( - ("appearance" | CharacterAppearanceData.codec) >>:~ { app => - ("bep" | uint32L) >>:~ { bep => - ("cep" | uint32L) :: - ignore(96) :: - ("healthMax" | uint16L) :: - ("health" | uint16L) :: - ignore(1) :: - ("armor" | uint16L) :: - ignore(9) :: - ("unk1" | uint8L) :: - ignore(8) :: - ("unk2" | uint4L) :: - ("unk3" | uintL(3)) :: - ("staminaMax" | uint16L) :: - ("stamina" | uint16L) :: - ignore(147) :: - ("certs" | listOfN(uint8L, CertificationType.codec)) :: - optional(bool, uint32L) :: //ask about sample CCRIDER - ignore(4) :: - (("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants => - ignore(12) :: - (("firstTimeEvent_length" | uint32L) >>:~ { len => - conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned(ftePadding(len, implantFieldPadding(implants, CharacterAppearanceData.altModelBit(app))))) :: - ("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) :: - (("tutorial_length" | uint32L) >>:~ { len2 => - conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned(tutPadding(len, len2, implantFieldPadding(implants, CharacterAppearanceData.altModelBit(app))))) :: - ("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) :: - ignore(160) :: - (bool >>:~ { br24 => //BR24+ - newcodecs.binary_choice(br24, ignore(33), ignore(46)) :: - conditional(br24, Cosmetics.codec) :: - optional(bool, "inventory" | InventoryData.codec_detailed) :: - ("drawn_slot" | DrawnSlot.codec) :: - bool //usually false - }) - }) - }) - }) - } + def codec(pad_length : Option[Int]) : Codec[DetailedCharacterData] = ( + ("bep" | uint32L) >>:~ { bep => + ("cep" | uint32L) :: + ignore(96) :: + ("healthMax" | uint16L) :: + ("health" | uint16L) :: + ignore(1) :: + ("armor" | uint16L) :: + ignore(9) :: + ("unk1" | uint8L) :: + ignore(8) :: + ("unk2" | uint4L) :: + ("unk3" | uintL(3)) :: + ("staminaMax" | uint16L) :: + ("stamina" | uint16L) :: + ignore(147) :: + ("certs" | listOfN(uint8L, CertificationType.codec)) :: + optional(bool, uint32L) :: //ask about sample CCRIDER + ignore(4) :: + (("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants => + ignore(12) :: + (("firstTimeEvent_length" | uint32L) >>:~ { len => + conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned(ftePadding(len, implantFieldPadding(implants, pad_length)))) :: + ("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) :: + (("tutorial_length" | uint32L) >>:~ { len2 => + conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned(tutPadding(len, len2, implantFieldPadding(implants, pad_length)))) :: + ("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) :: + ignore(160) :: + (bool >>:~ { br24 => //BR24+ + newcodecs.binary_choice(br24, ignore(33), ignore(46)) :: + conditional(br24, Cosmetics.codec) + }) + }) + }) + }) } ).exmap[DetailedCharacterData] ( { - case app :: bep :: cep :: _ :: hpmax :: hp :: _ :: armor :: _ :: u1 :: _ :: u2 :: u3 :: stamax :: stam :: _ :: certs :: _ :: _ :: implants :: _ :: _ :: fte0 :: fte1 :: _ :: tut0 :: tut1 :: _ :: _ :: _ :: cosmetics :: inv :: drawn :: false :: HNil => + case bep :: cep :: _ :: hpmax :: hp :: _ :: armor :: _ :: u1 :: _ :: u2 :: u3 :: stamax :: stam :: _ :: certs :: _ :: _ :: implants :: _ :: _ :: fte0 :: fte1 :: _ :: tut0 :: tut1 :: _ :: _ :: _ :: cosmetics :: HNil => //prepend the displaced first elements to their lists val fteList : List[String] = if(fte0.isDefined) { fte0.get +: fte1 } else { fte1 } val tutList : List[String] = if(tut0.isDefined) { tut0.get +: tut1 } else { tut1 } - Attempt.successful(DetailedCharacterData(app, bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cosmetics, inv, drawn)) + Attempt.successful(DetailedCharacterData(bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cosmetics)(pad_length)) }, { - case DetailedCharacterData(app, bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cos, inv, drawn) => + case DetailedCharacterData(bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cos) => val implantCapacity : Int = numberOfImplantSlots(bep) val implantList = if(implants.length > implantCapacity) { implants.slice(0, implantCapacity) @@ -349,7 +318,9 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { } val br24 : Boolean = isBR24(bep) val cosmetics : Option[Cosmetics] = if(br24) { cos } else { None } - Attempt.successful(app :: bep :: cep :: () :: hpmax :: hp :: () :: armor :: () :: u1 :: () :: u2 :: u3 :: stamax :: stam :: () :: certs :: None :: () :: implantList :: () :: fteList.size.toLong :: firstEvent :: fteListCopy :: tutList.size.toLong :: firstTutorial :: tutListCopy :: () :: br24 :: () :: cosmetics :: inv :: drawn :: false :: HNil) + Attempt.successful(bep :: cep :: () :: hpmax :: hp :: () :: armor :: () :: u1 :: () :: u2 :: u3 :: stamax :: stam :: () :: certs :: None :: () :: implantList :: () :: fteList.size.toLong :: firstEvent :: fteListCopy :: tutList.size.toLong :: firstTutorial :: tutListCopy :: () :: br24 :: () :: cosmetics :: HNil) } ) + + implicit val codec : Codec[DetailedCharacterData] = codec(None) } 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 new file mode 100644 index 000000000..6fcff934f --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedPlayerData.scala @@ -0,0 +1,103 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game.objectcreate + +import net.psforever.packet.Marshallable +import scodec.codecs._ +import scodec.Codec +import shapeless.{::, HNil} + +/** + * A representation of an `avatar` player for the `ObjectCreateDetailedMessage` packet. + * As an avatar, the character created by this data is expected to be controllable by the client that gets sent this data.
+ *
+ * Divisions exist to make the data more manageable. + * The first division defines the player's location within the game coordinate system. + * The second division defines features of the `avatar` + * that are shared by both the `ObjectCreateDetailedMessage` version of a controlled player character (this) + * and the `ObjectCreateMessage` version of a player character. + * The third field expands on the nature of the character and this avatar's campaign. + * Expansive information about previous interactions, the contents of their inventory, and equipment permissions are included.
+ *
+ * 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` + * @param pos the optional position of the character in the world environment + * @param basic_appearance common fields regarding the the character's appearance + * @param character_data the class-specific data that explains about the character + * @param position_defined used by the `Codec` to seed the state of the optional `pos` field + * @param inventory the player's full inventory + * @param drawn_slot the holster that is initially drawn + */ +final case class DetailedPlayerData(pos : Option[PlacementData], + basic_appearance : CharacterAppearanceData, + character_data : DetailedCharacterData, + inventory : Option[InventoryData], + drawn_slot : DrawnSlot.Value) + (position_defined : Boolean) extends ConstructorData { + override def bitsize : Long = { + val posSize : Long = if(pos.isDefined) { pos.get.bitsize } else { 0 } + val appSize : Long = basic_appearance.bitsize + val charSize = character_data.bitsize + val inventorySize : Long = if(inventory.isDefined) { inventory.get.bitsize } else { 0L } + 5L + posSize + appSize + charSize + inventorySize + } +} + +object DetailedPlayerData extends Marshallable[DetailedPlayerData] { + /** + * Overloaded constructor that ignores the coordinate information. + * It passes information between the three major divisions for the purposes of offset calculations. + * @param basic_appearance a curried function for the common fields regarding the the character's appearance + * @param character_data a curried function for the class-specific data that explains about the character + * @return a `DetailedPlayerData` object + */ + def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = { + val appearance = basic_appearance(0) + DetailedPlayerData(None, appearance, character_data(appearance.altModelBit), Some(inventory), drawn_slot)(false) + } + /** */ + def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = { + val appearance = basic_appearance(0) + DetailedPlayerData(None, appearance, character_data(appearance.altModelBit), None, drawn_slot)(false) + } + /** + * Overloaded constructor that includes the coordinate information. + * It passes information between the three major divisions for the purposes of offset calculations. + * @param basic_appearance a curried function for the common fields regarding the the character's appearance + * @param character_data a curried function for the class-specific data that explains about the character + * @return a `DetailedPlayerData` object + */ + def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = { + val appearance = basic_appearance(PlayerData.placementOffset(Some(pos))) + DetailedPlayerData(Some(pos), appearance, character_data(appearance.altModelBit), Some(inventory), drawn_slot)(true) + } + /** */ + def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = { + val appearance = basic_appearance(PlayerData.placementOffset(Some(pos))) + DetailedPlayerData(Some(pos), appearance, character_data(appearance.altModelBit), None, drawn_slot)(true) + } + + def codec(position_defined : Boolean) : Codec[DetailedPlayerData] = ( + conditional(position_defined, "pos" | PlacementData.codec) >>:~ { pos => + ("basic_appearance" | CharacterAppearanceData.codec(PlayerData.placementOffset(pos))) >>:~ { app => + ("character_data" | DetailedCharacterData.codec(app.altModelBit)) :: + optional(bool, "inventory" | InventoryData.codec_detailed) :: + ("drawn_slot" | DrawnSlot.codec) :: + bool //usually false + } + }).xmap[DetailedPlayerData] ( + { + case pos :: app :: data :: inv :: hand :: _ :: HNil => + DetailedPlayerData(pos, app, data, inv, hand)(pos.isDefined) + }, + { + case DetailedPlayerData(pos, app, data, inv, hand) => + pos :: app :: data :: inv :: hand :: false :: HNil + } + ) + + implicit val codec : Codec[DetailedPlayerData] = codec(false) +} diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala index 40a95b499..001351011 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala @@ -666,7 +666,7 @@ object ObjectClass { case ObjectClass.advanced_ace => ConstructorData.genericCodec(DetailedACEData.codec, "advanced ace") case ObjectClass.boomer_trigger => ConstructorData.genericCodec(DetailedBoomerTriggerData.codec, "boomer trigger") //other - case ObjectClass.avatar => ConstructorData.genericCodec(DetailedCharacterData.codec, "avatar") + case ObjectClass.avatar => ConstructorData.genericCodec(DetailedPlayerData.codec(true), "avatar") case ObjectClass.locker_container => ConstructorData.genericCodec(DetailedLockerContainerData.codec, "locker container") //failure case @@ -953,6 +953,7 @@ object ObjectClass { //other case ObjectClass.ams_order_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") case ObjectClass.ams_respawn_tube => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") + case ObjectClass.avatar => ConstructorData.genericCodec(PlayerData.codec(false), "avatar") case ObjectClass.bfr_rearm_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") case ObjectClass.implant_terminal_interface => ConstructorData.genericCodec(CommonTerminalData.codec, "implant terminal") case ObjectClass.lodestar_repair_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") @@ -1268,7 +1269,7 @@ object ObjectClass { case ObjectClass.wasp => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle") //other case ObjectClass.ams_respawn_tube => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal") - case ObjectClass.avatar => ConstructorData.genericCodec(CharacterData.codec, "avatar") + case ObjectClass.avatar => ConstructorData.genericCodec(PlayerData.codec(true), "avatar") case ObjectClass.capture_flag => ConstructorData.genericCodec(CaptureFlagData.codec, "capture flag") case ObjectClass.implant_terminal_interface => ConstructorData.genericCodec(CommonTerminalData.codec, "implant terminal") case ObjectClass.locker_container => ConstructorData.genericCodec(LockerContainerData.codec, "locker container") 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 new file mode 100644 index 000000000..f808af508 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/PlayerData.scala @@ -0,0 +1,128 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game.objectcreate + +import net.psforever.packet.Marshallable +import scodec.codecs._ +import scodec.Codec +import shapeless.{::, HNil} + +/** + * A representation of another player's character for the `ObjectCreateDetailedMessage` packet. + * In general, this packet is used to describe other players.
+ *
+ * Divisions exist to make the data more manageable. + * The first division defines the player's location within the game coordinate system. + * The second division defines features of the character + * that are shared by both the `ObjectCreateDetailedMessage` version of a controlled player character + * and the `ObjectCreateMessage` version of a player character (this). + * The third field provides further information on the appearance of the player character, albeit condensed. + * One of the most compact forms of a player character description is transcribed using this information.
+ *
+ * The presence or absence of position data as the first division creates a cascading effect + * causing all of fields in the other two divisions to gain offsets. + * These offsets exist in the form of `String` and `List` padding. + * @see `CharacterData`
+ * `InventoryData`
+ * `DrawnSlot` + * @param pos the optional position of the character in the world environment + * @param basic_appearance common fields regarding the the character's appearance + * @param character_data the class-specific data that explains about the character + * @param inventory the player's inventory; + * typically, only the tools and weapons in the equipment holster slots + * @param drawn_slot the holster that is initially drawn + * @param position_defined used by the `Codec` to seed the state of the optional `pos` field + */ +final case class PlayerData(pos : Option[PlacementData], + basic_appearance : CharacterAppearanceData, + character_data : CharacterData, + inventory : Option[InventoryData], + drawn_slot : DrawnSlot.Value) + (position_defined : Boolean) extends ConstructorData { + override def bitsize : Long = { + val posSize : Long = if(pos.isDefined) { pos.get.bitsize } else { 0 } + val appSize : Long = basic_appearance.bitsize + val charSize = character_data.bitsize + val inventorySize : Long = if(inventory.isDefined) { inventory.get.bitsize } else { 0L } + 5L + posSize + appSize + charSize + inventorySize + } +} + +object PlayerData extends Marshallable[PlayerData] { + /** + * Overloaded constructor that ignores the coordinate information. + * It passes information between the three major divisions for the purposes of offset calculations. + * @param basic_appearance a curried function for the common fields regarding the the character's appearance + * @param character_data a curried function for the class-specific data that explains about the character + * @param inventory the player's inventory + * @param drawn_slot the holster that is initially drawn + * @return a `PlayerData` object + */ + def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type) : PlayerData = { + val appearance = basic_appearance(0) + PlayerData(None, appearance, character_data(appearance.backpack), Some(inventory), drawn_slot)(false) + } + /** */ + def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean)=>CharacterData, hand_held : DrawnSlot.Type) : PlayerData = { + val appearance = basic_appearance(0) + PlayerData(None, appearance, character_data(appearance.backpack), None, hand_held)(false) + } + /** + * Overloaded constructor that includes the coordinate information. + * It passes information between the three major divisions for the purposes of offset calculations. + * @param pos the optional position of the character in the world environment + * @param basic_appearance a curried function for the common fields regarding the the character's appearance + * @param character_data a curried function for the class-specific data that explains about the character + * @param inventory the player's inventory + * @param drawn_slot the holster that is initially drawn + * @return a `PlayerData` object + */ + def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type) : PlayerData = { + val appearance = basic_appearance( placementOffset(Some(pos)) ) + PlayerData(Some(pos), appearance, character_data(appearance.backpack), Some(inventory), drawn_slot)(true) + } + /** */ + def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean)=>CharacterData, hand_held : DrawnSlot.Type) : PlayerData = { + val appearance = basic_appearance( placementOffset(Some(pos)) ) + PlayerData(Some(pos), appearance, character_data(appearance.backpack), None, hand_held)(true) + } + + /** + * Determine the padding offset for a subsequent field given the existence of `PlacementData`. + * The padding will always be a number 0-7. + * @see `PlacemtnData` + * @param pos the optional `PlacementData` object that creates the shift in bits + * @return the pad length in bits + */ + def placementOffset(pos : Option[PlacementData]) : Int = { + if(pos.isEmpty) { + 0 + } + else if(pos.get.vel.isDefined) { + 2 + } + else { + 4 + } + } + + def codec(position_defined : Boolean) : Codec[PlayerData] = ( + conditional(position_defined, "pos" | PlacementData.codec) >>:~ { pos => + ("basic_appearance" | CharacterAppearanceData.codec(placementOffset(pos))) >>:~ { app => + ("character_data" | CharacterData.codec(app.backpack)) :: + optional(bool, "inventory" | InventoryData.codec) :: + ("drawn_slot" | DrawnSlot.codec) :: + bool //usually false + } + }).xmap[PlayerData] ( + { + case pos :: app :: data :: inv :: hand :: _ :: HNil => + PlayerData(pos, app, data, inv, hand)(pos.isDefined) + }, + { + case PlayerData(pos, app, data, inv, hand) => + pos :: app :: data :: inv :: hand :: false :: HNil + } + ) + + implicit val codec : Codec[PlayerData] = codec(false) +} diff --git a/common/src/test/scala/game/objectcreate/CharacterDataTest.scala b/common/src/test/scala/game/objectcreate/CharacterDataTest.scala index 21d1cee08..843e61a2c 100644 --- a/common/src/test/scala/game/objectcreate/CharacterDataTest.scala +++ b/common/src/test/scala/game/objectcreate/CharacterDataTest.scala @@ -20,93 +20,92 @@ class CharacterDataTest extends Specification { cls mustEqual ObjectClass.avatar guid mustEqual PlanetSideGUID(3902) parent.isDefined mustEqual false - data.isDefined mustEqual true - data.get.isInstanceOf[CharacterData] mustEqual true - val pc = data.get.asInstanceOf[CharacterData] - pc.appearance.pos.coord.x mustEqual 3674.8438f - pc.appearance.pos.coord.y mustEqual 2726.789f - pc.appearance.pos.coord.z mustEqual 91.15625f - pc.appearance.pos.orient.x mustEqual 0f - pc.appearance.pos.orient.y mustEqual 0f - pc.appearance.pos.orient.z mustEqual 64.6875f - pc.appearance.pos.vel.isDefined mustEqual true - pc.appearance.pos.vel.get.x mustEqual 1.4375f - pc.appearance.pos.vel.get.y mustEqual -0.4375f - pc.appearance.pos.vel.get.z mustEqual 0f - pc.appearance.basic_appearance.name mustEqual "ScrawnyRonnie" - pc.appearance.basic_appearance.faction mustEqual PlanetSideEmpire.TR - pc.appearance.basic_appearance.sex mustEqual CharacterGender.Male - pc.appearance.basic_appearance.head mustEqual 5 - pc.appearance.basic_appearance.voice mustEqual 5 - pc.appearance.voice2 mustEqual 3 - pc.appearance.black_ops mustEqual false - pc.appearance.jammered mustEqual false - pc.appearance.exosuit mustEqual ExoSuitType.Reinforced - pc.appearance.outfit_name mustEqual "Black Beret Armoured Corps" - pc.appearance.outfit_logo mustEqual 23 - pc.appearance.facingPitch mustEqual 340.3125f - pc.appearance.facingYawUpper mustEqual 0 - pc.appearance.lfs mustEqual false - pc.appearance.grenade_state mustEqual GrenadeState.None - pc.appearance.is_cloaking mustEqual false - pc.appearance.charging_pose mustEqual false - pc.appearance.on_zipline mustEqual false - pc.appearance.ribbons.upper mustEqual MeritCommendation.MarkovVeteran - pc.appearance.ribbons.middle mustEqual MeritCommendation.HeavyInfantry4 - pc.appearance.ribbons.lower mustEqual MeritCommendation.TankBuster7 - pc.appearance.ribbons.tos mustEqual MeritCommendation.SixYearTR - pc.health mustEqual 255 - pc.armor mustEqual 253 - pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade - pc.command_rank mustEqual 5 - pc.implant_effects.isDefined mustEqual true - pc.implant_effects.get mustEqual ImplantEffects.NoEffects - pc.cosmetics.isDefined mustEqual true - pc.cosmetics.get.no_helmet mustEqual true - pc.cosmetics.get.beret mustEqual true - pc.cosmetics.get.sunglasses mustEqual true - pc.cosmetics.get.earpiece mustEqual true - pc.cosmetics.get.brimmed_cap mustEqual false - //short test of inventory items - pc.inventory.isDefined mustEqual true - val contents = pc.inventory.get.contents - contents.size mustEqual 5 - //0 - contents.head.objectClass mustEqual ObjectClass.plasma_grenade - contents.head.guid mustEqual PlanetSideGUID(3662) - contents.head.parentSlot mustEqual 0 - contents.head.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0 - contents.head.obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.plasma_grenade_ammo - contents.head.obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3751) - //1 - contents(1).objectClass mustEqual ObjectClass.bank - contents(1).guid mustEqual PlanetSideGUID(3908) - contents(1).parentSlot mustEqual 1 - contents(1).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1 - contents(1).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.armor_canister - contents(1).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(4143) - //2 - contents(2).objectClass mustEqual ObjectClass.mini_chaingun - contents(2).guid mustEqual PlanetSideGUID(4164) - contents(2).parentSlot mustEqual 2 - contents(2).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0 - contents(2).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.bullet_9mm - contents(2).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3728) - //3 - contents(3).objectClass mustEqual ObjectClass.phoenix //actually, a decimator - contents(3).guid mustEqual PlanetSideGUID(3603) - contents(3).parentSlot mustEqual 3 - contents(3).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0 - contents(3).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.phoenix_missile - contents(3).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3056) - //4 - contents(4).objectClass mustEqual ObjectClass.chainblade - contents(4).guid mustEqual PlanetSideGUID(4088) - contents(4).parentSlot mustEqual 4 - contents(4).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1 - contents(4).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.melee_ammo - contents(4).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3279) - pc.drawn_slot mustEqual DrawnSlot.Rifle1 + data match { + case Some(PlayerData(Some(pos), basic, char, inv, hand)) => + pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f) + pos.orient mustEqual Vector3(0f, 0f, 64.6875f) + pos.vel.isDefined mustEqual true + pos.vel.get mustEqual Vector3(1.4375f, -0.4375f, 0f) + + basic.app.name mustEqual "ScrawnyRonnie" + basic.app.faction mustEqual PlanetSideEmpire.TR + basic.app.sex mustEqual CharacterGender.Male + basic.app.head mustEqual 5 + basic.app.voice mustEqual 5 + basic.voice2 mustEqual 3 + basic.black_ops mustEqual false + basic.jammered mustEqual false + basic.exosuit mustEqual ExoSuitType.Reinforced + basic.outfit_name mustEqual "Black Beret Armoured Corps" + basic.outfit_logo mustEqual 23 + basic.facingPitch mustEqual 340.3125f + basic.facingYawUpper mustEqual 0 + basic.lfs mustEqual false + basic.grenade_state mustEqual GrenadeState.None + basic.is_cloaking mustEqual false + basic.charging_pose mustEqual false + basic.on_zipline mustEqual false + basic.ribbons.upper mustEqual MeritCommendation.MarkovVeteran + basic.ribbons.middle mustEqual MeritCommendation.HeavyInfantry4 + basic.ribbons.lower mustEqual MeritCommendation.TankBuster7 + basic.ribbons.tos mustEqual MeritCommendation.SixYearTR + + char.health mustEqual 255 + char.armor mustEqual 253 + char.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade + char.command_rank mustEqual 5 + char.implant_effects.isDefined mustEqual true + char.implant_effects.get mustEqual ImplantEffects.NoEffects + char.cosmetics.isDefined mustEqual true + char.cosmetics.get.no_helmet mustEqual true + char.cosmetics.get.beret mustEqual true + char.cosmetics.get.sunglasses mustEqual true + char.cosmetics.get.earpiece mustEqual true + char.cosmetics.get.brimmed_cap mustEqual false + //short test of inventory items + inv.isDefined mustEqual true + val contents = inv.get.contents + contents.size mustEqual 5 + //0 + contents.head.objectClass mustEqual ObjectClass.plasma_grenade + contents.head.guid mustEqual PlanetSideGUID(3662) + contents.head.parentSlot mustEqual 0 + contents.head.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0 + contents.head.obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.plasma_grenade_ammo + contents.head.obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3751) + //1 + contents(1).objectClass mustEqual ObjectClass.bank + contents(1).guid mustEqual PlanetSideGUID(3908) + contents(1).parentSlot mustEqual 1 + contents(1).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1 + contents(1).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.armor_canister + contents(1).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(4143) + //2 + contents(2).objectClass mustEqual ObjectClass.mini_chaingun + contents(2).guid mustEqual PlanetSideGUID(4164) + contents(2).parentSlot mustEqual 2 + contents(2).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0 + contents(2).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.bullet_9mm + contents(2).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3728) + //3 + contents(3).objectClass mustEqual ObjectClass.phoenix //actually, a decimator + contents(3).guid mustEqual PlanetSideGUID(3603) + contents(3).parentSlot mustEqual 3 + contents(3).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0 + contents(3).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.phoenix_missile + contents(3).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3056) + //4 + contents(4).objectClass mustEqual ObjectClass.chainblade + contents(4).guid mustEqual PlanetSideGUID(4088) + contents(4).parentSlot mustEqual 4 + contents(4).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1 + contents(4).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.melee_ammo + contents(4).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3279) + + hand mustEqual DrawnSlot.Rifle1 + case _ => + ko + } case _ => ko } @@ -119,104 +118,105 @@ class CharacterDataTest extends Specification { cls mustEqual ObjectClass.avatar guid mustEqual PlanetSideGUID(3380) parent.isDefined mustEqual false - data.isDefined mustEqual true - data.get.isInstanceOf[CharacterData] mustEqual true - val pc = data.get.asInstanceOf[CharacterData] - pc.appearance.pos.coord.x mustEqual 4629.8906f - pc.appearance.pos.coord.y mustEqual 6316.4453f - pc.appearance.pos.coord.z mustEqual 54.734375f - pc.appearance.pos.orient.x mustEqual 0f - pc.appearance.pos.orient.y mustEqual 0f - pc.appearance.pos.orient.z mustEqual 126.5625f - pc.appearance.pos.vel.isDefined mustEqual false - pc.appearance.basic_appearance.name mustEqual "Angello" - pc.appearance.basic_appearance.faction mustEqual PlanetSideEmpire.VS - pc.appearance.basic_appearance.sex mustEqual CharacterGender.Male - pc.appearance.basic_appearance.head mustEqual 10 - pc.appearance.basic_appearance.voice mustEqual 2 - pc.appearance.voice2 mustEqual 0 - pc.appearance.black_ops mustEqual false - pc.appearance.jammered mustEqual false - pc.appearance.exosuit mustEqual ExoSuitType.MAX - pc.appearance.outfit_name mustEqual "Original District" - pc.appearance.outfit_logo mustEqual 23 - pc.appearance.facingPitch mustEqual 0 - pc.appearance.facingYawUpper mustEqual 180.0f - pc.appearance.lfs mustEqual false - pc.appearance.grenade_state mustEqual GrenadeState.None - pc.appearance.is_cloaking mustEqual false - pc.appearance.charging_pose mustEqual false - pc.appearance.on_zipline mustEqual false - pc.appearance.ribbons.upper mustEqual MeritCommendation.Jacking2 - pc.appearance.ribbons.middle mustEqual MeritCommendation.ScavengerVS1 - pc.appearance.ribbons.lower mustEqual MeritCommendation.AMSSupport4 - pc.appearance.ribbons.tos mustEqual MeritCommendation.SixYearVS - pc.health mustEqual 0 - pc.armor mustEqual 0 - pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade - pc.command_rank mustEqual 2 - pc.implant_effects.isDefined mustEqual false - pc.cosmetics.isDefined mustEqual true - pc.cosmetics.get.no_helmet mustEqual true - pc.cosmetics.get.beret mustEqual true - pc.cosmetics.get.sunglasses mustEqual true - pc.cosmetics.get.earpiece mustEqual true - pc.cosmetics.get.brimmed_cap mustEqual false - pc.inventory.isDefined mustEqual false - pc.drawn_slot mustEqual DrawnSlot.Pistol1 + data match { + case Some(PlayerData(Some(pos), basic, char, None, hand)) => + pos.coord mustEqual Vector3(4629.8906f, 6316.4453f, 54.734375f) + pos.orient mustEqual Vector3(0, 0, 126.5625f) + pos.vel.isDefined mustEqual false + + basic.app.name mustEqual "Angello" + basic.app.faction mustEqual PlanetSideEmpire.VS + basic.app.sex mustEqual CharacterGender.Male + basic.app.head mustEqual 10 + basic.app.voice mustEqual 2 + basic.voice2 mustEqual 0 + basic.black_ops mustEqual false + basic.jammered mustEqual false + basic.exosuit mustEqual ExoSuitType.MAX + basic.outfit_name mustEqual "Original District" + basic.outfit_logo mustEqual 23 + basic.facingPitch mustEqual 0 + basic.facingYawUpper mustEqual 180.0f + basic.lfs mustEqual false + basic.grenade_state mustEqual GrenadeState.None + basic.is_cloaking mustEqual false + basic.charging_pose mustEqual false + basic.on_zipline mustEqual false + basic.ribbons.upper mustEqual MeritCommendation.Jacking2 + basic.ribbons.middle mustEqual MeritCommendation.ScavengerVS1 + basic.ribbons.lower mustEqual MeritCommendation.AMSSupport4 + basic.ribbons.tos mustEqual MeritCommendation.SixYearVS + + char.health mustEqual 0 + char.armor mustEqual 0 + char.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade + char.command_rank mustEqual 2 + char.implant_effects.isDefined mustEqual false + char.cosmetics.isDefined mustEqual true + char.cosmetics.get.no_helmet mustEqual true + char.cosmetics.get.beret mustEqual true + char.cosmetics.get.sunglasses mustEqual true + char.cosmetics.get.earpiece mustEqual true + char.cosmetics.get.brimmed_cap mustEqual false + + hand mustEqual DrawnSlot.Pistol1 + case _ => + ko + } case _ => ko } } "encode" in { - val obj = CharacterData( - CharacterAppearanceData( - PlacementData( - Vector3(3674.8438f, 2726.789f, 91.15625f), - Vector3(0f, 0f, 64.6875f), - Some(Vector3(1.4375f, -0.4375f, 0f)) - ), - BasicCharacterData( - "ScrawnyRonnie", - PlanetSideEmpire.TR, - CharacterGender.Male, - 5, - 5 - ), - 3, - false, - false, - ExoSuitType.Reinforced, - "Black Beret Armoured Corps", - 23, - false, - 340.3125f, 0f, - false, - GrenadeState.None, - false, false, false, - RibbonBars( - MeritCommendation.MarkovVeteran, - MeritCommendation.HeavyInfantry4, - MeritCommendation.TankBuster7, - MeritCommendation.SixYearTR - ) + val pos : PlacementData = PlacementData( + Vector3(3674.8438f, 2726.789f, 91.15625f), + Vector3(0f, 0f, 64.6875f), + Some(Vector3(1.4375f, -0.4375f, 0f)) + ) + val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData( + BasicCharacterData( + "ScrawnyRonnie", + PlanetSideEmpire.TR, + CharacterGender.Male, + 5, + 5 ), + 3, + false, + false, + ExoSuitType.Reinforced, + "Black Beret Armoured Corps", + 23, + false, + 340.3125f, 0f, + false, + GrenadeState.None, + false, false, false, + RibbonBars( + MeritCommendation.MarkovVeteran, + MeritCommendation.HeavyInfantry4, + MeritCommendation.TankBuster7, + MeritCommendation.SixYearTR + ) + ) + val char : (Boolean)=>CharacterData = CharacterData( 255, 253, UniformStyle.ThirdUpgrade, 5, Some(ImplantEffects.NoEffects), - Some(Cosmetics(true, true, true, true, false)), - InventoryData( - InventoryItemData(ObjectClass.plasma_grenade, PlanetSideGUID(3662), 0, WeaponData(0, 0, ObjectClass.plasma_grenade_ammo, PlanetSideGUID(3751), 0, AmmoBoxData())) :: - InventoryItemData(ObjectClass.bank, PlanetSideGUID(3908), 1, WeaponData(0, 0, 1, ObjectClass.armor_canister, PlanetSideGUID(4143), 0, AmmoBoxData())) :: - InventoryItemData(ObjectClass.mini_chaingun, PlanetSideGUID(4164), 2, WeaponData(0, 0, ObjectClass.bullet_9mm, PlanetSideGUID(3728), 0, AmmoBoxData())) :: - InventoryItemData(ObjectClass.phoenix, PlanetSideGUID(3603), 3, WeaponData(0, 0, ObjectClass.phoenix_missile, PlanetSideGUID(3056), 0, AmmoBoxData())) :: - InventoryItemData(ObjectClass.chainblade, PlanetSideGUID(4088), 4, WeaponData(0, 0, 1, ObjectClass.melee_ammo, PlanetSideGUID(3279), 0, AmmoBoxData())) :: - Nil - ), - DrawnSlot.Rifle1 + Some(Cosmetics(true, true, true, true, false)) ) + val inv = InventoryData( + InventoryItemData(ObjectClass.plasma_grenade, PlanetSideGUID(3662), 0, WeaponData(0, 0, ObjectClass.plasma_grenade_ammo, PlanetSideGUID(3751), 0, AmmoBoxData())) :: + InventoryItemData(ObjectClass.bank, PlanetSideGUID(3908), 1, WeaponData(0, 0, 1, ObjectClass.armor_canister, PlanetSideGUID(4143), 0, AmmoBoxData())) :: + InventoryItemData(ObjectClass.mini_chaingun, PlanetSideGUID(4164), 2, WeaponData(0, 0, ObjectClass.bullet_9mm, PlanetSideGUID(3728), 0, AmmoBoxData())) :: + InventoryItemData(ObjectClass.phoenix, PlanetSideGUID(3603), 3, WeaponData(0, 0, ObjectClass.phoenix_missile, PlanetSideGUID(3056), 0, AmmoBoxData())) :: + InventoryItemData(ObjectClass.chainblade, PlanetSideGUID(4088), 4, WeaponData(0, 0, 1, ObjectClass.melee_ammo, PlanetSideGUID(3279), 0, AmmoBoxData())) :: + Nil + ) + val obj = PlayerData.apply(pos, app, char, inv, DrawnSlot.Rifle1) + val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3902), obj) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt_bitv = pkt.toBitVector @@ -230,42 +230,45 @@ class CharacterDataTest extends Specification { } "encode (backpack)" in { - val obj = CharacterData( - CharacterAppearanceData( - PlacementData(4629.8906f, 6316.4453f, 54.734375f, 0f, 0f, 126.5625f), - BasicCharacterData( - "Angello", - PlanetSideEmpire.VS, - CharacterGender.Male, - 10, - 2 - ), - 0, - false, - false, - ExoSuitType.MAX, - "Original District", - 23, - true, //backpack - 0f, 180.0f, - false, - GrenadeState.None, - false, false, false, - RibbonBars( - MeritCommendation.Jacking2, - MeritCommendation.ScavengerVS1, - MeritCommendation.AMSSupport4, - MeritCommendation.SixYearVS - ) + val pos = PlacementData( + Vector3(4629.8906f, 6316.4453f, 54.734375f), + Vector3(0, 0, 126.5625f) + ) + val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData( + BasicCharacterData( + "Angello", + PlanetSideEmpire.VS, + CharacterGender.Male, + 10, + 2 ), + 0, + false, + false, + ExoSuitType.MAX, + "Original District", + 23, + true, //backpack + 0f, 180.0f, + false, + GrenadeState.None, + false, false, false, + RibbonBars( + MeritCommendation.Jacking2, + MeritCommendation.ScavengerVS1, + MeritCommendation.AMSSupport4, + MeritCommendation.SixYearVS + ) + ) + val char : (Boolean)=>CharacterData = CharacterData( 0, 0, UniformStyle.ThirdUpgrade, 2, None, - Some(Cosmetics(true, true, true, true, false)), - None, - DrawnSlot.Pistol1 + Some(Cosmetics(true, true, true, true, false)) ) + val obj = PlayerData.apply(pos, app, char, DrawnSlot.Pistol1) + val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3380), obj) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt_bitv = pkt.toBitVector diff --git a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala index 38a31b52d..b7fb703b4 100644 --- a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala +++ b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala @@ -20,130 +20,132 @@ class DetailedCharacterDataTest extends Specification { cls mustEqual ObjectClass.avatar guid mustEqual PlanetSideGUID(75) parent.isDefined mustEqual false - data.isDefined mustEqual true - val char = data.get.asInstanceOf[DetailedCharacterData] - char.appearance.pos.coord.x mustEqual 3674.8438f - char.appearance.pos.coord.y mustEqual 2726.789f - char.appearance.pos.coord.z mustEqual 91.15625f - char.appearance.pos.orient.x mustEqual 0 - char.appearance.pos.orient.y mustEqual 0f - char.appearance.pos.orient.z mustEqual 36.5625f - char.appearance.basic_appearance.name mustEqual "IlllIIIlllIlIllIlllIllI" - char.appearance.basic_appearance.faction mustEqual PlanetSideEmpire.VS - char.appearance.basic_appearance.sex mustEqual CharacterGender.Female - char.appearance.basic_appearance.head mustEqual 41 - char.appearance.basic_appearance.voice mustEqual 1 //female 1 - char.appearance.voice2 mustEqual 3 - char.appearance.black_ops mustEqual false - char.appearance.jammered mustEqual false - char.appearance.exosuit mustEqual ExoSuitType.Standard - char.appearance.outfit_name mustEqual "" - char.appearance.outfit_logo mustEqual 0 - char.appearance.backpack mustEqual false - char.appearance.facingPitch mustEqual 2.8125f - char.appearance.facingYawUpper mustEqual 210.9375f - char.appearance.lfs mustEqual true - char.appearance.grenade_state mustEqual GrenadeState.None - char.appearance.is_cloaking mustEqual false - char.appearance.charging_pose mustEqual false - char.appearance.on_zipline mustEqual false - char.appearance.ribbons.upper mustEqual MeritCommendation.None - char.appearance.ribbons.middle mustEqual MeritCommendation.None - char.appearance.ribbons.lower mustEqual MeritCommendation.None - char.appearance.ribbons.tos mustEqual MeritCommendation.None - char.bep mustEqual 0 - char.cep mustEqual 0 - char.healthMax mustEqual 100 - char.health mustEqual 100 - char.armor mustEqual 50 //standard exosuit value - char.unk1 mustEqual 1 - char.unk2 mustEqual 7 - char.unk3 mustEqual 7 - char.staminaMax mustEqual 100 - char.stamina mustEqual 100 - char.certs.length mustEqual 7 - char.certs.head mustEqual CertificationType.StandardAssault - char.certs(1) mustEqual CertificationType.MediumAssault - char.certs(2) mustEqual CertificationType.ATV - char.certs(3) mustEqual CertificationType.Harasser - char.certs(4) mustEqual CertificationType.StandardExoSuit - char.certs(5) mustEqual CertificationType.AgileExoSuit - char.certs(6) mustEqual CertificationType.ReinforcedExoSuit - char.implants.length mustEqual 0 - char.firstTimeEvents.size mustEqual 4 - char.firstTimeEvents.head mustEqual "xpe_sanctuary_help" - char.firstTimeEvents(1) mustEqual "xpe_th_firemodes" - char.firstTimeEvents(2) mustEqual "used_beamer" - char.firstTimeEvents(3) mustEqual "map13" - char.tutorials.size mustEqual 0 - char.cosmetics.isDefined mustEqual false - char.inventory.isDefined mustEqual true - val inventory = char.inventory.get.contents - inventory.size mustEqual 10 - //0 - inventory.head.objectClass mustEqual ObjectClass.beamer - inventory.head.guid mustEqual PlanetSideGUID(76) - inventory.head.parentSlot mustEqual 0 - var wep = inventory.head.obj.asInstanceOf[DetailedWeaponData] - wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell - wep.ammo.head.guid mustEqual PlanetSideGUID(77) - wep.ammo.head.parentSlot mustEqual 0 - wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 16 - //1 - inventory(1).objectClass mustEqual ObjectClass.suppressor - inventory(1).guid mustEqual PlanetSideGUID(78) - inventory(1).parentSlot mustEqual 2 - wep = inventory(1).obj.asInstanceOf[DetailedWeaponData] - wep.ammo.head.objectClass mustEqual ObjectClass.bullet_9mm - wep.ammo.head.guid mustEqual PlanetSideGUID(79) - wep.ammo.head.parentSlot mustEqual 0 - wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 25 - //2 - inventory(2).objectClass mustEqual ObjectClass.forceblade - inventory(2).guid mustEqual PlanetSideGUID(80) - inventory(2).parentSlot mustEqual 4 - wep = inventory(2).obj.asInstanceOf[DetailedWeaponData] - wep.ammo.head.objectClass mustEqual ObjectClass.melee_ammo - wep.ammo.head.guid mustEqual PlanetSideGUID(81) - wep.ammo.head.parentSlot mustEqual 0 - wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1 - //3 - inventory(3).objectClass mustEqual ObjectClass.locker_container - inventory(3).guid mustEqual PlanetSideGUID(82) - inventory(3).parentSlot mustEqual 5 - inventory(3).obj.isInstanceOf[DetailedLockerContainerData] mustEqual true - inventory(3).obj.asInstanceOf[DetailedLockerContainerData].inventory.isDefined mustEqual false - //4 - inventory(4).objectClass mustEqual ObjectClass.bullet_9mm - inventory(4).guid mustEqual PlanetSideGUID(83) - inventory(4).parentSlot mustEqual 6 - inventory(4).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 - //5 - inventory(5).objectClass mustEqual ObjectClass.bullet_9mm - inventory(5).guid mustEqual PlanetSideGUID(84) - inventory(5).parentSlot mustEqual 9 - inventory(5).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 - //6 - inventory(6).objectClass mustEqual ObjectClass.bullet_9mm - inventory(6).guid mustEqual PlanetSideGUID(85) - inventory(6).parentSlot mustEqual 12 - inventory(6).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 - //7 - inventory(7).objectClass mustEqual ObjectClass.bullet_9mm_AP - inventory(7).guid mustEqual PlanetSideGUID(86) - inventory(7).parentSlot mustEqual 33 - inventory(7).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 - //8 - inventory(8).objectClass mustEqual ObjectClass.energy_cell - inventory(8).guid mustEqual PlanetSideGUID(87) - inventory(8).parentSlot mustEqual 36 - inventory(8).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 - //9 - inventory(9).objectClass mustEqual ObjectClass.remote_electronics_kit - inventory(9).guid mustEqual PlanetSideGUID(88) - inventory(9).parentSlot mustEqual 39 - //the rek has data but none worth testing here - char.drawn_slot mustEqual DrawnSlot.Pistol1 + data match { + case Some(DetailedPlayerData(Some(pos), basic, char, inv, hand)) => + pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f) + pos.orient mustEqual Vector3(0, 0, 36.5625f) + pos.vel.isDefined mustEqual false + + basic.app.name mustEqual "IlllIIIlllIlIllIlllIllI" + basic.app.faction mustEqual PlanetSideEmpire.VS + basic.app.sex mustEqual CharacterGender.Female + basic.app.head mustEqual 41 + basic.app.voice mustEqual 1 //female 1 + basic.voice2 mustEqual 3 + basic.black_ops mustEqual false + basic.jammered mustEqual false + basic.exosuit mustEqual ExoSuitType.Standard + basic.outfit_name mustEqual "" + basic.outfit_logo mustEqual 0 + basic.backpack mustEqual false + basic.facingPitch mustEqual 2.8125f + basic.facingYawUpper mustEqual 210.9375f + basic.lfs mustEqual true + basic.grenade_state mustEqual GrenadeState.None + basic.is_cloaking mustEqual false + basic.charging_pose mustEqual false + basic.on_zipline mustEqual false + basic.ribbons.upper mustEqual MeritCommendation.None + basic.ribbons.middle mustEqual MeritCommendation.None + basic.ribbons.lower mustEqual MeritCommendation.None + basic.ribbons.tos mustEqual MeritCommendation.None + + char.bep mustEqual 0 + char.cep mustEqual 0 + char.healthMax mustEqual 100 + char.health mustEqual 100 + char.armor mustEqual 50 //standard exosuit value + char.unk1 mustEqual 1 + char.unk2 mustEqual 7 + char.unk3 mustEqual 7 + char.staminaMax mustEqual 100 + char.stamina mustEqual 100 + char.certs.length mustEqual 7 + char.certs.head mustEqual CertificationType.StandardAssault + char.certs(1) mustEqual CertificationType.MediumAssault + char.certs(2) mustEqual CertificationType.ATV + char.certs(3) mustEqual CertificationType.Harasser + char.certs(4) mustEqual CertificationType.StandardExoSuit + char.certs(5) mustEqual CertificationType.AgileExoSuit + char.certs(6) mustEqual CertificationType.ReinforcedExoSuit + char.implants.length mustEqual 0 + char.firstTimeEvents.size mustEqual 4 + char.firstTimeEvents.head mustEqual "xpe_sanctuary_help" + char.firstTimeEvents(1) mustEqual "xpe_th_firemodes" + char.firstTimeEvents(2) mustEqual "used_beamer" + char.firstTimeEvents(3) mustEqual "map13" + char.tutorials.size mustEqual 0 + char.cosmetics.isDefined mustEqual false + inv.isDefined mustEqual true + val inventory = inv.get.contents + inventory.size mustEqual 10 + //0 + inventory.head.objectClass mustEqual ObjectClass.beamer + inventory.head.guid mustEqual PlanetSideGUID(76) + inventory.head.parentSlot mustEqual 0 + var wep = inventory.head.obj.asInstanceOf[DetailedWeaponData] + wep.ammo.head.objectClass mustEqual ObjectClass.energy_cell + wep.ammo.head.guid mustEqual PlanetSideGUID(77) + wep.ammo.head.parentSlot mustEqual 0 + wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 16 + //1 + inventory(1).objectClass mustEqual ObjectClass.suppressor + inventory(1).guid mustEqual PlanetSideGUID(78) + inventory(1).parentSlot mustEqual 2 + wep = inventory(1).obj.asInstanceOf[DetailedWeaponData] + wep.ammo.head.objectClass mustEqual ObjectClass.bullet_9mm + wep.ammo.head.guid mustEqual PlanetSideGUID(79) + wep.ammo.head.parentSlot mustEqual 0 + wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 25 + //2 + inventory(2).objectClass mustEqual ObjectClass.forceblade + inventory(2).guid mustEqual PlanetSideGUID(80) + inventory(2).parentSlot mustEqual 4 + wep = inventory(2).obj.asInstanceOf[DetailedWeaponData] + wep.ammo.head.objectClass mustEqual ObjectClass.melee_ammo + wep.ammo.head.guid mustEqual PlanetSideGUID(81) + wep.ammo.head.parentSlot mustEqual 0 + wep.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 1 + //3 + inventory(3).objectClass mustEqual ObjectClass.locker_container + inventory(3).guid mustEqual PlanetSideGUID(82) + inventory(3).parentSlot mustEqual 5 + inventory(3).obj.isInstanceOf[DetailedLockerContainerData] mustEqual true + inventory(3).obj.asInstanceOf[DetailedLockerContainerData].inventory.isDefined mustEqual false + //4 + inventory(4).objectClass mustEqual ObjectClass.bullet_9mm + inventory(4).guid mustEqual PlanetSideGUID(83) + inventory(4).parentSlot mustEqual 6 + inventory(4).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 + //5 + inventory(5).objectClass mustEqual ObjectClass.bullet_9mm + inventory(5).guid mustEqual PlanetSideGUID(84) + inventory(5).parentSlot mustEqual 9 + inventory(5).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 + //6 + inventory(6).objectClass mustEqual ObjectClass.bullet_9mm + inventory(6).guid mustEqual PlanetSideGUID(85) + inventory(6).parentSlot mustEqual 12 + inventory(6).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 + //7 + inventory(7).objectClass mustEqual ObjectClass.bullet_9mm_AP + inventory(7).guid mustEqual PlanetSideGUID(86) + inventory(7).parentSlot mustEqual 33 + inventory(7).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 + //8 + inventory(8).objectClass mustEqual ObjectClass.energy_cell + inventory(8).guid mustEqual PlanetSideGUID(87) + inventory(8).parentSlot mustEqual 36 + inventory(8).obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 50 + //9 + inventory(9).objectClass mustEqual ObjectClass.remote_electronics_kit + inventory(9).guid mustEqual PlanetSideGUID(88) + inventory(9).parentSlot mustEqual 39 + //the rek has data but none worth testing here + hand mustEqual DrawnSlot.Pistol1 + case _ => + ko + } case _ => ko } @@ -155,76 +157,80 @@ class DetailedCharacterDataTest extends Specification { //this test is mainly for an alternate bitstream parsing order //the object produced is massive and most of it is already covered in other tests //only certain details towards the end of the stream will be checked - data.isDefined mustEqual true - val char = data.get.asInstanceOf[DetailedCharacterData] - DetailedCharacterData.isBR24(char.bep) mustEqual true - char.certs.size mustEqual 15 - char.certs.head mustEqual CertificationType.StandardAssault - char.certs(14) mustEqual CertificationType.CombatEngineering - char.implants.size mustEqual 3 - char.implants.head.implant mustEqual ImplantType.AudioAmplifier - char.implants.head.activation mustEqual None - char.implants(1).implant mustEqual ImplantType.Targeting - char.implants(1).activation mustEqual None - char.implants(2).implant mustEqual ImplantType.Surge - char.implants(2).activation mustEqual None - char.firstTimeEvents.size mustEqual 298 - char.firstTimeEvents.head mustEqual "xpe_overhead_map" - char.firstTimeEvents(297) mustEqual "map10" - char.tutorials.size mustEqual 3 - char.tutorials.head mustEqual "training_start_nc" - char.tutorials(1) mustEqual "training_ui" - char.tutorials(2) mustEqual "training_map" - char.cosmetics.isDefined mustEqual true - char.cosmetics.get.no_helmet mustEqual true - char.cosmetics.get.beret mustEqual true - char.cosmetics.get.earpiece mustEqual true - char.cosmetics.get.sunglasses mustEqual true - char.cosmetics.get.brimmed_cap mustEqual false - //inventory - char.inventory.isDefined mustEqual true - char.inventory.get.contents.size mustEqual 12 - //0 - char.inventory.get.contents.head.objectClass mustEqual 531 - char.inventory.get.contents.head.guid mustEqual PlanetSideGUID(4202) - char.inventory.get.contents.head.parentSlot mustEqual 0 - val wep1 = char.inventory.get.contents.head.obj.asInstanceOf[DetailedWeaponData] - wep1.unk1 mustEqual 2 - wep1.unk2 mustEqual 8 - wep1.ammo.head.objectClass mustEqual 389 - wep1.ammo.head.guid mustEqual PlanetSideGUID(3942) - wep1.ammo.head.parentSlot mustEqual 0 - wep1.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].unk mustEqual 8 - wep1.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 100 - //4 - char.inventory.get.contents(4).objectClass mustEqual 456 - char.inventory.get.contents(4).guid mustEqual PlanetSideGUID(5374) - char.inventory.get.contents(4).parentSlot mustEqual 5 - char.inventory.get.contents(4).obj.asInstanceOf[DetailedLockerContainerData].inventory.get.contents.size mustEqual 61 - //11 - char.inventory.get.contents(11).objectClass mustEqual 673 - char.inventory.get.contents(11).guid mustEqual PlanetSideGUID(3661) - char.inventory.get.contents(11).parentSlot mustEqual 60 - val wep2 = char.inventory.get.contents(11).obj.asInstanceOf[DetailedWeaponData] - wep2.unk1 mustEqual 2 - wep2.unk2 mustEqual 8 - wep2.ammo.head.objectClass mustEqual 674 - wep2.ammo.head.guid mustEqual PlanetSideGUID(8542) - wep2.ammo.head.parentSlot mustEqual 0 - wep2.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].unk mustEqual 8 - wep2.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 3 - char.drawn_slot mustEqual DrawnSlot.None + data match { + case Some(DetailedPlayerData(Some(_), _, char, inv, hand)) => + DetailedCharacterData.isBR24(char.bep) mustEqual true + char.certs.size mustEqual 15 + char.certs.head mustEqual CertificationType.StandardAssault + char.certs(14) mustEqual CertificationType.CombatEngineering + char.implants.size mustEqual 3 + char.implants.head.implant mustEqual ImplantType.AudioAmplifier + char.implants.head.activation mustEqual None + char.implants(1).implant mustEqual ImplantType.Targeting + char.implants(1).activation mustEqual None + char.implants(2).implant mustEqual ImplantType.Surge + char.implants(2).activation mustEqual None + char.firstTimeEvents.size mustEqual 298 + char.firstTimeEvents.head mustEqual "xpe_overhead_map" + char.firstTimeEvents(297) mustEqual "map10" + char.tutorials.size mustEqual 3 + char.tutorials.head mustEqual "training_start_nc" + char.tutorials(1) mustEqual "training_ui" + char.tutorials(2) mustEqual "training_map" + char.cosmetics.isDefined mustEqual true + char.cosmetics.get.no_helmet mustEqual true + char.cosmetics.get.beret mustEqual true + char.cosmetics.get.earpiece mustEqual true + char.cosmetics.get.sunglasses mustEqual true + char.cosmetics.get.brimmed_cap mustEqual false + //inventory + inv.isDefined mustEqual true + inv.get.contents.size mustEqual 12 + //0 + inv.get.contents.head.objectClass mustEqual 531 + inv.get.contents.head.guid mustEqual PlanetSideGUID(4202) + inv.get.contents.head.parentSlot mustEqual 0 + val wep1 = inv.get.contents.head.obj.asInstanceOf[DetailedWeaponData] + wep1.unk1 mustEqual 2 + wep1.unk2 mustEqual 8 + wep1.ammo.head.objectClass mustEqual 389 + wep1.ammo.head.guid mustEqual PlanetSideGUID(3942) + wep1.ammo.head.parentSlot mustEqual 0 + wep1.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].unk mustEqual 8 + wep1.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 100 + //4 + inv.get.contents(4).objectClass mustEqual 456 + inv.get.contents(4).guid mustEqual PlanetSideGUID(5374) + inv.get.contents(4).parentSlot mustEqual 5 + inv.get.contents(4).obj.asInstanceOf[DetailedLockerContainerData].inventory.get.contents.size mustEqual 61 + //11 + inv.get.contents(11).objectClass mustEqual 673 + inv.get.contents(11).guid mustEqual PlanetSideGUID(3661) + inv.get.contents(11).parentSlot mustEqual 60 + val wep2 = inv.get.contents(11).obj.asInstanceOf[DetailedWeaponData] + wep2.unk1 mustEqual 2 + wep2.unk2 mustEqual 8 + wep2.ammo.head.objectClass mustEqual 674 + wep2.ammo.head.guid mustEqual PlanetSideGUID(8542) + wep2.ammo.head.parentSlot mustEqual 0 + wep2.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].unk mustEqual 8 + wep2.ammo.head.obj.asInstanceOf[DetailedAmmoBoxData].magazine mustEqual 3 + + hand mustEqual DrawnSlot.None + case _ => + ko + } case _ => ko } } "encode (character)" in { - val app = CharacterAppearanceData( - PlacementData( - Vector3(3674.8438f, 2726.789f, 91.15625f), - Vector3(0f, 0f, 36.5625f) - ), + val pos : PlacementData = PlacementData( + 3674.8438f, 2726.789f, 91.15625f, + 0, 0, 36.5625f + ) + val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData( BasicCharacterData( "IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, @@ -247,19 +253,7 @@ class DetailedCharacterDataTest extends Specification { false, RibbonBars() ) - val inv = InventoryItemData(ObjectClass.beamer, PlanetSideGUID(76), 0, DetailedWeaponData(4, 8, ObjectClass.energy_cell, PlanetSideGUID(77), 0, DetailedAmmoBoxData(8, 16))) :: - InventoryItemData(ObjectClass.suppressor, PlanetSideGUID(78), 2, DetailedWeaponData(4, 8, ObjectClass.bullet_9mm, PlanetSideGUID(79), 0, DetailedAmmoBoxData(8, 25))) :: - InventoryItemData(ObjectClass.forceblade, PlanetSideGUID(80), 4, DetailedWeaponData(4, 8, ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1))) :: - InventoryItemData(ObjectClass.locker_container, PlanetSideGUID(82), 5, DetailedLockerContainerData(8)) :: - InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(83), 6, DetailedAmmoBoxData(8, 50)) :: - InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(84), 9, DetailedAmmoBoxData(8, 50)) :: - InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(85), 12, DetailedAmmoBoxData(8, 50)) :: - InventoryItemData(ObjectClass.bullet_9mm_AP, PlanetSideGUID(86), 33, DetailedAmmoBoxData(8, 50)) :: - InventoryItemData(ObjectClass.energy_cell, PlanetSideGUID(87), 36, DetailedAmmoBoxData(8, 50)) :: - InventoryItemData(ObjectClass.remote_electronics_kit, PlanetSideGUID(88), 39, DetailedREKData(8)) :: - Nil - val obj = DetailedCharacterData( - app, + val char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData( 0, 0, 100, 100, @@ -278,13 +272,25 @@ class DetailedCharacterDataTest extends Specification { List(), "xpe_sanctuary_help" :: "xpe_th_firemodes" :: "used_beamer" :: "map13" :: Nil, List.empty, - None, - Some(InventoryData(inv)), - DrawnSlot.Pistol1 + None ) + val inv = InventoryData( + InventoryItemData(ObjectClass.beamer, PlanetSideGUID(76), 0, DetailedWeaponData(4, 8, ObjectClass.energy_cell, PlanetSideGUID(77), 0, DetailedAmmoBoxData(8, 16))) :: + InventoryItemData(ObjectClass.suppressor, PlanetSideGUID(78), 2, DetailedWeaponData(4, 8, ObjectClass.bullet_9mm, PlanetSideGUID(79), 0, DetailedAmmoBoxData(8, 25))) :: + InventoryItemData(ObjectClass.forceblade, PlanetSideGUID(80), 4, DetailedWeaponData(4, 8, ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1))) :: + InventoryItemData(ObjectClass.locker_container, PlanetSideGUID(82), 5, DetailedLockerContainerData(8)) :: + InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(83), 6, DetailedAmmoBoxData(8, 50)) :: + InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(84), 9, DetailedAmmoBoxData(8, 50)) :: + InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(85), 12, DetailedAmmoBoxData(8, 50)) :: + InventoryItemData(ObjectClass.bullet_9mm_AP, PlanetSideGUID(86), 33, DetailedAmmoBoxData(8, 50)) :: + InventoryItemData(ObjectClass.energy_cell, PlanetSideGUID(87), 36, DetailedAmmoBoxData(8, 50)) :: + InventoryItemData(ObjectClass.remote_electronics_kit, PlanetSideGUID(88), 39, DetailedREKData(8)) :: + Nil + ) + val obj = DetailedPlayerData.apply(pos, app, char, inv, DrawnSlot.Pistol1) + val msg = ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector - val pkt_bitv = pkt.toBitVector val ori_bitv = string_testchar.toBitVector pkt_bitv.take(153) mustEqual ori_bitv.take(153) //skip 1 @@ -295,26 +301,31 @@ class DetailedCharacterDataTest extends Specification { } "encode (character, br32)" in { - val obj = DetailedCharacterData( - CharacterAppearanceData( - PlacementData( - Vector3(5500.0f, 3800.0f, 71.484375f), - Vector3(0.0f, 0.0f, 90.0f), - None - ), - BasicCharacterData("KiCkJr", PlanetSideEmpire.NC, CharacterGender.Male, 24, 4), - 3, - false, false, - ExoSuitType.Agile, - "", - 14, - false, - 354.375f, 354.375f, - false, - GrenadeState.None, - false, false, false, - RibbonBars(MeritCommendation.Loser4, MeritCommendation.EventNCElite, MeritCommendation.HeavyAssault6, MeritCommendation.SixYearNC) - ), + val pos : PlacementData = PlacementData( + Vector3(5500.0f, 3800.0f, 71.484375f), + Vector3(0, 0, 90.0f), + None + ) + val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData( + BasicCharacterData("KiCkJr", PlanetSideEmpire.NC, CharacterGender.Male, 24, 4), + 3, + false, false, + ExoSuitType.Agile, + "", + 14, + false, + 354.375f, 354.375f, + false, + GrenadeState.None, + false, false, false, + RibbonBars( + MeritCommendation.Loser4, + MeritCommendation.EventNCElite, + MeritCommendation.HeavyAssault6, + MeritCommendation.SixYearNC + ) + ) + val char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData( 6366766, 694787, 100, 100, 100, @@ -647,198 +658,196 @@ class DetailedCharacterDataTest extends Specification { "training_ui", "training_map" ), - Some(Cosmetics(true, true, true, true, false)), - Some( - InventoryData( - List( - InternalSlot(531, PlanetSideGUID(4202), 0, - DetailedWeaponData(2, 8, 0, List(InternalSlot(389, PlanetSideGUID(3942), 0,DetailedAmmoBoxData(8, 100)))) - ), - InternalSlot(132, PlanetSideGUID(6924), 1, - DetailedWeaponData(2, 8, 0, List(InternalSlot(111, PlanetSideGUID(9157), 0, DetailedAmmoBoxData(8, 100)))) - ), - InternalSlot(714, PlanetSideGUID(8498), 2, - DetailedWeaponData(2, 8, 0, List(InternalSlot(755, PlanetSideGUID(5356), 0, DetailedAmmoBoxData(8, 16)))) - ), - InternalSlot(468, PlanetSideGUID(7198), 4, - DetailedWeaponData(2, 8, 0, List(InternalSlot(540, PlanetSideGUID(5009), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(456, PlanetSideGUID(5374), 5, - DetailedLockerContainerData(8, Some(InventoryData(List( - InternalSlot(429, PlanetSideGUID(3021), 0, - DetailedWeaponData(6, 8, 0, List(InternalSlot(272, PlanetSideGUID(8729), 0, DetailedAmmoBoxData(8, 0)))) - ), - InternalSlot(838, PlanetSideGUID(8467), 9, - DetailedWeaponData(6, 8, 0, List(InternalSlot(839, PlanetSideGUID(8603), 0, DetailedAmmoBoxData(8, 5)))) - ), - InternalSlot(272, PlanetSideGUID(3266), 18, DetailedAmmoBoxData(8, 27)), - InternalSlot(577, PlanetSideGUID(2934), 22, - DetailedWeaponData(6, 8, 0, List(InternalSlot(111, PlanetSideGUID(4682), 0, DetailedAmmoBoxData(8, 100)))) - ), - InternalSlot(839, PlanetSideGUID(3271), 90, DetailedAmmoBoxData(8, 15)), - InternalSlot(839, PlanetSideGUID(7174), 94, DetailedAmmoBoxData(8, 6)), - InternalSlot(429, PlanetSideGUID(6084), 98, - DetailedWeaponData(6, 8, 0, List(InternalSlot(272, PlanetSideGUID(5928), 0, DetailedAmmoBoxData(8, 35)))) - ), - InternalSlot(462, PlanetSideGUID(5000), 108, - DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(6277), 0, DetailedAmmoBoxData(8, 150)))) - ), - InternalSlot(429, PlanetSideGUID(4341), 189, - DetailedWeaponData(6, 8, 0, List(InternalSlot(272, PlanetSideGUID(7043), 0, DetailedAmmoBoxData(8, 35)))) - ), - InternalSlot(556, PlanetSideGUID(4168), 198, - DetailedWeaponData(6, 8, 0, List(InternalSlot(28, PlanetSideGUID(8937), 0, DetailedAmmoBoxData(8, 100)))) - ), - InternalSlot(272, PlanetSideGUID(3173), 207, DetailedAmmoBoxData(8, 50)), - InternalSlot(462, PlanetSideGUID(3221), 210, - DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(4031), 0, DetailedAmmoBoxData(8, 150)))) - ), - InternalSlot(556, PlanetSideGUID(6853), 280, - DetailedWeaponData(6, 8, 0, List(InternalSlot(29, PlanetSideGUID(8524), 0, DetailedAmmoBoxData(8, 67)))) - ), - InternalSlot(556, PlanetSideGUID(4569), 290, - DetailedWeaponData(6, 8, 0, List(InternalSlot(28, PlanetSideGUID(5584), 0, DetailedAmmoBoxData(8, 100)))) - ), - InternalSlot(462, PlanetSideGUID(9294), 300, - DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(3118), 0, DetailedAmmoBoxData(8, 150)))) - ), - InternalSlot(272, PlanetSideGUID(4759), 387, DetailedAmmoBoxData(8, 50)), - InternalSlot(462, PlanetSideGUID(7377), 390, - DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(8155), 0, DetailedAmmoBoxData(8, 150)))) - ), - InternalSlot(843, PlanetSideGUID(6709), 480, DetailedAmmoBoxData(8, 1)), - InternalSlot(843, PlanetSideGUID(5276), 484, DetailedAmmoBoxData(8, 1)), - InternalSlot(843, PlanetSideGUID(7769), 488, DetailedAmmoBoxData(8, 1)), - InternalSlot(844, PlanetSideGUID(5334), 492, DetailedAmmoBoxData(8, 1)), - InternalSlot(844, PlanetSideGUID(6219), 496, DetailedAmmoBoxData(8, 1)), - InternalSlot(842, PlanetSideGUID(7279), 500, DetailedAmmoBoxData(8, 1)), - InternalSlot(842, PlanetSideGUID(5415), 504, DetailedAmmoBoxData(8, 1)), - InternalSlot(175, PlanetSideGUID(5741), 540, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5183), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(324, PlanetSideGUID(6208), 541, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5029), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(324, PlanetSideGUID(8589), 542, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(9217), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(175, PlanetSideGUID(8901), 543, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7633), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(175, PlanetSideGUID(8419), 544, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6546), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(175, PlanetSideGUID(4715), 545, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(8453), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(324, PlanetSideGUID(3577), 546, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(9202), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(324, PlanetSideGUID(6003), 547, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(3260), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(324, PlanetSideGUID(9140), 548, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(3815),0,DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(324, PlanetSideGUID(4913), 549, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(7222),0,DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(324, PlanetSideGUID(6954), 550, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(2953),0,DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(324, PlanetSideGUID(6405), 551, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(4676),0,DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(324, PlanetSideGUID(8915), 552, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(4018),0,DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(324, PlanetSideGUID(4993), 553, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(6775),0,DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(175, PlanetSideGUID(5053), 554, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(6418),0,DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(324, PlanetSideGUID(9244), 555, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(3327),0,DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(468, PlanetSideGUID(6292), 556, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(6918),0,DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(842, PlanetSideGUID(5357), 558, DetailedAmmoBoxData(8, 1)), - InternalSlot(844, PlanetSideGUID(4435), 562, DetailedAmmoBoxData(8, 1)), - InternalSlot(843, PlanetSideGUID(7242), 566, DetailedAmmoBoxData(8, 1)), - InternalSlot(175, PlanetSideGUID(7330), 570, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4786), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(468, PlanetSideGUID(7415), 571, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6536), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(175, PlanetSideGUID(3949), 572, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7526), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(175, PlanetSideGUID(3805), 573, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7358), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(324, PlanetSideGUID(4493), 574, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6852), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(324, PlanetSideGUID(5762), 575, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(3463), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(175, PlanetSideGUID(3315), 576, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7619), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(324, PlanetSideGUID(6263), 577, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5912), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(468, PlanetSideGUID(4028), 578, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(8021), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(175, PlanetSideGUID(2843), 579, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7250), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(175, PlanetSideGUID(9143), 580, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5195), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(468, PlanetSideGUID(5024), 581, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4287), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(468, PlanetSideGUID(6582), 582, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4915), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(468, PlanetSideGUID(6425), 583, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(8872), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(468, PlanetSideGUID(4431), 584, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4191), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(175, PlanetSideGUID(8339), 585, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7317), 0, DetailedAmmoBoxData(8, 1)))) - ), - InternalSlot(175, PlanetSideGUID(3277), 586, - DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6469), 0, DetailedAmmoBoxData(8, 1)))) - ) - )))) - ), - InternalSlot(213, PlanetSideGUID(6877), 6, DetailedCommandDetonaterData(4, 8)), - InternalSlot(755, PlanetSideGUID(6227), 9, DetailedAmmoBoxData(8, 16)), - InternalSlot(728, PlanetSideGUID(7181), 12, DetailedREKData(4, 16)), - InternalSlot(536, PlanetSideGUID(4077), 33, DetailedAmmoBoxData(8, 1)), - InternalSlot(680, PlanetSideGUID(4377), 37, - DetailedWeaponData(2, 8, 0, List(InternalSlot(681, PlanetSideGUID(8905), 0, DetailedAmmoBoxData(8, 3)))) - ), - InternalSlot(32, PlanetSideGUID(5523), 39, DetailedACEData(4)), - InternalSlot(673, PlanetSideGUID(3661), 60, - DetailedWeaponData(2, 8, 0, List(InternalSlot(674, PlanetSideGUID(8542), 0, DetailedAmmoBoxData(8, 3)))) - ) - ) - ) - ), - DrawnSlot.None + Some(Cosmetics(true, true, true, true, false)) ) + val inv = InventoryData( + List( + InternalSlot(531, PlanetSideGUID(4202), 0, + DetailedWeaponData(2, 8, 0, List(InternalSlot(389, PlanetSideGUID(3942), 0,DetailedAmmoBoxData(8, 100)))) + ), + InternalSlot(132, PlanetSideGUID(6924), 1, + DetailedWeaponData(2, 8, 0, List(InternalSlot(111, PlanetSideGUID(9157), 0, DetailedAmmoBoxData(8, 100)))) + ), + InternalSlot(714, PlanetSideGUID(8498), 2, + DetailedWeaponData(2, 8, 0, List(InternalSlot(755, PlanetSideGUID(5356), 0, DetailedAmmoBoxData(8, 16)))) + ), + InternalSlot(468, PlanetSideGUID(7198), 4, + DetailedWeaponData(2, 8, 0, List(InternalSlot(540, PlanetSideGUID(5009), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(456, PlanetSideGUID(5374), 5, + DetailedLockerContainerData(8, Some(InventoryData(List( + InternalSlot(429, PlanetSideGUID(3021), 0, + DetailedWeaponData(6, 8, 0, List(InternalSlot(272, PlanetSideGUID(8729), 0, DetailedAmmoBoxData(8, 0)))) + ), + InternalSlot(838, PlanetSideGUID(8467), 9, + DetailedWeaponData(6, 8, 0, List(InternalSlot(839, PlanetSideGUID(8603), 0, DetailedAmmoBoxData(8, 5)))) + ), + InternalSlot(272, PlanetSideGUID(3266), 18, DetailedAmmoBoxData(8, 27)), + InternalSlot(577, PlanetSideGUID(2934), 22, + DetailedWeaponData(6, 8, 0, List(InternalSlot(111, PlanetSideGUID(4682), 0, DetailedAmmoBoxData(8, 100)))) + ), + InternalSlot(839, PlanetSideGUID(3271), 90, DetailedAmmoBoxData(8, 15)), + InternalSlot(839, PlanetSideGUID(7174), 94, DetailedAmmoBoxData(8, 6)), + InternalSlot(429, PlanetSideGUID(6084), 98, + DetailedWeaponData(6, 8, 0, List(InternalSlot(272, PlanetSideGUID(5928), 0, DetailedAmmoBoxData(8, 35)))) + ), + InternalSlot(462, PlanetSideGUID(5000), 108, + DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(6277), 0, DetailedAmmoBoxData(8, 150)))) + ), + InternalSlot(429, PlanetSideGUID(4341), 189, + DetailedWeaponData(6, 8, 0, List(InternalSlot(272, PlanetSideGUID(7043), 0, DetailedAmmoBoxData(8, 35)))) + ), + InternalSlot(556, PlanetSideGUID(4168), 198, + DetailedWeaponData(6, 8, 0, List(InternalSlot(28, PlanetSideGUID(8937), 0, DetailedAmmoBoxData(8, 100)))) + ), + InternalSlot(272, PlanetSideGUID(3173), 207, DetailedAmmoBoxData(8, 50)), + InternalSlot(462, PlanetSideGUID(3221), 210, + DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(4031), 0, DetailedAmmoBoxData(8, 150)))) + ), + InternalSlot(556, PlanetSideGUID(6853), 280, + DetailedWeaponData(6, 8, 0, List(InternalSlot(29, PlanetSideGUID(8524), 0, DetailedAmmoBoxData(8, 67)))) + ), + InternalSlot(556, PlanetSideGUID(4569), 290, + DetailedWeaponData(6, 8, 0, List(InternalSlot(28, PlanetSideGUID(5584), 0, DetailedAmmoBoxData(8, 100)))) + ), + InternalSlot(462, PlanetSideGUID(9294), 300, + DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(3118), 0, DetailedAmmoBoxData(8, 150)))) + ), + InternalSlot(272, PlanetSideGUID(4759), 387, DetailedAmmoBoxData(8, 50)), + InternalSlot(462, PlanetSideGUID(7377), 390, + DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(8155), 0, DetailedAmmoBoxData(8, 150)))) + ), + InternalSlot(843, PlanetSideGUID(6709), 480, DetailedAmmoBoxData(8, 1)), + InternalSlot(843, PlanetSideGUID(5276), 484, DetailedAmmoBoxData(8, 1)), + InternalSlot(843, PlanetSideGUID(7769), 488, DetailedAmmoBoxData(8, 1)), + InternalSlot(844, PlanetSideGUID(5334), 492, DetailedAmmoBoxData(8, 1)), + InternalSlot(844, PlanetSideGUID(6219), 496, DetailedAmmoBoxData(8, 1)), + InternalSlot(842, PlanetSideGUID(7279), 500, DetailedAmmoBoxData(8, 1)), + InternalSlot(842, PlanetSideGUID(5415), 504, DetailedAmmoBoxData(8, 1)), + InternalSlot(175, PlanetSideGUID(5741), 540, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5183), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(324, PlanetSideGUID(6208), 541, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5029), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(324, PlanetSideGUID(8589), 542, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(9217), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(175, PlanetSideGUID(8901), 543, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7633), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(175, PlanetSideGUID(8419), 544, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6546), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(175, PlanetSideGUID(4715), 545, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(8453), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(324, PlanetSideGUID(3577), 546, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(9202), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(324, PlanetSideGUID(6003), 547, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(3260), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(324, PlanetSideGUID(9140), 548, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(3815),0,DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(324, PlanetSideGUID(4913), 549, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(7222),0,DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(324, PlanetSideGUID(6954), 550, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(2953),0,DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(324, PlanetSideGUID(6405), 551, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(4676),0,DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(324, PlanetSideGUID(8915), 552, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(4018),0,DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(324, PlanetSideGUID(4993), 553, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(6775),0,DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(175, PlanetSideGUID(5053), 554, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(6418),0,DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(324, PlanetSideGUID(9244), 555, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(3327),0,DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(468, PlanetSideGUID(6292), 556, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(6918),0,DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(842, PlanetSideGUID(5357), 558, DetailedAmmoBoxData(8, 1)), + InternalSlot(844, PlanetSideGUID(4435), 562, DetailedAmmoBoxData(8, 1)), + InternalSlot(843, PlanetSideGUID(7242), 566, DetailedAmmoBoxData(8, 1)), + InternalSlot(175, PlanetSideGUID(7330), 570, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4786), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(468, PlanetSideGUID(7415), 571, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6536), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(175, PlanetSideGUID(3949), 572, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7526), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(175, PlanetSideGUID(3805), 573, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7358), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(324, PlanetSideGUID(4493), 574, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6852), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(324, PlanetSideGUID(5762), 575, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(3463), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(175, PlanetSideGUID(3315), 576, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7619), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(324, PlanetSideGUID(6263), 577, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5912), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(468, PlanetSideGUID(4028), 578, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(8021), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(175, PlanetSideGUID(2843), 579, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7250), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(175, PlanetSideGUID(9143), 580, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5195), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(468, PlanetSideGUID(5024), 581, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4287), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(468, PlanetSideGUID(6582), 582, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4915), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(468, PlanetSideGUID(6425), 583, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(8872), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(468, PlanetSideGUID(4431), 584, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4191), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(175, PlanetSideGUID(8339), 585, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7317), 0, DetailedAmmoBoxData(8, 1)))) + ), + InternalSlot(175, PlanetSideGUID(3277), 586, + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6469), 0, DetailedAmmoBoxData(8, 1)))) + ) + )))) + ), + InternalSlot(213, PlanetSideGUID(6877), 6, DetailedCommandDetonaterData(4, 8)), + InternalSlot(755, PlanetSideGUID(6227), 9, DetailedAmmoBoxData(8, 16)), + InternalSlot(728, PlanetSideGUID(7181), 12, DetailedREKData(4, 16)), + InternalSlot(536, PlanetSideGUID(4077), 33, DetailedAmmoBoxData(8, 1)), + InternalSlot(680, PlanetSideGUID(4377), 37, + DetailedWeaponData(2, 8, 0, List(InternalSlot(681, PlanetSideGUID(8905), 0, DetailedAmmoBoxData(8, 3)))) + ), + InternalSlot(32, PlanetSideGUID(5523), 39, DetailedACEData(4)), + InternalSlot(673, PlanetSideGUID(3661), 60, + DetailedWeaponData(2, 8, 0, List(InternalSlot(674, PlanetSideGUID(8542), 0, DetailedAmmoBoxData(8, 3)))) + ) + ) + ) + val obj = DetailedPlayerData(pos, app, char, inv, DrawnSlot.None) + val msg = ObjectCreateDetailedMessage(ObjectClass.avatar, PlanetSideGUID(75), obj) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector - val pkt_bitv = pkt.toBitVector val ori_bitv = string_testchar_br32.toBitVector pkt_bitv.take(153) mustEqual ori_bitv.take(153) //skip 1 diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 976b74977..33e4ab6e2 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1828,7 +1828,7 @@ class WorldSessionActor extends Actor with MDCContextAware { galaxy ! Zone.Lattice.RequestSpawnPoint(u5.toInt, player, u2.toInt) case msg @ SetChatFilterMessage(send_channel, origin, whitelist) => - log.info("SetChatFilters: " + msg) + //log.info("SetChatFilters: " + msg) case msg @ ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents) => var makeReply : Boolean = true diff --git a/pslogin/src/test/scala/PacketCodingActorTest.scala b/pslogin/src/test/scala/PacketCodingActorTest.scala index a2096f014..674e0fa33 100644 --- a/pslogin/src/test/scala/PacketCodingActorTest.scala +++ b/pslogin/src/test/scala/PacketCodingActorTest.scala @@ -452,45 +452,44 @@ class PacketCodingActorHTest extends ActorTest { } class PacketCodingActorITest extends ActorTest { + import net.psforever.packet.game.objectcreate._ + val pos : PlacementData = PlacementData(Vector3.Zero, Vector3.Zero) + val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData( + BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1), + 3, + false, + false, + ExoSuitType.Standard, + "", + 0, + false, + 2.8125f, 210.9375f, + true, + GrenadeState.None, + false, + false, + false, + RibbonBars() + ) + var char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData( + 0, + 0, + 100, 100, + 50, + 1, 7, 7, + 100, 100, + List(CertificationType.StandardAssault, CertificationType.MediumAssault, CertificationType.ATV, CertificationType.Harasser, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit, CertificationType.ReinforcedExoSuit), + List(), + List(), + List.empty, + None + ) + val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None) + val pkt = MultiPacketBundle(List(ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj))) + val string_hex = hex"000900001879060000bc84b000000000000000000002040000097049006c006c006c004900490049006c006c006c0049006c0049006c006c0049006c006c006c0049006c006c0049008452700000000000000000000000000000002000000fe6a703fffffffffffffffffffffffffffffffc00000000000000000000000000000000000000019001900064000001007ec800c80000000000000000000000000000000000000001c00042c54686c7000000000000000000000000000000000000000000000000000000000000000000000000200700" + "PacketCodingActor" should { "bundle an r-originating packet into an l-facing SlottedMetaPacket byte stream data (SlottedMetaPacket)" in { - import net.psforever.packet.game.objectcreate._ - val obj = DetailedCharacterData( - CharacterAppearanceData( - PlacementData(Vector3.Zero, Vector3.Zero), - BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1), - 3, - false, - false, - ExoSuitType.Standard, - "", - 0, - false, - 2.8125f, 210.9375f, - true, - GrenadeState.None, - false, - false, - false, - RibbonBars() - ), - 0, - 0, - 100, 100, - 50, - 1, 7, 7, - 100, 100, - List(CertificationType.StandardAssault,CertificationType.MediumAssault,CertificationType.ATV,CertificationType.Harasser,CertificationType.StandardExoSuit,CertificationType.AgileExoSuit,CertificationType.ReinforcedExoSuit), - List(), - List(), - List.empty, - None, - Some(InventoryData(Nil)), - DrawnSlot.None - ) - val pkt = MultiPacketBundle(List(ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj))) - val string_hex = hex"000900001879060000bc84b000000000000000000002040000097049006c006c006c004900490049006c006c006c0049006c0049006c006c0049006c006c006c0049006c006c0049008452700000000000000000000000000000002000000fe6a703fffffffffffffffffffffffffffffffc00000000000000000000000000000000000000019001900064000001007ec800c80000000000000000000000000000000000000001c00042c54686c7000000000000000000000000000000000000000000000000000000000000000000000000200700" - val probe1 = TestProbe() val probe2 = system.actorOf(Props(classOf[ActorTest.MDCTestProbe], probe1), "mdc-probe") val pca : ActorRef = system.actorOf(Props[PacketCodingActor], "pca") @@ -547,25 +546,25 @@ class PacketCodingActorJTest extends ActorTest { class PacketCodingActorKTest extends ActorTest { import net.psforever.packet.game.objectcreate._ - val obj = DetailedCharacterData( - CharacterAppearanceData( - PlacementData(Vector3.Zero, Vector3.Zero), - BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1), - 3, - false, - false, - ExoSuitType.Standard, - "", - 0, - false, - 2.8125f, 210.9375f, - true, - GrenadeState.None, - false, - false, - false, - RibbonBars() - ), + val pos : PlacementData = PlacementData(Vector3.Zero, Vector3.Zero) + val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData( + BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1), + 3, + false, + false, + ExoSuitType.Standard, + "", + 0, + false, + 2.8125f, 210.9375f, + true, + GrenadeState.None, + false, + false, + false, + RibbonBars() + ) + var char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData( 0, 0, 100, 100, @@ -576,10 +575,9 @@ class PacketCodingActorKTest extends ActorTest { List(), List("xpe_sanctuary_help", "xpe_th_firemodes", "used_beamer", "map13"), List.empty, - None, - Some(InventoryData(Nil)), - DrawnSlot.None + None ) + val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None) val list = List( ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj), ObjectDeleteMessage(PlanetSideGUID(1103), 2),