mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-07-15 16:34:42 +00:00
Add OutfitMemberEvent packet
Fix and improve some other Outfit packets and tests
This commit is contained in:
parent
4bc272c2f0
commit
d75ab1166f
6 changed files with 638 additions and 81 deletions
|
|
@ -474,7 +474,7 @@ object GamePacketOpcode extends Enumeration {
|
||||||
case 0x8f => game.OutfitEvent.decode
|
case 0x8f => game.OutfitEvent.decode
|
||||||
|
|
||||||
// OPCODES 0x90-9f
|
// OPCODES 0x90-9f
|
||||||
case 0x90 => noDecoder(OutfitMemberEvent)
|
case 0x90 => game.OutfitMemberEvent.decode
|
||||||
case 0x91 => game.OutfitMemberUpdate.decode
|
case 0x91 => game.OutfitMemberUpdate.decode
|
||||||
case 0x92 => game.PlanetsideStringAttributeMessage.decode
|
case 0x92 => game.PlanetsideStringAttributeMessage.decode
|
||||||
case 0x93 => game.DataChallengeMessage.decode
|
case 0x93 => game.DataChallengeMessage.decode
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package net.psforever.packet.game
|
||||||
|
|
||||||
import net.psforever.packet.GamePacketOpcode.Type
|
import net.psforever.packet.GamePacketOpcode.Type
|
||||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||||
|
import net.psforever.types.PlanetSideGUID
|
||||||
import scodec.{Attempt, Codec, Err}
|
import scodec.{Attempt, Codec, Err}
|
||||||
import scodec.bits.BitVector
|
import scodec.bits.BitVector
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
|
|
@ -10,7 +11,7 @@ import shapeless.{::, HNil}
|
||||||
|
|
||||||
final case class OutfitEvent(
|
final case class OutfitEvent(
|
||||||
request_type: OutfitEvent.RequestType.Type,
|
request_type: OutfitEvent.RequestType.Type,
|
||||||
unk1: Int,
|
outfit_guid: PlanetSideGUID,
|
||||||
action: OutfitEventAction
|
action: OutfitEventAction
|
||||||
) extends PlanetSideGamePacket {
|
) extends PlanetSideGamePacket {
|
||||||
type Packet = OutfitEvent
|
type Packet = OutfitEvent
|
||||||
|
|
@ -24,33 +25,79 @@ abstract class OutfitEventAction(val code: Int)
|
||||||
|
|
||||||
object OutfitEventAction {
|
object OutfitEventAction {
|
||||||
|
|
||||||
final case class CreatedOutfit(
|
final case class OutfitRankNames(
|
||||||
|
rank1: String,
|
||||||
|
rank2: String,
|
||||||
|
rank3: String,
|
||||||
|
rank4: String,
|
||||||
|
rank5: String,
|
||||||
|
rank6: String,
|
||||||
|
rank7: String,
|
||||||
|
rank8: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
final case class OutfitInfo(
|
||||||
|
unk1: Int,
|
||||||
unk2: Int,
|
unk2: Int,
|
||||||
unk3: Int,
|
|
||||||
unk4: Int,
|
|
||||||
outfit_name: String,
|
outfit_name: String,
|
||||||
unk6: Long,
|
unk6: Long,
|
||||||
unk7: Long,
|
unk7: Long,
|
||||||
members: Int,
|
member_count: Int,
|
||||||
unk9: Int,
|
unk9: Int,
|
||||||
unk10: String,
|
outfit_rank_names: OutfitRankNames,
|
||||||
unk11: String,
|
motd: String,
|
||||||
unk12: String,
|
owner_guid: PlanetSideGUID, // ?
|
||||||
unk13: String,
|
|
||||||
unk14: String,
|
|
||||||
unk15: String,
|
|
||||||
unk16: String,
|
|
||||||
unk17: String,
|
|
||||||
unk18: String,
|
|
||||||
unk19: Int,
|
|
||||||
unk20: Int,
|
unk20: Int,
|
||||||
unk21: Long,
|
unk21: Int,
|
||||||
unk22: Long,
|
unk21_2: Int,
|
||||||
|
created_timestamp: Long,
|
||||||
unk23: Long,
|
unk23: Long,
|
||||||
unk24: Long,
|
unk24: Long,
|
||||||
unk25: Int,
|
unk25: Long,
|
||||||
|
u123: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
final case class Unk0(
|
||||||
|
outfitInfo: OutfitInfo
|
||||||
|
) extends OutfitEventAction(code = 0)
|
||||||
|
|
||||||
|
final case class Unk1(
|
||||||
|
unk0: Int,
|
||||||
|
unk1: Int,
|
||||||
|
unk2: Int,
|
||||||
|
unk3: Boolean,
|
||||||
|
) extends OutfitEventAction(code = 1)
|
||||||
|
|
||||||
|
final case class Unk2(
|
||||||
|
outfitInfo: OutfitInfo,
|
||||||
|
) extends OutfitEventAction(code = 2)
|
||||||
|
|
||||||
|
final case class Unk3(
|
||||||
|
unk0: Int,
|
||||||
|
unk1: Int,
|
||||||
|
unk2: Int,
|
||||||
|
unk3: Boolean,
|
||||||
|
data: BitVector,
|
||||||
|
) extends OutfitEventAction(code = 3)
|
||||||
|
|
||||||
|
final case class Unk4(
|
||||||
|
unk0: Int,
|
||||||
|
unk1: Int,
|
||||||
|
unk2: Int,
|
||||||
|
unk3: Int,
|
||||||
|
unk4: Boolean,
|
||||||
|
data: BitVector,
|
||||||
) extends OutfitEventAction(code = 4)
|
) extends OutfitEventAction(code = 4)
|
||||||
|
|
||||||
|
final case class Unk5(
|
||||||
|
unk0: Int,
|
||||||
|
unk1: Int,
|
||||||
|
unk2: Int,
|
||||||
|
unk3: Int,
|
||||||
|
unk4: Boolean,
|
||||||
|
data: BitVector,
|
||||||
|
) extends OutfitEventAction(code = 5)
|
||||||
|
|
||||||
final case class Unknown(badCode: Int, data: BitVector) extends OutfitEventAction(badCode)
|
final case class Unknown(badCode: Int, data: BitVector) extends OutfitEventAction(badCode)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -60,42 +107,151 @@ object OutfitEventAction {
|
||||||
object Codecs {
|
object Codecs {
|
||||||
private val everFailCondition = conditional(included = false, bool)
|
private val everFailCondition = conditional(included = false, bool)
|
||||||
|
|
||||||
val CreatedOutfitCodec: Codec[CreatedOutfit] =
|
private val OutfitRankNamesCodec: Codec[OutfitRankNames] = (
|
||||||
(uint8L ::
|
PacketHelpers.encodedWideString ::
|
||||||
uint4L ::
|
PacketHelpers.encodedWideString ::
|
||||||
uintL(3) ::
|
PacketHelpers.encodedWideString ::
|
||||||
|
PacketHelpers.encodedWideString ::
|
||||||
|
PacketHelpers.encodedWideString ::
|
||||||
|
PacketHelpers.encodedWideString ::
|
||||||
|
PacketHelpers.encodedWideString ::
|
||||||
|
PacketHelpers.encodedWideString
|
||||||
|
).xmap[OutfitRankNames](
|
||||||
|
{
|
||||||
|
case u0 :: u1 :: u2 :: u3 :: u4 :: u5 :: u6 :: u7 :: HNil =>
|
||||||
|
OutfitRankNames(u0, u1, u2, u3, u4, u5, u6, u7)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case OutfitRankNames(u0, u1, u2, u3, u4, u5, u6, u7) =>
|
||||||
|
u0 :: u1 :: u2 :: u3 :: u4 :: u5 :: u6 :: u7 :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
private val InfoCodec: Codec[OutfitInfo] = (
|
||||||
|
uint8L ::
|
||||||
|
uint8L ::
|
||||||
PacketHelpers.encodedWideStringAligned(5) ::
|
PacketHelpers.encodedWideStringAligned(5) ::
|
||||||
uint32L ::
|
uint32L ::
|
||||||
uint32L ::
|
uint32L ::
|
||||||
uint16L ::
|
uint16L ::
|
||||||
uint16L ::
|
uint16L ::
|
||||||
PacketHelpers.encodedString ::
|
OutfitRankNamesCodec ::
|
||||||
PacketHelpers.encodedString ::
|
PacketHelpers.encodedWideString ::
|
||||||
PacketHelpers.encodedString ::
|
PlanetSideGUID.codec ::
|
||||||
PacketHelpers.encodedString ::
|
uint16L :: //
|
||||||
PacketHelpers.encodedString ::
|
uint8L :: // bool somewhere here
|
||||||
PacketHelpers.encodedString ::
|
uintL(1) :: //
|
||||||
PacketHelpers.encodedString ::
|
("created_timestamp" | uint32L) ::
|
||||||
PacketHelpers.encodedString ::
|
|
||||||
PacketHelpers.encodedString ::
|
|
||||||
uint16L ::
|
|
||||||
uint16L ::
|
|
||||||
uint32L ::
|
uint32L ::
|
||||||
uint32L ::
|
uint32L ::
|
||||||
uint32L ::
|
uint32L ::
|
||||||
uint32L ::
|
uintL(7)
|
||||||
uint16L
|
).xmap[OutfitInfo](
|
||||||
).xmap[CreatedOutfit](
|
|
||||||
{
|
{
|
||||||
case u2 :: u3 :: u4 :: outfit_name :: u6 :: u7 :: members :: u9 :: u10 :: u11 :: u12 :: u13 :: u14 :: u15 :: u16 :: u17 :: u18 :: u19 :: u20 :: u21 :: u22 :: u23 :: u24 :: u25 :: HNil =>
|
case u1 :: u2 :: outfit_name :: u6 :: u7 :: member_count :: u9 :: outfit_rank_names :: motd :: u19 :: u20 :: u21 :: u21_2 :: created_timestamp :: u23 :: u24 :: u25 :: u123 :: HNil =>
|
||||||
CreatedOutfit(u2, u3, u4, outfit_name, u6, u7, members, u9, u10, u11, u12, u13, u14, u15, u16, u17, u18, u19, u20, u21, u22, u23, u24, u25)
|
OutfitInfo(u1, u2, outfit_name, u6, u7, member_count, u9, outfit_rank_names, motd, u19, u20, u21, u21_2, created_timestamp, u23, u24, u25, u123)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case CreatedOutfit(u2, u3, u4, outfit_name, u6, u7, members, u9, u10, u11, u12, u13, u14, u15, u16, u17, u18, u19, u20, u21, u22, u23, u24, u25) =>
|
case OutfitInfo(u1, u2, outfit_name, u6, u7, member_count, u9, outfit_rank_names, motd, u19, u20, u21, u21_2, created_timestamp, u23, u24, u25, u123) =>
|
||||||
u2 :: u3 :: u4 :: outfit_name :: u6 :: u7 :: members :: u9 :: u10 :: u11 :: u12 :: u13 :: u14 :: u15 :: u16 :: u17 :: u18 :: u19 :: u20 :: u21 :: u22 :: u23 :: u24 :: u25 :: HNil
|
u1 :: u2 :: outfit_name :: u6 :: u7 :: member_count :: u9 :: outfit_rank_names :: motd :: u19 :: u20 :: u21 :: u21_2 :: created_timestamp :: u23 :: u24 :: u25 :: u123 :: HNil
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val Unk0Codec: Codec[Unk0] = (
|
||||||
|
InfoCodec
|
||||||
|
).xmap[Unk0](
|
||||||
|
{
|
||||||
|
case info =>
|
||||||
|
Unk0(info)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case Unk0(info) =>
|
||||||
|
info
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val Unk1Codec: Codec[Unk1] = (
|
||||||
|
uint8L ::
|
||||||
|
uint8L ::
|
||||||
|
uint4L ::
|
||||||
|
bool
|
||||||
|
).xmap[Unk1](
|
||||||
|
{
|
||||||
|
case u0 :: u1 :: u2 :: u3 :: HNil =>
|
||||||
|
Unk1(u0, u1, u2, u3)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case Unk1(u0, u1, u2, u3) =>
|
||||||
|
u0 :: u1 :: u2 :: u3 :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val Unk2Codec: Codec[Unk2] = (
|
||||||
|
InfoCodec
|
||||||
|
).xmap[Unk2](
|
||||||
|
{
|
||||||
|
case info =>
|
||||||
|
Unk2(info)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case Unk2(info) =>
|
||||||
|
info
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val Unk3Codec: Codec[Unk3] = (
|
||||||
|
uint8L ::
|
||||||
|
uint8L ::
|
||||||
|
uint4L ::
|
||||||
|
bool ::
|
||||||
|
bits
|
||||||
|
).xmap[Unk3](
|
||||||
|
{
|
||||||
|
case u0 :: u1 :: u2 :: u3 :: data :: HNil =>
|
||||||
|
Unk3(u0, u1, u2, u3, data)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case Unk3(u0, u1, u2, u3, data) =>
|
||||||
|
u0 :: u1 :: u2 :: u3 :: data :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val Unk4Codec: Codec[Unk4] = (
|
||||||
|
uint16L ::
|
||||||
|
uint16L ::
|
||||||
|
uint16L ::
|
||||||
|
uint4L ::
|
||||||
|
bool ::
|
||||||
|
bits
|
||||||
|
).xmap[Unk4](
|
||||||
|
{
|
||||||
|
case u0 :: u1 :: u2 :: u3 :: u4 :: data :: HNil =>
|
||||||
|
Unk4(u0, u1, u2, u3, u4, data)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case Unk4(u0, u1, u2, u3, u4, data) =>
|
||||||
|
u0 :: u1 :: u2 ::u3 :: u4 :: data :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val Unk5Codec: Codec[Unk5] = (
|
||||||
|
uint16L ::
|
||||||
|
uint16L ::
|
||||||
|
uint16L ::
|
||||||
|
uint4L ::
|
||||||
|
bool ::
|
||||||
|
bits
|
||||||
|
).xmap[Unk5](
|
||||||
|
{
|
||||||
|
case u0 :: u1 :: u2 :: u3 :: u4 :: data :: HNil =>
|
||||||
|
Unk5(u0, u1, u2, u3, u4, data)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case Unk5(u0, u1, u2, u3, u4, data) =>
|
||||||
|
u0 :: u1 :: u2 :: u3 :: u4 :: data :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A common form for known action code indexes with an unknown purpose and transformation is an "Unknown" object.
|
* A common form for known action code indexes with an unknown purpose and transformation is an "Unknown" object.
|
||||||
*
|
*
|
||||||
|
|
@ -129,16 +285,36 @@ object OutfitEvent extends Marshallable[OutfitEvent] {
|
||||||
object RequestType extends Enumeration {
|
object RequestType extends Enumeration {
|
||||||
type Type = Value
|
type Type = Value
|
||||||
|
|
||||||
val unk0: RequestType.Value = Value(0)
|
val Unk0: RequestType.Value = Value(0)
|
||||||
val unk1: RequestType.Value = Value(1)
|
val Unk1: RequestType.Value = Value(1) // send in SMP after a f load of OutfitMemberEvent
|
||||||
val unk2: RequestType.Value = Value(2)
|
val Unk2: RequestType.Value = Value(2) // send after creating an outfit
|
||||||
val unk3: RequestType.Value = Value(3)
|
val Unk3: RequestType.Value = Value(3) // below
|
||||||
val CreatedOutfit: RequestType.Value = Value(4)
|
val Unk4: RequestType.Value = Value(4)
|
||||||
val unk5: RequestType.Value = Value(5)
|
val Unk5: RequestType.Value = Value(5)
|
||||||
val Unk6: RequestType.Value = Value(6)
|
val unk6: RequestType.Value = Value(6)
|
||||||
val Unk7: RequestType.Value = Value(7)
|
val unk7: RequestType.Value = Value(7)
|
||||||
|
|
||||||
implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uint4L)
|
/*
|
||||||
|
unk3 -- #66162 PSCap-2016-02-28_02-58-10-PM.txt
|
||||||
|
|
||||||
|
MP(
|
||||||
|
SMP(
|
||||||
|
MPEx(
|
||||||
|
OutfitMembershipResponse,
|
||||||
|
OutfitEvent,
|
||||||
|
SquadMemberEvent
|
||||||
|
)
|
||||||
|
),
|
||||||
|
SMP(
|
||||||
|
MPEx(
|
||||||
|
PlanetsideAttributeMessage x 3 + PlanetsideStringAttributeMessage
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
|
||||||
|
implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uintL(3))
|
||||||
}
|
}
|
||||||
|
|
||||||
private def selectFromType(code: Int): Codec[OutfitEventAction] = {
|
private def selectFromType(code: Int): Codec[OutfitEventAction] = {
|
||||||
|
|
@ -146,12 +322,12 @@ object OutfitEvent extends Marshallable[OutfitEvent] {
|
||||||
import scala.annotation.switch
|
import scala.annotation.switch
|
||||||
|
|
||||||
((code: @switch) match {
|
((code: @switch) match {
|
||||||
case 0 => unknownCodec(action = code)
|
case 0 => Unk0Codec
|
||||||
case 1 => unknownCodec(action = code)
|
case 1 => Unk1Codec
|
||||||
case 2 => unknownCodec(action = code)
|
case 2 => Unk2Codec // sent after /outfitcreate ?
|
||||||
case 3 => unknownCodec(action = code)
|
case 3 => Unk3Codec
|
||||||
case 4 => CreatedOutfitCodec // sent after /outfitcreate
|
case 4 => Unk4Codec
|
||||||
case 5 => unknownCodec(action = code)
|
case 5 => Unk5Codec
|
||||||
case 6 => unknownCodec(action = code)
|
case 6 => unknownCodec(action = code)
|
||||||
case 7 => unknownCodec(action = code)
|
case 7 => unknownCodec(action = code)
|
||||||
|
|
||||||
|
|
@ -161,17 +337,17 @@ object OutfitEvent extends Marshallable[OutfitEvent] {
|
||||||
|
|
||||||
implicit val codec: Codec[OutfitEvent] = (
|
implicit val codec: Codec[OutfitEvent] = (
|
||||||
("request_type" | RequestType.codec) >>:~ { request_type =>
|
("request_type" | RequestType.codec) >>:~ { request_type =>
|
||||||
("avatar_guid" | uint16L) ::
|
("outfit_guid" | PlanetSideGUID.codec) ::
|
||||||
("action" | selectFromType(request_type.id))
|
("action" | selectFromType(request_type.id))
|
||||||
}
|
}
|
||||||
).xmap[OutfitEvent](
|
).xmap[OutfitEvent](
|
||||||
{
|
{
|
||||||
case request_type :: avatar_guid :: action :: HNil =>
|
case request_type :: outfit_guid :: action :: HNil =>
|
||||||
OutfitEvent(request_type, avatar_guid, action)
|
OutfitEvent(request_type, outfit_guid, action)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case OutfitEvent(request_type, avatar_guid, action) =>
|
case OutfitEvent(request_type, outfit_guid, action) =>
|
||||||
request_type :: avatar_guid :: action :: HNil
|
request_type :: outfit_guid :: action :: HNil
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright (c) 2024 PSForever
|
||||||
|
package net.psforever.packet.game
|
||||||
|
|
||||||
|
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||||
|
import net.psforever.types.PlanetSideGUID
|
||||||
|
import scodec.Codec
|
||||||
|
import scodec.codecs._
|
||||||
|
import shapeless.{::, HNil}
|
||||||
|
|
||||||
|
final case class OutfitMemberEvent(
|
||||||
|
unk00: Int,
|
||||||
|
outfit_id: PlanetSideGUID,
|
||||||
|
unk1: Int,
|
||||||
|
unk2: Int,
|
||||||
|
unk3: Int,
|
||||||
|
member_name: String,
|
||||||
|
unk7: Int,
|
||||||
|
unk8: Int,
|
||||||
|
unk9: Int,
|
||||||
|
unk10: Int,
|
||||||
|
unk12: Int,
|
||||||
|
unk13: Int,
|
||||||
|
unk14: Int,
|
||||||
|
unk15: Int,
|
||||||
|
) extends PlanetSideGamePacket {
|
||||||
|
type Packet = OutfitMemberEvent
|
||||||
|
def opcode = GamePacketOpcode.OutfitMemberEvent
|
||||||
|
def encode = OutfitMemberEvent.encode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
object OutfitMemberEvent extends Marshallable[OutfitMemberEvent] {
|
||||||
|
implicit val codec: Codec[OutfitMemberEvent] = (
|
||||||
|
("unk00" | uintL(2)) ::
|
||||||
|
("outfit_id" | PlanetSideGUID.codec) ::
|
||||||
|
("unk1" | uint16L) ::
|
||||||
|
("unk2" | uint16L) ::
|
||||||
|
("unk3" | uint16L) ::
|
||||||
|
("member_name" | PacketHelpers.encodedWideStringAligned(6)) ::
|
||||||
|
("unk7" | uint8L) ::
|
||||||
|
("unk8" | uint8L) ::
|
||||||
|
("unk9" | uint16L) ::
|
||||||
|
("unk10" | uint8L) ::
|
||||||
|
("unk12" | uint8L) ::
|
||||||
|
("unk13" | uint8L) ::
|
||||||
|
("unk14" | uint8L) ::
|
||||||
|
("unk15" | uint8L)
|
||||||
|
).xmap[OutfitMemberEvent](
|
||||||
|
{
|
||||||
|
case unk00 :: outfit_id :: u1 :: u2 :: u3 :: member_name :: u7 :: u8 :: u9 :: u10 :: u12 :: u13 :: u14 :: u15 :: HNil =>
|
||||||
|
OutfitMemberEvent(unk00, outfit_id, u1, u2, u3, member_name, u7, u8, u9, u10, u12, u13, u14, u15)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case OutfitMemberEvent(unk00, outfit_id, u1, u2, u3, member_name, u7, u8, u9, u10, u12, u13, u14, u15) =>
|
||||||
|
unk00 :: outfit_id :: u1 :: u2 :: u3 :: member_name :: u7 :: u8 :: u9 :: u10 :: u12 :: u13 :: u14 :: u15 :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
// Copyright (c) 2024 PSForever
|
// Copyright (c) 2024 PSForever
|
||||||
package net.psforever.packet.game
|
package net.psforever.packet.game
|
||||||
|
|
||||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
|
||||||
import net.psforever.types.PlanetSideGUID
|
import net.psforever.types.PlanetSideGUID
|
||||||
import scodec.Codec
|
import scodec.Codec
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
import shapeless.{::, HNil}
|
import shapeless.{::, HNil}
|
||||||
|
|
||||||
final case class OutfitMemberUpdate(outfit_id: PlanetSideGUID, unk1: Int, unk2: Int, unk3: Int) extends PlanetSideGamePacket {
|
final case class OutfitMemberUpdate(
|
||||||
|
outfit_guid: PlanetSideGUID,
|
||||||
|
unk1: Int,
|
||||||
|
unk2: Int,
|
||||||
|
unk3: Int,
|
||||||
|
) extends PlanetSideGamePacket {
|
||||||
type Packet = OutfitMemberUpdate
|
type Packet = OutfitMemberUpdate
|
||||||
def opcode = GamePacketOpcode.OutfitMemberUpdate
|
def opcode = GamePacketOpcode.OutfitMemberUpdate
|
||||||
def encode = OutfitMemberUpdate.encode(this)
|
def encode = OutfitMemberUpdate.encode(this)
|
||||||
|
|
@ -15,19 +20,18 @@ final case class OutfitMemberUpdate(outfit_id: PlanetSideGUID, unk1: Int, unk2:
|
||||||
|
|
||||||
object OutfitMemberUpdate extends Marshallable[OutfitMemberUpdate] {
|
object OutfitMemberUpdate extends Marshallable[OutfitMemberUpdate] {
|
||||||
implicit val codec: Codec[OutfitMemberUpdate] = (
|
implicit val codec: Codec[OutfitMemberUpdate] = (
|
||||||
("outfit_id" | PlanetSideGUID.codec) ::
|
("outfit_guid" | PlanetSideGUID.codec) ::
|
||||||
("unk1" | uint16L) ::
|
("unk1" | uint16L) ::
|
||||||
("unk2" | uint16L) ::
|
("unk2" | uint16L) ::
|
||||||
("unk3" | uint8L)
|
("unk3" | uint8L)
|
||||||
).xmap[OutfitMemberUpdate](
|
).xmap[OutfitMemberUpdate](
|
||||||
{
|
{
|
||||||
case u0 :: u1 :: u2 :: u3 :: HNil =>
|
case outfit_guid :: u1 :: u2 :: u3 :: HNil =>
|
||||||
OutfitMemberUpdate(u0, u1, u2, u3)
|
OutfitMemberUpdate(outfit_guid, u1, u2, u3)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case OutfitMemberUpdate(u0, u1, u2, u3) =>
|
case OutfitMemberUpdate(outfit_guid, u1, u2, u3) =>
|
||||||
u0 :: u1 :: u2 :: u3 :: HNil
|
outfit_guid :: u1 :: u2 :: u3 :: HNil
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
// ).as[OutfitMemberUpdate]
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,294 @@
|
||||||
// Copyright (c) 2023 PSForever
|
// Copyright (c) 2024 PSForever
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import net.psforever.packet._
|
import net.psforever.packet._
|
||||||
import net.psforever.packet.game.OutfitEvent.RequestType
|
import net.psforever.packet.game.OutfitEvent.RequestType
|
||||||
import net.psforever.packet.game.OutfitEventAction._
|
import net.psforever.packet.game.OutfitEventAction._
|
||||||
import net.psforever.packet.game._
|
import net.psforever.packet.game._
|
||||||
|
import net.psforever.types.PlanetSideGUID
|
||||||
import org.specs2.mutable._
|
import org.specs2.mutable._
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
|
|
||||||
class OutfitEventTest extends Specification {
|
class OutfitEventTest extends Specification {
|
||||||
val created_ABC = hex"8f 4 0201 feff 2e 0 50006c0061006e006500740053006900640065005f0046006f00720065007600650072005f00560061006e007500 00000000 00000000 0100 0000 80 80 80 80 80 80 80 80 80 0070 4982 00000000 00000000 00000000 00000000 0000"
|
val unk0_ABC: ByteVector = ByteVector.fromValidHex(
|
||||||
|
"8f 1 a8c2 0001" + // packet head
|
||||||
|
"2a 0 42006c00610063006b002000410072006d006f0072006500640020005200650061007000650072007300" + // Black Armored Reapers
|
||||||
|
"1d9c4d0d" +
|
||||||
|
"1d9c4d0d" +
|
||||||
|
"ab00 0000" +
|
||||||
|
"88 44006f00670020004d00650061007400" + // Dog Meat
|
||||||
|
"87 5200750073007300690061006e00" + // Russian
|
||||||
|
"80" + //
|
||||||
|
"80" + //
|
||||||
|
"8d 5300710075006100640020004c00650061006400650072007300" + // Squad Leaders
|
||||||
|
"91 41006300740069006e006700200043006f006d006d0061006e006400650072007300" + // Acting Commanders
|
||||||
|
"87 5200650061007000650072007300" + // Reapers
|
||||||
|
"80" + //
|
||||||
|
"00" +
|
||||||
|
"9c 5c0023003000300030003000660066004d0075006d0062006c00650020005c00230030003000330033006600660049006e0066006f0020005c0023003000300036003600660066006900730020005c0023003000300039003900660066007400680065006d006f006f00730065002e00740079007000650066007200610067002e0063006f006d0020005c00230030003000630063006600660070006f007200740020005c002300300030006600660066006600390033003500300020005c0023003000300063006300660066006a006f0069006e0020005c0023003000300039003900660066006900740020005c0023003000300036003600660066006f00720020005c0023003000300033003300660066006200650020005c0023003000300030003000660066006b00690063006b00650064002e00" +
|
||||||
|
"0f80" +
|
||||||
|
"0000 00737296 24000000 00000000 00000000 0000")
|
||||||
|
val unk1_ABC: ByteVector = hex"8f 2 302a 10 00 0"
|
||||||
|
val unk2_ABC: ByteVector = ByteVector.fromValidHex(
|
||||||
|
"8f 4 0201 feff" +
|
||||||
|
"2e 0 50006c0061006e006500740053006900640065005f0046006f00720065007600650072005f00560061006e007500" + // PlanetSide_Forever_Vanu
|
||||||
|
"00000000" +
|
||||||
|
"00000000" +
|
||||||
|
"0100 0000" +
|
||||||
|
"80" +
|
||||||
|
"80" +
|
||||||
|
"80" +
|
||||||
|
"80" +
|
||||||
|
"80" +
|
||||||
|
"80" +
|
||||||
|
"80" +
|
||||||
|
"80" +
|
||||||
|
"80" +
|
||||||
|
"0070" +
|
||||||
|
"4982 00000000 00000000 00000000 00000000 0000")
|
||||||
|
val unk3_ABC: ByteVector = hex"8f 6 0201 fe fe 0"
|
||||||
|
val unk4_ABC: ByteVector = hex"8f 8 0201 fefe a02a 1000 0"
|
||||||
|
val unk5_ABC: ByteVector = hex"8f a 0201 fefe 0400 0000 0"
|
||||||
|
|
||||||
|
"decode Unk0 ABC" in {
|
||||||
"decode CreatedOutfit ABC" in {
|
PacketCoding.decodePacket(unk0_ABC).require match {
|
||||||
PacketCoding.decodePacket(created_ABC).require match {
|
case OutfitEvent(request_type, outfit_guid, action) =>
|
||||||
case OutfitEvent(request_type, unk1, action) =>
|
request_type mustEqual RequestType.Unk0
|
||||||
request_type mustEqual RequestType.CreatedOutfit
|
outfit_guid mustEqual PlanetSideGUID(25044)
|
||||||
unk1 mustEqual 258
|
action mustEqual Unk0(
|
||||||
action mustEqual CreatedOutfit(unk2 = 254, unk3 = 15, unk4 = 7, outfit_name = "PlanetSide_Forever_Vanu", unk6 = 0, unk7 = 0, members = 1, unk9 = 0, "", "", "", "", "", "", "", "", "", 28672, 33353, 0, 0, 0, 0, 0)
|
OutfitInfo(
|
||||||
|
unk1 = 0,
|
||||||
|
unk2 = 0,
|
||||||
|
outfit_name = "Black Armored Reapers",
|
||||||
|
unk6 = 223190045,
|
||||||
|
unk7 = 223190045,
|
||||||
|
member_count = 171,
|
||||||
|
unk9 = 0,
|
||||||
|
OutfitRankNames("Dog Meat","Russian","","","Squad Leaders","Acting Commanders","Reapers",""),
|
||||||
|
"\\#0000ffMumble \\#0033ffInfo \\#0066ffis \\#0099ffthemoose.typefrag.com \\#00ccffport \\#00ffff9350 \\#00ccffjoin \\#0099ffit \\#0066ffor \\#0033ffbe \\#0000ffkicked.",
|
||||||
|
PlanetSideGUID(32783),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1210901990,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
)
|
||||||
case _ =>
|
case _ =>
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"encode CreatedOutfit ABC" in {
|
"encode Unk0 ABC" in {
|
||||||
val msg = OutfitEvent(RequestType.CreatedOutfit, 258, CreatedOutfit(unk2 = 254, unk3 = 15, unk4 = 7, outfit_name = "PlanetSide_Forever_Vanu", unk6 = 0, unk7 = 0, members = 1, unk9 = 0, "", "", "", "", "", "", "", "", "", 28672, 33353, 0, 0, 0, 0, 0))
|
val msg = OutfitEvent(
|
||||||
|
RequestType.Unk0,
|
||||||
|
PlanetSideGUID(25044),
|
||||||
|
Unk0(
|
||||||
|
OutfitInfo(
|
||||||
|
unk1 = 0,
|
||||||
|
unk2 = 0,
|
||||||
|
outfit_name = "Black Armored Reapers",
|
||||||
|
unk6 = 223190045,
|
||||||
|
unk7 = 223190045,
|
||||||
|
member_count = 171,
|
||||||
|
unk9 = 0,
|
||||||
|
OutfitRankNames("Dog Meat","Russian","","","Squad Leaders","Acting Commanders","Reapers",""),
|
||||||
|
"\\#0000ffMumble \\#0033ffInfo \\#0066ffis \\#0099ffthemoose.typefrag.com \\#00ccffport \\#00ffff9350 \\#00ccffjoin \\#0099ffit \\#0066ffor \\#0033ffbe \\#0000ffkicked.",
|
||||||
|
PlanetSideGUID(32783),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1210901990,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
pkt mustEqual created_ABC
|
pkt mustEqual unk0_ABC
|
||||||
|
}
|
||||||
|
|
||||||
|
"decode Unk1 ABC" in {
|
||||||
|
PacketCoding.decodePacket(unk1_ABC).require match {
|
||||||
|
case OutfitEvent(request_type, outfit_guid, action) =>
|
||||||
|
request_type mustEqual RequestType.Unk1
|
||||||
|
outfit_guid mustEqual PlanetSideGUID(5400)
|
||||||
|
action mustEqual Unk1(unk0 = 8, unk1 = 0, unk2 = 0, unk3 = false)
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode Unk1 ABC" in {
|
||||||
|
val msg = OutfitEvent(
|
||||||
|
RequestType.Unk1,
|
||||||
|
PlanetSideGUID(5400),
|
||||||
|
Unk1(
|
||||||
|
unk0 = 8,
|
||||||
|
unk1 = 0,
|
||||||
|
unk2 = 0,
|
||||||
|
unk3 = false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
pkt mustEqual unk1_ABC
|
||||||
|
}
|
||||||
|
|
||||||
|
"decode Unk2 ABC" in {
|
||||||
|
PacketCoding.decodePacket(unk2_ABC).require match {
|
||||||
|
case OutfitEvent(request_type, outfit_guid, action) =>
|
||||||
|
request_type mustEqual RequestType.Unk2
|
||||||
|
outfit_guid mustEqual PlanetSideGUID(1)
|
||||||
|
action mustEqual Unk2(OutfitInfo(unk1 = 255, unk2 = 127, outfit_name = "PlanetSide_Forever_Vanu",
|
||||||
|
unk6 = 0, unk7 = 0, member_count = 1, unk9 = 0, OutfitRankNames("","","","","","","",""),
|
||||||
|
"", PlanetSideGUID(28672), 33353, 0, 0, 0, 0, 0, 0, 0))
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode Unk2 ABC" in {
|
||||||
|
val msg = OutfitEvent(
|
||||||
|
RequestType.Unk2,
|
||||||
|
PlanetSideGUID(1),
|
||||||
|
Unk2(
|
||||||
|
OutfitInfo(
|
||||||
|
unk1 = 255,
|
||||||
|
unk2 = 127,
|
||||||
|
outfit_name = "PlanetSide_Forever_Vanu",
|
||||||
|
unk6 = 0,
|
||||||
|
unk7 = 0,
|
||||||
|
member_count = 1,
|
||||||
|
unk9 = 0,
|
||||||
|
OutfitRankNames("","","","","","","",""),
|
||||||
|
"",
|
||||||
|
PlanetSideGUID(28672),
|
||||||
|
33353,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
pkt mustEqual unk2_ABC
|
||||||
|
}
|
||||||
|
|
||||||
|
"decode Unk3 ABC" in {
|
||||||
|
PacketCoding.decodePacket(unk3_ABC).require match {
|
||||||
|
case OutfitEvent(request_type, outfit_guid, action) =>
|
||||||
|
request_type mustEqual RequestType.Unk3
|
||||||
|
outfit_guid mustEqual PlanetSideGUID(1)
|
||||||
|
action mustEqual Unk3(
|
||||||
|
unk0 = 255,
|
||||||
|
unk1 = 127,
|
||||||
|
unk2 = 0,
|
||||||
|
unk3 = false,
|
||||||
|
BitVector.fromValidHex("")
|
||||||
|
)
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode Unk3 ABC" in {
|
||||||
|
val msg = OutfitEvent(
|
||||||
|
RequestType.Unk3,
|
||||||
|
PlanetSideGUID(1),
|
||||||
|
Unk3(
|
||||||
|
unk0 = 255,
|
||||||
|
unk1 = 127,
|
||||||
|
unk2 = 0,
|
||||||
|
unk3 = false,
|
||||||
|
BitVector.fromValidHex("")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
pkt mustEqual unk3_ABC
|
||||||
|
}
|
||||||
|
|
||||||
|
"decode Unk4 ABC" in {
|
||||||
|
PacketCoding.decodePacket(unk4_ABC).require match {
|
||||||
|
case OutfitEvent(request_type, outfit_guid, action) =>
|
||||||
|
request_type mustEqual RequestType.Unk4
|
||||||
|
outfit_guid mustEqual PlanetSideGUID(1)
|
||||||
|
action mustEqual Unk4(
|
||||||
|
unk0 = 32767,
|
||||||
|
unk1 = 5456,
|
||||||
|
unk2 = 8,
|
||||||
|
0,
|
||||||
|
unk4 = false,
|
||||||
|
BitVector.fromValidHex("")
|
||||||
|
)
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode Unk4 ABC" in {
|
||||||
|
val msg = OutfitEvent(
|
||||||
|
RequestType.Unk4,
|
||||||
|
PlanetSideGUID(1),
|
||||||
|
Unk4(
|
||||||
|
unk0 = 32767,
|
||||||
|
unk1 = 5456,
|
||||||
|
unk2 = 8,
|
||||||
|
unk3 = 0,
|
||||||
|
unk4 = false,
|
||||||
|
BitVector.fromValidHex("")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
pkt mustEqual unk4_ABC
|
||||||
|
}
|
||||||
|
|
||||||
|
"decode Unk5 ABC" in {
|
||||||
|
PacketCoding.decodePacket(unk5_ABC).require match {
|
||||||
|
case OutfitEvent(request_type, outfit_guid, action) =>
|
||||||
|
request_type mustEqual RequestType.Unk5
|
||||||
|
outfit_guid mustEqual PlanetSideGUID(1)
|
||||||
|
action mustEqual Unk5(
|
||||||
|
unk0 = 32767,
|
||||||
|
unk1 = 2,
|
||||||
|
unk2 = 0,
|
||||||
|
unk3 = 0,
|
||||||
|
unk4 = false,
|
||||||
|
BitVector.fromValidHex("") // OR f88c2a0417c1a06101001f20f4b8c00000404090ac9c6745dea88cadf0f810e03e0200f92 with bool at the back
|
||||||
|
)
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode Unk5 ABC" in {
|
||||||
|
val msg = OutfitEvent(
|
||||||
|
RequestType.Unk5,
|
||||||
|
PlanetSideGUID(1),
|
||||||
|
Unk5(
|
||||||
|
unk0 = 32767,
|
||||||
|
unk1 = 2,
|
||||||
|
unk2 = 0,
|
||||||
|
unk3 = 0,
|
||||||
|
unk4 = false,
|
||||||
|
BitVector.fromValidHex("")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
pkt mustEqual unk5_ABC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
58
src/test/scala/game/OutfitMemberEventTest.scala
Normal file
58
src/test/scala/game/OutfitMemberEventTest.scala
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright (c) 2024 PSForever
|
||||||
|
package game
|
||||||
|
|
||||||
|
import net.psforever.packet._
|
||||||
|
|
||||||
|
import net.psforever.packet.game._
|
||||||
|
import net.psforever.types.PlanetSideGUID
|
||||||
|
import org.specs2.mutable._
|
||||||
|
import scodec.bits._
|
||||||
|
|
||||||
|
class OutfitMemberEventTest extends Specification {
|
||||||
|
|
||||||
|
//val unk0_ABC: ByteVector = hex"90 3518 4000 1a4e 4100 2 180 450078007000650072007400 8483 07e0 119d bfe0 70" // 0x90048640001030c28022404c0061007a00650072003100390038003200f43a45e00b4c604010
|
||||||
|
val unk0_ABC: ByteVector = hex"90 048640001030c28022404c0061007a00650072003100390038003200f43a45e00b4c604010"
|
||||||
|
"decode Unk0 ABC" in {
|
||||||
|
PacketCoding.decodePacket(unk0_ABC).require match {
|
||||||
|
case OutfitMemberEvent(unk00, outfit_guid, unk1, unk2, unk3, member_name, unk7, unk8, unk9, unk10, unk12, unk13, unk14, unk15) =>
|
||||||
|
unk00 mustEqual 0
|
||||||
|
outfit_guid mustEqual PlanetSideGUID(6418)
|
||||||
|
unk1 mustEqual 0
|
||||||
|
unk2 mustEqual 49984
|
||||||
|
unk3 mustEqual 10
|
||||||
|
member_name mustEqual "Lazer1982"
|
||||||
|
unk7 mustEqual 244
|
||||||
|
unk8 mustEqual 58
|
||||||
|
unk9 mustEqual 57413
|
||||||
|
unk10 mustEqual 11
|
||||||
|
unk12 mustEqual 76
|
||||||
|
unk13 mustEqual 96
|
||||||
|
unk14 mustEqual 64
|
||||||
|
unk15 mustEqual 16
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode Unk0 ABC" in {
|
||||||
|
val msg = OutfitMemberEvent(
|
||||||
|
unk00 = 0,
|
||||||
|
outfit_id = PlanetSideGUID(6418),
|
||||||
|
unk1 = 0,
|
||||||
|
unk2 = 49984,
|
||||||
|
unk3 = 10,
|
||||||
|
member_name = "Lazer1982",
|
||||||
|
unk7 = 244,
|
||||||
|
unk8 = 58,
|
||||||
|
unk9 = 57413,
|
||||||
|
unk10 = 11,
|
||||||
|
unk12 = 76,
|
||||||
|
unk13 = 96,
|
||||||
|
unk14 = 64,
|
||||||
|
unk15 = 16,
|
||||||
|
)
|
||||||
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
pkt mustEqual unk0_ABC
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue