evict players from the outfit. no longer shall you spy on our stale info!

update packet types
This commit is contained in:
Resaec 2025-08-31 03:05:31 +02:00
parent 8dcf678045
commit c84bf9ae74
12 changed files with 163 additions and 119 deletions

View file

@ -30,7 +30,7 @@ import net.psforever.objects.vehicles.Utility
import net.psforever.objects.vital.Vitality
import net.psforever.objects.zones.{ZoneProjectile, Zoning}
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.OutfitEventAction.{OutfitInfo, OutfitRankNames, Unk0, Unk1}
import net.psforever.packet.game.OutfitEventAction.{OutfitInfo, OutfitRankNames, Initial, Unk1}
import net.psforever.packet.game.{ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, ChatMsg, CollisionIs, ConnectToWorldRequestMessage, CreateShortcutMessage, DeadState, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, OutfitEvent, OutfitMemberEvent, OutfitMembershipRequest, OutfitMembershipResponse, OutfitRequest, OutfitRequestAction, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, RequestDestroyMessage, TargetingImplantRequest, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
import net.psforever.services.RemoverActor
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
@ -673,9 +673,9 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
def handleOutfitRequest(pkt: OutfitRequest): Unit = {
pkt match {
case OutfitRequest(_, OutfitRequestAction.Unk3(true)) =>
case OutfitRequest(_, OutfitRequestAction.OutfitWindowOpen(true)) =>
case OutfitRequest(_, OutfitRequestAction.Unk3(false)) =>
case OutfitRequest(_, OutfitRequestAction.OutfitWindowOpen(false)) =>
case _ =>
}

View file

@ -37,7 +37,7 @@ import net.psforever.objects.vital.etc.SuicideReason
import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.zones.{ZoneProjectile, Zoning}
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.OutfitEventAction.{OutfitInfo, OutfitRankNames, Unk2}
import net.psforever.packet.game.OutfitEventAction.{OutfitInfo, OutfitRankNames, Update}
import net.psforever.packet.game.{ActionCancelMessage, ActionResultMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestAction, CharacterRequestMessage, ChatMsg, CollisionIs, ConnectToWorldRequestMessage, CreateShortcutMessage, DeadState, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, OutfitEvent, OutfitMembershipRequest, OutfitMembershipRequestAction, OutfitMembershipResponse, OutfitRequest, OutfitRequestAction, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, RequestDestroyMessage, TargetingImplantRequest, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
import net.psforever.services.account.{AccountPersistenceService, RetrieveAccountData}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
@ -836,12 +836,12 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
case OutfitRequest(_, OutfitRequestAction.Ranks(List(r1, r2, r3, r4, r5, r6, r7, r8))) =>
SessionOutfitHandlers.HandleOutfitRank(zones, List(r1, r2, r3, r4, r5, r6, r7, r8), player)
case OutfitRequest(_, OutfitRequestAction.Unk3(true)) =>
case OutfitRequest(_, OutfitRequestAction.OutfitWindowOpen(true)) =>
SessionOutfitHandlers.HandleViewOutfitWindow(zones, player, player.outfit_id)
case OutfitRequest(_, OutfitRequestAction.Unk3(false)) =>
case OutfitRequest(_, OutfitRequestAction.OutfitWindowOpen(false)) =>
case OutfitRequest(_, OutfitRequestAction.Unk4(true)) =>
case OutfitRequest(_, OutfitRequestAction.OutfitListWindowOpen(true)) =>
SessionOutfitHandlers.HandleGetOutfitList(player)
case _ =>

View file

@ -5,7 +5,7 @@ import io.getquill.{ActionReturning, EntityQuery, Insert, PostgresJAsyncContext,
import net.psforever.objects.avatar.PlayerControl
import net.psforever.objects.zones.Zone
import net.psforever.objects.Player
import net.psforever.packet.game.OutfitEventAction.{OutfitInfo, OutfitRankNames, Unk0, Unk1, Unk2}
import net.psforever.packet.game.OutfitEventAction.{Leaving, OutfitInfo, OutfitRankNames, Initial, Unk1, Update, UpdateMemberCount}
import net.psforever.packet.game.OutfitMembershipResponse.PacketType.CreateResponse
import net.psforever.packet.game._
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
@ -63,7 +63,7 @@ object SessionOutfitHandlers {
outfit.created.atZone(java.time.ZoneOffset.UTC).toInstant.toEpochMilli / 1000
PlayerControl.sendResponse(player.Zone, player.Name,
OutfitEvent(outfit.id, Unk2(
OutfitEvent(outfit.id, Update(
OutfitInfo(
outfit.name, 0, 0, 1,
OutfitRankNames("", "", "", "", "", "", "", ""),
@ -143,16 +143,16 @@ object SessionOutfitHandlers {
invited.CharId, outfitInvite.sentFrom.CharId, invited.Name, outfit.name, flag = true))
PlayerControl.sendResponse(outfitInvite.sentFrom.Zone, outfitInvite.sentFrom.Name,
OutfitEvent(outfitId, OutfitEventAction.Unk5(memberCount)))
OutfitEvent(outfitId, UpdateMemberCount(memberCount)))
PlayerControl.sendResponse(outfitInvite.sentFrom.Zone, outfitInvite.sentFrom.Name,
OutfitMemberEvent(outfitId, invited.CharId,
OutfitMemberEventAction.Unk0(invited.Name, 0, 0, 0,
OutfitMemberEventAction.Update(invited.Name, 0, 0, 0,
OutfitMemberEventAction.PacketType.Padding, 0)))
val seconds: Long = outfit.created.atZone(java.time.ZoneOffset.UTC).toInstant.toEpochMilli / 1000
PlayerControl.sendResponse(invited.Zone, invited.Name,
OutfitEvent(outfitId, Unk0(OutfitInfo(
OutfitEvent(outfitId, Initial(OutfitInfo(
outfit.name, points, points, memberCount,
OutfitRankNames("", "", "", "", "", "", "", ""),
outfit.motd.getOrElse(""),
@ -211,12 +211,12 @@ object SessionOutfitHandlers {
case (deleted, _) =>
if (deleted > 0) {
PlayerControl.sendResponse(kickedBy.Zone, kickedBy.Name,
OutfitMemberEvent(kickedBy.outfit_id, kickedId, OutfitMemberEventAction.Unk1()))
OutfitMemberEvent(kickedBy.outfit_id, kickedId, OutfitMemberEventAction.Kicked()))
zones.filter(z => z.AllPlayers.nonEmpty).flatMap(_.AllPlayers)
.filter(p => p.outfit_id == kickedBy.outfit_id).foreach(outfitMember =>
PlayerControl.sendResponse(outfitMember.Zone, outfitMember.Name,
OutfitMemberEvent(kickedBy.outfit_id, kickedId, OutfitMemberEventAction.Unk1()))
OutfitMemberEvent(kickedBy.outfit_id, kickedId, OutfitMemberEventAction.Kicked()))
)
session.chat.LeaveChannel(OutfitChannel(kickedBy.outfit_id))
@ -238,8 +238,13 @@ object SessionOutfitHandlers {
case (deleted, _) =>
if (deleted > 0) {
findPlayerByIdForOutfitAction(zones, kickedId, kickedBy).foreach { kicked =>
PlayerControl.sendResponse(kicked.Zone, kicked.Name,
OutfitMembershipResponse(OutfitMembershipResponse.PacketType.Kick, 0, 1,
OutfitEvent(kickedBy.outfit_id, Leaving())
)
PlayerControl.sendResponse(kicked.Zone, kicked.Name,
OutfitMembershipResponse(OutfitMembershipResponse.PacketType.YouGotKicked, 0, 1,
kickedBy.CharId, kicked.CharId, kicked.Name, kickedBy.Name, flag = false))
kicked.Zone.AvatarEvents ! AvatarServiceMessage(kicked.Zone.id,
@ -251,7 +256,7 @@ object SessionOutfitHandlers {
kicked.outfit_id = 0
kicked.outfit_name = ""
PlayerControl.sendResponse(kicked.Zone, kicked.Name,
OutfitMemberEvent(kickedBy.outfit_id, kickedId, OutfitMemberEventAction.Unk1()))
OutfitMemberEvent(kickedBy.outfit_id, kickedId, OutfitMemberEventAction.Kicked()))
}
val avatarName: Future[Option[String]] =
ctx.run(
@ -260,15 +265,15 @@ object SessionOutfitHandlers {
avatarName.foreach {
case Some(name) => PlayerControl.sendResponse(kickedBy.Zone, kickedBy.Name,
OutfitMembershipResponse(OutfitMembershipResponse.PacketType.Kick, 0, 1, kickedBy.CharId, kickedId, name, "", flag = true))
OutfitMembershipResponse(OutfitMembershipResponse.PacketType.YouKicked, 0, 1, kickedBy.CharId, kickedId, name, "", flag = true))
case None => PlayerControl.sendResponse(kickedBy.Zone, kickedBy.Name,
OutfitMembershipResponse(OutfitMembershipResponse.PacketType.Kick, 0, 1, kickedBy.CharId, kickedId, "NameNotFound", "", flag = true))
OutfitMembershipResponse(OutfitMembershipResponse.PacketType.YouKicked, 0, 1, kickedBy.CharId, kickedId, "NameNotFound", "", flag = true))
}
zones.filter(z => z.AllPlayers.nonEmpty).flatMap(_.AllPlayers)
.filter(p => p.outfit_id == kickedBy.outfit_id).foreach(outfitMember =>
PlayerControl.sendResponse(outfitMember.Zone, outfitMember.Name,
OutfitMemberEvent(kickedBy.outfit_id, kickedId, OutfitMemberEventAction.Unk1()))
OutfitMemberEvent(kickedBy.outfit_id, kickedId, OutfitMemberEventAction.Kicked()))
)
// this needs to be the kicked player
// session.chat.LeaveChannel(OutfitChannel(kickedBy.outfit_id))
@ -303,7 +308,7 @@ object SessionOutfitHandlers {
PlayerControl.sendResponse(
zone, outfitMember.Name,
OutfitMemberEvent(outfit_id, promoter.avatar.id,
OutfitMemberEventAction.Unk0(promoter.Name, 6, owner_points, 0, OutfitMemberEventAction.PacketType.Padding, 0)))
OutfitMemberEventAction.Update(promoter.Name, 6, owner_points, 0, OutfitMemberEventAction.PacketType.Padding, 0)))
})
})
}
@ -327,7 +332,7 @@ object SessionOutfitHandlers {
PlayerControl.sendResponse(
zone, player.Name,
OutfitMemberEvent(outfit_id, promoted.avatar.id,
OutfitMemberEventAction.Unk0(promoted.Name, newRank, member_points, 0, OutfitMemberEventAction.PacketType.Padding, 0)))
OutfitMemberEventAction.Update(promoted.Name, newRank, member_points, 0, OutfitMemberEventAction.PacketType.Padding, 0)))
})
})
}
@ -356,7 +361,7 @@ object SessionOutfitHandlers {
val seconds: Long = outfit.created.atZone(java.time.ZoneOffset.UTC).toInstant.toEpochMilli / 1000
PlayerControl.sendResponse(player.Zone, player.Name,
OutfitEvent(outfit.id, Unk0(OutfitInfo(
OutfitEvent(outfit.id, Initial(OutfitInfo(
outfit.name,
totalPoints,
totalPoints,
@ -382,7 +387,7 @@ object SessionOutfitHandlers {
}
PlayerControl.sendResponse(player.Zone, player.Name,
OutfitMemberEvent(outfit.id, avatarId,
OutfitMemberEventAction.Unk0(
OutfitMemberEventAction.Update(
avatarName,
rank,
points,
@ -438,7 +443,7 @@ object SessionOutfitHandlers {
// send to all online players in outfit
val outfit_event = OutfitEvent(
outfit_id,
Unk2(
Update(
OutfitInfo(
outfit_name = outfit.name,
outfit_points1 = totalPoints,
@ -500,7 +505,7 @@ object SessionOutfitHandlers {
// send to all online players in outfit
val outfit_event = OutfitEvent(
outfit_id,
Unk2(
Update(
OutfitInfo(
outfit_name = outfit.name,
outfit_points1 = totalPoints,
@ -555,7 +560,7 @@ object SessionOutfitHandlers {
val seconds: Long = outfit.created.atZone(java.time.ZoneOffset.UTC).toInstant.toEpochMilli / 1000
PlayerControl.sendResponse(player.Zone, player.Name,
OutfitEvent(outfitId, Unk2(OutfitInfo(
OutfitEvent(outfitId, Update(OutfitInfo(
outfit.name, points, points, memberCount,
OutfitRankNames(outfit.rank0.getOrElse(""), outfit.rank1.getOrElse(""), outfit.rank2.getOrElse(""),
outfit.rank3.getOrElse(""), outfit.rank4.getOrElse(""), outfit.rank5.getOrElse(""),
@ -744,7 +749,7 @@ object SessionOutfitHandlers {
}
}
def updateMemberRankById(outfit_id: Long, avatar_id: Long, rank: Int): Quoted[Update[Outfitmember]] = quote {
def updateMemberRankById(outfit_id: Long, avatar_id: Long, rank: Int): Quoted[io.getquill.Update[Outfitmember]] = quote {
query[Outfitmember]
.filter(_.outfit_id == lift(outfit_id))
.filter(_.avatar_id == lift(avatar_id))
@ -759,7 +764,7 @@ object SessionOutfitHandlers {
}
}
def updateOutfitOwnerById(outfit_id: Long, owner_id: Long): Quoted[Update[Outfit]] = quote {
def updateOutfitOwnerById(outfit_id: Long, owner_id: Long): Quoted[io.getquill.Update[Outfit]] = quote {
query[Outfit]
.filter(_.id == lift(outfit_id))
.update(_.owner_id -> lift(owner_id))
@ -775,7 +780,7 @@ object SessionOutfitHandlers {
}
}
def updateOutfitMotdById(outfit_id: Long, motd: Option[String]): Quoted[Update[Outfit]] = quote {
def updateOutfitMotdById(outfit_id: Long, motd: Option[String]): Quoted[io.getquill.Update[Outfit]] = quote {
query[Outfit]
.filter(_.id == lift(outfit_id))
.update(_.motd -> lift(motd))
@ -789,7 +794,7 @@ object SessionOutfitHandlers {
}
}
def updateOutfitRanksById(outfit_id: Long, list: List[Option[String]]): Quoted[Update[Outfit]] = {
def updateOutfitRanksById(outfit_id: Long, list: List[Option[String]]): Quoted[io.getquill.Update[Outfit]] = {
// Normalize: turn empty strings into None
val normalized = list.map {

View file

@ -50,26 +50,55 @@ object OutfitEventAction {
unk25: Long,
)
final case class Unk0(
/**
* Initial
*
* Send at the start of an OutfitWindow info dump.
*
* Not always complete, seen as an initialization after login, join or while outfit is in formation.
* @param outfit_info
*/
final case class Initial(
outfit_info: OutfitInfo
) extends OutfitEventAction(code = 0)
final case class Unk1(
) extends OutfitEventAction(code = 1)
final case class Unk2(
/**
* Update
*
* Send after changing outfit Ranks, MOTD and other situations.
* @param outfit_info
*/
final case class Update(
outfit_info: OutfitInfo,
) extends OutfitEventAction(code = 2)
final case class Unk3(
/**
* Send to players to tell them they left the outfit.
*
* Resets them to behave like they have no outfit.
* Will have them open the OutfitListWindow instead of the OutfitWindow.
*/
final case class Leaving(
) extends OutfitEventAction(code = 3)
/**
* Used to switch from the temporary "invalid" outfit ID used while formation to a valid ID used from that point on.
* @param new_outfit_id the new ID that represents this specific outfit in the DB
*/
final case class UpdateOutfitId(
new_outfit_id: Long,
) extends OutfitEventAction(code = 4)
final case class Unk5(
unk1: Long,
/**
* Used to tell outfit members that the member count changed.
* Send after InviteAccept or Kick actions
* @param member_count
*/
final case class UpdateMemberCount(
member_count: Long,
) extends OutfitEventAction(code = 5)
final case class Unknown(badCode: Int, data: BitVector) extends OutfitEventAction(badCode)
@ -126,35 +155,35 @@ object OutfitEventAction {
}
)
val Unk0Codec: Codec[Unk0] = (
val Unk0Codec: Codec[Initial] = (
("outfit_info" | InfoCodec)
).xmap[Unk0](
).xmap[Initial](
{
case info =>
Unk0(info)
Initial(info)
},
{
case Unk0(info) =>
case Initial(info) =>
info
}
)
val Unk1Codec: Codec[Unk1] = PacketHelpers.emptyCodec(Unk1())
val Unk2Codec: Codec[Unk2] = (
val Unk2Codec: Codec[Update] = (
("outfit_info" | InfoCodec)
).xmap[Unk2](
).xmap[Update](
{
case info =>
Unk2(info)
Update(info)
},
{
case Unk2(info) =>
case Update(info) =>
info
}
)
val Unk3Codec: Codec[Unk3] = PacketHelpers.emptyCodec(Unk3())
val Unk3Codec: Codec[Leaving] = PacketHelpers.emptyCodec(Leaving())
val UpdateOutfitIdCodec: Codec[UpdateOutfitId] = ( // update outfit_id? // 2016.03.18 #10640 // after this packet the referenced id changes to the new one, old is not used again
("new_outfit_id" | uint32L)
@ -169,15 +198,15 @@ object OutfitEventAction {
}
)
val Unk5Codec: Codec[Unk5] = (
val UpdateMemberCountCodec: Codec[UpdateMemberCount] = (
("" | uint32L)
).xmap[Unk5](
).xmap[UpdateMemberCount](
{
case u1 =>
Unk5(u1)
UpdateMemberCount(u1)
},
{
case Unk5(u1) =>
case UpdateMemberCount(u1) =>
u1
}
)
@ -220,7 +249,7 @@ object OutfitEvent extends Marshallable[OutfitEvent] {
val Unk2: PacketType.Value = Value(2) // send after creating an outfit // normal info, same as Unk0
val Unk3: PacketType.Value = Value(3) // below
val UpdateOutfitId: PacketType.Value = Value(4)
val Unk5: PacketType.Value = Value(5)
val UpdateMemberCount: PacketType.Value = Value(5)
val Unk6: PacketType.Value = Value(6)
val Unk7: PacketType.Value = Value(7)
@ -237,7 +266,7 @@ object OutfitEvent extends Marshallable[OutfitEvent] {
case 2 => Unk2Codec // sent after /outfitcreate and on login if in an outfit
case 3 => Unk3Codec
case 4 => UpdateOutfitIdCodec
case 5 => Unk5Codec
case 5 => UpdateMemberCountCodec
case 6 => unknownCodec(action = code)
case 7 => unknownCodec(action = code)

View file

@ -33,20 +33,30 @@ object OutfitMemberEventAction {
}
/*
action is unimplemented! if action == 0 unk2 will contain one additional uint32L
padding contains one uint4L of padding. may contain uint32L of unknown data depending on action
*/
final case class Unk0(
/**
*
* Update
*
* Update is used to inform outfit members about a new member.
* Gets send after an InviteAccept or Rank changes.
*
* @param member_name
* @param rank
* @param points client divides this by 100
* @param last_online seconds ago from current time, 0 if online
* @param action should always be 1, otherwise there will be actual data in padding. not implemented!
* @param padding should always be 0, 4 bits of padding // only contains data if action is 0
*/
final case class Update(
member_name: String,
rank: Int,
points: Long, // client divides this by 100
last_online: Long, // seconds ago from current time, 0 if online
action: PacketType.Type, // should always be 1, otherwise there will be actual data in padding. not implemented!
padding: Int // should always be 0, 4 bits of padding // only contains data if action is 0
points: Long,
last_online: Long,
action: PacketType.Type,
padding: Int
) extends OutfitMemberEventAction(code = 0)
final case class Unk1(
final case class Kicked(
) extends OutfitMemberEventAction(code = 1)
final case class Unknown(badCode: Int, data: BitVector) extends OutfitMemberEventAction(badCode)
@ -58,25 +68,25 @@ object OutfitMemberEventAction {
object Codecs {
private val everFailCondition = conditional(included = false, bool)
val Unk0Codec: Codec[Unk0] = (
("member_name" | PacketHelpers.encodedWideStringAligned(6)) :: // from here is packet_type == 0 only
val UpdateCodec: Codec[Update] = (
("member_name" | PacketHelpers.encodedWideStringAligned(6)) ::
("rank" | uint(3)) ::
("points" | uint32L) ::
("last_login" | uint32L) ::
("action" | OutfitMemberEventAction.PacketType.codec) ::
("padding" | uint4L)
).xmap[Unk0](
).xmap[Update](
{
case member_name :: rank :: points :: last_login :: action :: padding :: HNil =>
Unk0(member_name, rank, points, last_login, action, padding)
Update(member_name, rank, points, last_login, action, padding)
},
{
case Unk0(member_name, rank, points, last_login, action, padding) =>
case Update(member_name, rank, points, last_login, action, padding) =>
member_name :: rank :: points :: last_login :: action :: padding :: HNil
}
)
val Unk1Codec: Codec[Unk1] = PacketHelpers.emptyCodec(Unk1())
val KickedCodec: Codec[Kicked] = PacketHelpers.emptyCodec(Kicked())
/**
* A common form for known action code indexes with an unknown purpose and transformation is an "Unknown" object.
@ -109,8 +119,8 @@ object OutfitMemberEvent extends Marshallable[OutfitMemberEvent] {
object PacketType extends Enumeration {
type Type = Value
val Unk0: PacketType.Value = Value(0)
val Unk1: PacketType.Value = Value(1) // Info: Player has been invited / response to OutfitMembershipRequest Unk2 for that player
val Update: PacketType.Value = Value(0)
val Kicked: PacketType.Value = Value(1)
val Unk2: PacketType.Value = Value(2)
val Unk3: PacketType.Value = Value(3)
@ -122,8 +132,8 @@ object OutfitMemberEvent extends Marshallable[OutfitMemberEvent] {
import scala.annotation.switch
((code: @switch) match {
case 0 => Unk0Codec
case 1 => Unk1Codec
case 0 => UpdateCodec
case 1 => KickedCodec
case 2 => unknownCodec(code)
case 3 => unknownCodec(code)

View file

@ -9,7 +9,7 @@ import scodec.codecs._
import shapeless.{::, HNil}
final case class OutfitMembershipRequest(
outfit_id: Long,
requester_id: Long,
action: OutfitMembershipRequestAction
) extends PlanetSideGamePacket {
type Packet = OutfitMembershipRequest
@ -37,8 +37,8 @@ object OutfitMembershipRequestAction {
) extends OutfitMembershipRequestAction(code = 1)
final case class Invite(
avatar_id: Long,
member_name: String,
target_id: Long,
target_name: String,
) extends OutfitMembershipRequestAction(code = 2)
final case class AcceptInvite(
@ -50,19 +50,19 @@ object OutfitMembershipRequestAction {
) extends OutfitMembershipRequestAction(code = 4)
final case class CancelInvite(
avatar_id: Long,
member_name: String,
target_id: Long,
target_name: String,
) extends OutfitMembershipRequestAction(code = 5)
final case class Kick(
avatar_id: Long,
member_name: String,
target_id: Long,
target_name: String,
) extends OutfitMembershipRequestAction(code = 6)
final case class SetRank(
avatar_id: Long,
target_id: Long,
rank: Int,
member_name: String,
target_name: String,
) extends OutfitMembershipRequestAction(code = 7)
final case class Unknown(badCode: Int, data: BitVector) extends OutfitMembershipRequestAction(badCode)

View file

@ -12,7 +12,7 @@ final case class OutfitMembershipResponse(
packet_type: OutfitMembershipResponse.PacketType.Type,
unk0: Int,
unk1: Int,
outfit_id: Long,
requester_id: Long,
target_id: Long,
str1: String,
str2: String,
@ -34,8 +34,8 @@ object OutfitMembershipResponse extends Marshallable[OutfitMembershipResponse] {
val Invite: PacketType.Value = Value(1) // response to OutfitMembershipRequest Unk2 for that player
val InviteAccepted: PacketType.Value = Value(2)
val InviteRejected: PacketType.Value = Value(3)
val Unk4: PacketType.Value = Value(4)
val Kick: PacketType.Value = Value(5)
val YouGotKicked: PacketType.Value = Value(4)
val YouKicked: PacketType.Value = Value(5)
val Unk6: PacketType.Value = Value(6) // 6 and 7 seen as failed decodes, validity unknown
val Unk7: PacketType.Value = Value(7)

View file

@ -8,7 +8,7 @@ import scodec.codecs._
import shapeless.{::, HNil}
final case class OutfitRequest(
outfit_id: Long,
requester_id: Long,
action: OutfitRequestAction
) extends PlanetSideGamePacket {
type Packet = OutfitRequest
@ -42,13 +42,13 @@ object OutfitRequestAction {
* na
* @param unk na
*/
final case class Unk3(menuOpen: Boolean) extends OutfitRequestAction(code = 3)
final case class OutfitWindowOpen(menuOpen: Boolean) extends OutfitRequestAction(code = 3)
/**
* na
* @param unk na
*/
final case class Unk4(menuOpen: Boolean) extends OutfitRequestAction(code = 4)
final case class OutfitListWindowOpen(menuOpen: Boolean) extends OutfitRequestAction(code = 4)
/**
* na
@ -116,24 +116,24 @@ object OutfitRequest extends Marshallable[OutfitRequest] {
/**
* na
*/
private val unk3Codec: Codec[OutfitRequestAction] = bool.hlist.xmap[OutfitRequestAction] (
private val OutfitWindowOpenCodec: Codec[OutfitRequestAction] = bool.hlist.xmap[OutfitRequestAction] (
{
case value :: HNil => OutfitRequestAction.Unk3(value)
case value :: HNil => OutfitRequestAction.OutfitWindowOpen(value)
},
{
case OutfitRequestAction.Unk3(value) => value :: HNil
case OutfitRequestAction.OutfitWindowOpen(value) => value :: HNil
}
)
/**
* na
*/
private val unk4Codec: Codec[OutfitRequestAction] = bool.hlist.xmap[OutfitRequestAction] (
private val OutfitListWindowOpenCodec: Codec[OutfitRequestAction] = bool.hlist.xmap[OutfitRequestAction] (
{
case value :: HNil => OutfitRequestAction.Unk4(value)
case value :: HNil => OutfitRequestAction.OutfitListWindowOpen(value)
},
{
case OutfitRequestAction.Unk4(value) => value :: HNil
case OutfitRequestAction.OutfitListWindowOpen(value) => value :: HNil
}
)
@ -151,8 +151,8 @@ object OutfitRequest extends Marshallable[OutfitRequest] {
val Motd: PacketType.Value = Value(0)
val Rank: PacketType.Value = Value(1)
val Unk2: PacketType.Value = Value(2)
val Detail: PacketType.Value = Value(3)
val List: PacketType.Value = Value(4) // sent by client if menu is either open (true) or closed (false)
val OutfitWindowOpen: PacketType.Value = Value(3)
val OutfitListWindowOpen: PacketType.Value = Value(4) // sent by client if menu is either open (true) or closed (false)
implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uintL(3))
}
@ -165,8 +165,8 @@ object OutfitRequest extends Marshallable[OutfitRequest] {
case 0 => MotdCodec
case 1 => RankCodec
case 2 => unk2Codec
case 3 => unk3Codec
case 4 => unk4Codec
case 3 => OutfitWindowOpenCodec
case 4 => OutfitListWindowOpenCodec
case _ => failCodec(code)
}
}

View file

@ -53,7 +53,7 @@ class OutfitEventTest extends Specification {
PacketCoding.decodePacket(unk0_ABC).require match {
case OutfitEvent(outfit_guid, action) =>
outfit_guid mustEqual 25044
action mustEqual Unk0(
action mustEqual Initial(
OutfitInfo(
outfit_name = "Black Armored Reapers",
outfit_points1 = 223190045,
@ -78,7 +78,7 @@ class OutfitEventTest extends Specification {
"encode Unk0 ABC" in {
val msg = OutfitEvent(
25044,
Unk0(
Initial(
OutfitInfo(
outfit_name = "Black Armored Reapers",
outfit_points1 = 223190045,
@ -125,7 +125,7 @@ class OutfitEventTest extends Specification {
PacketCoding.decodePacket(unk2_ABC).require match {
case OutfitEvent(outfit_guid, action) =>
outfit_guid mustEqual 2147418113L
action mustEqual Unk2(OutfitInfo(
action mustEqual Update(OutfitInfo(
outfit_name = "PlanetSide_Forever_Vanu",
outfit_points1 = 0,
outfit_points2 = 0,
@ -148,7 +148,7 @@ class OutfitEventTest extends Specification {
"encode Unk2 ABC" in {
val msg = OutfitEvent(
2147418113L,
Unk2(
Update(
OutfitInfo(
outfit_name = "PlanetSide_Forever_Vanu",
outfit_points1 = 0,
@ -175,7 +175,7 @@ class OutfitEventTest extends Specification {
PacketCoding.decodePacket(unk3_ABC).require match {
case OutfitEvent(outfit_guid, action) =>
outfit_guid mustEqual 2147418113L
action mustEqual Unk3()
action mustEqual Leaving()
case _ =>
ko
}
@ -184,7 +184,7 @@ class OutfitEventTest extends Specification {
"encode Unk3 ABC" in {
val msg = OutfitEvent(
2147418113L,
Unk3()
Leaving()
)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
@ -219,8 +219,8 @@ class OutfitEventTest extends Specification {
PacketCoding.decodePacket(unk5_ABC).require match {
case OutfitEvent(outfit_guid, action) =>
outfit_guid mustEqual 2147418113L
action mustEqual Unk5(
unk1 = 2,
action mustEqual UpdateMemberCount(
member_count = 2,
)
case _ =>
ko
@ -230,8 +230,8 @@ class OutfitEventTest extends Specification {
"encode Unk5 ABC" in {
val msg = OutfitEvent(
2147418113L,
Unk5(
unk1 = 2,
UpdateMemberCount(
member_count = 2,
)
)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector

View file

@ -22,7 +22,7 @@ class OutfitMemberEventTest extends Specification {
"decode Lazer padding" in {
PacketCoding.decodePacket(Lazer).require match {
case OutfitMemberEvent(outfit_id, member_id, Unk0(member_name, rank, points, last_login, action, padding)) =>
case OutfitMemberEvent(outfit_id, member_id, Update(member_name, rank, points, last_login, action, padding)) =>
outfit_id mustEqual 6418
member_id mustEqual 705344
member_name mustEqual "Lazer1982"
@ -40,7 +40,7 @@ class OutfitMemberEventTest extends Specification {
val msg = OutfitMemberEvent(
outfit_id = 6418,
member_id = 705344,
Unk0(
Update(
member_name = "Lazer1982",
rank = 7,
points = 3134113,
@ -56,7 +56,7 @@ class OutfitMemberEventTest extends Specification {
"decode OpolE padding" in {
PacketCoding.decodePacket(OpolE).require match {
case OutfitMemberEvent(outfit_id, member_id, Unk0(member_name, rank, points, last_login, action, unk0_padding)) =>
case OutfitMemberEvent(outfit_id, member_id, Update(member_name, rank, points, last_login, action, unk0_padding)) =>
outfit_id mustEqual 6418
member_id mustEqual 42644970
member_name mustEqual "OpolE"
@ -74,7 +74,7 @@ class OutfitMemberEventTest extends Specification {
val msg = OutfitMemberEvent(
outfit_id = 6418,
member_id = 42644970,
Unk0(
Update(
member_name = "OpolE",
rank = 6,
points = 461901,
@ -91,7 +91,7 @@ class OutfitMemberEventTest extends Specification {
"decode Unk1" in {
PacketCoding.decodePacket(unk1).require match {
case OutfitMemberEvent(outfit_id, member_id, Unk1()) =>
case OutfitMemberEvent(outfit_id, member_id, Kicked()) =>
outfit_id mustEqual 529744
member_id mustEqual 41605263
case _ =>
@ -103,7 +103,7 @@ class OutfitMemberEventTest extends Specification {
val msg = OutfitMemberEvent(
outfit_id = 529744,
member_id = 41605263,
Unk1()
Kicked()
)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector

View file

@ -111,7 +111,7 @@ class OutfitMembershipResponseTest extends Specification {
"decode unk4" in {
PacketCoding.decodePacket(unk4).require match {
case OutfitMembershipResponse(packet_type, unk0, unk1, outfit_id, target_id, str1, str2, flag) =>
packet_type mustEqual PacketType.Unk4
packet_type mustEqual PacketType.YouGotKicked
unk0 mustEqual 0
unk1 mustEqual 0
outfit_id mustEqual 41593365
@ -125,7 +125,7 @@ class OutfitMembershipResponseTest extends Specification {
}
"encode unk4" in {
val msg = OutfitMembershipResponse(PacketType.Unk4, 0, 0, 41593365, 0, "", "", flag = true)
val msg = OutfitMembershipResponse(PacketType.YouGotKicked, 0, 0, 41593365, 0, "", "", flag = true)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual unk4
@ -134,7 +134,7 @@ class OutfitMembershipResponseTest extends Specification {
"decode unk5" in {
PacketCoding.decodePacket(unk5).require match {
case OutfitMembershipResponse(packet_type, unk0, unk1, outfit_id, target_id, str1, str2, flag) =>
packet_type mustEqual PacketType.Kick
packet_type mustEqual PacketType.YouKicked
unk0 mustEqual 0
unk1 mustEqual 1
outfit_id mustEqual 41593365
@ -148,7 +148,7 @@ class OutfitMembershipResponseTest extends Specification {
}
"encode unk5" in {
val msg = OutfitMembershipResponse(PacketType.Kick, 0, 1, 41593365, 41605263, "PSFoutfittest1", "", flag = true)
val msg = OutfitMembershipResponse(PacketType.YouKicked, 0, 1, 41593365, 41605263, "PSFoutfittest1", "", flag = true)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual unk5

View file

@ -46,7 +46,7 @@ class OutfitRequestTest extends Specification {
"decode Unk3" in {
PacketCoding.decodePacket(string6).require match {
case OutfitRequest(id, OutfitRequestAction.Unk3(value)) =>
case OutfitRequest(id, OutfitRequestAction.OutfitWindowOpen(value)) =>
id mustEqual 1176612L
value mustEqual true
case _ =>
@ -56,7 +56,7 @@ class OutfitRequestTest extends Specification {
"decode Unk4" in {
PacketCoding.decodePacket(string8).require match {
case OutfitRequest(id, OutfitRequestAction.Unk4(value)) =>
case OutfitRequest(id, OutfitRequestAction.OutfitListWindowOpen(value)) =>
id mustEqual 41588237L
value mustEqual true
case _ =>
@ -88,14 +88,14 @@ class OutfitRequestTest extends Specification {
}
"encode Unk3" in {
val msg = OutfitRequest(1176612L, OutfitRequestAction.Unk3(true))
val msg = OutfitRequest(1176612L, OutfitRequestAction.OutfitWindowOpen(true))
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual string6
}
"encode Unk4" in {
val msg = OutfitRequest(41588237L, OutfitRequestAction.Unk4(true))
val msg = OutfitRequest(41588237L, OutfitRequestAction.OutfitListWindowOpen(true))
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual string8