mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-20 02:54:46 +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
|
|
@ -7,7 +7,7 @@ class Member {
|
|||
//about the position to be filled
|
||||
private var role : 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
|
||||
private var name : String = ""
|
||||
private var health : Int = 0
|
||||
|
|
@ -29,11 +29,11 @@ class Member {
|
|||
Orders
|
||||
}
|
||||
|
||||
def Restrictions : Set[CertificationType.Value] = restrictions
|
||||
def Requirements : Set[CertificationType.Value] = requirements
|
||||
|
||||
def Restrictions_=(requirements : Set[CertificationType.Value]) = {
|
||||
restrictions = requirements
|
||||
Restrictions
|
||||
def Requirements_=(req : Set[CertificationType.Value]) : Set[CertificationType.Value] = {
|
||||
requirements = req
|
||||
Requirements
|
||||
}
|
||||
|
||||
def Name : String = name
|
||||
|
|
@ -73,7 +73,7 @@ class Member {
|
|||
|
||||
def Close() : Unit = {
|
||||
role = ""
|
||||
restrictions = Set()
|
||||
requirements = Set()
|
||||
//about the individual filling the position
|
||||
name = ""
|
||||
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 availability : Array[Boolean] = Array.fill[Boolean](10)(true)
|
||||
private var listed : Boolean = false
|
||||
private var leaderPositionIndex : Int = 0
|
||||
|
||||
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 LeaderPositionIndex : Int = leaderPositionIndex
|
||||
|
||||
def LeaderPositionIndex_=(position : Int) : Int = {
|
||||
if(availability.lift(position).contains(true)) {
|
||||
leaderPositionIndex = position
|
||||
}
|
||||
LeaderPositionIndex
|
||||
}
|
||||
|
||||
def Leader : String = {
|
||||
membership.headOption match {
|
||||
membership.lift(leaderPositionIndex) match {
|
||||
case Some(member) =>
|
||||
member.Name
|
||||
case None =>
|
||||
|
|
|
|||
|
|
@ -592,7 +592,7 @@ object GamePacketOpcode extends Enumeration {
|
|||
case 0xe6 => game.ReplicationStreamMessage.decode
|
||||
case 0xe7 => game.SquadDefinitionActionMessage.decode
|
||||
// 0xe8
|
||||
case 0xe8 => noDecoder(SquadDetailDefinitionUpdateMessage)
|
||||
case 0xe8 => game.SquadDetailDefinitionUpdateMessage.decode
|
||||
case 0xe9 => noDecoder(TacticsMessage)
|
||||
case 0xea => noDecoder(RabbitUpdateMessage)
|
||||
case 0xeb => noDecoder(SquadInvitationRequestMessage)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
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._
|
||||
|
|
@ -15,12 +16,20 @@ import shapeless.{::, HNil}
|
|||
abstract class SquadAction(val code : Int)
|
||||
|
||||
object SquadAction{
|
||||
final case class DisplaySquad() extends SquadAction(0)
|
||||
|
||||
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 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 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 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)
|
||||
|
||||
|
|
@ -58,6 +67,13 @@ object SquadAction{
|
|||
object Codecs {
|
||||
private val everFailCondition = conditional(included = false, bool)
|
||||
|
||||
val displaySquadCodec = everFailCondition.xmap[DisplaySquad] (
|
||||
_ => DisplaySquad(),
|
||||
{
|
||||
case DisplaySquad() => None
|
||||
}
|
||||
)
|
||||
|
||||
val saveSquadDefinitionCodec = everFailCondition.xmap[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] (
|
||||
_ => 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] (
|
||||
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 following formats are translated; their purposes are listed:<br>
|
||||
* `(None)`<br>
|
||||
* `0 ` - UNKNOWN<br>
|
||||
* `0 ` - Display Squad<br>
|
||||
* `1 ` - UNKNOWN<br>
|
||||
* `2 ` - UNKNOWN<br>
|
||||
* `3 ` - Save Squad Definition<br>
|
||||
* `4 ` - UNKNOWN<br>
|
||||
* `4 ` - Load Squad Definition<br>
|
||||
* `6 ` - UNKNOWN<br>
|
||||
* `8 ` - List Squad<br>
|
||||
* `9 ` - UNKNOWN<br>
|
||||
|
|
@ -251,10 +290,10 @@ object SquadAction{
|
|||
* `Long`<br>
|
||||
* `13` - UNKNOWN<br>
|
||||
* `14` - UNKNOWN<br>
|
||||
* `15` - UNKNOWN<br>
|
||||
* `15` - Select this Role for Yourself<br>
|
||||
* `37` - UNKNOWN<br>
|
||||
* `String`<br>
|
||||
* `7 ` - UNKNOWN<br>
|
||||
* `7 ` - List Squad Definition<br>
|
||||
* `19` - (Squad leader) Change Squad Purpose<br>
|
||||
* `Int :: Long`<br>
|
||||
* `12` - UNKNOWN<br>
|
||||
|
|
@ -293,16 +332,20 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
|
|||
import SquadAction.Codecs._
|
||||
import scala.annotation.switch
|
||||
((code : @switch) match {
|
||||
case 0 => displaySquadCodec
|
||||
case 3 => saveSquadDefinitionCodec
|
||||
case 4 => loadSquadDefinitionCodec
|
||||
case 7 => listSquadDefinitionCodec
|
||||
case 8 => listSquadCodec
|
||||
case 10 => selectRoleForYourselfCodec
|
||||
case 15 => cancelSelectRoleForYourselfCodec
|
||||
case 19 => changeSquadPurposeCodec
|
||||
case 20 => changeSquadZoneCodec
|
||||
case 21 => closeSquadMemberPositionCodec
|
||||
case 22 => addSquadMemberPositionCodec
|
||||
case 23 => changeSquadMemberRequirementsRoleCodec
|
||||
case 24 => changeSquadMemberRequirementsDetailedOrdersCodec
|
||||
case 25 => changeSquadMemberRequirementsWeaponsCodec
|
||||
case 25 => changeSquadMemberRequirementsCertificationsCodec
|
||||
case 26 => resetAllCodec
|
||||
case 28 => autoApproveInvitationRequestsCodec
|
||||
case 31 => locationFollowsSquadLeadCodec
|
||||
|
|
@ -310,8 +353,8 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
|
|||
case 35 => cancelSquadSearchCodec
|
||||
case 40 => findLfsSoldiersForRoleCodec
|
||||
case 41 => cancelFindCodec
|
||||
case 0 | 1 | 2 | 4 | 6 | 7 | 9 |
|
||||
11 | 12 | 13 | 14 | 15 | 16 |
|
||||
case 1 | 2 | 6 | 9 |
|
||||
11 | 12 | 13 | 14 | 16 |
|
||||
17 | 18 | 29 | 30 | 33 | 36 |
|
||||
37 | 38 | 42 | 43 => unknownCodec(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 scodec.codecs._
|
||||
|
||||
import scala.annotation.tailrec
|
||||
/**
|
||||
* An `Enumeration` of the available certifications.<br>
|
||||
* <br>
|
||||
|
|
@ -76,4 +78,59 @@ object CertificationType extends Enumeration {
|
|||
= Value
|
||||
|
||||
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 =>
|
||||
val id = GetNextSquadId()
|
||||
val squad = new Squad(id, faction)
|
||||
val leadPosition = squad.Membership(0)
|
||||
val leadPosition = squad.Membership(squad.LeaderPositionIndex)
|
||||
leadPosition.Name = name
|
||||
leadPosition.Health = player.Health
|
||||
leadPosition.Armor = player.Armor
|
||||
|
|
@ -80,7 +80,7 @@ class SquadService extends Actor {
|
|||
val path = s"$name/Squad"
|
||||
val who = sender()
|
||||
log.info(s"$who has joined $path")
|
||||
SquadEvents.subscribe(who, path)
|
||||
SquadEvents.subscribe(who, path) //TODO squad-specific switchboard
|
||||
//check for renewable squad information
|
||||
memberToSquad.get(name) match {
|
||||
case None => ;
|
||||
|
|
@ -101,99 +101,107 @@ class SquadService extends Actor {
|
|||
val squad = GetSquadFromPlayer(tplayer)
|
||||
val member = squad.Membership.find(_.Name == tplayer.Name).get //should never fail
|
||||
member.ZoneId = zone_ordinal_number //TODO improve this requirement
|
||||
var listingChanged : List[Int] = Nil
|
||||
action match {
|
||||
case ChangeSquadPurpose(purpose) =>
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed his squad's task to $purpose")
|
||||
squad.Description = purpose
|
||||
listingChanged = List(SquadInfo.Field.Task)
|
||||
if(tplayer.Name.equals(squad.Leader)) {
|
||||
var listingChanged : List[Int] = Nil
|
||||
action match {
|
||||
case ChangeSquadPurpose(purpose) =>
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed his squad's task to $purpose")
|
||||
squad.Description = purpose
|
||||
listingChanged = List(SquadInfo.Field.Task)
|
||||
|
||||
case ChangeSquadZone(zone) =>
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed his squad's ops zone to $zone")
|
||||
squad.ZoneId = zone.zoneId.toInt
|
||||
listingChanged = List(SquadInfo.Field.ZoneId)
|
||||
case ChangeSquadZone(zone) =>
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed his squad's ops zone to $zone")
|
||||
squad.ZoneId = zone.zoneId.toInt
|
||||
listingChanged = List(SquadInfo.Field.ZoneId)
|
||||
|
||||
case CloseSquadMemberPosition(position) =>
|
||||
if(position > 0) {
|
||||
case CloseSquadMemberPosition(position) =>
|
||||
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 {
|
||||
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()
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the role of squad position #$position")
|
||||
squad.Membership(position).Role = role
|
||||
case Some(false) => ;
|
||||
case None => ;
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.warn(s"can not close the lead 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 ChangeSquadMemberRequirementsDetailedOrders(position, orders) =>
|
||||
squad.Availability.lift(position) match {
|
||||
case Some(true) =>
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the orders for squad position #$position")
|
||||
squad.Membership(position).Orders = orders
|
||||
case Some(false) => ;
|
||||
case None => ;
|
||||
}
|
||||
|
||||
case ChangeSquadMemberRequirementsRole(position, role) =>
|
||||
squad.Availability.lift(position) match {
|
||||
case Some(true) =>
|
||||
squad.Membership(position).Role = role
|
||||
case Some(false) => ;
|
||||
case None => ;
|
||||
}
|
||||
case ChangeSquadMemberRequirementsCertifications(position, certs) =>
|
||||
squad.Availability.lift(position) match {
|
||||
case Some(true) =>
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the requirements for squad position #$position")
|
||||
squad.Membership(position).Requirements = certs
|
||||
case Some(false) => ;
|
||||
case None => ;
|
||||
}
|
||||
|
||||
case ChangeSquadMemberRequirementsDetailedOrders(position, orders) =>
|
||||
squad.Availability.lift(position) match {
|
||||
case Some(true) =>
|
||||
squad.Membership(position).Orders = orders
|
||||
case Some(false) => ;
|
||||
case None => ;
|
||||
}
|
||||
case ListSquad() =>
|
||||
if(!squad.Listed) {
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened recruitment for his squad")
|
||||
squad.Listed = true
|
||||
}
|
||||
|
||||
case ListSquad() =>
|
||||
if(!squad.Listed) {
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened recruitment for his squad")
|
||||
squad.Listed = true
|
||||
}
|
||||
|
||||
case ResetAll() =>
|
||||
squad.Description = ""
|
||||
squad.ZoneId = None
|
||||
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?
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
//queue updates
|
||||
if(squad.Listed) {
|
||||
val entry = SquadService.Publish(squad)
|
||||
val faction = squad.Faction
|
||||
val factionListings = publishedLists(faction)
|
||||
factionListings.find(info => {
|
||||
info.squad_guid match {
|
||||
case Some(guid) => guid == squad.GUID
|
||||
case _ => false
|
||||
}
|
||||
}) match {
|
||||
case Some(listedSquad) =>
|
||||
val index = factionListings.indexOf(listedSquad)
|
||||
if(squad.Listed) {
|
||||
//squad information update
|
||||
log.info(s"Squad will be updated")
|
||||
factionListings(index) = entry
|
||||
case _ => ;
|
||||
}
|
||||
//queue updates
|
||||
if(squad.Listed) {
|
||||
val entry = SquadService.Publish(squad)
|
||||
val faction = squad.Faction
|
||||
val factionListings = publishedLists(faction)
|
||||
factionListings.find(info => {
|
||||
info.squad_guid match {
|
||||
case Some(guid) => guid == squad.GUID
|
||||
case _ => false
|
||||
}
|
||||
}) match {
|
||||
case Some(listedSquad) =>
|
||||
val index = factionListings.indexOf(listedSquad)
|
||||
val changes = if(listingChanged.nonEmpty) {
|
||||
SquadService.Differences(listingChanged, entry)
|
||||
}
|
||||
|
|
@ -201,27 +209,29 @@ class SquadService extends Actor {
|
|||
SquadService.Differences(listedSquad, entry)
|
||||
}
|
||||
if(changes != SquadInfo.Blank) {
|
||||
//squad information update
|
||||
log.info(s"Squad will be updated")
|
||||
factionListings(index) = entry
|
||||
SquadEvents.publish(
|
||||
SquadServiceResponse(s"$faction/Squad", SquadResponse.Update(Seq((index, changes))))
|
||||
)
|
||||
}
|
||||
}
|
||||
else {
|
||||
//remove squad from listing
|
||||
log.info(s"Squad will be removed")
|
||||
factionListings.remove(index)
|
||||
else {
|
||||
//remove squad from listing
|
||||
log.info(s"Squad will be removed")
|
||||
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(
|
||||
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 = {
|
||||
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
|
||||
({
|
||||
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)
|
||||
)
|
||||
updates.map(i => list(i)).filterNot { _ == SquadInfo.Blank }
|
||||
}) //ignore what code inspection tells you - the parenthesis is necessary
|
||||
updates
|
||||
.map(i => list(i))
|
||||
.filterNot { _ == SquadInfo.Blank }
|
||||
.foreach(sinfo => out = out And sinfo )
|
||||
out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,15 @@ import org.specs2.mutable._
|
|||
import net.psforever.packet._
|
||||
import net.psforever.packet.game.SquadAction._
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.CertificationType
|
||||
import scodec.bits._
|
||||
|
||||
class SquadDefinitionActionMessageTest extends Specification {
|
||||
//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_04 = hex"E7 10 0000c0" //index: 3
|
||||
val string_07 = hex"e7 1c 0000e68043006f0070007300200061006e00640020004d0069006c006900740061007200790020004f006600660069006300650072007300"
|
||||
val string_08 = hex"E7 20 000000"
|
||||
val string_10 = hex"E7 28 000004" //index: 1
|
||||
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_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 {
|
||||
PacketCoding.DecodePacket(string_03).require match {
|
||||
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 {
|
||||
PacketCoding.DecodePacket(string_08).require match {
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
|
|
@ -138,7 +175,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual 0
|
||||
unk2 mustEqual 0
|
||||
action mustEqual ChangeSquadMemberRequirementsWeapons(1, 536870928L)
|
||||
action mustEqual ChangeSquadMemberRequirementsCertifications(
|
||||
1,
|
||||
Set(CertificationType.AntiVehicular, CertificationType.InfiltrationSuit)
|
||||
)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -280,6 +320,13 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
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 {
|
||||
val msg = SquadDefinitionActionMessage(0, 3, SaveSquadDefinition())
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
|
@ -287,6 +334,20 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
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 {
|
||||
val msg = SquadDefinitionActionMessage(0, 0, ListSquad())
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
|
@ -344,7 +405,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
}
|
||||
|
||||
"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
|
||||
|
||||
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
|
||||
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) = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue