mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-07-13 07:24:53 +00:00
too much to itemize; still getting squads working, more or less
This commit is contained in:
parent
3e631657b8
commit
2a93e6a8be
10 changed files with 2481 additions and 1491 deletions
|
|
@ -48,13 +48,8 @@ class Avatar(private val char_id : Long, val name : String, val faction : Planet
|
||||||
private val deployables : DeployableToolbox = new DeployableToolbox
|
private val deployables : DeployableToolbox = new DeployableToolbox
|
||||||
/**
|
/**
|
||||||
* Looking For Squad:<br>
|
* Looking For Squad:<br>
|
||||||
* Used to indicate both the marque that appears underneath a player's nameplate and an actual player state<br>
|
* Indicates both a player state and the text on the marquee under the player nameplate.
|
||||||
* This `Avatar`-specific "Looking for Squad" variable
|
* Should only be valid when the player is not in a squad.
|
||||||
* 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
|
private var lfs : Boolean = false
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,7 @@ class Squad(squadId : PlanetSideGUID, alignment : PlanetSideEmpire.Value) extend
|
||||||
private var zoneId : Option[Int] = None
|
private var zoneId : Option[Int] = None
|
||||||
private var task : String = ""
|
private var task : String = ""
|
||||||
private val membership : Array[Member] = Array.fill[Member](10)(new Member)
|
private val membership : Array[Member] = Array.fill[Member](10)(new Member)
|
||||||
private val availability : Array[Boolean] = Array.fill[Boolean](10)(true)
|
private val availability : Array[Boolean] = Array.fill[Boolean](10)(elem = true)
|
||||||
private var listed : Boolean = false
|
|
||||||
private var leaderPositionIndex : Int = 0
|
|
||||||
private var autoApproveInvitationRequests : Boolean = false
|
|
||||||
private var locationFollowsSquadLead : Boolean = false
|
|
||||||
|
|
||||||
override def GUID_=(d : PlanetSideGUID) : PlanetSideGUID = GUID
|
override def GUID_=(d : PlanetSideGUID) : PlanetSideGUID = GUID
|
||||||
|
|
||||||
|
|
@ -23,14 +19,7 @@ class Squad(squadId : PlanetSideGUID, alignment : PlanetSideEmpire.Value) extend
|
||||||
|
|
||||||
def CustomZoneId : Boolean = zoneId.isDefined
|
def CustomZoneId : Boolean = zoneId.isDefined
|
||||||
|
|
||||||
def ZoneId : Int = zoneId.getOrElse({
|
def ZoneId : Int = zoneId.getOrElse(membership(0).ZoneId)
|
||||||
membership.lift(leaderPositionIndex) match {
|
|
||||||
case Some(leader) =>
|
|
||||||
leader.ZoneId
|
|
||||||
case _ =>
|
|
||||||
0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
def ZoneId_=(id : Int) : Int = {
|
def ZoneId_=(id : Int) : Int = {
|
||||||
ZoneId_=(Some(id))
|
ZoneId_=(Some(id))
|
||||||
|
|
@ -48,42 +37,12 @@ class Squad(squadId : PlanetSideGUID, alignment : PlanetSideEmpire.Value) extend
|
||||||
Task
|
Task
|
||||||
}
|
}
|
||||||
|
|
||||||
def Listed : Boolean = listed
|
|
||||||
|
|
||||||
def Listed_=(announce : Boolean) : Boolean = {
|
|
||||||
listed = announce
|
|
||||||
Listed
|
|
||||||
}
|
|
||||||
|
|
||||||
def LocationFollowsSquadLead : Boolean = locationFollowsSquadLead
|
|
||||||
|
|
||||||
def LocationFollowsSquadLead_=(follow : Boolean) : Boolean = {
|
|
||||||
locationFollowsSquadLead = follow
|
|
||||||
LocationFollowsSquadLead
|
|
||||||
}
|
|
||||||
|
|
||||||
def AutoApproveInvitationRequests : Boolean = autoApproveInvitationRequests
|
|
||||||
|
|
||||||
def AutoApproveInvitationRequests_=(autoApprove : Boolean) : Boolean = {
|
|
||||||
autoApproveInvitationRequests = autoApprove
|
|
||||||
AutoApproveInvitationRequests
|
|
||||||
}
|
|
||||||
|
|
||||||
def Membership : Array[Member] = membership
|
def Membership : Array[Member] = membership
|
||||||
|
|
||||||
def Availability : Array[Boolean] = availability
|
def Availability : Array[Boolean] = availability
|
||||||
|
|
||||||
def LeaderPositionIndex : Int = leaderPositionIndex
|
|
||||||
|
|
||||||
def LeaderPositionIndex_=(position : Int) : Int = {
|
|
||||||
if(availability.lift(position).contains(true)) {
|
|
||||||
leaderPositionIndex = position
|
|
||||||
}
|
|
||||||
LeaderPositionIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
def Leader : Member = {
|
def Leader : Member = {
|
||||||
membership(leaderPositionIndex) match {
|
membership(0) match {
|
||||||
case member if !member.Name.equals("") =>
|
case member if !member.Name.equals("") =>
|
||||||
member
|
member
|
||||||
case _ =>
|
case _ =>
|
||||||
|
|
@ -102,9 +61,7 @@ object Squad {
|
||||||
override def ZoneId_=(id : Int) : Int = 0
|
override def ZoneId_=(id : Int) : Int = 0
|
||||||
override def ZoneId_=(id : Option[Int]) : Int = 0
|
override def ZoneId_=(id : Option[Int]) : Int = 0
|
||||||
override def Task_=(assignment : String) : String = ""
|
override def Task_=(assignment : String) : String = ""
|
||||||
override def Listed_=(announce : Boolean) : Boolean = false
|
|
||||||
override def Membership : Array[Member] = Array.empty[Member]
|
override def Membership : Array[Member] = Array.empty[Member]
|
||||||
override def Availability : Array[Boolean] = Array.fill[Boolean](10)(false)
|
override def Availability : Array[Boolean] = Array.fill[Boolean](10)(false)
|
||||||
override def LeaderPositionIndex_=(position : Int) : Int = 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
// Copyright (c) 2019 PSForever
|
||||||
|
package net.psforever.objects.teamwork
|
||||||
|
|
||||||
|
import akka.actor.{Actor, ActorContext, ActorRef, Cancellable, Props}
|
||||||
|
import net.psforever.objects.DefaultCancellable
|
||||||
|
import services.teamwork.SquadService.WaypointData
|
||||||
|
import services.teamwork.SquadSwitchboard
|
||||||
|
|
||||||
|
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.
|
||||||
|
* Dispatched only once when a squad is first listed
|
||||||
|
* or when the squad leader searches for recruits by proximity or for certain roles or by invite
|
||||||
|
* or when a spontaneous squad forms,
|
||||||
|
* whichever happens first.
|
||||||
|
* Additionally, the packets are also sent when the check is made when the continent is changed (or set).
|
||||||
|
*/
|
||||||
|
private var initialAssociation : Boolean = true
|
||||||
|
/**
|
||||||
|
* na
|
||||||
|
*/
|
||||||
|
private var switchboard : ActorRef = ActorRef.noSender
|
||||||
|
/**
|
||||||
|
* Waypoint data.
|
||||||
|
* The first four slots are used for squad waypoints.
|
||||||
|
* The fifth slot is used for the squad leader experience waypoint.<br>
|
||||||
|
* <br>
|
||||||
|
* All of the waypoints constantly exist as long as the squad to which they are attached exists.
|
||||||
|
* They are merely "activated" and "deactivated."
|
||||||
|
* When "activated," the waypoint knows on which continent to appear and where on the map and in the game world to be positioned.
|
||||||
|
* Waypoints manifest in the game world as a far-off beam of light that extends into the sky
|
||||||
|
* and whose ground contact utilizes a downwards pulsating arrow.
|
||||||
|
* On the continental map and deployment map, they appear as a diamond, with a differentiating number where applicable.
|
||||||
|
* The squad leader experience rally, for example, does not have a number like the preceding four waypoints.
|
||||||
|
* @see `Start`
|
||||||
|
*/
|
||||||
|
private var waypoints : Array[WaypointData] = Array[WaypointData]()
|
||||||
|
/**
|
||||||
|
* The particular position being recruited right at the moment.
|
||||||
|
* When `None`. no highlighted searches have been indicated.
|
||||||
|
* When a positive integer or 0, indicates distributed `LookingForSquadRoleInvite` messages as recorded by `proxyInvites`.
|
||||||
|
* Only one position may bne actively recruited at a time in this case.
|
||||||
|
* When -1, indicates distributed `ProximityIvite` messages as recorded by `proxyInvites`.
|
||||||
|
* Previous efforts may or may not be forgotten if there is a switch between the two modes.
|
||||||
|
*/
|
||||||
|
private var searchForRole : Option[Int] = None
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* such as Looking For Squad checks and proximity invitation,
|
||||||
|
* the unique character identifier numbers in this list are skipped.
|
||||||
|
* Direct invitation requests from the non sqad member should remain functional.
|
||||||
|
*/
|
||||||
|
private var refusedPlayers : List[Long] = Nil
|
||||||
|
private var autoApproveInvitationRequests : Boolean = true
|
||||||
|
private var locationFollowsSquadLead : Boolean = true
|
||||||
|
|
||||||
|
private var listed : Boolean = false
|
||||||
|
|
||||||
|
private lazy val channel : String = s"${Squad.Faction}-Squad${Squad.GUID.guid}"
|
||||||
|
|
||||||
|
def Start(implicit context : ActorContext) : SquadFeatures = {
|
||||||
|
switchboard = context.actorOf(Props[SquadSwitchboard], s"squad${Squad.GUID.guid}")
|
||||||
|
waypoints = Array.fill[WaypointData](5)(new WaypointData())
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def Stop : SquadFeatures = {
|
||||||
|
switchboard ! akka.actor.PoisonPill
|
||||||
|
switchboard = Actor.noSender
|
||||||
|
waypoints = Array.empty
|
||||||
|
requestInvitePrompt.cancel
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def InitialAssociation : Boolean = initialAssociation
|
||||||
|
|
||||||
|
def InitialAssociation_=(assoc : Boolean) : Boolean = {
|
||||||
|
initialAssociation = assoc
|
||||||
|
InitialAssociation
|
||||||
|
}
|
||||||
|
|
||||||
|
def Switchboard : ActorRef = switchboard
|
||||||
|
|
||||||
|
def Waypoints : Array[WaypointData] = waypoints
|
||||||
|
|
||||||
|
def SearchForRole : Option[Int] = searchForRole
|
||||||
|
|
||||||
|
def SearchForRole_=(role : Int) : Option[Int] = SearchForRole_=(Some(role))
|
||||||
|
|
||||||
|
def SearchForRole_=(role : Option[Int]) : Option[Int] = {
|
||||||
|
searchForRole = role
|
||||||
|
SearchForRole
|
||||||
|
}
|
||||||
|
|
||||||
|
def ProxyInvites : List[Long] = proxyInvites
|
||||||
|
|
||||||
|
def ProxyInvites_=(list : List[Long]) : List[Long] = {
|
||||||
|
proxyInvites = list
|
||||||
|
ProxyInvites
|
||||||
|
}
|
||||||
|
|
||||||
|
def Refuse : List[Long] = refusedPlayers
|
||||||
|
|
||||||
|
def Refuse_=(charId : Long) : List[Long] = {
|
||||||
|
Refuse_=(List(charId))
|
||||||
|
}
|
||||||
|
|
||||||
|
def Refuse_=(list : List[Long]) : List[Long] = {
|
||||||
|
refusedPlayers = list ++ refusedPlayers
|
||||||
|
Refuse
|
||||||
|
}
|
||||||
|
|
||||||
|
def LocationFollowsSquadLead : Boolean = locationFollowsSquadLead
|
||||||
|
|
||||||
|
def LocationFollowsSquadLead_=(follow : Boolean) : Boolean = {
|
||||||
|
locationFollowsSquadLead = follow
|
||||||
|
LocationFollowsSquadLead
|
||||||
|
}
|
||||||
|
|
||||||
|
def AutoApproveInvitationRequests : Boolean = autoApproveInvitationRequests
|
||||||
|
|
||||||
|
def AutoApproveInvitationRequests_=(autoApprove : Boolean) : Boolean = {
|
||||||
|
autoApproveInvitationRequests = autoApprove
|
||||||
|
AutoApproveInvitationRequests
|
||||||
|
}
|
||||||
|
|
||||||
|
def Listed : Boolean = listed
|
||||||
|
|
||||||
|
def Listed_=(announce : Boolean) : Boolean = {
|
||||||
|
listed = announce
|
||||||
|
Listed
|
||||||
|
}
|
||||||
|
|
||||||
|
def ToChannel : String = channel
|
||||||
|
|
||||||
|
def Prompt : Cancellable = requestInvitePrompt
|
||||||
|
|
||||||
|
def Prompt_=(callback: Cancellable) : Cancellable = {
|
||||||
|
if(requestInvitePrompt.isCancelled) {
|
||||||
|
requestInvitePrompt = callback
|
||||||
|
}
|
||||||
|
Prompt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -118,7 +118,10 @@ import scodec.codecs._
|
||||||
* `27 - PA_JAMMED - plays jammed buzzing sound`<br>
|
* `27 - PA_JAMMED - plays jammed buzzing sound`<br>
|
||||||
* `28 - PA_IMPLANT_ACTIVE - Plays implant sounds. Valid values seem to be up to 20.`<br>
|
* `28 - PA_IMPLANT_ACTIVE - Plays implant sounds. Valid values seem to be up to 20.`<br>
|
||||||
* `29 - PA_VAPORIZED - Visible ?! That's not the cloaked effect, Maybe for spectator mode ?. Value is 0 to visible, 1 to invisible.`<br>
|
* `29 - PA_VAPORIZED - Visible ?! That's not the cloaked effect, Maybe for spectator mode ?. Value is 0 to visible, 1 to invisible.`<br>
|
||||||
* `31 - Info under avatar name : 0 = LFS, 1 = Looking For Squad Members`<br>
|
* `31 - Looking for Squad info (marquee and ui):<br>
|
||||||
|
* ` - 0 is LFS`<br>
|
||||||
|
* ` - 1 is LFSM (Looking for Squad Members)`<br>
|
||||||
|
* ` - n is the supplemental squad identifier number; same as "LFS;" for the leader, sets "LFSM" after the first manual flagging`<br>
|
||||||
* `32 - Info under avatar name : 0 = Looking For Squad Members, 1 = LFS`<br>
|
* `32 - Info under avatar name : 0 = Looking For Squad Members, 1 = LFS`<br>
|
||||||
* `35 - BR. Value is the BR`<br>
|
* `35 - BR. Value is the BR`<br>
|
||||||
* `36 - CR. Value is the CR`<br>
|
* `36 - CR. Value is the CR`<br>
|
||||||
|
|
|
||||||
|
|
@ -360,6 +360,7 @@ object SquadAction{
|
||||||
* `17` - Set List Squad (ui)<br>
|
* `17` - Set List Squad (ui)<br>
|
||||||
* `18` - UNKNOWN<br>
|
* `18` - UNKNOWN<br>
|
||||||
* `26` - Reset All<br>
|
* `26` - Reset All<br>
|
||||||
|
* `32` - UNKNOWN<br>
|
||||||
* `35` - Cancel Squad Search<br>
|
* `35` - Cancel Squad Search<br>
|
||||||
* `39` - No Squad Search Results<br>
|
* `39` - No Squad Search Results<br>
|
||||||
* `41` - Cancel Find<br>
|
* `41` - Cancel Find<br>
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,14 @@ package services.teamwork
|
||||||
|
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
import net.psforever.packet.game._
|
import net.psforever.packet.game._
|
||||||
import net.psforever.types.{SquadRequestType, Vector3}
|
import net.psforever.types.{PlanetSideEmpire, SquadRequestType, Vector3}
|
||||||
|
|
||||||
object SquadAction {
|
object SquadAction {
|
||||||
trait Action
|
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 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 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 : Int, unk : Option[Long], waypoint_info : Option[WaypointInfo]) extends Action
|
final case class Waypoint(event_type : WaypointEventAction.Value, waypoint_type : Int, unk : Option[Long], waypoint_info : Option[WaypointInfo]) extends Action
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ object SquadResponse {
|
||||||
|
|
||||||
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 Membership(request_type : SquadResponseType.Value, unk1 : Int, unk2 : Int, unk3 : Long, unk4 : Option[Long], player_name : String, unk5 : Boolean, unk6 : Option[Option[String]]) extends Response //see SquadMembershipResponse
|
||||||
final case class Invite(from_char_id : Long, to_char_id : Long, name : String) extends Response
|
final case class Invite(from_char_id : Long, to_char_id : Long, name : String) extends Response
|
||||||
final case class WantsSquadPosition(bid_name : String) extends Response
|
final case class WantsSquadPosition(leader_char_id : Long, bid_name : String) extends Response
|
||||||
final case class Join(squad : Squad, positionsToUpdate : List[Int]) 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 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 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 AssignMember(squad : Squad, from_index : Int, to_index : Int) extends Response
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,14 @@
|
||||||
// Copyright (c) 2019 PSForever
|
// Copyright (c) 2019 PSForever
|
||||||
package services.teamwork
|
package services.teamwork
|
||||||
|
|
||||||
import net.psforever.packet.game.SquadInfo
|
|
||||||
import services.GenericEventBusMsg
|
import services.GenericEventBusMsg
|
||||||
|
|
||||||
final case class SquadServiceResponse(toChannel : String, response : SquadResponse.Response) extends GenericEventBusMsg
|
final case class SquadServiceResponse(toChannel : String, exclude : Iterable[Long], response : SquadResponse.Response) extends GenericEventBusMsg
|
||||||
|
|
||||||
|
object SquadServiceResponse {
|
||||||
|
def apply(toChannel : String, response : SquadResponse.Response) : SquadServiceResponse =
|
||||||
|
SquadServiceResponse(toChannel, Nil, response)
|
||||||
|
|
||||||
|
def apply(toChannel : String, exclude : Long, response : SquadResponse.Response) : SquadServiceResponse =
|
||||||
|
SquadServiceResponse(toChannel, Seq(exclude), response)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
var whenUsedLastKit : Long = 0
|
var whenUsedLastKit : Long = 0
|
||||||
val projectiles : Array[Option[Projectile]] = Array.fill[Option[Projectile]](Projectile.RangeUID - Projectile.BaseUID)(None)
|
val projectiles : Array[Option[Projectile]] = Array.fill[Option[Projectile]](Projectile.RangeUID - Projectile.BaseUID)(None)
|
||||||
var drawDeloyableIcon : PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons
|
var drawDeloyableIcon : PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons
|
||||||
|
var updateSquad : () => Unit = NoSquadUpdates
|
||||||
var recentTeleportAttempt : Long = 0
|
var recentTeleportAttempt : Long = 0
|
||||||
var lastTerminalOrderFulfillment : Boolean = true /**
|
var lastTerminalOrderFulfillment : Boolean = true /**
|
||||||
* used during zone transfers to maintain reference to seated vehicle (which does not yet exist in the new zone)
|
* used during zone transfers to maintain reference to seated vehicle (which does not yet exist in the new zone)
|
||||||
|
|
@ -118,12 +119,18 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
*/
|
*/
|
||||||
var interstellarFerryTopLevelGUID : Option[PlanetSideGUID] = None
|
var interstellarFerryTopLevelGUID : Option[PlanetSideGUID] = None
|
||||||
val squadUI : LongMap[SquadUIElement] = new LongMap[SquadUIElement]()
|
val squadUI : LongMap[SquadUIElement] = new LongMap[SquadUIElement]()
|
||||||
|
val squad_supplement_id : Int = 11
|
||||||
/**
|
/**
|
||||||
* `AvatarConverter` can only rely on the `Avatar`-local Looking For Squad variable.
|
* `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.
|
* When joining or creating a squad, the original state of the avatar's local LFS variable is blanked.
|
||||||
* Upon leaving or disbanding a squad, this value is restored to the avatar's LFS variable.
|
* This `WSA`-local variable is then used to indicate the ongoing state of the LFS UI component,
|
||||||
|
* now called "Looking for Squad Member."
|
||||||
|
* Upon leaving or disbanding a squad, this value is made false.
|
||||||
|
* Control switching between the `Avatar`-local and the `WSA`-local variable is contingent on `squadUI` being populated.
|
||||||
*/
|
*/
|
||||||
var lfs : Boolean = false
|
var lfs : Boolean = false
|
||||||
|
var squadChannel : Option[String] = None
|
||||||
|
var squadSetup : () => Unit = FirstTimeSquadSetup
|
||||||
|
|
||||||
var amsSpawnPoints : List[SpawnPoint] = Nil
|
var amsSpawnPoints : List[SpawnPoint] = Nil
|
||||||
var clientKeepAlive : Cancellable = DefaultCancellable.obj
|
var clientKeepAlive : Cancellable = DefaultCancellable.obj
|
||||||
|
|
@ -168,9 +175,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
) ++ player.Inventory.Items)
|
) ++ player.Inventory.Items)
|
||||||
.filterNot({ case InventoryItem(obj, _) => obj.isInstanceOf[BoomerTrigger] || obj.isInstanceOf[Telepad] })
|
.filterNot({ case InventoryItem(obj, _) => obj.isInstanceOf[BoomerTrigger] || obj.isInstanceOf[Telepad] })
|
||||||
//put any temporary value back into the avatar
|
//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)
|
//TODO final character save before doing any of this (use equipment)
|
||||||
continent.Population ! Zone.Population.Release(avatar)
|
continent.Population ! Zone.Population.Release(avatar)
|
||||||
if(player.isAlive) {
|
if(player.isAlive) {
|
||||||
|
|
@ -356,216 +360,221 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
case VehicleServiceResponse(toChannel, guid, reply) =>
|
case VehicleServiceResponse(toChannel, guid, reply) =>
|
||||||
HandleVehicleServiceResponse(toChannel, guid, reply)
|
HandleVehicleServiceResponse(toChannel, guid, reply)
|
||||||
|
|
||||||
case SquadServiceResponse(toChannel, response) =>
|
case SquadServiceResponse(_, excluded, response) =>
|
||||||
response match {
|
if(!excluded.exists(_ == avatar.CharId)) {
|
||||||
case SquadResponse.ListSquadFavorite(line, task) =>
|
response match {
|
||||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), line, SquadAction.ListSquadFavorite(task)))
|
case SquadResponse.ListSquadFavorite(line, task) =>
|
||||||
|
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), line, SquadAction.ListSquadFavorite(task)))
|
||||||
|
|
||||||
case SquadResponse.InitList(infos) if infos.nonEmpty =>
|
case SquadResponse.InitList(infos) =>
|
||||||
sendResponse(ReplicationStreamMessage(infos))
|
sendResponse(ReplicationStreamMessage(infos))
|
||||||
|
|
||||||
case SquadResponse.UpdateList(infos) if infos.nonEmpty =>
|
case SquadResponse.UpdateList(infos) if infos.nonEmpty =>
|
||||||
val o = ReplicationStreamMessage(6, None,
|
|
||||||
infos.map { case (index, squadInfo) =>
|
|
||||||
SquadListing(index, squadInfo)
|
|
||||||
}.toVector
|
|
||||||
)
|
|
||||||
sendResponse(
|
|
||||||
ReplicationStreamMessage(6, None,
|
|
||||||
infos.map { case (index, squadInfo) =>
|
|
||||||
SquadListing(index, squadInfo)
|
|
||||||
}.toVector
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
case SquadResponse.RemoveFromList(infos) if infos.nonEmpty =>
|
|
||||||
sendResponse(
|
|
||||||
ReplicationStreamMessage(1, None,
|
|
||||||
infos.map { index =>
|
|
||||||
SquadListing(index, None)
|
|
||||||
}.toVector
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
case SquadResponse.Detail(guid, detail) =>
|
|
||||||
sendResponse(SquadDetailDefinitionUpdateMessage(guid, detail))
|
|
||||||
|
|
||||||
case SquadResponse.AssociateWithSquad(squad_guid) =>
|
|
||||||
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.AssociateWithSquad()))
|
|
||||||
|
|
||||||
case SquadResponse.SetListSquad(squad_guid) =>
|
|
||||||
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.SetListSquad()))
|
|
||||||
|
|
||||||
case SquadResponse.Unknown17(squad, char_id) =>
|
|
||||||
sendResponse(
|
|
||||||
SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(33))
|
|
||||||
)
|
|
||||||
|
|
||||||
case SquadResponse.Membership(request_type, unk1, unk2, char_id, opt_char_id, player_name, unk5, unk6) =>
|
|
||||||
val name = request_type match {
|
|
||||||
case SquadResponseType.Invite if unk5 =>
|
|
||||||
//player_name is our name; the name of the player indicated by unk3 is needed
|
|
||||||
LivePlayerList.WorldPopulation({ case (_, a : Avatar) => char_id == a.CharId }).headOption match {
|
|
||||||
case Some(player) =>
|
|
||||||
player.name
|
|
||||||
case None =>
|
|
||||||
player_name
|
|
||||||
}
|
|
||||||
case _ =>
|
|
||||||
player_name
|
|
||||||
}
|
|
||||||
sendResponse(SquadMembershipResponse(request_type, unk1, unk2, char_id, opt_char_id, name, unk5, unk6))
|
|
||||||
|
|
||||||
case SquadResponse.Invite(from_char_id, to_char_id, name) =>
|
|
||||||
sendResponse(SquadMembershipResponse(SquadResponseType.Invite, 0, 0, from_char_id, Some(to_char_id), s"$name", false, Some(None)))
|
|
||||||
|
|
||||||
case SquadResponse.WantsSquadPosition(name : String) =>
|
|
||||||
sendResponse(
|
|
||||||
ChatMsg(
|
|
||||||
ChatMessageType.CMT_TELL, true, "",
|
|
||||||
s"\\#6[SQUAD] \\#3$name\\#6 would like to join your squad. (respond with \\#3/accept\\#6 or \\#3/reject\\#6)",
|
|
||||||
None
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
case SquadResponse.Join(squad, positionsToUpdate) =>
|
|
||||||
val leader = squad.Leader
|
|
||||||
val id = 11
|
|
||||||
val membershipPositions = squad.Membership
|
|
||||||
.zipWithIndex
|
|
||||||
.filter { case (_, index ) => positionsToUpdate.contains(index) }
|
|
||||||
membershipPositions.find({ case(member, _) => member.CharId == avatar.CharId }) match {
|
|
||||||
case Some((ourMember, ourIndex)) =>
|
|
||||||
//we are joining the squad
|
|
||||||
//load each member's entry (our own too)
|
|
||||||
membershipPositions.foreach { case(member, index) =>
|
|
||||||
sendResponse(SquadMemberEvent.Add(id, member.CharId, index, member.Name, member.ZoneId, unk7 = 0))
|
|
||||||
squadUI(member.CharId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
|
||||||
}
|
|
||||||
//initialization
|
|
||||||
sendResponse(SquadMemberEvent.Add(id, ourMember.CharId, ourIndex, ourMember.Name, ourMember.ZoneId, unk7 = 0)) //repeat of our entry
|
|
||||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 31, id)) //associate with squad?
|
|
||||||
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
|
|
||||||
membershipPositions.foreach { case(member, index) =>
|
|
||||||
sendResponse(SquadMemberEvent.Add(id, member.CharId, index, member.Name, member.ZoneId, unk7 = 0))
|
|
||||||
squadUI(member.CharId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//send an initial dummy update for map icon(s)
|
|
||||||
sendResponse(SquadState(PlanetSideGUID(id),
|
|
||||||
membershipPositions
|
|
||||||
.filterNot { case (member, _) => member.CharId == avatar.CharId }
|
|
||||||
.map{ case (member, _) => SquadStateInfo(member.CharId, member.Health, member.Armor, member.Position, 2,2, false, 429, None,None) }
|
|
||||||
.toList
|
|
||||||
))
|
|
||||||
log.info(s"SquadCards: ${squadUI.map { case(id, card) => s"[${card.index}:${card.name}/$id]" }.mkString}")
|
|
||||||
|
|
||||||
case SquadResponse.Leave(_, positionsToUpdate) =>
|
|
||||||
val id = 11
|
|
||||||
positionsToUpdate.find({ case(member, _) => member == avatar.CharId }) match {
|
|
||||||
case Some((ourMember, ourIndex)) =>
|
|
||||||
//we are leaving the squad
|
|
||||||
//remove each member's entry (our own too)
|
|
||||||
positionsToUpdate.foreach { case(member, index) =>
|
|
||||||
sendResponse(SquadMemberEvent.Remove(id, member, index))
|
|
||||||
squadUI.remove(member)
|
|
||||||
}
|
|
||||||
//uninitialize
|
|
||||||
sendResponse(SquadMemberEvent.Remove(id, ourMember, ourIndex)) //repeat of our entry
|
|
||||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 31, 0)) //disassociate with squad?
|
|
||||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, 0)) //disassociate with member position in squad?
|
|
||||||
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) =>
|
|
||||||
sendResponse(SquadMemberEvent.Remove(id, member, index))
|
|
||||||
squadUI.remove(member)
|
|
||||||
}
|
|
||||||
log.info(s"SquadCards: ${squadUI.map { case(id, card) => s"[${card.index}:${card.name}/$id]" }.mkString}")
|
|
||||||
}
|
|
||||||
|
|
||||||
case SquadResponse.UpdateMembers(squad, positions) =>
|
|
||||||
import services.teamwork.SquadAction.{Update => SAUpdate}
|
|
||||||
val id = 11
|
|
||||||
val pairedEntries = positions.collect {
|
|
||||||
case entry if squadUI.contains(entry.char_id) =>
|
|
||||||
(entry, 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(11, entry.char_id, element.index, entry.zone_number))
|
|
||||||
squadUI(entry.char_id) = SquadUIElement(element.name, 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
|
|
||||||
squadUI(entry.char_id) = SquadUIElement(element.name, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
|
|
||||||
entry
|
|
||||||
})
|
|
||||||
.filterNot(_.char_id == avatar.CharId) //we want to update our backend, but not our frontend
|
|
||||||
if(updatedEntries.nonEmpty) {
|
|
||||||
sendResponse(
|
sendResponse(
|
||||||
SquadState(
|
ReplicationStreamMessage(6, None,
|
||||||
PlanetSideGUID(id),
|
infos.map { case (index, squadInfo) =>
|
||||||
updatedEntries.map { entry => SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos, 2,2, false, 429, None,None)}
|
SquadListing(index, squadInfo)
|
||||||
|
}.toVector
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
case SquadResponse.AssignMember(squad, from_index, to_index) =>
|
case SquadResponse.RemoveFromList(infos) if infos.nonEmpty =>
|
||||||
//we've already swapped position internally; now we swap the cards
|
sendResponse(
|
||||||
SwapSquadUIElements(squad, from_index, to_index)
|
ReplicationStreamMessage(1, None,
|
||||||
log.info(s"SquadCards: ${squadUI.map { case(id, card) => s"[${card.index}:${card.name}/$id]" }.mkString}")
|
infos.map { index =>
|
||||||
|
SquadListing(index, None)
|
||||||
|
}.toVector
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
case SquadResponse.PromoteMember(squad, char_id, from_index, to_index) =>
|
case SquadResponse.Detail(guid, detail) =>
|
||||||
//promotion will swap cards visually, but we must fix the backend
|
sendResponse(SquadDetailDefinitionUpdateMessage(guid, detail))
|
||||||
val id = 11
|
|
||||||
sendResponse(SquadMemberEvent.Promote(id, char_id))
|
|
||||||
SwapSquadUIElements(squad, from_index, to_index)
|
|
||||||
log.info(s"SquadCards: ${squadUI.map { case(id, card) => s"[${card.index}:${card.name}/$id]" }.mkString}")
|
|
||||||
|
|
||||||
case SquadResponse.SquadSearchResults() =>
|
case SquadResponse.AssociateWithSquad(squad_guid) =>
|
||||||
//I don't actually know how to return search results
|
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.AssociateWithSquad()))
|
||||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.NoSquadSearchResults()))
|
|
||||||
|
|
||||||
case SquadResponse.InitWaypoints(char_id, waypoints) =>
|
case SquadResponse.SetListSquad(squad_guid) =>
|
||||||
val id = 11
|
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.SetListSquad()))
|
||||||
StartBundlingPackets()
|
|
||||||
waypoints.foreach { case (waypoint_type, info, unk) =>
|
|
||||||
sendResponse(SquadWaypointEvent.Add(id, char_id, waypoint_type, WaypointEvent(info.zone_number, info.pos, unk)))
|
|
||||||
}
|
|
||||||
StopBundlingPackets()
|
|
||||||
|
|
||||||
case SquadResponse.WaypointEvent(WaypointEventAction.Add, char_id, waypoint_type, _, Some(info), unk) =>
|
case SquadResponse.Unknown17(squad, char_id) =>
|
||||||
val id = 11
|
sendResponse(
|
||||||
sendResponse(SquadWaypointEvent.Add(id, char_id, waypoint_type, WaypointEvent(info.zone_number, info.pos, unk)))
|
SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(33))
|
||||||
|
)
|
||||||
|
|
||||||
case SquadResponse.WaypointEvent(WaypointEventAction.Remove, char_id, waypoint_type, _, _, _) =>
|
case SquadResponse.Membership(request_type, unk1, unk2, char_id, opt_char_id, player_name, unk5, unk6) =>
|
||||||
val id = 11
|
val name = request_type match {
|
||||||
sendResponse(SquadWaypointEvent.Remove(id, char_id, waypoint_type))
|
case SquadResponseType.Invite if unk5 =>
|
||||||
|
//player_name is our name; the name of the player indicated by unk3 is needed
|
||||||
|
LivePlayerList.WorldPopulation({ case (_, a : Avatar) => char_id == a.CharId }).headOption match {
|
||||||
|
case Some(player) =>
|
||||||
|
player.name
|
||||||
|
case None =>
|
||||||
|
player_name
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
|
player_name
|
||||||
|
}
|
||||||
|
sendResponse(SquadMembershipResponse(request_type, unk1, unk2, char_id, opt_char_id, name, unk5, unk6))
|
||||||
|
|
||||||
case _ => ;
|
case SquadResponse.Invite(from_char_id, to_char_id, name) =>
|
||||||
|
sendResponse(SquadMembershipResponse(SquadResponseType.Invite, 0, 0, from_char_id, Some(to_char_id), s"$name", false, Some(None)))
|
||||||
|
|
||||||
|
case SquadResponse.WantsSquadPosition(_, name) =>
|
||||||
|
sendResponse(
|
||||||
|
ChatMsg(
|
||||||
|
ChatMessageType.CMT_SQUAD, true, name,
|
||||||
|
s"\\#6 would like to join your squad. (respond with \\#3/accept\\#6 or \\#3/reject\\#6)",
|
||||||
|
None
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
case SquadResponse.Join(squad, positionsToUpdate, toChannel) =>
|
||||||
|
val leader = squad.Leader
|
||||||
|
val membershipPositions = squad.Membership
|
||||||
|
.zipWithIndex
|
||||||
|
.filter { case (_, index ) => positionsToUpdate.contains(index) }
|
||||||
|
membershipPositions.find({ case(member, _) => member.CharId == avatar.CharId }) match {
|
||||||
|
case Some((ourMember, ourIndex)) =>
|
||||||
|
//we are joining the squad
|
||||||
|
//load each member's entry (our own too)
|
||||||
|
membershipPositions.foreach { case(member, index) =>
|
||||||
|
sendResponse(SquadMemberEvent.Add(squad_supplement_id, member.CharId, index, member.Name, member.ZoneId, unk7 = 0))
|
||||||
|
squadUI(member.CharId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
||||||
|
}
|
||||||
|
//repeat our entry
|
||||||
|
sendResponse(SquadMemberEvent.Add(squad_supplement_id, ourMember.CharId, ourIndex, ourMember.Name, ourMember.ZoneId, unk7 = 0)) //repeat of our entry
|
||||||
|
//turn lfs off
|
||||||
|
val factionOnContinentChannel = s"${continent.Id}/${player.Faction}"
|
||||||
|
if(avatar.LFS) {
|
||||||
|
avatar.LFS = false
|
||||||
|
sendResponse(PlanetsideAttributeMessage(player.GUID, 53, 0))
|
||||||
|
avatarService ! AvatarServiceMessage(factionOnContinentChannel, AvatarAction.PlanetsideAttribute(player.GUID, 53, 0))
|
||||||
|
}
|
||||||
|
//squad colors
|
||||||
|
sendResponse(PlanetsideAttributeMessage(player.GUID, 31, squad_supplement_id))
|
||||||
|
avatarService ! AvatarServiceMessage(factionOnContinentChannel, AvatarAction.PlanetsideAttribute(player.GUID, 31, squad_supplement_id))
|
||||||
|
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, ourIndex)) //associate with member position in squad
|
||||||
|
//a finalization? what does this do?
|
||||||
|
sendResponse(SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(18)))
|
||||||
|
updateSquad = UpdatesWhenEnrolledInSquad
|
||||||
|
squadChannel = Some(toChannel)
|
||||||
|
case _ =>
|
||||||
|
//other player is joining our squad
|
||||||
|
//load each member's entry
|
||||||
|
membershipPositions.foreach { case(member, index) =>
|
||||||
|
sendResponse(SquadMemberEvent.Add(squad_supplement_id, member.CharId, index, member.Name, member.ZoneId, unk7 = 0))
|
||||||
|
squadUI(member.CharId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//send an initial dummy update for map icon(s)
|
||||||
|
sendResponse(SquadState(PlanetSideGUID(squad_supplement_id),
|
||||||
|
membershipPositions
|
||||||
|
.filterNot { case (member, _) => member.CharId == avatar.CharId }
|
||||||
|
.map{ case (member, _) => SquadStateInfo(member.CharId, member.Health, member.Armor, member.Position, 2,2, false, 429, None,None) }
|
||||||
|
.toList
|
||||||
|
))
|
||||||
|
|
||||||
|
case SquadResponse.Leave(squad, positionsToUpdate) =>
|
||||||
|
positionsToUpdate.find({ case(member, _) => member == avatar.CharId }) match {
|
||||||
|
case Some((ourMember, ourIndex)) =>
|
||||||
|
//we are leaving the squad
|
||||||
|
//remove each member's entry (our own too)
|
||||||
|
positionsToUpdate.foreach { case(member, index) =>
|
||||||
|
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index))
|
||||||
|
squadUI.remove(member)
|
||||||
|
}
|
||||||
|
//uninitialize
|
||||||
|
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, ourMember, ourIndex)) //repeat of our entry
|
||||||
|
sendResponse(PlanetsideAttributeMessage(player.GUID, 31, 0)) //disassociate with squad?
|
||||||
|
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, 0)) //disassociate with member position in squad?
|
||||||
|
sendResponse(PlanetsideAttributeMessage(player.GUID, 34, 4294967295L)) //unknown, perhaps unrelated?
|
||||||
|
lfs = false
|
||||||
|
//a finalization? what does this do?
|
||||||
|
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
|
||||||
|
updateSquad = NoSquadUpdates
|
||||||
|
squadChannel = None
|
||||||
|
case _ =>
|
||||||
|
//remove each member's entry
|
||||||
|
positionsToUpdate.foreach { case(member, index) =>
|
||||||
|
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index))
|
||||||
|
squadUI.remove(member)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case SquadResponse.AssignMember(squad, from_index, to_index) =>
|
||||||
|
//we've already swapped position internally; now we swap the cards
|
||||||
|
SwapSquadUIElements(squad, from_index, to_index)
|
||||||
|
|
||||||
|
case SquadResponse.PromoteMember(squad, char_id, from_index, to_index) =>
|
||||||
|
val charId = player.CharId
|
||||||
|
val guid = player.GUID
|
||||||
|
lazy val factionOnContinentChannel = s"${continent.Id}/${player.Faction}"
|
||||||
|
//are we being demoted?
|
||||||
|
if(squadUI(charId).index == 0) {
|
||||||
|
//lfsm -> lfs
|
||||||
|
if(lfs) {
|
||||||
|
sendResponse(PlanetsideAttributeMessage(guid, 53, 0))
|
||||||
|
avatarService ! AvatarServiceMessage(factionOnContinentChannel, AvatarAction.PlanetsideAttribute(guid, 53, 0))
|
||||||
|
}
|
||||||
|
lfs = false
|
||||||
|
sendResponse(PlanetsideAttributeMessage(guid, 32, from_index)) //associate with member position in squad
|
||||||
|
}
|
||||||
|
//are we being promoted?
|
||||||
|
else if(charId == char_id) {
|
||||||
|
sendResponse(PlanetsideAttributeMessage(guid, 32, 0)) //associate with member position in squad
|
||||||
|
}
|
||||||
|
avatarService ! AvatarServiceMessage(factionOnContinentChannel, AvatarAction.PlanetsideAttribute(guid, 31, squad_supplement_id))
|
||||||
|
//we must fix the squad cards backend
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
//prune entries
|
||||||
|
val updatedEntries = pairedEntries
|
||||||
|
.collect({
|
||||||
|
case (entry, element) if entry.zone_number != element.zone =>
|
||||||
|
//zone gets updated for these entries
|
||||||
|
sendResponse(SquadMemberEvent.UpdateZone(squad_supplement_id, entry.char_id, element.index, entry.zone_number))
|
||||||
|
squadUI(entry.char_id) = SquadUIElement(element.name, 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
|
||||||
|
squadUI(entry.char_id) = SquadUIElement(element.name, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
|
||||||
|
entry
|
||||||
|
})
|
||||||
|
.filterNot(_.char_id == avatar.CharId) //we want to update our backend, but not our frontend
|
||||||
|
if(updatedEntries.nonEmpty) {
|
||||||
|
sendResponse(
|
||||||
|
SquadState(
|
||||||
|
PlanetSideGUID(squad_supplement_id),
|
||||||
|
updatedEntries.map { entry => SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos, 2,2, false, 429, None,None)}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
case SquadResponse.SquadSearchResults() =>
|
||||||
|
//I don't actually know how to return search results
|
||||||
|
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.NoSquadSearchResults()))
|
||||||
|
|
||||||
|
case SquadResponse.InitWaypoints(char_id, waypoints) =>
|
||||||
|
StartBundlingPackets()
|
||||||
|
waypoints.foreach { case (waypoint_type, info, unk) =>
|
||||||
|
sendResponse(SquadWaypointEvent.Add(squad_supplement_id, char_id, waypoint_type, WaypointEvent(info.zone_number, info.pos, unk)))
|
||||||
|
}
|
||||||
|
StopBundlingPackets()
|
||||||
|
|
||||||
|
case SquadResponse.WaypointEvent(WaypointEventAction.Add, char_id, waypoint_type, _, Some(info), unk) =>
|
||||||
|
sendResponse(SquadWaypointEvent.Add(squad_supplement_id, char_id, waypoint_type, WaypointEvent(info.zone_number, info.pos, unk)))
|
||||||
|
|
||||||
|
case SquadResponse.WaypointEvent(WaypointEventAction.Remove, char_id, waypoint_type, _, _, _) =>
|
||||||
|
sendResponse(SquadWaypointEvent.Remove(squad_supplement_id, char_id, waypoint_type))
|
||||||
|
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case Deployment.CanDeploy(obj, state) =>
|
case Deployment.CanDeploy(obj, state) =>
|
||||||
|
|
@ -1040,6 +1049,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
galaxyService ! Service.Join(s"${avatar.faction}") //for hotspots
|
galaxyService ! Service.Join(s"${avatar.faction}") //for hotspots
|
||||||
squadService ! Service.Join(s"${avatar.faction}") //channel will be player.Faction
|
squadService ! Service.Join(s"${avatar.faction}") //channel will be player.Faction
|
||||||
squadService ! Service.Join(s"${avatar.CharId}") //channel will be player.CharId (in order to work with packets)
|
squadService ! Service.Join(s"${avatar.CharId}") //channel will be player.CharId (in order to work with packets)
|
||||||
|
squadService ! Service.Join(s"${avatar.faction}") //channel will be player.Faction
|
||||||
cluster ! InterstellarCluster.GetWorld("home3")
|
cluster ! InterstellarCluster.GetWorld("home3")
|
||||||
|
|
||||||
case InterstellarCluster.GiveWorld(zoneId, zone) =>
|
case InterstellarCluster.GiveWorld(zoneId, zone) =>
|
||||||
|
|
@ -3034,7 +3044,15 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
sendResponse(SetChatFilterMessage(ChatChannel.Local, false, ChatChannel.values.toList)) //TODO will not always be "on" like this
|
sendResponse(SetChatFilterMessage(ChatChannel.Local, false, ChatChannel.values.toList)) //TODO will not always be "on" like this
|
||||||
deadState = DeadState.Alive
|
deadState = DeadState.Alive
|
||||||
sendResponse(AvatarDeadStateMessage(DeadState.Alive, 0, 0, tplayer.Position, player.Faction, true))
|
sendResponse(AvatarDeadStateMessage(DeadState.Alive, 0, 0, tplayer.Position, player.Faction, true))
|
||||||
sendResponse(PlanetsideAttributeMessage(guid, 53, tplayer.LFS))
|
//looking for squad (members)
|
||||||
|
if(squadUI.nonEmpty && squadUI(avatar.CharId).index == 0) {
|
||||||
|
sendResponse(PlanetsideAttributeMessage(guid, 31, 1))
|
||||||
|
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(guid, 31, 1))
|
||||||
|
}
|
||||||
|
if(tplayer.LFS || lfs) {
|
||||||
|
sendResponse(PlanetsideAttributeMessage(guid, 53, 1))
|
||||||
|
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(guid, 53, 1))
|
||||||
|
}
|
||||||
sendResponse(AvatarSearchCriteriaMessage(guid, List(0, 0, 0, 0, 0, 0)))
|
sendResponse(AvatarSearchCriteriaMessage(guid, List(0, 0, 0, 0, 0, 0)))
|
||||||
(1 to 73).foreach(i => {
|
(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?
|
// not all GUID's are set, and not all of the set ones will always be zero; what does this section do?
|
||||||
|
|
@ -3047,14 +3065,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
//AvatarAwardMessage
|
//AvatarAwardMessage
|
||||||
//DisplayAwardMessage
|
//DisplayAwardMessage
|
||||||
sendResponse(PlanetsideStringAttributeMessage(guid, 0, "Outfit Name"))
|
sendResponse(PlanetsideStringAttributeMessage(guid, 0, "Outfit Name"))
|
||||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(6)))
|
//squad stuff (loadouts, assignment)
|
||||||
(0 to 9).foreach(line => {
|
squadSetup()
|
||||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), line, SquadAction.ListSquadFavorite("")))
|
|
||||||
})
|
|
||||||
sendResponse(SquadDetailDefinitionUpdateMessage.Init)
|
|
||||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0,SquadAction.AssociateWithSquad()))
|
|
||||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0,SquadAction.SetListSquad()))
|
|
||||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0,SquadAction.Unknown(18)))
|
|
||||||
//MapObjectStateBlockMessage and ObjectCreateMessage?
|
//MapObjectStateBlockMessage and ObjectCreateMessage?
|
||||||
//TacticsMessage?
|
//TacticsMessage?
|
||||||
//change the owner on our deployables (re-draw the icons for our deployables too)
|
//change the owner on our deployables (re-draw the icons for our deployables too)
|
||||||
|
|
@ -3089,7 +3101,30 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
interstellarFerryTopLevelGUID = None
|
interstellarFerryTopLevelGUID = None
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
squadService ! Service.Join(s"${avatar.faction}") //channel will be player.Faction
|
}
|
||||||
|
|
||||||
|
def FirstTimeSquadSetup() : Unit = {
|
||||||
|
sendResponse(SquadDetailDefinitionUpdateMessage.Init)
|
||||||
|
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(6)))
|
||||||
|
//only need to load these once
|
||||||
|
avatar.SquadLoadouts.Loadouts.foreach {
|
||||||
|
case (index, loadout : SquadLoadout) =>
|
||||||
|
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), index, SquadAction.ListSquadFavorite(loadout.task)))
|
||||||
|
}
|
||||||
|
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.AssociateWithSquad()))
|
||||||
|
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.SetListSquad()))
|
||||||
|
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
|
||||||
|
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitSquadList())
|
||||||
|
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitCharId())
|
||||||
|
squadSetup = SubsequentSpawnSquadSetup
|
||||||
|
}
|
||||||
|
|
||||||
|
def SubsequentSpawnSquadSetup() : Unit = {
|
||||||
|
if(squadUI.nonEmpty) {
|
||||||
|
//sendResponse(PlanetsideAttributeMessage(player.GUID, 31, squad_supplement_id))
|
||||||
|
val (_, ourCard) = squadUI.find { case (id, card) => id == player.CharId }.get
|
||||||
|
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, ourCard.index))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def handleControlPkt(pkt : PlanetSideControlPacket) = {
|
def handleControlPkt(pkt : PlanetSideControlPacket) = {
|
||||||
|
|
@ -3503,23 +3538,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) =>
|
case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) =>
|
||||||
if(deadState == DeadState.Alive) {
|
if(deadState == DeadState.Alive) {
|
||||||
if(!player.Crouching && is_crouching) { //SQUAD TESTING CODE
|
if(!player.Crouching && is_crouching) { //SQUAD TESTING CODE
|
||||||
sendResponse(
|
//...
|
||||||
CharacterKnowledgeMessage(
|
|
||||||
41577140L,
|
|
||||||
CharacterKnowledgeInfo(
|
|
||||||
"Degrado",
|
|
||||||
Set(
|
|
||||||
CertificationType.StandardAssault,
|
|
||||||
CertificationType.AgileExoSuit,
|
|
||||||
CertificationType.StandardExoSuit
|
|
||||||
),
|
|
||||||
37,
|
|
||||||
5,
|
|
||||||
PlanetSideGUID(7)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
sendResponse(SquadInvitationRequestMessage(PlanetSideGUID(1), 4, 41577140L, "Degrado"))
|
|
||||||
}
|
}
|
||||||
player.Position = pos
|
player.Position = pos
|
||||||
player.Velocity = vel
|
player.Velocity = vel
|
||||||
|
|
@ -3562,7 +3581,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
case None => false
|
case None => false
|
||||||
}
|
}
|
||||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, msg, spectator, wepInHand))
|
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, msg, spectator, wepInHand))
|
||||||
//squadService ! SquadServiceMessage(tplayer, continent, SquadAction.Update(tplayer.CharId, tplayer.Health, tplayer.MaxHealth, tplayer.Armor, tplayer.MaxArmor, pos, zone.Number))
|
updateSquad()
|
||||||
}
|
}
|
||||||
|
|
||||||
case msg @ ChildObjectStateMessage(object_guid, pitch, yaw) =>
|
case msg @ ChildObjectStateMessage(object_guid, pitch, yaw) =>
|
||||||
|
|
@ -3603,6 +3622,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
}
|
}
|
||||||
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, pos, ang, vel, flight, unk6, unk7, wheels, unk9, unkA))
|
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, pos, ang, vel, flight, unk6, unk7, wheels, unk9, unkA))
|
||||||
}
|
}
|
||||||
|
//vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, pos, ang, vel, flight, unk6, unk7, wheels, unk9, unkA))
|
||||||
|
updateSquad()
|
||||||
case (None, _) =>
|
case (None, _) =>
|
||||||
//log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
|
//log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
|
||||||
//TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
|
//TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
|
||||||
|
|
@ -4704,24 +4725,28 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
log.info(s"GenericObject: $player is MAX with an unexpected weapon - ${definition.Name}")
|
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
|
else if(action == 36) { //Looking For Squad ON
|
||||||
if(squadUI.nonEmpty) {
|
if(squadUI.nonEmpty) {
|
||||||
lfs = true
|
if(!lfs && squadUI(player.CharId).index == 0) {
|
||||||
|
lfs = true
|
||||||
|
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 53, 1))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if(!avatar.LFS) {
|
else if(!avatar.LFS) {
|
||||||
avatar.LFS = true
|
avatar.LFS = true
|
||||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 53, 1))
|
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 53, 1))
|
||||||
log.info(s"GenericObject: ${player.Name} has made himself available to join a squad")
|
}
|
||||||
|
}
|
||||||
|
else if(action == 37) { //Looking For Squad OFF
|
||||||
|
if(squadUI.nonEmpty) {
|
||||||
|
if(lfs && squadUI(player.CharId).index == 0) {
|
||||||
|
lfs = false
|
||||||
|
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 53, 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(avatar.LFS) {
|
||||||
|
avatar.LFS = false
|
||||||
|
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 53, 0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -9185,17 +9210,15 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
|
|
||||||
def SwapSquadUIElements(squad : Squad, fromIndex : Int, toIndex : Int) : Unit = {
|
def SwapSquadUIElements(squad : Squad, fromIndex : Int, toIndex : Int) : Unit = {
|
||||||
if(squadUI.nonEmpty) {
|
if(squadUI.nonEmpty) {
|
||||||
val fromMember = squad.Membership(fromIndex)
|
val fromMember = squad.Membership(toIndex) //the players have already been swapped in the backend object
|
||||||
val fromCharId = fromMember.CharId
|
val fromCharId = fromMember.CharId
|
||||||
val toMember = squad.Membership(toIndex)
|
val toMember = squad.Membership(fromIndex) //the players have already been swapped in the backend object
|
||||||
val toCharId = toMember.CharId
|
val toCharId = toMember.CharId
|
||||||
val id = 11
|
val id = 11
|
||||||
if(fromCharId > 0) {
|
if(toCharId > 0) {
|
||||||
//toMember and fromMember have swapped places
|
//toMember and fromMember have swapped places
|
||||||
val fromElem = squadUI(fromCharId)
|
val fromElem = squadUI(fromCharId)
|
||||||
val toElem = squadUI(toCharId)
|
val toElem = squadUI(toCharId)
|
||||||
sendResponse(SquadMemberEvent.Remove(id, toCharId, fromIndex))
|
|
||||||
sendResponse(SquadMemberEvent.Remove(id, fromCharId, toIndex))
|
|
||||||
squadUI(toCharId) = SquadUIElement(fromElem.name, toIndex, fromElem.zone, fromElem.health, fromElem.armor, fromElem.position)
|
squadUI(toCharId) = SquadUIElement(fromElem.name, toIndex, fromElem.zone, fromElem.health, fromElem.armor, fromElem.position)
|
||||||
squadUI(fromCharId) = SquadUIElement(toElem.name, fromIndex, toElem.zone, toElem.health, toElem.armor, toElem.position)
|
squadUI(fromCharId) = SquadUIElement(toElem.name, fromIndex, toElem.zone, toElem.health, toElem.armor, toElem.position)
|
||||||
sendResponse(SquadMemberEvent.Add(id, toCharId, toIndex, fromElem.name, fromElem.zone, unk7 = 0))
|
sendResponse(SquadMemberEvent.Add(id, toCharId, toIndex, fromElem.name, fromElem.zone, unk7 = 0))
|
||||||
|
|
@ -9212,14 +9235,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//previous fromMember has moved toMember
|
//previous fromMember has moved toMember
|
||||||
val elem = squadUI(toCharId)
|
val elem = squadUI(fromCharId)
|
||||||
sendResponse(SquadMemberEvent.Remove(id, toCharId, fromIndex))
|
squadUI(fromCharId) = SquadUIElement(elem.name, toIndex, elem.zone, elem.health, elem.armor, elem.position)
|
||||||
squadUI(toCharId) = SquadUIElement(elem.name, toIndex, elem.zone, elem.health, elem.armor, elem.position)
|
sendResponse(SquadMemberEvent.Remove(id, fromCharId, fromIndex))
|
||||||
sendResponse(SquadMemberEvent.Add(id, toCharId, toIndex, elem.name, elem.zone, unk7 = 0))
|
sendResponse(SquadMemberEvent.Add(id, fromCharId, toIndex, elem.name, elem.zone, unk7 = 0))
|
||||||
sendResponse(
|
sendResponse(
|
||||||
SquadState(
|
SquadState(
|
||||||
PlanetSideGUID(id),
|
PlanetSideGUID(id),
|
||||||
List(SquadStateInfo(toCharId, elem.health, elem.armor, elem.position, 2, 2, false, 429, None, None))
|
List(SquadStateInfo(fromCharId, elem.health, elem.armor, elem.position, 2, 2, false, 429, None, None))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -9233,6 +9256,23 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def NoSquadUpdates() : Unit = { }
|
||||||
|
|
||||||
|
def UpdatesWhenEnrolledInSquad() : Unit = {
|
||||||
|
squadService ! SquadServiceMessage(
|
||||||
|
player,
|
||||||
|
continent,
|
||||||
|
continent.GUID(player.VehicleSeated) match {
|
||||||
|
case Some(vehicle : Vehicle) =>
|
||||||
|
SquadServiceAction.Update(player.CharId, vehicle.Health, vehicle.MaxHealth, vehicle.Shields, vehicle.MaxShields, vehicle.Position, continent.Number)
|
||||||
|
case Some(obj : PlanetSideGameObject with WeaponTurret) =>
|
||||||
|
SquadServiceAction.Update(player.CharId, obj.Health, obj.MaxHealth, 0, 0, obj.Position, continent.Number)
|
||||||
|
case _ =>
|
||||||
|
SquadServiceAction.Update(player.CharId, player.Health, player.MaxHealth, player.Armor, player.MaxArmor, player.Position, continent.Number)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
def failWithError(error : String) = {
|
def failWithError(error : String) = {
|
||||||
log.error(error)
|
log.error(error)
|
||||||
sendResponse(ConnectionClose())
|
sendResponse(ConnectionClose())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue