From 60a11b8c52d47ff6d57db75de3dda35f7dc422be Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 8 Jul 2019 23:04:57 -0400 Subject: [PATCH] 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 --- .../psforever/objects/teamwork/Squad.scala | 12 +- .../SquadDetailDefinitionUpdateMessage.scala | 91 +++- .../packet/game/SquadMemberEvent.scala | 54 +-- .../scala/services/teamwork/SquadAction.scala | 13 + .../services/teamwork/SquadResponse.scala | 6 + .../services/teamwork/SquadService.scala | 413 ++++++++++-------- .../teamwork/SquadServiceMessage.scala | 5 +- ...uadDetailDefinitionUpdateMessageTest.scala | 38 +- .../src/main/scala/WorldSessionActor.scala | 133 +++--- 9 files changed, 456 insertions(+), 309 deletions(-) create mode 100644 common/src/main/scala/services/teamwork/SquadAction.scala diff --git a/common/src/main/scala/net/psforever/objects/teamwork/Squad.scala b/common/src/main/scala/net/psforever/objects/teamwork/Squad.scala index 21a48cb3..b5e5d81d 100644 --- a/common/src/main/scala/net/psforever/objects/teamwork/Squad.scala +++ b/common/src/main/scala/net/psforever/objects/teamwork/Squad.scala @@ -82,12 +82,12 @@ class Squad(squadId : PlanetSideGUID, alignment : PlanetSideEmpire.Value) extend LeaderPositionIndex } - def Leader : String = { - membership.lift(leaderPositionIndex) match { - case Some(member) => - member.Name - case None => - "" + def Leader : Member = { + membership(leaderPositionIndex) match { + case member if !member.Name.equals("") => + member + case _ => + throw new Exception("can not find squad leader!") } } diff --git a/common/src/main/scala/net/psforever/packet/game/SquadDetailDefinitionUpdateMessage.scala b/common/src/main/scala/net/psforever/packet/game/SquadDetailDefinitionUpdateMessage.scala index ad080bcf..9bda7527 100644 --- a/common/src/main/scala/net/psforever/packet/game/SquadDetailDefinitionUpdateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/SquadDetailDefinitionUpdateMessage.scala @@ -198,17 +198,16 @@ final case class SquadDetail(unk1 : Option[Int], leader_name.orElse(Some("")), task.orElse(Some("")), zone_id.orElse(Some(PlanetSideZoneID(0))), - unk7.orElse(Some(0)), + unk7.orElse(Some(4983296)), //FullSquad value { val complete = SquadPositionDetail().Complete Some(member_info match { case Some(info) => //create one list that ensures all existing positions are "complete" then add a list of the missing indices - val fields = info.collect { - case SquadPositionEntry(a, Some(b)) => SquadPositionEntry(a, b.Complete) - case out @ SquadPositionEntry(_, None) => out - } - val indices = info.map { case SquadPositionEntry(a, _) => a } + val (indices, fields) = info.collect { + case SquadPositionEntry(a, Some(b)) => (a, SquadPositionEntry(a, b.Complete)) + case out @ SquadPositionEntry(a, None) => (a, out) + }.unzip ((0 to 9).toSet.diff(indices.toSet).map { SquadPositionEntry(_, complete) } ++ fields).toList.sortBy(_.index) case None => //original list @@ -585,7 +584,6 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini private def membersCodec(bitsOverByte : StreamLengthToken) : Codec[SquadDetail] = { import shapeless.:: ( - //TODO you can replace this outer structure with an either Codec bool >>:~ { flag => conditional(flag, { bitsOverByte.Add(4) @@ -593,15 +591,15 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini }) :: conditional(!flag, { bitsOverByte.Add(3) - uint(2) :: FullyPopulatedPositions.codec(bitsOverByte) + uint2 :: FullyPopulatedPositions.codec(bitsOverByte) }) } ).exmap[SquadDetail] ( { 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 => - 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)) => @@ -612,16 +610,54 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini } .flatten .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 { - Attempt.successful(true :: Some(4 :: member_list.toVector :: HNil) :: None :: HNil) + Attempt.successful(true :: Some(4 :: ensureTerminatingEntry(member_list).toVector :: HNil) :: None :: HNil) } case _ => 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. * This pattern does not read or write any bit data. @@ -1245,10 +1281,9 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini }).xmap[Vector[SquadPositionEntry]] ( { case _ :: _ :: linkedMembers :: HNil => - unlinkFields(linkedMembers).toVector + ignoreTerminatingEntry(unlinkFields(linkedMembers)).toVector }, - //TODO "memberList.size - 1"? the only two examples are "10" anyway - memberList => memberList.size - 1 :: 12 :: linkFields(memberList.reverse.toList) :: HNil + memberList => 10 :: 12 :: linkFields(ensureTerminatingEntry(memberList.toList).reverse) :: 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] = { import shapeless.:: import net.psforever.newcodecs.newcodecs diff --git a/common/src/main/scala/net/psforever/packet/game/SquadMemberEvent.scala b/common/src/main/scala/net/psforever/packet/game/SquadMemberEvent.scala index df8bd7b0..50dec0f6 100644 --- a/common/src/main/scala/net/psforever/packet/game/SquadMemberEvent.scala +++ b/common/src/main/scala/net/psforever/packet/game/SquadMemberEvent.scala @@ -8,10 +8,10 @@ import shapeless.{::, HNil} final case class SquadMemberEvent(unk1 : Int, unk2 : Int, - unk3 : Long, - unk4 : Int, - unk5 : Option[String], - unk6 : Option[Int], + char_id : Long, + member_position : Int, + player_name : Option[String], + zone_number : Option[Int], unk7 : Option[Long]) extends PlanetSideGamePacket { type Packet = SquadMemberEvent @@ -20,40 +20,40 @@ final case class SquadMemberEvent(unk1 : Int, } object SquadMemberEvent extends Marshallable[SquadMemberEvent] { - def apply(unk1 : Int, unk2 : Int, unk3 : Long, unk4 : Int) : SquadMemberEvent = - SquadMemberEvent(unk1, unk2, unk3, unk4, None, None, None) + def apply(unk1 : Int, unk2 : Int, char_id : Long, member_position : Int) : SquadMemberEvent = + 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 = - SquadMemberEvent(0, unk2, unk3, unk4, Some(unk5), Some(unk6), Some(unk7)) + def apply(unk2 : Int, char_id : Long, member_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)) - def apply(unk2 : Int, unk3 : Long, unk4 : Int, unk6 : Int) : SquadMemberEvent = - SquadMemberEvent(3, unk2, unk3, unk4, None, Some(unk6), None) + def apply(unk2 : Int, char_id : Long, member_position : Int, zone_number : Int) : SquadMemberEvent = + SquadMemberEvent(3, unk2, char_id, member_position, None, Some(zone_number), None) - def apply(unk2 : Int, unk3 : Long, unk4 : Int, unk7 : Long) : SquadMemberEvent = - SquadMemberEvent(4, unk2, unk3, unk4, None, None, Some(unk7)) + def apply(unk2 : Int, char_id : Long, member_position : Int, unk7 : Long) : SquadMemberEvent = + SquadMemberEvent(4, unk2, char_id, member_position, None, None, Some(unk7)) implicit val codec : Codec[SquadMemberEvent] = ( - ("unk1" | uintL(3)) >>:~ { unk1 => + ("unk1" | uint(3)) >>:~ { unk1 => ("unk2" | uint16L) :: - ("unk3" | uint32L) :: - ("unk4" | uintL(4)) :: - conditional(unk1 == 0, "unk5" | PacketHelpers.encodedWideStringAligned(1)) :: - conditional(unk1 == 0 || unk1 == 3, "unk6" | uint16L) :: + ("char_id" | uint32L) :: + ("member_position" | uint4) :: + conditional(unk1 == 0, "player_name" | PacketHelpers.encodedWideStringAligned(1)) :: + conditional(unk1 == 0 || unk1 == 3, "zone_number" | uint16L) :: conditional(unk1 == 0 || unk1 == 4, "unk7" | uint32L) }).exmap[SquadMemberEvent] ( { - case unk1 :: unk2 :: unk3 :: unk4 :: unk5 :: unk6 :: unk7 :: HNil => - Attempt.Successful(SquadMemberEvent(unk1, unk2, unk3, unk4, unk5, unk6, unk7)) + case unk1 :: 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)) }, { - case data @ SquadMemberEvent(0, unk2, unk3, unk4, Some(unk5), Some(unk6), Some(unk7)) => - Attempt.Successful(0 :: unk2 :: unk3 :: unk4 :: Some(unk5) :: Some(unk6) :: Some(unk7) :: HNil) - case data @ SquadMemberEvent(3, unk2, unk3, unk4, None, Some(unk6), None) => - Attempt.Successful(3 :: unk2 :: unk3 :: unk4 :: None :: Some(unk6) :: None :: HNil) - case data @ SquadMemberEvent(4, unk2, unk3, unk4, None, None, Some(unk7)) => - Attempt.Successful(4 :: unk2 :: unk3 :: unk4 :: None :: None :: Some(unk7) :: HNil) - case data @ SquadMemberEvent(unk1, unk2, unk3, unk4, None, None, None) => - Attempt.Successful(unk1 :: unk2 :: unk3 :: unk4 :: None :: None :: None :: HNil) + case data @ SquadMemberEvent(0, 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) + case data @ SquadMemberEvent(3, unk2, char_id, member_position, None, Some(zone_number), None) => + Attempt.Successful(3 :: unk2 :: char_id :: member_position :: None :: Some(zone_number) :: None :: HNil) + case data @ SquadMemberEvent(4, unk2, char_id, member_position, None, None, Some(unk7)) => + Attempt.Successful(4 :: unk2 :: char_id :: member_position :: None :: None :: Some(unk7) :: HNil) + case data @ SquadMemberEvent(unk1, unk2, char_id, member_position, None, None, None) => + Attempt.Successful(unk1 :: unk2 :: char_id :: member_position :: None :: None :: None :: HNil) case data => Attempt.Failure(Err(s"SquadMemberEvent can not encode with this pattern - $data")) } diff --git a/common/src/main/scala/services/teamwork/SquadAction.scala b/common/src/main/scala/services/teamwork/SquadAction.scala new file mode 100644 index 00000000..744a4940 --- /dev/null +++ b/common/src/main/scala/services/teamwork/SquadAction.scala @@ -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) +} diff --git a/common/src/main/scala/services/teamwork/SquadResponse.scala b/common/src/main/scala/services/teamwork/SquadResponse.scala index 27ef6dcd..773b4067 100644 --- a/common/src/main/scala/services/teamwork/SquadResponse.scala +++ b/common/src/main/scala/services/teamwork/SquadResponse.scala @@ -1,7 +1,9 @@ // Copyright (c) 2019 PSForever package services.teamwork +import net.psforever.objects.teamwork.{Member, Squad} import net.psforever.packet.game._ +import net.psforever.types.SquadRequestType object SquadResponse { trait Response @@ -10,5 +12,9 @@ object SquadResponse { final case class Update(infos : Iterable[(Int, SquadInfo)]) 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 } diff --git a/common/src/main/scala/services/teamwork/SquadService.scala b/common/src/main/scala/services/teamwork/SquadService.scala index 245c3a29..070cdacc 100644 --- a/common/src/main/scala/services/teamwork/SquadService.scala +++ b/common/src/main/scala/services/teamwork/SquadService.scala @@ -3,12 +3,13 @@ package services.teamwork import akka.actor.Actor 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.types.{PlanetSideEmpire, Vector3} +import net.psforever.types.{PlanetSideEmpire, SquadRequestType, Vector3} import services.{GenericEventBus, Service} import scala.collection.concurrent.TrieMap +import scala.collection.mutable import scala.collection.mutable.ListBuffer //import scala.concurrent.duration._ @@ -22,11 +23,35 @@ class SquadService extends Actor { PlanetSideEmpire.NC -> ListBuffer.empty, PlanetSideEmpire.VS -> ListBuffer.empty ) + private val bids : mutable.LongMap[(PlanetSideGUID, Int)] = mutable.LongMap[(PlanetSideGUID, Int)]() private [this] val log = org.log4s.getLogger override def preStart = { 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 = { @@ -55,7 +80,7 @@ class SquadService extends Actor { val name = player.Name val squadOut = opt match { case Some(squad) => - if(squad.Leader.equals(name)) { + if(squad.Leader.Name.equals(name)) { squad } else { @@ -64,7 +89,7 @@ class SquadService extends Actor { case None => memberToSquad.get(name) match { - case Some(squad) if squad.Leader.equals(name) => + case Some(squad) if squad.Leader.Name.equals(name) => squad case _ => val faction = player.Faction @@ -115,7 +140,7 @@ class SquadService extends Actor { SquadEvents.unsubscribe(sender()) memberToSquad.get(name) match { case Some(squad) => - if(squad.Leader.equals(name)) { + if(squad.Leader.Name.equals(name)) { //we were the leader if(squad.Membership.count(p => p.Name.equals("")) > 1) { //other players were in the squad; publicly disband it @@ -144,190 +169,218 @@ class SquadService extends Actor { } 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 SquadServiceMessage.SquadDefinitionAction(tplayer, zone_ordinal_number, guid, _, action) => - 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) + case SquadServiceMessage(tplayer, squad_action) => squad_action match { + case SquadAction.Membership(request_type, char_id, optional_char_id, name, unk5) => request_type match { + case SquadRequestType.Accept => + bids.get(char_id) match { + case Some((squadGUID, line)) if idToSquad.get(squadGUID).nonEmpty => + //join squad + val squad = idToSquad(squadGUID) + val position = squad.Membership(line) + if(squad.Availability(line) && position.CharId == 0 && + tplayer.Certifications.intersect(position.Requirements) == position.Requirements) { + position.Name = tplayer.Name + position.CharId = char_id + position.Health = tplayer.Health + position.Armor = tplayer.Armor + position.Position = tplayer.Position + position.ZoneId = 13 + memberToSquad(tplayer.Name) = squad + sender ! SquadServiceResponse("", SquadResponse.Join( + squad, + squad.Membership.zipWithIndex.collect({ case (member, index) if member.CharId != 0 => index }).toList + )) } - else { - List(SquadInfo.Field.Capacity) - } - memberPosition.Close() - UpdateSquadList(squad, listingChanged) - UpdateSquadDetail(squad) - case Some(false) | None => ; + bids.remove(char_id) + case _ => ; } - 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(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 SquadRequestType.Leave => + if(optional_char_id.contains(char_id)) { + //we're leaving the squad on our own + val name = tplayer.Name + val squad = memberToSquad(name) + val membership = squad.Membership.zipWithIndex + val (member, index) = membership + .find { case (_member, _) => _member.Name == name } + .get + val updateList = membership.collect({ case (_member, _) if _member.CharId > 0 => (_member.CharId, index) }).toList + memberToSquad.remove(name) + member.Name = "" + member.CharId = 0 + sender ! SquadServiceResponse("", SquadResponse.Leave(squad, updateList)) } case _ => ; } - case msg => - log.info(s"Unhandled message $msg from $sender") + case SquadAction.Definition(tplayer : Player, zone_ordinal_number : Int, guid : PlanetSideGUID, line : Int, action : SquadAction) => + import net.psforever.packet.game.SquadAction._ + val squadOpt = GetParticipatingSquad(tplayer, zone_ordinal_number) + action match { + case SaveSquadDefinition() => + + case 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 = { @@ -380,7 +433,7 @@ class SquadService extends Actor { def GenSquadDetail(squad : Squad) : SquadResponse.Detail = { SquadResponse.Detail( squad.GUID, - squad.Leader, + squad.Leader.Name, squad.Task, PlanetSideZoneID(squad.ZoneId), squad.Membership.zipWithIndex.map({ case (p, index) => @@ -410,7 +463,7 @@ class SquadService extends Actor { object SquadService { def Publish(squad : Squad) : SquadInfo = { SquadInfo( - squad.Leader, + squad.Leader.Name, squad.Task, PlanetSideZoneID(squad.ZoneId), squad.Size, diff --git a/common/src/main/scala/services/teamwork/SquadServiceMessage.scala b/common/src/main/scala/services/teamwork/SquadServiceMessage.scala index 2157d44c..1e6c5559 100644 --- a/common/src/main/scala/services/teamwork/SquadServiceMessage.scala +++ b/common/src/main/scala/services/teamwork/SquadServiceMessage.scala @@ -2,12 +2,9 @@ package services.teamwork 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 { - final case class SquadDefinitionAction(player : Player, zone_ordinal_number : Int, guid : PlanetSideGUID, line : Int, action : SquadAction) - final case class RecoverSquadMembership() } diff --git a/common/src/test/scala/game/SquadDetailDefinitionUpdateMessageTest.scala b/common/src/test/scala/game/SquadDetailDefinitionUpdateMessageTest.scala index 8f04cf53..9e3fd2c1 100644 --- a/common/src/test/scala/game/SquadDetailDefinitionUpdateMessageTest.scala +++ b/common/src/test/scala/game/SquadDetailDefinitionUpdateMessageTest.scala @@ -146,7 +146,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification { guid mustEqual PlanetSideGUID(3) detail match { 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.info match { case Some(SquadPositionDetail(Some(is_closed), None, None, None, None, None)) => @@ -169,7 +169,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification { guid mustEqual PlanetSideGUID(7) detail match { 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.info match { case Some(SquadPositionDetail(None, Some(role), None, None, None, None)) => @@ -192,7 +192,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification { guid mustEqual PlanetSideGUID(1) detail match { 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.info match { case Some(SquadPositionDetail(None, Some(role), None, Some(req), None, None)) => @@ -217,7 +217,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification { guid mustEqual PlanetSideGUID(3) detail match { 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.info match { case Some(SquadPositionDetail(None, None, None, None, Some(char_id), Some(name))) => @@ -242,7 +242,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification { detail match { case SquadDetail(None, None, None, None, None, Some(task), None, None, Some(members)) => task mustEqual "\\#FF0000 The \\#ffffff Blades" - members.size mustEqual 11 + members.size mustEqual 10 // members.head.index mustEqual 9 members.head.info match { @@ -399,7 +399,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification { detail match { case SquadDetail(None, None, None, None, None, Some(task), None, None, Some(member_list)) => task mustEqual "PSForever Packet Collection" - member_list.size mustEqual 11 + member_list.size mustEqual 10 member_list.head mustEqual SquadPositionEntry(9,Some( SquadPositionDetail( Some(false), @@ -516,8 +516,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification { .Field1(0) .LeaderCharId(1221560L) .Members(List( - SquadPositionEntry(6, SquadPositionDetail().Player(0L, "")), - SquadPositionEntry(255, None) + SquadPositionEntry(6, SquadPositionDetail().Player(0L, "")) )) ) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector @@ -540,8 +539,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification { .Leader(42631712L, "Jaako") .Field3(556403L) .Members(List( - SquadPositionEntry(0, SquadPositionDetail().Player(0L, "")), - SquadPositionEntry(255, None) + SquadPositionEntry(0, SquadPositionDetail().Player(0L, "")) )) ) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector @@ -593,8 +591,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification { SquadPositionEntry(3, SquadPositionDetail().Role("The Princess")), SquadPositionEntry(2, SquadPositionDetail().Role("The Prince")), SquadPositionEntry(1, SquadPositionDetail().Role("The Queen")), - SquadPositionEntry(0, SquadPositionDetail().Role("The King")), - SquadPositionEntry(255, None) + SquadPositionEntry(0, SquadPositionDetail().Role("The King")) )) ) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector @@ -606,8 +603,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification { PlanetSideGUID(3), SquadDetail() .Members(List( - SquadPositionEntry(5, SquadPositionDetail.Closed), - SquadPositionEntry(255, None) + SquadPositionEntry(5, SquadPositionDetail.Closed) )) ) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector @@ -620,8 +616,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification { PlanetSideGUID(7), SquadDetail() .Members(List( - SquadPositionEntry(0, SquadPositionDetail().Role("Commander")), - SquadPositionEntry(255, None) + SquadPositionEntry(0, SquadPositionDetail().Role("Commander")) )) ) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector @@ -635,8 +630,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification { .Members(List( SquadPositionEntry(6, SquadPositionDetail() .Role("ADV Hacker") - .Requirements(Set(CertificationType.AdvancedHacking))), - SquadPositionEntry(255, None) + .Requirements(Set(CertificationType.AdvancedHacking))) )) ) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector @@ -648,8 +642,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification { PlanetSideGUID(3), SquadDetail() .Members(List( - SquadPositionEntry(5, SquadPositionDetail().Player(1218249L, "Duckmaster43")), - SquadPositionEntry(255, None) + SquadPositionEntry(5, SquadPositionDetail().Player(1218249L, "Duckmaster43")) )) ) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector @@ -671,8 +664,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification { SquadPositionEntry(3, SquadPositionDetail().Role("")), SquadPositionEntry(2, SquadPositionDetail().Role("")), SquadPositionEntry(1, SquadPositionDetail().Role("")), - SquadPositionEntry(0, SquadPositionDetail().Role("")), - SquadPositionEntry(255, None) + SquadPositionEntry(0, SquadPositionDetail().Role("")) )) ) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector @@ -715,7 +707,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification { PlanetSideGUID(3), SquadDetail .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 pkt mustEqual string_mixed diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 7aa34958..0499a0aa 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -52,7 +52,7 @@ import services.galaxy.{GalaxyResponse, GalaxyServiceResponse} import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse} import services.vehicle.support.TurretUpgrader 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.ExecutionContext.Implicits.global @@ -376,16 +376,65 @@ class WorldSessionActor extends Actor with MDCContextAware { SquadDetailDefinitionUpdateMessage( guid, SquadDetail() - .LeaderCharId(member_info.find(_.name == leader).get.char_id.get) + .LeaderCharId(member_info.find(_.name.contains(leader)).get.char_id.get) .LeaderName(leader) .Task(task) .ZoneId(zone) - .Members( - member_info.zipWithIndex.map { case (a, b) => SquadPositionEntry(b, a) } - ) + .Members(member_info.zipWithIndex.map { case (a, b) => SquadPositionEntry(b, a) }) .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 _ => ; } @@ -2911,42 +2960,7 @@ class WorldSessionActor extends Actor with MDCContextAware { interstellarFerryTopLevelGUID = None case _ => ; } - //SQUAD TESTING CODE - 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, "")) - ) - ) - ) - ) + squadService ! Service.Join(s"${avatar.faction}") //channel will be player.Faction } 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) => if(deadState == DeadState.Alive) { 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.Velocity = vel @@ -4850,7 +4853,29 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ SquadDefinitionActionMessage(u1, u2, action) => 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) => log.info("Ouch! " + msg)