From 76d954db21b6edfc81747be2be55291b3a3ffcd2 Mon Sep 17 00:00:00 2001 From: Tomas Slusny Date: Thu, 22 Mar 2018 00:35:23 +0100 Subject: [PATCH 1/6] Improve ground items plugin - Reduce amout of iterations per frame by pre-collecting the ground items on game tick - Collect ground items in ground item plugins and pre-compute some easy-to-get values - Replace the looping over all tiles with looping over collected ground items - Change GroundItemOverlay logic to support single list of items instead of multi-dimensional array Signed-off-by: Tomas Slusny --- .../plugins/grounditems/GroundItem.java | 51 +++ .../grounditems/GroundItemsOverlay.java | 375 +++++++----------- .../grounditems/GroundItemsPlugin.java | 198 ++++++++- 3 files changed, 367 insertions(+), 257 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java new file mode 100644 index 0000000000..f713e23ff3 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.grounditems; + +import lombok.Builder; +import lombok.Data; +import lombok.Value; +import net.runelite.api.coords.WorldPoint; + +@Data +@Builder +class GroundItem +{ + private int index; + private int id; + private int itemId; + private String name; + private int quantity; + private WorldPoint location; + private int haPrice; + private int gePrice; + + @Value + static class GroundItemKey + { + private int itemId; + private WorldPoint location; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java index 213c445aac..31b4b8fe63 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java @@ -29,72 +29,50 @@ import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Rectangle; -import static java.lang.Math.max; -import static java.lang.Math.min; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; +import java.util.HashMap; import java.util.Map; import javax.inject.Inject; import net.runelite.api.Client; -import net.runelite.api.Item; -import net.runelite.api.ItemComposition; -import net.runelite.api.ItemID; -import net.runelite.api.ItemLayer; -import net.runelite.api.Node; +import net.runelite.api.Perspective; import net.runelite.api.Player; import net.runelite.api.Point; -import net.runelite.api.Region; -import net.runelite.api.Tile; import net.runelite.api.coords.LocalPoint; -import net.runelite.client.game.ItemManager; +import net.runelite.api.coords.WorldPoint; import net.runelite.client.ui.FontManager; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.components.TextComponent; import net.runelite.client.util.StackFormatter; -import net.runelite.http.api.item.ItemPrice; public class GroundItemsOverlay extends Overlay { - private static final int REGION_SIZE = 104; + private static final int MAX_DISTANCE = 2500; // We must offset the text on the z-axis such that // it doesn't obscure the ground items below it. private static final int OFFSET_Z = 20; // The game won't send anything higher than this value to the plugin - // so we replace any item quantity higher with "Lots" instead. private static final int MAX_QUANTITY = 65535; - // The max distance in tiles between the player and the item. - private static final int MAX_RANGE = 18; // The 15 pixel gap between each drawn ground item. private static final int STRING_GAP = 15; // Threshold for highlighting items as blue. - static final int LOW_VALUE = 20_000; + private static final int LOW_VALUE = 20_000; // Threshold for highlighting items as green. private static final int MEDIUM_VALUE = 100_000; // Threshold for highlighting items as amber. private static final int HIGH_VALUE = 1_000_000; // Threshold for highlighting items as pink. private static final int INSANE_VALUE = 10_000_000; - // Used when getting High Alchemy value - multiplied by general store price. - private static final float HIGH_ALCHEMY_CONSTANT = 0.6f; - // ItemID for coins - private static final int COINS = ItemID.COINS_995; - // Size of the hidden/highlight boxes private static final int RECTANGLE_SIZE = 8; - private Rectangle itemHiddenBox; - private Rectangle itemHighlightBox; - private final Client client; private final GroundItemsPlugin plugin; private final GroundItemsConfig config; private final StringBuilder itemStringBuilder = new StringBuilder(); - - @Inject - private ItemManager itemManager; + private final TextComponent textComponent = new TextComponent(); + private final Map offsetMap = new HashMap<>(); @Inject public GroundItemsOverlay(Client client, GroundItemsPlugin plugin, GroundItemsConfig config) @@ -109,239 +87,156 @@ public class GroundItemsOverlay extends Overlay @Override public Dimension render(Graphics2D graphics) { - Region region = client.getRegion(); - Tile[][][] tiles = region.getTiles(); - FontMetrics fm = graphics.getFontMetrics(); + final FontMetrics fm = graphics.getFontMetrics(); + final Player player = client.getLocalPlayer(); - Player player = client.getLocalPlayer(); if (player == null || client.getViewportWidget() == null) { return null; } graphics.setFont(FontManager.getRunescapeSmallFont()); + offsetMap.clear(); + final LocalPoint localLocation = player.getLocalLocation(); - int z = client.getPlane(); - LocalPoint from = player.getLocalLocation(); - - int lowerX = max(0, from.getRegionX() - MAX_RANGE); - int lowerY = max(0, from.getRegionY() - MAX_RANGE); - - int upperX = min(from.getRegionX() + MAX_RANGE, REGION_SIZE - 1); - int upperY = min(from.getRegionY() + MAX_RANGE, REGION_SIZE - 1); - - // Clear boxes - if (plugin.isHotKeyPressed()) + plugin.getCollectedGroundItems().forEach(item -> { - plugin.getHiddenBoxes().clear(); - plugin.getHighlightBoxes().clear(); - } + final LocalPoint groundPoint = LocalPoint.fromWorld(client, item.getLocation()); - for (int x = lowerX; x <= upperX; ++x) - { - for (int y = lowerY; y <= upperY; ++y) + if (groundPoint == null || localLocation.distanceTo(groundPoint) > MAX_DISTANCE) { - Tile tile = tiles[z][x][y]; - if (tile == null) + return; + } + + final boolean highlighted = plugin.isHighlighted(item.getName()); + + if (!plugin.isHotKeyPressed() && !highlighted + && ((item.getGePrice() > 0 && item.getGePrice() < config.getHideUnderGeValue()) + || item.getHaPrice() < config.getHideUnderHAValue())) + { + return; + } + + final boolean hidden = plugin.isHidden(item.getName()); + final Color color = getCostColor(item.getGePrice() > 0 ? item.getGePrice() : item.getHaPrice(), highlighted); + itemStringBuilder.append(item.getName()); + + if (item.getQuantity() > 1) + { + if (item.getQuantity() >= MAX_QUANTITY) { - continue; + itemStringBuilder.append(" (Lots!)"); } - - ItemLayer itemLayer = tile.getItemLayer(); - if (itemLayer == null) + else { - continue; - } - - Node current = itemLayer.getBottom(); - Map items = new LinkedHashMap<>(); - // adds the items on the ground to the ArrayList to be drawn - while (current instanceof Item) - { - Item item = (Item) current; - int itemId = item.getId(); - int itemQuantity = item.getQuantity(); - ItemComposition itemDefinition = itemManager.getItemComposition(itemId); - - Integer currentQuantity = items.get(itemId); - - if ((config.showHighlightedOnly() ? plugin.isHighlighted(itemDefinition.getName()) : !plugin.isHidden(itemDefinition.getName())) || plugin.isHotKeyPressed()) - { - if (itemDefinition.getNote() != -1) - { - itemId = itemDefinition.getLinkedNoteId(); - } - - int quantity = currentQuantity == null - ? itemQuantity - : currentQuantity + itemQuantity; - - ItemPrice itemPrice = itemManager.getItemPriceAsync(itemId); - - int gePrice, alchPrice; - - if (itemId == COINS) - { - gePrice = quantity; - alchPrice = quantity; - } - else - { - gePrice = itemPrice == null ? 0 : itemPrice.getPrice() * quantity; - alchPrice = Math.round(itemDefinition.getPrice() * HIGH_ALCHEMY_CONSTANT) * quantity; - } - if (plugin.isHighlighted(itemDefinition.getName()) || - gePrice == 0 || ((gePrice >= config.getHideUnderGeValue()) && - (alchPrice >= config.getHideUnderHAValue()))) - { - items.put(itemId, quantity); - } - } - - current = current.getNext(); - } - - // The bottom item is drawn first - List itemIds = new ArrayList<>(items.keySet()); - Collections.reverse(itemIds); - - for (int i = 0; i < itemIds.size(); ++i) - { - Point point = itemLayer.getCanvasLocation(itemLayer.getHeight() + OFFSET_Z); - // if the item is offscreen, don't bother drawing it - if (point == null) - { - continue; - } - - int itemId = itemIds.get(i); - int quantity = items.get(itemId); - ItemComposition item = itemManager.getItemComposition(itemId); - - if (item == null) - { - continue; - } - - itemStringBuilder.append(item.getName()); - if (quantity > 1) - { - if (quantity >= MAX_QUANTITY) - { - itemStringBuilder.append(" (Lots!)"); - } - else - { - itemStringBuilder.append(" (").append(quantity).append(")"); - } - } - - // sets item ID to unnoted version, if noted - if (item.getNote() != -1) - { - itemId = item.getLinkedNoteId(); - } - - Color textColor = config.defaultColor(); // Color to use when drawing the ground item - ItemPrice itemPrice = itemManager.getItemPriceAsync(itemId); - if (itemPrice != null && config.showGEPrice()) - { - int cost = itemPrice.getPrice() * quantity; - - textColor = getCostColor(cost); - - itemStringBuilder.append(" (EX: ") - .append(StackFormatter.quantityToStackSize(cost)) - .append(" gp)"); - } - - if (config.showHAValue()) - { - itemStringBuilder.append(" (HA: ") - .append(Math.round(item.getPrice() * HIGH_ALCHEMY_CONSTANT) * quantity) - .append(" gp)"); - } - - if (plugin.isHighlighted(item.getName())) - { - textColor = config.highlightedColor(); - } - - String itemString = itemStringBuilder.toString(); - itemStringBuilder.setLength(0); - - int screenX = point.getX() + 2 - (fm.stringWidth(itemString) / 2); - int screenY = point.getY() - (STRING_GAP * i); - - // Drawing the shadow for the text, 1px on both x and y - graphics.setColor(Color.BLACK); - graphics.drawString(itemString, screenX + 1, screenY + 1); - // Drawing the text itself - graphics.setColor(textColor); - graphics.drawString(itemString, screenX, screenY); - - if (plugin.isHotKeyPressed()) - { - // Hidden box - itemHiddenBox = new Rectangle - ( - screenX + fm.stringWidth(itemString), - screenY - (fm.getHeight() / 2) - fm.getDescent(), - RECTANGLE_SIZE, - fm.getHeight() / 2 - ); - plugin.getHiddenBoxes().put(itemHiddenBox, item.getName()); - - // Highlight box - itemHighlightBox = new Rectangle - ( - screenX + fm.stringWidth(itemString) + RECTANGLE_SIZE + 2, - screenY - (fm.getHeight() / 2) - fm.getDescent(), - RECTANGLE_SIZE, - fm.getHeight() / 2 - ); - plugin.getHighlightBoxes().put(itemHighlightBox, item.getName()); - - Point mousePos = client.getMouseCanvasPosition(); - boolean mouseInHiddenBox = itemHiddenBox.contains(mousePos.getX(), mousePos.getY()); - boolean mouseInHighlightBox = itemHighlightBox.contains(mousePos.getX(), mousePos.getY()); - - // Draw hidden box - drawRectangle(graphics, itemHiddenBox, mouseInHiddenBox ? Color.RED : textColor, plugin.isHidden(item.getName()), true); - - // Draw highlight box - drawRectangle(graphics, itemHighlightBox, mouseInHighlightBox ? Color.GREEN : textColor, plugin.isHighlighted(item.getName()), false); - } + itemStringBuilder.append(" (").append(item.getQuantity()).append(")"); } } - } + + if (config.showGEPrice() && item.getGePrice() > 0) + { + itemStringBuilder.append(" (EX: ") + .append(StackFormatter.quantityToStackSize(item.getGePrice())) + .append(" gp)"); + } + + if (config.showHAValue() && item.getHaPrice() > 0) + { + itemStringBuilder.append(" (HA: ") + .append(StackFormatter.quantityToStackSize(item.getHaPrice())) + .append(" gp)"); + } + + final String itemString = itemStringBuilder.toString(); + itemStringBuilder.setLength(0); + + final Point textPoint = Perspective.getCanvasTextLocation(client, + graphics, + groundPoint, + itemString, OFFSET_Z); + + if (textPoint == null) + { + return; + } + + final int offset = offsetMap.compute(item.getLocation(), (k, v) -> v != null ? v + 1 : 0); + final int textX = textPoint.getX(); + final int textY = textPoint.getY() - (STRING_GAP * offset); + + textComponent.setText(itemString); + textComponent.setColor(color); + textComponent.setPosition(new java.awt.Point(textX, textY)); + textComponent.render(graphics); + + if (plugin.isHotKeyPressed()) + { + final int stringWidth = fm.stringWidth(itemString); + final int stringHeight = fm.getHeight(); + final int descent = fm.getDescent(); + + // Hidden box + final Rectangle itemHiddenBox = new Rectangle( + textX + stringWidth, + textY - (stringHeight / 2) - descent, + RECTANGLE_SIZE, + stringHeight / 2); + + plugin.getHiddenBoxes().put(itemHiddenBox, item.getName()); + + // Highlight box + final Rectangle itemHighlightBox = new Rectangle( + textX + stringWidth + RECTANGLE_SIZE + 2, + textY - (stringHeight / 2) - descent, + RECTANGLE_SIZE, + stringHeight / 2); + + plugin.getHighlightBoxes().put(itemHighlightBox, item.getName()); + + final Point mousePos = client.getMouseCanvasPosition(); + boolean mouseInHiddenBox = itemHiddenBox.contains(mousePos.getX(), mousePos.getY()); + boolean mouseInHighlightBox = itemHighlightBox.contains(mousePos.getX(), mousePos.getY()); + + // Draw hidden box + drawRectangle(graphics, itemHiddenBox, mouseInHiddenBox ? Color.RED : color, hidden, true); + + // Draw highlight box + drawRectangle(graphics, itemHighlightBox, mouseInHighlightBox ? Color.GREEN : color, highlighted, false); + } + }); return null; } - Color getCostColor(int cost) + Color getCostColor(int cost, boolean highlighted) { + if (highlighted) + { + return config.highlightedColor(); + } + // set the color according to rarity, if possible if (cost >= INSANE_VALUE) // 10,000,000 gp { return config.insaneValueColor(); } - else if (cost >= HIGH_VALUE) // 1,000,000 gp + + if (cost >= HIGH_VALUE) // 1,000,000 gp { return config.highValueColor(); } - else if (cost >= MEDIUM_VALUE) // 100,000 gp + + if (cost >= MEDIUM_VALUE) // 100,000 gp { return config.mediumValueColor(); } - else if (cost >= LOW_VALUE) // 20,000 gp + + if (cost >= LOW_VALUE) // 20,000 gp { return config.lowValueColor(); } - else - { - return config.defaultColor(); - } + + return config.defaultColor(); } private void drawRectangle(Graphics2D graphics, Rectangle rect, Color color, boolean inList, boolean hiddenBox) @@ -360,23 +255,23 @@ public class GroundItemsOverlay extends Overlay graphics.setColor(Color.WHITE); // Minus symbol graphics.drawLine - ( - rect.x + 2, - rect.y + (RECTANGLE_SIZE / 2), - rect.x + RECTANGLE_SIZE - 2, - rect.y + (RECTANGLE_SIZE / 2) - ); + ( + rect.x + 2, + rect.y + (RECTANGLE_SIZE / 2), + rect.x + RECTANGLE_SIZE - 2, + rect.y + (RECTANGLE_SIZE / 2) + ); if (!hiddenBox) { // Plus symbol graphics.drawLine - ( - rect.x + (RECTANGLE_SIZE / 2), - rect.y + 2, - rect.x + (RECTANGLE_SIZE / 2), - rect.y + RECTANGLE_SIZE - 2 - ); + ( + rect.x + (RECTANGLE_SIZE / 2), + rect.y + 2, + rect.x + (RECTANGLE_SIZE / 2), + rect.y + RECTANGLE_SIZE - 2 + ); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java index f07d27c27d..839cdc476a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java @@ -38,22 +38,33 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import java.util.regex.Pattern; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import javax.annotation.Nullable; import javax.inject.Inject; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; +import net.runelite.api.GameState; import net.runelite.api.Item; import net.runelite.api.ItemComposition; +import net.runelite.api.ItemID; import net.runelite.api.ItemLayer; import net.runelite.api.MenuAction; import net.runelite.api.MenuEntry; import net.runelite.api.Node; +import net.runelite.api.Player; import net.runelite.api.Region; import net.runelite.api.Tile; +import net.runelite.api.coords.LocalPoint; import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.FocusChanged; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GameTick; import net.runelite.api.events.MenuEntryAdded; import net.runelite.client.config.ConfigManager; import net.runelite.client.game.ItemManager; @@ -67,8 +78,32 @@ import net.runelite.http.api.item.ItemPrice; @PluginDescriptor( name = "Ground Items" ) +@Slf4j public class GroundItemsPlugin extends Plugin { + //Size of one region + private static final int REGION_SIZE = 104; + // The max distance in tiles between the player and the item. + private static final int MAX_RANGE = 18; + // Used when getting High Alchemy value - multiplied by general store price. + private static final float HIGH_ALCHEMY_CONSTANT = 0.6f; + // ItemID for coins + private static final int COINS = ItemID.COINS_995; + // Collects similar ground items + private static final Collector> GROUND_ITEM_MAP_COLLECTOR = Collectors + .toMap + ((item) -> new GroundItem.GroundItemKey(item.getItemId(), item.getLocation()), Function.identity(), ((a, b) -> + GroundItem.builder() + .index(b.getIndex()) + .id(b.getId()) + .itemId(b.getItemId()) + .name(b.getName()) + .location(b.getLocation()) + .haPrice(a.getHaPrice() + b.getHaPrice()) + .gePrice(a.getGePrice() + b.getGePrice()) + .quantity(a.getQuantity() + b.getQuantity()) + .build())); + @Getter(AccessLevel.PACKAGE) private final Map hiddenBoxes = new HashMap<>(); @@ -94,9 +129,6 @@ public class GroundItemsPlugin extends Plugin @Inject private Client client; - @Inject - private ConfigManager configManager; - @Inject private ItemManager itemManager; @@ -106,6 +138,9 @@ public class GroundItemsPlugin extends Plugin @Inject private GroundItemsOverlay overlay; + @Getter + private final List collectedGroundItems = new ArrayList<>(); + private final List groundItems = new ArrayList<>(); private LoadingCache highlightedItems; private LoadingCache hiddenItems; @@ -125,7 +160,6 @@ public class GroundItemsPlugin extends Plugin protected void startUp() { reset(); - mouseManager.registerMouseListener(inputListener); keyManager.registerKeyListener(inputListener); } @@ -135,6 +169,14 @@ public class GroundItemsPlugin extends Plugin { mouseManager.unregisterMouseListener(inputListener); keyManager.unregisterKeyListener(inputListener); + groundItems.clear(); + collectedGroundItems.clear(); + highlightedItems.invalidateAll(); + highlightedItems = null; + hiddenItems.invalidateAll(); + hiddenItems = null; + hiddenItemList = null; + highlightedItemsList = null; } @Subscribe @@ -146,6 +188,139 @@ public class GroundItemsPlugin extends Plugin } } + @Subscribe + public void onGameStateChanged(final GameStateChanged event) + { + if (event.getGameState() == GameState.LOGGED_IN) + { + groundItems.clear(); + collectedGroundItems.clear(); + } + } + + @Subscribe + public void onGameTick(final GameTick event) + { + final Player player = client.getLocalPlayer(); + + if (player == null || client.getViewportWidget() == null) + { + return; + } + + final Region region = client.getRegion(); + final Tile[][][] tiles = region.getTiles(); + final int z = client.getPlane(); + final LocalPoint from = player.getLocalLocation(); + + final int lowerX = Math.max(0, from.getRegionX() - MAX_RANGE); + final int lowerY = Math.max(0, from.getRegionY() - MAX_RANGE); + + final int upperX = Math.min(from.getRegionX() + MAX_RANGE, REGION_SIZE - 1); + final int upperY = Math.min(from.getRegionY() + MAX_RANGE, REGION_SIZE - 1); + + groundItems.clear(); + int index = 0; + + for (int x = lowerX; x <= upperX; ++x) + { + for (int y = lowerY; y <= upperY; ++y) + { + Tile tile = tiles[z][x][y]; + if (tile == null) + { + continue; + } + + ItemLayer itemLayer = tile.getItemLayer(); + if (itemLayer == null) + { + continue; + } + + Node current = itemLayer.getBottom(); + + // adds the items on the ground to the ArrayList to be drawn + while (current instanceof Item) + { + final Item item = (Item) current; + + // Continue iteration + current = current.getNext(); + + // Build ground item + final GroundItem groundItem = buildGroundItem(tile, item); + + if (groundItem != null) + { + groundItems.add(groundItem); + + // Required for preserving the correct order later + groundItem.setIndex(index); + index++; + } + } + } + } + + // Group ground items together and sort them properly + collectedGroundItems.clear(); + groundItems.stream() + .collect(GROUND_ITEM_MAP_COLLECTOR) + .values() + .stream() + .sorted((left, right) -> Integer.compare(right.getIndex(), left.getIndex())) + .collect(Collectors.toCollection(() -> collectedGroundItems)); + } + + @Nullable + private GroundItem buildGroundItem(final Tile tile, final Item item) + { + // Collect the data for the item + final int itemId = item.getId(); + final ItemComposition itemComposition = itemManager.getItemComposition(itemId); + final int realItemId = itemComposition.getNote() != -1 ? itemComposition.getLinkedNoteId() : itemId; + final int alchPrice = Math.round(itemComposition.getPrice() * HIGH_ALCHEMY_CONSTANT); + final String name = itemComposition.getName(); + + final boolean hidden = isHidden(name); + + if (!isHotKeyPressed() && hidden) + { + return null; + } + + final boolean highlighted = isHighlighted(name); + + if (config.showHighlightedOnly() && !isHotKeyPressed() && !highlighted) + { + return null; + } + + final GroundItem groundItem = GroundItem.builder() + .id(itemId) + .location(tile.getWorldLocation()) + .itemId(realItemId) + .quantity(item.getQuantity()) + .name(itemComposition.getName()) + .haPrice(alchPrice * item.getQuantity()) + .build(); + + // Set the correct item price + if (realItemId == COINS) + { + groundItem.setHaPrice(item.getQuantity()); + groundItem.setGePrice(item.getQuantity()); + } + else + { + final ItemPrice itemPrice = itemManager.getItemPriceAsync(realItemId); + groundItem.setGePrice(itemPrice != null ? itemPrice.getPrice() * item.getQuantity() : 0); + } + + return groundItem; + } + private void reset() { Splitter COMMA_SPLITTER = Splitter.on(Pattern.compile("\\s*,\\s*")); @@ -219,20 +394,9 @@ public class GroundItemsPlugin extends Plugin ItemPrice itemPrice = getItemPrice(itemComposition); int price = itemPrice == null ? itemComposition.getPrice() : itemPrice.getPrice(); int cost = quantity * price; + Color color = overlay.getCostColor(cost, isHighlighted(itemComposition.getName())); - Color color = null; - - if (cost >= GroundItemsOverlay.LOW_VALUE) - { - color = overlay.getCostColor(cost); - } - - if (isHighlighted(itemComposition.getName())) - { - color = config.highlightedColor(); - } - - if (color != null) + if (!color.equals(config.defaultColor())) { String hexColor = Integer.toHexString(color.getRGB() & 0xFFFFFF); String colTag = ""; From 5272e2d9b696469d2933dc39294a790277c37622 Mon Sep 17 00:00:00 2001 From: Tomas Slusny Date: Tue, 20 Mar 2018 18:09:30 +0100 Subject: [PATCH 2/6] Color the hidden items gray when in highlight mode Color hidden ground items gray when in item highlight mode to show different between non-hidden and hidden items. Signed-off-by: Tomas Slusny --- .../client/plugins/grounditems/GroundItemsOverlay.java | 10 ++++++++-- .../client/plugins/grounditems/GroundItemsPlugin.java | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java index 31b4b8fe63..a3405ebdab 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java @@ -118,7 +118,8 @@ public class GroundItemsOverlay extends Overlay } final boolean hidden = plugin.isHidden(item.getName()); - final Color color = getCostColor(item.getGePrice() > 0 ? item.getGePrice() : item.getHaPrice(), highlighted); + final Color color = getCostColor(item.getGePrice() > 0 ? item.getGePrice() : item.getHaPrice(), + highlighted, hidden); itemStringBuilder.append(item.getName()); if (item.getQuantity() > 1) @@ -208,8 +209,13 @@ public class GroundItemsOverlay extends Overlay return null; } - Color getCostColor(int cost, boolean highlighted) + Color getCostColor(int cost, boolean highlighted, boolean hidden) { + if (hidden) + { + return Color.GRAY; + } + if (highlighted) { return config.highlightedColor(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java index 839cdc476a..441bfb288b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java @@ -394,7 +394,8 @@ public class GroundItemsPlugin extends Plugin ItemPrice itemPrice = getItemPrice(itemComposition); int price = itemPrice == null ? itemComposition.getPrice() : itemPrice.getPrice(); int cost = quantity * price; - Color color = overlay.getCostColor(cost, isHighlighted(itemComposition.getName())); + Color color = overlay.getCostColor(cost, isHighlighted(itemComposition.getName()), + isHidden(itemComposition.getName())); if (!color.equals(config.defaultColor())) { From 9dfd67112186b6decfaf28bc6d8dd276eb5cb9cc Mon Sep 17 00:00:00 2001 From: Tomas Slusny Date: Mon, 26 Mar 2018 16:13:18 +0200 Subject: [PATCH 3/6] Reduce the amount of temporary objects in items - Reduce the amount of GroundItem objects created by mutating objects instead of creating merged copies - Reduce the amount of stream transformations required by using LinkedHashMap and sorting the ground items before collection Signed-off-by: Tomas Slusny --- .../plugins/grounditems/GroundItem.java | 1 - .../grounditems/GroundItemsOverlay.java | 2 +- .../grounditems/GroundItemsPlugin.java | 42 +++++++------------ 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java index f713e23ff3..de639550f1 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java @@ -33,7 +33,6 @@ import net.runelite.api.coords.WorldPoint; @Builder class GroundItem { - private int index; private int id; private int itemId; private String name; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java index a3405ebdab..12bfea2ee6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java @@ -99,7 +99,7 @@ public class GroundItemsOverlay extends Overlay offsetMap.clear(); final LocalPoint localLocation = player.getLocalLocation(); - plugin.getCollectedGroundItems().forEach(item -> + plugin.getCollectedGroundItems().values().forEach(item -> { final LocalPoint groundPoint = LocalPoint.fromWorld(client, item.getLocation()); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java index 441bfb288b..e3f5d138fc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java @@ -28,6 +28,7 @@ import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.cache.CacheBuilder; import com.google.common.cache.LoadingCache; +import com.google.common.collect.Lists; import com.google.common.eventbus.Subscribe; import com.google.inject.Provides; import java.awt.Color; @@ -35,6 +36,7 @@ import java.awt.Rectangle; import static java.lang.Boolean.TRUE; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -89,20 +91,6 @@ public class GroundItemsPlugin extends Plugin private static final float HIGH_ALCHEMY_CONSTANT = 0.6f; // ItemID for coins private static final int COINS = ItemID.COINS_995; - // Collects similar ground items - private static final Collector> GROUND_ITEM_MAP_COLLECTOR = Collectors - .toMap - ((item) -> new GroundItem.GroundItemKey(item.getItemId(), item.getLocation()), Function.identity(), ((a, b) -> - GroundItem.builder() - .index(b.getIndex()) - .id(b.getId()) - .itemId(b.getItemId()) - .name(b.getName()) - .location(b.getLocation()) - .haPrice(a.getHaPrice() + b.getHaPrice()) - .gePrice(a.getGePrice() + b.getGePrice()) - .quantity(a.getQuantity() + b.getQuantity()) - .build())); @Getter(AccessLevel.PACKAGE) private final Map hiddenBoxes = new HashMap<>(); @@ -139,11 +127,23 @@ public class GroundItemsPlugin extends Plugin private GroundItemsOverlay overlay; @Getter - private final List collectedGroundItems = new ArrayList<>(); + private final Map collectedGroundItems = new LinkedHashMap<>(); private final List groundItems = new ArrayList<>(); private LoadingCache highlightedItems; private LoadingCache hiddenItems; + // Collects similar ground items + private final Collector> groundItemMapCollector = Collectors + .toMap + ((item) -> new GroundItem.GroundItemKey(item.getItemId(), item.getLocation()), Function.identity(), (a, b) -> + { + b.setHaPrice(a.getHaPrice() + b.getHaPrice()); + b.setGePrice(a.getGePrice() + b.getGePrice()); + b.setQuantity(a.getQuantity() + b.getQuantity()); + return b; + }, + () -> collectedGroundItems); + @Provides GroundItemsConfig provideConfig(ConfigManager configManager) { @@ -220,7 +220,6 @@ public class GroundItemsPlugin extends Plugin final int upperY = Math.min(from.getRegionY() + MAX_RANGE, REGION_SIZE - 1); groundItems.clear(); - int index = 0; for (int x = lowerX; x <= upperX; ++x) { @@ -254,10 +253,6 @@ public class GroundItemsPlugin extends Plugin if (groundItem != null) { groundItems.add(groundItem); - - // Required for preserving the correct order later - groundItem.setIndex(index); - index++; } } } @@ -265,12 +260,7 @@ public class GroundItemsPlugin extends Plugin // Group ground items together and sort them properly collectedGroundItems.clear(); - groundItems.stream() - .collect(GROUND_ITEM_MAP_COLLECTOR) - .values() - .stream() - .sorted((left, right) -> Integer.compare(right.getIndex(), left.getIndex())) - .collect(Collectors.toCollection(() -> collectedGroundItems)); + Lists.reverse(groundItems).stream().collect(groundItemMapCollector); } @Nullable From 42353e48634765de4b69e1332230da4c877e6cbf Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 30 Mar 2018 18:32:14 -0400 Subject: [PATCH 4/6] ground items overlay: use a loop --- .../client/plugins/grounditems/GroundItemsOverlay.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java index 12bfea2ee6..36c74e7f77 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java @@ -99,13 +99,13 @@ public class GroundItemsOverlay extends Overlay offsetMap.clear(); final LocalPoint localLocation = player.getLocalLocation(); - plugin.getCollectedGroundItems().values().forEach(item -> + for (GroundItem item : plugin.getCollectedGroundItems().values()) { final LocalPoint groundPoint = LocalPoint.fromWorld(client, item.getLocation()); if (groundPoint == null || localLocation.distanceTo(groundPoint) > MAX_DISTANCE) { - return; + continue; } final boolean highlighted = plugin.isHighlighted(item.getName()); @@ -114,7 +114,7 @@ public class GroundItemsOverlay extends Overlay && ((item.getGePrice() > 0 && item.getGePrice() < config.getHideUnderGeValue()) || item.getHaPrice() < config.getHideUnderHAValue())) { - return; + continue; } final boolean hidden = plugin.isHidden(item.getName()); @@ -158,7 +158,7 @@ public class GroundItemsOverlay extends Overlay if (textPoint == null) { - return; + continue; } final int offset = offsetMap.compute(item.getLocation(), (k, v) -> v != null ? v + 1 : 0); @@ -204,7 +204,7 @@ public class GroundItemsOverlay extends Overlay // Draw highlight box drawRectangle(graphics, itemHighlightBox, mouseInHighlightBox ? Color.GREEN : color, highlighted, false); } - }); + } return null; } From 819c7c6ec71b2b103e59c5d72faf393890206d4e Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 30 Mar 2018 18:27:20 -0400 Subject: [PATCH 5/6] Add item layer changed event --- .../runelite/api/events/ItemLayerChanged.java | 34 +++++++++++++++++++ .../java/net/runelite/mixins/RSTileMixin.java | 9 +++++ 2 files changed, 43 insertions(+) create mode 100644 runelite-api/src/main/java/net/runelite/api/events/ItemLayerChanged.java diff --git a/runelite-api/src/main/java/net/runelite/api/events/ItemLayerChanged.java b/runelite-api/src/main/java/net/runelite/api/events/ItemLayerChanged.java new file mode 100644 index 0000000000..ba1e3fc0e3 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/ItemLayerChanged.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018, 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.api.events; + +import lombok.Value; +import net.runelite.api.Tile; + +@Value +public class ItemLayerChanged +{ + private Tile tile; +} diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSTileMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSTileMixin.java index a084c8982c..9c31b9e375 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSTileMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSTileMixin.java @@ -38,6 +38,7 @@ import net.runelite.api.events.DecorativeObjectSpawned; import net.runelite.api.events.GameObjectChanged; import net.runelite.api.events.GameObjectDespawned; import net.runelite.api.events.GameObjectSpawned; +import net.runelite.api.events.ItemLayerChanged; import net.runelite.api.events.GroundObjectChanged; import net.runelite.api.events.GroundObjectDespawned; import net.runelite.api.events.GroundObjectSpawned; @@ -256,4 +257,12 @@ public abstract class RSTileMixin implements RSTile } } } + + @FieldHook("itemLayer") + @Inject + public void itemLayerChanged(int idx) + { + ItemLayerChanged itemLayerChanged = new ItemLayerChanged(this); + eventBus.post(itemLayerChanged); + } } From 8385ef174b7412871467e0b1da45875d5de45910 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 30 Mar 2018 18:28:48 -0400 Subject: [PATCH 6/6] ground items: use item layer changed event --- .../plugins/grounditems/GroundItemsOverlay.java | 3 +++ .../plugins/grounditems/GroundItemsPlugin.java | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java index 36c74e7f77..00ee6b8a96 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2017, Aria + * Copyright (c) 2018, Adam * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -95,6 +96,8 @@ public class GroundItemsOverlay extends Overlay return null; } + plugin.checkItems(); + graphics.setFont(FontManager.getRunescapeSmallFont()); offsetMap.clear(); final LocalPoint localLocation = player.getLocalLocation(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java index e3f5d138fc..6f23e313c5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2017, Aria + * Copyright (c) 2018, Adam * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -66,7 +67,7 @@ import net.runelite.api.coords.LocalPoint; import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.FocusChanged; import net.runelite.api.events.GameStateChanged; -import net.runelite.api.events.GameTick; +import net.runelite.api.events.ItemLayerChanged; import net.runelite.api.events.MenuEntryAdded; import net.runelite.client.config.ConfigManager; import net.runelite.client.game.ItemManager; @@ -104,6 +105,7 @@ public class GroundItemsPlugin extends Plugin private List hiddenItemList = new ArrayList<>(); private List highlightedItemsList = new ArrayList<>(); + private boolean dirty; @Inject private GroundItemInputListener inputListener; @@ -199,15 +201,22 @@ public class GroundItemsPlugin extends Plugin } @Subscribe - public void onGameTick(final GameTick event) + public void onItemLayerChanged(ItemLayerChanged event) + { + dirty = true; + } + + void checkItems() { final Player player = client.getLocalPlayer(); - if (player == null || client.getViewportWidget() == null) + if (!dirty || player == null || client.getViewportWidget() == null) { return; } + dirty = false; + final Region region = client.getRegion(); final Tile[][][] tiles = region.getTiles(); final int z = client.getPlane(); @@ -330,6 +339,8 @@ public class GroundItemsPlugin extends Plugin .maximumSize(512L) .expireAfterAccess(10, TimeUnit.MINUTES) .build(new WildcardMatchLoader(hiddenItemList)); + + dirty = true; } private ItemPrice getItemPrice(ItemComposition itemComposition)