mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-04-22 20:35:21 +00:00
initial work and dummy tests for SquadDetailDefinitionUpdateMessage packet; 46-bit certification encoding; indicating four previously unhandled SquadAction types; the squad leader is allowed to move around his squad
This commit is contained in:
parent
ceb145d94f
commit
5c433204cf
10 changed files with 582 additions and 129 deletions
|
|
@ -7,7 +7,7 @@ class Member {
|
||||||
//about the position to be filled
|
//about the position to be filled
|
||||||
private var role : String = ""
|
private var role : String = ""
|
||||||
private var orders : String = ""
|
private var orders : String = ""
|
||||||
private var restrictions : Set[CertificationType.Value] = Set()
|
private var requirements : Set[CertificationType.Value] = Set()
|
||||||
//about the individual filling the position
|
//about the individual filling the position
|
||||||
private var name : String = ""
|
private var name : String = ""
|
||||||
private var health : Int = 0
|
private var health : Int = 0
|
||||||
|
|
@ -29,11 +29,11 @@ class Member {
|
||||||
Orders
|
Orders
|
||||||
}
|
}
|
||||||
|
|
||||||
def Restrictions : Set[CertificationType.Value] = restrictions
|
def Requirements : Set[CertificationType.Value] = requirements
|
||||||
|
|
||||||
def Restrictions_=(requirements : Set[CertificationType.Value]) = {
|
def Requirements_=(req : Set[CertificationType.Value]) : Set[CertificationType.Value] = {
|
||||||
restrictions = requirements
|
requirements = req
|
||||||
Restrictions
|
Requirements
|
||||||
}
|
}
|
||||||
|
|
||||||
def Name : String = name
|
def Name : String = name
|
||||||
|
|
@ -73,7 +73,7 @@ class Member {
|
||||||
|
|
||||||
def Close() : Unit = {
|
def Close() : Unit = {
|
||||||
role = ""
|
role = ""
|
||||||
restrictions = Set()
|
requirements = Set()
|
||||||
//about the individual filling the position
|
//about the individual filling the position
|
||||||
name = ""
|
name = ""
|
||||||
health = 0
|
health = 0
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ class Squad(squadId : PlanetSideGUID, alignment : PlanetSideEmpire.Value) extend
|
||||||
private val membership : Array[Member] = Array.fill[Member](10)(new Member)
|
private val membership : Array[Member] = Array.fill[Member](10)(new Member)
|
||||||
private val availability : Array[Boolean] = Array.fill[Boolean](10)(true)
|
private val availability : Array[Boolean] = Array.fill[Boolean](10)(true)
|
||||||
private var listed : Boolean = false
|
private var listed : Boolean = false
|
||||||
|
private var leaderPositionIndex : Int = 0
|
||||||
|
|
||||||
override def GUID_=(d : PlanetSideGUID) : PlanetSideGUID = GUID
|
override def GUID_=(d : PlanetSideGUID) : PlanetSideGUID = GUID
|
||||||
|
|
||||||
|
|
@ -62,8 +63,17 @@ class Squad(squadId : PlanetSideGUID, alignment : PlanetSideEmpire.Value) extend
|
||||||
|
|
||||||
def Availability : Array[Boolean] = availability
|
def Availability : Array[Boolean] = availability
|
||||||
|
|
||||||
|
def LeaderPositionIndex : Int = leaderPositionIndex
|
||||||
|
|
||||||
|
def LeaderPositionIndex_=(position : Int) : Int = {
|
||||||
|
if(availability.lift(position).contains(true)) {
|
||||||
|
leaderPositionIndex = position
|
||||||
|
}
|
||||||
|
LeaderPositionIndex
|
||||||
|
}
|
||||||
|
|
||||||
def Leader : String = {
|
def Leader : String = {
|
||||||
membership.headOption match {
|
membership.lift(leaderPositionIndex) match {
|
||||||
case Some(member) =>
|
case Some(member) =>
|
||||||
member.Name
|
member.Name
|
||||||
case None =>
|
case None =>
|
||||||
|
|
|
||||||
|
|
@ -592,7 +592,7 @@ object GamePacketOpcode extends Enumeration {
|
||||||
case 0xe6 => game.ReplicationStreamMessage.decode
|
case 0xe6 => game.ReplicationStreamMessage.decode
|
||||||
case 0xe7 => game.SquadDefinitionActionMessage.decode
|
case 0xe7 => game.SquadDefinitionActionMessage.decode
|
||||||
// 0xe8
|
// 0xe8
|
||||||
case 0xe8 => noDecoder(SquadDetailDefinitionUpdateMessage)
|
case 0xe8 => game.SquadDetailDefinitionUpdateMessage.decode
|
||||||
case 0xe9 => noDecoder(TacticsMessage)
|
case 0xe9 => noDecoder(TacticsMessage)
|
||||||
case 0xea => noDecoder(RabbitUpdateMessage)
|
case 0xea => noDecoder(RabbitUpdateMessage)
|
||||||
case 0xeb => noDecoder(SquadInvitationRequestMessage)
|
case 0xeb => noDecoder(SquadInvitationRequestMessage)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
package net.psforever.packet.game
|
package net.psforever.packet.game
|
||||||
|
|
||||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||||
|
import net.psforever.types.CertificationType
|
||||||
import scodec.bits.BitVector
|
import scodec.bits.BitVector
|
||||||
import scodec.{Attempt, Codec, Err}
|
import scodec.{Attempt, Codec, Err}
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
|
|
@ -15,12 +16,20 @@ import shapeless.{::, HNil}
|
||||||
abstract class SquadAction(val code : Int)
|
abstract class SquadAction(val code : Int)
|
||||||
|
|
||||||
object SquadAction{
|
object SquadAction{
|
||||||
|
final case class DisplaySquad() extends SquadAction(0)
|
||||||
|
|
||||||
final case class SaveSquadDefinition() extends SquadAction(3)
|
final case class SaveSquadDefinition() extends SquadAction(3)
|
||||||
|
|
||||||
|
final case class LoadSquadDefinition() extends SquadAction(4)
|
||||||
|
|
||||||
|
final case class ListSquadDefinition(name : String) extends SquadAction(7)
|
||||||
|
|
||||||
final case class ListSquad() extends SquadAction(8)
|
final case class ListSquad() extends SquadAction(8)
|
||||||
|
|
||||||
final case class SelectRoleForYourself(state : Int) extends SquadAction(10)
|
final case class SelectRoleForYourself(state : Int) extends SquadAction(10)
|
||||||
|
|
||||||
|
final case class CancelSelectRoleForYourself(value: Long = 0) extends SquadAction(15)
|
||||||
|
|
||||||
final case class ChangeSquadPurpose(purpose : String) extends SquadAction(19)
|
final case class ChangeSquadPurpose(purpose : String) extends SquadAction(19)
|
||||||
|
|
||||||
final case class ChangeSquadZone(zone : PlanetSideZoneID) extends SquadAction(20)
|
final case class ChangeSquadZone(zone : PlanetSideZoneID) extends SquadAction(20)
|
||||||
|
|
@ -33,7 +42,7 @@ object SquadAction{
|
||||||
|
|
||||||
final case class ChangeSquadMemberRequirementsDetailedOrders(u1 : Int, orders : String) extends SquadAction(24)
|
final case class ChangeSquadMemberRequirementsDetailedOrders(u1 : Int, orders : String) extends SquadAction(24)
|
||||||
|
|
||||||
final case class ChangeSquadMemberRequirementsWeapons(u1 : Int, u2 : Long) extends SquadAction(25)
|
final case class ChangeSquadMemberRequirementsCertifications(u1 : Int, certs : Set[CertificationType.Value]) extends SquadAction(25)
|
||||||
|
|
||||||
final case class ResetAll() extends SquadAction(26)
|
final case class ResetAll() extends SquadAction(26)
|
||||||
|
|
||||||
|
|
@ -58,6 +67,13 @@ object SquadAction{
|
||||||
object Codecs {
|
object Codecs {
|
||||||
private val everFailCondition = conditional(included = false, bool)
|
private val everFailCondition = conditional(included = false, bool)
|
||||||
|
|
||||||
|
val displaySquadCodec = everFailCondition.xmap[DisplaySquad] (
|
||||||
|
_ => DisplaySquad(),
|
||||||
|
{
|
||||||
|
case DisplaySquad() => None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
val saveSquadDefinitionCodec = everFailCondition.xmap[SaveSquadDefinition] (
|
val saveSquadDefinitionCodec = everFailCondition.xmap[SaveSquadDefinition] (
|
||||||
_ => SaveSquadDefinition(),
|
_ => SaveSquadDefinition(),
|
||||||
{
|
{
|
||||||
|
|
@ -65,6 +81,20 @@ object SquadAction{
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val loadSquadDefinitionCodec = everFailCondition.xmap[LoadSquadDefinition] (
|
||||||
|
_ => LoadSquadDefinition(),
|
||||||
|
{
|
||||||
|
case LoadSquadDefinition() => None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val listSquadDefinitionCodec = PacketHelpers.encodedWideStringAligned(6).xmap[ListSquadDefinition] (
|
||||||
|
text => ListSquadDefinition(text),
|
||||||
|
{
|
||||||
|
case ListSquadDefinition(text) => text
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
val listSquadCodec = everFailCondition.xmap[ListSquad] (
|
val listSquadCodec = everFailCondition.xmap[ListSquad] (
|
||||||
_ => ListSquad(),
|
_ => ListSquad(),
|
||||||
{
|
{
|
||||||
|
|
@ -79,6 +109,13 @@ object SquadAction{
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val cancelSelectRoleForYourselfCodec = uint32.xmap[CancelSelectRoleForYourself] (
|
||||||
|
value => CancelSelectRoleForYourself(value),
|
||||||
|
{
|
||||||
|
case CancelSelectRoleForYourself(value) => value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
val changeSquadPurposeCodec = PacketHelpers.encodedWideStringAligned(6).xmap[ChangeSquadPurpose] (
|
val changeSquadPurposeCodec = PacketHelpers.encodedWideStringAligned(6).xmap[ChangeSquadPurpose] (
|
||||||
purpose => ChangeSquadPurpose(purpose),
|
purpose => ChangeSquadPurpose(purpose),
|
||||||
{
|
{
|
||||||
|
|
@ -125,12 +162,14 @@ object SquadAction{
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
val changeSquadMemberRequirementsWeaponsCodec = (uint4 :: ulongL(46)).xmap[ChangeSquadMemberRequirementsWeapons] (
|
val changeSquadMemberRequirementsCertificationsCodec = (uint4 :: ulongL(46)).xmap[ChangeSquadMemberRequirementsCertifications] (
|
||||||
{
|
{
|
||||||
case u1 :: u2 :: HNil => ChangeSquadMemberRequirementsWeapons(u1, u2)
|
case u1 :: u2 :: HNil =>
|
||||||
|
ChangeSquadMemberRequirementsCertifications(u1, CertificationType.fromEncodedLong(u2))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case ChangeSquadMemberRequirementsWeapons(u1, u2) => u1 :: u2 :: HNil
|
case ChangeSquadMemberRequirementsCertifications(u1, u2) =>
|
||||||
|
u1 :: CertificationType.toEncodedLong(u2) :: HNil
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -219,11 +258,11 @@ object SquadAction{
|
||||||
* The `action` code indicates the format of the remainder data in the packet.
|
* The `action` code indicates the format of the remainder data in the packet.
|
||||||
* The following formats are translated; their purposes are listed:<br>
|
* The following formats are translated; their purposes are listed:<br>
|
||||||
* `(None)`<br>
|
* `(None)`<br>
|
||||||
* `0 ` - UNKNOWN<br>
|
* `0 ` - Display Squad<br>
|
||||||
* `1 ` - UNKNOWN<br>
|
* `1 ` - UNKNOWN<br>
|
||||||
* `2 ` - UNKNOWN<br>
|
* `2 ` - UNKNOWN<br>
|
||||||
* `3 ` - Save Squad Definition<br>
|
* `3 ` - Save Squad Definition<br>
|
||||||
* `4 ` - UNKNOWN<br>
|
* `4 ` - Load Squad Definition<br>
|
||||||
* `6 ` - UNKNOWN<br>
|
* `6 ` - UNKNOWN<br>
|
||||||
* `8 ` - List Squad<br>
|
* `8 ` - List Squad<br>
|
||||||
* `9 ` - UNKNOWN<br>
|
* `9 ` - UNKNOWN<br>
|
||||||
|
|
@ -251,10 +290,10 @@ object SquadAction{
|
||||||
* `Long`<br>
|
* `Long`<br>
|
||||||
* `13` - UNKNOWN<br>
|
* `13` - UNKNOWN<br>
|
||||||
* `14` - UNKNOWN<br>
|
* `14` - UNKNOWN<br>
|
||||||
* `15` - UNKNOWN<br>
|
* `15` - Select this Role for Yourself<br>
|
||||||
* `37` - UNKNOWN<br>
|
* `37` - UNKNOWN<br>
|
||||||
* `String`<br>
|
* `String`<br>
|
||||||
* `7 ` - UNKNOWN<br>
|
* `7 ` - List Squad Definition<br>
|
||||||
* `19` - (Squad leader) Change Squad Purpose<br>
|
* `19` - (Squad leader) Change Squad Purpose<br>
|
||||||
* `Int :: Long`<br>
|
* `Int :: Long`<br>
|
||||||
* `12` - UNKNOWN<br>
|
* `12` - UNKNOWN<br>
|
||||||
|
|
@ -293,16 +332,20 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
|
||||||
import SquadAction.Codecs._
|
import SquadAction.Codecs._
|
||||||
import scala.annotation.switch
|
import scala.annotation.switch
|
||||||
((code : @switch) match {
|
((code : @switch) match {
|
||||||
|
case 0 => displaySquadCodec
|
||||||
case 3 => saveSquadDefinitionCodec
|
case 3 => saveSquadDefinitionCodec
|
||||||
|
case 4 => loadSquadDefinitionCodec
|
||||||
|
case 7 => listSquadDefinitionCodec
|
||||||
case 8 => listSquadCodec
|
case 8 => listSquadCodec
|
||||||
case 10 => selectRoleForYourselfCodec
|
case 10 => selectRoleForYourselfCodec
|
||||||
|
case 15 => cancelSelectRoleForYourselfCodec
|
||||||
case 19 => changeSquadPurposeCodec
|
case 19 => changeSquadPurposeCodec
|
||||||
case 20 => changeSquadZoneCodec
|
case 20 => changeSquadZoneCodec
|
||||||
case 21 => closeSquadMemberPositionCodec
|
case 21 => closeSquadMemberPositionCodec
|
||||||
case 22 => addSquadMemberPositionCodec
|
case 22 => addSquadMemberPositionCodec
|
||||||
case 23 => changeSquadMemberRequirementsRoleCodec
|
case 23 => changeSquadMemberRequirementsRoleCodec
|
||||||
case 24 => changeSquadMemberRequirementsDetailedOrdersCodec
|
case 24 => changeSquadMemberRequirementsDetailedOrdersCodec
|
||||||
case 25 => changeSquadMemberRequirementsWeaponsCodec
|
case 25 => changeSquadMemberRequirementsCertificationsCodec
|
||||||
case 26 => resetAllCodec
|
case 26 => resetAllCodec
|
||||||
case 28 => autoApproveInvitationRequestsCodec
|
case 28 => autoApproveInvitationRequestsCodec
|
||||||
case 31 => locationFollowsSquadLeadCodec
|
case 31 => locationFollowsSquadLeadCodec
|
||||||
|
|
@ -310,8 +353,8 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
|
||||||
case 35 => cancelSquadSearchCodec
|
case 35 => cancelSquadSearchCodec
|
||||||
case 40 => findLfsSoldiersForRoleCodec
|
case 40 => findLfsSoldiersForRoleCodec
|
||||||
case 41 => cancelFindCodec
|
case 41 => cancelFindCodec
|
||||||
case 0 | 1 | 2 | 4 | 6 | 7 | 9 |
|
case 1 | 2 | 6 | 9 |
|
||||||
11 | 12 | 13 | 14 | 15 | 16 |
|
11 | 12 | 13 | 14 | 16 |
|
||||||
17 | 18 | 29 | 30 | 33 | 36 |
|
17 | 18 | 29 | 30 | 33 | 36 |
|
||||||
37 | 38 | 42 | 43 => unknownCodec(code)
|
37 | 38 | 42 | 43 => unknownCodec(code)
|
||||||
case _ => failureCodec(code)
|
case _ => failureCodec(code)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
// Copyright (c) 2019 PSForever
|
||||||
|
package net.psforever.packet.game
|
||||||
|
|
||||||
|
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||||
|
import net.psforever.types.CertificationType
|
||||||
|
import scodec.bits.BitVector
|
||||||
|
import scodec.{Attempt, Codec, Err}
|
||||||
|
import scodec.codecs._
|
||||||
|
import shapeless.HNil
|
||||||
|
|
||||||
|
import scala.annotation.tailrec
|
||||||
|
|
||||||
|
final case class SquadPositionDetail(is_closed : Boolean,
|
||||||
|
role : String,
|
||||||
|
detailed_orders : String,
|
||||||
|
requirements : Set[CertificationType.Value],
|
||||||
|
char_id : Long,
|
||||||
|
name : String)
|
||||||
|
|
||||||
|
final case class SquadDetailDefinitionUpdateMessage(guid : PlanetSideGUID,
|
||||||
|
unk : BitVector,
|
||||||
|
leader_name : String,
|
||||||
|
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, "")
|
||||||
|
|
||||||
|
private def reliableNameHash(name : String) : Long = {
|
||||||
|
val hash = name.hashCode.toLong
|
||||||
|
if(hash < 0) {
|
||||||
|
-1L * hash
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def apply() : SquadPositionDetail = SquadPositionDetail(is_closed = false, "", "", Set.empty, 0L, "")
|
||||||
|
|
||||||
|
def apply(name : String) : SquadPositionDetail = SquadPositionDetail(is_closed = false, "", "", Set.empty, reliableNameHash(name), name)
|
||||||
|
|
||||||
|
def apply(role : String, detailed_orders : String) : SquadPositionDetail = SquadPositionDetail(is_closed = false, role, detailed_orders, Set.empty, 0L, "")
|
||||||
|
|
||||||
|
def apply(role : String, detailed_orders : String, requirements : Set[CertificationType.Value]) : SquadPositionDetail = SquadPositionDetail(is_closed = false, role, detailed_orders, requirements, 0L, "")
|
||||||
|
|
||||||
|
def apply(role : String, detailed_orders : String, name : String) : SquadPositionDetail = SquadPositionDetail(is_closed = false, role, detailed_orders, Set.empty, reliableNameHash(name), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefinitionUpdateMessage] {
|
||||||
|
final val defaultRequirements : Set[CertificationType.Value] = Set(
|
||||||
|
CertificationType.StandardAssault,
|
||||||
|
CertificationType.StandardExoSuit,
|
||||||
|
CertificationType.AgileExoSuit
|
||||||
|
)
|
||||||
|
|
||||||
|
def apply(guid : PlanetSideGUID, leader_name : String, task : String, zone_id : PlanetSideZoneID, member_info : List[SquadPositionDetail]) : SquadDetailDefinitionUpdateMessage = {
|
||||||
|
import scodec.bits._
|
||||||
|
SquadDetailDefinitionUpdateMessage(guid, hex"080000000000000000000".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) ::
|
||||||
|
uint8 ::
|
||||||
|
uint4 ::
|
||||||
|
bits(83) :: //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 :: _ :: _ :: _ :: _ :: leader :: task :: zone :: _ :: Some(member_list) :: HNil =>
|
||||||
|
Attempt.Successful(SquadDetailDefinitionUpdateMessage(guid, leader, task, zone, unlinkMemberList(member_list)))
|
||||||
|
case data =>
|
||||||
|
Attempt.failure(Err(s"can not get squad detail definition from data $data"))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case SquadDetailDefinitionUpdateMessage(guid, unk, leader, task, zone, member_list) =>
|
||||||
|
Attempt.Successful(guid :: 132 :: 8 :: unk.take(83) :: 0 :: leader :: task :: zone :: 4983296 :: Some(linkMemberList(member_list.reverse)) :: HNil)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,8 @@ package net.psforever.types
|
||||||
|
|
||||||
import net.psforever.packet.PacketHelpers
|
import net.psforever.packet.PacketHelpers
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
|
|
||||||
|
import scala.annotation.tailrec
|
||||||
/**
|
/**
|
||||||
* An `Enumeration` of the available certifications.<br>
|
* An `Enumeration` of the available certifications.<br>
|
||||||
* <br>
|
* <br>
|
||||||
|
|
@ -76,4 +78,59 @@ object CertificationType extends Enumeration {
|
||||||
= Value
|
= Value
|
||||||
|
|
||||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint8L)
|
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint8L)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Certifications are often stored, in object form, as a 46-member collection.
|
||||||
|
* Encode a subset of certification values for packet form.
|
||||||
|
* @see `ChangeSquadMemberRequirementsCertifications`
|
||||||
|
* @see `changeSquadMemberRequirementsCertificationsCodec`
|
||||||
|
* @param certs the certifications, as a sequence of values
|
||||||
|
* @return the certifications, as a single value
|
||||||
|
*/
|
||||||
|
def toEncodedLong(certs : Set[CertificationType.Value]) : Long = {
|
||||||
|
certs
|
||||||
|
.map{ cert => math.pow(2, cert.id).toLong }
|
||||||
|
.foldLeft(0L)(_ + _)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Certifications are often stored, in packet form, as an encoded little-endian `46u` value.
|
||||||
|
* Decode a representative value into a subset of certification values.
|
||||||
|
* @see `ChangeSquadMemberRequirementsCertifications`
|
||||||
|
* @see `changeSquadMemberRequirementsCertificationsCodec`
|
||||||
|
* @see `fromEncodedLong(Long, Iterable[Long], Set[CertificationType.Value])`
|
||||||
|
* @param certs the certifications, as a single value
|
||||||
|
* @return the certifications, as a sequence of values
|
||||||
|
*/
|
||||||
|
def fromEncodedLong(certs : Long) : Set[CertificationType.Value] = {
|
||||||
|
recursiveFromEncodedLong(
|
||||||
|
certs,
|
||||||
|
CertificationType.values.map{ cert => math.pow(2, cert.id).toLong }.toSeq.sorted
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Certifications are often stored, in packet form, as an encoded little-endian `46u` value.
|
||||||
|
* Decode a representative value into a subset of certification values
|
||||||
|
* by repeatedly finding the partition point of values less than a specific one,
|
||||||
|
* providing for both the next lowest value (to subtract) and an index (of a certification).
|
||||||
|
* @see `ChangeSquadMemberRequirementsCertifications`
|
||||||
|
* @see `changeSquadMemberRequirementsCertificationsCodec`
|
||||||
|
* @see `fromEncodedLong(Long)`
|
||||||
|
* @param certs the certifications, as a single value
|
||||||
|
* @param splitList the available values to partition
|
||||||
|
* @param out the accumulating certification values;
|
||||||
|
* defaults to an empty set
|
||||||
|
* @return the certifications, as a sequence of values
|
||||||
|
*/
|
||||||
|
@tailrec
|
||||||
|
private def recursiveFromEncodedLong(certs : Long, splitList : Iterable[Long], out : Set[CertificationType.Value] = Set.empty) : Set[CertificationType.Value] = {
|
||||||
|
if(certs == 0 || splitList.isEmpty) {
|
||||||
|
out
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val (less, _) = splitList.partition(_ <= certs)
|
||||||
|
recursiveFromEncodedLong(certs - less.last, less, out ++ Set(CertificationType(less.size - 1)))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ class SquadService extends Actor {
|
||||||
case None =>
|
case None =>
|
||||||
val id = GetNextSquadId()
|
val id = GetNextSquadId()
|
||||||
val squad = new Squad(id, faction)
|
val squad = new Squad(id, faction)
|
||||||
val leadPosition = squad.Membership(0)
|
val leadPosition = squad.Membership(squad.LeaderPositionIndex)
|
||||||
leadPosition.Name = name
|
leadPosition.Name = name
|
||||||
leadPosition.Health = player.Health
|
leadPosition.Health = player.Health
|
||||||
leadPosition.Armor = player.Armor
|
leadPosition.Armor = player.Armor
|
||||||
|
|
@ -80,7 +80,7 @@ class SquadService extends Actor {
|
||||||
val path = s"$name/Squad"
|
val path = s"$name/Squad"
|
||||||
val who = sender()
|
val who = sender()
|
||||||
log.info(s"$who has joined $path")
|
log.info(s"$who has joined $path")
|
||||||
SquadEvents.subscribe(who, path)
|
SquadEvents.subscribe(who, path) //TODO squad-specific switchboard
|
||||||
//check for renewable squad information
|
//check for renewable squad information
|
||||||
memberToSquad.get(name) match {
|
memberToSquad.get(name) match {
|
||||||
case None => ;
|
case None => ;
|
||||||
|
|
@ -101,99 +101,107 @@ class SquadService extends Actor {
|
||||||
val squad = GetSquadFromPlayer(tplayer)
|
val squad = GetSquadFromPlayer(tplayer)
|
||||||
val member = squad.Membership.find(_.Name == tplayer.Name).get //should never fail
|
val member = squad.Membership.find(_.Name == tplayer.Name).get //should never fail
|
||||||
member.ZoneId = zone_ordinal_number //TODO improve this requirement
|
member.ZoneId = zone_ordinal_number //TODO improve this requirement
|
||||||
var listingChanged : List[Int] = Nil
|
if(tplayer.Name.equals(squad.Leader)) {
|
||||||
action match {
|
var listingChanged : List[Int] = Nil
|
||||||
case ChangeSquadPurpose(purpose) =>
|
action match {
|
||||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed his squad's task to $purpose")
|
case ChangeSquadPurpose(purpose) =>
|
||||||
squad.Description = purpose
|
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed his squad's task to $purpose")
|
||||||
listingChanged = List(SquadInfo.Field.Task)
|
squad.Description = purpose
|
||||||
|
listingChanged = List(SquadInfo.Field.Task)
|
||||||
|
|
||||||
case ChangeSquadZone(zone) =>
|
case ChangeSquadZone(zone) =>
|
||||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed his squad's ops zone to $zone")
|
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed his squad's ops zone to $zone")
|
||||||
squad.ZoneId = zone.zoneId.toInt
|
squad.ZoneId = zone.zoneId.toInt
|
||||||
listingChanged = List(SquadInfo.Field.ZoneId)
|
listingChanged = List(SquadInfo.Field.ZoneId)
|
||||||
|
|
||||||
case CloseSquadMemberPosition(position) =>
|
case CloseSquadMemberPosition(position) =>
|
||||||
if(position > 0) {
|
if(position != squad.LeaderPositionIndex) {
|
||||||
|
squad.Availability.lift(position) match {
|
||||||
|
case Some(true) =>
|
||||||
|
squad.Availability.update(position, false)
|
||||||
|
log.info(s"${tplayer.Name}-${tplayer.Faction} has closed the #$position position in his squad")
|
||||||
|
val memberPosition = squad.Membership(position)
|
||||||
|
listingChanged = if(memberPosition.Name.nonEmpty) {
|
||||||
|
List(SquadInfo.Field.Size, SquadInfo.Field.Capacity)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
List(SquadInfo.Field.Capacity)
|
||||||
|
}
|
||||||
|
memberPosition.Close()
|
||||||
|
case Some(false) => ;
|
||||||
|
case None => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.warn(s"can not close the leader position in squad-${squad.GUID.guid}")
|
||||||
|
}
|
||||||
|
|
||||||
|
case AddSquadMemberPosition(position) =>
|
||||||
|
squad.Availability.lift(position) match {
|
||||||
|
case Some(false) =>
|
||||||
|
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened the #$position position in his squad")
|
||||||
|
squad.Availability.update(position, true)
|
||||||
|
listingChanged = List(SquadInfo.Field.Capacity)
|
||||||
|
case Some(true) => ;
|
||||||
|
case None => ;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ChangeSquadMemberRequirementsRole(position, role) =>
|
||||||
squad.Availability.lift(position) match {
|
squad.Availability.lift(position) match {
|
||||||
case Some(true) =>
|
case Some(true) =>
|
||||||
squad.Availability.update(position, false)
|
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the role of squad position #$position")
|
||||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has closed the #$position position in his squad")
|
squad.Membership(position).Role = role
|
||||||
val memberPosition = squad.Membership(position)
|
|
||||||
listingChanged = if(memberPosition.Name.nonEmpty) {
|
|
||||||
List(SquadInfo.Field.Size, SquadInfo.Field.Capacity)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
List(SquadInfo.Field.Capacity)
|
|
||||||
}
|
|
||||||
memberPosition.Close()
|
|
||||||
case Some(false) => ;
|
case Some(false) => ;
|
||||||
case None => ;
|
case None => ;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.warn(s"can not close the lead position in squad-${squad.GUID.guid}")
|
|
||||||
}
|
|
||||||
|
|
||||||
case AddSquadMemberPosition(position) =>
|
case ChangeSquadMemberRequirementsDetailedOrders(position, orders) =>
|
||||||
squad.Availability.lift(position) match {
|
squad.Availability.lift(position) match {
|
||||||
case Some(false) =>
|
case Some(true) =>
|
||||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened the #$position position in his squad")
|
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the orders for squad position #$position")
|
||||||
squad.Availability.update(position, true)
|
squad.Membership(position).Orders = orders
|
||||||
listingChanged = List(SquadInfo.Field.Capacity)
|
case Some(false) => ;
|
||||||
case Some(true) => ;
|
case None => ;
|
||||||
case None => ;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
case ChangeSquadMemberRequirementsRole(position, role) =>
|
case ChangeSquadMemberRequirementsCertifications(position, certs) =>
|
||||||
squad.Availability.lift(position) match {
|
squad.Availability.lift(position) match {
|
||||||
case Some(true) =>
|
case Some(true) =>
|
||||||
squad.Membership(position).Role = role
|
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the requirements for squad position #$position")
|
||||||
case Some(false) => ;
|
squad.Membership(position).Requirements = certs
|
||||||
case None => ;
|
case Some(false) => ;
|
||||||
}
|
case None => ;
|
||||||
|
}
|
||||||
|
|
||||||
case ChangeSquadMemberRequirementsDetailedOrders(position, orders) =>
|
case ListSquad() =>
|
||||||
squad.Availability.lift(position) match {
|
if(!squad.Listed) {
|
||||||
case Some(true) =>
|
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened recruitment for his squad")
|
||||||
squad.Membership(position).Orders = orders
|
squad.Listed = true
|
||||||
case Some(false) => ;
|
}
|
||||||
case None => ;
|
|
||||||
}
|
|
||||||
|
|
||||||
case ListSquad() =>
|
case ResetAll() =>
|
||||||
if(!squad.Listed) {
|
squad.Description = ""
|
||||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened recruitment for his squad")
|
squad.ZoneId = None
|
||||||
squad.Listed = true
|
squad.Availability.indices.foreach { i =>
|
||||||
}
|
squad.Availability.update(i, true)
|
||||||
|
}
|
||||||
case ResetAll() =>
|
|
||||||
squad.Description = ""
|
|
||||||
squad.ZoneId = None
|
|
||||||
squad.Availability.indices.foreach { i =>
|
|
||||||
squad.Availability.update(i, true)
|
|
||||||
}
|
|
||||||
//TODO squad members?
|
//TODO squad members?
|
||||||
|
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
//queue updates
|
//queue updates
|
||||||
if(squad.Listed) {
|
if(squad.Listed) {
|
||||||
val entry = SquadService.Publish(squad)
|
val entry = SquadService.Publish(squad)
|
||||||
val faction = squad.Faction
|
val faction = squad.Faction
|
||||||
val factionListings = publishedLists(faction)
|
val factionListings = publishedLists(faction)
|
||||||
factionListings.find(info => {
|
factionListings.find(info => {
|
||||||
info.squad_guid match {
|
info.squad_guid match {
|
||||||
case Some(guid) => guid == squad.GUID
|
case Some(guid) => guid == squad.GUID
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
}) match {
|
}) match {
|
||||||
case Some(listedSquad) =>
|
case Some(listedSquad) =>
|
||||||
val index = factionListings.indexOf(listedSquad)
|
val index = factionListings.indexOf(listedSquad)
|
||||||
if(squad.Listed) {
|
|
||||||
//squad information update
|
|
||||||
log.info(s"Squad will be updated")
|
|
||||||
factionListings(index) = entry
|
|
||||||
val changes = if(listingChanged.nonEmpty) {
|
val changes = if(listingChanged.nonEmpty) {
|
||||||
SquadService.Differences(listingChanged, entry)
|
SquadService.Differences(listingChanged, entry)
|
||||||
}
|
}
|
||||||
|
|
@ -201,27 +209,29 @@ class SquadService extends Actor {
|
||||||
SquadService.Differences(listedSquad, entry)
|
SquadService.Differences(listedSquad, entry)
|
||||||
}
|
}
|
||||||
if(changes != SquadInfo.Blank) {
|
if(changes != SquadInfo.Blank) {
|
||||||
|
//squad information update
|
||||||
|
log.info(s"Squad will be updated")
|
||||||
|
factionListings(index) = entry
|
||||||
SquadEvents.publish(
|
SquadEvents.publish(
|
||||||
SquadServiceResponse(s"$faction/Squad", SquadResponse.Update(Seq((index, changes))))
|
SquadServiceResponse(s"$faction/Squad", SquadResponse.Update(Seq((index, changes))))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
else {
|
//remove squad from listing
|
||||||
//remove squad from listing
|
log.info(s"Squad will be removed")
|
||||||
log.info(s"Squad will be removed")
|
factionListings.remove(index)
|
||||||
factionListings.remove(index)
|
SquadEvents.publish(
|
||||||
|
SquadServiceResponse(s"$faction/Squad", SquadResponse.Remove(Seq(index)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case None =>
|
||||||
|
//first time being published
|
||||||
|
log.info(s"Squad will be introduced")
|
||||||
|
factionListings += SquadService.Publish(squad)
|
||||||
SquadEvents.publish(
|
SquadEvents.publish(
|
||||||
SquadServiceResponse(s"$faction/Squad", SquadResponse.Remove(Seq(index)))
|
SquadServiceResponse(s"$faction/Squad", SquadResponse.Init(factionListings.toVector))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
case None if squad.Listed =>
|
|
||||||
log.info(s"Squad will be introduced")
|
|
||||||
//first time being published?
|
|
||||||
factionListings += SquadService.Publish(squad)
|
|
||||||
SquadEvents.publish(
|
|
||||||
SquadServiceResponse(s"$faction/Squad", SquadResponse.Init(factionListings.toVector))
|
|
||||||
)
|
|
||||||
case _ => ;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,18 +254,18 @@ object SquadService {
|
||||||
|
|
||||||
def Differences(updates : List[Int], info : SquadInfo) : SquadInfo = {
|
def Differences(updates : List[Int], info : SquadInfo) : SquadInfo = {
|
||||||
if(updates.nonEmpty) {
|
if(updates.nonEmpty) {
|
||||||
|
val list = Seq(
|
||||||
|
SquadInfo.Blank, //must be index-0
|
||||||
|
SquadInfo(info.leader, None, None, None, None),
|
||||||
|
SquadInfo(None, info.task, None, None, None),
|
||||||
|
SquadInfo(None, None, info.zone_id, None, None),
|
||||||
|
SquadInfo(None, None, None, info.size, None),
|
||||||
|
SquadInfo(None, None, None, None, info.capacity)
|
||||||
|
)
|
||||||
var out = SquadInfo.Blank
|
var out = SquadInfo.Blank
|
||||||
({
|
updates
|
||||||
val list = Seq(
|
.map(i => list(i))
|
||||||
SquadInfo.Blank, //must be index-0
|
.filterNot { _ == SquadInfo.Blank }
|
||||||
SquadInfo(info.leader, None, None, None, None),
|
|
||||||
SquadInfo(None, info.task, None, None, None),
|
|
||||||
SquadInfo(None, None, info.zone_id, None, None),
|
|
||||||
SquadInfo(None, None, None, info.size, None),
|
|
||||||
SquadInfo(None, None, None, None, info.capacity)
|
|
||||||
)
|
|
||||||
updates.map(i => list(i)).filterNot { _ == SquadInfo.Blank }
|
|
||||||
}) //ignore what code inspection tells you - the parenthesis is necessary
|
|
||||||
.foreach(sinfo => out = out And sinfo )
|
.foreach(sinfo => out = out And sinfo )
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,15 @@ import org.specs2.mutable._
|
||||||
import net.psforever.packet._
|
import net.psforever.packet._
|
||||||
import net.psforever.packet.game.SquadAction._
|
import net.psforever.packet.game.SquadAction._
|
||||||
import net.psforever.packet.game._
|
import net.psforever.packet.game._
|
||||||
|
import net.psforever.types.CertificationType
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
|
|
||||||
class SquadDefinitionActionMessageTest extends Specification {
|
class SquadDefinitionActionMessageTest extends Specification {
|
||||||
//local test data; note that the second field - unk1 - is always blank for now, but that probably changes
|
//local test data; note that the second field - unk1 - is always blank for now, but that probably changes
|
||||||
|
val string_00 = hex"e7 00 0c0000" //guid: 3
|
||||||
val string_03 = hex"E7 0c 0000c0" //index: 3
|
val string_03 = hex"E7 0c 0000c0" //index: 3
|
||||||
|
val string_04 = hex"E7 10 0000c0" //index: 3
|
||||||
|
val string_07 = hex"e7 1c 0000e68043006f0070007300200061006e00640020004d0069006c006900740061007200790020004f006600660069006300650072007300"
|
||||||
val string_08 = hex"E7 20 000000"
|
val string_08 = hex"E7 20 000000"
|
||||||
val string_10 = hex"E7 28 000004" //index: 1
|
val string_10 = hex"E7 28 000004" //index: 1
|
||||||
val string_19 = hex"E7 4c 0000218041002d005400650061006d00" //"A-Team"
|
val string_19 = hex"E7 4c 0000218041002d005400650061006d00" //"A-Team"
|
||||||
|
|
@ -34,6 +38,17 @@ class SquadDefinitionActionMessageTest extends Specification {
|
||||||
val string_43 = hex"e7 ac 000000"
|
val string_43 = hex"e7 ac 000000"
|
||||||
val string_failure = hex"E7 ff"
|
val string_failure = hex"E7 ff"
|
||||||
|
|
||||||
|
"decode (00)" in {
|
||||||
|
PacketCoding.DecodePacket(string_00).require match {
|
||||||
|
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||||
|
unk1 mustEqual 3
|
||||||
|
unk2 mustEqual 0
|
||||||
|
action mustEqual DisplaySquad()
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
"decode (03)" in {
|
"decode (03)" in {
|
||||||
PacketCoding.DecodePacket(string_03).require match {
|
PacketCoding.DecodePacket(string_03).require match {
|
||||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||||
|
|
@ -45,6 +60,28 @@ class SquadDefinitionActionMessageTest extends Specification {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"decode (03)" in {
|
||||||
|
PacketCoding.DecodePacket(string_04).require match {
|
||||||
|
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||||
|
unk1 mustEqual 0
|
||||||
|
unk2 mustEqual 3
|
||||||
|
action mustEqual LoadSquadDefinition()
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"decode (07)" in {
|
||||||
|
PacketCoding.DecodePacket(string_07).require match {
|
||||||
|
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||||
|
unk1 mustEqual 0
|
||||||
|
unk2 mustEqual 3
|
||||||
|
action mustEqual ListSquadDefinition("Cops and Military Officers")
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
"decode (08)" in {
|
"decode (08)" in {
|
||||||
PacketCoding.DecodePacket(string_08).require match {
|
PacketCoding.DecodePacket(string_08).require match {
|
||||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||||
|
|
@ -138,7 +175,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
||||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||||
unk1 mustEqual 0
|
unk1 mustEqual 0
|
||||||
unk2 mustEqual 0
|
unk2 mustEqual 0
|
||||||
action mustEqual ChangeSquadMemberRequirementsWeapons(1, 536870928L)
|
action mustEqual ChangeSquadMemberRequirementsCertifications(
|
||||||
|
1,
|
||||||
|
Set(CertificationType.AntiVehicular, CertificationType.InfiltrationSuit)
|
||||||
|
)
|
||||||
case _ =>
|
case _ =>
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
|
|
@ -280,6 +320,13 @@ class SquadDefinitionActionMessageTest extends Specification {
|
||||||
PacketCoding.DecodePacket(string_failure).isFailure mustEqual true
|
PacketCoding.DecodePacket(string_failure).isFailure mustEqual true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"encode (00)" in {
|
||||||
|
val msg = SquadDefinitionActionMessage(3, 0, DisplaySquad())
|
||||||
|
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
pkt mustEqual string_00
|
||||||
|
}
|
||||||
|
|
||||||
"encode (03)" in {
|
"encode (03)" in {
|
||||||
val msg = SquadDefinitionActionMessage(0, 3, SaveSquadDefinition())
|
val msg = SquadDefinitionActionMessage(0, 3, SaveSquadDefinition())
|
||||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||||
|
|
@ -287,6 +334,20 @@ class SquadDefinitionActionMessageTest extends Specification {
|
||||||
pkt mustEqual string_03
|
pkt mustEqual string_03
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"encode (03)" in {
|
||||||
|
val msg = SquadDefinitionActionMessage(0, 3, LoadSquadDefinition())
|
||||||
|
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
pkt mustEqual string_04
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode (07)" in {
|
||||||
|
val msg = SquadDefinitionActionMessage(0, 3, ListSquadDefinition("Cops and Military Officers"))
|
||||||
|
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
pkt mustEqual string_07
|
||||||
|
}
|
||||||
|
|
||||||
"encode (08)" in {
|
"encode (08)" in {
|
||||||
val msg = SquadDefinitionActionMessage(0, 0, ListSquad())
|
val msg = SquadDefinitionActionMessage(0, 0, ListSquad())
|
||||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||||
|
|
@ -344,7 +405,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
||||||
}
|
}
|
||||||
|
|
||||||
"encode (25)" in {
|
"encode (25)" in {
|
||||||
val msg = SquadDefinitionActionMessage(0, 0, ChangeSquadMemberRequirementsWeapons(1, 536870928L))
|
val msg = SquadDefinitionActionMessage(0, 0, ChangeSquadMemberRequirementsCertifications(
|
||||||
|
1,
|
||||||
|
Set(CertificationType.AntiVehicular, CertificationType.InfiltrationSuit)
|
||||||
|
))
|
||||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
pkt mustEqual string_25
|
pkt mustEqual string_25
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright (c) 2019 PSForever
|
||||||
|
package game
|
||||||
|
|
||||||
|
import net.psforever.packet._
|
||||||
|
import net.psforever.packet.game._
|
||||||
|
import org.specs2.mutable._
|
||||||
|
import scodec.bits._
|
||||||
|
|
||||||
|
class SquadDetailDefinitionUpdateMessageTest extends Specification {
|
||||||
|
val string = hex"e80300848180038021514601288a8400420048006f0066004400bf5c0023006600660064006300300030002a002a002a005c0023003900360034003000660066003d004b004f004b002b005300500043002b0046004c0059003d005c0023006600660064006300300030002a002a002a005c002300460046003400300034003000200041006c006c002000570065006c0063006f006d006500070000009814010650005c00230066006600300030003000300020007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c008000000000800100000c00020c8c5c00230066006600640063003000300020002000200043008000000000800100000c00020c8c5c002300660066006400630030003000200020002000480080eab58a02854f0070006f006c0045000100000c00020c8d5c002300660066006400630030003000200020002000200049008072d47a028b42006f006200610046003300740074003900300037000100000c00020c8c5c0023006600660064006300300030002000200020004e008000000000800100000c00020c8c5c00230066006600640063003000300020002000200041008000000000800100000c00020ca05c00230066006600300030003000300020007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c008000000000800100000c00020c8c5c0023003900360034003000660066002000200020004b008000000000800100000c00020c8c5c0023003900360034003000660066002000200020004f008042a28c028448006f00660044000100000c00020c8c5c0023003900360034003000660066002000200020004b008000000000800100000c0000"
|
||||||
|
|
||||||
|
"SquadDetailDefinitionUpdateMessage" should {
|
||||||
|
"decode" in {
|
||||||
|
PacketCoding.DecodePacket(string).require match {
|
||||||
|
case SquadDetailDefinitionUpdateMessage(guid, unk, leader, task, zone, member_info) =>
|
||||||
|
ok
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode" in {
|
||||||
|
val msg = SquadDetailDefinitionUpdateMessage(
|
||||||
|
PlanetSideGUID(3),
|
||||||
|
"HofD",
|
||||||
|
"\\#ffdc00***\\#9640ff=KOK+SPC+FLY=\\#ffdc00***\\#FF4040 All Welcome",
|
||||||
|
PlanetSideZoneID(7),
|
||||||
|
List(
|
||||||
|
SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", ""),
|
||||||
|
SquadPositionDetail("\\#ffdc00 C", ""),
|
||||||
|
SquadPositionDetail("\\#ffdc00 H", "", "OpoIE"),
|
||||||
|
SquadPositionDetail("\\#ffdc00 I", "", "BobaF3tt907"),
|
||||||
|
SquadPositionDetail("\\#ffdc00 N", ""),
|
||||||
|
SquadPositionDetail("\\#ffdc00 A", ""),
|
||||||
|
SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", ""),
|
||||||
|
SquadPositionDetail("\\#9640ff K", ""),
|
||||||
|
SquadPositionDetail("\\#9640ff O", "", "HofD"),
|
||||||
|
SquadPositionDetail("\\#9640ff K", "")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||||
|
ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2885,6 +2885,35 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
interstellarFerryTopLevelGUID = None
|
interstellarFerryTopLevelGUID = None
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
sendResponse(ReplicationStreamMessage(
|
||||||
|
5,
|
||||||
|
Some(6),
|
||||||
|
Vector(
|
||||||
|
SquadListing(0, SquadInfo(Some("xNick"), Some("FLY,ALL WELCOME!"), Some(PlanetSideZoneID(7)), Some(8), Some(10), Some(PlanetSideGUID(1)))),
|
||||||
|
SquadListing(1, SquadInfo(Some("HofD"), Some("=KOK+SPC+FLY= All Welcome"), Some(PlanetSideZoneID(7)), Some(3), Some(10), Some(PlanetSideGUID(3))))
|
||||||
|
)
|
||||||
|
))
|
||||||
|
//sendRawResponse(hex"e803008484000c800259e8809fda020043004a0069006d006d0079006e009b48006f006d006900630069006400690061006c00200053006d007500720066007300200041006e006f006e0079006d006f007500730004000000981401064580540061006e006b002000440072006900760065007200a05200650063006f006d006d0065006e00640065006400200074006f0020006800610076006500200065006e00670069006e0065006500720069006e0067002e0000000000800180000c00020c8c46007200650065006200690065002000730070006f007400cf44006f002000770068006100740065007600650072002c0020006200750074002000700072006500660065007200610062006c007900200073007400690063006b002000770069007400680020007400680065002000730071007500610064002e00200044006f006e002700740020006e00650065006400200061006e007900200073007000650063006900660069006300200063006500720074002e0096e27a0290540068006500460069006e0061006c005300740072007500670067006c0065000000000000020c8c46007200650065006200690065002000530070006f007400cf44006f002000770068006100740065007600650072002c0020006200750074002000700072006500660065007200610062006c007900200073007400690063006b002000770069007400680020007400680065002000730071007500610064002e00200044006f006e002700740020006e00650065006400200061006e007900200073007000650063006900660069006300200063006500720074002e0000000000800000000000020c8a41004d0053002000440072006900760065007200b34700690076006500200075007300200073007000610077006e00200070006f0069006e00740073002c0020006800610063006b0069006e006700200061006e006400200069006e00660069006c0020006100720065002000750073006500660075006c002e00fb02790287440030004f004d006700750079000100020c00020c8d410076006500720061006700650020004a0069006d006d007900a05200650069006e0066006f0072006300650064002000450078006f007300750069007400200077006f0075006c00640020006200650020006e0069006300650000000000800300000c00020c8b4100760065007200610067006500200042006f006200a05200650069006e0066006f0072006300650064002000450078006f007300750069007400200077006f0075006c00640020006200650020006e0069006300650000000000800300000c00020c8b410076006500720061006700650020004a006f006500a05200650069006e0066006f0072006300650064002000450078006f007300750069007400200077006f0075006c00640020006200650020006e0069006300650000000000800300000c00020c8753007500700070006f0072007400a2520065007300730075007200650063007400200073006f006c00640069006500720073002c0020006b00650065007000200075007300200061006c006900760065002e0000000000800100000c0c0a0c8845006e00670069006e00650065007200a043006f006d00620061007400200045006e00670069006e0065006500720069006e006700200077006f0075006c00640020006200650020006e0069006300650004b3d101864a0069006d006d0079006e000100000c000a0c854d0065006400690063009a4100640076002e0020004d00650064006900630061006c00200077006f0075006c00640020006200650020006e0069006300650000000000800100000c0400")
|
||||||
|
sendResponse(
|
||||||
|
SquadDetailDefinitionUpdateMessage(
|
||||||
|
PlanetSideGUID(3),
|
||||||
|
"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", "", "OpoIE"),
|
||||||
|
SquadPositionDetail("\\#ffdc00 I", "", "BobaF3tt907"),
|
||||||
|
SquadPositionDetail("\\#ffdc00 N", ""),
|
||||||
|
SquadPositionDetail("\\#ffdc00 A", ""),
|
||||||
|
SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", "Another space filler"),
|
||||||
|
SquadPositionDetail("\\#9640ff K", ""),
|
||||||
|
SquadPositionDetail("\\#9640ff O", "", "HofD"),
|
||||||
|
SquadPositionDetail("\\#9640ff K", "")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
def handleControlPkt(pkt : PlanetSideControlPacket) = {
|
def handleControlPkt(pkt : PlanetSideControlPacket) = {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue