mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-07-12 15:04:39 +00:00
Merge pull request #234 from Fate-JH/char-app-data
Character Bitstream Data
This commit is contained in:
commit
5a67fcd88d
15 changed files with 2896 additions and 788 deletions
|
|
@ -64,58 +64,92 @@ object AvatarConverter {
|
||||||
* @return the resulting `CharacterAppearanceData`
|
* @return the resulting `CharacterAppearanceData`
|
||||||
*/
|
*/
|
||||||
def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
|
def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
|
||||||
CharacterAppearanceData(
|
val alt_model_flag : Boolean = obj.isBackpack
|
||||||
|
val aa : Int=>CharacterAppearanceA = CharacterAppearanceA(
|
||||||
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, obj.Voice),
|
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, obj.Voice),
|
||||||
voice2 = 0,
|
|
||||||
black_ops = false,
|
black_ops = false,
|
||||||
|
alt_model_flag,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
jammered = false,
|
jammered = false,
|
||||||
obj.ExoSuit,
|
obj.ExoSuit,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0L,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
val ab : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
|
||||||
|
0L,
|
||||||
outfit_name = "",
|
outfit_name = "",
|
||||||
outfit_logo = 0,
|
outfit_logo = 0,
|
||||||
|
false,
|
||||||
obj.isBackpack,
|
obj.isBackpack,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
facingPitch = obj.Orientation.y,
|
facingPitch = obj.Orientation.y,
|
||||||
facingYawUpper = obj.FacingYawUpper,
|
facingYawUpper = obj.FacingYawUpper,
|
||||||
lfs = true,
|
lfs = true,
|
||||||
GrenadeState.None,
|
GrenadeState.None,
|
||||||
is_cloaking = false,
|
obj.Cloaked,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
charging_pose = false,
|
charging_pose = false,
|
||||||
on_zipline = false,
|
false,
|
||||||
RibbonBars()
|
on_zipline = None
|
||||||
)
|
)
|
||||||
|
CharacterAppearanceData(aa, ab, RibbonBars())
|
||||||
}
|
}
|
||||||
|
|
||||||
def MakeCharacterData(obj : Player) : (Boolean,Boolean)=>CharacterData = {
|
def MakeCharacterData(obj : Player) : (Boolean,Boolean)=>CharacterData = {
|
||||||
val MaxArmor = obj.MaxArmor
|
val MaxArmor = obj.MaxArmor
|
||||||
CharacterData(
|
CharacterData(
|
||||||
255 * obj.Health / obj.MaxHealth, //TODO not precise
|
255 * obj.Health / obj.MaxHealth,
|
||||||
if(MaxArmor == 0) {
|
if(MaxArmor == 0) {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
255 * obj.Armor / MaxArmor
|
255 * obj.Armor / MaxArmor
|
||||||
}, //TODO not precise
|
},
|
||||||
DressBattleRank(obj),
|
DressBattleRank(obj),
|
||||||
|
0,
|
||||||
DressCommandRank(obj),
|
DressCommandRank(obj),
|
||||||
recursiveMakeImplantEffects(obj.Implants.iterator),
|
MakeImplantEffectList(obj.Implants),
|
||||||
MakeCosmetics(obj.BEP)
|
MakeCosmetics(obj.BEP)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
def MakeDetailedCharacterData(obj : Player) : (Option[Int])=>DetailedCharacterData = {
|
def MakeDetailedCharacterData(obj : Player) : (Option[Int])=>DetailedCharacterData = {
|
||||||
DetailedCharacterData(
|
val bep = obj.BEP
|
||||||
obj.BEP,
|
val ba : DetailedCharacterA = DetailedCharacterA(
|
||||||
|
bep,
|
||||||
obj.CEP,
|
obj.CEP,
|
||||||
obj.MaxHealth,
|
0L, 0L, 0L,
|
||||||
obj.Health,
|
obj.MaxHealth, obj.Health,
|
||||||
|
false,
|
||||||
obj.Armor,
|
obj.Armor,
|
||||||
obj.MaxStamina,
|
0L,
|
||||||
obj.Stamina,
|
obj.MaxStamina, obj.Stamina,
|
||||||
obj.Certifications.toList.sortBy(_.id), //TODO is sorting necessary?
|
0, 0, 0L,
|
||||||
|
List(0, 0, 0, 0, 0, 0),
|
||||||
|
obj.Certifications.toList.sortBy(_.id) //TODO is sorting necessary?
|
||||||
|
)
|
||||||
|
val bb : (Long, Option[Int])=>DetailedCharacterB = DetailedCharacterB(
|
||||||
|
None,
|
||||||
MakeImplantEntries(obj),
|
MakeImplantEntries(obj),
|
||||||
|
Nil, Nil,
|
||||||
firstTimeEvents = List.empty[String], //TODO fte list
|
firstTimeEvents = List.empty[String], //TODO fte list
|
||||||
tutorials = List.empty[String], //TODO tutorial list
|
tutorials = List.empty[String], //TODO tutorial list
|
||||||
MakeCosmetics(obj.BEP)
|
0L, 0L, 0L, 0L, 0L,
|
||||||
|
Some(DCDExtra2(0, 0)),
|
||||||
|
Nil, Nil, false,
|
||||||
|
MakeCosmetics(bep)
|
||||||
)
|
)
|
||||||
|
(pad_length : Option[Int]) => DetailedCharacterData(ba, bb(bep, pad_length))(pad_length)
|
||||||
}
|
}
|
||||||
|
|
||||||
def MakeInventoryData(obj : Player) : InventoryData = {
|
def MakeInventoryData(obj : Player) : InventoryData = {
|
||||||
|
|
@ -185,7 +219,7 @@ object AvatarConverter {
|
||||||
private def MakeImplantEntries(obj : Player) : List[ImplantEntry] = {
|
private def MakeImplantEntries(obj : Player) : List[ImplantEntry] = {
|
||||||
val numImplants : Int = DetailedCharacterData.numberOfImplantSlots(obj.BEP)
|
val numImplants : Int = DetailedCharacterData.numberOfImplantSlots(obj.BEP)
|
||||||
val implants = obj.Implants
|
val implants = obj.Implants
|
||||||
obj.Implants.map({ case(implant, initialization, active) =>
|
obj.Implants.map({ case(implant, initialization, _) =>
|
||||||
if(initialization == 0) {
|
if(initialization == 0) {
|
||||||
ImplantEntry(implant, None)
|
ImplantEntry(implant, None)
|
||||||
}
|
}
|
||||||
|
|
@ -196,34 +230,24 @@ object AvatarConverter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find an active implant whose effect will be displayed on this player.
|
* Find and encode implants whose effect will be displayed on this player.
|
||||||
* @param iter an `Iterator` of `ImplantSlot` objects
|
* @param implants a `Sequence` of `ImplantSlot` objects
|
||||||
* @return the effect of an active implant
|
* @return the effect of an active implant
|
||||||
*/
|
*/
|
||||||
@tailrec private def recursiveMakeImplantEffects(iter : Iterator[(ImplantType.Value, Long, Boolean)]) : Option[ImplantEffects.Value] = {
|
private def MakeImplantEffectList(implants : Seq[(ImplantType.Value, Long, Boolean)]) : List[ImplantEffects.Value] = {
|
||||||
if(!iter.hasNext) {
|
implants.collect {
|
||||||
None
|
case ((implant,_,true)) =>
|
||||||
}
|
|
||||||
else {
|
|
||||||
val(implant, _, active) = iter.next
|
|
||||||
if(active) {
|
|
||||||
implant match {
|
implant match {
|
||||||
case ImplantType.AdvancedRegen =>
|
case ImplantType.AdvancedRegen =>
|
||||||
Some(ImplantEffects.RegenEffects)
|
ImplantEffects.RegenEffects
|
||||||
case ImplantType.DarklightVision =>
|
case ImplantType.DarklightVision =>
|
||||||
Some(ImplantEffects.DarklightEffects)
|
ImplantEffects.DarklightEffects
|
||||||
case ImplantType.PersonalShield =>
|
case ImplantType.PersonalShield =>
|
||||||
Some(ImplantEffects.PersonalShieldEffects)
|
ImplantEffects.PersonalShieldEffects
|
||||||
case ImplantType.Surge =>
|
case ImplantType.Surge =>
|
||||||
Some(ImplantEffects.SurgeEffects)
|
ImplantEffects.SurgeEffects
|
||||||
case _ =>
|
|
||||||
recursiveMakeImplantEffects(iter)
|
|
||||||
}
|
}
|
||||||
}
|
}.toList
|
||||||
else {
|
|
||||||
recursiveMakeImplantEffects(iter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,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.objectcreate._
|
import net.psforever.packet.game.objectcreate._
|
||||||
import net.psforever.types.{CharacterVoice, GrenadeState, ImplantType}
|
import net.psforever.types.{CertificationType, CharacterVoice, GrenadeState, ImplantType}
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
@ -22,20 +22,7 @@ class CharacterSelectConverter extends AvatarConverter {
|
||||||
DetailedPlayerData.apply(
|
DetailedPlayerData.apply(
|
||||||
PlacementData(0, 0, 0),
|
PlacementData(0, 0, 0),
|
||||||
MakeAppearanceData(obj),
|
MakeAppearanceData(obj),
|
||||||
DetailedCharacterData(
|
MakeDetailedCharacterData(obj),
|
||||||
obj.BEP,
|
|
||||||
obj.CEP,
|
|
||||||
healthMax = 1,
|
|
||||||
health = 1,
|
|
||||||
armor = 0,
|
|
||||||
staminaMax = 1,
|
|
||||||
stamina = 1,
|
|
||||||
certs = Nil,
|
|
||||||
MakeImplantEntries(obj), //necessary for correct stream length
|
|
||||||
firstTimeEvents = Nil,
|
|
||||||
tutorials = Nil,
|
|
||||||
AvatarConverter.MakeCosmetics(obj.BEP)
|
|
||||||
),
|
|
||||||
InventoryData(recursiveMakeHolsters(obj.Holsters().iterator)),
|
InventoryData(recursiveMakeHolsters(obj.Holsters().iterator)),
|
||||||
AvatarConverter.GetDrawnSlot(obj)
|
AvatarConverter.GetDrawnSlot(obj)
|
||||||
)
|
)
|
||||||
|
|
@ -49,24 +36,73 @@ class CharacterSelectConverter extends AvatarConverter {
|
||||||
* @return the resulting `CharacterAppearanceData`
|
* @return the resulting `CharacterAppearanceData`
|
||||||
*/
|
*/
|
||||||
private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
|
private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
|
||||||
CharacterAppearanceData(
|
val aa : Int=>CharacterAppearanceA = CharacterAppearanceA(
|
||||||
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, CharacterVoice.Mute),
|
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, CharacterVoice.Mute),
|
||||||
voice2 = 0,
|
|
||||||
black_ops = false,
|
black_ops = false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
jammered = false,
|
jammered = false,
|
||||||
obj.ExoSuit,
|
obj.ExoSuit,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0L,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
val ab : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
|
||||||
|
0L,
|
||||||
outfit_name = "",
|
outfit_name = "",
|
||||||
outfit_logo = 0,
|
outfit_logo = 0,
|
||||||
|
false,
|
||||||
backpack = false,
|
backpack = false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
facingPitch = 0,
|
facingPitch = 0,
|
||||||
facingYawUpper = 0,
|
facingYawUpper = 0,
|
||||||
lfs = true,
|
lfs = false,
|
||||||
GrenadeState.None,
|
GrenadeState.None,
|
||||||
is_cloaking = false,
|
obj.Cloaked,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
charging_pose = false,
|
charging_pose = false,
|
||||||
on_zipline = false,
|
false,
|
||||||
RibbonBars()
|
on_zipline = None
|
||||||
)
|
)
|
||||||
|
CharacterAppearanceData(aa, ab, RibbonBars())
|
||||||
|
}
|
||||||
|
|
||||||
|
private def MakeDetailedCharacterData(obj : Player) : (Option[Int]=>DetailedCharacterData) = {
|
||||||
|
val bep = obj.BEP
|
||||||
|
val ba : DetailedCharacterA = DetailedCharacterA(
|
||||||
|
bep,
|
||||||
|
obj.CEP,
|
||||||
|
0L, 0L, 0L,
|
||||||
|
1, 1,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
0L,
|
||||||
|
1, 1,
|
||||||
|
0, 0, 0L,
|
||||||
|
List(0, 0, 0, 0, 0, 0),
|
||||||
|
certs = List.empty[CertificationType.Value]
|
||||||
|
)
|
||||||
|
val bb : (Long, Option[Int])=>DetailedCharacterB = DetailedCharacterB(
|
||||||
|
None,
|
||||||
|
MakeImplantEntries(obj), //necessary for correct stream length
|
||||||
|
Nil, Nil,
|
||||||
|
firstTimeEvents = List.empty[String],
|
||||||
|
tutorials = List.empty[String],
|
||||||
|
0L, 0L, 0L, 0L, 0L,
|
||||||
|
Some(DCDExtra2(0, 0)),
|
||||||
|
Nil, Nil, false,
|
||||||
|
AvatarConverter.MakeCosmetics(bep)
|
||||||
|
)
|
||||||
|
(pad_length : Option[Int]) => DetailedCharacterData(ba, bb(bep, pad_length))(pad_length)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,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.objectcreate._
|
import net.psforever.packet.game.objectcreate._
|
||||||
import net.psforever.types.{CharacterGender, CharacterVoice, GrenadeState, Vector3}
|
import net.psforever.types._
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
@ -18,20 +18,7 @@ class CorpseConverter extends AvatarConverter {
|
||||||
DetailedPlayerData.apply(
|
DetailedPlayerData.apply(
|
||||||
PlacementData(obj.Position, Vector3(0,0, obj.Orientation.z)),
|
PlacementData(obj.Position, Vector3(0,0, obj.Orientation.z)),
|
||||||
MakeAppearanceData(obj),
|
MakeAppearanceData(obj),
|
||||||
DetailedCharacterData(
|
MakeDetailedCharacterData(obj),
|
||||||
bep = 0,
|
|
||||||
cep = 0,
|
|
||||||
healthMax = 0,
|
|
||||||
health = 0,
|
|
||||||
armor = 0,
|
|
||||||
staminaMax = 0,
|
|
||||||
stamina = 0,
|
|
||||||
certs = Nil,
|
|
||||||
implants = Nil,
|
|
||||||
firstTimeEvents = Nil,
|
|
||||||
tutorials = Nil,
|
|
||||||
cosmetics = None
|
|
||||||
),
|
|
||||||
InventoryData((MakeHolsters(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)),
|
InventoryData((MakeHolsters(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)),
|
||||||
DrawnSlot.None
|
DrawnSlot.None
|
||||||
)
|
)
|
||||||
|
|
@ -44,24 +31,72 @@ class CorpseConverter extends AvatarConverter {
|
||||||
* @return the resulting `CharacterAppearanceData`
|
* @return the resulting `CharacterAppearanceData`
|
||||||
*/
|
*/
|
||||||
private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
|
private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
|
||||||
CharacterAppearanceData(
|
val aa : Int=>CharacterAppearanceA = CharacterAppearanceA(
|
||||||
BasicCharacterData(obj.Name, obj.Faction, CharacterGender.Male, 0, CharacterVoice.Mute),
|
BasicCharacterData(obj.Name, obj.Faction, CharacterGender.Male, 0, CharacterVoice.Mute),
|
||||||
voice2 = 0,
|
|
||||||
black_ops = false,
|
black_ops = false,
|
||||||
|
altModel = true,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
jammered = false,
|
jammered = false,
|
||||||
obj.ExoSuit,
|
obj.ExoSuit,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0L,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
val ab : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
|
||||||
|
0L,
|
||||||
outfit_name = "",
|
outfit_name = "",
|
||||||
outfit_logo = 0,
|
outfit_logo = 0,
|
||||||
|
false,
|
||||||
backpack = true,
|
backpack = true,
|
||||||
facingPitch = obj.Orientation.y, //TODO is this important?
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
facingPitch = 0,
|
||||||
facingYawUpper = 0,
|
facingYawUpper = 0,
|
||||||
lfs = true,
|
lfs = false,
|
||||||
GrenadeState.None,
|
GrenadeState.None,
|
||||||
is_cloaking = false,
|
is_cloaking = false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
charging_pose = false,
|
charging_pose = false,
|
||||||
on_zipline = false,
|
false,
|
||||||
RibbonBars()
|
on_zipline = None
|
||||||
)
|
)
|
||||||
|
CharacterAppearanceData(aa, ab, RibbonBars())
|
||||||
|
}
|
||||||
|
|
||||||
|
private def MakeDetailedCharacterData(obj : Player) : (Option[Int]=>DetailedCharacterData) = {
|
||||||
|
val ba : DetailedCharacterA = DetailedCharacterA(
|
||||||
|
bep = 0L,
|
||||||
|
cep = 0L,
|
||||||
|
0L, 0L, 0L,
|
||||||
|
0, 0,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
0L,
|
||||||
|
0, 0,
|
||||||
|
0, 0, 0L,
|
||||||
|
List(0, 0, 0, 0, 0, 0),
|
||||||
|
certs = List.empty[CertificationType.Value]
|
||||||
|
)
|
||||||
|
val bb : (Long, Option[Int])=>DetailedCharacterB = DetailedCharacterB(
|
||||||
|
None,
|
||||||
|
implants = List.empty[ImplantEntry],
|
||||||
|
Nil, Nil,
|
||||||
|
firstTimeEvents = List.empty[String],
|
||||||
|
tutorials = List.empty[String],
|
||||||
|
0L, 0L, 0L, 0L, 0L,
|
||||||
|
Some(DCDExtra2(0, 0)),
|
||||||
|
Nil, Nil, false,
|
||||||
|
cosmetics = None
|
||||||
|
)
|
||||||
|
(pad_length : Option[Int]) => DetailedCharacterData(ba, bb(0, pad_length))(pad_length)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,7 @@ abstract class IdentifiableEntity extends Identifiable {
|
||||||
private val container : GUIDContainable = GUIDContainer()
|
private val container : GUIDContainable = GUIDContainer()
|
||||||
private var current : GUIDContainable = IdentifiableEntity.noGUIDContainer
|
private var current : GUIDContainable = IdentifiableEntity.noGUIDContainer
|
||||||
|
|
||||||
def HasGUID : Boolean = {
|
def HasGUID : Boolean = current ne IdentifiableEntity.noGUIDContainer
|
||||||
try {
|
|
||||||
GUID
|
|
||||||
true
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
case _ : NoGUIDException =>
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def GUID : PlanetSideGUID = current.GUID
|
def GUID : PlanetSideGUID = current.GUID
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,17 +44,15 @@ class UniqueNumberSystem(private val guid : NumberPoolHub, private val poolActor
|
||||||
def receive : Receive = {
|
def receive : Receive = {
|
||||||
case Register(obj, Some(pname), None, call) =>
|
case Register(obj, Some(pname), None, call) =>
|
||||||
val callback = call.getOrElse(sender())
|
val callback = call.getOrElse(sender())
|
||||||
try {
|
if(obj.HasGUID) {
|
||||||
obj.GUID //stop if object already has a GUID; sometimes this happens
|
|
||||||
AlreadyRegistered(obj, pname)
|
AlreadyRegistered(obj, pname)
|
||||||
callback ! Success(obj)
|
callback ! Success(obj)
|
||||||
}
|
}
|
||||||
catch {
|
else {
|
||||||
case _ : Exception =>
|
val id : Long = index
|
||||||
val id : Long = index
|
index += 1
|
||||||
index += 1
|
requestQueue += id -> UniqueNumberSystem.GUIDRequest(obj, pname, callback)
|
||||||
requestQueue += id -> UniqueNumberSystem.GUIDRequest(obj, pname, callback)
|
RegistrationProcess(pname, id)
|
||||||
RegistrationProcess(pname, id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//this message is automatically sent by NumberPoolActor
|
//this message is automatically sent by NumberPoolActor
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,96 @@ import scodec.{Attempt, Codec, Err}
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
import shapeless.{::, HNil}
|
import shapeless.{::, HNil}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A part of a representation of the avatar portion of `ObjectCreateDetailedMessage` packet data.
|
||||||
|
* @see `CharacterData`
|
||||||
|
* @see `DetailedCharacterData`
|
||||||
|
* @see `ExoSuitType`
|
||||||
|
* @param app the player's cardinal appearance settings
|
||||||
|
* @param black_ops whether or not this avatar is enrolled in Black OPs
|
||||||
|
* @param jammered the player has been caught in an EMP blast recently;
|
||||||
|
* creates a jammered sound effect that follows the player around and can be heard by others
|
||||||
|
* @param exosuit the type of exo-suit the avatar will be depicted in;
|
||||||
|
* for Black OPs, the agile exo-suit and the reinforced exo-suit are replaced with the Black OPs exo-suits
|
||||||
|
*/
|
||||||
|
final case class CharacterAppearanceA(app : BasicCharacterData,
|
||||||
|
black_ops : Boolean,
|
||||||
|
altModel : Boolean,
|
||||||
|
unk1 : Boolean,
|
||||||
|
unk2 : Option[CharacterAppearanceData.ExtraData],
|
||||||
|
jammered : Boolean,
|
||||||
|
exosuit : ExoSuitType.Value,
|
||||||
|
unk3 : Option[Int],
|
||||||
|
unk4 : Int,
|
||||||
|
unk5 : Int,
|
||||||
|
unk6 : Long,
|
||||||
|
unk7 : Int,
|
||||||
|
unk8 : Int,
|
||||||
|
unk9 : Int,
|
||||||
|
unkA : Int)
|
||||||
|
(name_padding : Int) extends StreamBitSize {
|
||||||
|
override def bitsize : Long = {
|
||||||
|
//factor guard bool values into the base size, not its corresponding optional field
|
||||||
|
val unk2Size : Long = unk2 match { case Some(n) => n.bitsize ; case None => 0L }
|
||||||
|
val nameStringSize : Long = StreamBitSize.stringBitSize(app.name, 16) + name_padding
|
||||||
|
val unk3Size : Long = unk3 match { case Some(_) => 32L ; case None => 0L }
|
||||||
|
137L + unk2Size + nameStringSize + unk3Size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A part of a representation of the avatar portion of `ObjectCreateDetailedMessage` packet data.
|
||||||
|
* @see `CharacterData`
|
||||||
|
* @see `DetailedCharacterData`
|
||||||
|
* @see `ExoSuitType`
|
||||||
|
* @see `GrenadeState`
|
||||||
|
* @see `RibbonBars`
|
||||||
|
* @see `http://www.planetside-universe.com/p-outfit-decals-31.htm`
|
||||||
|
* @param outfit_name the name of the outfit to which this player belongs;
|
||||||
|
* if the option is selected, allies with see either "[`outfit_name`]" or "{No Outfit}" under the player's name
|
||||||
|
* @param outfit_logo the decal seen on the player's exo-suit (and beret and cap) associated with the player's outfit;
|
||||||
|
* if there is a variable color for that decal, the faction-appropriate one is selected
|
||||||
|
* @param facingPitch a "pitch" angle
|
||||||
|
* @param facingYawUpper a "yaw" angle that represents the angle of the avatar's upper body with respect to its forward-facing direction;
|
||||||
|
* this number is normally 0 for forward facing;
|
||||||
|
* the range is limited between approximately 61 degrees of center turned to left or right
|
||||||
|
* @param lfs this player is looking for a squad;
|
||||||
|
* all allies will see the phrase "[Looking for Squad]" under the player's name
|
||||||
|
* @param is_cloaking avatar is cloaked by virtue of an Infiltration Suit
|
||||||
|
* @param grenade_state if the player has a grenade `Primed`;
|
||||||
|
* should be `GrenadeStateState.None` if nothing special
|
||||||
|
* @param charging_pose animation pose for both charging modules and BFR imprinting
|
||||||
|
* @param on_zipline player's model is changed into a faction-color ball of energy, as if on a zip line
|
||||||
|
*/
|
||||||
|
final case class CharacterAppearanceB(unk0 : Long,
|
||||||
|
outfit_name : String,
|
||||||
|
outfit_logo : Int,
|
||||||
|
unk1 : Boolean,
|
||||||
|
backpack : Boolean,
|
||||||
|
unk2 : Boolean,
|
||||||
|
unk3 : Boolean,
|
||||||
|
unk4 : Boolean,
|
||||||
|
facingPitch : Float,
|
||||||
|
facingYawUpper : Float,
|
||||||
|
lfs : Boolean,
|
||||||
|
grenade_state : GrenadeState.Value,
|
||||||
|
is_cloaking : Boolean,
|
||||||
|
unk5 : Boolean,
|
||||||
|
unk6 : Boolean,
|
||||||
|
charging_pose : Boolean,
|
||||||
|
unk7 : Boolean,
|
||||||
|
on_zipline : Option[CharacterAppearanceData.ZiplineData])
|
||||||
|
(alt_model : Boolean, name_padding : Int) extends StreamBitSize {
|
||||||
|
override def bitsize : Long = {
|
||||||
|
//factor guard bool values into the base size, not its corresponding optional field
|
||||||
|
val outfitStringSize : Long = StreamBitSize.stringBitSize(outfit_name, 16) +
|
||||||
|
CharacterAppearanceData.outfitNamePadding //even if the outfit_name is blank, string is always padded
|
||||||
|
val backpackSize = if(backpack) { 1L } else { 0L }
|
||||||
|
val onZiplineSize : Long = on_zipline match { case Some(n) => n.bitsize; case None => 0 }
|
||||||
|
70L + outfitStringSize + backpackSize + onZiplineSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A part of a representation of the avatar portion of `ObjectCreateDetailedMessage` packet data.<br>
|
* A part of a representation of the avatar portion of `ObjectCreateDetailedMessage` packet data.<br>
|
||||||
* <br>
|
* <br>
|
||||||
|
|
@ -28,64 +118,16 @@ import shapeless.{::, HNil}
|
||||||
* <br>
|
* <br>
|
||||||
* Exploration:<br>
|
* Exploration:<br>
|
||||||
* How do I crouch?
|
* How do I crouch?
|
||||||
* @see `CharacterData`<br>
|
* @see `CharacterData`
|
||||||
* `DetailedCharacterData`<br>
|
* @see `DetailedCharacterData`
|
||||||
* `ExoSuitType`<br>
|
|
||||||
* `GrenadeState`<br>
|
|
||||||
* `RibbonBars`
|
|
||||||
* @see `http://www.planetside-universe.com/p-outfit-decals-31.htm`
|
|
||||||
* @param app the player's cardinal appearance settings
|
|
||||||
* @param voice2 na;
|
|
||||||
* affects the frequency by which the character's voice is heard (somehow);
|
|
||||||
* commonly 3 for best results
|
|
||||||
* @param black_ops whether or not this avatar is enrolled in Black OPs
|
|
||||||
* @param jammered the player has been caught in an EMP blast recently;
|
|
||||||
* creates a jammered sound effect that follows the player around and can be heard by others
|
|
||||||
* @param exosuit the type of exo-suit the avatar will be depicted in;
|
|
||||||
* for Black OPs, the agile exo-suit and the reinforced exo-suit are replaced with the Black OPs exo-suits
|
|
||||||
* @param outfit_name the name of the outfit to which this player belongs;
|
|
||||||
* if the option is selected, allies with see either "[`outfit_name`]" or "{No Outfit}" under the player's name
|
|
||||||
* @param outfit_logo the decal seen on the player's exo-suit (and beret and cap) associated with the player's outfit;
|
|
||||||
* if there is a variable color for that decal, the faction-appropriate one is selected
|
|
||||||
* @param facingPitch a "pitch" angle
|
|
||||||
* @param facingYawUpper a "yaw" angle that represents the angle of the avatar's upper body with respect to its forward-facing direction;
|
|
||||||
* this number is normally 0 for forward facing;
|
|
||||||
* the range is limited between approximately 61 degrees of center turned to left or right
|
|
||||||
* @param lfs this player is looking for a squad;
|
|
||||||
* all allies will see the phrase "[Looking for Squad]" under the player's name
|
|
||||||
* @param is_cloaking avatar is cloaked by virtue of an Infiltration Suit
|
|
||||||
* @param grenade_state if the player has a grenade `Primed`;
|
|
||||||
* should be `GrenadeStateState.None` if nothing special
|
|
||||||
* @param charging_pose animation pose for both charging modules and BFR imprinting
|
|
||||||
* @param on_zipline player's model is changed into a faction-color ball of energy, as if on a zip line
|
|
||||||
* @param ribbons the four merit commendation ribbon medals
|
* @param ribbons the four merit commendation ribbon medals
|
||||||
*/
|
*/
|
||||||
final case class CharacterAppearanceData(app : BasicCharacterData,
|
final case class CharacterAppearanceData(a : CharacterAppearanceA,
|
||||||
voice2 : Int,
|
b : CharacterAppearanceB,
|
||||||
black_ops : Boolean,
|
|
||||||
jammered : Boolean,
|
|
||||||
exosuit : ExoSuitType.Value,
|
|
||||||
outfit_name : String,
|
|
||||||
outfit_logo : Int,
|
|
||||||
backpack : Boolean,
|
|
||||||
facingPitch : Float,
|
|
||||||
facingYawUpper : Float,
|
|
||||||
lfs : Boolean,
|
|
||||||
grenade_state : GrenadeState.Value,
|
|
||||||
is_cloaking : Boolean,
|
|
||||||
charging_pose : Boolean,
|
|
||||||
on_zipline : Boolean,
|
|
||||||
ribbons : RibbonBars)
|
ribbons : RibbonBars)
|
||||||
(name_padding : Int) extends StreamBitSize {
|
(name_padding : Int) extends StreamBitSize {
|
||||||
|
|
||||||
override def bitsize : Long = {
|
override def bitsize : Long = 128L + a.bitsize + b.bitsize
|
||||||
//factor guard bool values into the base size, not its corresponding optional field
|
|
||||||
val nameStringSize : Long = StreamBitSize.stringBitSize(app.name, 16) + name_padding
|
|
||||||
val outfitStringSize : Long = StreamBitSize.stringBitSize(outfit_name, 16) +
|
|
||||||
CharacterAppearanceData.outfitNamePadding //even if the outfit_name is blank, string is always padded
|
|
||||||
val altModelSize = CharacterAppearanceData.altModelBit(this).getOrElse(0)
|
|
||||||
335L + nameStringSize + outfitStringSize + altModelSize //base value includes the ribbons
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* External access to the value padding on the name field.
|
* External access to the value padding on the name field.
|
||||||
|
|
@ -102,6 +144,91 @@ final case class CharacterAppearanceData(app : BasicCharacterData,
|
||||||
}
|
}
|
||||||
|
|
||||||
object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
||||||
|
def apply(app : BasicCharacterData,
|
||||||
|
black_ops : Boolean,
|
||||||
|
jammered : Boolean,
|
||||||
|
exosuit : ExoSuitType.Value,
|
||||||
|
outfit_name : String,
|
||||||
|
outfit_logo : Int,
|
||||||
|
backpack : Boolean,
|
||||||
|
facingPitch : Float,
|
||||||
|
facingYawUpper : Float,
|
||||||
|
lfs : Boolean,
|
||||||
|
grenade_state : GrenadeState.Value,
|
||||||
|
is_cloaking : Boolean,
|
||||||
|
charging_pose : Boolean,
|
||||||
|
on_zipline : Option[ZiplineData],
|
||||||
|
ribbons : RibbonBars)(name_padding : Int) : CharacterAppearanceData = {
|
||||||
|
val altModel : Boolean = backpack || on_zipline.isDefined
|
||||||
|
val a = CharacterAppearanceA(
|
||||||
|
app,
|
||||||
|
black_ops,
|
||||||
|
altModel,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
jammered,
|
||||||
|
exosuit,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
)(name_padding)
|
||||||
|
val b = CharacterAppearanceB(
|
||||||
|
outfit_name.length,
|
||||||
|
outfit_name : String,
|
||||||
|
outfit_logo : Int,
|
||||||
|
false,
|
||||||
|
backpack,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
facingPitch : Float,
|
||||||
|
facingYawUpper : Float,
|
||||||
|
lfs : Boolean,
|
||||||
|
grenade_state : GrenadeState.Value,
|
||||||
|
is_cloaking : Boolean,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
charging_pose : Boolean,
|
||||||
|
false,
|
||||||
|
on_zipline
|
||||||
|
)(altModel, name_padding)
|
||||||
|
new CharacterAppearanceData(
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
ribbons
|
||||||
|
)(name_padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
def apply(a : Int=>CharacterAppearanceA, b : (Boolean,Int)=>CharacterAppearanceB, ribbons : RibbonBars)(name_padding : Int) : CharacterAppearanceData = {
|
||||||
|
val first = a(name_padding)
|
||||||
|
CharacterAppearanceData(a(name_padding), b(first.altModel, name_padding), ribbons)(name_padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* na
|
||||||
|
* @param unk1 na
|
||||||
|
* @param unk2 na
|
||||||
|
*/
|
||||||
|
final case class ExtraData(unk1 : Boolean,
|
||||||
|
unk2 : Boolean) extends StreamBitSize {
|
||||||
|
override def bitsize : Long = 2L
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* na
|
||||||
|
* @param unk1 na
|
||||||
|
* @param unk2 na
|
||||||
|
*/
|
||||||
|
final case class ZiplineData(unk1 : Long,
|
||||||
|
unk2 : Boolean) extends StreamBitSize {
|
||||||
|
override def bitsize : Long = 33L
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When a player is released-dead or attached to a zipline, their basic infantry model is replaced with a different one.
|
* When a player is released-dead or attached to a zipline, their basic infantry model is replaced with a different one.
|
||||||
* In the former case, a backpack.
|
* In the former case, a backpack.
|
||||||
|
|
@ -110,13 +237,26 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
||||||
* @param app the appearance
|
* @param app the appearance
|
||||||
* @return the length of the variable field that exists when using alternate models
|
* @return the length of the variable field that exists when using alternate models
|
||||||
*/
|
*/
|
||||||
def altModelBit(app : CharacterAppearanceData) : Option[Int] = if(app.backpack || app.on_zipline) {
|
def altModelBit(app : CharacterAppearanceData) : Option[Int] = if(app.b.backpack || app.b.on_zipline.isDefined) {
|
||||||
Some(1)
|
Some(1)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def namePadding(inheritPad : Int, pad : Option[ExtraData]) : Int = {
|
||||||
|
pad match {
|
||||||
|
case Some(n) =>
|
||||||
|
val bitsize = n.bitsize.toInt % 8
|
||||||
|
if(inheritPad > bitsize)
|
||||||
|
inheritPad - bitsize
|
||||||
|
else
|
||||||
|
8 - bitsize
|
||||||
|
case None =>
|
||||||
|
inheritPad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the padding of the outfit's name.
|
* Get the padding of the outfit's name.
|
||||||
* The padding will always be a number 0-7.
|
* The padding will always be a number 0-7.
|
||||||
|
|
@ -126,72 +266,60 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
||||||
6
|
6
|
||||||
}
|
}
|
||||||
|
|
||||||
def codec(name_padding : Int) : Codec[CharacterAppearanceData] = (
|
private val extra_codec : Codec[ExtraData] = (
|
||||||
|
("unk1" | bool) ::
|
||||||
|
("unk2" | bool)
|
||||||
|
).as[ExtraData]
|
||||||
|
|
||||||
|
private val zipline_codec : Codec[ZiplineData] = (
|
||||||
|
("unk1" | uint32L) ::
|
||||||
|
("unk2" | bool)
|
||||||
|
).as[ZiplineData]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* na
|
||||||
|
* @param name_padding na
|
||||||
|
* @return na
|
||||||
|
*/
|
||||||
|
def a_codec(name_padding : Int) : Codec[CharacterAppearanceA] = (
|
||||||
("faction" | PlanetSideEmpire.codec) ::
|
("faction" | PlanetSideEmpire.codec) ::
|
||||||
("black_ops" | bool) ::
|
("black_ops" | bool) ::
|
||||||
(("alt_model" | bool) >>:~ { alt_model => //modifies stream format (to display alternate player models)
|
(("alt_model" | bool) >>:~ { alt_model => //modifies stream format (to display alternate player models)
|
||||||
ignore(1) :: //unknown
|
("unk1" | bool) :: //serves a different internal purpose depending on the state of alt_model
|
||||||
("jammered" | bool) ::
|
(conditional(false, "unk2" | extra_codec) >>:~ { extra => //TODO not sure what causes this branch
|
||||||
bool :: //crashes client
|
("jammered" | bool) ::
|
||||||
uint(16) :: //unknown, but usually 0
|
optional(bool, "unk3" | uint16L) ::
|
||||||
("name" | PacketHelpers.encodedWideStringAligned(name_padding)) ::
|
("unk4" | uint16L) ::
|
||||||
("exosuit" | ExoSuitType.codec) ::
|
("name" | PacketHelpers.encodedWideStringAligned(namePadding(name_padding, extra))) ::
|
||||||
ignore(2) :: //unknown
|
("exosuit" | ExoSuitType.codec) ::
|
||||||
("sex" | CharacterGender.codec) ::
|
("unk5" | uint2) :: //unknown
|
||||||
("head" | uint8L) ::
|
("sex" | CharacterGender.codec) ::
|
||||||
("voice" | CharacterVoice.codec) ::
|
("head" | uint8L) ::
|
||||||
("voice2" | uint2L) ::
|
("voice" | CharacterVoice.codec) ::
|
||||||
ignore(78) :: //unknown
|
("unk6" | uint32L) ::
|
||||||
uint16L :: //usually either 0 or 65535
|
("unk7" | uint16L) ::
|
||||||
uint32L :: //for outfit_name (below) to be visible in-game, this value should be non-zero
|
("unk8" | uint16L) ::
|
||||||
("outfit_name" | PacketHelpers.encodedWideStringAligned( outfitNamePadding )) ::
|
("unk9" | uint16L) ::
|
||||||
("outfit_logo" | uint8L) ::
|
("unkA" | uint16L) //usually either 0 or 65535
|
||||||
ignore(1) :: //unknown
|
})
|
||||||
("backpack" | bool) :: //requires alt_model flag (does NOT require health == 0)
|
|
||||||
bool :: //stream misalignment when set
|
|
||||||
("facingPitch" | Angular.codec_pitch) ::
|
|
||||||
("facingYawUpper" | Angular.codec_yaw(0f)) ::
|
|
||||||
ignore(1) :: //unknown
|
|
||||||
conditional(alt_model, bool) :: //alt_model flag adds a bit before lfs
|
|
||||||
ignore(1) :: //an alternate lfs?
|
|
||||||
("lfs" | bool) ::
|
|
||||||
("grenade_state" | GrenadeState.codec_2u) :: //note: bin10 and bin11 are neutral (bin00 is not defined)
|
|
||||||
("is_cloaking" | bool) ::
|
|
||||||
ignore(1) :: //unknown
|
|
||||||
bool :: //stream misalignment when set
|
|
||||||
("charging_pose" | bool) ::
|
|
||||||
ignore(1) :: //alternate charging pose?
|
|
||||||
("on_zipline" | bool) :: //requires alt_model flag
|
|
||||||
("ribbons" | RibbonBars.codec)
|
|
||||||
})
|
})
|
||||||
).exmap[CharacterAppearanceData] (
|
).exmap[CharacterAppearanceA] (
|
||||||
{
|
{
|
||||||
case _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil |
|
case faction :: bops :: alt :: u1 :: u2 :: jamd :: u3 :: u4 :: name :: suit :: u5 :: sex :: head :: v1 :: u6 :: u7 :: u8 :: u9 :: uA :: HNil =>
|
||||||
_ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: HNil =>
|
|
||||||
Attempt.Failure(Err("invalid character appearance data; can not encode alternate model without required bit set"))
|
|
||||||
|
|
||||||
case faction :: bops :: _ :: _ :: jamd :: false :: 0 :: name :: suit :: _ :: sex :: head :: v1 :: v2 :: _ :: _ :: _/*has_outfit_name*/ :: outfit :: logo :: _ :: bpack :: false :: facingPitch :: facingYawUpper :: _ :: _ :: _ :: lfs :: gstate :: cloaking :: _ :: false :: charging :: _ :: zipline :: ribbons :: HNil =>
|
|
||||||
Attempt.successful(
|
Attempt.successful(
|
||||||
CharacterAppearanceData(BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons)(name_padding)
|
CharacterAppearanceA(BasicCharacterData(name, faction, sex, head, v1), bops, alt, u1, u2, jamd, suit, u3, u4, u5, u6, u7, u8, u9, uA)(name_padding)
|
||||||
)
|
)
|
||||||
|
|
||||||
case _ =>
|
case _ =>
|
||||||
Attempt.Failure(Err("invalid character appearance data; can not encode"))
|
Attempt.Failure(Err("invalid character appearance data; can not encode"))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case CharacterAppearanceData(BasicCharacterData(name, PlanetSideEmpire.NEUTRAL, _, _, _), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
|
case CharacterAppearanceA(BasicCharacterData(name, PlanetSideEmpire.NEUTRAL, _, _, _), _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
|
||||||
Attempt.failure(Err(s"character $name's faction can not declare as neutral"))
|
Attempt.failure(Err(s"character $name's faction can not declare as neutral"))
|
||||||
|
|
||||||
case CharacterAppearanceData(BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons) =>
|
case CharacterAppearanceA(BasicCharacterData(name, faction, sex, head, v1), bops, alt, u1, u2, jamd, suit, u3, u4, u5, u6, u7, u8, u9, uA) =>
|
||||||
val has_outfit_name : Long = outfit.length.toLong //TODO this is a kludge
|
|
||||||
var alt_model : Boolean = false
|
|
||||||
var alt_model_extrabit : Option[Boolean] = None
|
|
||||||
if(zipline || bpack) {
|
|
||||||
alt_model = true
|
|
||||||
alt_model_extrabit = Some(false)
|
|
||||||
}
|
|
||||||
Attempt.successful(
|
Attempt.successful(
|
||||||
faction :: bops :: alt_model :: () :: jamd :: false :: 0 :: name :: suit :: () :: sex :: head :: v1 :: v2 :: () :: 0 :: has_outfit_name :: outfit :: logo :: () :: bpack :: false :: facingPitch :: facingYawUpper :: () :: alt_model_extrabit :: () :: lfs :: gstate :: cloaking :: () :: false :: charging :: () :: zipline :: ribbons :: HNil
|
faction :: bops :: alt :: u1 :: u2 :: jamd :: u3 :: u4 :: name :: suit :: u5 :: sex :: head :: v1 :: u6 :: u7 :: u8 :: u9 :: uA :: HNil
|
||||||
)
|
)
|
||||||
|
|
||||||
case _ =>
|
case _ =>
|
||||||
|
|
@ -199,5 +327,77 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* na
|
||||||
|
* @param alt_model na
|
||||||
|
* @param name_padding na
|
||||||
|
* @return na
|
||||||
|
*/
|
||||||
|
def b_codec(alt_model : Boolean, name_padding : Int) : Codec[CharacterAppearanceB] = (
|
||||||
|
("unk0" | uint32L) :: //for outfit_name (below) to be visible in-game, this value should be non-zero
|
||||||
|
("outfit_name" | PacketHelpers.encodedWideStringAligned(outfitNamePadding)) ::
|
||||||
|
("outfit_logo" | uint8L) ::
|
||||||
|
("unk1" | bool) :: //unknown
|
||||||
|
conditional(alt_model, "backpack" | bool) :: //alt_model flag adds this bit; see ps.c:line#1069587
|
||||||
|
("unk2" | bool) :: //requires alt_model flag (does NOT require health == 0)
|
||||||
|
("unk3" | bool) :: //stream misalignment when set
|
||||||
|
("unk4" | bool) :: //unknown
|
||||||
|
("facingPitch" | Angular.codec_pitch) ::
|
||||||
|
("facingYawUpper" | Angular.codec_yaw(0f)) ::
|
||||||
|
("lfs" | uint2) ::
|
||||||
|
("grenade_state" | GrenadeState.codec_2u) :: //note: bin10 and bin11 are neutral (bin00 is not defined)
|
||||||
|
("is_cloaking" | bool) ::
|
||||||
|
("unk5" | bool) :: //unknown
|
||||||
|
("unk6" | bool) :: //stream misalignment when set
|
||||||
|
("charging_pose" | bool) ::
|
||||||
|
("unk7" | bool) :: //alternate charging pose?
|
||||||
|
optional(bool, "on_zipline" | zipline_codec)
|
||||||
|
).exmap[CharacterAppearanceB] (
|
||||||
|
{
|
||||||
|
case u0 :: outfit :: logo :: u1 :: bpack :: u2 :: u3 :: u4 :: facingPitch :: facingYawUpper :: lfs :: gstate :: cloaking :: u5 :: u6 :: charging :: u7 :: zipline :: HNil =>
|
||||||
|
val lfsBool = if(lfs == 0) false else true
|
||||||
|
val bpackBool = bpack match { case Some(_) => alt_model ; case None => false }
|
||||||
|
Attempt.successful(
|
||||||
|
CharacterAppearanceB(u0, outfit, logo, u1, bpackBool, u2, u3, u4, facingPitch, facingYawUpper, lfsBool, gstate, cloaking, u5, u6, charging, u7, zipline)(alt_model, name_padding)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case CharacterAppearanceB(u0, outfit, logo, u1, bpack, u2, u3, u4, facingPitch, facingYawUpper, lfs, gstate, cloaking, u5, u6, charging, u7, zipline) =>
|
||||||
|
val u0Long = if(u0 == 0 && outfit.nonEmpty) {
|
||||||
|
outfit.length.toLong
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
u0
|
||||||
|
} //TODO this is a kludge; unk0 must be (some) non-zero if outfit_name is defined
|
||||||
|
val (bpackOpt, zipOpt) = if(alt_model) {
|
||||||
|
val bpackOpt = if(bpack) { Some(true) } else { None }
|
||||||
|
(bpackOpt, zipline)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
(None, None)
|
||||||
|
} //alt_model must be set for either of the other two to be valid
|
||||||
|
val lfsInt = if(lfs) { 1 } else { 0 }
|
||||||
|
Attempt.successful(
|
||||||
|
u0Long :: outfit :: logo :: u1 :: bpackOpt :: u2 :: u3 :: u4 :: facingPitch :: facingYawUpper :: lfsInt :: gstate :: cloaking :: u5 :: u6 :: charging :: u7 :: zipOpt :: HNil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def codec(name_padding : Int) : Codec[CharacterAppearanceData] = (
|
||||||
|
("a" | a_codec(name_padding)) >>:~ { a =>
|
||||||
|
("b" | b_codec(a.altModel, name_padding)) ::
|
||||||
|
("ribbons" | RibbonBars.codec)
|
||||||
|
}
|
||||||
|
).xmap[CharacterAppearanceData] (
|
||||||
|
{
|
||||||
|
case a :: b :: ribbons :: HNil =>
|
||||||
|
CharacterAppearanceData(a, b, ribbons)(name_padding)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case CharacterAppearanceData(a, b, ribbons) =>
|
||||||
|
a :: b :: ribbons :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
implicit val codec : Codec[CharacterAppearanceData] = codec(0)
|
implicit val codec : Codec[CharacterAppearanceData] = codec(0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,10 @@ import shapeless.{::, HNil}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Values for the implant effects on a character model.
|
* Values for the implant effects on a character model.
|
||||||
* The effects can not be activated simultaneously.
|
* The effects are not additive and this value is not a bitmask.<br>
|
||||||
* In at least one case, attempting to activate multiple effects will cause the PlanetSide client to crash.<br>
|
|
||||||
* <br>
|
* <br>
|
||||||
* `RegenEffects` is a reverse-flagged item - inactive when the corresponding bit is set.
|
* `RegenEffects` is a reverse-flagged item - inactive when the corresponding bit is set.
|
||||||
* For that reason, every other effect is `n`+1, while `NoEffects` is 1 and `RegenEffects` is 0.
|
* For that reason, every other effect is `n + 1`, while `NoEffects` is `1` and `RegenEffects` is `0`.
|
||||||
*/
|
*/
|
||||||
object ImplantEffects extends Enumeration {
|
object ImplantEffects extends Enumeration {
|
||||||
type Type = Value
|
type Type = Value
|
||||||
|
|
@ -44,19 +43,20 @@ object UniformStyle extends Enumeration {
|
||||||
/**
|
/**
|
||||||
* A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.<br>
|
* A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.<br>
|
||||||
* <br>
|
* <br>
|
||||||
* This densely-packed information outlines most of the specifics required to depict some other player's character.
|
* This information outlines most of the specifics required to depict some other player's character.
|
||||||
* Someone else decides how that character is behaving and the server tells each client how to depict that behavior.
|
* Someone else decides how that character is behaving and the server tells each client how to depict that behavior.
|
||||||
* For that reason, the character is mostly for presentation purposes, rather than really being fleshed-out.
|
* For that reason, the character is mostly for presentation purposes, rather than really being fleshed-out.
|
||||||
* Of the inventory for this character, only the initial five weapon slots are defined.<br>
|
* Of the inventory for this character, only the initial five weapon slots are defined.
|
||||||
* <br>
|
|
||||||
* In the "backend of the client," the character produced by this data is no different
|
* In the "backend of the client," the character produced by this data is no different
|
||||||
* from the kind of character that could be declared a given player's avatar.
|
* from the kind of character that could be declared a given player's avatar.
|
||||||
* In terms of equipment and complicated features common to an avatar character, however,
|
* In terms of equipment and complicated features common to an avatar character, however,
|
||||||
* any user would find this character ill-equipped.
|
* any user would find this character ill-equipped.
|
||||||
|
* @see `DetailedCharacterData`
|
||||||
|
* @see `Cosmetics`
|
||||||
* @param health the amount of health the player has, as a percentage of a filled bar;
|
* @param health the amount of health the player has, as a percentage of a filled bar;
|
||||||
* the bar has 85 states, with 3 points for each state;
|
* the bar has 85 states, with 3 points for each state;
|
||||||
* when 0% (less than 3 of 255), the player will collapse into a death pose on the ground;
|
* when 0% (less than 3 of 255), the player will collapse into a death pose on the ground;
|
||||||
* while `is_corpse == true`, `health` will always report as 0;
|
* while `is_backpack == true`, `health` will always report as 0;
|
||||||
* while `is_seated == true`, `health` will (try to) report as 100
|
* while `is_seated == true`, `health` will (try to) report as 100
|
||||||
* @param armor the amount of armor the player has, as a percentage of a filled bar;
|
* @param armor the amount of armor the player has, as a percentage of a filled bar;
|
||||||
* the bar has 85 states, with 3 points for each state;
|
* the bar has 85 states, with 3 points for each state;
|
||||||
|
|
@ -65,7 +65,8 @@ object UniformStyle extends Enumeration {
|
||||||
* @param command_rank the player's command rank as a number from 0 to 5;
|
* @param command_rank the player's command rank as a number from 0 to 5;
|
||||||
* cosmetic armor associated with the command rank will be applied automatically
|
* cosmetic armor associated with the command rank will be applied automatically
|
||||||
* @param implant_effects the effects of implants that can be seen on a player's character;
|
* @param implant_effects the effects of implants that can be seen on a player's character;
|
||||||
* though many implants can be used simultaneously, only one implant effect can be applied here
|
* the number of entries equates to the number of effects applied;
|
||||||
|
* the maximu number of effects is three
|
||||||
* @param cosmetics optional decorative features that are added to the player's head model by console/chat commands;
|
* @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, but here they require the third uniform upgrade (rank 25);
|
* they become available at battle rank 24, but here they require the third uniform upgrade (rank 25);
|
||||||
* these flags do not exist if they are not applicable
|
* these flags do not exist if they are not applicable
|
||||||
|
|
@ -74,23 +75,20 @@ object UniformStyle extends Enumeration {
|
||||||
* the alternate model bit should be flipped
|
* the alternate model bit should be flipped
|
||||||
* @param is_seated this player character is seated in a vehicle or mounted to some other object;
|
* @param is_seated this player character is seated in a vehicle or mounted to some other object;
|
||||||
* alternate format for data parsing applies
|
* alternate format for data parsing applies
|
||||||
* @see `DetailedCharacterData`<br>
|
|
||||||
* `CharacterAppearanceData`
|
|
||||||
*/
|
*/
|
||||||
final case class CharacterData(health : Int,
|
final case class CharacterData(health : Int,
|
||||||
armor : Int,
|
armor : Int,
|
||||||
uniform_upgrade : UniformStyle.Value,
|
uniform_upgrade : UniformStyle.Value,
|
||||||
unk : Int,
|
unk : Int,
|
||||||
command_rank : Int,
|
command_rank : Int,
|
||||||
implant_effects : Option[ImplantEffects.Value],
|
implant_effects : List[ImplantEffects.Value],
|
||||||
cosmetics : Option[Cosmetics])
|
cosmetics : Option[Cosmetics])
|
||||||
(is_backpack : Boolean,
|
(is_backpack : Boolean,
|
||||||
is_seated : Boolean) extends ConstructorData {
|
is_seated : Boolean) extends ConstructorData {
|
||||||
|
|
||||||
override def bitsize : Long = {
|
override def bitsize : Long = {
|
||||||
//factor guard bool values into the base size, not its corresponding optional field
|
|
||||||
val seatedSize = if(is_seated) { 0 } else { 16 }
|
val seatedSize = if(is_seated) { 0 } else { 16 }
|
||||||
val effectsSize : Long = if(implant_effects.isDefined) { 4L } else { 0L }
|
val effectsSize : Long = implant_effects.length * 4L
|
||||||
val cosmeticsSize : Long = if(cosmetics.isDefined) { cosmetics.get.bitsize } else { 0L }
|
val cosmeticsSize : Long = if(cosmetics.isDefined) { cosmetics.get.bitsize } else { 0L }
|
||||||
11L + seatedSize + effectsSize + cosmeticsSize
|
11L + seatedSize + effectsSize + cosmeticsSize
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +96,7 @@ final case class CharacterData(health : Int,
|
||||||
|
|
||||||
object CharacterData extends Marshallable[CharacterData] {
|
object CharacterData extends Marshallable[CharacterData] {
|
||||||
/**
|
/**
|
||||||
* An overloaded constructor for `CharacterData` that allows for a not-optional inventory.
|
* An overloaded constructor for `CharacterData`.
|
||||||
* @param health the amount of health the player has, as a percentage of a filled bar
|
* @param health the amount of health the player has, as a percentage of a filled bar
|
||||||
* @param armor the amount of armor the player has, as a percentage of a filled bar
|
* @param armor the amount of armor the player has, as a percentage of a filled bar
|
||||||
* @param uniform the level of upgrade to apply to the player's base uniform
|
* @param uniform the level of upgrade to apply to the player's base uniform
|
||||||
|
|
@ -107,32 +105,31 @@ object CharacterData extends Marshallable[CharacterData] {
|
||||||
* @param cosmetics optional decorative features that are added to the player's head model by console/chat commands
|
* @param cosmetics optional decorative features that are added to the player's head model by console/chat commands
|
||||||
* @return a `CharacterData` object
|
* @return a `CharacterData` object
|
||||||
*/
|
*/
|
||||||
def apply(health : Int, armor : Int, uniform : UniformStyle.Value, cr : Int, implant_effects : Option[ImplantEffects.Value], cosmetics : Option[Cosmetics]) : (Boolean,Boolean)=>CharacterData =
|
def apply(health : Int, armor : Int, uniform : UniformStyle.Value, cr : Int, implant_effects : List[ImplantEffects.Value], cosmetics : Option[Cosmetics]) : (Boolean,Boolean)=>CharacterData =
|
||||||
CharacterData(health, armor, uniform, 0, cr, implant_effects, cosmetics)
|
CharacterData(health, armor, uniform, 0, cr, implant_effects, cosmetics)
|
||||||
|
|
||||||
def codec(is_backpack : Boolean) : Codec[CharacterData] = (
|
def codec(is_backpack : Boolean) : Codec[CharacterData] = (
|
||||||
("health" | uint8L) :: //dead state when health == 0
|
("health" | uint8L) :: //dead state when health == 0
|
||||||
("armor" | uint8L) ::
|
("armor" | uint8L) ::
|
||||||
(("uniform_upgrade" | UniformStyle.codec) >>:~ { style =>
|
(("uniform_upgrade" | UniformStyle.codec) >>:~ { style =>
|
||||||
ignore(3) :: //unknown
|
uint(3) :: //uniform_upgrade is actually interpreted as a 6u field, but the lower 3u seems to be discarded
|
||||||
("command_rank" | uintL(3)) ::
|
("command_rank" | uintL(3)) ::
|
||||||
bool :: //misalignment when == 1
|
listOfN(uint2, "implant_effects" | ImplantEffects.codec) ::
|
||||||
optional(bool, "implant_effects" | ImplantEffects.codec) ::
|
|
||||||
conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec)
|
conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec)
|
||||||
})
|
})
|
||||||
).exmap[CharacterData] (
|
).exmap[CharacterData] (
|
||||||
{
|
{
|
||||||
case health :: armor :: uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: HNil =>
|
case health :: armor :: uniform :: unk :: cr :: implant_effects :: cosmetics :: HNil =>
|
||||||
val newHealth = if(is_backpack) { 0 } else { health }
|
val newHealth = if(is_backpack) { 0 } else { health }
|
||||||
Attempt.Successful(CharacterData(newHealth, armor, uniform, 0, cr, implant_effects, cosmetics)(is_backpack, false))
|
Attempt.Successful(CharacterData(newHealth, armor, uniform, unk, cr, implant_effects, cosmetics)(is_backpack, false))
|
||||||
|
|
||||||
case _ =>
|
case _ =>
|
||||||
Attempt.Failure(Err("invalid character data; can not encode"))
|
Attempt.Failure(Err("invalid character data; can not encode"))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case CharacterData(health, armor, uniform, _, cr, implant_effects, cosmetics) =>
|
case CharacterData(health, armor, uniform, unk, cr, implant_effects, cosmetics) =>
|
||||||
val newHealth = if(is_backpack) { 0 } else { health }
|
val newHealth = if(is_backpack) { 0 } else { health }
|
||||||
Attempt.Successful(newHealth :: armor :: uniform :: () :: cr :: false :: implant_effects :: cosmetics :: HNil)
|
Attempt.Successful(newHealth :: armor :: uniform :: unk :: cr :: implant_effects :: cosmetics :: HNil)
|
||||||
|
|
||||||
case _ =>
|
case _ =>
|
||||||
Attempt.Failure(Err("invalid character data; can not decode"))
|
Attempt.Failure(Err("invalid character data; can not decode"))
|
||||||
|
|
@ -141,23 +138,22 @@ object CharacterData extends Marshallable[CharacterData] {
|
||||||
|
|
||||||
def codec_seated(is_backpack : Boolean) : Codec[CharacterData] = (
|
def codec_seated(is_backpack : Boolean) : Codec[CharacterData] = (
|
||||||
("uniform_upgrade" | UniformStyle.codec) >>:~ { style =>
|
("uniform_upgrade" | UniformStyle.codec) >>:~ { style =>
|
||||||
ignore(3) :: //unknown
|
uint(3) :: //uniform_upgrade is actually interpreted as a 6u field, but the lower 3u seems to be discarded
|
||||||
("command_rank" | uintL(3)) ::
|
("command_rank" | uintL(3)) ::
|
||||||
bool :: //stream misalignment when != 1
|
listOfN(uint2, "implant_effects" | ImplantEffects.codec) ::
|
||||||
optional(bool, "implant_effects" | ImplantEffects.codec) ::
|
|
||||||
conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec)
|
conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec)
|
||||||
}
|
}
|
||||||
).exmap[CharacterData] (
|
).exmap[CharacterData] (
|
||||||
{
|
{
|
||||||
case uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: HNil =>
|
case uniform :: unk :: cr :: implant_effects :: cosmetics :: HNil =>
|
||||||
Attempt.Successful(new CharacterData(100, 0, uniform, 0, cr, implant_effects, cosmetics)(is_backpack, true))
|
Attempt.Successful(new CharacterData(100, 0, uniform, unk, cr, implant_effects, cosmetics)(is_backpack, true))
|
||||||
|
|
||||||
case _ =>
|
case _ =>
|
||||||
Attempt.Failure(Err("invalid character data; can not encode"))
|
Attempt.Failure(Err("invalid character data; can not encode"))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case obj @ CharacterData(_, _, uniform, _, cr, implant_effects, cosmetics) =>
|
case CharacterData(_, _, uniform, unk, cr, implant_effects, cosmetics) =>
|
||||||
Attempt.Successful(uniform :: () :: cr :: false :: implant_effects :: cosmetics :: HNil)
|
Attempt.Successful(uniform :: unk :: cr :: implant_effects :: cosmetics :: HNil)
|
||||||
|
|
||||||
case _ =>
|
case _ =>
|
||||||
Attempt.Failure(Err("invalid character data; can not decode"))
|
Attempt.Failure(Err("invalid character data; can not decode"))
|
||||||
|
|
|
||||||
|
|
@ -12,29 +12,52 @@ import scala.annotation.tailrec
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An entry in the `List` of valid implant slots in `DetailedCharacterData`.
|
* An entry in the `List` of valid implant slots in `DetailedCharacterData`.
|
||||||
* `activation`, if defined, indicates the time remaining (in seconds?) before an implant becomes usable.
|
|
||||||
* @param implant the type of implant
|
* @param implant the type of implant
|
||||||
* @param activation the activation timer;
|
* @param initialization the amount of time necessary until this implant is ready to be activated;
|
||||||
* technically, this is "unconfirmed"
|
* technically, this is unconfirmed
|
||||||
|
* @param active whether this implant is turned on;
|
||||||
|
* technically, this is unconfirmed
|
||||||
* @see `ImplantType`
|
* @see `ImplantType`
|
||||||
*/
|
*/
|
||||||
final case class ImplantEntry(implant : ImplantType.Value,
|
final case class ImplantEntry(implant : ImplantType.Value,
|
||||||
activation : Option[Int]) extends StreamBitSize {
|
initialization : Option[Int],
|
||||||
|
active : Boolean) extends StreamBitSize {
|
||||||
override def bitsize : Long = {
|
override def bitsize : Long = {
|
||||||
val activationSize = if(activation.isDefined) { 12L } else { 5L }
|
val timerSize = initialization match { case Some(_) => 8L ; case None => 1L }
|
||||||
5L + activationSize
|
9L + timerSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ImplantEntry {
|
||||||
|
def apply(implant : ImplantType.Value, initialization : Option[Int]) : ImplantEntry = {
|
||||||
|
ImplantEntry(implant, initialization, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.<br>
|
* na
|
||||||
* <br>
|
* @param unk1 na
|
||||||
* This densely-packed information outlines most of the specifics required to depict a character as an avatar.
|
* @param unk2 na
|
||||||
* It goes into depth about information related to the given character in-game career that is not revealed to other players.
|
*/
|
||||||
* To be specific, it passes more thorough data about the character that the client can display to the owner of the client.
|
final case class DCDExtra1(unk1 : String,
|
||||||
* For example, health is a full number, rather than a percentage.
|
unk2 : Int) extends StreamBitSize {
|
||||||
* Just as prominent is the list of first time events and the list of completed tutorials.
|
override def bitsize : Long = 16L + StreamBitSize.stringBitSize(unk1)
|
||||||
* Additionally, a full inventory, as opposed to the initial five weapon slots.
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* na
|
||||||
|
* @param unk1 an
|
||||||
|
* @param unk2 na
|
||||||
|
*/
|
||||||
|
final case class DCDExtra2(unk1 : Int,
|
||||||
|
unk2 : Int) extends StreamBitSize {
|
||||||
|
override def bitsize : Long = 13L
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.
|
||||||
|
* @see `CharacterData`
|
||||||
|
* @see `CertificationType`
|
||||||
* @param bep the avatar's battle experience points, which determines his Battle Rank
|
* @param bep the avatar's battle experience points, which determines his Battle Rank
|
||||||
* @param cep the avatar's command experience points, which determines his Command Rank
|
* @param cep the avatar's command experience points, which determines his Command Rank
|
||||||
* @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value;
|
* @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value;
|
||||||
|
|
@ -44,17 +67,39 @@ final case class ImplantEntry(implant : ImplantType.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;
|
||||||
* range is 0-65535;
|
* range is 0-65535;
|
||||||
* the avatar's `y` armor points is tied to their exo-suit type
|
* the avatar's `y` armor points is tied to their exo-suit type
|
||||||
* @param unk1 na;
|
|
||||||
* defaults to 1
|
|
||||||
* @param unk2 na;
|
|
||||||
* defaults to 7
|
|
||||||
* @param unk3 na;
|
|
||||||
* defaults to 7
|
|
||||||
* @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;
|
||||||
* range is 0-65535
|
* range is 0-65535
|
||||||
* @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;
|
||||||
* range is 0-65535
|
* range is 0-65535
|
||||||
* @param certs the `List` of active certifications
|
* @param certs the `List` of certifications
|
||||||
|
*/
|
||||||
|
final case class DetailedCharacterA(bep : Long,
|
||||||
|
cep : Long,
|
||||||
|
unk1 : Long,
|
||||||
|
unk2 : Long,
|
||||||
|
unk3 : Long,
|
||||||
|
healthMax : Int,
|
||||||
|
health : Int,
|
||||||
|
unk4 : Boolean,
|
||||||
|
armor : Int,
|
||||||
|
unk5 : Long,
|
||||||
|
staminaMax : Int,
|
||||||
|
stamina : Int,
|
||||||
|
unk6 : Int,
|
||||||
|
unk7 : Int,
|
||||||
|
unk8 : Long,
|
||||||
|
unk9 : List[Int],
|
||||||
|
certs : List[CertificationType.Value]) extends StreamBitSize {
|
||||||
|
override def bitsize : Long = {
|
||||||
|
val certSize : Long = certs.length * 8
|
||||||
|
428L + certSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.
|
||||||
|
* @see `CharacterData`
|
||||||
|
* @see `Cosmetics`
|
||||||
* @param implants the `List` of implant slots currently possessed by this avatar
|
* @param implants the `List` of implant slots currently possessed by this avatar
|
||||||
* @param firstTimeEvents the list of first time events performed by this avatar;
|
* @param 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;
|
||||||
|
|
@ -65,92 +110,160 @@ final case class ImplantEntry(implant : ImplantType.Value,
|
||||||
* @param cosmetics optional decorative features that are added to the player's head model by console/chat commands;
|
* @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;
|
* they become available at battle rank 24;
|
||||||
* these flags do not exist if they are not applicable
|
* these flags do not exist if they are not applicable
|
||||||
* @see `CharacterData`<br>
|
|
||||||
* `CertificationType`
|
|
||||||
*/
|
*/
|
||||||
final case class DetailedCharacterData(bep : Long,
|
final case class DetailedCharacterB(unk1 : Option[Long],
|
||||||
cep : Long,
|
implants : List[ImplantEntry],
|
||||||
healthMax : Int,
|
unk2 : List[DCDExtra1],
|
||||||
health : Int,
|
unk3 : List[DCDExtra1],
|
||||||
armor : Int,
|
firstTimeEvents : List[String],
|
||||||
unk1 : Int, //1
|
tutorials : List[String],
|
||||||
unk2 : Int, //7
|
unk4 : Long,
|
||||||
unk3 : Int, //7
|
unk5 : Long,
|
||||||
staminaMax : Int,
|
unk6 : Long,
|
||||||
stamina : Int,
|
unk7 : Long,
|
||||||
certs : List[CertificationType.Value],
|
unk8 : Long,
|
||||||
implants : List[ImplantEntry],
|
unk9 : Option[DCDExtra2],
|
||||||
firstTimeEvents : List[String],
|
unkA : List[Long],
|
||||||
tutorials : List[String],
|
unkB : List[String],
|
||||||
cosmetics : Option[Cosmetics])
|
unkC : Boolean,
|
||||||
(pad_length : Option[Int]) extends ConstructorData {
|
cosmetics : Option[Cosmetics])
|
||||||
|
(bep : Long,
|
||||||
|
pad_length : Option[Int]) extends StreamBitSize {
|
||||||
override def bitsize : Long = {
|
override def bitsize : Long = {
|
||||||
//factor guard bool values into the base size, not corresponding optional fields, unless contained or enumerated
|
//unk1
|
||||||
val certSize = (certs.length + 1) * 8 //cert list
|
val unk1Size = unk1 match { case Some(_) => 32L ; case None => 0L }
|
||||||
var implantSize : Long = 0L //implant list
|
//implant list
|
||||||
for(entry <- implants) {
|
val implantSize : Long = implants.foldLeft(0L)(_ + _.bitsize)
|
||||||
implantSize += entry.bitsize
|
//fte list
|
||||||
|
val eventListSize : Long = firstTimeEvents.foldLeft(0L)(_ + StreamBitSize.stringBitSize(_))
|
||||||
|
//tutorial list
|
||||||
|
val tutorialListSize : Long = tutorials.foldLeft(0L)(_ + StreamBitSize.stringBitSize(_))
|
||||||
|
val unk2Len = unk2.size
|
||||||
|
val unk3Len = unk3.size
|
||||||
|
val unkAllLen = unk2Len + unk3Len
|
||||||
|
val unk2_3ListSize : Long = if(unk2Len > 0) {
|
||||||
|
unkAllLen * unk2.head.bitsize
|
||||||
}
|
}
|
||||||
val implantPadding = DetailedCharacterData.implantFieldPadding(implants, pad_length)
|
else if(unk3Len > 0) {
|
||||||
val fteLen = firstTimeEvents.size //fte list
|
unkAllLen * unk3.head.bitsize
|
||||||
var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen, implantPadding)
|
|
||||||
for(str <- firstTimeEvents) {
|
|
||||||
eventListSize += StreamBitSize.stringBitSize(str)
|
|
||||||
}
|
}
|
||||||
val tutLen = tutorials.size //tutorial list
|
else {
|
||||||
var tutorialListSize : Long = 32L + DetailedCharacterData.tutPadding(fteLen, tutLen, implantPadding)
|
0L
|
||||||
for(str <- tutorials) {
|
|
||||||
tutorialListSize += StreamBitSize.stringBitSize(str)
|
|
||||||
}
|
}
|
||||||
val br24 = DetailedCharacterData.isBR24(bep) //character is at least BR24
|
//character is at least BR24
|
||||||
val extraBitSize : Long = if(br24) { 33L } else { 46L }
|
val br24 = DetailedCharacterData.isBR24(bep)
|
||||||
|
val unk9Size : Long = if(br24) { 0L } else { 13L }
|
||||||
|
val unkASize : Long = unkA.length * 32L
|
||||||
|
val unkBSize : Long = unkB.foldLeft(0L)(_ + StreamBitSize.stringBitSize(_))
|
||||||
val cosmeticsSize : Long = if(br24) { cosmetics.get.bitsize } else { 0L }
|
val cosmeticsSize : Long = if(br24) { cosmetics.get.bitsize } else { 0L }
|
||||||
598L + certSize + implantSize + eventListSize + extraBitSize + cosmeticsSize + tutorialListSize
|
|
||||||
|
val paddingSize : Int =
|
||||||
|
DetailedCharacterData.paddingCalculations(pad_length, implants, Nil)(unk2Len) + /* unk2 */
|
||||||
|
DetailedCharacterData.paddingCalculations(pad_length, implants, List(unk2))(unk3Len) + /* unk3 */
|
||||||
|
DetailedCharacterData.paddingCalculations(pad_length, implants, List(unk3, unk2))(firstTimeEvents.length) + /* firstTimeEvents */
|
||||||
|
DetailedCharacterData.paddingCalculations(pad_length, implants, List(firstTimeEvents, unk3, unk2))(tutorials.size) + /* tutorials */
|
||||||
|
DetailedCharacterData.paddingCalculations(
|
||||||
|
DetailedCharacterData.displaceByUnk9(pad_length, unk9, 5),
|
||||||
|
implants,
|
||||||
|
List(DetailedCharacterData.optToList(unk9), tutorials, firstTimeEvents, unk3, unk2)
|
||||||
|
)(unkB.length) /* unkB */
|
||||||
|
275L + unk1Size + implantSize + eventListSize + unk2_3ListSize + tutorialListSize + unk9Size + unkASize + unkBSize + cosmeticsSize + paddingSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.<br>
|
||||||
|
* <br>
|
||||||
|
* This densely-packed information outlines most of the specifics required to depict a character as an avatar.
|
||||||
|
* It goes into depth about information related to the given character in-game career that is not revealed to other players.
|
||||||
|
* To be specific, it passes more thorough data about the character that the client can display to the owner of the client.
|
||||||
|
* For example, health is a full number, rather than a percentage, as is the case with `CharacterData`.
|
||||||
|
* Just as prominent is the list of first time events and the list of completed tutorials.
|
||||||
|
* Additionally, a full inventory, as opposed to the initial five weapon slots.
|
||||||
|
* @see `CharacterData`
|
||||||
|
*/
|
||||||
|
final case class DetailedCharacterData(a : DetailedCharacterA,
|
||||||
|
b : DetailedCharacterB)
|
||||||
|
(pad_length : Option[Int]) extends ConstructorData {
|
||||||
|
|
||||||
|
override def bitsize : Long = a.bitsize + b.bitsize
|
||||||
|
}
|
||||||
|
|
||||||
object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
/**
|
def apply(bep : Long,
|
||||||
* Overloaded constructor for `DetailedCharacterData` that requires an inventory and drops unknown values.
|
cep : Long,
|
||||||
* @param bep the avatar's battle experience points, which determines his Battle Rank
|
healthMax : Int,
|
||||||
* @param cep the avatar's command experience points, which determines his Command Rank
|
health : Int,
|
||||||
* @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value
|
armor : Int,
|
||||||
* @param health for `x / y` of hitpoints, this is the avatar's `x` value
|
staminaMax : Int,
|
||||||
* @param armor for `x / y` of armor points, this is the avatar's `x` value
|
stamina : Int,
|
||||||
* @param staminaMax for `x / y` of stamina points, this is the avatar's `y` value
|
certs : List[CertificationType.Value],
|
||||||
* @param stamina for `x / y` of stamina points, this is the avatar's `x` value
|
implants : List[ImplantEntry],
|
||||||
* @param certs the `List` of active certifications
|
firstTimeEvents : List[String],
|
||||||
* @param implants the `List` of implant slots currently possessed by this avatar
|
tutorials : List[String],
|
||||||
* @param firstTimeEvents the list of first time events performed by this avatar
|
cosmetics : Option[Cosmetics]) : Option[Int]=>DetailedCharacterData = {
|
||||||
* @param tutorials the list of tutorials completed by this avatar
|
val a = DetailedCharacterA(
|
||||||
* @return a `DetailedCharacterData` object
|
bep,
|
||||||
*/
|
cep,
|
||||||
def apply(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]) : (Option[Int])=>DetailedCharacterData =
|
0L,
|
||||||
DetailedCharacterData(bep, cep, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, certs, implants, firstTimeEvents, tutorials, cosmetics)
|
0L,
|
||||||
|
0L,
|
||||||
|
healthMax, health,
|
||||||
|
false,
|
||||||
|
armor,
|
||||||
|
0L,
|
||||||
|
staminaMax, stamina,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0L,
|
||||||
|
List(0,0,0,0,0,0),
|
||||||
|
certs
|
||||||
|
)
|
||||||
|
val b : (Long, Option[Int]) => DetailedCharacterB = DetailedCharacterB(
|
||||||
|
None,
|
||||||
|
implants,
|
||||||
|
Nil,
|
||||||
|
Nil,
|
||||||
|
firstTimeEvents,
|
||||||
|
tutorials,
|
||||||
|
0L,
|
||||||
|
0L,
|
||||||
|
0L,
|
||||||
|
0L,
|
||||||
|
0L,
|
||||||
|
None,
|
||||||
|
Nil,
|
||||||
|
Nil,
|
||||||
|
false,
|
||||||
|
cosmetics
|
||||||
|
)
|
||||||
|
(pad_length : Option[Int]) => DetailedCharacterData(a, b(a.bep, pad_length))(pad_length)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `Codec` for entries in the `List` of implants.
|
* `Codec` for entries in the `List` of implants.
|
||||||
*/
|
*/
|
||||||
private val implant_entry_codec : Codec[ImplantEntry] = (
|
private val implant_entry_codec : Codec[ImplantEntry] = (
|
||||||
("implant" | ImplantType.codec) ::
|
("implant" | uint8L) ::
|
||||||
(bool >>:~ { guard =>
|
(bool >>:~ { guard =>
|
||||||
newcodecs.binary_choice(guard, uintL(5), uintL(12)).hlist
|
newcodecs.binary_choice(guard, uint(1), uint8L).hlist
|
||||||
})
|
})
|
||||||
).xmap[ImplantEntry] (
|
).xmap[ImplantEntry] (
|
||||||
{
|
{
|
||||||
case implant :: true :: _ :: HNil =>
|
case implant :: true :: n :: HNil => //initialized (no timer), active/inactive?
|
||||||
ImplantEntry(implant, None)
|
val activeBool : Boolean = n != 0
|
||||||
|
ImplantEntry(ImplantType(implant), None, activeBool) //TODO catch potential NoSuchElementException?
|
||||||
|
|
||||||
case implant :: false :: extra :: HNil =>
|
case implant :: false :: extra :: HNil => //uninitialized (timer), inactive
|
||||||
ImplantEntry(implant, Some(extra))
|
ImplantEntry(ImplantType(implant), Some(extra), false) //TODO catch potential NoSuchElementException?
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case ImplantEntry(implant, None) =>
|
case ImplantEntry(implant, None, n) => //initialized (no timer), active/inactive?
|
||||||
implant :: true :: 0 :: HNil
|
val activeInt : Int = if(n) { 1 } else { 0 }
|
||||||
|
implant.id :: true :: activeInt :: HNil
|
||||||
|
|
||||||
case ImplantEntry(implant, Some(extra)) =>
|
case ImplantEntry(implant, Some(extra), _) => //uninitialized (timer), inactive
|
||||||
implant :: false :: extra :: HNil
|
implant.id :: false :: extra :: HNil
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -176,21 +289,215 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The padding value of the first entry in either of two byte-aligned `List` structures.
|
* `Codec` for a `List` of `DCDExtra1` objects.
|
||||||
* @param implants implant entries
|
* The first entry contains a padded `String` so it must be processed different from the remainder.
|
||||||
* @return the pad length in bits `0 <= n < 8`
|
|
||||||
*/
|
*/
|
||||||
private def implantFieldPadding(implants : List[ImplantEntry], varBit : Option[Int] = None) : Int = {
|
private def dcd_list_codec(padFunc : (Long)=>Int) : Codec[List[DCDExtra1]] = (
|
||||||
val base : Int = 5 //the offset with no implant entries
|
uint8 >>:~ { size =>
|
||||||
val baseOffset : Int = base - varBit.getOrElse(0)
|
conditional(size > 0, dcd_extra1_codec(padFunc(size))) ::
|
||||||
val resultA = if(baseOffset < 0) { 8 - baseOffset } else { baseOffset % 8 }
|
PacketHelpers.listOfNSized(size - 1, dcd_extra1_codec(0))
|
||||||
|
}
|
||||||
|
).xmap[List[DCDExtra1]] (
|
||||||
|
{
|
||||||
|
case _ :: Some(first) :: Nil :: HNil =>
|
||||||
|
List(first)
|
||||||
|
case _ :: Some(first) :: rest :: HNil =>
|
||||||
|
first +: rest
|
||||||
|
case _ :: None :: _ :: HNil =>
|
||||||
|
List()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case List() =>
|
||||||
|
0 :: None :: Nil :: HNil
|
||||||
|
case contents =>
|
||||||
|
contents.length :: contents.headOption :: contents.tail :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
var implantOffset : Int = 0
|
/**
|
||||||
implants.foreach({entry =>
|
* `Codec` for entries in the `List` of `DCDExtra1` objects.
|
||||||
implantOffset += entry.bitsize.toInt
|
* The first entry's size of 80 characters is hard-set by the client.
|
||||||
})
|
*/
|
||||||
val resultB : Int = resultA - (implantOffset % 8)
|
private def dcd_extra1_codec(pad : Int) : Codec[DCDExtra1] = (
|
||||||
if(resultB < 0) { 8 + resultB } else { resultB }
|
("unk1" | PacketHelpers.encodedStringAligned(pad)) ::
|
||||||
|
("unk2" | uint16L)
|
||||||
|
).xmap[DCDExtra1] (
|
||||||
|
{
|
||||||
|
case unk1 :: unk2 :: HNil =>
|
||||||
|
DCDExtra1(unk1, unk2)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case DCDExtra1(unk1, unk2) =>
|
||||||
|
unk1.slice(0, 80) :: unk2 :: HNil //max 80 characters
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A common `Codec` for a `List` of `String` objects
|
||||||
|
* used for first time events list and for the tutorials list.
|
||||||
|
* The first entry contains a padded `String` so it must be processed different from the remainder.
|
||||||
|
* @param padFunc a curried function awaiting the extracted length of the current `List`
|
||||||
|
*/
|
||||||
|
private def eventsListCodec(padFunc : (Long)=>Int) : Codec[List[String]] = (
|
||||||
|
uint32L >>:~ { size =>
|
||||||
|
conditional(size > 0, PacketHelpers.encodedStringAligned(padFunc(size))) ::
|
||||||
|
PacketHelpers.listOfNSized(size - 1, PacketHelpers.encodedString)
|
||||||
|
}
|
||||||
|
).xmap[List[String]] (
|
||||||
|
{
|
||||||
|
case _ :: Some(first) :: Nil :: HNil =>
|
||||||
|
List(first)
|
||||||
|
case _ :: Some(first) :: rest :: HNil =>
|
||||||
|
first +: rest
|
||||||
|
case _ :: None :: _ :: HNil =>
|
||||||
|
List()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case List() =>
|
||||||
|
0 :: None :: Nil :: HNil
|
||||||
|
case contents =>
|
||||||
|
contents.length :: contents.headOption :: contents.tail :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `Codec` for a `DCDExtra2` object.
|
||||||
|
*/
|
||||||
|
private val dcd_extra2_codec : Codec[DCDExtra2] = (
|
||||||
|
uint(5) ::
|
||||||
|
uint8L
|
||||||
|
).as[DCDExtra2]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `Codec` for a `List` of `String` objects.
|
||||||
|
* The first entry contains a padded `String` so it must be processed different from the remainder.
|
||||||
|
* The padding length is the conclusion of the summation of all the bits up until the point of this `String` object.
|
||||||
|
* Additionally, the length of this current string is also a necessary consideration.
|
||||||
|
* @see `paddingCalculations`
|
||||||
|
* @param padFunc a curried function awaiting the extracted length of the current `List` and will count the padding bits
|
||||||
|
*/
|
||||||
|
private def unkBCodec(padFunc : (Long)=>Int) : Codec[List[String]] = (
|
||||||
|
uint16L >>:~ { size =>
|
||||||
|
conditional(size > 0, PacketHelpers.encodedStringAligned(padFunc(size))) ::
|
||||||
|
PacketHelpers.listOfNSized(size - 1, PacketHelpers.encodedString)
|
||||||
|
}
|
||||||
|
).xmap[List[String]] (
|
||||||
|
{
|
||||||
|
case _ :: Some(first) :: Nil :: HNil =>
|
||||||
|
List(first)
|
||||||
|
case _ :: Some(first) :: rest :: HNil =>
|
||||||
|
first +: rest
|
||||||
|
case _ :: None :: _ :: HNil =>
|
||||||
|
List()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case List() =>
|
||||||
|
0 :: None :: Nil :: HNil
|
||||||
|
case contents =>
|
||||||
|
contents.length :: contents.headOption :: contents.tail :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suport function that obtains the "absolute list value" of an `Option` object.
|
||||||
|
* @param opt the `Option` object
|
||||||
|
* @return if defined, returns a `List` of the `Option` object's contents;
|
||||||
|
* if undefined (`None`), returns an empty list
|
||||||
|
*/
|
||||||
|
def optToList(opt : Option[Any]) : List[Any] = opt match {
|
||||||
|
case Some(o) => List(o)
|
||||||
|
case None => Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A very specific `Option` object addition function.
|
||||||
|
* If a condition is met, the current `Optional` value is incremented by a specific amount.
|
||||||
|
* @param start the original amount
|
||||||
|
* @param test the test on whether to add to `start`
|
||||||
|
* @param value how much to add to `start`
|
||||||
|
* @return the amount after testing
|
||||||
|
*/
|
||||||
|
def displaceByUnk9(start : Option[Int], test : Option[Any], value : Int) : Option[Int] = test match {
|
||||||
|
case Some(_) =>
|
||||||
|
Some(start.getOrElse(0) + value)
|
||||||
|
case None =>
|
||||||
|
start
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `List` of bit distances between different sets of `String` objects in the `DetailedCharacterData` `Codec`
|
||||||
|
* in reverse order of encountered `String` fields (later to earlier).
|
||||||
|
* The distances are not the actual lengths but are modulo eight.
|
||||||
|
* Specific strings include (the contents of):<br>
|
||||||
|
* - `unk9` (as a `List` object)<br>
|
||||||
|
* - `tutorials`<br>
|
||||||
|
* - `firstTimeEvents`<br>
|
||||||
|
* - `unk3`<br>
|
||||||
|
* - `unk2`
|
||||||
|
*/
|
||||||
|
private val displacementPerEntry : List[Int] = List(7, 0, 0, 0, 0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A curried function to calculate a cumulative padding value
|
||||||
|
* for whichever of the groups of `List` objects of `String` objects are found in a `DetailedCharacterData` object.
|
||||||
|
* Defines the expected base value - the starting value for determining the padding.
|
||||||
|
* The specific `String` object being considered is determined by the number of input lists.
|
||||||
|
* @see `paddingCalculations(Int, Option[Int], List[ImplantEntry], List[List[Any]])(Long)`
|
||||||
|
* @param contextOffset an inherited modification of the `base` padding value
|
||||||
|
* @param implants the list of implants in the stream
|
||||||
|
* @param prevLists all of the important previous lists
|
||||||
|
* @param currListLen the length of the current list
|
||||||
|
* @return the padding value for the target list
|
||||||
|
*/
|
||||||
|
def paddingCalculations(contextOffset : Option[Int], implants : List[ImplantEntry], prevLists : List[List[Any]])(currListLen : Long) : Int = {
|
||||||
|
paddingCalculations(3, contextOffset, implants, prevLists)(currListLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A curried function to calculate a cumulative padding value
|
||||||
|
* for whichever of the groups of `List` objects of `String` objects are found in a `DetailedCharacterData` object.
|
||||||
|
* The specific `String` object being considered is determined by the number of input lists.
|
||||||
|
* @see `paddingCalculations(Option[Int], List[ImplantEntry], List[List[Any/]/])(Long)`
|
||||||
|
* @param base the starting value with no implant entries, or bits from context
|
||||||
|
* @param contextOffset an inherited modification of the `base` padding value
|
||||||
|
* @param implants the list of implants in the stream
|
||||||
|
* @param prevLists all of the important previous lists
|
||||||
|
* @param currListLen the length of the current list
|
||||||
|
* @throws Exception if the number of input lists (`prevLists`) exceeds the number of expected bit distances between known lists
|
||||||
|
* @return the padding value for the target list;
|
||||||
|
* a value clamped between 0 and 7
|
||||||
|
*/
|
||||||
|
def paddingCalculations(base : Int, contextOffset : Option[Int], implants : List[ImplantEntry], prevLists : List[List[Any]])(currListLen : Long) : Int = {
|
||||||
|
if(prevLists.length > displacementPerEntry.length) {
|
||||||
|
throw new Exception("mismatched number of input lists compared to bit distances")
|
||||||
|
}
|
||||||
|
else if(currListLen > 0) {
|
||||||
|
//displacement into next byte of the content field of the first relevant string without padding
|
||||||
|
val baseResult : Int = base + contextOffset.getOrElse(0) + implants.foldLeft(0L)(_ + _.bitsize).toInt
|
||||||
|
val displacementResult : Int = (if(prevLists.isEmpty) {
|
||||||
|
baseResult
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//isolate the displacements that are important
|
||||||
|
val sequentialEmptyLists : List[List[Any]] = prevLists.takeWhile(_.isEmpty)
|
||||||
|
val offsetSlice : List[Int] = displacementPerEntry.drop(displacementPerEntry.length - sequentialEmptyLists.length)
|
||||||
|
if(prevLists.length == sequentialEmptyLists.length) { //if all lists are empty, factor in the base displacement
|
||||||
|
baseResult + offsetSlice.sum
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
offsetSlice.sum
|
||||||
|
}
|
||||||
|
}) % 8
|
||||||
|
if(displacementResult != 0) {
|
||||||
|
8 - displacementResult
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
0 //if the current list has no length, there's no need to pad it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -212,92 +519,86 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the padding of the first entry in the first time events list.
|
* By comparing the battle experience points to a fixed number of points,
|
||||||
* @param len the length of the first time events list
|
* determine if the player is at least battle rank 24.
|
||||||
* @param implantPadding the padding that resulted from implant entries
|
* Important things happen if the player is at least battle rank 24 ...
|
||||||
* @return the pad length in bits `0 <= n < 8`
|
* @param bep the battle experience points being compared
|
||||||
|
* @return `true`, if the battle experience points are enough to be a player of the esteemed battle rank
|
||||||
*/
|
*/
|
||||||
private def ftePadding(len : Long, implantPadding : Int) : Int = {
|
|
||||||
//TODO the proper padding length should reflect all variability in the stream prior to this point
|
|
||||||
if(len > 0) {
|
|
||||||
implantPadding
|
|
||||||
}
|
|
||||||
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 first time events list
|
|
||||||
* @param len2 the length of the tutorial list
|
|
||||||
* @param implantPadding the padding that resulted from implant entries
|
|
||||||
* @return the pad length in bits `n < 8`
|
|
||||||
*/
|
|
||||||
private def tutPadding(len : Long, len2 : Long, implantPadding : Int) : Int = {
|
|
||||||
if(len > 0) {
|
|
||||||
0 //automatic alignment from previous List
|
|
||||||
}
|
|
||||||
else if(len2 > 0) {
|
|
||||||
implantPadding //need to align for elements
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
0 //both lists are empty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def isBR24(bep : Long) : Boolean = bep > 2286230
|
def isBR24(bep : Long) : Boolean = bep > 2286230
|
||||||
|
|
||||||
def codec(pad_length : Option[Int]) : Codec[DetailedCharacterData] = (
|
val a_codec : Codec[DetailedCharacterA] = (
|
||||||
("bep" | uint32L) >>:~ { bep =>
|
("bep" | uint32L) ::
|
||||||
("cep" | uint32L) ::
|
("cep" | uint32L) ::
|
||||||
ignore(96) ::
|
("unk1" | uint32L) ::
|
||||||
("healthMax" | uint16L) ::
|
("unk2" | uint32L) ::
|
||||||
("health" | uint16L) ::
|
("unk3" | uint32L) ::
|
||||||
ignore(1) ::
|
("healthMax" | uint16L) ::
|
||||||
("armor" | uint16L) ::
|
("health" | uint16L) ::
|
||||||
ignore(9) ::
|
("unk4" | bool) ::
|
||||||
("unk1" | uint8L) ::
|
("armor" | uint16L) ::
|
||||||
ignore(8) ::
|
("unk5" | uint32) :: //endianness?
|
||||||
("unk2" | uint4L) ::
|
("staminaMax" | uint16L) ::
|
||||||
("unk3" | uintL(3)) ::
|
("stamina" | uint16L) ::
|
||||||
("staminaMax" | uint16L) ::
|
conditional(false, uint32L) :: //see ps.c: sub_901150, line#1070692
|
||||||
("stamina" | uint16L) ::
|
("unk6" | uint16L) ::
|
||||||
ignore(147) ::
|
("unk7" | uint(3)) ::
|
||||||
("certs" | listOfN(uint8L, CertificationType.codec)) ::
|
("unk8" | uint32L) ::
|
||||||
optional(bool, uint32L) :: //ask about sample CCRIDER
|
("unk9" | PacketHelpers.listOfNSized(6, uint16L)) :: //always length of 6
|
||||||
ignore(4) ::
|
("certs" | listOfN(uint8L, CertificationType.codec))
|
||||||
(("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants =>
|
).exmap[DetailedCharacterA] (
|
||||||
ignore(12) ::
|
|
||||||
(("firstTimeEvent_length" | uint32L) >>:~ { len =>
|
|
||||||
conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned(ftePadding(len, implantFieldPadding(implants, pad_length)))) ::
|
|
||||||
("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) ::
|
|
||||||
(("tutorial_length" | uint32L) >>:~ { len2 =>
|
|
||||||
conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned(tutPadding(len, len2, implantFieldPadding(implants, pad_length)))) ::
|
|
||||||
("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) ::
|
|
||||||
ignore(160) ::
|
|
||||||
(bool >>:~ { br24 => //BR24+
|
|
||||||
newcodecs.binary_choice(br24, ignore(33), ignore(46)) ::
|
|
||||||
conditional(br24, Cosmetics.codec)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
).exmap[DetailedCharacterData] (
|
|
||||||
{
|
{
|
||||||
case bep :: cep :: _ :: hpmax :: hp :: _ :: armor :: _ :: u1 :: _ :: u2 :: u3 :: stamax :: stam :: _ :: certs :: _ :: _ :: implants :: _ :: _ :: fte0 :: fte1 :: _ :: tut0 :: tut1 :: _ :: _ :: _ :: cosmetics :: HNil =>
|
case bep :: cep :: u1 :: u2 :: u3 :: healthMax :: health :: u4 :: armor :: u5 :: staminaMax :: stamina :: None :: u6 :: u7 :: u8 :: u9 :: certs :: HNil =>
|
||||||
//prepend the displaced first elements to their lists
|
Attempt.successful(DetailedCharacterA(bep, cep, u1, u2, u3, healthMax, health, u4, armor, u5, staminaMax, stamina, u6, u7, u8, u9, certs))
|
||||||
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(bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cosmetics)(pad_length))
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case DetailedCharacterData(bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cos) =>
|
case DetailedCharacterA(bep, cep, u1, u2, u3, healthMax, health, u4, armor, u5, staminaMax, stamina, u6, u7, u8, u9, certs) =>
|
||||||
|
Attempt.successful(
|
||||||
|
bep :: cep :: u1 :: u2 :: u3 :: healthMax :: health :: u4 :: armor :: u5 :: staminaMax :: stamina :: None :: u6 :: u7 :: u8 :: u9 :: certs :: HNil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def b_codec(bep : Long, pad_length : Option[Int]) : Codec[DetailedCharacterB] = (
|
||||||
|
optional(bool, "unk1" | uint32L) ::
|
||||||
|
(("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants =>
|
||||||
|
("unk2" | dcd_list_codec(paddingCalculations(pad_length, implants, Nil))) >>:~ { unk2 =>
|
||||||
|
("unk3" | dcd_list_codec(paddingCalculations(pad_length, implants, List(unk2)))) >>:~ { unk3 =>
|
||||||
|
("firstTimeEvents" | eventsListCodec(paddingCalculations(pad_length, implants, List(unk3, unk2)))) >>:~ { fte =>
|
||||||
|
("tutorials" | eventsListCodec(paddingCalculations(pad_length, implants, List(fte, unk3, unk2)))) >>:~ { tut =>
|
||||||
|
("unk4" | uint32L) ::
|
||||||
|
("unk5" | uint32L) ::
|
||||||
|
("unk6" | uint32L) ::
|
||||||
|
("unk7" | uint32L) ::
|
||||||
|
("unk8" | uint32L) ::
|
||||||
|
(bool >>:~ { br24 => //BR24+
|
||||||
|
conditional(!br24, "unk9" | dcd_extra2_codec) >>:~ { unk9 =>
|
||||||
|
("unkA" | listOfN(uint16L, uint32L)) ::
|
||||||
|
("unkB" | unkBCodec(
|
||||||
|
paddingCalculations(
|
||||||
|
displaceByUnk9(pad_length, unk9, 5),
|
||||||
|
implants,
|
||||||
|
List(optToList(unk9), tut, fte, unk3, unk2)
|
||||||
|
)
|
||||||
|
)) ::
|
||||||
|
("unkC" | bool) ::
|
||||||
|
conditional(br24, "cosmetics" | Cosmetics.codec)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).exmap[DetailedCharacterB] (
|
||||||
|
{
|
||||||
|
case u1 :: implants :: u2 :: u3 :: fte :: tut :: u4 :: u5 :: u6 :: u7 :: u8 :: _ :: u9 :: uA :: uB :: uC :: cosmetics :: HNil =>
|
||||||
|
Attempt.successful(
|
||||||
|
DetailedCharacterB(u1, implants, u2, u3, fte, tut, u4, u5, u6, u7, u8, u9, uA, uB, uC, cosmetics)(bep, pad_length)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case DetailedCharacterB(u1, implants, u2, u3, fte, tut, u4, u5, u6, u7, u8, u9, uA, uB, uC, cosmetics) =>
|
||||||
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)
|
||||||
|
|
@ -305,20 +606,26 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
else {
|
else {
|
||||||
recursiveEnsureImplantSlots(implantCapacity, implants)
|
recursiveEnsureImplantSlots(implantCapacity, implants)
|
||||||
}
|
}
|
||||||
//shift the first elements off their lists
|
|
||||||
val (firstEvent, fteListCopy) = fteList match {
|
|
||||||
case (f : String) +: Nil => (Some(f), Nil)
|
|
||||||
case ((f : String) +: (rest : List[String])) => (Some(f), rest)
|
|
||||||
case Nil => (None, Nil)
|
|
||||||
}
|
|
||||||
val (firstTutorial, tutListCopy) = tutList match {
|
|
||||||
case (f : String) +: Nil => (Some(f), Nil)
|
|
||||||
case ((f : String) +: (rest : List[String])) => (Some(f), rest)
|
|
||||||
case Nil => (None, Nil)
|
|
||||||
}
|
|
||||||
val br24 : Boolean = isBR24(bep)
|
val br24 : Boolean = isBR24(bep)
|
||||||
val cosmetics : Option[Cosmetics] = if(br24) { cos } else { None }
|
val cos : Option[Cosmetics] = if(br24) { cosmetics } else { None }
|
||||||
Attempt.successful(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 :: HNil)
|
Attempt.successful(
|
||||||
|
u1 :: implantList :: u2 :: u3 :: fte :: tut :: u4 :: u5 :: u6 :: u7 :: u8 :: br24 :: u9 :: uA :: uB :: uC :: cos :: HNil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def codec(pad_length : Option[Int]) : Codec[DetailedCharacterData] = (
|
||||||
|
("a" | a_codec) >>:~ { a =>
|
||||||
|
("b" | b_codec(a.bep, pad_length)).hlist
|
||||||
|
}
|
||||||
|
).exmap[DetailedCharacterData] (
|
||||||
|
{
|
||||||
|
case a :: b :: HNil =>
|
||||||
|
Attempt.successful(DetailedCharacterData(a, b)(pad_length))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case DetailedCharacterData(a, b) =>
|
||||||
|
Attempt.successful(a :: b :: HNil)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,15 +21,16 @@ import shapeless.{::, HNil}
|
||||||
* The presence or absence of position data as the first division creates a cascading effect
|
* The presence or absence of position data as the first division creates a cascading effect
|
||||||
* causing all of fields in the other two divisions to gain offsets.
|
* causing all of fields in the other two divisions to gain offsets.
|
||||||
* These offsets exist in the form of `String` and `List` padding.
|
* These offsets exist in the form of `String` and `List` padding.
|
||||||
* @see `DetailedCharacterData`<br>
|
* @see `CharacterAppearanceData`
|
||||||
* `InventoryData`<br>
|
* @see `DetailedCharacterData`
|
||||||
* `DrawnSlot`
|
* @see `InventoryData`
|
||||||
|
* @see `DrawnSlot`
|
||||||
* @param pos the optional position of the character in the world environment
|
* @param pos the optional position of the character in the world environment
|
||||||
* @param basic_appearance common fields regarding the the character's appearance
|
* @param basic_appearance common fields regarding the the character's appearance
|
||||||
* @param character_data the class-specific data that explains about the character
|
* @param character_data the class-specific data that discusses the character
|
||||||
* @param position_defined used by the `Codec` to seed the state of the optional `pos` field
|
* @param position_defined used to seed the state of the optional position fields
|
||||||
* @param inventory the player's full inventory
|
* @param inventory the player's full or partial (holsters-only) inventory
|
||||||
* @param drawn_slot the holster that is initially drawn
|
* @param drawn_slot the holster that is depicted as exposed, or "drawn"
|
||||||
*/
|
*/
|
||||||
final case class DetailedPlayerData(pos : Option[PlacementData],
|
final case class DetailedPlayerData(pos : Option[PlacementData],
|
||||||
basic_appearance : CharacterAppearanceData,
|
basic_appearance : CharacterAppearanceData,
|
||||||
|
|
@ -54,8 +55,8 @@ object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
|
||||||
* This constructor should be used for players that are mounted.
|
* This constructor should be used for players that are mounted.
|
||||||
* @param basic_appearance a curried function for the common fields regarding the the character's appearance
|
* @param basic_appearance a curried function for the common fields regarding the the character's appearance
|
||||||
* @param character_data a curried function for the class-specific data that explains about the character
|
* @param character_data a curried function for the class-specific data that explains about the character
|
||||||
* @param inventory the player's inventory
|
* @param inventory the player's full or partial (holsters-only) inventory
|
||||||
* @param drawn_slot the holster that is initially drawn;
|
* @param drawn_slot the holster that is depicted as exposed, or "drawn";
|
||||||
* technically, always `DrawnSlot.None`, but the field is preserved to maintain similarity
|
* technically, always `DrawnSlot.None`, but the field is preserved to maintain similarity
|
||||||
* @return a `DetailedPlayerData` object
|
* @return a `DetailedPlayerData` object
|
||||||
*/
|
*/
|
||||||
|
|
@ -70,7 +71,7 @@ object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
|
||||||
* This constructor should be used for players that are mounted.
|
* This constructor should be used for players that are mounted.
|
||||||
* @param basic_appearance a curried function for the common fields regarding the the character's appearance
|
* @param basic_appearance a curried function for the common fields regarding the the character's appearance
|
||||||
* @param character_data a curried function for the class-specific data that explains about the character
|
* @param character_data a curried function for the class-specific data that explains about the character
|
||||||
* @param drawn_slot the holster that is initially drawn;
|
* @param drawn_slot the holster that is depicted as exposed, or "drawn;"
|
||||||
* technically, always `DrawnSlot.None`, but the field is preserved to maintain similarity
|
* technically, always `DrawnSlot.None`, but the field is preserved to maintain similarity
|
||||||
* @return a `DetailedPlayerData` object
|
* @return a `DetailedPlayerData` object
|
||||||
*/
|
*/
|
||||||
|
|
@ -86,8 +87,8 @@ object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
|
||||||
* @param pos the optional position of the character in the world environment
|
* @param pos the optional position of the character in the world environment
|
||||||
* @param basic_appearance a curried function for the common fields regarding the the character's appearance
|
* @param basic_appearance a curried function for the common fields regarding the the character's appearance
|
||||||
* @param character_data a curried function for the class-specific data that explains about the character
|
* @param character_data a curried function for the class-specific data that explains about the character
|
||||||
* @param inventory the player's inventory
|
* @param inventory the player's full or partial (holsters-only) inventory
|
||||||
* @param drawn_slot the holster that is initially drawn
|
* @param drawn_slot the holster that is depicted as exposed, or "drawn"
|
||||||
* @return a `DetailedPlayerData` object
|
* @return a `DetailedPlayerData` object
|
||||||
*/
|
*/
|
||||||
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
|
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
|
||||||
|
|
@ -102,7 +103,7 @@ object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
|
||||||
* @param pos the optional position of the character in the world environment
|
* @param pos the optional position of the character in the world environment
|
||||||
* @param basic_appearance a curried function for the common fields regarding the the character's appearance
|
* @param basic_appearance a curried function for the common fields regarding the the character's appearance
|
||||||
* @param character_data a curried function for the class-specific data that explains about the character
|
* @param character_data a curried function for the class-specific data that explains about the character
|
||||||
* @param drawn_slot the holster that is initially drawn
|
* @param drawn_slot the holster that is depicted as exposed, or "drawn"
|
||||||
* @return a `DetailedPlayerData` object
|
* @return a `DetailedPlayerData` object
|
||||||
*/
|
*/
|
||||||
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
|
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ object PlayerData extends Marshallable[PlayerData] {
|
||||||
*/
|
*/
|
||||||
def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type) : PlayerData = {
|
def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type) : PlayerData = {
|
||||||
val appearance = basic_appearance(5)
|
val appearance = basic_appearance(5)
|
||||||
PlayerData(None, appearance, character_data(appearance.backpack, true), Some(inventory), drawn_slot)(false)
|
PlayerData(None, appearance, character_data(appearance.a.altModel, true), Some(inventory), drawn_slot)(false)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Overloaded constructor that ignores the coordinate information and the inventory.
|
* Overloaded constructor that ignores the coordinate information and the inventory.
|
||||||
|
|
@ -79,7 +79,7 @@ object PlayerData extends Marshallable[PlayerData] {
|
||||||
*/
|
*/
|
||||||
def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, drawn_slot : DrawnSlot.Type) : PlayerData = {
|
def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, drawn_slot : DrawnSlot.Type) : PlayerData = {
|
||||||
val appearance = basic_appearance(5)
|
val appearance = basic_appearance(5)
|
||||||
PlayerData(None, appearance, character_data(appearance.backpack, true), None, drawn_slot)(false)
|
PlayerData(None, appearance, character_data(appearance.a.altModel, true), None, drawn_slot)(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -95,7 +95,7 @@ object PlayerData extends Marshallable[PlayerData] {
|
||||||
*/
|
*/
|
||||||
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type) : PlayerData = {
|
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type) : PlayerData = {
|
||||||
val appearance = basic_appearance( PaddingOffset(Some(pos)) )
|
val appearance = basic_appearance( PaddingOffset(Some(pos)) )
|
||||||
PlayerData(Some(pos), appearance, character_data(appearance.backpack, false), Some(inventory), drawn_slot)(true)
|
PlayerData(Some(pos), appearance, character_data(appearance.a.altModel, false), Some(inventory), drawn_slot)(true)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Overloaded constructor that includes the coordinate information but ignores the inventory.
|
* Overloaded constructor that includes the coordinate information but ignores the inventory.
|
||||||
|
|
@ -109,7 +109,7 @@ object PlayerData extends Marshallable[PlayerData] {
|
||||||
*/
|
*/
|
||||||
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, drawn_slot : DrawnSlot.Type) : PlayerData = {
|
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, drawn_slot : DrawnSlot.Type) : PlayerData = {
|
||||||
val appearance = basic_appearance( PaddingOffset(Some(pos)) )
|
val appearance = basic_appearance( PaddingOffset(Some(pos)) )
|
||||||
PlayerData(Some(pos), appearance, character_data(appearance.backpack, false), None, drawn_slot)(true)
|
PlayerData(Some(pos), appearance, character_data(appearance.a.altModel, false), None, drawn_slot)(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -166,8 +166,8 @@ object PlayerData extends Marshallable[PlayerData] {
|
||||||
conditional(position_defined, "pos" | PlacementData.codec) >>:~ { pos =>
|
conditional(position_defined, "pos" | PlacementData.codec) >>:~ { pos =>
|
||||||
("basic_appearance" | CharacterAppearanceData.codec(PaddingOffset(pos))) >>:~ { app =>
|
("basic_appearance" | CharacterAppearanceData.codec(PaddingOffset(pos))) >>:~ { app =>
|
||||||
("character_data" | newcodecs.binary_choice(position_defined,
|
("character_data" | newcodecs.binary_choice(position_defined,
|
||||||
CharacterData.codec(app.backpack),
|
CharacterData.codec(app.b.backpack),
|
||||||
CharacterData.codec_seated(app.backpack))) ::
|
CharacterData.codec_seated(app.b.backpack))) ::
|
||||||
optional(bool, "inventory" | InventoryData.codec) ::
|
optional(bool, "inventory" | InventoryData.codec) ::
|
||||||
("drawn_slot" | DrawnSlot.codec) ::
|
("drawn_slot" | DrawnSlot.codec) ::
|
||||||
bool //usually false
|
bool //usually false
|
||||||
|
|
@ -193,7 +193,7 @@ object PlayerData extends Marshallable[PlayerData] {
|
||||||
*/
|
*/
|
||||||
def codec(offset : Int) : Codec[PlayerData] = (
|
def codec(offset : Int) : Codec[PlayerData] = (
|
||||||
("basic_appearance" | CharacterAppearanceData.codec(offset)) >>:~ { app =>
|
("basic_appearance" | CharacterAppearanceData.codec(offset)) >>:~ { app =>
|
||||||
("character_data" | CharacterData.codec_seated(app.backpack)) ::
|
("character_data" | CharacterData.codec_seated(app.b.backpack)) ::
|
||||||
optional(bool, "inventory" | InventoryData.codec) ::
|
optional(bool, "inventory" | InventoryData.codec) ::
|
||||||
("drawn_slot" | DrawnSlot.codec) ::
|
("drawn_slot" | DrawnSlot.codec) ::
|
||||||
bool //usually false
|
bool //usually false
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@ object VehicleData extends Marshallable[VehicleData] {
|
||||||
*/
|
*/
|
||||||
def PlayerData(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type, accumulative : Long) : Player_Data = {
|
def PlayerData(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type, accumulative : Long) : Player_Data = {
|
||||||
val appearance = basic_appearance(CumulativeSeatedPlayerNamePadding(accumulative))
|
val appearance = basic_appearance(CumulativeSeatedPlayerNamePadding(accumulative))
|
||||||
Player_Data(None, appearance, character_data(appearance.backpack, true), Some(inventory), drawn_slot)(false)
|
Player_Data(None, appearance, character_data(appearance.b.backpack, true), Some(inventory), drawn_slot)(false)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Constructor for `PlayerData` that ignores the coordinate information and the inventory
|
* Constructor for `PlayerData` that ignores the coordinate information and the inventory
|
||||||
|
|
@ -181,7 +181,7 @@ object VehicleData extends Marshallable[VehicleData] {
|
||||||
*/
|
*/
|
||||||
def PlayerData(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, drawn_slot : DrawnSlot.Type, accumulative : Long) : Player_Data = {
|
def PlayerData(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, drawn_slot : DrawnSlot.Type, accumulative : Long) : Player_Data = {
|
||||||
val appearance = basic_appearance(CumulativeSeatedPlayerNamePadding(accumulative))
|
val appearance = basic_appearance(CumulativeSeatedPlayerNamePadding(accumulative))
|
||||||
Player_Data.apply(None, appearance, character_data(appearance.backpack, true), None, drawn_slot)(false)
|
Player_Data.apply(None, appearance, character_data(appearance.b.backpack, true), None, drawn_slot)(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val driveState8u = PacketHelpers.createEnumerationCodec(DriveState, uint8L)
|
private val driveState8u = PacketHelpers.createEnumerationCodec(DriveState, uint8L)
|
||||||
|
|
|
||||||
|
|
@ -33,41 +33,67 @@ class CharacterDataTest extends Specification {
|
||||||
pos.vel.isDefined mustEqual true
|
pos.vel.isDefined mustEqual true
|
||||||
pos.vel.get mustEqual Vector3(1.4375f, -0.4375f, 0f)
|
pos.vel.get mustEqual Vector3(1.4375f, -0.4375f, 0f)
|
||||||
|
|
||||||
basic.app.name mustEqual "ScrawnyRonnie"
|
basic match {
|
||||||
basic.app.faction mustEqual PlanetSideEmpire.TR
|
case CharacterAppearanceData(a, b, ribbons) =>
|
||||||
basic.app.sex mustEqual CharacterGender.Male
|
a.app.name mustEqual "ScrawnyRonnie"
|
||||||
basic.app.head mustEqual 5
|
a.app.faction mustEqual PlanetSideEmpire.TR
|
||||||
basic.app.voice mustEqual CharacterVoice.Voice5
|
a.app.sex mustEqual CharacterGender.Male
|
||||||
basic.voice2 mustEqual 3
|
a.app.head mustEqual 5
|
||||||
basic.black_ops mustEqual false
|
a.app.voice mustEqual CharacterVoice.Voice5
|
||||||
basic.jammered mustEqual false
|
a.black_ops mustEqual false
|
||||||
basic.exosuit mustEqual ExoSuitType.Reinforced
|
a.jammered mustEqual false
|
||||||
basic.outfit_name mustEqual "Black Beret Armoured Corps"
|
a.exosuit mustEqual ExoSuitType.Reinforced
|
||||||
basic.outfit_logo mustEqual 23
|
a.unk1 mustEqual false
|
||||||
basic.facingPitch mustEqual 340.3125f
|
a.unk2 mustEqual None
|
||||||
basic.facingYawUpper mustEqual 0
|
a.unk3 mustEqual None
|
||||||
basic.lfs mustEqual false
|
a.unk4 mustEqual 0
|
||||||
basic.grenade_state mustEqual GrenadeState.None
|
a.unk5 mustEqual 0
|
||||||
basic.is_cloaking mustEqual false
|
a.unk6 mustEqual 30777081L
|
||||||
basic.charging_pose mustEqual false
|
a.unk7 mustEqual 1
|
||||||
basic.on_zipline mustEqual false
|
a.unk8 mustEqual 4
|
||||||
basic.ribbons.upper mustEqual MeritCommendation.MarkovVeteran
|
a.unk9 mustEqual 0
|
||||||
basic.ribbons.middle mustEqual MeritCommendation.HeavyInfantry4
|
a.unkA mustEqual 0
|
||||||
basic.ribbons.lower mustEqual MeritCommendation.TankBuster7
|
|
||||||
basic.ribbons.tos mustEqual MeritCommendation.SixYearTR
|
b.outfit_name mustEqual "Black Beret Armoured Corps"
|
||||||
|
b.outfit_logo mustEqual 23
|
||||||
|
b.backpack mustEqual false
|
||||||
|
b.facingPitch mustEqual 320.625f
|
||||||
|
b.facingYawUpper mustEqual 0
|
||||||
|
b.lfs mustEqual false
|
||||||
|
b.grenade_state mustEqual GrenadeState.None
|
||||||
|
b.is_cloaking mustEqual false
|
||||||
|
b.charging_pose mustEqual false
|
||||||
|
b.on_zipline mustEqual None
|
||||||
|
b.unk0 mustEqual 316554L
|
||||||
|
b.unk1 mustEqual false
|
||||||
|
b.unk2 mustEqual false
|
||||||
|
b.unk3 mustEqual false
|
||||||
|
b.unk4 mustEqual false
|
||||||
|
b.unk5 mustEqual false
|
||||||
|
b.unk6 mustEqual false
|
||||||
|
b.unk7 mustEqual false
|
||||||
|
|
||||||
|
ribbons.upper mustEqual MeritCommendation.MarkovVeteran
|
||||||
|
ribbons.middle mustEqual MeritCommendation.HeavyInfantry4
|
||||||
|
ribbons.lower mustEqual MeritCommendation.TankBuster7
|
||||||
|
ribbons.tos mustEqual MeritCommendation.SixYearTR
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
|
||||||
char.health mustEqual 255
|
char.health mustEqual 255
|
||||||
char.armor mustEqual 253
|
char.armor mustEqual 253
|
||||||
char.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
|
char.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
|
||||||
char.command_rank mustEqual 5
|
char.command_rank mustEqual 5
|
||||||
char.implant_effects.isDefined mustEqual true
|
char.implant_effects.length mustEqual 1
|
||||||
char.implant_effects.get mustEqual ImplantEffects.NoEffects
|
char.implant_effects.head mustEqual ImplantEffects.NoEffects
|
||||||
char.cosmetics.isDefined mustEqual true
|
char.cosmetics.isDefined mustEqual true
|
||||||
char.cosmetics.get.no_helmet mustEqual true
|
char.cosmetics.get.no_helmet mustEqual true
|
||||||
char.cosmetics.get.beret mustEqual true
|
char.cosmetics.get.beret mustEqual true
|
||||||
char.cosmetics.get.sunglasses mustEqual true
|
char.cosmetics.get.sunglasses mustEqual true
|
||||||
char.cosmetics.get.earpiece mustEqual true
|
char.cosmetics.get.earpiece mustEqual true
|
||||||
char.cosmetics.get.brimmed_cap mustEqual false
|
char.cosmetics.get.brimmed_cap mustEqual false
|
||||||
|
char.unk mustEqual 7
|
||||||
//short test of inventory items
|
//short test of inventory items
|
||||||
inv.isDefined mustEqual true
|
inv.isDefined mustEqual true
|
||||||
val contents = inv.get.contents
|
val contents = inv.get.contents
|
||||||
|
|
@ -116,7 +142,7 @@ class CharacterDataTest extends Specification {
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//
|
||||||
"decode (seated)" in {
|
"decode (seated)" in {
|
||||||
PacketCoding.DecodePacket(string_seated).require match {
|
PacketCoding.DecodePacket(string_seated).require match {
|
||||||
case ObjectCreateMessage(len, cls, guid, parent, data) =>
|
case ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||||
|
|
@ -126,29 +152,54 @@ class CharacterDataTest extends Specification {
|
||||||
parent mustEqual Some(ObjectCreateMessageParent(PlanetSideGUID(1234), 0))
|
parent mustEqual Some(ObjectCreateMessageParent(PlanetSideGUID(1234), 0))
|
||||||
data match {
|
data match {
|
||||||
case Some(PlayerData(None, basic, char, inv, hand)) =>
|
case Some(PlayerData(None, basic, char, inv, hand)) =>
|
||||||
basic.app.name mustEqual "ScrawnyRonnie"
|
basic match {
|
||||||
basic.app.faction mustEqual PlanetSideEmpire.TR
|
case CharacterAppearanceData(a, b, ribbons) =>
|
||||||
basic.app.sex mustEqual CharacterGender.Male
|
a.app.name mustEqual "ScrawnyRonnie"
|
||||||
basic.app.head mustEqual 5
|
a.app.faction mustEqual PlanetSideEmpire.TR
|
||||||
basic.app.voice mustEqual CharacterVoice.Voice5
|
a.app.sex mustEqual CharacterGender.Male
|
||||||
basic.voice2 mustEqual 3
|
a.app.head mustEqual 5
|
||||||
basic.black_ops mustEqual false
|
a.app.voice mustEqual CharacterVoice.Voice5
|
||||||
basic.jammered mustEqual false
|
a.black_ops mustEqual false
|
||||||
basic.exosuit mustEqual ExoSuitType.Reinforced
|
a.jammered mustEqual false
|
||||||
basic.outfit_name mustEqual "Black Beret Armoured Corps"
|
a.exosuit mustEqual ExoSuitType.Reinforced
|
||||||
basic.outfit_logo mustEqual 23
|
a.unk1 mustEqual false
|
||||||
basic.facingPitch mustEqual 340.3125f
|
a.unk2 mustEqual None
|
||||||
basic.facingYawUpper mustEqual 0
|
a.unk3 mustEqual None
|
||||||
basic.lfs mustEqual false
|
a.unk4 mustEqual 0
|
||||||
basic.grenade_state mustEqual GrenadeState.None
|
a.unk5 mustEqual 0
|
||||||
basic.is_cloaking mustEqual false
|
a.unk6 mustEqual 192L
|
||||||
basic.charging_pose mustEqual false
|
a.unk7 mustEqual 0
|
||||||
basic.on_zipline mustEqual false
|
a.unk8 mustEqual 0
|
||||||
basic.ribbons.upper mustEqual MeritCommendation.MarkovVeteran
|
a.unk9 mustEqual 0
|
||||||
basic.ribbons.middle mustEqual MeritCommendation.HeavyInfantry4
|
a.unkA mustEqual 0
|
||||||
basic.ribbons.lower mustEqual MeritCommendation.TankBuster7
|
|
||||||
basic.ribbons.tos mustEqual MeritCommendation.SixYearTR
|
b.outfit_name mustEqual "Black Beret Armoured Corps"
|
||||||
//etc..
|
b.outfit_logo mustEqual 23
|
||||||
|
b.backpack mustEqual false
|
||||||
|
b.facingPitch mustEqual 320.625f
|
||||||
|
b.facingYawUpper mustEqual 0
|
||||||
|
b.lfs mustEqual false
|
||||||
|
b.grenade_state mustEqual GrenadeState.None
|
||||||
|
b.is_cloaking mustEqual false
|
||||||
|
b.charging_pose mustEqual false
|
||||||
|
b.on_zipline mustEqual None
|
||||||
|
b.unk0 mustEqual 26L
|
||||||
|
b.unk1 mustEqual false
|
||||||
|
b.unk2 mustEqual false
|
||||||
|
b.unk3 mustEqual false
|
||||||
|
b.unk4 mustEqual false
|
||||||
|
b.unk5 mustEqual false
|
||||||
|
b.unk6 mustEqual false
|
||||||
|
b.unk7 mustEqual false
|
||||||
|
|
||||||
|
ribbons.upper mustEqual MeritCommendation.MarkovVeteran
|
||||||
|
ribbons.middle mustEqual MeritCommendation.HeavyInfantry4
|
||||||
|
ribbons.lower mustEqual MeritCommendation.TankBuster7
|
||||||
|
ribbons.tos mustEqual MeritCommendation.SixYearTR
|
||||||
|
//etc..
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
case _ =>
|
case _ =>
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
|
|
@ -170,40 +221,67 @@ class CharacterDataTest extends Specification {
|
||||||
pos.orient mustEqual Vector3(0, 0, 126.5625f)
|
pos.orient mustEqual Vector3(0, 0, 126.5625f)
|
||||||
pos.vel.isDefined mustEqual false
|
pos.vel.isDefined mustEqual false
|
||||||
|
|
||||||
basic.app.name mustEqual "Angello"
|
basic match {
|
||||||
basic.app.faction mustEqual PlanetSideEmpire.VS
|
case CharacterAppearanceData(a, b, ribbons) =>
|
||||||
basic.app.sex mustEqual CharacterGender.Male
|
a.app.name mustEqual "Angello"
|
||||||
basic.app.head mustEqual 10
|
a.app.faction mustEqual PlanetSideEmpire.VS
|
||||||
basic.app.voice mustEqual CharacterVoice.Voice2
|
a.app.sex mustEqual CharacterGender.Male
|
||||||
basic.voice2 mustEqual 0
|
a.app.head mustEqual 10
|
||||||
basic.black_ops mustEqual false
|
a.app.voice mustEqual CharacterVoice.Voice2
|
||||||
basic.jammered mustEqual false
|
a.black_ops mustEqual false
|
||||||
basic.exosuit mustEqual ExoSuitType.MAX
|
a.jammered mustEqual false
|
||||||
basic.outfit_name mustEqual "Original District"
|
a.exosuit mustEqual ExoSuitType.MAX
|
||||||
basic.outfit_logo mustEqual 23
|
a.unk1 mustEqual false
|
||||||
basic.facingPitch mustEqual 0
|
a.unk2 mustEqual None
|
||||||
basic.facingYawUpper mustEqual 180.0f
|
a.unk3 mustEqual None
|
||||||
basic.lfs mustEqual false
|
a.unk4 mustEqual 0
|
||||||
basic.grenade_state mustEqual GrenadeState.None
|
a.unk5 mustEqual 1
|
||||||
basic.is_cloaking mustEqual false
|
a.unk6 mustEqual 0L
|
||||||
basic.charging_pose mustEqual false
|
a.unk7 mustEqual 0
|
||||||
basic.on_zipline mustEqual false
|
a.unk8 mustEqual 0
|
||||||
basic.ribbons.upper mustEqual MeritCommendation.Jacking2
|
a.unk9 mustEqual 0
|
||||||
basic.ribbons.middle mustEqual MeritCommendation.ScavengerVS1
|
a.unkA mustEqual 0
|
||||||
basic.ribbons.lower mustEqual MeritCommendation.AMSSupport4
|
|
||||||
basic.ribbons.tos mustEqual MeritCommendation.SixYearVS
|
b.outfit_name mustEqual "Original District"
|
||||||
|
b.outfit_logo mustEqual 23
|
||||||
|
b.backpack mustEqual true
|
||||||
|
b.facingPitch mustEqual 351.5625f
|
||||||
|
b.facingYawUpper mustEqual 0
|
||||||
|
b.lfs mustEqual false
|
||||||
|
b.grenade_state mustEqual GrenadeState.None
|
||||||
|
b.is_cloaking mustEqual false
|
||||||
|
b.charging_pose mustEqual false
|
||||||
|
b.on_zipline mustEqual None
|
||||||
|
b.unk0 mustEqual 529687L
|
||||||
|
b.unk1 mustEqual false
|
||||||
|
b.unk2 mustEqual false
|
||||||
|
b.unk3 mustEqual false
|
||||||
|
b.unk4 mustEqual false
|
||||||
|
b.unk5 mustEqual false
|
||||||
|
b.unk6 mustEqual false
|
||||||
|
b.unk7 mustEqual false
|
||||||
|
|
||||||
|
ribbons.upper mustEqual MeritCommendation.Jacking2
|
||||||
|
ribbons.middle mustEqual MeritCommendation.ScavengerVS1
|
||||||
|
ribbons.lower mustEqual MeritCommendation.AMSSupport4
|
||||||
|
ribbons.tos mustEqual MeritCommendation.SixYearVS
|
||||||
|
//etc..
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
|
||||||
char.health mustEqual 0
|
char.health mustEqual 0
|
||||||
char.armor mustEqual 0
|
char.armor mustEqual 0
|
||||||
char.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
|
char.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
|
||||||
char.command_rank mustEqual 2
|
char.command_rank mustEqual 2
|
||||||
char.implant_effects.isDefined mustEqual false
|
char.implant_effects.isEmpty mustEqual true
|
||||||
char.cosmetics.isDefined mustEqual true
|
char.cosmetics.isDefined mustEqual true
|
||||||
char.cosmetics.get.no_helmet mustEqual true
|
char.cosmetics.get.no_helmet mustEqual true
|
||||||
char.cosmetics.get.beret mustEqual true
|
char.cosmetics.get.beret mustEqual true
|
||||||
char.cosmetics.get.sunglasses mustEqual true
|
char.cosmetics.get.sunglasses mustEqual true
|
||||||
char.cosmetics.get.earpiece mustEqual true
|
char.cosmetics.get.earpiece mustEqual true
|
||||||
char.cosmetics.get.brimmed_cap mustEqual false
|
char.cosmetics.get.brimmed_cap mustEqual false
|
||||||
|
char.unk mustEqual 1
|
||||||
|
|
||||||
hand mustEqual DrawnSlot.Pistol1
|
hand mustEqual DrawnSlot.Pistol1
|
||||||
case _ =>
|
case _ =>
|
||||||
|
|
@ -220,7 +298,7 @@ class CharacterDataTest extends Specification {
|
||||||
Vector3(0f, 0f, 64.6875f),
|
Vector3(0f, 0f, 64.6875f),
|
||||||
Some(Vector3(1.4375f, -0.4375f, 0f))
|
Some(Vector3(1.4375f, -0.4375f, 0f))
|
||||||
)
|
)
|
||||||
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
|
val a : Int=>CharacterAppearanceA = CharacterAppearanceA(
|
||||||
BasicCharacterData(
|
BasicCharacterData(
|
||||||
"ScrawnyRonnie",
|
"ScrawnyRonnie",
|
||||||
PlanetSideEmpire.TR,
|
PlanetSideEmpire.TR,
|
||||||
|
|
@ -228,17 +306,43 @@ class CharacterDataTest extends Specification {
|
||||||
5,
|
5,
|
||||||
CharacterVoice.Voice5
|
CharacterVoice.Voice5
|
||||||
),
|
),
|
||||||
3,
|
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
ExoSuitType.Reinforced,
|
ExoSuitType.Reinforced,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
30777081L,
|
||||||
|
1,
|
||||||
|
4,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
val b : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
|
||||||
|
316554L,
|
||||||
"Black Beret Armoured Corps",
|
"Black Beret Armoured Corps",
|
||||||
23,
|
23,
|
||||||
false,
|
false,
|
||||||
340.3125f, 0f,
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
320.625f, 0f,
|
||||||
false,
|
false,
|
||||||
GrenadeState.None,
|
GrenadeState.None,
|
||||||
false, false, false,
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
|
||||||
|
a, b,
|
||||||
RibbonBars(
|
RibbonBars(
|
||||||
MeritCommendation.MarkovVeteran,
|
MeritCommendation.MarkovVeteran,
|
||||||
MeritCommendation.HeavyInfantry4,
|
MeritCommendation.HeavyInfantry4,
|
||||||
|
|
@ -249,8 +353,9 @@ class CharacterDataTest extends Specification {
|
||||||
val char : (Boolean,Boolean)=>CharacterData = CharacterData(
|
val char : (Boolean,Boolean)=>CharacterData = CharacterData(
|
||||||
255, 253,
|
255, 253,
|
||||||
UniformStyle.ThirdUpgrade,
|
UniformStyle.ThirdUpgrade,
|
||||||
|
7,
|
||||||
5,
|
5,
|
||||||
Some(ImplantEffects.NoEffects),
|
List(ImplantEffects.NoEffects),
|
||||||
Some(Cosmetics(true, true, true, true, false))
|
Some(Cosmetics(true, true, true, true, false))
|
||||||
)
|
)
|
||||||
val inv = InventoryData(
|
val inv = InventoryData(
|
||||||
|
|
@ -265,18 +370,11 @@ class CharacterDataTest extends Specification {
|
||||||
|
|
||||||
val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3902), obj)
|
val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3902), obj)
|
||||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||||
val pkt_bitv = pkt.toBitVector
|
pkt mustEqual string
|
||||||
val ori_bitv = string.toBitVector
|
|
||||||
pkt_bitv.take(452) mustEqual ori_bitv.take(452) //skip 126
|
|
||||||
pkt_bitv.drop(578).take(438) mustEqual ori_bitv.drop(578).take(438) //skip 2
|
|
||||||
pkt_bitv.drop(1018).take(17) mustEqual ori_bitv.drop(1018).take(17) //skip 11
|
|
||||||
pkt_bitv.drop(1046).take(147) mustEqual ori_bitv.drop(1046).take(147) //skip 3
|
|
||||||
pkt_bitv.drop(1196) mustEqual ori_bitv.drop(1196)
|
|
||||||
//TODO work on CharacterData to make this pass as a single stream
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"encode (seated)" in {
|
"encode (seated)" in {
|
||||||
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
|
val a : Int=>CharacterAppearanceA = CharacterAppearanceA(
|
||||||
BasicCharacterData(
|
BasicCharacterData(
|
||||||
"ScrawnyRonnie",
|
"ScrawnyRonnie",
|
||||||
PlanetSideEmpire.TR,
|
PlanetSideEmpire.TR,
|
||||||
|
|
@ -284,17 +382,43 @@ class CharacterDataTest extends Specification {
|
||||||
5,
|
5,
|
||||||
CharacterVoice.Voice5
|
CharacterVoice.Voice5
|
||||||
),
|
),
|
||||||
3,
|
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
ExoSuitType.Reinforced,
|
ExoSuitType.Reinforced,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
192L,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
val b : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
|
||||||
|
26L,
|
||||||
"Black Beret Armoured Corps",
|
"Black Beret Armoured Corps",
|
||||||
23,
|
23,
|
||||||
false,
|
false,
|
||||||
340.3125f, 0f,
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
320.625f, 0f,
|
||||||
false,
|
false,
|
||||||
GrenadeState.None,
|
GrenadeState.None,
|
||||||
false, false, false,
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
|
||||||
|
a, b,
|
||||||
RibbonBars(
|
RibbonBars(
|
||||||
MeritCommendation.MarkovVeteran,
|
MeritCommendation.MarkovVeteran,
|
||||||
MeritCommendation.HeavyInfantry4,
|
MeritCommendation.HeavyInfantry4,
|
||||||
|
|
@ -306,7 +430,7 @@ class CharacterDataTest extends Specification {
|
||||||
255, 253,
|
255, 253,
|
||||||
UniformStyle.ThirdUpgrade,
|
UniformStyle.ThirdUpgrade,
|
||||||
5,
|
5,
|
||||||
Some(ImplantEffects.NoEffects),
|
List(ImplantEffects.NoEffects),
|
||||||
Some(Cosmetics(true, true, true, true, false))
|
Some(Cosmetics(true, true, true, true, false))
|
||||||
)
|
)
|
||||||
val inv = InventoryData(
|
val inv = InventoryData(
|
||||||
|
|
@ -329,7 +453,7 @@ class CharacterDataTest extends Specification {
|
||||||
Vector3(4629.8906f, 6316.4453f, 54.734375f),
|
Vector3(4629.8906f, 6316.4453f, 54.734375f),
|
||||||
Vector3(0, 0, 126.5625f)
|
Vector3(0, 0, 126.5625f)
|
||||||
)
|
)
|
||||||
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
|
val a : Int=>CharacterAppearanceA = CharacterAppearanceA(
|
||||||
BasicCharacterData(
|
BasicCharacterData(
|
||||||
"Angello",
|
"Angello",
|
||||||
PlanetSideEmpire.VS,
|
PlanetSideEmpire.VS,
|
||||||
|
|
@ -337,17 +461,43 @@ class CharacterDataTest extends Specification {
|
||||||
10,
|
10,
|
||||||
CharacterVoice.Voice2
|
CharacterVoice.Voice2
|
||||||
),
|
),
|
||||||
0,
|
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
ExoSuitType.MAX,
|
ExoSuitType.MAX,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0L,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
val b : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
|
||||||
|
529687L,
|
||||||
"Original District",
|
"Original District",
|
||||||
23,
|
23,
|
||||||
|
false, //unk1
|
||||||
true, //backpack
|
true, //backpack
|
||||||
0f, 180.0f,
|
false, //unk2
|
||||||
false,
|
false, //unk3
|
||||||
|
false, //unk4
|
||||||
|
351.5625f, 0f,
|
||||||
|
false, //lfs
|
||||||
GrenadeState.None,
|
GrenadeState.None,
|
||||||
false, false, false,
|
false, //is_cloaking
|
||||||
|
false, //unk5
|
||||||
|
false, //unk6
|
||||||
|
false, //charging_pose
|
||||||
|
false, //unk7
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
|
||||||
|
a, b,
|
||||||
RibbonBars(
|
RibbonBars(
|
||||||
MeritCommendation.Jacking2,
|
MeritCommendation.Jacking2,
|
||||||
MeritCommendation.ScavengerVS1,
|
MeritCommendation.ScavengerVS1,
|
||||||
|
|
@ -358,22 +508,19 @@ class CharacterDataTest extends Specification {
|
||||||
val char : (Boolean,Boolean)=>CharacterData = CharacterData(
|
val char : (Boolean,Boolean)=>CharacterData = CharacterData(
|
||||||
0, 0,
|
0, 0,
|
||||||
UniformStyle.ThirdUpgrade,
|
UniformStyle.ThirdUpgrade,
|
||||||
2,
|
1,
|
||||||
None,
|
List(),
|
||||||
Some(Cosmetics(true, true, true, true, false))
|
Some(Cosmetics(true, true, true, true, false))
|
||||||
)
|
)
|
||||||
val obj = PlayerData(pos, app, char, DrawnSlot.Pistol1)
|
val obj = PlayerData(pos, app, char, DrawnSlot.Pistol1)
|
||||||
|
|
||||||
val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3380), obj)
|
val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3380), obj)
|
||||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||||
|
//granular test
|
||||||
val pkt_bitv = pkt.toBitVector
|
val pkt_bitv = pkt.toBitVector
|
||||||
val ori_bitv = string_backpack.toBitVector
|
val ori_bitv = string_backpack.toBitVector
|
||||||
pkt_bitv.take(300) mustEqual ori_bitv.take(300) //skip 2
|
pkt_bitv.take(916) mustEqual pkt_bitv.take(916) //skip 4
|
||||||
pkt_bitv.drop(302).take(14) mustEqual ori_bitv.drop(302).take(14) //skip 126
|
pkt_bitv.drop(920) mustEqual pkt_bitv.drop(920)
|
||||||
pkt_bitv.drop(442).take(305) mustEqual ori_bitv.drop(442).take(305) //skip 1
|
|
||||||
pkt_bitv.drop(748).take(9) mustEqual ori_bitv.drop(748).take(9) // skip 2
|
|
||||||
pkt_bitv.drop(759).take(157) mustEqual ori_bitv.drop(759).take(157) //skip 1
|
|
||||||
pkt_bitv.drop(917) mustEqual ori_bitv.drop(917)
|
|
||||||
//TODO work on CharacterData to make this pass as a single stream
|
//TODO work on CharacterData to make this pass as a single stream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -50,34 +50,60 @@ class MountedVehiclesTest extends Specification {
|
||||||
list.head.guid mustEqual PlanetSideGUID(3776)
|
list.head.guid mustEqual PlanetSideGUID(3776)
|
||||||
list.head.parentSlot mustEqual 0
|
list.head.parentSlot mustEqual 0
|
||||||
list.head.obj match {
|
list.head.obj match {
|
||||||
case PlayerData(pos, app, char, Some(InventoryData(inv)), hand) =>
|
case PlayerData(None, app, char, Some(InventoryData(inv)), DrawnSlot.None) =>
|
||||||
pos mustEqual None
|
app match {
|
||||||
app.app.name mustEqual "ScrawnyRonnie"
|
case CharacterAppearanceData(a, b, ribbons) =>
|
||||||
app.app.faction mustEqual PlanetSideEmpire.TR
|
a.app mustEqual BasicCharacterData("ScrawnyRonnie", PlanetSideEmpire.TR, CharacterGender.Male, 5, CharacterVoice.Voice5)
|
||||||
app.app.sex mustEqual CharacterGender.Male
|
a.black_ops mustEqual false
|
||||||
app.app.head mustEqual 5
|
a.jammered mustEqual false
|
||||||
app.app.voice mustEqual CharacterVoice.Voice5
|
a.exosuit mustEqual ExoSuitType.Agile
|
||||||
app.voice2 mustEqual 3
|
a.unk1 mustEqual false
|
||||||
app.black_ops mustEqual false
|
a.unk2 mustEqual None
|
||||||
app.lfs mustEqual false
|
a.unk3 mustEqual None
|
||||||
app.outfit_name mustEqual "Black Beret Armoured Corps"
|
a.unk4 mustEqual 0
|
||||||
app.outfit_logo mustEqual 23
|
a.unk5 mustEqual 0
|
||||||
app.facingPitch mustEqual 354.375f
|
a.unk6 mustEqual 30777081L
|
||||||
app.facingYawUpper mustEqual 0.0f
|
a.unk7 mustEqual 1
|
||||||
app.altModelBit mustEqual None
|
a.unk8 mustEqual 4
|
||||||
app.charging_pose mustEqual false
|
a.unk9 mustEqual 0
|
||||||
app.on_zipline mustEqual false
|
a.unkA mustEqual 0
|
||||||
app.backpack mustEqual false
|
|
||||||
app.ribbons.upper mustEqual MeritCommendation.MarkovVeteran
|
b.outfit_name mustEqual "Black Beret Armoured Corps"
|
||||||
app.ribbons.middle mustEqual MeritCommendation.HeavyInfantry4
|
b.outfit_logo mustEqual 23
|
||||||
app.ribbons.lower mustEqual MeritCommendation.TankBuster7
|
b.backpack mustEqual false
|
||||||
app.ribbons.tos mustEqual MeritCommendation.SixYearTR
|
b.facingPitch mustEqual 348.75f
|
||||||
char.health mustEqual 100
|
b.facingYawUpper mustEqual 0
|
||||||
char.armor mustEqual 0
|
b.lfs mustEqual false
|
||||||
char.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
|
b.grenade_state mustEqual GrenadeState.None
|
||||||
char.command_rank mustEqual 5
|
b.is_cloaking mustEqual false
|
||||||
char.implant_effects mustEqual None
|
b.charging_pose mustEqual false
|
||||||
char.cosmetics mustEqual Some(Cosmetics(true, true, true, true, false))
|
b.on_zipline mustEqual None
|
||||||
|
b.unk0 mustEqual 316554L
|
||||||
|
b.unk1 mustEqual false
|
||||||
|
b.unk2 mustEqual false
|
||||||
|
b.unk3 mustEqual false
|
||||||
|
b.unk4 mustEqual false
|
||||||
|
b.unk5 mustEqual false
|
||||||
|
b.unk6 mustEqual false
|
||||||
|
b.unk7 mustEqual false
|
||||||
|
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
|
||||||
|
char match {
|
||||||
|
case CharacterData(health, armor, uniform, unk, cr, implants, cosmetics) =>
|
||||||
|
health mustEqual 100
|
||||||
|
armor mustEqual 0
|
||||||
|
uniform mustEqual UniformStyle.ThirdUpgrade
|
||||||
|
unk mustEqual 7
|
||||||
|
cr mustEqual 5
|
||||||
|
implants mustEqual Nil
|
||||||
|
cosmetics mustEqual Some(Cosmetics(true, true, true, true, false))
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
//briefly ...
|
||||||
inv.size mustEqual 4
|
inv.size mustEqual 4
|
||||||
inv.head.objectClass mustEqual ObjectClass.medicalapplicator
|
inv.head.objectClass mustEqual ObjectClass.medicalapplicator
|
||||||
inv.head.parentSlot mustEqual 0
|
inv.head.parentSlot mustEqual 0
|
||||||
|
|
@ -87,10 +113,10 @@ class MountedVehiclesTest extends Specification {
|
||||||
inv(2).parentSlot mustEqual 2
|
inv(2).parentSlot mustEqual 2
|
||||||
inv(3).objectClass mustEqual ObjectClass.chainblade
|
inv(3).objectClass mustEqual ObjectClass.chainblade
|
||||||
inv(3).parentSlot mustEqual 4
|
inv(3).parentSlot mustEqual 4
|
||||||
hand mustEqual DrawnSlot.None
|
|
||||||
case _ =>
|
case _ =>
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
|
//back to mosquito inventory
|
||||||
list(1).objectClass mustEqual ObjectClass.rotarychaingun_mosquito
|
list(1).objectClass mustEqual ObjectClass.rotarychaingun_mosquito
|
||||||
list(1).parentSlot mustEqual 1
|
list(1).parentSlot mustEqual 1
|
||||||
case None =>
|
case None =>
|
||||||
|
|
@ -105,17 +131,51 @@ class MountedVehiclesTest extends Specification {
|
||||||
}
|
}
|
||||||
|
|
||||||
"encode (Scrawny Ronnie's mosquito)" in {
|
"encode (Scrawny Ronnie's mosquito)" in {
|
||||||
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
|
val a : Int=>CharacterAppearanceA = CharacterAppearanceA(
|
||||||
BasicCharacterData("ScrawnyRonnie", PlanetSideEmpire.TR, CharacterGender.Male, 5, CharacterVoice.Voice5),
|
BasicCharacterData(
|
||||||
3,
|
"ScrawnyRonnie",
|
||||||
false, false,
|
PlanetSideEmpire.TR,
|
||||||
|
CharacterGender.Male,
|
||||||
|
5,
|
||||||
|
CharacterVoice.Voice5
|
||||||
|
),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
ExoSuitType.Agile,
|
ExoSuitType.Agile,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
30777081L,
|
||||||
|
1,
|
||||||
|
4,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
val b : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
|
||||||
|
316554L,
|
||||||
"Black Beret Armoured Corps",
|
"Black Beret Armoured Corps",
|
||||||
23,
|
23,
|
||||||
false,
|
false,
|
||||||
354.375f, 0.0f,
|
|
||||||
false,
|
false,
|
||||||
GrenadeState.None, false, false, false,
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
348.75f, 0,
|
||||||
|
false,
|
||||||
|
GrenadeState.None,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
|
||||||
|
a, b,
|
||||||
RibbonBars(
|
RibbonBars(
|
||||||
MeritCommendation.MarkovVeteran,
|
MeritCommendation.MarkovVeteran,
|
||||||
MeritCommendation.HeavyInfantry4,
|
MeritCommendation.HeavyInfantry4,
|
||||||
|
|
@ -126,9 +186,9 @@ class MountedVehiclesTest extends Specification {
|
||||||
val char : (Boolean,Boolean)=>CharacterData = CharacterData(
|
val char : (Boolean,Boolean)=>CharacterData = CharacterData(
|
||||||
100, 0,
|
100, 0,
|
||||||
UniformStyle.ThirdUpgrade,
|
UniformStyle.ThirdUpgrade,
|
||||||
0,
|
7,
|
||||||
5,
|
5,
|
||||||
None,
|
Nil,
|
||||||
Some(Cosmetics(true, true, true, true, false))
|
Some(Cosmetics(true, true, true, true, false))
|
||||||
)
|
)
|
||||||
val inv : InventoryData = InventoryData(
|
val inv : InventoryData = InventoryData(
|
||||||
|
|
@ -178,15 +238,7 @@ class MountedVehiclesTest extends Specification {
|
||||||
)(VehicleFormat.Variant)
|
)(VehicleFormat.Variant)
|
||||||
val msg = ObjectCreateMessage(ObjectClass.mosquito, PlanetSideGUID(4308), obj)
|
val msg = ObjectCreateMessage(ObjectClass.mosquito, PlanetSideGUID(4308), obj)
|
||||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||||
|
pkt mustEqual string_mosquito_seated
|
||||||
val pkt_bitv = pkt.toBitVector
|
|
||||||
val ori_bitv = string_mosquito_seated.toBitVector
|
|
||||||
pkt_bitv.take(555) mustEqual ori_bitv.take(555) //skip 126
|
|
||||||
pkt_bitv.drop(681).take(512) mustEqual ori_bitv.drop(681).take(512) //renew
|
|
||||||
pkt_bitv.drop(1193).take(88) mustEqual ori_bitv.drop(1193).take(88) //skip 3
|
|
||||||
pkt_bitv.drop(1284).take(512) mustEqual ori_bitv.drop(1284).take(512) //renew
|
|
||||||
pkt_bitv.drop(1796) mustEqual ori_bitv.drop(1796)
|
|
||||||
//TODO work on CharacterData to make this pass as a single stream
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -456,7 +456,6 @@ class PacketCodingActorITest extends ActorTest {
|
||||||
val pos : PlacementData = PlacementData(Vector3.Zero, Vector3.Zero)
|
val pos : PlacementData = PlacementData(Vector3.Zero, Vector3.Zero)
|
||||||
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
|
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
|
||||||
BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1),
|
BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1),
|
||||||
3,
|
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
ExoSuitType.Standard,
|
ExoSuitType.Standard,
|
||||||
|
|
@ -468,7 +467,7 @@ class PacketCodingActorITest extends ActorTest {
|
||||||
GrenadeState.None,
|
GrenadeState.None,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
None,
|
||||||
RibbonBars()
|
RibbonBars()
|
||||||
)
|
)
|
||||||
var char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData(
|
var char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData(
|
||||||
|
|
@ -476,7 +475,6 @@ class PacketCodingActorITest extends ActorTest {
|
||||||
0,
|
0,
|
||||||
100, 100,
|
100, 100,
|
||||||
50,
|
50,
|
||||||
1, 7, 7,
|
|
||||||
100, 100,
|
100, 100,
|
||||||
List(CertificationType.StandardAssault, CertificationType.MediumAssault, CertificationType.ATV, CertificationType.Harasser, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit, CertificationType.ReinforcedExoSuit),
|
List(CertificationType.StandardAssault, CertificationType.MediumAssault, CertificationType.ATV, CertificationType.Harasser, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit, CertificationType.ReinforcedExoSuit),
|
||||||
List(),
|
List(),
|
||||||
|
|
@ -486,7 +484,7 @@ class PacketCodingActorITest extends ActorTest {
|
||||||
)
|
)
|
||||||
val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None)
|
val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None)
|
||||||
val pkt = MultiPacketBundle(List(ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj)))
|
val pkt = MultiPacketBundle(List(ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj)))
|
||||||
val string_hex = hex"000900001879060000bc84b000000000000000000002040000097049006c006c006c004900490049006c006c006c0049006c0049006c006c0049006c006c006c0049006c006c0049008452700000000000000000000000000000002000000fe6a703fffffffffffffffffffffffffffffffc00000000000000000000000000000000000000019001900064000001007ec800c80000000000000000000000000000000000000001c00042c54686c7000000000000000000000000000000000000000000000000000000000000000000000000200700"
|
val string_hex = hex"000900001879060000bc84b000000000000000000002040000097049006c006c006c004900490049006c006c006c0049006c0049006c006c0049006c006c006c0049006c006c00490084524000000000000000000000000000000020000007f35703fffffffffffffffffffffffffffffffc000000000000000000000000000000000000000190019000640000000000c800c80000000000000000000000000000000000000001c00042c54686c7000000000000000000000000000000000000000000000000000000000000000000000400e0"
|
||||||
|
|
||||||
"PacketCodingActor" should {
|
"PacketCodingActor" should {
|
||||||
"bundle an r-originating packet into an l-facing SlottedMetaPacket byte stream data (SlottedMetaPacket)" in {
|
"bundle an r-originating packet into an l-facing SlottedMetaPacket byte stream data (SlottedMetaPacket)" in {
|
||||||
|
|
@ -547,36 +545,92 @@ class PacketCodingActorJTest extends ActorTest {
|
||||||
class PacketCodingActorKTest extends ActorTest {
|
class PacketCodingActorKTest extends ActorTest {
|
||||||
import net.psforever.packet.game.objectcreate._
|
import net.psforever.packet.game.objectcreate._
|
||||||
val pos : PlacementData = PlacementData(Vector3.Zero, Vector3.Zero)
|
val pos : PlacementData = PlacementData(Vector3.Zero, Vector3.Zero)
|
||||||
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
|
val aa : Int=>CharacterAppearanceA = CharacterAppearanceA(
|
||||||
BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1),
|
BasicCharacterData(
|
||||||
3,
|
"IlllIIIlllIlIllIlllIllI",
|
||||||
|
PlanetSideEmpire.VS,
|
||||||
|
CharacterGender.Female,
|
||||||
|
41,
|
||||||
|
CharacterVoice.Voice1
|
||||||
|
),
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
ExoSuitType.Standard,
|
ExoSuitType.Standard,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
41605313L,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
65535
|
||||||
|
)
|
||||||
|
val ab : (Boolean,Int)=>CharacterAppearanceB = CharacterAppearanceB(
|
||||||
|
0L,
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
2.8125f, 210.9375f,
|
2.8125f, 210.9375f,
|
||||||
true,
|
false,
|
||||||
GrenadeState.None,
|
GrenadeState.None,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
RibbonBars()
|
false,
|
||||||
)
|
false,
|
||||||
var char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
100, 100,
|
|
||||||
50,
|
|
||||||
1, 7, 7,
|
|
||||||
100, 100,
|
|
||||||
List(CertificationType.StandardAssault, CertificationType.MediumAssault, CertificationType.ATV, CertificationType.Harasser, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit, CertificationType.ReinforcedExoSuit),
|
|
||||||
List(),
|
|
||||||
List("xpe_sanctuary_help", "xpe_th_firemodes", "used_beamer", "map13"),
|
|
||||||
List.empty,
|
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
|
||||||
|
aa, ab,
|
||||||
|
RibbonBars()
|
||||||
|
)
|
||||||
|
val ba : DetailedCharacterA = DetailedCharacterA(
|
||||||
|
0L,
|
||||||
|
0L,
|
||||||
|
0L, 0L, 0L,
|
||||||
|
100, 100,
|
||||||
|
false,
|
||||||
|
50,
|
||||||
|
32831L,
|
||||||
|
100, 100,
|
||||||
|
0, 0, 0L,
|
||||||
|
List(0, 0, 0, 0, 0, 0),
|
||||||
|
List(
|
||||||
|
CertificationType.StandardAssault,
|
||||||
|
CertificationType.MediumAssault,
|
||||||
|
CertificationType.ATV,
|
||||||
|
CertificationType.Harasser,
|
||||||
|
CertificationType.StandardExoSuit,
|
||||||
|
CertificationType.AgileExoSuit,
|
||||||
|
CertificationType.ReinforcedExoSuit
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val bb : (Long, Option[Int])=>DetailedCharacterB = DetailedCharacterB(
|
||||||
|
None,
|
||||||
|
Nil,
|
||||||
|
Nil, Nil,
|
||||||
|
List(
|
||||||
|
"xpe_sanctuary_help",
|
||||||
|
"xpe_th_firemodes",
|
||||||
|
"used_beamer",
|
||||||
|
"map13"
|
||||||
|
),
|
||||||
|
Nil,
|
||||||
|
0L, 0L, 0L, 0L, 0L,
|
||||||
|
Some(DCDExtra2(0, 0)),
|
||||||
|
Nil, Nil, false,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
val char : (Option[Int])=>DetailedCharacterData =
|
||||||
|
(pad_length : Option[Int]) => DetailedCharacterData(ba, bb(ba.bep, pad_length))(pad_length)
|
||||||
val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None)
|
val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None)
|
||||||
val list = List(
|
val list = List(
|
||||||
ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj),
|
ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue