This commit is contained in:
Resaec 2026-01-19 12:47:24 +00:00 committed by GitHub
commit df356c5919
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 337 additions and 5 deletions

View file

@ -569,9 +569,9 @@ object GamePacketOpcode extends Enumeration {
case 0xdf => game.ZoneLockInfoMessage.decode case 0xdf => game.ZoneLockInfoMessage.decode
// OPCODES 0xe0-ef // OPCODES 0xe0-ef
case 0xe0 => noDecoder(SquadBindInfoMessage) case 0xe0 => game.SquadBindInfoMessage.decode
case 0xe1 => noDecoder(AudioSequenceMessage) case 0xe1 => noDecoder(AudioSequenceMessage)
case 0xe2 => noDecoder(SquadFacilityBindInfoMessage) case 0xe2 => game.SquadFacilityBindInfoMessage.decode
case 0xe3 => game.ZoneForcedCavernConnectionsMessage.decode case 0xe3 => game.ZoneForcedCavernConnectionsMessage.decode
case 0xe4 => noDecoder(MissionActionMessage) case 0xe4 => noDecoder(MissionActionMessage)
case 0xe5 => noDecoder(MissionKillTriggerMessage) case 0xe5 => noDecoder(MissionKillTriggerMessage)

View file

@ -58,7 +58,9 @@ object BindStatus extends Enumeration(1) {
* @param zone_number the number of the zone in which to display this spawn option; * @param zone_number the number of the zone in which to display this spawn option;
* if `zone_number` is not the current zone, and the action is positive, * if `zone_number` is not the current zone, and the action is positive,
* a small map of the alternate zone with selectable spawn point will become visible * a small map of the alternate zone with selectable spawn point will become visible
* @param unk4 na * @param map_id the "MapID" of the facility the player is bound to;
* if spawn_group is AMS it is likely the source facility of the AMS;
* with spawn_group AMS pos does point to the AMS position;
* @param pos coordinates for any displayed deployment map icon; * @param pos coordinates for any displayed deployment map icon;
* `x` and `y` determine the position * `x` and `y` determine the position
*/ */
@ -69,7 +71,7 @@ final case class BindPlayerMessage(
logging: Boolean, logging: Boolean,
spawn_group: SpawnGroup, spawn_group: SpawnGroup,
zone_number: Long, zone_number: Long,
unk4: Long, map_id: Long,
pos: Vector3 pos: Vector3
) extends PlanetSideGamePacket { ) extends PlanetSideGamePacket {
type Packet = BindPlayerMessage type Packet = BindPlayerMessage

View file

@ -0,0 +1,48 @@
// Copyright (c) 2025 PSForever
package net.psforever.packet.game
import net.psforever.packet.game.SquadBindInfoMessage.SquadBindEntry
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
final case class SquadBindInfoMessage(
unk0: Int, // squad/platoon index?
elements: Vector[SquadBindEntry],
) extends PlanetSideGamePacket {
type Packet = SquadBindInfoMessage
def opcode = GamePacketOpcode.SquadBindInfoMessage
def encode = SquadBindInfoMessage.encode(this)
}
object SquadBindInfoMessage extends Marshallable[SquadBindInfoMessage] {
/**
* SquadBindEntry
*
* If isBound is false unk1 and unk2 are 0.
* unk1
* @param squadMember index of squad member
* @param zoneID zone ID as in zX / mapX
* @param mapID MapID identifier in mapX.json
* @param isBound is bound to a facility
*/
final case class SquadBindEntry(
squadMember: Long,
zoneID: Long,
mapID: Int,
isBound: Boolean,
)
private implicit val squadBindEntryCodec: Codec[SquadBindEntry] = (
("squadMember" | uint32L) ::
("zoneID" | uint32L) ::
("mapID" | uint16L) ::
("isBound" | bool)
).as[SquadBindEntry]
implicit val codec: Codec[SquadBindInfoMessage] = (
("unk0" | int32L) ::
("squadBindEntries" | vectorOfN(int32L, squadBindEntryCodec))
).as[SquadBindInfoMessage]
}

View file

@ -0,0 +1,35 @@
// Copyright (c) 2025 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
/**
* SquadFacilityBindInfoMessage
* @param unk0
* @param squadID ID of the squad this message is relating to
* @param mapID MapID of the facility the player is bound to;
* alternatively of the facility the AMS was pulled from?;
* @param zoneID ZoneID of where the bind is from and the MapID relates to
*/
final case class SquadFacilityBindInfoMessage(
unk0: Boolean,
squadID: Long,
mapID: Long,
zoneID: Long
) extends PlanetSideGamePacket {
type Packet = EmpireBenefitsMessage
def opcode = GamePacketOpcode.SquadFacilityBindInfoMessage
def encode = SquadFacilityBindInfoMessage.encode(this)
}
object SquadFacilityBindInfoMessage extends Marshallable[SquadFacilityBindInfoMessage] {
implicit val codec: Codec[SquadFacilityBindInfoMessage] = (
("unk0" | bool) ::
("squadID" | uint32L) ::
("mapID" | uint32L) ::
("zoneID" | uint32L)
).as[SquadFacilityBindInfoMessage]
}

View file

@ -1,4 +1,4 @@
// Copyright (c) 2023 PSForever // Copyright (c) 2023, 2025 PSForever
package net.psforever.types package net.psforever.types
import enumeratum.values.{IntEnum, IntEnumEntry} import enumeratum.values.{IntEnum, IntEnumEntry}
@ -12,6 +12,7 @@ object ExperienceType extends IntEnum[ExperienceType] {
val values: IndexedSeq[ExperienceType] = findValues val values: IndexedSeq[ExperienceType] = findValues
case object Normal extends ExperienceType(value = 0) case object Normal extends ExperienceType(value = 0)
case object Unk1 extends ExperienceType(value = 1) // lotsplay.gcap #33316 @ 596.403340s / asdf.gcap #26207 @ 157.773563s
case object Support extends ExperienceType(value = 2) case object Support extends ExperienceType(value = 2)
case object RabbitBall extends ExperienceType(value = 4) case object RabbitBall extends ExperienceType(value = 4)

View file

@ -0,0 +1,160 @@
// Copyright (c) 2025 PSForever
package game
import net.psforever.packet._
import net.psforever.packet.game.SquadBindInfoMessage
import net.psforever.packet.game.SquadBindInfoMessage.SquadBindEntry
import org.specs2.mutable._
import scodec.bits._
class SquadBindInfoMessageTest extends Specification {
private val sample1 = hex"e0 00000000 04000000 000000000000000000000 08000000000000000000 08000000000000000000 10000000000000000000 0"
private val sample2 = hex"e0 01000000 06000000 000000000700000007008 08000000000000000000 08000000000000000000 060000000e0000000e01 05000000000000000000 04000000038000000800 4"
private val sample3 = hex"e0 01000000 08000000 000000000000000000000 08000000080000004004 08000000040000002002 06000000000000000000 04000000000000000000 02800000000000000000 01800000000000000000 00e0000000e0000000e0 1"
private val sample4 = hex"e0 ffffffff 0a000000 000000000000000000000 08000000000000000000 08000000000000000000 06000000000000000000 04000000070000000c00 82800000038000000700 41800000000000000000 00e00000000000000000 00800000000000000000 00480000003800000070 040"
"decode sample1" in {
PacketCoding.decodePacket(sample1).require match {
case SquadBindInfoMessage(u0, elements) =>
u0 mustEqual 0
elements mustEqual Vector(
SquadBindEntry(0,0,0,false),
SquadBindEntry(1,0,0,false),
SquadBindEntry(2,0,0,false),
SquadBindEntry(8,0,0,false)
)
case _ =>
ko
}
}
"encode sample1" in {
val msg = SquadBindInfoMessage(
unk0 = 0,
elements = Vector(
SquadBindEntry(0,0,0,false),
SquadBindEntry(1,0,0,false),
SquadBindEntry(2,0,0,false),
SquadBindEntry(8,0,0,false)
)
)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual sample1
}
"decode sample2" in {
PacketCoding.decodePacket(sample2).require match {
case SquadBindInfoMessage(u0, elements) =>
u0 mustEqual 1
elements mustEqual Vector(
SquadBindEntry(0,7,7,true),
SquadBindEntry(1,0,0,false),
SquadBindEntry(2,0,0,false),
SquadBindEntry(3,7,7,true),
SquadBindEntry(5,0,0,false),
SquadBindEntry(8,7,16,true)
)
case _ =>
ko
}
}
"encode sample2" in {
val msg = SquadBindInfoMessage(
unk0 = 1,
elements = Vector(
SquadBindEntry(0,7,7,true),
SquadBindEntry(1,0,0,false),
SquadBindEntry(2,0,0,false),
SquadBindEntry(3,7,7,true),
SquadBindEntry(5,0,0,false),
SquadBindEntry(8,7,16,true)
)
)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual sample2
}
"decode sample3" in {
PacketCoding.decodePacket(sample3).require match {
case SquadBindInfoMessage(u0, elements) =>
u0 mustEqual 1
elements mustEqual Vector(
SquadBindEntry(0,0,0,false),
SquadBindEntry(1,1,8,true),
SquadBindEntry(2,1,8,true),
SquadBindEntry(3,0,0,false),
SquadBindEntry(4,0,0,false),
SquadBindEntry(5,0,0,false),
SquadBindEntry(6,0,0,false),
SquadBindEntry(7,7,7,true)
)
case _ =>
ko
}
}
"encode sample3" in {
val msg = SquadBindInfoMessage(
unk0 = 1,
elements = Vector(
SquadBindEntry(0,0,0,false),
SquadBindEntry(1,1,8,true),
SquadBindEntry(2,1,8,true),
SquadBindEntry(3,0,0,false),
SquadBindEntry(4,0,0,false),
SquadBindEntry(5,0,0,false),
SquadBindEntry(6,0,0,false),
SquadBindEntry(7,7,7,true)
)
)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual sample3
}
"decode sample4" in {
PacketCoding.decodePacket(sample4).require match {
case SquadBindInfoMessage(u0, elements) =>
u0 mustEqual -1
elements mustEqual Vector(
SquadBindEntry(0,0,0,false),
SquadBindEntry(1,0,0,false),
SquadBindEntry(2,0,0,false),
SquadBindEntry(3,0,0,false),
SquadBindEntry(4,7,12,true),
SquadBindEntry(5,7,14,true),
SquadBindEntry(6,0,0,false),
SquadBindEntry(7,0,0,false),
SquadBindEntry(8,0,0,false),
SquadBindEntry(9,7,14,true)
)
case _ =>
ko
}
}
"encode sample4" in {
val msg = SquadBindInfoMessage(
unk0 = -1,
elements = Vector(
SquadBindEntry(0,0,0,false),
SquadBindEntry(1,0,0,false),
SquadBindEntry(2,0,0,false),
SquadBindEntry(3,0,0,false),
SquadBindEntry(4,7,12,true),
SquadBindEntry(5,7,14,true),
SquadBindEntry(6,0,0,false),
SquadBindEntry(7,0,0,false),
SquadBindEntry(8,0,0,false),
SquadBindEntry(9,7,14,true)
)
)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual sample4
}
}

View file

@ -0,0 +1,86 @@
// Copyright (c) 2025 PSForever
package game
import net.psforever.packet._
import net.psforever.packet.game.SquadFacilityBindInfoMessage
import org.specs2.mutable._
import scodec.bits._
class SquadFacilityBindInfoMessageTest extends Specification {
private val sample1 = hex"e2 0 38000000 00000000 00000000 0"
private val sample2 = hex"e2 0 20000000 80000000 38000000 0"
private val sample3 = hex"e2 0 18000000 48000000 28000000 0"
"decode sample1" in {
PacketCoding.decodePacket(sample1).require match {
case SquadFacilityBindInfoMessage(unk0, squadID, mapID, zoneID) =>
unk0 mustEqual false
squadID mustEqual 7
mapID mustEqual 0
zoneID mustEqual 0
case _ =>
ko
}
}
"encode sample1" in {
val msg = SquadFacilityBindInfoMessage(
unk0 = false,
squadID = 7,
mapID = 0,
zoneID = 0
)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual sample1
}
"decode sample2" in {
PacketCoding.decodePacket(sample2).require match {
case SquadFacilityBindInfoMessage(unk0, squadID, mapID, zoneID) =>
unk0 mustEqual false
squadID mustEqual 4
mapID mustEqual 16
zoneID mustEqual 7
case _ =>
ko
}
}
"encode sample2" in {
val msg = SquadFacilityBindInfoMessage(
unk0 = false,
squadID = 4,
mapID = 16,
zoneID = 7
)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual sample2
}
"decode sample3" in {
PacketCoding.decodePacket(sample3).require match {
case SquadFacilityBindInfoMessage(unk0, squadID, mapID, zoneID) =>
unk0 mustEqual false
squadID mustEqual 3
mapID mustEqual 9
zoneID mustEqual 5
case _ =>
ko
}
}
"encode sample3" in {
val msg = SquadFacilityBindInfoMessage(
unk0 = false,
squadID = 3,
mapID = 9,
zoneID = 5
)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual sample3
}
}