From 73c88fb9de4706f4946cd902c228278f934b4e65 Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 27 Apr 2017 15:32:49 -0400 Subject: [PATCH] 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) + } + ) +}