The Official SCAR Scripting Guide
This tutorial is under construction, check back later for updates!
Introduction
History
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.
Scripting
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.
Basics
Distinctions
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.
Hello World
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.
Output:
Hello World!
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.
Constants
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.
Output:
Hello World! 12345 12,345 1
Variables
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.
Output:
Hello World! 5 6
Types
It is common practice to prefix abstract data types in Pascal with the letter T, most basic types don't have this prefix.
Basic Types
Pascal provides a series of basic data types which are the most basic form of data you can store.
Logical data types:
Name | Data | Example |
---|---|---|
Boolean | True or False (1 or 0) | True,False |
Integer data types:
Name | Data | Example |
---|---|---|
Byte | 0..255 | 0,1,6,77,98 |
ShortInt | -128..127 | -48,-6,0,75,125 |
Word | 0..65535 | 0,5,96,7768,15568 |
SmallInt | -32768..32767 | -2587,-68,0,96,15568 |
Cardinal | 0..4294967295 | 0,125,16875,4987542,65987423 |
Integer | -2147483648..2147483647 | -15266984,-687450,0,4987542,65987423 |
Int64 | -18446744073709551616..18446744073709551615 | -15224679526487264,-687458366350,0,78479875757242,659757875777423 |
Decimal data types:
Name | Data | Example |
---|---|---|
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:
Name | Data | Example |
---|---|---|
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.
Type Aliases
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.
Record Types
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.
Enumerations
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.
Class Types
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[0]); finally Value.Free; end; end.
Conditions
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
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.
Comparison Operators
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.
Operator | Name | Description |
---|---|---|
= | 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
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.
Not Operator
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.
And Operator
The and operator returns true if both of the statements are true.
True | False | |
---|---|---|
True | True | False |
False | False | False |
Or Operator
The or operator returns true if one or both of the statements are true.
True | False | |
---|---|---|
True | True | True |
False | True | False |
Xor Operator
The xor (exclusive or) operator returns true if only one of the statements is true, but not both.
True | False | |
---|---|---|
True | False | True |
False | True | False |
Case Statements
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.