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),