commit before changes, and to switch gears momentarily; making charId somewhat native to Avatar and its converter, though not yet essential; protype for squad definition loadouts, even though no example of functionality exists; a lot of unimplemented work in preparation to convert SquadDetailDefinitionUpdateMessage from a static format to a variable-field format

This commit is contained in:
FateJH 2019-06-18 14:43:26 -04:00
parent 14bdcb7a7e
commit fd9a3a0216
22 changed files with 938 additions and 131 deletions

View file

@ -1,10 +1,9 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects 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.definition.{AvatarDefinition, ImplantDefinition}
import net.psforever.objects.equipment.{EquipmentSize, EquipmentSlot} import net.psforever.objects.equipment.{EquipmentSize, EquipmentSlot}
import net.psforever.objects.loadouts.Loadout
import net.psforever.packet.game.objectcreate.Cosmetics import net.psforever.packet.game.objectcreate.Cosmetics
import net.psforever.types._ import net.psforever.types._
@ -12,6 +11,8 @@ import scala.annotation.tailrec
import scala.collection.mutable 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(val name : String, val faction : PlanetSideEmpire.Value, val sex : CharacterGender.Value, val head : Int, val voice : CharacterVoice.Value) {
/** Character ID; a unique identifier corresponding to a database table row index */
private var charId : Long = 0
/** Battle Experience Points */ /** Battle Experience Points */
private var bep : Long = 0 private var bep : Long = 0
/** Command Experience Points */ /** Command Experience Points */
@ -29,11 +30,15 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
* @see `DetailedCharacterData.implants` * @see `DetailedCharacterData.implants`
*/ */
private val implants : Array[ImplantSlot] = Array.fill[ImplantSlot](3)(new ImplantSlot) private val implants : Array[ImplantSlot] = Array.fill[ImplantSlot](3)(new ImplantSlot)
/** Loadouts<br> /** Equipment Loadouts<br>
* 0-9 are Infantry loadouts * 0-9 are Infantry loadouts<br>
* 10-14 are Vehicle loadouts * 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 */ /** Locker */
private val locker : LockerContainer = new LockerContainer() { private val locker : LockerContainer = new LockerContainer() {
override def toString : String = { override def toString : String = {
@ -43,6 +48,15 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
private val deployables : DeployableToolbox = new DeployableToolbox private val deployables : DeployableToolbox = new DeployableToolbox
def CharId : Long = charId
def CharId_=(id : Long) : Long = {
if(charId == 0) { //does not need to be set but can only set once
charId = id
}
CharId
}
def BEP : Long = bep def BEP : Long = bep
def BEP_=(battleExperiencePoints : Long) : Long = { def BEP_=(battleExperiencePoints : Long) : Long = {
@ -164,25 +178,9 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
}) })
} }
def SaveLoadout(owner : Player, label : String, line : Int) : Unit = { def EquipmentLoadouts : LoadoutManager = equipmentLoadouts
if(line > -1 && line < 10) {
loadouts(line) = Some(Loadout.Create(owner, label))
}
}
def SaveLoadout(owner : Vehicle, label : String, line : Int) : Unit = { def SquadLoadouts : LoadoutManager = squadLoadouts
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 Locker : LockerContainer = locker def Locker : LockerContainer = locker

View file

@ -1,10 +1,10 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.avatar.LoadoutManager
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition} import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition}
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot} import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} 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.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.resistance.ResistanceProfile import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.objects.vital.{DamageResistanceModel, Vitality} import net.psforever.objects.vital.{DamageResistanceModel, Vitality}
@ -58,6 +58,8 @@ class Player(private val core : Avatar) extends PlanetSideGameObject
Player.SuitSetup(this, exosuit) Player.SuitSetup(this, exosuit)
def CharId : Long = core.CharId
def Name : String = core.name def Name : String = core.name
def Faction : PlanetSideEmpire.Value = core.faction def Faction : PlanetSideEmpire.Value = core.faction
@ -294,7 +296,9 @@ class Player(private val core : Avatar) extends PlanetSideGameObject
def RadiationShielding = exosuit.RadiationShielding 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 def BEP : Long = core.BEP

View file

@ -0,0 +1,27 @@
// 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) =>
entries(line) = Some(loadout)
case _ => ;
}
}
def LoadLoadout(line : Int) : Option[Loadout] = entries.lift(line).flatten
def DeleteLoadout(line : Int) : Unit = {
entries(line) = None
}
def Loadouts : Seq[(Int, Loadout)] = entries.zipWithIndex.collect { case(Some(loadout), index) => (index, loadout) } toSeq
}

View file

@ -81,7 +81,7 @@ object AvatarConverter {
), ),
obj.ExoSuit, obj.ExoSuit,
0, 0,
0L, obj.CharId,
0, 0,
0, 0,
0, 0,

View file

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

View file

