The Official SCAR Scripting Guide
This tutorial is under construction, check back later for updates!
- 1 Introduction
- 2 Basics
- 3 Constants
- 4 Variables
- 5 Types
- 6 Conditions
- 7 Loops
- 8 Arrays
SCAR was originally created by Kaitnieks (Aivars Irmejs) in 2003 with the purpose of replacing his previous creation AutoRune, a program designed to automate tasks in RuneScape. The program rapidly evolved into a very powerful color based macroing application and was used not only for RuneScape, but also to automate other games andtasks not related to gaming. Late 2006 Kaitnieks retired from the scene and passed on the task of developing SCAR to Freddy1990 (Frédéric Hannes), who up to this day still develops SCAR. When the development was passed on, SCAR was renamed to SCAR Divi. Divi is the Latvian word for "two", as now 2 people had developed the program.
SCAR has been from the start a scriptable macroign environment. Scripting SCAR is done in a Pascal based language. The syntax of this is very different from C based languages, however, it was originally designed for teaching purposes which makes it very easy to comprehend.
When thinking about scripting/programming, tehre are a few important distinctions you have to make. There is data and logic. The data in your script originates from values you hardcode into a script or data you get from SCAR's API. This data can be stored in variables, which we'll discuss later on. The logic of your script is the code which makes it work. It is a blueprint for the behavior of your script, it tells it what to do under which circumstances.
The first thing you'll want to do when learning any new programming language is create a "Hello World" application. This is an extremely basic script which outputs "Hello World!" in a way the end-user can read it. The reason why you want to do this is to get a basic idea of how your programming environment works. "Hello World!" is a string, this basically means that it's a sequence of characters. The Pascal programming language marks strings by surrounding them with apostrophes. If you want to add an apostrophe to a string, you need to place 2 inside of the string which makes the first one "escape" the second one and add it to the string. For basic output we can use the WriteLn function, which simply outputs text to the debug box in SCAR's main GUI.
Our resulting script looks like this:
program HelloWorld; begin WriteLn('Hello World!'); end.
The "Hello World!" string we passed to the WriteLn function is surrounded by rounds. When calling any function that takes arguments, in this case the message to output, you have to pass the arguments by placing them in between rounds after the function name and separate them with commas. Except for a few exceptions which we'll see in the future, you should always terminate lines that contain a funciton call witha semicolon. The "begin .. end." clause we see in the script is always the last thing you'll find in your script (everything after "end." is ignored). It represents the entry point of your script, it will be the first thing that is being executed and you call everything else from there. At the top of the script we notice the program line. This line should always be the very first of the script, if you place things above it, it will cause errors. This line simply denotes a name for your script but it is not required to include it in a script, you can simply choose to remove it. Future examples in this guide will not include this line.
The most basic form of data storage are constants. These are fields in the memory which contain a single data entry and which are denoted by a specific reference name. In SCAR constants can only contain basic data such as strings, numbers and boolean values. A very important thing to note about constants is that they are immutable, which means that they can not be changed. You can only set constants once by manually assigning them a value in your script, they can not be assigned dynamically during runtime.
Constants are defined in the constants sections of a script, there can be multiple of these sections and they have to be defined in the global scope and not in a code section. All constants must have a different name, if they don't, the compiler does not know which is which.
This example shows the simple use of constants:
const StringConst = 'Hello World!'; IntConst = 12345; FloatConst = 12.345; BoolConst = True; begin WriteLn(StringConst); WriteLn(IntConst); WriteLn(FloatConst); WriteLn(BoolConst); end.
Hello World! 12345 12,345 1
A second form of data storage are variables. Variables work like constants in the way that they are denoted by a unique name (which can't clash with names of constants), they can't however be assigned a value directly. All data in varaibles has to be assigned inside of code sections. this means that thye can both be read and written to. Variables can not only hold basic data types, but also abstract data types which we'll take a look at later. They are defined in the variable section in the global scope or a local scope. To define them, you place a colon and the data type after the name of the variable and terminate it with a semicolon. You can also add several variables of the same type to a single declaration by separating them with commas.
In this example we declare 3 variables, 1 of the type string and 2 more of the type Integer, the latter of which holds a whole number:
var Str: string; i1, i2: Integer; begin Str := 'Hello World!'; WriteLn(Str); i1 := 5; i2 := 6; WriteLn(i1); WriteLn(i2); end.
Hello World! 5 6
It is common practice to prefix abstract data types in Pascal with the letter T, most basic types don't have this prefix.
Pascal provides a series of basic data types which are the most basic form of data you can store.
Logical data types:
|Boolean||True or False (1 or 0)||True,False|
Integer data types:
Decimal data types:
|Single||Single precision floating point value (4 bytes)||-2.9,0,5.874|
|Double||Double precision floating point value (8 bytes)||-136.236542738,0,4875.56974584|
|Extended||Extended precision floating point value (10 bytes)||-136.23654573742738,0,4875.569174537384|
Text data types:
|Char||A single character||a,9,B,!,@|
|String||A string of characters||Hello World!|
|AnsiString||An ansi string of characters||Hello World!|
You should always use the smallest type available that fits your needs, this will optimize your memory usage and overall performance as smaller data types require less cpu cycles to read, write and perform operations on.
A type can easily be given an alias. You simple assign the type to a new name in the type section which can only be placed in the global scope. Type names can't clash with any other names such as constant ot variable names in your script.
In this example we create an alias for the Integer data type:
type MyInt = Integer; var Value: MyInt; begin Value := 123456; WriteLn(Value); end.
A record type or structured type is a type that consists out of multiple named fields which can be defined as a different type. An example of this is the TPoint type.
To create a record type, you have to assign a new record to a name in the type section, in it's definition clause, add the fields the same way you would define variables.
In this example we create a simple record type with a single Integer field:
type TMyRecord = record Field: Integer; end; var Value: TMyRecord; begin Value.Field := 123456; WriteLn(Value.Field); end.
Records don't work recursively, which means you can't create a field inside of a record of that same record type.
The with keyword can be used to access the fields of a record without having to repeatedly writing the name of the record variable as prefix of the access call.
This example shows how to with keyword works.
type TMyRecord = record Field1: Integer; Field2: Integer; end; var Value: TMyRecord; begin Value.Field1 := 123456; with Value do begin Field2 := 1369; WriteLn(Field1); Inc(Value.Field2); // You can still access the field with the full name WriteLn(Field2); end; end.
An enumeration is a collection of named values. This sometimes makes it easier to assign meaning to values rather than using numbers. This is assigned to a type as a comma separated list surrounded by rounds.
In this example we show that enumeration values are stored as simple integer values in the memory:
type TMyEnum = (meTest, meTest2); var Value: TMyEnum; begin Value := meTest; WriteLn(Value); Value := meTest2; WriteLn(Value); end.
An enumeration can hold as many as 256 different values.
SCAR provides a whole bunch of class types to use in scripts, you can't define class types yourself though. A class has to be used as an object which is an instance of the class. An object has to be created and freed after you're done using it or it will get stuck in the memory.
This example shows the usage of a TStringList class. This class can hold a string, allow easy access to individual lines of the string and perform operations on it.
var Value: TStringList; begin Value := TStringList.Create; try Value.Text := 'Hello World!'; WriteLn(Value); finally Value.Free; end; end.
A script can check if a certain condition is met by checking if functions return a specific result or variables contains a specific value. To do this, you need to evaluate a comparison between 2 values and perform an action based on the result of that comparison.
If statements can be used to evaluate very simple or very complex statements. The basic concept works like this: "if a comparison returns true, then do something, if it returned false, do something else". Obviously you can check another condition if the first one was not met.
This example shows a vary basic construct with an if statement which checks if a constant equals 5. If you look closely, you'll see that the first WriteLn statement is not terminated with a semicolon, this is an exception that was mentioned earlier in the guide. When you are not using a "begin..end" bloxk in the if statement, which allows you to only execute a single statement, the statement that is followed by else can not have a semicolon at then end.
const Const1 = 5; begin if Const1 = 5 then WriteLn('Const1 is 5') else WriteLn('Const1 is not 5'); end.
This example is the same as the previous one with the exception that it contains a "begin..end" block in the if statement. This allows you to execute multiple lines. The "end" keyword should normally be terminated with a semicolon, but it is followed by else, so this is dropped. If you add a "begin..end" after the else keyword, you have to terminate the end with a semicolon.
const Const1 = 5; begin if Const1 = 5 then begin WriteLn('Const1 is 5'); end else WriteLn('Const1 is not 5'); end.
Like the previous example, but with a "begin..end" clause in the else statement.
const Const1 = 5; begin if Const1 = 5 then WriteLn('Const1 is 5') else begin WriteLn('Const1 is not 5'); end; end.
Pascal provides a series of logical operators to evaluate expressions. Previously we used the equals operator to check if 2 values are equal.
This list contains all logical operators. Take in mind that most of these only work on numerical types with the exception of equals and does not equal.
|=||Equals||Returns true if 2 values are equal.|
|<>||Does not equal||Returns true if 2 values are not equal.|
|<||Smaller than||Returns true if the first value is smaller than the second one.|
|<=||Smaller than or equal||Returns true if the first value is smaller than or equal to the second one.|
|>||Larger than||Returns true if the first value is larger than the second one.|
|>=||Larger than or equal||Returns true if the first value is larger than or equal to the second one.|
This example shows the use of the <> operator and an if statement that follows an else statement to check a second condition.
const Const1 = 4; Const2 = 4; begin if Const1 = 5 then WriteLn('Const1 is 5') else if Const2 <> 6 then WriteLn('Const2 is not 6'); end.
Boolean operators can be used to combine boolean values. This allows you to evaluate more complex statements by combining multiple statements in a certain way.
Unlike all other boolean oeprators, the not operator is the only unary operator, which means it only takes 1 statement as an argument. It's purpose is to flip a boolean. True becomes false and the other way around.
This example shows you how the not operator works.
const MyBool = False; begin if not MyBool then WriteLn('MyBool is False'); end.
The and operator returns true if both of the statements are true.
This example shows you how the and operator works.
const Bool1 = False; Bool2 = True; begin if Bool1 and Bool2 then WriteLn('Both booleans are True'); end.
The or operator returns true if one or both of the statements are true.
This example shows you how the or operator works.
const Bool1 = False; Bool2 = True; begin if Bool1 or Bool2 then WriteLn('One or both of the booleans are True'); end.
The xor (exclusive or) operator returns true if only one of the statements is true, but not both.
This example shows you how the xor operator works.
const Bool1 = False; Bool2 = True; begin if Bool1 xor Bool2 then WriteLn('Only one of the booleans is True'); end.
Case statements allow you to replace a big amount of if..else statements when using some basic types. The logic of this construct is very simple, you examine a value and then simple check if it matches different cases and provide a code block to execute for each case. You can also execute a single code block in several different cases.
This example shows the many uses of a case statement for an integer value.
const Const1 = 4; begin case Const1 of 1: WriteLn('1'); 2..5: WriteLn('2 up to 5'); 6, 8: begin WriteLn('6 or 8'); end; 7, 9..11: WriteLn('7 or 9 up to 11'); else WriteLn('Not 1 to 9'); end; end.
Loops allow you to "loop" through code several times, you are able to control when a loop stops by passing it certain conditions or by manually breaking out of it.
The while loop is a construct where you loop while something is true.
In this example we're going to loop while the variable "Int" is not 5, when the loop reaches the top again and finds that the variable is 5, it will jump to after the loop and continue executing code there. We're using the Inc function to increase the variable by 1 every time the function loops. Take in mind that as previously, the begin..end block can be dropped to affect just a single line.
var Int: Integer; begin Int := 0; while Int <> 5 do begin WriteLn('Hello World ' + IntToStr(Int) + '!'); Inc(Int); end; WriteLn('Out of the loop'); end.
The repeat loop is a construct where you loop until something is true. This condition contrary to the while loop is checked at the end of the loop, not the start. This loop does not use a begin..end construct so it will always work on multiple lines.
This example shows the example from the while loop adapted for the repeat loop. The condition is not checked at the start of the loop, so if the variable is 5, it will loop infinitely as it will be increased to 6 inside of the loop before it's checked.
var Int: Integer; begin Int := 0; repeat WriteLn('Hello World ' + IntToStr(Int) + '!'); Inc(Int); until Int = 5; WriteLn('Out of the loop'); end.
The for loop allows you to loop within a specific range of integer values. You loop from a small value to a large value without ever incrementing it yourself. You can't use this construct to loop from a large value to a smaller one.
This example shows you how to loop from 1 to 5 and print out the current number.
var Int: Integer; begin for Int := 1 to 5 do WriteLn(Int); WriteLn('Out of the loop'); end.
Reversed For Loop
As previously mentioned you can't use the "for..to..do" construct to loop downwards, it is however possible to do this using the "downto" keyword instead of "to".
This example loops down from 5 to 1 using the downto keyword.
var Int: Integer; begin for Int := 5 downto 1 do WriteLn(Int); WriteLn('Out of the loop'); end.
Arrays are a means of storing one or more values in a single variable. An array has 2 properties, it's type and size. The type of an array indicates the type of value you can store in it, every array you define can only store a specific type of values, if you define an array of integer values, you'll only be able to store integer values in it. The size of an array doesn't indicate how many values are stored in the array, it indicates how many values can be stored in it. Usually this number is the same for both as you'll want to keep the array at a size that can just fit all of the values you want to store in it.
Every item in an array has an integer index that is associated with it. These indexes start at 0 for the first item and go all the way up to the size - 1 for the last item. This means the index is offset at 0 rather than 1 which is the case for characters in strings. Accessing an array-item can be achieved by placing the index of the item you want to store or retrieve in between square brackets after the name of the array, at this point it will act as a variable of the type of the array.
Dynamic arrays can be used as a type by simple adding the type of the array after "array of". SCAR does come with a set of array types which you can use to replace common arrays. An "array of Integer" is defined as TIntArray by default. Dynamic arrays can easily be initialized using a comma-separated list surrounded by square brackets.
This example shows the basic use of dynamic arrays. First the array is initialized with 5 values, the we increment them by 1 and immediately print them by looping through it with a for loop and using the loop variable as an index for the array. For the purpose of this example we use "array of Integer" as type, but this is encouraged to be replaced with SCAR's TIntArray type.
var Arr: array of Integer; Index: Integer; begin Arr := [5, 8, 11, 3, 6]; for Index := 0 to 4 do begin Inc(Arr[Index]); WriteLn(Arr[Index]); end; end.
A set of functions is provided by SCAR to perform operations related to the size of an array. The Length function allows you to simply retrieve the size of an array by entering the array as an argument. You can use the SetLength function to modify the size of an array by passing the array as the first argument and the new size as the second argument. If you shrink an array in size, the data that gets cut off should be considered as lost as it will then be possible for it to be overwritten in the memory with new data. By default the size of an array is 0 unless you initialized it with a set of values, you'll need to use the SetLength function to change it's size before you can store data in it.
In this example we look at the usage of the Length and SetLength functions.
var Arr: array of Integer; Size, Index: Integer; begin SetLength(Arr, 2); Arr := 5; Arr := 2; Size := Length(Arr); for Index := 0 to Size - 1 do WriteLn(Arr[Index]); end.
Another two functions which can be useful are the Low and High functions which respectively return the first and last indexes of the items in the arrays. For a dynamic array Low will obviously always return 0 as it always starts at 0.
In this example we look at the usage of the Low and High for looping though an array.
var Arr: array of Integer; Index: Integer; begin SetLength(Arr, 2); Arr := 5; Arr := 2; for Index := Low(Arr) to High(Arr) do WriteLn(Arr[Index]); end.
On top of dynamic arrays, SCAR also supports static arrays. These are arrays with a predefined size, which can not be changed during the script's execution. They are specified by a start and end-index and are not offset at 0.
In this example we define a static array offset at the index -1 and with a maximum index of 1. This means it can contain a maximum of 3 values.
var Arr: array[-1..1] of Integer; begin Arr[-1] := 5; Arr := 4; Arr := 3; WriteLn(Arr[-1]); WriteLn(Arr); WriteLn(Arr); end.
Aside from defining arrays of regular data types, it is also possible to define arrays of arrays. Every "array of" adds an additional dimension to your array. A regular array only has a single dimension. The X-dimensional array has to be accessed like any other array, in the order of the definition. Consider this type of arrays to be a colletion of arrays inside of an array.
In this example we define a 2 dimension array of integers.
var Arr: array of array of Integer; Arr2: array of TIntArray; begin SetLength(Arr, 2); Arr := [1, 2]; Arr := [3, 4]; WriteLn(Arr); SetLength(Arr2, 2); Arr2 := [1, 2]; Arr2 := [3, 4]; WriteLn(Arr2); end.