From 758eab167b25436e67a081444d3aadd39c6b28c6 Mon Sep 17 00:00:00 2001 From: FateJH Date: Sun, 18 Sep 2016 02:47:55 -0400 Subject: [PATCH 1/5] added initial CreateShortcutMessage packet and tests --- .../psforever/packet/GamePacketOpcode.scala | 2 +- .../packet/game/CreateShortcutMessage.scala | 56 +++++++++++++++ common/src/test/scala/GamePacketTest.scala | 72 +++++++++++++++++++ .../src/main/scala/WorldSessionActor.scala | 1 + 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index 6e9984e5..6a5de924 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -366,7 +366,7 @@ object GamePacketOpcode extends Enumeration { case 0x26 => noDecoder(UnuseItemMessage) case 0x27 => noDecoder(ObjectDetachMessage) // 0x28 - case 0x28 => noDecoder(CreateShortcutMessage) + case 0x28 => game.CreateShortcutMessage.decode case 0x29 => noDecoder(ChangeShortcutBankMessage) case 0x2a => noDecoder(ObjectAttachMessage) case 0x2b => noDecoder(UnknownMessage43) diff --git a/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala b/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala new file mode 100644 index 00000000..682867af --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala @@ -0,0 +1,56 @@ +// Copyright (c) 2016 PSForever.net to present +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import scodec.Codec +import scodec.codecs._ + +/** + * Create a quick-use button for the hotbar.
+ *
+ * Purpose:
+ * `advanced_regen` (regeneration)
+ * `audio_amplifier`
+ * `darklight_vision`
+ * `medkit`
+ * `melee_booster`
+ * `personal_shield`
+ * `range_magnifier`
+ * `second_wind`
+ * `shortcut_macro`
+ * `silent_run` (sensor shield)
+ * `surge`
+ * `targeting` + * @param player_guid the player + * @param slot the hotbar slot number (one-indexed) + * @param unk1 na + * @param unk2 na + * @param purpose the primary purpose/use of this shortcut + * @param effect1 for macros, a three letter acronym displayed in the hotbar + * @param effect2 for macros, the chat message content + */ +//TODO Failed to parse game packet 0x28: purpose: cannot acquire 15 bits from a vector that contains 4 bits -- encountered when attempting to move shortcuts -- look into this +final case class CreateShortcutMessage(player_guid : PlanetSideGUID, + slot : Int, + unk1 : Int, + unk2 : Int, + purpose : String, + effect1 : String = "", + effect2 : String = "") + extends PlanetSideGamePacket { + type Packet = CreateShortcutMessage + def opcode = GamePacketOpcode.CreateShortcutMessage + def encode = CreateShortcutMessage.encode(this) +} + +object CreateShortcutMessage extends Marshallable[CreateShortcutMessage] { + implicit val codec : Codec[CreateShortcutMessage] = ( + ("player_guid" | PlanetSideGUID.codec) :: + ("slot" | uint8L) :: + ("unk1" | uint8L) :: + ("unk2" | uintL(3)) :: + ("purpose" | PacketHelpers.encodedStringAligned(5)) :: + ("effect1" | PacketHelpers.encodedWideString) :: + ("effect2" | PacketHelpers.encodedWideString) + ).as[CreateShortcutMessage] +} diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala index 2f7e8a11..e65d3698 100644 --- a/common/src/test/scala/GamePacketTest.scala +++ b/common/src/test/scala/GamePacketTest.scala @@ -299,6 +299,78 @@ class GamePacketTest extends Specification { } } + "CreateShortcutMessage" should { + val stringMedkit = hex"28 7210 01 00 90 C0 6D65646B6974 80 80" + val stringImplant = hex"28 7210 04 00 D0 A0 7375726765 80 80" + val stringMacro = hex"28 4C05 08 00 B1 C0 73686F72746375745F6D6163726F 83 4E00 5400 5500 9B 2F00 7000 6C00 6100 7400 6F00 6F00 6E00 2000 4900 6E00 6300 6F00 6D00 6900 6E00 6700 2000 4E00 5400 5500 2000 7300 7000 6100 6D00 2100" + + "decode (medkit)" in { + PacketCoding.DecodePacket(stringMedkit).require match { + case CreateShortcutMessage(player_guid, slot, unk1, unk2, purpose, effect1, effect2) => + player_guid mustEqual PlanetSideGUID(4210) + slot mustEqual 1 + unk1 mustEqual 0 + unk2 mustEqual 4 + purpose mustEqual "medkit" + effect1 mustEqual "" + effect2 mustEqual "" + case default => + ko + } + } + + "decode (implant)" in { + PacketCoding.DecodePacket(stringImplant).require match { + case CreateShortcutMessage(player_guid, slot, unk1, unk2, purpose, effect1, effect2) => + player_guid mustEqual PlanetSideGUID(4210) + slot mustEqual 4 + unk1 mustEqual 0 + unk2 mustEqual 6 + purpose mustEqual "surge" + effect1 mustEqual "" + effect2 mustEqual "" + case default => + ko + } + } + + "decode (macro)" in { + PacketCoding.DecodePacket(stringMacro).require match { + case CreateShortcutMessage(player_guid, slot, unk1, unk2, purpose, effect1, effect2) => + player_guid mustEqual PlanetSideGUID(1356) + slot mustEqual 8 + unk1 mustEqual 0 + unk2 mustEqual 5 + purpose mustEqual "shortcut_macro" + effect1 mustEqual "NTU" + effect2 mustEqual "/platoon Incoming NTU spam!" + case default => + ko + } + } + + "encode (medkit)" in { + val msg = CreateShortcutMessage(PlanetSideGUID(4210), 1, 0, 4, "medkit") + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual stringMedkit + } + + "encode (implant)" in { + val msg = CreateShortcutMessage(PlanetSideGUID(4210), 4, 0, 6, "surge") + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual stringImplant + } + + "encode (macro)" in { + val msg = CreateShortcutMessage(PlanetSideGUID(1356), 8, 0, 5, "shortcut_macro", "NTU", "/platoon Incoming NTU spam!") + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual stringMacro + } + } + "DropItemMessage" should { val string = hex"37 4C00" diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index ea99a03c..625838e7 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -171,6 +171,7 @@ class WorldSessionActor extends Actor with MDCContextAware { true))) //Boosted generator room pain field sendResponse(PacketCoding.CreateGamePacket(0, SetCurrentAvatarMessage(PlanetSideGUID(guid),0,0))) + sendResponse(PacketCoding.CreateGamePacket(0, CreateShortcutMessage(PlanetSideGUID(guid), 1, 0, 6, "medkit"))) import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global From ce165aa9a52c1a3e967ec01f4ceb573abfe9a9da Mon Sep 17 00:00:00 2001 From: FateJH Date: Sun, 18 Sep 2016 15:18:38 -0400 Subject: [PATCH 2/5] added (failing) tests for unhandled situation; unworking code that leads towards handling that situation --- .../packet/game/CreateShortcutMessage.scala | 21 ++++++++++++++--- common/src/test/scala/GamePacketTest.scala | 23 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala b/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala index 682867af..f3bcd4da 100644 --- a/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala @@ -29,12 +29,12 @@ import scodec.codecs._ * @param effect1 for macros, a three letter acronym displayed in the hotbar * @param effect2 for macros, the chat message content */ -//TODO Failed to parse game packet 0x28: purpose: cannot acquire 15 bits from a vector that contains 4 bits -- encountered when attempting to move shortcuts -- look into this +//TODO must handle case 0x28 xxxx 01 00 00 for moving shortcuts final case class CreateShortcutMessage(player_guid : PlanetSideGUID, slot : Int, unk1 : Int, unk2 : Int, - purpose : String, + purpose : String = "", effect1 : String = "", effect2 : String = "") extends PlanetSideGamePacket { @@ -42,7 +42,7 @@ final case class CreateShortcutMessage(player_guid : PlanetSideGUID, def opcode = GamePacketOpcode.CreateShortcutMessage def encode = CreateShortcutMessage.encode(this) } - +//* object CreateShortcutMessage extends Marshallable[CreateShortcutMessage] { implicit val codec : Codec[CreateShortcutMessage] = ( ("player_guid" | PlanetSideGUID.codec) :: @@ -54,3 +54,18 @@ object CreateShortcutMessage extends Marshallable[CreateShortcutMessage] { ("effect2" | PacketHelpers.encodedWideString) ).as[CreateShortcutMessage] } +// */ +/* +object CreateShortcutMessage extends Marshallable[CreateShortcutMessage] { + implicit val codec : Codec[CreateShortcutMessage] = ( + ("player_guid" | PlanetSideGUID.codec) :: + ("slot" | uint8L) :: + ("unk1" | uint8L) :: + ("unk2" | uintL(3)) >>:~ ( value => + conditional(value.last > 0, "purpose" | PacketHelpers.encodedStringAligned(5)) :: + conditional(value.last > 0, "effect1" | PacketHelpers.encodedWideString) :: + conditional(value.last > 0, "effect2" | PacketHelpers.encodedWideString) + ) + ).as[CreateShortcutMessage] +} +// */ diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala index e65d3698..19a8c326 100644 --- a/common/src/test/scala/GamePacketTest.scala +++ b/common/src/test/scala/GamePacketTest.scala @@ -303,6 +303,7 @@ class GamePacketTest extends Specification { val stringMedkit = hex"28 7210 01 00 90 C0 6D65646B6974 80 80" val stringImplant = hex"28 7210 04 00 D0 A0 7375726765 80 80" val stringMacro = hex"28 4C05 08 00 B1 C0 73686F72746375745F6D6163726F 83 4E00 5400 5500 9B 2F00 7000 6C00 6100 7400 6F00 6F00 6E00 2000 4900 6E00 6300 6F00 6D00 6900 6E00 6700 2000 4E00 5400 5500 2000 7300 7000 6100 6D00 2100" + val stringRemove = hex"28 4C05 01 00 00" "decode (medkit)" in { PacketCoding.DecodePacket(stringMedkit).require match { @@ -349,6 +350,21 @@ class GamePacketTest extends Specification { } } + "decode (remove)" in { + PacketCoding.DecodePacket(stringRemove).require match { + case CreateShortcutMessage(player_guid, slot, unk1, unk2, purpose, effect1, effect2) => + player_guid mustEqual PlanetSideGUID(1356) + slot mustEqual 1 + unk1 mustEqual 0 + unk2 mustEqual 0 + purpose mustEqual "" + effect1 mustEqual "" + effect2 mustEqual "" + case default => + ko + } + } + "encode (medkit)" in { val msg = CreateShortcutMessage(PlanetSideGUID(4210), 1, 0, 4, "medkit") val pkt = PacketCoding.EncodePacket(msg).require.toByteVector @@ -369,6 +385,13 @@ class GamePacketTest extends Specification { pkt mustEqual stringMacro } + + "encode (remove)" in { + val msg = CreateShortcutMessage(PlanetSideGUID(1356), 1, 0, 0, "") + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual stringRemove + } } "DropItemMessage" should { From 008cb4e919f0b6018260a6a29010b101f296a6bf Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 19 Sep 2016 10:03:43 -0400 Subject: [PATCH 3/5] working version; all checks pass --- .../packet/game/CreateShortcutMessage.scala | 67 +++++++++++-------- common/src/test/scala/GamePacketTest.scala | 41 ++++++------ .../src/main/scala/WorldSessionActor.scala | 2 +- 3 files changed, 62 insertions(+), 48 deletions(-) diff --git a/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala b/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala index f3bcd4da..bdd77ed8 100644 --- a/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala @@ -6,7 +6,7 @@ import scodec.Codec import scodec.codecs._ /** - * Create a quick-use button for the hotbar.
+ * Details regarding this shortcut. *
* Purpose:
* `advanced_regen` (regeneration)
@@ -20,52 +20,65 @@ import scodec.codecs._ * `shortcut_macro`
* `silent_run` (sensor shield)
* `surge`
- * `targeting` - * @param player_guid the player - * @param slot the hotbar slot number (one-indexed) - * @param unk1 na - * @param unk2 na + * `targeting` (enchanced targetting) * @param purpose the primary purpose/use of this shortcut * @param effect1 for macros, a three letter acronym displayed in the hotbar * @param effect2 for macros, the chat message content */ -//TODO must handle case 0x28 xxxx 01 00 00 for moving shortcuts +final case class Shortcut(purpose : String, + effect1 : String = "", + effect2 : String = "") + +/** + * Manipulate a quick-use button for the hotbar. + *
+ * na + * @param player_guid the player + * @param slot the hotbar slot number (one-indexed) + * @param unk1 na + * @param unk2 na + * @param shortcut optional; details about the shortcut to be created + */ final case class CreateShortcutMessage(player_guid : PlanetSideGUID, slot : Int, unk1 : Int, unk2 : Int, - purpose : String = "", - effect1 : String = "", - effect2 : String = "") + shortcut : Option[Shortcut] = None) extends PlanetSideGamePacket { type Packet = CreateShortcutMessage def opcode = GamePacketOpcode.CreateShortcutMessage def encode = CreateShortcutMessage.encode(this) } -//* -object CreateShortcutMessage extends Marshallable[CreateShortcutMessage] { - implicit val codec : Codec[CreateShortcutMessage] = ( - ("player_guid" | PlanetSideGUID.codec) :: - ("slot" | uint8L) :: - ("unk1" | uint8L) :: - ("unk2" | uintL(3)) :: - ("purpose" | PacketHelpers.encodedStringAligned(5)) :: + +object Shortcut extends Marshallable[Shortcut] { + // Needs to be Marshallable[T] for .as[T] to work its magic on the type of the codec Codec[T] + implicit val codec : Codec[Shortcut] = ( + ("purpose" | PacketHelpers.encodedStringAligned(5)) :: ("effect1" | PacketHelpers.encodedWideString) :: ("effect2" | PacketHelpers.encodedWideString) - ).as[CreateShortcutMessage] + ).as[Shortcut] + + // Convenient predefined Shortcuts for the Medkit and Implants + final val AUDIO_AMPLIFIER : Shortcut = Shortcut("audio_amplifier") + final val DARKLIGHT_VISION : Shortcut = Shortcut("darklight_vision") + final val ENHANCED_TARGETING = Shortcut("targeting") + final val MEDKIT : Shortcut = Shortcut("medkit") + final val MELEE_BOOSTER : Shortcut = Shortcut("melee_booster") + final val PERSONAL_SHIELD : Shortcut = Shortcut("personal_shield") + final val RANGE_MAGNIFIER : Shortcut = Shortcut("range_magnifier") + final val REGENERATION : Shortcut = Shortcut("advanced_regen") + final val SECOND_WIND : Shortcut = Shortcut("second_wind") + final val SENSOR_SHIELD : Shortcut = Shortcut("silent_run") + final val SURGE : Shortcut = Shortcut("surge") } -// */ -/* + object CreateShortcutMessage extends Marshallable[CreateShortcutMessage] { implicit val codec : Codec[CreateShortcutMessage] = ( ("player_guid" | PlanetSideGUID.codec) :: ("slot" | uint8L) :: ("unk1" | uint8L) :: - ("unk2" | uintL(3)) >>:~ ( value => - conditional(value.last > 0, "purpose" | PacketHelpers.encodedStringAligned(5)) :: - conditional(value.last > 0, "effect1" | PacketHelpers.encodedWideString) :: - conditional(value.last > 0, "effect2" | PacketHelpers.encodedWideString) - ) + (("unk2" | uintL(3)) >>:~ { value => + conditional(value > 0, "shortcut" | Shortcut.codec).hlist + }) ).as[CreateShortcutMessage] } -// */ diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala index 19a8c326..ab89e878 100644 --- a/common/src/test/scala/GamePacketTest.scala +++ b/common/src/test/scala/GamePacketTest.scala @@ -307,14 +307,15 @@ class GamePacketTest extends Specification { "decode (medkit)" in { PacketCoding.DecodePacket(stringMedkit).require match { - case CreateShortcutMessage(player_guid, slot, unk1, unk2, purpose, effect1, effect2) => + case CreateShortcutMessage(player_guid, slot, unk1, unk2, shortcut) => player_guid mustEqual PlanetSideGUID(4210) slot mustEqual 1 unk1 mustEqual 0 unk2 mustEqual 4 - purpose mustEqual "medkit" - effect1 mustEqual "" - effect2 mustEqual "" + shortcut.isDefined mustEqual true + shortcut.get.purpose mustEqual "medkit" + shortcut.get.effect1 mustEqual "" + shortcut.get.effect2 mustEqual "" case default => ko } @@ -322,14 +323,15 @@ class GamePacketTest extends Specification { "decode (implant)" in { PacketCoding.DecodePacket(stringImplant).require match { - case CreateShortcutMessage(player_guid, slot, unk1, unk2, purpose, effect1, effect2) => + case CreateShortcutMessage(player_guid, slot, unk1, unk2, shortcut) => player_guid mustEqual PlanetSideGUID(4210) slot mustEqual 4 unk1 mustEqual 0 unk2 mustEqual 6 - purpose mustEqual "surge" - effect1 mustEqual "" - effect2 mustEqual "" + shortcut.isDefined mustEqual true + shortcut.get.purpose mustEqual "surge" + shortcut.get.effect1 mustEqual "" + shortcut.get.effect2 mustEqual "" case default => ko } @@ -337,14 +339,15 @@ class GamePacketTest extends Specification { "decode (macro)" in { PacketCoding.DecodePacket(stringMacro).require match { - case CreateShortcutMessage(player_guid, slot, unk1, unk2, purpose, effect1, effect2) => + case CreateShortcutMessage(player_guid, slot, unk1, unk2, shortcut) => player_guid mustEqual PlanetSideGUID(1356) slot mustEqual 8 unk1 mustEqual 0 unk2 mustEqual 5 - purpose mustEqual "shortcut_macro" - effect1 mustEqual "NTU" - effect2 mustEqual "/platoon Incoming NTU spam!" + shortcut.isDefined mustEqual true + shortcut.get.purpose mustEqual "shortcut_macro" + shortcut.get.effect1 mustEqual "NTU" + shortcut.get.effect2 mustEqual "/platoon Incoming NTU spam!" case default => ko } @@ -352,42 +355,40 @@ class GamePacketTest extends Specification { "decode (remove)" in { PacketCoding.DecodePacket(stringRemove).require match { - case CreateShortcutMessage(player_guid, slot, unk1, unk2, purpose, effect1, effect2) => + case CreateShortcutMessage(player_guid, slot, unk1, unk2, shortcut) => player_guid mustEqual PlanetSideGUID(1356) slot mustEqual 1 unk1 mustEqual 0 unk2 mustEqual 0 - purpose mustEqual "" - effect1 mustEqual "" - effect2 mustEqual "" + shortcut.isDefined mustEqual false case default => ko } } "encode (medkit)" in { - val msg = CreateShortcutMessage(PlanetSideGUID(4210), 1, 0, 4, "medkit") + val msg = CreateShortcutMessage(PlanetSideGUID(4210), 1, 0, 4, Some(Shortcut("medkit"))) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual stringMedkit } "encode (implant)" in { - val msg = CreateShortcutMessage(PlanetSideGUID(4210), 4, 0, 6, "surge") + val msg = CreateShortcutMessage(PlanetSideGUID(4210), 4, 0, 6, Some(Shortcut("surge"))) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual stringImplant } "encode (macro)" in { - val msg = CreateShortcutMessage(PlanetSideGUID(1356), 8, 0, 5, "shortcut_macro", "NTU", "/platoon Incoming NTU spam!") + val msg = CreateShortcutMessage(PlanetSideGUID(1356), 8, 0, 5, Some(Shortcut("shortcut_macro", "NTU", "/platoon Incoming NTU spam!"))) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual stringMacro } "encode (remove)" in { - val msg = CreateShortcutMessage(PlanetSideGUID(1356), 1, 0, 0, "") + val msg = CreateShortcutMessage(PlanetSideGUID(1356), 1, 0, 0) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual stringRemove diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 625838e7..4902709d 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -171,7 +171,7 @@ class WorldSessionActor extends Actor with MDCContextAware { true))) //Boosted generator room pain field sendResponse(PacketCoding.CreateGamePacket(0, SetCurrentAvatarMessage(PlanetSideGUID(guid),0,0))) - sendResponse(PacketCoding.CreateGamePacket(0, CreateShortcutMessage(PlanetSideGUID(guid), 1, 0, 6, "medkit"))) + sendResponse(PacketCoding.CreateGamePacket(0, CreateShortcutMessage(PlanetSideGUID(guid), 1, 0, 6, Some(Shortcut.MEDKIT)))) import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global From f1efc8ecc4434b63fbe9daa4dab1c8480ded1a3b Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 19 Sep 2016 18:50:48 -0400 Subject: [PATCH 4/5] finished packet; added presets for known Shortcut patterns; tests --- .../packet/game/CreateShortcutMessage.scala | 103 +++++++++++++----- common/src/test/scala/GamePacketTest.scala | 87 ++++++++------- .../src/main/scala/WorldSessionActor.scala | 2 +- 3 files changed, 128 insertions(+), 64 deletions(-) diff --git a/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala b/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala index bdd77ed8..a5d1ac35 100644 --- a/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala @@ -6,9 +6,21 @@ import scodec.Codec import scodec.codecs._ /** - * Details regarding this shortcut. + * Details regarding this shortcut.
+ *
+ * The parameters `purpose` and `tile` are closely related, as is explained below. + * These two fields are constant for all shortcuts. + * The parameters `effect1` and `effect2` are exclusive to text macro shortcuts and are defaulted to empty `String`s. + * `tile` is related to what kind of graphic is displayed in this shortcut's slot on the hotbar. + * Implants and the medkit have self-explanatory graphics. + * The `shortcut_macro` setting displays a word bubble superimposed by the (first three letters of) `effect1` text.
*
* Purpose:
+ * `0 - Medkit`
+ * `1 - Macro`
+ * `2 - Implant`
+ *
+ * Tile:
* `advanced_regen` (regeneration)
* `audio_amplifier`
* `darklight_vision`
@@ -20,29 +32,41 @@ import scodec.codecs._ * `shortcut_macro`
* `silent_run` (sensor shield)
* `surge`
- * `targeting` (enchanced targetting) + * `targeting` (enhanced targetting)
+ *
+ * Exploration:
+ * Does the shortcut do something different when `purpose` is set to 3? * @param purpose the primary purpose/use of this shortcut + * @param tile the visual element of the shortcut * @param effect1 for macros, a three letter acronym displayed in the hotbar * @param effect2 for macros, the chat message content */ -final case class Shortcut(purpose : String, +final case class Shortcut(purpose : Int, + tile : String, effect1 : String = "", effect2 : String = "") /** - * Manipulate a quick-use button for the hotbar. + * Facilitate a quick-use button for the hotbar.
*
- * na + * The hotbar is the eight quick-use slots along the bottom center of the HUD. + * Each of these slots is a shortcut to the application of medkits from one's inventory, or use of an implant, or repetition of a text macro. + * There are actually sixty-four of these slots, divided into sets of eight, and tentatively bound to the Function keys depending on which set is selected at the time.
+ *
+ * When `addShortcut` is `true`, the provided `shortcut` will be defined and attached to the respective hotbar slot indicated by `slot`. + * If it is `false`, the given slot will be unbound and `shortcut` will be undefined. + * Nothing happens if the `slot` selection is invalid. * @param player_guid the player * @param slot the hotbar slot number (one-indexed) - * @param unk1 na - * @param unk2 na + * @param unk na; always zero? + * @param addShortcut true, if we are adding a shortcut; false, if we are removing any current shortcut * @param shortcut optional; details about the shortcut to be created + * @see ChangeShortcutBankMessage */ final case class CreateShortcutMessage(player_guid : PlanetSideGUID, slot : Int, - unk1 : Int, - unk2 : Int, + unk : Int, + addShortcut : Boolean, shortcut : Option[Shortcut] = None) extends PlanetSideGamePacket { type Packet = CreateShortcutMessage @@ -51,34 +75,63 @@ final case class CreateShortcutMessage(player_guid : PlanetSideGUID, } object Shortcut extends Marshallable[Shortcut] { - // Needs to be Marshallable[T] for .as[T] to work its magic on the type of the codec Codec[T] implicit val codec : Codec[Shortcut] = ( - ("purpose" | PacketHelpers.encodedStringAligned(5)) :: + ("purpose" | uintL(2)) :: + ("tile" | PacketHelpers.encodedStringAligned(5)) :: ("effect1" | PacketHelpers.encodedWideString) :: ("effect2" | PacketHelpers.encodedWideString) ).as[Shortcut] // Convenient predefined Shortcuts for the Medkit and Implants - final val AUDIO_AMPLIFIER : Shortcut = Shortcut("audio_amplifier") - final val DARKLIGHT_VISION : Shortcut = Shortcut("darklight_vision") - final val ENHANCED_TARGETING = Shortcut("targeting") - final val MEDKIT : Shortcut = Shortcut("medkit") - final val MELEE_BOOSTER : Shortcut = Shortcut("melee_booster") - final val PERSONAL_SHIELD : Shortcut = Shortcut("personal_shield") - final val RANGE_MAGNIFIER : Shortcut = Shortcut("range_magnifier") - final val REGENERATION : Shortcut = Shortcut("advanced_regen") - final val SECOND_WIND : Shortcut = Shortcut("second_wind") - final val SENSOR_SHIELD : Shortcut = Shortcut("silent_run") - final val SURGE : Shortcut = Shortcut("surge") + /** + * Preset for the Audio Amplifier implant. */ + final val AUDIO_AMPLIFIER : Some[Shortcut] = Some(Shortcut(2, "audio_amplifier")) + /** + * Preset for the Darklight Vision implant. */ + final val DARKLIGHT_VISION : Some[Shortcut] = Some(Shortcut(2, "darklight_vision")) + /** + * Preset for the Enhanced Targeting implant. */ + final val ENHANCED_TARGETING : Some[Shortcut] = Some(Shortcut(2, "targeting")) + /** + * Preset for the medkit quick-use option. */ + final val MEDKIT : Some[Shortcut] = Some(Shortcut(0, "medkit")) + /** + * Preset for the Melee Booster implant. */ + final val MELEE_BOOSTER : Some[Shortcut] = Some(Shortcut(2, "melee_booster")) + /** + * Preset for the Personal Shield implant. */ + final val PERSONAL_SHIELD : Some[Shortcut] = Some(Shortcut(2, "personal_shield")) + /** + * Preset for the Range Magnifier implant. */ + final val RANGE_MAGNIFIER : Some[Shortcut] = Some(Shortcut(2, "range_magnifier")) + /** + * Preset for the Regeneration implant. */ + final val REGENERATION : Some[Shortcut] = Some(Shortcut(2, "advanced_regen")) + /** + * Preset for the Second Wind implant. */ + final val SECOND_WIND : Some[Shortcut] = Some(Shortcut(2, "second_wind")) + /** + * Preset for the Sensor Shield implant. */ + final val SENSOR_SHIELD : Some[Shortcut] = Some(Shortcut(2, "silent_run")) + /** + * Preset for the Surge implant. */ + final val SURGE : Some[Shortcut] = Some(Shortcut(2, "surge")) + /** + * Converter for text macro parameters that acts like a preset. + * @param effect1 a three letter acronym displayed in the hotbar + * @param effect2 the chat message content + * @return `Some` shortcut that represents a voice macro command + */ + def MACRO(effect1 : String, effect2 : String) : Some[Shortcut] = Some(Shortcut(1, "shortcut_macro", effect1, effect2)) } object CreateShortcutMessage extends Marshallable[CreateShortcutMessage] { implicit val codec : Codec[CreateShortcutMessage] = ( ("player_guid" | PlanetSideGUID.codec) :: ("slot" | uint8L) :: - ("unk1" | uint8L) :: - (("unk2" | uintL(3)) >>:~ { value => - conditional(value > 0, "shortcut" | Shortcut.codec).hlist + ("unk" | uint8L) :: + (("addShortcut" | bool) >>:~ { value => + conditional(value, "shortcut" | Shortcut.codec).hlist }) ).as[CreateShortcutMessage] } diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala index ab89e878..16053c5e 100644 --- a/common/src/test/scala/GamePacketTest.scala +++ b/common/src/test/scala/GamePacketTest.scala @@ -301,35 +301,19 @@ class GamePacketTest extends Specification { "CreateShortcutMessage" should { val stringMedkit = hex"28 7210 01 00 90 C0 6D65646B6974 80 80" - val stringImplant = hex"28 7210 04 00 D0 A0 7375726765 80 80" val stringMacro = hex"28 4C05 08 00 B1 C0 73686F72746375745F6D6163726F 83 4E00 5400 5500 9B 2F00 7000 6C00 6100 7400 6F00 6F00 6E00 2000 4900 6E00 6300 6F00 6D00 6900 6E00 6700 2000 4E00 5400 5500 2000 7300 7000 6100 6D00 2100" val stringRemove = hex"28 4C05 01 00 00" "decode (medkit)" in { PacketCoding.DecodePacket(stringMedkit).require match { - case CreateShortcutMessage(player_guid, slot, unk1, unk2, shortcut) => + case CreateShortcutMessage(player_guid, slot, unk, addShortcut, shortcut) => player_guid mustEqual PlanetSideGUID(4210) slot mustEqual 1 - unk1 mustEqual 0 - unk2 mustEqual 4 + unk mustEqual 0 + addShortcut mustEqual true shortcut.isDefined mustEqual true - shortcut.get.purpose mustEqual "medkit" - shortcut.get.effect1 mustEqual "" - shortcut.get.effect2 mustEqual "" - case default => - ko - } - } - - "decode (implant)" in { - PacketCoding.DecodePacket(stringImplant).require match { - case CreateShortcutMessage(player_guid, slot, unk1, unk2, shortcut) => - player_guid mustEqual PlanetSideGUID(4210) - slot mustEqual 4 - unk1 mustEqual 0 - unk2 mustEqual 6 - shortcut.isDefined mustEqual true - shortcut.get.purpose mustEqual "surge" + shortcut.get.purpose mustEqual 0 + shortcut.get.tile mustEqual "medkit" shortcut.get.effect1 mustEqual "" shortcut.get.effect2 mustEqual "" case default => @@ -339,13 +323,14 @@ class GamePacketTest extends Specification { "decode (macro)" in { PacketCoding.DecodePacket(stringMacro).require match { - case CreateShortcutMessage(player_guid, slot, unk1, unk2, shortcut) => + case CreateShortcutMessage(player_guid, slot, unk, addShortcut, shortcut) => player_guid mustEqual PlanetSideGUID(1356) slot mustEqual 8 - unk1 mustEqual 0 - unk2 mustEqual 5 + unk mustEqual 0 + addShortcut mustEqual true shortcut.isDefined mustEqual true - shortcut.get.purpose mustEqual "shortcut_macro" + shortcut.get.purpose mustEqual 1 + shortcut.get.tile mustEqual "shortcut_macro" shortcut.get.effect1 mustEqual "NTU" shortcut.get.effect2 mustEqual "/platoon Incoming NTU spam!" case default => @@ -355,11 +340,11 @@ class GamePacketTest extends Specification { "decode (remove)" in { PacketCoding.DecodePacket(stringRemove).require match { - case CreateShortcutMessage(player_guid, slot, unk1, unk2, shortcut) => + case CreateShortcutMessage(player_guid, slot, unk, addShortcut, shortcut) => player_guid mustEqual PlanetSideGUID(1356) slot mustEqual 1 - unk1 mustEqual 0 - unk2 mustEqual 0 + unk mustEqual 0 + addShortcut mustEqual false shortcut.isDefined mustEqual false case default => ko @@ -367,32 +352,58 @@ class GamePacketTest extends Specification { } "encode (medkit)" in { - val msg = CreateShortcutMessage(PlanetSideGUID(4210), 1, 0, 4, Some(Shortcut("medkit"))) + val msg = CreateShortcutMessage(PlanetSideGUID(4210), 1, 0, true, Some(Shortcut(0, "medkit"))) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual stringMedkit } - "encode (implant)" in { - val msg = CreateShortcutMessage(PlanetSideGUID(4210), 4, 0, 6, Some(Shortcut("surge"))) - val pkt = PacketCoding.EncodePacket(msg).require.toByteVector - - pkt mustEqual stringImplant - } - "encode (macro)" in { - val msg = CreateShortcutMessage(PlanetSideGUID(1356), 8, 0, 5, Some(Shortcut("shortcut_macro", "NTU", "/platoon Incoming NTU spam!"))) + val msg = CreateShortcutMessage(PlanetSideGUID(1356), 8, 0, true, Some(Shortcut(1, "shortcut_macro", "NTU", "/platoon Incoming NTU spam!"))) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual stringMacro } "encode (remove)" in { - val msg = CreateShortcutMessage(PlanetSideGUID(1356), 1, 0, 0) + val msg = CreateShortcutMessage(PlanetSideGUID(1356), 1, 0, false) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual stringRemove } + + "macro" in { + val MACRO : Some[Shortcut] = Shortcut.MACRO("NTU", "/platoon Incoming NTU spam!") + MACRO.get.purpose mustEqual 1 + MACRO.get.tile mustEqual "shortcut_macro" + MACRO.get.effect1 mustEqual "NTU" + MACRO.get.effect2 mustEqual "/platoon Incoming NTU spam!" + } + + "presets" in { + Shortcut.AUDIO_AMPLIFIER.get.purpose mustEqual 2 + Shortcut.AUDIO_AMPLIFIER.get.tile mustEqual "audio_amplifier" + Shortcut.DARKLIGHT_VISION.get.purpose mustEqual 2 + Shortcut.DARKLIGHT_VISION.get.tile mustEqual "darklight_vision" + Shortcut.ENHANCED_TARGETING.get.purpose mustEqual 2 + Shortcut.ENHANCED_TARGETING.get.tile mustEqual "targeting" + Shortcut.MEDKIT.get.purpose mustEqual 0 + Shortcut.MEDKIT.get.tile mustEqual "medkit" + Shortcut.MELEE_BOOSTER.get.purpose mustEqual 2 + Shortcut.MELEE_BOOSTER.get.tile mustEqual "melee_booster" + Shortcut.PERSONAL_SHIELD.get.purpose mustEqual 2 + Shortcut.PERSONAL_SHIELD.get.tile mustEqual "personal_shield" + Shortcut.RANGE_MAGNIFIER.get.purpose mustEqual 2 + Shortcut.RANGE_MAGNIFIER.get.tile mustEqual "range_magnifier" + Shortcut.REGENERATION.get.purpose mustEqual 2 + Shortcut.REGENERATION.get.tile mustEqual "advanced_regen" + Shortcut.SECOND_WIND.get.purpose mustEqual 2 + Shortcut.SECOND_WIND.get.tile mustEqual "second_wind" + Shortcut.SENSOR_SHIELD.get.purpose mustEqual 2 + Shortcut.SENSOR_SHIELD.get.tile mustEqual "silent_run" + Shortcut.SURGE.get.purpose mustEqual 2 + Shortcut.SURGE.get.tile mustEqual "surge" + } } "DropItemMessage" should { diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 4902709d..e1bfb323 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -171,7 +171,7 @@ class WorldSessionActor extends Actor with MDCContextAware { true))) //Boosted generator room pain field sendResponse(PacketCoding.CreateGamePacket(0, SetCurrentAvatarMessage(PlanetSideGUID(guid),0,0))) - sendResponse(PacketCoding.CreateGamePacket(0, CreateShortcutMessage(PlanetSideGUID(guid), 1, 0, 6, Some(Shortcut.MEDKIT)))) + sendResponse(PacketCoding.CreateGamePacket(0, CreateShortcutMessage(PlanetSideGUID(guid), 1, 0, true, Shortcut.MEDKIT))) import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global From b30a30b95721ad4d60176d79e2dc35bf8c43aa16 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 10 Jan 2017 07:46:09 -0500 Subject: [PATCH 5/5] minor adjustments to shortcut packet before merge --- .../packet/game/CreateShortcutMessage.scala | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala b/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala index a5d1ac35..cfed1492 100644 --- a/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala @@ -8,12 +8,14 @@ import scodec.codecs._ /** * Details regarding this shortcut.
*
- * The parameters `purpose` and `tile` are closely related, as is explained below. - * These two fields are constant for all shortcuts. - * The parameters `effect1` and `effect2` are exclusive to text macro shortcuts and are defaulted to empty `String`s. - * `tile` is related to what kind of graphic is displayed in this shortcut's slot on the hotbar. - * Implants and the medkit have self-explanatory graphics. + * The parameters `purpose` and `tile` are closely related. + * These two fields are consistent for all shortcuts of the same type. + * `purpose` indicates the purpose of the shortcut. + * `tile` is related to what kind of graphic is displayed in this shortcut's slot on the hotbar based on its purpose. + * The parameters `effect1` and `effect2` are exclusive to text macro shortcuts and are defaulted to empty `String`s.
+ *
* The `shortcut_macro` setting displays a word bubble superimposed by the (first three letters of) `effect1` text.
+ * Implants and the medkit should have self-explanatory graphics. *
* Purpose:
* `0 - Medkit`
@@ -35,8 +37,8 @@ import scodec.codecs._ * `targeting` (enhanced targetting)
*
* Exploration:
- * Does the shortcut do something different when `purpose` is set to 3? - * @param purpose the primary purpose/use of this shortcut + * What is `purpose` when 3? + * @param purpose the primary use of this shortcut * @param tile the visual element of the shortcut * @param effect1 for macros, a three letter acronym displayed in the hotbar * @param effect2 for macros, the chat message content @@ -50,11 +52,11 @@ final case class Shortcut(purpose : Int, * Facilitate a quick-use button for the hotbar.
*
* The hotbar is the eight quick-use slots along the bottom center of the HUD. - * Each of these slots is a shortcut to the application of medkits from one's inventory, or use of an implant, or repetition of a text macro. - * There are actually sixty-four of these slots, divided into sets of eight, and tentatively bound to the Function keys depending on which set is selected at the time.
+ * Each of these slots is the application of a medkit, or use of an implant, or repetition of a text macro. + * There are actually sixty-four of these slots, eight bound to the Function keys depending on which set is selected.
*
- * When `addShortcut` is `true`, the provided `shortcut` will be defined and attached to the respective hotbar slot indicated by `slot`. - * If it is `false`, the given slot will be unbound and `shortcut` will be undefined. + * When `addShortcut` is `true`, the provided `Shortcut` will be defined and attached to the respective hotbar slot indicated by `slot`. + * If it is `false`, the given `slot` will be unbound. * Nothing happens if the `slot` selection is invalid. * @param player_guid the player * @param slot the hotbar slot number (one-indexed) @@ -75,13 +77,6 @@ final case class CreateShortcutMessage(player_guid : PlanetSideGUID, } object Shortcut extends Marshallable[Shortcut] { - implicit val codec : Codec[Shortcut] = ( - ("purpose" | uintL(2)) :: - ("tile" | PacketHelpers.encodedStringAligned(5)) :: - ("effect1" | PacketHelpers.encodedWideString) :: - ("effect2" | PacketHelpers.encodedWideString) - ).as[Shortcut] - // Convenient predefined Shortcuts for the Medkit and Implants /** * Preset for the Audio Amplifier implant. */ @@ -123,6 +118,13 @@ object Shortcut extends Marshallable[Shortcut] { * @return `Some` shortcut that represents a voice macro command */ def MACRO(effect1 : String, effect2 : String) : Some[Shortcut] = Some(Shortcut(1, "shortcut_macro", effect1, effect2)) + + implicit val codec : Codec[Shortcut] = ( + ("purpose" | uint2L) :: + ("tile" | PacketHelpers.encodedStringAligned(5)) :: + ("effect1" | PacketHelpers.encodedWideString) :: + ("effect2" | PacketHelpers.encodedWideString) + ).as[Shortcut] } object CreateShortcutMessage extends Marshallable[CreateShortcutMessage] {