Merge pull request #193 from Fate-JH/server-init

Packets, and a Warp Gate
This commit is contained in:
Fate-JH 2018-03-15 19:44:06 -04:00 committed by GitHub
commit bd76d28564
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 1031 additions and 271 deletions

View file

@ -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
}
}

View file

@ -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,

View file

@ -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 _ => ;

View file

@ -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`:<br>
* - `HackMessage`<br>
* - `PlanetsideAttributeMessage`<br>
* - `SetEmpireMessage`<br>
* - `TimeOfDayMessage`<br>
* - `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()`<br>
* `InterstallarCluster`
*/
final case class ClientInitialization(list : List[GamePacket])
final case class ClientInitialization(zone : Zone)
/**
* Overloaded constructor.

View file

@ -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)

View file

@ -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.<br>
* 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.<br>
* <br>
* 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`:<br>
* `Add` - 0-9 depending on the `ImplantType`<br>
* `Remove` - any valid value; field is not significant to this action<br>
* `Initialization` - 0 to revoke slot; 1 to allocate implant slot<br>
* `Activation` - 0 to deactivate implant; 1 to activate implant<br>
* `UnlockMessage` - 0-3 as an unlocked implant slot; display a message<br>
* `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]

View file

@ -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)
}
}
)
}

View file

@ -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)
}

View file

@ -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]
}

View file

@ -87,8 +87,8 @@ final case class Additional3(unk1 : Boolean,
* 064 - Health Module<br>
* 128 - Pain Module<br>
* `
* @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) ::

View file

@ -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]
}

View file

@ -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}

View file

@ -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)
}
}
)
}

View file

@ -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)
},
{

View file

@ -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

View file

@ -32,13 +32,13 @@ final case class HotSpotInfo(x : Float,
* <br>
* Exploration:<br>
* 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]
}

View file

@ -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.<br>
* <br>
* 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.<br>
* <br>
* The `send_channel` and the `channel_filter` values are in the following order:<br>
* Unknown, Tells, Local, Squad, Outfit, Command, Platoon, Broadcast, Squad Leader<br>
* 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)
}
)
}

View file

@ -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]
}

View file

@ -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]

View file

@ -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) ::

View file

@ -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

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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
}
}

View file

@ -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

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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)
}
}
}

View file

@ -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")

View file

@ -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())

View file

@ -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
}
}