diff --git a/common/src/main/scala/net/psforever/objects/Avatar.scala b/common/src/main/scala/net/psforever/objects/Avatar.scala
index 1307ee58d..01ce2d46b 100644
--- a/common/src/main/scala/net/psforever/objects/Avatar.scala
+++ b/common/src/main/scala/net/psforever/objects/Avatar.scala
@@ -1,10 +1,9 @@
// 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._
@@ -12,6 +11,8 @@ 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) {
+ /** Character ID; a unique identifier corresponding to a database table row index */
+ private var charId : Long = 0
/** Battle Experience Points */
private var bep : Long = 0
/** Command Experience Points */
@@ -29,11 +30,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
- * 0-9 are Infantry loadouts
+ /** Equipment Loadouts
+ * 0-9 are Infantry 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 */
private val locker : LockerContainer = new LockerContainer() {
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
+ 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_=(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 = {
- 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
diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala
index e1e015f2c..7577b545c 100644
--- a/common/src/main/scala/net/psforever/objects/Player.scala
+++ b/common/src/main/scala/net/psforever/objects/Player.scala
@@ -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
@@ -294,7 +296,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
diff --git a/common/src/main/scala/net/psforever/objects/avatar/LoadoutManager.scala b/common/src/main/scala/net/psforever/objects/avatar/LoadoutManager.scala
new file mode 100644
index 000000000..c59787231
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/avatar/LoadoutManager.scala
@@ -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
+}
diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala
index a141529c2..bbd9f1f54 100644
--- a/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala
@@ -81,7 +81,7 @@ object AvatarConverter {
),
obj.ExoSuit,
0,
- 0L,
+ obj.CharId,
0,
0,
0,
diff --git a/common/src/main/scala/net/psforever/objects/loadouts/EquipmentLoadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/EquipmentLoadout.scala
new file mode 100644
index 000000000..53925f90c
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/loadouts/EquipmentLoadout.scala
@@ -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`.
+ * 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]`.
+ *
+ * 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)
diff --git a/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala
index 0f9986e29..18c2f57fe 100644
--- a/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala
+++ b/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala
@@ -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
diff --git a/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala
index 12e0cf410..58490f60e 100644
--- a/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala
+++ b/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala
@@ -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`.
- * 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]`.
- *
- * 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, label))
+ 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,32 @@ 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,
+ 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.
*/
diff --git a/common/src/main/scala/net/psforever/objects/loadouts/SquadLoadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/SquadLoadout.scala
new file mode 100644
index 000000000..9039c5a07
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/loadouts/SquadLoadout.scala
@@ -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)
diff --git a/common/src/main/scala/net/psforever/objects/loadouts/VehicleLoadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/VehicleLoadout.scala
index 5f7e0b63a..c4199e0ce 100644
--- a/common/src/main/scala/net/psforever/objects/loadouts/VehicleLoadout.scala
+++ b/common/src/main/scala/net/psforever/objects/loadouts/VehicleLoadout.scala
@@ -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)
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala
index 4784d8de3..9c4e34d7c 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala
@@ -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) })
diff --git a/common/src/main/scala/net/psforever/objects/teamwork/Member.scala b/common/src/main/scala/net/psforever/objects/teamwork/Member.scala
index 4d3b24e88..1ddfc46da 100644
--- a/common/src/main/scala/net/psforever/objects/teamwork/Member.scala
+++ b/common/src/main/scala/net/psforever/objects/teamwork/Member.scala
@@ -10,6 +10,7 @@ class Member {
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
@@ -43,6 +44,13 @@ class Member {
Name
}
+ def CharId : Long = charId
+
+ def CharId_=(id : Long) : Long = {
+ charId = id
+ CharId
+ }
+
def Health : Int = health
def Health_=(red : Int) : Int = {
diff --git a/common/src/main/scala/net/psforever/packet/game/SquadDefinitionActionMessage.scala b/common/src/main/scala/net/psforever/packet/game/SquadDefinitionActionMessage.scala
index 43009a3b4..7b9b46472 100644
--- a/common/src/main/scala/net/psforever/packet/game/SquadDefinitionActionMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/SquadDefinitionActionMessage.scala
@@ -366,7 +366,7 @@ object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMe
case 2 | 6 | 9 | 11 |
12 | 13 | 14 | 16 | 17 |
18 | 29 | 30 | 32 | 33 |
- 36 | 37 | 38 | 42 | 43 => unknownCodec(code)
+ 36 | 37 | 38 | 39 | 42 | 43 => unknownCodec(code)
case _ => failureCodec(code)
}).asInstanceOf[Codec[SquadAction]]
}
diff --git a/common/src/main/scala/net/psforever/packet/game/SquadDetailDefinitionUpdateMessage.scala b/common/src/main/scala/net/psforever/packet/game/SquadDetailDefinitionUpdateMessage.scala
index 76fae0227..98d6adef9 100644
--- a/common/src/main/scala/net/psforever/packet/game/SquadDetailDefinitionUpdateMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/SquadDetailDefinitionUpdateMessage.scala
@@ -1,6 +1,7 @@
// Copyright (c) 2019 PSForever
package net.psforever.packet.game
+import net.psforever.packet.game.SquadDetailDefinitionUpdateMessage.defaultRequirements
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.CertificationType
import scodec.bits.BitVector
@@ -18,7 +19,9 @@ final case class SquadPositionDetail(is_closed : Boolean,
name : String)
final case class SquadDetailDefinitionUpdateMessage(guid : PlanetSideGUID,
- unk : BitVector,
+ unk1 : Int,
+ leader_char_id : Long,
+ unk2 : BitVector,
leader_name : String,
task : String,
zone_id : PlanetSideZoneID,
@@ -32,27 +35,17 @@ final case class SquadDetailDefinitionUpdateMessage(guid : PlanetSideGUID,
object SquadPositionDetail {
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(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, 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] {
@@ -64,6 +57,7 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
final val Init = SquadDetailDefinitionUpdateMessage(
PlanetSideGUID(0),
+ 0L,
"",
"",
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._
- 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] = {
@@ -191,9 +185,14 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
import shapeless.::
(
("guid" | PlanetSideGUID.codec) ::
- uint8 ::
- uint4 ::
- bits(83) :: //variable fields, but can be 0'd
+ uint4 :: //constant = 8
+ uint4 :: //variable = 0-4
+ 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
("leader" | PacketHelpers.encodedWideStringAligned(7)) ::
("task" | PacketHelpers.encodedWideString) ::
@@ -202,15 +201,721 @@ object SquadDetailDefinitionUpdateMessage extends Marshallable[SquadDetailDefini
optional(bool, "member_info" | initial_member_codec)
).exmap[SquadDetailDefinitionUpdateMessage] (
{
- case guid :: _ :: _ :: _ :: _ :: leader :: task :: zone :: _ :: Some(member_list) :: HNil =>
- Attempt.Successful(SquadDetailDefinitionUpdateMessage(guid, leader, task, zone, unlinkMemberList(member_list)))
+ case guid :: _ :: _ :: _ :: _ :: u1 :: _ :: char_id:: u2 :: _ :: leader :: task :: zone :: _ :: Some(member_list) :: HNil =>
+ Attempt.Successful(SquadDetailDefinitionUpdateMessage(guid, u1, char_id, u2, leader, task, zone, unlinkMemberList(member_list)))
case data =>
Attempt.failure(Err(s"can not get squad detail definition from data $data"))
},
{
- case SquadDetailDefinitionUpdateMessage(guid, unk, leader, task, zone, member_list) =>
- Attempt.Successful(guid :: 132 :: 8 :: unk.take(83) :: 0 :: leader :: task :: zone :: 4983296 :: Some(linkMemberList(member_list.reverse)) :: HNil)
+ case SquadDetailDefinitionUpdateMessage(guid, unk1, char_id, unk2, leader, task, zone, member_list) =>
+ 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()
+}
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala
index c2b064d37..c3f179217 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala
@@ -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,
diff --git a/common/src/main/scala/services/teamwork/SquadService.scala b/common/src/main/scala/services/teamwork/SquadService.scala
index f7b96aa0f..245c3a29a 100644
--- a/common/src/main/scala/services/teamwork/SquadService.scala
+++ b/common/src/main/scala/services/teamwork/SquadService.scala
@@ -121,6 +121,7 @@ class SquadService extends Actor {
//other players were in the squad; publicly disband it
squad.Membership.foreach(position => {
position.Name = ""
+ position.CharId = 0L
position.ZoneId = 0
position.Position = Vector3.Zero
position.Health = 0
@@ -151,6 +152,8 @@ class SquadService extends Actor {
import net.psforever.packet.game.SquadAction._
val squadOpt = GetParticipatingSquad(tplayer, zone_ordinal_number)
action match {
+ case SaveSquadDefinition() =>
+
case ChangeSquadPurpose(purpose) =>
log.info(s"${tplayer.Name}-${tplayer.Faction} has changed his squad's task to $purpose")
val squad = GetLeadingSquad(tplayer, zone_ordinal_number, squadOpt)
@@ -265,6 +268,7 @@ class SquadService extends Actor {
val hadPreviousPosition = squad.Membership.find(_.Name == name) match {
case Some(currentPosition)=>
currentPosition.Name = ""
+ currentPosition.CharId = 0L
currentPosition.ZoneId = 0
currentPosition.Health = 0
currentPosition.Armor = 0
@@ -274,6 +278,7 @@ class SquadService extends Actor {
false
}
desiredPosition.Name = name
+ desiredPosition.CharId = tplayer.CharId
desiredPosition.ZoneId = zone_ordinal_number
desiredPosition.Health = tplayer.Health
desiredPosition.Armor = tplayer.Armor
@@ -380,7 +385,7 @@ class SquadService extends Actor {
PlanetSideZoneID(squad.ZoneId),
squad.Membership.zipWithIndex.map({ case (p, 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 {
SquadPositionDetail.Closed
diff --git a/common/src/test/scala/game/SquadDetailDefinitionUpdateMessageTest.scala b/common/src/test/scala/game/SquadDetailDefinitionUpdateMessageTest.scala
index 5ad56bd07..cf56fb86a 100644
--- a/common/src/test/scala/game/SquadDetailDefinitionUpdateMessageTest.scala
+++ b/common/src/test/scala/game/SquadDetailDefinitionUpdateMessageTest.scala
@@ -12,7 +12,7 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
"SquadDetailDefinitionUpdateMessage" should {
"decode" in {
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
case _ =>
ko
@@ -22,24 +22,25 @@ class SquadDetailDefinitionUpdateMessageTest extends Specification {
"encode" in {
val msg = SquadDetailDefinitionUpdateMessage(
PlanetSideGUID(3),
+ 42771010L,
"HofD",
"\\#ffdc00***\\#9640ff=KOK+SPC+FLY=\\#ffdc00***\\#FF4040 All Welcome",
PlanetSideZoneID(7),
List(
- SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", ""),
+ SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", "Just a space filler"),
SquadPositionDetail("\\#ffdc00 C", ""),
- SquadPositionDetail("\\#ffdc00 H", "", "OpoIE"),
- SquadPositionDetail("\\#ffdc00 I", "", "BobaF3tt907"),
+ SquadPositionDetail("\\#ffdc00 H", "", Set(), 42644970L, "OpoIE"),
+ SquadPositionDetail("\\#ffdc00 I", "", Set(), 41604210L, "BobaF3tt907"),
SquadPositionDetail("\\#ffdc00 N", ""),
SquadPositionDetail("\\#ffdc00 A", ""),
- SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", ""),
+ SquadPositionDetail("\\#ff0000 |||||||||||||||||||||||", "Another space filler"),
SquadPositionDetail("\\#9640ff K", ""),
- SquadPositionDetail("\\#9640ff O", "", "HofD"),
+ SquadPositionDetail("\\#9640ff O", "", Set(), 42771010L, "HofD"),
SquadPositionDetail("\\#9640ff K", "")
)
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
- ok
+ pkt mustEqual string
}
}
}
diff --git a/common/src/test/scala/game/objectcreate/CharacterDataTest.scala b/common/src/test/scala/game/objectcreate/CharacterDataTest.scala
index eece51214..22a1e7db4 100644
--- a/common/src/test/scala/game/objectcreate/CharacterDataTest.scala
+++ b/common/src/test/scala/game/objectcreate/CharacterDataTest.scala
@@ -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
diff --git a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala
index e64dd8fcf..34175c272 100644
--- a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala
+++ b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala
@@ -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
diff --git a/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala b/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala
index 5b73667f0..1c831f74b 100644
--- a/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala
+++ b/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala
@@ -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
diff --git a/common/src/test/scala/objects/AvatarTest.scala b/common/src/test/scala/objects/AvatarTest.scala
index c2c482c06..33b53ce79 100644
--- a/common/src/test/scala/objects/AvatarTest.scala
+++ b/common/src/test/scala/objects/AvatarTest.scala
@@ -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(_) mustEqual None }
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", 10)
- avatar.LoadLoadout(10) mustEqual None
+ avatar.EquipmentLoadouts.LoadLoadout(10) mustEqual None
}
"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) mustEqual None
}
"the fifth slot is the locker wrapped in an EquipmentSlot" in {
diff --git a/common/src/test/scala/objects/terminal/OrderTerminalTest.scala b/common/src/test/scala/objects/terminal/OrderTerminalTest.scala
index 34b21670b..c60e1c17f 100644
--- a/common/src/test/scala/objects/terminal/OrderTerminalTest.scala
+++ b/common/src/test/scala/objects/terminal/OrderTerminalTest.scala
@@ -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 {
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index b20a2364e..e59fe4232 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -375,6 +375,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(
SquadDetailDefinitionUpdateMessage(
guid,
+ member_info.find(_.name == leader).get.char_id,
leader,
task,
zone,
@@ -532,7 +533,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.Stamina = stamina
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)
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(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)))
@@ -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))))
)
))
- //sendRawResponse(hex"e803008484000c800259e8809fda020043004a0069006d006d0079006e009b48006f006d006900630069006400690061006c00200053006d007500720066007300200041006e006f006e0079006d006f007500730004000000981401064580540061006e006b002000440072006900760065007200a05200650063006f006d006d0065006e00640065006400200074006f0020006800610076006500200065006e00670069006e0065006500720069006e0067002e0000000000800180000c00020c8c46007200650065006200690065002000730070006f007400cf44006f002000770068006100740065007600650072002c0020006200750074002000700072006500660065007200610062006c007900200073007400690063006b002000770069007400680020007400680065002000730071007500610064002e00200044006f006e002700740020006e00650065006400200061006e007900200073007000650063006900660069006300200063006500720074002e0096e27a0290540068006500460069006e0061006c005300740072007500670067006c0065000000000000020c8c46007200650065006200690065002000530070006f007400cf44006f002000770068006100740065007600650072002c0020006200750074002000700072006500660065007200610062006c007900200073007400690063006b002000770069007400680020007400680065002000730071007500610064002e00200044006f006e002700740020006e00650065006400200061006e007900200073007000650063006900660069006300200063006500720074002e0000000000800000000000020c8a41004d0053002000440072006900760065007200b34700690076006500200075007300200073007000610077006e00200070006f0069006e00740073002c0020006800610063006b0069006e006700200061006e006400200069006e00660069006c0020006100720065002000750073006500660075006c002e00fb02790287440030004f004d006700750079000100020c00020c8d410076006500720061006700650020004a0069006d006d007900a05200650069006e0066006f0072006300650064002000450078006f007300750069007400200077006f0075006c00640020006200650020006e0069006300650000000000800300000c00020c8b4100760065007200610067006500200042006f006200a05200650069006e0066006f0072006300650064002000450078006f007300750069007400200077006f0075006c00640020006200650020006e0069006300650000000000800300000c00020c8b410076006500720061006700650020004a006f006500a05200650069006e0066006f0072006300650064002000450078006f007300750069007400200077006f0075006c00640020006200650020006e0069006300650000000000800300000c00020c8753007500700070006f0072007400a2520065007300730075007200650063007400200073006f006c00640069006500720073002c0020006b00650065007000200075007300200061006c006900760065002e0000000000800100000c0c0a0c8845006e00670069006e00650065007200a043006f006d00620061007400200045006e00670069006e0065006500720069006e006700200077006f0075006c00640020006200650020006e0069006300650004b3d101864a0069006d006d0079006e000100000c000a0c854d0065006400690063009a4100640076002e0020004d00650064006900630061006c00200077006f0075006c00640020006200650020006e0069006300650000000000800100000c0400")
sendResponse(
SquadDetailDefinitionUpdateMessage(
PlanetSideGUID(3),
+ 42771010L,
"HofD",
"\\#ffdc00***\\#9640ff=KOK+SPC+FLY=\\#ffdc00***\\#FF4040 All Welcome",
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) =>
if(deadState == DeadState.Alive) {
if(!player.Crouching && is_crouching) { //SQUAD TESTING CODE
- sendResponse(SquadMembershipResponse(SquadRequestType.Invite,0,0,42771010,Some(41605313),"HofD",false,None))
- sendResponse(SquadMembershipResponse(SquadRequestType.Accept,0,0,41605313,Some(42771010),"VirusGiver",true,Some(None)))
- sendResponse(SquadMemberEvent(0,7,42771010,0,Some("HofD"),Some(7),Some(529745)))
- sendResponse(SquadMemberEvent(0,7,42644970,1,Some("OpolE"),Some(7),Some(6418)))
- sendResponse(SquadMemberEvent(0,7,41604210,8,Some("BobaF3tt907"),Some(12),Some(8097)))
- sendResponse(PlanetsideAttributeMessage(player.GUID, 49, 7))
- sendResponse(PlanetsideAttributeMessage(player.GUID, 50, 2))
- sendResponse(PlanetsideAttributeMessage(player.GUID, 51, 8))
- sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(3), 0, SquadAction.Unknown(18, hex"".toBitVector)))
- sendResponse(PlanetsideAttributeMessage(player.GUID, 83, 0))
- sendResponse(SquadState(PlanetSideGUID(7),List(SquadStateInfo(41605313L,64,64,Vector3(3464.0469f,4065.5703f,20.015625f),2,2,false,0,None,None))))
+ //sendRawResponse(hex"e8030080c603c043fe")
+ sendRawResponse(hex"e8 0300 80 c 600408c00000001200008811f6200400144004f007a007a0069004b0069006e006700ff")
+// sendResponse(SquadMembershipResponse(SquadRequestType.Invite,0,0,42771010,Some(avatar.CharId),"HofD",false,None))
+// sendResponse(SquadMembershipResponse(SquadRequestType.Accept,0,0,avatar.CharId,Some(42771010),"VirusGiver",true,Some(None)))
+// sendResponse(SquadMemberEvent(0,7,42771010,0,Some("HofD"),Some(7),Some(529745)))
+// sendResponse(SquadMemberEvent(0,7,42644970,1,Some("OpolE"),Some(7),Some(6418)))
+// sendResponse(SquadMemberEvent(0,7,41604210,8,Some("BobaF3tt907"),Some(12),Some(8097)))
+// sendResponse(PlanetsideAttributeMessage(player.GUID, 49, 7))
+// sendResponse(PlanetsideAttributeMessage(player.GUID, 50, 2))
+// sendResponse(PlanetsideAttributeMessage(player.GUID, 51, 8))
+// 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.Velocity = vel
@@ -4579,18 +4583,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 =>
@@ -7574,9 +7578,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
@@ -7585,12 +7587,16 @@ 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 killerCharId : Long = killer.CharId
+ val victimCharId : Long = {
+ //TODO this block of code is only necessary for this particular dev build; use "victim.CharId" normallyLoado
+ val id = victim.CharId
+ if(killerCharId == id) {
+ Int.MaxValue - id + 1
+ }
+ else {
+ id
+ }
}
val killer_seated = killer match {
case obj : PlayerSource => obj.Seated