split original aphedox SquadRequestType's into two different enumerations, one for Request and the other for Response; added functionality for more squad interactions; squad favorites, excepting the ability to actually load them onto one's client; extensive comments about SquadResponseMessage events

This commit is contained in:
FateJH 2019-07-22 17:56:09 -04:00
parent 640a96ae9c
commit f81c87ce22
13 changed files with 820 additions and 293 deletions

View file

@ -23,7 +23,7 @@ object Loadout {
owner match {
case p : Player => Success(Create(p, label))
case v : Vehicle => Success(Create(v, label))
case s : Squad => Success(Create(s, label))
case s : Squad => Success(Create(s, s.Task))
case _ => Failure(new MatchError(s"can not create a loadout based on the (current status of) $owner"))
}
}
@ -65,7 +65,6 @@ object Loadout {
def Create(squad : Squad, label : String) : Loadout = {
SquadLoadout(
label,
squad.Task,
if(squad.CustomZoneId) { Some(squad.ZoneId) } else { None },
squad.Membership
.zipWithIndex

View file

@ -5,7 +5,6 @@ import net.psforever.types.CertificationType
final case class SquadPositionLoadout(index : Int, role : String, orders : String, requirements : Set[CertificationType.Value])
final case class SquadLoadout(label : String,
task : String,
final case class SquadLoadout(task : String,
zone_id : Option[Int],
members : List[SquadPositionLoadout]) extends Loadout(label)
members : List[SquadPositionLoadout]) extends Loadout(task)

View file

@ -91,7 +91,7 @@ class Squad(squadId : PlanetSideGUID, alignment : PlanetSideEmpire.Value) extend
}
}
def Size : Int = membership.count(member => !member.Name.equals(""))
def Size : Int = membership.count(member => member.CharId != 0)
def Capacity : Int = availability.count(open => open)
}

View file

