Packet: WeatherMessage (#112)

* imported WeatherMessage packet and tests

* trusting the debug information, converting from Long to Float (Vector3)

* field identification for storms

* copyright corrections
This commit is contained in:
Fate-JH 2017-03-10 21:33:14 -05:00 committed by pschord
parent e84e9d47e6
commit 9275150c4b
3 changed files with 220 additions and 1 deletions

View file

@ -495,7 +495,7 @@ object GamePacketOpcode extends Enumeration {
case 0x92 => noDecoder(PlanetsideStringAttributeMessage)
case 0x93 => noDecoder(DataChallengeMessage)
case 0x94 => noDecoder(DataChallengeMessageResp)
case 0x95 => noDecoder(WeatherMessage)
case 0x95 => game.WeatherMessage.decode
case 0x96 => noDecoder(SimDataChallenge)
case 0x97 => noDecoder(SimDataChallengeResp)
// 0x98

View file

@ -0,0 +1,115 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.Vector3
import scodec.Codec
import scodec.codecs._
import shapeless.{::, HNil}
/**
* Cloud data.<br>
* <br>
* The remaining fields should be divided between a "location" and a "velocity" as per debug output.
* The values are probably paired.
* The converted data, however, seems weird for the kind of information those fields would suggest.
* @param id the id of the cloud;
* zero-indexed counter (probably)
* @param unk1 na;
* the z-component is always `0.0f`
* @param unk2 na;
* the z-component is always `0.0f`
*/
final case class CloudInfo(id : Int,
unk1 : Vector3,
unk2 : Vector3)
/**
* Storm data.
* @param loc the location of the storm;
* the z-component is always `0.0f`
* @param intensity na
* @param radius na;
* 100 is about 819.2
*/
final case class StormInfo(loc : Vector3,
intensity : Int,
radius : Int)
/**
* Dispatched by the server to update weather conditions.
* On former live (Gemini), the server sent a new packet to connected clients once every ~60s.<br>
* <br>
* Information about the fields in this packet come from extracted debug information.
* It is not necessarily "correct" but it is the best approximation for now.<br>
* <br>
* `
* Message type: %d (%s)\n length: %d\n<br>
* Number of Clouds : %d\n<br>
* Cloud ID: %d\n<br>
* \tCloud Location: %f %f\n<br>
* \tCloud Velocity: %f %f\n<br>
* Number of Storms : %d\n<br>
* Storm:\n<br>
* \tStorm Location: %f %f\n<br>
* \tStorm Intensity: %d\n<br>
* \tStorm Radius: %d\n<br>
* `
* @param clouds a list of cloud data;
* typically, just one entry
* @param storms a list of storm data;
* typically, fluctuates between nine and eleven entries
*/
final case class WeatherMessage(clouds : List[CloudInfo],
storms : List[StormInfo])
extends PlanetSideGamePacket {
type Packet = WeatherMessage
def opcode = GamePacketOpcode.WeatherMessage
def encode = WeatherMessage.encode(this)
}
object WeatherMessage extends Marshallable[WeatherMessage] {
/**
* `Codec` for `CloudInfo` data.
*/
private val cloudCodec : Codec[CloudInfo] = (
("id" | uint8L) ::
("unk1x" | floatL) ::
("unk1y" | floatL) ::
("unk2x" | floatL) ::
("unk2y" | floatL)
).xmap[CloudInfo] (
{
case id :: x1 :: y1 :: x2 :: y2 :: HNil =>
CloudInfo(id, Vector3(x1, y1, 0.0f), Vector3(x2, y2, 0.0f))
},
{
case CloudInfo(id, Vector3(x1, y1, _), Vector3(x2, y2, _)) =>
id :: x1 :: y1 :: x2 :: y2 :: HNil
}
)
/**
* `Codec` for `StormInfo` data.
*/
private val stormCodec : Codec[StormInfo] = (
("unkx" | floatL) ::
("unky" | floatL) ::
("i" | uint8L) ::
("r" | uint8L)
).xmap[StormInfo] (
{
case x :: y :: i :: r :: HNil =>
StormInfo(Vector3(x, y, 0.0f), i, r)
},
{
case StormInfo(Vector3(x, y, _), i, r) =>
x :: y :: i :: r :: HNil
}
)
implicit val codec : Codec[WeatherMessage] = (
("clouds" | PacketHelpers.listOfNAligned(uint32L, 0, cloudCodec)) ::
("storms" | PacketHelpers.listOfNAligned(uint32L, 0, stormCodec))
).as[WeatherMessage]
}

View file

@ -0,0 +1,104 @@
// Copyright (c) 2017 PSForever
package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game.{WeatherMessage, CloudInfo, StormInfo}
import net.psforever.types.Vector3
import scodec.bits._
class WeatherMessageTest extends Specification {
val string = hex"9501000000004A0807C0D65B8FBF2427663F178608BE0B000000006CE13E0C390E3F64445CB7BF3E0C2FF23DA46264A3193FBA522E3F597D9A96093F95B99E3D0800096FE53E6CD6523F39198EAF683F9BA0363D01009C35503F9E5F3E3F3C304E46F23EF9668E3E6B56C8277F3FB084F33EB6C10291423FB17F663F00008C077F3E3135D03E320A"
"decode" in {
PacketCoding.DecodePacket(string).require match {
case WeatherMessage(clouds, storms) =>
clouds.size mustEqual 1
clouds.head.id mustEqual 0
clouds.head.unk1.x mustEqual -2.109881f
clouds.head.unk1.y mustEqual -1.1199901f
clouds.head.unk2.x mustEqual 0.89903474f
clouds.head.unk2.y mustEqual -0.13332401f
storms.size mustEqual 11
//0
storms.head.loc.x mustEqual 0.4402771f
storms.head.loc.y mustEqual 0.55555797f
storms.head.intensity mustEqual 100
storms.head.radius mustEqual 68
//1
storms(1).loc.x mustEqual 0.3744458f
storms(1).loc.y mustEqual 0.1182538f
storms(1).intensity mustEqual 164
storms(1).radius mustEqual 98
//2
storms(2).loc.x mustEqual 0.6001494f
storms(2).loc.y mustEqual 0.6809498f
storms(2).intensity mustEqual 89
storms(2).radius mustEqual 125
//3
storms(3).loc.x mustEqual 0.53745425f
storms(3).loc.y mustEqual 0.07750241f
storms(3).intensity mustEqual 8
storms(3).radius mustEqual 0
//4
storms(4).loc.x mustEqual 0.44811276f
storms(4).loc.y mustEqual 0.8235843f
storms(4).intensity mustEqual 57
storms(4).radius mustEqual 25
//5
storms(5).loc.x mustEqual 0.90892875f
storms(5).loc.y mustEqual 0.04458676f
storms(5).intensity mustEqual 1
storms(5).radius mustEqual 0
//6
storms(6).loc.x mustEqual 0.813318f
storms(6).loc.y mustEqual 0.7436465f
storms(6).intensity mustEqual 60
storms(6).radius mustEqual 48
//7
storms(7).loc.x mustEqual 0.47319263f
storms(7).loc.y mustEqual 0.27812937f
storms(7).intensity mustEqual 107
storms(7).radius mustEqual 86
//8
storms(8).loc.x mustEqual 0.99670076f
storms(8).loc.y mustEqual 0.4756217f
storms(8).intensity mustEqual 182
storms(8).radius mustEqual 193
//9
storms(9).loc.x mustEqual 0.76002514f
storms(9).loc.y mustEqual 0.9003859f
storms(9).intensity mustEqual 0
storms(9).radius mustEqual 0
//10
storms(10).loc.x mustEqual 0.24905223f
storms(10).loc.y mustEqual 0.40665582f
storms(10).intensity mustEqual 50
storms(10).radius mustEqual 10
case _ =>
ko
}
}
"encode" in {
val msg = WeatherMessage(
CloudInfo(0, Vector3(-2.109881f, -1.1199901f, 0.0f), Vector3(0.89903474f, -0.13332401f, 0.0f)) ::
Nil,
StormInfo(Vector3(0.4402771f, 0.55555797f, 0.0f), 100, 68) ::
StormInfo(Vector3(0.3744458f, 0.1182538f, 0.0f), 164, 98) ::
StormInfo(Vector3(0.6001494f, 0.6809498f, 0.0f), 89, 125) ::
StormInfo(Vector3(0.53745425f, 0.07750241f, 0.0f), 8, 0) ::
StormInfo(Vector3(0.44811276f, 0.8235843f, 0.0f), 57, 25) ::
StormInfo(Vector3(0.90892875f, 0.04458676f, 0.0f),1 ,0) ::
StormInfo(Vector3(0.813318f, 0.7436465f, 0.0f), 60, 48) ::
StormInfo(Vector3(0.47319263f, 0.27812937f, 0.0f), 107, 86) ::
StormInfo(Vector3(0.99670076f, 0.4756217f, 0.0f), 182, 193) ::
StormInfo(Vector3(0.76002514f, 0.9003859f, 0.0f), 0, 0) ::
StormInfo(Vector3(0.24905223f, 0.40665582f, 0.0f), 50, 10) ::
Nil
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
}