- Dec 8, 2007
- 139
- 250
- 0
This tutorial is part of a series on 317 interfaces. For some of the other entries, see the list below:
We'll be covering the creation of new equipment slots as well as a rudimentary understanding of inventory sprites, spacing, etc.
Let's take a look at type 2 in the interface decoder, the inventory type. The things of note for us are the following lines:
The "marginX" and "marginY" properties determine how many pixels of space should exist between inventory items. The "OffsetX" and "OffsetY" arrays allow you to offset a specific inventory slot by a given number of pixels.
In addition to the inventory specific properties, "height" and "width", found at the top of the decoder, are also very important to us as they behave a little bit differently for inventories.
For inventory interfaces, rather than determining the size of the interface in pixels, as would the fields would normally do, these actually determine the number of inventory rows and columns (height and width, respectively).
So, given an inventory with a width of 3, height of 3, offsets and margins of 0, our inventory would look a little something like this when full (X, Y), with the total size being 96x96 (64 being the end position + 32, which is the size of an inventory item).
Should we add a marginX of 8, we'd end up with the following, with a total size of 112x96 (96 + 16 from the two items with a margin of 8).
Similarly, modifying the marginY to be 8 produces the following, with a total size of 112x112.
Next, the last bit we need to know how to play around with in order to add new slots is the offsets since these allow you to more precisely position things in the equipment tab. Let's demonstrate by moving the 8th Chaos rune a little bit. We'll give it an offset of 16 on the X and -16 on the Y. This'll push it over to the right and pull it upwards.
Using the information we've learned, we can go ahead and start repositioning the slots for new items. Let's go ahead and change slot 14. The equipment inventory has a size of 3x5, so we'll need to take the row and column into account when doing the offsets.

Next, let's go ahead and add a tab sprite behind it. However, we need to be careful doing this as we can't simply add a new child to the equipment tab (1644) as that'll cause the sprite to draw above the item due to the fact that the inventory (1688) will no longer be the final child interface in the array.
Let's start by adding a method to determine the next free ID in the interface array:
Let's add a couple of methods to create a new sprite interface:
Next, we need a method to fetch the index of an interface in the child array:
Finally, we need a method to parent an interface to a given parent interface:
Now, we can use the methods we created earlier to add the tab sprite and splice it into the array above where the inventory sits:

Finally, the last thing we need to do is add a placeholder sprite for when the slot is empty so that it isn't just blank. Let's quickly look at the decoder again:
When iterating over the slots in an inventory, in addition to decoding the offsets, it also decodes and sets sprites, if available, to those slots. These inventory sprites only show if the slot is empty. Otherwise, they are disabled. We can go ahead and hardcode these, like so:

