SquadAction and SquadResponse were worked into SquadServiceMessage and SquadServiceResponse, respectively, so that traits could be sealed; the continuous join request prompt has been exorcised from squad operations; switching to auto-approve for squads will now resolve all pending join requests; enumerated shared cases in SDAM

This commit is contained in:
FateJH 2019-10-21 19:41:29 -04:00
parent 93f2264f61
commit 5916fa68be
9 changed files with 91 additions and 120 deletions

View file

@ -1,8 +1,7 @@
// Copyright (c) 2019 PSForever
package net.psforever.objects.teamwork
import akka.actor.{Actor, ActorContext, ActorRef, Cancellable, Props}
import net.psforever.objects.DefaultCancellable
import akka.actor.{Actor, ActorContext, ActorRef, Props}
import net.psforever.types.SquadWaypoints
import services.teamwork.SquadService.WaypointData
import services.teamwork.SquadSwitchboard
@ -11,8 +10,8 @@ class SquadFeatures(val Squad : Squad) {
/**
* `initialAssociation` per squad is similar to "Does this squad want to recruit members?"
* The squad does not have to be flagged.
* Dispatches an `AssociateWithSquad` `SDAM` to the squad leader and ???
* and then a `SDDUM` that includes at least the squad owner name and char id.
* Dispatches an `AssociateWithSquad` `SquadDefinitionActionMessage` packet to the squad leader and ???
* and then a `SquadDetailDefinitionUpdateMessage` that includes at least the squad owner name and char id.
* 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,
@ -52,8 +51,6 @@ class SquadFeatures(val Squad : Squad) {
* Handle persistent data related to `ProximityInvite` and `LookingForSquadRoleInvite` messages
*/
private var proxyInvites : List[Long] = Nil
private var requestInvitePrompt : Cancellable = DefaultCancellable.obj
/**
* These useres rejected invitation to this squad.
* For the purposes of wide-searches for membership
@ -79,7 +76,6 @@ class SquadFeatures(val Squad : Squad) {
switchboard ! akka.actor.PoisonPill
switchboard = Actor.noSender
waypoints = Array.empty
requestInvitePrompt.cancel
this
}
@ -143,13 +139,4 @@ class SquadFeatures(val Squad : Squad) {
}
def ToChannel : String = channel
def Prompt : Cancellable = requestInvitePrompt
def Prompt_=(callback: Cancellable) : Cancellable = {
if(requestInvitePrompt.isCancelled) {
requestInvitePrompt = callback
}
Prompt
}
}

View file

@ -426,16 +426,23 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
((code : @switch) match {
case 0 => displaySquadCodec
case 1 => squadMemberInitializationIssueCodec
case 2 => unknownCodec(action = 2)
case 3 => saveSquadFavoriteCodec
case 4 => loadSquadFavoriteCodec
case 5 => deleteSquadFavoriteCodec
case 6 => unknownCodec(action = 6)
case 7 => listSquadFavoriteCodec
case 8 => requestListSquadCodec
case 9 => stopListSquadCodec
case 10 => selectRoleForYourselfCodec
case 11 => unknownCodec(action = 11)
case 12 => unknownCodec(action = 12)
case 13 => unknownCodec(action = 13)
case 14 => unknownCodec(action = 14)
case 15 => cancelSelectRoleForYourselfCodec
case 16 => associateWithSquadCodec
case 17 => setListSquadCodec
case 18 => unknownCodec(action = 18)
case 19 => changeSquadPurposeCodec
case 20 => changeSquadZoneCodec
case 21 => closeSquadMemberPositionCodec
@ -444,17 +451,23 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
case 24 => changeSquadMemberRequirementsDetailedOrdersCodec
case 25 => changeSquadMemberRequirementsCertificationsCodec
case 26 => resetAllCodec
//case 27 => ?
case 28 => autoApproveInvitationRequestsCodec
case 29 => unknownCodec(action = 29)
case 30 => unknownCodec(action = 30)
case 31 => locationFollowsSquadLeadCodec
case 32 => unknownCodec(action = 32)
case 33 => unknownCodec(action = 33)
case 34 => searchForSquadsWithParticularRoleCodec
case 35 => cancelSquadSearchCodec
case 36 => unknownCodec(action = 36)
case 37 => unknownCodec(action = 37)
case 38 => assignSquadMemberToRoleCodec
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 42 => unknownCodec(action = 42)
case 43 => unknownCodec(action = 43)
case _ => failureCodec(code)
}).asInstanceOf[Codec[SquadAction]]
}

View file

@ -657,44 +657,6 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
}
)
}
//TODO while this pattern looks elegant, bitsOverByte does not accumulate properly with the either(bool, L, R); why?
// private def membersCodec(bitsOverByte : StreamLengthToken) : Codec[SquadDetail] = {
// import shapeless.::
// either(bool,
// { //false
// bitsOverByte.Add(3)
// uint2 :: FullyPopulatedPositions.codec(bitsOverByte)
// },
// { //true
// bitsOverByte.Add(4)
// uint(3) :: vector(ItemizedPositions.codec(bitsOverByte))
// }
// ).exmap[SquadDetail] (
// {
// case Left(_ :: member_list :: HNil) =>
// Attempt.successful(SquadDetail(None, None, None, None, None, None, None, None, Some(ignoreTerminatingEntry(member_list.toList))))
// case Right(_ :: member_list :: HNil) =>
// Attempt.successful(SquadDetail(None, None, None, None, None, None, None, None, Some(ignoreTerminatingEntry(member_list.toList))))
// },
// {
// case SquadDetail(_, _, _, _, _, _, _, _, Some(member_list)) =>
// if(member_list
// .collect { case position if position.info.nonEmpty =>
// val info = position.info.get
// List(info.is_closed, info.role, info.detailed_orders, info.requirements, info.char_id, info.name)
// }
// .flatten
// .count(_.isEmpty) == 0) {
// Attempt.successful(Left(2 :: ensureTerminatingEntry(member_list).toVector :: HNil))
// }
// else {
// Attempt.successful(Right(4 :: ensureTerminatingEntry(member_list).toVector :: HNil))
// }
// case _ =>
// Attempt.failure(Err("failed to encode squad data for members"))
// }
// )
// }
/**
* A failing pattern for when the coded value is not tied to a known field pattern.
* This pattern does not read or write any bit data.

View file

@ -1,18 +0,0 @@
// Copyright (c) 2019 PSForever
package services.teamwork
import net.psforever.objects.zones.Zone
import net.psforever.packet.game._
import net.psforever.types.{PlanetSideEmpire, SquadRequestType, SquadWaypoints, Vector3}
object SquadAction {
trait Action
final case class InitSquadList() extends Action
final case class InitCharId() extends Action
final case class Definition(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 Waypoint(event_type : WaypointEventAction.Value, waypoint_type : SquadWaypoints.Value, unk : Option[Long], waypoint_info : Option[WaypointInfo]) 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

@ -1,34 +0,0 @@
// Copyright (c) 2019 PSForever
package services.teamwork
import net.psforever.objects.teamwork.Squad
import net.psforever.packet.game._
import net.psforever.types.{SquadResponseType, SquadWaypoints}
object SquadResponse {
trait Response
final case class ListSquadFavorite(line : Int, task : String) extends Response
final case class InitList(info : Vector[SquadInfo]) extends Response
final case class UpdateList(infos : Iterable[(Int, SquadInfo)]) extends Response
final case class RemoveFromList(infos : Iterable[Int]) extends Response
final case class AssociateWithSquad(squad_guid : PlanetSideGUID) extends Response
final case class SetListSquad(squad_guid : PlanetSideGUID) 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 WantsSquadPosition(leader_char_id : Long, bid_name : String) extends Response
final case class Join(squad : Squad, positionsToUpdate : List[Int], channel : String) extends Response
final case class Leave(squad : Squad, positionsToUpdate : List[(Long, Int)]) extends Response
final case class UpdateMembers(squad : Squad, update_info : List[SquadAction.Update]) extends Response
final case class AssignMember(squad : Squad, from_index : Int, to_index : Int) extends Response
final case class PromoteMember(squad : Squad, char_id : Long, from_index : Int, to_index : Int) extends Response
final case class Detail(guid : PlanetSideGUID, squad_detail : SquadDetail) extends Response
final case class InitWaypoints(char_id : Long, waypoints : Iterable[(SquadWaypoints.Value, WaypointInfo, Int)]) extends Response
final case class WaypointEvent(event_type : WaypointEventAction.Value, char_id : Long, waypoint_type : SquadWaypoints.Value, unk5 : Option[Long], waypoint_info : Option[WaypointInfo], unk : Int) extends Response
final case class SquadSearchResults() extends Response
}

View file

@ -612,7 +612,6 @@ class SquadService extends Actor {
//player requested to join a squad's specific position
//invitedPlayer is actually the squad leader; petitioner is the actual "invitedPlayer"
val features = squadFeatures(guid)
features.Prompt.cancel
JoinSquad(petitioner, features.Squad, position)
RemoveInvitesForSquadAndPosition(guid, position)
@ -832,7 +831,6 @@ class SquadService extends Actor {
//rejectingPlayer is the squad leader; candidate is the would-be squad member who was rejected
val features = squadFeatures(guid)
features.Refuse = rejectingPlayer
features.Prompt.cancel
(Some(rejectingPlayer), None)
case _ => ;
@ -902,8 +900,6 @@ class SquadService extends Actor {
val (member, index) = membership.zipWithIndex.find { case (_member, _) => _member.CharId == promotedPlayer }.get
val features = squadFeatures(squad.GUID)
SwapMemberPosition(leader, member)
//cancel previous leader invite prompt, if any
features.Prompt.cancel
//move around invites so that the proper squad leader deals with them
val leaderInvite = invites.remove(promotingPlayer)
val leaderQueuedInvites = queuedInvites.remove(promotingPlayer).toList.flatten
@ -1108,6 +1104,28 @@ class SquadService extends Actor {
case AutoApproveInvitationRequests(state) =>
val features = squadFeatures(lSquadOpt.getOrElse(StartSquad(tplayer)).GUID)
features.AutoApproveInvitationRequests = state
if(state) {
//allowed auto-approval - resolve the requests (only)
val charId = tplayer.CharId
val (requests, others) = (invites.get(charId).toList ++ queuedInvites.get(charId).toList)
.partition({ case _ : RequestRole => true})
invites.remove(charId)
queuedInvites.remove(charId)
previousInvites.remove(charId)
requests.foreach {
case request : RequestRole =>
JoinSquad(request.player, features.Squad, request.position)
case _ => ;
}
others.collect { case invite : Invitation => invite } match {
case Nil => ;
case x :: Nil =>
AddInviteAndRespond(charId, x, x.InviterCharId, x.InviterName)
case x :: xs =>
AddInviteAndRespond(charId, x, x.InviterCharId, x.InviterName)
queuedInvites += charId -> xs
}
}
case FindLfsSoldiersForRole(position) =>
lSquadOpt match {
@ -1439,9 +1457,6 @@ class SquadService extends Actor {
debug(s"Unhandled message $msg from $sender")
}
case data @ SquadResponse.WantsSquadPosition(leader_char_id, _) =>
Publish(leader_char_id, data)
case msg =>
debug(s"Unhandled message $msg from $sender")
}
@ -2145,9 +2160,7 @@ class SquadService extends Actor {
self ! SquadServiceMessage(player, Zone.Nowhere, SquadAction.Membership(SquadRequestType.Accept, leaderCharId, None, "", None))
}
else {
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
features.Prompt = context.system.scheduler.schedule(1 milliseconds, 45000 milliseconds, self, SquadResponse.WantsSquadPosition(leaderCharId, player.Name))
Publish(leaderCharId, SquadResponse.WantsSquadPosition(leaderCharId, player.Name))
}
true
case _ =>

View file

@ -3,9 +3,23 @@ package services.teamwork
import net.psforever.objects.Player
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{PlanetSideGUID, SquadAction => PacketSquadAction, WaypointEventAction, WaypointInfo}
import net.psforever.types.{SquadRequestType, SquadWaypoints, Vector3}
final case class SquadServiceMessage(tplayer : Player, zone : Zone, actionMessage : Any)
object SquadServiceMessage {
final case class RecoverSquadMembership()
}
object SquadAction {
sealed trait Action
final case class InitSquadList() extends Action
final case class InitCharId() extends Action
final case class Definition(guid : PlanetSideGUID, line : Int, action : PacketSquadAction) 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 Waypoint(event_type : WaypointEventAction.Value, waypoint_type : SquadWaypoints.Value, unk : Option[Long], waypoint_info : Option[WaypointInfo]) 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

@ -1,6 +1,9 @@
// Copyright (c) 2019 PSForever
package services.teamwork
import net.psforever.objects.teamwork.Squad
import net.psforever.packet.game._
import net.psforever.types.{SquadResponseType, SquadWaypoints}
import services.GenericEventBusMsg
final case class SquadServiceResponse(toChannel : String, exclude : Iterable[Long], response : SquadResponse.Response) extends GenericEventBusMsg
@ -12,3 +15,31 @@ object SquadServiceResponse {
def apply(toChannel : String, exclude : Long, response : SquadResponse.Response) : SquadServiceResponse =
SquadServiceResponse(toChannel, Seq(exclude), response)
}
object SquadResponse {
sealed trait Response
final case class ListSquadFavorite(line : Int, task : String) extends Response
final case class InitList(info : Vector[SquadInfo]) extends Response
final case class UpdateList(infos : Iterable[(Int, SquadInfo)]) extends Response
final case class RemoveFromList(infos : Iterable[Int]) extends Response
final case class AssociateWithSquad(squad_guid : PlanetSideGUID) extends Response
final case class SetListSquad(squad_guid : PlanetSideGUID) 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 WantsSquadPosition(leader_char_id : Long, bid_name : String) extends Response
final case class Join(squad : Squad, positionsToUpdate : List[Int], channel : String) extends Response
final case class Leave(squad : Squad, positionsToUpdate : List[(Long, Int)]) extends Response
final case class UpdateMembers(squad : Squad, update_info : List[SquadAction.Update]) extends Response
final case class AssignMember(squad : Squad, from_index : Int, to_index : Int) extends Response
final case class PromoteMember(squad : Squad, char_id : Long, from_index : Int, to_index : Int) extends Response
final case class Detail(guid : PlanetSideGUID, squad_detail : SquadDetail) extends Response
final case class InitWaypoints(char_id : Long, waypoints : Iterable[(SquadWaypoints.Value, WaypointInfo, Int)]) extends Response
final case class WaypointEvent(event_type : WaypointEventAction.Value, char_id : Long, waypoint_type : SquadWaypoints.Value, unk5 : Option[Long], waypoint_info : Option[WaypointInfo], unk : Int) extends Response
final case class SquadSearchResults() extends Response
}

View file

@ -539,7 +539,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
SwapSquadUIElements(squad, from_index, to_index)
case SquadResponse.UpdateMembers(squad, positions) =>
import services.teamwork.SquadAction.{Update => SAUpdate}
val pairedEntries = positions.collect {
case entry if squadUI.contains(entry.char_id) =>
(entry, squadUI(entry.char_id))
@ -3220,6 +3219,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
val clientVersion = s"Client Version: $majorVersion.$minorVersion.$revision, $buildDate"
log.info(s"New world login to $server with Token:$token. $clientVersion")
//TODO begin temp player character auto-loading; remove later
//this is all just temporary character creation used in the dev branch, making explicit values that allow for testing
//the unique character identifier number for this testing character is based on the original test character,
//whose identifier number was 41605314
//all head features, faction, and sex also match that test character
import net.psforever.objects.GlobalDefinitions._
import net.psforever.types.CertificationType._
val faction = PlanetSideEmpire.VS