diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index d4d70c557c..022e3bab64 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -25,9 +25,9 @@ object ProjectVersions { const val launcherVersion = "2.2.0" - const val rlVersion = "1.6.26" + const val rlVersion = "1.6.28" - const val openosrsVersion = "3.4.5" + const val openosrsVersion = "3.5.0" const val rsversion = 191 const val cacheversion = 165 diff --git a/cache/src/main/java/net/runelite/cache/script/disassembler/Disassembler.java b/cache/src/main/java/net/runelite/cache/script/disassembler/Disassembler.java index 0a6382fec8..523796c49f 100644 --- a/cache/src/main/java/net/runelite/cache/script/disassembler/Disassembler.java +++ b/cache/src/main/java/net/runelite/cache/script/disassembler/Disassembler.java @@ -24,6 +24,8 @@ */ package net.runelite.cache.script.disassembler; +import com.google.common.escape.Escaper; +import com.google.common.escape.Escapers; import java.io.IOException; import java.util.Map; import java.util.Map.Entry; @@ -37,6 +39,10 @@ import org.slf4j.LoggerFactory; public class Disassembler { private static final Logger logger = LoggerFactory.getLogger(Disassembler.class); + private static final Escaper ESCAPER = Escapers.builder() + .addEscape('"', "\\\"") + .addEscape('\\', "\\\\") + .build(); private final Instructions instructions = new Instructions(); @@ -165,7 +171,7 @@ public class Disassembler if (sop != null) { - writer.append(" \"").append(sop).append("\""); + writer.append(" \"").append(ESCAPER.escape(sop)).append("\""); } if (opcode == Opcodes.SWITCH) diff --git a/http-api/src/main/java/net/runelite/http/api/loottracker/LootTrackerClient.java b/http-api/src/main/java/net/runelite/http/api/loottracker/LootTrackerClient.java index d551ad1572..4a66d04a2a 100644 --- a/http-api/src/main/java/net/runelite/http/api/loottracker/LootTrackerClient.java +++ b/http-api/src/main/java/net/runelite/http/api/loottracker/LootTrackerClient.java @@ -35,7 +35,9 @@ import java.util.Collection; import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import net.runelite.http.api.RuneLiteAPI; import static net.runelite.http.api.RuneLiteAPI.JSON; @@ -48,30 +50,29 @@ import okhttp3.RequestBody; import okhttp3.Response; @Slf4j -@AllArgsConstructor +@RequiredArgsConstructor public class LootTrackerClient { private static final Gson GSON = RuneLiteAPI.GSON; private final OkHttpClient client; - private final UUID uuid; + @Getter + @Setter + private UUID uuid; public CompletableFuture submit(Collection lootRecords) { CompletableFuture future = new CompletableFuture<>(); - HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() - .addPathSegment("loottracker") - .build(); - RequestBody body = RequestBody.Companion.create(GSON.toJson(lootRecords), JSON); - Request request = new Request.Builder() - .header(RuneLiteAPI.RUNELITE_AUTH, uuid.toString()) - .post(body) - .url(url) - .build(); + Request.Builder requestBuilder = new Request.Builder(); + if (uuid != null) + { + requestBuilder.header(RuneLiteAPI.RUNELITE_AUTH, uuid.toString()); + } + requestBuilder.post(body); - client.newCall(request).enqueue(new Callback() + client.newCall(requestBuilder.build()).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) diff --git a/runelite-api/src/main/java/net/runelite/api/Perspective.java b/runelite-api/src/main/java/net/runelite/api/Perspective.java index 962055d443..49c8690021 100644 --- a/runelite-api/src/main/java/net/runelite/api/Perspective.java +++ b/runelite-api/src/main/java/net/runelite/api/Perspective.java @@ -50,7 +50,7 @@ import net.runelite.api.widgets.WidgetInfo; */ public class Perspective { - private static final double UNIT = Math.PI / 1024d; // How much of the circle each unit of SINE/COSINE is + public static final double UNIT = Math.PI / 1024d; // How much of the circle each unit of SINE/COSINE is public static final int LOCAL_COORD_BITS = 7; public static final int LOCAL_TILE_SIZE = 1 << LOCAL_COORD_BITS; // 128 - size of a tile in local coordinates 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 60cd9e3c20..f982a29f18 100644 --- a/runelite-api/src/main/java/net/runelite/api/Varbits.java +++ b/runelite-api/src/main/java/net/runelite/api/Varbits.java @@ -493,6 +493,7 @@ public enum Varbits FARMING_7909(7909), FARMING_7910(7910), FARMING_7911(7911), + FARMING_7912(7912), /** * Transmog controllers for grapes diff --git a/runelite-api/src/main/java/net/runelite/api/util/Text.java b/runelite-api/src/main/java/net/runelite/api/util/Text.java index f837b4b918..c13cbdcb74 100644 --- a/runelite-api/src/main/java/net/runelite/api/util/Text.java +++ b/runelite-api/src/main/java/net/runelite/api/util/Text.java @@ -328,7 +328,7 @@ public class Text * * @return true if all search terms matches at least one keyword, or false if otherwise. */ - public static boolean matchesSearchTerms(String[] searchTerms, final Collection keywords) + public static boolean matchesSearchTerms(Iterable searchTerms, final Collection keywords) { for (String term : searchTerms) { diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java index 4a66c52ad8..dd3eb57f5f 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java @@ -173,6 +173,9 @@ public class WidgetID public static final int GAUNTLET_MAP_GROUP_ID = 638; public static final int HALLOWED_SEPULCHRE_TIMER_GROUP_ID = 668; public static final int HEALTH_OVERLAY_BAR_GROUP_ID = 303; + public static final int CHAMBERS_OF_XERIC_STORAGE_UNIT_PRIVATE_GROUP_ID = 271; + public static final int CHAMBERS_OF_XERIC_STORAGE_UNIT_SHARED_GROUP_ID = 550; + public static final int CHAMBERS_OF_XERIC_STORAGE_UNIT_INVENTORY_GROUP_ID = 551; static class WorldMap { diff --git a/runelite-api/src/test/java/net/runelite/api/ExperienceTest.java b/runelite-api/src/test/java/net/runelite/api/ExperienceTest.java index 29920638bd..1755ab5e59 100644 --- a/runelite-api/src/test/java/net/runelite/api/ExperienceTest.java +++ b/runelite-api/src/test/java/net/runelite/api/ExperienceTest.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2017, Adam + * Copyright (c) 2018, Brett Middle * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,7 +25,7 @@ */ package net.runelite.api;; -import org.junit.Assert; +import static org.junit.Assert.assertEquals; import org.junit.Test; public class ExperienceTest @@ -36,13 +37,13 @@ public class ExperienceTest public void testGetXpForLevel() { int xp = Experience.getXpForLevel(99); - Assert.assertEquals(XP_FOR_99, xp); + assertEquals(XP_FOR_99, xp); xp = Experience.getXpForLevel(126); - Assert.assertEquals(XP_FOR_126, xp); + assertEquals(XP_FOR_126, xp); xp = Experience.getXpForLevel(1); - Assert.assertEquals(0, xp); + assertEquals(0, xp); } @Test(expected = IllegalArgumentException.class) @@ -61,22 +62,22 @@ public class ExperienceTest public void testGetLevelForXp() { int level = Experience.getLevelForXp(XP_FOR_99); - Assert.assertEquals(99, level); + assertEquals(99, level); level = Experience.getLevelForXp(XP_FOR_99 - 1); - Assert.assertEquals(98, level); + assertEquals(98, level); level = Experience.getLevelForXp(XP_FOR_126); - Assert.assertEquals(126, level); + assertEquals(126, level); level = Experience.getLevelForXp(XP_FOR_126 - 1); - Assert.assertEquals(125, level); + assertEquals(125, level); level = Experience.getLevelForXp(Integer.MAX_VALUE); - Assert.assertEquals(126, level); + assertEquals(126, level); level = Experience.getLevelForXp(0); - Assert.assertEquals(1, level); + assertEquals(1, level); } @Test(expected = IllegalArgumentException.class) @@ -88,7 +89,371 @@ public class ExperienceTest @Test public void testGetCombatLevel() { - Assert.assertEquals(126, Experience.getCombatLevel(99, 99, 99, 99, 70, 42, 98)); - Assert.assertEquals(40, Experience.getCombatLevel(27, 22, 1, 36, 64, 45, 1)); + assertEquals(126, Experience.getCombatLevel(99, 99, 99, 99, 70, 42, 98)); + assertEquals(40, Experience.getCombatLevel(27, 22, 1, 36, 64, 45, 1)); + } + + @Test + public void testNewPlayerNextCombatLevel() + { + int attackLevel = 1; + int strengthLevel = 1; + int defenceLevel = 1; + int hitpointsLevel = 10; + int magicLevel = 1; + int rangeLevel = 1; + int prayerLevel = 1; + + int combatLevel = Experience.getCombatLevel(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int meleeNeed = Experience.getNextCombatLevelMelee(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int hpDefNeed = Experience.getNextCombatLevelHpDef(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int rangeNeed = Experience.getNextCombatLevelRange(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int magicNeed = Experience.getNextCombatLevelMagic(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int prayerNeed = Experience.getNextCombatLevelPrayer(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + + // test combat level + assertEquals(3, combatLevel); + + // test attack/strength + assertEquals(2, meleeNeed); + + // test defence/hitpoints + assertEquals(3, hpDefNeed); + + // test ranged + assertEquals(2, rangeNeed); + + // test magic + assertEquals(2, magicNeed); + + // test prayer + assertEquals(5, prayerNeed); + } + + @Test + public void testAll10NextCombatLevel() + { + int attackLevel = 10; + int strengthLevel = 10; + int defenceLevel = 10; + int hitpointsLevel = 10; + int magicLevel = 10; + int rangeLevel = 10; + int prayerLevel = 10; + + int combatLevel = Experience.getCombatLevel(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int meleeNeed = Experience.getNextCombatLevelMelee(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int hpDefNeed = Experience.getNextCombatLevelHpDef(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int rangeNeed = Experience.getNextCombatLevelRange(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int magicNeed = Experience.getNextCombatLevelMagic(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int prayerNeed = Experience.getNextCombatLevelPrayer(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + + // test combat level + assertEquals(12, combatLevel); + + // test attack/strength + assertEquals(1, meleeNeed); + + // test defence/hitpoints + assertEquals(1, hpDefNeed); + + // test ranged + assertEquals(4, rangeNeed); + + // test magic + assertEquals(4, magicNeed); + + // test prayer + assertEquals(2, prayerNeed); + } + + @Test + public void testPlayerBmidNextCombatLevel() + { + // snapshot of current stats 2018-10-2 + int attackLevel = 65; + int strengthLevel = 70; + int defenceLevel = 60; + int hitpointsLevel = 71; + int magicLevel = 73; + int rangeLevel = 75; + int prayerLevel = 56; + + int combatLevel = Experience.getCombatLevel(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int meleeNeed = Experience.getNextCombatLevelMelee(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int hpDefNeed = Experience.getNextCombatLevelHpDef(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int rangeNeed = Experience.getNextCombatLevelRange(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int magicNeed = Experience.getNextCombatLevelMagic(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int prayerNeed = Experience.getNextCombatLevelPrayer(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + + // test combat level + assertEquals(83, combatLevel); + + // test attack/strength + assertEquals(2, meleeNeed); + + // test defence/hitpoints + assertEquals(2, hpDefNeed); + + // test ranged + assertEquals(17, rangeNeed); + + // test magic + assertEquals(19, magicNeed); + + // test prayer + assertEquals(4, prayerNeed); + } + + @Test + public void testPlayerRuneliteNextCombatLevel() + { + // snapshot of current stats 2018-10-2 + int attackLevel = 43; + int strengthLevel = 36; + int defenceLevel = 1; + int hitpointsLevel = 42; + int magicLevel = 64; + int rangeLevel = 51; + int prayerLevel = 15; + + int combatLevel = Experience.getCombatLevel(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int meleeNeed = Experience.getNextCombatLevelMelee(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int hpDefNeed = Experience.getNextCombatLevelHpDef(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int rangeNeed = Experience.getNextCombatLevelRange(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int magicNeed = Experience.getNextCombatLevelMagic(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int prayerNeed = Experience.getNextCombatLevelPrayer(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + + // test combat level + assertEquals(43, combatLevel); + + // test attack/strength + assertEquals(18, meleeNeed); + + // test defence/hitpoints + assertEquals(2, hpDefNeed); + + // test ranged + assertEquals(14, rangeNeed); + + // test magic + assertEquals(1, magicNeed); + + // test prayer + assertEquals(3, prayerNeed); + } + + @Test + public void testPlayerZezimaNextCombatLevel() + { + // snapshot of current stats 2018-10-3 + // Zezima cannot earn a combat level from ranged/magic anymore, so it won't show as the result is too high + int attackLevel = 74; + int strengthLevel = 74; + int defenceLevel = 72; + int hitpointsLevel = 72; + int magicLevel = 60; + int rangeLevel = 44; + int prayerLevel = 52; + + int combatLevel = Experience.getCombatLevel(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int meleeNeed = Experience.getNextCombatLevelMelee(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int hpDefNeed = Experience.getNextCombatLevelHpDef(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int prayerNeed = Experience.getNextCombatLevelPrayer(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + + // test combat level + assertEquals(90, combatLevel); + + // test attack/strength + assertEquals(2, meleeNeed); + + // test defence/hitpoints + assertEquals(2, hpDefNeed); + + // test prayer + assertEquals(4, prayerNeed); + } + + @Test + public void testPrayerLevelsNeeded() + { + int attackLevel = 99; + int strengthLevel = 99; + int defenceLevel = 99; + int hitpointsLevel = 99; + int magicLevel = 99; + int rangeLevel = 99; + int prayerLevel = 89; + + int combatLevel = Experience.getCombatLevel(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int prayerNeed = Experience.getNextCombatLevelPrayer(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + + // test combat level + assertEquals(124, combatLevel); + + // test prayer + assertEquals(1, prayerNeed); + } + + @Test + public void testEvenPrayerLevelsNeededWhenNearNextCombatLevel() + { + int attackLevel = 74; + int strengthLevel = 75; + int defenceLevel = 72; + int hitpointsLevel = 72; + int magicLevel = 60; + int rangeLevel = 44; + int prayerLevel = 52; + + int combatLevel = Experience.getCombatLevel(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int prayerNeed = Experience.getNextCombatLevelPrayer(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + + // test combat level + assertEquals(90, combatLevel); + + // test prayer + assertEquals(2, prayerNeed); + } + + @Test + public void testOddPrayerLevelsNeededWhenNearNextCombatLevel() + { + int attackLevel = 74; + int strengthLevel = 75; + int defenceLevel = 72; + int hitpointsLevel = 72; + int magicLevel = 60; + int rangeLevel = 44; + int prayerLevel = 53; + + int combatLevel = Experience.getCombatLevel(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int prayerNeed = Experience.getNextCombatLevelPrayer(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + + // test combat level + assertEquals(90, combatLevel); + + // test prayer + assertEquals(1, prayerNeed); + } + + @Test + public void testNextMagicLevelBarelyReachesNextCombatLevel() + { + int attackLevel = 40; + int strengthLevel = 44; + int defenceLevel = 46; + int hitpointsLevel = 39; + int magicLevel = 57; + int rangeLevel = 40; + int prayerLevel = 29; + + int combatLevel = Experience.getCombatLevel(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int meleeNeed = Experience.getNextCombatLevelMelee(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int hpDefNeed = Experience.getNextCombatLevelHpDef(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int rangeNeed = Experience.getNextCombatLevelRange(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int magicNeed = Experience.getNextCombatLevelMagic(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int prayerNeed = Experience.getNextCombatLevelPrayer(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + + // test combat level + assertEquals(52, combatLevel); + + // test attack/strength + assertEquals(3, meleeNeed); + + // test defence/hitpoints + assertEquals(3, hpDefNeed); + + // test ranged + assertEquals(18, rangeNeed); + + // test magic + assertEquals(1, magicNeed); + + // test prayer + assertEquals(5, prayerNeed); + } + + @Test + public void testRangeMagicLevelsNeeded() + { + int attackLevel = 60; + int strengthLevel = 69; + int defenceLevel = 1; + int hitpointsLevel = 78; + int magicLevel = 85; + int rangeLevel = 85; + int prayerLevel = 52; + + int combatLevel = Experience.getCombatLevel(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int meleeNeed = Experience.getNextCombatLevelMelee(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int hpDefNeed = Experience.getNextCombatLevelHpDef(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int rangeNeed = Experience.getNextCombatLevelRange(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int magicNeed = Experience.getNextCombatLevelMagic(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + int prayerNeed = Experience.getNextCombatLevelPrayer(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, + magicLevel, rangeLevel, prayerLevel); + + // test combat level + assertEquals(68, combatLevel); + + // test attack/strength + assertEquals(3, meleeNeed); + + // test defence/hitpoints + assertEquals(4, hpDefNeed); + + // test ranged + assertEquals(3, rangeNeed); + + // test magic + assertEquals(3, magicNeed); + + // test prayer + assertEquals(8, prayerNeed); } } diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index c3ba022e05..109ec10d71 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -102,7 +102,7 @@ import net.runelite.client.ui.overlay.OverlayRenderer; import net.runelite.client.ui.overlay.WidgetOverlay; import net.runelite.client.ui.overlay.arrow.ArrowMinimapOverlay; import net.runelite.client.ui.overlay.arrow.ArrowWorldOverlay; -import net.runelite.client.ui.overlay.infobox.InfoBoxOverlay; +import net.runelite.client.ui.overlay.infobox.InfoBoxManager; import net.runelite.client.ui.overlay.tooltip.TooltipManager; import net.runelite.client.ui.overlay.tooltip.TooltipOverlay; import net.runelite.client.ui.overlay.worldmap.WorldMapOverlay; @@ -190,7 +190,7 @@ public class RuneLite private Provider commandManager; @Inject - private Provider infoBoxOverlay; + private Provider infoBoxManager; @Inject private Provider tooltipOverlay; @@ -511,6 +511,7 @@ public class RuneLite // Initialize chat colors chatMessageManager.get().loadColors(); + infoBoxManager.get(); overlayRenderer.get(); friendChatManager.get(); itemManager.get(); @@ -522,14 +523,12 @@ public class RuneLite playerManager.get(); chatboxPanelManager.get(); partyService.get(); - infoBoxOverlay.get(); eventBus.subscribe(GameStateChanged.class, this, hooks::onGameStateChanged); eventBus.subscribe(ScriptCallbackEvent.class, this, hooks::onScriptCallbackEvent); // Add core overlays WidgetOverlay.createOverlays(client).forEach(overlayManager::add); - overlayManager.add(infoBoxOverlay.get()); overlayManager.add(worldMapOverlay.get()); overlayManager.add(tooltipOverlay.get()); overlayManager.add(arrowWorldOverlay.get()); diff --git a/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java b/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java index b7072c8887..0e7d781337 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java @@ -400,12 +400,25 @@ public interface RuneLiteConfig extends Config return new Title(); } + @ConfigItem( + keyName = "infoBoxTextOutline", + name = "Outline infobox text", + description = "Draw a full outline instead of a simple shadow for infobox text", + position = 31, + titleSection = "infoboxTitle" + ) + default boolean infoBoxTextOutline() + { + return false; + } + @ConfigItem( keyName = "infoBoxVertical", name = "Display infoboxes vertically", description = "Toggles the infoboxes to display vertically", - position = 31, - titleSection = "infoboxTitle" + position = 32, + titleSection = "infoboxTitle", + hidden = true ) default boolean infoBoxVertical() { @@ -416,7 +429,7 @@ public interface RuneLiteConfig extends Config keyName = "infoBoxSize", name = "Infobox size", description = "Configures the size of each infobox in pixels", - position = 32, + position = 33, titleSection = "infoboxTitle" ) @Units(Units.PIXELS) @@ -429,7 +442,7 @@ public interface RuneLiteConfig extends Config keyName = "keybindsTitle", name = "Key binds", description = "", - position = 33 + position = 34 ) default Title keybindsTitle() { @@ -440,7 +453,7 @@ public interface RuneLiteConfig extends Config keyName = "sidebarToggleKey", name = "Sidebar Toggle Key", description = "The key that will toggle the sidebar (accepts modifiers)", - position = 34, + position = 35, titleSection = "keybindsTitle" ) default Keybind sidebarToggleKey() @@ -452,7 +465,7 @@ public interface RuneLiteConfig extends Config keyName = "panelToggleKey", name = "Plugin Panel Toggle Key", description = "The key that will toggle the current or last opened plugin panel (accepts modifiers)", - position = 35, + position = 36, titleSection = "keybindsTitle" ) default Keybind panelToggleKey() @@ -464,7 +477,7 @@ public interface RuneLiteConfig extends Config keyName = "blockExtraMouseButtons", name = "Block Extra Mouse Buttons", description = "Blocks extra mouse buttons (4 and above)", - position = 36, + position = 37, titleSection = "keybindsTitle" ) default boolean blockExtraMouseButtons() diff --git a/runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java b/runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java index 3696b65e08..91b0ef4f7c 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java +++ b/runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java @@ -187,6 +187,7 @@ public enum AgilityShortcut AL_KHARID_WINDOW(70, "Window", new WorldPoint(3293, 3158, 0), BROKEN_WALL_33344, BIG_WINDOW), GWD_SARADOMIN_ROPE_NORTH(70, "Rope Descent", new WorldPoint(2912, 5300, 0), NULL_26371, NULL_26561), GWD_SARADOMIN_ROPE_SOUTH(70, "Rope Descent", new WorldPoint(2951, 5267, 0), NULL_26375, NULL_26562), + GU_TANOTH_CRUMBLING_WALL(71, "Rocks", new WorldPoint(2545, 3032, 0), CRUMBLING_WALL_40355, ROCKS_40356), SLAYER_TOWER_ADVANCED_CHAIN_FIRST(71, "Spiked Chain (Floor 2)", new WorldPoint(3447, 3578, 0), SPIKEY_CHAIN ), SLAYER_TOWER_ADVANCED_CHAIN_SECOND(71, "Spiked Chain (Floor 3)", new WorldPoint(3446, 3576, 0), SPIKEY_CHAIN_16538), STRONGHOLD_SLAYER_CAVE_TUNNEL(72, "Tunnel", new WorldPoint(2431, 9806, 0), TUNNEL_30174, TUNNEL_30175), diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java index 303b2cb09f..c715a098ce 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java +++ b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java @@ -32,6 +32,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers; import java.awt.Color; import java.awt.image.BufferedImage; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -276,10 +277,10 @@ public class ItemManager /** * Look up an item's price * - * @param itemID item id - * @param ignoreUntradeableMap should the price returned ignore the {@link UntradeableItemMapping} + * @param itemID item id + * @param ignoreUntradeableMap should the price returned ignore items that are not tradeable for coins in regular way * @return item price - */ + * */ public int getItemPrice(int itemID, boolean ignoreUntradeableMap) { if (itemID == ItemID.COINS_995) @@ -291,31 +292,38 @@ public class ItemManager return 1000; } - ItemDefinition itemComposition = getItemDefinition(itemID); - if (itemComposition.getNote() != -1) + ItemDefinition itemDefinition = getItemDefinition(itemID); + if (itemDefinition.getNote() != -1) { - itemID = itemComposition.getLinkedNoteId(); + itemID = itemDefinition.getLinkedNoteId(); } itemID = WORN_ITEMS.getOrDefault(itemID, itemID); - if (!ignoreUntradeableMap) - { - UntradeableItemMapping p = UntradeableItemMapping.map(ItemVariationMapping.map(itemID)); - if (p != null) - { - return getItemPrice(p.getPriceID()) * p.getQuantity(); - } - } - int price = 0; - for (int mappedID : ItemMapping.map(itemID)) + + final Collection mappedItems = ItemMapping.map(itemID); + + if (mappedItems == null) { - ItemPrice ip = itemPrices.get(mappedID); + final ItemPrice ip = itemPrices.get(itemID); + if (ip != null) { price += ip.getPrice(); } } + else + { + for (final ItemMapping mappedItem : mappedItems) + { + if (ignoreUntradeableMap && mappedItem.isUntradeable()) + { + continue; + } + + price += getItemPrice(mappedItem.getTradeableItem(), ignoreUntradeableMap) * mappedItem.getQuantity(); + } + } return price; } diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemMapping.java b/runelite-client/src/main/java/net/runelite/client/game/ItemMapping.java index b7f59e0394..b5f0db7595 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/ItemMapping.java +++ b/runelite-client/src/main/java/net/runelite/client/game/ItemMapping.java @@ -28,12 +28,14 @@ package net.runelite.client.game; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import java.util.Collection; -import java.util.Collections; +import javax.annotation.Nullable; +import lombok.Getter; import static net.runelite.api.ItemID.*; /** * Converts untradeable items to it's tradeable counterparts */ +@Getter public enum ItemMapping { // Barrows equipment @@ -249,6 +251,9 @@ public enum ItemMapping ITEM_CRYSTAL_BOW(CRYSTAL_WEAPON_SEED, CRYSTAL_BOW, CRYSTAL_BOW_24123, CRYSTAL_BOW_INACTIVE), ITEM_CRYSTAL_HALBERD(CRYSTAL_WEAPON_SEED, CRYSTAL_HALBERD, CRYSTAL_HALBERD_24125, CRYSTAL_HALBERD_INACTIVE), ITEM_CRYSTAL_SHIELD(CRYSTAL_WEAPON_SEED, CRYSTAL_SHIELD, CRYSTAL_SHIELD_24127, CRYSTAL_SHIELD_INACTIVE), + ITEM_CRYSTAL_HELMET(CRYSTAL_ARMOUR_SEED, CRYSTAL_HELM, CRYSTAL_HELM_INACTIVE), + ITEM_CRYSTAL_LEGS(CRYSTAL_ARMOUR_SEED, 2L, CRYSTAL_LEGS, CRYSTAL_LEGS_INACTIVE), + ITEM_CRYSTAL_BODY(CRYSTAL_ARMOUR_SEED, 3L, CRYSTAL_BODY, CRYSTAL_BODY_INACTIVE), // Bird nests ITEM_BIRD_NEST(BIRD_NEST_5075, BIRD_NEST, BIRD_NEST_5071, BIRD_NEST_5072, BIRD_NEST_5073, BIRD_NEST_5074, BIRD_NEST_7413, BIRD_NEST_13653, BIRD_NEST_22798, BIRD_NEST_22800, CLUE_NEST_EASY, CLUE_NEST_MEDIUM, CLUE_NEST_HARD, CLUE_NEST_ELITE), @@ -256,11 +261,41 @@ public enum ItemMapping // Ancestral robes ITEM_ANCESTRAL_HAT(ANCESTRAL_HAT, TWISTED_ANCESTRAL_HAT), ITEM_ANCESTRAL_ROBE_TOP(ANCESTRAL_ROBE_TOP, TWISTED_ANCESTRAL_ROBE_TOP), - ITEM_ANCESTRAL_ROBE_BOTTOM(ANCESTRAL_ROBE_BOTTOM, TWISTED_ANCESTRAL_ROBE_BOTTOM); + ITEM_ANCESTRAL_ROBE_BOTTOM(ANCESTRAL_ROBE_BOTTOM, TWISTED_ANCESTRAL_ROBE_BOTTOM), - private static final Multimap MAPPINGS = HashMultimap.create(); + // Graceful + ITEM_MARK_OF_GRACE(AMYLASE_CRYSTAL, true, 10L, MARK_OF_GRACE), + ITEM_GRACEFUL_HOOD(MARK_OF_GRACE, true, 28L, GRACEFUL_HOOD), + ITEM_GRACEFUL_TOP(MARK_OF_GRACE, true, 44L, GRACEFUL_TOP), + ITEM_GRACEFUL_LEGS(MARK_OF_GRACE, true, 48L, GRACEFUL_LEGS), + ITEM_GRACEFUL_GLOVES(MARK_OF_GRACE, true, 24L, GRACEFUL_GLOVES), + ITEM_GRACEFUL_BOOTS(MARK_OF_GRACE, true, 32L, GRACEFUL_BOOTS), + ITEM_GRACEFUL_CAPE(MARK_OF_GRACE, true, 32L, GRACEFUL_CAPE), + + // 10 golden nuggets = 100 soft clay + ITEM_GOLDEN_NUGGET(SOFT_CLAY, true, 10L, GOLDEN_NUGGET), + ITEM_PROSPECTOR_HELMET(GOLDEN_NUGGET, true, 32L, PROSPECTOR_HELMET), + ITEM_PROSPECTOR_JACKET(GOLDEN_NUGGET, true, 48L, PROSPECTOR_JACKET), + ITEM_PROSPECTOR_LEGS(GOLDEN_NUGGET, true, 40L, PROSPECTOR_LEGS), + ITEM_PROSPECTOR_BOOTS(GOLDEN_NUGGET, true, 24L, PROSPECTOR_BOOTS), + + // 10 unidentified minerals = 100 soft clay + ITEM_UNIDENTIFIED_MINERALS(SOFT_CLAY, true, 10L, UNIDENTIFIED_MINERALS), + + // Converted to coins + ITEM_TATTERED_PAGE(COINS_995, true, 1000L, TATTERED_MOON_PAGE, TATTERED_SUN_PAGE, TATTERED_TEMPLE_PAGE), + ITEM_LONG_BONE(COINS_995, true, 1000L, LONG_BONE), + ITEM_CURVED_BONE(COINS_995, true, 2000L, CURVED_BONE), + ITEM_PERFECT_SHELL(COINS_995, true, 600L, PERFECT_SHELL), + ITEM_PERFECT_SNAIL_SHELL(COINS_995, true, 600L, PERFECT_SNAIL_SHELL), + ITEM_SNAIL_SHELL(COINS_995, true, 600L, SNAIL_SHELL), + ITEM_TORTOISE_SHELL(COINS_995, true, 250L, TORTOISE_SHELL); + + private static final Multimap MAPPINGS = HashMultimap.create(); private final int tradeableItem; private final int[] untradableItems; + private final long quantity; + private final boolean untradeable; static { @@ -268,15 +303,35 @@ public enum ItemMapping { for (int itemId : item.untradableItems) { - MAPPINGS.put(itemId, item.tradeableItem); + if (item.untradeable) + { + for (final Integer variation : ItemVariationMapping.getVariations(itemId)) + { + MAPPINGS.put(variation, item); + } + } + + MAPPINGS.put(itemId, item); } } } - ItemMapping(int tradeableItem, int... untradableItems) + ItemMapping(int tradeableItem, boolean untradeable, long quantity, int... untradableItems) { this.tradeableItem = tradeableItem; this.untradableItems = untradableItems; + this.quantity = quantity; + this.untradeable = untradeable; + } + + ItemMapping(int tradeableItem, long quantity, int... untradableItems) + { + this(tradeableItem, false, quantity, untradableItems); + } + + ItemMapping(int tradeableItem, int... untradableItems) + { + this(tradeableItem, 1L, untradableItems); } /** @@ -285,36 +340,19 @@ public enum ItemMapping * @param itemId the item id * @return the collection */ - public static Collection map(int itemId) + @Nullable + public static Collection map(int itemId) { - final Collection mapping = MAPPINGS.get(itemId); + final Collection mapping = MAPPINGS.get(itemId); - if (mapping == null || mapping.isEmpty()) + if (mapping.isEmpty()) { - return Collections.singleton(itemId); + return null; } return mapping; } - /** - * Map an item from its untradeable version to its tradeable version - * - * @param itemId - * @return - */ - public static int mapFirst(int itemId) - { - final Collection mapping = MAPPINGS.get(itemId); - - if (mapping == null || mapping.isEmpty()) - { - return itemId; - } - - return mapping.iterator().next(); - } - public static boolean isMapped(int itemId) { return MAPPINGS.containsValue(itemId); diff --git a/runelite-client/src/main/java/net/runelite/client/game/UntradeableItemMapping.java b/runelite-client/src/main/java/net/runelite/client/game/UntradeableItemMapping.java deleted file mode 100644 index 5dd393acb1..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/game/UntradeableItemMapping.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2018, TheStonedTurtle - * 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.game; - -import com.google.common.collect.ImmutableMap; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import net.runelite.api.ItemID; - -@Getter -@RequiredArgsConstructor -public enum UntradeableItemMapping -{ - MARK_OF_GRACE(ItemID.MARK_OF_GRACE, 10, ItemID.AMYLASE_CRYSTAL), - GRACEFUL_HOOD(ItemID.GRACEFUL_HOOD, 28, ItemID.MARK_OF_GRACE), - GRACEFUL_TOP(ItemID.GRACEFUL_TOP, 44, ItemID.MARK_OF_GRACE), - GRACEFUL_LEGS(ItemID.GRACEFUL_LEGS, 48, ItemID.MARK_OF_GRACE), - GRACEFUL_GLOVES(ItemID.GRACEFUL_GLOVES, 24, ItemID.MARK_OF_GRACE), - GRACEFUL_BOOTS(ItemID.GRACEFUL_BOOTS, 32, ItemID.MARK_OF_GRACE), - GRACEFUL_CAPE(ItemID.GRACEFUL_CAPE, 32, ItemID.MARK_OF_GRACE), - - // 10 golden nuggets = 100 soft clay - GOLDEN_NUGGET(ItemID.GOLDEN_NUGGET, 10, ItemID.SOFT_CLAY), - PROSPECTOR_HELMET(ItemID.PROSPECTOR_HELMET, 32, ItemID.GOLDEN_NUGGET), - PROSPECTOR_JACKET(ItemID.PROSPECTOR_JACKET, 48, ItemID.GOLDEN_NUGGET), - PROSPECTOR_LEGS(ItemID.PROSPECTOR_LEGS, 40, ItemID.GOLDEN_NUGGET), - PROSPECTOR_BOOTS(ItemID.PROSPECTOR_BOOTS, 24, ItemID.GOLDEN_NUGGET), - - CRYSTAL_HELMET(ItemID.CRYSTAL_HELM, 1, ItemID.CRYSTAL_ARMOUR_SEED), - CRYSTAL_HELMET_INACTIVE(ItemID.CRYSTAL_HELM_INACTIVE, 1, ItemID.CRYSTAL_ARMOUR_SEED), - CRYSTAL_LEGS(ItemID.CRYSTAL_LEGS, 2, ItemID.CRYSTAL_ARMOUR_SEED), - CRYSTAL_LEGS_INACTIVE(ItemID.CRYSTAL_LEGS_INACTIVE, 2, ItemID.CRYSTAL_ARMOUR_SEED), - CRYSTAL_BODY(ItemID.CRYSTAL_BODY, 3, ItemID.CRYSTAL_ARMOUR_SEED), - CRYSTAL_BODY_INACTIVE(ItemID.CRYSTAL_BODY_INACTIVE, 3, ItemID.CRYSTAL_ARMOUR_SEED), - - TATTERED_MOON_PAGE(ItemID.TATTERED_MOON_PAGE, 1000, ItemID.COINS_995), - TATTERED_SUN_PAGE(ItemID.TATTERED_SUN_PAGE, 1000, ItemID.COINS_995), - TATTERED_TEMPLE_PAGE(ItemID.TATTERED_TEMPLE_PAGE, 1000, ItemID.COINS_995), - - LONG_BONE(ItemID.LONG_BONE, 1000, ItemID.COINS_995), - CURVED_BONE(ItemID.CURVED_BONE, 2000, ItemID.COINS_995), - PERFECT_SHELL(ItemID.PERFECT_SHELL, 600, ItemID.COINS_995), - PERFECT_SNAIL_SHELL(ItemID.PERFECT_SNAIL_SHELL, 600, ItemID.COINS_995), - SNAIL_SHELL(ItemID.SNAIL_SHELL, 600, ItemID.COINS_995), - TORTOISE_SHELL(ItemID.TORTOISE_SHELL, 250, ItemID.COINS_995); - - private static final ImmutableMap UNTRADEABLE_RECLAIM_MAP; - - private final int itemID; - private final int quantity; - private final int priceID; - - static - { - ImmutableMap.Builder map = ImmutableMap.builder(); - for (UntradeableItemMapping p : values()) - { - map.put(p.getItemID(), p); - } - UNTRADEABLE_RECLAIM_MAP = map.build(); - } - - public static UntradeableItemMapping map(int itemId) - { - return UNTRADEABLE_RECLAIM_MAP.get(itemId); - } -} - diff --git a/runelite-client/src/main/java/net/runelite/client/input/KeyManager.java b/runelite-client/src/main/java/net/runelite/client/input/KeyManager.java index b464e2fc84..1de44da134 100644 --- a/runelite-client/src/main/java/net/runelite/client/input/KeyManager.java +++ b/runelite-client/src/main/java/net/runelite/client/input/KeyManager.java @@ -30,10 +30,12 @@ import java.util.concurrent.CopyOnWriteArrayList; import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.GameState; @Singleton +@Slf4j public class KeyManager { private final Client client; @@ -50,13 +52,18 @@ public class KeyManager { if (!keyListeners.contains(keyListener)) { + log.debug("Registering key listener: {}", keyListener); keyListeners.add(keyListener); } } public void unregisterKeyListener(KeyListener keyListener) { - keyListeners.remove(keyListener); + final boolean unregistered = keyListeners.remove(keyListener); + if (unregistered) + { + log.debug("Unregistered key listener: {}", keyListener); + } } public void processKeyPressed(KeyEvent keyEvent) @@ -73,9 +80,12 @@ public class KeyManager continue; } + log.trace("Processing key pressed {} for key listener {}", keyEvent.paramString(), keyListener); + keyListener.keyPressed(keyEvent); if (keyEvent.isConsumed()) { + log.debug("Consuming key pressed {} for key listener {}", keyEvent.paramString(), keyListener); break; } } @@ -95,9 +105,12 @@ public class KeyManager continue; } + log.trace("Processing key released {} for key listener {}", keyEvent.paramString(), keyListener); + keyListener.keyReleased(keyEvent); if (keyEvent.isConsumed()) { + log.debug("Consuming key released {} for listener {}", keyEvent.paramString(), keyListener); break; } } @@ -117,9 +130,12 @@ public class KeyManager continue; } + log.trace("Processing key typed {} for key listener {}", keyEvent.paramString(), keyListener); + keyListener.keyTyped(keyEvent); if (keyEvent.isConsumed()) { + log.debug("Consuming key typed {} for key listener {}", keyEvent.paramString(), keyListener); break; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListPanel.java index d1bc1f6484..e2b8d13568 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListPanel.java @@ -24,6 +24,7 @@ */ package net.runelite.client.plugins.config; +import com.google.common.base.Splitter; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; @@ -102,6 +103,7 @@ public class PluginListPanel extends PluginPanel private static final String RUNELITE_GROUP_NAME = RuneLiteConfig.class.getAnnotation(ConfigGroup.class).value(); private static final String PINNED_PLUGINS_CONFIG_KEY = "pinnedPlugins"; + private static final Splitter SPLITTER = Splitter.on(" ").trimResults().omitEmptyStrings(); private static final List CATEGORY_TAGS = List.of( "Combat", "Chat", @@ -443,10 +445,9 @@ public class PluginListPanel extends PluginPanel } else { - final String[] searchTerms = text.toLowerCase().split(" "); pluginList.forEach(listItem -> { - if (pinned == listItem.isPinned() && Text.matchesSearchTerms(searchTerms, listItem.getKeywords())) + if (pinned == listItem.isPinned() && Text.matchesSearchTerms(SPLITTER.split(text.toLowerCase()), listItem.getKeywords())) { if (openOSRSConfig.pluginSortMode() == OpenOSRSConfig.SortStyle.ALPHABETICALLY || (!openOSRSConfig.enableCategories() && (openOSRSConfig.pluginSortMode() != OpenOSRSConfig.SortStyle.REPOSITORY))) { diff --git a/runelite-client/src/main/java/net/runelite/client/ui/ContainableFrame.java b/runelite-client/src/main/java/net/runelite/client/ui/ContainableFrame.java index a8d9d8aedf..5c0ec3ca17 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/ContainableFrame.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/ContainableFrame.java @@ -24,6 +24,7 @@ */ package net.runelite.client.ui; +import com.google.common.annotations.VisibleForTesting; import java.awt.Frame; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; @@ -49,6 +50,7 @@ public class ContainableFrame extends JFrame NEVER } + private static final int SCREEN_EDGE_CLOSE_DISTANCE = 40; private static boolean jdk8231564; static @@ -56,9 +58,7 @@ public class ContainableFrame extends JFrame try { String javaVersion = System.getProperty("java.version"); - String[] s = javaVersion.split("\\."); - int major = Integer.parseInt(s[0]), minor = Integer.parseInt(s[1]), patch = Integer.parseInt(s[2]); - jdk8231564 = major > 11 || (major == 11 && minor > 0) || (major == 11 && minor == 0 && patch >= 8); + jdk8231564 = jdk8231564(javaVersion); } catch (Exception ex) { @@ -66,7 +66,23 @@ public class ContainableFrame extends JFrame } } - private static final int SCREEN_EDGE_CLOSE_DISTANCE = 40; + @VisibleForTesting + static boolean jdk8231564(String javaVersion) + { + int idx = javaVersion.indexOf('_'); + if (idx != -1) + { + javaVersion = javaVersion.substring(0, idx); + } + String[] s = javaVersion.split("\\."); + int major = Integer.parseInt(s[0]), minor = Integer.parseInt(s[1]), patch = Integer.parseInt(s[2]); + if (major == 12 || major == 13 || major == 14) + { + // These versions are since EOL & do not include JDK-8231564 + return false; + } + return major > 11 || (major == 11 && minor > 0) || (major == 11 && minor == 0 && patch >= 8); + } @Setter private ExpandResizeType expandResizeType; diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java index bfb4a1223b..f74a4366a6 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java @@ -30,6 +30,7 @@ import java.awt.Rectangle; import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; +import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import net.runelite.client.plugins.Plugin; @@ -52,6 +53,13 @@ public abstract class Overlay implements LayoutableRenderableEntity private boolean resizable; private boolean resettable = true; + /** + * Whether this overlay can be dragged onto other overlays & have + * other overlays dragged onto it. + */ + @Setter(AccessLevel.PROTECTED) + private boolean dragTargetable; + protected Overlay() { plugin = null; @@ -75,4 +83,17 @@ public abstract class Overlay implements LayoutableRenderableEntity public void onMouseOver() { } + + /** + * Called when an overlay is dragged onto this, if dragTargetable is true. + * Return true to consume the mouse event and prevent the other + * overlay from being moved + * + * @param other the overlay being dragged + * @return + */ + public boolean onDrag(Overlay other) + { + return false; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java index 2b1efb53f0..0d392bea04 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java @@ -49,6 +49,7 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.GameState; +import net.runelite.api.KeyCode; import net.runelite.api.MenuEntry; import net.runelite.api.events.BeforeRender; import net.runelite.api.events.ClientTick; @@ -80,6 +81,7 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener private static final Color SNAP_CORNER_ACTIVE_COLOR = new Color(0, 255, 0, 100); private static final Color MOVING_OVERLAY_COLOR = new Color(255, 255, 0, 100); private static final Color MOVING_OVERLAY_ACTIVE_COLOR = new Color(255, 255, 0, 200); + private static final Color MOVING_OVERLAY_TARGET_COLOR = Color.RED; private static final Color MOVING_OVERLAY_RESIZING_COLOR = new Color(255, 0, 255, 200); private final Client client; private final OverlayManager overlayManager; @@ -90,11 +92,11 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener private final Point overlayOffset = new Point(); private final Point mousePosition = new Point(); private Overlay currentManagedOverlay; + private Overlay dragTargetOverlay; private Rectangle currentManagedBounds; private boolean inOverlayManagingMode; private boolean inOverlayResizingMode; private boolean inOverlayDraggingMode; - private boolean inMenuEntryMode; private boolean startedMovingOverlay; private MenuEntry[] menuEntries; @@ -141,7 +143,6 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener resetOverlayManagementMode(); } - inMenuEntryMode = false; menuEntries = null; } } @@ -153,7 +154,8 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener return; } - if (!inMenuEntryMode && runeLiteConfig.menuEntryShift()) + final boolean shift = client.isKeyPressed(KeyCode.KC_SHIFT); + if (!shift && runeLiteConfig.menuEntryShift()) { return; } @@ -313,15 +315,28 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener { if (inOverlayManagingMode) { + Color boundsColor; if (inOverlayResizingMode && currentManagedOverlay == overlay) { - graphics.setColor(MOVING_OVERLAY_RESIZING_COLOR); + boundsColor = MOVING_OVERLAY_RESIZING_COLOR; + } + else if (inOverlayDraggingMode && currentManagedOverlay == overlay) + { + boundsColor = MOVING_OVERLAY_ACTIVE_COLOR; + } + else if (inOverlayDraggingMode && overlay.isDragTargetable() && currentManagedOverlay.isDragTargetable() + && currentManagedOverlay.getBounds().intersects(bounds)) + { + boundsColor = MOVING_OVERLAY_TARGET_COLOR; + assert currentManagedOverlay != overlay; + dragTargetOverlay = overlay; } else { - graphics.setColor(inOverlayDraggingMode && currentManagedOverlay == overlay ? MOVING_OVERLAY_ACTIVE_COLOR : MOVING_OVERLAY_COLOR); + boundsColor = MOVING_OVERLAY_COLOR; } + graphics.setColor(boundsColor); graphics.draw(bounds); graphics.setPaint(paint); } @@ -402,6 +417,12 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener { for (Overlay overlay : overlayManager.getOverlays()) { + if (overlay.getPosition() == OverlayPosition.DYNAMIC || overlay.getPosition() == OverlayPosition.TOOLTIP) + { + // never allow moving dynamic or tooltip overlays + continue; + } + final Rectangle bounds = overlay.getBounds(); if (bounds.contains(mousePoint)) { @@ -472,6 +493,12 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener return mouseEvent; } + if (dragTargetOverlay != null && !currentManagedOverlay.getBounds().intersects(dragTargetOverlay.getBounds())) + { + // No longer over drag target + dragTargetOverlay = null; + } + final Rectangle canvasRect = new Rectangle(client.getRealDimensions()); if (!canvasRect.contains(p)) @@ -598,7 +625,17 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener mousePosition.setLocation(-1, -1); - // do not snapcorner detached overlays + if (dragTargetOverlay != null) + { + if (dragTargetOverlay.onDrag(currentManagedOverlay)) + { + mouseEvent.consume(); + resetOverlayManagementMode(); + return mouseEvent; + } + } + + // Check if the overlay is over a snapcorner and move it if so, unless it is a detached overlay if (currentManagedOverlay.getPosition() != OverlayPosition.DETACHED && inOverlayDraggingMode) { final OverlayBounds snapCorners = this.snapCorners.translated(-SNAP_CORNER_SIZE.width, -SNAP_CORNER_SIZE.height); @@ -640,11 +677,6 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener { inOverlayManagingMode = true; } - - if (e.isShiftDown() && runeLiteConfig.menuEntryShift()) - { - inMenuEntryMode = true; - } } @Override @@ -655,11 +687,6 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener inOverlayManagingMode = false; resetOverlayManagementMode(); } - - if (!e.isShiftDown()) - { - inMenuEntryMode = false; - } } private void safeRender(Client client, Overlay overlay, OverlayLayer layer, Graphics2D graphics, Point point) @@ -750,6 +777,7 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener inOverlayResizingMode = false; inOverlayDraggingMode = false; currentManagedOverlay = null; + dragTargetOverlay = null; currentManagedBounds = null; clientUI.setCursor(clientUI.getDefaultCursor()); } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/InfoBoxComponent.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/InfoBoxComponent.java index 33e053ccbe..2e203ba5f8 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/InfoBoxComponent.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/InfoBoxComponent.java @@ -53,6 +53,7 @@ public class InfoBoxComponent implements LayoutableRenderableEntity private Dimension preferredSize = new Dimension(DEFAULT_SIZE, DEFAULT_SIZE); private String text; private Color color = Color.WHITE; + private boolean outline; private Color backgroundColor = ComponentConstants.STANDARD_BACKGROUND_COLOR; private BufferedImage image; @Getter @@ -93,6 +94,7 @@ public class InfoBoxComponent implements LayoutableRenderableEntity { final TextComponent textComponent = new TextComponent(); textComponent.setColor(color); + textComponent.setOutline(outline); textComponent.setText(text); textComponent.setPosition(new Point(baseX + ((size - metrics.stringWidth(text)) / 2), baseY + size - SEPARATOR)); textComponent.render(graphics); diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/ProgressBarComponent.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/ProgressBarComponent.java index 01f52d6ad0..8ad38171ae 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/ProgressBarComponent.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/ProgressBarComponent.java @@ -114,7 +114,7 @@ public class ProgressBarComponent implements LayoutableRenderableEntity // Draw bar graphics.setColor(backgroundColor); - graphics.fillRect(barX, barY, width, height); + graphics.fillRect(barX + progressFill, barY, width - progressFill, height); graphics.setColor(foregroundColor); graphics.fillRect(barX, barY, progressFill, height); diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBox.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBox.java index 364979cf36..58459dff23 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBox.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBox.java @@ -81,4 +81,11 @@ public abstract class InfoBox { return false; } + + public String getName() + { + // Use a combination of plugin name and infobox implementation name to try and make each infobox as unique + // as possible by default + return plugin.getClass().getSimpleName() + "_" + getClass().getSimpleName(); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxManager.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxManager.java index 6756197705..420c6947ea 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxManager.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxManager.java @@ -24,43 +24,112 @@ */ package net.runelite.client.ui.overlay.infobox; +import com.google.common.base.Strings; import com.google.common.collect.ComparisonChain; import java.awt.Graphics; import java.awt.image.BufferedImage; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.Map; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.MenuOpcode; +import net.runelite.client.config.ConfigManager; import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.eventbus.EventBus; import net.runelite.client.events.ConfigChanged; +import net.runelite.client.events.InfoBoxMenuClicked; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ui.overlay.OverlayMenuEntry; +import net.runelite.client.ui.overlay.components.ComponentOrientation; +import net.runelite.client.ui.overlay.tooltip.TooltipManager; import net.runelite.client.util.AsyncBufferedImage; @Singleton @Slf4j public class InfoBoxManager { - private final List infoBoxes = new CopyOnWriteArrayList<>(); + private static final String INFOBOXLAYER_KEY = "infoboxlayer"; + private static final String INFOBOXOVERLAY_KEY = "infoboxoverlay"; + private static final String INFOBOXOVERLAY_ORIENTATION_PREFIX = "orient_"; + private static final String DEFAULT_LAYER = "InfoBoxOverlay"; + + private static final String DETACH = "Detach"; + private static final String FLIP = "Flip"; + private static final String DELETE = "Delete"; + + private static final OverlayMenuEntry DETACH_ME = new OverlayMenuEntry(MenuOpcode.RUNELITE_INFOBOX, DETACH, "InfoBox"); + private static final OverlayMenuEntry FLIP_ME = new OverlayMenuEntry(MenuOpcode.RUNELITE_INFOBOX, FLIP, "InfoBox Group"); + private static final OverlayMenuEntry DELETE_ME = new OverlayMenuEntry(MenuOpcode.RUNELITE_INFOBOX, DELETE, "InfoBox Group"); + + private final Map layers = new ConcurrentHashMap<>(); + private final RuneLiteConfig runeLiteConfig; + private final TooltipManager tooltipManager; + private final Client client; + private final EventBus eventBus; + private final OverlayManager overlayManager; + private final ConfigManager configManager; @Inject - private InfoBoxManager(final RuneLiteConfig runeLiteConfig, final EventBus eventbus) + private InfoBoxManager(final RuneLiteConfig runeLiteConfig, + final TooltipManager tooltipManager, + final Client client, + final EventBus eventBus, + final OverlayManager overlayManager, + final ConfigManager configManager, + final EventBus eventbus) { this.runeLiteConfig = runeLiteConfig; + this.tooltipManager = tooltipManager; + this.client = client; + this.eventBus = eventBus; + this.overlayManager = overlayManager; + this.configManager = configManager; eventbus.subscribe(ConfigChanged.class, this, this::onConfigChanged); + eventbus.subscribe(InfoBoxMenuClicked.class, this, this::onInfoBoxMenuClicked); } private void onConfigChanged(ConfigChanged event) { if (event.getGroup().equals("runelite") && event.getKey().equals("infoBoxSize")) { - infoBoxes.forEach(this::updateInfoBoxImage); + + layers.values().forEach(l -> l.getInfoBoxes().forEach(this::updateInfoBoxImage)); + } + } + + private void onInfoBoxMenuClicked(InfoBoxMenuClicked event) + { + if (DETACH.equals(event.getEntry().getOption())) + { + // The layer name doesn't matter as long as it is unique + splitInfobox(event.getInfoBox().getName() + "_" + System.currentTimeMillis(), event.getInfoBox()); + } + else if (FLIP.equals(event.getEntry().getOption())) + { + InfoBoxOverlay infoBoxOverlay = layers.get(getLayer(event.getInfoBox())); + ComponentOrientation newOrientation = infoBoxOverlay.flip(); + setOrientation(infoBoxOverlay.getName(), newOrientation); + } + else if (DELETE.equals(event.getEntry().getOption())) + { + // This is just a merge into the default layer + InfoBoxOverlay source = layers.get(getLayer(event.getInfoBox())); + InfoBoxOverlay dest = layers.computeIfAbsent(DEFAULT_LAYER, this::makeOverlay); + if (source != dest) + { + mergeInfoBoxes(source, dest); + } } } @@ -71,14 +140,25 @@ public class InfoBoxManager updateInfoBoxImage(infoBox); + String layerName = getLayer(infoBox); + InfoBoxOverlay overlay = layers.computeIfAbsent(layerName, this::makeOverlay); + List menuEntries = infoBox.getMenuEntries(); + menuEntries.add(DETACH_ME); + menuEntries.add(FLIP_ME); + if (!layerName.equals(DEFAULT_LAYER)) + { + // Non default-group infoboxes have a delete option to delete the group + menuEntries.add(DELETE_ME); + } + synchronized (this) { - int idx = findInsertionIndex(infoBoxes, infoBox, (b1, b2) -> ComparisonChain + int idx = findInsertionIndex(overlay.getInfoBoxes(), infoBox, (b1, b2) -> ComparisonChain .start() .compare(b1.getPriority(), b2.getPriority()) .compare(b1.getPlugin().getName(), b2.getPlugin().getName()) .result()); - infoBoxes.add(idx, infoBox); + overlay.getInfoBoxes().add(idx, infoBox); } BufferedImage image = infoBox.getImage(); @@ -92,28 +172,40 @@ public class InfoBoxManager public synchronized void removeInfoBox(InfoBox infoBox) { - if (infoBoxes.remove(infoBox)) + if (infoBox == null) + { + return; + } + + if (layers.get(getLayer(infoBox)).getInfoBoxes().remove(infoBox)) { log.debug("Removed InfoBox {}", infoBox); } + + infoBox.getMenuEntries().remove(DETACH_ME); + infoBox.getMenuEntries().remove(FLIP_ME); + infoBox.getMenuEntries().remove(DELETE_ME); } public synchronized void removeIf(Predicate filter) { - if (infoBoxes.removeIf(filter)) + for (InfoBoxOverlay overlay : layers.values()) { - log.debug("Removed InfoBoxes for filter {}", filter); + if (overlay.getInfoBoxes().removeIf(filter)) + { + log.debug("Removed InfoBoxes for filter {} from {}", filter, overlay); + } } } public List getInfoBoxes() { - return Collections.unmodifiableList(infoBoxes); + return layers.values().stream().map(InfoBoxOverlay::getInfoBoxes).flatMap(Collection::stream).collect(Collectors.toList()); } public synchronized void cull() { - infoBoxes.removeIf(InfoBox::cull); + layers.values().forEach(l -> l.getInfoBoxes().removeIf(InfoBox::cull)); } public void updateInfoBoxImage(final InfoBox infoBox) @@ -152,9 +244,144 @@ public class InfoBoxManager infoBox.setScaledImage(resultImage); } + private InfoBoxOverlay makeOverlay(String name) + { + ComponentOrientation orientation = getOrientation(name); + if (orientation == null) + { + if (name.equals(DEFAULT_LAYER)) + { + // Fall back to old orientation config option + orientation = runeLiteConfig.infoBoxVertical() ? ComponentOrientation.VERTICAL : ComponentOrientation.HORIZONTAL; + setOrientation(name, orientation); + } + else + { + // Default infobox orientation + orientation = ComponentOrientation.HORIZONTAL; + } + } + + InfoBoxOverlay infoBoxOverlay = new InfoBoxOverlay( + this, + tooltipManager, + client, + runeLiteConfig, + eventBus, + name, + orientation); + overlayManager.add(infoBoxOverlay); + + return infoBoxOverlay; + } + + private void removeOverlay(InfoBoxOverlay overlay) + { + unsetOrientation(overlay.getName()); + eventBus.unregister(overlay); + overlayManager.remove(overlay); + layers.remove(overlay.getName()); + } + + private synchronized void splitInfobox(String newLayer, InfoBox infoBox) + { + String layer = getLayer(infoBox); + InfoBoxOverlay oldOverlay = layers.get(layer); + // Find all infoboxes with the same name, as they are all within the same group and so move at once. + Collection filtered = oldOverlay.getInfoBoxes().stream() + .filter(i -> i.getName().equals(infoBox.getName())).collect(Collectors.toList()); + + oldOverlay.getInfoBoxes().removeAll(filtered); + if (oldOverlay.getInfoBoxes().isEmpty()) + { + log.debug("Deleted layer: {}", oldOverlay.getName()); + removeOverlay(oldOverlay); + } + + InfoBoxOverlay newOverlay = layers.computeIfAbsent(newLayer, this::makeOverlay); + newOverlay.getInfoBoxes().addAll(filtered); + + // Adjust config for new infoboxes + for (InfoBox i : filtered) + { + setLayer(i, newLayer); + + if (!i.getMenuEntries().contains(DELETE_ME)) + { + i.getMenuEntries().add(DELETE_ME); + } + } + + log.debug("Moving infobox named {} (layer {}) to layer {}: {} boxes", infoBox.getName(), layer, newLayer, filtered.size()); + } + + public synchronized void mergeInfoBoxes(InfoBoxOverlay source, InfoBoxOverlay dest) + { + Collection infoBoxesToMove = source.getInfoBoxes(); + boolean isDefault = dest.getName().equals(DEFAULT_LAYER); + + log.debug("Merging InfoBoxes from {} into {} ({} boxes)", source.getName(), dest.getName(), infoBoxesToMove.size()); + + for (InfoBox infoBox : infoBoxesToMove) + { + setLayer(infoBox, dest.getName()); + + if (isDefault) + { + infoBox.getMenuEntries().remove(DELETE_ME); + } + } + + dest.getInfoBoxes().addAll(infoBoxesToMove); + source.getInfoBoxes().clear(); + + // remove source + removeOverlay(source); + log.debug("Deleted layer: {}", source.getName()); + } + + private String getLayer(InfoBox infoBox) + { + String name = configManager.getConfiguration(INFOBOXLAYER_KEY, infoBox.getName()); + if (Strings.isNullOrEmpty(name)) + { + return DEFAULT_LAYER; + } + + return name; + } + + private void setLayer(InfoBox infoBox, String layer) + { + if (layer.equals(DEFAULT_LAYER)) + { + configManager.unsetConfiguration(INFOBOXLAYER_KEY, infoBox.getName()); + } + else + { + configManager.setConfiguration(INFOBOXLAYER_KEY, infoBox.getName(), layer); + } + } + + ComponentOrientation getOrientation(String name) + { + return configManager.getConfiguration(INFOBOXOVERLAY_KEY, INFOBOXOVERLAY_ORIENTATION_PREFIX + name, ComponentOrientation.class); + } + + void setOrientation(String name, ComponentOrientation orientation) + { + configManager.setConfiguration(INFOBOXOVERLAY_KEY, INFOBOXOVERLAY_ORIENTATION_PREFIX + name, orientation); + } + + void unsetOrientation(String name) + { + configManager.unsetConfiguration(INFOBOXOVERLAY_KEY, INFOBOXOVERLAY_ORIENTATION_PREFIX + name); + } + /** * Find insertion point for the given key into the given sorted list. If key already exists in the list, * return the index after the last occurrence. + * * @param list * @param key * @param c diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxOverlay.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxOverlay.java index 9b40fb2dc6..a6351e62a3 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxOverlay.java @@ -33,14 +33,17 @@ import java.awt.Point; import java.awt.Rectangle; import java.util.Collections; import java.util.List; -import javax.inject.Inject; +import java.util.concurrent.CopyOnWriteArrayList; import javax.inject.Singleton; +import lombok.Getter; +import lombok.NonNull; import net.runelite.api.Client; import net.runelite.api.MenuOpcode; import net.runelite.api.events.MenuOptionClicked; import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.eventbus.EventBus; import net.runelite.client.events.InfoBoxMenuClicked; +import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayMenuEntry; import net.runelite.client.ui.overlay.OverlayPanel; import net.runelite.client.ui.overlay.OverlayPosition; @@ -61,24 +64,33 @@ public class InfoBoxOverlay extends OverlayPanel private final Client client; private final RuneLiteConfig config; private final EventBus eventBus; + private final String name; + private ComponentOrientation orientation; + + @Getter + private final List infoBoxes = new CopyOnWriteArrayList<>(); private InfoBoxComponent hoveredComponent; - @Inject - private InfoBoxOverlay( + InfoBoxOverlay( InfoBoxManager infoboxManager, TooltipManager tooltipManager, Client client, RuneLiteConfig config, - EventBus eventBus) + EventBus eventBus, + String name, + @NonNull ComponentOrientation orientation) { this.tooltipManager = tooltipManager; this.infoboxManager = infoboxManager; this.client = client; this.config = config; this.eventBus = eventBus; + this.name = name; + this.orientation = orientation; setPosition(OverlayPosition.TOP_LEFT); setClearChildren(false); + setDragTargetable(true); panelComponent.setWrap(true); panelComponent.setBackgroundColor(null); @@ -88,11 +100,15 @@ public class InfoBoxOverlay extends OverlayPanel eventBus.subscribe(MenuOptionClicked.class, this, this::onMenuOptionClicked); } + @Override + public String getName() + { + return this.name; + } + @Override public Dimension render(Graphics2D graphics) { - final List infoBoxes = infoboxManager.getInfoBoxes(); - final boolean menuOpen = client.isMenuOpen(); if (!menuOpen) { @@ -107,9 +123,7 @@ public class InfoBoxOverlay extends OverlayPanel // Set preferred size to the size of DEFAULT_WRAP_COUNT infoboxes, including the padding - which is applied // to the last infobox prior to wrapping too. panelComponent.setPreferredSize(new Dimension(DEFAULT_WRAP_COUNT * (config.infoBoxSize() + GAP), DEFAULT_WRAP_COUNT * (config.infoBoxSize() + GAP))); - panelComponent.setOrientation(config.infoBoxVertical() - ? ComponentOrientation.VERTICAL - : ComponentOrientation.HORIZONTAL); + panelComponent.setOrientation(orientation); for (InfoBox box : infoBoxes) { @@ -127,6 +141,7 @@ public class InfoBoxOverlay extends OverlayPanel { infoBoxComponent.setColor(color); } + infoBoxComponent.setOutline(config.infoBoxTextOutline()); infoBoxComponent.setImage(box.getScaledImage()); infoBoxComponent.setTooltip(box.getTooltip()); infoBoxComponent.setPreferredSize(new Dimension(config.infoBoxSize(), config.infoBoxSize())); @@ -177,7 +192,7 @@ public class InfoBoxOverlay extends OverlayPanel public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked) { - if (menuOptionClicked.getMenuOpcode() != MenuOpcode.RUNELITE_INFOBOX) + if (menuOptionClicked.getMenuOpcode() != MenuOpcode.RUNELITE_INFOBOX || hoveredComponent == null) { return; } @@ -187,4 +202,21 @@ public class InfoBoxOverlay extends OverlayPanel .filter(me -> me.getOption().equals(menuOptionClicked.getOption())) .findAny().ifPresent(overlayMenuEntry -> eventBus.post(InfoBoxMenuClicked.class, new InfoBoxMenuClicked(overlayMenuEntry, infoBox))); } + + @Override + public boolean onDrag(Overlay source) + { + if (!(source instanceof InfoBoxOverlay)) + { + return false; + } + + infoboxManager.mergeInfoBoxes((InfoBoxOverlay) source, this); + return true; + } + + ComponentOrientation flip() + { + return orientation = orientation == ComponentOrientation.HORIZONTAL ? ComponentOrientation.VERTICAL : ComponentOrientation.HORIZONTAL; + } } diff --git a/runelite-client/src/test/java/net/runelite/client/ui/ContainableFrameTest.java b/runelite-client/src/test/java/net/runelite/client/ui/ContainableFrameTest.java new file mode 100644 index 0000000000..8ee1b71a80 --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/ui/ContainableFrameTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020, Adam + * 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.ui; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.Test; + +public class ContainableFrameTest +{ + @Test + public void testJdk8231564() + { + assertTrue(ContainableFrame.jdk8231564("11.0.8")); + assertFalse(ContainableFrame.jdk8231564("11.0.7")); + assertFalse(ContainableFrame.jdk8231564("1.8.0_261")); + assertFalse(ContainableFrame.jdk8231564("12.0.0")); + assertFalse(ContainableFrame.jdk8231564("13.0.0")); + assertFalse(ContainableFrame.jdk8231564("14.0.0")); + } +} \ No newline at end of file diff --git a/runelite-client/src/test/java/net/runelite/client/ui/overlay/infobox/InfoBoxManagerTest.java b/runelite-client/src/test/java/net/runelite/client/ui/overlay/infobox/InfoBoxManagerTest.java index a2eb380d3f..7e53107523 100644 --- a/runelite-client/src/test/java/net/runelite/client/ui/overlay/infobox/InfoBoxManagerTest.java +++ b/runelite-client/src/test/java/net/runelite/client/ui/overlay/infobox/InfoBoxManagerTest.java @@ -32,6 +32,8 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.client.config.ConfigManager; import net.runelite.client.config.OpenOSRSConfig; import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.plugins.Plugin; @@ -58,6 +60,14 @@ public class InfoBoxManagerTest @Bind private OpenOSRSConfig openOSRSConfig; + @Mock + @Bind + private ConfigManager configManager; + + @Mock + @Bind + private Client client; + @Before public void before() { diff --git a/runelite-client/src/test/java/net/runelite/client/util/ColorUtilTest.java b/runelite-client/src/test/java/net/runelite/client/util/ColorUtilTest.java index 9d5f9d8f75..7df23f7d87 100644 --- a/runelite-client/src/test/java/net/runelite/client/util/ColorUtilTest.java +++ b/runelite-client/src/test/java/net/runelite/client/util/ColorUtilTest.java @@ -28,8 +28,6 @@ import java.awt.Color; import java.util.HashMap; import java.util.Map; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import org.junit.Test; public class ColorUtilTest @@ -97,26 +95,4 @@ public class ColorUtilTest COLOR_HEXSTRING_MAP.forEach((color, hex) -> assertEquals(hex, ColorUtil.colorToHexCode(color))); } - - @Test - public void isFullyTransparent() - { - for (Color color : COLOR_HEXSTRING_MAP.keySet()) - { - assertFalse(ColorUtil.isFullyTransparent(color)); - } - assertTrue(ColorUtil.isFullyTransparent(new Color(0, 0, 0, 0))); - assertFalse(ColorUtil.isFullyTransparent(new Color(0, 0, 0, 1))); - } - - @Test - public void isNotFullyTransparent() - { - for (Color color : COLOR_HEXSTRING_MAP.keySet()) - { - assertTrue(ColorUtil.isNotFullyTransparent(color)); - } - assertFalse(ColorUtil.isNotFullyTransparent(new Color(0, 0, 0, 0))); - assertTrue(ColorUtil.isNotFullyTransparent(new Color(0, 0, 0, 1))); - } } diff --git a/runelite-client/src/test/java/net/runelite/client/util/ImageUtilTest.java b/runelite-client/src/test/java/net/runelite/client/util/ImageUtilTest.java index 5b504e4da5..9357217bac 100644 --- a/runelite-client/src/test/java/net/runelite/client/util/ImageUtilTest.java +++ b/runelite-client/src/test/java/net/runelite/client/util/ImageUtilTest.java @@ -35,7 +35,6 @@ import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.awt.image.DataBufferInt; import java.util.Arrays; -import java.util.function.Predicate; import javax.annotation.Nonnull; import org.apache.commons.lang3.ArrayUtils; import static org.junit.Assert.assertEquals; @@ -256,11 +255,6 @@ public class ImageUtilTest assertTrue(bufferedImagesEqual(centeredPixel(GRAY), ImageUtil.fillImage(centeredPixel(BLACK), GRAY))); assertTrue(bufferedImagesEqual(solidColor(3, 3, GREEN), ImageUtil.fillImage(solidColor(3, 3, BLACK), GREEN))); assertTrue(bufferedImagesEqual(oneByOne(BLACK_TRANSPARENT), ImageUtil.fillImage(oneByOne(BLACK_TRANSPARENT), WHITE))); - - // fillImage(BufferedImage image, Color color, Predicate fillCondition) - BufferedImage expected = solidColor(CORNER_SIZE, CORNER_SIZE, WHITE); - expected.setRGB(0, 0, new Color(0, true).getRGB()); - assertTrue(bufferedImagesEqual(expected, ImageUtil.fillImage(BLACK_PIXEL_TOP_LEFT, WHITE, ColorUtil::isFullyTransparent))); } @Test @@ -287,39 +281,11 @@ public class ImageUtilTest expected.setRGB(1, 1, new Color(0, true).getRGB()); assertTrue(bufferedImagesEqual(expected, ImageUtil.outlineImage(BLACK_PIXEL_TOP_LEFT, WHITE))); - // outlineImage(BufferedImage image, Color color, Predicate fillCondition) - BufferedImage test = new BufferedImage(CORNER_SIZE, CORNER_SIZE, BufferedImage.TYPE_INT_ARGB); - test.setRGB(0, 0, BLACK.getRGB()); - test.setRGB(1, 0, GRAY.getRGB()); - expected = test; - expected.setRGB(0, 1, BLUE.getRGB()); - assertTrue(bufferedImagesEqual(expected, ImageUtil.outlineImage(test, BLUE, (color -> color.equals(BLACK))))); - // outlineImage(BufferedImage image, Color color, Boolean outlineCorners) expected = solidColor(CORNER_SIZE, CORNER_SIZE, WHITE); expected.setRGB(0, 0, BLACK.getRGB()); assertTrue(bufferedImagesEqual(expected, ImageUtil.outlineImage(BLACK_PIXEL_TOP_LEFT, WHITE, true))); assertTrue(bufferedImagesEqual(solidColor(3, 3, BLACK), ImageUtil.outlineImage(centeredPixel(BLACK), BLACK, true))); - - // outlineImage(BufferedImage image, Color color, Predicate fillCondition, Boolean outlineCorners) - test = new BufferedImage(5, 5, BufferedImage.TYPE_INT_ARGB); - test.setRGB(2, 2, BLACK.getRGB()); - test.setRGB(1, 2, new Color(50, 50, 50).getRGB()); - test.setRGB(3, 2, new Color(100, 100, 100).getRGB()); - test.setRGB(2, 3, new Color(150, 150, 150).getRGB()); - expected = test; - expected.setRGB(2, 1, RED.getRGB()); - expected.setRGB(3, 1, RED.getRGB()); - expected.setRGB(4, 1, RED.getRGB()); - expected.setRGB(4, 2, RED.getRGB()); - expected.setRGB(1, 3, RED.getRGB()); - expected.setRGB(3, 3, RED.getRGB()); - expected.setRGB(4, 3, RED.getRGB()); - expected.setRGB(1, 4, RED.getRGB()); - expected.setRGB(2, 4, RED.getRGB()); - expected.setRGB(3, 4, RED.getRGB()); - Predicate testPredicate = (color -> ColorUtil.isNotFullyTransparent(color) && color.getRed() > 75 && color.getGreen() > 75 && color.getBlue() > 75); - assertTrue(bufferedImagesEqual(expected, ImageUtil.outlineImage(test, RED, testPredicate, true))); } /**