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()); + } + } +}