Part 1: Introduction
What We Will Learn
In this tutorial, you will learn a method of finding objects on the main screen using your minimap and RSWalker. This object finding technique uses both minimap coordinates
and colour finding to allow very accurate object finding. The initial work was done by @
slacky and he has documented his information
here.
If you find that you are lost, stuck, or confused about anything at any point in this tutorial, please read through the section again to make sure you have not skipped over anything important. Now, let's get started...
Part 2: Getting The Coordinates
How It Works
Before we get going, we need to understand the general process of what is happening. Something to note is that the entirety of the Runescape world is made up into tiles (Yes, the tiles you see while in-game). For every object in the game, it can be pinpointed down to one or sometimes more tiles. For example, take a generic bank booth. The one showed below is located on a single tile and uses the entirety of the tile. Our goal is to locate
only this bank booth using this object finding technique and to filter out the others.
To do this, we need to find a method of getting the coordinates of the booth.
The Script
The coordinates of the bank booth can be gathered using the following collection of code:
Simba Code:
program new;
{$define SMART}
{$I SRL/OSR.simba}
{$I RSWalker/Walker.simba}
const
WINAPI_CC = {$IFDEF CPU386}' stdcall'{$ELSE}' win64'{$ENDIF};
ffi_winapi = {$IFDEF CPU386}ffi_stdcall{$ELSE}ffi_win64{$ENDIF};
// START FILLING IN HERE
LOGIN_NAME = ''; // Account Login Email/Username
LOGIN_PASS = ''; // Account Password
RS_WORLD = -1; // Preferred world, -1 = random world
IS_MEMBER = False; // Are you a member? True or False
MAP = 'world[3_6].png'; // Map that you are using
// END HERE
type
TMSObject = record
WorldLoc: TPointArray;
Color: TCTS2Color;
MinCount: Int32;
SplitDist: Int32;
end;
_EnumWindowsProc = function(wnd:DWORD; Param:Pointer): LongBool;
TEnumWindowsProc = native(_EnumWindowsProc, ffi_winapi);
var
RSW: TRSWalker;
startupHandle: PtrInt;
client2: TClient;
function GetForegroundWindow(): PtrUInt; external 'GetForegroundWindow@user32.dll' + WINAPI_CC;
function GetWindowThreadProcessId(wnd: PtrUInt; out dwProcessId: DWORD): DWORD; external 'GetWindowThreadProcessId@user32.dll' + WINAPI_CC;
function EnumChildWindows(hWndParent: DWORD; func: TEnumWindowsProc; Param: Pointer): LongBool; external 'EnumChildWindows@user32.dll' + WINAPI_CC;
procedure declarePlayers();
begin
with Players.New()^ do
begin
LoginName := LOGIN_NAME;
Password := LOGIN_PASS;
IsActive := True;
IsMember := IS_MEMBER;
World := RS_WORLD;
end;
Players.SetCurrent(0);
end;
function WorldToMSTile(Me, ObjLoc: TPoint; Height:Double=0; Offx,Offy:Double=0): TRectangle;
var
Angle: Double;
begin
ObjLoc := Point(MM2MS.MMCX, MM2MS.MMCY) + (ObjLoc - Me);
Angle := Minimap.GetCompassAngle(False);
ObjLoc := ObjLoc.Rotate(Angle, Point(MM2MS.MMCX, MM2MS.MMCY));
Result := Minimap.VecToMSRect(Vec3(ObjLoc.x - offx, ObjLoc.y - offy, Height), Angle);
end;
function HasFocus(PID: PtrUInt): Boolean;
var tmp: DWORD;
begin
GetWindowThreadProcessId(GetForegroundWindow(), tmp);
Result := tmp = PID;
end;
function GetRSAppletWnd(PID: DWORD): DWORD;
function GetLastChild(Handle: DWORD; Param: Pointer): LongBool; static;
begin
DWORD(Param^) := handle;
Result := True;
end;
var
p: TSysProc;
client: DWORD;
begin
for p in GetProcesses() do
if p.Pid = PID then
Break;
EnumChildWindows(p.Handle, @GetLastChild, @Result);
end;
function SlowMSToMM(MS: TPoint): TPoint;
var
x,y: Int32;
best,test: TPoint;
begin
for x:=MM2MS.MMCX-52 to MM2MS.MMCX+52 do
for y:=MM2MS.MMCY-52 to MM2MS.MMCY+52 do
begin
test := Minimap.PointToMs([x,y],0);
if Distance(test, MS) < Distance(best, MS) then
begin
best := test;
Result := Point(x,y);
end;
end;
end;
procedure WaitReleaseKey();
begin
while client2.GetIOManager.IsMouseButtonDown(0) do Wait(10);
end;
function GetClick(out p: TPoint): Boolean;
begin
if not HasFocus(smart.PID) then Exit();
if client2.GetIOManager.IsMouseButtonDown(0) then
begin
client2.GetIOManager.GetMousePos(p.x,p.y);
Result := p.InBox(GetClientBounds);
WaitReleaseKey();
end;
end;
function SetupLocations(): TPointArray;
var
p, me, worldPt: TPoint;
values: TPointArray;
i: Integer;
rect: TRectangle;
begin
while True do
begin
me := RSW.GetMyPos();
if GetClick(p) then
begin
worldPt := (SlowMSToMM(p) - Point(MM2MS.MMCX, MM2MS.MMCY)) + me;
values.append(worldPt);
writeln(worldPt);
end;
if (length(values) > -1) then
begin
smart.Image.Clear();
for i := 0 to high(values) do
begin
rect := WorldToMSTile(me, values[i]);
if Mainscreen.GetBounds.Contains(rect.Bounds) then
begin
smart.Image.DrawTPA(rect.ToTPA.Connect, $FFFF);
end;
Wait(250);
end;
end;
end;
smart.Image.Clear();
end;
begin
startupHandle := GetNativeWindow;
smart.EnableDrawing := True;
srl.Setup([]);
RSW.Init(MAP, -1);
declarePlayers();
client2.Init(PluginPath);
if GetRSAppletWnd(SMART.PID) = 0 then
client2.GetIOManager.SetTarget2(startupHandle)
else
client2.GetIOManager.SetTarget2(GetRSAppletWnd(SMART.PID));
if (not SRL.isLoggedIn) then
begin
Players.LoginCurrent();
Mainscreen.SetAngle(True);
end;
writeln(startupHandle);
writeln('WARNING: Moving the screen around and then selecting a point can be inaccurate. Be careful when using.');
SetupLocations();
RSW.Free();
client2.Free();
end.
Since this code is fairly complex and a lot is going on, let me do my best to explain what is happening in a very basic manner. When this program is initially run, it will load up a S.M.A.R.T. window. Once Runescape has finished loading, it will attempt to log the player in using the specified details in the highlighted section. Once the player is logged in, you can click anywhere on the screen and a yellow bounding box will appear. The unique ability of this snippet of code is that you're able to click on the screen and select the tile you want while S.M.A.R.T. is still enabled (when you see

on the bottom bar of S.M.A.R.T.) and your input is not recognized by the Runescape client.
Simple, right?!
The Map
Let's set this up so that we can get the location of our bank booth. To do this properly, we will need to fill our the
constants section of the script.
Simba Code:
// START FILLING IN HERE
LOGIN_NAME = ''; // Account Login Email/Username
LOGIN_PASS = ''; // Account Password
RS_WORLD = -1; // Preferred world, -1 = random world
IS_MEMBER = False; // Are you a member? True or False
MAP = 'world[3_6].png'; // Map that you are using
// END HERE
Obviously, you will want to enter the login information, but the most important part of this section is the
MAP. You want to make sure you load the correct part of the map so that the points that we are gathering will be accurate to our location. There are a large variety of maps that you can choose from that come bundled with RSWalker. To find them, navigate to
C:\Simba\Includes\RSWalker\maps.
It is useful to note that depending on where you are in the world and what you are specifically attempting to do, it is recommended that you use the smallest map possible. This is because attempting to locate a specific point on a large map takes a larger amount of time as opposed to a smaller map. Normally, the best case is to use the already sliced up world chunks located in the
world folder.
For our case, we are wanting to use the Varrock East bank. This happens to be located on the [3_6] map in the
world folder and looks like this:
Once you have your map selected and your login information filled out, run the program and let your account log in. Once you are logged in, you can now select the object that you wish to locate. In this case, we want to select that bank booth.

