PDA

View Full Version : Using records to create and find mainscreen objects



The Mayor
05-23-2015, 04:29 PM
Using records to create and find mainscreen objects


In this tutorial I'm going to show you how to create and find mainscreen objects using records. This might look complicated at first, but it can shorten your scripts and make them much easier to maintain. Would you like to be able to write a script like this:


if Copper.Find() then
Copper.Drop()
else
Copper.WalkTo();




PART 1: Records



A record is an advanced data type which is used to group multiple variables (known as 'fields') together under the same umbrella. Each of these variables can be a different type, so a record can be very complex.

For example, a type that you are no doubt familiar with is the TPoint. This is actually a record that contains 2 fields:


type TPoint = record
x, y: integer;
end;


Each TPoint has an x and a y coordinate. You can access each of the fields like so:


procedure example();
var
p: TPoint;
begin
p.x := 10;
p.y := 20;
writeLn(p); // Will print [10, 20]
end;


The fields that make up a record will appear in the codehints (see X and Y at the bottom), which is helpful:

http://puu.sh/hXBnp/044dde93af.png

Because a TPoint is a type, you can create type methods. To try and explain this simply, the type becomes an object, on which the method is called (all of the procedures and functions in the above codehint are methods that act on a TPoint). Look at the following example because my explanation probably doesn't make sense:


procedure TPoint.print();
begin
writeLn(Self.X); // Self refers to the TPoint which is passed to this procedure
writeLn(Self.Y);
end;

procedure example();
var
p: TPoint;
begin
p := point(10, 20);
p.print(); // Calling the above print method on p
end;


This picture might help you understand:

http://puu.sh/hXDll/f9113e42ce.png

That is basically what records and type methods looks like, and how to access fields in a record.



PART 2: Creating a TMSObject record



What I'm going to do now is create a new type that will represent a MainScreen Object (something I want to find on the MainScreen, such as a NPC, a Tree, or a Rock). Each object will have certain information associated with it, such as a colour and mouseOverText. Each piece of information will represent a new 'field' (a new variable) in the record.


type TMSObject = record
Name: String;
Colour: TColorData; // TColorData isn't used that much, but I'll explain below
OverText, ChooseOption: TStringArray;
end;


Above you can see I've created a new type which I called a 'TMSObject' ("Type MainScreen Object"). This type has 4 fields, a name, colour, overText and chooseOptions. Now I can create some variables of this new TMSObject type:


var
Copper, Tin, Banker: TMSObject;


Now that I've created 3 TMSObjects, I need to assign some information to them. I could assign each field for each object one by one, like:


procedure loadObjects();
begin
Copper.Name := 'Copper Rock';
Copper.OverText := ['opper'];
Copper.Colour := [4808543, 7, [2, [0.14, 0.15, 0.00]]]; // TColorData structure, see note below
Tin.Name := 'Tin Rock';
// Etc....
end;





Just a quick note: notice how I put all of the colour information (color, tol, hue, sat, cts) together? You might have guessed that the TColorData type is also a record, with fields for color, tolerance, CTS, etc. This is the TColorData type structure:

[Color, Tolerance, [CTS, [Hue, Saturation, Sensitivity]]]

This also means that I am using a 'record within a record', so to access the Copper object's tolerance I would write:

writeLn(Copper.Colour.tolerance);


This is the easy way to assign information, but imagine if I had a large number of objects, each with a large number of fields; that would take quite a few lines to write. Instead of doing it that way, I'll create a procedure which will take the field information as parameters, before assigning that information to the object:


procedure TMSObject.Init(_Name: String; _Colour: TColorData; _OverText, _ChooseOption: TStringArray);
begin
Self.Name := _Name; // The procedure parameters start with '_' to differentiate them from the record fields
Self.Colour := _Colour;
Self.OverText := _OverText;
Self.ChooseOption := _ChooseOption;
end;


Now in my loadObjects() procedure I can just pass all the information to the above .Init() method. Remember, I'm calling the .Init() method on the objects themselves:


procedure loadObjects();
begin
Copper.Init('Copper Rock', [4808543, 7, [2, [0.14, 0.15, 0.00]]], ['opper'], ['ine Cop']);
Tin.Init('Tin Rock', [3808543, 7, [2, [0.13, 0.13, 0.00]]], ['in'], ['ine Tin']);
Banker.Init('Banker NPC', [6508543, 3, [2, [0.11, 0.86, 0.00]]], ['ank'], ['pen Bank']);
end;


Now that I've entered all the information, all I have to do is run loadObjects() when my script starts, to initialise them. The last thing I need to do is write some code to actually find them.




PART 3: Finding the TMSObject



I'm going to use this method to find the colours:

function findColorsTolerance(var points: TPointArray; color: integer; searchBox: TBox; tol: Integer; settings: TColorSettings): Boolean;

If I wanted to search for the Copper, what I could do is this:

procedure findCopper();
var
TPA: TPointArray;
begin
findColorsTolerance(TPA, Copper.Colour.color, mainScreen.getBounds(), Copper.Colour.tolerance, Copper.Colour.settings);
// ATPA + mouse code
end;


