diff --git a/common/src/main/scala/net/psforever/objects/teamwork/Member.scala b/common/src/main/scala/net/psforever/objects/teamwork/Member.scala new file mode 100644 index 00000000..fc1988e3 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/teamwork/Member.scala @@ -0,0 +1,65 @@ +// Copyright (c) 2019 PSForever +package net.psforever.objects.teamwork + +import net.psforever.types.{CertificationType, Vector3} + +class Member { + //about the position to be filled + private var role : String = "" + private var restrictions : Set[CertificationType.Value] = Set() + //about the individual filling the position + private var name : String = "" + private var health : Int = 0 + private var armor : Int = 0 + private var zoneId : String = "Nowhere" + private var position : Vector3 = Vector3.Zero + + def Role : String = role + + def Role_=(title : String) : String = { + role = title + Role + } + + def Restrictions : Set[CertificationType.Value] = restrictions + + def Restrictions_=(requirements : Set[CertificationType.Value]) = { + restrictions = requirements + Restrictions + } + + def Name : String = name + + def Name_=(moniker : String) : String = { + name = moniker + Name + } + + def Health : Int = health + + def Health_=(red : Int) : Int = { + health = red + Health + } + + def Armor : Int = armor + + def Armor_=(blue : Int) : Int = { + armor = blue + Armor + } + + def ZoneId : String = zoneId + + def ZoneId_=(id : String) : String = { + zoneId = id + ZoneId + } + + def Position : Vector3 = position + + def Position_=(pos : Vector3) : Vector3 = { + position = pos + Position + } +} diff --git a/common/src/main/scala/net/psforever/objects/teamwork/Squad.scala b/common/src/main/scala/net/psforever/objects/teamwork/Squad.scala new file mode 100644 index 00000000..23a96a7a --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/teamwork/Squad.scala @@ -0,0 +1,51 @@ +// Copyright (c) 2019 PSForever +package net.psforever.objects.teamwork + +import net.psforever.objects.entity.IdentifiableEntity +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.types.PlanetSideEmpire + +class Squad(squadId : PlanetSideGUID, alignment : PlanetSideEmpire.Value) extends IdentifiableEntity { + super.GUID_=(squadId) + private val faction : PlanetSideEmpire.Value = alignment //does not change + private val zoneId : Option[String] = None + private var task : String = "" + private var description : String = "" + private val membership : Array[Member] = Array.fill[Member](10)(new Member) + private val availability : Array[Boolean] = Array.fill[Boolean](10)(true) + + override def GUID_=(d : PlanetSideGUID) : PlanetSideGUID = GUID + + def Faction : PlanetSideEmpire.Value = faction + + def ZoneId : String = zoneId.getOrElse({ + membership.headOption match { + case Some(leader) => + leader.ZoneId + case _ => + "Nowhere" + } + }) + + def Task : String = task + + def Task_=(assignment : String) : String = { + task = assignment + Task + } + + def Description : String = description + + def Description_=(desc : String) : String = { + description = desc + Description + } + + def Membership : Array[Member] = membership + + def Availability : Array[Boolean] = availability + + def Size : Int = membership.count(member => !member.Name.equals("")) + + def Capacity : Int = availability.count(open => open) +} 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 052c0e55..0baf8518 100644 --- a/common/src/main/scala/net/psforever/packet/game/SquadDefinitionActionMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/SquadDefinitionActionMessage.scala @@ -2,21 +2,239 @@ package net.psforever.packet.game import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import scodec.bits.BitVector import scodec.{Attempt, Codec, Err} import scodec.codecs._ import shapeless.{::, HNil} +/** + * The generic superclass of a specific behavior for this type of squad definition action. + * All behaviors have a "code" that indicates how the rest of the data is parsed. + * @param code the action behavior code + */ +protected abstract class SquadAction(val code : Int) + +object SquadAction{ + final case class SaveSquadDefinition() extends SquadAction(3) + + final case class ListSquad() extends SquadAction(8) + + final case class SelectRoleForYourself(state : Int) extends SquadAction(10) + + final case class ChangeSquadPurpose(purpose : String) extends SquadAction(19) + + final case class ChangeSquadZone(zone : PlanetSideZoneID) extends SquadAction(20) + + final case class CloseSquadMemberPosition(position : Int) extends SquadAction(21) + + final case class AddSquadMemberPosition(position : Int) extends SquadAction(22) + + final case class ChangeSquadMemberRequirementsRole(u1 : Int, role : String) extends SquadAction(23) + + final case class ChangeSquadMemberRequirementsDetailedOrders(u1 : Int, orders : String) extends SquadAction(24) + + final case class ChangeSquadMemberRequirementsWeapons(u1 : Int, u2 : Long) extends SquadAction(25) + + final case class ResetAll() extends SquadAction(26) + + final case class AutoApproveInvitationRequests(state : Boolean) extends SquadAction(28) + + final case class LocationFollowsSquadLead(state : Boolean) extends SquadAction(31) + + final case class SearchForSquadsWithParticularRole(u1: String, u2 : Long, u3: Int, u4 : Int) extends SquadAction(34) + + final case class CancelSquadSearch() extends SquadAction(35) + + final case class FindLfsSoldiersForRole(state : Int) extends SquadAction(40) + + final case class CancelFind() extends SquadAction(41) + + final case class Unknown(badCode : Int, data : BitVector) extends SquadAction(badCode) + + /** + * The `Codec`s used to transform the input stream into the context of a specific action + * and extract the field data from that stream. + */ + object Codecs { + private val everFailCondition = conditional(included = false, bool) + + val saveSquadDefinitionCodec = everFailCondition.xmap[SaveSquadDefinition] ( + _ => SaveSquadDefinition(), + { + case SaveSquadDefinition() => None + } + ) + + val listSquadCodec = everFailCondition.xmap[ListSquad] ( + _ => ListSquad(), + { + case ListSquad() => None + } + ) + + val selectRoleForYourselfCodec = uint4.xmap[SelectRoleForYourself] ( + value => SelectRoleForYourself(value), + { + case SelectRoleForYourself(value) => value + } + ) + + val changeSquadPurposeCodec = PacketHelpers.encodedWideStringAligned(6).xmap[ChangeSquadPurpose] ( + purpose => ChangeSquadPurpose(purpose), + { + case ChangeSquadPurpose(purpose) => purpose + } + ) + + val changeSquadZoneCodec = uint16L.xmap[ChangeSquadZone] ( + value => ChangeSquadZone(PlanetSideZoneID(value)), + { + case ChangeSquadZone(value) => value.zoneId.toInt + } + ) + + val closeSquadMemberPositionCodec = uint4.xmap[CloseSquadMemberPosition] ( + position => CloseSquadMemberPosition(position), + { + case CloseSquadMemberPosition(position) => position + } + ) + + val addSquadMemberPositionCodec = uint4.xmap[AddSquadMemberPosition] ( + position => AddSquadMemberPosition(position), + { + case AddSquadMemberPosition(position) => position + } + ) + + val changeSquadMemberRequirementsRoleCodec = (uint4L :: PacketHelpers.encodedWideStringAligned(2)).xmap[ChangeSquadMemberRequirementsRole] ( + { + case u1 :: role :: HNil => ChangeSquadMemberRequirementsRole(u1, role) + }, + { + case ChangeSquadMemberRequirementsRole(u1, role) => u1 :: role :: HNil + } + ) + + val changeSquadMemberRequirementsDetailedOrdersCodec = (uint4L :: PacketHelpers.encodedWideStringAligned(2)).xmap[ChangeSquadMemberRequirementsDetailedOrders] ( + { + case u1 :: role :: HNil => ChangeSquadMemberRequirementsDetailedOrders(u1, role) + }, + { + case ChangeSquadMemberRequirementsDetailedOrders(u1, role) => u1 :: role :: HNil + } + ) + + val changeSquadMemberRequirementsWeaponsCodec = (uint4 :: ulongL(46)).xmap[ChangeSquadMemberRequirementsWeapons] ( + { + case u1 :: u2 :: HNil => ChangeSquadMemberRequirementsWeapons(u1, u2) + }, + { + case ChangeSquadMemberRequirementsWeapons(u1, u2) => u1 :: u2 :: HNil + } + ) + + val resetAllCodec = everFailCondition.xmap[ResetAll] ( + _ => ResetAll(), + { + case ResetAll() => None + } + ) + + val autoApproveInvitationRequestsCodec = bool.xmap[AutoApproveInvitationRequests] ( + state => AutoApproveInvitationRequests(state), + { + case AutoApproveInvitationRequests(state) => state + } + ) + + val locationFollowsSquadLeadCodec = bool.xmap[LocationFollowsSquadLead] ( + state => LocationFollowsSquadLead(state), + { + case LocationFollowsSquadLead(state) => state + } + ) + + val searchForSquadsWithParticularRoleCodec = ( + PacketHelpers.encodedWideStringAligned(6) :: + ulongL(46) :: + uint16L :: + uintL(3)).xmap[SearchForSquadsWithParticularRole] ( + { + case u1 :: u2 :: u3 :: u4 :: HNil => SearchForSquadsWithParticularRole(u1, u2, u3, u4) + }, + { + case SearchForSquadsWithParticularRole(u1, u2, u3, u4) => u1 :: u2 :: u3 :: u4 :: HNil + } + ) + + val cancelSquadSearchCodec = everFailCondition.xmap[CancelSquadSearch] ( + _ => CancelSquadSearch(), + { + case CancelSquadSearch() => None + } + ) + + val findLfsSoldiersForRoleCodec = uint4.xmap[FindLfsSoldiersForRole] ( + state => FindLfsSoldiersForRole(state), + { + case FindLfsSoldiersForRole(state) => state + } + ) + + val cancelFindCodec = everFailCondition.xmap[CancelFind] ( + _ => CancelFind(), + { + case CancelFind() => None + } + ) + + /** + * A common form for known action code indexes with an unknown purpose and transformation is an "Unknown" object. + * @param action the action behavior code + * @return a transformation between the action code and the unknown bit data + */ + def unknownCodec(action : Int) = bits.xmap[Unknown] ( + data => Unknown(action, data), + { + case Unknown(_, data) => data + } + ) + + /** + * The action code was completely unanticipated! + * @param action the action behavior code + * @return nothing; always fail + */ + def failureCodec(action : Int)= everFailCondition.exmap[SquadAction] ( + _ => Attempt.failure(Err(s"can not match a codec pattern for decoding $action")), + _ => Attempt.failure(Err(s"can not match a codec pattern for encoding $action")) + ) + } +} + /** * Manage composition and details of a player's current squad, or the currently-viewed squad.
*
* The `action` code indicates the format of the remainder data in the packet. * The following formats are translated; their purposes are listed:
*   `(None)`
- *     `3 ` - Save Squad Definition - *     `8 ` - List Squad - *     `26` - Reset All - *     `35` - Cancel Squad Search - *     `41` - Cancel Find + *     `0 ` - UNKNOWN
+ *     `1 ` - UNKNOWN
+ *     `2 ` - UNKNOWN
+ *     `3 ` - Save Squad Definition
+ *     `4 ` - UNKNOWN
+ *     `6 ` - UNKNOWN
+ *     `8 ` - List Squad
+ *     `9 ` - UNKNOWN
+ *     `16` - UNKNOWN
+ *     `17` - UNKNOWN
+ *     `18` - UNKNOWN
+ *     `26` - Reset All
+ *     `35` - Cancel Squad Search
+ *     `41` - Cancel Find
+ *     `42` - UNKNOWN
+ *     `43` - UNKNOWN
*   `Boolean`
*     `28` - Auto-approve Requests for Invitation
*     `29` - UNKNOWN
@@ -48,34 +266,15 @@ import shapeless.{::, HNil} *   `Long :: Long`
*     `36` - UNKNOWN
*   `String :: Long :: Int :: Int`
- *     `34` - Search for Squads with a Particular Role
- *
- * Exploration:
- * Some notes regarding the full list of action codes follows after this packet. - * Asides from codes whose behaviors are unknown, some codes also have unknown data format. - * No information for codes 1, 5, 9, 27, or 35 has been found yet. - * @param action the purpose of this packet; - * also decides the content of the parameter fields + *     `34` - Search for Squads with a Particular Role * @param unk1 na * @param unk2 na - * @param string_opt the optional `String` parameter - * @param int1_opt the first optional `Int` parameter; - * will not necessarily conform to a single bit length - * @param int2_opt the second optional `Int` parameter - * @param long1_opt the first optional `Long` parameter; - * will not necessarily conform to a single bit length - * @param long2_opt the second optional `Long` parameter - * @param bool_opt the optional `Boolean` parameter + * @param action the purpose of this packet; + * also decides the content of the parameter fields */ -final case class SquadDefinitionActionMessage(action : Int, - unk1 : Int, +final case class SquadDefinitionActionMessage(unk1 : Int, unk2 : Int, - string_opt : Option[String], - int1_opt : Option[Int], - int2_opt : Option[Int], - long1_opt : Option[Long], - long2_opt : Option[Long], - bool_opt : Option[Boolean]) + action : SquadAction) extends PlanetSideGamePacket { type Packet = SquadDefinitionActionMessage def opcode = GamePacketOpcode.SquadDefinitionActionMessage @@ -84,266 +283,57 @@ final case class SquadDefinitionActionMessage(action : Int, object SquadDefinitionActionMessage extends Marshallable[SquadDefinitionActionMessage] { /** - * Common pattern for the parameters, with enough fields to support all possible outputs. - * All fields are `Option`al purposefully. + * Use the action code to transform between + * the specific action object and its field data + * and the stream of bits of the original packet. + * @param code the action behavior code + * @return the `SquadAction` `Codec` to use for the given `code` */ - private type allPattern = Option[String] :: Option[Int] :: Option[Int] :: Option[Long] :: Option[Long] :: Option[Boolean] :: HNil - - /** - * `Codec` for reading nothing from the remainder of the stream data. - * @return a filled-out `allPattern` if successful - */ - def noneCodec : Codec[allPattern] = ignore(0).xmap[allPattern] ( - { - case () => - None :: None :: None :: None :: None :: None :: HNil - }, - { - case _ :: _ :: _ :: _ :: _ :: _ :: HNil => - () - } - ) - - /** - * `Codec` for reading a single `Boolean` from remaining stream data. - * @return a filled-out `allPattern` if successful - */ - def boolCodec : Codec[allPattern] = bool.hlist.exmap[allPattern] ( - { - case n :: HNil => - Attempt.successful(None :: None :: None :: None :: None :: Some(n) :: HNil) - }, - { - case _ :: _ :: _ :: _ :: _ :: None :: HNil => - Attempt.failure(Err("expected a boolean value but found nothing")) - case _ :: _ :: _ :: _ :: _ :: Some(n) :: HNil => - Attempt.successful(n :: HNil) - } - ) - - /** - * `Codec` for reading a single `Int` from remaining stream data. - * Multiple bit lengths can be processed from this reading. - * @param icodec the `Codec[Int]` read by this method - * @return a filled-out `allPattern` if successful - */ - def intCodec(icodec : Codec[Int]) : Codec[allPattern] = icodec.hlist.exmap[allPattern] ( - { - case n :: HNil => - Attempt.successful(None :: Some(n) :: None :: None :: None :: None :: HNil) - }, - { - case _ :: None :: _ :: _ :: _ :: _ :: HNil => - Attempt.failure(Err("expected an integer value but found nothing")) - case _ :: Some(n) :: _ :: _ :: _ :: _ :: HNil => - Attempt.successful(n :: HNil) - } - ) - - /** - * `Codec` for reading a single `Long` from remaining stream data. - * @return a filled-out `allPattern` if successful - */ - def longCodec : Codec[allPattern] = uint32L.hlist.exmap[allPattern] ( - { - case n :: HNil => - Attempt.successful(None :: None :: None :: Some(n) :: None :: None :: HNil) - }, - { - case _ :: _ :: _ :: None :: _ :: _ :: HNil => - Attempt.failure(Err("expected a long value but found nothing")) - case _ :: _ :: _ :: Some(n) :: _ :: _ :: HNil => - Attempt.successful(n :: HNil) - } - ) - - /** - * `Codec` for reading a `String` from remaining stream data. - * All `String`s processed by this reading are wide character and are padded by six. - * @return a filled-out `allPattern` if successful - */ - def stringCodec : Codec[allPattern] = PacketHelpers.encodedWideStringAligned(6).hlist.exmap[allPattern] ( - { - case a :: HNil => - Attempt.successful(Some(a) :: None :: None :: None :: None :: None :: HNil) - }, - { - case None:: _ :: _ :: _ :: _ :: _ :: HNil => - Attempt.failure(Err("expected a string value but found nothing")) - case Some(a) :: _ :: _ :: _ :: _ :: _ :: HNil => - Attempt.successful(a :: HNil) - } - ) - - /** - * `Codec` for reading an `Int` followed by a `Long` from remaining stream data. - * Multiple bit lengths can be processed for the `Long1` value from this reading. - * @param lcodec the `Codec[Long]` read by this method - * @return a filled-out `allPattern` if successful - */ - def intLongCodec(lcodec : Codec[Long]) : Codec[allPattern] = ( - uint4L :: - lcodec - ).exmap[allPattern] ( - { - case a :: b :: HNil => - Attempt.successful(None :: Some(a) :: None :: Some(b) :: None :: None :: HNil) - }, - { - case _ :: None :: _ :: _ :: _ :: _ :: HNil => - Attempt.failure(Err("expected a integer value but found nothing")) - case _ :: _ :: _ :: None :: _ :: _ :: HNil => - Attempt.failure(Err("expected a long value but found nothing")) - case _ :: Some(a) :: _ :: Some(b) :: _ :: _ :: HNil => - Attempt.successful(a :: b :: HNil) - } - ) - - /** - * `Codec` for reading an `Int` followed by a `String` from remaining stream data. - * All `String`s processed by this reading are wide character and are padded by two. - * @return a filled-out `allPattern` if successful - */ - def intStringCodec : Codec[allPattern] = ( - uint4L :: - PacketHelpers.encodedWideStringAligned(2) - ).exmap[allPattern] ( - { - case a :: b :: HNil => - Attempt.successful(Some(b) :: Some(a) :: None :: None :: None :: None :: HNil) - }, - { - case None:: _ :: _ :: _ :: _ :: _ :: HNil => - Attempt.failure(Err("expected a string value but found nothing")) - case _ :: None :: _ :: _ :: _ :: _ :: HNil => - Attempt.failure(Err("expected an integer value but found nothing")) - case Some(b) :: Some(a) :: _ :: _ :: _ :: _ :: HNil => - Attempt.successful(a :: b :: HNil) - } - ) - - /** - * `Codec` for reading two `Long`s from remaining stream data. - * @return a filled-out `allPattern` if successful - */ - def longLongCodec : Codec[allPattern] = ( - ulongL(46) :: - uint32L - ).exmap[allPattern] ( - { - case a :: b :: HNil => - Attempt.successful(None :: None :: None :: Some(a) :: Some(b) :: None :: HNil) - }, - { - case (_ :: _ :: _ :: None :: _ :: _ :: HNil) | (_ :: _ :: _ :: _ :: None :: _ :: HNil) => - Attempt.failure(Err("expected two long values but found one")) - case _ :: _ :: _ :: Some(a) :: Some(b) :: _ :: HNil => - Attempt.successful(a :: b :: HNil) - } - ) - - /** - * `Codec` for reading a `String`, a `Long`, and two `Int`s from remaining stream data. - * All `String`s processed by this reading are wide character and are padded by six. - * @return a filled-out `allPattern` if successful - */ - def complexCodec : Codec[allPattern] = ( - PacketHelpers.encodedWideStringAligned(6) :: - ulongL(46) :: - uint16L :: - uintL(3) - ).exmap[allPattern] ( - { - case a :: b :: c :: d :: HNil => - Attempt.successful(Some(a) :: Some(c) :: Some(d) :: Some(b) :: None :: None :: HNil) - }, - { - case None:: _ :: _ :: _ :: _ :: _ :: HNil => - Attempt.failure(Err("expected a string value but found nothing")) - case _ :: _ :: _ :: None :: _ :: _ :: HNil => - Attempt.failure(Err("expected a long value but found nothing")) - case (_ :: None :: _ :: _ :: _ :: _ :: HNil) | (_ :: _ :: None :: _ :: _ :: _ :: HNil) => - Attempt.failure(Err("expected two integer values but found one")) - case Some(a) :: Some(c) :: Some(d) :: Some(b) :: _ :: _ :: HNil => - Attempt.successful(a :: b :: c :: d :: HNil) - } - ) - - import scala.annotation.switch - - /** - * Select the `Codec` to translate bit data in this packet with an `allPattern` format. - * @param action the purpose of this packet; - * also decides the content of the parameter fields - * @return an `allPattern` `Codec` that parses the appropriate data - */ - def selectCodec(action : Int) : Codec[allPattern] = (action : @switch) match { - case 3 | 8 | 26 | 35 | 41 => //TODO double check these - noneCodec - - case 28 | 29 | 30 | 31 => - boolCodec - - case 33 => - intCodec(uintL(3)) - case 10 | 11 | 21 | 22 | 40 => - intCodec(uint4L) - case 20 => - intCodec(uint16L) - - case 13 | 14 | 15 | 37 => - longCodec - - case 7 | 19 => - stringCodec - - case 12 | 38 => - intLongCodec(uint32L) - case 25 => - intLongCodec(ulongL(46)) - - case 23 | 24 => - intStringCodec - - case 36 => - longLongCodec - - case 34 => - complexCodec - - case _ => - //TODO for debugging purposes only; normal failure condition below - bits.hlist.exmap[allPattern] ( - { - case x :: HNil => - org.log4s.getLogger.warn(s"can not match a codec pattern for decoding $action") - Attempt.successful(Some(x.toString) :: None :: None :: None :: None :: None :: HNil) - }, - { - case Some(x) :: None :: None :: None :: None :: None :: HNil => - org.log4s.getLogger.warn(s"can not match a codec pattern for encoding $action") - Attempt.successful(scodec.bits.BitVector.fromValidBin(x) :: HNil) - } - ) -// ignore(0).exmap[allPattern] ( -// { -// case () => -// Attempt.failure(Err(s"can not match a codec pattern for decoding $action")) -// }, -// { -// case _ :: _ :: _ :: _ :: _ :: _ :: HNil => -// Attempt.failure(Err(s"can not match a codec pattern for encoding $action")) -// } -// ) + def selectFromActionCode(code : Int) : Codec[SquadAction] = { + import SquadAction.Codecs._ + import scala.annotation.switch + ((code : @switch) match { + case 3 => saveSquadDefinitionCodec + case 8 => listSquadCodec + case 10 => selectRoleForYourselfCodec + case 19 => changeSquadPurposeCodec + case 20 => changeSquadZoneCodec + case 21 => closeSquadMemberPositionCodec + case 22 => addSquadMemberPositionCodec + case 23 => changeSquadMemberRequirementsRoleCodec + case 24 => changeSquadMemberRequirementsDetailedOrdersCodec + case 25 => changeSquadMemberRequirementsWeaponsCodec + case 26 => resetAllCodec + case 28 => autoApproveInvitationRequestsCodec + case 31 => locationFollowsSquadLeadCodec + case 34 => searchForSquadsWithParticularRoleCodec + case 35 => cancelSquadSearchCodec + case 40 => findLfsSoldiersForRoleCodec + case 41 => cancelFindCodec + case 0 | 1 | 2 | 4 | 6 | 7 | 9 | + 11 | 12 | 13 | 14 | 15 | 16 | + 17 | 18 | 29 | 30 | 33 | 36 | + 37 | 38 | 42 | 43 => unknownCodec(code) + case _ => failureCodec(code) + }).asInstanceOf[Codec[SquadAction]] } implicit val codec : Codec[SquadDefinitionActionMessage] = ( - ("action" | uintL(6)) >>:~ { action => + uintL(6) >>:~ { code => ("unk1" | uint16L) :: ("unk2" | uint4L) :: - selectCodec(action) + ("action" | selectFromActionCode(code)) } - ).as[SquadDefinitionActionMessage] + ).xmap[SquadDefinitionActionMessage] ( + { + case _ :: u1 :: u2 :: action :: HNil => + SquadDefinitionActionMessage(u1, u2, action) + }, + { + case SquadDefinitionActionMessage(u1, u2, action) => + action.code :: u1 :: u2 :: action :: HNil + } + ) } /* diff --git a/common/src/test/scala/game/SquadDefinitionActionMessageTest.scala b/common/src/test/scala/game/SquadDefinitionActionMessageTest.scala index a9657dec..934422a7 100644 --- a/common/src/test/scala/game/SquadDefinitionActionMessageTest.scala +++ b/common/src/test/scala/game/SquadDefinitionActionMessageTest.scala @@ -3,6 +3,7 @@ package game import org.specs2.mutable._ import net.psforever.packet._ +import net.psforever.packet.game.SquadAction._ import net.psforever.packet.game._ import scodec.bits._ @@ -30,18 +31,15 @@ class SquadDefinitionActionMessageTest extends Specification { val string_40 = hex"E7 a0 000004" //index: 1 val string_41 = hex"E7 a4 000000" + val string_43 = hex"e7 ac 000000" + val string_failure = hex"E7 ff" + "decode (03)" in { PacketCoding.DecodePacket(string_03).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 3 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 3 - str.isDefined mustEqual false - int1.isDefined mustEqual false - int2.isDefined mustEqual false - long1.isDefined mustEqual false - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual SaveSquadDefinition() case _ => ko } @@ -49,16 +47,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (08)" in { PacketCoding.DecodePacket(string_08).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 8 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual false - int1.isDefined mustEqual false - int2.isDefined mustEqual false - long1.isDefined mustEqual false - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual ListSquad() case _ => ko } @@ -66,17 +58,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (10)" in { PacketCoding.DecodePacket(string_10).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 10 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual false - int1.isDefined mustEqual true - int1.get mustEqual 1 - int2.isDefined mustEqual false - long1.isDefined mustEqual false - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual SelectRoleForYourself(1) case _ => ko } @@ -84,17 +69,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (19)" in { PacketCoding.DecodePacket(string_19).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 19 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual true - str.get mustEqual "A-Team" - int1.isDefined mustEqual false - int2.isDefined mustEqual false - long1.isDefined mustEqual false - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual ChangeSquadPurpose("A-Team") case _ => ko } @@ -102,17 +80,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (20)" in { PacketCoding.DecodePacket(string_20).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 20 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual false - int1.isDefined mustEqual true - int1.get mustEqual 1 - int2.isDefined mustEqual false - long1.isDefined mustEqual false - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual ChangeSquadZone(PlanetSideZoneID(1)) case _ => ko } @@ -120,17 +91,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (21)" in { PacketCoding.DecodePacket(string_21).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 21 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual false - int1.isDefined mustEqual true - int1.get mustEqual 2 - int2.isDefined mustEqual false - long1.isDefined mustEqual false - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual CloseSquadMemberPosition(2) case _ => ko } @@ -138,17 +102,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (22)" in { PacketCoding.DecodePacket(string_22).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 22 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual false - int1.isDefined mustEqual true - int1.get mustEqual 2 - int2.isDefined mustEqual false - long1.isDefined mustEqual false - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual AddSquadMemberPosition(2) case _ => ko } @@ -156,18 +113,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (23)" in { PacketCoding.DecodePacket(string_23).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 23 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual true - str.get mustEqual "BLUFOR" - int1.isDefined mustEqual true - int1.get mustEqual 1 - int2.isDefined mustEqual false - long1.isDefined mustEqual false - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual ChangeSquadMemberRequirementsRole(1, "BLUFOR") case _ => ko } @@ -175,18 +124,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (24)" in { PacketCoding.DecodePacket(string_24).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 24 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual true - str.get mustEqual "kill bad dudes" - int1.isDefined mustEqual true - int1.get mustEqual 1 - int2.isDefined mustEqual false - long1.isDefined mustEqual false - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual ChangeSquadMemberRequirementsDetailedOrders(1, "kill bad dudes") case _ => ko } @@ -194,18 +135,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (25)" in { PacketCoding.DecodePacket(string_25).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 25 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual false - int1.isDefined mustEqual true - int1.get mustEqual 1 - int2.isDefined mustEqual false - long1.isDefined mustEqual true - long1.get mustEqual 536870928L - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual ChangeSquadMemberRequirementsWeapons(1, 536870928L) case _ => ko } @@ -213,16 +146,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (26)" in { PacketCoding.DecodePacket(string_26).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 26 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual false - int1.isDefined mustEqual false - int2.isDefined mustEqual false - long1.isDefined mustEqual false - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual ResetAll() case _ => ko } @@ -230,17 +157,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (28)" in { PacketCoding.DecodePacket(string_28).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 28 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual false - int1.isDefined mustEqual false - int2.isDefined mustEqual false - long1.isDefined mustEqual false - long2.isDefined mustEqual false - bool.isDefined mustEqual true - bool.get mustEqual true + action mustEqual AutoApproveInvitationRequests(true) case _ => ko } @@ -248,17 +168,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (31)" in { PacketCoding.DecodePacket(string_31).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 31 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual false - int1.isDefined mustEqual false - int2.isDefined mustEqual false - long1.isDefined mustEqual false - long2.isDefined mustEqual false - bool.isDefined mustEqual true - bool.get mustEqual true + action mustEqual LocationFollowsSquadLead(true) case _ => ko } @@ -266,20 +179,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (34a)" in { PacketCoding.DecodePacket(string_34a).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 34 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual true - str.get mustEqual "Badass" - int1.isDefined mustEqual true - int1.get mustEqual 1 - int2.isDefined mustEqual true - int2.get mustEqual 0 - long1.isDefined mustEqual true - long1.get mustEqual 0 - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual SearchForSquadsWithParticularRole("Badass", 0L, 1, 0) case _ => ko } @@ -287,20 +190,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (34b)" in { PacketCoding.DecodePacket(string_34b).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 34 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual true - str.get mustEqual "Badass" - int1.isDefined mustEqual true - int1.get mustEqual 2 - int2.isDefined mustEqual true - int2.get mustEqual 0 - long1.isDefined mustEqual true - long1.get mustEqual 0 - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual SearchForSquadsWithParticularRole("Badass", 0L, 2, 0) case _ => ko } @@ -308,20 +201,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (34c)" in { PacketCoding.DecodePacket(string_34c).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 34 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual true - str.get mustEqual "Badass" - int1.isDefined mustEqual true - int1.get mustEqual 2 - int2.isDefined mustEqual true - int2.get mustEqual 1 - long1.isDefined mustEqual true - long1.get mustEqual 0 - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual SearchForSquadsWithParticularRole("Badass", 0L, 2, 1) case _ => ko } @@ -329,20 +212,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (34d)" in { PacketCoding.DecodePacket(string_34d).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 34 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual true - str.get mustEqual "Badass" - int1.isDefined mustEqual true - int1.get mustEqual 2 - int2.isDefined mustEqual true - int2.get mustEqual 2 - long1.isDefined mustEqual true - long1.get mustEqual 536870928L - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual SearchForSquadsWithParticularRole("Badass", 536870928L, 2, 2) case _ => ko } @@ -350,20 +223,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (34e)" in { PacketCoding.DecodePacket(string_34e).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 34 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual true - str.get mustEqual "Badass" - int1.isDefined mustEqual true - int1.get mustEqual 2 - int2.isDefined mustEqual true - int2.get mustEqual 3 - long1.isDefined mustEqual true - long1.get mustEqual 536870928L - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual SearchForSquadsWithParticularRole("Badass", 536870928L, 2, 3) case _ => ko } @@ -371,16 +234,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (35)" in { PacketCoding.DecodePacket(string_35).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 35 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual false - int1.isDefined mustEqual false - int2.isDefined mustEqual false - long1.isDefined mustEqual false - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual CancelSquadSearch() case _ => ko } @@ -388,17 +245,10 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (40)" in { PacketCoding.DecodePacket(string_40).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 40 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual false - int1.isDefined mustEqual true - int1.get mustEqual 1 - int2.isDefined mustEqual false - long1.isDefined mustEqual false - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual FindLfsSoldiersForRole(1) case _ => ko } @@ -406,165 +256,181 @@ class SquadDefinitionActionMessageTest extends Specification { "decode (41)" in { PacketCoding.DecodePacket(string_41).require match { - case SquadDefinitionActionMessage(action, unk1, unk2, str, int1, int2, long1, long2, bool) => - action mustEqual 41 + case SquadDefinitionActionMessage(unk1, unk2, action) => unk1 mustEqual 0 unk2 mustEqual 0 - str.isDefined mustEqual false - int1.isDefined mustEqual false - int2.isDefined mustEqual false - long1.isDefined mustEqual false - long2.isDefined mustEqual false - bool.isDefined mustEqual false + action mustEqual CancelFind() case _ => ko } } + "decode (43, unknown)" in { + PacketCoding.DecodePacket(string_43).require match { + case SquadDefinitionActionMessage(unk1, unk2, action) => + unk1 mustEqual 0 + unk2 mustEqual 0 + action mustEqual Unknown(43, hex"00".toBitVector.take(6)) + case _ => + ko + } + } + + "decode (failure)" in { + PacketCoding.DecodePacket(string_failure).isFailure mustEqual true + } + "encode (03)" in { - val msg = SquadDefinitionActionMessage(3, 0, 3, None, None, None, None, None, None) + val msg = SquadDefinitionActionMessage(0, 3, SaveSquadDefinition()) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_03 } "encode (08)" in { - val msg = SquadDefinitionActionMessage(8, 0, 0, None, None, None, None, None, None) + val msg = SquadDefinitionActionMessage(0, 0, ListSquad()) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_08 } "encode (10)" in { - val msg = SquadDefinitionActionMessage(10, 0, 0, None, Some(1), None, None, None, None) + val msg = SquadDefinitionActionMessage(0, 0, SelectRoleForYourself(1)) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_10 } "encode (19)" in { - val msg = SquadDefinitionActionMessage(19, 0, 0, Some("A-Team"), None, None, None, None, None) + val msg = SquadDefinitionActionMessage(0, 0, ChangeSquadPurpose("A-Team")) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_19 } "encode (20)" in { - val msg = SquadDefinitionActionMessage(20, 0, 0, None, Some(1), None, None, None, None) + val msg = SquadDefinitionActionMessage(0, 0, ChangeSquadZone(PlanetSideZoneID(1))) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_20 } "encode (21)" in { - val msg = SquadDefinitionActionMessage(21, 0, 0, None, Some(2), None, None, None, None) + val msg = SquadDefinitionActionMessage(0, 0, CloseSquadMemberPosition(2)) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_21 } "encode (22)" in { - val msg = SquadDefinitionActionMessage(22, 0, 0, None, Some(2), None, None, None, None) + val msg = SquadDefinitionActionMessage(0, 0, AddSquadMemberPosition(2)) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_22 } "encode (23)" in { - val msg = SquadDefinitionActionMessage(23, 0, 0, Some("BLUFOR"), Some(1), None, None, None, None) + val msg = SquadDefinitionActionMessage(0, 0, ChangeSquadMemberRequirementsRole(1, "BLUFOR")) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_23 } "encode (24)" in { - val msg = SquadDefinitionActionMessage(24, 0, 0, Some("kill bad dudes"), Some(1), None, None, None, None) + val msg = SquadDefinitionActionMessage(0, 0, ChangeSquadMemberRequirementsDetailedOrders(1, "kill bad dudes")) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_24 } "encode (25)" in { - val msg = SquadDefinitionActionMessage(25, 0, 0, None, Some(1), None, Some(536870928L), None, None) + val msg = SquadDefinitionActionMessage(0, 0, ChangeSquadMemberRequirementsWeapons(1, 536870928L)) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_25 } "encode (26)" in { - val msg = SquadDefinitionActionMessage(26, 0, 0, None, None, None, None, None, None) + val msg = SquadDefinitionActionMessage(0, 0, ResetAll()) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_26 } "encode (28)" in { - val msg = SquadDefinitionActionMessage(28, 0, 0, None, None, None, None, None, Some(true)) + val msg = SquadDefinitionActionMessage(0, 0, AutoApproveInvitationRequests(true)) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_28 } "encode (31)" in { - val msg = SquadDefinitionActionMessage(31, 0, 0, None, None, None, None, None, Some(true)) + val msg = SquadDefinitionActionMessage(0, 0, LocationFollowsSquadLead(true)) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_31 } "encode (34a)" in { - val msg = SquadDefinitionActionMessage(34, 0, 0, Some("Badass"), Some(1), Some(0), Some(0L), None, None) + val msg = SquadDefinitionActionMessage(0, 0, SearchForSquadsWithParticularRole("Badass", 0L, 1, 0)) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_34a } "encode (34b)" in { - val msg = SquadDefinitionActionMessage(34, 0, 0, Some("Badass"), Some(2), Some(0), Some(0L), None, None) + val msg = SquadDefinitionActionMessage(0, 0, SearchForSquadsWithParticularRole("Badass", 0L, 2, 0)) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_34b } "encode (34c)" in { - val msg = SquadDefinitionActionMessage(34, 0, 0, Some("Badass"), Some(2), Some(1), Some(0L), None, None) + val msg = SquadDefinitionActionMessage(0, 0, SearchForSquadsWithParticularRole("Badass", 0L, 2, 1)) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_34c } "encode (34d)" in { - val msg = SquadDefinitionActionMessage(34, 0, 0, Some("Badass"), Some(2), Some(2), Some(536870928L), None, None) + val msg = SquadDefinitionActionMessage(0, 0, SearchForSquadsWithParticularRole("Badass", 536870928L, 2, 2)) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_34d } "encode (34e)" in { - val msg = SquadDefinitionActionMessage(34, 0, 0, Some("Badass"), Some(2), Some(3), Some(536870928L), None, None) + val msg = SquadDefinitionActionMessage(0, 0, SearchForSquadsWithParticularRole("Badass", 536870928L, 2, 3)) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_34e } "encode (35)" in { - val msg = SquadDefinitionActionMessage(35, 0, 0, None, None, None, None, None, None) + val msg = SquadDefinitionActionMessage(0, 0, CancelSquadSearch()) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_35 } "encode (40)" in { - val msg = SquadDefinitionActionMessage(40, 0, 0, None, Some(1), None, None, None, None) + val msg = SquadDefinitionActionMessage(0, 0, FindLfsSoldiersForRole(1)) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_40 } "encode (41)" in { - val msg = SquadDefinitionActionMessage(41, 0, 0, None, None, None, None, None, None) + val msg = SquadDefinitionActionMessage(0, 0, CancelFind()) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_41 } + + "encode (43, unknown)" in { + val msg = SquadDefinitionActionMessage(0, 0, Unknown(43, BitVector.empty)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_43 + } }