completely retooled login messages system to support a series of tasks queued up for when the ui has finished loading and the player has control of their game; attempt to clean up old squad cards during proper log-outs and before relog; ability to pass indices to squad invitations for targeted acceptance or rejection

This commit is contained in:
Fate-JH 2024-09-17 02:30:58 -04:00
parent 1968377d05
commit aeb6b8f2a9
12 changed files with 310 additions and 126 deletions

View file

@ -538,6 +538,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
continent.actor ! ZoneActor.RewardThisDeath(player) continent.actor ! ZoneActor.RewardThisDeath(player)
//player state changes //player state changes
sessionLogic.zoning.spawn.avatarActive = false
AvatarActor.updateToolDischargeFor(avatar) AvatarActor.updateToolDischargeFor(avatar)
player.FreeHand.Equipment.foreach { item => player.FreeHand.Equipment.foreach { item =>
DropEquipmentFromInventory(player)(item) DropEquipmentFromInventory(player)(item)

View file

@ -99,6 +99,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
_, _,
_ _
)= pkt )= pkt
sessionLogic.zoning.spawn.tryQueuedActivity(vel)
sessionLogic.persist() sessionLogic.persist()
sessionLogic.turnCounterFunc(avatarGuid) sessionLogic.turnCounterFunc(avatarGuid)
sessionLogic.updateBlockMap(player, pos) sessionLogic.updateBlockMap(player, pos)

View file

