adjusted packet Codec to deal with players with BR19+ levels of BEP

This commit is contained in:
FateJH 2017-08-31 22:44:28 -04:00
parent a4b14e5da4
commit 9566eb4381
3 changed files with 287 additions and 108 deletions

View file

@ -3,8 +3,7 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.{EquipmentSlot, Player} import net.psforever.objects.{EquipmentSlot, Player}
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.packet.game.PlanetSideGUID 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 net.psforever.types.GrenadeState
import scala.annotation.tailrec import scala.annotation.tailrec
@ -32,13 +31,15 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
Success( Success(
DetailedCharacterData( DetailedCharacterData(
MakeAppearanceData(obj), MakeAppearanceData(obj),
0,
obj.MaxHealth, obj.MaxHealth,
obj.Health, obj.Health,
obj.Armor, obj.Armor,
1, 7, 7, 1, 7, 7,
obj.MaxStamina, obj.MaxStamina,
obj.Stamina, 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 fte list
List.empty[String], //TODO tutorial list List.empty[String], //TODO tutorial list
InventoryData((MakeHolsters(obj, BuildDetailedEquipment) ++ MakeFifthSlot(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)), InventoryData((MakeHolsters(obj, BuildDetailedEquipment) ++ MakeFifthSlot(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)),

View file

@ -2,10 +2,44 @@
package net.psforever.packet.game.objectcreate package net.psforever.packet.game.objectcreate
import net.psforever.packet.{Marshallable, PacketHelpers} import net.psforever.packet.{Marshallable, PacketHelpers}
import scodec.{Attempt, Codec} import scodec.{Attempt, Codec, Err}
import scodec.codecs._ import scodec.codecs._
import shapeless.{::, HNil} 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. * 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.<br> * This densely-packed information outlines most of the specifics required to depict a character as an avatar.<br>
@ -46,14 +80,7 @@ import shapeless.{::, HNil}
* defaults to 28 * defaults to 28
* @param unk5 na; * @param unk5 na;
* defaults to 4 * defaults to 4
* @param unk6 na; * @param brFields na
* defaults to 44
* @param unk7 na;
* defaults to 84
* @param unk8 na;
* defaults to 104
* @param unk9 na;
* defaults to 1900
* @param firstTimeEvents the list of first time events performed by this avatar; * @param firstTimeEvents the list of first time events performed by this avatar;
* the size field is a 32-bit number; * the size field is a 32-bit number;
* the first entry may be padded * the first entry may be padded
@ -68,6 +95,7 @@ import shapeless.{::, HNil}
* @see `DrawnSlot` * @see `DrawnSlot`
*/ */
final case class DetailedCharacterData(appearance : CharacterAppearanceData, final case class DetailedCharacterData(appearance : CharacterAppearanceData,
bep : Int,
healthMax : Int, healthMax : Int,
health : Int, health : Int,
armor : Int, armor : Int,
@ -78,26 +106,24 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData,
stamina : Int, stamina : Int,
unk4 : Int, //28 unk4 : Int, //28
unk5 : Int, //4 unk5 : Int, //4
unk6 : Int, //44 brFields : BattleRankFieldData,
unk7 : Int, //84
unk8 : Int, //104
unk9 : Int, //1900
firstTimeEvents : List[String], firstTimeEvents : List[String],
tutorials : List[String], tutorials : List[String],
inventory : Option[InventoryData], inventory : Option[InventoryData],
drawn_slot : DrawnSlot.Value = DrawnSlot.None drawn_slot : DrawnSlot.Value = DrawnSlot.None
) extends ConstructorData { ) extends ConstructorData {
override def bitsize : Long = { 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 its corresponding optional field
val appearanceSize = appearance.bitsize val appearanceSize = appearance.bitsize
val brFieldSize = brFields.bitsize
val fteLen = firstTimeEvents.size //fte list val fteLen = firstTimeEvents.size //fte list
var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen) var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen, bep)
for(str <- firstTimeEvents) { for(str <- firstTimeEvents) {
eventListSize += StreamBitSize.stringBitSize(str) eventListSize += StreamBitSize.stringBitSize(str)
} }
val tutLen = tutorials.size //tutorial list 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) { for(str <- tutorials) {
tutorialListSize += StreamBitSize.stringBitSize(str) tutorialListSize += StreamBitSize.stringBitSize(str)
} }
@ -105,28 +131,28 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData,
if(inventory.isDefined) { if(inventory.isDefined) {
inventorySize = inventory.get.bitsize inventorySize = inventory.get.bitsize
} }
713L + appearanceSize + eventListSize + tutorialListSize + inventorySize 658L + appearanceSize + brFieldSize + eventListSize + tutorialListSize + inventorySize
} }
} }
object DetailedCharacterData extends Marshallable[DetailedCharacterData] { object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
/** // /**
* Overloaded constructor for `DetailedCharacterData` that skips all the unknowns by assigning defaulted values. // * Overloaded constructor for `DetailedCharacterData` that skips all the unknowns by assigning defaulted values.
* It also allows for a not-optional inventory. // * It also allows for a not-optional inventory.
* @param appearance data about the avatar's basic aesthetics // * @param appearance data about the avatar's basic aesthetics
* @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value // * @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 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 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 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 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 firstTimeEvents the list of first time events performed by this avatar
* @param tutorials the list of tutorials completed by this avatar // * @param tutorials the list of tutorials completed by this avatar
* @param inventory the avatar's inventory // * @param inventory the avatar's inventory
* @param drawn_slot the holster that is initially drawn // * @param drawn_slot the holster that is initially drawn
* @return a `DetailedCharacterData` object // * @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 = // 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, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, 28, 4, 44, 84, 104, 1900, firstTimeEvents, tutorials, Some(inventory), drawn_slot) // 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. * Overloaded constructor for `DetailedCharacterData` that allows for a not-optional inventory.
@ -142,97 +168,244 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
* @param unk4 na * @param unk4 na
* @param unk5 na * @param unk5 na
* @param unk6 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 firstTimeEvents the list of first time events performed by this avatar
* @param tutorials the list of tutorials completed by this avatar * @param tutorials the list of tutorials completed by this avatar
* @param inventory the avatar's inventory * @param inventory the avatar's inventory
* @param drawn_slot the holster that is initially drawn * @param drawn_slot the holster that is initially drawn
* @return a `DetailedCharacterData` object * @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 = 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, healthMax, health, armor, unk1, unk2, unk3, staminaMax, stamina, unk4, unk5, unk6, unk7, unk8, unk9, firstTimeEvents, tutorials, Some(inventory), drawn_slot) 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. * na
* The padding will always be a number 0-7. * @param bep the battle experience points
* @param len the length of the list * @return the appropriate `Codec` for the fields representing a player with the implied battle rank
* @return the pad length in bits
*/ */
private def ftePadding(len : Long) : Int = { private def selectBattleRankFieldCodec(bep : Int) : Codec[BattleRankFieldData] = {
//TODO the parameters for this function are not correct if(bep > 754370) {
//TODO the proper padding length should reflect all variability in the stream prior to this point br18FieldCodec
if(len > 0) { }
5 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 value of the first entry in either of two byte-aligned `List` structures.
* The padding will always be a number 0-7.<br> * @param bep the battle experience points
* <br> * @return the pad length in bits `n < 8`
* 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
*/ */
private def tutPadding(len : Long, len2 : Long) : Int = { private def bepFieldPadding(bep : Int) : Int = {
if(len > 0) //automatic alignment from previous List if(bep > 754370) { //BR18+
0 7
else if(len2 > 0) //need to align for elements }
else if(bep > 197753) { //BR12+
1
}
else if(bep > 29999) { //BR6+
3
}
else { //BR1+
5 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 0
}
}
/**
* Get the padding of the first entry in the completed tutorials list.<br>
* <br>
* 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] = ( implicit val codec : Codec[DetailedCharacterData] = (
("appearance" | CharacterAppearanceData.codec) :: ("appearance" | CharacterAppearanceData.codec) ::
ignore(160) :: (("bep" | uint24L) >>:~ { bep =>
("healthMax" | uint16L) :: ignore(136) ::
("health" | uint16L) :: ("healthMax" | uint16L) ::
ignore(1) :: ("health" | uint16L) ::
("armor" | uint16L) :: ignore(1) ::
ignore(9) :: ("armor" | uint16L) ::
("unk1" | uint8L) :: ignore(9) ::
ignore(8) :: ("unk1" | uint8L) ::
("unk2" | uint4L) :: ignore(8) ::
("unk3" | uintL(3)) :: ("unk2" | uint4L) ::
("staminaMax" | uint16L) :: ("unk3" | uintL(3)) ::
("stamina" | uint16L) :: ("staminaMax" | uint16L) ::
ignore(149) :: ("stamina" | uint16L) ::
("unk4" | uint16L) :: ignore(149) ::
("unk5" | uint8L) :: ("unk4" | uint16L) ::
("unk6" | uint8L) :: ("unk5" | uint8L) ::
("unk7" | uint8L) :: ("brFields" | selectBattleRankFieldCodec(bep)) :: //TODO do this for all these fields until their bits are better defined
("unk8" | uint8L) :: (("firstTimeEvent_length" | uint32L) >>:~ { len =>
("unk9" | uintL(12)) :: conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned(ftePadding(len, bep))) ::
ignore(19) :: ("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) ::
(("firstTimeEvent_length" | uint32L) >>:~ { len => (("tutorial_length" | uint32L) >>:~ { len2 =>
conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned( ftePadding(len) )) :: conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned(tutPadding(len, len2, bep))) ::
("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) :: ("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) ::
(("tutorial_length" | uint32L) >>:~ { len2 => ignore(207) ::
conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned( tutPadding(len, len2) )) :: optional(bool, "inventory" | InventoryData.codec_detailed) ::
("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) :: ("drawn_slot" | DrawnSlot.codec) ::
ignore(207) :: bool //usually false
optional(bool, "inventory" | InventoryData.codec_detailed) :: })
("drawn_slot" | DrawnSlot.codec) :: })
bool //usually false
})
}) })
).exmap[DetailedCharacterData] ( ).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 //prepend the displaced first elements to their lists
val fteList : List[String] = if(q.isDefined) { q.get :: r } else r val fteList : List[String] = if(fte0.isDefined) { fte0.get +: fte1 } else fte1
val tutList : List[String] = if(t.isDefined) { t.get :: u } else u val tutList : List[String] = if(tut0.isDefined) { tut0.get +: tut1 } else tut1
Attempt.successful(DetailedCharacterData(app, b, c, d, e, f, g, h, i, j, k, l, m, n, o, fteList, tutList, v, w)) 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 //shift the first elements off their lists
var fteListCopy = fteList var fteListCopy = fteList
var firstEvent : Option[String] = None var firstEvent : Option[String] = None
@ -246,7 +419,7 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
firstTutorial = Some(tutList.head) firstTutorial = Some(tutList.head)
tutListCopy = tutList.drop(1) 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)
} }
) )
} }

View file

@ -216,10 +216,13 @@ class ObjectCreateDetailedMessageTest extends Specification {
char.stamina mustEqual 100 char.stamina mustEqual 100
char.unk4 mustEqual 28 char.unk4 mustEqual 28
char.unk5 mustEqual 4 char.unk5 mustEqual 4
char.unk6 mustEqual 44 char.brFields.field00 mustEqual 44
char.unk7 mustEqual 84 char.brFields.field01 mustEqual 84
char.unk8 mustEqual 104 char.brFields.field02 mustEqual 104
char.unk9 mustEqual 1900 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.size mustEqual 4
char.firstTimeEvents.head mustEqual "xpe_sanctuary_help" char.firstTimeEvents.head mustEqual "xpe_sanctuary_help"
char.firstTimeEvents(1) mustEqual "xpe_th_firemodes" char.firstTimeEvents(1) mustEqual "xpe_th_firemodes"
@ -405,11 +408,13 @@ class ObjectCreateDetailedMessageTest extends Specification {
Nil Nil
val obj = DetailedCharacterData( val obj = DetailedCharacterData(
app, app,
0,
100, 100, 100, 100,
50, 50,
1, 7, 7, 1, 7, 7,
100, 100, 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, "xpe_sanctuary_help" :: "xpe_th_firemodes" :: "used_beamer" :: "map13" :: Nil,
List.empty, List.empty,
InventoryData(inv), InventoryData(inv),