mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-20 02:54:46 +00:00
split original aphedox SquadRequestType's into two different enumerations, one for Request and the other for Response; added functionality for more squad interactions; squad favorites, excepting the ability to actually load them onto one's client; extensive comments about SquadResponseMessage events
This commit is contained in:
parent
640a96ae9c
commit
f81c87ce22
|
|
@ -23,7 +23,7 @@ object Loadout {
|
|||
owner match {
|
||||
case p : Player => Success(Create(p, label))
|
||||
case v : Vehicle => Success(Create(v, label))
|
||||
case s : Squad => Success(Create(s, label))
|
||||
case s : Squad => Success(Create(s, s.Task))
|
||||
case _ => Failure(new MatchError(s"can not create a loadout based on the (current status of) $owner"))
|
||||
}
|
||||
}
|
||||
|
|
@ -65,7 +65,6 @@ object Loadout {
|
|||
def Create(squad : Squad, label : String) : Loadout = {
|
||||
SquadLoadout(
|
||||
label,
|
||||
squad.Task,
|
||||
if(squad.CustomZoneId) { Some(squad.ZoneId) } else { None },
|
||||
squad.Membership
|
||||
.zipWithIndex
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import net.psforever.types.CertificationType
|
|||
|
||||
final case class SquadPositionLoadout(index : Int, role : String, orders : String, requirements : Set[CertificationType.Value])
|
||||
|
||||
final case class SquadLoadout(label : String,
|
||||
task : String,
|
||||
final case class SquadLoadout(task : String,
|
||||
zone_id : Option[Int],
|
||||
members : List[SquadPositionLoadout]) extends Loadout(label)
|
||||
members : List[SquadPositionLoadout]) extends Loadout(task)
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ class Squad(squadId : PlanetSideGUID, alignment : PlanetSideEmpire.Value) extend
|
|||
}
|
||||
}
|
||||
|
||||
def Size : Int = membership.count(member => !member.Name.equals(""))
|
||||
def Size : Int = membership.count(member => member.CharId != 0)
|
||||
|
||||
def Capacity : Int = availability.count(open => open)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,22 @@ final case class SquadInfo(leader : Option[String],
|
|||
squad_guid.orElse(info.squad_guid)
|
||||
)
|
||||
}
|
||||
|
||||
//methods intended to combine the fields of itself and another object
|
||||
def Leader(leader : String) : SquadInfo =
|
||||
this And SquadInfo(Some(leader), None, None, None, None, None)
|
||||
def Task(task : String) : SquadInfo =
|
||||
this And SquadInfo(None, Some(task), None, None, None, None)
|
||||
def ZoneId(zone : PlanetSideZoneID) : SquadInfo =
|
||||
this And SquadInfo(None, None, Some(zone), None, None, None)
|
||||
def ZoneId(zone : Option[PlanetSideZoneID]) : SquadInfo = zone match {
|
||||
case Some(zoneId) => this And SquadInfo(None, None, zone, None, None, None)
|
||||
case None => SquadInfo(leader, task, zone, size, capacity, squad_guid)
|
||||
}
|
||||
def Size(sz : Int) : SquadInfo =
|
||||
this And SquadInfo(None, None, None, Some(sz), None, None)
|
||||
def Capacity(cap : Int) : SquadInfo =
|
||||
this And SquadInfo(None, None, None, None, Some(cap), None)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -117,7 +133,9 @@ object SquadInfo {
|
|||
/**
|
||||
* An entry where no fields are defined.
|
||||
*/
|
||||
final val Blank = SquadInfo(None, None, None, None, None)
|
||||
final val Blank = SquadInfo()
|
||||
|
||||
def apply() : SquadInfo = SquadInfo(None, None, None, None, None, None)
|
||||
|
||||
/**
|
||||
* Alternate constructor for `SquadInfo` that ignores the `Option` requirement for the fields.<br>
|
||||
|
|
|
|||
|
|
@ -20,18 +20,24 @@ object SquadAction{
|
|||
|
||||
final case class AnswerSquadJoinRequest() extends SquadAction(1)
|
||||
|
||||
final case class SaveSquadDefinition() extends SquadAction(3)
|
||||
final case class SaveSquadFavorite() extends SquadAction(3)
|
||||
|
||||
final case class LoadSquadDefinition() extends SquadAction(4)
|
||||
final case class LoadSquadFavorite() extends SquadAction(4)
|
||||
|
||||
final case class ListSquadDefinition(name : String) extends SquadAction(7)
|
||||
final case class DeleteSquadFavorite() extends SquadAction(5)
|
||||
|
||||
final case class ListSquad() extends SquadAction(8)
|
||||
final case class ListSquadFavorite(name : String) extends SquadAction(7)
|
||||
|
||||
final case class RequestListSquad() extends SquadAction(8)
|
||||
|
||||
final case class StopListSquad() extends SquadAction(9)
|
||||
|
||||
final case class SelectRoleForYourself(state : Int) extends SquadAction(10)
|
||||
|
||||
final case class CancelSelectRoleForYourself(value: Long = 0) extends SquadAction(15)
|
||||
|
||||
final case class SetListSquad() extends SquadAction(17)
|
||||
|
||||
final case class ChangeSquadPurpose(purpose : String) extends SquadAction(19)
|
||||
|
||||
final case class ChangeSquadZone(zone : PlanetSideZoneID) extends SquadAction(20)
|
||||
|
|
@ -56,12 +62,21 @@ object SquadAction{
|
|||
|
||||
final case class CancelSquadSearch() extends SquadAction(35)
|
||||
|
||||
final case class AssignSquadMemberToRole(position : Int, char_id : Long) extends SquadAction(38)
|
||||
|
||||
final case class FindLfsSoldiersForRole(state : Int) extends SquadAction(40)
|
||||
|
||||
final case class CancelFind() extends SquadAction(41)
|
||||
|
||||
final case class Unknown(badCode : Int, data : BitVector) extends SquadAction(badCode)
|
||||
|
||||
object Unknown {
|
||||
import scodec.bits._
|
||||
val StandardBits : BitVector = hex"00".toBitVector.take(6)
|
||||
|
||||
def apply(badCode : Int) : Unknown = Unknown(badCode, StandardBits)
|
||||
}
|
||||
|
||||
/**
|
||||
* The `Codec`s used to transform the input stream into the context of a specific action
|
||||
* and extract the field data from that stream.
|
||||
|
|
@ -83,31 +98,45 @@ object SquadAction{
|
|||
}
|
||||
)
|
||||
|
||||
val saveSquadDefinitionCodec = everFailCondition.xmap[SaveSquadDefinition] (
|
||||
_ => SaveSquadDefinition(),
|
||||
val saveSquadFavoriteCodec = everFailCondition.xmap[SaveSquadFavorite] (
|
||||
_ => SaveSquadFavorite(),
|
||||
{
|
||||
case SaveSquadDefinition() => None
|
||||
case SaveSquadFavorite() => None
|
||||
}
|
||||
)
|
||||
|
||||
val loadSquadDefinitionCodec = everFailCondition.xmap[LoadSquadDefinition] (
|
||||
_ => LoadSquadDefinition(),
|
||||
val loadSquadFavoriteCodec = everFailCondition.xmap[LoadSquadFavorite] (
|
||||
_ => LoadSquadFavorite(),
|
||||
{
|
||||
case LoadSquadDefinition() => None
|
||||
case LoadSquadFavorite() => None
|
||||
}
|
||||
)
|
||||
|
||||
val listSquadDefinitionCodec = PacketHelpers.encodedWideStringAligned(6).xmap[ListSquadDefinition] (
|
||||
text => ListSquadDefinition(text),
|
||||
val deleteSquadFavoriteCodec = everFailCondition.xmap[DeleteSquadFavorite] (
|
||||
_ => DeleteSquadFavorite(),
|
||||
{
|
||||
case ListSquadDefinition(text) => text
|
||||
case DeleteSquadFavorite() => None
|
||||
}
|
||||
)
|
||||
|
||||
val listSquadCodec = everFailCondition.xmap[ListSquad] (
|
||||
_ => ListSquad(),
|
||||
val listSquadFavoriteCodec = PacketHelpers.encodedWideStringAligned(6).xmap[ListSquadFavorite] (
|
||||
text => ListSquadFavorite(text),
|
||||
{
|
||||
case ListSquad() => None
|
||||
case ListSquadFavorite(text) => text
|
||||
}
|
||||
)
|
||||
|
||||
val requestListSquadCodec = everFailCondition.xmap[RequestListSquad] (
|
||||
_ => RequestListSquad(),
|
||||
{
|
||||
case RequestListSquad() => None
|
||||
}
|
||||
)
|
||||
|
||||
val stopListSquadCodec = everFailCondition.xmap[StopListSquad] (
|
||||
_ => StopListSquad(),
|
||||
{
|
||||
case StopListSquad() => None
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -125,6 +154,13 @@ object SquadAction{
|
|||
}
|
||||
)
|
||||
|
||||
val setListSquadCodec = everFailCondition.xmap[SetListSquad] (
|
||||
_ => SetListSquad(),
|
||||
{
|
||||
case SetListSquad() => None
|
||||
}
|
||||
)
|
||||
|
||||
val changeSquadPurposeCodec = PacketHelpers.encodedWideStringAligned(6).xmap[ChangeSquadPurpose] (
|
||||
purpose => ChangeSquadPurpose(purpose),
|
||||
{
|
||||
|
|
@ -223,6 +259,15 @@ object SquadAction{
|
|||
}
|
||||
)
|
||||
|
||||
val assignSquadMemberToRoleCodec = (uint4 :: uint32L).xmap[AssignSquadMemberToRole] (
|
||||
{
|
||||
case u1 :: u2 :: HNil => AssignSquadMemberToRole(u1, u2)
|
||||
},
|
||||
{
|
||||
case AssignSquadMemberToRole(u1, u2) => u1 :: u2 :: HNil
|
||||
}
|
||||
)
|
||||
|
||||
val findLfsSoldiersForRoleCodec = uint4.xmap[FindLfsSoldiersForRole] (
|
||||
state => FindLfsSoldiersForRole(state),
|
||||
{
|
||||
|
|
@ -270,13 +315,14 @@ object SquadAction{
|
|||
* `0 ` - Display Squad<br>
|
||||
* `1 ` - Answer Squad Join Request<br>
|
||||
* `2 ` - UNKNOWN<br>
|
||||
* `3 ` - Save Squad Definition<br>
|
||||
* `4 ` - Load Squad Definition<br>
|
||||
* `3 ` - Save Squad Favorite<br>
|
||||
* `4 ` - Load Squad Favorite<br>
|
||||
* `5 ` - Delete Squad Favorite<br>
|
||||
* `6 ` - UNKNOWN<br>
|
||||
* `8 ` - List Squad<br>
|
||||
* `9 ` - UNKNOWN<br>
|
||||
* `8 ` - Request List Squad<br>
|
||||
* `9 ` - Stop List Squad<br>
|
||||
* `16` - UNKNOWN<br>
|
||||
* `17` - UNKNOWN<br>
|
||||
* `17` - Set List Squad (ui)<br>
|
||||
* `18` - UNKNOWN<br>
|
||||
* `26` - Reset All<br>
|
||||
* `35` - Cancel Squad Search<br>
|
||||
|
|
@ -302,12 +348,12 @@ object SquadAction{
|
|||
* `15` - Select this Role for Yourself<br>
|
||||
* `37` - UNKNOWN<br>
|
||||
* `String`<br>
|
||||
* `7 ` - List Squad Definition<br>
|
||||
* `7 ` - List Squad Favorite<br>
|
||||
* `19` - (Squad leader) Change Squad Purpose<br>
|
||||
* `Int :: Long`<br>
|
||||
* `12` - UNKNOWN<br>
|
||||
* `25` - (Squad leader) Change Squad Member Requirements - Weapons<br>
|
||||
* `38` - UNKNOWN<br>
|
||||
* `38` - Assign Squad Member To Role<br>
|
||||
* `Int :: String`<br>
|
||||
* `23` - (Squad leader) Change Squad Member Requirements - Role<br>
|
||||
* `24` - (Squad leader) Change Squad Member Requirements - Detailed Orders<br>
|
||||
|
|
@ -343,12 +389,15 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
|
|||
((code : @switch) match {
|
||||
case 0 => displaySquadCodec
|
||||
case 1 => answerSquadJoinRequestCodec
|
||||
case 3 => saveSquadDefinitionCodec
|
||||
case 4 => loadSquadDefinitionCodec
|
||||
case 7 => listSquadDefinitionCodec
|
||||
case 8 => listSquadCodec
|
||||
case 3 => saveSquadFavoriteCodec
|
||||
case 4 => loadSquadFavoriteCodec
|
||||
case 5 => deleteSquadFavoriteCodec
|
||||
case 7 => listSquadFavoriteCodec
|
||||
case 8 => requestListSquadCodec
|
||||
case 9 => stopListSquadCodec
|
||||
case 10 => selectRoleForYourselfCodec
|
||||
case 15 => cancelSelectRoleForYourselfCodec
|
||||
case 17 => setListSquadCodec
|
||||
case 19 => changeSquadPurposeCodec
|
||||
case 20 => changeSquadZoneCodec
|
||||
case 21 => closeSquadMemberPositionCodec
|
||||
|
|
@ -361,12 +410,13 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
|
|||
case 31 => locationFollowsSquadLeadCodec
|
||||
case 34 => searchForSquadsWithParticularRoleCodec
|
||||
case 35 => cancelSquadSearchCodec
|
||||
case 38 => assignSquadMemberToRoleCodec
|
||||
case 40 => findLfsSoldiersForRoleCodec
|
||||
case 41 => cancelFindCodec
|
||||
case 2 | 6 | 9 | 11 |
|
||||
12 | 13 | 14 | 16 | 17 |
|
||||
case 2 | 6 | 11 |
|
||||
12 | 13 | 14 | 16 |
|
||||
18 | 29 | 30 | 32 | 33 |
|
||||
36 | 37 | 38 | 39 | 42 | 43 => unknownCodec(code)
|
||||
36 | 37 | 39 | 42 | 43 => unknownCodec(code)
|
||||
case _ => failureCodec(code)
|
||||
}).asInstanceOf[Codec[SquadAction]]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -161,7 +161,21 @@ final case class SquadDetail(unk1 : Option[Int],
|
|||
task.orElse(info.task),
|
||||
zone_id.orElse(info.zone_id),
|
||||
unk7.orElse(info.unk7),
|
||||
member_info.orElse(info.member_info)
|
||||
{
|
||||
(member_info, info.member_info) match {
|
||||
case (Some(info1), Some(info2)) =>
|
||||
//combine the first list with the elements of the second list whose indices not found in the first list
|
||||
val indices = info1.map { _.index }
|
||||
Some(info1 ++ (for {
|
||||
position <- info2
|
||||
if !indices.contains(position.index)
|
||||
} yield position).sortBy(_.index))
|
||||
case (Some(info1), None) =>
|
||||
Some(info1)
|
||||
case (None, _) =>
|
||||
info.member_info
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -269,6 +283,15 @@ object SquadPositionDetail {
|
|||
* @return a `SquadPositionDetail` object
|
||||
*/
|
||||
def apply(role : String, detailed_orders : String, requirements : Set[CertificationType.Value], char_id : Long, name : String) : SquadPositionDetail = SquadPositionDetail(Some(false), Some(role), Some(detailed_orders), Some(requirements), Some(char_id), Some(name))
|
||||
|
||||
object Fields {
|
||||
final val Closed = 0
|
||||
final val Role = 1
|
||||
final val Orders = 2
|
||||
final val CharId = 3
|
||||
final val Name = 4
|
||||
final val Requirements = 5
|
||||
}
|
||||
}
|
||||
|
||||
object SquadPositionEntry {
|
||||
|
|
@ -318,6 +341,17 @@ object SquadDetail {
|
|||
SquadDetail(None, None, None, None, None, None, None, Some(unk7), None)
|
||||
def Members(list : List[SquadPositionEntry]) : SquadDetail =
|
||||
SquadDetail(None, None, None, None, None, None, None, None, Some(list))
|
||||
|
||||
object Fields {
|
||||
final val Field1 = 1
|
||||
final val CharId = 2
|
||||
final val Field3 = 3
|
||||
final val Leader = 4
|
||||
final val Task = 5
|
||||
final val ZoneId = 6
|
||||
final val Field7 = 7
|
||||
final val Members = 8
|
||||
}
|
||||
}
|
||||
|
||||
object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefinitionUpdateMessage] {
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||
import net.psforever.types.SquadRequestType
|
||||
import net.psforever.types.SquadResponseType
|
||||
import scodec.Codec
|
||||
import scodec.codecs._
|
||||
|
||||
/**
|
||||
* Dispatched by the server as manipulation protocol for squad and platoon members.
|
||||
* Dispatched by the server as message generation protocol for squad and platoon members.
|
||||
* Prompted by and answers for a `SquadMembershipRequest` packet.
|
||||
* @param request_type the purpose of the request
|
||||
* @param unk1 na
|
||||
|
|
@ -17,11 +17,57 @@ import scodec.codecs._
|
|||
* @param other_id another squad member's unique identifier;
|
||||
* may be the same as `char_id`
|
||||
* @param player_name name of the player being affected, if applicable
|
||||
* @param unk5 na
|
||||
* @param unk5 adjusts the nature of the request-type response based on the message recipient
|
||||
* @param unk6 na;
|
||||
* the internal field, the `Option[String]`, never seems to be set
|
||||
* <br>
|
||||
* `request_type` (enum value) / `unk5` state (`false`/`true`)<br>
|
||||
* ----------------------------------------<br>
|
||||
* - `Invite` (0)<br>
|
||||
* false => [PROMPT] "`player_name` has invited you into a squad." [YES/NO]<br>
|
||||
* true => "You have invited `player_name` to join your squad."<br>
|
||||
* - `Unk01` (1)<br>
|
||||
* false => n/a<br>
|
||||
* true => n/a<br>
|
||||
* - `Accept` (2)<br>
|
||||
* false => "`player_name` has accepted your invitation to join into your squad.<br>
|
||||
* "You have formed a squad and are now that squad's commander." (if first time)<br>
|
||||
* true => "You have accepted an invitation to join a squad."<br>
|
||||
* "You have successfully joined a squad for the first time." (if first time)<br>
|
||||
* - `Reject` (3)<br>
|
||||
* false => "`player_name` does not want to join your squad at this time."<br>
|
||||
* true => "You have declined an invitation to join a squad."<br>
|
||||
* - `Cancel` (4)<br>
|
||||
* false => "`player_name` has withdrawn his invitation."<br>
|
||||
* true => "You have canceled your invitation to `player_name`."<br>
|
||||
* - `Leave` (5)<br>
|
||||
* false => "The Squad Leader has kicked you out of the squad."<br>
|
||||
* true => "You have kicked `player_name` out of the squad."<br>
|
||||
* - `Disband` (6)<br>
|
||||
* false => "The squad has been disbanded."<br>
|
||||
* true => "You have disbanded the squad."<br>
|
||||
* - `PlatoonInvite` (7)<br>
|
||||
* false => [PROMPT] "`player_name` has invited you into a platoon." [YES/NO]<br>
|
||||
* true => "You have invited `player_name`'s squad to join your platoon."<br>
|
||||
* - `PlatoonAccept` (8)
|
||||
* false => "`player_name` has accepted your invitation to join into your platoon.<br>
|
||||
* "You have formed a platoon and are now that platoon commander." (if first time)<br>
|
||||
* true => "You have accepted an invitation to join a platoon."<br>
|
||||
* "You have successfully joined a platoon for the first time." (if first time)<br>
|
||||
* - `PlatoonReject` (9)<br>
|
||||
* false => "`player_name` does not want to join your platoon at this time."<br>
|
||||
* true => "You have declined an invitation to join a platoon."<br>
|
||||
* - `PlatoonCancel` (10)<br>
|
||||
* false => "`player_name` has withdrawn his invitation."<br>
|
||||
* true => "You have declined your invitation to `player_name`." (nonsense?)<br>
|
||||
* - `PlatoonLeave` (11)<br>
|
||||
* false => "The Platoon Leader has kicked you out of the platoon."<br>
|
||||
* true => "You have kicked `player_name`'s squad out of the platoon."<br>
|
||||
* - `PlatoonDisband` (12)<br>
|
||||
* false => "The platoon has been disbanded."<br>
|
||||
* true => "You have disbanded the platoon."
|
||||
*/
|
||||
final case class SquadMembershipResponse(request_type : SquadRequestType.Value,
|
||||
final case class SquadMembershipResponse(request_type : SquadResponseType.Value,
|
||||
unk1 : Int,
|
||||
unk2 : Int,
|
||||
char_id : Long,
|
||||
|
|
@ -37,14 +83,14 @@ final case class SquadMembershipResponse(request_type : SquadRequestType.Value,
|
|||
|
||||
object SquadMembershipResponse extends Marshallable[SquadMembershipResponse] {
|
||||
implicit val codec : Codec[SquadMembershipResponse] = (
|
||||
"request_type" | SquadRequestType.codec >>:~ { d =>
|
||||
"request_type" | SquadResponseType.codec >>:~ { d =>
|
||||
("unk1" | uint(5)) ::
|
||||
("unk2" | uint2) ::
|
||||
("char_id" | uint32L) ::
|
||||
("other_id" | conditional(d != SquadRequestType.Promote && d != SquadRequestType.PlatoonLeave, uint32L)) ::
|
||||
("other_id" | conditional(d != SquadResponseType.Disband && d != SquadResponseType.PlatoonDisband, uint32L)) ::
|
||||
("player_name" | PacketHelpers.encodedWideStringAligned(5)) ::
|
||||
("unk5" | bool) ::
|
||||
conditional(d != SquadRequestType.Invite, optional(bool, "unk6" | PacketHelpers.encodedWideStringAligned(6)))
|
||||
conditional(d != SquadResponseType.Invite, optional(bool, "unk6" | PacketHelpers.encodedWideStringAligned(6)))
|
||||
}
|
||||
).as[SquadMembershipResponse]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,23 +7,28 @@ import scodec.codecs._
|
|||
object SquadRequestType extends Enumeration {
|
||||
type Type = Value
|
||||
val
|
||||
Invite, //00
|
||||
Unk01, //01
|
||||
Accept, //02
|
||||
Reject, //03
|
||||
Cancel, //04
|
||||
Leave, //05
|
||||
Promote, //06
|
||||
Disband, //07
|
||||
PlatoonInvite, //08
|
||||
PlatoonAccept, //09
|
||||
PlatoonReject, //10
|
||||
PlatoonCancel, //11
|
||||
PlatoonLeave, //12
|
||||
PlatoonDisband, //13
|
||||
Unk14, //14
|
||||
Unk15 //15
|
||||
Invite,
|
||||
Unk01,
|
||||
Accept,
|
||||
Reject,
|
||||
Cancel,
|
||||
Leave,
|
||||
Promote,
|
||||
Disband,
|
||||
PlatoonInvite,
|
||||
PlatoonAccept,
|
||||
PlatoonReject,
|
||||
PlatoonCancel,
|
||||
PlatoonLeave,
|
||||
PlatoonDisband
|
||||
= Value
|
||||
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L)
|
||||
|
||||
def toResponse(request : SquadRequestType.Value) : SquadResponseType.Value = {
|
||||
val id = request.id
|
||||
if(id < 6) SquadResponseType(id)
|
||||
else if(id > 6) SquadResponseType(id - 1)
|
||||
else throw new NoSuchElementException("request does not have an applicable response")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.types
|
||||
|
||||
import net.psforever.packet.PacketHelpers
|
||||
import scodec.codecs._
|
||||
|
||||
object SquadResponseType extends Enumeration {
|
||||
type Type = Value
|
||||
val
|
||||
Invite,
|
||||
Unk01,
|
||||
Accept,
|
||||
Reject,
|
||||
Cancel,
|
||||
Leave,
|
||||
Disband,
|
||||
PlatoonInvite,
|
||||
PlatoonAccept,
|
||||
PlatoonReject,
|
||||
PlatoonCancel,
|
||||
PlatoonLeave,
|
||||
PlatoonDisband
|
||||
= Value
|
||||
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L)
|
||||
|
||||
def fromRequest(response : SquadResponseType.Value) : SquadRequestType.Value = {
|
||||
val id = response.id
|
||||
if(id < 6) SquadRequestType(id)
|
||||
else if(id > 5) SquadRequestType(id + 1)
|
||||
else throw new NoSuchElementException("response does not stem from an applicable request")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,27 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package services.teamwork
|
||||
|
||||
import net.psforever.objects.teamwork.{Member, Squad}
|
||||
import net.psforever.objects.teamwork.Squad
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.SquadRequestType
|
||||
import net.psforever.types.SquadResponseType
|
||||
|
||||
object SquadResponse {
|
||||
trait Response
|
||||
|
||||
final case class Init(info : Vector[SquadInfo]) extends Response
|
||||
final case class Update(infos : Iterable[(Int, SquadInfo)]) extends Response
|
||||
final case class Remove(infos : Iterable[Int]) extends Response
|
||||
final case class ListSquadFavorite(line : Int, task : String) extends Response
|
||||
|
||||
final case class Membership(request_type : SquadRequestType.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 InitList(info : Vector[SquadInfo]) extends Response
|
||||
final case class UpdateList(infos : Iterable[(Int, SquadInfo)]) extends Response
|
||||
final case class RemoveFromList(infos : Iterable[Int]) extends Response
|
||||
|
||||
final case class InitSquad(squad_guid : PlanetSideGUID) extends Response
|
||||
final case class Unknown17(squad : Squad, char_id : Long) extends Response
|
||||
final case class Membership(request_type : SquadResponseType.Value, unk1 : Int, unk2 : Int, unk3 : Long, unk4 : Option[Long], player_name : String, unk5 : Boolean, unk6 : Option[Option[String]]) extends Response //see SquadMembershipResponse
|
||||
final case class Invite(from_char_id : Long, to_char_id : Long, name : String) extends Response
|
||||
final case class Join(squad : Squad, positionsToUpdate : List[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 SwapMember(squad : Squad, from_index : Int, to_index : Int) extends Response
|
||||
|
||||
final case class Detail(guid : PlanetSideGUID, leader : String, task : String, zone : PlanetSideZoneID, member_info : List[SquadPositionDetail]) extends Response
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ package services.teamwork
|
|||
import akka.actor.Actor
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.definition.converter.StatConverter
|
||||
import net.psforever.objects.teamwork.Squad
|
||||
import net.psforever.objects.loadouts.SquadLoadout
|
||||
import net.psforever.objects.teamwork.{Member, Squad}
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.{PlanetSideEmpire, SquadRequestType, Vector3}
|
||||
import net.psforever.types._
|
||||
import services.{GenericEventBus, Service}
|
||||
|
||||
import scala.collection.concurrent.TrieMap
|
||||
|
|
@ -16,7 +17,9 @@ import scala.collection.mutable.ListBuffer
|
|||
//import scala.concurrent.duration._
|
||||
|
||||
class SquadService extends Actor {
|
||||
private var memberToSquad : TrieMap[String, Squad] = new TrieMap[String, Squad]()
|
||||
import SquadService._
|
||||
|
||||
private var memberToSquad : mutable.LongMap[Squad] = mutable.LongMap[Squad]()
|
||||
private var idToSquad : TrieMap[PlanetSideGUID, Squad] = new TrieMap[PlanetSideGUID, Squad]()
|
||||
private var i : Int = 1
|
||||
private val publishedLists : TrieMap[PlanetSideEmpire.Value, ListBuffer[SquadInfo]] = TrieMap[PlanetSideEmpire.Value, ListBuffer[SquadInfo]](
|
||||
|
|
@ -24,35 +27,39 @@ class SquadService extends Actor {
|
|||
PlanetSideEmpire.NC -> ListBuffer.empty,
|
||||
PlanetSideEmpire.VS -> ListBuffer.empty
|
||||
)
|
||||
private val bids : mutable.LongMap[(PlanetSideGUID, Int)] = mutable.LongMap[(PlanetSideGUID, Int)]()
|
||||
private val bids : mutable.LongMap[PositionBid] = mutable.LongMap[PositionBid]()
|
||||
|
||||
private [this] val log = org.log4s.getLogger
|
||||
|
||||
override def preStart = {
|
||||
override def preStart : Unit = {
|
||||
log.info("Starting...")
|
||||
|
||||
val testSquad = new Squad(PlanetSideGUID(3), PlanetSideEmpire.VS)
|
||||
testSquad.Task = "\\#66CCFF Sentinels of Auraxis\\#FFFFFF ... \\#40FF40 Squad Up!!"
|
||||
testSquad.ZoneId = 5
|
||||
testSquad.Membership(0).Name = "Wizkid45"
|
||||
testSquad.Membership(0).Role = "Fearless Leader"
|
||||
testSquad.Membership(0).CharId = 30910985L
|
||||
testSquad.Membership(0).ZoneId = 5
|
||||
testSquad.Membership(0).Health = 64
|
||||
testSquad.Membership(0).Armor = 34
|
||||
testSquad.Membership(0).Position = Vector3(5526.5234f, 3818.7344f, 54.59375f)
|
||||
testSquad.Membership(1).Name = "xoBLADEox"
|
||||
testSquad.Membership(1).Role = "Right Hand"
|
||||
testSquad.Membership(1).CharId = 42781919L
|
||||
testSquad.Membership(1).ZoneId = 5
|
||||
testSquad.Membership(1).Health = 54
|
||||
testSquad.Membership(1).Armor = 44
|
||||
testSquad.Membership(1).Position = Vector3(4673.5312f, 2604.8047f, 40.015625f)
|
||||
testSquad.Membership(3).Name = "cabal0428"
|
||||
testSquad.Membership(3).Role = "Left Hand"
|
||||
testSquad.Membership(3).CharId = 353380L
|
||||
testSquad.Membership(3).ZoneId = 5
|
||||
testSquad.Membership(3).Health = 44
|
||||
testSquad.Membership(3).Armor = 54
|
||||
testSquad.Membership(3).Position = Vector3(4727.492f, 2613.5312f, 51.390625f)
|
||||
testSquad.Membership(4).Name = "xSkiku"
|
||||
testSquad.Membership(4).Role = "Right Foot's Middle Toe's Nail"
|
||||
testSquad.Membership(4).CharId = 41588340L
|
||||
testSquad.Membership(4).ZoneId = 5
|
||||
testSquad.Membership(4).Health = 34
|
||||
|
|
@ -60,7 +67,7 @@ class SquadService extends Actor {
|
|||
testSquad.Membership(4).Position = Vector3(3675.0f, 4789.8047f, 63.21875f)
|
||||
idToSquad(PlanetSideGUID(3)) = testSquad
|
||||
testSquad.Listed = true
|
||||
UpdateSquadList(testSquad, List())
|
||||
UpdateSquadList(testSquad, None)
|
||||
}
|
||||
|
||||
def GetNextSquadId() : PlanetSideGUID = {
|
||||
|
|
@ -76,7 +83,7 @@ class SquadService extends Actor {
|
|||
}
|
||||
|
||||
def GetParticipatingSquad(player : Player, zone : Int) : Option[Squad] = {
|
||||
memberToSquad.get(player.Name) match {
|
||||
memberToSquad.get(player.CharId) match {
|
||||
case opt @ Some(squad) =>
|
||||
squad.Membership.find(_.Name == player.Name).get.ZoneId = zone
|
||||
opt
|
||||
|
|
@ -86,10 +93,10 @@ class SquadService extends Actor {
|
|||
}
|
||||
|
||||
def GetLeadingSquad(player : Player, zone : Int, opt : Option[Squad]) : Squad = {
|
||||
val name = player.Name
|
||||
val charId = player.CharId
|
||||
val squadOut = opt match {
|
||||
case Some(squad) =>
|
||||
if(squad.Leader.Name.equals(name)) {
|
||||
if(squad.Leader.CharId == charId) {
|
||||
squad
|
||||
}
|
||||
else {
|
||||
|
|
@ -97,21 +104,23 @@ class SquadService extends Actor {
|
|||
}
|
||||
|
||||
case None =>
|
||||
memberToSquad.get(name) match {
|
||||
case Some(squad) if squad.Leader.Name.equals(name) =>
|
||||
memberToSquad.get(charId) match {
|
||||
case Some(squad) if squad.Leader.CharId.equals(charId) =>
|
||||
squad
|
||||
case _ =>
|
||||
val faction = player.Faction
|
||||
val id = GetNextSquadId()
|
||||
val squad = new Squad(id, faction)
|
||||
val leadPosition = squad.Membership(squad.LeaderPositionIndex)
|
||||
val name = player.Name
|
||||
leadPosition.Name = name
|
||||
leadPosition.CharId = charId
|
||||
leadPosition.Health = player.Health
|
||||
leadPosition.Armor = player.Armor
|
||||
leadPosition.Position = player.Position
|
||||
leadPosition.ZoneId = zone
|
||||
log.info(s"$name-$faction has started a new squad")
|
||||
memberToSquad += name -> squad
|
||||
memberToSquad += charId -> squad
|
||||
idToSquad += id -> squad
|
||||
squad
|
||||
}
|
||||
|
|
@ -130,26 +139,27 @@ class SquadService extends Actor {
|
|||
log.info(s"$who has joined $path")
|
||||
SquadEvents.subscribe(who, path)
|
||||
//send initial squad catalog
|
||||
sender ! SquadServiceResponse(s"$faction/Squad", SquadResponse.Init(publishedLists(PlanetSideEmpire(faction)).toVector))
|
||||
sender ! SquadServiceResponse(s"$faction/Squad", SquadResponse.InitList(publishedLists(PlanetSideEmpire(faction)).toVector))
|
||||
|
||||
//subscribe to the player's personal channel - necessary only to inform about any previous squad association
|
||||
case Service.Join(name) =>
|
||||
val path = s"$name/Squad"
|
||||
case Service.Join(char_id) =>
|
||||
val path = s"$char_id/Squad"
|
||||
val who = sender()
|
||||
log.info(s"$who has joined $path")
|
||||
SquadEvents.subscribe(who, path) //TODO squad-specific switchboard
|
||||
//check for renewable squad information
|
||||
memberToSquad.get(name) match {
|
||||
memberToSquad.get(char_id.toLong) match {
|
||||
case None => ;
|
||||
case Some(_) =>
|
||||
sender ! SquadServiceMessage.RecoverSquadMembership() //TODO?
|
||||
}
|
||||
|
||||
case Service.Leave(Some(name)) => ;
|
||||
case Service.Leave(Some(char_id)) => ;
|
||||
SquadEvents.unsubscribe(sender())
|
||||
memberToSquad.get(name) match {
|
||||
val longCharId = char_id.toLong
|
||||
memberToSquad.get(longCharId) match {
|
||||
case Some(squad) =>
|
||||
if(squad.Leader.Name.equals(name)) {
|
||||
if(squad.Leader.Name.equals(char_id)) {
|
||||
//we were the leader
|
||||
if(squad.Membership.count(p => p.Name.equals("")) > 1) {
|
||||
//other players were in the squad; publicly disband it
|
||||
|
|
@ -162,19 +172,19 @@ class SquadService extends Actor {
|
|||
position.Armor = 0
|
||||
})
|
||||
}
|
||||
memberToSquad.remove(name)
|
||||
memberToSquad.remove(longCharId)
|
||||
idToSquad.remove(squad.GUID)
|
||||
UpdateSquadList(squad, List())
|
||||
UpdateSquadList(squad, None)
|
||||
}
|
||||
else {
|
||||
//we were just a grunt in the squad
|
||||
val position = squad.Membership.find(_.Name == name).get
|
||||
val position = squad.Membership.find(_.CharId == longCharId).get
|
||||
position.Name = ""
|
||||
position.ZoneId = 0
|
||||
position.Position = Vector3.Zero
|
||||
position.Health = 0
|
||||
position.Armor = 0
|
||||
UpdateSquadDetail(squad)
|
||||
UpdateSquadDetail(squad.GUID, squad)
|
||||
}
|
||||
case None => ;
|
||||
}
|
||||
|
|
@ -184,67 +194,116 @@ class SquadService extends Actor {
|
|||
|
||||
case SquadServiceMessage(tplayer, squad_action) => squad_action match {
|
||||
case SquadAction.Membership(request_type, char_id, optional_char_id, _, _) => request_type match {
|
||||
case SquadRequestType.Accept =>
|
||||
bids.get(char_id) match {
|
||||
case Some((squadGUID, line)) if idToSquad.get(squadGUID).nonEmpty =>
|
||||
//join squad
|
||||
val squad = idToSquad(squadGUID)
|
||||
val position = squad.Membership(line)
|
||||
if(squad.Availability(line) && position.CharId == 0 &&
|
||||
tplayer.Certifications.intersect(position.Requirements) == position.Requirements) {
|
||||
position.Name = tplayer.Name
|
||||
position.CharId = char_id
|
||||
position.Health = tplayer.Health
|
||||
position.Armor = tplayer.Armor
|
||||
position.Position = tplayer.Position
|
||||
position.ZoneId = 13
|
||||
memberToSquad(tplayer.Name) = squad
|
||||
//joining the squad
|
||||
sender ! SquadServiceResponse("", SquadResponse.Join(
|
||||
squad,
|
||||
squad.Membership.zipWithIndex.collect({ case (member, index) if member.CharId != 0 => index }).toList
|
||||
))
|
||||
//other squad members see us joining the squad
|
||||
val updatedIndex = List(line)
|
||||
squad.Membership
|
||||
.collect({ case member if member.CharId != 0 && member.CharId != char_id => member.Name })
|
||||
.foreach { name =>
|
||||
SquadEvents.publish( SquadServiceResponse(s"$name/Squad", SquadResponse.Join(squad, updatedIndex)) )
|
||||
}
|
||||
}
|
||||
bids.remove(char_id)
|
||||
case SquadRequestType.Invite =>
|
||||
(optional_char_id, memberToSquad.get(char_id)) match {
|
||||
case (Some(toCharId), Some(squad)) =>
|
||||
bids(toCharId) = VacancyBid(char_id, squad.GUID)
|
||||
SquadEvents.publish( SquadServiceResponse(s"$toCharId/Squad", SquadResponse.Invite(char_id, toCharId, tplayer.Name)) )
|
||||
case (Some(toCharId), None) =>
|
||||
val ourSquad = GetLeadingSquad(tplayer, 1, None)
|
||||
memberToSquad.remove(char_id)
|
||||
bids(toCharId) = CamraderieBid(char_id, ourSquad)
|
||||
SquadEvents.publish( SquadServiceResponse(s"$toCharId/Squad", SquadResponse.Invite(char_id, toCharId, tplayer.Name)) )
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
case SquadRequestType.Leave =>
|
||||
val squad = memberToSquad(char_id)
|
||||
if(optional_char_id.contains(char_id)) {
|
||||
//we're leaving the squad on our own
|
||||
val name = tplayer.Name
|
||||
val squad = memberToSquad(name)
|
||||
val membership = squad.Membership.zipWithIndex
|
||||
val (member, index) = membership
|
||||
.find { case (_member, _) => _member.Name == name }
|
||||
.get
|
||||
val updateList = membership.collect({ case (_member, _index) if _member.CharId > 0 => (_member.CharId, _index) }).toList
|
||||
memberToSquad.remove(name)
|
||||
member.Name = ""
|
||||
member.CharId = 0
|
||||
//leaving the squad completely
|
||||
//leaving the squad of our own accord
|
||||
}
|
||||
else {
|
||||
val leader = squad.Leader
|
||||
if(optional_char_id.contains(leader.CharId)) {
|
||||
//kicked by the squad leader
|
||||
}
|
||||
}
|
||||
val membership = squad.Membership.zipWithIndex
|
||||
val (member, index) = membership
|
||||
.find { case (_member, _) => _member.CharId == char_id }
|
||||
.get
|
||||
val updateList = membership.collect({ case (_member, _index) if _member.CharId > 0 => (_member.CharId, _index) }).toList
|
||||
memberToSquad.remove(char_id)
|
||||
member.Name = ""
|
||||
member.CharId = 0
|
||||
|
||||
val size = squad.Size
|
||||
if(size == 1) {
|
||||
//squad is rendered to just one person; collapse it
|
||||
(membership.collect({ case (_member, _) if _member.CharId > 0 => _member.CharId }) :+ char_id)
|
||||
.foreach { charId =>
|
||||
SquadEvents.publish( SquadServiceResponse(s"$charId/Squad", SquadResponse.Leave(squad, updateList)) )
|
||||
memberToSquad.remove(charId)
|
||||
}
|
||||
idToSquad.remove(squad.GUID)
|
||||
}
|
||||
else {
|
||||
//squad continues, despite player's parting
|
||||
//member leaves the squad completely
|
||||
sender ! SquadServiceResponse("", SquadResponse.Leave(squad, updateList))
|
||||
//other squad members see us leaving the squad
|
||||
//other squad members see the member leaving only
|
||||
val leavingMember = List((char_id, index))
|
||||
membership
|
||||
.collect({ case (_member, _) if _member.CharId > 0 => _member.Name })
|
||||
.foreach { name =>
|
||||
SquadEvents.publish( SquadServiceResponse(s"$name/Squad", SquadResponse.Leave(squad, leavingMember)) )
|
||||
}
|
||||
.collect({ case (_member, _) if _member.CharId > 0 => _member.CharId })
|
||||
.foreach { charId =>
|
||||
SquadEvents.publish( SquadServiceResponse(s"$charId/Squad", SquadResponse.Leave(squad, leavingMember)) )
|
||||
}
|
||||
}
|
||||
|
||||
case SquadRequestType.Accept =>
|
||||
bids.remove(char_id) match {
|
||||
case Some(NormalBid(inviterCharId, squadGUID, line)) if idToSquad.get(squadGUID).nonEmpty =>
|
||||
JoinSquad(tplayer, idToSquad(squadGUID), line)
|
||||
|
||||
case Some(VacancyBid(inviterCharId, squadGUID)) if idToSquad.get(squadGUID).nonEmpty =>
|
||||
val squad = idToSquad(squadGUID)
|
||||
squad.Membership.zipWithIndex.find({ case (member, index) =>
|
||||
ValidOpenSquadPosition(squad, index, squad.Membership(index), tplayer.Certifications)
|
||||
}) match {
|
||||
case Some((_, line)) =>
|
||||
JoinSquad(tplayer, squad, line)
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
case Some(CamraderieBid(inviterCharId, placeholderSquad)) =>
|
||||
(GetParticipatingSquad(tplayer, 1) match {
|
||||
case Some(participating) =>
|
||||
if(participating.Leader.CharId == inviterCharId) {
|
||||
Some(participating)
|
||||
}
|
||||
else {
|
||||
//inviter is not leader of squad; bounce this request off of the squad leader
|
||||
val leaderCharId = participating.Leader.CharId
|
||||
bids(char_id) = VacancyBid(leaderCharId, participating.GUID) //reframed request
|
||||
//TODO squad leader receives "<player> wants to join squad" prompt
|
||||
//SquadEvents.publish(SquadServiceResponse(s"$leaderCharId/Squad", SquadResponse.Invite(char_id, leaderCharId, tplayer.Name)))
|
||||
None
|
||||
}
|
||||
case None =>
|
||||
placeholderSquad.Task = s"${tplayer.Name}'s Squad"
|
||||
memberToSquad(inviterCharId) = placeholderSquad
|
||||
SquadEvents.publish(SquadServiceResponse(s"$inviterCharId/Squad", SquadResponse.InitSquad(placeholderSquad.GUID)))
|
||||
Some(placeholderSquad)
|
||||
}) match {
|
||||
case Some(squad) =>
|
||||
squad.Membership.zipWithIndex.find({ case (member, index) =>
|
||||
ValidOpenSquadPosition(squad, index, squad.Membership(index), tplayer.Certifications)
|
||||
}) match {
|
||||
case Some((_, line)) =>
|
||||
JoinSquad(tplayer, squad, line)
|
||||
case _ => ;
|
||||
}
|
||||
case None => ;
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
case SquadAction.Update(char_id, health, max_health, armor, max_armor, pos, zone_number) =>
|
||||
memberToSquad.get(tplayer.Name) match {
|
||||
memberToSquad.get(char_id) match {
|
||||
case Some(squad) =>
|
||||
squad.Membership.find(_.CharId == char_id) match {
|
||||
case Some(member) =>
|
||||
|
|
@ -263,46 +322,64 @@ class SquadService extends Actor {
|
|||
))
|
||||
case _ => ;
|
||||
}
|
||||
// val (self, others) = squad.Membership.partition(_.CharId == char_id)
|
||||
// self match {
|
||||
// case Array(member) =>
|
||||
// val newHealth = StatConverter.Health(health, max_health, min=1, max=64)
|
||||
// val newArmor = StatConverter.Health(armor, max_armor, min=1, max=64)
|
||||
// member.Health = newHealth
|
||||
// member.Armor = newArmor
|
||||
// member.Position = pos
|
||||
// member.ZoneId = zone_number
|
||||
// sender ! SquadServiceResponse("", SquadResponse.UpdateMembers(
|
||||
// squad,
|
||||
// others
|
||||
// .map { member => SquadAction.Update(member.CharId, member.Health, 0, member.Armor, 0, member.Position, member.ZoneId) }
|
||||
// .toList
|
||||
// ))
|
||||
// case _ => ;
|
||||
// }
|
||||
|
||||
case None => ;
|
||||
}
|
||||
|
||||
case SquadAction.Definition(tplayer : Player, zone_ordinal_number : Int, guid : PlanetSideGUID, _ : Int, action : SquadAction) =>
|
||||
case SquadAction.Definition(tplayer : Player, zone_ordinal_number : Int, guid : PlanetSideGUID, line : Int, action : SquadAction) =>
|
||||
import net.psforever.packet.game.SquadAction._
|
||||
val squadOpt = GetParticipatingSquad(tplayer, zone_ordinal_number)
|
||||
action match {
|
||||
case SaveSquadDefinition() =>
|
||||
case SaveSquadFavorite() =>
|
||||
squadOpt match {
|
||||
case Some(squad) if squad.Leader.CharId == tplayer.CharId && squad.Task.nonEmpty && squad.ZoneId > 0 =>
|
||||
tplayer.SquadLoadouts.SaveLoadout(squad, squad.Task, line)
|
||||
sender ! SquadServiceResponse("", SquadResponse.ListSquadFavorite(line, squad.Task))
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
case LoadSquadFavorite() =>
|
||||
(squadOpt match {
|
||||
case Some(squad) if squad.Size == 1 =>
|
||||
//we are the leader of our own squad, but no one else has joined yet
|
||||
Some(squad)
|
||||
case None => ;
|
||||
//we are not yet member of a squad; start a squad to support our favorite definition
|
||||
Some(GetLeadingSquad(tplayer, zone_ordinal_number, None))
|
||||
case _ => ;
|
||||
//player is member of populated squad; should not overwrite squad definition with favorite
|
||||
None
|
||||
}, tplayer.SquadLoadouts.LoadLoadout(line)) match {
|
||||
case (Some(squad : Squad), Some(loadout : SquadLoadout)) =>
|
||||
log.info(s"${tplayer.Name} is loading a squad composition: $loadout")
|
||||
SquadService.LoadSquadDefinition(squad, loadout)
|
||||
sender ! SquadServiceResponse("", SquadResponse.InitSquad(squad.GUID))
|
||||
UpdateSquadList(squad, SquadInfo().Task(squad.Task).ZoneId(PlanetSideZoneID(squad.ZoneId)).Capacity(squad.Capacity))
|
||||
UpdateSquadDetail(PlanetSideGUID(0), squad)
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
case DeleteSquadFavorite() =>
|
||||
tplayer.SquadLoadouts.DeleteLoadout(line)
|
||||
sender ! SquadServiceResponse("", SquadResponse.ListSquadFavorite(line, ""))
|
||||
|
||||
case ChangeSquadPurpose(purpose) =>
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed his squad's task to $purpose")
|
||||
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
|
||||
squad.Task = purpose
|
||||
UpdateSquadList(squad, List(SquadInfo.Field.Task))
|
||||
UpdateSquadDetail(squad)
|
||||
|
||||
UpdateSquadListWhenListed(squad, SquadInfo().Task(purpose))
|
||||
SquadDetail().Task(purpose)
|
||||
UpdateListedSquadDetail(squad.GUID, squad)
|
||||
|
||||
case ChangeSquadZone(zone) =>
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed squad's ops zone to $zone")
|
||||
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
|
||||
squad.ZoneId = zone.zoneId.toInt
|
||||
UpdateSquadList(squad, List(SquadInfo.Field.ZoneId))
|
||||
UpdateSquadDetail(squad)
|
||||
UpdateSquadListWhenListed(squad, SquadInfo().ZoneId(zone))
|
||||
SquadDetail().ZoneId(zone)
|
||||
UpdateListedSquadDetail(squad.GUID, squad)
|
||||
|
||||
case CloseSquadMemberPosition(position) =>
|
||||
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
|
||||
|
|
@ -312,14 +389,15 @@ class SquadService extends Actor {
|
|||
log.info(s"${tplayer.Name}-${tplayer.Faction} has closed the #$position position in squad")
|
||||
val memberPosition = squad.Membership(position)
|
||||
val listingChanged = if(memberPosition.Name.nonEmpty) {
|
||||
List(SquadInfo.Field.Size, SquadInfo.Field.Capacity)
|
||||
SquadInfo().Size(squad.Size).Capacity(squad.Capacity)
|
||||
}
|
||||
else {
|
||||
List(SquadInfo.Field.Capacity)
|
||||
SquadInfo().Capacity(squad.Capacity)
|
||||
}
|
||||
memberPosition.Close()
|
||||
UpdateSquadList(squad, listingChanged)
|
||||
UpdateSquadDetail(squad)
|
||||
UpdateSquadListWhenListed(squad, listingChanged)
|
||||
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail.Closed)))
|
||||
UpdateListedSquadDetail(squad.GUID, squad)
|
||||
case Some(false) | None => ;
|
||||
}
|
||||
|
||||
|
|
@ -329,8 +407,9 @@ class SquadService extends Actor {
|
|||
case Some(false) =>
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened the #$position position in squad")
|
||||
squad.Availability.update(position, true)
|
||||
UpdateSquadList(squad, List(SquadInfo.Field.Capacity))
|
||||
UpdateSquadDetail(squad)
|
||||
UpdateSquadListWhenListed(squad, SquadInfo().Capacity(squad.Capacity))
|
||||
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail.Open)))
|
||||
UpdateListedSquadDetail(squad.GUID, squad)
|
||||
case Some(true) | None => ;
|
||||
}
|
||||
|
||||
|
|
@ -340,7 +419,8 @@ class SquadService extends Actor {
|
|||
case Some(true) =>
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the role of squad position #$position")
|
||||
squad.Membership(position).Role = role
|
||||
UpdateSquadDetail(squad)
|
||||
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail().Role(role))))
|
||||
UpdateListedSquadDetail(squad.GUID, squad)
|
||||
case Some(false) | None => ;
|
||||
}
|
||||
|
||||
|
|
@ -350,7 +430,8 @@ class SquadService extends Actor {
|
|||
case Some(true) =>
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the orders for squad position #$position")
|
||||
squad.Membership(position).Orders = orders
|
||||
UpdateSquadDetail(squad)
|
||||
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail().DetailedOrders(orders))))
|
||||
UpdateListedSquadDetail(squad.GUID, squad)
|
||||
case Some(false) | None => ;
|
||||
}
|
||||
|
||||
|
|
@ -360,7 +441,8 @@ class SquadService extends Actor {
|
|||
case Some(true) =>
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the requirements for squad position #$position")
|
||||
squad.Membership(position).Requirements = certs
|
||||
UpdateSquadDetail(squad)
|
||||
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail().Requirements(certs))))
|
||||
UpdateListedSquadDetail(squad.GUID, squad)
|
||||
case Some(false) | None => ;
|
||||
}
|
||||
|
||||
|
|
@ -386,12 +468,47 @@ class SquadService extends Actor {
|
|||
|
||||
case SelectRoleForYourself(position) =>
|
||||
//TODO need to ask permission from the squad leader, unless our character is the squad leader or already currently in the squad
|
||||
//val name = tplayer.Name
|
||||
squadOpt match {
|
||||
case Some(squad) if squad.GUID == guid =>
|
||||
//already a member of this squad; swap positions freely
|
||||
case Some(squad) if squad.GUID == guid || squad.Leader.Name == tplayer.Name =>
|
||||
//already a member of this squad, or the leader; swap positions freely
|
||||
val membership = squad.Membership.zipWithIndex
|
||||
val toMember = squad.Membership(position)
|
||||
if(ValidOpenSquadPosition(squad, position, toMember, tplayer.Certifications)) {
|
||||
//acquire this membership position
|
||||
membership.find { case (member, _) => member.Name == tplayer.Name } match {
|
||||
case Some((fromMember, fromIndex)) =>
|
||||
//copy member details
|
||||
toMember.Name = fromMember.Name
|
||||
toMember.CharId = fromMember.CharId
|
||||
toMember.Health = fromMember.Health
|
||||
toMember.Armor = fromMember.Armor
|
||||
toMember.Position = fromMember.Position
|
||||
toMember.ZoneId = fromMember.ZoneId
|
||||
//old membership no longer valid
|
||||
fromMember.Name = ""
|
||||
fromMember.CharId = 0L
|
||||
if(fromIndex == squad.LeaderPositionIndex) {
|
||||
squad.LeaderPositionIndex = position
|
||||
}
|
||||
membership
|
||||
.collect({ case (_member, _) if _member.CharId > 0 => _member.Name })
|
||||
.foreach { name =>
|
||||
SquadEvents.publish(SquadServiceResponse(s"$name/Squad", SquadResponse.SwapMember(squad, fromIndex, position)))
|
||||
}
|
||||
case None =>
|
||||
toMember.Name = tplayer.Name
|
||||
toMember.CharId = tplayer.CharId
|
||||
toMember.Health = StatConverter.Health(tplayer.Health, tplayer.MaxHealth, min = 1, max = 64)
|
||||
toMember.Armor = StatConverter.Health(tplayer.Armor, tplayer.MaxArmor, min = 1, max = 64)
|
||||
toMember.Position = tplayer.Position
|
||||
toMember.ZoneId = 13
|
||||
memberToSquad(tplayer.CharId) = squad
|
||||
}
|
||||
}
|
||||
UpdateSquadDetail(squad.GUID, squad)
|
||||
|
||||
case Some(_) =>
|
||||
//not a member of the requesting squad; do nothing
|
||||
//not a member of the requesting squad; do nothing
|
||||
case None =>
|
||||
//not a member of any squad; consider request of joining the target squad
|
||||
idToSquad.get(guid) match {
|
||||
|
|
@ -399,10 +516,10 @@ class SquadService extends Actor {
|
|||
val member = squad.Membership(position)
|
||||
if(squad.Availability(position) && member.CharId == 0 &&
|
||||
tplayer.Certifications.intersect(member.Requirements) == member.Requirements) {
|
||||
bids(tplayer.CharId) = (guid, position)
|
||||
bids(tplayer.CharId) = NormalBid(squad.Leader.CharId, guid, position)
|
||||
val leader = squad.Leader
|
||||
//TODO need to ask permission from the squad leader, unless auto-approve is in effect
|
||||
sender ! SquadServiceResponse("", SquadResponse.Membership(SquadRequestType.Invite, 0, 0, leader.CharId, Some(tplayer.CharId), leader.Name, false, None))
|
||||
sender ! SquadServiceResponse("", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, leader.CharId, Some(tplayer.CharId), leader.Name, false, None))
|
||||
}
|
||||
|
||||
case None =>
|
||||
|
|
@ -411,36 +528,62 @@ class SquadService extends Actor {
|
|||
}
|
||||
}
|
||||
|
||||
case ListSquad() =>
|
||||
case RequestListSquad() =>
|
||||
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
|
||||
if(!squad.Listed) {
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened recruitment for this squad")
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened public recruitment for squad ${squad.Task}")
|
||||
squad.Listed = true
|
||||
sender ! SquadServiceResponse("", SquadResponse.InitSquad(squad.GUID))
|
||||
UpdateSquadList(squad, None)
|
||||
sender ! SquadServiceResponse("", SquadResponse.Unknown17(squad, tplayer.CharId))
|
||||
}
|
||||
|
||||
case StopListSquad() =>
|
||||
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
|
||||
if(!squad.Listed) {
|
||||
log.info(s"${tplayer.Name}-${tplayer.Faction} has closed public recruitment for squad ${squad.Task}")
|
||||
squad.Listed = false
|
||||
sender ! SquadServiceResponse("", SquadResponse.InitSquad(PlanetSideGUID(0)))
|
||||
UpdateSquadList(squad, None)
|
||||
}
|
||||
UpdateSquadList(squad, List())
|
||||
|
||||
case ResetAll() =>
|
||||
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
|
||||
squad.Task = ""
|
||||
squad.ZoneId = None
|
||||
squad.Availability.indices.foreach { i =>
|
||||
squad.Availability.update(i, true)
|
||||
squadOpt match {
|
||||
case Some(squad) if squad.Leader.CharId == tplayer.CharId =>
|
||||
squad.Task = ""
|
||||
squad.ZoneId = None
|
||||
squad.Availability.indices.foreach { i =>
|
||||
squad.Availability.update(i, true)
|
||||
}
|
||||
squad.Membership.foreach(position => {
|
||||
position.Role = ""
|
||||
position.Orders = ""
|
||||
position.Requirements = Set()
|
||||
})
|
||||
squad.LocationFollowsSquadLead = false
|
||||
squad.AutoApproveInvitationRequests = false
|
||||
UpdateSquadList(squad, SquadInfo().Task("").ZoneId(None).Capacity(squad.Capacity))
|
||||
UpdateSquadDetail(squad.GUID, squad)
|
||||
case None => ;
|
||||
}
|
||||
squad.Membership.foreach(position => {
|
||||
position.Role = ""
|
||||
position.Orders = ""
|
||||
position.Requirements = Set()
|
||||
})
|
||||
UpdateSquadList(squad, List(SquadInfo.Field.Task, SquadInfo.Field.ZoneId, SquadInfo.Field.Size, SquadInfo.Field.Capacity))
|
||||
UpdateSquadDetail(squad)
|
||||
|
||||
case DisplaySquad() =>
|
||||
idToSquad.get(guid) match {
|
||||
case Some(squad) =>
|
||||
sender ! SquadServiceResponse(s"${tplayer.Name}/Squad", GenSquadDetail(squad))
|
||||
sender ! SquadServiceResponse(s"${tplayer.Name}/Squad", GenSquadDetail(squad.GUID, squad))
|
||||
case None => ;
|
||||
}
|
||||
|
||||
// case AnswerSquadJoinRequest() =>
|
||||
// idToSquad.get(guid) match {
|
||||
// case Some(squad) =>
|
||||
// if(squad.Leader.Name == tplayer.Name && squad.Listed) {
|
||||
// //squad was just listed but we have not yet declared ourselves as the leader
|
||||
// UpdateSquadDetail(squad)
|
||||
// }
|
||||
// case None => ;
|
||||
// }
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
|
|
@ -449,54 +592,109 @@ class SquadService extends Actor {
|
|||
}
|
||||
}
|
||||
|
||||
def UpdateSquadList(squad : Squad, listingChanged : List[Int]) : Unit = {
|
||||
//queue updates
|
||||
def JoinSquad(player : Player, squad : Squad, line : Int) : Boolean = {
|
||||
val charId = player.CharId
|
||||
val position = squad.Membership(line)
|
||||
if(squad.Availability(line) && position.CharId == 0 &&
|
||||
player.Certifications.intersect(position.Requirements) == position.Requirements) {
|
||||
position.Name = player.Name
|
||||
position.CharId = charId
|
||||
position.Health = StatConverter.Health(player.Health, player.MaxHealth, min=1, max=64)
|
||||
position.Armor = StatConverter.Health(player.Armor, player.MaxArmor, min=1, max=64)
|
||||
position.Position = player.Position
|
||||
position.ZoneId = 13
|
||||
memberToSquad(player.CharId) = squad
|
||||
|
||||
val size = squad.Size
|
||||
if(size == 1) {
|
||||
//leader joins the squad? do nothing?
|
||||
squad.LeaderPositionIndex = line
|
||||
}
|
||||
else if(size == 2) {
|
||||
//first squad member after leader; both members fully initialize
|
||||
val indices = squad.Membership.zipWithIndex.collect({ case (member, index) if member.CharId != 0 => index }).toList
|
||||
squad.Membership
|
||||
.collect({ case member if member.CharId != 0 => member.CharId })
|
||||
.foreach { charId =>
|
||||
SquadEvents.publish(SquadServiceResponse(s"$charId/Squad", SquadResponse.Join(squad, indices)))
|
||||
}
|
||||
}
|
||||
else {
|
||||
//joining an active squad; everybody updates differently
|
||||
//new member gets full UI updates
|
||||
sender ! SquadServiceResponse("", SquadResponse.Join(
|
||||
squad,
|
||||
squad.Membership.zipWithIndex.collect({ case (member, index) if member.CharId != 0 => index }).toList
|
||||
))
|
||||
//other squad members see us joining the squad
|
||||
val updatedIndex = List(line)
|
||||
squad.Membership
|
||||
.collect({ case member if member.CharId != 0 && member.CharId != charId => member.CharId })
|
||||
.foreach { charId =>
|
||||
SquadEvents.publish(SquadServiceResponse(s"$charId/Squad", SquadResponse.Join(squad, updatedIndex)))
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
def UpdateSquadList(squad : Squad, changes : SquadInfo) : Unit = {
|
||||
UpdateSquadList(squad, Some(changes))
|
||||
}
|
||||
|
||||
def UpdateSquadListWhenListed(squad : Squad, changes : SquadInfo) : Unit = {
|
||||
UpdateSquadListWhenListed(squad, Some(changes))
|
||||
}
|
||||
|
||||
def UpdateSquadListWhenListed(squad : Squad, changes : Option[SquadInfo]) : Unit = {
|
||||
if(squad.Listed) {
|
||||
val entry = SquadService.Publish(squad)
|
||||
val faction = squad.Faction
|
||||
val factionListings = publishedLists(faction)
|
||||
factionListings.find(info => {
|
||||
info.squad_guid match {
|
||||
case Some(sguid) => sguid == squad.GUID
|
||||
case _ => false
|
||||
}
|
||||
}) match {
|
||||
case Some(listedSquad) =>
|
||||
val index = factionListings.indexOf(listedSquad)
|
||||
val changes = if(listingChanged.nonEmpty) {
|
||||
SquadService.Differences(listingChanged, entry)
|
||||
}
|
||||
else {
|
||||
SquadService.Differences(listedSquad, entry)
|
||||
}
|
||||
if(changes != SquadInfo.Blank) {
|
||||
UpdateSquadList(squad, changes)
|
||||
}
|
||||
}
|
||||
|
||||
def UpdateSquadList(squad : Squad, changes : Option[SquadInfo]) : Unit = {
|
||||
val entry = SquadService.SquadList.Publish(squad)
|
||||
val faction = squad.Faction
|
||||
val factionListings = publishedLists(faction)
|
||||
factionListings.find(info => {
|
||||
info.squad_guid match {
|
||||
case Some(sguid) => sguid == squad.GUID
|
||||
case _ => false
|
||||
}
|
||||
}) match {
|
||||
case Some(listedSquad) =>
|
||||
val index = factionListings.indexOf(listedSquad)
|
||||
changes match {
|
||||
case Some(changedFields) =>
|
||||
//squad information update
|
||||
log.info(s"Squad will be updated")
|
||||
factionListings(index) = entry
|
||||
SquadEvents.publish(
|
||||
SquadServiceResponse(s"$faction/Squad", SquadResponse.Update(Seq((index, changes))))
|
||||
SquadServiceResponse(s"$faction/Squad", SquadResponse.UpdateList(Seq((index, changedFields))))
|
||||
)
|
||||
}
|
||||
else {
|
||||
case None =>
|
||||
//remove squad from listing
|
||||
log.info(s"Squad will be removed")
|
||||
factionListings.remove(index)
|
||||
SquadEvents.publish(
|
||||
SquadServiceResponse(s"$faction/Squad", SquadResponse.Remove(Seq(index)))
|
||||
//SquadServiceResponse(s"$faction/Squad", SquadResponse.RemoveFromList(Seq(index)))
|
||||
SquadServiceResponse(s"$faction/Squad", SquadResponse.InitList(factionListings.toVector))
|
||||
)
|
||||
}
|
||||
case None =>
|
||||
//first time being published
|
||||
log.info(s"Squad will be introduced")
|
||||
factionListings += SquadService.Publish(squad)
|
||||
SquadEvents.publish(
|
||||
SquadServiceResponse(s"$faction/Squad", SquadResponse.Init(factionListings.toVector))
|
||||
)
|
||||
}
|
||||
}
|
||||
case None =>
|
||||
//first time being published
|
||||
log.info(s"Squad will be introduced")
|
||||
factionListings += entry
|
||||
SquadEvents.publish(
|
||||
SquadServiceResponse(s"$faction/Squad", SquadResponse.InitList(factionListings.toVector))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def GenSquadDetail(squad : Squad) : SquadResponse.Detail = {
|
||||
def GenSquadDetail(guid : PlanetSideGUID, squad : Squad) : SquadResponse.Detail = {
|
||||
SquadResponse.Detail(
|
||||
squad.GUID,
|
||||
squad.Leader.Name,
|
||||
|
|
@ -513,60 +711,164 @@ class SquadService extends Actor {
|
|||
)
|
||||
}
|
||||
|
||||
def UpdateSquadDetail(squad : Squad) : Unit = {
|
||||
val detail = GenSquadDetail(squad)
|
||||
squad.Membership.collect {
|
||||
case member if !member.Name.equals("") =>
|
||||
member.Name
|
||||
}.foreach { name =>
|
||||
SquadEvents.publish(
|
||||
SquadServiceResponse(s"$name/Squad", detail)
|
||||
)
|
||||
def UpdateListedSquadDetail(guid : PlanetSideGUID, squad : Squad) : Unit = {
|
||||
if(squad.Listed) {
|
||||
UpdateSquadDetail(guid, squad)
|
||||
}
|
||||
}
|
||||
|
||||
def UpdateSquadDetail(guid : PlanetSideGUID, squad : Squad) : Unit = {
|
||||
val detail = GenSquadDetail(guid, squad)
|
||||
squad.Membership
|
||||
.collect { case member if member.CharId > 0L => member.CharId }
|
||||
.foreach { charId =>
|
||||
SquadEvents.publish(SquadServiceResponse(s"$charId/Squad", detail))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object SquadService {
|
||||
def Publish(squad : Squad) : SquadInfo = {
|
||||
SquadInfo(
|
||||
squad.Leader.Name,
|
||||
squad.Task,
|
||||
PlanetSideZoneID(squad.ZoneId),
|
||||
squad.Size,
|
||||
squad.Capacity,
|
||||
squad.GUID
|
||||
)
|
||||
}
|
||||
trait PositionBid
|
||||
|
||||
def Differences(updates : List[Int], info : SquadInfo) : SquadInfo = {
|
||||
if(updates.nonEmpty) {
|
||||
val list = Seq(
|
||||
SquadInfo.Blank, //must be index-0
|
||||
SquadInfo(info.leader, None, None, None, None),
|
||||
SquadInfo(None, info.task, None, None, None),
|
||||
SquadInfo(None, None, info.zone_id, None, None),
|
||||
SquadInfo(None, None, None, info.size, None),
|
||||
SquadInfo(None, None, None, None, info.capacity)
|
||||
final case class NormalBid(char_id : Long, squad_guid : PlanetSideGUID, position : Int) extends PositionBid
|
||||
|
||||
final case class VacancyBid(char_id : Long, squad_guid : PlanetSideGUID) extends PositionBid
|
||||
|
||||
final case class CamraderieBid(char_id : Long, placeholder : Squad) extends PositionBid
|
||||
|
||||
object SquadList {
|
||||
def Publish(squad : Squad) : SquadInfo = {
|
||||
SquadInfo(
|
||||
squad.Leader.Name,
|
||||
squad.Task,
|
||||
PlanetSideZoneID(squad.ZoneId),
|
||||
squad.Size,
|
||||
squad.Capacity,
|
||||
squad.GUID
|
||||
)
|
||||
var out = SquadInfo.Blank
|
||||
updates
|
||||
.map(i => list(i))
|
||||
.filterNot { _ == SquadInfo.Blank }
|
||||
.foreach(sinfo => out = out And sinfo )
|
||||
out
|
||||
}
|
||||
else {
|
||||
SquadInfo.Blank
|
||||
}
|
||||
}
|
||||
|
||||
def Differences(before : SquadInfo, after : SquadInfo) : SquadInfo = {
|
||||
SquadInfo(
|
||||
if(!before.leader.equals(after.leader)) after.leader else None,
|
||||
if(!before.task.equals(after.task)) after.task else None,
|
||||
if(!before.zone_id.equals(after.zone_id)) after.zone_id else None,
|
||||
if(!before.size.equals(after.size)) after.size else None,
|
||||
if(!before.capacity.equals(after.capacity)) after.capacity else None
|
||||
)
|
||||
object Detail {
|
||||
def Publish(squad : Squad) : SquadDetail = {
|
||||
SquadDetail()
|
||||
.LeaderCharId(squad.Leader.CharId)
|
||||
.LeaderName(squad.Leader.Name)
|
||||
.Task(squad.Task)
|
||||
.ZoneId(PlanetSideZoneID(squad.ZoneId))
|
||||
.Members(
|
||||
squad.Membership.zipWithIndex.map({ case (p, index) =>
|
||||
SquadPositionEntry(index, if(squad.Availability(index)) {
|
||||
SquadPositionDetail(p.Role, p.Orders, p.Requirements, p.CharId, p.Name)
|
||||
}
|
||||
else {
|
||||
SquadPositionDetail.Closed
|
||||
})
|
||||
}).toList
|
||||
)
|
||||
.Complete
|
||||
}
|
||||
|
||||
def Differences(updates : List[Int], info : SquadDetail) : SquadDetail = {
|
||||
if(updates.nonEmpty) {
|
||||
val list = Seq(
|
||||
SquadDetail.Blank, //must be index-0
|
||||
SquadDetail(info.unk1, None, None, None, None, None, None, None, None),
|
||||
SquadDetail(None, None, info.leader_char_id, None, None, None, None, None, None),
|
||||
SquadDetail(None, None, None, info.unk3, None, None, None, None, None),
|
||||
SquadDetail(None, None, None, None, info.leader_name, None, None, None, None),
|
||||
SquadDetail(None, None, None, None, None, info.task, None, None, None),
|
||||
SquadDetail(None, None, None, None, None, None, info.zone_id, None, None),
|
||||
SquadDetail(None, None, None, None, None, None, None, info.unk7, None),
|
||||
SquadDetail(None, None, None, None, None, None, None, None, info.member_info)
|
||||
)
|
||||
var out = SquadDetail.Blank
|
||||
updates
|
||||
.map(i => list(i))
|
||||
.filterNot { _ == SquadDetail.Blank }
|
||||
.foreach(sinfo => out = out And sinfo )
|
||||
out
|
||||
}
|
||||
else {
|
||||
SquadDetail.Blank
|
||||
}
|
||||
}
|
||||
|
||||
def Differences(before : SquadDetail, after : SquadDetail) : SquadDetail = {
|
||||
SquadDetail(
|
||||
if(!before.unk1.equals(after.unk1)) after.unk1 else None,
|
||||
None,
|
||||
if(!before.leader_char_id.equals(after.leader_char_id)) after.leader_char_id else None,
|
||||
if(!before.unk3.equals(after.unk3)) after.unk3 else None,
|
||||
if(!before.leader_name.equals(after.leader_name)) after.leader_name else None,
|
||||
if(!before.task.equals(after.task)) after.task else None,
|
||||
if(!before.zone_id.equals(after.zone_id)) after.zone_id else None,
|
||||
if(!before.unk7.equals(after.unk7)) after.unk7 else None,
|
||||
{
|
||||
(before.member_info, after.member_info) match {
|
||||
case (Some(beforeEntry), Some(afterEntry)) =>
|
||||
val out = beforeEntry.zip(afterEntry)
|
||||
.map { case (b, a) => PositionEquality(b, a) }
|
||||
.collect { case Some(entry) => entry }
|
||||
if(out.nonEmpty) {
|
||||
Some(out)
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private def PositionEquality(before : SquadPositionEntry, after : SquadPositionEntry) : Option[SquadPositionEntry] = {
|
||||
(before.info, after.info) match {
|
||||
case (Some(binfo), Some(ainfo)) =>
|
||||
val out = MemberEquality(binfo, ainfo)
|
||||
if(out == SquadPositionDetail.Blank) {
|
||||
None
|
||||
}
|
||||
else {
|
||||
Some(SquadPositionEntry(before.index, out))
|
||||
}
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
private def MemberEquality(before : SquadPositionDetail, after : SquadPositionDetail) : SquadPositionDetail = {
|
||||
SquadPositionDetail(
|
||||
if(!before.is_closed.equals(after.is_closed)) after.is_closed else None,
|
||||
if(!before.role.equals(after.role)) after.role else None,
|
||||
if(!before.detailed_orders.equals(after.detailed_orders)) after.detailed_orders else None,
|
||||
if(!before.requirements.equals(after.requirements)) after.requirements else None,
|
||||
if(!before.char_id.equals(after.char_id)) after.char_id else None,
|
||||
if(!before.name.equals(after.name)) after.name else None
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def LoadSquadDefinition(squad : Squad, favorite : SquadLoadout) : Unit = {
|
||||
squad.Task = favorite.task
|
||||
squad.ZoneId = favorite.zone_id.getOrElse(squad.ZoneId)
|
||||
squad.Availability.indices.foreach { index => squad.Availability.update(index, false) }
|
||||
squad.Membership.foreach { position =>
|
||||
position.Role = ""
|
||||
position.Orders = ""
|
||||
position.Requirements = Set()
|
||||
}
|
||||
favorite.members.foreach { position =>
|
||||
squad.Availability.update(position.index, true)
|
||||
val member = squad.Membership(position.index)
|
||||
member.Role = position.role
|
||||
member.Orders = position.orders
|
||||
member.Requirements = position.requirements
|
||||
}
|
||||
}
|
||||
|
||||
def ValidOpenSquadPosition(squad : Squad, position : Int, member : Member, reqs : Set[CertificationType.Value]) : Boolean = {
|
||||
squad.Availability(position) && member.CharId == 0 && reqs.intersect(member.Requirements) == member.Requirements
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 3
|
||||
action mustEqual SaveSquadDefinition()
|
||||
action mustEqual SaveSquadFavorite()
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 3
|
||||
action mustEqual LoadSquadDefinition()
|
||||
action mustEqual LoadSquadFavorite()
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -76,7 +76,7 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 3
|
||||
action mustEqual ListSquadDefinition("Cops and Military Officers")
|
||||
action mustEqual ListSquadFavorite("Cops and Military Officers")
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
action mustEqual ListSquad()
|
||||
action mustEqual RequestListSquad()
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -328,28 +328,28 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
}
|
||||
|
||||
"encode (03)" in {
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 3, SaveSquadDefinition())
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 3, SaveSquadFavorite())
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_03
|
||||
}
|
||||
|
||||
"encode (03)" in {
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 3, LoadSquadDefinition())
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 3, LoadSquadFavorite())
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_04
|
||||
}
|
||||
|
||||
"encode (07)" in {
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 3, ListSquadDefinition("Cops and Military Officers"))
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 3, ListSquadFavorite("Cops and Military Officers"))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_07
|
||||
}
|
||||
|
||||
"encode (08)" in {
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, ListSquad())
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, RequestListSquad())
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_08
|
||||
|
|
|
|||
|
|
@ -346,10 +346,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
|
||||
case SquadServiceResponse(toChannel, response) =>
|
||||
response match {
|
||||
case SquadResponse.Init(infos) if infos.nonEmpty =>
|
||||
case SquadResponse.ListSquadFavorite(line, task) =>
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), line, SquadAction.ListSquadFavorite(task)))
|
||||
|
||||
case SquadResponse.InitList(infos) if infos.nonEmpty =>
|
||||
sendResponse(ReplicationStreamMessage(infos))
|
||||
|
||||
case SquadResponse.Update(infos) if infos.nonEmpty =>
|
||||
case SquadResponse.UpdateList(infos) if infos.nonEmpty =>
|
||||
val o = ReplicationStreamMessage(6, None,
|
||||
infos.map { case (index, squadInfo) =>
|
||||
SquadListing(index, squadInfo)
|
||||
|
|
@ -364,7 +367,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
)
|
||||
)
|
||||
|
||||
case SquadResponse.Remove(infos) if infos.nonEmpty =>
|
||||
case SquadResponse.RemoveFromList(infos) if infos.nonEmpty =>
|
||||
sendResponse(
|
||||
ReplicationStreamMessage(1, None,
|
||||
infos.map { index =>
|
||||
|
|
@ -387,9 +390,21 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
)
|
||||
)
|
||||
|
||||
case SquadResponse.InitSquad(squad_guid) =>
|
||||
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.Unknown(16)))
|
||||
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, unk3, unk4, player_name, unk5, unk6) =>
|
||||
sendResponse(SquadMembershipResponse(request_type, unk1, unk2, unk3, unk4, player_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.Join(squad, positionsToUpdate) =>
|
||||
val leader = squad.Leader
|
||||
val id = 11
|
||||
|
|
@ -399,7 +414,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
membershipPositions.find({ case(member, _) => member.CharId == avatar.CharId }) match {
|
||||
case Some((ourMember, ourIndex)) =>
|
||||
//we are joining the squad
|
||||
sendResponse(SquadMembershipResponse(SquadRequestType.Accept, 0, 0, player.CharId, Some(leader.CharId), player.Name, true, Some(None)))
|
||||
sendResponse(SquadMembershipResponse(SquadResponseType.Accept, 0, 0, player.CharId, Some(leader.CharId), player.Name, true, Some(None)))
|
||||
//load each member's entry (our own too)
|
||||
membershipPositions.foreach { case(member, index) =>
|
||||
sendResponse(SquadMemberEvent(0, id, member.CharId, index, Some(member.Name), Some(member.ZoneId), Some(0)))
|
||||
|
|
@ -410,7 +425,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
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(squad.GUID, 0, SquadAction.Unknown(18, hex"00".toBitVector.take(6))))
|
||||
sendResponse(SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(18)))
|
||||
case _ =>
|
||||
//other player is joining our squad
|
||||
//load each member's entry
|
||||
|
|
@ -432,7 +447,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
positionsToUpdate.find({ case(member, _) => member == avatar.CharId }) match {
|
||||
case Some((ourMember, ourIndex)) =>
|
||||
//we are leaving the squad
|
||||
sendResponse(SquadMembershipResponse(SquadRequestType.Leave, 0,1, avatar.CharId, Some(avatar.CharId), avatar.name, true, Some(None)))
|
||||
sendResponse(SquadMembershipResponse(SquadResponseType.Leave, 0,1, avatar.CharId, Some(avatar.CharId), avatar.name, true, Some(None)))
|
||||
//remove each member's entry (our own too)
|
||||
positionsToUpdate.foreach { case(member, index) =>
|
||||
sendResponse(SquadMemberEvent(1, id, member, index, None, None, None))
|
||||
|
|
@ -444,7 +459,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
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, hex"00".toBitVector.take(6))))
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
|
||||
case _ =>
|
||||
//remove each member's entry
|
||||
positionsToUpdate.foreach { case(member, index) =>
|
||||
|
|
@ -455,6 +470,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
|
||||
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))
|
||||
|
|
@ -476,12 +492,31 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
if(updatedEntries.nonEmpty) {
|
||||
sendResponse(
|
||||
SquadState(
|
||||
PlanetSideGUID(11),
|
||||
PlanetSideGUID(id),
|
||||
updatedEntries.map { entry => SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos, 2,2, false, 429, None,None)}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case SquadResponse.SwapMember(squad, to_index, from_index) =>
|
||||
//this failsafe is not supported by normal squad member operations
|
||||
val member = squad.Membership(to_index)
|
||||
val charId = member.CharId
|
||||
val elem = squadUI(charId)
|
||||
val id = 11
|
||||
squadUI(charId) = SquadUIElement(elem.name, to_index, elem.zone, elem.health, elem.armor, elem.position)
|
||||
sendResponse(SquadMemberEvent(1, id, charId, from_index, None, None, None))
|
||||
sendResponse(SquadMemberEvent(0, id, charId, to_index, Some(elem.name), Some(elem.zone), Some(0)))
|
||||
sendResponse(
|
||||
SquadState(
|
||||
PlanetSideGUID(id),
|
||||
List(SquadStateInfo(charId, elem.health, elem.armor, elem.position, 2,2, false, 429, None,None))
|
||||
)
|
||||
)
|
||||
if(charId == avatar.CharId) {
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, to_index))
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
|
|
@ -956,7 +991,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
galaxyService ! Service.Join("galaxy") //for galaxy-wide messages
|
||||
galaxyService ! Service.Join(s"${avatar.faction}") //for hotspots
|
||||
squadService ! Service.Join(s"${avatar.faction}") //channel will be player.Faction
|
||||
squadService ! Service.Join(avatar.name) //management of any lingering squad information connected to this player
|
||||
squadService ! Service.Join(s"${avatar.CharId}") //channel will be player.CharId (in order to work with packets)
|
||||
cluster ! InterstellarCluster.GetWorld("home3")
|
||||
|
||||
case InterstellarCluster.GiveWorld(zoneId, zone) =>
|
||||
|
|
@ -2964,14 +2999,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
//AvatarAwardMessage
|
||||
//DisplayAwardMessage
|
||||
sendResponse(PlanetsideStringAttributeMessage(guid, 0, "Outfit Name"))
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(6, hex"".toBitVector)))
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(6)))
|
||||
(0 to 9).foreach(line => {
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), line, SquadAction.ListSquadDefinition("")))
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), line, SquadAction.ListSquadFavorite("")))
|
||||
})
|
||||
sendResponse(SquadDetailDefinitionUpdateMessage.Init)
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0,SquadAction.Unknown(16, hex"".toBitVector)))
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0,SquadAction.Unknown(17, hex"".toBitVector)))
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0,SquadAction.Unknown(18, hex"".toBitVector)))
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0,SquadAction.Unknown(16)))
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0,SquadAction.SetListSquad()))
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0,SquadAction.Unknown(18)))
|
||||
//MapObjectStateBlockMessage and ObjectCreateMessage?
|
||||
//TacticsMessage?
|
||||
//change the owner on our deployables (re-draw the icons for our deployables too)
|
||||
|
|
@ -3419,7 +3454,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) =>
|
||||
if(deadState == DeadState.Alive) {
|
||||
if(!player.Crouching && is_crouching) { //SQUAD TESTING CODE
|
||||
sendResponse(SquadMemberEvent(3, 11, 353380L, 3, None, Some(13), None))
|
||||
sendResponse(SquadMembershipResponse(SquadResponseType.Unk01, 0, 0, player.CharId, None, "Dummy", false, Some(None)))
|
||||
}
|
||||
player.Position = pos
|
||||
player.Velocity = vel
|
||||
|
|
|
|||
Loading…
Reference in a new issue