better support for the various ways to join a squad, and the various ways to leave a squad; squad-based messages; jury-rigged 'player wants to join' operations

This commit is contained in:
FateJH 2019-08-07 10:56:13 -04:00
parent 24691ec239
commit e5b2efd381
7 changed files with 681 additions and 331 deletions

View file

@ -16,9 +16,26 @@ import shapeless.{::, HNil}
abstract class SquadAction(val code : Int) abstract class SquadAction(val code : Int)
object SquadAction{ object SquadAction{
object SearchMode extends Enumeration {
type Type = Value
val
AnyPositions,
AvailablePositions,
SomeCertifications,
AllCertifications
= Value
implicit val codec : Codec[SearchMode.Value] = PacketHelpers.createEnumerationCodec(enum = this, uint(bits = 3))
}
final case class DisplaySquad() extends SquadAction(0) final case class DisplaySquad() extends SquadAction(0)
final case class AnswerSquadJoinRequest() extends SquadAction(1) /**
* Dispatched from client to server to indicate a squad detail update that has no foundation entry to update?
* Not dissimilar from `DisplaySquad`.
*/
final case class DisplayFullSquad() extends SquadAction(1)
final case class SaveSquadFavorite() extends SquadAction(3) final case class SaveSquadFavorite() extends SquadAction(3)
@ -58,12 +75,14 @@ object SquadAction{
final case class LocationFollowsSquadLead(state : Boolean) extends SquadAction(31) final case class LocationFollowsSquadLead(state : Boolean) extends SquadAction(31)
final case class SearchForSquadsWithParticularRole(u1: String, u2 : Long, u3: Int, u4 : Int) extends SquadAction(34) final case class SearchForSquadsWithParticularRole(role: String, requirements : Set[CertificationType.Value], zone_id: Int, mode : SearchMode.Value) extends SquadAction(34)
final case class CancelSquadSearch() extends SquadAction(35) final case class CancelSquadSearch() extends SquadAction(35)
final case class AssignSquadMemberToRole(position : Int, char_id : Long) extends SquadAction(38) final case class AssignSquadMemberToRole(position : Int, char_id : Long) extends SquadAction(38)
final case class NoSquadSearchResults() extends SquadAction(39)
final case class FindLfsSoldiersForRole(state : Int) extends SquadAction(40) final case class FindLfsSoldiersForRole(state : Int) extends SquadAction(40)
final case class CancelFind() extends SquadAction(41) final case class CancelFind() extends SquadAction(41)
@ -91,10 +110,10 @@ object SquadAction{
} }
) )
val answerSquadJoinRequestCodec = everFailCondition.xmap[AnswerSquadJoinRequest] ( val displayFullSquadCodec = everFailCondition.xmap[DisplayFullSquad] (
_ => AnswerSquadJoinRequest(), _ => DisplayFullSquad(),
{ {
case AnswerSquadJoinRequest() => None case DisplayFullSquad() => None
} }
) )
@ -243,12 +262,12 @@ object SquadAction{
PacketHelpers.encodedWideStringAligned(6) :: PacketHelpers.encodedWideStringAligned(6) ::
ulongL(46) :: ulongL(46) ::
uint16L :: uint16L ::
uintL(3)).xmap[SearchForSquadsWithParticularRole] ( SearchMode.codec).xmap[SearchForSquadsWithParticularRole] (
{ {
case u1 :: u2 :: u3 :: u4 :: HNil => SearchForSquadsWithParticularRole(u1, u2, u3, u4) case u1 :: u2 :: u3 :: u4 :: HNil => SearchForSquadsWithParticularRole(u1, CertificationType.fromEncodedLong(u2), u3, u4)
}, },
{ {
case SearchForSquadsWithParticularRole(u1, u2, u3, u4) => u1 :: u2 :: u3 :: u4 :: HNil case SearchForSquadsWithParticularRole(u1, u2, u3, u4) => u1 :: CertificationType.toEncodedLong(u2) :: u3 :: u4 :: HNil
} }
) )
@ -268,6 +287,13 @@ object SquadAction{
} }
) )
val noSquadSearchResultsCodec = everFailCondition.xmap[NoSquadSearchResults] (
_ => NoSquadSearchResults(),
{
case NoSquadSearchResults() => None
}
)
val findLfsSoldiersForRoleCodec = uint4.xmap[FindLfsSoldiersForRole] ( val findLfsSoldiersForRoleCodec = uint4.xmap[FindLfsSoldiersForRole] (
state => FindLfsSoldiersForRole(state), state => FindLfsSoldiersForRole(state),
{ {
@ -326,6 +352,7 @@ object SquadAction{
* &nbsp;&nbsp;&nbsp;&nbsp;`18` - UNKNOWN<br> * &nbsp;&nbsp;&nbsp;&nbsp;`18` - UNKNOWN<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`26` - Reset All<br> * &nbsp;&nbsp;&nbsp;&nbsp;`26` - Reset All<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`35` - Cancel Squad Search<br> * &nbsp;&nbsp;&nbsp;&nbsp;`35` - Cancel Squad Search<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`39` - No Squad Search Results<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`41` - Cancel Find<br> * &nbsp;&nbsp;&nbsp;&nbsp;`41` - Cancel Find<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`42` - UNKNOWN<br> * &nbsp;&nbsp;&nbsp;&nbsp;`42` - UNKNOWN<br>
* &nbsp;&nbsp;&nbsp;&nbsp;`43` - UNKNOWN<br> * &nbsp;&nbsp;&nbsp;&nbsp;`43` - UNKNOWN<br>
@ -388,7 +415,7 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
import scala.annotation.switch import scala.annotation.switch
((code : @switch) match { ((code : @switch) match {
case 0 => displaySquadCodec case 0 => displaySquadCodec
case 1 => answerSquadJoinRequestCodec case 1 => displayFullSquadCodec
case 3 => saveSquadFavoriteCodec case 3 => saveSquadFavoriteCodec
case 4 => loadSquadFavoriteCodec case 4 => loadSquadFavoriteCodec
case 5 => deleteSquadFavoriteCodec case 5 => deleteSquadFavoriteCodec
@ -411,12 +438,13 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
case 34 => searchForSquadsWithParticularRoleCodec case 34 => searchForSquadsWithParticularRoleCodec
case 35 => cancelSquadSearchCodec case 35 => cancelSquadSearchCodec
case 38 => assignSquadMemberToRoleCodec case 38 => assignSquadMemberToRoleCodec
case 39 => noSquadSearchResultsCodec
case 40 => findLfsSoldiersForRoleCodec case 40 => findLfsSoldiersForRoleCodec
case 41 => cancelFindCodec case 41 => cancelFindCodec
case 2 | 6 | 11 | case 2 | 6 | 11 |
12 | 13 | 14 | 16 | 12 | 13 | 14 | 16 |
18 | 29 | 30 | 32 | 33 | 18 | 29 | 30 | 32 | 33 |
36 | 37 | 39 | 42 | 43 => unknownCodec(code) 36 | 37 | 42 | 43 => unknownCodec(code)
case _ => failureCodec(code) case _ => failureCodec(code)
}).asInstanceOf[Codec[SquadAction]] }).asInstanceOf[Codec[SquadAction]]
} }

View file

@ -12,18 +12,18 @@ object MemberEvent extends Enumeration {
val val
Add, Add,
Remove, Remove,
Unknown2, Promote,
UpdateZone, UpdateZone,
Unknown4 Unknown4
= Value = Value
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint(3)) implicit val codec = PacketHelpers.createEnumerationCodec(enum = this, uint(bits = 3))
} }
final case class SquadMemberEvent(unk1 : Int, final case class SquadMemberEvent(action : MemberEvent.Value,
unk2 : Int, unk2 : Int,
char_id : Long, char_id : Long,
member_position : Int, position : Int,
player_name : Option[String], player_name : Option[String],
zone_number : Option[Int], zone_number : Option[Int],
unk7 : Option[Long]) unk7 : Option[Long])
@ -34,40 +34,46 @@ final case class SquadMemberEvent(unk1 : Int,
} }
object SquadMemberEvent extends Marshallable[SquadMemberEvent] { object SquadMemberEvent extends Marshallable[SquadMemberEvent] {
def apply(unk1 : Int, unk2 : Int, char_id : Long, member_position : Int) : SquadMemberEvent = def apply(action : MemberEvent.Value, unk2 : Int, char_id : Long, position : Int) : SquadMemberEvent =
SquadMemberEvent(unk1, unk2, char_id, member_position, None, None, None) SquadMemberEvent(action, unk2, char_id, position, None, None, None)
def apply(unk2 : Int, char_id : Long, member_position : Int, player_name : String, zone_number : Int, unk7 : Long) : SquadMemberEvent = def Add(unk2 : Int, char_id : Long, position : Int, player_name : String, zone_number : Int, unk7 : Long) : SquadMemberEvent =
SquadMemberEvent(0, unk2, char_id, member_position, Some(player_name), Some(zone_number), Some(unk7)) SquadMemberEvent(MemberEvent.Add, unk2, char_id, position, Some(player_name), Some(zone_number), Some(unk7))
def apply(unk2 : Int, char_id : Long, member_position : Int, zone_number : Int) : SquadMemberEvent = def Remove(unk2 : Int, char_id : Long, position : Int) : SquadMemberEvent =
SquadMemberEvent(3, unk2, char_id, member_position, None, Some(zone_number), None) SquadMemberEvent(MemberEvent.Remove, unk2, char_id, position, None, None, None)
def apply(unk2 : Int, char_id : Long, member_position : Int, unk7 : Long) : SquadMemberEvent = def Promote(unk2 : Int, char_id : Long) : SquadMemberEvent =
SquadMemberEvent(4, unk2, char_id, member_position, None, None, Some(unk7)) SquadMemberEvent(MemberEvent.Promote, unk2, char_id, 0, None, None, None)
def UpdateZone(unk2 : Int, char_id : Long, position : Int, zone_number : Int) : SquadMemberEvent =
SquadMemberEvent(MemberEvent.UpdateZone, unk2, char_id, position, None, Some(zone_number), None)
def Unknown4(unk2 : Int, char_id : Long, position : Int, unk7 : Long) : SquadMemberEvent =
SquadMemberEvent(MemberEvent.Unknown4, unk2, char_id, position, None, None, Some(unk7))
implicit val codec : Codec[SquadMemberEvent] = ( implicit val codec : Codec[SquadMemberEvent] = (
("unk1" | uint(3)) >>:~ { unk1 => ("action" | MemberEvent.codec) >>:~ { action =>
("unk2" | uint16L) :: ("unk2" | uint16L) ::
("char_id" | uint32L) :: ("char_id" | uint32L) ::
("member_position" | uint4) :: ("position" | uint4) ::
conditional(unk1 == 0, "player_name" | PacketHelpers.encodedWideStringAligned(1)) :: conditional(action == MemberEvent.Add, "player_name" | PacketHelpers.encodedWideStringAligned(1)) ::
conditional(unk1 == 0 || unk1 == 3, "zone_number" | uint16L) :: conditional(action == MemberEvent.Add || action == MemberEvent.UpdateZone, "zone_number" | uint16L) ::
conditional(unk1 == 0 || unk1 == 4, "unk7" | uint32L) conditional(action == MemberEvent.Add || action == MemberEvent.Unknown4, "unk7" | uint32L)
}).exmap[SquadMemberEvent] ( }).exmap[SquadMemberEvent] (
{ {
case unk1 :: unk2 :: char_id :: member_position :: player_name :: zone_number :: unk7 :: HNil => case action :: unk2 :: char_id :: member_position :: player_name :: zone_number :: unk7 :: HNil =>
Attempt.Successful(SquadMemberEvent(unk1, unk2, char_id, member_position, player_name, zone_number, unk7)) Attempt.Successful(SquadMemberEvent(action, unk2, char_id, member_position, player_name, zone_number, unk7))
}, },
{ {
case data @ SquadMemberEvent(0, unk2, char_id, member_position, Some(player_name), Some(zone_number), Some(unk7)) => case SquadMemberEvent(MemberEvent.Add, unk2, char_id, member_position, Some(player_name), Some(zone_number), Some(unk7)) =>
Attempt.Successful(0 :: unk2 :: char_id :: member_position :: Some(player_name) :: Some(zone_number) :: Some(unk7) :: HNil) Attempt.Successful(MemberEvent.Add :: unk2 :: char_id :: member_position :: Some(player_name) :: Some(zone_number) :: Some(unk7) :: HNil)
case data @ SquadMemberEvent(3, unk2, char_id, member_position, None, Some(zone_number), None) => case SquadMemberEvent(MemberEvent.UpdateZone, unk2, char_id, member_position, None, Some(zone_number), None) =>
Attempt.Successful(3 :: unk2 :: char_id :: member_position :: None :: Some(zone_number) :: None :: HNil) Attempt.Successful(MemberEvent.UpdateZone :: unk2 :: char_id :: member_position :: None :: Some(zone_number) :: None :: HNil)
case data @ SquadMemberEvent(4, unk2, char_id, member_position, None, None, Some(unk7)) => case SquadMemberEvent(MemberEvent.Unknown4, unk2, char_id, member_position, None, None, Some(unk7)) =>
Attempt.Successful(4 :: unk2 :: char_id :: member_position :: None :: None :: Some(unk7) :: HNil) Attempt.Successful(MemberEvent.Unknown4 :: unk2 :: char_id :: member_position :: None :: None :: Some(unk7) :: HNil)
case data @ SquadMemberEvent(unk1, unk2, char_id, member_position, None, None, None) => case SquadMemberEvent(action, unk2, char_id, member_position, None, None, None) =>
Attempt.Successful(unk1 :: unk2 :: char_id :: member_position :: None :: None :: None :: HNil) Attempt.Successful(action :: unk2 :: char_id :: member_position :: None :: None :: None :: HNil)
case data => case data =>
Attempt.Failure(Err(s"SquadMemberEvent can not encode with this pattern - $data")) Attempt.Failure(Err(s"SquadMemberEvent can not encode with this pattern - $data"))
} }

View file

@ -18,10 +18,14 @@ object SquadResponse {
final case class Unknown17(squad : Squad, char_id : Long) 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 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 Join(squad : Squad, positionsToUpdate : List[Int]) 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 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
final case class PromoteMember(squad : Squad, char_id : Long, from_index : Int, to_index : Int) extends Response
final case class Detail(guid : PlanetSideGUID, squad_detail : SquadDetail) extends Response final case class Detail(guid : PlanetSideGUID, squad_detail : SquadDetail) extends Response
final case class SquadSearchResults() extends Response
} }

View file

@ -22,13 +22,14 @@ class SquadService extends Actor {
private var memberToSquad : mutable.LongMap[Squad] = mutable.LongMap[Squad]() private var memberToSquad : mutable.LongMap[Squad] = mutable.LongMap[Squad]()
private var idToSquad : TrieMap[PlanetSideGUID, Squad] = new TrieMap[PlanetSideGUID, Squad]() private var idToSquad : TrieMap[PlanetSideGUID, Squad] = new TrieMap[PlanetSideGUID, Squad]()
private var idToSwitchboard : TrieMap[PlanetSideGUID, ActorRef] = new TrieMap[PlanetSideGUID, ActorRef]() private var idToSwitchboard : TrieMap[PlanetSideGUID, ActorRef] = new TrieMap[PlanetSideGUID, ActorRef]()
private var i : Int = 1 private var sid : Int = 1
private val publishedLists : TrieMap[PlanetSideEmpire.Value, ListBuffer[SquadInfo]] = TrieMap[PlanetSideEmpire.Value, ListBuffer[SquadInfo]]( private val publishedLists : TrieMap[PlanetSideEmpire.Value, ListBuffer[SquadInfo]] = TrieMap[PlanetSideEmpire.Value, ListBuffer[SquadInfo]](
PlanetSideEmpire.TR -> ListBuffer.empty, PlanetSideEmpire.TR -> ListBuffer.empty,
PlanetSideEmpire.NC -> ListBuffer.empty, PlanetSideEmpire.NC -> ListBuffer.empty,
PlanetSideEmpire.VS -> ListBuffer.empty PlanetSideEmpire.VS -> ListBuffer.empty
) )
private val bids : mutable.LongMap[PositionBid] = mutable.LongMap[PositionBid]() private val invites : mutable.LongMap[Invitation] = mutable.LongMap[Invitation]()
private val queuedInvites : mutable.LongMap[List[Invitation]] = mutable.LongMap[List[Invitation]]()
private val viewDetails : mutable.LongMap[PlanetSideGUID] = mutable.LongMap[PlanetSideGUID]() private val viewDetails : mutable.LongMap[PlanetSideGUID] = mutable.LongMap[PlanetSideGUID]()
private [this] val log = org.log4s.getLogger private [this] val log = org.log4s.getLogger
@ -36,56 +37,70 @@ class SquadService extends Actor {
override def preStart : Unit = { override def preStart : Unit = {
log.info("Starting...") log.info("Starting...")
val testSquad = new Squad(PlanetSideGUID(3), PlanetSideEmpire.VS) // val testSquad = new Squad(PlanetSideGUID(3), PlanetSideEmpire.VS)
testSquad.Task = "\\#66CCFF Sentinels of Auraxis\\#FFFFFF ... \\#40FF40 Squad Up!!" // testSquad.Task = "\\#66CCFF Sentinels of Auraxis\\#FFFFFF ... \\#40FF40 Squad Up!!"
testSquad.ZoneId = 5 // testSquad.ZoneId = 5
testSquad.Membership(0).Name = "Wizkid45" // testSquad.Membership(0).Name = "Wizkid45"
testSquad.Membership(0).Role = "Fearless Leader" // testSquad.Membership(0).Role = "Fearless Leader"
testSquad.Membership(0).CharId = 30910985L // testSquad.Membership(0).CharId = 30910985L
testSquad.Membership(0).ZoneId = 5 // testSquad.Membership(0).ZoneId = 5
testSquad.Membership(0).Health = 64 // testSquad.Membership(0).Health = 64
testSquad.Membership(0).Armor = 34 // testSquad.Membership(0).Armor = 34
testSquad.Membership(0).Position = Vector3(5526.5234f, 3818.7344f, 54.59375f) // testSquad.Membership(0).Position = Vector3(5526.5234f, 3818.7344f, 54.59375f)
testSquad.Membership(1).Name = "xoBLADEox" // testSquad.Membership(1).Name = "xoBLADEox"
testSquad.Membership(1).Role = "Right Hand" // testSquad.Membership(1).Role = "Right Hand"
testSquad.Membership(1).CharId = 42781919L // testSquad.Membership(1).CharId = 42781919L
testSquad.Membership(1).ZoneId = 5 // testSquad.Membership(1).ZoneId = 5
testSquad.Membership(1).Health = 54 // testSquad.Membership(1).Health = 54
testSquad.Membership(1).Armor = 44 // testSquad.Membership(1).Armor = 44
testSquad.Membership(1).Position = Vector3(4673.5312f, 2604.8047f, 40.015625f) // testSquad.Membership(1).Position = Vector3(4673.5312f, 2604.8047f, 40.015625f)
testSquad.Membership(3).Name = "cabal0428" // testSquad.Membership(3).Name = "cabal0428"
testSquad.Membership(3).Role = "Left Hand" // testSquad.Membership(3).Role = "Left Hand"
testSquad.Membership(3).CharId = 353380L // testSquad.Membership(3).CharId = 353380L
testSquad.Membership(3).ZoneId = 5 // testSquad.Membership(3).ZoneId = 5
testSquad.Membership(3).Health = 44 // testSquad.Membership(3).Health = 44
testSquad.Membership(3).Armor = 54 // testSquad.Membership(3).Armor = 54
testSquad.Membership(3).Position = Vector3(4727.492f, 2613.5312f, 51.390625f) // testSquad.Membership(3).Position = Vector3(4727.492f, 2613.5312f, 51.390625f)
testSquad.Membership(4).Name = "xSkiku" // testSquad.Membership(4).Name = "xSkiku"
testSquad.Membership(4).Role = "Right Foot's Middle Toe's Nail" // testSquad.Membership(4).Role = "Right Foot's Middle Toe's Nail"
testSquad.Membership(4).CharId = 41588340L // testSquad.Membership(4).CharId = 41588340L
testSquad.Membership(4).ZoneId = 5 // testSquad.Membership(4).ZoneId = 5
testSquad.Membership(4).Health = 34 // testSquad.Membership(4).Health = 34
testSquad.Membership(4).Armor = 64 // testSquad.Membership(4).Armor = 64
testSquad.Membership(4).Position = Vector3(3675.0f, 4789.8047f, 63.21875f) // testSquad.Membership(4).Position = Vector3(3675.0f, 4789.8047f, 63.21875f)
idToSquad(PlanetSideGUID(3)) = testSquad // idToSquad(PlanetSideGUID(3)) = testSquad
testSquad.Listed = true // testSquad.Listed = true
UpdateSquadList(testSquad, None) // UpdateSquadList(testSquad, None)
} }
def GetNextSquadId() : PlanetSideGUID = { def GetNextSquadId() : PlanetSideGUID = {
val out = i val out = sid
val j = i + 1 val j = sid + 1
if(j == 65536) { if(j == 65536) {
i = 1 sid = 1
} }
else { else {
i = j sid = j
} }
PlanetSideGUID(out) PlanetSideGUID(out)
} }
def TryResetSquadId() : Boolean = {
if(idToSquad.isEmpty) {
sid = 1
true
}
else {
false
}
}
def GetParticipatingSquad(player : Player) : Option[Squad] = { def GetParticipatingSquad(player : Player) : Option[Squad] = {
memberToSquad.get(player.CharId) match { GetParticipatingSquad(player.CharId)
}
def GetParticipatingSquad(charId : Long) : Option[Squad] = {
memberToSquad.get(charId) match {
case opt @ Some(_) => case opt @ Some(_) =>
opt opt
case None => case None =>
@ -114,24 +129,49 @@ class SquadService extends Actor {
} }
} }
def StartSquad(player : Player) : Squad = { def GetLeadingSquad(charId : Long, opt : Option[Squad]) : Option[Squad] = {
opt.orElse(memberToSquad.get(charId)) match {
case Some(squad) =>
if(squad.Leader.CharId == charId) {
Some(squad)
}
else {
None
}
case _ =>
None
}
}
def CreateSquad(player : Player) : Squad = {
val charId = player.CharId val charId = player.CharId
val faction = player.Faction val faction = player.Faction
val id = GetNextSquadId()
val name = player.Name val name = player.Name
val squad = new Squad(id, faction) val squad = new Squad(GetNextSquadId(), faction)
val leadPosition = squad.Membership(squad.LeaderPositionIndex) val leadPosition = squad.Membership(squad.LeaderPositionIndex)
leadPosition.Name = name leadPosition.Name = name
leadPosition.CharId = charId leadPosition.CharId = player.CharId
leadPosition.Health = player.Health leadPosition.Health = player.Health
leadPosition.Armor = player.Armor leadPosition.Armor = player.Armor
leadPosition.Position = player.Position leadPosition.Position = player.Position
leadPosition.ZoneId = 1 leadPosition.ZoneId = 1
log.info(s"$name-$faction has started a new squad")
squad
}
def StartSquad(squad : Squad) : Squad = {
val charId = squad.Leader.CharId
val id = squad.GUID
val switchboard = context.actorOf(Props[SquadSwitchboard], s"squad${id.guid}") val switchboard = context.actorOf(Props[SquadSwitchboard], s"squad${id.guid}")
memberToSquad += charId -> squad memberToSquad += charId -> squad
idToSquad += id -> squad idToSquad += id -> squad
idToSwitchboard += id -> switchboard idToSwitchboard += id -> switchboard
log.info(s"$name-$faction has started a new squad") squad
}
def StartSquad(player : Player) : Squad = {
val squad = CreateSquad(player)
StartSquad(squad)
squad squad
} }
@ -199,137 +239,253 @@ class SquadService extends Actor {
case Service.Leave(None) | Service.LeaveAll() => ; case Service.Leave(None) | Service.LeaveAll() => ;
case SquadServiceMessage(tplayer, squad_action) => squad_action match { case SquadServiceMessage(tplayer, squad_action) => squad_action match {
case SquadAction.Membership(request_type, char_id, optional_char_id, _, _) => request_type match { case SquadAction.Membership(SquadRequestType.Invite, invitingPlayer, Some(invitedPlayer), _, _) =>
case SquadRequestType.Invite => //this is just busy work; for actual joining operations, see SquadRequestType.Accept
//char_id is the inviter, e.g., the (prospective) squad leader // FindBid(invitingPlayer, invitedPlayer) match {
//this is just busy work; for actual joining operations, see SquadRequestType.Accept // case Some(bid) =>
(optional_char_id, memberToSquad.get(char_id)) match { // //invitingPlayer and invitedPlayer have both tried to join each others's squads
case (Some(invitee), Some(squad)) => // //treat this junction as consent
bids(invitee) = VacancyBid(char_id, squad.GUID) // self ! SquadServiceMessage(tplayer, SquadAction.Membership(SquadRequestType.Accept, invitingPlayer, Some(invitedPlayer), "", None))
log.info(s"$invitee has been invited to squad ${squad.Task} by $char_id") // case _ => ;
SquadEvents.publish( SquadServiceResponse(s"/$invitee/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, char_id, Some(invitee), tplayer.Name, false, Some(None))) ) // }
SquadEvents.publish( SquadServiceResponse(s"/$char_id/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitee, Some(char_id), tplayer.Name, true, Some(None))) ) (memberToSquad.get(invitingPlayer), memberToSquad.get(invitedPlayer)) match {
case (Some(invitee), None) => case (Some(squad1), Some(squad2)) =>
//the inviter does not currently belong to a squad; check for an existing placeholder, or create a new one //both players are in squads
val ourSquad = { if(squad1.GUID == squad2.GUID) {
bids.find { case (inviter, _) => inviter == char_id } match { //both players are in the same squad; no need to do anything
case Some((_, SpontaneousBid(_, _squad))) => }
_squad //borrow else {
case _ => //we might do some platoon chicanery with this case later
val _squad = StartSquad(tplayer) //TODO platoons
memberToSquad.remove(char_id) //completely unlist until assured the squad is necessary }
_squad
}
}
bids(invitee) = SpontaneousBid(char_id, ourSquad)
SquadEvents.publish( SquadServiceResponse(s"/$invitee/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, char_id, Some(invitee), tplayer.Name, false, Some(None))) )
SquadEvents.publish( SquadServiceResponse(s"/$char_id/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitee, Some(char_id), tplayer.Name, true, Some(None))) )
case _ => ;
}
case SquadRequestType.Accept => case (Some(squad), None) =>
//char_id is the invitee, e.g., the person joining the squad //the classic situation
bids.remove(char_id) match { log.info(s"$invitedPlayer has been invited to squad ${squad.Task} by $invitingPlayer")
case Some(NormalBid(_/*inviterCharId*/, squadGUID, line)) if idToSquad.get(squadGUID).nonEmpty => val leader = squad.Leader
//player requested to join a squad's specific position val leaderCharId = leader.CharId
JoinSquad(tplayer, idToSquad(squadGUID), line) val bid = VacancyInvite(leaderCharId, leader.Name, squad.GUID)
AddInvite(invitedPlayer, bid) match {
case out @ Some(_) if out.contains(bid) =>
SquadEvents.publish(SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, leaderCharId, Some(invitedPlayer), tplayer.Name, false, Some(None))))
SquadEvents.publish(SquadServiceResponse(s"/$leaderCharId/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitedPlayer, Some(leaderCharId), tplayer.Name, true, Some(None))))
case Some(_) =>
SquadEvents.publish(SquadServiceResponse(s"/$leaderCharId/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitedPlayer, Some(leaderCharId), tplayer.Name, true, Some(None))))
case _ => ;
}
case Some(VacancyBid(inviterCharId, squadGUID)) if idToSquad.get(squadGUID).nonEmpty => case (None, Some(squad)) =>
//we were invited by the squad leader into an existing squad //flip around the roles - the inviting becomes the invited
val squad = idToSquad(squadGUID) //TODO needs work
log.info(s"$invitedPlayer has asked $invitingPlayer for an invition to squad ${squad.Task}")
val bid = VacancyInvite(invitedPlayer, "", squad.GUID)
AddInvite(invitingPlayer, bid) match {
case out @ Some(_) if out.contains(bid) =>
SquadEvents.publish(SquadServiceResponse(s"/$invitingPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitedPlayer, Some(invitingPlayer), tplayer.Name, false, Some(None))))
SquadEvents.publish(SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitingPlayer, Some(invitedPlayer), tplayer.Name, true, Some(None))))
case Some(_) =>
SquadEvents.publish(SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitingPlayer, Some(invitedPlayer), tplayer.Name, true, Some(None))))
case _ => ;
}
case (None, None) =>
//neither the invited player nor the inviting player belong to any squad
log.info(s"$invitedPlayer has been invited to join $invitingPlayer's spontaneous squad")
val bid = SpontaneousInvite(tplayer)
AddInvite(invitedPlayer, bid) match {
case out @ Some(_) if out.contains(bid) =>
SquadEvents.publish(SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitingPlayer, Some(invitedPlayer), tplayer.Name, false, Some(None))))
SquadEvents.publish(SquadServiceResponse(s"/$invitingPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitedPlayer, Some(invitingPlayer), tplayer.Name, true, Some(None))))
case Some(_) =>
SquadEvents.publish(SquadServiceResponse(s"/$invitingPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, invitedPlayer, Some(invitingPlayer), tplayer.Name, true, Some(None))))
case _ => ;
}
case _ => ;
}
case SquadAction.Membership(SquadRequestType.Accept, invitedPlayer, _, _, _) =>
val acceptedInvite = RemoveInvite(invitedPlayer)
acceptedInvite match {
case Some(BidForPosition(petitioner, guid, position)) if idToSquad.get(guid).nonEmpty =>
//player requested to join a squad's specific position
//invitedPlayer is actually the squad leader; petitioner is the actual "invitedPlayer"
if(memberToSquad.get(petitioner.CharId).isEmpty) {
JoinSquad(petitioner, idToSquad(guid), position)
}
else {
log.warn("Accept -> Bid: the invited player is already a member of a squad and can not join a second one")
}
case Some(VacancyInvite(invitingPlayer, _, guid))
if idToSquad.get(guid).nonEmpty =>
//we were invited by the squad leader into an existing squad
if(memberToSquad.get(invitedPlayer).isEmpty) {
val squad = idToSquad(guid)
squad.Membership.zipWithIndex.find({ case (member, index) => squad.Membership.zipWithIndex.find({ case (member, index) =>
ValidOpenSquadPosition(squad, index, member, tplayer.Certifications) ValidOpenSquadPosition(squad, index, member, tplayer.Certifications)
}) match { }) match {
case Some((_, line)) => case Some((_, line)) =>
SquadEvents.publish( SquadServiceResponse(s"/$inviterCharId/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, inviterCharId, Some(char_id), tplayer.Name, false, Some(None))) ) SquadEvents.publish(SquadServiceResponse(s"/$invitingPlayer/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitingPlayer, Some(invitedPlayer), tplayer.Name, false, Some(None))))
SquadEvents.publish( SquadServiceResponse(s"/$char_id/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, char_id, Some(inviterCharId), "", true, Some(None))) ) SquadEvents.publish(SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitedPlayer, Some(invitingPlayer), "", true, Some(None))))
JoinSquad(tplayer, squad, line) JoinSquad(tplayer, squad, line)
RemoveQueuedInvites(invitedPlayer) //TODO deal with these somehow
case _ => ; case _ => ;
} }
}
else {
log.warn("Accept -> Invite: the invited player is already a member of a squad and can not join a second one")
}
case Some(SpontaneousBid(inviterCharId, placeholderSquad)) => case Some(SpontaneousInvite(invitingPlayer)) =>
//we were invited by someone into a new squad they would form //we were invited by someone into a new squad they would form
(GetParticipatingSquad(tplayer) match { val invitingPlayerCharId = invitingPlayer.CharId
case Some(participating) => (GetParticipatingSquad(invitingPlayer) match {
if(participating.Leader.CharId == inviterCharId) { case Some(participating) =>
Some(participating) if(participating.Leader.CharId == invitingPlayerCharId) {
Some(participating)
}
else {
//inviter joined a squad and is not its leader; bounce this request off of the squad leader
participating.Membership.zipWithIndex.find({ case (member, index) =>
ValidOpenSquadPosition(participating, index, member, tplayer.Certifications)
}) match {
case Some((_, line)) =>
val bid = BidForPosition(tplayer, participating.GUID, line)
AddInvite(participating.Leader.CharId, bid) match {
case out @ Some(_) if out.contains(bid) =>
HandleBidForPosition(bid, tplayer)
case _ => ;
}
case _ => ;
} }
else { None
//inviter joined a squad and is not its leader; bounce this request off of the squad leader }
//TODO squad leader receives "<player> wants to join squad" prompt case None =>
val leaderCharId = participating.Leader.CharId val squad = StartSquad(invitingPlayer)
bids(char_id) = VacancyBid(leaderCharId, participating.GUID) //reframed request squad.Task = s"${tplayer.Name}'s Squad"
SquadEvents.publish(SquadServiceResponse(s"/$leaderCharId/Squad", SquadResponse.Invite(char_id, leaderCharId, tplayer.Name))) SquadEvents.publish(SquadServiceResponse(s"/$invitingPlayer/Squad", SquadResponse.InitSquad(squad.GUID)))
None Some(squad)
} }) match {
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) => case Some(squad) =>
squad.Membership.zipWithIndex.find({ case (member, index) => squad.Membership.zipWithIndex.find({ case (member, index) =>
ValidOpenSquadPosition(squad, index, member, tplayer.Certifications) ValidOpenSquadPosition(squad, index, member, tplayer.Certifications)
}) match { }) match {
case Some((_, line)) => case Some((_, line)) =>
SquadEvents.publish( SquadServiceResponse(s"/$inviterCharId/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, inviterCharId, Some(char_id), tplayer.Name, false, Some(None))) ) SquadEvents.publish( SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitedPlayer, Some(invitingPlayerCharId), "", true, Some(None))) )
SquadEvents.publish( SquadServiceResponse(s"/$char_id/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, char_id, Some(inviterCharId), "", true, Some(None))) )
JoinSquad(tplayer, squad, line) JoinSquad(tplayer, squad, line)
SquadEvents.publish( SquadServiceResponse(s"/$invitingPlayerCharId/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitingPlayerCharId, Some(invitedPlayer), tplayer.Name, false, Some(None))) )
RemoveQueuedInvites(invitedPlayer) //TODO deal with these somehow
case _ => ; case _ => ;
} }
case None => ; case None => ;
}
case _ => ;
}
case SquadRequestType.Leave =>
//char_id is the player leaving
val squad = memberToSquad(char_id)
val leader = squad.Leader.CharId
if(char_id == leader) {
//squad leader is leaving his own squad, so it will be disbanded
squad.Membership
.filterNot { _.CharId == leader }
.foreach { member =>
val charId = member.CharId
SquadEvents.publish(SquadServiceResponse(s"/$charId/Squad", SquadResponse.Membership(SquadResponseType.Disband, 0, 0, charId, None, "", false, Some(None))))
}
CloseOutSquad(squad)
SquadEvents.publish(SquadServiceResponse(s"/$leader/Squad", SquadResponse.Membership(SquadResponseType.Disband, 0, 0, leader, None, "", true, Some(None))))
}
else {
if(optional_char_id.contains(char_id)) {
//leaving the squad of our own accord
LeaveSquad(tplayer, squad)
} }
else if(optional_char_id.contains(leader)) {
//kicked by the squad leader case None =>
SquadEvents.publish( SquadServiceResponse(s"/$char_id/Squad", SquadResponse.Membership(SquadResponseType.Leave, 0, 0, char_id, Some(leader), tplayer.Name, false, Some(None))) ) //the invite either timed-out or was withdrawn; select a new one?
SquadEvents.publish( SquadServiceResponse(s"/$leader/Squad", SquadResponse.Membership(SquadResponseType.Leave, 0, 0, leader, Some(char_id), "", true, Some(None))) ) NextInvite(invitedPlayer) match {
LeaveSquad(tplayer, squad) case Some(bid : BidForPosition) if !acceptedInvite.contains(bid) =>
HandleBidForPosition(bid, tplayer)
case Some(bid) if !acceptedInvite.contains(bid) =>
SquadEvents.publish(SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, bid.InviterCharId, Some(invitedPlayer), bid.InviterName, false, Some(None))))
case None => ;
} }
} }
case SquadRequestType.Reject => case SquadAction.Membership(SquadRequestType.Leave, leavingPlayer, optionalPlayer, _, _) =>
//a player has opted out of joining a squad val squad = memberToSquad(leavingPlayer)
(bids.remove(char_id) match { val leader = squad.Leader.CharId
case Some(SpontaneousBid(inviterCharId, squad)) if squad.Leader.CharId != char_id => if(leavingPlayer == leader || squad.Size == 2) {
(inviterCharId, squad) //squad leader is leaving his own squad, so it will be disbanded
case Some(VacancyBid(inviterCharId, guid)) if idToSquad(guid).Leader.CharId != char_id => //alternately, squad is only composed of two people, so it will be closed-out when one of them leaves
(inviterCharId, idToSquad.get(guid)) val membership = squad.Membership.collect { case member if member.CharId > 0 => member.CharId }
case _ => ; membership.foreach { charId =>
(0L, None) SquadEvents.publish(SquadServiceResponse(s"/$charId/Squad", SquadResponse.Membership(SquadResponseType.Leave, 0, 0, charId, None, "", false, Some(None))))
}) match {
case (inviterCharId, Some(squad)) =>
SquadEvents.publish( SquadServiceResponse(s"/$inviterCharId/Squad", SquadResponse.Membership(SquadResponseType.Reject, 0, 0, inviterCharId, Some(char_id), tplayer.Name, false, Some(None))) )
SquadEvents.publish( SquadServiceResponse(s"/$char_id/Squad", SquadResponse.Membership(SquadResponseType.Reject, 0, 0, char_id, Some(inviterCharId), "", true, Some(None))) )
case _ => ;
} }
CloseOutSquad(squad)
SquadEvents.publish(SquadServiceResponse(s"/$leader/Squad", SquadResponse.Membership(SquadResponseType.Disband, 0, 0, leader, None, "", true, Some(None))))
membership
.filterNot(_ == leader)
.foreach { charId =>
SquadEvents.publish(SquadServiceResponse(s"/$charId/Squad", SquadResponse.Membership(SquadResponseType.Disband, 0, 0, charId, None, "", false, Some(None))))
}
SquadEvents.publish( SquadServiceResponse(s"/$leader/Squad", SquadResponse.InitSquad(PlanetSideGUID(0))) )
SquadEvents.publish( SquadServiceResponse(s"/$leader/Squad", SquadResponse.Detail(PlanetSideGUID(0), SquadDetail().Complete)) )
}
else {
if(optionalPlayer.contains(leavingPlayer)) {
//leaving the squad of own accord
LeaveSquad(tplayer, squad)
}
else if(optionalPlayer.contains(leader)) {
//kicked by the squad leader
SquadEvents.publish( SquadServiceResponse(s"/$leavingPlayer/Squad", SquadResponse.Membership(SquadResponseType.Leave, 0, 0, leavingPlayer, Some(leader), tplayer.Name, false, Some(None))) )
SquadEvents.publish( SquadServiceResponse(s"/$leader/Squad", SquadResponse.Membership(SquadResponseType.Leave, 0, 0, leader, Some(leavingPlayer), "", true, Some(None))) )
LeaveSquad(tplayer, squad)
}
}
case _ => ; case SquadAction.Membership(SquadRequestType.Reject, rejectingPlayer, optionalPlayer, _, _) =>
} val rejectedBid = RemoveInvite(rejectingPlayer)
//(A, B) -> person who made the rejection, person who was rejected
(rejectedBid match {
case Some(SpontaneousInvite(invitingPlayer)) =>
//rejectingPlayer is the would-be squad member
(Some(rejectingPlayer), Some(invitingPlayer.CharId))
case Some(VacancyInvite(invitingPlayer, _, guid)) if idToSquad(guid).Leader.CharId != rejectingPlayer =>
//rejectingPlayer is the would-be squad member
(Some(rejectingPlayer), Some(invitingPlayer))
case Some(BidForPosition(_, guid, _)) if idToSquad(guid).Leader.CharId != rejectingPlayer =>
//rejectingPlayer is the squad leader
(Some(rejectingPlayer), None)
case _ => ;
(None, None)
}) match {
case (Some(rejected), Some(invited)) =>
SquadEvents.publish( SquadServiceResponse(s"/$rejected/Squad", SquadResponse.Membership(SquadResponseType.Reject, 0, 0, rejected, Some(invited), "", true, Some(None))) )
SquadEvents.publish( SquadServiceResponse(s"/$invited/Squad", SquadResponse.Membership(SquadResponseType.Reject, 0, 0, invited, Some(rejected), tplayer.Name, false, Some(None))) )
case (Some(rejected), None) =>
SquadEvents.publish( SquadServiceResponse(s"/$rejected/Squad", SquadResponse.Membership(SquadResponseType.Reject, 0, 0, rejected, Some(rejected), "", true, Some(None))) )
case _ => ;
}
NextInvite(rejectingPlayer) match {
case Some(bid : BidForPosition) if rejectedBid.isEmpty || !rejectedBid.contains(bid) =>
HandleBidForPosition(bid, tplayer)
case Some(bid) if rejectedBid.isEmpty || !rejectedBid.contains(bid) =>
SquadEvents.publish(SquadServiceResponse(s"/$rejectingPlayer/Squad", SquadResponse.Membership(SquadResponseType.Invite, 0, 0, bid.InviterCharId, Some(rejectingPlayer), bid.InviterName, false, Some(None))))
case None => ;
}
case SquadAction.Membership(SquadRequestType.Promote, promotingPlayer, Some(promotedPlayer), _, _) =>
(memberToSquad.get(promotingPlayer), memberToSquad.get(promotedPlayer)) match {
case (Some(squad), Some(squad2)) if squad.GUID == squad2.GUID && squad.Leader.CharId == promotingPlayer =>
val membership = squad.Membership.filter { _member => _member.CharId > 0 }
val (leader, position) = (squad.Leader, 0)
val (member, index) = membership.zipWithIndex.find { case (_member, _) => _member.CharId == promotedPlayer }.get
SwapMemberPosition(squad, leader, member)
log.info(s"Promoting player ${leader.Name} to be the leader of ${squad.Task}")
membership.foreach { _member =>
SquadEvents.publish(SquadServiceResponse(s"/${_member.CharId}/Squad", SquadResponse.PromoteMember(squad, promotedPlayer, index, position)))
}
SquadEvents.publish(SquadServiceResponse(s"/$promotingPlayer/Squad", SquadResponse.InitSquad(PlanetSideGUID(0))))
SquadEvents.publish(SquadServiceResponse(s"/$promotedPlayer/Squad", SquadResponse.InitSquad(squad.GUID)))
UpdateSquadListWhenListed(
squad,
SquadInfo().Leader(leader.Name)
)
UpdateSquadDetail(squad.GUID, squad,
SquadDetail()
.LeaderCharId(leader.CharId)
.Field3(value = 0L)
.LeaderName(leader.Name)
.Members(List(
SquadPositionEntry(position, SquadPositionDetail().CharId(member.CharId).Name(member.Name)),
SquadPositionEntry(index, SquadPositionDetail().CharId(leader.CharId).Name(leader.Name))
))
)
case _ => ;
}
case SquadAction.Update(char_id, health, max_health, armor, max_armor, pos, zone_number) => case SquadAction.Update(char_id, health, max_health, armor, max_armor, pos, zone_number) =>
memberToSquad.get(char_id) match { memberToSquad.get(char_id) match {
@ -387,14 +543,14 @@ class SquadService extends Actor {
val squad = lSquadOpt.getOrElse(StartSquad(tplayer)) val squad = lSquadOpt.getOrElse(StartSquad(tplayer))
squad.Task = purpose squad.Task = purpose
UpdateSquadListWhenListed(squad, SquadInfo().Task(purpose)) UpdateSquadListWhenListed(squad, SquadInfo().Task(purpose))
UpdateSquadDetailWhenListed(squad.GUID, squad, SquadDetail().Task(purpose)) UpdateSquadDetail(squad.GUID, squad, SquadDetail().Task(purpose))
case ChangeSquadZone(zone) => case ChangeSquadZone(zone) =>
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed squad's ops zone to $zone") log.info(s"${tplayer.Name}-${tplayer.Faction} has changed squad's ops zone to $zone")
val squad = lSquadOpt.getOrElse(StartSquad(tplayer)) val squad = lSquadOpt.getOrElse(StartSquad(tplayer))
squad.ZoneId = zone.zoneId.toInt squad.ZoneId = zone.zoneId.toInt
UpdateSquadListWhenListed(squad, SquadInfo().ZoneId(zone)) UpdateSquadListWhenListed(squad, SquadInfo().ZoneId(zone))
UpdateSquadDetailWhenListed(squad.GUID, squad, SquadDetail().ZoneId(zone)) UpdateSquadDetail(squad.GUID, squad, SquadDetail().ZoneId(zone))
case CloseSquadMemberPosition(position) => case CloseSquadMemberPosition(position) =>
val squad = lSquadOpt.getOrElse(StartSquad(tplayer)) val squad = lSquadOpt.getOrElse(StartSquad(tplayer))
@ -411,7 +567,7 @@ class SquadService extends Actor {
} }
memberPosition.Close() memberPosition.Close()
UpdateSquadListWhenListed(squad, listingChanged) UpdateSquadListWhenListed(squad, listingChanged)
UpdateSquadDetailWhenListed(squad.GUID, squad, UpdateSquadDetail(squad.GUID, squad,
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail.Closed))) SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail.Closed)))
) )
case Some(false) | None => ; case Some(false) | None => ;
@ -424,7 +580,7 @@ class SquadService extends Actor {
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened the #$position position in squad") log.info(s"${tplayer.Name}-${tplayer.Faction} has opened the #$position position in squad")
squad.Availability.update(position, true) squad.Availability.update(position, true)
UpdateSquadListWhenListed(squad, SquadInfo().Capacity(squad.Capacity)) UpdateSquadListWhenListed(squad, SquadInfo().Capacity(squad.Capacity))
UpdateSquadDetailWhenListed(squad.GUID, squad, UpdateSquadDetail(squad.GUID, squad,
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail.Open))) SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail.Open)))
) )
case Some(true) | None => ; case Some(true) | None => ;
@ -436,7 +592,7 @@ class SquadService extends Actor {
case Some(true) => case Some(true) =>
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the role of squad position #$position") log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the role of squad position #$position")
squad.Membership(position).Role = role squad.Membership(position).Role = role
UpdateSquadDetailWhenListed(squad.GUID, squad, UpdateSquadDetail(squad.GUID, squad,
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail().Role(role)))) SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail().Role(role))))
) )
case Some(false) | None => ; case Some(false) | None => ;
@ -448,7 +604,7 @@ class SquadService extends Actor {
case Some(true) => case Some(true) =>
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the orders for squad position #$position") log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the orders for squad position #$position")
squad.Membership(position).Orders = orders squad.Membership(position).Orders = orders
UpdateSquadDetailWhenListed(squad.GUID, squad, UpdateSquadDetail(squad.GUID, squad,
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail().DetailedOrders(orders)))) SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail().DetailedOrders(orders))))
) )
case Some(false) | None => ; case Some(false) | None => ;
@ -460,7 +616,7 @@ class SquadService extends Actor {
case Some(true) => case Some(true) =>
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the requirements for squad position #$position") log.info(s"${tplayer.Name}-${tplayer.Faction} has changed the requirements for squad position #$position")
squad.Membership(position).Requirements = certs squad.Membership(position).Requirements = certs
UpdateSquadDetailWhenListed(squad.GUID, squad, UpdateSquadDetail(squad.GUID, squad,
SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail().Requirements(certs)))) SquadDetail().Members(List(SquadPositionEntry(position, SquadPositionDetail().Requirements(certs))))
) )
case Some(false) | None => ; case Some(false) | None => ;
@ -519,7 +675,7 @@ class SquadService extends Actor {
squad.LocationFollowsSquadLead = false squad.LocationFollowsSquadLead = false
squad.AutoApproveInvitationRequests = false squad.AutoApproveInvitationRequests = false
UpdateSquadListWhenListed(squad, SquadInfo().Task("").ZoneId(None).Capacity(squad.Capacity)) UpdateSquadListWhenListed(squad, SquadInfo().Task("").ZoneId(None).Capacity(squad.Capacity))
UpdateSquadDetailWhenListed(squad.GUID, squad) UpdateSquadDetail(squad.GUID, squad)
case _ => case _ =>
} }
@ -530,6 +686,9 @@ class SquadService extends Actor {
log.info(s"${tplayer.Name} would like the #${position+1} spot in this squad") log.info(s"${tplayer.Name} would like the #${position+1} spot in this squad")
val membership = squad.Membership.zipWithIndex val membership = squad.Membership.zipWithIndex
val toMember = squad.Membership(position) val toMember = squad.Membership(position)
if(squad.Leader.CharId == tplayer.CharId) {
//TODO squad leader currently disallowed
} else
//the squad leader may swap to any open position; a normal member has to validate against requirements //the squad leader may swap to any open position; a normal member has to validate against requirements
if((squad.Leader.CharId == tplayer.CharId && toMember.CharId == 0) || ValidOpenSquadPosition(squad, position, toMember, tplayer.Certifications)) { if((squad.Leader.CharId == tplayer.CharId && toMember.CharId == 0) || ValidOpenSquadPosition(squad, position, toMember, tplayer.Certifications)) {
membership.find { case (member, _) => member.CharId == tplayer.CharId } match { membership.find { case (member, _) => member.CharId == tplayer.CharId } match {
@ -538,12 +697,15 @@ class SquadService extends Actor {
if(fromIndex == squad.LeaderPositionIndex) { if(fromIndex == squad.LeaderPositionIndex) {
squad.LeaderPositionIndex = position squad.LeaderPositionIndex = position
} }
//RemoveInvite(tplayer.CharId).foreach { _ =>
//close the old bids out
//}
membership membership
.filter { case (_member, _) => _member.CharId > 0 } .filter { case (_member, _) => _member.CharId > 0 }
.foreach { case (_member, _) => .foreach { case (_member, _) =>
SquadEvents.publish(SquadServiceResponse(s"/${_member.CharId}/Squad", SquadResponse.AssignMember(squad, fromIndex, position))) SquadEvents.publish(SquadServiceResponse(s"/${_member.CharId}/Squad", SquadResponse.AssignMember(squad, fromIndex, position)))
} }
UpdateSquadDetailWhenListed(squad.GUID, squad) UpdateSquadDetail(squad.GUID, squad)
case _ => ; case _ => ;
//somehow, this is not our squad; do nothing, for now //somehow, this is not our squad; do nothing, for now
} }
@ -561,10 +723,14 @@ class SquadService extends Actor {
if(ValidOpenSquadPosition(squad, position, toMember, tplayer.Certifications)) { if(ValidOpenSquadPosition(squad, position, toMember, tplayer.Certifications)) {
//we could join but we may need permission from the squad leader first //we could join but we may need permission from the squad leader first
log.info(s"Player ${tplayer.Name} would like to join the squad ${squad.Task}.") log.info(s"Player ${tplayer.Name} would like to join the squad ${squad.Task}.")
bids(tplayer.CharId) = NormalBid(squad.Leader.CharId, guid, position) val leader = squad.Leader
//val leader = squad.Leader val bid = BidForPosition(tplayer, guid, position)
//TODO ask permission from the squad leader, unless auto-approve is in effect val leaderCharId = leader.CharId
self ! SquadServiceMessage(tplayer, SquadAction.Membership(SquadRequestType.Accept, tplayer.CharId, None, "", None)) AddInvite(leaderCharId, bid) match {
case out @ Some(_) if out.contains(bid) =>
HandleBidForPosition(bid, tplayer)
case _ => ;
}
} }
case None => ; case None => ;
//squad does not exist? assume old local data; force update to correct discrepancy //squad does not exist? assume old local data; force update to correct discrepancy
@ -574,16 +740,19 @@ class SquadService extends Actor {
case (Some(squad), AssignSquadMemberToRole(position, char_id)) => case (Some(squad), AssignSquadMemberToRole(position, char_id)) =>
val membership = squad.Membership.zipWithIndex val membership = squad.Membership.zipWithIndex
(membership.find({ case (member, _) => member.CharId == char_id}), membership(position)) match { (membership.find({ case (member, _) => member.CharId == char_id}), membership(position)) match {
case (Some((fromMember, fromPosition)), (toMember, _)) => //TODO squad leader currently disallowed
case (Some((fromMember, fromPosition)), (toMember, _)) if fromPosition != 0 =>
val name = fromMember.Name val name = fromMember.Name
SwapMemberPosition(squad, toMember, fromMember) SwapMemberPosition(squad, toMember, fromMember)
// if(fromPosition == squad.LeaderPositionIndex) {
squad.LeaderPositionIndex = position
}
membership membership
.filter({ case (_member, _) => _member.CharId > 0 }) .filter({ case (_member, _) => _member.CharId > 0 })
.foreach { case (_member, _) => .foreach { case (_member, _) =>
SquadEvents.publish(SquadServiceResponse(s"/${_member.CharId}/Squad", SquadResponse.AssignMember(squad, fromPosition, position))) SquadEvents.publish(SquadServiceResponse(s"/${_member.CharId}/Squad", SquadResponse.AssignMember(squad, fromPosition, position)))
} }
UpdateSquadDetailWhenListed(squad.GUID, squad, UpdateSquadDetail(squad.GUID, squad,
SquadDetail().Members(List( SquadDetail().Members(List(
SquadPositionEntry(position, SquadPositionDetail().CharId(fromMember.CharId).Name(fromMember.Name)), SquadPositionEntry(position, SquadPositionDetail().CharId(fromMember.CharId).Name(fromMember.Name)),
SquadPositionEntry(fromPosition, SquadPositionDetail().CharId(char_id).Name(name)) SquadPositionEntry(fromPosition, SquadPositionDetail().CharId(char_id).Name(name))
@ -597,7 +766,15 @@ class SquadService extends Actor {
idToSquad.get(guid) match { idToSquad.get(guid) match {
case Some(squad) => case Some(squad) =>
viewDetails(tplayer.CharId) = guid viewDetails(tplayer.CharId) = guid
sender ! SquadServiceResponse(s"/${tplayer.CharId}/Squad", SquadResponse.Detail(squad.GUID, SquadService.Detail.Publish(squad))) sender ! SquadServiceResponse("", SquadResponse.Detail(squad.GUID, SquadService.Detail.Publish(squad)))
case None => ;
}
//the following message is feedback from a specific client, awaiting proper initialization
case (_, DisplayFullSquad()) =>
idToSquad.get(guid) match {
case Some(squad) =>
sender ! SquadServiceResponse("", SquadResponse.InitSquad(squad.GUID))
case None => ; case None => ;
} }
@ -609,6 +786,122 @@ class SquadService extends Actor {
} }
} }
/**
*
* @param invitedPlayer
* @param bid
* @return an optional invite;
* if added to the active invite position, return the parameter bid;
* if added to the queued invite, return the invite in the active position;
* if not added, return `None`
*/
def AddInvite(invitedPlayer : Long, bid : Invitation) : Option[Invitation] = {
invites.get(invitedPlayer) match {
case Some(_bid) =>
queuedInvites.get(invitedPlayer) match {
case Some(bidList) =>
if(_bid.InviterCharId != bid.InviterCharId && !bidList.exists { eachBid => eachBid.InviterCharId == bid.InviterCharId }) {
log.debug(s"Invite from ${bid.InviterCharId} to $invitedPlayer stored in queue while active invite request pending")
queuedInvites(invitedPlayer) = bid match {
case _: BidForPosition =>
val (normals, others) = bidList.partition(_.isInstanceOf[BidForPosition])
(normals :+ bid) ++ others
case _ =>
bidList :+ bid
}
None
}
else {
Some(_bid)
}
case None =>
if(_bid.InviterCharId != bid.InviterCharId) {
log.debug(s"Invite from ${bid.InviterCharId} to $invitedPlayer stored while active invite request pending")
queuedInvites(invitedPlayer) = List[Invitation](bid)
}
Some(_bid)
}
case None =>
invites(invitedPlayer) = bid
Some(bid)
}
}
def RemoveInvite(invitedPlayer : Long) : Option[Invitation] = {
invites.remove(invitedPlayer)
}
def RemoveQueuedInvites(invitedPlayer : Long) : List[Invitation] = {
queuedInvites.remove(invitedPlayer) match {
case Some(_bidList) => _bidList.toList
case None => Nil
}
}
def RemoveInvites(invitedPlayer : Long, invitingPlayer : Long) : Unit = {
queuedInvites.get(invitedPlayer) match {
case Some(bidList) =>
val list = bidList.filterNot { _.InviterCharId == invitingPlayer }
if(list.nonEmpty) {
queuedInvites(invitedPlayer) = list
}
else {
queuedInvites.remove(invitedPlayer)
}
case None => ;
}
invites.get(invitedPlayer) match {
case Some(_bid) =>
if(_bid.InviterCharId == invitingPlayer) {
//drop bid, try reload new bid
}
case None => ;
}
}
def NextInvite(invitedPlayer : Long) : Option[Invitation] = {
invites.get(invitedPlayer) match {
case None =>
queuedInvites.get(invitedPlayer) match {
case Some(list) =>
list match {
case Nil =>
None
case x :: Nil =>
invites(invitedPlayer) = x
queuedInvites.remove(invitedPlayer)
Some(x)
case x :: xs =>
invites(invitedPlayer) = x
queuedInvites(invitedPlayer) = xs
Some(x)
}
case None =>
None
}
case Some(_) =>
None
}
}
def HandleBidForPosition(bid : BidForPosition, player : Player) : Unit = {
idToSquad.get(bid.squad_guid) match {
case Some(squad) =>
val leaderCharId = squad.Leader.CharId
if(squad.AutoApproveInvitationRequests) {
self ! SquadServiceMessage(player, SquadAction.Membership(SquadRequestType.Accept, leaderCharId, None, "", None))
}
else {
SquadEvents.publish(SquadServiceResponse(s"/$leaderCharId/Squad", SquadResponse.WantsSquadPosition(bid.player.Name)))
}
case _ =>
//squad is missing; will this properly short-circuit?
log.error(s"Attempted to process ${bid.InviterName}'s bid for a position in a squad (id:${bid.squad_guid.guid}) that does not exist")
}
}
def JoinSquad(player : Player, squad : Squad, line : Int) : Boolean = { def JoinSquad(player : Player, squad : Squad, line : Int) : Boolean = {
val charId = player.CharId val charId = player.CharId
val position = squad.Membership(line) val position = squad.Membership(line)
@ -652,7 +945,7 @@ class SquadService extends Actor {
.foreach { member => .foreach { member =>
SquadEvents.publish(SquadServiceResponse(s"/${member.CharId}/Squad", SquadResponse.Join(squad, updatedIndex))) SquadEvents.publish(SquadServiceResponse(s"/${member.CharId}/Squad", SquadResponse.Join(squad, updatedIndex)))
} }
UpdateSquadDetailWhenListed(squad.GUID, squad, UpdateSquadDetail(squad.GUID, squad,
SquadDetail().Members(List(SquadPositionEntry(line, SquadPositionDetail().CharId(charId).Name(player.Name)))) SquadDetail().Members(List(SquadPositionEntry(line, SquadPositionDetail().CharId(charId).Name(player.Name))))
) )
} }
@ -670,30 +963,18 @@ class SquadService extends Actor {
membership.find { case (_member, _) => _member.CharId == charId } match { membership.find { case (_member, _) => _member.CharId == charId } match {
case Some((member, index)) => case Some((member, index)) =>
val updateList = membership.collect({ case (_member, _index) if _member.CharId > 0 => (_member.CharId, _index) }).toList val updateList = membership.collect({ case (_member, _index) if _member.CharId > 0 => (_member.CharId, _index) }).toList
//member leaves the squad completely
memberToSquad.remove(charId) memberToSquad.remove(charId)
member.Name = "" member.Name = ""
member.CharId = 0 member.CharId = 0
sender ! SquadServiceResponse("", SquadResponse.Leave(squad, updateList))
val size = squad.Size //other squad members see the member leaving
if(size < 2) { val leavingMember = List((charId, index))
//squad is rendered to just one person or less; collapse it membership
squad.Membership.foreach { _member => .filter { case (_member, _) => _member.CharId > 0 }
SquadEvents.publish(SquadServiceResponse(s"/${_member.CharId}/Squad", SquadResponse.Membership(SquadResponseType.Disband, 0, 0, charId, None, "", false, Some(None)))) .foreach { case (_member, _) =>
SquadEvents.publish( SquadServiceResponse(s"/${_member.CharId}/Squad", SquadResponse.Leave(squad, leavingMember)) )
} }
CloseOutSquad(squad, membership, updateList)
}
else {
//squad continues, despite player's parting
//member leaves the squad completely
sender ! SquadServiceResponse("", SquadResponse.Leave(squad, updateList))
//other squad members see the member leaving
val leavingMember = List((charId, index))
membership
.filter { case (_member, _) => _member.CharId > 0 }
.foreach { case (_member, _) =>
SquadEvents.publish( SquadServiceResponse(s"/${_member.CharId}/Squad", SquadResponse.Leave(squad, leavingMember)) )
}
}
true true
case None => case None =>
false false
@ -720,6 +1001,7 @@ class SquadService extends Actor {
SquadEvents.publish( SquadServiceResponse(s"/$charId/Squad", SquadResponse.Leave(squad, updateList)) ) SquadEvents.publish( SquadServiceResponse(s"/$charId/Squad", SquadResponse.Leave(squad, updateList)) )
} }
idToSquad.remove(squad.GUID) idToSquad.remove(squad.GUID)
UpdateSquadList(squad, None)
} }
def SwapMemberPosition(squad : Squad, toMember : Member, fromMember : Member) : Unit = { def SwapMemberPosition(squad : Squad, toMember : Member, fromMember : Member) : Unit = {
@ -744,6 +1026,13 @@ class SquadService extends Actor {
toMember.Armor = armor toMember.Armor = armor
} }
def UpdateSquadList(faction : PlanetSideEmpire.Value): Unit = {
val factionListings = publishedLists(faction)
SquadEvents.publish(
SquadServiceResponse(s"/$faction/Squad", SquadResponse.InitList(factionListings.toVector))
)
}
def UpdateSquadList(squad : Squad, changes : SquadInfo) : Unit = { def UpdateSquadList(squad : Squad, changes : SquadInfo) : Unit = {
UpdateSquadList(squad, Some(changes)) UpdateSquadList(squad, Some(changes))
} }
@ -759,7 +1048,6 @@ class SquadService extends Actor {
} }
def UpdateSquadList(squad : Squad, changes : Option[SquadInfo]) : Unit = { def UpdateSquadList(squad : Squad, changes : Option[SquadInfo]) : Unit = {
val entry = SquadService.SquadList.Publish(squad)
val faction = squad.Faction val faction = squad.Faction
val factionListings = publishedLists(faction) val factionListings = publishedLists(faction)
factionListings.find(info => { factionListings.find(info => {
@ -774,7 +1062,7 @@ class SquadService extends Actor {
case Some(changedFields) => case Some(changedFields) =>
//squad information update //squad information update
log.info(s"Squad will be updated") log.info(s"Squad will be updated")
factionListings(index) = entry factionListings(index) = SquadService.SquadList.Publish(squad)
SquadEvents.publish( SquadEvents.publish(
SquadServiceResponse(s"/$faction/Squad", SquadResponse.UpdateList(Seq((index, changedFields)))) SquadServiceResponse(s"/$faction/Squad", SquadResponse.UpdateList(Seq((index, changedFields))))
) )
@ -790,7 +1078,7 @@ class SquadService extends Actor {
case None => case None =>
//first time being published //first time being published
log.info(s"Squad will be introduced") log.info(s"Squad will be introduced")
factionListings += entry factionListings += SquadService.SquadList.Publish(squad)
SquadEvents.publish( SquadEvents.publish(
SquadServiceResponse(s"/$faction/Squad", SquadResponse.InitList(factionListings.toVector)) SquadServiceResponse(s"/$faction/Squad", SquadResponse.InitList(factionListings.toVector))
) )
@ -798,19 +1086,7 @@ class SquadService extends Actor {
} }
def UpdateSquadDetail(guid : PlanetSideGUID, squad : Squad) : Unit = { def UpdateSquadDetail(guid : PlanetSideGUID, squad : Squad) : Unit = {
UpdateSquadDetailWhenListed(guid, squad, SquadService.Detail.Publish(squad)) UpdateSquadDetail(guid, squad, SquadService.Detail.Publish(squad))
}
def UpdateSquadDetailWhenListed(guid : PlanetSideGUID, squad : Squad) : Unit = {
if(squad.Listed || squad.Size > 1) {
UpdateSquadDetail(guid, squad, SquadService.Detail.Publish(squad))
}
}
def UpdateSquadDetailWhenListed(guid : PlanetSideGUID, squad : Squad, detail : SquadDetail) : Unit = {
if(squad.Listed || squad.Size > 1) {
UpdateSquadDetail(guid, squad, detail)
}
} }
def UpdateSquadDetail(guid : PlanetSideGUID, squad : Squad, detail : SquadDetail) : Unit = { def UpdateSquadDetail(guid : PlanetSideGUID, squad : Squad, detail : SquadDetail) : Unit = {
@ -824,13 +1100,19 @@ class SquadService extends Actor {
} }
object SquadService { object SquadService {
trait PositionBid abstract class Invitation(char_id : Long, name : String) {
def InviterCharId : Long = char_id
def InviterName : String = name
}
final case class NormalBid(char_id : Long, squad_guid : PlanetSideGUID, position : Int) extends PositionBid final case class BidForPosition(player : Player, squad_guid : PlanetSideGUID, position : Int)
extends Invitation(player.CharId, player.Name)
final case class VacancyBid(char_id : Long, squad_guid : PlanetSideGUID) extends PositionBid final case class VacancyInvite(char_id : Long, name : String, squad_guid : PlanetSideGUID)
extends Invitation(char_id, name)
final case class SpontaneousBid(char_id : Long, placeholder : Squad) extends PositionBid final case class SpontaneousInvite(player : Player)
extends Invitation(player.CharId, player.Name)
object SquadList { object SquadList {
def Publish(squad : Squad) : SquadInfo = { def Publish(squad : Squad) : SquadInfo = {

View file

@ -222,7 +222,7 @@ class SquadDefinitionActionMessageTest extends Specification {
case SquadDefinitionActionMessage(unk1, unk2, action) => case SquadDefinitionActionMessage(unk1, unk2, action) =>
unk1 mustEqual PlanetSideGUID(0) unk1 mustEqual PlanetSideGUID(0)
unk2 mustEqual 0 unk2 mustEqual 0
action mustEqual SearchForSquadsWithParticularRole("Badass", 0L, 1, 0) action mustEqual SearchForSquadsWithParticularRole("Badass", Set(), 1, SearchMode.AnyPositions)
case _ => case _ =>
ko ko
} }
@ -233,7 +233,7 @@ class SquadDefinitionActionMessageTest extends Specification {
case SquadDefinitionActionMessage(unk1, unk2, action) => case SquadDefinitionActionMessage(unk1, unk2, action) =>
unk1 mustEqual PlanetSideGUID(0) unk1 mustEqual PlanetSideGUID(0)
unk2 mustEqual 0 unk2 mustEqual 0
action mustEqual SearchForSquadsWithParticularRole("Badass", 0L, 2, 0) action mustEqual SearchForSquadsWithParticularRole("Badass", Set(), 2, SearchMode.AnyPositions)
case _ => case _ =>
ko ko
} }
@ -244,7 +244,7 @@ class SquadDefinitionActionMessageTest extends Specification {
case SquadDefinitionActionMessage(unk1, unk2, action) => case SquadDefinitionActionMessage(unk1, unk2, action) =>
unk1 mustEqual PlanetSideGUID(0) unk1 mustEqual PlanetSideGUID(0)
unk2 mustEqual 0 unk2 mustEqual 0
action mustEqual SearchForSquadsWithParticularRole("Badass", 0L, 2, 1) action mustEqual SearchForSquadsWithParticularRole("Badass", Set(), 2, SearchMode.AvailablePositions)
case _ => case _ =>
ko ko
} }
@ -255,7 +255,7 @@ class SquadDefinitionActionMessageTest extends Specification {
case SquadDefinitionActionMessage(unk1, unk2, action) => case SquadDefinitionActionMessage(unk1, unk2, action) =>
unk1 mustEqual PlanetSideGUID(0) unk1 mustEqual PlanetSideGUID(0)
unk2 mustEqual 0 unk2 mustEqual 0
action mustEqual SearchForSquadsWithParticularRole("Badass", 536870928L, 2, 2) action mustEqual SearchForSquadsWithParticularRole("Badass", Set(CertificationType.InfiltrationSuit, CertificationType.AntiVehicular), 2, SearchMode.SomeCertifications)
case _ => case _ =>
ko ko
} }
@ -266,7 +266,7 @@ class SquadDefinitionActionMessageTest extends Specification {
case SquadDefinitionActionMessage(unk1, unk2, action) => case SquadDefinitionActionMessage(unk1, unk2, action) =>
unk1 mustEqual PlanetSideGUID(0) unk1 mustEqual PlanetSideGUID(0)
unk2 mustEqual 0 unk2 mustEqual 0
action mustEqual SearchForSquadsWithParticularRole("Badass", 536870928L, 2, 3) action mustEqual SearchForSquadsWithParticularRole("Badass", Set(CertificationType.InfiltrationSuit, CertificationType.AntiVehicular), 2, SearchMode.AllCertifications)
case _ => case _ =>
ko ko
} }
@ -436,35 +436,35 @@ class SquadDefinitionActionMessageTest extends Specification {
} }
"encode (34a)" in { "encode (34a)" in {
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", 0L, 1, 0)) val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", Set(), 1, SearchMode.AnyPositions))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_34a pkt mustEqual string_34a
} }
"encode (34b)" in { "encode (34b)" in {
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", 0L, 2, 0)) val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", Set(), 2, SearchMode.AnyPositions))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_34b pkt mustEqual string_34b
} }
"encode (34c)" in { "encode (34c)" in {
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", 0L, 2, 1)) val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", Set(), 2, SearchMode.AvailablePositions))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_34c pkt mustEqual string_34c
} }
"encode (34d)" in { "encode (34d)" in {
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", 536870928L, 2, 2)) val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", Set(CertificationType.InfiltrationSuit, CertificationType.AntiVehicular), 2, SearchMode.SomeCertifications))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_34d pkt mustEqual string_34d
} }
"encode (34e)" in { "encode (34e)" in {
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", 536870928L, 2, 3)) val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", Set(CertificationType.InfiltrationSuit, CertificationType.AntiVehicular), 2, SearchMode.AllCertifications))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_34e pkt mustEqual string_34e

View file

@ -12,7 +12,7 @@ class SquadMemberEventTest extends Specification {
"decode" in { "decode" in {
PacketCoding.DecodePacket(string).require match { PacketCoding.DecodePacket(string).require match {
case SquadMemberEvent(u1, u2, u3, u4, u5, u6, u7) => case SquadMemberEvent(u1, u2, u3, u4, u5, u6, u7) =>
u1 mustEqual 0 u1 mustEqual MemberEvent.Add
u2 mustEqual 7 u2 mustEqual 7
u3 mustEqual 42771010L u3 mustEqual 42771010L
u4 mustEqual 0 u4 mustEqual 0
@ -25,7 +25,7 @@ class SquadMemberEventTest extends Specification {
} }
"encode" in { "encode" in {
val msg = SquadMemberEvent(0, 7, 42771010L, 0, Some("HofD"), Some(7), Some(529745L)) val msg = SquadMemberEvent(MemberEvent.Add, 7, 42771010L, 0, Some("HofD"), Some(7), Some(529745L))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string pkt mustEqual string
} }

View file

@ -40,6 +40,7 @@ import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurret} import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurret}
import net.psforever.objects.teamwork.Squad
import net.psforever.objects.vehicles.{AccessPermissionGroup, Cargo, Utility, VehicleLockState, _} import net.psforever.objects.vehicles.{AccessPermissionGroup, Cargo, Utility, VehicleLockState, _}
import net.psforever.objects.vital._ import net.psforever.objects.vital._
import net.psforever.objects.zones.{InterstellarCluster, Zone, ZoneHotSpotProjector} import net.psforever.objects.zones.{InterstellarCluster, Zone, ZoneHotSpotProjector}
@ -358,7 +359,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
SquadListing(index, squadInfo) SquadListing(index, squadInfo)
}.toVector }.toVector
) )
log.info(s"updating squad with msg $o")
sendResponse( sendResponse(
ReplicationStreamMessage(6, None, ReplicationStreamMessage(6, None,
infos.map { case (index, squadInfo) => infos.map { case (index, squadInfo) =>
@ -388,12 +388,33 @@ class WorldSessionActor extends Actor with MDCContextAware {
SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(33)) SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(33))
) )
case SquadResponse.Membership(request_type, unk1, unk2, unk3, unk4, player_name, unk5, unk6) => case SquadResponse.Membership(request_type, unk1, unk2, char_id, opt_char_id, player_name, unk5, unk6) =>
sendResponse(SquadMembershipResponse(request_type, unk1, unk2, unk3, unk4, 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) => 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))) 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) => case SquadResponse.Join(squad, positionsToUpdate) =>
val leader = squad.Leader val leader = squad.Leader
val id = 11 val id = 11
@ -405,20 +426,20 @@ class WorldSessionActor extends Actor with MDCContextAware {
//we are joining the squad //we are joining the squad
//load each member's entry (our own too) //load each member's entry (our own too)
membershipPositions.foreach { case(member, index) => membershipPositions.foreach { case(member, index) =>
sendResponse(SquadMemberEvent(0, id, member.CharId, index, Some(member.Name), Some(member.ZoneId), Some(0))) 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) squadUI(member.CharId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position)
} }
//initialization //initialization
sendResponse(SquadMemberEvent(0, id, ourMember.CharId, ourIndex, Some(ourMember.Name), Some(ourMember.ZoneId), Some(0))) //repeat of our entry 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, 31, id)) //associate with squad?
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, ourIndex)) //associate with member position in squad? sendResponse(PlanetsideAttributeMessage(player.GUID, 32, ourIndex)) //associate with member position in squad?
//a finalization? what does this do? //a finalization? what does this do?
sendResponse(SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(18))) sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
case _ => case _ =>
//other player is joining our squad //other player is joining our squad
//load each member's entry //load each member's entry
membershipPositions.foreach { case(member, index) => membershipPositions.foreach { case(member, index) =>
sendResponse(SquadMemberEvent(0, id, member.CharId, index, Some(member.Name), Some(member.ZoneId), Some(0))) 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) squadUI(member.CharId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position)
} }
} }
@ -429,6 +450,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
.map{ case (member, _) => SquadStateInfo(member.CharId, member.Health, member.Armor, member.Position, 2,2, false, 429, None,None) } .map{ case (member, _) => SquadStateInfo(member.CharId, member.Health, member.Armor, member.Position, 2,2, false, 429, None,None) }
.toList .toList
)) ))
log.info(s"SquadCards: ${squadUI.map { case(id, card) => s"[${card.index}:${card.name}/$id]" }.mkString}")
case SquadResponse.Leave(_, positionsToUpdate) => case SquadResponse.Leave(_, positionsToUpdate) =>
val id = 11 val id = 11
@ -437,11 +459,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
//we are leaving the squad //we are leaving the squad
//remove each member's entry (our own too) //remove each member's entry (our own too)
positionsToUpdate.foreach { case(member, index) => positionsToUpdate.foreach { case(member, index) =>
sendResponse(SquadMemberEvent(1, id, member, index, None, None, None)) sendResponse(SquadMemberEvent.Remove(id, member, index))
squadUI.remove(member) squadUI.remove(member)
} }
//uninitialize //uninitialize
sendResponse(SquadMemberEvent(1, id, ourMember, ourIndex, None, None, None)) //repeat of our entry sendResponse(SquadMemberEvent.Remove(id, ourMember, ourIndex)) //repeat of our entry
sendResponse(PlanetsideAttributeMessage(player.GUID, 31, 0)) //disassociate with squad? 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, 32, 0)) //disassociate with member position in squad?
sendResponse(PlanetsideAttributeMessage(player.GUID, 34, 4294967295L)) //unknown, perhaps unrelated? sendResponse(PlanetsideAttributeMessage(player.GUID, 34, 4294967295L)) //unknown, perhaps unrelated?
@ -450,9 +472,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
case _ => case _ =>
//remove each member's entry //remove each member's entry
positionsToUpdate.foreach { case(member, index) => positionsToUpdate.foreach { case(member, index) =>
sendResponse(SquadMemberEvent(1, id, member, index, None, None, None)) sendResponse(SquadMemberEvent.Remove(id, member, index))
squadUI.remove(member) squadUI.remove(member)
} }
log.info(s"SquadCards: ${squadUI.map { case(id, card) => s"[${card.index}:${card.name}/$id]" }.mkString}")
} }
case SquadResponse.UpdateMembers(squad, positions) => case SquadResponse.UpdateMembers(squad, positions) =>
@ -467,7 +490,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
.collect({ .collect({
case (entry, element) if entry.zone_number != element.zone => case (entry, element) if entry.zone_number != element.zone =>
//zone gets updated for these entries //zone gets updated for these entries
sendResponse(SquadMemberEvent(3, 11, entry.char_id, element.index, None, Some(entry.zone_number), None)) 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) squadUI(entry.char_id) = SquadUIElement(element.name, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
entry entry
case (entry, element) if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position => case (entry, element) if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position =>
@ -486,53 +509,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
case SquadResponse.AssignMember(squad, from_index, to_index) => case SquadResponse.AssignMember(squad, from_index, to_index) =>
if(squadUI.nonEmpty) { //we've already swapped position internally; now we swap the cards
val toMember = squad.Membership(to_index) SwapSquadUIElements(squad, from_index, to_index)
val toCharId = toMember.CharId log.info(s"SquadCards: ${squadUI.map { case(id, card) => s"[${card.index}:${card.name}/$id]" }.mkString}")
val fromMember = squad.Membership(from_index)
val fromCharId = fromMember.CharId case SquadResponse.PromoteMember(squad, char_id, from_index, to_index) =>
val id = 11 //promotion will swap visual cards, but we must fix the backend
if(fromCharId > 0) { val id = 11
//toMember and fromMember have swapped places sendResponse(SquadMemberEvent.Promote(id, char_id))
val fromElem = squadUI(fromCharId) SwapSquadUIElements(squad, from_index, to_index)
val toElem = squadUI(toCharId) log.info(s"SquadCards: ${squadUI.map { case(id, card) => s"[${card.index}:${card.name}/$id]" }.mkString}")
sendResponse(SquadMemberEvent(1, id, toCharId, from_index, None, None, None))
sendResponse(SquadMemberEvent(1, id, fromCharId, to_index, None, None, None)) case SquadResponse.SquadSearchResults() =>
squadUI(toCharId) = SquadUIElement(fromElem.name, to_index, fromElem.zone, fromElem.health, fromElem.armor, fromElem.position) sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.NoSquadSearchResults()))
squadUI(fromCharId) = SquadUIElement(toElem.name, from_index, toElem.zone, toElem.health, toElem.armor, toElem.position)
sendResponse(SquadMemberEvent(0, id, toCharId, to_index, Some(fromElem.name), Some(fromElem.zone), Some(0)))
sendResponse(SquadMemberEvent(0, id, fromCharId, from_index, Some(toElem.name), Some(toElem.zone), Some(0)))
sendResponse(
SquadState(
PlanetSideGUID(id),
List(
SquadStateInfo(fromCharId, toElem.health, toElem.armor, toElem.position, 2, 2, false, 429, None, None),
SquadStateInfo(toCharId, fromElem.health, fromElem.armor, fromElem.position, 2, 2, false, 429, None, None)
)
)
)
}
else {
//previous fromMember has moved toMember
val elem = squadUI(fromCharId)
sendResponse(SquadMemberEvent(1, id, toCharId, from_index, None, None, None))
squadUI(toCharId) = SquadUIElement(elem.name, to_index, elem.zone, elem.health, elem.armor, elem.position)
sendResponse(SquadMemberEvent(0, id, toCharId, to_index, Some(elem.name), Some(elem.zone), Some(0)))
sendResponse(
SquadState(
PlanetSideGUID(id),
List(SquadStateInfo(toCharId, elem.health, elem.armor, elem.position, 2, 2, false, 429, None, None))
)
)
}
val charId = avatar.CharId
if(toCharId == charId) {
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, to_index))
}
else if(fromCharId == charId) {
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, from_index))
}
}
case _ => ; case _ => ;
} }
@ -3478,25 +3467,16 @@ class WorldSessionActor extends Actor with MDCContextAware {
"Degrado", "Degrado",
Set( Set(
CertificationType.StandardAssault, CertificationType.StandardAssault,
CertificationType.ArmoredAssault1,
CertificationType.MediumAssault,
CertificationType.ReinforcedExoSuit,
CertificationType.Harasser,
CertificationType.Engineering,
CertificationType.GroundSupport,
CertificationType.AgileExoSuit, CertificationType.AgileExoSuit,
CertificationType.AIMAX, CertificationType.StandardExoSuit
CertificationType.StandardExoSuit,
CertificationType.AAMAX,
CertificationType.ArmoredAssault2
), ),
9, 37,
0, 5,
PlanetSideGUID(1) PlanetSideGUID(7)
) )
) )
) )
sendResponse(SquadInvitationRequestMessage(PlanetSideGUID(1), 9, 41577140L, "Degrado")) sendResponse(SquadInvitationRequestMessage(PlanetSideGUID(1), 4, 41577140L, "Degrado"))
} }
player.Position = pos player.Position = pos
player.Velocity = vel player.Velocity = vel
@ -9136,6 +9116,56 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
} }
def SwapSquadUIElements(squad : Squad, fromIndex : Int, toIndex : Int) : Unit = {
if(squadUI.nonEmpty) {
val fromMember = squad.Membership(fromIndex)
val fromCharId = fromMember.CharId
val toMember = squad.Membership(toIndex)
val toCharId = toMember.CharId
val id = 11
if(fromCharId > 0) {
//toMember and fromMember have swapped places
val fromElem = squadUI(fromCharId)
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(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, fromCharId, fromIndex, toElem.name, toElem.zone, unk7 = 0))
sendResponse(
SquadState(
PlanetSideGUID(id),
List(
SquadStateInfo(fromCharId, toElem.health, toElem.armor, toElem.position, 2, 2, false, 429, None, None),
SquadStateInfo(toCharId, fromElem.health, fromElem.armor, fromElem.position, 2, 2, false, 429, None, None)
)
)
)
}
else {
//previous fromMember has moved toMember
val elem = squadUI(toCharId)
sendResponse(SquadMemberEvent.Remove(id, toCharId, fromIndex))
squadUI(toCharId) = SquadUIElement(elem.name, toIndex, elem.zone, elem.health, elem.armor, elem.position)
sendResponse(SquadMemberEvent.Add(id, toCharId, toIndex, elem.name, elem.zone, unk7 = 0))
sendResponse(
SquadState(
PlanetSideGUID(id),
List(SquadStateInfo(toCharId, elem.health, elem.armor, elem.position, 2, 2, false, 429, None, None))
)
)
}
val charId = avatar.CharId
if(toCharId == charId) {
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, toIndex))
}
else if(fromCharId == charId) {
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, fromIndex))
}
}
}
def failWithError(error : String) = { def failWithError(error : String) = {
log.error(error) log.error(error)
sendResponse(ConnectionClose()) sendResponse(ConnectionClose())