From 1a9efe885ea28128a12833c482da198bb5c5e683 Mon Sep 17 00:00:00 2001 From: Ryan H Date: Sat, 8 Feb 2020 18:19:05 -0500 Subject: [PATCH 1/2] customcursor: support providing custom cursor images Co-authored-by: Adam --- .../plugins/customcursor/CustomCursor.java | 19 +++++---- .../customcursor/CustomCursorPlugin.java | 40 ++++++++++++++++++- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursor.java b/runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursor.java index 2e371632b4..5012f3d564 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursor.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursor.java @@ -25,9 +25,11 @@ package net.runelite.client.plugins.customcursor; import java.awt.image.BufferedImage; +import javax.annotation.Nullable; import lombok.Getter; import net.runelite.client.util.ImageUtil; +@Getter public enum CustomCursor { RS3_GOLD("RS3 Gold", "cursor-rs3-gold.png"), @@ -35,21 +37,22 @@ public enum CustomCursor DRAGON_DAGGER("Dragon Dagger", "cursor-dragon-dagger.png"), DRAGON_DAGGER_POISON("Dragon Dagger (p)", "cursor-dragon-dagger-p.png"), TROUT("Trout", "cursor-trout.png"), - DRAGON_SCIMITAR("Dragon Scimitar", "cursor-dragon-scimitar.png"); + DRAGON_SCIMITAR("Dragon Scimitar", "cursor-dragon-scimitar.png"), + CUSTOM_IMAGE("Custom Image"); private final String name; - @Getter + @Nullable private final BufferedImage cursorImage; + CustomCursor(String name) + { + this.name = name; + this.cursorImage = null; + } + CustomCursor(String name, String icon) { this.name = name; this.cursorImage = ImageUtil.getResourceStreamFromClass(CustomCursorPlugin.class, icon); } - - @Override - public String toString() - { - return name; - } } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursorPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursorPlugin.java index 1c511c6753..2c5d87361f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursorPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/customcursor/CustomCursorPlugin.java @@ -25,10 +25,15 @@ package net.runelite.client.plugins.customcursor; import com.google.inject.Provides; +import java.awt.image.BufferedImage; +import java.io.File; +import javax.imageio.ImageIO; import javax.inject.Inject; -import net.runelite.client.events.ConfigChanged; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.RuneLite; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.ui.ClientUI; @@ -38,8 +43,11 @@ import net.runelite.client.ui.ClientUI; description = "Replaces your mouse cursor image", enabledByDefault = false ) +@Slf4j public class CustomCursorPlugin extends Plugin { + private static final File CUSTOM_IMAGE_FILE = new File(RuneLite.RUNELITE_DIR, "cursor.png"); + @Inject private ClientUI clientUI; @@ -76,6 +84,34 @@ public class CustomCursorPlugin extends Plugin private void updateCursor() { CustomCursor selectedCursor = config.selectedCursor(); - clientUI.setCursor(selectedCursor.getCursorImage(), selectedCursor.toString()); + + if (selectedCursor == CustomCursor.CUSTOM_IMAGE) + { + if (CUSTOM_IMAGE_FILE.exists()) + { + try + { + BufferedImage image; + synchronized (ImageIO.class) + { + image = ImageIO.read(CUSTOM_IMAGE_FILE); + } + clientUI.setCursor(image, selectedCursor.getName()); + } + catch (Exception e) + { + log.error("error setting custom cursor", e); + clientUI.resetCursor(); + } + } + else + { + clientUI.resetCursor(); + } + return; + } + + assert selectedCursor.getCursorImage() != null; + clientUI.setCursor(selectedCursor.getCursorImage(), selectedCursor.getName()); } } \ No newline at end of file From 45c5df3379767146f91e1e4a1f48d9692599a1d4 Mon Sep 17 00:00:00 2001 From: Jordan Atwood Date: Sun, 2 Feb 2020 22:24:21 -0800 Subject: [PATCH 2/2] 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()); + } + } +}