From 640a96ae9c84f936f179427e2fb3b8205ab192ad Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 10 Jul 2019 16:12:58 -0400 Subject: [PATCH] wrote comments for SquadState, SquadMembershipRequest, and SquadMembershipResponse; messaging for players joining and leaving squads in WSA and SquadService; char_id is now a required field for Avatars --- .../scala/net/psforever/objects/Avatar.scala | 16 +- .../packet/game/SquadMemberEvent.scala | 14 ++ .../packet/game/SquadMembershipRequest.scala | 16 +- .../packet/game/SquadMembershipResponse.scala | 30 ++-- .../psforever/packet/game/SquadState.scala | 57 +++++-- .../scala/services/teamwork/SquadAction.scala | 5 +- .../services/teamwork/SquadResponse.scala | 1 + .../services/teamwork/SquadService.scala | 76 +++++++++- .../src/main/scala/WorldSessionActor.scala | 140 ++++++++++-------- 9 files changed, 237 insertions(+), 118 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/Avatar.scala b/common/src/main/scala/net/psforever/objects/Avatar.scala index 01ce2d46..ceae122b 100644 --- a/common/src/main/scala/net/psforever/objects/Avatar.scala +++ b/common/src/main/scala/net/psforever/objects/Avatar.scala @@ -10,9 +10,8 @@ import net.psforever.types._ import scala.annotation.tailrec import scala.collection.mutable -class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : CharacterGender.Value, val head : Int, val voice : CharacterVoice.Value) { - /** Character ID; a unique identifier corresponding to a database table row index */ - private var charId : Long = 0 +class Avatar(private val char_id : Long, val name : String, val faction : PlanetSideEmpire.Value, val sex : CharacterGender.Value, val head : Int, val voice : CharacterVoice.Value) { + /** char_id, Character ID; a unique identifier corresponding to a database table row index */ /** Battle Experience Points */ private var bep : Long = 0 /** Command Experience Points */ @@ -48,14 +47,7 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : private val deployables : DeployableToolbox = new DeployableToolbox - def CharId : Long = charId - - def CharId_=(id : Long) : Long = { - if(charId == 0) { //does not need to be set but can only set once - charId = id - } - CharId - } + def CharId : Long = char_id def BEP : Long = bep @@ -226,7 +218,7 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : object Avatar { def apply(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : CharacterVoice.Value) : Avatar = { - new Avatar(name, faction, sex, head, voice) + new Avatar(0L, name, faction, sex, head, voice) } def toString(avatar : Avatar) : String = s"${avatar.faction} ${avatar.name}" 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 50dec0f6..503254a5 100644 --- a/common/src/main/scala/net/psforever/packet/game/SquadMemberEvent.scala +++ b/common/src/main/scala/net/psforever/packet/game/SquadMemberEvent.scala @@ -6,6 +6,20 @@ import scodec.{Attempt, Codec, Err} import scodec.codecs._ import shapeless.{::, HNil} +object MemberEvent extends Enumeration { + type Type = Value + + val + Add, + Remove, + Unknown2, + UpdateZone, + Unknown4 + = Value + + implicit val codec = PacketHelpers.createEnumerationCodec(this, uint(3)) +} + final case class SquadMemberEvent(unk1 : Int, unk2 : Int, char_id : Long, diff --git a/common/src/main/scala/net/psforever/packet/game/SquadMembershipRequest.scala b/common/src/main/scala/net/psforever/packet/game/SquadMembershipRequest.scala index 27cf2b79..c5bd3550 100644 --- a/common/src/main/scala/net/psforever/packet/game/SquadMembershipRequest.scala +++ b/common/src/main/scala/net/psforever/packet/game/SquadMembershipRequest.scala @@ -7,15 +7,17 @@ import scodec.Codec import scodec.codecs._ /** - * SquadMembershipRequest is a Client->Server message for manipulating squad and platoon members. - * @param request_type na - * @param unk2 na - * @param unk3 na - * @param player_name na - * @param unk5 na + * Dispatched by the client as manipulation protocol for squad and platoon members. + * Answerable by a `SquadMembershipResponse` packet. + * @param request_type the purpose of the request + * @param char_id a squad member unique identifier; + * usually, the player being addresses by thie packet + * @param unk3 na + * @param player_name name of the player being affected, if applicable + * @param unk5 na */ final case class SquadMembershipRequest(request_type : SquadRequestType.Value, - unk2 : Long, + char_id : Long, unk3 : Option[Long], player_name : String, unk5 : Option[Option[String]]) diff --git a/common/src/main/scala/net/psforever/packet/game/SquadMembershipResponse.scala b/common/src/main/scala/net/psforever/packet/game/SquadMembershipResponse.scala index 0d01a938..ee1746dd 100644 --- a/common/src/main/scala/net/psforever/packet/game/SquadMembershipResponse.scala +++ b/common/src/main/scala/net/psforever/packet/game/SquadMembershipResponse.scala @@ -7,31 +7,29 @@ import scodec.Codec import scodec.codecs._ /** - * na - * @param request_type the type of request being answered + * Dispatched by the server as manipulation protocol for squad and platoon members. + * Prompted by and answers for a `SquadMembershipRequest` packet. + * @param request_type the purpose of the request * @param unk1 na * @param unk2 na - * @param unk3 na - * @param unk4 na - * @param player_name the player being affected, if applicable + * @param char_id a squad member unique identifier; + * usually, the player being addresses by thie packet + * @param other_id another squad member's unique identifier; + * may be the same as `char_id` + * @param player_name name of the player being affected, if applicable * @param unk5 na - * @param unk6 na + * @param unk6 na; + * the internal field, the `Option[String]`, never seems to be set */ final case class SquadMembershipResponse(request_type : SquadRequestType.Value, unk1 : Int, unk2 : Int, - unk3 : Long, - unk4 : Option[Long], + char_id : Long, + other_id : Option[Long], player_name : String, unk5 : Boolean, unk6 : Option[Option[String]]) extends PlanetSideGamePacket { - /* - if(response_type != 6 && response_type != 12) - assert(unk5.isDefined, "unk5 field required") - else - assert(!unk5.isDefined, "unk5 defined but unk1 invalid value") - */ type Packet = SquadMembershipResponse def opcode = GamePacketOpcode.SquadMembershipResponse def encode = SquadMembershipResponse.encode(this) @@ -42,8 +40,8 @@ object SquadMembershipResponse extends Marshallable[SquadMembershipResponse] { "request_type" | SquadRequestType.codec >>:~ { d => ("unk1" | uint(5)) :: ("unk2" | uint2) :: - ("unk3" | uint32L) :: - ("unk4" | conditional(d != SquadRequestType.Promote && d != SquadRequestType.PlatoonLeave, uint32L)) :: + ("char_id" | uint32L) :: + ("other_id" | conditional(d != SquadRequestType.Promote && d != SquadRequestType.PlatoonLeave, uint32L)) :: ("player_name" | PacketHelpers.encodedWideStringAligned(5)) :: ("unk5" | bool) :: conditional(d != SquadRequestType.Invite, optional(bool, "unk6" | PacketHelpers.encodedWideStringAligned(6))) diff --git a/common/src/main/scala/net/psforever/packet/game/SquadState.scala b/common/src/main/scala/net/psforever/packet/game/SquadState.scala index ae745a74..dc092749 100644 --- a/common/src/main/scala/net/psforever/packet/game/SquadState.scala +++ b/common/src/main/scala/net/psforever/packet/game/SquadState.scala @@ -7,9 +7,27 @@ import scodec.{Attempt, Codec, Err} import scodec.codecs._ import shapeless.{::, HNil} +/** + * Information about a specific squad member. + * @param char_id the character's unique identifier + * @param health the character's health value percentage, divided into 64 units + * @param armor the character's armor value percentage, divided into 64 units + * @param pos the world coordinates of the character + * @param unk4 na; + * usually, 2 + * @param unk5 na; + * usually, 2 + * @param unk6 na; + * usually, false + * @param unk7 na + * @param unk8 na; + * if defined, will be defined with unk9 + * @param unk9 na; + * if defined, will be defined with unk8 + */ final case class SquadStateInfo(char_id : Long, - unk2 : Int, - unk3 : Int, + health : Int, + armor : Int, pos : Vector3, unk4 : Int, unk5 : Int, @@ -18,12 +36,19 @@ final case class SquadStateInfo(char_id : Long, unk8 : Option[Int], unk9 : Option[Boolean]) -//7704001646c7a02810050a1a2bc97842280000 -// SquadState(PlanetSideGUID(4),List(SquadStateInfo(1684830722,64,64,Vector3(3152.1562,3045.0781,35.515625),2,2,false,0,None,None))) -//770700342a28c028100420d60e9df8c2800000eab58a028100ce514b655ddc341400286d9130000021eb951539f4c4050800 -// SquadState(PlanetSideGUID(7),List(SquadStateInfo(1117948930,64,64,Vector3(2822.125,3919.0234,43.546875),0,0,false,0,None,None), SquadStateInfo(3937765890,64,64,Vector3(2856.3984,3882.3516,53.859375),0,0,false,416,None,None), SquadStateInfo(2262373120,0,0,Vector3(2909.0547,3740.539,67.296875),0,1,false,132,None,None))) -//7704005dd9ccf01810132fdf9f9ef5c4a8000084de7a022c00e5898c8d5e4c429b004ed01d50181016395a4c364e08280001b901a070bd0140805308d59f90641f40000c001db11e280a00088d19e3a190f0ca6c0100 -// SquadState(PlanetSideGUID(4),List(SquadStateInfo(3718041345,64,64,Vector3(3966.5938,6095.8047,75.359375),2,2,false,0,None,None), SquadStateInfo(2229172738,22,0,Vector3(3268.4453,3690.3906,66.296875),2,2,false,728,None,None), SquadStateInfo(3976320257,64,64,Vector3(3530.6875,4635.1484,128.875),2,2,false,0,Some(441),Some(true)), SquadStateInfo(1088518658,64,64,Vector3(3336.3203,4601.3438,60.78125),2,2,false,0,Some(896),Some(true)), SquadStateInfo(1816627714,64,0,Vector3(5027.0625,4931.7734,48.234375),2,2,false,728,None,None))) +/** + * Dispatched by the server to update a squad member's representative icons on the continental maps and the interstellar map.
+ *
+ * This packet must be preceded by the correct protocol + * to assign any character who is defined by `char_id` in `info_list` + * as a member of this client's player's assigned squad by means of associating that said `char_id`. + * The said preceding protocol also assigns the player's current zone (continent) and their ordinal position in the squad. + * @see `SquadMemberEvent` + * @param guid the squad's unique identifier; + * must be consistent per packet on a given client; + * does not have to be the global uid of the squad as according to the server + * @param info_list information about the members in this squad who will be updated + */ final case class SquadState(guid : PlanetSideGUID, info_list : List[SquadStateInfo]) extends PlanetSideGamePacket { @@ -43,8 +68,8 @@ object SquadStateInfo { object SquadState extends Marshallable[SquadState] { private val info_codec : Codec[SquadStateInfo] = ( ("char_id" | uint32L) :: - ("unk2" | uint(7)) :: - ("unk3" | uint(7)) :: + ("health" | uint(7)) :: + ("armor" | uint(7)) :: ("pos" | Vector3.codec_pos) :: ("unk4" | uint2) :: ("unk5" | uint2) :: @@ -56,14 +81,14 @@ object SquadState extends Marshallable[SquadState] { }) ).exmap[SquadStateInfo] ( { - case char_id :: u2 :: u3 :: pos :: u4 :: u5 :: u6 :: u7 :: _ :: u8 :: u9 :: HNil => - Attempt.Successful(SquadStateInfo(char_id, u2, u3, pos, u4, u5, u6, u7, u8, u9)) + case char_id :: health :: armor :: pos :: u4 :: u5 :: u6 :: u7 :: _ :: u8 :: u9 :: HNil => + Attempt.Successful(SquadStateInfo(char_id, health, armor, pos, u4, u5, u6, u7, u8, u9)) }, { - case SquadStateInfo(char_id, u2, u3, pos, u4, u5, u6, u7, Some(u8), Some(u9)) => - Attempt.Successful(char_id :: u2 :: u3 :: pos :: u4 :: u5 :: u6 :: u7 :: true :: Some(u8) :: Some(u9) :: HNil) - case SquadStateInfo(char_id, u2, u3, pos, u4, u5, u6, u7, None, None) => - Attempt.Successful(char_id :: u2 :: u3 :: pos :: u4 :: u5 :: u6 :: u7 :: false :: None :: None :: HNil) + case SquadStateInfo(char_id, health, armor, pos, u4, u5, u6, u7, Some(u8), Some(u9)) => + Attempt.Successful(char_id :: health :: armor :: pos :: u4 :: u5 :: u6 :: u7 :: true :: Some(u8) :: Some(u9) :: HNil) + case SquadStateInfo(char_id, health, armor, pos, u4, u5, u6, u7, None, None) => + Attempt.Successful(char_id :: health :: armor :: pos :: u4 :: u5 :: u6 :: u7 :: false :: None :: None :: HNil) case data @ (SquadStateInfo(_, _, _, _, _, _, _, _, Some(_), None) | SquadStateInfo(_, _, _, _, _, _, _, _, None, Some(_))) => Attempt.Failure(Err(s"SquadStateInfo requires both unk8 and unk9 to be either defined or undefined at the same time - $data")) } diff --git a/common/src/main/scala/services/teamwork/SquadAction.scala b/common/src/main/scala/services/teamwork/SquadAction.scala index 744a4940..1677cf75 100644 --- a/common/src/main/scala/services/teamwork/SquadAction.scala +++ b/common/src/main/scala/services/teamwork/SquadAction.scala @@ -3,11 +3,12 @@ package services.teamwork import net.psforever.objects.Player import net.psforever.packet.game._ -import net.psforever.types.SquadRequestType +import net.psforever.types.{SquadRequestType, Vector3} object SquadAction { trait Action + final case class Definition(player : Player, zone_ordinal_number : Int, guid : PlanetSideGUID, line : Int, action : SquadAction) extends Action final case class Membership(request_type : SquadRequestType.Value, unk2 : Long, unk3 : Option[Long], player_name : String, unk5 : Option[Option[String]]) extends Action - final case class Definition(player : Player, zone_ordinal_number : Int, guid : PlanetSideGUID, line : Int, action : SquadAction) + final case class Update(char_id : Long, health : Int, max_health : Int, armor : Int, max_armor : Int, pos : Vector3, zone_number : Int) extends Action } diff --git a/common/src/main/scala/services/teamwork/SquadResponse.scala b/common/src/main/scala/services/teamwork/SquadResponse.scala index 773b4067..e1ab03cb 100644 --- a/common/src/main/scala/services/teamwork/SquadResponse.scala +++ b/common/src/main/scala/services/teamwork/SquadResponse.scala @@ -15,6 +15,7 @@ object SquadResponse { 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 UpdateMembers(squad : Squad, update_info : List[SquadAction.Update]) 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 070cdacc..9aa5954b 100644 --- a/common/src/main/scala/services/teamwork/SquadService.scala +++ b/common/src/main/scala/services/teamwork/SquadService.scala @@ -3,6 +3,7 @@ package services.teamwork import akka.actor.Actor import net.psforever.objects.Player +import net.psforever.objects.definition.converter.StatConverter import net.psforever.objects.teamwork.Squad import net.psforever.packet.game._ import net.psforever.types.{PlanetSideEmpire, SquadRequestType, Vector3} @@ -36,18 +37,26 @@ class SquadService extends Actor { testSquad.Membership(0).Name = "Wizkid45" testSquad.Membership(0).CharId = 30910985L testSquad.Membership(0).ZoneId = 5 + testSquad.Membership(0).Health = 64 + testSquad.Membership(0).Armor = 34 testSquad.Membership(0).Position = Vector3(5526.5234f, 3818.7344f, 54.59375f) testSquad.Membership(1).Name = "xoBLADEox" testSquad.Membership(1).CharId = 42781919L testSquad.Membership(1).ZoneId = 5 + testSquad.Membership(1).Health = 54 + testSquad.Membership(1).Armor = 44 testSquad.Membership(1).Position = Vector3(4673.5312f, 2604.8047f, 40.015625f) testSquad.Membership(3).Name = "cabal0428" testSquad.Membership(3).CharId = 353380L testSquad.Membership(3).ZoneId = 5 + testSquad.Membership(3).Health = 44 + testSquad.Membership(3).Armor = 54 testSquad.Membership(3).Position = Vector3(4727.492f, 2613.5312f, 51.390625f) testSquad.Membership(4).Name = "xSkiku" testSquad.Membership(4).CharId = 41588340L testSquad.Membership(4).ZoneId = 5 + testSquad.Membership(4).Health = 34 + testSquad.Membership(4).Armor = 64 testSquad.Membership(4).Position = Vector3(3675.0f, 4789.8047f, 63.21875f) idToSquad(PlanetSideGUID(3)) = testSquad testSquad.Listed = true @@ -174,7 +183,7 @@ class SquadService extends Actor { case Service.Leave(None) | Service.LeaveAll() => ; case SquadServiceMessage(tplayer, squad_action) => squad_action match { - case SquadAction.Membership(request_type, char_id, optional_char_id, name, unk5) => request_type match { + case SquadAction.Membership(request_type, char_id, optional_char_id, _, _) => request_type match { case SquadRequestType.Accept => bids.get(char_id) match { case Some((squadGUID, line)) if idToSquad.get(squadGUID).nonEmpty => @@ -190,10 +199,18 @@ class SquadService extends Actor { position.Position = tplayer.Position position.ZoneId = 13 memberToSquad(tplayer.Name) = squad + //joining the squad sender ! SquadServiceResponse("", SquadResponse.Join( squad, squad.Membership.zipWithIndex.collect({ case (member, index) if member.CharId != 0 => index }).toList )) + //other squad members see us joining the squad + val updatedIndex = List(line) + squad.Membership + .collect({ case member if member.CharId != 0 && member.CharId != char_id => member.Name }) + .foreach { name => + SquadEvents.publish( SquadServiceResponse(s"$name/Squad", SquadResponse.Join(squad, updatedIndex)) ) + } } bids.remove(char_id) case _ => ; @@ -208,17 +225,66 @@ class SquadService extends Actor { 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 + val updateList = membership.collect({ case (_member, _index) if _member.CharId > 0 => (_member.CharId, _index) }).toList memberToSquad.remove(name) member.Name = "" member.CharId = 0 + //leaving the squad completely sender ! SquadServiceResponse("", SquadResponse.Leave(squad, updateList)) + //other squad members see us leaving the squad + val leavingMember = List((char_id, index)) + membership + .collect({ case (_member, _) if _member.CharId > 0 => _member.Name }) + .foreach { name => + SquadEvents.publish( SquadServiceResponse(s"$name/Squad", SquadResponse.Leave(squad, leavingMember)) ) + } } case _ => ; } - case SquadAction.Definition(tplayer : Player, zone_ordinal_number : Int, guid : PlanetSideGUID, line : Int, action : SquadAction) => + case SquadAction.Update(char_id, health, max_health, armor, max_armor, pos, zone_number) => + memberToSquad.get(tplayer.Name) match { + case Some(squad) => + squad.Membership.find(_.CharId == char_id) match { + case Some(member) => + val newHealth = StatConverter.Health(health, max_health, min=1, max=64) + val newArmor = StatConverter.Health(armor, max_armor, min=1, max=64) + member.Health = newHealth + member.Armor = newArmor + member.Position = pos + member.ZoneId = zone_number + sender ! SquadServiceResponse("", SquadResponse.UpdateMembers( + squad, + squad.Membership + .filterNot { _.CharId == 0 } + .map { member => SquadAction.Update(member.CharId, member.Health, 0, member.Armor, 0, member.Position, member.ZoneId) } + .toList + )) + case _ => ; + } +// val (self, others) = squad.Membership.partition(_.CharId == char_id) +// self match { +// case Array(member) => +// val newHealth = StatConverter.Health(health, max_health, min=1, max=64) +// val newArmor = StatConverter.Health(armor, max_armor, min=1, max=64) +// member.Health = newHealth +// member.Armor = newArmor +// member.Position = pos +// member.ZoneId = zone_number +// sender ! SquadServiceResponse("", SquadResponse.UpdateMembers( +// squad, +// others +// .map { member => SquadAction.Update(member.CharId, member.Health, 0, member.Armor, 0, member.Position, member.ZoneId) } +// .toList +// )) +// case _ => ; +// } + + case None => ; + } + + case SquadAction.Definition(tplayer : Player, zone_ordinal_number : Int, guid : PlanetSideGUID, _ : Int, action : SquadAction) => import net.psforever.packet.game.SquadAction._ val squadOpt = GetParticipatingSquad(tplayer, zone_ordinal_number) action match { @@ -320,11 +386,11 @@ class SquadService extends Actor { case SelectRoleForYourself(position) => //TODO need to ask permission from the squad leader, unless our character is the squad leader or already currently in the squad - val name = tplayer.Name + //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) => + case Some(_) => //not a member of the requesting squad; do nothing case None => //not a member of any squad; consider request of joining the target squad diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 0499a0aa..6064e729 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -54,6 +54,7 @@ import services.vehicle.support.TurretUpgrader import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse} import services.teamwork.{SquadAction => SquadServiceAction, SquadServiceMessage, SquadServiceResponse, SquadResponse, SquadService} +import scala.collection.mutable.LongMap import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global import scala.annotation.tailrec @@ -115,6 +116,7 @@ class WorldSessionActor extends Actor with MDCContextAware { * no harm should come from leaving the field set to an old unique identifier value after the transfer period */ var interstellarFerryTopLevelGUID : Option[PlanetSideGUID] = None + val squadUI : LongMap[SquadUIElement] = new LongMap[SquadUIElement]() var amsSpawnPoints : List[SpawnPoint] = Nil var clientKeepAlive : Cancellable = DefaultCancellable.obj @@ -394,47 +396,92 @@ class WorldSessionActor extends Actor with MDCContextAware { 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))) + case Some((ourMember, ourIndex)) => + //we are joining the squad + sendResponse(SquadMembershipResponse(SquadRequestType.Accept, 0, 0, player.CharId, Some(leader.CharId), player.Name, true, Some(None))) + //load each member's entry (our own too) + membershipPositions.foreach { case(member, index) => + sendResponse(SquadMemberEvent(0, id, member.CharId, index, Some(member.Name), Some(member.ZoneId), Some(0))) + squadUI(member.CharId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position) + } + //initialization + sendResponse(SquadMemberEvent(0, id, ourMember.CharId, ourIndex, Some(ourMember.Name), Some(ourMember.ZoneId), Some(0))) //repeat of our entry sendResponse(PlanetsideAttributeMessage(player.GUID, 31, id)) //associate with squad? - sendResponse(PlanetsideAttributeMessage(player.GUID, 32, index)) //associate with member position in squad? - case _ => ; + sendResponse(PlanetsideAttributeMessage(player.GUID, 32, ourIndex)) //associate with member position in squad? + //a finalization? what does this do? + sendResponse(SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(18, hex"00".toBitVector.take(6)))) + case _ => + //other player is joining our squad + //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))) + squadUI(member.CharId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position) + } } - //send first update for map icons + //send an initial dummy update for map icon(s) sendResponse(SquadState(PlanetSideGUID(id), membershipPositions .filterNot { case (member, _) => member.CharId == avatar.CharId } - .map{ case (member, _) => SquadStateInfo(member.CharId, 64,64, member.Position, 2,2, false, 429, None,None) } + .map{ case (member, _) => SquadStateInfo(member.CharId, member.Health, member.Armor, member.Position, 2,2, false, 429, None,None) } .toList )) - //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)) + case Some((ourMember, ourIndex)) => + //we are leaving the squad + sendResponse(SquadMembershipResponse(SquadRequestType.Leave, 0,1, avatar.CharId, Some(avatar.CharId), avatar.name, true, Some(None))) + //remove each member's entry (our own too) + positionsToUpdate.foreach { case(member, index) => + sendResponse(SquadMemberEvent(1, id, member, index, None, None, None)) + squadUI.remove(member) + } + //uninitialize + sendResponse(SquadMemberEvent(1, id, ourMember, ourIndex, None, None, None)) //repeat of our entry sendResponse(PlanetsideAttributeMessage(player.GUID, 31, 0)) //disassociate with squad? sendResponse(PlanetsideAttributeMessage(player.GUID, 32, 0)) //disassociate with member position in squad? - sendResponse(PlanetsideAttributeMessage(player.GUID, 34, 4294967295L)) //unknown - case _ => ; + sendResponse(PlanetsideAttributeMessage(player.GUID, 34, 4294967295L)) //unknown, perhaps unrelated? + //a finalization? what does this do? + sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18, hex"00".toBitVector.take(6)))) + case _ => + //remove each member's entry + positionsToUpdate.foreach { case(member, index) => + sendResponse(SquadMemberEvent(1, id, member, index, None, None, None)) + squadUI.remove(member) + } } - //a finalization? what does this do? - sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18, hex"00".toBitVector.take(6)))) + + case SquadResponse.UpdateMembers(squad, positions) => + import services.teamwork.SquadAction.{Update => SAUpdate} + val pairedEntries = positions.collect { + case entry if squadUI.contains(entry.char_id) => + (entry, squadUI(entry.char_id)) + } + //prune entries + val updatedEntries = pairedEntries + .collect({ + case (entry, element) if entry.zone_number != element.zone => + //zone gets updated for these entries + sendResponse(SquadMemberEvent(3, 11, entry.char_id, element.index, None, Some(entry.zone_number), None)) + squadUI(entry.char_id) = SquadUIElement(element.name, element.index, entry.zone_number, entry.health, entry.armor, entry.pos) + entry + case (entry, element) if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position => + //other elements that need to be updated + squadUI(entry.char_id) = SquadUIElement(element.name, element.index, entry.zone_number, entry.health, entry.armor, entry.pos) + entry + }) + .filterNot(_.char_id == avatar.CharId) //we want to update our backend, but not our frontend + if(updatedEntries.nonEmpty) { + sendResponse( + SquadState( + PlanetSideGUID(11), + updatedEntries.map { entry => SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos, 2,2, false, 429, None,None)} + ) + ) + } + case _ => ; } @@ -586,7 +633,6 @@ class WorldSessionActor extends Actor with MDCContextAware { player.Stamina = stamina player.Armor = armor } - avatar.CharId = 41605313L sendResponse(CharacterInfoMessage(15, PlanetSideZoneID(10000), avatar.CharId, player.GUID, false, 6404428)) RemoveCharacterSelectScreenGUID(player) sendResponse(CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0)) @@ -2986,7 +3032,7 @@ class WorldSessionActor extends Actor with MDCContextAware { import net.psforever.objects.GlobalDefinitions._ import net.psforever.types.CertificationType._ val faction = PlanetSideEmpire.VS - val avatar = Avatar(s"TestCharacter$sessionId", faction, CharacterGender.Female, 41, CharacterVoice.Voice1) + val avatar = new Avatar(41605313L+sessionId, s"TestCharacter$sessionId", faction, CharacterGender.Female, 41, CharacterVoice.Voice1) avatar.Certifications += StandardAssault avatar.Certifications += MediumAssault avatar.Certifications += StandardExoSuit @@ -3373,7 +3419,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) => if(deadState == DeadState.Alive) { if(!player.Crouching && is_crouching) { //SQUAD TESTING CODE - //... + sendResponse(SquadMemberEvent(3, 11, 353380L, 3, None, Some(13), None)) } player.Position = pos player.Velocity = vel @@ -3416,6 +3462,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case None => false } avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, msg, spectator, wepInHand)) + //squadService ! SquadServiceMessage(tplayer, SquadAction.Update(tplayer.CharId, tplayer.Health, tplayer.MaxHealth, tplayer.Armor, tplayer.MaxArmor, pos, zone.Number)) } case msg @ ChildObjectStateMessage(object_guid, pitch, yaw) => @@ -4858,24 +4905,6 @@ class WorldSessionActor extends Actor with MDCContextAware { 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) @@ -7621,17 +7650,6 @@ class WorldSessionActor extends Actor with MDCContextAware { * @return a `DestroyDisplayMessage` packet that is properly formatted */ def DestroyDisplayMessage(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int = 121) : DestroyDisplayMessage = { - val killerCharId : Long = killer.CharId - val victimCharId : Long = { - //TODO this block of code is only necessary for this particular dev build; use "victim.CharId" normallyLoado - val id = victim.CharId - if(killerCharId == id) { - Int.MaxValue - id + 1 - } - else { - id - } - } val killer_seated = killer match { case obj : PlayerSource => obj.Seated case _ => false @@ -7641,9 +7659,9 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ => false } new DestroyDisplayMessage( - killer.Name, killerCharId, killer.Faction, killer_seated, + killer.Name, killer.CharId, killer.Faction, killer_seated, unk, method, - victim.Name, victimCharId, victim.Faction, victim_seated + victim.Name, victim.CharId, victim.Faction, victim_seated ) } @@ -9197,6 +9215,8 @@ object WorldSessionActor { completeAction : () => Unit, tickAction : Option[() => Unit] = None) + protected final case class SquadUIElement(name : String, index : Int, zone : Int, health : Int, armor : Int, position : Vector3) + private final case class NtuCharging(tplayer: Player, vehicle: Vehicle) private final case class NtuDischarging(tplayer: Player, vehicle: Vehicle, silo_guid: PlanetSideGUID)