From 4c5e67ca89c73886d41ad5cb579ad5ac4cb73cd0 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 5 Sep 2017 19:22:48 -0400 Subject: [PATCH] more accomodations for bep field decoding/encoding --- .../converter/AvatarConverter.scala | 9 +- .../CharacterAppearanceData.scala | 21 +- .../objectcreate/DetailedCharacterData.scala | 390 +++++++----------- .../net/psforever/types/ImplantType.scala | 22 +- .../ObjectCreateDetailedMessageTest.scala | 15 +- .../src/main/scala/WorldSessionActor.scala | 138 +++---- 6 files changed, 261 insertions(+), 334 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala index 203dc2ac5..e42c22143 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,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, BattleRankFieldData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle} +import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle} import net.psforever.types.GrenadeState import scala.annotation.tailrec @@ -31,15 +31,16 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { Success( DetailedCharacterData( MakeAppearanceData(obj), - 0, + 0L, + 0L, obj.MaxHealth, obj.Health, obj.Armor, 1, 7, 7, obj.MaxStamina, obj.Stamina, - 28, 4, - BattleRankFieldData(44, 84, 104, 108, 112, 0, 0), + List(0, 1, 11, 21, 26, 27, 28), //TODO certification list + List(), //TODO implant list List.empty[String], //TODO fte list List.empty[String], //TODO tutorial list InventoryData((MakeHolsters(obj, BuildDetailedEquipment) ++ MakeFifthSlot(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)), 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 66380b62b..92dd6e6b1 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 @@ -117,12 +117,27 @@ final case class CharacterAppearanceData(pos : PlacementData, val placementSize : Long = pos.bitsize val nameStringSize : Long = StreamBitSize.stringBitSize(basic_appearance.name, 16) + CharacterAppearanceData.namePadding(pos.vel) val outfitStringSize : Long = StreamBitSize.stringBitSize(outfit_name, 16) + CharacterAppearanceData.outfitNamePadding - val altModelSize = if(on_zipline || backpack) { 1L } else { 0L } + val altModelSize = CharacterAppearanceData.altModelBit(this).getOrElse(0) 335L + placementSize + nameStringSize + outfitStringSize + altModelSize } } 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 latter case, a ball of colored energy. + * In this state, the length of the stream of data is modified. + * @param app the appearance + * @return the length of the variable field that exists when using alternate models + */ + def altModelBit(app : CharacterAppearanceData) : Option[Int] = if(app.backpack || app.on_zipline) { + Some(1) + } + else { + None + } + /** * Get the padding of the player's name. * The padding will always be a number 0-7. @@ -151,7 +166,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] { ("faction" | PlanetSideEmpire.codec) :: ("black_ops" | bool) :: (("alt_model" | bool) >>:~ { alt_model => //modifies stream format (to display alternate player models) - ignore(1) :: //unknown + ignore(1) :: //unknown ("jammered" | bool) :: bool :: //crashes client uint(16) :: //unknown, but usually 0 @@ -204,7 +219,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] { 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) => - val has_outfit_name : Long = outfit.length.toLong //todo this is a kludge + val has_outfit_name : Long = outfit.length.toLong //TODO this is a kludge var alt_model : Boolean = false var alt_model_extrabit : Option[Boolean] = None if(zipline || bpack) { 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 6d1c940db..89678276e 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 @@ -1,42 +1,27 @@ // Copyright (c) 2017 PSForever package net.psforever.packet.game.objectcreate +import net.psforever.newcodecs.newcodecs import net.psforever.packet.{Marshallable, PacketHelpers} -import scodec.{Attempt, Codec, Err} +import net.psforever.types.ImplantType +import scodec.{Attempt, Codec} import scodec.codecs._ import shapeless.{::, HNil} -final case class BattleRankFieldData(field00 : Int, - field01 : Int, - field02 : Int, - field03 : Int, - field04 : Int, - field05 : Int, - field06 : Int, - field07 : Option[Int] = None, - field08 : Option[Int] = None, - field09 : Option[Int] = None, - field0A : Option[Int] = None, - field0B : Option[Int] = None, - field0C : Option[Int] = None, - field0D : Option[Int] = None, - field0E : Option[Int] = None, - field0F : Option[Int] = None, - field10 : Option[Int] = None) extends StreamBitSize { +import scala.annotation.tailrec + +/** + * An entry in the `List` of valid implant slots in `DetailedCharacterData`. + * "`activation`" is not necessarily the best word for it ... + * @param implant the type of implant + * @param activation na + * @see `ImplantType` + */ +final case class ImplantEntry(implant : ImplantType.Value, + activation : Option[Int]) extends StreamBitSize { override def bitsize : Long = { - val extraFieldSize : Long = if(field10.isDefined) { - 70L - } - else if(field0E.isDefined) { - 50L - } - else if(field09.isDefined) { - 10L - } - else { - 0L - } - 55L + extraFieldSize + val activationSize = if(activation.isDefined) { 12L } else { 5L } + 5L + activationSize } } @@ -49,16 +34,16 @@ final case class BattleRankFieldData(field00 : Int, *
* 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 as an avatar. + * 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. * 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).
- *
- * Exploration:
- * Lots of analysis needed for the remainder of the byte data. + * The fourth is the inventory (composed of `Direct`-type objects). * @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; * range is 0-65535 * @param health for `x / y` of hitpoints, this is the avatar's `x` value; @@ -76,15 +61,12 @@ final case class BattleRankFieldData(field00 : Int, * range is 0-65535 * @param stamina for `x / y` of stamina points, this is the avatar's `x` value; * range is 0-65535 - * @param unk4 na; - * defaults to 28 - * @param unk5 na; - * defaults to 4 - * @param brFields na + * @param certs the `List` of active certifications + * @param implants the `List` of implant slots currently possessed by this avatar * @param firstTimeEvents the list of first time events performed by this avatar; * the size field is a 32-bit number; * the first entry may be padded - * @param tutorials the list of tutorials completed by this avatar; + * @param tutorials the `List` of tutorials completed by this avatar; * the size field is a 32-bit number; * the first entry may be padded * @param inventory the avatar's inventory @@ -95,7 +77,8 @@ final case class BattleRankFieldData(field00 : Int, * @see `DrawnSlot` */ final case class DetailedCharacterData(appearance : CharacterAppearanceData, - bep : Int, + bep : Long, + cep : Long, healthMax : Int, health : Int, armor : Int, @@ -104,9 +87,8 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData, unk3 : Int, //7 staminaMax : Int, stamina : Int, - unk4 : Int, //28 - unk5 : Int, //4 - brFields : BattleRankFieldData, + certs : List[Int], + implants : List[ImplantEntry], firstTimeEvents : List[String], tutorials : List[String], inventory : Option[InventoryData], @@ -114,46 +96,36 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData, ) extends ConstructorData { override def bitsize : Long = { - //factor guard bool values into the base size, not its corresponding optional field + //factor guard bool values into the base size, not corresponding optional fields, unless contained or enumerated val appearanceSize = appearance.bitsize - val brFieldSize = brFields.bitsize + val varBit : Option[Int] = CharacterAppearanceData.altModelBit(appearance) + 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, varBit) val fteLen = firstTimeEvents.size //fte list - var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen, bep) + var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen, implantPadding) for(str <- firstTimeEvents) { eventListSize += StreamBitSize.stringBitSize(str) } val tutLen = tutorials.size //tutorial list - var tutorialListSize : Long = 32L + DetailedCharacterData.tutPadding(fteLen, tutLen, bep) + var tutorialListSize : Long = 32L + DetailedCharacterData.tutPadding(fteLen, tutLen, implantPadding) for(str <- tutorials) { tutorialListSize += StreamBitSize.stringBitSize(str) } - var inventorySize : Long = 0L //inventory - if(inventory.isDefined) { - inventorySize = inventory.get.bitsize + val inventorySize : Long = if(inventory.isDefined) { //inventory + inventory.get.bitsize } - 658L + appearanceSize + brFieldSize + eventListSize + tutorialListSize + inventorySize + else { + 0L + } + 649L + appearanceSize + certSize + implantSize + eventListSize + tutorialListSize + inventorySize } } object DetailedCharacterData extends Marshallable[DetailedCharacterData] { -// /** -// * Overloaded constructor for `DetailedCharacterData` that skips all the unknowns by assigning defaulted values. -// * It also allows for a not-optional inventory. -// * @param appearance data about the avatar's basic aesthetics -// * @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value -// * @param health for `x / y` of hitpoints, this is the avatar's `x` value -// * @param armor for `x / y` of armor points, this is the avatar's `x` value -// * @param staminaMax for `x / y` of stamina points, this is the avatar's `y` value -// * @param stamina for `x / y` of stamina points, this is the avatar's `x` value -// * @param 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 : Int, healthMax : Int, health : Int, armor : Int, staminaMax : Int, stamina : Int, firstTimeEvents : List[String], tutorials : List[String], inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedCharacterData = -// new DetailedCharacterData(appearance, bep, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, 28, 4, 44, 84, 104, 1900, firstTimeEvents, tutorials, Some(inventory), drawn_slot) - /** * Overloaded constructor for `DetailedCharacterData` that allows for a not-optional inventory. * @param appearance data about the avatar's basic aesthetics @@ -165,171 +137,108 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { * @param unk3 na * @param staminaMax for `x / y` of stamina points, this is the avatar's `y` value * @param stamina for `x / y` of stamina points, this is the avatar's `x` value - * @param unk4 na - * @param unk5 na - * @param unk6 na + * @param certs na * @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 : Int, healthMax : Int, health : Int, armor : Int, unk1 : Int, unk2 : Int, unk3 : Int, staminaMax : Int, stamina : Int, unk4 : Int, unk5 : Int, unk6 : BattleRankFieldData, firstTimeEvents : List[String], tutorials : List[String], inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedCharacterData = - new DetailedCharacterData(appearance, bep, healthMax, health, armor, unk1, unk2, unk3, staminaMax, stamina, unk4, unk5, unk6, firstTimeEvents, tutorials, Some(inventory), drawn_slot) + def apply(appearance : CharacterAppearanceData, bep : Long, cep : Long, healthMax : Int, health : Int, armor : Int, unk1 : Int, unk2 : Int, unk3 : Int, staminaMax : Int, stamina : Int, certs : List[Int], implants : List[ImplantEntry], firstTimeEvents : List[String], tutorials : List[String], inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedCharacterData = + new DetailedCharacterData(appearance, bep, cep, healthMax, health, armor, unk1, unk2, unk3, staminaMax, stamina, certs, implants, firstTimeEvents, tutorials, Some(inventory), drawn_slot) - private val br1FieldCodec : Codec[BattleRankFieldData] = ( // +0u - ("f1" | uint8L) :: - ("f2" | uint8L) :: - ("f3" | uint8L) :: - ("f4" | uint8L) :: - ("f5" | uint8L) :: - ("f6" | uint8L) :: - ("f7" | uintL(7)) - ).exmap[BattleRankFieldData] ( + /** + * `Codec` for entires in the list of implants. + */ + private val implant_entry_codec : Codec[ImplantEntry] = ( + ("implant" | ImplantType.codec) :: + (bool >>:~ { guard => + newcodecs.binary_choice(guard, uintL(5), uintL(12)).hlist + }) + ).xmap[ImplantEntry] ( { - case f1 :: f2 :: f3 :: f4 :: f5 :: f6 :: f7 :: HNil => - Attempt.successful(BattleRankFieldData(f1, f2, f3, f4, f5, f6, f7)) + case implant :: true :: _ :: HNil => + ImplantEntry(implant, None) + + case implant :: false :: extra :: HNil => + ImplantEntry(implant, Some(extra)) }, { - case BattleRankFieldData(f1, f2, f3, f4, f5, f6, f7, _, _, _, _, _, _, _, _, _, _) => - Attempt.successful(f1 :: f2 :: f3 :: f4 :: f5 :: f6 :: f7 :: HNil) - } - ) + case ImplantEntry(implant, None) => + implant :: true :: 0 :: HNil - private val br6FieldCodec : Codec[BattleRankFieldData] = ( //+10u - ("f1" | uint8L) :: - ("f2" | uint8L) :: - ("f3" | uint8L) :: - ("f4" | uint8L) :: - ("f5" | uint8L) :: - ("f6" | uint8L) :: - ("f7" | uint8L) :: - ("f8" | uint8L) :: - ("f9" | bool) - ).exmap[BattleRankFieldData] ( - { - case f1 :: f2 :: f3 :: f4 :: f5 :: f6 :: f7 :: f8 :: f9 :: HNil => - val f9Int : Int = if(f9) { 1 } else { 0 } - Attempt.successful(BattleRankFieldData(f1, f2, f3, f4, f5, f6, f7, Some(f8), Some(f9Int))) - }, - { - case BattleRankFieldData(f1, f2, f3, f4, f5, f6, f7, Some(f8), Some(f9), _, _, _, _, _, _, _, _) => - val f9Bool : Boolean = if(f9 == 0) { false } else { true } - Attempt.successful(f1 :: f2 :: f3 :: f4 :: f5 :: f6 :: f7 :: f8 :: f9Bool :: HNil) - case _ => - Attempt.failure(Err("expected battle rank 6 field data")) - } - ) - - private val br12FieldCodec : Codec[BattleRankFieldData] = ( //+52u - ("f1" | uint8L) :: - ("f2" | uint8L) :: - ("f3" | uint8L) :: - ("f4" | uint8L) :: - ("f5" | uint8L) :: - ("f6" | uint8L) :: - ("f7" | uint8L) :: - ("f8" | uint8L) :: - ("f9" | uint8L) :: - ("fA" | uint8L) :: - ("fB" | uint8L) :: - ("fC" | uint8L) :: - ("fD" | uint8L) :: - ("fE" | uintL(3)) - ).exmap[BattleRankFieldData] ( - { - case f1 :: f2 :: f3 :: f4 :: f5 :: f6 :: f7 :: f8 :: f9 :: fa :: fb :: fc :: fd :: fe :: HNil => - Attempt.successful(BattleRankFieldData(f1, f2, f3, f4, f5, f6, f7, Some(f8), Some(f9), Some(fa), Some(fb), Some(fc), Some(fd), Some(fe))) - }, - { - case BattleRankFieldData(f1, f2, f3, f4, f5, f6, f7, Some(f8), Some(f9), Some(fa), Some(fb), Some(fc), Some(fd), Some(fe), _, _, _) => - Attempt.successful(f1 :: f2 :: f3 :: f4 :: f5 :: f6 :: f7 :: f8 :: f9 :: fa :: fb :: fc :: fd :: fe :: HNil) - case _ => - Attempt.failure(Err("expected battle rank 12 field data")) - } - ) - - private val br18FieldCodec : Codec[BattleRankFieldData] = ( //+70u - ("f01" | uint8L) :: - ("f02" | uint8L) :: - ("f03" | uint8L) :: - ("f04" | uint8L) :: - ("f05" | uint8L) :: - ("f06" | uint8L) :: - ("f07" | uint8L) :: - ("f08" | uint8L) :: - ("f09" | uint8L) :: - ("f0A" | uint8L) :: - ("f0B" | uint8L) :: - ("f0C" | uint8L) :: - ("f0D" | uint8L) :: - ("f0E" | uint8L) :: - ("f0F" | uint8L) :: - ("f10" | uintL(5)) - ).exmap[BattleRankFieldData] ( - { - case f01 :: f02 :: f03 :: f04 :: f05 :: f06 :: f07 :: f08 :: f09 :: f0a :: f0b :: f0c :: f0d :: f0e :: f0f :: f10 :: HNil => - Attempt.successful(BattleRankFieldData(f01, f02, f03, f04, f05, f06, f07, Some(f08), Some(f09), Some(f0a), Some(f0b), Some(f0c), Some(f0d), Some(f0e), Some(f0f), Some(f10))) - }, - { - case BattleRankFieldData(f01, f02, f03, f04, f05, f06, f07, Some(f08), Some(f09), Some(f0a), Some(f0b), Some(f0c), Some(f0d), Some(f0e), Some(f0f), Some(f10), _) => - Attempt.successful(f01 :: f02 :: f03 :: f04 :: f05 :: f06 :: f07 :: f08 :: f09 :: f0a :: f0b :: f0c :: f0d :: f0e :: f0f :: f10 :: HNil) - case _ => - Attempt.failure(Err("expected battle rank 18 field data")) + case ImplantEntry(implant, Some(extra)) => + implant :: false :: extra :: HNil } ) /** - * na - * @param bep the battle experience points - * @return the appropriate `Codec` for the fields representing a player with the implied battle rank + * A player's battle rank, determined by their battle experience points, determines how many implants to which they have access. + * Starting with "no implants" at BR1, a player earns one at each of the three ranks: BR6, BR12, and BR18. + * @param bep battle experience points + * @return the number of accessible implant slots */ - private def selectBattleRankFieldCodec(bep : Int) : Codec[BattleRankFieldData] = { - if(bep > 754370) { - br18FieldCodec + private def numberOfImplantSlots(bep : Long) : Int = { + if(bep > 754370) { //BR18+ + 3 } - else if(bep > 197753) { - br12FieldCodec + else if(bep > 197753) { //BR12+ + 2 } - else if(bep > 29999) { - br6FieldCodec + else if(bep > 29999) { //BR6+ + 1 } - else { - br1FieldCodec + else { //BR1+ + 0 } } /** * The padding value of the first entry in either of two byte-aligned `List` structures. - * @param bep the battle experience points - * @return the pad length in bits `n < 8` + * @param implants implant entries + * @return the pad length in bits `0 <= n < 8` */ - private def bepFieldPadding(bep : Int) : Int = { - if(bep > 754370) { //BR18+ - 7 + private def implantFieldPadding(implants : List[ImplantEntry], varBit : Option[Int] = None) : Int = { + val base : Int = 5 //the offset with no implant entries + val baseOffset : Int = base - varBit.getOrElse(0) + val resultA = if(baseOffset < 0) { 8 - baseOffset } else { baseOffset % 8 } + + var implantOffset : Int = 0 + implants.foreach({entry => + implantOffset += entry.bitsize.toInt + }) + val resultB : Int = resultA - (implantOffset % 8) + if(resultB < 0) { 8 - resultB } else { resultB } + } + + /** + * Players with certain battle rank will always have a certain number of implant slots. + * The encoding requires it. + * Pad empty slots onto the end of a list of + * @param size the required number of implant slots + * @param list the `List` of implant slots + * @return a fully-populated (or over-populated) `List` of implant slots + * @see `ImplantEntry` + */ + @tailrec private def recursiveEnsureImplantSlots(size : Int, list : List[ImplantEntry] = Nil) : List[ImplantEntry] = { + if(list.length >= size) { + list } - else if(bep > 197753) { //BR12+ - 1 - } - else if(bep > 29999) { //BR6+ - 3 - } - else { //BR1+ - 5 + else { + recursiveEnsureImplantSlots(size, list :+ ImplantEntry(ImplantType.None, None)) } } /** * Get the padding of the first entry in the first time events list. - * @param len the length of the list - * @param bep the battle experience points - * @return the pad length in bits `n < 8` + * @param len the length of the first time events list + * @param implantPadding the padding that resulted from implant entries + * @return the pad length in bits `0 <= n < 8` */ - private def ftePadding(len : Long, bep : Int) : Int = { - //TODO the parameters for this function are not correct + private def ftePadding(len : Long, implantPadding : Int) : Int = { //TODO the proper padding length should reflect all variability in the stream prior to this point if(len > 0) { - bepFieldPadding(bep) + implantPadding } else { 0 @@ -342,29 +251,28 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { * The tutorials list follows the first time event list and also contains byte-aligned strings. * If the both lists are populated or empty at the same time, the first entry will not need padding. * If the first time events list is unpopulated, but this list is populated, the first entry will need padding bits. - * @param len the length of the list - * @param bep the battle experience points + * @param len the length of the first time events list + * @param len2 the length of the tutorial list + * @param implantPadding the padding that resulted from implant entries * @return the pad length in bits `n < 8` */ - private def tutPadding(len : Long, len2 : Long, bep : Int) : Int = { + private def tutPadding(len : Long, len2 : Long, implantPadding : Int) : Int = { if(len > 0) { - //automatic alignment from previous List - 0 + 0 //automatic alignment from previous List } else if(len2 > 0) { - //need to align for elements - bepFieldPadding(bep) + implantPadding //need to align for elements } else { - //both lists are empty - 0 + 0 //both lists are empty } } implicit val codec : Codec[DetailedCharacterData] = ( - ("appearance" | CharacterAppearanceData.codec) :: - (("bep" | uint24L) >>:~ { bep => - ignore(136) :: + ("appearance" | CharacterAppearanceData.codec) >>:~ { app => + ("bep" | uint32L) >>:~ { bep => + ("cep" | uint32L) :: + ignore(96) :: ("healthMax" | uint16L) :: ("health" | uint16L) :: ignore(1) :: @@ -376,47 +284,57 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { ("unk3" | uintL(3)) :: ("staminaMax" | uint16L) :: ("stamina" | uint16L) :: - ignore(149) :: - ("unk4" | uint16L) :: - ("unk5" | uint8L) :: - ("brFields" | selectBattleRankFieldCodec(bep)) :: //TODO do this for all these fields until their bits are better defined - (("firstTimeEvent_length" | uint32L) >>:~ { len => - conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned(ftePadding(len, bep))) :: - ("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) :: - (("tutorial_length" | uint32L) >>:~ { len2 => - conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned(tutPadding(len, len2, bep))) :: - ("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) :: - ignore(207) :: - optional(bool, "inventory" | InventoryData.codec_detailed) :: - ("drawn_slot" | DrawnSlot.codec) :: - bool //usually false + ignore(147) :: + ("certs" | listOfN(uint8L, uint8L)) :: + ignore(5) :: + (("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(207) :: + optional(bool, "inventory" | InventoryData.codec_detailed) :: + ("drawn_slot" | DrawnSlot.codec) :: + bool //usually false + }) }) }) - }) + } + } ).exmap[DetailedCharacterData] ( { - case app :: bep :: _ :: hpmax :: hp :: _ :: armor :: _ :: u1 :: _ :: u2 :: u3 :: stamax :: stam :: _ :: u4 :: u5 :: brFields :: _ :: fte0 :: fte1 :: _ :: tut0 :: tut1 :: _ :: inv :: drawn :: false :: HNil => + case app :: bep :: cep :: _ :: hpmax :: hp :: _ :: armor :: _ :: u1 :: _ :: u2 :: u3 :: stamax :: stam :: _ :: certs :: _ :: implants :: _ :: _ :: fte0 :: fte1 :: _ :: tut0 :: tut1 :: _ :: inv :: drawn :: false :: 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, hpmax, hp, armor, u1, u2, u3, stamax, stam, u4, u5, brFields, fteList, tutList, inv, drawn)) + Attempt.successful(DetailedCharacterData(app, bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, inv, drawn)) }, { - case DetailedCharacterData(app, bep, hpmax, hp, armor, u1, u2, u3, stamax, stam, u4, u5, brFields, fteList, tutList, inv, drawn) => + case DetailedCharacterData(app, bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, inv, drawn) => + val implantCapacity : Int = numberOfImplantSlots(bep) + val implantList = if(implants.length > implantCapacity) { + implants.slice(0, implantCapacity) + } + else { + recursiveEnsureImplantSlots(implantCapacity, implants) + } //shift the first elements off their lists var fteListCopy = fteList var firstEvent : Option[String] = None if(fteList.nonEmpty) { firstEvent = Some(fteList.head) - fteListCopy = fteList.drop(1) + fteListCopy = fteList.tail } var tutListCopy = tutList var firstTutorial : Option[String] = None if(tutList.nonEmpty) { firstTutorial = Some(tutList.head) - tutListCopy = tutList.drop(1) + tutListCopy = tutList.tail } - Attempt.successful(app :: bep :: () :: hpmax :: hp :: () :: armor :: () :: u1 :: () :: u2 :: u3 :: stamax :: stam :: () :: u4 :: u5 :: brFields :: fteList.size.toLong :: firstEvent :: fteListCopy :: tutList.size.toLong :: firstTutorial :: tutListCopy :: () :: inv :: drawn :: false :: HNil) + Attempt.successful(app :: bep :: cep :: () :: hpmax :: hp :: () :: armor :: () :: u1 :: () :: u2 :: u3 :: stamax :: stam :: () :: certs :: () :: implantList :: () :: fteList.size.toLong :: firstEvent :: fteListCopy :: tutList.size.toLong :: firstTutorial :: tutListCopy :: () :: inv :: drawn :: false :: HNil) } ) } diff --git a/common/src/main/scala/net/psforever/types/ImplantType.scala b/common/src/main/scala/net/psforever/types/ImplantType.scala index 3449c0e45..acd47b5b3 100644 --- a/common/src/main/scala/net/psforever/types/ImplantType.scala +++ b/common/src/main/scala/net/psforever/types/ImplantType.scala @@ -9,16 +9,16 @@ import scodec.codecs._ *
* Implant:
* ` - * 00 - Regeneration (advanced_regen)
- * 01 - Enhanced Targeting (targeting)
- * 02 - Audio Amplifier (audio_amplifier)
- * 03 - Darklight Vision (darklight_vision)
- * 04 - Melee Booster (melee_booster)
- * 05 - Personal Shield (personal_shield)
- * 06 - Range Magnifier (range_magnifier)
- * 07 - Second Wind `(na)`
- * 08 - Sensor Shield (silent_run)
- * 09 - Surge (surge)
+ * 0 - Regeneration (advanced_regen)
+ * 1 - Enhanced Targeting (targeting)
+ * 2 - Audio Amplifier (audio_amplifier)
+ * 3 - Darklight Vision (darklight_vision)
+ * 4 - Melee Booster (melee_booster)
+ * 5 - Personal Shield (personal_shield)
+ * 6 - Range Magnifier (range_magnifier)
+ * 7 - Second Wind `(na)`
+ * 8 - Sensor Shield (silent_run)
+ * 9 - Surge (surge) * ` */ object ImplantType extends Enumeration { @@ -34,5 +34,7 @@ object ImplantType extends Enumeration { SilentRun, Surge = Value + val None = Value(15) //TODO unconfirmed + implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L) } diff --git a/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala b/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala index 188fd5d5a..d585e60af 100644 --- a/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala +++ b/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala @@ -214,15 +214,7 @@ class ObjectCreateDetailedMessageTest extends Specification { char.unk3 mustEqual 7 char.staminaMax mustEqual 100 char.stamina mustEqual 100 - char.unk4 mustEqual 28 - char.unk5 mustEqual 4 - char.brFields.field00 mustEqual 44 - char.brFields.field01 mustEqual 84 - char.brFields.field02 mustEqual 104 - char.brFields.field03 mustEqual 108 - char.brFields.field04 mustEqual 112 - char.brFields.field05 mustEqual 0 - char.brFields.field06 mustEqual 0 + char.certs mustEqual List(0, 1, 11, 21, 26, 27, 28) char.firstTimeEvents.size mustEqual 4 char.firstTimeEvents.head mustEqual "xpe_sanctuary_help" char.firstTimeEvents(1) mustEqual "xpe_th_firemodes" @@ -409,12 +401,13 @@ class ObjectCreateDetailedMessageTest extends Specification { val obj = DetailedCharacterData( app, 0, + 0, 100, 100, 50, 1, 7, 7, 100, 100, - 28, 4, - BattleRankFieldData(44, 84, 104, 108, 112, 0, 0), + List(0, 1, 11, 21, 26, 27, 28), + List(), "xpe_sanctuary_help" :: "xpe_th_firemodes" :: "used_beamer" :: "map13" :: Nil, List.empty, InventoryData(inv), diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 18a4a9fa2..0063b8b1f 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -394,73 +394,25 @@ class WorldSessionActor extends Actor with MDCContextAware { case ListAccountCharacters => val gen : AtomicInteger = new AtomicInteger(1) - log.info(PacketCoding.DecodePacket(hex"18 34 6F 00 00 BC 84 B0 00 90 76 BA 3F 94 60 E0 00 0B 04 40 00 08 60 70 00 73 00 65 00 6D 00 75 00 32 00 82 00 47 34 9E 80 80 00 00 00 00 00 3F FF C0 00 00 00 20 00 00 00 37 E3 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF AD E8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 E0 01 E0 00 64 00 00 01 00 7E C8 00 C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 C0 00 42 C6 86 C7 CA 40 70 00 05 80 00 00 04 88 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 33 93 78 70 65 5F 77 61 72 70 5F 67 61 74 65 5F 75 73 61 67 65 93 78 70 65 5F 6F 72 62 69 74 61 6C 5F 73 68 75 74 74 6C 65 8C 78 70 65 5F 64 72 6F 70 5F 70 6F 64 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 32 92 78 70 65 5F 73 61 6E 63 74 75 61 72 79 5F 68 65 6C 70 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 36 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 35 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 34 8E 78 70 65 5F 74 68 5F 6E 6F 6E 73 61 6E 63 92 78 70 65 5F 74 68 5F 73 77 69 74 63 68 62 6C 61 64 65 8D 78 70 65 5F 74 68 5F 72 6F 75 74 65 72 8C 78 70 65 5F 74 68 5F 66 6C 61 69 6C 8A 78 70 65 5F 74 68 5F 61 6E 74 8A 78 70 65 5F 74 68 5F 61 6D 73 8F 78 70 65 5F 74 68 5F 67 72 6F 75 6E 64 5F 70 8C 78 70 65 5F 74 68 5F 61 69 72 5F 70 8C 78 70 65 5F 74 68 5F 68 6F 76 65 72 8D 78 70 65 5F 74 68 5F 67 72 6F 75 6E 64 8A 78 70 65 5F 74 68 5F 62 66 72 92 78 70 65 5F 74 68 5F 61 66 74 65 72 62 75 72 6E 65 72 8A 78 70 65 5F 74 68 5F 61 69 72 8B 78 70 65 5F 74 68 5F 61 6D 6D 6F 90 78 70 65 5F 74 68 5F 66 69 72 65 6D 6F 64 65 73 8C 78 70 65 5F 74 68 5F 63 6C 6F 61 6B 8A 78 70 65 5F 74 68 5F 6D 61 78 89 75 73 65 64 5F 6F 69 63 77 91 75 73 65 64 5F 61 64 76 61 6E 63 65 64 5F 61 63 65 97 76 69 73 69 74 65 64 5F 73 70 69 74 66 69 72 65 5F 74 75 72 72 65 74 98 76 69 73 69 74 65 64 5F 73 70 69 74 66 69 72 65 5F 63 6C 6F 61 6B 65 64 93 76 69 73 69 74 65 64 5F 73 70 69 74 66 69 72 65 5F 61 61 92 76 69 73 69 74 65 64 5F 74 61 6E 6B 5F 74 72 61 70 73 A1 76 69 73 69 74 65 64 5F 70 6F 72 74 61 62 6C 65 5F 6D 61 6E 6E 65 64 5F 74 75 72 72 65 74 5F 76 73 8F 75 73 65 64 5F 66 6F 72 63 65 62 6C 61 64 65 93 76 69 73 69 74 65 64 5F 77 61 6C 6C 5F 74 75 72 72 65 74 8B 76 69 73 69 74 65 64 5F 61 6D 73 8B 76 69 73 69 74 65 64 5F 61 6E 74 90 76 69 73 69 74 65 64 5F 64 72 6F 70 73 68 69 70 91 76 69 73 69 74 65 64 5F 6C 69 62 65 72 61 74 6F 72 94 76 69 73 69 74 65 64 5F 6C 69 67 68 74 67 75 6E 73 68 69 70 91 76 69 73 69 74 65 64 5F 6C 69 67 68 74 6E 69 6E 67 90 76 69 73 69 74 65 64 5F 6D 61 67 72 69 64 65 72 93 76 69 73 69 74 65 64 5F 71 75 61 64 73 74 65 61 6C 74 68 90 76 69 73 69 74 65 64 5F 73 6B 79 67 75 61 72 64 9D 76 69 73 69 74 65 64 5F 74 77 6F 5F 6D 61 6E 5F 61 73 73 61 75 6C 74 5F 62 75 67 67 79 98 76 69 73 69 74 65 64 5F 74 77 6F 6D 61 6E 68 6F 76 65 72 62 75 67 67 79 8D 76 69 73 69 74 65 64 5F 66 6C 61 69 6C 8E 76 69 73 69 74 65 64 5F 72 6F 75 74 65 72 93 76 69 73 69 74 65 64 5F 73 77 69 74 63 68 62 6C 61 64 65 8E 76 69 73 69 74 65 64 5F 61 75 72 6F 72 61 8C 76 69 73 69 74 65 64 5F 66 75 72 79 93 76 69 73 69 74 65 64 5F 71 75 61 64 61 73 73 61 75 6C 74 96 76 69 73 69 74 65 64 5F 67 61 6C 61 78 79 5F 67 75 6E 73 68 69 70 8E 76 69 73 69 74 65 64 5F 61 70 63 5F 76 73 90 76 69 73 69 74 65 64 5F 6C 6F 64 65 73 74 61 72 90 76 69 73 69 74 65 64 5F 70 68 61 6E 74 61 73 6D 8F 76 69 73 69 74 65 64 5F 76 75 6C 74 75 72 65 8C 76 69 73 69 74 65 64 5F 77 61 73 70 90 76 69 73 69 74 65 64 5F 6D 6F 73 71 75 69 74 6F 97 76 69 73 69 74 65 64 5F 61 70 68 65 6C 69 6F 6E 5F 66 6C 69 67 68 74 97 76 69 73 69 74 65 64 5F 61 70 68 65 6C 69 6F 6E 5F 67 75 6E 6E 65 72 89 75 73 65 64 5F 62 61 6E 6B 95 76 69 73 69 74 65 64 5F 72 65 73 6F 75 72 63 65 5F 73 69 6C 6F 9E 76 69 73 69 74 65 64 5F 63 65 72 74 69 66 69 63 61 74 69 6F 6E 5F 74 65 72 6D 69 6E 61 6C 94 76 69 73 69 74 65 64 5F 6D 65 64 5F 74 65 72 6D 69 6E 61 6C 93 75 73 65 64 5F 6E 61 6E 6F 5F 64 69 73 70 65 6E 73 65 72 95 76 69 73 69 74 65 64 5F 73 65 6E 73 6F 72 5F 73 68 69 65 6C 64 9A 76 69 73 69 74 65 64 5F 62 72 6F 61 64 63 61 73 74 5F 77 61 72 70 67 61 74 65 8C 75 73 65 64 5F 70 68 61 6C 61 6E 78 94 75 73 65 64 5F 70 68 61 6C 61 6E 78 5F 61 76 63 6F 6D 62 6F 96 75 73 65 64 5F 70 68 61 6C 61 6E 78 5F 66 6C 61 6B 63 6F 6D 62 6F 96 76 69 73 69 74 65 64 5F 77 61 72 70 67 61 74 65 5F 73 6D 61 6C 6C 88 75 73 65 64 5F 61 63 65 98 76 69 73 69 74 65 64 5F 61 64 76 5F 6D 65 64 5F 74 65 72 6D 69 6E 61 6C 9C 76 69 73 69 74 65 64 5F 61 69 72 5F 76 65 68 69 63 6C 65 5F 74 65 72 6D 69 6E 61 6C 95 75 73 65 64 5F 61 6E 6E 69 76 65 72 73 61 72 79 5F 67 75 6E 62 90 75 73 65 64 5F 37 35 6D 6D 5F 63 61 6E 6E 6F 6E 92 75 73 65 64 5F 61 70 63 5F 76 73 5F 77 65 61 70 6F 6E 9F 75 73 65 64 5F 61 70 68 65 6C 69 6F 6E 5F 69 6D 6D 6F 6C 61 74 69 6F 6E 5F 63 61 6E 6E 6F 6E 93 75 73 65 64 5F 61 70 68 65 6C 69 6F 6E 5F 6C 61 73 65 72 9F 75 73 65 64 5F 61 70 68 65 6C 69 6F 6E 5F 70 6C 61 73 6D 61 5F 72 6F 63 6B 65 74 5F 70 6F 64 91 75 73 65 64 5F 61 70 68 65 6C 69 6F 6E 5F 70 70 61 96 75 73 65 64 5F 61 70 68 65 6C 69 6F 6E 5F 73 74 61 72 66 69 72 65 8B 75 73 65 64 5F 62 65 61 6D 65 72 94 76 69 73 69 74 65 64 5F 62 66 72 5F 74 65 72 6D 69 6E 61 6C 90 75 73 65 64 5F 62 6F 6C 74 5F 64 72 69 76 65 72 97 76 69 73 69 74 65 64 5F 6D 65 64 69 75 6D 74 72 61 6E 73 70 6F 72 74 98 76 69 73 69 74 65 64 5F 73 68 69 65 6C 64 5F 67 65 6E 65 72 61 74 6F 72 97 76 69 73 69 74 65 64 5F 67 61 6C 61 78 79 5F 74 65 72 6D 69 6E 61 6C 92 75 73 65 64 5F 65 6E 65 72 67 79 5F 67 75 6E 5F 76 73 91 75 73 65 64 5F 6C 61 7A 65 5F 70 6F 69 6E 74 65 72 91 75 73 65 64 5F 66 6C 61 69 6C 5F 77 65 61 70 6F 6E 91 75 73 65 64 5F 66 6C 61 6D 65 74 68 72 6F 77 65 72 8E 75 73 65 64 5F 66 6C 65 63 68 65 74 74 65 90 75 73 65 64 5F 66 6C 75 78 5F 63 61 6E 6E 6F 6E 8C 75 73 65 64 5F 66 6C 75 78 70 6F 64 91 75 73 65 64 5F 67 72 65 6E 61 64 65 5F 66 72 61 67 98 75 73 65 64 5F 33 35 6D 6D 5F 72 6F 74 61 72 79 63 68 61 69 6E 67 75 6E 9A 76 69 73 69 74 65 64 5F 67 65 6E 65 72 61 74 6F 72 5F 74 65 72 6D 69 6E 61 6C 9B 75 73 65 64 5F 68 65 61 76 79 5F 67 72 65 6E 61 64 65 5F 6C 61 75 6E 63 68 65 72 94 75 73 65 64 5F 68 65 61 76 79 5F 72 61 69 6C 5F 62 65 61 6D 91 75 73 65 64 5F 68 65 61 76 79 5F 73 6E 69 70 65 72 89 75 73 65 64 5F 69 6C 63 39 98 76 69 73 69 74 65 64 5F 69 6D 70 6C 61 6E 74 5F 74 65 72 6D 69 6E 61 6C 93 75 73 65 64 5F 67 72 65 6E 61 64 65 5F 6A 61 6D 6D 65 72 8B 75 73 65 64 5F 6C 61 6E 63 65 72 8B 75 73 65 64 5F 6C 61 73 68 65 72 90 75 73 65 64 5F 32 35 6D 6D 5F 63 61 6E 6E 6F 6E 99 75 73 65 64 5F 6C 69 62 65 72 61 74 6F 72 5F 62 6F 6D 62 61 72 64 69 65 72 90 75 73 65 64 5F 33 35 6D 6D 5F 63 61 6E 6E 6F 6E 93 75 73 65 64 5F 72 65 61 76 65 72 5F 77 65 61 70 6F 6E 73 96 75 73 65 64 5F 6C 69 67 68 74 6E 69 6E 67 5F 77 65 61 70 6F 6E 73 92 76 69 73 69 74 65 64 5F 4C 4C 55 5F 73 6F 63 6B 65 74 9A 76 69 73 69 74 65 64 5F 65 78 74 65 72 6E 61 6C 5F 64 6F 6F 72 5F 6C 6F 63 6B 8E 75 73 65 64 5F 6D 61 65 6C 73 74 72 6F 6D 98 76 69 73 69 74 65 64 5F 72 65 73 70 61 77 6E 5F 74 65 72 6D 69 6E 61 6C 8E 76 69 73 69 74 65 64 5F 6C 6F 63 6B 65 72 8C 75 73 65 64 5F 6D 65 64 5F 61 70 70 90 75 73 65 64 5F 32 30 6D 6D 5F 63 61 6E 6E 6F 6E 98 76 69 73 69 74 65 64 5F 6D 6F 6E 6F 6C 69 74 68 5F 61 6D 65 72 69 73 68 99 76 69 73 69 74 65 64 5F 6D 6F 6E 6F 6C 69 74 68 5F 63 65 72 79 73 68 65 6E 97 76 69 73 69 74 65 64 5F 6D 6F 6E 6F 6C 69 74 68 5F 63 79 73 73 6F 72 97 76 69 73 69 74 65 64 5F 6D 6F 6E 6F 6C 69 74 68 5F 65 73 61 6D 69 72 99 76 69 73 69 74 65 64 5F 6D 6F 6E 6F 6C 69 74 68 5F 66 6F 72 73 65 72 61 6C 97 76 69 73 69 74 65 64 5F 6D 6F 6E 6F 6C 69 74 68 5F 68 6F 73 73 69 6E 99 76 69 73 69 74 65 64 5F 6D 6F 6E 6F 6C 69 74 68 5F 69 73 68 75 6E 64 61 72 98 76 69 73 69 74 65 64 5F 6D 6F 6E 6F 6C 69 74 68 5F 73 65 61 72 68 75 73 97 76 69 73 69 74 65 64 5F 6D 6F 6E 6F 6C 69 74 68 5F 73 6F 6C 73 61 72 95 76 69 73 69 74 65 64 5F 6D 6F 74 69 6F 6E 5F 73 65 6E 73 6F 72 91 75 73 65 64 5F 61 72 6D 6F 72 5F 73 69 70 68 6F 6E 8F 75 73 65 64 5F 6E 74 75 5F 73 69 70 68 6F 6E 8C 75 73 65 64 5F 70 68 6F 65 6E 69 78 93 75 73 65 64 5F 67 72 65 6E 61 64 65 5F 70 6C 61 73 6D 61 8B 75 73 65 64 5F 70 75 6C 73 61 72 A0 75 73 65 64 5F 70 75 6C 73 65 64 5F 70 61 72 74 69 63 6C 65 5F 61 63 63 65 6C 65 72 61 74 6F 72 8D 75 73 65 64 5F 70 75 6E 69 73 68 65 72 8D 75 73 65 64 5F 72 61 64 69 61 74 6F 72 88 75 73 65 64 5F 72 65 6B 93 76 69 73 69 74 65 64 5F 72 65 70 61 69 72 5F 73 69 6C 6F 9F 76 69 73 69 74 65 64 5F 64 65 63 6F 6E 73 74 72 75 63 74 69 6F 6E 5F 74 65 72 6D 69 6E 61 6C 8C 75 73 65 64 5F 72 6F 63 6B 6C 65 74 93 75 73 65 64 5F 72 6F 74 61 72 79 63 68 61 69 6E 67 75 6E 8B 75 73 65 64 5F 73 63 79 74 68 65 99 76 69 73 69 74 65 64 5F 73 65 63 6F 6E 64 61 72 79 5F 63 61 70 74 75 72 65 95 75 73 65 64 5F 73 6B 79 67 75 61 72 64 5F 77 65 61 70 6F 6E 73 91 76 69 73 69 74 65 64 5F 67 65 6E 65 72 61 74 6F 72 8B 75 73 65 64 5F 73 70 69 6B 65 72 8F 75 73 65 64 5F 73 75 70 70 72 65 73 73 6F 72 8C 75 73 65 64 5F 74 68 75 6D 70 65 72 89 75 73 65 64 5F 74 72 65 6B 98 76 69 73 69 74 65 64 5F 63 61 70 74 75 72 65 5F 74 65 72 6D 69 6E 61 6C 96 76 69 73 69 74 65 64 5F 6F 72 64 65 72 5F 74 65 72 6D 69 6E 61 6C 9F 76 69 73 69 74 65 64 5F 67 72 6F 75 6E 64 5F 76 65 68 69 63 6C 65 5F 74 65 72 6D 69 6E 61 6C 90 75 73 65 64 5F 76 73 68 65 76 5F 63 6F 6D 65 74 91 75 73 65 64 5F 76 73 68 65 76 5F 71 75 61 73 61 72 93 75 73 65 64 5F 76 73 68 65 76 5F 73 74 61 72 66 69 72 65 97 75 73 65 64 5F 76 75 6C 74 75 72 65 5F 62 6F 6D 62 61 72 64 69 65 72 98 75 73 65 64 5F 76 75 6C 74 75 72 65 5F 6E 6F 73 65 5F 63 61 6E 6E 6F 6E 98 75 73 65 64 5F 76 75 6C 74 75 72 65 5F 74 61 69 6C 5F 63 61 6E 6E 6F 6E 97 75 73 65 64 5F 77 61 73 70 5F 77 65 61 70 6F 6E 5F 73 79 73 74 65 6D 85 6D 61 70 39 39 85 6D 61 70 39 38 85 6D 61 70 39 37 85 6D 61 70 39 36 85 6D 61 70 31 35 85 6D 61 70 31 34 85 6D 61 70 31 33 85 6D 61 70 31 30 85 6D 61 70 30 39 85 6D 61 70 30 37 85 6D 61 70 30 36 85 6D 61 70 30 35 85 6D 61 70 30 34 85 6D 61 70 30 33 85 6D 61 70 30 32 85 6D 61 70 30 31 0B 00 00 00 8F 74 72 61 69 6E 69 6E 67 5F 61 72 6D 6F 72 73 97 74 72 61 69 6E 69 6E 67 5F 63 65 72 74 69 66 69 63 61 74 69 6F 6E 73 8D 74 72 61 69 6E 69 6E 67 5F 68 61 72 74 90 74 72 61 69 6E 69 6E 67 5F 68 65 61 6C 69 6E 67 91 74 72 61 69 6E 69 6E 67 5F 69 6D 70 6C 61 6E 74 73 92 74 72 61 69 6E 69 6E 67 5F 69 6E 76 65 6E 74 6F 72 79 8C 74 72 61 69 6E 69 6E 67 5F 6D 61 70 91 74 72 61 69 6E 69 6E 67 5F 76 65 68 69 63 6C 65 73 92 74 72 61 69 6E 69 6E 67 5F 77 61 72 70 67 61 74 65 73 92 74 72 61 69 6E 69 6E 67 5F 77 65 61 70 6F 6E 73 30 31 90 74 72 61 69 6E 69 6E 67 5F 77 65 6C 63 6F 6D 65 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 02 11 08 58 14 24 40 00 00 10 00 06 02 0E 20 C0 28 0C 80 00 00 20 06 41 0D 02 85 C8 00 00 02 00 E0").toString) -// sendRawResponse( -// PacketCoding.EncodePacket( -// ObjectCreateDetailedMessage( -// ObjectClass.avatar, -// PlanetSideGUID(75), -// DetailedCharacterData( -// CharacterAppearanceData( -// PlacementData( -// Vector3(2900.8125f,4818.8516f,58.78125f), -// Vector3(0.0f,0.0f,174.375f) -// ), -// BasicCharacterData("FateJH",PlanetSideEmpire.VS,CharacterGender.Male,23,2), -// 1, -// false, -// false, -// ExoSuitType.Agile, -// "", -// 23, -// false, -// 5.625f, 0.0f, -// false, -// GrenadeState.None, -// false, -// false, -// false, -// RibbonBars(MeritCommendation.EventVSSoldier,MeritCommendation.CombatMedic7,MeritCommendation.None,MeritCommendation.OneYearTR) -// ), -// 2286230, //BR23, 1xp from BR24 -// 100, 100, -// 100, -// 1,7,7, -// 100,100, -// 48,4, -// BattleRankFieldData(12,16,24,52,56,104,108,Some(112),Some(164),Some(168),Some(3),Some(1),Some(193),Some(48),Some(0),Some(0),None), -// List("xpe_battle_rank_8"), -// List(), -// Some( -// InventoryData(List( -// InternalSlot(32,PlanetSideGUID(363),0,DetailedACEData(8)), -// InternalSlot(728,PlanetSideGUID(364),1,DetailedREKData(8)), -// InternalSlot(706,PlanetSideGUID(365),2,DetailedWeaponData(4,8,List(InternalSlot(28,PlanetSideGUID(366),0,DetailedAmmoBoxData(8,30)), InternalSlot(413,PlanetSideGUID(367),1,DetailedAmmoBoxData(8,1))))(2)), -// InternalSlot(324,PlanetSideGUID(368),4,DetailedWeaponData(4,8,List(InternalSlot(540,PlanetSideGUID(369),0,DetailedAmmoBoxData(8,1))))) -// )) -// ), -// DrawnSlot.None -// ) -// ) -// ).require.toByteVector -// ) -// sendResponse(PacketCoding.CreateGamePacket(0, CharacterInfoMessage(15,PlanetSideZoneID(10000), 41605313, PlanetSideGUID(75), false, 6404428))) + log.info(s"${PacketCoding.DecodePacket(objectHex)}") + sendRawResponse(objectHex) + sendResponse(PacketCoding.CreateGamePacket(0, CharacterInfoMessage(15,PlanetSideZoneID(10000), 41605313, PlanetSideGUID(75), false, 6404428))) //load characters - SetCharacterSelectScreenGUID(player, gen) - val health = player.Health - val stamina = player.Stamina - val armor = player.Armor - player.Spawn - sendResponse(PacketCoding.CreateGamePacket(0, - ObjectCreateMessage(ObjectClass.avatar, player.GUID, player.Definition.Packet.ConstructorData(player).get) - )) - if(health > 0) { //player can not be dead; stay spawned as alive - player.Health = health - player.Stamina = stamina - player.Armor = armor - } - sendResponse(PacketCoding.CreateGamePacket(0, CharacterInfoMessage(15,PlanetSideZoneID(10000), 41605313, player.GUID, false, 6404428))) - RemoveCharacterSelectScreenGUID(player) +// SetCharacterSelectScreenGUID(player, gen) +// val health = player.Health +// val stamina = player.Stamina +// val armor = player.Armor +// player.Spawn +// sendResponse(PacketCoding.CreateGamePacket(0, +// ObjectCreateMessage(ObjectClass.avatar, player.GUID, player.Definition.Packet.ConstructorData(player).get) +// )) +// if(health > 0) { //player can not be dead; stay spawned as alive +// player.Health = health +// player.Stamina = stamina +// player.Armor = armor +// } +// sendResponse(PacketCoding.CreateGamePacket(0, CharacterInfoMessage(15,PlanetSideZoneID(10000), 41605313, player.GUID, false, 6404428))) +// RemoveCharacterSelectScreenGUID(player) sendResponse(PacketCoding.CreateGamePacket(0, CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0))) @@ -501,15 +453,16 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, LoadMapMessage("map13","home3",40100,25,true,3770441820L))) //VS Sanctuary //load the now-registered player tplayer.Spawn - sendResponse(PacketCoding.CreateGamePacket(0, - ObjectCreateDetailedMessage(ObjectClass.avatar, tplayer.GUID, tplayer.Definition.Packet.DetailedConstructorData(tplayer).get) - )) +// sendResponse(PacketCoding.CreateGamePacket(0, +// ObjectCreateDetailedMessage(ObjectClass.avatar, tplayer.GUID, tplayer.Definition.Packet.DetailedConstructorData(tplayer).get) +// )) + sendRawResponse(objectHex) avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.LoadPlayer(tplayer.GUID, tplayer.Definition.Packet.ConstructorData(tplayer).get)) log.debug(s"ObjectCreateDetailedMessage: ${tplayer.Definition.Packet.DetailedConstructorData(tplayer).get}") case SetCurrentAvatar(tplayer) => //avatar-specific - val guid = tplayer.GUID + val guid = PlanetSideGUID(75)//tplayer.GUID LivePlayerList.Assign(sessionId, guid) sendResponse(PacketCoding.CreateGamePacket(0, SetCurrentAvatarMessage(guid,0,0))) sendResponse(PacketCoding.CreateGamePacket(0, CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT))) @@ -542,6 +495,48 @@ class WorldSessionActor extends Actor with MDCContextAware { failWithError(s"Invalid packet class received: $default") } + val objectHex = hex"18 34 FF 00 00 BC 84 B0 00 90 76 BA 3F 94 60 E0 00 0B 04 40 00 08 30 46 00 4A 00 48 00 82 00 47 34 9E 80 80 00 00 00 00 00 3F FF C0 00 00 00 20 00 00 00 37 E3 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF AD E8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 E0 01 E0 00 64 00 00 01 00 7E C8 00 C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 C0 00 42 C6 86 C7 CA 41 F0 00 00 08 00 00 04 90 7870655F73616E6374756172795F68656C70 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 02 11 08 58 14 24 40 00 00 10 00 06 02 0E 20 C0 28 0C 80 00 00 20 06 41 0D 02 85 C8 00 00 02 00 E0" +// val objectHex = PacketCoding.CreateGamePacket(0, +// ObjectCreateDetailedMessage( +// 121, +// PlanetSideGUID(75), +// DetailedCharacterData( +// CharacterAppearanceData( +// PlacementData(Vector3(2931.5f,4404.6953f,45.0625f), Vector3(0.0f,0.0f,36.5625f)), +// BasicCharacterData("psemu2",PlanetSideEmpire.VS,CharacterGender.Male,20,3), +// 0, +// false, +// false, +// ExoSuitType.Standard, +// "", +// 15, +// false, +// 0.0f, 0.0f, +// false, +// GrenadeState.None, +// false, +// false, +// false, +// RibbonBars( +// MeritCommendation.None,MeritCommendation.None,MeritCommendation.None,MeritCommendation.None +// ) +// ), +// 31467, +// 0, +// 100, 100, +// 50, +// 1,7,7, +// 100,100, +// 7, +// CertificationEntries(List(0, 0, 11, 26, 27, 31, 41, 0, 0),Some(0)), +// List("xpe_blackops"), +// List(), +// None, +// DrawnSlot.None +// ) +// ) +// ) + def handlePkt(pkt : PlanetSidePacket) : Unit = pkt match { case ctrl : PlanetSideControlPacket => handleControlPkt(ctrl) @@ -988,6 +983,9 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ChangeAmmoMessage(item_guid, unk1) => log.info("ChangeAmmo: " + msg) + case msg @ AvatarImplantMessage(_, _, _, _) => //(player_guid, unk1, unk2, implant) => + log.info("AvatarImplantMessage: " + msg) + case msg @ UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType) => log.info("UseItem: " + msg) // TODO: Not all fields in the response are identical to source in real packet logs (but seems to be ok)