@ -28,7 +28,7 @@ final case class InfantryLoadout(label : String,
visible_slots : List[Loadout.SimplifiedEntry], visible_slots : List[Loadout.SimplifiedEntry],
inventory : List[Loadout.SimplifiedEntry], inventory : List[Loadout.SimplifiedEntry],
exosuit : ExoSuitType.Value, exosuit : ExoSuitType.Value,
subtype : Int) extends Loadout(label, visible_slots, inventory) subtype : Int) extends EquipmentLoadout(label, visible_slots, inventory)
object InfantryLoadout { object InfantryLoadout {
import net.psforever.objects.Player import net.psforever.objects.Player

View file

@ -5,30 +5,29 @@ import net.psforever.objects._
import net.psforever.objects.definition._ import net.psforever.objects.definition._
import net.psforever.objects.equipment.{Equipment, EquipmentSlot} import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.objects.inventory.InventoryItem import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.teamwork.Squad
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.util.{Failure, Success, Try}
/** /**
* The base of all specific kinds of blueprint containers. * 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 * This previous state can be restored on any appropriate template from which the loadout was copied
* by reconstructing the items (if permitted). * 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 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, abstract class Loadout(label : String)
visible_slots : List[Loadout.SimplifiedEntry],
inventory : List[Loadout.SimplifiedEntry])
object Loadout { 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, label))
case _ => Failure(new MatchError(s"can not create a loadout based on the (current status of) $owner"))
}
}
/** /**
* Produce the blueprint on a player. * Produce the blueprint on a player.
* @param player the player * @param player the player
@ -54,12 +53,32 @@ object Loadout {
def Create(vehicle : Vehicle, label : String) : Loadout = { def Create(vehicle : Vehicle, label : String) : Loadout = {
VehicleLoadout( VehicleLoadout(
label, 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), packageSimplifications(vehicle.Trunk.Items),
vehicle.Definition vehicle.Definition
) )
} }
/**
* na
*/
def Create(squad : Squad, label : String) : Loadout = {
SquadLoadout(
label,
squad.Task,
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. * A basic `Trait` connecting all of the `Equipment` blueprints.
*/ */

View file

@ -0,0 +1,11 @@
// 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(label : String,
task : String,
zone_id : Option[Int],
members : List[SquadPositionLoadout]) extends Loadout(label)

View file

@ -24,4 +24,4 @@ import net.psforever.objects.definition._
final case class VehicleLoadout(label : String, final case class VehicleLoadout(label : String,
visible_slots : List[Loadout.SimplifiedEntry], visible_slots : List[Loadout.SimplifiedEntry],
inventory : 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)

View file

@ -258,7 +258,7 @@ object OrderTerminalDefinition {
//TODO block equipment by blocking ammunition type //TODO block equipment by blocking ammunition type
final case class InfantryLoadoutPage() extends LoadoutTab { final case class InfantryLoadoutPage() extends LoadoutTab {
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { 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)) => case Some(loadout : InfantryLoadout) if !Exclude.contains(loadout.exosuit) && !Exclude.contains((loadout.exosuit, loadout.subtype)) =>
val holsters = loadout.visible_slots val holsters = loadout.visible_slots
.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) .map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
@ -287,7 +287,7 @@ object OrderTerminalDefinition {
*/ */
final case class VehicleLoadoutPage() extends LoadoutTab { final case class VehicleLoadoutPage() extends LoadoutTab {
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { 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) => case Some(loadout : VehicleLoadout) if !Exclude.contains(loadout.vehicle_definition) =>
val weapons = loadout.visible_slots val weapons = loadout.visible_slots
.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) .map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })

View file

@ -10,6 +10,7 @@ class Member {
private var requirements : Set[CertificationType.Value] = Set() private var requirements : Set[CertificationType.Value] = Set()
//about the individual filling the position //about the individual filling the position
private var name : String = "" private var name : String = ""
private var charId : Long = 0L
private var health : Int = 0 private var health : Int = 0
private var armor : Int = 0 private var armor : Int = 0
private var zoneId : Int = 0 private var zoneId : Int = 0
@ -43,6 +44,13 @@ class Member {
Name Name
} }
def CharId : Long = charId
def CharId_=(id : Long) : Long = {
charId = id
CharId
}
def Health : Int = health def Health : Int = health
def Health_=(red : Int) : Int = { def Health_=(red : Int) : Int = {

View file

@ -366,7 +366,7 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
case 2 | 6 | 9 | 11 | case 2 | 6 | 9 | 11 |
12 | 13 | 14 | 16 | 17 | 12 | 13 | 14 | 16 | 17 |
18 | 29 | 30 | 32 | 33 | 18 | 29 | 30 | 32 | 33 |
36 | 37 | 38 | 42 | 43 => unknownCodec(code) 36 | 37 | 38 | 39 | 42 | 43 => unknownCodec(code)
case _ => failureCodec(code) case _ => failureCodec(code)
}).asInstanceOf[Codec[SquadAction]] }).asInstanceOf[Codec[SquadAction]]
} }

View file

@ -1,6 +1,7 @@
// Copyright (c) 2019 PSForever // Copyright (c) 2019 PSForever
package net.psforever.packet.game package net.psforever.packet.game
import net.psforever.packet.game.SquadDetailDefinitionUpdateMessage.defaultRequirements
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.CertificationType import net.psforever.types.CertificationType
import scodec.bits.BitVector import scodec.bits.BitVector
@ -18,7 +19,9 @@ final case class SquadPositionDetail(is_closed : Boolean,
name : String) name : String)
final case class SquadDetailDefinitionUpdateMessage(guid : PlanetSideGUID, final case class SquadDetailDefinitionUpdateMessage(guid : PlanetSideGUID,
unk : BitVector, unk1 : Int,
leader_char_id : Long,
unk2 : BitVector,
leader_name : String, leader_name : String,
task : String, task : String,
zone_id : PlanetSideZoneID, zone_id : PlanetSideZoneID,
@ -32,27 +35,17 @@ final case class SquadDetailDefinitionUpdateMessage(guid : PlanetSideGUID,
object SquadPositionDetail { object SquadPositionDetail {
final val Closed : SquadPositionDetail = SquadPositionDetail(is_closed = true, "", "", Set.empty, 0L, "") final val Closed : SquadPositionDetail = SquadPositionDetail(is_closed = true, "", "", Set.empty, 0L, "")
private def reliableNameHash(name : String) : Long = {
val hash = name.hashCode.toLong
if(hash < 0) {
-1L * hash
}
else {
hash
}
}
def apply() : SquadPositionDetail = SquadPositionDetail(is_closed = false, "", "", Set.empty, 0L, "") def apply() : SquadPositionDetail = SquadPositionDetail(is_closed = false, "", "", Set.empty, 0L, "")
def apply(name : String) : SquadPositionDetail = SquadPositionDetail(is_closed = false, "", "", Set.empty, reliableNameHash(name), name) def apply(char_id : Long, name : String) : SquadPositionDetail = SquadPositionDetail(is_closed = false, "", "", Set.empty, char_id, name)
def apply(role : String, detailed_orders : String) : SquadPositionDetail = SquadPositionDetail(is_closed = false, role, detailed_orders, Set.empty, 0L, "") def apply(role : String, detailed_orders : String) : SquadPositionDetail = SquadPositionDetail(is_closed = false, role, detailed_orders, Set.empty, 0L, "")
def apply(role : String, detailed_orders : String, requirements : Set[CertificationType.Value]) : SquadPositionDetail = SquadPositionDetail(is_closed = false, role, detailed_orders, requirements, 0L, "") def apply(role : String, detailed_orders : String, requirements : Set[CertificationType.Value]) : SquadPositionDetail = SquadPositionDetail(is_closed = false, role, detailed_orders, requirements, 0L, "")
def apply(role : String, detailed_orders : String, name : String) : SquadPositionDetail = SquadPositionDetail(is_closed = false, role, detailed_orders, Set.empty, reliableNameHash(name), name) def apply(role : String, detailed_orders : String, char_id : Long, name : String) : SquadPositionDetail = SquadPositionDetail(is_closed = false, role, detailed_orders, Set.empty, char_id, name)
def apply(role : String, detailed_orders : String, requirements : Set[CertificationType.Value], name : String) : SquadPositionDetail = SquadPositionDetail(is_closed = false, role, detailed_orders, requirements, reliableNameHash(name), name) def apply(role : String, detailed_orders : String, requirements : Set[CertificationType.Value], char_id : Long, name : String) : SquadPositionDetail = SquadPositionDetail(is_closed = false, role, detailed_orders, requirements, char_id, name)
} }
object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefinitionUpdateMessage] { object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefinitionUpdateMessage] {
@ -64,6 +57,7 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
final val Init = SquadDetailDefinitionUpdateMessage( final val Init = SquadDetailDefinitionUpdateMessage(
PlanetSideGUID(0), PlanetSideGUID(0),
0L,
"", "",
"", "",
PlanetSideZoneID(0), PlanetSideZoneID(0),
@ -81,9 +75,9 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
) )
) )
def apply(guid : PlanetSideGUID, leader_name : String, task : String, zone_id : PlanetSideZoneID, member_info : List[SquadPositionDetail]) : SquadDetailDefinitionUpdateMessage = { def apply(guid : PlanetSideGUID, char_id : Long, leader_name : String, task : String, zone_id : PlanetSideZoneID, member_info : List[SquadPositionDetail]) : SquadDetailDefinitionUpdateMessage = {
import scodec.bits._ import scodec.bits._
SquadDetailDefinitionUpdateMessage(guid, hex"080000000000000000000".toBitVector, leader_name, task, zone_id, member_info) SquadDetailDefinitionUpdateMessage(guid, 1, char_id, hex"000000".toBitVector, leader_name, task, zone_id, member_info)
} }
private def memberCodec(pad : Int) : Codec[SquadPositionDetail] = { private def memberCodec(pad : Int) : Codec[SquadPositionDetail] = {
@ -191,9 +185,14 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
import shapeless.:: import shapeless.::
( (
("guid" | PlanetSideGUID.codec) :: ("guid" | PlanetSideGUID.codec) ::
uint8 :: uint4 :: //constant = 8
uint4 :: uint4 :: //variable = 0-4
bits(83) :: //variable fields, but can be 0'd bool :: //true, when 4
uint4 :: //variable = 0-12?
("unk1" | uint4) ::
uint24 :: //unknown, but can be 0'd
("leader_char_id" | uint32L) ::
("unk2" | bits(22)) :: //variable fields, but can be 0'd
uint(10) :: //constant = 0 uint(10) :: //constant = 0
("leader" | PacketHelpers.encodedWideStringAligned(7)) :: ("leader" | PacketHelpers.encodedWideStringAligned(7)) ::
("task" | PacketHelpers.encodedWideString) :: ("task" | PacketHelpers.encodedWideString) ::
@ -202,15 +201,721 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
optional(bool, "member_info" | initial_member_codec) optional(bool, "member_info" | initial_member_codec)
).exmap[SquadDetailDefinitionUpdateMessage] ( ).exmap[SquadDetailDefinitionUpdateMessage] (
{ {
case guid :: _ :: _ :: _ :: _ :: leader :: task :: zone :: _ :: Some(member_list) :: HNil => case guid :: _ :: _ :: _ :: _ :: u1 :: _ :: char_id:: u2 :: _ :: leader :: task :: zone :: _ :: Some(member_list) :: HNil =>
Attempt.Successful(SquadDetailDefinitionUpdateMessage(guid, leader, task, zone, unlinkMemberList(member_list))) Attempt.Successful(SquadDetailDefinitionUpdateMessage(guid, u1, char_id, u2, leader, task, zone, unlinkMemberList(member_list)))
case data => case data =>
Attempt.failure(Err(s"can not get squad detail definition from data $data")) Attempt.failure(Err(s"can not get squad detail definition from data $data"))
}, },
{ {
case SquadDetailDefinitionUpdateMessage(guid, unk, leader, task, zone, member_list) => case SquadDetailDefinitionUpdateMessage(guid, unk1, char_id, unk2, leader, task, zone, member_list) =>
Attempt.Successful(guid :: 132 :: 8 :: unk.take(83) :: 0 :: leader :: task :: zone :: 4983296 :: Some(linkMemberList(member_list.reverse)) :: HNil) Attempt.Successful(guid :: 8 :: 4 :: true :: 0 :: math.max(unk1, 1) :: 0 :: char_id :: unk2.take(22) :: 0 :: leader :: task :: zone :: 4983296 :: Some(linkMemberList(member_list.reverse)) :: HNil)
} }
) )
} }
} }
//NEW FORM SquadDetailDefinitionUpdateMessage
private class StreamLengthToken(init : Int = 0) {
private var bitLength : Int = init
def Length : Int = bitLength
def Length_=(toLength : Int) : StreamLengthToken = {
bitLength = toLength
this
}
def Add(more : Int) : StreamLengthToken = {
bitLength += more
this
}
}
final case class SquadPositionDetail2(is_closed : Option[Boolean],
role : Option[String],
detailed_orders : Option[String],
requirements : Option[Set[CertificationType.Value]],
char_id : Option[Long],
name : Option[String]) {
def And(info : SquadPositionDetail2) : SquadPositionDetail2 = {
SquadPositionDetail2(
is_closed match {
case Some(true) | None =>
info.is_closed.orElse(is_closed)
case _ =>
Some(false)
},
role.orElse(info.role),
detailed_orders.orElse(info.detailed_orders),
requirements.orElse(info.requirements),
char_id.orElse(info.char_id),
name.orElse(info.name)
)
}
}
final case class SquadPositionEntry(index : Int, info : Option[SquadPositionDetail2])
final case class SquadDetail(unk1 : Option[Int],
leader_char_id : Option[Long],
unk2 : Option[BitVector],
leader_name : Option[String],
task : Option[String],
zone_id : Option[PlanetSideZoneID],
member_info : Option[List[SquadPositionEntry]]) {
def And(info : SquadDetail) : SquadDetail = {
SquadDetail(
unk1.orElse(info.unk1),
leader_char_id.orElse(info.leader_char_id),
unk2.orElse(info.unk2),
leader_name.orElse(info.leader_name),
task.orElse(info.task),
zone_id.orElse(info.zone_id),
(member_info, info.member_info) match {
case (Some(info1), Some(info2)) => Some(info1 ++ info2)
case (Some(info1), _) => Some(info1)
case (None, Some(info2)) => Some(info2)
case _ => None
}
)
}
}
final case class SquadDetailDefinitionUpdateMessage2(guid : PlanetSideGUID,
detail : SquadDetail)
object SquadPositionDetail2 {
final val Blank : SquadPositionDetail2 = SquadPositionDetail2()
final val Closed : SquadPositionDetail2 = SquadPositionDetail2(is_closed = Some(true), None, None, None, None, None)
def apply() : SquadPositionDetail2 = SquadPositionDetail2(is_closed = Some(false), None, None, None, None, None)
def apply(role : String, detailed_orders : Option[String]) : SquadPositionDetail2 = SquadPositionDetail2(is_closed = Some(false), Some(role), detailed_orders, None, None, None)
def apply(role : Option[String], detailed_orders : String) : SquadPositionDetail2 = SquadPositionDetail2(is_closed = Some(false), role, Some(detailed_orders), None, None, None)
def apply(char_id : Long) : SquadPositionDetail2 = SquadPositionDetail2(is_closed = Some(false), None, None, None, Some(char_id), None)
def apply(name : String) : SquadPositionDetail2 = SquadPositionDetail2(is_closed = Some(false), None, None, None, None, Some(name))
def apply(requirements : Set[CertificationType.Value]) : SquadPositionDetail2 = SquadPositionDetail2(is_closed = Some(false), None, None, Some(requirements), None, None)
def apply(role : String, detailed_orders : String, requirements : Set[CertificationType.Value], char_id : Long, name : String) : SquadPositionDetail2 = SquadPositionDetail2(is_closed = Some(false), Some(role), Some(detailed_orders), Some(requirements), Some(char_id), Some(name))
}
object SquadPositionEntry {
import SquadDetailDefinitionUpdateMessage2.paddedStringMetaCodec
private def roleCodec(bitsOverByte : StreamLengthToken) : Codec[SquadPositionDetail2] = paddedStringMetaCodec(bitsOverByte.Length).exmap[SquadPositionDetail2] (
role => Attempt.successful(SquadPositionDetail2(role)),
{
case SquadPositionDetail2(_, Some(role), _, _, _, _) =>
Attempt.successful(role)
case _ =>
Attempt.failure(Err("failed to encode squad position data for role"))
}
)
private def ordersCodec(bitsOverByte : StreamLengthToken) : Codec[SquadPositionDetail2] = paddedStringMetaCodec(bitsOverByte.Length).exmap[SquadPositionDetail2] (
orders => Attempt.successful(SquadPositionDetail2(None, orders)),
{
case SquadPositionDetail2(_, _, Some(orders), _, _, _) =>
Attempt.successful(orders)
case _ =>
Attempt.failure(Err("failed to encode squad position data for detailed orders"))
}
)
private val requirementsCodec : Codec[SquadPositionDetail2] = ulongL(46).exmap[SquadPositionDetail2] (
requirements => Attempt.successful(SquadPositionDetail2(CertificationType.fromEncodedLong(requirements))),
{
case SquadPositionDetail2(_, _, _, Some(requirements), _, _) =>
Attempt.successful(CertificationType.toEncodedLong(requirements))
case _ =>
Attempt.failure(Err("failed to encode squad position data for certification requirements"))
}
)
private val charIdCodec : Codec[SquadPositionDetail2] = uint32L.exmap[SquadPositionDetail2] (
char_id => Attempt.successful(SquadPositionDetail2(char_id)),
{
case SquadPositionDetail2(_, _, _, _, Some(char_id), _) =>
Attempt.successful(char_id)
case _ =>
Attempt.failure(Err("failed to encode squad data for member id"))
}
)
private def nameCodec(bitsOverByte : StreamLengthToken) : Codec[SquadPositionDetail2] = paddedStringMetaCodec(bitsOverByte.Length).exmap[SquadPositionDetail2] (
name => Attempt.successful(SquadPositionDetail2(name)),
{
case SquadPositionDetail2(_, _, _, _, _, Some(orders)) =>
Attempt.successful(orders)
case _ =>
Attempt.failure(Err("failed to encode squad position data for member name"))
}
)
/**
* `Codec` for failing to determine a valid `Codec` based on the entry data.
* This `Codec` is an invalid codec that does not read any bit data.
* The `conditional` will always return `None` because
* its determining conditional statement is explicitly `false`
* and all cases involving explicit failure.
*/
private val failureCodec : Codec[SquadPositionDetail2] = conditional(included = false, bool).exmap[SquadPositionDetail2] (
_ => Attempt.failure(Err("decoding with unhandled codec")),
_ => Attempt.failure(Err("encoding with unhandled codec"))
)
private final case class LinkedSquadPositionInfo(code : Int, info : SquadPositionDetail2, next : Option[LinkedSquadPositionInfo])
private def unlinkSquadPositionInfo(info : LinkedSquadPositionInfo) : SquadPositionDetail2 = unlinkSquadPositionInfo(Some(info))
/**
* Concatenate a `SquadInfo` object chain into a single `SquadInfo` object.
* Recursively visits every link in a `SquadInfo` object chain.
* @param info the current link in the chain
* @param squadInfo the persistent `SquadInfo` concatenation object;
* defaults to `SquadInfo.Blank`
* @return the concatenated `SquadInfo` object
*/
@tailrec
private def unlinkSquadPositionInfo(info : Option[LinkedSquadPositionInfo], squadInfo : SquadPositionDetail2 = SquadPositionDetail2.Blank) : SquadPositionDetail2 = {
info match {
case None =>
squadInfo
case Some(sqInfo) =>
unlinkSquadPositionInfo(sqInfo.next, squadInfo And sqInfo.info)
}
}
/**
* Decompose a single `SquadInfo` object into a `SquadInfo` object chain of the original's fields.
* The fields as a linked list are explicitly organized "leader", "task", "zone_id", "size", "capacity,"
* or as "(leader, (task, (zone_id, (size, (capacity, None)))))" when fully populated and composed.
* @param info a `SquadInfo` object that has all relevant fields populated
* @return a linked list of `SquadInfo` objects, each with a single field from the input `SquadInfo` object
*/
private def linkSquadPositionInfo(info : SquadPositionDetail2) : LinkedSquadPositionInfo = {
//import scala.collection.immutable.::
Seq(
(5, SquadPositionDetail2(None, None, None, info.requirements, None, None)),
(4, SquadPositionDetail2(None, None, None, None, None, info.name)),
(3, SquadPositionDetail2(None, None, None, None, info.char_id, None)),
(2, SquadPositionDetail2(None, None, info.detailed_orders, None, None, None)),
(1, SquadPositionDetail2(None, info.role, None, None, None, None)),
(0, SquadPositionDetail2(info.is_closed, None, None, None, None, None))
) //in reverse order so that the linked list is in the correct order
.filterNot { case (_, sqInfo) => sqInfo == SquadPositionDetail2.Blank}
match {
case Nil =>
throw new Exception("no linked list squad position fields encountered where at least one was expected") //bad end
case x :: Nil =>
val (code, squadInfo) = x
LinkedSquadPositionInfo(code, squadInfo, None)
case x :: xs =>
val (code, squadInfo) = x
linkSquadPositionInfo(xs, LinkedSquadPositionInfo(code, squadInfo, None))
}
}
/**
* Decompose a single `SquadInfo` object into a `SquadInfo` object chain of the original's fields.
* The fields as a linked list are explicitly organized "leader", "task", "zone_id", "size", "capacity,"
* or as "(leader, (task, (zone_id, (size, (capacity, None)))))" when fully populated and composed.
* @param infoList a series of paired field codes and `SquadInfo` objects with data in the indicated fields
* @return a linked list of `SquadInfo` objects, each with a single field from the input `SquadInfo` object
*/
@tailrec
private def linkSquadPositionInfo(infoList : Seq[(Int, SquadPositionDetail2)], linkedInfo : LinkedSquadPositionInfo) : LinkedSquadPositionInfo = {
if(infoList.isEmpty) {
linkedInfo
}
else {
val (code, data) = infoList.head
linkSquadPositionInfo(infoList.tail, LinkedSquadPositionInfo(code, data, Some(linkedInfo)))
}
}
private def listing_codec(size : Int, bitsOverByte : StreamLengthToken) : Codec[LinkedSquadPositionInfo] = {
import shapeless.::
(
uint4 >>:~ { code =>
selectCodecAction(code, bitsOverByte.Add(4)) ::
conditional(size - 1 > 0, listing_codec(size - 1, modifyCodecPadValue(code, bitsOverByte)))
}
).xmap[LinkedSquadPositionInfo] (
{
case code :: entry :: next :: HNil =>
LinkedSquadPositionInfo(code, entry, next)
},
{
case LinkedSquadPositionInfo(code, entry, next) =>
code :: entry :: next :: HNil
}
)
}
private def selectCodecAction(code : Int, bitsOverByte : StreamLengthToken) : Codec[SquadPositionDetail2] = {
code match {
case 1 => roleCodec(bitsOverByte)
case 2 => ordersCodec(bitsOverByte)
case 3 => charIdCodec
case 4 => nameCodec(bitsOverByte)
case 5 => requirementsCodec
case _ => failureCodec
}
}
private def modifyCodecPadValue(code : Int, bitsOverByte : StreamLengthToken) : StreamLengthToken = {
code match {
case 1 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
case 2 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
case 3 => bitsOverByte //32u = no added padding
case 4 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
case 5 => bitsOverByte.Add(6) //46u = 5*8u + 6u = additional 6u
case _ => bitsOverByte.Length = Int.MinValue //wildly incorrect
}
}
private def squad_member_details_codec(bitsOverByte : StreamLengthToken) : Codec[LinkedSquadPositionInfo] = {
import shapeless.::
(
uint8 >>:~ { size =>
listing_codec(size, bitsOverByte).hlist
}
).xmap[LinkedSquadPositionInfo] (
{
case _ :: info :: HNil =>
info
},
info => {
var i = 1
var dinfo = info
while(info.next.nonEmpty) {
i += 1
dinfo = info.next.get
}
i :: info :: HNil
}
)
}
def codec(bitsOverByte : StreamLengthToken) : Codec[SquadPositionEntry] = {
import shapeless.::
(
("index" | uint8) >>:~ { index =>
conditional(index < 255, bool) >>:~ { is_open =>
conditional(is_open.contains(true) && index < 255, "info" | squad_member_details_codec(bitsOverByte.Add(1))).hlist
}
}
).xmap[SquadPositionEntry] (
{
case 255 :: _ :: _ :: HNil =>
SquadPositionEntry(255, None)
case ndx :: Some(false) :: None :: HNil =>
SquadPositionEntry(ndx, None)
case ndx :: Some(true) :: Some(info) :: HNil =>
SquadPositionEntry(ndx, Some(unlinkSquadPositionInfo(info)))
},
{
case SquadPositionEntry(255, _) =>
255 :: None :: None :: HNil
case SquadPositionEntry(ndx, None) =>
ndx :: Some(false) :: None :: HNil
case SquadPositionEntry(ndx, Some(info)) =>
ndx :: Some(true) :: Some(linkSquadPositionInfo(info)) :: HNil
}
)
}
}
object SquadDetail {
final val Blank = SquadDetail(None, None, None, None, None, None, None)
def apply(leader_char_id : Long) : SquadDetail = SquadDetail(None, Some(leader_char_id), None, None, None, None, None)
def apply(leader_name : String, task : Option[String]) : SquadDetail = SquadDetail(None, None, None, Some(leader_name), task, None, None)
def apply(leader_name : Option[String], task : String) : SquadDetail = SquadDetail(None, None, None, leader_name, Some(task), None, None)
def apply(zone_id : PlanetSideZoneID) : SquadDetail = SquadDetail(None, None, None, None, None, Some(zone_id), None)
def apply(member_list : List[SquadPositionEntry]) : SquadDetail = SquadDetail(None, None, None, None, None, None, Some(member_list))
def apply(unk1 : Int, leader_char_id : Long, unk2 : BitVector, leader_name : String, task : String, zone_id : PlanetSideZoneID, member_info : List[SquadPositionEntry]) : SquadDetail = {
SquadDetail(Some(unk1), Some(leader_char_id), Some(unk2), Some(leader_name), Some(task), Some(zone_id), Some(member_info))
}
}
object SquadDetailDefinitionUpdateMessage2 {
/**
* Produces a `Codec` function for byte-aligned, padded Pascal strings encoded through common manipulations.
* @see `PacketHelpers.encodedWideStringAligned`
* @param bitsOverByte the number of bits past the previous byte-aligned index;
* should be a 0-7 number that gets converted to a 1-7 string padding number
* @return the encoded string `Codec`
*/
def paddedStringMetaCodec(bitsOverByte : Int) : Codec[String] = PacketHelpers.encodedWideStringAligned({
val mod8 = bitsOverByte % 8
if(mod8 == 0) {
0
}
else {
8 - mod8
}
})
private def memberCodec(pad : Int) : Codec[SquadPositionDetail2] = {
import shapeless.::
(
uint8 :: //required value = 6
("is_closed" | bool) :: //if all positions are closed, the squad detail menu display no positions at all
("role" | PacketHelpers.encodedWideStringAligned(pad)) ::
("detailed_orders" | PacketHelpers.encodedWideString) ::
("char_id" | uint32L) ::
("name" | PacketHelpers.encodedWideString) ::
("requirements" | ulongL(46))
).exmap[SquadPositionDetail2] (
{
case 6 :: closed :: role :: orders :: char_id :: name :: requirements :: HNil =>
Attempt.Successful(
SquadPositionDetail2(Some(closed), Some(role), Some(orders), Some(defaultRequirements ++ CertificationType.fromEncodedLong(requirements)), Some(char_id), Some(name))
)
case data =>
Attempt.Failure(Err(s"can not decode a SquadDetailDefinitionUpdate member's data - $data"))
},
{
case SquadPositionDetail2(Some(closed), Some(role), Some(orders), Some(requirements), Some(char_id), Some(name)) =>
Attempt.Successful(6 :: closed :: role :: orders :: char_id :: name :: CertificationType.toEncodedLong(defaultRequirements ++ requirements) :: HNil)
}
)
}
private val first_member_codec : Codec[SquadPositionDetail2] = memberCodec(pad = 7)
private val member_codec : Codec[SquadPositionDetail2] = memberCodec(pad = 0)
private case class LinkedMemberList(member : SquadPositionDetail2, next : Option[LinkedMemberList])
private def subsequent_member_codec : Codec[LinkedMemberList] = {
import shapeless.::
(
//disruptive coupling action (e.g., flatPrepend) is necessary to allow for recursive Codec
("member" | member_codec) >>:~ { _ =>
optional(bool, "next" | subsequent_member_codec).hlist
}
).xmap[LinkedMemberList] (
{
case a :: b :: HNil =>
LinkedMemberList(a, b)
},
{
case LinkedMemberList(a, b) =>
a :: b :: HNil
}
)
}
private def initial_member_codec : Codec[LinkedMemberList] = {
import shapeless.::
(
("member" | first_member_codec) ::
optional(bool, "next" | subsequent_member_codec)
).xmap[LinkedMemberList] (
{
case a :: b :: HNil =>
LinkedMemberList(a, b)
},
{
case LinkedMemberList(a, b) =>
a :: b :: HNil
}
)
}
@tailrec
private def unlinkMemberList(list : LinkedMemberList, out : List[SquadPositionDetail2] = Nil) : List[SquadPositionDetail2] = {
list.next match {
case None =>
out :+ list.member
case Some(next) =>
unlinkMemberList(next, out :+ list.member)
}
}
private def linkMemberList(list : List[SquadPositionDetail2]) : LinkedMemberList = {
list match {
case Nil =>
throw new Exception("")
case x :: Nil =>
LinkedMemberList(x, None)
case x :: xs =>
linkMemberList(xs, LinkedMemberList(x, None))
}
}
@tailrec
private def linkMemberList(list : List[SquadPositionDetail2], out : LinkedMemberList) : LinkedMemberList = {
list match {
case Nil =>
out
case x :: Nil =>
LinkedMemberList(x, Some(out))
case x :: xs =>
linkMemberList(xs, LinkedMemberList(x, Some(out)))
}
}
val full_squad_detail_codec : Codec[SquadDetail] = {
import shapeless.::
(
("unk1" | uint8) ::
uint24 :: //unknown, but can be 0'd
("leader_char_id" | uint32L) ::
("unk2" | bits(22)) :: //variable fields, but can be 0'd
uint(10) :: //constant = 0
("leader" | PacketHelpers.encodedWideStringAligned(7)) ::
("task" | PacketHelpers.encodedWideString) ::
("zone_id" | PlanetSideZoneID.codec) ::
uint(23) :: //constant = 4983296
optional(bool, "member_info" | initial_member_codec)
).exmap[SquadDetail] (
{
case u1 :: _ :: char_id :: u2 :: _ :: leader :: task :: zone :: _ :: Some(member_list) :: HNil =>
Attempt.Successful(
SquadDetail(Some(u1), Some(char_id), Some(u2), Some(leader), Some(task), Some(zone),
Some(unlinkMemberList(member_list).zipWithIndex.map { case (entry, index) => SquadPositionEntry(index, Some(entry)) })
)
)
case data =>
Attempt.failure(Err(s"can not get squad detail definition from data $data"))
},
{
case SquadDetail(Some(u1), Some(char_id), Some(u2), Some(leader), Some(task), Some(zone), Some(member_list)) =>
Attempt.Successful(
math.max(u1, 1) :: 0 :: char_id :: u2.take(22) :: 0 :: leader :: task :: zone :: 4983296 ::
Some(linkMemberList(member_list.collect { case SquadPositionEntry(_, Some(entry)) => entry }.reverse)) ::
HNil
)
}
)
}
private val leaderCharIdCodec : Codec[SquadDetail] = uint32L.exmap[SquadDetail] (
char_id => Attempt.successful(SquadDetail(char_id)),
{
case SquadDetail(_, Some(char_id), _, _, _, _, _) =>
Attempt.successful(char_id)
case _ =>
Attempt.failure(Err("failed to encode squad data for leader id"))
}
)
private def leaderNameCodec(bitsOverByte : StreamLengthToken) : Codec[SquadDetail] = paddedStringMetaCodec(bitsOverByte.Length).exmap[SquadDetail] (
name => Attempt.successful(SquadDetail(name, None)),
{
case SquadDetail(_, _, _, Some(name), _, _, _) =>
Attempt.successful(name)
case _ =>
Attempt.failure(Err("failed to encode squad data for leader name"))
}
)
private def taskCodec(bitsOverByte : StreamLengthToken) : Codec[SquadDetail] = paddedStringMetaCodec(bitsOverByte.Length).exmap[SquadDetail] (
task => Attempt.successful(SquadDetail(None, task)),
{
case SquadDetail(_, _, _, _, Some(task), _, _) =>
Attempt.successful(task)
case _ =>
Attempt.failure(Err("failed to encode squad data for task"))
}
)
private val zoneCodec : Codec[SquadDetail] = PlanetSideZoneID.codec.exmap[SquadDetail] (
zone_id => Attempt.successful(SquadDetail(zone_id)),
{
case SquadDetail(_, _, _, _, _, Some(zone_id), _) =>
Attempt.successful(zone_id)
case _ =>
Attempt.failure(Err("failed to encode squad data for zone id"))
}
)
private def membersCodec(bitsOverByte : StreamLengthToken) : Codec[SquadDetail] = {
import shapeless.::
bitsOverByte.Add(19)
(
uint(19) :: //constant = 0x60040, or 393280 in 19u BE
vector(SquadPositionEntry.codec(bitsOverByte))
).exmap[SquadDetail] (
{
case _ :: member_list :: HNil =>
Attempt.successful(SquadDetail(member_list.toList))
},
{
case SquadDetail(_, _, _, _, _, _, Some(member_list)) =>
Attempt.successful(393280 :: member_list.toVector :: HNil)
case _ =>
Attempt.failure(Err("failed to encode squad data for members"))
}
)
}
private val failureCodec : Codec[SquadDetail] = conditional(included = false, bool).exmap[SquadDetail] (
_ => Attempt.failure(Err("decoding with unhandled codec")),
_ => Attempt.failure(Err("encoding with unhandled codec"))
)
private def selectCodecAction(code : Int, bitsOverByte : StreamLengthToken) : Codec[SquadDetail] = {
code match {
case 2 => leaderCharIdCodec
case 4 => leaderNameCodec(bitsOverByte)
case 5 => taskCodec(bitsOverByte)
case 6 => zoneCodec
case 8 => membersCodec(bitsOverByte)
case _ => failureCodec
}
}
private def modifyCodecPadValue(code : Int, bitsOverByte : StreamLengthToken) : StreamLengthToken = {
code match {
case 2 => bitsOverByte //32u = no added padding
case 4 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
case 5 => bitsOverByte.Length = 0 //byte-aligned string; padding zero'd
case 6 => bitsOverByte //32u = no added padding
case 8 => bitsOverByte.Length = 0 //end of stream
case _ => bitsOverByte.Length = Int.MinValue //wildly incorrect
}
}
private final case class LinkedSquadInfo(code : Int, info : SquadDetail, next : Option[LinkedSquadInfo])
private def unlinkSquadInfo(info : LinkedSquadInfo) : SquadDetail = unlinkSquadInfo(Some(info))
@tailrec
private def unlinkSquadInfo(info : Option[LinkedSquadInfo], squadInfo : SquadDetail = SquadDetail.Blank) : SquadDetail = {
info match {
case None =>
squadInfo
case Some(sqInfo) =>
unlinkSquadInfo(sqInfo.next, squadInfo And sqInfo.info)
}
}
private def linkSquadInfo(info : SquadDetail) : LinkedSquadInfo = {
//import scala.collection.immutable.::
Seq(
(8, SquadDetail(None, None, None, None, None, None, info.member_info)),
(6, SquadDetail(None, None, None, None, None, info.zone_id, None)),
(5, SquadDetail(None, None, None, None, info.task, None, None)),
(4, SquadDetail(None, None, None, info.leader_name, None, None, None)),
(2, SquadDetail(None, info.leader_char_id, None, None, None, None, None))
) //in reverse order so that the linked list is in the correct order
.filterNot { case (_, sqInfo) => sqInfo == SquadDetail.Blank}
match {
case Nil =>
throw new Exception("no linked list squad fields encountered where at least one was expected") //bad end
case x :: Nil =>
val (code, squadInfo) = x
LinkedSquadInfo(code, squadInfo, None)
case x :: xs =>
val (code, squadInfo) = x
linkSquadInfo(xs, LinkedSquadInfo(code, squadInfo, None))
}
}
@tailrec
private def linkSquadInfo(infoList : Seq[(Int, SquadDetail)], linkedInfo : LinkedSquadInfo) : LinkedSquadInfo = {
if(infoList.isEmpty) {
linkedInfo
}
else {
val (code, data) = infoList.head
linkSquadInfo(infoList.tail, LinkedSquadInfo(code, data, Some(linkedInfo)))
}
}
private def linked_squad_detail_codec(size : Int, bitsOverByte : StreamLengthToken) : Codec[LinkedSquadInfo] = {
import shapeless.::
(
uint4 >>:~ { code =>
selectCodecAction(code, bitsOverByte.Add(4)) ::
conditional(size - 1 > 0, linked_squad_detail_codec(size - 1, modifyCodecPadValue(code, bitsOverByte)))
}
).exmap[LinkedSquadInfo] (
{
case action :: detail :: next :: HNil =>
Attempt.Successful(LinkedSquadInfo(action, detail, next))
},
{
case LinkedSquadInfo(action, detail, next) =>
Attempt.Successful(action :: detail :: next :: HNil)
}
)
}
def squadDetailSelectCodec(size : Int) : Codec[SquadDetail] = {
if(size == 9) {
full_squad_detail_codec
}
else {
linked_squad_detail_codec(size, new StreamLengthToken(1)).xmap[SquadDetail] (
linkedDetail => unlinkSquadInfo(linkedDetail),
unlinkedDetail => linkSquadInfo(unlinkedDetail)
)
}
}
private def codec() : Codec[SquadDetailDefinitionUpdateMessage2] = {
import shapeless.::
(
("guid" | PlanetSideGUID.codec) ::
bool ::
(uint8 >>:~ { size =>
squadDetailSelectCodec(size).hlist
})
).exmap[SquadDetailDefinitionUpdateMessage2] (
{
case guid :: _ :: _ :: info :: HNil =>
Attempt.Successful(SquadDetailDefinitionUpdateMessage2(guid, info))
},
{
case SquadDetailDefinitionUpdateMessage2(guid, info) =>
val occupiedSquadFieldCount = List(info.unk1, info.leader_char_id, info.unk2, info.leader_name, info.task, info.zone_id, info.member_info)
.count(_.nonEmpty)
if(occupiedSquadFieldCount < 9) {
//itemized detail definition protocol
Attempt.Successful(guid :: true :: occupiedSquadFieldCount :: info :: HNil)
}
else {
info.member_info match {
case Some(list) =>
if(list.size == 10 &&
list
.collect { case position if position.info.nonEmpty =>
val info = position.info.get
List(info.is_closed, info.role, info.detailed_orders, info.requirements, info.char_id, info.name)
}
.flatten
.count(_.isEmpty) == 0) {
//full squad detail definition protocol
Attempt.Successful(guid :: true :: 9 :: info :: HNil)
}
else {
//unhandled state
Attempt.Failure(Err("can not split encoding patterns - all squad fields are defined but not all squad member fields are defined"))
}
case None =>
//impossible?
Attempt.Failure(Err("the members field can not be empty; the existence of this field was already proven"))
}
}
}
)
}
implicit val code : Codec[SquadDetailDefinitionUpdateMessage2] = codec()
}

