From e50b0f61a8e10476b63a00095d8eb2d2c59889fa Mon Sep 17 00:00:00 2001 From: Tal Skverer Date: Thu, 4 Nov 2021 11:46:28 -0700 Subject: [PATCH] timers: Track imbued heart timer from varbit --- .../main/java/net/runelite/api/Varbits.java | 7 + .../client/plugins/timers/TimersPlugin.java | 76 +++------- .../plugins/timers/TimersPluginTest.java | 141 ++++-------------- 3 files changed, 55 insertions(+), 169 deletions(-) diff --git a/runelite-api/src/main/java/net/runelite/api/Varbits.java b/runelite-api/src/main/java/net/runelite/api/Varbits.java index d7165d0951..7676ea3cd9 100644 --- a/runelite-api/src/main/java/net/runelite/api/Varbits.java +++ b/runelite-api/src/main/java/net/runelite/api/Varbits.java @@ -529,6 +529,13 @@ public enum Varbits VENGEANCE_COOLDOWN(2451), CORRUPTION_COOLDOWN(12288), + /** + * Imbued Heart cooldown + * Number of game tick remaining on cooldown in intervals of 10; for a value X there are 10 * X game ticks remaining. + * The heart regains its power once this reaches 0. + */ + IMBUED_HEART_COOLDOWN(5361), + /** * Amount of items in each bank tab */ 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 0171b9148e..2adb8fa1b1 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 @@ -39,7 +39,6 @@ import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.Constants; import net.runelite.api.EquipmentInventorySlot; -import net.runelite.api.GameState; import net.runelite.api.InventoryID; import net.runelite.api.Item; import net.runelite.api.ItemContainer; @@ -62,7 +61,6 @@ import net.runelite.api.events.GraphicChanged; import net.runelite.api.events.ItemContainerChanged; import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.events.NpcDespawned; -import net.runelite.api.events.StatChanged; import net.runelite.api.events.VarbitChanged; import net.runelite.api.widgets.Widget; import static net.runelite.api.widgets.WidgetInfo.PVP_WORLD_SAFE_ZONE; @@ -105,7 +103,6 @@ public class TimersPlugin extends Plugin private static final String FROZEN_MESSAGE = "You have been frozen!"; private static final String GAUNTLET_ENTER_MESSAGE = "You enter the Gauntlet."; private static final String GOD_WARS_ALTAR_MESSAGE = "you recharge your prayer."; - private static final String IMBUED_HEART_READY_MESSAGE = "Your imbued heart has regained its magical power."; private static final String MAGIC_IMBUE_EXPIRED_MESSAGE = "Your Magic Imbue charge has ended."; private static final String MAGIC_IMBUE_MESSAGE = "You are charged to combine runes!"; private static final String STAFF_OF_THE_DEAD_SPEC_EXPIRED_MESSAGE = "Your protection fades away"; @@ -153,14 +150,14 @@ public class TimersPlugin extends Plugin private int lastPoisonVarp; private int lastPvpVarb; private int lastCorruptionVarb; + private int lastImbuedHeartVarb; + private boolean imbuedHeartTimerActive; private int nextPoisonTick; private WorldPoint lastPoint; private TeleportWidget lastTeleportClicked; private int lastAnimation; private boolean widgetHiddenChangedOnPvpWorld; private ElapsedTimer tzhaarTimer; - private int imbuedHeartClickTick = -1; - private int lastBoostedMagicLevel = -1; @Inject private ItemManager itemManager; @@ -186,10 +183,6 @@ public class TimersPlugin extends Plugin @Override public void startUp() { - if (client.getGameState() == GameState.LOGGED_IN) - { - lastBoostedMagicLevel = client.getBoostedSkillLevel(Skill.MAGIC); - } } @Override @@ -205,8 +198,8 @@ public class TimersPlugin extends Plugin nextPoisonTick = 0; removeTzhaarTimer(); staminaTimer = null; - imbuedHeartClickTick = -1; - lastBoostedMagicLevel = -1; + imbuedHeartTimerActive = false; + lastImbuedHeartVarb = 0; } @Subscribe @@ -218,6 +211,7 @@ public class TimersPlugin extends Plugin int poisonVarp = client.getVar(VarPlayer.POISON); int pvpVarb = client.getVar(Varbits.PVP_SPEC_ORB); int corruptionCooldownVarb = client.getVar(Varbits.CORRUPTION_COOLDOWN); + int imbuedHeartCooldownVarb = client.getVar(Varbits.IMBUED_HEART_COOLDOWN); if (lastRaidVarb != raidVarb) { @@ -308,6 +302,22 @@ public class TimersPlugin extends Plugin lastPvpVarb = pvpVarb; } + + if (lastImbuedHeartVarb != imbuedHeartCooldownVarb && config.showImbuedHeart()) + { + if (imbuedHeartCooldownVarb == 0) + { + removeGameTimer(IMBUEDHEART); + imbuedHeartTimerActive = false; + } + else if (!imbuedHeartTimerActive) + { + createGameTimer(IMBUEDHEART, Duration.of(10L * imbuedHeartCooldownVarb, RSTimeUnit.GAME_TICKS)); + imbuedHeartTimerActive = true; + } + + lastImbuedHeartVarb = imbuedHeartCooldownVarb; + } } @Subscribe @@ -375,6 +385,7 @@ public class TimersPlugin extends Plugin if (!config.showImbuedHeart()) { removeGameTimer(IMBUEDHEART); + imbuedHeartTimerActive = false; } if (!config.showStaffOfTheDead()) @@ -481,12 +492,6 @@ public class TimersPlugin extends Plugin return; } - if (event.getMenuOption().contains("Invigorate") - && event.getId() == ItemID.IMBUED_HEART) - { - imbuedHeartClickTick = client.getTickCount(); - } - TeleportWidget teleportWidget = TeleportWidget.of(event.getParam1()); if (teleportWidget != null) { @@ -636,11 +641,6 @@ public class TimersPlugin extends Plugin removeGameTimer(SUPERANTIFIRE); } - if (config.showImbuedHeart() && message.equals(IMBUED_HEART_READY_MESSAGE)) - { - removeGameTimer(IMBUEDHEART); - } - if (config.showPrayerEnhance() && message.startsWith("You drink some of your") && message.contains("prayer enhance")) { createGameTimer(PRAYER_ENHANCE); @@ -922,7 +922,6 @@ public class TimersPlugin extends Plugin } break; case LOGIN_SCREEN: - lastBoostedMagicLevel = -1; // fall through case HOPPING: // pause tzhaar timer if logged out without pausing @@ -1100,37 +1099,6 @@ public class TimersPlugin extends Plugin } } - @Subscribe - public void onStatChanged(StatChanged statChanged) - { - if (statChanged.getSkill() != Skill.MAGIC) - { - return; - } - - final int boostedMagicLevel = statChanged.getBoostedLevel(); - - if (imbuedHeartClickTick < 0 - || client.getTickCount() > imbuedHeartClickTick + 3 // allow for 2 ticks of lag - || !config.showImbuedHeart()) - { - lastBoostedMagicLevel = boostedMagicLevel; - return; - } - - final int boostAmount = boostedMagicLevel - statChanged.getLevel(); - final int boostChange = boostedMagicLevel - lastBoostedMagicLevel; - final int heartBoost = 1 + (statChanged.getLevel() / 10); - - if ((boostAmount == heartBoost || (lastBoostedMagicLevel != -1 && boostChange == heartBoost)) - && boostChange > 0) - { - createGameTimer(IMBUEDHEART); - } - - lastBoostedMagicLevel = boostedMagicLevel; - } - private void createStaminaTimer() { Duration duration = Duration.ofMinutes(wasWearingEndurance ? 4 : 2); 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 58b17fd2ca..62bd1d38ae 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 @@ -33,16 +33,12 @@ import java.time.Instant; import java.util.function.Predicate; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; -import net.runelite.api.Experience; import net.runelite.api.InventoryID; import net.runelite.api.ItemContainer; -import net.runelite.api.ItemID; import net.runelite.api.Skill; import net.runelite.api.Varbits; import net.runelite.api.events.ChatMessage; import net.runelite.api.events.ItemContainerChanged; -import net.runelite.api.events.MenuOptionClicked; -import net.runelite.api.events.StatChanged; import net.runelite.api.events.VarbitChanged; import net.runelite.client.game.ItemManager; import net.runelite.client.game.SpriteManager; @@ -63,11 +59,9 @@ import static org.mockito.ArgumentMatchers.nullable; import org.mockito.Mock; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; @@ -327,7 +321,7 @@ public class TimersPluginTest assertTrue(captor.getValue() instanceof ElapsedTimer); // test timer pause: verify the added ElapsedTimer has a non-null lastTime - chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "The Inferno has been paused. You may now log out.", "", 0); + chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "The Inferno has been paused. You may now log out.", "", 0); timersPlugin.onChatMessage(chatMessage); verify(infoBoxManager, times(1)).removeInfoBox(captor.capture()); verify(infoBoxManager, times(2)).addInfoBox(captor.capture()); @@ -337,7 +331,7 @@ public class TimersPluginTest Instant oldTime = ((ElapsedTimer) captor.getValue()).getStartTime(); // test timer unpause: verify the last time is null after being unpaused - chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "Wave: 2", "", 0); + chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "Wave: 2", "", 0); timersPlugin.onChatMessage(chatMessage); verify(infoBoxManager, times(2)).removeInfoBox(captor.capture()); verify(infoBoxManager, times(3)).addInfoBox(captor.capture()); @@ -346,7 +340,7 @@ public class TimersPluginTest assertNull(timer.getLastTime()); // test timer remove: verify the infobox was removed (and no more were added) - chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "You have been defeated!", "", 0); + chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "You have been defeated!", "", 0); timersPlugin.onChatMessage(chatMessage); verify(infoBoxManager, times(3)).removeInfoBox(captor.capture()); verify(infoBoxManager, times(3)).addInfoBox(captor.capture()); @@ -543,123 +537,40 @@ public class TimersPluginTest // endregion @Test - public void testImbuedHeartBoost() + public void testImbuedHeartStart() { when(timersConfig.showImbuedHeart()).thenReturn(true); - when(client.getTickCount()).thenReturn(100); - StatChanged event; + when(client.getVar(Varbits.IMBUED_HEART_COOLDOWN)).thenReturn(70); + timersPlugin.onVarbitChanged(new VarbitChanged()); - final MenuOptionClicked imbuedHeartClick = new MenuOptionClicked(); - imbuedHeartClick.setMenuOption("Invigorate"); - imbuedHeartClick.setId(ItemID.IMBUED_HEART); - timersPlugin.onMenuOptionClicked(imbuedHeartClick); - - when(client.getTickCount()).thenReturn(101); - - for (int level = 1, i = 0; level <= Experience.MAX_REAL_LEVEL; level++, i++) - { - event = new StatChanged(Skill.MAGIC, 0, level, heartBoostedLevel(level)); - 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()); - } + ArgumentCaptor captor = ArgumentCaptor.forClass(InfoBox.class); + verify(infoBoxManager).addInfoBox(captor.capture()); + TimerTimer infoBox = (TimerTimer) captor.getValue(); + assertEquals(GameTimer.IMBUEDHEART, infoBox.getTimer()); + assertEquals(GameTimer.IMBUEDHEART.getDuration(), infoBox.getDuration()); } @Test - public void testImbuedHeartBoostFromDrained() + public void testImbuedHeartEnd() { when(timersConfig.showImbuedHeart()).thenReturn(true); - when(client.getTickCount()).thenReturn(100); - final MenuOptionClicked imbuedHeartClick = new MenuOptionClicked(); - imbuedHeartClick.setMenuOption("Invigorate"); - imbuedHeartClick.setId(ItemID.IMBUED_HEART); - timersPlugin.onMenuOptionClicked(imbuedHeartClick); + when(client.getVar(Varbits.IMBUED_HEART_COOLDOWN)).thenReturn(70); + timersPlugin.onVarbitChanged(new VarbitChanged()); // Calls removeIf once (on createGameTimer) - when(client.getTickCount()).thenReturn(101); + ArgumentCaptor> prcaptor = ArgumentCaptor.forClass(Predicate.class); + TimerTimer imbuedHeartInfoBox = new TimerTimer(GameTimer.IMBUEDHEART, Duration.ofSeconds(420), timersPlugin); + verify(infoBoxManager, times (1)).addInfoBox(any()); + verify(infoBoxManager, times(1)).removeIf(prcaptor.capture()); + Predicate pred = prcaptor.getValue(); + assertTrue(pred.test(imbuedHeartInfoBox)); - for (int level = 1, i = 0; level <= Experience.MAX_REAL_LEVEL; level++, i++) - { - timersPlugin.onStatChanged(new StatChanged(Skill.MAGIC, 0, level, level - 1)); - timersPlugin.onStatChanged(new StatChanged(Skill.MAGIC, 0, level, heartBoostedLevel(level) - 1)); + when(client.getVar(Varbits.IMBUED_HEART_COOLDOWN)).thenReturn(0); + timersPlugin.onVarbitChanged(new VarbitChanged()); // Calls removeIf once - ArgumentCaptor captor = ArgumentCaptor.forClass(InfoBox.class); - verify(infoBoxManager, times(i + 1)).addInfoBox(captor.capture()); - TimerTimer infoBox = (TimerTimer) captor.getValue(); - assertEquals(GameTimer.IMBUEDHEART, infoBox.getTimer()); - } - } - - @Test - public void testImbuedHeartBoostFromPartialBoost() - { - when(timersConfig.showImbuedHeart()).thenReturn(true); - when(client.getTickCount()).thenReturn(100); - - final MenuOptionClicked imbuedHeartClick = new MenuOptionClicked(); - imbuedHeartClick.setMenuOption("Invigorate"); - imbuedHeartClick.setId(ItemID.IMBUED_HEART); - timersPlugin.onMenuOptionClicked(imbuedHeartClick); - - when(client.getTickCount()).thenReturn(101); - - for (int level = 10, i = 0; level <= Experience.MAX_REAL_LEVEL; level++, i++) - { - timersPlugin.onStatChanged(new StatChanged(Skill.MAGIC, 0, level, level + 1)); - timersPlugin.onStatChanged(new StatChanged(Skill.MAGIC, 0, level, heartBoostedLevel(level))); - - ArgumentCaptor captor = ArgumentCaptor.forClass(InfoBox.class); - verify(infoBoxManager, times(i + 1)).addInfoBox(captor.capture()); - TimerTimer infoBox = (TimerTimer) captor.getValue(); - assertEquals(GameTimer.IMBUEDHEART, infoBox.getTimer()); - } - } - - @Test - public void testNonImbuedHeartBoost() - { - lenient().when(timersConfig.showImbuedHeart()).thenReturn(true); - timersPlugin.onStatChanged(new StatChanged(Skill.MAGIC, 0, 1, 1)); - - // Simulate stat changes of imbued heart boost amount without having clicked the imbued heart - timersPlugin.onStatChanged(new StatChanged(Skill.MAGIC, 0, 29, 34)); // equal to magic essence - timersPlugin.onStatChanged(new StatChanged(Skill.MAGIC, 0, 39, 43)); // equal to magic potion - timersPlugin.onStatChanged(new StatChanged(Skill.MAGIC, 0, 49, 54)); // equal to spicy stew - timersPlugin.onStatChanged(new StatChanged(Skill.MAGIC, 0, 99, 109)); - - verifyNoInteractions(infoBoxManager); - } - - @Test - public void testMagicLevelDrain() - { - lenient().when(timersConfig.showImbuedHeart()).thenReturn(true); - timersPlugin.onStatChanged(new StatChanged(Skill.MAGIC, 0, 1, 1)); - when(client.getTickCount()).thenReturn(100); - - final MenuOptionClicked imbuedHeartClick = new MenuOptionClicked(); - imbuedHeartClick.setMenuOption("Invigorate"); - imbuedHeartClick.setId(ItemID.IMBUED_HEART); - timersPlugin.onMenuOptionClicked(imbuedHeartClick); - - when(client.getTickCount()).thenReturn(101); - - // Simulate stat changes draining to the imbued heart boost amount - for (int level = 1; level <= Experience.MAX_REAL_LEVEL; level++) - { - timersPlugin.onStatChanged(new StatChanged(Skill.MAGIC, 0, level, level)); - timersPlugin.onStatChanged(new StatChanged(Skill.MAGIC, 0, level, heartBoostedLevel(level) + 1)); - timersPlugin.onStatChanged(new StatChanged(Skill.MAGIC, 0, level, heartBoostedLevel(level))); - } - - verifyNoInteractions(infoBoxManager); - } - - private static int heartBoostedLevel(final int level) - { - return level + 1 + (level / 10); + verify(infoBoxManager, times(1)).addInfoBox(any()); + verify(infoBoxManager, times(2)).removeIf(prcaptor.capture()); + pred = prcaptor.getValue(); + assertTrue(pred.test(imbuedHeartInfoBox)); } }