diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index 2ac15cec..46c4b1ec 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -511,7 +511,7 @@ object GamePacketOpcode extends Enumeration { case HotSpotUpdateMessage => noDecoder(opcode) // OPCODE 160 - case BuildingInfoUpdateMessage => noDecoder(opcode) + case BuildingInfoUpdateMessage => game.BuildingInfoUpdateMessage.decode case FireHintMessage => noDecoder(opcode) case UplinkRequest => noDecoder(opcode) case UplinkResponse => noDecoder(opcode) diff --git a/common/src/main/scala/net/psforever/packet/game/BuildingInfoUpdateMessage.scala b/common/src/main/scala/net/psforever/packet/game/BuildingInfoUpdateMessage.scala new file mode 100644 index 00000000..11510128 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/BuildingInfoUpdateMessage.scala @@ -0,0 +1,73 @@ +// Copyright (c) 2016 PSForever.net to present +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import scodec.Codec +import scodec.codecs._ +import scodec.bits._ + +object PlanetSideGeneratorState extends Enumeration { + type Type = Value + val Normal, + Critical, + Destroyed, + Unk3 + = Value + + implicit val codec = PacketHelpers.createEnumerationCodec(this, uintL(2)) +} + +/** + * BuildingInfoUpdateMessage is sent for all bases, towers, and warpgates from all continents upon login. + */ +final case class BuildingInfoUpdateMessage(continent_guid : PlanetSideGUID, + building_guid : PlanetSideGUID, + ntu_level : Int, + is_hacked : Boolean, + empire_hack : PlanetSideEmpire.Value, + hack_time_remaining : Long, + empire_own : PlanetSideEmpire.Value, + unk1 : Long, + generator_state : PlanetSideGeneratorState.Value, + spawn_tubes_normal : Boolean, + force_dome_active : Boolean, + lattice_benefit : Int, + unk3 : Int, + unk4 : Int, + unk5 : Long, + unk6 : Boolean, + unk7 : Int, + boost_spawn_pain : Boolean, + boost_generator_pain : Boolean) + extends PlanetSideGamePacket { + type Packet = BuildingInfoUpdateMessage + def opcode = GamePacketOpcode.BuildingInfoUpdateMessage + def encode = BuildingInfoUpdateMessage.encode(this) +} + +object BuildingInfoUpdateMessage extends Marshallable[BuildingInfoUpdateMessage] { + implicit val codec : Codec[BuildingInfoUpdateMessage] = ( + ("continent_guid" | PlanetSideGUID.codec) :: + ("building_guid" | PlanetSideGUID.codec) :: + ("ntu_level" | uint4L) :: + ("is_hacked" | bool ) :: + ("empire_hack" | PlanetSideEmpire.codec) :: + ("hack_time_remaining" | uint32L ) :: //In milliseconds + ("empire_own" | PlanetSideEmpire.codec) :: + ("unk1" | uint32L) :: //TODO: string, uint16L, and uint32L follow if unk1 != 0 + + ("generator_state" | PlanetSideGeneratorState.codec) :: + ("spawn_tubes_normal" | bool) :: + ("force_dome_active" | bool) :: + ("lattice_benefit" | uintL(5)) :: //5 possible benefits, bitwise combination. (MSB)5:Tech 4:Inter 3:Bio 2:Drop 1:Amp(LSB) + ("unk3" | uintL(10)) :: //Module related. 0x3FF gives all modules with no timer. Unclear how this works. + ("unk4" | uint4L) :: //TODO: additional fields if unk4 > 0 + + ("unk5" | uint32L) :: + ("unk6" | bool) :: + ("unk7" | uint4L) :: //TODO: bool and uintL(2) follow if unk7 != 8 + + ("boost_spawn_pain" | bool) :: + ("boost_generator_pain" | bool) + ).as[BuildingInfoUpdateMessage] +} diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala index e1b47823..fc47ab15 100644 --- a/common/src/test/scala/GamePacketTest.scala +++ b/common/src/test/scala/GamePacketTest.scala @@ -861,6 +861,79 @@ class GamePacketTest extends Specification { } } + "BuildingInfoUpdateMessage" should { + val string = hex"a0 04 00 09 00 16 00 00 00 00 80 00 00 00 17 00 00 00 00 00 00 40" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case BuildingInfoUpdateMessage(continent_guid : PlanetSideGUID, + building_guid : PlanetSideGUID, + ntu_level : Int, + is_hacked : Boolean, + empire_hack : PlanetSideEmpire.Value, + hack_time_remaining : Long, + empire_own : PlanetSideEmpire.Value, + unk1 : Long, + generator_state : PlanetSideGeneratorState.Value, + spawn_tubes_normal : Boolean, + force_dome_active : Boolean, + lattice_benefit : Int, + unk3 : Int, + unk4 : Int, + unk5 : Long, + unk6 : Boolean, + unk7 : Int, + boost_spawn_pain : Boolean, + boost_generator_pain : Boolean) => + continent_guid mustEqual PlanetSideGUID(4) + building_guid mustEqual PlanetSideGUID(9) + ntu_level mustEqual 1 + is_hacked mustEqual false + empire_hack mustEqual PlanetSideEmpire.NEUTRAL + hack_time_remaining mustEqual 0 + empire_own mustEqual PlanetSideEmpire.NC + unk1 mustEqual 0 + generator_state mustEqual PlanetSideGeneratorState.Normal + spawn_tubes_normal mustEqual true + force_dome_active mustEqual false + lattice_benefit mustEqual 28 + unk3 mustEqual 0 + unk4 mustEqual 0 + unk5 mustEqual 0 + unk6 mustEqual false + unk7 mustEqual 8 + boost_spawn_pain mustEqual false + boost_generator_pain mustEqual false + case default => + ko + } + } + + "encode" in { + val msg = BuildingInfoUpdateMessage(PlanetSideGUID(4), + PlanetSideGUID(9), + 1, + false, + PlanetSideEmpire.NEUTRAL, + 0, + PlanetSideEmpire.NC, + 0, + PlanetSideGeneratorState.Normal, + true, + false, + 28, + 0, + 0, + 0, + false, + 8, + false, + false) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } + } "QuantityUpdateMessage" should { val string = hex"3D 5300 7B000000" diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 1a9a21f3..d6b99bef 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -147,6 +147,27 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, ContinentalLockUpdateMessage(PlanetSideGUID(13), PlanetSideEmpire.VS))) // "The VS have captured the VS Sanctuary." sendResponse(PacketCoding.CreateGamePacket(0, BroadcastWarpgateUpdateMessage(PlanetSideGUID(13), PlanetSideGUID(1), 32))) // VS Sanctuary: Inactive Warpgate -> Broadcast Warpgate + sendResponse(PacketCoding.CreateGamePacket(0,BuildingInfoUpdateMessage( + PlanetSideGUID(6), //Ceryshen + PlanetSideGUID(2), //Anguta + 8, //80% NTU + true, //Base hacked + PlanetSideEmpire.NC, //Base hacked by NC + 600000, //10 minutes remaining for hack + PlanetSideEmpire.VS, //Base owned by VS + 0, //!! Field != 0 will cause malformed packet. See class def. + PlanetSideGeneratorState.Critical, //Generator critical + true, //Respawn tubes destroyed + true, //Force dome active + 16, //Tech plant lattice benefit + 0, + 0, //!! Field > 0 will cause malformed packet. See class def. + 0, + false, + 8, //!! Field != 8 will cause malformed packet. See class def. + true, //Boosted spawn room pain field + true))) //Boosted generator room pain field + sendResponse(PacketCoding.CreateGamePacket(0, SetCurrentAvatarMessage(PlanetSideGUID(guid),0,0))) import scala.concurrent.duration._