Looking For Squad player operations and interaction with squad services; evene more conditions to resolve joining/forming a squad

This commit is contained in:
FateJH 2019-08-20 23:03:04 -04:00
parent 60d65e22d3
commit ad4b259014
6 changed files with 198 additions and 91 deletions

View file

@ -46,6 +46,17 @@ class Avatar(private val char_id : Long, val name : String, val faction : Planet
}
private val deployables : DeployableToolbox = new DeployableToolbox
/**
* Looking For Squad:<br>
* Used to indicate both the marque that appears underneath a player's nameplate and an actual player state<br>
* This `Avatar`-specific "Looking for Squad" variable
* is used to indicate the active state of the LFS marque in the game
* and will change to `false` if the player is the member of a squad.
* A client-local version of "Looking for Squad" will maintain the real state of LFS
* once the player has joined a squad.
* The client-local version will restore the `Avatar`-local variable upon leaving the squad.
*/
private var lfs : Boolean = false
def CharId : Long = char_id
@ -184,6 +195,13 @@ class Avatar(private val char_id : Long, val name : String, val faction : Planet
def Deployables : DeployableToolbox = deployables
def LFS : Boolean = lfs
def LFS_=(looking : Boolean) : Boolean = {
lfs = looking
LFS
}
def Definition : AvatarDefinition = GlobalDefinitions.avatar
/*

View file

@ -70,6 +70,8 @@ class Player(private val core : Avatar) extends PlanetSideGameObject
def Voice : CharacterVoice.Value = core.voice
def LFS : Boolean = core.LFS
def isAlive : Boolean = alive
def isBackpack : Boolean = backpack

View file

@ -98,7 +98,7 @@ object AvatarConverter {
false,
facingPitch = obj.Orientation.y,
facingYawUpper = obj.FacingYawUpper,
lfs = true,
obj.LFS,
GrenadeState.None,
obj.Cloaked,
false,

View file

@ -8,7 +8,7 @@ object SquadRequestType extends Enumeration {
type Type = Value
val
Invite,
Unk01,
ProximityInvite,
Accept,
Reject,
Cancel,

View file

@ -266,14 +266,41 @@ class SquadService extends Actor {
// case _ => ;
// }
(memberToSquad.get(invitingPlayer), memberToSquad.get(invitedPlayer)) match {
case (Some(squad1), Some(squad2)) =>
//both players are in squads
if(squad1.GUID == squad2.GUID) {
//both players are in the same squad; no need to do anything
case (Some(squad1), Some(squad2))
if squad1.GUID == squad2.GUID =>
//both players are in the same squad; no need to do anything
case (Some(squad1), Some(squad2))
if 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(squad1), Some(squad2)) if squad2.Size == 1 =>
//both players belong to squads, but the invitedplayer's squad is underutilized by comparison
//treat the same as "the classic situation" using squad1
log.info(s"$invitedPlayer has been invited to squad ${squad1.Task} by $invitingPlayer")
val charId = tplayer.CharId
val bid = VacancyInvite(charId, tplayer.Name, squad1.GUID)
AddInvite(invitedPlayer, bid) match {
case out @ Some(_) if out.contains(bid) =>
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"/$charId/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitedPlayer, Some(charId), tplayer.Name, true, Some(None))))
case _ => ;
}
else {
//we might do some platoon chicanery with this case later
//TODO platoons
case (Some(squad1), Some(squad2)) if squad1.Size == 1 =>
//both players belong to squads, but the invitingplayer's squad is underutilized by comparison
//treat the same as "indirection ..." using squad2
val leaderCharId = squad2.Leader.CharId
val bid = IndirectVacancy(tplayer, squad2.GUID)
log.warn(s"$invitedPlayer has asked $invitingPlayer for an invitation to squad ${squad2.Task}, but the squad leader needs to approve")
AddInvite(leaderCharId, bid) match {
case out @ Some(_) if out.contains(bid) =>
HandleBidForPosition(bid, tplayer)
case _ => ;
}
case (Some(squad), None) =>
@ -314,79 +341,76 @@ class SquadService extends Actor {
case _ => ;
}
case _ => ;
case _ => //
}
case SquadAction.Membership(SquadRequestType.Accept, invitedPlayer, _, _, _) =>
val acceptedInvite = RemoveInvite(invitedPlayer)
acceptedInvite match {
case Some(BidForPosition(petitioner, guid, position)) if idToSquad.get(guid).nonEmpty =>
//player requested to join a squad's specific position
//invitedPlayer is actually the squad leader; petitioner is the actual "invitedPlayer"
if(memberToSquad.get(petitioner.CharId).isEmpty) {
if(EnsureEmptySquad(invitedPlayer, "Accept: the invited player is already a member of a squad and can not join a second one")) {
acceptedInvite match {
case Some(BidForPosition(petitioner, guid, position)) if idToSquad.get(guid).nonEmpty =>
//player requested to join a squad's specific position
//invitedPlayer is actually the squad leader; petitioner is the actual "invitedPlayer"
JoinSquad(petitioner, idToSquad(guid), position)
}
else {
log.warn("Accept->Bid: the invited player is already a member of a squad and can not join a second one")
}
case Some(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)
case Some(IndirectVacancy(recruit, guid)) =>
//tplayer / invitedPlayer is actually the squad leader
val recruitCharId = recruit.CharId
HandleVacancyInvite(guid, recruitCharId, invitedPlayer, recruit) match {
case Some((squad, line)) =>
SquadEvents.publish(SquadServiceResponse(s"/$recruitCharId/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitedPlayer, Some(recruitCharId), "", 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 _ => ;
}
case _ => ;
}
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 _ => ;
}
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 _ => ;
}
case Some(SpontaneousInvite(invitingPlayer)) =>
//originally, we were invited by someone into a new squad they would form
val invitingPlayerCharId = invitingPlayer.CharId
(GetParticipatingSquad(invitingPlayer) match {
case Some(participating) =>
//invitingPlayer became part of a squad while invited player was answering the original summons
Some(participating)
case _ =>
//generate a new squad, with invitingPlayer as the leader
val squad = StartSquad(invitingPlayer)
squad.Task = s"${invitingPlayer.Name}'s Squad"
SquadEvents.publish( SquadServiceResponse(s"/$invitingPlayerCharId/Squad", SquadResponse.AssociateWithSquad(squad.GUID)) )
Some(squad)
}) match {
case Some(squad) =>
HandleVacancyInvite(squad.GUID, tplayer.CharId, invitingPlayerCharId, tplayer) match {
case Some((_, line)) =>
SquadEvents.publish( SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitedPlayer, Some(invitingPlayerCharId), "", true, Some(None))) )
JoinSquad(tplayer, squad, line)
SquadEvents.publish( SquadServiceResponse(s"/$invitingPlayerCharId/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitingPlayerCharId, Some(invitedPlayer), tplayer.Name, false, Some(None))) )
RemoveQueuedInvites(tplayer.CharId) //TODO deal with these somehow
case _ => ;
}
case _ => ;
}
case Some(SpontaneousInvite(invitingPlayer)) =>
//originally, we were invited by someone into a new squad they would form
val invitingPlayerCharId = invitingPlayer.CharId
(GetParticipatingSquad(invitingPlayer) match {
case Some(participating) =>
//invitingPlayer became part of a squad while invited player was answering the original summons
Some(participating)
case _ =>
//generate a new squad, with invitingPlayer as the leader
val squad = StartSquad(invitingPlayer)
squad.Task = s"${invitingPlayer.Name}'s Squad"
SquadEvents.publish( SquadServiceResponse(s"/$invitingPlayerCharId/Squad", SquadResponse.AssociateWithSquad(squad.GUID)) )
Some(squad)
}) match {
case Some(squad) =>
HandleVacancyInvite(squad.GUID, tplayer.CharId, invitingPlayerCharId, tplayer) match {
case Some((_, line)) =>
SquadEvents.publish( SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitedPlayer, Some(invitingPlayerCharId), "", true, Some(None))) )
JoinSquad(tplayer, squad, line)
SquadEvents.publish( SquadServiceResponse(s"/$invitingPlayerCharId/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitingPlayerCharId, Some(invitedPlayer), tplayer.Name, false, Some(None))) )
RemoveQueuedInvites(tplayer.CharId) //TODO deal with these somehow
case _ => ;
}
case _ => ;
}
case None =>
//the invite either timed-out or was withdrawn; select a new one?
NextInvite(invitedPlayer) match {
case Some(bid : BidForPosition) if !acceptedInvite.contains(bid) =>
HandleBidForPosition(bid, tplayer)
case Some(bid) if !acceptedInvite.contains(bid) =>
SquadEvents.publish(SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, bid.InviterCharId, Some(invitedPlayer), bid.InviterName, false, Some(None))))
case None => ;
}
case None =>
//the invite either timed-out or was withdrawn; select a new one?
NextInvite(invitedPlayer) match {
case Some(bid : BidForPosition) if !acceptedInvite.contains(bid) =>
HandleBidForPosition(bid, tplayer)
case Some(bid) if !acceptedInvite.contains(bid) =>
SquadEvents.publish(SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, bid.InviterCharId, Some(invitedPlayer), bid.InviterName, false, Some(None))))
case None => ;
}
}
}
case SquadAction.Membership(SquadRequestType.Leave, leavingPlayer, optionalPlayer, _, _) =>
@ -712,24 +736,32 @@ class SquadService extends Actor {
}
case ResetAll() =>
val squad = lSquadOpt.getOrElse(StartSquad(tplayer))
squad.Task = ""
squad.ZoneId = None
squad.Availability.indices.foreach { i =>
squad.Availability.update(i, true)
lSquadOpt match {
case Some(squad) if squad.Size > 1 =>
squad.Task = ""
squad.ZoneId = None
squad.Availability.indices.foreach { i =>
squad.Availability.update(i, true)
}
squad.Membership.foreach(position => {
position.Role = ""
position.Orders = ""
position.Requirements = Set()
})
squad.LocationFollowsSquadLead = false
squad.AutoApproveInvitationRequests = false
UpdateSquadListWhenListed(squad, SquadInfo().Task("").ZoneId(None).Capacity(squad.Capacity))
UpdateSquadDetail(squad.GUID, squad)
sender ! SquadServiceResponse("", SquadResponse.AssociateWithSquad(PlanetSideGUID(0)))
if(!initialAssociation.contains(squad.GUID)) {
initialAssociation += squad.GUID
}
//do not unlist an already listed squad
case Some(squad) =>
//underutilized squad; just close it out
CloseOutSquad(squad)
case _ => ;
}
squad.Membership.foreach(position => {
position.Role = ""
position.Orders = ""
position.Requirements = Set()
})
squad.LocationFollowsSquadLead = false
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 _ =>
}
@ -1193,6 +1225,19 @@ class SquadService extends Actor {
}
}
def EnsureEmptySquad(char_id : Long, msg : String = "default warning message") : Boolean = {
memberToSquad.get(char_id) match {
case None =>
true
case Some(squad) if squad.Size == 1 =>
CloseOutSquad(squad)
true
case _ =>
log.warn(msg)
false
}
}
def CloseOutSquad(squad : Squad) : Unit = {
val membership = squad.Membership.zipWithIndex
CloseOutSquad(

View file

@ -118,6 +118,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
*/
var interstellarFerryTopLevelGUID : Option[PlanetSideGUID] = None
val squadUI : LongMap[SquadUIElement] = new LongMap[SquadUIElement]()
/**
* `AvatarConverter` can only rely on the `Avatar`-local Looking For Squad variable.
* When joining or creating a squad, the original state of the avatar's LFS variable is stored here.
* Upon leaving or disbanding a squad, this value is restored to the avatar's LFS variable.
*/
var lfs : Boolean = false
var amsSpawnPoints : List[SpawnPoint] = Nil
var clientKeepAlive : Cancellable = DefaultCancellable.obj
@ -160,6 +166,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
.collect { case ((index, Some(obj))) => InventoryItem(obj, index) }
) ++ player.Inventory.Items)
.filterNot({ case InventoryItem(obj, _) => obj.isInstanceOf[BoomerTrigger] || obj.isInstanceOf[Telepad] })
//put any temporary value back into the avatar
if(squadUI.nonEmpty) {
avatar.LFS = lfs
}
//TODO final character save before doing any of this (use equipment)
continent.Population ! Zone.Population.Release(avatar)
if(player.isAlive) {
@ -437,6 +447,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, ourIndex)) //associate with member position in squad?
//a finalization? what does this do?
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
//lfs state management (always OFF)
if(avatar.LFS) {
lfs = avatar.LFS
avatar.LFS = false
sendResponse(PlanetsideAttributeMessage(player.GUID, 53, 0))
}
case _ =>
//other player is joining our squad
//load each member's entry
@ -471,6 +487,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PlanetsideAttributeMessage(player.GUID, 34, 4294967295L)) //unknown, perhaps unrelated?
//a finalization? what does this do?
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
//lfs state management (maybe ON)
if(lfs) {
avatar.LFS = lfs
sendResponse(PlanetsideAttributeMessage(player.GUID, 53, 1))
lfs = false
}
case _ =>
//remove each member's entry
positionsToUpdate.foreach { case(member, index) =>
@ -3011,7 +3033,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(SetChatFilterMessage(ChatChannel.Local, false, ChatChannel.values.toList)) //TODO will not always be "on" like this
deadState = DeadState.Alive
sendResponse(AvatarDeadStateMessage(DeadState.Alive, 0, 0, tplayer.Position, player.Faction, true))
sendResponse(PlanetsideAttributeMessage(guid, 53, 1))
sendResponse(PlanetsideAttributeMessage(guid, 53, tplayer.LFS))
sendResponse(AvatarSearchCriteriaMessage(guid, List(0, 0, 0, 0, 0, 0)))
(1 to 73).foreach(i => {
// not all GUID's are set, and not all of the set ones will always be zero; what does this section do?
@ -4663,7 +4685,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info(s"GenericObject: $player is MAX with an unexpected weapon - ${definition.Name}")
}
}
else if(action == 16) {
else if(action == 16) { //max deployment
log.info(s"GenericObject: $player has released the anchors")
player.UsingSpecial = SpecialExoSuitDefinition.Mode.Normal
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 19, 0))
@ -4681,6 +4703,26 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info(s"GenericObject: $player is MAX with an unexpected weapon - ${definition.Name}")
}
}
else if(action == 37) { //Looking For Squad OFF
if(squadUI.nonEmpty) {
lfs = false
}
else if(avatar.LFS) {
avatar.LFS = false
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 53, 0))
log.info(s"GenericObject: ${player.Name} is no longer looking for a squad to join")
}
}
else if(action == 36) { //Looking For Squad ON
if(squadUI.nonEmpty) {
lfs = true
}
else if(!avatar.LFS) {
avatar.LFS = true
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 53, 1))
log.info(s"GenericObject: ${player.Name} has made himself available to join a squad")
}
}
case msg @ ItemTransactionMessage(terminal_guid, transaction_type, _, _, _, _) =>
log.info("ItemTransaction: " + msg)