Repository: pajlads/DinkPlugin
Pull request #178 (2023-02-24 00:59:10)

feat(quest): include completions and points fields
Same idea as #174 but for quest notifier

![image](https://user-images.githubusercontent.com/8106344/221103284-74427c66-352c-4143-93da-e7ef596f2b00.png)


iProdigy: Is there any concern for people misinterpreting `%POINTS%`/`questPoints` as the incremental points earned from the given quest?

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6a5d59b..c2280bd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
 ## Unreleased
 
+- Minor: Include number of quest completions and quest points in quest notifications. (#178)
 - Minor: Collapse all notifier configuration sections by default. (#176)
 - Minor: Include number of completed entries in collection log notification. (#174)
 
diff --git a/README.md b/README.md
index 00d4148..bfb174f 100644
--- a/README.md
+++ b/README.md
@@ -283,7 +283,11 @@ The examples below omit `embeds` and `playerName` keys because they are always t
 {
   "content": "%USERNAME% has completed a quest: %QUEST%",
   "extra": {
-    "questName": "Recipe for Disaster"
+    "questName": "Dragon Slayer I",
+    "completedQuests": 22,
+    "totalQuests": 156,
+    "questPoints": 44,
+    "totalQuestPoints": 293
   },
   "type": "QUEST"
 }
diff --git a/src/main/java/dinkplugin/message/Field.java b/src/main/java/dinkplugin/message/Field.java
index 3b572fa..85b3cca 100644
--- a/src/main/java/dinkplugin/message/Field.java
+++ b/src/main/java/dinkplugin/message/Field.java
@@ -27,4 +27,10 @@ public class Field {
     public static String formatBlock(String codeBlockLanguage, String content) {
         return String.format("```%s\n%s\n```", StringUtils.defaultString(codeBlockLanguage), content);
     }
+
+    public static String formatProgress(int completed, int total) {
+        assert total != 0;
+        double percent = 100.0 * completed / total;
+        return Field.formatBlock("", String.format("%d/%d (%.1f%%)", completed, total, percent));
+    }
 }
diff --git a/src/main/java/dinkplugin/notifiers/CollectionNotifier.java b/src/main/java/dinkplugin/notifiers/CollectionNotifier.java
index 8c36f1a..466fa46 100644
--- a/src/main/java/dinkplugin/notifiers/CollectionNotifier.java
+++ b/src/main/java/dinkplugin/notifiers/CollectionNotifier.java
@@ -24,8 +24,11 @@ public class CollectionNotifier extends BaseNotifier {
     static final Pattern COLLECTION_LOG_REGEX = Pattern.compile("New item added to your collection log: (?<itemName>(.*))");
     public static final String ADDITION_WARNING = "Collection notifier will not fire unless you enable the game setting: Collection log - New addition notification";
 
+    /*
+     * https://github.com/Joshua-F/cs2-scripts/blob/master/scripts/%5Bclientscript,collection_init_frame%5D.cs2#L3
+     */
     @VisibleForTesting
-    static final int COMPLETED_VARP = 2943, TOTAL_VARP = 2944; // https://github.com/Joshua-F/cs2-scripts/blob/master/scripts/[clientscript,collection_init_frame].cs2#L3
+    static final int COMPLETED_VARP = 2943, TOTAL_VARP = 2944;
 
     /**
      * The number of completed entries in the collection log, as implied by {@link #COMPLETED_VARP}.
diff --git a/src/main/java/dinkplugin/notifiers/QuestNotifier.java b/src/main/java/dinkplugin/notifiers/QuestNotifier.java
index 7d82483..fc4ed69 100644
--- a/src/main/java/dinkplugin/notifiers/QuestNotifier.java
+++ b/src/main/java/dinkplugin/notifiers/QuestNotifier.java
@@ -5,15 +5,38 @@ import dinkplugin.message.NotificationType;
 import dinkplugin.util.QuestUtils;
 import dinkplugin.util.Utils;
 import dinkplugin.notifiers.data.QuestNotificationData;
+import net.runelite.api.VarPlayer;
+import net.runelite.api.annotations.Varbit;
 import net.runelite.api.events.WidgetLoaded;
 import net.runelite.api.widgets.Widget;
 import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.client.callback.ClientThread;
 import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.VisibleForTesting;
+
+import javax.inject.Inject;
 
 import static net.runelite.api.widgets.WidgetID.QUEST_COMPLETED_GROUP_ID;
 
 public class QuestNotifier extends BaseNotifier {
 
+    /*
+     * https://github.com/Joshua-F/cs2-scripts/blob/master/scripts/%5Bproc,questlist_completed%5D.cs2#L5
+     */
+    @Varbit
+    @VisibleForTesting
+    static final int COMPLETED_ID = 6347, TOTAL_ID = 11877;
+
+    /*
+     * https://github.com/Joshua-F/cs2-scripts/blob/master/scripts/%5Bproc,questlist_qp%5D.cs2#L5
+     */
+    @Varbit
+    @VisibleForTesting
+    static final int QP_TOTAL_ID = 1782;
+
+    @Inject
+    private ClientThread clientThread;
+
     @Override
     public boolean isEnabled() {
         return config.notifyQuest() && super.isEnabled();
@@ -28,12 +51,22 @@ public class QuestNotifier extends BaseNotifier {
         if (event.getGroupId() == QUEST_COMPLETED_GROUP_ID && isEnabled()) {
             Widget quest = client.getWidget(WidgetInfo.QUEST_COMPLETED_NAME_TEXT);
             if (quest != null) {
-                this.handleNotify(quest.getText());
+                String questText = quest.getText();
+                // 1 tick delay to ensure relevant varbits have been processed by the client
+                clientThread.invokeLater(() -> handleNotify(questText));
             }
         }
     }
 
     private void handleNotify(String questText) {
+        int completedQuests = client.getVarbitValue(COMPLETED_ID);
+        int totalQuests = client.getVarbitValue(TOTAL_ID);
+        boolean validQuests = completedQuests > 0 && totalQuests > 0;
+
+        int questPoints = client.getVarpValue(VarPlayer.QUEST_POINTS);
+        int totalQuestPoints = client.getVarbitValue(QP_TOTAL_ID);
+        boolean validPoints = questPoints > 0 && totalQuestPoints > 0;
+
         String parsed = QuestUtils.parseQuestWidget(questText);
         String notifyMessage = StringUtils.replaceEach(
             config.questNotifyMessage(),
@@ -41,9 +74,17 @@ public class QuestNotifier extends BaseNotifier {
             new String[] { Utils.getPlayerName(client), parsed }
         );
 
+        QuestNotificationData extra = new QuestNotificationData(
+            parsed,
+            validQuests ? completedQuests : null,
+            validQuests ? totalQuests : null,
+            validPoints ? questPoints : null,
+            validPoints ? totalQuestPoints : null
+        );
+
         createMessage(config.questSendImage(), NotificationBody.builder()
             .text(notifyMessage)
-            .extra(new QuestNotificationData(parsed))
+            .extra(extra)
             .type(NotificationType.QUEST)
             .build());
     }
diff --git a/src/main/java/dinkplugin/notifiers/data/CollectionNotificationData.java b/src/main/java/dinkplugin/notifiers/data/CollectionNotificationData.java
index 9817527..90a225b 100644
--- a/src/main/java/dinkplugin/notifiers/data/CollectionNotificationData.java
+++ b/src/main/java/dinkplugin/notifiers/data/CollectionNotificationData.java
@@ -30,15 +30,11 @@ public class CollectionNotificationData extends NotificationData {
 
     @Override
     public List<Field> getFields() {
-        if (completedEntries != null && totalEntries != null) {
-            double percent = 100.0 * completedEntries / totalEntries;
-            return Collections.singletonList(
-                new Field(
-                    "Completed Entries",
-                    Field.formatBlock("", String.format("%d/%d (%.1f%%)", completedEntries, totalEntries, percent)))
-            );
-        }
-
-        return super.getFields();
+        if (completedEntries == null || totalEntries == null)
+            return super.getFields();
+
+        return Collections.singletonList(
+            new Field("Completed Entries", Field.formatProgress(completedEntries, totalEntries))
+        );
     }
 }
diff --git a/src/main/java/dinkplugin/notifiers/data/QuestNotificationData.java b/src/main/java/dinkplugin/notifiers/data/QuestNotificationData.java
index 645407d..844fc5c 100644
--- a/src/main/java/dinkplugin/notifiers/data/QuestNotificationData.java
+++ b/src/main/java/dinkplugin/notifiers/data/QuestNotificationData.java
@@ -1,10 +1,43 @@
 package dinkplugin.notifiers.data;
 
-import lombok.Data;
+import dinkplugin.message.Field;
 import lombok.EqualsAndHashCode;
+import lombok.Value;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
-@Data
+import java.util.ArrayList;
+import java.util.List;
+
+@Value
 @EqualsAndHashCode(callSuper = false)
 public class QuestNotificationData extends NotificationData {
-    final String questName;
+
+    @NotNull
+    String questName;
+
+    @Nullable
+    Integer completedQuests;
+
+    @Nullable
+    Integer totalQuests;
+
+    @Nullable
+    Integer questPoints;
+
+    @Nullable
+    Integer totalQuestPoints;
+
+    @Override
+    public List<Field> getFields() {
+        List<Field> fields = new ArrayList<>(2);
+
+        if (completedQuests != null && totalQuests != null)
+            fields.add(new Field("Completed Quests", Field.formatProgress(completedQuests, totalQuests)));
+
+        if (questPoints != null && totalQuestPoints != null)
+            fields.add(new Field("Quest Points", Field.formatProgress(questPoints, totalQuestPoints)));
+
+        return fields;
+    }
 }
diff --git a/src/main/java/dinkplugin/notifiers/data/SpeedrunNotificationData.java b/src/main/java/dinkplugin/notifiers/data/SpeedrunNotificationData.java
index 277ddfe..2091ee8 100644
--- a/src/main/java/dinkplugin/notifiers/data/SpeedrunNotificationData.java
+++ b/src/main/java/dinkplugin/notifiers/data/SpeedrunNotificationData.java
@@ -1,19 +1,12 @@
 package dinkplugin.notifiers.data;
 
 import lombok.EqualsAndHashCode;
-import lombok.ToString;
 import lombok.Value;
 
 @Value
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class SpeedrunNotificationData extends QuestNotificationData {
+@EqualsAndHashCode(callSuper = false)
+public class SpeedrunNotificationData extends NotificationData {
+    String questName;
     String personalBest;
     String currentTime;
-
-    public SpeedrunNotificationData(String questName, String personalBest, String currentTime) {
-        super(questName);
-        this.personalBest = personalBest;
-        this.currentTime = currentTime;
-    }
 }


Test patch:
diff --git a/src/test/java/dinkplugin/notifiers/QuestNotifierTest.java b/src/test/java/dinkplugin/notifiers/QuestNotifierTest.java
index e537a6d..22c0fef 100644
--- a/src/test/java/dinkplugin/notifiers/QuestNotifierTest.java
+++ b/src/test/java/dinkplugin/notifiers/QuestNotifierTest.java
@@ -4,6 +4,7 @@ import com.google.inject.testing.fieldbinder.Bind;
 import dinkplugin.message.NotificationBody;
 import dinkplugin.message.NotificationType;
 import dinkplugin.notifiers.data.QuestNotificationData;
+import net.runelite.api.VarPlayer;
 import net.runelite.api.events.WidgetLoaded;
 import net.runelite.api.widgets.Widget;
 import org.junit.jupiter.api.BeforeEach;
@@ -34,14 +35,21 @@ class QuestNotifierTest extends MockedNotifierTest {
         when(config.notifyQuest()).thenReturn(true);
         when(config.questSendImage()).thenReturn(false);
         when(config.questNotifyMessage()).thenReturn("%USERNAME% has completed: %QUEST%");
+
     }
 
     @Test
     void testNotify() {
+        // init client mocks
+        when(client.getVarbitValue(QuestNotifier.COMPLETED_ID)).thenReturn(22);
+        when(client.getVarbitValue(QuestNotifier.TOTAL_ID)).thenReturn(156);
+        when(client.getVarpValue(VarPlayer.QUEST_POINTS)).thenReturn(44);
+        when(client.getVarbitValue(QuestNotifier.QP_TOTAL_ID)).thenReturn(293);
+
         // mock widget
         Widget questWidget = mock(Widget.class);
         when(client.getWidget(QUEST_COMPLETED_NAME_TEXT)).thenReturn(questWidget);
-        when(questWidget.getText()).thenReturn("You have completed the Dragon Slayer quest!");
+        when(questWidget.getText()).thenReturn("You have completed the Dragon Slayer I quest!");
 
         // send event
         plugin.onWidgetLoaded(event(QUEST_COMPLETED_GROUP_ID));
@@ -51,8 +59,8 @@ class QuestNotifierTest extends MockedNotifierTest {
             PRIMARY_WEBHOOK_URL,
             false,
             NotificationBody.builder()
-                .text(PLAYER_NAME + " has completed: Dragon Slayer")
-                .extra(new QuestNotificationData("Dragon Slayer"))
+                .text(PLAYER_NAME + " has completed: Dragon Slayer I")
+                .extra(new QuestNotificationData("Dragon Slayer I", 22, 156, 44, 293))
                 .type(NotificationType.QUEST)
                 .build()
         );