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 6b2cea5d6c..3dc8a3a4c2 100644
--- a/runelite-api/src/main/java/net/runelite/api/Client.java
+++ b/runelite-api/src/main/java/net/runelite/api/Client.java
@@ -412,15 +412,6 @@ public interface Client extends GameEngine
*/
Widget getWidget(WidgetInfo widget);
- /**
- * Gets an array of widgets that correspond to the passed group ID.
- *
- * @param groupId the group ID
- * @return the widget group
- * @see net.runelite.api.widgets.WidgetID
- */
- Widget[] getGroup(int groupId);
-
/**
* Gets a widget by its raw group ID and child ID.
*
@@ -692,7 +683,7 @@ public interface Client extends GameEngine
* @return the widget node component table
* @see WidgetNode
*/
- HashTable getComponentTable();
+ HashTable getComponentTable();
/**
* Gets an array of current grand exchange offers.
diff --git a/runelite-api/src/main/java/net/runelite/api/HashTable.java b/runelite-api/src/main/java/net/runelite/api/HashTable.java
index 32a3f7d3e3..a7adda6c32 100644
--- a/runelite-api/src/main/java/net/runelite/api/HashTable.java
+++ b/runelite-api/src/main/java/net/runelite/api/HashTable.java
@@ -30,7 +30,7 @@ import java.util.Collection;
* A data structure that uses a hash function to compute an index into an
* array of buckets from which node objects can be quickly obtained.
*/
-public interface HashTable
+public interface HashTable
{
/**
* Gets a node by its hash value.
@@ -38,12 +38,12 @@ public interface HashTable
* @param value the node value
* @return the associated node
*/
- Node get(long value);
+ T get(long value);
/**
* Gets a collection of all nodes stored in this table.
*
* @return the nodes stored
*/
- Collection getNodes();
+ Collection getNodes();
}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/reorderprayers/ReorderPrayersPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/reorderprayers/ReorderPrayersPlugin.java
index 370bcae612..8d7690579a 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/reorderprayers/ReorderPrayersPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/reorderprayers/ReorderPrayersPlugin.java
@@ -36,14 +36,13 @@ import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.HashTable;
-import net.runelite.api.Node;
import net.runelite.api.Prayer;
import net.runelite.api.WidgetNode;
import net.runelite.api.events.ConfigChanged;
import net.runelite.api.events.DraggingWidgetChanged;
import net.runelite.api.events.GameStateChanged;
-import net.runelite.api.events.WidgetMenuOptionClicked;
import net.runelite.api.events.WidgetLoaded;
+import net.runelite.api.events.WidgetMenuOptionClicked;
import net.runelite.api.widgets.Widget;
import static net.runelite.api.widgets.WidgetConfig.DRAG;
import static net.runelite.api.widgets.WidgetConfig.DRAG_ON;
@@ -339,10 +338,9 @@ public class ReorderPrayersPlugin extends Plugin
private PrayerTabState getPrayerTabState()
{
- HashTable componentTable = client.getComponentTable();
- for (Node node : componentTable.getNodes())
+ HashTable componentTable = client.getComponentTable();
+ for (WidgetNode widgetNode : componentTable.getNodes())
{
- WidgetNode widgetNode = (WidgetNode) node;
if (widgetNode.getId() == WidgetID.PRAYER_GROUP_ID)
{
return PrayerTabState.PRAYERS;
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 ae63a28ae8..48b37398e1 100644
--- a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java
+++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java
@@ -35,6 +35,7 @@ import net.runelite.api.Friend;
import net.runelite.api.GameState;
import net.runelite.api.GrandExchangeOffer;
import net.runelite.api.GraphicsObject;
+import net.runelite.api.HashTable;
import net.runelite.api.HintArrowType;
import net.runelite.api.IndexedSprite;
import net.runelite.api.InventoryID;
@@ -61,6 +62,7 @@ import net.runelite.api.SpritePixels;
import net.runelite.api.Tile;
import net.runelite.api.VarPlayer;
import net.runelite.api.Varbits;
+import net.runelite.api.WidgetNode;
import net.runelite.api.WorldType;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldPoint;
@@ -321,9 +323,9 @@ public abstract class RSClientMixin implements RSClient
{
int topGroup = getWidgetRoot();
List widgets = new ArrayList();
- for (Widget widget : getWidgets()[topGroup])
+ for (RSWidget widget : getWidgets()[topGroup])
{
- if (widget != null && widget.getParentId() == -1)
+ if (widget != null && widget.getRSParentId() == -1)
{
widgets.add(widget);
}
@@ -343,7 +345,7 @@ public abstract class RSClientMixin implements RSClient
@Inject
@Override
- public Widget[] getGroup(int groupId)
+ public RSWidget[] getGroup(int groupId)
{
RSWidget[][] widgets = getWidgets();
@@ -352,15 +354,7 @@ public abstract class RSClientMixin implements RSClient
return null;
}
- List w = new ArrayList();
- for (Widget widget : widgets[groupId])
- {
- if (widget != null)
- {
- w.add(widget);
- }
- }
- return w.toArray(new Widget[w.size()]);
+ return widgets[groupId];
}
@Inject
@@ -564,7 +558,7 @@ public abstract class RSClientMixin implements RSClient
for (Node node = head.getNext(); node != head; node = node.getNext())
{
- graphicsObjects.add((GraphicsObject)node);
+ graphicsObjects.add((GraphicsObject) node);
}
return graphicsObjects;
@@ -1099,4 +1093,45 @@ public abstract class RSClientMixin implements RSClient
{
callbacks.clientMainLoop();
}
+
+ @MethodHook("gameDraw")
+ @Inject
+ public static void gameDraw(Widget[] widgets, int parentId, int var2, int var3, int var4, int var5, int x, int y, int var8)
+ {
+ for (Widget rlWidget : widgets)
+ {
+ RSWidget widget = (RSWidget) rlWidget;
+ if (widget == null)
+ {
+ continue;
+ }
+
+ if (widget.getRSParentId() == parentId)
+ {
+ if (parentId != -1)
+ {
+ widget.setRenderParentId(parentId);
+ }
+ widget.setRenderX(x + widget.getRelativeX());
+ widget.setRenderY(y + widget.getRelativeY());
+ }
+
+ HashTable componentTable = client.getComponentTable();
+ WidgetNode childNode = componentTable.get(widget.getId());
+ if (childNode != null)
+ {
+ int widgetId = widget.getId();
+ int groupId = childNode.getId();
+ RSWidget[] children = client.getWidgets()[groupId];
+
+ for (RSWidget child : children)
+ {
+ if (child.getRSParentId() == -1)
+ {
+ child.setRenderParentId(widgetId);
+ }
+ }
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java
index 14a44ec564..fe02d93f1a 100644
--- a/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java
+++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java
@@ -28,7 +28,7 @@ import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import net.runelite.api.Node;
+import net.runelite.api.HashTable;
import net.runelite.api.Point;
import net.runelite.api.WidgetNode;
import net.runelite.api.events.WidgetHiddenChanged;
@@ -42,8 +42,6 @@ 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.rs.api.RSClient;
-import net.runelite.rs.api.RSHashTable;
-import net.runelite.rs.api.RSNode;
import net.runelite.rs.api.RSWidget;
@Mixin(RSWidget.class)
@@ -57,6 +55,44 @@ public abstract class RSWidgetMixin implements RSWidget
@Inject
private static int rl$widgetLastPosChanged;
+ @Inject
+ private int rl$parentId;
+
+ @Inject
+ private int rl$x;
+
+ @Inject
+ private int rl$y;
+
+ @Inject
+ RSWidgetMixin()
+ {
+ rl$parentId = -1;
+ rl$x = -1;
+ rl$y = -1;
+ }
+
+ @Inject
+ @Override
+ public void setRenderParentId(int parentId)
+ {
+ rl$parentId = parentId;
+ }
+
+ @Inject
+ @Override
+ public void setRenderX(int x)
+ {
+ rl$x = x;
+ }
+
+ @Inject
+ @Override
+ public void setRenderY(int y)
+ {
+ rl$y = y;
+ }
+
@Inject
@Override
public Widget getParent()
@@ -74,34 +110,27 @@ public abstract class RSWidgetMixin implements RSWidget
@Override
public int getParentId()
{
- int parentId = getRSParentId();
- if (parentId != -1)
- {
- return parentId;
- }
+ int parentId = rl$parentId;
- int groupId = TO_GROUP(getId());
- RSHashTable componentTable = client.getComponentTable();
- RSNode[] buckets = componentTable.getBuckets();
- for (int i = 0; i < buckets.length; ++i)
+ if (parentId != -1 && getRSParentId() == -1)
{
- Node node = buckets[i];
+ // if this happens, the widget is or was nested.
+ // rl$parentId is updated when drawing, but will not be updated when
+ // the widget is no longer reachable in the tree, leaving
+ // parent id potentially incorrect
- // It looks like the first node in the bucket is always
- // a sentinel
- Node cur = node.getNext();
- while (cur != node)
+ // check the parent in the component table
+ HashTable componentTable = client.getComponentTable();
+ WidgetNode widgetNode = componentTable.get(parentId);
+ if (widgetNode == null || widgetNode.getId() != TO_GROUP(getId()))
{
- WidgetNode wn = (WidgetNode) cur;
-
- if (groupId == wn.getId())
- {
- return (int) wn.getHash();
- }
- cur = cur.getNext();
+ // invalidate parent
+ rl$parentId = -1;
+ return -1;
}
}
- return -1;
+
+ return parentId;
}
@Inject
@@ -153,42 +182,7 @@ public abstract class RSWidgetMixin implements RSWidget
@Override
public Point getCanvasLocation()
{
- int x = 0;
- int y = 0;
- RSWidget cur;
-
- for (cur = this; cur.getParent() != null; cur = (RSWidget) cur.getParent())
- {
- x += cur.getRelativeX();
- y += cur.getRelativeY();
-
- x -= cur.getScrollX();
- y -= cur.getScrollY();
- }
-
- // cur is now the root
- int[] widgetBoundsWidth = client.getWidgetPositionsX();
- int[] widgetBoundsHeight = client.getWidgetPositionsY();
-
- int boundsIndex = cur.getBoundsIndex();
- if (boundsIndex != -1)
- {
- x += widgetBoundsWidth[boundsIndex];
- y += widgetBoundsHeight[boundsIndex];
-
- if (cur.getType() > 0)
- {
- x += cur.getRelativeX();
- y += cur.getRelativeY();
- }
- }
- else
- {
- x += cur.getRelativeX();
- y += cur.getRelativeY();
- }
-
- return new Point(x, y);
+ return new Point(rl$x, rl$y);
}
@Inject
@@ -285,9 +279,9 @@ public abstract class RSWidgetMixin implements RSWidget
}
List widgets = new ArrayList();
- for (Widget widget : children)
+ for (RSWidget widget : children)
{
- if (widget != null && widget.getParentId() == getId())
+ if (widget != null && widget.getRSParentId() == getId())
{
widgets.add(widget);
}
@@ -299,51 +293,52 @@ public abstract class RSWidgetMixin implements RSWidget
@Override
public Widget[] getStaticChildren()
{
- List widgets = new ArrayList();
- for (Widget widget : client.getGroup(TO_GROUP(getId())))
+ if (getRSParentId() == getId())
{
- if (widget != null && widget.getParentId() == getId())
+ // This is a dynamic widget, so it can't have static children
+ return new Widget[0];
+ }
+
+ List widgets = new ArrayList();
+ for (RSWidget widget : client.getGroup(TO_GROUP(getId())))
+ {
+ if (widget != null && widget.getRSParentId() == getId())
{
widgets.add(widget);
}
}
- return widgets.toArray(new Widget[widgets.size()]);
+ return widgets.toArray(new RSWidget[widgets.size()]);
}
@Inject
@Override
public Widget[] getNestedChildren()
{
- RSHashTable componentTable = client.getComponentTable();
- int group = -1;
-
- // XXX can actually use hashtable lookup instead of table
- // iteration here...
- for (Node node : componentTable.getNodes())
- {
- WidgetNode wn = (WidgetNode) node;
-
- if (wn.getHash() == getId())
- {
- group = wn.getId();
- break;
- }
- }
-
- if (group == -1)
+ if (getRSParentId() == getId())
{
+ // This is a dynamic widget, so it can't have nested children
return new Widget[0];
}
- List widgets = new ArrayList();
- for (Widget widget : client.getGroup(group))
+ HashTable componentTable = client.getComponentTable();
+
+ WidgetNode wn = componentTable.get(getId());
+ if (wn == null)
{
- if (widget != null && widget.getParentId() == getId())
+ return new RSWidget[0];
+ }
+
+ int group = wn.getId();
+
+ List widgets = new ArrayList();
+ for (RSWidget widget : client.getGroup(group))
+ {
+ if (widget != null && widget.getRSParentId() == -1)
{
widgets.add(widget);
}
}
- return widgets.toArray(new Widget[widgets.size()]);
+ return widgets.toArray(new RSWidget[widgets.size()]);
}
@Inject
@@ -373,7 +368,9 @@ public abstract class RSWidgetMixin implements RSWidget
{
// if the widget is hidden it will not magically unhide from its parent changing
if (child == null || child.isSelfHidden())
+ {
continue;
+ }
child.broadcastHidden(hidden);
}
@@ -386,7 +383,9 @@ public abstract class RSWidgetMixin implements RSWidget
for (Widget nestedChild : nestedChildren)
{
if (nestedChild == null || nestedChild.isSelfHidden())
+ {
continue;
+ }
((RSWidget) nestedChild).broadcastHidden(hidden);
}
diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java b/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java
index c73c8c16dd..fa7cae893e 100644
--- a/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java
+++ b/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java
@@ -156,6 +156,15 @@ public interface RSClient extends RSGameEngine, Client
@Import("widgets")
RSWidget[][] getWidgets();
+ /**
+ * Gets an array of widgets that correspond to the passed group ID.
+ *
+ * @param groupId the group ID
+ * @return the widget group
+ * @see net.runelite.api.widgets.WidgetID
+ */
+ RSWidget[] getGroup(int groupId);
+
@Import("region")
@Override
RSRegion getRegion();
diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSHashTable.java b/runescape-api/src/main/java/net/runelite/rs/api/RSHashTable.java
index a2403b7026..e1462ce1dd 100644
--- a/runescape-api/src/main/java/net/runelite/rs/api/RSHashTable.java
+++ b/runescape-api/src/main/java/net/runelite/rs/api/RSHashTable.java
@@ -30,6 +30,7 @@ import net.runelite.mapping.Import;
public interface RSHashTable extends HashTable
{
@Import("get")
+ @Override
RSNode get(long value);
@Import("size")
diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSWidget.java b/runescape-api/src/main/java/net/runelite/rs/api/RSWidget.java
index d0db093204..53bfa46cfd 100644
--- a/runescape-api/src/main/java/net/runelite/rs/api/RSWidget.java
+++ b/runescape-api/src/main/java/net/runelite/rs/api/RSWidget.java
@@ -40,6 +40,12 @@ public interface RSWidget extends Widget
@Override
int getId();
+ void setRenderParentId(int parentId);
+
+ void setRenderX(int x);
+
+ void setRenderY(int y);
+
@Import("parentId")
int getRSParentId();