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

View file

@ -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.<br>
@ -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.<br>
* <br>
* 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.<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] = (
("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)
}
)
}

View file

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