@ -2,20 +2,24 @@
package net.psforever.actors.session.normal package net.psforever.actors.session.normal
import akka.actor.{ActorContext, ActorRef, typed} import akka.actor.{ActorContext, ActorRef, typed}
import net.psforever.actors.session.support.SessionSquadHandlers.SquadUIElement
import net.psforever.actors.session.AvatarActor import net.psforever.actors.session.AvatarActor
import net.psforever.actors.session.support.{SessionData, SessionSquadHandlers, SquadHandlerFunctions} import net.psforever.actors.session.support.SessionSquadHandlers.{rethrowSquadServiceResponse, SquadUIElement}
import net.psforever.actors.session.support.{SessionData, SessionSquadHandlers, SpawnOperations, SquadHandlerFunctions}
import net.psforever.objects.{Default, LivePlayerList} import net.psforever.objects.{Default, LivePlayerList}
import net.psforever.objects.avatar.Avatar import net.psforever.objects.avatar.Avatar
import net.psforever.packet.game.{CharacterKnowledgeInfo, CharacterKnowledgeMessage, ChatMsg, MemberEvent, PlanetsideAttributeMessage, ReplicationStreamMessage, SquadAction, SquadDefinitionActionMessage, SquadDetailDefinitionUpdateMessage, SquadListing, SquadMemberEvent, SquadMembershipRequest, SquadMembershipResponse, SquadState, SquadStateInfo, SquadWaypointEvent, SquadWaypointRequest, WaypointEvent, WaypointEventAction} import net.psforever.packet.game.{CharacterKnowledgeInfo, CharacterKnowledgeMessage, ChatMsg, MemberEvent, PlanetsideAttributeMessage, ReplicationStreamMessage, SquadAction, SquadDefinitionActionMessage, SquadDetailDefinitionUpdateMessage, SquadListing, SquadMemberEvent, SquadMembershipRequest, SquadMembershipResponse, SquadState, SquadStateInfo, SquadWaypointEvent, SquadWaypointRequest, WaypointEvent, WaypointEventAction}
import net.psforever.services.chat.SquadChannel import net.psforever.services.chat.SquadChannel
import net.psforever.services.teamwork.{SquadResponse, SquadServiceMessage, SquadAction => SquadServiceAction} import net.psforever.services.teamwork.{SquadResponse, SquadServiceMessage, SquadServiceResponse, SquadAction => SquadServiceAction}
import net.psforever.types.{ChatMessageType, PlanetSideGUID, SquadListDecoration, SquadResponseType, WaypointSubtype} import net.psforever.types.{ChatMessageType, PlanetSideGUID, SquadListDecoration, SquadResponseType, WaypointSubtype}
object SquadHandlerLogic { object SquadHandlerLogic {
def apply(ops: SessionSquadHandlers): SquadHandlerLogic = { def apply(ops: SessionSquadHandlers): SquadHandlerLogic = {
new SquadHandlerLogic(ops, ops.context) new SquadHandlerLogic(ops, ops.context)
} }
def rethrowSquadServiceResponse(response: SquadResponse.Response)(sessionLogic: SessionData): Unit = {
sessionLogic.context.self ! SquadServiceResponse("", response)
}
} }
class SquadHandlerLogic(val ops: SessionSquadHandlers, implicit val context: ActorContext) extends SquadHandlerFunctions { class SquadHandlerLogic(val ops: SessionSquadHandlers, implicit val context: ActorContext) extends SquadHandlerFunctions {
@ -59,6 +63,7 @@ class SquadHandlerLogic(val ops: SessionSquadHandlers, implicit val context: Act
def handle(response: SquadResponse.Response, excluded: Iterable[Long]): Unit = { def handle(response: SquadResponse.Response, excluded: Iterable[Long]): Unit = {
if (!excluded.exists(_ == avatar.id)) { if (!excluded.exists(_ == avatar.id)) {
response match { response match {
/* these messages will never be queued for later */
case SquadResponse.ListSquadFavorite(line, task) => case SquadResponse.ListSquadFavorite(line, task) =>
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), line, SquadAction.ListSquadFavorite(task))) sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), line, SquadAction.ListSquadFavorite(task)))
@ -106,12 +111,69 @@ class SquadHandlerLogic(val ops: SessionSquadHandlers, implicit val context: Act
case SquadResponse.Detail(guid, detail) => case SquadResponse.Detail(guid, detail) =>
sendResponse(SquadDetailDefinitionUpdateMessage(guid, detail)) sendResponse(SquadDetailDefinitionUpdateMessage(guid, detail))
case SquadResponse.IdentifyAsSquadLeader(squad_guid) =>
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.IdentifyAsSquadLeader()))
case SquadResponse.SetListSquad(squad_guid) => case SquadResponse.SetListSquad(squad_guid) =>
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.SetListSquad())) sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.SetListSquad()))
case SquadResponse.SquadSearchResults(results) =>
//TODO positive squad search results message?
if(results.nonEmpty) {
results.foreach { guid =>
sendResponse(SquadDefinitionActionMessage(
guid,
0,
SquadAction.SquadListDecorator(SquadListDecoration.SearchResult))
)
}
} else {
sendResponse(SquadDefinitionActionMessage(player.GUID, 0, SquadAction.NoSquadSearchResults()))
}
sendResponse(SquadDefinitionActionMessage(player.GUID, 0, SquadAction.CancelSquadSearch()))
case SquadResponse.UpdateMembers(_, positions) =>
val pairedEntries = positions.collect {
case entry if ops.squadUI.contains(entry.char_id) =>
(entry, ops.squadUI(entry.char_id))
}
//prune entries
val updatedEntries = pairedEntries
.collect({
case (entry, element) if entry.zone_number != element.zone =>
//zone gets updated for these entries
sendResponse(
SquadMemberEvent.UpdateZone(ops.squad_supplement_id, entry.char_id, element.index, entry.zone_number)
)
ops.squadUI(entry.char_id) =
SquadUIElement(element.name, element.outfit, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
entry
case (entry, element)
if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position =>
//other elements that need to be updated
ops.squadUI(entry.char_id) =
SquadUIElement(element.name, element.outfit, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
entry
})
.filterNot(_.char_id == avatar.id) //we want to update our backend, but not our frontend
if (updatedEntries.nonEmpty) {
sendResponse(
SquadState(
PlanetSideGUID(ops.squad_supplement_id),
updatedEntries.map { entry =>
SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos)
}
)
)
}
/* queue below messages for later if the initial conditions are inappropriate */
case msg if !sessionLogic.zoning.spawn.startEnqueueSquadMessages =>
sessionLogic.zoning.spawn.enqueueNewActivity(
SpawnOperations.ActivityQueuedTask(rethrowSquadServiceResponse(msg), 1)
)
/* these messages will be queued for later if initial conditions are inappropriate */
case SquadResponse.IdentifyAsSquadLeader(squad_guid) =>
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.IdentifyAsSquadLeader()))
case SquadResponse.Membership(request_type, unk1, unk2, charId, opt_char_id, player_name, unk5, unk6) => case SquadResponse.Membership(request_type, unk1, unk2, charId, opt_char_id, player_name, unk5, unk6) =>
val name = request_type match { val name = request_type match {
case SquadResponseType.Invite if unk5 => case SquadResponseType.Invite if unk5 =>
@ -270,59 +332,9 @@ class SquadHandlerLogic(val ops: SessionSquadHandlers, implicit val context: Act
//the players have already been swapped in the backend object //the players have already been swapped in the backend object
ops.PromoteSquadUIElements(squad, from_index) ops.PromoteSquadUIElements(squad, from_index)
case SquadResponse.UpdateMembers(_, positions) =>
val pairedEntries = positions.collect {
case entry if ops.squadUI.contains(entry.char_id) =>
(entry, ops.squadUI(entry.char_id))
}
//prune entries
val updatedEntries = pairedEntries
.collect({
case (entry, element) if entry.zone_number != element.zone =>
//zone gets updated for these entries
sendResponse(
SquadMemberEvent.UpdateZone(ops.squad_supplement_id, entry.char_id, element.index, entry.zone_number)
)
ops.squadUI(entry.char_id) =
SquadUIElement(element.name, element.outfit, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
entry
case (entry, element)
if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position =>
//other elements that need to be updated
ops.squadUI(entry.char_id) =
SquadUIElement(element.name, element.outfit, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
entry
})
.filterNot(_.char_id == avatar.id) //we want to update our backend, but not our frontend
if (updatedEntries.nonEmpty) {
sendResponse(
SquadState(
PlanetSideGUID(ops.squad_supplement_id),
updatedEntries.map { entry =>
SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos)
}
)
)
}
case SquadResponse.CharacterKnowledge(charId, name, certs, u1, u2, zone) => case SquadResponse.CharacterKnowledge(charId, name, certs, u1, u2, zone) =>
sendResponse(CharacterKnowledgeMessage(charId, Some(CharacterKnowledgeInfo(name, certs, u1, u2, zone)))) sendResponse(CharacterKnowledgeMessage(charId, Some(CharacterKnowledgeInfo(name, certs, u1, u2, zone))))
case SquadResponse.SquadSearchResults(_/*results*/) =>
//TODO positive squad search results message?
// if(results.nonEmpty) {
// results.foreach { guid =>
// sendResponse(SquadDefinitionActionMessage(
// guid,
// 0,
// SquadAction.SquadListDecorator(SquadListDecoration.SearchResult))
// )
// }
// } else {
// sendResponse(SquadDefinitionActionMessage(player.GUID, 0, SquadAction.NoSquadSearchResults()))
// }
// sendResponse(SquadDefinitionActionMessage(player.GUID, 0, SquadAction.CancelSquadSearch()))
case SquadResponse.InitWaypoints(char_id, waypoints) => case SquadResponse.InitWaypoints(char_id, waypoints) =>
waypoints.foreach { waypoints.foreach {
case (waypoint_type, info, unk) => case (waypoint_type, info, unk) =>

View file

@ -44,6 +44,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
ops.GetVehicleAndSeat() match { ops.GetVehicleAndSeat() match {
case (Some(obj), Some(0)) => case (Some(obj), Some(0)) =>
//we're driving the vehicle //we're driving the vehicle
sessionLogic.zoning.spawn.tryQueuedActivity(vel)
sessionLogic.persist() sessionLogic.persist()
sessionLogic.turnCounterFunc(player.GUID) sessionLogic.turnCounterFunc(player.GUID)
sessionLogic.general.fallHeightTracker(pos.z) sessionLogic.general.fallHeightTracker(pos.z)
@ -128,6 +129,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
ops.GetVehicleAndSeat() match { ops.GetVehicleAndSeat() match {
case (Some(obj), Some(0)) => case (Some(obj), Some(0)) =>
//we're driving the vehicle //we're driving the vehicle
sessionLogic.zoning.spawn.tryQueuedActivity(vel)
sessionLogic.persist() sessionLogic.persist()
sessionLogic.turnCounterFunc(player.GUID) sessionLogic.turnCounterFunc(player.GUID)
val (position, angle, velocity, notMountedState) = continent.GUID(obj.MountedIn) match { val (position, angle, velocity, notMountedState) = continent.GUID(obj.MountedIn) match {
@ -217,6 +219,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
case (None, None) | (_, None) | (Some(_: Vehicle), Some(0)) => case (None, None) | (_, None) | (Some(_: Vehicle), Some(0)) =>
() ()
case _ => case _ =>
sessionLogic.zoning.spawn.tryQueuedActivity() //todo conditionals?
sessionLogic.persist() sessionLogic.persist()
sessionLogic.turnCounterFunc(player.GUID) sessionLogic.turnCounterFunc(player.GUID)
} }

View file

@ -450,6 +450,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
sessionLogic.zoning.CancelZoningProcess() sessionLogic.zoning.CancelZoningProcess()
//player state changes //player state changes
sessionLogic.zoning.spawn.avatarActive = false
AvatarActor.updateToolDischargeFor(avatar) AvatarActor.updateToolDischargeFor(avatar)
player.FreeHand.Equipment.foreach { item => player.FreeHand.Equipment.foreach { item =>
DropEquipmentFromInventory(player)(item) DropEquipmentFromInventory(player)(item)

View file

@ -56,6 +56,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
_, _,
_ _
)= pkt )= pkt
sessionLogic.zoning.spawn.tryQueuedActivity(vel)
sessionLogic.persist() sessionLogic.persist()
sessionLogic.turnCounterFunc(avatarGuid) sessionLogic.turnCounterFunc(avatarGuid)
sessionLogic.updateBlockMap(player, pos) sessionLogic.updateBlockMap(player, pos)

View file

@ -2,15 +2,15 @@
package net.psforever.actors.session.spectator package net.psforever.actors.session.spectator
import akka.actor.{ActorContext, typed} import akka.actor.{ActorContext, typed}
import net.psforever.actors.session.support.SessionSquadHandlers.SquadUIElement
import net.psforever.actors.session.AvatarActor import net.psforever.actors.session.AvatarActor
import net.psforever.actors.session.support.{SessionData, SessionSquadHandlers, SquadHandlerFunctions} import net.psforever.actors.session.support.SessionSquadHandlers.{rethrowSquadServiceResponse, SquadUIElement}
import net.psforever.actors.session.support.{SessionData, SessionSquadHandlers, SpawnOperations, SquadHandlerFunctions}
import net.psforever.objects.{Default, LivePlayerList} import net.psforever.objects.{Default, LivePlayerList}
import net.psforever.objects.avatar.Avatar import net.psforever.objects.avatar.Avatar
import net.psforever.packet.game.{CharacterKnowledgeInfo, CharacterKnowledgeMessage, PlanetsideAttributeMessage, ReplicationStreamMessage, SquadAction, SquadDefinitionActionMessage, SquadDetailDefinitionUpdateMessage, SquadListing, SquadMemberEvent, SquadMembershipRequest, SquadMembershipResponse, SquadState, SquadStateInfo, SquadWaypointEvent, SquadWaypointRequest, WaypointEventAction} import net.psforever.packet.game.{CharacterKnowledgeInfo, CharacterKnowledgeMessage, ChatMsg, PlanetsideAttributeMessage, ReplicationStreamMessage, SquadAction, SquadDefinitionActionMessage, SquadDetailDefinitionUpdateMessage, SquadListing, SquadMemberEvent, SquadMembershipRequest, SquadMembershipResponse, SquadState, SquadStateInfo, SquadWaypointEvent, SquadWaypointRequest, WaypointEventAction}
import net.psforever.services.chat.SquadChannel import net.psforever.services.chat.SquadChannel
import net.psforever.services.teamwork.SquadResponse import net.psforever.services.teamwork.SquadResponse
import net.psforever.types.{PlanetSideGUID, SquadListDecoration, SquadResponseType} import net.psforever.types.{ChatMessageType, PlanetSideGUID, SquadListDecoration, SquadResponseType}
object SquadHandlerLogic { object SquadHandlerLogic {
def apply(ops: SessionSquadHandlers): SquadHandlerLogic = { def apply(ops: SessionSquadHandlers): SquadHandlerLogic = {
@ -77,6 +77,62 @@ class SquadHandlerLogic(val ops: SessionSquadHandlers, implicit val context: Act
} }
sendResponse(SquadDefinitionActionMessage(guid, 0, SquadAction.SquadListDecorator(decoration))) sendResponse(SquadDefinitionActionMessage(guid, 0, SquadAction.SquadListDecorator(decoration)))
case SquadResponse.SquadSearchResults(results) =>
//TODO positive squad search results message?
if(results.nonEmpty) {
results.foreach { guid =>
sendResponse(SquadDefinitionActionMessage(
guid,
0,
SquadAction.SquadListDecorator(SquadListDecoration.SearchResult))
)
}
} else {
sendResponse(SquadDefinitionActionMessage(player.GUID, 0, SquadAction.NoSquadSearchResults()))
}
sendResponse(SquadDefinitionActionMessage(player.GUID, 0, SquadAction.CancelSquadSearch()))
case SquadResponse.UpdateMembers(_, positions) =>
val pairedEntries = positions.collect {
case entry if ops.squadUI.contains(entry.char_id) =>
(entry, ops.squadUI(entry.char_id))
}
//prune entries
val updatedEntries = pairedEntries
.collect({
case (entry, element) if entry.zone_number != element.zone =>
//zone gets updated for these entries
sendResponse(
SquadMemberEvent.UpdateZone(ops.squad_supplement_id, entry.char_id, element.index, entry.zone_number)
)
ops.squadUI(entry.char_id) =
SquadUIElement(element.name, element.outfit, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
entry
case (entry, element)
if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position =>
//other elements that need to be updated
ops.squadUI(entry.char_id) =
SquadUIElement(element.name, element.outfit, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
entry
})
.filterNot(_.char_id == avatar.id) //we want to update our backend, but not our frontend
if (updatedEntries.nonEmpty) {
sendResponse(
SquadState(
PlanetSideGUID(ops.squad_supplement_id),
updatedEntries.map { entry =>
SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos)
}
)
)
}
/* queue below messages for later if the initial conditions are inappropriate */
case msg if !sessionLogic.zoning.spawn.startEnqueueSquadMessages =>
sessionLogic.zoning.spawn.enqueueNewActivity(
SpawnOperations.ActivityQueuedTask(rethrowSquadServiceResponse(msg), 1)
)
case SquadResponse.Detail(guid, detail) => case SquadResponse.Detail(guid, detail) =>
sendResponse(SquadDetailDefinitionUpdateMessage(guid, detail)) sendResponse(SquadDetailDefinitionUpdateMessage(guid, detail))
@ -132,47 +188,15 @@ class SquadHandlerLogic(val ops: SessionSquadHandlers, implicit val context: Act
) )
} }
case SquadResponse.UpdateMembers(_, positions) =>
val pairedEntries = positions.collect {
case entry if ops.squadUI.contains(entry.char_id) =>
(entry, ops.squadUI(entry.char_id))
}
//prune entries
val updatedEntries = pairedEntries
.collect({
case (entry, element) if entry.zone_number != element.zone =>
//zone gets updated for these entries
sendResponse(
SquadMemberEvent.UpdateZone(ops.squad_supplement_id, entry.char_id, element.index, entry.zone_number)
)
ops.squadUI(entry.char_id) =
SquadUIElement(element.name, element.outfit, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
entry
case (entry, element)
if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position =>
//other elements that need to be updated
ops.squadUI(entry.char_id) =
SquadUIElement(element.name, element.outfit, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
entry
})
.filterNot(_.char_id == avatar.id) //we want to update our backend, but not our frontend
if (updatedEntries.nonEmpty) {
sendResponse(
SquadState(
PlanetSideGUID(ops.squad_supplement_id),
updatedEntries.map { entry =>
SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos)
}
)
)
}
case SquadResponse.CharacterKnowledge(charId, name, certs, u1, u2, zone) => case SquadResponse.CharacterKnowledge(charId, name, certs, u1, u2, zone) =>
sendResponse(CharacterKnowledgeMessage(charId, Some(CharacterKnowledgeInfo(name, certs, u1, u2, zone)))) sendResponse(CharacterKnowledgeMessage(charId, Some(CharacterKnowledgeInfo(name, certs, u1, u2, zone))))
case SquadResponse.WaypointEvent(WaypointEventAction.Remove, char_id, waypoint_type, _, _, _) => case SquadResponse.WaypointEvent(WaypointEventAction.Remove, char_id, waypoint_type, _, _, _) =>
sendResponse(SquadWaypointEvent.Remove(ops.squad_supplement_id, char_id, waypoint_type)) sendResponse(SquadWaypointEvent.Remove(ops.squad_supplement_id, char_id, waypoint_type))
case SquadResponse.SquadRelatedComment(comment) =>
sendResponse(ChatMsg(ChatMessageType.UNK_227, comment))
case _ => () case _ => ()
} }
} }

View file

@ -2,26 +2,30 @@
package net.psforever.actors.session.support package net.psforever.actors.session.support
import akka.actor.Cancellable import akka.actor.Cancellable
import net.psforever.objects.LivePlayerList
import akka.actor.{ActorRef => ClassicActorRef} import akka.actor.{ActorRef => ClassicActorRef}
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
import akka.actor.{ActorContext, typed} import akka.actor.{ActorContext, typed}
import akka.pattern.ask
import akka.util.Timeout
import net.psforever.actors.session.spectator.SpectatorMode import net.psforever.actors.session.spectator.SpectatorMode
import net.psforever.actors.session.{AvatarActor, SessionActor} import net.psforever.actors.session.{AvatarActor, SessionActor}
import net.psforever.actors.zone.ZoneActor import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.LivePlayerList
import net.psforever.objects.sourcing.PlayerSource import net.psforever.objects.sourcing.PlayerSource
import net.psforever.objects.zones.ZoneInfo import net.psforever.objects.zones.ZoneInfo
import net.psforever.packet.game.SetChatFilterMessage import net.psforever.packet.game.SetChatFilterMessage
import net.psforever.services.chat.{DefaultChannel, SquadChannel} import net.psforever.services.chat.{DefaultChannel, SquadChannel}
import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.services.teamwork.SquadService import net.psforever.services.teamwork.{SquadResponse, SquadService, SquadServiceResponse}
import net.psforever.types.ChatMessageType.CMT_QUIT import net.psforever.types.ChatMessageType.CMT_QUIT
import org.log4s.Logger import org.log4s.Logger
import java.util.concurrent.{Executors, TimeUnit} import java.util.concurrent.{Executors, TimeUnit}
import scala.annotation.unused import scala.annotation.unused
import scala.collection.{Seq, mutable} import scala.collection.{Seq, mutable}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.util.Success
// //
import net.psforever.actors.zone.BuildingActor import net.psforever.actors.zone.BuildingActor
import net.psforever.login.WorldSession import net.psforever.login.WorldSession
@ -78,6 +82,10 @@ class ChatOperations(
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._
private val chatServiceAdapter: ActorRef[ChatService.MessageResponse] = context.self.toTyped[ChatService.MessageResponse] private val chatServiceAdapter: ActorRef[ChatService.MessageResponse] = context.self.toTyped[ChatService.MessageResponse]
private implicit lazy val timeout: Timeout = Timeout(2.seconds)
private var invitationList: Array[String] = Array()
def JoinChannel(channel: ChatChannel): Unit = { def JoinChannel(channel: ChatChannel): Unit = {
chatService ! ChatService.JoinChannel(chatServiceAdapter, sessionLogic, channel) chatService ! ChatService.JoinChannel(chatServiceAdapter, sessionLogic, channel)
channels ++= List(channel) channels ++= List(channel)
@ -1291,22 +1299,37 @@ class ChatOperations(
def customCommandSquad(params: Seq[String]): Boolean = { def customCommandSquad(params: Seq[String]): Boolean = {
params match { params match {
case "invites" :: _ => case "invites" :: _ =>
squadService ! SquadService.ListAllCurrentInvites invitationList = Array()
ask(squadService, SquadService.ListAllCurrentInvites)
.onComplete {
case Success(msg @ SquadServiceResponse(_, _, SquadResponse.WantsSquadPosition(_, str))) =>
invitationList = str.replaceAll("/s","").split(",")
context.self.forward(msg)
case _ => ()
}
case "accept" :: names if names.contains("all") => case "accept" :: names if names.contains("all") =>
squadService ! SquadService.ChainAcceptance(player, player.CharId, Nil) squadService ! SquadService.ChainAcceptance(player, player.CharId, Nil)
case "accept" :: names if names.nonEmpty => case "accept" :: names if names.nonEmpty =>
val results = names.flatMap { name => //when passing indices to existing invite list, the indices are 1-based
LivePlayerList.WorldPopulation { case (_, p) => p.name.equals(name) }.map(_.id.toLong) val results = (names.flatMap(_.toIntOption.flatMap(i => invitationList.lift(i-1))) ++ names)
.distinct
.flatMap { name =>
LivePlayerList.WorldPopulation { case (_, p) => p.name.equalsIgnoreCase(name) }
.map(_.id.toLong)
} }
squadService ! SquadService.ChainAcceptance(player, player.CharId, results) squadService ! SquadService.ChainAcceptance(player, player.CharId, results)
case "reject" :: names if names.contains("all") => case "reject" :: names if names.contains("all") =>
squadService ! SquadService.ChainRejection(player, player.CharId, Nil) squadService ! SquadService.ChainRejection(player, player.CharId, Nil)
case "reject" :: names if names.nonEmpty => case "reject" :: names if names.nonEmpty =>
val results = names.flatMap { name => //when passing indices to existing invite list, the indices are 1-based
LivePlayerList.WorldPopulation { case (_, p) => p.name.equals(name) }.map(_.id.toLong) val results = (names.flatMap(_.toIntOption.flatMap(i => invitationList.lift(i-1))) ++ names)
} .distinct
.flatMap { name =>
LivePlayerList.WorldPopulation { case (_, p) => p.name.equalsIgnoreCase(name) }
.map(_.id.toLong)
}
squadService ! SquadService.ChainRejection(player, player.CharId, results) squadService ! SquadService.ChainRejection(player, player.CharId, results)
case _ => () case _ => ()

View file

@ -554,6 +554,7 @@ class SessionData(
if (avatar != null) { if (avatar != null) {
accountPersistence ! AccountPersistenceService.Logout(avatar.name) accountPersistence ! AccountPersistenceService.Logout(avatar.name)
} }
squad.cleanUpSquadCards()
middlewareActor ! MiddlewareActor.Teardown() middlewareActor ! MiddlewareActor.Teardown()
} }

View file

@ -2,6 +2,8 @@
package net.psforever.actors.session.support package net.psforever.actors.session.support
import akka.actor.{ActorContext, ActorRef, typed} import akka.actor.{ActorContext, ActorRef, typed}
import net.psforever.services.teamwork.SquadServiceResponse
import scala.collection.mutable import scala.collection.mutable
// //
import net.psforever.actors.session.AvatarActor import net.psforever.actors.session.AvatarActor
@ -36,6 +38,9 @@ object SessionSquadHandlers {
armor: Int, armor: Int,
position: Vector3 position: Vector3
) )
def rethrowSquadServiceResponse(response: SquadResponse.Response)(sessionLogic: SessionData): Unit = {
sessionLogic.context.self ! SquadServiceResponse("", response)
}
} }
class SessionSquadHandlers( class SessionSquadHandlers(
@ -84,6 +89,7 @@ class SessionSquadHandlers(
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18))) sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitSquadList()) squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitSquadList())
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitCharId()) squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitCharId())
cleanUpSquadCards()
squadSetup = RespawnSquadSetup squadSetup = RespawnSquadSetup
} }
@ -345,4 +351,12 @@ class SessionSquadHandlers(
) )
} }
} }
def cleanUpSquadCards(): Unit = {
squadUI.foreach { case (id, card) =>
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, id, card.index))
}
squadUI.clear()
squad_supplement_id = 0
}
} }