@ -51,6 +51,22 @@ final case class SquadInfo(leader : Option[String],
squad_guid.orElse(info.squad_guid)
)
}
//methods intended to combine the fields of itself and another object
def Leader(leader : String) : SquadInfo =
this And SquadInfo(Some(leader), None, None, None, None, None)
def Task(task : String) : SquadInfo =
this And SquadInfo(None, Some(task), None, None, None, None)
def ZoneId(zone : PlanetSideZoneID) : SquadInfo =
this And SquadInfo(None, None, Some(zone), None, None, None)
def ZoneId(zone : Option[PlanetSideZoneID]) : SquadInfo = zone match {
case Some(zoneId) => this And SquadInfo(None, None, zone, None, None, None)
case None => SquadInfo(leader, task, zone, size, capacity, squad_guid)
}
def Size(sz : Int) : SquadInfo =
this And SquadInfo(None, None, None, Some(sz), None, None)
def Capacity(cap : Int) : SquadInfo =
this And SquadInfo(None, None, None, None, Some(cap), None)
}
/**
@ -117,7 +133,9 @@ object SquadInfo {
/**
* An entry where no fields are defined.
*/
final val Blank = SquadInfo(None, None, None, None, None)
final val Blank = SquadInfo()
def apply() : SquadInfo = SquadInfo(None, None, None, None, None, None)
/**
* Alternate constructor for `SquadInfo` that ignores the `Option` requirement for the fields.<br>

View file

@ -20,18 +20,24 @@ object SquadAction{
final case class AnswerSquadJoinRequest() extends SquadAction(1)
final case class SaveSquadDefinition() extends SquadAction(3)
final case class SaveSquadFavorite() extends SquadAction(3)
final case class LoadSquadDefinition() extends SquadAction(4)
final case class LoadSquadFavorite() extends SquadAction(4)
final case class ListSquadDefinition(name : String) extends SquadAction(7)
final case class DeleteSquadFavorite() extends SquadAction(5)
final case class ListSquad() extends SquadAction(8)
final case class ListSquadFavorite(name : String) extends SquadAction(7)
final case class RequestListSquad() extends SquadAction(8)
final case class StopListSquad() extends SquadAction(9)
final case class SelectRoleForYourself(state : Int) extends SquadAction(10)
final case class CancelSelectRoleForYourself(value: Long = 0) extends SquadAction(15)
final case class SetListSquad() extends SquadAction(17)
final case class ChangeSquadPurpose(purpose : String) extends SquadAction(19)
final case class ChangeSquadZone(zone : PlanetSideZoneID) extends SquadAction(20)
@ -56,12 +62,21 @@ object SquadAction{
final case class CancelSquadSearch() extends SquadAction(35)
final case class AssignSquadMemberToRole(position : Int, char_id : Long) extends SquadAction(38)
final case class FindLfsSoldiersForRole(state : Int) extends SquadAction(40)
final case class CancelFind() extends SquadAction(41)
final case class Unknown(badCode : Int, data : BitVector) extends SquadAction(badCode)
object Unknown {
import scodec.bits._
val StandardBits : BitVector = hex"00".toBitVector.take(6)
def apply(badCode : Int) : Unknown = Unknown(badCode, StandardBits)
}
/**
* The `Codec`s used to transform the input stream into the context of a specific action
* and extract the field data from that stream.
@ -83,31 +98,45 @@ object SquadAction{
}
)
val saveSquadDefinitionCodec = everFailCondition.xmap[SaveSquadDefinition] (
_ => SaveSquadDefinition(),
val saveSquadFavoriteCodec = everFailCondition.xmap[SaveSquadFavorite] (
_ => SaveSquadFavorite(),
{
case SaveSquadDefinition() => None
case SaveSquadFavorite() => None
}
)
val loadSquadDefinitionCodec = everFailCondition.xmap[LoadSquadDefinition] (
_ => LoadSquadDefinition(),
val loadSquadFavoriteCodec = everFailCondition.xmap[LoadSquadFavorite] (
_ => LoadSquadFavorite(),
{
case LoadSquadDefinition() => None
case LoadSquadFavorite() => None
}
)
val listSquadDefinitionCodec = PacketHelpers.encodedWideStringAligned(6).xmap[ListSquadDefinition] (
text => ListSquadDefinition(text),
val deleteSquadFavoriteCodec = everFailCondition.xmap[DeleteSquadFavorite] (
_ => DeleteSquadFavorite(),
{
case ListSquadDefinition(text) => text
case DeleteSquadFavorite() => None
}
)
val listSquadCodec = everFailCondition.xmap[ListSquad] (
_ => ListSquad(),
val listSquadFavoriteCodec = PacketHelpers.encodedWideStringAligned(6).xmap[ListSquadFavorite] (
text => ListSquadFavorite(text),
{
case ListSquad() => None
case ListSquadFavorite(text) => text
}
)
val requestListSquadCodec = everFailCondition.xmap[RequestListSquad] (
_ => RequestListSquad(),
{
case RequestListSquad() => None
}
)
val stopListSquadCodec = everFailCondition.xmap[StopListSquad] (
_ => StopListSquad(),
{
case StopListSquad() => None
}
)
@ -125,6 +154,13 @@ object SquadAction{
}
)
val setListSquadCodec = everFailCondition.xmap[SetListSquad] (
_ => SetListSquad(),
{
case SetListSquad() => None
}
)
val changeSquadPurposeCodec = PacketHelpers.encodedWideStringAligned(6).xmap[ChangeSquadPurpose] (
purpose => ChangeSquadPurpose(purpose),
{
@ -223,6 +259,15 @@ object SquadAction{
}
)
val assignSquadMemberToRoleCodec = (uint4 :: uint32L).xmap[AssignSquadMemberToRole] (
{
case u1 :: u2 :: HNil => AssignSquadMemberToRole(u1, u2)
},
{
case AssignSquadMemberToRole(u1, u2) => u1 :: u2 :: HNil
}
)
val findLfsSoldiersForRoleCodec = uint4.xmap[FindLfsSoldiersForRole] (
state => FindLfsSoldiersForRole(state),
{
@ -270,13 +315,14 @@ object SquadAction{
* &nbsp;&nbsp;&nbsp;&nbsp;`0 ` - Display Squad<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`1 ` - Answer Squad Join Request<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`2 ` - UNKNOWN<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`3 ` - Save Squad Definition<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`4 ` - Load Squad Definition<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`3 ` - Save Squad Favorite<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`4 ` - Load Squad Favorite<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`5 ` - Delete Squad Favorite<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`6 ` - UNKNOWN<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`8 ` - List Squad<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`9 ` - UNKNOWN<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`8 ` - Request List Squad<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`9 ` - Stop List Squad<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`16` - UNKNOWN<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`17` - UNKNOWN<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`17` - Set List Squad (ui)<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`18` - UNKNOWN<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`26` - Reset All<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`35` - Cancel Squad Search<br>
@ -302,12 +348,12 @@ object SquadAction{
* &nbsp;&nbsp;&nbsp;&nbsp;`15` - Select this Role for Yourself<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`37` - UNKNOWN<br>
* &nbsp;&nbsp;`String`<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`7 ` - List Squad Definition<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`7 ` - List Squad Favorite<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`19` - (Squad leader) Change Squad Purpose<br>
* &nbsp;&nbsp;`Int :: Long`<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`12` - UNKNOWN<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`25` - (Squad leader) Change Squad Member Requirements - Weapons<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`38` - UNKNOWN<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`38` - Assign Squad Member To Role<br>
* &nbsp;&nbsp;`Int :: String`<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`23` - (Squad leader) Change Squad Member Requirements - Role<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`24` - (Squad leader) Change Squad Member Requirements - Detailed Orders<br>
@ -343,12 +389,15 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
((code : @switch) match {
case 0 => displaySquadCodec
case 1 => answerSquadJoinRequestCodec
case 3 => saveSquadDefinitionCodec
case 4 => loadSquadDefinitionCodec
case 7 => listSquadDefinitionCodec
case 8 => listSquadCodec
case 3 => saveSquadFavoriteCodec
case 4 => loadSquadFavoriteCodec
case 5 => deleteSquadFavoriteCodec
case 7 => listSquadFavoriteCodec
case 8 => requestListSquadCodec
case 9 => stopListSquadCodec
case 10 => selectRoleForYourselfCodec
case 15 => cancelSelectRoleForYourselfCodec
case 17 => setListSquadCodec
case 19 => changeSquadPurposeCodec
case 20 => changeSquadZoneCodec
case 21 => closeSquadMemberPositionCodec
@ -361,12 +410,13 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
case 31 => locationFollowsSquadLeadCodec
case 34 => searchForSquadsWithParticularRoleCodec
case 35 => cancelSquadSearchCodec
case 38 => assignSquadMemberToRoleCodec
case 40 => findLfsSoldiersForRoleCodec
case 41 => cancelFindCodec
case 2 | 6 | 9 | 11 |
12 | 13 | 14 | 16 | 17 |
case 2 | 6 | 11 |
12 | 13 | 14 | 16 |
18 | 29 | 30 | 32 | 33 |
36 | 37 | 38 | 39 | 42 | 43 => unknownCodec(code)
36 | 37 | 39 | 42 | 43 => unknownCodec(code)
case _ => failureCodec(code)
}).asInstanceOf[Codec[SquadAction]]
}

View file

@ -161,7 +161,21 @@ final case class SquadDetail(unk1 : Option[Int],
task.orElse(info.task),
zone_id.orElse(info.zone_id),
unk7.orElse(info.unk7),
member_info.orElse(info.member_info)
{
(member_info, info.member_info) match {
case (Some(info1), Some(info2)) =>
//combine the first list with the elements of the second list whose indices not found in the first list
val indices = info1.map { _.index }
Some(info1 ++ (for {
position <- info2
if !indices.contains(position.index)
} yield position).sortBy(_.index))
case (Some(info1), None) =>
Some(info1)
case (None, _) =>
info.member_info
}
}
)
}
@ -269,6 +283,15 @@ object SquadPositionDetail {
* @return a `SquadPositionDetail` object
*/
def apply(role : String, detailed_orders : String, requirements : Set[CertificationType.Value], char_id : Long, name : String) : SquadPositionDetail = SquadPositionDetail(Some(false), Some(role), Some(detailed_orders), Some(requirements), Some(char_id), Some(name))
object Fields {
final val Closed = 0
final val Role = 1
final val Orders = 2
final val CharId = 3
final val Name = 4
final val Requirements = 5
}
}
object SquadPositionEntry {
@ -318,6 +341,17 @@ object SquadDetail {
SquadDetail(None, None, None, None, None, None, None, Some(unk7), None)
def Members(list : List[SquadPositionEntry]) : SquadDetail =
SquadDetail(None, None, None, None, None, None, None, None, Some(list))
object Fields {
final val Field1 = 1
final val CharId = 2
final val Field3 = 3
final val Leader = 4
final val Task = 5
final val ZoneId = 6
final val Field7 = 7
final val Members = 8
}
}
object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefinitionUpdateMessage] {

View file

@ -2,12 +2,12 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.SquadRequestType
import net.psforever.types.SquadResponseType
import scodec.Codec
import scodec.codecs._
/**
* Dispatched by the server as manipulation protocol for squad and platoon members.
* Dispatched by the server as message generation protocol for squad and platoon members.
* Prompted by and answers for a `SquadMembershipRequest` packet.
* @param request_type the purpose of the request
* @param unk1 na
@ -17,11 +17,57 @@ import scodec.codecs._
* @param other_id another squad member's unique identifier;
* may be the same as `char_id`
* @param player_name name of the player being affected, if applicable
* @param unk5 na
* @param unk5 adjusts the nature of the request-type response based on the message recipient
* @param unk6 na;
* the internal field, the `Option[String]`, never seems to be set
* <br>
* `request_type` (enum value) / `unk5` state (`false`/`true`)<br>
* ----------------------------------------<br>
* - `Invite` (0)<br>
* false => [PROMPT] "`player_name` has invited you into a squad." [YES/NO]<br>
* true => "You have invited `player_name` to join your squad."<br>
* - `Unk01` (1)<br>
* false => n/a<br>
* true => n/a<br>
* - `Accept` (2)<br>
* false => "`player_name` has accepted your invitation to join into your squad.<br>
* "You have formed a squad and are now that squad's commander." (if first time)<br>
* true => "You have accepted an invitation to join a squad."<br>
* "You have successfully joined a squad for the first time." (if first time)<br>
* - `Reject` (3)<br>
* false => "`player_name` does not want to join your squad at this time."<br>
* true => "You have declined an invitation to join a squad."<br>
* - `Cancel` (4)<br>
* false => "`player_name` has withdrawn his invitation."<br>
* true => "You have canceled your invitation to `player_name`."<br>
* - `Leave` (5)<br>
* false => "The Squad Leader has kicked you out of the squad."<br>
* true => "You have kicked `player_name` out of the squad."<br>
* - `Disband` (6)<br>
* false => "The squad has been disbanded."<br>
* true => "You have disbanded the squad."<br>
* - `PlatoonInvite` (7)<br>
* false => [PROMPT] "`player_name` has invited you into a platoon." [YES/NO]<br>
* true => "You have invited `player_name`'s squad to join your platoon."<br>
* - `PlatoonAccept` (8)
* false => "`player_name` has accepted your invitation to join into your platoon.<br>
* "You have formed a platoon and are now that platoon commander." (if first time)<br>
* true => "You have accepted an invitation to join a platoon."<br>
* "You have successfully joined a platoon for the first time." (if first time)<br>
* - `PlatoonReject` (9)<br>
* false => "`player_name` does not want to join your platoon at this time."<br>
* true => "You have declined an invitation to join a platoon."<br>
* - `PlatoonCancel` (10)<br>
* false => "`player_name` has withdrawn his invitation."<br>
* true => "You have declined your invitation to `player_name`." (nonsense?)<br>
* - `PlatoonLeave` (11)<br>
* false => "The Platoon Leader has kicked you out of the platoon."<br>
* true => "You have kicked `player_name`'s squad out of the platoon."<br>
* - `PlatoonDisband` (12)<br>
* false => "The platoon has been disbanded."<br>
* true => "You have disbanded the platoon."
*/
final case class SquadMembershipResponse(request_type : SquadRequestType.Value,
final case class SquadMembershipResponse(request_type : SquadResponseType.Value,
unk1 : Int,
unk2 : Int,
char_id : Long,
@ -37,14 +83,14 @@ final case class SquadMembershipResponse(request_type : SquadRequestType.Value,
object SquadMembershipResponse extends Marshallable[SquadMembershipResponse] {
implicit val codec : Codec[SquadMembershipResponse] = (
"request_type" | SquadRequestType.codec >>:~ { d =>
"request_type" | SquadResponseType.codec >>:~ { d =>
("unk1" | uint(5)) ::
("unk2" | uint2) ::
("char_id" | uint32L) ::
("other_id" | conditional(d != SquadRequestType.Promote && d != SquadRequestType.PlatoonLeave, uint32L)) ::
("other_id" | conditional(d != SquadResponseType.Disband && d != SquadResponseType.PlatoonDisband, uint32L)) ::
("player_name" | PacketHelpers.encodedWideStringAligned(5)) ::
("unk5" | bool) ::
conditional(d != SquadRequestType.Invite, optional(bool, "unk6" | PacketHelpers.encodedWideStringAligned(6)))
conditional(d != SquadResponseType.Invite, optional(bool, "unk6" | PacketHelpers.encodedWideStringAligned(6)))
}
).as[SquadMembershipResponse]
}

View file

@ -7,23 +7,28 @@ import scodec.codecs._
object SquadRequestType extends Enumeration {
type Type = Value
val
Invite, //00
Unk01, //01
Accept, //02
Reject, //03
Cancel, //04
Leave, //05
Promote, //06
Disband, //07
PlatoonInvite, //08
PlatoonAccept, //09
PlatoonReject, //10
PlatoonCancel, //11
PlatoonLeave, //12
PlatoonDisband, //13
Unk14, //14
Unk15 //15
Invite,
Unk01,
Accept,
Reject,
Cancel,
Leave,
Promote,
Disband,
PlatoonInvite,
PlatoonAccept,
PlatoonReject,
PlatoonCancel,
PlatoonLeave,
PlatoonDisband
= Value
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L)
def toResponse(request : SquadRequestType.Value) : SquadResponseType.Value = {
val id = request.id
if(id < 6) SquadResponseType(id)
else if(id > 6) SquadResponseType(id - 1)
else throw new NoSuchElementException("request does not have an applicable response")
}
}

View file

@ -0,0 +1,33 @@
// Copyright (c) 2019 PSForever
package net.psforever.types
import net.psforever.packet.PacketHelpers
import scodec.codecs._
object SquadResponseType extends Enumeration {
type Type = Value
val
Invite,
Unk01,
Accept,
Reject,
Cancel,
Leave,
Disband,
PlatoonInvite,
PlatoonAccept,
PlatoonReject,
PlatoonCancel,
PlatoonLeave,
PlatoonDisband
= Value
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L)
def fromRequest(response : SquadResponseType.Value) : SquadRequestType.Value = {
val id = response.id
if(id < 6) SquadRequestType(id)
else if(id > 5) SquadRequestType(id + 1)
else throw new NoSuchElementException("response does not stem from an applicable request")
}
}

View file

@ -1,21 +1,27 @@
// Copyright (c) 2019 PSForever
package services.teamwork
import net.psforever.objects.teamwork.{Member, Squad}
import net.psforever.objects.teamwork.Squad
import net.psforever.packet.game._
import net.psforever.types.SquadRequestType
import net.psforever.types.SquadResponseType
object SquadResponse {
trait Response
final case class Init(info : Vector[SquadInfo]) extends Response
final case class Update(infos : Iterable[(Int, SquadInfo)]) extends Response
final case class Remove(infos : Iterable[Int]) extends Response
final case class ListSquadFavorite(line : Int, task : String) extends Response
final case class Membership(request_type : SquadRequestType.Value, unk1 : Int, unk2 : Int, unk3 : Long, unk4 : Option[Long], player_name : String, unk5 : Boolean, unk6 : Option[Option[String]]) extends Response //see SquadMembershipResponse
final case class InitList(info : Vector[SquadInfo]) extends Response
final case class UpdateList(infos : Iterable[(Int, SquadInfo)]) extends Response
final case class RemoveFromList(infos : Iterable[Int]) extends Response
final case class InitSquad(squad_guid : PlanetSideGUID) extends Response
final case class Unknown17(squad : Squad, char_id : Long) extends Response
final case class Membership(request_type : SquadResponseType.Value, unk1 : Int, unk2 : Int, unk3 : Long, unk4 : Option[Long], player_name : String, unk5 : Boolean, unk6 : Option[Option[String]]) extends Response //see SquadMembershipResponse
final case class Invite(from_char_id : Long, to_char_id : Long, name : String) extends Response
final case class Join(squad : Squad, positionsToUpdate : List[Int]) extends Response
final case class Leave(squad : Squad, positionsToUpdate : List[(Long, Int)]) extends Response
final case class UpdateMembers(squad : Squad, update_info : List[SquadAction.Update]) extends Response
final case class SwapMember(squad : Squad, from_index : Int, to_index : Int) extends Response
final case class Detail(guid : PlanetSideGUID, leader : String, task : String, zone : PlanetSideZoneID, member_info : List[SquadPositionDetail]) extends Response
}

View file

@ -4,9 +4,10 @@ package services.teamwork
import akka.actor.Actor
import net.psforever.objects.Player
import net.psforever.objects.definition.converter.StatConverter
import net.psforever.objects.teamwork.Squad
import net.psforever.objects.loadouts.SquadLoadout
import net.psforever.objects.teamwork.{Member, Squad}
import net.psforever.packet.game._
import net.psforever.types.{PlanetSideEmpire, SquadRequestType, Vector3}
import net.psforever.types._
import services.{GenericEventBus, Service}
import scala.collection.concurrent.TrieMap
@ -16,7 +17,9 @@ import scala.collection.mutable.ListBuffer
//import scala.concurrent.duration._
class SquadService extends Actor {
private var memberToSquad : TrieMap[String, Squad] = new TrieMap[String, Squad]()
import SquadService._
private var memberToSquad : mutable.LongMap[Squad] = mutable.LongMap[Squad]()
private var idToSquad : TrieMap[PlanetSideGUID, Squad] = new TrieMap[PlanetSideGUID, Squad]()
private var i : Int = 1
private val publishedLists : TrieMap[PlanetSideEmpire.Value, ListBuffer[SquadInfo]] = TrieMap[PlanetSideEmpire.Value, ListBuffer[SquadInfo]](
@ -24,35 +27,39 @@ class SquadService extends Actor {
PlanetSideEmpire.NC -> ListBuffer.empty,
PlanetSideEmpire.VS -> ListBuffer.empty
)
private val bids : mutable.LongMap[(PlanetSideGUID, Int)] = mutable.LongMap[(PlanetSideGUID, Int)]()
private val bids : mutable.LongMap[PositionBid] = mutable.LongMap[PositionBid]()
private [this] val log = org.log4s.getLogger
override def preStart = {
override def preStart : Unit = {
log.info("Starting...")
val testSquad = new Squad(PlanetSideGUID(3), PlanetSideEmpire.VS)
testSquad.Task = "\\#66CCFF Sentinels of Auraxis\\#FFFFFF ... \\#40FF40 Squad Up!!"
testSquad.ZoneId = 5
testSquad.Membership(0).Name = "Wizkid45"
testSquad.Membership(0).Role = "Fearless Leader"
testSquad.Membership(0).CharId = 30910985L
testSquad.Membership(0).ZoneId = 5
testSquad.Membership(0).Health = 64
testSquad.Membership(0).Armor = 34
testSquad.Membership(0).Position = Vector3(5526.5234f, 3818.7344f, 54.59375f)
testSquad.Membership(1).Name = "xoBLADEox"
testSquad.Membership(1).Role = "Right Hand"
testSquad.Membership(1).CharId = 42781919L
testSquad.Membership(1).ZoneId = 5
testSquad.Membership(1).Health = 54
testSquad.Membership(1).Armor = 44
testSquad.Membership(1).Position = Vector3(4673.5312f, 2604.8047f, 40.015625f)
testSquad.Membership(3).Name = "cabal0428"
testSquad.Membership(3).Role = "Left Hand"
testSquad.Membership(3).CharId = 353380L
testSquad.Membership(3).ZoneId = 5
testSquad.Membership(3).Health = 44
testSquad.Membership(3).Armor = 54
testSquad.Membership(3).Position = Vector3(4727.492f, 2613.5312f, 51.390625f)
testSquad.Membership(4).Name = "xSkiku"
testSquad.Membership(4).Role = "Right Foot's Middle Toe's Nail"
testSquad.Membership(4).CharId = 41588340L
testSquad.Membership(4).ZoneId = 5
testSquad.Membership(4).Health = 34
@ -60,7 +67,7 @@ class SquadService extends Actor {
testSquad.Membership(4).Position = Vector3(3675.0f, 4789.8047f, 63.21875f)
idToSquad(PlanetSideGUID(3)) = testSquad
testSquad.Listed = true
UpdateSquadList(testSquad, List())
UpdateSquadList(testSquad, None)
}
def GetNextSquadId() : PlanetSideGUID = {
@ -76,7 +83,7 @@ class SquadService extends Actor {
}
def GetParticipatingSquad(player : Player, zone : Int) : Option[Squad] = {
memberToSquad.get(player.Name) match {
memberToSquad.get(player.CharId) match {
case opt @ Some(squad) =>
squad.Membership.find(_.Name == player.Name).get.ZoneId = zone
opt
@ -86,10 +93,10 @@ class SquadService extends Actor {
}
def GetLeadingSquad(player : Player, zone : Int, opt : Option[Squad]) : Squad = {
val name = player.Name
val charId = player.CharId
val squadOut = opt match {
case Some(squad) =>
if(squad.Leader.Name.equals(name)) {
if(squad.Leader.CharId == charId) {
squad
}
else {
@ -97,21 +104,23 @@ class SquadService extends Actor {
}
case None =>
memberToSquad.get(name) match {
case Some(squad) if squad.Leader.Name.equals(name) =>
memberToSquad.get(charId) match {
case Some(squad) if squad.Leader.CharId.equals(charId) =>
squad
case _ =>
val faction = player.Faction
val id = GetNextSquadId()
val squad = new Squad(id, faction)
val leadPosition = squad.Membership(squad.LeaderPositionIndex)
val name = player.Name
leadPosition.Name = name
leadPosition.CharId = charId
leadPosition.Health = player.Health
leadPosition.Armor = player.Armor
leadPosition.Position = player.Position
leadPosition.ZoneId = zone
log.info(s"$name-$faction has started a new squad")
memberToSquad += name -> squad
memberToSquad += charId -> squad
idToSquad += id -> squad
squad
}
@ -130,26 +139,27 @@ class SquadService extends Actor {
log.info(s"$who has joined $path")
SquadEvents.subscribe(who, path)
//send initial squad catalog
sender ! SquadServiceResponse(s"$faction/Squad", SquadResponse.Init(publishedLists(PlanetSideEmpire(faction)).toVector))
sender ! SquadServiceResponse(s"$faction/Squad", SquadResponse.InitList(publishedLists(PlanetSideEmpire(faction)).toVector))
//subscribe to the player's personal channel - necessary only to inform about any previous squad association
case Service.Join(name) =>
val path = s"$name/Squad"
case Service.Join(char_id) =>
val path = s"$char_id/Squad"
val who = sender()
log.info(s"$who has joined $path")
SquadEvents.subscribe(who, path) //TODO squad-specific switchboard
//check for renewable squad information
memberToSquad.get(name) match {
memberToSquad.get(char_id.toLong) match {
case None => ;
case Some(_) =>
sender ! SquadServiceMessage.RecoverSquadMembership() //TODO?
}
case Service.Leave(Some(name)) => ;
case Service.Leave(Some(char_id)) => ;
SquadEvents.unsubscribe(sender())
memberToSquad.get(name) match {
val longCharId = char_id.toLong
memberToSquad.get(longCharId) match {
case Some(squad) =>
if(squad.Leader.Name.equals(name)) {
if(squad.Leader.Name.equals(char_id)) {
//we were the leader
if(squad.Membership.count(p => p.Name.equals("")) > 1) {
//other players were in the squad; publicly disband it
@ -162,19 +172,19 @@ class SquadService extends Actor {
position.Armor = 0
})
}
memberToSquad.remove(name)
memberToSquad.remove(longCharId)
idToSquad.remove(squad.GUID)
UpdateSquadList(squad, List())
UpdateSquadList(squad, None)
}
else {
//we were just a grunt in the squad
val position = squad.Membership.find(_.Name == name).get
val position = squad.Membership.find(_.CharId == longCharId).get
position.Name = ""
position.ZoneId = 0
position.Position = Vector3.Zero
position.Health = 0
position.Armor = 0
UpdateSquadDetail(squad)
UpdateSquadDetail(squad.GUID, squad)
}
case None => ;
}
@ -184,67 +194,116 @@ class SquadService extends Actor {
case SquadServiceMessage(tplayer, squad_action) => squad_action match {
case SquadAction.Membership(request_type, char_id, optional_char_id, _, _) => request_type match {
case SquadRequestType.Accept =>
bids.get(char_id) match {
case Some((squadGUID, line)) if idToSquad.get(squadGUID).nonEmpty =>
//join squad
val squad = idToSquad(squadGUID)
val position = squad.Membership(line)
if(squad.Availability(line) && position.CharId == 0 &&
tplayer.Certifications.intersect(position.Requirements) == position.Requirements) {
position.Name = tplayer.Name
position.CharId = char_id
position.Health = tplayer.Health
position.Armor = tplayer.Armor
position.Position = tplayer.Position
position.ZoneId = 13
memberToSquad(tplayer.Name) = squad
//joining the squad
sender ! SquadServiceResponse("", SquadResponse.Join(
squad,
squad.Membership.zipWithIndex.collect({ case (member, index) if member.CharId != 0 => index }).toList
))
//other squad members see us joining the squad
val updatedIndex = List(line)
squad.Membership
.collect({ case member if member.CharId != 0 && member.CharId != char_id => member.Name })
.foreach { name =>
SquadEvents.publish( SquadServiceResponse(s"$name/Squad", SquadResponse.Join(squad, updatedIndex)) )
}
}
bids.remove(char_id)
case SquadRequestType.Invite =>
(optional_char_id, memberToSquad.get(char_id)) match {
case (Some(toCharId), Some(squad)) =>
bids(toCharId) = VacancyBid(char_id, squad.GUID)
SquadEvents.publish( SquadServiceResponse(s"$toCharId/Squad", SquadResponse.Invite(char_id, toCharId, tplayer.Name)) )
case (Some(toCharId), None) =>
val ourSquad = GetLeadingSquad(tplayer, 1, None)
memberToSquad.remove(char_id)
bids(toCharId) = CamraderieBid(char_id, ourSquad)
SquadEvents.publish( SquadServiceResponse(s"$toCharId/Squad", SquadResponse.Invite(char_id, toCharId, tplayer.Name)) )
case _ => ;
}
case SquadRequestType.Leave =>
val squad = memberToSquad(char_id)
if(optional_char_id.contains(char_id)) {
//we're leaving the squad on our own
val name = tplayer.Name
val squad = memberToSquad(name)
val membership = squad.Membership.zipWithIndex
val (member, index) = membership
.find { case (_member, _) => _member.Name == name }
.get
val updateList = membership.collect({ case (_member, _index) if _member.CharId > 0 => (_member.CharId, _index) }).toList
memberToSquad.remove(name)
member.Name = ""
member.CharId = 0
//leaving the squad completely
//leaving the squad of our own accord
}
else {
val leader = squad.Leader
if(optional_char_id.contains(leader.CharId)) {
//kicked by the squad leader
}
}
val membership = squad.Membership.zipWithIndex
val (member, index) = membership
.find { case (_member, _) => _member.CharId == char_id }
.get
val updateList = membership.collect({ case (_member, _index) if _member.CharId > 0 => (_member.CharId, _index) }).toList
memberToSquad.remove(char_id)
member.Name = ""
member.CharId = 0
val size = squad.Size
if(size == 1) {
//squad is rendered to just one person; collapse it
(membership.collect({ case (_member, _) if _member.CharId > 0 => _member.CharId }) :+ char_id)
.foreach { charId =>
SquadEvents.publish( SquadServiceResponse(s"$charId/Squad", SquadResponse.Leave(squad, updateList)) )
memberToSquad.remove(charId)
}
idToSquad.remove(squad.GUID)
}
else {
//squad continues, despite player's parting
//member leaves the squad completely
sender ! SquadServiceResponse("", SquadResponse.Leave(squad, updateList))
//other squad members see us leaving the squad
//other squad members see the member leaving only
val leavingMember = List((char_id, index))
membership
.collect({ case (_member, _) if _member.CharId > 0 => _member.Name })
.foreach { name =>
SquadEvents.publish( SquadServiceResponse(s"$name/Squad", SquadResponse.Leave(squad, leavingMember)) )
}
.collect({ case (_member, _) if _member.CharId > 0 => _member.CharId })
.foreach { charId =>
SquadEvents.publish( SquadServiceResponse(s"$charId/Squad", SquadResponse.Leave(squad, leavingMember)) )
}
}
case SquadRequestType.Accept =>
bids.remove(char_id) match {
case Some(NormalBid(inviterCharId, squadGUID, line)) if idToSquad.get(squadGUID).nonEmpty =>
JoinSquad(tplayer, idToSquad(squadGUID), line)
case Some(VacancyBid(inviterCharId, squadGUID)) if idToSquad.get(squadGUID).nonEmpty =>
val squad = idToSquad(squadGUID)
squad.Membership.zipWithIndex.find({ case (member, index) =>
ValidOpenSquadPosition(squad, index, squad.Membership(index), tplayer.Certifications)
}) match {
case Some((_, line)) =>
JoinSquad(tplayer, squad, line)
case _ => ;
}
case Some(CamraderieBid(inviterCharId, placeholderSquad)) =>
(GetParticipatingSquad(tplayer, 1) match {
case Some(participating) =>
if(participating.Leader.CharId == inviterCharId) {
Some(participating)
}
else {
//inviter is not leader of squad; bounce this request off of the squad leader
val leaderCharId = participating.Leader.CharId
bids(char_id) = VacancyBid(leaderCharId, participating.GUID) //reframed request
//TODO squad leader receives "<player> wants to join squad" prompt
//SquadEvents.publish(SquadServiceResponse(s"$leaderCharId/Squad", SquadResponse.Invite(char_id, leaderCharId, tplayer.Name)))
None
}
case None =>
placeholderSquad.Task = s"${tplayer.Name}'s Squad"
memberToSquad(inviterCharId) = placeholderSquad
SquadEvents.publish(SquadServiceResponse(s"$inviterCharId/Squad", SquadResponse.InitSquad(placeholderSquad.GUID)))
Some(placeholderSquad)
}) match {
case Some(squad) =>
squad.Membership.zipWithIndex.find({ case (member, index) =>
ValidOpenSquadPosition(squad, index, squad.Membership(index), tplayer.Certifications)
}) match {
case Some((_, line)) =>
JoinSquad(tplayer, squad, line)
case _ => ;
}
case None => ;
}
case _ => ;
}
case _ => ;
}
case SquadAction.Update(char_id, health, max_health, armor, max_armor, pos, zone_number) =>
memberToSquad.get(tplayer.Name) match {
memberToSquad.get(char_id) match {
case Some(squad) =>
squad.Membership.find(_.CharId == char_id) match {
case Some(member) =>
@ -263,46 +322,64 @@ class SquadService extends Actor {
))
case _ => ;
}
// val (self, others) = squad.Membership.partition(_.CharId == char_id)
// self match {
// case Array(member) =>
// val newHealth = StatConverter.Health(health, max_health, min=1, max=64)
// val newArmor = StatConverter.Health(armor, max_armor, min=1, max=64)
// member.Health = newHealth
// member.Armor = newArmor
// member.Position = pos
// member.ZoneId = zone_number
// sender ! SquadServiceResponse("", SquadResponse.UpdateMembers(
// squad,
// others
// .map { member => SquadAction.Update(member.CharId, member.Health, 0, member.Armor, 0, member.Position, member.ZoneId) }
// .toList
// ))
// case _ => ;
// }
case None => ;
}
case SquadAction.Definition(tplayer : Player, zone_ordinal_number : Int, guid : PlanetSideGUID, _ : Int, action : SquadAction) =>
case SquadAction.Definition(tplayer : Player, zone_ordinal_number : Int, guid : PlanetSideGUID, line : Int, action : SquadAction) =>
import net.psforever.packet.game.SquadAction._
val squadOpt = GetParticipatingSquad(tplayer, zone_ordinal_number)
action match {
case SaveSquadDefinition() =>
case SaveSquadFavorite() =>
squadOpt match {
case Some(squad) if squad.Leader.CharId == tplayer.CharId && squad.Task.nonEmpty && squad.ZoneId > 0 =>
tplayer.SquadLoadouts.SaveLoadout(squad, squad.Task, line)
sender ! SquadServiceResponse("", SquadResponse.ListSquadFavorite(line, squad.Task))
case _ => ;
}
case LoadSquadFavorite() =>
(squadOpt match {
case Some(squad) if squad.Size == 1 =>
//we are the leader of our own squad, but no one else has joined yet
Some(squad)
case None => ;
//we are not yet member of a squad; start a squad to support our favorite definition
Some(GetLeadingSquad(tplayer, zone_ordinal_number, None))
case _ => ;
//player is member of populated squad; should not overwrite squad definition with favorite
None
}, tplayer.SquadLoadouts.LoadLoadout(line)) match {
case (Some(squad : Squad), Some(loadout : SquadLoadout)) =>
log.info(s"${tplayer.Name} is loading a squad composition: $loadout")
SquadService.LoadSquadDefinition(squad, loadout)
sender ! SquadServiceResponse("", SquadResponse.InitSquad(squad.GUID))
UpdateSquadList(squad, SquadInfo().Task(squad.Task).ZoneId(PlanetSideZoneID(squad.ZoneId)).Capacity(squad.Capacity))
UpdateSquadDetail(PlanetSideGUID(0), squad)
case _ => ;
}
case DeleteSquadFavorite() =>
tplayer.SquadLoadouts.DeleteLoadout(line)
sender ! SquadServiceResponse("", SquadResponse.ListSquadFavorite(line, ""))
case ChangeSquadPurpose(purpose) =>
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed his squad's task to $purpose")
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
squad.Task = purpose
UpdateSquadList(squad, List(SquadInfo.Field.Task))
UpdateSquadDetail(squad)
UpdateSquadListWhenListed(squad, SquadInfo().Task(purpose))
SquadDetail().Task(purpose)
UpdateListedSquadDetail(squad.GUID, squad)
case ChangeSquadZone(zone) =>
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed squad's ops zone to $zone")
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
squad.ZoneId = zone.zoneId.toInt
UpdateSquadList(squad, List(SquadInfo.Field.ZoneId))
UpdateSquadDetail(squad)
UpdateSquadListWhenListed(squad, SquadInfo().ZoneId(zone))
SquadDetail().ZoneId(zone)
UpdateListedSquadDetail(squad.GUID, squad)
case CloseSquadMemberPosition(position) =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
@ -312,14 +389,15 @@ class SquadService extends Actor {
log.info(s"${tplayer.Name}-${tplayer.Faction} has closed the #$position position in squad")
val memberPosition = squad.Membership(position)
val listingChanged = if(memberPosition.Name.nonEmpty) {
List(SquadInfo.Field.Size, SquadInfo.Field.Capacity)
SquadInfo().Size(squad.Size).Capacity(squad.Capacity)
}
else {
List(SquadInfo.Field.Capacity)
SquadInfo().Capacity(squad.Capacity)
}
memberPosition.Close()
UpdateSquadList(squad, listingChanged)
UpdateSquadDetail(squad)
UpdateSquadListWhenListed(squad, listingChanged)
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail.Closed)))
UpdateListedSquadDetail(squad.GUID, squad)
case Some(false) | None => ;
}
@ -329,8 +407,9 @@ class SquadService extends Actor {
case Some(false) =>
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened the #$position position in squad")
squad.Availability.update(position, true)
UpdateSquadList(squad, List(SquadInfo.Field.Capacity))
UpdateSquadDetail(squad)
UpdateSquadListWhenListed(squad, SquadInfo().Capacity(squad.Capacity))
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail.Open)))
UpdateListedSquadDetail(squad.GUID, squad)
case Some(true) | None => ;
}
@ -340,7 +419,8 @@ class SquadService extends Actor {
case Some(true) =>
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the role of squad position #$position")
squad.Membership(position).Role = role
UpdateSquadDetail(squad)
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail().Role(role))))
UpdateListedSquadDetail(squad.GUID, squad)
case Some(false) | None => ;
}
@ -350,7 +430,8 @@ class SquadService extends Actor {
case Some(true) =>
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the orders for squad position #$position")
squad.Membership(position).Orders = orders
UpdateSquadDetail(squad)
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail().DetailedOrders(orders))))
UpdateListedSquadDetail(squad.GUID, squad)
case Some(false) | None => ;
}
@ -360,7 +441,8 @@ class SquadService extends Actor {
case Some(true) =>
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the requirements for squad position #$position")
squad.Membership(position).Requirements = certs
UpdateSquadDetail(squad)
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail().Requirements(certs))))
UpdateListedSquadDetail(squad.GUID, squad)
case Some(false) | None => ;
}
@ -386,12 +468,47 @@ class SquadService extends Actor {
case SelectRoleForYourself(position) =>
//TODO need to ask permission from the squad leader, unless our character is the squad leader or already currently in the squad
//val name = tplayer.Name
squadOpt match {
case Some(squad) if squad.GUID == guid =>
//already a member of this squad; swap positions freely
case Some(squad) if squad.GUID == guid || squad.Leader.Name == tplayer.Name =>
//already a member of this squad, or the leader; swap positions freely
val membership = squad.Membership.zipWithIndex
val toMember = squad.Membership(position)
if(ValidOpenSquadPosition(squad, position, toMember, tplayer.Certifications)) {
//acquire this membership position
membership.find { case (member, _) => member.Name == tplayer.Name } match {
case Some((fromMember, fromIndex)) =>
//copy member details
toMember.Name = fromMember.Name
toMember.CharId = fromMember.CharId
toMember.Health = fromMember.Health
toMember.Armor = fromMember.Armor
toMember.Position = fromMember.Position
toMember.ZoneId = fromMember.ZoneId
//old membership no longer valid
fromMember.Name = ""
fromMember.CharId = 0L
if(fromIndex == squad.LeaderPositionIndex) {
squad.LeaderPositionIndex = position
}
membership
.collect({ case (_member, _) if _member.CharId > 0 => _member.Name })
.foreach { name =>
SquadEvents.publish(SquadServiceResponse(s"$name/Squad", SquadResponse.SwapMember(squad, fromIndex, position)))
}
case None =>
toMember.Name = tplayer.Name
toMember.CharId = tplayer.CharId
toMember.Health = StatConverter.Health(tplayer.Health, tplayer.MaxHealth, min = 1, max = 64)
toMember.Armor = StatConverter.Health(tplayer.Armor, tplayer.MaxArmor, min = 1, max = 64)
toMember.Position = tplayer.Position
toMember.ZoneId = 13
memberToSquad(tplayer.CharId) = squad
}
}
UpdateSquadDetail(squad.GUID, squad)
case Some(_) =>
//not a member of the requesting squad; do nothing
//not a member of the requesting squad; do nothing
case None =>
//not a member of any squad; consider request of joining the target squad
idToSquad.get(guid) match {
@ -399,10 +516,10 @@ class SquadService extends Actor {
val member = squad.Membership(position)
if(squad.Availability(position) && member.CharId == 0 &&
tplayer.Certifications.intersect(member.Requirements) == member.Requirements) {
bids(tplayer.CharId) = (guid, position)
bids(tplayer.CharId) = NormalBid(squad.Leader.CharId, guid, position)
val leader = squad.Leader
//TODO need to ask permission from the squad leader, unless auto-approve is in effect
sender ! SquadServiceResponse("", SquadResponse.Membership(SquadRequestType.Invite, 0, 0, leader.CharId, Some(tplayer.CharId), leader.Name, false, None))
sender ! SquadServiceResponse("", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, leader.CharId, Some(tplayer.CharId), leader.Name, false, None))
}
case None =>
@ -411,36 +528,62 @@ class SquadService extends Actor {
}
}
case ListSquad() =>
case RequestListSquad() =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
if(!squad.Listed) {
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened recruitment for this squad")
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened public recruitment for squad ${squad.Task}")
squad.Listed = true
sender ! SquadServiceResponse("", SquadResponse.InitSquad(squad.GUID))
UpdateSquadList(squad, None)
sender ! SquadServiceResponse("", SquadResponse.Unknown17(squad, tplayer.CharId))
}
case StopListSquad() =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
if(!squad.Listed) {
log.info(s"${tplayer.Name}-${tplayer.Faction} has closed public recruitment for squad ${squad.Task}")
squad.Listed = false
sender ! SquadServiceResponse("", SquadResponse.InitSquad(PlanetSideGUID(0)))
UpdateSquadList(squad, None)
}
UpdateSquadList(squad, List())
case ResetAll() =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
squad.Task = ""
squad.ZoneId = None
squad.Availability.indices.foreach { i =>
squad.Availability.update(i, true)
squadOpt match {
case Some(squad) if squad.Leader.CharId == tplayer.CharId =>
squad.Task = ""
squad.ZoneId = None
squad.Availability.indices.foreach { i =>
squad.Availability.update(i, true)
}
squad.Membership.foreach(position => {
position.Role = ""
position.Orders = ""
position.Requirements = Set()
})
squad.LocationFollowsSquadLead = false
squad.AutoApproveInvitationRequests = false
UpdateSquadList(squad, SquadInfo().Task("").ZoneId(None).Capacity(squad.Capacity))
UpdateSquadDetail(squad.GUID, squad)
case None => ;
}
squad.Membership.foreach(position => {
position.Role = ""
position.Orders = ""
position.Requirements = Set()
})
UpdateSquadList(squad, List(SquadInfo.Field.Task, SquadInfo.Field.ZoneId, SquadInfo.Field.Size, SquadInfo.Field.Capacity))
UpdateSquadDetail(squad)
case DisplaySquad() =>
idToSquad.get(guid) match {
case Some(squad) =>
sender ! SquadServiceResponse(s"${tplayer.Name}/Squad", GenSquadDetail(squad))
sender ! SquadServiceResponse(s"${tplayer.Name}/Squad", GenSquadDetail(squad.GUID, squad))
case None => ;
}
// case AnswerSquadJoinRequest() =>
// idToSquad.get(guid) match {
// case Some(squad) =>
// if(squad.Leader.Name == tplayer.Name && squad.Listed) {
// //squad was just listed but we have not yet declared ourselves as the leader
// UpdateSquadDetail(squad)
// }
// case None => ;
// }
case _ => ;
}
@ -449,54 +592,109 @@ class SquadService extends Actor {
}
}
def UpdateSquadList(squad : Squad, listingChanged : List[Int]) : Unit = {
//queue updates
def JoinSquad(player : Player, squad : Squad, line : Int) : Boolean = {
val charId = player.CharId
val position = squad.Membership(line)
if(squad.Availability(line) && position.CharId == 0 &&
player.Certifications.intersect(position.Requirements) == position.Requirements) {
position.Name = player.Name
position.CharId = charId
position.Health = StatConverter.Health(player.Health, player.MaxHealth, min=1, max=64)
position.Armor = StatConverter.Health(player.Armor, player.MaxArmor, min=1, max=64)
position.Position = player.Position
position.ZoneId = 13
memberToSquad(player.CharId) = squad
val size = squad.Size
if(size == 1) {
//leader joins the squad? do nothing?
squad.LeaderPositionIndex = line
}
else if(size == 2) {
//first squad member after leader; both members fully initialize
val indices = squad.Membership.zipWithIndex.collect({ case (member, index) if member.CharId != 0 => index }).toList
squad.Membership
.collect({ case member if member.CharId != 0 => member.CharId })
.foreach { charId =>
SquadEvents.publish(SquadServiceResponse(s"$charId/Squad", SquadResponse.Join(squad, indices)))
}
}
else {
//joining an active squad; everybody updates differently
//new member gets full UI updates
sender ! SquadServiceResponse("", SquadResponse.Join(
squad,
squad.Membership.zipWithIndex.collect({ case (member, index) if member.CharId != 0 => index }).toList
))
//other squad members see us joining the squad
val updatedIndex = List(line)
squad.Membership
.collect({ case member if member.CharId != 0 && member.CharId != charId => member.CharId })
.foreach { charId =>
SquadEvents.publish(SquadServiceResponse(s"$charId/Squad", SquadResponse.Join(squad, updatedIndex)))
}
}
true
}
else {
false
}
}
def UpdateSquadList(squad : Squad, changes : SquadInfo) : Unit = {
UpdateSquadList(squad, Some(changes))
}
def UpdateSquadListWhenListed(squad : Squad, changes : SquadInfo) : Unit = {
UpdateSquadListWhenListed(squad, Some(changes))
}
def UpdateSquadListWhenListed(squad : Squad, changes : Option[SquadInfo]) : Unit = {
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(sguid) => sguid == squad.GUID
case _ => false
}
}) match {
case Some(listedSquad) =>
val index = factionListings.indexOf(listedSquad)
val changes = if(listingChanged.nonEmpty) {
SquadService.Differences(listingChanged, entry)
}
else {
SquadService.Differences(listedSquad, entry)
}
if(changes != SquadInfo.Blank) {
UpdateSquadList(squad, changes)
}
}
def UpdateSquadList(squad : Squad, changes : Option[SquadInfo]) : Unit = {
val entry = SquadService.SquadList.Publish(squad)
val faction = squad.Faction
val factionListings = publishedLists(faction)
factionListings.find(info => {
info.squad_guid match {
case Some(sguid) => sguid == squad.GUID
case _ => false
}
}) match {
case Some(listedSquad) =>
val index = factionListings.indexOf(listedSquad)
changes match {
case Some(changedFields) =>
//squad information update
log.info(s"Squad will be updated")
factionListings(index) = entry
SquadEvents.publish(
SquadServiceResponse(s"$faction/Squad", SquadResponse.Update(Seq((index, changes))))
SquadServiceResponse(s"$faction/Squad", SquadResponse.UpdateList(Seq((index, changedFields))))
)
}
else {
case None =>
//remove squad from listing
log.info(s"Squad will be removed")
factionListings.remove(index)
SquadEvents.publish(
SquadServiceResponse(s"$faction/Squad", SquadResponse.Remove(Seq(index)))
//SquadServiceResponse(s"$faction/Squad", SquadResponse.RemoveFromList(Seq(index)))
SquadServiceResponse(s"$faction/Squad", SquadResponse.InitList(factionListings.toVector))
)
}
case None =>
//first time being published
log.info(s"Squad will be introduced")
factionListings += SquadService.Publish(squad)
SquadEvents.publish(
SquadServiceResponse(s"$faction/Squad", SquadResponse.Init(factionListings.toVector))
)
}
}
case None =>
//first time being published
log.info(s"Squad will be introduced")
factionListings += entry
SquadEvents.publish(
SquadServiceResponse(s"$faction/Squad", SquadResponse.InitList(factionListings.toVector))
)
}
}
def GenSquadDetail(squad : Squad) : SquadResponse.Detail = {
def GenSquadDetail(guid : PlanetSideGUID, squad : Squad) : SquadResponse.Detail = {
SquadResponse.Detail(
squad.GUID,
squad.Leader.Name,
@ -513,60 +711,164 @@ class SquadService extends Actor {
)
}
def UpdateSquadDetail(squad : Squad) : Unit = {
val detail = GenSquadDetail(squad)
squad.Membership.collect {
case member if !member.Name.equals("") =>
member.Name
}.foreach { name =>
SquadEvents.publish(
SquadServiceResponse(s"$name/Squad", detail)
)
def UpdateListedSquadDetail(guid : PlanetSideGUID, squad : Squad) : Unit = {
if(squad.Listed) {
UpdateSquadDetail(guid, squad)
}
}
def UpdateSquadDetail(guid : PlanetSideGUID, squad : Squad) : Unit = {
val detail = GenSquadDetail(guid, squad)
squad.Membership
.collect { case member if member.CharId > 0L => member.CharId }
.foreach { charId =>
SquadEvents.publish(SquadServiceResponse(s"$charId/Squad", detail))
}
}
}
object SquadService {
def Publish(squad : Squad) : SquadInfo = {
SquadInfo(
squad.Leader.Name,
squad.Task,
PlanetSideZoneID(squad.ZoneId),
squad.Size,
squad.Capacity,
squad.GUID
)
}
trait PositionBid
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)
final case class NormalBid(char_id : Long, squad_guid : PlanetSideGUID, position : Int) extends PositionBid
final case class VacancyBid(char_id : Long, squad_guid : PlanetSideGUID) extends PositionBid
final case class CamraderieBid(char_id : Long, placeholder : Squad) extends PositionBid
object SquadList {
def Publish(squad : Squad) : SquadInfo = {
SquadInfo(
squad.Leader.Name,
squad.Task,
PlanetSideZoneID(squad.ZoneId),
squad.Size,
squad.Capacity,
squad.GUID
)
var out = SquadInfo.Blank
updates
.map(i => list(i))
.filterNot { _ == SquadInfo.Blank }
.foreach(sinfo => out = out And sinfo )
out
}
else {
SquadInfo.Blank
}
}
def Differences(before : SquadInfo, after : SquadInfo) : SquadInfo = {
SquadInfo(
if(!before.leader.equals(after.leader)) after.leader else None,
if(!before.task.equals(after.task)) after.task else None,
if(!before.zone_id.equals(after.zone_id)) after.zone_id else None,
if(!before.size.equals(after.size)) after.size else None,
if(!before.capacity.equals(after.capacity)) after.capacity else None
)
object Detail {
def Publish(squad : Squad) : SquadDetail = {
SquadDetail()
.LeaderCharId(squad.Leader.CharId)
.LeaderName(squad.Leader.Name)
.Task(squad.Task)
.ZoneId(PlanetSideZoneID(squad.ZoneId))
.Members(
squad.Membership.zipWithIndex.map({ case (p, index) =>
SquadPositionEntry(index, if(squad.Availability(index)) {
SquadPositionDetail(p.Role, p.Orders, p.Requirements, p.CharId, p.Name)
}
else {
SquadPositionDetail.Closed
})
}).toList
)
.Complete
}
def Differences(updates : List[Int], info : SquadDetail) : SquadDetail = {
if(updates.nonEmpty) {
val list = Seq(
SquadDetail.Blank, //must be index-0
SquadDetail(info.unk1, None, None, None, None, None, None, None, None),
SquadDetail(None, None, info.leader_char_id, None, None, None, None, None, None),
SquadDetail(None, None, None, info.unk3, None, None, None, None, None),
SquadDetail(None, None, None, None, info.leader_name, None, None, None, None),
SquadDetail(None, None, None, None, None, info.task, None, None, None),
SquadDetail(None, None, None, None, None, None, info.zone_id, None, None),
SquadDetail(None, None, None, None, None, None, None, info.unk7, None),
SquadDetail(None, None, None, None, None, None, None, None, info.member_info)
)
var out = SquadDetail.Blank
updates
.map(i => list(i))
.filterNot { _ == SquadDetail.Blank }
.foreach(sinfo => out = out And sinfo )
out
}
else {
SquadDetail.Blank
}
}
def Differences(before : SquadDetail, after : SquadDetail) : SquadDetail = {
SquadDetail(
if(!before.unk1.equals(after.unk1)) after.unk1 else None,
None,
if(!before.leader_char_id.equals(after.leader_char_id)) after.leader_char_id else None,
if(!before.unk3.equals(after.unk3)) after.unk3 else None,
if(!before.leader_name.equals(after.leader_name)) after.leader_name else None,
if(!before.task.equals(after.task)) after.task else None,
if(!before.zone_id.equals(after.zone_id)) after.zone_id else None,
if(!before.unk7.equals(after.unk7)) after.unk7 else None,
{
(before.member_info, after.member_info) match {
case (Some(beforeEntry), Some(afterEntry)) =>
val out = beforeEntry.zip(afterEntry)
.map { case (b, a) => PositionEquality(b, a) }
.collect { case Some(entry) => entry }
if(out.nonEmpty) {
Some(out)
}
else {
None
}
case _ =>
None
}
}
)
}
private def PositionEquality(before : SquadPositionEntry, after : SquadPositionEntry) : Option[SquadPositionEntry] = {
(before.info, after.info) match {
case (Some(binfo), Some(ainfo)) =>
val out = MemberEquality(binfo, ainfo)
if(out == SquadPositionDetail.Blank) {
None
}
else {
Some(SquadPositionEntry(before.index, out))
}
case _ =>
None
}
}
private def MemberEquality(before : SquadPositionDetail, after : SquadPositionDetail) : SquadPositionDetail = {
SquadPositionDetail(
if(!before.is_closed.equals(after.is_closed)) after.is_closed else None,
if(!before.role.equals(after.role)) after.role else None,
if(!before.detailed_orders.equals(after.detailed_orders)) after.detailed_orders else None,
if(!before.requirements.equals(after.requirements)) after.requirements else None,
if(!before.char_id.equals(after.char_id)) after.char_id else None,
if(!before.name.equals(after.name)) after.name else None
)
}
}
def LoadSquadDefinition(squad : Squad, favorite : SquadLoadout) : Unit = {
squad.Task = favorite.task
squad.ZoneId = favorite.zone_id.getOrElse(squad.ZoneId)
squad.Availability.indices.foreach { index => squad.Availability.update(index, false) }
squad.Membership.foreach { position =>
position.Role = ""
position.Orders = ""
position.Requirements = Set()
}
favorite.members.foreach { position =>
squad.Availability.update(position.index, true)
val member = squad.Membership(position.index)
member.Role = position.role
member.Orders = position.orders
member.Requirements = position.requirements
}
}
def ValidOpenSquadPosition(squad : Squad, position : Int, member : Member, reqs : Set[CertificationType.Value]) : Boolean = {
squad.Availability(position) && member.CharId == 0 && reqs.intersect(member.Requirements) == member.Requirements
}
}

View file

@ -54,7 +54,7 @@ class SquadDefinitionActionMessageTest extends Specification {
case SquadDefinitionActionMessage(unk1, unk2, action) =>
unk1 mustEqual PlanetSideGUID(0)
unk2 mustEqual 3
action mustEqual SaveSquadDefinition()
action mustEqual SaveSquadFavorite()
case _ =>
ko
}
@ -65,7 +65,7 @@ class SquadDefinitionActionMessageTest extends Specification {
case SquadDefinitionActionMessage(unk1, unk2, action) =>
unk1 mustEqual PlanetSideGUID(0)
unk2 mustEqual 3
action mustEqual LoadSquadDefinition()
action mustEqual LoadSquadFavorite()
case _ =>
ko
}
@ -76,7 +76,7 @@ class SquadDefinitionActionMessageTest extends Specification {
case SquadDefinitionActionMessage(unk1, unk2, action) =>
unk1 mustEqual PlanetSideGUID(0)
unk2 mustEqual 3
action mustEqual ListSquadDefinition("Cops and Military Officers")
action mustEqual ListSquadFavorite("Cops and Military Officers")
case _ =>
ko
}
@ -87,7 +87,7 @@ class SquadDefinitionActionMessageTest extends Specification {
case SquadDefinitionActionMessage(unk1, unk2, action) =>
unk1 mustEqual PlanetSideGUID(0)
unk2 mustEqual 0
action mustEqual ListSquad()
action mustEqual RequestListSquad()
case _ =>
ko
}
@ -328,28 +328,28 @@ class SquadDefinitionActionMessageTest extends Specification {
}
"encode (03)" in {
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 3, SaveSquadDefinition())
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 3, SaveSquadFavorite())
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_03
}
"encode (03)" in {
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 3, LoadSquadDefinition())
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 3, LoadSquadFavorite())
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_04
}
"encode (07)" in {
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 3, ListSquadDefinition("Cops and Military Officers"))
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 3, ListSquadFavorite("Cops and Military Officers"))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_07
}
"encode (08)" in {
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, ListSquad())
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, RequestListSquad())
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_08

View file

@ -346,10 +346,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
case SquadServiceResponse(toChannel, response) =>
response match {
case SquadResponse.Init(infos) if infos.nonEmpty =>
case SquadResponse.ListSquadFavorite(line, task) =>
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), line, SquadAction.ListSquadFavorite(task)))
case SquadResponse.InitList(infos) if infos.nonEmpty =>
sendResponse(ReplicationStreamMessage(infos))
case SquadResponse.Update(infos) if infos.nonEmpty =>
case SquadResponse.UpdateList(infos) if infos.nonEmpty =>
val o = ReplicationStreamMessage(6, None,
infos.map { case (index, squadInfo) =>
SquadListing(index, squadInfo)
@ -364,7 +367,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
)
)
case SquadResponse.Remove(infos) if infos.nonEmpty =>
case SquadResponse.RemoveFromList(infos) if infos.nonEmpty =>
sendResponse(
ReplicationStreamMessage(1, None,
infos.map { index =>
@ -387,9 +390,21 @@ class WorldSessionActor extends Actor with MDCContextAware {
)
)
case SquadResponse.InitSquad(squad_guid) =>
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.Unknown(16)))
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.SetListSquad()))
case SquadResponse.Unknown17(squad, char_id) =>
sendResponse(
SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(33))
)
case SquadResponse.Membership(request_type, unk1, unk2, unk3, unk4, player_name, unk5, unk6) =>
sendResponse(SquadMembershipResponse(request_type, unk1, unk2, unk3, unk4, player_name, unk5, unk6))
case SquadResponse.Invite(from_char_id, to_char_id, name) =>
sendResponse(SquadMembershipResponse(SquadResponseType.Invite, 0, 0, from_char_id, Some(to_char_id), s"$name", false, Some(None)))
case SquadResponse.Join(squad, positionsToUpdate) =>
val leader = squad.Leader
val id = 11
@ -399,7 +414,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
membershipPositions.find({ case(member, _) => member.CharId == avatar.CharId }) match {
case Some((ourMember, ourIndex)) =>
//we are joining the squad
sendResponse(SquadMembershipResponse(SquadRequestType.Accept, 0, 0, player.CharId, Some(leader.CharId), player.Name, true, Some(None)))
sendResponse(SquadMembershipResponse(SquadResponseType.Accept, 0, 0, player.CharId, Some(leader.CharId), player.Name, true, Some(None)))
//load each member's entry (our own too)
membershipPositions.foreach { case(member, index) =>
sendResponse(SquadMemberEvent(0, id, member.CharId, index, Some(member.Name), Some(member.ZoneId), Some(0)))
@ -410,7 +425,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PlanetsideAttributeMessage(player.GUID, 31, id)) //associate with squad?
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, ourIndex)) //associate with member position in squad?
//a finalization? what does this do?
sendResponse(SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(18, hex"00".toBitVector.take(6))))
sendResponse(SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(18)))
case _ =>
//other player is joining our squad
//load each member's entry
@ -432,7 +447,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
positionsToUpdate.find({ case(member, _) => member == avatar.CharId }) match {
case Some((ourMember, ourIndex)) =>
//we are leaving the squad
sendResponse(SquadMembershipResponse(SquadRequestType.Leave, 0,1, avatar.CharId, Some(avatar.CharId), avatar.name, true, Some(None)))
sendResponse(SquadMembershipResponse(SquadResponseType.Leave, 0,1, avatar.CharId, Some(avatar.CharId), avatar.name, true, Some(None)))
//remove each member's entry (our own too)
positionsToUpdate.foreach { case(member, index) =>
sendResponse(SquadMemberEvent(1, id, member, index, None, None, None))
@ -444,7 +459,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, 0)) //disassociate with member position in squad?
sendResponse(PlanetsideAttributeMessage(player.GUID, 34, 4294967295L)) //unknown, perhaps unrelated?
//a finalization? what does this do?
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18, hex"00".toBitVector.take(6))))
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
case _ =>
//remove each member's entry
positionsToUpdate.foreach { case(member, index) =>
@ -455,6 +470,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case SquadResponse.UpdateMembers(squad, positions) =>
import services.teamwork.SquadAction.{Update => SAUpdate}
val id = 11
val pairedEntries = positions.collect {
case entry if squadUI.contains(entry.char_id) =>
(entry, squadUI(entry.char_id))
@ -476,12 +492,31 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(updatedEntries.nonEmpty) {
sendResponse(
SquadState(
PlanetSideGUID(11),
PlanetSideGUID(id),
updatedEntries.map { entry => SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos, 2,2, false, 429, None,None)}
)
)
}
case SquadResponse.SwapMember(squad, to_index, from_index) =>
//this failsafe is not supported by normal squad member operations
val member = squad.Membership(to_index)
val charId = member.CharId
val elem = squadUI(charId)
val id = 11
squadUI(charId) = SquadUIElement(elem.name, to_index, elem.zone, elem.health, elem.armor, elem.position)
sendResponse(SquadMemberEvent(1, id, charId, from_index, None, None, None))
sendResponse(SquadMemberEvent(0, id, charId, to_index, Some(elem.name), Some(elem.zone), Some(0)))
sendResponse(
SquadState(
PlanetSideGUID(id),
List(SquadStateInfo(charId, elem.health, elem.armor, elem.position, 2,2, false, 429, None,None))
)
)
if(charId == avatar.CharId) {
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, to_index))
}
case _ => ;
}
@ -956,7 +991,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
galaxyService ! Service.Join("galaxy") //for galaxy-wide messages
galaxyService ! Service.Join(s"${avatar.faction}") //for hotspots
squadService ! Service.Join(s"${avatar.faction}") //channel will be player.Faction
squadService ! Service.Join(avatar.name) //management of any lingering squad information connected to this player
squadService ! Service.Join(s"${avatar.CharId}") //channel will be player.CharId (in order to work with packets)
cluster ! InterstellarCluster.GetWorld("home3")
case InterstellarCluster.GiveWorld(zoneId, zone) =>
@ -2964,14 +2999,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
//AvatarAwardMessage
//DisplayAwardMessage
sendResponse(PlanetsideStringAttributeMessage(guid, 0, "Outfit Name"))
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(6, hex"".toBitVector)))
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(6)))
(0 to 9).foreach(line => {
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), line, SquadAction.ListSquadDefinition("")))
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), line, SquadAction.ListSquadFavorite("")))
})
sendResponse(SquadDetailDefinitionUpdateMessage.Init)
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0,SquadAction.Unknown(16, hex"".toBitVector)))
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0,SquadAction.Unknown(17, hex"".toBitVector)))
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0,SquadAction.Unknown(18, hex"".toBitVector)))
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0,SquadAction.Unknown(16)))
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0,SquadAction.SetListSquad()))
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0,SquadAction.Unknown(18)))
//MapObjectStateBlockMessage and ObjectCreateMessage?
//TacticsMessage?
//change the owner on our deployables (re-draw the icons for our deployables too)
@ -3419,7 +3454,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) =>
if(deadState == DeadState.Alive) {
if(!player.Crouching && is_crouching) { //SQUAD TESTING CODE
sendResponse(SquadMemberEvent(3, 11, 353380L, 3, None, Some(13), None))
sendResponse(SquadMembershipResponse(SquadResponseType.Unk01, 0, 0, player.CharId, None, "Dummy", false, Some(None)))
}
player.Position = pos
player.Velocity = vel