diff --git a/runelite-api/src/main/java/net/runelite/api/Client.java b/runelite-api/src/main/java/net/runelite/api/Client.java index de43f83b3b..0e83f9a419 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -1838,6 +1838,11 @@ public interface Client extends GameShell */ int getItemCount(); + /** + * Makes all widgets behave as if they are {@link WidgetConfig#WIDGET_USE_TARGET} + */ + void setAllWidgetsAreOpTargetable(boolean value); + /** * Adds a MenuEntry to the current menu. */ diff --git a/runelite-api/src/main/java/net/runelite/api/MenuOpcode.java b/runelite-api/src/main/java/net/runelite/api/MenuOpcode.java index 1d38976bbf..a1b9a044aa 100644 --- a/runelite-api/src/main/java/net/runelite/api/MenuOpcode.java +++ b/runelite-api/src/main/java/net/runelite/api/MenuOpcode.java @@ -232,6 +232,11 @@ public enum MenuOpcode */ WIDGET_DEFAULT(57), + /** + * Casting a spell / op target on a widget + */ + SPELL_CAST_ON_WIDGET(58), + /** * Sub 1000 so it doesn't get sorted down in the list */ diff --git a/runelite-api/src/main/java/net/runelite/api/WallObject.java b/runelite-api/src/main/java/net/runelite/api/WallObject.java index 4e477ecc0c..328db7d878 100644 --- a/runelite-api/src/main/java/net/runelite/api/WallObject.java +++ b/runelite-api/src/main/java/net/runelite/api/WallObject.java @@ -65,4 +65,5 @@ public interface WallObject extends TileObject * @see net.runelite.api.model.Jarvis */ Shape getConvexHull(); + Shape getConvexHull2(); } diff --git a/runelite-client/src/main/java/net/runelite/client/menus/MenuManager.java b/runelite-client/src/main/java/net/runelite/client/menus/MenuManager.java index 0d0e405229..667f68f24b 100644 --- a/runelite-client/src/main/java/net/runelite/client/menus/MenuManager.java +++ b/runelite-client/src/main/java/net/runelite/client/menus/MenuManager.java @@ -247,6 +247,11 @@ public class MenuManager private void onMenuEntryAdded(MenuEntryAdded event) { + if (client.isSpellSelected()) + { + return; + } + for (AbstractComparableEntry e : hiddenEntries) { if (e.matches(event)) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java index eba53c5838..886c3a193b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java @@ -37,8 +37,6 @@ import java.awt.geom.Rectangle2D; import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; -import lombok.Getter; -import lombok.Setter; import net.runelite.api.Actor; import net.runelite.api.Client; import net.runelite.api.Constants; @@ -76,9 +74,6 @@ import net.runelite.client.ui.overlay.tooltip.TooltipManager; @Singleton class DevToolsOverlay extends Overlay { - private static final int ITEM_EMPTY = 6512; - private static final int ITEM_FILLED = 20594; - private static final Font FONT = FontManager.getRunescapeFont().deriveFont(Font.BOLD, 16); private static final Color RED = new Color(221, 44, 0); private static final Color GREEN = new Color(0, 200, 83); @@ -97,13 +92,6 @@ class DevToolsOverlay extends Overlay private final DevToolsPlugin plugin; private final TooltipManager toolTipManager; - @Setter - @Getter - private Widget widget; - - @Setter - private int itemIndex = -1; - @Inject private DevToolsOverlay(Client client, DevToolsPlugin plugin, TooltipManager toolTipManager) { @@ -155,8 +143,6 @@ class DevToolsOverlay extends Overlay renderCursorTooltip(graphics); } - renderWidgets(graphics); - return null; } @@ -490,70 +476,6 @@ class DevToolsOverlay extends Overlay } } - private void renderWidgets(Graphics2D graphics) - { - if (widget == null || widget.isHidden()) - { - return; - } - - Rectangle childBounds = widget.getBounds(); - graphics.setColor(CYAN); - graphics.draw(childBounds); - - if (itemIndex == -1) - { - return; - } - - if (widget.getItemId() != ITEM_EMPTY - && widget.getItemId() != ITEM_FILLED) - { - Rectangle componentBounds = widget.getBounds(); - - graphics.setColor(ORANGE); - graphics.draw(componentBounds); - - renderWidgetText(graphics, componentBounds, widget.getItemId(), YELLOW); - } - - WidgetItem widgetItem = widget.getWidgetItem(itemIndex); - if (widgetItem == null - || widgetItem.getId() < 0 - || widgetItem.getId() == ITEM_EMPTY - || widgetItem.getId() == ITEM_FILLED) - { - return; - } - - Rectangle itemBounds = widgetItem.getCanvasBounds(); - - graphics.setColor(ORANGE); - graphics.draw(itemBounds); - - renderWidgetText(graphics, itemBounds, widgetItem.getId(), YELLOW); - } - - private void renderWidgetText(Graphics2D graphics, Rectangle bounds, int itemId, Color color) - { - if (itemId == -1) - { - return; - } - - String text = itemId + ""; - FontMetrics fm = graphics.getFontMetrics(); - Rectangle2D textBounds = fm.getStringBounds(text, graphics); - - int textX = (int) (bounds.getX() + (bounds.getWidth() / 2) - (textBounds.getWidth() / 2)); - int textY = (int) (bounds.getY() + (bounds.getHeight() / 2) + (textBounds.getHeight() / 2)); - - graphics.setColor(Color.BLACK); - graphics.drawString(text, textX + 1, textY + 1); - graphics.setColor(color); - graphics.drawString(text, textX, textY); - } - private void renderPlayerWireframe(Graphics2D graphics, Player player, Color color) { Polygon[] polys = player.getPolygons(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspector.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspector.java index 492206a0aa..3d3304447b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspector.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspector.java @@ -27,13 +27,20 @@ package net.runelite.client.plugins.devtools; import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; import java.awt.BorderLayout; +import java.awt.Color; import java.awt.Dimension; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.Collection; +import java.util.Comparator; +import java.util.Enumeration; import java.util.HashMap; import java.util.Map; +import java.util.Stack; +import java.util.stream.Stream; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JFrame; @@ -45,29 +52,67 @@ import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; +import net.runelite.api.MenuEntry; +import net.runelite.api.MenuOpcode; +import net.runelite.api.SpriteID; import net.runelite.api.events.ConfigChanged; +import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.widgets.JavaScriptCallback; import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetConfig; import net.runelite.api.widgets.WidgetInfo; import net.runelite.api.widgets.WidgetItem; +import net.runelite.api.widgets.WidgetType; import net.runelite.client.callback.ClientThread; import net.runelite.client.eventbus.EventBus; import net.runelite.client.ui.ClientUI; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.util.ColorUtil; @Slf4j +@Singleton class WidgetInspector extends JFrame { + private static final Map widgetIdMap = new HashMap<>(); + + static final Color SELECTED_WIDGET_COLOR = Color.CYAN; + private static final float SELECTED_WIDGET_HUE; + + static + { + float[] hsb = new float[3]; + Color.RGBtoHSB(SELECTED_WIDGET_COLOR.getRed(), SELECTED_WIDGET_COLOR.getGreen(), SELECTED_WIDGET_COLOR.getBlue(), hsb); + SELECTED_WIDGET_HUE = hsb[0]; + } + private final Client client; private final ClientThread clientThread; private final DevToolsConfig config; - private final DevToolsOverlay overlay; + private final Provider overlay; + private final OverlayManager overlayManager; private final JTree widgetTree; private final WidgetInfoTableModel infoTableModel; private final JCheckBox alwaysOnTop; + private final JCheckBox hideHidden; - private static final Map widgetIdMap = new HashMap<>(); + private DefaultMutableTreeNode root; + + @Getter + private Widget selectedWidget; + + @Getter + private int selectedItem; + + private Widget picker = null; + + @Getter + private boolean pickerSelected = false; @Inject private WidgetInspector( @@ -75,17 +120,21 @@ class WidgetInspector extends JFrame ClientThread clientThread, WidgetInfoTableModel infoTableModel, DevToolsConfig config, + DevToolsPlugin plugin, EventBus eventBus, - DevToolsOverlay overlay, - DevToolsPlugin plugin) + Provider overlay, + OverlayManager overlayManager) { this.client = client; this.clientThread = clientThread; this.infoTableModel = infoTableModel; this.config = config; this.overlay = overlay; + this.overlayManager = overlayManager; eventBus.subscribe(ConfigChanged.class, this, this::onConfigChanged); + eventBus.subscribe(MenuOptionClicked.class, this, this::onMenuOptionClicked); + eventBus.subscribe(MenuEntryAdded.class, this, this::onMenuEntryAdded); setTitle("RuneLite Widget Inspector"); setIconImage(ClientUI.ICON); @@ -96,7 +145,6 @@ class WidgetInspector extends JFrame @Override public void windowClosing(WindowEvent e) { - eventBus.unregister(this); close(); plugin.getWidgetInspector().setActive(false); } @@ -114,16 +162,12 @@ class WidgetInspector extends JFrame { WidgetTreeNode node = (WidgetTreeNode) selected; Widget widget = node.getWidget(); - overlay.setWidget(widget); - overlay.setItemIndex(widget.getItemId()); - refreshInfo(); - log.debug("Set widget to {} and item index to {}", widget, widget.getItemId()); + setSelectedWidget(widget, -1, false); } else if (selected instanceof WidgetItemNode) { WidgetItemNode node = (WidgetItemNode) selected; - overlay.setItemIndex(node.getWidgetItem().getIndex()); - log.debug("Set item index to {}", node.getWidgetItem().getIndex()); + setSelectedWidget(node.getWidgetItem().getWidget(), node.getWidgetItem().getIndex(), false); } }); @@ -149,15 +193,20 @@ class WidgetInspector extends JFrame onConfigChanged(null); bottomPanel.add(alwaysOnTop); + hideHidden = new JCheckBox("Hide hidden"); + hideHidden.setSelected(true); + hideHidden.addItemListener(ev -> refreshWidgets()); + bottomPanel.add(hideHidden); + final JButton revalidateWidget = new JButton("Revalidate"); revalidateWidget.addActionListener(ev -> clientThread.invokeLater(() -> { - if (overlay.getWidget() == null) + if (selectedWidget == null) { return; } - overlay.getWidget().revalidate(); + selectedWidget.revalidate(); })); bottomPanel.add(revalidateWidget); @@ -165,7 +214,6 @@ class WidgetInspector extends JFrame add(split, BorderLayout.CENTER); pack(); - } private void onConfigChanged(ConfigChanged ev) @@ -180,10 +228,13 @@ class WidgetInspector extends JFrame clientThread.invokeLater(() -> { Widget[] rootWidgets = client.getWidgetRoots(); - DefaultMutableTreeNode root = new DefaultMutableTreeNode(); + root = new DefaultMutableTreeNode(); - overlay.setWidget(null); - overlay.setItemIndex(-1); + Widget wasSelectedWidget = selectedWidget; + int wasSelectedItem = selectedItem; + + selectedWidget = null; + selectedItem = -1; for (Widget widget : rootWidgets) { @@ -196,17 +247,15 @@ class WidgetInspector extends JFrame SwingUtilities.invokeLater(() -> { - overlay.setWidget(null); - overlay.setItemIndex(-1); - refreshInfo(); widgetTree.setModel(new DefaultTreeModel(root)); + setSelectedWidget(wasSelectedWidget, wasSelectedItem, true); }); }); } private DefaultMutableTreeNode addWidget(String type, Widget widget) { - if (widget == null || widget.isHidden()) + if (widget == null || (hideHidden.isSelected() && widget.isHidden())) { return null; } @@ -269,9 +318,70 @@ class WidgetInspector extends JFrame return node; } - private void refreshInfo() + private void setSelectedWidget(Widget widget, int item, boolean updateTree) { - infoTableModel.setWidget(overlay.getWidget()); + infoTableModel.setWidget(widget); + + if (this.selectedWidget == widget && this.selectedItem == item) + { + return; + } + + this.selectedWidget = widget; + this.selectedItem = item; + + if (root == null || !updateTree) + { + return; + } + + clientThread.invoke(() -> + { + Stack treePath = new Stack<>(); + for (Widget w = widget; w != null; w = w.getParent()) + { + treePath.push(w); + } + + DefaultMutableTreeNode node = root; + deeper: + for (; !treePath.empty(); ) + { + Widget w = treePath.pop(); + for (Enumeration it = node.children(); it.hasMoreElements(); ) + { + WidgetTreeNode inner = (WidgetTreeNode) it.nextElement(); + if (inner.getWidget().getId() == w.getId() && inner.getWidget().getIndex() == w.getIndex()) + { + node = inner; + continue deeper; + } + } + } + if (selectedItem != -1) + { + for (Enumeration it = node.children(); it.hasMoreElements(); ) + { + Object wiw = it.nextElement(); + if (wiw instanceof WidgetItemNode) + { + WidgetItemNode inner = (WidgetItemNode) wiw; + if (inner.getWidgetItem().getIndex() == selectedItem) + { + node = inner; + break; + } + } + } + } + + final DefaultMutableTreeNode fnode = node; + SwingUtilities.invokeLater(() -> + { + widgetTree.getSelectionModel().clearSelection(); + widgetTree.getSelectionModel().addSelectionPath(new TreePath(fnode.getPath())); + }); + }); } static WidgetInfo getWidgetInfo(int packedId) @@ -295,12 +405,176 @@ class WidgetInspector extends JFrame setVisible(true); toFront(); repaint(); + overlayManager.add(this.overlay.get()); + clientThread.invokeLater(this::addPickerWidget); } public void close() { - overlay.setWidget(null); - overlay.setItemIndex(-1); + overlayManager.remove(this.overlay.get()); + clientThread.invokeLater(this::removePickerWidget); + setSelectedWidget(null, -1, false); setVisible(false); } -} + + private void removePickerWidget() + { + if (picker == null) + { + return; + } + + Widget parent = picker.getParent(); + if (parent == null) + { + return; + } + + Widget[] children = parent.getChildren(); + if (children == null || children.length <= picker.getIndex() || children[picker.getIndex()] != picker) + { + return; + } + + children[picker.getIndex()] = null; + } + + private void addPickerWidget() + { + removePickerWidget(); + + int x = 10, y = 2; + Widget parent = client.getWidget(WidgetInfo.MINIMAP_ORBS); + if (parent == null) + { + Widget[] roots = client.getWidgetRoots(); + + parent = Stream.of(roots) + .filter(w -> w.getType() == WidgetType.LAYER && w.getContentType() == 0 && !w.isSelfHidden()) + .sorted(Comparator.comparing((Widget w) -> w.getRelativeX() + w.getRelativeY()) + .reversed() + .thenComparing(Widget::getId) + .reversed()) + .findFirst().get(); + x = 4; + y = 4; + } + + picker = parent.createChild(-1, WidgetType.GRAPHIC); + + log.info("Picker is {}.{} [{}]", WidgetInfo.TO_GROUP(picker.getId()), WidgetInfo.TO_CHILD(picker.getId()), picker.getIndex()); + + picker.setSpriteId(SpriteID.MOBILE_FINGER_ON_INTERFACE); + picker.setOriginalWidth(15); + picker.setOriginalHeight(17); + picker.setOriginalX(x); + picker.setOriginalY(y); + picker.revalidate(); + picker.setTargetVerb("Select"); + picker.setName("Pick"); + picker.setClickMask(WidgetConfig.USE_WIDGET | WidgetConfig.USE_ITEM); + picker.setNoClickThrough(true); + picker.setOnTargetEnterListener((JavaScriptCallback) ev -> + { + pickerSelected = true; + picker.setOpacity(30); + client.setAllWidgetsAreOpTargetable(true); + }); + picker.setOnTargetLeaveListener((JavaScriptCallback) ev -> onPickerDeselect()); + } + + private void onPickerDeselect() + { + client.setAllWidgetsAreOpTargetable(false); + picker.setOpacity(0); + pickerSelected = false; + } + + private void onMenuOptionClicked(MenuOptionClicked ev) + { + if (!pickerSelected) + { + return; + } + + onPickerDeselect(); + client.setSpellSelected(false); + ev.consume(); + + Object target = getWidgetOrWidgetItemForMenuOption(ev.getMenuOpcode().getId(), ev.getParam0(), ev.getParam1()); + if (target == null) + { + return; + } + if (target instanceof WidgetItem) + { + WidgetItem iw = (WidgetItem) target; + setSelectedWidget(iw.getWidget(), iw.getIndex(), true); + } + else + { + setSelectedWidget((Widget) target, -1, true); + } + } + + + private void onMenuEntryAdded(MenuEntryAdded event) + { + if (!pickerSelected) + { + return; + } + + MenuEntry[] menuEntries = client.getMenuEntries(); + + for (int i = 0; i < menuEntries.length; i++) + { + MenuEntry entry = menuEntries[i]; + if (entry.getOpcode() != MenuOpcode.ITEM_USE_ON_WIDGET.getId() + && entry.getOpcode() != MenuOpcode.SPELL_CAST_ON_WIDGET.getId()) + { + continue; + } + String name = WidgetInfo.TO_GROUP(entry.getParam1()) + "." + WidgetInfo.TO_CHILD(entry.getParam1()); + + if (entry.getParam0() != -1) + { + name += " [" + entry.getParam0() + "]"; + } + + Color color = colorForWidget(i, menuEntries.length); + + entry.setTarget(ColorUtil.wrapWithColorTag(name, color)); + } + + client.setMenuEntries(menuEntries); + } + + Color colorForWidget(int index, int length) + { + float h = SELECTED_WIDGET_HUE + .1f + (.8f / length) * index; + + return Color.getHSBColor(h, 1, 1); + } + + Object getWidgetOrWidgetItemForMenuOption(int type, int param0, int param1) + { + if (type == MenuOpcode.SPELL_CAST_ON_WIDGET.getId()) + { + Widget w = client.getWidget(WidgetInfo.TO_GROUP(param1), WidgetInfo.TO_CHILD(param1)); + if (param0 != -1) + { + w = w.getChild(param0); + } + + return w; + } + else if (type == MenuOpcode.ITEM_USE_ON_WIDGET.getId()) + { + Widget w = client.getWidget(WidgetInfo.TO_GROUP(param1), WidgetInfo.TO_CHILD(param1)); + return w.getWidgetItem(param0); + } + + return null; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspectorOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspectorOverlay.java new file mode 100644 index 0000000000..0d9e6095de --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspectorOverlay.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2018 Abex + * Copyright (c) 2017, Kronos + * Copyright (c) 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.plugins.devtools; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.geom.Rectangle2D; +import javax.inject.Inject; +import javax.inject.Singleton; +import net.runelite.api.Client; +import net.runelite.api.MenuEntry; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetItem; +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.OverlayPriority; + +@Singleton +public class WidgetInspectorOverlay extends Overlay +{ + private final Client client; + private final WidgetInspector inspector; + + @Inject + public WidgetInspectorOverlay( + Client client, + WidgetInspector inspector + ) + { + this.client = client; + this.inspector = inspector; + + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_WIDGETS); + setPriority(OverlayPriority.HIGHEST); + } + + @Override + public Dimension render(Graphics2D g) + { + Widget w = inspector.getSelectedWidget(); + if (w != null) + { + Object wiw = w; + if (inspector.getSelectedItem() != -1) + { + wiw = w.getWidgetItem(inspector.getSelectedItem()); + } + + renderWiw(g, wiw, WidgetInspector.SELECTED_WIDGET_COLOR); + } + + if (inspector.isPickerSelected()) + { + boolean menuOpen = client.isMenuOpen(); + + MenuEntry[] entries = client.getMenuEntries(); + for (int i = menuOpen ? 0 : entries.length - 1; i < entries.length; i++) + { + MenuEntry e = entries[i]; + + Object wiw = inspector.getWidgetOrWidgetItemForMenuOption(e.getOpcode(), e.getParam0(), e.getParam1()); + if (wiw == null) + { + continue; + } + + Color color = inspector.colorForWidget(i, entries.length); + renderWiw(g, wiw, color); + } + } + + return null; + } + + private void renderWiw(Graphics2D g, Object wiw, Color color) + { + g.setColor(color); + + if (wiw instanceof WidgetItem) + { + WidgetItem wi = (WidgetItem) wiw; + Rectangle bounds = wi.getCanvasBounds(); + g.draw(bounds); + + String text = wi.getId() + ""; + FontMetrics fm = g.getFontMetrics(); + Rectangle2D textBounds = fm.getStringBounds(text, g); + + int textX = (int) (bounds.getX() + (bounds.getWidth() / 2) - (textBounds.getWidth() / 2)); + int textY = (int) (bounds.getY() + (bounds.getHeight() / 2) + (textBounds.getHeight() / 2)); + + g.setColor(Color.BLACK); + g.drawString(text, textX + 1, textY + 1); + g.setColor(Color.ORANGE); + g.drawString(text, textX, textY); + } + else + { + Widget w = (Widget) wiw; + g.draw(w.getBounds()); + } + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java index 7e5e58701d..37650c4fd0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java @@ -36,6 +36,7 @@ import net.runelite.api.Client; import net.runelite.api.DecorativeObject; import net.runelite.api.GameObject; import net.runelite.api.TileObject; +import net.runelite.api.WallObject; import net.runelite.client.graphics.ModelOutlineRenderer; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; @@ -107,6 +108,11 @@ class ObjectIndicatorsOverlay extends Overlay { polygon = ((GameObject) object).getConvexHull(); } + else if (object instanceof WallObject) + { + polygon = ((WallObject) object).getConvexHull(); + polygon2 = ((WallObject) object).getConvexHull2(); + } else if (object instanceof DecorativeObject) { polygon = ((DecorativeObject) object).getConvexHull(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java index 40b63ebb7f..56aafdf018 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java @@ -38,21 +38,25 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import lombok.AccessLevel; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import static net.runelite.api.Constants.REGION_SIZE; import net.runelite.api.DecorativeObject; import net.runelite.api.GameObject; import net.runelite.api.GameState; +import net.runelite.api.GroundObject; import net.runelite.api.MenuOpcode; import net.runelite.api.MenuEntry; import net.runelite.api.ObjectDefinition; import net.runelite.api.Scene; import net.runelite.api.Tile; import net.runelite.api.TileObject; +import net.runelite.api.WallObject; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.DecorativeObjectDespawned; @@ -61,8 +65,13 @@ import net.runelite.api.events.FocusChanged; import net.runelite.api.events.GameObjectDespawned; import net.runelite.api.events.GameObjectSpawned; import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GroundObjectDespawned; +import net.runelite.api.events.GroundObjectSpawned; import net.runelite.api.events.MenuEntryAdded; import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.events.WallObjectChanged; +import net.runelite.api.events.WallObjectDespawned; +import net.runelite.api.events.WallObjectSpawned; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.EventBus; import net.runelite.client.input.KeyListener; @@ -78,6 +87,7 @@ import net.runelite.client.ui.overlay.OverlayManager; enabledByDefault = false ) @Singleton +@Slf4j public class ObjectIndicatorsPlugin extends Plugin implements KeyListener { private static final String CONFIG_GROUP = "objectindicators"; @@ -152,10 +162,15 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener { eventbus.subscribe(ConfigChanged.class, this, this::onConfigChanged); eventbus.subscribe(FocusChanged.class, this, this::onFocusChanged); + eventbus.subscribe(WallObjectSpawned.class, this, this::onWallObjectSpawned); + eventbus.subscribe(WallObjectChanged.class, this, this::onWallObjectChanged); + eventbus.subscribe(WallObjectDespawned.class, this, this::onWallObjectDespawned); eventbus.subscribe(GameObjectSpawned.class, this, this::onGameObjectSpawned); eventbus.subscribe(DecorativeObjectSpawned.class, this, this::onDecorativeObjectSpawned); eventbus.subscribe(GameObjectDespawned.class, this, this::onGameObjectDespawned); eventbus.subscribe(DecorativeObjectDespawned.class, this, this::onDecorativeObjectDespawned); + eventbus.subscribe(GroundObjectDespawned.class, this, this::onGroundObjectDespawned); + eventbus.subscribe(GroundObjectSpawned.class, this, this::onGroundObjectSpawned); eventbus.subscribe(GameStateChanged.class, this, this::onGameStateChanged); eventbus.subscribe(MenuOptionClicked.class, this, this::onMenuOptionClicked); eventbus.subscribe(MenuEntryAdded.class, this, this::onMenuEntryAdded); @@ -193,6 +208,25 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener } } + private void onWallObjectSpawned(WallObjectSpawned event) + { + checkObjectPoints(event.getWallObject()); + } + + private void onWallObjectChanged(WallObjectChanged event) + { + WallObject previous = event.getPrevious(); + WallObject wallObject = event.getWallObject(); + + objects.remove(previous); + checkObjectPoints(wallObject); + } + + private void onWallObjectDespawned(WallObjectDespawned event) + { + objects.remove(event.getWallObject()); + } + private void onGameObjectSpawned(GameObjectSpawned event) { final GameObject eventObject = event.getGameObject(); @@ -215,6 +249,18 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener objects.remove(event.getDecorativeObject()); } + private void onGroundObjectSpawned(GroundObjectSpawned groundObjectSpawned) + { + final GroundObject groundObject = groundObjectSpawned.getGroundObject(); + checkObjectPoints(groundObject); + } + + private void onGroundObjectDespawned(GroundObjectDespawned groundObjectDespawned) + { + GroundObject groundObject = groundObjectDespawned.getGroundObject(); + objects.remove(groundObject); + } + private void onGameStateChanged(GameStateChanged gameStateChanged) { GameState gameState = gameStateChanged.getGameState(); @@ -283,9 +329,13 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener return; } - ObjectDefinition objectDefinition = client.getObjectDefinition(object.getId()); + // object.getId() is always the base object id, getObjectComposition transforms it to + // the correct object we see + ObjectDefinition objectDefinition = getObjectDefinition(object.getId()); String name = objectDefinition.getName(); - if (Strings.isNullOrEmpty(name)) + // Name is probably never "null" - however prevent adding it if it is, as it will + // become ambiguous as objects with no name are assigned name "null" + if (Strings.isNullOrEmpty(name) || name.equals("null")) { return; } @@ -306,11 +356,15 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener for (ObjectPoint objectPoint : objectPoints) { if ((worldPoint.getX() & (REGION_SIZE - 1)) == objectPoint.getRegionX() - && (worldPoint.getY() & (REGION_SIZE - 1)) == objectPoint.getRegionY() - && objectPoint.getName().equals(client.getObjectDefinition(object.getId()).getName())) + && (worldPoint.getY() & (REGION_SIZE - 1)) == objectPoint.getRegionY()) { - objects.add(object); - break; + // Transform object to get the name which matches against what we've stored + if (objectPoint.getName().equals(getObjectDefinition(object.getId()).getName())) + { + log.debug("Marking object {} due to matching {}", object, objectPoint); + objects.add(object); + break; + } } } } @@ -324,42 +378,65 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener final GameObject[] tileGameObjects = tile.getGameObjects(); final DecorativeObject tileDecorativeObject = tile.getDecorativeObject(); + final WallObject tileWallObject = tile.getWallObject(); + final GroundObject groundObject = tile.getGroundObject(); - if (tileDecorativeObject != null && tileDecorativeObject.getId() == id) + if (objectIdEquals(tileWallObject, id)) + { + return tileWallObject; + } + + if (objectIdEquals(tileDecorativeObject, id)) { return tileDecorativeObject; } + if (objectIdEquals(groundObject, id)) + { + return groundObject; + } + for (GameObject object : tileGameObjects) { - if (object == null) - { - continue; - } - - if (object.getId() == id) + if (objectIdEquals(object, id)) { return object; } - - // Check impostors - final ObjectDefinition comp = client.getObjectDefinition(object.getId()); - - if (comp.getImpostorIds() != null) - { - for (int impostorId : comp.getImpostorIds()) - { - if (impostorId == id) - { - return object; - } - } - } } return null; } + private boolean objectIdEquals(TileObject tileObject, int id) + { + if (tileObject == null) + { + return false; + } + + if (tileObject.getId() == id) + { + return true; + } + + // Menu action EXAMINE_OBJECT sends the transformed object id, not the base id, unlike + // all of the GAME_OBJECT_OPTION actions, so check the id against the impostor ids + final ObjectDefinition comp = client.getObjectDefinition(tileObject.getId()); + + if (comp.getImpostorIds() != null) + { + for (int impostorId : comp.getImpostorIds()) + { + if (impostorId == id) + { + return true; + } + } + } + + return false; + } + private void markObject(String name, final TileObject object) { if (object == null) @@ -382,11 +459,13 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener { objectPoints.remove(point); objects.remove(object); + log.debug("Unmarking object: {}", point); } else { objectPoints.add(point); objects.add(object); + log.debug("Marking object: {}", point); } savePoints(regionId, objectPoints); @@ -414,11 +493,18 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener return null; } - return GSON.fromJson(json, new TypeToken>() + Set points = GSON.fromJson(json, new TypeToken>() { }.getType()); + // Prior to multiloc support the plugin would mark objects named "null", which breaks + // in most cases due to the specific object being identified being ambiguous, so remove + // them + return points.stream() + .filter(point -> !point.getName().equals("null")) + .collect(Collectors.toSet()); } + private void onConfigChanged(ConfigChanged event) { if (!event.getGroup().equals("objectindicators")) @@ -436,4 +522,10 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener this.objectMarkerColor = config.objectMarkerColor(); this.objectMarkerAlpha = config.objectMarkerAlpha(); } + + private ObjectDefinition getObjectDefinition(int id) + { + ObjectDefinition objectComposition = client.getObjectDefinition(id); + return objectComposition.getImpostorIds() == null ? objectComposition : objectComposition.getImpostor(); + } } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiPlugin.java index cceb3fb268..b7e87d4206 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiPlugin.java @@ -25,13 +25,14 @@ package net.runelite.client.plugins.wiki; import com.google.common.primitives.Ints; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.Arrays; +import java.util.stream.Stream; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; +import net.runelite.api.MenuEntry; import net.runelite.api.MenuOpcode; import net.runelite.api.NPC; import net.runelite.api.NPCDefinition; @@ -40,6 +41,7 @@ import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.MenuEntryAdded; import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.events.WidgetLoaded; +import net.runelite.api.util.Text; import net.runelite.api.widgets.JavaScriptCallback; import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetConfig; @@ -55,7 +57,6 @@ import net.runelite.client.game.chatbox.ChatboxPanelManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.util.LinkBrowser; -import net.runelite.api.util.Text; import okhttp3.HttpUrl; @Slf4j @@ -82,9 +83,6 @@ public class WikiPlugin extends Plugin private static final String MENUOP_QUICKGUIDE = "Quick Guide"; private static final String MENUOP_WIKI = "Wiki"; - private static final Pattern SKILL_REGEX = Pattern.compile("([A-Za-z]+) guide"); - private static final Pattern DIARY_REGEX = Pattern.compile("([A-Za-z &]+) Journal"); - @Inject private SpriteManager spriteManager; @@ -177,12 +175,14 @@ public class WikiPlugin extends Plugin icon.setOriginalHeight(16); icon.setTargetVerb("Lookup"); icon.setName("Wiki"); - icon.setClickMask(WidgetConfig.USE_GROUND_ITEM | WidgetConfig.USE_ITEM | WidgetConfig.USE_NPC | WidgetConfig.USE_OBJECT); + icon.setClickMask(WidgetConfig.USE_GROUND_ITEM | WidgetConfig.USE_ITEM | WidgetConfig.USE_NPC + | WidgetConfig.USE_OBJECT | WidgetConfig.USE_WIDGET); icon.setNoClickThrough(true); icon.setOnTargetEnterListener((JavaScriptCallback) ev -> { wikiSelected = true; icon.setSpriteId(WikiSprite.WIKI_SELECTED_ICON.getSpriteId()); + client.setAllWidgetsAreOpTargetable(true); }); icon.setAction(5, "Search"); // Start at option 5 so the target op is ontop icon.setOnOpListener((JavaScriptCallback) ev -> @@ -199,6 +199,8 @@ public class WikiPlugin extends Plugin private void onDeselect() { + client.setAllWidgetsAreOpTargetable(false); + wikiSelected = false; if (icon != null) { @@ -208,6 +210,7 @@ public class WikiPlugin extends Plugin private void onMenuOptionClicked(MenuOptionClicked ev) { + optarget: if (wikiSelected) { onDeselect(); @@ -221,6 +224,9 @@ public class WikiPlugin extends Plugin switch (ev.getMenuOpcode()) { + case RUNELITE: + // This is a quest widget op + break optarget; case CANCEL: return; case ITEM_USE_ON_WIDGET: @@ -255,6 +261,18 @@ public class WikiPlugin extends Plugin location = WorldPoint.fromScene(client, ev.getParam0(), ev.getParam1(), client.getPlane()); break; } + case SPELL_CAST_ON_WIDGET: + Widget w = getWidget(ev.getParam1(), ev.getParam0()); + + if (w.getType() == WidgetType.GRAPHIC && w.getItemId() != -1) + { + type = "item"; + id = itemManager.canonicalize(w.getItemId()); + name = itemManager.getItemDefinition(id).getName(); + location = null; + break; + } + // fallthrough default: log.info("Unknown menu option: {} {} {}", ev, ev.getMenuOpcode(), ev.getMenuOpcode() == MenuOpcode.CANCEL); return; @@ -303,25 +321,11 @@ public class WikiPlugin extends Plugin LinkBrowser.browse(ub.build().toString()); break; case MENUOP_WIKI: - Matcher skillRegex = WikiPlugin.SKILL_REGEX.matcher(Text.removeTags(ev.getTarget())); - Matcher diaryRegex = WikiPlugin.DIARY_REGEX.matcher(Text.removeTags(ev.getTarget())); - - if (skillRegex.find()) - { - LinkBrowser.browse(WIKI_BASE.newBuilder() - .addPathSegment("w") - .addPathSegment(skillRegex.group(1)) - .addQueryParameter(UTM_SOURCE_KEY, UTM_SOURCE_VALUE) - .build().toString()); - } - else if (diaryRegex.find()) - { - LinkBrowser.browse(WIKI_BASE.newBuilder() - .addPathSegment("w") - .addPathSegment(diaryRegex.group(1) + " Diary") - .addQueryParameter(UTM_SOURCE_KEY, UTM_SOURCE_VALUE) - .build().toString()); - } + LinkBrowser.browse(WIKI_BASE.newBuilder() + .addPathSegment("w") + .addPathSegment(Text.removeTags(ev.getTarget())) + .addQueryParameter(UTM_SOURCE_KEY, UTM_SOURCE_VALUE) + .build().toString()); } } } @@ -332,16 +336,51 @@ public class WikiPlugin extends Plugin .build(); } + private Widget getWidget(int wid, int index) + { + Widget w = client.getWidget(WidgetInfo.TO_GROUP(wid), WidgetInfo.TO_CHILD(wid)); + if (index != -1) + { + w = w.getChild(index); + } + return w; + } + private void onMenuEntryAdded(MenuEntryAdded event) { int widgetIndex = event.getParam0(); int widgetID = event.getParam1(); + MenuEntry[] menuEntries = client.getMenuEntries(); - if (Ints.contains(QUESTLIST_WIDGET_IDS, widgetID) && "Read Journal:".equals(event.getOption())) + if (wikiSelected && event.getOpcode() == MenuOpcode.SPELL_CAST_ON_WIDGET.getId()) { + Widget w = getWidget(widgetID, widgetIndex); + if (!(w.getType() == WidgetType.GRAPHIC && w.getItemId() != -1)) + { + // we don't support this widget + // remove the last SPELL_CAST_ON_WIDGET; we can't blindly remove the top action because some other + // plugin might have added something on this same event, and we probably shouldn't remove that instead + MenuEntry[] oldEntries = menuEntries; + menuEntries = Arrays.copyOf(menuEntries, menuEntries.length - 1); + for (int ourEntry = oldEntries.length - 1; + ourEntry >= 2 && oldEntries[oldEntries.length - 1].getOpcode() != MenuOpcode.SPELL_CAST_ON_WIDGET.getId(); + ourEntry--) + { + menuEntries[ourEntry - 1] = oldEntries[ourEntry]; + } + client.setMenuEntries(menuEntries); + } + } + + if (Ints.contains(QUESTLIST_WIDGET_IDS, widgetID) + && ((wikiSelected && widgetIndex != -1) || "Read Journal:".equals(event.getOption()))) + { + Widget w = getWidget(widgetID, widgetIndex); + String target = w.getName(); + client.insertMenuItem( MENUOP_QUICKGUIDE, - event.getTarget(), + target, MenuOpcode.RUNELITE.getId(), 0, widgetIndex, @@ -351,7 +390,7 @@ public class WikiPlugin extends Plugin client.insertMenuItem( MENUOP_GUIDE, - event.getTarget(), + target, MenuOpcode.RUNELITE.getId(), 0, widgetIndex, @@ -360,12 +399,52 @@ public class WikiPlugin extends Plugin ); } - if ((WidgetInfo.TO_GROUP(widgetID) == WidgetID.SKILLS_GROUP_ID && event.getOption().startsWith("View")) - || (WidgetInfo.TO_GROUP(widgetID) == WidgetID.ACHIEVEMENT_DIARY_GROUP_ID && event.getOption().startsWith("Open"))) + if (widgetID == WidgetInfo.ACHIEVEMENT_DIARY_CONTAINER.getId()) { + Widget w = getWidget(widgetID, widgetIndex); + if (w.getActions() == null) + { + return; + } + + String action = Stream.of(w.getActions()) + .filter(s -> s != null && !s.isEmpty()) + .findFirst().orElse(null); + if (action == null) + { + return; + } + client.insertMenuItem( MENUOP_WIKI, - event.getOption().replace("View ", "").replace("Open ", ""), + action.replace("Open ", "").replace("Journal", "Diary"), + MenuOpcode.RUNELITE.getId(), + 0, + widgetIndex, + widgetID, + false + ); + } + + if (WidgetInfo.TO_GROUP(widgetID) == WidgetInfo.SKILLS_CONTAINER.getGroupId()) + { + Widget w = getWidget(widgetID, widgetIndex); + if (w.getParentId() != WidgetInfo.SKILLS_CONTAINER.getId()) + { + return; + } + + String action = Stream.of(w.getActions()) + .filter(s -> s != null && !s.isEmpty()) + .findFirst().orElse(null); + if (action == null) + { + return; + } + + client.insertMenuItem( + MENUOP_WIKI, + action.replace("View ", "").replace("Guide ", ""), MenuOpcode.RUNELITE.getId(), event.getIdentifier(), widgetIndex, diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java index 42156c3105..44d0b9290c 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java @@ -316,7 +316,7 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener graphics.setColor(previous); } - if (menuEntries == null && !client.isMenuOpen() && bounds.contains(mouse)) + if (menuEntries == null && !client.isMenuOpen() && !client.isSpellSelected() && bounds.contains(mouse)) { menuEntries = createRightClickMenuEntries(overlay); } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSBoundaryObjectMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSBoundaryObjectMixin.java index aa3e32b502..90e39a4ea0 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSBoundaryObjectMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSBoundaryObjectMixin.java @@ -1,10 +1,10 @@ package net.runelite.mixins; -import java.awt.Polygon; import java.awt.Shape; import net.runelite.api.Model; import net.runelite.api.Perspective; import net.runelite.api.coords.LocalPoint; +import net.runelite.api.geometry.Shapes; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Shadow; @@ -86,6 +86,11 @@ public abstract class RSBoundaryObjectMixin implements RSBoundaryObject return null; } + if (clickboxA != null && clickboxB != null) + { + return new Shapes(new Shape[]{clickboxA, clickboxB}); + } + if (clickboxA != null) { return clickboxA; @@ -94,11 +99,9 @@ public abstract class RSBoundaryObjectMixin implements RSBoundaryObject return clickboxB; } - - @Inject @Override - public Polygon getConvexHull() + public Shape getConvexHull() { RSModel model = getModelA(); @@ -108,6 +111,23 @@ public abstract class RSBoundaryObjectMixin implements RSBoundaryObject } int tileHeight = Perspective.getTileHeight(client, new LocalPoint(getX(), getY()), client.getPlane()); + + return model.getConvexHull(getX(), getY(), 0, tileHeight); + } + + @Inject + @Override + public Shape getConvexHull2() + { + RSModel model = getModelB(); + + if (model == null) + { + return null; + } + + int tileHeight = Perspective.getTileHeight(client, new LocalPoint(getX(), getY()), client.getPlane()); + return model.getConvexHull(getX(), getY(), 0, tileHeight); } } 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 1ea35c1cc5..c2d2bb72cc 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java @@ -49,6 +49,7 @@ import net.runelite.api.HintArrowType; import net.runelite.api.Ignore; import net.runelite.api.IndexDataBase; import net.runelite.api.IndexedSprite; +import net.runelite.api.IntegerNode; import net.runelite.api.InventoryID; import net.runelite.api.MenuEntry; import net.runelite.api.MenuOpcode; @@ -111,6 +112,7 @@ import net.runelite.api.mixins.Replace; import net.runelite.api.mixins.Shadow; import net.runelite.api.vars.AccountType; import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetConfig; import net.runelite.api.widgets.WidgetInfo; import net.runelite.api.widgets.WidgetItem; import net.runelite.api.widgets.WidgetType; @@ -215,6 +217,9 @@ public abstract class RSClientMixin implements RSClient @Inject private static boolean hideClanmateCastOptions = false; + @Inject + private static boolean allWidgetsAreOpTargetable = false; + @Inject private static Set unhiddenCasts = new HashSet(); @@ -260,6 +265,13 @@ public abstract class RSClientMixin implements RSClient hideClanmateCastOptions = yes; } + @Inject + @Override + public void setAllWidgetsAreOpTargetable(boolean yes) + { + allWidgetsAreOpTargetable = yes; + } + @Inject @Override public void setUnhiddenCasts(Set casts) @@ -1820,4 +1832,28 @@ public abstract class RSClientMixin implements RSClient client.getCallbacks().post(VolumeChanged.class, volumeChanged); } } + + @Replace("getWidgetClickMask") + public static int getWidgetClickMask(Widget widget) + { + IntegerNode integerNode = (IntegerNode) client.getWidgetFlags().get(((long) widget.getId() << 32) + (long) widget.getIndex()); + + int widgetClickMask; + + if (integerNode == null) + { + widgetClickMask = widget.getClickMask(); + } + else + { + widgetClickMask = integerNode.getValue(); + } + + if (allWidgetsAreOpTargetable) + { + widgetClickMask |= WidgetConfig.WIDGET_USE_TARGET; + } + + return widgetClickMask; + } } \ No newline at end of file diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSFloorDecorationMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSFloorDecorationMixin.java index f1149980ef..1ae112a915 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSFloorDecorationMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSFloorDecorationMixin.java @@ -1,6 +1,5 @@ package net.runelite.mixins; -import java.awt.Polygon; import java.awt.Shape; import net.runelite.api.Model; import net.runelite.api.Perspective; @@ -60,7 +59,7 @@ public abstract class RSFloorDecorationMixin implements RSFloorDecoration @Inject @Override - public Polygon getConvexHull() + public Shape getConvexHull() { RSModel model = getModel(); @@ -70,6 +69,7 @@ public abstract class RSFloorDecorationMixin implements RSFloorDecoration } int tileHeight = Perspective.getTileHeight(client, new LocalPoint(getX(), getY()), client.getPlane()); + return model.getConvexHull(getX(), getY(), 0, tileHeight); } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java index 1b34663f6f..2935fcdaa5 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java @@ -24,7 +24,6 @@ */ package net.runelite.mixins; -import java.awt.Polygon; import java.awt.Shape; import net.runelite.api.Perspective; import net.runelite.api.Point; @@ -87,7 +86,7 @@ public abstract class RSGameObjectMixin implements RSGameObject @Inject @Override - public Polygon getConvexHull() + public Shape getConvexHull() { RSModel model = getModel(); @@ -97,6 +96,7 @@ public abstract class RSGameObjectMixin implements RSGameObject } int tileHeight = Perspective.getTileHeight(client, new LocalPoint(getX(), getY()), client.getPlane()); + return model.getConvexHull(getX(), getY(), getRsOrientation(), tileHeight); } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSItemContainerMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSItemContainerMixin.java index c12aa762a6..504125de14 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSItemContainerMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSItemContainerMixin.java @@ -24,13 +24,11 @@ */ package net.runelite.mixins; -import net.runelite.api.InventoryID; import net.runelite.api.Item; import net.runelite.api.events.ItemContainerChanged; -import net.runelite.api.mixins.Copy; +import net.runelite.api.mixins.FieldHook; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; -import net.runelite.api.mixins.Replace; import net.runelite.api.mixins.Shadow; import net.runelite.rs.api.RSClient; import net.runelite.rs.api.RSItemContainer; @@ -41,11 +39,8 @@ public abstract class RSItemContainerMixin implements RSItemContainer @Shadow("client") private static RSClient client; - @Inject - static private int rl$lastCycle; - - @Inject - static private int rl$lastContainer; + @Shadow("changedItemContainers") + private static int[] changedItemContainers; @Inject @Override @@ -67,35 +62,22 @@ public abstract class RSItemContainerMixin implements RSItemContainer return items; } - @Copy("itemContainerSetItem") - static void rs$itemContainerSetItem(int itemContainerId, int index, int itemId, int itemQuantity) + @FieldHook("changedItemContainers") + @Inject + public static void onItemContainerUpdate(int idx) { - } - - @Replace("itemContainerSetItem") - static void rl$itemContainerSetItem(int itemContainerId, int index, int itemId, int itemQuantity) - { - rs$itemContainerSetItem(itemContainerId, index, itemId, itemQuantity); - - int cycle = client.getGameCycle(); - - if (rl$lastCycle == cycle && rl$lastContainer == itemContainerId) + if (idx != -1) { - // Limit item container updates to one per cycle per container - return; + int changedId = idx - 1 & 31; + int containerId = changedItemContainers[changedId]; + + RSItemContainer changedContainer = (RSItemContainer) client.getItemContainers().get(containerId); + + if (changedContainer != null) + { + ItemContainerChanged event = new ItemContainerChanged(containerId, changedContainer); + client.getCallbacks().postDeferred(ItemContainerChanged.class, event); + } } - - InventoryID container = InventoryID.getValue(itemContainerId); - - if (container == null) - { - return; - } - - rl$lastCycle = cycle; - rl$lastContainer = itemContainerId; - - ItemContainerChanged event = new ItemContainerChanged(itemContainerId, client.getItemContainer(container)); - client.getCallbacks().postDeferred(ItemContainerChanged.class, event); } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSModelMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSModelMixin.java index c9e2f52de6..0164864b24 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSModelMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSModelMixin.java @@ -24,12 +24,11 @@ */ package net.runelite.mixins; -import java.awt.Polygon; +import java.awt.Shape; import java.util.ArrayList; import java.util.List; import net.runelite.api.Model; import net.runelite.api.Perspective; -import net.runelite.api.Point; import net.runelite.api.mixins.Copy; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.MethodHook; @@ -140,7 +139,7 @@ public abstract class RSModelMixin implements RSModel int[] trianglesZ = getTrianglesZ(); List vertices = getVertices(); - List triangles = new ArrayList(getTrianglesCount()); + List triangles = new ArrayList<>(getTrianglesCount()); for (int i = 0; i < getTrianglesCount(); ++i) { @@ -321,49 +320,14 @@ public abstract class RSModelMixin implements RSModel @Override @Inject - public Polygon getConvexHull(int localX, int localY, int orientation, int tileHeight) + public Shape getConvexHull(int localX, int localY, int orientation, int tileHeight) { - assert client.isClientThread(); + int[] x2d = new int[this.getVerticesCount()]; + int[] y2d = new int[this.getVerticesCount()]; - List vertices = getVertices(); + Perspective.modelToCanvas(client, this.getVerticesCount(), localX, localY, tileHeight, orientation, this.getVerticesX(), this.getVerticesZ(), this.getVerticesY(), x2d, y2d); - // rotate vertices - for (int i = 0; i < vertices.size(); ++i) - { - Vertex v = vertices.get(i); - vertices.set(i, v.rotate(orientation)); - } - - List points = new ArrayList(); - - for (Vertex v : vertices) - { - // Compute canvas location of vertex - Point p = Perspective.localToCanvas(client, - localX - v.getX(), - localY - v.getZ(), - tileHeight + v.getY()); - if (p != null) - { - points.add(p); - } - } - - // Run Jarvis march algorithm - points = Jarvis.convexHull(points); - if (points == null) - { - return null; - } - - // Convert to a polygon - Polygon p = new Polygon(); - for (Point point : points) - { - p.addPoint(point.getX(), point.getY()); - } - - return p; + return Jarvis.convexHull(x2d, y2d); } @Inject diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSNPCMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSNPCMixin.java index bb7932fe63..0d7b46d337 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSNPCMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSNPCMixin.java @@ -24,7 +24,7 @@ */ package net.runelite.mixins; -import java.awt.Polygon; +import java.awt.Shape; import net.runelite.api.AnimationID; import net.runelite.api.NPCDefinition; import net.runelite.api.Perspective; @@ -178,7 +178,7 @@ public abstract class RSNPCMixin implements RSNPC @Inject @Override - public Polygon getConvexHull() + public Shape getConvexHull() { RSModel model = getModel(); if (model == null) @@ -192,6 +192,7 @@ public abstract class RSNPCMixin implements RSNPC size * Perspective.LOCAL_HALF_TILE_SIZE - Perspective.LOCAL_HALF_TILE_SIZE + getY()); int tileHeight = Perspective.getTileHeight(client, tileHeightPoint, client.getPlane()); + return model.getConvexHull(getX(), getY(), getOrientation(), tileHeight); } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSPlayerMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSPlayerMixin.java index 7d04713ff3..56fad5dd38 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSPlayerMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSPlayerMixin.java @@ -25,8 +25,8 @@ package net.runelite.mixins; import java.awt.Polygon; +import java.awt.Shape; import java.util.ArrayList; -import java.util.List; import net.runelite.api.HeadIcon; import static net.runelite.api.HeadIcon.MAGIC; import static net.runelite.api.HeadIcon.MELEE; @@ -36,7 +36,6 @@ import static net.runelite.api.HeadIcon.RETRIBUTION; import static net.runelite.api.HeadIcon.SMITE; import net.runelite.api.Model; import net.runelite.api.Perspective; -import net.runelite.api.Point; import net.runelite.api.SkullIcon; import static net.runelite.api.SkullIcon.DEAD_MAN_FIVE; import static net.runelite.api.SkullIcon.DEAD_MAN_FOUR; @@ -52,8 +51,6 @@ import net.runelite.api.mixins.MethodHook; import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Replace; import net.runelite.api.mixins.Shadow; -import net.runelite.api.model.Triangle; -import net.runelite.api.model.Vertex; import net.runelite.rs.api.RSClient; import net.runelite.rs.api.RSModel; import net.runelite.rs.api.RSPlayer; @@ -148,58 +145,42 @@ public abstract class RSPlayerMixin implements RSPlayer return null; } + int[] x2d = new int[model.getVerticesCount()]; + int[] y2d = new int[model.getVerticesCount()]; + int localX = getX(); int localY = getY(); - int orientation = getOrientation(); - final int tileHeight = Perspective.getTileHeight(client, new LocalPoint(localX, localY), client.getPlane()); - List triangles = model.getTriangles(); + Perspective.modelToCanvas(client, model.getVerticesCount(), localX, localY, tileHeight, getOrientation(), model.getVerticesX(), model.getVerticesZ(), model.getVerticesY(), x2d, y2d); + ArrayList polys = new ArrayList(model.getTrianglesCount()); - triangles = rotate(triangles, orientation); + int[] trianglesX = model.getTrianglesX(); + int[] trianglesY = model.getTrianglesY(); + int[] trianglesZ = model.getTrianglesZ(); - List polys = new ArrayList(); - for (Triangle triangle : triangles) + for (int triangle = 0; triangle < model.getTrianglesCount(); ++triangle) { - Vertex vx = triangle.getA(); - Vertex vy = triangle.getB(); - Vertex vz = triangle.getC(); - - System.err.println("vx: " + vx.getX() + " localX: " + localX); - - Point x = Perspective.localToCanvas(client, - localX - vx.getX(), - localY - vx.getZ(), - tileHeight + vx.getY()); - - Point y = Perspective.localToCanvas(client, - localX - vy.getX(), - localY - vy.getZ(), - tileHeight + vy.getY()); - - Point z = Perspective.localToCanvas(client, - localX - vz.getX(), - localY - vz.getZ(), - tileHeight + vz.getY()); - int[] xx = { - x.getX(), y.getX(), z.getX() + x2d[trianglesX[triangle]], x2d[trianglesY[triangle]], x2d[trianglesZ[triangle]] }; + int[] yy = { - x.getY(), y.getY(), z.getY() + y2d[trianglesX[triangle]], y2d[trianglesY[triangle]], y2d[trianglesZ[triangle]] }; + polys.add(new Polygon(xx, yy, 3)); } - return polys.toArray(new Polygon[polys.size()]); + return (Polygon[]) polys.toArray(new Polygon[0]); } @Inject @Override - public Polygon getConvexHull() + public Shape getConvexHull() { RSModel model = getModel(); if (model == null) @@ -208,29 +189,10 @@ public abstract class RSPlayerMixin implements RSPlayer } int tileHeight = Perspective.getTileHeight(client, new LocalPoint(getX(), getY()), client.getPlane()); + return model.getConvexHull(getX(), getY(), getOrientation(), tileHeight); } - @Inject - private List rotate(List triangles, int orientation) - { - List rotatedTriangles = new ArrayList(); - for (Triangle triangle : triangles) - { - Vertex a = triangle.getA(); - Vertex b = triangle.getB(); - Vertex c = triangle.getC(); - - Triangle rotatedTriangle = new Triangle( - a.rotate(orientation), - b.rotate(orientation), - c.rotate(orientation) - ); - rotatedTriangles.add(rotatedTriangle); - } - return rotatedTriangles; - } - @Copy("getModel") public abstract RSModel rs$getModel(); diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSWallDecorationMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSWallDecorationMixin.java index c1395bc3c0..16da2a8abf 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSWallDecorationMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSWallDecorationMixin.java @@ -1,11 +1,10 @@ package net.runelite.mixins; -import java.awt.Polygon; import java.awt.Shape; -import java.awt.geom.Area; import net.runelite.api.Model; import net.runelite.api.Perspective; import net.runelite.api.coords.LocalPoint; +import net.runelite.api.geometry.Shapes; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Shadow; @@ -89,9 +88,8 @@ public abstract class RSWallDecorationMixin implements RSWallDecoration @Override public Shape getClickbox() { - Area clickbox = new Area(); - LocalPoint lp = getLocalLocation(); + Shape clickboxA = Perspective.getClickbox(client, getModel1(), 0, new LocalPoint(lp.getX() + getXOffset(), lp.getY() + getYOffset())); Shape clickboxB = Perspective.getClickbox(client, getModel2(), 0, lp); @@ -101,6 +99,11 @@ public abstract class RSWallDecorationMixin implements RSWallDecoration return null; } + if (clickboxA != null && clickboxB != null) + { + return new Shapes(new Shape[]{clickboxA, clickboxB}); + } + if (clickboxA != null) { return clickboxA; @@ -111,7 +114,7 @@ public abstract class RSWallDecorationMixin implements RSWallDecoration @Inject @Override - public Polygon getConvexHull() + public Shape getConvexHull() { RSModel model = getModel1(); @@ -121,12 +124,13 @@ public abstract class RSWallDecorationMixin implements RSWallDecoration } int tileHeight = Perspective.getTileHeight(client, new LocalPoint(getX(), getY()), client.getPlane()); + return model.getConvexHull(getX() + getXOffset(), getY() + getYOffset(), 0, tileHeight); } @Inject @Override - public Polygon getConvexHull2() + public Shape getConvexHull2() { RSModel model = getModel2(); @@ -136,6 +140,7 @@ public abstract class RSWallDecorationMixin implements RSWallDecoration } int tileHeight = Perspective.getTileHeight(client, new LocalPoint(getX(), getY()), client.getPlane()); + return model.getConvexHull(getX(), getY(), 0, tileHeight); } } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSModel.java b/runescape-api/src/main/java/net/runelite/rs/api/RSModel.java index c4e2786ca4..e2e5d3d79b 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSModel.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSModel.java @@ -24,8 +24,8 @@ */ package net.runelite.rs.api; +import java.awt.Shape; import net.runelite.api.Model; -import java.awt.Polygon; import net.runelite.mapping.Import; public interface RSModel extends RSEntity, Model @@ -163,7 +163,7 @@ public interface RSModel extends RSEntity, Model /** * Compute the convex hull of this model */ - Polygon getConvexHull(int localX, int localY, int orientation, int tileHeight); + Shape getConvexHull(int localX, int localY, int orientation, int tileHeight); float[][] getFaceTextureUCoordinates(); void setFaceTextureUCoordinates(float[][] rl$faceTextureUCoordinates); diff --git a/runescape-client/src/main/java/ArchiveLoader.java b/runescape-client/src/main/java/ArchiveLoader.java index 5b2bdcfdb1..16c5340e59 100644 --- a/runescape-client/src/main/java/ArchiveLoader.java +++ b/runescape-client/src/main/java/ArchiveLoader.java @@ -723,7 +723,7 @@ public class ArchiveLoader { if (var9.invTransmitTriggers != null && Client.field820 - var9.field2567 <= 32) { label862: for (var35 = var9.field2567; var35 < Client.field820; ++var35) { - var23 = Client.field816[var35 & 31]; + var23 = Client.changedItemContainers[var35 & 31]; for (var36 = 0; var36 < var9.invTransmitTriggers.length; ++var36) { if (var23 == var9.invTransmitTriggers[var36]) { diff --git a/runescape-client/src/main/java/Client.java b/runescape-client/src/main/java/Client.java index 4ec14479d6..22e6bce188 100644 --- a/runescape-client/src/main/java/Client.java +++ b/runescape-client/src/main/java/Client.java @@ -216,7 +216,8 @@ public final class Client extends GameShell implements Usernamed { @Export("meslayerContinueWidget") static Widget meslayerContinueWidget; @ObfuscatedName("no") - static int[] field816; + @Export("changedItemContainers") + static int[] changedItemContainers; @ObfuscatedName("sm") @ObfuscatedGetter( intValue = 857681821 @@ -1451,7 +1452,7 @@ public final class Client extends GameShell implements Usernamed { cycleCntr = 1; field817 = new int[32]; field679 = 0; - field816 = new int[32]; + changedItemContainers = new int[32]; field820 = 0; changedSkills = new int[32]; changedSkillsCount = 0; @@ -5071,7 +5072,7 @@ public final class Client extends GameShell implements Usernamed { } BoundaryObject.method3393(); - field816[++field820 - 1 & 31] = var5 & 32767; + changedItemContainers[++field820 - 1 & 31] = var5 & 32767; var1.serverPacket = null; return true; } @@ -5079,7 +5080,7 @@ public final class Client extends GameShell implements Usernamed { if (ServerPacket.field2136 == var1.serverPacket) { var16 = var3.method5646(); WorldMapIcon_0.method252(var16); - field816[++field820 - 1 & 31] = var16 & 32767; + changedItemContainers[++field820 - 1 & 31] = var16 & 32767; var1.serverPacket = null; return true; } @@ -5590,7 +5591,7 @@ public final class Client extends GameShell implements Usernamed { } BoundaryObject.method3393(); - field816[++field820 - 1 & 31] = var5 & 32767; + changedItemContainers[++field820 - 1 & 31] = var5 & 32767; var1.serverPacket = null; return true; }