conversion of variable field SquadDetailDefinitionUpdateMessage mostly complete, handling more than 99% of cases in our captures; modifications to existing test-important SSDUM packet in WSA

This commit is contained in:
FateJH 2019-07-01 18:43:57 -04:00
parent fd9a3a0216
commit d9619e4185
3 changed files with 797 additions and 288 deletions

View file

@ -1,7 +1,6 @@
// Copyright (c) 2019 PSForever
package net.psforever.packet.game
import net.psforever.packet.game.SquadDetailDefinitionUpdateMessage.defaultRequirements
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.CertificationType
import scodec.bits.BitVector
@ -18,7 +17,7 @@ final case class SquadPositionDetail(is_closed : Boolean,
char_id : Long,
name : String)
final case class SquadDetailDefinitionUpdateMessage(guid : PlanetSideGUID,
final case class OldSquadDetailDefinitionUpdateMessage(guid : PlanetSideGUID,
unk1 : Int,
leader_char_id : Long,
unk2 : BitVector,
@ -26,11 +25,6 @@ final case class SquadDetailDefinitionUpdateMessage(guid : PlanetSideGUID,
task : String,
zone_id : PlanetSideZoneID,
member_info : List[SquadPositionDetail])
extends PlanetSideGamePacket {
type Packet = SquadDetailDefinitionUpdateMessage
def opcode = GamePacketOpcode.SquadDetailDefinitionUpdateMessage
def encode = SquadDetailDefinitionUpdateMessage.encode(this)
}
object SquadPositionDetail {
final val Closed : SquadPositionDetail = SquadPositionDetail(is_closed = true, "", "", Set.empty, 0L, "")
@ -48,169 +42,10 @@ object SquadPositionDetail {
def apply(role : String, detailed_orders : String, requirements : Set[CertificationType.Value], char_id : Long, name : String) : SquadPositionDetail = SquadPositionDetail(is_closed = false, role, detailed_orders, requirements, char_id, name)
}
object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefinitionUpdateMessage] {
final val defaultRequirements : Set[CertificationType.Value] = Set(
CertificationType.StandardAssault,
CertificationType.StandardExoSuit,
CertificationType.AgileExoSuit
)
final val Init = SquadDetailDefinitionUpdateMessage(
PlanetSideGUID(0),
0L,
"",
"",
PlanetSideZoneID(0),
List(
SquadPositionDetail(),
SquadPositionDetail(),
SquadPositionDetail(),
SquadPositionDetail(),
SquadPositionDetail(),
SquadPositionDetail(),
SquadPositionDetail(),
SquadPositionDetail(),
SquadPositionDetail(),
SquadPositionDetail()
)
)
def apply(guid : PlanetSideGUID, char_id : Long, leader_name : String, task : String, zone_id : PlanetSideZoneID, member_info : List[SquadPositionDetail]) : SquadDetailDefinitionUpdateMessage = {
object OldSquadDetailDefinitionUpdateMessage {
def apply(guid : PlanetSideGUID, char_id : Long, leader_name : String, task : String, zone_id : PlanetSideZoneID, member_info : List[SquadPositionDetail]) : OldSquadDetailDefinitionUpdateMessage = {
import scodec.bits._
SquadDetailDefinitionUpdateMessage(guid, 1, char_id, hex"000000".toBitVector, leader_name, task, zone_id, member_info)
}
private def memberCodec(pad : Int) : Codec[SquadPositionDetail] = {
import shapeless.::
(
uint8 :: //required value = 6
("is_closed" | bool) :: //if all positions are closed, the squad detail menu display no positions at all
("role" | PacketHelpers.encodedWideStringAligned(pad)) ::
("detailed_orders" | PacketHelpers.encodedWideString) ::
("char_id" | uint32L) ::
("name" | PacketHelpers.encodedWideString) ::
("requirements" | ulongL(46))
).exmap[SquadPositionDetail] (
{
case 6 :: closed :: role :: orders :: char_id :: name :: requirements :: HNil =>
Attempt.Successful(
SquadPositionDetail(closed, role, orders, defaultRequirements ++ CertificationType.fromEncodedLong(requirements), char_id, name)
)
case data =>
Attempt.Failure(Err(s"can not decode a SquadDetailDefinitionUpdate member's data - $data"))
},
{
case SquadPositionDetail(closed, role, orders, requirements, char_id, name) =>
Attempt.Successful(6 :: closed :: role :: orders :: char_id :: name :: CertificationType.toEncodedLong(defaultRequirements ++ requirements) :: HNil)
}
)
}
private val first_member_codec : Codec[SquadPositionDetail] = memberCodec(pad = 7)
private val member_codec : Codec[SquadPositionDetail] = memberCodec(pad = 0)
private case class LinkedMemberList(member : SquadPositionDetail, next : Option[LinkedMemberList])
private def subsequent_member_codec : Codec[LinkedMemberList] = {
import shapeless.::
(
//disruptive coupling action (e.g., flatPrepend) necessary to allow for recursive Codec
("member" | member_codec) >>:~ { _ =>
optional(bool, "next" | subsequent_member_codec).hlist
}
).xmap[LinkedMemberList] (
{
case a :: b :: HNil =>
LinkedMemberList(a, b)
},
{
case LinkedMemberList(a, b) =>
a :: b :: HNil
}
)
}
private def initial_member_codec : Codec[LinkedMemberList] = {
import shapeless.::
(
("member" | first_member_codec) ::
optional(bool, "next" | subsequent_member_codec)
).xmap[LinkedMemberList] (
{
case a :: b :: HNil =>
LinkedMemberList(a, b)
},
{
case LinkedMemberList(a, b) =>
a :: b :: HNil
}
)
}
@tailrec
private def unlinkMemberList(list : LinkedMemberList, out : List[SquadPositionDetail] = Nil) : List[SquadPositionDetail] = {
list.next match {
case None =>
out :+ list.member
case Some(next) =>
unlinkMemberList(next, out :+ list.member)
}
}
private def linkMemberList(list : List[SquadPositionDetail]) : LinkedMemberList = {
list match {
case Nil =>
throw new Exception("")
case x :: Nil =>
LinkedMemberList(x, None)
case x :: xs =>
linkMemberList(xs, LinkedMemberList(x, None))
}
}
@tailrec
private def linkMemberList(list : List[SquadPositionDetail], out : LinkedMemberList) : LinkedMemberList = {
list match {
case Nil =>
out
case x :: Nil =>
LinkedMemberList(x, Some(out))
case x :: xs =>
linkMemberList(xs, LinkedMemberList(x, Some(out)))
}
}
implicit val codec : Codec[SquadDetailDefinitionUpdateMessage] = {
import shapeless.::
(
("guid" | PlanetSideGUID.codec) ::
uint4 :: //constant = 8
uint4 :: //variable = 0-4
bool :: //true, when 4
uint4 :: //variable = 0-12?
("unk1" | uint4) ::
uint24 :: //unknown, but can be 0'd
("leader_char_id" | uint32L) ::
("unk2" | bits(22)) :: //variable fields, but can be 0'd
uint(10) :: //constant = 0
("leader" | PacketHelpers.encodedWideStringAligned(7)) ::
("task" | PacketHelpers.encodedWideString) ::
("zone_id" | PlanetSideZoneID.codec) ::
uint(23) :: //constant = 4983296
optional(bool, "member_info" | initial_member_codec)
).exmap[SquadDetailDefinitionUpdateMessage] (
{
case guid :: _ :: _ :: _ :: _ :: u1 :: _ :: char_id:: u2 :: _ :: leader :: task :: zone :: _ :: Some(member_list) :: HNil =>
Attempt.Successful(SquadDetailDefinitionUpdateMessage(guid, u1, char_id, u2, leader, task, zone, unlinkMemberList(member_list)))
case data =>
Attempt.failure(Err(s"can not get squad detail definition from data $data"))
},
{
case SquadDetailDefinitionUpdateMessage(guid, unk1, char_id, unk2, leader, task, zone, member_list) =>
Attempt.Successful(guid :: 8 :: 4 :: true :: 0 :: math.max(unk1, 1) :: 0 :: char_id :: unk2.take(22) :: 0 :: leader :: task :: zone :: 4983296 :: Some(linkMemberList(member_list.reverse)) :: HNil)
}
)
OldSquadDetailDefinitionUpdateMessage(guid, 1, char_id, hex"000000".toBitVector, leader_name, task, zone_id, member_info)
}
}
@ -241,10 +76,10 @@ final case class SquadPositionDetail2(is_closed : Option[Boolean],
def And(info : SquadPositionDetail2) : SquadPositionDetail2 = {
SquadPositionDetail2(
is_closed match {
case Some(true) | None =>
case Some(false) | None =>
info.is_closed.orElse(is_closed)
case _ =>
Some(false)
Some(true)
},
role.orElse(info.role),
detailed_orders.orElse(info.detailed_orders),
@ -258,20 +93,24 @@ final case class SquadPositionDetail2(is_closed : Option[Boolean],
final case class SquadPositionEntry(index : Int, info : Option[SquadPositionDetail2])
final case class SquadDetail(unk1 : Option[Int],
unk2 : Option[Int],
leader_char_id : Option[Long],
unk2 : Option[BitVector],
unk3 : Option[Long],
leader_name : Option[String],
task : Option[String],
zone_id : Option[PlanetSideZoneID],
unk7 : Option[Int],
member_info : Option[List[SquadPositionEntry]]) {
def And(info : SquadDetail) : SquadDetail = {
SquadDetail(
unk1.orElse(info.unk1),
leader_char_id.orElse(info.leader_char_id),
unk2.orElse(info.unk2),
leader_char_id.orElse(info.leader_char_id),
unk3.orElse(info.unk3),
leader_name.orElse(info.leader_name),
task.orElse(info.task),
zone_id.orElse(info.zone_id),
unk7.orElse(info.unk7),
(member_info, info.member_info) match {
case (Some(info1), Some(info2)) => Some(info1 ++ info2)
case (Some(info1), _) => Some(info1)
@ -282,33 +121,57 @@ final case class SquadDetail(unk1 : Option[Int],
}
}
final case class SquadDetailDefinitionUpdateMessage2(guid : PlanetSideGUID,
detail : SquadDetail)
final case class SquadDetailDefinitionUpdateMessage(guid : PlanetSideGUID,
detail : SquadDetail)
extends PlanetSideGamePacket {
type Packet = SquadDetailDefinitionUpdateMessage
def opcode = GamePacketOpcode.SquadDetailDefinitionUpdateMessage
def encode = SquadDetailDefinitionUpdateMessage.encode(this)
}
object SquadPositionDetail2 {
final val Blank : SquadPositionDetail2 = SquadPositionDetail2()
final val Closed : SquadPositionDetail2 = SquadPositionDetail2(is_closed = Some(true), None, None, None, None, None)
final val Open : SquadPositionDetail2 = SquadPositionDetail2(is_closed = Some(false), None, None, None, None, None)
def apply() : SquadPositionDetail2 = SquadPositionDetail2(is_closed = Some(false), None, None, None, None, None)
def apply() : SquadPositionDetail2 = SquadPositionDetail2(None, None, None, None, None, None)
def apply(role : String, detailed_orders : Option[String]) : SquadPositionDetail2 = SquadPositionDetail2(is_closed = Some(false), Some(role), detailed_orders, None, None, None)
def apply(role : String, detailed_orders : Option[String]) : SquadPositionDetail2 = SquadPositionDetail2(None, Some(role), detailed_orders, None, None, None)
def apply(role : Option[String], detailed_orders : String) : SquadPositionDetail2 = SquadPositionDetail2(is_closed = Some(false), role, Some(detailed_orders), None, None, None)
def apply(role : Option[String], detailed_orders : String) : SquadPositionDetail2 = SquadPositionDetail2(None, role, Some(detailed_orders), None, None, None)
def apply(char_id : Long) : SquadPositionDetail2 = SquadPositionDetail2(is_closed = Some(false), None, None, None, Some(char_id), None)
def apply(role : String, detailed_orders : String) : SquadPositionDetail2 = SquadPositionDetail2(None, Some(role), Some(detailed_orders), None, None, None)
def apply(name : String) : SquadPositionDetail2 = SquadPositionDetail2(is_closed = Some(false), None, None, None, None, Some(name))
def apply(requirements : Set[CertificationType.Value]) : SquadPositionDetail2 = SquadPositionDetail2(None, None, None, Some(requirements), None, None)
def apply(requirements : Set[CertificationType.Value]) : SquadPositionDetail2 = SquadPositionDetail2(is_closed = Some(false), None, None, Some(requirements), None, None)
def apply(role : String, detailed_orders : String, requirements : Set[CertificationType.Value]) : SquadPositionDetail2 = SquadPositionDetail2(None, Some(role), Some(detailed_orders), Some(requirements), None, None)
def apply(char_id : Long) : SquadPositionDetail2 = SquadPositionDetail2(None, None, None, None, Some(char_id), None)
def apply(name : String) : SquadPositionDetail2 = SquadPositionDetail2(None, None, None, None, None, Some(name))
def apply(char_id : Long, name : String) : SquadPositionDetail2 = SquadPositionDetail2(None, None, None, None, Some(char_id), Some(name))
def apply(role : String, detailed_orders : String, requirements : Set[CertificationType.Value], char_id : Long, name : String) : SquadPositionDetail2 = SquadPositionDetail2(is_closed = Some(false), Some(role), Some(detailed_orders), Some(requirements), Some(char_id), Some(name))
}
object SquadPositionEntry {
import SquadDetailDefinitionUpdateMessage2.paddedStringMetaCodec
import SquadDetailDefinitionUpdateMessage.paddedStringMetaCodec
def apply(index : Int, detail : SquadPositionDetail2) : SquadPositionEntry = SquadPositionEntry(index, Some(detail))
private val isClosedCodec : Codec[SquadPositionDetail2] = bool.exmap[SquadPositionDetail2] (
state => Attempt.successful(SquadPositionDetail2(Some(state), None, None, None, None, None)),
{
case SquadPositionDetail2(Some(state), _, _, _, _, _) =>
Attempt.successful(state)
case _ =>
Attempt.failure(Err("failed to encode squad position data for availability"))
}
)
private def roleCodec(bitsOverByte : StreamLengthToken) : Codec[SquadPositionDetail2] = paddedStringMetaCodec(bitsOverByte.Length).exmap[SquadPositionDetail2] (
role => Attempt.successful(SquadPositionDetail2(role)),
role => Attempt.successful(SquadPositionDetail2(role, None)),
{
case SquadPositionDetail2(_, Some(role), _, _, _, _) =>
Attempt.successful(role)
@ -364,15 +227,13 @@ object SquadPositionEntry {
* its determining conditional statement is explicitly `false`
* and all cases involving explicit failure.
*/
private val failureCodec : Codec[SquadPositionDetail2] = conditional(included = false, bool).exmap[SquadPositionDetail2] (
_ => Attempt.failure(Err("decoding with unhandled codec")),
_ => Attempt.failure(Err("encoding with unhandled codec"))
private def failureCodec(code : Int) : Codec[SquadPositionDetail2] = conditional(included = false, bool).exmap[SquadPositionDetail2] (
_ => Attempt.failure(Err(s"decoding with unhandled codec - $code")),
_ => Attempt.failure(Err(s"encoding with unhandled codec - $code"))
)
private final case class LinkedSquadPositionInfo(code : Int, info : SquadPositionDetail2, next : Option[LinkedSquadPositionInfo])
private def unlinkSquadPositionInfo(info : LinkedSquadPositionInfo) : SquadPositionDetail2 = unlinkSquadPositionInfo(Some(info))
/**
* Concatenate a `SquadInfo` object chain into a single `SquadInfo` object.
* Recursively visits every link in a `SquadInfo` object chain.
@ -443,8 +304,10 @@ object SquadPositionEntry {
import shapeless.::
(
uint4 >>:~ { code =>
selectCodecAction(code, bitsOverByte.Add(4)) ::
conditional(size - 1 > 0, listing_codec(size - 1, modifyCodecPadValue(code, bitsOverByte)))
selectCodecAction(code, bitsOverByte.Add(4)) >>:~ { _ =>
modifyCodecPadValue(code, bitsOverByte)
conditional(size - 1 > 0, listing_codec(size - 1, bitsOverByte)).hlist
}
}
).xmap[LinkedSquadPositionInfo] (
{
@ -460,17 +323,19 @@ object SquadPositionEntry {
private def selectCodecAction(code : Int, bitsOverByte : StreamLengthToken) : Codec[SquadPositionDetail2] = {
code match {
case 0 => isClosedCodec
case 1 => roleCodec(bitsOverByte)
case 2 => ordersCodec(bitsOverByte)
case 3 => charIdCodec
case 4 => nameCodec(bitsOverByte)
case 5 => requirementsCodec
case _ => failureCodec
case _ => failureCodec(code)
}
}
private def modifyCodecPadValue(code : Int, bitsOverByte : StreamLengthToken) : StreamLengthToken = {
code match {
case 0 => bitsOverByte.Add(1) //additional 1u
case 1 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
case 2 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
case 3 => bitsOverByte //32u = no added padding
@ -494,9 +359,9 @@ object SquadPositionEntry {
info => {
var i = 1
var dinfo = info
while(info.next.nonEmpty) {
while(dinfo.next.nonEmpty) {
i += 1
dinfo = info.next.get
dinfo = dinfo.next.get
}
i :: info :: HNil
}
@ -505,52 +370,109 @@ object SquadPositionEntry {
def codec(bitsOverByte : StreamLengthToken) : Codec[SquadPositionEntry] = {
import shapeless.::
/*
import net.psforever.newcodecs.newcodecs
// newcodecs.binary_choice[Option[LinkedSquadPositionInfo] :: HNil](
// index < 255,
// (bool :: squad_member_details_codec(bitsOverByte.Add(1))).exmap[Option[LinkedSquadPositionInfo] :: HNil] (
// {
// case _ :: info :: HNil => Attempt.Successful(Some(info) :: HNil)
// },
// {
// case Some(info) :: HNil => Attempt.Successful(true :: info :: HNil)
// case None :: HNil => Attempt.Failure(Err(s"this data for this position should not be None - $index < 255"))
// }
// ),
// bits.xmap[Option[LinkedSquadPositionInfo] :: HNil] (
// _ => None :: HNil,
// {
// case _ :: HNil => BitVector.empty
// }
// )
// )
*/
(
("index" | uint8) >>:~ { index =>
conditional(index < 255, bool) >>:~ { is_open =>
conditional(is_open.contains(true) && index < 255, "info" | squad_member_details_codec(bitsOverByte.Add(1))).hlist
}
conditional(index < 255, bool :: squad_member_details_codec(bitsOverByte.Add(1))) ::
conditional(index == 255, bits)
}
).xmap[SquadPositionEntry] (
{
case 255 :: _ :: _ :: HNil =>
SquadPositionEntry(255, None)
case ndx :: Some(false) :: None :: HNil =>
SquadPositionEntry(ndx, None)
case ndx :: Some(true) :: Some(info) :: HNil =>
SquadPositionEntry(ndx, Some(unlinkSquadPositionInfo(info)))
case ndx :: Some(_ :: info :: HNil) :: _ :: HNil =>
SquadPositionEntry(ndx, Some(unlinkSquadPositionInfo(Some(info))))
},
{
case SquadPositionEntry(255, _) =>
255 :: None :: None :: HNil
case SquadPositionEntry(ndx, None) =>
ndx :: Some(false) :: None :: HNil
255 :: None :: None :: HNil
case SquadPositionEntry(ndx, Some(info)) =>
ndx :: Some(true) :: Some(linkSquadPositionInfo(info)) :: HNil
ndx :: Some(true :: linkSquadPositionInfo(info) :: HNil) :: None :: HNil
}
)
}
}
object SquadDetail {
final val Blank = SquadDetail(None, None, None, None, None, None, None)
final val Blank = SquadDetail(None, None, None, None, None, None, None, None, None)
def apply(leader_char_id : Long) : SquadDetail = SquadDetail(None, Some(leader_char_id), None, None, None, None, None)
def apply(leader_char_id : Long) : SquadDetail = SquadDetail(None, None, Some(leader_char_id), None, None, None, None, None, None)
def apply(leader_name : String, task : Option[String]) : SquadDetail = SquadDetail(None, None, None, Some(leader_name), task, None, None)
def apply(leader_char_id : Long, leader_name : String) : SquadDetail = SquadDetail(None, None, Some(leader_char_id), None, Some(leader_name), None, None, None, None)
def apply(leader_name : Option[String], task : String) : SquadDetail = SquadDetail(None, None, None, leader_name, Some(task), None, None)
def apply(leader_name : String, task : Option[String]) : SquadDetail = SquadDetail(None, None, None, None, Some(leader_name), task, None, None, None)
def apply(zone_id : PlanetSideZoneID) : SquadDetail = SquadDetail(None, None, None, None, None, Some(zone_id), None)
def apply(leader_name : Option[String], task : String) : SquadDetail = SquadDetail(None, None, None, None, leader_name, Some(task), None, None, None)
def apply(member_list : List[SquadPositionEntry]) : SquadDetail = SquadDetail(None, None, None, None, None, None, Some(member_list))
def apply(zone_id : PlanetSideZoneID) : SquadDetail = SquadDetail(None, None, None, None, None, None, Some(zone_id), None, None)
def apply(unk1 : Int, leader_char_id : Long, unk2 : BitVector, leader_name : String, task : String, zone_id : PlanetSideZoneID, member_info : List[SquadPositionEntry]) : SquadDetail = {
SquadDetail(Some(unk1), Some(leader_char_id), Some(unk2), Some(leader_name), Some(task), Some(zone_id), Some(member_info))
def apply(member_list : List[SquadPositionEntry]) : SquadDetail = SquadDetail(None, None, None, None, None, None, None, None, Some(member_list))
def apply(unk1 : Int, unk2 : Int, leader_char_id : Long, unk3 : Long, leader_name : String, task : String, zone_id : PlanetSideZoneID, unk7 : Int, member_info : List[SquadPositionEntry]) : SquadDetail = {
SquadDetail(Some(unk1), Some(unk2), Some(leader_char_id), Some(unk3), Some(leader_name), Some(task), Some(zone_id), Some(unk7), Some(member_info))
}
}
object SquadDetailDefinitionUpdateMessage2 {
object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefinitionUpdateMessage] {
final val DefaultRequirements : Set[CertificationType.Value] = Set(
CertificationType.StandardAssault,
CertificationType.StandardExoSuit,
CertificationType.AgileExoSuit
)
final val Init = SquadDetailDefinitionUpdateMessage(
PlanetSideGUID(0),
SquadDetail(
0,
0,
0L,
0L,
"",
"",
PlanetSideZoneID(0),
0,
(0 to 9).map { i => SquadPositionEntry(i, SquadPositionDetail2.Blank)} toList
)
)
/*
DECOY CONSTRUCTORS
*/
def apply(guid : PlanetSideGUID,
unk1 : Int,
leader_char_id : Long,
unk2 : BitVector,
leader_name : String,
task : String,
zone_id : PlanetSideZoneID,
member_info : List[SquadPositionDetail]) : SquadDetailDefinitionUpdateMessage = {
SquadDetailDefinitionUpdateMessage(PlanetSideGUID(0), SquadDetail.Blank)
}
def apply(guid : PlanetSideGUID, char_id : Long, leader_name : String, task : String, zone_id : PlanetSideZoneID, member_info : List[SquadPositionDetail]) : SquadDetailDefinitionUpdateMessage = {
SquadDetailDefinitionUpdateMessage(PlanetSideGUID(0), SquadDetail.Blank)
}
/**
* Produces a `Codec` function for byte-aligned, padded Pascal strings encoded through common manipulations.
* @see `PacketHelpers.encodedWideStringAligned`
@ -582,14 +504,14 @@ object SquadDetailDefinitionUpdateMessage2 {
{
case 6 :: closed :: role :: orders :: char_id :: name :: requirements :: HNil =>
Attempt.Successful(
SquadPositionDetail2(Some(closed), Some(role), Some(orders), Some(defaultRequirements ++ CertificationType.fromEncodedLong(requirements)), Some(char_id), Some(name))
SquadPositionDetail2(Some(closed), Some(role), Some(orders), Some(DefaultRequirements ++ CertificationType.fromEncodedLong(requirements)), Some(char_id), Some(name))
)
case data =>
Attempt.Failure(Err(s"can not decode a SquadDetailDefinitionUpdate member's data - $data"))
},
{
case SquadPositionDetail2(Some(closed), Some(role), Some(orders), Some(requirements), Some(char_id), Some(name)) =>
Attempt.Successful(6 :: closed :: role :: orders :: char_id :: name :: CertificationType.toEncodedLong(defaultRequirements ++ requirements) :: HNil)
Attempt.Successful(6 :: closed :: role :: orders :: char_id :: name :: CertificationType.toEncodedLong(DefaultRequirements ++ requirements) :: HNil)
}
)
}
@ -673,20 +595,19 @@ object SquadDetailDefinitionUpdateMessage2 {
import shapeless.::
(
("unk1" | uint8) ::
uint24 :: //unknown, but can be 0'd
("unk2" | uint24) :: //unknown, but can be 0'd
("leader_char_id" | uint32L) ::
("unk2" | bits(22)) :: //variable fields, but can be 0'd
uint(10) :: //constant = 0
("unk3" | uint32L) :: //variable fields, but can be 0'd
("leader" | PacketHelpers.encodedWideStringAligned(7)) ::
("task" | PacketHelpers.encodedWideString) ::
("zone_id" | PlanetSideZoneID.codec) ::
uint(23) :: //constant = 4983296
("unk7" | uint(23)) :: //during full squad mode, constant = 4983296
optional(bool, "member_info" | initial_member_codec)
).exmap[SquadDetail] (
{
case u1 :: _ :: char_id :: u2 :: _ :: leader :: task :: zone :: _ :: Some(member_list) :: HNil =>
case u1 :: u2 :: char_id :: u3 :: leader :: task :: zone :: unk7 :: Some(member_list) :: HNil =>
Attempt.Successful(
SquadDetail(Some(u1), Some(char_id), Some(u2), Some(leader), Some(task), Some(zone),
SquadDetail(Some(u1), Some(u2), Some(char_id), Some(u3), Some(leader), Some(task), Some(zone), Some(unk7),
Some(unlinkMemberList(member_list).zipWithIndex.map { case (entry, index) => SquadPositionEntry(index, Some(entry)) })
)
)
@ -694,9 +615,9 @@ object SquadDetailDefinitionUpdateMessage2 {
Attempt.failure(Err(s"can not get squad detail definition from data $data"))
},
{
case SquadDetail(Some(u1), Some(char_id), Some(u2), Some(leader), Some(task), Some(zone), Some(member_list)) =>
case SquadDetail(Some(u1), Some(u2), Some(char_id), Some(u3), Some(leader), Some(task), Some(zone), Some(unk7), Some(member_list)) =>
Attempt.Successful(
math.max(u1, 1) :: 0 :: char_id :: u2.take(22) :: 0 :: leader :: task :: zone :: 4983296 ::
math.max(u1, 1) :: u2 :: char_id :: u3 :: leader :: task :: zone :: unk7 ::
Some(linkMemberList(member_list.collect { case SquadPositionEntry(_, Some(entry)) => entry }.reverse)) ::
HNil
)
@ -704,20 +625,40 @@ object SquadDetailDefinitionUpdateMessage2 {
)
}
private val field1Codec : Codec[SquadDetail] = uint16L.exmap[SquadDetail] (
unk1 => Attempt.successful(SquadDetail(Some(unk1), None, None, None, None, None, None, None, None)),
{
case SquadDetail(Some(unk1), _, _, _, _, _, _, _, _) =>
Attempt.successful(unk1)
case _ =>
Attempt.failure(Err("failed to encode squad data for unknown field #1"))
}
)
private val leaderCharIdCodec : Codec[SquadDetail] = uint32L.exmap[SquadDetail] (
char_id => Attempt.successful(SquadDetail(char_id)),
{
case SquadDetail(_, Some(char_id), _, _, _, _, _) =>
case SquadDetail(_, _, Some(char_id), _, _, _, _, _, _) =>
Attempt.successful(char_id)
case _ =>
Attempt.failure(Err("failed to encode squad data for leader id"))
}
)
private val field3Codec : Codec[SquadDetail] = uint32L.exmap[SquadDetail] (
unk3 => Attempt.successful(SquadDetail(None, None, None, Some(unk3), None, None, None, None, None)),
{
case SquadDetail(_, _, _, Some(unk3), _, _, _, _, _) =>
Attempt.successful(unk3)
case _ =>
Attempt.failure(Err("failed to encode squad data for unknown field #3"))
}
)
private def leaderNameCodec(bitsOverByte : StreamLengthToken) : Codec[SquadDetail] = paddedStringMetaCodec(bitsOverByte.Length).exmap[SquadDetail] (
name => Attempt.successful(SquadDetail(name, None)),
{
case SquadDetail(_, _, _, Some(name), _, _, _) =>
case SquadDetail(_, _, _, _, Some(name), _, _, _, _) =>
Attempt.successful(name)
case _ =>
Attempt.failure(Err("failed to encode squad data for leader name"))
@ -727,7 +668,7 @@ object SquadDetailDefinitionUpdateMessage2 {
private def taskCodec(bitsOverByte : StreamLengthToken) : Codec[SquadDetail] = paddedStringMetaCodec(bitsOverByte.Length).exmap[SquadDetail] (
task => Attempt.successful(SquadDetail(None, task)),
{
case SquadDetail(_, _, _, _, Some(task), _, _) =>
case SquadDetail(_, _, _, _, _, Some(task), _, _, _) =>
Attempt.successful(task)
case _ =>
Attempt.failure(Err("failed to encode squad data for task"))
@ -737,27 +678,39 @@ object SquadDetailDefinitionUpdateMessage2 {
private val zoneCodec : Codec[SquadDetail] = PlanetSideZoneID.codec.exmap[SquadDetail] (
zone_id => Attempt.successful(SquadDetail(zone_id)),
{
case SquadDetail(_, _, _, _, _, Some(zone_id), _) =>
case SquadDetail(_, _, _, _, _, _, Some(zone_id), _, _) =>
Attempt.successful(zone_id)
case _ =>
Attempt.failure(Err("failed to encode squad data for zone id"))
}
)
private val field7Codec : Codec[SquadDetail] = {
uint4.exmap[SquadDetail] (
unk7 => Attempt.successful(SquadDetail(None, None, None, None, None, None, None, Some(unk7), None)),
{
case SquadDetail(_, _, _, _, _, _, _, Some(unk7), _) =>
Attempt.successful(unk7)
case _ =>
Attempt.failure(Err("failed to encode squad data for unknown field #7"))
}
)
}
private def membersCodec(bitsOverByte : StreamLengthToken) : Codec[SquadDetail] = {
import shapeless.::
bitsOverByte.Add(19)
bitsOverByte.Add(4)
(
uint(19) :: //constant = 0x60040, or 393280 in 19u BE
uint4 :: //constant = 12
vector(SquadPositionEntry.codec(bitsOverByte))
).exmap[SquadDetail] (
{
case _ :: member_list :: HNil =>
case 12 :: member_list :: HNil =>
Attempt.successful(SquadDetail(member_list.toList))
},
{
case SquadDetail(_, _, _, _, _, _, Some(member_list)) =>
Attempt.successful(393280 :: member_list.toVector :: HNil)
case SquadDetail(_, _, _, _, _, _, _, _, Some(member_list)) =>
Attempt.successful(12 :: member_list.toVector :: HNil)
case _ =>
Attempt.failure(Err("failed to encode squad data for members"))
}
@ -771,10 +724,13 @@ object SquadDetailDefinitionUpdateMessage2 {
private def selectCodecAction(code : Int, bitsOverByte : StreamLengthToken) : Codec[SquadDetail] = {
code match {
case 1 => field1Codec
case 2 => leaderCharIdCodec
case 3 => field3Codec
case 4 => leaderNameCodec(bitsOverByte)
case 5 => taskCodec(bitsOverByte)
case 6 => zoneCodec
case 7 => field7Codec
case 8 => membersCodec(bitsOverByte)
case _ => failureCodec
}
@ -782,10 +738,13 @@ object SquadDetailDefinitionUpdateMessage2 {
private def modifyCodecPadValue(code : Int, bitsOverByte : StreamLengthToken) : StreamLengthToken = {
code match {
case 1 => bitsOverByte //16u = no added padding
case 2 => bitsOverByte //32u = no added padding
case 3 => bitsOverByte //32u = no added padding
case 4 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
case 5 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
case 6 => bitsOverByte //32u = no added padding
case 7 => bitsOverByte.Add(4) //additional 4u
case 8 => bitsOverByte.Length = 0 //end of stream
case _ => bitsOverByte.Length = Int.MinValue //wildly incorrect
}
@ -808,11 +767,14 @@ object SquadDetailDefinitionUpdateMessage2 {
private def linkSquadInfo(info : SquadDetail) : LinkedSquadInfo = {
//import scala.collection.immutable.::
Seq(
(8, SquadDetail(None, None, None, None, None, None, info.member_info)),
(6, SquadDetail(None, None, None, None, None, info.zone_id, None)),
(5, SquadDetail(None, None, None, None, info.task, None, None)),
(4, SquadDetail(None, None, None, info.leader_name, None, None, None)),
(2, SquadDetail(None, info.leader_char_id, None, None, None, None, None))
(8, SquadDetail(None, None, None, None, None, None, None, None, info.member_info)),
(7, SquadDetail(None, None, None, None, None, None, None, info.unk7, None)),
(6, SquadDetail(None, None, None, None, None, None, info.zone_id, None, None)),
(5, SquadDetail(None, None, None, None, None, info.task, None, None, None)),
(4, SquadDetail(None, None, None, None, info.leader_name, None, None, None, None)),
(3, SquadDetail(None, None, None, info.unk3, None, None, None, None, None)),
(2, SquadDetail(None, None, info.leader_char_id, None, None, None, None, None, None)),
(1, SquadDetail(info.unk1, None, None, None, None, None, None, None, None))
) //in reverse order so that the linked list is in the correct order
.filterNot { case (_, sqInfo) => sqInfo == SquadDetail.Blank}
match {
@ -869,7 +831,7 @@ object SquadDetailDefinitionUpdateMessage2 {
}
}
private def codec() : Codec[SquadDetailDefinitionUpdateMessage2] = {
implicit val codec : Codec[SquadDetailDefinitionUpdateMessage] = {
import shapeless.::
(
("guid" | PlanetSideGUID.codec) ::
@ -877,14 +839,14 @@ object SquadDetailDefinitionUpdateMessage2 {
(uint8 >>:~ { size =>
squadDetailSelectCodec(size).hlist
})
).exmap[SquadDetailDefinitionUpdateMessage2] (
).exmap[SquadDetailDefinitionUpdateMessage] (
{
case guid :: _ :: _ :: info :: HNil =>
Attempt.Successful(SquadDetailDefinitionUpdateMessage2(guid, info))
Attempt.Successful(SquadDetailDefinitionUpdateMessage(guid, info))
},
{
case SquadDetailDefinitionUpdateMessage2(guid, info) =>
val occupiedSquadFieldCount = List(info.unk1, info.leader_char_id, info.unk2, info.leader_name, info.task, info.zone_id, info.member_info)
case SquadDetailDefinitionUpdateMessage(guid, info) =>
val occupiedSquadFieldCount = List(info.unk1, info.unk2, info.leader_char_id, info.unk3, info.leader_name, info.task, info.zone_id, info.unk7, info.member_info)
.count(_.nonEmpty)
if(occupiedSquadFieldCount < 9) {
//itemized detail definition protocol
@ -916,6 +878,4 @@ object SquadDetailDefinitionUpdateMessage2 {
}
)
}
implicit val code : Codec[SquadDetailDefinitionUpdateMessage2] = codec()
}

View file

@ -3,44 +3,587 @@ package game
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.types.CertificationType
import org.specs2.mutable._
import scodec.bits._
class SquadDetailDefinitionUpdateMessageTest extends Specification {
val string = hex"e80300848180038021514601288a8400420048006f0066004400bf5c0023006600660064006300300030002a002a002a005c0023003900360034003000660066003d004b004f004b002b005300500043002b0046004c0059003d005c0023006600660064006300300030002a002a002a005c002300460046003400300034003000200041006c006c002000570065006c0063006f006d006500070000009814010650005c00230066006600300030003000300020007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c008000000000800100000c00020c8c5c00230066006600640063003000300020002000200043008000000000800100000c00020c8c5c002300660066006400630030003000200020002000480080eab58a02854f0070006f006c0045000100000c00020c8d5c002300660066006400630030003000200020002000200049008072d47a028b42006f006200610046003300740074003900300037000100000c00020c8c5c0023006600660064006300300030002000200020004e008000000000800100000c00020c8c5c00230066006600640063003000300020002000200041008000000000800100000c00020ca05c00230066006600300030003000300020007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c008000000000800100000c00020c8c5c0023003900360034003000660066002000200020004b008000000000800100000c00020c8c5c0023003900360034003000660066002000200020004f008042a28c028448006f00660044000100000c00020c8c5c0023003900360034003000660066002000200020004b008000000000800100000c0000"
val string_unk = hex"e80300821104145011b9be840024284a00610061006b006f008c008118000000024000ff"
val string_unk1 = hex"e80300818800015c5189004603408c000000012000ff"
val string_leader_char_id = hex"e8050080904d56b808"
val string_unk3LeaderName = hex"e80300821104145011b9be840024284a00610061006b006f008c008118000000024000ff"
val string_task = hex"e8050080ac6041006c006c002000570065006c0063006f006d0065002000"
val string_zone = hex"e8030080b0a8000000"
val string_taskZone = hex"e80200812ce05c002300460046003000300030003000200054006800650020005c002300660066006600660066006600200042006c0061006400650073006040000000"
val string_unk7 = hex"e8030081ac8054006800650020004b0069006e00670027007300200053007100750061006400788c09808c4854006800650020004700750061007200640008808c5054006800650020004b006e00690067006800740007808c4054006800650020004500610072006c0006808c4054006800650020004c006f007200640005808c405400680065002000440075006b00650004808c4854006800650020004200610072006f006e0003808c6054006800650020005000720069006e00630065007300730002808c5054006800650020005000720069006e006300650001808c48540068006500200051007500650065006e0000808c4054006800650020004b0069006e006700ff"
val string_member_closed = hex"e8030080c602c043fe"
val string_member_role = hex"e8070080c60040462443006f006d006d0061006e00640065007200ff"
val string_member_roleRequirements = hex"e8010080c60340862841004400560020004800610063006b00650072005000000002003fc0"
val string_member_charIdName = hex"e8030080c602c08f2658480123004400750063006b006d006100730074006500720034003300ff"
val string_task_memberEtc = hex"e80100812ce05c002300460046003000300030003000200054006800650020005c002300660066006600660066006600200042006c0061006400650073008c09810c005000000000000220230007808c0006808c0005808c0004808c0003808c0002808c0001808c0000808c00ff"
val string_full = hex"e80300848180038021514601288a8400420048006f0066004400bf5c0023006600660064006300300030002a002a002a005c0023003900360034003000660066003d004b004f004b002b005300500043002b0046004c0059003d005c0023006600660064006300300030002a002a002a005c002300460046003400300034003000200041006c006c002000570065006c0063006f006d006500070000009814010650005c00230066006600300030003000300020007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c008000000000800100000c00020c8c5c00230066006600640063003000300020002000200043008000000000800100000c00020c8c5c002300660066006400630030003000200020002000480080eab58a02854f0070006f006c0045000100000c00020c8d5c002300660066006400630030003000200020002000200049008072d47a028b42006f006200610046003300740074003900300037000100000c00020c8c5c0023006600660064006300300030002000200020004e008000000000800100000c00020c8c5c00230066006600640063003000300020002000200041008000000000800100000c00020ca05c00230066006600300030003000300020007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c008000000000800100000c00020c8c5c0023003900360034003000660066002000200020004b008000000000800100000c00020c8c5c0023003900360034003000660066002000200020004f008042a28c028448006f00660044000100000c00020c8c5c0023003900360034003000660066002000200020004b008000000000800100000c0000"
"SquadDetailDefinitionUpdateMessage" should {
"decode" in {
PacketCoding.DecodePacket(string).require match {
case SquadDetailDefinitionUpdateMessage(guid, unk1, char_id, unk2, leader, task, zone, member_info) =>
"decode (test)" in {
PacketCoding.DecodePacket(string_unk).require match {
case SquadDetailDefinitionUpdateMessage(guid, detail) =>
detail
ok
case _ =>
ko
}
ok
}
"decode (unk1 + members)" in {
PacketCoding.DecodePacket(string_unk1).require match {
case SquadDetailDefinitionUpdateMessage(guid, detail) =>
guid mustEqual PlanetSideGUID(3)
detail match {
case SquadDetail(Some(unk1), None, Some(char_id), None, None, None, None, None, Some(_)) =>
unk1 mustEqual 0
char_id mustEqual 1221560L
//members tests follow ...
case _ =>
ko
}
case _ =>
ko
}
}
"encode" in {
val msg = SquadDetailDefinitionUpdateMessage(
PlanetSideGUID(3),
42771010L,
"HofD",
"\\#ffdc00***\\#9640ff=KOK+SPC+FLY=\\#ffdc00***\\#FF4040 All Welcome",
PlanetSideZoneID(7),
List(
SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", "Just a space filler"),
SquadPositionDetail("\\#ffdc00 C", ""),
SquadPositionDetail("\\#ffdc00 H", "", Set(), 42644970L, "OpoIE"),
SquadPositionDetail("\\#ffdc00 I", "", Set(), 41604210L, "BobaF3tt907"),
SquadPositionDetail("\\#ffdc00 N", ""),
SquadPositionDetail("\\#ffdc00 A", ""),
SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", "Another space filler"),
SquadPositionDetail("\\#9640ff K", ""),
SquadPositionDetail("\\#9640ff O", "", Set(), 42771010L, "HofD"),
SquadPositionDetail("\\#9640ff K", "")
)
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
// "decode (char id)" in {
// PacketCoding.DecodePacket(string_leader_char_id).require match {
// case SquadDetailDefinitionUpdateMessage(guid, detail) =>
// guid mustEqual PlanetSideGUID(5)
// detail match {
// case SquadDetail(None, None, Some(char_id), None, None, None, None, None, None) =>
// char_id mustEqual 30910985
// case _ =>
// ko
// }
// ok
// case _ =>
// ko
// }
// }
//
// "decode (unk3 + leader name)" in {
// PacketCoding.DecodePacket(string_unk3LeaderName).require match {
// case SquadDetailDefinitionUpdateMessage(guid, detail) =>
// guid mustEqual PlanetSideGUID(3)
// detail match {
// case SquadDetail(None, None, Some(char_id), Some(unk3), Some(leader), None, None, None, Some(_)) =>
// char_id mustEqual 42631712L
// unk3 mustEqual 556403L
// leader mustEqual "Jaako"
// //members tests follow ...
// case _ =>
// ko
// }
// case _ =>
// ko
// }
// }
//
// "decode (task)" in {
// PacketCoding.DecodePacket(string_task).require match {
// case SquadDetailDefinitionUpdateMessage(guid, detail) =>
// guid mustEqual PlanetSideGUID(5)
// detail match {
// case SquadDetail(None, None, None, None, None, Some(task), None, None, None) =>
// task mustEqual "All Welcome "
// case _ =>
// ko
// }
// ok
// case _ =>
// ko
// }
// }
//
// "decode (zone)" in {
// PacketCoding.DecodePacket(string_zone).require match {
// case SquadDetailDefinitionUpdateMessage(guid, detail) =>
// guid mustEqual PlanetSideGUID(3)
// detail match {
// case SquadDetail(None, None, None, None, None, None, Some(zone), None, None) =>
// zone mustEqual PlanetSideZoneID(21)
// case _ =>
// ko
// }
// ok
// case _ =>
// ko
// }
// }
//
// "decode (task + zone)" in {
// PacketCoding.DecodePacket(string_taskZone).require match {
// case SquadDetailDefinitionUpdateMessage(guid, detail) =>
// detail match {
// case SquadDetail(None, None, None, None, None, Some(task), Some(zone), None, None) =>
// task mustEqual "\\#FF0000 The \\#ffffff Blades"
// zone mustEqual PlanetSideZoneID(4)
// case _ =>
// ko
// }
// case _ =>
// ko
// }
// ok
// }
//
// "decode (unk7 + members)" in {
// PacketCoding.DecodePacket(string_unk7).require match {
// case SquadDetailDefinitionUpdateMessage(guid, detail) =>
// guid mustEqual PlanetSideGUID(3)
// detail match {
// case SquadDetail(None, None, None, None, None, Some(task), None, Some(unk7), Some(_)) =>
// task mustEqual "The King's Squad"
// unk7 mustEqual 8
// //members tests follow ...
// case _ =>
// ko
// }
// case _ =>
// ko
// }
// }
//
// "decode (member closed)" in {
// PacketCoding.DecodePacket(string_member_closed).require match {
// case SquadDetailDefinitionUpdateMessage(guid, detail) =>
// guid mustEqual PlanetSideGUID(3)
// detail match {
// case SquadDetail(None, None, None, None, None, None, None, None, Some(members)) =>
// members.size mustEqual 2
// members.head.index mustEqual 5
// members.head.info match {
// case Some(SquadPositionDetail2(Some(is_closed), None, None, None, None, None)) =>
// is_closed mustEqual true
// case _ =>
// ko
// }
// case _ =>
// ko
// }
// ok
// case _ =>
// ko
// }
// }
//
// "decode (member role)" in {
// PacketCoding.DecodePacket(string_member_role).require match {
// case SquadDetailDefinitionUpdateMessage(guid, detail) =>
// guid mustEqual PlanetSideGUID(7)
// detail match {
// case SquadDetail(None, None, None, None, None, None, None, None, Some(members)) =>
// members.size mustEqual 2
// members.head.index mustEqual 0
// members.head.info match {
// case Some(SquadPositionDetail2(None, Some(role), None, None, None, None)) =>
// role mustEqual "Commander"
// case _ =>
// ko
// }
// case _ =>
// ko
// }
// ok
// case _ =>
// ko
// }
// }
//
// "decode (member role + requirements)" in {
// PacketCoding.DecodePacket(string_member_roleRequirements).require match {
// case SquadDetailDefinitionUpdateMessage(guid, detail) =>
// guid mustEqual PlanetSideGUID(1)
// detail match {
// case SquadDetail(None, None, None, None, None, None, None, None, Some(members)) =>
// members.size mustEqual 2
// members.head.index mustEqual 6
// members.head.info match {
// case Some(SquadPositionDetail2(None, Some(role), None, Some(req), None, None)) =>
// role mustEqual "ADV Hacker"
// req.size mustEqual 1
// req.contains(CertificationType.AdvancedHacking) mustEqual true
// case _ =>
// ko
// }
// case _ =>
// ko
// }
// ok
// case _ =>
// ko
// }
// }
//
// "decode (member char id + name)" in {
// PacketCoding.DecodePacket(string_member_charIdName).require match {
// case SquadDetailDefinitionUpdateMessage(guid, detail) =>
// guid mustEqual PlanetSideGUID(3)
// detail match {
// case SquadDetail(None, None, None, None, None, None, None, None, Some(members)) =>
// members.size mustEqual 2
// members.head.index mustEqual 5
// members.head.info match {
// case Some(SquadPositionDetail2(None, None, None, None, Some(char_id), Some(name))) =>
// char_id mustEqual 1218249L
// name mustEqual "Duckmaster43"
// case _ =>
// ko
// }
// case _ =>
// ko
// }
// ok
// case _ =>
// ko
// }
// }
//
// "decode (task + member etc)" in {
// PacketCoding.DecodePacket(string_task_memberEtc).require match {
// case SquadDetailDefinitionUpdateMessage(guid, detail) =>
// guid mustEqual PlanetSideGUID(1)
// detail match {
// case SquadDetail(None, None, None, None, None, Some(task), None, None, Some(members)) =>
// task mustEqual "\\#FF0000 The \\#ffffff Blades"
// members.size mustEqual 11
// //
// members.head.index mustEqual 9
// members.head.info match {
// case Some(SquadPositionDetail2(None, Some(role), None, Some(req), None, None)) =>
// role mustEqual ""
// req mustEqual Set.empty
// case _ =>
// ko
// }
// //
// (1 to 9).foreach { index =>
// members(index).index mustEqual 9 - index
// members(index).info match {
// case Some(SquadPositionDetail2(None, Some(role), None, None, None, None)) =>
// role mustEqual ""
// case _ =>
// ko
// }
// }
// case _ =>
// ko
// }
// case _ =>
// ko
// }
// ok
// }
//
// "decode (full squad)" in {
// PacketCoding.DecodePacket(string_full).require match {
// case SquadDetailDefinitionUpdateMessage(guid, detail) =>
// guid mustEqual PlanetSideGUID(3)
// detail match {
// case SquadDetail(Some(u1), Some(u2), Some(char_id), Some(u3), Some(leader), Some(task), Some(zone), Some(unk7), Some(member_list)) =>
// u1 mustEqual 3
// u2 mustEqual 1792
// char_id mustEqual 42771010L
// u3 mustEqual 529745L
// leader mustEqual "HofD"
// task mustEqual "\\#ffdc00***\\#9640ff=KOK+SPC+FLY=\\#ffdc00***\\#FF4040 All Welcome"
// zone mustEqual PlanetSideZoneID(7)
// unk7 mustEqual 4983296
// member_list.size mustEqual 10
// member_list.head mustEqual SquadPositionEntry(0,Some(
// SquadPositionDetail2(
// Some(false),
// Some("\\#ff0000 |||||||||||||||||||||||"),
// Some(""),
// Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
// Some(0),
// Some("")))
// )
// member_list(1) mustEqual SquadPositionEntry(1,Some(
// SquadPositionDetail2(
// Some(false),
// Some("\\#ffdc00 C"),
// Some(""),
// Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
// Some(0),
// Some("")))
// )
// member_list(2) mustEqual SquadPositionEntry(2,Some(
// SquadPositionDetail2(
// Some(false),
// Some("\\#ffdc00 H"),
// Some(""),
// Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
// Some(42644970L),
// Some("OpolE")
// )
// ))
// member_list(3) mustEqual SquadPositionEntry(3,Some(
// SquadPositionDetail2(
// Some(false),
// Some("\\#ffdc00 I"),
// Some(""),
// Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
// Some(41604210L),
// Some("BobaF3tt907")
// )
// ))
// member_list(4) mustEqual SquadPositionEntry(4,Some(
// SquadPositionDetail2(
// Some(false),
// Some("\\#ffdc00 N"),
// Some(""),
// Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
// Some(0),
// Some("")
// )
// ))
// member_list(5) mustEqual SquadPositionEntry(5,Some(
// SquadPositionDetail2(
// Some(false),
// Some("\\#ffdc00 A"),
// Some(""),
// Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
// Some(0),
// Some("")
// )
// ))
// member_list(6) mustEqual SquadPositionEntry(6,Some(
// SquadPositionDetail2(
// Some(false),
// Some("\\#ff0000 |||||||||||||||||||||||"),
// Some(""),
// Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
// Some(0),
// Some("")
// )
// ))
// member_list(7) mustEqual SquadPositionEntry(7,Some(
// SquadPositionDetail2(
// Some(false),
// Some("\\#9640ff K"),
// Some(""),
// Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
// Some(0),
// Some("")
// )
// ))
// member_list(8) mustEqual SquadPositionEntry(8,Some(
// SquadPositionDetail2(
// Some(false),
// Some("\\#9640ff O"),
// Some(""),
// Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
// Some(42771010L),
// Some("HofD")
// )
// ))
// member_list(9) mustEqual SquadPositionEntry(9,Some(
// SquadPositionDetail2(
// Some(false),
// Some("\\#9640ff K"),
// Some(""),
// Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
// Some(0),
// Some("")
// )
// ))
// case _ =>
// ko
// }
// case _ =>
// ko
// }
// }
//
// "encode (char id)" in {
// val msg = SquadDetailDefinitionUpdateMessage(
// PlanetSideGUID(5),
// SquadDetail(30910985L)
// )
// val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
// pkt mustEqual string_leader_char_id
// }
//
// "encode (unk3 + leader name)" in {
// val msg = SquadDetailDefinitionUpdateMessage(
// PlanetSideGUID(3),
// SquadDetail(None, None, Some(42631712L), Some(556403L), Some("Jaako"), None, None, None, Some(List(
// SquadPositionEntry(0, SquadPositionDetail2(0L, "")),
// SquadPositionEntry(255, None)
// )))
// )
// val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
// pkt mustEqual string_unk3LeaderName
// }
//
// "encode (task)" in {
// val msg = SquadDetailDefinitionUpdateMessage(
// PlanetSideGUID(5),
// SquadDetail(None, "All Welcome ")
// )
// val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
// pkt mustEqual string_task
// }
//
// "encode (zone)" in {
// val msg = SquadDetailDefinitionUpdateMessage(
// PlanetSideGUID(3),
// SquadDetail(PlanetSideZoneID(21))
// )
// val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
// pkt mustEqual string_zone
// }
//
// "encode (task + zone)" in {
// val msg = SquadDetailDefinitionUpdateMessage(
// PlanetSideGUID(2),
// SquadDetail(None, None, None, None, None, Some("\\#FF0000 The \\#ffffff Blades"), Some(PlanetSideZoneID(4)), None)
// )
// val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
// pkt mustEqual string_taskZone
// }
//
// "encode (unk7 + members)" in {
// val msg = SquadDetailDefinitionUpdateMessage(
// PlanetSideGUID(3),
// SquadDetail(
// None, None, None, None, None,
// Some("The King's Squad"),
// None, Some(8),
// Some(List(
// SquadPositionEntry(9, SquadPositionDetail2("The Guard", None)),
// SquadPositionEntry(8, SquadPositionDetail2("The Knight", None)),
// SquadPositionEntry(7, SquadPositionDetail2("The Earl", None)),
// SquadPositionEntry(6, SquadPositionDetail2("The Lord", None)),
// SquadPositionEntry(5, SquadPositionDetail2("The Duke", None)),
// SquadPositionEntry(4, SquadPositionDetail2("The Baron", None)),
// SquadPositionEntry(3, SquadPositionDetail2("The Princess", None)),
// SquadPositionEntry(2, SquadPositionDetail2("The Prince", None)),
// SquadPositionEntry(1, SquadPositionDetail2("The Queen", None)),
// SquadPositionEntry(0, SquadPositionDetail2("The King", None)),
// SquadPositionEntry(255, None)
// ))
// )
// )
// val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
// pkt mustEqual string_unk7
// }
//
// "encode (member closed)" in {
// val msg = SquadDetailDefinitionUpdateMessage(
// PlanetSideGUID(3),
// SquadDetail(List(
// SquadPositionEntry(5, SquadPositionDetail2.Closed),
// SquadPositionEntry(255, None)
// ))
// )
// val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
// pkt mustEqual string_member_closed
// }
//
//
// "encode (member role)" in {
// val msg = SquadDetailDefinitionUpdateMessage(
// PlanetSideGUID(7),
// SquadDetail(List(
// SquadPositionEntry(0, SquadPositionDetail2("Commander", None)),
// SquadPositionEntry(255, None)
// ))
// )
// val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
// pkt mustEqual string_member_role
// }
//
// "encode (member role + requirements)" in {
// val msg = SquadDetailDefinitionUpdateMessage(
// PlanetSideGUID(1),
// SquadDetail(List(
// SquadPositionEntry(6, SquadPositionDetail2(None, Some("ADV Hacker"), None, Some(Set(CertificationType.AdvancedHacking)), None, None)),
// SquadPositionEntry(255, None)
// ))
// )
// val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
// pkt mustEqual string_member_roleRequirements
// }
//
// "encode (member char id + name)" in {
// val msg = SquadDetailDefinitionUpdateMessage(
// PlanetSideGUID(3),
// SquadDetail(List(
// SquadPositionEntry(5, SquadPositionDetail2(1218249L, "Duckmaster43")),
// SquadPositionEntry(255, None)
// ))
// )
// val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
// pkt mustEqual string_member_charIdName
// }
//
// "encode (task + member etc)" in {
// val msg = SquadDetailDefinitionUpdateMessage(
// PlanetSideGUID(1),
// SquadDetail(
// None, None, None, None, None,
// Some("\\#FF0000 The \\#ffffff Blades"), None, None,
// Some(List(
// SquadPositionEntry(9, Some(SquadPositionDetail2(None, Some(""), None, Some(Set()), None, None))),
// SquadPositionEntry(8, Some(SquadPositionDetail2(None, Some(""), None, None, None, None))),
// SquadPositionEntry(7, Some(SquadPositionDetail2(None, Some(""), None, None, None, None))),
// SquadPositionEntry(6, Some(SquadPositionDetail2(None, Some(""), None, None, None, None))),
// SquadPositionEntry(5, Some(SquadPositionDetail2(None, Some(""), None, None, None, None))),
// SquadPositionEntry(4, Some(SquadPositionDetail2(None, Some(""), None, None, None, None))),
// SquadPositionEntry(3, Some(SquadPositionDetail2(None, Some(""), None, None, None, None))),
// SquadPositionEntry(2, Some(SquadPositionDetail2(None, Some(""), None, None, None, None))),
// SquadPositionEntry(1, Some(SquadPositionDetail2(None, Some(""), None, None, None, None))),
// SquadPositionEntry(0, Some(SquadPositionDetail2(None, Some(""), None, None, None, None))),
// SquadPositionEntry(255, None)
// ))
// )
// )
// val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
// pkt mustEqual string_task_memberEtc
// }
//
// "encode (full squad)" in {
// val msg = SquadDetailDefinitionUpdateMessage(
// PlanetSideGUID(3),
// SquadDetail(
// Some(3),
// Some(1792),
// Some(42771010L),
// Some(529745L),
// Some("HofD"),
// Some("\\#ffdc00***\\#9640ff=KOK+SPC+FLY=\\#ffdc00***\\#FF4040 All Welcome"),
// Some(PlanetSideZoneID(7)),
// Some(4983296),
// Some(List(
// SquadPositionEntry(0, SquadPositionDetail2("\\#ff0000 |||||||||||||||||||||||", "", Set(), 0, "")),
// SquadPositionEntry(1, SquadPositionDetail2("\\#ffdc00 C", "", Set(), 0, "")),
// SquadPositionEntry(2, SquadPositionDetail2("\\#ffdc00 H", "", Set(), 42644970L, "OpolE")),
// SquadPositionEntry(3, SquadPositionDetail2("\\#ffdc00 I", "", Set(), 41604210L, "BobaF3tt907")),
// SquadPositionEntry(4, SquadPositionDetail2("\\#ffdc00 N", "", Set(), 0, "")),
// SquadPositionEntry(5, SquadPositionDetail2("\\#ffdc00 A", "", Set(), 0, "")),
// SquadPositionEntry(6, SquadPositionDetail2("\\#ff0000 |||||||||||||||||||||||", "", Set(), 0, "")),
// SquadPositionEntry(7, SquadPositionDetail2("\\#9640ff K", "", Set(), 0, "")),
// SquadPositionEntry(8, SquadPositionDetail2("\\#9640ff O", "", Set(), 42771010L ,"HofD")),
// SquadPositionEntry(9, SquadPositionDetail2("\\#9640ff K", "", Set(), 0, ""))
// )
// ))
// )
// val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
// val pktBits = pkt.toBitVector
// val strBits = string_full.toBitVector
// pktBits.grouped(100).zip(strBits.grouped(100)).foreach({ case (a, b) =>
// a mustEqual b
// })
// pkt mustEqual string_full
// }
}
}

View file

@ -2919,21 +2919,28 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(
SquadDetailDefinitionUpdateMessage(
PlanetSideGUID(3),
42771010L,
"HofD",
"\\#ffdc00***\\#9640ff=KOK+SPC+FLY=\\#ffdc00***\\#FF4040 All Welcome",
PlanetSideZoneID(7),
List(
SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", "Just a space filler"),
SquadPositionDetail("\\#ffdc00 C", ""),
SquadPositionDetail(false, "\\#ffdc00 H", "", Set(), 42644970L, "OpoIE"),
SquadPositionDetail(false, "\\#ffdc00 I", "", Set(), 41604210L, "BobaF3tt907"),
SquadPositionDetail("\\#ffdc00 N", ""),
SquadPositionDetail("\\#ffdc00 A", ""),
SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", "Another space filler"),
SquadPositionDetail("\\#9640ff K", ""),
SquadPositionDetail(false, "\\#9640ff O", "", Set(), 42771010L, "HofD"),
SquadPositionDetail("\\#9640ff K", "")
SquadDetail(
3,
1792,
42771010L,
529745L,
"HofD",
"\\#ffdc00***\\#9640ff=KOK+SPC+FLY=\\#ffdc00***\\#FF4040 All Welcome",
PlanetSideZoneID(7),
4983296,
List(
SquadPositionEntry(0, SquadPositionDetail2("\\#ff0000 |||||||||||||||||||||||", "", Set(), 0, "")),
SquadPositionEntry(1, SquadPositionDetail2("\\#ffdc00 C", "", Set(), 0, "")),
SquadPositionEntry(2, SquadPositionDetail2("\\#ffdc00 H", "", Set(), 42644970L, "OpolE")),
SquadPositionEntry(3, SquadPositionDetail2("\\#ffdc00 I", "", Set(), 41604210L, "BobaF3tt907")),
SquadPositionEntry(4, SquadPositionDetail2("\\#ffdc00 N", "", Set(), 0, "")),
SquadPositionEntry(5, SquadPositionDetail2("\\#ffdc00 A", "", Set(), 0, "")),
// SquadPositionEntry(6, SquadPositionDetail2("\\#ff0000 |||||||||||||||||||||||", "", Set(), 0, "")),
SquadPositionEntry(6, SquadPositionDetail2("\\#ff0000 |||||||||||||||||||||||", "", Set(), 1, "Test")),
SquadPositionEntry(7, SquadPositionDetail2("\\#9640ff K", "", Set(), 0, "")),
SquadPositionEntry(8, SquadPositionDetail2("\\#9640ff O", "", Set(), 42771010L ,"HofD")),
SquadPositionEntry(9, SquadPositionDetail2("\\#9640ff K", "", Set(), 0, ""))
)
)
)
)
@ -3349,8 +3356,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) =>
if(deadState == DeadState.Alive) {
if(!player.Crouching && is_crouching) { //SQUAD TESTING CODE
//sendRawResponse(hex"e8030080c603c043fe")
sendRawResponse(hex"e8 0300 80 c 600408c00000001200008811f6200400144004f007a007a0069004b0069006e006700ff")
sendRawResponse(hex"e80300818800015c5189004603408c000000012000ff")
// sendResponse(SquadMembershipResponse(SquadRequestType.Invite,0,0,42771010,Some(avatar.CharId),"HofD",false,None))
// sendResponse(SquadMembershipResponse(SquadRequestType.Accept,0,0,avatar.CharId,Some(42771010),"VirusGiver",true,Some(None)))
// sendResponse(SquadMemberEvent(0,7,42771010,0,Some("HofD"),Some(7),Some(529745)))