diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index 986a2082..5c29f2b0 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -477,7 +477,7 @@ object GamePacketOpcode extends Enumeration { case 0x83 => noDecoder(SquadWaypointRequest) case 0x84 => noDecoder(SquadWaypointEvent) case 0x85 => noDecoder(OffshoreVehicleMessage) - case 0x86 => noDecoder(ObjectDeployedMessage) + case 0x86 => game.ObjectDeployedMessage.decode case 0x87 => noDecoder(ObjectDeployedCountMessage) // 0x88 case 0x88 => game.WeaponDelayFireMessage.decode diff --git a/common/src/main/scala/net/psforever/packet/game/ObjectDeployedMessage.scala b/common/src/main/scala/net/psforever/packet/game/ObjectDeployedMessage.scala new file mode 100644 index 00000000..9b6e237a --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/ObjectDeployedMessage.scala @@ -0,0 +1,89 @@ +// 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} + +/** + * An `Enumeration` for the forms of the event chat message produced by this packet. + */ +object DeploymentOutcome extends Enumeration(1) { + type Type = Value + + val Failure = Value(2) + //3 produces a Success message, but 4 is common + val Success = Value(4) + + val codec = PacketHelpers.createLongEnumerationCodec(this, uint32L) +} + +/** + * Dispatched by the server to generate a message in the events chat when placing deployables.
+ *
+ * This packet does not actually modify anything in regards to deployables. + * The most common form of the generated message is:
+ * `"You have placed x of a possible y thing s."`
+ * ... where `x` is the current count of objects of this type that have been deployed; + * `y` is the (reported) maximum amount of objects of this type that can be deployed; + * and, `thing` is the token for objects of this type. + * If the `thing` is a valid string token, it will be replaced by language-appropriate descriptive text in the message. + * Otherwise, that text is placed directly into the message, with an obvious space between the text and the "s". + * "boomer," for example, is replaced by "Boomer Heavy Explosives" in the message for English language. + * "bullet_9mm_AP," however, is just "bullet_9mm_AP s."
+ *
+ * When the `action` is `Success`, the message in the chat will be shown as above. + * When the `action` is `Failure`, the message will be:
+ * `"thing failed to deploy and was destroyed."`
+ * ... where, again, `thing` is a valid string token. + * @param unk na; + * usually 0? + * @param desc descriptive text of what kind of object is being deployed; + * string token of the object, at best + * @param action the form the message will take + * @param count the current number of this type of object deployed + * @param max the maximum number of this type of object that can be deployed + */ +final case class ObjectDeployedMessage(unk : Int, + desc : String, + action : DeploymentOutcome.Value, + count : Long, + max : Long) + extends PlanetSideGamePacket { + type Packet = ObjectDeployedMessage + def opcode = GamePacketOpcode.ObjectDeployedMessage + def encode = ObjectDeployedMessage.encode(this) +} + +object ObjectDeployedMessage extends Marshallable[ObjectDeployedMessage] { + /** + * Overloaded constructor for when the guid is not required. + * @param desc descriptive text of what kind of object is being deployed + * @param action na + * @param count the number of this type of object deployed + * @param max the maximum number of this type of object that can be deployed + * @return an `ObjectDeployedMessage` object + */ + def apply(desc : String, action : DeploymentOutcome.Value, count : Long, max : Long) : ObjectDeployedMessage = + new ObjectDeployedMessage(0, desc, action, count, max) + + implicit val codec : Codec[ObjectDeployedMessage] = ( + ("unk" | uint16L) :: + ("desc" | PacketHelpers.encodedString) :: + ("action" | DeploymentOutcome.codec) :: + ("count" | uint32L) :: + ("max" | uint32L) + ).xmap[ObjectDeployedMessage] ( + { + case guid :: str :: unk :: cnt ::mx :: HNil => + ObjectDeployedMessage(guid, str, unk, cnt, mx) + }, + { + case ObjectDeployedMessage(guid, str, unk, cnt, mx) => + //truncate string length to 100 characters; raise no warnings + val limitedStr : String = if(str.length() > 100) { str.substring(0,100) } else { str } + guid :: limitedStr :: unk :: cnt :: mx :: HNil + } + ) +} diff --git a/common/src/test/scala/game/ObjectDeployedMessageTest.scala b/common/src/test/scala/game/ObjectDeployedMessageTest.scala new file mode 100644 index 00000000..88c9c0b7 --- /dev/null +++ b/common/src/test/scala/game/ObjectDeployedMessageTest.scala @@ -0,0 +1,31 @@ +// Copyright (c) 2017 PSForever +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import scodec.bits._ + +class ObjectDeployedMessageTest extends Specification { + val string_boomer = hex"86 000086626F6F6D6572040000000100000019000000" + + "decode" in { + PacketCoding.DecodePacket(string_boomer).require match { + case ObjectDeployedMessage(unk : Int, desc : String, act : DeploymentOutcome.Value, count : Long, max : Long) => + unk mustEqual 0 + desc mustEqual "boomer" + act mustEqual DeploymentOutcome.Success + count mustEqual 1 + max mustEqual 25 + case _ => + ko + } + } + + "encode" in { + val msg = ObjectDeployedMessage("boomer", DeploymentOutcome.Success, 1, 25) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_boomer + } +}