From daa22c572e479ea7fe37027e9c022cb53afb3ce9 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Fri, 26 Aug 2016 23:49:51 -0400 Subject: [PATCH] Packet: ZonePopulationUpdateMessage (#65) * added ZonePopulationUpdateMessage packet and tests * updated parameters and comments based on IDA feedback and better testing --- .../psforever/packet/GamePacketOpcode.scala | 2 +- .../game/ZonePopulationUpdateMessage.scala | 71 +++++++++++++++++++ common/src/test/scala/GamePacketTest.scala | 29 ++++++++ .../src/main/scala/WorldSessionActor.scala | 1 + 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 common/src/main/scala/net/psforever/packet/game/ZonePopulationUpdateMessage.scala diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index c34b247f..6e9984e5 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -534,7 +534,7 @@ object GamePacketOpcode extends Enumeration { case 0xb3 => noDecoder(BattleplanMessage) case 0xb4 => noDecoder(BattleExperienceMessage) case 0xb5 => noDecoder(TargetingImplantRequest) - case 0xb6 => noDecoder(ZonePopulationUpdateMessage) + case 0xb6 => game.ZonePopulationUpdateMessage.decode case 0xb7 => noDecoder(DisconnectMessage) // 0xb8 case 0xb8 => noDecoder(ExperienceAddedMessage) diff --git a/common/src/main/scala/net/psforever/packet/game/ZonePopulationUpdateMessage.scala b/common/src/main/scala/net/psforever/packet/game/ZonePopulationUpdateMessage.scala new file mode 100644 index 00000000..7be59000 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/ZonePopulationUpdateMessage.scala @@ -0,0 +1,71 @@ +// Copyright (c) 2016 PSForever.net to present +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} +import scodec.Codec +import scodec.codecs._ + +/** + * Report the raw numerical population for a zone (continent).
+ *
+ * Populations are displayed as percentages of the three main empires against each other. + * Populations specific to a zone will be displayed in the Incentives window for that zone. + * Populations in all zones will contribute to the Global Population window and the Incentives window for the server. + * The Black OPs population does not show up in the Incentives window for a zone but will be indirectly represented in the other two windows. + * This packet also shifts the flavor text for that zone.
+ *
+ * The size of zone's queue is the final upper population limit for that zone. + * Common values for the zone queue fields are 00 00 00 00 (locked) and 9E 01 00 00 (414 positions). + * When a continent can not accept any players at all, a lock icon will appear over its view pane in the Interstellar View. + * Setting the zone's queue to zero will also render this icon.
+ *
+ * The individual queue fields set the maximum empire occupancy for a zone that is represented in the zone Incentives text. + * Common values for the empire queue fields are 00 00 00 00 (locked population), 8A 00 00 00 (138 positions), and FA 01 00 00 (500 positions). + * Zone Incentives text, however, will never report more than a "100+" vacancy. + * The actual limits are probably set based on server load. + * The latter queue value is typical for VR area zones.
+ *
+ * The value of the zone queue trumps the sum of all individual empire queues. + * Regardless of individual queues, once total zone population matches the zone queue size, all populations will lock. + * For normal zones, if the individual queues are not set properly, whole empires can even be locked out of a zone for this reason. + * In the worst case, other empires are allowed enough individual queue vacancy that they can occupy all the available slots. + * Sanctuary zones possess strange queue values that are occasionally zero'd. + * They do not have a lock icon and may not limit populations the same way as normal zones. + * + * @param continent_guid identifies the zone (continent) + * @param zone_queue the maximum population of all three (four) empires that can join this zone + * @param tr_queue the maximum number of TR players that can join this zone + * @param tr_pop the current TR population in this zone + * @param nc_queue the maximum number of NC players that can join this zone + * @param nc_pop the current NC population in this zone + * @param vs_queue the maximum number of VS players that can join this zone + * @param vs_pop the VS population in this zone + * @param bo_queue the maximum number of Black OPs players that can join this zone + * @param bo_pop the current Black OPs population in this zone + */ +final case class ZonePopulationUpdateMessage(continent_guid : PlanetSideGUID, + zone_queue : Long, + tr_queue : Long, + tr_pop : Long, + nc_queue : Long, + nc_pop : Long, + vs_queue : Long, + vs_pop : Long, + bo_queue : Long, + bo_pop : Long) + extends PlanetSideGamePacket { + type Packet = ZonePopulationUpdateMessage + def opcode = GamePacketOpcode.ZonePopulationUpdateMessage + def encode = ZonePopulationUpdateMessage.encode(this) +} + +object ZonePopulationUpdateMessage extends Marshallable[ZonePopulationUpdateMessage] { + implicit val codec : Codec[ZonePopulationUpdateMessage] = ( + ("continent_guid" | PlanetSideGUID.codec) :: + ("zone_queue" | ulongL(32)) :: + ("tr_queue" | ulongL(32)) :: ("tr_pop" | ulongL(32)) :: + ("nc_queue" | ulongL(32)) :: ("nc_pop" | ulongL(32)) :: + ("vs_queue" | ulongL(32)) :: ("vs_pop" | ulongL(32)) :: + ("bo_queue" | ulongL(32)) :: ("bo_pop" | ulongL(32)) + ).as[ZonePopulationUpdateMessage] +} diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala index 6ecdaf87..2f7e8a11 100644 --- a/common/src/test/scala/GamePacketTest.scala +++ b/common/src/test/scala/GamePacketTest.scala @@ -703,6 +703,35 @@ class GamePacketTest extends Specification { } } + "ZonePopulationUpdateMessage" should { + val string = hex"B6 0400 9E010000 8A000000 25000000 8A000000 25000000 8A000000 25000000 8A000000 25000000" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case ZonePopulationUpdateMessage(continent_guid, zone_queue, tr_queue, tr_pop, nc_queue, nc_pop, vs_queue, vs_pop, bo_queue, bo_pop) => + continent_guid mustEqual PlanetSideGUID(4) + zone_queue mustEqual 414 + tr_queue mustEqual 138 + tr_pop mustEqual 37 + nc_queue mustEqual 138 + nc_pop mustEqual 37 + vs_queue mustEqual 138 + vs_pop mustEqual 37 + bo_queue mustEqual 138 + bo_pop mustEqual 37 + case default => + ko + } + } + + "encode" in { + val msg = ZonePopulationUpdateMessage(PlanetSideGUID(4), 414, 138, 37, 138, 37, 138, 37, 138, 37) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } + } + "WeaponFireMessage" should { val string = hex"34 44130029272F0B5DFD4D4EC5C00009BEF78172003FC0" diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index ac792f4b..ea99a03c 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -138,6 +138,7 @@ class WorldSessionActor extends Actor with MDCContextAware { // 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, ZonePopulationUpdateMessage(PlanetSideGUID(13), 414, 138, 0, 138, 0, 138, 0, 138, 0))) sendRawResponse(objectHex) // These object_guids are specfic to VS Sanc