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 32bbc68e..11a56ee2 100644
--- a/common/src/main/scala/net/psforever/packet/game/SquadDefinitionActionMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/SquadDefinitionActionMessage.scala
@@ -16,9 +16,26 @@ import shapeless.{::, HNil}
abstract class SquadAction(val code : Int)
object SquadAction{
+ object SearchMode extends Enumeration {
+ type Type = Value
+
+ val
+ AnyPositions,
+ AvailablePositions,
+ SomeCertifications,
+ AllCertifications
+ = Value
+
+ implicit val codec : Codec[SearchMode.Value] = PacketHelpers.createEnumerationCodec(enum = this, uint(bits = 3))
+ }
+
final case class DisplaySquad() extends SquadAction(0)
- final case class AnswerSquadJoinRequest() extends SquadAction(1)
+ /**
+ * Dispatched from client to server to indicate a squad detail update that has no foundation entry to update?
+ * Not dissimilar from `DisplaySquad`.
+ */
+ final case class DisplayFullSquad() extends SquadAction(1)
final case class SaveSquadFavorite() extends SquadAction(3)
@@ -58,12 +75,14 @@ object SquadAction{
final case class LocationFollowsSquadLead(state : Boolean) extends SquadAction(31)
- final case class SearchForSquadsWithParticularRole(u1: String, u2 : Long, u3: Int, u4 : Int) extends SquadAction(34)
+ final case class SearchForSquadsWithParticularRole(role: String, requirements : Set[CertificationType.Value], zone_id: Int, mode : SearchMode.Value) extends SquadAction(34)
final case class CancelSquadSearch() extends SquadAction(35)
final case class AssignSquadMemberToRole(position : Int, char_id : Long) extends SquadAction(38)
+ final case class NoSquadSearchResults() extends SquadAction(39)
+
final case class FindLfsSoldiersForRole(state : Int) extends SquadAction(40)
final case class CancelFind() extends SquadAction(41)
@@ -91,10 +110,10 @@ object SquadAction{
}
)
- val answerSquadJoinRequestCodec = everFailCondition.xmap[AnswerSquadJoinRequest] (
- _ => AnswerSquadJoinRequest(),
+ val displayFullSquadCodec = everFailCondition.xmap[DisplayFullSquad] (
+ _ => DisplayFullSquad(),
{
- case AnswerSquadJoinRequest() => None
+ case DisplayFullSquad() => None
}
)
@@ -243,12 +262,12 @@ object SquadAction{
PacketHelpers.encodedWideStringAligned(6) ::
ulongL(46) ::
uint16L ::
- uintL(3)).xmap[SearchForSquadsWithParticularRole] (
+ SearchMode.codec).xmap[SearchForSquadsWithParticularRole] (
{
- case u1 :: u2 :: u3 :: u4 :: HNil => SearchForSquadsWithParticularRole(u1, u2, u3, u4)
+ case u1 :: u2 :: u3 :: u4 :: HNil => SearchForSquadsWithParticularRole(u1, CertificationType.fromEncodedLong(u2), u3, u4)
},
{
- case SearchForSquadsWithParticularRole(u1, u2, u3, u4) => u1 :: u2 :: u3 :: u4 :: HNil
+ case SearchForSquadsWithParticularRole(u1, u2, u3, u4) => u1 :: CertificationType.toEncodedLong(u2) :: u3 :: u4 :: HNil
}
)
@@ -268,6 +287,13 @@ object SquadAction{
}
)
+ val noSquadSearchResultsCodec = everFailCondition.xmap[NoSquadSearchResults] (
+ _ => NoSquadSearchResults(),
+ {
+ case NoSquadSearchResults() => None
+ }
+ )
+
val findLfsSoldiersForRoleCodec = uint4.xmap[FindLfsSoldiersForRole] (
state => FindLfsSoldiersForRole(state),
{
@@ -326,6 +352,7 @@ object SquadAction{
* `18` - UNKNOWN
* `26` - Reset All
* `35` - Cancel Squad Search
+ * `39` - No Squad Search Results
* `41` - Cancel Find
* `42` - UNKNOWN
* `43` - UNKNOWN
@@ -388,7 +415,7 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
import scala.annotation.switch
((code : @switch) match {
case 0 => displaySquadCodec
- case 1 => answerSquadJoinRequestCodec
+ case 1 => displayFullSquadCodec
case 3 => saveSquadFavoriteCodec
case 4 => loadSquadFavoriteCodec
case 5 => deleteSquadFavoriteCodec
@@ -411,12 +438,13 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
case 34 => searchForSquadsWithParticularRoleCodec
case 35 => cancelSquadSearchCodec
case 38 => assignSquadMemberToRoleCodec
+ case 39 => noSquadSearchResultsCodec
case 40 => findLfsSoldiersForRoleCodec
case 41 => cancelFindCodec
case 2 | 6 | 11 |
12 | 13 | 14 | 16 |
18 | 29 | 30 | 32 | 33 |
- 36 | 37 | 39 | 42 | 43 => unknownCodec(code)
+ 36 | 37 | 42 | 43 => unknownCodec(code)
case _ => failureCodec(code)
}).asInstanceOf[Codec[SquadAction]]
}
diff --git a/common/src/main/scala/net/psforever/packet/game/SquadMemberEvent.scala b/common/src/main/scala/net/psforever/packet/game/SquadMemberEvent.scala
index 503254a5..51b74955 100644
--- a/common/src/main/scala/net/psforever/packet/game/SquadMemberEvent.scala
+++ b/common/src/main/scala/net/psforever/packet/game/SquadMemberEvent.scala
@@ -12,18 +12,18 @@ object MemberEvent extends Enumeration {
val
Add,
Remove,
- Unknown2,
+ Promote,
UpdateZone,
Unknown4
= Value
- implicit val codec = PacketHelpers.createEnumerationCodec(this, uint(3))
+ implicit val codec = PacketHelpers.createEnumerationCodec(enum = this, uint(bits = 3))
}
-final case class SquadMemberEvent(unk1 : Int,
+final case class SquadMemberEvent(action : MemberEvent.Value,
unk2 : Int,
char_id : Long,
- member_position : Int,
+ position : Int,
player_name : Option[String],
zone_number : Option[Int],
unk7 : Option[Long])
@@ -34,40 +34,46 @@ final case class SquadMemberEvent(unk1 : Int,
}
object SquadMemberEvent extends Marshallable[SquadMemberEvent] {
- def apply(unk1 : Int, unk2 : Int, char_id : Long, member_position : Int) : SquadMemberEvent =
- SquadMemberEvent(unk1, unk2, char_id, member_position, None, None, None)
+ def apply(action : MemberEvent.Value, unk2 : Int, char_id : Long, position : Int) : SquadMemberEvent =
+ SquadMemberEvent(action, unk2, char_id, position, None, None, None)
- def apply(unk2 : Int, char_id : Long, member_position : Int, player_name : String, zone_number : Int, unk7 : Long) : SquadMemberEvent =
- SquadMemberEvent(0, unk2, char_id, member_position, Some(player_name), Some(zone_number), Some(unk7))
+ def Add(unk2 : Int, char_id : Long, position : Int, player_name : String, zone_number : Int, unk7 : Long) : SquadMemberEvent =
+ SquadMemberEvent(MemberEvent.Add, unk2, char_id, position, Some(player_name), Some(zone_number), Some(unk7))
- def apply(unk2 : Int, char_id : Long, member_position : Int, zone_number : Int) : SquadMemberEvent =
- SquadMemberEvent(3, unk2, char_id, member_position, None, Some(zone_number), None)
+ def Remove(unk2 : Int, char_id : Long, position : Int) : SquadMemberEvent =
+ SquadMemberEvent(MemberEvent.Remove, unk2, char_id, position, None, None, None)
- def apply(unk2 : Int, char_id : Long, member_position : Int, unk7 : Long) : SquadMemberEvent =
- SquadMemberEvent(4, unk2, char_id, member_position, None, None, Some(unk7))
+ def Promote(unk2 : Int, char_id : Long) : SquadMemberEvent =
+ SquadMemberEvent(MemberEvent.Promote, unk2, char_id, 0, None, None, None)
+
+ def UpdateZone(unk2 : Int, char_id : Long, position : Int, zone_number : Int) : SquadMemberEvent =
+ SquadMemberEvent(MemberEvent.UpdateZone, unk2, char_id, position, None, Some(zone_number), None)
+
+ def Unknown4(unk2 : Int, char_id : Long, position : Int, unk7 : Long) : SquadMemberEvent =
+ SquadMemberEvent(MemberEvent.Unknown4, unk2, char_id, position, None, None, Some(unk7))
implicit val codec : Codec[SquadMemberEvent] = (
- ("unk1" | uint(3)) >>:~ { unk1 =>
+ ("action" | MemberEvent.codec) >>:~ { action =>
("unk2" | uint16L) ::
("char_id" | uint32L) ::
- ("member_position" | uint4) ::
- conditional(unk1 == 0, "player_name" | PacketHelpers.encodedWideStringAligned(1)) ::
- conditional(unk1 == 0 || unk1 == 3, "zone_number" | uint16L) ::
- conditional(unk1 == 0 || unk1 == 4, "unk7" | uint32L)
+ ("position" | uint4) ::
+ conditional(action == MemberEvent.Add, "player_name" | PacketHelpers.encodedWideStringAligned(1)) ::
+ conditional(action == MemberEvent.Add || action == MemberEvent.UpdateZone, "zone_number" | uint16L) ::
+ conditional(action == MemberEvent.Add || action == MemberEvent.Unknown4, "unk7" | uint32L)
}).exmap[SquadMemberEvent] (
{
- case unk1 :: unk2 :: char_id :: member_position :: player_name :: zone_number :: unk7 :: HNil =>
- Attempt.Successful(SquadMemberEvent(unk1, unk2, char_id, member_position, player_name, zone_number, unk7))
+ case action :: unk2 :: char_id :: member_position :: player_name :: zone_number :: unk7 :: HNil =>
+ Attempt.Successful(SquadMemberEvent(action, unk2, char_id, member_position, player_name, zone_number, unk7))
},
{
- case data @ SquadMemberEvent(0, unk2, char_id, member_position, Some(player_name), Some(zone_number), Some(unk7)) =>
- Attempt.Successful(0 :: unk2 :: char_id :: member_position :: Some(player_name) :: Some(zone_number) :: Some(unk7) :: HNil)
- case data @ SquadMemberEvent(3, unk2, char_id, member_position, None, Some(zone_number), None) =>
- Attempt.Successful(3 :: unk2 :: char_id :: member_position :: None :: Some(zone_number) :: None :: HNil)
- case data @ SquadMemberEvent(4, unk2, char_id, member_position, None, None, Some(unk7)) =>
- Attempt.Successful(4 :: unk2 :: char_id :: member_position :: None :: None :: Some(unk7) :: HNil)
- case data @ SquadMemberEvent(unk1, unk2, char_id, member_position, None, None, None) =>
- Attempt.Successful(unk1 :: unk2 :: char_id :: member_position :: None :: None :: None :: HNil)
+ case SquadMemberEvent(MemberEvent.Add, unk2, char_id, member_position, Some(player_name), Some(zone_number), Some(unk7)) =>
+ Attempt.Successful(MemberEvent.Add :: unk2 :: char_id :: member_position :: Some(player_name) :: Some(zone_number) :: Some(unk7) :: HNil)
+ case SquadMemberEvent(MemberEvent.UpdateZone, unk2, char_id, member_position, None, Some(zone_number), None) =>
+ Attempt.Successful(MemberEvent.UpdateZone :: unk2 :: char_id :: member_position :: None :: Some(zone_number) :: None :: HNil)
+ case SquadMemberEvent(MemberEvent.Unknown4, unk2, char_id, member_position, None, None, Some(unk7)) =>
+ Attempt.Successful(MemberEvent.Unknown4 :: unk2 :: char_id :: member_position :: None :: None :: Some(unk7) :: HNil)
+ case SquadMemberEvent(action, unk2, char_id, member_position, None, None, None) =>
+ Attempt.Successful(action :: unk2 :: char_id :: member_position :: None :: None :: None :: HNil)
case data =>
Attempt.Failure(Err(s"SquadMemberEvent can not encode with this pattern - $data"))
}
diff --git a/common/src/main/scala/services/teamwork/SquadResponse.scala b/common/src/main/scala/services/teamwork/SquadResponse.scala
index 26215f02..562082ae 100644
--- a/common/src/main/scala/services/teamwork/SquadResponse.scala
+++ b/common/src/main/scala/services/teamwork/SquadResponse.scala
@@ -18,10 +18,14 @@ object SquadResponse {
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 WantsSquadPosition(bid_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 AssignMember(squad : Squad, from_index : Int, to_index : Int) extends Response
+ final case class PromoteMember(squad : Squad, char_id : Long, from_index : Int, to_index : Int) extends Response
final case class Detail(guid : PlanetSideGUID, squad_detail : SquadDetail) extends Response
+
+ final case class SquadSearchResults() extends Response
}
diff --git a/common/src/main/scala/services/teamwork/SquadService.scala b/common/src/main/scala/services/teamwork/SquadService.scala
index e3dbe592..8f3b0de2 100644
--- a/common/src/main/scala/services/teamwork/SquadService.scala
+++ b/common/src/main/scala/services/teamwork/SquadService.scala
@@ -22,13 +22,14 @@ class SquadService extends Actor {
private var memberToSquad : mutable.LongMap[Squad] = mutable.LongMap[Squad]()
private var idToSquad : TrieMap[PlanetSideGUID, Squad] = new TrieMap[PlanetSideGUID, Squad]()
private var idToSwitchboard : TrieMap[PlanetSideGUID, ActorRef] = new TrieMap[PlanetSideGUID, ActorRef]()
- private var i : Int = 1
+ private var sid : Int = 1
private val publishedLists : TrieMap[PlanetSideEmpire.Value, ListBuffer[SquadInfo]] = TrieMap[PlanetSideEmpire.Value, ListBuffer[SquadInfo]](
PlanetSideEmpire.TR -> ListBuffer.empty,
PlanetSideEmpire.NC -> ListBuffer.empty,
PlanetSideEmpire.VS -> ListBuffer.empty
)
- private val bids : mutable.LongMap[PositionBid] = mutable.LongMap[PositionBid]()
+ private val invites : mutable.LongMap[Invitation] = mutable.LongMap[Invitation]()
+ private val queuedInvites : mutable.LongMap[List[Invitation]] = mutable.LongMap[List[Invitation]]()
private val viewDetails : mutable.LongMap[PlanetSideGUID] = mutable.LongMap[PlanetSideGUID]()
private [this] val log = org.log4s.getLogger
@@ -36,56 +37,70 @@ class SquadService extends Actor {
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
- testSquad.Membership(4).Armor = 64
- testSquad.Membership(4).Position = Vector3(3675.0f, 4789.8047f, 63.21875f)
- idToSquad(PlanetSideGUID(3)) = testSquad
- testSquad.Listed = true
- UpdateSquadList(testSquad, None)
+// 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
+// testSquad.Membership(4).Armor = 64
+// testSquad.Membership(4).Position = Vector3(3675.0f, 4789.8047f, 63.21875f)
+// idToSquad(PlanetSideGUID(3)) = testSquad
+// testSquad.Listed = true
+// UpdateSquadList(testSquad, None)
}
def GetNextSquadId() : PlanetSideGUID = {
- val out = i
- val j = i + 1
+ val out = sid
+ val j = sid + 1
if(j == 65536) {
- i = 1
+ sid = 1
}
else {
- i = j
+ sid = j
}
PlanetSideGUID(out)
}
+ def TryResetSquadId() : Boolean = {
+ if(idToSquad.isEmpty) {
+ sid = 1
+ true
+ }
+ else {
+ false
+ }
+ }
+
def GetParticipatingSquad(player : Player) : Option[Squad] = {
- memberToSquad.get(player.CharId) match {
+ GetParticipatingSquad(player.CharId)
+ }
+
+ def GetParticipatingSquad(charId : Long) : Option[Squad] = {
+ memberToSquad.get(charId) match {
case opt @ Some(_) =>
opt
case None =>
@@ -114,24 +129,49 @@ class SquadService extends Actor {
}
}
- def StartSquad(player : Player) : Squad = {
+ def GetLeadingSquad(charId : Long, opt : Option[Squad]) : Option[Squad] = {
+ opt.orElse(memberToSquad.get(charId)) match {
+ case Some(squad) =>
+ if(squad.Leader.CharId == charId) {
+ Some(squad)
+ }
+ else {
+ None
+ }
+ case _ =>
+ None
+ }
+ }
+
+ def CreateSquad(player : Player) : Squad = {
val charId = player.CharId
val faction = player.Faction
- val id = GetNextSquadId()
val name = player.Name
- val squad = new Squad(id, faction)
+ val squad = new Squad(GetNextSquadId(), faction)
val leadPosition = squad.Membership(squad.LeaderPositionIndex)
leadPosition.Name = name
- leadPosition.CharId = charId
+ leadPosition.CharId = player.CharId
leadPosition.Health = player.Health
leadPosition.Armor = player.Armor
leadPosition.Position = player.Position
leadPosition.ZoneId = 1
+ log.info(s"$name-$faction has started a new squad")
+ squad
+ }
+
+ def StartSquad(squad : Squad) : Squad = {
+ val charId = squad.Leader.CharId
+ val id = squad.GUID
val switchboard = context.actorOf(Props[SquadSwitchboard], s"squad${id.guid}")
memberToSquad += charId -> squad
idToSquad += id -> squad
idToSwitchboard += id -> switchboard
- log.info(s"$name-$faction has started a new squad")
+ squad
+ }
+
+ def StartSquad(player : Player) : Squad = {
+ val squad = CreateSquad(player)
+ StartSquad(squad)
squad
}
@@ -199,137 +239,253 @@ class SquadService extends Actor {
case Service.Leave(None) | Service.LeaveAll() => ;
case SquadServiceMessage(tplayer, squad_action) => squad_action match {
- case SquadAction.Membership(request_type, char_id, optional_char_id, _, _) => request_type match {
- case SquadRequestType.Invite =>
- //char_id is the inviter, e.g., the (prospective) squad leader
- //this is just busy work; for actual joining operations, see SquadRequestType.Accept
- (optional_char_id, memberToSquad.get(char_id)) match {
- case (Some(invitee), Some(squad)) =>
- bids(invitee) = VacancyBid(char_id, squad.GUID)
- log.info(s"$invitee has been invited to squad ${squad.Task} by $char_id")
- SquadEvents.publish( SquadServiceResponse(s"/$invitee/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, char_id, Some(invitee), tplayer.Name, false, Some(None))) )
- SquadEvents.publish( SquadServiceResponse(s"/$char_id/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitee, Some(char_id), tplayer.Name, true, Some(None))) )
- case (Some(invitee), None) =>
- //the inviter does not currently belong to a squad; check for an existing placeholder, or create a new one
- val ourSquad = {
- bids.find { case (inviter, _) => inviter == char_id } match {
- case Some((_, SpontaneousBid(_, _squad))) =>
- _squad //borrow
- case _ =>
- val _squad = StartSquad(tplayer)
- memberToSquad.remove(char_id) //completely unlist until assured the squad is necessary
- _squad
- }
- }
- bids(invitee) = SpontaneousBid(char_id, ourSquad)
- SquadEvents.publish( SquadServiceResponse(s"/$invitee/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, char_id, Some(invitee), tplayer.Name, false, Some(None))) )
- SquadEvents.publish( SquadServiceResponse(s"/$char_id/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitee, Some(char_id), tplayer.Name, true, Some(None))) )
- case _ => ;
- }
+ case SquadAction.Membership(SquadRequestType.Invite, invitingPlayer, Some(invitedPlayer), _, _) =>
+ //this is just busy work; for actual joining operations, see SquadRequestType.Accept
+// FindBid(invitingPlayer, invitedPlayer) match {
+// case Some(bid) =>
+// //invitingPlayer and invitedPlayer have both tried to join each others's squads
+// //treat this junction as consent
+// self ! SquadServiceMessage(tplayer, SquadAction.Membership(SquadRequestType.Accept, invitingPlayer, Some(invitedPlayer), "", None))
+// case _ => ;
+// }
+ (memberToSquad.get(invitingPlayer), memberToSquad.get(invitedPlayer)) match {
+ case (Some(squad1), Some(squad2)) =>
+ //both players are in squads
+ if(squad1.GUID == squad2.GUID) {
+ //both players are in the same squad; no need to do anything
+ }
+ else {
+ //we might do some platoon chicanery with this case later
+ //TODO platoons
+ }
- case SquadRequestType.Accept =>
- //char_id is the invitee, e.g., the person joining the squad
- bids.remove(char_id) match {
- case Some(NormalBid(_/*inviterCharId*/, squadGUID, line)) if idToSquad.get(squadGUID).nonEmpty =>
- //player requested to join a squad's specific position
- JoinSquad(tplayer, idToSquad(squadGUID), line)
+ case (Some(squad), None) =>
+ //the classic situation
+ log.info(s"$invitedPlayer has been invited to squad ${squad.Task} by $invitingPlayer")
+ val leader = squad.Leader
+ val leaderCharId = leader.CharId
+ val bid = VacancyInvite(leaderCharId, leader.Name, squad.GUID)
+ AddInvite(invitedPlayer, bid) match {
+ case out @ Some(_) if out.contains(bid) =>
+ SquadEvents.publish(SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, leaderCharId, Some(invitedPlayer), tplayer.Name, false, Some(None))))
+ SquadEvents.publish(SquadServiceResponse(s"/$leaderCharId/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitedPlayer, Some(leaderCharId), tplayer.Name, true, Some(None))))
+ case Some(_) =>
+ SquadEvents.publish(SquadServiceResponse(s"/$leaderCharId/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitedPlayer, Some(leaderCharId), tplayer.Name, true, Some(None))))
+ case _ => ;
+ }
- case Some(VacancyBid(inviterCharId, squadGUID)) if idToSquad.get(squadGUID).nonEmpty =>
- //we were invited by the squad leader into an existing squad
- val squad = idToSquad(squadGUID)
+ case (None, Some(squad)) =>
+ //flip around the roles - the inviting becomes the invited
+ //TODO needs work
+ log.info(s"$invitedPlayer has asked $invitingPlayer for an invition to squad ${squad.Task}")
+ val bid = VacancyInvite(invitedPlayer, "", squad.GUID)
+ AddInvite(invitingPlayer, bid) match {
+ case out @ Some(_) if out.contains(bid) =>
+ SquadEvents.publish(SquadServiceResponse(s"/$invitingPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitedPlayer, Some(invitingPlayer), tplayer.Name, false, Some(None))))
+ SquadEvents.publish(SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitingPlayer, Some(invitedPlayer), tplayer.Name, true, Some(None))))
+ case Some(_) =>
+ SquadEvents.publish(SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitingPlayer, Some(invitedPlayer), tplayer.Name, true, Some(None))))
+ case _ => ;
+ }
+
+ case (None, None) =>
+ //neither the invited player nor the inviting player belong to any squad
+ log.info(s"$invitedPlayer has been invited to join $invitingPlayer's spontaneous squad")
+ val bid = SpontaneousInvite(tplayer)
+ AddInvite(invitedPlayer, bid) match {
+ case out @ Some(_) if out.contains(bid) =>
+ SquadEvents.publish(SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitingPlayer, Some(invitedPlayer), tplayer.Name, false, Some(None))))
+ SquadEvents.publish(SquadServiceResponse(s"/$invitingPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitedPlayer, Some(invitingPlayer), tplayer.Name, true, Some(None))))
+ case Some(_) =>
+ SquadEvents.publish(SquadServiceResponse(s"/$invitingPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitedPlayer, Some(invitingPlayer), tplayer.Name, true, Some(None))))
+ case _ => ;
+ }
+
+ case _ => ;
+ }
+
+ case SquadAction.Membership(SquadRequestType.Accept, invitedPlayer, _, _, _) =>
+ val acceptedInvite = RemoveInvite(invitedPlayer)
+ acceptedInvite match {
+ case Some(BidForPosition(petitioner, guid, position)) if idToSquad.get(guid).nonEmpty =>
+ //player requested to join a squad's specific position
+ //invitedPlayer is actually the squad leader; petitioner is the actual "invitedPlayer"
+ if(memberToSquad.get(petitioner.CharId).isEmpty) {
+ JoinSquad(petitioner, idToSquad(guid), position)
+ }
+ else {
+ log.warn("Accept -> Bid: the invited player is already a member of a squad and can not join a second one")
+ }
+
+ case Some(VacancyInvite(invitingPlayer, _, guid))
+ if idToSquad.get(guid).nonEmpty =>
+ //we were invited by the squad leader into an existing squad
+ if(memberToSquad.get(invitedPlayer).isEmpty) {
+ val squad = idToSquad(guid)
squad.Membership.zipWithIndex.find({ case (member, index) =>
ValidOpenSquadPosition(squad, index, member, tplayer.Certifications)
}) match {
case Some((_, line)) =>
- SquadEvents.publish( SquadServiceResponse(s"/$inviterCharId/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, inviterCharId, Some(char_id), tplayer.Name, false, Some(None))) )
- SquadEvents.publish( SquadServiceResponse(s"/$char_id/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, char_id, Some(inviterCharId), "", true, Some(None))) )
+ SquadEvents.publish(SquadServiceResponse(s"/$invitingPlayer/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitingPlayer, Some(invitedPlayer), tplayer.Name, false, Some(None))))
+ SquadEvents.publish(SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitedPlayer, Some(invitingPlayer), "", true, Some(None))))
JoinSquad(tplayer, squad, line)
+ RemoveQueuedInvites(invitedPlayer) //TODO deal with these somehow
case _ => ;
}
+ }
+ else {
+ log.warn("Accept -> Invite: the invited player is already a member of a squad and can not join a second one")
+ }
- case Some(SpontaneousBid(inviterCharId, placeholderSquad)) =>
- //we were invited by someone into a new squad they would form
- (GetParticipatingSquad(tplayer) match {
- case Some(participating) =>
- if(participating.Leader.CharId == inviterCharId) {
- Some(participating)
+ case Some(SpontaneousInvite(invitingPlayer)) =>
+ //we were invited by someone into a new squad they would form
+ val invitingPlayerCharId = invitingPlayer.CharId
+ (GetParticipatingSquad(invitingPlayer) match {
+ case Some(participating) =>
+ if(participating.Leader.CharId == invitingPlayerCharId) {
+ Some(participating)
+ }
+ else {
+ //inviter joined a squad and is not its leader; bounce this request off of the squad leader
+ participating.Membership.zipWithIndex.find({ case (member, index) =>
+ ValidOpenSquadPosition(participating, index, member, tplayer.Certifications)
+ }) match {
+ case Some((_, line)) =>
+ val bid = BidForPosition(tplayer, participating.GUID, line)
+ AddInvite(participating.Leader.CharId, bid) match {
+ case out @ Some(_) if out.contains(bid) =>
+ HandleBidForPosition(bid, tplayer)
+ case _ => ;
+ }
+ case _ => ;
}
- else {
- //inviter joined a squad and is not its leader; bounce this request off of the squad leader
- //TODO squad leader receives " wants to join squad" prompt
- val leaderCharId = participating.Leader.CharId
- bids(char_id) = VacancyBid(leaderCharId, participating.GUID) //reframed request
- 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 {
+ None
+ }
+ case None =>
+ val squad = StartSquad(invitingPlayer)
+ squad.Task = s"${tplayer.Name}'s Squad"
+ SquadEvents.publish(SquadServiceResponse(s"/$invitingPlayer/Squad", SquadResponse.InitSquad(squad.GUID)))
+ Some(squad)
+ }) match {
case Some(squad) =>
squad.Membership.zipWithIndex.find({ case (member, index) =>
ValidOpenSquadPosition(squad, index, member, tplayer.Certifications)
}) match {
case Some((_, line)) =>
- SquadEvents.publish( SquadServiceResponse(s"/$inviterCharId/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, inviterCharId, Some(char_id), tplayer.Name, false, Some(None))) )
- SquadEvents.publish( SquadServiceResponse(s"/$char_id/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, char_id, Some(inviterCharId), "", true, Some(None))) )
+ SquadEvents.publish( SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitedPlayer, Some(invitingPlayerCharId), "", true, Some(None))) )
JoinSquad(tplayer, squad, line)
+ SquadEvents.publish( SquadServiceResponse(s"/$invitingPlayerCharId/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitingPlayerCharId, Some(invitedPlayer), tplayer.Name, false, Some(None))) )
+ RemoveQueuedInvites(invitedPlayer) //TODO deal with these somehow
case _ => ;
}
case None => ;
- }
-
- case _ => ;
- }
-
- case SquadRequestType.Leave =>
- //char_id is the player leaving
- val squad = memberToSquad(char_id)
- val leader = squad.Leader.CharId
- if(char_id == leader) {
- //squad leader is leaving his own squad, so it will be disbanded
- squad.Membership
- .filterNot { _.CharId == leader }
- .foreach { member =>
- val charId = member.CharId
- SquadEvents.publish(SquadServiceResponse(s"/$charId/Squad", SquadResponse.Membership(SquadResponseType.Disband, 0, 0, charId, None, "", false, Some(None))))
- }
- CloseOutSquad(squad)
- SquadEvents.publish(SquadServiceResponse(s"/$leader/Squad", SquadResponse.Membership(SquadResponseType.Disband, 0, 0, leader, None, "", true, Some(None))))
- }
- else {
- if(optional_char_id.contains(char_id)) {
- //leaving the squad of our own accord
- LeaveSquad(tplayer, squad)
}
- else if(optional_char_id.contains(leader)) {
- //kicked by the squad leader
- SquadEvents.publish( SquadServiceResponse(s"/$char_id/Squad", SquadResponse.Membership(SquadResponseType.Leave, 0, 0, char_id, Some(leader), tplayer.Name, false, Some(None))) )
- SquadEvents.publish( SquadServiceResponse(s"/$leader/Squad", SquadResponse.Membership(SquadResponseType.Leave, 0, 0, leader, Some(char_id), "", true, Some(None))) )
- LeaveSquad(tplayer, squad)
+
+ case None =>
+ //the invite either timed-out or was withdrawn; select a new one?
+ NextInvite(invitedPlayer) match {
+ case Some(bid : BidForPosition) if !acceptedInvite.contains(bid) =>
+ HandleBidForPosition(bid, tplayer)
+ case Some(bid) if !acceptedInvite.contains(bid) =>
+ SquadEvents.publish(SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, bid.InviterCharId, Some(invitedPlayer), bid.InviterName, false, Some(None))))
+ case None => ;
}
- }
+ }
- case SquadRequestType.Reject =>
- //a player has opted out of joining a squad
- (bids.remove(char_id) match {
- case Some(SpontaneousBid(inviterCharId, squad)) if squad.Leader.CharId != char_id =>
- (inviterCharId, squad)
- case Some(VacancyBid(inviterCharId, guid)) if idToSquad(guid).Leader.CharId != char_id =>
- (inviterCharId, idToSquad.get(guid))
- case _ => ;
- (0L, None)
- }) match {
- case (inviterCharId, Some(squad)) =>
- SquadEvents.publish( SquadServiceResponse(s"/$inviterCharId/Squad", SquadResponse.Membership(SquadResponseType.Reject, 0, 0, inviterCharId, Some(char_id), tplayer.Name, false, Some(None))) )
- SquadEvents.publish( SquadServiceResponse(s"/$char_id/Squad", SquadResponse.Membership(SquadResponseType.Reject, 0, 0, char_id, Some(inviterCharId), "", true, Some(None))) )
- case _ => ;
+ case SquadAction.Membership(SquadRequestType.Leave, leavingPlayer, optionalPlayer, _, _) =>
+ val squad = memberToSquad(leavingPlayer)
+ val leader = squad.Leader.CharId
+ if(leavingPlayer == leader || squad.Size == 2) {
+ //squad leader is leaving his own squad, so it will be disbanded
+ //alternately, squad is only composed of two people, so it will be closed-out when one of them leaves
+ val membership = squad.Membership.collect { case member if member.CharId > 0 => member.CharId }
+ membership.foreach { charId =>
+ SquadEvents.publish(SquadServiceResponse(s"/$charId/Squad", SquadResponse.Membership(SquadResponseType.Leave, 0, 0, charId, None, "", false, Some(None))))
}
+ CloseOutSquad(squad)
+ SquadEvents.publish(SquadServiceResponse(s"/$leader/Squad", SquadResponse.Membership(SquadResponseType.Disband, 0, 0, leader, None, "", true, Some(None))))
+ membership
+ .filterNot(_ == leader)
+ .foreach { charId =>
+ SquadEvents.publish(SquadServiceResponse(s"/$charId/Squad", SquadResponse.Membership(SquadResponseType.Disband, 0, 0, charId, None, "", false, Some(None))))
+ }
+ SquadEvents.publish( SquadServiceResponse(s"/$leader/Squad", SquadResponse.InitSquad(PlanetSideGUID(0))) )
+ SquadEvents.publish( SquadServiceResponse(s"/$leader/Squad", SquadResponse.Detail(PlanetSideGUID(0), SquadDetail().Complete)) )
+ }
+ else {
+ if(optionalPlayer.contains(leavingPlayer)) {
+ //leaving the squad of own accord
+ LeaveSquad(tplayer, squad)
+ }
+ else if(optionalPlayer.contains(leader)) {
+ //kicked by the squad leader
+ SquadEvents.publish( SquadServiceResponse(s"/$leavingPlayer/Squad", SquadResponse.Membership(SquadResponseType.Leave, 0, 0, leavingPlayer, Some(leader), tplayer.Name, false, Some(None))) )
+ SquadEvents.publish( SquadServiceResponse(s"/$leader/Squad", SquadResponse.Membership(SquadResponseType.Leave, 0, 0, leader, Some(leavingPlayer), "", true, Some(None))) )
+ LeaveSquad(tplayer, squad)
+ }
+ }
- case _ => ;
- }
+ case SquadAction.Membership(SquadRequestType.Reject, rejectingPlayer, optionalPlayer, _, _) =>
+ val rejectedBid = RemoveInvite(rejectingPlayer)
+ //(A, B) -> person who made the rejection, person who was rejected
+ (rejectedBid match {
+ case Some(SpontaneousInvite(invitingPlayer)) =>
+ //rejectingPlayer is the would-be squad member
+ (Some(rejectingPlayer), Some(invitingPlayer.CharId))
+ case Some(VacancyInvite(invitingPlayer, _, guid)) if idToSquad(guid).Leader.CharId != rejectingPlayer =>
+ //rejectingPlayer is the would-be squad member
+ (Some(rejectingPlayer), Some(invitingPlayer))
+ case Some(BidForPosition(_, guid, _)) if idToSquad(guid).Leader.CharId != rejectingPlayer =>
+ //rejectingPlayer is the squad leader
+ (Some(rejectingPlayer), None)
+ case _ => ;
+ (None, None)
+ }) match {
+ case (Some(rejected), Some(invited)) =>
+ SquadEvents.publish( SquadServiceResponse(s"/$rejected/Squad", SquadResponse.Membership(SquadResponseType.Reject, 0, 0, rejected, Some(invited), "", true, Some(None))) )
+ SquadEvents.publish( SquadServiceResponse(s"/$invited/Squad", SquadResponse.Membership(SquadResponseType.Reject, 0, 0, invited, Some(rejected), tplayer.Name, false, Some(None))) )
+ case (Some(rejected), None) =>
+ SquadEvents.publish( SquadServiceResponse(s"/$rejected/Squad", SquadResponse.Membership(SquadResponseType.Reject, 0, 0, rejected, Some(rejected), "", true, Some(None))) )
+ case _ => ;
+ }
+ NextInvite(rejectingPlayer) match {
+ case Some(bid : BidForPosition) if rejectedBid.isEmpty || !rejectedBid.contains(bid) =>
+ HandleBidForPosition(bid, tplayer)
+ case Some(bid) if rejectedBid.isEmpty || !rejectedBid.contains(bid) =>
+ SquadEvents.publish(SquadServiceResponse(s"/$rejectingPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, bid.InviterCharId, Some(rejectingPlayer), bid.InviterName, false, Some(None))))
+ case None => ;
+ }
+
+ case SquadAction.Membership(SquadRequestType.Promote, promotingPlayer, Some(promotedPlayer), _, _) =>
+ (memberToSquad.get(promotingPlayer), memberToSquad.get(promotedPlayer)) match {
+ case (Some(squad), Some(squad2)) if squad.GUID == squad2.GUID && squad.Leader.CharId == promotingPlayer =>
+ val membership = squad.Membership.filter { _member => _member.CharId > 0 }
+ val (leader, position) = (squad.Leader, 0)
+ val (member, index) = membership.zipWithIndex.find { case (_member, _) => _member.CharId == promotedPlayer }.get
+ SwapMemberPosition(squad, leader, member)
+ log.info(s"Promoting player ${leader.Name} to be the leader of ${squad.Task}")
+ membership.foreach { _member =>
+ SquadEvents.publish(SquadServiceResponse(s"/${_member.CharId}/Squad", SquadResponse.PromoteMember(squad, promotedPlayer, index, position)))
+ }
+ SquadEvents.publish(SquadServiceResponse(s"/$promotingPlayer/Squad", SquadResponse.InitSquad(PlanetSideGUID(0))))
+ SquadEvents.publish(SquadServiceResponse(s"/$promotedPlayer/Squad", SquadResponse.InitSquad(squad.GUID)))
+ UpdateSquadListWhenListed(
+ squad,
+ SquadInfo().Leader(leader.Name)
+ )
+ UpdateSquadDetail(squad.GUID, squad,
+ SquadDetail()
+ .LeaderCharId(leader.CharId)
+ .Field3(value = 0L)
+ .LeaderName(leader.Name)
+ .Members(List(
+ SquadPositionEntry(position, SquadPositionDetail().CharId(member.CharId).Name(member.Name)),
+ SquadPositionEntry(index, SquadPositionDetail().CharId(leader.CharId).Name(leader.Name))
+ ))
+ )
+
+ case _ => ;
+ }
case SquadAction.Update(char_id, health, max_health, armor, max_armor, pos, zone_number) =>
memberToSquad.get(char_id) match {
@@ -387,14 +543,14 @@ class SquadService extends Actor {
val squad = lSquadOpt.getOrElse(StartSquad(tplayer))
squad.Task = purpose
UpdateSquadListWhenListed(squad, SquadInfo().Task(purpose))
- UpdateSquadDetailWhenListed(squad.GUID, squad, SquadDetail().Task(purpose))
+ UpdateSquadDetail(squad.GUID, squad, SquadDetail().Task(purpose))
case ChangeSquadZone(zone) =>
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed squad's ops zone to $zone")
val squad = lSquadOpt.getOrElse(StartSquad(tplayer))
squad.ZoneId = zone.zoneId.toInt
UpdateSquadListWhenListed(squad, SquadInfo().ZoneId(zone))
- UpdateSquadDetailWhenListed(squad.GUID, squad, SquadDetail().ZoneId(zone))
+ UpdateSquadDetail(squad.GUID, squad, SquadDetail().ZoneId(zone))
case CloseSquadMemberPosition(position) =>
val squad = lSquadOpt.getOrElse(StartSquad(tplayer))
@@ -411,7 +567,7 @@ class SquadService extends Actor {
}
memberPosition.Close()
UpdateSquadListWhenListed(squad, listingChanged)
- UpdateSquadDetailWhenListed(squad.GUID, squad,
+ UpdateSquadDetail(squad.GUID, squad,
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail.Closed)))
)
case Some(false) | None => ;
@@ -424,7 +580,7 @@ class SquadService extends Actor {
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened the #$position position in squad")
squad.Availability.update(position, true)
UpdateSquadListWhenListed(squad, SquadInfo().Capacity(squad.Capacity))
- UpdateSquadDetailWhenListed(squad.GUID, squad,
+ UpdateSquadDetail(squad.GUID, squad,
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail.Open)))
)
case Some(true) | None => ;
@@ -436,7 +592,7 @@ 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
- UpdateSquadDetailWhenListed(squad.GUID, squad,
+ UpdateSquadDetail(squad.GUID, squad,
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail().Role(role))))
)
case Some(false) | None => ;
@@ -448,7 +604,7 @@ 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
- UpdateSquadDetailWhenListed(squad.GUID, squad,
+ UpdateSquadDetail(squad.GUID, squad,
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail().DetailedOrders(orders))))
)
case Some(false) | None => ;
@@ -460,7 +616,7 @@ 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
- UpdateSquadDetailWhenListed(squad.GUID, squad,
+ UpdateSquadDetail(squad.GUID, squad,
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail().Requirements(certs))))
)
case Some(false) | None => ;
@@ -519,7 +675,7 @@ class SquadService extends Actor {
squad.LocationFollowsSquadLead = false
squad.AutoApproveInvitationRequests = false
UpdateSquadListWhenListed(squad, SquadInfo().Task("").ZoneId(None).Capacity(squad.Capacity))
- UpdateSquadDetailWhenListed(squad.GUID, squad)
+ UpdateSquadDetail(squad.GUID, squad)
case _ =>
}
@@ -530,6 +686,9 @@ class SquadService extends Actor {
log.info(s"${tplayer.Name} would like the #${position+1} spot in this squad")
val membership = squad.Membership.zipWithIndex
val toMember = squad.Membership(position)
+ if(squad.Leader.CharId == tplayer.CharId) {
+ //TODO squad leader currently disallowed
+ } else
//the squad leader may swap to any open position; a normal member has to validate against requirements
if((squad.Leader.CharId == tplayer.CharId && toMember.CharId == 0) || ValidOpenSquadPosition(squad, position, toMember, tplayer.Certifications)) {
membership.find { case (member, _) => member.CharId == tplayer.CharId } match {
@@ -538,12 +697,15 @@ class SquadService extends Actor {
if(fromIndex == squad.LeaderPositionIndex) {
squad.LeaderPositionIndex = position
}
+ //RemoveInvite(tplayer.CharId).foreach { _ =>
+ //close the old bids out
+ //}
membership
.filter { case (_member, _) => _member.CharId > 0 }
.foreach { case (_member, _) =>
SquadEvents.publish(SquadServiceResponse(s"/${_member.CharId}/Squad", SquadResponse.AssignMember(squad, fromIndex, position)))
}
- UpdateSquadDetailWhenListed(squad.GUID, squad)
+ UpdateSquadDetail(squad.GUID, squad)
case _ => ;
//somehow, this is not our squad; do nothing, for now
}
@@ -561,10 +723,14 @@ class SquadService extends Actor {
if(ValidOpenSquadPosition(squad, position, toMember, tplayer.Certifications)) {
//we could join but we may need permission from the squad leader first
log.info(s"Player ${tplayer.Name} would like to join the squad ${squad.Task}.")
- bids(tplayer.CharId) = NormalBid(squad.Leader.CharId, guid, position)
- //val leader = squad.Leader
- //TODO ask permission from the squad leader, unless auto-approve is in effect
- self ! SquadServiceMessage(tplayer, SquadAction.Membership(SquadRequestType.Accept, tplayer.CharId, None, "", None))
+ val leader = squad.Leader
+ val bid = BidForPosition(tplayer, guid, position)
+ val leaderCharId = leader.CharId
+ AddInvite(leaderCharId, bid) match {
+ case out @ Some(_) if out.contains(bid) =>
+ HandleBidForPosition(bid, tplayer)
+ case _ => ;
+ }
}
case None => ;
//squad does not exist? assume old local data; force update to correct discrepancy
@@ -574,16 +740,19 @@ class SquadService extends Actor {
case (Some(squad), AssignSquadMemberToRole(position, char_id)) =>
val membership = squad.Membership.zipWithIndex
(membership.find({ case (member, _) => member.CharId == char_id}), membership(position)) match {
- case (Some((fromMember, fromPosition)), (toMember, _)) =>
+ //TODO squad leader currently disallowed
+ case (Some((fromMember, fromPosition)), (toMember, _)) if fromPosition != 0 =>
val name = fromMember.Name
SwapMemberPosition(squad, toMember, fromMember)
- //
+ if(fromPosition == squad.LeaderPositionIndex) {
+ squad.LeaderPositionIndex = position
+ }
membership
.filter({ case (_member, _) => _member.CharId > 0 })
.foreach { case (_member, _) =>
SquadEvents.publish(SquadServiceResponse(s"/${_member.CharId}/Squad", SquadResponse.AssignMember(squad, fromPosition, position)))
}
- UpdateSquadDetailWhenListed(squad.GUID, squad,
+ UpdateSquadDetail(squad.GUID, squad,
SquadDetail().Members(List(
SquadPositionEntry(position, SquadPositionDetail().CharId(fromMember.CharId).Name(fromMember.Name)),
SquadPositionEntry(fromPosition, SquadPositionDetail().CharId(char_id).Name(name))
@@ -597,7 +766,15 @@ class SquadService extends Actor {
idToSquad.get(guid) match {
case Some(squad) =>
viewDetails(tplayer.CharId) = guid
- sender ! SquadServiceResponse(s"/${tplayer.CharId}/Squad", SquadResponse.Detail(squad.GUID, SquadService.Detail.Publish(squad)))
+ sender ! SquadServiceResponse("", SquadResponse.Detail(squad.GUID, SquadService.Detail.Publish(squad)))
+ case None => ;
+ }
+
+ //the following message is feedback from a specific client, awaiting proper initialization
+ case (_, DisplayFullSquad()) =>
+ idToSquad.get(guid) match {
+ case Some(squad) =>
+ sender ! SquadServiceResponse("", SquadResponse.InitSquad(squad.GUID))
case None => ;
}
@@ -609,6 +786,122 @@ class SquadService extends Actor {
}
}
+ /**
+ *
+ * @param invitedPlayer
+ * @param bid
+ * @return an optional invite;
+ * if added to the active invite position, return the parameter bid;
+ * if added to the queued invite, return the invite in the active position;
+ * if not added, return `None`
+ */
+ def AddInvite(invitedPlayer : Long, bid : Invitation) : Option[Invitation] = {
+ invites.get(invitedPlayer) match {
+ case Some(_bid) =>
+ queuedInvites.get(invitedPlayer) match {
+ case Some(bidList) =>
+ if(_bid.InviterCharId != bid.InviterCharId && !bidList.exists { eachBid => eachBid.InviterCharId == bid.InviterCharId }) {
+ log.debug(s"Invite from ${bid.InviterCharId} to $invitedPlayer stored in queue while active invite request pending")
+ queuedInvites(invitedPlayer) = bid match {
+ case _: BidForPosition =>
+ val (normals, others) = bidList.partition(_.isInstanceOf[BidForPosition])
+ (normals :+ bid) ++ others
+ case _ =>
+ bidList :+ bid
+ }
+ None
+ }
+ else {
+ Some(_bid)
+ }
+ case None =>
+ if(_bid.InviterCharId != bid.InviterCharId) {
+ log.debug(s"Invite from ${bid.InviterCharId} to $invitedPlayer stored while active invite request pending")
+ queuedInvites(invitedPlayer) = List[Invitation](bid)
+ }
+ Some(_bid)
+ }
+
+ case None =>
+ invites(invitedPlayer) = bid
+ Some(bid)
+ }
+ }
+
+ def RemoveInvite(invitedPlayer : Long) : Option[Invitation] = {
+ invites.remove(invitedPlayer)
+ }
+
+ def RemoveQueuedInvites(invitedPlayer : Long) : List[Invitation] = {
+ queuedInvites.remove(invitedPlayer) match {
+ case Some(_bidList) => _bidList.toList
+ case None => Nil
+ }
+ }
+
+ def RemoveInvites(invitedPlayer : Long, invitingPlayer : Long) : Unit = {
+ queuedInvites.get(invitedPlayer) match {
+ case Some(bidList) =>
+ val list = bidList.filterNot { _.InviterCharId == invitingPlayer }
+ if(list.nonEmpty) {
+ queuedInvites(invitedPlayer) = list
+ }
+ else {
+ queuedInvites.remove(invitedPlayer)
+ }
+ case None => ;
+ }
+ invites.get(invitedPlayer) match {
+ case Some(_bid) =>
+ if(_bid.InviterCharId == invitingPlayer) {
+ //drop bid, try reload new bid
+ }
+ case None => ;
+ }
+ }
+
+ def NextInvite(invitedPlayer : Long) : Option[Invitation] = {
+ invites.get(invitedPlayer) match {
+ case None =>
+ queuedInvites.get(invitedPlayer) match {
+ case Some(list) =>
+ list match {
+ case Nil =>
+ None
+ case x :: Nil =>
+ invites(invitedPlayer) = x
+ queuedInvites.remove(invitedPlayer)
+ Some(x)
+ case x :: xs =>
+ invites(invitedPlayer) = x
+ queuedInvites(invitedPlayer) = xs
+ Some(x)
+ }
+
+ case None =>
+ None
+ }
+ case Some(_) =>
+ None
+ }
+ }
+
+ def HandleBidForPosition(bid : BidForPosition, player : Player) : Unit = {
+ idToSquad.get(bid.squad_guid) match {
+ case Some(squad) =>
+ val leaderCharId = squad.Leader.CharId
+ if(squad.AutoApproveInvitationRequests) {
+ self ! SquadServiceMessage(player, SquadAction.Membership(SquadRequestType.Accept, leaderCharId, None, "", None))
+ }
+ else {
+ SquadEvents.publish(SquadServiceResponse(s"/$leaderCharId/Squad", SquadResponse.WantsSquadPosition(bid.player.Name)))
+ }
+ case _ =>
+ //squad is missing; will this properly short-circuit?
+ log.error(s"Attempted to process ${bid.InviterName}'s bid for a position in a squad (id:${bid.squad_guid.guid}) that does not exist")
+ }
+ }
+
def JoinSquad(player : Player, squad : Squad, line : Int) : Boolean = {
val charId = player.CharId
val position = squad.Membership(line)
@@ -652,7 +945,7 @@ class SquadService extends Actor {
.foreach { member =>
SquadEvents.publish(SquadServiceResponse(s"/${member.CharId}/Squad", SquadResponse.Join(squad, updatedIndex)))
}
- UpdateSquadDetailWhenListed(squad.GUID, squad,
+ UpdateSquadDetail(squad.GUID, squad,
SquadDetail().Members(List(SquadPositionEntry(line, SquadPositionDetail().CharId(charId).Name(player.Name))))
)
}
@@ -670,30 +963,18 @@ class SquadService extends Actor {
membership.find { case (_member, _) => _member.CharId == charId } match {
case Some((member, index)) =>
val updateList = membership.collect({ case (_member, _index) if _member.CharId > 0 => (_member.CharId, _index) }).toList
+ //member leaves the squad completely
memberToSquad.remove(charId)
member.Name = ""
member.CharId = 0
-
- val size = squad.Size
- if(size < 2) {
- //squad is rendered to just one person or less; collapse it
- squad.Membership.foreach { _member =>
- SquadEvents.publish(SquadServiceResponse(s"/${_member.CharId}/Squad", SquadResponse.Membership(SquadResponseType.Disband, 0, 0, charId, None, "", false, Some(None))))
+ sender ! SquadServiceResponse("", SquadResponse.Leave(squad, updateList))
+ //other squad members see the member leaving
+ val leavingMember = List((charId, index))
+ membership
+ .filter { case (_member, _) => _member.CharId > 0 }
+ .foreach { case (_member, _) =>
+ SquadEvents.publish( SquadServiceResponse(s"/${_member.CharId}/Squad", SquadResponse.Leave(squad, leavingMember)) )
}
- CloseOutSquad(squad, membership, updateList)
- }
- else {
- //squad continues, despite player's parting
- //member leaves the squad completely
- sender ! SquadServiceResponse("", SquadResponse.Leave(squad, updateList))
- //other squad members see the member leaving
- val leavingMember = List((charId, index))
- membership
- .filter { case (_member, _) => _member.CharId > 0 }
- .foreach { case (_member, _) =>
- SquadEvents.publish( SquadServiceResponse(s"/${_member.CharId}/Squad", SquadResponse.Leave(squad, leavingMember)) )
- }
- }
true
case None =>
false
@@ -720,6 +1001,7 @@ class SquadService extends Actor {
SquadEvents.publish( SquadServiceResponse(s"/$charId/Squad", SquadResponse.Leave(squad, updateList)) )
}
idToSquad.remove(squad.GUID)
+ UpdateSquadList(squad, None)
}
def SwapMemberPosition(squad : Squad, toMember : Member, fromMember : Member) : Unit = {
@@ -744,6 +1026,13 @@ class SquadService extends Actor {
toMember.Armor = armor
}
+ def UpdateSquadList(faction : PlanetSideEmpire.Value): Unit = {
+ val factionListings = publishedLists(faction)
+ SquadEvents.publish(
+ SquadServiceResponse(s"/$faction/Squad", SquadResponse.InitList(factionListings.toVector))
+ )
+ }
+
def UpdateSquadList(squad : Squad, changes : SquadInfo) : Unit = {
UpdateSquadList(squad, Some(changes))
}
@@ -759,7 +1048,6 @@ class SquadService extends Actor {
}
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 => {
@@ -774,7 +1062,7 @@ class SquadService extends Actor {
case Some(changedFields) =>
//squad information update
log.info(s"Squad will be updated")
- factionListings(index) = entry
+ factionListings(index) = SquadService.SquadList.Publish(squad)
SquadEvents.publish(
SquadServiceResponse(s"/$faction/Squad", SquadResponse.UpdateList(Seq((index, changedFields))))
)
@@ -790,7 +1078,7 @@ class SquadService extends Actor {
case None =>
//first time being published
log.info(s"Squad will be introduced")
- factionListings += entry
+ factionListings += SquadService.SquadList.Publish(squad)
SquadEvents.publish(
SquadServiceResponse(s"/$faction/Squad", SquadResponse.InitList(factionListings.toVector))
)
@@ -798,19 +1086,7 @@ class SquadService extends Actor {
}
def UpdateSquadDetail(guid : PlanetSideGUID, squad : Squad) : Unit = {
- UpdateSquadDetailWhenListed(guid, squad, SquadService.Detail.Publish(squad))
- }
-
- def UpdateSquadDetailWhenListed(guid : PlanetSideGUID, squad : Squad) : Unit = {
- if(squad.Listed || squad.Size > 1) {
- UpdateSquadDetail(guid, squad, SquadService.Detail.Publish(squad))
- }
- }
-
- def UpdateSquadDetailWhenListed(guid : PlanetSideGUID, squad : Squad, detail : SquadDetail) : Unit = {
- if(squad.Listed || squad.Size > 1) {
- UpdateSquadDetail(guid, squad, detail)
- }
+ UpdateSquadDetail(guid, squad, SquadService.Detail.Publish(squad))
}
def UpdateSquadDetail(guid : PlanetSideGUID, squad : Squad, detail : SquadDetail) : Unit = {
@@ -824,13 +1100,19 @@ class SquadService extends Actor {
}
object SquadService {
- trait PositionBid
+ abstract class Invitation(char_id : Long, name : String) {
+ def InviterCharId : Long = char_id
+ def InviterName : String = name
+ }
- final case class NormalBid(char_id : Long, squad_guid : PlanetSideGUID, position : Int) extends PositionBid
+ final case class BidForPosition(player : Player, squad_guid : PlanetSideGUID, position : Int)
+ extends Invitation(player.CharId, player.Name)
- final case class VacancyBid(char_id : Long, squad_guid : PlanetSideGUID) extends PositionBid
+ final case class VacancyInvite(char_id : Long, name : String, squad_guid : PlanetSideGUID)
+ extends Invitation(char_id, name)
- final case class SpontaneousBid(char_id : Long, placeholder : Squad) extends PositionBid
+ final case class SpontaneousInvite(player : Player)
+ extends Invitation(player.CharId, player.Name)
object SquadList {
def Publish(squad : Squad) : SquadInfo = {
diff --git a/common/src/test/scala/game/SquadDefinitionActionMessageTest.scala b/common/src/test/scala/game/SquadDefinitionActionMessageTest.scala
index 09e2cd42..3ef29577 100644
--- a/common/src/test/scala/game/SquadDefinitionActionMessageTest.scala
+++ b/common/src/test/scala/game/SquadDefinitionActionMessageTest.scala
@@ -222,7 +222,7 @@ class SquadDefinitionActionMessageTest extends Specification {
case SquadDefinitionActionMessage(unk1, unk2, action) =>
unk1 mustEqual PlanetSideGUID(0)
unk2 mustEqual 0
- action mustEqual SearchForSquadsWithParticularRole("Badass", 0L, 1, 0)
+ action mustEqual SearchForSquadsWithParticularRole("Badass", Set(), 1, SearchMode.AnyPositions)
case _ =>
ko
}
@@ -233,7 +233,7 @@ class SquadDefinitionActionMessageTest extends Specification {
case SquadDefinitionActionMessage(unk1, unk2, action) =>
unk1 mustEqual PlanetSideGUID(0)
unk2 mustEqual 0
- action mustEqual SearchForSquadsWithParticularRole("Badass", 0L, 2, 0)
+ action mustEqual SearchForSquadsWithParticularRole("Badass", Set(), 2, SearchMode.AnyPositions)
case _ =>
ko
}
@@ -244,7 +244,7 @@ class SquadDefinitionActionMessageTest extends Specification {
case SquadDefinitionActionMessage(unk1, unk2, action) =>
unk1 mustEqual PlanetSideGUID(0)
unk2 mustEqual 0
- action mustEqual SearchForSquadsWithParticularRole("Badass", 0L, 2, 1)
+ action mustEqual SearchForSquadsWithParticularRole("Badass", Set(), 2, SearchMode.AvailablePositions)
case _ =>
ko
}
@@ -255,7 +255,7 @@ class SquadDefinitionActionMessageTest extends Specification {
case SquadDefinitionActionMessage(unk1, unk2, action) =>
unk1 mustEqual PlanetSideGUID(0)
unk2 mustEqual 0
- action mustEqual SearchForSquadsWithParticularRole("Badass", 536870928L, 2, 2)
+ action mustEqual SearchForSquadsWithParticularRole("Badass", Set(CertificationType.InfiltrationSuit, CertificationType.AntiVehicular), 2, SearchMode.SomeCertifications)
case _ =>
ko
}
@@ -266,7 +266,7 @@ class SquadDefinitionActionMessageTest extends Specification {
case SquadDefinitionActionMessage(unk1, unk2, action) =>
unk1 mustEqual PlanetSideGUID(0)
unk2 mustEqual 0
- action mustEqual SearchForSquadsWithParticularRole("Badass", 536870928L, 2, 3)
+ action mustEqual SearchForSquadsWithParticularRole("Badass", Set(CertificationType.InfiltrationSuit, CertificationType.AntiVehicular), 2, SearchMode.AllCertifications)
case _ =>
ko
}
@@ -436,35 +436,35 @@ class SquadDefinitionActionMessageTest extends Specification {
}
"encode (34a)" in {
- val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", 0L, 1, 0))
+ val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", Set(), 1, SearchMode.AnyPositions))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_34a
}
"encode (34b)" in {
- val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", 0L, 2, 0))
+ val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", Set(), 2, SearchMode.AnyPositions))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_34b
}
"encode (34c)" in {
- val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", 0L, 2, 1))
+ val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", Set(), 2, SearchMode.AvailablePositions))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_34c
}
"encode (34d)" in {
- val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", 536870928L, 2, 2))
+ val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", Set(CertificationType.InfiltrationSuit, CertificationType.AntiVehicular), 2, SearchMode.SomeCertifications))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_34d
}
"encode (34e)" in {
- val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", 536870928L, 2, 3))
+ val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", Set(CertificationType.InfiltrationSuit, CertificationType.AntiVehicular), 2, SearchMode.AllCertifications))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_34e
diff --git a/common/src/test/scala/game/SquadMemberEventTest.scala b/common/src/test/scala/game/SquadMemberEventTest.scala
index 5ecc1cef..2f431991 100644
--- a/common/src/test/scala/game/SquadMemberEventTest.scala
+++ b/common/src/test/scala/game/SquadMemberEventTest.scala
@@ -12,7 +12,7 @@ class SquadMemberEventTest extends Specification {
"decode" in {
PacketCoding.DecodePacket(string).require match {
case SquadMemberEvent(u1, u2, u3, u4, u5, u6, u7) =>
- u1 mustEqual 0
+ u1 mustEqual MemberEvent.Add
u2 mustEqual 7
u3 mustEqual 42771010L
u4 mustEqual 0
@@ -25,7 +25,7 @@ class SquadMemberEventTest extends Specification {
}
"encode" in {
- val msg = SquadMemberEvent(0, 7, 42771010L, 0, Some("HofD"), Some(7), Some(529745L))
+ val msg = SquadMemberEvent(MemberEvent.Add, 7, 42771010L, 0, Some("HofD"), Some(7), Some(529745L))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index 49fdca7e..a7bbe9ef 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -40,6 +40,7 @@ import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurret}
+import net.psforever.objects.teamwork.Squad
import net.psforever.objects.vehicles.{AccessPermissionGroup, Cargo, Utility, VehicleLockState, _}
import net.psforever.objects.vital._
import net.psforever.objects.zones.{InterstellarCluster, Zone, ZoneHotSpotProjector}
@@ -358,7 +359,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
SquadListing(index, squadInfo)
}.toVector
)
- log.info(s"updating squad with msg $o")
sendResponse(
ReplicationStreamMessage(6, None,
infos.map { case (index, squadInfo) =>
@@ -388,12 +388,33 @@ class WorldSessionActor extends Actor with MDCContextAware {
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.Membership(request_type, unk1, unk2, char_id, opt_char_id, player_name, unk5, unk6) =>
+ val name = request_type match {
+ case SquadResponseType.Invite if unk5 =>
+ //player_name is our name; the name of the player indicated by unk3 is needed
+ LivePlayerList.WorldPopulation({ case (_, a : Avatar) => char_id == a.CharId }).headOption match {
+ case Some(player) =>
+ player.name
+ case None =>
+ player_name
+ }
+ case _ =>
+ player_name
+ }
+ sendResponse(SquadMembershipResponse(request_type, unk1, unk2, char_id, opt_char_id, 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.WantsSquadPosition(name : String) =>
+ sendResponse(
+ ChatMsg(
+ ChatMessageType.CMT_TELL, true, "",
+ s"\\#6[SQUAD] \\#3$name\\#6 would like to join your squad. (respond with \\#3/accept\\#6 or \\#3/reject\\#6)",
+ None
+ )
+ )
+
case SquadResponse.Join(squad, positionsToUpdate) =>
val leader = squad.Leader
val id = 11
@@ -405,20 +426,20 @@ class WorldSessionActor extends Actor with MDCContextAware {
//we are joining the squad
//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)))
+ sendResponse(SquadMemberEvent.Add(id, member.CharId, index, member.Name, member.ZoneId, unk7 = 0))
squadUI(member.CharId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position)
}
//initialization
- sendResponse(SquadMemberEvent(0, id, ourMember.CharId, ourIndex, Some(ourMember.Name), Some(ourMember.ZoneId), Some(0))) //repeat of our entry
+ sendResponse(SquadMemberEvent.Add(id, ourMember.CharId, ourIndex, ourMember.Name, ourMember.ZoneId, unk7 = 0)) //repeat of our entry
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)))
+ sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
case _ =>
//other player is joining our squad
//load each member's entry
membershipPositions.foreach { case(member, index) =>
- sendResponse(SquadMemberEvent(0, id, member.CharId, index, Some(member.Name), Some(member.ZoneId), Some(0)))
+ sendResponse(SquadMemberEvent.Add(id, member.CharId, index, member.Name, member.ZoneId, unk7 = 0))
squadUI(member.CharId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position)
}
}
@@ -429,6 +450,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
.map{ case (member, _) => SquadStateInfo(member.CharId, member.Health, member.Armor, member.Position, 2,2, false, 429, None,None) }
.toList
))
+ log.info(s"SquadCards: ${squadUI.map { case(id, card) => s"[${card.index}:${card.name}/$id]" }.mkString}")
case SquadResponse.Leave(_, positionsToUpdate) =>
val id = 11
@@ -437,11 +459,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
//we are leaving the squad
//remove each member's entry (our own too)
positionsToUpdate.foreach { case(member, index) =>
- sendResponse(SquadMemberEvent(1, id, member, index, None, None, None))
+ sendResponse(SquadMemberEvent.Remove(id, member, index))
squadUI.remove(member)
}
//uninitialize
- sendResponse(SquadMemberEvent(1, id, ourMember, ourIndex, None, None, None)) //repeat of our entry
+ sendResponse(SquadMemberEvent.Remove(id, ourMember, ourIndex)) //repeat of our entry
sendResponse(PlanetsideAttributeMessage(player.GUID, 31, 0)) //disassociate with squad?
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, 0)) //disassociate with member position in squad?
sendResponse(PlanetsideAttributeMessage(player.GUID, 34, 4294967295L)) //unknown, perhaps unrelated?
@@ -450,9 +472,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
case _ =>
//remove each member's entry
positionsToUpdate.foreach { case(member, index) =>
- sendResponse(SquadMemberEvent(1, id, member, index, None, None, None))
+ sendResponse(SquadMemberEvent.Remove(id, member, index))
squadUI.remove(member)
}
+ log.info(s"SquadCards: ${squadUI.map { case(id, card) => s"[${card.index}:${card.name}/$id]" }.mkString}")
}
case SquadResponse.UpdateMembers(squad, positions) =>
@@ -467,7 +490,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
.collect({
case (entry, element) if entry.zone_number != element.zone =>
//zone gets updated for these entries
- sendResponse(SquadMemberEvent(3, 11, entry.char_id, element.index, None, Some(entry.zone_number), None))
+ sendResponse(SquadMemberEvent.UpdateZone(11, entry.char_id, element.index, entry.zone_number))
squadUI(entry.char_id) = SquadUIElement(element.name, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
entry
case (entry, element) if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position =>
@@ -486,53 +509,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
case SquadResponse.AssignMember(squad, from_index, to_index) =>
- if(squadUI.nonEmpty) {
- val toMember = squad.Membership(to_index)
- val toCharId = toMember.CharId
- val fromMember = squad.Membership(from_index)
- val fromCharId = fromMember.CharId
- val id = 11
- if(fromCharId > 0) {
- //toMember and fromMember have swapped places
- val fromElem = squadUI(fromCharId)
- val toElem = squadUI(toCharId)
- sendResponse(SquadMemberEvent(1, id, toCharId, from_index, None, None, None))
- sendResponse(SquadMemberEvent(1, id, fromCharId, to_index, None, None, None))
- squadUI(toCharId) = SquadUIElement(fromElem.name, to_index, fromElem.zone, fromElem.health, fromElem.armor, fromElem.position)
- squadUI(fromCharId) = SquadUIElement(toElem.name, from_index, toElem.zone, toElem.health, toElem.armor, toElem.position)
- sendResponse(SquadMemberEvent(0, id, toCharId, to_index, Some(fromElem.name), Some(fromElem.zone), Some(0)))
- sendResponse(SquadMemberEvent(0, id, fromCharId, from_index, Some(toElem.name), Some(toElem.zone), Some(0)))
- sendResponse(
- SquadState(
- PlanetSideGUID(id),
- List(
- SquadStateInfo(fromCharId, toElem.health, toElem.armor, toElem.position, 2, 2, false, 429, None, None),
- SquadStateInfo(toCharId, fromElem.health, fromElem.armor, fromElem.position, 2, 2, false, 429, None, None)
- )
- )
- )
- }
- else {
- //previous fromMember has moved toMember
- val elem = squadUI(fromCharId)
- sendResponse(SquadMemberEvent(1, id, toCharId, from_index, None, None, None))
- squadUI(toCharId) = SquadUIElement(elem.name, to_index, elem.zone, elem.health, elem.armor, elem.position)
- sendResponse(SquadMemberEvent(0, id, toCharId, to_index, Some(elem.name), Some(elem.zone), Some(0)))
- sendResponse(
- SquadState(
- PlanetSideGUID(id),
- List(SquadStateInfo(toCharId, elem.health, elem.armor, elem.position, 2, 2, false, 429, None, None))
- )
- )
- }
- val charId = avatar.CharId
- if(toCharId == charId) {
- sendResponse(PlanetsideAttributeMessage(player.GUID, 32, to_index))
- }
- else if(fromCharId == charId) {
- sendResponse(PlanetsideAttributeMessage(player.GUID, 32, from_index))
- }
- }
+ //we've already swapped position internally; now we swap the cards
+ SwapSquadUIElements(squad, from_index, to_index)
+ log.info(s"SquadCards: ${squadUI.map { case(id, card) => s"[${card.index}:${card.name}/$id]" }.mkString}")
+
+ case SquadResponse.PromoteMember(squad, char_id, from_index, to_index) =>
+ //promotion will swap visual cards, but we must fix the backend
+ val id = 11
+ sendResponse(SquadMemberEvent.Promote(id, char_id))
+ SwapSquadUIElements(squad, from_index, to_index)
+ log.info(s"SquadCards: ${squadUI.map { case(id, card) => s"[${card.index}:${card.name}/$id]" }.mkString}")
+
+ case SquadResponse.SquadSearchResults() =>
+ sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.NoSquadSearchResults()))
case _ => ;
}
@@ -3478,25 +3467,16 @@ class WorldSessionActor extends Actor with MDCContextAware {
"Degrado",
Set(
CertificationType.StandardAssault,
- CertificationType.ArmoredAssault1,
- CertificationType.MediumAssault,
- CertificationType.ReinforcedExoSuit,
- CertificationType.Harasser,
- CertificationType.Engineering,
- CertificationType.GroundSupport,
CertificationType.AgileExoSuit,
- CertificationType.AIMAX,
- CertificationType.StandardExoSuit,
- CertificationType.AAMAX,
- CertificationType.ArmoredAssault2
+ CertificationType.StandardExoSuit
),
- 9,
- 0,
- PlanetSideGUID(1)
+ 37,
+ 5,
+ PlanetSideGUID(7)
)
)
)
- sendResponse(SquadInvitationRequestMessage(PlanetSideGUID(1), 9, 41577140L, "Degrado"))
+ sendResponse(SquadInvitationRequestMessage(PlanetSideGUID(1), 4, 41577140L, "Degrado"))
}
player.Position = pos
player.Velocity = vel
@@ -9136,6 +9116,56 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
}
+ def SwapSquadUIElements(squad : Squad, fromIndex : Int, toIndex : Int) : Unit = {
+ if(squadUI.nonEmpty) {
+ val fromMember = squad.Membership(fromIndex)
+ val fromCharId = fromMember.CharId
+ val toMember = squad.Membership(toIndex)
+ val toCharId = toMember.CharId
+ val id = 11
+ if(fromCharId > 0) {
+ //toMember and fromMember have swapped places
+ val fromElem = squadUI(fromCharId)
+ val toElem = squadUI(toCharId)
+ sendResponse(SquadMemberEvent.Remove(id, toCharId, fromIndex))
+ sendResponse(SquadMemberEvent.Remove(id, fromCharId, toIndex))
+ squadUI(toCharId) = SquadUIElement(fromElem.name, toIndex, fromElem.zone, fromElem.health, fromElem.armor, fromElem.position)
+ squadUI(fromCharId) = SquadUIElement(toElem.name, fromIndex, toElem.zone, toElem.health, toElem.armor, toElem.position)
+ sendResponse(SquadMemberEvent.Add(id, toCharId, toIndex, fromElem.name, fromElem.zone, unk7 = 0))
+ sendResponse(SquadMemberEvent.Add(id, fromCharId, fromIndex, toElem.name, toElem.zone, unk7 = 0))
+ sendResponse(
+ SquadState(
+ PlanetSideGUID(id),
+ List(
+ SquadStateInfo(fromCharId, toElem.health, toElem.armor, toElem.position, 2, 2, false, 429, None, None),
+ SquadStateInfo(toCharId, fromElem.health, fromElem.armor, fromElem.position, 2, 2, false, 429, None, None)
+ )
+ )
+ )
+ }
+ else {
+ //previous fromMember has moved toMember
+ val elem = squadUI(toCharId)
+ sendResponse(SquadMemberEvent.Remove(id, toCharId, fromIndex))
+ squadUI(toCharId) = SquadUIElement(elem.name, toIndex, elem.zone, elem.health, elem.armor, elem.position)
+ sendResponse(SquadMemberEvent.Add(id, toCharId, toIndex, elem.name, elem.zone, unk7 = 0))
+ sendResponse(
+ SquadState(
+ PlanetSideGUID(id),
+ List(SquadStateInfo(toCharId, elem.health, elem.armor, elem.position, 2, 2, false, 429, None, None))
+ )
+ )
+ }
+ val charId = avatar.CharId
+ if(toCharId == charId) {
+ sendResponse(PlanetsideAttributeMessage(player.GUID, 32, toIndex))
+ }
+ else if(fromCharId == charId) {
+ sendResponse(PlanetsideAttributeMessage(player.GUID, 32, fromIndex))
+ }
+ }
+ }
+
def failWithError(error : String) = {
log.error(error)
sendResponse(ConnectionClose())