diff --git a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java index df8c316524..7476e7346c 100644 --- a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java +++ b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java @@ -40,6 +40,7 @@ import net.runelite.api.Point; import net.runelite.api.Projectile; import net.runelite.api.Skill; import net.runelite.client.RuneLite; +import net.runelite.client.chat.ChatMessageManager; import net.runelite.client.events.*; import net.runelite.client.game.DeathChecker; import net.runelite.client.task.Scheduler; @@ -56,6 +57,7 @@ public class Hooks private static final EventBus eventBus = injector.getInstance(EventBus.class); private static final Scheduler scheduler = injector.getInstance(Scheduler.class); private static final InfoBoxManager infoBoxManager = injector.getInstance(InfoBoxManager.class); + private static final ChatMessageManager chatMessageManager = injector.getInstance(ChatMessageManager.class); private static final DeathChecker death = new DeathChecker(client, eventBus); private static final GameTick tick = new GameTick(); @@ -86,6 +88,8 @@ public class Hooks // cull infoboxes infoBoxManager.cull(); + + chatMessageManager.process(); } public static void draw(MainBufferProvider mainBufferProvider, Graphics graphics, int x, int y) @@ -186,7 +190,7 @@ public class Hooks } } - public static void menuActionHook(int var0, int widgetId, int menuAction, int id, String menuOption, String menuTarget, int var6, int var7) + public static void menuActionHook(int actionParam, int widgetId, int menuAction, int id, String menuOption, String menuTarget, int var6, int var7) { /* Along the way, the RuneScape client may change a menuAction by incrementing it with 2000. * I have no idea why, but it does. Their code contains the same conditional statement. @@ -197,9 +201,10 @@ public class Hooks } log.debug("Menu action clicked: {} ({}) on {} ({} widget: {})", - menuOption, menuAction, menuTarget.isEmpty() ? "" : menuTarget, id, var0, widgetId); + menuOption, menuAction, menuTarget.isEmpty() ? "" : menuTarget, id, actionParam, widgetId); MenuOptionClicked menuOptionClicked = new MenuOptionClicked(); + menuOptionClicked.setActionParam(actionParam); menuOptionClicked.setMenuOption(menuOption); menuOptionClicked.setMenuTarget(menuTarget); menuOptionClicked.setMenuAction(MenuAction.of(menuAction)); diff --git a/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java b/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java index dc0eda1628..73b2a6ce4e 100644 --- a/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java +++ b/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java @@ -28,9 +28,12 @@ import com.google.common.eventbus.Subscribe; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.Map; import java.util.Objects; +import java.util.Queue; import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -56,6 +59,7 @@ public class ChatMessageManager private final ScheduledExecutorService executor; private final RuneliteConfig config; private int transparancyVarbit = -1; + private final Queue queuedMessages = new ConcurrentLinkedQueue<>(); @Inject public ChatMessageManager(Provider clientProvider, ScheduledExecutorService executor, RuneliteConfig config) @@ -96,10 +100,25 @@ public class ChatMessageManager return this; } + public void queue(ChatMessageType type, String message) + { + queuedMessages.add(new QueuedMessage(type, message)); + } + + public void process() + { + for (Iterator it = queuedMessages.iterator(); it.hasNext();) + { + QueuedMessage message = it.next(); + add(message.getType(), message.getMessage()); + it.remove(); + } + } + public void add(final ChatMessageType type, final String mesage) { final Client client = clientProvider.get(); - client.sendGameMessage(type, mesage); + client.sendGameMessage(type, mesage); // this updates chat cycle final ChatLineBuffer chatLineBuffer = client.getChatLineMap().get(type.getType()); final MessageNode[] lines = chatLineBuffer.getLines(); final MessageNode line = lines[0]; diff --git a/runelite-client/src/main/java/net/runelite/client/chat/QueuedMessage.java b/runelite-client/src/main/java/net/runelite/client/chat/QueuedMessage.java new file mode 100644 index 0000000000..e25172fa46 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/chat/QueuedMessage.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.chat; + +import lombok.AllArgsConstructor; +import lombok.Data; +import net.runelite.api.ChatMessageType; + +@Data +@AllArgsConstructor +class QueuedMessage +{ + private final ChatMessageType type; + private final String message; +} diff --git a/runelite-client/src/main/java/net/runelite/client/events/MenuOptionClicked.java b/runelite-client/src/main/java/net/runelite/client/events/MenuOptionClicked.java index b2fd4c7af8..270b1d4c2a 100644 --- a/runelite-client/src/main/java/net/runelite/client/events/MenuOptionClicked.java +++ b/runelite-client/src/main/java/net/runelite/client/events/MenuOptionClicked.java @@ -30,6 +30,7 @@ import net.runelite.api.MenuAction; @Data public class MenuOptionClicked { + private int actionParam; private String menuOption; private String menuTarget; private MenuAction menuAction; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java index 4ab858d72a..a5074c7fff 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java @@ -39,6 +39,10 @@ import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.ItemComposition; +import net.runelite.api.widgets.Widget; +import static net.runelite.api.widgets.WidgetInfo.TO_CHILD; +import static net.runelite.api.widgets.WidgetInfo.TO_GROUP; +import net.runelite.api.widgets.WidgetItem; import net.runelite.client.chat.ChatColor; import net.runelite.client.chat.ChatColorType; import net.runelite.client.chat.ChatMessageBuilder; @@ -151,7 +155,12 @@ public class ExaminePlugin extends Plugin return; } - PendingExamine pendingExamine = new PendingExamine(type, id, Instant.now()); + PendingExamine pendingExamine = new PendingExamine(); + pendingExamine.setWidgetId(event.getWidgetId()); + pendingExamine.setActionParam(event.getActionParam()); + pendingExamine.setType(type); + pendingExamine.setId(id); + pendingExamine.setCreated(Instant.now()); pending.push(pendingExamine); } @@ -193,7 +202,19 @@ public class ExaminePlugin extends Plugin if (config.itemPrice() && pendingExamine.getType() == ExamineType.ITEM) { - executor.submit(() -> getItemPrice(pendingExamine)); + // get quantity from widget + int widgetId = pendingExamine.getWidgetId(); + Widget widget = client.getWidget(TO_GROUP(widgetId), TO_CHILD(widgetId)); + + WidgetItem widgetItem = widget != null ? widget.getWidgetItem(pendingExamine.getActionParam()) : null; + int quantity = widgetItem != null ? widgetItem.getQuantity() : 1; + + ItemComposition itemComposition = itemManager.getItemComposition(pendingExamine.getId()); + + if (itemComposition != null) + { + executor.submit(() -> getItemPrice(itemComposition, quantity)); + } } CacheKey key = new CacheKey(type, pendingExamine.getId()); @@ -207,41 +228,66 @@ public class ExaminePlugin extends Plugin executor.submit(() -> submitExamine(pendingExamine, event.getMessage())); } - private void getItemPrice(PendingExamine examine) + private void getItemPrice(ItemComposition itemComposition, int quantity) { + // convert to unnoted id + final boolean note = itemComposition.getNote() != -1; + final int id = note ? itemComposition.getLinkedNoteId() : itemComposition.getId(); + + ItemPrice itemPrice; try { - final ItemComposition itemComposition = itemManager.getItemComposition(examine.getId()); - - if (itemComposition != null) - { - final int id = itemComposition.getNote() != -1 ? itemComposition.getLinkedNoteId() : itemComposition.getId(); - final ItemPrice itemPrice = itemManager.getItemPrice(id); - final int gePrice = itemPrice == null ? 0 : itemPrice.getPrice(); - final int alchPrice = Math.round(itemComposition.getPrice() * HIGH_ALCHEMY_CONSTANT); - - final String message = new ChatMessageBuilder() - .append(ChatColorType.NORMAL) - .append("Price of ") - .append(ChatColorType.HIGHLIGHT) - .append(itemComposition.getName()) - .append(ChatColorType.NORMAL) - .append(": GE average ") - .append(ChatColorType.HIGHLIGHT) - .append(String.valueOf(gePrice)) - .append(ChatColorType.NORMAL) - .append(" HA value ") - .append(ChatColorType.HIGHLIGHT) - .append(String.valueOf(alchPrice)) - .build(); - - chatMessageManager.add(ChatMessageType.EXAMINE_ITEM, message); - client.refreshChat(); - } + itemPrice = itemManager.getItemPrice(id); } catch (IOException e) { log.warn("Error looking up item price", e); + return; + } + + int itemCompositionPrice = itemComposition.getPrice(); + final int gePrice = itemPrice == null ? 0 : itemPrice.getPrice() * quantity; + final int alchPrice = itemCompositionPrice <= 0 + ? 0 + : Math.round(itemCompositionPrice * HIGH_ALCHEMY_CONSTANT) * quantity; + + if (gePrice > 0 || alchPrice > 0) + { + final ChatMessageBuilder message = new ChatMessageBuilder() + .append(ChatColorType.NORMAL) + .append("Price of ") + .append(ChatColorType.HIGHLIGHT); + + if (quantity > 1) + { + message + .append(String.format("%,d", quantity)) + .append(" x "); + } + + message + .append(itemComposition.getName()); + + if (gePrice > 0) + { + message + .append(ChatColorType.NORMAL) + .append(": GE average ") + .append(ChatColorType.HIGHLIGHT) + .append(String.format("%,d", gePrice)); + } + + if (alchPrice > 0) + { + message + .append(ChatColorType.NORMAL) + .append(" HA value ") + .append(ChatColorType.HIGHLIGHT) + .append(String.format("%,d", alchPrice)); + } + + chatMessageManager.queue(ChatMessageType.EXAMINE_ITEM, message.build()); + client.refreshChat(); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/examine/PendingExamine.java b/runelite-client/src/main/java/net/runelite/client/plugins/examine/PendingExamine.java index a58ff59177..46bcb9da76 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/examine/PendingExamine.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/examine/PendingExamine.java @@ -25,32 +25,14 @@ package net.runelite.client.plugins.examine; import java.time.Instant; +import lombok.Data; -public class PendingExamine +@Data +class PendingExamine { - private final ExamineType type; - private final int id; - private final Instant created; - - public PendingExamine(ExamineType type, int id, Instant created) - { - this.type = type; - this.id = id; - this.created = created; - } - - public ExamineType getType() - { - return type; - } - - public int getId() - { - return id; - } - - public Instant getCreated() - { - return created; - } + private ExamineType type; + private int id; + private int widgetId; + private int actionParam; + private Instant created; }