Merge branch 'master' into destroy-display-updates

This commit is contained in:
Fate-JH 2017-03-02 07:51:35 -05:00 committed by GitHub
commit 85be04a8d8
21 changed files with 1623 additions and 63 deletions

2
.codecov.yml Normal file
View file

@ -0,0 +1,2 @@
# Too spammy for us
comment: off

View file

@ -13,6 +13,20 @@ object CryptoInterface {
final val PSCRYPTO_VERSION_MAJOR = 1
final val PSCRYPTO_VERSION_MINOR = 1
/**
NOTE: this is a single, global shared library for the entire server's crypto needs
Unfortunately, access to this object didn't used to be synchronized. I noticed that
tests for this module were hanging ("arrive at a shared secret" & "must fail to agree on
a secret..."). This heisenbug was responsible for failed Travis test runs and developer
issues as well. Using Windows minidumps, I tracked the issue to a single thread deep in
pscrypto.dll. It appeared to be executing an EB FE instruction (on Intel x86 this is
`jmp $-2` or jump to self), which is an infinite loop. The stack trace made little to no
sense and after banging my head on the wall for many hours, I assumed that something deep
in CryptoPP, the libgcc libraries, or MSVC++ was the cause (or myself). Now all access to
pscrypto functions that allocate and deallocate memory (DH_Start, RC5_Init) are synchronized.
This *appears* to have fixed the problem.
*/
final val psLib = new Library(libName)
final val RC5_BLOCK_SIZE = 8
@ -48,7 +62,7 @@ object CryptoInterface {
if(!psLib.PSCrypto_Init(PSCRYPTO_VERSION_MAJOR, PSCRYPTO_VERSION_MINOR)[Boolean]) {
throw new IllegalArgumentException(s"Invalid PSCrypto library version ${libraryMajor.getValue}.${libraryMinor.getValue}. Expected " +
s"${PSCRYPTO_VERSION_MAJOR}.${PSCRYPTO_VERSION_MINOR}")
s"$PSCRYPTO_VERSION_MAJOR.$PSCRYPTO_VERSION_MINOR")
}
}
@ -121,7 +135,9 @@ object CryptoInterface {
if(started)
throw new IllegalStateException("DH state has already been started")
dhHandle = psLib.DH_Start(modulus.toArray, generator.toArray, privateKey, publicKey)[Pointer]
psLib.synchronized {
dhHandle = psLib.DH_Start(modulus.toArray, generator.toArray, privateKey, publicKey)[Pointer]
}
if(dhHandle == Pointer.NULL)
throw new Exception("DH initialization failed!")
@ -138,7 +154,9 @@ object CryptoInterface {
if(started)
throw new IllegalStateException("DH state has already been started")
dhHandle = psLib.DH_Start_Generate(privateKey, publicKey, p, g)[Pointer]
psLib.synchronized {
dhHandle = psLib.DH_Start_Generate(privateKey, publicKey, p, g)[Pointer]
}
if(dhHandle == Pointer.NULL)
throw new Exception("DH initialization failed!")
@ -185,7 +203,9 @@ object CryptoInterface {
override def close = {
if(started) {
// TODO: zero private key material
psLib.Free_DH(dhHandle)[Unit]
psLib.synchronized {
psLib.Free_DH(dhHandle)[Unit]
}
started = false
}
@ -196,8 +216,13 @@ object CryptoInterface {
class CryptoState(val decryptionKey : ByteVector,
val encryptionKey : ByteVector) extends IFinalizable {
// Note that the keys must be returned as primitive Arrays for JNA to work
val encCryptoHandle = psLib.RC5_Init(encryptionKey.toArray, encryptionKey.length, true)[Pointer]
val decCryptoHandle = psLib.RC5_Init(decryptionKey.toArray, decryptionKey.length, false)[Pointer]
var encCryptoHandle : Pointer = Pointer.NULL
var decCryptoHandle : Pointer = Pointer.NULL
psLib.synchronized {
encCryptoHandle = psLib.RC5_Init(encryptionKey.toArray, encryptionKey.length, true)[Pointer]
decCryptoHandle = psLib.RC5_Init(decryptionKey.toArray, decryptionKey.length, false)[Pointer]
}
if(encCryptoHandle == Pointer.NULL)
throw new Exception("Encryption initialization failed!")
@ -234,8 +259,10 @@ object CryptoInterface {
}
override def close = {
psLib.Free_RC5(encCryptoHandle)[Unit]
psLib.Free_RC5(decCryptoHandle)[Unit]
psLib.synchronized {
psLib.Free_RC5(encCryptoHandle)[Unit]
psLib.Free_RC5(decCryptoHandle)[Unit]
}
super.close
}
}
@ -246,8 +273,8 @@ object CryptoInterface {
val encryptionMACKey : ByteVector) extends CryptoState(decryptionKey, encryptionKey) {
/**
* Performs a MAC operation over the message. Used when encrypting packets
*
* @param message
*
* @param message the input message
* @return ByteVector
*/
def macForEncrypt(message : ByteVector) : ByteVector = {
@ -256,8 +283,8 @@ object CryptoInterface {
/**
* Performs a MAC operation over the message. Used when verifying decrypted packets
*
* @param message
*
* @param message the input message
* @return ByteVector
*/
def macForDecrypt(message : ByteVector) : ByteVector = {

View file

@ -544,13 +544,13 @@ object GamePacketOpcode extends Enumeration {
case 0xbc => noDecoder(SnoopMsg)
case 0xbd => game.PlayerStateMessageUpstream.decode
case 0xbe => game.PlayerStateShiftMessage.decode
case 0xbf => noDecoder(ZipLineMessage)
case 0xbf => game.ZipLineMessage.decode
// OPCODES 0xc0-cf
case 0xc0 => noDecoder(CaptureFlagUpdateMessage)
case 0xc1 => noDecoder(VanuModuleUpdateMessage)
case 0xc2 => noDecoder(FacilityBenefitShieldChargeRequestMessage)
case 0xc3 => noDecoder(ProximityTerminalUseMessage)
case 0xc3 => game.ProximityTerminalUseMessage.decode
case 0xc4 => game.QuantityDeltaUpdateMessage.decode
case 0xc5 => noDecoder(ChainLashMessage)
case 0xc6 => game.ZoneInfoMessage.decode
@ -591,7 +591,7 @@ object GamePacketOpcode extends Enumeration {
case 0xe3 => noDecoder(ZoneForcedCavernConnectionsMessage)
case 0xe4 => noDecoder(MissionActionMessage)
case 0xe5 => noDecoder(MissionKillTriggerMessage)
case 0xe6 => noDecoder(ReplicationStreamMessage)
case 0xe6 => game.ReplicationStreamMessage.decode
case 0xe7 => game.SquadDefinitionActionMessage.decode
// 0xe8
case 0xe8 => noDecoder(SquadDetailDefinitionUpdateMessage)

View file

@ -5,9 +5,29 @@ import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, Plan
import scodec.Codec
import scodec.codecs._
/**
* Dispatched to the server when the player encounters something for the very first time in their campaign.
* For example, the first time the player rubs up against a game object with a yellow exclamation point.
* For example, the first time the player draws a specific weapon.<br>
* <br>
* When the first time events (FTE's) are received, battle experience is awarded.
* Text information about the object will be displayed.
* A certain itemized checkbox under the "Training" tab that corresponds is marked off.
* The latter list indicates all "encounter-able" game objects for which a FTE exists.
* These effects only happen once per character/campaign.
* (The Motion Sensor will occasionally erroneously display the information window on repeat encounters.
* No additional experience is given, though.)<br>
* <br>
* FTE's are recorded in a great `List` of `String`s in the middle of player `ObjectCreateMessage` data.
* Tutorial complete events are enqueued nearby.
* @param avatar_guid the player
* @param object_id the game object that triggers the event
* @param unk na
* @param event_name the string name of the event
*/
final case class AvatarFirstTimeEventMessage(avatar_guid : PlanetSideGUID,
object_guid : PlanetSideGUID,
unk1 : Long,
object_id : PlanetSideGUID,
unk : Long,
event_name : String)
extends PlanetSideGamePacket {
type Packet = AvatarFirstTimeEventMessage
@ -18,8 +38,8 @@ final case class AvatarFirstTimeEventMessage(avatar_guid : PlanetSideGUID,
object AvatarFirstTimeEventMessage extends Marshallable[AvatarFirstTimeEventMessage] {
implicit val codec : Codec[AvatarFirstTimeEventMessage] = (
("avatar_guid" | PlanetSideGUID.codec) ::
("object_guid" | PlanetSideGUID.codec) ::
("unk1" | uint32L ) ::
("object_id" | PlanetSideGUID.codec) ::
("unk" | uint32L ) ::
("event_name" | PacketHelpers.encodedString)
).as[AvatarFirstTimeEventMessage]
}

View file

@ -34,10 +34,9 @@ object BugType extends Enumeration {
* @param version_date the date the client was compiled
* @param bug_type the kind of bug that took place
* @param repeatable whether the bug is repeatable
* @param unk na;
* always 0?
* @param location 0 when "other location", 2 when "current location"
* @param zone which zone the bug took place
* @param pos the location where ther bug took place
* @param pos the x y z location where the bug took place
* @param summary a short explanation of the bug
* @param desc a detailed explanation of the bug
*/
@ -46,7 +45,7 @@ final case class BugReportMessage(version_major : Long,
version_date : String,
bug_type : BugType.Value,
repeatable : Boolean,
unk : Int,
location : Int,
zone : Int,
pos : Vector3,
summary : String,
@ -65,7 +64,7 @@ object BugReportMessage extends Marshallable[BugReportMessage] {
("bug_type" | BugType.codec) ::
ignore(3) ::
("repeatable" | bool) ::
("unk" | uint4L) ::
("location" | uint4L) ::
("zone" | uint8L) ::
("pos" | Vector3.codec_pos) ::
("summary" | PacketHelpers.encodedWideStringAligned(4)) ::

View file

@ -2,6 +2,7 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.PlanetSideEmpire
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}

View file

@ -2,8 +2,10 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import scodec.Codec
import net.psforever.types.PlanetSideEmpire
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
object CharacterGender extends Enumeration(1) {
type Type = Value
@ -34,5 +36,17 @@ object CharacterCreateRequestMessage extends Marshallable[CharacterCreateRequest
("voiceId" | uint8L) ::
("gender" | CharacterGender.codec) ::
("empire" | PlanetSideEmpire.codec)
).as[CharacterCreateRequestMessage]
).exmap[CharacterCreateRequestMessage] (
{
case name :: headId :: voiceId :: gender :: empire :: HNil =>
Attempt.successful(CharacterCreateRequestMessage(name, headId, voiceId, gender, empire))
},
{
case CharacterCreateRequestMessage(name, _, _, _, PlanetSideEmpire.NEUTRAL) =>
Attempt.failure(Err(s"character $name's faction can not declare as neutral"))
case CharacterCreateRequestMessage(name, headId, voiceId, gender, empire) =>
Attempt.successful(name :: headId :: voiceId :: gender :: empire :: HNil)
}
)
}

View file

@ -2,21 +2,21 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.PlanetSideEmpire
import scodec.Codec
import scodec.codecs._
/**
* Create a dispatched game packet that instructs the client to update the user about continents that are conquered.
*
* Create a dispatched game packet that instructs the client to update the user about continents that are conquered.<br>
* <br>
* 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, 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 empire identifies the empire; this value is matchable against PlanetSideEmpire
* @param empire identifies the empire
*/
final case class ContinentalLockUpdateMessage(continent_guid : PlanetSideGUID,
empire : PlanetSideEmpire.Value) // 00 for TR, 40 for NC, 80 for VS; C0 generates no message
empire : PlanetSideEmpire.Value)
extends PlanetSideGamePacket {
type Packet = ContinentalLockUpdateMessage
def opcode = GamePacketOpcode.ContinentalLockUpdateMessage

View file

@ -2,6 +2,7 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.PlanetSideEmpire
import scodec.Codec
import scodec.codecs._
@ -24,8 +25,8 @@ import scodec.codecs._
* In the case of absentee kills, for example, where there is no killer listed, this field has been zero'd (`00000000`).<br>
* <br>
* The faction affiliation is different from the normal way `PlanetSideEmpire` values are recorded.
* The higher nibble will reflect the first part of the `PlanetSideEmpire` value - `0` for TR, `4` for NC `8` for TR, `C` for Neutral/BOPs.
* An extra `20` will be added if the player is in a vehicle or turret at the time - `2` for TR, `6` for NC, `A` for VS, `E` for Neutral/BOPs.
* The higher nibble will reflect the first part of the `PlanetSideEmpire` value.
* An extra `20` will be added if the player is in a vehicle or turret at the time.
* When marked as being in a vehicle or turret, the player's name will be enclosed within square brackets.
* The length of the player's name found at the start of the wide character string does not reflect whether or not there will be square brackets (fortunately).<br>
* <br>
@ -36,15 +37,13 @@ import scodec.codecs._
* It is also unknown what the two bytes preceding `method` specify, as changing them does nothing to the displayed message.
* @param killer the name of the player who did the killing
* @param killer_unk See above
* @param killer_empire the empire affiliation of the killer:
* 0 - TR, 1 - NC, 2 - VS, 3 - Neutral/BOPs
* @param killer_empire the empire affiliation of the killer
* @param killer_inVehicle true, if the killer was in a vehicle at the time of the kill; false, otherwise
* @param unk na; but does not like being set to 0
* @param method modifies the icon in the message, related to the way the victim was killed
* @param victim the name of the player who was killed
* @param victim_unk See above
* @param victim_empire the empire affiliation of the victim:
* 0 - TR, 1 - NC, 2 - VS, 3 - Neutral/BOPs
* @param victim_empire the empire affiliation of the victim
* @param victim_inVehicle true, if the victim was in a vehicle when he was killed; false, otherwise
*/
final case class DestroyDisplayMessage(killer : String,

View file

@ -0,0 +1,35 @@
// Copyright (c) 2016 PSForever.net to present
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
/**
* The player's avatar has moved in relation to a set piece that reacts with the player due to his proximity.<br>
* <br>
* Elements that exhibit this behavior include Repair/Rearm Silos in facility courtyards and various cavern crystals.
* The packets are only dispatched when it is appropriate for the player to be affected.<br>
* <br>
* Exploration:<br>
* Packets where the bytes for the player's GUID are blank exist.
* @param player_guid the player
* @param object_guid the object whose functionality is triggered
* @param unk na
*/
final case class ProximityTerminalUseMessage(player_guid : PlanetSideGUID,
object_guid : PlanetSideGUID,
unk : Boolean)
extends PlanetSideGamePacket {
type Packet = ProximityTerminalUseMessage
def opcode = GamePacketOpcode.ProximityTerminalUseMessage
def encode = ProximityTerminalUseMessage.encode(this)
}
object ProximityTerminalUseMessage extends Marshallable[ProximityTerminalUseMessage] {
implicit val codec : Codec[ProximityTerminalUseMessage] = (
("player_guid" | PlanetSideGUID.codec) ::
("object_guid" | PlanetSideGUID.codec) ::
("unk" | bool)
).as[ProximityTerminalUseMessage]
}

View file

@ -0,0 +1,660 @@
// Copyright (c) 2016 PSForever.net to present
package net.psforever.packet.game
import net.psforever.newcodecs.newcodecs
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* Maintains squad information changes performed by this listing.
* Only certain information will be transmitted depending on the purpose of the packet.
* @param leader the name of the squad leader as a wide character string, or `None` if not applicable
* @param task the task the squad is trying to perform as a wide character string, or `None` if not applicable
* @param zone_id the continent on which the squad is acting, or `None` if not applicable
* @param size the current size of the squad, or `None` if not applicable;
* "can" be greater than `capacity`, though with issues
* @param capacity the maximum number of members that the squad can tolerate, or `None` if not applicable;
* normal count is 10;
* maximum is 15 but naturally can not be assigned that many
* @param squad_guid a GUID associated with the squad, used to recover the squad definition, or `None` if not applicable;
* sometimes it is defined but is still not applicable
*/
final case class SquadInfo(leader : Option[String],
task : Option[String],
zone_id : Option[PlanetSideZoneID],
size : Option[Int],
capacity : Option[Int],
squad_guid : Option[PlanetSideGUID] = None)
/**
* Define three fields determining the purpose of data in this listing.<br>
* <br>
* The third field `unk3` is not always be defined and will be supplanted by the squad (definition) GUID during initialization and a full update.<br>
* <br>
* Actions:<br>
* `unk1&nbsp;unk2&nbsp;&nbsp;unk3`<br>
* `0&nbsp;&nbsp;&nbsp;&nbsp;true&nbsp;&nbsp;4 -- `Remove a squad from listing<br>
* `128&nbsp;&nbsp;true&nbsp;&nbsp;0 -- `Update a squad's leader<br>
* `128&nbsp;&nbsp;true&nbsp;&nbsp;1 -- `Update a squad's task or continent<br>
* `128&nbsp;&nbsp;true&nbsp;&nbsp;2 -- `Update a squad's size<br>
* `129&nbsp;&nbsp;false&nbsp;0 -- `Update a squad's leader or size<br>
* `129&nbsp;&nbsp;false&nbsp;1 -- `Update a squad's task and continent<br>
* `131&nbsp;&nbsp;false&nbsp;X -- `Add all squads during initialization / update all information pertaining to this squad
* @param unk1 na
* @param unk2 na
* @param unk3 na;
* not always defined
* @param info information pertaining to this squad listing
*/
//TODO when these unk# values are better understood, transform SquadHeader to streamline the actions to be performed
final case class SquadHeader(unk1 : Int,
unk2 : Boolean,
unk3 : Option[Int],
info : Option[SquadInfo] = None)
/**
* An indexed entry in the listing of squads.<br>
* <br>
* Squad listing indices are not an arbitrary order.
* The server communicates changes to the client by referencing a squad's listing index, defined at the time of list initialization.
* Once initialized, each client may organize their squads however they wish, e.g., by leader, by task, etc., without compromising this index.
* During the list initialization process, the entries must always follow numerical order, increasing from `0`.
* During any other operation, the entries may be prefixed with whichever index is necessary to indicate the squad listing in question.
* @param index the index of this listing;
* first entry should be 0, and subsequent valid entries are sequentially incremental;
* last entry is always a placeholder with index 255
* @param listing the data for this entry, defining both the actions and the pertinent squad information
*/
final case class SquadListing(index : Int = 255,
listing : Option[SquadHeader] = None)
/**
* Modify the list of squads available to a given player.
* The squad list updates in real time rather than just whenever a player opens the squad information window.<br>
* <br>
* The four main operations are: initializing the list, updating entries in the list, removing entries from the list, and clearing the list.
* The process of initializing the list and clearing the list actually are performed by similar behavior.
* Squads would just not be added after the list clears.
* Moreover, removing entries from the list overrides the behavior to update entries in the list.
* The two-three codes per entry (see `SquadHeader`) are important for determining the effect of a specific entry.
* As of the moment, the important details of the behaviors is that they modify how the packet is encoded and padded.<br>
* <br>
* Referring to information in `SquadListing`, entries are identified by their index in the list.
* This is followed by a coded section that indicates what action the entry will execute on that squad listing.
* After the "coded action" section is the "general information" section where the data for the change is specified.
* In this manner, all the entries will have a knowable length.<br>
* <br>
* The total number of entries in a packet is not known until they have all been parsed.
* During the list initialization process, the entries must be in ascending order of index.
* Otherwise, the specific index of the squad listing is referenced.
* The minimum number of entries is "no entries."
* The maximum number of entries is supposedly 254.
* The last item is always the index 255 and this is interpreted as the end of the stream.<br>
* <br>
* When no updates are provided, the client loads a default (but invalid) selection of data comprising four squads:<br>
* `0&nbsp;&nbsp;Holeesh&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;another purpose&nbsp;&nbsp;Desolation&nbsp;&nbsp;6/7`<br>
* `1&nbsp;&nbsp;Korealis&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;another purpose&nbsp;&nbsp;Drugaskan&nbsp;&nbsp;&nbsp;10/10`<br>
* `2&nbsp;&nbsp;PsychoSanta&nbsp;&nbsp;blah blah blah&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;10/10`<br>
* `3&nbsp;&nbsp;Squishling&nbsp;&nbsp;&nbsp;another purpose&nbsp;&nbsp;Cyssor&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;8/10`<br>
* The last entry is entirely in green text.<br>
* <br>
* Behaviors:<br>
* `behavior behavior2`<br>
* `1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;X&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; `Update where initial entry removes a squad from the list<br>
* `5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; `Clear squad list and initialize new squad list<br>
* `5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; `Clear squad list (ransitions directly into 255-entry)<br>
* `6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;X&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; `Update a squad in the list
* @param behavior a code that suggests the primary purpose of the data in this packet
* @param behavior2 during initialization, this code is read;
* it supplements the normal `behavior` and is typically is an "update" code
* @param entries a `Vector` of the squad listings
*/
final case class ReplicationStreamMessage(behavior : Int,
behavior2 : Option[Int],
entries : Vector[SquadListing])
extends PlanetSideGamePacket {
type Packet = ReplicationStreamMessage
def opcode = GamePacketOpcode.ReplicationStreamMessage
def encode = ReplicationStreamMessage.encode(this)
}
object SquadInfo {
/**
* Alternate constructor for `SquadInfo` that ignores the `Option` requirement for the fields.<br>
* <br>
* This constructor is not actually used at the moment.
* @param leader the name of the squad leader
* @param task the task the squad is trying to perform
* @param continent_guid the continent on which the squad is acting
* @param size the current size of the squad
* @param capacity the maximum number of members that the squad can tolerate
* @return a `SquadInfo` object
*/
def apply(leader : String, task : String, continent_guid : PlanetSideZoneID, size : Int, capacity : Int) : SquadInfo = {
SquadInfo(Some(leader), Some(task), Some(continent_guid), Some(size), Some(capacity))
}
/**
* Alternate constructor for `SquadInfo` that ignores the `Option` requirement for the fields.<br>
* <br>
* This constructor is used by the `initCodec`, `alt_initCodec`, and `allCodec`.
* @param leader the name of the squad leader
* @param task the task the squad is trying to perform
* @param continent_guid the continent on which the squad is acting
* @param size the current size of the squad
* @param capacity the maximum number of members that the squad can tolerate
* @param squad_guid a GUID associated with the squad, used to recover the squad definition
* @return a `SquadInfo` object
*/
def apply(leader : String, task : String, continent_guid : PlanetSideZoneID, size : Int, capacity : Int, squad_guid : PlanetSideGUID) : SquadInfo = {
SquadInfo(Some(leader), Some(task), Some(continent_guid), Some(size), Some(capacity), Some(squad_guid))
}
/**
* Alternate constructor for `SquadInfo` that ignores the `Option` requirement for the important field.<br>
* <br>
* This constructor is used by `leaderCodec`.
* Two of the fields normally are `Option[String]`s.
* Only the `leader` field in this packet is a `String`, giving the method a distinct signature.
* The other field - an `Option[String]` for `task` - can still be set if passed.<br>
* <br>
* Recommended use: `SquadInfo(leader, None)`
* @param leader the name of the squad leader
* @param task the task the squad is trying to perform, if not `None`
* @return a `SquadInfo` object
*/
def apply(leader : String, task : Option[String]) : SquadInfo = {
SquadInfo(Some(leader), task, None, None, None)
}
/**
* Alternate constructor for `SquadInfo` that ignores the `Option` requirement for the important field.<br>
* <br>
* This constructor is used by `taskOrContinentCodec`.
* Two of the fields normally are `Option[String]`s.
* Only the `task` field in this packet is a `String`, giving the method a distinct signature.
* The other field - an `Option[String]` for `leader` - can still be set if passed.<br>
* <br>
* Recommended use: `SquadInfo(None, task)`
* @param leader the name of the squad leader, if not `None`
* @param task the task the squad is trying to perform
* @return a `SquadInfo` object
*/
def apply(leader : Option[String], task : String) : SquadInfo = {
SquadInfo(leader, Some(task), None, None, None)
}
/**
* Alternate constructor for `SquadInfo` that ignores the `Option` requirement for the field.<br>
* <br>
* This constructor is used by `taskOrContinentCodec`.
* @param continent_guid the continent on which the squad is acting
* @return a `SquadInfo` object
*/
def apply(continent_guid : PlanetSideZoneID) : SquadInfo = {
SquadInfo(None, None, Some(continent_guid), None, None)
}
/**
* Alternate constructor for `SquadInfo` that ignores the `Option` requirement for the important field.<br>
* <br>
* This constructor is used by `sizeCodec`.
* Two of the fields normally are `Option[Int]`s.
* Only the `size` field in this packet is an `Int`, giving the method a distinct signature.<br>
* <br>
* Recommended use: `SquadInfo(size, None)`<br>
* <br>
* Exploration:<br>
* We do not currently know any `SquadHeader` action codes for adjusting `capacity`.
* @param size the current size of the squad
* @param capacity the maximum number of members that the squad can tolerate, if not `None`
* @return a `SquadInfo` object
*/
def apply(size : Int, capacity : Option[Int]) : SquadInfo = {
SquadInfo(None, None, None, Some(size), None)
}
/**
* Alternate constructor for `SquadInfo` that ignores the `Option` requirement for the important field.<br>
* <br>
* This constructor is not actually used at the moment.
* Two of the fields normally are `Option[Int]`s.
* Only the `capacity` field in this packet is an `Int`, giving the method a distinct signature.<br>
* <br>
* Recommended use: `SquadInfo(None, capacity)`<br>
* <br>
* Exploration:<br>
* We do not currently know any `SquadHeader` action codes for adjusting `capacity`.
* @param size the current size of the squad
* @param capacity the maximum number of members that the squad can tolerate, if not `None`
* @return a `SquadInfo` object
*/
def apply(size : Option[Int], capacity : Int) : SquadInfo = {
SquadInfo(None, None, None, None, Some(capacity))
}
/**
* Alternate constructor for `SquadInfo` that ignores the `Option` requirement for the fields.<br>
* <br>
* This constructor is used by `leaderSizeCodec`.
* @param leader the name of the squad leader
* @param size the current size of the squad
* @return a `SquadInfo` object
*/
def apply(leader : String, size : Int) : SquadInfo = {
SquadInfo(Some(leader), None, None, Some(size), None)
}
/**
* Alternate constructor for `SquadInfo` that ignores the Option requirement for the fields.<br>
* <br>
* This constructor is used by `taskAndContinentCodec`.
* @param task the task the squad is trying to perform
* @param continent_guid the continent on which the squad is acting
* @return a `SquadInfo` object
*/
def apply(task : String, continent_guid : PlanetSideZoneID) : SquadInfo = {
SquadInfo(None, Some(task), Some(continent_guid), None, None, None)
}
}
object SquadHeader {
/**
* Alternate constructor for SquadInfo that ignores the Option requirement for the `info` field.
* @param unk1 na
* @param unk2 na
* @param unk3 na; not always defined
* @param info information pertaining to this squad listing
*/
def apply(unk1 : Int, unk2 : Boolean, unk3 : Option[Int], info : SquadInfo) : SquadHeader = {
SquadHeader(unk1, unk2, unk3, Some(info))
}
/**
* `squadPattern` completes the fields for the `SquadHeader` class.
* It translates an indeterminate number of bit regions into something that can be processed as an `Option[SquadInfo]`.
*/
private type squadPattern = Option[SquadInfo] :: HNil
/**
* `Codec` for reading `SquadInfo` data from the first entry from a packet with squad list initialization entries.
*/
private val initCodec : Codec[squadPattern] = (
("squad_guid" | PlanetSideGUID.codec) ::
("leader" | PacketHelpers.encodedWideString) ::
("task" | PacketHelpers.encodedWideString) ::
("continent_guid" | PlanetSideZoneID.codec) ::
("size" | uint4L) ::
("capacity" | uint4L)
).exmap[squadPattern] (
{
case sguid :: lead :: tsk :: cguid :: sz :: cap :: HNil =>
Attempt.successful(Some(SquadInfo(lead, tsk, cguid, sz, cap, sguid)) :: HNil)
case _ =>
Attempt.failure(Err("failed to decode squad data for adding [A] a squad entry"))
},
{
case Some(SquadInfo(Some(lead), Some(tsk), Some(cguid), Some(sz), Some(cap), Some(sguid))) :: HNil =>
Attempt.successful(sguid :: lead :: tsk :: cguid :: sz :: cap :: HNil)
case _ =>
Attempt.failure(Err("failed to encode squad data for adding [A] a squad entry"))
}
)
/**
* `Codec` for reading `SquadInfo` data from all entries other than the first from a packet with squad list initialization entries.
*/
private val alt_initCodec : Codec[squadPattern] = (
("squad_guid" | PlanetSideGUID.codec) ::
("leader" | PacketHelpers.encodedWideStringAligned(7)) ::
("task" | PacketHelpers.encodedWideString) ::
("continent_guid" | PlanetSideZoneID.codec) ::
("size" | uint4L) ::
("capacity" | uint4L)
).exmap[squadPattern] (
{
case sguid :: lead :: tsk :: cguid :: sz :: cap :: HNil =>
Attempt.successful(Some(SquadInfo(lead, tsk, cguid, sz, cap, sguid)) :: HNil)
case _ =>
Attempt.failure(Err("failed to decode squad data for adding [B] a squad entry"))
},
{
case Some(SquadInfo(Some(lead), Some(tsk), Some(cguid), Some(sz), Some(cap), Some(sguid))) :: HNil =>
Attempt.successful(sguid :: lead :: tsk :: cguid :: sz :: cap :: HNil)
case _ =>
Attempt.failure(Err("failed to encode squad data for adding [B] a squad entry"))
}
)
/**
* `Codec` for reading the `SquadInfo` data in an "update all squad data" entry.
*/
private val allCodec : Codec[squadPattern] = (
("squad_guid" | PlanetSideGUID.codec) ::
("leader" | PacketHelpers.encodedWideStringAligned(3)) ::
("task" | PacketHelpers.encodedWideString) ::
("continent_guid" | PlanetSideZoneID.codec) ::
("size" | uint4L) ::
("capacity" | uint4L)
).exmap[squadPattern] (
{
case sguid :: lead :: tsk :: cguid :: sz :: cap :: HNil =>
Attempt.successful(Some(SquadInfo(lead, tsk, cguid, sz, cap, sguid)) :: HNil)
case _ =>
Attempt.failure(Err("failed to decode squad data for updating a squad entry"))
},
{
case Some(SquadInfo(Some(lead), Some(tsk), Some(cguid), Some(sz), Some(cap), Some(sguid))) :: HNil =>
Attempt.successful(sguid :: lead :: tsk :: cguid :: sz :: cap :: HNil)
case _ =>
Attempt.failure(Err("failed to encode squad data for updating a squad entry"))
}
)
/**
* `Codec` for reading the `SquadInfo` data in an "update squad leader" entry.
*/
private val leaderCodec : Codec[squadPattern] = (
bool ::
("leader" | PacketHelpers.encodedWideStringAligned(7))
).exmap[squadPattern] (
{
case true :: lead :: HNil =>
Attempt.successful(Some(SquadInfo(lead, None)) :: HNil)
case _ =>
Attempt.failure(Err("failed to decode squad data for a leader name"))
},
{
case Some(SquadInfo(Some(lead), _, _, _, _, _)) :: HNil =>
Attempt.successful(true :: lead :: HNil)
case _ =>
Attempt.failure(Err("failed to encode squad data for a leader name"))
}
)
/**
* `Codec` for reading the `SquadInfo` data in an "update squad task or continent" entry.
*/
private val taskOrContinentCodec : Codec[squadPattern] = (
bool >>:~ { path =>
conditional(path, "continent_guid" | PlanetSideZoneID.codec) ::
conditional(!path, "task" | PacketHelpers.encodedWideStringAligned(7))
}
).exmap[squadPattern] (
{
case true :: Some(cguid) :: _ :: HNil =>
Attempt.successful(Some(SquadInfo(cguid)) :: HNil)
case true :: None :: _ :: HNil =>
Attempt.failure(Err("failed to decode squad data for a task - no continent"))
case false :: _ :: Some(tsk) :: HNil =>
Attempt.successful(Some(SquadInfo(None, tsk)) :: HNil)
case false :: _ :: None :: HNil =>
Attempt.failure(Err("failed to decode squad data for a task - no task"))
},
{
case Some(SquadInfo(_, None, Some(cguid), _, _, _)) :: HNil =>
Attempt.successful(true :: Some(cguid) :: None :: HNil)
case Some(SquadInfo(_, Some(tsk), None, _, _, _)) :: HNil =>
Attempt.successful(false :: None :: Some(tsk) :: HNil)
case Some(SquadInfo(_, Some(_), Some(_), _, _, _)) :: HNil =>
Attempt.failure(Err("failed to encode squad data for either a task or a continent - multiple encodings reachable"))
case _ =>
Attempt.failure(Err("failed to encode squad data for either a task or a continent"))
}
)
/**
* `Codec` for reading the `SquadInfo` data in an "update squad size" entry.
*/
private val sizeCodec : Codec[squadPattern] = (
bool ::
("size" | uint4L)
).exmap[squadPattern] (
{
case false :: sz :: HNil =>
Attempt.successful(Some(SquadInfo(sz, None)) :: HNil)
case _ =>
Attempt.failure(Err("failed to decode squad data for a size"))
},
{
case Some(SquadInfo(_, _, _, Some(sz), _, _)) :: HNil =>
Attempt.successful(false :: sz :: HNil)
case _ =>
Attempt.failure(Err("failed to encode squad data for a size"))
}
)
/**
* `Codec` for reading the `SquadInfo` data in an "update squad leader and size" entry.
*/
private val leaderSizeCodec : Codec[squadPattern] = (
bool ::
("leader" | PacketHelpers.encodedWideStringAligned(7)) ::
uint4L ::
("size" | uint4L)
).exmap[squadPattern] (
{
case true :: lead :: 4 :: sz :: HNil =>
Attempt.successful(Some(SquadInfo(lead, sz)) :: HNil)
case _ =>
Attempt.failure(Err("failed to decode squad data for a leader and a size"))
},
{
case Some(SquadInfo(Some(lead), _, _, Some(sz), _, _)) :: HNil =>
Attempt.successful(true :: lead :: 4 :: sz :: HNil)
case _ =>
Attempt.failure(Err("failed to encode squad data for a leader and a size"))
}
)
/**
* `Codec` for reading the `SquadInfo` data in an "update squad task and continent" entry.
*/
private val taskAndContinentCodec : Codec[squadPattern] = (
bool ::
("task" | PacketHelpers.encodedWideStringAligned(7)) ::
uintL(3) ::
bool ::
("continent_guid" | PlanetSideZoneID.codec)
).exmap[squadPattern] (
{
case false :: tsk :: 1 :: true :: cguid :: HNil =>
Attempt.successful(Some(SquadInfo(tsk, cguid)) :: HNil)
case _ =>
Attempt.failure(Err("failed to decode squad data for a task and a continent"))
},
{
case Some(SquadInfo(_, Some(tsk), Some(cguid), _, _, _)) :: HNil =>
Attempt.successful(false :: tsk :: 1 :: true :: cguid :: HNil)
case _ =>
Attempt.failure(Err("failed to encode squad data for a task and a continent"))
}
)
/**
* Codec for reading the `SquadInfo` data in a "remove squad from list" entry.
* This `Codec` is unique because it is considered a valid `Codec` that does not read any bit data.
* The `conditional` will always return `None` because its determining conditional statement is explicitly `false`.
*/
private val removeCodec : Codec[squadPattern] = conditional(false, bool).exmap[squadPattern] (
{
case None | _ =>
Attempt.successful(None :: HNil)
},
{
case None :: HNil | _ =>
Attempt.successful(None)
}
)
/**
* `Codec` for failing to determine a valid `Codec` based on the entry data.
* This `Codec` is an invalid codec that does not read any bit data.
* The `conditional` will always return `None` because its determining conditional statement is explicitly `false`.
*/
private val failureCodec : Codec[squadPattern] = conditional(false, bool).exmap[squadPattern] (
{
case None | _ =>
Attempt.failure(Err("decoding with unhandled codec"))
},
{
case None :: HNil | _ =>
Attempt.failure(Err("encoding with unhandled codec"))
}
)
/**
* Select the `Codec` to translate bit data in this packet into an `Option[SquadInfo]` using other fields of the same packet.
* Refer to comments for the primary `case class` constructor for `SquadHeader` to explain how the conditions in this function path.
* @param a na
* @param b na
* @param c na; may be `None`
* @param optionalCodec a to-be-defined `Codec` that is determined by the suggested mood of the packet and listing of the squad;
* despite the name, actually a required parameter
* @return a `Codec` that corresponds to a `squadPattern` translation
*/
private def selectCodec(a : Int, b : Boolean, c : Option[Int], optionalCodec : Codec[squadPattern]) : Codec[squadPattern] = {
if(c.isDefined) {
val cVal = c.get
if(a == 0 && b)
if(cVal == 4)
return removeCodec
if(a == 128 && b) {
if(cVal == 0)
return leaderCodec
else if(cVal == 1)
return taskOrContinentCodec
else if(cVal == 2)
return sizeCodec
}
else if(a == 129 && !b) {
if(cVal == 0)
return leaderSizeCodec
else if(cVal == 1)
return taskAndContinentCodec
}
}
else {
if(a == 131 && !b)
return optionalCodec
}
//we've not encountered a valid Codec
failureCodec
}
/**
* `Codec` for standard `SquadHeader` entries.
*/
val codec : Codec[SquadHeader] = (
("unk1" | uint8L) >>:~ { unk1 =>
("unk2" | bool) >>:~ { unk2 =>
conditional(unk1 != 131, "unk3" | uintL(3)) >>:~ { unk3 =>
selectCodec(unk1, unk2, unk3, allCodec)
}
}
}).as[SquadHeader]
/**
* `Codec` for types of `SquadHeader` initializations.
*/
val init_codec : Codec[SquadHeader] = (
("unk1" | uint8L) >>:~ { unk1 =>
("unk2" | bool) >>:~ { unk2 =>
conditional(unk1 != 131, "unk3" | uintL(3)) >>:~ { unk3 =>
selectCodec(unk1, unk2, unk3, initCodec)
}
}
}).as[SquadHeader]
/**
* Alternate `Codec` for types of `SquadHeader` initializations.
*/
val alt_init_codec : Codec[SquadHeader] = (
("unk1" | uint8L) >>:~ { unk1 =>
("unk2" | bool) >>:~ { unk2 =>
conditional(unk1 != 131, "unk3" | uintL(3)) >>:~ { unk3 =>
selectCodec(unk1, unk2, unk3, alt_initCodec)
}
}
}).as[SquadHeader]
}
object SquadListing {
/**
* `Codec` for standard `SquadListing` entries.
*/
val codec : Codec[SquadListing] = (
("index" | uint8L) >>:~ { index =>
conditional(index < 255, "listing" | SquadHeader.codec) ::
conditional(index == 255, bits) //consume n < 8 bits padding the tail entry, else vector will try to operate on invalid data
}).xmap[SquadListing] (
{
case ndx :: lstng :: _ :: HNil =>
SquadListing(ndx, lstng)
},
{
case SquadListing(ndx, lstng) =>
ndx :: lstng :: None :: HNil
}
)
/**
* `Codec` for branching types of `SquadListing` initializations.
*/
val init_codec : Codec[SquadListing] = (
("index" | uint8L) >>:~ { index =>
conditional(index < 255,
newcodecs.binary_choice(index == 0,
"listing" | SquadHeader.init_codec,
"listing" | SquadHeader.alt_init_codec)
) ::
conditional(index == 255, bits) //consume n < 8 bits padding the tail entry, else vector will try to operate on invalid data
}).xmap[SquadListing] (
{
case ndx :: lstng :: _ :: HNil =>
SquadListing(ndx, lstng)
},
{
case SquadListing(ndx, lstng) =>
ndx :: lstng :: None :: HNil
}
)
}
object ReplicationStreamMessage extends Marshallable[ReplicationStreamMessage] {
implicit val codec : Codec[ReplicationStreamMessage] = (
("behavior" | uintL(3)) >>:~ { behavior =>
conditional(behavior == 5, "behavior2" | uintL(3)) ::
conditional(behavior != 1, bool) ::
newcodecs.binary_choice(behavior != 5,
"entries" | vector(SquadListing.codec),
"entries" | vector(SquadListing.init_codec)
)
}).xmap[ReplicationStreamMessage] (
{
case bhvr :: bhvr2 :: _ :: lst :: HNil =>
ReplicationStreamMessage(bhvr, bhvr2, lst)
},
{
case ReplicationStreamMessage(1, bhvr2, lst) =>
1 :: bhvr2 :: None :: lst :: HNil
case ReplicationStreamMessage(bhvr, bhvr2, lst) =>
bhvr :: bhvr2 :: Some(false) :: lst :: HNil
}
)
}
/*
+-> SquadListing.codec -------> SquadHeader.codec ----------+
| |
| |
ReplicationStream.codec -+ |
| |
| +-> SquadHeader.init_codec -----+-> SquadInfo
| | |
+-> SquadListing.initCodec -+ |
| |
+-> SquadHeader.alt_init_codec -+
*/

View file

@ -2,6 +2,7 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.PlanetSideEmpire
import scodec.Codec
import scodec.codecs._

View file

@ -4,6 +4,7 @@ package net.psforever.packet.game
import java.net.{InetAddress, InetSocketAddress}
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.PlanetSideEmpire
import scodec._
import scodec.bits._
import scodec.codecs._
@ -22,13 +23,6 @@ object ServerType extends Enumeration(1) {
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint8L)
}
object PlanetSideEmpire extends Enumeration {
type Type = Value
val TR, NC, VS, NEUTRAL = Value
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint2L)
}
final case class WorldConnectionInfo(address : InetSocketAddress)
final case class WorldInformation(name : String, status : WorldStatus.Value,

View file

@ -0,0 +1,74 @@
// Copyright (c) 2016 PSForever.net to present
package net.psforever.packet.game
import net.psforever.newcodecs.newcodecs
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
import shapeless.{::, HNil}
/**
* Dispatched by the client when the player is interacting with a zip line.
* Dispatched by the server to instruct the client to use the zip line.
* Cavern teleportation rings also count as "zip lines" as far as the game is concerned, in that they use this packet.<br>
* <br>
* Action:<br>
* `0 - Attach to a node`<br>
* `1 - Arrived at destination`<br>
* `2 - Forcibly detach from zip line in mid-transit`
* @param player_guid the player
* @param origin_side whether this corresponds with the "entry" or the "exit" of the zip line, as per the direction of the light pulse visuals
* @param action how the player interacts with the zip line
* @param guid a number that is consistent to a terminus
* @param x the x-coordinate of the point where the player is interacting with the zip line
* @param y the y-coordinate of the point where the player is interacting with the zip line
* @param z the z-coordinate of the point where the player is interacting with the zip line
*/
final case class ZipLineMessage(player_guid : PlanetSideGUID,
origin_side : Boolean,
action : Int,
guid : Long,
x : Float,
y : Float,
z : Float)
extends PlanetSideGamePacket {
type Packet = ZipLineMessage
def opcode = GamePacketOpcode.ZipLineMessage
def encode = ZipLineMessage.encode(this)
}
object ZipLineMessage extends Marshallable[ZipLineMessage] {
type threeFloatsPattern = Float :: Float :: Float :: HNil
/**
* A `Codec` for when three `Float` values are to be read or written.
*/
val threeFloatValues : Codec[threeFloatsPattern] = (
("x" | floatL) ::
("y" | floatL) ::
("z" | floatL)
).as[threeFloatsPattern]
/**
* A `Codec` for when there are no extra `Float` values present.
*/
val noFloatValues : Codec[threeFloatsPattern] = ignore(0).xmap[threeFloatsPattern] (
{
case () =>
0f :: 0f :: 0f :: HNil
},
{
case _ =>
()
}
)
implicit val codec : Codec[ZipLineMessage] = (
("player_guid" | PlanetSideGUID.codec) >>:~ { player =>
("origin_side" | bool) ::
("action" | uint2) ::
("id" | uint32L) ::
newcodecs.binary_choice(player.guid > 0, threeFloatValues, noFloatValues) // !(player.guid == 0)
}
).as[ZipLineMessage]
}

View file

@ -2,7 +2,7 @@
package net.psforever.packet.game.objectcreate
import net.psforever.packet.{Marshallable, PacketHelpers}
import net.psforever.types.Vector3
import net.psforever.types.{PlanetSideEmpire, Vector3}
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
@ -18,11 +18,6 @@ import shapeless.{::, HNil}
* This base length of this stream is __430__ known bits, excluding the length of the name and the padding on that name.
* Of that, __203__ bits are perfectly unknown in significance.
* <br>
* Faction:<br>
* `0 - Terran Republic`<br>
* `1 - New Conglomerate`<br>
* `2 - Vanu Sovereignty`<br>
* <br>
* Exo-suit:<br>
* `0 - Agile`<br>
* `1 - Refinforced`<br>
@ -91,7 +86,7 @@ import shapeless.{::, HNil}
*/
final case class CharacterAppearanceData(pos : Vector3,
objYaw : Int,
faction : Int,
faction : PlanetSideEmpire.Value,
bops : Boolean,
unk1 : Int,
name : String,
@ -138,7 +133,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
ignore(16) ::
("objYaw" | uint8L) ::
ignore(1) ::
("faction" | uint2L) ::
("faction" | PlanetSideEmpire.codec) ::
("bops" | bool) ::
("unk1" | uint4L) ::
ignore(16) ::
@ -164,7 +159,23 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
("unk8" | uint4L) ::
ignore(6) ::
("ribbons" | RibbonBars.codec)
).as[CharacterAppearanceData]
).exmap[CharacterAppearanceData] (
{
case a :: _ :: b :: _ :: c :: d :: e :: _ :: f :: g :: _ :: h :: i :: j :: k :: l :: _ :: m :: n :: o :: _ :: p :: _ :: q :: _ :: r :: s :: t :: _ :: u :: HNil =>
Attempt.successful(
CharacterAppearanceData(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u)
)
},
{
case CharacterAppearanceData(_, _, PlanetSideEmpire.NEUTRAL, _, _, name, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
Attempt.failure(Err(s"character $name's faction can not declare as neutral"))
case CharacterAppearanceData(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u) =>
Attempt.successful(
a :: () :: b :: () :: c :: d :: e :: () :: f :: g :: () :: h :: i :: j :: k :: l :: () :: m :: n :: o :: () :: p :: () :: q :: () :: r :: s :: t :: () :: u :: HNil
)
}
)
}
/**

View file

@ -421,7 +421,6 @@ object ObjectClass {
case ObjectClass.battlewagon_weapon_systemb => WeaponData.genericCodec
case ObjectClass.battlewagon_weapon_systemc => WeaponData.genericCodec
case ObjectClass.battlewagon_weapon_systemd => WeaponData.genericCodec
case ObjectClass.beamer => WeaponData.genericCodec
case ObjectClass.bolt_driver => WeaponData.genericCodec
case ObjectClass.chainblade => WeaponData.genericCodec
case ObjectClass.chaingun_p => WeaponData.genericCodec

View file

@ -0,0 +1,15 @@
// Copyright (c) 2016 PSForever.net to present
package net.psforever.types
import net.psforever.packet.PacketHelpers
import scodec.codecs.uint2L
/**
* Values for the three empires and the neutral/Black Ops group.
*/
object PlanetSideEmpire extends Enumeration {
type Type = Value
val TR, NC, VS, NEUTRAL = Value
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint2L)
}

View file

@ -109,14 +109,12 @@ class CryptoInterfaceTest extends Specification { args(stopOnFail = true)
"safely handle multiple starts" in {
val dontCare = ByteVector.fill(16)(0x42)
var dh = new CryptoDHState()
val dh = new CryptoDHState()
dh.start()
dh.start() must throwA[IllegalStateException]
dh.close
dh = new CryptoDHState()
ok
}

View file

@ -374,7 +374,7 @@ class GamePacketTest extends Specification {
char.appearance.pos.y mustEqual 2726.789f
char.appearance.pos.z mustEqual 91.15625f
char.appearance.objYaw mustEqual 19
char.appearance.faction mustEqual 2 //vs
char.appearance.faction mustEqual PlanetSideEmpire.VS
char.appearance.bops mustEqual false
char.appearance.unk1 mustEqual 4
char.appearance.name mustEqual "IlllIIIlllIlIllIlllIllI"
@ -529,7 +529,7 @@ class GamePacketTest extends Specification {
val app = CharacterAppearanceData(
Vector3(3674.8438f, 2726.789f, 91.15625f),
19,
2,
PlanetSideEmpire.VS,
false,
4,
"IlllIIIlllIlIllIlllIllI",
@ -1139,6 +1139,32 @@ class GamePacketTest extends Specification {
}
}
"ZipLineMessage" should {
val string = hex"BF 4B00 19 80000010 5bb4089c 52116881 cf76e840"
"decode" in {
PacketCoding.DecodePacket(string).require match {
case ZipLineMessage(player_guid, origin_side, action, uid, x, y, z) =>
player_guid mustEqual PlanetSideGUID(75)
origin_side mustEqual false
action mustEqual 0
uid mustEqual 204
x mustEqual 1286.9221f
y mustEqual 1116.5276f
z mustEqual 91.74034f
case _ =>
ko
}
}
"encode" in {
val msg = ZipLineMessage(PlanetSideGUID(75), false, 0, 204, 1286.9221f, 1116.5276f, 91.74034f)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
}
"PlayerStateShiftMessage" should {
val string_short = hex"BE 68"
val string_pos = hex"BE 95 A0 89 13 91 B8 B0 BF F0"
@ -1212,6 +1238,25 @@ class GamePacketTest extends Specification {
}
}
"ProximityTerminalUseMessage" should {
val string = hex"C3 4B00 A700 80"
"decode" in {
PacketCoding.DecodePacket(string).require match {
case ProximityTerminalUseMessage(player_guid, object_guid, unk) =>
player_guid mustEqual PlanetSideGUID(75)
object_guid mustEqual PlanetSideGUID(167)
unk mustEqual true
case _ =>
ko
}
}
"encode" in {
val msg = ProximityTerminalUseMessage(PlanetSideGUID(75), PlanetSideGUID(167), true)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
}
"UseItemMessage" should {
val string = hex"10 4B00 0000 7401 FFFFFFFF 4001000000000000000000000000058C803600800000"
@ -1346,7 +1391,7 @@ class GamePacketTest extends Specification {
}
"encode" in {
val msg = DestroyDisplayMessage("Angello", 30981173,PlanetSideEmpire.VS, false, 121, 969, "HMFIC", 31035057, PlanetSideEmpire.TR, false)
val msg = DestroyDisplayMessage("Angello", 30981173, PlanetSideEmpire.VS, false, 121, 969, "HMFIC", 31035057, PlanetSideEmpire.TR, false)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
@ -1492,6 +1537,654 @@ class GamePacketTest extends Specification {
}
}
"ReplicationStreamMessage" should {
val stringListClear = hex"E6 B9 FE"
val stringListOne = hex"E6 B8 01 06 01 00 8B 46007200610067004C0041004E00640049004E004300 84 4600720061006700 0A00 00 00 0A FF"
val stringListTwo = hex"E6 B8 01 06 06 00 8E 470065006E006500720061006C0047006F0072006700750074007A00 A1 46004C0059002C0041006C006C002000770065006C0063006F006D0065002C0063006E0020006C0061007300740020006E0069006700680074002100210021002100 0400 00 00 7A 01 83 02 00 45 80 4B004F004B006B006900610073004D00460043004E00 87 5300710075006100640020003200 0400 00 00 6A FF"
val stringListThree = hex"E6 B8 01 06 06 00 8E 470065006E006500720061006C0047006F0072006700750074007A00 A1 46004C0059002C0041006C006C002000770065006C0063006F006D0065002C0063006E0020006C0061007300740020006E0069006700680074002100210021002100 0400 00 00 7A 01 83 01 80 4600 4E0049004700480054003800380052004100560045004E00 8B 41006C006C002000570065006C0063006F006D006500 0A 00 00 00 4A 02 83 02 00 45 80 4B004F004B006B006900610073004D00460043004E00 87 5300710075006100640020003200 0400 00 00 6A FF"
val stringListRemove = hex"E6 20 A0 19 FE"
val stringUpdateLeader = hex"E6 C0 28 08 C4 00 46006100740065004A0048004E004300 FF"
val stringUpdateTask = hex"E6 C0 58 094E00 52004900500020005000530031002C0020007600690073006900740020005000530046006F00720065007600650072002E006E0065007400 FF"
val stringUpdateContinent = hex"E6 C0 38 09 85000000 7F80"
val stringUpdateSize = hex"E6 C0 18 0A 37 F8"
val stringUpdateLeaderSize = hex"E6 C0 58 10 C3 00 4A0069006D006D0079006E00 43 FF"
val stringUpdateTaskContinent = hex"E6 C0 58 11 40 80 3200 3 04000000 FF0"
val stringUpdateAll = hex"E6 C0 78 30 58 0430 6D00610064006D0075006A00 80 040000000A FF"
//failing conditions
val stringCodecFail = hex"E6 20 A1 19 FE"
val stringListOneFail = hex"E6 B8 01 06 01 00 8B 46007200610067004C0041004E00640049004E004300 84 4600720061006700 0A00 00 01 0A FF"
val stringListTwoFail = hex"E6 B8 01 06 06 00 8E 470065006E006500720061006C0047006F0072006700750074007A00 A1 46004C0059002C0041006C006C002000770065006C0063006F006D0065002C0063006E0020006C0061007300740020006E0069006700680074002100210021002100 0400 00 00 7A 01 83 02 00 45 80 4B004F004B006B006900610073004D00460043004E00 87 5300710075006100640020003200 0400 00 01 6A FF"
val stringUpdateLeaderFail = hex"E6 C0 28 08 44 00 46006100740065004A0048004E004300 FF"
val stringUpdateTaskFail = hex"E6 C0 58 09CE00 52004900500020005000530031002C0020007600690073006900740020005000530046006F00720065007600650072002E006E0065007400 FF"
val stringUpdateContinentFail = hex"E6 C0 38 09 85000001 7F80"
val stringUpdateSizeFail = hex"E6 C0 18 0A B7 F8"
val stringUpdateLeaderSizeFail = hex"E6 C0 58 10 43 00 4A0069006D006D0079006E00 43 FF"
val stringUpdateTaskContinentFail = hex"E6 C0 58 11 C0 80 3200 3 04000000 FF0"
val stringUpdateAllFail = hex"E6 C0 78 30 58 0430 6D00610064006D0075006A00 80 04000001 0A FF"
"SquadInfo (w/ squad_guid)" in {
val o = SquadInfo("FragLANdINC", "Frag", PlanetSideZoneID(10), 0, 10)
o.leader.isDefined mustEqual true
o.leader.get mustEqual "FragLANdINC"
o.task.isDefined mustEqual true
o.task.get mustEqual "Frag"
o.zone_id.isDefined mustEqual true
o.zone_id.get mustEqual PlanetSideZoneID(10)
o.size.isDefined mustEqual true
o.size.get mustEqual 0
o.capacity.isDefined mustEqual true
o.capacity.get mustEqual 10
}
"SquadInfo (capacity)" in {
val o = SquadInfo(None, 7)
o.leader.isDefined mustEqual false
o.task.isDefined mustEqual false
o.zone_id.isDefined mustEqual false
o.size.isDefined mustEqual false
o.capacity.isDefined mustEqual true
o.capacity.get mustEqual 7
}
"decode (clear)" in {
PacketCoding.DecodePacket(stringListClear).require match {
case ReplicationStreamMessage(behavior, behavior2, entries) =>
behavior mustEqual 5
behavior2.isDefined mustEqual true
behavior2.get mustEqual 6
entries.length mustEqual 1
entries.head.index mustEqual 255
entries.head.listing.isDefined mustEqual false
case _ =>
ko
}
}
"decode (one)" in {
PacketCoding.DecodePacket(stringListOne).require match {
case ReplicationStreamMessage(behavior, behavior2, entries) =>
behavior mustEqual 5
behavior2.get mustEqual 6
entries.length mustEqual 2
entries.head.index mustEqual 0
entries.head.listing.isDefined mustEqual true
entries.head.listing.get.unk1 mustEqual 131
entries.head.listing.get.unk2 mustEqual false
entries.head.listing.get.unk3.isDefined mustEqual false
entries.head.listing.get.info.isDefined mustEqual true
entries.head.listing.get.info.get.leader.isDefined mustEqual true
entries.head.listing.get.info.get.leader.get mustEqual "FragLANdINC"
entries.head.listing.get.info.get.task.isDefined mustEqual true
entries.head.listing.get.info.get.task.get mustEqual "Frag"
entries.head.listing.get.info.get.zone_id.isDefined mustEqual true
entries.head.listing.get.info.get.zone_id.get mustEqual PlanetSideZoneID(10)
entries.head.listing.get.info.get.size.isDefined mustEqual true
entries.head.listing.get.info.get.size.get mustEqual 0
entries.head.listing.get.info.get.capacity.isDefined mustEqual true
entries.head.listing.get.info.get.capacity.get mustEqual 10
entries.head.listing.get.info.get.squad_guid.isDefined mustEqual true
entries.head.listing.get.info.get.squad_guid.get mustEqual PlanetSideGUID(1)
entries(1).index mustEqual 255
entries(1).listing.isDefined mustEqual false
case _ =>
ko
}
}
"decode (two)" in {
PacketCoding.DecodePacket(stringListTwo).require match {
case ReplicationStreamMessage(behavior, behavior2, entries) =>
behavior mustEqual 5
behavior2.get mustEqual 6
entries.length mustEqual 3
entries.head.index mustEqual 0
entries.head.listing.get.unk1 mustEqual 131
entries.head.listing.get.unk2 mustEqual false
entries.head.listing.get.unk3.isDefined mustEqual false
entries.head.listing.get.info.get.leader.get mustEqual "GeneralGorgutz"
entries.head.listing.get.info.get.task.get mustEqual "FLY,All welcome,cn last night!!!!"
entries.head.listing.get.info.get.zone_id.get mustEqual PlanetSideZoneID(4)
entries.head.listing.get.info.get.size.get mustEqual 7
entries.head.listing.get.info.get.capacity.get mustEqual 10
entries.head.listing.get.info.get.squad_guid.get mustEqual PlanetSideGUID(6)
entries(1).index mustEqual 1
entries(1).listing.get.unk1 mustEqual 131
entries(1).listing.get.unk2 mustEqual false
entries(1).listing.get.unk3.isDefined mustEqual false
entries(1).listing.get.info.get.leader.get mustEqual "KOKkiasMFCN"
entries(1).listing.get.info.get.task.get mustEqual "Squad 2"
entries(1).listing.get.info.get.zone_id.get mustEqual PlanetSideZoneID(4)
entries(1).listing.get.info.get.size.get mustEqual 6
entries(1).listing.get.info.get.capacity.get mustEqual 10
entries(1).listing.get.info.get.squad_guid.get mustEqual PlanetSideGUID(4)
entries(2).index mustEqual 255
case _ =>
ko
}
}
"decode (three)" in {
PacketCoding.DecodePacket(stringListThree).require match {
case ReplicationStreamMessage(behavior, behavior2, entries) =>
behavior mustEqual 5
behavior2.get mustEqual 6
entries.length mustEqual 4
entries.head.index mustEqual 0
entries.head.listing.get.unk1 mustEqual 131
entries.head.listing.get.unk2 mustEqual false
entries.head.listing.get.unk3.isDefined mustEqual false
entries.head.listing.get.info.get.leader.get mustEqual "GeneralGorgutz"
entries.head.listing.get.info.get.task.get mustEqual "FLY,All welcome,cn last night!!!!"
entries.head.listing.get.info.get.zone_id.get mustEqual PlanetSideZoneID(4)
entries.head.listing.get.info.get.size.get mustEqual 7
entries.head.listing.get.info.get.capacity.get mustEqual 10
entries.head.listing.get.info.get.squad_guid.get mustEqual PlanetSideGUID(6)
entries(1).index mustEqual 1
entries(1).listing.get.unk1 mustEqual 131
entries(1).listing.get.unk2 mustEqual false
entries(1).listing.get.unk3.isDefined mustEqual false
entries(1).listing.get.info.get.leader.get mustEqual "NIGHT88RAVEN"
entries(1).listing.get.info.get.task.get mustEqual "All Welcome"
entries(1).listing.get.info.get.zone_id.get mustEqual PlanetSideZoneID(10)
entries(1).listing.get.info.get.size.get mustEqual 4
entries(1).listing.get.info.get.capacity.get mustEqual 10
entries(1).listing.get.info.get.squad_guid.get mustEqual PlanetSideGUID(3)
entries(2).index mustEqual 2
entries(2).listing.get.unk1 mustEqual 131
entries(2).listing.get.unk2 mustEqual false
entries(2).listing.get.unk3.isDefined mustEqual false
entries(2).listing.get.info.get.leader.get mustEqual "KOKkiasMFCN"
entries(2).listing.get.info.get.task.get mustEqual "Squad 2"
entries(2).listing.get.info.get.zone_id.get mustEqual PlanetSideZoneID(4)
entries(2).listing.get.info.get.size.get mustEqual 6
entries(2).listing.get.info.get.capacity.get mustEqual 10
entries(2).listing.get.info.get.squad_guid.get mustEqual PlanetSideGUID(4)
entries(3).index mustEqual 255
case _ =>
ko
}
}
"decode (remove)" in {
PacketCoding.DecodePacket(stringListRemove).require match {
case ReplicationStreamMessage(behavior, behavior2, entries) =>
behavior mustEqual 1
behavior2.isDefined mustEqual false
entries.length mustEqual 2
entries.head.index mustEqual 5
entries.head.listing.isDefined mustEqual true
entries.head.listing.get.unk1 mustEqual 0
entries.head.listing.get.unk2 mustEqual true
entries.head.listing.get.unk3.isDefined mustEqual true
entries.head.listing.get.unk3.get mustEqual 4
entries.head.listing.get.info.isDefined mustEqual false
entries(1).index mustEqual 255
case _ =>
ko
}
}
"decode (update leader)" in {
PacketCoding.DecodePacket(stringUpdateLeader).require match {
case ReplicationStreamMessage(behavior, behavior2, entries) =>
behavior mustEqual 6
behavior2.isDefined mustEqual false
entries.length mustEqual 2
entries.head.index mustEqual 2
entries.head.listing.isDefined mustEqual true
entries.head.listing.get.unk1 mustEqual 128
entries.head.listing.get.unk2 mustEqual true
entries.head.listing.get.unk3.isDefined mustEqual true
entries.head.listing.get.unk3.get mustEqual 0
entries.head.listing.get.info.isDefined mustEqual true
entries.head.listing.get.info.get.leader.isDefined mustEqual true
entries.head.listing.get.info.get.leader.get mustEqual "FateJHNC"
entries.head.listing.get.info.get.task.isDefined mustEqual false
entries.head.listing.get.info.get.zone_id.isDefined mustEqual false
entries.head.listing.get.info.get.size.isDefined mustEqual false
entries.head.listing.get.info.get.capacity.isDefined mustEqual false
entries.head.listing.get.info.get.squad_guid.isDefined mustEqual false
entries(1).index mustEqual 255
case _ =>
ko
}
}
"decode (update task)" in {
PacketCoding.DecodePacket(stringUpdateTask).require match {
case ReplicationStreamMessage(behavior, behavior2, entries) =>
behavior mustEqual 6
behavior2.isDefined mustEqual false
entries.length mustEqual 2
entries.head.index mustEqual 5
entries.head.listing.isDefined mustEqual true
entries.head.listing.get.unk1 mustEqual 128
entries.head.listing.get.unk2 mustEqual true
entries.head.listing.get.unk3.isDefined mustEqual true
entries.head.listing.get.unk3.get mustEqual 1
entries.head.listing.get.info.isDefined mustEqual true
entries.head.listing.get.info.get.leader.isDefined mustEqual false
entries.head.listing.get.info.get.task.isDefined mustEqual true
entries.head.listing.get.info.get.task.get mustEqual "RIP PS1, visit PSForever.net"
entries.head.listing.get.info.get.zone_id.isDefined mustEqual false
entries.head.listing.get.info.get.size.isDefined mustEqual false
entries.head.listing.get.info.get.capacity.isDefined mustEqual false
entries.head.listing.get.info.get.squad_guid.isDefined mustEqual false
entries(1).index mustEqual 255
case _ =>
ko
}
}
"decode (update continent)" in {
PacketCoding.DecodePacket(stringUpdateContinent).require match {
case ReplicationStreamMessage(behavior, behavior2, entries) =>
behavior mustEqual 6
behavior2.isDefined mustEqual false
entries.length mustEqual 2
entries.head.index mustEqual 3
entries.head.listing.isDefined mustEqual true
entries.head.listing.get.unk1 mustEqual 128
entries.head.listing.get.unk2 mustEqual true
entries.head.listing.get.unk3.isDefined mustEqual true
entries.head.listing.get.unk3.get mustEqual 1
entries.head.listing.get.info.isDefined mustEqual true
entries.head.listing.get.info.get.leader.isDefined mustEqual false
entries.head.listing.get.info.get.task.isDefined mustEqual false
entries.head.listing.get.info.get.zone_id.isDefined mustEqual true
entries.head.listing.get.info.get.zone_id.get mustEqual PlanetSideZoneID(10)
entries.head.listing.get.info.get.size.isDefined mustEqual false
entries.head.listing.get.info.get.capacity.isDefined mustEqual false
entries.head.listing.get.info.get.squad_guid.isDefined mustEqual false
entries(1).index mustEqual 255
case _ =>
ko
}
}
"decode (update size)" in {
PacketCoding.DecodePacket(stringUpdateSize).require match {
case ReplicationStreamMessage(behavior, behavior2, entries) =>
behavior mustEqual 6
behavior2.isDefined mustEqual false
entries.length mustEqual 2
entries.head.index mustEqual 1
entries.head.listing.isDefined mustEqual true
entries.head.listing.get.unk1 mustEqual 128
entries.head.listing.get.unk2 mustEqual true
entries.head.listing.get.unk3.isDefined mustEqual true
entries.head.listing.get.unk3.get mustEqual 2
entries.head.listing.get.info.isDefined mustEqual true
entries.head.listing.get.info.get.leader.isDefined mustEqual false
entries.head.listing.get.info.get.task.isDefined mustEqual false
entries.head.listing.get.info.get.zone_id.isDefined mustEqual false
entries.head.listing.get.info.get.size.isDefined mustEqual true
entries.head.listing.get.info.get.size.get mustEqual 6
entries.head.listing.get.info.get.capacity.isDefined mustEqual false
entries.head.listing.get.info.get.squad_guid.isDefined mustEqual false
entries(1).index mustEqual 255
case _ =>
ko
}
}
"decode (update leader and size)" in {
PacketCoding.DecodePacket(stringUpdateLeaderSize).require match {
case ReplicationStreamMessage(behavior, behavior2, entries) =>
behavior mustEqual 6
behavior2.isDefined mustEqual false
entries.length mustEqual 2
entries.head.index mustEqual 5
entries.head.listing.isDefined mustEqual true
entries.head.listing.get.unk1 mustEqual 129
entries.head.listing.get.unk2 mustEqual false
entries.head.listing.get.unk3.isDefined mustEqual true
entries.head.listing.get.unk3.get mustEqual 0
entries.head.listing.get.info.isDefined mustEqual true
entries.head.listing.get.info.get.leader.isDefined mustEqual true
entries.head.listing.get.info.get.leader.get mustEqual "Jimmyn"
entries.head.listing.get.info.get.task.isDefined mustEqual false
entries.head.listing.get.info.get.zone_id.isDefined mustEqual false
entries.head.listing.get.info.get.size.isDefined mustEqual true
entries.head.listing.get.info.get.size.get mustEqual 3
entries.head.listing.get.info.get.capacity.isDefined mustEqual false
entries.head.listing.get.info.get.squad_guid.isDefined mustEqual false
entries(1).index mustEqual 255
case _ =>
ko
}
}
"decode (update task and continent)" in {
PacketCoding.DecodePacket(stringUpdateTaskContinent).require match {
case ReplicationStreamMessage(behavior, behavior2, entries) =>
behavior mustEqual 6
behavior2.isDefined mustEqual false
entries.length mustEqual 2
entries.head.index mustEqual 5
entries.head.listing.isDefined mustEqual true
entries.head.listing.get.unk1 mustEqual 129
entries.head.listing.get.unk2 mustEqual false
entries.head.listing.get.unk3.isDefined mustEqual true
entries.head.listing.get.unk3.get mustEqual 1
entries.head.listing.get.info.isDefined mustEqual true
entries.head.listing.get.info.get.leader.isDefined mustEqual false
entries.head.listing.get.info.get.task.isDefined mustEqual true
entries.head.listing.get.info.get.task.get mustEqual "2"
entries.head.listing.get.info.get.zone_id.isDefined mustEqual true
entries.head.listing.get.info.get.zone_id.get mustEqual PlanetSideZoneID(4)
entries.head.listing.get.info.get.size.isDefined mustEqual false
entries.head.listing.get.info.get.capacity.isDefined mustEqual false
entries.head.listing.get.info.get.squad_guid.isDefined mustEqual false
entries(1).index mustEqual 255
case _ =>
ko
}
}
"decode (update all)" in {
PacketCoding.DecodePacket(stringUpdateAll).require match {
case ReplicationStreamMessage(behavior, behavior2, entries) =>
behavior mustEqual 6
behavior2.isDefined mustEqual false
entries.length mustEqual 2
entries.head.index mustEqual 7
entries.head.listing.isDefined mustEqual true
entries.head.listing.get.unk1 mustEqual 131
entries.head.listing.get.unk2 mustEqual false
entries.head.listing.get.unk3.isDefined mustEqual false
entries.head.listing.get.info.isDefined mustEqual true
entries.head.listing.get.info.get.leader.isDefined mustEqual true
entries.head.listing.get.info.get.leader.get mustEqual "madmuj"
entries.head.listing.get.info.get.task.isDefined mustEqual true
entries.head.listing.get.info.get.task.get mustEqual ""
entries.head.listing.get.info.get.zone_id.isDefined mustEqual true
entries.head.listing.get.info.get.zone_id.get mustEqual PlanetSideZoneID(4)
entries.head.listing.get.info.get.size.isDefined mustEqual true
entries.head.listing.get.info.get.size.get mustEqual 0
entries.head.listing.get.info.get.capacity.isDefined mustEqual true
entries.head.listing.get.info.get.capacity.get mustEqual 10
entries.head.listing.get.info.get.squad_guid.isDefined mustEqual true
entries.head.listing.get.info.get.squad_guid.get mustEqual PlanetSideGUID(11)
entries(1).index mustEqual 255
case _ =>
ko
}
}
"decode (fails)" in {
PacketCoding.DecodePacket(stringCodecFail).isFailure mustEqual true
//PacketCoding.DecodePacket(stringListOneFail).isFailure mustEqual true -> used to fail
//PacketCoding.DecodePacket(stringListTwoFail).isFailure mustEqual true -> used to fail
PacketCoding.DecodePacket(stringUpdateLeaderFail).isFailure mustEqual true
PacketCoding.DecodePacket(stringUpdateTaskFail).isFailure mustEqual true
//PacketCoding.DecodePacket(stringUpdateContinentFail).isFailure mustEqual true -> used to fail
PacketCoding.DecodePacket(stringUpdateSizeFail).isFailure mustEqual true
PacketCoding.DecodePacket(stringUpdateLeaderSizeFail).isFailure mustEqual true
PacketCoding.DecodePacket(stringUpdateTaskContinentFail).isFailure mustEqual true
//PacketCoding.DecodePacket(stringUpdateAllFail).isFailure mustEqual true -> used to fail
}
"encode (clear)" in {
val msg = ReplicationStreamMessage(5, Some(6),
Vector(
SquadListing(255)
)
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringListClear
}
"encode (one)" in {
val msg = ReplicationStreamMessage(5, Some(6),
Vector(
SquadListing(0, Some(SquadHeader(131, false, None, SquadInfo("FragLANdINC", "Frag", PlanetSideZoneID(10), 0, 10, PlanetSideGUID(1))))),
SquadListing(255)
)
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringListOne
}
"encode (two)" in {
val msg = ReplicationStreamMessage(5, Some(6),
Vector(
SquadListing(0, Some(SquadHeader(131, false, None, SquadInfo("GeneralGorgutz", "FLY,All welcome,cn last night!!!!", PlanetSideZoneID(4), 7, 10, PlanetSideGUID(6))))),
SquadListing(1, Some(SquadHeader(131, false, None, SquadInfo("KOKkiasMFCN", "Squad 2", PlanetSideZoneID(4), 6, 10, PlanetSideGUID(4))))),
SquadListing(255)
)
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringListTwo
}
"encode (three)" in {
val msg = ReplicationStreamMessage(5, Some(6),
Vector(
SquadListing(0, Some(SquadHeader(131, false, None, SquadInfo("GeneralGorgutz", "FLY,All welcome,cn last night!!!!", PlanetSideZoneID(4), 7, 10, PlanetSideGUID(6))))),
SquadListing(1, Some(SquadHeader(131, false, None, SquadInfo("NIGHT88RAVEN", "All Welcome", PlanetSideZoneID(10), 4, 10, PlanetSideGUID(3))))),
SquadListing(2, Some(SquadHeader(131, false, None, SquadInfo("KOKkiasMFCN", "Squad 2", PlanetSideZoneID(4), 6, 10, PlanetSideGUID(4))))),
SquadListing(255)
)
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringListThree
}
"encode (remove)" in {
val msg = ReplicationStreamMessage(1, None,
Vector(
SquadListing(5, Some(SquadHeader(0, true, Some(4)))),
SquadListing(255)
)
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringListRemove
}
"encode (update leader)" in {
val msg = ReplicationStreamMessage(6, None,
Vector(
SquadListing(2, Some(SquadHeader(128, true, Some(0), SquadInfo("FateJHNC", None)))),
SquadListing(255)
)
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringUpdateLeader
}
"encode (update task)" in {
val msg = ReplicationStreamMessage(6, None,
Vector(
SquadListing(5, Some(SquadHeader(128, true, Some(1), SquadInfo(None, "RIP PS1, visit PSForever.net")))),
SquadListing(255)
)
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringUpdateTask
}
"encode (update continent)" in {
val msg = ReplicationStreamMessage(6, None,
Vector(
SquadListing(3, Some(SquadHeader(128, true, Some(1), SquadInfo(PlanetSideZoneID(10))))),
SquadListing(255)
)
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringUpdateContinent
}
"encode (update size)" in {
val msg = ReplicationStreamMessage(6, None,
Vector(
SquadListing(1, Some(SquadHeader(128, true, Some(2), SquadInfo(6, None)))),
SquadListing(255)
)
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringUpdateSize
}
"encode (update leader and size)" in {
val msg = ReplicationStreamMessage(6, None,
Vector(
SquadListing(5, Some(SquadHeader(129, false, Some(0), SquadInfo("Jimmyn", 3)))),
SquadListing(255)
)
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringUpdateLeaderSize
}
"encode (update task and continent)" in {
val msg = ReplicationStreamMessage(6, None,
Vector(
SquadListing(5, Some(SquadHeader(129, false, Some(1), SquadInfo("2", PlanetSideZoneID(4))))),
SquadListing(255)
)
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringUpdateTaskContinent
}
"encode (update all)" in {
val msg = ReplicationStreamMessage(6, None,
Vector(
SquadListing(7, Some(SquadHeader(131, false, None, SquadInfo("madmuj", "", PlanetSideZoneID(4), 0, 10, PlanetSideGUID(11))))),
SquadListing(255)
)
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringUpdateAll
}
"encode (fails)" in {
//encode codec fail
PacketCoding.EncodePacket(
ReplicationStreamMessage(1, None,
Vector(
SquadListing(5, Some(SquadHeader(0, false, Some(4)))),
SquadListing(255)
)
)
).isFailure mustEqual true
//encode one
PacketCoding.EncodePacket(
ReplicationStreamMessage(5, Some(6),
Vector(
SquadListing(0, Some(SquadHeader(131, false, None, Some(SquadInfo(Some("FragLANdINC"), Some("Frag"), None, Some(0),Some(10), Some(PlanetSideGUID(1))))))),
SquadListing(255)
)
)
).isFailure mustEqual true
//encode two
PacketCoding.EncodePacket(
ReplicationStreamMessage(5, Some(6),
Vector(
SquadListing(0, Some(SquadHeader(131, false, None, SquadInfo("GeneralGorgutz", "FLY,All welcome,cn last night!!!!", PlanetSideZoneID(4), 7, 10, PlanetSideGUID(6))))),
SquadListing(1, Some(SquadHeader(131, false, None, Some(SquadInfo(Some("KOKkiasMFCN"), Some("Squad 2"), None, Some(6), Some(10), Some(PlanetSideGUID(4))))))),
SquadListing(255)
)
)
).isFailure mustEqual true
//encode leader
PacketCoding.EncodePacket(
ReplicationStreamMessage(6, None,
Vector(
SquadListing(2, Some(SquadHeader(128, true, Some(0), Some(SquadInfo(None, None, None, None, None, None))))),
SquadListing(255)
)
)
).isFailure mustEqual true
//encode task
PacketCoding.EncodePacket(
ReplicationStreamMessage(6, None,
Vector(
SquadListing(5, Some(SquadHeader(128, true, Some(1), Some(SquadInfo(None, None, None, None, None, None))))),
SquadListing(255)
)
)
).isFailure mustEqual true
//encode continent
PacketCoding.EncodePacket(
ReplicationStreamMessage(6, None,
Vector(
SquadListing(3, Some(SquadHeader(128, true, Some(1), Some(SquadInfo(None, None, None, None, None, None))))),
SquadListing(255)
)
)
).isFailure mustEqual true
//encode task or continent
PacketCoding.EncodePacket(
ReplicationStreamMessage(6, None,
Vector(
SquadListing(3, Some(SquadHeader(128, true, Some(1), Some(SquadInfo(None, Some(""), Some(PlanetSideZoneID(10)), None, None, None))))),
SquadListing(255)
)
)
).isFailure mustEqual true
//encode size
PacketCoding.EncodePacket(
ReplicationStreamMessage(6, None,
Vector(
SquadListing(1, Some(SquadHeader(128, true, Some(2), Some(SquadInfo(None, None, None, None, None, None))))),
SquadListing(255)
)
)
).isFailure mustEqual true
//encode leader and size
PacketCoding.EncodePacket(
ReplicationStreamMessage(6, None,
Vector(
SquadListing(5, Some(SquadHeader(129, false, Some(0), Some(SquadInfo(None, None, None, None, None, None))))),
SquadListing(255)
)
)
).isFailure mustEqual true
//encode task and continent
PacketCoding.EncodePacket(
ReplicationStreamMessage(6, None,
Vector(
SquadListing(5, Some(SquadHeader(129, false, Some(1), Some(SquadInfo(None, None, None, None, None, None))))),
SquadListing(255)
)
)
).isFailure mustEqual true
//encode all
PacketCoding.EncodePacket(
ReplicationStreamMessage(6, None,
Vector(
SquadListing(7, Some(SquadHeader(131, false, None, Some(SquadInfo(None, None, None, None, None, None))))),
SquadListing(255)
)
)
).isFailure mustEqual true
}
}
"ZoneLockInfoMesage" should {
val string = hex"DF 1B 00 40"

View file

@ -9,6 +9,7 @@ import org.log4s.MDC
import scodec.Attempt.{Failure, Successful}
import scodec.bits._
import MDCContextAware.Implicits._
import net.psforever.types.PlanetSideEmpire
import scala.concurrent.duration._
import scala.util.Random

View file

@ -10,7 +10,7 @@ import scodec.bits._
import org.log4s.MDC
import MDCContextAware.Implicits._
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{ChatMessageType, Vector3}
import net.psforever.types.{ChatMessageType, PlanetSideEmpire, Vector3}
class WorldSessionActor extends Actor with MDCContextAware {
private[this] val log = org.log4s.getLogger
@ -113,7 +113,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val app = CharacterAppearanceData(
Vector3(3674.8438f, 2726.789f, 91.15625f),
19,
2,
PlanetSideEmpire.VS,
false,
4,
"IlllIIIlllIlIllIlllIllI",
@ -216,6 +216,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PacketCoding.CreateGamePacket(0, SetCurrentAvatarMessage(guid,0,0)))
sendResponse(PacketCoding.CreateGamePacket(0, CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT)))
sendResponse(PacketCoding.CreateGamePacket(0, ReplicationStreamMessage(5, Some(6), Vector(SquadListing(255))))) //clear squad list
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
@ -286,6 +287,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ AvatarJumpMessage(state) =>
//log.info("AvatarJump: " + msg)
case msg @ ZipLineMessage(player_guid,origin_side,action,id,x,y,z) =>
log.info("ZipLineMessage: " + msg)
if(action == 0) {
//doing this lets you use the zip line, but you can't get off
//sendResponse(PacketCoding.CreateGamePacket(0,ZipLineMessage(player_guid, origin_side, action, id, x,y,z)))
}
else if(action == 1) {
//disembark from zipline at destination?
}
else if(action == 2) {
//get off by force
}
case msg @ RequestDestroyMessage(object_guid) =>
log.info("RequestDestroy: " + msg)
// TODO: Make sure this is the correct response in all cases
@ -335,6 +349,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ SquadDefinitionActionMessage(a, b, c, d, e, f, g, h, i) =>
log.info("SquadDefinitionAction: " + msg)
case msg @ BugReportMessage(version_major,version_minor,version_date,bug_type,repeatable,location,zone,pos,summary,desc) =>
log.info("BugReportMessage: " + msg)
case default => log.debug(s"Unhandled GamePacket ${pkt}")
}