From 9566eb438124a35e360e81c74f61f106766a734f Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 31 Aug 2017 22:44:28 -0400 Subject: [PATCH 01/10] adjusted packet Codec to deal with players with BR19+ levels of BEP --- .../converter/AvatarConverter.scala | 7 +- .../objectcreate/DetailedCharacterData.scala | 373 +++++++++++++----- .../ObjectCreateDetailedMessageTest.scala | 15 +- 3 files changed, 287 insertions(+), 108 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 e94547b1..203dc2ac 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,8 +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.PlanetSideGUID -import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle} +import net.psforever.packet.game.objectcreate.{BasicCharacterData, BattleRankFieldData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle} import net.psforever.types.GrenadeState import scala.annotation.tailrec @@ -32,13 +31,15 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { Success( DetailedCharacterData( MakeAppearanceData(obj), + 0, obj.MaxHealth, obj.Health, obj.Armor, 1, 7, 7, obj.MaxStamina, obj.Stamina, - 28, 4, 44, 84, 104, 1900, + 28, 4, + BattleRankFieldData(44, 84, 104, 108, 112, 0, 0), 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/DetailedCharacterData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala index 34e6a6db..b34d0f38 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 @@ -2,10 +2,44 @@ package net.psforever.packet.game.objectcreate import net.psforever.packet.{Marshallable, PacketHelpers} -import scodec.{Attempt, Codec} +import scodec.{Attempt, Codec, Err} 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 { + override def bitsize : Long = { + val extraFieldSize : Long = if(field10.isDefined) { + 72L + } + else if(field0E.isDefined) { + 50L + } + else if(field09.isDefined) { + 10L + } + else { + 0L + } + 55L + extraFieldSize + } +} + /** * 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.
@@ -46,14 +80,7 @@ import shapeless.{::, HNil} * defaults to 28 * @param unk5 na; * defaults to 4 - * @param unk6 na; - * defaults to 44 - * @param unk7 na; - * defaults to 84 - * @param unk8 na; - * defaults to 104 - * @param unk9 na; - * defaults to 1900 + * @param brFields na * @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 @@ -68,6 +95,7 @@ import shapeless.{::, HNil} * @see `DrawnSlot` */ final case class DetailedCharacterData(appearance : CharacterAppearanceData, + bep : Int, healthMax : Int, health : Int, armor : Int, @@ -78,26 +106,24 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData, stamina : Int, unk4 : Int, //28 unk5 : Int, //4 - unk6 : Int, //44 - unk7 : Int, //84 - unk8 : Int, //104 - unk9 : Int, //1900 + brFields : BattleRankFieldData, firstTimeEvents : List[String], tutorials : List[String], inventory : Option[InventoryData], drawn_slot : DrawnSlot.Value = DrawnSlot.None - ) extends ConstructorData { + ) extends ConstructorData { override def bitsize : Long = { //factor guard bool values into the base size, not its corresponding optional field val appearanceSize = appearance.bitsize + val brFieldSize = brFields.bitsize val fteLen = firstTimeEvents.size //fte list - var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen) + var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen, bep) for(str <- firstTimeEvents) { eventListSize += StreamBitSize.stringBitSize(str) } val tutLen = tutorials.size //tutorial list - var tutorialListSize : Long = 32L + DetailedCharacterData.tutPadding(fteLen, tutLen) + var tutorialListSize : Long = 32L + DetailedCharacterData.tutPadding(fteLen, tutLen, bep) for(str <- tutorials) { tutorialListSize += StreamBitSize.stringBitSize(str) } @@ -105,28 +131,28 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData, if(inventory.isDefined) { inventorySize = inventory.get.bitsize } - 713L + appearanceSize + eventListSize + tutorialListSize + inventorySize + 658L + appearanceSize + brFieldSize + 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, 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, 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 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. @@ -142,97 +168,244 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { * @param unk4 na * @param unk5 na * @param unk6 na - * @param unk7 na - * @param unk8 na - * @param unk9 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, healthMax : Int, health : Int, armor : Int, unk1 : Int, unk2 : Int, unk3 : Int, staminaMax : Int, stamina : Int, unk4 : Int, unk5 : Int, unk6 : Int, unk7 : Int, unk8 : Int, unk9 : Int, firstTimeEvents : List[String], tutorials : List[String], inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedCharacterData = - new DetailedCharacterData(appearance, healthMax, health, armor, unk1, unk2, unk3, staminaMax, stamina, unk4, unk5, unk6, unk7, unk8, unk9, firstTimeEvents, tutorials, Some(inventory), drawn_slot) + 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) + + private val br1FieldCodec : Codec[BattleRankFieldData] = ( // +0u + ("f1" | uint8L) :: + ("f2" | uint8L) :: + ("f3" | uint8L) :: + ("f4" | uint8L) :: + ("f5" | uint8L) :: + ("f6" | uint8L) :: + ("f7" | uintL(7)) + ).exmap[BattleRankFieldData] ( + { + case f1 :: f2 :: f3 :: f4 :: f5 :: f6 :: f7 :: HNil => + Attempt.successful(BattleRankFieldData(f1, f2, f3, f4, f5, f6, f7)) + }, + { + case BattleRankFieldData(f1, f2, f3, f4, f5, f6, f7, _, _, _, _, _, _, _, _, _, _) => + Attempt.successful(f1 :: f2 :: f3 :: f4 :: f5 :: f6 :: f7 :: 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" | uint8L) :: + ("f11" | bool) + ).exmap[BattleRankFieldData] ( + { + case f01 :: f02 :: f03 :: f04 :: f05 :: f06 :: f07 :: f08 :: f09 :: f0a :: f0b :: f0c :: f0d :: f0e :: f0f :: f10 :: f11:: HNil => + val f11Int : Int = if(f11) { 1 } else { 0 } + 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), Some(f11Int))) + }, + { + 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), Some(f11)) => + val f11Bool : Boolean = if(f11 == 0) { false } else { true } + Attempt.successful(f01 :: f02 :: f03 :: f04 :: f05 :: f06 :: f07 :: f08 :: f09 :: f0a :: f0b :: f0c :: f0d :: f0e :: f0f :: f10 :: f11Bool :: HNil) + case _ => + Attempt.failure(Err("expected battle rank 18 field data")) + } + ) /** - * Get the padding of the first entry in the first time events list. - * The padding will always be a number 0-7. - * @param len the length of the list - * @return the pad length in bits + * na + * @param bep the battle experience points + * @return the appropriate `Codec` for the fields representing a player with the implied battle rank */ - private def ftePadding(len : Long) : Int = { - //TODO the parameters for this function are not correct - //TODO the proper padding length should reflect all variability in the stream prior to this point - if(len > 0) { - 5 + private def selectBattleRankFieldCodec(bep : Int) : Codec[BattleRankFieldData] = { + if(bep > 754370) { + br18FieldCodec + } + else if(bep > 197753) { + br12FieldCodec + } + else if(bep > 29999) { + br6FieldCodec + } + else { + br1FieldCodec } - else - 0 } /** - * Get the padding of the first entry in the completed tutorials list. - * The padding will always be a number 0-7.
- *
- * The tutorials list follows the first time event list and that contains byte-aligned strings too. - * While there will be more to the padding, this other list is important. - * Any elements in that list causes the automatic byte-alignment of this list's first entry. - * @param len the length of the list - * @return the pad length in bits + * 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` */ - private def tutPadding(len : Long, len2 : Long) : Int = { - if(len > 0) //automatic alignment from previous List - 0 - else if(len2 > 0) //need to align for elements + private def bepFieldPadding(bep : Int) : Int = { + if(bep > 754370) { //BR18+ + 7 + } + else if(bep > 197753) { //BR12+ + 1 + } + else if(bep > 29999) { //BR6+ + 3 + } + else { //BR1+ 5 - else //both lists are empty + } + } + + /** + * 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` + */ + private def ftePadding(len : Long, bep : Int) : Int = { + //TODO the parameters for this function are not correct + //TODO the proper padding length should reflect all variability in the stream prior to this point + if(len > 0) { + bepFieldPadding(bep) + } + else { 0 + } + } + + /** + * Get the padding of the first entry in the completed tutorials list.
+ *
+ * The tutorials list follows the first time event list and also contains byte-aligned strings. + * If the both lists are populated or empty at the same time, the first entry will not need padding. + * If the first time events list is unpopulated, but this list is populated, the first entry will need padding bits. + * @param len the length of the list + * @param bep the battle experience points + * @return the pad length in bits `n < 8` + */ + private def tutPadding(len : Long, len2 : Long, bep : Int) : Int = { + if(len > 0) { + //automatic alignment from previous List + 0 + } + else if(len2 > 0) { + //need to align for elements + bepFieldPadding(bep) + } + else { + //both lists are empty + 0 + } } implicit val codec : Codec[DetailedCharacterData] = ( ("appearance" | CharacterAppearanceData.codec) :: - ignore(160) :: - ("healthMax" | uint16L) :: - ("health" | uint16L) :: - ignore(1) :: - ("armor" | uint16L) :: - ignore(9) :: - ("unk1" | uint8L) :: - ignore(8) :: - ("unk2" | uint4L) :: - ("unk3" | uintL(3)) :: - ("staminaMax" | uint16L) :: - ("stamina" | uint16L) :: - ignore(149) :: - ("unk4" | uint16L) :: - ("unk5" | uint8L) :: - ("unk6" | uint8L) :: - ("unk7" | uint8L) :: - ("unk8" | uint8L) :: - ("unk9" | uintL(12)) :: - ignore(19) :: - (("firstTimeEvent_length" | uint32L) >>:~ { len => - conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned( ftePadding(len) )) :: - ("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) :: - (("tutorial_length" | uint32L) >>:~ { len2 => - conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned( tutPadding(len, len2) )) :: - ("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) :: - ignore(207) :: - optional(bool, "inventory" | InventoryData.codec_detailed) :: - ("drawn_slot" | DrawnSlot.codec) :: - bool //usually false - }) + (("bep" | uint24L) >>:~ { bep => + ignore(136) :: + ("healthMax" | uint16L) :: + ("health" | uint16L) :: + ignore(1) :: + ("armor" | uint16L) :: + ignore(9) :: + ("unk1" | uint8L) :: + ignore(8) :: + ("unk2" | uint4L) :: + ("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 + }) + }) }) ).exmap[DetailedCharacterData] ( { - case app :: _ :: b :: c :: _ :: d :: _ :: e :: _ :: f :: g :: h :: i :: _ :: j :: k :: l :: m :: n :: o :: _ :: _ :: q :: r :: _ :: t :: u :: _ :: v :: w :: false :: HNil => + case app :: bep :: _ :: hpmax :: hp :: _ :: armor :: _ :: u1 :: _ :: u2 :: u3 :: stamax :: stam :: _ :: u4 :: u5 :: brFields :: _ :: fte0 :: fte1 :: _ :: tut0 :: tut1 :: _ :: inv :: drawn :: false :: HNil => //prepend the displaced first elements to their lists - val fteList : List[String] = if(q.isDefined) { q.get :: r } else r - val tutList : List[String] = if(t.isDefined) { t.get :: u } else u - Attempt.successful(DetailedCharacterData(app, b, c, d, e, f, g, h, i, j, k, l, m, n, o, fteList, tutList, v, w)) + 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)) }, { - case DetailedCharacterData(app, b, c, d, e, f, g, h, i, j, k, l, m, n, o, fteList, tutList, p, q) => + case DetailedCharacterData(app, bep, hpmax, hp, armor, u1, u2, u3, stamax, stam, u4, u5, brFields, fteList, tutList, inv, drawn) => //shift the first elements off their lists var fteListCopy = fteList var firstEvent : Option[String] = None @@ -246,7 +419,7 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { firstTutorial = Some(tutList.head) tutListCopy = tutList.drop(1) } - Attempt.successful(app :: () :: b :: c :: () :: d :: () :: e :: () :: f :: g :: h :: i :: () :: j :: k :: l :: m :: n :: o :: () :: fteList.size.toLong :: firstEvent :: fteListCopy :: tutList.size.toLong :: firstTutorial :: tutListCopy :: () :: p :: q :: false :: HNil) + 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) } ) } diff --git a/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala b/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala index dfccf584..188fd5d5 100644 --- a/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala +++ b/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala @@ -216,10 +216,13 @@ class ObjectCreateDetailedMessageTest extends Specification { char.stamina mustEqual 100 char.unk4 mustEqual 28 char.unk5 mustEqual 4 - char.unk6 mustEqual 44 - char.unk7 mustEqual 84 - char.unk8 mustEqual 104 - char.unk9 mustEqual 1900 + 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.firstTimeEvents.size mustEqual 4 char.firstTimeEvents.head mustEqual "xpe_sanctuary_help" char.firstTimeEvents(1) mustEqual "xpe_th_firemodes" @@ -405,11 +408,13 @@ class ObjectCreateDetailedMessageTest extends Specification { Nil val obj = DetailedCharacterData( app, + 0, 100, 100, 50, 1, 7, 7, 100, 100, - 28, 4, 44, 84, 104, 1900, + 28, 4, + BattleRankFieldData(44, 84, 104, 108, 112, 0, 0), "xpe_sanctuary_help" :: "xpe_th_firemodes" :: "used_beamer" :: "map13" :: Nil, List.empty, InventoryData(inv), From 2b70b98e350ab4c24a0496e187bc9df7e2dd77f5 Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 1 Sep 2017 20:56:11 -0400 Subject: [PATCH 02/10] checked BR6, BR12, and BR18 field Codecs against bep values; corrected extra field in BR18 Codec --- .../objectcreate/DetailedCharacterData.scala | 15 +++--- .../src/main/scala/WorldSessionActor.scala | 51 +++++++++++++++++++ 2 files changed, 57 insertions(+), 9 deletions(-) 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 b34d0f38..6d1c940d 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 @@ -25,7 +25,7 @@ final case class BattleRankFieldData(field00 : Int, field10 : Option[Int] = None) extends StreamBitSize { override def bitsize : Long = { val extraFieldSize : Long = if(field10.isDefined) { - 72L + 70L } else if(field0E.isDefined) { 50L @@ -265,18 +265,15 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { ("f0D" | uint8L) :: ("f0E" | uint8L) :: ("f0F" | uint8L) :: - ("f10" | uint8L) :: - ("f11" | bool) + ("f10" | uintL(5)) ).exmap[BattleRankFieldData] ( { - case f01 :: f02 :: f03 :: f04 :: f05 :: f06 :: f07 :: f08 :: f09 :: f0a :: f0b :: f0c :: f0d :: f0e :: f0f :: f10 :: f11:: HNil => - val f11Int : Int = if(f11) { 1 } else { 0 } - 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), Some(f11Int))) + 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), Some(f11)) => - val f11Bool : Boolean = if(f11 == 0) { false } else { true } - Attempt.successful(f01 :: f02 :: f03 :: f04 :: f05 :: f06 :: f07 :: f08 :: f09 :: f0a :: f0b :: f0c :: f0d :: f0e :: f0f :: f10 :: f11Bool :: HNil) + 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")) } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 9a3e3931..18a4a9fa 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -394,6 +394,57 @@ 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))) //load characters SetCharacterSelectScreenGUID(player, gen) val health = player.Health From 4c5e67ca89c73886d41ad5cb579ad5ac4cb73cd0 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 5 Sep 2017 19:22:48 -0400 Subject: [PATCH 03/10] 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 203dc2ac..e42c2214 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 66380b62..92dd6e6b 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 6d1c940d..89678276 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 3449c0e4..acd47b5b 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 188fd5d5..d585e60a 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 18a4a9fa..0063b8b1 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) From db8d3fc7ca4ae6607fabf53f17c4416f1180b63f Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 5 Sep 2017 21:27:53 -0400 Subject: [PATCH 04/10] optional field spacing data (CCRIDER sample) --- .../packet/game/objectcreate/DetailedCharacterData.scala | 7 ++++--- pslogin/src/main/scala/WorldSessionActor.scala | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) 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 89678276..ffae12a5 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 @@ -286,7 +286,8 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { ("stamina" | uint16L) :: ignore(147) :: ("certs" | listOfN(uint8L, uint8L)) :: - ignore(5) :: + optional(bool, uint32L) :: //ask about sample CCRIDER + ignore(4) :: (("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants => ignore(12) :: (("firstTimeEvent_length" | uint32L) >>:~ { len => @@ -306,7 +307,7 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { } ).exmap[DetailedCharacterData] ( { - case app :: bep :: cep :: _ :: hpmax :: hp :: _ :: armor :: _ :: u1 :: _ :: u2 :: u3 :: stamax :: stam :: _ :: certs :: _ :: implants :: _ :: _ :: 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 @@ -334,7 +335,7 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { firstTutorial = Some(tutList.head) tutListCopy = tutList.tail } - 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) + 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 :: () :: inv :: drawn :: false :: HNil) } ) } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 0063b8b1..8013b820 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -495,7 +495,7 @@ 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 = hex"18 4a c7 00 00 bc 84 B0 0c 0b 95 59 9a 84 40 b0 00 01 32 00 00 08 70 43 00 43 00 52 00 49 00 44 00 45 00 52 00 82 28 c9 3d 04 40 03 c0 01 40 02 80 00 40 35 18 40 00 25 40 42 00 6c 00 61 00 63 00 6b 00 20 00 41 00 72 00 6d 00 6f 00 72 00 65 00 64 00 20 00 52 00 65 00 61 00 70 00 65 00 72 00 73 00 0f 00 00 03 02 0c 00 00 03 88 00 00 00 d4 00 00 01 9c 04 00 00 09 19 90 02 04 3c 28 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 64 00 00 01 00 7e c8 00 c8 00 00 01 b9 53 4c 00 00 00 00 00 00 00 00 00 00 00 00 00 03 40 00 40 81 c6 86 c8 48 88 c9 09 49 8a 67 86 e0 00 01 25 e0 32 d8 09 6c 00 00 3c 04 00 02 30 7870655f626c61636b6f7073917870655f626174746c655f72616e6b5f39927870655f626174746c655f72616e6b5f3233927870655f636f6d6d616e645f72616e6b5f318f7870655f666f726d5f6f75746669748e7870655f6d61696c5f616c657274927870655f626174746c655f72616e6b5f3232927870655f636f6d6d616e645f72616e6b5f33907870655f666f726d5f706c61746f6f6e927870655f626174746c655f72616e6b5f3133927870655f626174746c655f72616e6b5f3132927870655f626174746c655f72616e6b5f3130927870655f626174746c655f72616e6b5f3134927870655f626174746c655f72616e6b5f3135917870655f626174746c655f72616e6b5f38927870655f626174746c655f72616e6b5f3131927870655f626174746c655f72616e6b5f3231927870655f626174746c655f72616e6b5f3230927870655f626174746c655f72616e6b5f3138927870655f626174746c655f72616e6b5f3139907870655f6a6f696e5f706c61746f6f6e927870655f626174746c655f72616e6b5f3137927870655f626174746c655f72616e6b5f31368f7870655f6a6f696e5f6f7574666974927870655f626174746c655f72616e6b5f3235927870655f626174746c655f72616e6b5f3234917870655f626174746c655f72616e6b5f37917870655f62696e645f666163696c6974798c7870655f62696e645f616d73927870655f636f6d6d616e645f72616e6b5f35927870655f636f6d6d616e645f72616e6b5f32927870655f696e7374616e745f616374696f6e8e7870655f666f726d5f7371756164917870655f626174746c655f72616e6b5f34937870655f776172705f676174655f7573616765917870655f626174746c655f72616e6b5f32917870655f626174746c655f72616e6b5f33927870655f73616e6374756172795f68656c708c7870655f64726f705f706f64937870655f6f72626974616c5f73687574746c65917870655f626174746c655f72616e6b5f368e7870655f6a6f696e5f7371756164917870655f626174746c655f72616e6b5f35927870655f74685f737769746368626c6164658d7870655f74685f726f757465728c7870655f74685f666c61696c8a7870655f74685f616e748a7870655f74685f616d738a7870655f74685f62667289757365645f6f69637791757365645f616476616e6365645f61636597766973697465645f73706974666972655f74757272657498766973697465645f73706974666972655f636c6f616b656493766973697465645f73706974666972655f616192766973697465645f74616e6b5f7472617073a1766973697465645f706f727461626c655f6d616e6e65645f7475727265745f6e638e757365645f6d616763757474657293766973697465645f77616c6c5f7475727265748b766973697465645f616d738b766973697465645f616e7490766973697465645f64726f707368697091766973697465645f6c6962657261746f7294766973697465645f6c6967687467756e7368697091766973697465645f6c696768746e696e6790766973697465645f6d616772696465728f766973697465645f70726f776c657293766973697465645f71756164737465616c746890766973697465645f736b7967756172649a766973697465645f74687265656d616e686561767962756767799d766973697465645f74776f5f6d616e5f61737361756c745f627567677998766973697465645f74776f6d616e6865617679627567677998766973697465645f74776f6d616e686f766572627567677990766973697465645f76616e67756172648d766973697465645f666c61696c8e766973697465645f726f7574657293766973697465645f737769746368626c6164658e766973697465645f6175726f726193766973697465645f626174746c657761676f6e8c766973697465645f6675727993766973697465645f7175616461737361756c7496766973697465645f67616c6178795f67756e736869708e766973697465645f6170635f74728e766973697465645f6170635f767390766973697465645f6c6f64657374617290766973697465645f7068616e7461736d91766973697465645f7468756e64657265728e766973697465645f6170635f6e638f766973697465645f76756c747572658c766973697465645f7761737090766973697465645f6d6f73717569746f97766973697465645f617068656c696f6e5f666c6967687497766973697465645f617068656c696f6e5f67756e6e657297766973697465645f636f6c6f737375735f666c6967687497766973697465645f636f6c6f737375735f67756e6e657298766973697465645f706572656772696e655f666c6967687498766973697465645f706572656772696e655f67756e6e657289757365645f62616e6b95766973697465645f7265736f757263655f73696c6f9e766973697465645f63657274696669636174696f6e5f7465726d696e616c94766973697465645f6d65645f7465726d696e616c93757365645f6e616e6f5f64697370656e73657295766973697465645f73656e736f725f736869656c649a766973697465645f62726f6164636173745f77617270676174658c757365645f7068616c616e7894757365645f7068616c616e785f6176636f6d626f96757365645f7068616c616e785f666c616b636f6d626f96766973697465645f77617270676174655f736d616c6c91757365645f666c616d657468726f7765729a757365645f616e6369656e745f7475727265745f776561706f6e92766973697465645f4c4c555f736f636b657492757365645f656e657267795f67756e5f6e6397766973697465645f6d656469756d7472616e73706f72749f757365645f617068656c696f6e5f696d6d6f6c6174696f6e5f63616e6e6f6e93757365645f6772656e6164655f706c61736d6193757365645f6772656e6164655f6a616d6d657298766973697465645f736869656c645f67656e657261746f7295766973697465645f6d6f74696f6e5f73656e736f7296766973697465645f6865616c74685f6372797374616c96766973697465645f7265706169725f6372797374616c97766973697465645f76656869636c655f6372797374616c91757365645f6772656e6164655f6672616788757365645f61636598766973697465645f6164765f6d65645f7465726d696e616c8b757365645f6265616d657290757365645f626f6c745f6472697665728b757365645f6379636c65728a757365645f676175737391757365645f68756e7465727365656b657288757365645f6973708b757365645f6c616e6365728b757365645f6c61736865728e757365645f6d61656c7374726f6d8c757365645f70686f656e69788b757365645f70756c7361728d757365645f70756e69736865728e757365645f725f73686f7467756e8d757365645f7261646961746f7288757365645f72656b8d757365645f72657065617465728c757365645f726f636b6c65748c757365645f737472696b65728f757365645f73757070726573736f728c757365645f7468756d7065729c766973697465645f76616e755f636f6e74726f6c5f636f6e736f6c6598766973697465645f636170747572655f7465726d696e616c92757365645f6d696e695f636861696e67756e91757365645f6c617a655f706f696e7465728c757365645f74656c657061648b757365645f7370696b657291757365645f68656176795f736e6970657293757365645f636f6d6d616e645f75706c696e6b8d757365645f66697265626972648e757365645f666c6563686574746594757365645f68656176795f7261696c5f6265616d89757365645f696c63399a766973697465645f67656e657261746f725f7465726d696e616c8e766973697465645f6c6f636b65729a766973697465645f65787465726e616c5f646f6f725f6c6f636b9c766973697465645f6169725f76656869636c655f7465726d696e616c97766973697465645f67616c6178795f7465726d696e616c98766973697465645f696d706c616e745f7465726d696e616c99766973697465645f7365636f6e646172795f6361707475726590757365645f32356d6d5f63616e6e6f6e99757365645f6c6962657261746f725f626f6d6261726469657293766973697465645f7265706169725f73696c6f93766973697465645f76616e755f6d6f64756c6591757365645f666c61696c5f776561706f6e8b757365645f73637974686598766973697465645f7265737061776e5f7465726d696e616c8c757365645f62616c6c67756e95757365645f616e6e69766572736172795f67756e6195757365645f616e6e69766572736172795f67756e6294757365645f616e6e69766572736172795f67756e90757365645f37356d6d5f63616e6e6f6e92757365645f6170635f6e635f776561706f6e92757365645f6170635f74725f776561706f6e92757365645f6170635f76735f776561706f6e90757365645f666c75785f63616e6e6f6e93757365645f617068656c696f6e5f6c617365729f757365645f617068656c696f6e5f706c61736d615f726f636b65745f706f6491757365645f617068656c696f6e5f70706196757365645f617068656c696f6e5f73746172666972658c757365645f666c7578706f6494766973697465645f6266725f7465726d696e616c95757365645f636f6c6f737375735f6275727374657296757365645f636f6c6f737375735f636861696e67756e9e757365645f636f6c6f737375735f636c75737465725f626f6d625f706f64a0757365645f636f6c6f737375735f6475616c5f3130306d6d5f63616e6e6f6e7399757365645f636f6c6f737375735f74616e6b5f63616e6e6f6e96766973697465645f64616d6167655f6372797374616c96766973697465645f656e657267795f6372797374616c9b757365645f68656176795f6772656e6164655f6c61756e6368657298757365645f33356d6d5f726f74617279636861696e67756e8b757365645f6b6174616e6190757365645f33356d6d5f63616e6e6f6e93757365645f7265617665725f776561706f6e7396757365645f6c696768746e696e675f776561706f6e738c757365645f6d65645f61707090757365645f32306d6d5f63616e6e6f6e98766973697465645f6d6f6e6f6c6974685f616d657269736899766973697465645f6d6f6e6f6c6974685f636572797368656e97766973697465645f6d6f6e6f6c6974685f637973736f7297766973697465645f6d6f6e6f6c6974685f6573616d697299766973697465645f6d6f6e6f6c6974685f666f72736572616c97766973697465645f6d6f6e6f6c6974685f686f7373696e99766973697465645f6d6f6e6f6c6974685f697368756e64617298766973697465645f6d6f6e6f6c6974685f7365617268757397766973697465645f6d6f6e6f6c6974685f736f6c73617292757365645f6e635f6865765f66616c636f6e99757365645f6e635f6865765f7363617474657263616e6e6f6e93757365645f6e635f6865765f73706172726f7791757365645f61726d6f725f736970686f6e9f757365645f706572656772696e655f6475616c5f6d616368696e655f67756e9f757365645f706572656772696e655f6475616c5f726f636b65745f706f647399757365645f706572656772696e655f6d65636868616d6d65728f757365645f6e74755f736970686f6e9e757365645f706572656772696e655f7061727469636c655f63616e6e6f6e96757365645f706572656772696e655f73706172726f7791757365645f3130356d6d5f63616e6e6f6e92757365645f31356d6d5f636861696e67756ea0757365645f70756c7365645f7061727469636c655f616363656c657261746f7293757365645f726f74617279636861696e67756e9f766973697465645f6465636f6e737472756374696f6e5f7465726d696e616c95757365645f736b7967756172645f776561706f6e7391766973697465645f67656e657261746f7291757365645f67617573735f63616e6e6f6e89757365645f7472656b95757365645f76616e67756172645f776561706f6e73a4766973697465645f616e6369656e745f6169725f76656869636c655f7465726d696e616ca2766973697465645f616e6369656e745f65717569706d656e745f7465726d696e616c96766973697465645f6f726465725f7465726d696e616ca7766973697465645f616e6369656e745f67726f756e645f76656869636c655f7465726d696e616c9f766973697465645f67726f756e645f76656869636c655f7465726d696e616c97757365645f76756c747572655f626f6d6261726469657298757365645f76756c747572655f6e6f73655f63616e6e6f6e98757365645f76756c747572655f7461696c5f63616e6e6f6e97757365645f776173705f776561706f6e5f73797374656d91766973697465645f636861726c6965303591766973697465645f636861726c6965303691766973697465645f636861726c6965303791766973697465645f636861726c6965303891766973697465645f636861726c696530398e766973697465645f736c656430348e766973697465645f736c656430358e766973697465645f736c656430368e766973697465645f736c656430378e766973697465645f736c65643038857567643036857567643035857567643034857567643033857567643032857567643031856d61703939856d61703938856d61703937856d61703936856d61703135856d61703134856d61703133856d61703132856d61703131856d61703034856d61703035856d61703033856d61703031856d61703036856d61703032856d61703039856d61703037856d617031300800000092747261696e696e675f776561706f6e73303192747261696e696e675f7761727067617465738c747261696e696e675f6d617092747261696e696e675f696e76656e746f727990747261696e696e675f6865616c696e678d747261696e696e675f686172748f747261696e696e675f61726d6f727390747261696e696e675f77656c636f6d650000000000000000000000000000000000000000ab000480000005000000058000000800000008800000090000000f0000000f8000001000000012800000130000001a0000001a8000001c800000228000002e00000031800000320000003580000036000000368000003700000039000000398000003a0000003a8000003b0000003f800000400000004080000041000000418000004e000000518000005580000059000000598000005a0000005a800000610000006180000063000000648000006b8000006c0000006c8000006f0000006f800000700000007080000071000000718000007280000073000000798000007a0000000080800004808000050080000a8080000b0080000d8080000e0080000e8080000f0080001080800011008000140080001c808000270080002a8080002b00800033808000388080003c0080003c8080004000800043808000440080004480800045008000470080004c0080004f808000520080005400800000002f0a26c9313c024000001000060279a272780c80000100026b271682480000020000c041c0464f019000006400d43541d08900000040001808388946a0320000008019041c821700000008046ce915631018000000400018083822c6a030000019001d2338421b00000008000301070438ac060000032003a4630c466000000100006020e06c1e80c000006400748e01c92c00000020000c041c11e4101800000c800e90a02331800000040001808381b06e030000019001989ec436b000000080003010c81d924060000019003310e87f0600000010000602190d31780c000003200662fa22e6c00000020000c04320a82f018000006400e90683bd9800000040001808381205a030000019000205808bcb000001900040e4908058e0000032006629c1d00b4c00000020000c043209c35018000006400cc480320175800000040001808642084a03000000c80198880540303000000080003010c87d86c060000019000b17e9100636000000100006020e01f2080c00000c800081580f010bc000006400414961a022f800000040001808de0004603000003200082818840477000000080003011bc0a11406000006400040a28680b2e000003200569271a017ac00000020000c041037435018000008c00ce2e04003498000000400018099e6c46e03000004b0019c4483406b70000000800030133cc28ec06000009600040ad8e80dfe0000032006710e1201fec00000020000c04cf2a837018000025800ce37432040f8000000400018099e7586a03000004b00058b18480843000000080003010702f11406000006400040f690010ce0000032000e0222786c8000064001c1d82b13900000c800380205233200001900074559050e40000320079afa0fa4c800002000d84b64f4e9000000400010300380" // val objectHex = PacketCoding.CreateGamePacket(0, // ObjectCreateDetailedMessage( // 121, From 69af5124ba5941be065f326ad6a98c8610776efa Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 6 Sep 2017 08:26:28 -0400 Subject: [PATCH 05/10] added an Enumeration of certifications --- .../converter/AvatarConverter.scala | 4 +- .../objectcreate/DetailedCharacterData.scala | 29 +++--- .../psforever/types/CertificationType.scala | 79 ++++++++++++++++ .../ObjectCreateDetailedMessageTest.scala | 24 ++++- .../src/main/scala/WorldSessionActor.scala | 94 ++++++++++--------- 5 files changed, 166 insertions(+), 64 deletions(-) create mode 100644 common/src/main/scala/net/psforever/types/CertificationType.scala 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 e42c2214..6c97c17c 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 @@ -28,6 +28,7 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { } override def DetailedConstructorData(obj : Player) : Try[DetailedCharacterData] = { + import net.psforever.types.CertificationType._ Success( DetailedCharacterData( MakeAppearanceData(obj), @@ -36,10 +37,9 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { obj.MaxHealth, obj.Health, obj.Armor, - 1, 7, 7, obj.MaxStamina, obj.Stamina, - List(0, 1, 11, 21, 26, 27, 28), //TODO certification list + List(StandardAssault, MediumAssault, ATV, Harasser, StandardExoSuit, AgileExoSuit, ReinforcedExoSuit), //TODO certification list List(), //TODO implant list List.empty[String], //TODO fte list List.empty[String], //TODO tutorial list 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 ffae12a5..e678c3f1 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 @@ -3,7 +3,7 @@ package net.psforever.packet.game.objectcreate import net.psforever.newcodecs.newcodecs import net.psforever.packet.{Marshallable, PacketHelpers} -import net.psforever.types.ImplantType +import net.psforever.types.{CertificationType, ImplantType} import scodec.{Attempt, Codec} import scodec.codecs._ import shapeless.{::, HNil} @@ -12,9 +12,9 @@ import scala.annotation.tailrec /** * An entry in the `List` of valid implant slots in `DetailedCharacterData`. - * "`activation`" is not necessarily the best word for it ... + * `activation`, if defined, indicates the time remaining (in seconds?) before an implant can be activated. * @param implant the type of implant - * @param activation na + * @param activation the activation timer * @see `ImplantType` */ final case class ImplantEntry(implant : ImplantType.Value, @@ -87,7 +87,7 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData, unk3 : Int, //7 staminaMax : Int, stamina : Int, - certs : List[Int], + certs : List[CertificationType.Value], implants : List[ImplantEntry], firstTimeEvents : List[String], tutorials : List[String], @@ -98,13 +98,12 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData, 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 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 implantPadding = DetailedCharacterData.implantFieldPadding(implants, CharacterAppearanceData.altModelBit(appearance)) val fteLen = firstTimeEvents.size //fte list var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen, implantPadding) for(str <- firstTimeEvents) { @@ -127,28 +126,28 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData, object DetailedCharacterData extends Marshallable[DetailedCharacterData] { /** - * Overloaded constructor for `DetailedCharacterData` that allows for a not-optional inventory. + * 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 * @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 unk1 na - * @param unk2 na - * @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 certs 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 * @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, 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) + 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], inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedCharacterData = + new DetailedCharacterData(appearance, bep, cep, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, certs, implants, firstTimeEvents, tutorials, Some(inventory), drawn_slot) /** - * `Codec` for entires in the list of implants. + * `Codec` for entries in the `List` of implants. */ private val implant_entry_codec : Codec[ImplantEntry] = ( ("implant" | ImplantType.codec) :: @@ -285,7 +284,7 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { ("staminaMax" | uint16L) :: ("stamina" | uint16L) :: ignore(147) :: - ("certs" | listOfN(uint8L, uint8L)) :: + ("certs" | listOfN(uint8L, CertificationType.codec)) :: optional(bool, uint32L) :: //ask about sample CCRIDER ignore(4) :: (("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants => diff --git a/common/src/main/scala/net/psforever/types/CertificationType.scala b/common/src/main/scala/net/psforever/types/CertificationType.scala new file mode 100644 index 00000000..d06e2d19 --- /dev/null +++ b/common/src/main/scala/net/psforever/types/CertificationType.scala @@ -0,0 +1,79 @@ +// Copyright (c) 2017 PSForever +package net.psforever.types + +import net.psforever.packet.PacketHelpers +import scodec.codecs._ +/** + * An `Enumeration` of the available certifications.
+ *
+ * As indicated, the following certifications are always enqueued on an avatar's permissions: + * `StandardAssault`, `StandardExoSuit`, `AgileExoSuit`. + * They must still be included in any formal lists of permitted equipment for a user. + * The other noted certifications require all prerequisite certifications listed or they themselves will not be listed: + * `ElectronicsExpert` and `AdvancedEngineering`. + * No other certification requires its prerequisites explicitly listed to be listed itself. + * Any certification that contains multiple other certifications overrides those individual certifications in the list. + * There is no certification for the Advanced Nanite Transport.
+ *
+ * In terms of pricing, `StandardAssault`, `StandardExoSuit`, and `AgileExoSuit` are costless. + * A certification that contains multiple other certifications acts as the overriding cost. + * (Taking `UniMAX` while owning `AAMAX` will refund the `AAMAX` cost and replace it with the `UniMAX` cost.) + */ +object CertificationType extends Enumeration { + type Type = Value + val + //0 + StandardAssault, //always listed + MediumAssault, + HeavyAssault, + SpecialAssault, + AntiVehicular, + Sniping, + EliteAssault, + AirCalvaryScout, + AirCalvaryInterceptor, + AirCalvaryAssault, + //10 + AirSupport, + ATV, + LightScout, + AssaultBuggy, + ArmoredAssault1, + ArmoredAssault2, + GroundTransport, + GroundSupport, + BattleFrameRobotics, + Flail, + //20 + Switchblade, + Harasser, + Phantasm, + GalaxyGunship, + BFRAntiAircraft, + BFRAntiInfantry, + StandardExoSuit, //always listed + AgileExoSuit, //always listed + ReinforcedExoSuit, + InfiltrationSuit, + //30 + AAMAX, + AIMAX, + AVMAX, + UniMAX, + Medical, + AdvancedMedical, + Hacking, + AdvancedHacking, + ExpertHacking, + DataCorruption, + //40 + ElectronicsExpert, //requires Hacking and AdvancedHacking + Engineering, + CombatEngineering, + FortificationEngineering, + AssaultEngineering, + AdvancedEngineering //requires Engineering and CombatEngineering + = Value + + implicit val codec = PacketHelpers.createEnumerationCodec(this, uint8L) +} diff --git a/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala b/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala index d585e60a..f6163520 100644 --- a/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala +++ b/common/src/test/scala/game/ObjectCreateDetailedMessageTest.scala @@ -206,6 +206,8 @@ class ObjectCreateDetailedMessageTest extends Specification { 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 @@ -214,7 +216,15 @@ class ObjectCreateDetailedMessageTest extends Specification { char.unk3 mustEqual 7 char.staminaMax mustEqual 100 char.stamina mustEqual 100 - char.certs mustEqual List(0, 1, 11, 21, 26, 27, 28) + 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" @@ -406,11 +416,19 @@ class ObjectCreateDetailedMessageTest extends Specification { 50, 1, 7, 7, 100, 100, - List(0, 1, 11, 21, 26, 27, 28), + List( + CertificationType.StandardAssault, + CertificationType.MediumAssault, + CertificationType.ATV, + CertificationType.Harasser, + CertificationType.StandardExoSuit, + CertificationType.AgileExoSuit, + CertificationType.ReinforcedExoSuit + ), List(), "xpe_sanctuary_help" :: "xpe_th_firemodes" :: "used_beamer" :: "map13" :: Nil, List.empty, - InventoryData(inv), + Some(InventoryData(inv)), DrawnSlot.Pistol1 ) val msg = ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 8013b820..56e0024d 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -394,8 +394,8 @@ class WorldSessionActor extends Actor with MDCContextAware { case ListAccountCharacters => val gen : AtomicInteger = new AtomicInteger(1) - log.info(s"${PacketCoding.DecodePacket(objectHex)}") - sendRawResponse(objectHex) + //log.info(s"${PacketCoding.DecodePacket(objectHex)}") + sendResponse(objectHex) sendResponse(PacketCoding.CreateGamePacket(0, CharacterInfoMessage(15,PlanetSideZoneID(10000), 41605313, PlanetSideGUID(75), false, 6404428))) //load characters // SetCharacterSelectScreenGUID(player, gen) @@ -456,7 +456,7 @@ class WorldSessionActor extends Actor with MDCContextAware { // sendResponse(PacketCoding.CreateGamePacket(0, // ObjectCreateDetailedMessage(ObjectClass.avatar, tplayer.GUID, tplayer.Definition.Packet.DetailedConstructorData(tplayer).get) // )) - sendRawResponse(objectHex) + sendResponse(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}") @@ -495,47 +495,53 @@ class WorldSessionActor extends Actor with MDCContextAware { failWithError(s"Invalid packet class received: $default") } - val objectHex = hex"18 4a c7 00 00 bc 84 B0 0c 0b 95 59 9a 84 40 b0 00 01 32 00 00 08 70 43 00 43 00 52 00 49 00 44 00 45 00 52 00 82 28 c9 3d 04 40 03 c0 01 40 02 80 00 40 35 18 40 00 25 40 42 00 6c 00 61 00 63 00 6b 00 20 00 41 00 72 00 6d 00 6f 00 72 00 65 00 64 00 20 00 52 00 65 00 61 00 70 00 65 00 72 00 73 00 0f 00 00 03 02 0c 00 00 03 88 00 00 00 d4 00 00 01 9c 04 00 00 09 19 90 02 04 3c 28 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 64 00 00 01 00 7e c8 00 c8 00 00 01 b9 53 4c 00 00 00 00 00 00 00 00 00 00 00 00 00 03 40 00 40 81 c6 86 c8 48 88 c9 09 49 8a 67 86 e0 00 01 25 e0 32 d8 09 6c 00 00 3c 04 00 02 30 7870655f626c61636b6f7073917870655f626174746c655f72616e6b5f39927870655f626174746c655f72616e6b5f3233927870655f636f6d6d616e645f72616e6b5f318f7870655f666f726d5f6f75746669748e7870655f6d61696c5f616c657274927870655f626174746c655f72616e6b5f3232927870655f636f6d6d616e645f72616e6b5f33907870655f666f726d5f706c61746f6f6e927870655f626174746c655f72616e6b5f3133927870655f626174746c655f72616e6b5f3132927870655f626174746c655f72616e6b5f3130927870655f626174746c655f72616e6b5f3134927870655f626174746c655f72616e6b5f3135917870655f626174746c655f72616e6b5f38927870655f626174746c655f72616e6b5f3131927870655f626174746c655f72616e6b5f3231927870655f626174746c655f72616e6b5f3230927870655f626174746c655f72616e6b5f3138927870655f626174746c655f72616e6b5f3139907870655f6a6f696e5f706c61746f6f6e927870655f626174746c655f72616e6b5f3137927870655f626174746c655f72616e6b5f31368f7870655f6a6f696e5f6f7574666974927870655f626174746c655f72616e6b5f3235927870655f626174746c655f72616e6b5f3234917870655f626174746c655f72616e6b5f37917870655f62696e645f666163696c6974798c7870655f62696e645f616d73927870655f636f6d6d616e645f72616e6b5f35927870655f636f6d6d616e645f72616e6b5f32927870655f696e7374616e745f616374696f6e8e7870655f666f726d5f7371756164917870655f626174746c655f72616e6b5f34937870655f776172705f676174655f7573616765917870655f626174746c655f72616e6b5f32917870655f626174746c655f72616e6b5f33927870655f73616e6374756172795f68656c708c7870655f64726f705f706f64937870655f6f72626974616c5f73687574746c65917870655f626174746c655f72616e6b5f368e7870655f6a6f696e5f7371756164917870655f626174746c655f72616e6b5f35927870655f74685f737769746368626c6164658d7870655f74685f726f757465728c7870655f74685f666c61696c8a7870655f74685f616e748a7870655f74685f616d738a7870655f74685f62667289757365645f6f69637791757365645f616476616e6365645f61636597766973697465645f73706974666972655f74757272657498766973697465645f73706974666972655f636c6f616b656493766973697465645f73706974666972655f616192766973697465645f74616e6b5f7472617073a1766973697465645f706f727461626c655f6d616e6e65645f7475727265745f6e638e757365645f6d616763757474657293766973697465645f77616c6c5f7475727265748b766973697465645f616d738b766973697465645f616e7490766973697465645f64726f707368697091766973697465645f6c6962657261746f7294766973697465645f6c6967687467756e7368697091766973697465645f6c696768746e696e6790766973697465645f6d616772696465728f766973697465645f70726f776c657293766973697465645f71756164737465616c746890766973697465645f736b7967756172649a766973697465645f74687265656d616e686561767962756767799d766973697465645f74776f5f6d616e5f61737361756c745f627567677998766973697465645f74776f6d616e6865617679627567677998766973697465645f74776f6d616e686f766572627567677990766973697465645f76616e67756172648d766973697465645f666c61696c8e766973697465645f726f7574657293766973697465645f737769746368626c6164658e766973697465645f6175726f726193766973697465645f626174746c657761676f6e8c766973697465645f6675727993766973697465645f7175616461737361756c7496766973697465645f67616c6178795f67756e736869708e766973697465645f6170635f74728e766973697465645f6170635f767390766973697465645f6c6f64657374617290766973697465645f7068616e7461736d91766973697465645f7468756e64657265728e766973697465645f6170635f6e638f766973697465645f76756c747572658c766973697465645f7761737090766973697465645f6d6f73717569746f97766973697465645f617068656c696f6e5f666c6967687497766973697465645f617068656c696f6e5f67756e6e657297766973697465645f636f6c6f737375735f666c6967687497766973697465645f636f6c6f737375735f67756e6e657298766973697465645f706572656772696e655f666c6967687498766973697465645f706572656772696e655f67756e6e657289757365645f62616e6b95766973697465645f7265736f757263655f73696c6f9e766973697465645f63657274696669636174696f6e5f7465726d696e616c94766973697465645f6d65645f7465726d696e616c93757365645f6e616e6f5f64697370656e73657295766973697465645f73656e736f725f736869656c649a766973697465645f62726f6164636173745f77617270676174658c757365645f7068616c616e7894757365645f7068616c616e785f6176636f6d626f96757365645f7068616c616e785f666c616b636f6d626f96766973697465645f77617270676174655f736d616c6c91757365645f666c616d657468726f7765729a757365645f616e6369656e745f7475727265745f776561706f6e92766973697465645f4c4c555f736f636b657492757365645f656e657267795f67756e5f6e6397766973697465645f6d656469756d7472616e73706f72749f757365645f617068656c696f6e5f696d6d6f6c6174696f6e5f63616e6e6f6e93757365645f6772656e6164655f706c61736d6193757365645f6772656e6164655f6a616d6d657298766973697465645f736869656c645f67656e657261746f7295766973697465645f6d6f74696f6e5f73656e736f7296766973697465645f6865616c74685f6372797374616c96766973697465645f7265706169725f6372797374616c97766973697465645f76656869636c655f6372797374616c91757365645f6772656e6164655f6672616788757365645f61636598766973697465645f6164765f6d65645f7465726d696e616c8b757365645f6265616d657290757365645f626f6c745f6472697665728b757365645f6379636c65728a757365645f676175737391757365645f68756e7465727365656b657288757365645f6973708b757365645f6c616e6365728b757365645f6c61736865728e757365645f6d61656c7374726f6d8c757365645f70686f656e69788b757365645f70756c7361728d757365645f70756e69736865728e757365645f725f73686f7467756e8d757365645f7261646961746f7288757365645f72656b8d757365645f72657065617465728c757365645f726f636b6c65748c757365645f737472696b65728f757365645f73757070726573736f728c757365645f7468756d7065729c766973697465645f76616e755f636f6e74726f6c5f636f6e736f6c6598766973697465645f636170747572655f7465726d696e616c92757365645f6d696e695f636861696e67756e91757365645f6c617a655f706f696e7465728c757365645f74656c657061648b757365645f7370696b657291757365645f68656176795f736e6970657293757365645f636f6d6d616e645f75706c696e6b8d757365645f66697265626972648e757365645f666c6563686574746594757365645f68656176795f7261696c5f6265616d89757365645f696c63399a766973697465645f67656e657261746f725f7465726d696e616c8e766973697465645f6c6f636b65729a766973697465645f65787465726e616c5f646f6f725f6c6f636b9c766973697465645f6169725f76656869636c655f7465726d696e616c97766973697465645f67616c6178795f7465726d696e616c98766973697465645f696d706c616e745f7465726d696e616c99766973697465645f7365636f6e646172795f6361707475726590757365645f32356d6d5f63616e6e6f6e99757365645f6c6962657261746f725f626f6d6261726469657293766973697465645f7265706169725f73696c6f93766973697465645f76616e755f6d6f64756c6591757365645f666c61696c5f776561706f6e8b757365645f73637974686598766973697465645f7265737061776e5f7465726d696e616c8c757365645f62616c6c67756e95757365645f616e6e69766572736172795f67756e6195757365645f616e6e69766572736172795f67756e6294757365645f616e6e69766572736172795f67756e90757365645f37356d6d5f63616e6e6f6e92757365645f6170635f6e635f776561706f6e92757365645f6170635f74725f776561706f6e92757365645f6170635f76735f776561706f6e90757365645f666c75785f63616e6e6f6e93757365645f617068656c696f6e5f6c617365729f757365645f617068656c696f6e5f706c61736d615f726f636b65745f706f6491757365645f617068656c696f6e5f70706196757365645f617068656c696f6e5f73746172666972658c757365645f666c7578706f6494766973697465645f6266725f7465726d696e616c95757365645f636f6c6f737375735f6275727374657296757365645f636f6c6f737375735f636861696e67756e9e757365645f636f6c6f737375735f636c75737465725f626f6d625f706f64a0757365645f636f6c6f737375735f6475616c5f3130306d6d5f63616e6e6f6e7399757365645f636f6c6f737375735f74616e6b5f63616e6e6f6e96766973697465645f64616d6167655f6372797374616c96766973697465645f656e657267795f6372797374616c9b757365645f68656176795f6772656e6164655f6c61756e6368657298757365645f33356d6d5f726f74617279636861696e67756e8b757365645f6b6174616e6190757365645f33356d6d5f63616e6e6f6e93757365645f7265617665725f776561706f6e7396757365645f6c696768746e696e675f776561706f6e738c757365645f6d65645f61707090757365645f32306d6d5f63616e6e6f6e98766973697465645f6d6f6e6f6c6974685f616d657269736899766973697465645f6d6f6e6f6c6974685f636572797368656e97766973697465645f6d6f6e6f6c6974685f637973736f7297766973697465645f6d6f6e6f6c6974685f6573616d697299766973697465645f6d6f6e6f6c6974685f666f72736572616c97766973697465645f6d6f6e6f6c6974685f686f7373696e99766973697465645f6d6f6e6f6c6974685f697368756e64617298766973697465645f6d6f6e6f6c6974685f7365617268757397766973697465645f6d6f6e6f6c6974685f736f6c73617292757365645f6e635f6865765f66616c636f6e99757365645f6e635f6865765f7363617474657263616e6e6f6e93757365645f6e635f6865765f73706172726f7791757365645f61726d6f725f736970686f6e9f757365645f706572656772696e655f6475616c5f6d616368696e655f67756e9f757365645f706572656772696e655f6475616c5f726f636b65745f706f647399757365645f706572656772696e655f6d65636868616d6d65728f757365645f6e74755f736970686f6e9e757365645f706572656772696e655f7061727469636c655f63616e6e6f6e96757365645f706572656772696e655f73706172726f7791757365645f3130356d6d5f63616e6e6f6e92757365645f31356d6d5f636861696e67756ea0757365645f70756c7365645f7061727469636c655f616363656c657261746f7293757365645f726f74617279636861696e67756e9f766973697465645f6465636f6e737472756374696f6e5f7465726d696e616c95757365645f736b7967756172645f776561706f6e7391766973697465645f67656e657261746f7291757365645f67617573735f63616e6e6f6e89757365645f7472656b95757365645f76616e67756172645f776561706f6e73a4766973697465645f616e6369656e745f6169725f76656869636c655f7465726d696e616ca2766973697465645f616e6369656e745f65717569706d656e745f7465726d696e616c96766973697465645f6f726465725f7465726d696e616ca7766973697465645f616e6369656e745f67726f756e645f76656869636c655f7465726d696e616c9f766973697465645f67726f756e645f76656869636c655f7465726d696e616c97757365645f76756c747572655f626f6d6261726469657298757365645f76756c747572655f6e6f73655f63616e6e6f6e98757365645f76756c747572655f7461696c5f63616e6e6f6e97757365645f776173705f776561706f6e5f73797374656d91766973697465645f636861726c6965303591766973697465645f636861726c6965303691766973697465645f636861726c6965303791766973697465645f636861726c6965303891766973697465645f636861726c696530398e766973697465645f736c656430348e766973697465645f736c656430358e766973697465645f736c656430368e766973697465645f736c656430378e766973697465645f736c65643038857567643036857567643035857567643034857567643033857567643032857567643031856d61703939856d61703938856d61703937856d61703936856d61703135856d61703134856d61703133856d61703132856d61703131856d61703034856d61703035856d61703033856d61703031856d61703036856d61703032856d61703039856d61703037856d617031300800000092747261696e696e675f776561706f6e73303192747261696e696e675f7761727067617465738c747261696e696e675f6d617092747261696e696e675f696e76656e746f727990747261696e696e675f6865616c696e678d747261696e696e675f686172748f747261696e696e675f61726d6f727390747261696e696e675f77656c636f6d650000000000000000000000000000000000000000ab000480000005000000058000000800000008800000090000000f0000000f8000001000000012800000130000001a0000001a8000001c800000228000002e00000031800000320000003580000036000000368000003700000039000000398000003a0000003a8000003b0000003f800000400000004080000041000000418000004e000000518000005580000059000000598000005a0000005a800000610000006180000063000000648000006b8000006c0000006c8000006f0000006f800000700000007080000071000000718000007280000073000000798000007a0000000080800004808000050080000a8080000b0080000d8080000e0080000e8080000f0080001080800011008000140080001c808000270080002a8080002b00800033808000388080003c0080003c8080004000800043808000440080004480800045008000470080004c0080004f808000520080005400800000002f0a26c9313c024000001000060279a272780c80000100026b271682480000020000c041c0464f019000006400d43541d08900000040001808388946a0320000008019041c821700000008046ce915631018000000400018083822c6a030000019001d2338421b00000008000301070438ac060000032003a4630c466000000100006020e06c1e80c000006400748e01c92c00000020000c041c11e4101800000c800e90a02331800000040001808381b06e030000019001989ec436b000000080003010c81d924060000019003310e87f0600000010000602190d31780c000003200662fa22e6c00000020000c04320a82f018000006400e90683bd9800000040001808381205a030000019000205808bcb000001900040e4908058e0000032006629c1d00b4c00000020000c043209c35018000006400cc480320175800000040001808642084a03000000c80198880540303000000080003010c87d86c060000019000b17e9100636000000100006020e01f2080c00000c800081580f010bc000006400414961a022f800000040001808de0004603000003200082818840477000000080003011bc0a11406000006400040a28680b2e000003200569271a017ac00000020000c041037435018000008c00ce2e04003498000000400018099e6c46e03000004b0019c4483406b70000000800030133cc28ec06000009600040ad8e80dfe0000032006710e1201fec00000020000c04cf2a837018000025800ce37432040f8000000400018099e7586a03000004b00058b18480843000000080003010702f11406000006400040f690010ce0000032000e0222786c8000064001c1d82b13900000c800380205233200001900074559050e40000320079afa0fa4c800002000d84b64f4e9000000400010300380" -// 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 -// ) -// ) -// ) +// val objectHex = hex"18 57 0C 00 00 BC 84 B0 06 C2 D7 65 53 5C A1 60 00 01 34 40 00 09 70 49 00 6C 00 6C 00 6C 00 49 00 49 00 49 00 6C 00 6C 00 6C 00 49 00 6C 00 49 00 6C 00 6C 00 49 00 6C 00 6C 00 6C 00 49 00 6C 00 6C 00 49 00 84 52 70 76 1E 80 80 00 00 00 00 00 3F FF C0 00 00 00 20 00 00 0F F6 A7 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FD 90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 64 00 00 01 00 7E C8 00 C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 C0 00 42 C5 46 86 C7 00 00 00 80 00 00 12 40 78 70 65 5F 73 61 6E 63 74 75 61 72 79 5F 68 65 6C 70 90 78 70 65 5F 74 68 5F 66 69 72 65 6D 6F 64 65 73 8B 75 73 65 64 5F 62 65 61 6D 65 72 85 6D 61 70 31 33 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 00 00 00 00 01 0A 23 02 60 04 04 40 00 00 10 00 06 02 08 14 D0 08 0C 80 00 02 00 02 6B 4E 00 82 88 00 00 02 00 00 C0 41 C0 9E 01 01 90 00 00 64 00 44 2A 00 10 91 00 00 00 40 00 18 08 38 94 40 20 32 00 00 00 80 19 05 48 02 17 20 00 00 08 00 70 29 80 43 64 00 00 32 00 0E 05 40 08 9C 80 00 06 40 01 C0 AA 01 19 90 00 00 C8 00 3A 15 80 28 72 00 00 19 00 04 0A B8 05 26 40 00 03 20 06 C2 58 00 A7 88 00 00 02 00 00 80 00 00" + val objectHex = PacketCoding.CreateGamePacket(0, + ObjectCreateDetailedMessage( + 121, + PlanetSideGUID(75), + DetailedCharacterData( + CharacterAppearanceData( + PlacementData(Vector3(3674.8438f,2726.789f,91.15625f), Vector3(0.0f,0.0f,36.5625f)), + BasicCharacterData("psemu2",PlanetSideEmpire.VS,CharacterGender.Female,41,1), + 0, + 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("xpe_blackops"), + List(), + None, + DrawnSlot.None + ) + ) + ) def handlePkt(pkt : PlanetSidePacket) : Unit = pkt match { case ctrl : PlanetSideControlPacket => From 976c31da2bbd4e1662aad6692813292d0b1bc536 Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 6 Sep 2017 19:40:52 -0400 Subject: [PATCH 06/10] set up rudimentary certification and implant list-building calls in AvatarConverter, as supported by (existing) data structures in Player --- .../net/psforever/objects/ImplantSlot.scala | 2 +- .../scala/net/psforever/objects/Player.scala | 20 ++++++++++ .../converter/AvatarConverter.scala | 40 ++++++++++++++----- .../objectcreate/DetailedCharacterData.scala | 2 +- 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/ImplantSlot.scala b/common/src/main/scala/net/psforever/objects/ImplantSlot.scala index 00951f50..bf6d0b01 100644 --- a/common/src/main/scala/net/psforever/objects/ImplantSlot.scala +++ b/common/src/main/scala/net/psforever/objects/ImplantSlot.scala @@ -54,7 +54,7 @@ class ImplantSlot { } object ImplantSlot { - private val default = new Implant(ImplantDefinition(ImplantType.RangeMagnifier)) + private val default = new Implant(ImplantDefinition(ImplantType.None)) def apply() : ImplantSlot = { new ImplantSlot() diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index c2300965..0873c1c9 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -8,6 +8,7 @@ import net.psforever.packet.game.PlanetSideGUID import net.psforever.types._ import scala.annotation.tailrec +import scala.collection.mutable class Player(private val name : String, private val faction : PlanetSideEmpire.Value, @@ -33,6 +34,9 @@ class Player(private val name : String, private val loadouts : Array[Option[InfantryLoadout]] = Array.fill[Option[InfantryLoadout]](10)(None) + private var bep : Long = 0 + private var cep : Long = 0 + private val certifications : mutable.Set[CertificationType.Value] = mutable.Set[CertificationType.Value]() private val implants : Array[ImplantSlot] = Array.fill[ImplantSlot](3)(new ImplantSlot) // private var tosRibbon : MeritCommendation.Value = MeritCommendation.None @@ -312,6 +316,22 @@ class Player(private val name : String, exosuit = suit } + def BEP : Long = bep + + def BEP_=(battleExperiencePoints : Long) : Long = { + bep = math.max(0L, math.min(battleExperiencePoints, 4294967295L)) + BEP + } + + def CEP : Long = cep + + def CEP_=(commandExperiencePoints : Long) : Long = { + cep = math.max(0L, math.min(commandExperiencePoints, 4294967295L)) + CEP + } + + def Certifications : mutable.Set[CertificationType.Value] = certifications + def Implants : Array[ImplantSlot] = implants def Implant(slot : Int) : Option[ImplantType.Value] = { 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 6c97c17c..12e5ee29 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,8 +3,8 @@ 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, UniformStyle} -import net.psforever.types.GrenadeState +import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle} +import net.psforever.types.{GrenadeState, ImplantType} import scala.annotation.tailrec import scala.util.{Success, Try} @@ -28,26 +28,24 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { } override def DetailedConstructorData(obj : Player) : Try[DetailedCharacterData] = { - import net.psforever.types.CertificationType._ Success( DetailedCharacterData( MakeAppearanceData(obj), - 0L, - 0L, + obj.BEP, + obj.CEP, obj.MaxHealth, obj.Health, obj.Armor, obj.MaxStamina, obj.Stamina, - List(StandardAssault, MediumAssault, ATV, Harasser, StandardExoSuit, AgileExoSuit, ReinforcedExoSuit), //TODO certification list - List(), //TODO implant list + obj.Certifications.toList.sortBy(_.id), + MakeImplantEntries(obj), List.empty[String], //TODO fte list List.empty[String], //TODO tutorial list InventoryData((MakeHolsters(obj, BuildDetailedEquipment) ++ MakeFifthSlot(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)), GetDrawnSlot(obj) ) ) - //TODO tidy this mess up } /** @@ -66,8 +64,8 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { "", 0, obj.isBackpack, - obj.Orientation.y.toInt, - obj.FacingYawUpper.toInt, + obj.Orientation.y, + obj.FacingYawUpper, true, GrenadeState.None, false, @@ -77,6 +75,28 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { ) } + /** + * Transform an `Array` of `Implant` objects into a `List` of `ImplantEntry` objects suitable as packet data. + * @param obj the `Player` game object + * @return the resulting implant `List` + * @see `ImplantEntry` in `DetailedCharacterData` + */ + private def MakeImplantEntries(obj : Player) : List[ImplantEntry] = { + obj.Implants.map(impl => { + impl.Installed match { + case Some(module) => + if(impl.Implant.get.Ready) { + ImplantEntry(module, None) + } + else { + ImplantEntry(module, Some(impl.Implant.get.Timer.toInt)) + } + case None => + ImplantEntry(ImplantType.None, None) + } + }).toList + } + /** * Given a player with an inventory, convert the contents of that inventory into converted-decoded packet data. * The inventory is not represented in a `0x17` `Player`, so the conversion is only valid for `0x18` avatars. 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 e678c3f1..cc3663a9 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala @@ -12,7 +12,7 @@ import scala.annotation.tailrec /** * An entry in the `List` of valid implant slots in `DetailedCharacterData`. - * `activation`, if defined, indicates the time remaining (in seconds?) before an implant can be activated. + * `activation`, if defined, indicates the time remaining (in seconds?) before an implant becomes usable. * @param implant the type of implant * @param activation the activation timer * @see `ImplantType` From a0252e8d9acdedd434777024d48e65bd01982746 Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 6 Sep 2017 20:33:39 -0400 Subject: [PATCH 07/10] DetailedCharData now divides Lists using pattern matching; restored spontaneous avatar in WSA --- .../objectcreate/DetailedCharacterData.scala | 27 +++--- .../src/main/scala/WorldSessionActor.scala | 97 +++++-------------- 2 files changed, 39 insertions(+), 85 deletions(-) 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 cc3663a9..721f5978 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 @@ -71,10 +71,11 @@ final case class ImplantEntry(implant : ImplantType.Value, * the first entry may be padded * @param inventory the avatar's inventory * @param drawn_slot the holster that is initially drawn - * @see `CharacterAppearanceData` - * @see `CharacterData` - * @see `InventoryData` - * @see `DrawnSlot` + * @see `CharacterAppearanceData`
+ * `CharacterData`
+ * `CertificationType`
+ * `InventoryData`
+ * `DrawnSlot` */ final case class DetailedCharacterData(appearance : CharacterAppearanceData, bep : Long, @@ -322,17 +323,15 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] { 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.tail + val (firstEvent, fteListCopy) = fteList match { + case (f : String) +: Nil => (Some(f), Nil) + case ((f : String) +: (rest : List[String])) => (Some(f), rest) + case Nil => (None, Nil) } - var tutListCopy = tutList - var firstTutorial : Option[String] = None - if(tutList.nonEmpty) { - firstTutorial = Some(tutList.head) - tutListCopy = tutList.tail + val (firstTutorial, tutListCopy) = tutList match { + case (f : String) +: Nil => (Some(f), Nil) + case ((f : String) +: (rest : List[String])) => (Some(f), rest) + case Nil => (None, Nil) } 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 :: () :: inv :: drawn :: false :: HNil) } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 56e0024d..c69ead66 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -394,25 +394,22 @@ class WorldSessionActor extends Actor with MDCContextAware { case ListAccountCharacters => val gen : AtomicInteger = new AtomicInteger(1) - //log.info(s"${PacketCoding.DecodePacket(objectHex)}") - sendResponse(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))) @@ -453,16 +450,15 @@ 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(objectHex) + sendResponse(PacketCoding.CreateGamePacket(0, + ObjectCreateDetailedMessage(ObjectClass.avatar, tplayer.GUID, tplayer.Definition.Packet.DetailedConstructorData(tplayer).get) + )) 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 = PlanetSideGUID(75)//tplayer.GUID + val guid = 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))) @@ -495,54 +491,6 @@ class WorldSessionActor extends Actor with MDCContextAware { failWithError(s"Invalid packet class received: $default") } -// val objectHex = hex"18 57 0C 00 00 BC 84 B0 06 C2 D7 65 53 5C A1 60 00 01 34 40 00 09 70 49 00 6C 00 6C 00 6C 00 49 00 49 00 49 00 6C 00 6C 00 6C 00 49 00 6C 00 49 00 6C 00 6C 00 49 00 6C 00 6C 00 6C 00 49 00 6C 00 6C 00 49 00 84 52 70 76 1E 80 80 00 00 00 00 00 3F FF C0 00 00 00 20 00 00 0F F6 A7 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FD 90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 64 00 00 01 00 7E C8 00 C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 C0 00 42 C5 46 86 C7 00 00 00 80 00 00 12 40 78 70 65 5F 73 61 6E 63 74 75 61 72 79 5F 68 65 6C 70 90 78 70 65 5F 74 68 5F 66 69 72 65 6D 6F 64 65 73 8B 75 73 65 64 5F 62 65 61 6D 65 72 85 6D 61 70 31 33 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 00 00 00 00 01 0A 23 02 60 04 04 40 00 00 10 00 06 02 08 14 D0 08 0C 80 00 02 00 02 6B 4E 00 82 88 00 00 02 00 00 C0 41 C0 9E 01 01 90 00 00 64 00 44 2A 00 10 91 00 00 00 40 00 18 08 38 94 40 20 32 00 00 00 80 19 05 48 02 17 20 00 00 08 00 70 29 80 43 64 00 00 32 00 0E 05 40 08 9C 80 00 06 40 01 C0 AA 01 19 90 00 00 C8 00 3A 15 80 28 72 00 00 19 00 04 0A B8 05 26 40 00 03 20 06 C2 58 00 A7 88 00 00 02 00 00 80 00 00" - val objectHex = PacketCoding.CreateGamePacket(0, - ObjectCreateDetailedMessage( - 121, - PlanetSideGUID(75), - DetailedCharacterData( - CharacterAppearanceData( - PlacementData(Vector3(3674.8438f,2726.789f,91.15625f), Vector3(0.0f,0.0f,36.5625f)), - BasicCharacterData("psemu2",PlanetSideEmpire.VS,CharacterGender.Female,41,1), - 0, - 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("xpe_blackops"), - List(), - None, - DrawnSlot.None - ) - ) - ) - def handlePkt(pkt : PlanetSidePacket) : Unit = pkt match { case ctrl : PlanetSideControlPacket => handleControlPkt(ctrl) @@ -636,6 +584,13 @@ class WorldSessionActor extends Actor with MDCContextAware { player.Position = Vector3(3674.8438f, 2726.789f, 91.15625f) player.Orientation = Vector3(0f, 0f, 90f) player.Continent = "home3" + player.Certifications += CertificationType.StandardAssault + player.Certifications += CertificationType.MediumAssault + player.Certifications += CertificationType.StandardExoSuit + player.Certifications += CertificationType.AgileExoSuit + player.Certifications += CertificationType.ReinforcedExoSuit + player.Certifications += CertificationType.ATV + player.Certifications += CertificationType.Harasser player.Slot(0).Equipment = beamer1 player.Slot(2).Equipment = suppressor1 player.Slot(4).Equipment = forceblade1 From 5962227ad2e14ec4b7f6774ef2b8b2fd4774aefc Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 7 Sep 2017 00:10:10 -0400 Subject: [PATCH 08/10] added OCM UniformStyle selection and command rank selection for AvatarConverter; more helpers for GlobalDefinitions --- .../psforever/objects/GlobalDefinitions.scala | 32 ++++++++++- .../converter/AvatarConverter.scala | 56 ++++++++++++++++++- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index b8666006..7a6a1bcd 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -6,7 +6,6 @@ import net.psforever.objects.definition.converter.{CommandDetonaterConverter, Lo import net.psforever.objects.equipment.CItem.DeployedItem import net.psforever.objects.equipment._ import net.psforever.objects.inventory.InventoryTile -import net.psforever.packet.game.objectcreate.ObjectClass import net.psforever.types.PlanetSideEmpire object GlobalDefinitions { @@ -161,6 +160,36 @@ object GlobalDefinitions { } } + /** + * Using the definition for a piece of `Equipment` determine if it is a grenade-type weapon. + * Only the normal grenades count; the grenade packs are excluded. + * @param edef the `EquipmentDefinition` of the item + * @return `true`, if it is a grenade-type weapon; `false`, otherwise + */ + def isGrenade(edef : EquipmentDefinition) : Boolean = { + edef match { + case `frag_grenade` | `jammer_grenade` | `plasma_grenade` => + true + case _ => + false + } + } + + /** + * Using the definition for a piece of `Equipment` determine if it is a grenade-type weapon. + * Only the grenade packs count; the normal grenades are excluded. + * @param edef the `EquipmentDefinition` of the item + * @return `true`, if it is a grenade-type weapon; `false`, otherwise + */ + def isGrenadePack(edef : EquipmentDefinition) : Boolean = { + edef match { + case `frag_cartridge` | `jammer_cartridge` | `plasma_cartridge` => + true + case _ => + false + } + } + /** * Using the definition for a piece of `Equipment` determine with which faction it aligns if it is a weapon. * Only checks `Tool` objects. @@ -241,6 +270,7 @@ object GlobalDefinitions { } } + import net.psforever.packet.game.objectcreate.ObjectClass val locker_container = new EquipmentDefinition(456) { Name = "locker container" 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 12e5ee29..963a7acb 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 @@ -16,8 +16,8 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { MakeAppearanceData(obj), obj.Health / obj.MaxHealth * 255, //TODO not precise obj.Armor / obj.MaxArmor * 255, //TODO not precise - UniformStyle.Normal, - 0, + DressBattleRank(obj), + DressCommandRank(obj), None, //TODO cosmetics None, //TODO implant effects InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot)), @@ -38,7 +38,7 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { obj.Armor, obj.MaxStamina, obj.Stamina, - obj.Certifications.toList.sortBy(_.id), + obj.Certifications.toList.sortBy(_.id), //TODO is sorting necessary? MakeImplantEntries(obj), List.empty[String], //TODO fte list List.empty[String], //TODO tutorial list @@ -75,6 +75,56 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { ) } + /** + * Select the appropriate `UniformStyle` design for a player's accumulated battle experience points. + * At certain battle ranks, all exo-suits undergo some form of coloration change. + * @param obj the `Player` game object + * @return the resulting uniform upgrade level + */ + private def DressBattleRank(obj : Player) : UniformStyle.Value = { + val bep : Long = obj.BEP + if(bep > 2583440) { //BR25+ + UniformStyle.ThirdUpgrade + } + else if(bep > 308989) { //BR14+ + UniformStyle.SecondUpgrade + } + else if(bep > 44999) { //BR7+ + UniformStyle.FirstUpgrade + } + else { //BR1+ + UniformStyle.Normal + } + } + + /** + * Select the appropriate design for a player's accumulated command experience points. + * Visual cues for command rank include armlets, anklets, and, finally, a backpack, awarded at different ranks. + * @param obj the `Player` game object + * @return the resulting uniform upgrade level + */ + private def DressCommandRank(obj : Player) : Int = { + val cep = obj.CEP + if(cep > 599999) { + 5 + } + else if(cep > 299999) { + 4 + } + else if(cep > 149999) { + 3 + } + else if(cep > 49999) { + 2 + } + else if(cep > 9999) { + 1 + } + else { + 0 + } + } + /** * Transform an `Array` of `Implant` objects into a `List` of `ImplantEntry` objects suitable as packet data. * @param obj the `Player` game object From 5b4ba246e1ee3fd6f6855bc32de3fb1eb7ad9225 Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 7 Sep 2017 23:31:25 -0400 Subject: [PATCH 09/10] simplified the implant management process by removing an unnecessary middleman object; corrected tests that included implants; worked implants into player PacketConverter --- .../psforever/objects/GlobalDefinitions.scala | 36 ++++ .../scala/net/psforever/objects/Implant.scala | 86 ---------- .../net/psforever/objects/ImplantSlot.scala | 116 +++++++++---- .../scala/net/psforever/objects/Player.scala | 44 +++-- .../definition/ImplantDefinition.scala | 11 +- .../converter/AvatarConverter.scala | 57 +++++-- .../objectcreate/DetailedCharacterData.scala | 3 +- .../src/test/scala/objects/ImplantTest.scala | 161 ++++++++++++------ .../src/test/scala/objects/PlayerTest.scala | 44 +++-- 9 files changed, 335 insertions(+), 223 deletions(-) delete mode 100644 common/src/main/scala/net/psforever/objects/Implant.scala diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 7a6a1bcd..330cba5b 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -270,6 +270,42 @@ object GlobalDefinitions { } } + /* + Implants + */ + val + advanced_regen = ImplantDefinition(0) + + val + targeting = ImplantDefinition(1) + + val + audio_amplifier = ImplantDefinition(2) + + val + darklight_vision = ImplantDefinition(3) + + val + melee_booster = ImplantDefinition(4) + + val + personal_shield = ImplantDefinition(5) + + val + range_magnifier = ImplantDefinition(6) + + val + second_wind = ImplantDefinition(7) + + val + silent_run = ImplantDefinition(8) + + val + surge = ImplantDefinition(9) + + /* + Equipment (locker_container, kits, ammunition, weapons) + */ import net.psforever.packet.game.objectcreate.ObjectClass val locker_container = new EquipmentDefinition(456) { diff --git a/common/src/main/scala/net/psforever/objects/Implant.scala b/common/src/main/scala/net/psforever/objects/Implant.scala deleted file mode 100644 index 2d7483f7..00000000 --- a/common/src/main/scala/net/psforever/objects/Implant.scala +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects - -import net.psforever.objects.definition.{ImplantDefinition, Stance} -import net.psforever.types.{ExoSuitType, ImplantType} - -/** - * A type of installable player utility that grants a perk, usually in exchange for stamina (energy).
- *
- * An implant starts with a never-to-initialized timer value of -1 and will not report as `Ready` until the timer is 0. - * The `Timer`, however, will report to the user a time of 0 since negative time does not make sense. - * Although the `Timer` can be manually set, using `Reset` is the better way to default the initialization timer to the correct amount. - * An external script will be necessary to operate the actual initialization countdown. - * An implant must be `Ready` before it can be `Active`. - * The `Timer` must be set (or reset) (or countdown) to 0 to be `Ready` and then it must be activated. - * @param implantDef the `ObjectDefinition` that constructs this item and maintains some of its immutable fields - */ -class Implant(implantDef : ImplantDefinition) { - private var active : Boolean = false - private var initTimer : Long = -1L - - def Name : String = implantDef.Name - - def Ready : Boolean = initTimer == 0L - - def Active : Boolean = active - - def Active_=(isActive : Boolean) : Boolean = { - active = Ready && isActive - Active - } - - def Timer : Long = math.max(0, initTimer) - - def Timer_=(time : Long) : Long = { - initTimer = math.max(0, time) - Timer - } - - def MaxTimer : Long = implantDef.Initialization - - def ActivationCharge : Int = Definition.ActivationCharge - - /** - * Calculate the stamina consumption of the implant for any given moment of being active after its activation. - * As implant energy use can be influenced by both exo-suit worn and general stance held, both are considered. - * @param suit the exo-suit being worn - * @param stance the player's stance - * @return the amount of stamina (energy) that is consumed - */ - def Charge(suit : ExoSuitType.Value, stance : Stance.Value) : Int = { - if(active) { - implantDef.DurationChargeBase + implantDef.DurationChargeByExoSuit(suit) + implantDef.DurationChargeByStance(stance) - } - else { - 0 - } - } - - /** - * Place an implant back in its initializing state. - */ - def Reset() : Unit = { - Active = false - Timer = MaxTimer - } - - /** - * Place an implant back in its pre-initialization state. - * The implant is inactive and can not proceed to a `Ready` condition naturally from this state. - */ - def Jammed() : Unit = { - Active = false - Timer = -1 - } - - def Definition : ImplantDefinition = implantDef -} - -object Implant { - def default : Implant = new Implant(ImplantDefinition(ImplantType.RangeMagnifier)) - - def apply(implantDef : ImplantDefinition) : Implant = { - new Implant(implantDef) - } -} diff --git a/common/src/main/scala/net/psforever/objects/ImplantSlot.scala b/common/src/main/scala/net/psforever/objects/ImplantSlot.scala index bf6d0b01..736b03f1 100644 --- a/common/src/main/scala/net/psforever/objects/ImplantSlot.scala +++ b/common/src/main/scala/net/psforever/objects/ImplantSlot.scala @@ -1,61 +1,117 @@ // Copyright (c) 2017 PSForever package net.psforever.objects -import net.psforever.objects.definition.ImplantDefinition -import net.psforever.types.ImplantType +import net.psforever.objects.definition.{ImplantDefinition, Stance} +import net.psforever.types.{ExoSuitType, ImplantType} /** - * A slot "on the player" into which an implant is installed.
+ * A slot "on the player" into which an implant is installed. + * In total, players have three implant slots.
*
- * In total, players have three implant slots. - * At battle rank one (BR1), however, all of those slots are locked. - * The player earns implants at BR16, BR12, and BR18. - * A locked implant slot can not be used. - * (The code uses "not yet unlocked" logic.) - * When unlocked, an implant may be installed into that slot.
- *
- * The default implant that the underlying slot utilizes is the "Range Magnifier." - * Until the `Installed` condition is some value other than `None`, however, the implant in the slot will not work. + * All implants slots start as "locked" and must be "unlocked" through battle rank advancement. + * Only after it is "unlocked" may an implant be "installed" into the slot. + * Upon installation, it undergoes an initialization period and then, after which, it is ready for user activation. + * Being jammed de-activates the implant, put it into a state of "not being ready," and causes the initialization to repeat. */ class ImplantSlot { /** is this slot available for holding an implant */ private var unlocked : Boolean = false + /** whether this implant is ready for use */ + private var initialized : Boolean = false + /** is this implant active */ + private var active : Boolean = false /** what implant is currently installed in this slot; None if there is no implant currently installed */ - private var installed : Option[ImplantType.Value] = None - /** the entry for that specific implant used by the a player; always occupied by some type of implant */ - private var implant : Implant = ImplantSlot.default + private var implant : Option[ImplantDefinition] = None def Unlocked : Boolean = unlocked def Unlocked_=(lock : Boolean) : Boolean = { - unlocked = lock + unlocked = lock || unlocked Unlocked } - def Installed : Option[ImplantType.Value] = installed + def Initialized : Boolean = initialized - def Implant : Option[Implant] = if(Installed.isDefined) { Some(implant) } else { None } + def Initialized_=(init : Boolean) : Boolean = { + initialized = Installed.isDefined && init + Active = Active && initialized //can not be active just yet + Initialized + } - def Implant_=(anImplant : Option[Implant]) : Option[Implant] = { - anImplant match { - case Some(module) => - Implant = module - case None => - installed = None + def Active : Boolean = active + + def Active_=(state : Boolean) : Boolean = { + active = Initialized && state + Active + } + + def Implant : ImplantType.Value = if(Installed.isDefined) { + implant.get.Type + } + else { + Active = false + Initialized = false + ImplantType.None + } + + def Implant_=(anImplant : ImplantDefinition) : ImplantType.Value = { + Implant_=(Some(anImplant)) + } + + def Implant_=(anImplant : Option[ImplantDefinition]) : ImplantType.Value = { + if(Unlocked) { + anImplant match { + case Some(_) => + implant = anImplant + case None => + implant = None + } } Implant } - def Implant_=(anImplant : Implant) : Option[Implant] = { - implant = anImplant - installed = Some(anImplant.Definition.Type) - Implant + def Installed : Option[ImplantDefinition] = implant + + def MaxTimer : Long = Implant match { + case ImplantType.None => + -1L + case _ => + Installed.get.Initialization + } + + def ActivationCharge : Int = { + if(Active) { + Installed.get.ActivationCharge + } + else { + 0 + } + } + + /** + * Calculate the stamina consumption of the implant for any given moment of being active after its activation. + * As implant energy use can be influenced by both exo-suit worn and general stance held, both are considered. + * @param suit the exo-suit being worn + * @param stance the player's stance + * @return the amount of stamina (energy) that is consumed + */ + def Charge(suit : ExoSuitType.Value, stance : Stance.Value) : Int = { + if(Active) { + val inst = Installed.get + inst.DurationChargeBase + inst.DurationChargeByExoSuit(suit) + inst.DurationChargeByStance(stance) + } + else { + 0 + } + } + + def Jammed() : Unit = { + Active = false + Initialized = false } } object ImplantSlot { - private val default = new Implant(ImplantDefinition(ImplantType.None)) - def apply() : ImplantSlot = { new ImplantSlot() } diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index 0873c1c9..9d59f733 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects -import net.psforever.objects.definition.AvatarDefinition +import net.psforever.objects.definition.{AvatarDefinition, ImplantDefinition} import net.psforever.objects.equipment.{Equipment, EquipmentSize} import net.psforever.objects.inventory.{GridInventory, InventoryItem} import net.psforever.packet.game.PlanetSideGUID @@ -334,26 +334,22 @@ class Player(private val name : String, def Implants : Array[ImplantSlot] = implants - def Implant(slot : Int) : Option[ImplantType.Value] = { - if(-1 < slot && slot < implants.length) { implants(slot).Installed } else { None } + def Implant(slot : Int) : ImplantType.Value = { + if(-1 < slot && slot < implants.length) { implants(slot).Implant } else { ImplantType.None } } - def Implant(implantType : ImplantType.Value) : Option[Implant] = { - implants.find(_.Installed.contains(implantType)) match { - case Some(slot) => - slot.Implant - case None => - None - } - } - - def InstallImplant(implant : Implant) : Boolean = { - getAvailableImplantSlot(implants.iterator, implant.Definition.Type) match { - case Some(slot) => - slot.Implant = implant - slot.Implant.get.Reset() - true + def InstallImplant(implant : ImplantDefinition) : Boolean = { + implants.find({p => p.Installed.contains(implant)}) match { //try to find the installed implant case None => + //install in a free slot + getAvailableImplantSlot(implants.iterator, implant.Type) match { + case Some(slot) => + slot.Implant = implant + true + case None => + false + } + case Some(_) => false } } @@ -364,7 +360,7 @@ class Player(private val name : String, } else { val slot = iter.next - if(!slot.Unlocked || slot.Installed.contains(implantType)) { + if(!slot.Unlocked || slot.Implant == implantType) { None } else if(slot.Installed.isEmpty) { @@ -377,7 +373,7 @@ class Player(private val name : String, } def UninstallImplant(implantType : ImplantType.Value) : Boolean = { - implants.find({slot => slot.Installed.contains(implantType)}) match { + implants.find({slot => slot.Implant == implantType}) match { case Some(slot) => slot.Implant = None true @@ -388,9 +384,9 @@ class Player(private val name : String, def ResetAllImplants() : Unit = { implants.foreach(slot => { - slot.Implant match { - case Some(implant) => - implant.Reset() + slot.Installed match { + case Some(_) => + slot.Initialized = false case None => ; } }) @@ -590,7 +586,7 @@ object Player { //hand over implants (0 until 3).foreach(index => { if(obj.Implants(index).Unlocked = player.Implants(index).Unlocked) { - obj.Implants(index).Implant = player.Implants(index).Implant + obj.Implants(index).Implant = player.Implants(index).Installed } }) //hand over knife diff --git a/common/src/main/scala/net/psforever/objects/definition/ImplantDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/ImplantDefinition.scala index 1825b58e..7984b310 100644 --- a/common/src/main/scala/net/psforever/objects/definition/ImplantDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/ImplantDefinition.scala @@ -9,10 +9,13 @@ import scala.collection.mutable * An `Enumeration` of a variety of poses or generalized movement. */ object Stance extends Enumeration { - val Crouching, - Standing, - Walking, //not used, but should still be defined - Running = Value + val + Crouching, + CrouchWalking, //not used, but should still be defined + Standing, + Walking, //not used, but should still be defined + Running + = Value } /** 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 963a7acb..b20812fd 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 @@ -1,9 +1,9 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.definition.converter -import net.psforever.objects.{EquipmentSlot, Player} +import net.psforever.objects.{EquipmentSlot, GlobalDefinitions, ImplantSlot, Player} import net.psforever.objects.equipment.Equipment -import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle} +import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, ImplantEffects, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle} import net.psforever.types.{GrenadeState, ImplantType} import scala.annotation.tailrec @@ -18,9 +18,9 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { obj.Armor / obj.MaxArmor * 255, //TODO not precise DressBattleRank(obj), DressCommandRank(obj), + recursiveMakeImplantEffects(obj.Implants.iterator), None, //TODO cosmetics - None, //TODO implant effects - InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot)), + InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot)), //TODO is sorting necessary? GetDrawnSlot(obj) ) ) @@ -132,14 +132,14 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { * @see `ImplantEntry` in `DetailedCharacterData` */ private def MakeImplantEntries(obj : Player) : List[ImplantEntry] = { - obj.Implants.map(impl => { - impl.Installed match { - case Some(module) => - if(impl.Implant.get.Ready) { - ImplantEntry(module, None) + obj.Implants.map(slot => { + slot.Installed match { + case Some(_) => + if(slot.Initialized) { + ImplantEntry(slot.Implant, None) } else { - ImplantEntry(module, Some(impl.Implant.get.Timer.toInt)) + ImplantEntry(slot.Implant, Some(slot.Installed.get.Initialization.toInt)) } case None => ImplantEntry(ImplantType.None, None) @@ -147,6 +147,35 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { }).toList } + /** + * Find an active implant whose effect will be displayed on this player. + * @param iter an `Iterator` of `ImplantSlot` objects + * @return the effect of an active implant + */ + @tailrec private def recursiveMakeImplantEffects(iter : Iterator[ImplantSlot]) : Option[ImplantEffects.Value] = { + if(!iter.hasNext) { + None + } + else { + val slot = iter.next + if(slot.Active) { + import GlobalDefinitions._ + slot.Installed match { + case Some(`advanced_regen`) => + Some(ImplantEffects.RegenEffects) + case Some(`darklight_vision`) => + Some(ImplantEffects.DarklightEffects) + case Some(`personal_shield`) => + Some(ImplantEffects.PersonalShieldEffects) + case Some(`surge`) => + Some(ImplantEffects.SurgeEffects) + case _ => ; + } + } + recursiveMakeImplantEffects(iter) + } + } + /** * Given a player with an inventory, convert the contents of that inventory into converted-decoded packet data. * The inventory is not represented in a `0x17` `Player`, so the conversion is only valid for `0x18` avatars. @@ -211,6 +240,14 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { InternalSlot(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.Packet.DetailedConstructorData(equip).get) } + /** + * Given some equipment holsters, convert the contents of those holsters into converted-decoded packet data. + * @param iter an `Iterator` of `EquipmentSlot` objects that are a part of the player's holsters + * @param builder the function used to transform to the decoded packet form + * @param list the current `List` of transformed data + * @param index which holster is currently being explored + * @return the `List` of inventory data created from the holsters + */ @tailrec private def recursiveMakeHolsters(iter : Iterator[EquipmentSlot], builder : ((Int, Equipment) => InternalSlot), list : List[InternalSlot] = Nil, index : Int = 0) : List[InternalSlot] = { if(!iter.hasNext) { list 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 721f5978..13ccf6ee 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 @@ -14,7 +14,8 @@ import scala.annotation.tailrec * An entry in the `List` of valid implant slots in `DetailedCharacterData`. * `activation`, if defined, indicates the time remaining (in seconds?) before an implant becomes usable. * @param implant the type of implant - * @param activation the activation timer + * @param activation the activation timer; + * technically, this is "unconfirmed" * @see `ImplantType` */ final case class ImplantEntry(implant : ImplantType.Value, diff --git a/common/src/test/scala/objects/ImplantTest.scala b/common/src/test/scala/objects/ImplantTest.scala index e1f47936..e7bc2390 100644 --- a/common/src/test/scala/objects/ImplantTest.scala +++ b/common/src/test/scala/objects/ImplantTest.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package objects -import net.psforever.objects.Implant +import net.psforever.objects.ImplantSlot import net.psforever.objects.definition.{ImplantDefinition, Stance} import net.psforever.types.{ExoSuitType, ImplantType} import org.specs2.mutable._ @@ -16,61 +16,122 @@ class ImplantTest extends Specification { sample.DurationChargeByExoSuit += ExoSuitType.Standard -> 1 sample.DurationChargeByStance += Stance.Running -> 1 - "define" in { - sample.Initialization mustEqual 90000 - sample.ActivationCharge mustEqual 3 - sample.DurationChargeBase mustEqual 1 - sample.DurationChargeByExoSuit(ExoSuitType.Agile) mustEqual 2 - sample.DurationChargeByExoSuit(ExoSuitType.Reinforced) mustEqual 2 - sample.DurationChargeByExoSuit(ExoSuitType.Standard) mustEqual 1 - sample.DurationChargeByExoSuit(ExoSuitType.Infiltration) mustEqual 0 //default value - sample.DurationChargeByStance(Stance.Running) mustEqual 1 - sample.DurationChargeByStance(Stance.Crouching) mustEqual 0 //default value - sample.Type mustEqual ImplantType.SilentRun + "ImplantDefinition" should { + "define" in { + sample.Initialization mustEqual 90000 + sample.ActivationCharge mustEqual 3 + sample.DurationChargeBase mustEqual 1 + sample.DurationChargeByExoSuit(ExoSuitType.Agile) mustEqual 2 + sample.DurationChargeByExoSuit(ExoSuitType.Reinforced) mustEqual 2 + sample.DurationChargeByExoSuit(ExoSuitType.Standard) mustEqual 1 + sample.DurationChargeByExoSuit(ExoSuitType.Infiltration) mustEqual 0 //default value + sample.DurationChargeByStance(Stance.Running) mustEqual 1 + sample.DurationChargeByStance(Stance.Crouching) mustEqual 0 //default value + sample.Type mustEqual ImplantType.SilentRun + } } - "construct" in { - val obj = new Implant(sample) - obj.Definition.Type mustEqual sample.Type - obj.Active mustEqual false - obj.Ready mustEqual false - obj.Timer mustEqual 0 - } + "ImplantSlot" should { + "construct" in { + val obj = new ImplantSlot + obj.Unlocked mustEqual false + obj.Initialized mustEqual false + obj.Active mustEqual false + obj.Implant mustEqual ImplantType.None + obj.Installed mustEqual None + } - "reset/init their timer" in { - val obj = new Implant(sample) - obj.Timer mustEqual 0 - obj.Reset() - obj.Timer mustEqual 90000 - } + "load an implant when locked" in { + val obj = new ImplantSlot + obj.Unlocked mustEqual false + obj.Implant mustEqual ImplantType.None - "reset/init their readiness condition" in { - val obj = new Implant(sample) - obj.Ready mustEqual false - obj.Timer = 0 - obj.Ready mustEqual true - obj.Reset() - obj.Ready mustEqual false - } + obj.Implant = sample + obj.Implant mustEqual ImplantType.None + } - "not activate until they are ready" in { - val obj = new Implant(sample) - obj.Active = true - obj.Active mustEqual false - obj.Timer = 0 - obj.Active = true - obj.Active mustEqual true - } + "load an implant when unlocked" in { + val obj = new ImplantSlot + obj.Unlocked mustEqual false + obj.Implant mustEqual ImplantType.None + sample.Type mustEqual ImplantType.SilentRun - "not cost energy while not active" in { - val obj = new Implant(sample) - obj.Charge(ExoSuitType.Reinforced, Stance.Running) mustEqual 0 - } + obj.Unlocked = true + obj.Implant = sample + obj.Implant mustEqual ImplantType.SilentRun + } - "cost energy while active" in { - val obj = new Implant(sample) - obj.Timer = 0 - obj.Active = true - obj.Charge(ExoSuitType.Reinforced, Stance.Running) mustEqual 4 + "can not re-lock an unlocked implant slot" in { + val obj = new ImplantSlot + obj.Unlocked mustEqual false + + obj.Unlocked = false + obj.Unlocked mustEqual false + obj.Unlocked = true + obj.Unlocked mustEqual true + obj.Unlocked = false + obj.Unlocked mustEqual true + } + + "initialize without an implant" in { + val obj = new ImplantSlot + obj.Initialized mustEqual false + obj.Initialized = true + obj.Initialized mustEqual false + } + + "initialize an implant" in { + val obj = new ImplantSlot + obj.Initialized mustEqual false + + obj.Unlocked = true + obj.Implant = sample + obj.Initialized = true + obj.Initialized mustEqual true + } + + "activate an uninitialized implant" in { + val obj = new ImplantSlot + obj.Unlocked = true + obj.Implant = sample + obj.Initialized mustEqual false + obj.Active mustEqual false + + obj.Active = true + obj.Active mustEqual false + } + + "activate an initialized implant" in { + val obj = new ImplantSlot + obj.Unlocked = true + obj.Implant = sample + obj.Initialized mustEqual false + obj.Active mustEqual false + + obj.Initialized = true + obj.Active = true + obj.Active mustEqual true + } + + "not cost energy while not active" in { + val obj = new ImplantSlot + obj.Unlocked = true + obj.Implant = sample + obj.Initialized = true + obj.Active mustEqual false + obj.ActivationCharge mustEqual 0 + obj.Charge(ExoSuitType.Reinforced, Stance.Running) mustEqual 0 + } + + "cost energy while active" in { + val obj = new ImplantSlot + obj.Unlocked = true + obj.Implant = sample + obj.Initialized = true + obj.Active = true + obj.Active mustEqual true + obj.ActivationCharge mustEqual 3 + obj.Charge(ExoSuitType.Reinforced, Stance.Running) mustEqual 4 + } } } diff --git a/common/src/test/scala/objects/PlayerTest.scala b/common/src/test/scala/objects/PlayerTest.scala index fd2d3fc5..2a95f598 100644 --- a/common/src/test/scala/objects/PlayerTest.scala +++ b/common/src/test/scala/objects/PlayerTest.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package objects -import net.psforever.objects.{Implant, Player, SimpleItem} +import net.psforever.objects.{Player, SimpleItem} import net.psforever.objects.definition.{ImplantDefinition, SimpleItemDefinition} import net.psforever.objects.equipment.EquipmentSize import net.psforever.types.{CharacterGender, ExoSuitType, ImplantType, PlanetSideEmpire} @@ -106,31 +106,39 @@ class PlayerTest extends Specification { obj.LastDrawnSlot mustEqual 1 } - "install no implants until a slot is unlocked" in { - val testplant : Implant = Implant(ImplantDefinition(1)) + "install an implant" in { + val testplant : ImplantDefinition = ImplantDefinition(1) val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Implants(0).Unlocked mustEqual false - obj.Implant(0) mustEqual None - obj.InstallImplant(testplant) - obj.Implant(0) mustEqual None - obj.Implant(ImplantType(1)) mustEqual None - obj.Implants(0).Unlocked = true - obj.InstallImplant(testplant) - obj.Implant(0) mustEqual Some(testplant.Definition.Type) - obj.Implant(ImplantType(1)) mustEqual Some(testplant) + obj.InstallImplant(testplant) mustEqual true + obj.Implants.find({p => p.Implant == ImplantType(1)}) match { //find the installed implant + case Some(slot) => + slot.Installed mustEqual Some(testplant) + case _ => + ko + } + ok + } + + "can not install the same type of implant twice" in { + val testplant1 : ImplantDefinition = ImplantDefinition(1) + val testplant2 : ImplantDefinition = ImplantDefinition(1) + val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.Implants(1).Unlocked = true + obj.InstallImplant(testplant1) mustEqual true + obj.InstallImplant(testplant2) mustEqual false } "uninstall implants" in { - val testplant : Implant = Implant(ImplantDefinition(1)) + val testplant : ImplantDefinition = ImplantDefinition(1) val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Implants(0).Unlocked = true - obj.InstallImplant(testplant) - obj.Implant(ImplantType(1)) mustEqual Some(testplant) + obj.InstallImplant(testplant) mustEqual true + obj.Implants(0).Installed mustEqual Some(testplant) - obj.UninstallImplant(ImplantType(1)) - obj.Implant(0) mustEqual None - obj.Implant(ImplantType(1)) mustEqual None + obj.UninstallImplant(testplant.Type) + obj.Implants(0).Installed mustEqual None } "administrate" in { From 5444048e8d9ad09286bc9f7c76e31b35901a70b8 Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 8 Sep 2017 20:12:36 -0400 Subject: [PATCH 10/10] fixes for MeritCommendation tests --- .../game/DisplayedAwardMessageTest.scala | 4 +-- .../scala/game/ObjectCreateMessageTest.scala | 28 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/common/src/test/scala/game/DisplayedAwardMessageTest.scala b/common/src/test/scala/game/DisplayedAwardMessageTest.scala index 2c8a5851..cbe6d565 100644 --- a/common/src/test/scala/game/DisplayedAwardMessageTest.scala +++ b/common/src/test/scala/game/DisplayedAwardMessageTest.scala @@ -14,7 +14,7 @@ class DisplayedAwardMessageTest extends Specification { PacketCoding.DecodePacket(string).require match { case DisplayedAwardMessage(player_guid, ribbon, bar) => player_guid mustEqual PlanetSideGUID(1695) - ribbon mustEqual MeritCommendation.TwoYearTR + ribbon mustEqual MeritCommendation.TwoYearVS bar mustEqual RibbonBarsSlot.TermOfService case _ => ko @@ -22,7 +22,7 @@ class DisplayedAwardMessageTest extends Specification { } "encode" in { - val msg = DisplayedAwardMessage(PlanetSideGUID(1695), MeritCommendation.TwoYearTR, RibbonBarsSlot.TermOfService) + val msg = DisplayedAwardMessage(PlanetSideGUID(1695), MeritCommendation.TwoYearVS, RibbonBarsSlot.TermOfService) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string diff --git a/common/src/test/scala/game/ObjectCreateMessageTest.scala b/common/src/test/scala/game/ObjectCreateMessageTest.scala index af4d8df2..76b8669e 100644 --- a/common/src/test/scala/game/ObjectCreateMessageTest.scala +++ b/common/src/test/scala/game/ObjectCreateMessageTest.scala @@ -691,10 +691,10 @@ class ObjectCreateMessageTest extends Specification { pc.appearance.is_cloaking mustEqual false pc.appearance.charging_pose mustEqual false pc.appearance.on_zipline mustEqual false - pc.appearance.ribbons.upper mustEqual MeritCommendation.Loser4 - pc.appearance.ribbons.middle mustEqual MeritCommendation.HeavyInfantry3 - pc.appearance.ribbons.lower mustEqual MeritCommendation.TankBuster6 - pc.appearance.ribbons.tos mustEqual MeritCommendation.SixYearNC + 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 @@ -787,10 +787,10 @@ class ObjectCreateMessageTest extends Specification { pc.appearance.is_cloaking mustEqual false pc.appearance.charging_pose mustEqual false pc.appearance.on_zipline mustEqual false - pc.appearance.ribbons.upper mustEqual MeritCommendation.Jacking - pc.appearance.ribbons.middle mustEqual MeritCommendation.ScavengerTR6 + 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.SixYearTR + pc.appearance.ribbons.tos mustEqual MeritCommendation.SixYearVS pc.health mustEqual 0 pc.armor mustEqual 0 pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade @@ -1115,10 +1115,10 @@ class ObjectCreateMessageTest extends Specification { GrenadeState.None, false, false, false, RibbonBars( - MeritCommendation.Loser4, - MeritCommendation.HeavyInfantry3, - MeritCommendation.TankBuster6, - MeritCommendation.SixYearNC + MeritCommendation.MarkovVeteran, + MeritCommendation.HeavyInfantry4, + MeritCommendation.TankBuster7, + MeritCommendation.SixYearTR ) ), 255, 253, @@ -1172,10 +1172,10 @@ class ObjectCreateMessageTest extends Specification { GrenadeState.None, false, false, false, RibbonBars( - MeritCommendation.Jacking, - MeritCommendation.ScavengerTR6, + MeritCommendation.Jacking2, + MeritCommendation.ScavengerVS1, MeritCommendation.AMSSupport4, - MeritCommendation.SixYearTR + MeritCommendation.SixYearVS ) ), 0, 0,