project: Merge upstream

This commit is contained in:
Owain van Brakel
2020-10-21 12:57:38 +02:00
28 changed files with 968 additions and 278 deletions

View File

@@ -25,9 +25,9 @@
object ProjectVersions { object ProjectVersions {
const val launcherVersion = "2.2.0" 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 rsversion = 191
const val cacheversion = 165 const val cacheversion = 165

View File

@@ -24,6 +24,8 @@
*/ */
package net.runelite.cache.script.disassembler; package net.runelite.cache.script.disassembler;
import com.google.common.escape.Escaper;
import com.google.common.escape.Escapers;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -37,6 +39,10 @@ import org.slf4j.LoggerFactory;
public class Disassembler public class Disassembler
{ {
private static final Logger logger = LoggerFactory.getLogger(Disassembler.class); 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(); private final Instructions instructions = new Instructions();
@@ -165,7 +171,7 @@ public class Disassembler
if (sop != null) if (sop != null)
{ {
writer.append(" \"").append(sop).append("\""); writer.append(" \"").append(ESCAPER.escape(sop)).append("\"");
} }
if (opcode == Opcodes.SWITCH) if (opcode == Opcodes.SWITCH)

View File

@@ -35,7 +35,9 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import lombok.AllArgsConstructor; import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.runelite.http.api.RuneLiteAPI; import net.runelite.http.api.RuneLiteAPI;
import static net.runelite.http.api.RuneLiteAPI.JSON; import static net.runelite.http.api.RuneLiteAPI.JSON;
@@ -48,30 +50,29 @@ import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
@Slf4j @Slf4j
@AllArgsConstructor @RequiredArgsConstructor
public class LootTrackerClient public class LootTrackerClient
{ {
private static final Gson GSON = RuneLiteAPI.GSON; private static final Gson GSON = RuneLiteAPI.GSON;
private final OkHttpClient client; private final OkHttpClient client;
private final UUID uuid; @Getter
@Setter
private UUID uuid;
public CompletableFuture<Void> submit(Collection<LootRecord> lootRecords) public CompletableFuture<Void> submit(Collection<LootRecord> lootRecords)
{ {
CompletableFuture<Void> future = new CompletableFuture<>(); CompletableFuture<Void> future = new CompletableFuture<>();
HttpUrl url = RuneLiteAPI.getApiBase().newBuilder()
.addPathSegment("loottracker")
.build();
RequestBody body = RequestBody.Companion.create(GSON.toJson(lootRecords), JSON); RequestBody body = RequestBody.Companion.create(GSON.toJson(lootRecords), JSON);
Request request = new Request.Builder() Request.Builder requestBuilder = new Request.Builder();
.header(RuneLiteAPI.RUNELITE_AUTH, uuid.toString()) if (uuid != null)
.post(body) {
.url(url) requestBuilder.header(RuneLiteAPI.RUNELITE_AUTH, uuid.toString());
.build(); }
requestBuilder.post(body);
client.newCall(request).enqueue(new Callback() client.newCall(requestBuilder.build()).enqueue(new Callback()
{ {
@Override @Override
public void onFailure(Call call, IOException e) public void onFailure(Call call, IOException e)

View File

@@ -50,7 +50,7 @@ import net.runelite.api.widgets.WidgetInfo;
*/ */
public class Perspective 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_COORD_BITS = 7;
public static final int LOCAL_TILE_SIZE = 1 << LOCAL_COORD_BITS; // 128 - size of a tile in local coordinates public static final int LOCAL_TILE_SIZE = 1 << LOCAL_COORD_BITS; // 128 - size of a tile in local coordinates

View File

@@ -493,6 +493,7 @@ public enum Varbits
FARMING_7909(7909), FARMING_7909(7909),
FARMING_7910(7910), FARMING_7910(7910),
FARMING_7911(7911), FARMING_7911(7911),
FARMING_7912(7912),
/** /**
* Transmog controllers for grapes * Transmog controllers for grapes

View File

@@ -328,7 +328,7 @@ public class Text
* *
* @return true if all search terms matches at least one keyword, or false if otherwise. * @return true if all search terms matches at least one keyword, or false if otherwise.
*/ */
public static boolean matchesSearchTerms(String[] searchTerms, final Collection<String> keywords) public static boolean matchesSearchTerms(Iterable<String> searchTerms, final Collection<String> keywords)
{ {
for (String term : searchTerms) for (String term : searchTerms)
{ {

View File

@@ -173,6 +173,9 @@ public class WidgetID
public static final int GAUNTLET_MAP_GROUP_ID = 638; public static final int GAUNTLET_MAP_GROUP_ID = 638;
public static final int HALLOWED_SEPULCHRE_TIMER_GROUP_ID = 668; public static final int HALLOWED_SEPULCHRE_TIMER_GROUP_ID = 668;
public static final int HEALTH_OVERLAY_BAR_GROUP_ID = 303; 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 static class WorldMap
{ {

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2017, Adam <Adam@sigterm.info> * Copyright (c) 2017, Adam <Adam@sigterm.info>
* Copyright (c) 2018, Brett Middle <https://github.com/bmiddle>
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@@ -24,7 +25,7 @@
*/ */
package net.runelite.api;; package net.runelite.api;;
import org.junit.Assert; import static org.junit.Assert.assertEquals;
import org.junit.Test; import org.junit.Test;
public class ExperienceTest public class ExperienceTest
@@ -36,13 +37,13 @@ public class ExperienceTest
public void testGetXpForLevel() public void testGetXpForLevel()
{ {
int xp = Experience.getXpForLevel(99); int xp = Experience.getXpForLevel(99);
Assert.assertEquals(XP_FOR_99, xp); assertEquals(XP_FOR_99, xp);
xp = Experience.getXpForLevel(126); xp = Experience.getXpForLevel(126);
Assert.assertEquals(XP_FOR_126, xp); assertEquals(XP_FOR_126, xp);
xp = Experience.getXpForLevel(1); xp = Experience.getXpForLevel(1);
Assert.assertEquals(0, xp); assertEquals(0, xp);
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
@@ -61,22 +62,22 @@ public class ExperienceTest
public void testGetLevelForXp() public void testGetLevelForXp()
{ {
int level = Experience.getLevelForXp(XP_FOR_99); int level = Experience.getLevelForXp(XP_FOR_99);
Assert.assertEquals(99, level); assertEquals(99, level);
level = Experience.getLevelForXp(XP_FOR_99 - 1); level = Experience.getLevelForXp(XP_FOR_99 - 1);
Assert.assertEquals(98, level); assertEquals(98, level);
level = Experience.getLevelForXp(XP_FOR_126); level = Experience.getLevelForXp(XP_FOR_126);
Assert.assertEquals(126, level); assertEquals(126, level);
level = Experience.getLevelForXp(XP_FOR_126 - 1); level = Experience.getLevelForXp(XP_FOR_126 - 1);
Assert.assertEquals(125, level); assertEquals(125, level);
level = Experience.getLevelForXp(Integer.MAX_VALUE); level = Experience.getLevelForXp(Integer.MAX_VALUE);
Assert.assertEquals(126, level); assertEquals(126, level);
level = Experience.getLevelForXp(0); level = Experience.getLevelForXp(0);
Assert.assertEquals(1, level); assertEquals(1, level);
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
@@ -88,7 +89,371 @@ public class ExperienceTest
@Test @Test
public void testGetCombatLevel() public void testGetCombatLevel()
{ {
Assert.assertEquals(126, Experience.getCombatLevel(99, 99, 99, 99, 70, 42, 98)); assertEquals(126, Experience.getCombatLevel(99, 99, 99, 99, 70, 42, 98));
Assert.assertEquals(40, Experience.getCombatLevel(27, 22, 1, 36, 64, 45, 1)); 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);
} }
} }

View File

@@ -102,7 +102,7 @@ import net.runelite.client.ui.overlay.OverlayRenderer;
import net.runelite.client.ui.overlay.WidgetOverlay; import net.runelite.client.ui.overlay.WidgetOverlay;
import net.runelite.client.ui.overlay.arrow.ArrowMinimapOverlay; import net.runelite.client.ui.overlay.arrow.ArrowMinimapOverlay;
import net.runelite.client.ui.overlay.arrow.ArrowWorldOverlay; 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.TooltipManager;
import net.runelite.client.ui.overlay.tooltip.TooltipOverlay; import net.runelite.client.ui.overlay.tooltip.TooltipOverlay;
import net.runelite.client.ui.overlay.worldmap.WorldMapOverlay; import net.runelite.client.ui.overlay.worldmap.WorldMapOverlay;
@@ -190,7 +190,7 @@ public class RuneLite
private Provider<CommandManager> commandManager; private Provider<CommandManager> commandManager;
@Inject @Inject
private Provider<InfoBoxOverlay> infoBoxOverlay; private Provider<InfoBoxManager> infoBoxManager;
@Inject @Inject
private Provider<TooltipOverlay> tooltipOverlay; private Provider<TooltipOverlay> tooltipOverlay;
@@ -511,6 +511,7 @@ public class RuneLite
// Initialize chat colors // Initialize chat colors
chatMessageManager.get().loadColors(); chatMessageManager.get().loadColors();
infoBoxManager.get();
overlayRenderer.get(); overlayRenderer.get();
friendChatManager.get(); friendChatManager.get();
itemManager.get(); itemManager.get();
@@ -522,14 +523,12 @@ public class RuneLite
playerManager.get(); playerManager.get();
chatboxPanelManager.get(); chatboxPanelManager.get();
partyService.get(); partyService.get();
infoBoxOverlay.get();
eventBus.subscribe(GameStateChanged.class, this, hooks::onGameStateChanged); eventBus.subscribe(GameStateChanged.class, this, hooks::onGameStateChanged);
eventBus.subscribe(ScriptCallbackEvent.class, this, hooks::onScriptCallbackEvent); eventBus.subscribe(ScriptCallbackEvent.class, this, hooks::onScriptCallbackEvent);
// Add core overlays // Add core overlays
WidgetOverlay.createOverlays(client).forEach(overlayManager::add); WidgetOverlay.createOverlays(client).forEach(overlayManager::add);
overlayManager.add(infoBoxOverlay.get());
overlayManager.add(worldMapOverlay.get()); overlayManager.add(worldMapOverlay.get());
overlayManager.add(tooltipOverlay.get()); overlayManager.add(tooltipOverlay.get());
overlayManager.add(arrowWorldOverlay.get()); overlayManager.add(arrowWorldOverlay.get());

View File

@@ -400,12 +400,25 @@ public interface RuneLiteConfig extends Config
return new Title(); 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( @ConfigItem(
keyName = "infoBoxVertical", keyName = "infoBoxVertical",
name = "Display infoboxes vertically", name = "Display infoboxes vertically",
description = "Toggles the infoboxes to display vertically", description = "Toggles the infoboxes to display vertically",
position = 31, position = 32,
titleSection = "infoboxTitle" titleSection = "infoboxTitle",
hidden = true
) )
default boolean infoBoxVertical() default boolean infoBoxVertical()
{ {
@@ -416,7 +429,7 @@ public interface RuneLiteConfig extends Config
keyName = "infoBoxSize", keyName = "infoBoxSize",
name = "Infobox size", name = "Infobox size",
description = "Configures the size of each infobox in pixels", description = "Configures the size of each infobox in pixels",
position = 32, position = 33,
titleSection = "infoboxTitle" titleSection = "infoboxTitle"
) )
@Units(Units.PIXELS) @Units(Units.PIXELS)
@@ -429,7 +442,7 @@ public interface RuneLiteConfig extends Config
keyName = "keybindsTitle", keyName = "keybindsTitle",
name = "Key binds", name = "Key binds",
description = "", description = "",
position = 33 position = 34
) )
default Title keybindsTitle() default Title keybindsTitle()
{ {
@@ -440,7 +453,7 @@ public interface RuneLiteConfig extends Config
keyName = "sidebarToggleKey", keyName = "sidebarToggleKey",
name = "Sidebar Toggle Key", name = "Sidebar Toggle Key",
description = "The key that will toggle the sidebar (accepts modifiers)", description = "The key that will toggle the sidebar (accepts modifiers)",
position = 34, position = 35,
titleSection = "keybindsTitle" titleSection = "keybindsTitle"
) )
default Keybind sidebarToggleKey() default Keybind sidebarToggleKey()
@@ -452,7 +465,7 @@ public interface RuneLiteConfig extends Config
keyName = "panelToggleKey", keyName = "panelToggleKey",
name = "Plugin Panel Toggle Key", name = "Plugin Panel Toggle Key",
description = "The key that will toggle the current or last opened plugin panel (accepts modifiers)", description = "The key that will toggle the current or last opened plugin panel (accepts modifiers)",
position = 35, position = 36,
titleSection = "keybindsTitle" titleSection = "keybindsTitle"
) )
default Keybind panelToggleKey() default Keybind panelToggleKey()
@@ -464,7 +477,7 @@ public interface RuneLiteConfig extends Config
keyName = "blockExtraMouseButtons", keyName = "blockExtraMouseButtons",
name = "Block Extra Mouse Buttons", name = "Block Extra Mouse Buttons",
description = "Blocks extra mouse buttons (4 and above)", description = "Blocks extra mouse buttons (4 and above)",
position = 36, position = 37,
titleSection = "keybindsTitle" titleSection = "keybindsTitle"
) )
default boolean blockExtraMouseButtons() default boolean blockExtraMouseButtons()

View File

@@ -187,6 +187,7 @@ public enum AgilityShortcut
AL_KHARID_WINDOW(70, "Window", new WorldPoint(3293, 3158, 0), BROKEN_WALL_33344, BIG_WINDOW), 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_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), 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_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), 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), STRONGHOLD_SLAYER_CAVE_TUNNEL(72, "Tunnel", new WorldPoint(2431, 9806, 0), TUNNEL_30174, TUNNEL_30175),

View File

@@ -32,6 +32,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
import java.awt.Color; import java.awt.Color;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -276,10 +277,10 @@ public class ItemManager
/** /**
* Look up an item's price * Look up an item's price
* *
* @param itemID item id * @param itemID item id
* @param ignoreUntradeableMap should the price returned ignore the {@link UntradeableItemMapping} * @param ignoreUntradeableMap should the price returned ignore items that are not tradeable for coins in regular way
* @return item price * @return item price
*/ * */
public int getItemPrice(int itemID, boolean ignoreUntradeableMap) public int getItemPrice(int itemID, boolean ignoreUntradeableMap)
{ {
if (itemID == ItemID.COINS_995) if (itemID == ItemID.COINS_995)
@@ -291,31 +292,38 @@ public class ItemManager
return 1000; return 1000;
} }
ItemDefinition itemComposition = getItemDefinition(itemID); ItemDefinition itemDefinition = getItemDefinition(itemID);
if (itemComposition.getNote() != -1) if (itemDefinition.getNote() != -1)
{ {
itemID = itemComposition.getLinkedNoteId(); itemID = itemDefinition.getLinkedNoteId();
} }
itemID = WORN_ITEMS.getOrDefault(itemID, itemID); 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; int price = 0;
for (int mappedID : ItemMapping.map(itemID))
final Collection<ItemMapping> mappedItems = ItemMapping.map(itemID);
if (mappedItems == null)
{ {
ItemPrice ip = itemPrices.get(mappedID); final ItemPrice ip = itemPrices.get(itemID);
if (ip != null) if (ip != null)
{ {
price += ip.getPrice(); price += ip.getPrice();
} }
} }
else
{
for (final ItemMapping mappedItem : mappedItems)
{
if (ignoreUntradeableMap && mappedItem.isUntradeable())
{
continue;
}
price += getItemPrice(mappedItem.getTradeableItem(), ignoreUntradeableMap) * mappedItem.getQuantity();
}
}
return price; return price;
} }

View File

@@ -28,12 +28,14 @@ package net.runelite.client.game;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import javax.annotation.Nullable;
import lombok.Getter;
import static net.runelite.api.ItemID.*; import static net.runelite.api.ItemID.*;
/** /**
* Converts untradeable items to it's tradeable counterparts * Converts untradeable items to it's tradeable counterparts
*/ */
@Getter
public enum ItemMapping public enum ItemMapping
{ {
// Barrows equipment // 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_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_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_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 // 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), 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 // Ancestral robes
ITEM_ANCESTRAL_HAT(ANCESTRAL_HAT, TWISTED_ANCESTRAL_HAT), ITEM_ANCESTRAL_HAT(ANCESTRAL_HAT, TWISTED_ANCESTRAL_HAT),
ITEM_ANCESTRAL_ROBE_TOP(ANCESTRAL_ROBE_TOP, TWISTED_ANCESTRAL_ROBE_TOP), 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<Integer, Integer> 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<Integer, ItemMapping> MAPPINGS = HashMultimap.create();
private final int tradeableItem; private final int tradeableItem;
private final int[] untradableItems; private final int[] untradableItems;
private final long quantity;
private final boolean untradeable;
static static
{ {
@@ -268,15 +303,35 @@ public enum ItemMapping
{ {
for (int itemId : item.untradableItems) 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.tradeableItem = tradeableItem;
this.untradableItems = untradableItems; 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 * @param itemId the item id
* @return the collection * @return the collection
*/ */
public static Collection<Integer> map(int itemId) @Nullable
public static Collection<ItemMapping> map(int itemId)
{ {
final Collection<Integer> mapping = MAPPINGS.get(itemId); final Collection<ItemMapping> mapping = MAPPINGS.get(itemId);
if (mapping == null || mapping.isEmpty()) if (mapping.isEmpty())
{ {
return Collections.singleton(itemId); return null;
} }
return mapping; return mapping;
} }
/**
* Map an item from its untradeable version to its tradeable version
*
* @param itemId
* @return
*/
public static int mapFirst(int itemId)
{
final Collection<Integer> mapping = MAPPINGS.get(itemId);
if (mapping == null || mapping.isEmpty())
{
return itemId;
}
return mapping.iterator().next();
}
public static boolean isMapped(int itemId) public static boolean isMapped(int itemId)
{ {
return MAPPINGS.containsValue(itemId); return MAPPINGS.containsValue(itemId);

View File

@@ -1,90 +0,0 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/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<Integer, UntradeableItemMapping> UNTRADEABLE_RECLAIM_MAP;
private final int itemID;
private final int quantity;
private final int priceID;
static
{
ImmutableMap.Builder<Integer, UntradeableItemMapping> 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);
}
}

View File

@@ -30,10 +30,12 @@ import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.api.GameState; import net.runelite.api.GameState;
@Singleton @Singleton
@Slf4j
public class KeyManager public class KeyManager
{ {
private final Client client; private final Client client;
@@ -50,13 +52,18 @@ public class KeyManager
{ {
if (!keyListeners.contains(keyListener)) if (!keyListeners.contains(keyListener))
{ {
log.debug("Registering key listener: {}", keyListener);
keyListeners.add(keyListener); keyListeners.add(keyListener);
} }
} }
public void unregisterKeyListener(KeyListener 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) public void processKeyPressed(KeyEvent keyEvent)
@@ -73,9 +80,12 @@ public class KeyManager
continue; continue;
} }
log.trace("Processing key pressed {} for key listener {}", keyEvent.paramString(), keyListener);
keyListener.keyPressed(keyEvent); keyListener.keyPressed(keyEvent);
if (keyEvent.isConsumed()) if (keyEvent.isConsumed())
{ {
log.debug("Consuming key pressed {} for key listener {}", keyEvent.paramString(), keyListener);
break; break;
} }
} }
@@ -95,9 +105,12 @@ public class KeyManager
continue; continue;
} }
log.trace("Processing key released {} for key listener {}", keyEvent.paramString(), keyListener);
keyListener.keyReleased(keyEvent); keyListener.keyReleased(keyEvent);
if (keyEvent.isConsumed()) if (keyEvent.isConsumed())
{ {
log.debug("Consuming key released {} for listener {}", keyEvent.paramString(), keyListener);
break; break;
} }
} }
@@ -117,9 +130,12 @@ public class KeyManager
continue; continue;
} }
log.trace("Processing key typed {} for key listener {}", keyEvent.paramString(), keyListener);
keyListener.keyTyped(keyEvent); keyListener.keyTyped(keyEvent);
if (keyEvent.isConsumed()) if (keyEvent.isConsumed())
{ {
log.debug("Consuming key typed {} for key listener {}", keyEvent.paramString(), keyListener);
break; break;
} }
} }

View File

@@ -24,6 +24,7 @@
*/ */
package net.runelite.client.plugins.config; package net.runelite.client.plugins.config;
import com.google.common.base.Splitter;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Color; import java.awt.Color;
import java.awt.Component; 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 RUNELITE_GROUP_NAME = RuneLiteConfig.class.getAnnotation(ConfigGroup.class).value();
private static final String PINNED_PLUGINS_CONFIG_KEY = "pinnedPlugins"; private static final String PINNED_PLUGINS_CONFIG_KEY = "pinnedPlugins";
private static final Splitter SPLITTER = Splitter.on(" ").trimResults().omitEmptyStrings();
private static final List<String> CATEGORY_TAGS = List.of( private static final List<String> CATEGORY_TAGS = List.of(
"Combat", "Combat",
"Chat", "Chat",
@@ -443,10 +445,9 @@ public class PluginListPanel extends PluginPanel
} }
else else
{ {
final String[] searchTerms = text.toLowerCase().split(" ");
pluginList.forEach(listItem -> 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))) if (openOSRSConfig.pluginSortMode() == OpenOSRSConfig.SortStyle.ALPHABETICALLY || (!openOSRSConfig.enableCategories() && (openOSRSConfig.pluginSortMode() != OpenOSRSConfig.SortStyle.REPOSITORY)))
{ {

View File

@@ -24,6 +24,7 @@
*/ */
package net.runelite.client.ui; package net.runelite.client.ui;
import com.google.common.annotations.VisibleForTesting;
import java.awt.Frame; import java.awt.Frame;
import java.awt.GraphicsConfiguration; import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice; import java.awt.GraphicsDevice;
@@ -49,6 +50,7 @@ public class ContainableFrame extends JFrame
NEVER NEVER
} }
private static final int SCREEN_EDGE_CLOSE_DISTANCE = 40;
private static boolean jdk8231564; private static boolean jdk8231564;
static static
@@ -56,9 +58,7 @@ public class ContainableFrame extends JFrame
try try
{ {
String javaVersion = System.getProperty("java.version"); String javaVersion = System.getProperty("java.version");
String[] s = javaVersion.split("\\."); jdk8231564 = jdk8231564(javaVersion);
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);
} }
catch (Exception ex) 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 @Setter
private ExpandResizeType expandResizeType; private ExpandResizeType expandResizeType;

View File

@@ -30,6 +30,7 @@ import java.awt.Rectangle;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.Plugin;
@@ -52,6 +53,13 @@ public abstract class Overlay implements LayoutableRenderableEntity
private boolean resizable; private boolean resizable;
private boolean resettable = true; 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() protected Overlay()
{ {
plugin = null; plugin = null;
@@ -75,4 +83,17 @@ public abstract class Overlay implements LayoutableRenderableEntity
public void onMouseOver() 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;
}
} }

View File

@@ -49,6 +49,7 @@ import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.api.GameState; import net.runelite.api.GameState;
import net.runelite.api.KeyCode;
import net.runelite.api.MenuEntry; import net.runelite.api.MenuEntry;
import net.runelite.api.events.BeforeRender; import net.runelite.api.events.BeforeRender;
import net.runelite.api.events.ClientTick; 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 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_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_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 static final Color MOVING_OVERLAY_RESIZING_COLOR = new Color(255, 0, 255, 200);
private final Client client; private final Client client;
private final OverlayManager overlayManager; private final OverlayManager overlayManager;
@@ -90,11 +92,11 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener
private final Point overlayOffset = new Point(); private final Point overlayOffset = new Point();
private final Point mousePosition = new Point(); private final Point mousePosition = new Point();
private Overlay currentManagedOverlay; private Overlay currentManagedOverlay;
private Overlay dragTargetOverlay;
private Rectangle currentManagedBounds; private Rectangle currentManagedBounds;
private boolean inOverlayManagingMode; private boolean inOverlayManagingMode;
private boolean inOverlayResizingMode; private boolean inOverlayResizingMode;
private boolean inOverlayDraggingMode; private boolean inOverlayDraggingMode;
private boolean inMenuEntryMode;
private boolean startedMovingOverlay; private boolean startedMovingOverlay;
private MenuEntry[] menuEntries; private MenuEntry[] menuEntries;
@@ -141,7 +143,6 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener
resetOverlayManagementMode(); resetOverlayManagementMode();
} }
inMenuEntryMode = false;
menuEntries = null; menuEntries = null;
} }
} }
@@ -153,7 +154,8 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener
return; return;
} }
if (!inMenuEntryMode && runeLiteConfig.menuEntryShift()) final boolean shift = client.isKeyPressed(KeyCode.KC_SHIFT);
if (!shift && runeLiteConfig.menuEntryShift())
{ {
return; return;
} }
@@ -313,15 +315,28 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener
{ {
if (inOverlayManagingMode) if (inOverlayManagingMode)
{ {
Color boundsColor;
if (inOverlayResizingMode && currentManagedOverlay == overlay) 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 else
{ {
graphics.setColor(inOverlayDraggingMode && currentManagedOverlay == overlay ? MOVING_OVERLAY_ACTIVE_COLOR : MOVING_OVERLAY_COLOR); boundsColor = MOVING_OVERLAY_COLOR;
} }
graphics.setColor(boundsColor);
graphics.draw(bounds); graphics.draw(bounds);
graphics.setPaint(paint); graphics.setPaint(paint);
} }
@@ -402,6 +417,12 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener
{ {
for (Overlay overlay : overlayManager.getOverlays()) 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(); final Rectangle bounds = overlay.getBounds();
if (bounds.contains(mousePoint)) if (bounds.contains(mousePoint))
{ {
@@ -472,6 +493,12 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener
return mouseEvent; return mouseEvent;
} }
if (dragTargetOverlay != null && !currentManagedOverlay.getBounds().intersects(dragTargetOverlay.getBounds()))
{
// No longer over drag target
dragTargetOverlay = null;
}
final Rectangle canvasRect = new Rectangle(client.getRealDimensions()); final Rectangle canvasRect = new Rectangle(client.getRealDimensions());
if (!canvasRect.contains(p)) if (!canvasRect.contains(p))
@@ -598,7 +625,17 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener
mousePosition.setLocation(-1, -1); 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) if (currentManagedOverlay.getPosition() != OverlayPosition.DETACHED && inOverlayDraggingMode)
{ {
final OverlayBounds snapCorners = this.snapCorners.translated(-SNAP_CORNER_SIZE.width, -SNAP_CORNER_SIZE.height); 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; inOverlayManagingMode = true;
} }
if (e.isShiftDown() && runeLiteConfig.menuEntryShift())
{
inMenuEntryMode = true;
}
} }
@Override @Override
@@ -655,11 +687,6 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener
inOverlayManagingMode = false; inOverlayManagingMode = false;
resetOverlayManagementMode(); resetOverlayManagementMode();
} }
if (!e.isShiftDown())
{
inMenuEntryMode = false;
}
} }
private void safeRender(Client client, Overlay overlay, OverlayLayer layer, Graphics2D graphics, Point point) 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; inOverlayResizingMode = false;
inOverlayDraggingMode = false; inOverlayDraggingMode = false;
currentManagedOverlay = null; currentManagedOverlay = null;
dragTargetOverlay = null;
currentManagedBounds = null; currentManagedBounds = null;
clientUI.setCursor(clientUI.getDefaultCursor()); clientUI.setCursor(clientUI.getDefaultCursor());
} }

