From 30b5ad49bd443b0e136ea1525174c53a6b8d2605 Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 4 May 2017 23:20:31 -0400 Subject: [PATCH] initial work on TriggerEffectMessage packet and tests --- .../psforever/packet/GamePacketOpcode.scala | 2 +- .../packet/game/TriggerEffectMessage.scala | 83 +++++++++++++++++++ .../scala/game/TriggerEffectMessageTest.scala | 73 ++++++++++++++++ 3 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 common/src/main/scala/net/psforever/packet/game/TriggerEffectMessage.scala create mode 100644 common/src/test/scala/game/TriggerEffectMessageTest.scala diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index cad228880..0943fb3f0 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -415,7 +415,7 @@ object GamePacketOpcode extends Enumeration { // OPCODES 0x50-5f case 0x50 => game.TargetingInfoMessage.decode - case 0x51 => noDecoder(TriggerEffectMessage) + case 0x51 => game.TriggerEffectMessage.decode case 0x52 => game.WeaponDryFireMessage.decode case 0x53 => noDecoder(DroppodLaunchRequestMessage) case 0x54 => noDecoder(HackMessage) diff --git a/common/src/main/scala/net/psforever/packet/game/TriggerEffectMessage.scala b/common/src/main/scala/net/psforever/packet/game/TriggerEffectMessage.scala new file mode 100644 index 000000000..a8c2cc9be --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/TriggerEffectMessage.scala @@ -0,0 +1,83 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import net.psforever.types.Vector3 +import scodec.Codec +import scodec.codecs._ + +/** + * na + * @param unk1 na; + * `true` to apply the effect usually + * @param unk2 na + */ +final case class TriggeredEffect(unk1 : Boolean, + unk2 : Long) + +/** + * Activate an effect that is not directly associated with an existing game object. + * Without a game object from which to inherit position and orientation, those explicit parameters must be provided. + * @param pos the position in the game world + * @param roll the amount of roll that affects orientation + * @param pitch the amount of pitch that affects orientation + * @param yaw the amount of yaw that affects orientation + */ +final case class TriggeredEffectLocation(pos : Vector3, + roll : Int, + pitch : Int, + yaw : Int) + +/** + * Dispatched by the server to cause a client to display a special graphical effect.
+ *
+ * The effect being triggered can be based around a specific game object or replayed freely, absent of an anchoring object. + * If object-based then the kinds of effects that can be activated are specific to the object. + * If unbound, then a wider range of effects can be displayed. + * Regardless, one category will rarely ever be activated under the same valid conditions of the other category. + * For example, the effect "on" will only work on objects that accept "on" normally, like a deployed `motionalarmsensor`. + * The effect "spawn_object_effect" can be applied anywhere in the environment; + * but, it can not be activated in conjunction with an existing object. + * @param obj an object that accepts the effect + * @param effect the name of the effect + * @param unk na; + * when activating an effect on an existing object + * @param location an optional position where the effect will be displayed; + * when activating an effect independently + */ +final case class TriggerEffectMessage(obj : PlanetSideGUID, + effect : String, + unk : Option[TriggeredEffect] = None, + location : Option[TriggeredEffectLocation] = None + ) extends PlanetSideGamePacket { + type Packet = TriggerEffectMessage + def opcode = GamePacketOpcode.TriggerEffectMessage + def encode = TriggerEffectMessage.encode(this) +} + +object TriggerEffectMessage extends Marshallable[TriggerEffectMessage] { + /** + * A `Codec` for `TriggeredEffect` data. + */ + private val effect_codec : Codec[TriggeredEffect] = ( + ("unk1" | bool) :: + ("unk2" | uint32L) + ).as[TriggeredEffect] + + /** + * A `Codec` for `TriggeredEffectLocation` data. + */ + private val effect_location_codec : Codec[TriggeredEffectLocation] = ( + ("pos" | Vector3.codec_pos) :: + ("roll" | uint8L) :: + ("pitch" | uint8L) :: + ("yaw" | uint8L) + ).as[TriggeredEffectLocation] + + implicit val codec : Codec[TriggerEffectMessage] = ( + ("obj" | PlanetSideGUID.codec) >>:~ { obj => + ("effect" | PacketHelpers.encodedString) :: + optional(bool, "unk" | effect_codec) :: + conditional(obj.guid == 0, "location" | effect_location_codec) + }).as[TriggerEffectMessage] +} diff --git a/common/src/test/scala/game/TriggerEffectMessageTest.scala b/common/src/test/scala/game/TriggerEffectMessageTest.scala new file mode 100644 index 000000000..9e3e6faa5 --- /dev/null +++ b/common/src/test/scala/game/TriggerEffectMessageTest.scala @@ -0,0 +1,73 @@ +// Copyright (c) 2017 PSForever +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import net.psforever.types.Vector3 +import scodec.bits._ + +class TriggerEffectMessageTest extends Specification { + val string_motionalarmsensor = hex"51 970B 82 6F6E FA00C00000" + val string_boomer = hex"51 0000 93 737061776E5F6F626A6563745F656666656374 417BB2CB3B4F8E00000000" + + "decode (motion alarm sensor)" in { + PacketCoding.DecodePacket(string_motionalarmsensor).require match { + case TriggerEffectMessage(guid, effect, unk, location) => + guid mustEqual PlanetSideGUID(2967) + effect mustEqual "on" + unk.isDefined mustEqual true + unk.get.unk1 mustEqual true + unk.get.unk2 mustEqual 1000L + location.isDefined mustEqual false + case _ => + ko + } + } + + "decode (boomer)" in { + PacketCoding.DecodePacket(string_boomer).require match { + case TriggerEffectMessage(guid, effect, unk, location) => + guid mustEqual PlanetSideGUID(0) + effect mustEqual "spawn_object_effect" + unk.isDefined mustEqual false + location.isDefined mustEqual true + location.get.pos.x mustEqual 3567.0156f + location.get.pos.y mustEqual 3278.6953f + location.get.pos.z mustEqual 114.484375f + location.get.roll mustEqual 0 + location.get.pitch mustEqual 0 + location.get.yaw mustEqual 0 + case _ => + ko + } + } + + "encode (motion alarm sensor)" in { + val msg = TriggerEffectMessage( + PlanetSideGUID(2967), + "on", + Some(TriggeredEffect(true, 1000L)), + None + ) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_motionalarmsensor + } + + "encode (boomer)" in { + val msg = TriggerEffectMessage( + PlanetSideGUID(0), + "spawn_object_effect", + None, + Some(TriggeredEffectLocation( + Vector3(3567.0156f, 3278.6953f, 114.484375f), + 0, 0, 0 + )) + ) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_boomer + } +} +