Skip to content

Commit

Permalink
Merge pull request #1160 from Resaec/outfit_membership_request_packet
Browse files Browse the repository at this point in the history
OutfitMembershipRequest packet
  • Loading branch information
Fate-JH authored Jan 8, 2024
2 parents 6eba049 + 2555875 commit cc48e96
Show file tree
Hide file tree
Showing 4 changed files with 436 additions and 18 deletions.
2 changes: 1 addition & 1 deletion src/main/scala/net/psforever/packet/GamePacketOpcode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ object GamePacketOpcode extends Enumeration {
case 0x89 => game.BugReportMessage.decode
case 0x8a => game.PlayerStasisMessage.decode
case 0x8b => noDecoder(UnknownMessage139)
case 0x8c => noDecoder(OutfitMembershipRequest)
case 0x8c => game.OutfitMembershipRequest.decode
case 0x8d => noDecoder(OutfitMembershipResponse)
case 0x8e => game.OutfitRequest.decode
case 0x8f => noDecoder(OutfitEvent)
Expand Down
184 changes: 184 additions & 0 deletions src/main/scala/net/psforever/packet/game/OutfitMembershipRequest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Copyright (c) 2023 PSForever
package net.psforever.packet.game

import net.psforever.packet.GamePacketOpcode.Type
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.PlanetSideGUID
import scodec.{Attempt, Codec, Err}
import scodec.bits.BitVector
import scodec.codecs._
import shapeless.{::, HNil}

final case class OutfitMembershipRequest(
request_type: OutfitMembershipRequest.RequestType.Type,
avatar_guid: PlanetSideGUID,
unk1: Int,
action: OutfitAction
) extends PlanetSideGamePacket {
type Packet = OutfitMembershipRequest

def opcode: Type = GamePacketOpcode.OutfitMembershipRequest

def encode: Attempt[BitVector] = OutfitMembershipRequest.encode(this)
}

abstract class OutfitAction(val code: Int)
object OutfitAction {

final case class CreateOutfit(unk2: String, unk3: Int, unk4: Boolean, outfit_name: String) extends OutfitAction(code = 0)

final case class FormOutfit(unk2: String, unk3: Int, unk4: Boolean, outfit_name: String) extends OutfitAction(code = 1)

final case class AcceptOutfitInvite(unk2: String) extends OutfitAction(code = 3)

final case class RejectOutfitInvite(unk2: String) extends OutfitAction(code = 4)

final case class CancelOutfitInvite(unk5: Int, unk6: Int, outfit_name: String) extends OutfitAction(code = 5)

final case class Unknown(badCode: Int, data: BitVector) extends OutfitAction(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 CreateOutfitCodec: Codec[CreateOutfit] =
(PacketHelpers.encodedWideString :: uint4L :: bool :: PacketHelpers.encodedWideString).xmap[CreateOutfit](
{
case unk2 :: unk3 :: unk4 :: outfit_name :: HNil =>
CreateOutfit(unk2, unk3, unk4, outfit_name)
},
{
case CreateOutfit(unk2, unk3, unk4, outfit_name) =>
unk2 :: unk3 :: unk4 :: outfit_name :: HNil
}
)

val FormOutfitCodec: Codec[FormOutfit] =
(PacketHelpers.encodedWideString :: uint4L :: bool :: PacketHelpers.encodedWideString).xmap[FormOutfit](
{
case unk2 :: unk3 :: unk4 :: outfit_name :: HNil =>
FormOutfit(unk2, unk3, unk4, outfit_name)
},
{
case FormOutfit(unk2, unk3, unk4, outfit_name) =>
unk2 :: unk3 :: unk4 :: outfit_name :: HNil
}
)

val AcceptOutfitCodec: Codec[AcceptOutfitInvite] =
PacketHelpers.encodedWideString.xmap[AcceptOutfitInvite](
{
case unk2 =>
AcceptOutfitInvite(unk2)
},
{
case AcceptOutfitInvite(unk2) =>
unk2
}
)

val RejectOutfitCodec: Codec[RejectOutfitInvite] =
PacketHelpers.encodedWideString.xmap[RejectOutfitInvite](
{
case unk2 =>
RejectOutfitInvite(unk2)
},
{
case RejectOutfitInvite(unk2) =>
unk2
}
)

val CancelOutfitCodec: Codec[CancelOutfitInvite] =
(uint16L :: uint16L :: PacketHelpers.encodedWideStringAligned(5)).xmap[CancelOutfitInvite](
{
case unk5 :: unk6 :: outfit_name :: HNil =>
CancelOutfitInvite(unk5, unk6, outfit_name)
},
{
case CancelOutfitInvite(unk5, unk6, outfit_name) =>
unk5 :: unk6 :: outfit_name :: 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[OutfitAction] =
everFailCondition.exmap[OutfitAction](
_ => 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 OutfitMembershipRequest extends Marshallable[OutfitMembershipRequest] {

object RequestType extends Enumeration {
type Type = Value

val Create: RequestType.Value = Value(0)
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)

implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uintL(3))
}

private def selectFromType(code: Int): Codec[OutfitAction] = {
import OutfitAction.Codecs._
import scala.annotation.switch

((code: @switch) match {
case 0 => CreateOutfitCodec
case 1 => FormOutfitCodec // so far same as Create
case 2 => unknownCodec(action = code)
case 3 => AcceptOutfitCodec
case 4 => RejectOutfitCodec // so far same as Accept
case 5 => CancelOutfitCodec
case 6 => unknownCodec(action = code)
case 7 => unknownCodec(action = code)
// 3 bit limit
case _ => failureCodec(code)
}).asInstanceOf[Codec[OutfitAction]]
}

implicit val codec: Codec[OutfitMembershipRequest] = (
("request_type" | RequestType.codec) >>:~ { request_type =>
("avatar_guid" | PlanetSideGUID.codec) ::
("unk1" | uint16L) ::
("action" | selectFromType(request_type.id))
}
).xmap[OutfitMembershipRequest](
{
case request_type :: avatar_guid :: u1 :: action :: HNil =>
OutfitMembershipRequest(request_type, avatar_guid, u1, action)
},
{
case OutfitMembershipRequest(request_type, avatar_guid, u1, action) =>
request_type :: avatar_guid :: u1 :: action :: HNil
}
)
}
Loading

0 comments on commit cc48e96

Please sign in to comment.