This tutorial is part of a series on 317 interfaces. For some of the other entries, see the list below:
In this guide, we’ll cover the creation of standard, 317-compatible hover buttons.
Essentially, the way this works is by creating three separate interfaces ("components"):
- The unhovered interface (typically a sprite that acts a button, for example)
- An invisible container interface that is only to be shown when moused over
- A child interface of the aforementioned container which will be used as the hovered state of the first interface
This does have some limitations, however; with the way the 317 client renders interfaces, nested containers do not have their children clipped to the boundaries of the parent container.
What I mean by that is, for example, let’s imagine we create an interface with a scrolling container. Inside of which, we place hover buttons. You can see a real-world demo of this with a replica of Roat Pkz’ task interface that @mikan and I had done for a customer.
In this interface, we have the main scrolling container and inside of it we have child containers used for the claim buttons. With standard hover buttons, scrolling the container downwards to a position where one of the buttons is partially hidden, and then hovering, we would see the hovered state of that button appear fully and completely rather than have some of it clipped by the scroll boundaries. This is an unfortunate limitation of the engine.
As an example of what I mean, here is a demonstration of Jagex-style hover buttons being used inside of a scroll container:
Having said that, for interfaces where you do not need hovers inside of scrolling containers, this method is ideal as it can be cache-packed straight to the standard if1 format found in the client without any modifications to the engine or interface decoders. This also means that, should you use an interface editor like Displee’s tool, you’ll be able to view the interface properly.
The reason why this is not possible with custom hover buttons is because the essential field we need to make all of this work, the "hidden" property, is only ever decoded if the interface type is a container (type 0). For reference, here is the relevant line in my refactored interface class (special thanks to @Dane for his work on the RS2 beta client): Interface.java
In the coming days, I'll make a separate tutorial showing you how to modify the engine to support hovers without the use of a container, which will alleviate the issues mentioned above (do note that you will need to modify your decoders if you intend on cache packing those!!).
Anyway, preface and explanations aside, let’s get into the actual creation of these buttons. In your interface class, which may be labelled as Interface, RSInterface, Widget or perhaps even Class9, you’ll need to add a set of functions like so (NOTE: As Jagex uses XZY coordinates for 3D space, this code had been refactored similarly for consistency rather than accuracy - "Z" in these examples will typically be, and should be, "Y" in your client):
Code:
private static int getFreeIndex() {
for (int i = 0; i < instances.length; i++) {
if (instances[i] == null) {
return i;
}
}
return -1;
}
public static Interface createSprite(final int parentIndex, final int index, final String disabled, final String enabled) {
final Sprite spriteDisabled = Sprite.fetchSprite(disabled);
final Sprite spriteEnabled = Sprite.fetchSprite(enabled);
final Interface sprite = instances[index] = new Interface();
sprite.index = index;
sprite.parent = parentIndex;
sprite.type = 5;
sprite.width = spriteDisabled.myWidth;
sprite.height = spriteDisabled.myHeight;
sprite.spriteDisabled = spriteDisabled;
sprite.spriteEnabled = spriteEnabled;
return sprite;
}
public static Interface createSprite(final int parentIndex, final String disabled, final String enabled) {
final int index = getFreeIndex();
if (index < 0) {
throw new IllegalStateException("Interface cache full; expand the size of the array before attempting to create a component!");
}
return createSprite(parentIndex, index, disabled, enabled);
}
public static void createHoverButton(final int x, final int z, final Interface parent, final Interface unhovered, final Interface hovered) {
final int index = getFreeIndex();
if (index < 0) {
throw new IllegalStateException("Interface cache full; expand the size of the array before attempting to create a component!");
}
final Interface container = instances[index] = new Interface();
container.id = index;
container.parent = index;
container.hoverParentIndex = -1;
container.width = hovered.width;
container.height = hovered.height;
container.hidden = true;
container.childX = new int[1];
container.childZ = new int[1];
container.children = new int[]{hovered.id};
unhovered.hoverParentIndex = index;
final int[] childX = new int[parent.childX.length + 2];
final int[] childZ = new int[parent.childZ.length + 2];
final int[] children = new int[parent.children.length + 2];
System.arraycopy(parent.childX, 0, childX, 0, parent.childX.length);
System.arraycopy(parent.childZ, 0, childZ, 0, parent.childZ.length);
System.arraycopy(parent.children, 0, children, 0, parent.children.length);
childX[childX.length - 2] = x;
childZ[childZ.length - 2] = z;
childX[childX.length - 1] = x;
childZ[childZ.length - 1] = z;
children[children.length - 2] = unhovered.id;
children[children.length - 1] = index;
parent.childX = childX;
parent.childZ = childZ;
parent.children = children;
}
public static void createHoverButton(final int x, final int z, final int buttonType, final Interface parent, final String unhoveredSprite, final String hoveredSprite, final String option) {
final Interface unhoveredButton = createSprite(parent.id, unhoveredSprite, unhoveredSprite);
unhoveredButton.buttonType = buttonType;
unhoveredButton.option = option;
final Interface hoveredButton = createSprite(parent.id, hoveredSprite, hoveredSprite);
hoveredButton.buttonType = buttonType;
hoveredButton.option = option;
createHoverButton(x, z, parent, unhoveredButton, hoveredButton);
}
public static void createHoverButton(final int x, final int z, final int buttonType, final Interface parent, final String spriteGroup, final String option) {
createHoverButton(x, z, buttonType, parent, spriteGroup + ",0", spriteGroup + ",1", option);
}
You may already have some of these functions, such as those that create sprites, etc. Feel free to use whatever your client comes with, if you'd like. The above code assumes you have absolutely zero custom functions in your interface class. The key function here is:
Code:
1 public static void createHoverButton(final int x, final int z, final Interface parent, final Interface unhovered, final Interface hovered) {
2 final int index = getFreeIndex();
3 if (index < 0) {
4 throw new IllegalStateException("Interface cache full; expand the size of the array before attempting to create a component!");
5 }
6 final Interface container = instances[index] = new Interface();
7 container.id = index;
8 container.parent = index;
9 container.hoverParentIndex = -1;
10 container.width = hovered.width;
11 container.height = hovered.height;
12 container.hidden = true;
13 container.childX = new int[1];
14 container.childZ = new int[1];
15 container.children = new int[]{hovered.id};
16 unhovered.hoverParentIndex = index;
17
18 final int[] childX = new int[parent.childX.length + 2];
19 final int[] childZ = new int[parent.childZ.length + 2];
20 final int[] children = new int[parent.children.length + 2];
21 System.arraycopy(parent.childX, 0, childX, 0, parent.childX.length);
22 System.arraycopy(parent.childZ, 0, childZ, 0, parent.childZ.length);
23 System.arraycopy(parent.children, 0, children, 0, parent.children.length);
24 childX[childX.length - 2] = x;
25 childZ[childZ.length - 2] = z;
26 childX[childX.length - 1] = x;
27 childZ[childZ.length - 1] = z;
28 children[children.length - 2] = unhovered.id;
29 children[children.length - 1] = index;
30 parent.childX = childX;
31 parent.childZ = childZ;
32 parent.children = children;
33 }
Inside of that function, on line 6, we create a new container interface and add it to the interface array. Afterwards, on line 12, we set the container to be invisible. Then, on line 15, we create an integer array to store the children of the container and add the hovered sprite's index. Next, on line 16, we set the "hoverParentIndex" property, which determines what interface to show upon hovering over an interface, on the unhovered sprite to be the index of the invisible container. Finally, from 18 to 32, we expand the child interface arrays on the parent and add the container and unhovered sprite.
For those needing the spoon, copy paste everything from the original code block and call this function:
Code:
createHoverButton(final int x, final int z, final int buttonType, final Interface parent, final String spriteGroup, final String option)
籠 Pass it the X position and Z position where you'd like it to display on the screen along with a button type (typically 1), a reference the interface which you'd like to parent the buttons to, the sprite group (the function uses index 0 and 1 of the group, so you should only have two sprites - the unhovered and hovered sprites inside of the group), and the option (tooltip text that'll display when hovering).
Like, subscribe, 5 star me on yelp, sub to my onlyfans.
Special thanks: