diff --git a/runelite-api/src/main/java/net/runelite/api/VarPlayer.java b/runelite-api/src/main/java/net/runelite/api/VarPlayer.java index ed2f25e74e..62489df3f0 100644 --- a/runelite-api/src/main/java/net/runelite/api/VarPlayer.java +++ b/runelite-api/src/main/java/net/runelite/api/VarPlayer.java @@ -68,6 +68,11 @@ public enum VarPlayer */ THRONE_OF_MISCELLANIA(359), + /** + * Item currently active in the creation of a buy or sell offer + */ + CURRENT_GE_ITEM(1151), + /** * Experience tracker goal start. */ diff --git a/runelite-api/src/main/java/net/runelite/api/Varbits.java b/runelite-api/src/main/java/net/runelite/api/Varbits.java index f3ce1e6e12..ca053bd207 100644 --- a/runelite-api/src/main/java/net/runelite/api/Varbits.java +++ b/runelite-api/src/main/java/net/runelite/api/Varbits.java @@ -468,7 +468,14 @@ public enum Varbits BANK_TAB_SIX_COUNT(4176), BANK_TAB_SEVEN_COUNT(4177), BANK_TAB_EIGHT_COUNT(4178), - BANK_TAB_NINE_COUNT(4179); + BANK_TAB_NINE_COUNT(4179), + + /** + * Type of GE offer currently being created + * 0 = buy + * 1 = sell + */ + GE_OFFER_CREATION_TYPE(4397); /** * The raw varbit ID. diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java index a8a1ec668e..0473179604 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java @@ -350,6 +350,7 @@ public class WidgetID static final int ROOT_INTERFACE_CONTAINER = 62; static final int BANK_CONTAINER = 64; static final int INTERFACE_CONTAINER = 65; + static final int INVENTORY_CONTAINER = 69; } static class ResizableViewport @@ -383,6 +384,7 @@ public class WidgetID static final int PRAYER_ICON = 63; static final int MAGIC_ICON = 64; static final int INTERFACE_CONTAINER = 65; + static final int INVENTORY_CONTAINER = 71; } static class ResizableViewportBottomLine @@ -415,6 +417,7 @@ public class WidgetID static final int MUSIC_TAB = 40; static final int MUSIC_ICON = 46; static final int MAGIC_ICON = 63; + static final int INVENTORY_CONTAINER = 71; } static class Chatbox diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java index 7808e06cba..83b335d6be 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java @@ -168,6 +168,7 @@ public enum WidgetInfo FIXED_VIEWPORT_ROOT_INTERFACE_CONTAINER(WidgetID.FIXED_VIEWPORT_GROUP_ID, WidgetID.FixedViewport.ROOT_INTERFACE_CONTAINER), FIXED_VIEWPORT_BANK_CONTAINER(WidgetID.FIXED_VIEWPORT_GROUP_ID, WidgetID.FixedViewport.BANK_CONTAINER), FIXED_VIEWPORT_INTERFACE_CONTAINER(WidgetID.FIXED_VIEWPORT_GROUP_ID, WidgetID.FixedViewport.INTERFACE_CONTAINER), + FIXED_VIEWPORT_INVENTORY_CONTAINER(WidgetID.FIXED_VIEWPORT_GROUP_ID, WidgetID.FixedViewport.INVENTORY_CONTAINER), FIXED_VIEWPORT_COMBAT_TAB(WidgetID.FIXED_VIEWPORT_GROUP_ID, WidgetID.FixedViewport.COMBAT_TAB), FIXED_VIEWPORT_STATS_TAB(WidgetID.FIXED_VIEWPORT_GROUP_ID, WidgetID.FixedViewport.STATS_TAB), FIXED_VIEWPORT_QUESTS_TAB(WidgetID.FIXED_VIEWPORT_GROUP_ID, WidgetID.FixedViewport.QUESTS_TAB), @@ -258,7 +259,9 @@ public enum WidgetInfo RESIZABLE_VIEWPORT_BOTTOM_LINE_OPTIONS_ICON(WidgetID.RESIZABLE_VIEWPORT_BOTTOM_LINE_GROUP_ID, WidgetID.ResizableViewportBottomLine.SETTINGS_ICON), RESIZABLE_VIEWPORT_BOTTOM_LINE_EMOTES_ICON(WidgetID.RESIZABLE_VIEWPORT_BOTTOM_LINE_GROUP_ID, WidgetID.ResizableViewportBottomLine.EMOTE_ICON), RESIZABLE_VIEWPORT_BOTTOM_LINE_MUSIC_ICON(WidgetID.RESIZABLE_VIEWPORT_BOTTOM_LINE_GROUP_ID, WidgetID.ResizableViewportBottomLine.MUSIC_ICON), + RESIZABLE_VIEWPORT_BOTTOM_LINE_INVENTORY_CONTAINER(WidgetID.RESIZABLE_VIEWPORT_BOTTOM_LINE_GROUP_ID, WidgetID.ResizableViewportBottomLine.INVENTORY_CONTAINER), RESIZABLE_VIEWPORT_INTERFACE_CONTAINER(WidgetID.RESIZABLE_VIEWPORT_OLD_SCHOOL_BOX_GROUP_ID, WidgetID.ResizableViewport.INTERFACE_CONTAINER), + RESIZABLE_VIEWPORT_INVENTORY_CONTAINER(WidgetID.RESIZABLE_VIEWPORT_OLD_SCHOOL_BOX_GROUP_ID, WidgetID.ResizableViewport.INVENTORY_CONTAINER), RESIZABLE_VIEWPORT_BOTTOM_LINE_INTERFACE_CONTAINER(WidgetID.RESIZABLE_VIEWPORT_BOTTOM_LINE_GROUP_ID, WidgetID.ResizableViewport.INTERFACE_CONTAINER), PRAYER_THICK_SKIN(WidgetID.PRAYER_GROUP_ID, WidgetID.Prayer.THICK_SKIN), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatConfig.java index c9fa8af82b..3c7d586a86 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatConfig.java @@ -52,6 +52,16 @@ public interface ItemStatConfig extends Config return true; } + @ConfigItem( + keyName = "geStats", + name = "Enable GE item information", + description = "Shows an item information panel when buying items in the GE" + ) + default boolean geStats() + { + return true; + } + @ConfigItem( keyName = "relative", name = "Show Relative", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatPlugin.java index 250a6410cd..b32ddaf7bd 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatPlugin.java @@ -24,13 +24,45 @@ */ package net.runelite.client.plugins.itemstats; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.inject.Binder; import com.google.inject.Inject; import com.google.inject.Provides; +import java.awt.FontMetrics; +import java.util.List; +import java.util.Map; +import java.util.Set; +import net.runelite.api.Client; +import net.runelite.api.FontID; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemContainer; +import net.runelite.api.ItemID; +import net.runelite.api.SpriteID; +import net.runelite.api.VarPlayer; +import net.runelite.api.Varbits; +import net.runelite.api.events.ConfigChanged; +import net.runelite.api.events.ScriptCallbackEvent; +import net.runelite.api.events.VarbitChanged; +import net.runelite.api.events.WidgetHiddenChanged; +import net.runelite.api.widgets.JavaScriptCallback; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.api.widgets.WidgetTextAlignment; +import net.runelite.api.widgets.WidgetType; import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.ItemManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.JagexColors; import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.util.StackFormatter; +import net.runelite.http.api.item.ItemEquipmentStats; +import net.runelite.http.api.item.ItemStats; @PluginDescriptor( name = "Item Stats", @@ -39,12 +71,27 @@ import net.runelite.client.ui.overlay.OverlayManager; ) public class ItemStatPlugin extends Plugin { + private static final int ORANGE_TEXT = JagexColors.DARK_ORANGE_INTERFACE_TEXT.getRGB(); + private static final int YELLOW_TEXT = JagexColors.YELLOW_INTERFACE_TEXT.getRGB(); + private static final int TEXT_HEIGHT = 11; + @Inject private OverlayManager overlayManager; @Inject private ItemStatOverlay overlay; + @Inject + private Client client; + + @Inject + private ItemManager itemManager; + + @Inject + private ItemStatConfig config; + + private Widget itemInformationTitle; + @Provides ItemStatConfig getConfig(ConfigManager configManager) { @@ -67,5 +114,323 @@ public class ItemStatPlugin extends Plugin protected void shutDown() throws Exception { overlayManager.remove(overlay); + resetGEInventory(); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getKey().equals("geStats")) + { + resetGEInventory(); + } + } + + @Subscribe + public void onWidgetHiddenChanged(WidgetHiddenChanged event) + { + if (!config.geStats()) + { + return; + } + + if (event.getWidget() == client.getWidget(WidgetInfo.INVENTORY)) + { + resetGEInventory(); + } + } + + @Subscribe + public void onVarbitChanged(VarbitChanged event) + { + if (client.getVar(VarPlayer.CURRENT_GE_ITEM) == -1 && config.geStats()) + { + resetGEInventory(); + } + } + + @Subscribe + public void onScriptCallbackEvent(ScriptCallbackEvent event) + { + if (event.getEventName().equals("geBuilt") && config.geStats()) + { + int currentGeItem = client.getVar(VarPlayer.CURRENT_GE_ITEM); + if (currentGeItem != -1 && client.getVar(Varbits.GE_OFFER_CREATION_TYPE) == 0) + { + createItemInformation(currentGeItem); + } + } + } + + private void createItemInformation(int id) + { + final ItemStats itemStats = itemManager.getItemStats(id, false); + + if (itemStats == null || !itemStats.isEquipable()) + { + return; + } + + final ItemEquipmentStats equipmentStats = itemStats.getEquipment(); + + if (equipmentStats == null) + { + return; + } + + final Widget geInv = client.getWidget(WidgetInfo.GRAND_EXCHANGE_INVENTORY_ITEMS_CONTAINER); + + if (geInv == null) + { + return; + } + + final Widget invContainer = getInventoryContainer(); + + if (invContainer == null) + { + return; + } + + invContainer.deleteAllChildren(); + geInv.setHidden(true); + + int yPos = 0; + + final FontMetrics smallFM = client.getCanvas().getFontMetrics(FontManager.getRunescapeSmallFont()); + + // HEADER + + itemInformationTitle = createText(invContainer, "Item Information", FontID.BOLD_12, ORANGE_TEXT, + 8, 8, invContainer.getWidth(), 16); + itemInformationTitle.setYTextAlignment(WidgetTextAlignment.CENTER); + + Widget closeButton = invContainer.createChild(-1, WidgetType.GRAPHIC); + closeButton.setOriginalY(8); + closeButton.setOriginalX(invContainer.getWidth() - 24); + closeButton.setOriginalHeight(16); + closeButton.setOriginalWidth(16); + closeButton.setSpriteId(SpriteID.BOTTOM_LINE_MODE_WINDOW_CLOSE_BUTTON_SMALL); + closeButton.setAction(0, "Close"); + closeButton.setOnMouseOverListener((JavaScriptCallback) (ev) -> + { + closeButton.setSpriteId(SpriteID.BOTTOM_LINE_MODE_WINDOW_CLOSE_BUTTON_SMALL_HOVERED); + }); + closeButton.setOnMouseLeaveListener((JavaScriptCallback) (ev) -> + { + closeButton.setSpriteId(SpriteID.BOTTOM_LINE_MODE_WINDOW_CLOSE_BUTTON_SMALL); + }); + closeButton.setOnOpListener((JavaScriptCallback) (ev) -> resetGEInventory()); + closeButton.setHasListener(true); + closeButton.revalidate(); + + yPos += 15; + + createSeparator(invContainer, yPos); + + // ICON AND TITLE + + yPos += 25; + + Widget icon = invContainer.createChild(-1, WidgetType.GRAPHIC); + icon.setOriginalX(8); + icon.setOriginalY(yPos); + icon.setOriginalWidth(36); + icon.setOriginalHeight(32); + icon.setItemId(id); + icon.setItemQuantityMode(0); + icon.setBorderType(1); + icon.revalidate(); + + Widget itemName = createText(invContainer, itemManager.getItemComposition(id).getName(), FontID.PLAIN_12, ORANGE_TEXT, + 50, yPos, invContainer.getWidth() - 40, 30); + itemName.setYTextAlignment(WidgetTextAlignment.CENTER); + + yPos += 20; + + createSeparator(invContainer, yPos); + + // STATS HEADER + + yPos += 25; + + createText(invContainer, "Attack", FontID.PLAIN_11, ORANGE_TEXT, 5, yPos, 50, -1); + + int defenceXPos = invContainer.getWidth() - (smallFM.stringWidth("Defence") + 5); + createText(invContainer, "Defence", FontID.PLAIN_11, ORANGE_TEXT, defenceXPos, yPos, 50, -1); + + // STYLE BONUSES + + final Set stats = ImmutableSet.of( + "Stab", + "Slash", + "Crush", + "Magic", + "Ranged" + ); + + final List attackStats = ImmutableList.of( + equipmentStats.getAstab(), + equipmentStats.getAslash(), + equipmentStats.getAcrush(), + equipmentStats.getAmagic(), + equipmentStats.getArange() + ); + + final List defenceStats = ImmutableList.of( + equipmentStats.getDstab(), + equipmentStats.getDslash(), + equipmentStats.getDcrush(), + equipmentStats.getDmagic(), + equipmentStats.getDrange() + ); + + int index = 0; + + for (final String stat : stats) + { + yPos += TEXT_HEIGHT + 2; + + // Style label + final Widget styleText = createText(invContainer, stat, FontID.PLAIN_11, ORANGE_TEXT, + 0, yPos, invContainer.getWidth(), -1); + styleText.setXTextAlignment(WidgetTextAlignment.CENTER); + + // Attack bonus + createText(invContainer, attackStats.get(index).toString(), FontID.PLAIN_11, YELLOW_TEXT, + 5, yPos, 50, -1); + + // Defence bonus + final int defenceX = invContainer.getWidth() - (smallFM.stringWidth(defenceStats.get(index).toString()) + 5); + createText(invContainer, defenceStats.get(index).toString(), FontID.PLAIN_11, YELLOW_TEXT, + defenceX, yPos, 50, -1); + + index++; + } + + // MISC BONUSES + + yPos += TEXT_HEIGHT + 8; + + final Map miscStats = ImmutableMap.of( + "Strength", equipmentStats.getStr(), + "Ranged Strength", equipmentStats.getRstr(), + "Magic Damage", equipmentStats.getMdmg(), + "Prayer Bonus", equipmentStats.getPrayer() + ); + + for (final Map.Entry miscStat : miscStats.entrySet()) + { + final String name = miscStat.getKey(); + final String value = miscStat.getValue().toString(); + + // Stat label + createText(invContainer, name, FontID.PLAIN_11, ORANGE_TEXT, 5, yPos, 50, -1); + + // Stat bonus + int valueXPos = invContainer.getWidth() - (smallFM.stringWidth(value) + 5); + createText(invContainer, value, FontID.PLAIN_11, YELLOW_TEXT, valueXPos, yPos, 50, -1); + + yPos += TEXT_HEIGHT + 2; + } + + // COINS + + createSeparator(invContainer, invContainer.getHeight() - 40); + + final String coinText = "You have " + StackFormatter.quantityToRSStackSize(getCurrentGP()) + + (getCurrentGP() == 1 ? " coin." : " coins."); + + final Widget coinWidget = createText(invContainer, coinText, FontID.PLAIN_12, ORANGE_TEXT, + 0, invContainer.getHeight() - 18, invContainer.getWidth(), -1); + + coinWidget.setXTextAlignment(WidgetTextAlignment.CENTER); + } + + private static Widget createText(Widget parent, String text, int fontId, int textColor, + int x, int y, int width, int height) + { + final Widget widget = parent.createChild(-1, WidgetType.TEXT); + widget.setText(text); + widget.setFontId(fontId); + widget.setTextColor(textColor); + widget.setTextShadowed(true); + widget.setOriginalHeight(height == -1 ? TEXT_HEIGHT : height); + widget.setOriginalWidth(width); + widget.setOriginalY(y); + widget.setOriginalX(x); + widget.revalidate(); + return widget; + } + + private static void createSeparator(Widget parent, int y) + { + Widget separator = parent.createChild(-1, WidgetType.GRAPHIC); + separator.setOriginalWidth(parent.getWidth()); + separator.setOriginalY(y); + separator.setOriginalHeight(32); + separator.setSpriteId(SpriteID.UNKNOWN_BORDER_EDGE_HORIZONTAL_995); + separator.revalidate(); + } + + private void resetGEInventory() + { + final Widget invContainer = getInventoryContainer(); + + if (invContainer == null) + { + return; + } + + if (itemInformationTitle != null && invContainer.getChild(0) == itemInformationTitle) + { + invContainer.deleteAllChildren(); + itemInformationTitle = null; + } + + final Widget geInv = client.getWidget(WidgetInfo.GRAND_EXCHANGE_INVENTORY_ITEMS_CONTAINER); + if (geInv != null) + { + geInv.setHidden(false); + } + } + + private int getCurrentGP() + { + final ItemContainer inventory = client.getItemContainer(InventoryID.INVENTORY); + + if (inventory == null) + { + return 0; + } + + for (final Item item : inventory.getItems()) + { + if (item.getId() == ItemID.COINS_995) + { + return item.getQuantity(); + } + } + + return 0; + } + + private Widget getInventoryContainer() + { + if (client.isResized()) + { + if (client.getVar(Varbits.SIDE_PANELS) == 1) + { + return client.getWidget(WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_INVENTORY_CONTAINER); + } + else + { + return client.getWidget(WidgetInfo.RESIZABLE_VIEWPORT_INVENTORY_CONTAINER); + } + } + else + { + return client.getWidget(WidgetInfo.FIXED_VIEWPORT_INVENTORY_CONTAINER); + } } } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/JagexColors.java b/runelite-client/src/main/java/net/runelite/client/ui/JagexColors.java index ac5fbeed6f..38b1ea657f 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/JagexColors.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/JagexColors.java @@ -62,4 +62,10 @@ public class JagexColors public static final Color TOOLTIP_BACKGROUND = new Color(255, 255, 160); public static final Color TOOLTIP_BORDER = Color.BLACK; public static final Color TOOLTIP_TEXT = Color.BLACK; + + /* + * Colors used in interfaces + */ + public static final Color DARK_ORANGE_INTERFACE_TEXT = new Color(255, 152, 31); + public static final Color YELLOW_INTERFACE_TEXT = Color.YELLOW; } diff --git a/runelite-client/src/main/scripts/GEOffersSetupInit.rs2asm b/runelite-client/src/main/scripts/GEOffersSetupInit.rs2asm index 422b76a7f7..e884731804 100644 --- a/runelite-client/src/main/scripts/GEOffersSetupInit.rs2asm +++ b/runelite-client/src/main/scripts/GEOffersSetupInit.rs2asm @@ -389,4 +389,6 @@ LABEL315: iload 13 invoke 780 LABEL319: - return + sconst "geBuilt" ; + runelite_callback ; + return