mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-07-13 15:34:42 +00:00
fixed OCDM for BR24+; updated AvatarConverter; moved Cosmetics into own file as a StreamBitSize; created and implemented truncated converter for character select screen; modified DetailedREKData based on potential field
This commit is contained in:
parent
47adfef5c8
commit
4ac93de065
8 changed files with 124 additions and 149 deletions
|
|
@ -3,7 +3,7 @@ package net.psforever.objects.definition.converter
|
||||||
|
|
||||||
import net.psforever.objects.{EquipmentSlot, GlobalDefinitions, ImplantSlot, Player}
|
import net.psforever.objects.{EquipmentSlot, GlobalDefinitions, ImplantSlot, Player}
|
||||||
import net.psforever.objects.equipment.Equipment
|
import net.psforever.objects.equipment.Equipment
|
||||||
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, ImplantEffects, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle}
|
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, Cosmetics, DetailedCharacterData, DrawnSlot, ImplantEffects, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle}
|
||||||
import net.psforever.types.{GrenadeState, ImplantType}
|
import net.psforever.types.{GrenadeState, ImplantType}
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
|
|
@ -40,8 +40,9 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
|
||||||
obj.Stamina,
|
obj.Stamina,
|
||||||
obj.Certifications.toList.sortBy(_.id), //TODO is sorting necessary?
|
obj.Certifications.toList.sortBy(_.id), //TODO is sorting necessary?
|
||||||
MakeImplantEntries(obj),
|
MakeImplantEntries(obj),
|
||||||
"xpe_battle_rank_10" :: Nil, //TODO fte list
|
List.empty[String], //TODO fte list
|
||||||
List.empty[String], //TODO tutorial list
|
List.empty[String], //TODO tutorial list
|
||||||
|
MakeCosmetics(obj.BEP),
|
||||||
InventoryData((MakeHolsters(obj, BuildDetailedEquipment) ++ MakeFifthSlot(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)),
|
InventoryData((MakeHolsters(obj, BuildDetailedEquipment) ++ MakeFifthSlot(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)),
|
||||||
GetDrawnSlot(obj)
|
GetDrawnSlot(obj)
|
||||||
)
|
)
|
||||||
|
|
@ -132,7 +133,7 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
|
||||||
* @see `ImplantEntry` in `DetailedCharacterData`
|
* @see `ImplantEntry` in `DetailedCharacterData`
|
||||||
*/
|
*/
|
||||||
private def MakeImplantEntries(obj : Player) : List[ImplantEntry] = {
|
private def MakeImplantEntries(obj : Player) : List[ImplantEntry] = {
|
||||||
val numImplants : Int = NumberOfImplantSlots(obj.BEP)
|
val numImplants : Int = DetailedCharacterData.numberOfImplantSlots(obj.BEP)
|
||||||
val implants = obj.Implants
|
val implants = obj.Implants
|
||||||
(0 until numImplants).map(index => {
|
(0 until numImplants).map(index => {
|
||||||
val slot = implants(index)
|
val slot = implants(index)
|
||||||
|
|
@ -150,27 +151,6 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
|
||||||
}).toList
|
}).toList
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 NumberOfImplantSlots(bep : Long) : Int = {
|
|
||||||
if(bep > 754370) { //BR18+
|
|
||||||
3
|
|
||||||
}
|
|
||||||
else if(bep > 197753) { //BR12+
|
|
||||||
2
|
|
||||||
}
|
|
||||||
else if(bep > 29999) { //BR6+
|
|
||||||
1
|
|
||||||
}
|
|
||||||
else { //BR1+
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find an active implant whose effect will be displayed on this player.
|
* Find an active implant whose effect will be displayed on this player.
|
||||||
* @param iter an `Iterator` of `ImplantSlot` objects
|
* @param iter an `Iterator` of `ImplantSlot` objects
|
||||||
|
|
@ -200,6 +180,20 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should this player be of battle rank 24 or higher, they will have a mandatory cosmetics object.
|
||||||
|
* @param bep battle experience points
|
||||||
|
* @see `Cosmetics`
|
||||||
|
* @return the `Cosmetics` options
|
||||||
|
*/
|
||||||
|
protected def MakeCosmetics(bep : Long) : Option[Cosmetics] =
|
||||||
|
if(DetailedCharacterData.isBR24(bep)) {
|
||||||
|
Some(Cosmetics(false, false, false, false, false))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a player with an inventory, convert the contents of that inventory into converted-decoded packet data.
|
* 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.
|
* The inventory is not represented in a `0x17` `Player`, so the conversion is only valid for `0x18` avatars.
|
||||||
|
|
@ -260,7 +254,7 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
|
||||||
* @param equip the game object
|
* @param equip the game object
|
||||||
* @return the game object in decoded packet form
|
* @return the game object in decoded packet form
|
||||||
*/
|
*/
|
||||||
private def BuildDetailedEquipment(index : Int, equip : Equipment) : InternalSlot = {
|
protected def BuildDetailedEquipment(index : Int, equip : Equipment) : InternalSlot = {
|
||||||
InternalSlot(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.Packet.DetailedConstructorData(equip).get)
|
InternalSlot(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.Packet.DetailedConstructorData(equip).get)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -298,7 +292,7 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
|
||||||
* @param obj the `Player` game object
|
* @param obj the `Player` game object
|
||||||
* @return the holster's Enumeration value
|
* @return the holster's Enumeration value
|
||||||
*/
|
*/
|
||||||
private def GetDrawnSlot(obj : Player) : DrawnSlot.Value = {
|
protected def GetDrawnSlot(obj : Player) : DrawnSlot.Value = {
|
||||||
try { DrawnSlot(obj.DrawnSlot) } catch { case _ : Exception => DrawnSlot.None }
|
try { DrawnSlot(obj.DrawnSlot) } catch { case _ : Exception => DrawnSlot.None }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,20 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects.definition.converter
|
package net.psforever.objects.definition.converter
|
||||||
|
|
||||||
import net.psforever.objects.GlobalDefinitions.{advanced_regen, darklight_vision, personal_shield, surge}
|
import net.psforever.objects.{EquipmentSlot, Player}
|
||||||
import net.psforever.objects.{EquipmentSlot, GlobalDefinitions, ImplantSlot, Player}
|
|
||||||
import net.psforever.objects.equipment.Equipment
|
import net.psforever.objects.equipment.Equipment
|
||||||
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, ImplantEffects, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars}
|
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars}
|
||||||
import net.psforever.types.{GrenadeState, ImplantType}
|
import net.psforever.types.{GrenadeState, ImplantType}
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `CharacterSelectConverter` is based on `AvatarConverter`
|
* `CharacterSelectConverter` is a simplified `AvatarConverter`
|
||||||
* but it is tailored for appearance of the player character on the character selection screen only.
|
* that is tailored for appearance of the player character on the character selection screen.
|
||||||
* Details that would not be apparent on that screen such as implants or certifications are ignored.
|
* Details that would not be apparent on that screen such as implants or certifications are ignored.
|
||||||
*/
|
*/
|
||||||
class CharacterSelectConverter extends ObjectCreateConverter[Player]() {
|
class CharacterSelectConverter extends AvatarConverter {
|
||||||
override def ConstructorData(obj : Player) : Try[CharacterData] = Failure(new Exception("CharacterSelectConverter should not be used to generate CharacterData"))
|
override def ConstructorData(obj : Player) : Try[CharacterData] = Failure(new Exception("CharacterSelectConverter should not be used to generate CharacterData"))
|
||||||
|
|
||||||
override def DetailedConstructorData(obj : Player) : Try[DetailedCharacterData] = {
|
override def DetailedConstructorData(obj : Player) : Try[DetailedCharacterData] = {
|
||||||
|
|
@ -26,8 +25,9 @@ class CharacterSelectConverter extends ObjectCreateConverter[Player]() {
|
||||||
obj.CEP,
|
obj.CEP,
|
||||||
1, 1, 0, 1, 1,
|
1, 1, 0, 1, 1,
|
||||||
Nil,
|
Nil,
|
||||||
MakeImplantEntries(obj),
|
MakeImplantEntries(obj), //necessary for correct stream length
|
||||||
Nil, Nil,
|
Nil, Nil,
|
||||||
|
MakeCosmetics(obj.BEP),
|
||||||
InventoryData(recursiveMakeHolsters(obj.Holsters().iterator)),
|
InventoryData(recursiveMakeHolsters(obj.Holsters().iterator)),
|
||||||
GetDrawnSlot(obj)
|
GetDrawnSlot(obj)
|
||||||
)
|
)
|
||||||
|
|
@ -69,39 +69,7 @@ class CharacterSelectConverter extends ObjectCreateConverter[Player]() {
|
||||||
* @see `ImplantEntry` in `DetailedCharacterData`
|
* @see `ImplantEntry` in `DetailedCharacterData`
|
||||||
*/
|
*/
|
||||||
private def MakeImplantEntries(obj : Player) : List[ImplantEntry] = {
|
private def MakeImplantEntries(obj : Player) : List[ImplantEntry] = {
|
||||||
List.fill[ImplantEntry](NumberOfImplantSlots(obj.BEP))(ImplantEntry(ImplantType.None, None))
|
List.fill[ImplantEntry](DetailedCharacterData.numberOfImplantSlots(obj.BEP))(ImplantEntry(ImplantType.None, None))
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 NumberOfImplantSlots(bep : Long) : Int = {
|
|
||||||
if(bep > 754370) { //BR18+
|
|
||||||
3
|
|
||||||
}
|
|
||||||
else if(bep > 197753) { //BR12+
|
|
||||||
2
|
|
||||||
}
|
|
||||||
else if(bep > 29999) { //BR6+
|
|
||||||
1
|
|
||||||
}
|
|
||||||
else { //BR1+
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A builder method for turning an object into `0x18` decoded packet form.
|
|
||||||
* @param index the position of the object
|
|
||||||
* @param equip the game object
|
|
||||||
* @see `AvatarConverter.BuildDetailedEquipment`
|
|
||||||
* @return the game object in decoded packet form
|
|
||||||
*/
|
|
||||||
private def BuildDetailedEquipment(index : Int, equip : Equipment) : InternalSlot = {
|
|
||||||
InternalSlot(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.Packet.DetailedConstructorData(equip).get)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -131,14 +99,4 @@ class CharacterSelectConverter extends ObjectCreateConverter[Player]() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve which holster the player has drawn, if any.
|
|
||||||
* @param obj the `Player` game object
|
|
||||||
* @see `AvatarConverter.GetDrawnSlot`
|
|
||||||
* @return the holster's Enumeration value
|
|
||||||
*/
|
|
||||||
private def GetDrawnSlot(obj : Player) : DrawnSlot.Value = {
|
|
||||||
try { DrawnSlot(obj.DrawnSlot) } catch { case _ : Exception => DrawnSlot.None }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,24 +41,6 @@ object UniformStyle extends Enumeration {
|
||||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uintL(3))
|
implicit val codec = PacketHelpers.createEnumerationCodec(this, uintL(3))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The different cosmetics that a player can apply to their model's head.<br>
|
|
||||||
* <br>
|
|
||||||
* The player gets the ability to apply these minor modifications at battle rank twenty-four, just one rank before the third uniform upgrade.
|
|
||||||
* @param no_helmet removes the current helmet on the reinforced exo-suit and the agile exo-suit;
|
|
||||||
* all other cosmetics require `no_helmet` to be `true` before they can be seen
|
|
||||||
* @param beret player dons a beret
|
|
||||||
* @param sunglasses player dons sunglasses
|
|
||||||
* @param earpiece player dons an earpiece on the left
|
|
||||||
* @param brimmed_cap player dons a cap;
|
|
||||||
* the cap overrides the beret, if both are selected
|
|
||||||
*/
|
|
||||||
final case class Cosmetics(no_helmet : Boolean,
|
|
||||||
beret : Boolean,
|
|
||||||
sunglasses : Boolean,
|
|
||||||
earpiece : Boolean,
|
|
||||||
brimmed_cap : Boolean)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A part of a representation of the avatar portion of `ObjectCreateMessage` packet data.
|
* A part of a representation of the avatar portion of `ObjectCreateMessage` packet data.
|
||||||
* This densely-packed information outlines most of the specifics of depicting some other character.<br>
|
* This densely-packed information outlines most of the specifics of depicting some other character.<br>
|
||||||
|
|
@ -118,7 +100,7 @@ final case class CharacterData(appearance : CharacterAppearanceData,
|
||||||
//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 : Long = appearance.bitsize
|
val appearanceSize : Long = appearance.bitsize
|
||||||
val effectsSize : Long = if(implant_effects.isDefined) { 4L } else { 0L }
|
val effectsSize : Long = if(implant_effects.isDefined) { 4L } else { 0L }
|
||||||
val cosmeticsSize : Long = if(cosmetics.isDefined) { 5L } else { 0L }
|
val cosmeticsSize : Long = if(cosmetics.isDefined) { cosmetics.get.bitsize } else { 0L }
|
||||||
val inventorySize : Long = if(inventory.isDefined) { inventory.get.bitsize } else { 0L }
|
val inventorySize : Long = if(inventory.isDefined) { inventory.get.bitsize } else { 0L }
|
||||||
32L + appearanceSize + effectsSize + cosmeticsSize + inventorySize
|
32L + appearanceSize + effectsSize + cosmeticsSize + inventorySize
|
||||||
}
|
}
|
||||||
|
|
@ -141,19 +123,6 @@ object CharacterData extends Marshallable[CharacterData] {
|
||||||
def apply(appearance : CharacterAppearanceData, health : Int, armor : Int, uniform : UniformStyle.Value, cr : Int, implant_effects : Option[ImplantEffects.Value], cosmetics : Option[Cosmetics], inv : InventoryData, drawn_slot : DrawnSlot.Value) : CharacterData =
|
def apply(appearance : CharacterAppearanceData, health : Int, armor : Int, uniform : UniformStyle.Value, cr : Int, implant_effects : Option[ImplantEffects.Value], cosmetics : Option[Cosmetics], inv : InventoryData, drawn_slot : DrawnSlot.Value) : CharacterData =
|
||||||
new CharacterData(appearance, health, armor, uniform, cr, implant_effects, cosmetics, Some(inv), drawn_slot)
|
new CharacterData(appearance, health, armor, uniform, cr, implant_effects, cosmetics, Some(inv), drawn_slot)
|
||||||
|
|
||||||
/**
|
|
||||||
* Check for the bit flags for the cosmetic items.
|
|
||||||
* These flags are only valid if the player has acquired their third uniform upgrade.
|
|
||||||
* @see `UniformStyle.ThirdUpgrade`
|
|
||||||
*/
|
|
||||||
private val cosmeticsCodec : Codec[Cosmetics] = (
|
|
||||||
("no_helmet" | bool) ::
|
|
||||||
("beret" | bool) ::
|
|
||||||
("sunglasses" | bool) ::
|
|
||||||
("earpiece" | bool) ::
|
|
||||||
("brimmed_cap" | bool)
|
|
||||||
).as[Cosmetics]
|
|
||||||
|
|
||||||
implicit val codec : Codec[CharacterData] = (
|
implicit val codec : Codec[CharacterData] = (
|
||||||
("app" | CharacterAppearanceData.codec) ::
|
("app" | CharacterAppearanceData.codec) ::
|
||||||
("health" | uint8L) :: //dead state when health == 0
|
("health" | uint8L) :: //dead state when health == 0
|
||||||
|
|
@ -163,7 +132,7 @@ object CharacterData extends Marshallable[CharacterData] {
|
||||||
("command_rank" | uintL(3)) ::
|
("command_rank" | uintL(3)) ::
|
||||||
bool :: //stream misalignment when != 1
|
bool :: //stream misalignment when != 1
|
||||||
optional(bool, "implant_effects" | ImplantEffects.codec) ::
|
optional(bool, "implant_effects" | ImplantEffects.codec) ::
|
||||||
conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | cosmeticsCodec) ::
|
conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec) ::
|
||||||
optional(bool, "inventory" | InventoryData.codec) ::
|
optional(bool, "inventory" | InventoryData.codec) ::
|
||||||
("drawn_slot" | DrawnSlot.codec) ::
|
("drawn_slot" | DrawnSlot.codec) ::
|
||||||
bool //usually false
|
bool //usually false
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright (c) 2017 PSForever
|
||||||
|
package net.psforever.packet.game.objectcreate
|
||||||
|
|
||||||
|
import scodec.codecs._
|
||||||
|
import scodec.Codec
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The different cosmetics that a player can apply to their character model's head.<br>
|
||||||
|
* <br>
|
||||||
|
* The player gets the ability to apply these minor modifications at battle rank twenty-four, just one rank before the third uniform upgrade.
|
||||||
|
* These flags are only valid if the player has:
|
||||||
|
* for `DetailedCharacterData`, achieved at least battle rank twenty-four (battle experience points greater than 2286230),
|
||||||
|
* or, for `CharacterData`, achieved at least battle rank twenty-five (acquired their third uniform upgrade).
|
||||||
|
* `CharacterData`, as implied, will not display these options until one battle rank after they would have become available.
|
||||||
|
* @param no_helmet removes the current helmet on the reinforced exo-suit and the agile exo-suit;
|
||||||
|
* all other cosmetics require `no_helmet` to be `true` before they can be seen
|
||||||
|
* @param beret player dons a beret
|
||||||
|
* @param sunglasses player dons sunglasses
|
||||||
|
* @param earpiece player dons an earpiece on the left
|
||||||
|
* @param brimmed_cap player dons a cap;
|
||||||
|
* the cap overrides the beret, if both are selected
|
||||||
|
* @see `UniformStyle.ThirdUpgrade`
|
||||||
|
*/
|
||||||
|
final case class Cosmetics(no_helmet : Boolean,
|
||||||
|
beret : Boolean,
|
||||||
|
sunglasses : Boolean,
|
||||||
|
earpiece : Boolean,
|
||||||
|
brimmed_cap : Boolean
|
||||||
|
) extends StreamBitSize {
|
||||||
|
override def bitsize : Long = 5L
|
||||||
|
}
|
||||||
|
|
||||||
|
object Cosmetics {
|
||||||
|
implicit val codec : Codec[Cosmetics] = (
|
||||||
|
("no_helmet" | bool) ::
|
||||||
|
("beret" | bool) ::
|
||||||
|
("sunglasses" | bool) ::
|
||||||
|
("earpiece" | bool) ::
|
||||||
|
("brimmed_cap" | bool)
|
||||||
|
).as[Cosmetics]
|
||||||
|
}
|
||||||
|
|
@ -70,6 +70,9 @@ final case class ImplantEntry(implant : ImplantType.Value,
|
||||||
* @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 size field is a 32-bit number;
|
||||||
* the first entry may be padded
|
* the first entry may be padded
|
||||||
|
* @param cosmetics optional decorative features that are added to the player's head model by console/chat commands;
|
||||||
|
* they become available at battle rank 24;
|
||||||
|
* these flags do not exist if they are not applicable
|
||||||
* @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
|
||||||
* @see `CharacterAppearanceData`<br>
|
* @see `CharacterAppearanceData`<br>
|
||||||
|
|
@ -93,6 +96,7 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData,
|
||||||
implants : List[ImplantEntry],
|
implants : List[ImplantEntry],
|
||||||
firstTimeEvents : List[String],
|
firstTimeEvents : List[String],
|
||||||
tutorials : List[String],
|
tutorials : List[String],
|
||||||
|
cosmetics : Option[Cosmetics],
|
||||||
inventory : Option[InventoryData],
|
inventory : Option[InventoryData],
|
||||||
drawn_slot : DrawnSlot.Value = DrawnSlot.None
|
drawn_slot : DrawnSlot.Value = DrawnSlot.None
|
||||||
) extends ConstructorData {
|
) extends ConstructorData {
|
||||||
|
|
@ -116,13 +120,16 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData,
|
||||||
for(str <- tutorials) {
|
for(str <- tutorials) {
|
||||||
tutorialListSize += StreamBitSize.stringBitSize(str)
|
tutorialListSize += StreamBitSize.stringBitSize(str)
|
||||||
}
|
}
|
||||||
|
val br24 = DetailedCharacterData.isBR24(bep) //character is at least BR24
|
||||||
|
val extraBitSize : Long = if(br24) { 33L } else { 46L }
|
||||||
|
val cosmeticsSize : Long = if(br24) { cosmetics.get.bitsize } else { 0L }
|
||||||
val inventorySize : Long = if(inventory.isDefined) { //inventory
|
val inventorySize : Long = if(inventory.isDefined) { //inventory
|
||||||
inventory.get.bitsize
|
inventory.get.bitsize
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
0L
|
0L
|
||||||
}
|
}
|
||||||
649L + appearanceSize + certSize + implantSize + eventListSize + tutorialListSize + inventorySize
|
603L + appearanceSize + certSize + implantSize + eventListSize + extraBitSize + cosmeticsSize + tutorialListSize + inventorySize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,8 +152,8 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
* @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, 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 =
|
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], cosmetics : Option[Cosmetics], 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)
|
new DetailedCharacterData(appearance, bep, cep, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, certs, implants, firstTimeEvents, tutorials, cosmetics, Some(inventory), drawn_slot)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `Codec` for entries in the `List` of implants.
|
* `Codec` for entries in the `List` of implants.
|
||||||
|
|
@ -179,7 +186,7 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
* @param bep battle experience points
|
* @param bep battle experience points
|
||||||
* @return the number of accessible implant slots
|
* @return the number of accessible implant slots
|
||||||
*/
|
*/
|
||||||
private def numberOfImplantSlots(bep : Long) : Int = {
|
def numberOfImplantSlots(bep : Long) : Int = {
|
||||||
if(bep > 754370) { //BR18+
|
if(bep > 754370) { //BR18+
|
||||||
3
|
3
|
||||||
}
|
}
|
||||||
|
|
@ -269,6 +276,8 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def isBR24(bep : Long) : Boolean = bep > 2286230
|
||||||
|
|
||||||
implicit val codec : Codec[DetailedCharacterData] = (
|
implicit val codec : Codec[DetailedCharacterData] = (
|
||||||
("appearance" | CharacterAppearanceData.codec) >>:~ { app =>
|
("appearance" | CharacterAppearanceData.codec) >>:~ { app =>
|
||||||
("bep" | uint32L) >>:~ { bep =>
|
("bep" | uint32L) >>:~ { bep =>
|
||||||
|
|
@ -297,10 +306,14 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
(("tutorial_length" | uint32L) >>:~ { len2 =>
|
(("tutorial_length" | uint32L) >>:~ { len2 =>
|
||||||
conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned(tutPadding(len, len2, implantFieldPadding(implants, CharacterAppearanceData.altModelBit(app))))) ::
|
conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned(tutPadding(len, len2, implantFieldPadding(implants, CharacterAppearanceData.altModelBit(app))))) ::
|
||||||
("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) ::
|
("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) ::
|
||||||
ignore(200) ::
|
ignore(160) ::
|
||||||
conditional(true, "inventory" | InventoryData.codec_detailed) ::
|
(bool >>:~ { br24 => //BR24+
|
||||||
("drawn_slot" | DrawnSlot.codec) ::
|
newcodecs.binary_choice(br24, ignore(33), ignore(46)) ::
|
||||||
bool //usually false
|
conditional(br24, Cosmetics.codec) ::
|
||||||
|
optional(bool, "inventory" | InventoryData.codec_detailed) ::
|
||||||
|
("drawn_slot" | DrawnSlot.codec) ::
|
||||||
|
bool //usually false
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -308,14 +321,14 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
}
|
}
|
||||||
).exmap[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 :: _ :: _ :: _ :: cosmetics :: 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(fte0.isDefined) { fte0.get +: fte1 } else fte1
|
val fteList : List[String] = if(fte0.isDefined) { fte0.get +: fte1 } else { fte1 }
|
||||||
val tutList : List[String] = if(tut0.isDefined) { tut0.get +: tut1 } else tut1
|
val tutList : List[String] = if(tut0.isDefined) { tut0.get +: tut1 } else { tut1 }
|
||||||
Attempt.successful(DetailedCharacterData(app, bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, inv, drawn))
|
Attempt.successful(DetailedCharacterData(app, bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cosmetics, inv, drawn))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case DetailedCharacterData(app, bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, inv, drawn) =>
|
case DetailedCharacterData(app, bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cos, inv, drawn) =>
|
||||||
val implantCapacity : Int = numberOfImplantSlots(bep)
|
val implantCapacity : Int = numberOfImplantSlots(bep)
|
||||||
val implantList = if(implants.length > implantCapacity) {
|
val implantList = if(implants.length > implantCapacity) {
|
||||||
implants.slice(0, implantCapacity)
|
implants.slice(0, implantCapacity)
|
||||||
|
|
@ -334,7 +347,9 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
case ((f : String) +: (rest : List[String])) => (Some(f), rest)
|
case ((f : String) +: (rest : List[String])) => (Some(f), rest)
|
||||||
case Nil => (None, Nil)
|
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)
|
val br24 : Boolean = isBR24(bep)
|
||||||
|
val cosmetics : Option[Cosmetics] = if(br24) { cos } else { None }
|
||||||
|
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 :: () :: br24 :: () :: cosmetics :: inv :: drawn :: false :: HNil)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,12 @@ import shapeless.{::, HNil}
|
||||||
* This data will help construct the "tool" called a Remote Electronics Kit.<br>
|
* This data will help construct the "tool" called a Remote Electronics Kit.<br>
|
||||||
* <br>
|
* <br>
|
||||||
* Of note is the first portion of the data which resembles the `DetailedWeaponData` format.
|
* Of note is the first portion of the data which resembles the `DetailedWeaponData` format.
|
||||||
* @param unk na
|
* @param unk1 na
|
||||||
|
* @param unk2 na
|
||||||
*/
|
*/
|
||||||
final case class DetailedREKData(unk : Int) extends ConstructorData {
|
final case class DetailedREKData(unk1 : Int,
|
||||||
|
unk2 : Int = 0
|
||||||
|
) extends ConstructorData {
|
||||||
override def bitsize : Long = 67L
|
override def bitsize : Long = 67L
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,17 +28,17 @@ object DetailedREKData extends Marshallable[DetailedREKData] {
|
||||||
uint4L ::
|
uint4L ::
|
||||||
uint16L ::
|
uint16L ::
|
||||||
uint4L ::
|
uint4L ::
|
||||||
uintL(15)
|
("unk2" | uintL(15))
|
||||||
).exmap[DetailedREKData] (
|
).exmap[DetailedREKData] (
|
||||||
{
|
{
|
||||||
case code :: 8 :: 0 :: 2 :: 0 :: 8 :: 0 :: HNil =>
|
case code :: 8 :: 0 :: 2 :: 0 :: 8 :: unk2 :: HNil =>
|
||||||
Attempt.successful(DetailedREKData(code))
|
Attempt.successful(DetailedREKData(code, unk2))
|
||||||
case _ =>
|
case _ =>
|
||||||
Attempt.failure(Err("invalid rek data format"))
|
Attempt.failure(Err("invalid rek data format"))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case DetailedREKData(code) =>
|
case DetailedREKData(code, unk2) =>
|
||||||
Attempt.successful(code :: 8 :: 0 :: 2 :: 0 :: 8 :: 0 :: HNil)
|
Attempt.successful(code :: 8 :: 0 :: 2 :: 0 :: 8 :: unk2 :: HNil)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,8 @@ class ObjectCreateDetailedMessageTest extends Specification {
|
||||||
parent.get.guid mustEqual PlanetSideGUID(75)
|
parent.get.guid mustEqual PlanetSideGUID(75)
|
||||||
parent.get.slot mustEqual 1
|
parent.get.slot mustEqual 1
|
||||||
data.isDefined mustEqual true
|
data.isDefined mustEqual true
|
||||||
data.get.asInstanceOf[DetailedREKData].unk mustEqual 4
|
data.get.asInstanceOf[DetailedREKData].unk1 mustEqual 4
|
||||||
|
data.get.asInstanceOf[DetailedREKData].unk2 mustEqual 0
|
||||||
case _ =>
|
case _ =>
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
|
|
@ -231,6 +232,7 @@ class ObjectCreateDetailedMessageTest extends Specification {
|
||||||
char.firstTimeEvents(2) mustEqual "used_beamer"
|
char.firstTimeEvents(2) mustEqual "used_beamer"
|
||||||
char.firstTimeEvents(3) mustEqual "map13"
|
char.firstTimeEvents(3) mustEqual "map13"
|
||||||
char.tutorials.size mustEqual 0
|
char.tutorials.size mustEqual 0
|
||||||
|
char.cosmetics.isDefined mustEqual false
|
||||||
char.inventory.isDefined mustEqual true
|
char.inventory.isDefined mustEqual true
|
||||||
val inventory = char.inventory.get.contents
|
val inventory = char.inventory.get.contents
|
||||||
inventory.size mustEqual 10
|
inventory.size mustEqual 10
|
||||||
|
|
@ -428,6 +430,7 @@ class ObjectCreateDetailedMessageTest extends Specification {
|
||||||
List(),
|
List(),
|
||||||
"xpe_sanctuary_help" :: "xpe_th_firemodes" :: "used_beamer" :: "map13" :: Nil,
|
"xpe_sanctuary_help" :: "xpe_th_firemodes" :: "used_beamer" :: "map13" :: Nil,
|
||||||
List.empty,
|
List.empty,
|
||||||
|
None,
|
||||||
Some(InventoryData(inv)),
|
Some(InventoryData(inv)),
|
||||||
DrawnSlot.Pistol1
|
DrawnSlot.Pistol1
|
||||||
)
|
)
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue