Repository: pajlads/DinkPlugin
Pull request #164 (2023-02-08 02:26:40)

fix: notify when jumping over level that fits interval
Notify on level up if jumping over a level that would've warranted a notification.

For example: if interval is set to 5 & player jumps from level 4 to 6 in a skill, a notification will now be fired.

Note: notification will include the *latest* skill level (not the potentially outdated level that matches the interval)



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1b2e7b7..2bc796b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
 ## Unreleased
 
+- Bugfix: Fire skill notification when jumping over a level that matches the configured interval. (#164)
+
 ## 1.3.0
 
 - Major: Add Barbarian Assault high level gambling notifications. (#150)
diff --git a/src/main/java/dinkplugin/notifiers/LevelNotifier.java b/src/main/java/dinkplugin/notifiers/LevelNotifier.java
index 6168032..0f009c3 100644
--- a/src/main/java/dinkplugin/notifiers/LevelNotifier.java
+++ b/src/main/java/dinkplugin/notifiers/LevelNotifier.java
@@ -37,6 +37,7 @@ public class LevelNotifier extends BaseNotifier {
         if (client.getGameState() != GameState.LOGGED_IN) return;
         for (Skill skill : Skill.values()) {
             if (skill != Skill.OVERALL) {
+                // uses log(n) operation to support virtual levels
                 currentLevels.put(skill.getName(), Experience.getLevelForXp(client.getSkillExperience(skill)));
             }
         }
@@ -81,11 +82,20 @@ public class LevelNotifier extends BaseNotifier {
 
         int virtualLevel = level < 99 ? level : Experience.getLevelForXp(xp); // avoid log(n) query when not needed
         Integer previousLevel = currentLevels.put(skill, virtualLevel);
-        if (config.notifyLevel() && checkLevelInterval(virtualLevel) && previousLevel != null) {
-            if (virtualLevel > previousLevel) {
-                levelledSkills.add(skill);
-                sendMessage = true;
-            }
+
+        if (!config.notifyLevel() || previousLevel == null || virtualLevel == previousLevel) {
+            return;
+        }
+
+        if (virtualLevel < previousLevel) {
+            // base skill level should never regress; reset notifier state
+            reset();
+            return;
+        }
+
+        if (checkLevelInterval(previousLevel, virtualLevel)) {
+            levelledSkills.add(skill);
+            sendMessage = true;
         }
     }
 
@@ -126,15 +136,21 @@ public class LevelNotifier extends BaseNotifier {
             .build());
     }
 
-    private boolean checkLevelInterval(int level) {
+    private boolean checkLevelInterval(int previous, int level) {
         if (level < config.levelMinValue())
             return false;
+
         if (level > 99 && !config.levelNotifyVirtual())
             return false;
+
         int interval = config.levelInterval();
-        return interval <= 1
-            || level == 99
-            || level % interval == 0;
+        if (interval <= 1 || level == 99)
+            return true;
+
+        // Check levels in (previous, current] for divisibility by interval
+        // Allows for firing notification if jumping over a level that would've notified
+        int remainder = level % interval;
+        return remainder == 0 || (level - remainder) > previous;
     }
 
     private static String getSkillIcon(String skillName) {


Test patch:
diff --git a/src/test/java/dinkplugin/notifiers/LevelNotifierTest.java b/src/test/java/dinkplugin/notifiers/LevelNotifierTest.java
index 5f7a453..b499f07 100644
--- a/src/test/java/dinkplugin/notifiers/LevelNotifierTest.java
+++ b/src/test/java/dinkplugin/notifiers/LevelNotifierTest.java
@@ -63,6 +63,26 @@ class LevelNotifierTest extends MockedNotifierTest {
         );
     }
 
+    @Test
+    void testNotifyJump() {
+        // fire skill event (4 => 6, skipping 5 while 5 is level interval)
+        plugin.onStatChanged(new StatChanged(Skill.HUNTER, 200, 6, 6));
+
+        // let ticks pass
+        IntStream.range(0, 4).forEach(i -> notifier.onTick());
+
+        // verify handled
+        verify(messageHandler).createMessage(
+            PRIMARY_WEBHOOK_URL,
+            false,
+            NotificationBody.builder()
+                .text(PLAYER_NAME + " has levelled Hunter to 6")
+                .extra(new LevelNotificationData(ImmutableMap.of("Hunter", 6), ImmutableMap.of("Agility", 1, "Attack", 99, "Hunter", 6)))
+                .type(NotificationType.LEVEL)
+                .build()
+        );
+    }
+
     @Test
     void testNotifyVirtual() {
         // fire skill event