diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala new file mode 100644 index 000000000..784685377 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala @@ -0,0 +1,22 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.structures + +import akka.actor.ActorContext +import net.psforever.objects.zones.Zone + +class WarpGate(id : Int, zone : Zone) extends Building(id, zone) { + //TODO stuff later +} + +object WarpGate { + def apply(id : Int, zone : Zone) : WarpGate = { + new WarpGate(id, zone) + } + + def Structure(id : Int, zone : Zone, context : ActorContext) : WarpGate = { + import akka.actor.Props + val obj = new WarpGate(id, zone) + obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-gate") + obj + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ImplantTerminalInterfaceDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ImplantTerminalInterfaceDefinition.scala index 1549d76f3..51c62e4ea 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ImplantTerminalInterfaceDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ImplantTerminalInterfaceDefinition.scala @@ -16,7 +16,7 @@ import net.psforever.packet.game.objectcreate.ObjectClass */ class ImplantTerminalInterfaceDefinition extends TerminalDefinition(ObjectClass.implant_terminal_interface) { Packet = new ImplantTerminalInterfaceConverter - Name = "implante_terminal_interface" + Name = "implant_terminal_interface" private val implants : Map[String, ImplantDefinition] = Map ( "advanced_regen" -> GlobalDefinitions.advanced_regen, diff --git a/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala b/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala index e09d55c27..ddd70cca2 100644 --- a/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala +++ b/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala @@ -50,9 +50,7 @@ class InterstellarCluster(zones : List[Zone]) extends Actor { } case InterstellarCluster.RequestClientInitialization(tplayer) => - zones.foreach(zone => { - sender ! Zone.ClientInitialization(zone.ClientInitialization()) //do this for each Zone - }) + zones.foreach(zone => { sender ! Zone.ClientInitialization(zone.ClientInitialization()) }) sender ! InterstellarCluster.ClientInitializationComplete(tplayer) //will be processed after all Zones case _ => ; diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala index 13e055048..f084443df 100644 --- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -10,7 +10,6 @@ import net.psforever.objects.guid.actor.UniqueNumberSystem import net.psforever.objects.guid.selector.RandomSelector import net.psforever.objects.guid.source.LimitedNumberSource import net.psforever.objects.serverobject.structures.{Amenity, Building} -import net.psforever.packet.GamePacket import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.Vector3 @@ -218,6 +217,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { def Transport : ActorRef = transport + def Buildings : Map[Int, Building] = buildings + def Building(id : Int) : Option[Building] = { buildings.get(id) } @@ -250,25 +251,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { * - `ZonePopulationUpdateMessage` * @return a `List` of `GamePacket` messages */ - def ClientInitialization() : List[GamePacket] = { - //TODO unimplemented - List.empty[GamePacket] - } - - /** - * Provide bulk correspondence on all server objects that can be composed into packet messages and reported to a client. - * These messages are sent in this fashion at the time of joining a specific `Zone`:
- * - `HackMessage`
- * - `PlanetsideAttributeMessage`
- * - `SetEmpireMessage`
- * - `TimeOfDayMessage`
- * - `WeatherMessage` - * @return a `List` of `GamePacket` messages - */ - def ClientConfiguration() : List[GamePacket] = { - //TODO unimplemented - List.empty[GamePacket] - } + def ClientInitialization() : Zone = this } object Zone { @@ -308,11 +291,11 @@ object Zone { /** * Message to report the packet messages that initialize the client. - * @param list a `List` of `GamePacket` messages + * @param zone a `Zone` to have its buildings and continental parameters turned into packet data * @see `Zone.ClientInitialization()`
* `InterstallarCluster` */ - final case class ClientInitialization(list : List[GamePacket]) + final case class ClientInitialization(zone : Zone) /** * Overloaded constructor. diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index 21ae82f69..1c1185470 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -436,8 +436,8 @@ object GamePacketOpcode extends Enumeration { case 0x60 => game.FavoritesMessage.decode case 0x61 => game.ObjectDetectedMessage.decode case 0x62 => game.SplashHitMessage.decode - case 0x63 => noDecoder(SetChatFilterMessage) - case 0x64 => noDecoder(AvatarSearchCriteriaMessage) + case 0x63 => game.SetChatFilterMessage.decode + case 0x64 => game.AvatarSearchCriteriaMessage.decode case 0x65 => noDecoder(AvatarSearchResponse) case 0x66 => game.WeaponJammedMessage.decode case 0x67 => noDecoder(LinkDeadAwarenessMsg) @@ -468,7 +468,7 @@ object GamePacketOpcode extends Enumeration { case 0x7c => game.DismountBuildingMsg.decode case 0x7d => noDecoder(UnknownMessage125) case 0x7e => noDecoder(UnknownMessage126) - case 0x7f => noDecoder(AvatarStatisticsMessage) + case 0x7f => game.AvatarStatisticsMessage.decode // OPCODES 0x80-8f case 0x80 => noDecoder(GenericObjectAction2Message) @@ -561,7 +561,7 @@ object GamePacketOpcode extends Enumeration { case 0xca => noDecoder(OutfitBenefitMessage) case 0xcb => noDecoder(EmpireChangeTimeMessage) case 0xcc => noDecoder(ClockCalibrationMessage) - case 0xcd => noDecoder(DensityLevelUpdateMessage) + case 0xcd => game.DensityLevelUpdateMessage.decode case 0xce => noDecoder(ActOfGodMessage) case 0xcf => noDecoder(AvatarAwardMessage) diff --git a/common/src/main/scala/net/psforever/packet/game/AvatarImplantMessage.scala b/common/src/main/scala/net/psforever/packet/game/AvatarImplantMessage.scala index 8369a6425..4505578f9 100644 --- a/common/src/main/scala/net/psforever/packet/game/AvatarImplantMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/AvatarImplantMessage.scala @@ -1,40 +1,48 @@ // Copyright (c) 2017 PSForever package net.psforever.packet.game -import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} -import net.psforever.types.ImplantType +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} import scodec.Codec import scodec.codecs._ /** - * Change the state of the implant.
+ * An `Enumeration` for all the actions that can be applied to implants and implant slots. + */ +object ImplantAction extends Enumeration { + type Type = Value + + val + Add, + Remove, + Initialization, + Activation, + UnlockMessage, + OutOfStamina + = Value + + implicit val codec = PacketHelpers.createEnumerationCodec(this, uintL(3)) +} + +/** + * Change the state of the implant. + * Spawn messages for certain implant-related events.
*
* The implant Second Wind is technically an invalid `ImplantType` for this packet. * This owes to the unique activation trigger for that implant - a near-death experience of ~0HP. + * @see `ImplantType` * @param player_guid the player - * @param action - * 0 : add implant - * with status = 0 to 9 (from ImplantType) - * 1 : remove implant - * seems work with any value in status - * 2 : init implant - * status : 0 to "uninit" - * status : 1 to init - * 3 : activate implant - * status : 0 to desactivate - * status : 1 to activate - * 4 : number of implant slots unlocked - * status : 0 = no implant slot - * status : 1 = first implant slot + "implant message" - * status : 2 or 3 = unlock second & third slots - * 5 : out of stamina message - * status : 0 to stop the lock - * status : 1 to active the lock + * @param action how to affect the implant or the slot * @param implantSlot : from 0 to 2 - * @param status : see action + * @param status : a value that depends on context from `ImplantAction`:
+ * `Add` - 0-9 depending on the `ImplantType`
+ * `Remove` - any valid value; field is not significant to this action
+ * `Initialization` - 0 to revoke slot; 1 to allocate implant slot
+ * `Activation` - 0 to deactivate implant; 1 to activate implant
+ * `UnlockMessage` - 0-3 as an unlocked implant slot; display a message
+ * `OutOfStamina` - lock implant; 0 to lock; 1 to unlock; display a message */ final case class AvatarImplantMessage(player_guid : PlanetSideGUID, - action : Int, + action : ImplantAction.Value, implantSlot : Int, status : Int) extends PlanetSideGamePacket { @@ -46,7 +54,7 @@ final case class AvatarImplantMessage(player_guid : PlanetSideGUID, object AvatarImplantMessage extends Marshallable[AvatarImplantMessage] { implicit val codec : Codec[AvatarImplantMessage] = ( ("player_guid" | PlanetSideGUID.codec) :: - ("action" | uintL(3)) :: + ("action" | ImplantAction.codec) :: ("implantSlot" | uint2L) :: ("status" | uint4L) ).as[AvatarImplantMessage] diff --git a/common/src/main/scala/net/psforever/packet/game/AvatarSearchCriteriaMessage.scala b/common/src/main/scala/net/psforever/packet/game/AvatarSearchCriteriaMessage.scala new file mode 100644 index 000000000..b6ed5c526 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/AvatarSearchCriteriaMessage.scala @@ -0,0 +1,44 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import scodec.{Attempt, Codec, Err} +import scodec.codecs._ +import shapeless.{::, HNil} + +/** + * na + * @param unk1 na + * @param unk2 na + */ +final case class AvatarSearchCriteriaMessage(unk1 : PlanetSideGUID, + unk2 : List[Int]) + extends PlanetSideGamePacket { + type Packet = AvatarSearchCriteriaMessage + def opcode = GamePacketOpcode.AvatarSearchCriteriaMessage + def encode = AvatarSearchCriteriaMessage.encode(this) +} + +object AvatarSearchCriteriaMessage extends Marshallable[AvatarSearchCriteriaMessage] { + implicit val codec : Codec[AvatarSearchCriteriaMessage] = ( + ("unk1" | PlanetSideGUID.codec) :: + ("unk2" | PacketHelpers.listOfNSized(6, uint8L)) + ).exmap[AvatarSearchCriteriaMessage] ( + { + case a :: b :: HNil => + Attempt.Successful(AvatarSearchCriteriaMessage(a, b)) + }, + { + case AvatarSearchCriteriaMessage(a, b) => + if(b.length != 6) { + Attempt.Failure(Err("list must have 6 entries")) + } + else if(b.count(i => { i < 0 || i > 255 }) > 0) { + Attempt.Failure(Err("list entries must be 0-255 inclusive")) + } + else { + Attempt.Successful(a :: b :: HNil) + } + } + ) +} diff --git a/common/src/main/scala/net/psforever/packet/game/AvatarStatisticsMessage.scala b/common/src/main/scala/net/psforever/packet/game/AvatarStatisticsMessage.scala new file mode 100644 index 000000000..9b510f629 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/AvatarStatisticsMessage.scala @@ -0,0 +1,105 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import scodec.{Attempt, Codec, Err} +import scodec.codecs._ +import shapeless.{::, HNil} + +import scala.annotation.switch + +/** + * na + * @param unk1 na + * @param unk2 na + * @param unk3 na + */ +final case class Statistics(unk1 : Option[Int], + unk2 : Option[Int], + unk3 : List[Long]) + +/** + * na + * @param unk na + * @param stats na + */ +final case class AvatarStatisticsMessage(unk : Int, + stats : Statistics) + extends PlanetSideGamePacket { + type Packet = AvatarStatisticsMessage + def opcode = GamePacketOpcode.AvatarStatisticsMessage + def encode = AvatarStatisticsMessage.encode(this) +} + +object AvatarStatisticsMessage extends Marshallable[AvatarStatisticsMessage] { + /** + * na + */ + private val longCodec : Codec[Statistics] = ulong(32).hlist.exmap ( + { + case n :: HNil => + Attempt.Successful(Statistics(None,None, List(n))) + }, + { + case Statistics(_, _, Nil) => + Attempt.Failure(Err("missing value (32-bit)")) + + case Statistics(_, _, n) => + Attempt.Successful(n.head :: HNil) + } + ) + + /** + * na + */ + private val complexCodec : Codec[Statistics] = ( + uint(5) :: + uintL(11) :: + PacketHelpers.listOfNSized(8, uint32L) + ).exmap[Statistics] ( + { + case a :: b :: c :: HNil => + Attempt.Successful(Statistics(Some(a), Some(b), c)) + }, + { + case Statistics(None, _, _) => + Attempt.Failure(Err("missing value (5-bit)")) + + case Statistics(_, None, _) => + Attempt.Failure(Err("missing value (11-bit)")) + + case Statistics(a, b, c) => + if(c.length != 8) { + Attempt.Failure(Err("list must have 8 entries")) + } + else { + Attempt.Successful(a.get :: b.get :: c :: HNil) + } + } + ) + + /** + * na + * @param n na + * @return na + */ + private def selectCodec(n : Int) : Codec[Statistics] = (n : @switch) match { + case 2 | 3 => + longCodec + case _ => + complexCodec + } + + implicit val codec : Codec[AvatarStatisticsMessage] = ( + ("unk" | uint(3)) >>:~ { unk => + ("stats" | selectCodec(unk)).hlist + }).as[AvatarStatisticsMessage] +} + +object Statistics { + def apply(unk : Long) : Statistics = + Statistics(None, None, List(unk)) + + def apply(unk1 : Int, unk2 : Int, unk3 : List[Long]) : Statistics = + Statistics(Some(unk1), Some(unk2), unk3) +} diff --git a/common/src/main/scala/net/psforever/packet/game/BroadcastWarpgateUpdateMessage.scala b/common/src/main/scala/net/psforever/packet/game/BroadcastWarpgateUpdateMessage.scala index d786560b3..89dac67f2 100644 --- a/common/src/main/scala/net/psforever/packet/game/BroadcastWarpgateUpdateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/BroadcastWarpgateUpdateMessage.scala @@ -17,17 +17,17 @@ import scodec.codecs._ * I believe these `Boolean` values actually indicate some measure of warpgate operation. * Geowarps, for example, though their appearance does not change, recieve this packet. * Moreover, they can operate as a receiving-end broadcast gate. - * @param continent_guid identifies the zone (continent) - * @param building_guid identifies the warpgate (see `BuildingInfoUpdateMessage`) + * @param continent_id the zone + * @param building_id the warp gate (see `BuildingInfoUpdateMessage`) * @param unk1 na * @param unk2 na - * @param is_broadcast if true, the gate replaces its destination text with "Broadcast" + * @param broadcast if true, the gate replaces its destination text with "Broadcast" */ -final case class BroadcastWarpgateUpdateMessage(continent_guid : PlanetSideGUID, - building_guid : PlanetSideGUID, +final case class BroadcastWarpgateUpdateMessage(continent_id : Int, + building_id : Int, unk1 : Boolean, unk2 : Boolean, - is_broadcast : Boolean) + broadcast : Boolean) extends PlanetSideGamePacket { type Packet = BroadcastWarpgateUpdateMessage def opcode = GamePacketOpcode.BroadcastWarpgateUpdateMessage @@ -36,10 +36,10 @@ final case class BroadcastWarpgateUpdateMessage(continent_guid : PlanetSideGUID, object BroadcastWarpgateUpdateMessage extends Marshallable[BroadcastWarpgateUpdateMessage] { implicit val codec : Codec[BroadcastWarpgateUpdateMessage] = ( - ("continent_guid" | PlanetSideGUID.codec) :: - ("building_guid" | PlanetSideGUID.codec) :: + ("continent_id" | uint16L) :: + ("building_id" | uint16L) :: ("unk1" | bool) :: ("unk2" | bool) :: - ("is_broadcast" | bool) + ("broadcast" | bool) ).as[BroadcastWarpgateUpdateMessage] } diff --git a/common/src/main/scala/net/psforever/packet/game/BuildingInfoUpdateMessage.scala b/common/src/main/scala/net/psforever/packet/game/BuildingInfoUpdateMessage.scala index 9887e4093..753a0a72a 100644 --- a/common/src/main/scala/net/psforever/packet/game/BuildingInfoUpdateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/BuildingInfoUpdateMessage.scala @@ -87,8 +87,8 @@ final case class Additional3(unk1 : Boolean, * 064 - Health Module
* 128 - Pain Module
* ` - * @param continent_guid the continent (zone) - * @param building_guid the building + * @param continent_id the continent (zone) + * @param building_id the building * @param ntu_level if the building has a silo, the amount of NTU in that silo; * NTU is reported in multiples of 10%; * valid for 0 (0%) to 10 (100%) @@ -117,8 +117,8 @@ final case class Additional3(unk1 : Boolean, * @param boost_spawn_pain if the building has spawn tubes, the (boosted) strength of its enemy pain field * @param boost_generator_pain if the building has a generator, the (boosted) strength of its enemy pain field */ -final case class BuildingInfoUpdateMessage(continent_guid : PlanetSideGUID, - building_guid : PlanetSideGUID, +final case class BuildingInfoUpdateMessage(continent_id : Int, + building_id : Int, ntu_level : Int, is_hacked : Boolean, empire_hack : PlanetSideEmpire.Value, @@ -171,8 +171,8 @@ object BuildingInfoUpdateMessage extends Marshallable[BuildingInfoUpdateMessage] ).as[Additional3] implicit val codec : Codec[BuildingInfoUpdateMessage] = ( - ("continent_guid" | PlanetSideGUID.codec) :: - ("building_guid" | PlanetSideGUID.codec) :: + ("continent_id" | uint16L) :: + ("building_id" | uint16L) :: ("ntu_level" | uint4L) :: ("is_hacked" | bool ) :: ("empire_hack" | PlanetSideEmpire.codec) :: diff --git a/common/src/main/scala/net/psforever/packet/game/ContinentalLockUpdateMessage.scala b/common/src/main/scala/net/psforever/packet/game/ContinentalLockUpdateMessage.scala index 1aa707084..204db94c0 100644 --- a/common/src/main/scala/net/psforever/packet/game/ContinentalLockUpdateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/ContinentalLockUpdateMessage.scala @@ -12,10 +12,10 @@ import scodec.codecs._ * This generates the event message "The [empire] have captured [continent]." * If the continent_guid is not a valid zone, no message is displayed. * If empire is not a valid empire, or refers to the neutral or Black Ops forces, no message is displayed. - * @param continent_guid identifies the zone (continent) + * @param continent_id identifies the zone (continent) * @param empire identifies the empire */ -final case class ContinentalLockUpdateMessage(continent_guid : PlanetSideGUID, +final case class ContinentalLockUpdateMessage(continent_id : Int, empire : PlanetSideEmpire.Value) extends PlanetSideGamePacket { type Packet = ContinentalLockUpdateMessage @@ -25,7 +25,7 @@ final case class ContinentalLockUpdateMessage(continent_guid : PlanetSideGUID, object ContinentalLockUpdateMessage extends Marshallable[ContinentalLockUpdateMessage] { implicit val codec : Codec[ContinentalLockUpdateMessage] = ( - ("continent_guid" | PlanetSideGUID.codec) :: + ("continent_id" | uint16L) :: ("empire" | PlanetSideEmpire.codec) ).as[ContinentalLockUpdateMessage] } diff --git a/common/src/main/scala/net/psforever/packet/game/DelayedPathMountMsg.scala b/common/src/main/scala/net/psforever/packet/game/DelayedPathMountMsg.scala index 4c713e096..643898e6e 100644 --- a/common/src/main/scala/net/psforever/packet/game/DelayedPathMountMsg.scala +++ b/common/src/main/scala/net/psforever/packet/game/DelayedPathMountMsg.scala @@ -1,4 +1,4 @@ -// Copyright (c) 2016 PSForever.net to present +// Copyright (c) 2017 PSForever package net.psforever.packet.game import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} diff --git a/common/src/main/scala/net/psforever/packet/game/DensityLevelUpdateMessage.scala b/common/src/main/scala/net/psforever/packet/game/DensityLevelUpdateMessage.scala new file mode 100644 index 000000000..957367aeb --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/DensityLevelUpdateMessage.scala @@ -0,0 +1,47 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import scodec.{Attempt, Codec, Err} +import scodec.codecs._ +import shapeless.{::, HNil} + +/** + * na + * @param zone_id the continent + * @param building_id the building + * @param density na + */ +final case class DensityLevelUpdateMessage(zone_id : Int, + building_id : Int, + density : List[Int]) + extends PlanetSideGamePacket { + type Packet = DensityLevelUpdateMessage + def opcode = GamePacketOpcode.DensityLevelUpdateMessage + def encode = DensityLevelUpdateMessage.encode(this) +} + +object DensityLevelUpdateMessage extends Marshallable[DensityLevelUpdateMessage] { + implicit val codec : Codec[DensityLevelUpdateMessage] = ( + ("zone_id" | uint16L) :: + ("building_id" | uint16L) :: + ("density" | PacketHelpers.listOfNSized(8, uint(3))) + ).exmap[DensityLevelUpdateMessage] ( + { + case a :: b :: c :: HNil => + Attempt.Successful(DensityLevelUpdateMessage(a, b, c)) + }, + { + case DensityLevelUpdateMessage(a, b, c) => + if(c.length != 8) { + Attempt.Failure(Err("list must have 8 entries")) + } + else if(c.count(i => { i < 0 || i > 7 }) > 0) { + Attempt.Failure(Err("list entries must be 0-7 inclusive")) + } + else { + Attempt.Successful(a :: b :: c :: HNil) + } + } + ) +} diff --git a/common/src/main/scala/net/psforever/packet/game/FriendsResponse.scala b/common/src/main/scala/net/psforever/packet/game/FriendsResponse.scala index 49c1f215a..8eb97abe5 100644 --- a/common/src/main/scala/net/psforever/packet/game/FriendsResponse.scala +++ b/common/src/main/scala/net/psforever/packet/game/FriendsResponse.scala @@ -6,6 +6,22 @@ import scodec.Codec import scodec.codecs._ import shapeless.{::, HNil} +object FriendAction extends Enumeration { + type Type = Value + + val + InitializeFriendList, + AddFriend, + RemoveFriend, + UpdateFriend, + InitializeIgnoreList, + AddIgnoredPlayer, + RemoveIgnoredPlayer + = Value + + implicit val codec = PacketHelpers.createEnumerationCodec(this, uint(3)) +} + /** * An entry in the list of players known to and tracked by this player. * They're called "friends" even though they can be used for a list of ignored players as well. @@ -37,7 +53,7 @@ final case class Friend(name : String, * @param unk3 na; always `true`? * @param friends a list of `Friend`s */ -final case class FriendsResponse(action : Int, +final case class FriendsResponse(action : FriendAction.Value, unk1 : Int, unk2 : Boolean, unk3 : Boolean, @@ -66,7 +82,7 @@ object Friend extends Marshallable[Friend] { object FriendsResponse extends Marshallable[FriendsResponse] { implicit val codec : Codec[FriendsResponse] = ( - ("action" | uintL(3)) :: + ("action" | FriendAction.codec) :: ("unk1" | uint4L) :: ("unk2" | bool) :: ("unk3" | bool) :: @@ -76,8 +92,8 @@ object FriendsResponse extends Marshallable[FriendsResponse] { }) ).xmap[FriendsResponse] ( { - case act :: u1 :: u2 :: u3 :: num :: friend1 :: friends :: HNil => - val friendList : List[Friend] = if(friend1.isDefined) { friend1.get :: friends } else { friends } + case act :: u1 :: u2 :: u3 :: _ :: friend1 :: friends :: HNil => + val friendList : List[Friend] = if(friend1.isDefined) { friend1.get +: friends } else { friends } FriendsResponse(act, u1, u2, u3, friendList) }, { diff --git a/common/src/main/scala/net/psforever/packet/game/HackMessage.scala b/common/src/main/scala/net/psforever/packet/game/HackMessage.scala index 2225033e3..492e5b53d 100644 --- a/common/src/main/scala/net/psforever/packet/game/HackMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/HackMessage.scala @@ -47,6 +47,8 @@ object HackState extends Enumeration { * Upon the hack's completion, the target on the client will automatically revert back to its original state, if possible. * (It will still be necessary to alert this change from the server's perspective.) * @param unk1 na; + * 0 commonly; + * 3 for building objects during login phase; * hack type? * @param target_guid the target of the hack * @param player_guid the player 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 839ae77a4..f605363b7 100644 --- a/common/src/main/scala/net/psforever/packet/game/HotSpotUpdateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/HotSpotUpdateMessage.scala @@ -32,13 +32,13 @@ final case class HotSpotInfo(x : Float, *
* Exploration:
* What does (zone) priority entail? - * @param continent_guid the zone (continent) + * @param continent_id the zone * @param priority na * @param spots a List of HotSpotInfo */ -final case class HotSpotUpdateMessage(continent_guid : PlanetSideGUID, +final case class HotSpotUpdateMessage(continent_id : Int, priority : Int, - spots : List[HotSpotInfo] = Nil) + spots : List[HotSpotInfo]) extends PlanetSideGamePacket { type Packet = HotSpotUpdateMessage def opcode = GamePacketOpcode.HotSpotUpdateMessage @@ -47,9 +47,9 @@ final case class HotSpotUpdateMessage(continent_guid : PlanetSideGUID, object HotSpotInfo extends Marshallable[HotSpotInfo] { /* - the scale is technically not "correct" - the client is looking for a normal 0-8192 value - we are trying to enforce a more modest graphic scale at 128.0f + the client is looking for a normal 0-8192 value where default is 1.0f + we try to enforce a more modest graphic scale where default is 64.0f (arbitrary) + personally, I'd like scale to equal the sprite width in map units but the pulsation makes it hard to apply */ implicit val codec : Codec[HotSpotInfo] = { ("x" | newcodecs.q_float(0.0, 8192.0, 20)) :: @@ -60,8 +60,8 @@ object HotSpotInfo extends Marshallable[HotSpotInfo] { object HotSpotUpdateMessage extends Marshallable[HotSpotUpdateMessage] { implicit val codec : Codec[HotSpotUpdateMessage] = ( - ("continent_guid" | PlanetSideGUID.codec) :: + ("continent_guid" | uint16L) :: ("priority" | uint4L) :: - ("spots" | PacketHelpers.listOfNAligned(longL(8), 4, HotSpotInfo.codec)) + ("spots" | PacketHelpers.listOfNAligned(longL(12), 0, HotSpotInfo.codec)) ).as[HotSpotUpdateMessage] } diff --git a/common/src/main/scala/net/psforever/packet/game/SetChatFilterMessage.scala b/common/src/main/scala/net/psforever/packet/game/SetChatFilterMessage.scala new file mode 100644 index 000000000..c51f9f4bb --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/SetChatFilterMessage.scala @@ -0,0 +1,96 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import scodec.{Attempt, Codec} +import scodec.codecs._ +import shapeless.{::, HNil} + +/** + * An `Enumeration` of the valid chat channels. + */ +object ChatChannel extends Enumeration { + type Type = Value + + val + Unknown, + Tells, + Local, + Squad, + Outfit, + Command, + Platoon, + Broadcast, + SquadLeader + = Value + + implicit val codec = PacketHelpers.createEnumerationCodec(this, uint(7)) +} + +/** + * Which comm. channels are allowed to display in the main chat window. + * The server sends a `SetChatFilterMessage` and the client responds with the same during login.
+ *
+ * Nine channels exist. + * Their values can be modified by radio buttons found under the current chat window's "Options" pane. + * Each time the client updates the channel permissions, it sends this packet to the server nine times. + * The packet starts with the previous channel filter states and then updates each channel sequentially.
+ *
+ * The `send_channel` and the `channel_filter` values are in the following order:
+ * Unknown, Tells, Local, Squad, Outfit, Command, Platoon, Broadcast, Squad Leader
+ * The first channel is unlisted. + * @param send_channel automatically select the fully qualified channel to which the user sends messages + * @param origin where this packet was dispatched; + * `true`, from the server; `false`, from the client + * @param whitelist each channel permitted to post its messages; + * when evaluated from a packet, always in original order + */ +final case class SetChatFilterMessage(send_channel : ChatChannel.Value, + origin : Boolean, + whitelist : List[ChatChannel.Value]) + extends PlanetSideGamePacket { + type Packet = SetChatFilterMessage + def opcode = GamePacketOpcode.SetChatFilterMessage + def encode = SetChatFilterMessage.encode(this) +} + +object SetChatFilterMessage extends Marshallable[SetChatFilterMessage] { + /** + * Transform a `List` of `Boolean` values into a `List` of `ChatChannel` values. + * @param filters the boolean values representing ordered channel filters + * @return the names of the channels permitted + */ + private def stateArrayToChannelFilters(filters : List[Boolean]) : List[ChatChannel.Value] = { + (0 until 9) + .filter(channel => { filters(channel) }) + .map(channel => ChatChannel(channel)) + .toList + } + + /** + * Transform a `List` of `ChatChannel` values into a `List` of `Boolean` values. + * @param filters the names of the channels permitted + * @return the boolean values representing ordered channel filters + */ + private def channelFiltersToStateArray(filters : List[ChatChannel.Value]) : List[Boolean] = { + import scala.collection.mutable.ListBuffer + val list = ListBuffer.fill(9)(false) + filters.foreach(channel => { list(channel.id) = true }) + list.toList + } + + implicit val codec : Codec[SetChatFilterMessage] = ( + ("send_channel" | ChatChannel.codec) :: + ("origin" | bool) :: + ("whitelist" | PacketHelpers.listOfNSized(9, bool)) + ).exmap[SetChatFilterMessage] ( + { + case a :: b :: c :: HNil => + Attempt.Successful(SetChatFilterMessage(a, b, stateArrayToChannelFilters(c))) + }, + { + case SetChatFilterMessage(a, b, c) => + Attempt.Successful(a :: b :: channelFiltersToStateArray(c) :: HNil) + } + ) +} diff --git a/common/src/main/scala/net/psforever/packet/game/ZoneForcedCavernConnectionsMessage.scala b/common/src/main/scala/net/psforever/packet/game/ZoneForcedCavernConnectionsMessage.scala index fc35796ec..0a10fe33a 100644 --- a/common/src/main/scala/net/psforever/packet/game/ZoneForcedCavernConnectionsMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/ZoneForcedCavernConnectionsMessage.scala @@ -10,7 +10,7 @@ import scodec.codecs._ * @param zone the zone * @param unk na */ -final case class ZoneForcedCavernConnectionsMessage(zone : PlanetSideGUID, +final case class ZoneForcedCavernConnectionsMessage(zone : Int, unk : Int) extends PlanetSideGamePacket { type Packet = ZoneForcedCavernConnectionsMessage @@ -20,7 +20,7 @@ final case class ZoneForcedCavernConnectionsMessage(zone : PlanetSideGUID, object ZoneForcedCavernConnectionsMessage extends Marshallable[ZoneForcedCavernConnectionsMessage] { implicit val codec : Codec[ZoneForcedCavernConnectionsMessage] = ( - ("zone" | PlanetSideGUID.codec) :: + ("zone" | uint16L) :: ("unk" | uint2L) ).as[ZoneForcedCavernConnectionsMessage] } diff --git a/common/src/main/scala/net/psforever/packet/game/ZoneLockInfoMessage.scala b/common/src/main/scala/net/psforever/packet/game/ZoneLockInfoMessage.scala index 83ae5609c..6d0f6d5ac 100644 --- a/common/src/main/scala/net/psforever/packet/game/ZoneLockInfoMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/ZoneLockInfoMessage.scala @@ -14,7 +14,7 @@ import scodec.codecs._ * @param unk na; * usually `true` */ -final case class ZoneLockInfoMessage(zone : PlanetSideGUID, +final case class ZoneLockInfoMessage(zone : Int, lock_status : Boolean, unk : Boolean) extends PlanetSideGamePacket { @@ -25,7 +25,7 @@ final case class ZoneLockInfoMessage(zone : PlanetSideGUID, object ZoneLockInfoMessage extends Marshallable[ZoneLockInfoMessage] { implicit val codec : Codec[ZoneLockInfoMessage] = ( - ("zone" | PlanetSideGUID.codec) :: + ("zone" | uint16L) :: ("lock_status" | bool) :: ("unk" | bool) ).as[ZoneLockInfoMessage] diff --git a/common/src/main/scala/net/psforever/packet/game/ZonePopulationUpdateMessage.scala b/common/src/main/scala/net/psforever/packet/game/ZonePopulationUpdateMessage.scala index 22f3a43d0..0235913d0 100644 --- a/common/src/main/scala/net/psforever/packet/game/ZonePopulationUpdateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/ZonePopulationUpdateMessage.scala @@ -32,7 +32,7 @@ import scodec.codecs._ * 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_id the 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 @@ -43,7 +43,7 @@ import scodec.codecs._ * @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, +final case class ZonePopulationUpdateMessage(zone_id : Int, zone_queue : Long, tr_queue : Long, tr_pop : Long, @@ -61,7 +61,7 @@ final case class ZonePopulationUpdateMessage(continent_guid : PlanetSideGUID, object ZonePopulationUpdateMessage extends Marshallable[ZonePopulationUpdateMessage] { implicit val codec : Codec[ZonePopulationUpdateMessage] = ( - ("continent_guid" | PlanetSideGUID.codec) :: + ("zone_id" | uint16L) :: ("zone_queue" | uint32L) :: ("tr_queue" | uint32L) :: ("tr_pop" | uint32L) :: ("nc_queue" | uint32L) :: ("nc_pop" | uint32L) :: diff --git a/common/src/test/scala/game/AvatarImplantMessageTest.scala b/common/src/test/scala/game/AvatarImplantMessageTest.scala index c52d50db0..78b269603 100644 --- a/common/src/test/scala/game/AvatarImplantMessageTest.scala +++ b/common/src/test/scala/game/AvatarImplantMessageTest.scala @@ -4,7 +4,6 @@ package game import org.specs2.mutable._ import net.psforever.packet._ import net.psforever.packet.game._ -import net.psforever.types.ImplantType import scodec.bits._ class AvatarImplantMessageTest extends Specification { @@ -14,7 +13,7 @@ class AvatarImplantMessageTest extends Specification { PacketCoding.DecodePacket(string).require match { case AvatarImplantMessage(player_guid, unk1, unk2, implant) => player_guid mustEqual PlanetSideGUID(3171) - unk1 mustEqual 3 + unk1 mustEqual ImplantAction.Activation unk2 mustEqual 1 implant mustEqual 1 case _ => @@ -23,7 +22,7 @@ class AvatarImplantMessageTest extends Specification { } "encode" in { - val msg = AvatarImplantMessage(PlanetSideGUID(3171), 3, 1, 1) + val msg = AvatarImplantMessage(PlanetSideGUID(3171), ImplantAction.Activation, 1, 1) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string diff --git a/common/src/test/scala/game/AvatarSearchCriteriaMessageTest.scala b/common/src/test/scala/game/AvatarSearchCriteriaMessageTest.scala new file mode 100644 index 000000000..d0175d061 --- /dev/null +++ b/common/src/test/scala/game/AvatarSearchCriteriaMessageTest.scala @@ -0,0 +1,49 @@ +// Copyright (c) 2017 PSForever +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import scodec.bits._ + +class AvatarSearchCriteriaMessageTest extends Specification { + val string = hex"64 C604 00 00 00 00 00 00" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case AvatarSearchCriteriaMessage(unk1, unk2) => + unk1 mustEqual PlanetSideGUID(1222) + unk2.length mustEqual 6 + unk2.head mustEqual 0 + unk2(1) mustEqual 0 + unk2(2) mustEqual 0 + unk2(3) mustEqual 0 + unk2(4) mustEqual 0 + unk2(5) mustEqual 0 + case _ => + ko + } + } + + "encode" in { + val msg = AvatarSearchCriteriaMessage(PlanetSideGUID(1222), List(0, 0, 0, 0, 0, 0)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } + + "encode (failure; wrong number of list entries)" in { + val msg = AvatarSearchCriteriaMessage(PlanetSideGUID(1222), List(0)) + PacketCoding.EncodePacket(msg).isSuccessful mustEqual false + } + + "encode (failure; list number too big)" in { + val msg = AvatarSearchCriteriaMessage(PlanetSideGUID(1222), List(0, 0, 0, 0, 0, 256)) + PacketCoding.EncodePacket(msg).isSuccessful mustEqual false + } + + "encode (failure; list number too small)" in { + val msg = AvatarSearchCriteriaMessage(PlanetSideGUID(1222), List(0, 0, 0, -1, 0, 0)) + PacketCoding.EncodePacket(msg).isSuccessful mustEqual false + } +} diff --git a/common/src/test/scala/game/AvatarStatisticsMessageTest.scala b/common/src/test/scala/game/AvatarStatisticsMessageTest.scala new file mode 100644 index 000000000..bca185fb5 --- /dev/null +++ b/common/src/test/scala/game/AvatarStatisticsMessageTest.scala @@ -0,0 +1,79 @@ +// Copyright (c) 2017 PSForever +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import scodec.bits._ + +class AvatarStatisticsMessageTest extends Specification { + val string_long = hex"7F 4 00000000 0" + val string_complex = hex"7F 01 3C 40 20 00 00 00 C0 00 00 00 00 00 00 00 20 00 00 00 20 00 00 00 40 00 00 00 00 00 00 00 00 00 00 00" + + "decode (long)" in { + PacketCoding.DecodePacket(string_long).require match { + case AvatarStatisticsMessage(unk, stats) => + unk mustEqual 2 + stats.unk1 mustEqual None + stats.unk2 mustEqual None + stats.unk3.length mustEqual 1 + stats.unk3.head mustEqual 0 + case _ => + ko + } + } + + "decode (complex)" in { + PacketCoding.DecodePacket(string_complex).require match { + case AvatarStatisticsMessage(unk, stats) => + unk mustEqual 0 + stats.unk1 mustEqual Some(1) + stats.unk2 mustEqual Some(572) + stats.unk3.length mustEqual 8 + stats.unk3.head mustEqual 1 + stats.unk3(1) mustEqual 6 + stats.unk3(2) mustEqual 0 + stats.unk3(3) mustEqual 1 + stats.unk3(4) mustEqual 1 + stats.unk3(5) mustEqual 2 + stats.unk3(6) mustEqual 0 + stats.unk3(7) mustEqual 0 + case _ => + ko + } + } + + "encode (long)" in { + val msg = AvatarStatisticsMessage(2, Statistics(0L)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_long + } + + "encode (complex)" in { + val msg = AvatarStatisticsMessage(0, Statistics(1, 572, List[Long](1,6,0,1,1,2,0,0))) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_complex + } + + "encode (failure; long; missing value)" in { + val msg = AvatarStatisticsMessage(0, Statistics(None, None,List(0L))) + PacketCoding.EncodePacket(msg).isFailure mustEqual true + } + + "encode (failure; complex; missing value (5-bit))" in { + val msg = AvatarStatisticsMessage(0, Statistics(None, Some(572), List[Long](1,6,0,1,1,2,0,0))) + PacketCoding.EncodePacket(msg).isFailure mustEqual true + } + + "encode (failure; complex; missing value (11-bit))" in { + val msg = AvatarStatisticsMessage(0, Statistics(Some(1), None, List[Long](1,6,0,1,1,2,0,0))) + PacketCoding.EncodePacket(msg).isFailure mustEqual true + } + + "encode (failure; complex; wrong number of list entries)" in { + val msg = AvatarStatisticsMessage(0, Statistics(Some(1), None, List[Long](1,6,0,1))) + PacketCoding.EncodePacket(msg).isFailure mustEqual true + } +} diff --git a/common/src/test/scala/game/BroadcastWarpgateUpdateMessageTest.scala b/common/src/test/scala/game/BroadcastWarpgateUpdateMessageTest.scala index 49bf1221c..eaeed5e8b 100644 --- a/common/src/test/scala/game/BroadcastWarpgateUpdateMessageTest.scala +++ b/common/src/test/scala/game/BroadcastWarpgateUpdateMessageTest.scala @@ -12,8 +12,8 @@ class BroadcastWarpgateUpdateMessageTest extends Specification { "decode" in { PacketCoding.DecodePacket(string).require match { case BroadcastWarpgateUpdateMessage(continent_guid, building_guid, state1, state2, state3) => - continent_guid mustEqual PlanetSideGUID(13) - building_guid mustEqual PlanetSideGUID(1) + continent_guid mustEqual 13 + building_guid mustEqual 1 state1 mustEqual false state2 mustEqual false state3 mustEqual true @@ -23,7 +23,7 @@ class BroadcastWarpgateUpdateMessageTest extends Specification { } "encode" in { - val msg = BroadcastWarpgateUpdateMessage(PlanetSideGUID(13), PlanetSideGUID(1), false, false, true) + val msg = BroadcastWarpgateUpdateMessage(13, 1, false, false, true) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string diff --git a/common/src/test/scala/game/BuildingInfoUpdateMessageTest.scala b/common/src/test/scala/game/BuildingInfoUpdateMessageTest.scala index edbd476f5..983f25beb 100644 --- a/common/src/test/scala/game/BuildingInfoUpdateMessageTest.scala +++ b/common/src/test/scala/game/BuildingInfoUpdateMessageTest.scala @@ -12,29 +12,29 @@ class BuildingInfoUpdateMessageTest extends Specification { "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, - unk1x : Option[Additional1], - generator_state : PlanetSideGeneratorState.Value, - spawn_tubes_normal : Boolean, - force_dome_active : Boolean, - lattice_benefit : Int, - unk3 : Int, - unk4 : List[Additional2], - unk5 : Long, - unk6 : Boolean, - unk7 : Int, - unk7x : Option[Additional3], - boost_spawn_pain : Boolean, - boost_generator_pain : Boolean) => - continent_guid mustEqual PlanetSideGUID(4) - building_guid mustEqual PlanetSideGUID(9) + case BuildingInfoUpdateMessage(continent_guid, + building_guid, + ntu_level, + is_hacked, + empire_hack, + hack_time_remaining, + empire_own, + unk1, + unk1x, + generator_state, + spawn_tubes_normal, + force_dome_active, + lattice_benefit, + unk3, + unk4, + unk5, + unk6, + unk7, + unk7x, + boost_spawn_pain, + boost_generator_pain) => + continent_guid mustEqual 4 + building_guid mustEqual 9 ntu_level mustEqual 1 is_hacked mustEqual false empire_hack mustEqual PlanetSideEmpire.NEUTRAL @@ -61,8 +61,8 @@ class BuildingInfoUpdateMessageTest extends Specification { } "encode" in { - val msg = BuildingInfoUpdateMessage(PlanetSideGUID(4), - PlanetSideGUID(9), + val msg = BuildingInfoUpdateMessage(4, + 9, 1, false, PlanetSideEmpire.NEUTRAL, diff --git a/common/src/test/scala/game/ContinentalLockUpdateMessageTest.scala b/common/src/test/scala/game/ContinentalLockUpdateMessageTest.scala index 3f231341e..4db015235 100644 --- a/common/src/test/scala/game/ContinentalLockUpdateMessageTest.scala +++ b/common/src/test/scala/game/ContinentalLockUpdateMessageTest.scala @@ -13,7 +13,7 @@ class ContinentalLockUpdateMessageTest extends Specification { "decode" in { PacketCoding.DecodePacket(string).require match { case ContinentalLockUpdateMessage(continent_guid, empire) => - continent_guid mustEqual PlanetSideGUID(22) + continent_guid mustEqual 22 empire mustEqual PlanetSideEmpire.NC case _ => ko @@ -21,7 +21,7 @@ class ContinentalLockUpdateMessageTest extends Specification { } "encode" in { - val msg = ContinentalLockUpdateMessage(PlanetSideGUID(22), PlanetSideEmpire.NC) + val msg = ContinentalLockUpdateMessage(22, PlanetSideEmpire.NC) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string diff --git a/common/src/test/scala/game/DensityLevelUpdateMessageTest.scala b/common/src/test/scala/game/DensityLevelUpdateMessageTest.scala new file mode 100644 index 000000000..aa5e1f551 --- /dev/null +++ b/common/src/test/scala/game/DensityLevelUpdateMessageTest.scala @@ -0,0 +1,52 @@ +// Copyright (c) 2017 PSForever +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import scodec.bits._ + +class DensityLevelUpdateMessageTest extends Specification { + val string = hex"cd 0100 1f4e 000000" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case DensityLevelUpdateMessage(zone_id, building_id, unk) => + zone_id mustEqual 1 + building_id mustEqual 19999 + unk.length mustEqual 8 + unk.head mustEqual 0 + unk(1) mustEqual 0 + unk(2) mustEqual 0 + unk(3) mustEqual 0 + unk(4) mustEqual 0 + unk(5) mustEqual 0 + unk(6) mustEqual 0 + unk(7) mustEqual 0 + case _ => + ko + } + } + + "encode" in { + val msg = DensityLevelUpdateMessage(1, 19999, List(0,0, 0,0, 0,0, 0,0)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } + + "encode (failure; wrong number of list entries)" in { + val msg = DensityLevelUpdateMessage(1, 19999, List(0)) + PacketCoding.EncodePacket(msg).isSuccessful mustEqual false + } + + "encode (failure; list number too big)" in { + val msg = DensityLevelUpdateMessage(1, 19999, List(0,0, 0,0, 0,0, 0,8)) + PacketCoding.EncodePacket(msg).isSuccessful mustEqual false + } + + "encode (failure; list number too small)" in { + val msg = DensityLevelUpdateMessage(1, 19999, List(0,0, 0,0, 0,-1, 0,0)) + PacketCoding.EncodePacket(msg).isSuccessful mustEqual false + } +} diff --git a/common/src/test/scala/game/FriendsResponseTest.scala b/common/src/test/scala/game/FriendsResponseTest.scala index 47a13c13c..3b30d6b5f 100644 --- a/common/src/test/scala/game/FriendsResponseTest.scala +++ b/common/src/test/scala/game/FriendsResponseTest.scala @@ -14,7 +14,7 @@ class FriendsResponseTest extends Specification { "decode (one friend)" in { PacketCoding.DecodePacket(stringOneFriend).require match { case FriendsResponse(action, unk2, unk3, unk4, list) => - action mustEqual 3 + action mustEqual FriendAction.UpdateFriend unk2 mustEqual 0 unk3 mustEqual true unk4 mustEqual true @@ -29,7 +29,7 @@ class FriendsResponseTest extends Specification { "decode (multiple friends)" in { PacketCoding.DecodePacket(stringManyFriends).require match { case FriendsResponse(action, unk2, unk3, unk4, list) => - action mustEqual 0 + action mustEqual FriendAction.InitializeFriendList unk2 mustEqual 0 unk3 mustEqual true unk4 mustEqual true @@ -52,7 +52,7 @@ class FriendsResponseTest extends Specification { "decode (short)" in { PacketCoding.DecodePacket(stringShort).require match { case FriendsResponse(action, unk2, unk3, unk4, list) => - action mustEqual 4 + action mustEqual FriendAction.InitializeIgnoreList unk2 mustEqual 0 unk3 mustEqual true unk4 mustEqual true @@ -63,7 +63,7 @@ class FriendsResponseTest extends Specification { } "encode (one friend)" in { - val msg = FriendsResponse(3, 0, true, true, + val msg = FriendsResponse(FriendAction.UpdateFriend, 0, true, true, Friend("KurtHectic-G", false) :: Nil) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector @@ -72,7 +72,7 @@ class FriendsResponseTest extends Specification { } "encode (multiple friends)" in { - val msg = FriendsResponse(0, 0, true, true, + val msg = FriendsResponse(FriendAction.InitializeFriendList, 0, true, true, Friend("Angello-W", false) :: Friend("thephattphrogg", false) :: Friend("Kimpossible12", false) :: @@ -85,7 +85,7 @@ class FriendsResponseTest extends Specification { } "encode (short)" in { - val msg = FriendsResponse(4, 0, true, true) + val msg = FriendsResponse(FriendAction.InitializeIgnoreList, 0, true, true) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual stringShort diff --git a/common/src/test/scala/game/HotSpotUpdateMessageTest.scala b/common/src/test/scala/game/HotSpotUpdateMessageTest.scala index 129f508c1..72413f284 100644 --- a/common/src/test/scala/game/HotSpotUpdateMessageTest.scala +++ b/common/src/test/scala/game/HotSpotUpdateMessageTest.scala @@ -4,17 +4,19 @@ package game import org.specs2.mutable._ import net.psforever.packet._ import net.psforever.packet.game._ +import net.psforever.types.Vector3 import scodec.bits._ class HotSpotUpdateMessageTest extends Specification { - val stringClear = hex"9F 0500 1 00 0" - val stringOne = hex"9F 0500 1 01 0 00 2E9 00 145 80000 0" - val stringTwo = hex"9F 0500 5 02 0 00 D07 00 8CA 80000 00 BEA 00 4C4 80000" + val stringClear = hex"9F 0500 1 000" + val stringOne = hex"9F 0500 1 010 002E9 00145 80000 0" + val stringTwo = hex"9F 0500 5 020 00D07 008CA 80000 00BEA 004C4 80000" + val stringThree = hex"9F 0A00 4 030 00FC8 00F0A 80000 002E9 00BEA 80000 00FC8 00BEA 80000 0" "decode (clear)" in { PacketCoding.DecodePacket(stringClear).require match { - case HotSpotUpdateMessage(continent_guid, unk, spots) => - continent_guid mustEqual PlanetSideGUID(5) + case HotSpotUpdateMessage(continent_id, unk, spots) => + continent_id mustEqual 5 unk mustEqual 1 spots.size mustEqual 0 case _ => @@ -24,13 +26,11 @@ class HotSpotUpdateMessageTest extends Specification { "decode (one)" in { PacketCoding.DecodePacket(stringOne).require match { - case HotSpotUpdateMessage(continent_guid, unk, spots) => - continent_guid mustEqual PlanetSideGUID(5) + case HotSpotUpdateMessage(continent_id, unk, spots) => + continent_id mustEqual 5 unk mustEqual 1 spots.size mustEqual 1 - spots.head.x mustEqual 4700.0f - spots.head.y mustEqual 2600.0f - spots.head.scale mustEqual 64.0f + spots.head mustEqual HotSpotInfo(4700.0f, 2600.0f, 64.0f) case _ => ko } @@ -38,36 +38,52 @@ class HotSpotUpdateMessageTest extends Specification { "decode (two)" in { PacketCoding.DecodePacket(stringTwo).require match { - case HotSpotUpdateMessage(continent_guid, unk, spots) => - continent_guid mustEqual PlanetSideGUID(5) + case HotSpotUpdateMessage(continent_id, unk, spots) => + continent_id mustEqual 5 unk mustEqual 5 spots.size mustEqual 2 - spots.head.x mustEqual 4000.0f - spots.head.y mustEqual 5400.0f - spots.head.scale mustEqual 64.0f - spots(1).x mustEqual 5500.0f - spots(1).y mustEqual 2200.0f - spots(1).scale mustEqual 64.0f + spots.head mustEqual HotSpotInfo(4000.0f, 5400.0f, 64.0f) + spots(1) mustEqual HotSpotInfo(5500.0f, 2200.0f, 64.0f) + case _ => + ko + } + } + + "decode (three)" in { + PacketCoding.DecodePacket(stringThree).require match { + case HotSpotUpdateMessage(continent_id, unk, spots) => + continent_id mustEqual 10 + unk mustEqual 4 + spots.size mustEqual 3 + spots.head mustEqual HotSpotInfo(4600.0f, 5600.0f, 64.0f) + spots(1) mustEqual HotSpotInfo(4700.0f, 5500.0f, 64.0f) + spots(2) mustEqual HotSpotInfo(4600.0f, 5500.0f, 64.0f) case _ => ko } } "encode (clear)" in { - val msg = HotSpotUpdateMessage(PlanetSideGUID(5),1) + val msg = HotSpotUpdateMessage(5,1, Nil) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual stringClear } "encode (one)" in { - val msg = HotSpotUpdateMessage(PlanetSideGUID(5),1, HotSpotInfo(4700.0f, 2600.0f, 64.0f)::Nil) + val msg = HotSpotUpdateMessage(5,1, List(HotSpotInfo(4700.0f, 2600.0f, 64.0f))) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual stringOne } "encode (two)" in { - val msg = HotSpotUpdateMessage(PlanetSideGUID(5),5, HotSpotInfo(4000.0f, 5400.0f, 64.0f)::HotSpotInfo(5500.0f, 2200.0f, 64.0f)::Nil) + val msg = HotSpotUpdateMessage(5,5, List(HotSpotInfo(4000.0f, 5400.0f, 64.0f), HotSpotInfo(5500.0f, 2200.0f, 64.0f))) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual stringTwo } + + "encode (three)" in { + val msg = HotSpotUpdateMessage(10,4, List(HotSpotInfo(4600.0f, 5600.0f, 64.0f), HotSpotInfo(4700.0f, 5500.0f, 64.0f), HotSpotInfo(4600.0f, 5500.0f, 64.0f))) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + pkt mustEqual stringThree + } } diff --git a/common/src/test/scala/game/SetChatFilterMessageTest.scala b/common/src/test/scala/game/SetChatFilterMessageTest.scala new file mode 100644 index 000000000..7bf9f5185 --- /dev/null +++ b/common/src/test/scala/game/SetChatFilterMessageTest.scala @@ -0,0 +1,75 @@ +// Copyright (c) 2017 PSForever +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import scodec.bits._ + +class SetChatFilterMessageTest extends Specification { + val string = hex"63 05FF80" + val string_custom = hex"63 05C180" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case SetChatFilterMessage(send, origin, filters) => + send mustEqual ChatChannel.Local + origin mustEqual true + filters.length mustEqual 9 + filters.head mustEqual ChatChannel.Unknown + filters(1) mustEqual ChatChannel.Tells + filters(2) mustEqual ChatChannel.Local + filters(3) mustEqual ChatChannel.Squad + filters(4) mustEqual ChatChannel.Outfit + filters(5) mustEqual ChatChannel.Command + filters(6) mustEqual ChatChannel.Platoon + filters(7) mustEqual ChatChannel.Broadcast + filters(8) mustEqual ChatChannel.SquadLeader + case _ => + ko + } + } + + "decode (custom)" in { + PacketCoding.DecodePacket(string_custom).require match { + case SetChatFilterMessage(send, origin, filters) => + send mustEqual ChatChannel.Local + origin mustEqual true + filters.length mustEqual 4 + filters.head mustEqual ChatChannel.Unknown + filters(1) mustEqual ChatChannel.Tells + filters(2) mustEqual ChatChannel.Broadcast + filters(3) mustEqual ChatChannel.SquadLeader + case _ => + ko + } + } + + "encode" in { + val msg = SetChatFilterMessage(ChatChannel.Local, true, List(ChatChannel.Unknown, ChatChannel.Tells, ChatChannel.Local, ChatChannel.Squad, ChatChannel.Outfit, ChatChannel.Command, ChatChannel.Platoon, ChatChannel.Broadcast, ChatChannel.SquadLeader)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } + + "encode (success; same channel listed multiple times)" in { + val msg = SetChatFilterMessage(ChatChannel.Local, true, List(ChatChannel.Unknown, ChatChannel.Unknown, ChatChannel.Tells, ChatChannel.Tells, ChatChannel.Local, ChatChannel.Squad, ChatChannel.Outfit, ChatChannel.Command, ChatChannel.Platoon, ChatChannel.Broadcast, ChatChannel.SquadLeader)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } + + "encode (success; out of order)" in { + val msg = SetChatFilterMessage(ChatChannel.Local, true, List(ChatChannel.Squad, ChatChannel.Outfit, ChatChannel.SquadLeader, ChatChannel.Unknown, ChatChannel.Command, ChatChannel.Platoon, ChatChannel.Broadcast, ChatChannel.Tells, ChatChannel.Local)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } + + "encode (success; custom)" in { + val msg = SetChatFilterMessage(ChatChannel.Local, true, List(ChatChannel.Unknown, ChatChannel.Tells, ChatChannel.Broadcast, ChatChannel.SquadLeader)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_custom + } +} diff --git a/common/src/test/scala/game/ZoneForcedCavernConnectionsMessageTest.scala b/common/src/test/scala/game/ZoneForcedCavernConnectionsMessageTest.scala index d963928d1..a31736ae3 100644 --- a/common/src/test/scala/game/ZoneForcedCavernConnectionsMessageTest.scala +++ b/common/src/test/scala/game/ZoneForcedCavernConnectionsMessageTest.scala @@ -13,7 +13,7 @@ class ZoneForcedCavernConnectionsMessageTest extends Specification { "decode" in { PacketCoding.DecodePacket(string).require match { case ZoneForcedCavernConnectionsMessage(zone, unk) => - zone mustEqual PlanetSideGUID(32) + zone mustEqual 32 unk mustEqual 1 case _ => ko @@ -21,7 +21,7 @@ class ZoneForcedCavernConnectionsMessageTest extends Specification { } "encode" in { - val msg = ZoneForcedCavernConnectionsMessage(PlanetSideGUID(32), 1) + val msg = ZoneForcedCavernConnectionsMessage(32, 1) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string diff --git a/common/src/test/scala/game/ZoneLockInfoMesageTest.scala b/common/src/test/scala/game/ZoneLockInfoMesageTest.scala index 01fb3316e..a4238a38d 100644 --- a/common/src/test/scala/game/ZoneLockInfoMesageTest.scala +++ b/common/src/test/scala/game/ZoneLockInfoMesageTest.scala @@ -12,7 +12,7 @@ class ZoneLockInfoMesageTest extends Specification { "decode" in { PacketCoding.DecodePacket(string).require match { case ZoneLockInfoMessage(zone, locked, unk) => - zone mustEqual PlanetSideGUID(27) + zone mustEqual 27 locked mustEqual false unk mustEqual true case _ => @@ -21,7 +21,7 @@ class ZoneLockInfoMesageTest extends Specification { } "encode" in { - val msg = ZoneLockInfoMessage(PlanetSideGUID(27), false, true) + val msg = ZoneLockInfoMessage(27, false, true) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string diff --git a/common/src/test/scala/game/ZonePopulationUpdateMessageTest.scala b/common/src/test/scala/game/ZonePopulationUpdateMessageTest.scala index c43832fb8..468002180 100644 --- a/common/src/test/scala/game/ZonePopulationUpdateMessageTest.scala +++ b/common/src/test/scala/game/ZonePopulationUpdateMessageTest.scala @@ -12,7 +12,7 @@ class ZonePopulationUpdateMessageTest extends Specification { "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) + continent_guid mustEqual 4 zone_queue mustEqual 414 tr_queue mustEqual 138 tr_pop mustEqual 37 @@ -28,7 +28,7 @@ class ZonePopulationUpdateMessageTest extends Specification { } "encode" in { - val msg = ZonePopulationUpdateMessage(PlanetSideGUID(4), 414, 138, 37, 138, 37, 138, 37, 138, 37) + val msg = ZonePopulationUpdateMessage(4, 414, 138, 37, 138, 37, 138, 37, 138, 37) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string diff --git a/common/src/test/scala/objects/BuildingTest.scala b/common/src/test/scala/objects/BuildingTest.scala index 122f3d993..dc533b1b8 100644 --- a/common/src/test/scala/objects/BuildingTest.scala +++ b/common/src/test/scala/objects/BuildingTest.scala @@ -6,7 +6,7 @@ import net.psforever.objects.GlobalDefinitions import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.doors.{Door, DoorControl} -import net.psforever.objects.serverobject.structures.{Amenity, Building, BuildingControl} +import net.psforever.objects.serverobject.structures.{Amenity, Building, BuildingControl, WarpGate} import net.psforever.objects.zones.Zone import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.PlanetSideEmpire @@ -98,6 +98,19 @@ class BuildingTest extends Specification { } } +class WarpGateTest extends Specification { + "WarpGate" should { + "construct" in { + val bldg = WarpGate(10, Zone.Nowhere) + bldg.Id mustEqual 10 + bldg.Actor mustEqual ActorRef.noSender + bldg.Amenities mustEqual Nil + bldg.Zone mustEqual Zone.Nowhere + bldg.Faction mustEqual PlanetSideEmpire.NEUTRAL + } + } +} + class BuildingControl1Test extends ActorTest { "Building Control" should { "construct" in { diff --git a/common/src/test/scala/objects/ServerObjectBuilderTest.scala b/common/src/test/scala/objects/ServerObjectBuilderTest.scala index 73f802031..903e2e50d 100644 --- a/common/src/test/scala/objects/ServerObjectBuilderTest.scala +++ b/common/src/test/scala/objects/ServerObjectBuilderTest.scala @@ -1,11 +1,11 @@ // Copyright (c) 2017 PSForever package objects -import akka.actor.{Actor, Props} +import akka.actor.{Actor, ActorContext, Props} import net.psforever.objects.guid.NumberPoolHub import net.psforever.packet.game.PlanetSideGUID import net.psforever.objects.serverobject.ServerObjectBuilder -import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder} +import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, WarpGate} import net.psforever.objects.zones.Zone import net.psforever.types.Vector3 @@ -14,7 +14,23 @@ import scala.concurrent.duration.Duration class BuildingBuilderTest extends ActorTest { "Building object" should { "build" in { - val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuildingTestActor], 10, Zone.Nowhere), "building") + val structure : (Int,Zone,ActorContext)=>Building = Building.Structure + val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuildingTestActor], structure, 10, Zone.Nowhere), "building") + actor ! "!" + + val reply = receiveOne(Duration.create(1000, "ms")) + assert(reply.isInstanceOf[Building]) + assert(reply.asInstanceOf[Building].Id == 10) + assert(reply.asInstanceOf[Building].Zone == Zone.Nowhere) + } + } +} + +class WarpGateBuilderTest extends ActorTest { + "WarpGate object" should { + "build" in { + val structure : (Int,Zone,ActorContext)=>Building = WarpGate.Structure + val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuildingTestActor], structure, 10, Zone.Nowhere), "wgate") actor ! "!" val reply = receiveOne(Duration.create(1000, "ms")) @@ -167,10 +183,10 @@ object ServerObjectBuilderTest { } } - class BuildingTestActor(building_id : Int, zone : Zone) extends Actor { + class BuildingTestActor(structure_con : (Int,Zone,ActorContext)=>Building, building_id : Int, zone : Zone) extends Actor { def receive : Receive = { case _ => - sender ! FoundationBuilder(Building.Structure).Build(building_id, zone)(context) + sender ! FoundationBuilder(structure_con).Build(building_id, zone)(context) } } } diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala index d8c854c8c..ab19d2dbb 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/pslogin/src/main/scala/Maps.scala @@ -7,7 +7,7 @@ import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.mblocker.Locker import net.psforever.objects.serverobject.pad.VehicleSpawnPad -import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder} +import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, WarpGate} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.types.Vector3 @@ -48,15 +48,27 @@ object Maps { val map12 = new ZoneMap("map12") val map13 = new ZoneMap("map13") { - LocalObject(ServerObjectBuilder(330, Door.Constructor(Vector3(3979.9219f, 2592.0547f, 91.140625f), Vector3(0, 0, 180)))) - LocalObject(ServerObjectBuilder(331, Door.Constructor(Vector3(3979.9219f, 2592.0547f, 111.140625f), Vector3(0, 0, 180)))) - LocalObject(ServerObjectBuilder(332, Door.Constructor(Vector3(3979.9688f, 2608.0625f, 91.140625f), Vector3(0, 0, 0)))) - LocalObject(ServerObjectBuilder(333, Door.Constructor(Vector3(3979.9688f, 2608.0625f, 111.140625f), Vector3(0, 0, 0)))) + LocalBuilding(1, FoundationBuilder(WarpGate.Structure)) + LocalBuilding(2, FoundationBuilder(WarpGate.Structure)) + LocalBuilding(3, FoundationBuilder(WarpGate.Structure)) + + LocalObject(ServerObjectBuilder(372, Door.Constructor)) + LocalObject(ServerObjectBuilder(373, Door.Constructor)) + + LocalObject(ServerObjectBuilder(520, ImplantTerminalMech.Constructor)) //Hart B + LocalObject(ServerObjectBuilder(853, Terminal.Constructor(order_terminal))) + LocalObject(ServerObjectBuilder(855, Terminal.Constructor(order_terminal))) + LocalObject(ServerObjectBuilder(860, Terminal.Constructor(order_terminal))) + LocalObject(ServerObjectBuilder(1081, Terminal.Constructor(implant_terminal_interface))) //tube 520 + TerminalToInterface(520, 1081) + + LocalBuilding(2, FoundationBuilder(Building.Structure)) //HART building C + LocalObject(ServerObjectBuilder(186, Terminal.Constructor(cert_terminal))) + LocalObject(ServerObjectBuilder(187, Terminal.Constructor(cert_terminal))) + LocalObject(ServerObjectBuilder(188, Terminal.Constructor(cert_terminal))) LocalObject(ServerObjectBuilder(362, Door.Constructor)) LocalObject(ServerObjectBuilder(370, Door.Constructor)) LocalObject(ServerObjectBuilder(371, Door.Constructor)) - LocalObject(ServerObjectBuilder(372, Door.Constructor)) - LocalObject(ServerObjectBuilder(373, Door.Constructor)) LocalObject(ServerObjectBuilder(374, Door.Constructor)) LocalObject(ServerObjectBuilder(375, Door.Constructor)) LocalObject(ServerObjectBuilder(394, Door.Constructor)) @@ -66,7 +78,6 @@ object Maps { LocalObject(ServerObjectBuilder(398, Door.Constructor)) LocalObject(ServerObjectBuilder(462, Door.Constructor)) LocalObject(ServerObjectBuilder(463, Door.Constructor)) - LocalObject(ServerObjectBuilder(520, ImplantTerminalMech.Constructor)) //Hart B LocalObject(ServerObjectBuilder(522, ImplantTerminalMech.Constructor)) //Hart C LocalObject(ServerObjectBuilder(523, ImplantTerminalMech.Constructor)) //Hart C LocalObject(ServerObjectBuilder(524, ImplantTerminalMech.Constructor)) //Hart C @@ -75,10 +86,6 @@ object Maps { LocalObject(ServerObjectBuilder(527, ImplantTerminalMech.Constructor)) //Hart C LocalObject(ServerObjectBuilder(528, ImplantTerminalMech.Constructor)) //Hart C LocalObject(ServerObjectBuilder(529, ImplantTerminalMech.Constructor)) //Hart C - LocalObject(ServerObjectBuilder(556, IFFLock.Constructor)) - LocalObject(ServerObjectBuilder(557, IFFLock.Constructor)) - LocalObject(ServerObjectBuilder(558, IFFLock.Constructor)) - LocalObject(ServerObjectBuilder(559, IFFLock.Constructor)) LocalObject(ServerObjectBuilder(686, Locker.Constructor)) LocalObject(ServerObjectBuilder(687, Locker.Constructor)) LocalObject(ServerObjectBuilder(688, Locker.Constructor)) @@ -87,17 +94,10 @@ object Maps { LocalObject(ServerObjectBuilder(691, Locker.Constructor)) LocalObject(ServerObjectBuilder(692, Locker.Constructor)) LocalObject(ServerObjectBuilder(693, Locker.Constructor)) - LocalObject(ServerObjectBuilder(186, Terminal.Constructor(cert_terminal))) - LocalObject(ServerObjectBuilder(187, Terminal.Constructor(cert_terminal))) - LocalObject(ServerObjectBuilder(188, Terminal.Constructor(cert_terminal))) LocalObject(ServerObjectBuilder(842, Terminal.Constructor(order_terminal))) LocalObject(ServerObjectBuilder(843, Terminal.Constructor(order_terminal))) LocalObject(ServerObjectBuilder(844, Terminal.Constructor(order_terminal))) LocalObject(ServerObjectBuilder(845, Terminal.Constructor(order_terminal))) - LocalObject(ServerObjectBuilder(853, Terminal.Constructor(order_terminal))) - LocalObject(ServerObjectBuilder(855, Terminal.Constructor(order_terminal))) - LocalObject(ServerObjectBuilder(860, Terminal.Constructor(order_terminal))) - LocalObject(ServerObjectBuilder(1081, Terminal.Constructor(implant_terminal_interface))) //tube 520 LocalObject(ServerObjectBuilder(1082, Terminal.Constructor(implant_terminal_interface))) //TODO guid not correct LocalObject(ServerObjectBuilder(1083, Terminal.Constructor(implant_terminal_interface))) //TODO guid not correct LocalObject(ServerObjectBuilder(1084, Terminal.Constructor(implant_terminal_interface))) //TODO guid not correct @@ -106,16 +106,6 @@ object Maps { LocalObject(ServerObjectBuilder(1087, Terminal.Constructor(implant_terminal_interface))) //TODO guid not correct LocalObject(ServerObjectBuilder(1088, Terminal.Constructor(implant_terminal_interface))) //TODO guid not correct LocalObject(ServerObjectBuilder(1089, Terminal.Constructor(implant_terminal_interface))) //TODO guid not correct - LocalObject(ServerObjectBuilder(1063, Terminal.Constructor(ground_vehicle_terminal))) - LocalObject(ServerObjectBuilder(500, - VehicleSpawnPad.Constructor(Vector3(3506.0f, 2820.0f, 92.0f), Vector3(0f, 0f, 270.0f)) - )) //TODO guid not correct - LocalObject(ServerObjectBuilder(304, Terminal.Constructor(dropship_vehicle_terminal))) - LocalObject(ServerObjectBuilder(501, - VehicleSpawnPad.Constructor(Vector3(3508.9844f, 2895.961f, 92.296875f), Vector3(0f, 0f, 270.0f)) - )) //TODO guid not correct - - LocalBuilding(2, FoundationBuilder(Building.Structure)) ObjectToBuilding(186, 2) ObjectToBuilding(187, 2) ObjectToBuilding(188, 2) @@ -139,30 +129,14 @@ object Maps { ObjectToBuilding(843, 2) ObjectToBuilding(844, 2) ObjectToBuilding(845, 2) - ObjectToBuilding(853, 2) //TODO check building_id - ObjectToBuilding(855, 2) //TODO check building_id - ObjectToBuilding(860, 2) //TODO check building_id - ObjectToBuilding(1063, 2) //TODO unowned courtyard terminal? - ObjectToBuilding(500, 2) //TODO unowned courtyard spawnpad? - ObjectToBuilding(304, 2) //TODO unowned courtyard terminal? - ObjectToBuilding(501, 2) //TODO unowned courtyard spawnpad? - - LocalBuilding(29, FoundationBuilder(Building.Structure)) - ObjectToBuilding(330, 29) - ObjectToBuilding(332, 29) - ObjectToBuilding(556, 29) - ObjectToBuilding(558, 29) - - //ObjectToBuilding(1081, ?) - //ObjectToBuilding(520, ?) - - DoorToLock(330, 558) - DoorToLock(331, 559) - DoorToLock(332, 556) - DoorToLock(333, 557) - TerminalToSpawnPad(1063, 500) - TerminalToSpawnPad(304, 501) - TerminalToInterface(520, 1081) + ObjectToBuilding(1082, 2) + ObjectToBuilding(1083, 2) + ObjectToBuilding(1084, 2) + ObjectToBuilding(1085, 2) + ObjectToBuilding(1086, 2) + ObjectToBuilding(1087, 2) + ObjectToBuilding(1088, 2) + ObjectToBuilding(1089, 2) TerminalToInterface(522, 1082) TerminalToInterface(523, 1083) TerminalToInterface(524, 1084) @@ -171,6 +145,50 @@ object Maps { TerminalToInterface(527, 1087) TerminalToInterface(528, 1088) TerminalToInterface(529, 1089) + + LocalBuilding(29, FoundationBuilder(Building.Structure)) //South Villa Gun Tower + LocalObject(ServerObjectBuilder(330, Door.Constructor(Vector3(3979.9219f, 2592.0547f, 91.140625f), Vector3(0, 0, 180)))) + LocalObject(ServerObjectBuilder(331, Door.Constructor(Vector3(3979.9219f, 2592.0547f, 111.140625f), Vector3(0, 0, 180)))) + LocalObject(ServerObjectBuilder(332, Door.Constructor(Vector3(3979.9688f, 2608.0625f, 91.140625f), Vector3(0, 0, 0)))) + LocalObject(ServerObjectBuilder(333, Door.Constructor(Vector3(3979.9688f, 2608.0625f, 111.140625f), Vector3(0, 0, 0)))) + LocalObject(ServerObjectBuilder(556, IFFLock.Constructor)) + LocalObject(ServerObjectBuilder(557, IFFLock.Constructor)) + LocalObject(ServerObjectBuilder(558, IFFLock.Constructor)) + LocalObject(ServerObjectBuilder(559, IFFLock.Constructor)) + ObjectToBuilding(330, 29) + ObjectToBuilding(331, 29) + ObjectToBuilding(332, 29) + ObjectToBuilding(333, 29) + ObjectToBuilding(556, 29) + ObjectToBuilding(557, 29) + ObjectToBuilding(558, 29) + ObjectToBuilding(559, 29) + DoorToLock(330, 558) + DoorToLock(331, 559) + DoorToLock(332, 556) + DoorToLock(333, 557) + + LocalBuilding(51, FoundationBuilder(Building.Structure)) + LocalObject(ServerObjectBuilder(304, Terminal.Constructor(dropship_vehicle_terminal))) + LocalObject(ServerObjectBuilder(292, + VehicleSpawnPad.Constructor(Vector3(3508.9844f, 2895.961f, 92.296875f), Vector3(0f, 0f, 270.0f)) + )) + ObjectToBuilding(304, 51) + ObjectToBuilding(292, 51) + TerminalToSpawnPad(304, 292) + + LocalBuilding(77, FoundationBuilder(Building.Structure)) + LocalObject(ServerObjectBuilder(1063, Terminal.Constructor(ground_vehicle_terminal))) + LocalObject(ServerObjectBuilder(706, + VehicleSpawnPad.Constructor(Vector3(3506.0f, 2820.0f, 92.0f), Vector3(0f, 0f, 270.0f)) + )) + ObjectToBuilding(1063, 77) + ObjectToBuilding(706, 77) + TerminalToSpawnPad(1063, 706) + + ObjectToBuilding(853, 2) //TODO check building_id + ObjectToBuilding(855, 2) //TODO check building_id + ObjectToBuilding(860, 2) //TODO check building_id } val map14 = new ZoneMap("map13") diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index a7c9d8f3d..cbd3698d0 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -26,6 +26,9 @@ import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, Terminal} import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, VehicleLockState} +import net.psforever.objects.serverobject.structures.{Building, WarpGate} +import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.vehicles.{AccessPermissionGroup, VehicleLockState} import net.psforever.objects.zones.{InterstellarCluster, Zone} import net.psforever.packet.game.objectcreate._ import net.psforever.types._ @@ -798,7 +801,7 @@ class WorldSessionActor extends Actor with MDCContextAware { if(interface.contains(terminal_guid.guid) && slotNumber.isDefined) { val slot = slotNumber.get log.info(s"$message - put in slot $slot") - sendResponse(AvatarImplantMessage(tplayer.GUID, 0, slot, implant_type.id)) + sendResponse(AvatarImplantMessage(tplayer.GUID, ImplantAction.Add, slot, implant_type.id)) sendResponse(ItemTransactionResultMessage(terminal_guid, TransactionType.Learn, true)) } else { @@ -833,7 +836,7 @@ class WorldSessionActor extends Actor with MDCContextAware { if(interface.contains(terminal_guid.guid) && slotNumber.isDefined) { val slot = slotNumber.get log.info(s"$tplayer is selling $implant_type - take from slot $slot") - sendResponse(AvatarImplantMessage(tplayer.GUID, 1, slot, 0)) + sendResponse(AvatarImplantMessage(tplayer.GUID, ImplantAction.Remove, slot, 0)) sendResponse(ItemTransactionResultMessage(terminal_guid, TransactionType.Sell, true)) } else { @@ -976,40 +979,34 @@ class WorldSessionActor extends Actor with MDCContextAware { case VehicleLoaded(_/*vehicle*/) => ; //currently being handled by VehicleSpawnPad.LoadVehicle during testing phase - case Zone.ClientInitialization(/*initList*/_) => - //TODO iterate over initList; for now, just do this - sendResponse( - BuildingInfoUpdateMessage( - PlanetSideGUID(6), //Ceryshen - PlanetSideGUID(2), //Anguta - 8, //80% NTU - false, //Base hacked - PlanetSideEmpire.NEUTRAL, //Base hacked by NC - 0, //10 minutes remaining for hack - PlanetSideEmpire.VS, //Base owned by VS - 0, //!! Field != 0 will cause malformed packet. See class def. - None, - PlanetSideGeneratorState.Normal, //Generator critical - true, //Respawn tubes destroyed - true, //Force dome active - 16, //Tech plant lattice benefit - 0, - Nil, //!! Field > 0 will cause malformed packet. See class def. - 0, - false, - 8, //!! Field != 8 will cause malformed packet. See class def. - None, - true, //Boosted spawn room pain field - true //Boosted generator room pain field - ) - ) - sendResponse(ContinentalLockUpdateMessage(PlanetSideGUID(13), PlanetSideEmpire.VS)) // "The VS have captured the VS Sanctuary." - sendResponse(BroadcastWarpgateUpdateMessage(PlanetSideGUID(13), PlanetSideGUID(1), false, false, true)) // VS Sanctuary: Inactive Warpgate -> Broadcast Warpgate - sendResponse(ZonePopulationUpdateMessage(PlanetSideGUID(13), 414, 138, 0, 138, 0, 138, 0, 138, 0)) + case Zone.ClientInitialization(zone) => + val continentNumber = zone.Number + val poplist = LivePlayerList.ZonePopulation(continentNumber, _ => true) + val popBO = 0 //TODO black ops test (partition) + val popTR = poplist.count(_.Faction == PlanetSideEmpire.TR) + val popNC = poplist.count(_.Faction == PlanetSideEmpire.NC) + val popVS = poplist.count(_.Faction == PlanetSideEmpire.VS) + + zone.Buildings.foreach({ case(id, building) => initBuilding(continentNumber, id, building) }) + sendResponse(ZonePopulationUpdateMessage(continentNumber, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO)) + sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NEUTRAL)) + //CaptureFlagUpdateMessage() + //VanuModuleUpdateMessage() + //ModuleLimitsMessage() + sendResponse(ZoneInfoMessage(continentNumber, true, 0)) + sendResponse(ZoneLockInfoMessage(continentNumber, false, true)) + sendResponse(ZoneForcedCavernConnectionsMessage(continentNumber, 0)) + sendResponse(HotSpotUpdateMessage(continentNumber, 1, Nil)) //normally set in bulk; should be fine doing per continent case InterstellarCluster.ClientInitializationComplete(tplayer)=> - //this will cause the client to send back a BeginZoningMessage packet (see below) - sendResponse(LoadMapMessage(continent.Map.Name, continent.Id, 40100,25,true,3770441820L)) //VS Sanctuary + //PropertyOverrideMessage + sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1)) + sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list + sendResponse(FriendsResponse(FriendAction.InitializeFriendList, 0, true, true, Nil)) + sendResponse(FriendsResponse(FriendAction.InitializeIgnoreList, 0, true, true, Nil)) + + //LoadMapMessage will cause the client to send back a BeginZoningMessage packet (see below) + sendResponse(LoadMapMessage(continent.Map.Name, continent.Id, 40100,25,true,3770441820L)) log.info("Load the now-registered player") //load the now-registered player tplayer.Spawn @@ -1023,13 +1020,35 @@ class WorldSessionActor extends Actor with MDCContextAware { val guid = tplayer.GUID LivePlayerList.Assign(continent.Number, sessionId, guid) sendResponse(SetCurrentAvatarMessage(guid,0,0)) - sendResponse(CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT)) - sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on - sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0)) - (1 to 73).foreach( i => { + (0 until DetailedCharacterData.numberOfImplantSlots(tplayer.BEP)).foreach(slot => { + sendResponse(AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 1)) //init implant slot + sendResponse(AvatarImplantMessage(guid, ImplantAction.Activation, slot, 0)) //deactivate implant + //TODO: if this implant is Installed but does not have shortcut, add to a free slot or write over slot 61/62/63 + }) + + sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0)) + //TODO: if Medkit does not have shortcut, add to a free slot or write over slot 64 + sendResponse(CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT)) + sendResponse(ChangeShortcutBankMessage(guid, 0)) + //FavoritesMessage + sendResponse(SetChatFilterMessage(ChatChannel.Local, false, ChatChannel.values.toList)) //TODO will not always be "on" like this + sendResponse(AvatarDeadStateMessage(DeadState.Nothing, 0,0, tplayer.Position, 0, true)) + sendResponse(PlanetsideAttributeMessage(guid, 53, 1)) + sendResponse(AvatarSearchCriteriaMessage(guid, List(0,0,0,0,0,0))) + (1 to 73).foreach(i => { sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(i), 67, 0)) }) + (0 to 30).foreach(i => { //TODO 30 for a new character only? + sendResponse(AvatarStatisticsMessage(2, Statistics(0L))) + }) + //AvatarAwardMessage + //DisplayAwardMessage + //SquadDefinitionActionMessage and SquadDetailDefinitionUpdateMessage + //MapObjectStateBlockMessage and ObjectCreateMessage + //TacticsMessage + + sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on case Zone.ItemFromGround(tplayer, item) => val obj_guid = item.GUID @@ -1241,19 +1260,13 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ BeginZoningMessage() => log.info("Reticulating splines ...") //map-specific initializations - //TODO continent.ClientConfiguration() - sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1)) - - sendResponse(SetEmpireMessage(PlanetSideGUID(2), PlanetSideEmpire.VS)) //HART building C - sendResponse(SetEmpireMessage(PlanetSideGUID(29), PlanetSideEmpire.NC)) //South Villa Gun Tower - + configZone(continent) //todo density sendResponse(TimeOfDayMessage(1191182336)) - sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list - - sendResponse(ZonePopulationUpdateMessage(PlanetSideGUID(6), 414, 138, 0, 138, 0, 138, 0, 138, 0)) + //custom + sendResponse(ContinentalLockUpdateMessage(13, PlanetSideEmpire.VS)) // "The VS have captured the VS Sanctuary." (1 to 255).foreach(i => { sendResponse(SetEmpireMessage(PlanetSideGUID(i), PlanetSideEmpire.VS)) }) - //render Equipment that was dropped into zone before the player arrived + //render Equipment that was dropped into zone before the player arrived continent.EquipmentOnGround.foreach(item => { val definition = item.Definition sendResponse( @@ -1408,6 +1421,9 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ SpawnRequestMessage(u1, u2, u3, u4, u5) => log.info(s"SpawnRequestMessage: $msg") + case msg @ SetChatFilterMessage(send_channel, origin, whitelist) => + log.info("SetChatFilters: " + msg) + case msg @ ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents) => // TODO: Prevents log spam, but should be handled correctly if (messagetype != ChatMessageType.CMT_TOGGLE_GM) { @@ -2019,11 +2035,12 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info("ItemTransaction: " + msg) continent.GUID(terminal_guid) match { case Some(term : Terminal) => - if(player.Faction == term.Faction) { - term.Actor ! Terminal.Request(player, msg) - } - case Some(obj : PlanetSideGameObject) => ; - case None => ; + log.info(s"ItemTransaction: ${term.Definition.Name} found") + term.Actor ! Terminal.Request(player, msg) + case Some(obj : PlanetSideGameObject) => + log.error(s"ItemTransaction: $obj is not a terminal") + case _ => + log.error(s"ItemTransaction: $terminal_guid does not exist") } case msg @ FavoritesRequest(player_guid, unk, action, line, label) => @@ -3050,6 +3067,111 @@ class WorldSessionActor extends Actor with MDCContextAware { log.error(s"DeployRequest: $obj can not transition to $state - $reason$mobileShift") } + /** + * For a given continental structure, determine the method of generating server-join client configuration packets. + * @param continentNumber the zone id + * @param buildingNumber the building id + * @param building the building object + */ + def initBuilding(continentNumber : Int, buildingNumber : Int, building : Building) : Unit = { + building match { + case _ : WarpGate => + initGate(continentNumber, buildingNumber, building) + case _ : Building => + initFacility(continentNumber, buildingNumber, building) + } + } + + /** + * For a given facility structure, configure a client by dispatching the appropriate packets. + * Pay special attention to the details of `BuildingInfoUpdateMessage` when preparing this packet. + * @see `BuildingInfoUpdateMessage` + * @see `DensityLevelUpdateMessage` + * @param continentNumber the zone id + * @param buildingNumber the building id + * @param building the building object + */ + def initFacility(continentNumber : Int, buildingNumber : Int, building : Building) : Unit = { + sendResponse( + BuildingInfoUpdateMessage( + continentNumber, //Zone + buildingNumber, //Facility + 8, //NTU% + false, //Hacked + PlanetSideEmpire.NEUTRAL, //Base hacked by + 0, //Time remaining for hack (ms) + building.Faction, //Base owned by + 0, //!! Field != 0 will cause malformed packet. See class def. + None, + PlanetSideGeneratorState.Normal, //Generator state + true, //Respawn tubes operating state + false, //Force dome state + 0, //Lattice benefits + 0, //!! Field > 0 will cause malformed packet. See class def. + Nil, + 0, + false, + 8, //!! Field != 8 will cause malformed packet. See class def. + None, + false, //Boosted spawn room pain field + false //Boosted generator room pain field + ) + ) + sendResponse(DensityLevelUpdateMessage(continentNumber, buildingNumber, List(0,0, 0,0, 0,0, 0,0))) + } + + /** + * For a given lattice warp gate structure, configure a client by dispatching the appropriate packets. + * Unlike other facilities, gates do not have complicated `BuildingInfoUpdateMessage` packets. + * Also unlike facilities, gates have an additional packet. + * @see `BuildingInfoUpdateMessage` + * @see `DensityLevelUpdateMessage` + * @see `BroadcastWarpgateUpdateMessage` + * @param continentNumber the zone id + * @param buildingNumber the building id + * @param building the building object + */ + def initGate(continentNumber : Int, buildingNumber : Int, building : Building) : Unit = { + sendResponse( + BuildingInfoUpdateMessage( + continentNumber, buildingNumber, + 0, + false, + PlanetSideEmpire.NEUTRAL, + 0, + building.Faction, + 0, + None, + PlanetSideGeneratorState.Normal, + true, + false, + 0, + 0, + Nil, + 0, + false, + 8, + None, + false, + false + ) + ) + sendResponse(DensityLevelUpdateMessage(continentNumber, buildingNumber, List(0,0, 0,0, 0,0, 0,0))) + sendResponse(BroadcastWarpgateUpdateMessage(continentNumber, buildingNumber, false, false, true)) + } + + def configZone(zone : Zone) : Unit = { + zone.Buildings.foreach({case (id, building) => + sendResponse(SetEmpireMessage(PlanetSideGUID(id), building.Faction)) + building.Amenities.foreach(amenity => { + val amenityId = amenity.GUID + sendResponse(PlanetsideAttributeMessage(amenityId, 50, 0)) + sendResponse(PlanetsideAttributeMessage(amenityId, 51, 0)) + }) + sendResponse(HackMessage(3, PlanetSideGUID(id), PlanetSideGUID(0), 0, 3212836864L, HackState.HackCleared, 8)) + }) + } + def failWithError(error : String) = { log.error(error) sendResponse(ConnectionClose()) diff --git a/pslogin/src/main/scala/Zones.scala b/pslogin/src/main/scala/Zones.scala index 60de7f9c8..352c27294 100644 --- a/pslogin/src/main/scala/Zones.scala +++ b/pslogin/src/main/scala/Zones.scala @@ -39,7 +39,7 @@ object Zones { super.Init(context) import net.psforever.types.PlanetSideEmpire - Building(2).get.Faction = PlanetSideEmpire.VS //HART building C + Buildings.values.foreach(building => { building.Faction = PlanetSideEmpire.VS }) Building(29).get.Faction = PlanetSideEmpire.NC //South Villa Gun Tower } }