retooled invitation case classes into much more complicated invitation entities that manage their own behaviors for messaging invites, acceptances, rejection, and generalize queries; this removes a ton of match casting as a branch mechanic

This commit is contained in:
Fate-JH 2024-09-16 01:48:35 -04:00
parent 2372a95040
commit 1968377d05
10 changed files with 1867 additions and 1827 deletions

View file

@ -1,25 +1,23 @@
// Copyright (c) 2019-2022 PSForever
// Copyright (c) 2019-2024 PSForever
package net.psforever.services.teamwork
import akka.actor.{Actor, ActorRef, Terminated}
import net.psforever.actors.session.SessionActor
import net.psforever.packet.game.ChatMsg
import net.psforever.types.ChatMessageType
import java.io.{PrintWriter, StringWriter}
import scala.annotation.unused
import scala.collection.concurrent.TrieMap
import scala.collection.mutable
//
import net.psforever.actors.session.SessionActor
import net.psforever.objects.{LivePlayerList, Player}
import net.psforever.objects.teamwork.{Member, Squad, SquadFeatures}
import net.psforever.objects.avatar.{Avatar, Certification}
import net.psforever.objects.definition.converter.StatConverter
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.ChatMsg
import net.psforever.packet.game.SquadAction._
import net.psforever.packet.game.{PlanetSideZoneID, SquadDetail, SquadInfo, SquadPositionDetail, SquadPositionEntry, SquadAction => SquadRequestAction}
import net.psforever.services.Service
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, SquadRequestType, SquadResponseType}
import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID, SquadRequestType, SquadResponseType}
class SquadService extends Actor {
import SquadService._
@ -75,8 +73,6 @@ class SquadService extends Actor {
private def info(msg: String): Unit = log.info(msg)
private def debug(msg: String): Unit = log.debug(msg)
override def postStop(): Unit = {
//squads and members (users)
squadFeatures.foreach {
@ -143,13 +139,11 @@ class SquadService extends Actor {
* @param charId the potential member identifier
* @return the discovered squad, or `None`
*/
def GetParticipatingSquad(charId: Long): Option[SquadFeatures] =
memberToSquad.get(charId) match {
case Some(id) =>
squadFeatures.get(id)
case None =>
None
}
def GetParticipatingSquad(charId: Long): Option[SquadFeatures] = {
memberToSquad
.get(charId)
.flatMap(GetSquad)
}
/**
* If this player is a member of any squad, discover that squad.
@ -171,28 +165,25 @@ class SquadService extends Actor {
* the expectation is that the provided squad is a known participating squad
* @return the discovered squad, or `None`
*/
def GetLeadingSquad(charId: Long, opt: Option[SquadFeatures]): Option[SquadFeatures] =
opt.orElse(GetParticipatingSquad(charId)) match {
case Some(features) =>
if (features.Squad.Leader.CharId == charId) {
Some(features)
} else {
None
}
case _ =>
None
}
def GetLeadingSquad(charId: Long, opt: Option[SquadFeatures]): Option[SquadFeatures] = {
opt
.orElse(GetParticipatingSquad(charId))
.collect {
case features if features.Squad.Leader.CharId == charId =>
features
}
}
def receive: Receive = {
//subscribe to a faction's channel - necessary to receive updates about listed squads
case Service.Join(faction) if "TRNCVS".indexOf(faction) > -1 =>
case Service.Join(faction) if SquadService.FactionWordSalad.indexOf(faction) > -1 =>
JoinByFaction(faction, sender())
//subscribe to the player's personal channel - necessary for future and previous squad information
case Service.Join(char_id) =>
JoinByCharacterId(char_id, sender())
case Service.Leave(Some(faction)) if "TRNCVS".indexOf(faction) > -1 =>
case Service.Leave(Some(faction)) if SquadService.FactionWordSalad.indexOf(faction) > -1 =>
LeaveByFaction(faction, sender())
case Service.Leave(Some(char_id)) =>
@ -244,7 +235,7 @@ class SquadService extends Actor {
UpdateSquadListWhenListed(features, changes)
case SquadService.ResendActiveInvite(charId) =>
invitations.resendActiveInvite(charId)
invitations.reloadActiveInvite(charId)
case SquadService.ListAllCurrentInvites(charId) =>
ListCurrentInvitations(charId)
@ -259,6 +250,121 @@ class SquadService extends Actor {
log.warn(s"Unhandled message $msg from ${sender()}")
}
def SquadActionMembership(tplayer: Player, zone: Zone, action: Any): Unit = {
action match {
case SquadAction.Membership(SquadRequestType.Invite, invitingPlayer, Some(_invitedPlayer), invitedName, _) =>
SquadActionMembershipInvite(tplayer, invitingPlayer, _invitedPlayer, invitedName)
case SquadAction.Membership(SquadRequestType.ProximityInvite, invitingPlayer, _, _, _) =>
SquadActionMembershipProximityInvite(zone, invitingPlayer)
case SquadAction.Membership(SquadRequestType.Accept, invitedPlayer, _, _, _) =>
SquadActionMembershipAccept(tplayer, invitedPlayer)
case SquadAction.Membership(SquadRequestType.Leave, actingPlayer, _leavingPlayer, name, _) =>
SquadActionMembershipLeave(tplayer, actingPlayer, _leavingPlayer, name)
case SquadAction.Membership(SquadRequestType.Reject, rejectingPlayer, _, _, _) =>
SquadActionMembershipReject(tplayer, rejectingPlayer)
case SquadAction.Membership(SquadRequestType.Disband, char_id, _, _, _) =>
SquadActionMembershipDisband(char_id)
case SquadAction.Membership(SquadRequestType.Cancel, cancellingPlayer, _, _, _) =>
SquadActionMembershipCancel(cancellingPlayer)
case SquadAction.Membership(SquadRequestType.Promote, _, _, _, _) => ()
// case SquadAction.Membership(SquadRequestType.Promote, promotingPlayer, Some(_promotedPlayer), promotedName, _) =>
// SquadActionMembershipPromote(promotingPlayer, _promotedPlayer, promotedName, SquadServiceMessage(tplayer, zone, action), sender())
case SquadAction.Membership(event, _, _, _, _) =>
info(s"SquadAction.Membership: $event is not yet supported")
case msg =>
log.warn(s"Unhandled message $msg from ${sender()}")
}
}
def SquadActionDefinition(
message: SquadServiceMessage,
action: SquadRequestAction,
guid: PlanetSideGUID
): Unit = {
val tplayer = message.tplayer
(action match {
//the following actions only perform an action upon the squad
case _: ChangeSquadPurpose => GetOrCreateSquadOnlyIfLeader(tplayer)
case _: ChangeSquadZone => GetOrCreateSquadOnlyIfLeader(tplayer)
case _: AddSquadMemberPosition => GetOrCreateSquadOnlyIfLeader(tplayer)
case _: ChangeSquadMemberRequirementsRole => GetOrCreateSquadOnlyIfLeader(tplayer)
case _: ChangeSquadMemberRequirementsDetailedOrders => GetOrCreateSquadOnlyIfLeader(tplayer)
case _: ChangeSquadMemberRequirementsCertifications => GetOrCreateSquadOnlyIfLeader(tplayer)
case _: LocationFollowsSquadLead => GetOrCreateSquadOnlyIfLeader(tplayer)
case _: RequestListSquad => GetOrCreateSquadOnlyIfLeader(tplayer)
case _: StopListSquad => GetLeadingSquad(tplayer, None)
//the following actions cause changes with the squad composition or with invitations
case AutoApproveInvitationRequests(_) =>
GetOrCreateSquadOnlyIfLeader(tplayer)
.foreach(features => invitations.autoApproveInvitationRequests(tplayer.CharId, features))
None
case CloseSquadMemberPosition(position) =>
GetOrCreateSquadOnlyIfLeader(tplayer)
.collect {
case features if features.Squad.Membership(position).CharId > 0 =>
LeaveSquad(features.Squad.Membership(position).CharId, features)
features
}
case FindLfsSoldiersForRole(position) =>
GetLeadingSquad(tplayer, None)
.foreach(features => invitations.findLfsSoldiersForRole(tplayer, features, position))
None
case CancelFind() =>
GetLeadingSquad(tplayer, None)
.foreach(features => invitations.cancelFind(Some(features)))
None
case SelectRoleForYourself(position) =>
GetParticipatingSquad(tplayer) match {
case out @ Some(features) =>
if (features.Squad.GUID == guid) {
out
} else {
//this isn't the squad we're looking for by GUID; as a precaution, reload all of the published squad list
val faction = tplayer.Faction
subs.Publish(faction, SquadResponse.InitList(PublishedLists(tplayer.Faction)))
None
}
case _ =>
GetSquad(guid)
.foreach(features => invitations.selectRoleForYourselfAsInvite(tplayer, features, position))
None
}
case _: CancelSelectRoleForYourself =>
GetSquad(guid)
.foreach(features => invitations.cancelSelectRoleForYourself(tplayer, features))
None
case search: SearchForSquadsWithParticularRole =>
SquadActionDefinitionSearchForSquadsWithParticularRole(tplayer, search)
None
case _: CancelSquadSearch =>
SquadActionDefinitionCancelSquadSearch(tplayer.CharId)
None
case _: DisplaySquad =>
GetSquad(guid) match {
case out @ Some(_) =>
SquadActionDefinitionDisplaySquad(tplayer, guid)
out
case None =>
None
}
case _: SquadInitializationIssue =>
SquadActionDefinitionSquadInitializationIssue(tplayer, guid)
None
case _ =>
GetSquad(guid)
})
.foreach(features => features.Switchboard.tell(message, sender()))
}
/**
* Subscribe to a faction-wide channel.
* @param faction sub-channel name
@ -337,23 +443,17 @@ class SquadService extends Actor {
*/
def LeaveInGeneral(sender: ActorRef): Unit = {
context.unwatch(sender)
subs.UserEvents.find {
case (_, subscription) => (subscription eq sender) || subscription.path.equals(sender.path)
} match {
case Some((to, _)) =>
LeaveService(to, sender)
case _ => ()
}
subs
.UserEvents
.find { case (_, subscription) => (subscription eq sender) || subscription.path.equals(sender.path) }
.foreach { case (to, _) => LeaveService(to, sender) }
}
def performStartSquad(sender: ActorRef, player: Player): Unit = {
def performStartSquad(sender: ActorRef, player: Player): Option[SquadFeatures] = {
val invitingPlayerCharId = player.CharId
if (EnsureEmptySquad(invitingPlayerCharId)) {
GetParticipatingSquad(player) match {
case Some(participating) =>
//invitingPlayer became part of a squad while invited player was answering the original summons
Some(participating)
case _ =>
GetParticipatingSquad(player)
.orElse {
//generate a new squad, with invitingPlayer as the leader
val features = StartSquad(player)
val squad = features.Squad
@ -361,7 +461,9 @@ class SquadService extends Actor {
subs.Publish(invitingPlayerCharId, SquadResponse.IdentifyAsSquadLeader(squad.GUID))
sender.tell(SquadInvitationManager.FinishStartSquad(features), self)
Some(features)
}
}
} else {
None
}
}
@ -381,52 +483,14 @@ class SquadService extends Actor {
def SquadActionInitCharId(tplayer: Player): Unit = {
val charId = tplayer.CharId
GetParticipatingSquad(charId) match {
case None => ()
case Some(features) =>
features.Switchboard ! SquadSwitchboard.Join(tplayer, 0, sender())
}
GetParticipatingSquad(charId)
.foreach(features => features.Switchboard.tell(SquadSwitchboard.Join(tplayer, 0, sender()), self))
}
def SquadServiceReloadSquadDecoration(faction: PlanetSideEmpire.Value, to: Long): Unit = {
ApplySquadDecorationToEntriesForUser(faction, to)
}
def SquadActionMembership(tplayer: Player, zone: Zone, action: Any): Unit = {
action match {
case SquadAction.Membership(SquadRequestType.Invite, invitingPlayer, Some(_invitedPlayer), invitedName, _) =>
SquadActionMembershipInvite(tplayer, invitingPlayer, _invitedPlayer, invitedName)
case SquadAction.Membership(SquadRequestType.ProximityInvite, invitingPlayer, _, _, _) =>
SquadActionMembershipProximityInvite(zone, invitingPlayer)
case SquadAction.Membership(SquadRequestType.Accept, invitedPlayer, _, _, _) =>
SquadActionMembershipAccept(tplayer, invitedPlayer)
case SquadAction.Membership(SquadRequestType.Leave, actingPlayer, _leavingPlayer, name, _) =>
SquadActionMembershipLeave(tplayer, actingPlayer, _leavingPlayer, name)
case SquadAction.Membership(SquadRequestType.Reject, rejectingPlayer, _, _, _) =>
SquadActionMembershipReject(tplayer, rejectingPlayer)
case SquadAction.Membership(SquadRequestType.Disband, char_id, _, _, _) =>
SquadActionMembershipDisband(char_id)
case SquadAction.Membership(SquadRequestType.Cancel, cancellingPlayer, _, _, _) =>
SquadActionMembershipCancel(cancellingPlayer)
case SquadAction.Membership(SquadRequestType.Promote, _, _, _, _) =>
()
// case SquadAction.Membership(SquadRequestType.Promote, promotingPlayer, Some(_promotedPlayer), promotedName, _) =>
// SquadActionMembershipPromote(promotingPlayer, _promotedPlayer, promotedName, SquadServiceMessage(tplayer, zone, action), sender())
case SquadAction.Membership(event, _, _, _, _) =>
debug(s"SquadAction.Membership: $event is not yet supported")
case _ => ()
}
}
def SquadActionMembershipInvite(
tplayer: Player,
invitingPlayer: Long,
@ -445,59 +509,57 @@ class SquadService extends Actor {
})
.headOption
.collectFirst {
//important: squads must know about the person too
//important: squads must know about the person too
a => subs.UserEvents.keys.find(_ == a.id)
}.flatten match {
case Some(invitedPlayer) if invitingPlayer != invitedPlayer =>
(GetParticipatingSquad(invitingPlayer), GetParticipatingSquad(invitedPlayer)) match {
case (Some(features1), Some(features2))
if features1.Squad.GUID == features2.Squad.GUID =>
//both players are in the same squad; no need to do anything
}
.flatten
.collect {
case invitedPlayer if invitingPlayer != invitedPlayer =>
(GetParticipatingSquad(invitingPlayer), GetParticipatingSquad(invitedPlayer)) match {
case (Some(features1), Some(features2))
if features1.Squad.GUID == features2.Squad.GUID =>
//both players are in the same squad; no need to do anything
case (Some(invitersFeatures), Some(invitedFeatures)) if {
val squad1 = invitersFeatures.Squad
val squad2 = invitedFeatures.Squad
squad1.Leader.CharId == invitingPlayer && squad2.Leader.CharId == invitedPlayer &&
squad1.Size > 1 && squad2.Size > 1 } =>
//we might do some platoon chicanery with this case later
//TODO platoons
case (Some(invitersFeatures), Some(invitedFeatures)) if {
val squad1 = invitersFeatures.Squad
val squad2 = invitedFeatures.Squad
squad1.Leader.CharId == invitingPlayer && squad2.Leader.CharId == invitedPlayer &&
squad1.Size > 1 && squad2.Size > 1 } =>
//we might do some platoon chicanery with this case later
//TODO platoons
case (Some(invitersFeatures), Some(invitedFeatures))
if invitedFeatures.Squad.Size == 1 =>
//both players belong to squads, but the invitedPlayer's squad (invitedFeatures) is underutilized
//treat the same as "the classic situation" using invitersFeatures
invitations.createVacancyInvite(tplayer, invitedPlayer, invitersFeatures)
case (Some(invitersFeatures), Some(invitedFeatures))
if invitedFeatures.Squad.Size == 1 =>
//both players belong to squads, but the invitedPlayer's squad (invitedFeatures) is underutilized
//treat the same as "the classic situation" using invitersFeatures
invitations.createInvitationToJoinSquad(tplayer, invitedPlayer, invitersFeatures)
case (Some(invitersFeatures), Some(invitedFeatures))
if invitersFeatures.Squad.Size == 1 =>
//both players belong to squads, but the invitingPlayer's squad is underutilized by comparison
//treat the same as "indirection ..." using squad2
invitations.createIndirectInvite(tplayer, invitedPlayer, invitedFeatures)
case (Some(invitersFeatures), Some(invitedFeatures))
if invitersFeatures.Squad.Size == 1 =>
//both players belong to squads, but the invitingPlayer's squad is underutilized by comparison
//treat the same as "indirection ..." using squad2
invitations.createIndirectInvite(tplayer, invitedPlayer, invitedFeatures)
case (Some(features), None) =>
//the classic situation
invitations.createVacancyInvite(tplayer, invitedPlayer, features)
case (Some(features), None) =>
//the classic situation
invitations.createInvitationToJoinSquad(tplayer, invitedPlayer, features)
case (None, Some(features)) =>
//indirection; we're trying to invite ourselves to someone else's squad
invitations.createIndirectInvite(tplayer, invitedPlayer, features)
case (None, Some(features)) =>
//indirection; we're trying to invite ourselves to someone else's squad
invitations.createIndirectInvite(tplayer, invitedPlayer, features)
case (None, None) =>
//neither the invited player nor the inviting player belong to any squad
invitations.createSpontaneousInvite(tplayer, invitedPlayer)
case (None, None) =>
//neither the invited player nor the inviting player belong to any squad
invitations.createInvitationToCreateASquad(tplayer, invitedPlayer)
case _ => ()
}
case _ => ()
}
case _ => ()
}
}
}
def SquadActionMembershipProximityInvite(zone: Zone, invitingPlayer: Long): Unit = {
GetLeadingSquad(invitingPlayer, None) match {
case Some(features) =>
invitations.handleProximityInvite(zone, invitingPlayer, features)
case _ => ()
}
GetLeadingSquad(invitingPlayer, None)
.foreach(features => invitations.createProximityInvite(zone, invitingPlayer, features))
}
def SquadActionMembershipAccept(tplayer: Player, invitedPlayer: Long): Unit = {
@ -505,8 +567,8 @@ class SquadService extends Actor {
}
def SquadActionMembershipLeave(tplayer: Player, actingPlayer: Long, _leavingPlayer: Option[Long], name: String): Unit = {
GetParticipatingSquad(actingPlayer) match {
case Some(features) =>
GetParticipatingSquad(actingPlayer)
.foreach { features =>
val squad = features.Squad
val leader = squad.Leader.CharId
(if (name.nonEmpty) {
@ -523,58 +585,37 @@ class SquadService extends Actor {
case Some(id) => subs.UserEvents.keys.find(_ == id)
case None => None
}
}) match {
case _ @ Some(leavingPlayer)
if GetParticipatingSquad(leavingPlayer).contains(features) => //kicked player must be in the same squad
if (actingPlayer == leader) {
if (leavingPlayer == leader || squad.Size == 2) {
//squad leader is leaving his own squad, so it will be disbanded
//OR squad is only composed of two people, so it will be closed-out when one of them leaves
DisbandSquad(features)
} else {
//kicked by the squad leader
subs.Publish(
leavingPlayer,
SquadResponse.Membership(
SquadResponseType.Leave,
0,
0,
leavingPlayer,
Some(leader),
tplayer.Name,
unk5=false,
Some(None)
)
)
subs.Publish(
leader,
SquadResponse.Membership(
SquadResponseType.Leave,
0,
0,
leader,
Some(leavingPlayer),
"",
unk5=true,
Some(None)
)
)
LeaveSquad(leavingPlayer, features)
}
} else if (leavingPlayer == actingPlayer) {
if (squad.Size == 2) {
//squad is only composed of two people, so it will be closed-out when one of them leaves
DisbandSquad(features)
} else {
//leaving the squad of own accord
LeaveSquad(actingPlayer, features)
}
})
.collect { case leavingPlayer
if GetParticipatingSquad(leavingPlayer).contains(features) => //kicked player must be in the same squad
if (actingPlayer == leader) {
if (leavingPlayer == leader || squad.Size == 2) {
//squad leader is leaving his own squad, so it will be disbanded
//OR squad is only composed of two people, so it will be closed-out when one of them leaves
DisbandSquad(features)
} else {
//kicked by the squad leader
subs.Publish(
leavingPlayer,
SquadResponse.Membership(SquadResponseType.Leave, leavingPlayer, Some(leader), tplayer.Name, unk5 = false)
)
subs.Publish(
leader,
SquadResponse.Membership( SquadResponseType.Leave, leader, Some(leavingPlayer), "", unk5 = true)
)
LeaveSquad(leavingPlayer, features)
}
case _ => ()
} else if (leavingPlayer == actingPlayer) {
if (squad.Size == 2) {
//squad is only composed of two people, so it will be closed-out when one of them leaves
DisbandSquad(features)
} else {
//leaving the squad of own accord
LeaveSquad(actingPlayer, features)
}
}
}
case _ => ()
}
}
}
def SquadActionMembershipReject(tplayer: Player, rejectingPlayer: Long): Unit = {
@ -586,11 +627,8 @@ class SquadService extends Actor {
}
def SquadActionMembershipDisband(charId: Long): Unit = {
GetLeadingSquad(charId, None) match {
case Some(features) =>
DisbandSquad(features)
case None => ()
}
GetLeadingSquad(charId, None)
.foreach(features => DisbandSquad(features))
}
def SquadActionMembershipCancel(cancellingPlayer: Long): Unit = {
@ -605,17 +643,17 @@ class SquadService extends Actor {
msg: SquadServiceMessage,
ref: ActorRef
): Unit = {
val promotedPlayer: Long = subs.UserEvents.keys.find(_ == promotionCandidatePlayer).orElse({
LivePlayerList
.WorldPopulation({ case (_, a: Avatar) => a.name.equalsIgnoreCase(promotionCandidateName) })
.headOption match {
case Some(a) => Some(a.id)
case None => None
val promotedPlayer: Long = subs
.UserEvents
.keys
.find(_ == promotionCandidatePlayer)
.orElse {
LivePlayerList
.WorldPopulation({ case (_, a: Avatar) => a.name.equalsIgnoreCase(promotionCandidateName) })
.headOption
.map(_.id.toLong)
}
}) match {
case Some(player: Long) => player
case _ => -1L
}
.getOrElse(-1L)
//sponsorPlayer should be squad leader
(GetLeadingSquad(sponsoringPlayer, None), GetParticipatingSquad(promotedPlayer)) match {
case (Some(features), Some(features2)) if features.Squad.GUID == features2.Squad.GUID =>
@ -639,113 +677,15 @@ class SquadService extends Actor {
message: SquadServiceMessage,
tplayer: Player
): Unit = {
GetParticipatingSquad(tplayer) match {
case Some(features) =>
GetParticipatingSquad(tplayer)
.collect { features =>
features.Switchboard.tell(message, sender())
case None =>
features
}
.orElse {
log.warn(s"Unsupported squad waypoint behavior: $message")
}
}
def SquadActionDefinition(
message: SquadServiceMessage,
action: SquadRequestAction,
guid: PlanetSideGUID
): Unit = {
val tplayer = message.tplayer
(action match {
//the following actions only perform an action upon the squad
case _: ChangeSquadPurpose => GetOrCreateSquadOnlyIfLeader(tplayer)
case _: ChangeSquadZone => GetOrCreateSquadOnlyIfLeader(tplayer)
case _: AddSquadMemberPosition => GetOrCreateSquadOnlyIfLeader(tplayer)
case _: ChangeSquadMemberRequirementsRole => GetOrCreateSquadOnlyIfLeader(tplayer)
case _: ChangeSquadMemberRequirementsDetailedOrders => GetOrCreateSquadOnlyIfLeader(tplayer)
case _: ChangeSquadMemberRequirementsCertifications => GetOrCreateSquadOnlyIfLeader(tplayer)
case _: LocationFollowsSquadLead => GetOrCreateSquadOnlyIfLeader(tplayer)
case _: RequestListSquad => GetOrCreateSquadOnlyIfLeader(tplayer)
case _: StopListSquad => GetLeadingSquad(tplayer, None)
//the following actions cause changes with the squad composition or with invitations
case AutoApproveInvitationRequests(_) =>
GetOrCreateSquadOnlyIfLeader(tplayer) match {
case out @ Some(features) =>
invitations.handleDefinitionAction(tplayer, action, features)
out
case None =>
None
}
case CloseSquadMemberPosition(position) =>
GetOrCreateSquadOnlyIfLeader(tplayer) match {
case out @ Some(features)
if features.Squad.Membership(position).CharId > 0 =>
val squad = features.Squad
LeaveSquad(squad.Membership(position).CharId, features)
out
case _ =>
None
}
case FindLfsSoldiersForRole(_) =>
GetLeadingSquad(tplayer, None) match {
case Some(features) =>
invitations.handleDefinitionAction(tplayer, action, features)
case _ => ()
}
None
case CancelFind() =>
GetLeadingSquad(tplayer, None) match {
case Some(features) =>
invitations.handleDefinitionAction(tplayer, action, features)
case _ => ()
}
None
case SelectRoleForYourself(_) =>
GetParticipatingSquad(tplayer) match {
case out @ Some(features) =>
if (features.Squad.GUID == guid) {
out
} else {
//this isn't the squad we're looking for by GUID; as a precaution, reload all of the published squad list
val faction = tplayer.Faction
subs.Publish(faction, SquadResponse.InitList(PublishedLists(tplayer.Faction)))
None
}
case _ =>
GetSquad(guid) match {
case Some(features) =>
invitations.handleDefinitionAction(tplayer, action, features)
case _ => ()
}
None
}
case _: CancelSelectRoleForYourself =>
GetSquad(guid) match {
case Some(features) =>
invitations.handleDefinitionAction(tplayer, action, features)
case _ => ()
}
None
case _/*search*/: SearchForSquadsWithParticularRole =>
// SquadActionDefinitionSearchForSquadsWithParticularRole(tplayer, search)
None
case _: CancelSquadSearch =>
// SquadActionDefinitionCancelSquadSearch(tplayer.CharId)
None
case _: DisplaySquad =>
GetSquad(guid) match {
case out @ Some(_) =>
SquadActionDefinitionDisplaySquad(tplayer, guid)
out
case None =>
None
}
case _: SquadInitializationIssue =>
SquadActionDefinitionSquadInitializationIssue(tplayer, guid)
None
case _ =>
GetSquad(guid)
}) match {
case Some(features) => features.Switchboard.tell(message, sender())
case None => ()
}
}
}
def SquadActionUpdate(
@ -753,10 +693,8 @@ class SquadService extends Actor {
char_id: Long,
replyTo: ActorRef,
): Unit = {
GetParticipatingSquad(char_id) match {
case Some(features) => features.Switchboard.tell(message, replyTo)
case None => ()
}
GetParticipatingSquad(char_id)
.foreach(features => features.Switchboard.tell(message, replyTo))
}
def GetOrCreateSquadOnlyIfLeader(player: Player): Option[SquadFeatures] = {
@ -776,14 +714,15 @@ class SquadService extends Actor {
criteria: SearchForSquadsWithParticularRole
): Unit = {
val charId = tplayer.CharId
searchData.get(charId) match {
case Some(_) => ()
//already searching, so do nothing(?)
case None =>
searchData
.get(charId)
.orElse {
val data = SquadService.SearchCriteria(tplayer.Faction, criteria)
searchData.put(charId, data)
SquadActionDefinitionSearchForSquadsUsingCriteria(charId, data)
}
None
}
//if already searching, do nothing
}
private def SquadActionDefinitionSearchForSquadsUsingCriteria(
@ -797,22 +736,23 @@ class SquadService extends Actor {
}
private def SearchForSquadsResults(criteria: SquadService.SearchCriteria): List[PlanetSideGUID] = {
publishedLists.get(criteria.faction) match {
case Some(squads) if squads.nonEmpty =>
squads.flatMap { guid => SearchForSquadsResults(criteria, guid) }.toList
case _ =>
Nil
}
publishedLists
.get(criteria.faction)
.collect {
case squads if squads.nonEmpty =>
squads.flatMap { guid => SearchForSquadsResults(criteria, guid) }.toList
}
.getOrElse(Nil)
}
def SquadActionDefinitionCancelSquadSearch(charId: Long): Unit = {
searchData.remove(charId) match {
case None => ()
case Some(data) =>
SearchForSquadsResults(data).foreach { guid =>
subs.Publish(charId, SquadResponse.SquadDecoration(guid, squadFeatures(guid).Squad))
}
}
searchData
.remove(charId)
.map(SearchForSquadsResults)
.getOrElse(Nil)
.foreach { guid =>
subs.Publish(charId, SquadResponse.SquadDecoration(guid, squadFeatures(guid).Squad))
}
}
private def SearchForSquadsResults(
@ -872,14 +812,14 @@ class SquadService extends Actor {
}
def CleanUpSquadFeatures(removed: List[Long], guid: PlanetSideGUID, @unused position: Int): Unit = {
GetSquad(guid) match {
case Some(features) =>
features.ProxyInvites = features.ProxyInvites.filterNot(removed.contains)
if (features.ProxyInvites.isEmpty) {
GetSquad(guid)
.collect {
case features if features.ProxyInvites.isEmpty =>
features.ProxyInvites = features.ProxyInvites.filterNot(removed.contains)
features.SearchForRole = None
}
case None => ()
}
case features =>
features.ProxyInvites = features.ProxyInvites.filterNot(removed.contains)
}
}
/**
@ -945,18 +885,19 @@ class SquadService extends Actor {
def JoinSquad(player: Player, features: SquadFeatures, position: Int): Boolean = {
val charId = player.CharId
val squad = features.Squad
subs.UserEvents.get(charId) match {
case Some(events)
if squad.isAvailable(position, player.avatar.certifications) &&
EnsureEmptySquad(charId) =>
memberToSquad(charId) = squad.GUID
subs.MonitorSquadDetails.subtractOne(charId)
invitations.handleCleanup(charId)
features.Switchboard ! SquadSwitchboard.Join(player, position, events)
true
case _ =>
false
}
subs
.UserEvents
.get(charId)
.collect {
case events
if squad.isAvailable(position, player.avatar.certifications) && EnsureEmptySquad(charId) =>
memberToSquad(charId) = squad.GUID
subs.MonitorSquadDetails.subtractOne(charId)
invitations.handleCleanup(charId)
features.Switchboard ! SquadSwitchboard.Join(player, position, events)
true
}
.getOrElse(false)
}
/**
@ -968,16 +909,16 @@ class SquadService extends Actor {
* `false`, otherwise
*/
def EnsureEmptySquad(charId: Long): Boolean = {
GetParticipatingSquad(charId) match {
case None =>
true
case Some(features) if features.Squad.Size == 1 =>
CloseSquad(features.Squad)
true
case _ =>
log.warn("EnsureEmptySquad: the invited player is already a member of a squad and can not join a second one")
false
}
GetParticipatingSquad(charId)
.collect {
case features if features.Squad.Size == 1 =>
CloseSquad(features.Squad)
true
case _ =>
log.warn("EnsureEmptySquad: the invited player is already a member of a squad and can not join a second one")
false
}
.getOrElse(true)
}
/**
@ -992,15 +933,15 @@ class SquadService extends Actor {
def LeaveSquad(charId: Long, features: SquadFeatures): Boolean = {
val squad = features.Squad
val membership = squad.Membership.zipWithIndex
membership.find { case (_member, _) => _member.CharId == charId } match {
case Some(_) if squad.Leader.CharId != charId =>
membership
.find { case (_member, _) => _member.CharId == charId }
.collect { case _ if squad.Leader.CharId != charId =>
memberToSquad.remove(charId)
subs.MonitorSquadDetails.subtractOne(charId)
features.Switchboard ! SquadSwitchboard.Leave(charId)
true
case _ =>
false
}
}
.getOrElse(false)
}
/**
@ -1079,7 +1020,7 @@ class SquadService extends Actor {
)
//the squad is being disbanded, the squad events channel is also going away; use cached character ids
info(s"Squad #${squad.GUID.guid} has been disbanded.")
subs.Publish(leader, SquadResponse.Membership(SquadResponseType.Disband, 0, 0, leader, None, "", unk5=true, Some(None)))
subs.Publish(leader, SquadResponse.Membership(SquadResponseType.Disband, leader, None, "", unk5=true))
}
/**
@ -1107,7 +1048,7 @@ class SquadService extends Actor {
(membership.filterNot(_ == leader) ++ subs.PublishToMonitorTargets(squad.GUID, Nil))
.toSet
.foreach { charId : Long =>
subs.Publish(charId, SquadResponse.Membership(SquadResponseType.Disband, 0, 0, charId, None, "", unk5=false, Some(None)))
subs.Publish(charId, SquadResponse.Membership(SquadResponseType.Disband, charId, None, "", unk5=false))
}
}
@ -1355,24 +1296,25 @@ class SquadService extends Actor {
if (listOfCharIds.nonEmpty) {
invitations.tryChainAcceptance(player, charId, listOfCharIds, features)
} else {
invitations.SquadActionDefinitionAutoApproveInvitationRequests(charId, features)
invitations.autoApproveInvitationRequests(charId, features)
}
}
}
def ChainRejectionFromSquad(player: Player, charId: Long, listOfCharIds: List[Long]): Unit = {
GetLeadingSquad(charId, None)
.foreach { features =>
if (listOfCharIds.nonEmpty) {
.collect {
case features if listOfCharIds.nonEmpty =>
invitations.tryChainRejection(player, charId, listOfCharIds, features)
} else {
case features =>
invitations.tryChainRejectionAll(charId, features)
}
}
}
}
object SquadService {
final private val FactionWordSalad: String = "TRNCVS"
final case class PerformStartSquad(player: Player)
final case class PerformJoinSquad(player: Player, features: SquadFeatures, position: Int)

View file

@ -41,6 +41,16 @@ object SquadResponse {
unk5: Boolean,
unk6: Option[Option[String]]
) extends Response //see SquadMembershipResponse
object Membership {
def apply(
requestType: SquadResponseType.Value,
unk3: Long,
unk4: Option[Long],
playerName: String,
unk5: Boolean
): Membership = new Membership(requestType, unk1 = 0, unk2 = 0, unk3, unk4, playerName, unk5, Some(None))
}
final case class WantsSquadPosition(leader_char_id: Long, bid_name: String) extends Response
final case class Join(squad: Squad, positionsToUpdate: List[Int], channel: String, ref: ActorRef) extends Response
final case class Leave(squad: Squad, positionsToUpdate: List[(Long, Int)]) extends Response

View file

@ -0,0 +1,80 @@
// Copyright (c) 2024 PSForever
package net.psforever.services.teamwork.invitations
import net.psforever.objects.Player
import net.psforever.objects.teamwork.SquadFeatures
import net.psforever.services.teamwork.SquadInvitationManager
import net.psforever.types.PlanetSideGUID
import scala.annotation.unused
/**
* Utilized to redirect an (accepted) invitation request to the proper squad leader.
* No direct action causes this message.
* Depending on the situation, either the squad leader or the player who would join the squad handle this invitation.
*
* @param recruitOrOwner player who would be joining the squad;
* may or may not have actually requested it in the first place
* @param features squad
*/
final case class IndirectInvite(recruitOrOwner: Player, features: SquadFeatures)
extends Invitation(recruitOrOwner.CharId, recruitOrOwner.Name) {
def handleInvitation(indirectInviteFunc: (IndirectInvite, Player, Long, Long, String) => Boolean)(
manager: SquadInvitationManager,
invitedPlayer: Long,
invitingPlayer: Long,
otherName: String
): Unit = {
indirectInviteFunc(this, recruitOrOwner, invitedPlayer, invitingPlayer, otherName)
}
def handleAcceptance(
manager: SquadInvitationManager,
@unused player: Player,
invitedPlayer: Long,
@unused invitedPlayerSquadOpt: Option[SquadFeatures]
): Unit = {
//tplayer / invitedPlayer is actually the squad leader
if (SquadInvitationManager.canEnrollInSquad(features, recruitOrOwner.CharId)) {
val recruitCharId = recruitOrOwner.CharId
manager.handleVacancyInvite(features, recruitCharId, invitedPlayer, recruitOrOwner) match {
case Some((_, line)) =>
manager.acceptanceMessages(invitedPlayer, recruitCharId, recruitOrOwner.Name)
manager.joinSquad(recruitOrOwner, features, line)
manager.cleanUpAllInvitesWithPlayer(recruitCharId)
manager.cleanUpInvitesForSquadAndPosition(features, line)
//TODO since we are the squad leader, we do not want to brush off our queued squad invite tasks
case _ => ()
}
}
}
def handleRejection(
manager: SquadInvitationManager,
player: Player,
rejectingPlayer: Long,
@unused squadsToLeaders: List[(PlanetSideGUID, SquadFeatures)]
): Unit = {
//todo how to do this?
}
def doRejection(
manager: SquadInvitationManager,
player: Player,
rejectingPlayer: Long
): Unit = {
//todo how to do this?
}
def canBeAutoApproved: Boolean = true
def getOptionalSquad: Option[SquadFeatures] = Some(features)
def getPlayer: Player = recruitOrOwner
def appliesToPlayer(playerCharId: Long): Boolean = playerCharId == recruitOrOwner.CharId
def appliesToSquad(guid: PlanetSideGUID): Boolean = features.Squad.GUID == guid
def appliesToSquadAndPosition(guid: PlanetSideGUID, squadPosition: Int): Boolean = false
}

View file

@ -0,0 +1,75 @@
// Copyright (c) 2024 PSForever
package net.psforever.services.teamwork.invitations
import net.psforever.objects.Player
import net.psforever.objects.teamwork.SquadFeatures
import net.psforever.services.teamwork.SquadInvitationManager
import net.psforever.types.PlanetSideGUID
/**
* The base of all objects that exist for the purpose of communicating invitation from one player to the next.
* @param charId inviting player's unique identifier number
* @param name inviting player's name
*/
abstract class Invitation(charId: Long, name: String) {
def inviterCharId: Long = charId
def inviterName: String = name
/**
* A branched response for processing (new) invitation objects that have been submitted to the system.<br>
* <br>
* A comparison is performed between the original invitation object and an invitation object
* that represents the potential modification or redirection of the current active invitation obect.
* Any further action is only performed when an "is equal" comparison is `true`.
* When passing, the system publishes up to two messages
* to users that would anticipate being informed of squad join activity.
* @param indirectInviteFunc the method that cans the responding behavior should an `IndirectInvite` object being consumed
* @param invitedPlayer the unique character identifier for the player being invited;
* in actuality, represents the player who will address the invitation object
* @param invitingPlayer the unique character identifier for the player who invited the former
* @param otherName a name to be used in message composition
*/
def handleInvitation(indirectInviteFunc: (IndirectInvite, Player, Long, Long, String) => Boolean)(
manager: SquadInvitationManager,
invitedPlayer: Long,
invitingPlayer: Long,
otherName: String
): Unit
def handleAcceptance(
manager: SquadInvitationManager,
player: Player,
invitedPlayer: Long,
invitedPlayerSquadOpt: Option[SquadFeatures]
): Unit
def handleRejection(
manager: SquadInvitationManager,
player: Player,
rejectingPlayer: Long,
squadsToLeaders: List[(PlanetSideGUID, SquadFeatures)]
): Unit
def doRejection(
manager: SquadInvitationManager,
player: Player,
rejectingPlayer: Long
): Unit
def canBeAutoApproved: Boolean
def getOptionalSquad: Option[SquadFeatures]
/**
* na
* @return active player entity associated with this invite;
* can be `null` as some invitations do not retain such character data
*/
def getPlayer: Player
def appliesToPlayer(playerCharId: Long): Boolean
def appliesToSquad(guid: PlanetSideGUID): Boolean
def appliesToSquadAndPosition(guid: PlanetSideGUID, squadPosition: Int): Boolean
}

View file

@ -0,0 +1,112 @@
// Copyright (c) 2024 PSForever
package net.psforever.services.teamwork.invitations
import net.psforever.objects.Player
import net.psforever.objects.teamwork.SquadFeatures
import net.psforever.services.teamwork.SquadInvitationManager.FinishStartSquad
import net.psforever.services.teamwork.{SquadInvitationManager, SquadResponse}
import net.psforever.types.{PlanetSideGUID, SquadResponseType}
import scala.annotation.unused
import scala.util.Success
/**
* Utilized when one player issues an invite for some other player for a squad that does not yet exist.
* This invitation is handled by the player who would be joining the squad.
*
* @param futureSquadLeader player who wishes to become the leader of a squad
*/
final case class InvitationToCreateASquad(futureSquadLeader: Player)
extends Invitation(futureSquadLeader.CharId, futureSquadLeader.Name) {
def handleInvitation(indirectInviteFunc: (IndirectInvite, Player, Long, Long, String) => Boolean)(
manager: SquadInvitationManager,
invitedPlayer: Long,
invitingPlayer: Long,
otherName: String
): Unit = {
manager.publish(
invitedPlayer,
SquadResponse.Membership(SquadResponseType.Invite, inviterCharId, Some(invitedPlayer), futureSquadLeader.Name, unk5 = false)
)
manager.publish(
inviterCharId,
SquadResponse.Membership(SquadResponseType.Invite, invitedPlayer, Some(inviterCharId), futureSquadLeader.Name, unk5 = true)
)
}
def handleAcceptance(
manager: SquadInvitationManager,
player: Player,
invitedPlayer: Long,
invitedPlayerSquadOpt: Option[SquadFeatures]
): Unit = {
if (manager.notLimitedByEnrollmentInSquad(invitedPlayerSquadOpt, invitedPlayer)) {
//accepted an invitation to join an existing squad
import scala.concurrent.ExecutionContext.Implicits.global
val leaderCharId = futureSquadLeader.CharId
manager
.askToCreateANewSquad(futureSquadLeader)
.onComplete {
case Success(FinishStartSquad(features)) =>
manager.handleVacancyInvite(features, invitedPlayer, leaderCharId, player) match {
case Some((_, line)) =>
manager.publish(
invitedPlayer,
SquadResponse.Membership(SquadResponseType.Accept, invitedPlayer, Some(leaderCharId), "", unk5 = true)
)
manager.publish(
leaderCharId,
SquadResponse.Membership(SquadResponseType.Accept, leaderCharId, Some(invitedPlayer), player.Name, unk5 = false)
)
manager.joinSquad(player, features, line)
manager.cleanUpQueuedInvites(invitedPlayer)
case _ => ()
}
case _ => ()
}
}
}
def handleRejection(
manager: SquadInvitationManager,
player: Player,
rejectingPlayer: Long,
@unused squadsToLeaders: List[(PlanetSideGUID, SquadFeatures)]
): Unit = {
//rejectingPlayer is the would-be squad member; the would-be squad leader sent the request and was rejected
val invitingPlayerCharId = futureSquadLeader.CharId
doRejection(manager, player, rejectingPlayer)
manager.publish(
rejectingPlayer,
SquadResponse.Membership(SquadResponseType.Reject, rejectingPlayer, Some(invitingPlayerCharId), "", unk5 = true)
)
manager.publish(
invitingPlayerCharId,
SquadResponse.Membership(SquadResponseType.Reject, invitingPlayerCharId, Some(rejectingPlayer), player.Name, unk5 = false)
)
manager.publish(
rejectingPlayer,
SquadResponse.SquadRelatedComment(s"Your request to form a squad has been refused.")
)
}
def doRejection(
manager: SquadInvitationManager,
player: Player,
rejectingPlayer: Long
): Unit = {
manager.refused(rejectingPlayer, futureSquadLeader.CharId)
}
def canBeAutoApproved: Boolean = false
def getOptionalSquad: Option[SquadFeatures] = None
def getPlayer: Player = futureSquadLeader
def appliesToPlayer(playerCharId: Long): Boolean = playerCharId == futureSquadLeader.CharId
def appliesToSquad(guid: PlanetSideGUID): Boolean = false
def appliesToSquadAndPosition(guid: PlanetSideGUID, squadPosition: Int): Boolean = false
}

View file

@ -0,0 +1,95 @@
// Copyright (c) 2024 PSForever
package net.psforever.services.teamwork.invitations
import net.psforever.objects.Player
import net.psforever.objects.teamwork.SquadFeatures
import net.psforever.services.teamwork.{SquadInvitationManager, SquadResponse}
import net.psforever.types.{PlanetSideGUID, SquadResponseType}
import scala.annotation.unused
/**
* Utilized when one squad member issues an invite for some other player.
* Accessed by an existing squad member using the "Invite" menu option on another player.
* This invitation is handled by the player who would join the squad.
*
* @param charId unique character identifier of the player who sent the invite
* @param name name the player who sent the invite
* @param features the squad
*/
final case class InvitationToJoinSquad(charId: Long, name: String, features: SquadFeatures)
extends Invitation(charId, name) {
def handleInvitation(indirectInviteFunc: (IndirectInvite, Player, Long, Long, String) => Boolean)(
manager: SquadInvitationManager,
invitedPlayer: Long,
invitingPlayer: Long,
otherName: String
): Unit = {
manager.publish(
invitedPlayer,
SquadResponse.Membership(SquadResponseType.Invite, charId, Some(invitedPlayer), name, unk5 = false)
)
manager.publish(
charId,
SquadResponse.Membership(SquadResponseType.Invite, invitedPlayer, Some(charId), name, unk5 = true)
)
}
def handleAcceptance(
manager: SquadInvitationManager,
player: Player,
invitedPlayer: Long,
@unused invitedPlayerSquadOpt: Option[SquadFeatures]
): Unit = {
if (
manager.notLimitedByEnrollmentInSquad(invitedPlayerSquadOpt, invitedPlayer) &&
SquadInvitationManager.canEnrollInSquad(features, invitedPlayer)
) {
//accepted an invitation to join an existing squad
manager.handleVacancyInvite(features, invitedPlayer, charId, player) match {
case Some((_, line)) =>
manager.acceptanceMessages(charId, invitedPlayer, player.Name)
manager.joinSquad(player, features, line)
manager.cleanUpQueuedInvites(invitedPlayer)
manager.cleanUpInvitesForSquadAndPosition(features, line)
case _ => ()
}
}
}
def handleRejection(
manager: SquadInvitationManager,
player: Player,
rejectingPlayer: Long,
@unused squadsToLeaders: List[(PlanetSideGUID, SquadFeatures)]
): Unit = {
/*if SquadInvitationManager.notLeaderOfThisSquad(squadsToLeaders, features.Squad.GUID, rejectingPlayer)*/
//rejectingPlayer is the would-be squad member; the squad leader sent the request and was rejected
doRejection(manager, player, rejectingPlayer)
manager.rejectionMessages(rejectingPlayer, charId, player.Name)
manager.publish(
rejectingPlayer,
SquadResponse.SquadRelatedComment(s"Your request to join squad '${features.Squad.Task}' has been refused.")
)
}
def doRejection(
manager: SquadInvitationManager,
player: Player,
rejectingPlayer: Long
): Unit = {
manager.refused(rejectingPlayer, charId)
}
def canBeAutoApproved: Boolean = false
def getOptionalSquad: Option[SquadFeatures] = Some(features)
def getPlayer: Player = null
def appliesToPlayer(playerCharId: Long): Boolean = playerCharId == charId
def appliesToSquad(guid: PlanetSideGUID): Boolean = features.Squad.GUID == guid
def appliesToSquadAndPosition(guid: PlanetSideGUID, squadPosition: Int): Boolean = false
}

View file

@ -0,0 +1,96 @@
// Copyright (c) 2024 PSForever
package net.psforever.services.teamwork.invitations
import net.psforever.objects.teamwork.{Member, SquadFeatures}
import net.psforever.objects.{LivePlayerList, Player}
import net.psforever.services.teamwork.{SquadInvitationManager, SquadResponse}
import net.psforever.types.{PlanetSideGUID, SquadResponseType}
import scala.annotation.unused
/**
* Utilized in conjunction with an external queuing data structure
* to search for and submit requests to other players
* for the purposes of fill out an unoccupied squad role.
* This invitation is handled by the player who would be joining the squad.
*
* @param squadLeader squad leader
* @param features squad with the role
* @param position index of the role
*/
final case class LookingForSquadRoleInvite(squadLeader: Member, features: SquadFeatures, position: Int)
extends Invitation(squadLeader.CharId, squadLeader.Name) {
def handleInvitation(indirectInviteFunc: (IndirectInvite, Player, Long, Long, String) => Boolean)(
manager: SquadInvitationManager,
invitedPlayer: Long,
invitingPlayer: Long,
otherName: String
): Unit = {
manager.publish(
invitedPlayer,
SquadResponse.Membership(SquadResponseType.Invite, invitedPlayer, Some(squadLeader.CharId), squadLeader.Name, unk5 = false)
)
}
def handleAcceptance(
manager: SquadInvitationManager,
player: Player,
invitedPlayer: Long,
@unused invitedPlayerSquadOpt: Option[SquadFeatures]
): Unit = {
if (
manager.notLimitedByEnrollmentInSquad(invitedPlayerSquadOpt, invitedPlayer) &&
SquadInvitationManager.canEnrollInSquad(features, invitedPlayer)
) {
val invitingPlayer = squadLeader.CharId
features.ProxyInvites = features.ProxyInvites.filterNot { _ == invitedPlayer }
if (manager.joinSquad(player, features, position)) {
//join this squad
manager.acceptanceMessages(invitingPlayer, invitedPlayer, player.Name)
manager.cleanUpQueuedInvites(player.CharId)
manager.cleanUpInvitesForSquadAndPosition(features, position)
}
}
}
def handleRejection(
manager: SquadInvitationManager,
player: Player,
rejectingPlayer: Long,
@unused squadsToLeaders: List[(PlanetSideGUID, SquadFeatures)]
): Unit = {
val leaderCharId = squadLeader.CharId
//rejectingPlayer is the would-be squad member; the squad leader sent the request and was rejected
doRejection(manager, player, rejectingPlayer)
manager.rejectionMessages(rejectingPlayer, leaderCharId, player.Name)
manager.publish(
rejectingPlayer,
SquadResponse.SquadRelatedComment(s"Your request to join squad '${features.Squad.Task}' has been refused.")
)
}
def doRejection(
manager: SquadInvitationManager,
player: Player,
rejectingPlayer: Long
): Unit = {
manager.reloadSearchForRoleInvite(
LivePlayerList.WorldPopulation { _ => true },
rejectingPlayer,
features,
position
)
}
def canBeAutoApproved: Boolean = false
def getOptionalSquad: Option[SquadFeatures] = Some(features)
def getPlayer: Player = null
def appliesToPlayer(playerCharId: Long): Boolean = playerCharId == squadLeader.CharId
def appliesToSquad(guid: PlanetSideGUID): Boolean = features.Squad.GUID == guid
def appliesToSquadAndPosition(guid: PlanetSideGUID, squadPosition: Int): Boolean = appliesToSquad(guid) && position == squadPosition
}

View file

@ -0,0 +1,104 @@
// Copyright (c) 2024 PSForever
package net.psforever.services.teamwork.invitations
import net.psforever.objects.Player
import net.psforever.objects.teamwork.{Member, SquadFeatures}
import net.psforever.services.teamwork.{SquadInvitationManager, SquadResponse}
import net.psforever.types.{PlanetSideGUID, SquadResponseType}
import scala.annotation.unused
/**
* Utilized in conjunction with an external queuing data structure
* to search for and submit requests to other players
* for the purposes of fill out unoccupied squad roles.
* This invitation is handled by the player who would be joining the squad.
*
* @param squadLeader squad leader
* @param features squad
* @param position index of a role
*/
final case class ProximityInvite(squadLeader: Member, features: SquadFeatures, position: Int)
extends Invitation(squadLeader.CharId, squadLeader.Name) {
def handleInvitation(indirectInviteFunc: (IndirectInvite, Player, Long, Long, String) => Boolean)(
manager: SquadInvitationManager,
invitedPlayer: Long,
invitingPlayer: Long,
otherName: String
): Unit = {
manager.publish(
invitedPlayer,
SquadResponse.Membership(
SquadResponseType.Invite,
invitedPlayer,
Some(squadLeader.CharId),
squadLeader.Name,
unk5 = false
)
)
}
def handleAcceptance(
manager: SquadInvitationManager,
player: Player,
invitedPlayer: Long,
invitedPlayerSquadOpt: Option[SquadFeatures]
): Unit = {
if (
manager.notLimitedByEnrollmentInSquad(invitedPlayerSquadOpt, invitedPlayer) &&
SquadInvitationManager.canEnrollInSquad(features, invitedPlayer)
) {
val invitingPlayer = squadLeader.CharId
features.ProxyInvites = features.ProxyInvites.filterNot { _ == invitedPlayer }
if (manager.joinSquad(player, features, position)) {
//join this squad
manager.acceptanceMessages(invitingPlayer, invitedPlayer, player.Name)
manager.cleanUpAllInvitesWithPlayer(invitedPlayer)
val squad = features.Squad
if (squad.Size == squad.Capacity) {
//all available squad positions filled; terminate all remaining invitations
manager.cleanUpAllInvitesToSquad(features)
}
} else {
manager.reloadProximityInvite(player.Zone.Players, invitedPlayer, features, position) //TODO ?
}
}
}
def handleRejection(
manager: SquadInvitationManager,
player: Player,
rejectingPlayer: Long,
@unused squadsToLeaders: List[(PlanetSideGUID, SquadFeatures)]
): Unit = {
/*if SquadInvitationManager.notLeaderOfThisSquad(squadsToLeaders, features.Squad.GUID, rejectingPlayer)*/
//rejectingPlayer is the would-be squad member; the squad leader sent the request and was rejected
doRejection(manager, player, rejectingPlayer)
manager.rejectionMessage(rejectingPlayer)
manager.publish(
rejectingPlayer,
SquadResponse.SquadRelatedComment(s"Your request to join squad '${features.Squad.Task}' has been refused.")
)
}
def doRejection(
manager: SquadInvitationManager,
player: Player,
rejectingPlayer: Long
): Unit = {
manager.reloadProximityInvite(player.Zone.Players, rejectingPlayer, features, position)
}
def canBeAutoApproved: Boolean = false
def getOptionalSquad: Option[SquadFeatures] = Some(features)
def getPlayer: Player = null
def appliesToPlayer(playerCharId: Long): Boolean = playerCharId == squadLeader.CharId
def appliesToSquad(guid: PlanetSideGUID): Boolean = features.Squad.GUID == guid
def appliesToSquadAndPosition(guid: PlanetSideGUID, squadPosition: Int): Boolean = appliesToSquad(guid) && position == squadPosition
}

View file

@ -0,0 +1,84 @@
// Copyright (c) 2024 PSForever
package net.psforever.services.teamwork.invitations
import net.psforever.objects.Player
import net.psforever.objects.teamwork.SquadFeatures
import net.psforever.services.teamwork.{SquadInvitationManager, SquadResponse}
import net.psforever.types.PlanetSideGUID
import scala.annotation.unused
/**
* Utilized when one player attempts to join an existing squad in a specific role.
* Accessed by the joining player from the squad detail window.
* This invitation is handled by the squad leader.
*
* @param requestee player who requested the role
* @param features squad with the role
* @param position index of the role
*/
final case class RequestToJoinSquadRole(requestee: Player, features: SquadFeatures, position: Int)
extends Invitation(requestee.CharId, requestee.Name) {
def handleInvitation(indirectInviteFunc: (IndirectInvite, Player, Long, Long, String) => Boolean)(
manager: SquadInvitationManager,
invitedPlayer: Long,
invitingPlayer: Long,
otherName: String
): Unit = {
SquadInvitationManager.handleRequestRole(manager, requestee, bid = this)
}
def handleAcceptance(
manager: SquadInvitationManager,
@unused player: Player,
invitedPlayer: Long,
@unused invitedPlayerSquadOpt: Option[SquadFeatures]
): Unit = {
//player requested to join a squad's specific position
//invitedPlayer is actually the squad leader; petitioner is the actual "invitedPlayer"
if (
SquadInvitationManager.canEnrollInSquad(features, requestee.CharId) &&
manager.joinSquad(requestee, features, position)
) {
manager.acceptanceMessages(invitedPlayer, requestee.CharId, requestee.Name)
manager.cleanUpInvitesForSquadAndPosition(features, position)
}
}
def handleRejection(
manager: SquadInvitationManager,
@unused player: Player,
rejectingPlayer: Long,
@unused squadsToLeaders: List[(PlanetSideGUID, SquadFeatures)]
): Unit = {
if (SquadInvitationManager.notLeaderOfThisSquad(squadsToLeaders, features.Squad.GUID, requestee.CharId)) {
//rejected is the would-be squad member; rejectingPlayer is the squad leader who rejected the request
doRejection(manager, player, rejectingPlayer)
manager.rejectionMessage(rejectingPlayer)
manager.publish(
rejectingPlayer,
SquadResponse.SquadRelatedComment(s"Your request to join squad '${features.Squad.Task}' has been refused.")
)
}
}
def doRejection(
manager: SquadInvitationManager,
player: Player,
rejectingPlayer: Long
): Unit = {
features.DeniedPlayers(requestee.CharId)
}
def canBeAutoApproved: Boolean = true
def getOptionalSquad: Option[SquadFeatures] = Some(features)
def getPlayer: Player = requestee
def appliesToPlayer(playerCharId: Long): Boolean = playerCharId == requestee.CharId
def appliesToSquad(guid: PlanetSideGUID): Boolean = features.Squad.GUID == guid
def appliesToSquadAndPosition(guid: PlanetSideGUID, squadPosition: Int): Boolean = appliesToSquad(guid) && position == squadPosition
}