diff --git a/runelite-api/src/main/java/net/runelite/api/InventoryID.java b/runelite-api/src/main/java/net/runelite/api/InventoryID.java
index de07152082..5c58a476d1 100644
--- a/runelite-api/src/main/java/net/runelite/api/InventoryID.java
+++ b/runelite-api/src/main/java/net/runelite/api/InventoryID.java
@@ -28,6 +28,7 @@ public enum InventoryID
{
INVENTORY(93),
EQUIPMENT(94),
+ BANK(95),
PUZZLE_BOX(140);
private final int id;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesConfig.java
new file mode 100644
index 0000000000..36b8ffa0aa
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesConfig.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2018 Charlie Waters
+ * 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.itemprices;
+
+import net.runelite.client.config.Config;
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup(
+ keyName = "itemprices",
+ name = "Item Prices",
+ description = "Configuration for the Item Prices plugin"
+)
+public interface ItemPricesConfig extends Config
+{
+ @ConfigItem(
+ keyName = "showGEPrice",
+ name = "Show Grand Exchange Prices",
+ description = "Grand exchange prices should be shown on tooltips",
+ position = 1
+ )
+ default boolean showGEPrice()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "showHAValue",
+ name = "Show High Alchemy Values",
+ description = "High Alchemy values should be shown on tooltips",
+ position = 2
+ )
+ default boolean showHAValue()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "showEA",
+ name = "Show Price Each on Stacks",
+ description = "The price/value of each item should be shown on stacks",
+ position = 3
+ )
+ default boolean showEA()
+ {
+ return true;
+ }
+
+ @ConfigItem(
+ keyName = "hideInventory",
+ name = "Hide Tooltips on Inventory Items",
+ description = "Tooltips should be hidden on items in the inventory",
+ position = 4
+ )
+ default boolean hideInventory()
+ {
+ return true;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java
new file mode 100644
index 0000000000..1327459093
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2018, Charlie Waters
+ * 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.itemprices;
+
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.text.NumberFormat;
+import javax.inject.Inject;
+import net.runelite.api.Client;
+import net.runelite.api.InventoryID;
+import net.runelite.api.Item;
+import net.runelite.api.ItemComposition;
+import net.runelite.api.ItemContainer;
+import net.runelite.api.ItemID;
+import net.runelite.api.MenuAction;
+import net.runelite.api.MenuEntry;
+import net.runelite.api.widgets.WidgetID;
+import net.runelite.api.widgets.WidgetInfo;
+import net.runelite.client.game.ItemManager;
+import net.runelite.client.ui.overlay.Overlay;
+import net.runelite.client.ui.overlay.OverlayPosition;
+import net.runelite.client.ui.overlay.tooltip.Tooltip;
+import net.runelite.client.ui.overlay.tooltip.TooltipManager;
+import net.runelite.client.util.StackFormatter;
+import net.runelite.http.api.item.ItemPrice;
+
+class ItemPricesOverlay extends Overlay
+{
+ // Used when getting High Alchemy value - multiplied by general store price.
+ private static final float HIGH_ALCHEMY_CONSTANT = 0.6f;
+
+ private static final NumberFormat NUMBER_FORMATTER = NumberFormat.getInstance();
+
+ private static final int INVENTORY_ITEM_WIDGETID = WidgetInfo.INVENTORY.getPackedId();
+ private static final int BANK_INVENTORY_ITEM_WIDGETID = WidgetInfo.BANK_INVENTORY_ITEMS_CONTAINER.getPackedId();
+ private static final int BANK_ITEM_WIDGETID = WidgetInfo.BANK_ITEM_CONTAINER.getPackedId();
+
+ private final Client client;
+ private final ItemPricesConfig config;
+ private final TooltipManager tooltipManager;
+ private final StringBuilder itemStringBuilder = new StringBuilder();
+
+ @Inject
+ ItemManager itemManager;
+
+ @Inject
+ ItemPricesOverlay(Client client, ItemPricesConfig config, TooltipManager tooltipManager)
+ {
+ setPosition(OverlayPosition.DYNAMIC);
+ this.client = client;
+ this.config = config;
+ this.tooltipManager = tooltipManager;
+ }
+
+ @Override
+ public Dimension render(Graphics2D graphics, Point point)
+ {
+ if (client.isMenuOpen())
+ {
+ return null;
+ }
+
+ final MenuEntry[] menuEntries = client.getMenuEntries();
+ final int last = menuEntries.length - 1;
+
+ if (last < 0)
+ {
+ return null;
+ }
+
+ final MenuEntry menuEntry = menuEntries[last];
+ final MenuAction action = menuEntry.getType();
+ final int widgetId = menuEntry.getParam1();
+ final int groupId = WidgetInfo.TO_GROUP(widgetId);
+
+ // Tooltip action type handling
+ switch (action)
+ {
+ case WIDGET_DEFAULT:
+ case ITEM_USE:
+ case ITEM_FIRST_OPTION:
+ case ITEM_SECOND_OPTION:
+ case ITEM_THIRD_OPTION:
+ case ITEM_FOURTH_OPTION:
+ case ITEM_FIFTH_OPTION:
+ // Item tooltip values
+ switch (groupId)
+ {
+ case WidgetID.INVENTORY_GROUP_ID:
+ if (config.hideInventory())
+ {
+ return null;
+ }
+ // intentional fallthrough
+ case WidgetID.BANK_GROUP_ID:
+ case WidgetID.BANK_INVENTORY_GROUP_ID:
+ // Make tooltip
+ final String text = makeValueTooltip(menuEntry);
+ if (text != null)
+ {
+ tooltipManager.add(new Tooltip("
" + text));
+ }
+ break;
+ }
+ break;
+ }
+ return null;
+ }
+
+ private String makeValueTooltip(MenuEntry menuEntry)
+ {
+ // Disabling both disables all value tooltips
+ if (!config.showGEPrice() && !config.showHAValue())
+ {
+ return null;
+ }
+
+ final int widgetId = menuEntry.getParam1();
+ ItemContainer container = null;
+
+ // Inventory item
+ if (widgetId == INVENTORY_ITEM_WIDGETID || widgetId == BANK_INVENTORY_ITEM_WIDGETID)
+ {
+ container = client.getItemContainer(InventoryID.INVENTORY);
+ }
+ // Bank item
+ else if (widgetId == BANK_ITEM_WIDGETID)
+ {
+ container = client.getItemContainer(InventoryID.BANK);
+ }
+
+ if (container == null)
+ {
+ return null;
+ }
+
+ // Find the item in the container to get stack size
+ final Item[] items = container.getItems();
+ final int index = menuEntry.getParam0();
+ if (index < items.length)
+ {
+ final Item item = items[index];
+ return getItemStackValueText(item);
+ }
+
+ return null;
+ }
+
+ private String getItemStackValueText(Item item)
+ {
+ int id = item.getId();
+ int qty = item.getQuantity();
+
+ // Special case for coins and platinum tokens
+ if (id == ItemID.COINS_995)
+ {
+ return NUMBER_FORMATTER.format(qty) + " gp";
+ }
+ else if (id == ItemID.PLATINUM_TOKEN)
+ {
+ return NUMBER_FORMATTER.format(qty * 1000) + " gp";
+ }
+
+ final ItemComposition itemDef = itemManager.getItemComposition(id);
+ // Only check prices for things with store prices
+ if (itemDef.getPrice() <= 0)
+ {
+ return null;
+ }
+
+ int gePrice = 0;
+ int haPrice = 0;
+
+ if (config.showGEPrice())
+ {
+ final ItemPrice price = itemManager.getItemPriceAsync(id);
+ if (price != null)
+ {
+ gePrice = price.getPrice();
+ }
+ }
+ if (config.showHAValue())
+ {
+ haPrice = Math.round(itemDef.getPrice() * HIGH_ALCHEMY_CONSTANT);
+ }
+
+ if (gePrice > 0 || haPrice > 0)
+ {
+ return stackValueText(qty, gePrice, haPrice);
+ }
+
+ return null;
+ }
+
+ private String stackValueText(int qty, int gePrice, int haValue)
+ {
+ if (qty > 1)
+ {
+ itemStringBuilder.append("(").append(qty).append(") ");
+ }
+ if (gePrice > 0)
+ {
+ itemStringBuilder.append("EX: ")
+ .append(StackFormatter.quantityToStackSize(gePrice * qty))
+ .append(" gp");
+ if (config.showEA() && qty > 1)
+ {
+ itemStringBuilder.append(" (")
+ .append(StackFormatter.quantityToStackSize(gePrice))
+ .append(" ea)");
+ }
+ }
+ if (haValue > 0)
+ {
+ itemStringBuilder.append(gePrice > 0 ? ", " : "")
+ .append("HA: ")
+ .append(StackFormatter.quantityToStackSize(haValue * qty))
+ .append(" gp");
+ if (config.showEA() && qty > 1)
+ {
+ itemStringBuilder.append(" (")
+ .append(StackFormatter.quantityToStackSize(haValue))
+ .append(" ea)");
+ }
+ }
+
+ // Build string and reset builder
+ final String text = itemStringBuilder.toString();
+ itemStringBuilder.setLength(0);
+ return text;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesPlugin.java
new file mode 100644
index 0000000000..c83f92a657
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesPlugin.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018 Charlie Waters
+ * 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.itemprices;
+
+import com.google.inject.Provides;
+import javax.inject.Inject;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.ui.overlay.Overlay;
+
+@PluginDescriptor(
+ name = "Item Prices",
+ enabledByDefault = false
+)
+public class ItemPricesPlugin extends Plugin
+{
+ @Inject
+ private ItemPricesConfig config;
+ @Inject
+ private ItemPricesOverlay overlay;
+
+ @Provides
+ ItemPricesConfig getConfig(ConfigManager configManager)
+ {
+ return configManager.getConfig(ItemPricesConfig.class);
+ }
+
+ @Override
+ public Overlay getOverlay()
+ {
+ return overlay;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mousehighlight/MouseHighlightOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/mousehighlight/MouseHighlightOverlay.java
index d5bdbdc62a..2e5ae6f305 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/mousehighlight/MouseHighlightOverlay.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mousehighlight/MouseHighlightOverlay.java
@@ -89,7 +89,7 @@ class MouseHighlightOverlay extends Overlay
}
}
- tooltipManager.add(new Tooltip(option + (Strings.isNullOrEmpty(target) ? "" : " " + target)));
+ tooltipManager.addFront(new Tooltip(option + (Strings.isNullOrEmpty(target) ? "" : " " + target)));
return null;
}
}
diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltip/TooltipManager.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltip/TooltipManager.java
index 3d0f27a8fb..6472381ec3 100644
--- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltip/TooltipManager.java
+++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltip/TooltipManager.java
@@ -28,10 +28,8 @@ import java.util.ArrayList;
import java.util.List;
import javax.inject.Singleton;
import lombok.Getter;
-import lombok.extern.slf4j.Slf4j;
@Singleton
-@Slf4j
public class TooltipManager
{
@Getter
@@ -42,6 +40,11 @@ public class TooltipManager
tooltips.add(tooltip);
}
+ public void addFront(Tooltip tooltip)
+ {
+ tooltips.add(0, tooltip);
+ }
+
public void clear()
{
tooltips.clear();
diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltip/TooltipOverlay.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltip/TooltipOverlay.java
index 763291c4d2..d84074e304 100644
--- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltip/TooltipOverlay.java
+++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltip/TooltipOverlay.java
@@ -57,14 +57,14 @@ public class TooltipOverlay extends Overlay
@Override
public Dimension render(Graphics2D graphics, Point parent)
{
- List tooltips = tooltipManager.getTooltips();
+ final List tooltips = tooltipManager.getTooltips();
if (tooltips.isEmpty())
{
return null;
}
- final Rectangle lastLocation = new Rectangle();
+ Rectangle lastLocation = null;
for (Tooltip tooltip : tooltips)
{
@@ -88,14 +88,26 @@ public class TooltipOverlay extends Overlay
position.setLocation(tooltip.getPosition());
}
- if (lastLocation.contains(position))
+ // check if this tooltip would overlap the last
+ if (lastLocation != null && lastLocation.contains(position))
{
+ // shift tooltip above previous
position.translate(0, -lastLocation.height - PADDING);
}
+ // render tooltip
tooltipComponent.setPosition(position);
- lastLocation.setLocation(position);
- lastLocation.setSize(tooltipComponent.render(graphics, parent));
+ final Dimension thisSize = tooltipComponent.render(graphics, parent);
+
+ // update tooltip bounding rect
+ if (lastLocation == null)
+ {
+ lastLocation = new Rectangle(position, thisSize);
+ }
+ else
+ {
+ lastLocation.setSize(new Dimension(Math.max(lastLocation.width, thisSize.width), lastLocation.height + thisSize.height + PADDING));
+ }
}
tooltipManager.clear();