Files
runelite/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java

574 lines
12 KiB
Java

/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* 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.mixins;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import net.runelite.api.HashTable;
import net.runelite.api.Node;
import net.runelite.api.Point;
import net.runelite.api.WidgetNode;
import net.runelite.api.events.WidgetHiddenChanged;
import net.runelite.api.events.WidgetPositioned;
import net.runelite.api.mixins.FieldHook;
import net.runelite.api.mixins.Inject;
import net.runelite.api.mixins.Mixin;
import net.runelite.api.mixins.Shadow;
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.rs.api.RSClient;
import net.runelite.rs.api.RSHashTable;
import net.runelite.rs.api.RSNode;
import net.runelite.rs.api.RSWidget;
@Mixin(RSWidget.class)
public abstract class RSWidgetMixin implements RSWidget
{
private static final int ITEM_SLOT_SIZE = 32;
@Shadow("clientInstance")
private static RSClient client;
@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()
{
int id = getParentId();
if (id == -1)
{
return null;
}
return client.getWidget(TO_GROUP(id), TO_CHILD(id));
}
@Inject
@Override
public int getParentId()
{
assert client.isClientThread();
int rsParentId = getRSParentId();
if (rsParentId != -1)
{
return rsParentId;
}
final int id = getId();
if (TO_GROUP(id) == client.getWidgetRoot())
{
// this is a root widget
return -1;
}
int parentId = rl$parentId;
if (parentId != -1)
{
// 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
// check the parent in the component table
HashTable<WidgetNode> componentTable = client.getComponentTable();
WidgetNode widgetNode = componentTable.get(parentId);
if (widgetNode == null || widgetNode.getId() != TO_GROUP(id))
{
// invalidate parent
rl$parentId = -1;
}
else
{
return parentId;
}
}
// also the widget may not have been drawn, yet
int groupId = TO_GROUP(getId());
RSHashTable componentTable = client.getComponentTable();
RSNode[] buckets = componentTable.getBuckets();
for (RSNode node : buckets)
{
// It looks like the first node in the bucket is always
// a sentinel
Node cur = node.getNext();
while (cur != node)
{
WidgetNode wn = (WidgetNode) cur;
if (groupId == wn.getId())
{
return (int) wn.getHash();
}
cur = cur.getNext();
}
}
return -1;
}
@Inject
@Override
public String getText()
{
return getRSText().replace('\u00A0', ' ');
}
@Inject
@Override
public String getName()
{
return getRSName().replace('\u00A0', ' ');
}
@Inject
@Override
public void setName(String name)
{
setRSName(name.replace(' ', '\u00A0'));
}
@Inject
@Override
public boolean isHidden()
{
assert client.isClientThread();
if (isSelfHidden())
{
return true;
}
Widget parent = getParent();
if (parent == null)
{
if (TO_GROUP(getId()) != client.getWidgetRoot())
{
// Widget has no parent and is not the root widget (which is always visible),
// so it's not visible.
return true;
}
}
else if (parent.isHidden())
{
// If the parent is hidden, this widget is also hidden.
return true;
}
return false;
}
@Inject
@Override
public Point getCanvasLocation()
{
return new Point(rl$x, rl$y);
}
@Inject
@Override
public Rectangle getBounds()
{
Point canvasLocation = getCanvasLocation();
return new Rectangle(canvasLocation.getX(), canvasLocation.getY(), getWidth(), getHeight());
}
@Inject
@Override
public Collection<WidgetItem> getWidgetItems()
{
int[] itemIds = getItemIds();
if (itemIds == null)
{
return null;
}
List<WidgetItem> items = new ArrayList<WidgetItem>(itemIds.length);
for (int i = 0; i < itemIds.length; ++i)
{
WidgetItem item = getWidgetItem(i);
if (item != null)
{
items.add(item);
}
}
return items;
}
@Inject
@Override
public WidgetItem getWidgetItem(int index)
{
int[] itemIds = getItemIds();
int[] itemQuantities = getItemQuantities();
if (itemIds == null || itemQuantities == null)
{
return null;
}
int columns = getWidth(); // the number of item slot columns is stored here
int xPitch = getXPitch();
int yPitch = getYPitch();
int itemId = itemIds[index];
int itemQuantity = itemQuantities[index];
Point widgetCanvasLocation = getCanvasLocation();
if (itemId <= 0 || itemQuantity <= 0 || columns <= 0)
{
return null;
}
int row = index / columns;
int col = index % columns;
int itemX = widgetCanvasLocation.getX() + ((ITEM_SLOT_SIZE + xPitch) * col);
int itemY = widgetCanvasLocation.getY() + ((ITEM_SLOT_SIZE + yPitch) * row);
Rectangle bounds = new Rectangle(itemX - 1, itemY - 1, ITEM_SLOT_SIZE, ITEM_SLOT_SIZE);
return new WidgetItem(itemId - 1, itemQuantity, index, bounds);
}
@Inject
@Override
public Widget getChild(int index)
{
RSWidget[] widgets = getChildren();
if (widgets == null || widgets[index] == null)
{
return null;
}
return widgets[index];
}
@Inject
@Override
public Widget[] getDynamicChildren()
{
RSWidget[] children = getChildren();
if (children == null)
{
return new Widget[0];
}
List<Widget> widgets = new ArrayList<Widget>();
for (RSWidget widget : children)
{
if (widget != null && widget.getRSParentId() == getId())
{
widgets.add(widget);
}
}
return widgets.toArray(new Widget[widgets.size()]);
}
@Inject
@Override
public Widget[] getStaticChildren()
{
if (getRSParentId() == getId())
{
// This is a dynamic widget, so it can't have static children
return new Widget[0];
}
List<Widget> widgets = new ArrayList<Widget>();
for (RSWidget widget : client.getGroup(TO_GROUP(getId())))
{
if (widget != null && widget.getRSParentId() == getId())
{
widgets.add(widget);
}
}
return widgets.toArray(new RSWidget[widgets.size()]);
}
@Inject
@Override
public Widget[] getNestedChildren()
{
assert client.isClientThread();
if (getRSParentId() == getId())
{
// This is a dynamic widget, so it can't have nested children
return new Widget[0];
}
HashTable<WidgetNode> componentTable = client.getComponentTable();
WidgetNode wn = componentTable.get(getId());
if (wn == null)
{
return new RSWidget[0];
}
int group = wn.getId();
List<RSWidget> widgets = new ArrayList<RSWidget>();
for (RSWidget widget : client.getGroup(group))
{
if (widget != null && widget.getRSParentId() == -1)
{
widgets.add(widget);
}
}
return widgets.toArray(new RSWidget[widgets.size()]);
}
@Inject
@Override
public boolean contains(Point point)
{
Rectangle bounds = getBounds();
return bounds != null && bounds.contains(new java.awt.Point(point.getX(), point.getY()));
}
@Inject
@Override
public void broadcastHidden(boolean hidden)
{
WidgetHiddenChanged event = new WidgetHiddenChanged();
event.setWidget(this);
event.setHidden(hidden);
client.getCallbacks().post(event);
RSWidget[] children = getChildren();
if (children != null)
{
// recursive through children
for (RSWidget child : children)
{
// if the widget is hidden it will not magically unhide from its parent changing
if (child == null || child.isSelfHidden())
{
continue;
}
child.broadcastHidden(hidden);
}
}
// make sure we iterate nested children as well
// cannot be null
Widget[] nestedChildren = getNestedChildren();
for (Widget nestedChild : nestedChildren)
{
if (nestedChild == null || nestedChild.isSelfHidden())
{
continue;
}
((RSWidget) nestedChild).broadcastHidden(hidden);
}
}
@FieldHook("isHidden")
@Inject
public void onHiddenChanged(int idx)
{
int id = getId();
if (id == -1)
{
return;
}
Widget parent = getParent();
// if the parent is hidden then changes in this widget don't have any visual effect
// so ignore them
if (parent != null)
{
if (parent.isHidden())
{
return;
}
}
else if (TO_GROUP(id) != client.getWidgetRoot())
{
return;
}
broadcastHidden(isSelfHidden());
}
@FieldHook("relativeY")
@Inject
public void onPositionChanged(int idx)
{
int id = getId();
if (id == -1)
{
return;
}
int tick = client.getGameCycle();
if (tick == rl$widgetLastPosChanged)
{
return;
}
rl$widgetLastPosChanged = tick;
client.getLogger().trace("Posting widget position changed");
WidgetPositioned widgetPositioned = new WidgetPositioned();
client.getCallbacks().postDeferred(widgetPositioned);
}
@Inject
@Override
public Widget createChild(int index, int type)
{
assert client.isClientThread();
RSWidget w = client.createWidget();
w.setType(type);
w.setParentId(getId());
w.setId(getId());
w.setIsIf3(true);
RSWidget[] siblings = getChildren();
if (index < 0)
{
if (siblings == null)
{
index = 0;
}
else
{
index = siblings.length;
}
}
if (siblings == null)
{
siblings = new RSWidget[index + 1];
setChildren(siblings);
}
else if (siblings.length <= index)
{
RSWidget[] newSiblings = new RSWidget[index + 1];
System.arraycopy(siblings, 0, newSiblings, 0, siblings.length);
siblings = newSiblings;
setChildren(siblings);
}
siblings[index] = w;
w.setIndex(index);
return w;
}
@Inject
@Override
public void revalidate()
{
assert client.isClientThread();
client.revalidateWidget(this);
}
@Inject
@Override
public void revalidateScroll()
{
assert client.isClientThread();
client.revalidateWidget(this);
client.revalidateWidgetScroll(client.getWidgets()[TO_GROUP(this.getId())], this, false);
}
@Inject
@Override
public void deleteAllChildren()
{
if (getChildren() != null)
{
Arrays.fill(getChildren(), null);
}
}
}