sufficient functionality that allows the UI for squads to display properly on squad joining and on squad leaving; clarification for SquadMemberEvent fields; QoL support functionality intergrated into SDDUM streams

This commit is contained in:
FateJH 2019-07-08 23:04:57 -04:00
parent 2a1b051710
commit 60a11b8c52
9 changed files with 456 additions and 309 deletions

View file

@ -82,12 +82,12 @@ class Squad(squadId : PlanetSideGUID, alignment : PlanetSideEmpire.Value) extend
LeaderPositionIndex LeaderPositionIndex
} }
def Leader : String = { def Leader : Member = {
membership.lift(leaderPositionIndex) match { membership(leaderPositionIndex) match {
case Some(member) => case member if !member.Name.equals("") =>
member.Name member
case None => case _ =>
"" throw new Exception("can not find squad leader!")
} }
} }

View file

@ -198,17 +198,16 @@ final case class SquadDetail(unk1 : Option[Int],
leader_name.orElse(Some("")), leader_name.orElse(Some("")),
task.orElse(Some("")), task.orElse(Some("")),
zone_id.orElse(Some(PlanetSideZoneID(0))), zone_id.orElse(Some(PlanetSideZoneID(0))),
unk7.orElse(Some(0)), unk7.orElse(Some(4983296)), //FullSquad value
{ {
val complete = SquadPositionDetail().Complete val complete = SquadPositionDetail().Complete
Some(member_info match { Some(member_info match {
case Some(info) => case Some(info) =>
//create one list that ensures all existing positions are "complete" then add a list of the missing indices //create one list that ensures all existing positions are "complete" then add a list of the missing indices
val fields = info.collect { val (indices, fields) = info.collect {
case SquadPositionEntry(a, Some(b)) => SquadPositionEntry(a, b.Complete) case SquadPositionEntry(a, Some(b)) => (a, SquadPositionEntry(a, b.Complete))
case out @ SquadPositionEntry(_, None) => out case out @ SquadPositionEntry(a, None) => (a, out)
} }.unzip
val indices = info.map { case SquadPositionEntry(a, _) => a }
((0 to 9).toSet.diff(indices.toSet).map { SquadPositionEntry(_, complete) } ++ fields).toList.sortBy(_.index) ((0 to 9).toSet.diff(indices.toSet).map { SquadPositionEntry(_, complete) } ++ fields).toList.sortBy(_.index)
case None => case None =>
//original list //original list
@ -585,7 +584,6 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
private def membersCodec(bitsOverByte : StreamLengthToken) : Codec[SquadDetail] = { private def membersCodec(bitsOverByte : StreamLengthToken) : Codec[SquadDetail] = {
import shapeless.:: import shapeless.::
( (
//TODO you can replace this outer structure with an either Codec
bool >>:~ { flag => bool >>:~ { flag =>
conditional(flag, { conditional(flag, {
bitsOverByte.Add(4) bitsOverByte.Add(4)
@ -593,15 +591,15 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
}) :: }) ::
conditional(!flag, { conditional(!flag, {
bitsOverByte.Add(3) bitsOverByte.Add(3)
uint(2) :: FullyPopulatedPositions.codec(bitsOverByte) uint2 :: FullyPopulatedPositions.codec(bitsOverByte)
}) })
} }
).exmap[SquadDetail] ( ).exmap[SquadDetail] (
{ {
case true :: Some(_ :: member_list :: HNil) :: _ :: HNil => case true :: Some(_ :: member_list :: HNil) :: _ :: HNil =>
Attempt.successful(SquadDetail(None, None, None, None, None, None, None, None, Some(member_list.toList))) Attempt.successful(SquadDetail(None, None, None, None, None, None, None, None, Some(ignoreTerminatingEntry(member_list.toList))))
case false :: None :: Some(_ :: member_list :: HNil) :: HNil => case false :: None :: Some(_ :: member_list :: HNil) :: HNil =>
Attempt.successful(SquadDetail(None, None, None, None, None, None, None, None, Some(member_list.toList))) Attempt.successful(SquadDetail(None, None, None, None, None, None, None, None, Some(ignoreTerminatingEntry(member_list.toList))))
}, },
{ {
case SquadDetail(_, _, _, _, _, _, _, _, Some(member_list)) => case SquadDetail(_, _, _, _, _, _, _, _, Some(member_list)) =>
@ -612,16 +610,54 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
} }
.flatten .flatten
.count(_.isEmpty) == 0) { .count(_.isEmpty) == 0) {
Attempt.successful(false :: None :: Some(2 :: member_list.toVector :: HNil) :: HNil) Attempt.successful(false :: None :: Some(2 :: ensureTerminatingEntry(member_list).toVector :: HNil) :: HNil)
} }
else { else {
Attempt.successful(true :: Some(4 :: member_list.toVector :: HNil) :: None :: HNil) Attempt.successful(true :: Some(4 :: ensureTerminatingEntry(member_list).toVector :: HNil) :: None :: HNil)
} }
case _ => case _ =>
Attempt.failure(Err("failed to encode squad data for members")) Attempt.failure(Err("failed to encode squad data for members"))
} }
) )
} }
//TODO while this pattern looks elegant, bitsOverByte does not accumulate properly with the either(bool, L, R); why?
// private def membersCodec(bitsOverByte : StreamLengthToken) : Codec[SquadDetail] = {
// import shapeless.::
// either(bool,
// { //false
// bitsOverByte.Add(3)
// uint2 :: FullyPopulatedPositions.codec(bitsOverByte)
// },
// { //true
// bitsOverByte.Add(4)
// uint(3) :: vector(ItemizedPositions.codec(bitsOverByte))
// }
// ).exmap[SquadDetail] (
// {
// case Left(_ :: member_list :: HNil) =>
// Attempt.successful(SquadDetail(None, None, None, None, None, None, None, None, Some(ignoreTerminatingEntry(member_list.toList))))
// case Right(_ :: member_list :: HNil) =>
// Attempt.successful(SquadDetail(None, None, None, None, None, None, None, None, Some(ignoreTerminatingEntry(member_list.toList))))
// },
// {
// case SquadDetail(_, _, _, _, _, _, _, _, Some(member_list)) =>
// if(member_list
// .collect { case position if position.info.nonEmpty =>
// val info = position.info.get
// List(info.is_closed, info.role, info.detailed_orders, info.requirements, info.char_id, info.name)
// }
// .flatten
// .count(_.isEmpty) == 0) {
// Attempt.successful(Left(2 :: ensureTerminatingEntry(member_list).toVector :: HNil))
// }
// else {
// Attempt.successful(Right(4 :: ensureTerminatingEntry(member_list).toVector :: HNil))
// }
// case _ =>
// Attempt.failure(Err("failed to encode squad data for members"))
// }
// )
// }
/** /**
* A failing pattern for when the coded value is not tied to a known field pattern. * A failing pattern for when the coded value is not tied to a known field pattern.
* This pattern does not read or write any bit data. * This pattern does not read or write any bit data.
@ -1245,10 +1281,9 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
}).xmap[Vector[SquadPositionEntry]] ( }).xmap[Vector[SquadPositionEntry]] (
{ {
case _ :: _ :: linkedMembers :: HNil => case _ :: _ :: linkedMembers :: HNil =>
unlinkFields(linkedMembers).toVector ignoreTerminatingEntry(unlinkFields(linkedMembers)).toVector
}, },
//TODO "memberList.size - 1"? the only two examples are "10" anyway memberList => 10 :: 12 :: linkFields(ensureTerminatingEntry(memberList.toList).reverse) :: HNil
memberList => memberList.size - 1 :: 12 :: linkFields(memberList.reverse.toList) :: HNil
) )
} }
} }
@ -1320,6 +1355,32 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
) )
} }
/**
* The last entry in the sequence of squad information listings should be a dummied listing with an index of 255.
* Ensure that this terminal entry is located at the end.
* @param list the listing of squad information
* @return the listing of squad information, with a specific final entry
*/
private def ensureTerminatingEntry(list : List[SquadPositionEntry]) : List[SquadPositionEntry] = {
list.lastOption match {
case Some(SquadPositionEntry(255, _)) => list
case Some(_) | None => list :+ SquadPositionEntry(255, None)
}
}
/**
* The last entry in the sequence of squad information listings should be a dummied listing with an index of 255.
* Remove this terminal entry from the end of the list so as not to hassle with it.
* @param list the listing of squad information
* @return the listing of squad information, with a specific final entry truncated
*/
private def ignoreTerminatingEntry(list : List[SquadPositionEntry]) : List[SquadPositionEntry] = {
list.lastOption match {
case Some(SquadPositionEntry(255, _)) => list.init
case Some(_) | None => list
}
}
implicit val codec : Codec[SquadDetailDefinitionUpdateMessage] = { implicit val codec : Codec[SquadDetailDefinitionUpdateMessage] = {
import shapeless.:: import shapeless.::
import net.psforever.newcodecs.newcodecs import net.psforever.newcodecs.newcodecs

View file

@ -8,10 +8,10 @@ import shapeless.{::, HNil}
final case class SquadMemberEvent(unk1 : Int, final case class SquadMemberEvent(unk1 : Int,
unk2 : Int, unk2 : Int,
unk3 : Long, char_id : Long,
unk4 : Int, member_position : Int,
unk5 : Option[String], player_name : Option[String],
unk6 : Option[Int], zone_number : Option[Int],
unk7 : Option[Long]) unk7 : Option[Long])
extends PlanetSideGamePacket { extends PlanetSideGamePacket {
type Packet = SquadMemberEvent type Packet = SquadMemberEvent
@ -20,40 +20,40 @@ final case class SquadMemberEvent(unk1 : Int,
} }
object SquadMemberEvent extends Marshallable[SquadMemberEvent] { object SquadMemberEvent extends Marshallable[SquadMemberEvent] {
def apply(unk1 : Int, unk2 : Int, unk3 : Long, unk4 : Int) : SquadMemberEvent = def apply(unk1 : Int, unk2 : Int, char_id : Long, member_position : Int) : SquadMemberEvent =
SquadMemberEvent(unk1, unk2, unk3, unk4, None, None, None) SquadMemberEvent(unk1, unk2, char_id, member_position, None, None, None)
def apply(unk2 : Int, unk3 : Long, unk4 : Int, unk5 : String, unk6 : Int, unk7 : Long) : SquadMemberEvent = def apply(unk2 : Int, char_id : Long, member_position : Int, player_name : String, zone_number : Int, unk7 : Long) : SquadMemberEvent =
SquadMemberEvent(0, unk2, unk3, unk4, Some(unk5), Some(unk6), Some(unk7)) SquadMemberEvent(0, unk2, char_id, member_position, Some(player_name), Some(zone_number), Some(unk7))
def apply(unk2 : Int, unk3 : Long, unk4 : Int, unk6 : Int) : SquadMemberEvent = def apply(unk2 : Int, char_id : Long, member_position : Int, zone_number : Int) : SquadMemberEvent =
SquadMemberEvent(3, unk2, unk3, unk4, None, Some(unk6), None) SquadMemberEvent(3, unk2, char_id, member_position, None, Some(zone_number), None)
def apply(unk2 : Int, unk3 : Long, unk4 : Int, unk7 : Long) : SquadMemberEvent = def apply(unk2 : Int, char_id : Long, member_position : Int, unk7 : Long) : SquadMemberEvent =
SquadMemberEvent(4, unk2, unk3, unk4, None, None, Some(unk7)) SquadMemberEvent(4, unk2, char_id, member_position, None, None, Some(unk7))
implicit val codec : Codec[SquadMemberEvent] = ( implicit val codec : Codec[SquadMemberEvent] = (
("unk1" | uintL(3)) >>:~ { unk1 => ("unk1" | uint(3)) >>:~ { unk1 =>
("unk2" | uint16L) :: ("unk2" | uint16L) ::
("unk3" | uint32L) :: ("char_id" | uint32L) ::
("unk4" | uintL(4)) :: ("member_position" | uint4) ::
conditional(unk1 == 0, "unk5" | PacketHelpers.encodedWideStringAligned(1)) :: conditional(unk1 == 0, "player_name" | PacketHelpers.encodedWideStringAligned(1)) ::
conditional(unk1 == 0 || unk1 == 3, "unk6" | uint16L) :: conditional(unk1 == 0 || unk1 == 3, "zone_number" | uint16L) ::
conditional(unk1 == 0 || unk1 == 4, "unk7" | uint32L) conditional(unk1 == 0 || unk1 == 4, "unk7" | uint32L)
}).exmap[SquadMemberEvent] ( }).exmap[SquadMemberEvent] (
{ {
case unk1 :: unk2 :: unk3 :: unk4 :: unk5 :: unk6 :: unk7 :: HNil => case unk1 :: unk2 :: char_id :: member_position :: player_name :: zone_number :: unk7 :: HNil =>
Attempt.Successful(SquadMemberEvent(unk1, unk2, unk3, unk4, unk5, unk6, unk7)) Attempt.Successful(SquadMemberEvent(unk1, unk2, char_id, member_position, player_name, zone_number, unk7))
}, },
{ {
case data @ SquadMemberEvent(0, unk2, unk3, unk4, Some(unk5), Some(unk6), Some(unk7)) => case data @ SquadMemberEvent(0, unk2, char_id, member_position, Some(player_name), Some(zone_number), Some(unk7)) =>
Attempt.Successful(0 :: unk2 :: unk3 :: unk4 :: Some(unk5) :: Some(unk6) :: Some(unk7) :: HNil) Attempt.Successful(0 :: unk2 :: char_id :: member_position :: Some(player_name) :: Some(zone_number) :: Some(unk7) :: HNil)
case data @ SquadMemberEvent(3, unk2, unk3, unk4, None, Some(unk6), None) => case data @ SquadMemberEvent(3, unk2, char_id, member_position, None, Some(zone_number), None) =>
Attempt.Successful(3 :: unk2 :: unk3 :: unk4 :: None :: Some(unk6) :: None :: HNil) Attempt.Successful(3 :: unk2 :: char_id :: member_position :: None :: Some(zone_number) :: None :: HNil)
case data @ SquadMemberEvent(4, unk2, unk3, unk4, None, None, Some(unk7)) => case data @ SquadMemberEvent(4, unk2, char_id, member_position, None, None, Some(unk7)) =>
Attempt.Successful(4 :: unk2 :: unk3 :: unk4 :: None :: None :: Some(unk7) :: HNil) Attempt.Successful(4 :: unk2 :: char_id :: member_position :: None :: None :: Some(unk7) :: HNil)
case data @ SquadMemberEvent(unk1, unk2, unk3, unk4, None, None, None) => case data @ SquadMemberEvent(unk1, unk2, char_id, member_position, None, None, None) =>
Attempt.Successful(unk1 :: unk2 :: unk3 :: unk4 :: None :: None :: None :: HNil) Attempt.Successful(unk1 :: 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

@ -0,0 +1,13 @@
// Copyright (c) 2019 PSForever
package services.teamwork
import net.psforever.objects.Player
import net.psforever.packet.game._
import net.psforever.types.SquadRequestType
object SquadAction {
trait Action
final case class Membership(request_type : SquadRequestType.Value, unk2 : Long, unk3 : Option[Long], player_name : String, unk5 : Option[Option[String]]) extends Action
final case class Definition(player : Player, zone_ordinal_number : Int, guid : PlanetSideGUID, line : Int, action : SquadAction)
}

View file

@ -1,7 +1,9 @@
// Copyright (c) 2019 PSForever // Copyright (c) 2019 PSForever
package services.teamwork package services.teamwork
import net.psforever.objects.teamwork.{Member, Squad}
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.types.SquadRequestType
object SquadResponse { object SquadResponse {
trait Response trait Response
@ -10,5 +12,9 @@ object SquadResponse {
final case class Update(infos : Iterable[(Int, 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 Remove(infos : Iterable[Int]) 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 Join(squad : Squad, positionsToUpdate : List[Int]) extends Response
final case class Leave(squad : Squad, positionsToUpdate : List[(Long, Int)]) extends Response
final case class Detail(guid : PlanetSideGUID, leader : String, task : String, zone : PlanetSideZoneID, member_info : List[SquadPositionDetail]) extends Response final case class Detail(guid : PlanetSideGUID, leader : String, task : String, zone : PlanetSideZoneID, member_info : List[SquadPositionDetail]) extends Response
} }

View file

@ -3,12 +3,13 @@ package services.teamwork
import akka.actor.Actor import akka.actor.Actor
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.teamwork.{Member, Squad} import net.psforever.objects.teamwork.Squad
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.types.{PlanetSideEmpire, Vector3} import net.psforever.types.{PlanetSideEmpire, SquadRequestType, Vector3}
import services.{GenericEventBus, Service} import services.{GenericEventBus, Service}
import scala.collection.concurrent.TrieMap import scala.collection.concurrent.TrieMap
import scala.collection.mutable
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
//import scala.concurrent.duration._ //import scala.concurrent.duration._
@ -22,11 +23,35 @@ class SquadService extends Actor {
PlanetSideEmpire.NC -> ListBuffer.empty, PlanetSideEmpire.NC -> ListBuffer.empty,
PlanetSideEmpire.VS -> ListBuffer.empty PlanetSideEmpire.VS -> ListBuffer.empty
) )
private val bids : mutable.LongMap[(PlanetSideGUID, Int)] = mutable.LongMap[(PlanetSideGUID, Int)]()
private [this] val log = org.log4s.getLogger private [this] val log = org.log4s.getLogger
override def preStart = { override def preStart = {
log.info("Starting...") 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).CharId = 30910985L
testSquad.Membership(0).ZoneId = 5
testSquad.Membership(0).Position = Vector3(5526.5234f, 3818.7344f, 54.59375f)
testSquad.Membership(1).Name = "xoBLADEox"
testSquad.Membership(1).CharId = 42781919L
testSquad.Membership(1).ZoneId = 5
testSquad.Membership(1).Position = Vector3(4673.5312f, 2604.8047f, 40.015625f)
testSquad.Membership(3).Name = "cabal0428"
testSquad.Membership(3).CharId = 353380L
testSquad.Membership(3).ZoneId = 5
testSquad.Membership(3).Position = Vector3(4727.492f, 2613.5312f, 51.390625f)
testSquad.Membership(4).Name = "xSkiku"
testSquad.Membership(4).CharId = 41588340L
testSquad.Membership(4).ZoneId = 5
testSquad.Membership(4).Position = Vector3(3675.0f, 4789.8047f, 63.21875f)
idToSquad(PlanetSideGUID(3)) = testSquad
testSquad.Listed = true
UpdateSquadList(testSquad, List())
} }
def GetNextSquadId() : PlanetSideGUID = { def GetNextSquadId() : PlanetSideGUID = {
@ -55,7 +80,7 @@ class SquadService extends Actor {
val name = player.Name val name = player.Name
val squadOut = opt match { val squadOut = opt match {
case Some(squad) => case Some(squad) =>
if(squad.Leader.equals(name)) { if(squad.Leader.Name.equals(name)) {
squad squad
} }
else { else {
@ -64,7 +89,7 @@ class SquadService extends Actor {
case None => case None =>
memberToSquad.get(name) match { memberToSquad.get(name) match {
case Some(squad) if squad.Leader.equals(name) => case Some(squad) if squad.Leader.Name.equals(name) =>
squad squad
case _ => case _ =>
val faction = player.Faction val faction = player.Faction
@ -115,7 +140,7 @@ class SquadService extends Actor {
SquadEvents.unsubscribe(sender()) SquadEvents.unsubscribe(sender())
memberToSquad.get(name) match { memberToSquad.get(name) match {
case Some(squad) => case Some(squad) =>
if(squad.Leader.equals(name)) { if(squad.Leader.Name.equals(name)) {
//we were the leader //we were the leader
if(squad.Membership.count(p => p.Name.equals("")) > 1) { if(squad.Membership.count(p => p.Name.equals("")) > 1) {
//other players were in the squad; publicly disband it //other players were in the squad; publicly disband it
@ -144,190 +169,218 @@ class SquadService extends Actor {
} }
case None => ; case None => ;
} }
//TODO leave squad, if joined to one, and perform clean-up //TODO leave squad, if joined to one, and perform clean-up
case Service.Leave(None) | Service.LeaveAll() => ; case Service.Leave(None) | Service.LeaveAll() => ;
case SquadServiceMessage.SquadDefinitionAction(tplayer, zone_ordinal_number, guid, _, action) => case SquadServiceMessage(tplayer, squad_action) => squad_action match {
import net.psforever.packet.game.SquadAction._ case SquadAction.Membership(request_type, char_id, optional_char_id, name, unk5) => request_type match {
val squadOpt = GetParticipatingSquad(tplayer, zone_ordinal_number) case SquadRequestType.Accept =>
action match { bids.get(char_id) match {
case SaveSquadDefinition() => case Some((squadGUID, line)) if idToSquad.get(squadGUID).nonEmpty =>
//join squad
case ChangeSquadPurpose(purpose) => val squad = idToSquad(squadGUID)
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed his squad's task to $purpose") val position = squad.Membership(line)
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt) if(squad.Availability(line) && position.CharId == 0 &&
squad.Task = purpose tplayer.Certifications.intersect(position.Requirements) == position.Requirements) {
UpdateSquadList(squad, List(SquadInfo.Field.Task)) position.Name = tplayer.Name
UpdateSquadDetail(squad) position.CharId = char_id
position.Health = tplayer.Health
case ChangeSquadZone(zone) => position.Armor = tplayer.Armor
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed squad's ops zone to $zone") position.Position = tplayer.Position
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt) position.ZoneId = 13
squad.ZoneId = zone.zoneId.toInt memberToSquad(tplayer.Name) = squad
UpdateSquadList(squad, List(SquadInfo.Field.ZoneId)) sender ! SquadServiceResponse("", SquadResponse.Join(
UpdateSquadDetail(squad) squad,
squad.Membership.zipWithIndex.collect({ case (member, index) if member.CharId != 0 => index }).toList
case CloseSquadMemberPosition(position) => ))
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
squad.Availability.lift(position) match {
case Some(true) =>
squad.Availability.update(position, false)
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)
} }
else { bids.remove(char_id)
List(SquadInfo.Field.Capacity) case _ => ;
}
memberPosition.Close()
UpdateSquadList(squad, listingChanged)
UpdateSquadDetail(squad)
case Some(false) | None => ;
} }
case AddSquadMemberPosition(position) => case SquadRequestType.Leave =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt) if(optional_char_id.contains(char_id)) {
squad.Availability.lift(position) match { //we're leaving the squad on our own
case Some(false) => val name = tplayer.Name
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened the #$position position in squad") val squad = memberToSquad(name)
squad.Availability.update(position, true) val membership = squad.Membership.zipWithIndex
UpdateSquadList(squad, List(SquadInfo.Field.Capacity)) val (member, index) = membership
UpdateSquadDetail(squad) .find { case (_member, _) => _member.Name == name }
case Some(true) | None => ; .get
} val updateList = membership.collect({ case (_member, _) if _member.CharId > 0 => (_member.CharId, index) }).toList
memberToSquad.remove(name)
case ChangeSquadMemberRequirementsRole(position, role) => member.Name = ""
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt) member.CharId = 0
squad.Availability.lift(position) match { sender ! SquadServiceResponse("", SquadResponse.Leave(squad, updateList))
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)
case Some(false) | None => ;
}
case ChangeSquadMemberRequirementsDetailedOrders(position, orders) =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
squad.Availability.lift(position) match {
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)
case Some(false) | None => ;
}
case ChangeSquadMemberRequirementsCertifications(position, certs) =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
squad.Availability.lift(position) match {
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)
case Some(false) | None => ;
}
case LocationFollowsSquadLead(state) =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
if(state) {
log.info(s"${tplayer.Name}-${tplayer.Faction} has moves the rally to the leader's position")
}
else {
log.info(s"${tplayer.Name}-${tplayer.Faction} has let the rally move freely")
}
squad.LocationFollowsSquadLead = state
case AutoApproveInvitationRequests(state) =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
if(state) {
log.info(s"${tplayer.Name}-${tplayer.Faction} is allowing all requests to join the squad")
}
else {
log.info(s"${tplayer.Name}-${tplayer.Faction} has started screening invitation requests")
}
squad.AutoApproveInvitationRequests = state
case SelectRoleForYourself(line) =>
//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.Availability(line))
squad.Membership.lift(line)
else
None
} match {
case Some(desiredPosition : Member)
if desiredPosition.Requirements.intersect(tplayer.Certifications) == desiredPosition.Requirements =>
//our character is qualified for this new position
if(squad.Leader.equals(tplayer.Name)) {
squad.LeaderPositionIndex = line //update
}
val hadPreviousPosition = squad.Membership.find(_.Name == name) match {
case Some(currentPosition)=>
currentPosition.Name = ""
currentPosition.CharId = 0L
currentPosition.ZoneId = 0
currentPosition.Health = 0
currentPosition.Armor = 0
currentPosition.Position = Vector3.Zero
true
case None =>
false
}
desiredPosition.Name = name
desiredPosition.CharId = tplayer.CharId
desiredPosition.ZoneId = zone_ordinal_number
desiredPosition.Health = tplayer.Health
desiredPosition.Armor = tplayer.Armor
desiredPosition.Position = tplayer.Position
if(!hadPreviousPosition) {
UpdateSquadList(squad, List(SquadInfo.Field.Size))
}
UpdateSquadDetail(squad)
case None => ;
}
case None => ;
}
case ListSquad() =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
if(!squad.Listed) {
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened recruitment for this squad")
squad.Listed = true
}
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)
}
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))
case None => ;
} }
case _ => ; case _ => ;
} }
case msg => case SquadAction.Definition(tplayer : Player, zone_ordinal_number : Int, guid : PlanetSideGUID, line : Int, action : SquadAction) =>
log.info(s"Unhandled message $msg from $sender") import net.psforever.packet.game.SquadAction._
val squadOpt = GetParticipatingSquad(tplayer, zone_ordinal_number)
action match {
case SaveSquadDefinition() =>
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)
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)
case CloseSquadMemberPosition(position) =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
squad.Availability.lift(position) match {
case Some(true) =>
squad.Availability.update(position, false)
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)
}
else {
List(SquadInfo.Field.Capacity)
}
memberPosition.Close()
UpdateSquadList(squad, listingChanged)
UpdateSquadDetail(squad)
case Some(false) | None => ;
}
case AddSquadMemberPosition(position) =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
squad.Availability.lift(position) match {
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)
case Some(true) | None => ;
}
case ChangeSquadMemberRequirementsRole(position, role) =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
squad.Availability.lift(position) match {
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)
case Some(false) | None => ;
}
case ChangeSquadMemberRequirementsDetailedOrders(position, orders) =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
squad.Availability.lift(position) match {
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)
case Some(false) | None => ;
}
case ChangeSquadMemberRequirementsCertifications(position, certs) =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
squad.Availability.lift(position) match {
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)
case Some(false) | None => ;
}
case LocationFollowsSquadLead(state) =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
if(state) {
log.info(s"${tplayer.Name}-${tplayer.Faction} has moves the rally to the leader's position")
}
else {
log.info(s"${tplayer.Name}-${tplayer.Faction} has let the rally move freely")
}
squad.LocationFollowsSquadLead = state
case AutoApproveInvitationRequests(state) =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
if(state) {
log.info(s"${tplayer.Name}-${tplayer.Faction} is allowing all requests to join the squad")
}
else {
log.info(s"${tplayer.Name}-${tplayer.Faction} has started screening invitation requests")
}
squad.AutoApproveInvitationRequests = state
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) =>
//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 {
case Some(squad) =>
val member = squad.Membership(position)
if(squad.Availability(position) && member.CharId == 0 &&
tplayer.Certifications.intersect(member.Requirements) == member.Requirements) {
bids(tplayer.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))
}
case None =>
//squad does not exist!? assume old data
//reload squad list data and blank the squad definition the user is looking at
}
}
case ListSquad() =>
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
if(!squad.Listed) {
log.info(s"${tplayer.Name}-${tplayer.Faction} has opened recruitment for this squad")
squad.Listed = true
}
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)
}
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))
case None => ;
}
case _ => ;
}
case msg =>
log.info(s"Unhandled message $msg from $sender")
}
} }
def UpdateSquadList(squad : Squad, listingChanged : List[Int]) : Unit = { def UpdateSquadList(squad : Squad, listingChanged : List[Int]) : Unit = {
@ -380,7 +433,7 @@ class SquadService extends Actor {
def GenSquadDetail(squad : Squad) : SquadResponse.Detail = { def GenSquadDetail(squad : Squad) : SquadResponse.Detail = {
SquadResponse.Detail( SquadResponse.Detail(
squad.GUID, squad.GUID,
squad.Leader, squad.Leader.Name,
squad.Task, squad.Task,
PlanetSideZoneID(squad.ZoneId), PlanetSideZoneID(squad.ZoneId),
squad.Membership.zipWithIndex.map({ case (p, index) => squad.Membership.zipWithIndex.map({ case (p, index) =>
@ -410,7 +463,7 @@ class SquadService extends Actor {
object SquadService { object SquadService {
def Publish(squad : Squad) : SquadInfo = { def Publish(squad : Squad) : SquadInfo = {
SquadInfo( SquadInfo(
squad.Leader, squad.Leader.Name,
squad.Task, squad.Task,
PlanetSideZoneID(squad.ZoneId), PlanetSideZoneID(squad.ZoneId),
squad.Size, squad.Size,

View file

@ -2,12 +2,9 @@
package services.teamwork package services.teamwork
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.packet.game.{PlanetSideGUID, SquadAction}
final case class SquadServiceMessage(forChannel : String, actionMessage : Any) final case class SquadServiceMessage(tplayer : Player, actionMessage : Any)
object SquadServiceMessage { object SquadServiceMessage {
final case class SquadDefinitionAction(player : Player, zone_ordinal_number : Int, guid : PlanetSideGUID, line : Int, action : SquadAction)
final case class RecoverSquadMembership() final case class RecoverSquadMembership()
} }

View file

@ -146,7 +146,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
guid mustEqual PlanetSideGUID(3) guid mustEqual PlanetSideGUID(3)
detail match { detail match {
case SquadDetail(None, None, None, None, None, None, None, None, Some(members)) => case SquadDetail(None, None, None, None, None, None, None, None, Some(members)) =>
members.size mustEqual 2 members.size mustEqual 1
members.head.index mustEqual 5 members.head.index mustEqual 5
members.head.info match { members.head.info match {
case Some(SquadPositionDetail(Some(is_closed), None, None, None, None, None)) => case Some(SquadPositionDetail(Some(is_closed), None, None, None, None, None)) =>
@ -169,7 +169,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
guid mustEqual PlanetSideGUID(7) guid mustEqual PlanetSideGUID(7)
detail match { detail match {
case SquadDetail(None, None, None, None, None, None, None, None, Some(members)) => case SquadDetail(None, None, None, None, None, None, None, None, Some(members)) =>
members.size mustEqual 2 members.size mustEqual 1
members.head.index mustEqual 0 members.head.index mustEqual 0
members.head.info match { members.head.info match {
case Some(SquadPositionDetail(None, Some(role), None, None, None, None)) => case Some(SquadPositionDetail(None, Some(role), None, None, None, None)) =>
@ -192,7 +192,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
guid mustEqual PlanetSideGUID(1) guid mustEqual PlanetSideGUID(1)
detail match { detail match {
case SquadDetail(None, None, None, None, None, None, None, None, Some(members)) => case SquadDetail(None, None, None, None, None, None, None, None, Some(members)) =>
members.size mustEqual 2 members.size mustEqual 1
members.head.index mustEqual 6 members.head.index mustEqual 6
members.head.info match { members.head.info match {
case Some(SquadPositionDetail(None, Some(role), None, Some(req), None, None)) => case Some(SquadPositionDetail(None, Some(role), None, Some(req), None, None)) =>
@ -217,7 +217,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
guid mustEqual PlanetSideGUID(3) guid mustEqual PlanetSideGUID(3)
detail match { detail match {
case SquadDetail(None, None, None, None, None, None, None, None, Some(members)) => case SquadDetail(None, None, None, None, None, None, None, None, Some(members)) =>
members.size mustEqual 2 members.size mustEqual 1
members.head.index mustEqual 5 members.head.index mustEqual 5
members.head.info match { members.head.info match {
case Some(SquadPositionDetail(None, None, None, None, Some(char_id), Some(name))) => case Some(SquadPositionDetail(None, None, None, None, Some(char_id), Some(name))) =>
@ -242,7 +242,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
detail match { detail match {
case SquadDetail(None, None, None, None, None, Some(task), None, None, Some(members)) => case SquadDetail(None, None, None, None, None, Some(task), None, None, Some(members)) =>
task mustEqual "\\#FF0000 The \\#ffffff Blades" task mustEqual "\\#FF0000 The \\#ffffff Blades"
members.size mustEqual 11 members.size mustEqual 10
// //
members.head.index mustEqual 9 members.head.index mustEqual 9
members.head.info match { members.head.info match {
@ -399,7 +399,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
detail match { detail match {
case SquadDetail(None, None, None, None, None, Some(task), None, None, Some(member_list)) => case SquadDetail(None, None, None, None, None, Some(task), None, None, Some(member_list)) =>
task mustEqual "PSForever Packet Collection" task mustEqual "PSForever Packet Collection"
member_list.size mustEqual 11 member_list.size mustEqual 10
member_list.head mustEqual SquadPositionEntry(9,Some( member_list.head mustEqual SquadPositionEntry(9,Some(
SquadPositionDetail( SquadPositionDetail(
Some(false), Some(false),
@ -516,8 +516,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
.Field1(0) .Field1(0)
.LeaderCharId(1221560L) .LeaderCharId(1221560L)
.Members(List( .Members(List(
SquadPositionEntry(6, SquadPositionDetail().Player(0L, "")), SquadPositionEntry(6, SquadPositionDetail().Player(0L, ""))
SquadPositionEntry(255, None)
)) ))
) )
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@ -540,8 +539,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
.Leader(42631712L, "Jaako") .Leader(42631712L, "Jaako")
.Field3(556403L) .Field3(556403L)
.Members(List( .Members(List(
SquadPositionEntry(0, SquadPositionDetail().Player(0L, "")), SquadPositionEntry(0, SquadPositionDetail().Player(0L, ""))
SquadPositionEntry(255, None)
)) ))
) )
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@ -593,8 +591,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
SquadPositionEntry(3, SquadPositionDetail().Role("The Princess")), SquadPositionEntry(3, SquadPositionDetail().Role("The Princess")),
SquadPositionEntry(2, SquadPositionDetail().Role("The Prince")), SquadPositionEntry(2, SquadPositionDetail().Role("The Prince")),
SquadPositionEntry(1, SquadPositionDetail().Role("The Queen")), SquadPositionEntry(1, SquadPositionDetail().Role("The Queen")),
SquadPositionEntry(0, SquadPositionDetail().Role("The King")), SquadPositionEntry(0, SquadPositionDetail().Role("The King"))
SquadPositionEntry(255, None)
)) ))
) )
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@ -606,8 +603,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
PlanetSideGUID(3), PlanetSideGUID(3),
SquadDetail() SquadDetail()
.Members(List( .Members(List(
SquadPositionEntry(5, SquadPositionDetail.Closed), SquadPositionEntry(5, SquadPositionDetail.Closed)
SquadPositionEntry(255, None)
)) ))
) )
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@ -620,8 +616,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
PlanetSideGUID(7), PlanetSideGUID(7),
SquadDetail() SquadDetail()
.Members(List( .Members(List(
SquadPositionEntry(0, SquadPositionDetail().Role("Commander")), SquadPositionEntry(0, SquadPositionDetail().Role("Commander"))
SquadPositionEntry(255, None)
)) ))
) )
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@ -635,8 +630,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
.Members(List( .Members(List(
SquadPositionEntry(6, SquadPositionDetail() SquadPositionEntry(6, SquadPositionDetail()
.Role("ADV Hacker") .Role("ADV Hacker")
.Requirements(Set(CertificationType.AdvancedHacking))), .Requirements(Set(CertificationType.AdvancedHacking)))
SquadPositionEntry(255, None)
)) ))
) )
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@ -648,8 +642,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
PlanetSideGUID(3), PlanetSideGUID(3),
SquadDetail() SquadDetail()
.Members(List( .Members(List(
SquadPositionEntry(5, SquadPositionDetail().Player(1218249L, "Duckmaster43")), SquadPositionEntry(5, SquadPositionDetail().Player(1218249L, "Duckmaster43"))
SquadPositionEntry(255, None)
)) ))
) )
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@ -671,8 +664,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
SquadPositionEntry(3, SquadPositionDetail().Role("")), SquadPositionEntry(3, SquadPositionDetail().Role("")),
SquadPositionEntry(2, SquadPositionDetail().Role("")), SquadPositionEntry(2, SquadPositionDetail().Role("")),
SquadPositionEntry(1, SquadPositionDetail().Role("")), SquadPositionEntry(1, SquadPositionDetail().Role("")),
SquadPositionEntry(0, SquadPositionDetail().Role("")), SquadPositionEntry(0, SquadPositionDetail().Role(""))
SquadPositionEntry(255, None)
)) ))
) )
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@ -715,7 +707,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
PlanetSideGUID(3), PlanetSideGUID(3),
SquadDetail SquadDetail
.Task("PSForever Packet Collection") .Task("PSForever Packet Collection")
.Members((0 to 9).map { index => SquadPositionEntry(index, position) }.reverse.toList :+ SquadPositionEntry(255, None)) .Members((0 to 9).map { index => SquadPositionEntry(index, position) }.reverse.toList)
) )
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_mixed pkt mustEqual string_mixed

View file

@ -52,7 +52,7 @@ import services.galaxy.{GalaxyResponse, GalaxyServiceResponse}
import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse} import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse}
import services.vehicle.support.TurretUpgrader import services.vehicle.support.TurretUpgrader
import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse} import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse}
import services.teamwork._ import services.teamwork.{SquadAction => SquadServiceAction, SquadServiceMessage, SquadServiceResponse, SquadResponse, SquadService}
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
@ -376,16 +376,65 @@ class WorldSessionActor extends Actor with MDCContextAware {
SquadDetailDefinitionUpdateMessage( SquadDetailDefinitionUpdateMessage(
guid, guid,
SquadDetail() SquadDetail()
.LeaderCharId(member_info.find(_.name == leader).get.char_id.get) .LeaderCharId(member_info.find(_.name.contains(leader)).get.char_id.get)
.LeaderName(leader) .LeaderName(leader)
.Task(task) .Task(task)
.ZoneId(zone) .ZoneId(zone)
.Members( .Members(member_info.zipWithIndex.map { case (a, b) => SquadPositionEntry(b, a) })
member_info.zipWithIndex.map { case (a, b) => SquadPositionEntry(b, a) }
)
.Complete .Complete
) )
) )
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.Join(squad, positionsToUpdate) =>
val leader = squad.Leader
val id = 11
val membershipPositions = squad.Membership
.zipWithIndex
.filter { case (_, index ) => positionsToUpdate.contains(index) }
sendResponse(SquadMembershipResponse(SquadRequestType.Accept, 0, 0, player.CharId, Some(leader.CharId), player.Name, true, Some(None)))
//load each member's entry
membershipPositions.foreach { case(member, index) =>
sendResponse(SquadMemberEvent(0, id, member.CharId, index, Some(member.Name), Some(member.ZoneId), Some(0)))
}
//repeat own entry and initialize connection to squad
membershipPositions.find({ case(member, _) => member.CharId == avatar.CharId }) match {
case Some((member, index)) =>
sendResponse(SquadMemberEvent(0, id, member.CharId, index, Some(member.Name), Some(member.ZoneId), Some(0)))
sendResponse(PlanetsideAttributeMessage(player.GUID, 31, id)) //associate with squad?
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, index)) //associate with member position in squad?
case _ => ;
}
//send first update for map icons
sendResponse(SquadState(PlanetSideGUID(id),
membershipPositions
.filterNot { case (member, _) => member.CharId == avatar.CharId }
.map{ case (member, _) => SquadStateInfo(member.CharId, 64,64, member.Position, 2,2, false, 429, None,None) }
.toList
))
//a finalization? what does this do?
sendResponse(SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(18, hex"00".toBitVector.take(6))))
case SquadResponse.Leave(_, positionsToUpdate) =>
val id = 11
sendResponse(SquadMembershipResponse(SquadRequestType.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))
}
//repeat own entry and rescind connection to squad
positionsToUpdate.find({ case(member, _) => member == avatar.CharId }) match {
case Some((member, index)) =>
sendResponse(SquadMemberEvent(1, id, member, index, None, None, None))
sendResponse(PlanetsideAttributeMessage(player.GUID, 31, 0)) //disassociate with squad?
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, 0)) //disassociate with member position in squad?
sendResponse(PlanetsideAttributeMessage(player.GUID, 34, 4294967295L)) //unknown
case _ => ;
}
//a finalization? what does this do?
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18, hex"00".toBitVector.take(6))))
case _ => ; case _ => ;
} }
@ -2911,42 +2960,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
interstellarFerryTopLevelGUID = None interstellarFerryTopLevelGUID = None
case _ => ; case _ => ;
} }
//SQUAD TESTING CODE squadService ! Service.Join(s"${avatar.faction}") //channel will be player.Faction
sendResponse(ReplicationStreamMessage(
5,
Some(6),
Vector(
SquadListing(0, SquadInfo(Some("xNick"), Some("FLY,ALL WELCOME!"), Some(PlanetSideZoneID(7)), Some(8), Some(10), Some(PlanetSideGUID(1)))),
SquadListing(1, SquadInfo(Some("HofD"), Some("=KOK+SPC+FLY= All Welcome"), Some(PlanetSideZoneID(7)), Some(3), Some(10), Some(PlanetSideGUID(3))))
)
))
sendResponse(
SquadDetailDefinitionUpdateMessage(
PlanetSideGUID(3),
SquadDetail(
3,
1792,
42771010L,
529745L,
"HofD",
"\\#ffdc00***\\#9640ff=KOK+SPC+FLY=\\#ffdc00***\\#FF4040 All Welcome",
PlanetSideZoneID(7),
4983296,
List(
SquadPositionEntry(0, SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", "", Set(), 0, "")),
SquadPositionEntry(1, SquadPositionDetail("\\#ffdc00 C", "", Set(), 0, "")),
SquadPositionEntry(2, SquadPositionDetail("\\#ffdc00 H", "", Set(), 42644970L, "OpolE")),
SquadPositionEntry(3, SquadPositionDetail("\\#ffdc00 I", "", Set(), 41604210L, "BobaF3tt907")),
SquadPositionEntry(4, SquadPositionDetail("\\#ffdc00 N", "", Set(), 0, "")),
SquadPositionEntry(5, SquadPositionDetail("\\#ffdc00 A", "", Set(), 0, "")),
SquadPositionEntry(6, SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", "", Set(), 0, "")),
SquadPositionEntry(7, SquadPositionDetail("\\#9640ff K", "", Set(), 0, "")),
SquadPositionEntry(8, SquadPositionDetail("\\#9640ff O", "", Set(), 42771010L ,"HofD")),
SquadPositionEntry(9, SquadPositionDetail("\\#9640ff K", "", Set(), 0, ""))
)
)
)
)
} }
def handleControlPkt(pkt : PlanetSideControlPacket) = { def handleControlPkt(pkt : PlanetSideControlPacket) = {
@ -3359,18 +3373,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) => case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) =>
if(deadState == DeadState.Alive) { if(deadState == DeadState.Alive) {
if(!player.Crouching && is_crouching) { //SQUAD TESTING CODE if(!player.Crouching && is_crouching) { //SQUAD TESTING CODE
sendRawResponse(hex"e80300818800015c5189004603408c000000012000ff") //...
// sendResponse(SquadMembershipResponse(SquadRequestType.Invite,0,0,42771010,Some(avatar.CharId),"HofD",false,None))
// sendResponse(SquadMembershipResponse(SquadRequestType.Accept,0,0,avatar.CharId,Some(42771010),"VirusGiver",true,Some(None)))
// sendResponse(SquadMemberEvent(0,7,42771010,0,Some("HofD"),Some(7),Some(529745)))
// sendResponse(SquadMemberEvent(0,7,42644970,1,Some("OpolE"),Some(7),Some(6418)))
// sendResponse(SquadMemberEvent(0,7,41604210,8,Some("BobaF3tt907"),Some(12),Some(8097)))
// sendResponse(PlanetsideAttributeMessage(player.GUID, 49, 7))
// sendResponse(PlanetsideAttributeMessage(player.GUID, 50, 2))
// sendResponse(PlanetsideAttributeMessage(player.GUID, 51, 8))
// sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(3), 0, SquadAction.Unknown(18, hex"".toBitVector)))
// sendResponse(PlanetsideAttributeMessage(player.GUID, 83, 0))
// sendResponse(SquadState(PlanetSideGUID(7),List(SquadStateInfo(avatar.CharId,64,64,Vector3(3464.0469f,4065.5703f,20.015625f),2,2,false,0,None,None))))
} }
player.Position = pos player.Position = pos
player.Velocity = vel player.Velocity = vel
@ -4850,7 +4853,29 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ SquadDefinitionActionMessage(u1, u2, action) => case msg @ SquadDefinitionActionMessage(u1, u2, action) =>
log.info(s"SquadDefinitionAction: $msg") log.info(s"SquadDefinitionAction: $msg")
squadService ! SquadServiceMessage.SquadDefinitionAction(player, continent.Number, u1, u2, action) squadService ! SquadServiceMessage(player, SquadServiceAction.Definition(player, continent.Number, u1, u2, action))
case msg @ SquadMembershipRequest(request_type, unk2, unk3, player_name, unk5) =>
log.info(s"$msg")
squadService ! SquadServiceMessage(player, SquadServiceAction.Membership(request_type, unk2, unk3, player_name, unk5))
// if(request_type == SquadRequestType.Accept) {
// sendResponse(SquadMembershipResponse(SquadRequestType.Accept, 0, 0, unk2, Some(30910985L), player.Name, true, Some(None)))
// sendResponse(SquadMemberEvent(0, 11, 30910985L, 0, Some("Wizkid45"), Some(5), Some(320036)))
// sendResponse(SquadMemberEvent(0, 11, 42781919L, 1, Some("xoBLADEox"), Some(5), Some(528805)))
// sendResponse(SquadMemberEvent(0, 11, avatar.CharId, 2, Some(player.Name), Some(13), Some(320036)))
// sendResponse(SquadMemberEvent(0, 11, 353380L, 3, Some("cabal0428") ,Some(5), Some(8156)))
// sendResponse(SquadMemberEvent(0, 11, 41588340L, 4 ,Some("xSkiku"), Some(5), Some(0)))
// sendResponse(SquadMemberEvent(0, 11, avatar.CharId, 2, Some(player.Name), Some(13), Some(320036)))
// sendResponse(PlanetsideAttributeMessage(player.GUID, 31, 11)) //associate with squad?
// sendResponse(PlanetsideAttributeMessage(player.GUID, 32, 2)) //associate with member position in squad?
// sendResponse(SquadState(PlanetSideGUID(11),List(
// SquadStateInfo(30910985L, 50,64, Vector3(5526.5234f,3818.7344f,54.59375f), 2,2, false, 429, None,None),
// SquadStateInfo(42781919L, 64,0, Vector3(4673.5312f,2604.8047f,40.015625f), 2,2, false, 149, None,None),
// SquadStateInfo(353380L, 64,64, Vector3(4727.492f,2613.5312f,51.390625f), 2,2, false, 0, None,None),
// SquadStateInfo(41588340L, 64,64, Vector3(3675.0f,4789.8047f,63.21875f), 2,2, false, 0, None,None)
// )))
// sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(3), 0, SquadAction.Unknown(18, hex"00".toBitVector.take(6))))
// }
case msg @ GenericCollisionMsg(u1, p, t, php, thp, pv, tv, ppos, tpos, u2, u3, u4) => case msg @ GenericCollisionMsg(u1, p, t, php, thp, pv, tv, ppos, tpos, u2, u3, u4) =>
log.info("Ouch! " + msg) log.info("Ouch! " + msg)