View File

@@ -53,6 +53,7 @@ public class InfoBoxComponent implements LayoutableRenderableEntity
private Dimension preferredSize = new Dimension(DEFAULT_SIZE, DEFAULT_SIZE); private Dimension preferredSize = new Dimension(DEFAULT_SIZE, DEFAULT_SIZE);
private String text; private String text;
private Color color = Color.WHITE; private Color color = Color.WHITE;
private boolean outline;
private Color backgroundColor = ComponentConstants.STANDARD_BACKGROUND_COLOR; private Color backgroundColor = ComponentConstants.STANDARD_BACKGROUND_COLOR;
private BufferedImage image; private BufferedImage image;
@Getter @Getter
@@ -93,6 +94,7 @@ public class InfoBoxComponent implements LayoutableRenderableEntity
{ {
final TextComponent textComponent = new TextComponent(); final TextComponent textComponent = new TextComponent();
textComponent.setColor(color); textComponent.setColor(color);
textComponent.setOutline(outline);
textComponent.setText(text); textComponent.setText(text);
textComponent.setPosition(new Point(baseX + ((size - metrics.stringWidth(text)) / 2), baseY + size - SEPARATOR)); textComponent.setPosition(new Point(baseX + ((size - metrics.stringWidth(text)) / 2), baseY + size - SEPARATOR));
textComponent.render(graphics); textComponent.render(graphics);

View File

@@ -114,7 +114,7 @@ public class ProgressBarComponent implements LayoutableRenderableEntity
// Draw bar // Draw bar
graphics.setColor(backgroundColor); graphics.setColor(backgroundColor);
graphics.fillRect(barX, barY, width, height); graphics.fillRect(barX + progressFill, barY, width - progressFill, height);
graphics.setColor(foregroundColor); graphics.setColor(foregroundColor);
graphics.fillRect(barX, barY, progressFill, height); graphics.fillRect(barX, barY, progressFill, height);

View File

@@ -81,4 +81,11 @@ public abstract class InfoBox
{ {
return false; 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();
}
} }

View File

@@ -24,43 +24,112 @@
*/ */
package net.runelite.client.ui.overlay.infobox; package net.runelite.client.ui.overlay.infobox;
import com.google.common.base.Strings;
import com.google.common.collect.ComparisonChain; import com.google.common.collect.ComparisonChain;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j; 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.config.RuneLiteConfig;
import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.EventBus;
import net.runelite.client.events.ConfigChanged; 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; import net.runelite.client.util.AsyncBufferedImage;
@Singleton @Singleton
@Slf4j @Slf4j
public class InfoBoxManager public class InfoBoxManager
{ {
private final List<InfoBox> 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<String, InfoBoxOverlay> layers = new ConcurrentHashMap<>();
private final RuneLiteConfig runeLiteConfig; 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 @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.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(ConfigChanged.class, this, this::onConfigChanged);
eventbus.subscribe(InfoBoxMenuClicked.class, this, this::onInfoBoxMenuClicked);
} }
private void onConfigChanged(ConfigChanged event) private void onConfigChanged(ConfigChanged event)
{ {
if (event.getGroup().equals("runelite") && event.getKey().equals("infoBoxSize")) 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); updateInfoBoxImage(infoBox);
String layerName = getLayer(infoBox);
InfoBoxOverlay overlay = layers.computeIfAbsent(layerName, this::makeOverlay);
List<OverlayMenuEntry> 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) synchronized (this)
{ {
int idx = findInsertionIndex(infoBoxes, infoBox, (b1, b2) -> ComparisonChain int idx = findInsertionIndex(overlay.getInfoBoxes(), infoBox, (b1, b2) -> ComparisonChain
.start() .start()
.compare(b1.getPriority(), b2.getPriority()) .compare(b1.getPriority(), b2.getPriority())
.compare(b1.getPlugin().getName(), b2.getPlugin().getName()) .compare(b1.getPlugin().getName(), b2.getPlugin().getName())
.result()); .result());
infoBoxes.add(idx, infoBox); overlay.getInfoBoxes().add(idx, infoBox);
} }
BufferedImage image = infoBox.getImage(); BufferedImage image = infoBox.getImage();
@@ -92,28 +172,40 @@ public class InfoBoxManager
public synchronized void removeInfoBox(InfoBox infoBox) 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); 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<InfoBox> filter) public synchronized void removeIf(Predicate<InfoBox> 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<InfoBox> getInfoBoxes() public List<InfoBox> getInfoBoxes()
{ {
return Collections.unmodifiableList(infoBoxes); return layers.values().stream().map(InfoBoxOverlay::getInfoBoxes).flatMap(Collection::stream).collect(Collectors.toList());
} }
public synchronized void cull() public synchronized void cull()
{ {
infoBoxes.removeIf(InfoBox::cull); layers.values().forEach(l -> l.getInfoBoxes().removeIf(InfoBox::cull));
} }
public void updateInfoBoxImage(final InfoBox infoBox) public void updateInfoBoxImage(final InfoBox infoBox)
@@ -152,9 +244,144 @@ public class InfoBoxManager
infoBox.setScaledImage(resultImage); 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<InfoBox> 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<InfoBox> 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, * 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. * return the index after the last occurrence.
*
* @param list * @param list
* @param key * @param key
* @param c * @param c

View File

@@ -33,14 +33,17 @@ import java.awt.Point;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import javax.inject.Inject; import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Singleton; import javax.inject.Singleton;
import lombok.Getter;
import lombok.NonNull;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.api.MenuOpcode; import net.runelite.api.MenuOpcode;
import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.events.MenuOptionClicked;
import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.config.RuneLiteConfig;
import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.EventBus;
import net.runelite.client.events.InfoBoxMenuClicked; 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.OverlayMenuEntry;
import net.runelite.client.ui.overlay.OverlayPanel; import net.runelite.client.ui.overlay.OverlayPanel;
import net.runelite.client.ui.overlay.OverlayPosition; import net.runelite.client.ui.overlay.OverlayPosition;
@@ -61,24 +64,33 @@ public class InfoBoxOverlay extends OverlayPanel
private final Client client; private final Client client;
private final RuneLiteConfig config; private final RuneLiteConfig config;
private final EventBus eventBus; private final EventBus eventBus;
private final String name;
private ComponentOrientation orientation;
@Getter
private final List<InfoBox> infoBoxes = new CopyOnWriteArrayList<>();
private InfoBoxComponent hoveredComponent; private InfoBoxComponent hoveredComponent;
@Inject InfoBoxOverlay(
private InfoBoxOverlay(
InfoBoxManager infoboxManager, InfoBoxManager infoboxManager,
TooltipManager tooltipManager, TooltipManager tooltipManager,
Client client, Client client,
RuneLiteConfig config, RuneLiteConfig config,
EventBus eventBus) EventBus eventBus,
String name,
@NonNull ComponentOrientation orientation)
{ {
this.tooltipManager = tooltipManager; this.tooltipManager = tooltipManager;
this.infoboxManager = infoboxManager; this.infoboxManager = infoboxManager;
this.client = client; this.client = client;
this.config = config; this.config = config;
this.eventBus = eventBus; this.eventBus = eventBus;
this.name = name;
this.orientation = orientation;
setPosition(OverlayPosition.TOP_LEFT); setPosition(OverlayPosition.TOP_LEFT);
setClearChildren(false); setClearChildren(false);
setDragTargetable(true);
panelComponent.setWrap(true); panelComponent.setWrap(true);
panelComponent.setBackgroundColor(null); panelComponent.setBackgroundColor(null);
@@ -88,11 +100,15 @@ public class InfoBoxOverlay extends OverlayPanel
eventBus.subscribe(MenuOptionClicked.class, this, this::onMenuOptionClicked); eventBus.subscribe(MenuOptionClicked.class, this, this::onMenuOptionClicked);
} }
@Override
public String getName()
{
return this.name;
}
@Override @Override
public Dimension render(Graphics2D graphics) public Dimension render(Graphics2D graphics)
{ {
final List<InfoBox> infoBoxes = infoboxManager.getInfoBoxes();
final boolean menuOpen = client.isMenuOpen(); final boolean menuOpen = client.isMenuOpen();
if (!menuOpen) 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 // 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. // 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.setPreferredSize(new Dimension(DEFAULT_WRAP_COUNT * (config.infoBoxSize() + GAP), DEFAULT_WRAP_COUNT * (config.infoBoxSize() + GAP)));
panelComponent.setOrientation(config.infoBoxVertical() panelComponent.setOrientation(orientation);
? ComponentOrientation.VERTICAL
: ComponentOrientation.HORIZONTAL);
for (InfoBox box : infoBoxes) for (InfoBox box : infoBoxes)
{ {
@@ -127,6 +141,7 @@ public class InfoBoxOverlay extends OverlayPanel
{ {
infoBoxComponent.setColor(color); infoBoxComponent.setColor(color);
} }
infoBoxComponent.setOutline(config.infoBoxTextOutline());
infoBoxComponent.setImage(box.getScaledImage()); infoBoxComponent.setImage(box.getScaledImage());
infoBoxComponent.setTooltip(box.getTooltip()); infoBoxComponent.setTooltip(box.getTooltip());
infoBoxComponent.setPreferredSize(new Dimension(config.infoBoxSize(), config.infoBoxSize())); infoBoxComponent.setPreferredSize(new Dimension(config.infoBoxSize(), config.infoBoxSize()));
@@ -177,7 +192,7 @@ public class InfoBoxOverlay extends OverlayPanel
public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked) public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked)
{ {
if (menuOptionClicked.getMenuOpcode() != MenuOpcode.RUNELITE_INFOBOX) if (menuOptionClicked.getMenuOpcode() != MenuOpcode.RUNELITE_INFOBOX || hoveredComponent == null)
{ {
return; return;
} }
@@ -187,4 +202,21 @@ public class InfoBoxOverlay extends OverlayPanel
.filter(me -> me.getOption().equals(menuOptionClicked.getOption())) .filter(me -> me.getOption().equals(menuOptionClicked.getOption()))
.findAny().ifPresent(overlayMenuEntry -> eventBus.post(InfoBoxMenuClicked.class, new InfoBoxMenuClicked(overlayMenuEntry, infoBox))); .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;
}
} }

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2020, Adam <Adam@sigterm.info>
* 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"));
}
}

