diff --git a/common/src/main/scala/net/psforever/packet/game/HotSpotUpdateMessage.scala b/common/src/main/scala/net/psforever/packet/game/HotSpotUpdateMessage.scala
index 9309f3ab7..f3dc760dd 100644
--- a/common/src/main/scala/net/psforever/packet/game/HotSpotUpdateMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/HotSpotUpdateMessage.scala
@@ -6,25 +6,43 @@ import scodec.Codec
import scodec.codecs._
/**
- * na
- * @param unk na
+ * Information for positioning a hotspot on the continental map.
+ *
+ * The coordinate values and the scaling value have different endianness than most numbers transmitted as packet data.
+ * The two unknown values are not part of the positioning system, at least not a part of the coordinates.
+ *
+ * The origin point is the lowest left corner of the map grid.
+ * On either axis, the other edge of the map is found at the maximum value 4096 (`FFF`).
+ * The scale is typically set as 128 (`80000`) but can also be made smaller or made absurdly big.
+ * @param unk1 na
* @param x the x-coord of the center of the hotspot
+ * @param unk2 na
* @param y the y-coord of the center of the hotspot
- * @param scale the scaling of the hotspot graphic
+ * @param scale the scaling of the hotspot icon
*/
-final case class HotSpotInfo(unk : Int,
+final case class HotSpotInfo(unk1 : Int,
x : Int,
+ unk2 : Int,
y : Int,
scale : Int)
/**
- * na
+ * A list of data for creating hotspots on a continental map.
+ *
+ * The hotspot system is a forgetful all-or-nothing affair.
+ * The packet that is always initially sent during server login clears any would-be hotspots from the map.
+ * Each time a hotspot packet is received for a zone, all of the previous hotspots for that zone are forgotten.
+ * To simply add a hotspot, the next packet has to contain information that re-explains the packets that were originally rendered.
+ *
+ * Exploration 1:
+ * The unknown parameter has been observed with various non-zero values such as 1, 2, and 5.
+ * Visually, however, `unk` does not affect anything.
+ * Does it do something internally?
* @param continent_guid the zone (continent)
* @param unk na
- * @param spots a list of HotSpotInfo, or Nil if empty
+ * @param spots a List of HotSpotInfo, or `Nil` if empty
*/
-// TODO test for size > 0 (e.g., > hex'00')
-// TODO test for size > 15 (e.g., > hex'F0')
+// TODO need aligned/padded list support
final case class HotSpotUpdateMessage(continent_guid : PlanetSideGUID,
unk : Int,
spots : List[HotSpotInfo] = Nil)
@@ -36,15 +54,16 @@ final case class HotSpotUpdateMessage(continent_guid : PlanetSideGUID,
object HotSpotUpdateMessage extends Marshallable[HotSpotUpdateMessage] {
implicit val hotspot_codec : Codec[HotSpotInfo] = {
- ("unk" | uint8L) ::
- ("x" | uint16L) ::
- ("y" | uint16L) ::
- ("scale" | uintL(20))
+ ("unk1" | uint8) ::
+ ("x" | uint(12)) ::
+ ("unk2" | uint8) ::
+ ("y" | uint(12)) ::
+ ("scale" | uint(20))
}.as[HotSpotInfo]
implicit val codec : Codec[HotSpotUpdateMessage] = (
("continent_guid" | PlanetSideGUID.codec) ::
- ("unk" | uint8L) ::
+ ("unk" | uint4L) ::
("spots" | listOfN(uint8L, hotspot_codec))
).as[HotSpotUpdateMessage]
}
diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala
index 0f88abdfe..288c42cfe 100644
--- a/common/src/test/scala/GamePacketTest.scala
+++ b/common/src/test/scala/GamePacketTest.scala
@@ -916,13 +916,14 @@ class GamePacketTest extends Specification {
}
"HotSpotUpdateMessage" should {
- val stringClear = hex"9F 0500 10 00"
+ val stringClear = hex"9F 0500 1 00 0"
+ val stringOne = hex"9F 0500 1 01 0 00 2E9 00 145 80000 0"
"decode (clear)" in {
PacketCoding.DecodePacket(stringClear).require match {
case HotSpotUpdateMessage(continent_guid, unk, spots) =>
continent_guid mustEqual PlanetSideGUID(5)
- unk mustEqual 16
+ unk mustEqual 1
spots.size mustEqual 0
case _ =>
ko
@@ -930,7 +931,7 @@ class GamePacketTest extends Specification {
}
"encode (clear)" in {
- val msg = HotSpotUpdateMessage(PlanetSideGUID(5),16)
+ val msg = HotSpotUpdateMessage(PlanetSideGUID(5),1)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringClear
}