That's it. You're done!
Special thanks:
- Making toggle buttons (checkboxes, etc)
- Making hover buttons (standard 317/Jagex way)
- Making radio buttons
We'll be covering the creation of new equipment slots as well as a rudimentary understanding of inventory sprites, spacing, etc.
Let's take a look at type 2 in the interface decoder, the inventory type. The things of note for us are the following lines:
Java:
w.inventoryMarginX = b.getUByte();
w.inventoryMarginY = b.getUByte();
w.inventoryOffsetX = new int[20];
w.inventoryOffsetY = new int[20];
The "marginX" and "marginY" properties determine how many pixels of space should exist between inventory items. The "OffsetX" and "OffsetY" arrays allow you to offset a specific inventory slot by a given number of pixels.
In addition to the inventory specific properties, "height" and "width", found at the top of the decoder, are also very important to us as they behave a little bit differently for inventories.
Java:
w.width = b.getUShort();
w.height = b.getUShort();
For inventory interfaces, rather than determining the size of the interface in pixels, as would the fields would normally do, these actually determine the number of inventory rows and columns (height and width, respectively).
So, given an inventory with a width of 3, height of 3, offsets and margins of 0, our inventory would look a little something like this when full (X, Y), with the total size being 96x96 (64 being the end position + 32, which is the size of an inventory item).
0, 0 | 32, 0 | 64, 0 |
0, 32 | 32, 32 | 64, 32 |
0, 64 | 32, 64 | 64, 64 |
Spoiler for Code:
Java:
override fun buildView() {
val container = interfaceBuilder().id(parentId).asTabInterface()
val rect = interfaceBuilder(container).color(GREY).size(96, 96).position(200, 125).filled().asRectangle()
interfaceBuilder(rect).size(3, 3).items(Array(9) {
Item(CHAOS_RUNE, 1)
}).asInventory()
}
Should we add a marginX of 8, we'd end up with the following, with a total size of 112x96 (96 + 16 from the two items with a margin of 8).
0, 0 | 40, 0 | 80, 0 |
0, 32 | 40, 32 | 80, 32 |
0, 64 | 40, 64 | 80, 64 |
Spoiler for Code:
Java:
override fun buildView() {
val container = interfaceBuilder().id(parentId).asTabInterface()
val rect = interfaceBuilder(container).color(GREY).size(112, 96).position(200, 125).filled().asRectangle()
interfaceBuilder(rect).size(3, 3).items(Array(9) {
Item(CHAOS_RUNE, 1)
}).inventorySpritePadding(8, 0).asInventory()
}
Similarly, modifying the marginY to be 8 produces the following, with a total size of 112x112.
0, 0 | 40, 0 | 80, 0 |
0, 40 | 40, 40 | 80, 40 |
0, 80 | 40, 80 | 80, 80 |
Spoiler for Code:
Java:
override fun buildView() {
val container = interfaceBuilder().id(parentId).asTabInterface()
val rect = interfaceBuilder(container).color(GREY).size(112, 112).position(200, 125).filled().asRectangle()
interfaceBuilder(rect).size(3, 3).items(Array(9) {
Item(CHAOS_RUNE, 1)
}).inventorySpritePadding(8, 8).asInventory()
}
Next, the last bit we need to know how to play around with in order to add new slots is the offsets since these allow you to more precisely position things in the equipment tab. Let's demonstrate by moving the 8th Chaos rune a little bit. We'll give it an offset of 16 on the X and -16 on the Y. This'll push it over to the right and pull it upwards.
0, 0 | 40, 0 | 80, 0 |
0, 40 | 40, 40 | 80, 40 |
0, 80 | 56, 64 | 80, 80 |
Spoiler for Code:
Java:
override fun buildView() {
val container = interfaceBuilder().id(parentId).asTabInterface()
val rect = interfaceBuilder(container).color(GREY).size(112, 112).position(200, 125).filled().asRectangle()
val inventory = interfaceBuilder(rect).size(3, 3).items(Array(9) {
Item(CHAOS_RUNE, 1)
}).inventorySpritePadding(8, 8).asInventory()
inventory.inventoryOffsetX[7] = 8
inventory.inventoryOffsetY[7] = -16
}
Using the information we've learned, we can go ahead and start repositioning the slots for new items. Let's go ahead and change slot 14. The equipment inventory has a size of 3x5, so we'll need to take the row and column into account when doing the offsets.
Java:
public static void buildEquipmentTab() {
final RSInterface inventory = instances[1688];
inventory.inventoryOffsetX[14] = -97;
inventory.inventoryOffsetY[14] = -198;
}