View file

@ -6,10 +6,12 @@ import akka.actor.typed.scaladsl.adapter._
import akka.actor.{ActorContext, ActorRef, Cancellable, typed} import akka.actor.{ActorContext, ActorRef, Cancellable, typed}
import akka.pattern.ask import akka.pattern.ask
import akka.util.Timeout import akka.util.Timeout
import net.psforever.actors.session.support.SpawnOperations.ActivityQueuedTask
import net.psforever.login.WorldSession import net.psforever.login.WorldSession
import net.psforever.objects.avatar.{BattleRank, DeployableToolbox} import net.psforever.objects.avatar.{BattleRank, DeployableToolbox}
import net.psforever.objects.avatar.scoring.{CampaignStatistics, ScoreCard, SessionStatistics} import net.psforever.objects.avatar.scoring.{CampaignStatistics, ScoreCard, SessionStatistics}
import net.psforever.objects.definition.converter.OCM import net.psforever.objects.definition.converter.OCM
import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.inventory.InventoryItem import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.serverobject.interior.Sidedness import net.psforever.objects.serverobject.interior.Sidedness
import net.psforever.objects.serverobject.mount.Seat import net.psforever.objects.serverobject.mount.Seat
@ -21,7 +23,6 @@ import net.psforever.objects.zones.blockmap.BlockMapEntity
import net.psforever.packet.game.{CampaignStatistic, ChangeFireStateMessage_Start, HackState7, MailMessage, ObjectDetectedMessage, SessionStatistic, TriggeredSound} import net.psforever.packet.game.{CampaignStatistic, ChangeFireStateMessage_Start, HackState7, MailMessage, ObjectDetectedMessage, SessionStatistic, TriggeredSound}
import net.psforever.services.chat.DefaultChannel import net.psforever.services.chat.DefaultChannel
import scala.collection.mutable
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future import scala.concurrent.Future
@ -83,8 +84,8 @@ object ZoningOperations {
private final val zoningCountdownMessages: Seq[Int] = Seq(5, 10, 20) private final val zoningCountdownMessages: Seq[Int] = Seq(5, 10, 20)
def reportProgressionSystem(sessionActor: ActorRef): Unit = { def reportProgressionSystem(logic: SessionData): Unit = {
sessionActor ! SessionActor.SendResponse( logic.context.self ! SessionActor.SendResponse(
MailMessage( MailMessage(
"High Command", "High Command",
"Progress versus Promotion", "Progress versus Promotion",
@ -185,6 +186,14 @@ object ZoningOperations {
} }
} }
object SpawnOperations {
final case class ActivityQueuedTask(task: SessionData => Unit, delayBeforeNext: Int, repeat: Int = 0)
def sendEventMessage(msg: ChatMsg)(sessionLogic: SessionData): Unit = {
sessionLogic.sendResponse(msg)
}
}
class ZoningOperations( class ZoningOperations(
val sessionLogic: SessionData, val sessionLogic: SessionData,
avatarActor: typed.ActorRef[AvatarActor.Command], avatarActor: typed.ActorRef[AvatarActor.Command],
@ -671,11 +680,15 @@ class ZoningOperations(
zoningType = Zoning.Method.Login zoningType = Zoning.Method.Login
response match { response match {
case Some((zone, spawnPoint)) => case Some((zone, spawnPoint)) =>
spawn.loginChatMessage.addOne("@login_reposition_to_friendly_facility") //Your previous location was held by the enemy. You have been moved to the nearest friendly facility. spawn.enqueueNewActivity(ActivityQueuedTask(
SpawnOperations.sendEventMessage(ChatMsg(ChatMessageType.CMT_QUIT, "@login_reposition_to_friendly_facility")), 20)
)
val (pos, ori) = spawnPoint.SpecificPoint(player) val (pos, ori) = spawnPoint.SpecificPoint(player)
spawn.LoadZonePhysicalSpawnPoint(zone.id, pos, ori, respawnTime = 0 seconds, Some(spawnPoint)) spawn.LoadZonePhysicalSpawnPoint(zone.id, pos, ori, respawnTime = 0 seconds, Some(spawnPoint))
case _ => case _ =>
spawn.loginChatMessage.addOne("@login_reposition_to_sanctuary") //Your previous location was held by the enemy. As there were no operational friendly facilities on that continent, you have been brought back to your Sanctuary. spawn.enqueueNewActivity(ActivityQueuedTask(
SpawnOperations.sendEventMessage(ChatMsg(ChatMessageType.CMT_QUIT, "@login_reposition_to_sanctuary")), 20)
)
RequestSanctuaryZoneSpawn(player, player.Zone.Number) RequestSanctuaryZoneSpawn(player, player.Zone.Number)
} }
@ -1863,7 +1876,6 @@ class ZoningOperations(
class SpawnOperations() { class SpawnOperations() {
private[session] var deadState: DeadState.Value = DeadState.Dead private[session] var deadState: DeadState.Value = DeadState.Dead
private[session] var loginChatMessage: mutable.ListBuffer[String] = new mutable.ListBuffer[String]()
private[session] var amsSpawnPoints: List[SpawnPoint] = Nil private[session] var amsSpawnPoints: List[SpawnPoint] = Nil
private[session] var noSpawnPointHere: Boolean = false private[session] var noSpawnPointHere: Boolean = false
private[session] var setupAvatarFunc: () => Unit = AvatarCreate private[session] var setupAvatarFunc: () => Unit = AvatarCreate
@ -1886,9 +1898,14 @@ class ZoningOperations(
private[session] var drawDeloyableIcon: PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons private[session] var drawDeloyableIcon: PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons
private[session] var populateAvatarAwardRibbonsFunc: (Int, Long) => Unit = setupAvatarAwardMessageDelivery private[session] var populateAvatarAwardRibbonsFunc: (Int, Long) => Unit = setupAvatarAwardMessageDelivery
private[session] var setAvatar: Boolean = false private[session] var setAvatar: Boolean = false
private[session] var avatarActive: Boolean = false
private[session] var reviveTimer: Cancellable = Default.Cancellable private[session] var reviveTimer: Cancellable = Default.Cancellable
private[session] var respawnTimer: Cancellable = Default.Cancellable private[session] var respawnTimer: Cancellable = Default.Cancellable
private var queuedActivities: Seq[SpawnOperations.ActivityQueuedTask] = Seq()
private var initialActivityDelay: Int = 4
private var nextActivityDelay: Int = 0
private var statisticsPacketFunc: () => Unit = loginAvatarStatisticsFields private var statisticsPacketFunc: () => Unit = loginAvatarStatisticsFields
/* packets */ /* packets */
@ -1984,16 +2001,19 @@ class ZoningOperations(
} }
val noFriendlyPlayersInZone = friendlyPlayersInZone == 0 val noFriendlyPlayersInZone = friendlyPlayersInZone == 0
if (inZone.map.cavern) { if (inZone.map.cavern) {
loginChatMessage.addOne("@reset_sanctuary_locked") enqueueNewActivity(ActivityQueuedTask(
//You have been returned to the sanctuary because the location you logged out is not available. SpawnOperations.sendEventMessage(ChatMsg(ChatMessageType.CMT_QUIT, "@reset_sanctuary_locked")), 20)
) //You have been returned to the sanctuary because the location you logged out is not available.
player.Zone = Zone.Nowhere player.Zone = Zone.Nowhere
} else if (ourBuildings.isEmpty && (amsSpawnPoints.isEmpty || noFriendlyPlayersInZone)) { } else if (ourBuildings.isEmpty && (amsSpawnPoints.isEmpty || noFriendlyPlayersInZone)) {
loginChatMessage.addOne("@reset_sanctuary_locked") enqueueNewActivity(ActivityQueuedTask(
//You have been returned to the sanctuary because the location you logged out is not available. SpawnOperations.sendEventMessage(ChatMsg(ChatMessageType.CMT_QUIT, "@reset_sanctuary_locked")), 20)
) //You have been returned to the sanctuary because the location you logged out is not available.
player.Zone = Zone.Nowhere player.Zone = Zone.Nowhere
} else if (friendlyPlayersInZone > 137 || playersInZone.size > 413) { } else if (friendlyPlayersInZone > 137 || playersInZone.size > 413) {
loginChatMessage.addOne("@reset_sanctuary_full") enqueueNewActivity(ActivityQueuedTask(
//You have been returned to the sanctuary because the zone you logged out on is full. SpawnOperations.sendEventMessage(ChatMsg(ChatMessageType.CMT_QUIT, "@reset_sanctuary_full")), 20)
) //You have been returned to the sanctuary because the zone you logged out on is full.
player.Zone = Zone.Nowhere player.Zone = Zone.Nowhere
} else { } else {
val inBuildingSOI = buildings.filter { b => val inBuildingSOI = buildings.filter { b =>
@ -2010,8 +2030,9 @@ class ZoningOperations(
} }
} else { } else {
if (noFriendlyPlayersInZone) { if (noFriendlyPlayersInZone) {
loginChatMessage.addOne("@reset_sanctuary_inactive") enqueueNewActivity(ActivityQueuedTask(
//You have been returned to the sanctuary because the location you logged out is not available. SpawnOperations.sendEventMessage(ChatMsg(ChatMessageType.CMT_QUIT, "@reset_sanctuary_inactive")), 20)
) //You have been returned to the sanctuary because the location you logged out is not available.
player.Zone = Zone.Nowhere player.Zone = Zone.Nowhere
} }
} }
@ -2019,8 +2040,9 @@ class ZoningOperations(
} }
} else { } else {
//player is dead; go back to sanctuary //player is dead; go back to sanctuary
loginChatMessage.addOne("@reset_sanctuary_inactive") enqueueNewActivity(ActivityQueuedTask(
//You have been returned to the sanctuary because the location you logged out is not available. SpawnOperations.sendEventMessage(ChatMsg(ChatMessageType.CMT_QUIT, "@reset_sanctuary_inactive")), 20)
) //You have been returned to the sanctuary because the location you logged out is not available.
player.Zone = Zone.Nowhere player.Zone = Zone.Nowhere
} }
@ -2118,11 +2140,15 @@ class ZoningOperations(
zoningType = Zoning.Method.Login zoningType = Zoning.Method.Login
response match { response match {
case Some((zone, spawnPoint)) => case Some((zone, spawnPoint)) =>
loginChatMessage.addOne("@login_reposition_to_friendly_facility") //Your previous location was held by the enemy. You have been moved to the nearest friendly facility. enqueueNewActivity(ActivityQueuedTask(
SpawnOperations.sendEventMessage(ChatMsg(ChatMessageType.CMT_QUIT, "@login_reposition_to_friendly_facility")), 20)
) //Your previous location was held by the enemy. You have been moved to the nearest friendly facility.
val (pos, ori) = spawnPoint.SpecificPoint(player) val (pos, ori) = spawnPoint.SpecificPoint(player)
LoadZonePhysicalSpawnPoint(zone.id, pos, ori, respawnTime = 0 seconds, Some(spawnPoint)) LoadZonePhysicalSpawnPoint(zone.id, pos, ori, respawnTime = 0 seconds, Some(spawnPoint))
case _ => case _ =>
loginChatMessage.addOne("@login_reposition_to_sanctuary") //Your previous location was held by the enemy. As there were no operational friendly facilities on that continent, you have been brought back to your Sanctuary. enqueueNewActivity(ActivityQueuedTask(
SpawnOperations.sendEventMessage(ChatMsg(ChatMessageType.CMT_QUIT, "@login_reposition_to_sanctuary")), 20)
) //Your previous location was held by the enemy. As there were no operational friendly facilities on that continent, you have been brought back to your Sanctuary.
RequestSanctuaryZoneSpawn(player, player.Zone.Number) RequestSanctuaryZoneSpawn(player, player.Zone.Number)
} }
@ -2899,6 +2925,7 @@ class ZoningOperations(
respawnTimer.cancel() respawnTimer.cancel()
reviveTimer.cancel() reviveTimer.cancel()
deadState = DeadState.RespawnTime deadState = DeadState.RespawnTime
avatarActive = false
sendResponse( sendResponse(
AvatarDeadStateMessage( AvatarDeadStateMessage(
DeadState.RespawnTime, DeadState.RespawnTime,
@ -3268,7 +3295,7 @@ class ZoningOperations(
tavatar.scorecard.CurrentLife.prior.isEmpty && /* no revives */ tavatar.scorecard.CurrentLife.prior.isEmpty && /* no revives */
tplayer.History.size == 1 /* did nothing but come into existence */ tplayer.History.size == 1 /* did nothing but come into existence */
) { ) {
ZoningOperations.reportProgressionSystem(context.self) enqueueNewActivity(ActivityQueuedTask(ZoningOperations.reportProgressionSystem, 2))
} }
} }
} }
@ -3529,8 +3556,6 @@ class ZoningOperations(
*/ */
def TurnCounterLogin(guid: PlanetSideGUID): Unit = { def TurnCounterLogin(guid: PlanetSideGUID): Unit = {
NormalTurnCounter(guid) NormalTurnCounter(guid)
loginChatMessage.foreach { msg => sendResponse(ChatMsg(zoningChatMessageType, wideContents=false, "", msg, None)) }
loginChatMessage.clear()
CancelZoningProcess() CancelZoningProcess()
sessionLogic.turnCounterFunc = NormalTurnCounter sessionLogic.turnCounterFunc = NormalTurnCounter
} }
@ -3741,6 +3766,7 @@ class ZoningOperations(
nextSpawnPoint = Some(obj) //set fallback nextSpawnPoint = Some(obj) //set fallback
zoningStatus = Zoning.Status.Deconstructing zoningStatus = Zoning.Status.Deconstructing
player.allowInteraction = false player.allowInteraction = false
avatarActive = false
if (player.death_by == 0) { if (player.death_by == 0) {
player.death_by = 1 player.death_by = 1
} }
@ -3775,7 +3801,7 @@ class ZoningOperations(
private def usingSpawnTubeAnimation(): Unit = { private def usingSpawnTubeAnimation(): Unit = {
getSpawnTubeOwner getSpawnTubeOwner
.collect { case (sp, owner @ Some(_)) => (sp, owner) } .collect { case (sp, owner@Some(_)) => (sp, owner) }
.collect { .collect {
case (sp, Some(_: Vehicle)) => case (sp, Some(_: Vehicle)) =>
ZoningOperations.usingVehicleSpawnTubeAnimation(sp.Zone, sp.WhichSide, sp.Faction, player.Position, sp.Orientation, List(player.Name)) ZoningOperations.usingVehicleSpawnTubeAnimation(sp.Zone, sp.WhichSide, sp.Faction, player.Position, sp.Orientation, List(player.Name))
@ -3783,6 +3809,84 @@ class ZoningOperations(
ZoningOperations.usingFacilitySpawnTubeAnimation(sp.Zone, sp.WhichSide, sp.Faction, player.Position, sp.Orientation, List(player.Name)) ZoningOperations.usingFacilitySpawnTubeAnimation(sp.Zone, sp.WhichSide, sp.Faction, player.Position, sp.Orientation, List(player.Name))
} }
} }
def startEnqueueSquadMessages: Boolean = {
sessionLogic.zoning.zoneReload && sessionLogic.zoning.spawn.setAvatar && player.isAlive
}
def enqueueNewActivity(newTasking: SpawnOperations.ActivityQueuedTask): Unit = {
if (avatarActive && queuedActivities.isEmpty) {
nextActivityDelay = initialActivityDelay
}
queuedActivities = queuedActivities :+ newTasking
}
def tryQueuedActivity(pos1: Vector3, pos2: Vector3, distanceSquared: Float = 1f) : Unit = {
if (!avatarActive) {
if (Vector3.DistanceSquared(pos1, pos2) > distanceSquared) {
startExecutingQueuedActivity()
}
} else {
countDownUntilQueuedActivity()
}
}
def tryQueuedActivity(vel: Option[Vector3]) : Unit = {
if (!avatarActive) {
if (WorldEntity.isMoving(vel)) {
startExecutingQueuedActivity()
}
} else {
countDownUntilQueuedActivity()
}
}
def tryQueuedActivity() : Unit = {
if (!avatarActive) {
startExecutingQueuedActivity()
} else {
countDownUntilQueuedActivity()
}
}
def addDelayBeforeNextQueuedActivity(delay: Int): Unit = {
if (nextActivityDelay == 0) {
nextActivityDelay = initialActivityDelay
} else if (queuedActivities.nonEmpty) {
val lastActivity = queuedActivities.last
if (lastActivity.delayBeforeNext < delay) {
queuedActivities = queuedActivities.dropRight(1) :+ lastActivity.copy(delayBeforeNext = delay)
}
}
}
private def startExecutingQueuedActivity(): Unit = {
avatarActive = startEnqueueSquadMessages
if (nextActivityDelay == 0) {
nextActivityDelay = initialActivityDelay
}
}
private def countDownUntilQueuedActivity(): Unit = {
if (nextActivityDelay > 0) {
nextActivityDelay -= 1
} else if (queuedActivities.nonEmpty) {
val task :: rest = queuedActivities
queuedActivities = if (task.repeat > 0) {
task.copy(repeat = task.repeat - 1) +: rest //positive: repeat immediately
} else if (task.repeat < 0) {
rest :+ task.copy(repeat = task.repeat + 1) //negative: repeat after all other tasks have been completed
} else {
rest
}
nextActivityDelay = task.delayBeforeNext
task.task(sessionLogic)
}
}
def clearAllQueuedActivity(): Unit = {
queuedActivities = Seq()
}
} }
def doorsThatShouldBeClosedOrBeOpenedByRange( def doorsThatShouldBeClosedOrBeOpenedByRange(

View file

@ -6,7 +6,6 @@ import akka.pattern.ask
import akka.util.Timeout import akka.util.Timeout
import scala.collection.mutable import scala.collection.mutable
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future import scala.concurrent.Future
// //
import net.psforever.objects.{LivePlayerList, Player} import net.psforever.objects.{LivePlayerList, Player}