diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index 008dbcb6..27ef110d 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -435,7 +435,7 @@ object GamePacketOpcode extends Enumeration { // OPCODES 0x60-6f case 0x60 => game.FavoritesMessage.decode case 0x61 => noDecoder(ObjectDetectedMessage) - case 0x62 => noDecoder(SplashHitMessage) + case 0x62 => game.SplashHitMessage.decode case 0x63 => noDecoder(SetChatFilterMessage) case 0x64 => noDecoder(AvatarSearchCriteriaMessage) case 0x65 => noDecoder(AvatarSearchResponse) diff --git a/common/src/main/scala/net/psforever/packet/game/SplashHitMessage.scala b/common/src/main/scala/net/psforever/packet/game/SplashHitMessage.scala new file mode 100644 index 00000000..76c02f2a --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/SplashHitMessage.scala @@ -0,0 +1,88 @@ +// Copyright (c) 2016 PSForever.net to present +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import net.psforever.types.Vector3 +import scodec.Codec +import scodec.codecs._ + +/** + * An entry of the target that was hit by splash damage. + * @param uid the target's uid + * @param pos the target's position (when hit) + * @param unk1 na + * @param unk2 na + */ +final case class SplashedTarget(uid : PlanetSideGUID, + pos : Vector3, + unk1 : Long, + unk2 : Option[Int]) + +/** + * Dispatched to the server when a type of effect that influence multiple targets activates.
+ *
+ * Splash does not refer to the effect upon an applicable target. + * Splash denotes the fixed radius wherein a said effect exerts temporary influence. + * Being damaged is the most common splash effect; the jammering effect is another. + * A pain field does not count because it is an environmental constant. + * Lashing is considered different because it is a type of inheritable influence.
+ *
+ * Valid targets for splash are all interactive game objects that maintain a GUID. + * This includes: players, of course; vehicles, of course; doors; terminals; spawn tubes; and, such objects. + * Not all targets listed will actually be influenced by the effect carried by splash.
+ *
+ * The effect commonly modifies the visual depiction of the splash. + * Being able to "see" splash also does not necessarily mean that one will be influenced by it. + * Visually and spatially, it may seem to bleed through surfaces on occasion. + * The effect will not be carried, however. + * Splash will also respect the game's internal zoning and not pass through temporary obstacles like closed doors. + * Not being able to see splash also does not stop a target from being affected. + * The radius of influence is typically a bit larger than the visual indication.
+ *
+ * All sources of splash damage herein will be called "grenades" for simplicity. + * @param unk1 na + * @param projectile_uid the grenade's object + * @param projectile_pos the position where the grenade landed (where it is) + * @param unk2 na; + * frequently 42 + * @param unk3 na; + * frequently 0 + * @param projectile_vel the velocity of the grenade when it landed + * @param unk4 na + * @param targets a `List` of all targets influenced by the splash + */ +final case class SplashHitMessage(unk1 : Int, + projectile_uid : PlanetSideGUID, + projectile_pos : Vector3, + unk2 : Int, + unk3 : Int, + projectile_vel : Option[Vector3], + unk4 : Option[Int], + targets : List[SplashedTarget]) + extends PlanetSideGamePacket { + type Packet = SplashHitMessage + def opcode = GamePacketOpcode.SplashHitMessage + def encode = SplashHitMessage.encode(this) +} + +object SplashedTarget extends Marshallable[SplashedTarget] { + implicit val codec : Codec[SplashedTarget] = ( + ("uid" | PlanetSideGUID.codec) :: + ("pos" | Vector3.codec_pos) :: + ("unk1" | uint32L) :: + optional(bool, "unk2" | uint16L) + ).as[SplashedTarget] +} + +object SplashHitMessage extends Marshallable[SplashHitMessage] { + implicit val codec : Codec[SplashHitMessage] = ( + ("unk1" | uintL(10)) :: + ("projectile_uid" | PlanetSideGUID.codec) :: + ("projectile_pos" | Vector3.codec_pos) :: + ("unk2" | uint16L) :: + ("unk3" | uintL(3)) :: + optional(bool, "projectile_vel" | Vector3.codec_vel) :: + optional(bool, "unk4" | uint16L) :: + ("targets" | PacketHelpers.listOfNAligned(uint32L, 0, SplashedTarget.codec)) + ).as[SplashHitMessage] +} diff --git a/common/src/test/scala/game/SplashHitMessageTest.scala b/common/src/test/scala/game/SplashHitMessageTest.scala new file mode 100644 index 00000000..9d7e8dbd --- /dev/null +++ b/common/src/test/scala/game/SplashHitMessageTest.scala @@ -0,0 +1,60 @@ +// Copyright (c) 2016 PSForever.net to present +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import net.psforever.types.Vector3 +import scodec.bits._ + +class SplashHitMessageTest extends Specification { + val string = hex"62 7129e72b0c1dd1516ec58000051e01d8371f0100000025803616bb2a9ae50b000008889d00644bdd35454c45c000000400" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case SplashHitMessage(unk1, projectile_uid, projectile_pos, unk2, unk3, projectile_vel, unk4, targets) => + unk1 mustEqual 113 + projectile_uid mustEqual PlanetSideGUID(40103) + projectile_pos.x mustEqual 3681.3438f + projectile_pos.y mustEqual 2728.9062f + projectile_pos.z mustEqual 90.921875f + unk2 mustEqual 0 + unk3 mustEqual 0 + projectile_vel.isDefined mustEqual true + projectile_vel.get.x mustEqual 2.21875f + projectile_vel.get.y mustEqual 0.90625f + projectile_vel.get.z mustEqual -1.125f + unk4.isDefined mustEqual false + targets.size mustEqual 2 + //0 + targets.head.uid mustEqual PlanetSideGUID(75) + targets.head.pos.x mustEqual 3674.8438f + targets.head.pos.y mustEqual 2726.789f + targets.head.pos.z mustEqual 91.15625f + targets.head.unk1 mustEqual 286326784L + targets.head.unk2.isDefined mustEqual false + //1 + targets(1).uid mustEqual PlanetSideGUID(372) + targets(1).pos.x mustEqual 3679.1328f + targets(1).pos.y mustEqual 2722.6016f + targets(1).pos.z mustEqual 92.765625f + targets(1).unk1 mustEqual 268435456L + targets(1).unk2.isDefined mustEqual false + case _ => + ko + } + } + + "encode" in { + val msg = SplashHitMessage(113, PlanetSideGUID(40103), + Vector3(3681.3438f, 2728.9062f, 90.921875f), 0, 0, + Some(Vector3(2.21875f, 0.90625f, -1.125f)), None, + SplashedTarget(PlanetSideGUID(75), Vector3(3674.8438f, 2726.789f, 91.15625f), 286326784L, None) :: + SplashedTarget(PlanetSideGUID(372), Vector3(3679.1328f, 2722.6016f, 92.765625f), 268435456L, None) :: + Nil + ) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } +} diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 2f8e0af4..8126ad8c 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -351,6 +351,9 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ HitMessage(seq_time, projectile_guid, unk1, hit_info, unk2, unk3, unk4) => log.info("Hit: " + msg) + case msg @ SplashHitMessage(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + log.info("SplashHitMessage: " + msg) + case msg @ AvatarFirstTimeEventMessage(avatar_guid, object_guid, unk1, event_name) => log.info("AvatarFirstTimeEvent: " + msg)