diff --git a/common/src/main/scala/net/psforever/packet/game/ObjectCreateMessage.scala b/common/src/main/scala/net/psforever/packet/game/ObjectCreateMessage.scala index 727c4da6..3730482c 100644 --- a/common/src/main/scala/net/psforever/packet/game/ObjectCreateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/ObjectCreateMessage.scala @@ -8,42 +8,48 @@ import shapeless._ import scala.annotation.switch -case class Weapon(unk1 : Int, - magazine : Int, - unk2 : Int) +case class AmmoBox(magazine : Int) -object Weapon extends Marshallable[Weapon] { - implicit val codec : Codec[Weapon] = ( - ("unk1" | uintL(23)) :: - ("magazine" | uint8L) :: - ("unk2" | uintL(13)) - ).as[Weapon] +object AmmoBox extends Marshallable[AmmoBox] { + implicit val codec : Codec[AmmoBox] = ( + ("code" | uintL(23)) :: + ("magazine" | uint16L) + ).exmap[AmmoBox] ( + { + case 0xC8 :: mag :: HNil => + Attempt.successful(AmmoBox(mag)) + case x :: _ :: HNil => + Attempt.failure(Err("code wrong - looking for 200, found "+x)) + }, + { + case AmmoBox(mag) => + Attempt.successful(0xC8 :: mag :: HNil) + } + ).as[AmmoBox] } case class Mold(objectClass : Int, - dataPortion : BitVector) { + data : BitVector) { + private val obj : Option[Any] = Mold.selectMold(objectClass, data) - private var obj : Option[Any] = Mold.selectMold(objectClass, dataPortion) + def isDefined : Boolean = this.obj.isDefined + + def get : T forSome { type T } = this.obj.get } -object Mold extends Marshallable[Mold] { +object Mold { def apply(objectClass : Int, obj : T forSome { type T }) : Mold = new Mold(objectClass, bin"") - def selectMold(objClass : Int, data : BitVector) : Option[Any] = { + def selectMold(objClass : Int, data : BitVector) : Option[_] = { (objClass : @switch) match { - case 0x4D3 => - Weapon.codec.decode(data).toOption + case 0x1C => + Some(AmmoBox.codec.decode(data)) case _ => None } } - - implicit val codec : Codec[Mold] = ( - ("objectClass" | uintL(11)) :: - ("dataPortion" | bits) - ).as[Mold] } /** @@ -87,14 +93,14 @@ case class ObjectCreateMessageParent(guid : PlanetSideGUID, * @param objectClass the code for the type of object being constructed * @param guid the GUID this object will be assigned * @param parentInfo if defined, the relationship between this object and another object (its parent) - * @param data the data used to construct this type of object; + * @param mold the data used to construct this type of object; * requires further object-specific processing */ case class ObjectCreateMessage(streamLength : Long, objectClass : Int, guid : PlanetSideGUID, parentInfo : Option[ObjectCreateMessageParent], - data : BitVector) + mold : Mold) extends PlanetSideGamePacket { def opcode = GamePacketOpcode.ObjectCreateMessage def encode = ObjectCreateMessage.encode(this) @@ -195,12 +201,12 @@ object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] { ).xmap[ObjectCreateMessage] ( { case len :: cls :: guid :: info :: data :: HNil => - ObjectCreateMessage(len, cls, guid, info, data) + ObjectCreateMessage(len, cls, guid, info, Mold(cls, data)) }, { //the user should not have to manually supply a proper stream length, that's a restrictive requirement - case ObjectCreateMessage(_, cls, guid, info, data) => - streamLen(info, data) :: cls :: guid :: info :: data :: HNil + case ObjectCreateMessage(_, cls, guid, info, mold) => + streamLen(info, mold.data) :: cls :: guid :: info :: mold.data :: HNil } ).as[ObjectCreateMessage] } diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala index 2d25cb26..6366f785 100644 --- a/common/src/test/scala/GamePacketTest.scala +++ b/common/src/test/scala/GamePacketTest.scala @@ -148,22 +148,39 @@ class GamePacketTest extends Specification { val packet = hex"18 CF 13 00 00 BC 87 00 0A F0 16 C3 43 A1 30 90 00 02 C0 40 00 08 70 43 00 68 00 6F 00 72 00 64 00 54 00 52 00 82 65 1F F5 9E 80 80 00 00 00 00 00 3F FF C0 00 00 00 20 00 00 00 20 27 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FC CC 10 00 03 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 00 00 00 01 00 7E C8 00 C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 C0 00 42 C5 46 86 C7 00 00 02 A0 00 00 12 60 78 70 65 5F 77 61 72 70 5F 67 61 74 65 5F 75 73 61 67 65 92 78 70 65 5F 69 6E 73 74 61 6E 74 5F 61 63 74 69 6F 6E 92 78 70 65 5F 73 61 6E 63 74 75 61 72 79 5F 68 65 6C 70 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 32 8E 78 70 65 5F 66 6F 72 6D 5F 73 71 75 61 64 8E 78 70 65 5F 74 68 5F 6E 6F 6E 73 61 6E 63 8B 78 70 65 5F 74 68 5F 61 6D 6D 6F 90 78 70 65 5F 74 68 5F 66 69 72 65 6D 6F 64 65 73 8F 75 73 65 64 5F 63 68 61 69 6E 62 6C 61 64 65 9A 76 69 73 69 74 65 64 5F 62 72 6F 61 64 63 61 73 74 5F 77 61 72 70 67 61 74 65 8E 76 69 73 69 74 65 64 5F 6C 6F 63 6B 65 72 8D 75 73 65 64 5F 70 75 6E 69 73 68 65 72 88 75 73 65 64 5F 72 65 6B 8D 75 73 65 64 5F 72 65 70 65 61 74 65 72 9F 76 69 73 69 74 65 64 5F 64 65 63 6F 6E 73 74 72 75 63 74 69 6F 6E 5F 74 65 72 6D 69 6E 61 6C 8F 75 73 65 64 5F 73 75 70 70 72 65 73 73 6F 72 96 76 69 73 69 74 65 64 5F 6F 72 64 65 72 5F 74 65 72 6D 69 6E 61 6C 85 6D 61 70 31 35 85 6D 61 70 31 34 85 6D 61 70 31 32 85 6D 61 70 30 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 0A 36 13 88 04 00 40 00 00 10 00 04 00 00 4D 6E 40 10 41 00 00 00 40 00 18 08 38 1C C0 20 32 00 00 07 80 15 E1 D0 02 10 20 00 00 08 00 03 01 07 13 A8 04 06 40 00 00 10 03 20 BB 00 42 E4 00 00 01 00 0E 07 70 08 6C 80 00 06 40 01 C0 F0 01 13 90 00 00 C8 00 38 1E 40 23 32 00 00 19 00 07 03 D0 05 0E 40 00 03 20 00 E8 7B 00 A4 C8 00 00 64 00 DA 4F 80 14 E1 00 00 00 40 00 18 08 38 1F 40 20 32 00 00 0A 00 08 " val packet2 = hex"18 F8 00 00 00 BC 8C 10 90 3B 45 C6 FA 94 00 9F F0 00 00 40 00 08 C0 44 00 69 00 66 00 66 00 45" //faked data? val packet2Rest = packet2.bits.drop(8 + 32 + 1 + 11 + 16) + val string_9mm = hex"18 7C000000 2580 0E0 0005 A1 C8000064000" "decode (2)" in { PacketCoding.DecodePacket(packet2).require match { - case obj @ ObjectCreateMessage(len, cls, guid, parent, rest) => + case obj @ ObjectCreateMessage(len, cls, guid, parent, mold) => len mustEqual 248 cls mustEqual 121 guid mustEqual PlanetSideGUID(2497) parent mustEqual None - rest mustEqual packet2Rest + mold.data mustEqual packet2Rest + mold.isDefined mustEqual false + case default => + ko + } + } + + "decode (9mm)" in { + PacketCoding.DecodePacket(string_9mm).require match { + case obj @ ObjectCreateMessage(len, cls, guid, parent, mold) => + len mustEqual 124 + cls mustEqual 28 + guid mustEqual PlanetSideGUID(1280) + parent.isDefined mustEqual true + parent.get.guid mustEqual PlanetSideGUID(75) + parent.get.slot mustEqual 33 + mold.isDefined mustEqual true case default => ko } } "encode (2)" in { - val msg = ObjectCreateMessage(0, 121, PlanetSideGUID(2497), None, packet2Rest) + val msg = ObjectCreateMessage(0, 121, PlanetSideGUID(2497), None, Mold(121, packet2Rest)) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual packet2 diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index ea99a03c..27c607b2 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -170,7 +170,7 @@ class WorldSessionActor extends Actor with MDCContextAware { true, //Boosted spawn room pain field true))) //Boosted generator room pain field - sendResponse(PacketCoding.CreateGamePacket(0, SetCurrentAvatarMessage(PlanetSideGUID(guid),0,0))) + sendResponse(PacketCoding.CreateGamePacket(0, SetCurrentAvatarMessage(guid,0,0))) import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global