added a refactored squad joining entrypoint; initial work and test on SquadWaypointEvent packet; initial work on SquadWaypointRequest packet; squad waypoints up to #5 (squad experience) should be working

This commit is contained in:
FateJH 2019-08-19 19:20:42 -04:00
parent 56d8748e99
commit 60d65e22d3
8 changed files with 343 additions and 147 deletions

View file

@ -472,7 +472,7 @@ object GamePacketOpcode extends Enumeration {
case 0x80 => noDecoder(GenericObjectAction2Message)
case 0x81 => game.DestroyDisplayMessage.decode
case 0x82 => noDecoder(TriggerBotAction)
case 0x83 => noDecoder(SquadWaypointRequest)
case 0x83 => game.SquadWaypointRequest.decode
case 0x84 => game.SquadWaypointEvent.decode
case 0x85 => noDecoder(OffshoreVehicleMessage)
case 0x86 => game.ObjectDeployedMessage.decode

View file

@ -7,16 +7,16 @@ import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
final case class WaypointEvent(unk1 : Int,
final case class WaypointEvent(zone_number : Int,
pos : Vector3,
unk2 : Int)
unk : Int)
final case class SquadWaypointEvent(unk1 : Int,
unk2 : Int,
unk3 : Long,
unk4 : Int,
final case class SquadWaypointEvent(event_type : WaypointEventAction.Value,
unk : Int,
char_id : Long,
waypoint_type : Int,
unk5 : Option[Long],
unk6 : Option[WaypointEvent])
waypoint_info : Option[WaypointEvent])
extends PlanetSideGamePacket {
type Packet = SquadWaypointEvent
def opcode = GamePacketOpcode.SquadWaypointEvent
@ -24,55 +24,55 @@ final case class SquadWaypointEvent(unk1 : Int,
}
object SquadWaypointEvent extends Marshallable[SquadWaypointEvent] {
def apply(unk1 : Int, unk2 : Int, unk3 : Long, unk4 : Int, unk_a : Long) : SquadWaypointEvent =
SquadWaypointEvent(unk1, unk2, unk3, unk4, Some(unk_a), None)
def Add(unk : Int, char_id : Long, waypoint_type : Int, waypoint : WaypointEvent) : SquadWaypointEvent =
SquadWaypointEvent(WaypointEventAction.Add, unk, char_id, waypoint_type, None, Some(waypoint))
def apply(unk1 : Int, unk2 : Int, unk3 : Long, unk4 : Int, unk_a : Int, pos : Vector3, unk_b : Int) : SquadWaypointEvent =
SquadWaypointEvent(unk1, unk2, unk3, unk4, None, Some(WaypointEvent(unk_a, pos, unk_b)))
def Unknown1(unk : Int, char_id : Long, waypoint_type : Int, unk_a : Long) : SquadWaypointEvent =
SquadWaypointEvent(WaypointEventAction.Unknown1, unk, char_id, waypoint_type, Some(unk_a), None)
def apply(unk1 : Int, unk2 : Int, unk3 : Long, unk4 : Int) : SquadWaypointEvent =
SquadWaypointEvent(unk1, unk2, unk3, unk4, None, None)
def Remove(unk : Int, char_id : Long, waypoint_type : Int) : SquadWaypointEvent =
SquadWaypointEvent(WaypointEventAction.Remove, unk, char_id, waypoint_type, None, None)
private val waypoint_codec : Codec[WaypointEvent] = (
("unk1" | uint16L) ::
("zone_number" | uint16L) ::
("pos" | Vector3.codec_pos) ::
("unk2" | uint(3))
("unk" | uint(3))
).as[WaypointEvent]
implicit val codec : Codec[SquadWaypointEvent] = (
("unk1" | uint2) >>:~ { unk1 =>
("unk2" | uint16L) ::
("unk3" | uint32L) ::
("unk4" | uint8L) ::
("unk5" | conditional(unk1 == 1, uint32L)) ::
("unk6" | conditional(unk1 == 0, waypoint_codec))
("event_type" | WaypointEventAction.codec) >>:~ { event_type =>
("unk" | uint16L) ::
("char_id" | uint32L) ::
("waypoint_type" | uint8L) ::
("unk5" | conditional(event_type == WaypointEventAction.Unknown1, uint32L)) ::
("waypoint_info" | conditional(event_type == WaypointEventAction.Add, waypoint_codec))
}
).exmap[SquadWaypointEvent] (
{
case 0 :: a :: b :: c :: None :: Some(d) :: HNil =>
Attempt.Successful(SquadWaypointEvent(0, a, b, c, None, Some(d)))
case WaypointEventAction.Add :: a :: char_id :: waypoint_type :: None :: Some(waypoint) :: HNil =>
Attempt.Successful(SquadWaypointEvent(WaypointEventAction.Add, a, char_id, waypoint_type, None, Some(waypoint)))
case 1 :: a :: b :: c :: Some(d) :: None :: HNil =>
Attempt.Successful(SquadWaypointEvent(1, a, b, c, Some(d), None))
case WaypointEventAction.Unknown1 :: a :: char_id :: waypoint_type :: Some(d) :: None :: HNil =>
Attempt.Successful(SquadWaypointEvent(WaypointEventAction.Unknown1, a, char_id, waypoint_type, Some(d), None))
case a :: b :: c :: d :: None :: None :: HNil =>
Attempt.Successful(SquadWaypointEvent(a, b, c, d, None, None))
case event_type :: b :: char_id :: waypoint_type :: None :: None :: HNil =>
Attempt.Successful(SquadWaypointEvent(event_type, b, char_id, waypoint_type, None, None))
case n :: _ :: _ :: _ :: _ :: _ :: HNil =>
Attempt.Failure(Err(s"unexpected format for unk1 - $n"))
case data =>
Attempt.Failure(Err(s"unexpected format for $data"))
},
{
case SquadWaypointEvent(0, a, b, c, None, Some(d)) =>
Attempt.Successful(0 :: a :: b :: c :: None :: Some(d) :: HNil)
case SquadWaypointEvent(WaypointEventAction.Add, a, char_id, waypoint_type, None, Some(waypoint)) =>
Attempt.Successful(WaypointEventAction.Add :: a :: char_id :: waypoint_type :: None :: Some(waypoint) :: HNil)
case SquadWaypointEvent(1, a, b, c, Some(d), None) =>
Attempt.Successful(1 :: a :: b :: c :: Some(d) :: None :: HNil)
case SquadWaypointEvent(WaypointEventAction.Unknown1, a, char_id, waypoint_type, Some(d), None) =>
Attempt.Successful(WaypointEventAction.Unknown1 :: a :: char_id :: waypoint_type :: Some(d) :: None :: HNil)
case SquadWaypointEvent(a, b, c, d, None, None) =>
Attempt.Successful(a :: b :: c :: d :: None :: None :: HNil)
case SquadWaypointEvent(event_type, b, char_id, waypoint_type, None, None) =>
Attempt.Successful(event_type :: b :: char_id :: waypoint_type :: None :: None :: HNil)
case SquadWaypointEvent(n, _, _, _, _, _) =>
Attempt.Failure(Err(s"unexpected format for unk1 - $n"))
case data =>
Attempt.Failure(Err(s"unexpected format for $data"))
}
)
}

View file

@ -0,0 +1,115 @@
// Copyright (c) 2019 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.Vector3
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* Actions that can be requested of the specific waypoint.
*/
object WaypointEventAction extends Enumeration {
type Type = Value
val
Add,
Unknown1,
Remove,
Unknown3 //unconfirmed
= Value
implicit val codec : Codec[WaypointEventAction.Value] = PacketHelpers.createEnumerationCodec(enum = this, uint2)
}
/**
* na
* @param zone_number the zone
* @param pos the continental map coordinate location of the waypoint;
* the z-coordinate is almost always 0.0
*/
final case class WaypointInfo(zone_number : Int,
pos : Vector3)
/**
* na
* @param request_type the action to be performed
* @param char_id the unique id of player setting the waypoint
* @param waypoint_type the waypoint being updated;
* 0-3 for the standard squad waypoints numbered "1-4";
* 4 for the squad leader experience waypoint;
* cycles through 0-3 continuously
*
* @param unk4 na
* @param waypoint_info essential data about the waypoint
*/
final case class SquadWaypointRequest(request_type : WaypointEventAction.Value,
char_id : Long,
waypoint_type : Int,
unk4 : Option[Long],
waypoint_info : Option[WaypointInfo])
extends PlanetSideGamePacket {
type Packet = SquadWaypointRequest
def opcode = GamePacketOpcode.SquadWaypointRequest
def encode = SquadWaypointRequest.encode(this)
}
object SquadWaypointRequest extends Marshallable[SquadWaypointRequest] {
def Add(char_id : Long, waypoint_type : Int, waypoint : WaypointInfo) : SquadWaypointRequest =
SquadWaypointRequest(WaypointEventAction.Add, char_id, waypoint_type, None, Some(waypoint))
def Unknown1(char_id : Long, waypoint_type : Int, unk_a : Long) : SquadWaypointRequest =
SquadWaypointRequest(WaypointEventAction.Unknown1, char_id, waypoint_type, Some(unk_a), None)
def Remove(char_id : Long, waypoint_type : Int) : SquadWaypointRequest =
SquadWaypointRequest(WaypointEventAction.Remove, char_id, waypoint_type, None, None)
private val waypoint_codec : Codec[WaypointInfo] = (
("zone_number" | uint16L) ::
("pos" | Vector3.codec_pos)
).xmap[WaypointInfo] (
{
case zone_number :: pos :: HNil => WaypointInfo(zone_number, pos)
},
{
case WaypointInfo(zone_number, pos) => zone_number :: pos.xy :: HNil
}
)
implicit val codec : Codec[SquadWaypointRequest] = (
("request_type" | WaypointEventAction.codec) >>:~ { request_type =>
("char_id" | uint32L) ::
("waypoint_type" | uint8L) ::
("unk4" | conditional(request_type == WaypointEventAction.Unknown1, uint32L)) ::
("waypoint" | conditional(request_type == WaypointEventAction.Add, waypoint_codec))
}
).exmap[SquadWaypointRequest] (
{
case WaypointEventAction.Add :: char_id :: waypoint_type :: None :: Some(waypoint) :: HNil =>
Attempt.Successful(SquadWaypointRequest(WaypointEventAction.Add, char_id, waypoint_type, None, Some(waypoint)))
case WaypointEventAction.Unknown1 :: char_id :: waypoint_type :: Some(d) :: None :: HNil =>
Attempt.Successful(SquadWaypointRequest(WaypointEventAction.Unknown1, char_id, waypoint_type, Some(d), None))
case request_type :: char_id :: waypoint_type :: None :: None :: HNil =>
Attempt.Successful(SquadWaypointRequest(request_type, char_id, waypoint_type, None, None))
case data =>
Attempt.Failure(Err(s"unexpected format while decoding - $data"))
},
{
case SquadWaypointRequest(WaypointEventAction.Add, char_id, waypoint_type, None, Some(waypoint)) =>
Attempt.Successful(WaypointEventAction.Add :: char_id :: waypoint_type :: None :: Some(waypoint) :: HNil)
case SquadWaypointRequest(WaypointEventAction.Unknown1, char_id, waypoint_type, Some(d), None) =>
Attempt.Successful(WaypointEventAction.Unknown1 :: char_id :: waypoint_type :: Some(d) :: None :: HNil)
case SquadWaypointRequest(request_type, char_id, waypoint_type, None, None) =>
Attempt.Successful(request_type :: char_id :: waypoint_type :: None :: None :: HNil)
case data : SquadWaypointRequest =>
Attempt.Failure(Err(s"unexpected format while encoding - $data"))
}
)
}

View file

@ -1,7 +1,6 @@
// Copyright (c) 2019 PSForever
package services.teamwork
import net.psforever.objects.Player
import net.psforever.objects.zones.Zone
import net.psforever.packet.game._
import net.psforever.types.{SquadRequestType, Vector3}
@ -9,7 +8,8 @@ import net.psforever.types.{SquadRequestType, Vector3}
object SquadAction {
trait Action
final case class Definition(player : Player, zone : Zone, guid : PlanetSideGUID, line : Int, action : SquadAction) extends Action
final case class Definition(zone : Zone, 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 Waypoint(event_type : WaypointEventAction.Value, waypoint_type : Int, unk : Option[Long], waypoint_info : Option[WaypointInfo]) extends Action
final case class Update(char_id : Long, health : Int, max_health : Int, armor : Int, max_armor : Int, pos : Vector3, zone_number : Int) extends Action
}

View file

@ -29,5 +29,8 @@ object SquadResponse {
final case class Detail(guid : PlanetSideGUID, squad_detail : SquadDetail) extends Response
final case class InitWaypoints(char_id : Long, waypoints : Iterable[(Int, WaypointInfo, Int)]) extends Response
final case class WaypointEvent(event_type : WaypointEventAction.Value, char_id : Long, waypoint_type : Int, unk5 : Option[Long], waypoint_info : Option[WaypointInfo], unk : Int) extends Response
final case class SquadSearchResults() extends Response
}

View file

@ -6,7 +6,6 @@ import net.psforever.objects.Player
import net.psforever.objects.definition.converter.StatConverter
import net.psforever.objects.loadouts.SquadLoadout
import net.psforever.objects.teamwork.{Member, Squad}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game._
import net.psforever.types._
import services.{GenericEventBus, Service}
@ -44,6 +43,8 @@ class SquadService extends Actor {
*/
private val initialAssociation : ListBuffer[PlanetSideGUID] = new ListBuffer[PlanetSideGUID]()
private val queuedInvites : mutable.LongMap[List[Invitation]] = mutable.LongMap[List[Invitation]]()
private val waypoints : TrieMap[PlanetSideGUID, Array[WaypointData]] =
new TrieMap[PlanetSideGUID, Array[WaypointData]]()
private val viewDetails : mutable.LongMap[PlanetSideGUID] = mutable.LongMap[PlanetSideGUID]()
private [this] val log = org.log4s.getLogger
@ -122,7 +123,7 @@ class SquadService extends Actor {
}
}
def GetLeadingSquad(player : Player, zone : Int, opt : Option[Squad]) : Option[Squad] = {
def GetLeadingSquad(player : Player, opt : Option[Squad]) : Option[Squad] = {
val charId = player.CharId
opt match {
case Some(squad) =>
@ -350,80 +351,31 @@ class SquadService extends Actor {
RemoveQueuedInvites(invitedPlayer) //TODO deal with these somehow
case _ => ;
}
// if(idToSquad.get(guid).isEmpty) {
// log.warn("Accept->Invite: the squad no longer exists")
// }
// else if(memberToSquad.get(invitedPlayer).nonEmpty) {
// log.warn("Accept->Invite: player is already a member of a squad and can not join a second one")
// }
// else {
// val squad = idToSquad(guid)
// if(!squad.AutoApproveInvitationRequests && squad.Leader.CharId != invitingPlayer) {
// //the inviting player was not the squad leader and this decision should be bounced off the squad leader
// val bid = IndirectVacancy(tplayer, guid)
// AddInvite(squad.Leader.CharId, bid) match {
// case out @ Some(_) if out.contains(bid) =>
// HandleBidForPosition(bid, tplayer)
// case _ => ;
// }
// }
// else {
// //if a suitable position in the squad can be found, player may occupy it
// squad.Membership.zipWithIndex.find({ case (member, index) =>
// ValidOpenSquadPosition(squad, index, member, tplayer.Certifications)
// }) match {
// case Some((_, line)) =>
// SquadEvents.publish(SquadServiceResponse(s"/$invitingPlayer/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitingPlayer, Some(invitedPlayer), tplayer.Name, false, Some(None))))
// SquadEvents.publish(SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitedPlayer, Some(invitingPlayer), "", true, Some(None))))
// JoinSquad(tplayer, squad, line)
// RemoveQueuedInvites(invitedPlayer) //TODO deal with these somehow
// case _ => ;
// }
// }
// }
case Some(SpontaneousInvite(invitingPlayer)) =>
//we were invited by someone into a new squad they would form
//originally, we were invited by someone into a new squad they would form
val invitingPlayerCharId = invitingPlayer.CharId
(GetParticipatingSquad(invitingPlayer) match {
case Some(participating) =>
if(participating.Leader.CharId == invitingPlayerCharId) {
Some(participating)
}
else {
//inviter joined a squad and is not its leader; bounce this request off of the squad leader
participating.Membership.zipWithIndex.find({ case (member, index) =>
ValidOpenSquadPosition(participating, index, member, tplayer.Certifications)
}) match {
case Some((_, line)) =>
val bid = BidForPosition(tplayer, participating.GUID, line)
AddInvite(participating.Leader.CharId, bid) match {
case out @ Some(_) if out.contains(bid) =>
HandleBidForPosition(bid, tplayer)
case _ => ;
}
case _ => ;
}
None
}
case None =>
//invitingPlayer became part of a squad while invited player was answering the original summons
Some(participating)
case _ =>
//generate a new squad, with invitingPlayer as the leader
val squad = StartSquad(invitingPlayer)
squad.Task = s"${tplayer.Name}'s Squad"
SquadEvents.publish(SquadServiceResponse(s"/$invitingPlayer/Squad", SquadResponse.AssociateWithSquad(squad.GUID)))
squad.Task = s"${invitingPlayer.Name}'s Squad"
SquadEvents.publish( SquadServiceResponse(s"/$invitingPlayerCharId/Squad", SquadResponse.AssociateWithSquad(squad.GUID)) )
Some(squad)
}) match {
case Some(squad) =>
squad.Membership.zipWithIndex.find({ case (member, index) =>
ValidOpenSquadPosition(squad, index, member, tplayer.Certifications)
}) match {
HandleVacancyInvite(squad.GUID, tplayer.CharId, invitingPlayerCharId, tplayer) match {
case Some((_, line)) =>
SquadEvents.publish( SquadServiceResponse(s"/$invitedPlayer/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitedPlayer, Some(invitingPlayerCharId), "", true, Some(None))) )
JoinSquad(tplayer, squad, line)
SquadEvents.publish( SquadServiceResponse(s"/$invitingPlayerCharId/Squad", SquadResponse.Membership(SquadResponseType.Accept, 0, 0, invitingPlayerCharId, Some(invitedPlayer), tplayer.Name, false, Some(None))) )
RemoveQueuedInvites(invitedPlayer) //TODO deal with these somehow
RemoveQueuedInvites(tplayer.CharId) //TODO deal with these somehow
case _ => ;
}
case None => ;
case _ => ;
}
case None =>
@ -566,32 +518,46 @@ class SquadService extends Actor {
case _ => ;
}
case SquadAction.Update(char_id, health, max_health, armor, max_armor, pos, zone_number) =>
memberToSquad.get(char_id) match {
case SquadAction.Waypoint(_, wtype, _, info) =>
val playerCharId = tplayer.CharId
(GetLeadingSquad(tplayer, None) match {
case Some(squad) =>
squad.Membership.find(_.CharId == char_id) match {
case Some(member) =>
member.Health = StatConverter.Health(health, max_health, min=1, max=64)
member.Armor = StatConverter.Health(armor, max_armor, min=1, max=64)
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 _ => ;
info match {
case Some(winfo) =>
(Some(squad), AddWaypoint(squad.GUID, wtype, winfo))
case _ =>
RemoveWaypoint(squad.GUID, wtype)
(Some(squad), None)
}
case None => ;
case _ => (None, None)
}) match {
case (Some(squad), Some(waypoint)) =>
//waypoint added or updated
squad.Membership
.filterNot { member => member.CharId == tplayer.CharId }
.foreach { member =>
val charId = member.CharId
SquadEvents.publish(
SquadServiceResponse(s"/$charId/Squad", SquadResponse.WaypointEvent(WaypointEventAction.Add, playerCharId, wtype, None, info, 1))
)
}
case (Some(squad), None) =>
//waypoint removed?
squad.Membership
.filterNot { member => member.CharId == tplayer.CharId }
.foreach { member =>
val charId = member.CharId
SquadEvents.publish(
SquadServiceResponse(s"/$charId/Squad", SquadResponse.WaypointEvent(WaypointEventAction.Remove, playerCharId, wtype, None, None, 0))
)
}
case _ => ;
}
case SquadAction.Definition(tplayer : Player, zone : Zone, guid : PlanetSideGUID, line : Int, action : SquadAction) =>
case SquadAction.Definition(zone, guid, line, action) =>
import net.psforever.packet.game.SquadAction._
val pSquadOpt = GetParticipatingSquad(tplayer)
val lSquadOpt = GetLeadingSquad(tplayer, zone.Number, pSquadOpt)
val lSquadOpt = GetLeadingSquad(tplayer, pSquadOpt)
//the following actions can only be performed by a squad's leader
action match {
case SaveSquadFavorite() =>
@ -624,17 +590,17 @@ class SquadService extends Actor {
UpdateSquadListWhenListed(squad, SquadInfo().Task(purpose))
UpdateSquadDetail(squad.GUID, squad, SquadDetail().Task(purpose))
case ChangeSquadZone(zone) =>
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed squad's ops zone to $zone")
case ChangeSquadZone(zone_id) =>
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed squad's ops zone to $zone_id")
val squad = lSquadOpt.getOrElse(StartSquad(tplayer))
squad.ZoneId = zone.zoneId.toInt
UpdateSquadListWhenListed(squad, SquadInfo().ZoneId(zone))
squad.ZoneId = zone_id.zoneId.toInt
UpdateSquadListWhenListed(squad, SquadInfo().ZoneId(zone_id))
InitialAssociation(squad)
sender ! SquadServiceResponse("", SquadResponse.Detail(
squad.GUID,
SquadService.Detail.Publish(squad))
)
UpdateSquadDetail(squad.GUID, squad.Membership.map { _m => _m.CharId }.filterNot { _ == squad.Leader.CharId }, SquadDetail().ZoneId(zone))
UpdateSquadDetail(squad.GUID, squad.Membership.map { _m => _m.CharId }.filterNot { _ == squad.Leader.CharId }, SquadDetail().ZoneId(zone_id))
case CloseSquadMemberPosition(position) =>
val squad = lSquadOpt.getOrElse(StartSquad(tplayer))
@ -906,6 +872,12 @@ class SquadService extends Actor {
case _ => ;
}
//the following action can be peprformed by anyone
case (_, SearchForSquadsWithParticularRole(role, requirements, zone_id, search_mode)) =>
//though we should be able correctly search squads as is intended
//I don't know how search results should be prioritized or even how to return search results to the user
sender ! SquadServiceResponse("", SquadResponse.SquadSearchResults())
//the following action can be performed by anyone
case (_, DisplaySquad()) =>
idToSquad.get(guid) match {
@ -926,6 +898,28 @@ class SquadService extends Actor {
case _ => ;
}
case SquadAction.Update(char_id, health, max_health, armor, max_armor, pos, zone_number) =>
memberToSquad.get(char_id) match {
case Some(squad) =>
squad.Membership.find(_.CharId == char_id) match {
case Some(member) =>
member.Health = StatConverter.Health(health, max_health, min=1, max=64)
member.Armor = StatConverter.Health(armor, max_armor, min=1, max=64)
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 _ => ;
}
case None => ;
}
case msg =>
log.info(s"Unhandled message $msg from $sender")
}
@ -1146,6 +1140,7 @@ class SquadService extends Actor {
.filterNot { _.CharId == 0 }
.foreach { member =>
SquadEvents.publish(SquadServiceResponse(s"/${member.CharId}/Squad", SquadResponse.Join(squad, indices)))
InitWaypoints(member.CharId, squad.GUID)
}
//fully update for all users
UpdateSquadDetail(squad.GUID, squad)
@ -1156,6 +1151,7 @@ class SquadService extends Actor {
val indices = squad.Membership.zipWithIndex.collect({ case (member, index) if member.CharId != 0 => index }).toList
SquadEvents.publish(SquadServiceResponse(s"/$charId/Squad", SquadResponse.Join(squad, indices)))
InitSquadDetail(squad.GUID, Seq(charId), squad)
InitWaypoints(charId, squad.GUID)
//other squad members see new member joining the squad
val updatedIndex = List(line)
val otherMembers = squad.Membership.filterNot { member => member.CharId == 0 || member.CharId == charId }.map{ _.CharId }
@ -1163,7 +1159,7 @@ class SquadService extends Actor {
SquadEvents.publish(SquadServiceResponse(s"/$member/Squad", SquadResponse.Join(squad, updatedIndex)))
}
val details = SquadDetail().Members(List(SquadPositionEntry(line, SquadPositionDetail().CharId(charId).Name(player.Name))))
UpdateSquadDetail(squad.GUID, Seq(charId), details)
UpdateSquadDetail(squad.GUID, otherMembers, details)
}
UpdateSquadListWhenListed(squad, SquadInfo().Size(size))
true
@ -1207,7 +1203,6 @@ class SquadService extends Actor {
}
def CloseOutSquad(squad : Squad, membership : Iterable[(Member, Int)], updateList : List[(Long, Int)]) : Unit = {
val leaderCharId = squad.Leader.CharId
membership.foreach {
case (member, _) =>
val charId = member.CharId
@ -1217,6 +1212,7 @@ class SquadService extends Actor {
SquadEvents.publish( SquadServiceResponse(s"/$charId/Squad", SquadResponse.Leave(squad, updateList)) )
}
idToSquad.remove(squad.GUID)
waypoints.remove(squad.GUID)
UpdateSquadList(squad, None)
}
@ -1332,9 +1328,70 @@ class SquadService extends Actor {
toMembers.foreach { charId => SquadEvents.publish(SquadServiceResponse(s"/$charId/Squad", output)) }
}
}
def AddWaypoint(guid : PlanetSideGUID, waypointType : Int, info : WaypointInfo) : Option[WaypointData] = {
(waypoints.get(guid) match {
case Some(array) =>
array
case None if idToSquad.get(guid).nonEmpty =>
log.debug(s"initializing squad waypoint system for squad #${guid.guid}")
val array = Array.fill[WaypointData](5)(new WaypointData())
waypoints(guid) = array
array
case _ =>
log.warn(s"squad #${guid.guid} does not currently exist so it can not render waypoints")
Array.empty[WaypointData]
}).lift(waypointType) match {
case Some(point) =>
//update the waypoint
log.debug(s"rendering squad waypoint $waypointType for squad #${guid.guid}")
point.zone_number = info.zone_number
point.pos = info.pos
Some(point)
case _ =>
log.warn(s"no squad waypoint $waypointType found")
None
}
}
def RemoveWaypoint(guid : PlanetSideGUID, waypointType : Int) : Unit = {
(waypoints.get(guid) match {
case Some(array) =>
array
case None =>
Array.empty[WaypointData]
}).lift(waypointType) match {
case Some(point) =>
//update the waypoint
log.debug(s"removing squad waypoint $waypointType for squad #${guid.guid}")
point.zone_number = 1
point.pos = Vector3.z(1)
case _ => ;
}
}
def InitWaypoints(toCharId : Long, guid : PlanetSideGUID) : Unit = {
(idToSquad.get(guid), waypoints.get(guid)) match {
case (Some(squad), Some(list)) =>
val vz1 = Vector3.z(1)
SquadEvents.publish(
SquadServiceResponse(s"/$toCharId/Squad", SquadResponse.InitWaypoints(squad.Leader.CharId,
list.zipWithIndex.collect { case (point, index) if point.pos != vz1 =>
(index, WaypointInfo(point.zone_number, point.pos), 1)
}
))
)
case _ => ;
}
}
}
object SquadService {
class WaypointData() {
var zone_number : Int = 1
var pos : Vector3 = Vector3.z(1) //a waypoint with a non-zero z-coordinate will flag as not getting drawn
}
abstract class Invitation(char_id : Long, name : String) {
def InviterCharId : Long = char_id
def InviterName : String = name

View file

@ -3,7 +3,7 @@ package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game.{SquadWaypointEvent, WaypointEvent}
import net.psforever.packet.game.{SquadWaypointEvent, WaypointEventAction, WaypointEvent}
import net.psforever.types.Vector3
import scodec.bits._
@ -16,12 +16,12 @@ class SquadWaypointEventTest extends Specification {
"decode (1)" in {
PacketCoding.DecodePacket(string_1).require match {
case SquadWaypointEvent(unk1, unk2, unk3, unk4, unk5, unk6) =>
unk1 mustEqual 2
unk1 mustEqual WaypointEventAction.Remove
unk2 mustEqual 11
unk3 mustEqual 31155863L
unk4 mustEqual 0
unk5 mustEqual None
unk6 mustEqual None
unk5.isEmpty mustEqual true
unk6.isEmpty mustEqual true
case _ =>
ko
}
@ -30,12 +30,12 @@ class SquadWaypointEventTest extends Specification {
"decode (2)" in {
PacketCoding.DecodePacket(string_2).require match {
case SquadWaypointEvent(unk1, unk2, unk3, unk4, unk5, unk6) =>
unk1 mustEqual 2
unk1 mustEqual WaypointEventAction.Remove
unk2 mustEqual 10
unk3 mustEqual 0L
unk4 mustEqual 4
unk5 mustEqual None
unk6 mustEqual None
unk5.isEmpty mustEqual true
unk6.isEmpty mustEqual true
case _ =>
ko
}
@ -44,12 +44,12 @@ class SquadWaypointEventTest extends Specification {
"decode (3)" in {
PacketCoding.DecodePacket(string_3).require match {
case SquadWaypointEvent(unk1, unk2, unk3, unk4, unk5, unk6) =>
unk1 mustEqual 0
unk1 mustEqual WaypointEventAction.Add
unk2 mustEqual 3
unk3 mustEqual 41581052L
unk4 mustEqual 1
unk5 mustEqual None
unk6 mustEqual Some(WaypointEvent(10, Vector3(3457.9688f, 5514.4688f, 0.0f), 1))
unk5.isEmpty mustEqual true
unk6.contains( WaypointEvent(10, Vector3(3457.9688f, 5514.4688f, 0.0f), 1) ) mustEqual true
case _ =>
ko
}
@ -58,40 +58,40 @@ class SquadWaypointEventTest extends Specification {
"decode (4)" in {
PacketCoding.DecodePacket(string_4).require match {
case SquadWaypointEvent(unk1, unk2, unk3, unk4, unk5, unk6) =>
unk1 mustEqual 1
unk1 mustEqual WaypointEventAction.Unknown1
unk2 mustEqual 3
unk3 mustEqual 41581052L
unk4 mustEqual 1
unk5 mustEqual Some(4L)
unk6 mustEqual None
unk5.contains( 4L ) mustEqual true
unk6.isEmpty mustEqual true
case _ =>
ko
}
}
"encode (1)" in {
val msg = SquadWaypointEvent(2, 11, 31155863L, 0)
val msg = SquadWaypointEvent.Remove(11, 31155863L, 0)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_1
}
"encode (2)" in {
val msg = SquadWaypointEvent(2, 10, 0L, 4)
val msg = SquadWaypointEvent.Remove(10, 0L, 4)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_2
}
"encode (3)" in {
val msg = SquadWaypointEvent(0, 3, 41581052L, 1, 10, Vector3(3457.9688f, 5514.4688f, 0.0f), 1)
val msg = SquadWaypointEvent.Add(3, 41581052L, 1, WaypointEvent(10, Vector3(3457.9688f, 5514.4688f, 0.0f), 1))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_3
}
"encode (4)" in {
val msg = SquadWaypointEvent(1, 3, 41581052L, 1, 4L)
val msg = SquadWaypointEvent.Unknown1(3, 41581052L, 1, 4L)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_4

View file

@ -516,15 +516,32 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info(s"SquadCards: ${squadUI.map { case(id, card) => s"[${card.index}:${card.name}/$id]" }.mkString}")
case SquadResponse.PromoteMember(squad, char_id, from_index, to_index) =>
//promotion will swap visual cards, but we must fix the backend
//promotion will swap cards visually, but we must fix the backend
val id = 11
sendResponse(SquadMemberEvent.Promote(id, char_id))
SwapSquadUIElements(squad, from_index, to_index)
log.info(s"SquadCards: ${squadUI.map { case(id, card) => s"[${card.index}:${card.name}/$id]" }.mkString}")
case SquadResponse.SquadSearchResults() =>
//I don't actually know how to return search results
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.NoSquadSearchResults()))
case SquadResponse.InitWaypoints(char_id, waypoints) =>
val id = 11
StartBundlingPackets()
waypoints.foreach { case (waypoint_type, info, unk) =>
sendResponse(SquadWaypointEvent.Add(id, char_id, waypoint_type, WaypointEvent(info.zone_number, info.pos, unk)))
}
StopBundlingPackets()
case SquadResponse.WaypointEvent(WaypointEventAction.Add, char_id, waypoint_type, _, Some(info), unk) =>
val id = 11
sendResponse(SquadWaypointEvent.Add(id, char_id, waypoint_type, WaypointEvent(info.zone_number, info.pos, unk)))
case SquadResponse.WaypointEvent(WaypointEventAction.Remove, char_id, waypoint_type, _, _, _) =>
val id = 11
sendResponse(SquadWaypointEvent.Remove(id, char_id, waypoint_type))
case _ => ;
}
@ -4960,12 +4977,16 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ SquadDefinitionActionMessage(u1, u2, action) =>
log.info(s"SquadDefinitionAction: $msg")
squadService ! SquadServiceMessage(player, SquadServiceAction.Definition(player, continent, u1, u2, action))
squadService ! SquadServiceMessage(player, SquadServiceAction.Definition(continent, 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))
case msg @ SquadWaypointRequest(request, _, wtype, unk, info) =>
log.info(s"Waypoint Request: $msg")
squadService ! SquadServiceMessage(player, SquadServiceAction.Waypoint(request, wtype, unk, info))
case msg @ GenericCollisionMsg(u1, p, t, php, thp, pv, tv, ppos, tpos, u2, u3, u4) =>
log.info("Ouch! " + msg)