I am sure most are familiar with the minigames - http://runescape.wikia.com/wiki/Warriors'_Guild. I was bored and I decided to see if it was at all possible to do something about the minigames.
- Animation room
- Catapult room
- Dummy room
- Keg room
- Shotput room
I have only started working on the first minigame, but I plan to work on others if I have incentives and time. This will be a development journal showing how it was done and some of the considerations I had when making. I hope this will open some of your eyes as to how a scripter makes his decisions, and hopefully learn something from it.
5/21/2012 The catapult minigame - Is it at all possible?
It was an idea I had sometime ago, and I decided to give it a go at one of the more challenging minigames among the 5, the catapult one.
You should know how it works. You get a shield from that guy, and you stand on the stand to be shot by the catapult and change your shield to different styles in order to block and earn points.
1. Setting up our player
The difficulty in doing the minigame mainly has to do with the fact that we have very little time to determine what the object is and switch to the correct style. So in order to make our lives easier, the first thing we do is to position ourselves at a better angle.

1. Getting object colors
Now it's up to me to decide how this is going to work. Apparently we need to get the colors of the projectiles so that we can detect them. And this is where the 2nd problem comes in - colors false positive with the background, which is not good news for us.
But before we look at our problem, we first define a simple data structure that will house our objects.
Simba Code:
type
projectile = record
color, tol, minc: integer;
hmod, smod: extended;
name: string;
end;
var projectiles: array of projectile;
Now with our objects, after a bit of ACA-ing, I happen to only get some of the values...
Simba Code:
with projectiles[0] do
begin
color:= 1545178;
tol:= 7;
minc:= 30;
hmod:= 0.11;
smod:= 0.92;
name:= 'magic';
end;
with projectiles[1] do
begin
color:= 6841443;
tol:= 7;
minc:= 30;
hmod:= 0.48;
smod:= 0.07;
name:= 'stab';
end;
with projectiles[2] do
begin
color:= 8748670;
tol:= 8;
minc:= 25;
hmod:= 0.00;
smod:= 0.11;
name:= 'slash';
end;
This is not good news at first sight, because I have only managed to get 3 of the required objects out of 4 in ACA. But if you think about it more deeply, if a detection does not return the either 3 as the result, it's very likely the object is the last one that we didn't have. It's not perfect, but at least it will work and we are gonna use that.
3. Designing our script logic
The minigame itself is simple enough. You see Magic projectile, you click magic shield, and you do that for other projectiles. So our next step would be scripting the part where you click the correct style.

