diff --git a/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala
index 58490f60e..64668c4e7 100644
--- a/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala
+++ b/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala
@@ -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
diff --git a/common/src/main/scala/net/psforever/objects/loadouts/SquadLoadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/SquadLoadout.scala
index 9039c5a07..029c58b7f 100644
--- a/common/src/main/scala/net/psforever/objects/loadouts/SquadLoadout.scala
+++ b/common/src/main/scala/net/psforever/objects/loadouts/SquadLoadout.scala
@@ -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)
diff --git a/common/src/main/scala/net/psforever/objects/teamwork/Squad.scala b/common/src/main/scala/net/psforever/objects/teamwork/Squad.scala
index b5e5d81da..5f653dcdd 100644
--- a/common/src/main/scala/net/psforever/objects/teamwork/Squad.scala
+++ b/common/src/main/scala/net/psforever/objects/teamwork/Squad.scala
@@ -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)
}
diff --git a/common/src/main/scala/net/psforever/packet/game/ReplicationStreamMessage.scala b/common/src/main/scala/net/psforever/packet/game/ReplicationStreamMessage.scala
index 233f5a59b..c34a057ce 100644
--- a/common/src/main/scala/net/psforever/packet/game/ReplicationStreamMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/ReplicationStreamMessage.scala
@@ -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.
diff --git a/common/src/main/scala/net/psforever/packet/game/SquadDefinitionActionMessage.scala b/common/src/main/scala/net/psforever/packet/game/SquadDefinitionActionMessage.scala
index 7b9b46472..32bbc68e2 100644
--- a/common/src/main/scala/net/psforever/packet/game/SquadDefinitionActionMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/SquadDefinitionActionMessage.scala
@@ -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{
* `0 ` - Display Squad
* `1 ` - Answer Squad Join Request
* `2 ` - UNKNOWN
- * `3 ` - Save Squad Definition
- * `4 ` - Load Squad Definition
+ * `3 ` - Save Squad Favorite
+ * `4 ` - Load Squad Favorite
+ * `5 ` - Delete Squad Favorite
* `6 ` - UNKNOWN
- * `8 ` - List Squad
- * `9 ` - UNKNOWN
+ * `8 ` - Request List Squad
+ * `9 ` - Stop List Squad
* `16` - UNKNOWN
- * `17` - UNKNOWN
+ * `17` - Set List Squad (ui)
* `18` - UNKNOWN
* `26` - Reset All
* `35` - Cancel Squad Search
@@ -302,12 +348,12 @@ object SquadAction{
* `15` - Select this Role for Yourself
* `37` - UNKNOWN
* `String`
- * `7 ` - List Squad Definition
+ * `7 ` - List Squad Favorite
* `19` - (Squad leader) Change Squad Purpose
* `Int :: Long`
* `12` - UNKNOWN
* `25` - (Squad leader) Change Squad Member Requirements - Weapons
- * `38` - UNKNOWN
+ * `38` - Assign Squad Member To Role
* `Int :: String`
* `23` - (Squad leader) Change Squad Member Requirements - Role
* `24` - (Squad leader) Change Squad Member Requirements - Detailed Orders
@@ -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]]
}
diff --git a/common/src/main/scala/net/psforever/packet/game/SquadDetailDefinitionUpdateMessage.scala b/common/src/main/scala/net/psforever/packet/game/SquadDetailDefinitionUpdateMessage.scala
index 9bda75277..225489b76 100644
--- a/common/src/main/scala/net/psforever/packet/game/SquadDetailDefinitionUpdateMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/SquadDetailDefinitionUpdateMessage.scala
@@ -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] {
diff --git a/common/src/main/scala/net/psforever/packet/game/SquadMembershipResponse.scala b/common/src/main/scala/net/psforever/packet/game/SquadMembershipResponse.scala
index ee1746ddd..a9f035183 100644
--- a/common/src/main/scala/net/psforever/packet/game/SquadMembershipResponse.scala
+++ b/common/src/main/scala/net/psforever/packet/game/SquadMembershipResponse.scala
@@ -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
+ *
+ * `request_type` (enum value) / `unk5` state (`false`/`true`)
+ * ----------------------------------------
+ * - `Invite` (0)
+ * false => [PROMPT] "`player_name` has invited you into a squad." [YES/NO]
+ * true => "You have invited `player_name` to join your squad."
+ * - `Unk01` (1)
+ * false => n/a
+ * true => n/a
+ * - `Accept` (2)
+ * false => "`player_name` has accepted your invitation to join into your squad.
+ * "You have formed a squad and are now that squad's commander." (if first time)
+ * true => "You have accepted an invitation to join a squad."
+ * "You have successfully joined a squad for the first time." (if first time)
+ * - `Reject` (3)
+ * false => "`player_name` does not want to join your squad at this time."
+ * true => "You have declined an invitation to join a squad."
+ * - `Cancel` (4)
+ * false => "`player_name` has withdrawn his invitation."
+ * true => "You have canceled your invitation to `player_name`."
+ * - `Leave` (5)
+ * false => "The Squad Leader has kicked you out of the squad."
+ * true => "You have kicked `player_name` out of the squad."
+ * - `Disband` (6)
+ * false => "The squad has been disbanded."
+ * true => "You have disbanded the squad."
+ * - `PlatoonInvite` (7)
+ * false => [PROMPT] "`player_name` has invited you into a platoon." [YES/NO]
+ * true => "You have invited `player_name`'s squad to join your platoon."
+ * - `PlatoonAccept` (8)
+ * false => "`player_name` has accepted your invitation to join into your platoon.
+ * "You have formed a platoon and are now that platoon commander." (if first time)
+ * true => "You have accepted an invitation to join a platoon."
+ * "You have successfully joined a platoon for the first time." (if first time)
+ * - `PlatoonReject` (9)
+ * false => "`player_name` does not want to join your platoon at this time."
+ * true => "You have declined an invitation to join a platoon."
+ * - `PlatoonCancel` (10)
+ * false => "`player_name` has withdrawn his invitation."
+ * true => "You have declined your invitation to `player_name`." (nonsense?)
+ * - `PlatoonLeave` (11)
+ * false => "The Platoon Leader has kicked you out of the platoon."
+ * true => "You have kicked `player_name`'s squad out of the platoon."
+ * - `PlatoonDisband` (12)
+ * false => "The platoon has been disbanded."
+ * 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]
}
diff --git a/common/src/main/scala/net/psforever/types/SquadRequestType.scala b/common/src/main/scala/net/psforever/types/SquadRequestType.scala
index 8d7e24351..1b22bc380 100644
--- a/common/src/main/scala/net/psforever/types/SquadRequestType.scala
+++ b/common/src/main/scala/net/psforever/types/SquadRequestType.scala
@@ -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")
+ }
}
diff --git a/common/src/main/scala/net/psforever/types/SquadResponseType.scala b/common/src/main/scala/net/psforever/types/SquadResponseType.scala
new file mode 100644
index 000000000..1f73ccc45
--- /dev/null
+++ b/common/src/main/scala/net/psforever/types/SquadResponseType.scala
@@ -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")
+ }
+}
diff --git a/common/src/main/scala/services/teamwork/SquadResponse.scala b/common/src/main/scala/services/teamwork/SquadResponse.scala
index e1ab03cb1..b74f153fa 100644
--- a/common/src/main/scala/services/teamwork/SquadResponse.scala
+++ b/common/src/main/scala/services/teamwork/SquadResponse.scala
@@ -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
}
diff --git a/common/src/main/scala/services/teamwork/SquadService.scala b/common/src/main/scala/services/teamwork/SquadService.scala
index 9aa5954bc..0f8ae794a 100644
--- a/common/src/main/scala/services/teamwork/SquadService.scala
+++ b/common/src/main/scala/services/teamwork/SquadService.scala
@@ -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 " 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
}
}
diff --git a/common/src/test/scala/game/SquadDefinitionActionMessageTest.scala b/common/src/test/scala/game/SquadDefinitionActionMessageTest.scala
index f36b61c54..09e2cd42a 100644
--- a/common/src/test/scala/game/SquadDefinitionActionMessageTest.scala
+++ b/common/src/test/scala/game/SquadDefinitionActionMessageTest.scala
@@ -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
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index 6064e729a..37619aeca 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -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