317 Adding new slots to the equipment tab

 
 
 
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:

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, 032, 064, 0
0, 3232, 3264, 32
0, 6432, 6464, 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()
    }


Spoiler for Image example:

zep4wx.png


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, 040, 080, 0
0, 3240, 3280, 32
0, 6440, 6480, 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()
    }


Spoiler for Image example:

rkv4iy.png


Similarly, modifying the marginY to be 8 produces the following, with a total size of 112x112.

0, 040, 080, 0
0, 4040, 4080, 40
0, 8040, 8080, 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()
    }


Spoiler for Image example:

5s9w1x.png


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, 040, 080, 0
0, 4040, 4080, 40
0, 8056, 6480, 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
    }


Spoiler for Image example:

y7v885.png


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;
    }

x1bw78.png

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));
    }

x786u6.png

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));
    }

92885r.png

That's it. You're done!

Special thanks:

 
Last edited:
Appreciate the detailed and free content coming from this one. <3
 
@Omar @Rebecca

Code:
// 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;
    }

I dont have a "Sprite.fetchSprite" or any method with just 1 param in Sprite.
 
@Omar @Rebecca

Java:
// 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;
    }

I dont have a "Sprite.fetchSprite" or any method with just 1 param in Sprite.

I believe that was just a wrapper for this method:

getImage()

Java:
    public static Image24 getImage(int id, FileArchive media, String name) {
        long uid = (StringUtil.hashCode(name) << 8) + (long) id;
        Image24 image = imageCache.get(uid);

        if (image != null) {
            return image;
        }

        try {
            image = new Image24(media, name, id);
            imageCache.put(uid, image);
        } catch (Exception _ex) {
            return null;
        }
        return image;
    }


    public static Image24 getImage(final String image, final FileArchive media) {
        final int index = image.lastIndexOf(",");
        return getImage(Integer.parseInt(image.substring(index + 1)), media, image.substring(0, index));
    }


    public static Image24 getImage(final String image) {
        return getImage(image, Component.media);
    }

With "Component.media" being the media archive passed in the interface decoder: unpack()

Many clients are modified to just store a reference to that somewhere:

MarxistTimidRoidcel.png
 
Last edited:
im having trouble adding this to my 718 server, i manage to make new slot (i copy the aura slot) but and it works fine but when i activate my aura my wings slot disappear
 

Users who are viewing this thread (total: 1, members: 0, guests: 1)