mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-07-13 07:24:57 +00:00
Add OutfitEvent packet
Add OutfitMemberUpdate packet
This commit is contained in:
parent
06eea3b3e7
commit
4bc272c2f0
5 changed files with 249 additions and 7 deletions
|
|
@ -471,11 +471,11 @@ object GamePacketOpcode extends Enumeration {
|
||||||
case 0x8c => game.OutfitMembershipRequest.decode
|
case 0x8c => game.OutfitMembershipRequest.decode
|
||||||
case 0x8d => game.OutfitMembershipResponse.decode
|
case 0x8d => game.OutfitMembershipResponse.decode
|
||||||
case 0x8e => game.OutfitRequest.decode
|
case 0x8e => game.OutfitRequest.decode
|
||||||
case 0x8f => noDecoder(OutfitEvent)
|
case 0x8f => game.OutfitEvent.decode
|
||||||
|
|
||||||
// OPCODES 0x90-9f
|
// OPCODES 0x90-9f
|
||||||
case 0x90 => noDecoder(OutfitMemberEvent)
|
case 0x90 => noDecoder(OutfitMemberEvent)
|
||||||
case 0x91 => noDecoder(OutfitMemberUpdate)
|
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
|
||||||
case 0x94 => game.DataChallengeMessageResp.decode
|
case 0x94 => game.DataChallengeMessageResp.decode
|
||||||
|
|
|
||||||
177
src/main/scala/net/psforever/packet/game/OutfitEvent.scala
Normal file
177
src/main/scala/net/psforever/packet/game/OutfitEvent.scala
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
// Copyright (c) 2024 PSForever
|
||||||
|
package net.psforever.packet.game
|
||||||
|
|
||||||
|
import net.psforever.packet.GamePacketOpcode.Type
|
||||||
|
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||||
|
import scodec.{Attempt, Codec, Err}
|
||||||
|
import scodec.bits.BitVector
|
||||||
|
import scodec.codecs._
|
||||||
|
import shapeless.{::, HNil}
|
||||||
|
|
||||||
|
final case class OutfitEvent(
|
||||||
|
request_type: OutfitEvent.RequestType.Type,
|
||||||
|
unk1: Int,
|
||||||
|
action: OutfitEventAction
|
||||||
|
) extends PlanetSideGamePacket {
|
||||||
|
type Packet = OutfitEvent
|
||||||
|
|
||||||
|
def opcode: Type = GamePacketOpcode.OutfitEvent
|
||||||
|
|
||||||
|
def encode: Attempt[BitVector] = OutfitEvent.encode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class OutfitEventAction(val code: Int)
|
||||||
|
|
||||||
|
object OutfitEventAction {
|
||||||
|
|
||||||
|
final case class CreatedOutfit(
|
||||||
|
unk2: Int,
|
||||||
|
unk3: Int,
|
||||||
|
unk4: Int,
|
||||||
|
outfit_name: String,
|
||||||
|
unk6: Long,
|
||||||
|
unk7: Long,
|
||||||
|
members: Int,
|
||||||
|
unk9: Int,
|
||||||
|
unk10: String,
|
||||||
|
unk11: String,
|
||||||
|
unk12: String,
|
||||||
|
unk13: String,
|
||||||
|
unk14: String,
|
||||||
|
unk15: String,
|
||||||
|
unk16: String,
|
||||||
|
unk17: String,
|
||||||
|
unk18: String,
|
||||||
|
unk19: Int,
|
||||||
|
unk20: Int,
|
||||||
|
unk21: Long,
|
||||||
|
unk22: Long,
|
||||||
|
unk23: Long,
|
||||||
|
unk24: Long,
|
||||||
|
unk25: Int,
|
||||||
|
) extends OutfitEventAction(code = 4)
|
||||||
|
|
||||||
|
final case class Unknown(badCode: Int, data: BitVector) extends OutfitEventAction(badCode)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Codec`s used to transform the input stream into the context of a specific action
|
||||||
|
* and extract the field data from that stream.
|
||||||
|
*/
|
||||||
|
object Codecs {
|
||||||
|
private val everFailCondition = conditional(included = false, bool)
|
||||||
|
|
||||||
|
val CreatedOutfitCodec: Codec[CreatedOutfit] =
|
||||||
|
(uint8L ::
|
||||||
|
uint4L ::
|
||||||
|
uintL(3) ::
|
||||||
|
PacketHelpers.encodedWideStringAligned(5) ::
|
||||||
|
uint32L ::
|
||||||
|
uint32L ::
|
||||||
|
uint16L ::
|
||||||
|
uint16L ::
|
||||||
|
PacketHelpers.encodedString ::
|
||||||
|
PacketHelpers.encodedString ::
|
||||||
|
PacketHelpers.encodedString ::
|
||||||
|
PacketHelpers.encodedString ::
|
||||||
|
PacketHelpers.encodedString ::
|
||||||
|
PacketHelpers.encodedString ::
|
||||||
|
PacketHelpers.encodedString ::
|
||||||
|
PacketHelpers.encodedString ::
|
||||||
|
PacketHelpers.encodedString ::
|
||||||
|
uint16L ::
|
||||||
|
uint16L ::
|
||||||
|
uint32L ::
|
||||||
|
uint32L ::
|
||||||
|
uint32L ::
|
||||||
|
uint32L ::
|
||||||
|
uint16L
|
||||||
|
).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 =>
|
||||||
|
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 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) =>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A common form for known action code indexes with an unknown purpose and transformation is an "Unknown" object.
|
||||||
|
*
|
||||||
|
* @param action the action behavior code
|
||||||
|
* @return a transformation between the action code and the unknown bit data
|
||||||
|
*/
|
||||||
|
def unknownCodec(action: Int): Codec[Unknown] =
|
||||||
|
bits.xmap[Unknown](
|
||||||
|
data => Unknown(action, data),
|
||||||
|
{
|
||||||
|
case Unknown(_, data) => data
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The action code was completely unanticipated!
|
||||||
|
*
|
||||||
|
* @param action the action behavior code
|
||||||
|
* @return nothing; always fail
|
||||||
|
*/
|
||||||
|
def failureCodec(action: Int): Codec[OutfitEventAction] =
|
||||||
|
everFailCondition.exmap[OutfitEventAction](
|
||||||
|
_ => Attempt.failure(Err(s"can not match a codec pattern for decoding $action")),
|
||||||
|
_ => Attempt.failure(Err(s"can not match a codec pattern for encoding $action"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object OutfitEvent extends Marshallable[OutfitEvent] {
|
||||||
|
|
||||||
|
object RequestType extends Enumeration {
|
||||||
|
type Type = Value
|
||||||
|
|
||||||
|
val unk0: RequestType.Value = Value(0)
|
||||||
|
val unk1: RequestType.Value = Value(1)
|
||||||
|
val unk2: RequestType.Value = Value(2)
|
||||||
|
val unk3: RequestType.Value = Value(3)
|
||||||
|
val CreatedOutfit: RequestType.Value = Value(4)
|
||||||
|
val unk5: RequestType.Value = Value(5)
|
||||||
|
val Unk6: RequestType.Value = Value(6)
|
||||||
|
val Unk7: RequestType.Value = Value(7)
|
||||||
|
|
||||||
|
implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uint4L)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def selectFromType(code: Int): Codec[OutfitEventAction] = {
|
||||||
|
import OutfitEventAction.Codecs._
|
||||||
|
import scala.annotation.switch
|
||||||
|
|
||||||
|
((code: @switch) match {
|
||||||
|
case 0 => unknownCodec(action = code)
|
||||||
|
case 1 => unknownCodec(action = code)
|
||||||
|
case 2 => unknownCodec(action = code)
|
||||||
|
case 3 => unknownCodec(action = code)
|
||||||
|
case 4 => CreatedOutfitCodec // sent after /outfitcreate
|
||||||
|
case 5 => unknownCodec(action = code)
|
||||||
|
case 6 => unknownCodec(action = code)
|
||||||
|
case 7 => unknownCodec(action = code)
|
||||||
|
|
||||||
|
case _ => failureCodec(code)
|
||||||
|
}).asInstanceOf[Codec[OutfitEventAction]]
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val codec: Codec[OutfitEvent] = (
|
||||||
|
("request_type" | RequestType.codec) >>:~ { request_type =>
|
||||||
|
("avatar_guid" | uint16L) ::
|
||||||
|
("action" | selectFromType(request_type.id))
|
||||||
|
}
|
||||||
|
).xmap[OutfitEvent](
|
||||||
|
{
|
||||||
|
case request_type :: avatar_guid :: action :: HNil =>
|
||||||
|
OutfitEvent(request_type, avatar_guid, action)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case OutfitEvent(request_type, avatar_guid, action) =>
|
||||||
|
request_type :: avatar_guid :: action :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// 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 OutfitMemberUpdate(outfit_id: PlanetSideGUID, unk1: Int, unk2: Int, unk3: Int) extends PlanetSideGamePacket {
|
||||||
|
type Packet = OutfitMemberUpdate
|
||||||
|
def opcode = GamePacketOpcode.OutfitMemberUpdate
|
||||||
|
def encode = OutfitMemberUpdate.encode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
object OutfitMemberUpdate extends Marshallable[OutfitMemberUpdate] {
|
||||||
|
implicit val codec: Codec[OutfitMemberUpdate] = (
|
||||||
|
("outfit_id" | PlanetSideGUID.codec) ::
|
||||||
|
("unk1" | uint16L) ::
|
||||||
|
("unk2" | uint16L) ::
|
||||||
|
("unk3" | uint8L)
|
||||||
|
).xmap[OutfitMemberUpdate](
|
||||||
|
{
|
||||||
|
case u0 :: u1 :: u2 :: u3 :: HNil =>
|
||||||
|
OutfitMemberUpdate(u0, u1, u2, u3)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case OutfitMemberUpdate(u0, u1, u2, u3) =>
|
||||||
|
u0 :: u1 :: u2 :: u3 :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// ).as[OutfitMemberUpdate]
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2023 PSForever
|
// Copyright (c) 2024 PSForever
|
||||||
package net.psforever.packet.game
|
package net.psforever.packet.game
|
||||||
|
|
||||||
import net.psforever.packet.GamePacketOpcode.Type
|
import net.psforever.packet.GamePacketOpcode.Type
|
||||||
|
|
@ -139,13 +139,13 @@ object OutfitMembershipRequest extends Marshallable[OutfitMembershipRequest] {
|
||||||
type Type = Value
|
type Type = Value
|
||||||
|
|
||||||
val Create: RequestType.Value = Value(0)
|
val Create: RequestType.Value = Value(0)
|
||||||
val Form: RequestType.Value = Value(1)
|
val Form: RequestType.Value = Value(1)
|
||||||
val Unk2: RequestType.Value = Value(2)
|
val Unk2: RequestType.Value = Value(2)
|
||||||
val Accept: RequestType.Value = Value(3)
|
val Accept: RequestType.Value = Value(3)
|
||||||
val Reject: RequestType.Value = Value(4)
|
val Reject: RequestType.Value = Value(4)
|
||||||
val Cancel: RequestType.Value = Value(5)
|
val Cancel: RequestType.Value = Value(5)
|
||||||
val Unk6: RequestType.Value = Value(6) // 6 and 7 seen as failed decodes, validity unknown
|
val Unk6: RequestType.Value = Value(6) // 6 and 7 seen as failed decodes, validity unknown
|
||||||
val Unk7: RequestType.Value = Value(7)
|
val Unk7: RequestType.Value = Value(7)
|
||||||
|
|
||||||
implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uintL(3))
|
implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uintL(3))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
32
src/test/scala/game/OutfitEventTest.scala
Normal file
32
src/test/scala/game/OutfitEventTest.scala
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright (c) 2023 PSForever
|
||||||
|
package game
|
||||||
|
|
||||||
|
import net.psforever.packet._
|
||||||
|
import net.psforever.packet.game.OutfitEvent.RequestType
|
||||||
|
import net.psforever.packet.game.OutfitEventAction._
|
||||||
|
import net.psforever.packet.game._
|
||||||
|
import org.specs2.mutable._
|
||||||
|
import scodec.bits._
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
|
||||||
|
"decode CreatedOutfit ABC" in {
|
||||||
|
PacketCoding.decodePacket(created_ABC).require match {
|
||||||
|
case OutfitEvent(request_type, unk1, action) =>
|
||||||
|
request_type mustEqual RequestType.CreatedOutfit
|
||||||
|
unk1 mustEqual 258
|
||||||
|
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)
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode CreatedOutfit 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 pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
pkt mustEqual created_ABC
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue