A few people in #free-help were asking about creating "toggle buttons", so here's how to do it. Big thanks to Dane; all code in this thread (minus the actual button creation part) is from his 186 refactor.
Interfaces have various "enabled" and "disabled" properties that let you change how the interface is displayed based on its current state. Examples are things like changing text color when enabled/disabled, or, in our case, changing the sprite based on the state.
See below for a list of these properties (186):
Code:
public String messageDisabled;
public String messageEnabled;
public int colorDisabled;
public int colorEnabled;
public Sprite spriteDisabled;
public Sprite spriteEnabled;
public Model modelDisabled;
public Model modelEnabled;
The method below determines whether or not an interface is enabled. "scriptCompareType" is essentially which operator to use when making the comparison against the expected value ("2" being >=, "3" being <=, "4" being ==, and anything else being !=).
Code:
public final boolean isInterfaceEnabled(final RSInterface rsInterface) {
if (rsInterface.scriptCompareType == null) {
return false;
}
for (int n = 0; n < rsInterface.scriptCompareType.length; n++) {
int a = executeInterface(rsInterface, n);
int b = rsInterface.scriptCompareValue[n];
if (rsInterface.scriptCompareType[n] == 2) {
if (a >= b) {
return false;
}
} else if (rsInterface.scriptCompareType[n] == 3) {
if (a <= b) {
return false;
}
} else if (rsInterface.scriptCompareType[n] == 4) {
if (a == b) {
return false;
}
} else if (a != b) {
return false;
}
}
return true;
}
In the code above, you'll see a call to a method named "executeInterface()". Below, you can find an excerpt of it containing the relevant parts, otherwise, if you want to see the full thing, click the link. What this method does is essentially execute a CS1 script and return a value. There are a few different opcodes that will determine what exactly we want to perform, but, in our case, we only need to care about #5, which fetches and accumulates a value from a varp, and #0 which returns the accumulator.
Code:
public final int executeInterface(RSInterface parent, int script) {
if (parent.script == null || script >= parent.script.length) {
return -2;
}
try {
int[] code = parent.script[script];
int a = 0;
int position = 0;
for (; ; ) {
int opcode = code[position++];
if (opcode == 0) {
return a;
}
...
if (opcode == 5) {
a += variables[code[position++]];
}
...
}
}
Now that we have some of the pre-requisite knowledge out of the way, we can move onto the buttons themselves. When the button type of an interface is a toggle button (4) and the opcode of the first CS1 script is 5, we execute the block of code below. Essentially, it fetches the value of a varp and then sets the varp to be equal to 1 minus the value of itself. This allows us to create a toggle button by flipping between 1 and 0 (if our value is 1, subtracting 1 by 1 will set it to 0, and if it is currently 0, subtracting 1 by 0 will ofc be 1).
Code:
if (type == 739) {
out.putOpcode(101);
out.putShort(c);
RSInterface w = RSInterface.instances[c];
if (w.script != null && w.script[0][0] == 5) {
int j = w.script[0][1];
variables[j] = 1 - variables[j];
updateVarp(j);
sidebarRedraw = true;
}
}
Finally, we can put all we've learned together and build some methods to create a toggle button:
Code:
public static void addToggleButton(final int index, final int parentIndex, final int varpIndex, final String tooltip, final String enabledSprite, final String disabledSprite) {
final Sprite enabled = Sprite.fetchSprite(enabledSprite);
final Sprite disabled = Sprite.fetchSprite(disabledSprite);
final RSInterface rsInterface = instances[index] = new RSInterface();
rsInterface.index = index;
rsInterface.parent = parentIndex;
rsInterface.script = new int[][]{{5, varpIndex, 0}};
rsInterface.scriptCompareValue = new int[]{1};
rsInterface.scriptCompareType = new int[]{1};
rsInterface.width = enabled.myWidth;
rsInterface.height = enabled.myHeight;
rsInterface.buttonType = TOGGLE_BUTTON; // 4
rsInterface.type = TYPE_SPRITE; // 5
rsInterface.spriteEnabled = enabled;
rsInterface.spriteDisabled = disabled;
rsInterface.tooltip = tooltip;
}
public static void addToggleButton(final int index, final int parentIndex, final int varpIndex, final String tooltip, final String spriteGroup) {
// SPRITE_TEMPLATE = "%s,%d"
addToggleButton(index, parentIndex, varpIndex, tooltip, String.format(SPRITE_TEMPLATE, spriteGroup, 0), String.format(SPRITE_TEMPLATE, spriteGroup, 1));
}
public static void addToggleButton(final int index, final int varpIndex, final String tooltip, final String spriteGroup) {
addToggleButton(index, index, varpIndex, tooltip, spriteGroup);
}
useful knowledge, i suggest people interested in interfaces read this and reduce hardcoding habits when not needed.
cs1 can apply to a bunch of different places and can make some (bad) server code obsolete, make sure u read the opcodes =] (e.g skill requirements, coordinates)
this can be used for different things, such as changing the color of text/rectangles, changing text from one string to another, changing spellbook interfaces and a few other things. (for example, changing NO to YES, or things like sprites changing based on things like skill requirements, coordinates, having an item etc)
nice, cs1 is surprisingly poorly documented, i added the 377 cache to an mitb deob a while back and had to add opcode 8, didn't find an explanation anywhere