diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xpglobes/XpGlobesConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/xpglobes/XpGlobesConfig.java index c32a8c598a..2a956da851 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xpglobes/XpGlobesConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xpglobes/XpGlobesConfig.java @@ -89,11 +89,22 @@ public interface XpGlobesConfig extends Config return false; } + @ConfigItem( + keyName = "showVirtualLevel", + name = "Show virtual level", + description = "Shows virtual level if over 99 in a skill and Hide maxed skill is not checked", + position = 5 + ) + default boolean showVirtualLevel() + { + return false; + } + @ConfigItem( keyName = "enableCustomArcColor", name = "Enable custom arc color", description = "Enables the custom coloring of the globe's arc instead of using the skill's default color.", - position = 5 + position = 6 ) default boolean enableCustomArcColor() { @@ -105,7 +116,7 @@ public interface XpGlobesConfig extends Config keyName = "Progress arc color", name = "Progress arc color", description = "Change the color of the progress arc in the xp orb", - position = 6 + position = 7 ) default Color progressArcColor() { @@ -117,7 +128,7 @@ public interface XpGlobesConfig extends Config keyName = "Progress orb outline color", name = "Progress orb outline color", description = "Change the color of the progress orb outline", - position = 7 + position = 8 ) default Color progressOrbOutLineColor() { @@ -129,7 +140,7 @@ public interface XpGlobesConfig extends Config keyName = "Progress orb background color", name = "Progress orb background color", description = "Change the color of the progress orb background", - position = 8 + position = 9 ) default Color progressOrbBackgroundColor() { @@ -140,7 +151,7 @@ public interface XpGlobesConfig extends Config keyName = "Progress arc width", name = "Progress arc width", description = "Change the stroke width of the progress arc", - position = 9 + position = 10 ) @Units(Units.PIXELS) default int progressArcStrokeWidth() @@ -152,7 +163,7 @@ public interface XpGlobesConfig extends Config keyName = "Orb size", name = "Size of orbs", description = "Change the size of the xp orbs", - position = 10 + position = 11 ) @Units(Units.PIXELS) default int xpOrbSize() @@ -164,7 +175,7 @@ public interface XpGlobesConfig extends Config keyName = "Orb duration", name = "Duration of orbs", description = "Change the duration the xp orbs are visible", - position = 11 + position = 12 ) @Units(Units.SECONDS) default int xpOrbDuration() diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xpglobes/XpGlobesPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/xpglobes/XpGlobesPlugin.java index 46b514151e..39b947616a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xpglobes/XpGlobesPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xpglobes/XpGlobesPlugin.java @@ -106,9 +106,17 @@ public class XpGlobesPlugin extends Plugin return; } - if (config.hideMaxed() && currentLevel >= Experience.MAX_REAL_LEVEL) + if (currentLevel >= Experience.MAX_REAL_LEVEL) { - return; + if (config.hideMaxed()) + { + return; + } + + if (config.showVirtualLevel()) + { + currentLevel = Experience.getLevelForXp(currentXp); + } } if (cachedGlobe != null) diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/xpglobes/XpGlobesPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/xpglobes/XpGlobesPluginTest.java new file mode 100644 index 0000000000..bfc5de4b0d --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/xpglobes/XpGlobesPluginTest.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2021, Wright + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.xpglobes; + +import com.google.inject.Guice; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import javax.inject.Inject; +import net.runelite.api.Experience; +import net.runelite.api.Skill; +import net.runelite.api.events.StatChanged; +import net.runelite.client.plugins.xptracker.XpTrackerService; +import net.runelite.client.ui.overlay.OverlayManager; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.when; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class XpGlobesPluginTest +{ + private static final int VIRTUAL_LEVEL_TOTAL_XP = Experience.getXpForLevel(Experience.MAX_REAL_LEVEL + 1); + + @Inject + private XpGlobesPlugin xpGlobesPlugin; + + @Mock + @Bind + private OverlayManager overlayManager; + + @Mock + @Bind + private XpGlobesOverlay xpGlobesOverlay; + + @Mock + @Bind + private XpTrackerService xpTrackerService; + + @Mock + @Bind + private XpGlobesConfig xpGlobesConfig; + + @Before + public void before() + { + Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); + + statChanged(VIRTUAL_LEVEL_TOTAL_XP, Skill.AGILITY); + assertTrue(xpGlobesPlugin.getXpGlobes().isEmpty()); + } + + @Test + public void testVirtualLevelInGlobeIsNotShownByDefault() + { + when(xpGlobesConfig.showVirtualLevel()).thenReturn(false); + + statChanged(VIRTUAL_LEVEL_TOTAL_XP + 1, Skill.AGILITY); + + assertEquals(Experience.MAX_REAL_LEVEL, xpGlobesPlugin.getXpGlobes().get(0).getCurrentLevel()); + } + + @Test + public void testVirtualLevelInGlobeIsShownWhenConfigured() + { + when(xpGlobesConfig.showVirtualLevel()).thenReturn(true); + + statChanged(VIRTUAL_LEVEL_TOTAL_XP + 1, Skill.AGILITY); + + assertEquals(Experience.getLevelForXp(VIRTUAL_LEVEL_TOTAL_XP + 1), xpGlobesPlugin.getXpGlobes().get(0).getCurrentLevel()); + } + + @Test + public void testGlobeIsNotShownWhenHideMaxAndShowVirtualLevelConfigured() + { + when(xpGlobesConfig.hideMaxed()).thenReturn(true); + lenient().when(xpGlobesConfig.showVirtualLevel()).thenReturn(true); + + statChanged(VIRTUAL_LEVEL_TOTAL_XP + 1, Skill.AGILITY); + + assertTrue(xpGlobesPlugin.getXpGlobes().isEmpty()); + } + + @Test + public void testGlobeIsNotShownWhenHideMaxConfigured() + { + when(xpGlobesConfig.hideMaxed()).thenReturn(true); + + statChanged(VIRTUAL_LEVEL_TOTAL_XP + 1, Skill.AGILITY); + + assertTrue(xpGlobesPlugin.getXpGlobes().isEmpty()); + } + + @Test + public void testGlobeIsShownOnXpGainBelowMaxWhenHideMaxConfigured() + { + lenient().when(xpGlobesConfig.hideMaxed()).thenReturn(true); + + int totalXp = 1; + statChanged(totalXp, Skill.FARMING); + assertTrue(xpGlobesPlugin.getXpGlobes().isEmpty()); + + statChanged(totalXp + 150, Skill.FARMING); + + assertEquals(Experience.getLevelForXp(totalXp + 150), xpGlobesPlugin.getXpGlobes().get(0).getCurrentLevel()); + } + + @Test + public void testStatChangesFromBoostDoNotAffectXpGlobes() + { + statChanged(VIRTUAL_LEVEL_TOTAL_XP, Skill.AGILITY, 5); + + assertTrue(xpGlobesPlugin.getXpGlobes().isEmpty()); + } + + private void statChanged(int totalXp, Skill skill) + { + statChanged(totalXp, skill, 0); + } + + private void statChanged(int totalXp, Skill skill, int boostedLevel) + { + // A statChanged event uses the max real level + int statChangedLevel = Math.min(Experience.getLevelForXp(totalXp), Experience.MAX_REAL_LEVEL); + + StatChanged firstStatChangedEvent = new StatChanged( + skill, + totalXp, + statChangedLevel, + boostedLevel + ); + + // The first xp change is cached + xpGlobesPlugin.onStatChanged(firstStatChangedEvent); + } +}