From 45c5df3379767146f91e1e4a1f48d9692599a1d4 Mon Sep 17 00:00:00 2001 From: Jordan Atwood Date: Sun, 2 Feb 2020 22:24:21 -0800 Subject: [PATCH] timers plugin: Improve imbued heart detection As is the case with other graphics-based timers, the imbued heart timer will not fire if enough other graphics animations are triggered (such as those created when fighting the Dagannoth Kings). To add this timer more reliably, this commit will add the timer when a Magic stat boost occurs which is large enough that it can only be triggered by the heart. Because the heart's boost scales with the player's level, it gives an equal boost to the Magic essence and Magic potion boosts, depending on the base magic level. To account for this, it will only be applied if the user's base magic level is high enough to assuredly identify that a potion was not used to trigger the boost. Fixes runelite/runelite#3516 Co-authored-by: Lucas --- .../client/plugins/timers/TimersPlugin.java | 24 ++++++++++ .../plugins/timers/TimersPluginTest.java | 48 ++++++++++++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java index 3b6f84823e..0b2151ab16 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java @@ -25,6 +25,7 @@ */ package net.runelite.client.plugins.timers; +import com.google.common.annotations.VisibleForTesting; import com.google.inject.Provides; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -43,10 +44,12 @@ import net.runelite.api.ItemID; import net.runelite.api.NPC; import net.runelite.api.NpcID; import net.runelite.api.Player; +import net.runelite.api.Skill; import net.runelite.api.Varbits; import net.runelite.api.WorldType; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.AnimationChanged; +import net.runelite.api.events.StatChanged; import net.runelite.api.events.ChatMessage; import net.runelite.client.events.ConfigChanged; import net.runelite.api.events.GameStateChanged; @@ -112,6 +115,9 @@ public class TimersPlugin extends Plugin private static final Pattern HALF_TELEBLOCK_PATTERN = Pattern.compile("A Tele Block spell has been cast on you by (.+)\\. It will expire in 2 minutes, 30 seconds\\."); private static final Pattern DIVINE_POTION_PATTERN = Pattern.compile("You drink some of your divine (.+) potion\\."); + @VisibleForTesting + static final int IMBUED_HEART_MIN_CERTAIN_BOOST_LEVEL = 40; // Before this level, other effects can grant boosts of equal amounts + private TimerTimer freezeTimer; private int freezeTime = -1; // time frozen, in game ticks @@ -892,6 +898,24 @@ public class TimersPlugin extends Plugin } } + @Subscribe + public void onStatChanged(StatChanged statChanged) + { + if (statChanged.getSkill() != Skill.MAGIC || !config.showImbuedHeart()) + { + return; + } + + final int magicLevel = statChanged.getLevel(); + final int boostAmount = statChanged.getBoostedLevel() - magicLevel; + final int heartBoost = 1 + (magicLevel / 10); + + if (magicLevel >= IMBUED_HEART_MIN_CERTAIN_BOOST_LEVEL && boostAmount == heartBoost) + { + createGameTimer(IMBUEDHEART); + } + } + private TimerTimer createGameTimer(final GameTimer timer) { removeGameTimer(timer); diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/timers/TimersPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/timers/TimersPluginTest.java index 976b0e2970..cf359ed14d 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/timers/TimersPluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/timers/TimersPluginTest.java @@ -31,8 +31,11 @@ import com.google.inject.testing.fieldbinder.BoundFieldModule; import java.util.EnumSet; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; +import net.runelite.api.Experience; +import net.runelite.api.Skill; import net.runelite.api.WorldType; import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.StatChanged; import net.runelite.client.game.ItemManager; import net.runelite.client.game.SpriteManager; import net.runelite.client.ui.overlay.infobox.InfoBox; @@ -42,7 +45,10 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import static org.mockito.ArgumentMatchers.any; import org.mockito.Mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.mockito.junit.MockitoJUnitRunner; @@ -136,4 +142,44 @@ public class TimersPluginTest TimerTimer infoBox = (TimerTimer) captor.getValue(); assertEquals(GameTimer.DMM_FULLTB, infoBox.getTimer()); } -} \ No newline at end of file + + @Test + public void testImbuedHeartBoost() + { + when(timersConfig.showImbuedHeart()).thenReturn(true); + StatChanged event; + + // The following simulates imbued heart boosts at low magic levels, but should not create an imbued heart timer + // because it is ambiguous what caused the boost. (Magic essences and potions can create similar boost amounts) + for (int level = 1; level < TimersPlugin.IMBUED_HEART_MIN_CERTAIN_BOOST_LEVEL; level++) + { + event = new StatChanged(Skill.MAGIC, 0, level, level + 1 + (level / 10)); + timersPlugin.onStatChanged(event); + verify(infoBoxManager, never()).addInfoBox(any()); + } + + // The following simulates magic essence and magic potion boosts and should not create an imbued heart timer + for (int level = TimersPlugin.IMBUED_HEART_MIN_CERTAIN_BOOST_LEVEL; level <= Experience.MAX_REAL_LEVEL; level++) + { + event = new StatChanged(Skill.MAGIC, 0, level, level + 3); // Magic essence + timersPlugin.onStatChanged(event); + verify(infoBoxManager, never()).addInfoBox(any()); + + event = new StatChanged(Skill.MAGIC, 0, level, level + 4); + timersPlugin.onStatChanged(event); + verify(infoBoxManager, never()).addInfoBox(any()); + } + + // The following simulates a real imbued heart magic boost and should create imbued heart timers + for (int level = TimersPlugin.IMBUED_HEART_MIN_CERTAIN_BOOST_LEVEL, i = 0; level <= Experience.MAX_REAL_LEVEL; level++, i++) + { + event = new StatChanged(Skill.MAGIC, 0, level, level + 1 + (level / 10)); + timersPlugin.onStatChanged(event); + + ArgumentCaptor captor = ArgumentCaptor.forClass(InfoBox.class); + verify(infoBoxManager, times(i + 1)).addInfoBox(captor.capture()); + TimerTimer infoBox = (TimerTimer) captor.getValue(); + assertEquals(GameTimer.IMBUEDHEART, infoBox.getTimer()); + } + } +}