mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-07-09 21:44:43 +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 0x8d => game.OutfitMembershipResponse.decode
|
||||
case 0x8e => game.OutfitRequest.decode
|
||||
case 0x8f => noDecoder(OutfitEvent)
|
||||
case 0x8f => game.OutfitEvent.decode
|
||||
|
||||
// OPCODES 0x90-9f
|
||||
case 0x90 => noDecoder(OutfitMemberEvent)
|
||||
case 0x91 => noDecoder(OutfitMemberUpdate)
|
||||
case 0x91 => game.OutfitMemberUpdate.decode
|
||||
case 0x92 => game.PlanetsideStringAttributeMessage.decode
|
||||
case 0x93 => game.DataChallengeMessage.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
|
||||
|
||||
import net.psforever.packet.GamePacketOpcode.Type
|
||||
|
|
@ -139,13 +139,13 @@ object OutfitMembershipRequest extends Marshallable[OutfitMembershipRequest] {
|
|||
type Type = Value
|
||||
|
||||
val Create: RequestType.Value = Value(0)
|
||||
val Form: RequestType.Value = Value(1)
|
||||
val Unk2: RequestType.Value = Value(2)
|
||||
val Form: RequestType.Value = Value(1)
|
||||
val Unk2: RequestType.Value = Value(2)
|
||||
val Accept: RequestType.Value = Value(3)
|
||||
val Reject: RequestType.Value = Value(4)
|
||||
val Cancel: RequestType.Value = Value(5)
|
||||
val Unk6: RequestType.Value = Value(6) // 6 and 7 seen as failed decodes, validity unknown
|
||||
val Unk7: RequestType.Value = Value(7)
|
||||
val Unk6: RequestType.Value = Value(6) // 6 and 7 seen as failed decodes, validity unknown
|
||||
val Unk7: RequestType.Value = Value(7)
|
||||
|
||||
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