From 9b9aee3e2b7264057e068fd906cc068f1f68c959 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 10 Apr 2019 19:16:37 -0400 Subject: [PATCH] Add WidgetItemOverlay This simplifies the logic required for plugins to draw an overlay over an item. --- .../net/runelite/api/hooks/Callbacks.java | 6 ++ .../net/runelite/api/widgets/WidgetItem.java | 18 +---- .../net/runelite/client/callback/Hooks.java | 18 +++++ .../client/ui/overlay/OverlayManager.java | 9 +++ .../client/ui/overlay/WidgetItemOverlay.java | 78 +++++++++++++++++++ .../net/runelite/mixins/RSClientMixin.java | 34 +++++++- 6 files changed, 145 insertions(+), 18 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/ui/overlay/WidgetItemOverlay.java diff --git a/runelite-api/src/main/java/net/runelite/api/hooks/Callbacks.java b/runelite-api/src/main/java/net/runelite/api/hooks/Callbacks.java index 9a1a6eb3cb..b6960d761f 100644 --- a/runelite-api/src/main/java/net/runelite/api/hooks/Callbacks.java +++ b/runelite-api/src/main/java/net/runelite/api/hooks/Callbacks.java @@ -29,6 +29,7 @@ import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import net.runelite.api.MainBufferProvider; +import net.runelite.api.widgets.WidgetItem; /** * Interface of callbacks the injected client uses to send events @@ -79,6 +80,11 @@ public interface Callbacks */ void draw(MainBufferProvider mainBufferProvider, Graphics graphics, int x, int y); + /** + * Called before the client will render an item widget. + */ + void drawItem(int itemId, WidgetItem widgetItem); + /** * Mouse pressed event. If this event will be consumed it will not be propagated further to client. * diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetItem.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetItem.java index fbbc8938f2..51f63ad40f 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetItem.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetItem.java @@ -25,11 +25,15 @@ package net.runelite.api.widgets; import java.awt.Rectangle; +import lombok.AllArgsConstructor; +import lombok.ToString; import net.runelite.api.Point; /** * An item that is being represented in a {@link Widget}. */ +@AllArgsConstructor +@ToString public class WidgetItem { private final int id; @@ -37,20 +41,6 @@ public class WidgetItem private final int index; private final Rectangle canvasBounds; - public WidgetItem(int id, int quantity, int index, Rectangle canvasBounds) - { - this.id = id; - this.quantity = quantity; - this.index = index; - this.canvasBounds = canvasBounds; - } - - @Override - public String toString() - { - return "WidgetItem{" + "id=" + id + ", quantity=" + quantity + ", index=" + index + ", canvasBounds=" + canvasBounds + '}'; - } - /** * Gets the ID of the item represented. * 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 08cd4ea3fe..ae1dff022f 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 @@ -43,6 +43,7 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.BufferProvider; import net.runelite.api.Client; import net.runelite.api.MainBufferProvider; +import net.runelite.api.NullItemID; import net.runelite.api.RenderOverview; import net.runelite.api.Renderable; import net.runelite.api.WorldMapManager; @@ -52,6 +53,7 @@ import net.runelite.api.hooks.Callbacks; import net.runelite.api.hooks.DrawCallbacks; import net.runelite.api.widgets.Widget; import static net.runelite.api.widgets.WidgetInfo.WORLD_MAP_VIEW; +import net.runelite.api.widgets.WidgetItem; import net.runelite.client.Notifier; import net.runelite.client.RuneLite; import net.runelite.client.chat.ChatMessageManager; @@ -62,6 +64,7 @@ import net.runelite.client.task.Scheduler; import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.DrawManager; import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.ui.overlay.OverlayRenderer; import net.runelite.client.ui.overlay.infobox.InfoBoxManager; import net.runelite.client.util.DeferredEventBus; @@ -80,6 +83,7 @@ public class Hooks implements Callbacks private static final Injector injector = RuneLite.getInjector(); private static final Client client = injector.getInstance(Client.class); private static final OverlayRenderer renderer = injector.getInstance(OverlayRenderer.class); + private static final OverlayManager overlayManager = injector.getInstance(OverlayManager.class); private static final GameTick GAME_TICK = new GameTick(); private static final BeforeRender BEFORE_RENDER = new BeforeRender(); @@ -443,6 +447,10 @@ public class Hooks implements Callbacks { graphics2d.dispose(); } + + // WidgetItemOverlays render at ABOVE_WIDGETS, reset widget item + // list for next frame. + overlayManager.getItemWidgets().clear(); } @Override @@ -490,4 +498,14 @@ public class Hooks implements Callbacks pixelPos += pixelJump; } } + + @Override + public void drawItem(int itemId, WidgetItem widgetItem) + { + // Empty bank item + if (widgetItem.getId() != NullItemID.NULL_6512) + { + overlayManager.getItemWidgets().add(widgetItem); + } + } } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayManager.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayManager.java index 5ee3303462..5a6e240f90 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayManager.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayManager.java @@ -41,6 +41,7 @@ import lombok.AccessLevel; import lombok.Getter; import net.runelite.api.MenuAction; import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.widgets.WidgetItem; import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigManager; import net.runelite.client.config.RuneLiteConfig; @@ -96,6 +97,8 @@ public class OverlayManager */ @Getter(AccessLevel.PACKAGE) private final List overlays = new ArrayList<>(); + @Getter + private final List itemWidgets = new ArrayList<>(); private final Map> overlayLayers = new EnumMap<>(OverlayLayer.class); @@ -168,6 +171,12 @@ public class OverlayManager // Add is always true overlays.add(overlay); loadOverlay(overlay); + // WidgetItemOverlays have a reference to the overlay manager in order to get the WidgetItems + // for each frame. + if (overlay instanceof WidgetItemOverlay) + { + ((WidgetItemOverlay) overlay).setOverlayManager(this); + } rebuildOverlayLayers(); return true; } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/WidgetItemOverlay.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/WidgetItemOverlay.java new file mode 100644 index 0000000000..0eaae6e50b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/WidgetItemOverlay.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019, 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.ui.overlay; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.util.List; +import lombok.AccessLevel; +import lombok.Setter; +import net.runelite.api.widgets.WidgetItem; + +public abstract class WidgetItemOverlay extends Overlay +{ + @Setter(AccessLevel.PACKAGE) + private OverlayManager overlayManager; + + protected WidgetItemOverlay() + { + super.setPosition(OverlayPosition.DYNAMIC); + super.setPriority(OverlayPriority.LOW); + super.setLayer(OverlayLayer.ABOVE_WIDGETS); + } + + public abstract void renderItemOverlay(Graphics2D graphics, int itemId, WidgetItem itemWidget); + + @Override + public Dimension render(Graphics2D graphics) + { + final List itemWidgets = overlayManager.getItemWidgets(); + for (WidgetItem widget : itemWidgets) + { + renderItemOverlay(graphics, widget.getId(), widget); + } + return null; + } + + // Don't allow setting position, priority, or layer + + @Override + public void setPosition(OverlayPosition position) + { + throw new IllegalStateException(); + } + + @Override + public void setPriority(OverlayPriority priority) + { + throw new IllegalStateException(); + } + + @Override + public void setLayer(OverlayLayer layer) + { + throw new IllegalStateException(); + } +} diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java index 6c3d1f1ae0..56b9190ead 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java @@ -28,6 +28,7 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.EnumSet; import java.util.List; import java.util.Map; @@ -108,6 +109,8 @@ import net.runelite.api.mixins.Shadow; import net.runelite.api.vars.AccountType; import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetInfo; +import net.runelite.api.widgets.WidgetItem; +import net.runelite.api.widgets.WidgetType; import net.runelite.rs.api.RSChatLineBuffer; import net.runelite.rs.api.RSClanMemberManager; import net.runelite.rs.api.RSClient; @@ -1324,8 +1327,11 @@ public abstract class RSClientMixin implements RSClient @MethodHook("renderWidgetLayer") @Inject - public static void renderWidgetLayer(Widget[] widgets, int parentId, int var2, int var3, int var4, int var5, int x, int y, int var8) + public static void renderWidgetLayer(Widget[] widgets, int parentId, int minX, int minY, int maxX, int maxY, int x, int y, int var8) { + Callbacks callbacks = client.getCallbacks(); + HashTable componentTable = client.getComponentTable(); + for (Widget rlWidget : widgets) { RSWidget widget = (RSWidget) rlWidget; @@ -1338,10 +1344,30 @@ public abstract class RSClientMixin implements RSClient { widget.setRenderParentId(parentId); } - widget.setRenderX(x + widget.getRelativeX()); - widget.setRenderY(y + widget.getRelativeY()); - HashTable componentTable = client.getComponentTable(); + final int renderX = x + widget.getRelativeX(); + final int renderY = y + widget.getRelativeY(); + widget.setRenderX(renderX); + widget.setRenderY(renderY); + + final int widgetType = widget.getType(); + if (widgetType == WidgetType.GRAPHIC && widget.getItemId() != -1) + { + if (renderX >= minX && renderX <= maxX && renderY >= minY && renderY <= maxY) + { + WidgetItem widgetItem = new WidgetItem(widget.getItemId(), widget.getItemQuantity(), -1, widget.getBounds()); + callbacks.drawItem(widget.getItemId(), widgetItem); + } + } + else if (widgetType == WidgetType.INVENTORY) + { + Collection widgetItems = widget.getWidgetItems(); + for (WidgetItem widgetItem : widgetItems) + { + callbacks.drawItem(widgetItem.getId(), widgetItem); + } + } + WidgetNode childNode = componentTable.get(widget.getId()); if (childNode != null) {