modified squad user initialization messages to better focus on specific steps of the process; dealt with different methods of communicating squad information to users, though not yet ready to implement the switchboard protocol

This commit is contained in:
FateJH 2019-08-13 12:52:43 -04:00
parent e5b2efd381
commit 56d8748e99
5 changed files with 356 additions and 85 deletions

View file

@ -35,7 +35,7 @@ object SquadAction{
* 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 SquadMemberInitializationIssue() extends SquadAction(1)
final case class SaveSquadFavorite() extends SquadAction(3)
@ -53,6 +53,8 @@ object SquadAction{
final case class CancelSelectRoleForYourself(value: Long = 0) extends SquadAction(15)
final case class AssociateWithSquad() extends SquadAction(16)
final case class SetListSquad() extends SquadAction(17)
final case class ChangeSquadPurpose(purpose : String) extends SquadAction(19)
@ -110,10 +112,10 @@ object SquadAction{
}
)
val displayFullSquadCodec = everFailCondition.xmap[DisplayFullSquad] (
_ => DisplayFullSquad(),
val squadMemberInitializationIssueCodec = everFailCondition.xmap[SquadMemberInitializationIssue] (
_ => SquadMemberInitializationIssue(),
{
case DisplayFullSquad() => None
case SquadMemberInitializationIssue() => None
}
)
@ -173,6 +175,13 @@ object SquadAction{
}
)
val associateWithSquadCodec = everFailCondition.xmap[AssociateWithSquad] (
_ => AssociateWithSquad(),
{
case AssociateWithSquad() => None
}
)
val setListSquadCodec = everFailCondition.xmap[SetListSquad] (
_ => SetListSquad(),
{
@ -347,7 +356,7 @@ object SquadAction{
* &nbsp;&nbsp;&nbsp;&nbsp;`6 ` - UNKNOWN<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`8 ` - Request List Squad<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`9 ` - Stop List Squad<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`16` - UNKNOWN<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`16` - Associate with Squad<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`17` - Set List Squad (ui)<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`18` - UNKNOWN<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`26` - Reset All<br>
@ -415,7 +424,7 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
import scala.annotation.switch
((code : @switch) match {
case 0 => displaySquadCodec
case 1 => displayFullSquadCodec
case 1 => squadMemberInitializationIssueCodec
case 3 => saveSquadFavoriteCodec
case 4 => loadSquadFavoriteCodec
case 5 => deleteSquadFavoriteCodec
@ -424,6 +433,7 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
case 9 => stopListSquadCodec
case 10 => selectRoleForYourselfCodec
case 15 => cancelSelectRoleForYourselfCodec
case 16 => associateWithSquadCodec
case 17 => setListSquadCodec
case 19 => changeSquadPurposeCodec
case 20 => changeSquadZoneCodec
@ -442,7 +452,7 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
case 40 => findLfsSoldiersForRoleCodec
case 41 => cancelFindCodec
case 2 | 6 | 11 |
12 | 13 | 14 | 16 |
12 | 13 | 14 |
18 | 29 | 30 | 32 | 33 |
36 | 37 | 42 | 43 => unknownCodec(code)
case _ => failureCodec(code)

View file

@ -2,13 +2,14 @@
package services.teamwork
import net.psforever.objects.Player
import net.psforever.objects.zones.Zone
import net.psforever.packet.game._
import net.psforever.types.{SquadRequestType, Vector3}
object SquadAction {
trait Action
final case class Definition(player : Player, zone_ordinal_number : Int, guid : PlanetSideGUID, line : Int, action : SquadAction) extends Action
final case class Definition(player : Player, zone : Zone, guid : PlanetSideGUID, line : Int, action : SquadAction) extends Action
final case class Membership(request_type : SquadRequestType.Value, unk2 : Long, unk3 : Option[Long], player_name : String, unk5 : Option[Option[String]]) extends Action
final case class Update(char_id : Long, health : Int, max_health : Int, armor : Int, max_armor : Int, pos : Vector3, zone_number : Int) extends Action
}

View file

@ -14,8 +14,10 @@ object SquadResponse {
final case class UpdateList(infos : Iterable[(Int, SquadInfo)]) extends Response
final case class RemoveFromList(infos : Iterable[Int]) extends Response
final case class InitSquad(squad_guid : PlanetSideGUID) extends Response
final case class 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
final case class WantsSquadPosition(bid_name : String) extends Response

View file

@ -6,6 +6,7 @@ import net.psforever.objects.Player
import net.psforever.objects.definition.converter.StatConverter
import net.psforever.objects.loadouts.SquadLoadout
import net.psforever.objects.teamwork.{Member, Squad}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game._
import net.psforever.types._
import services.{GenericEventBus, Service}
@ -29,6 +30,19 @@ class SquadService extends Actor {
PlanetSideEmpire.VS -> ListBuffer.empty
)
private val invites : mutable.LongMap[Invitation] = mutable.LongMap[Invitation]()
/**
* `initialAssociation` per squad is similar to "Does this squad want to recruit members?"
* The squad does not have to be listed.
* Dispatches an `AssociateWithSquad` `SDAM` to the squad leader and ???
* and then a `SDDUM` that includes at least the squad owner name and char id
* when a squad entry is removed from the list.
* Dispatched only once when a squad is first listed
* or when the squad leader searches for recruits by proximity or for certain roles or by invite
* or when a spontaneous squad forms,
* whichever happens first.
* Additionally, the packets are also sent when the check is made when the continent is changed (or set).
*/
private val initialAssociation : ListBuffer[PlanetSideGUID] = new ListBuffer[PlanetSideGUID]()
private val queuedInvites : mutable.LongMap[List[Invitation]] = mutable.LongMap[List[Invitation]]()
private val viewDetails : mutable.LongMap[PlanetSideGUID] = mutable.LongMap[PlanetSideGUID]()
@ -166,6 +180,7 @@ class SquadService extends Actor {
memberToSquad += charId -> squad
idToSquad += id -> squad
idToSwitchboard += id -> switchboard
initialAssociation += squad.GUID
squad
}
@ -241,6 +256,7 @@ class SquadService extends Actor {
case SquadServiceMessage(tplayer, squad_action) => squad_action match {
case SquadAction.Membership(SquadRequestType.Invite, invitingPlayer, Some(invitedPlayer), _, _) =>
//this is just busy work; for actual joining operations, see SquadRequestType.Accept
//for the purposes of this code, tplayer.CharId == invitingPlayer
// FindBid(invitingPlayer, invitedPlayer) match {
// case Some(bid) =>
// //invitingPlayer and invitedPlayer have both tried to join each others's squads
@ -262,29 +278,25 @@ class SquadService extends Actor {
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)
val charId = tplayer.CharId
val bid = VacancyInvite(charId, tplayer.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))))
SquadEvents.publish(SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, charId, Some(invitedPlayer), tplayer.Name, false, Some(None))))
SquadEvents.publish(SquadServiceResponse(s"/$charId/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitedPlayer, Some(charId), 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))))
SquadEvents.publish(SquadServiceResponse(s"/$charId/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitedPlayer, Some(charId), tplayer.Name, true, Some(None))))
case _ => ;
}
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 {
//indirection; we're trying to invite ourselves to someone else's squad
val leaderCharId = squad.Leader.CharId
val bid = IndirectVacancy(tplayer, squad.GUID)
log.warn(s"$invitedPlayer has asked $invitingPlayer for an invitation to squad ${squad.Task}, but the squad leader needs to approve")
AddInvite(leaderCharId, 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))))
HandleBidForPosition(bid, tplayer)
case _ => ;
}
@ -314,28 +326,61 @@ class SquadService extends Actor {
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")
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"/$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 _ => ;
}
case Some(IndirectVacancy(recruit, guid)) =>
//tplayer / invitedPlayer is actually the squad leader
val recuitCharId = recruit.CharId
HandleVacancyInvite(guid, recuitCharId, invitedPlayer, recruit) match {
case Some((squad, line)) =>
SquadEvents.publish(SquadServiceResponse(s"/$recuitCharId/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitedPlayer, Some(recuitCharId), "", true, Some(None))))
JoinSquad(recruit, squad, line)
//since we are the squad leader, we do not want to brush off our queued squad invite tasks
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(VacancyInvite(invitingPlayer, _, guid)) =>
//accepted an invitation to join an existing squad
HandleVacancyInvite(guid, invitedPlayer, invitingPlayer, tplayer) match {
case Some((squad, line)) =>
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 _ => ;
}
// if(idToSquad.get(guid).isEmpty) {
// log.warn("Accept->Invite: the squad no longer exists")
// }
// else if(memberToSquad.get(invitedPlayer).nonEmpty) {
// log.warn("Accept->Invite: player is already a member of a squad and can not join a second one")
// }
// else {
// val squad = idToSquad(guid)
// if(!squad.AutoApproveInvitationRequests && squad.Leader.CharId != invitingPlayer) {
// //the inviting player was not the squad leader and this decision should be bounced off the squad leader
// val bid = IndirectVacancy(tplayer, guid)
// AddInvite(squad.Leader.CharId, bid) match {
// case out @ Some(_) if out.contains(bid) =>
// HandleBidForPosition(bid, tplayer)
// case _ => ;
// }
// }
// else {
// //if a suitable position in the squad can be found, player may occupy it
// squad.Membership.zipWithIndex.find({ case (member, index) =>
// ValidOpenSquadPosition(squad, index, member, tplayer.Certifications)
// }) match {
// case Some((_, line)) =>
// 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 _ => ;
// }
// }
// }
case Some(SpontaneousInvite(invitingPlayer)) =>
//we were invited by someone into a new squad they would form
@ -364,7 +409,7 @@ class SquadService extends Actor {
case None =>
val squad = StartSquad(invitingPlayer)
squad.Task = s"${tplayer.Name}'s Squad"
SquadEvents.publish(SquadServiceResponse(s"/$invitingPlayer/Squad", SquadResponse.InitSquad(squad.GUID)))
SquadEvents.publish(SquadServiceResponse(s"/$invitingPlayer/Squad", SquadResponse.AssociateWithSquad(squad.GUID)))
Some(squad)
}) match {
case Some(squad) =>
@ -409,7 +454,7 @@ class SquadService extends Actor {
.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.AssociateWithSquad(PlanetSideGUID(0))) )
SquadEvents.publish( SquadServiceResponse(s"/$leader/Squad", SquadResponse.Detail(PlanetSideGUID(0), SquadDetail().Complete)) )
}
else {
@ -456,19 +501,53 @@ class SquadService extends Actor {
case None => ;
}
// case SquadAction.Membership(SquadRequestType.Cancel, cancellingPlayer, _, _, _) =>
// //look for queued BidForPosition entries where we are the player who wants to join
// queuedInvites.foreach { case (leader, queueOfInvites) =>
// val list = queueOfInvites.filterNot { entry =>
// entry.isInstanceOf[BidForPosition] &&
// entry.asInstanceOf[BidForPosition].player.CharId == cancellingPlayer
// }
// if(list.nonEmpty && list.size != queueOfInvites.size) {
// queuedInvites(leader) = list
// }
// else if(list.isEmpty) {
// queuedInvites.remove(leader)
// }
// }
// //clean up active BidForPosition invite entries where we are the player who wants to join
// val list = invites.filter { case(_, entry) =>
// entry.isInstanceOf[BidForPosition] &&
// entry.asInstanceOf[BidForPosition].player.CharId == cancellingPlayer
// }
// list.foreach {
// case(charId, entry : BidForPosition) =>
// RemoveInvite(charId)
// SquadEvents.publish( SquadServiceResponse(s"/$charId/Squad", SquadResponse.Membership(SquadResponseType.Cancel, 0, 0, cancellingPlayer, None, entry.player.Name, false, None)))
// NextInvite(charId) match {
// case Some(bid : BidForPosition) =>
// HandleBidForPosition(bid, tplayer)
// case Some(bid) =>
// SquadEvents.publish(SquadServiceResponse(s"/$charId/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, bid.InviterCharId, Some(charId), bid.InviterName, false, Some(None))))
// case _ => ;
// }
// case _ => ;
// }
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
log.info(s"Player ${leader.Name} steps down from leading ${squad.Task}")
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)))
SquadEvents.publish(SquadServiceResponse(s"/$promotingPlayer/Squad", SquadResponse.AssociateWithSquad(PlanetSideGUID(0))))
SquadEvents.publish(SquadServiceResponse(s"/$promotedPlayer/Squad", SquadResponse.AssociateWithSquad(squad.GUID)))
UpdateSquadListWhenListed(
squad,
SquadInfo().Leader(leader.Name)
@ -509,10 +588,10 @@ class SquadService extends Actor {
case None => ;
}
case SquadAction.Definition(tplayer : Player, zone_ordinal_number : Int, guid : PlanetSideGUID, line : Int, action : SquadAction) =>
case SquadAction.Definition(tplayer : Player, zone : Zone, guid : PlanetSideGUID, line : Int, action : SquadAction) =>
import net.psforever.packet.game.SquadAction._
val pSquadOpt = GetParticipatingSquad(tplayer)
val lSquadOpt = GetLeadingSquad(tplayer, zone_ordinal_number, pSquadOpt)
val lSquadOpt = GetLeadingSquad(tplayer, zone.Number, pSquadOpt)
//the following actions can only be performed by a squad's leader
action match {
case SaveSquadFavorite() =>
@ -528,8 +607,8 @@ class SquadService extends Actor {
case Some(loadout : SquadLoadout) if squad.Size == 1 =>
log.info(s"${tplayer.Name} is loading a squad composition: $loadout")
SquadService.LoadSquadDefinition(squad, loadout)
sender ! SquadServiceResponse("", SquadResponse.InitSquad(squad.GUID))
UpdateSquadList(squad, SquadInfo().Task(squad.Task).ZoneId(PlanetSideZoneID(squad.ZoneId)).Capacity(squad.Capacity))
sender ! SquadServiceResponse("", SquadResponse.AssociateWithSquad(squad.GUID))
UpdateSquadList(squad, SquadService.SquadList.Publish(squad))
UpdateSquadDetail(PlanetSideGUID(0), squad)
case _ =>
}
@ -550,7 +629,12 @@ class SquadService extends Actor {
val squad = lSquadOpt.getOrElse(StartSquad(tplayer))
squad.ZoneId = zone.zoneId.toInt
UpdateSquadListWhenListed(squad, SquadInfo().ZoneId(zone))
UpdateSquadDetail(squad.GUID, squad, SquadDetail().ZoneId(zone))
InitialAssociation(squad)
sender ! SquadServiceResponse("", SquadResponse.Detail(
squad.GUID,
SquadService.Detail.Publish(squad))
)
UpdateSquadDetail(squad.GUID, squad.Membership.map { _m => _m.CharId }.filterNot { _ == squad.Leader.CharId }, SquadDetail().ZoneId(zone))
case CloseSquadMemberPosition(position) =>
val squad = lSquadOpt.getOrElse(StartSquad(tplayer))
@ -647,7 +731,8 @@ class SquadService extends Actor {
if(!squad.Listed && squad.Task.nonEmpty && squad.ZoneId > 0) {
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened public recruitment for squad ${squad.Task}")
squad.Listed = true
sender ! SquadServiceResponse("", SquadResponse.InitSquad(squad.GUID))
InitialAssociation(squad)
sender ! SquadServiceResponse("", SquadResponse.SetListSquad(squad.GUID))
UpdateSquadList(squad, None)
}
@ -656,7 +741,7 @@ class SquadService extends Actor {
if(squad.Listed) {
log.info(s"${tplayer.Name}-${tplayer.Faction} has closed public recruitment for squad ${squad.Task}")
squad.Listed = false
sender ! SquadServiceResponse("", SquadResponse.InitSquad(PlanetSideGUID(0)))
sender ! SquadServiceResponse("", SquadResponse.AssociateWithSquad(PlanetSideGUID(0)))
UpdateSquadList(squad, None)
}
@ -676,6 +761,9 @@ class SquadService extends Actor {
squad.AutoApproveInvitationRequests = false
UpdateSquadListWhenListed(squad, SquadInfo().Task("").ZoneId(None).Capacity(squad.Capacity))
UpdateSquadDetail(squad.GUID, squad)
sender ! SquadServiceResponse("", SquadResponse.AssociateWithSquad(PlanetSideGUID(0)))
initialAssociation += squad.GUID
//do not unlist an already listed squad
case _ =>
}
@ -736,6 +824,63 @@ class SquadService extends Actor {
//squad does not exist? assume old local data; force update to correct discrepancy
}
//the following action can be performed by anyone who has tried to join a squad
case (_, CancelSelectRoleForYourself(_)) =>
val cancellingPlayer = tplayer.CharId
idToSquad.get(guid) match {
case Some(squad) =>
//assumption: a player who is cancelling will rarely end up with their invite queued
val leaderCharId = squad.Leader.CharId
//clean up any active BidForPosition invite entry where we are the player who wants to join the leader's squad
((invites.get(leaderCharId) match {
case out @ Some(entry) if entry.isInstanceOf[BidForPosition] &&
entry.asInstanceOf[BidForPosition].player.CharId == cancellingPlayer =>
out
case _ =>
None
}) match {
case Some(entry : BidForPosition) =>
RemoveInvite(leaderCharId)
SquadEvents.publish( SquadServiceResponse(s"/$leaderCharId/Squad", SquadResponse.Membership(SquadResponseType.Cancel, 0, 0, cancellingPlayer, None, entry.player.Name, false, Some(None))))
NextInvite(leaderCharId) match {
case Some(bid : BidForPosition) =>
HandleBidForPosition(bid, tplayer)
case Some(bid) =>
SquadEvents.publish(SquadServiceResponse(s"/$leaderCharId/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, bid.InviterCharId, Some(leaderCharId), bid.InviterName, false, Some(None))))
case _ => ;
}
Some(true)
case _ =>
None
}).orElse(
//look for a queued BidForPosition entry where we are the player who wants to join the leader's squad
(queuedInvites.get(leaderCharId) match {
case Some(_list) =>
(_list, _list.indexWhere { entry =>
entry.isInstanceOf[BidForPosition] &&
entry.asInstanceOf[BidForPosition].player.CharId == cancellingPlayer
})
case None =>
(Nil, -1)
}) match {
case (_, -1) =>
None //no change
case (list, index) if list.size == 1 =>
val entry = list.head.asInstanceOf[BidForPosition]
SquadEvents.publish( SquadServiceResponse(s"/$leaderCharId/Squad", SquadResponse.Membership(SquadResponseType.Cancel, 0, 0, cancellingPlayer, None, entry.player.Name, false, Some(None))))
queuedInvites.remove(leaderCharId)
Some(true)
case (list, index) =>
val entry = list(index).asInstanceOf[BidForPosition]
SquadEvents.publish( SquadServiceResponse(s"/$leaderCharId/Squad", SquadResponse.Membership(SquadResponseType.Cancel, 0, 0, cancellingPlayer, None, entry.player.Name, false, Some(None))))
queuedInvites(leaderCharId) = list.take(index) ++ list.drop(index+1)
Some(true)
}
)
case _ => ;
}
//the following action can be performed by ???
case (Some(squad), AssignSquadMemberToRole(position, char_id)) =>
val membership = squad.Membership.zipWithIndex
@ -771,12 +916,12 @@ class SquadService extends Actor {
}
//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 => ;
}
case (_, SquadMemberInitializationIssue()) =>
// idToSquad.get(guid) match {
// case Some(squad) =>
// sender ! SquadServiceResponse("", SquadResponse.Detail(squad.GUID, SquadService.Detail.Publish(squad)))
// case None => ;
// }
case _ => ;
}
@ -886,19 +1031,90 @@ class SquadService extends Actor {
}
}
def HandleVacancyInvite(squad_guid : PlanetSideGUID, invitedPlayer : Long, invitingPlayer : Long, recruit : Player) : Option[(Squad, Int)] = {
//accepted an invitation to join an existing squad
if(idToSquad.get(squad_guid).isEmpty) {
log.warn(s"Accept->Invite: the squad #${squad_guid.guid} no longer exists")
None
}
else if(memberToSquad.get(invitedPlayer).nonEmpty) {
log.warn(s"Accept->Invite: ${recruit.Name} is already a member of a squad and can not join squad #${squad_guid.guid}")
None
}
else {
val squad = idToSquad(squad_guid)
if(!squad.AutoApproveInvitationRequests && squad.Leader.CharId != invitingPlayer) {
//the inviting player was not the squad leader and this decision should be bounced off the squad leader
val bid = IndirectVacancy(recruit, squad_guid)
AddInvite(squad.Leader.CharId, bid) match {
case out @ Some(_) if out.contains(bid) =>
SquadEvents.publish(SquadServiceResponse(s"/$invitingPlayer/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitingPlayer, Some(invitedPlayer), recruit.Name, false, Some(None))))
HandleBidForPosition(bid, recruit)
case _ => ;
}
log.info(s"Accept->Invite: ${recruit.Name} must await an invitation from the leader of squad #${squad_guid.guid}")
None
}
else {
//if a suitable position in the squad can be found, player may occupy it
squad.Membership.zipWithIndex.find({ case (member, index) =>
ValidOpenSquadPosition(squad, index, member, recruit.Certifications)
}) match {
case Some((_, line)) =>
Some((squad, line))
case _ =>
if(squad.Size == squad.Capacity) {
log.warn(s"Accept->Invite: squad #${squad_guid.guid} is already full and ${recruit.Name} can not join it")
}
else {
log.warn(s"Accept->Invite: squad #${squad_guid.guid} has no positions available that satisfy ${recruit.Name}")
}
None
}
}
}
}
def InitialAssociation(squad : Squad) : Boolean = {
val guid = squad.GUID
initialAssociation.indexOf(guid) match {
case -1 => ;
case index =>
initialAssociation.remove(index)
val charId = squad.Leader.CharId
SquadEvents.publish(
SquadServiceResponse(s"/$charId/Squad", SquadResponse.AssociateWithSquad(guid))
)
SquadEvents.publish(
SquadServiceResponse(s"/$charId/Squad", SquadResponse.Detail(
guid,
SquadService.Detail.Publish(squad))
)
)
}
false
}
def HandleBidForPosition(bid : BidForPosition, player : Player) : Unit = {
idToSquad.get(bid.squad_guid) match {
HandleBidForPosition(bid, bid.squad_guid, bid.player.Name, player)
}
def HandleBidForPosition(bid : IndirectVacancy, player : Player) : Unit = {
HandleBidForPosition(bid, bid.squad_guid, bid.player.Name, player)
}
def HandleBidForPosition(bid : Invitation, squad_guid : PlanetSideGUID, name : String, player : Player) : Unit = {
idToSquad.get(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)))
SquadEvents.publish(SquadServiceResponse(s"/$leaderCharId/Squad", SquadResponse.WantsSquadPosition(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")
log.error(s"Attempted to process ${bid.InviterName}'s bid for a position in a squad (id:${squad_guid.guid}) that does not exist")
}
}
@ -915,6 +1131,8 @@ class SquadService extends Actor {
position.ZoneId = 13
memberToSquad(charId) = squad
InitialAssociation(squad)
SquadEvents.publish( SquadServiceResponse(s"/$charId/Squad", SquadResponse.AssociateWithSquad(squad.GUID)) )
val size = squad.Size
if(size == 1) {
//leader joins the squad? do nothing?
@ -934,20 +1152,18 @@ class SquadService extends Actor {
}
else {
//joining an active squad; everybody updates differently
//new member gets full UI updates
val indices = squad.Membership.zipWithIndex
.collect({ case (member, index) if member.CharId != 0 => index }).toList
//new member gets full squad UI updates
val indices = squad.Membership.zipWithIndex.collect({ case (member, index) if member.CharId != 0 => index }).toList
SquadEvents.publish(SquadServiceResponse(s"/$charId/Squad", SquadResponse.Join(squad, indices)))
//other squad members see us joining the squad
InitSquadDetail(squad.GUID, Seq(charId), squad)
//other squad members see new member joining the squad
val updatedIndex = List(line)
squad.Membership
.filterNot { member => member.CharId == 0 || member.CharId == charId }
.foreach { member =>
SquadEvents.publish(SquadServiceResponse(s"/${member.CharId}/Squad", SquadResponse.Join(squad, updatedIndex)))
}
UpdateSquadDetail(squad.GUID, squad,
SquadDetail().Members(List(SquadPositionEntry(line, SquadPositionDetail().CharId(charId).Name(player.Name))))
)
val otherMembers = squad.Membership.filterNot { member => member.CharId == 0 || member.CharId == charId }.map{ _.CharId }
otherMembers.foreach { member =>
SquadEvents.publish(SquadServiceResponse(s"/$member/Squad", SquadResponse.Join(squad, updatedIndex)))
}
val details = SquadDetail().Members(List(SquadPositionEntry(line, SquadPositionDetail().CharId(charId).Name(player.Name))))
UpdateSquadDetail(squad.GUID, Seq(charId), details)
}
UpdateSquadListWhenListed(squad, SquadInfo().Size(size))
true
@ -1085,17 +1301,36 @@ class SquadService extends Actor {
}
}
def InitSquadDetail(squad : Squad) : Unit = {
InitSquadDetail(squad.GUID, squad.Membership.map { member => member.CharId }, squad)
}
def InitSquadDetail(guid : PlanetSideGUID, squad : Squad) : Unit = {
InitSquadDetail(guid, squad.Membership.map { member => member.CharId }, squad)
}
def InitSquadDetail(guid : PlanetSideGUID, toMembers : Iterable[Long], squad : Squad) : Unit = {
val output = SquadResponse.Detail(guid, SquadService.Detail.Publish(squad))
toMembers.foreach { charId => SquadEvents.publish(SquadServiceResponse(s"/$charId/Squad", output)) }
}
def UpdateSquadDetail(guid : PlanetSideGUID, squad : Squad) : Unit = {
UpdateSquadDetail(guid, squad, SquadService.Detail.Publish(squad))
}
def UpdateSquadDetail(guid : PlanetSideGUID, squad : Squad, detail : SquadDetail) : Unit = {
val output = SquadResponse.Detail(guid, detail)
squad.Membership
.filter { _.CharId > 0L }
.foreach { member =>
SquadEvents.publish(SquadServiceResponse(s"/${member.CharId}/Squad", output))
}
def UpdateSquadDetail(squad : Squad, details : SquadDetail) : Unit = {
UpdateSquadDetail(squad.GUID, squad.Membership.map { member => member.CharId }, details)
}
def UpdateSquadDetail(guid : PlanetSideGUID, squad : Squad, details : SquadDetail) : Unit = {
UpdateSquadDetail(guid, squad.Membership.map { member => member.CharId }, details)
}
def UpdateSquadDetail(guid : PlanetSideGUID, toMembers : Iterable[Long], details : SquadDetail) : Unit = {
if(toMembers.nonEmpty) {
val output = SquadResponse.Detail(guid, details)
toMembers.foreach { charId => SquadEvents.publish(SquadServiceResponse(s"/$charId/Squad", output)) }
}
}
}
@ -1105,12 +1340,31 @@ object SquadService {
def InviterName : String = name
}
/**
* Utilized when one player attempts to join an existing squad in a specific role.
* @param player na
* @param squad_guid na
* @param position na
*/
final case class BidForPosition(player : Player, squad_guid : PlanetSideGUID, position : Int)
extends Invitation(player.CharId, player.Name)
/**
* Utilized when one squad member issues an invite for some other player.
* @param char_id na
* @param name na
* @param squad_guid na
*/
final case class VacancyInvite(char_id : Long, name : String, squad_guid : PlanetSideGUID)
extends Invitation(char_id, name)
final case class IndirectVacancy(player : Player, squad_guid : PlanetSideGUID)
extends Invitation(player.CharId, player.Name)
/**
* Utilized when one player issues an invite for some other player for a squad that does not yet exist.
* @param player na
*/
final case class SpontaneousInvite(player : Player)
extends Invitation(player.CharId, player.Name)
@ -1130,6 +1384,7 @@ object SquadService {
object Detail {
def Publish(squad : Squad) : SquadDetail = {
SquadDetail()
.Field1(squad.GUID.guid)
.LeaderCharId(squad.Leader.CharId)
.LeaderName(squad.Leader.Name)
.Task(squad.Task)