View file

@ -23,12 +23,13 @@ import shapeless.{::, HNil}
* -player_guid - does nothing? * -player_guid - does nothing?
* @param exosuit the type of exo-suit the avatar will be depicted in; * @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 * 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, final case class CharacterAppearanceA(app : BasicCharacterData,
data : CommonFieldData, data : CommonFieldData,
exosuit : ExoSuitType.Value, exosuit : ExoSuitType.Value,
unk5 : Int, unk5 : Int,
unk6 : Long, char_id : Long,
unk7 : Int, unk7 : Int,
unk8 : Int, unk8 : Int,
unk9 : Int, unk9 : Int,

View file

@ -121,6 +121,7 @@ class SquadService extends Actor {
//other players were in the squad; publicly disband it //other players were in the squad; publicly disband it
squad.Membership.foreach(position => { squad.Membership.foreach(position => {
position.Name = "" position.Name = ""
position.CharId = 0L
position.ZoneId = 0 position.ZoneId = 0
position.Position = Vector3.Zero position.Position = Vector3.Zero
position.Health = 0 position.Health = 0
@ -151,6 +152,8 @@ class SquadService extends Actor {
import net.psforever.packet.game.SquadAction._ import net.psforever.packet.game.SquadAction._
val squadOpt = GetParticipatingSquad(tplayer, zone_ordinal_number) val squadOpt = GetParticipatingSquad(tplayer, zone_ordinal_number)
action match { action match {
case SaveSquadDefinition() =>
case ChangeSquadPurpose(purpose) => case ChangeSquadPurpose(purpose) =>
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed his squad's task to $purpose") log.info(s"${tplayer.Name}-${tplayer.Faction} has changed his squad's task to $purpose")
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt) val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
@ -265,6 +268,7 @@ class SquadService extends Actor {
val hadPreviousPosition = squad.Membership.find(_.Name == name) match { val hadPreviousPosition = squad.Membership.find(_.Name == name) match {
case Some(currentPosition)=> case Some(currentPosition)=>
currentPosition.Name = "" currentPosition.Name = ""
currentPosition.CharId = 0L
currentPosition.ZoneId = 0 currentPosition.ZoneId = 0
currentPosition.Health = 0 currentPosition.Health = 0
currentPosition.Armor = 0 currentPosition.Armor = 0
@ -274,6 +278,7 @@ class SquadService extends Actor {
false false
} }
desiredPosition.Name = name desiredPosition.Name = name
desiredPosition.CharId = tplayer.CharId
desiredPosition.ZoneId = zone_ordinal_number desiredPosition.ZoneId = zone_ordinal_number
desiredPosition.Health = tplayer.Health desiredPosition.Health = tplayer.Health
desiredPosition.Armor = tplayer.Armor desiredPosition.Armor = tplayer.Armor
@ -380,7 +385,7 @@ class SquadService extends Actor {
PlanetSideZoneID(squad.ZoneId), PlanetSideZoneID(squad.ZoneId),
squad.Membership.zipWithIndex.map({ case (p, index) => squad.Membership.zipWithIndex.map({ case (p, index) =>
if(squad.Availability(index)) { if(squad.Availability(index)) {
SquadPositionDetail(p.Role, p.Orders, p.Requirements, p.Name) SquadPositionDetail(p.Role, p.Orders, p.Requirements, p.CharId, p.Name)
} }
else { else {
SquadPositionDetail.Closed SquadPositionDetail.Closed

View file

@ -12,7 +12,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
"SquadDetailDefinitionUpdateMessage" should { "SquadDetailDefinitionUpdateMessage" should {
"decode" in { "decode" in {
PacketCoding.DecodePacket(string).require match { PacketCoding.DecodePacket(string).require match {
case SquadDetailDefinitionUpdateMessage(guid, unk, leader, task, zone, member_info) => case SquadDetailDefinitionUpdateMessage(guid, unk1, char_id, unk2, leader, task, zone, member_info) =>
ok ok
case _ => case _ =>
ko ko
@ -22,24 +22,25 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
"encode" in { "encode" in {
val msg = SquadDetailDefinitionUpdateMessage( val msg = SquadDetailDefinitionUpdateMessage(
PlanetSideGUID(3), PlanetSideGUID(3),
42771010L,
"HofD", "HofD",
"\\#ffdc00***\\#9640ff=KOK+SPC+FLY=\\#ffdc00***\\#FF4040 All Welcome", "\\#ffdc00***\\#9640ff=KOK+SPC+FLY=\\#ffdc00***\\#FF4040 All Welcome",
PlanetSideZoneID(7), PlanetSideZoneID(7),
List( List(
SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", ""), SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", "Just a space filler"),
SquadPositionDetail("\\#ffdc00 C", ""), SquadPositionDetail("\\#ffdc00 C", ""),
SquadPositionDetail("\\#ffdc00 H", "", "OpoIE"), SquadPositionDetail("\\#ffdc00 H", "", Set(), 42644970L, "OpoIE"),
SquadPositionDetail("\\#ffdc00 I", "", "BobaF3tt907"), SquadPositionDetail("\\#ffdc00 I", "", Set(), 41604210L, "BobaF3tt907"),
SquadPositionDetail("\\#ffdc00 N", ""), SquadPositionDetail("\\#ffdc00 N", ""),
SquadPositionDetail("\\#ffdc00 A", ""), SquadPositionDetail("\\#ffdc00 A", ""),
SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", ""), SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", "Another space filler"),
SquadPositionDetail("\\#9640ff K", ""), SquadPositionDetail("\\#9640ff K", ""),
SquadPositionDetail("\\#9640ff O", "", "HofD"), SquadPositionDetail("\\#9640ff O", "", Set(), 42771010L, "HofD"),
SquadPositionDetail("\\#9640ff K", "") SquadPositionDetail("\\#9640ff K", "")
) )
) )
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
ok pkt mustEqual string
} }
} }
} }

View file

@ -48,7 +48,7 @@ class CharacterDataTest extends Specification {
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Reinforced a.exosuit mustEqual ExoSuitType.Reinforced
a.unk5 mustEqual 0 a.unk5 mustEqual 0
a.unk6 mustEqual 30777081L a.char_id mustEqual 30777081L
a.unk7 mustEqual 1 a.unk7 mustEqual 1
a.unk8 mustEqual 4 a.unk8 mustEqual 4
a.unk9 mustEqual 0 a.unk9 mustEqual 0
@ -167,7 +167,7 @@ class CharacterDataTest extends Specification {
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Reinforced a.exosuit mustEqual ExoSuitType.Reinforced
a.unk5 mustEqual 0 a.unk5 mustEqual 0
a.unk6 mustEqual 192L a.char_id mustEqual 192L
a.unk7 mustEqual 0 a.unk7 mustEqual 0
a.unk8 mustEqual 0 a.unk8 mustEqual 0
a.unk9 mustEqual 0 a.unk9 mustEqual 0
@ -236,7 +236,7 @@ class CharacterDataTest extends Specification {
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.MAX a.exosuit mustEqual ExoSuitType.MAX
a.unk5 mustEqual 1 a.unk5 mustEqual 1
a.unk6 mustEqual 0L a.char_id mustEqual 0L
a.unk7 mustEqual 0 a.unk7 mustEqual 0
a.unk8 mustEqual 0 a.unk8 mustEqual 0
a.unk9 mustEqual 0 a.unk9 mustEqual 0

View file

@ -76,7 +76,7 @@ class DetailedCharacterDataTest extends Specification {
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Standard a.exosuit mustEqual ExoSuitType.Standard
a.unk5 mustEqual 0 a.unk5 mustEqual 0
a.unk6 mustEqual 41605313L a.char_id mustEqual 41605313L
a.unk7 mustEqual 0 a.unk7 mustEqual 0
a.unk8 mustEqual 0 a.unk8 mustEqual 0
a.unk9 mustEqual 0 a.unk9 mustEqual 0
@ -264,7 +264,7 @@ class DetailedCharacterDataTest extends Specification {
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Standard a.exosuit mustEqual ExoSuitType.Standard
a.unk5 mustEqual 0 a.unk5 mustEqual 0
a.unk6 mustEqual 192L a.char_id mustEqual 192L
a.unk7 mustEqual 0 a.unk7 mustEqual 0
a.unk8 mustEqual 0 a.unk8 mustEqual 0
a.unk9 mustEqual 0 a.unk9 mustEqual 0
@ -449,7 +449,7 @@ class DetailedCharacterDataTest extends Specification {
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.MAX a.exosuit mustEqual ExoSuitType.MAX
a.unk5 mustEqual 1 a.unk5 mustEqual 1
a.unk6 mustEqual 41605870L a.char_id mustEqual 41605870L
a.unk7 mustEqual 0 a.unk7 mustEqual 0
a.unk8 mustEqual 0 a.unk8 mustEqual 0
a.unk9 mustEqual 0 a.unk9 mustEqual 0
@ -657,7 +657,7 @@ class DetailedCharacterDataTest extends Specification {
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Agile a.exosuit mustEqual ExoSuitType.Agile
a.unk5 mustEqual 0 a.unk5 mustEqual 0
a.unk6 mustEqual 733931L a.char_id mustEqual 733931L
a.unk7 mustEqual 0 a.unk7 mustEqual 0
a.unk8 mustEqual 0 a.unk8 mustEqual 0
a.unk9 mustEqual 0 a.unk9 mustEqual 0
@ -1165,7 +1165,7 @@ class DetailedCharacterDataTest extends Specification {
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Standard a.exosuit mustEqual ExoSuitType.Standard
a.unk5 mustEqual 0 a.unk5 mustEqual 0
a.unk6 mustEqual 1176612L a.char_id mustEqual 1176612L
a.unk7 mustEqual 15 a.unk7 mustEqual 15
a.unk8 mustEqual 5 a.unk8 mustEqual 5
a.unk9 mustEqual 10 a.unk9 mustEqual 10

View file

@ -62,7 +62,7 @@ class MountedVehiclesTest extends Specification {
a.data.v5.isEmpty mustEqual true a.data.v5.isEmpty mustEqual true
a.exosuit mustEqual ExoSuitType.Agile a.exosuit mustEqual ExoSuitType.Agile
a.unk5 mustEqual 0 a.unk5 mustEqual 0
a.unk6 mustEqual 30777081L a.char_id mustEqual 30777081L
a.unk7 mustEqual 1 a.unk7 mustEqual 1
a.unk8 mustEqual 4 a.unk8 mustEqual 4
a.unk9 mustEqual 0 a.unk9 mustEqual 0

View file

@ -281,7 +281,7 @@ class AvatarTest extends Specification {
"does not have any loadout specifications by default" in { "does not have any loadout specifications by default" in {
val (_, avatar) = CreatePlayer() val (_, avatar) = CreatePlayer()
(0 to 9).foreach { avatar.LoadLoadout(_) mustEqual None } (0 to 9).foreach { avatar.EquipmentLoadouts.LoadLoadout(_) mustEqual None }
ok ok
} }
@ -289,9 +289,9 @@ class AvatarTest extends Specification {
val (obj, avatar) = CreatePlayer() val (obj, avatar) = CreatePlayer()
obj.Slot(0).Equipment.get.asInstanceOf[Tool].Magazine = 1 //non-standard but legal 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) 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) => case Some(items : InfantryLoadout) =>
items.label mustEqual "test" items.label mustEqual "test"
items.exosuit mustEqual obj.ExoSuit 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 { "save player's current inventory as a loadout, only found in the called-out slot number" in {
val (obj, avatar) = CreatePlayer() val (obj, avatar) = CreatePlayer()
avatar.SaveLoadout(obj, "test", 0) avatar.EquipmentLoadouts.SaveLoadout(obj, "test", 0)
avatar.LoadLoadout(1).isDefined mustEqual false avatar.EquipmentLoadouts.LoadLoadout(1).isDefined mustEqual false
avatar.LoadLoadout(0).isDefined mustEqual true 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 { "try to save player's current inventory as a loadout, but will not save to an invalid slot" in {
val (obj, avatar) = CreatePlayer() val (obj, avatar) = CreatePlayer()
avatar.SaveLoadout(obj, "test", 10) avatar.EquipmentLoadouts.SaveLoadout(obj, "test", 10)
avatar.LoadLoadout(10) mustEqual None avatar.EquipmentLoadouts.LoadLoadout(10) mustEqual None
} }
"save player's current inventory as a loadout, without inventory contents" in { "save player's current inventory as a loadout, without inventory contents" in {
val (obj, avatar) = CreatePlayer() val (obj, avatar) = CreatePlayer()
obj.Inventory.Clear() 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) => case Some(items : InfantryLoadout) =>
items.label mustEqual "test" items.label mustEqual "test"
items.exosuit mustEqual obj.ExoSuit items.exosuit mustEqual obj.ExoSuit
@ -364,9 +364,9 @@ class AvatarTest extends Specification {
obj.Slot(0).Equipment = None obj.Slot(0).Equipment = None
obj.Slot(2).Equipment = None obj.Slot(2).Equipment = None
obj.Slot(4).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) => case Some(items : InfantryLoadout) =>
items.label mustEqual "test" items.label mustEqual "test"
items.exosuit mustEqual obj.ExoSuit items.exosuit mustEqual obj.ExoSuit
@ -380,11 +380,11 @@ class AvatarTest extends Specification {
"save, load, delete; rapidly" in { "save, load, delete; rapidly" in {
val (obj, avatar) = CreatePlayer() val (obj, avatar) = CreatePlayer()
avatar.SaveLoadout(obj, "test", 0) avatar.EquipmentLoadouts.SaveLoadout(obj, "test", 0)
avatar.LoadLoadout(0).isDefined mustEqual true avatar.EquipmentLoadouts.LoadLoadout(0).isDefined mustEqual true
avatar.DeleteLoadout(0) avatar.EquipmentLoadouts.DeleteLoadout(0)
avatar.LoadLoadout(0) mustEqual None avatar.EquipmentLoadouts.LoadLoadout(0) mustEqual None
} }
"the fifth slot is the locker wrapped in an EquipmentSlot" in { "the fifth slot is the locker wrapped in an EquipmentSlot" in {

View file

@ -87,7 +87,7 @@ class OrderTerminalTest extends Specification {
player.ExoSuit = ExoSuitType.Agile player.ExoSuit = ExoSuitType.Agile
player.Slot(0).Equipment = Tool(GlobalDefinitions.beamer) player.Slot(0).Equipment = Tool(GlobalDefinitions.beamer)
player.Slot(6).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))) val msg = infantryTerminal.Request(player, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 4, "", 0, PlanetSideGUID(0)))
msg.isInstanceOf[Terminal.InfantryLoadout] mustEqual true msg.isInstanceOf[Terminal.InfantryLoadout] mustEqual true
@ -137,7 +137,7 @@ class OrderTerminalTest extends Specification {
"player can retrieve a vehicle loadout" in { "player can retrieve a vehicle loadout" in {
val fury = Vehicle(GlobalDefinitions.fury) val fury = Vehicle(GlobalDefinitions.fury)
fury.Slot(30).Equipment = AmmoBox(GlobalDefinitions.hellfire_ammo) 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)) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Loadout, 4, "test", 0, PlanetSideGUID(0))
terminal.Request(player, msg) match { terminal.Request(player, msg) match {

View file

@ -375,6 +375,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse( sendResponse(
SquadDetailDefinitionUpdateMessage( SquadDetailDefinitionUpdateMessage(
guid, guid,
member_info.find(_.name == leader).get.char_id,
leader, leader,
task, task,
zone, zone,
@ -532,7 +533,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.Stamina = stamina player.Stamina = stamina
player.Armor = armor player.Armor = armor
} }
sendResponse(CharacterInfoMessage(15, PlanetSideZoneID(10000), 41605313, player.GUID, false, 6404428)) avatar.CharId = 41605313L
sendResponse(CharacterInfoMessage(15, PlanetSideZoneID(10000), avatar.CharId, player.GUID, false, 6404428))
RemoveCharacterSelectScreenGUID(player) RemoveCharacterSelectScreenGUID(player)
sendResponse(CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0)) sendResponse(CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0))
sendResponse(CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0)) sendResponse(CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0))
@ -2838,7 +2840,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT)) sendResponse(CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT))
sendResponse(ChangeShortcutBankMessage(guid, 0)) sendResponse(ChangeShortcutBankMessage(guid, 0))
//Favorites lists //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 { inf.foreach {
case (index, loadout : InfantryLoadout) => case (index, loadout : InfantryLoadout) =>
sendResponse(FavoritesMessage(LoadoutType.Infantry, guid, index, loadout.label, InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype))) sendResponse(FavoritesMessage(LoadoutType.Infantry, guid, index, loadout.label, InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype)))
@ -2914,10 +2916,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
SquadListing(1, SquadInfo(Some("HofD"), Some("=KOK+SPC+FLY= All Welcome"), Some(PlanetSideZoneID(7)), Some(3), Some(10), Some(PlanetSideGUID(3)))) SquadListing(1, SquadInfo(Some("HofD"), Some("=KOK+SPC+FLY= All Welcome"), Some(PlanetSideZoneID(7)), Some(3), Some(10), Some(PlanetSideGUID(3))))
) )
)) ))
//sendRawResponse(hex"e803008484000c800259e8809fda020043004a0069006d006d0079006e009b48006f006d006900630069006400690061006c00200053006d007500720066007300200041006e006f006e0079006d006f007500730004000000981401064580540061006e006b002000440072006900760065007200a05200650063006f006d006d0065006e00640065006400200074006f0020006800610076006500200065006e00670069006e0065006500720069006e0067002e0000000000800180000c00020c8c46007200650065006200690065002000730070006f007400cf44006f002000770068006100740065007600650072002c0020006200750074002000700072006500660065007200610062006c007900200073007400690063006b002000770069007400680020007400680065002000730071007500610064002e00200044006f006e002700740020006e00650065006400200061006e007900200073007000650063006900660069006300200063006500720074002e0096e27a0290540068006500460069006e0061006c005300740072007500670067006c0065000000000000020c8c46007200650065006200690065002000530070006f007400cf44006f002000770068006100740065007600650072002c0020006200750074002000700072006500660065007200610062006c007900200073007400690063006b002000770069007400680020007400680065002000730071007500610064002e00200044006f006e002700740020006e00650065006400200061006e007900200073007000650063006900660069006300200063006500720074002e0000000000800000000000020c8a41004d0053002000440072006900760065007200b34700690076006500200075007300200073007000610077006e00200070006f0069006e00740073002c0020006800610063006b0069006e006700200061006e006400200069006e00660069006c0020006100720065002000750073006500660075006c002e00fb02790287440030004f004d006700750079000100020c00020c8d410076006500720061006700650020004a0069006d006d007900a05200650069006e0066006f0072006300650064002000450078006f007300750069007400200077006f0075006c00640020006200650020006e0069006300650000000000800300000c00020c8b4100760065007200610067006500200042006f006200a05200650069006e0066006f0072006300650064002000450078006f007300750069007400200077006f0075006c00640020006200650020006e0069006300650000000000800300000c00020c8b410076006500720061006700650020004a006f006500a05200650069006e0066006f0072006300650064002000450078006f007300750069007400200077006f0075006c00640020006200650020006e0069006300650000000000800300000c00020c8753007500700070006f0072007400a2520065007300730075007200650063007400200073006f006c00640069006500720073002c0020006b00650065007000200075007300200061006c006900760065002e0000000000800100000c0c0a0c8845006e00670069006e00650065007200a043006f006d00620061007400200045006e00670069006e0065006500720069006e006700200077006f0075006c00640020006200650020006e0069006300650004b3d101864a0069006d006d0079006e000100000c000a0c854d0065006400690063009a4100640076002e0020004d00650064006900630061006c00200077006f0075006c00640020006200650020006e0069006300650000000000800100000c0400")
sendResponse( sendResponse(
SquadDetailDefinitionUpdateMessage( SquadDetailDefinitionUpdateMessage(
PlanetSideGUID(3), PlanetSideGUID(3),
42771010L,
"HofD", "HofD",
"\\#ffdc00***\\#9640ff=KOK+SPC+FLY=\\#ffdc00***\\#FF4040 All Welcome", "\\#ffdc00***\\#9640ff=KOK+SPC+FLY=\\#ffdc00***\\#FF4040 All Welcome",
PlanetSideZoneID(7), PlanetSideZoneID(7),
@ -3347,17 +3349,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) => case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) =>
if(deadState == DeadState.Alive) { if(deadState == DeadState.Alive) {
if(!player.Crouching && is_crouching) { //SQUAD TESTING CODE if(!player.Crouching && is_crouching) { //SQUAD TESTING CODE
sendResponse(SquadMembershipResponse(SquadRequestType.Invite,0,0,42771010,Some(41605313),"HofD",false,None)) //sendRawResponse(hex"e8030080c603c043fe")
sendResponse(SquadMembershipResponse(SquadRequestType.Accept,0,0,41605313,Some(42771010),"VirusGiver",true,Some(None))) sendRawResponse(hex"e8 0300 80 c 600408c00000001200008811f6200400144004f007a007a0069004b0069006e006700ff")
sendResponse(SquadMemberEvent(0,7,42771010,0,Some("HofD"),Some(7),Some(529745))) // sendResponse(SquadMembershipResponse(SquadRequestType.Invite,0,0,42771010,Some(avatar.CharId),"HofD",false,None))
sendResponse(SquadMemberEvent(0,7,42644970,1,Some("OpolE"),Some(7),Some(6418))) // sendResponse(SquadMembershipResponse(SquadRequestType.Accept,0,0,avatar.CharId,Some(42771010),"VirusGiver",true,Some(None)))
sendResponse(SquadMemberEvent(0,7,41604210,8,Some("BobaF3tt907"),Some(12),Some(8097))) // sendResponse(SquadMemberEvent(0,7,42771010,0,Some("HofD"),Some(7),Some(529745)))
sendResponse(PlanetsideAttributeMessage(player.GUID, 49, 7)) // sendResponse(SquadMemberEvent(0,7,42644970,1,Some("OpolE"),Some(7),Some(6418)))
sendResponse(PlanetsideAttributeMessage(player.GUID, 50, 2)) // sendResponse(SquadMemberEvent(0,7,41604210,8,Some("BobaF3tt907"),Some(12),Some(8097)))
sendResponse(PlanetsideAttributeMessage(player.GUID, 51, 8)) // sendResponse(PlanetsideAttributeMessage(player.GUID, 49, 7))
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(3), 0, SquadAction.Unknown(18, hex"".toBitVector))) // sendResponse(PlanetsideAttributeMessage(player.GUID, 50, 2))
sendResponse(PlanetsideAttributeMessage(player.GUID, 83, 0)) // sendResponse(PlanetsideAttributeMessage(player.GUID, 51, 8))
sendResponse(SquadState(PlanetSideGUID(7),List(SquadStateInfo(41605313L,64,64,Vector3(3464.0469f,4065.5703f,20.015625f),2,2,false,0,None,None)))) // sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(3), 0, SquadAction.Unknown(18, hex"".toBitVector)))
// sendResponse(PlanetsideAttributeMessage(player.GUID, 83, 0))
// sendResponse(SquadState(PlanetSideGUID(7),List(SquadStateInfo(avatar.CharId,64,64,Vector3(3464.0469f,4065.5703f,20.015625f),2,2,false,0,None,None))))
} }
player.Position = pos player.Position = pos
player.Velocity = vel player.Velocity = vel
@ -4579,18 +4583,18 @@ class WorldSessionActor extends Actor with MDCContextAware {
None None
}) match { }) match {
case Some(owner : Player) => //InfantryLoadout case Some(owner : Player) => //InfantryLoadout
avatar.SaveLoadout(owner, name, lineno) avatar.EquipmentLoadouts.SaveLoadout(owner, name, lineno)
import InfantryLoadout._ import InfantryLoadout._
sendResponse(FavoritesMessage(list, player_guid, line, name, DetermineSubtypeB(player.ExoSuit, DetermineSubtype(player)))) sendResponse(FavoritesMessage(list, player_guid, line, name, DetermineSubtypeB(player.ExoSuit, DetermineSubtype(player))))
case Some(owner : Vehicle) => //VehicleLoadout case Some(owner : Vehicle) => //VehicleLoadout
avatar.SaveLoadout(owner, name, lineno) avatar.EquipmentLoadouts.SaveLoadout(owner, name, lineno)
sendResponse(FavoritesMessage(list, player_guid, line, name)) sendResponse(FavoritesMessage(list, player_guid, line, name))
case Some(_) | None => case Some(_) | None =>
log.error("FavoritesRequest: unexpected owner for favorites") log.error("FavoritesRequest: unexpected owner for favorites")
} }
case FavoritesAction.Delete => case FavoritesAction.Delete =>
avatar.DeleteLoadout(lineno) avatar.EquipmentLoadouts.DeleteLoadout(lineno)
sendResponse(FavoritesMessage(list, player_guid, line, "")) sendResponse(FavoritesMessage(list, player_guid, line, ""))
case FavoritesAction.Unknown => case FavoritesAction.Unknown =>
@ -7574,9 +7578,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
/** /**
* Properly format a `DestroyDisplayMessage` packet * Properly format a `DestroyDisplayMessage` packet
* given sufficient information about a target (victim) and an actor (killer). * 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. * For the packet, the `charId` field is important for 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.
* @param killer the killer's entry * @param killer the killer's entry
* @param victim the victim's entry * @param victim the victim's entry
* @param method the manner of death * @param method the manner of death
@ -7585,12 +7587,16 @@ class WorldSessionActor extends Actor with MDCContextAware {
* @return a `DestroyDisplayMessage` packet that is properly formatted * @return a `DestroyDisplayMessage` packet that is properly formatted
*/ */
def DestroyDisplayMessage(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int = 121) : DestroyDisplayMessage = { def DestroyDisplayMessage(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int = 121) : DestroyDisplayMessage = {
//TODO charId should reflect the player more properly val killerCharId : Long = killer.CharId
val killerCharId = math.abs(killer.Name.hashCode) val victimCharId : Long = {
var victimCharId = math.abs(victim.Name.hashCode) //TODO this block of code is only necessary for this particular dev build; use "victim.CharId" normallyLoado
if(killerCharId == victimCharId && !killer.Name.equals(victim.Name)) { val id = victim.CharId
//odds of hash collision in a populated zone should be close to odds of being struck by lightning if(killerCharId == id) {
victimCharId = Int.MaxValue - victimCharId + 1 Int.MaxValue - id + 1
}
else {
id
}
} }
val killer_seated = killer match { val killer_seated = killer match {
case obj : PlayerSource => obj.Seated case obj : PlayerSource => obj.Seated