added initial AvatarStatisticsMessage packet and tests; gave an Enumeration type to AvtarImplant action field

This commit is contained in:
FateJH 2018-03-08 10:26:36 -05:00
parent 2475b468b8
commit d965b5b3c8
8 changed files with 254 additions and 44 deletions

View file

@ -16,7 +16,7 @@ import net.psforever.packet.game.objectcreate.ObjectClass
*/
class ImplantTerminalInterfaceDefinition extends TerminalDefinition(ObjectClass.implant_terminal_interface) {
Packet = new ImplantTerminalInterfaceConverter
Name = "implante_terminal_interface"
Name = "implant_terminal_interface"
private val implants : Map[String, ImplantDefinition] = Map (
"advanced_regen" -> GlobalDefinitions.advanced_regen,

View file

@ -468,7 +468,7 @@ object GamePacketOpcode extends Enumeration {
case 0x7c => game.DismountBuildingMsg.decode
case 0x7d => noDecoder(UnknownMessage125)
case 0x7e => noDecoder(UnknownMessage126)
case 0x7f => noDecoder(AvatarStatisticsMessage)
case 0x7f => game.AvatarStatisticsMessage.decode
// OPCODES 0x80-8f
case 0x80 => noDecoder(GenericObjectAction2Message)

View file

@ -1,40 +1,48 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.ImplantType
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
/**
* Change the state of the implant.<br>
* An `Enumeration` for all the actions that can be applied to implants and implant slots.
*/
object ImplantAction extends Enumeration {
type Type = Value
val
Add,
Remove,
Initialization,
Activation,
UnlockMessage,
OutOfStamina
= Value
implicit val codec = PacketHelpers.createEnumerationCodec(this, uintL(3))
}
/**
* Change the state of the implant.
* Spawn messages for certain implant-related events.<br>
* <br>
* The implant Second Wind is technically an invalid `ImplantType` for this packet.
* This owes to the unique activation trigger for that implant - a near-death experience of ~0HP.
* @see `ImplantType`
* @param player_guid the player
* @param action
* 0 : add implant
* with status = 0 to 9 (from ImplantType)
* 1 : remove implant
* seems work with any value in status
* 2 : init implant
* status : 0 to "uninit"
* status : 1 to init
* 3 : activate implant
* status : 0 to desactivate
* status : 1 to activate
* 4 : number of implant slots unlocked
* status : 0 = no implant slot
* status : 1 = first implant slot + "implant message"
* status : 2 or 3 = unlock second & third slots
* 5 : out of stamina message
* status : 0 to stop the lock
* status : 1 to active the lock
* @param action how to affect the implant or the slot
* @param implantSlot : from 0 to 2
* @param status : see action
* @param status : a value that depends on context from `ImplantAction`:<br>
* `Add` - 0-9 depending on the `ImplantType`<br>
* `Remove` - any valid value; field is not significant to this action<br>
* `Initialization` - 0 to revoke slot; 1 to allocate implant slot<br>
* `Activation` - 0 to deactivate implant; 1 to activate implant<br>
* `UnlockMessage` - 0-3 as an unlocked implant slot; display a message<br>
* `OutOfStamina` - lock implant; 0 to lock; 1 to unlock; display a message
*/
final case class AvatarImplantMessage(player_guid : PlanetSideGUID,
action : Int,
action : ImplantAction.Value,
implantSlot : Int,
status : Int)
extends PlanetSideGamePacket {
@ -46,7 +54,7 @@ final case class AvatarImplantMessage(player_guid : PlanetSideGUID,
object AvatarImplantMessage extends Marshallable[AvatarImplantMessage] {
implicit val codec : Codec[AvatarImplantMessage] = (
("player_guid" | PlanetSideGUID.codec) ::
("action" | uintL(3)) ::
("action" | ImplantAction.codec) ::
("implantSlot" | uint2L) ::
("status" | uint4L)
).as[AvatarImplantMessage]

View file

@ -0,0 +1,105 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
import scala.annotation.switch
/**
* na
* @param unk1 na
* @param unk2 na
* @param unk3 na
*/
final case class Statistics(unk1 : Option[Int],
unk2 : Option[Int],
unk3 : List[Long])
/**
* na
* @param unk na
* @param stats na
*/
final case class AvatarStatisticsMessage(unk : Int,
stats : Statistics)
extends PlanetSideGamePacket {
type Packet = AvatarStatisticsMessage
def opcode = GamePacketOpcode.AvatarStatisticsMessage
def encode = AvatarStatisticsMessage.encode(this)
}
object AvatarStatisticsMessage extends Marshallable[AvatarStatisticsMessage] {
/**
* na
*/
private val longCodec : Codec[Statistics] = ulong(32).hlist.exmap (
{
case n :: HNil =>
Attempt.Successful(Statistics(None,None, List(n)))
},
{
case Statistics(_, _, Nil) =>
Attempt.Failure(Err("missing value (32-bit)"))
case Statistics(_, _, n) =>
Attempt.Successful(n.head :: HNil)
}
)
/**
* na
*/
private val complexCodec : Codec[Statistics] = (
uint(5) ::
uintL(11) ::
PacketHelpers.listOfNSized(8, uint32L)
).exmap[Statistics] (
{
case a :: b :: c :: HNil =>
Attempt.Successful(Statistics(Some(a), Some(b), c))
},
{
case Statistics(None, _, _) =>
Attempt.Failure(Err("missing value (5-bit)"))
case Statistics(_, None, _) =>
Attempt.Failure(Err("missing value (11-bit)"))
case Statistics(a, b, c) =>
if(c.length != 8) {
Attempt.Failure(Err("list must have 8 entries"))
}
else {
Attempt.Successful(a.get :: b.get :: c :: HNil)
}
}
)
/**
* na
* @param n na
* @return na
*/
private def selectCodec(n : Int) : Codec[Statistics] = (n : @switch) match {
case 2 | 3 =>
longCodec
case _ =>
complexCodec
}
implicit val codec : Codec[AvatarStatisticsMessage] = (
("unk" | uint(3)) >>:~ { unk =>
("stats" | selectCodec(unk)).hlist
}).as[AvatarStatisticsMessage]
}
object Statistics {
def apply(unk : Long) : Statistics =
Statistics(None, None, List(unk))
def apply(unk1 : Int, unk2 : Int, unk3 : List[Long]) : Statistics =
Statistics(Some(unk1), Some(unk2), unk3)
}

View file

@ -4,7 +4,6 @@ package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.types.ImplantType
import scodec.bits._
class AvatarImplantMessageTest extends Specification {
@ -14,7 +13,7 @@ class AvatarImplantMessageTest extends Specification {
PacketCoding.DecodePacket(string).require match {
case AvatarImplantMessage(player_guid, unk1, unk2, implant) =>
player_guid mustEqual PlanetSideGUID(3171)
unk1 mustEqual 3
unk1 mustEqual ImplantAction.Activation
unk2 mustEqual 1
implant mustEqual 1
case _ =>
@ -23,7 +22,7 @@ class AvatarImplantMessageTest extends Specification {
}
"encode" in {
val msg = AvatarImplantMessage(PlanetSideGUID(3171), 3, 1, 1)
val msg = AvatarImplantMessage(PlanetSideGUID(3171), ImplantAction.Activation, 1, 1)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string

View file

@ -0,0 +1,79 @@
// Copyright (c) 2017 PSForever
package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import scodec.bits._
class AvatarStatisticsMessageTest extends Specification {
val string_long = hex"7F 4 00000000 0"
val string_complex = hex"7F 01 3C 40 20 00 00 00 C0 00 00 00 00 00 00 00 20 00 00 00 20 00 00 00 40 00 00 00 00 00 00 00 00 00 00 00"
"decode (long)" in {
PacketCoding.DecodePacket(string_long).require match {
case AvatarStatisticsMessage(unk, stats) =>
unk mustEqual 2
stats.unk1 mustEqual None
stats.unk2 mustEqual None
stats.unk3.length mustEqual 1
stats.unk3.head mustEqual 0
case _ =>
ko
}
}
"decode (complex)" in {
PacketCoding.DecodePacket(string_complex).require match {
case AvatarStatisticsMessage(unk, stats) =>
unk mustEqual 0
stats.unk1 mustEqual Some(1)
stats.unk2 mustEqual Some(572)
stats.unk3.length mustEqual 8
stats.unk3.head mustEqual 1
stats.unk3(1) mustEqual 6
stats.unk3(2) mustEqual 0
stats.unk3(3) mustEqual 1
stats.unk3(4) mustEqual 1
stats.unk3(5) mustEqual 2
stats.unk3(6) mustEqual 0
stats.unk3(7) mustEqual 0
case _ =>
ko
}
}
"encode (long)" in {
val msg = AvatarStatisticsMessage(2, Statistics(0L))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_long
}
"encode (complex)" in {
val msg = AvatarStatisticsMessage(0, Statistics(1, 572, List[Long](1,6,0,1,1,2,0,0)))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_complex
}
"encode (failure; long; missing value)" in {
val msg = AvatarStatisticsMessage(0, Statistics(None, None,List(0L)))
PacketCoding.EncodePacket(msg).isFailure mustEqual true
}
"encode (failure; complex; missing value (5-bit))" in {
val msg = AvatarStatisticsMessage(0, Statistics(None, Some(572), List[Long](1,6,0,1,1,2,0,0)))
PacketCoding.EncodePacket(msg).isFailure mustEqual true
}
"encode (failure; complex; missing value (11-bit))" in {
val msg = AvatarStatisticsMessage(0, Statistics(Some(1), None, List[Long](1,6,0,1,1,2,0,0)))
PacketCoding.EncodePacket(msg).isFailure mustEqual true
}
"encode (failure; complex; wrong number of list entries)" in {
val msg = AvatarStatisticsMessage(0, Statistics(Some(1), None, List[Long](1,6,0,1)))
PacketCoding.EncodePacket(msg).isFailure mustEqual true
}
}