Next, let's go ahead and add a tab sprite behind it. However, we need to be careful doing this as we can't simply add a new child to the equipment tab (1644) as that'll cause the sprite to draw above the item due to the fact that the inventory (1688) will no longer be the final child interface in the array.
Let's start by adding a method to determine the next free ID in the interface array:
Java:
private static int getFreeIndex() {
for (int i = 0; i < instances.length; i++) {
if (instances[i] == null) {
return i;
}
}
return -1;
}
Let's add a couple of methods to create a new sprite interface:
Java:
// Add these to your sprite class or replace their usages in the createSprite code
public static Sprite fetchSprite(final String name, final FileArchive graphics) {
final int index = name.lastIndexOf(",");
return RSInterface.getSprite(Integer.parseInt(name.substring(index + 1)), graphics, name.substring(0, index));
}
public static Sprite fetchSprite(final String name) {
return fetchSprite(name, RSInterface.graphics);
}
// Add these to your interface class
public static RSInterface createSprite(final int parentIndex, final int index, final String disabled, final String enabled) {
final Sprite disabledSprite = Sprite.fetchSprite(disabled);
final Sprite enabledSprite = Sprite.fetchSprite(enabled);
final RSInterface sprite = instances[index] = new RSInterface();
sprite.id = index;
sprite.parent = parentIndex;
sprite.type = 5;
sprite.width = disabledSprite.width;
sprite.height = disabledSprite.height;
sprite.disabledSprite = disabledSprite;
sprite.enabledSprite = enabledSprite;
return sprite;
}
public static RSInterface 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);
}
Next, we need a method to fetch the index of an interface in the child array:
Java:
public static int getIndexOfChild(final int parent, final int child) {
final RSInterface rsi = instances[parent];
for (int i = 0; i < rsi.children.length; i++) {
if (rsi.children[i] == child) {
return i;
}
}
return -1;
}
Finally, we need a method to parent an interface to a given parent interface:
Java:
public static void addChild(final int parentIndex, final int childIndex, final int x, final int y, final int index) {
final RSInterface parent = instances[parentIndex];
final int[] childX = new int[parent.childX.length + 1];
final int[] childY = new int[parent.childY.length + 1];
final int[] children = new int[parent.children.length + 1];
System.arraycopy(parent.childX, 0, childX, 0, index);
System.arraycopy(parent.childX, index, childX, index + 1, parent.childX.length - index);
System.arraycopy(parent.childY, 0, childY, 0, parent.childY.length);
System.arraycopy(parent.childY, index, childY, index + 1, parent.childY.length - index);
System.arraycopy(parent.children, 0, children, 0, index);
System.arraycopy(parent.children, index, children, index + 1, parent.children.length - index);
childX[index] = x;
childY[index] = y;
children[index] = childIndex;
parent.childX = childX;
parent.childY = childY;
parent.children = children;
}
Now, we can use the methods we created earlier to add the tab sprite and splice it into the array above where the inventory sits:
Java:
public static void buildEquipmentTab() {
final RSInterface parent = instances[1644];
final RSInterface newTab = createSprite(parent.id, "miscgraphics,0", "miscgraphics,0");
final RSInterface inventory = instances[1688];
inventory.inventoryOffsetX[14] = -97;
inventory.inventoryOffsetY[14] = -198;
addChild(parent.id, newTab.id, 37, 4, getIndexOfChild(parent.id, inventory.id));
}

Finally, the last thing we need to do is add a placeholder sprite for when the slot is empty so that it isn't just blank. Let's quickly look at the decoder again:
Java:
String s = b.getString();
if (media != null && s.length() > 0) {
int j = s.lastIndexOf(",");
w.inventorySprite[n] = getSprite(s.substring(0, j), media, (Integer.parseInt(s.substring(j + 1))));
}
When iterating over the slots in an inventory, in addition to decoding the offsets, it also decodes and sets sprites, if available, to those slots. These inventory sprites only show if the slot is empty. Otherwise, they are disabled. We can go ahead and hardcode these, like so:
Java:
public static void buildEquipmentTab() {
final RSInterface parent = instances[1644];
final RSInterface newTab = createSprite(parent.id, "miscgraphics,0", "miscgraphics,0");
final RSInterface inventory = instances[1688];
inventory.inventoryOffsetX[14] = -97;
inventory.inventoryOffsetY[14] = -198;
inventory.inventorySprite[14] = fetchSprite("wornicons,12");
addChild(parent.id, newTab.id, 37, 4, getIndexOfChild(parent.id, inventory.id));
}

That's it. You're done!
Special thanks:
Last edited: