From 73c88fb9de4706f4946cd902c228278f934b4e65 Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 27 Apr 2017 15:32:49 -0400 Subject: [PATCH 1/5] initial work on BattleplanMessage packet --- .../psforever/packet/GamePacketOpcode.scala | 2 +- .../packet/game/BattleplanMessage.scala | 195 ++++++++++++++++++ 2 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index 5c29f2b0..958d9666 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -531,7 +531,7 @@ object GamePacketOpcode extends Enumeration { case 0xb0 => game.VoiceHostRequest.decode case 0xb1 => game.VoiceHostKill.decode case 0xb2 => game.VoiceHostInfo.decode - case 0xb3 => noDecoder(BattleplanMessage) + case 0xb3 => game.BattleplanMessage.decode case 0xb4 => game.BattleExperienceMessage.decode case 0xb5 => noDecoder(TargetingImplantRequest) case 0xb6 => game.ZonePopulationUpdateMessage.decode diff --git a/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala b/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala new file mode 100644 index 00000000..b5005ad2 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala @@ -0,0 +1,195 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game + +import net.psforever.newcodecs.newcodecs +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import scodec.{Attempt, Codec, Err} +import scodec.codecs._ +import shapeless.{::, HNil} + +final case class SheetOne(unk1 : Float, + unk2 : Int) + +final case class SheetTwo(unk1 : Float, + unk2 : Float) + +final case class SheetFive(unk1 : Float, + unk2 : Float, + unk3 : Float, + unk4 : Int) + +final case class SheetSix(unk1 : Float, + unk2 : Float, + unk3 : Int, + unk4 : Int, + unk5 : String) + +final case class SheetSeven(unk : Int) + +final case class BattleDiagram(sheet1 : Option[SheetOne] = None, + sheet2 : Option[SheetTwo] = None, + sheet3 : Option[SheetFive] = None, + sheet4 : Option[SheetSix] = None, + sheet5 : Option[SheetSeven] = None) + +final case class BattleplanMessage(unk1 : Long, + unk2 : String, + unk3 : Int, + unk4 : List[BattleDiagram]) + extends PlanetSideGamePacket { + type Packet = BattleplanMessage + def opcode = GamePacketOpcode.BattleplanMessage + def encode = BattleplanMessage.encode(this) +} + +object BattelplanDiagram { + def sheet1(unk1 : Float, unk2 : Int) : BattleDiagram = + BattleDiagram(Some(SheetOne(unk1, unk2))) + + def sheet2(unk1 : Float, unk2 : Float) : BattleDiagram = + BattleDiagram(None, Some(SheetTwo(unk1, unk2))) + + def sheet3(unk1 : Float, unk2 : Float, unk3 : Float, unk4 : Int) : BattleDiagram = + BattleDiagram(None, None, Some(SheetFive(unk1, unk2, unk3, unk4))) + + def sheet4(unk1 : Float, unk2 : Float, unk3 : Int, unk4 : Int, unk5 : String) : BattleDiagram = + BattleDiagram(None, None, None, Some(SheetSix(unk1, unk2, unk3, unk4, unk5))) + + def sheet5(unk1 : Int) : BattleDiagram = + BattleDiagram(None, None, None, None, Some(SheetSeven(unk1))) +} + +object BattleplanMessage extends Marshallable[BattleplanMessage] { + private final case class BattleDiagramLayer(diagram : BattleDiagram, + next : Option[BattleDiagramLayer]) + + private val plan1_codec : Codec[SheetOne] = ( //size: 8; pad: +0 + ("unk1" | newcodecs.q_float(0.0, 16.0, 5)) :: + ("unk2" | uintL(3)) + ).as[SheetOne] + + private val plan2_codec : Codec[SheetTwo] = ( //size: 22; pad: +2 + ("unk1" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: + ("unk2" | newcodecs.q_float(-4096.0, 12288.0, 11)) + ).as[SheetTwo] + + private val plan5_codec : Codec[SheetFive] = ( //size: 44; pad: +4 + ("unk1" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: + ("unk2" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: + ("unk3" | newcodecs.q_float(1024.0, 0.0, 11)) :: + ("unk4" | uintL(11)) + ).as[SheetFive] + + private def plan6_codec(pad : Int) : Codec[SheetSix] = ( //size: 31 + string.length.field + string.length * 16 + padding; pad: value resets + ("unk1" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: + ("unk2" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: + ("unk3" | uintL(3)) :: + ("unk4" | uintL(6)) :: + ("unk5" | PacketHelpers.encodedWideStringAligned( (pad + 1) % 8 )) + ).as[SheetSix] + + private val plan7_codec : Codec[SheetSeven] = ("unk" | uintL(6)).as[SheetSeven] // size: 6; pad: +2 + + private def diagram_codec(plan : Int, pad : Int) : Codec[BattleDiagram] = ( + conditional(plan == 1, plan1_codec) :: + conditional(plan == 2, plan2_codec) :: + conditional(plan == 5, plan5_codec) :: + conditional(plan == 6, plan6_codec(pad)) :: + conditional(plan == 7, plan7_codec) + ).exmap[BattleDiagram] ( + { + case None :: None :: None :: None :: None :: HNil => + Attempt.failure(Err(s"unknown sheet number $plan")) + + case a :: b :: c :: d :: e :: HNil => + Attempt.successful(BattleDiagram(a, b, c, d, e)) + }, + { + case BattleDiagram(Some(sheet), _, _, _, _) => + Attempt.successful(Some(sheet) :: None :: None :: None :: None :: HNil) + + case BattleDiagram(None, Some(sheet), _, _, _) => + Attempt.successful(None :: Some(sheet) :: None :: None :: None :: HNil) + + case BattleDiagram(None, None, Some(sheet), _, _) => + Attempt.successful(None :: None :: Some(sheet) :: None :: None :: HNil) + + case BattleDiagram(None, None, None, Some(sheet), _) => + Attempt.successful(None :: None :: None :: Some(sheet) :: None :: HNil) + + case BattleDiagram(None, None, None, None, Some(sheet)) => + Attempt.successful(None :: None :: None :: None :: Some(sheet) :: HNil) + + case BattleDiagram(None, None, None, None, None) => + Attempt.failure(Err("can not deal with blank sheet")) + } + ) + + private def parse_diagrams_codec(remaining : Int, pad : Int = 0) : Codec[BattleDiagramLayer] = ( + uint4L >>:~ { plan => + ("diagram" | diagram_codec(plan, pad)) :: + conditional(remaining > 1, + "next" | parse_diagrams_codec( + remaining - 1, + pad + (if(plan == 2 || plan == 7) { 2 } else if(plan == 5) { 4 } else if(plan == 6) { -pad } else { 0 }) + ) + ) + }).exmap[BattleDiagramLayer] ( + { + case _ :: diagram :: next :: HNil => + Attempt.successful(BattleDiagramLayer(diagram, next)) + }, + { + case BattleDiagramLayer(BattleDiagram(Some(sheet), _, _, _, _), next) => + Attempt.successful(1 :: BattleDiagram(Some(sheet)) :: next :: HNil) + + case BattleDiagramLayer(BattleDiagram(None, Some(sheet), _, _, _), next) => + Attempt.successful(2 :: BattleDiagram(None, Some(sheet)) :: next :: HNil) + + case BattleDiagramLayer(BattleDiagram(None, None, Some(sheet), _, _), next) => + Attempt.successful(5 :: BattleDiagram(None, None, Some(sheet)) :: next :: HNil) + + case BattleDiagramLayer(BattleDiagram(None, None, None, Some(sheet), _), next) => + Attempt.successful(6 :: BattleDiagram(None, None, None, Some(sheet)) :: next :: HNil) + + case BattleDiagramLayer(BattleDiagram(None, None, None, None, Some(sheet)), next) => + Attempt.successful(7 :: BattleDiagram(None, None, None, None, Some(sheet)) :: next :: HNil) + } + ) + + import scala.collection.mutable.ListBuffer + private def rollDiagramLayers(element : BattleDiagramLayer, list : ListBuffer[BattleDiagram]) : Unit = { + list += element.diagram + if(element.next.isDefined) + rollDiagramLayers(element.next.get, list) + } + + private def unrollDiagramLayers(revIter : Iterator[BattleDiagram], layers : Option[BattleDiagramLayer] = None) : Option[BattleDiagramLayer] = { + if(!revIter.hasNext) + return layers + val elem : BattleDiagram = revIter.next + unrollDiagramLayers(revIter, Some(BattleDiagramLayer(elem, layers))) + } + + implicit val codec : Codec[BattleplanMessage] = ( + ("unk1" | uint32L) :: + ("unk2" | PacketHelpers.encodedWideString) :: + ("unk3" | uint16L) :: + (uint8L >>:~ { count => + conditional(count > 0, parse_diagrams_codec(count)).hlist + }) + ).exmap[BattleplanMessage] ( + { + case unk1 :: unk2 :: unk3 :: _ :: diagramLayers :: HNil => + val list : ListBuffer[BattleDiagram] = new ListBuffer() + if(diagramLayers.isDefined) + rollDiagramLayers(diagramLayers.get, list) + Attempt.successful(BattleplanMessage(unk1, unk2, unk3, list.toList)) + }, + { + case BattleplanMessage(unk1, unk2, unk3, diagrams) => + val layersOpt = unrollDiagramLayers(diagrams.reverseIterator) + Attempt.successful(unk1 :: unk2 :: unk3 :: diagrams.size :: layersOpt :: HNil) + } + ) +} From 7d158eba1a59157ba49f387ac5b52d91503cf41c Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 27 Apr 2017 23:16:36 -0400 Subject: [PATCH 2/5] adjusted workflow of Codec to better account for String padding and to consolidate different data formats; added comments; added working (simple) test --- .../packet/game/BattleplanMessage.scala | 250 ++++++++++++++---- .../scala/game/BattleplanMessageTest.scala | 39 +++ 2 files changed, 233 insertions(+), 56 deletions(-) create mode 100644 common/src/test/scala/game/BattleplanMessageTest.scala diff --git a/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala b/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala index b5005ad2..277eb103 100644 --- a/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala @@ -7,35 +7,78 @@ import scodec.{Attempt, Codec, Err} import scodec.codecs._ import shapeless.{::, HNil} +/** + * A common ancestor of all the different "sheets" used to keep track of the data. + */ +sealed trait DiagramSheet + +/** + * na + * @param unk1 na + * @param unk2 na + */ final case class SheetOne(unk1 : Float, - unk2 : Int) + unk2 : Int) extends DiagramSheet +/** + * na + * @param unk1 na + * @param unk2 na + */ final case class SheetTwo(unk1 : Float, - unk2 : Float) + unk2 : Float) extends DiagramSheet +/** + * na + * @param unk1 na + * @param unk2 na + * @param unk3 na + * @param unk4 na + */ final case class SheetFive(unk1 : Float, unk2 : Float, unk3 : Float, - unk4 : Int) + unk4 : Int) extends DiagramSheet +/** + * na + * @param unk1 na + * @param unk2 na + * @param unk3 na + * @param unk4 na + * @param unk5 na + */ final case class SheetSix(unk1 : Float, unk2 : Float, unk3 : Int, unk4 : Int, - unk5 : String) + unk5 : String) extends DiagramSheet -final case class SheetSeven(unk : Int) +/** + * na + * @param unk na + */ +final case class SheetSeven(unk : Int) extends DiagramSheet -final case class BattleDiagram(sheet1 : Option[SheetOne] = None, - sheet2 : Option[SheetTwo] = None, - sheet3 : Option[SheetFive] = None, - sheet4 : Option[SheetSix] = None, - sheet5 : Option[SheetSeven] = None) +/** + * na + * @param pageNum a hint to kind of data stored + * @param sheet the data + */ +final case class BattleDiagram(pageNum : Int, + sheet : Option[DiagramSheet] = None) +/** + * na + * @param unk1 na + * @param mastermind the player who contributed this battle plan + * @param unk2 na + * @param diagrams a list of the individual `BattleDiagram`s that compose this plan + */ final case class BattleplanMessage(unk1 : Long, - unk2 : String, - unk3 : Int, - unk4 : List[BattleDiagram]) + mastermind : String, + unk2 : Int, + diagrams : List[BattleDiagram]) extends PlanetSideGamePacket { type Packet = BattleplanMessage def opcode = GamePacketOpcode.BattleplanMessage @@ -43,36 +86,90 @@ final case class BattleplanMessage(unk1 : Long, } object BattelplanDiagram { + /** + * Create a `BattleDiagram` object containing `SheetOne` data. + * @param unk1 na + * @param unk2 na + * @return a `BattleDiagram` object + */ def sheet1(unk1 : Float, unk2 : Int) : BattleDiagram = - BattleDiagram(Some(SheetOne(unk1, unk2))) + BattleDiagram(1, Some(SheetOne(unk1, unk2))) + /** + * Create a `BattleDiagram` object containing `SheetTwo` data. + * @param unk1 na + * @param unk2 na + * @return a `BattleDiagram` object + */ def sheet2(unk1 : Float, unk2 : Float) : BattleDiagram = - BattleDiagram(None, Some(SheetTwo(unk1, unk2))) + BattleDiagram(2, Some(SheetTwo(unk1, unk2))) - def sheet3(unk1 : Float, unk2 : Float, unk3 : Float, unk4 : Int) : BattleDiagram = - BattleDiagram(None, None, Some(SheetFive(unk1, unk2, unk3, unk4))) + /** + * Create a `BattleDiagram` object containing `SheetFive` data. + * @param unk1 na + * @param unk2 na + * @param unk3 na + * @param unk4 na + * @return a `BattleDiagram` object + */ + def sheet5(unk1 : Float, unk2 : Float, unk3 : Float, unk4 : Int) : BattleDiagram = + BattleDiagram(5, Some(SheetFive(unk1, unk2, unk3, unk4))) - def sheet4(unk1 : Float, unk2 : Float, unk3 : Int, unk4 : Int, unk5 : String) : BattleDiagram = - BattleDiagram(None, None, None, Some(SheetSix(unk1, unk2, unk3, unk4, unk5))) + /** + * Create a `BattleDiagram` object containing `SheetSix` data. + * @param unk1 na + * @param unk2 na + * @param unk3 na + * @param unk4 na + * @param unk5 na + * @return a `BattleDiagram` object + */ + def sheet6(unk1 : Float, unk2 : Float, unk3 : Int, unk4 : Int, unk5 : String) : BattleDiagram = + BattleDiagram(6, Some(SheetSix(unk1, unk2, unk3, unk4, unk5))) - def sheet5(unk1 : Int) : BattleDiagram = - BattleDiagram(None, None, None, None, Some(SheetSeven(unk1))) + /** + * Create a `BattleDiagram` object containing `SheetSeven` data. + * @param unk na + * @return a `BattleDiagram` object + */ + def sheet7(unk : Int) : BattleDiagram = + BattleDiagram(7, Some(SheetSeven(unk))) } object BattleplanMessage extends Marshallable[BattleplanMessage] { + + /** + * An intermediary object intended to temporarily store `BattleDiagram` objects.
+ *
+ * This hidden object is arranged like a linked list; + * but, later, it is converted into an accessible formal `List` of `BattleDiagram` objects during decoding; + * likewise, during the encoding process, the `List` is transformed back into a linked list structure. + * `Scala`'s own linked list `Collection` is deprecated, without substitution, so this custom one must be used. + * @param diagram the contained `BattleDiagram` with the sheet that maintains the data + * @param next the next `BattleDiagramLayer`, if any, arranging into a linked list + */ private final case class BattleDiagramLayer(diagram : BattleDiagram, next : Option[BattleDiagramLayer]) + /** + * Parse data into a `SheetOne` object. + */ private val plan1_codec : Codec[SheetOne] = ( //size: 8; pad: +0 ("unk1" | newcodecs.q_float(0.0, 16.0, 5)) :: ("unk2" | uintL(3)) ).as[SheetOne] + /** + * Parse data into a `SheetTwo` object. + */ private val plan2_codec : Codec[SheetTwo] = ( //size: 22; pad: +2 ("unk1" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: ("unk2" | newcodecs.q_float(-4096.0, 12288.0, 11)) ).as[SheetTwo] + /** + * Parse data into a `SheetFive` object. + */ private val plan5_codec : Codec[SheetFive] = ( //size: 44; pad: +4 ("unk1" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: ("unk2" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: @@ -80,6 +177,10 @@ object BattleplanMessage extends Marshallable[BattleplanMessage] { ("unk4" | uintL(11)) ).as[SheetFive] + /** + * Parse data into a `SheetSix` object. + * @param pad the current padding for the `String` entry + */ private def plan6_codec(pad : Int) : Codec[SheetSix] = ( //size: 31 + string.length.field + string.length * 16 + padding; pad: value resets ("unk1" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: ("unk2" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: @@ -88,8 +189,18 @@ object BattleplanMessage extends Marshallable[BattleplanMessage] { ("unk5" | PacketHelpers.encodedWideStringAligned( (pad + 1) % 8 )) ).as[SheetSix] + /** + * Parse data into a `SheetSeven` object. + */ private val plan7_codec : Codec[SheetSeven] = ("unk" | uintL(6)).as[SheetSeven] // size: 6; pad: +2 + /** + * Switch between different patterns to create a `BattleDiagram` for the following data. + * @param plan a hint to help parse the following data + * @param pad the current padding for any `String` entry stored within the parsed elements; + * when `plan == 6`, `plan6_codec` utilizes this value + * @return a `BattleDiagram` object + */ private def diagram_codec(plan : Int, pad : Int) : Codec[BattleDiagram] = ( conditional(plan == 1, plan1_codec) :: conditional(plan == 2, plan2_codec) :: @@ -98,33 +209,59 @@ object BattleplanMessage extends Marshallable[BattleplanMessage] { conditional(plan == 7, plan7_codec) ).exmap[BattleDiagram] ( { - case None :: None :: None :: None :: None :: HNil => - Attempt.failure(Err(s"unknown sheet number $plan")) + case Some(sheet) :: None :: None :: None :: None :: HNil => + Attempt.successful(BattleDiagram(plan, Some(sheet))) - case a :: b :: c :: d :: e :: HNil => - Attempt.successful(BattleDiagram(a, b, c, d, e)) + case None :: Some(sheet) :: None :: None :: None :: HNil => + Attempt.successful(BattleDiagram(plan, Some(sheet))) + + case None :: None :: Some(sheet) :: None :: None :: HNil => + Attempt.successful(BattleDiagram(plan, Some(sheet))) + + case None :: None :: None :: Some(sheet) :: None :: HNil => + Attempt.successful(BattleDiagram(plan, Some(sheet))) + + case None :: None :: None :: None :: Some(sheet) :: HNil => + Attempt.successful(BattleDiagram(plan, Some(sheet))) + + case None :: None :: None :: None :: None :: HNil => + Attempt.successful(BattleDiagram(plan, None)) + + case _:: _ :: _ :: _ :: _ :: HNil => + Attempt.failure(Err(s"too many sheets at once for $plan")) }, { - case BattleDiagram(Some(sheet), _, _, _, _) => - Attempt.successful(Some(sheet) :: None :: None :: None :: None :: HNil) + case BattleDiagram(1, Some(sheet)) => + Attempt.successful(Some(sheet.asInstanceOf[SheetOne]) :: None :: None :: None :: None :: HNil) - case BattleDiagram(None, Some(sheet), _, _, _) => - Attempt.successful(None :: Some(sheet) :: None :: None :: None :: HNil) + case BattleDiagram(2, Some(sheet)) => + Attempt.successful(None :: Some(sheet.asInstanceOf[SheetTwo]) :: None :: None :: None :: HNil) - case BattleDiagram(None, None, Some(sheet), _, _) => - Attempt.successful(None :: None :: Some(sheet) :: None :: None :: HNil) + case BattleDiagram(5, Some(sheet)) => + Attempt.successful(None :: None :: Some(sheet.asInstanceOf[SheetFive]) :: None :: None :: HNil) - case BattleDiagram(None, None, None, Some(sheet), _) => - Attempt.successful(None :: None :: None :: Some(sheet) :: None :: HNil) + case BattleDiagram(6, Some(sheet)) => + Attempt.successful(None :: None :: None :: Some(sheet.asInstanceOf[SheetSix]) :: None :: HNil) - case BattleDiagram(None, None, None, None, Some(sheet)) => - Attempt.successful(None :: None :: None :: None :: Some(sheet) :: HNil) + case BattleDiagram(7, Some(sheet)) => + Attempt.successful(None :: None :: None :: None :: Some(sheet.asInstanceOf[SheetSeven]) :: HNil) - case BattleDiagram(None, None, None, None, None) => - Attempt.failure(Err("can not deal with blank sheet")) + case BattleDiagram(_, None) => + Attempt.successful(None :: None :: None :: None :: None :: HNil) + + case BattleDiagram(n, _) => + Attempt.failure(Err(s"unhandled sheet number $n")) } ) + /** + * Parse what was originally an encoded `List` of elements as a linked list of elements. + * Maintain a `String` padding value that applies an appropriate offset value. + * @param remaining the number of elements remaining to parse + * @param pad the current padding for any `String` entry stored within the parsed elements; + * different elements add different padding offset to this field on subsequent passes + * @return a `Codec` for `BattleDiagramLayer` objects + */ private def parse_diagrams_codec(remaining : Int, pad : Int = 0) : Codec[BattleDiagramLayer] = ( uint4L >>:~ { plan => ("diagram" | diagram_codec(plan, pad)) :: @@ -140,43 +277,44 @@ object BattleplanMessage extends Marshallable[BattleplanMessage] { Attempt.successful(BattleDiagramLayer(diagram, next)) }, { - case BattleDiagramLayer(BattleDiagram(Some(sheet), _, _, _, _), next) => - Attempt.successful(1 :: BattleDiagram(Some(sheet)) :: next :: HNil) - - case BattleDiagramLayer(BattleDiagram(None, Some(sheet), _, _, _), next) => - Attempt.successful(2 :: BattleDiagram(None, Some(sheet)) :: next :: HNil) - - case BattleDiagramLayer(BattleDiagram(None, None, Some(sheet), _, _), next) => - Attempt.successful(5 :: BattleDiagram(None, None, Some(sheet)) :: next :: HNil) - - case BattleDiagramLayer(BattleDiagram(None, None, None, Some(sheet), _), next) => - Attempt.successful(6 :: BattleDiagram(None, None, None, Some(sheet)) :: next :: HNil) - - case BattleDiagramLayer(BattleDiagram(None, None, None, None, Some(sheet)), next) => - Attempt.successful(7 :: BattleDiagram(None, None, None, None, Some(sheet)) :: next :: HNil) + case BattleDiagramLayer(BattleDiagram(num, sheet), next) => + Attempt.successful(num :: BattleDiagram(num, sheet) :: next :: HNil) } ) import scala.collection.mutable.ListBuffer + /** + * Transform a linked list of `BattleDiagramLayer` into a `List` of `BattleDiagram` objects. + * @param element the current link in a chain of `BattleDiagramLayer` objects + * @param list a `List` of extracted `BattleDiagrams`; + * technically, the output + */ private def rollDiagramLayers(element : BattleDiagramLayer, list : ListBuffer[BattleDiagram]) : Unit = { list += element.diagram if(element.next.isDefined) - rollDiagramLayers(element.next.get, list) + rollDiagramLayers(element.next.get, list) //tail call optimization } + /** + * Transform a `List` of `BattleDiagram` objects into a linked list of `BattleDiagramLayer` objects. + * @param revIter a reverse `List` `Iterator` for a `List` of `BattleDiagrams` + * @param layers the current head of a chain of `BattleDiagramLayer` objects; + * technically, the output + * @return a linked list of `BattleDiagramLayer` objects + */ private def unrollDiagramLayers(revIter : Iterator[BattleDiagram], layers : Option[BattleDiagramLayer] = None) : Option[BattleDiagramLayer] = { if(!revIter.hasNext) return layers val elem : BattleDiagram = revIter.next - unrollDiagramLayers(revIter, Some(BattleDiagramLayer(elem, layers))) + unrollDiagramLayers(revIter, Some(BattleDiagramLayer(elem, layers))) //tail call optimization } implicit val codec : Codec[BattleplanMessage] = ( ("unk1" | uint32L) :: - ("unk2" | PacketHelpers.encodedWideString) :: - ("unk3" | uint16L) :: + ("mastermind" | PacketHelpers.encodedWideString) :: + ("unk2" | uint16L) :: (uint8L >>:~ { count => - conditional(count > 0, parse_diagrams_codec(count)).hlist + conditional(count > 0, "diagrams" | parse_diagrams_codec(count)).hlist }) ).exmap[BattleplanMessage] ( { diff --git a/common/src/test/scala/game/BattleplanMessageTest.scala b/common/src/test/scala/game/BattleplanMessageTest.scala new file mode 100644 index 00000000..2eb171bd --- /dev/null +++ b/common/src/test/scala/game/BattleplanMessageTest.scala @@ -0,0 +1,39 @@ +// Copyright (c) 2017 PSForever +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import scodec.bits._ + +class BattleplanMessageTest extends Specification { + val string = hex"b3 3a197902 94 59006500740041006e006f0074006800650072004600610069006c0075007200650041006c007400 0000 01 e0" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case BattleplanMessage(unk1, mastermind, unk2, diagrams) => + unk1 mustEqual 41490746 + mastermind mustEqual "YetAnotherFailureAlt" + unk2 mustEqual 0 + diagrams.size mustEqual 1 + //h0 + diagrams.head.pageNum mustEqual 14 + diagrams.head.sheet.isDefined mustEqual false + case _ => + ko + } + } + + "encode" in { + val msg = BattleplanMessage( + 41490746, + "YetAnotherFailureAlt", + 0, + BattleDiagram(14) :: + Nil + ) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } +} From 331406a84945135215ba5239ae282d5a78ddebc3 Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 28 Apr 2017 09:11:15 -0400 Subject: [PATCH 3/5] renamed classes in BattleplanMessage to more appropriate names; added an Enumeration class for action numbers; queued up a bunch of test data for later --- .../packet/game/BattleplanMessage.scala | 286 ++++++++++-------- .../scala/game/BattleplanMessageTest.scala | 31 +- .../src/main/scala/WorldSessionActor.scala | 5 +- 3 files changed, 185 insertions(+), 137 deletions(-) diff --git a/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala b/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala index 277eb103..fbcbc4da 100644 --- a/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala @@ -8,25 +8,53 @@ import scodec.codecs._ import shapeless.{::, HNil} /** - * A common ancestor of all the different "sheets" used to keep track of the data. + * A `Codec` for the actions that each layer of the diagram performs. + * `Action1`, `Action2`, `Action5`, `Action6`, and `Action7` have additional `DiagramStroke` input data. */ -sealed trait DiagramSheet +object DiagramActionCode extends Enumeration { + type Type = Value + + val Action0, + Action1, + Action2, + Action3, + Action4, + Action5, + Action6, + Action7, + Action8, + Action9, + ActionA, + ActionB, + ActionC, + ActionD, + ActionE, + ActionF + = Value //TODO replace these with descriptive wording + + implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L) +} + +/** + * A common ancestor of all the different "strokes" used to keep track of the data. + */ +sealed trait DiagramStroke /** * na * @param unk1 na * @param unk2 na */ -final case class SheetOne(unk1 : Float, - unk2 : Int) extends DiagramSheet +final case class StrokeOne(unk1 : Float, + unk2 : Int) extends DiagramStroke /** * na * @param unk1 na * @param unk2 na */ -final case class SheetTwo(unk1 : Float, - unk2 : Float) extends DiagramSheet +final case class StrokeTwo(unk1 : Float, + unk2 : Float) extends DiagramStroke /** * na @@ -35,10 +63,10 @@ final case class SheetTwo(unk1 : Float, * @param unk3 na * @param unk4 na */ -final case class SheetFive(unk1 : Float, - unk2 : Float, - unk3 : Float, - unk4 : Int) extends DiagramSheet +final case class StrokeFive(unk1 : Float, + unk2 : Float, + unk3 : Float, + unk4 : Int) extends DiagramStroke /** * na @@ -48,37 +76,39 @@ final case class SheetFive(unk1 : Float, * @param unk4 na * @param unk5 na */ -final case class SheetSix(unk1 : Float, - unk2 : Float, - unk3 : Int, - unk4 : Int, - unk5 : String) extends DiagramSheet +final case class StrokeSix(unk1 : Float, + unk2 : Float, + unk3 : Int, + unk4 : Int, + unk5 : String) extends DiagramStroke /** * na * @param unk na */ -final case class SheetSeven(unk : Int) extends DiagramSheet +final case class StrokeSeven(unk : Int) extends DiagramStroke /** * na - * @param pageNum a hint to kind of data stored - * @param sheet the data + * @param action the behavior of this stroke; + * a hint to kind of stroke data stored, if at all, and how to use it or incorporate prior data + * @param stroke the data */ -final case class BattleDiagram(pageNum : Int, - sheet : Option[DiagramSheet] = None) +final case class BattleDiagramAction(action : DiagramActionCode.Value, + stroke : Option[DiagramStroke] = None) /** * na - * @param unk1 na - * @param mastermind the player who contributed this battle plan - * @param unk2 na - * @param diagrams a list of the individual `BattleDiagram`s that compose this plan + * @param char_id na; + * same as in `CharacterInfoMessage` + * @param player_name the player who contributed this battle plan + * @param zone_id on which continent the battle plan will be overlaid + * @param diagrams a list of the individual actions that compose this plan */ -final case class BattleplanMessage(unk1 : Long, - mastermind : String, - unk2 : Int, - diagrams : List[BattleDiagram]) +final case class BattleplanMessage(char_id : Long, + player_name : String, + zone_id : Int, + diagrams : List[BattleDiagramAction]) extends PlanetSideGamePacket { type Packet = BattleplanMessage def opcode = GamePacketOpcode.BattleplanMessage @@ -87,170 +117,170 @@ final case class BattleplanMessage(unk1 : Long, object BattelplanDiagram { /** - * Create a `BattleDiagram` object containing `SheetOne` data. + * Create a `BattleDiagramAction` object containing `StrokeOne` data. * @param unk1 na * @param unk2 na - * @return a `BattleDiagram` object + * @return a `BattleDiagramAction` object */ - def sheet1(unk1 : Float, unk2 : Int) : BattleDiagram = - BattleDiagram(1, Some(SheetOne(unk1, unk2))) + def stroke1(unk1 : Float, unk2 : Int) : BattleDiagramAction = + BattleDiagramAction(DiagramActionCode.Action1, Some(StrokeOne(unk1, unk2))) /** - * Create a `BattleDiagram` object containing `SheetTwo` data. + * Create a `BattleDiagramAction` object containing `StrokeTwo` data. * @param unk1 na * @param unk2 na - * @return a `BattleDiagram` object + * @return a `BattleDiagramAction` object */ - def sheet2(unk1 : Float, unk2 : Float) : BattleDiagram = - BattleDiagram(2, Some(SheetTwo(unk1, unk2))) + def stroke2(unk1 : Float, unk2 : Float) : BattleDiagramAction = + BattleDiagramAction(DiagramActionCode.Action2, Some(StrokeTwo(unk1, unk2))) /** - * Create a `BattleDiagram` object containing `SheetFive` data. + * Create a `BattleDiagramAction` object containing `StrokeFive` data. * @param unk1 na * @param unk2 na * @param unk3 na * @param unk4 na - * @return a `BattleDiagram` object + * @return a `BattleDiagramAction` object */ - def sheet5(unk1 : Float, unk2 : Float, unk3 : Float, unk4 : Int) : BattleDiagram = - BattleDiagram(5, Some(SheetFive(unk1, unk2, unk3, unk4))) + def stroke5(unk1 : Float, unk2 : Float, unk3 : Float, unk4 : Int) : BattleDiagramAction = + BattleDiagramAction(DiagramActionCode.Action5, Some(StrokeFive(unk1, unk2, unk3, unk4))) /** - * Create a `BattleDiagram` object containing `SheetSix` data. + * Create a `BattleDiagramAction` object containing `StrokeSix` data. * @param unk1 na * @param unk2 na * @param unk3 na * @param unk4 na * @param unk5 na - * @return a `BattleDiagram` object + * @return a `BattleDiagramAction` object */ - def sheet6(unk1 : Float, unk2 : Float, unk3 : Int, unk4 : Int, unk5 : String) : BattleDiagram = - BattleDiagram(6, Some(SheetSix(unk1, unk2, unk3, unk4, unk5))) + def stroke6(unk1 : Float, unk2 : Float, unk3 : Int, unk4 : Int, unk5 : String) : BattleDiagramAction = + BattleDiagramAction(DiagramActionCode.Action6, Some(StrokeSix(unk1, unk2, unk3, unk4, unk5))) /** - * Create a `BattleDiagram` object containing `SheetSeven` data. + * Create a `BattleDiagramAction` object containing `StrokeSeven` data. * @param unk na - * @return a `BattleDiagram` object + * @return a `BattleDiagramAction` object */ - def sheet7(unk : Int) : BattleDiagram = - BattleDiagram(7, Some(SheetSeven(unk))) + def stroke7(unk : Int) : BattleDiagramAction = + BattleDiagramAction(DiagramActionCode.Action7, Some(StrokeSeven(unk))) } object BattleplanMessage extends Marshallable[BattleplanMessage] { /** - * An intermediary object intended to temporarily store `BattleDiagram` objects.
+ * An intermediary object intended to temporarily store `BattleDiagramAction` objects.
*
* This hidden object is arranged like a linked list; - * but, later, it is converted into an accessible formal `List` of `BattleDiagram` objects during decoding; + * but, later, it is converted into an accessible formal `List` during decoding; * likewise, during the encoding process, the `List` is transformed back into a linked list structure. * `Scala`'s own linked list `Collection` is deprecated, without substitution, so this custom one must be used. - * @param diagram the contained `BattleDiagram` with the sheet that maintains the data - * @param next the next `BattleDiagramLayer`, if any, arranging into a linked list + * @param diagram the contained object that maintains the data + * @param next the next `BattleDiagramChain`, if any */ - private final case class BattleDiagramLayer(diagram : BattleDiagram, - next : Option[BattleDiagramLayer]) + private final case class BattleDiagramChain(diagram : BattleDiagramAction, + next : Option[BattleDiagramChain]) /** - * Parse data into a `SheetOne` object. + * Parse data into a `StrokeOne` object. */ - private val plan1_codec : Codec[SheetOne] = ( //size: 8; pad: +0 + private val plan1_codec : Codec[StrokeOne] = ( //size: 8; pad: +0 ("unk1" | newcodecs.q_float(0.0, 16.0, 5)) :: ("unk2" | uintL(3)) - ).as[SheetOne] + ).as[StrokeOne] /** - * Parse data into a `SheetTwo` object. + * Parse data into a `StrokeTwo` object. */ - private val plan2_codec : Codec[SheetTwo] = ( //size: 22; pad: +2 + private val plan2_codec : Codec[StrokeTwo] = ( //size: 22; pad: +2 ("unk1" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: ("unk2" | newcodecs.q_float(-4096.0, 12288.0, 11)) - ).as[SheetTwo] + ).as[StrokeTwo] /** - * Parse data into a `SheetFive` object. + * Parse data into a `StrokeFive` object. */ - private val plan5_codec : Codec[SheetFive] = ( //size: 44; pad: +4 + private val plan5_codec : Codec[StrokeFive] = ( //size: 44; pad: +4 ("unk1" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: ("unk2" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: ("unk3" | newcodecs.q_float(1024.0, 0.0, 11)) :: ("unk4" | uintL(11)) - ).as[SheetFive] + ).as[StrokeFive] /** - * Parse data into a `SheetSix` object. + * Parse data into a `StrokeSix` object. * @param pad the current padding for the `String` entry */ - private def plan6_codec(pad : Int) : Codec[SheetSix] = ( //size: 31 + string.length.field + string.length * 16 + padding; pad: value resets + private def plan6_codec(pad : Int) : Codec[StrokeSix] = ( //size: 31 + string.length.field + string.length * 16 + padding; pad: value resets ("unk1" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: ("unk2" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: ("unk3" | uintL(3)) :: ("unk4" | uintL(6)) :: ("unk5" | PacketHelpers.encodedWideStringAligned( (pad + 1) % 8 )) - ).as[SheetSix] + ).as[StrokeSix] /** - * Parse data into a `SheetSeven` object. + * Parse data into a `StrokeSeven` object. */ - private val plan7_codec : Codec[SheetSeven] = ("unk" | uintL(6)).as[SheetSeven] // size: 6; pad: +2 + private val plan7_codec : Codec[StrokeSeven] = ("unk" | uintL(6)).as[StrokeSeven] // size: 6; pad: +2 /** - * Switch between different patterns to create a `BattleDiagram` for the following data. + * Switch between different patterns to create a `BattleDiagramAction` for the following data. * @param plan a hint to help parse the following data * @param pad the current padding for any `String` entry stored within the parsed elements; * when `plan == 6`, `plan6_codec` utilizes this value - * @return a `BattleDiagram` object + * @return a `BattleDiagramAction` object */ - private def diagram_codec(plan : Int, pad : Int) : Codec[BattleDiagram] = ( - conditional(plan == 1, plan1_codec) :: - conditional(plan == 2, plan2_codec) :: - conditional(plan == 5, plan5_codec) :: - conditional(plan == 6, plan6_codec(pad)) :: - conditional(plan == 7, plan7_codec) - ).exmap[BattleDiagram] ( + private def diagram_codec(plan : DiagramActionCode.Value, pad : Int) : Codec[BattleDiagramAction] = ( + conditional(plan == DiagramActionCode.Action1, plan1_codec) :: + conditional(plan == DiagramActionCode.Action2, plan2_codec) :: + conditional(plan == DiagramActionCode.Action5, plan5_codec) :: + conditional(plan == DiagramActionCode.Action6, plan6_codec(pad)) :: + conditional(plan == DiagramActionCode.Action7, plan7_codec) + ).exmap[BattleDiagramAction] ( { - case Some(sheet) :: None :: None :: None :: None :: HNil => - Attempt.successful(BattleDiagram(plan, Some(sheet))) + case Some(stroke) :: None :: None :: None :: None :: HNil => + Attempt.successful(BattleDiagramAction(plan, Some(stroke))) - case None :: Some(sheet) :: None :: None :: None :: HNil => - Attempt.successful(BattleDiagram(plan, Some(sheet))) + case None :: Some(stroke) :: None :: None :: None :: HNil => + Attempt.successful(BattleDiagramAction(plan, Some(stroke))) - case None :: None :: Some(sheet) :: None :: None :: HNil => - Attempt.successful(BattleDiagram(plan, Some(sheet))) + case None :: None :: Some(stroke) :: None :: None :: HNil => + Attempt.successful(BattleDiagramAction(plan, Some(stroke))) - case None :: None :: None :: Some(sheet) :: None :: HNil => - Attempt.successful(BattleDiagram(plan, Some(sheet))) + case None :: None :: None :: Some(stroke) :: None :: HNil => + Attempt.successful(BattleDiagramAction(plan, Some(stroke))) - case None :: None :: None :: None :: Some(sheet) :: HNil => - Attempt.successful(BattleDiagram(plan, Some(sheet))) + case None :: None :: None :: None :: Some(stroke) :: HNil => + Attempt.successful(BattleDiagramAction(plan, Some(stroke))) case None :: None :: None :: None :: None :: HNil => - Attempt.successful(BattleDiagram(plan, None)) + Attempt.successful(BattleDiagramAction(plan, None)) case _:: _ :: _ :: _ :: _ :: HNil => - Attempt.failure(Err(s"too many sheets at once for $plan")) + Attempt.failure(Err(s"too many strokes for action $plan")) }, { - case BattleDiagram(1, Some(sheet)) => - Attempt.successful(Some(sheet.asInstanceOf[SheetOne]) :: None :: None :: None :: None :: HNil) + case BattleDiagramAction(DiagramActionCode.Action1, Some(stroke)) => + Attempt.successful(Some(stroke.asInstanceOf[StrokeOne]) :: None :: None :: None :: None :: HNil) - case BattleDiagram(2, Some(sheet)) => - Attempt.successful(None :: Some(sheet.asInstanceOf[SheetTwo]) :: None :: None :: None :: HNil) + case BattleDiagramAction(DiagramActionCode.Action2, Some(stroke)) => + Attempt.successful(None :: Some(stroke.asInstanceOf[StrokeTwo]) :: None :: None :: None :: HNil) - case BattleDiagram(5, Some(sheet)) => - Attempt.successful(None :: None :: Some(sheet.asInstanceOf[SheetFive]) :: None :: None :: HNil) + case BattleDiagramAction(DiagramActionCode.Action5, Some(stroke)) => + Attempt.successful(None :: None :: Some(stroke.asInstanceOf[StrokeFive]) :: None :: None :: HNil) - case BattleDiagram(6, Some(sheet)) => - Attempt.successful(None :: None :: None :: Some(sheet.asInstanceOf[SheetSix]) :: None :: HNil) + case BattleDiagramAction(DiagramActionCode.Action6, Some(stroke)) => + Attempt.successful(None :: None :: None :: Some(stroke.asInstanceOf[StrokeSix]) :: None :: HNil) - case BattleDiagram(7, Some(sheet)) => - Attempt.successful(None :: None :: None :: None :: Some(sheet.asInstanceOf[SheetSeven]) :: HNil) + case BattleDiagramAction(DiagramActionCode.Action7, Some(stroke)) => + Attempt.successful(None :: None :: None :: None :: Some(stroke.asInstanceOf[StrokeSeven]) :: HNil) - case BattleDiagram(_, None) => + case BattleDiagramAction(_, None) => Attempt.successful(None :: None :: None :: None :: None :: HNil) - case BattleDiagram(n, _) => - Attempt.failure(Err(s"unhandled sheet number $n")) + case BattleDiagramAction(n, _) => + Attempt.failure(Err(s"unhandled stroke action number $n")) } ) @@ -260,74 +290,74 @@ object BattleplanMessage extends Marshallable[BattleplanMessage] { * @param remaining the number of elements remaining to parse * @param pad the current padding for any `String` entry stored within the parsed elements; * different elements add different padding offset to this field on subsequent passes - * @return a `Codec` for `BattleDiagramLayer` objects + * @return a `Codec` for `BattleDiagramChain` segments */ - private def parse_diagrams_codec(remaining : Int, pad : Int = 0) : Codec[BattleDiagramLayer] = ( - uint4L >>:~ { plan => + private def parse_diagrams_codec(remaining : Int, pad : Int = 0) : Codec[BattleDiagramChain] = ( + DiagramActionCode.codec >>:~ { plan => ("diagram" | diagram_codec(plan, pad)) :: conditional(remaining > 1, "next" | parse_diagrams_codec( remaining - 1, - pad + (if(plan == 2 || plan == 7) { 2 } else if(plan == 5) { 4 } else if(plan == 6) { -pad } else { 0 }) + pad + (if(plan == DiagramActionCode.Action2 || plan == DiagramActionCode.Action7) { 2 } else if(plan == DiagramActionCode.Action5) { 4 } else if(plan == DiagramActionCode.Action6) { -pad } else { 0 }) ) ) - }).exmap[BattleDiagramLayer] ( + }).exmap[BattleDiagramChain] ( { case _ :: diagram :: next :: HNil => - Attempt.successful(BattleDiagramLayer(diagram, next)) + Attempt.successful(BattleDiagramChain(diagram, next)) }, { - case BattleDiagramLayer(BattleDiagram(num, sheet), next) => - Attempt.successful(num :: BattleDiagram(num, sheet) :: next :: HNil) + case BattleDiagramChain(BattleDiagramAction(num, stroke), next) => + Attempt.successful(num :: BattleDiagramAction(num, stroke) :: next :: HNil) } ) import scala.collection.mutable.ListBuffer /** - * Transform a linked list of `BattleDiagramLayer` into a `List` of `BattleDiagram` objects. - * @param element the current link in a chain of `BattleDiagramLayer` objects + * Transform a linked list of `BattleDiagramChain` into a `List` of `BattleDiagramAction` objects. + * @param element the current link in a chain of `BattleDiagramChain` objects * @param list a `List` of extracted `BattleDiagrams`; * technically, the output */ - private def rollDiagramLayers(element : BattleDiagramLayer, list : ListBuffer[BattleDiagram]) : Unit = { + private def rollDiagramLayers(element : BattleDiagramChain, list : ListBuffer[BattleDiagramAction]) : Unit = { list += element.diagram if(element.next.isDefined) rollDiagramLayers(element.next.get, list) //tail call optimization } /** - * Transform a `List` of `BattleDiagram` objects into a linked list of `BattleDiagramLayer` objects. + * Transform a `List` of `BattleDiagramAction` objects into a linked list of `BattleDiagramChain` objects. * @param revIter a reverse `List` `Iterator` for a `List` of `BattleDiagrams` - * @param layers the current head of a chain of `BattleDiagramLayer` objects; + * @param layers the current head of a chain of `BattleDiagramChain` objects; * technically, the output - * @return a linked list of `BattleDiagramLayer` objects + * @return a linked list of `BattleDiagramChain` objects */ - private def unrollDiagramLayers(revIter : Iterator[BattleDiagram], layers : Option[BattleDiagramLayer] = None) : Option[BattleDiagramLayer] = { + private def unrollDiagramLayers(revIter : Iterator[BattleDiagramAction], layers : Option[BattleDiagramChain] = None) : Option[BattleDiagramChain] = { if(!revIter.hasNext) return layers - val elem : BattleDiagram = revIter.next - unrollDiagramLayers(revIter, Some(BattleDiagramLayer(elem, layers))) //tail call optimization + val elem : BattleDiagramAction = revIter.next + unrollDiagramLayers(revIter, Some(BattleDiagramChain(elem, layers))) //tail call optimization } implicit val codec : Codec[BattleplanMessage] = ( - ("unk1" | uint32L) :: - ("mastermind" | PacketHelpers.encodedWideString) :: - ("unk2" | uint16L) :: + ("char_id" | uint32L) :: + ("player_name" | PacketHelpers.encodedWideString) :: + ("zone_id" | uint16L) :: (uint8L >>:~ { count => conditional(count > 0, "diagrams" | parse_diagrams_codec(count)).hlist }) ).exmap[BattleplanMessage] ( { - case unk1 :: unk2 :: unk3 :: _ :: diagramLayers :: HNil => - val list : ListBuffer[BattleDiagram] = new ListBuffer() + case char_id :: player :: zone_id :: _ :: diagramLayers :: HNil => + val list : ListBuffer[BattleDiagramAction] = new ListBuffer() if(diagramLayers.isDefined) rollDiagramLayers(diagramLayers.get, list) - Attempt.successful(BattleplanMessage(unk1, unk2, unk3, list.toList)) + Attempt.successful(BattleplanMessage(char_id, player, zone_id, list.toList)) }, { - case BattleplanMessage(unk1, unk2, unk3, diagrams) => - val layersOpt = unrollDiagramLayers(diagrams.reverseIterator) - Attempt.successful(unk1 :: unk2 :: unk3 :: diagrams.size :: layersOpt :: HNil) + case BattleplanMessage(char_id, player_name, zone_id, diagrams) => + val layersOpt : Option[BattleDiagramChain] = unrollDiagramLayers(diagrams.reverseIterator) + Attempt.successful(char_id :: player_name :: zone_id :: diagrams.size :: layersOpt :: HNil) } ) } diff --git a/common/src/test/scala/game/BattleplanMessageTest.scala b/common/src/test/scala/game/BattleplanMessageTest.scala index 2eb171bd..860ee0dd 100644 --- a/common/src/test/scala/game/BattleplanMessageTest.scala +++ b/common/src/test/scala/game/BattleplanMessageTest.scala @@ -8,17 +8,34 @@ import scodec.bits._ class BattleplanMessageTest extends Specification { val string = hex"b3 3a197902 94 59006500740041006e006f0074006800650072004600610069006c0075007200650041006c007400 0000 01 e0" + //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a00202aba2b4aae8bd2aba334aae8dd2aca3b4ab28fd2aca414ab29152aca474ab292d2ada4d4ab69452ada534ab695d2ada594ab696d2ada5d4ab697d2ada614ab698d2ada654ab699d2ada694ab69ad2aea6d4aba9bd2aea714aba9cd2aea754aba9dd2aea794aba9ed + //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a002025ca4d497292525ca47497291525ca4149728f525ca3b49728dd25ca3549728cd25ca3149728bd25ca2d49728ad25ca29497289d25ca25497288d25ca21497287d25ca1d497286d25ca19497285d25ca15497284d25da11497683d25da0d497682d25da09497681d + //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a002025da05497680d25da014976ffc25dbfd0976fec25dbf90976fdc25dbf50976fcc25dbf10976fbc25dbed0976fac25dbe90976f9425dbe30972f8425cbdd0972f6425cbd70972f4c25bbcf096ef2c25bbc9096ef1c25bbc5096ef0425bbbf096eef425bbbb096eee4 + //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a002027aa4349ea90527aa3f49ea8f527aa3b49ea8e527ba3749ee8d527ba3349ee8bd27ba2d49ee8ad27ca2949f289d27ca2549f288d27ca2149f287d27da1d49f686d27da1749fa85527ea1349fa84527ea0f49fe83527fa0b49fe82527fa0749fe81527fa0349fe805 + //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a00202a8bbb0aa6eec2a9bbd0aa6efc2a9bc10aa6f0c2a9bc50aa6f1c2a9bc90aa6f2c2a9bcf0aa6f442a9bd30aa6f542a9bd70aa6f642a9bdb0aa6f742a9bdf0aa6f842a9be30aa6f942a9be70aa6fa42a9beb0aa6fb42a9bef0aa6fcc2a9bf50aa6fdc2a9bf90aa6ff4 + //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a00201b127ba8949eea1d27ba8549eea0527aa7f49ea9f527aa7b49ea9e527aa7749ea9d527aa7349ea9c527aa6f49ea9ad27aa6949ea99d27aa6349ea98527aa5f49ea97527aa5b49ea96527aa5549ea94d27aa5149ea93d27aa4d49ea92d27aa4949ea91d27aa4540 + //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a001e27fbff09feff427fbfb09fafe427ebf709fafd427ebf309f6fcc27dbf109f6fbc27dbed09f2fac27cbe909f2f9c27cbe509eef8c27bbe109eef7c27abdd09eaf6c27abd909eaf5c27abd509e6f54279bd309e6f44279bcf09e6f34279bcb09e6f240 + //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a0020891b1260a8b4982a25260a874982a15260a834982a0525fa7f497e9f525fa7b497e9e525ea77497a9d525ea73497a9c525ea6f497a9b525ea6b497a9a525da67497699525da63497698525da5f497697525da5b497696525ca57497295525ca534 + //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a001a25abad096aeac25aba9096ae9c25aba5096ae8c25aba1096ae7c25ab9d096ae6c25ab99096ae5c25ab95096ae4c25ab91096ae3c25ab8d096ae2c25ab89096ae1c25ab85096ae0c25ab81096adfc25bb7d096edec0 + //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a0018241c8149071fd241c7d49071ed241c7949071dd241c7549031cd240c7149031bd240c6d49031ad240c69490319d240c65490318d240c61490317d240c5d490316d240c59490315d240c55490314d + //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a000a2a9bff0aaa8052aaa074aaa82d2aaa0f4aaa85d2aaa1b4aaa87d2aaa234aae89d0 + //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a000a279bc709e6f14279bc309e6f04279bbf09e6ef4279bbb09e2eec278bb90c + //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a000829ebb90a7eee42a0bb90a86eec2a2bbb0a96eec2a6bbb0a9eeec + //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a00072aea7d4aba9fd2aea814abaa0d2aea854abaa1d2aea894 + //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a000625bbb7096eed425bbb3096eec425bbaf096aebc0 + //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a000225ca51497293d0 + //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a000130 "decode" in { PacketCoding.DecodePacket(string).require match { - case BattleplanMessage(unk1, mastermind, unk2, diagrams) => - unk1 mustEqual 41490746 - mastermind mustEqual "YetAnotherFailureAlt" - unk2 mustEqual 0 + case BattleplanMessage(char_id, player_name, zone_id, diagrams) => + char_id mustEqual 41490746 + player_name mustEqual "YetAnotherFailureAlt" + zone_id mustEqual 0 diagrams.size mustEqual 1 //h0 - diagrams.head.pageNum mustEqual 14 - diagrams.head.sheet.isDefined mustEqual false + diagrams.head.action mustEqual DiagramActionCode.ActionE + diagrams.head.stroke.isDefined mustEqual false case _ => ko } @@ -29,7 +46,7 @@ class BattleplanMessageTest extends Specification { 41490746, "YetAnotherFailureAlt", 0, - BattleDiagram(14) :: + BattleDiagramAction(DiagramActionCode.ActionE) :: Nil ) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 56b00e01..cf620c3d 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -179,7 +179,7 @@ class WorldSessionActor extends Actor with MDCContextAware { log.debug("Object: " + obj) // LoadMapMessage 13714 in mossy .gcap // XXX: hardcoded shit - sendResponse(PacketCoding.CreateGamePacket(0, LoadMapMessage("map13","home3",40100,25,true,3770441820L))) //VS Sanctuary + sendResponse(PacketCoding.CreateGamePacket(0, LoadMapMessage("map10","z10",40100,25,true,3770441820L))) //VS Sanctuary sendResponse(PacketCoding.CreateGamePacket(0, ZonePopulationUpdateMessage(PlanetSideGUID(13), 414, 138, 0, 138, 0, 138, 0, 138, 0))) sendResponse(PacketCoding.CreateGamePacket(0, objectHex)) @@ -217,7 +217,8 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, SetCurrentAvatarMessage(guid,0,0))) sendResponse(PacketCoding.CreateGamePacket(0, CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT))) sendResponse(PacketCoding.CreateGamePacket(0, ReplicationStreamMessage(5, Some(6), Vector(SquadListing(255))))) //clear squad list - +sendRawResponse(hex"b3 3a197902 94 59006500740041006e006f0074006800650072004600610069006c0075007200650041006c007400 0000 01 e0") +sendRawResponse(hex"b3 3a197902 8c 4f0075007400730074006100620075006c006f0075007300 0a00202aba2b4aae8bd2aba334aae8dd2aca3b4ab28fd2aca414ab29152aca474ab292d2ada4d4ab69452ada534ab695d2ada594ab696d2ada5d4ab697d2ada614ab698d2ada654ab699d2ada694ab69ad2aea6d4aba9bd2aea714aba9cd2aea754aba9dd2aea794aba9ed") import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient()) From ef1a8694496f66de316da9f2e0ee37580aa44fc8 Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 28 Apr 2017 23:03:50 -0400 Subject: [PATCH 4/5] added tests for detailed entry in battle plan; identified specifics of one of stroke types; added case to accept BattleplanMessages in WSA --- .../packet/game/BattleplanMessage.scala | 36 +-- .../scala/game/BattleplanMessageTest.scala | 213 ++++++++++++++++-- .../src/main/scala/WorldSessionActor.scala | 6 +- 3 files changed, 214 insertions(+), 41 deletions(-) diff --git a/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala b/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala index fbcbc4da..e4afebc2 100644 --- a/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala @@ -16,7 +16,7 @@ object DiagramActionCode extends Enumeration { val Action0, Action1, - Action2, + Vertex, Action3, Action4, Action5, @@ -49,12 +49,12 @@ final case class StrokeOne(unk1 : Float, unk2 : Int) extends DiagramStroke /** - * na - * @param unk1 na - * @param unk2 na + * Mark coordinates on the tactical map. + * @param x the x-coordinate of this point + * @param y the y-coordinate of this point */ -final case class StrokeTwo(unk1 : Float, - unk2 : Float) extends DiagramStroke +final case class StrokeTwo(x : Float, + y : Float) extends DiagramStroke /** * na @@ -91,7 +91,7 @@ final case class StrokeSeven(unk : Int) extends DiagramStroke /** * na * @param action the behavior of this stroke; - * a hint to kind of stroke data stored, if at all, and how to use it or incorporate prior data + * a hint to the kind of stroke data stored, if at all, and how to use it or incorporate prior data * @param stroke the data */ final case class BattleDiagramAction(action : DiagramActionCode.Value, @@ -107,7 +107,7 @@ final case class BattleDiagramAction(action : DiagramActionCode.Value, */ final case class BattleplanMessage(char_id : Long, player_name : String, - zone_id : Int, + zone_id : PlanetSideGUID, diagrams : List[BattleDiagramAction]) extends PlanetSideGamePacket { type Packet = BattleplanMessage @@ -115,7 +115,7 @@ final case class BattleplanMessage(char_id : Long, def encode = BattleplanMessage.encode(this) } -object BattelplanDiagram { +object BattleDiagramAction { /** * Create a `BattleDiagramAction` object containing `StrokeOne` data. * @param unk1 na @@ -126,13 +126,13 @@ object BattelplanDiagram { BattleDiagramAction(DiagramActionCode.Action1, Some(StrokeOne(unk1, unk2))) /** - * Create a `BattleDiagramAction` object containing `StrokeTwo` data. - * @param unk1 na - * @param unk2 na + * Create a `BattleDiagramAction` object containing `StrokeTwo` vertex data. + * @param x the x-coordinate of this point + * @param y the y-coordinate of this point * @return a `BattleDiagramAction` object */ - def stroke2(unk1 : Float, unk2 : Float) : BattleDiagramAction = - BattleDiagramAction(DiagramActionCode.Action2, Some(StrokeTwo(unk1, unk2))) + def vertex(x : Float, y : Float) : BattleDiagramAction = + BattleDiagramAction(DiagramActionCode.Vertex, Some(StrokeTwo(x, y))) /** * Create a `BattleDiagramAction` object containing `StrokeFive` data. @@ -233,7 +233,7 @@ object BattleplanMessage extends Marshallable[BattleplanMessage] { */ private def diagram_codec(plan : DiagramActionCode.Value, pad : Int) : Codec[BattleDiagramAction] = ( conditional(plan == DiagramActionCode.Action1, plan1_codec) :: - conditional(plan == DiagramActionCode.Action2, plan2_codec) :: + conditional(plan == DiagramActionCode.Vertex, plan2_codec) :: conditional(plan == DiagramActionCode.Action5, plan5_codec) :: conditional(plan == DiagramActionCode.Action6, plan6_codec(pad)) :: conditional(plan == DiagramActionCode.Action7, plan7_codec) @@ -264,7 +264,7 @@ object BattleplanMessage extends Marshallable[BattleplanMessage] { case BattleDiagramAction(DiagramActionCode.Action1, Some(stroke)) => Attempt.successful(Some(stroke.asInstanceOf[StrokeOne]) :: None :: None :: None :: None :: HNil) - case BattleDiagramAction(DiagramActionCode.Action2, Some(stroke)) => + case BattleDiagramAction(DiagramActionCode.Vertex, Some(stroke)) => Attempt.successful(None :: Some(stroke.asInstanceOf[StrokeTwo]) :: None :: None :: None :: HNil) case BattleDiagramAction(DiagramActionCode.Action5, Some(stroke)) => @@ -298,7 +298,7 @@ object BattleplanMessage extends Marshallable[BattleplanMessage] { conditional(remaining > 1, "next" | parse_diagrams_codec( remaining - 1, - pad + (if(plan == DiagramActionCode.Action2 || plan == DiagramActionCode.Action7) { 2 } else if(plan == DiagramActionCode.Action5) { 4 } else if(plan == DiagramActionCode.Action6) { -pad } else { 0 }) + pad + (if(plan == DiagramActionCode.Vertex || plan == DiagramActionCode.Action7) { 2 } else if(plan == DiagramActionCode.Action5) { 4 } else if(plan == DiagramActionCode.Action6) { -pad } else { 0 }) ) ) }).exmap[BattleDiagramChain] ( @@ -342,7 +342,7 @@ object BattleplanMessage extends Marshallable[BattleplanMessage] { implicit val codec : Codec[BattleplanMessage] = ( ("char_id" | uint32L) :: ("player_name" | PacketHelpers.encodedWideString) :: - ("zone_id" | uint16L) :: + ("zone_id" | PlanetSideGUID.codec) :: (uint8L >>:~ { count => conditional(count > 0, "diagrams" | parse_diagrams_codec(count)).hlist }) diff --git a/common/src/test/scala/game/BattleplanMessageTest.scala b/common/src/test/scala/game/BattleplanMessageTest.scala index 860ee0dd..0fbbaaf9 100644 --- a/common/src/test/scala/game/BattleplanMessageTest.scala +++ b/common/src/test/scala/game/BattleplanMessageTest.scala @@ -8,32 +8,17 @@ import scodec.bits._ class BattleplanMessageTest extends Specification { val string = hex"b3 3a197902 94 59006500740041006e006f0074006800650072004600610069006c0075007200650041006c007400 0000 01 e0" - //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a00202aba2b4aae8bd2aba334aae8dd2aca3b4ab28fd2aca414ab29152aca474ab292d2ada4d4ab69452ada534ab695d2ada594ab696d2ada5d4ab697d2ada614ab698d2ada654ab699d2ada694ab69ad2aea6d4aba9bd2aea714aba9cd2aea754aba9dd2aea794aba9ed - //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a002025ca4d497292525ca47497291525ca4149728f525ca3b49728dd25ca3549728cd25ca3149728bd25ca2d49728ad25ca29497289d25ca25497288d25ca21497287d25ca1d497286d25ca19497285d25ca15497284d25da11497683d25da0d497682d25da09497681d - //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a002025da05497680d25da014976ffc25dbfd0976fec25dbf90976fdc25dbf50976fcc25dbf10976fbc25dbed0976fac25dbe90976f9425dbe30972f8425cbdd0972f6425cbd70972f4c25bbcf096ef2c25bbc9096ef1c25bbc5096ef0425bbbf096eef425bbbb096eee4 - //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a002027aa4349ea90527aa3f49ea8f527aa3b49ea8e527ba3749ee8d527ba3349ee8bd27ba2d49ee8ad27ca2949f289d27ca2549f288d27ca2149f287d27da1d49f686d27da1749fa85527ea1349fa84527ea0f49fe83527fa0b49fe82527fa0749fe81527fa0349fe805 - //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a00202a8bbb0aa6eec2a9bbd0aa6efc2a9bc10aa6f0c2a9bc50aa6f1c2a9bc90aa6f2c2a9bcf0aa6f442a9bd30aa6f542a9bd70aa6f642a9bdb0aa6f742a9bdf0aa6f842a9be30aa6f942a9be70aa6fa42a9beb0aa6fb42a9bef0aa6fcc2a9bf50aa6fdc2a9bf90aa6ff4 - //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a00201b127ba8949eea1d27ba8549eea0527aa7f49ea9f527aa7b49ea9e527aa7749ea9d527aa7349ea9c527aa6f49ea9ad27aa6949ea99d27aa6349ea98527aa5f49ea97527aa5b49ea96527aa5549ea94d27aa5149ea93d27aa4d49ea92d27aa4949ea91d27aa4540 - //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a001e27fbff09feff427fbfb09fafe427ebf709fafd427ebf309f6fcc27dbf109f6fbc27dbed09f2fac27cbe909f2f9c27cbe509eef8c27bbe109eef7c27abdd09eaf6c27abd909eaf5c27abd509e6f54279bd309e6f44279bcf09e6f34279bcb09e6f240 - //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a0020891b1260a8b4982a25260a874982a15260a834982a0525fa7f497e9f525fa7b497e9e525ea77497a9d525ea73497a9c525ea6f497a9b525ea6b497a9a525da67497699525da63497698525da5f497697525da5b497696525ca57497295525ca534 - //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a001a25abad096aeac25aba9096ae9c25aba5096ae8c25aba1096ae7c25ab9d096ae6c25ab99096ae5c25ab95096ae4c25ab91096ae3c25ab8d096ae2c25ab89096ae1c25ab85096ae0c25ab81096adfc25bb7d096edec0 - //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a0018241c8149071fd241c7d49071ed241c7949071dd241c7549031cd240c7149031bd240c6d49031ad240c69490319d240c65490318d240c61490317d240c5d490316d240c59490315d240c55490314d - //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a000a2a9bff0aaa8052aaa074aaa82d2aaa0f4aaa85d2aaa1b4aaa87d2aaa234aae89d0 - //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a000a279bc709e6f14279bc309e6f04279bbf09e6ef4279bbb09e2eec278bb90c - //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a000829ebb90a7eee42a0bb90a86eec2a2bbb0a96eec2a6bbb0a9eeec - //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a00072aea7d4aba9fd2aea814abaa0d2aea854abaa1d2aea894 - //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a000625bbb7096eed425bbb3096eec425bbaf096aebc0 - //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a000225ca51497293d0 + val string_line = hex"b3 85647702 8c4f0075007400730074006100620075006c006f0075007300 0a00 20 2aba2b4aae8bd2aba334aae8dd2aca3b4ab28fd2aca414ab29152aca474ab292d2ada4d4ab69452ada534ab695d2ada594ab696d2ada5d4ab697d2ada614ab698d2ada654ab699d2ada694ab69ad2aea6d4aba9bd2aea714aba9cd2aea754aba9dd2aea794aba9ed" //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a000130 - "decode" in { + "decode (short)" in { PacketCoding.DecodePacket(string).require match { case BattleplanMessage(char_id, player_name, zone_id, diagrams) => char_id mustEqual 41490746 player_name mustEqual "YetAnotherFailureAlt" - zone_id mustEqual 0 + zone_id mustEqual PlanetSideGUID(0) diagrams.size mustEqual 1 - //h0 + //0 diagrams.head.action mustEqual DiagramActionCode.ActionE diagrams.head.stroke.isDefined mustEqual false case _ => @@ -41,11 +26,153 @@ class BattleplanMessageTest extends Specification { } } - "encode" in { + "decode (line)" in { + PacketCoding.DecodePacket(string_line).require match { + case BattleplanMessage(char_id, player_name, zone_id, diagrams) => + char_id mustEqual 41378949 + player_name mustEqual "Outstabulous" + zone_id mustEqual PlanetSideGUID(10) + diagrams.size mustEqual 32 + //0 + diagrams.head.action mustEqual DiagramActionCode.Vertex + diagrams.head.stroke.isDefined mustEqual true + diagrams.head.stroke.get.isInstanceOf[StrokeTwo] mustEqual true + diagrams.head.stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7512.0f + diagrams.head.stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6312.0f + //1 + diagrams(1).action mustEqual DiagramActionCode.Vertex + diagrams(1).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7512.0f + diagrams(1).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6328.0f + //2 + diagrams(2).action mustEqual DiagramActionCode.Vertex + diagrams(2).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7512.0f + diagrams(2).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6344.0f + //3 + diagrams(3).action mustEqual DiagramActionCode.Vertex + diagrams(3).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7512.0f + diagrams(3).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6360.0f + //4 + diagrams(4).action mustEqual DiagramActionCode.Vertex + diagrams(4).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7520.0f + diagrams(4).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6376.0f + //5 + diagrams(5).action mustEqual DiagramActionCode.Vertex + diagrams(5).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7520.0f + diagrams(5).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6392.0f + //6 + diagrams(6).action mustEqual DiagramActionCode.Vertex + diagrams(6).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7520.0f + diagrams(6).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6400.0f + //7 + diagrams(7).action mustEqual DiagramActionCode.Vertex + diagrams(7).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7520.0f + diagrams(7).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6416.0f + //8 + diagrams(8).action mustEqual DiagramActionCode.Vertex + diagrams(8).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7520.0f + diagrams(8).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6424.0f + //9 + diagrams(9).action mustEqual DiagramActionCode.Vertex + diagrams(9).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7520.0f + diagrams(9).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6440.0f + //10 + diagrams(10).action mustEqual DiagramActionCode.Vertex + diagrams(10).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f + diagrams(10).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6448.0f + //11 + diagrams(11).action mustEqual DiagramActionCode.Vertex + diagrams(11).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f + diagrams(11).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6464.0f + //12 + diagrams(12).action mustEqual DiagramActionCode.Vertex + diagrams(12).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f + diagrams(12).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6472.0f + //13 + diagrams(13).action mustEqual DiagramActionCode.Vertex + diagrams(13).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f + diagrams(13).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6488.0f + //14 + diagrams(14).action mustEqual DiagramActionCode.Vertex + diagrams(14).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f + diagrams(14).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6496.0f + //15 + diagrams(15).action mustEqual DiagramActionCode.Vertex + diagrams(15).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f + diagrams(15).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6504.0f + //16 + diagrams(16).action mustEqual DiagramActionCode.Vertex + diagrams(16).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f + diagrams(16).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6512.0f + //17 + diagrams(17).action mustEqual DiagramActionCode.Vertex + diagrams(17).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f + diagrams(17).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6520.0f + //18 + diagrams(18).action mustEqual DiagramActionCode.Vertex + diagrams(18).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f + diagrams(18).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6528.0f + //19 + diagrams(19).action mustEqual DiagramActionCode.Vertex + diagrams(19).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f + diagrams(19).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6536.0f + //20 + diagrams(20).action mustEqual DiagramActionCode.Vertex + diagrams(20).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f + diagrams(20).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6544.0f + //21 + diagrams(21).action mustEqual DiagramActionCode.Vertex + diagrams(21).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f + diagrams(21).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6552.0f + //22 + diagrams(22).action mustEqual DiagramActionCode.Vertex + diagrams(22).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f + diagrams(22).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6560.0f + //23 + diagrams(23).action mustEqual DiagramActionCode.Vertex + diagrams(23).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f + diagrams(23).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6568.0f + //24 + diagrams(24).action mustEqual DiagramActionCode.Vertex + diagrams(24).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7536.0f + diagrams(24).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6576.0f + //25 + diagrams(25).action mustEqual DiagramActionCode.Vertex + diagrams(25).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7536.0f + diagrams(25).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6584.0f + //26 + diagrams(26).action mustEqual DiagramActionCode.Vertex + diagrams(26).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7536.0f + diagrams(26).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6592.0f + //27 + diagrams(27).action mustEqual DiagramActionCode.Vertex + diagrams(27).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7536.0f + diagrams(27).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6600.0f + //28 + diagrams(28).action mustEqual DiagramActionCode.Vertex + diagrams(28).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7536.0f + diagrams(28).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6608.0f + //29 + diagrams(29).action mustEqual DiagramActionCode.Vertex + diagrams(29).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7536.0f + diagrams(29).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6616.0f + //30 + diagrams(30).action mustEqual DiagramActionCode.Vertex + diagrams(30).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7536.0f + diagrams(30).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6624.0f + //31 + diagrams(31).action mustEqual DiagramActionCode.Vertex + diagrams(31).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7536.0f + diagrams(31).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6632.0f + case _ => + ko + } + } + + "encode (short)" in { val msg = BattleplanMessage( 41490746, "YetAnotherFailureAlt", - 0, + PlanetSideGUID(0), BattleDiagramAction(DiagramActionCode.ActionE) :: Nil ) @@ -53,4 +180,48 @@ class BattleplanMessageTest extends Specification { pkt mustEqual string } + + "encode (line)" in { + val msg = BattleplanMessage( + 41378949, + "Outstabulous", + PlanetSideGUID(10), + BattleDiagramAction.vertex(7512.0f, 6312.0f) :: + BattleDiagramAction.vertex(7512.0f, 6328.0f) :: + BattleDiagramAction.vertex(7512.0f, 6344.0f) :: + BattleDiagramAction.vertex(7512.0f, 6360.0f) :: + BattleDiagramAction.vertex(7520.0f, 6376.0f) :: + BattleDiagramAction.vertex(7520.0f, 6392.0f) :: + BattleDiagramAction.vertex(7520.0f, 6400.0f) :: + BattleDiagramAction.vertex(7520.0f, 6416.0f) :: + BattleDiagramAction.vertex(7520.0f, 6424.0f) :: + BattleDiagramAction.vertex(7520.0f, 6440.0f) :: + BattleDiagramAction.vertex(7528.0f, 6448.0f) :: + BattleDiagramAction.vertex(7528.0f, 6464.0f) :: + BattleDiagramAction.vertex(7528.0f, 6472.0f) :: + BattleDiagramAction.vertex(7528.0f, 6488.0f) :: + BattleDiagramAction.vertex(7528.0f, 6496.0f) :: + BattleDiagramAction.vertex(7528.0f, 6504.0f) :: + BattleDiagramAction.vertex(7528.0f, 6512.0f) :: + BattleDiagramAction.vertex(7528.0f, 6520.0f) :: + BattleDiagramAction.vertex(7528.0f, 6528.0f) :: + BattleDiagramAction.vertex(7528.0f, 6536.0f) :: + BattleDiagramAction.vertex(7528.0f, 6544.0f) :: + BattleDiagramAction.vertex(7528.0f, 6552.0f) :: + BattleDiagramAction.vertex(7528.0f, 6560.0f) :: + BattleDiagramAction.vertex(7528.0f, 6568.0f) :: + BattleDiagramAction.vertex(7536.0f, 6576.0f) :: + BattleDiagramAction.vertex(7536.0f, 6584.0f) :: + BattleDiagramAction.vertex(7536.0f, 6592.0f) :: + BattleDiagramAction.vertex(7536.0f, 6600.0f) :: + BattleDiagramAction.vertex(7536.0f, 6608.0f) :: + BattleDiagramAction.vertex(7536.0f, 6616.0f) :: + BattleDiagramAction.vertex(7536.0f, 6624.0f) :: + BattleDiagramAction.vertex(7536.0f, 6632.0f) :: + Nil + ) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_line + } } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index cf620c3d..eed6df42 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -217,8 +217,7 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, SetCurrentAvatarMessage(guid,0,0))) sendResponse(PacketCoding.CreateGamePacket(0, CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT))) sendResponse(PacketCoding.CreateGamePacket(0, ReplicationStreamMessage(5, Some(6), Vector(SquadListing(255))))) //clear squad list -sendRawResponse(hex"b3 3a197902 94 59006500740041006e006f0074006800650072004600610069006c0075007200650041006c007400 0000 01 e0") -sendRawResponse(hex"b3 3a197902 8c 4f0075007400730074006100620075006c006f0075007300 0a00202aba2b4aae8bd2aba334aae8dd2aca3b4ab28fd2aca414ab29152aca474ab292d2ada4d4ab69452ada534ab695d2ada594ab696d2ada5d4ab697d2ada614ab698d2ada654ab699d2ada694ab69ad2aea6d4aba9bd2aea714aba9cd2aea754aba9dd2aea794aba9ed") + import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient()) @@ -403,6 +402,9 @@ sendRawResponse(hex"b3 3a197902 8c 4f0075007400730074006100620075006c006f0075007 log.info("PlanetsideAttributeMessage: "+msg) sendResponse(PacketCoding.CreateGamePacket(0,PlanetsideAttributeMessage(avatar_guid, attribute_type, attribute_value))) + case msg @ BattleplanMessage(char_id, player_name, zonr_id, diagrams) => + log.info("Battleplan: "+msg) + case default => log.error(s"Unhandled GamePacket ${pkt}") } From 0e81b21d118338577182838621bb6990cb2a1f92 Mon Sep 17 00:00:00 2001 From: FateJH Date: Sun, 30 Apr 2017 00:01:24 -0400 Subject: [PATCH 5/5] final changes to the Stroke classes for this iteration of BattleplanMessage; comments and clarification; tests (made-up, admittedly, but they work) --- .../packet/game/BattleplanMessage.scala | 258 +++++++++++------- .../scala/game/BattleplanMessageTest.scala | 253 ++++++++++++----- 2 files changed, 332 insertions(+), 179 deletions(-) diff --git a/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala b/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala index e4afebc2..cecd28ea 100644 --- a/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/BattleplanMessage.scala @@ -9,28 +9,28 @@ import shapeless.{::, HNil} /** * A `Codec` for the actions that each layer of the diagram performs. - * `Action1`, `Action2`, `Action5`, `Action6`, and `Action7` have additional `DiagramStroke` input data. + * `Style`, `Vertex`, `Action5`, `DrawString`, and `Action7` have additional `DiagramStroke` input data. */ object DiagramActionCode extends Enumeration { type Type = Value val Action0, - Action1, + Style, Vertex, Action3, Action4, Action5, - Action6, + DrawString, Action7, Action8, Action9, ActionA, ActionB, - ActionC, - ActionD, - ActionE, - ActionF - = Value //TODO replace these with descriptive wording + ActionC, //clear? + ActionD, //opposite of clear? + StartDrawing, + StopDrawing + = Value //TODO replace all these with descriptive words implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L) } @@ -41,46 +41,61 @@ object DiagramActionCode extends Enumeration { sealed trait DiagramStroke /** - * na - * @param unk1 na - * @param unk2 na + * Set style properties for the line segemnt(s) to be drawn. + * Color and thickness can not vary within a given line and will only apply to the subsequent line segments. + * Attempting to list a change in between coordinate points will invalidate that segment. + * @param thickness the line width in pixels; + * 0.0f - 16.0f; + * 3.0f is about normal and 0.0f is smaller than the map grid lines + * @param color the color of the line; + * 0 is gray (default); + * 1 is red; + * 2 is green; + * 3 is blue */ -final case class StrokeOne(unk1 : Float, - unk2 : Int) extends DiagramStroke +final case class Style(thickness : Float, + color : Int) extends DiagramStroke /** - * Mark coordinates on the tactical map. + * Indicate coordinates on the tactical map. + * Any adjacent sets of coordinates will be connected with a line segment. * @param x the x-coordinate of this point * @param y the y-coordinate of this point */ -final case class StrokeTwo(x : Float, - y : Float) extends DiagramStroke +final case class Vertex(x : Float, + y : Float) extends DiagramStroke /** * na - * @param unk1 na - * @param unk2 na - * @param unk3 na - * @param unk4 na + * @param x the x-coordinate of this point + * @param y the y-coordinate of this point + * @param unk na; + * 1024.0f - 0.0f */ -final case class StrokeFive(unk1 : Float, - unk2 : Float, - unk3 : Float, - unk4 : Int) extends DiagramStroke +final case class StrokeFive(x : Float, + y : Float, + unk : Float) extends DiagramStroke /** - * na - * @param unk1 na - * @param unk2 na - * @param unk3 na - * @param unk4 na - * @param unk5 na + * Draw a string message on the tactical map. + * String messages have their own color designation and will not inherit line properties. + * @param x the x-coordinate marking the bottom center of this message's text + * @param y the y-coordinate marking the bottom center of this message's text + * @param color the color of the message; + * 0 is gray (default); + * 1 is red; + * 2 is green; + * 3 is blue + * @param channel the available "slots" in which to display messages on the map; + * a maximum of 16 channels/messages (0-15) are available per player; + * no two messages may inhabit the same channel + * @param message the text to display */ -final case class StrokeSix(unk1 : Float, - unk2 : Float, - unk3 : Int, - unk4 : Int, - unk5 : String) extends DiagramStroke +final case class DrawString(x : Float, + y : Float, + color : Int, + channel : Int, + message : String) extends DiagramStroke /** * na @@ -89,21 +104,51 @@ final case class StrokeSix(unk1 : Float, final case class StrokeSeven(unk : Int) extends DiagramStroke /** - * na + * A particular instruction in the rendering of this battleplan's diagram entry. * @param action the behavior of this stroke; * a hint to the kind of stroke data stored, if at all, and how to use it or incorporate prior data - * @param stroke the data + * @param stroke the data; + * defaults to `None` */ final case class BattleDiagramAction(action : DiagramActionCode.Value, stroke : Option[DiagramStroke] = None) /** - * na + * Share drawn images and words on the tactical map among a group of players.
+ *
+ * Each packet usually contains a small portion of an image, herein called a "diagram." + * `BattleplanMessage` packets are accumulative towards a full diagram. + * Moreover, rather than the `player_name`, each diagram is associated on a client by the `char_id` field. + * Only squad leaders and platoon leaders can draw on the map and share with other players in their squad or platoon.
+ *
+ * To start drawing, a would-be artist must have all clients who will receive their diagrams acknowledge a `StartDrawing` action. + * The `char_id` with this `StartDrawing` will associate all diagrams submitted with the same `char_id`'s portfolio. + * Multiple portfolio definitions may exist on a client at a given time and each will manage their own diagrams. + * When a given portfolio submits a `StopDrawing` action that is received, the previous diagrams associated with it will be cleared. + * That `char_id` will no longer accept diagrams on that client. + * Other portfolios will continue to accept diagrams as initialized. + * When no portfolios are being accepted, the "Toggle -> Battleplan" button on that client's tactical map will be disabled. + * When there is at least one portfolio accepted, the "Battleplan" button will be functional and can be toggled.
+ *
+ * To construct line segments, chain `StrokeTwo` diagrams in the given packet entry. + * Each defined point will act like a successive vertex in a chain of segments. + * Any non-vertex entry in between entries, e.g., a change of line color, will break the chain of line segments. + * For example:
+ * RED-A-B-C will construct red lines segments A-B and B-C.
+ * RED-A-B-GREEN-C will only construct a red line segement A-B.
+ * RED-A-B-GREEN-C-D will construct a red line segement A-B and a green line segment C-D.
+ * (Default line color, if none is declared specifically, is gray.)
+ *
+ * To construct a message, define a point to act as the center baseline for the text. + * The message will be written above and outwards from that point. + * Messages do not carry properties over from line segments - they set their own color and do not have line thickness. + * Any single portfolio may have only fifteen messages written to the tactical map at a time. * @param char_id na; * same as in `CharacterInfoMessage` * @param player_name the player who contributed this battle plan - * @param zone_id on which continent the battle plan will be overlaid - * @param diagrams a list of the individual actions that compose this plan + * @param zone_id on which continent the battle plan will be overlaid; + * can identify as "no zone" 0 when performing instructions not specific to drawing + * @param diagrams a list of the itemized actions that will construct this plan or are used to modify the plan */ final case class BattleplanMessage(char_id : Long, player_name : String, @@ -118,12 +163,12 @@ final case class BattleplanMessage(char_id : Long, object BattleDiagramAction { /** * Create a `BattleDiagramAction` object containing `StrokeOne` data. - * @param unk1 na - * @param unk2 na + * @param thickness the line width in pixels + * @param color the color of the line * @return a `BattleDiagramAction` object */ - def stroke1(unk1 : Float, unk2 : Int) : BattleDiagramAction = - BattleDiagramAction(DiagramActionCode.Action1, Some(StrokeOne(unk1, unk2))) + def style(thickness : Float, color : Int) : BattleDiagramAction = + BattleDiagramAction(DiagramActionCode.Style, Some(Style(thickness, color))) /** * Create a `BattleDiagramAction` object containing `StrokeTwo` vertex data. @@ -132,30 +177,28 @@ object BattleDiagramAction { * @return a `BattleDiagramAction` object */ def vertex(x : Float, y : Float) : BattleDiagramAction = - BattleDiagramAction(DiagramActionCode.Vertex, Some(StrokeTwo(x, y))) + BattleDiagramAction(DiagramActionCode.Vertex, Some(Vertex(x, y))) /** * Create a `BattleDiagramAction` object containing `StrokeFive` data. - * @param unk1 na - * @param unk2 na - * @param unk3 na - * @param unk4 na + * @param x the x-coordinate of this point + * @param y the y-coordinate of this point + * @param unk na * @return a `BattleDiagramAction` object */ - def stroke5(unk1 : Float, unk2 : Float, unk3 : Float, unk4 : Int) : BattleDiagramAction = - BattleDiagramAction(DiagramActionCode.Action5, Some(StrokeFive(unk1, unk2, unk3, unk4))) + def stroke5(x : Float, y : Float, unk : Float) : BattleDiagramAction = + BattleDiagramAction(DiagramActionCode.Action5, Some(StrokeFive(x, y, unk))) /** * Create a `BattleDiagramAction` object containing `StrokeSix` data. - * @param unk1 na - * @param unk2 na - * @param unk3 na - * @param unk4 na - * @param unk5 na - * @return a `BattleDiagramAction` object + * @param x the x-coordinate marking the bottom center of this message's text + * @param y the y-coordinate marking the bottom center of this message's text + * @param color the color of the message + * @param channel the available "slots" in which to display messages on the map + * @param message the text to display */ - def stroke6(unk1 : Float, unk2 : Float, unk3 : Int, unk4 : Int, unk5 : String) : BattleDiagramAction = - BattleDiagramAction(DiagramActionCode.Action6, Some(StrokeSix(unk1, unk2, unk3, unk4, unk5))) + def drawString(x : Float, y : Float, color : Int, channel : Int, message : String) : BattleDiagramAction = + BattleDiagramAction(DiagramActionCode.DrawString, Some(DrawString(x, y, color, channel, message))) /** * Create a `BattleDiagramAction` object containing `StrokeSeven` data. @@ -171,58 +214,61 @@ object BattleplanMessage extends Marshallable[BattleplanMessage] { /** * An intermediary object intended to temporarily store `BattleDiagramAction` objects.
*
- * This hidden object is arranged like a linked list; - * but, later, it is converted into an accessible formal `List` during decoding; - * likewise, during the encoding process, the `List` is transformed back into a linked list structure. - * `Scala`'s own linked list `Collection` is deprecated, without substitution, so this custom one must be used. + * This hidden object is arranged like a linked list. + * During the decoding process, it is converted into an accessible formal `List`. + * During the encoding process, the `List` is transformed back into a linked list structure. + * Scala's own linked list `Collection` is deprecated, without substitution, so this custom one shall be used. * @param diagram the contained object that maintains the data * @param next the next `BattleDiagramChain`, if any + * @see scala.collection.mutable.LinkedList<E> + * @see java.util.LinkedList<E> */ private final case class BattleDiagramChain(diagram : BattleDiagramAction, next : Option[BattleDiagramChain]) /** - * Parse data into a `StrokeOne` object. + * Parse data into a `Style` object. */ - private val plan1_codec : Codec[StrokeOne] = ( //size: 8; pad: +0 - ("unk1" | newcodecs.q_float(0.0, 16.0, 5)) :: - ("unk2" | uintL(3)) - ).as[StrokeOne] + private val plan1_codec : Codec[Style] = ( //size: 8 (12) + ("thickness" | newcodecs.q_float(16.0, 0.0, 5)) :: + ("color" | uintL(3)) + ).as[Style] /** - * Parse data into a `StrokeTwo` object. + * Parse data into a `Vertex` object. */ - private val plan2_codec : Codec[StrokeTwo] = ( //size: 22; pad: +2 - ("unk1" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: - ("unk2" | newcodecs.q_float(-4096.0, 12288.0, 11)) - ).as[StrokeTwo] + private val plan2_codec : Codec[Vertex] = ( //size: 22 (26) + ("x" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: + ("y" | newcodecs.q_float(-4096.0, 12288.0, 11)) + ).as[Vertex] /** * Parse data into a `StrokeFive` object. */ - private val plan5_codec : Codec[StrokeFive] = ( //size: 44; pad: +4 + private val plan5_codec : Codec[StrokeFive] = ( //size: 33 (37) ("unk1" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: ("unk2" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: - ("unk3" | newcodecs.q_float(1024.0, 0.0, 11)) :: - ("unk4" | uintL(11)) + ("unk3" | newcodecs.q_float(1024.0, 0.0, 11)) ).as[StrokeFive] /** - * Parse data into a `StrokeSix` object. - * @param pad the current padding for the `String` entry + * Parse data into a `DrawString` object.
+ * If we are on a byte boundary upon starting this entry, our message is padded by `5u` (always). + * If we are not on a byte boundary, we must use our current offset and this size (`31u + 4u`) to calculate the padding value. + * @param padOffset the current padding value for the `String` entry */ - private def plan6_codec(pad : Int) : Codec[StrokeSix] = ( //size: 31 + string.length.field + string.length * 16 + padding; pad: value resets - ("unk1" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: - ("unk2" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: - ("unk3" | uintL(3)) :: - ("unk4" | uintL(6)) :: - ("unk5" | PacketHelpers.encodedWideStringAligned( (pad + 1) % 8 )) - ).as[StrokeSix] + private def plan6_codec(padOffset : Int) : Codec[DrawString] = ( //size: irrelevant, pad value resets + ("x" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: + ("y" | newcodecs.q_float(-4096.0, 12288.0, 11)) :: + ("color" | uintL(3)) :: + ("font_size" | uintL(6)) :: + ("message" | PacketHelpers.encodedWideStringAligned( if(padOffset % 8 == 0) { 5 } else { 8 - (padOffset + 35) % 8 } )) + ).as[DrawString] /** * Parse data into a `StrokeSeven` object. */ - private val plan7_codec : Codec[StrokeSeven] = ("unk" | uintL(6)).as[StrokeSeven] // size: 6; pad: +2 + private val plan7_codec : Codec[StrokeSeven] = ("unk" | uintL(6)).as[StrokeSeven] // size: 6 (10) /** * Switch between different patterns to create a `BattleDiagramAction` for the following data. @@ -232,10 +278,10 @@ object BattleplanMessage extends Marshallable[BattleplanMessage] { * @return a `BattleDiagramAction` object */ private def diagram_codec(plan : DiagramActionCode.Value, pad : Int) : Codec[BattleDiagramAction] = ( - conditional(plan == DiagramActionCode.Action1, plan1_codec) :: + conditional(plan == DiagramActionCode.Style, plan1_codec) :: conditional(plan == DiagramActionCode.Vertex, plan2_codec) :: conditional(plan == DiagramActionCode.Action5, plan5_codec) :: - conditional(plan == DiagramActionCode.Action6, plan6_codec(pad)) :: + conditional(plan == DiagramActionCode.DrawString, plan6_codec(pad)) :: conditional(plan == DiagramActionCode.Action7, plan7_codec) ).exmap[BattleDiagramAction] ( { @@ -261,17 +307,17 @@ object BattleplanMessage extends Marshallable[BattleplanMessage] { Attempt.failure(Err(s"too many strokes for action $plan")) }, { - case BattleDiagramAction(DiagramActionCode.Action1, Some(stroke)) => - Attempt.successful(Some(stroke.asInstanceOf[StrokeOne]) :: None :: None :: None :: None :: HNil) + case BattleDiagramAction(DiagramActionCode.Style, Some(stroke)) => + Attempt.successful(Some(stroke.asInstanceOf[Style]) :: None :: None :: None :: None :: HNil) case BattleDiagramAction(DiagramActionCode.Vertex, Some(stroke)) => - Attempt.successful(None :: Some(stroke.asInstanceOf[StrokeTwo]) :: None :: None :: None :: HNil) + Attempt.successful(None :: Some(stroke.asInstanceOf[Vertex]) :: None :: None :: None :: HNil) case BattleDiagramAction(DiagramActionCode.Action5, Some(stroke)) => Attempt.successful(None :: None :: Some(stroke.asInstanceOf[StrokeFive]) :: None :: None :: HNil) - case BattleDiagramAction(DiagramActionCode.Action6, Some(stroke)) => - Attempt.successful(None :: None :: None :: Some(stroke.asInstanceOf[StrokeSix]) :: None :: HNil) + case BattleDiagramAction(DiagramActionCode.DrawString, Some(stroke)) => + Attempt.successful(None :: None :: None :: Some(stroke.asInstanceOf[DrawString]) :: None :: HNil) case BattleDiagramAction(DiagramActionCode.Action7, Some(stroke)) => Attempt.successful(None :: None :: None :: None :: Some(stroke.asInstanceOf[StrokeSeven]) :: HNil) @@ -285,20 +331,20 @@ object BattleplanMessage extends Marshallable[BattleplanMessage] { ) /** - * Parse what was originally an encoded `List` of elements as a linked list of elements. - * Maintain a `String` padding value that applies an appropriate offset value. + * Parse diagram instructions as a linked list. + * Maintain a `String` padding value that applies an appropriate offset value regardless of where in the elements it is required. * @param remaining the number of elements remaining to parse - * @param pad the current padding for any `String` entry stored within the parsed elements; - * different elements add different padding offset to this field on subsequent passes + * @param padOffset the current padding for any `String` entry stored within the parsed elements; + * different elements add different padding offset to this field on subsequent passes * @return a `Codec` for `BattleDiagramChain` segments */ - private def parse_diagrams_codec(remaining : Int, pad : Int = 0) : Codec[BattleDiagramChain] = ( + private def parse_diagrams_codec(remaining : Int, padOffset : Int = 0) : Codec[BattleDiagramChain] = ( DiagramActionCode.codec >>:~ { plan => - ("diagram" | diagram_codec(plan, pad)) :: + ("diagram" | diagram_codec(plan, padOffset)) :: conditional(remaining > 1, "next" | parse_diagrams_codec( remaining - 1, - pad + (if(plan == DiagramActionCode.Vertex || plan == DiagramActionCode.Action7) { 2 } else if(plan == DiagramActionCode.Action5) { 4 } else if(plan == DiagramActionCode.Action6) { -pad } else { 0 }) + padOffset + (if(plan == DiagramActionCode.DrawString) { -padOffset } else if(plan == DiagramActionCode.Action5) { 37 } else if(plan == DiagramActionCode.Vertex) { 26 } else if(plan == DiagramActionCode.Style) { 12 } else if(plan == DiagramActionCode.Action7) { 10 } else { 4 }) ) ) }).exmap[BattleDiagramChain] ( @@ -319,16 +365,18 @@ object BattleplanMessage extends Marshallable[BattleplanMessage] { * @param list a `List` of extracted `BattleDiagrams`; * technically, the output */ - private def rollDiagramLayers(element : BattleDiagramChain, list : ListBuffer[BattleDiagramAction]) : Unit = { - list += element.diagram - if(element.next.isDefined) - rollDiagramLayers(element.next.get, list) //tail call optimization + private def rollDiagramLayers(element : Option[BattleDiagramChain], list : ListBuffer[BattleDiagramAction]) : Unit = { + if(element.isEmpty) + return + list += element.get.diagram + rollDiagramLayers(element.get.next, list) //tail call optimization } /** * Transform a `List` of `BattleDiagramAction` objects into a linked list of `BattleDiagramChain` objects. * @param revIter a reverse `List` `Iterator` for a `List` of `BattleDiagrams` * @param layers the current head of a chain of `BattleDiagramChain` objects; + * defaults to `None`, so does not need to be defined during the initial pass; * technically, the output * @return a linked list of `BattleDiagramChain` objects */ @@ -351,7 +399,7 @@ object BattleplanMessage extends Marshallable[BattleplanMessage] { case char_id :: player :: zone_id :: _ :: diagramLayers :: HNil => val list : ListBuffer[BattleDiagramAction] = new ListBuffer() if(diagramLayers.isDefined) - rollDiagramLayers(diagramLayers.get, list) + rollDiagramLayers(diagramLayers, list) Attempt.successful(BattleplanMessage(char_id, player, zone_id, list.toList)) }, { diff --git a/common/src/test/scala/game/BattleplanMessageTest.scala b/common/src/test/scala/game/BattleplanMessageTest.scala index 0fbbaaf9..0b015a70 100644 --- a/common/src/test/scala/game/BattleplanMessageTest.scala +++ b/common/src/test/scala/game/BattleplanMessageTest.scala @@ -7,26 +7,44 @@ import net.psforever.packet.game._ import scodec.bits._ class BattleplanMessageTest extends Specification { - val string = hex"b3 3a197902 94 59006500740041006e006f0074006800650072004600610069006c0075007200650041006c007400 0000 01 e0" - val string_line = hex"b3 85647702 8c4f0075007400730074006100620075006c006f0075007300 0a00 20 2aba2b4aae8bd2aba334aae8dd2aca3b4ab28fd2aca414ab29152aca474ab292d2ada4d4ab69452ada534ab695d2ada594ab696d2ada5d4ab697d2ada614ab698d2ada654ab699d2ada694ab69ad2aea6d4aba9bd2aea714aba9cd2aea754aba9dd2aea794aba9ed" + val string_start = hex"b3 3a197902 94 59006500740041006e006f0074006800650072004600610069006c0075007200650041006c007400 0000 01 e0" + val string_stop = hex"b3 3a197902 94 59006500740041006e006f0074006800650072004600610069006c0075007200650041006c007400 0000 01 f0" + val string_line = hex"b3 85647702 8c 4f0075007400730074006100620075006c006f0075007300 0a00 20 2aba2b4aae8bd2aba334aae8dd2aca3b4ab28fd2aca414ab29152aca474ab292d2ada4d4ab69452ada534ab695d2ada594ab696d2ada5d4ab697d2ada614ab698d2ada654ab699d2ada694ab69ad2aea6d4aba9bd2aea714aba9cd2aea754aba9dd2aea794aba9ed" + val string_style = hex"b3856477028c4f0075007400730074006100620075006c006f00750073000a00031d22aba2f4aae8cd" + val string_message = hex"b3 85647702 8c 4f0075007400730074006100620075006c006f0075007300 0a00 01 6aba2b5011c0480065006c006c006f00200041007500720061007800690073002100" //0xb3856477028c4f0075007400730074006100620075006c006f00750073000a000130 - "decode (short)" in { - PacketCoding.DecodePacket(string).require match { + "decode (start)" in { + PacketCoding.DecodePacket(string_start).require match { case BattleplanMessage(char_id, player_name, zone_id, diagrams) => char_id mustEqual 41490746 player_name mustEqual "YetAnotherFailureAlt" zone_id mustEqual PlanetSideGUID(0) diagrams.size mustEqual 1 //0 - diagrams.head.action mustEqual DiagramActionCode.ActionE + diagrams.head.action mustEqual DiagramActionCode.StartDrawing diagrams.head.stroke.isDefined mustEqual false case _ => ko } } - "decode (line)" in { + "decode (end)" in { + PacketCoding.DecodePacket(string_stop).require match { + case BattleplanMessage(char_id, player_name, zone_id, diagrams) => + char_id mustEqual 41490746 + player_name mustEqual "YetAnotherFailureAlt" + zone_id mustEqual PlanetSideGUID(0) + diagrams.size mustEqual 1 + //0 + diagrams.head.action mustEqual DiagramActionCode.StopDrawing + diagrams.head.stroke.isDefined mustEqual false + case _ => + ko + } + } + + "decode (stop)" in { PacketCoding.DecodePacket(string_line).require match { case BattleplanMessage(char_id, player_name, zone_id, diagrams) => char_id mustEqual 41378949 @@ -36,149 +54,209 @@ class BattleplanMessageTest extends Specification { //0 diagrams.head.action mustEqual DiagramActionCode.Vertex diagrams.head.stroke.isDefined mustEqual true - diagrams.head.stroke.get.isInstanceOf[StrokeTwo] mustEqual true - diagrams.head.stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7512.0f - diagrams.head.stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6312.0f + diagrams.head.stroke.get.isInstanceOf[Vertex] mustEqual true + diagrams.head.stroke.get.asInstanceOf[Vertex].x mustEqual 7512.0f + diagrams.head.stroke.get.asInstanceOf[Vertex].y mustEqual 6312.0f //1 diagrams(1).action mustEqual DiagramActionCode.Vertex - diagrams(1).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7512.0f - diagrams(1).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6328.0f + diagrams(1).stroke.get.asInstanceOf[Vertex].x mustEqual 7512.0f + diagrams(1).stroke.get.asInstanceOf[Vertex].y mustEqual 6328.0f //2 diagrams(2).action mustEqual DiagramActionCode.Vertex - diagrams(2).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7512.0f - diagrams(2).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6344.0f + diagrams(2).stroke.get.asInstanceOf[Vertex].x mustEqual 7512.0f + diagrams(2).stroke.get.asInstanceOf[Vertex].y mustEqual 6344.0f //3 diagrams(3).action mustEqual DiagramActionCode.Vertex - diagrams(3).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7512.0f - diagrams(3).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6360.0f + diagrams(3).stroke.get.asInstanceOf[Vertex].x mustEqual 7512.0f + diagrams(3).stroke.get.asInstanceOf[Vertex].y mustEqual 6360.0f //4 diagrams(4).action mustEqual DiagramActionCode.Vertex - diagrams(4).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7520.0f - diagrams(4).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6376.0f + diagrams(4).stroke.get.asInstanceOf[Vertex].x mustEqual 7520.0f + diagrams(4).stroke.get.asInstanceOf[Vertex].y mustEqual 6376.0f //5 diagrams(5).action mustEqual DiagramActionCode.Vertex - diagrams(5).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7520.0f - diagrams(5).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6392.0f + diagrams(5).stroke.get.asInstanceOf[Vertex].x mustEqual 7520.0f + diagrams(5).stroke.get.asInstanceOf[Vertex].y mustEqual 6392.0f //6 diagrams(6).action mustEqual DiagramActionCode.Vertex - diagrams(6).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7520.0f - diagrams(6).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6400.0f + diagrams(6).stroke.get.asInstanceOf[Vertex].x mustEqual 7520.0f + diagrams(6).stroke.get.asInstanceOf[Vertex].y mustEqual 6400.0f //7 diagrams(7).action mustEqual DiagramActionCode.Vertex - diagrams(7).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7520.0f - diagrams(7).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6416.0f + diagrams(7).stroke.get.asInstanceOf[Vertex].x mustEqual 7520.0f + diagrams(7).stroke.get.asInstanceOf[Vertex].y mustEqual 6416.0f //8 diagrams(8).action mustEqual DiagramActionCode.Vertex - diagrams(8).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7520.0f - diagrams(8).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6424.0f + diagrams(8).stroke.get.asInstanceOf[Vertex].x mustEqual 7520.0f + diagrams(8).stroke.get.asInstanceOf[Vertex].y mustEqual 6424.0f //9 diagrams(9).action mustEqual DiagramActionCode.Vertex - diagrams(9).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7520.0f - diagrams(9).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6440.0f + diagrams(9).stroke.get.asInstanceOf[Vertex].x mustEqual 7520.0f + diagrams(9).stroke.get.asInstanceOf[Vertex].y mustEqual 6440.0f //10 diagrams(10).action mustEqual DiagramActionCode.Vertex - diagrams(10).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f - diagrams(10).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6448.0f + diagrams(10).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f + diagrams(10).stroke.get.asInstanceOf[Vertex].y mustEqual 6448.0f //11 diagrams(11).action mustEqual DiagramActionCode.Vertex - diagrams(11).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f - diagrams(11).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6464.0f + diagrams(11).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f + diagrams(11).stroke.get.asInstanceOf[Vertex].y mustEqual 6464.0f //12 diagrams(12).action mustEqual DiagramActionCode.Vertex - diagrams(12).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f - diagrams(12).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6472.0f + diagrams(12).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f + diagrams(12).stroke.get.asInstanceOf[Vertex].y mustEqual 6472.0f //13 diagrams(13).action mustEqual DiagramActionCode.Vertex - diagrams(13).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f - diagrams(13).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6488.0f + diagrams(13).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f + diagrams(13).stroke.get.asInstanceOf[Vertex].y mustEqual 6488.0f //14 diagrams(14).action mustEqual DiagramActionCode.Vertex - diagrams(14).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f - diagrams(14).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6496.0f + diagrams(14).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f + diagrams(14).stroke.get.asInstanceOf[Vertex].y mustEqual 6496.0f //15 diagrams(15).action mustEqual DiagramActionCode.Vertex - diagrams(15).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f - diagrams(15).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6504.0f + diagrams(15).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f + diagrams(15).stroke.get.asInstanceOf[Vertex].y mustEqual 6504.0f //16 diagrams(16).action mustEqual DiagramActionCode.Vertex - diagrams(16).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f - diagrams(16).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6512.0f + diagrams(16).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f + diagrams(16).stroke.get.asInstanceOf[Vertex].y mustEqual 6512.0f //17 diagrams(17).action mustEqual DiagramActionCode.Vertex - diagrams(17).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f - diagrams(17).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6520.0f + diagrams(17).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f + diagrams(17).stroke.get.asInstanceOf[Vertex].y mustEqual 6520.0f //18 diagrams(18).action mustEqual DiagramActionCode.Vertex - diagrams(18).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f - diagrams(18).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6528.0f + diagrams(18).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f + diagrams(18).stroke.get.asInstanceOf[Vertex].y mustEqual 6528.0f //19 diagrams(19).action mustEqual DiagramActionCode.Vertex - diagrams(19).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f - diagrams(19).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6536.0f + diagrams(19).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f + diagrams(19).stroke.get.asInstanceOf[Vertex].y mustEqual 6536.0f //20 diagrams(20).action mustEqual DiagramActionCode.Vertex - diagrams(20).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f - diagrams(20).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6544.0f + diagrams(20).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f + diagrams(20).stroke.get.asInstanceOf[Vertex].y mustEqual 6544.0f //21 diagrams(21).action mustEqual DiagramActionCode.Vertex - diagrams(21).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f - diagrams(21).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6552.0f + diagrams(21).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f + diagrams(21).stroke.get.asInstanceOf[Vertex].y mustEqual 6552.0f //22 diagrams(22).action mustEqual DiagramActionCode.Vertex - diagrams(22).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f - diagrams(22).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6560.0f + diagrams(22).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f + diagrams(22).stroke.get.asInstanceOf[Vertex].y mustEqual 6560.0f //23 diagrams(23).action mustEqual DiagramActionCode.Vertex - diagrams(23).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7528.0f - diagrams(23).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6568.0f + diagrams(23).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f + diagrams(23).stroke.get.asInstanceOf[Vertex].y mustEqual 6568.0f //24 diagrams(24).action mustEqual DiagramActionCode.Vertex - diagrams(24).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7536.0f - diagrams(24).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6576.0f + diagrams(24).stroke.get.asInstanceOf[Vertex].x mustEqual 7536.0f + diagrams(24).stroke.get.asInstanceOf[Vertex].y mustEqual 6576.0f //25 diagrams(25).action mustEqual DiagramActionCode.Vertex - diagrams(25).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7536.0f - diagrams(25).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6584.0f + diagrams(25).stroke.get.asInstanceOf[Vertex].x mustEqual 7536.0f + diagrams(25).stroke.get.asInstanceOf[Vertex].y mustEqual 6584.0f //26 diagrams(26).action mustEqual DiagramActionCode.Vertex - diagrams(26).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7536.0f - diagrams(26).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6592.0f + diagrams(26).stroke.get.asInstanceOf[Vertex].x mustEqual 7536.0f + diagrams(26).stroke.get.asInstanceOf[Vertex].y mustEqual 6592.0f //27 diagrams(27).action mustEqual DiagramActionCode.Vertex - diagrams(27).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7536.0f - diagrams(27).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6600.0f + diagrams(27).stroke.get.asInstanceOf[Vertex].x mustEqual 7536.0f + diagrams(27).stroke.get.asInstanceOf[Vertex].y mustEqual 6600.0f //28 diagrams(28).action mustEqual DiagramActionCode.Vertex - diagrams(28).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7536.0f - diagrams(28).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6608.0f + diagrams(28).stroke.get.asInstanceOf[Vertex].x mustEqual 7536.0f + diagrams(28).stroke.get.asInstanceOf[Vertex].y mustEqual 6608.0f //29 diagrams(29).action mustEqual DiagramActionCode.Vertex - diagrams(29).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7536.0f - diagrams(29).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6616.0f + diagrams(29).stroke.get.asInstanceOf[Vertex].x mustEqual 7536.0f + diagrams(29).stroke.get.asInstanceOf[Vertex].y mustEqual 6616.0f //30 diagrams(30).action mustEqual DiagramActionCode.Vertex - diagrams(30).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7536.0f - diagrams(30).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6624.0f + diagrams(30).stroke.get.asInstanceOf[Vertex].x mustEqual 7536.0f + diagrams(30).stroke.get.asInstanceOf[Vertex].y mustEqual 6624.0f //31 diagrams(31).action mustEqual DiagramActionCode.Vertex - diagrams(31).stroke.get.asInstanceOf[StrokeTwo].x mustEqual 7536.0f - diagrams(31).stroke.get.asInstanceOf[StrokeTwo].y mustEqual 6632.0f + diagrams(31).stroke.get.asInstanceOf[Vertex].x mustEqual 7536.0f + diagrams(31).stroke.get.asInstanceOf[Vertex].y mustEqual 6632.0f case _ => ko } } - "encode (short)" in { + "decode (style)" in { + PacketCoding.DecodePacket(string_style).require match { + case BattleplanMessage(char_id, player_name, zone_id, diagrams) => + char_id mustEqual 41378949 + player_name mustEqual "Outstabulous" + zone_id mustEqual PlanetSideGUID(10) + diagrams.size mustEqual 3 + //0 + diagrams.head.action mustEqual DiagramActionCode.Style + diagrams.head.stroke.isDefined mustEqual true + diagrams.head.stroke.get.isInstanceOf[Style] mustEqual true + diagrams.head.stroke.get.asInstanceOf[Style].thickness mustEqual 3.0f + diagrams.head.stroke.get.asInstanceOf[Style].color mustEqual 2 + //1 + diagrams(1).action mustEqual DiagramActionCode.Vertex + diagrams(1).stroke.get.asInstanceOf[Vertex].x mustEqual 7512.0f + diagrams(1).stroke.get.asInstanceOf[Vertex].y mustEqual 6328.0f + //2 + diagrams(2).action mustEqual DiagramActionCode.Vertex + diagrams(2).stroke.get.asInstanceOf[Vertex].x mustEqual 7512.0f + diagrams(2).stroke.get.asInstanceOf[Vertex].y mustEqual 6344.0f + case _ => + ko + } + } + + "decode (message)" in { + PacketCoding.DecodePacket(string_message).require match { + case BattleplanMessage(char_id, player_name, zone_id, diagrams) => + char_id mustEqual 41378949 + player_name mustEqual "Outstabulous" + zone_id mustEqual PlanetSideGUID(10) + diagrams.size mustEqual 1 + //0 + diagrams.head.action mustEqual DiagramActionCode.DrawString + diagrams.head.stroke.isDefined mustEqual true + diagrams.head.stroke.get.isInstanceOf[DrawString] mustEqual true + diagrams.head.stroke.get.asInstanceOf[DrawString].x mustEqual 7512.0f + diagrams.head.stroke.get.asInstanceOf[DrawString].y mustEqual 6312.0f + diagrams.head.stroke.get.asInstanceOf[DrawString].color mustEqual 2 + diagrams.head.stroke.get.asInstanceOf[DrawString].channel mustEqual 0 + diagrams.head.stroke.get.asInstanceOf[DrawString].message mustEqual "Hello Auraxis!" + case _ => + ko + } + } + + "encode (start)" in { val msg = BattleplanMessage( 41490746, "YetAnotherFailureAlt", PlanetSideGUID(0), - BattleDiagramAction(DiagramActionCode.ActionE) :: + BattleDiagramAction(DiagramActionCode.StartDrawing) :: Nil ) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector - pkt mustEqual string + pkt mustEqual string_start + } + + "encode (stop)" in { + val msg = BattleplanMessage( + 41490746, + "YetAnotherFailureAlt", + PlanetSideGUID(0), + BattleDiagramAction(DiagramActionCode.StopDrawing) :: + Nil + ) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_stop } "encode (line)" in { @@ -224,4 +302,31 @@ class BattleplanMessageTest extends Specification { pkt mustEqual string_line } + + "encode (style)" in { + val msg = BattleplanMessage( + 41378949, + "Outstabulous", + PlanetSideGUID(10), + BattleDiagramAction.style(3.0f, 2) :: + BattleDiagramAction.vertex(7512.0f, 6328.0f) :: + BattleDiagramAction.vertex(7512.0f, 6344.0f) :: + Nil + ) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_style + } + + "encode (message)" in { + val msg = BattleplanMessage( + 41378949, + "Outstabulous", + PlanetSideGUID(10), + BattleDiagramAction.drawString(7512.0f, 6312.0f, 2, 0, "Hello Auraxis!") :: Nil + ) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_message + } }