From f7098618d791122e32e550bc7249f05d4147365c Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 7 Oct 2019 21:41:29 -0400 Subject: [PATCH] Members and Squads now deal with their own inherent availabilities; code to transfer knowledge of character squad association by color; old code --- .../psforever/objects/teamwork/Member.scala | 15 +- .../psforever/objects/teamwork/Squad.scala | 20 ++- .../game/SquadDefinitionActionMessage.scala | 54 +------ .../services/teamwork/SquadResponse.scala | 1 - .../services/teamwork/SquadService.scala | 110 +++---------- .../src/main/scala/WorldSessionActor.scala | 149 +++++++++++++----- 6 files changed, 160 insertions(+), 189 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/teamwork/Member.scala b/common/src/main/scala/net/psforever/objects/teamwork/Member.scala index 1ddfc46d..a2406a51 100644 --- a/common/src/main/scala/net/psforever/objects/teamwork/Member.scala +++ b/common/src/main/scala/net/psforever/objects/teamwork/Member.scala @@ -79,14 +79,11 @@ class Member { Position } - def Close() : Unit = { - role = "" - requirements = Set() - //about the individual filling the position - name = "" - health = 0 - armor = 0 - zoneId = 0 - position = Vector3.Zero + def isAvailable : Boolean = { + charId == 0 + } + + def isAvailable(certs : Set[CertificationType.Value]) : Boolean = { + isAvailable && certs.intersect(requirements) == requirements } } 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 3ff63035..0ecd2e20 100644 --- a/common/src/main/scala/net/psforever/objects/teamwork/Squad.scala +++ b/common/src/main/scala/net/psforever/objects/teamwork/Squad.scala @@ -3,7 +3,7 @@ package net.psforever.objects.teamwork import net.psforever.objects.entity.IdentifiableEntity import net.psforever.packet.game.PlanetSideGUID -import net.psforever.types.PlanetSideEmpire +import net.psforever.types.{CertificationType, PlanetSideEmpire} class Squad(squadId : PlanetSideGUID, alignment : PlanetSideEmpire.Value) extends IdentifiableEntity { super.GUID_=(squadId) @@ -53,6 +53,24 @@ class Squad(squadId : PlanetSideGUID, alignment : PlanetSideEmpire.Value) extend def Size : Int = membership.count(member => member.CharId != 0) def Capacity : Int = availability.count(open => open) + + def isAvailable(role : Int) : Boolean = { + availability.lift(role) match { + case Some(true) => + membership(role).isAvailable + case _ => + false + } + } + + def isAvailable(role : Int, certs : Set[CertificationType.Value]) : Boolean = { + availability.lift(role) match { + case Some(true) => + membership(role).isAvailable(certs) + case _ => + false + } + } } object Squad { 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 eab055a9..a0a02c7d 100644 --- a/common/src/main/scala/net/psforever/packet/game/SquadDefinitionActionMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/SquadDefinitionActionMessage.scala @@ -370,7 +370,7 @@ object SquadAction{ *     `28` - Auto-approve Requests for Invitation
*     `29` - UNKNOWN
*     `30` - UNKNOWN
- *     `31` - Location Follows Squad Lead
+ *     `31` - Location Follows Squad Leader
*   `Int`
*     `10` - Select this Role for Yourself
*     `11` - UNKNOWN
@@ -452,10 +452,9 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe case 39 => noSquadSearchResultsCodec case 40 => findLfsSoldiersForRoleCodec case 41 => cancelFindCodec - case 2 | 6 | 11 | - 12 | 13 | 14 | - 18 | 29 | 30 | 32 | 33 | - 36 | 37 | 42 | 43 => unknownCodec(code) + case 2 | 6 | 11 | 12 | 13 | + 14 | 18 | 29 | 30 | 32 | + 33 | 36 | 37 | 42 | 43 => unknownCodec(code) case _ => failureCodec(code) }).asInstanceOf[Codec[SquadAction]] } @@ -477,48 +476,3 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe } ) } - -/* -("change" specifically indicates the perspective is from the SL; "update" indicates squad members other than the oen who made the change -("[#]" indicates the mode is detected but not properly parsed; the length of the combined fields may follow - -[0] - clicking on a squad listed in the "Find Squad" tab / cancel squad search (6 bits/pad?) -[2] - ? (6 bits/pad?) -[3] - save squad favorite (6 bits/pad?) -[4] - load a squad definition favorite (6 bits/pad?) -[6] - ? (6 bits/pad?) -7 - ? -[8] - list squad (6 bits/pad?) -10 - select this role for yourself -11 - ? -12 - ? -13 - ? -14 - ? -15 - ? -[16] - ? (6 bits/pad?) -[17] - ? (6 bits/pad?) -[18] - ? (6 bits/pad?) -19 - change purpose -20 - change zone -21 - change/close squad member position -22 - change/add squad member position -23 - change squad member req role -24 - change squad member req detailed orders -25 - change squad member req weapons -[26] - reset all (6 bits/pad?) -28 - auto-approve requests for invitation -29 - -30 - -31 - location follows squad lead -[32] - ? (6 bits/pad?) -33 - -34 - search for squads with a particular role -36 - -37 - -38 - -[39] - ? (?) -40 - find LFS soldiers that meet the requirements for this role -[41] - cancel search for LFS soldiers (6 bits) -[42] - ? (6 bits/pad?) -[43] - ? (6 bits/pad?) -*/ diff --git a/common/src/main/scala/services/teamwork/SquadResponse.scala b/common/src/main/scala/services/teamwork/SquadResponse.scala index 5860145c..3ad55b6c 100644 --- a/common/src/main/scala/services/teamwork/SquadResponse.scala +++ b/common/src/main/scala/services/teamwork/SquadResponse.scala @@ -16,7 +16,6 @@ object SquadResponse { final case class AssociateWithSquad(squad_guid : PlanetSideGUID) extends Response final case class SetListSquad(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 diff --git a/common/src/main/scala/services/teamwork/SquadService.scala b/common/src/main/scala/services/teamwork/SquadService.scala index b837e7a1..11f4eaa5 100644 --- a/common/src/main/scala/services/teamwork/SquadService.scala +++ b/common/src/main/scala/services/teamwork/SquadService.scala @@ -55,7 +55,7 @@ class SquadService extends Actor { */ private val queuedInvites : mutable.LongMap[List[Invitation]] = mutable.LongMap[List[Invitation]]() /** - * The gien player has refused participation into this other player's sqaud.
+ * The given player has refused participation into this other player's squad.
* key - a unique character identifier number; value - a list of unique character identifier numbers */ private val refused : mutable.LongMap[List[Long]] = mutable.LongMap[List[Long]]() @@ -65,9 +65,10 @@ class SquadService extends Actor { */ private val continueToMonitorDetails : mutable.LongMap[PlanetSideGUID] = mutable.LongMap[PlanetSideGUID]() /** - * A placeholder for an absent active invite that has not (yet) been accepted or rejected, equal to the then-current active invite. + * A placeholder for an absent active invite that has not (yet) been accepted or rejected, + * equal to the then-current active invite. * Created when removing an active invite. - * Checked when trying to add a new invite (if found, the invite is queued). + * Checked when trying to add a new invite (if found, the new invite is queued). * Cleared when the next queued invite becomes active.
* key - unique character identifier number; value, unique character identifier number */ @@ -91,10 +92,10 @@ class SquadService extends Actor { */ private val SquadEvents = new GenericEventBus[SquadServiceResponse] /** - * This collection contains the message-sending contact reference for individuals who may become squad members. + * This collection contains the message-sending contact reference for individuals. * When the user joins the `SquadService` with a `Service.Join` message * that includes their unique character identifier, - * the origin `ActorRef` is added as a subscription. + * and the origin `ActorRef` is added as a subscription. * It is maintained until they disconnect entirely. * The subscription is anticipated to belong to an instance of `WorldSessionActor`.
* key - unique character identifier number; value - `ActorRef` reference for that character @@ -688,7 +689,7 @@ class SquadService extends Actor { if(squad.Size < squad.Capacity) { val positions = (for { (member, index) <- squad.Membership.zipWithIndex - if ValidOpenSquadPosition(squad, index, member, tplayer.Certifications) + if squad.isAvailable(index, tplayer.Certifications) } yield (index, member.Requirements.size)) .toList .sortBy({ case (_, reqs) => reqs }) @@ -814,7 +815,7 @@ class SquadService extends Actor { } (None, None) - case Some(RequestRole(candidate, guid, _)) + case Some(RequestRole(_, guid, _)) if squadFeatures.get(guid).nonEmpty && squadFeatures(guid).Squad.Leader.CharId == rejectingPlayer => //rejectingPlayer is the squad leader; candidate is the would-be squad member who was rejected val features = squadFeatures(guid) @@ -1024,7 +1025,6 @@ class SquadService extends Actor { if(memberPosition.CharId > 0) { LeaveSquad(memberPosition.CharId, squad) } - memberPosition.Close() UpdateSquadListWhenListed(squadFeatures(squad.GUID), SquadInfo().Capacity(squad.Capacity)) UpdateSquadDetail( squad.GUID, @@ -1232,11 +1232,11 @@ class SquadService extends Actor { position.Requirements = Set() }) val features = squadFeatures(squad.GUID) - features.LocationFollowsSquadLead = false - features.AutoApproveInvitationRequests = false + features.LocationFollowsSquadLead = true + features.AutoApproveInvitationRequests = true UpdateSquadListWhenListed(features, SquadInfo().Task("").ZoneId(None).Capacity(squad.Capacity)) UpdateSquadDetail(squad) - Publish(sender, SquadResponse.AssociateWithSquad(PlanetSideGUID(0))) + InitialAssociation(squad) squadFeatures(guid).InitialAssociation = true //do not unlist an already listed squad case Some(squad) => @@ -1248,13 +1248,13 @@ class SquadService extends Actor { case _ => (pSquadOpt, action) match { //the following action can be performed by the squad leader and maybe an unaffiliated player - case (Some(squad), SelectRoleForYourself(position)) => + case (Some(_), SelectRoleForYourself(_)) => //TODO should be possible, but doesn't work correctly due to UI squad cards not updating as expected // if(squad.Leader.CharId == tplayer.CharId) { // //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 || ValidOpenSquadPosition(squad, position, tplayer.Certifications)) { +// if(squad.Leader.CharId == tplayer.CharId || squad.isAvailable(position, tplayer.Certifications)) { // squad.Membership.zipWithIndex.find { case (member, _) => member.CharId == tplayer.CharId } match { // case Some((fromMember, fromIndex)) => // SwapMemberPosition(squad.Membership(position), fromMember) @@ -1273,7 +1273,7 @@ class SquadService extends Actor { //not a member of any squad, but we might become a member of this one GetSquad(guid) match { case Some(squad) => - if(ValidOpenSquadPosition(squad, position, tplayer.Certifications)) { + if(squad.isAvailable(position, tplayer.Certifications)) { //we could join but we may need permission from the squad leader first AddInviteAndRespond( squad.Leader.CharId, @@ -1940,7 +1940,7 @@ class SquadService extends Actor { * @param list a list of players to squads with expected entry redundancy */ def RemoveProximityInvites(list : Iterable[(Long, PlanetSideGUID)]) : Unit = { - val (ids, squads) = list.unzip + val (_, squads) = list.unzip squads.toSeq.distinct.foreach { squad => squadFeatures.get(squad) match { case Some(features) => @@ -2039,7 +2039,6 @@ class SquadService extends Actor { * @see `IndirectInvite` * @see `SquadFeatures::AutoApproveInvitationRequests` * @see `VacancyInvite` - * @see `ValidOpenSquadPosition` * @param squad the squad * @param invitedPlayer the unique character identifier for the player being invited * @param invitingPlayer the unique character identifier for the player who invited the former @@ -2049,8 +2048,8 @@ class SquadService extends Actor { */ def HandleVacancyInvite(squad : Squad, invitedPlayer : Long, invitingPlayer : Long, recruit : Player) : Option[(Squad, Int)] = { //accepted an invitation to join an existing squad - squad.Membership.zipWithIndex.find({ case (member, index) => - ValidOpenSquadPosition(squad, index, member, recruit.Certifications) + squad.Membership.zipWithIndex.find({ case (_, index) => + squad.isAvailable(index, recruit.Certifications) }) match { case Some((_, line)) => //position in squad found @@ -2206,7 +2205,6 @@ class SquadService extends Actor { * @see `SquadResponse.Join` * @see `StatConverter.Health` * @see `UpdateSquadListWhenListed` - * @see `ValidOpenSquadPosition` * @param player the new squad member; * this player is NOT the squad leader * @param squad the squad the player is joining @@ -2217,7 +2215,7 @@ class SquadService extends Actor { def JoinSquad(player : Player, squad : Squad, position : Int) : Boolean = { val charId = player.CharId val role = squad.Membership(position) - if(UserEvents.get(charId).nonEmpty && squad.Leader.CharId != charId && ValidOpenSquadPosition(squad, position, role, player.Certifications)) { + if(UserEvents.get(charId).nonEmpty && squad.Leader.CharId != charId && squad.isAvailable(position, player.Certifications)) { role.Name = player.Name role.CharId = charId role.Health = StatConverter.Health(player.Health, player.MaxHealth, min=1, max=64) @@ -2616,19 +2614,6 @@ class SquadService extends Actor { SquadEvents.unsubscribe(sender) //just to make certain } - /** - * Dispatch a message entailing the composition of this squad - * and focus on any specific aspects of it, purported as being changed recently. - * @see `SquadInfo` - * @see `UpdateSquadList(Squad, Option[SquadInfo])` - * @param squad the squad - * @param changes the highlighted aspects of the squad; - * these "changes" do not have to reflect the actual squad but are related to the contents of the message - */ - private def UpdateSquadList(squad : Squad, changes : SquadInfo) : Unit = { - UpdateSquadList(squad, Some(changes)) - } - /** * Dispatch a message entailing the composition of this squad when that squad is publicly available * and focus on any specific aspects of it, purported as being changed recently. @@ -2989,63 +2974,4 @@ object SquadService { member.Requirements = position.requirements } } - - /** - * Determine if the indicated squad role can be used for a player trying to join the squad. - * @see `ValidOpenSquadPosition` - * @param squad the squad - * @param position the position of the squad role being tested - * @param certs the certifications being offered for comparison testing - * @return `true`, if the squad role may accept the player with the given certification permissions; - * `false`, otherwise - */ - def ValidOpenSquadPosition(squad : Squad, position : Int, certs : Set[CertificationType.Value]) : Boolean = { - squad.Membership.lift(position) match { - case Some(member) => - ValidOpenSquadPosition(squad, position, member, certs) - case None => - false - } - } - - /** - * Determine if the indicated squad role can be used for a player trying to join the squad. - * @see `AvailableSquadPosition` - * @param squad the squad - * @param position the position of the squad role being tested - * @param member the squad role being tested - * @param certs the certifications being offered for comparison testing - * @return `true`, if the squad role may accept the player with the given certification permissions; - * `false`, otherwise - */ - def ValidOpenSquadPosition(squad : Squad, position : Int, member : Member, certs : Set[CertificationType.Value]) : Boolean = { - AvailableSquadPosition(squad, position, member) && certs.intersect(member.Requirements) == member.Requirements - } - - /** - * Determine if the indicated squad role is unoccupied but is also capable of being occupied. - * @see `AvailableSquadPosition(Squad, Int, Member)` - * @param squad the squad - * @param position the position of the squad role being tested - * @return `true`, if the squad role is unoccupied; - * `false`, otherwise - */ - def AvailableSquadPosition(squad : Squad, position : Int) : Boolean = squad.Membership.lift(position) match { - case Some(member) => - AvailableSquadPosition(squad, position, member) - case None => - false - } - - /** - * Determine if the indicated squad role is unoccupied but is also capable of being occupied. - * @param squad the squad - * @param position the position of the squad role being tested - * @param member the squad role being tested - * @return `true`, if the squad role is unoccupied; - * `false`, otherwise - */ - def AvailableSquadPosition(squad : Squad, position : Int, member : Member) : Boolean = { - squad.Availability(position) && member.CharId == 0 - } } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 2f5a03a8..b19f72bd 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -119,7 +119,7 @@ class WorldSessionActor extends Actor with MDCContextAware { */ var interstellarFerryTopLevelGUID : Option[PlanetSideGUID] = None val squadUI : LongMap[SquadUIElement] = new LongMap[SquadUIElement]() - val squad_supplement_id : Int = 11 + var squad_supplement_id : Int = 0 /** * `AvatarConverter` can only rely on the `Avatar`-local Looking For Squad variable. * When joining or creating a squad, the original state of the avatar's local LFS variable is blanked. @@ -396,11 +396,6 @@ class WorldSessionActor extends Actor with MDCContextAware { case SquadResponse.SetListSquad(squad_guid) => 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, char_id, opt_char_id, player_name, unk5, unk6) => val name = request_type match { case SquadResponseType.Invite if unk5 => @@ -433,27 +428,31 @@ class WorldSessionActor extends Actor with MDCContextAware { val membershipPositions = squad.Membership .zipWithIndex .filter { case (_, index ) => positionsToUpdate.contains(index) } + StartBundlingPackets() membershipPositions.find({ case(member, _) => member.CharId == avatar.CharId }) match { case Some((ourMember, ourIndex)) => //we are joining the squad //load each member's entry (our own too) + squad_supplement_id = squad.GUID.guid + 1 membershipPositions.foreach { case(member, index) => sendResponse(SquadMemberEvent.Add(squad_supplement_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) } //repeat our entry sendResponse(SquadMemberEvent.Add(squad_supplement_id, ourMember.CharId, ourIndex, ourMember.Name, ourMember.ZoneId, unk7 = 0)) //repeat of our entry + val playerGuid = player.GUID //turn lfs off val factionOnContinentChannel = s"${continent.Id}/${player.Faction}" if(avatar.LFS) { avatar.LFS = false - sendResponse(PlanetsideAttributeMessage(player.GUID, 53, 0)) - avatarService ! AvatarServiceMessage(factionOnContinentChannel, AvatarAction.PlanetsideAttribute(player.GUID, 53, 0)) + sendResponse(PlanetsideAttributeMessage(playerGuid, 53, 0)) + avatarService ! AvatarServiceMessage(factionOnContinentChannel, AvatarAction.PlanetsideAttribute(playerGuid, 53, 0)) } //squad colors - sendResponse(PlanetsideAttributeMessage(player.GUID, 31, squad_supplement_id)) - avatarService ! AvatarServiceMessage(factionOnContinentChannel, AvatarAction.PlanetsideAttribute(player.GUID, 31, squad_supplement_id)) - sendResponse(PlanetsideAttributeMessage(player.GUID, 32, ourIndex)) //associate with member position in squad + GiveSquadColorsInZone() + avatarService ! AvatarServiceMessage(factionOnContinentChannel, AvatarAction.PlanetsideAttribute(playerGuid, 31, squad_supplement_id)) + //associate with member position in squad + sendResponse(PlanetsideAttributeMessage(playerGuid, 32, ourIndex)) //a finalization? what does this do? sendResponse(SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(18))) updateSquad = UpdatesWhenEnrolledInSquad @@ -461,11 +460,16 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ => //other player is joining our squad //load each member's entry - membershipPositions.foreach { case(member, index) => - sendResponse(SquadMemberEvent.Add(squad_supplement_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) - } + GiveSquadColorsInZone( + membershipPositions.map { case(member, index) => + val charId = member.CharId + sendResponse(SquadMemberEvent.Add(squad_supplement_id, charId, index, member.Name, member.ZoneId, unk7 = 0)) + squadUI(charId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position) + charId + } + ) } + StopBundlingPackets() //send an initial dummy update for map icon(s) sendResponse(SquadState(PlanetSideGUID(squad_supplement_id), membershipPositions @@ -475,6 +479,7 @@ class WorldSessionActor extends Actor with MDCContextAware { )) case SquadResponse.Leave(squad, positionsToUpdate) => + StartBundlingPackets() positionsToUpdate.find({ case(member, _) => member == avatar.CharId }) match { case Some((ourMember, ourIndex)) => //we are leaving the squad @@ -484,10 +489,12 @@ class WorldSessionActor extends Actor with MDCContextAware { squadUI.remove(member) } //uninitialize + val playerGuid = player.GUID sendResponse(SquadMemberEvent.Remove(squad_supplement_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? + sendResponse(PlanetsideAttributeMessage(playerGuid, 31, 0)) //disassociate with squad? + avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(playerGuid, 31, 0)) + sendResponse(PlanetsideAttributeMessage(playerGuid, 32, 0)) //disassociate with member position in squad? + sendResponse(PlanetsideAttributeMessage(playerGuid, 34, 4294967295L)) //unknown, perhaps unrelated? lfs = false //a finalization? what does this do? sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18))) @@ -495,11 +502,17 @@ class WorldSessionActor extends Actor with MDCContextAware { squadChannel = None case _ => //remove each member's entry - positionsToUpdate.foreach { case(member, index) => - sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index)) - squadUI.remove(member) - } + GiveSquadColorsInZone( + positionsToUpdate.map { case(member, index) => + sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index)) + squadUI.remove(member) + member + }, + value = 0 + ) } + StopBundlingPackets() + squad_supplement_id = 0 case SquadResponse.AssignMember(squad, from_index, to_index) => //we've already swapped position internally; now we swap the cards @@ -3045,10 +3058,6 @@ class WorldSessionActor extends Actor with MDCContextAware { deadState = DeadState.Alive sendResponse(AvatarDeadStateMessage(DeadState.Alive, 0, 0, tplayer.Position, player.Faction, true)) //looking for squad (members) - if(squadUI.nonEmpty && squadUI(avatar.CharId).index == 0) { - sendResponse(PlanetsideAttributeMessage(guid, 31, 1)) - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(guid, 31, 1)) - } if(tplayer.LFS || lfs) { sendResponse(PlanetsideAttributeMessage(guid, 53, 1)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(guid, 53, 1)) @@ -3103,30 +3112,97 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + /** + * These messages are dispatched when first starting up the client and connecting to the server for the first time. + * While many of thee messages will be reused for other situations, they appear in this order only during startup. + */ def FirstTimeSquadSetup() : Unit = { sendResponse(SquadDetailDefinitionUpdateMessage.Init) + sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(6))) - //only need to load these once + //only need to load these once - they persist between zone transfers and respawns avatar.SquadLoadouts.Loadouts.foreach { case (index, loadout : SquadLoadout) => sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), index, SquadAction.ListSquadFavorite(loadout.task))) } + //non-squad GUID-0 counts as the settings when not joined with a squad sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.AssociateWithSquad())) sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.SetListSquad())) sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18))) squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitSquadList()) squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitCharId()) - squadSetup = SubsequentSpawnSquadSetup + squadSetup = RespawnSquadSetup } - def SubsequentSpawnSquadSetup() : Unit = { + /** + * These messages are used during each subsequent respawn to reset the squad colors on player nameplates and marquees. + * By using `squadUI` to maintain relevant information about squad members, + * especially the unique character identifier number, + * only the zone-specific squad members will receive the important messages about their squad member's spawn. + */ + def RespawnSquadSetup() : Unit = { if(squadUI.nonEmpty) { - //sendResponse(PlanetsideAttributeMessage(player.GUID, 31, squad_supplement_id)) - val (_, ourCard) = squadUI.find { case (id, card) => id == player.CharId }.get - sendResponse(PlanetsideAttributeMessage(player.GUID, 32, ourCard.index)) + sendResponse(PlanetsideAttributeMessage(player.GUID, 31, squad_supplement_id)) + avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 31, squad_supplement_id)) + sendResponse(PlanetsideAttributeMessage(player.GUID, 32, squadUI(player.CharId).index)) } } + /** + * These messages are used during each subsequent respawn to reset the squad colors on player nameplates and marquees. + * During a zone change, + * on top of other squad mates in the zone needing to have their knowledge of this player's squad colors changed, + * the player must also set squad colors for each other squad members. + * Default respawn functionality may resume afterwards. + */ + def ZoneChangeSquadSetup() : Unit = { + RespawnSquadSetup() + GiveSquadColorsInZone() + squadSetup = RespawnSquadSetup + } + + /** + * Allocate all squad members in zone and give their nameplates and their marquees the appropriate squad color. + */ + def GiveSquadColorsInZone() : Unit = { + GiveSquadColorsInZone(squadUI.keys, squad_supplement_id) + } + + /** + * Allocate the listed squad members in zone and give their nameplates and their marquees the appropriate squad color. + * @param members members of the squad to target + */ + def GiveSquadColorsInZone(members : Iterable[Long]) : Unit = { + GiveSquadColorsInZone(members, squad_supplement_id) + } + + /** + * Allocate the listed squad members in zone and give their nameplates and their marquees the appropriate squad color. + * @see `PlanetsideAttributeMessage` + * @param members members of the squad to target + * @param value the assignment value + */ + def GiveSquadColorsInZone(members : Iterable[Long], value : Long) : Unit = { + SquadMembersInZone(members).foreach { + members => sendResponse(PlanetsideAttributeMessage(members.GUID, 31, value)) + } + } + + /** + * For the listed squad member unique character identifier numbers, + * find and return all squad members in the current zone. + * @param members members of the squad to target + * @return a list of `Player` objects + */ + def SquadMembersInZone(members : Iterable[Long]) : Iterable[Player] = { + val players = continent.LivePlayers + for { + charId <- members + player = players.find { _.CharId == charId } + if player.nonEmpty + } yield player.get + } + def handleControlPkt(pkt : PlanetSideControlPacket) = { pkt match { case sync @ ControlSync(diff, _, _, _, _, _, fa, fb) => @@ -4729,24 +4805,24 @@ class WorldSessionActor extends Actor with MDCContextAware { if(squadUI.nonEmpty) { if(!lfs && squadUI(player.CharId).index == 0) { lfs = true - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 53, 1)) + avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 53, 1)) } } else if(!avatar.LFS) { avatar.LFS = true - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 53, 1)) + avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 53, 1)) } } else if(action == 37) { //Looking For Squad OFF if(squadUI.nonEmpty) { if(lfs && squadUI(player.CharId).index == 0) { lfs = false - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 53, 0)) + avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 53, 0)) } } else if(avatar.LFS) { avatar.LFS = false - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 53, 0)) + avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 53, 0)) } } @@ -8739,6 +8815,7 @@ class WorldSessionActor extends Actor with MDCContextAware { }) DisownDeployables() drawDeloyableIcon = RedrawDeployableIcons //important for when SetCurrentAvatar initializes the UI next zone + squadSetup = ZoneChangeSquadSetup continent.Population ! Zone.Population.Leave(avatar) }