# Thread: [SRL] Finding Objects Accurately With RSWalker

1. ## [SRL] Finding Objects Accurately With RSWalker

Finding Objects Accurately With RSWalker
Verified Working On: February 12, 2019

Part 1: Introduction
Part 2: Getting The Coordinates
Part 3: Using Colours
Part 4: Putting It Together
Part 5: Final Thoughts

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 HEREtype 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:
Code:
{X = 214, Y = 469}
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 HEREvar  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 HEREvar 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.
Last edited by StickToTheScript; 02-12-2019 at 10:27 PM. Reason: Updated

2. very nice tut

3. Nice, I fiddled with a similar concept with SPS but is wasn't nearly as accurate as RSwalker

4. SRL Junior Member
Join Date
Feb 2012
Posts
173
Mentioned
2 Post(s)
Quoted
82 Post(s)
Thank you for posting this tutorial

5. Great Tut

6. Registered User
Join Date
Jul 2018
Posts
23
Mentioned
0 Post(s)
Quoted
8 Post(s)
Can it only be used with SMART?

7. Originally Posted by felparers
Can it only be used with SMART?
Nope. It can work with any RS client. Only the drawing needs to be done in SMART. If you ignore the drawing, then you should be fine.

8. Registered User
Join Date
Apr 2015
Posts
2
Mentioned
0 Post(s)
Quoted
1 Post(s)
Hey guys, This looks amazing and I would love to use it, I am trying to use the script for coordinates but I am getting an "Access Violation", I have tried running as admin with no luck

"Access violation" at line 209, column 41 in file "C:\Simba 1.3 32\Includes\RSWalker\Walker.simba"
Code:
xcorr   := WorldSample.MatchTemplate(mmsample, Self.TMFormula);
I am using Simba 1.3-rc1 32bit with RSWalker 1.3.0

Fixed:

Had to remove the -1 from

Code:
RSW.Init(MAP, -1);
Last edited by EmberZ; 06-27-2019 at 03:50 PM.

9. SRL Junior Member
Join Date
Aug 2019
Posts
30
Mentioned
0 Post(s)
Quoted
1 Post(s)
Thanks for another very helpful guide.

10. SRL Junior Member
Join Date
Feb 2012
Location
Florida
Posts
193
Mentioned
0 Post(s)
Quoted
6 Post(s)
EDIT: Yeah after testing this over n over. It seems a medium zoom level is optimal for this to work.
Zoomed all the way out. This will not produce accurate tile locations over distance.
This is good for providing reliable search locations.