add initial SetChatFilterMessage packet and tests; gave FriendsResponse a valid action enumeration; changed continent id and building id in DensityUpdateLevel to use simple data type rather than PlanetSideGUID; worked these packet changes into WSA initialization workflow

This commit is contained in:
FateJH 2018-03-07 22:32:27 -05:00
parent 2526f15406
commit 0578d291a5
7 changed files with 224 additions and 24 deletions

View file

@ -436,7 +436,7 @@ object GamePacketOpcode extends Enumeration {
case 0x60 => game.FavoritesMessage.decode
case 0x61 => game.ObjectDetectedMessage.decode
case 0x62 => game.SplashHitMessage.decode
case 0x63 => noDecoder(SetChatFilterMessage)
case 0x63 => game.SetChatFilterMessage.decode
case 0x64 => noDecoder(AvatarSearchCriteriaMessage)
case 0x65 => noDecoder(AvatarSearchResponse)
case 0x66 => game.WeaponJammedMessage.decode

View file

@ -6,6 +6,22 @@ import scodec.Codec
import scodec.codecs._
import shapeless.{::, HNil}
object FriendAction extends Enumeration {
type Type = Value
val
InitializeFriendList,
AddFriend,
RemoveFriend,
UpdateFriend,
InitializeIgnoreList,
AddIgnoredPlayer,
RemoveIgnoredPlayer
= Value
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint(3))
}
/**
* An entry in the list of players known to and tracked by this player.
* They're called "friends" even though they can be used for a list of ignored players as well.
@ -37,7 +53,7 @@ final case class Friend(name : String,
* @param unk3 na; always `true`?
* @param friends a list of `Friend`s
*/
final case class FriendsResponse(action : Int,
final case class FriendsResponse(action : FriendAction.Value,
unk1 : Int,
unk2 : Boolean,
unk3 : Boolean,
@ -66,7 +82,7 @@ object Friend extends Marshallable[Friend] {
object FriendsResponse extends Marshallable[FriendsResponse] {
implicit val codec : Codec[FriendsResponse] = (
("action" | uintL(3)) ::
("action" | FriendAction.codec) ::
("unk1" | uint4L) ::
("unk2" | bool) ::
("unk3" | bool) ::
@ -76,8 +92,8 @@ object FriendsResponse extends Marshallable[FriendsResponse] {
})
).xmap[FriendsResponse] (
{
case act :: u1 :: u2 :: u3 :: num :: friend1 :: friends :: HNil =>
val friendList : List[Friend] = if(friend1.isDefined) { friend1.get :: friends } else { friends }
case act :: u1 :: u2 :: u3 :: _ :: friend1 :: friends :: HNil =>
val friendList : List[Friend] = if(friend1.isDefined) { friend1.get +: friends } else { friends }
FriendsResponse(act, u1, u2, u3, friendList)
},
{

View file

@ -0,0 +1,96 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import scodec.{Attempt, Codec}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* An `Enumeration` of the valid chat channels.
*/
object ChatChannel extends Enumeration {
type Type = Value
val
Unknown,
Tells,
Local,
Squad,
Outfit,
Command,
Platoon,
Broadcast,
SquadLeader
= Value
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint(7))
}
/**
* Which comm. channels are allowed to display in the main chat window.
* The server sends a `SetChatFilterMessage` and the client responds with the same during login.<br>
* <br>
* Nine channels exist.
* Their values can be modified by radio buttons found under the current chat window's "Options" pane.
* Each time the client updates the channel permissions, it sends this packet to the server nine times.
* The packet starts with the previous channel filter states and then updates each channel sequentially.<br>
* <br>
* The `send_channel` and the `channel_filter` values are in the following order:<br>
* Unknown, Tells, Local, Squad, Outfit, Command, Platoon, Broadcast, Squad Leader<br>
* The first channel is unlisted.
* @param send_channel automatically select the fully qualified channel to which the user sends messages
* @param origin where this packet was dispatched;
* `true`, from the server; `false`, from the client
* @param whitelist each channel permitted to post its messages;
* when evaluated from a packet, always in original order
*/
final case class SetChatFilterMessage(send_channel : ChatChannel.Value,
origin : Boolean,
whitelist : List[ChatChannel.Value])
extends PlanetSideGamePacket {
type Packet = SetChatFilterMessage
def opcode = GamePacketOpcode.SetChatFilterMessage
def encode = SetChatFilterMessage.encode(this)
}
object SetChatFilterMessage extends Marshallable[SetChatFilterMessage] {
/**
* Transform a `List` of `Boolean` values into a `List` of `ChatChannel` values.
* @param filters the boolean values representing ordered channel filters
* @return the names of the channels permitted
*/
private def stateArrayToChannelFilters(filters : List[Boolean]) : List[ChatChannel.Value] = {
(0 until 9)
.filter(channel => { filters(channel) })
.map(channel => ChatChannel(channel))
.toList
}
/**
* Transform a `List` of `ChatChannel` values into a `List` of `Boolean` values.
* @param filters the names of the channels permitted
* @return the boolean values representing ordered channel filters
*/
private def channelFiltersToStateArray(filters : List[ChatChannel.Value]) : List[Boolean] = {
import scala.collection.mutable.ListBuffer
val list = ListBuffer.fill(9)(false)
filters.foreach(channel => { list(channel.id) = true })
list.toList
}
implicit val codec : Codec[SetChatFilterMessage] = (
("send_channel" | ChatChannel.codec) ::
("origin" | bool) ::
("whitelist" | PacketHelpers.listOfNSized(9, bool))
).exmap[SetChatFilterMessage] (
{
case a :: b :: c :: HNil =>
Attempt.Successful(SetChatFilterMessage(a, b, stateArrayToChannelFilters(c)))
},
{
case SetChatFilterMessage(a, b, c) =>
Attempt.Successful(a :: b :: channelFiltersToStateArray(c) :: HNil)
}
)
}

View file

@ -49,4 +49,4 @@ class DensityLevelUpdateMessageTest extends Specification {
val msg1 = DensityLevelUpdateMessage(1, 19999, List(0,0, 0,0, 0,-1, 0,0))
PacketCoding.EncodePacket(msg1).isSuccessful mustEqual false
}
}
}

View file

@ -14,7 +14,7 @@ class FriendsResponseTest extends Specification {
"decode (one friend)" in {
PacketCoding.DecodePacket(stringOneFriend).require match {
case FriendsResponse(action, unk2, unk3, unk4, list) =>
action mustEqual 3
action mustEqual FriendAction.UpdateFriend
unk2 mustEqual 0
unk3 mustEqual true
unk4 mustEqual true
@ -29,7 +29,7 @@ class FriendsResponseTest extends Specification {
"decode (multiple friends)" in {
PacketCoding.DecodePacket(stringManyFriends).require match {
case FriendsResponse(action, unk2, unk3, unk4, list) =>
action mustEqual 0
action mustEqual FriendAction.InitializeFriendList
unk2 mustEqual 0
unk3 mustEqual true
unk4 mustEqual true
@ -52,7 +52,7 @@ class FriendsResponseTest extends Specification {
"decode (short)" in {
PacketCoding.DecodePacket(stringShort).require match {
case FriendsResponse(action, unk2, unk3, unk4, list) =>
action mustEqual 4
action mustEqual FriendAction.InitializeIgnoreList
unk2 mustEqual 0
unk3 mustEqual true
unk4 mustEqual true
@ -63,7 +63,7 @@ class FriendsResponseTest extends Specification {
}
"encode (one friend)" in {
val msg = FriendsResponse(3, 0, true, true,
val msg = FriendsResponse(FriendAction.UpdateFriend, 0, true, true,
Friend("KurtHectic-G", false) ::
Nil)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
@ -72,7 +72,7 @@ class FriendsResponseTest extends Specification {
}
"encode (multiple friends)" in {
val msg = FriendsResponse(0, 0, true, true,
val msg = FriendsResponse(FriendAction.InitializeFriendList, 0, true, true,
Friend("Angello-W", false) ::
Friend("thephattphrogg", false) ::
Friend("Kimpossible12", false) ::
@ -85,7 +85,7 @@ class FriendsResponseTest extends Specification {
}
"encode (short)" in {
val msg = FriendsResponse(4, 0, true, true)
val msg = FriendsResponse(FriendAction.InitializeIgnoreList, 0, true, true)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringShort

View file

@ -0,0 +1,75 @@
// Copyright (c) 2017 PSForever
package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import scodec.bits._
class SetChatFilterMessageTest extends Specification {
val string = hex"63 05FF80"
val string_custom = hex"63 05C180"
"decode" in {
PacketCoding.DecodePacket(string).require match {
case SetChatFilterMessage(send, origin, filters) =>
send mustEqual ChatChannel.Local
origin mustEqual true
filters.length mustEqual 9
filters.head mustEqual ChatChannel.Unknown
filters(1) mustEqual ChatChannel.Tells
filters(2) mustEqual ChatChannel.Local
filters(3) mustEqual ChatChannel.Squad
filters(4) mustEqual ChatChannel.Outfit
filters(5) mustEqual ChatChannel.Command
filters(6) mustEqual ChatChannel.Platoon
filters(7) mustEqual ChatChannel.Broadcast
filters(8) mustEqual ChatChannel.SquadLeader
case _ =>
ko
}
}
"decode (custom)" in {
PacketCoding.DecodePacket(string_custom).require match {
case SetChatFilterMessage(send, origin, filters) =>
send mustEqual ChatChannel.Local
origin mustEqual true
filters.length mustEqual 4
filters.head mustEqual ChatChannel.Unknown
filters(1) mustEqual ChatChannel.Tells
filters(2) mustEqual ChatChannel.Broadcast
filters(3) mustEqual ChatChannel.SquadLeader
case _ =>
ko
}
}
"encode" in {
val msg = SetChatFilterMessage(ChatChannel.Local, true, List(ChatChannel.Unknown, ChatChannel.Tells, ChatChannel.Local, ChatChannel.Squad, ChatChannel.Outfit, ChatChannel.Command, ChatChannel.Platoon, ChatChannel.Broadcast, ChatChannel.SquadLeader))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
"encode (success; same channel listed multiple times)" in {
val msg = SetChatFilterMessage(ChatChannel.Local, true, List(ChatChannel.Unknown, ChatChannel.Unknown, ChatChannel.Tells, ChatChannel.Tells, ChatChannel.Local, ChatChannel.Squad, ChatChannel.Outfit, ChatChannel.Command, ChatChannel.Platoon, ChatChannel.Broadcast, ChatChannel.SquadLeader))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
"encode (success; out of order)" in {
val msg = SetChatFilterMessage(ChatChannel.Local, true, List(ChatChannel.Squad, ChatChannel.Outfit, ChatChannel.SquadLeader, ChatChannel.Unknown, ChatChannel.Command, ChatChannel.Platoon, ChatChannel.Broadcast, ChatChannel.Tells, ChatChannel.Local))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
"encode (success; custom)" in {
val msg = SetChatFilterMessage(ChatChannel.Local, true, List(ChatChannel.Unknown, ChatChannel.Tells, ChatChannel.Broadcast, ChatChannel.SquadLeader))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_custom
}
}