Apparently we can do the easy way where we simply get a point in each "box" and call Mouse(x, y, 15, 15, mouse_left) to deal with it. But that wasn't very satisfying and I wanted to use a Mousebox. To use a mousebox, however, I will have to do some trial and error to get the box areas with paintsmart.
Simba Code:
procedure Draw;
var i: integer;
begin
for i:=0 to 3 do
SMART_DRAWBox(IntToBox(564, 232+56*3, 564 + 156, 232 + 49 +56*3));
// for i:=0 to 3 do
// SMART_DRAWBox(IntToBox(575, 235 + 57*i, 575 + 135, 235 + 40 +57*i));
end;
So I went do do some trial and error, and in a few minutes I get these values. They draw our boxes very well, and now we can proceed to code our functions.
In order to select a style, we need to decide which style is active at the moment, and whether we need to actually click another box to switch style. Effectively this means we need two functions - one that tells us what we're choosing now, another one that tells us to click a style.
Simba Code:
function ActiveStyle(style: string): boolean;
var i: integer;
begin
case style of
'stab': i:=0;
'blunt': i:=1;
'slash': i:=2;
'magic': i:=3;
end;
SetColorToleranceSpeed(1);
result:= CountColorTolerance(3908062, 564, 232+56*i, 564 + 156, 232 + 49 +56*i, 27) > 100;
end;
This decides what is active. Active means the lighter yellowish-orange borders in the box. To play it safe, CTS1 with 25 tolerance has never really failed me in circumstances with menus, which colors are mostly static
Now, onto selecting the style, it keeps getting easier, isn't it?
Simba Code:
function SelectStyle(style: string): boolean;
var i: integer;
v: TVariantArray;
begin
result:= true;
if ActiveStyle(style) then Exit;
case style of
'stab': i:=0;
'blunt': i:=1;
'slash': i:=2;
'magic': i:=3;
end;
MouseBox(575, 235 + 57*i, 575 + 135, 235 + 40 +57*i, mouse_left);
v:= [style];
if not WaitFuncEx('ActiveStyle', v, 100, 2000) then
result:=false;
end;
We use a simple WaitFuncEx here to make sure we the clicked style activates before returning the true/false values. In reality, however, we might not really need to check the success/failure, but the function is written to return a boolean just for the sake of easy debugging later.
4. Deciding which projectile to defend against
Here's the most important part of the script. Once we get onto the stand and gets barraged, we need to react to the projectiles and select the correct style. However, we know TPA finding is not perfect, and it might find the wrong object, so what we need to look for here is a more robust approach that gives high success rate. Also we must take care of the potential issue of switching between the styles too often.
Here is an example. Assume the object finder returns the following series of decisions:
Progress Report:
Stab, magic, stab, stab, stab, blunt, slash, magic
So what could this object be? Common sense tells us it could more likely be stab than others, because for this series of decisions "stab" style has the most number of occurrence. We are going to use this in our decision making function.
Simba Code:
function FindMode(a: TIntegerArray): integer;
var i, m: integer;
b: TIntegerArray;
begin
setlength(b, 4);
for i:=0 to high(a) do
Inc(b[a[i]]);
m:= max(b[0], max(b[1], max(b[2], b[3])));
for i:=0 to high(b) do
if b[i]= m then
result:= i;
writeln('mode: ' + IntToStr(m) + ' style: ' + IntToStr(result));
end;
The above concept is realized into the above block of code, returning the mode of a sample. In our application, we will call our object finders a number of times, and use the found mode as the guess of what the object might be. This is far from being perfect, but robust enough to give high success rates, given our object finders are accurate.
Simba Code:
function DecideStyle: string;
var i: integer;
a: TIntegerArray;
begin
setLength(a, 7);
for i:=1 to length(a) do
FindProjectile(a[i-1]);
case FindMode(a) of
0: result:= 'stab';
1: result:= 'blunt';
2: result:= 'slash';
3: result:= 'magic';
end;
writeln('decide style: ' + result);
end;
Here is the meat we're looking for. We apply our knowledge of statistics and use the mode to guess the current projectile. We have also decided we do a sampling of 7 times and use the data to make our decision. Of course, this number can be changed, but remember TPA finding takes time and we need to balance between making a decision too quickly and missing the time to make a decision and get hit by the catapult.
5. Filling in the blank - setting up our object finder
All our pretty talk does not work if our object finder returns trash. This is also the most difficult part of setting up the script correctly - determining the correct style given the TPA finds.
I am not going to give the exact code here. It worked for me one day, it messed up on the other, so it's pretty much a trial and error thing, but with the code below you will start to see the logic and how the pieces fit together.
Simba Code:
function FindProjectile(var v: integer): boolean;
var i, x, y: integer;
s: TIntegerArray;
tpa: TPointArray;
pass: boolean;
begin
SetLength(s, 3);
SetColorToleranceSpeed(2);
v:=-1;
for i:=0 to high(projectiles) do
begin
SetColorSpeed2Modifiers(projectiles[i].hmod, projectiles[i].smod);
FindColorsTolerance(tpa, projectiles[i].color, MSX1, MSY1, MSX2, MSY2, projectiles[i].tol);
s[i]:= length(tpa);
end;
writeln(format('magic: %d stab %d slash %d', [s[0], s[1], s[2]]));
//Process the colors
//Code your own logic here
if (s[0] >= projectiles[0].minc) then
v:=3 //magic
else if (s[1] >= 25) and (s[2] <= 25) then
v:= 0 //stab
else if {(s[1] <= 50) and} (s[2] >= 25) then
v:= 2 //slash
else if (v=-1) then //blunt
v:= 1;
case v of
0: writeln('projectile guess: stab');
1: writeln('projectile guess: blunt');
2: writeln('projectile guess: slash');
3: writeln('projectile guess: magic');
end;
end;
Basically what we do here is to iterate through our objects and get the color counts. Our decision will be based on how many colors of each style we find, and we decide what values will fit to which style. Because colors change all the time, something that worked yesterday might not work today, and I'm leaving it here for you to do the trial and error and relate the found color counts to a particular style.
To facilitate the process I also have a small snippet to do the sampling without select a style. This might come in handy for observing.
Simba Code:
procedure Test;
var s: integer;
begin
while true do
begin
FindProjectile(s);
wait(100+random(50));
end;
end;
6. Putting it all together - main loop
This is quite self-explanatory. I never really expect it to be unattended, so I don't go over the crap of setting up the player angles with the script. Also FindNormalRandoms seems to misdetect the area as Cap'n Arnav random, so I am forced to drop it here. All in all, it will be a babysit script, you will eventually die depending how well your colors fit and how well the object finder makes the correct decisions, but for an attempt to simply cut you some slack in playing the minigame, it's a good try nevertheless.
Simba Code:
procedure MainLoop;
var style: string;
begin
reincarnate:= true;
while LoggedIn do
begin
// FindNormalRandoms;
FindDead;
SelectStyle(DecideStyle);
wait(100+random(50));
end;
end;
End result

The script is made and tested in action. Needless to say I died a few times, but with some tweaking and babysitting for an hour or 2, I have already accumulated enough tokens for hours of defender hunting if used in the Multi-token mode.
Script is attached for reference. ACA colors might need redo, and you're expected to make FindProjectile work for your own circumstances. It's one of the more difficult challenges for which I don't think it's worth the efforts to make it fully automatic, so I will just leave it here for those who're willing to DIY to get their tokens