Repository: pajlads/DinkPlugin (java)

Issue:
Pet: No XP milestone when getting skilling pet
**Dink version:** 1.3.1

Just got my first skilling pet and yet the plugin didn't posted the milestone (at xxx XP). The milestone did get posted in the GIM chat.
![No milestone skilling pet](https://user-images.githubusercontent.com/11279045/226682020-2cf99976-8bab-4530-8b0b-804db8e71955.png)

![GIM chat message](https://user-images.githubusercontent.com/11279045/226680744-15050d0d-8ab9-4f98-9528-05b512d35f80.png)
From the screenshot the plugin didn't waited for the GIM message.

[Code snippet was here about client log but that was from a different plugin]


Pull request description:
fix: delay pet notifications for milestone
Fixes #192


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3103f24..078ad6e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
 - 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, #181)
+- Bugfix: Read pet acquisition milestone from delayed clan messages. (#193)
 - Bugfix: Fire slayer notifications beyond 999 points. (#182)
 - Dev: Improve thread-safety of slayer notifier. (#184)
 - Dev: Update Gradle to 8.0.2 patch version. (#185)
diff --git a/src/main/java/dinkplugin/notifiers/PetNotifier.java b/src/main/java/dinkplugin/notifiers/PetNotifier.java
index d29055c..75f3344 100644
--- a/src/main/java/dinkplugin/notifiers/PetNotifier.java
+++ b/src/main/java/dinkplugin/notifiers/PetNotifier.java
@@ -17,6 +17,7 @@ import javax.inject.Inject;
 import javax.inject.Singleton;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -43,6 +44,23 @@ public class PetNotifier extends BaseNotifier {
     private static final Set<String> PET_NAMES;
     private static final String PRIMED_NAME = "";
 
+    /**
+     * The maximum number ticks to wait for {@link #milestone} to be populated,
+     * before firing notification with only the {@link #petName}.
+     *
+     * @see #ticksWaited
+     */
+    @VisibleForTesting
+    static final int MAX_TICKS_WAIT = 5;
+
+    /**
+     * Tracks the number of ticks that occur where {@link #milestone} is not populated
+     * while {@link #petName} <i>is</i> populated.
+     *
+     * @see #onTick()
+     */
+    private final AtomicInteger ticksWaited = new AtomicInteger();
+
     @Inject
     private ItemSearcher itemSearcher;
 
@@ -95,13 +113,17 @@ public class PetNotifier extends BaseNotifier {
     }
 
     public void onTick() {
-        if (petName != null)
+        if (petName == null)
+            return;
+
+        if (milestone != null || ticksWaited.incrementAndGet() > MAX_TICKS_WAIT)
             this.handleNotify();
     }
 
     public void reset() {
         this.petName = null;
         this.milestone = null;
+        this.ticksWaited.set(0);
     }
 
     private void handleNotify() {


Test patch:
diff --git a/src/test/java/dinkplugin/notifiers/PetNotifierTest.java b/src/test/java/dinkplugin/notifiers/PetNotifierTest.java
index c417be7..004e7eb 100644
--- a/src/test/java/dinkplugin/notifiers/PetNotifierTest.java
+++ b/src/test/java/dinkplugin/notifiers/PetNotifierTest.java
@@ -12,6 +12,9 @@ import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 
+import java.util.stream.IntStream;
+
+import static dinkplugin.notifiers.PetNotifier.MAX_TICKS_WAIT;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.never;
@@ -43,7 +46,7 @@ class PetNotifierTest extends MockedNotifierTest {
     void testNotify() {
         // send fake message
         notifier.onChatMessage("You feel something weird sneaking into your backpack.");
-        notifier.onTick();
+        IntStream.rangeClosed(0, MAX_TICKS_WAIT).forEach(i -> notifier.onTick());
 
         // verify handled
         verify(messageHandler).createMessage(
@@ -68,7 +71,7 @@ class PetNotifierTest extends MockedNotifierTest {
         // send fake message
         notifier.onChatMessage("You have a funny feeling like you're being followed.");
         notifier.onChatMessage("New item added to your collection log: " + petName);
-        notifier.onTick();
+        IntStream.rangeClosed(0, MAX_TICKS_WAIT).forEach(i -> notifier.onTick());
 
         // verify handled
         verify(messageHandler).createMessage(
@@ -94,7 +97,7 @@ class PetNotifierTest extends MockedNotifierTest {
         // send fake message
         notifier.onChatMessage("You have a funny feeling like you're being followed.");
         notifier.onChatMessage("Untradeable drop: " + petName);
-        notifier.onTick();
+        IntStream.rangeClosed(0, MAX_TICKS_WAIT).forEach(i -> notifier.onTick());
 
         // verify handled
         verify(messageHandler).createMessage(
@@ -117,7 +120,7 @@ class PetNotifierTest extends MockedNotifierTest {
         // send fake message
         notifier.onChatMessage("You have a funny feeling like you're being followed.");
         notifier.onChatMessage("Untradeable drop: " + petName);
-        notifier.onTick();
+        IntStream.rangeClosed(0, MAX_TICKS_WAIT).forEach(i -> notifier.onTick());
 
         // verify handled
         verify(messageHandler).createMessage(
@@ -143,6 +146,12 @@ class PetNotifierTest extends MockedNotifierTest {
         // send fake message
         notifier.onChatMessage("You have a funny feeling like you're being followed.");
         notifier.onChatMessage("Untradeable drop: " + petName);
+        notifier.onTick();
+
+        notifier.onChatMessage("random unrelated chat message");
+        notifier.onClanNotification("random unrelated clan message");
+        notifier.onTick();
+
         notifier.onClanNotification(
             String.format(
                 "[ClanName] %s has a funny feeling like he would have been followed: %s at 50 killcount.",
@@ -175,6 +184,12 @@ class PetNotifierTest extends MockedNotifierTest {
 
         // send fake message
         notifier.onChatMessage("You have a funny feeling like you're being followed.");
+        notifier.onTick();
+
+        notifier.onChatMessage("random unrelated chat message");
+        notifier.onClanNotification("random unrelated clan message");
+        notifier.onTick();
+
         notifier.onClanNotification(
             String.format(
                 "[ClanName] %s has a funny feeling like he would have been followed: %s at 50 killcount.",
@@ -204,7 +219,7 @@ class PetNotifierTest extends MockedNotifierTest {
 
         // send fake message
         notifier.onChatMessage("You feel something weird sneaking into your backpack.");
-        notifier.onTick();
+        IntStream.rangeClosed(0, MAX_TICKS_WAIT).forEach(i -> notifier.onTick());
 
         // verify handled at override url
         verify(messageHandler).createMessage(
@@ -222,7 +237,7 @@ class PetNotifierTest extends MockedNotifierTest {
     void testIgnore() {
         // send non-pet message
         notifier.onChatMessage("You feel Forsen's warmth behind you.");
-        notifier.onTick();
+        IntStream.rangeClosed(0, MAX_TICKS_WAIT).forEach(i -> notifier.onTick());
 
         // ensure no notification occurred
         verify(messageHandler, never()).createMessage(any(), anyBoolean(), any());
@@ -235,7 +250,7 @@ class PetNotifierTest extends MockedNotifierTest {
 
         // send fake message
         notifier.onChatMessage("You feel something weird sneaking into your backpack.");
-        notifier.onTick();
+        IntStream.rangeClosed(0, MAX_TICKS_WAIT).forEach(i -> notifier.onTick());
 
         // ensure no notification occurred
         verify(messageHandler, never()).createMessage(any(), anyBoolean(), any());
@@ -249,7 +264,7 @@ class PetNotifierTest extends MockedNotifierTest {
 
         // send fake message
         notifier.onChatMessage("You feel something weird sneaking into your backpack.");
-        notifier.onTick();
+        IntStream.rangeClosed(0, MAX_TICKS_WAIT).forEach(i -> notifier.onTick());
 
         // verify handled
         verify(messageHandler).createMessage(
@@ -271,7 +286,7 @@ class PetNotifierTest extends MockedNotifierTest {
 
         // send fake message
         notifier.onChatMessage("You feel something weird sneaking into your backpack.");
-        notifier.onTick();
+        IntStream.rangeClosed(0, MAX_TICKS_WAIT).forEach(i -> notifier.onTick());
 
         // ensure no notification occurred
         verify(messageHandler, never()).createMessage(any(), anyBoolean(), any());