diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index a502732a..757772ee 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -571,7 +571,7 @@ object GamePacketOpcode extends Enumeration { case 0xd2 => noDecoder(RespawnAMSInfoMessage) case 0xd3 => noDecoder(ComponentDamageMessage) case 0xd4 => noDecoder(GenericObjectActionAtPositionMessage) - case 0xd5 => noDecoder(PropertyOverrideMessage) + case 0xd5 => game.PropertyOverrideMessage.decode case 0xd6 => noDecoder(WarpgateLinkOverrideMessage) case 0xd7 => noDecoder(EmpireBenefitsMessage) // 0xd8 diff --git a/common/src/main/scala/net/psforever/packet/game/PropertyOverrideMessage.scala b/common/src/main/scala/net/psforever/packet/game/PropertyOverrideMessage.scala new file mode 100644 index 00000000..39a5a7ae --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/PropertyOverrideMessage.scala @@ -0,0 +1,202 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import scodec.Codec +import scodec.codecs._ +import shapeless.{::, HNil} + +/** + * Dispatched by the server to alert the client about custom permissions in different zones.
+ *
+ * The primarily way this packet was used on Gemini Live was to restrict weapons per zone. + * The Battle Island restrictions, for example, were enforced by these properties.
+ *
+ * Exploration:
+ * What else can you do with this packet? + * @param list a `List` defining scopes for the targets of internal property changes + */ +final case class PropertyOverrideMessage(list : List[PropertyOverrideMessage.GamePropertyScope]) + extends PlanetSideGamePacket { + type Packet = PropertyOverrideMessage + def opcode = GamePacketOpcode.PropertyOverrideMessage + def encode = PropertyOverrideMessage.encode(this) +} + +object GamePropertyTarget { + /** + * A target value referring to general game properties. + * In the context of this `GamePacket`, usually scoped to a "global" zone. + */ + final val game_properties : Int = 343 + + /** + * Overloaded constructor for defining a target for a single paired key and value (String -> String). + * @param target the target + * @param kv the key-value pair + * @return a `PropertyOverrideMessage.GamePropertyTarget` association object + */ + def apply(target : Int, kv : (String, String)) : PropertyOverrideMessage.GamePropertyTarget = { + PropertyOverrideMessage.GamePropertyTarget(target, PropertyOverrideMessage.GameProperty(kv._1, kv._2) :: Nil) + } + + /** + * Overloaded constructor for defining a target for a list of paired key and value. + * @param target the target + * @param list a `List` of key-value pairs + * @return a `PropertyOverrideMessage.GamePropertyTarget` association object + */ + def apply(target : Int, list : List[(String, String)]) : PropertyOverrideMessage.GamePropertyTarget = { + PropertyOverrideMessage.GamePropertyTarget(target, list.map({ + case(key, value) => + PropertyOverrideMessage.GameProperty(key, value) + })) + } +} + +object GamePropertyScope { + /** + * Overloaded constructor for defining a scope for the contained property. + * @param zone a game zone where this property is valid + * @param property a targeted key-value pair + * @return a `PropertyOverrideMessage.GamePropertyScope` association object + */ + def apply(zone : Int, property : PropertyOverrideMessage.GamePropertyTarget) : PropertyOverrideMessage.GamePropertyScope = { + PropertyOverrideMessage.GamePropertyScope(zone, property :: Nil) + } + + /** + * Overloaded constructor for defining a scope for the contained properties. + * @param zone a game zone where this property is valid + * @param list a `List` of targeted key-value pairs + * @return a `PropertyOverrideMessage.GamePropertyScope` association object + */ + def apply(zone : Int, list : List[PropertyOverrideMessage.GamePropertyTarget]) : PropertyOverrideMessage.GamePropertyScope = { + PropertyOverrideMessage.GamePropertyScope(zone, list) + } +} + +object PropertyOverrideMessage extends Marshallable[PropertyOverrideMessage] { + /** + * A wrapper class for the key-value pair. + * Another class's overloading allows this to be parsed in a format `field1 -> field2` slightly more idiomatic to pairs. + * @param field1 usually the "key;" + * occasionally, the only param + * @param field2 the "value" + */ + final case class GameProperty(field1 : String, field2 : String) + + /** + * The association between a target and the properties that affect it. + * @param target what game object is affected by these properties + * @param list the properties + * @see `ObjectClass` + */ + final case class GamePropertyTarget(target : Int, list : List[GameProperty]) + + /** + * The association between a continent/zone and how game objects are affected differently in that region. + * @param zone the continent/zone number; + * 0 refers to server-wide properties + * @param list the target and its property changes + */ + final case class GamePropertyScope(zone : Int, list : List[GamePropertyTarget]) + + /** + * Overloaded constructor for defining a single region where object properties are to be changed. + * @param list a list of regions, objects, and changed properties + * @return a `PropertyOverrideMessage` object + */ + def apply(list : PropertyOverrideMessage.GamePropertyScope) : PropertyOverrideMessage = { + PropertyOverrideMessage(list :: Nil) + } + + /** + * `Codec` for two strings containing a key-value pair, with the key being padded. + * @param n the padding of the first `String` + * @return a `GameProperty` object + */ + private def value_pair_aligned_codec(n : Int) : Codec[GameProperty] = ( + ("field1" | PacketHelpers.encodedStringAligned(n)) :: + ("field2" | PacketHelpers.encodedString) + ).as[GameProperty] + + /** + * `Codec` for two strings containing a key-value pair. + */ + private val value_pair_codec : Codec[GameProperty] = ( + ("field1" | PacketHelpers.encodedString) :: + ("field2" | PacketHelpers.encodedString) + ).as[GameProperty] + + /** + * `Codec` for defining the target and switching between and concatenating different key-value pair `Codec`s. + * @param n the padding of the first key in the first entry of the contents + * @return a `GamePropertyTarget` object + */ + private def game_property_target_codec(n : Int) : Codec[GamePropertyTarget] = ( + ("target" | uintL(11)) :: + (uint16L >>:~ { len => + conditional(len > 0, value_pair_aligned_codec(n)) :: + conditional(len > 1, PacketHelpers.listOfNSized((len - 1).toLong, value_pair_codec)) + }) + ).xmap[GamePropertyTarget] ( + { + case target :: _ :: None :: None :: HNil => //unlikely + GamePropertyTarget(target, Nil) + + case target :: _ :: Some(first) :: None :: HNil => + GamePropertyTarget(target, first :: Nil) + + case target :: _ :: Some(first) :: Some(other) :: HNil => + GamePropertyTarget(target, first +: other) + }, + { + case GamePropertyTarget(target, list) => + val (first, other) = list match { + case ((f : GameProperty) +: (rest : List[GameProperty])) => (Some(f), Some(rest)) + case (f : GameProperty) +: Nil => (Some(f), None) + case Nil => (None, None) //unlikely + } + target :: list.length :: first :: other :: HNil + } + ) + + /** + * `Codec` for defining the scope and switching between and concatenating different alignments for the target `Codec`.
+ *
+ * For every first target entry of a scope, the leading property string will incur the displacement of two 11-bit fields. + * That property will be byte-aligned by two bits. + * For every subsequent scope, the leading property will only incur the displacement of a single 11-bit field. + * These properties will be byte-aligned by five bits. + */ + private val game_property_scope_codec : Codec[GamePropertyScope] = ( + ("zone" | uint16L) :: + (uintL(11) >>:~ { len => + conditional(len > 0, game_property_target_codec(2)) :: + conditional(len > 1, PacketHelpers.listOfNSized((len - 1).toLong, game_property_target_codec(5))) + }) + ).xmap[GamePropertyScope] ( + { + case zone :: _ :: None :: None :: HNil => //unlikely + GamePropertyScope(zone, Nil) + + case zone :: _ :: Some(first) :: None :: HNil => + GamePropertyScope(zone, first :: Nil) + + case zone :: _ :: Some(first) :: Some(other) :: HNil => + GamePropertyScope(zone, first +: other) + }, + { + case GamePropertyScope(zone, list) => + val (first, other) = list match { + case ((f : GamePropertyTarget) +: (rest : List[GamePropertyTarget])) => (Some(f), Some(rest)) + case (f : GamePropertyTarget) +: Nil => (Some(f), None) + case Nil => (None, None) //unlikely + } + zone :: list.length :: first :: other :: HNil + } + ) + + implicit val codec : Codec[PropertyOverrideMessage] = listOfN(uint16L, game_property_scope_codec).as[PropertyOverrideMessage] +} diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala index 030def3d..53de8257 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala @@ -304,15 +304,27 @@ object ObjectClass { final val ams_destroyed = 47 final val ant = 60 final val ant_destroyed = 61 + final val aphelion_flight = 83 + final val aphelion_gunner = 84 final val aurora = 118 final val battlewagon = 135 //raider + final val colossus_flight = 199 + final val colossus_gunner = 200 final val droppod = 258 + final val flail = 294 final val fury = 335 + final val galaxy_gunship = 338 + final val liberator = 432 + final val lightgunship = 441 final val lightning = 446 final val lightning_destroyed = 447 + final val magrider = 470 final val mediumtransport = 532 final val mediumtransport_destroyed = 533 final val orbital_shuttle = 608 + final val peregrine_flight = 642 + final val peregrine_gunner = 643 + final val prowler = 697 final val quadassault = 707 final val quadassault_destroyed = 708 final val quadstealth = 710 @@ -328,6 +340,8 @@ object ObjectClass { final val twomanheavybuggy_destroyed = 899 final val twomanhoverbuggy = 900 //thresher final val twomanhoverbuggy_destroyed = 901 + final val vanguard = 923 + final val vulture = 986 //other final val ams_respawn_tube = 49 final val avatar = 121 diff --git a/common/src/test/scala/game/PropertyOverrideMessageTest.scala b/common/src/test/scala/game/PropertyOverrideMessageTest.scala new file mode 100644 index 00000000..4eb301cd --- /dev/null +++ b/common/src/test/scala/game/PropertyOverrideMessageTest.scala @@ -0,0 +1,548 @@ +// Copyright (c) 2017 PSForever +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import net.psforever.packet.game.objectcreate.ObjectClass +import scodec.bits._ + +class PropertyOverrideMessageTest extends Specification { + val string = hex"D5 0B 00 00 00 01 0A E4 0C 02 48 70 75 72 63 68 61 73 65 5F 65 78 65 6D 70 74 5F 76 73 80 92 70 75 72 63 68 61 73 65 5F 65 78 65 6D 70 74 5F 74 72 80 92 70 75 72 63 68 61 73 65 5F 65 78 65 6D 70 74 5F 6E 63 80 11 00 01 14 A4 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 12 00 01 14 A4 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 13 00 01 14 A4 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 14 00 01 14 A4 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 15 00 01 14 A4 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 16 00 01 14 A4 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 1D 00 15 0A 60 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 54 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 76 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 87 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 C7 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 C8 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 26 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 52 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 AD 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B0 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B9 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 CE 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 D6 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 2C 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 82 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 83 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B9 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 CA 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 61 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 9B 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 DA 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 1E 00 15 0A 60 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 54 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 76 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 87 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 C7 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 C8 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 26 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 52 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 AD 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B0 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B9 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 CE 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 D6 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 2C 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 82 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 83 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B9 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 CA 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 61 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 9B 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 DA 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 1F 00 15 0A 60 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 54 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 76 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 87 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 C7 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 C8 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 26 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 52 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 AD 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B0 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B9 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 CE 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 D6 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 2C 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 82 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 83 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B9 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 CA 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 61 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 9B 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 DA 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 20 00 15 0A 60 04 02 1C 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 54 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 76 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 87 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 C7 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 C8 00 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 26 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 52 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 AD 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B0 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B9 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 CE 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 D6 20 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 2C 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 82 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 83 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 B9 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 CA 40 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 61 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 9B 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65 DA 60 20 10 E0 61 6C 6C 6F 77 65 64 85 66 61 6C 73 65" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case PropertyOverrideMessage(list) => + list.length mustEqual 11 + // + list.head.zone mustEqual 0 + list.head.list.length mustEqual 1 + list.head.list.head.target mustEqual 343 + list.head.list.head.list.length mustEqual 3 + list.head.list.head.list.head.field1 mustEqual "purchase_exempt_vs" + list.head.list.head.list.head.field2 mustEqual "" + list.head.list.head.list(1).field1 mustEqual "purchase_exempt_tr" + list.head.list.head.list(1).field2 mustEqual "" + list.head.list.head.list(2).field1 mustEqual "purchase_exempt_nc" + list.head.list.head.list(2).field2 mustEqual "" + // + list(1).zone mustEqual 17 + list(1).list.length mustEqual 1 + list(1).list.head.target mustEqual 421 + list(1).list.head.list.length mustEqual 1 + list(1).list.head.list.head.field1 mustEqual "allowed" + list(1).list.head.list.head.field2 mustEqual "false" + // + list(2).zone mustEqual 18 + list(2).list.length mustEqual 1 + list(2).list.head.target mustEqual 421 + list(2).list.head.list.length mustEqual 1 + list(2).list.head.list.head.field1 mustEqual "allowed" + list(2).list.head.list.head.field2 mustEqual "false" + // + list(3).zone mustEqual 19 + list(3).list.length mustEqual 1 + list(3).list.head.target mustEqual 421 + list(3).list.head.list.length mustEqual 1 + list(3).list.head.list.head.field1 mustEqual "allowed" + list(3).list.head.list.head.field2 mustEqual "false" + // + list(4).zone mustEqual 20 + list(4).list.length mustEqual 1 + list(4).list.head.target mustEqual 421 + list(4).list.head.list.length mustEqual 1 + list(4).list.head.list.head.field1 mustEqual "allowed" + list(4).list.head.list.head.field2 mustEqual "false" + // + list(5).zone mustEqual 21 + list(5).list.length mustEqual 1 + list(5).list.head.target mustEqual 421 + list(5).list.head.list.length mustEqual 1 + list(5).list.head.list.head.field1 mustEqual "allowed" + list(5).list.head.list.head.field2 mustEqual "false" + // + list(6).zone mustEqual 22 + list(6).list.length mustEqual 1 + list(6).list.head.target mustEqual 421 + list(6).list.head.list.length mustEqual 1 + list(6).list.head.list.head.field1 mustEqual "allowed" + list(6).list.head.list.head.field2 mustEqual "false" + // + list(7).zone mustEqual 29 + list(7).list.length mustEqual 21 + list(7).list.head.target mustEqual 83 + list(7).list.head.list.length mustEqual 1 + list(7).list.head.list.head.field1 mustEqual "allowed" + list(7).list.head.list.head.field2 mustEqual "false" + list(7).list(1).target mustEqual 84 + list(7).list(1).list.length mustEqual 1 + list(7).list(1).list.head.field1 mustEqual "allowed" + list(7).list(1).list.head.field2 mustEqual "false" + list(7).list(2).target mustEqual 118 + list(7).list(2).list.length mustEqual 1 + list(7).list(2).list.head.field1 mustEqual "allowed" + list(7).list(2).list.head.field2 mustEqual "false" + list(7).list(3).target mustEqual 135 + list(7).list(3).list.length mustEqual 1 + list(7).list(3).list.head.field1 mustEqual "allowed" + list(7).list(3).list.head.field2 mustEqual "false" + list(7).list(4).target mustEqual 199 + list(7).list(4).list.length mustEqual 1 + list(7).list(4).list.head.field1 mustEqual "allowed" + list(7).list(4).list.head.field2 mustEqual "false" + list(7).list(5).target mustEqual 200 + list(7).list(5).list.length mustEqual 1 + list(7).list(5).list.head.field1 mustEqual "allowed" + list(7).list(5).list.head.field2 mustEqual "false" + list(7).list(6).target mustEqual 294 + list(7).list(6).list.length mustEqual 1 + list(7).list(6).list.head.field1 mustEqual "allowed" + list(7).list(6).list.head.field2 mustEqual "false" + list(7).list(7).target mustEqual 338 + list(7).list(7).list.length mustEqual 1 + list(7).list(7).list.head.field1 mustEqual "allowed" + list(7).list(7).list.head.field2 mustEqual "false" + list(7).list(8).target mustEqual 429 + list(7).list(8).list.length mustEqual 1 + list(7).list(8).list.head.field1 mustEqual "allowed" + list(7).list(8).list.head.field2 mustEqual "false" + list(7).list(9).target mustEqual 432 + list(7).list(9).list.length mustEqual 1 + list(7).list(9).list.head.field1 mustEqual "allowed" + list(7).list(9).list.head.field2 mustEqual "false" + list(7).list(10).target mustEqual 441 + list(7).list(10).list.length mustEqual 1 + list(7).list(10).list.head.field1 mustEqual "allowed" + list(7).list(10).list.head.field2 mustEqual "false" + list(7).list(11).target mustEqual 462 + list(7).list(11).list.length mustEqual 1 + list(7).list(11).list.head.field1 mustEqual "allowed" + list(7).list(11).list.head.field2 mustEqual "false" + list(7).list(12).target mustEqual 470 + list(7).list(12).list.length mustEqual 1 + list(7).list(12).list.head.field1 mustEqual "allowed" + list(7).list(12).list.head.field2 mustEqual "false" + list(7).list(13).target mustEqual 556 + list(7).list(13).list.length mustEqual 1 + list(7).list(13).list.head.field1 mustEqual "allowed" + list(7).list(13).list.head.field2 mustEqual "false" + list(7).list(14).target mustEqual 642 + list(7).list(14).list.length mustEqual 1 + list(7).list(14).list.head.field1 mustEqual "allowed" + list(7).list(14).list.head.field2 mustEqual "false" + list(7).list(15).target mustEqual 643 + list(7).list(15).list.length mustEqual 1 + list(7).list(15).list.head.field1 mustEqual "allowed" + list(7).list(15).list.head.field2 mustEqual "false" + list(7).list(16).target mustEqual 697 + list(7).list(16).list.length mustEqual 1 + list(7).list(16).list.head.field1 mustEqual "allowed" + list(7).list(16).list.head.field2 mustEqual "false" + list(7).list(17).target mustEqual 714 + list(7).list(17).list.length mustEqual 1 + list(7).list(17).list.head.field1 mustEqual "allowed" + list(7).list(17).list.head.field2 mustEqual "false" + list(7).list(18).target mustEqual 865 + list(7).list(18).list.length mustEqual 1 + list(7).list(18).list.head.field1 mustEqual "allowed" + list(7).list(18).list.head.field2 mustEqual "false" + list(7).list(19).target mustEqual 923 + list(7).list(19).list.length mustEqual 1 + list(7).list(19).list.head.field1 mustEqual "allowed" + list(7).list(19).list.head.field2 mustEqual "false" + list(7).list(20).target mustEqual 986 + list(7).list(20).list.length mustEqual 1 + list(7).list(20).list.head.field1 mustEqual "allowed" + list(7).list(20).list.head.field2 mustEqual "false" + // + list(8).zone mustEqual 30 + list(8).list.length mustEqual 21 + list(8).list.head.target mustEqual 83 + list(8).list.head.list.length mustEqual 1 + list(8).list.head.list.head.field1 mustEqual "allowed" + list(8).list.head.list.head.field2 mustEqual "false" + list(8).list(1).target mustEqual 84 + list(8).list(1).list.length mustEqual 1 + list(8).list(1).list.head.field1 mustEqual "allowed" + list(8).list(1).list.head.field2 mustEqual "false" + list(8).list(2).target mustEqual 118 + list(8).list(2).list.length mustEqual 1 + list(8).list(2).list.head.field1 mustEqual "allowed" + list(8).list(2).list.head.field2 mustEqual "false" + list(8).list(3).target mustEqual 135 + list(8).list(3).list.length mustEqual 1 + list(8).list(3).list.head.field1 mustEqual "allowed" + list(8).list(3).list.head.field2 mustEqual "false" + list(8).list(4).target mustEqual 199 + list(8).list(4).list.length mustEqual 1 + list(8).list(4).list.head.field1 mustEqual "allowed" + list(8).list(4).list.head.field2 mustEqual "false" + list(8).list(5).target mustEqual 200 + list(8).list(5).list.length mustEqual 1 + list(8).list(5).list.head.field1 mustEqual "allowed" + list(8).list(5).list.head.field2 mustEqual "false" + list(8).list(6).target mustEqual 294 + list(8).list(6).list.length mustEqual 1 + list(8).list(6).list.head.field1 mustEqual "allowed" + list(8).list(6).list.head.field2 mustEqual "false" + list(8).list(7).target mustEqual 338 + list(8).list(7).list.length mustEqual 1 + list(8).list(7).list.head.field1 mustEqual "allowed" + list(8).list(7).list.head.field2 mustEqual "false" + list(8).list(8).target mustEqual 429 + list(8).list(8).list.length mustEqual 1 + list(8).list(8).list.head.field1 mustEqual "allowed" + list(8).list(8).list.head.field2 mustEqual "false" + list(8).list(9).target mustEqual 432 + list(8).list(9).list.length mustEqual 1 + list(8).list(9).list.head.field1 mustEqual "allowed" + list(8).list(9).list.head.field2 mustEqual "false" + list(8).list(10).target mustEqual 441 + list(8).list(10).list.length mustEqual 1 + list(8).list(10).list.head.field1 mustEqual "allowed" + list(8).list(10).list.head.field2 mustEqual "false" + list(8).list(11).target mustEqual 462 + list(8).list(11).list.length mustEqual 1 + list(8).list(11).list.head.field1 mustEqual "allowed" + list(8).list(11).list.head.field2 mustEqual "false" + list(8).list(12).target mustEqual 470 + list(8).list(12).list.length mustEqual 1 + list(8).list(12).list.head.field1 mustEqual "allowed" + list(8).list(12).list.head.field2 mustEqual "false" + list(8).list(13).target mustEqual 556 + list(8).list(13).list.length mustEqual 1 + list(8).list(13).list.head.field1 mustEqual "allowed" + list(8).list(13).list.head.field2 mustEqual "false" + list(8).list(14).target mustEqual 642 + list(8).list(14).list.length mustEqual 1 + list(8).list(14).list.head.field1 mustEqual "allowed" + list(8).list(14).list.head.field2 mustEqual "false" + list(8).list(15).target mustEqual 643 + list(8).list(15).list.length mustEqual 1 + list(8).list(15).list.head.field1 mustEqual "allowed" + list(8).list(15).list.head.field2 mustEqual "false" + list(8).list(16).target mustEqual 697 + list(8).list(16).list.length mustEqual 1 + list(8).list(16).list.head.field1 mustEqual "allowed" + list(8).list(16).list.head.field2 mustEqual "false" + list(8).list(17).target mustEqual 714 + list(8).list(17).list.length mustEqual 1 + list(8).list(17).list.head.field1 mustEqual "allowed" + list(8).list(17).list.head.field2 mustEqual "false" + list(8).list(18).target mustEqual 865 + list(8).list(18).list.length mustEqual 1 + list(8).list(18).list.head.field1 mustEqual "allowed" + list(8).list(18).list.head.field2 mustEqual "false" + list(8).list(19).target mustEqual 923 + list(8).list(19).list.length mustEqual 1 + list(8).list(19).list.head.field1 mustEqual "allowed" + list(8).list(19).list.head.field2 mustEqual "false" + list(8).list(20).target mustEqual 986 + list(8).list(20).list.length mustEqual 1 + list(8).list(20).list.head.field1 mustEqual "allowed" + list(8).list(20).list.head.field2 mustEqual "false" + // + list(9).zone mustEqual 31 + list(9).list.length mustEqual 21 + list(9).list.head.target mustEqual 83 + list(9).list.head.list.length mustEqual 1 + list(9).list.head.list.head.field1 mustEqual "allowed" + list(9).list.head.list.head.field2 mustEqual "false" + list(9).list(1).target mustEqual 84 + list(9).list(1).list.length mustEqual 1 + list(9).list(1).list.head.field1 mustEqual "allowed" + list(9).list(1).list.head.field2 mustEqual "false" + list(9).list(2).target mustEqual 118 + list(9).list(2).list.length mustEqual 1 + list(9).list(2).list.head.field1 mustEqual "allowed" + list(9).list(2).list.head.field2 mustEqual "false" + list(9).list(3).target mustEqual 135 + list(9).list(3).list.length mustEqual 1 + list(9).list(3).list.head.field1 mustEqual "allowed" + list(9).list(3).list.head.field2 mustEqual "false" + list(9).list(4).target mustEqual 199 + list(9).list(4).list.length mustEqual 1 + list(9).list(4).list.head.field1 mustEqual "allowed" + list(9).list(4).list.head.field2 mustEqual "false" + list(9).list(5).target mustEqual 200 + list(9).list(5).list.length mustEqual 1 + list(9).list(5).list.head.field1 mustEqual "allowed" + list(9).list(5).list.head.field2 mustEqual "false" + list(9).list(6).target mustEqual 294 + list(9).list(6).list.length mustEqual 1 + list(9).list(6).list.head.field1 mustEqual "allowed" + list(9).list(6).list.head.field2 mustEqual "false" + list(9).list(7).target mustEqual 338 + list(9).list(7).list.length mustEqual 1 + list(9).list(7).list.head.field1 mustEqual "allowed" + list(9).list(7).list.head.field2 mustEqual "false" + list(9).list(8).target mustEqual 429 + list(9).list(8).list.length mustEqual 1 + list(9).list(8).list.head.field1 mustEqual "allowed" + list(9).list(8).list.head.field2 mustEqual "false" + list(9).list(9).target mustEqual 432 + list(9).list(9).list.length mustEqual 1 + list(9).list(9).list.head.field1 mustEqual "allowed" + list(9).list(9).list.head.field2 mustEqual "false" + list(9).list(10).target mustEqual 441 + list(9).list(10).list.length mustEqual 1 + list(9).list(10).list.head.field1 mustEqual "allowed" + list(9).list(10).list.head.field2 mustEqual "false" + list(9).list(11).target mustEqual 462 + list(9).list(11).list.length mustEqual 1 + list(9).list(11).list.head.field1 mustEqual "allowed" + list(9).list(11).list.head.field2 mustEqual "false" + list(9).list(12).target mustEqual 470 + list(9).list(12).list.length mustEqual 1 + list(9).list(12).list.head.field1 mustEqual "allowed" + list(9).list(12).list.head.field2 mustEqual "false" + list(9).list(13).target mustEqual 556 + list(9).list(13).list.length mustEqual 1 + list(9).list(13).list.head.field1 mustEqual "allowed" + list(9).list(13).list.head.field2 mustEqual "false" + list(9).list(14).target mustEqual 642 + list(9).list(14).list.length mustEqual 1 + list(9).list(14).list.head.field1 mustEqual "allowed" + list(9).list(14).list.head.field2 mustEqual "false" + list(9).list(15).target mustEqual 643 + list(9).list(15).list.length mustEqual 1 + list(9).list(15).list.head.field1 mustEqual "allowed" + list(9).list(15).list.head.field2 mustEqual "false" + list(9).list(16).target mustEqual 697 + list(9).list(16).list.length mustEqual 1 + list(9).list(16).list.head.field1 mustEqual "allowed" + list(9).list(16).list.head.field2 mustEqual "false" + list(9).list(17).target mustEqual 714 + list(9).list(17).list.length mustEqual 1 + list(9).list(17).list.head.field1 mustEqual "allowed" + list(9).list(17).list.head.field2 mustEqual "false" + list(9).list(18).target mustEqual 865 + list(9).list(18).list.length mustEqual 1 + list(9).list(18).list.head.field1 mustEqual "allowed" + list(9).list(18).list.head.field2 mustEqual "false" + list(9).list(19).target mustEqual 923 + list(9).list(19).list.length mustEqual 1 + list(9).list(19).list.head.field1 mustEqual "allowed" + list(9).list(19).list.head.field2 mustEqual "false" + list(9).list(20).target mustEqual 986 + list(9).list(20).list.length mustEqual 1 + list(9).list(20).list.head.field1 mustEqual "allowed" + list(9).list(20).list.head.field2 mustEqual "false" + // + list(10).zone mustEqual 32 + list(10).list.length mustEqual 21 + list(10).list.head.target mustEqual 83 + list(10).list.head.list.length mustEqual 1 + list(10).list.head.list.head.field1 mustEqual "allowed" + list(10).list.head.list.head.field2 mustEqual "false" + list(10).list(1).target mustEqual 84 + list(10).list(1).list.length mustEqual 1 + list(10).list(1).list.head.field1 mustEqual "allowed" + list(10).list(1).list.head.field2 mustEqual "false" + list(10).list(2).target mustEqual 118 + list(10).list(2).list.length mustEqual 1 + list(10).list(2).list.head.field1 mustEqual "allowed" + list(10).list(2).list.head.field2 mustEqual "false" + list(10).list(3).target mustEqual 135 + list(10).list(3).list.length mustEqual 1 + list(10).list(3).list.head.field1 mustEqual "allowed" + list(10).list(3).list.head.field2 mustEqual "false" + list(10).list(4).target mustEqual 199 + list(10).list(4).list.length mustEqual 1 + list(10).list(4).list.head.field1 mustEqual "allowed" + list(10).list(4).list.head.field2 mustEqual "false" + list(10).list(5).target mustEqual 200 + list(10).list(5).list.length mustEqual 1 + list(10).list(5).list.head.field1 mustEqual "allowed" + list(10).list(5).list.head.field2 mustEqual "false" + list(10).list(6).target mustEqual 294 + list(10).list(6).list.length mustEqual 1 + list(10).list(6).list.head.field1 mustEqual "allowed" + list(10).list(6).list.head.field2 mustEqual "false" + list(10).list(7).target mustEqual 338 + list(10).list(7).list.length mustEqual 1 + list(10).list(7).list.head.field1 mustEqual "allowed" + list(10).list(7).list.head.field2 mustEqual "false" + list(10).list(8).target mustEqual 429 + list(10).list(8).list.length mustEqual 1 + list(10).list(8).list.head.field1 mustEqual "allowed" + list(10).list(8).list.head.field2 mustEqual "false" + list(10).list(9).target mustEqual 432 + list(10).list(9).list.length mustEqual 1 + list(10).list(9).list.head.field1 mustEqual "allowed" + list(10).list(9).list.head.field2 mustEqual "false" + list(10).list(10).target mustEqual 441 + list(10).list(10).list.length mustEqual 1 + list(10).list(10).list.head.field1 mustEqual "allowed" + list(10).list(10).list.head.field2 mustEqual "false" + list(10).list(11).target mustEqual 462 + list(10).list(11).list.length mustEqual 1 + list(10).list(11).list.head.field1 mustEqual "allowed" + list(10).list(11).list.head.field2 mustEqual "false" + list(10).list(12).target mustEqual 470 + list(10).list(12).list.length mustEqual 1 + list(10).list(12).list.head.field1 mustEqual "allowed" + list(10).list(12).list.head.field2 mustEqual "false" + list(10).list(13).target mustEqual 556 + list(10).list(13).list.length mustEqual 1 + list(10).list(13).list.head.field1 mustEqual "allowed" + list(10).list(13).list.head.field2 mustEqual "false" + list(10).list(14).target mustEqual 642 + list(10).list(14).list.length mustEqual 1 + list(10).list(14).list.head.field1 mustEqual "allowed" + list(10).list(14).list.head.field2 mustEqual "false" + list(10).list(15).target mustEqual 643 + list(10).list(15).list.length mustEqual 1 + list(10).list(15).list.head.field1 mustEqual "allowed" + list(10).list(15).list.head.field2 mustEqual "false" + list(10).list(16).target mustEqual 697 + list(10).list(16).list.length mustEqual 1 + list(10).list(16).list.head.field1 mustEqual "allowed" + list(10).list(16).list.head.field2 mustEqual "false" + list(10).list(17).target mustEqual 714 + list(10).list(17).list.length mustEqual 1 + list(10).list(17).list.head.field1 mustEqual "allowed" + list(10).list(17).list.head.field2 mustEqual "false" + list(10).list(18).target mustEqual 865 + list(10).list(18).list.length mustEqual 1 + list(10).list(18).list.head.field1 mustEqual "allowed" + list(10).list(18).list.head.field2 mustEqual "false" + list(10).list(19).target mustEqual 923 + list(10).list(19).list.length mustEqual 1 + list(10).list(19).list.head.field1 mustEqual "allowed" + list(10).list(19).list.head.field2 mustEqual "false" + list(10).list(20).target mustEqual 986 + list(10).list(20).list.length mustEqual 1 + list(10).list(20).list.head.field1 mustEqual "allowed" + list(10).list(20).list.head.field2 mustEqual "false" + case _ => + ko + } + } + + "encode" in { + val msg = PropertyOverrideMessage( + List( + GamePropertyScope(0, + GamePropertyTarget(GamePropertyTarget.game_properties, List( + "purchase_exempt_vs" -> "", + "purchase_exempt_tr" -> "", + "purchase_exempt_nc" -> "" + ) + )), + GamePropertyScope(17, + GamePropertyTarget(ObjectClass.katana, "allowed" -> "false") + ), + GamePropertyScope(18, + GamePropertyTarget(ObjectClass.katana, "allowed" -> "false") + ), + GamePropertyScope(19, + GamePropertyTarget(ObjectClass.katana, "allowed" -> "false") + ), + GamePropertyScope(20, + GamePropertyTarget(ObjectClass.katana, "allowed" -> "false") + ), + GamePropertyScope(21, + GamePropertyTarget(ObjectClass.katana, "allowed" -> "false") + ), + GamePropertyScope(22, + GamePropertyTarget(ObjectClass.katana, "allowed" -> "false") + ), + GamePropertyScope(29, List( + GamePropertyTarget(ObjectClass.aphelion_flight, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.aphelion_gunner, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.aurora, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.battlewagon, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.colossus_flight, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.colossus_gunner, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.flail, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.galaxy_gunship, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.lasher, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.liberator, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.lightgunship, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.maelstrom, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.magrider, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.mini_chaingun, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.peregrine_flight, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.peregrine_gunner, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.prowler, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.r_shotgun, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.thunderer, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.vanguard, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.vulture, "allowed" -> "false") + )), + GamePropertyScope(30, List( + GamePropertyTarget(ObjectClass.aphelion_flight, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.aphelion_gunner, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.aurora, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.battlewagon, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.colossus_flight, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.colossus_gunner, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.flail, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.galaxy_gunship, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.lasher, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.liberator, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.lightgunship, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.maelstrom, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.magrider, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.mini_chaingun, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.peregrine_flight, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.peregrine_gunner, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.prowler, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.r_shotgun, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.thunderer, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.vanguard, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.vulture, "allowed" -> "false") + )), + GamePropertyScope(31, List( + GamePropertyTarget(ObjectClass.aphelion_flight, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.aphelion_gunner, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.aurora, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.battlewagon, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.colossus_flight, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.colossus_gunner, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.flail, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.galaxy_gunship, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.lasher, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.liberator, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.lightgunship, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.maelstrom, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.magrider, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.mini_chaingun, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.peregrine_flight, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.peregrine_gunner, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.prowler, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.r_shotgun, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.thunderer, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.vanguard, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.vulture, "allowed" -> "false") + )), + GamePropertyScope(32, List( + GamePropertyTarget(ObjectClass.aphelion_flight, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.aphelion_gunner, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.aurora, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.battlewagon, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.colossus_flight, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.colossus_gunner, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.flail, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.galaxy_gunship, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.lasher, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.liberator, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.lightgunship, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.maelstrom, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.magrider, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.mini_chaingun, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.peregrine_flight, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.peregrine_gunner, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.prowler, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.r_shotgun, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.thunderer, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.vanguard, "allowed" -> "false"), + GamePropertyTarget(ObjectClass.vulture, "allowed" -> "false") + )) + ) + ) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + pkt mustEqual string + } +}