mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
Team Building Exercise [Incomplete] (#1013)
* Squad -> SquadFeatures from a common lookup * refactor to SquadService, etc.; clarification of fields related to outfit ids * thorough pass over squad functionality in regards to joining a squad or rejecting a squad invitation * decorating squads in the squad list to indicate availability based on size/capacity or role requirements * squad list decoration; rudimentary squad list searching * renovations to squad joining, promotion, and certain invitation/cleanup procedures * further renovations to squad joining, promotion, and certain invitation/cleanup procedures * overhaul of squad joining and squad promotion * separated the invitation system from the squad publishing and manipulating system
This commit is contained in:
parent
3bd50dc89c
commit
ebfc028f5c
|
|
@ -53,6 +53,7 @@ import net.psforever.util.Database._
|
||||||
import net.psforever.persistence
|
import net.psforever.persistence
|
||||||
import net.psforever.util.{Config, Database, DefinitionUtil}
|
import net.psforever.util.{Config, Database, DefinitionUtil}
|
||||||
import net.psforever.services.Service
|
import net.psforever.services.Service
|
||||||
|
//import org.log4s.Logger
|
||||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
|
|
||||||
object AvatarActor {
|
object AvatarActor {
|
||||||
|
|
@ -435,6 +436,15 @@ object AvatarActor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def displayLookingForSquad(session: Session, state: Int): Unit = {
|
||||||
|
val player = session.player
|
||||||
|
session.zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
|
player.Faction.toString,
|
||||||
|
AvatarAction.PlanetsideAttribute(player.GUID, 53, state)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for an avatar being online at the moment by matching against their name.
|
* Check for an avatar being online at the moment by matching against their name.
|
||||||
* If discovered, run a function based on the avatar's characteristics.
|
* If discovered, run a function based on the avatar's characteristics.
|
||||||
|
|
@ -831,6 +841,11 @@ class AvatarActor(
|
||||||
session = Some(newSession)
|
session = Some(newSession)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
|
case SetLookingForSquad(lfs) =>
|
||||||
|
avatarCopy(avatar.copy(lookingForSquad = lfs))
|
||||||
|
AvatarActor.displayLookingForSquad(session.get, if (lfs) 1 else 0)
|
||||||
|
Behaviors.same
|
||||||
|
|
||||||
case CreateAvatar(name, head, voice, gender, empire) =>
|
case CreateAvatar(name, head, voice, gender, empire) =>
|
||||||
import ctx._
|
import ctx._
|
||||||
ctx.run(query[persistence.Avatar].filter(_.name ilike lift(name)).filter(!_.deleted)).onComplete {
|
ctx.run(query[persistence.Avatar].filter(_.name ilike lift(name)).filter(!_.deleted)).onComplete {
|
||||||
|
|
|
||||||
|
|
@ -823,20 +823,26 @@ class ChatActor(
|
||||||
val popTR = players.count(_.faction == PlanetSideEmpire.TR)
|
val popTR = players.count(_.faction == PlanetSideEmpire.TR)
|
||||||
val popNC = players.count(_.faction == PlanetSideEmpire.NC)
|
val popNC = players.count(_.faction == PlanetSideEmpire.NC)
|
||||||
val popVS = players.count(_.faction == PlanetSideEmpire.VS)
|
val popVS = players.count(_.faction == PlanetSideEmpire.VS)
|
||||||
val contName = session.zone.map.name
|
|
||||||
|
|
||||||
sessionActor ! SessionActor.SendResponse(
|
if (popNC + popTR + popVS == 0) {
|
||||||
ChatMsg(ChatMessageType.CMT_WHO, true, "", "That command doesn't work for now, but : ", None)
|
sessionActor ! SessionActor.SendResponse(
|
||||||
)
|
ChatMsg(ChatMessageType.CMT_WHO, false, "", "@Nomatches", None)
|
||||||
sessionActor ! SessionActor.SendResponse(
|
)
|
||||||
ChatMsg(ChatMessageType.CMT_WHO, true, "", "NC online : " + popNC + " on " + contName, None)
|
} else {
|
||||||
)
|
val contName = session.zone.map.name
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
ChatMsg(ChatMessageType.CMT_WHO, true, "", "TR online : " + popTR + " on " + contName, None)
|
ChatMsg(ChatMessageType.CMT_WHO, true, "", "That command doesn't work for now, but : ", None)
|
||||||
)
|
)
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
ChatMsg(ChatMessageType.CMT_WHO, true, "", "VS online : " + popVS + " on " + contName, None)
|
ChatMsg(ChatMessageType.CMT_WHO, true, "", "NC online : " + popNC + " on " + contName, None)
|
||||||
)
|
)
|
||||||
|
sessionActor ! SessionActor.SendResponse(
|
||||||
|
ChatMsg(ChatMessageType.CMT_WHO, true, "", "TR online : " + popTR + " on " + contName, None)
|
||||||
|
)
|
||||||
|
sessionActor ! SessionActor.SendResponse(
|
||||||
|
ChatMsg(ChatMessageType.CMT_WHO, true, "", "VS online : " + popVS + " on " + contName, None)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
case (CMT_ZONE, _, contents) if gmCommandAllowed =>
|
case (CMT_ZONE, _, contents) if gmCommandAllowed =>
|
||||||
val buffer = contents.toLowerCase.split("\\s+")
|
val buffer = contents.toLowerCase.split("\\s+")
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ import net.psforever.objects.serverobject.tube.SpawnTube
|
||||||
import net.psforever.objects.serverobject.turret.{FacilityTurret, WeaponTurret}
|
import net.psforever.objects.serverobject.turret.{FacilityTurret, WeaponTurret}
|
||||||
import net.psforever.objects.serverobject.zipline.ZipLinePath
|
import net.psforever.objects.serverobject.zipline.ZipLinePath
|
||||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject, ServerObject}
|
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject, ServerObject}
|
||||||
import net.psforever.objects.teamwork.Squad
|
import net.psforever.objects.teamwork.{Member, Squad}
|
||||||
import net.psforever.objects.vehicles.Utility.InternalTelepad
|
import net.psforever.objects.vehicles.Utility.InternalTelepad
|
||||||
import net.psforever.objects.vehicles._
|
import net.psforever.objects.vehicles._
|
||||||
import net.psforever.objects.vehicles.control.BfrFlight
|
import net.psforever.objects.vehicles.control.BfrFlight
|
||||||
|
|
@ -151,12 +151,13 @@ object SessionActor {
|
||||||
private final val zoningCountdownMessages: Seq[Int] = Seq(5, 10, 20)
|
private final val zoningCountdownMessages: Seq[Int] = Seq(5, 10, 20)
|
||||||
|
|
||||||
protected final case class SquadUIElement(
|
protected final case class SquadUIElement(
|
||||||
name: String,
|
name: String = "",
|
||||||
index: Int,
|
outfit: Long = 0,
|
||||||
zone: Int,
|
index: Int = -1,
|
||||||
health: Int,
|
zone: Int = 0,
|
||||||
armor: Int,
|
health: Int = 0,
|
||||||
position: Vector3
|
armor: Int = 0,
|
||||||
|
position: Vector3 = Vector3.Zero
|
||||||
)
|
)
|
||||||
|
|
||||||
private final case class NtuCharging(tplayer: Player, vehicle: Vehicle)
|
private final case class NtuCharging(tplayer: Player, vehicle: Vehicle)
|
||||||
|
|
@ -207,6 +208,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
val projectiles: Array[Option[Projectile]] =
|
val projectiles: Array[Option[Projectile]] =
|
||||||
Array.fill[Option[Projectile]](Projectile.rangeUID - Projectile.baseUID)(None)
|
Array.fill[Option[Projectile]](Projectile.rangeUID - Projectile.baseUID)(None)
|
||||||
var drawDeloyableIcon: PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons
|
var drawDeloyableIcon: PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons
|
||||||
|
var updateSquadRef: ActorRef = Default.Actor
|
||||||
var updateSquad: () => Unit = NoSquadUpdates
|
var updateSquad: () => Unit = NoSquadUpdates
|
||||||
var recentTeleportAttempt: Long = 0
|
var recentTeleportAttempt: Long = 0
|
||||||
var lastTerminalOrderFulfillment: Boolean = true
|
var lastTerminalOrderFulfillment: Boolean = true
|
||||||
|
|
@ -740,26 +742,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
zoningType = Zoning.Method.InstantAction
|
zoningType = Zoning.Method.InstantAction
|
||||||
zoningChatMessageType = ChatMessageType.CMT_INSTANTACTION
|
zoningChatMessageType = ChatMessageType.CMT_INSTANTACTION
|
||||||
zoningStatus = Zoning.Status.Request
|
zoningStatus = Zoning.Status.Request
|
||||||
/* TODO no ask or adapters from classic to typed so this logic is happening in SpawnPointResponse
|
|
||||||
implicit val timeout = Timeout(1 seconds)
|
|
||||||
val future =
|
|
||||||
ask(cluster.toClassic, ICS.GetInstantActionSpawnPoint(player.Faction, context.self))
|
|
||||||
.mapTo[ICS.SpawnPointResponse]
|
|
||||||
Await.result(future, 2 second) match {
|
|
||||||
case ICS.SpawnPointResponse(None) =>
|
|
||||||
sendResponse(
|
|
||||||
ChatMsg(ChatMessageType.CMT_INSTANTACTION, false, "", "@InstantActionNoHotspotsAvailable", None)
|
|
||||||
)
|
|
||||||
case ICS.SpawnPointResponse(Some(_)) =>
|
|
||||||
beginZoningCountdown(() => {
|
|
||||||
cluster ! ICS.GetInstantActionSpawnPoint(player.Faction, context.self)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
beginZoningCountdown(() => {
|
|
||||||
cluster ! ICS.GetInstantActionSpawnPoint(player.Faction, context.self)
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
cluster ! ICS.GetInstantActionSpawnPoint(player.Faction, context.self)
|
cluster ! ICS.GetInstantActionSpawnPoint(player.Faction, context.self)
|
||||||
|
|
||||||
case Quit() =>
|
case Quit() =>
|
||||||
|
|
@ -991,11 +973,26 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
case SquadResponse.SquadDecoration(guid, squad) =>
|
||||||
|
val decoration = if (
|
||||||
|
squadUI.nonEmpty ||
|
||||||
|
squad.Size == squad.Capacity ||
|
||||||
|
{
|
||||||
|
val offer = avatar.certifications
|
||||||
|
!squad.Membership.exists { _.isAvailable(offer) }
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
SquadListDecoration.NotAvailable
|
||||||
|
} else {
|
||||||
|
SquadListDecoration.Available
|
||||||
|
}
|
||||||
|
sendResponse(SquadDefinitionActionMessage(guid, 0, SquadAction.SquadListDecorator(decoration)))
|
||||||
|
|
||||||
case SquadResponse.Detail(guid, detail) =>
|
case SquadResponse.Detail(guid, detail) =>
|
||||||
sendResponse(SquadDetailDefinitionUpdateMessage(guid, detail))
|
sendResponse(SquadDetailDefinitionUpdateMessage(guid, detail))
|
||||||
|
|
||||||
case SquadResponse.AssociateWithSquad(squad_guid) =>
|
case SquadResponse.IdentifyAsSquadLeader(squad_guid) =>
|
||||||
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.AssociateWithSquad()))
|
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.IdentifyAsSquadLeader()))
|
||||||
|
|
||||||
case SquadResponse.SetListSquad(squad_guid) =>
|
case SquadResponse.SetListSquad(squad_guid) =>
|
||||||
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.SetListSquad()))
|
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.SetListSquad()))
|
||||||
|
|
@ -1003,7 +1000,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
case SquadResponse.Membership(request_type, unk1, unk2, charId, opt_char_id, player_name, unk5, unk6) =>
|
case SquadResponse.Membership(request_type, unk1, unk2, charId, opt_char_id, player_name, unk5, unk6) =>
|
||||||
val name = request_type match {
|
val name = request_type match {
|
||||||
case SquadResponseType.Invite if unk5 =>
|
case SquadResponseType.Invite if unk5 =>
|
||||||
//player_name is our name; the name of the player indicated by unk3 is needed
|
//the name of the player indicated by unk3 is needed
|
||||||
LivePlayerList.WorldPopulation({ case (_, a: Avatar) => charId == a.id }).headOption match {
|
LivePlayerList.WorldPopulation({ case (_, a: Avatar) => charId == a.id }).headOption match {
|
||||||
case Some(player) =>
|
case Some(player) =>
|
||||||
player.name
|
player.name
|
||||||
|
|
@ -1026,10 +1023,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
case SquadResponse.Join(squad, positionsToUpdate, toChannel) =>
|
case SquadResponse.Join(squad, positionsToUpdate, _, ref) =>
|
||||||
val leader = squad.Leader
|
val avatarId = avatar.id
|
||||||
val membershipPositions = positionsToUpdate map squad.Membership.zipWithIndex
|
val membershipPositions = (positionsToUpdate map squad.Membership.zipWithIndex)
|
||||||
membershipPositions.find({ case (member, _) => member.CharId == avatar.id }) match {
|
.filter { case (mem, index) =>
|
||||||
|
mem.CharId > 0 && positionsToUpdate.contains(index)
|
||||||
|
}
|
||||||
|
membershipPositions.find { case (mem, _) => mem.CharId == avatarId } match {
|
||||||
case Some((ourMember, ourIndex)) =>
|
case Some((ourMember, ourIndex)) =>
|
||||||
//we are joining the squad
|
//we are joining the squad
|
||||||
//load each member's entry (our own too)
|
//load each member's entry (our own too)
|
||||||
|
|
@ -1043,11 +1043,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
index,
|
index,
|
||||||
member.Name,
|
member.Name,
|
||||||
member.ZoneId,
|
member.ZoneId,
|
||||||
unk7 = 0
|
outfit_id = 0
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
squadUI(member.CharId) =
|
squadUI(member.CharId) =
|
||||||
SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
SquadUIElement(member.Name, outfit=0L, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
||||||
}
|
}
|
||||||
//repeat our entry
|
//repeat our entry
|
||||||
sendResponse(
|
sendResponse(
|
||||||
|
|
@ -1057,39 +1057,38 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
ourIndex,
|
ourIndex,
|
||||||
ourMember.Name,
|
ourMember.Name,
|
||||||
ourMember.ZoneId,
|
ourMember.ZoneId,
|
||||||
unk7 = 0
|
outfit_id = 0
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val playerGuid = player.GUID
|
|
||||||
//turn lfs off
|
//turn lfs off
|
||||||
val factionChannel = s"${player.Faction}"
|
|
||||||
if (avatar.lookingForSquad) {
|
if (avatar.lookingForSquad) {
|
||||||
avatarActor ! AvatarActor.SetLookingForSquad(false)
|
avatarActor ! AvatarActor.SetLookingForSquad(false)
|
||||||
}
|
}
|
||||||
|
val playerGuid = player.GUID
|
||||||
|
val factionChannel = s"${player.Faction}"
|
||||||
//squad colors
|
//squad colors
|
||||||
GiveSquadColorsInZone()
|
GiveSquadColorsToMembers()
|
||||||
continent.AvatarEvents ! AvatarServiceMessage(
|
GiveSquadColorsForOthers(playerGuid, factionChannel, squad_supplement_id)
|
||||||
factionChannel,
|
|
||||||
AvatarAction.PlanetsideAttribute(playerGuid, 31, squad_supplement_id)
|
|
||||||
)
|
|
||||||
//associate with member position in squad
|
//associate with member position in squad
|
||||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 32, ourIndex))
|
sendResponse(PlanetsideAttributeMessage(playerGuid, 32, ourIndex))
|
||||||
//a finalization? what does this do?
|
//a finalization? what does this do?
|
||||||
sendResponse(SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(18)))
|
sendResponse(SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(18)))
|
||||||
|
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.ReloadDecoration())
|
||||||
|
updateSquadRef = ref
|
||||||
updateSquad = PeriodicUpdatesWhenEnrolledInSquad
|
updateSquad = PeriodicUpdatesWhenEnrolledInSquad
|
||||||
chatActor ! ChatActor.JoinChannel(ChatService.ChatChannel.Squad(squad.GUID))
|
chatActor ! ChatActor.JoinChannel(ChatService.ChatChannel.Squad(squad.GUID))
|
||||||
case _ =>
|
case _ =>
|
||||||
//other player is joining our squad
|
//other player is joining our squad
|
||||||
//load each member's entry
|
//load each member's entry
|
||||||
GiveSquadColorsInZone(
|
GiveSquadColorsToMembers(
|
||||||
membershipPositions.map {
|
membershipPositions.map {
|
||||||
case (member, index) =>
|
case (member, index) =>
|
||||||
val charId = member.CharId
|
val charId = member.CharId
|
||||||
sendResponse(
|
sendResponse(
|
||||||
SquadMemberEvent.Add(squad_supplement_id, charId, index, member.Name, member.ZoneId, unk7 = 0)
|
SquadMemberEvent.Add(squad_supplement_id, charId, index, member.Name, member.ZoneId, outfit_id = 0)
|
||||||
)
|
)
|
||||||
squadUI(charId) =
|
squadUI(charId) =
|
||||||
SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
SquadUIElement(member.Name, outfit=0L, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
||||||
charId
|
charId
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -1098,23 +1097,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
sendResponse(
|
sendResponse(
|
||||||
SquadState(
|
SquadState(
|
||||||
PlanetSideGUID(squad_supplement_id),
|
PlanetSideGUID(squad_supplement_id),
|
||||||
membershipPositions
|
membershipPositions.map { case (member, _) =>
|
||||||
.filterNot { case (member, _) => member.CharId == avatar.id }
|
SquadStateInfo(member.CharId, member.Health, member.Armor, member.Position)
|
||||||
.map {
|
}
|
||||||
case (member, _) =>
|
|
||||||
SquadStateInfo(
|
|
||||||
member.CharId,
|
|
||||||
member.Health,
|
|
||||||
member.Armor,
|
|
||||||
member.Position,
|
|
||||||
2,
|
|
||||||
2,
|
|
||||||
false,
|
|
||||||
429,
|
|
||||||
None,
|
|
||||||
None
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1123,6 +1108,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
case Some((ourMember, ourIndex)) =>
|
case Some((ourMember, ourIndex)) =>
|
||||||
//we are leaving the squad
|
//we are leaving the squad
|
||||||
//remove each member's entry (our own too)
|
//remove each member's entry (our own too)
|
||||||
|
updateSquadRef = Default.Actor
|
||||||
positionsToUpdate.foreach {
|
positionsToUpdate.foreach {
|
||||||
case (member, index) =>
|
case (member, index) =>
|
||||||
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index))
|
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index))
|
||||||
|
|
@ -1131,16 +1117,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
//uninitialize
|
//uninitialize
|
||||||
val playerGuid = player.GUID
|
val playerGuid = player.GUID
|
||||||
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, ourMember, ourIndex)) //repeat of our entry
|
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, ourMember, ourIndex)) //repeat of our entry
|
||||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 31, 0)) //disassociate with squad?
|
GiveSquadColorsToSelf(value = 0)
|
||||||
continent.AvatarEvents ! AvatarServiceMessage(
|
sendResponse(PlanetsideAttributeMessage(playerGuid, 32, 0)) //disassociate with member position in squad?
|
||||||
s"${player.Faction}",
|
|
||||||
AvatarAction.PlanetsideAttribute(playerGuid, 31, 0)
|
|
||||||
)
|
|
||||||
sendResponse(
|
|
||||||
PlanetsideAttributeMessage(playerGuid, 32, 0)
|
|
||||||
) //disassociate with member position in squad?
|
|
||||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 34, 4294967295L)) //unknown, perhaps unrelated?
|
sendResponse(PlanetsideAttributeMessage(playerGuid, 34, 4294967295L)) //unknown, perhaps unrelated?
|
||||||
lfsm = false
|
lfsm = false
|
||||||
|
avatarActor ! AvatarActor.SetLookingForSquad(false)
|
||||||
//a finalization? what does this do?
|
//a finalization? what does this do?
|
||||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
|
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
|
||||||
squad_supplement_id = 0
|
squad_supplement_id = 0
|
||||||
|
|
@ -1149,7 +1130,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
chatActor ! ChatActor.LeaveChannel(ChatService.ChatChannel.Squad(squad.GUID))
|
chatActor ! ChatActor.LeaveChannel(ChatService.ChatChannel.Squad(squad.GUID))
|
||||||
case _ =>
|
case _ =>
|
||||||
//remove each member's entry
|
//remove each member's entry
|
||||||
GiveSquadColorsInZone(
|
GiveSquadColorsToMembers(
|
||||||
positionsToUpdate.map {
|
positionsToUpdate.map {
|
||||||
case (member, index) =>
|
case (member, index) =>
|
||||||
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index))
|
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index))
|
||||||
|
|
@ -1164,35 +1145,19 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
//we've already swapped position internally; now we swap the cards
|
//we've already swapped position internally; now we swap the cards
|
||||||
SwapSquadUIElements(squad, from_index, to_index)
|
SwapSquadUIElements(squad, from_index, to_index)
|
||||||
|
|
||||||
case SquadResponse.PromoteMember(squad, char_id, from_index, to_index) =>
|
case SquadResponse.PromoteMember(squad, promotedPlayer, from_index) =>
|
||||||
val charId = player.CharId
|
if (promotedPlayer != player.CharId) {
|
||||||
val guid = player.GUID
|
//demoted from leader; no longer lfsm
|
||||||
lazy val factionChannel = s"${player.Faction}"
|
|
||||||
//are we being demoted?
|
|
||||||
if (squadUI(charId).index == 0) {
|
|
||||||
//lfsm -> lfs
|
|
||||||
if (lfsm) {
|
if (lfsm) {
|
||||||
sendResponse(PlanetsideAttributeMessage(guid, 53, 0))
|
lfsm = false
|
||||||
continent.AvatarEvents ! AvatarServiceMessage(
|
AvatarActor.displayLookingForSquad(session, state = 0)
|
||||||
factionChannel,
|
|
||||||
AvatarAction.PlanetsideAttribute(guid, 53, 0)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
lfsm = false
|
|
||||||
sendResponse(PlanetsideAttributeMessage(guid, 32, from_index)) //associate with member position in squad
|
|
||||||
}
|
}
|
||||||
//are we being promoted?
|
sendResponse(SquadMemberEvent(MemberEvent.Promote, squad.GUID.guid, promotedPlayer, position = 0))
|
||||||
else if (charId == char_id) {
|
//the players have already been swapped in the backend object
|
||||||
sendResponse(PlanetsideAttributeMessage(guid, 32, 0)) //associate with member position in squad
|
PromoteSquadUIElements(squad, from_index)
|
||||||
}
|
|
||||||
continent.AvatarEvents ! AvatarServiceMessage(
|
|
||||||
factionChannel,
|
|
||||||
AvatarAction.PlanetsideAttribute(guid, 31, squad_supplement_id)
|
|
||||||
)
|
|
||||||
//we must fix the squad cards backend
|
|
||||||
SwapSquadUIElements(squad, from_index, to_index)
|
|
||||||
|
|
||||||
case SquadResponse.UpdateMembers(squad, positions) =>
|
case SquadResponse.UpdateMembers(_, positions) =>
|
||||||
val pairedEntries = positions.collect {
|
val pairedEntries = positions.collect {
|
||||||
case entry if squadUI.contains(entry.char_id) =>
|
case entry if squadUI.contains(entry.char_id) =>
|
||||||
(entry, squadUI(entry.char_id))
|
(entry, squadUI(entry.char_id))
|
||||||
|
|
@ -1206,13 +1171,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
SquadMemberEvent.UpdateZone(squad_supplement_id, entry.char_id, element.index, entry.zone_number)
|
SquadMemberEvent.UpdateZone(squad_supplement_id, entry.char_id, element.index, entry.zone_number)
|
||||||
)
|
)
|
||||||
squadUI(entry.char_id) =
|
squadUI(entry.char_id) =
|
||||||
SquadUIElement(element.name, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
|
SquadUIElement(element.name, element.outfit, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
|
||||||
entry
|
entry
|
||||||
case (entry, element)
|
case (entry, element)
|
||||||
if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position =>
|
if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position =>
|
||||||
//other elements that need to be updated
|
//other elements that need to be updated
|
||||||
squadUI(entry.char_id) =
|
squadUI(entry.char_id) =
|
||||||
SquadUIElement(element.name, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
|
SquadUIElement(element.name, element.outfit, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
|
||||||
entry
|
entry
|
||||||
})
|
})
|
||||||
.filterNot(_.char_id == avatar.id) //we want to update our backend, but not our frontend
|
.filterNot(_.char_id == avatar.id) //we want to update our backend, but not our frontend
|
||||||
|
|
@ -1221,15 +1186,29 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
SquadState(
|
SquadState(
|
||||||
PlanetSideGUID(squad_supplement_id),
|
PlanetSideGUID(squad_supplement_id),
|
||||||
updatedEntries.map { entry =>
|
updatedEntries.map { entry =>
|
||||||
SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos, 2, 2, false, 429, None, None)
|
SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
case SquadResponse.SquadSearchResults() =>
|
case SquadResponse.CharacterKnowledge(charId, name, certs, u1, u2, zone) =>
|
||||||
//I don't actually know how to return search results
|
sendResponse(CharacterKnowledgeMessage(charId, Some(CharacterKnowledgeInfo(name, certs, u1, u2, zone))))
|
||||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.NoSquadSearchResults()))
|
|
||||||
|
case SquadResponse.SquadSearchResults(results) =>
|
||||||
|
//TODO positive squad search results message?
|
||||||
|
if(results.nonEmpty) {
|
||||||
|
results.foreach { guid =>
|
||||||
|
sendResponse(SquadDefinitionActionMessage(
|
||||||
|
guid,
|
||||||
|
0,
|
||||||
|
SquadAction.SquadListDecorator(SquadListDecoration.SearchResult))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendResponse(SquadDefinitionActionMessage(player.GUID, 0, SquadAction.NoSquadSearchResults()))
|
||||||
|
}
|
||||||
|
sendResponse(SquadDefinitionActionMessage(player.GUID, 0, SquadAction.CancelSquadSearch()))
|
||||||
|
|
||||||
case SquadResponse.InitWaypoints(char_id, waypoints) =>
|
case SquadResponse.InitWaypoints(char_id, waypoints) =>
|
||||||
waypoints.foreach {
|
waypoints.foreach {
|
||||||
|
|
@ -3086,7 +3065,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
case None =>
|
case None =>
|
||||||
avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition)
|
avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition)
|
||||||
TaskWorkflow.execute(BuyNewEquipmentPutInInventory(
|
TaskWorkflow.execute(BuyNewEquipmentPutInInventory(
|
||||||
continent.GUID(tplayer.VehicleSeated) match { case Some(v: Vehicle) => v; case _ => player },
|
continent.GUID(tplayer.VehicleSeated) match { case Some(v: Vehicle) => v; case _ => tplayer },
|
||||||
tplayer,
|
tplayer,
|
||||||
msg.terminal_guid
|
msg.terminal_guid
|
||||||
)(item))
|
)(item))
|
||||||
|
|
@ -3642,8 +3621,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
sendResponse(AvatarDeadStateMessage(DeadState.Alive, 0, 0, tplayer.Position, player.Faction, unk5=true))
|
sendResponse(AvatarDeadStateMessage(DeadState.Alive, 0, 0, tplayer.Position, player.Faction, unk5=true))
|
||||||
//looking for squad (members)
|
//looking for squad (members)
|
||||||
if (tplayer.avatar.lookingForSquad || lfsm) {
|
if (tplayer.avatar.lookingForSquad || lfsm) {
|
||||||
sendResponse(PlanetsideAttributeMessage(guid, 53, 1))
|
AvatarActor.displayLookingForSquad(session, state = 1)
|
||||||
continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.PlanetsideAttribute(guid, 53, 1))
|
|
||||||
}
|
}
|
||||||
sendResponse(AvatarSearchCriteriaMessage(guid, List(0, 0, 0, 0, 0, 0)))
|
sendResponse(AvatarSearchCriteriaMessage(guid, List(0, 0, 0, 0, 0, 0)))
|
||||||
//these are facilities and towers and bunkers in the zone, but not necessarily all of them for some reason
|
//these are facilities and towers and bunkers in the zone, but not necessarily all of them for some reason
|
||||||
|
|
@ -3862,7 +3840,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
case (None, _) => ;
|
case (None, _) => ;
|
||||||
}
|
}
|
||||||
//non-squad GUID-0 counts as the settings when not joined with a squad
|
//non-squad GUID-0 counts as the settings when not joined with a squad
|
||||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.AssociateWithSquad()))
|
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.IdentifyAsSquadLeader()))
|
||||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.SetListSquad()))
|
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.SetListSquad()))
|
||||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
|
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
|
||||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitSquadList())
|
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitSquadList())
|
||||||
|
|
@ -3880,11 +3858,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
if (squad_supplement_id > 0) {
|
if (squad_supplement_id > 0) {
|
||||||
squadUI.get(player.CharId) match {
|
squadUI.get(player.CharId) match {
|
||||||
case Some(elem) =>
|
case Some(elem) =>
|
||||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 31, squad_supplement_id))
|
GiveSquadColorsToSelf(squad_supplement_id)
|
||||||
continent.AvatarEvents ! AvatarServiceMessage(
|
|
||||||
s"${player.Faction}",
|
|
||||||
AvatarAction.PlanetsideAttribute(player.GUID, 31, squad_supplement_id)
|
|
||||||
)
|
|
||||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, elem.index))
|
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, elem.index))
|
||||||
case _ =>
|
case _ =>
|
||||||
log.warn(s"RespawnSquadSetup: asked to redraw squad information, but ${player.Name} has no squad element for squad $squad_supplement_id")
|
log.warn(s"RespawnSquadSetup: asked to redraw squad information, but ${player.Name} has no squad element for squad $squad_supplement_id")
|
||||||
|
|
@ -3892,6 +3866,45 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give the squad colors associated with the current squad to the client's player character.
|
||||||
|
* @param value value to associate the player
|
||||||
|
*/
|
||||||
|
def GiveSquadColorsToSelf(value: Long): Unit = {
|
||||||
|
GiveSquadColorsToSelf(player.GUID, player.Faction, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give the squad colors associated with the current squad to the client's player character.
|
||||||
|
* @param guid player guid
|
||||||
|
* @param faction faction for targeted updates to other players
|
||||||
|
* @param value value to associate the player
|
||||||
|
*/
|
||||||
|
def GiveSquadColorsToSelf(guid: PlanetSideGUID, faction: PlanetSideEmpire.Value, value: Long): Unit = {
|
||||||
|
sendResponse(PlanetsideAttributeMessage(guid, 31, value))
|
||||||
|
GiveSquadColorsForOthers(guid, faction, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give the squad colors associated with the current squad to the client's player character.
|
||||||
|
* @param guid player guid
|
||||||
|
* @param faction faction for targeted updates to other players
|
||||||
|
* @param value value to associate the player
|
||||||
|
*/
|
||||||
|
def GiveSquadColorsForOthers(guid: PlanetSideGUID, faction: PlanetSideEmpire.Value, value: Long): Unit = {
|
||||||
|
GiveSquadColorsForOthers(guid, faction.toString, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give the squad colors associated with the current squad to the client's player character to other players.
|
||||||
|
* @param guid player guid
|
||||||
|
* @param faction faction for targeted updates to other players
|
||||||
|
* @param value value to associate the player
|
||||||
|
*/
|
||||||
|
def GiveSquadColorsForOthers(guid: PlanetSideGUID, factionChannel: String, value: Long): Unit = {
|
||||||
|
continent.AvatarEvents ! AvatarServiceMessage(factionChannel, AvatarAction.PlanetsideAttribute(guid, 31, value))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* These messages are used during each subsequent respawn to reset the squad colors on player nameplates and marquees.
|
* These messages are used during each subsequent respawn to reset the squad colors on player nameplates and marquees.
|
||||||
* During a zone change,
|
* During a zone change,
|
||||||
|
|
@ -3902,23 +3915,23 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
def ZoneChangeSquadSetup(): Unit = {
|
def ZoneChangeSquadSetup(): Unit = {
|
||||||
RespawnSquadSetup()
|
RespawnSquadSetup()
|
||||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitSquadList())
|
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitSquadList())
|
||||||
GiveSquadColorsInZone()
|
GiveSquadColorsToMembers()
|
||||||
squadSetup = RespawnSquadSetup
|
squadSetup = RespawnSquadSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocate all squad members in zone and give their nameplates and their marquees the appropriate squad color.
|
* Allocate all squad members in zone and give their nameplates and their marquees the appropriate squad color.
|
||||||
*/
|
*/
|
||||||
def GiveSquadColorsInZone(): Unit = {
|
def GiveSquadColorsToMembers(): Unit = {
|
||||||
GiveSquadColorsInZone(squadUI.keys, squad_supplement_id)
|
GiveSquadColorsToMembers(squadUI.keys, squad_supplement_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocate the listed squad members in zone and give their nameplates and their marquees the appropriate squad color.
|
* Allocate the listed squad members in zone and give their nameplates and their marquees the appropriate squad color.
|
||||||
* @param members members of the squad to target
|
* @param members members of the squad to target
|
||||||
*/
|
*/
|
||||||
def GiveSquadColorsInZone(members: Iterable[Long]): Unit = {
|
def GiveSquadColorsToMembers(members: Iterable[Long]): Unit = {
|
||||||
GiveSquadColorsInZone(members, squad_supplement_id)
|
GiveSquadColorsToMembers(members, squad_supplement_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -3927,7 +3940,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
* @param members members of the squad to target
|
* @param members members of the squad to target
|
||||||
* @param value the assignment value
|
* @param value the assignment value
|
||||||
*/
|
*/
|
||||||
def GiveSquadColorsInZone(members: Iterable[Long], value: Long): Unit = {
|
def GiveSquadColorsToMembers(members: Iterable[Long], value: Long): Unit = {
|
||||||
SquadMembersInZone(members).foreach { members =>
|
SquadMembersInZone(members).foreach { members =>
|
||||||
sendResponse(PlanetsideAttributeMessage(members.GUID, 31, value))
|
sendResponse(PlanetsideAttributeMessage(members.GUID, 31, value))
|
||||||
}
|
}
|
||||||
|
|
@ -5784,15 +5797,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
} else if (action == 30) {
|
} else if (action == 30) {
|
||||||
log.info(s"${player.Name} is back")
|
log.info(s"${player.Name} is back")
|
||||||
player.AwayFromKeyboard = false
|
player.AwayFromKeyboard = false
|
||||||
|
} else if (action == GenericActionEnum.DropSpecialItem.id) {
|
||||||
|
DropSpecialSlotItem()
|
||||||
renewCharSavedTimer(
|
renewCharSavedTimer(
|
||||||
Config.app.game.savedMsg.renewal.fixed,
|
Config.app.game.savedMsg.renewal.fixed,
|
||||||
Config.app.game.savedMsg.renewal.variable
|
Config.app.game.savedMsg.renewal.variable
|
||||||
)
|
)
|
||||||
}
|
} else if (action == 15) { //max deployment
|
||||||
if (action == GenericActionEnum.DropSpecialItem.id) {
|
|
||||||
DropSpecialSlotItem()
|
|
||||||
}
|
|
||||||
if (action == 15) { //max deployment
|
|
||||||
log.info(s"${player.Name} has anchored ${player.Sex.pronounObject}self to the ground")
|
log.info(s"${player.Name} has anchored ${player.Sex.pronounObject}self to the ground")
|
||||||
player.UsingSpecial = SpecialExoSuitDefinition.Mode.Anchored
|
player.UsingSpecial = SpecialExoSuitDefinition.Mode.Anchored
|
||||||
continent.AvatarEvents ! AvatarServiceMessage(
|
continent.AvatarEvents ! AvatarServiceMessage(
|
||||||
|
|
@ -5851,32 +5862,17 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
} else {
|
} else {
|
||||||
log.warn(s"GenericActionMessage: ${player.Name} can't handle action code 21")
|
log.warn(s"GenericActionMessage: ${player.Name} can't handle action code 21")
|
||||||
}
|
}
|
||||||
} else if (action == 36) { //Looking For Squad ON
|
} else if (action == 36 || action == 37) { //Looking For Squad (Members) (on/off)
|
||||||
if (squadUI.nonEmpty) {
|
val state = if (action == 36) { true } else { false }
|
||||||
if (!lfsm && squadUI(player.CharId).index == 0) {
|
squadUI.get(player.CharId) match {
|
||||||
lfsm = true
|
case Some(elem) if elem.index == 0 =>
|
||||||
continent.AvatarEvents ! AvatarServiceMessage(
|
lfsm = state
|
||||||
s"${player.Faction}",
|
AvatarActor.displayLookingForSquad(session, boolToInt(state))
|
||||||
AvatarAction.PlanetsideAttribute(player.GUID, 53, 1)
|
case _ =>
|
||||||
)
|
avatarActor ! AvatarActor.SetLookingForSquad(state)
|
||||||
}
|
|
||||||
} else if (!avatar.lookingForSquad) {
|
|
||||||
avatarActor ! AvatarActor.SetLookingForSquad(true)
|
|
||||||
}
|
|
||||||
} else if (action == 37) { //Looking For Squad OFF
|
|
||||||
if (squadUI.nonEmpty) {
|
|
||||||
if (lfsm && squadUI(player.CharId).index == 0) {
|
|
||||||
lfsm = false
|
|
||||||
continent.AvatarEvents ! AvatarServiceMessage(
|
|
||||||
s"${player.Faction}",
|
|
||||||
AvatarAction.PlanetsideAttribute(player.GUID, 53, 0)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (avatar.lookingForSquad) {
|
|
||||||
avatarActor ! AvatarActor.SetLookingForSquad(false)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debug(s"$msg")
|
log.warn(s"GenericActionMessage: ${player.Name} can't handle $msg")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -9280,66 +9276,131 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def SwapSquadUIElements(squad: Squad, fromIndex: Int, toIndex: Int): Unit = {
|
/**
|
||||||
if (squadUI.nonEmpty) {
|
* Swap the squad UI elements along the top of the screen
|
||||||
val fromMember = squad.Membership(toIndex) //the players have already been swapped in the backend object
|
* (colloquially referred to as "cards")
|
||||||
val fromCharId = fromMember.CharId
|
* to match recently updated positions of the members in a squad.<br>
|
||||||
val toMember = squad.Membership(fromIndex) //the players have already been swapped in the backend object
|
* <br>
|
||||||
val toCharId = toMember.CharId
|
* The squad membership structure should have already updated to the correct positions of the members.
|
||||||
val id = 11
|
* By referencing one of the indices in this structure,
|
||||||
if (toCharId > 0) {
|
* obtain the character identification number,
|
||||||
//toMember and fromMember have swapped places
|
* use the character identification number to locate that member's card,
|
||||||
val fromElem = squadUI(fromCharId)
|
* and update the card index to match the squad position in which that member is discovered.
|
||||||
val toElem = squadUI(toCharId)
|
* @see `PlanetsideAttributeMessage`
|
||||||
squadUI(toCharId) =
|
* @see `Squad`
|
||||||
SquadUIElement(fromElem.name, toIndex, fromElem.zone, fromElem.health, fromElem.armor, fromElem.position)
|
* @see `SquadMemberEvent.Add`
|
||||||
squadUI(fromCharId) =
|
* @see `SquadMemberEvent.Remove`
|
||||||
SquadUIElement(toElem.name, fromIndex, toElem.zone, toElem.health, toElem.armor, toElem.position)
|
* @see `SquadState`
|
||||||
sendResponse(SquadMemberEvent.Add(id, toCharId, toIndex, fromElem.name, fromElem.zone, unk7 = 0))
|
* @see `SquadStateInfo`
|
||||||
sendResponse(SquadMemberEvent.Add(id, fromCharId, fromIndex, toElem.name, toElem.zone, unk7 = 0))
|
* @param squad the squad that supports the membership structure
|
||||||
|
* @param firstIndex the index of a player in the squad's membership;
|
||||||
|
* the member position must be occupied
|
||||||
|
* @param secondIndex the optional index of a player in the squad's membership;
|
||||||
|
* if the member position is occupied,
|
||||||
|
* the member at the first index swaps with the member at this second index
|
||||||
|
*/
|
||||||
|
def SwapSquadUIElements(squad: Squad, firstIndex: Int, secondIndex: Int): Unit = {
|
||||||
|
//the players should have already been swapped in the backend object
|
||||||
|
val firstMember = squad.Membership(firstIndex)
|
||||||
|
val firstCharId = firstMember.CharId
|
||||||
|
if (squadUI.nonEmpty && firstCharId > 0) {
|
||||||
|
//the firstIndex should always point to a valid player in the squad
|
||||||
|
val sguid = squad.GUID
|
||||||
|
val id = squad_supplement_id
|
||||||
|
val isFirstMoving = firstCharId == player.CharId
|
||||||
|
val secondMember = squad.Membership(secondIndex)
|
||||||
|
val secondCharId = secondMember.CharId
|
||||||
|
if (secondCharId > 0 && firstCharId != secondCharId) {
|
||||||
|
//secondMember and firstMember swap places
|
||||||
|
val newFirstElem = squadUI(firstCharId).copy(index = firstIndex)
|
||||||
|
val newSecondElem = squadUI(secondCharId).copy(index = secondIndex)
|
||||||
|
squadUI.put(firstCharId, newFirstElem)
|
||||||
|
squadUI.put(secondCharId, newSecondElem)
|
||||||
|
// sendResponse(SquadMemberEvent.Remove(id, secondCharId, firstIndex))
|
||||||
|
// sendResponse(SquadMemberEvent.Remove(id, firstCharId, secondIndex))
|
||||||
|
sendResponse(SquadMemberEvent.Add(id, firstCharId, firstIndex, newFirstElem.name, newFirstElem.zone, outfit_id = 0))
|
||||||
|
sendResponse(SquadMemberEvent.Add(id, secondCharId, secondIndex, newSecondElem.name, newSecondElem.zone, outfit_id = 0))
|
||||||
|
if (isFirstMoving) {
|
||||||
|
sendResponse(PlanetsideAttributeMessage(firstMember.GUID, 32, firstIndex))
|
||||||
|
sendResponse(SquadDefinitionActionMessage(sguid, 0, SquadAction.Unknown(18)))
|
||||||
|
} else if (secondCharId == player.CharId) {
|
||||||
|
sendResponse(PlanetsideAttributeMessage(secondMember.GUID, 32, secondIndex))
|
||||||
|
sendResponse(SquadDefinitionActionMessage(sguid, 0, SquadAction.Unknown(18)))
|
||||||
|
}
|
||||||
sendResponse(
|
sendResponse(
|
||||||
SquadState(
|
SquadState(PlanetSideGUID(id), List(
|
||||||
PlanetSideGUID(id),
|
SquadStateInfo(firstCharId, newFirstElem.health, newFirstElem.armor, newFirstElem.position),
|
||||||
List(
|
SquadStateInfo(secondCharId, newSecondElem.health, newSecondElem.armor, newSecondElem.position)
|
||||||
SquadStateInfo(fromCharId, toElem.health, toElem.armor, toElem.position, 2, 2, false, 429, None, None),
|
))
|
||||||
SquadStateInfo(toCharId, fromElem.health, fromElem.armor, fromElem.position, 2, 2, false, 429, None, None)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
//previous fromMember has moved toMember
|
//firstMember has moved to a new position in squad
|
||||||
val elem = squadUI(fromCharId)
|
val elem = squadUI(firstCharId)
|
||||||
squadUI(fromCharId) = SquadUIElement(elem.name, toIndex, elem.zone, elem.health, elem.armor, elem.position)
|
squadUI.put(firstCharId, elem.copy(index = firstIndex))
|
||||||
sendResponse(SquadMemberEvent.Remove(id, fromCharId, fromIndex))
|
// sendResponse(SquadMemberEvent.Remove(id, firstCharId, elem.index))
|
||||||
sendResponse(SquadMemberEvent.Add(id, fromCharId, toIndex, elem.name, elem.zone, unk7 = 0))
|
sendResponse(SquadMemberEvent.Add(id, firstCharId, firstIndex, elem.name, elem.zone, outfit_id = 0))
|
||||||
|
if (firstCharId == player.CharId) {
|
||||||
|
sendResponse(PlanetsideAttributeMessage(firstMember.GUID, 32, firstIndex))
|
||||||
|
sendResponse(SquadDefinitionActionMessage(sguid, 0, SquadAction.Unknown(18)))
|
||||||
|
}
|
||||||
sendResponse(
|
sendResponse(
|
||||||
SquadState(
|
SquadState(PlanetSideGUID(id), List(SquadStateInfo(firstCharId, elem.health, elem.armor, elem.position)))
|
||||||
PlanetSideGUID(id),
|
|
||||||
List(SquadStateInfo(fromCharId, elem.health, elem.armor, elem.position, 2, 2, false, 429, None, None))
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val charId = avatar.id
|
|
||||||
if (toCharId == charId) {
|
|
||||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, toIndex))
|
|
||||||
} else if (fromCharId == charId) {
|
|
||||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, fromIndex))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def NoSquadUpdates(): Unit = {}
|
def PromoteSquadUIElements(squad: Squad, fromIndex: Int): Unit = {
|
||||||
|
//the players should have already been swapped in the backend object
|
||||||
|
val firstMember = squad.Membership(0)
|
||||||
|
val firstCharId = firstMember.CharId
|
||||||
|
val secondMember = squad.Membership(fromIndex)
|
||||||
|
val secondCharId = secondMember.CharId
|
||||||
|
if (squadUI.nonEmpty && fromIndex != 0 && firstCharId > 0 && secondCharId > 0) {
|
||||||
|
val newFirstElem = squadUI(firstCharId).copy(index = 0)
|
||||||
|
val newSecondElem = squadUI(secondCharId).copy(index = fromIndex)
|
||||||
|
val charId = player.CharId
|
||||||
|
val pguid = player.GUID
|
||||||
|
val sguid = squad.GUID
|
||||||
|
val id = squad_supplement_id
|
||||||
|
//secondMember and firstMember swap places
|
||||||
|
squadUI.put(firstCharId, newFirstElem)
|
||||||
|
squadUI.put(secondCharId, newSecondElem)
|
||||||
|
sendResponse(SquadMemberEvent(MemberEvent.Promote, id, firstCharId, position = 0))
|
||||||
|
//player is being either promoted or demoted?
|
||||||
|
if (firstCharId == charId) {
|
||||||
|
sendResponse(PlanetsideAttributeMessage(pguid, 32, 0))
|
||||||
|
sendResponse(SquadDefinitionActionMessage(sguid, 0, SquadAction.IdentifyAsSquadLeader()))
|
||||||
|
sendResponse(SquadDefinitionActionMessage(sguid, 0, SquadAction.Unknown(18)))
|
||||||
|
} else if (secondCharId == charId) {
|
||||||
|
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.IdentifyAsSquadLeader()))
|
||||||
|
sendResponse(PlanetsideAttributeMessage(pguid, 32, fromIndex))
|
||||||
|
sendResponse(SquadDefinitionActionMessage(sguid, 0, SquadAction.Unknown(18)))
|
||||||
|
}
|
||||||
|
//seed updates (just for the swapped players)
|
||||||
|
sendResponse(
|
||||||
|
SquadState(PlanetSideGUID(id), List(
|
||||||
|
SquadStateInfo(firstCharId, newFirstElem.health, newFirstElem.armor, newFirstElem.position),
|
||||||
|
SquadStateInfo(secondCharId, newSecondElem.health, newSecondElem.armor, newSecondElem.position)
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def NoSquadUpdates(): Unit = { }
|
||||||
|
|
||||||
def SquadUpdates(): Unit = {
|
def SquadUpdates(): Unit = {
|
||||||
squadService ! SquadServiceMessage(
|
updateSquadRef ! SquadServiceMessage(
|
||||||
player,
|
player,
|
||||||
continent,
|
continent,
|
||||||
SquadServiceAction.Update(
|
SquadServiceAction.Update(
|
||||||
player.CharId,
|
player.CharId,
|
||||||
|
player.GUID,
|
||||||
player.Health,
|
player.Health,
|
||||||
player.MaxHealth,
|
player.MaxHealth,
|
||||||
player.Armor,
|
player.Armor,
|
||||||
player.MaxArmor,
|
player.MaxArmor,
|
||||||
|
avatar.certifications,
|
||||||
player.Position,
|
player.Position,
|
||||||
continent.Number
|
continent.Number
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
package net.psforever.objects.teamwork
|
package net.psforever.objects.teamwork
|
||||||
|
|
||||||
import net.psforever.objects.avatar.Certification
|
import net.psforever.objects.avatar.Certification
|
||||||
import net.psforever.types.Vector3
|
import net.psforever.types.{PlanetSideGUID, Vector3}
|
||||||
|
|
||||||
class Member {
|
class Member {
|
||||||
//about the position to be filled
|
//about the position to be filled
|
||||||
|
|
@ -12,10 +12,12 @@ class Member {
|
||||||
//about the individual filling the position
|
//about the individual filling the position
|
||||||
private var name: String = ""
|
private var name: String = ""
|
||||||
private var charId: Long = 0L
|
private var charId: Long = 0L
|
||||||
|
private var guid: Int = 0
|
||||||
private var health: Int = 0
|
private var health: Int = 0
|
||||||
private var armor: Int = 0
|
private var armor: Int = 0
|
||||||
private var zoneId: Int = 0
|
private var zoneId: Int = 0
|
||||||
private var position: Vector3 = Vector3.Zero
|
private var position: Vector3 = Vector3.Zero
|
||||||
|
private var certs: Set[Certification] = Set()
|
||||||
|
|
||||||
def Role: String = role
|
def Role: String = role
|
||||||
|
|
||||||
|
|
@ -52,6 +54,17 @@ class Member {
|
||||||
CharId
|
CharId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def GUID: PlanetSideGUID = PlanetSideGUID(guid)
|
||||||
|
|
||||||
|
def GUID_=(guid: PlanetSideGUID): PlanetSideGUID = {
|
||||||
|
GUID_=(guid.guid)
|
||||||
|
}
|
||||||
|
|
||||||
|
def GUID_=(thisGuid: Int): PlanetSideGUID = {
|
||||||
|
guid = thisGuid
|
||||||
|
GUID
|
||||||
|
}
|
||||||
|
|
||||||
def Health: Int = health
|
def Health: Int = health
|
||||||
|
|
||||||
def Health_=(red: Int): Int = {
|
def Health_=(red: Int): Int = {
|
||||||
|
|
@ -80,6 +93,13 @@ class Member {
|
||||||
Position
|
Position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def Certifications: Set[Certification] = certs
|
||||||
|
|
||||||
|
def Certifications_=(req: Set[Certification]): Set[Certification] = {
|
||||||
|
certs = req
|
||||||
|
Certifications
|
||||||
|
}
|
||||||
|
|
||||||
def isAvailable: Boolean = {
|
def isAvailable: Boolean = {
|
||||||
charId == 0
|
charId == 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@
|
||||||
package net.psforever.objects.teamwork
|
package net.psforever.objects.teamwork
|
||||||
|
|
||||||
import akka.actor.{ActorContext, ActorRef, Props}
|
import akka.actor.{ActorContext, ActorRef, Props}
|
||||||
import net.psforever.types.SquadWaypoint
|
import net.psforever.objects.Default
|
||||||
import net.psforever.services.teamwork.SquadService.WaypointData
|
import net.psforever.packet.game.WaypointInfo
|
||||||
import net.psforever.services.teamwork.SquadSwitchboard
|
import net.psforever.types.{PlanetSideGUID, SquadWaypoint, Vector3}
|
||||||
|
import net.psforever.services.teamwork.{SquadSubscriptionEntity, SquadSwitchboard}
|
||||||
|
|
||||||
class SquadFeatures(val Squad: Squad) {
|
class SquadFeatures(val Squad: Squad) {
|
||||||
|
|
||||||
|
|
@ -40,7 +41,11 @@ class SquadFeatures(val Squad: Squad) {
|
||||||
* Waypoints manifest in the game world as a (usually far-off) beam of light that extends into the sky
|
* Waypoints manifest in the game world as a (usually far-off) beam of light that extends into the sky
|
||||||
* and whose ground contact utilizes a downwards pulsating arrow.
|
* and whose ground contact utilizes a downwards pulsating arrow.
|
||||||
* On the continental map and deployment map, they appear as a diamond, with a different number where applicable.
|
* On the continental map and deployment map, they appear as a diamond, with a different number where applicable.
|
||||||
* The squad leader experience rally, for example, does not have a number like the preceding four waypoints.
|
* The squad leader experience rally, for example, does not have a number like the preceding four waypoints.<br>
|
||||||
|
* <br>
|
||||||
|
* Laze waypoints are as numerous as the number of players in a squad and
|
||||||
|
* exist only for fifteen seconds at a time.
|
||||||
|
* They are not counted in this list.
|
||||||
* @see `Start`
|
* @see `Start`
|
||||||
*/
|
*/
|
||||||
private var waypoints: Array[WaypointData] = Array[WaypointData]()
|
private var waypoints: Array[WaypointData] = Array[WaypointData]()
|
||||||
|
|
@ -65,25 +70,25 @@ class SquadFeatures(val Squad: Squad) {
|
||||||
* For the purposes of wide-searches for membership
|
* For the purposes of wide-searches for membership
|
||||||
* such as Looking For Squad checks and proximity invitation,
|
* such as Looking For Squad checks and proximity invitation,
|
||||||
* the unique character identifier numbers in this list are skipped.
|
* the unique character identifier numbers in this list are skipped.
|
||||||
* Direct invitation requests from the non sqad member should remain functional.
|
* Direct invitation requests from the non squad member should remain functional.
|
||||||
*/
|
*/
|
||||||
private var refusedPlayers: List[Long] = Nil
|
private var refusedPlayers: List[Long] = Nil
|
||||||
private var autoApproveInvitationRequests: Boolean = false
|
private var autoApproveInvitationRequests: Boolean = false
|
||||||
private var locationFollowsSquadLead: Boolean = true
|
private var locationFollowsSquadLead: Boolean = true //TODO false
|
||||||
|
|
||||||
private var listed: Boolean = false
|
private var listed: Boolean = false
|
||||||
|
|
||||||
private lazy val channel: String = s"${Squad.Faction}-Squad${Squad.GUID.guid}"
|
private lazy val channel: String = s"${Squad.Faction}-Squad${Squad.GUID.guid}"
|
||||||
|
|
||||||
def Start(implicit context: ActorContext): SquadFeatures = {
|
def Start(implicit context: ActorContext, subs: SquadSubscriptionEntity): SquadFeatures = {
|
||||||
switchboard = context.actorOf(Props[SquadSwitchboard](), s"squad_${Squad.GUID.guid}_${System.currentTimeMillis}")
|
switchboard = context.actorOf(Props(classOf[SquadSwitchboard], this, subs), s"squad_${Squad.GUID.guid}_${System.currentTimeMillis}")
|
||||||
waypoints = Array.fill[WaypointData](SquadWaypoint.values.size)(new WaypointData())
|
waypoints = Array.fill[WaypointData](SquadWaypoint.values.size)(new WaypointData())
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
def Stop: SquadFeatures = {
|
def Stop: SquadFeatures = {
|
||||||
switchboard ! akka.actor.PoisonPill
|
switchboard ! akka.actor.PoisonPill
|
||||||
switchboard = ActorRef.noSender
|
switchboard = Default.Actor
|
||||||
waypoints = Array.empty
|
waypoints = Array.empty
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
@ -99,6 +104,58 @@ class SquadFeatures(val Squad: Squad) {
|
||||||
|
|
||||||
def Waypoints: Array[WaypointData] = waypoints
|
def Waypoints: Array[WaypointData] = waypoints
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the indicated waypoint.<br>
|
||||||
|
* <br>
|
||||||
|
* Despite the name, no waypoints are actually "added."
|
||||||
|
* All of the waypoints constantly exist as long as the squad to which they are attached exists.
|
||||||
|
* They are merely "activated" and "deactivated."
|
||||||
|
* No waypoint is ever remembered for the laze-indicated target.
|
||||||
|
* @see `SquadWaypointRequest`
|
||||||
|
* @see `WaypointInfo`
|
||||||
|
* @param guid the squad's unique identifier
|
||||||
|
* @param waypointType the type of the waypoint
|
||||||
|
* @param info information about the waypoint, as was reported by the client's packet
|
||||||
|
* @return the waypoint data, if the waypoint type is changed
|
||||||
|
*/
|
||||||
|
def AddWaypoint(
|
||||||
|
guid: PlanetSideGUID,
|
||||||
|
waypointType: SquadWaypoint,
|
||||||
|
info: WaypointInfo
|
||||||
|
): Option[WaypointData] = {
|
||||||
|
waypoints.lift(waypointType.value) match {
|
||||||
|
case Some(point) =>
|
||||||
|
point.zone_number = info.zone_number
|
||||||
|
point.pos = info.pos
|
||||||
|
Some(point)
|
||||||
|
case None =>
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the indicated waypoint.
|
||||||
|
* Unused waypoints are marked by having a non-zero z-coordinate.<br>
|
||||||
|
* <br>
|
||||||
|
* Despite the name, no waypoints are actually "removed."
|
||||||
|
* All of the waypoints constantly exist as long as the squad to which they are attached exists.
|
||||||
|
* They are merely "activated" and "deactivated."
|
||||||
|
* @param guid the squad's unique identifier
|
||||||
|
* @param waypointType the type of the waypoint
|
||||||
|
*/
|
||||||
|
def RemoveWaypoint(guid: PlanetSideGUID, waypointType: SquadWaypoint): Option[WaypointData] = {
|
||||||
|
waypoints.lift(waypointType.value) match {
|
||||||
|
case Some(point) =>
|
||||||
|
val oldWaypoint = WaypointData(point.zone_number, point.pos)
|
||||||
|
point.pos = Vector3.z(1)
|
||||||
|
Some(oldWaypoint)
|
||||||
|
case None =>
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def SearchForRole: Option[Int] = searchForRole
|
def SearchForRole: Option[Int] = searchForRole
|
||||||
|
|
||||||
def SearchForRole_=(role: Int): Option[Int] = SearchForRole_=(Some(role))
|
def SearchForRole_=(role: Int): Option[Int] = SearchForRole_=(Some(role))
|
||||||
|
|
@ -115,15 +172,24 @@ class SquadFeatures(val Squad: Squad) {
|
||||||
ProxyInvites
|
ProxyInvites
|
||||||
}
|
}
|
||||||
|
|
||||||
def Refuse: List[Long] = refusedPlayers
|
def DeniedPlayers(): List[Long] = refusedPlayers
|
||||||
|
|
||||||
def Refuse_=(charId: Long): List[Long] = {
|
def DeniedPlayers(charId: Long): List[Long] = {
|
||||||
Refuse_=(List(charId))
|
DeniedPlayers(List(charId))
|
||||||
}
|
}
|
||||||
|
|
||||||
def Refuse_=(list: List[Long]): List[Long] = {
|
def DeniedPlayers(list: List[Long]): List[Long] = {
|
||||||
refusedPlayers = list ++ refusedPlayers
|
refusedPlayers = list ++ refusedPlayers
|
||||||
Refuse
|
DeniedPlayers()
|
||||||
|
}
|
||||||
|
|
||||||
|
def AllowedPlayers(charId: Long): List[Long] = {
|
||||||
|
AllowedPlayers(List(charId))
|
||||||
|
}
|
||||||
|
|
||||||
|
def AllowedPlayers(list: List[Long]): List[Long] = {
|
||||||
|
refusedPlayers = refusedPlayers.filterNot(list.contains)
|
||||||
|
DeniedPlayers()
|
||||||
}
|
}
|
||||||
|
|
||||||
def LocationFollowsSquadLead: Boolean = locationFollowsSquadLead
|
def LocationFollowsSquadLead: Boolean = locationFollowsSquadLead
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright (c) 2022 PSForever
|
||||||
|
package net.psforever.objects.teamwork
|
||||||
|
|
||||||
|
import net.psforever.types.Vector3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information necessary to display a specific map marker.
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
object WaypointData{
|
||||||
|
def apply(zone_number: Int, pos: Vector3): WaypointData = {
|
||||||
|
val data = new WaypointData()
|
||||||
|
data.zone_number = zone_number
|
||||||
|
data.pos = pos
|
||||||
|
data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,10 +10,10 @@ import shapeless.{::, HNil}
|
||||||
|
|
||||||
final case class CharacterKnowledgeInfo(
|
final case class CharacterKnowledgeInfo(
|
||||||
name: String,
|
name: String,
|
||||||
permissions: Set[Certification],
|
certifications: Set[Certification],
|
||||||
unk1: Int,
|
unk1: Int,
|
||||||
unk2: Int,
|
unk2: Int,
|
||||||
unk3: PlanetSideGUID
|
zoneNumber: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
final case class CharacterKnowledgeMessage(char_id: Long, info: Option[CharacterKnowledgeInfo])
|
final case class CharacterKnowledgeMessage(char_id: Long, info: Option[CharacterKnowledgeInfo])
|
||||||
|
|
@ -30,25 +30,22 @@ object CharacterKnowledgeMessage extends Marshallable[CharacterKnowledgeMessage]
|
||||||
def apply(char_id: Long, info: CharacterKnowledgeInfo): CharacterKnowledgeMessage =
|
def apply(char_id: Long, info: CharacterKnowledgeInfo): CharacterKnowledgeMessage =
|
||||||
CharacterKnowledgeMessage(char_id, Some(info))
|
CharacterKnowledgeMessage(char_id, Some(info))
|
||||||
|
|
||||||
private val inverter: Codec[Boolean] = bool.xmap[Boolean](
|
private val inverter: Codec[Boolean] = bool.xmap[Boolean](state => !state, state => !state)
|
||||||
state => !state,
|
|
||||||
state => !state
|
|
||||||
)
|
|
||||||
|
|
||||||
private val info_codec: Codec[CharacterKnowledgeInfo] = (
|
private val info_codec: Codec[CharacterKnowledgeInfo] = (
|
||||||
("name" | PacketHelpers.encodedWideStringAligned(adjustment = 7)) ::
|
("name" | PacketHelpers.encodedWideStringAligned(adjustment = 7)) ::
|
||||||
("permissions" | ulongL(bits = 46)) ::
|
("certifications" | ulongL(bits = 46)) ::
|
||||||
("unk1" | uint(bits = 6)) ::
|
("unk1" | uint(bits = 6)) ::
|
||||||
("unk2" | uint(bits = 3)) ::
|
("unk2" | uint(bits = 3)) ::
|
||||||
("unk3" | PlanetSideGUID.codec)
|
("zone" | uint16L)
|
||||||
).xmap[CharacterKnowledgeInfo](
|
).xmap[CharacterKnowledgeInfo](
|
||||||
{
|
{
|
||||||
case name :: permissions :: u1 :: u2 :: u3 :: HNil =>
|
case name :: certs :: u1 :: u2 :: zone :: HNil =>
|
||||||
CharacterKnowledgeInfo(name, Certification.fromEncodedLong(permissions), u1, u2, u3)
|
CharacterKnowledgeInfo(name, Certification.fromEncodedLong(certs), u1, u2, zone)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case CharacterKnowledgeInfo(name, permissions, u1, u2, u3) =>
|
case CharacterKnowledgeInfo(name, certs, u1, u2, zone) =>
|
||||||
name :: Certification.toEncodedLong(permissions) :: u1 :: u2 :: u3 :: HNil
|
name :: Certification.toEncodedLong(certs) :: u1 :: u2 :: zone :: HNil
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,8 +65,8 @@ final case class SquadInfo(
|
||||||
this And SquadInfo(None, None, Some(zone), None, None, None)
|
this And SquadInfo(None, None, Some(zone), None, None, None)
|
||||||
def ZoneId(zone: Option[PlanetSideZoneID]): SquadInfo =
|
def ZoneId(zone: Option[PlanetSideZoneID]): SquadInfo =
|
||||||
zone match {
|
zone match {
|
||||||
case Some(zoneId) => this And SquadInfo(None, None, zone, None, None, None)
|
case Some(_) => this And SquadInfo(None, None, zone, None, None, None)
|
||||||
case None => SquadInfo(leader, task, zone, size, capacity, squad_guid)
|
case None => SquadInfo(leader, task, zone, size, capacity, squad_guid)
|
||||||
}
|
}
|
||||||
def Size(sz: Int): SquadInfo =
|
def Size(sz: Int): SquadInfo =
|
||||||
this And SquadInfo(None, None, None, Some(sz), None, None)
|
this And SquadInfo(None, None, None, Some(sz), None, None)
|
||||||
|
|
@ -612,8 +612,8 @@ object SquadHeader {
|
||||||
(bool >>:~ { unk1 =>
|
(bool >>:~ { unk1 =>
|
||||||
uint8 >>:~ { unk2 =>
|
uint8 >>:~ { unk2 =>
|
||||||
conditional(!unk1 && unk2 == 1, removeCodec) ::
|
conditional(!unk1 && unk2 == 1, removeCodec) ::
|
||||||
conditional(unk1 && unk2 == 6, providedCodec) ::
|
conditional(unk1 && unk2 == 6, providedCodec) ::
|
||||||
conditional(unk1 && unk2 != 6, listing_codec(unk2))
|
conditional(unk1 && unk2 != 6, listing_codec(unk2))
|
||||||
}
|
}
|
||||||
}).exmap[Option[SquadInfo]](
|
}).exmap[Option[SquadInfo]](
|
||||||
{
|
{
|
||||||
|
|
@ -691,10 +691,8 @@ object SquadListing {
|
||||||
private def meta_codec(entryFunc: Int => Codec[Option[SquadInfo]]): Codec[SquadListing] =
|
private def meta_codec(entryFunc: Int => Codec[Option[SquadInfo]]): Codec[SquadListing] =
|
||||||
(("index" | uint8L) >>:~ { index =>
|
(("index" | uint8L) >>:~ { index =>
|
||||||
conditional(index < 255, "listing" | entryFunc(index)) ::
|
conditional(index < 255, "listing" | entryFunc(index)) ::
|
||||||
conditional(
|
conditional(index == 255, bits)
|
||||||
index == 255,
|
//consume n < 8 bits after the tail entry, else vector will try to operate on invalid data
|
||||||
bits
|
|
||||||
) //consume n < 8 bits after the tail entry, else vector will try to operate on invalid data
|
|
||||||
}).xmap[SquadListing](
|
}).xmap[SquadListing](
|
||||||
{
|
{
|
||||||
case ndx :: Some(lstng) :: _ :: HNil =>
|
case ndx :: Some(lstng) :: _ :: HNil =>
|
||||||
|
|
@ -717,7 +715,7 @@ object SquadListing {
|
||||||
* `Codec` for branching types of `SquadListing` initializations.
|
* `Codec` for branching types of `SquadListing` initializations.
|
||||||
*/
|
*/
|
||||||
val info_codec: Codec[SquadListing] = meta_codec({ index: Int =>
|
val info_codec: Codec[SquadListing] = meta_codec({ index: Int =>
|
||||||
newcodecs.binary_choice(index == 0, "listing" | SquadHeader.info_codec, "listing" | SquadHeader.alt_info_codec)
|
newcodecs.binary_choice(index == 0, SquadHeader.info_codec, SquadHeader.alt_info_codec)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -738,14 +736,15 @@ object ReplicationStreamMessage extends Marshallable[ReplicationStreamMessage] {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit val codec: Codec[ReplicationStreamMessage] = (("behavior" | uintL(3)) >>:~ { behavior =>
|
implicit val codec: Codec[ReplicationStreamMessage] = (
|
||||||
conditional(behavior == 5, "behavior2" | uintL(3)) ::
|
("behavior" | uintL(bits = 3)) >>:~ { behavior =>
|
||||||
|
("behavior2" | conditional(behavior == 5, uintL(bits = 3))) ::
|
||||||
conditional(behavior != 1, bool) ::
|
conditional(behavior != 1, bool) ::
|
||||||
newcodecs.binary_choice(
|
("entries" | newcodecs.binary_choice(
|
||||||
behavior != 5,
|
behavior != 5,
|
||||||
"entries" | vector(SquadListing.codec),
|
vector(SquadListing.codec),
|
||||||
"entries" | vector(SquadListing.info_codec)
|
vector(SquadListing.info_codec)
|
||||||
)
|
))
|
||||||
}).xmap[ReplicationStreamMessage](
|
}).xmap[ReplicationStreamMessage](
|
||||||
{
|
{
|
||||||
case bhvr :: bhvr2 :: _ :: lst :: HNil =>
|
case bhvr :: bhvr2 :: _ :: lst :: HNil =>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package net.psforever.packet.game
|
||||||
|
|
||||||
import net.psforever.objects.avatar.Certification
|
import net.psforever.objects.avatar.Certification
|
||||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||||
import net.psforever.types.PlanetSideGUID
|
import net.psforever.types.{PlanetSideGUID, SquadListDecoration}
|
||||||
import scodec.bits.BitVector
|
import scodec.bits.BitVector
|
||||||
import scodec.{Attempt, Codec, Err}
|
import scodec.{Attempt, Codec, Err}
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
|
|
@ -25,71 +25,73 @@ object SquadAction {
|
||||||
implicit val codec: Codec[SearchMode.Value] = PacketHelpers.createEnumerationCodec(enum = this, uint(bits = 3))
|
implicit val codec: Codec[SearchMode.Value] = PacketHelpers.createEnumerationCodec(enum = this, uint(bits = 3))
|
||||||
}
|
}
|
||||||
|
|
||||||
final case class DisplaySquad() extends SquadAction(0)
|
final case class DisplaySquad() extends SquadAction(code = 0)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatched from client to server to indicate a squad detail update that has no foundation entry to update?
|
* Dispatched from client to server to indicate a squad detail update that has no foundation entry to update?
|
||||||
* Not dissimilar from `DisplaySquad`.
|
* Not dissimilar from `DisplaySquad`.
|
||||||
*/
|
*/
|
||||||
final case class SquadMemberInitializationIssue() extends SquadAction(1)
|
final case class SquadInitializationIssue() extends SquadAction(code = 1)
|
||||||
|
|
||||||
final case class SaveSquadFavorite() extends SquadAction(3)
|
final case class SaveSquadFavorite() extends SquadAction(code = 3)
|
||||||
|
|
||||||
final case class LoadSquadFavorite() extends SquadAction(4)
|
final case class LoadSquadFavorite() extends SquadAction(code = 4)
|
||||||
|
|
||||||
final case class DeleteSquadFavorite() extends SquadAction(5)
|
final case class DeleteSquadFavorite() extends SquadAction(code = 5)
|
||||||
|
|
||||||
final case class ListSquadFavorite(name: String) extends SquadAction(7)
|
final case class ListSquadFavorite(name: String) extends SquadAction(code = 7)
|
||||||
|
|
||||||
final case class RequestListSquad() extends SquadAction(8)
|
final case class RequestListSquad() extends SquadAction(code = 8)
|
||||||
|
|
||||||
final case class StopListSquad() extends SquadAction(9)
|
final case class StopListSquad() extends SquadAction(code = 9)
|
||||||
|
|
||||||
final case class SelectRoleForYourself(state: Int) extends SquadAction(10)
|
final case class SelectRoleForYourself(state: Int) extends SquadAction(code = 10)
|
||||||
|
|
||||||
final case class CancelSelectRoleForYourself(value: Long = 0) extends SquadAction(15)
|
final case class CancelSelectRoleForYourself(value: Long = 0) extends SquadAction(code = 15)
|
||||||
|
|
||||||
final case class AssociateWithSquad() extends SquadAction(16)
|
final case class IdentifyAsSquadLeader() extends SquadAction(code = 16)
|
||||||
|
|
||||||
final case class SetListSquad() extends SquadAction(17)
|
final case class SetListSquad() extends SquadAction(code = 17)
|
||||||
|
|
||||||
final case class ChangeSquadPurpose(purpose: String) extends SquadAction(19)
|
final case class ChangeSquadPurpose(purpose: String) extends SquadAction(code = 19)
|
||||||
|
|
||||||
final case class ChangeSquadZone(zone: PlanetSideZoneID) extends SquadAction(20)
|
final case class ChangeSquadZone(zone: PlanetSideZoneID) extends SquadAction(code = 20)
|
||||||
|
|
||||||
final case class CloseSquadMemberPosition(position: Int) extends SquadAction(21)
|
final case class CloseSquadMemberPosition(position: Int) extends SquadAction(code = 21)
|
||||||
|
|
||||||
final case class AddSquadMemberPosition(position: Int) extends SquadAction(22)
|
final case class AddSquadMemberPosition(position: Int) extends SquadAction(code = 22)
|
||||||
|
|
||||||
final case class ChangeSquadMemberRequirementsRole(u1: Int, role: String) extends SquadAction(23)
|
final case class ChangeSquadMemberRequirementsRole(u1: Int, role: String) extends SquadAction(code = 23)
|
||||||
|
|
||||||
final case class ChangeSquadMemberRequirementsDetailedOrders(u1: Int, orders: String) extends SquadAction(24)
|
final case class ChangeSquadMemberRequirementsDetailedOrders(u1: Int, orders: String) extends SquadAction(code = 24)
|
||||||
|
|
||||||
final case class ChangeSquadMemberRequirementsCertifications(u1: Int, certs: Set[Certification])
|
final case class ChangeSquadMemberRequirementsCertifications(u1: Int, certs: Set[Certification])
|
||||||
extends SquadAction(25)
|
extends SquadAction(code = 25)
|
||||||
|
|
||||||
final case class ResetAll() extends SquadAction(26)
|
final case class ResetAll() extends SquadAction(code = 26)
|
||||||
|
|
||||||
final case class AutoApproveInvitationRequests(state: Boolean) extends SquadAction(28)
|
final case class AutoApproveInvitationRequests(state: Boolean) extends SquadAction(code = 28)
|
||||||
|
|
||||||
final case class LocationFollowsSquadLead(state: Boolean) extends SquadAction(31)
|
final case class LocationFollowsSquadLead(state: Boolean) extends SquadAction(code = 31)
|
||||||
|
|
||||||
|
final case class SquadListDecorator(state: SquadListDecoration.Value) extends SquadAction(code = 33)
|
||||||
|
|
||||||
final case class SearchForSquadsWithParticularRole(
|
final case class SearchForSquadsWithParticularRole(
|
||||||
role: String,
|
role: String,
|
||||||
requirements: Set[Certification],
|
requirements: Set[Certification],
|
||||||
zone_id: Int,
|
zone_id: Int,
|
||||||
mode: SearchMode.Value
|
mode: SearchMode.Value
|
||||||
) extends SquadAction(34)
|
) extends SquadAction(code = 34)
|
||||||
|
|
||||||
final case class CancelSquadSearch() extends SquadAction(35)
|
final case class CancelSquadSearch() extends SquadAction(code = 35)
|
||||||
|
|
||||||
final case class AssignSquadMemberToRole(position: Int, char_id: Long) extends SquadAction(38)
|
final case class AssignSquadMemberToRole(position: Int, char_id: Long) extends SquadAction(code = 38)
|
||||||
|
|
||||||
final case class NoSquadSearchResults() extends SquadAction(39)
|
final case class NoSquadSearchResults() extends SquadAction(code = 39)
|
||||||
|
|
||||||
final case class FindLfsSoldiersForRole(state: Int) extends SquadAction(40)
|
final case class FindLfsSoldiersForRole(state: Int) extends SquadAction(code = 40)
|
||||||
|
|
||||||
final case class CancelFind() extends SquadAction(41)
|
final case class CancelFind() extends SquadAction(code = 41)
|
||||||
|
|
||||||
final case class Unknown(badCode: Int, data: BitVector) extends SquadAction(badCode)
|
final case class Unknown(badCode: Int, data: BitVector) extends SquadAction(badCode)
|
||||||
|
|
||||||
|
|
@ -114,10 +116,10 @@ object SquadAction {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
val squadMemberInitializationIssueCodec = everFailCondition.xmap[SquadMemberInitializationIssue](
|
val squadMemberInitializationIssueCodec = everFailCondition.xmap[SquadInitializationIssue](
|
||||||
_ => SquadMemberInitializationIssue(),
|
_ => SquadInitializationIssue(),
|
||||||
{
|
{
|
||||||
case SquadMemberInitializationIssue() => None
|
case SquadInitializationIssue() => None
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -179,10 +181,10 @@ object SquadAction {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
val associateWithSquadCodec = everFailCondition.xmap[AssociateWithSquad](
|
val identifyAsSquadLeaderCodec = everFailCondition.xmap[IdentifyAsSquadLeader](
|
||||||
_ => AssociateWithSquad(),
|
_ => IdentifyAsSquadLeader(),
|
||||||
{
|
{
|
||||||
case AssociateWithSquad() => None
|
case IdentifyAsSquadLeader() => None
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -276,6 +278,18 @@ object SquadAction {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val squadListDecoratorCodec = (
|
||||||
|
SquadListDecoration.codec ::
|
||||||
|
ignore(size = 3)
|
||||||
|
).xmap[SquadListDecorator](
|
||||||
|
{
|
||||||
|
case value :: _ :: HNil => SquadListDecorator(value)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case SquadListDecorator(value) => value :: () :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
val searchForSquadsWithParticularRoleCodec = (PacketHelpers.encodedWideStringAligned(6) ::
|
val searchForSquadsWithParticularRoleCodec = (PacketHelpers.encodedWideStringAligned(6) ::
|
||||||
ulongL(46) ::
|
ulongL(46) ::
|
||||||
uint16L ::
|
uint16L ::
|
||||||
|
|
@ -389,7 +403,7 @@ object SquadAction {
|
||||||
* `20` - (Squad leader) Change Squad Zone<br>
|
* `20` - (Squad leader) Change Squad Zone<br>
|
||||||
* `21` - (Squad leader) Close Squad Member Position<br>
|
* `21` - (Squad leader) Close Squad Member Position<br>
|
||||||
* `22` - (Squad leader) Add Squad Member Position<br>
|
* `22` - (Squad leader) Add Squad Member Position<br>
|
||||||
* `33` - UNKNOWN<br>
|
* `33` - Decorate a Squad in the List of Squads with Color<br>
|
||||||
* `40` - Find LFS Soldiers that Meet the Requirements for this Role<br>
|
* `40` - Find LFS Soldiers that Meet the Requirements for this Role<br>
|
||||||
* `Long`<br>
|
* `Long`<br>
|
||||||
* `13` - UNKNOWN<br>
|
* `13` - UNKNOWN<br>
|
||||||
|
|
@ -451,7 +465,7 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
|
||||||
case 13 => unknownCodec(action = 13)
|
case 13 => unknownCodec(action = 13)
|
||||||
case 14 => unknownCodec(action = 14)
|
case 14 => unknownCodec(action = 14)
|
||||||
case 15 => cancelSelectRoleForYourselfCodec
|
case 15 => cancelSelectRoleForYourselfCodec
|
||||||
case 16 => associateWithSquadCodec
|
case 16 => identifyAsSquadLeaderCodec
|
||||||
case 17 => setListSquadCodec
|
case 17 => setListSquadCodec
|
||||||
case 18 => unknownCodec(action = 18)
|
case 18 => unknownCodec(action = 18)
|
||||||
case 19 => changeSquadPurposeCodec
|
case 19 => changeSquadPurposeCodec
|
||||||
|
|
@ -468,7 +482,7 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
|
||||||
case 30 => unknownCodec(action = 30)
|
case 30 => unknownCodec(action = 30)
|
||||||
case 31 => locationFollowsSquadLeadCodec
|
case 31 => locationFollowsSquadLeadCodec
|
||||||
case 32 => unknownCodec(action = 32)
|
case 32 => unknownCodec(action = 32)
|
||||||
case 33 => unknownCodec(action = 33)
|
case 33 => squadListDecoratorCodec
|
||||||
case 34 => searchForSquadsWithParticularRoleCodec
|
case 34 => searchForSquadsWithParticularRoleCodec
|
||||||
case 35 => cancelSquadSearchCodec
|
case 35 => cancelSquadSearchCodec
|
||||||
case 36 => unknownCodec(action = 36)
|
case 36 => unknownCodec(action = 36)
|
||||||
|
|
|
||||||
|
|
@ -143,12 +143,12 @@ final case class SquadPositionEntry(index: Int, info: Option[SquadPositionDetail
|
||||||
* All fields are optional for that reason.<br>
|
* All fields are optional for that reason.<br>
|
||||||
* <br>
|
* <br>
|
||||||
* The squad leader does not necessarily have to be a person from the `member_info` list.
|
* The squad leader does not necessarily have to be a person from the `member_info` list.
|
||||||
* @param unk1 na;
|
* @param guid na;
|
||||||
* must be non-zero when parsed in a FullSquad pattern
|
* must be non-zero when parsed in a FullSquad pattern
|
||||||
* @param unk2 na;
|
* @param unk2 na;
|
||||||
* not associated with any fields during itemized parsing
|
* not associated with any fields during itemized parsing
|
||||||
* @param leader_char_id he unique character identification number for the squad leader
|
* @param leader_char_id he unique character identification number for the squad leader
|
||||||
* @param unk3 na
|
* @param outfit_id na
|
||||||
* @param leader_name the name of the player who is the squad leader
|
* @param leader_name the name of the player who is the squad leader
|
||||||
* @param task the suggested responsibilities or mission statement of the squad
|
* @param task the suggested responsibilities or mission statement of the squad
|
||||||
* @param zone_id the suggested area of engagement for this squad's activities;
|
* @param zone_id the suggested area of engagement for this squad's activities;
|
||||||
|
|
@ -157,10 +157,10 @@ final case class SquadPositionEntry(index: Int, info: Option[SquadPositionDetail
|
||||||
* @param member_info a list of squad position data
|
* @param member_info a list of squad position data
|
||||||
*/
|
*/
|
||||||
final case class SquadDetail(
|
final case class SquadDetail(
|
||||||
unk1: Option[Int],
|
guid: Option[Int],
|
||||||
unk2: Option[Int],
|
unk2: Option[Int],
|
||||||
leader_char_id: Option[Long],
|
leader_char_id: Option[Long],
|
||||||
unk3: Option[Long],
|
outfit_id: Option[Long],
|
||||||
leader_name: Option[String],
|
leader_name: Option[String],
|
||||||
task: Option[String],
|
task: Option[String],
|
||||||
zone_id: Option[PlanetSideZoneID],
|
zone_id: Option[PlanetSideZoneID],
|
||||||
|
|
@ -176,10 +176,10 @@ final case class SquadDetail(
|
||||||
*/
|
*/
|
||||||
def And(info: SquadDetail): SquadDetail = {
|
def And(info: SquadDetail): SquadDetail = {
|
||||||
SquadDetail(
|
SquadDetail(
|
||||||
unk1.orElse(info.unk1),
|
guid.orElse(info.guid),
|
||||||
unk2.orElse(info.unk2),
|
unk2.orElse(info.unk2),
|
||||||
leader_char_id.orElse(info.leader_char_id),
|
leader_char_id.orElse(info.leader_char_id),
|
||||||
unk3.orElse(info.unk3),
|
outfit_id.orElse(info.outfit_id),
|
||||||
leader_name.orElse(info.leader_name),
|
leader_name.orElse(info.leader_name),
|
||||||
task.orElse(info.task),
|
task.orElse(info.task),
|
||||||
zone_id.orElse(info.zone_id),
|
zone_id.orElse(info.zone_id),
|
||||||
|
|
@ -202,12 +202,14 @@ final case class SquadDetail(
|
||||||
}
|
}
|
||||||
|
|
||||||
//methods intended to combine the fields of itself and another object
|
//methods intended to combine the fields of itself and another object
|
||||||
def Field1(value: Int): SquadDetail =
|
def Guid(guid: Int): SquadDetail =
|
||||||
this And SquadDetail(Some(value), None, None, None, None, None, None, None, None)
|
this And SquadDetail(Some(guid), None, None, None, None, None, None, None, None)
|
||||||
|
def Field2(value: Int): SquadDetail =
|
||||||
|
this And SquadDetail(None, Some(value), None, None, None, None, None, None, None)
|
||||||
def LeaderCharId(char_id: Long): SquadDetail =
|
def LeaderCharId(char_id: Long): SquadDetail =
|
||||||
this And SquadDetail(None, None, Some(char_id), None, None, None, None, None, None)
|
this And SquadDetail(None, None, Some(char_id), None, None, None, None, None, None)
|
||||||
def Field3(value: Long): SquadDetail =
|
def OutfitId(outfit: Long): SquadDetail =
|
||||||
this And SquadDetail(None, None, None, Some(value), None, None, None, None, None)
|
this And SquadDetail(None, None, None, Some(outfit), None, None, None, None, None)
|
||||||
def LeaderName(name: String): SquadDetail =
|
def LeaderName(name: String): SquadDetail =
|
||||||
this And SquadDetail(None, None, None, None, Some(name), None, None, None, None)
|
this And SquadDetail(None, None, None, None, Some(name), None, None, None, None)
|
||||||
def Leader(char_id: Long, name: String): SquadDetail =
|
def Leader(char_id: Long, name: String): SquadDetail =
|
||||||
|
|
@ -228,14 +230,14 @@ final case class SquadDetail(
|
||||||
*/
|
*/
|
||||||
def Complete: SquadDetail =
|
def Complete: SquadDetail =
|
||||||
SquadDetail(
|
SquadDetail(
|
||||||
unk1.orElse(Some(1)),
|
guid.orElse(Some(1)),
|
||||||
unk2.orElse(Some(0)),
|
unk2.orElse(Some(0)),
|
||||||
leader_char_id.orElse(Some(0L)),
|
leader_char_id.orElse(Some(0L)),
|
||||||
unk3.orElse(Some(0L)),
|
outfit_id.orElse(Some(0L)),
|
||||||
leader_name.orElse(Some("")),
|
leader_name.orElse(Some("")),
|
||||||
task.orElse(Some("")),
|
task.orElse(Some("")),
|
||||||
zone_id.orElse(Some(PlanetSideZoneID(0))),
|
zone_id.orElse(Some(PlanetSideZoneID(0))),
|
||||||
unk7.orElse(Some(4983296)), //FullSquad value
|
unk7.orElse(Some(4983296)), //FullSquad value?
|
||||||
{
|
{
|
||||||
val complete = SquadPositionDetail().Complete
|
val complete = SquadPositionDetail().Complete
|
||||||
Some(member_info match {
|
Some(member_info match {
|
||||||
|
|
@ -359,7 +361,7 @@ object SquadDetail {
|
||||||
unk1: Int,
|
unk1: Int,
|
||||||
unk2: Int,
|
unk2: Int,
|
||||||
leader_char_id: Long,
|
leader_char_id: Long,
|
||||||
unk3: Long,
|
outfit_id: Long,
|
||||||
leader_name: String,
|
leader_name: String,
|
||||||
task: String,
|
task: String,
|
||||||
zone_id: PlanetSideZoneID,
|
zone_id: PlanetSideZoneID,
|
||||||
|
|
@ -370,7 +372,7 @@ object SquadDetail {
|
||||||
Some(unk1),
|
Some(unk1),
|
||||||
Some(unk2),
|
Some(unk2),
|
||||||
Some(leader_char_id),
|
Some(leader_char_id),
|
||||||
Some(unk3),
|
Some(outfit_id),
|
||||||
Some(leader_name),
|
Some(leader_name),
|
||||||
Some(task),
|
Some(task),
|
||||||
Some(zone_id),
|
Some(zone_id),
|
||||||
|
|
@ -380,12 +382,12 @@ object SquadDetail {
|
||||||
}
|
}
|
||||||
|
|
||||||
//individual field overloaded constructors
|
//individual field overloaded constructors
|
||||||
def Field1(unk1: Int): SquadDetail =
|
def Guid(guid: Int): SquadDetail =
|
||||||
SquadDetail(Some(unk1), None, None, None, None, None, None, None, None)
|
SquadDetail(Some(guid), None, None, None, None, None, None, None, None)
|
||||||
def LeaderCharId(char_id: Long): SquadDetail =
|
def LeaderCharId(char_id: Long): SquadDetail =
|
||||||
SquadDetail(None, None, Some(char_id), None, None, None, None, None, None)
|
SquadDetail(None, None, Some(char_id), None, None, None, None, None, None)
|
||||||
def Field3(char_id: Option[Long], unk3: Long): SquadDetail =
|
def OutfitId(char_id: Option[Long], outfit_id: Long): SquadDetail =
|
||||||
SquadDetail(None, None, None, Some(unk3), None, None, None, None, None)
|
SquadDetail(None, None, None, Some(outfit_id), None, None, None, None, None)
|
||||||
def LeaderName(name: String): SquadDetail =
|
def LeaderName(name: String): SquadDetail =
|
||||||
SquadDetail(None, None, None, None, Some(name), None, None, None, None)
|
SquadDetail(None, None, None, None, Some(name), None, None, None, None)
|
||||||
def Leader(char_id: Long, name: String): SquadDetail =
|
def Leader(char_id: Long, name: String): SquadDetail =
|
||||||
|
|
@ -400,9 +402,9 @@ object SquadDetail {
|
||||||
SquadDetail(None, None, None, None, None, None, None, None, Some(list))
|
SquadDetail(None, None, None, None, None, None, None, None, Some(list))
|
||||||
|
|
||||||
object Fields {
|
object Fields {
|
||||||
final val Field1 = 1
|
final val Guid = 1
|
||||||
final val CharId = 2
|
final val CharId = 2
|
||||||
final val Field3 = 3
|
final val Outfit = 3
|
||||||
final val Leader = 4
|
final val Leader = 4
|
||||||
final val Task = 5
|
final val Task = 5
|
||||||
final val ZoneId = 6
|
final val ZoneId = 6
|
||||||
|
|
@ -541,10 +543,10 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
|
||||||
val codec: Codec[SquadDetail] = {
|
val codec: Codec[SquadDetail] = {
|
||||||
import shapeless.::
|
import shapeless.::
|
||||||
(
|
(
|
||||||
("unk1" | uint8) ::
|
("guid" | uint16L) ::
|
||||||
("unk2" | uint24) :: //unknown, but can be 0'd
|
("unk2" | uint16L) :: //unknown, but can be 0'd
|
||||||
("leader_char_id" | uint32L) ::
|
("leader_char_id" | uint32L) ::
|
||||||
("unk3" | uint32L) :: //variable fields, but can be 0'd
|
("outfit_id" | uint32L) :: //can be 0'd
|
||||||
("leader" | PacketHelpers.encodedWideStringAligned(7)) ::
|
("leader" | PacketHelpers.encodedWideStringAligned(7)) ::
|
||||||
("task" | PacketHelpers.encodedWideString) ::
|
("task" | PacketHelpers.encodedWideString) ::
|
||||||
("zone_id" | PlanetSideZoneID.codec) ::
|
("zone_id" | PlanetSideZoneID.codec) ::
|
||||||
|
|
@ -552,13 +554,13 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
|
||||||
optional(bool, "member_info" | initial_member_codec)
|
optional(bool, "member_info" | initial_member_codec)
|
||||||
).exmap[SquadDetail](
|
).exmap[SquadDetail](
|
||||||
{
|
{
|
||||||
case u1 :: u2 :: char_id :: u3 :: leader :: task :: zone :: unk7 :: Some(member_list) :: HNil =>
|
case guid :: u2 :: char_id :: outfit_id :: leader :: task :: zone :: unk7 :: Some(member_list) :: HNil =>
|
||||||
Attempt.Successful(
|
Attempt.Successful(
|
||||||
SquadDetail(
|
SquadDetail(
|
||||||
Some(u1),
|
Some(guid),
|
||||||
Some(u2),
|
Some(u2),
|
||||||
Some(char_id),
|
Some(char_id),
|
||||||
Some(u3),
|
Some(outfit_id),
|
||||||
Some(leader),
|
Some(leader),
|
||||||
Some(task),
|
Some(task),
|
||||||
Some(zone),
|
Some(zone),
|
||||||
|
|
@ -573,10 +575,10 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case SquadDetail(
|
case SquadDetail(
|
||||||
Some(u1),
|
Some(guid),
|
||||||
Some(u2),
|
Some(u2),
|
||||||
Some(char_id),
|
Some(char_id),
|
||||||
Some(u3),
|
Some(outfit_id),
|
||||||
Some(leader),
|
Some(leader),
|
||||||
Some(task),
|
Some(task),
|
||||||
Some(zone),
|
Some(zone),
|
||||||
|
|
@ -584,7 +586,7 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
|
||||||
Some(member_list)
|
Some(member_list)
|
||||||
) =>
|
) =>
|
||||||
Attempt.Successful(
|
Attempt.Successful(
|
||||||
math.max(u1, 1) :: u2 :: char_id :: u3 :: leader :: task :: zone :: unk7 ::
|
math.max(guid, 1) :: u2 :: char_id :: outfit_id :: leader :: task :: zone :: unk7 ::
|
||||||
Some(linkFields(member_list.collect { case SquadPositionEntry(_, Some(entry)) => entry }.reverse)) ::
|
Some(linkFields(member_list.collect { case SquadPositionEntry(_, Some(entry)) => entry }.reverse)) ::
|
||||||
HNil
|
HNil
|
||||||
)
|
)
|
||||||
|
|
@ -605,15 +607,15 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
|
||||||
object ItemizedSquad {
|
object ItemizedSquad {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pattern for data related to "field1."
|
* A pattern for data related to the `guid` field.
|
||||||
*/
|
*/
|
||||||
private val field1Codec: Codec[SquadDetail] = uint16L.exmap[SquadDetail](
|
private val guidCodec: Codec[SquadDetail] = uint16L.exmap[SquadDetail](
|
||||||
unk1 => Attempt.successful(SquadDetail(Some(unk1), None, None, None, None, None, None, None, None)),
|
guid => Attempt.successful(SquadDetail(Some(guid), None, None, None, None, None, None, None, None)),
|
||||||
{
|
{
|
||||||
case SquadDetail(Some(unk1), _, _, _, _, _, _, _, _) =>
|
case SquadDetail(Some(guid), _, _, _, _, _, _, _, _) =>
|
||||||
Attempt.successful(unk1)
|
Attempt.successful(guid)
|
||||||
case _ =>
|
case _ =>
|
||||||
Attempt.failure(Err("failed to encode squad data for unknown field #1"))
|
Attempt.failure(Err("failed to encode squad data for the guid"))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -631,13 +633,13 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pattern for data related to "field3."
|
* A pattern for data related to the "outfit id" field.
|
||||||
*/
|
*/
|
||||||
private val field3Codec: Codec[SquadDetail] = uint32L.exmap[SquadDetail](
|
private val outfitCodec: Codec[SquadDetail] = uint32L.exmap[SquadDetail](
|
||||||
unk3 => Attempt.successful(SquadDetail(None, None, None, Some(unk3), None, None, None, None, None)),
|
outfit_id => Attempt.successful(SquadDetail(None, None, None, Some(outfit_id), None, None, None, None, None)),
|
||||||
{
|
{
|
||||||
case SquadDetail(_, _, _, Some(unk3), _, _, _, _, _) =>
|
case SquadDetail(_, _, _, Some(outfit_id), _, _, _, _, _) =>
|
||||||
Attempt.successful(unk3)
|
Attempt.successful(outfit_id)
|
||||||
case _ =>
|
case _ =>
|
||||||
Attempt.failure(Err("failed to encode squad data for unknown field #3"))
|
Attempt.failure(Err("failed to encode squad data for unknown field #3"))
|
||||||
}
|
}
|
||||||
|
|
@ -807,9 +809,9 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
|
||||||
*/
|
*/
|
||||||
private def selectCodedAction(code: Int, bitsOverByte: StreamLengthToken): Codec[SquadDetail] = {
|
private def selectCodedAction(code: Int, bitsOverByte: StreamLengthToken): Codec[SquadDetail] = {
|
||||||
code match {
|
code match {
|
||||||
case 1 => field1Codec
|
case 1 => guidCodec
|
||||||
case 2 => leaderCharIdCodec
|
case 2 => leaderCharIdCodec
|
||||||
case 3 => field3Codec
|
case 3 => outfitCodec
|
||||||
case 4 => leaderNameCodec(bitsOverByte)
|
case 4 => leaderNameCodec(bitsOverByte)
|
||||||
case 5 => taskCodec(bitsOverByte)
|
case 5 => taskCodec(bitsOverByte)
|
||||||
case 6 => zoneCodec
|
case 6 => zoneCodec
|
||||||
|
|
@ -820,7 +822,7 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Advance information about the current stream length because on which pattern was previously utilized.
|
* Advance information about the current stream length based on which pattern was previously utilized.
|
||||||
* @see `selectCodedAction(Int, StreamLengthToken)`
|
* @see `selectCodedAction(Int, StreamLengthToken)`
|
||||||
* @param code the action code, connecting to a field pattern
|
* @param code the action code, connecting to a field pattern
|
||||||
* @param bitsOverByte a token maintaining stream misalignment for purposes of calculating string padding
|
* @param bitsOverByte a token maintaining stream misalignment for purposes of calculating string padding
|
||||||
|
|
@ -834,7 +836,7 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
|
||||||
case 4 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
|
case 4 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
|
||||||
case 5 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
|
case 5 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
|
||||||
case 6 => bitsOverByte //32u = no added padding
|
case 6 => bitsOverByte //32u = no added padding
|
||||||
case 7 => bitsOverByte.Add(4) //additional 4u
|
case 7 => bitsOverByte.Add(more = 4) //additional 4u
|
||||||
case 8 => bitsOverByte.Length = 0 //end of stream
|
case 8 => bitsOverByte.Length = 0 //end of stream
|
||||||
case _ => bitsOverByte.Length = Int.MinValue //wildly incorrect
|
case _ => bitsOverByte.Length = Int.MinValue //wildly incorrect
|
||||||
}
|
}
|
||||||
|
|
@ -886,9 +888,9 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
|
||||||
(6, SquadDetail(None, None, None, None, None, None, info.zone_id, None, None)),
|
(6, SquadDetail(None, None, None, None, None, None, info.zone_id, None, None)),
|
||||||
(5, SquadDetail(None, None, None, None, None, info.task, None, None, None)),
|
(5, SquadDetail(None, None, None, None, None, info.task, None, None, None)),
|
||||||
(4, SquadDetail(None, None, None, None, info.leader_name, None, None, None, None)),
|
(4, SquadDetail(None, None, None, None, info.leader_name, None, None, None, None)),
|
||||||
(3, SquadDetail(None, None, None, info.unk3, None, None, None, None, None)),
|
(3, SquadDetail(None, None, None, info.outfit_id, None, None, None, None, None)),
|
||||||
(2, SquadDetail(None, None, info.leader_char_id, None, None, None, None, None, None)),
|
(2, SquadDetail(None, None, info.leader_char_id, None, None, None, None, None, None)),
|
||||||
(1, SquadDetail(info.unk1, None, None, None, None, None, None, None, None))
|
(1, SquadDetail(info.guid, None, None, None, None, None, None, None, None))
|
||||||
) //in reverse order so that the linked list is in the correct order
|
) //in reverse order so that the linked list is in the correct order
|
||||||
.filterNot { case (_, sqInfo) => sqInfo == SquadDetail.Blank } match {
|
.filterNot { case (_, sqInfo) => sqInfo == SquadDetail.Blank } match {
|
||||||
case Nil =>
|
case Nil =>
|
||||||
|
|
@ -1102,12 +1104,12 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
|
||||||
*/
|
*/
|
||||||
private def modifyCodedPadValue(code: Int, bitsOverByte: StreamLengthToken): StreamLengthToken = {
|
private def modifyCodedPadValue(code: Int, bitsOverByte: StreamLengthToken): StreamLengthToken = {
|
||||||
code match {
|
code match {
|
||||||
case 0 => bitsOverByte.Add(1) //additional 1u
|
case 0 => bitsOverByte.Add(1) //additional 1u
|
||||||
case 1 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
|
case 1 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
|
||||||
case 2 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
|
case 2 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
|
||||||
case 3 => bitsOverByte //32u = no added padding
|
case 3 => bitsOverByte //32u = no added padding
|
||||||
case 4 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
|
case 4 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
|
||||||
case 5 => bitsOverByte.Add(6) //46u = 5*8u + 6u = additional 6u
|
case 5 => bitsOverByte.Add(6) //46u = 5*8u + 6u = additional 6u
|
||||||
case _ => bitsOverByte.Length = Int.MinValue //wildly incorrect
|
case _ => bitsOverByte.Length = Int.MinValue //wildly incorrect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1587,10 +1589,10 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
|
||||||
{
|
{
|
||||||
case SquadDetailDefinitionUpdateMessage(guid, info) =>
|
case SquadDetailDefinitionUpdateMessage(guid, info) =>
|
||||||
val occupiedSquadFieldCount = List(
|
val occupiedSquadFieldCount = List(
|
||||||
info.unk1,
|
info.guid,
|
||||||
info.unk2,
|
info.unk2,
|
||||||
info.leader_char_id,
|
info.leader_char_id,
|
||||||
info.unk3,
|
info.outfit_id,
|
||||||
info.leader_name,
|
info.leader_name,
|
||||||
info.task,
|
info.task,
|
||||||
info.zone_id,
|
info.zone_id,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import shapeless.{::, HNil}
|
||||||
object MemberEvent extends Enumeration {
|
object MemberEvent extends Enumeration {
|
||||||
type Type = Value
|
type Type = Value
|
||||||
|
|
||||||
val Add, Remove, Promote, UpdateZone, Unknown4 = Value
|
val Add, Remove, Promote, UpdateZone, Outfit = Value
|
||||||
|
|
||||||
implicit val codec = PacketHelpers.createEnumerationCodec(enum = this, uint(bits = 3))
|
implicit val codec = PacketHelpers.createEnumerationCodec(enum = this, uint(bits = 3))
|
||||||
}
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ final case class SquadMemberEvent(
|
||||||
position: Int,
|
position: Int,
|
||||||
player_name: Option[String],
|
player_name: Option[String],
|
||||||
zone_number: Option[Int],
|
zone_number: Option[Int],
|
||||||
unk7: Option[Long]
|
outfit_id: Option[Long]
|
||||||
) extends PlanetSideGamePacket {
|
) extends PlanetSideGamePacket {
|
||||||
type Packet = SquadMemberEvent
|
type Packet = SquadMemberEvent
|
||||||
def opcode = GamePacketOpcode.SquadMemberEvent
|
def opcode = GamePacketOpcode.SquadMemberEvent
|
||||||
|
|
@ -38,9 +38,9 @@ object SquadMemberEvent extends Marshallable[SquadMemberEvent] {
|
||||||
position: Int,
|
position: Int,
|
||||||
player_name: String,
|
player_name: String,
|
||||||
zone_number: Int,
|
zone_number: Int,
|
||||||
unk7: Long
|
outfit_id: Long
|
||||||
): SquadMemberEvent =
|
): SquadMemberEvent =
|
||||||
SquadMemberEvent(MemberEvent.Add, unk2, char_id, position, Some(player_name), Some(zone_number), Some(unk7))
|
SquadMemberEvent(MemberEvent.Add, unk2, char_id, position, Some(player_name), Some(zone_number), Some(outfit_id))
|
||||||
|
|
||||||
def Remove(unk2: Int, char_id: Long, position: Int): SquadMemberEvent =
|
def Remove(unk2: Int, char_id: Long, position: Int): SquadMemberEvent =
|
||||||
SquadMemberEvent(MemberEvent.Remove, unk2, char_id, position, None, None, None)
|
SquadMemberEvent(MemberEvent.Remove, unk2, char_id, position, None, None, None)
|
||||||
|
|
@ -51,20 +51,20 @@ object SquadMemberEvent extends Marshallable[SquadMemberEvent] {
|
||||||
def UpdateZone(unk2: Int, char_id: Long, position: Int, zone_number: Int): SquadMemberEvent =
|
def UpdateZone(unk2: Int, char_id: Long, position: Int, zone_number: Int): SquadMemberEvent =
|
||||||
SquadMemberEvent(MemberEvent.UpdateZone, unk2, char_id, position, None, Some(zone_number), None)
|
SquadMemberEvent(MemberEvent.UpdateZone, unk2, char_id, position, None, Some(zone_number), None)
|
||||||
|
|
||||||
def Unknown4(unk2: Int, char_id: Long, position: Int, unk7: Long): SquadMemberEvent =
|
def Outfit(unk2: Int, char_id: Long, position: Int, outfit_id: Long): SquadMemberEvent =
|
||||||
SquadMemberEvent(MemberEvent.Unknown4, unk2, char_id, position, None, None, Some(unk7))
|
SquadMemberEvent(MemberEvent.Outfit, unk2, char_id, position, None, None, Some(outfit_id))
|
||||||
|
|
||||||
implicit val codec: Codec[SquadMemberEvent] = (("action" | MemberEvent.codec) >>:~ { action =>
|
implicit val codec: Codec[SquadMemberEvent] = (("action" | MemberEvent.codec) >>:~ { action =>
|
||||||
("unk2" | uint16L) ::
|
("unk2" | uint16L) ::
|
||||||
("char_id" | uint32L) ::
|
("char_id" | uint32L) ::
|
||||||
("position" | uint4) ::
|
("position" | uint4) ::
|
||||||
conditional(action == MemberEvent.Add, "player_name" | PacketHelpers.encodedWideStringAligned(1)) ::
|
("player_name" | conditional(action == MemberEvent.Add, PacketHelpers.encodedWideStringAligned(adjustment = 1))) ::
|
||||||
conditional(action == MemberEvent.Add || action == MemberEvent.UpdateZone, "zone_number" | uint16L) ::
|
("zone_number" | conditional(action == MemberEvent.Add || action == MemberEvent.UpdateZone, uint16L)) ::
|
||||||
conditional(action == MemberEvent.Add || action == MemberEvent.Unknown4, "unk7" | uint32L)
|
("outfit_id" | conditional(action == MemberEvent.Add || action == MemberEvent.Outfit, uint32L))
|
||||||
}).exmap[SquadMemberEvent](
|
}).exmap[SquadMemberEvent](
|
||||||
{
|
{
|
||||||
case action :: unk2 :: char_id :: member_position :: player_name :: zone_number :: unk7 :: HNil =>
|
case action :: unk2 :: char_id :: member_position :: player_name :: zone_number :: outfit_id :: HNil =>
|
||||||
Attempt.Successful(SquadMemberEvent(action, unk2, char_id, member_position, player_name, zone_number, unk7))
|
Attempt.Successful(SquadMemberEvent(action, unk2, char_id, member_position, player_name, zone_number, outfit_id))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case SquadMemberEvent(
|
case SquadMemberEvent(
|
||||||
|
|
@ -74,20 +74,20 @@ object SquadMemberEvent extends Marshallable[SquadMemberEvent] {
|
||||||
member_position,
|
member_position,
|
||||||
Some(player_name),
|
Some(player_name),
|
||||||
Some(zone_number),
|
Some(zone_number),
|
||||||
Some(unk7)
|
Some(outfit_id)
|
||||||
) =>
|
) =>
|
||||||
Attempt.Successful(
|
Attempt.Successful(
|
||||||
MemberEvent.Add :: unk2 :: char_id :: member_position :: Some(player_name) :: Some(zone_number) :: Some(
|
MemberEvent.Add :: unk2 :: char_id :: member_position :: Some(player_name) :: Some(zone_number) :: Some(
|
||||||
unk7
|
outfit_id
|
||||||
) :: HNil
|
) :: HNil
|
||||||
)
|
)
|
||||||
case SquadMemberEvent(MemberEvent.UpdateZone, unk2, char_id, member_position, None, Some(zone_number), None) =>
|
case SquadMemberEvent(MemberEvent.UpdateZone, unk2, char_id, member_position, None, Some(zone_number), None) =>
|
||||||
Attempt.Successful(
|
Attempt.Successful(
|
||||||
MemberEvent.UpdateZone :: unk2 :: char_id :: member_position :: None :: Some(zone_number) :: None :: HNil
|
MemberEvent.UpdateZone :: unk2 :: char_id :: member_position :: None :: Some(zone_number) :: None :: HNil
|
||||||
)
|
)
|
||||||
case SquadMemberEvent(MemberEvent.Unknown4, unk2, char_id, member_position, None, None, Some(unk7)) =>
|
case SquadMemberEvent(MemberEvent.Outfit, unk2, char_id, member_position, None, None, Some(outfit_id)) =>
|
||||||
Attempt.Successful(
|
Attempt.Successful(
|
||||||
MemberEvent.Unknown4 :: unk2 :: char_id :: member_position :: None :: None :: Some(unk7) :: HNil
|
MemberEvent.Outfit :: unk2 :: char_id :: member_position :: None :: None :: Some(outfit_id) :: HNil
|
||||||
)
|
)
|
||||||
case SquadMemberEvent(action, unk2, char_id, member_position, None, None, None) =>
|
case SquadMemberEvent(action, unk2, char_id, member_position, None, None, None) =>
|
||||||
Attempt.Successful(action :: unk2 :: char_id :: member_position :: None :: None :: None :: HNil)
|
Attempt.Successful(action :: unk2 :: char_id :: member_position :: None :: None :: None :: HNil)
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import scodec.codecs._
|
||||||
* - `Invite` (0)<br>
|
* - `Invite` (0)<br>
|
||||||
* false => [PROMPT] "`player_name` has invited you into a squad." [YES/NO]<br>
|
* false => [PROMPT] "`player_name` has invited you into a squad." [YES/NO]<br>
|
||||||
* true => "You have invited `player_name` to join your squad."<br>
|
* true => "You have invited `player_name` to join your squad."<br>
|
||||||
* - `Unk01` (1)<br>
|
* - `ProximityInvite` (1)<br>
|
||||||
* false => n/a<br>
|
* false => n/a<br>
|
||||||
* true => n/a<br>
|
* true => n/a<br>
|
||||||
* - `Accept` (2)<br>
|
* - `Accept` (2)<br>
|
||||||
|
|
|
||||||
|
|
@ -58,14 +58,16 @@ final case class SquadState(guid: PlanetSideGUID, info_list: List[SquadStateInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
object SquadStateInfo {
|
object SquadStateInfo {
|
||||||
def apply(unk1: Long, unk2: Int, unk3: Int, pos: Vector3, unk4: Int, unk5: Int, unk6: Boolean, unk7: Int)
|
def apply(charId: Long, health: Int, armor: Int, pos: Vector3): SquadStateInfo =
|
||||||
: SquadStateInfo =
|
SquadStateInfo(charId, health, armor, pos, 2, 2, unk6=false, 429, None, None)
|
||||||
SquadStateInfo(unk1, unk2, unk3, pos, unk4, unk5, unk6, unk7, None, None)
|
|
||||||
|
def apply(charId: Long, health: Int, armor: Int, pos: Vector3, unk4: Int, unk5: Int, unk6: Boolean, unk7: Int): SquadStateInfo =
|
||||||
|
SquadStateInfo(charId, health, armor, pos, unk4, unk5, unk6, unk7, None, None)
|
||||||
|
|
||||||
def apply(
|
def apply(
|
||||||
unk1: Long,
|
charId: Long,
|
||||||
unk2: Int,
|
health: Int,
|
||||||
unk3: Int,
|
armor: Int,
|
||||||
pos: Vector3,
|
pos: Vector3,
|
||||||
unk4: Int,
|
unk4: Int,
|
||||||
unk5: Int,
|
unk5: Int,
|
||||||
|
|
@ -74,22 +76,22 @@ object SquadStateInfo {
|
||||||
unk8: Int,
|
unk8: Int,
|
||||||
unk9: Boolean
|
unk9: Boolean
|
||||||
): SquadStateInfo =
|
): SquadStateInfo =
|
||||||
SquadStateInfo(unk1, unk2, unk3, pos, unk4, unk5, unk6, unk7, Some(unk8), Some(unk9))
|
SquadStateInfo(charId, health, armor, pos, unk4, unk5, unk6, unk7, Some(unk8), Some(unk9))
|
||||||
}
|
}
|
||||||
|
|
||||||
object SquadState extends Marshallable[SquadState] {
|
object SquadState extends Marshallable[SquadState] {
|
||||||
private val info_codec: Codec[SquadStateInfo] = (
|
private val info_codec: Codec[SquadStateInfo] = (
|
||||||
("char_id" | uint32L) ::
|
("char_id" | uint32L) ::
|
||||||
("health" | uint(7)) ::
|
("health" | uint(bits = 7)) ::
|
||||||
("armor" | uint(7)) ::
|
("armor" | uint(bits = 7)) ::
|
||||||
("pos" | Vector3.codec_pos) ::
|
("pos" | Vector3.codec_pos) ::
|
||||||
("unk4" | uint2) ::
|
("unk4" | uint2) ::
|
||||||
("unk5" | uint2) ::
|
("unk5" | uint2) ::
|
||||||
("unk6" | bool) ::
|
("unk6" | bool) ::
|
||||||
("unk7" | uint16L) ::
|
("unk7" | uint16L) ::
|
||||||
(bool >>:~ { out =>
|
(bool >>:~ { out =>
|
||||||
conditional(out, "unk8" | uint16L) ::
|
("unk8" | conditional(out, uint16L)) ::
|
||||||
conditional(out, "unk9" | bool)
|
("unk9" | conditional(out, bool))
|
||||||
})
|
})
|
||||||
).exmap[SquadStateInfo](
|
).exmap[SquadStateInfo](
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ final case class CharacterAppearanceA(
|
||||||
* @param on_zipline player's model is changed into a faction-color ball of energy, as if on a zip line
|
* @param on_zipline player's model is changed into a faction-color ball of energy, as if on a zip line
|
||||||
*/
|
*/
|
||||||
final case class CharacterAppearanceB(
|
final case class CharacterAppearanceB(
|
||||||
unk0: Long,
|
outfit_id: Long,
|
||||||
outfit_name: String,
|
outfit_name: String,
|
||||||
outfit_logo: Int,
|
outfit_logo: Int,
|
||||||
unk1: Boolean,
|
unk1: Boolean,
|
||||||
|
|
@ -394,7 +394,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
||||||
*/
|
*/
|
||||||
def b_codec(alt_model: Boolean, name_padding: Int): Codec[CharacterAppearanceB] =
|
def b_codec(alt_model: Boolean, name_padding: Int): Codec[CharacterAppearanceB] =
|
||||||
(
|
(
|
||||||
("unk0" | uint32L) :: //for outfit_name (below) to be visible in-game, this value should be non-zero
|
("outfit_id" | uint32L) :: //for outfit_name (below) to be visible in-game, this value should be non-zero
|
||||||
("outfit_name" | PacketHelpers.encodedWideStringAligned(outfitNamePadding)) ::
|
("outfit_name" | PacketHelpers.encodedWideStringAligned(outfitNamePadding)) ::
|
||||||
("outfit_logo" | uint8L) ::
|
("outfit_logo" | uint8L) ::
|
||||||
("unk1" | bool) :: //unknown
|
("unk1" | bool) :: //unknown
|
||||||
|
|
@ -414,12 +414,12 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
||||||
optional(bool, "on_zipline" | zipline_codec)
|
optional(bool, "on_zipline" | zipline_codec)
|
||||||
).exmap[CharacterAppearanceB](
|
).exmap[CharacterAppearanceB](
|
||||||
{
|
{
|
||||||
case u0 :: outfit :: logo :: u1 :: bpack :: u2 :: u3 :: u4 :: facingPitch :: facingYawUpper :: lfs :: gstate :: cloaking :: u5 :: u6 :: charging :: u7 :: zipline :: HNil =>
|
case outfit_id :: outfit :: logo :: u1 :: bpack :: u2 :: u3 :: u4 :: facingPitch :: facingYawUpper :: lfs :: gstate :: cloaking :: u5 :: u6 :: charging :: u7 :: zipline :: HNil =>
|
||||||
val lfsBool = if (lfs == 0) false else true
|
val lfsBool = if (lfs == 0) false else true
|
||||||
val bpackBool = bpack match { case Some(_) => alt_model; case None => false }
|
val bpackBool = bpack match { case Some(_) => alt_model; case None => false }
|
||||||
Attempt.successful(
|
Attempt.successful(
|
||||||
CharacterAppearanceB(
|
CharacterAppearanceB(
|
||||||
u0,
|
outfit_id,
|
||||||
outfit,
|
outfit,
|
||||||
logo,
|
logo,
|
||||||
u1,
|
u1,
|
||||||
|
|
@ -442,7 +442,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case CharacterAppearanceB(
|
case CharacterAppearanceB(
|
||||||
u0,
|
outfit_id,
|
||||||
outfit,
|
outfit,
|
||||||
logo,
|
logo,
|
||||||
u1,
|
u1,
|
||||||
|
|
@ -461,10 +461,10 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
||||||
u7,
|
u7,
|
||||||
zipline
|
zipline
|
||||||
) =>
|
) =>
|
||||||
val u0Long = if (u0 == 0 && outfit.nonEmpty) {
|
val u0Long = if (outfit_id == 0 && outfit.nonEmpty) {
|
||||||
outfit.length.toLong
|
outfit.length.toLong
|
||||||
} else {
|
} else {
|
||||||
u0
|
outfit_id
|
||||||
} //TODO this is a kludge; unk0 must be (some) non-zero if outfit_name is defined
|
} //TODO this is a kludge; unk0 must be (some) non-zero if outfit_name is defined
|
||||||
val (bpackOpt, zipOpt) = if (alt_model) {
|
val (bpackOpt, zipOpt) = if (alt_model) {
|
||||||
val bpackOpt = if (bpack) { Some(true) }
|
val bpackOpt = if (bpack) { Some(true) }
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -2,6 +2,7 @@
|
||||||
package net.psforever.services.teamwork
|
package net.psforever.services.teamwork
|
||||||
|
|
||||||
import net.psforever.objects.Player
|
import net.psforever.objects.Player
|
||||||
|
import net.psforever.objects.avatar.Certification
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
import net.psforever.packet.game.{WaypointEventAction, WaypointInfo, SquadAction => PacketSquadAction}
|
import net.psforever.packet.game.{WaypointEventAction, WaypointInfo, SquadAction => PacketSquadAction}
|
||||||
import net.psforever.types.{PlanetSideGUID, SquadRequestType, SquadWaypoint, Vector3}
|
import net.psforever.types.{PlanetSideGUID, SquadRequestType, SquadWaypoint, Vector3}
|
||||||
|
|
@ -16,8 +17,9 @@ object SquadServiceMessage {
|
||||||
object SquadAction {
|
object SquadAction {
|
||||||
sealed trait Action
|
sealed trait Action
|
||||||
|
|
||||||
final case class InitSquadList() extends Action
|
final case class InitSquadList() extends Action
|
||||||
final case class InitCharId() extends Action
|
final case class InitCharId() extends Action
|
||||||
|
final case class ReloadDecoration() extends Action
|
||||||
|
|
||||||
final case class Definition(guid: PlanetSideGUID, line: Int, action: PacketSquadAction) extends Action
|
final case class Definition(guid: PlanetSideGUID, line: Int, action: PacketSquadAction) extends Action
|
||||||
final case class Membership(
|
final case class Membership(
|
||||||
|
|
@ -35,10 +37,12 @@ object SquadAction {
|
||||||
) extends Action
|
) extends Action
|
||||||
final case class Update(
|
final case class Update(
|
||||||
char_id: Long,
|
char_id: Long,
|
||||||
|
guid: PlanetSideGUID,
|
||||||
health: Int,
|
health: Int,
|
||||||
max_health: Int,
|
max_health: Int,
|
||||||
armor: Int,
|
armor: Int,
|
||||||
max_armor: Int,
|
max_armor: Int,
|
||||||
|
certifications: Set[Certification],
|
||||||
pos: Vector3,
|
pos: Vector3,
|
||||||
zone_number: Int
|
zone_number: Int
|
||||||
) extends Action
|
) extends Action
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright (c) 2019 PSForever
|
// Copyright (c) 2019 PSForever
|
||||||
package net.psforever.services.teamwork
|
package net.psforever.services.teamwork
|
||||||
|
|
||||||
|
import akka.actor.ActorRef
|
||||||
|
import net.psforever.objects.avatar.Certification
|
||||||
import net.psforever.objects.teamwork.Squad
|
import net.psforever.objects.teamwork.Squad
|
||||||
import net.psforever.packet.game.{SquadDetail, SquadInfo, WaypointEventAction, WaypointInfo}
|
import net.psforever.packet.game.{SquadDetail, SquadInfo, WaypointEventAction, WaypointInfo}
|
||||||
import net.psforever.types.{PlanetSideGUID, SquadResponseType, SquadWaypoint}
|
import net.psforever.types.{PlanetSideGUID, SquadResponseType, SquadWaypoint}
|
||||||
|
|
@ -26,7 +28,7 @@ object SquadResponse {
|
||||||
final case class UpdateList(infos: Iterable[(Int, SquadInfo)]) extends Response
|
final case class UpdateList(infos: Iterable[(Int, SquadInfo)]) extends Response
|
||||||
final case class RemoveFromList(infos: Iterable[Int]) extends Response
|
final case class RemoveFromList(infos: Iterable[Int]) extends Response
|
||||||
|
|
||||||
final case class AssociateWithSquad(squad_guid: PlanetSideGUID) extends Response
|
final case class IdentifyAsSquadLeader(squad_guid: PlanetSideGUID) extends Response
|
||||||
final case class SetListSquad(squad_guid: PlanetSideGUID) extends Response
|
final case class SetListSquad(squad_guid: PlanetSideGUID) extends Response
|
||||||
|
|
||||||
final case class Membership(
|
final case class Membership(
|
||||||
|
|
@ -40,11 +42,11 @@ object SquadResponse {
|
||||||
unk6: Option[Option[String]]
|
unk6: Option[Option[String]]
|
||||||
) extends Response //see SquadMembershipResponse
|
) extends Response //see SquadMembershipResponse
|
||||||
final case class WantsSquadPosition(leader_char_id: Long, bid_name: String) extends Response
|
final case class WantsSquadPosition(leader_char_id: Long, bid_name: String) extends Response
|
||||||
final case class Join(squad: Squad, positionsToUpdate: List[Int], channel: String) extends Response
|
final case class Join(squad: Squad, positionsToUpdate: List[Int], channel: String, ref: ActorRef) extends Response
|
||||||
final case class Leave(squad: Squad, positionsToUpdate: List[(Long, Int)]) extends Response
|
final case class Leave(squad: Squad, positionsToUpdate: List[(Long, Int)]) extends Response
|
||||||
final case class UpdateMembers(squad: Squad, update_info: List[SquadAction.Update]) extends Response
|
final case class UpdateMembers(squad: Squad, update_info: List[SquadAction.Update]) extends Response
|
||||||
final case class AssignMember(squad: Squad, from_index: Int, to_index: Int) extends Response
|
final case class AssignMember(squad: Squad, from_index: Int, to_index: Int) extends Response
|
||||||
final case class PromoteMember(squad: Squad, char_id: Long, from_index: Int, to_index: Int) extends Response
|
final case class PromoteMember(squad: Squad, char_id: Long, from_index: Int) extends Response
|
||||||
|
|
||||||
final case class Detail(guid: PlanetSideGUID, squad_detail: SquadDetail) extends Response
|
final case class Detail(guid: PlanetSideGUID, squad_detail: SquadDetail) extends Response
|
||||||
|
|
||||||
|
|
@ -59,5 +61,16 @@ object SquadResponse {
|
||||||
unk: Int
|
unk: Int
|
||||||
) extends Response
|
) extends Response
|
||||||
|
|
||||||
final case class SquadSearchResults() extends Response
|
final case class SquadDecoration(guid: PlanetSideGUID, squad: Squad) extends Response
|
||||||
|
|
||||||
|
final case class SquadSearchResults(results: List[PlanetSideGUID]) extends Response
|
||||||
|
|
||||||
|
final case class CharacterKnowledge(
|
||||||
|
id: Long,
|
||||||
|
name: String,
|
||||||
|
certs: Set[Certification],
|
||||||
|
unk1: Int,
|
||||||
|
unk2: Int,
|
||||||
|
zoneNumber: Int
|
||||||
|
) extends Response
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,316 @@
|
||||||
|
// Copyright (c) 2022 PSForever
|
||||||
|
package net.psforever.services.teamwork
|
||||||
|
|
||||||
|
import akka.actor.ActorRef
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
import net.psforever.objects.teamwork.{Squad, SquadFeatures}
|
||||||
|
import net.psforever.packet.game.SquadDetail
|
||||||
|
import net.psforever.services.GenericEventBus
|
||||||
|
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID}
|
||||||
|
|
||||||
|
class SquadSubscriptionEntity {
|
||||||
|
private[this] val log = org.log4s.getLogger(name="SquadService")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a formal `ActorEventBus` object that is reserved for faction-wide messages and squad-specific messages.
|
||||||
|
* When the user joins the `SquadService` with a `Service.Join` message
|
||||||
|
* that includes a confirmed faction affiliation identifier,
|
||||||
|
* the origin `ActorRef` is added as a subscription.
|
||||||
|
* Squad channels are produced when a squad is created,
|
||||||
|
* and are subscribed to as users join the squad,
|
||||||
|
* and unsubscribed from as users leave the squad.<br>
|
||||||
|
* key - a `PlanetSideEmpire` value; value - `ActorRef` reference<br>
|
||||||
|
* key - a consistent squad channel name; value - `ActorRef` reference
|
||||||
|
* @see `CloseSquad`
|
||||||
|
* @see `JoinSquad`
|
||||||
|
* @see `LeaveSquad`
|
||||||
|
* @see `Service.Join`
|
||||||
|
* @see `Service.Leave`
|
||||||
|
*/
|
||||||
|
val SquadEvents = new GenericEventBus[SquadServiceResponse]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This collection contains the message-sending contact reference for individuals.
|
||||||
|
* When the user joins the `SquadService` with a `Service.Join` message
|
||||||
|
* that includes their unique character identifier,
|
||||||
|
* the origin `ActorRef` is added as a subscription.
|
||||||
|
* It is maintained until they disconnect entirely.
|
||||||
|
* The subscription is anticipated to belong to an instance of `SessionActor`.<br>
|
||||||
|
* key - unique character identifier number; value - `ActorRef` reference for that character
|
||||||
|
* @see `Service.Join`
|
||||||
|
*/
|
||||||
|
val UserEvents: mutable.LongMap[ActorRef] = mutable.LongMap[ActorRef]()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Players who are interested in updated details regarding a certain squad
|
||||||
|
* though they may not be a member of the squad.<br>
|
||||||
|
* key - unique character identifier number; value - a squad identifier number
|
||||||
|
*/
|
||||||
|
val MonitorSquadDetails: mutable.LongMap[SquadSubscriptionEntity.MonitorEntry] = mutable.LongMap[SquadSubscriptionEntity.MonitorEntry]()
|
||||||
|
|
||||||
|
def postStop(): Unit = {
|
||||||
|
MonitorSquadDetails.clear()
|
||||||
|
UserEvents.foreach {
|
||||||
|
case (_, actor) =>
|
||||||
|
SquadEvents.unsubscribe(actor)
|
||||||
|
}
|
||||||
|
UserEvents.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded message-sending operation.
|
||||||
|
* The `Actor` version wraps around the expected `!` functionality.
|
||||||
|
* @param to an `ActorRef` which to send the message
|
||||||
|
* @param msg a message that can be stored in a `SquadServiceResponse` object
|
||||||
|
*/
|
||||||
|
def Publish(to: ActorRef, msg: SquadResponse.Response): Unit = {
|
||||||
|
Publish(to, msg, Nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded message-sending operation.
|
||||||
|
* The `Actor` version wraps around the expected `!` functionality.
|
||||||
|
* @param to an `ActorRef` which to send the message
|
||||||
|
* @param msg a message that can be stored in a `SquadServiceResponse` object
|
||||||
|
* @param excluded a group of character identifier numbers who should not receive the message
|
||||||
|
* (resolved at destination)
|
||||||
|
*/
|
||||||
|
def Publish(to: ActorRef, msg: SquadResponse.Response, excluded: Iterable[Long]): Unit = {
|
||||||
|
to ! SquadServiceResponse("", excluded, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded message-sending operation.
|
||||||
|
* Always publishes on the `SquadEvents` object.
|
||||||
|
* @param to a faction affiliation used as the channel for the message
|
||||||
|
* @param msg a message that can be stored in a `SquadServiceResponse` object
|
||||||
|
*/
|
||||||
|
def Publish(to: PlanetSideEmpire.Type, msg: SquadResponse.Response): Unit = {
|
||||||
|
Publish(to, msg, Nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded message-sending operation.
|
||||||
|
* Always publishes on the `SquadEvents` object.
|
||||||
|
* @param to a faction affiliation used as the channel for the message
|
||||||
|
* @param msg a message that can be stored in a `SquadServiceResponse` object
|
||||||
|
* @param excluded a group of character identifier numbers who should not receive the message
|
||||||
|
* (resolved at destination)
|
||||||
|
*/
|
||||||
|
def Publish(to: PlanetSideEmpire.Type, msg: SquadResponse.Response, excluded: Iterable[Long]): Unit = {
|
||||||
|
SquadEvents.publish(SquadServiceResponse(s"/$to/Squad", excluded, msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded message-sending operation.
|
||||||
|
* Strings come in three accepted patterns.
|
||||||
|
* The first resolves into a faction name, as determined by `PlanetSideEmpire` when transformed into a string.
|
||||||
|
* The second resolves into a squad's dedicated channel, a name that is formulaic.
|
||||||
|
* The third resolves as a unique character identifier number.
|
||||||
|
* @param to a string used as the channel for the message
|
||||||
|
* @param msg a message that can be stored in a `SquadServiceResponse` object
|
||||||
|
*/
|
||||||
|
def Publish(to: String, msg: SquadResponse.Response): Unit = {
|
||||||
|
Publish(to, msg, Nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded message-sending operation.
|
||||||
|
* Strings come in three accepted patterns.
|
||||||
|
* The first resolves into a faction name, as determined by `PlanetSideEmpire` when transformed into a string.
|
||||||
|
* The second resolves into a squad's dedicated channel, a name that is formulaic.
|
||||||
|
* The third resolves as a unique character identifier number.
|
||||||
|
* @param to a string used as the channel for the message
|
||||||
|
* @param msg a message that can be stored in a `SquadServiceResponse` object
|
||||||
|
* @param excluded a group of character identifier numbers who should not receive the message
|
||||||
|
* (resolved at destination, usually)
|
||||||
|
*/
|
||||||
|
def Publish(to: String, msg: SquadResponse.Response, excluded: Iterable[Long]): Unit = {
|
||||||
|
to match {
|
||||||
|
case str if "TRNCVS".indexOf(str) > -1 || str.matches("(TR|NC|VS)-Squad\\d+") =>
|
||||||
|
SquadEvents.publish(SquadServiceResponse(s"/$str/Squad", excluded, msg))
|
||||||
|
case str if str.matches("\\d+") =>
|
||||||
|
Publish(to.toLong, msg, excluded)
|
||||||
|
case _ =>
|
||||||
|
log.warn(s"Publish(String): subscriber information is an unhandled format - $to")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded message-sending operation.
|
||||||
|
* Always publishes on the `ActorRef` objects retained by the `UserEvents` object.
|
||||||
|
* @param to a unique character identifier used as the channel for the message
|
||||||
|
* @param msg a message that can be stored in a `SquadServiceResponse` object
|
||||||
|
*/
|
||||||
|
def Publish(to: Long, msg: SquadResponse.Response): Unit = {
|
||||||
|
UserEvents.get(to) match {
|
||||||
|
case Some(user) =>
|
||||||
|
user ! SquadServiceResponse("", msg)
|
||||||
|
case None =>
|
||||||
|
log.warn(s"Publish(Long): subscriber information can not be found - $to")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded message-sending operation.
|
||||||
|
* Always publishes on the `ActorRef` objects retained by the `UserEvents` object.
|
||||||
|
* @param to a unique character identifier used as the channel for the message
|
||||||
|
* @param msg a message that can be stored in a `SquadServiceResponse` object
|
||||||
|
* @param excluded a group of character identifier numbers who should not receive the message
|
||||||
|
*/
|
||||||
|
def Publish(to: Long, msg: SquadResponse.Response, excluded: Iterable[Long]): Unit = {
|
||||||
|
if (!excluded.exists(_ == to)) {
|
||||||
|
Publish(to, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded message-sending operation.
|
||||||
|
* No message can be sent using this distinction.
|
||||||
|
* Log a warning.
|
||||||
|
* @param to something that was expected to be used as the channel for the message
|
||||||
|
* but is not handled as such
|
||||||
|
* @param msg a message that can be stored in a `SquadServiceResponse` object
|
||||||
|
*/
|
||||||
|
def Publish[ANY >: Any](to: ANY, msg: SquadResponse.Response): Unit = {
|
||||||
|
log.warn(s"Publish(Any): subscriber information is an unhandled format - $to")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded message-sending operation.
|
||||||
|
* No message can be sent using this distinction.
|
||||||
|
* Log a warning.
|
||||||
|
* @param to something that was expected to be used as the channel for the message
|
||||||
|
* but is not handled as such
|
||||||
|
* @param msg a message that can be stored in a `SquadServiceResponse` object
|
||||||
|
* @param excluded a group of character identifier numbers who should not receive the message
|
||||||
|
*/
|
||||||
|
def Publish[ANY >: Any](to: ANY, msg: SquadResponse.Response, excluded: Iterable[Long]): Unit = {
|
||||||
|
log.warn(s"Publish(Any): subscriber information is an unhandled format - $to")
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The following functions are related to common communications of squad information, mainly detail. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch a message entailing the composition of this squad.
|
||||||
|
* This is considered the first time this information will be dispatched to any relevant observers
|
||||||
|
* so the details of the squad will be updated in full and be sent to all relevant observers,
|
||||||
|
* namely, all the occupants of the squad.
|
||||||
|
* External observers are ignored.
|
||||||
|
* @see `InitSquadDetail(PlanetSideGUID, Iterable[Long], Squad)`
|
||||||
|
* @param features the squad
|
||||||
|
*/
|
||||||
|
def InitSquadDetail(features: SquadFeatures): Unit = {
|
||||||
|
val squad = features.Squad
|
||||||
|
InitSquadDetail(
|
||||||
|
squad.GUID,
|
||||||
|
squad.Membership.collect { case member if member.CharId > 0 => member.CharId },
|
||||||
|
squad
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch an intial message entailing the strategic information and the composition of this squad.
|
||||||
|
* The details of the squad will be updated in full and be sent to all indicated observers.
|
||||||
|
* @see `SquadService.PublishFullDetails`
|
||||||
|
* @param guid the unique squad identifier to be used when composing the details for this message
|
||||||
|
* @param to the unique character identifier numbers of the players who will receive this message
|
||||||
|
* @param squad the squad from which the squad details shall be composed
|
||||||
|
*/
|
||||||
|
def InitSquadDetail(guid: PlanetSideGUID, to: Iterable[Long], squad: Squad): Unit = {
|
||||||
|
val output = SquadResponse.Detail(guid, SquadService.PublishFullDetails(squad))
|
||||||
|
to.foreach { Publish(_, output) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message entailing the strategic information and the composition of the squad to the existing members of the squad.
|
||||||
|
* @see `SquadService.PublishFullDetails`
|
||||||
|
* @see `UpdateSquadDetail(PlanetSideGUID, PlanetSideGUID, List[Long], SquadDetail)`
|
||||||
|
* @param features the squad
|
||||||
|
*/
|
||||||
|
def UpdateSquadDetail(features: SquadFeatures): Unit = {
|
||||||
|
val squad = features.Squad
|
||||||
|
UpdateSquadDetail(
|
||||||
|
squad.GUID,
|
||||||
|
features.ToChannel,
|
||||||
|
Nil,
|
||||||
|
SquadService.PublishFullDetails(squad)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message entailing some of the strategic information and the composition to the existing members of the squad.
|
||||||
|
* @see `SquadResponse.Detail`
|
||||||
|
* @see `UpdateSquadDetail(PlanetSideGUID, PlanetSideGUID, List[Long], SquadDetail)`
|
||||||
|
* @param features information about the squad
|
||||||
|
* @param details the squad details to be included in the message
|
||||||
|
*/
|
||||||
|
def UpdateSquadDetail(features: SquadFeatures, details: SquadDetail): Unit = {
|
||||||
|
UpdateSquadDetail(
|
||||||
|
features.Squad.GUID,
|
||||||
|
features.ToChannel,
|
||||||
|
Nil,
|
||||||
|
details
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message entailing some of the strategic information and the composition to the existing members of the squad.
|
||||||
|
* Also send the same information to any users who are watching the squad, potentially for want to join it.
|
||||||
|
* The squad-specific message is contingent on finding the squad's features using the unique identifier number
|
||||||
|
* and, from that, reporting to the specific squad's messaging channel.
|
||||||
|
* Anyone watching the squad will always be updated the given details.
|
||||||
|
* @see `DisplaySquad`
|
||||||
|
* @see `Publish`
|
||||||
|
* @see `SquadDetail`
|
||||||
|
* @see `SquadResponse.Detail`
|
||||||
|
* @param guid the unique squad identifier number to be used for the squad detail message
|
||||||
|
* @param toChannel the squad broadcast channel name
|
||||||
|
* @param excluding the explicit unique character identifier numbers of individuals who should not receive the message
|
||||||
|
* @param details the squad details to be included in the message
|
||||||
|
*/
|
||||||
|
def UpdateSquadDetail(
|
||||||
|
guid: PlanetSideGUID,
|
||||||
|
toChannel: String,
|
||||||
|
excluding: Iterable[Long],
|
||||||
|
details: SquadDetail
|
||||||
|
): Unit = {
|
||||||
|
val output = SquadResponse.Detail(guid, details)
|
||||||
|
Publish(toChannel, output, excluding)
|
||||||
|
PublishToMonitorTargets(guid, excluding).foreach { charId => Publish(charId, output, Nil) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* na
|
||||||
|
* @see `LongMap.subtractOne`
|
||||||
|
* @see `SquadSubscriptionEntity.MonitorEntry`
|
||||||
|
* @param guid the unique squad identifier number to be used for the squad detail message
|
||||||
|
* @param excluding the explicit unique character identifier numbers of individuals who should not receive the message
|
||||||
|
*/
|
||||||
|
def PublishToMonitorTargets(
|
||||||
|
guid: PlanetSideGUID,
|
||||||
|
excluding: Iterable[Long]
|
||||||
|
): Iterable[Long] = {
|
||||||
|
val curr = System.currentTimeMillis()
|
||||||
|
MonitorSquadDetails
|
||||||
|
.toSeq
|
||||||
|
.collect {
|
||||||
|
case out @ (charId: Long, entry: SquadSubscriptionEntity.MonitorEntry)
|
||||||
|
if entry.squadGuid == guid && !excluding.exists(_ == charId) =>
|
||||||
|
if (curr - entry.time < 300000L) {
|
||||||
|
Some(out._1)
|
||||||
|
} else {
|
||||||
|
MonitorSquadDetails.subtractOne(charId)
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.flatten
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object SquadSubscriptionEntity {
|
||||||
|
private[teamwork] case class MonitorEntry(squadGuid: PlanetSideGUID) {
|
||||||
|
val time: Long = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
27
src/main/scala/net/psforever/types/SquadListDecoration.scala
Normal file
27
src/main/scala/net/psforever/types/SquadListDecoration.scala
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright (c) 2022 PSForever
|
||||||
|
package net.psforever.types
|
||||||
|
|
||||||
|
import scodec.codecs.uint
|
||||||
|
|
||||||
|
object SquadListDecoration extends Enumeration {
|
||||||
|
type Type = Value
|
||||||
|
|
||||||
|
val NotAvailable = Value(0)
|
||||||
|
val Available = Value(1)
|
||||||
|
val CertQualified = Value(2)
|
||||||
|
val SearchResult = Value(3)
|
||||||
|
|
||||||
|
implicit val codec = uint(bits = 3).xmap[SquadListDecoration.Value](
|
||||||
|
{
|
||||||
|
value =>
|
||||||
|
if (value < 4) {
|
||||||
|
SquadListDecoration(value)
|
||||||
|
} else if (value < 7) {
|
||||||
|
SquadListDecoration.Available
|
||||||
|
} else {
|
||||||
|
SquadListDecoration.NotAvailable
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -4,9 +4,10 @@ package net.psforever.types
|
||||||
import net.psforever.packet.PacketHelpers
|
import net.psforever.packet.PacketHelpers
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
|
|
||||||
|
//unk01 is some sort of reply to ProximityInvite
|
||||||
object SquadResponseType extends Enumeration {
|
object SquadResponseType extends Enumeration {
|
||||||
type Type = Value
|
type Type = Value
|
||||||
val Invite, Unk01, Accept, Reject, Cancel, Leave, Disband, PlatoonInvite, PlatoonAccept, PlatoonReject, PlatoonCancel,
|
val Invite, ProximityInvite, Accept, Reject, Cancel, Leave, Disband, PlatoonInvite, PlatoonAccept, PlatoonReject, PlatoonCancel,
|
||||||
PlatoonLeave, PlatoonDisband = Value
|
PlatoonLeave, PlatoonDisband = Value
|
||||||
|
|
||||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L)
|
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L)
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ sealed abstract class StandardWaypoint(override val value: Int) extends SquadWay
|
||||||
* is indicated in the game world, on the proximity map, and on the continental map,
|
* is indicated in the game world, on the proximity map, and on the continental map,
|
||||||
* and is designated by the number of the squad member that produced it.
|
* and is designated by the number of the squad member that produced it.
|
||||||
* Only one laze waypoint may be made visible from any one squad member at any given time, overwritten when replaced.
|
* Only one laze waypoint may be made visible from any one squad member at any given time, overwritten when replaced.
|
||||||
* When viewed by a squad member seated in a Flail, the waypoint includes an elevation reticle for aiming purposes.
|
* When viewed by a squad member seated in a Flail, the waypoint includes an elevation reticule for aiming purposes.
|
||||||
* YMMV.
|
* YMMV.
|
||||||
* @see `SquadWaypointEvent`
|
* @see `SquadWaypointEvent`
|
||||||
* @see `SquadWaypointRequest`
|
* @see `SquadWaypointRequest`
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ class CharacterKnowledgeMessageTest extends Specification {
|
||||||
),
|
),
|
||||||
15,
|
15,
|
||||||
0,
|
0,
|
||||||
PlanetSideGUID(12)
|
12
|
||||||
)
|
)
|
||||||
case _ =>
|
case _ =>
|
||||||
ko
|
ko
|
||||||
|
|
@ -61,7 +61,7 @@ class CharacterKnowledgeMessageTest extends Specification {
|
||||||
),
|
),
|
||||||
15,
|
15,
|
||||||
0,
|
0,
|
||||||
PlanetSideGUID(12)
|
12
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import org.specs2.mutable._
|
||||||
import net.psforever.packet._
|
import net.psforever.packet._
|
||||||
import net.psforever.packet.game.SquadAction._
|
import net.psforever.packet.game.SquadAction._
|
||||||
import net.psforever.packet.game._
|
import net.psforever.packet.game._
|
||||||
import net.psforever.types.PlanetSideGUID
|
import net.psforever.types.{PlanetSideGUID, SquadListDecoration}
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
|
|
||||||
class SquadDefinitionActionMessageTest extends Specification {
|
class SquadDefinitionActionMessageTest extends Specification {
|
||||||
|
|
@ -29,6 +29,7 @@ class SquadDefinitionActionMessageTest extends Specification {
|
||||||
val string_26 = hex"E7 68 000000"
|
val string_26 = hex"E7 68 000000"
|
||||||
val string_28 = hex"E7 70 000020" //On
|
val string_28 = hex"E7 70 000020" //On
|
||||||
val string_31 = hex"E7 7c 000020" //On
|
val string_31 = hex"E7 7c 000020" //On
|
||||||
|
val string_33 = hex"E7 84 0C0008"
|
||||||
val string_34a =
|
val string_34a =
|
||||||
hex"E7 88 00002180420061006400610073007300000000000000040000" //"Badass", Solsar, Any matching position
|
hex"E7 88 00002180420061006400610073007300000000000000040000" //"Badass", Solsar, Any matching position
|
||||||
val string_34b =
|
val string_34b =
|
||||||
|
|
@ -224,6 +225,17 @@ class SquadDefinitionActionMessageTest extends Specification {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"decode (33)" in {
|
||||||
|
PacketCoding.decodePacket(string_33).require match {
|
||||||
|
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||||
|
unk1 mustEqual PlanetSideGUID(3)
|
||||||
|
unk2 mustEqual 0
|
||||||
|
action mustEqual SquadListDecorator(SquadListDecoration.Available)
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
"decode (34a)" in {
|
"decode (34a)" in {
|
||||||
PacketCoding.decodePacket(string_34a).require match {
|
PacketCoding.decodePacket(string_34a).require match {
|
||||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||||
|
|
@ -460,6 +472,13 @@ class SquadDefinitionActionMessageTest extends Specification {
|
||||||
pkt mustEqual string_31
|
pkt mustEqual string_31
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"encode (33)" in {
|
||||||
|
val msg = SquadDefinitionActionMessage(PlanetSideGUID(3), 0, SquadAction.SquadListDecorator(SquadListDecoration.Available))
|
||||||
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
pkt mustEqual string_33
|
||||||
|
}
|
||||||
|
|
||||||
"encode (34a)" in {
|
"encode (34a)" in {
|
||||||
val msg = SquadDefinitionActionMessage(
|
val msg = SquadDefinitionActionMessage(
|
||||||
PlanetSideGUID(0),
|
PlanetSideGUID(0),
|
||||||
|
|
|
||||||
|
|
@ -651,7 +651,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
|
||||||
val msg = SquadDetailDefinitionUpdateMessage(
|
val msg = SquadDetailDefinitionUpdateMessage(
|
||||||
PlanetSideGUID(3),
|
PlanetSideGUID(3),
|
||||||
SquadDetail()
|
SquadDetail()
|
||||||
.Field1(0)
|
.Guid(0)
|
||||||
.LeaderCharId(1221560L)
|
.LeaderCharId(1221560L)
|
||||||
.Members(
|
.Members(
|
||||||
List(
|
List(
|
||||||
|
|
@ -677,7 +677,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
|
||||||
PlanetSideGUID(3),
|
PlanetSideGUID(3),
|
||||||
SquadDetail()
|
SquadDetail()
|
||||||
.Leader(42631712L, "Jaako")
|
.Leader(42631712L, "Jaako")
|
||||||
.Field3(556403L)
|
.OutfitId(556403L)
|
||||||
.Members(
|
.Members(
|
||||||
List(
|
List(
|
||||||
SquadPositionEntry(0, SquadPositionDetail().Player(0L, ""))
|
SquadPositionEntry(0, SquadPositionDetail().Player(0L, ""))
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ class SquadMembershipResponseTest extends Specification {
|
||||||
"decode (1-1)" in {
|
"decode (1-1)" in {
|
||||||
PacketCoding.decodePacket(string_11).require match {
|
PacketCoding.decodePacket(string_11).require match {
|
||||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||||
unk1 mustEqual SquadResponseType.Unk01
|
unk1 mustEqual SquadResponseType.ProximityInvite
|
||||||
unk2 mustEqual 19
|
unk2 mustEqual 19
|
||||||
unk3 mustEqual 0
|
unk3 mustEqual 0
|
||||||
unk4 mustEqual 41530025L
|
unk4 mustEqual 41530025L
|
||||||
|
|
@ -78,7 +78,7 @@ class SquadMembershipResponseTest extends Specification {
|
||||||
"decode (1-2)" in {
|
"decode (1-2)" in {
|
||||||
PacketCoding.decodePacket(string_12).require match {
|
PacketCoding.decodePacket(string_12).require match {
|
||||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||||
unk1 mustEqual SquadResponseType.Unk01
|
unk1 mustEqual SquadResponseType.ProximityInvite
|
||||||
unk2 mustEqual 18
|
unk2 mustEqual 18
|
||||||
unk3 mustEqual 0
|
unk3 mustEqual 0
|
||||||
unk4 mustEqual 41578085L
|
unk4 mustEqual 41578085L
|
||||||
|
|
@ -315,14 +315,14 @@ class SquadMembershipResponseTest extends Specification {
|
||||||
}
|
}
|
||||||
|
|
||||||
"encode (1-1)" in {
|
"encode (1-1)" in {
|
||||||
val msg = SquadMembershipResponse(SquadResponseType.Unk01, 19, 0, 41530025L, Some(0L), "", true, Some(None))
|
val msg = SquadMembershipResponse(SquadResponseType.ProximityInvite, 19, 0, 41530025L, Some(0L), "", true, Some(None))
|
||||||
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
pkt mustEqual string_11
|
pkt mustEqual string_11
|
||||||
}
|
}
|
||||||
|
|
||||||
"encode (1-2)" in {
|
"encode (1-2)" in {
|
||||||
val msg = SquadMembershipResponse(SquadResponseType.Unk01, 18, 0, 41578085L, Some(0L), "", true, Some(None))
|
val msg = SquadMembershipResponse(SquadResponseType.ProximityInvite, 18, 0, 41578085L, Some(0L), "", true, Some(None))
|
||||||
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
pkt mustEqual string_12
|
pkt mustEqual string_12
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ class CharacterDataTest extends Specification {
|
||||||
a.unk9 mustEqual 0
|
a.unk9 mustEqual 0
|
||||||
a.unkA mustEqual 0
|
a.unkA mustEqual 0
|
||||||
|
|
||||||
|
b.outfit_id mustEqual 316554L
|
||||||
b.outfit_name mustEqual "Black Beret Armoured Corps"
|
b.outfit_name mustEqual "Black Beret Armoured Corps"
|
||||||
b.outfit_logo mustEqual 23
|
b.outfit_logo mustEqual 23
|
||||||
b.backpack mustEqual false
|
b.backpack mustEqual false
|
||||||
|
|
@ -67,7 +68,6 @@ class CharacterDataTest extends Specification {
|
||||||
b.is_cloaking mustEqual false
|
b.is_cloaking mustEqual false
|
||||||
b.charging_pose mustEqual false
|
b.charging_pose mustEqual false
|
||||||
b.on_zipline.isEmpty mustEqual true
|
b.on_zipline.isEmpty mustEqual true
|
||||||
b.unk0 mustEqual 316554L
|
|
||||||
b.unk1 mustEqual false
|
b.unk1 mustEqual false
|
||||||
b.unk2 mustEqual false
|
b.unk2 mustEqual false
|
||||||
b.unk3 mustEqual false
|
b.unk3 mustEqual false
|
||||||
|
|
@ -174,6 +174,7 @@ class CharacterDataTest extends Specification {
|
||||||
a.unk9 mustEqual 0
|
a.unk9 mustEqual 0
|
||||||
a.unkA mustEqual 0
|
a.unkA mustEqual 0
|
||||||
|
|
||||||
|
b.outfit_id mustEqual 26L
|
||||||
b.outfit_name mustEqual "Black Beret Armoured Corps"
|
b.outfit_name mustEqual "Black Beret Armoured Corps"
|
||||||
b.outfit_logo mustEqual 23
|
b.outfit_logo mustEqual 23
|
||||||
b.backpack mustEqual false
|
b.backpack mustEqual false
|
||||||
|
|
@ -184,7 +185,6 @@ class CharacterDataTest extends Specification {
|
||||||
b.is_cloaking mustEqual false
|
b.is_cloaking mustEqual false
|
||||||
b.charging_pose mustEqual false
|
b.charging_pose mustEqual false
|
||||||
b.on_zipline.isEmpty mustEqual true
|
b.on_zipline.isEmpty mustEqual true
|
||||||
b.unk0 mustEqual 26L
|
|
||||||
b.unk1 mustEqual false
|
b.unk1 mustEqual false
|
||||||
b.unk2 mustEqual false
|
b.unk2 mustEqual false
|
||||||
b.unk3 mustEqual false
|
b.unk3 mustEqual false
|
||||||
|
|
@ -243,6 +243,7 @@ class CharacterDataTest extends Specification {
|
||||||
a.unk9 mustEqual 0
|
a.unk9 mustEqual 0
|
||||||
a.unkA mustEqual 0
|
a.unkA mustEqual 0
|
||||||
|
|
||||||
|
b.outfit_id mustEqual 529687L
|
||||||
b.outfit_name mustEqual "Original District"
|
b.outfit_name mustEqual "Original District"
|
||||||
b.outfit_logo mustEqual 23
|
b.outfit_logo mustEqual 23
|
||||||
b.backpack mustEqual true
|
b.backpack mustEqual true
|
||||||
|
|
@ -253,7 +254,6 @@ class CharacterDataTest extends Specification {
|
||||||
b.is_cloaking mustEqual false
|
b.is_cloaking mustEqual false
|
||||||
b.charging_pose mustEqual false
|
b.charging_pose mustEqual false
|
||||||
b.on_zipline.isEmpty mustEqual true
|
b.on_zipline.isEmpty mustEqual true
|
||||||
b.unk0 mustEqual 529687L
|
|
||||||
b.unk1 mustEqual false
|
b.unk1 mustEqual false
|
||||||
b.unk2 mustEqual false
|
b.unk2 mustEqual false
|
||||||
b.unk3 mustEqual false
|
b.unk3 mustEqual false
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ class DetailedCharacterDataTest extends Specification {
|
||||||
a.unk9 mustEqual 0
|
a.unk9 mustEqual 0
|
||||||
a.unkA mustEqual 65535
|
a.unkA mustEqual 65535
|
||||||
|
|
||||||
|
b.outfit_id mustEqual 0L
|
||||||
b.outfit_name mustEqual ""
|
b.outfit_name mustEqual ""
|
||||||
b.outfit_logo mustEqual 0
|
b.outfit_logo mustEqual 0
|
||||||
b.backpack mustEqual false
|
b.backpack mustEqual false
|
||||||
|
|
@ -97,7 +98,6 @@ class DetailedCharacterDataTest extends Specification {
|
||||||
b.is_cloaking mustEqual false
|
b.is_cloaking mustEqual false
|
||||||
b.charging_pose mustEqual false
|
b.charging_pose mustEqual false
|
||||||
b.on_zipline.isEmpty mustEqual true
|
b.on_zipline.isEmpty mustEqual true
|
||||||
b.unk0 mustEqual 0L
|
|
||||||
b.unk1 mustEqual false
|
b.unk1 mustEqual false
|
||||||
b.unk2 mustEqual false
|
b.unk2 mustEqual false
|
||||||
b.unk3 mustEqual false
|
b.unk3 mustEqual false
|
||||||
|
|
@ -275,6 +275,7 @@ class DetailedCharacterDataTest extends Specification {
|
||||||
a.unk9 mustEqual 0
|
a.unk9 mustEqual 0
|
||||||
a.unkA mustEqual 0
|
a.unkA mustEqual 0
|
||||||
|
|
||||||
|
b.outfit_id mustEqual 0L
|
||||||
b.outfit_name mustEqual ""
|
b.outfit_name mustEqual ""
|
||||||
b.outfit_logo mustEqual 0
|
b.outfit_logo mustEqual 0
|
||||||
b.backpack mustEqual false
|
b.backpack mustEqual false
|
||||||
|
|
@ -285,7 +286,6 @@ class DetailedCharacterDataTest extends Specification {
|
||||||
b.is_cloaking mustEqual false
|
b.is_cloaking mustEqual false
|
||||||
b.charging_pose mustEqual false
|
b.charging_pose mustEqual false
|
||||||
b.on_zipline.isEmpty mustEqual true
|
b.on_zipline.isEmpty mustEqual true
|
||||||
b.unk0 mustEqual 0L
|
|
||||||
b.unk1 mustEqual false
|
b.unk1 mustEqual false
|
||||||
b.unk2 mustEqual false
|
b.unk2 mustEqual false
|
||||||
b.unk3 mustEqual false
|
b.unk3 mustEqual false
|
||||||
|
|
@ -460,6 +460,7 @@ class DetailedCharacterDataTest extends Specification {
|
||||||
a.unk9 mustEqual 0
|
a.unk9 mustEqual 0
|
||||||
a.unkA mustEqual 65535
|
a.unkA mustEqual 65535
|
||||||
|
|
||||||
|
b.outfit_id mustEqual 0L
|
||||||
b.outfit_name mustEqual ""
|
b.outfit_name mustEqual ""
|
||||||
b.outfit_logo mustEqual 0
|
b.outfit_logo mustEqual 0
|
||||||
b.backpack mustEqual false
|
b.backpack mustEqual false
|
||||||
|
|
@ -470,7 +471,6 @@ class DetailedCharacterDataTest extends Specification {
|
||||||
b.is_cloaking mustEqual false
|
b.is_cloaking mustEqual false
|
||||||
b.charging_pose mustEqual false
|
b.charging_pose mustEqual false
|
||||||
b.on_zipline.isEmpty mustEqual true
|
b.on_zipline.isEmpty mustEqual true
|
||||||
b.unk0 mustEqual 0L
|
|
||||||
b.unk1 mustEqual false
|
b.unk1 mustEqual false
|
||||||
b.unk2 mustEqual false
|
b.unk2 mustEqual false
|
||||||
b.unk3 mustEqual false
|
b.unk3 mustEqual false
|
||||||
|
|
@ -687,6 +687,7 @@ class DetailedCharacterDataTest extends Specification {
|
||||||
a.unk9 mustEqual 0
|
a.unk9 mustEqual 0
|
||||||
a.unkA mustEqual 0
|
a.unkA mustEqual 0
|
||||||
|
|
||||||
|
b.outfit_id mustEqual 556539L
|
||||||
b.outfit_name mustEqual ""
|
b.outfit_name mustEqual ""
|
||||||
b.outfit_logo mustEqual 14
|
b.outfit_logo mustEqual 14
|
||||||
b.backpack mustEqual false
|
b.backpack mustEqual false
|
||||||
|
|
@ -697,7 +698,6 @@ class DetailedCharacterDataTest extends Specification {
|
||||||
b.is_cloaking mustEqual false
|
b.is_cloaking mustEqual false
|
||||||
b.charging_pose mustEqual false
|
b.charging_pose mustEqual false
|
||||||
b.on_zipline.isEmpty mustEqual true
|
b.on_zipline.isEmpty mustEqual true
|
||||||
b.unk0 mustEqual 556539L
|
|
||||||
b.unk1 mustEqual false
|
b.unk1 mustEqual false
|
||||||
b.unk2 mustEqual false
|
b.unk2 mustEqual false
|
||||||
b.unk3 mustEqual false
|
b.unk3 mustEqual false
|
||||||
|
|
@ -1208,7 +1208,7 @@ class DetailedCharacterDataTest extends Specification {
|
||||||
a.unk9 mustEqual 10
|
a.unk9 mustEqual 10
|
||||||
a.unkA mustEqual 1
|
a.unkA mustEqual 1
|
||||||
|
|
||||||
b.unk0 mustEqual 25044L
|
b.outfit_id mustEqual 25044L
|
||||||
b.outfit_name mustEqual "Black Armored Reapers"
|
b.outfit_name mustEqual "Black Armored Reapers"
|
||||||
b.outfit_logo mustEqual 15
|
b.outfit_logo mustEqual 15
|
||||||
b.unk1 mustEqual false
|
b.unk1 mustEqual false
|
||||||
|
|
@ -1355,7 +1355,7 @@ class DetailedCharacterDataTest extends Specification {
|
||||||
a.unk9 mustEqual 0
|
a.unk9 mustEqual 0
|
||||||
a.unkA mustEqual 0
|
a.unkA mustEqual 0
|
||||||
|
|
||||||
b.unk0 mustEqual 16507L
|
b.outfit_id mustEqual 16507L
|
||||||
b.outfit_name mustEqual "Hooked On Insanity"
|
b.outfit_name mustEqual "Hooked On Insanity"
|
||||||
b.outfit_logo mustEqual 5
|
b.outfit_logo mustEqual 5
|
||||||
b.unk1 mustEqual false
|
b.unk1 mustEqual false
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ class MountedVehiclesTest extends Specification {
|
||||||
a.unk9 mustEqual 0
|
a.unk9 mustEqual 0
|
||||||
a.unkA mustEqual 0
|
a.unkA mustEqual 0
|
||||||
|
|
||||||
|
b.outfit_id mustEqual 316554L
|
||||||
b.outfit_name mustEqual "Black Beret Armoured Corps"
|
b.outfit_name mustEqual "Black Beret Armoured Corps"
|
||||||
b.outfit_logo mustEqual 23
|
b.outfit_logo mustEqual 23
|
||||||
b.backpack mustEqual false
|
b.backpack mustEqual false
|
||||||
|
|
@ -85,7 +86,6 @@ class MountedVehiclesTest extends Specification {
|
||||||
b.is_cloaking mustEqual false
|
b.is_cloaking mustEqual false
|
||||||
b.charging_pose mustEqual false
|
b.charging_pose mustEqual false
|
||||||
b.on_zipline.isEmpty mustEqual true
|
b.on_zipline.isEmpty mustEqual true
|
||||||
b.unk0 mustEqual 316554L
|
|
||||||
b.unk1 mustEqual false
|
b.unk1 mustEqual false
|
||||||
b.unk2 mustEqual false
|
b.unk2 mustEqual false
|
||||||
b.unk3 mustEqual false
|
b.unk3 mustEqual false
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue