mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-20 02:54:46 +00:00
commit
ce99ea6ffc
|
|
@ -1,17 +1,17 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import net.psforever.objects.avatar.DeployableToolbox
|
||||
import net.psforever.objects.avatar.{DeployableToolbox, LoadoutManager}
|
||||
import net.psforever.objects.definition.{AvatarDefinition, ImplantDefinition}
|
||||
import net.psforever.objects.equipment.{EquipmentSize, EquipmentSlot}
|
||||
import net.psforever.objects.loadouts.Loadout
|
||||
import net.psforever.packet.game.objectcreate.Cosmetics
|
||||
import net.psforever.types._
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.mutable
|
||||
|
||||
class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : CharacterGender.Value, val head : Int, val voice : CharacterVoice.Value) {
|
||||
class Avatar(private val char_id : Long, val name : String, val faction : PlanetSideEmpire.Value, val sex : CharacterGender.Value, val head : Int, val voice : CharacterVoice.Value) {
|
||||
/** char_id, Character ID; a unique identifier corresponding to a database table row index */
|
||||
/** Battle Experience Points */
|
||||
private var bep : Long = 0
|
||||
/** Command Experience Points */
|
||||
|
|
@ -29,11 +29,15 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
|
|||
* @see `DetailedCharacterData.implants`
|
||||
*/
|
||||
private val implants : Array[ImplantSlot] = Array.fill[ImplantSlot](3)(new ImplantSlot)
|
||||
/** Loadouts<br>
|
||||
* 0-9 are Infantry loadouts
|
||||
/** Equipment Loadouts<br>
|
||||
* 0-9 are Infantry loadouts<br>
|
||||
* 10-14 are Vehicle loadouts
|
||||
*/
|
||||
private val loadouts : Array[Option[Loadout]] = Array.fill[Option[Loadout]](15)(None)
|
||||
private val equipmentLoadouts : LoadoutManager = new LoadoutManager(15)
|
||||
/**
|
||||
* Squad Loadouts
|
||||
*/
|
||||
private val squadLoadouts : LoadoutManager = new LoadoutManager(10)
|
||||
/** Locker */
|
||||
private val locker : LockerContainer = new LockerContainer() {
|
||||
override def toString : String = {
|
||||
|
|
@ -42,6 +46,14 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
|
|||
}
|
||||
|
||||
private val deployables : DeployableToolbox = new DeployableToolbox
|
||||
/**
|
||||
* Looking For Squad:<br>
|
||||
* Indicates both a player state and the text on the marquee under the player nameplate.
|
||||
* Should only be valid when the player is not in a squad.
|
||||
*/
|
||||
private var lfs : Boolean = false
|
||||
|
||||
def CharId : Long = char_id
|
||||
|
||||
def BEP : Long = bep
|
||||
|
||||
|
|
@ -164,25 +176,9 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
|
|||
})
|
||||
}
|
||||
|
||||
def SaveLoadout(owner : Player, label : String, line : Int) : Unit = {
|
||||
if(line > -1 && line < 10) {
|
||||
loadouts(line) = Some(Loadout.Create(owner, label))
|
||||
}
|
||||
}
|
||||
def EquipmentLoadouts : LoadoutManager = equipmentLoadouts
|
||||
|
||||
def SaveLoadout(owner : Vehicle, label : String, line : Int) : Unit = {
|
||||
if(line > 9 && line < loadouts.length) {
|
||||
loadouts(line) = Some(Loadout.Create(owner, label))
|
||||
}
|
||||
}
|
||||
|
||||
def LoadLoadout(line : Int) : Option[Loadout] = loadouts.lift(line).flatten
|
||||
|
||||
def DeleteLoadout(line : Int) : Unit = {
|
||||
loadouts(line) = None
|
||||
}
|
||||
|
||||
def Loadouts : Seq[(Int, Loadout)] = loadouts.zipWithIndex.collect { case(Some(loadout), index) => (index, loadout) } toSeq
|
||||
def SquadLoadouts : LoadoutManager = squadLoadouts
|
||||
|
||||
def Locker : LockerContainer = locker
|
||||
|
||||
|
|
@ -194,6 +190,13 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
|
|||
|
||||
def Deployables : DeployableToolbox = deployables
|
||||
|
||||
def LFS : Boolean = lfs
|
||||
|
||||
def LFS_=(looking : Boolean) : Boolean = {
|
||||
lfs = looking
|
||||
LFS
|
||||
}
|
||||
|
||||
def Definition : AvatarDefinition = GlobalDefinitions.avatar
|
||||
|
||||
/*
|
||||
|
|
@ -228,7 +231,7 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
|
|||
|
||||
object Avatar {
|
||||
def apply(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : CharacterVoice.Value) : Avatar = {
|
||||
new Avatar(name, faction, sex, head, voice)
|
||||
new Avatar(0L, name, faction, sex, head, voice)
|
||||
}
|
||||
|
||||
def toString(avatar : Avatar) : String = s"${avatar.faction} ${avatar.name}"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import net.psforever.objects.avatar.LoadoutManager
|
||||
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition}
|
||||
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot}
|
||||
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
|
||||
import net.psforever.objects.loadouts.Loadout
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||
import net.psforever.objects.vital.{DamageResistanceModel, Vitality}
|
||||
|
|
@ -58,6 +58,8 @@ class Player(private val core : Avatar) extends PlanetSideGameObject
|
|||
|
||||
Player.SuitSetup(this, exosuit)
|
||||
|
||||
def CharId : Long = core.CharId
|
||||
|
||||
def Name : String = core.name
|
||||
|
||||
def Faction : PlanetSideEmpire.Value = core.faction
|
||||
|
|
@ -68,6 +70,8 @@ class Player(private val core : Avatar) extends PlanetSideGameObject
|
|||
|
||||
def Voice : CharacterVoice.Value = core.voice
|
||||
|
||||
def LFS : Boolean = core.LFS
|
||||
|
||||
def isAlive : Boolean = alive
|
||||
|
||||
def isBackpack : Boolean = backpack
|
||||
|
|
@ -294,7 +298,9 @@ class Player(private val core : Avatar) extends PlanetSideGameObject
|
|||
|
||||
def RadiationShielding = exosuit.RadiationShielding
|
||||
|
||||
def LoadLoadout(line : Int) : Option[Loadout] = core.LoadLoadout(line)
|
||||
def EquipmentLoadouts : LoadoutManager = core.EquipmentLoadouts
|
||||
|
||||
def SquadLoadouts : LoadoutManager = core.SquadLoadouts
|
||||
|
||||
def BEP : Long = core.BEP
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.objects.avatar
|
||||
|
||||
import net.psforever.objects.loadouts.Loadout
|
||||
import net.psforever.types.LoadoutType
|
||||
|
||||
import scala.util.Success
|
||||
|
||||
class LoadoutManager(size : Int) {
|
||||
private val entries : Array[Option[Loadout]] = Array.fill[Option[Loadout]](size)(None)
|
||||
|
||||
def SaveLoadout(owner : Any, label : String, line : Int) : Unit = {
|
||||
Loadout.Create(owner, label) match {
|
||||
case Success(loadout) if entries.length > line =>
|
||||
entries(line) = Some(loadout)
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
def LoadLoadout(line : Int) : Option[Loadout] = entries.lift(line).flatten
|
||||
|
||||
def DeleteLoadout(line : Int) : Unit = {
|
||||
if(entries.length > line) {
|
||||
entries(line) = None
|
||||
}
|
||||
}
|
||||
|
||||
def Loadouts : Seq[(Int, Loadout)] = entries.zipWithIndex.collect { case(Some(loadout), index) => (index, loadout) } toSeq
|
||||
}
|
||||
|
|
@ -81,7 +81,7 @@ object AvatarConverter {
|
|||
),
|
||||
obj.ExoSuit,
|
||||
0,
|
||||
0L,
|
||||
obj.CharId,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
|
|
@ -98,7 +98,7 @@ object AvatarConverter {
|
|||
false,
|
||||
facingPitch = obj.Orientation.y,
|
||||
facingYawUpper = obj.FacingYawUpper,
|
||||
lfs = true,
|
||||
obj.LFS,
|
||||
GrenadeState.None,
|
||||
obj.Cloaked,
|
||||
false,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.loadouts
|
||||
|
||||
/**
|
||||
* The base of all specific kinds of blueprint containers.
|
||||
* This previous state can be restored on any appropriate template from which the loadout was copied
|
||||
* by reconstructing any items (if warranted and permitted) or restoring any appropriate fields.
|
||||
* The three fields are the name assigned to the loadout,
|
||||
* the visible items that are created (which obey different rules depending on the source),
|
||||
* and the concealed items that are created and added to the source's `Inventory`.<br>
|
||||
* For example, the `visible_slots` on a `Player`-borne loadout will transform into the form `Array[EquipmentSlot]`;
|
||||
* `Vehicle`-originating loadouts transform into the form `Map[Int, Equipment]`.
|
||||
* <br>
|
||||
* The lists of user-specific loadouts are initialized with `FavoritesMessage` packets.
|
||||
* Specific entries are loaded or removed using `FavoritesRequest` packets.
|
||||
* @param label the name by which this inventory will be known when displayed in a Favorites list
|
||||
* @param visible_slots simplified representation of the `Equipment` that can see "seen" on the target
|
||||
* @param inventory simplified representation of the `Equipment` in the target's inventory or trunk
|
||||
*/
|
||||
abstract class EquipmentLoadout(label : String,
|
||||
visible_slots : List[Loadout.SimplifiedEntry],
|
||||
inventory : List[Loadout.SimplifiedEntry]) extends Loadout(label)
|
||||
|
|
@ -28,7 +28,7 @@ final case class InfantryLoadout(label : String,
|
|||
visible_slots : List[Loadout.SimplifiedEntry],
|
||||
inventory : List[Loadout.SimplifiedEntry],
|
||||
exosuit : ExoSuitType.Value,
|
||||
subtype : Int) extends Loadout(label, visible_slots, inventory)
|
||||
subtype : Int) extends EquipmentLoadout(label, visible_slots, inventory)
|
||||
|
||||
object InfantryLoadout {
|
||||
import net.psforever.objects.Player
|
||||
|
|
|
|||
|
|
@ -5,30 +5,29 @@ import net.psforever.objects._
|
|||
import net.psforever.objects.definition._
|
||||
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
|
||||
import net.psforever.objects.inventory.InventoryItem
|
||||
import net.psforever.objects.teamwork.Squad
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
/**
|
||||
* The base of all specific kinds of blueprint containers.
|
||||
* This previous state can be restored on any appropriate template from which the loadout was copied
|
||||
* by reconstructing the items (if permitted).
|
||||
* The three fields are the name assigned to the loadout,
|
||||
* the visible items that are created (which obey different rules depending on the source),
|
||||
* and the concealed items that are created and added to the source's `Inventory`.<br>
|
||||
* For example, the `visible_slots` on a `Player`-borne loadout will transform into the form `Array[EquipmentSlot]`;
|
||||
* `Vehicle`-originating loadouts transform into the form `Map[Int, Equipment]`.
|
||||
* <br>
|
||||
* The lists of user-specific loadouts are initialized with `FavoritesMessage` packets.
|
||||
* Specific entries are loaded or removed using `FavoritesRequest` packets.
|
||||
* by reconstructing any items (if warranted and permitted) or restoring any appropriate fields.
|
||||
* @param label the name by which this inventory will be known when displayed in a Favorites list
|
||||
* @param visible_slots simplified representation of the `Equipment` that can see "seen" on the target
|
||||
* @param inventory simplified representation of the `Equipment` in the target's inventory or trunk
|
||||
*/
|
||||
abstract class Loadout(label : String,
|
||||
visible_slots : List[Loadout.SimplifiedEntry],
|
||||
inventory : List[Loadout.SimplifiedEntry])
|
||||
abstract class Loadout(label : String)
|
||||
|
||||
object Loadout {
|
||||
def Create(owner : Any, label : String) : Try[Loadout] = {
|
||||
owner match {
|
||||
case p : Player => Success(Create(p, label))
|
||||
case v : Vehicle => Success(Create(v, label))
|
||||
case s : Squad => Success(Create(s, s.Task))
|
||||
case _ => Failure(new MatchError(s"can not create a loadout based on the (current status of) $owner"))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce the blueprint on a player.
|
||||
* @param player the player
|
||||
|
|
@ -54,12 +53,31 @@ object Loadout {
|
|||
def Create(vehicle : Vehicle, label : String) : Loadout = {
|
||||
VehicleLoadout(
|
||||
label,
|
||||
packageSimplifications(vehicle.Weapons.map({ case ((index, weapon)) => InventoryItem(weapon.Equipment.get, index) }).toList),
|
||||
packageSimplifications(vehicle.Weapons.map({ case (index, weapon) => InventoryItem(weapon.Equipment.get, index) }).toList),
|
||||
packageSimplifications(vehicle.Trunk.Items),
|
||||
vehicle.Definition
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
*/
|
||||
def Create(squad : Squad, label : String) : Loadout = {
|
||||
SquadLoadout(
|
||||
label,
|
||||
if(squad.CustomZoneId) { Some(squad.ZoneId) } else { None },
|
||||
squad.Membership
|
||||
.zipWithIndex
|
||||
.filter { case (_, index) =>
|
||||
squad.Availability(index)
|
||||
}
|
||||
.map {case (member, index) =>
|
||||
SquadPositionLoadout(index, member.Role, member.Orders, member.Requirements)
|
||||
}
|
||||
.toList
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A basic `Trait` connecting all of the `Equipment` blueprints.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.objects.loadouts
|
||||
|
||||
import net.psforever.types.CertificationType
|
||||
|
||||
final case class SquadPositionLoadout(index : Int, role : String, orders : String, requirements : Set[CertificationType.Value])
|
||||
|
||||
final case class SquadLoadout(task : String,
|
||||
zone_id : Option[Int],
|
||||
members : List[SquadPositionLoadout]) extends Loadout(task)
|
||||
|
|
@ -24,4 +24,4 @@ import net.psforever.objects.definition._
|
|||
final case class VehicleLoadout(label : String,
|
||||
visible_slots : List[Loadout.SimplifiedEntry],
|
||||
inventory : List[Loadout.SimplifiedEntry],
|
||||
vehicle_definition : VehicleDefinition) extends Loadout(label, visible_slots, inventory)
|
||||
vehicle_definition : VehicleDefinition) extends EquipmentLoadout(label, visible_slots, inventory)
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ object OrderTerminalDefinition {
|
|||
//TODO block equipment by blocking ammunition type
|
||||
final case class InfantryLoadoutPage() extends LoadoutTab {
|
||||
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
|
||||
player.LoadLoadout(msg.unk1) match {
|
||||
player.EquipmentLoadouts.LoadLoadout(msg.unk1) match {
|
||||
case Some(loadout : InfantryLoadout) if !Exclude.contains(loadout.exosuit) && !Exclude.contains((loadout.exosuit, loadout.subtype)) =>
|
||||
val holsters = loadout.visible_slots
|
||||
.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
|
||||
|
|
@ -287,7 +287,7 @@ object OrderTerminalDefinition {
|
|||
*/
|
||||
final case class VehicleLoadoutPage() extends LoadoutTab {
|
||||
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
|
||||
player.LoadLoadout(msg.unk1 + 10) match {
|
||||
player.EquipmentLoadouts.LoadLoadout(msg.unk1 + 10) match {
|
||||
case Some(loadout : VehicleLoadout) if !Exclude.contains(loadout.vehicle_definition) =>
|
||||
val weapons = loadout.visible_slots
|
||||
.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.objects.teamwork
|
||||
|
||||
import net.psforever.types.{CertificationType, Vector3}
|
||||
|
||||
class Member {
|
||||
//about the position to be filled
|
||||
private var role : String = ""
|
||||
private var orders : String = ""
|
||||
private var requirements : Set[CertificationType.Value] = Set()
|
||||
//about the individual filling the position
|
||||
private var name : String = ""
|
||||
private var charId : Long = 0L
|
||||
private var health : Int = 0
|
||||
private var armor : Int = 0
|
||||
private var zoneId : Int = 0
|
||||
private var position : Vector3 = Vector3.Zero
|
||||
|
||||
def Role : String = role
|
||||
|
||||
def Role_=(title : String) : String = {
|
||||
role = title
|
||||
Role
|
||||
}
|
||||
|
||||
def Orders : String = orders
|
||||
|
||||
def Orders_=(text : String) : String = {
|
||||
orders = text
|
||||
Orders
|
||||
}
|
||||
|
||||
def Requirements : Set[CertificationType.Value] = requirements
|
||||
|
||||
def Requirements_=(req : Set[CertificationType.Value]) : Set[CertificationType.Value] = {
|
||||
requirements = req
|
||||
Requirements
|
||||
}
|
||||
|
||||
def Name : String = name
|
||||
|
||||
def Name_=(moniker : String) : String = {
|
||||
name = moniker
|
||||
Name
|
||||
}
|
||||
|
||||
def CharId : Long = charId
|
||||
|
||||
def CharId_=(id : Long) : Long = {
|
||||
charId = id
|
||||
CharId
|
||||
}
|
||||
|
||||
def Health : Int = health
|
||||
|
||||
def Health_=(red : Int) : Int = {
|
||||
health = red
|
||||
Health
|
||||
}
|
||||
|
||||
def Armor : Int = armor
|
||||
|
||||
def Armor_=(blue : Int) : Int = {
|
||||
armor = blue
|
||||
Armor
|
||||
}
|
||||
|
||||
def ZoneId : Int = zoneId
|
||||
|
||||
def ZoneId_=(id : Int) : Int = {
|
||||
zoneId = id
|
||||
ZoneId
|
||||
}
|
||||
|
||||
def Position : Vector3 = position
|
||||
|
||||
def Position_=(pos : Vector3) : Vector3 = {
|
||||
position = pos
|
||||
Position
|
||||
}
|
||||
|
||||
def isAvailable : Boolean = {
|
||||
charId == 0
|
||||
}
|
||||
|
||||
def isAvailable(certs : Set[CertificationType.Value]) : Boolean = {
|
||||
isAvailable && certs.intersect(requirements) == requirements
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.objects.teamwork
|
||||
|
||||
import net.psforever.objects.entity.IdentifiableEntity
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import net.psforever.types.{CertificationType, PlanetSideEmpire}
|
||||
|
||||
class Squad(squadId : PlanetSideGUID, alignment : PlanetSideEmpire.Value) extends IdentifiableEntity {
|
||||
super.GUID_=(squadId)
|
||||
private val faction : PlanetSideEmpire.Value = alignment //does not change
|
||||
private var zoneId : Option[Int] = None
|
||||
private var task : String = ""
|
||||
private val membership : Array[Member] = Array.fill[Member](10)(new Member)
|
||||
private val availability : Array[Boolean] = Array.fill[Boolean](10)(elem = true)
|
||||
|
||||
override def GUID_=(d : PlanetSideGUID) : PlanetSideGUID = GUID
|
||||
|
||||
def Faction : PlanetSideEmpire.Value = faction
|
||||
|
||||
def CustomZoneId : Boolean = zoneId.isDefined
|
||||
|
||||
def ZoneId : Int = zoneId.getOrElse(membership(0).ZoneId)
|
||||
|
||||
def ZoneId_=(id : Int) : Int = {
|
||||
ZoneId_=(Some(id))
|
||||
}
|
||||
|
||||
def ZoneId_=(id : Option[Int]) : Int = {
|
||||
zoneId = id
|
||||
ZoneId
|
||||
}
|
||||
|
||||
def Task : String = task
|
||||
|
||||
def Task_=(assignment : String) : String = {
|
||||
task = assignment
|
||||
Task
|
||||
}
|
||||
|
||||
def Membership : Array[Member] = membership
|
||||
|
||||
def Availability : Array[Boolean] = availability
|
||||
|
||||
def Leader : Member = {
|
||||
membership(0) match {
|
||||
case member if !member.Name.equals("") =>
|
||||
member
|
||||
case _ =>
|
||||
throw new Exception("can not find squad leader!")
|
||||
}
|
||||
}
|
||||
|
||||
def Size : Int = membership.count(member => member.CharId != 0)
|
||||
|
||||
def Capacity : Int = availability.count(open => open)
|
||||
|
||||
def isAvailable(role : Int) : Boolean = {
|
||||
availability.lift(role) match {
|
||||
case Some(true) =>
|
||||
membership(role).isAvailable
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
def isAvailable(role : Int, certs : Set[CertificationType.Value]) : Boolean = {
|
||||
availability.lift(role) match {
|
||||
case Some(true) =>
|
||||
membership(role).isAvailable(certs)
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Squad {
|
||||
final val Blank = new Squad(PlanetSideGUID(0), PlanetSideEmpire.NEUTRAL) {
|
||||
override def ZoneId : Int = 0
|
||||
override def ZoneId_=(id : Int) : Int = 0
|
||||
override def ZoneId_=(id : Option[Int]) : Int = 0
|
||||
override def Task_=(assignment : String) : String = ""
|
||||
override def Membership : Array[Member] = Array.empty[Member]
|
||||
override def Availability : Array[Boolean] = Array.fill[Boolean](10)(false)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.objects.teamwork
|
||||
|
||||
import akka.actor.{Actor, ActorContext, ActorRef, Props}
|
||||
import net.psforever.types.SquadWaypoints
|
||||
import services.teamwork.SquadService.WaypointData
|
||||
import services.teamwork.SquadSwitchboard
|
||||
|
||||
class SquadFeatures(val Squad : Squad) {
|
||||
/**
|
||||
* `initialAssociation` per squad is similar to "Does this squad want to recruit members?"
|
||||
* The squad does not have to be flagged.
|
||||
* Dispatches an `AssociateWithSquad` `SquadDefinitionActionMessage` packet to the squad leader and ???
|
||||
* and then a `SquadDetailDefinitionUpdateMessage` that includes at least the squad owner name and char id.
|
||||
* Dispatched only once when a squad is first listed
|
||||
* or when the squad leader searches for recruits by proximity or for certain roles or by invite
|
||||
* or when a spontaneous squad forms,
|
||||
* whatever happens first.
|
||||
* Additionally, the packets are also sent when the check is made when the continent is changed (or set).
|
||||
*/
|
||||
private var initialAssociation : Boolean = true
|
||||
/**
|
||||
* na
|
||||
*/
|
||||
private var switchboard : ActorRef = ActorRef.noSender
|
||||
/**
|
||||
* Waypoint data.
|
||||
* The first four slots are used for squad waypoints.
|
||||
* The fifth slot is used for the squad leader experience waypoint.<br>
|
||||
* <br>
|
||||
* All of the waypoints constantly exist as long as the squad to which they are attached exists.
|
||||
* They are merely "activated" and "deactivated."
|
||||
* When "activated," the waypoint knows on which continent to appear and where on the map and in the game world to be positioned.
|
||||
* Waypoints manifest in the game world as a far-off beam of light that extends into the sky
|
||||
* and whose ground contact utilizes a downwards pulsating arrow.
|
||||
* On the continental map and deployment map, they appear as a diamond, with a differentiating number where applicable.
|
||||
* The squad leader experience rally, for example, does not have a number like the preceding four waypoints.
|
||||
* @see `Start`
|
||||
*/
|
||||
private var waypoints : Array[WaypointData] = Array[WaypointData]()
|
||||
/**
|
||||
* The particular position being recruited right at the moment.
|
||||
* When `None`. no highlighted searches have been indicated.
|
||||
* When a positive integer or 0, indicates distributed `LookingForSquadRoleInvite` messages as recorded by `proxyInvites`.
|
||||
* Only one position may bne actively recruited at a time in this case.
|
||||
* When -1, indicates distributed `ProximityIvite` messages as recorded by `proxyInvites`.
|
||||
* Previous efforts may or may not be forgotten if there is a switch between the two modes.
|
||||
*/
|
||||
private var searchForRole : Option[Int] = None
|
||||
/**
|
||||
* Handle persistent data related to `ProximityInvite` and `LookingForSquadRoleInvite` messages
|
||||
*/
|
||||
private var proxyInvites : List[Long] = Nil
|
||||
/**
|
||||
* These useres rejected invitation to this squad.
|
||||
* For the purposes of wide-searches for membership
|
||||
* such as Looking For Squad checks and proximity invitation,
|
||||
* the unique character identifier numbers in this list are skipped.
|
||||
* Direct invitation requests from the non sqad member should remain functional.
|
||||
*/
|
||||
private var refusedPlayers : List[Long] = Nil
|
||||
private var autoApproveInvitationRequests : Boolean = true
|
||||
private var locationFollowsSquadLead : Boolean = true
|
||||
|
||||
private var listed : Boolean = false
|
||||
|
||||
private lazy val channel : String = s"${Squad.Faction}-Squad${Squad.GUID.guid}"
|
||||
|
||||
def Start(implicit context : ActorContext) : SquadFeatures = {
|
||||
switchboard = context.actorOf(Props[SquadSwitchboard], s"squad${Squad.GUID.guid}")
|
||||
waypoints = Array.fill[WaypointData](SquadWaypoints.values.size)(new WaypointData())
|
||||
this
|
||||
}
|
||||
|
||||
def Stop : SquadFeatures = {
|
||||
switchboard ! akka.actor.PoisonPill
|
||||
switchboard = Actor.noSender
|
||||
waypoints = Array.empty
|
||||
this
|
||||
}
|
||||
|
||||
def InitialAssociation : Boolean = initialAssociation
|
||||
|
||||
def InitialAssociation_=(assoc : Boolean) : Boolean = {
|
||||
initialAssociation = assoc
|
||||
InitialAssociation
|
||||
}
|
||||
|
||||
def Switchboard : ActorRef = switchboard
|
||||
|
||||
def Waypoints : Array[WaypointData] = waypoints
|
||||
|
||||
def SearchForRole : Option[Int] = searchForRole
|
||||
|
||||
def SearchForRole_=(role : Int) : Option[Int] = SearchForRole_=(Some(role))
|
||||
|
||||
def SearchForRole_=(role : Option[Int]) : Option[Int] = {
|
||||
searchForRole = role
|
||||
SearchForRole
|
||||
}
|
||||
|
||||
def ProxyInvites : List[Long] = proxyInvites
|
||||
|
||||
def ProxyInvites_=(list : List[Long]) : List[Long] = {
|
||||
proxyInvites = list
|
||||
ProxyInvites
|
||||
}
|
||||
|
||||
def Refuse : List[Long] = refusedPlayers
|
||||
|
||||
def Refuse_=(charId : Long) : List[Long] = {
|
||||
Refuse_=(List(charId))
|
||||
}
|
||||
|
||||
def Refuse_=(list : List[Long]) : List[Long] = {
|
||||
refusedPlayers = list ++ refusedPlayers
|
||||
Refuse
|
||||
}
|
||||
|
||||
def LocationFollowsSquadLead : Boolean = locationFollowsSquadLead
|
||||
|
||||
def LocationFollowsSquadLead_=(follow : Boolean) : Boolean = {
|
||||
locationFollowsSquadLead = follow
|
||||
LocationFollowsSquadLead
|
||||
}
|
||||
|
||||
def AutoApproveInvitationRequests : Boolean = autoApproveInvitationRequests
|
||||
|
||||
def AutoApproveInvitationRequests_=(autoApprove : Boolean) : Boolean = {
|
||||
autoApproveInvitationRequests = autoApprove
|
||||
AutoApproveInvitationRequests
|
||||
}
|
||||
|
||||
def Listed : Boolean = listed
|
||||
|
||||
def Listed_=(announce : Boolean) : Boolean = {
|
||||
listed = announce
|
||||
Listed
|
||||
}
|
||||
|
||||
def ToChannel : String = channel
|
||||
}
|
||||
|
|
@ -446,18 +446,18 @@ object GamePacketOpcode extends Enumeration {
|
|||
case 0x6b => game.TriggerSoundMessage.decode
|
||||
case 0x6c => game.LootItemMessage.decode
|
||||
case 0x6d => game.VehicleSubStateMessage.decode
|
||||
case 0x6e => noDecoder(SquadMembershipRequest)
|
||||
case 0x6f => noDecoder(SquadMembershipResponse)
|
||||
case 0x6e => game.SquadMembershipRequest.decode
|
||||
case 0x6f => game.SquadMembershipResponse.decode
|
||||
|
||||
// OPCODES 0x70-7f
|
||||
case 0x70 => noDecoder(SquadMemberEvent)
|
||||
case 0x70 => game.SquadMemberEvent.decode
|
||||
case 0x71 => noDecoder(PlatoonEvent)
|
||||
case 0x72 => game.FriendsRequest.decode
|
||||
case 0x73 => game.FriendsResponse.decode
|
||||
case 0x74 => game.TriggerEnvironmentalDamageMessage.decode
|
||||
case 0x75 => game.TrainingZoneMessage.decode
|
||||
case 0x76 => game.DeployableObjectsInfoMessage.decode
|
||||
case 0x77 => noDecoder(SquadState)
|
||||
case 0x77 => game.SquadState.decode
|
||||
// 0x78
|
||||
case 0x78 => game.OxygenStateMessage.decode
|
||||
case 0x79 => noDecoder(TradeMessage)
|
||||
|
|
@ -472,7 +472,7 @@ object GamePacketOpcode extends Enumeration {
|
|||
case 0x80 => noDecoder(GenericObjectAction2Message)
|
||||
case 0x81 => game.DestroyDisplayMessage.decode
|
||||
case 0x82 => noDecoder(TriggerBotAction)
|
||||
case 0x83 => noDecoder(SquadWaypointRequest)
|
||||
case 0x83 => game.SquadWaypointRequest.decode
|
||||
case 0x84 => game.SquadWaypointEvent.decode
|
||||
case 0x85 => noDecoder(OffshoreVehicleMessage)
|
||||
case 0x86 => game.ObjectDeployedMessage.decode
|
||||
|
|
@ -592,11 +592,11 @@ object GamePacketOpcode extends Enumeration {
|
|||
case 0xe6 => game.ReplicationStreamMessage.decode
|
||||
case 0xe7 => game.SquadDefinitionActionMessage.decode
|
||||
// 0xe8
|
||||
case 0xe8 => noDecoder(SquadDetailDefinitionUpdateMessage)
|
||||
case 0xe8 => game.SquadDetailDefinitionUpdateMessage.decode
|
||||
case 0xe9 => noDecoder(TacticsMessage)
|
||||
case 0xea => noDecoder(RabbitUpdateMessage)
|
||||
case 0xeb => noDecoder(SquadInvitationRequestMessage)
|
||||
case 0xec => noDecoder(CharacterKnowledgeMessage)
|
||||
case 0xeb => game.SquadInvitationRequestMessage.decode
|
||||
case 0xec => game.CharacterKnowledgeMessage.decode
|
||||
case 0xed => noDecoder(GameScoreUpdateMessage)
|
||||
case 0xee => noDecoder(UnknownMessage238)
|
||||
case 0xef => noDecoder(OrderTerminalBugMessage)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||
import net.psforever.types.CertificationType
|
||||
import scodec.Codec
|
||||
import scodec.codecs._
|
||||
import shapeless.{::, HNil}
|
||||
|
||||
final case class CharacterKnowledgeInfo(name : String,
|
||||
permissions : Set[CertificationType.Value],
|
||||
unk1 : Int,
|
||||
unk2 : Int,
|
||||
unk3 : PlanetSideGUID)
|
||||
|
||||
final case class CharacterKnowledgeMessage(char_id : Long,
|
||||
info : Option[CharacterKnowledgeInfo])
|
||||
extends PlanetSideGamePacket {
|
||||
type Packet = CharacterKnowledgeMessage
|
||||
def opcode = GamePacketOpcode.CharacterKnowledgeMessage
|
||||
def encode = CharacterKnowledgeMessage.encode(this)
|
||||
}
|
||||
|
||||
object CharacterKnowledgeMessage extends Marshallable[CharacterKnowledgeMessage] {
|
||||
def apply(char_id : Long) : CharacterKnowledgeMessage =
|
||||
CharacterKnowledgeMessage(char_id, None)
|
||||
|
||||
def apply(char_id : Long, info : CharacterKnowledgeInfo) : CharacterKnowledgeMessage =
|
||||
CharacterKnowledgeMessage(char_id, Some(info))
|
||||
|
||||
private val inverter : Codec[Boolean] = bool.xmap[Boolean] (
|
||||
state => !state,
|
||||
state => !state
|
||||
)
|
||||
|
||||
private val info_codec : Codec[CharacterKnowledgeInfo] = (
|
||||
("name" | PacketHelpers.encodedWideStringAligned(adjustment = 7)) ::
|
||||
("permissions" | ulongL(bits = 46)) ::
|
||||
("unk1" | uint(bits = 6)) ::
|
||||
("unk2" | uint(bits = 3)) ::
|
||||
("unk3" | PlanetSideGUID.codec)
|
||||
).xmap[CharacterKnowledgeInfo] (
|
||||
{
|
||||
case name :: permissions :: u1 :: u2 :: u3 :: HNil =>
|
||||
CharacterKnowledgeInfo(name, CertificationType.fromEncodedLong(permissions), u1, u2, u3)
|
||||
},
|
||||
{
|
||||
case CharacterKnowledgeInfo(name, permissions, u1, u2, u3) =>
|
||||
name :: CertificationType.toEncodedLong(permissions) :: u1 :: u2 :: u3 :: HNil
|
||||
}
|
||||
)
|
||||
|
||||
implicit val codec : Codec[CharacterKnowledgeMessage] = (
|
||||
("char_id" | uint32L) ::
|
||||
("info" | optional(inverter, info_codec))
|
||||
).as[CharacterKnowledgeMessage]
|
||||
}
|
||||
|
|
@ -118,8 +118,12 @@ import scodec.codecs._
|
|||
* `27 - PA_JAMMED - plays jammed buzzing sound`<br>
|
||||
* `28 - PA_IMPLANT_ACTIVE - Plays implant sounds. Valid values seem to be up to 20.`<br>
|
||||
* `29 - PA_VAPORIZED - Visible ?! That's not the cloaked effect, Maybe for spectator mode ?. Value is 0 to visible, 1 to invisible.`<br>
|
||||
* `31 - Info under avatar name : 0 = LFS, 1 = Looking For Squad Members`<br>
|
||||
* `32 - Info under avatar name : 0 = Looking For Squad Members, 1 = LFS`<br>
|
||||
* `31 - Looking for Squad info (marquee and ui):<br>
|
||||
* ` - 0 is LFS`<br>
|
||||
* ` - 1 is LFSM (Looking for Squad Members)`<br>
|
||||
* ` - n is the supplemental squad identifier number; same as "LFS;" for the leader, sets "LFSM" after the first manual flagging`<br>
|
||||
* `32 - Maintain the squad role index, when a member of a squad;<br>
|
||||
* - OLD: "Info under avatar name : 0 = Looking For Squad Members, 1 = LFS`"<br>
|
||||
* `35 - BR. Value is the BR`<br>
|
||||
* `36 - CR. Value is the CR`<br>
|
||||
* `43 - Info on avatar name : 0 = Nothing, 1 = "(LD)" message`<br>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2,26 +2,375 @@
|
|||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||
import net.psforever.types.CertificationType
|
||||
import scodec.bits.BitVector
|
||||
import scodec.{Attempt, Codec, Err}
|
||||
import scodec.codecs._
|
||||
import shapeless.{::, HNil}
|
||||
|
||||
/**
|
||||
* The generic superclass of a specific behavior for this type of squad definition action.
|
||||
* All behaviors have a "code" that indicates how the rest of the data is parsed.
|
||||
* @param code the action behavior code
|
||||
*/
|
||||
abstract class SquadAction(val code : Int)
|
||||
|
||||
object SquadAction{
|
||||
object SearchMode extends Enumeration {
|
||||
type Type = Value
|
||||
|
||||
val
|
||||
AnyPositions,
|
||||
AvailablePositions,
|
||||
SomeCertifications,
|
||||
AllCertifications
|
||||
= Value
|
||||
|
||||
implicit val codec : Codec[SearchMode.Value] = PacketHelpers.createEnumerationCodec(enum = this, uint(bits = 3))
|
||||
}
|
||||
|
||||
final case class DisplaySquad() extends SquadAction(0)
|
||||
|
||||
/**
|
||||
* Dispatched from client to server to indicate a squad detail update that has no foundation entry to update?
|
||||
* Not dissimilar from `DisplaySquad`.
|
||||
*/
|
||||
final case class SquadMemberInitializationIssue() extends SquadAction(1)
|
||||
|
||||
final case class SaveSquadFavorite() extends SquadAction(3)
|
||||
|
||||
final case class LoadSquadFavorite() extends SquadAction(4)
|
||||
|
||||
final case class DeleteSquadFavorite() extends SquadAction(5)
|
||||
|
||||
final case class ListSquadFavorite(name : String) extends SquadAction(7)
|
||||
|
||||
final case class RequestListSquad() extends SquadAction(8)
|
||||
|
||||
final case class StopListSquad() extends SquadAction(9)
|
||||
|
||||
final case class SelectRoleForYourself(state : Int) extends SquadAction(10)
|
||||
|
||||
final case class CancelSelectRoleForYourself(value: Long = 0) extends SquadAction(15)
|
||||
|
||||
final case class AssociateWithSquad() extends SquadAction(16)
|
||||
|
||||
final case class SetListSquad() extends SquadAction(17)
|
||||
|
||||
final case class ChangeSquadPurpose(purpose : String) extends SquadAction(19)
|
||||
|
||||
final case class ChangeSquadZone(zone : PlanetSideZoneID) extends SquadAction(20)
|
||||
|
||||
final case class CloseSquadMemberPosition(position : Int) extends SquadAction(21)
|
||||
|
||||
final case class AddSquadMemberPosition(position : Int) extends SquadAction(22)
|
||||
|
||||
final case class ChangeSquadMemberRequirementsRole(u1 : Int, role : String) extends SquadAction(23)
|
||||
|
||||
final case class ChangeSquadMemberRequirementsDetailedOrders(u1 : Int, orders : String) extends SquadAction(24)
|
||||
|
||||
final case class ChangeSquadMemberRequirementsCertifications(u1 : Int, certs : Set[CertificationType.Value]) extends SquadAction(25)
|
||||
|
||||
final case class ResetAll() extends SquadAction(26)
|
||||
|
||||
final case class AutoApproveInvitationRequests(state : Boolean) extends SquadAction(28)
|
||||
|
||||
final case class LocationFollowsSquadLead(state : Boolean) extends SquadAction(31)
|
||||
|
||||
final case class SearchForSquadsWithParticularRole(role: String, requirements : Set[CertificationType.Value], zone_id: Int, mode : SearchMode.Value) extends SquadAction(34)
|
||||
|
||||
final case class CancelSquadSearch() extends SquadAction(35)
|
||||
|
||||
final case class AssignSquadMemberToRole(position : Int, char_id : Long) extends SquadAction(38)
|
||||
|
||||
final case class NoSquadSearchResults() extends SquadAction(39)
|
||||
|
||||
final case class FindLfsSoldiersForRole(state : Int) extends SquadAction(40)
|
||||
|
||||
final case class CancelFind() extends SquadAction(41)
|
||||
|
||||
final case class Unknown(badCode : Int, data : BitVector) extends SquadAction(badCode)
|
||||
|
||||
object Unknown {
|
||||
import scodec.bits._
|
||||
val StandardBits : BitVector = hex"00".toBitVector.take(6)
|
||||
|
||||
def apply(badCode : Int) : Unknown = Unknown(badCode, StandardBits)
|
||||
}
|
||||
|
||||
/**
|
||||
* The `Codec`s used to transform the input stream into the context of a specific action
|
||||
* and extract the field data from that stream.
|
||||
*/
|
||||
object Codecs {
|
||||
private val everFailCondition = conditional(included = false, bool)
|
||||
|
||||
val displaySquadCodec = everFailCondition.xmap[DisplaySquad] (
|
||||
_ => DisplaySquad(),
|
||||
{
|
||||
case DisplaySquad() => None
|
||||
}
|
||||
)
|
||||
|
||||
val squadMemberInitializationIssueCodec = everFailCondition.xmap[SquadMemberInitializationIssue] (
|
||||
_ => SquadMemberInitializationIssue(),
|
||||
{
|
||||
case SquadMemberInitializationIssue() => None
|
||||
}
|
||||
)
|
||||
|
||||
val saveSquadFavoriteCodec = everFailCondition.xmap[SaveSquadFavorite] (
|
||||
_ => SaveSquadFavorite(),
|
||||
{
|
||||
case SaveSquadFavorite() => None
|
||||
}
|
||||
)
|
||||
|
||||
val loadSquadFavoriteCodec = everFailCondition.xmap[LoadSquadFavorite] (
|
||||
_ => LoadSquadFavorite(),
|
||||
{
|
||||
case LoadSquadFavorite() => None
|
||||
}
|
||||
)
|
||||
|
||||
val deleteSquadFavoriteCodec = everFailCondition.xmap[DeleteSquadFavorite] (
|
||||
_ => DeleteSquadFavorite(),
|
||||
{
|
||||
case DeleteSquadFavorite() => None
|
||||
}
|
||||
)
|
||||
|
||||
val listSquadFavoriteCodec = PacketHelpers.encodedWideStringAligned(6).xmap[ListSquadFavorite] (
|
||||
text => ListSquadFavorite(text),
|
||||
{
|
||||
case ListSquadFavorite(text) => text
|
||||
}
|
||||
)
|
||||
|
||||
val requestListSquadCodec = everFailCondition.xmap[RequestListSquad] (
|
||||
_ => RequestListSquad(),
|
||||
{
|
||||
case RequestListSquad() => None
|
||||
}
|
||||
)
|
||||
|
||||
val stopListSquadCodec = everFailCondition.xmap[StopListSquad] (
|
||||
_ => StopListSquad(),
|
||||
{
|
||||
case StopListSquad() => None
|
||||
}
|
||||
)
|
||||
|
||||
val selectRoleForYourselfCodec = uint4.xmap[SelectRoleForYourself] (
|
||||
value => SelectRoleForYourself(value),
|
||||
{
|
||||
case SelectRoleForYourself(value) => value
|
||||
}
|
||||
)
|
||||
|
||||
val cancelSelectRoleForYourselfCodec = uint32.xmap[CancelSelectRoleForYourself] (
|
||||
value => CancelSelectRoleForYourself(value),
|
||||
{
|
||||
case CancelSelectRoleForYourself(value) => value
|
||||
}
|
||||
)
|
||||
|
||||
val associateWithSquadCodec = everFailCondition.xmap[AssociateWithSquad] (
|
||||
_ => AssociateWithSquad(),
|
||||
{
|
||||
case AssociateWithSquad() => None
|
||||
}
|
||||
)
|
||||
|
||||
val setListSquadCodec = everFailCondition.xmap[SetListSquad] (
|
||||
_ => SetListSquad(),
|
||||
{
|
||||
case SetListSquad() => None
|
||||
}
|
||||
)
|
||||
|
||||
val changeSquadPurposeCodec = PacketHelpers.encodedWideStringAligned(6).xmap[ChangeSquadPurpose] (
|
||||
purpose => ChangeSquadPurpose(purpose),
|
||||
{
|
||||
case ChangeSquadPurpose(purpose) => purpose
|
||||
}
|
||||
)
|
||||
|
||||
val changeSquadZoneCodec = uint16L.xmap[ChangeSquadZone] (
|
||||
value => ChangeSquadZone(PlanetSideZoneID(value)),
|
||||
{
|
||||
case ChangeSquadZone(value) => value.zoneId.toInt
|
||||
}
|
||||
)
|
||||
|
||||
val closeSquadMemberPositionCodec = uint4.xmap[CloseSquadMemberPosition] (
|
||||
position => CloseSquadMemberPosition(position),
|
||||
{
|
||||
case CloseSquadMemberPosition(position) => position
|
||||
}
|
||||
)
|
||||
|
||||
val addSquadMemberPositionCodec = uint4.xmap[AddSquadMemberPosition] (
|
||||
position => AddSquadMemberPosition(position),
|
||||
{
|
||||
case AddSquadMemberPosition(position) => position
|
||||
}
|
||||
)
|
||||
|
||||
val changeSquadMemberRequirementsRoleCodec = (uint4L :: PacketHelpers.encodedWideStringAligned(2)).xmap[ChangeSquadMemberRequirementsRole] (
|
||||
{
|
||||
case u1 :: role :: HNil => ChangeSquadMemberRequirementsRole(u1, role)
|
||||
},
|
||||
{
|
||||
case ChangeSquadMemberRequirementsRole(u1, role) => u1 :: role :: HNil
|
||||
}
|
||||
)
|
||||
|
||||
val changeSquadMemberRequirementsDetailedOrdersCodec = (uint4L :: PacketHelpers.encodedWideStringAligned(2)).xmap[ChangeSquadMemberRequirementsDetailedOrders] (
|
||||
{
|
||||
case u1 :: role :: HNil => ChangeSquadMemberRequirementsDetailedOrders(u1, role)
|
||||
},
|
||||
{
|
||||
case ChangeSquadMemberRequirementsDetailedOrders(u1, role) => u1 :: role :: HNil
|
||||
}
|
||||
)
|
||||
|
||||
val changeSquadMemberRequirementsCertificationsCodec = (uint4 :: ulongL(46)).xmap[ChangeSquadMemberRequirementsCertifications] (
|
||||
{
|
||||
case u1 :: u2 :: HNil =>
|
||||
ChangeSquadMemberRequirementsCertifications(u1, CertificationType.fromEncodedLong(u2))
|
||||
},
|
||||
{
|
||||
case ChangeSquadMemberRequirementsCertifications(u1, u2) =>
|
||||
u1 :: CertificationType.toEncodedLong(u2) :: HNil
|
||||
}
|
||||
)
|
||||
|
||||
val resetAllCodec = everFailCondition.xmap[ResetAll] (
|
||||
_ => ResetAll(),
|
||||
{
|
||||
case ResetAll() => None
|
||||
}
|
||||
)
|
||||
|
||||
val autoApproveInvitationRequestsCodec = bool.xmap[AutoApproveInvitationRequests] (
|
||||
state => AutoApproveInvitationRequests(state),
|
||||
{
|
||||
case AutoApproveInvitationRequests(state) => state
|
||||
}
|
||||
)
|
||||
|
||||
val locationFollowsSquadLeadCodec = bool.xmap[LocationFollowsSquadLead] (
|
||||
state => LocationFollowsSquadLead(state),
|
||||
{
|
||||
case LocationFollowsSquadLead(state) => state
|
||||
}
|
||||
)
|
||||
|
||||
val searchForSquadsWithParticularRoleCodec = (
|
||||
PacketHelpers.encodedWideStringAligned(6) ::
|
||||
ulongL(46) ::
|
||||
uint16L ::
|
||||
SearchMode.codec).xmap[SearchForSquadsWithParticularRole] (
|
||||
{
|
||||
case u1 :: u2 :: u3 :: u4 :: HNil => SearchForSquadsWithParticularRole(u1, CertificationType.fromEncodedLong(u2), u3, u4)
|
||||
},
|
||||
{
|
||||
case SearchForSquadsWithParticularRole(u1, u2, u3, u4) => u1 :: CertificationType.toEncodedLong(u2) :: u3 :: u4 :: HNil
|
||||
}
|
||||
)
|
||||
|
||||
val cancelSquadSearchCodec = everFailCondition.xmap[CancelSquadSearch] (
|
||||
_ => CancelSquadSearch(),
|
||||
{
|
||||
case CancelSquadSearch() => None
|
||||
}
|
||||
)
|
||||
|
||||
val assignSquadMemberToRoleCodec = (uint4 :: uint32L).xmap[AssignSquadMemberToRole] (
|
||||
{
|
||||
case u1 :: u2 :: HNil => AssignSquadMemberToRole(u1, u2)
|
||||
},
|
||||
{
|
||||
case AssignSquadMemberToRole(u1, u2) => u1 :: u2 :: HNil
|
||||
}
|
||||
)
|
||||
|
||||
val noSquadSearchResultsCodec = everFailCondition.xmap[NoSquadSearchResults] (
|
||||
_ => NoSquadSearchResults(),
|
||||
{
|
||||
case NoSquadSearchResults() => None
|
||||
}
|
||||
)
|
||||
|
||||
val findLfsSoldiersForRoleCodec = uint4.xmap[FindLfsSoldiersForRole] (
|
||||
state => FindLfsSoldiersForRole(state),
|
||||
{
|
||||
case FindLfsSoldiersForRole(state) => state
|
||||
}
|
||||
)
|
||||
|
||||
val cancelFindCodec = everFailCondition.xmap[CancelFind] (
|
||||
_ => CancelFind(),
|
||||
{
|
||||
case CancelFind() => None
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* A common form for known action code indexes with an unknown purpose and transformation is an "Unknown" object.
|
||||
* @param action the action behavior code
|
||||
* @return a transformation between the action code and the unknown bit data
|
||||
*/
|
||||
def unknownCodec(action : Int) = bits.xmap[Unknown] (
|
||||
data => Unknown(action, data),
|
||||
{
|
||||
case Unknown(_, data) => data
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* The action code was completely unanticipated!
|
||||
* @param action the action behavior code
|
||||
* @return nothing; always fail
|
||||
*/
|
||||
def failureCodec(action : Int)= everFailCondition.exmap[SquadAction] (
|
||||
_ => Attempt.failure(Err(s"can not match a codec pattern for decoding $action")),
|
||||
_ => Attempt.failure(Err(s"can not match a codec pattern for encoding $action"))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage composition and details of a player's current squad, or the currently-viewed squad.<br>
|
||||
* <br>
|
||||
* The `action` code indicates the format of the remainder data in the packet.
|
||||
* The following formats are translated; their purposes are listed:<br>
|
||||
* `(None)`<br>
|
||||
* `3 ` - Save Squad Definition
|
||||
* `8 ` - List Squad
|
||||
* `26` - Reset All
|
||||
* `35` - Cancel Squad Search
|
||||
* `41` - Cancel Find
|
||||
* `0 ` - Display Squad<br>
|
||||
* `1 ` - Answer Squad Join Request<br>
|
||||
* `2 ` - UNKNOWN<br>
|
||||
* `3 ` - Save Squad Favorite<br>
|
||||
* `4 ` - Load Squad Favorite<br>
|
||||
* `5 ` - Delete Squad Favorite<br>
|
||||
* `6 ` - UNKNOWN<br>
|
||||
* `8 ` - Request List Squad<br>
|
||||
* `9 ` - Stop List Squad<br>
|
||||
* `16` - Associate with Squad<br>
|
||||
* `17` - Set List Squad (ui)<br>
|
||||
* `18` - UNKNOWN<br>
|
||||
* `26` - Reset All<br>
|
||||
* `32` - UNKNOWN<br>
|
||||
* `35` - Cancel Squad Search<br>
|
||||
* `39` - No Squad Search Results<br>
|
||||
* `41` - Cancel Find<br>
|
||||
* `42` - UNKNOWN<br>
|
||||
* `43` - UNKNOWN<br>
|
||||
* `Boolean`<br>
|
||||
* `28` - Auto-approve Requests for Invitation<br>
|
||||
* `29` - UNKNOWN<br>
|
||||
* `30` - UNKNOWN<br>
|
||||
* `31` - Location Follows Squad Lead<br>
|
||||
* `31` - Location Follows Squad Leader<br>
|
||||
* `Int`<br>
|
||||
* `10` - Select this Role for Yourself<br>
|
||||
* `11` - UNKNOWN<br>
|
||||
|
|
@ -33,49 +382,30 @@ import shapeless.{::, HNil}
|
|||
* `Long`<br>
|
||||
* `13` - UNKNOWN<br>
|
||||
* `14` - UNKNOWN<br>
|
||||
* `15` - UNKNOWN<br>
|
||||
* `15` - Select this Role for Yourself<br>
|
||||
* `37` - UNKNOWN<br>
|
||||
* `String`<br>
|
||||
* `7 ` - UNKNOWN<br>
|
||||
* `7 ` - List Squad Favorite<br>
|
||||
* `19` - (Squad leader) Change Squad Purpose<br>
|
||||
* `Int :: Long`<br>
|
||||
* `12` - UNKNOWN<br>
|
||||
* `25` - (Squad leader) Change Squad Member Requirements - Weapons<br>
|
||||
* `38` - UNKNOWN<br>
|
||||
* `38` - Assign Squad Member To Role<br>
|
||||
* `Int :: String`<br>
|
||||
* `23` - (Squad leader) Change Squad Member Requirements - Role<br>
|
||||
* `24` - (Squad leader) Change Squad Member Requirements - Detailed Orders<br>
|
||||
* `Long :: Long`<br>
|
||||
* `36` - UNKNOWN<br>
|
||||
* `String :: Long :: Int :: Int`<br>
|
||||
* `34` - Search for Squads with a Particular Role<br>
|
||||
* <br>
|
||||
* Exploration:<br>
|
||||
* Some notes regarding the full list of action codes follows after this packet.
|
||||
* Asides from codes whose behaviors are unknown, some codes also have unknown data format.
|
||||
* No information for codes 1, 5, 9, 27, or 35 has been found yet.
|
||||
* `34` - Search for Squads with a Particular Role
|
||||
* @param squad_guid the unique identifier of the squad, if non-zero
|
||||
* @param line the original listing line number, if applicable
|
||||
* @param action the purpose of this packet;
|
||||
* also decides the content of the parameter fields
|
||||
* @param unk1 na
|
||||
* @param unk2 na
|
||||
* @param string_opt the optional `String` parameter
|
||||
* @param int1_opt the first optional `Int` parameter;
|
||||
* will not necessarily conform to a single bit length
|
||||
* @param int2_opt the second optional `Int` parameter
|
||||
* @param long1_opt the first optional `Long` parameter;
|
||||
* will not necessarily conform to a single bit length
|
||||
* @param long2_opt the second optional `Long` parameter
|
||||
* @param bool_opt the optional `Boolean` parameter
|
||||
*/
|
||||
final case class SquadDefinitionActionMessage(action : Int,
|
||||
unk1 : Int,
|
||||
unk2 : Int,
|
||||
string_opt : Option[String],
|
||||
int1_opt : Option[Int],
|
||||
int2_opt : Option[Int],
|
||||
long1_opt : Option[Long],
|
||||
long2_opt : Option[Long],
|
||||
bool_opt : Option[Boolean])
|
||||
final case class SquadDefinitionActionMessage(squad_guid : PlanetSideGUID,
|
||||
line : Int,
|
||||
action : SquadAction)
|
||||
extends PlanetSideGamePacket {
|
||||
type Packet = SquadDefinitionActionMessage
|
||||
def opcode = GamePacketOpcode.SquadDefinitionActionMessage
|
||||
|
|
@ -84,309 +414,78 @@ final case class SquadDefinitionActionMessage(action : Int,
|
|||
|
||||
object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMessage] {
|
||||
/**
|
||||
* Common pattern for the parameters, with enough fields to support all possible outputs.
|
||||
* All fields are `Option`al purposefully.
|
||||
* Use the action code to transform between
|
||||
* the specific action object and its field data
|
||||
* and the stream of bits of the original packet.
|
||||
* @param code the action behavior code
|
||||
* @return the `SquadAction` `Codec` to use for the given `code`
|
||||
*/
|
||||
private type allPattern = Option[String] :: Option[Int] :: Option[Int] :: Option[Long] :: Option[Long] :: Option[Boolean] :: HNil
|
||||
|
||||
/**
|
||||
* `Codec` for reading nothing from the remainder of the stream data.
|
||||
* @return a filled-out `allPattern` if successful
|
||||
*/
|
||||
def noneCodec : Codec[allPattern] = ignore(0).xmap[allPattern] (
|
||||
{
|
||||
case () =>
|
||||
None :: None :: None :: None :: None :: None :: HNil
|
||||
},
|
||||
{
|
||||
case _ :: _ :: _ :: _ :: _ :: _ :: HNil =>
|
||||
()
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* `Codec` for reading a single `Boolean` from remaining stream data.
|
||||
* @return a filled-out `allPattern` if successful
|
||||
*/
|
||||
def boolCodec : Codec[allPattern] = bool.hlist.exmap[allPattern] (
|
||||
{
|
||||
case n :: HNil =>
|
||||
Attempt.successful(None :: None :: None :: None :: None :: Some(n) :: HNil)
|
||||
},
|
||||
{
|
||||
case _ :: _ :: _ :: _ :: _ :: None :: HNil =>
|
||||
Attempt.failure(Err("expected a boolean value but found nothing"))
|
||||
case _ :: _ :: _ :: _ :: _ :: Some(n) :: HNil =>
|
||||
Attempt.successful(n :: HNil)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* `Codec` for reading a single `Int` from remaining stream data.
|
||||
* Multiple bit lengths can be processed from this reading.
|
||||
* @param icodec the `Codec[Int]` read by this method
|
||||
* @return a filled-out `allPattern` if successful
|
||||
*/
|
||||
def intCodec(icodec : Codec[Int]) : Codec[allPattern] = icodec.hlist.exmap[allPattern] (
|
||||
{
|
||||
case n :: HNil =>
|
||||
Attempt.successful(None :: Some(n) :: None :: None :: None :: None :: HNil)
|
||||
},
|
||||
{
|
||||
case _ :: None :: _ :: _ :: _ :: _ :: HNil =>
|
||||
Attempt.failure(Err("expected an integer value but found nothing"))
|
||||
case _ :: Some(n) :: _ :: _ :: _ :: _ :: HNil =>
|
||||
Attempt.successful(n :: HNil)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* `Codec` for reading a single `Long` from remaining stream data.
|
||||
* @return a filled-out `allPattern` if successful
|
||||
*/
|
||||
def longCodec : Codec[allPattern] = uint32L.hlist.exmap[allPattern] (
|
||||
{
|
||||
case n :: HNil =>
|
||||
Attempt.successful(None :: None :: None :: Some(n) :: None :: None :: HNil)
|
||||
},
|
||||
{
|
||||
case _ :: _ :: _ :: None :: _ :: _ :: HNil =>
|
||||
Attempt.failure(Err("expected a long value but found nothing"))
|
||||
case _ :: _ :: _ :: Some(n) :: _ :: _ :: HNil =>
|
||||
Attempt.successful(n :: HNil)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* `Codec` for reading a `String` from remaining stream data.
|
||||
* All `String`s processed by this reading are wide character and are padded by six.
|
||||
* @return a filled-out `allPattern` if successful
|
||||
*/
|
||||
def stringCodec : Codec[allPattern] = PacketHelpers.encodedWideStringAligned(6).hlist.exmap[allPattern] (
|
||||
{
|
||||
case a :: HNil =>
|
||||
Attempt.successful(Some(a) :: None :: None :: None :: None :: None :: HNil)
|
||||
},
|
||||
{
|
||||
case None:: _ :: _ :: _ :: _ :: _ :: HNil =>
|
||||
Attempt.failure(Err("expected a string value but found nothing"))
|
||||
case Some(a) :: _ :: _ :: _ :: _ :: _ :: HNil =>
|
||||
Attempt.successful(a :: HNil)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* `Codec` for reading an `Int` followed by a `Long` from remaining stream data.
|
||||
* Multiple bit lengths can be processed for the `Long1` value from this reading.
|
||||
* @param lcodec the `Codec[Long]` read by this method
|
||||
* @return a filled-out `allPattern` if successful
|
||||
*/
|
||||
def intLongCodec(lcodec : Codec[Long]) : Codec[allPattern] = (
|
||||
uint4L ::
|
||||
lcodec
|
||||
).exmap[allPattern] (
|
||||
{
|
||||
case a :: b :: HNil =>
|
||||
Attempt.successful(None :: Some(a) :: None :: Some(b) :: None :: None :: HNil)
|
||||
},
|
||||
{
|
||||
case _ :: None :: _ :: _ :: _ :: _ :: HNil =>
|
||||
Attempt.failure(Err("expected a integer value but found nothing"))
|
||||
case _ :: _ :: _ :: None :: _ :: _ :: HNil =>
|
||||
Attempt.failure(Err("expected a long value but found nothing"))
|
||||
case _ :: Some(a) :: _ :: Some(b) :: _ :: _ :: HNil =>
|
||||
Attempt.successful(a :: b :: HNil)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* `Codec` for reading an `Int` followed by a `String` from remaining stream data.
|
||||
* All `String`s processed by this reading are wide character and are padded by two.
|
||||
* @return a filled-out `allPattern` if successful
|
||||
*/
|
||||
def intStringCodec : Codec[allPattern] = (
|
||||
uint4L ::
|
||||
PacketHelpers.encodedWideStringAligned(2)
|
||||
).exmap[allPattern] (
|
||||
{
|
||||
case a :: b :: HNil =>
|
||||
Attempt.successful(Some(b) :: Some(a) :: None :: None :: None :: None :: HNil)
|
||||
},
|
||||
{
|
||||
case None:: _ :: _ :: _ :: _ :: _ :: HNil =>
|
||||
Attempt.failure(Err("expected a string value but found nothing"))
|
||||
case _ :: None :: _ :: _ :: _ :: _ :: HNil =>
|
||||
Attempt.failure(Err("expected an integer value but found nothing"))
|
||||
case Some(b) :: Some(a) :: _ :: _ :: _ :: _ :: HNil =>
|
||||
Attempt.successful(a :: b :: HNil)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* `Codec` for reading two `Long`s from remaining stream data.
|
||||
* @return a filled-out `allPattern` if successful
|
||||
*/
|
||||
def longLongCodec : Codec[allPattern] = (
|
||||
ulongL(46) ::
|
||||
uint32L
|
||||
).exmap[allPattern] (
|
||||
{
|
||||
case a :: b :: HNil =>
|
||||
Attempt.successful(None :: None :: None :: Some(a) :: Some(b) :: None :: HNil)
|
||||
},
|
||||
{
|
||||
case (_ :: _ :: _ :: None :: _ :: _ :: HNil) | (_ :: _ :: _ :: _ :: None :: _ :: HNil) =>
|
||||
Attempt.failure(Err("expected two long values but found one"))
|
||||
case _ :: _ :: _ :: Some(a) :: Some(b) :: _ :: HNil =>
|
||||
Attempt.successful(a :: b :: HNil)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* `Codec` for reading a `String`, a `Long`, and two `Int`s from remaining stream data.
|
||||
* All `String`s processed by this reading are wide character and are padded by six.
|
||||
* @return a filled-out `allPattern` if successful
|
||||
*/
|
||||
def complexCodec : Codec[allPattern] = (
|
||||
PacketHelpers.encodedWideStringAligned(6) ::
|
||||
ulongL(46) ::
|
||||
uint16L ::
|
||||
uintL(3)
|
||||
).exmap[allPattern] (
|
||||
{
|
||||
case a :: b :: c :: d :: HNil =>
|
||||
Attempt.successful(Some(a) :: Some(c) :: Some(d) :: Some(b) :: None :: None :: HNil)
|
||||
},
|
||||
{
|
||||
case None:: _ :: _ :: _ :: _ :: _ :: HNil =>
|
||||
Attempt.failure(Err("expected a string value but found nothing"))
|
||||
case _ :: _ :: _ :: None :: _ :: _ :: HNil =>
|
||||
Attempt.failure(Err("expected a long value but found nothing"))
|
||||
case (_ :: None :: _ :: _ :: _ :: _ :: HNil) | (_ :: _ :: None :: _ :: _ :: _ :: HNil) =>
|
||||
Attempt.failure(Err("expected two integer values but found one"))
|
||||
case Some(a) :: Some(c) :: Some(d) :: Some(b) :: _ :: _ :: HNil =>
|
||||
Attempt.successful(a :: b :: c :: d :: HNil)
|
||||
}
|
||||
)
|
||||
|
||||
import scala.annotation.switch
|
||||
|
||||
/**
|
||||
* Select the `Codec` to translate bit data in this packet with an `allPattern` format.
|
||||
* @param action the purpose of this packet;
|
||||
* also decides the content of the parameter fields
|
||||
* @return an `allPattern` `Codec` that parses the appropriate data
|
||||
*/
|
||||
def selectCodec(action : Int) : Codec[allPattern] = (action : @switch) match {
|
||||
case 3 | 8 | 26 | 35 | 41 => //TODO double check these
|
||||
noneCodec
|
||||
|
||||
case 28 | 29 | 30 | 31 =>
|
||||
boolCodec
|
||||
|
||||
case 33 =>
|
||||
intCodec(uintL(3))
|
||||
case 10 | 11 | 21 | 22 | 40 =>
|
||||
intCodec(uint4L)
|
||||
case 20 =>
|
||||
intCodec(uint16L)
|
||||
|
||||
case 13 | 14 | 15 | 37 =>
|
||||
longCodec
|
||||
|
||||
case 7 | 19 =>
|
||||
stringCodec
|
||||
|
||||
case 12 | 38 =>
|
||||
intLongCodec(uint32L)
|
||||
case 25 =>
|
||||
intLongCodec(ulongL(46))
|
||||
|
||||
case 23 | 24 =>
|
||||
intStringCodec
|
||||
|
||||
case 36 =>
|
||||
longLongCodec
|
||||
|
||||
case 34 =>
|
||||
complexCodec
|
||||
|
||||
case _ =>
|
||||
//TODO for debugging purposes only; normal failure condition below
|
||||
bits.hlist.exmap[allPattern] (
|
||||
{
|
||||
case x :: HNil =>
|
||||
org.log4s.getLogger.warn(s"can not match a codec pattern for decoding $action")
|
||||
Attempt.successful(Some(x.toString) :: None :: None :: None :: None :: None :: HNil)
|
||||
},
|
||||
{
|
||||
case Some(x) :: None :: None :: None :: None :: None :: HNil =>
|
||||
org.log4s.getLogger.warn(s"can not match a codec pattern for encoding $action")
|
||||
Attempt.successful(scodec.bits.BitVector.fromValidBin(x) :: HNil)
|
||||
}
|
||||
)
|
||||
// ignore(0).exmap[allPattern] (
|
||||
// {
|
||||
// case () =>
|
||||
// Attempt.failure(Err(s"can not match a codec pattern for decoding $action"))
|
||||
// },
|
||||
// {
|
||||
// case _ :: _ :: _ :: _ :: _ :: _ :: HNil =>
|
||||
// Attempt.failure(Err(s"can not match a codec pattern for encoding $action"))
|
||||
// }
|
||||
// )
|
||||
def selectFromActionCode(code : Int) : Codec[SquadAction] = {
|
||||
import SquadAction.Codecs._
|
||||
import scala.annotation.switch
|
||||
((code : @switch) match {
|
||||
case 0 => displaySquadCodec
|
||||
case 1 => squadMemberInitializationIssueCodec
|
||||
case 2 => unknownCodec(action = 2)
|
||||
case 3 => saveSquadFavoriteCodec
|
||||
case 4 => loadSquadFavoriteCodec
|
||||
case 5 => deleteSquadFavoriteCodec
|
||||
case 6 => unknownCodec(action = 6)
|
||||
case 7 => listSquadFavoriteCodec
|
||||
case 8 => requestListSquadCodec
|
||||
case 9 => stopListSquadCodec
|
||||
case 10 => selectRoleForYourselfCodec
|
||||
case 11 => unknownCodec(action = 11)
|
||||
case 12 => unknownCodec(action = 12)
|
||||
case 13 => unknownCodec(action = 13)
|
||||
case 14 => unknownCodec(action = 14)
|
||||
case 15 => cancelSelectRoleForYourselfCodec
|
||||
case 16 => associateWithSquadCodec
|
||||
case 17 => setListSquadCodec
|
||||
case 18 => unknownCodec(action = 18)
|
||||
case 19 => changeSquadPurposeCodec
|
||||
case 20 => changeSquadZoneCodec
|
||||
case 21 => closeSquadMemberPositionCodec
|
||||
case 22 => addSquadMemberPositionCodec
|
||||
case 23 => changeSquadMemberRequirementsRoleCodec
|
||||
case 24 => changeSquadMemberRequirementsDetailedOrdersCodec
|
||||
case 25 => changeSquadMemberRequirementsCertificationsCodec
|
||||
case 26 => resetAllCodec
|
||||
//case 27 => ?
|
||||
case 28 => autoApproveInvitationRequestsCodec
|
||||
case 29 => unknownCodec(action = 29)
|
||||
case 30 => unknownCodec(action = 30)
|
||||
case 31 => locationFollowsSquadLeadCodec
|
||||
case 32 => unknownCodec(action = 32)
|
||||
case 33 => unknownCodec(action = 33)
|
||||
case 34 => searchForSquadsWithParticularRoleCodec
|
||||
case 35 => cancelSquadSearchCodec
|
||||
case 36 => unknownCodec(action = 36)
|
||||
case 37 => unknownCodec(action = 37)
|
||||
case 38 => assignSquadMemberToRoleCodec
|
||||
case 39 => noSquadSearchResultsCodec
|
||||
case 40 => findLfsSoldiersForRoleCodec
|
||||
case 41 => cancelFindCodec
|
||||
case 42 => unknownCodec(action = 42)
|
||||
case 43 => unknownCodec(action = 43)
|
||||
case _ => failureCodec(code)
|
||||
}).asInstanceOf[Codec[SquadAction]]
|
||||
}
|
||||
|
||||
implicit val codec : Codec[SquadDefinitionActionMessage] = (
|
||||
("action" | uintL(6)) >>:~ { action =>
|
||||
("unk1" | uint16L) ::
|
||||
("unk2" | uint4L) ::
|
||||
selectCodec(action)
|
||||
uintL(6) >>:~ { code =>
|
||||
("squad_guid" | PlanetSideGUID.codec) ::
|
||||
("line" | uint4L) ::
|
||||
("action" | selectFromActionCode(code))
|
||||
}
|
||||
).as[SquadDefinitionActionMessage]
|
||||
).xmap[SquadDefinitionActionMessage] (
|
||||
{
|
||||
case _ :: guid :: line :: action :: HNil =>
|
||||
SquadDefinitionActionMessage(guid, line, action)
|
||||
},
|
||||
{
|
||||
case SquadDefinitionActionMessage(guid, line, action) =>
|
||||
action.code :: guid :: line :: action :: HNil
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
("change" specifically indicates the perspective is from the SL; "update" indicates squad members other than the oen who made the change
|
||||
("[#]" indicates the mode is detected but not properly parsed; the length of the combined fields may follow
|
||||
|
||||
[0] - clicking on a squad listed in the "Find Squad" tab / cancel squad search (6 bits/pad?)
|
||||
[2] - ? (6 bits/pad?)
|
||||
[3] - save squad favorite (6 bits/pad?)
|
||||
[4] - load a squad definition favorite (6 bits/pad?)
|
||||
[6] - ? (6 bits/pad?)
|
||||
7 - ?
|
||||
[8] - list squad (6 bits/pad?)
|
||||
10 - select this role for yourself
|
||||
11 - ?
|
||||
12 - ?
|
||||
13 - ?
|
||||
14 - ?
|
||||
15 - ?
|
||||
[16] - ? (6 bits/pad?)
|
||||
[17] - ? (6 bits/pad?)
|
||||
[18] - ? (6 bits/pad?)
|
||||
19 - change purpose
|
||||
20 - change zone
|
||||
21 - change/close squad member position
|
||||
22 - change/add squad member position
|
||||
23 - change squad member req role
|
||||
24 - change squad member req detailed orders
|
||||
25 - change squad member req weapons
|
||||
[26] - reset all (6 bits/pad?)
|
||||
28 - auto-approve requests for invitation
|
||||
29 -
|
||||
30 -
|
||||
31 - location follows squad lead
|
||||
[32] - ? (6 bits/pad?)
|
||||
33 -
|
||||
34 - search for squads with a particular role
|
||||
36 -
|
||||
37 -
|
||||
38 -
|
||||
[39] - ? (?)
|
||||
40 - find LFS soldiers that meet the requirements for this role
|
||||
[41] - cancel search for LFS soldiers (6 bits)
|
||||
[42] - ? (6 bits/pad?)
|
||||
[43] - ? (6 bits/pad?)
|
||||
*/
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||
import scodec.Codec
|
||||
import scodec.codecs._
|
||||
|
||||
/**
|
||||
* A message for communicating squad invitation.
|
||||
* When received by a client, the event message "You have invited `name` to join your squad" is produced
|
||||
* and a `SquadMembershipRequest` packet of type `Invite`
|
||||
* using `char_id` as the optional unique character identifier field is dispatched to the server.
|
||||
* The message is equivalent to a dispatched packet of type `SquadMembershipResponse`
|
||||
* with an `Invite` event with the referral field set to `true`.
|
||||
* @see `SquadMembershipResponse`
|
||||
* @param squad_guid the squad's GUID
|
||||
* @param slot a potentially valid slot index;
|
||||
* 0-9; higher numbers produce no response
|
||||
* @param char_id the unique character identifier
|
||||
* @param name the character's name;
|
||||
* frequently, though that does not produce a coherent message,
|
||||
* the avatar's own name is supplied in the event message instead of the name of another player
|
||||
*/
|
||||
final case class SquadInvitationRequestMessage(squad_guid : PlanetSideGUID,
|
||||
slot : Int,
|
||||
char_id : Long,
|
||||
name : String)
|
||||
extends PlanetSideGamePacket {
|
||||
type Packet = SquadInvitationRequestMessage
|
||||
def opcode = GamePacketOpcode.SquadInvitationRequestMessage
|
||||
def encode = SquadInvitationRequestMessage.encode(this)
|
||||
}
|
||||
|
||||
object SquadInvitationRequestMessage extends Marshallable[SquadInvitationRequestMessage] {
|
||||
implicit val codec : Codec[SquadInvitationRequestMessage] = (
|
||||
("squad_guid" | PlanetSideGUID.codec) ::
|
||||
("slot" | uint4) ::
|
||||
("char_id" | uint32L) ::
|
||||
("name" | PacketHelpers.encodedWideStringAligned(adjustment = 4))
|
||||
).as[SquadInvitationRequestMessage]
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright (c) 2019 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}
|
||||
|
||||
object MemberEvent extends Enumeration {
|
||||
type Type = Value
|
||||
|
||||
val
|
||||
Add,
|
||||
Remove,
|
||||
Promote,
|
||||
UpdateZone,
|
||||
Unknown4
|
||||
= Value
|
||||
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(enum = this, uint(bits = 3))
|
||||
}
|
||||
|
||||
final case class SquadMemberEvent(action : MemberEvent.Value,
|
||||
unk2 : Int,
|
||||
char_id : Long,
|
||||
position : Int,
|
||||
player_name : Option[String],
|
||||
zone_number : Option[Int],
|
||||
unk7 : Option[Long])
|
||||
extends PlanetSideGamePacket {
|
||||
type Packet = SquadMemberEvent
|
||||
def opcode = GamePacketOpcode.SquadMemberEvent
|
||||
def encode = SquadMemberEvent.encode(this)
|
||||
}
|
||||
|
||||
object SquadMemberEvent extends Marshallable[SquadMemberEvent] {
|
||||
def apply(action : MemberEvent.Value, unk2 : Int, char_id : Long, position : Int) : SquadMemberEvent =
|
||||
SquadMemberEvent(action, unk2, char_id, position, None, None, None)
|
||||
|
||||
def Add(unk2 : Int, char_id : Long, position : Int, player_name : String, zone_number : Int, unk7 : Long) : SquadMemberEvent =
|
||||
SquadMemberEvent(MemberEvent.Add, unk2, char_id, position, Some(player_name), Some(zone_number), Some(unk7))
|
||||
|
||||
def Remove(unk2 : Int, char_id : Long, position : Int) : SquadMemberEvent =
|
||||
SquadMemberEvent(MemberEvent.Remove, unk2, char_id, position, None, None, None)
|
||||
|
||||
def Promote(unk2 : Int, char_id : Long) : SquadMemberEvent =
|
||||
SquadMemberEvent(MemberEvent.Promote, unk2, char_id, 0, None, None, None)
|
||||
|
||||
def UpdateZone(unk2 : Int, char_id : Long, position : Int, zone_number : Int) : SquadMemberEvent =
|
||||
SquadMemberEvent(MemberEvent.UpdateZone, unk2, char_id, position, None, Some(zone_number), None)
|
||||
|
||||
def Unknown4(unk2 : Int, char_id : Long, position : Int, unk7 : Long) : SquadMemberEvent =
|
||||
SquadMemberEvent(MemberEvent.Unknown4, unk2, char_id, position, None, None, Some(unk7))
|
||||
|
||||
implicit val codec : Codec[SquadMemberEvent] = (
|
||||
("action" | MemberEvent.codec) >>:~ { action =>
|
||||
("unk2" | uint16L) ::
|
||||
("char_id" | uint32L) ::
|
||||
("position" | uint4) ::
|
||||
conditional(action == MemberEvent.Add, "player_name" | PacketHelpers.encodedWideStringAligned(1)) ::
|
||||
conditional(action == MemberEvent.Add || action == MemberEvent.UpdateZone, "zone_number" | uint16L) ::
|
||||
conditional(action == MemberEvent.Add || action == MemberEvent.Unknown4, "unk7" | uint32L)
|
||||
}).exmap[SquadMemberEvent] (
|
||||
{
|
||||
case action :: unk2 :: char_id :: member_position :: player_name :: zone_number :: unk7 :: HNil =>
|
||||
Attempt.Successful(SquadMemberEvent(action, unk2, char_id, member_position, player_name, zone_number, unk7))
|
||||
},
|
||||
{
|
||||
case SquadMemberEvent(MemberEvent.Add, unk2, char_id, member_position, Some(player_name), Some(zone_number), Some(unk7)) =>
|
||||
Attempt.Successful(MemberEvent.Add :: unk2 :: char_id :: member_position :: Some(player_name) :: Some(zone_number) :: Some(unk7) :: HNil)
|
||||
case SquadMemberEvent(MemberEvent.UpdateZone, unk2, char_id, member_position, None, Some(zone_number), None) =>
|
||||
Attempt.Successful(MemberEvent.UpdateZone :: unk2 :: char_id :: member_position :: None :: Some(zone_number) :: None :: HNil)
|
||||
case SquadMemberEvent(MemberEvent.Unknown4, unk2, char_id, member_position, None, None, Some(unk7)) =>
|
||||
Attempt.Successful(MemberEvent.Unknown4 :: unk2 :: char_id :: member_position :: None :: None :: Some(unk7) :: HNil)
|
||||
case SquadMemberEvent(action, unk2, char_id, member_position, None, None, None) =>
|
||||
Attempt.Successful(action :: unk2 :: char_id :: member_position :: None :: None :: None :: HNil)
|
||||
case data =>
|
||||
Attempt.Failure(Err(s"SquadMemberEvent can not encode with this pattern - $data"))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||
import net.psforever.types.SquadRequestType
|
||||
import scodec.Codec
|
||||
import scodec.codecs._
|
||||
|
||||
/**
|
||||
* Dispatched by the client as manipulation protocol for squad and platoon members.
|
||||
* Answerable by a `SquadMembershipResponse` packet.
|
||||
* @param request_type the purpose of the request
|
||||
* @param char_id a squad member unique identifier;
|
||||
* usually, the player being addresses by thie packet
|
||||
* @param unk3 na
|
||||
* @param player_name name of the player being affected, if applicable
|
||||
* @param unk5 na
|
||||
*/
|
||||
final case class SquadMembershipRequest(request_type : SquadRequestType.Value,
|
||||
char_id : Long,
|
||||
unk3 : Option[Long],
|
||||
player_name : String,
|
||||
unk5 : Option[Option[String]])
|
||||
extends PlanetSideGamePacket {
|
||||
request_type match {
|
||||
case SquadRequestType.Accept | SquadRequestType.Reject | SquadRequestType.Disband |
|
||||
SquadRequestType.PlatoonAccept | SquadRequestType.PlatoonReject | SquadRequestType.PlatoonDisband =>
|
||||
assert(unk3.isEmpty, s"a $request_type request requires the unk3 field be undefined")
|
||||
case _ =>
|
||||
assert(unk3.nonEmpty, s"a $request_type request requires the unk3 field be defined")
|
||||
}
|
||||
if(request_type == SquadRequestType.Invite) {
|
||||
assert(unk5.nonEmpty, "an Invite request requires the unk5 field be defined")
|
||||
}
|
||||
|
||||
type Packet = SquadMembershipRequest
|
||||
def opcode = GamePacketOpcode.SquadMembershipRequest
|
||||
def encode = SquadMembershipRequest.encode(this)
|
||||
}
|
||||
|
||||
object SquadMembershipRequest extends Marshallable[SquadMembershipRequest] {
|
||||
implicit val codec : Codec[SquadMembershipRequest] = (
|
||||
("request_type" | SquadRequestType.codec) >>:~ { request_type =>
|
||||
("unk2" | uint32L) ::
|
||||
conditional(request_type != SquadRequestType.Accept &&
|
||||
request_type != SquadRequestType.Reject &&
|
||||
request_type != SquadRequestType.Disband &&
|
||||
request_type != SquadRequestType.PlatoonAccept &&
|
||||
request_type != SquadRequestType.PlatoonReject &&
|
||||
request_type != SquadRequestType.PlatoonDisband, "unk3" | uint32L) ::
|
||||
(("player_name" | PacketHelpers.encodedWideStringAligned(4)) >>:~ { pname =>
|
||||
conditional(request_type == SquadRequestType.Invite,
|
||||
"unk5" | optional(bool, PacketHelpers.encodedWideStringAligned({if(pname.length == 0) 3 else 7}))
|
||||
).hlist
|
||||
})
|
||||
}).as[SquadMembershipRequest]
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||
import net.psforever.types.SquadResponseType
|
||||
import scodec.Codec
|
||||
import scodec.codecs._
|
||||
|
||||
/**
|
||||
* Dispatched by the server as message generation protocol for squad and platoon members.
|
||||
* Prompted by and answers for a `SquadMembershipRequest` packet.
|
||||
* @param request_type the purpose of the request
|
||||
* @param unk1 na
|
||||
* @param unk2 na
|
||||
* @param char_id a squad member unique identifier;
|
||||
* usually, the player being addresses by thie packet
|
||||
* @param other_id another squad member's unique identifier;
|
||||
* may be the same as `char_id`
|
||||
* @param player_name name of the player being affected, if applicable
|
||||
* @param unk5 adjusts the nature of the request-type response based on the message recipient
|
||||
* @param unk6 na;
|
||||
* the internal field, the `Option[String]`, never seems to be set
|
||||
* <br>
|
||||
* `request_type` (enum value) / `unk5` state (`false`/`true`)<br>
|
||||
* ----------------------------------------<br>
|
||||
* - `Invite` (0)<br>
|
||||
* false => [PROMPT] "`player_name` has invited you into a squad." [YES/NO]<br>
|
||||
* true => "You have invited `player_name` to join your squad."<br>
|
||||
* - `Unk01` (1)<br>
|
||||
* false => n/a<br>
|
||||
* true => n/a<br>
|
||||
* - `Accept` (2)<br>
|
||||
* false => "`player_name` has accepted your invitation to join into your squad.<br>
|
||||
* "You have formed a squad and are now that squad's commander." (if first time)<br>
|
||||
* true => "You have accepted an invitation to join a squad."<br>
|
||||
* "You have successfully joined a squad for the first time." (if first time)<br>
|
||||
* - `Reject` (3)<br>
|
||||
* false => "`player_name` does not want to join your squad at this time."<br>
|
||||
* true => "You have declined an invitation to join a squad."<br>
|
||||
* - `Cancel` (4)<br>
|
||||
* false => "`player_name` has withdrawn his invitation."<br>
|
||||
* true => "You have canceled your invitation to `player_name`."<br>
|
||||
* - `Leave` (5)<br>
|
||||
* false => "The Squad Leader has kicked you out of the squad."<br>
|
||||
* true => "You have kicked `player_name` out of the squad."<br>
|
||||
* - `Disband` (6)<br>
|
||||
* false => "The squad has been disbanded."<br>
|
||||
* true => "You have disbanded the squad."<br>
|
||||
* - `PlatoonInvite` (7)<br>
|
||||
* false => [PROMPT] "`player_name` has invited you into a platoon." [YES/NO]<br>
|
||||
* true => "You have invited `player_name`'s squad to join your platoon."<br>
|
||||
* - `PlatoonAccept` (8)
|
||||
* false => "`player_name` has accepted your invitation to join into your platoon.<br>
|
||||
* "You have formed a platoon and are now that platoon commander." (if first time)<br>
|
||||
* true => "You have accepted an invitation to join a platoon."<br>
|
||||
* "You have successfully joined a platoon for the first time." (if first time)<br>
|
||||
* - `PlatoonReject` (9)<br>
|
||||
* false => "`player_name` does not want to join your platoon at this time."<br>
|
||||
* true => "You have declined an invitation to join a platoon."<br>
|
||||
* - `PlatoonCancel` (10)<br>
|
||||
* false => "`player_name` has withdrawn his invitation."<br>
|
||||
* true => "You have declined your invitation to `player_name`." (nonsense?)<br>
|
||||
* - `PlatoonLeave` (11)<br>
|
||||
* false => "The Platoon Leader has kicked you out of the platoon."<br>
|
||||
* true => "You have kicked `player_name`'s squad out of the platoon."<br>
|
||||
* - `PlatoonDisband` (12)<br>
|
||||
* false => "The platoon has been disbanded."<br>
|
||||
* true => "You have disbanded the platoon."
|
||||
*/
|
||||
final case class SquadMembershipResponse(request_type : SquadResponseType.Value,
|
||||
unk1 : Int,
|
||||
unk2 : Int,
|
||||
char_id : Long,
|
||||
other_id : Option[Long],
|
||||
player_name : String,
|
||||
unk5 : Boolean,
|
||||
unk6 : Option[Option[String]])
|
||||
extends PlanetSideGamePacket {
|
||||
type Packet = SquadMembershipResponse
|
||||
def opcode = GamePacketOpcode.SquadMembershipResponse
|
||||
def encode = SquadMembershipResponse.encode(this)
|
||||
}
|
||||
|
||||
object SquadMembershipResponse extends Marshallable[SquadMembershipResponse] {
|
||||
implicit val codec : Codec[SquadMembershipResponse] = (
|
||||
"request_type" | SquadResponseType.codec >>:~ { d =>
|
||||
("unk1" | uint(5)) ::
|
||||
("unk2" | uint2) ::
|
||||
("char_id" | uint32L) ::
|
||||
("other_id" | conditional(d != SquadResponseType.Disband && d != SquadResponseType.PlatoonDisband, uint32L)) ::
|
||||
("player_name" | PacketHelpers.encodedWideStringAligned(5)) ::
|
||||
("unk5" | bool) ::
|
||||
conditional(d != SquadResponseType.Invite, optional(bool, "unk6" | PacketHelpers.encodedWideStringAligned(6)))
|
||||
}
|
||||
).as[SquadMembershipResponse]
|
||||
}
|
||||
101
common/src/main/scala/net/psforever/packet/game/SquadState.scala
Normal file
101
common/src/main/scala/net/psforever/packet/game/SquadState.scala
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
|
||||
import net.psforever.types.Vector3
|
||||
import scodec.{Attempt, Codec, Err}
|
||||
import scodec.codecs._
|
||||
import shapeless.{::, HNil}
|
||||
|
||||
/**
|
||||
* Information about a specific squad member.
|
||||
* @param char_id the character's unique identifier
|
||||
* @param health the character's health value percentage, divided into 64 units
|
||||
* @param armor the character's armor value percentage, divided into 64 units
|
||||
* @param pos the world coordinates of the character
|
||||
* @param unk4 na;
|
||||
* usually, 2
|
||||
* @param unk5 na;
|
||||
* usually, 2
|
||||
* @param unk6 na;
|
||||
* usually, false
|
||||
* @param unk7 na
|
||||
* @param unk8 na;
|
||||
* if defined, will be defined with unk9
|
||||
* @param unk9 na;
|
||||
* if defined, will be defined with unk8
|
||||
*/
|
||||
final case class SquadStateInfo(char_id : Long,
|
||||
health : Int,
|
||||
armor : Int,
|
||||
pos : Vector3,
|
||||
unk4 : Int,
|
||||
unk5 : Int,
|
||||
unk6 : Boolean,
|
||||
unk7 : Int,
|
||||
unk8 : Option[Int],
|
||||
unk9 : Option[Boolean])
|
||||
|
||||
/**
|
||||
* Dispatched by the server to update a squad member's representative icons on the continental maps and the interstellar map.<br>
|
||||
* <br>
|
||||
* This packet must be preceded by the correct protocol
|
||||
* to assign any character who is defined by `char_id` in `info_list`
|
||||
* as a member of this client's player's assigned squad by means of associating that said `char_id`.
|
||||
* The said preceding protocol also assigns the player's current zone (continent) and their ordinal position in the squad.
|
||||
* @see `SquadMemberEvent`
|
||||
* @param guid the squad's unique identifier;
|
||||
* must be consistent per packet on a given client;
|
||||
* does not have to be the global uid of the squad as according to the server
|
||||
* @param info_list information about the members in this squad who will be updated
|
||||
*/
|
||||
final case class SquadState(guid : PlanetSideGUID,
|
||||
info_list : List[SquadStateInfo])
|
||||
extends PlanetSideGamePacket {
|
||||
type Packet = SquadState
|
||||
def opcode = GamePacketOpcode.SquadState
|
||||
def encode = SquadState.encode(this)
|
||||
}
|
||||
|
||||
object SquadStateInfo {
|
||||
def apply(unk1 : Long, unk2 : Int, unk3 : Int, pos : Vector3, unk4 : Int, unk5 : Int, unk6 : Boolean, unk7 : Int) : SquadStateInfo =
|
||||
SquadStateInfo(unk1, unk2, unk3, pos, unk4, unk5, unk6, unk7, None, None)
|
||||
|
||||
def apply(unk1 : Long, unk2 : Int, unk3 : Int, pos : Vector3, unk4 : Int, unk5 : Int, unk6 : Boolean, unk7 : Int, unk8 : Int, unk9 : Boolean) : SquadStateInfo =
|
||||
SquadStateInfo(unk1, unk2, unk3, pos, unk4, unk5, unk6, unk7, Some(unk8), Some(unk9))
|
||||
}
|
||||
|
||||
object SquadState extends Marshallable[SquadState] {
|
||||
private val info_codec : Codec[SquadStateInfo] = (
|
||||
("char_id" | uint32L) ::
|
||||
("health" | uint(7)) ::
|
||||
("armor" | uint(7)) ::
|
||||
("pos" | Vector3.codec_pos) ::
|
||||
("unk4" | uint2) ::
|
||||
("unk5" | uint2) ::
|
||||
("unk6" | bool) ::
|
||||
("unk7" | uint16L) ::
|
||||
(bool >>:~ { out =>
|
||||
conditional(out, "unk8" | uint16L) ::
|
||||
conditional(out, "unk9" | bool)
|
||||
})
|
||||
).exmap[SquadStateInfo] (
|
||||
{
|
||||
case char_id :: health :: armor :: pos :: u4 :: u5 :: u6 :: u7 :: _ :: u8 :: u9 :: HNil =>
|
||||
Attempt.Successful(SquadStateInfo(char_id, health, armor, pos, u4, u5, u6, u7, u8, u9))
|
||||
},
|
||||
{
|
||||
case SquadStateInfo(char_id, health, armor, pos, u4, u5, u6, u7, Some(u8), Some(u9)) =>
|
||||
Attempt.Successful(char_id :: health :: armor :: pos :: u4 :: u5 :: u6 :: u7 :: true :: Some(u8) :: Some(u9) :: HNil)
|
||||
case SquadStateInfo(char_id, health, armor, pos, u4, u5, u6, u7, None, None) =>
|
||||
Attempt.Successful(char_id :: health :: armor :: pos :: u4 :: u5 :: u6 :: u7 :: false :: None :: None :: HNil)
|
||||
case data @ (SquadStateInfo(_, _, _, _, _, _, _, _, Some(_), None) | SquadStateInfo(_, _, _, _, _, _, _, _, None, Some(_))) =>
|
||||
Attempt.Failure(Err(s"SquadStateInfo requires both unk8 and unk9 to be either defined or undefined at the same time - $data"))
|
||||
}
|
||||
)
|
||||
|
||||
implicit val codec : Codec[SquadState] = (
|
||||
("guid" | PlanetSideGUID.codec) ::
|
||||
("info_list" | listOfN(uint4, info_codec))
|
||||
).as[SquadState]
|
||||
}
|
||||
|
|
@ -1,22 +1,22 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
|
||||
import net.psforever.types.Vector3
|
||||
import net.psforever.types.{SquadWaypoints, Vector3}
|
||||
import scodec.{Attempt, Codec, Err}
|
||||
import scodec.codecs._
|
||||
import shapeless.{::, HNil}
|
||||
|
||||
final case class WaypointEvent(unk1 : Int,
|
||||
final case class WaypointEvent(zone_number : Int,
|
||||
pos : Vector3,
|
||||
unk2 : Int)
|
||||
unk : Int)
|
||||
|
||||
final case class SquadWaypointEvent(unk1 : Int,
|
||||
unk2 : Int,
|
||||
unk3 : Long,
|
||||
unk4 : Int,
|
||||
final case class SquadWaypointEvent(event_type : WaypointEventAction.Value,
|
||||
unk : Int,
|
||||
char_id : Long,
|
||||
waypoint_type : SquadWaypoints.Value,
|
||||
unk5 : Option[Long],
|
||||
unk6 : Option[WaypointEvent])
|
||||
waypoint_info : Option[WaypointEvent])
|
||||
extends PlanetSideGamePacket {
|
||||
type Packet = SquadWaypointEvent
|
||||
def opcode = GamePacketOpcode.SquadWaypointEvent
|
||||
|
|
@ -24,55 +24,55 @@ final case class SquadWaypointEvent(unk1 : Int,
|
|||
}
|
||||
|
||||
object SquadWaypointEvent extends Marshallable[SquadWaypointEvent] {
|
||||
def apply(unk1 : Int, unk2 : Int, unk3 : Long, unk4 : Int, unk_a : Long) : SquadWaypointEvent =
|
||||
SquadWaypointEvent(unk1, unk2, unk3, unk4, Some(unk_a), None)
|
||||
def Add(unk : Int, char_id : Long, waypoint_type : SquadWaypoints.Value, waypoint : WaypointEvent) : SquadWaypointEvent =
|
||||
SquadWaypointEvent(WaypointEventAction.Add, unk, char_id, waypoint_type, None, Some(waypoint))
|
||||
|
||||
def apply(unk1 : Int, unk2 : Int, unk3 : Long, unk4 : Int, unk_a : Int, pos : Vector3, unk_b : Int) : SquadWaypointEvent =
|
||||
SquadWaypointEvent(unk1, unk2, unk3, unk4, None, Some(WaypointEvent(unk_a, pos, unk_b)))
|
||||
def Unknown1(unk : Int, char_id : Long, waypoint_type : SquadWaypoints.Value, unk_a : Long) : SquadWaypointEvent =
|
||||
SquadWaypointEvent(WaypointEventAction.Unknown1, unk, char_id, waypoint_type, Some(unk_a), None)
|
||||
|
||||
def apply(unk1 : Int, unk2 : Int, unk3 : Long, unk4 : Int) : SquadWaypointEvent =
|
||||
SquadWaypointEvent(unk1, unk2, unk3, unk4, None, None)
|
||||
def Remove(unk : Int, char_id : Long, waypoint_type : SquadWaypoints.Value) : SquadWaypointEvent =
|
||||
SquadWaypointEvent(WaypointEventAction.Remove, unk, char_id, waypoint_type, None, None)
|
||||
|
||||
private val waypoint_codec : Codec[WaypointEvent] = (
|
||||
("unk1" | uint16L) ::
|
||||
("zone_number" | uint16L) ::
|
||||
("pos" | Vector3.codec_pos) ::
|
||||
("unk2" | uint(3))
|
||||
("unk" | uint(3))
|
||||
).as[WaypointEvent]
|
||||
|
||||
implicit val codec : Codec[SquadWaypointEvent] = (
|
||||
("unk1" | uint2) >>:~ { unk1 =>
|
||||
("unk2" | uint16L) ::
|
||||
("unk3" | uint32L) ::
|
||||
("unk4" | uint8L) ::
|
||||
("unk5" | conditional(unk1 == 1, uint32L)) ::
|
||||
("unk6" | conditional(unk1 == 0, waypoint_codec))
|
||||
("event_type" | WaypointEventAction.codec) >>:~ { event_type =>
|
||||
("unk" | uint16L) ::
|
||||
("char_id" | uint32L) ::
|
||||
("waypoint_type" | SquadWaypoints.codec) ::
|
||||
("unk5" | conditional(event_type == WaypointEventAction.Unknown1, uint32L)) ::
|
||||
("waypoint_info" | conditional(event_type == WaypointEventAction.Add, waypoint_codec))
|
||||
}
|
||||
).exmap[SquadWaypointEvent] (
|
||||
{
|
||||
case 0 :: a :: b :: c :: None :: Some(d) :: HNil =>
|
||||
Attempt.Successful(SquadWaypointEvent(0, a, b, c, None, Some(d)))
|
||||
case WaypointEventAction.Add :: a :: char_id :: waypoint_type :: None :: Some(waypoint) :: HNil =>
|
||||
Attempt.Successful(SquadWaypointEvent(WaypointEventAction.Add, a, char_id, waypoint_type, None, Some(waypoint)))
|
||||
|
||||
case 1 :: a :: b :: c :: Some(d) :: None :: HNil =>
|
||||
Attempt.Successful(SquadWaypointEvent(1, a, b, c, Some(d), None))
|
||||
case WaypointEventAction.Unknown1 :: a :: char_id :: waypoint_type :: Some(d) :: None :: HNil =>
|
||||
Attempt.Successful(SquadWaypointEvent(WaypointEventAction.Unknown1, a, char_id, waypoint_type, Some(d), None))
|
||||
|
||||
case a :: b :: c :: d :: None :: None :: HNil =>
|
||||
Attempt.Successful(SquadWaypointEvent(a, b, c, d, None, None))
|
||||
case event_type :: b :: char_id :: waypoint_type :: None :: None :: HNil =>
|
||||
Attempt.Successful(SquadWaypointEvent(event_type, b, char_id, waypoint_type, None, None))
|
||||
|
||||
case n :: _ :: _ :: _ :: _ :: _ :: HNil =>
|
||||
Attempt.Failure(Err(s"unexpected format for unk1 - $n"))
|
||||
case data =>
|
||||
Attempt.Failure(Err(s"unexpected format for $data"))
|
||||
},
|
||||
{
|
||||
case SquadWaypointEvent(0, a, b, c, None, Some(d)) =>
|
||||
Attempt.Successful(0 :: a :: b :: c :: None :: Some(d) :: HNil)
|
||||
case SquadWaypointEvent(WaypointEventAction.Add, a, char_id, waypoint_type, None, Some(waypoint)) =>
|
||||
Attempt.Successful(WaypointEventAction.Add :: a :: char_id :: waypoint_type :: None :: Some(waypoint) :: HNil)
|
||||
|
||||
case SquadWaypointEvent(1, a, b, c, Some(d), None) =>
|
||||
Attempt.Successful(1 :: a :: b :: c :: Some(d) :: None :: HNil)
|
||||
case SquadWaypointEvent(WaypointEventAction.Unknown1, a, char_id, waypoint_type, Some(d), None) =>
|
||||
Attempt.Successful(WaypointEventAction.Unknown1 :: a :: char_id :: waypoint_type :: Some(d) :: None :: HNil)
|
||||
|
||||
case SquadWaypointEvent(a, b, c, d, None, None) =>
|
||||
Attempt.Successful(a :: b :: c :: d :: None :: None :: HNil)
|
||||
case SquadWaypointEvent(event_type, b, char_id, waypoint_type, None, None) =>
|
||||
Attempt.Successful(event_type :: b :: char_id :: waypoint_type :: None :: None :: HNil)
|
||||
|
||||
case SquadWaypointEvent(n, _, _, _, _, _) =>
|
||||
Attempt.Failure(Err(s"unexpected format for unk1 - $n"))
|
||||
case data =>
|
||||
Attempt.Failure(Err(s"unexpected format for $data"))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||
import net.psforever.types.{SquadWaypoints, Vector3}
|
||||
import scodec.{Attempt, Codec, Err}
|
||||
import scodec.codecs._
|
||||
import shapeless.{::, HNil}
|
||||
|
||||
/**
|
||||
* Actions that can be requested of the specific waypoint.
|
||||
*/
|
||||
object WaypointEventAction extends Enumeration {
|
||||
type Type = Value
|
||||
|
||||
val
|
||||
Add,
|
||||
Unknown1,
|
||||
Remove,
|
||||
Unknown3 //unconfirmed
|
||||
= Value
|
||||
|
||||
implicit val codec : Codec[WaypointEventAction.Value] = PacketHelpers.createEnumerationCodec(enum = this, uint2)
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param zone_number the zone
|
||||
* @param pos the continental map coordinate location of the waypoint;
|
||||
* the z-coordinate is almost always 0.0
|
||||
*/
|
||||
final case class WaypointInfo(zone_number : Int,
|
||||
pos : Vector3)
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param request_type the action to be performed
|
||||
* @param char_id the unique id of player setting the waypoint
|
||||
* @param waypoint_type the waypoint being updated;
|
||||
* 0-3 for the standard squad waypoints numbered "1-4";
|
||||
* 4 for the squad leader experience waypoint;
|
||||
* cycles through 0-3 continuously
|
||||
*
|
||||
* @param unk4 na
|
||||
* @param waypoint_info essential data about the waypoint
|
||||
*/
|
||||
final case class SquadWaypointRequest(request_type : WaypointEventAction.Value,
|
||||
char_id : Long,
|
||||
waypoint_type : SquadWaypoints.Value,
|
||||
unk4 : Option[Long],
|
||||
waypoint_info : Option[WaypointInfo])
|
||||
extends PlanetSideGamePacket {
|
||||
type Packet = SquadWaypointRequest
|
||||
def opcode = GamePacketOpcode.SquadWaypointRequest
|
||||
def encode = SquadWaypointRequest.encode(this)
|
||||
}
|
||||
|
||||
object SquadWaypointRequest extends Marshallable[SquadWaypointRequest] {
|
||||
def Add(char_id : Long, waypoint_type : SquadWaypoints.Value, waypoint : WaypointInfo) : SquadWaypointRequest =
|
||||
SquadWaypointRequest(WaypointEventAction.Add, char_id, waypoint_type, None, Some(waypoint))
|
||||
|
||||
def Unknown1(char_id : Long, waypoint_type : SquadWaypoints.Value, unk_a : Long) : SquadWaypointRequest =
|
||||
SquadWaypointRequest(WaypointEventAction.Unknown1, char_id, waypoint_type, Some(unk_a), None)
|
||||
|
||||
def Remove(char_id : Long, waypoint_type : SquadWaypoints.Value) : SquadWaypointRequest =
|
||||
SquadWaypointRequest(WaypointEventAction.Remove, char_id, waypoint_type, None, None)
|
||||
|
||||
private val waypoint_codec : Codec[WaypointInfo] = (
|
||||
("zone_number" | uint16L) ::
|
||||
("pos" | Vector3.codec_pos)
|
||||
).xmap[WaypointInfo] (
|
||||
{
|
||||
case zone_number :: pos :: HNil => WaypointInfo(zone_number, pos)
|
||||
},
|
||||
{
|
||||
case WaypointInfo(zone_number, pos) => zone_number :: pos.xy :: HNil
|
||||
}
|
||||
)
|
||||
|
||||
implicit val codec : Codec[SquadWaypointRequest] = (
|
||||
("request_type" | WaypointEventAction.codec) >>:~ { request_type =>
|
||||
("char_id" | uint32L) ::
|
||||
("waypoint_type" | SquadWaypoints.codec) ::
|
||||
("unk4" | conditional(request_type == WaypointEventAction.Unknown1, uint32L)) ::
|
||||
("waypoint" | conditional(request_type == WaypointEventAction.Add, waypoint_codec))
|
||||
}
|
||||
).exmap[SquadWaypointRequest] (
|
||||
{
|
||||
case WaypointEventAction.Add :: char_id :: waypoint_type :: None :: Some(waypoint) :: HNil =>
|
||||
Attempt.Successful(SquadWaypointRequest(WaypointEventAction.Add, char_id, waypoint_type, None, Some(waypoint)))
|
||||
|
||||
case WaypointEventAction.Unknown1 :: char_id :: waypoint_type :: Some(d) :: None :: HNil =>
|
||||
Attempt.Successful(SquadWaypointRequest(WaypointEventAction.Unknown1, char_id, waypoint_type, Some(d), None))
|
||||
|
||||
case request_type :: char_id :: waypoint_type :: None :: None :: HNil =>
|
||||
Attempt.Successful(SquadWaypointRequest(request_type, char_id, waypoint_type, None, None))
|
||||
|
||||
case data =>
|
||||
Attempt.Failure(Err(s"unexpected format while decoding - $data"))
|
||||
},
|
||||
{
|
||||
case SquadWaypointRequest(WaypointEventAction.Add, char_id, waypoint_type, None, Some(waypoint)) =>
|
||||
Attempt.Successful(WaypointEventAction.Add :: char_id :: waypoint_type :: None :: Some(waypoint) :: HNil)
|
||||
|
||||
case SquadWaypointRequest(WaypointEventAction.Unknown1, char_id, waypoint_type, Some(d), None) =>
|
||||
Attempt.Successful(WaypointEventAction.Unknown1 :: char_id :: waypoint_type :: Some(d) :: None :: HNil)
|
||||
|
||||
case SquadWaypointRequest(request_type, char_id, waypoint_type, None, None) =>
|
||||
Attempt.Successful(request_type :: char_id :: waypoint_type :: None :: None :: HNil)
|
||||
|
||||
case data : SquadWaypointRequest =>
|
||||
Attempt.Failure(Err(s"unexpected format while encoding - $data"))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -23,12 +23,13 @@ import shapeless.{::, HNil}
|
|||
* -player_guid - does nothing?
|
||||
* @param exosuit the type of exo-suit the avatar will be depicted in;
|
||||
* for Black OPs, the agile exo-suit and the reinforced exo-suit are replaced with the Black OPs exo-suits
|
||||
* @param char_id a unique character reference identification number
|
||||
*/
|
||||
final case class CharacterAppearanceA(app : BasicCharacterData,
|
||||
data : CommonFieldData,
|
||||
exosuit : ExoSuitType.Value,
|
||||
unk5 : Int,
|
||||
unk6 : Long,
|
||||
char_id : Long,
|
||||
unk7 : Int,
|
||||
unk8 : Int,
|
||||
unk9 : Int,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package net.psforever.types
|
|||
|
||||
import net.psforever.packet.PacketHelpers
|
||||
import scodec.codecs._
|
||||
|
||||
import scala.annotation.tailrec
|
||||
/**
|
||||
* An `Enumeration` of the available certifications.<br>
|
||||
* <br>
|
||||
|
|
@ -76,4 +78,59 @@ object CertificationType extends Enumeration {
|
|||
= Value
|
||||
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint8L)
|
||||
|
||||
/**
|
||||
* Certifications are often stored, in object form, as a 46-member collection.
|
||||
* Encode a subset of certification values for packet form.
|
||||
* @see `ChangeSquadMemberRequirementsCertifications`
|
||||
* @see `changeSquadMemberRequirementsCertificationsCodec`
|
||||
* @param certs the certifications, as a sequence of values
|
||||
* @return the certifications, as a single value
|
||||
*/
|
||||
def toEncodedLong(certs : Set[CertificationType.Value]) : Long = {
|
||||
certs
|
||||
.map{ cert => math.pow(2, cert.id).toLong }
|
||||
.foldLeft(0L)(_ + _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Certifications are often stored, in packet form, as an encoded little-endian `46u` value.
|
||||
* Decode a representative value into a subset of certification values.
|
||||
* @see `ChangeSquadMemberRequirementsCertifications`
|
||||
* @see `changeSquadMemberRequirementsCertificationsCodec`
|
||||
* @see `fromEncodedLong(Long, Iterable[Long], Set[CertificationType.Value])`
|
||||
* @param certs the certifications, as a single value
|
||||
* @return the certifications, as a sequence of values
|
||||
*/
|
||||
def fromEncodedLong(certs : Long) : Set[CertificationType.Value] = {
|
||||
recursiveFromEncodedLong(
|
||||
certs,
|
||||
CertificationType.values.map{ cert => math.pow(2, cert.id).toLong }.toSeq.sorted
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Certifications are often stored, in packet form, as an encoded little-endian `46u` value.
|
||||
* Decode a representative value into a subset of certification values
|
||||
* by repeatedly finding the partition point of values less than a specific one,
|
||||
* providing for both the next lowest value (to subtract) and an index (of a certification).
|
||||
* @see `ChangeSquadMemberRequirementsCertifications`
|
||||
* @see `changeSquadMemberRequirementsCertificationsCodec`
|
||||
* @see `fromEncodedLong(Long)`
|
||||
* @param certs the certifications, as a single value
|
||||
* @param splitList the available values to partition
|
||||
* @param out the accumulating certification values;
|
||||
* defaults to an empty set
|
||||
* @return the certifications, as a sequence of values
|
||||
*/
|
||||
@tailrec
|
||||
private def recursiveFromEncodedLong(certs : Long, splitList : Iterable[Long], out : Set[CertificationType.Value] = Set.empty) : Set[CertificationType.Value] = {
|
||||
if(certs == 0 || splitList.isEmpty) {
|
||||
out
|
||||
}
|
||||
else {
|
||||
val (less, _) = splitList.partition(_ <= certs)
|
||||
recursiveFromEncodedLong(certs - less.last, less, out ++ Set(CertificationType(less.size - 1)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,4 +12,13 @@ object PlanetSideEmpire extends Enumeration {
|
|||
val TR, NC, VS, NEUTRAL = Value
|
||||
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint2L)
|
||||
|
||||
def apply(id : String) : PlanetSideEmpire.Value = {
|
||||
values.find(_.toString.equals(id)) match {
|
||||
case Some(faction) =>
|
||||
faction
|
||||
case None =>
|
||||
throw new NoSuchElementException(s"can not find an empire associated with $id")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.types
|
||||
|
||||
import net.psforever.packet.PacketHelpers
|
||||
import scodec.codecs._
|
||||
|
||||
object SquadRequestType extends Enumeration {
|
||||
type Type = Value
|
||||
val
|
||||
Invite,
|
||||
ProximityInvite,
|
||||
Accept,
|
||||
Reject,
|
||||
Cancel,
|
||||
Leave,
|
||||
Promote,
|
||||
Disband,
|
||||
PlatoonInvite,
|
||||
PlatoonAccept,
|
||||
PlatoonReject,
|
||||
PlatoonCancel,
|
||||
PlatoonLeave,
|
||||
PlatoonDisband
|
||||
= Value
|
||||
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L)
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.types
|
||||
|
||||
import net.psforever.packet.PacketHelpers
|
||||
import scodec.codecs._
|
||||
|
||||
object SquadResponseType extends Enumeration {
|
||||
type Type = Value
|
||||
val
|
||||
Invite,
|
||||
Unk01,
|
||||
Accept,
|
||||
Reject,
|
||||
Cancel,
|
||||
Leave,
|
||||
Disband,
|
||||
PlatoonInvite,
|
||||
PlatoonAccept,
|
||||
PlatoonReject,
|
||||
PlatoonCancel,
|
||||
PlatoonLeave,
|
||||
PlatoonDisband
|
||||
= Value
|
||||
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.types
|
||||
|
||||
import net.psforever.packet.PacketHelpers
|
||||
import scodec.codecs._
|
||||
|
||||
object SquadWaypoints extends Enumeration {
|
||||
type Type = Value
|
||||
val
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
ExperienceRally
|
||||
= Value
|
||||
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint8L)
|
||||
}
|
||||
3033
common/src/main/scala/services/teamwork/SquadService.scala
Normal file
3033
common/src/main/scala/services/teamwork/SquadService.scala
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package services.teamwork
|
||||
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.{PlanetSideGUID, SquadAction => PacketSquadAction, WaypointEventAction, WaypointInfo}
|
||||
import net.psforever.types.{SquadRequestType, SquadWaypoints, Vector3}
|
||||
|
||||
final case class SquadServiceMessage(tplayer : Player, zone : Zone, actionMessage : Any)
|
||||
|
||||
object SquadServiceMessage {
|
||||
final case class RecoverSquadMembership()
|
||||
}
|
||||
|
||||
object SquadAction {
|
||||
sealed trait Action
|
||||
|
||||
final case class InitSquadList() extends Action
|
||||
final case class InitCharId() extends Action
|
||||
|
||||
final case class Definition(guid : PlanetSideGUID, line : Int, action : PacketSquadAction) extends Action
|
||||
final case class Membership(request_type : SquadRequestType.Value, unk2 : Long, unk3 : Option[Long], player_name : String, unk5 : Option[Option[String]]) extends Action
|
||||
final case class Waypoint(event_type : WaypointEventAction.Value, waypoint_type : SquadWaypoints.Value, unk : Option[Long], waypoint_info : Option[WaypointInfo]) extends Action
|
||||
final case class Update(char_id : Long, health : Int, max_health : Int, armor : Int, max_armor : Int, pos : Vector3, zone_number : Int) extends Action
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package services.teamwork
|
||||
|
||||
import net.psforever.objects.teamwork.Squad
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.{SquadResponseType, SquadWaypoints}
|
||||
import services.GenericEventBusMsg
|
||||
|
||||
final case class SquadServiceResponse(toChannel : String, exclude : Iterable[Long], response : SquadResponse.Response) extends GenericEventBusMsg
|
||||
|
||||
object SquadServiceResponse {
|
||||
def apply(toChannel : String, response : SquadResponse.Response) : SquadServiceResponse =
|
||||
SquadServiceResponse(toChannel, Nil, response)
|
||||
|
||||
def apply(toChannel : String, exclude : Long, response : SquadResponse.Response) : SquadServiceResponse =
|
||||
SquadServiceResponse(toChannel, Seq(exclude), response)
|
||||
}
|
||||
|
||||
object SquadResponse {
|
||||
sealed trait Response
|
||||
|
||||
final case class ListSquadFavorite(line : Int, task : String) extends Response
|
||||
|
||||
final case class InitList(info : Vector[SquadInfo]) extends Response
|
||||
final case class UpdateList(infos : Iterable[(Int, SquadInfo)]) extends Response
|
||||
final case class RemoveFromList(infos : Iterable[Int]) extends Response
|
||||
|
||||
final case class AssociateWithSquad(squad_guid : PlanetSideGUID) extends Response
|
||||
final case class SetListSquad(squad_guid : PlanetSideGUID) extends Response
|
||||
|
||||
final case class Membership(request_type : SquadResponseType.Value, unk1 : Int, unk2 : Int, unk3 : Long, unk4 : Option[Long], player_name : String, unk5 : Boolean, unk6 : Option[Option[String]]) extends Response //see SquadMembershipResponse
|
||||
final case class WantsSquadPosition(leader_char_id : Long, bid_name : String) extends Response
|
||||
final case class Join(squad : Squad, positionsToUpdate : List[Int], channel : String) extends Response
|
||||
final case class Leave(squad : Squad, positionsToUpdate : List[(Long, Int)]) extends Response
|
||||
final case class UpdateMembers(squad : Squad, update_info : List[SquadAction.Update]) extends Response
|
||||
final case class AssignMember(squad : Squad, from_index : Int, to_index : Int) extends Response
|
||||
final case class PromoteMember(squad : Squad, char_id : Long, from_index : Int, to_index : Int) extends Response
|
||||
|
||||
final case class Detail(guid : PlanetSideGUID, squad_detail : SquadDetail) extends Response
|
||||
|
||||
final case class InitWaypoints(char_id : Long, waypoints : Iterable[(SquadWaypoints.Value, WaypointInfo, Int)]) extends Response
|
||||
final case class WaypointEvent(event_type : WaypointEventAction.Value, char_id : Long, waypoint_type : SquadWaypoints.Value, unk5 : Option[Long], waypoint_info : Option[WaypointInfo], unk : Int) extends Response
|
||||
|
||||
final case class SquadSearchResults() extends Response
|
||||
}
|
||||
142
common/src/main/scala/services/teamwork/SquadSwitchboard.scala
Normal file
142
common/src/main/scala/services/teamwork/SquadSwitchboard.scala
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package services.teamwork
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Terminated}
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
/**
|
||||
* The dedicated messaging switchboard for members and observers of a given squad.
|
||||
* It almost always dispatches messages to `WorldSessionActor` instances, much like any other `Service`.
|
||||
* The sole purpose of this `ActorBus` container is to manage a subscription model
|
||||
* that can involuntarily drop subscribers without informing them explicitly
|
||||
* or can just vanish without having to properly clean itself up.
|
||||
*/
|
||||
class SquadSwitchboard extends Actor {
|
||||
/**
|
||||
* This collection contains the message-sending contact reference for squad members.
|
||||
* Users are added to this collection via the `SquadSwitchboard.Join` message, or a
|
||||
* combination of the `SquadSwitchboard.DelayJoin` message followed by a
|
||||
* `SquadSwitchboard.Join` message with or without an `ActorRef` hook.
|
||||
* The message `SquadSwitchboard.Leave` removes the user from this collection.
|
||||
* key - unique character id; value - `Actor` reference for that character
|
||||
*/
|
||||
val UserActorMap : mutable.LongMap[ActorRef] = mutable.LongMap[ActorRef]()
|
||||
/**
|
||||
* This collection contains the message-sending contact information for would-be squad members.
|
||||
* Users are added to this collection via the `SquadSwitchboard.DelayJoin` message
|
||||
* and are promoted to an actual squad member through a `SquadSwitchboard.Join` message.
|
||||
* The message `SquadSwitchboard.Leave` removes the user from this collection.
|
||||
* key - unique character id; value - `Actor` reference for that character
|
||||
*/
|
||||
val DelayedJoin : mutable.LongMap[ActorRef] = mutable.LongMap[ActorRef]()
|
||||
/**
|
||||
* This collection contains the message-sending contact information for squad observers.
|
||||
* Squad observers only get "details" messages as opposed to the sort of messages squad members receive.
|
||||
* Squad observers are promoted to an actual squad member through a `SquadSwitchboard.Watch` message.
|
||||
* The message `SquadSwitchboard.Leave` removes the user from this collection.
|
||||
* The message `SquadSwitchboard.Unwatch` also removes the user from this collection.
|
||||
* key - unique character id; value - `Actor` reference for that character
|
||||
*/
|
||||
val Watchers : mutable.LongMap[ActorRef] = mutable.LongMap[ActorRef]()
|
||||
|
||||
override def postStop() : Unit = {
|
||||
UserActorMap.clear()
|
||||
DelayedJoin.clear()
|
||||
Watchers.clear()
|
||||
}
|
||||
|
||||
def receive : Receive = {
|
||||
case SquadSwitchboard.Join(char_id, Some(actor)) =>
|
||||
UserActorMap(char_id) = DelayedJoin.remove(char_id).orElse( Watchers.remove(char_id)) match {
|
||||
case Some(_actor) =>
|
||||
context.watch(_actor)
|
||||
_actor
|
||||
case None =>
|
||||
context.watch(actor)
|
||||
actor
|
||||
}
|
||||
|
||||
case SquadSwitchboard.Join(char_id, None) =>
|
||||
DelayedJoin.remove(char_id).orElse( Watchers.remove(char_id)) match {
|
||||
case Some(actor) =>
|
||||
UserActorMap(char_id) = actor
|
||||
case None => ;
|
||||
}
|
||||
|
||||
case SquadSwitchboard.DelayJoin(char_id, actor) =>
|
||||
context.watch(actor)
|
||||
DelayedJoin(char_id) = actor
|
||||
|
||||
case SquadSwitchboard.Leave(char_id) =>
|
||||
UserActorMap.find { case(charId, _) => charId == char_id }
|
||||
.orElse(DelayedJoin.find { case(charId, _) => charId == char_id })
|
||||
.orElse(Watchers.find { case(charId, _) => charId == char_id }) match {
|
||||
case Some((member, actor)) =>
|
||||
context.unwatch(actor)
|
||||
UserActorMap.remove(member)
|
||||
DelayedJoin.remove(member)
|
||||
Watchers.remove(member)
|
||||
case None => ;
|
||||
}
|
||||
|
||||
case SquadSwitchboard.Watch(char_id, actor) =>
|
||||
context.watch(actor)
|
||||
Watchers(char_id) = actor
|
||||
|
||||
case SquadSwitchboard.Unwatch(char_id) =>
|
||||
Watchers.remove(char_id)
|
||||
|
||||
case SquadSwitchboard.To(member, msg) =>
|
||||
UserActorMap.find { case (char_id, _) => char_id == member } match {
|
||||
case Some((_, actor)) =>
|
||||
actor ! msg
|
||||
case None => ;
|
||||
}
|
||||
|
||||
case SquadSwitchboard.ToAll(msg) =>
|
||||
UserActorMap
|
||||
.foreach { case (_, actor) =>
|
||||
actor ! msg
|
||||
}
|
||||
|
||||
case SquadSwitchboard.Except(excluded, msg) =>
|
||||
UserActorMap
|
||||
.filterNot { case (char_id, _) => char_id == excluded }
|
||||
.foreach { case (_, actor) =>
|
||||
actor ! msg
|
||||
}
|
||||
|
||||
case Terminated(actorRef) =>
|
||||
UserActorMap.find { case(_, ref) => ref == actorRef }
|
||||
.orElse(DelayedJoin.find { case(_, ref) => ref == actorRef })
|
||||
.orElse(Watchers.find { case(_, ref) => ref == actorRef }) match {
|
||||
case Some((member, actor)) =>
|
||||
context.unwatch(actor)
|
||||
UserActorMap.remove(member)
|
||||
DelayedJoin.remove(member)
|
||||
Watchers.remove(member)
|
||||
case None => ;
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
object SquadSwitchboard {
|
||||
final case class Join(char_id : Long, actor : Option[ActorRef])
|
||||
|
||||
final case class DelayJoin(char_id : Long, actor : ActorRef)
|
||||
|
||||
final case class Leave(char_id : Long)
|
||||
|
||||
final case class Watch(char_id : Long, actor : ActorRef)
|
||||
|
||||
final case class Unwatch(char_id : Long)
|
||||
|
||||
final case class To(member : Long, msg : SquadServiceResponse)
|
||||
|
||||
final case class ToAll(msg : SquadServiceResponse)
|
||||
|
||||
final case class Except(excluded_member : Long, msg : SquadServiceResponse)
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package game
|
||||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.CertificationType
|
||||
import scodec.bits._
|
||||
|
||||
class CharacterKnowledgeMessageTest extends Specification {
|
||||
val string = hex"ec cc637a02 45804600720061006e006b0065006e00740061006e006b0003c022dc0008f01800"
|
||||
|
||||
"decode" in {
|
||||
PacketCoding.DecodePacket(string).require match {
|
||||
case CharacterKnowledgeMessage(char_id, Some(info)) =>
|
||||
char_id mustEqual 41575372L
|
||||
info mustEqual CharacterKnowledgeInfo(
|
||||
"Frankentank",
|
||||
Set(
|
||||
CertificationType.StandardAssault,
|
||||
CertificationType.ArmoredAssault1,
|
||||
CertificationType.MediumAssault,
|
||||
CertificationType.ReinforcedExoSuit,
|
||||
CertificationType.Harasser,
|
||||
CertificationType.Engineering,
|
||||
CertificationType.GroundSupport,
|
||||
CertificationType.AgileExoSuit,
|
||||
CertificationType.AIMAX,
|
||||
CertificationType.StandardExoSuit,
|
||||
CertificationType.AAMAX,
|
||||
CertificationType.ArmoredAssault2
|
||||
),
|
||||
15,
|
||||
0,
|
||||
PlanetSideGUID(12)
|
||||
)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
val msg = CharacterKnowledgeMessage(
|
||||
41575372L,
|
||||
CharacterKnowledgeInfo(
|
||||
"Frankentank",
|
||||
Set(
|
||||
CertificationType.StandardAssault,
|
||||
CertificationType.ArmoredAssault1,
|
||||
CertificationType.MediumAssault,
|
||||
CertificationType.ReinforcedExoSuit,
|
||||
CertificationType.Harasser,
|
||||
CertificationType.Engineering,
|
||||
CertificationType.GroundSupport,
|
||||
CertificationType.AgileExoSuit,
|
||||
CertificationType.AIMAX,
|
||||
CertificationType.StandardExoSuit,
|
||||
CertificationType.AAMAX,
|
||||
CertificationType.ArmoredAssault2
|
||||
),
|
||||
15,
|
||||
0,
|
||||
PlanetSideGUID(12)
|
||||
)
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string
|
||||
}
|
||||
}
|
||||
|
|
@ -19,17 +19,7 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
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"
|
||||
val stringRemoveUpdate = hex"e6 20201801014aff"
|
||||
|
||||
"SquadInfo (w/out squad_guid)" in {
|
||||
val o = SquadInfo("FragLANdINC", "Frag", PlanetSideZoneID(10), 0, 10)
|
||||
|
|
@ -56,15 +46,27 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
o.capacity.get mustEqual 7
|
||||
}
|
||||
|
||||
"SquadInfo (Add)" in {
|
||||
val o1 = SquadInfo(Some("FragLANdINC"), Some("Frag"), Some(PlanetSideZoneID(10)), None, None)
|
||||
val o2 = SquadInfo(Some(7), 10)
|
||||
val o3 = SquadInfo("FragLANdINC", "Frag", PlanetSideZoneID(10), 7, 10)
|
||||
o1.And(o2) mustEqual o3
|
||||
}
|
||||
|
||||
"SquadInfo (Add, with blocked fields)" in {
|
||||
val o1 = SquadInfo(Some("FragLANdINC"), None, Some(PlanetSideZoneID(10)), None, Some(10))
|
||||
val o2 = SquadInfo(Some("Frag"), Some("Frag"), Some(PlanetSideZoneID(15)), Some(7), Some(7))
|
||||
val o3 = SquadInfo("FragLANdINC", "Frag", PlanetSideZoneID(10), 7, 10)
|
||||
o1.And(o2) mustEqual o3
|
||||
}
|
||||
|
||||
"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
|
||||
entries.length mustEqual 0
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -75,27 +77,21 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
case ReplicationStreamMessage(behavior, behavior2, entries) =>
|
||||
behavior mustEqual 5
|
||||
behavior2.get mustEqual 6
|
||||
entries.length mustEqual 2
|
||||
entries.length mustEqual 1
|
||||
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
|
||||
entries.head.listing.get.leader.isDefined mustEqual true
|
||||
entries.head.listing.get.leader.get mustEqual "FragLANdINC"
|
||||
entries.head.listing.get.task.isDefined mustEqual true
|
||||
entries.head.listing.get.task.get mustEqual "Frag"
|
||||
entries.head.listing.get.zone_id.isDefined mustEqual true
|
||||
entries.head.listing.get.zone_id.get mustEqual PlanetSideZoneID(10)
|
||||
entries.head.listing.get.size.isDefined mustEqual true
|
||||
entries.head.listing.get.size.get mustEqual 0
|
||||
entries.head.listing.get.capacity.isDefined mustEqual true
|
||||
entries.head.listing.get.capacity.get mustEqual 10
|
||||
entries.head.listing.get.squad_guid.isDefined mustEqual true
|
||||
entries.head.listing.get.squad_guid.get mustEqual PlanetSideGUID(1)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -106,28 +102,21 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
case ReplicationStreamMessage(behavior, behavior2, entries) =>
|
||||
behavior mustEqual 5
|
||||
behavior2.get mustEqual 6
|
||||
entries.length mustEqual 3
|
||||
entries.length mustEqual 2
|
||||
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.head.listing.get.leader.get mustEqual "GeneralGorgutz"
|
||||
entries.head.listing.get.task.get mustEqual "FLY,All welcome,cn last night!!!!"
|
||||
entries.head.listing.get.zone_id.get mustEqual PlanetSideZoneID(4)
|
||||
entries.head.listing.get.size.get mustEqual 7
|
||||
entries.head.listing.get.capacity.get mustEqual 10
|
||||
entries.head.listing.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
|
||||
entries(1).listing.get.leader.get mustEqual "KOKkiasMFCN"
|
||||
entries(1).listing.get.task.get mustEqual "Squad 2"
|
||||
entries(1).listing.get.zone_id.get mustEqual PlanetSideZoneID(4)
|
||||
entries(1).listing.get.size.get mustEqual 6
|
||||
entries(1).listing.get.capacity.get mustEqual 10
|
||||
entries(1).listing.get.squad_guid.get mustEqual PlanetSideGUID(4)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -138,38 +127,28 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
case ReplicationStreamMessage(behavior, behavior2, entries) =>
|
||||
behavior mustEqual 5
|
||||
behavior2.get mustEqual 6
|
||||
entries.length mustEqual 4
|
||||
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.head.listing.get.leader.get mustEqual "GeneralGorgutz"
|
||||
entries.head.listing.get.task.get mustEqual "FLY,All welcome,cn last night!!!!"
|
||||
entries.head.listing.get.zone_id.get mustEqual PlanetSideZoneID(4)
|
||||
entries.head.listing.get.size.get mustEqual 7
|
||||
entries.head.listing.get.capacity.get mustEqual 10
|
||||
entries.head.listing.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(1).listing.get.leader.get mustEqual "NIGHT88RAVEN"
|
||||
entries(1).listing.get.task.get mustEqual "All Welcome"
|
||||
entries(1).listing.get.zone_id.get mustEqual PlanetSideZoneID(10)
|
||||
entries(1).listing.get.size.get mustEqual 4
|
||||
entries(1).listing.get.capacity.get mustEqual 10
|
||||
entries(1).listing.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
|
||||
entries(2).listing.get.leader.get mustEqual "KOKkiasMFCN"
|
||||
entries(2).listing.get.task.get mustEqual "Squad 2"
|
||||
entries(2).listing.get.zone_id.get mustEqual PlanetSideZoneID(4)
|
||||
entries(2).listing.get.size.get mustEqual 6
|
||||
entries(2).listing.get.capacity.get mustEqual 10
|
||||
entries(2).listing.get.squad_guid.get mustEqual PlanetSideGUID(4)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -180,15 +159,9 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
case ReplicationStreamMessage(behavior, behavior2, entries) =>
|
||||
behavior mustEqual 1
|
||||
behavior2.isDefined mustEqual false
|
||||
entries.length mustEqual 2
|
||||
entries.length mustEqual 1
|
||||
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
|
||||
entries.head.listing.isDefined mustEqual false
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -199,22 +172,16 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
case ReplicationStreamMessage(behavior, behavior2, entries) =>
|
||||
behavior mustEqual 6
|
||||
behavior2.isDefined mustEqual false
|
||||
entries.length mustEqual 2
|
||||
entries.length mustEqual 1
|
||||
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
|
||||
entries.head.listing.get.leader.isDefined mustEqual true
|
||||
entries.head.listing.get.leader.get mustEqual "FateJHNC"
|
||||
entries.head.listing.get.task.isDefined mustEqual false
|
||||
entries.head.listing.get.zone_id.isDefined mustEqual false
|
||||
entries.head.listing.get.size.isDefined mustEqual false
|
||||
entries.head.listing.get.capacity.isDefined mustEqual false
|
||||
entries.head.listing.get.squad_guid.isDefined mustEqual false
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -225,22 +192,16 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
case ReplicationStreamMessage(behavior, behavior2, entries) =>
|
||||
behavior mustEqual 6
|
||||
behavior2.isDefined mustEqual false
|
||||
entries.length mustEqual 2
|
||||
entries.length mustEqual 1
|
||||
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
|
||||
entries.head.listing.get.leader.isDefined mustEqual false
|
||||
entries.head.listing.get.task.isDefined mustEqual true
|
||||
entries.head.listing.get.task.get mustEqual "RIP PS1, visit PSForever.net"
|
||||
entries.head.listing.get.zone_id.isDefined mustEqual false
|
||||
entries.head.listing.get.size.isDefined mustEqual false
|
||||
entries.head.listing.get.capacity.isDefined mustEqual false
|
||||
entries.head.listing.get.squad_guid.isDefined mustEqual false
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -251,22 +212,16 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
case ReplicationStreamMessage(behavior, behavior2, entries) =>
|
||||
behavior mustEqual 6
|
||||
behavior2.isDefined mustEqual false
|
||||
entries.length mustEqual 2
|
||||
entries.length mustEqual 1
|
||||
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
|
||||
entries.head.listing.get.leader.isDefined mustEqual false
|
||||
entries.head.listing.get.task.isDefined mustEqual false
|
||||
entries.head.listing.get.zone_id.isDefined mustEqual true
|
||||
entries.head.listing.get.zone_id.get mustEqual PlanetSideZoneID(10)
|
||||
entries.head.listing.get.size.isDefined mustEqual false
|
||||
entries.head.listing.get.capacity.isDefined mustEqual false
|
||||
entries.head.listing.get.squad_guid.isDefined mustEqual false
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -277,22 +232,16 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
case ReplicationStreamMessage(behavior, behavior2, entries) =>
|
||||
behavior mustEqual 6
|
||||
behavior2.isDefined mustEqual false
|
||||
entries.length mustEqual 2
|
||||
entries.length mustEqual 1
|
||||
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
|
||||
entries.head.listing.get.leader.isDefined mustEqual false
|
||||
entries.head.listing.get.task.isDefined mustEqual false
|
||||
entries.head.listing.get.zone_id.isDefined mustEqual false
|
||||
entries.head.listing.get.size.isDefined mustEqual true
|
||||
entries.head.listing.get.size.get mustEqual 6
|
||||
entries.head.listing.get.capacity.isDefined mustEqual false
|
||||
entries.head.listing.get.squad_guid.isDefined mustEqual false
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -303,23 +252,17 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
case ReplicationStreamMessage(behavior, behavior2, entries) =>
|
||||
behavior mustEqual 6
|
||||
behavior2.isDefined mustEqual false
|
||||
entries.length mustEqual 2
|
||||
entries.length mustEqual 1
|
||||
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
|
||||
entries.head.listing.get.leader.isDefined mustEqual true
|
||||
entries.head.listing.get.leader.get mustEqual "Jimmyn"
|
||||
entries.head.listing.get.task.isDefined mustEqual false
|
||||
entries.head.listing.get.zone_id.isDefined mustEqual false
|
||||
entries.head.listing.get.size.isDefined mustEqual true
|
||||
entries.head.listing.get.size.get mustEqual 3
|
||||
entries.head.listing.get.capacity.isDefined mustEqual false
|
||||
entries.head.listing.get.squad_guid.isDefined mustEqual false
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -330,23 +273,17 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
case ReplicationStreamMessage(behavior, behavior2, entries) =>
|
||||
behavior mustEqual 6
|
||||
behavior2.isDefined mustEqual false
|
||||
entries.length mustEqual 2
|
||||
entries.length mustEqual 1
|
||||
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
|
||||
entries.head.listing.get.leader.isDefined mustEqual false
|
||||
entries.head.listing.get.task.isDefined mustEqual true
|
||||
entries.head.listing.get.task.get mustEqual "2"
|
||||
entries.head.listing.get.zone_id.isDefined mustEqual true
|
||||
entries.head.listing.get.zone_id.get mustEqual PlanetSideZoneID(4)
|
||||
entries.head.listing.get.size.isDefined mustEqual false
|
||||
entries.head.listing.get.capacity.isDefined mustEqual false
|
||||
entries.head.listing.get.squad_guid.isDefined mustEqual false
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -357,60 +294,57 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
case ReplicationStreamMessage(behavior, behavior2, entries) =>
|
||||
behavior mustEqual 6
|
||||
behavior2.isDefined mustEqual false
|
||||
entries.length mustEqual 2
|
||||
entries.length mustEqual 1
|
||||
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
|
||||
entries.head.listing.get.leader.isDefined mustEqual true
|
||||
entries.head.listing.get.leader.get mustEqual "madmuj"
|
||||
entries.head.listing.get.task.isDefined mustEqual true
|
||||
entries.head.listing.get.task.get mustEqual ""
|
||||
entries.head.listing.get.zone_id.isDefined mustEqual true
|
||||
entries.head.listing.get.zone_id.get mustEqual PlanetSideZoneID(4)
|
||||
entries.head.listing.get.size.isDefined mustEqual true
|
||||
entries.head.listing.get.size.get mustEqual 0
|
||||
entries.head.listing.get.capacity.isDefined mustEqual true
|
||||
entries.head.listing.get.capacity.get mustEqual 10
|
||||
entries.head.listing.get.squad_guid.isDefined mustEqual true
|
||||
entries.head.listing.get.squad_guid.get mustEqual PlanetSideGUID(11)
|
||||
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
|
||||
"decode (remove 1 and update 0)" in {
|
||||
PacketCoding.DecodePacket(stringRemoveUpdate).require match {
|
||||
case ReplicationStreamMessage(behavior, behavior2, entries) =>
|
||||
behavior mustEqual 1
|
||||
behavior2.isDefined mustEqual false
|
||||
entries.length mustEqual 2
|
||||
entries.head.index mustEqual 1
|
||||
entries.head.listing.isDefined mustEqual false
|
||||
entries(1).listing.get.leader.isDefined mustEqual false
|
||||
entries(1).listing.get.task.isDefined mustEqual false
|
||||
entries(1).listing.get.zone_id.isDefined mustEqual false
|
||||
entries(1).listing.get.size.isDefined mustEqual true
|
||||
entries(1).listing.get.size.get mustEqual 10
|
||||
entries(1).listing.get.capacity.isDefined mustEqual false
|
||||
entries(1).listing.get.squad_guid.isDefined mustEqual false
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode (clear)" in {
|
||||
val msg = ReplicationStreamMessage(5, Some(6),
|
||||
Vector(
|
||||
SquadListing(255)
|
||||
)
|
||||
)
|
||||
val msg = ReplicationStreamMessage(5, Some(6), Vector.empty)
|
||||
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 msg = ReplicationStreamMessage(
|
||||
Seq(
|
||||
SquadInfo("FragLANdINC", "Frag", PlanetSideZoneID(10), 0, 10, PlanetSideGUID(1))
|
||||
)
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
|
@ -419,11 +353,10 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
}
|
||||
|
||||
"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 msg = ReplicationStreamMessage(
|
||||
Seq(
|
||||
SquadInfo("GeneralGorgutz", "FLY,All welcome,cn last night!!!!", PlanetSideZoneID(4), 7, 10, PlanetSideGUID(6)),
|
||||
SquadInfo("KOKkiasMFCN", "Squad 2", PlanetSideZoneID(4), 6, 10, PlanetSideGUID(4))
|
||||
)
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
|
@ -432,12 +365,11 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
}
|
||||
|
||||
"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 msg = ReplicationStreamMessage(
|
||||
Seq(
|
||||
SquadInfo("GeneralGorgutz", "FLY,All welcome,cn last night!!!!", PlanetSideZoneID(4), 7, 10, PlanetSideGUID(6)),
|
||||
SquadInfo("NIGHT88RAVEN", "All Welcome", PlanetSideZoneID(10), 4, 10, PlanetSideGUID(3)),
|
||||
SquadInfo("KOKkiasMFCN", "Squad 2", PlanetSideZoneID(4), 6, 10, PlanetSideGUID(4))
|
||||
)
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
|
@ -448,8 +380,7 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
"encode (remove)" in {
|
||||
val msg = ReplicationStreamMessage(1, None,
|
||||
Vector(
|
||||
SquadListing(5, Some(SquadHeader(0, true, Some(4)))),
|
||||
SquadListing(255)
|
||||
SquadListing(5, None)
|
||||
)
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
|
@ -460,8 +391,7 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
"encode (update leader)" in {
|
||||
val msg = ReplicationStreamMessage(6, None,
|
||||
Vector(
|
||||
SquadListing(2, Some(SquadHeader(128, true, Some(0), SquadInfo("FateJHNC", None)))),
|
||||
SquadListing(255)
|
||||
SquadListing(2, SquadInfo("FateJHNC"))
|
||||
)
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
|
@ -472,8 +402,7 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
"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)
|
||||
SquadListing(5, SquadInfo(None, "RIP PS1, visit PSForever.net"))
|
||||
)
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
|
@ -484,8 +413,7 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
"encode (update continent)" in {
|
||||
val msg = ReplicationStreamMessage(6, None,
|
||||
Vector(
|
||||
SquadListing(3, Some(SquadHeader(128, true, Some(1), SquadInfo(PlanetSideZoneID(10))))),
|
||||
SquadListing(255)
|
||||
SquadListing(3, SquadInfo(PlanetSideZoneID(10)))
|
||||
)
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
|
@ -496,8 +424,7 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
"encode (update size)" in {
|
||||
val msg = ReplicationStreamMessage(6, None,
|
||||
Vector(
|
||||
SquadListing(1, Some(SquadHeader(128, true, Some(2), SquadInfo(6, None)))),
|
||||
SquadListing(255)
|
||||
SquadListing(1, SquadInfo(6))
|
||||
)
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
|
@ -508,8 +435,7 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
"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)
|
||||
SquadListing(5, SquadInfo("Jimmyn").And(SquadInfo(3)))
|
||||
)
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
|
@ -520,8 +446,7 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
"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)
|
||||
SquadListing(5, SquadInfo(None, "2").And(SquadInfo(PlanetSideZoneID(4))))
|
||||
)
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
|
@ -532,8 +457,7 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
"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)
|
||||
SquadListing(7, SquadInfo("madmuj", "", PlanetSideZoneID(4), 0, 10, PlanetSideGUID(11)))
|
||||
)
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
|
@ -541,116 +465,15 @@ class ReplicationStreamMessageTest extends Specification {
|
|||
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)
|
||||
)
|
||||
"encode (remove 1 and update 0)" in {
|
||||
val msg = ReplicationStreamMessage(1, None,
|
||||
Vector(
|
||||
SquadListing(1, None),
|
||||
SquadListing(0, SquadInfo(10))
|
||||
)
|
||||
).isFailure mustEqual true
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
//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
|
||||
pkt mustEqual stringRemoveUpdate
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,17 @@ package game
|
|||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game.SquadAction._
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.CertificationType
|
||||
import scodec.bits._
|
||||
|
||||
class SquadDefinitionActionMessageTest extends Specification {
|
||||
//local test data; note that the second field - unk1 - is always blank for now, but that probably changes
|
||||
val string_00 = hex"e7 00 0c0000" //guid: 3
|
||||
val string_03 = hex"E7 0c 0000c0" //index: 3
|
||||
val string_04 = hex"E7 10 0000c0" //index: 3
|
||||
val string_07 = hex"e7 1c 0000e68043006f0070007300200061006e00640020004d0069006c006900740061007200790020004f006600660069006300650072007300"
|
||||
val string_08 = hex"E7 20 000000"
|
||||
val string_10 = hex"E7 28 000004" //index: 1
|
||||
val string_19 = hex"E7 4c 0000218041002d005400650061006d00" //"A-Team"
|
||||
|
|
@ -30,18 +35,48 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
val string_40 = hex"E7 a0 000004" //index: 1
|
||||
val string_41 = hex"E7 a4 000000"
|
||||
|
||||
val string_43 = hex"e7 ac 000000"
|
||||
val string_failure = hex"E7 ff"
|
||||
|
||||
"decode (00)" in {
|
||||
PacketCoding.DecodePacket(string_00).require match {
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(3)
|
||||
unk2 mustEqual 0
|
||||
action mustEqual DisplaySquad()
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (03)" in {
|
||||
PacketCoding.DecodePacket(string_03).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 3
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 3
|
||||
str.isDefined mustEqual false
|
||||
int1.isDefined mustEqual false
|
||||
int2.isDefined mustEqual false
|
||||
long1.isDefined mustEqual false
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual SaveSquadFavorite()
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (03)" in {
|
||||
PacketCoding.DecodePacket(string_04).require match {
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 3
|
||||
action mustEqual LoadSquadFavorite()
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (07)" in {
|
||||
PacketCoding.DecodePacket(string_07).require match {
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 3
|
||||
action mustEqual ListSquadFavorite("Cops and Military Officers")
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -49,16 +84,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (08)" in {
|
||||
PacketCoding.DecodePacket(string_08).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 8
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual false
|
||||
int1.isDefined mustEqual false
|
||||
int2.isDefined mustEqual false
|
||||
long1.isDefined mustEqual false
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual RequestListSquad()
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -66,17 +95,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (10)" in {
|
||||
PacketCoding.DecodePacket(string_10).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 10
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual false
|
||||
int1.isDefined mustEqual true
|
||||
int1.get mustEqual 1
|
||||
int2.isDefined mustEqual false
|
||||
long1.isDefined mustEqual false
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual SelectRoleForYourself(1)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -84,17 +106,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (19)" in {
|
||||
PacketCoding.DecodePacket(string_19).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 19
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual true
|
||||
str.get mustEqual "A-Team"
|
||||
int1.isDefined mustEqual false
|
||||
int2.isDefined mustEqual false
|
||||
long1.isDefined mustEqual false
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual ChangeSquadPurpose("A-Team")
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -102,17 +117,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (20)" in {
|
||||
PacketCoding.DecodePacket(string_20).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 20
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual false
|
||||
int1.isDefined mustEqual true
|
||||
int1.get mustEqual 1
|
||||
int2.isDefined mustEqual false
|
||||
long1.isDefined mustEqual false
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual ChangeSquadZone(PlanetSideZoneID(1))
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -120,17 +128,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (21)" in {
|
||||
PacketCoding.DecodePacket(string_21).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 21
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual false
|
||||
int1.isDefined mustEqual true
|
||||
int1.get mustEqual 2
|
||||
int2.isDefined mustEqual false
|
||||
long1.isDefined mustEqual false
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual CloseSquadMemberPosition(2)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -138,17 +139,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (22)" in {
|
||||
PacketCoding.DecodePacket(string_22).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 22
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual false
|
||||
int1.isDefined mustEqual true
|
||||
int1.get mustEqual 2
|
||||
int2.isDefined mustEqual false
|
||||
long1.isDefined mustEqual false
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual AddSquadMemberPosition(2)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -156,18 +150,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (23)" in {
|
||||
PacketCoding.DecodePacket(string_23).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 23
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual true
|
||||
str.get mustEqual "BLUFOR"
|
||||
int1.isDefined mustEqual true
|
||||
int1.get mustEqual 1
|
||||
int2.isDefined mustEqual false
|
||||
long1.isDefined mustEqual false
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual ChangeSquadMemberRequirementsRole(1, "BLUFOR")
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -175,18 +161,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (24)" in {
|
||||
PacketCoding.DecodePacket(string_24).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 24
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual true
|
||||
str.get mustEqual "kill bad dudes"
|
||||
int1.isDefined mustEqual true
|
||||
int1.get mustEqual 1
|
||||
int2.isDefined mustEqual false
|
||||
long1.isDefined mustEqual false
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual ChangeSquadMemberRequirementsDetailedOrders(1, "kill bad dudes")
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -194,18 +172,13 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (25)" in {
|
||||
PacketCoding.DecodePacket(string_25).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 25
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual false
|
||||
int1.isDefined mustEqual true
|
||||
int1.get mustEqual 1
|
||||
int2.isDefined mustEqual false
|
||||
long1.isDefined mustEqual true
|
||||
long1.get mustEqual 536870928L
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual ChangeSquadMemberRequirementsCertifications(
|
||||
1,
|
||||
Set(CertificationType.AntiVehicular, CertificationType.InfiltrationSuit)
|
||||
)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -213,16 +186,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (26)" in {
|
||||
PacketCoding.DecodePacket(string_26).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 26
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual false
|
||||
int1.isDefined mustEqual false
|
||||
int2.isDefined mustEqual false
|
||||
long1.isDefined mustEqual false
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual ResetAll()
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -230,17 +197,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (28)" in {
|
||||
PacketCoding.DecodePacket(string_28).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 28
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual false
|
||||
int1.isDefined mustEqual false
|
||||
int2.isDefined mustEqual false
|
||||
long1.isDefined mustEqual false
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual true
|
||||
bool.get mustEqual true
|
||||
action mustEqual AutoApproveInvitationRequests(true)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -248,17 +208,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (31)" in {
|
||||
PacketCoding.DecodePacket(string_31).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 31
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual false
|
||||
int1.isDefined mustEqual false
|
||||
int2.isDefined mustEqual false
|
||||
long1.isDefined mustEqual false
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual true
|
||||
bool.get mustEqual true
|
||||
action mustEqual LocationFollowsSquadLead(true)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -266,20 +219,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (34a)" in {
|
||||
PacketCoding.DecodePacket(string_34a).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 34
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual true
|
||||
str.get mustEqual "Badass"
|
||||
int1.isDefined mustEqual true
|
||||
int1.get mustEqual 1
|
||||
int2.isDefined mustEqual true
|
||||
int2.get mustEqual 0
|
||||
long1.isDefined mustEqual true
|
||||
long1.get mustEqual 0
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual SearchForSquadsWithParticularRole("Badass", Set(), 1, SearchMode.AnyPositions)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -287,20 +230,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (34b)" in {
|
||||
PacketCoding.DecodePacket(string_34b).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 34
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual true
|
||||
str.get mustEqual "Badass"
|
||||
int1.isDefined mustEqual true
|
||||
int1.get mustEqual 2
|
||||
int2.isDefined mustEqual true
|
||||
int2.get mustEqual 0
|
||||
long1.isDefined mustEqual true
|
||||
long1.get mustEqual 0
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual SearchForSquadsWithParticularRole("Badass", Set(), 2, SearchMode.AnyPositions)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -308,20 +241,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (34c)" in {
|
||||
PacketCoding.DecodePacket(string_34c).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 34
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual true
|
||||
str.get mustEqual "Badass"
|
||||
int1.isDefined mustEqual true
|
||||
int1.get mustEqual 2
|
||||
int2.isDefined mustEqual true
|
||||
int2.get mustEqual 1
|
||||
long1.isDefined mustEqual true
|
||||
long1.get mustEqual 0
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual SearchForSquadsWithParticularRole("Badass", Set(), 2, SearchMode.AvailablePositions)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -329,20 +252,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (34d)" in {
|
||||
PacketCoding.DecodePacket(string_34d).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 34
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual true
|
||||
str.get mustEqual "Badass"
|
||||
int1.isDefined mustEqual true
|
||||
int1.get mustEqual 2
|
||||
int2.isDefined mustEqual true
|
||||
int2.get mustEqual 2
|
||||
long1.isDefined mustEqual true
|
||||
long1.get mustEqual 536870928L
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual SearchForSquadsWithParticularRole("Badass", Set(CertificationType.InfiltrationSuit, CertificationType.AntiVehicular), 2, SearchMode.SomeCertifications)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -350,20 +263,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (34e)" in {
|
||||
PacketCoding.DecodePacket(string_34e).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 34
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual true
|
||||
str.get mustEqual "Badass"
|
||||
int1.isDefined mustEqual true
|
||||
int1.get mustEqual 2
|
||||
int2.isDefined mustEqual true
|
||||
int2.get mustEqual 3
|
||||
long1.isDefined mustEqual true
|
||||
long1.get mustEqual 536870928L
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual SearchForSquadsWithParticularRole("Badass", Set(CertificationType.InfiltrationSuit, CertificationType.AntiVehicular), 2, SearchMode.AllCertifications)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -371,16 +274,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (35)" in {
|
||||
PacketCoding.DecodePacket(string_35).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 35
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual false
|
||||
int1.isDefined mustEqual false
|
||||
int2.isDefined mustEqual false
|
||||
long1.isDefined mustEqual false
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual CancelSquadSearch()
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -388,17 +285,10 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (40)" in {
|
||||
PacketCoding.DecodePacket(string_40).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 40
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual false
|
||||
int1.isDefined mustEqual true
|
||||
int1.get mustEqual 1
|
||||
int2.isDefined mustEqual false
|
||||
long1.isDefined mustEqual false
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual FindLfsSoldiersForRole(1)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -406,165 +296,205 @@ class SquadDefinitionActionMessageTest extends Specification {
|
|||
|
||||
"decode (41)" in {
|
||||
PacketCoding.DecodePacket(string_41).require match {
|
||||
case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) =>
|
||||
action mustEqual 41
|
||||
unk1 mustEqual 0
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
str.isDefined mustEqual false
|
||||
int1.isDefined mustEqual false
|
||||
int2.isDefined mustEqual false
|
||||
long1.isDefined mustEqual false
|
||||
long2.isDefined mustEqual false
|
||||
bool.isDefined mustEqual false
|
||||
action mustEqual CancelFind()
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (43, unknown)" in {
|
||||
PacketCoding.DecodePacket(string_43).require match {
|
||||
case SquadDefinitionActionMessage(unk1, unk2, action) =>
|
||||
unk1 mustEqual PlanetSideGUID(0)
|
||||
unk2 mustEqual 0
|
||||
action mustEqual Unknown(43, hex"00".toBitVector.take(6))
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (failure)" in {
|
||||
PacketCoding.DecodePacket(string_failure).isFailure mustEqual true
|
||||
}
|
||||
|
||||
"encode (00)" in {
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(3), 0, DisplaySquad())
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_00
|
||||
}
|
||||
|
||||
"encode (03)" in {
|
||||
val msg = SquadDefinitionActionMessage(3, 0, 3, None, None, None, None, None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 3, SaveSquadFavorite())
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_03
|
||||
}
|
||||
|
||||
"encode (03)" in {
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 3, LoadSquadFavorite())
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_04
|
||||
}
|
||||
|
||||
"encode (07)" in {
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 3, ListSquadFavorite("Cops and Military Officers"))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_07
|
||||
}
|
||||
|
||||
"encode (08)" in {
|
||||
val msg = SquadDefinitionActionMessage(8, 0, 0, None, None, None, None, None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, RequestListSquad())
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_08
|
||||
}
|
||||
|
||||
"encode (10)" in {
|
||||
val msg = SquadDefinitionActionMessage(10, 0, 0, None, Some(1), None, None, None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SelectRoleForYourself(1))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_10
|
||||
}
|
||||
|
||||
"encode (19)" in {
|
||||
val msg = SquadDefinitionActionMessage(19, 0, 0, Some("A-Team"), None, None, None, None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, ChangeSquadPurpose("A-Team"))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_19
|
||||
}
|
||||
|
||||
"encode (20)" in {
|
||||
val msg = SquadDefinitionActionMessage(20, 0, 0, None, Some(1), None, None, None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, ChangeSquadZone(PlanetSideZoneID(1)))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_20
|
||||
}
|
||||
|
||||
"encode (21)" in {
|
||||
val msg = SquadDefinitionActionMessage(21, 0, 0, None, Some(2), None, None, None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, CloseSquadMemberPosition(2))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_21
|
||||
}
|
||||
|
||||
"encode (22)" in {
|
||||
val msg = SquadDefinitionActionMessage(22, 0, 0, None, Some(2), None, None, None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, AddSquadMemberPosition(2))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_22
|
||||
}
|
||||
|
||||
"encode (23)" in {
|
||||
val msg = SquadDefinitionActionMessage(23, 0, 0, Some("BLUFOR"), Some(1), None, None, None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, ChangeSquadMemberRequirementsRole(1, "BLUFOR"))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_23
|
||||
}
|
||||
|
||||
"encode (24)" in {
|
||||
val msg = SquadDefinitionActionMessage(24, 0, 0, Some("kill bad dudes"), Some(1), None, None, None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, ChangeSquadMemberRequirementsDetailedOrders(1, "kill bad dudes"))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_24
|
||||
}
|
||||
|
||||
"encode (25)" in {
|
||||
val msg = SquadDefinitionActionMessage(25, 0, 0, None, Some(1), None, Some(536870928L), None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, ChangeSquadMemberRequirementsCertifications(
|
||||
1,
|
||||
Set(CertificationType.AntiVehicular, CertificationType.InfiltrationSuit)
|
||||
))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_25
|
||||
}
|
||||
|
||||
"encode (26)" in {
|
||||
val msg = SquadDefinitionActionMessage(26, 0, 0, None, None, None, None, None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, ResetAll())
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_26
|
||||
}
|
||||
|
||||
"encode (28)" in {
|
||||
val msg = SquadDefinitionActionMessage(28, 0, 0, None, None, None, None, None, Some(true))
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, AutoApproveInvitationRequests(true))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_28
|
||||
}
|
||||
|
||||
"encode (31)" in {
|
||||
val msg = SquadDefinitionActionMessage(31, 0, 0, None, None, None, None, None, Some(true))
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, LocationFollowsSquadLead(true))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_31
|
||||
}
|
||||
|
||||
"encode (34a)" in {
|
||||
val msg = SquadDefinitionActionMessage(34, 0, 0, Some("Badass"), Some(1), Some(0), Some(0L), None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", Set(), 1, SearchMode.AnyPositions))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_34a
|
||||
}
|
||||
|
||||
"encode (34b)" in {
|
||||
val msg = SquadDefinitionActionMessage(34, 0, 0, Some("Badass"), Some(2), Some(0), Some(0L), None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", Set(), 2, SearchMode.AnyPositions))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_34b
|
||||
}
|
||||
|
||||
"encode (34c)" in {
|
||||
val msg = SquadDefinitionActionMessage(34, 0, 0, Some("Badass"), Some(2), Some(1), Some(0L), None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", Set(), 2, SearchMode.AvailablePositions))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_34c
|
||||
}
|
||||
|
||||
"encode (34d)" in {
|
||||
val msg = SquadDefinitionActionMessage(34, 0, 0, Some("Badass"), Some(2), Some(2), Some(536870928L), None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", Set(CertificationType.InfiltrationSuit, CertificationType.AntiVehicular), 2, SearchMode.SomeCertifications))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_34d
|
||||
}
|
||||
|
||||
"encode (34e)" in {
|
||||
val msg = SquadDefinitionActionMessage(34, 0, 0, Some("Badass"), Some(2), Some(3), Some(536870928L), None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SearchForSquadsWithParticularRole("Badass", Set(CertificationType.InfiltrationSuit, CertificationType.AntiVehicular), 2, SearchMode.AllCertifications))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_34e
|
||||
}
|
||||
|
||||
"encode (35)" in {
|
||||
val msg = SquadDefinitionActionMessage(35, 0, 0, None, None, None, None, None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, CancelSquadSearch())
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_35
|
||||
}
|
||||
|
||||
"encode (40)" in {
|
||||
val msg = SquadDefinitionActionMessage(40, 0, 0, None, Some(1), None, None, None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, FindLfsSoldiersForRole(1))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_40
|
||||
}
|
||||
|
||||
"encode (41)" in {
|
||||
val msg = SquadDefinitionActionMessage(41, 0, 0, None, None, None, None, None, None)
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, CancelFind())
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_41
|
||||
}
|
||||
|
||||
"encode (43, unknown)" in {
|
||||
val msg = SquadDefinitionActionMessage(PlanetSideGUID(0), 0, Unknown(43, BitVector.empty))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_43
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,716 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package game
|
||||
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.CertificationType
|
||||
import org.specs2.mutable._
|
||||
import scodec.bits._
|
||||
|
||||
class SquadDetailDefinitionUpdateMessageTest extends Specification {
|
||||
val string_unk1 = hex"e80300818800015c5189004603408c000000012000ff"
|
||||
val string_leader_char_id = hex"e8050080904d56b808"
|
||||
val string_unk3LeaderName = hex"e80300821104145011b9be840024284a00610061006b006f008c008118000000024000ff"
|
||||
val string_task = hex"e8050080ac6041006c006c002000570065006c0063006f006d0065002000"
|
||||
val string_zone = hex"e8030080b0a8000000"
|
||||
val string_taskZone = hex"e80200812ce05c002300460046003000300030003000200054006800650020005c002300660066006600660066006600200042006c0061006400650073006040000000"
|
||||
val string_unk7 = hex"e8030081ac8054006800650020004b0069006e00670027007300200053007100750061006400788c09808c4854006800650020004700750061007200640008808c5054006800650020004b006e00690067006800740007808c4054006800650020004500610072006c0006808c4054006800650020004c006f007200640005808c405400680065002000440075006b00650004808c4854006800650020004200610072006f006e0003808c6054006800650020005000720069006e00630065007300730002808c5054006800650020005000720069006e006300650001808c48540068006500200051007500650065006e0000808c4054006800650020004b0069006e006700ff"
|
||||
val string_member_closed = hex"e8030080c602c043fe"
|
||||
val string_member_role = hex"e8070080c60040462443006f006d006d0061006e00640065007200ff"
|
||||
val string_member_roleRequirements = hex"e8010080c60340862841004400560020004800610063006b00650072005000000002003fc0"
|
||||
val string_member_charIdName = hex"e8030080c602c08f2658480123004400750063006b006d006100730074006500720034003300ff"
|
||||
val string_task_memberEtc = hex"e80100812ce05c002300460046003000300030003000200054006800650020005c002300660066006600660066006600200042006c0061006400650073008c09810c005000000000000220230007808c0006808c0005808c0004808c0003808c0002808c0001808c0000808c00ff"
|
||||
val string_full = hex"e80300848180038021514601288a8400420048006f0066004400bf5c0023006600660064006300300030002a002a002a005c0023003900360034003000660066003d004b004f004b002b005300500043002b0046004c0059003d005c0023006600660064006300300030002a002a002a005c002300460046003400300034003000200041006c006c002000570065006c0063006f006d006500070000009814010650005c00230066006600300030003000300020007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c008000000000800100000c00020c8c5c00230066006600640063003000300020002000200043008000000000800100000c00020c8c5c002300660066006400630030003000200020002000480080eab58a02854f0070006f006c0045000100000c00020c8d5c002300660066006400630030003000200020002000200049008072d47a028b42006f006200610046003300740074003900300037000100000c00020c8c5c0023006600660064006300300030002000200020004e008000000000800100000c00020c8c5c00230066006600640063003000300020002000200041008000000000800100000c00020ca05c00230066006600300030003000300020007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c007c008000000000800100000c00020c8c5c0023003900360034003000660066002000200020004b008000000000800100000c00020c8c5c0023003900360034003000660066002000200020004f008042a28c028448006f00660044000100000c00020c8c5c0023003900360034003000660066002000200020004b008000000000800100000c0000"
|
||||
val string_mixed = hex"e80300812cd85000530046006f007200650076006500720020005000610063006b0065007400200043006f006c006c0065006300740069006f006e00841400000181306400800000000080000000000000220c808000000000800000000000001e0c808000000000800000000000001a0c80800000000080000000000000160c80800000000080000000000000120c808000000000800000000000000e0c808000000000800000000000000a0c80800000000080000000000000060c80800000000080000000000000020c80800000000080000000000003fc"
|
||||
|
||||
"SquadDetailDefinitionUpdateMessage" should {
|
||||
"decode (unk1 + members)" in {
|
||||
PacketCoding.DecodePacket(string_unk1).require match {
|
||||
case SquadDetailDefinitionUpdateMessage(guid, detail) =>
|
||||
guid mustEqual PlanetSideGUID(3)
|
||||
detail match {
|
||||
case SquadDetail(Some(unk1), None, Some(char_id), None, None, None, None, None, Some(_)) =>
|
||||
unk1 mustEqual 0
|
||||
char_id mustEqual 1221560L
|
||||
//members tests follow ...
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (char id)" in {
|
||||
PacketCoding.DecodePacket(string_leader_char_id).require match {
|
||||
case SquadDetailDefinitionUpdateMessage(guid, detail) =>
|
||||
guid mustEqual PlanetSideGUID(5)
|
||||
detail match {
|
||||
case SquadDetail(None, None, Some(char_id), None, None, None, None, None, None) =>
|
||||
char_id mustEqual 30910985
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
ok
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (unk3 + leader name)" in {
|
||||
PacketCoding.DecodePacket(string_unk3LeaderName).require match {
|
||||
case SquadDetailDefinitionUpdateMessage(guid, detail) =>
|
||||
guid mustEqual PlanetSideGUID(3)
|
||||
detail match {
|
||||
case SquadDetail(None, None, Some(char_id), Some(unk3), Some(leader), None, None, None, Some(_)) =>
|
||||
char_id mustEqual 42631712L
|
||||
unk3 mustEqual 556403L
|
||||
leader mustEqual "Jaako"
|
||||
//members tests follow ...
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (task)" in {
|
||||
PacketCoding.DecodePacket(string_task).require match {
|
||||
case SquadDetailDefinitionUpdateMessage(guid, detail) =>
|
||||
guid mustEqual PlanetSideGUID(5)
|
||||
detail match {
|
||||
case SquadDetail(None, None, None, None, None, Some(task), None, None, None) =>
|
||||
task mustEqual "All Welcome "
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
ok
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (zone)" in {
|
||||
PacketCoding.DecodePacket(string_zone).require match {
|
||||
case SquadDetailDefinitionUpdateMessage(guid, detail) =>
|
||||
guid mustEqual PlanetSideGUID(3)
|
||||
detail match {
|
||||
case SquadDetail(None, None, None, None, None, None, Some(zone), None, None) =>
|
||||
zone mustEqual PlanetSideZoneID(21)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
ok
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (task + zone)" in {
|
||||
PacketCoding.DecodePacket(string_taskZone).require match {
|
||||
case SquadDetailDefinitionUpdateMessage(_, detail) =>
|
||||
detail match {
|
||||
case SquadDetail(None, None, None, None, None, Some(task), Some(zone), None, None) =>
|
||||
task mustEqual "\\#FF0000 The \\#ffffff Blades"
|
||||
zone mustEqual PlanetSideZoneID(4)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
ok
|
||||
}
|
||||
|
||||
"decode (unk7 + members)" in {
|
||||
PacketCoding.DecodePacket(string_unk7).require match {
|
||||
case SquadDetailDefinitionUpdateMessage(guid, detail) =>
|
||||
guid mustEqual PlanetSideGUID(3)
|
||||
detail match {
|
||||
case SquadDetail(None, None, None, None, None, Some(task), None, Some(unk7), Some(_)) =>
|
||||
task mustEqual "The King's Squad"
|
||||
unk7 mustEqual 8
|
||||
//members tests follow ...
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (member closed)" in {
|
||||
PacketCoding.DecodePacket(string_member_closed).require match {
|
||||
case SquadDetailDefinitionUpdateMessage(guid, detail) =>
|
||||
guid mustEqual PlanetSideGUID(3)
|
||||
detail match {
|
||||
case SquadDetail(None, None, None, None, None, None, None, None, Some(members)) =>
|
||||
members.size mustEqual 1
|
||||
members.head.index mustEqual 5
|
||||
members.head.info match {
|
||||
case Some(SquadPositionDetail(Some(is_closed), None, None, None, None, None)) =>
|
||||
is_closed mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
ok
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (member role)" in {
|
||||
PacketCoding.DecodePacket(string_member_role).require match {
|
||||
case SquadDetailDefinitionUpdateMessage(guid, detail) =>
|
||||
guid mustEqual PlanetSideGUID(7)
|
||||
detail match {
|
||||
case SquadDetail(None, None, None, None, None, None, None, None, Some(members)) =>
|
||||
members.size mustEqual 1
|
||||
members.head.index mustEqual 0
|
||||
members.head.info match {
|
||||
case Some(SquadPositionDetail(None, Some(role), None, None, None, None)) =>
|
||||
role mustEqual "Commander"
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
ok
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (member role + requirements)" in {
|
||||
PacketCoding.DecodePacket(string_member_roleRequirements).require match {
|
||||
case SquadDetailDefinitionUpdateMessage(guid, detail) =>
|
||||
guid mustEqual PlanetSideGUID(1)
|
||||
detail match {
|
||||
case SquadDetail(None, None, None, None, None, None, None, None, Some(members)) =>
|
||||
members.size mustEqual 1
|
||||
members.head.index mustEqual 6
|
||||
members.head.info match {
|
||||
case Some(SquadPositionDetail(None, Some(role), None, Some(req), None, None)) =>
|
||||
role mustEqual "ADV Hacker"
|
||||
req.size mustEqual 1
|
||||
req.contains(CertificationType.AdvancedHacking) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
ok
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (member char id + name)" in {
|
||||
PacketCoding.DecodePacket(string_member_charIdName).require match {
|
||||
case SquadDetailDefinitionUpdateMessage(guid, detail) =>
|
||||
guid mustEqual PlanetSideGUID(3)
|
||||
detail match {
|
||||
case SquadDetail(None, None, None, None, None, None, None, None, Some(members)) =>
|
||||
members.size mustEqual 1
|
||||
members.head.index mustEqual 5
|
||||
members.head.info match {
|
||||
case Some(SquadPositionDetail(None, None, None, None, Some(char_id), Some(name))) =>
|
||||
char_id mustEqual 1218249L
|
||||
name mustEqual "Duckmaster43"
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
ok
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (task + member etc)" in {
|
||||
PacketCoding.DecodePacket(string_task_memberEtc).require match {
|
||||
case SquadDetailDefinitionUpdateMessage(guid, detail) =>
|
||||
guid mustEqual PlanetSideGUID(1)
|
||||
detail match {
|
||||
case SquadDetail(None, None, None, None, None, Some(task), None, None, Some(members)) =>
|
||||
task mustEqual "\\#FF0000 The \\#ffffff Blades"
|
||||
members.size mustEqual 10
|
||||
//
|
||||
members.head.index mustEqual 9
|
||||
members.head.info match {
|
||||
case Some(SquadPositionDetail(None, Some(role), None, Some(req), None, None)) =>
|
||||
role mustEqual ""
|
||||
req mustEqual Set.empty
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
//
|
||||
(1 to 9).foreach { index =>
|
||||
members(index).index mustEqual 9 - index
|
||||
members(index).info match {
|
||||
case Some(SquadPositionDetail(None, Some(role), None, None, None, None)) =>
|
||||
role mustEqual ""
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
ok
|
||||
}
|
||||
|
||||
"decode (full squad)" in {
|
||||
PacketCoding.DecodePacket(string_full).require match {
|
||||
case SquadDetailDefinitionUpdateMessage(guid, detail) =>
|
||||
guid mustEqual PlanetSideGUID(3)
|
||||
detail match {
|
||||
case SquadDetail(Some(u1), Some(u2), Some(char_id), Some(u3), Some(leader), Some(task), Some(zone), Some(unk7), Some(member_list)) =>
|
||||
u1 mustEqual 3
|
||||
u2 mustEqual 1792
|
||||
char_id mustEqual 42771010L
|
||||
u3 mustEqual 529745L
|
||||
leader mustEqual "HofD"
|
||||
task mustEqual "\\#ffdc00***\\#9640ff=KOK+SPC+FLY=\\#ffdc00***\\#FF4040 All Welcome"
|
||||
zone mustEqual PlanetSideZoneID(7)
|
||||
unk7 mustEqual 4983296
|
||||
member_list.size mustEqual 10
|
||||
member_list.head mustEqual SquadPositionEntry(0,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some("\\#ff0000 |||||||||||||||||||||||"),
|
||||
Some(""),
|
||||
Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
|
||||
Some(0),
|
||||
Some("")))
|
||||
)
|
||||
member_list(1) mustEqual SquadPositionEntry(1,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some("\\#ffdc00 C"),
|
||||
Some(""),
|
||||
Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
|
||||
Some(0),
|
||||
Some("")))
|
||||
)
|
||||
member_list(2) mustEqual SquadPositionEntry(2,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some("\\#ffdc00 H"),
|
||||
Some(""),
|
||||
Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
|
||||
Some(42644970L),
|
||||
Some("OpolE")
|
||||
)
|
||||
))
|
||||
member_list(3) mustEqual SquadPositionEntry(3,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some("\\#ffdc00 I"),
|
||||
Some(""),
|
||||
Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
|
||||
Some(41604210L),
|
||||
Some("BobaF3tt907")
|
||||
)
|
||||
))
|
||||
member_list(4) mustEqual SquadPositionEntry(4,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some("\\#ffdc00 N"),
|
||||
Some(""),
|
||||
Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
|
||||
Some(0),
|
||||
Some("")
|
||||
)
|
||||
))
|
||||
member_list(5) mustEqual SquadPositionEntry(5,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some("\\#ffdc00 A"),
|
||||
Some(""),
|
||||
Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
|
||||
Some(0),
|
||||
Some("")
|
||||
)
|
||||
))
|
||||
member_list(6) mustEqual SquadPositionEntry(6,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some("\\#ff0000 |||||||||||||||||||||||"),
|
||||
Some(""),
|
||||
Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
|
||||
Some(0),
|
||||
Some("")
|
||||
)
|
||||
))
|
||||
member_list(7) mustEqual SquadPositionEntry(7,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some("\\#9640ff K"),
|
||||
Some(""),
|
||||
Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
|
||||
Some(0),
|
||||
Some("")
|
||||
)
|
||||
))
|
||||
member_list(8) mustEqual SquadPositionEntry(8,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some("\\#9640ff O"),
|
||||
Some(""),
|
||||
Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
|
||||
Some(42771010L),
|
||||
Some("HofD")
|
||||
)
|
||||
))
|
||||
member_list(9) mustEqual SquadPositionEntry(9,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some("\\#9640ff K"),
|
||||
Some(""),
|
||||
Some(Set(CertificationType.StandardAssault, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit)),
|
||||
Some(0),
|
||||
Some("")
|
||||
)
|
||||
))
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (mixed)" in {
|
||||
PacketCoding.DecodePacket(string_mixed).require match {
|
||||
case SquadDetailDefinitionUpdateMessage(guid, detail) =>
|
||||
guid mustEqual PlanetSideGUID(3)
|
||||
detail match {
|
||||
case SquadDetail(None, None, None, None, None, Some(task), None, None, Some(member_list)) =>
|
||||
task mustEqual "PSForever Packet Collection"
|
||||
member_list.size mustEqual 10
|
||||
member_list.head mustEqual SquadPositionEntry(9,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some(""),
|
||||
Some(""),
|
||||
Some(Set.empty),
|
||||
Some(0),
|
||||
Some("")
|
||||
))
|
||||
)
|
||||
member_list(1) mustEqual SquadPositionEntry(8,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some(""),
|
||||
Some(""),
|
||||
Some(Set.empty),
|
||||
Some(0),
|
||||
Some("")
|
||||
))
|
||||
)
|
||||
member_list(2) mustEqual SquadPositionEntry(7,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some(""),
|
||||
Some(""),
|
||||
Some(Set.empty),
|
||||
Some(0),
|
||||
Some("")
|
||||
)
|
||||
))
|
||||
member_list(3) mustEqual SquadPositionEntry(6,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some(""),
|
||||
Some(""),
|
||||
Some(Set.empty),
|
||||
Some(0),
|
||||
Some("")
|
||||
)
|
||||
))
|
||||
member_list(4) mustEqual SquadPositionEntry(5,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some(""),
|
||||
Some(""),
|
||||
Some(Set.empty),
|
||||
Some(0),
|
||||
Some("")
|
||||
)
|
||||
))
|
||||
member_list(5) mustEqual SquadPositionEntry(4,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some(""),
|
||||
Some(""),
|
||||
Some(Set.empty),
|
||||
Some(0),
|
||||
Some("")
|
||||
)
|
||||
))
|
||||
member_list(6) mustEqual SquadPositionEntry(3,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some(""),
|
||||
Some(""),
|
||||
Some(Set.empty),
|
||||
Some(0),
|
||||
Some("")
|
||||
)
|
||||
))
|
||||
member_list(7) mustEqual SquadPositionEntry(2,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some(""),
|
||||
Some(""),
|
||||
Some(Set.empty),
|
||||
Some(0),
|
||||
Some("")
|
||||
)
|
||||
))
|
||||
member_list(8) mustEqual SquadPositionEntry(1,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some(""),
|
||||
Some(""),
|
||||
Some(Set.empty),
|
||||
Some(0),
|
||||
Some("")
|
||||
)
|
||||
))
|
||||
member_list(9) mustEqual SquadPositionEntry(0,Some(
|
||||
SquadPositionDetail(
|
||||
Some(false),
|
||||
Some(""),
|
||||
Some(""),
|
||||
Some(Set.empty),
|
||||
Some(0),
|
||||
Some("")
|
||||
)
|
||||
))
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
ok
|
||||
}
|
||||
|
||||
"encode (unk1 + members)" in {
|
||||
val msg = SquadDetailDefinitionUpdateMessage(
|
||||
PlanetSideGUID(3),
|
||||
SquadDetail()
|
||||
.Field1(0)
|
||||
.LeaderCharId(1221560L)
|
||||
.Members(List(
|
||||
SquadPositionEntry(6, SquadPositionDetail().Player(0L, ""))
|
||||
))
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string_unk1
|
||||
}
|
||||
|
||||
"encode (char id)" in {
|
||||
val msg = SquadDetailDefinitionUpdateMessage(
|
||||
PlanetSideGUID(5),
|
||||
SquadDetail().LeaderCharId(30910985L)
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string_leader_char_id
|
||||
}
|
||||
|
||||
"encode (unk3 + leader name)" in {
|
||||
val msg = SquadDetailDefinitionUpdateMessage(
|
||||
PlanetSideGUID(3),
|
||||
SquadDetail()
|
||||
.Leader(42631712L, "Jaako")
|
||||
.Field3(556403L)
|
||||
.Members(List(
|
||||
SquadPositionEntry(0, SquadPositionDetail().Player(0L, ""))
|
||||
))
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string_unk3LeaderName
|
||||
}
|
||||
|
||||
"encode (task)" in {
|
||||
val msg = SquadDetailDefinitionUpdateMessage(
|
||||
PlanetSideGUID(5),
|
||||
SquadDetail().Task("All Welcome ")
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string_task
|
||||
}
|
||||
|
||||
"encode (zone)" in {
|
||||
val msg = SquadDetailDefinitionUpdateMessage(
|
||||
PlanetSideGUID(3),
|
||||
SquadDetail().ZoneId(PlanetSideZoneID(21))
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string_zone
|
||||
}
|
||||
|
||||
"encode (task + zone)" in {
|
||||
val msg = SquadDetailDefinitionUpdateMessage(
|
||||
PlanetSideGUID(2),
|
||||
SquadDetail()
|
||||
.Task("\\#FF0000 The \\#ffffff Blades")
|
||||
.ZoneId(PlanetSideZoneID(4))
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string_taskZone
|
||||
}
|
||||
|
||||
"encode (unk7 + members)" in {
|
||||
val msg = SquadDetailDefinitionUpdateMessage(
|
||||
PlanetSideGUID(3),
|
||||
SquadDetail()
|
||||
.Task("The King's Squad")
|
||||
.Field7(8)
|
||||
.Members(List(
|
||||
SquadPositionEntry(9, SquadPositionDetail().Role("The Guard")),
|
||||
SquadPositionEntry(8, SquadPositionDetail().Role("The Knight")),
|
||||
SquadPositionEntry(7, SquadPositionDetail().Role("The Earl")),
|
||||
SquadPositionEntry(6, SquadPositionDetail().Role("The Lord")),
|
||||
SquadPositionEntry(5, SquadPositionDetail().Role("The Duke")),
|
||||
SquadPositionEntry(4, SquadPositionDetail().Role("The Baron")),
|
||||
SquadPositionEntry(3, SquadPositionDetail().Role("The Princess")),
|
||||
SquadPositionEntry(2, SquadPositionDetail().Role("The Prince")),
|
||||
SquadPositionEntry(1, SquadPositionDetail().Role("The Queen")),
|
||||
SquadPositionEntry(0, SquadPositionDetail().Role("The King"))
|
||||
))
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string_unk7
|
||||
}
|
||||
|
||||
"encode (member closed)" in {
|
||||
val msg = SquadDetailDefinitionUpdateMessage(
|
||||
PlanetSideGUID(3),
|
||||
SquadDetail()
|
||||
.Members(List(
|
||||
SquadPositionEntry(5, SquadPositionDetail.Closed)
|
||||
))
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string_member_closed
|
||||
}
|
||||
|
||||
|
||||
"encode (member role)" in {
|
||||
val msg = SquadDetailDefinitionUpdateMessage(
|
||||
PlanetSideGUID(7),
|
||||
SquadDetail()
|
||||
.Members(List(
|
||||
SquadPositionEntry(0, SquadPositionDetail().Role("Commander"))
|
||||
))
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string_member_role
|
||||
}
|
||||
|
||||
"encode (member role + requirements)" in {
|
||||
val msg = SquadDetailDefinitionUpdateMessage(
|
||||
PlanetSideGUID(1),
|
||||
SquadDetail()
|
||||
.Members(List(
|
||||
SquadPositionEntry(6, SquadPositionDetail()
|
||||
.Role("ADV Hacker")
|
||||
.Requirements(Set(CertificationType.AdvancedHacking)))
|
||||
))
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string_member_roleRequirements
|
||||
}
|
||||
|
||||
"encode (member char id + name)" in {
|
||||
val msg = SquadDetailDefinitionUpdateMessage(
|
||||
PlanetSideGUID(3),
|
||||
SquadDetail()
|
||||
.Members(List(
|
||||
SquadPositionEntry(5, SquadPositionDetail().Player(1218249L, "Duckmaster43"))
|
||||
))
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string_member_charIdName
|
||||
}
|
||||
|
||||
"encode (task + member etc)" in {
|
||||
val msg = SquadDetailDefinitionUpdateMessage(
|
||||
PlanetSideGUID(1),
|
||||
SquadDetail()
|
||||
.Task("\\#FF0000 The \\#ffffff Blades")
|
||||
.Members(List(
|
||||
SquadPositionEntry(9, SquadPositionDetail().Role("").Requirements(Set())),
|
||||
SquadPositionEntry(8, SquadPositionDetail().Role("")),
|
||||
SquadPositionEntry(7, SquadPositionDetail().Role("")),
|
||||
SquadPositionEntry(6, SquadPositionDetail().Role("")),
|
||||
SquadPositionEntry(5, SquadPositionDetail().Role("")),
|
||||
SquadPositionEntry(4, SquadPositionDetail().Role("")),
|
||||
SquadPositionEntry(3, SquadPositionDetail().Role("")),
|
||||
SquadPositionEntry(2, SquadPositionDetail().Role("")),
|
||||
SquadPositionEntry(1, SquadPositionDetail().Role("")),
|
||||
SquadPositionEntry(0, SquadPositionDetail().Role(""))
|
||||
))
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string_task_memberEtc
|
||||
}
|
||||
|
||||
"encode (full squad)" in {
|
||||
val msg = SquadDetailDefinitionUpdateMessage(
|
||||
PlanetSideGUID(3),
|
||||
SquadDetail(
|
||||
3,
|
||||
1792,
|
||||
42771010L,
|
||||
529745L,
|
||||
"HofD",
|
||||
"\\#ffdc00***\\#9640ff=KOK+SPC+FLY=\\#ffdc00***\\#FF4040 All Welcome",
|
||||
PlanetSideZoneID(7),
|
||||
4983296,
|
||||
List(
|
||||
SquadPositionEntry(0, SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", "", Set(), 0, "")),
|
||||
SquadPositionEntry(1, SquadPositionDetail("\\#ffdc00 C", "", Set(), 0, "")),
|
||||
SquadPositionEntry(2, SquadPositionDetail("\\#ffdc00 H", "", Set(), 42644970L, "OpolE")),
|
||||
SquadPositionEntry(3, SquadPositionDetail("\\#ffdc00 I", "", Set(), 41604210L, "BobaF3tt907")),
|
||||
SquadPositionEntry(4, SquadPositionDetail("\\#ffdc00 N", "", Set(), 0, "")),
|
||||
SquadPositionEntry(5, SquadPositionDetail("\\#ffdc00 A", "", Set(), 0, "")),
|
||||
SquadPositionEntry(6, SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", "", Set(), 0, "")),
|
||||
SquadPositionEntry(7, SquadPositionDetail("\\#9640ff K", "", Set(), 0, "")),
|
||||
SquadPositionEntry(8, SquadPositionDetail("\\#9640ff O", "", Set(), 42771010L ,"HofD")),
|
||||
SquadPositionEntry(9, SquadPositionDetail("\\#9640ff K", "", Set(), 0, ""))
|
||||
)
|
||||
)
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string_full
|
||||
}
|
||||
|
||||
"encode (mixed)" in {
|
||||
val position = Some(SquadPositionDetail("", "", Set(), 0, ""))
|
||||
val msg = SquadDetailDefinitionUpdateMessage(
|
||||
PlanetSideGUID(3),
|
||||
SquadDetail
|
||||
.Task("PSForever Packet Collection")
|
||||
.Members((0 to 9).map { index => SquadPositionEntry(index, position) }.reverse.toList)
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string_mixed
|
||||
}
|
||||
}
|
||||
}
|
||||
32
common/src/test/scala/game/SquadMemberEventTest.scala
Normal file
32
common/src/test/scala/game/SquadMemberEventTest.scala
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package game
|
||||
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game._
|
||||
import org.specs2.mutable._
|
||||
import scodec.bits._
|
||||
|
||||
class SquadMemberEventTest extends Specification {
|
||||
val string = hex"7000e008545180410848006f0066004400070051150800"
|
||||
|
||||
"decode" in {
|
||||
PacketCoding.DecodePacket(string).require match {
|
||||
case SquadMemberEvent(u1, u2, u3, u4, u5, u6, u7) =>
|
||||
u1 mustEqual MemberEvent.Add
|
||||
u2 mustEqual 7
|
||||
u3 mustEqual 42771010L
|
||||
u4 mustEqual 0
|
||||
u5.contains("HofD") mustEqual true
|
||||
u6.contains(7) mustEqual true
|
||||
u7.contains(529745L) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
val msg = SquadMemberEvent(MemberEvent.Add, 7, 42771010L, 0, Some("HofD"), Some(7), Some(529745L))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string
|
||||
}
|
||||
}
|
||||
67
common/src/test/scala/game/SquadMembershipRequestTest.scala
Normal file
67
common/src/test/scala/game/SquadMembershipRequestTest.scala
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package game
|
||||
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.SquadRequestType
|
||||
import org.specs2.mutable._
|
||||
import scodec.bits._
|
||||
|
||||
class SquadMembershipRequestTest extends Specification {
|
||||
//481c897e-b47b-41cc-b7ad-c604606d985e / PSCap-2016-03-18_12-48-12-PM.gcap / Game record 5521 at 662.786844s
|
||||
val string1 = hex"6e015aa7a0224d87a0280000"
|
||||
//... / PSCap-2016-06-29_07-49-26-PM (last).gcap / Game record 77 at 9.732430 (found in MultiPacket)
|
||||
val string2 = hex"6E265DD7A02800"
|
||||
//TODO find example where player_name field is defined
|
||||
//TODO find example where unk field is defined and is a string
|
||||
|
||||
"decode (1)" in {
|
||||
PacketCoding.DecodePacket(string1).require match {
|
||||
case SquadMembershipRequest(req_type, unk2, unk3, p_name, unk5) =>
|
||||
req_type mustEqual SquadRequestType.Invite
|
||||
unk2 mustEqual 41593365L
|
||||
unk3.contains(41605156L) mustEqual true
|
||||
p_name mustEqual ""
|
||||
unk5.contains(None) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (2)" in {
|
||||
PacketCoding.DecodePacket(string2).require match {
|
||||
case SquadMembershipRequest(req_type, unk2, unk3, p_name, unk5) =>
|
||||
req_type mustEqual SquadRequestType.Accept
|
||||
unk2 mustEqual 41606501L
|
||||
unk3.isEmpty mustEqual true
|
||||
p_name mustEqual ""
|
||||
unk5.isEmpty mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode (1)" in {
|
||||
val msg = SquadMembershipRequest(SquadRequestType.Invite, 41593365, Some(41605156L), "", Some(None))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string1
|
||||
}
|
||||
|
||||
"encode (1; failure 1)" in {
|
||||
SquadMembershipRequest(SquadRequestType.Invite, 41593365, None, "", Some(None)) must throwA[AssertionError]
|
||||
}
|
||||
|
||||
"encode (1; failure 2)" in {
|
||||
SquadMembershipRequest(SquadRequestType.Invite, 41593365, Some(41605156L), "", None) must throwA[AssertionError]
|
||||
}
|
||||
|
||||
"encode (2)" in {
|
||||
val msg = SquadMembershipRequest(SquadRequestType.Accept, 41606501, None, "", None)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string2
|
||||
}
|
||||
|
||||
"encode (2; failure)" in {
|
||||
SquadMembershipRequest(SquadRequestType.Accept, 41606501, Some(41606501), "", None) must throwA[AssertionError]
|
||||
}
|
||||
}
|
||||
421
common/src/test/scala/game/SquadMembershipResponseTest.scala
Normal file
421
common/src/test/scala/game/SquadMembershipResponseTest.scala
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package game
|
||||
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.SquadResponseType
|
||||
import org.specs2.mutable._
|
||||
import scodec.bits._
|
||||
|
||||
class SquadMembershipResponseTest extends Specification {
|
||||
val string_01 = hex"6f0 00854518050db2260108048006f006600440000"
|
||||
val string_02 = hex"6f0 0049e8220112aa1e01100530050004f0049004c0045005200530080"
|
||||
val string_11 = hex"6f1 995364f2040000000100080"
|
||||
val string_12 = hex"6f1 90cadcf4040000000100080"
|
||||
val string_21 = hex"6f2 010db2260085451805140560069007200750073004700690076006500720080"
|
||||
val string_22 = hex"6f2 010db22601da03aa03140560069007200750073004700690076006500720080"
|
||||
val string_31 = hex"6f3 07631db202854518050a048004d0046004900430000"
|
||||
val string_32 = hex"6f3 04c34fb402854518050e0440041004e00310031003100310000"
|
||||
val string_41 = hex"6f4 04cadcf405bbbef405140530041007200610069007300560061006e00750000"
|
||||
val string_42 = hex"6f4 05c9c0f405d71aec0516041006900720049006e006a006500630074006f00720000"
|
||||
val string_51 = hex"6f5 0249e8220049e822010e0430043005200490044004500520080"
|
||||
val string_71 = hex"6f7 1049e822000000000100080"
|
||||
val string_72 = hex"6f7 00cadcf4041355ae03100570069007a006b00690064003400350080"
|
||||
val string_81 = hex"6f8 001355ae02cadcf405100570069007a006b00690064003400350000"
|
||||
val string_91 = hex"6f9 008310080115aef40500080"
|
||||
val string_92 = hex"6f9 001355ae02cadcf405100570069007a006b00690064003400350000"
|
||||
val string_b1 = hex"6fb 021355ae02cadcf405140530041007200610069007300560061006e00750000"
|
||||
|
||||
"SquadMembershipResponse" should {
|
||||
"decode (0-1)" in {
|
||||
PacketCoding.DecodePacket(string_01).require match {
|
||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||
unk1 mustEqual SquadResponseType.Invite
|
||||
unk2 mustEqual 0
|
||||
unk3 mustEqual 0
|
||||
unk4 mustEqual 42771010L
|
||||
unk5.contains(1300870L) mustEqual true
|
||||
unk6 mustEqual "HofD"
|
||||
unk7 mustEqual false
|
||||
unk8.isEmpty mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (0-2)" in {
|
||||
PacketCoding.DecodePacket(string_02).require match {
|
||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||
unk1 mustEqual SquadResponseType.Invite
|
||||
unk2 mustEqual 0
|
||||
unk3 mustEqual 0
|
||||
unk4 mustEqual 1176612L
|
||||
unk5.contains(1004937L) mustEqual true
|
||||
unk6 mustEqual "SPOILERS"
|
||||
unk7 mustEqual true
|
||||
unk8.isEmpty mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (1-1)" in {
|
||||
PacketCoding.DecodePacket(string_11).require match {
|
||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||
unk1 mustEqual SquadResponseType.Unk01
|
||||
unk2 mustEqual 19
|
||||
unk3 mustEqual 0
|
||||
unk4 mustEqual 41530025L
|
||||
unk5.contains(0L) mustEqual true
|
||||
unk6 mustEqual ""
|
||||
unk7 mustEqual true
|
||||
unk8.contains(None) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (1-2)" in {
|
||||
PacketCoding.DecodePacket(string_12).require match {
|
||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||
unk1 mustEqual SquadResponseType.Unk01
|
||||
unk2 mustEqual 18
|
||||
unk3 mustEqual 0
|
||||
unk4 mustEqual 41578085L
|
||||
unk5.contains(0L) mustEqual true
|
||||
unk6 mustEqual ""
|
||||
unk7 mustEqual true
|
||||
unk8.contains(None) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (2-1)" in {
|
||||
PacketCoding.DecodePacket(string_21).require match {
|
||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||
unk1 mustEqual SquadResponseType.Accept
|
||||
unk2 mustEqual 0
|
||||
unk3 mustEqual 0
|
||||
unk4 mustEqual 1300870L
|
||||
unk5.contains(42771010L) mustEqual true
|
||||
unk6 mustEqual "VirusGiver"
|
||||
unk7 mustEqual true
|
||||
unk8.contains(None) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (2-2)" in {
|
||||
PacketCoding.DecodePacket(string_22).require match {
|
||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||
unk1 mustEqual SquadResponseType.Accept
|
||||
unk2 mustEqual 0
|
||||
unk3 mustEqual 0
|
||||
unk4 mustEqual 1300870L
|
||||
unk5.contains(30736877L) mustEqual true
|
||||
unk6 mustEqual "VirusGiver"
|
||||
unk7 mustEqual true
|
||||
unk8.contains(None) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (3-1)" in {
|
||||
PacketCoding.DecodePacket(string_31).require match {
|
||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||
unk1 mustEqual SquadResponseType.Reject
|
||||
unk2 mustEqual 0
|
||||
unk3 mustEqual 3
|
||||
unk4 mustEqual 31035057L
|
||||
unk5.contains(42771010L) mustEqual true
|
||||
unk6 mustEqual "HMFIC"
|
||||
unk7 mustEqual false
|
||||
unk8.contains(None) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (3-2)" in {
|
||||
PacketCoding.DecodePacket(string_32).require match {
|
||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||
unk1 mustEqual SquadResponseType.Reject
|
||||
unk2 mustEqual 0
|
||||
unk3 mustEqual 2
|
||||
unk4 mustEqual 31106913L
|
||||
unk5.contains(42771010L) mustEqual true
|
||||
unk6 mustEqual "DAN1111"
|
||||
unk7 mustEqual false
|
||||
unk8.contains(None) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (4-1)" in {
|
||||
PacketCoding.DecodePacket(string_41).require match {
|
||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||
unk1 mustEqual SquadResponseType.Cancel
|
||||
unk2 mustEqual 0
|
||||
unk3 mustEqual 2
|
||||
unk4 mustEqual 41578085L
|
||||
unk5.contains(41607133L) mustEqual true
|
||||
unk6 mustEqual "SAraisVanu"
|
||||
unk7 mustEqual false
|
||||
unk8.contains(None) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (4-2)" in {
|
||||
PacketCoding.DecodePacket(string_42).require match {
|
||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||
unk1 mustEqual SquadResponseType.Cancel
|
||||
unk2 mustEqual 0
|
||||
unk3 mustEqual 2
|
||||
unk4 mustEqual 41607396L
|
||||
unk5.contains(41324011L) mustEqual true
|
||||
unk6 mustEqual "AirInjector"
|
||||
unk7 mustEqual false
|
||||
unk8.contains(None) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (5-1)" in {
|
||||
PacketCoding.DecodePacket(string_51).require match {
|
||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||
unk1 mustEqual SquadResponseType.Leave
|
||||
unk2 mustEqual 0
|
||||
unk3 mustEqual 1
|
||||
unk4 mustEqual 1176612L
|
||||
unk5.contains(1176612L) mustEqual true
|
||||
unk6 mustEqual "CCRIDER"
|
||||
unk7 mustEqual true
|
||||
unk8.contains(None) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (7-1)" in {
|
||||
PacketCoding.DecodePacket(string_71).require match {
|
||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||
unk1 mustEqual SquadResponseType.PlatoonInvite
|
||||
unk2 mustEqual 2
|
||||
unk3 mustEqual 0
|
||||
unk4 mustEqual 1176612L
|
||||
unk5.contains(0L) mustEqual true
|
||||
unk6 mustEqual ""
|
||||
unk7 mustEqual true
|
||||
unk8.contains(None) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (7-2)" in {
|
||||
PacketCoding.DecodePacket(string_72).require match {
|
||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||
unk1 mustEqual SquadResponseType.PlatoonInvite
|
||||
unk2 mustEqual 0
|
||||
unk3 mustEqual 0
|
||||
unk4 mustEqual 41578085L
|
||||
unk5.contains(30910985L) mustEqual true
|
||||
unk6 mustEqual "Wizkid45"
|
||||
unk7 mustEqual true
|
||||
unk8.contains(None) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (8-1)" in {
|
||||
PacketCoding.DecodePacket(string_81).require match {
|
||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||
unk1 mustEqual SquadResponseType.PlatoonAccept
|
||||
unk2 mustEqual 0
|
||||
unk3 mustEqual 0
|
||||
unk4 mustEqual 30910985L
|
||||
unk5.contains(41578085L) mustEqual true
|
||||
unk6 mustEqual "Wizkid45"
|
||||
unk7 mustEqual false
|
||||
unk8.contains(None) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (9-1)" in {
|
||||
PacketCoding.DecodePacket(string_91).require match {
|
||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||
unk1 mustEqual SquadResponseType.PlatoonReject
|
||||
unk2 mustEqual 0
|
||||
unk3 mustEqual 0
|
||||
unk4 mustEqual 297025L
|
||||
unk5.contains(41605002L) mustEqual true
|
||||
unk6 mustEqual ""
|
||||
unk7 mustEqual true
|
||||
unk8.contains(None) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (9-2)" in {
|
||||
PacketCoding.DecodePacket(string_92).require match {
|
||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||
unk1 mustEqual SquadResponseType.PlatoonReject
|
||||
unk2 mustEqual 0
|
||||
unk3 mustEqual 0
|
||||
unk4 mustEqual 30910985L
|
||||
unk5.contains(41578085L) mustEqual true
|
||||
unk6 mustEqual "Wizkid45"
|
||||
unk7 mustEqual false
|
||||
unk8.contains(None) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (b-1)" in {
|
||||
PacketCoding.DecodePacket(string_b1).require match {
|
||||
case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
|
||||
unk1 mustEqual SquadResponseType.PlatoonLeave
|
||||
unk2 mustEqual 0
|
||||
unk3 mustEqual 1
|
||||
unk4 mustEqual 30910985L
|
||||
unk5.contains(41578085L) mustEqual true
|
||||
unk6 mustEqual "SAraisVanu"
|
||||
unk7 mustEqual false
|
||||
unk8.contains(None) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode (0-1)" in {
|
||||
val msg = SquadMembershipResponse(SquadResponseType.Invite, 0, 0, 42771010L, Some(1300870L), "HofD", false, None)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_01
|
||||
}
|
||||
|
||||
"encode (0-2)" in {
|
||||
val msg = SquadMembershipResponse(SquadResponseType.Invite, 0, 0, 1176612L, Some(1004937L), "SPOILERS", true, None)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_02
|
||||
}
|
||||
|
||||
"encode (1-1)" in {
|
||||
val msg = SquadMembershipResponse(SquadResponseType.Unk01, 19, 0, 41530025L, Some(0L), "", true, Some(None))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_11
|
||||
}
|
||||
|
||||
"encode (1-2)" in {
|
||||
val msg = SquadMembershipResponse(SquadResponseType.Unk01, 18, 0, 41578085L, Some(0L), "", true, Some(None))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_12
|
||||
}
|
||||
|
||||
"encode (2-1)" in {
|
||||
val msg = SquadMembershipResponse(SquadResponseType.Accept, 0, 0, 1300870L, Some(42771010L), "VirusGiver", true, Some(None))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_21
|
||||
}
|
||||
|
||||
"encode (2-2)" in {
|
||||
val msg = SquadMembershipResponse(SquadResponseType.Accept, 0, 0, 1300870L, Some(30736877L), "VirusGiver", true, Some(None))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_22
|
||||
}
|
||||
|
||||
"encode (3-1)" in {
|
||||
val msg = SquadMembershipResponse(SquadResponseType.Reject, 0, 3, 31035057L, Some(42771010L), "HMFIC", false, Some(None))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_31
|
||||
}
|
||||
|
||||
"encode (3-2)" in {
|
||||
val msg = SquadMembershipResponse(SquadResponseType.Reject, 0, 2, 31106913L, Some(42771010L), "DAN1111", false, Some(None))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_32
|
||||
}
|
||||
|
||||
"encode (4-1)" in {
|
||||
val msg = SquadMembershipResponse(SquadResponseType.Cancel, 0, 2, 41578085L, Some(41607133L), "SAraisVanu", false, Some(None))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_41
|
||||
}
|
||||
|
||||
"encode (4-2)" in {
|
||||
val msg = SquadMembershipResponse(SquadResponseType.Cancel, 0, 2, 41607396L, Some(41324011L), "AirInjector", false, Some(None))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_42
|
||||
}
|
||||
|
||||
"encode (5-1)" in {
|
||||
val msg = SquadMembershipResponse(SquadResponseType.Leave, 0, 1, 1176612L, Some(1176612L), "CCRIDER", true, Some(None))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_51
|
||||
}
|
||||
|
||||
"encode (7-1)" in {
|
||||
val msg = SquadMembershipResponse(SquadResponseType.PlatoonInvite, 2, 0, 1176612L, Some(0L), "", true, Some(None))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_71
|
||||
}
|
||||
|
||||
"encode (7-2)" in {
|
||||
val msg = SquadMembershipResponse(SquadResponseType.PlatoonInvite, 0, 0, 41578085L, Some(30910985L), "Wizkid45", true, Some(None))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_72
|
||||
}
|
||||
|
||||
"encode (8-1)" in {
|
||||
val msg = SquadMembershipResponse(SquadResponseType.PlatoonAccept, 0, 0, 30910985L, Some(41578085L), "Wizkid45", false, Some(None))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_81
|
||||
}
|
||||
|
||||
"encode (9-1)" in {
|
||||
val msg = SquadMembershipResponse(SquadResponseType.PlatoonReject, 0, 0, 297025L, Some(41605002L), "", true, Some(None))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_91
|
||||
}
|
||||
|
||||
"encode (9-2)" in {
|
||||
val msg = SquadMembershipResponse(SquadResponseType.PlatoonReject, 0, 0, 30910985L, Some(41578085L), "Wizkid45", false, Some(None))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_92
|
||||
}
|
||||
|
||||
"encode (b-1)" in {
|
||||
val msg = SquadMembershipResponse(SquadResponseType.PlatoonLeave, 0, 1, 30910985L, Some(41578085L), "SAraisVanu", false, Some(None))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_b1
|
||||
}
|
||||
}
|
||||
}
|
||||
244
common/src/test/scala/game/SquadStateTest.scala
Normal file
244
common/src/test/scala/game/SquadStateTest.scala
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package game
|
||||
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.Vector3
|
||||
import org.specs2.mutable._
|
||||
import scodec.bits._
|
||||
|
||||
class SquadStateTest extends Specification {
|
||||
val string1 = hex"770700186d9130081001b11b27c1c041680000"
|
||||
val string2 = hex"770700242a28c020003e9237a90e3382695004eab58a0281017eb95613df4c42950040"
|
||||
val stringx = hex"7704008dd9ccf010042a9837310e1b82a8c006646c7a028103984f34759c904a800014f01c26f3d014081ddd3896931bc25478037680ea80c081d699a147b01e154000031c0bc81407e08c1a3a890de1542c022070bd0140815958bf29efa6214300108023c01000ae491ac68d1a61342c023623c50140011d6ea0878f3026a00009e014"
|
||||
|
||||
"decode (1)" in {
|
||||
PacketCoding.DecodePacket(string1).require match {
|
||||
case SquadState(guid, list) =>
|
||||
guid mustEqual PlanetSideGUID(7)
|
||||
list.size mustEqual 1
|
||||
//0
|
||||
list.head match {
|
||||
case SquadStateInfo(char_id, u2, u3, pos, u4, u5, u6, u7, u8, u9) =>
|
||||
char_id mustEqual 1300870L
|
||||
u2 mustEqual 64
|
||||
u3 mustEqual 64
|
||||
pos mustEqual Vector3(3464.0469f, 4065.5703f, 20.015625f)
|
||||
u4 mustEqual 2
|
||||
u5 mustEqual 2
|
||||
u6 mustEqual false
|
||||
u7 mustEqual 0
|
||||
u8.isEmpty mustEqual true
|
||||
u9.isEmpty mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (2)" in {
|
||||
PacketCoding.DecodePacket(string2).require match {
|
||||
case SquadState(guid, list) =>
|
||||
guid mustEqual PlanetSideGUID(7)
|
||||
list.size mustEqual 2
|
||||
//0
|
||||
list.head match {
|
||||
case SquadStateInfo(char_id, u2, u3, pos, u4, u5, u6, u7, u8, u9) =>
|
||||
char_id mustEqual 42771010L
|
||||
u2 mustEqual 0
|
||||
u3 mustEqual 0
|
||||
pos mustEqual Vector3(6801.953f, 4231.828f, 39.21875f)
|
||||
u4 mustEqual 2
|
||||
u5 mustEqual 2
|
||||
u6 mustEqual false
|
||||
u7 mustEqual 680
|
||||
u8.isEmpty mustEqual true
|
||||
u9.isEmpty mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
list(1) match {
|
||||
case SquadStateInfo(char_id, u2, u3, pos, u4, u5, u6, u7, u8, u9) =>
|
||||
char_id mustEqual 42644970L
|
||||
u2 mustEqual 64
|
||||
u3 mustEqual 64
|
||||
pos mustEqual Vector3(2908.7422f, 3742.6875f, 67.296875f)
|
||||
u4 mustEqual 2
|
||||
u5 mustEqual 2
|
||||
u6 mustEqual false
|
||||
u7 mustEqual 680
|
||||
u8.isEmpty mustEqual true
|
||||
u9.isEmpty mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (8)" in {
|
||||
PacketCoding.DecodePacket(stringx).require match {
|
||||
case SquadState(guid, list) =>
|
||||
guid mustEqual PlanetSideGUID(4)
|
||||
list.size mustEqual 8
|
||||
//0
|
||||
list.head match {
|
||||
case SquadStateInfo(char_id, u2, u3, pos, u4, u5, u6, u7, u8, u9) =>
|
||||
char_id mustEqual 30383325L
|
||||
u2 mustEqual 0
|
||||
u3 mustEqual 16
|
||||
pos mustEqual Vector3(6849.328f, 4231.5938f, 41.71875f)
|
||||
u4 mustEqual 2
|
||||
u5 mustEqual 2
|
||||
u6 mustEqual false
|
||||
u7 mustEqual 864
|
||||
u8.isEmpty mustEqual true
|
||||
u9.isEmpty mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
list(1) match {
|
||||
case SquadStateInfo(char_id, u2, u3, pos, u4, u5, u6, u7, u8, u9) =>
|
||||
char_id mustEqual 41577572L
|
||||
u2 mustEqual 64
|
||||
u3 mustEqual 64
|
||||
pos mustEqual Vector3(6183.797f, 4013.6328f, 72.5625f)
|
||||
u4 mustEqual 2
|
||||
u5 mustEqual 2
|
||||
u6 mustEqual false
|
||||
u7 mustEqual 0
|
||||
u8.contains(335) mustEqual true
|
||||
u9.contains(true) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
list(2) match {
|
||||
case SquadStateInfo(char_id, u2, u3, pos, u4, u5, u6, u7, u8, u9) =>
|
||||
char_id mustEqual 41606788L
|
||||
u2 mustEqual 64
|
||||
u3 mustEqual 64
|
||||
pos mustEqual Vector3(6611.8594f, 4242.586f, 75.46875f)
|
||||
u4 mustEqual 2
|
||||
u5 mustEqual 2
|
||||
u6 mustEqual false
|
||||
u7 mustEqual 888
|
||||
u8.isEmpty mustEqual true
|
||||
u9.isEmpty mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
list(3) match {
|
||||
case SquadStateInfo(char_id, u2, u3, pos, u4, u5, u6, u7, u8, u9) =>
|
||||
char_id mustEqual 30736877L
|
||||
u2 mustEqual 64
|
||||
u3 mustEqual 64
|
||||
pos mustEqual Vector3(6809.836f, 4218.078f, 40.234375f)
|
||||
u4 mustEqual 2
|
||||
u5 mustEqual 2
|
||||
u6 mustEqual false
|
||||
u7 mustEqual 0
|
||||
u8.isEmpty mustEqual true
|
||||
u9.isEmpty mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
list(4) match {
|
||||
case SquadStateInfo(char_id, u2, u3, pos, u4, u5, u6, u7, u8, u9) =>
|
||||
char_id mustEqual 41517411L
|
||||
u2 mustEqual 64
|
||||
u3 mustEqual 63
|
||||
pos mustEqual Vector3(6848.0312f, 4232.2266f, 41.734375f)
|
||||
u4 mustEqual 2
|
||||
u5 mustEqual 2
|
||||
u6 mustEqual false
|
||||
u7 mustEqual 556
|
||||
u8.isEmpty mustEqual true
|
||||
u9.isEmpty mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
list(5) match {
|
||||
case SquadStateInfo(char_id, u2, u3, pos, u4, u5, u6, u7, u8, u9) =>
|
||||
char_id mustEqual 41607488L
|
||||
u2 mustEqual 64
|
||||
u3 mustEqual 64
|
||||
pos mustEqual Vector3(2905.3438f, 3743.9453f, 67.296875f)
|
||||
u4 mustEqual 2
|
||||
u5 mustEqual 2
|
||||
u6 mustEqual false
|
||||
u7 mustEqual 304
|
||||
u8.isEmpty mustEqual true
|
||||
u9.isEmpty mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
list(6) match {
|
||||
case SquadStateInfo(char_id, u2, u3, pos, u4, u5, u6, u7, u8, u9) =>
|
||||
char_id mustEqual 41419792L
|
||||
u2 mustEqual 0
|
||||
u3 mustEqual 5
|
||||
pos mustEqual Vector3(6800.8906f ,4236.7734f, 39.296875f)
|
||||
u4 mustEqual 2
|
||||
u5 mustEqual 2
|
||||
u6 mustEqual false
|
||||
u7 mustEqual 556
|
||||
u8.isEmpty mustEqual true
|
||||
u9.isEmpty mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
list(7) match {
|
||||
case SquadStateInfo(char_id, u2, u3, pos, u4, u5, u6, u7, u8, u9) =>
|
||||
char_id mustEqual 42616684L
|
||||
u2 mustEqual 64
|
||||
u3 mustEqual 0
|
||||
pos mustEqual Vector3(2927.1094f, 3704.0312f, 78.375f)
|
||||
u4 mustEqual 1
|
||||
u5 mustEqual 1
|
||||
u6 mustEqual false
|
||||
u7 mustEqual 0
|
||||
u8.contains(572) mustEqual true
|
||||
u9.contains(true) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode (1)" in {
|
||||
val msg = SquadState(PlanetSideGUID(7), List(
|
||||
SquadStateInfo(1300870L, 64, 64, Vector3(3464.0469f, 4065.5703f, 20.015625f), 2, 2, false, 0)
|
||||
))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string1
|
||||
}
|
||||
|
||||
"encode (2)" in {
|
||||
val msg = SquadState(PlanetSideGUID(7), List(
|
||||
SquadStateInfo(42771010L, 0, 0, Vector3(6801.953f, 4231.828f, 39.21875f), 2, 2, false, 680),
|
||||
SquadStateInfo(42644970L, 64, 64, Vector3(2908.7422f, 3742.6875f, 67.296875f), 2, 2, false, 680)
|
||||
))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string2
|
||||
}
|
||||
|
||||
"encode (8)" in {
|
||||
val msg = SquadState(PlanetSideGUID(4), List(
|
||||
SquadStateInfo(30383325L, 0, 16, Vector3(6849.328f, 4231.5938f, 41.71875f), 2, 2, false, 864),
|
||||
SquadStateInfo(41577572L, 64, 64, Vector3(6183.797f, 4013.6328f, 72.5625f), 2, 2, false, 0, 335, true),
|
||||
SquadStateInfo(41606788L, 64, 64, Vector3(6611.8594f, 4242.586f, 75.46875f), 2, 2, false, 888),
|
||||
SquadStateInfo(30736877L, 64, 64, Vector3(6809.836f, 4218.078f, 40.234375f), 2, 2, false, 0),
|
||||
SquadStateInfo(41517411L, 64, 63, Vector3(6848.0312f, 4232.2266f, 41.734375f), 2, 2, false, 556),
|
||||
SquadStateInfo(41607488L, 64, 64, Vector3(2905.3438f, 3743.9453f, 67.296875f), 2, 2, false, 304),
|
||||
SquadStateInfo(41419792L, 0, 5, Vector3(6800.8906f, 4236.7734f, 39.296875f), 2, 2, false, 556),
|
||||
SquadStateInfo(42616684L, 64, 0, Vector3(2927.1094f, 3704.0312f, 78.375f), 1, 1, false, 0, 572, true)
|
||||
))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
pkt mustEqual stringx
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,8 @@ package game
|
|||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game.{SquadWaypointEvent, WaypointEvent}
|
||||
import net.psforever.types.Vector3
|
||||
import net.psforever.packet.game.{SquadWaypointEvent, WaypointEvent, WaypointEventAction}
|
||||
import net.psforever.types.{SquadWaypoints, Vector3}
|
||||
import scodec.bits._
|
||||
|
||||
class SquadWaypointEventTest extends Specification {
|
||||
|
|
@ -16,12 +16,12 @@ class SquadWaypointEventTest extends Specification {
|
|||
"decode (1)" in {
|
||||
PacketCoding.DecodePacket(string_1).require match {
|
||||
case SquadWaypointEvent(unk1, unk2, unk3, unk4, unk5, unk6) =>
|
||||
unk1 mustEqual 2
|
||||
unk1 mustEqual WaypointEventAction.Remove
|
||||
unk2 mustEqual 11
|
||||
unk3 mustEqual 31155863L
|
||||
unk4 mustEqual 0
|
||||
unk5 mustEqual None
|
||||
unk6 mustEqual None
|
||||
unk4 mustEqual SquadWaypoints.One
|
||||
unk5.isEmpty mustEqual true
|
||||
unk6.isEmpty mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -30,12 +30,12 @@ class SquadWaypointEventTest extends Specification {
|
|||
"decode (2)" in {
|
||||
PacketCoding.DecodePacket(string_2).require match {
|
||||
case SquadWaypointEvent(unk1, unk2, unk3, unk4, unk5, unk6) =>
|
||||
unk1 mustEqual 2
|
||||
unk1 mustEqual WaypointEventAction.Remove
|
||||
unk2 mustEqual 10
|
||||
unk3 mustEqual 0L
|
||||
unk4 mustEqual 4
|
||||
unk5 mustEqual None
|
||||
unk6 mustEqual None
|
||||
unk4 mustEqual SquadWaypoints.ExperienceRally
|
||||
unk5.isEmpty mustEqual true
|
||||
unk6.isEmpty mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -44,12 +44,12 @@ class SquadWaypointEventTest extends Specification {
|
|||
"decode (3)" in {
|
||||
PacketCoding.DecodePacket(string_3).require match {
|
||||
case SquadWaypointEvent(unk1, unk2, unk3, unk4, unk5, unk6) =>
|
||||
unk1 mustEqual 0
|
||||
unk1 mustEqual WaypointEventAction.Add
|
||||
unk2 mustEqual 3
|
||||
unk3 mustEqual 41581052L
|
||||
unk4 mustEqual 1
|
||||
unk5 mustEqual None
|
||||
unk6 mustEqual Some(WaypointEvent(10, Vector3(3457.9688f, 5514.4688f, 0.0f), 1))
|
||||
unk4 mustEqual SquadWaypoints.Two
|
||||
unk5.isEmpty mustEqual true
|
||||
unk6.contains( WaypointEvent(10, Vector3(3457.9688f, 5514.4688f, 0.0f), 1) ) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -58,40 +58,40 @@ class SquadWaypointEventTest extends Specification {
|
|||
"decode (4)" in {
|
||||
PacketCoding.DecodePacket(string_4).require match {
|
||||
case SquadWaypointEvent(unk1, unk2, unk3, unk4, unk5, unk6) =>
|
||||
unk1 mustEqual 1
|
||||
unk1 mustEqual WaypointEventAction.Unknown1
|
||||
unk2 mustEqual 3
|
||||
unk3 mustEqual 41581052L
|
||||
unk4 mustEqual 1
|
||||
unk5 mustEqual Some(4L)
|
||||
unk6 mustEqual None
|
||||
unk4 mustEqual SquadWaypoints.Two
|
||||
unk5.contains( 4L ) mustEqual true
|
||||
unk6.isEmpty mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode (1)" in {
|
||||
val msg = SquadWaypointEvent(2, 11, 31155863L, 0)
|
||||
val msg = SquadWaypointEvent.Remove(11, 31155863L, SquadWaypoints.One)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_1
|
||||
}
|
||||
|
||||
"encode (2)" in {
|
||||
val msg = SquadWaypointEvent(2, 10, 0L, 4)
|
||||
val msg = SquadWaypointEvent.Remove(10, 0L, SquadWaypoints.ExperienceRally)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_2
|
||||
}
|
||||
|
||||
"encode (3)" in {
|
||||
val msg = SquadWaypointEvent(0, 3, 41581052L, 1, 10, Vector3(3457.9688f, 5514.4688f, 0.0f), 1)
|
||||
val msg = SquadWaypointEvent.Add(3, 41581052L, SquadWaypoints.Two, WaypointEvent(10, Vector3(3457.9688f, 5514.4688f, 0.0f), 1))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_3
|
||||
}
|
||||
|
||||
"encode (4)" in {
|
||||
val msg = SquadWaypointEvent(1, 3, 41581052L, 1, 4L)
|
||||
val msg = SquadWaypointEvent.Unknown1(3, 41581052L, SquadWaypoints.Two, 4L)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_4
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class CharacterDataTest extends Specification {
|
|||
a.data.v5.isEmpty mustEqual true
|
||||
a.exosuit mustEqual ExoSuitType.Reinforced
|
||||
a.unk5 mustEqual 0
|
||||
a.unk6 mustEqual 30777081L
|
||||
a.char_id mustEqual 30777081L
|
||||
a.unk7 mustEqual 1
|
||||
a.unk8 mustEqual 4
|
||||
a.unk9 mustEqual 0
|
||||
|
|
@ -167,7 +167,7 @@ class CharacterDataTest extends Specification {
|
|||
a.data.v5.isEmpty mustEqual true
|
||||
a.exosuit mustEqual ExoSuitType.Reinforced
|
||||
a.unk5 mustEqual 0
|
||||
a.unk6 mustEqual 192L
|
||||
a.char_id mustEqual 192L
|
||||
a.unk7 mustEqual 0
|
||||
a.unk8 mustEqual 0
|
||||
a.unk9 mustEqual 0
|
||||
|
|
@ -236,7 +236,7 @@ class CharacterDataTest extends Specification {
|
|||
a.data.v5.isEmpty mustEqual true
|
||||
a.exosuit mustEqual ExoSuitType.MAX
|
||||
a.unk5 mustEqual 1
|
||||
a.unk6 mustEqual 0L
|
||||
a.char_id mustEqual 0L
|
||||
a.unk7 mustEqual 0
|
||||
a.unk8 mustEqual 0
|
||||
a.unk9 mustEqual 0
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ class DetailedCharacterDataTest extends Specification {
|
|||
a.data.v5.isEmpty mustEqual true
|
||||
a.exosuit mustEqual ExoSuitType.Standard
|
||||
a.unk5 mustEqual 0
|
||||
a.unk6 mustEqual 41605313L
|
||||
a.char_id mustEqual 41605313L
|
||||
a.unk7 mustEqual 0
|
||||
a.unk8 mustEqual 0
|
||||
a.unk9 mustEqual 0
|
||||
|
|
@ -264,7 +264,7 @@ class DetailedCharacterDataTest extends Specification {
|
|||
a.data.v5.isEmpty mustEqual true
|
||||
a.exosuit mustEqual ExoSuitType.Standard
|
||||
a.unk5 mustEqual 0
|
||||
a.unk6 mustEqual 192L
|
||||
a.char_id mustEqual 192L
|
||||
a.unk7 mustEqual 0
|
||||
a.unk8 mustEqual 0
|
||||
a.unk9 mustEqual 0
|
||||
|
|
@ -449,7 +449,7 @@ class DetailedCharacterDataTest extends Specification {
|
|||
a.data.v5.isEmpty mustEqual true
|
||||
a.exosuit mustEqual ExoSuitType.MAX
|
||||
a.unk5 mustEqual 1
|
||||
a.unk6 mustEqual 41605870L
|
||||
a.char_id mustEqual 41605870L
|
||||
a.unk7 mustEqual 0
|
||||
a.unk8 mustEqual 0
|
||||
a.unk9 mustEqual 0
|
||||
|
|
@ -657,7 +657,7 @@ class DetailedCharacterDataTest extends Specification {
|
|||
a.data.v5.isEmpty mustEqual true
|
||||
a.exosuit mustEqual ExoSuitType.Agile
|
||||
a.unk5 mustEqual 0
|
||||
a.unk6 mustEqual 733931L
|
||||
a.char_id mustEqual 733931L
|
||||
a.unk7 mustEqual 0
|
||||
a.unk8 mustEqual 0
|
||||
a.unk9 mustEqual 0
|
||||
|
|
@ -1165,7 +1165,7 @@ class DetailedCharacterDataTest extends Specification {
|
|||
a.data.v5.isEmpty mustEqual true
|
||||
a.exosuit mustEqual ExoSuitType.Standard
|
||||
a.unk5 mustEqual 0
|
||||
a.unk6 mustEqual 1176612L
|
||||
a.char_id mustEqual 1176612L
|
||||
a.unk7 mustEqual 15
|
||||
a.unk8 mustEqual 5
|
||||
a.unk9 mustEqual 10
|
||||
|
|
@ -1315,7 +1315,7 @@ class DetailedCharacterDataTest extends Specification {
|
|||
a.data.v5.isEmpty mustEqual true
|
||||
a.exosuit mustEqual ExoSuitType.Standard
|
||||
a.unk5 mustEqual 0
|
||||
a.unk6 mustEqual 1267466L
|
||||
a.char_id mustEqual 1267466L
|
||||
a.unk7 mustEqual 3
|
||||
a.unk8 mustEqual 3
|
||||
a.unk9 mustEqual 0
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class MountedVehiclesTest extends Specification {
|
|||
a.data.v5.isEmpty mustEqual true
|
||||
a.exosuit mustEqual ExoSuitType.Agile
|
||||
a.unk5 mustEqual 0
|
||||
a.unk6 mustEqual 30777081L
|
||||
a.char_id mustEqual 30777081L
|
||||
a.unk7 mustEqual 1
|
||||
a.unk8 mustEqual 4
|
||||
a.unk9 mustEqual 0
|
||||
|
|
|
|||
|
|
@ -121,19 +121,19 @@ class AvatarTest extends Specification {
|
|||
obj.Implants(0).Active mustEqual false
|
||||
obj.Implants(0).Implant mustEqual ImplantType.None
|
||||
obj.Implant(0) mustEqual ImplantType.None
|
||||
obj.Implants(0).Installed mustEqual None
|
||||
obj.Implants(0).Installed.isEmpty mustEqual true
|
||||
obj.Implants(1).Unlocked mustEqual false
|
||||
obj.Implants(1).Initialized mustEqual false
|
||||
obj.Implants(1).Active mustEqual false
|
||||
obj.Implants(1).Implant mustEqual ImplantType.None
|
||||
obj.Implant(1) mustEqual ImplantType.None
|
||||
obj.Implants(1).Installed mustEqual None
|
||||
obj.Implants(1).Installed.isEmpty mustEqual true
|
||||
obj.Implants(2).Unlocked mustEqual false
|
||||
obj.Implants(2).Initialized mustEqual false
|
||||
obj.Implants(2).Active mustEqual false
|
||||
obj.Implants(2).Implant mustEqual ImplantType.None
|
||||
obj.Implant(2) mustEqual ImplantType.None
|
||||
obj.Implants(2).Installed mustEqual None
|
||||
obj.Implants(2).Installed.isEmpty mustEqual true
|
||||
|
||||
obj.Implant(3) mustEqual ImplantType.None //invalid slots beyond the third always reports as ImplantType.None
|
||||
}
|
||||
|
|
@ -142,10 +142,10 @@ class AvatarTest extends Specification {
|
|||
val testplant : ImplantDefinition = ImplantDefinition(1)
|
||||
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
|
||||
obj.Implants(0).Unlocked = true
|
||||
obj.InstallImplant(testplant) mustEqual Some(0)
|
||||
obj.InstallImplant(testplant).contains(0) mustEqual true
|
||||
obj.Implants.find({p => p.Implant == ImplantType(1)}) match { //find the installed implant
|
||||
case Some(slot) =>
|
||||
slot.Installed mustEqual Some(testplant)
|
||||
slot.Installed.contains(testplant) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -159,8 +159,8 @@ class AvatarTest extends Specification {
|
|||
obj.Implants(0).Unlocked = true
|
||||
obj.Implants(1).Unlocked = true
|
||||
|
||||
obj.InstallImplant(testplant1) mustEqual Some(0)
|
||||
obj.InstallImplant(testplant2) mustEqual Some(1)
|
||||
obj.InstallImplant(testplant1).contains(0) mustEqual true
|
||||
obj.InstallImplant(testplant2).contains(1) mustEqual true
|
||||
}
|
||||
|
||||
"can not install the same type of implant twice" in {
|
||||
|
|
@ -170,8 +170,8 @@ class AvatarTest extends Specification {
|
|||
obj.Implants(0).Unlocked = true
|
||||
obj.Implants(1).Unlocked = true
|
||||
|
||||
obj.InstallImplant(testplant1) mustEqual Some(0)
|
||||
obj.InstallImplant(testplant2) mustEqual None
|
||||
obj.InstallImplant(testplant1).contains(0) mustEqual true
|
||||
obj.InstallImplant(testplant2).isEmpty mustEqual true
|
||||
}
|
||||
|
||||
"can not install more implants than slots available (two unlocked)" in {
|
||||
|
|
@ -182,9 +182,9 @@ class AvatarTest extends Specification {
|
|||
obj.Implants(0).Unlocked = true
|
||||
obj.Implants(1).Unlocked = true
|
||||
|
||||
obj.InstallImplant(testplant1) mustEqual Some(0)
|
||||
obj.InstallImplant(testplant2) mustEqual Some(1)
|
||||
obj.InstallImplant(testplant3) mustEqual None
|
||||
obj.InstallImplant(testplant1).contains(0) mustEqual true
|
||||
obj.InstallImplant(testplant2).contains(1) mustEqual true
|
||||
obj.InstallImplant(testplant3).isEmpty mustEqual true
|
||||
}
|
||||
|
||||
"can not install more implants than slots available (four implants)" in {
|
||||
|
|
@ -197,21 +197,21 @@ class AvatarTest extends Specification {
|
|||
obj.Implants(1).Unlocked = true
|
||||
obj.Implants(2).Unlocked = true
|
||||
|
||||
obj.InstallImplant(testplant1) mustEqual Some(0)
|
||||
obj.InstallImplant(testplant2) mustEqual Some(1)
|
||||
obj.InstallImplant(testplant3) mustEqual Some(2)
|
||||
obj.InstallImplant(testplant4) mustEqual None
|
||||
obj.InstallImplant(testplant1).contains(0) mustEqual true
|
||||
obj.InstallImplant(testplant2).contains(1) mustEqual true
|
||||
obj.InstallImplant(testplant3).contains(2) mustEqual true
|
||||
obj.InstallImplant(testplant4).isEmpty mustEqual true
|
||||
}
|
||||
|
||||
"can uninstall an implant" in {
|
||||
val testplant : ImplantDefinition = ImplantDefinition(1)
|
||||
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
|
||||
obj.Implants(0).Unlocked = true
|
||||
obj.InstallImplant(testplant) mustEqual Some(0)
|
||||
obj.Implants(0).Installed mustEqual Some(testplant)
|
||||
obj.InstallImplant(testplant).contains(0) mustEqual true
|
||||
obj.Implants(0).Installed.contains(testplant) mustEqual true
|
||||
|
||||
obj.UninstallImplant(testplant.Type) mustEqual Some(0)
|
||||
obj.Implants(0).Installed mustEqual None
|
||||
obj.UninstallImplant(testplant.Type).contains(0) mustEqual true
|
||||
obj.Implants(0).Installed.isEmpty mustEqual true
|
||||
}
|
||||
|
||||
"can uninstall just a specific implant" in {
|
||||
|
|
@ -222,14 +222,14 @@ class AvatarTest extends Specification {
|
|||
obj.Implants(0).Unlocked = true
|
||||
obj.Implants(1).Unlocked = true
|
||||
obj.Implants(2).Unlocked = true
|
||||
obj.InstallImplant(testplant1) mustEqual Some(0)
|
||||
obj.InstallImplant(testplant2) mustEqual Some(1)
|
||||
obj.InstallImplant(testplant3) mustEqual Some(2)
|
||||
obj.InstallImplant(testplant1).contains(0) mustEqual true
|
||||
obj.InstallImplant(testplant2).contains(1) mustEqual true
|
||||
obj.InstallImplant(testplant3).contains(2) mustEqual true
|
||||
|
||||
obj.Implant(0) mustEqual testplant1.Type
|
||||
obj.Implant(1) mustEqual testplant2.Type
|
||||
obj.Implant(2) mustEqual testplant3.Type
|
||||
obj.UninstallImplant(testplant2.Type) mustEqual Some(1)
|
||||
obj.UninstallImplant(testplant2.Type).contains(1) mustEqual true
|
||||
obj.Implant(0) mustEqual testplant1.Type
|
||||
obj.Implant(1) mustEqual ImplantType.None
|
||||
obj.Implant(2) mustEqual testplant3.Type
|
||||
|
|
@ -243,16 +243,16 @@ class AvatarTest extends Specification {
|
|||
obj.Implants(0).Unlocked = true
|
||||
obj.Implants(1).Unlocked = true
|
||||
obj.Implants(2).Unlocked = true
|
||||
obj.InstallImplant(testplant1) mustEqual Some(0)
|
||||
obj.InstallImplant(testplant2) mustEqual Some(1)
|
||||
obj.InstallImplant(testplant3) mustEqual Some(2)
|
||||
obj.UninstallImplant(testplant2.Type) mustEqual Some(1)
|
||||
obj.InstallImplant(testplant1).contains(0) mustEqual true
|
||||
obj.InstallImplant(testplant2).contains(1) mustEqual true
|
||||
obj.InstallImplant(testplant3).contains(2) mustEqual true
|
||||
obj.UninstallImplant(testplant2.Type).contains(1) mustEqual true
|
||||
obj.Implant(0) mustEqual testplant1.Type
|
||||
obj.Implant(1) mustEqual ImplantType.None
|
||||
obj.Implant(2) mustEqual testplant3.Type
|
||||
|
||||
val testplant4 : ImplantDefinition = ImplantDefinition(4)
|
||||
obj.InstallImplant(testplant4) mustEqual Some(1)
|
||||
obj.InstallImplant(testplant4).contains(1) mustEqual true
|
||||
obj.Implant(0) mustEqual testplant1.Type
|
||||
obj.Implant(1) mustEqual testplant4.Type
|
||||
obj.Implant(2) mustEqual testplant3.Type
|
||||
|
|
@ -264,8 +264,8 @@ class AvatarTest extends Specification {
|
|||
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
|
||||
obj.Implants(0).Unlocked = true
|
||||
obj.Implants(1).Unlocked = true
|
||||
obj.InstallImplant(testplant1) mustEqual Some(0)
|
||||
obj.InstallImplant(testplant2) mustEqual Some(1)
|
||||
obj.InstallImplant(testplant1).contains(0) mustEqual true
|
||||
obj.InstallImplant(testplant2).contains(1) mustEqual true
|
||||
obj.Implants(0).Initialized = true
|
||||
obj.Implants(0).Active = true
|
||||
obj.Implants(1).Initialized = true
|
||||
|
|
@ -281,7 +281,7 @@ class AvatarTest extends Specification {
|
|||
|
||||
"does not have any loadout specifications by default" in {
|
||||
val (_, avatar) = CreatePlayer()
|
||||
(0 to 9).foreach { avatar.LoadLoadout(_) mustEqual None }
|
||||
(0 to 9).foreach { avatar.EquipmentLoadouts.LoadLoadout(_).isEmpty mustEqual true }
|
||||
ok
|
||||
}
|
||||
|
||||
|
|
@ -289,9 +289,9 @@ class AvatarTest extends Specification {
|
|||
val (obj, avatar) = CreatePlayer()
|
||||
obj.Slot(0).Equipment.get.asInstanceOf[Tool].Magazine = 1 //non-standard but legal
|
||||
obj.Slot(2).Equipment.get.asInstanceOf[Tool].AmmoSlot.Magazine = 100 //non-standard (and out of range, real=25)
|
||||
avatar.SaveLoadout(obj, "test", 0)
|
||||
avatar.EquipmentLoadouts.SaveLoadout(obj, "test", 0)
|
||||
|
||||
avatar.LoadLoadout(0) match {
|
||||
avatar.EquipmentLoadouts.LoadLoadout(0) match {
|
||||
case Some(items : InfantryLoadout) =>
|
||||
items.label mustEqual "test"
|
||||
items.exosuit mustEqual obj.ExoSuit
|
||||
|
|
@ -329,25 +329,25 @@ class AvatarTest extends Specification {
|
|||
|
||||
"save player's current inventory as a loadout, only found in the called-out slot number" in {
|
||||
val (obj, avatar) = CreatePlayer()
|
||||
avatar.SaveLoadout(obj, "test", 0)
|
||||
avatar.EquipmentLoadouts.SaveLoadout(obj, "test", 0)
|
||||
|
||||
avatar.LoadLoadout(1).isDefined mustEqual false
|
||||
avatar.LoadLoadout(0).isDefined mustEqual true
|
||||
avatar.EquipmentLoadouts.LoadLoadout(1).isDefined mustEqual false
|
||||
avatar.EquipmentLoadouts.LoadLoadout(0).isDefined mustEqual true
|
||||
}
|
||||
|
||||
"try to save player's current inventory as a loadout, but will not save to an invalid slot" in {
|
||||
val (obj, avatar) = CreatePlayer()
|
||||
avatar.SaveLoadout(obj, "test", 10)
|
||||
avatar.EquipmentLoadouts.SaveLoadout(obj, "test", 50)
|
||||
|
||||
avatar.LoadLoadout(10) mustEqual None
|
||||
avatar.EquipmentLoadouts.LoadLoadout(50).isEmpty mustEqual true
|
||||
}
|
||||
|
||||
"save player's current inventory as a loadout, without inventory contents" in {
|
||||
val (obj, avatar) = CreatePlayer()
|
||||
obj.Inventory.Clear()
|
||||
avatar.SaveLoadout(obj, "test", 0)
|
||||
avatar.EquipmentLoadouts.SaveLoadout(obj, "test", 0)
|
||||
|
||||
avatar.LoadLoadout(0) match {
|
||||
avatar.EquipmentLoadouts.LoadLoadout(0) match {
|
||||
case Some(items : InfantryLoadout) =>
|
||||
items.label mustEqual "test"
|
||||
items.exosuit mustEqual obj.ExoSuit
|
||||
|
|
@ -364,9 +364,9 @@ class AvatarTest extends Specification {
|
|||
obj.Slot(0).Equipment = None
|
||||
obj.Slot(2).Equipment = None
|
||||
obj.Slot(4).Equipment = None
|
||||
avatar.SaveLoadout(obj, "test", 0)
|
||||
avatar.EquipmentLoadouts.SaveLoadout(obj, "test", 0)
|
||||
|
||||
avatar.LoadLoadout(0) match {
|
||||
avatar.EquipmentLoadouts.LoadLoadout(0) match {
|
||||
case Some(items : InfantryLoadout) =>
|
||||
items.label mustEqual "test"
|
||||
items.exosuit mustEqual obj.ExoSuit
|
||||
|
|
@ -380,11 +380,11 @@ class AvatarTest extends Specification {
|
|||
|
||||
"save, load, delete; rapidly" in {
|
||||
val (obj, avatar) = CreatePlayer()
|
||||
avatar.SaveLoadout(obj, "test", 0)
|
||||
avatar.EquipmentLoadouts.SaveLoadout(obj, "test", 0)
|
||||
|
||||
avatar.LoadLoadout(0).isDefined mustEqual true
|
||||
avatar.DeleteLoadout(0)
|
||||
avatar.LoadLoadout(0) mustEqual None
|
||||
avatar.EquipmentLoadouts.LoadLoadout(0).isDefined mustEqual true
|
||||
avatar.EquipmentLoadouts.DeleteLoadout(0)
|
||||
avatar.EquipmentLoadouts.LoadLoadout(0).isEmpty mustEqual true
|
||||
}
|
||||
|
||||
"the fifth slot is the locker wrapped in an EquipmentSlot" in {
|
||||
|
|
|
|||
|
|
@ -70,11 +70,11 @@ class DamageCalculationsTests extends Specification {
|
|||
}
|
||||
|
||||
"calculate distance between target and source" in {
|
||||
DistanceBetweenTargetandSource(resprojectile) mustEqual 10
|
||||
DistanceBetweenTargetandSource(resprojectile) mustEqual 67.38225f
|
||||
}
|
||||
|
||||
"calculate distance between target and explosion (splash)" in {
|
||||
DistanceFromExplosionToTarget(resprojectile) mustEqual 64.03124f
|
||||
DistanceFromExplosionToTarget(resprojectile) mustEqual 63.031242f
|
||||
}
|
||||
|
||||
"calculate no damage from components" in {
|
||||
|
|
|
|||
|
|
@ -245,34 +245,34 @@ class PlayerTest extends Specification {
|
|||
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
|
||||
obj.Slot(-1).Equipment = wep
|
||||
|
||||
obj.Slot(-1).Equipment mustEqual None
|
||||
obj.Slot(-1).Equipment.isEmpty mustEqual true
|
||||
}
|
||||
|
||||
"search for the smallest available slot in which to store equipment" in {
|
||||
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
|
||||
obj.Inventory.Resize(3,3) //fits one item
|
||||
|
||||
obj.Fit(Tool(GlobalDefinitions.beamer)) mustEqual Some(0)
|
||||
obj.Fit(Tool(GlobalDefinitions.beamer)).contains(0) mustEqual true
|
||||
|
||||
obj.Fit(Tool(GlobalDefinitions.suppressor)) mustEqual Some(2)
|
||||
obj.Fit(Tool(GlobalDefinitions.suppressor)).contains(2) mustEqual true
|
||||
|
||||
val ammo = AmmoBox(GlobalDefinitions.bullet_9mm)
|
||||
val ammo2 = AmmoBox(GlobalDefinitions.bullet_9mm)
|
||||
val ammo3 = AmmoBox(GlobalDefinitions.bullet_9mm)
|
||||
obj.Fit(ammo) mustEqual Some(6)
|
||||
obj.Fit(ammo).contains(6) mustEqual true
|
||||
obj.Slot(6).Equipment = ammo
|
||||
obj.Fit(ammo2) mustEqual Some(Player.FreeHandSlot)
|
||||
obj.Fit(ammo2).contains(Player.FreeHandSlot) mustEqual true
|
||||
obj.Slot(Player.FreeHandSlot).Equipment = ammo2
|
||||
obj.Fit(ammo3) mustEqual None
|
||||
obj.Fit(ammo3).isEmpty mustEqual true
|
||||
}
|
||||
|
||||
"can use their free hand to hold things" in {
|
||||
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
|
||||
val ammo = AmmoBox(GlobalDefinitions.bullet_9mm)
|
||||
obj.FreeHand.Equipment mustEqual None
|
||||
obj.FreeHand.Equipment.isEmpty mustEqual true
|
||||
|
||||
obj.FreeHand.Equipment = ammo
|
||||
obj.FreeHand.Equipment mustEqual Some(ammo)
|
||||
obj.FreeHand.Equipment.contains(ammo) mustEqual true
|
||||
}
|
||||
|
||||
"can access the player's locker-space" in {
|
||||
|
|
@ -308,12 +308,12 @@ class PlayerTest extends Specification {
|
|||
item
|
||||
}
|
||||
|
||||
obj.Find(PlanetSideGUID(1)) mustEqual Some(0) //holsters
|
||||
obj.Find(PlanetSideGUID(2)) mustEqual Some(4) //holsters, melee
|
||||
obj.Find(PlanetSideGUID(3)) mustEqual Some(6) //inventory
|
||||
obj.Find(PlanetSideGUID(4)) mustEqual None //can not find in locker-space
|
||||
obj.Find(PlanetSideGUID(5)) mustEqual Some(Player.FreeHandSlot) //free hand
|
||||
obj.Find(PlanetSideGUID(6)) mustEqual None //not here
|
||||
obj.Find(PlanetSideGUID(1)).contains(0) mustEqual true //holsters
|
||||
obj.Find(PlanetSideGUID(2)).contains(4) mustEqual true //holsters, melee
|
||||
obj.Find(PlanetSideGUID(3)).contains(6) mustEqual true //inventory
|
||||
obj.Find(PlanetSideGUID(4)).isEmpty mustEqual true //can not find in locker-space
|
||||
obj.Find(PlanetSideGUID(5)).contains(Player.FreeHandSlot) mustEqual true //free hand
|
||||
obj.Find(PlanetSideGUID(6)).isEmpty mustEqual true //not here
|
||||
}
|
||||
|
||||
"does equipment collision checking (are we already holding something there?)" in {
|
||||
|
|
@ -437,20 +437,20 @@ class PlayerTest extends Specification {
|
|||
|
||||
"seat in a vehicle" in {
|
||||
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
|
||||
obj.VehicleSeated mustEqual None
|
||||
obj.VehicleSeated.isEmpty mustEqual true
|
||||
obj.VehicleSeated = PlanetSideGUID(65)
|
||||
obj.VehicleSeated mustEqual Some(PlanetSideGUID(65))
|
||||
obj.VehicleSeated.contains(PlanetSideGUID(65)) mustEqual true
|
||||
obj.VehicleSeated = None
|
||||
obj.VehicleSeated mustEqual None
|
||||
obj.VehicleSeated.isEmpty mustEqual true
|
||||
}
|
||||
|
||||
"own in a vehicle" in {
|
||||
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
|
||||
obj.VehicleOwned mustEqual None
|
||||
obj.VehicleOwned.isEmpty mustEqual true
|
||||
obj.VehicleOwned = PlanetSideGUID(65)
|
||||
obj.VehicleOwned mustEqual Some(PlanetSideGUID(65))
|
||||
obj.VehicleOwned.contains(PlanetSideGUID(65)) mustEqual true
|
||||
obj.VehicleOwned = None
|
||||
obj.VehicleOwned mustEqual None
|
||||
obj.VehicleOwned.isEmpty mustEqual true
|
||||
}
|
||||
|
||||
"remember what zone he is in" in {
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ class OrderTerminalTest extends Specification {
|
|||
player.ExoSuit = ExoSuitType.Agile
|
||||
player.Slot(0).Equipment = Tool(GlobalDefinitions.beamer)
|
||||
player.Slot(6).Equipment = Tool(GlobalDefinitions.beamer)
|
||||
avatar.SaveLoadout(player, "test", 0)
|
||||
avatar.EquipmentLoadouts.SaveLoadout(player, "test", 0)
|
||||
|
||||
val msg = infantryTerminal.Request(player, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 4, "", 0, PlanetSideGUID(0)))
|
||||
msg.isInstanceOf[Terminal.InfantryLoadout] mustEqual true
|
||||
|
|
@ -137,7 +137,7 @@ class OrderTerminalTest extends Specification {
|
|||
"player can retrieve a vehicle loadout" in {
|
||||
val fury = Vehicle(GlobalDefinitions.fury)
|
||||
fury.Slot(30).Equipment = AmmoBox(GlobalDefinitions.hellfire_ammo)
|
||||
avatar.SaveLoadout(fury, "test", 10)
|
||||
avatar.EquipmentLoadouts.SaveLoadout(fury, "test", 10)
|
||||
|
||||
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Loadout, 4, "test", 0, PlanetSideGUID(0))
|
||||
terminal.Request(player, msg) match {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import services.ServiceManager
|
|||
import services.avatar._
|
||||
import services.galaxy.GalaxyService
|
||||
import services.local._
|
||||
import services.teamwork.SquadService
|
||||
import services.vehicle.VehicleService
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
|
@ -258,6 +259,7 @@ object PsLogin {
|
|||
serviceManager ! ServiceManager.Register(Props[LocalService], "local")
|
||||
serviceManager ! ServiceManager.Register(Props[VehicleService], "vehicle")
|
||||
serviceManager ! ServiceManager.Register(Props[GalaxyService], "galaxy")
|
||||
serviceManager ! ServiceManager.Register(Props[SquadService], "squad")
|
||||
serviceManager ! ServiceManager.Register(Props(classOf[InterstellarCluster], continentList), "cluster")
|
||||
|
||||
//attach event bus entry point to each zone
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import net.psforever.objects.serverobject.terminals._
|
|||
import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
|
||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||
import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurret}
|
||||
import net.psforever.objects.teamwork.Squad
|
||||
import net.psforever.objects.vehicles.{AccessPermissionGroup, Cargo, Utility, VehicleLockState, _}
|
||||
import net.psforever.objects.vital._
|
||||
import net.psforever.objects.zones.{InterstellarCluster, Zone, ZoneHotSpotProjector}
|
||||
|
|
@ -52,7 +53,9 @@ import services.galaxy.{GalaxyResponse, GalaxyServiceResponse}
|
|||
import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse}
|
||||
import services.vehicle.support.TurretUpgrader
|
||||
import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse}
|
||||
import services.teamwork.{SquadAction => SquadServiceAction, SquadServiceMessage, SquadServiceResponse, SquadResponse, SquadService}
|
||||
|
||||
import scala.collection.mutable.LongMap
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.annotation.tailrec
|
||||
|
|
@ -76,6 +79,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
var localService : ActorRef = ActorRef.noSender
|
||||
var vehicleService : ActorRef = ActorRef.noSender
|
||||
var galaxyService : ActorRef = ActorRef.noSender
|
||||
var squadService : ActorRef = ActorRef.noSender
|
||||
var taskResolver : ActorRef = Actor.noSender
|
||||
var cluster : ActorRef = Actor.noSender
|
||||
var continent : Zone = Zone.Nowhere
|
||||
|
|
@ -97,6 +101,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
var whenUsedLastKit : Long = 0
|
||||
val projectiles : Array[Option[Projectile]] = Array.fill[Option[Projectile]](Projectile.RangeUID - Projectile.BaseUID)(None)
|
||||
var drawDeloyableIcon : PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons
|
||||
var updateSquad : () => Unit = NoSquadUpdates
|
||||
var recentTeleportAttempt : Long = 0
|
||||
var lastTerminalOrderFulfillment : Boolean = true /**
|
||||
* used during zone transfers to maintain reference to seated vehicle (which does not yet exist in the new zone)
|
||||
|
|
@ -113,6 +118,21 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
* no harm should come from leaving the field set to an old unique identifier value after the transfer period
|
||||
*/
|
||||
var interstellarFerryTopLevelGUID : Option[PlanetSideGUID] = None
|
||||
val squadUI : LongMap[SquadUIElement] = new LongMap[SquadUIElement]()
|
||||
var squad_supplement_id : Int = 0
|
||||
/**
|
||||
* When joining or creating a squad, the original state of the avatar's internal LFS variable is blanked.
|
||||
* This `WSA`-local variable is then used to indicate the ongoing state of the LFS UI component,
|
||||
* now called "Looking for Squad Member."
|
||||
* Only the squad leader may toggle the LFSM marquee.
|
||||
* Upon leaving or disbanding a squad, this value is made false.
|
||||
* Control switching between the `Avatar`-local and the `WorldSessionActor`-local variable is contingent on `squadUI` being populated.
|
||||
*/
|
||||
var lfsm : Boolean = false
|
||||
var squadChannel : Option[String] = None
|
||||
var squadSetup : () => Unit = FirstTimeSquadSetup
|
||||
var squadUpdateCounter : Int = 0
|
||||
val queuedSquadActions : Seq[() => Unit] = Seq(SquadUpdates, NoSquadUpdates, NoSquadUpdates, NoSquadUpdates)
|
||||
|
||||
var amsSpawnPoints : List[SpawnPoint] = Nil
|
||||
var clientKeepAlive : Cancellable = DefaultCancellable.obj
|
||||
|
|
@ -144,6 +164,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
galaxyService ! Service.Leave()
|
||||
LivePlayerList.Remove(sessionId)
|
||||
if(player != null && player.HasGUID) {
|
||||
squadService ! Service.Leave(Some(player.CharId.toString))
|
||||
val player_guid = player.GUID
|
||||
//handle orphaned deployables
|
||||
DisownDeployables()
|
||||
|
|
@ -155,6 +176,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
.collect { case ((index, Some(obj))) => InventoryItem(obj, index) }
|
||||
) ++ player.Inventory.Items)
|
||||
.filterNot({ case InventoryItem(obj, _) => obj.isInstanceOf[BoomerTrigger] || obj.isInstanceOf[Telepad] })
|
||||
//put any temporary value back into the avatar
|
||||
//TODO final character save before doing any of this (use equipment)
|
||||
continent.Population ! Zone.Population.Release(avatar)
|
||||
if(player.isAlive) {
|
||||
|
|
@ -266,6 +288,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
ServiceManager.serviceManager ! Lookup("taskResolver")
|
||||
ServiceManager.serviceManager ! Lookup("cluster")
|
||||
ServiceManager.serviceManager ! Lookup("galaxy")
|
||||
ServiceManager.serviceManager ! Lookup("squad")
|
||||
|
||||
case _ =>
|
||||
log.error("Unknown message")
|
||||
|
|
@ -291,6 +314,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case ServiceManager.LookupResult("cluster", endpoint) =>
|
||||
cluster = endpoint
|
||||
log.info("ID: " + sessionId + " Got cluster service " + endpoint)
|
||||
case ServiceManager.LookupResult("squad", endpoint) =>
|
||||
squadService = endpoint
|
||||
log.info("ID: " + sessionId + " Got squad service " + endpoint)
|
||||
|
||||
case ControlPacket(_, ctrl) =>
|
||||
handleControlPkt(ctrl)
|
||||
|
|
@ -336,6 +362,231 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case VehicleServiceResponse(toChannel, guid, reply) =>
|
||||
HandleVehicleServiceResponse(toChannel, guid, reply)
|
||||
|
||||
case SquadServiceResponse(_, excluded, response) =>
|
||||
if(!excluded.exists(_ == avatar.CharId)) {
|
||||
response match {
|
||||
case SquadResponse.ListSquadFavorite(line, task) =>
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), line, SquadAction.ListSquadFavorite(task)))
|
||||
|
||||
case SquadResponse.InitList(infos) =>
|
||||
sendResponse(ReplicationStreamMessage(infos))
|
||||
|
||||
case SquadResponse.UpdateList(infos) if infos.nonEmpty =>
|
||||
sendResponse(
|
||||
ReplicationStreamMessage(6, None,
|
||||
infos.map { case (index, squadInfo) =>
|
||||
SquadListing(index, squadInfo)
|
||||
}.toVector
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.RemoveFromList(infos) if infos.nonEmpty =>
|
||||
sendResponse(
|
||||
ReplicationStreamMessage(1, None,
|
||||
infos.map { index =>
|
||||
SquadListing(index, None)
|
||||
}.toVector
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.Detail(guid, detail) =>
|
||||
sendResponse(SquadDetailDefinitionUpdateMessage(guid, detail))
|
||||
|
||||
case SquadResponse.AssociateWithSquad(squad_guid) =>
|
||||
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.AssociateWithSquad()))
|
||||
|
||||
case SquadResponse.SetListSquad(squad_guid) =>
|
||||
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.SetListSquad()))
|
||||
|
||||
case SquadResponse.Membership(request_type, unk1, unk2, char_id, opt_char_id, player_name, unk5, unk6) =>
|
||||
val name = request_type match {
|
||||
case SquadResponseType.Invite if unk5 =>
|
||||
//player_name is our name; the name of the player indicated by unk3 is needed
|
||||
LivePlayerList.WorldPopulation({ case (_, a : Avatar) => char_id == a.CharId }).headOption match {
|
||||
case Some(player) =>
|
||||
player.name
|
||||
case None =>
|
||||
player_name
|
||||
}
|
||||
case _ =>
|
||||
player_name
|
||||
}
|
||||
sendResponse(SquadMembershipResponse(request_type, unk1, unk2, char_id, opt_char_id, name, unk5, unk6))
|
||||
|
||||
case SquadResponse.WantsSquadPosition(_, name) =>
|
||||
sendResponse(
|
||||
ChatMsg(
|
||||
ChatMessageType.CMT_SQUAD, true, name,
|
||||
s"\\#6 would like to join your squad. (respond with \\#3/accept\\#6 or \\#3/reject\\#6)",
|
||||
None
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.Join(squad, positionsToUpdate, toChannel) =>
|
||||
val leader = squad.Leader
|
||||
val membershipPositions = positionsToUpdate map squad.Membership.zipWithIndex
|
||||
StartBundlingPackets()
|
||||
membershipPositions.find({ case(member, _) => member.CharId == avatar.CharId }) match {
|
||||
case Some((ourMember, ourIndex)) =>
|
||||
//we are joining the squad
|
||||
//load each member's entry (our own too)
|
||||
squad_supplement_id = squad.GUID.guid + 1
|
||||
membershipPositions.foreach { case(member, index) =>
|
||||
sendResponse(SquadMemberEvent.Add(squad_supplement_id, member.CharId, index, member.Name, member.ZoneId, unk7 = 0))
|
||||
squadUI(member.CharId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
||||
}
|
||||
//repeat our entry
|
||||
sendResponse(SquadMemberEvent.Add(squad_supplement_id, ourMember.CharId, ourIndex, ourMember.Name, ourMember.ZoneId, unk7 = 0)) //repeat of our entry
|
||||
val playerGuid = player.GUID
|
||||
//turn lfs off
|
||||
val factionOnContinentChannel = s"${continent.Id}/${player.Faction}"
|
||||
if(avatar.LFS) {
|
||||
avatar.LFS = false
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 53, 0))
|
||||
avatarService ! AvatarServiceMessage(factionOnContinentChannel, AvatarAction.PlanetsideAttribute(playerGuid, 53, 0))
|
||||
}
|
||||
//squad colors
|
||||
GiveSquadColorsInZone()
|
||||
avatarService ! AvatarServiceMessage(factionOnContinentChannel, AvatarAction.PlanetsideAttribute(playerGuid, 31, squad_supplement_id))
|
||||
//associate with member position in squad
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 32, ourIndex))
|
||||
//a finalization? what does this do?
|
||||
sendResponse(SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(18)))
|
||||
updateSquad = PeriodicUpdatesWhenEnrolledInSquad
|
||||
squadChannel = Some(toChannel)
|
||||
case _ =>
|
||||
//other player is joining our squad
|
||||
//load each member's entry
|
||||
GiveSquadColorsInZone(
|
||||
membershipPositions.map { case(member, index) =>
|
||||
val charId = member.CharId
|
||||
sendResponse(SquadMemberEvent.Add(squad_supplement_id, charId, index, member.Name, member.ZoneId, unk7 = 0))
|
||||
squadUI(charId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
||||
charId
|
||||
}
|
||||
)
|
||||
}
|
||||
StopBundlingPackets()
|
||||
//send an initial dummy update for map icon(s)
|
||||
sendResponse(SquadState(PlanetSideGUID(squad_supplement_id),
|
||||
membershipPositions
|
||||
.filterNot { case (member, _) => member.CharId == avatar.CharId }
|
||||
.map{ case (member, _) => SquadStateInfo(member.CharId, member.Health, member.Armor, member.Position, 2,2, false, 429, None,None) }
|
||||
.toList
|
||||
))
|
||||
|
||||
case SquadResponse.Leave(squad, positionsToUpdate) =>
|
||||
StartBundlingPackets()
|
||||
positionsToUpdate.find({ case(member, _) => member == avatar.CharId }) match {
|
||||
case Some((ourMember, ourIndex)) =>
|
||||
//we are leaving the squad
|
||||
//remove each member's entry (our own too)
|
||||
positionsToUpdate.foreach { case(member, index) =>
|
||||
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index))
|
||||
squadUI.remove(member)
|
||||
}
|
||||
//uninitialize
|
||||
val playerGuid = player.GUID
|
||||
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, ourMember, ourIndex)) //repeat of our entry
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 31, 0)) //disassociate with squad?
|
||||
avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(playerGuid, 31, 0))
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 32, 0)) //disassociate with member position in squad?
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 34, 4294967295L)) //unknown, perhaps unrelated?
|
||||
lfsm = false
|
||||
//a finalization? what does this do?
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
|
||||
squad_supplement_id = 0
|
||||
squadUpdateCounter = 0
|
||||
updateSquad = NoSquadUpdates
|
||||
squadChannel = None
|
||||
case _ =>
|
||||
//remove each member's entry
|
||||
GiveSquadColorsInZone(
|
||||
positionsToUpdate.map { case(member, index) =>
|
||||
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index))
|
||||
squadUI.remove(member)
|
||||
member
|
||||
},
|
||||
value = 0
|
||||
)
|
||||
}
|
||||
StopBundlingPackets()
|
||||
|
||||
case SquadResponse.AssignMember(squad, from_index, to_index) =>
|
||||
//we've already swapped position internally; now we swap the cards
|
||||
SwapSquadUIElements(squad, from_index, to_index)
|
||||
|
||||
case SquadResponse.PromoteMember(squad, char_id, from_index, to_index) =>
|
||||
val charId = player.CharId
|
||||
val guid = player.GUID
|
||||
lazy val factionOnContinentChannel = s"${continent.Id}/${player.Faction}"
|
||||
//are we being demoted?
|
||||
if(squadUI(charId).index == 0) {
|
||||
//lfsm -> lfs
|
||||
if(lfsm) {
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 53, 0))
|
||||
avatarService ! AvatarServiceMessage(factionOnContinentChannel, AvatarAction.PlanetsideAttribute(guid, 53, 0))
|
||||
}
|
||||
lfsm = false
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 32, from_index)) //associate with member position in squad
|
||||
}
|
||||
//are we being promoted?
|
||||
else if(charId == char_id) {
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 32, 0)) //associate with member position in squad
|
||||
}
|
||||
avatarService ! AvatarServiceMessage(factionOnContinentChannel, AvatarAction.PlanetsideAttribute(guid, 31, squad_supplement_id))
|
||||
//we must fix the squad cards backend
|
||||
SwapSquadUIElements(squad, from_index, to_index)
|
||||
|
||||
case SquadResponse.UpdateMembers(squad, positions) =>
|
||||
val pairedEntries = positions.collect {
|
||||
case entry if squadUI.contains(entry.char_id) =>
|
||||
(entry, squadUI(entry.char_id))
|
||||
}
|
||||
//prune entries
|
||||
val updatedEntries = pairedEntries
|
||||
.collect({
|
||||
case (entry, element) if entry.zone_number != element.zone =>
|
||||
//zone gets updated for these entries
|
||||
sendResponse(SquadMemberEvent.UpdateZone(squad_supplement_id, entry.char_id, element.index, entry.zone_number))
|
||||
squadUI(entry.char_id) = SquadUIElement(element.name, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
|
||||
entry
|
||||
case (entry, element) if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position =>
|
||||
//other elements that need to be updated
|
||||
squadUI(entry.char_id) = SquadUIElement(element.name, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
|
||||
entry
|
||||
})
|
||||
.filterNot(_.char_id == avatar.CharId) //we want to update our backend, but not our frontend
|
||||
if(updatedEntries.nonEmpty) {
|
||||
sendResponse(
|
||||
SquadState(
|
||||
PlanetSideGUID(squad_supplement_id),
|
||||
updatedEntries.map { entry => SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos, 2,2, false, 429, None,None)}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case SquadResponse.SquadSearchResults() =>
|
||||
//I don't actually know how to return search results
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.NoSquadSearchResults()))
|
||||
|
||||
case SquadResponse.InitWaypoints(char_id, waypoints) =>
|
||||
StartBundlingPackets()
|
||||
waypoints.foreach { case (waypoint_type, info, unk) =>
|
||||
sendResponse(SquadWaypointEvent.Add(squad_supplement_id, char_id, waypoint_type, WaypointEvent(info.zone_number, info.pos, unk)))
|
||||
}
|
||||
StopBundlingPackets()
|
||||
|
||||
case SquadResponse.WaypointEvent(WaypointEventAction.Add, char_id, waypoint_type, _, Some(info), unk) =>
|
||||
sendResponse(SquadWaypointEvent.Add(squad_supplement_id, char_id, waypoint_type, WaypointEvent(info.zone_number, info.pos, unk)))
|
||||
|
||||
case SquadResponse.WaypointEvent(WaypointEventAction.Remove, char_id, waypoint_type, _, _, _) =>
|
||||
sendResponse(SquadWaypointEvent.Remove(squad_supplement_id, char_id, waypoint_type))
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
case Deployment.CanDeploy(obj, state) =>
|
||||
val vehicle_guid = obj.GUID
|
||||
//TODO remove this arbitrary allowance angle when no longer helpful
|
||||
|
|
@ -484,7 +735,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
player.Stamina = stamina
|
||||
player.Armor = armor
|
||||
}
|
||||
sendResponse(CharacterInfoMessage(15, PlanetSideZoneID(10000), 41605313, player.GUID, false, 6404428))
|
||||
sendResponse(CharacterInfoMessage(15, PlanetSideZoneID(10000), avatar.CharId, player.GUID, false, 6404428))
|
||||
RemoveCharacterSelectScreenGUID(player)
|
||||
sendResponse(CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0))
|
||||
sendResponse(CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0))
|
||||
|
|
@ -802,7 +1053,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
traveler = new Traveler(self, continent.Id)
|
||||
//PropertyOverrideMessage
|
||||
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 0)) // disable festive backpacks
|
||||
sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list
|
||||
sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list
|
||||
sendResponse(FriendsResponse(FriendAction.InitializeFriendList, 0, true, true, Nil))
|
||||
sendResponse(FriendsResponse(FriendAction.InitializeIgnoreList, 0, true, true, Nil))
|
||||
avatarService ! Service.Join(avatar.name) //channel will be player.Name
|
||||
|
|
@ -810,6 +1061,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
vehicleService ! Service.Join(avatar.name) //channel will be player.Name
|
||||
galaxyService ! Service.Join("galaxy") //for galaxy-wide messages
|
||||
galaxyService ! Service.Join(s"${avatar.faction}") //for hotspots
|
||||
squadService ! Service.Join(s"${avatar.faction}") //channel will be player.Faction
|
||||
squadService ! Service.Join(s"${avatar.CharId}") //channel will be player.CharId (in order to work with packets)
|
||||
cluster ! InterstellarCluster.GetWorld("home3")
|
||||
|
||||
case InterstellarCluster.GiveWorld(zoneId, zone) =>
|
||||
|
|
@ -2828,7 +3081,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT))
|
||||
sendResponse(ChangeShortcutBankMessage(guid, 0))
|
||||
//Favorites lists
|
||||
val (inf, veh) = avatar.Loadouts.partition { case (index, _) => index < 10 }
|
||||
val (inf, veh) = avatar.EquipmentLoadouts.Loadouts.partition { case (index, _) => index < 10 }
|
||||
inf.foreach {
|
||||
case (index, loadout : InfantryLoadout) =>
|
||||
sendResponse(FavoritesMessage(LoadoutType.Infantry, guid, index, loadout.label, InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype)))
|
||||
|
|
@ -2840,9 +3093,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(SetChatFilterMessage(ChatChannel.Local, false, ChatChannel.values.toList)) //TODO will not always be "on" like this
|
||||
deadState = DeadState.Alive
|
||||
sendResponse(AvatarDeadStateMessage(DeadState.Alive, 0, 0, tplayer.Position, player.Faction, true))
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 53, 1))
|
||||
//looking for squad (members)
|
||||
if(tplayer.LFS || lfsm) {
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 53, 1))
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(guid, 53, 1))
|
||||
}
|
||||
sendResponse(AvatarSearchCriteriaMessage(guid, List(0, 0, 0, 0, 0, 0)))
|
||||
(1 to 73).foreach(i => {
|
||||
// not all GUID's are set, and not all of the set ones will always be zero; what does this section do?
|
||||
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(i), 67, 0))
|
||||
})
|
||||
(0 to 30).foreach(i => {
|
||||
|
|
@ -2851,7 +3109,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
})
|
||||
//AvatarAwardMessage
|
||||
//DisplayAwardMessage
|
||||
//SquadDefinitionActionMessage and SquadDetailDefinitionUpdateMessage
|
||||
sendResponse(PlanetsideStringAttributeMessage(guid, 0, "Outfit Name"))
|
||||
//squad stuff (loadouts, assignment)
|
||||
squadSetup()
|
||||
//MapObjectStateBlockMessage and ObjectCreateMessage?
|
||||
//TacticsMessage?
|
||||
//change the owner on our deployables (re-draw the icons for our deployables too)
|
||||
|
|
@ -2888,6 +3148,97 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* These messages are dispatched when first starting up the client and connecting to the server for the first time.
|
||||
* While many of thee messages will be reused for other situations, they appear in this order only during startup.
|
||||
*/
|
||||
def FirstTimeSquadSetup() : Unit = {
|
||||
sendResponse(SquadDetailDefinitionUpdateMessage.Init)
|
||||
sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(6)))
|
||||
//only need to load these once - they persist between zone transfers and respawns
|
||||
avatar.SquadLoadouts.Loadouts.foreach {
|
||||
case (index, loadout : SquadLoadout) =>
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), index, SquadAction.ListSquadFavorite(loadout.task)))
|
||||
}
|
||||
//non-squad GUID-0 counts as the settings when not joined with a squad
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.AssociateWithSquad()))
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.SetListSquad()))
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitSquadList())
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitCharId())
|
||||
squadSetup = RespawnSquadSetup
|
||||
}
|
||||
|
||||
/**
|
||||
* These messages are used during each subsequent respawn to reset the squad colors on player nameplates and marquees.
|
||||
* By using `squadUI` to maintain relevant information about squad members,
|
||||
* especially the unique character identifier number,
|
||||
* only the zone-specific squad members will receive the important messages about their squad member's spawn.
|
||||
*/
|
||||
def RespawnSquadSetup() : Unit = {
|
||||
if(squadUI.nonEmpty) {
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 31, squad_supplement_id))
|
||||
avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 31, squad_supplement_id))
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, squadUI(player.CharId).index))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* These messages are used during each subsequent respawn to reset the squad colors on player nameplates and marquees.
|
||||
* During a zone change,
|
||||
* on top of other squad mates in the zone needing to have their knowledge of this player's squad colors changed,
|
||||
* the player must also set squad colors for each other squad members.
|
||||
* Default respawn functionality may resume afterwards.
|
||||
*/
|
||||
def ZoneChangeSquadSetup() : Unit = {
|
||||
RespawnSquadSetup()
|
||||
GiveSquadColorsInZone()
|
||||
squadSetup = RespawnSquadSetup
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate all squad members in zone and give their nameplates and their marquees the appropriate squad color.
|
||||
*/
|
||||
def GiveSquadColorsInZone() : Unit = {
|
||||
GiveSquadColorsInZone(squadUI.keys, squad_supplement_id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate the listed squad members in zone and give their nameplates and their marquees the appropriate squad color.
|
||||
* @param members members of the squad to target
|
||||
*/
|
||||
def GiveSquadColorsInZone(members : Iterable[Long]) : Unit = {
|
||||
GiveSquadColorsInZone(members, squad_supplement_id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate the listed squad members in zone and give their nameplates and their marquees the appropriate squad color.
|
||||
* @see `PlanetsideAttributeMessage`
|
||||
* @param members members of the squad to target
|
||||
* @param value the assignment value
|
||||
*/
|
||||
def GiveSquadColorsInZone(members : Iterable[Long], value : Long) : Unit = {
|
||||
SquadMembersInZone(members).foreach {
|
||||
members => sendResponse(PlanetsideAttributeMessage(members.GUID, 31, value))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For the listed squad member unique character identifier numbers,
|
||||
* find and return all squad members in the current zone.
|
||||
* @param members members of the squad to target
|
||||
* @return a list of `Player` objects
|
||||
*/
|
||||
def SquadMembersInZone(members : Iterable[Long]) : Iterable[Player] = {
|
||||
val players = continent.LivePlayers
|
||||
for {
|
||||
charId <- members
|
||||
player = players.find { _.CharId == charId }
|
||||
if player.nonEmpty
|
||||
} yield player.get
|
||||
}
|
||||
|
||||
def handleControlPkt(pkt : PlanetSideControlPacket) = {
|
||||
pkt match {
|
||||
case sync @ ControlSync(diff, _, _, _, _, _, fa, fb) =>
|
||||
|
|
@ -2908,10 +3259,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
val clientVersion = s"Client Version: $majorVersion.$minorVersion.$revision, $buildDate"
|
||||
log.info(s"New world login to $server with Token:$token. $clientVersion")
|
||||
//TODO begin temp player character auto-loading; remove later
|
||||
//this is all just temporary character creation used in the dev branch, making explicit values that allow for testing
|
||||
//the unique character identifier number for this testing character is based on the original test character,
|
||||
//whose identifier number was 41605314
|
||||
//all head features, faction, and sex also match that test character
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.types.CertificationType._
|
||||
val faction = PlanetSideEmpire.VS
|
||||
val avatar = Avatar(s"TestCharacter$sessionId", faction, CharacterGender.Female, 41, CharacterVoice.Voice1)
|
||||
val avatar = new Avatar(41605313L+sessionId, s"TestCharacter$sessionId", faction, CharacterGender.Female, 41, CharacterVoice.Voice1)
|
||||
avatar.Certifications += StandardAssault
|
||||
avatar.Certifications += MediumAssault
|
||||
avatar.Certifications += StandardExoSuit
|
||||
|
|
@ -2946,6 +3301,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
avatar.Certifications += AssaultEngineering
|
||||
avatar.Certifications += Hacking
|
||||
avatar.Certifications += AdvancedHacking
|
||||
avatar.CEP = 6000001
|
||||
this.avatar = avatar
|
||||
|
||||
InitializeDeployableQuantities(avatar) //set deployables ui elements
|
||||
|
|
@ -3043,7 +3399,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(TimeOfDayMessage(1191182336))
|
||||
//custom
|
||||
sendResponse(ContinentalLockUpdateMessage(13, PlanetSideEmpire.VS)) // "The VS have captured the VS Sanctuary."
|
||||
sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list
|
||||
sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list
|
||||
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 0)) // disable festive backpacks
|
||||
//(0 to 255).foreach(i => { sendResponse(SetEmpireMessage(PlanetSideGUID(i), PlanetSideEmpire.VS)) })
|
||||
|
||||
|
|
@ -3339,6 +3695,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case None => false
|
||||
}
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, msg, spectator, wepInHand))
|
||||
updateSquad()
|
||||
}
|
||||
|
||||
case msg @ ChildObjectStateMessage(object_guid, pitch, yaw) =>
|
||||
|
|
@ -3380,6 +3737,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
obj.Cloaked = obj.Definition.CanCloak && is_cloaked
|
||||
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, pos, ang, vel, flight, unk6, unk7, wheels, unk9, is_cloaked))
|
||||
}
|
||||
updateSquad()
|
||||
case (None, _) =>
|
||||
//log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
|
||||
//TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
|
||||
|
|
@ -4466,7 +4824,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
log.info(s"GenericObject: $player is MAX with an unexpected weapon - ${definition.Name}")
|
||||
}
|
||||
}
|
||||
else if(action == 16) {
|
||||
else if(action == 16) { //max deployment
|
||||
log.info(s"GenericObject: $player has released the anchors")
|
||||
player.UsingSpecial = SpecialExoSuitDefinition.Mode.Normal
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 19, 0))
|
||||
|
|
@ -4484,6 +4842,30 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
log.info(s"GenericObject: $player is MAX with an unexpected weapon - ${definition.Name}")
|
||||
}
|
||||
}
|
||||
else if(action == 36) { //Looking For Squad ON
|
||||
if(squadUI.nonEmpty) {
|
||||
if(!lfsm && squadUI(player.CharId).index == 0) {
|
||||
lfsm = true
|
||||
avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 53, 1))
|
||||
}
|
||||
}
|
||||
else if(!avatar.LFS) {
|
||||
avatar.LFS = true
|
||||
avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 53, 1))
|
||||
}
|
||||
}
|
||||
else if(action == 37) { //Looking For Squad OFF
|
||||
if(squadUI.nonEmpty) {
|
||||
if(lfsm && squadUI(player.CharId).index == 0) {
|
||||
lfsm = false
|
||||
avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 53, 0))
|
||||
}
|
||||
}
|
||||
else if(avatar.LFS) {
|
||||
avatar.LFS = false
|
||||
avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 53, 0))
|
||||
}
|
||||
}
|
||||
|
||||
case msg @ ItemTransactionMessage(terminal_guid, transaction_type, _, _, _, _) =>
|
||||
log.info("ItemTransaction: " + msg)
|
||||
|
|
@ -4522,18 +4904,18 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
None
|
||||
}) match {
|
||||
case Some(owner : Player) => //InfantryLoadout
|
||||
avatar.SaveLoadout(owner, name, lineno)
|
||||
avatar.EquipmentLoadouts.SaveLoadout(owner, name, lineno)
|
||||
import InfantryLoadout._
|
||||
sendResponse(FavoritesMessage(list, player_guid, line, name, DetermineSubtypeB(player.ExoSuit, DetermineSubtype(player))))
|
||||
case Some(owner : Vehicle) => //VehicleLoadout
|
||||
avatar.SaveLoadout(owner, name, lineno)
|
||||
avatar.EquipmentLoadouts.SaveLoadout(owner, name, lineno)
|
||||
sendResponse(FavoritesMessage(list, player_guid, line, name))
|
||||
case Some(_) | None =>
|
||||
log.error("FavoritesRequest: unexpected owner for favorites")
|
||||
}
|
||||
|
||||
case FavoritesAction.Delete =>
|
||||
avatar.DeleteLoadout(lineno)
|
||||
avatar.EquipmentLoadouts.DeleteLoadout(lineno)
|
||||
sendResponse(FavoritesMessage(list, player_guid, line, ""))
|
||||
|
||||
case FavoritesAction.Unknown =>
|
||||
|
|
@ -4778,8 +5160,17 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case msg @ AvatarGrenadeStateMessage(player_guid, state) =>
|
||||
log.info("AvatarGrenadeStateMessage: " + msg)
|
||||
|
||||
case msg @ SquadDefinitionActionMessage(a, b, c, d, e, f, g, h, i) =>
|
||||
log.info("SquadDefinitionAction: " + msg)
|
||||
case msg @ SquadDefinitionActionMessage(u1, u2, action) =>
|
||||
log.info(s"SquadDefinitionAction: $msg")
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Definition(u1, u2, action))
|
||||
|
||||
case msg @ SquadMembershipRequest(request_type, unk2, unk3, player_name, unk5) =>
|
||||
log.info(s"$msg")
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Membership(request_type, unk2, unk3, player_name, unk5))
|
||||
|
||||
case msg @ SquadWaypointRequest(request, _, wtype, unk, info) =>
|
||||
log.info(s"Waypoint Request: $msg")
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Waypoint(request, wtype, unk, info))
|
||||
|
||||
case msg @ GenericCollisionMsg(u1, p, t, php, thp, pv, tv, ppos, tpos, u2, u3, u4) =>
|
||||
log.info("Ouch! " + msg)
|
||||
|
|
@ -7520,9 +7911,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
/**
|
||||
* Properly format a `DestroyDisplayMessage` packet
|
||||
* given sufficient information about a target (victim) and an actor (killer).
|
||||
* For the packet, the `*_charId` field is most important to determining distinction between players.
|
||||
* The "char id" is not a currently supported field for different players so a name hash is used instead.
|
||||
* The virtually negligent chance of a name hash collision is covered.
|
||||
* For the packet, the `charId` field is important for determining distinction between players.
|
||||
* @param killer the killer's entry
|
||||
* @param victim the victim's entry
|
||||
* @param method the manner of death
|
||||
|
|
@ -7531,13 +7920,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
* @return a `DestroyDisplayMessage` packet that is properly formatted
|
||||
*/
|
||||
def DestroyDisplayMessage(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int = 121) : DestroyDisplayMessage = {
|
||||
//TODO charId should reflect the player more properly
|
||||
val killerCharId = math.abs(killer.Name.hashCode)
|
||||
var victimCharId = math.abs(victim.Name.hashCode)
|
||||
if(killerCharId == victimCharId && !killer.Name.equals(victim.Name)) {
|
||||
//odds of hash collision in a populated zone should be close to odds of being struck by lightning
|
||||
victimCharId = Int.MaxValue - victimCharId + 1
|
||||
}
|
||||
val killer_seated = killer match {
|
||||
case obj : PlayerSource => obj.Seated
|
||||
case _ => false
|
||||
|
|
@ -7547,9 +7929,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case _ => false
|
||||
}
|
||||
new DestroyDisplayMessage(
|
||||
killer.Name, killerCharId, killer.Faction, killer_seated,
|
||||
killer.Name, killer.CharId, killer.Faction, killer_seated,
|
||||
unk, method,
|
||||
victim.Name, victimCharId, victim.Faction, victim_seated
|
||||
victim.Name, victim.CharId, victim.Faction, victim_seated
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -8478,6 +8860,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
})
|
||||
DisownDeployables()
|
||||
drawDeloyableIcon = RedrawDeployableIcons //important for when SetCurrentAvatar initializes the UI next zone
|
||||
squadSetup = ZoneChangeSquadSetup
|
||||
continent.Population ! Zone.Population.Leave(avatar)
|
||||
}
|
||||
|
||||
|
|
@ -8947,6 +9330,76 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
}
|
||||
|
||||
def SwapSquadUIElements(squad : Squad, fromIndex : Int, toIndex : Int) : Unit = {
|
||||
if(squadUI.nonEmpty) {
|
||||
val fromMember = squad.Membership(toIndex) //the players have already been swapped in the backend object
|
||||
val fromCharId = fromMember.CharId
|
||||
val toMember = squad.Membership(fromIndex) //the players have already been swapped in the backend object
|
||||
val toCharId = toMember.CharId
|
||||
val id = 11
|
||||
if(toCharId > 0) {
|
||||
//toMember and fromMember have swapped places
|
||||
val fromElem = squadUI(fromCharId)
|
||||
val toElem = squadUI(toCharId)
|
||||
squadUI(toCharId) = SquadUIElement(fromElem.name, toIndex, fromElem.zone, fromElem.health, fromElem.armor, fromElem.position)
|
||||
squadUI(fromCharId) = SquadUIElement(toElem.name, fromIndex, toElem.zone, toElem.health, toElem.armor, toElem.position)
|
||||
sendResponse(SquadMemberEvent.Add(id, toCharId, toIndex, fromElem.name, fromElem.zone, unk7 = 0))
|
||||
sendResponse(SquadMemberEvent.Add(id, fromCharId, fromIndex, toElem.name, toElem.zone, unk7 = 0))
|
||||
sendResponse(
|
||||
SquadState(
|
||||
PlanetSideGUID(id),
|
||||
List(
|
||||
SquadStateInfo(fromCharId, toElem.health, toElem.armor, toElem.position, 2, 2, false, 429, None, None),
|
||||
SquadStateInfo(toCharId, fromElem.health, fromElem.armor, fromElem.position, 2, 2, false, 429, None, None)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
else {
|
||||
//previous fromMember has moved toMember
|
||||
val elem = squadUI(fromCharId)
|
||||
squadUI(fromCharId) = SquadUIElement(elem.name, toIndex, elem.zone, elem.health, elem.armor, elem.position)
|
||||
sendResponse(SquadMemberEvent.Remove(id, fromCharId, fromIndex))
|
||||
sendResponse(SquadMemberEvent.Add(id, fromCharId, toIndex, elem.name, elem.zone, unk7 = 0))
|
||||
sendResponse(
|
||||
SquadState(
|
||||
PlanetSideGUID(id),
|
||||
List(SquadStateInfo(fromCharId, elem.health, elem.armor, elem.position, 2, 2, false, 429, None, None))
|
||||
)
|
||||
)
|
||||
}
|
||||
val charId = avatar.CharId
|
||||
if(toCharId == charId) {
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, toIndex))
|
||||
}
|
||||
else if(fromCharId == charId) {
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, fromIndex))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def NoSquadUpdates() : Unit = { }
|
||||
|
||||
def SquadUpdates() : Unit = {
|
||||
squadService ! SquadServiceMessage(
|
||||
player,
|
||||
continent,
|
||||
continent.GUID(player.VehicleSeated) match {
|
||||
case Some(vehicle : Vehicle) =>
|
||||
SquadServiceAction.Update(player.CharId, vehicle.Health, vehicle.MaxHealth, vehicle.Shields, vehicle.MaxShields, vehicle.Position, continent.Number)
|
||||
case Some(obj : PlanetSideGameObject with WeaponTurret) =>
|
||||
SquadServiceAction.Update(player.CharId, obj.Health, obj.MaxHealth, 0, 0, obj.Position, continent.Number)
|
||||
case _ =>
|
||||
SquadServiceAction.Update(player.CharId, player.Health, player.MaxHealth, player.Armor, player.MaxArmor, player.Position, continent.Number)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
def PeriodicUpdatesWhenEnrolledInSquad() : Unit = {
|
||||
queuedSquadActions(squadUpdateCounter)()
|
||||
squadUpdateCounter = (squadUpdateCounter + 1) % queuedSquadActions.length
|
||||
}
|
||||
|
||||
def failWithError(error : String) = {
|
||||
log.error(error)
|
||||
sendResponse(ConnectionClose())
|
||||
|
|
@ -9103,6 +9556,8 @@ object WorldSessionActor {
|
|||
completeAction : () => Unit,
|
||||
tickAction : Option[() => Unit] = None)
|
||||
|
||||
protected final case class SquadUIElement(name : String, index : Int, zone : Int, health : Int, armor : Int, position : Vector3)
|
||||
|
||||
private final case class NtuCharging(tplayer: Player,
|
||||
vehicle: Vehicle)
|
||||
private final case class NtuDischarging(tplayer: Player, vehicle: Vehicle, silo_guid: PlanetSideGUID)
|
||||
|
|
|
|||
|
|
@ -485,7 +485,7 @@ class PacketCodingActorITest extends ActorTest {
|
|||
)
|
||||
val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None)
|
||||
val pkt = MultiPacketBundle(List(ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj)))
|
||||
val string_hex = hex"000900001879060000bc84b000000000000000000002040000097049006c006c006c004900490049006c006c006c0049006c0049006c006c0049006c006c006c0049006c006c00490084524000000000000000000000000000000020000007f35703fffffffffffffffffffffffffffffffc000000000000000000000000000000000000000190019000640000000000c800c80000000000000000000000000000000000000001c00042c54686c7000000000000000000000000000000000000000000000000000000000000000000000400e0"
|
||||
val string_hex = hex"00090000186c060000bc84b000000000000000000002040000097049006c006c006c004900490049006c006c006c0049006c0049006c006c0049006c006c006c0049006c006c00490084524000000000000000000000000000000020000007f35703fffffffffffffffffffffffffffffffc000000000000000000000000000000000000000190019000640000000000c800c80000000000000000000000000000000000000001c00042c54686c7000000000000000000000000000000000000000000000000000000000000100000000400e0"
|
||||
|
||||
"PacketCodingActor" should {
|
||||
"bundle an r-originating packet into an l-facing SlottedMetaPacket byte stream data (SlottedMetaPacket)" in {
|
||||
|
|
|
|||
Loading…
Reference in a new issue