The Coordinates
Once you have selected your object and you see the yellow box around it on the screen, we want to look at the output that has been displayed in the Simba console. You should see the following line:
This is the X and Y position of the object that you have chosen in relation to the map you have loaded.
Note that if you change your map to something like the world map instead of the [3_6] slice, the coordinate will not point to the same object as the coordinates are generated based on the size of the map loaded.
Once we have the coordinates of our object, we can continue on and use colour finding to make sure that we select the proper item in the tile (especially useful if the item does not cover a complete tile).
Part 3: Using Colours
Getting The Colours With ACA
The reason we still want to use colour in addition to the position is that we want to make sure we are as accurate as possible in locating the object we want to click. For example, if we wanted to open the bank using the bankers and we just use the tile location, we would have to click inaccurately inside of that tile. This is not something we want to do, especially if it is an object/NPC that is smaller than the tile size.
If you haven't already seen
this part of my other tutorial, please look through it and understand what we will be doing in order to gather the required colour.
Using the ACA tool, we want to gather the colours of the bank booth. This is what I've come up with:
Don't be alarmed with all of the other objects highlighted in red. This is because we will only be checking the bounds of the yellow square for this colour. This means that we will only find the booth located inside of the yellow square since it has this colour inside.
With this information, you can now plug it into the following line of code:
Simba Code:
CTS2(COLOUR, TOL, HUE, SAT);
After plugging them in, we should get:
Simba Code:
CTS2(2316393, 8, 0.06, 1.72);
Now that we have our colours, let's get clicking!
Part 4: Putting It Together
The Code
Before jumping into it, if you still haven't visited
this thread, please do so and read it. I will not be explaininig it the following as much since most of the important information is located there.
Below is the following program we will be using:
Simba Code:
program new;
{$H-}
{$define SMART}
{$I SRL/OSR.simba}
{$I RSWalker/Walker.simba}
type
TMSObject = record
WorldLoc: TPointArray; //loctions on the world map
Color: TCTS2Color; //must have color
MinCount: Int32; //size of TPA
SplitDist: Int32; //distance between pixels
end;
const
// START FILLING IN HERE
LOGIN_NAME = ''; // Account Login Email/Username
LOGIN_PASS = ''; // Account Password
RS_WORLD = -1; // Preferred world, -1 = random world
IS_MEMBER = False; // Are you a member? True or False
MAP = 'world[3_6].png'; // Map that you are using
// END HERE
var
RSW: TRSWalker;
procedure declarePlayers();
begin
with Players.New()^ do
begin
LoginName := LOGIN_NAME;
Password := LOGIN_PASS;
IsActive := True;
IsMember := IS_MEMBER;
World := RS_WORLD;
end;
Players.SetCurrent(0);
end;
function TMSObject.Find(DoSort: Boolean=True; Expand:Int32=0): TRectArray;
var
loc, me: TPoint;
rect: TRectangle;
locations, TPA: TPointArray;
ATPA: T2DPointArray;
begin
me := RSW.GetMyPos();
locations := Copy(Self.WorldLoc);
if DoSort then Locations.Sorted(me);
for loc in Locations do
begin
rect := RSW.GetTileMSEx(me, loc, 1).Expand(Expand);
if MainScreen.GetBounds.Contains(rect.Bounds) then
begin
if (srl.FindColors(TPA, Color, rect.Bounds) < Self.MinCount) then
Continue;
if (Self.SplitDist > 0) then
begin
TPA := rect.Filter(TPA);
ATPA := TPA.Cluster(Self.SplitDist);
ATPA.SortByMiddle(rect.Mean);
Result += ATPA[0].MinAreaRect;
end else
Result += rect.Expand(-Expand);
end;
end;
end;
procedure Test();
var
i: Int32;
obj: TMSObject;
rectangles: array of TRectangle;
begin
while True do
begin
//**************************************************************************
obj := [[[XXX, YYY], [XXX, YYY]], CTS2(CCCCCC, TTT, H.HH, S.SS), UUU, ZZZ];
//**************************************************************************
rectangles := obj.Find(True, 3);
smart.Image.Clear();
for i:=0 to High(rectangles) do
smart.Image.DrawTPA(rectangles[i].ToTPA.Connect, $00FFFF);
end;
end;
begin
smart.EnableDrawing := True;
srl.Setup([]);
RSW.Init(MAP, -1);
declarePlayers();
if (not SRL.isLoggedIn) then //If not logged in then..
begin
Players.LoginCurrent(); //Log player in
Mainscreen.setAngle(True); //Sets the camera angle to the highest point
end;
test();
RSW.Free();
end.
If you look through the code, you will notice a section where I have highlighted with comments that looks like this:
Simba Code:
//**************************************************************************
obj := [[[XXX, YYY], [XXX, YYY]], CTS2(CCCCCC, TTT, H.HH, S.SS), UUU, ZZZ];
//**************************************************************************
This is where we create the object that we want to find on our main screen. Let me explain what each entry does.
- '[XXX, YYY]': This is the location for your X and Y coordinates of the tile you are wanting to search for. You will notice that there are two slots in this array. This is useful in the case you want to locate another bank booth that might be either next to, or a little farther over from the first one.
- 'CTS2(CCCCCC, TTT, H.HH, S.SS)': This is the colour that we determined earlier using ACA.
- 'UUU': This is the minimum number of points the object must have of the given colour. If the number of points in the TPA falls below this, the object is ignored.
- 'ZZZ': This is the maximum distance that is allowed between two pixels before the TPA ends.
To plug this in with our information we gathered, we should get something like this:
Simba Code:
//**************************************************************************
obj := [[[162, 652]], CTS2(2316393, 8, 0.06, 1.72), 200, 8];
//**************************************************************************
The Final Product
Now your code should look something like this:
Simba Code:
program new;
{$H-}
{$define SMART}
{$I SRL/OSR.simba}
{$I RSWalker/Walker.simba}
type
TMSObject = record
WorldLoc: TPointArray; //loctions on the world map
Color: TCTS2Color; //must have color
MinCount: Int32; //size of TPA
SplitDist: Int32; //distance between pixels
end;
const
// START FILLING IN HERE
LOGIN_NAME = ''; // Account Login Email/Username
LOGIN_PASS = ''; // Account Password
RS_WORLD = -1; // Preferred world, -1 = random world
IS_MEMBER = False; // Are you a member? True or False
MAP = 'world[3_6].png'; // Map that you are using
// END HERE
var
RSW: TRSWalker;
procedure declarePlayers();
begin
with Players.New()^ do
begin
LoginName := LOGIN_NAME;
Password := LOGIN_PASS;
IsActive := True;
IsMember := IS_MEMBER;
World := RS_WORLD;
end;
Players.SetCurrent(0);
end;
function TMSObject.Find(DoSort: Boolean=True; Expand:Int32=0): TRectArray;
var
loc, me: TPoint;
rect: TRectangle;
locations, TPA: TPointArray;
ATPA: T2DPointArray;
begin
me := RSW.GetMyPos();
locations := Copy(Self.WorldLoc);
if DoSort then Locations.Sorted(me);
for loc in Locations do
begin
rect := RSW.GetTileMSEx(me, loc, 1).Expand(Expand);
if MainScreen.GetBounds.Contains(rect.Bounds) then
begin
if (srl.FindColors(TPA, Color, rect.Bounds) < Self.MinCount) then
Continue;
if (Self.SplitDist > 0) then
begin
TPA := rect.Filter(TPA);
ATPA := TPA.Cluster(Self.SplitDist);
ATPA.SortByMiddle(rect.Mean);
Result += ATPA[0].MinAreaRect;
end else
Result += rect.Expand(-Expand);
end;
end;
end;
procedure Test();
var
i: Int32;
obj: TMSObject;
rectangles: array of TRectangle;
begin
while True do
begin
//**************************************************************************
obj := [[[214, 469]], CTS2(2316393, 8, 0.06, 1.72), 200, 8];
//**************************************************************************
rectangles := obj.Find(True, 3);
smart.Image.Clear();
for i:=0 to High(rectangles) do
smart.Image.DrawTPA(rectangles[i].ToTPA.Connect, $00FFFF);
end;
end;
begin
smart.EnableDrawing := True;
srl.Setup([]);
RSW.Init(MAP, -1);
declarePlayers();
if (not SRL.isLoggedIn) then //If not logged in then..
begin
Players.LoginCurrent(); //Log player in
Mainscreen.setAngle(True); //Sets the camera angle to the highest point
end;
test();
RSW.Free();
end.
Now lets give it a run! We should have our character log in and our object appear on the screen like so:

Part 5: Final Thoughts
Conclusion
Now that you have figured out a method of locating static objects using both the minimap location and the colour of the object, feel free to experiment with it in your scripts!
If you happen to have any questions, feel free to ask them below. If there are any problems, errors, or anything that should be fixed, please let me know.