View File

@@ -32,6 +32,8 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.inject.Inject; 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.OpenOSRSConfig;
import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.config.RuneLiteConfig;
import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.Plugin;
@@ -58,6 +60,14 @@ public class InfoBoxManagerTest
@Bind @Bind
private OpenOSRSConfig openOSRSConfig; private OpenOSRSConfig openOSRSConfig;
@Mock
@Bind
private ConfigManager configManager;
@Mock
@Bind
private Client client;
@Before @Before
public void before() public void before()
{ {

View File

@@ -28,8 +28,6 @@ import java.awt.Color;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test; import org.junit.Test;
public class ColorUtilTest public class ColorUtilTest
@@ -97,26 +95,4 @@ public class ColorUtilTest
COLOR_HEXSTRING_MAP.forEach((color, hex) -> COLOR_HEXSTRING_MAP.forEach((color, hex) ->
assertEquals(hex, ColorUtil.colorToHexCode(color))); 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)));
}
} }

View File

@@ -35,7 +35,6 @@ import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer; import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt; import java.awt.image.DataBufferInt;
import java.util.Arrays; import java.util.Arrays;
import java.util.function.Predicate;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -256,11 +255,6 @@ public class ImageUtilTest
assertTrue(bufferedImagesEqual(centeredPixel(GRAY), ImageUtil.fillImage(centeredPixel(BLACK), GRAY))); assertTrue(bufferedImagesEqual(centeredPixel(GRAY), ImageUtil.fillImage(centeredPixel(BLACK), GRAY)));
assertTrue(bufferedImagesEqual(solidColor(3, 3, GREEN), ImageUtil.fillImage(solidColor(3, 3, BLACK), GREEN))); assertTrue(bufferedImagesEqual(solidColor(3, 3, GREEN), ImageUtil.fillImage(solidColor(3, 3, BLACK), GREEN)));
assertTrue(bufferedImagesEqual(oneByOne(BLACK_TRANSPARENT), ImageUtil.fillImage(oneByOne(BLACK_TRANSPARENT), WHITE))); assertTrue(bufferedImagesEqual(oneByOne(BLACK_TRANSPARENT), ImageUtil.fillImage(oneByOne(BLACK_TRANSPARENT), WHITE)));
// fillImage(BufferedImage image, Color color, Predicate<Color> 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 @Test
@@ -287,39 +281,11 @@ public class ImageUtilTest
expected.setRGB(1, 1, new Color(0, true).getRGB()); expected.setRGB(1, 1, new Color(0, true).getRGB());
assertTrue(bufferedImagesEqual(expected, ImageUtil.outlineImage(BLACK_PIXEL_TOP_LEFT, WHITE))); assertTrue(bufferedImagesEqual(expected, ImageUtil.outlineImage(BLACK_PIXEL_TOP_LEFT, WHITE)));
// outlineImage(BufferedImage image, Color color, Predicate<Color> 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) // outlineImage(BufferedImage image, Color color, Boolean outlineCorners)
expected = solidColor(CORNER_SIZE, CORNER_SIZE, WHITE); expected = solidColor(CORNER_SIZE, CORNER_SIZE, WHITE);
expected.setRGB(0, 0, BLACK.getRGB()); expected.setRGB(0, 0, BLACK.getRGB());
assertTrue(bufferedImagesEqual(expected, ImageUtil.outlineImage(BLACK_PIXEL_TOP_LEFT, WHITE, true))); assertTrue(bufferedImagesEqual(expected, ImageUtil.outlineImage(BLACK_PIXEL_TOP_LEFT, WHITE, true)));
assertTrue(bufferedImagesEqual(solidColor(3, 3, BLACK), ImageUtil.outlineImage(centeredPixel(BLACK), BLACK, true))); assertTrue(bufferedImagesEqual(solidColor(3, 3, BLACK), ImageUtil.outlineImage(centeredPixel(BLACK), BLACK, true)));
// outlineImage(BufferedImage image, Color color, Predicate<Color> 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<Color> testPredicate = (color -> ColorUtil.isNotFullyTransparent(color) && color.getRed() > 75 && color.getGreen() > 75 && color.getBlue() > 75);
assertTrue(bufferedImagesEqual(expected, ImageUtil.outlineImage(test, RED, testPredicate, true)));
} }
/** /**