While that is somewhat effective and clean, I can take it a step further by creating a type method to find any TMSObject:


function TMSObject.Find(): Boolean;
var
i: Integer;
TPA: TPointArray;
ATPA: T2DPointArray;
begin
writeLn('Searching for: ' + Self.Name); // Prints the TMSObject's name

if Self.Colour.gatherIn(TPA, mainScreen.getBounds()) then // A TColourData method. Puts matching colours into 'TPA'
begin
ATPA := TPA.cluster(15);
if (length(ATPA) < 1) then exit(false);

for i := 0 to high(ATPA) do
begin
mouse(ATPA[i].getBounds().getRandomPoint());
if isMouseOverText(Self.OverText) then // The TMSObject's OverText
begin
fastClick(MOUSE_RIGHT);
result := chooseOption.select(Self.ChooseOption); // The TMSObject's ChooseOptions
if result then break;
end;
end;

end;

writeLn(Self.Name + '.Find(): Result = ' + boolToStr(result));
end;


That is quite straight forward when you break it down. Now I can write code like:


procedure example();
begin
if Copper.Find() then
writeLn('We just clicked on a copper rock!');

if Banker.Find() then
if bankScreen.isOpen(5000) then
writeLn('We found and opened the bank!');
end;



So that's all there is to it. You can add an endless number of fields to a record. I could have added things such as:


Price
ClickType
DTM or Bitmap
TPA cluster distance
SPS map coordinates
Etc.


I hope you learnt something from this. Have fun with records!

StickToTheScript
05-23-2015, 04:32 PM
Great tutorial! Written very cleanly! Your tutorials are always great! :D

Ian
05-23-2015, 06:43 PM
Makes sense for things you'd need to use in more than one way, nice tutorial :)

Citrus
05-23-2015, 08:41 PM
Nice! I remember seeing this when I dug through your gelatinous abomination script. This is perfect for a script I've been meaning to start.

Incurable
05-23-2015, 10:33 PM
I've never used records because I never understood them properly, now I do. Thanks for another great tutorial, Maymay. :)

On an unrelated note, how come there's no advanced object finding function in SRL-6? We have the basic mainScreen.findObject, so why not add a more accurate one that works in most situations?

Thomas
05-25-2015, 01:52 PM
Should perhaps include T2DColorData?
I haven't seen it being used in many scripts, and combining 2 (or more) colors with low tolerances into a single TPA and then clustering them worked better then just clustering based on 1 color.
It allowed me to cluster based on shorter distances.

I sometimes even sort the clustered ATPA by size (color points), as that most often gives the closest TPA (more pixels/model onscreen).

Perhaps add an example with WaitTypeFunc near the end? That is also something I love using but barely see being used.

You could even talk a little about making includes with these records? :)
Would be usefull for some if they use multiple scripts with similar functions.

GetHyper
06-17-2015, 12:34 AM
Thank you, will help me tidy up my script.

Edit: Took me a while to get my head around but I got there eventually.

The Mayor
06-17-2015, 07:03 PM
Great tutorial! Written very cleanly! Your tutorials are always great! :D

Thanks


Makes sense for things you'd need to use in more than one way, nice tutorial :)

Thanks


Nice! I remember seeing this when I dug through your gelatinous abomination script. This is perfect for a script I've been meaning to start.

Don't think anything like this was in GAO :p


I've never used records because I never understood them properly, now I do. Thanks for another great tutorial, Maymay. :)

On an unrelated note, how come there's no advanced object finding function in SRL-6? We have the basic mainScreen.findObject, so why not add a more accurate one that works in most situations?

mainScreen.findObject isn't really that basic and it will most probably work in most situations.


Should perhaps include T2DColorData?
I haven't seen it being used in many scripts, and combining 2 (or more) colors with low tolerances into a single TPA and then clustering them worked better then just clustering based on 1 color.
It allowed me to cluster based on shorter distances.

I sometimes even sort the clustered ATPA by size (color points), as that most often gives the closest TPA (more pixels/model onscreen).

Perhaps add an example with WaitTypeFunc near the end? That is also something I love using but barely see being used.

You could even talk a little about making includes with these records? :)
Would be usefull for some if they use multiple scripts with similar functions.

Those things are beyond the scope of this tutorial :p


Thank you, will help me tidy up my script.

Edit: Took me a while to get my head around but I got there eventually.

Good to hear!

Citrus
06-17-2015, 07:27 PM
Don't think anything like this was in GAO :p

Ahh you're right, it was your Runespan script. GAO used mayorObjectFinder()

srlMW
08-13-2015, 12:07 AM
Cheers for the tutorial, I'll be implementing such features into my mining script to cut down on the if... else statements and to just tidy it up in general. Thanks again! :)

wallie018
08-13-2015, 06:55 PM
Thanks for this tutorial, I will be using this soon in my next script(s).. :D

Saint//+
10-17-2015, 06:48 PM
this is awesome.

How different is this from classes in python?