2. Pawn Language Basics
Areas of Interest:
- Variables
- Arrays
- Strings
- Functions
- Expressions and Operators
- Conditionals
- Looping
- Two Dimensional Arrays
- Compiler Directives
- Conclusion
1. Variables
Pawn is an embeddable, (almost) typeless, easy to use scripting language that is compiled for a virtual machine. AMX Mod X uses Small to route scripting functions to the Half-Life engine, using the Small Virtual Machine and Metamod (Small is written in C, Metamod is written in C++). While you write Small scripts in a text editor, the scripts must be compiled with a "Compiler", which produces a binary for AMX Mod X. The AMX Mod X team distributes a specially modified Small compiler.
Programming scripts in Pawn is relatively easy, and does not have concepts in other languages that are unnecessary for general use, such as pointers, vectors, structs, classes, streams, et cetera. Small has just three data types for declaring variables. The default variable type is a regular whole number, or integer. A variable name, for backwards compatibility, should be 19 characters or less, and MUST start with a letter. It can contain the symbols A-Z, a-z, 0-9, and the underscore ("_"). It is important to note that variable names are case sensitive - "myvar", "MyVaR", and "MYVAR" are three separate symbols. To declare a new variable, use the "new" operator like so:
new a //Declare empty variable "a"
new b=5 //Declare variable "b" and set it to 5.
new c=5.0 //This is invalid, technically not a whole number!
new d="hello" //"hello" is not a number either, this is invalid.
//You can also declare multiple variables on one line:
new e,f,g,h
new x=7, y=3
You can also declare a variable as a "Float", which means it can store numbers with decimal places. These are called "floating point" numbers:
new Float:a //Declare empty floating point variable "a"
new Float:b=5.3 //This will declare a new variable "b" and assign 5.3 to it.
new Float:c=5 //This is valid, but the compiler will give you a warning.
new Float:d="hello" //This is invalid, "hello" is not a decimal number.
You can also do the following:
//float(n) is a function that takes a number n and makes it a
// floating point number.
new Float:var = float(5)
new Float:var2 = 5.0
new Float:var3 = 1.0*5
new var4 = floatround(5.0)
//Note: floatround(n) is a function that takes a number n and rounds it to a whole number.
// this makes the assignment to a regular integer variable valid.
Note - Spacing does generally not matter, as long as the compiler can tell symbols apart from each other. If your spacing is REALLY bad, you will get errors or maybe even warnings. For example, "new var = 5" and "new var=5" are the same, but "newvar=5" is totally wrong.
The last variable type is "boolean". It is very simple - it is either "true", or "false". Both "true" and "false" are predefined data structures.
new bool:IsItOn //Declares a new variable "IsItOn" which is automatically false
new bool:xyz=true //Declares a new variable "xyz" set to true
2. Arrays
Pawn features basic "arrays". An array is a very important type of aggregate data. This means you can store multiple values in one variable! An array follows the same rules as a regular variable, and it has the same types. It simply can contain multiple values. You define an array with brackets, and how many values it can hold. For example:
//This will declare a variable called "Players" which holds 32 numbers.
new Players[32]
//You can now store values in any of the 32 "slots" this array has.
// The slots are numbered from 0 to n-1, or in this case, 0 to 31.
//Every slot starts off as 0.
//Set slot 0 to 5
Players[0] = 5
//Set slot 1 to whatever is in slot 0, in this case, the number 5
Players[1] = Players[0]
//This is invalid!
//Although there are 32 slots, they are numbered from 0 to 31.
//Doing this results in AMX Native Error 4 - AMX_ERR_BOUNDS
// or, it simply won't compile!
Players[32] = 15
//This is also totally invalid
Players[-1] = 6
new a = 3
//This is also totally invalid.
//a must be a constant number, so this is valid:
new BadArray[a]
const b = 3
new GoodArray[b]
//You can also use Compiler Directives (See last section)
#define ARRAY_SIZE 3
new Array[ARRAY_SIZE]
Arrays can also be declared with groups of data default, such as:
new Numbers[4] = {0,1,2,3}
//Note: it is important that you make sure the amount of numbers you pass and the size of the array
You can also use any data type with arrays:
//Array of floating points:
new Float:Numbers[4] = {0.0, 1.2, 2.4, 3.8}
//Array of booleans. Note this sets every slot to true.
new bool:playerHasGun[32] = true
3. Strings
You have probably noticed that an important data type is missing - characters (letters and symbols). These are called "strings", and in Pawn, they are technically numbers! A string of data is an array of numbers that translate to ASCII (character) symbols. For example:
//This will declare a number array "myString" that contains the data "Hello".
//It will have 6 slots, because there are 5 characters.
//The last slot is reserved for the number 0, which tells the Pawn engine that it is a string.
new myString[] = "Hello"
//Note: anything in between /* and */ is also a comment. You cannot use /* */ inside a /* */.
/* The following set of commands achieves the same purpose, however, it is longer and not recommended.
This works because each character of the string "Hello" is stored in a slot in the array.
new myString[6]
myString[0] = "H"
myString[1] = "e"
myString[2] = "l"
myString[3] = "l"
myString[4] = "o"
myString[5] = 0
/*You CANNOT do this! While it may compile, it is highly dangerous as it might cause overflow errors.*/
new myString[6]
myString = "Hello" //INVALID!
myString[0] = "Hello" //INVALID!
//To add data to a string, you can do this:
new goodString[6]
copy(goodString, 6, "Hello")
//Copy is a function that takes three parameters:
copy(destination[], length, source[])
//It copies the string inside the source array and places
// it into the destination array, but only copies up to length characters.
//Lastly, to prove that a string is really an array of numbers, this is completely valid:
new weird[6]
weird[0] = 68
weird[1] = 65
weird[2] = 73
weird[3] = 86
weird[4] = 68
weird[5] = 0
//This will set the variable "weird" to the string "DAVID".
//To see how letters and symbols translate into numbers, visit www.asctiitable.com
4. Functions
Pawn allows you to define your own functions. This comes in handy for removing code that is used in multiple places. Note that all functions should return a value. To do this, you use the "return" command, which immediately halts the function and returns the value of the expression passed to it. No code is executed in a function once the return is found. Here are some examples:
//This is a function that takes no parameters and returns 1.
//When activated, it uses the (non-existant) print function.
show()
{
print("Hello!")
return 1 //End, return 1
}
//Activate like this:
show()
You can also declare functions to take parameters.
//This declares a function called "add_two_numbers", which takes two numbers and returns the sum.
add_two_numbers(first, second)
{
new sum = first + second
return sum //Return the sum
}
//Then you can use your new function like this:
new a,b
a = 5
a = 12
new c = add_two_numbers(a,b)
//c will now be equal to 17.
You are not limited by what types of data parameters can accept. When you give parameters to a function, it is called "passing". You can pass either data or a variable to a function.
//This defines a new function called "add_two_floats"
// which takes two floating points and returns the sum
Float:add_two_floats(Float:first, Float:second)
{
new Float:sum = first + second
return sum
}
new Float:a
new Float:b
a = 5.0
b = 6.3
new Float:c
c = add_two_floats( a+b )
//c is now equal to 11.3
//You can even pass arrays! You do not have to specify the size of the array.
//If you do, you must make sure you are calling the function with an array of equal size and type.
add_two_from_array(array[], a, b)
{
new first = array[a]
new second = array[b]
new sum = add_two_numbers(first, second) //use our function from earlier
return sum
}
Note, that when you pass arrays through a function they are passed through what is called "by reference". When a normal variable is passed to a function, it is copied in memory, and the copy is sent and then deleted afterwards. This is not the case with an array. Because arrays can be very large, the array is "referenced" instead of copied. This means if you change the array, afterwards it will stay changed. For example:
//This function will switch slots a and b inside any array passed to this function.
swap_slots(array[], a, b)
{
new temp //Note, you need to temporarily hold one of the slots before swapping them
//Otherwise, you can't swap both values! This is a classic problem.
//If you have a and b, setting b equal to a eliminates the original value in b.
temp = array[b]
array[b] = array[a]
array[a] = temp
}
new myArray[2]
myArray[0] = 5
myArray[1] = 6
swap_slots(myArray, 0, 1)
//myArray[0] is 6, myArray[1] is 5
//You can prevent arrays from being modified by declaring them "constant", like so:
add_two_from_array(const array[], a, b)
{
new first = array[a]
new second = array[b]
new sum = add_two_from_array(first, second)
return sum
}
//Note, now when you use the function, you are guaranteed that the array will not be modified.
//This function modifies an array passed as a constant. It will not work.
bad_function(const array[])
{
array[0] = 0
}
5. Expressions
Expressions are just what they sound like from mathematics. They are groupings of symbols that return one piece of data. Expressions are normally comprised of parenthetical expressions, and are evaluated in a certain order (from innermost to outermost, parenthesis first, then multiplication, division, addition, subtraction, et cetera). You can put expressions anywhere. You can set variables equal to them or pass them to functions.
//This is the simplest expression. It returns the number zero.
0
//However, to make it easier to read, this is also valid:
(0)
If an expression is not zero or it is not false, it not only returns a value, it also returns "true". Otherwise, it will return 0, which is also "false".
//Here are more mathematical expressions. The mathematical operators are
// + for addition
// - for subtraction
// * for multiplication
// / for division
// % for modulus (finding the remainder of one number divided by another (5%2 is 1)
(5+6) //returns 11
((5*6)+3) //returns 33
((((5+3)/2)*4)-9) //returns 5
((5*6) % 7) //returns 2
//Here are other expressions:
(true) //returns true
(5.0 + 2.3) //returns 7.3 as a floating point
//There are also extensions of these operators for direct use on variables.
new a = 5
new b = 6
//The first are the post/pre increment and decrement operators.
a++ //returns a+1, or 6. This is a post increment.
++a //also returns a+1, or 6. This is a pre increment.
The difference between the two is subtle but important. a++ is evaluated LAST in an expression, while ++a is evaluated FIRST. This differences comes in handy with code that uses loops in certain ways. It is also important to know that the increment/decrement operators will not only return a+1, but set the variable a to a+1.
a-- //returns 4, post decrement
--a //returns 4, pre decrement
//Note that a++ essentially trims down this code:
a = a + 1
//However, there is another way to write lines of code of this form:
a = a OP y
//Where OP is a math operator. It can be shortened to:
a OP= x
//Observe:
a += 1 //This sets a to a + 1
a -= b //This sets b to a - b
a *= 0 //This multiplies a by 0
a /= 2 //This divides a by 2.
However, mathematical operators are not the only operators you are given. There are boolean operators to help you with logical circuits or logical decisions.
//The and operator takes in the left expression and right expression.
// If both are "true", then it returns true.
//This is false, because 1 returns true and 0 returns false.
//Since both are not true, && returns false.
(1 && 0)
(1 && 2) //Both numbers are "true", therefore the expression is true.
(true && false) //false
(false && false) //false
(true && true) //true
//The other important operator is "or". It returns true if one of two expressions are true.
(1 || 0) //true, since one of the values is true.
(1 || 2) //true
(true || true) //true
(false || false) //false
(true || true) //true
There are other operators as well, that you may not use as often. The "bitwise and" operator returns whether a binary bit sequence is contained in another sequence. In the technical terms, it does an "and (&&)" operation on each of the bits in both numbers. For example, say you have the number "9", which is "1001" in binary. If you want to know if that sequence contains the number "8" (1000), you can do:
//This will return 8, which means 8 is indeed a bit in 9.
(9 & 8)
//4 (00100) is not a bit inside 16 (10000) and this will return 0.
(16 & 4)
//The next operator is "bitwise or"
//which does an "or (||)' operation on each of the bits in both numbers.
//This will take 9 (1001) and match it with 3 (0011), resulting in 1011, or 11.
(9 | 3)
These two operators are also important, but not used often. They are the bitwise shift operators, << is a left shift and >> is a right shift. They shift the bits in a number to one direction.
//This takes the number 3 (00011) and shifts it three places to binary (11000), or 24.
(3 << 3)
//This takes the number 24 (11000) and shifts it three places to binary (00011), or 3.
(24 >> 3)
The last operator is "bitwise not". It returns the exact opposite of whatever is given to it. When used on a number, it will return each of the bits flipped (1 to 0, 0 to 1).
//This returns false
(!true)
//This returns true
(!false)
//This takes 9 (binary 1001) and makes it 6 (binary 0110).
(!(9))
6. Conditionals
Conditionals allow you to test if an expression meets a standard, and to execute code based on that decision. The most important conditional is called "if ... then". If evaluates whether a given expression is true or false. It if is true, it executes a block of code. If not, it executes a different block of code. For example:
This is an example of the most basic if ... then statement. The first line checks to see if the expression is true. In this case, if the variable a is equal to 5, then the if statement will execute the block of code underneath it, which sets a to 6.
if (a == 5)
{
a = 6
}
However, what happens if a does not equal 5? Then the code will not be executed. However, you can tell it to execute code if the conditions are not met. Now, if a is equal to 5, a will be set to 6. Otherwise, it will be set to 7.
if (a == 5)
{
a = 6
} else {
a = 7
}
There are many different operators you can use inside the if () statement. In fact, you can use any expression that evaluates to true (not zero) or false (zero).
//This will return true if a does not equal 5
if (a != 5) {
//Returns true if a is greater than 5
if (a > 5) {
//Returns true if a is less than 5
if (a < 5) {
//Returns true if a is greater than or equal to 5
if (a >= 5) {
//Returns true if a is less than or equal to 5
if (a <= 5) {
//Returns true because 11 is true
if (5+6) {
//Returns true of both a and b are true
if (a && b) {
//Returns true if 7.5 is greater than c
if ( ((5*3)/2) > c) {
//Always returns true no matter what
if (true) {
//Never returns true
if (false) {
Note that array comparisons have restrictions. This is invalid:
my arrayOne[3]
my arrayTwo[3]
if (arrayOne == arrayTwo) {
You must do:
if ((arrayOne[0] == arrayTwo[0]) &&
(arrayOne[1] == arrayTwo[1]) &&
(arrayOne[2] == arrayTwo[2])) {
Obviously, this would get very tedious with large arrays. You will see later on how to easily compare strings and arrays.
The if...then model of conditional switching can be brought up to another level. Pawn provides a way for you to provide multiple levels of true and false expressions.
//Example of "if...else if"
if (a == 5) {
//This code will be run if a is 5.
} else if (a < 6) {
//This code will be run if a is less than 6
} else if (a == 7) {
//This code will be run if a is 7.
} else {
//If none of the above conditions are met, this code will be run.
}
It is important to note that in the above example, each code block is not "fall through". That means each of the conditions will be checked in order, and if one is true, the code will be executed and the if statement is done. It will not execute multiple true conditions.
Lastly, there is one last type of conditional statement. It is called a "switch" statement, and it allows you to make a nicely ordered list of conditions similar to, but not as powerful as, "if...else if".
//Example of a switch statement
switch (a)
{
case 5:
{
//This code will run if a is equal to 5
}
case 6:
{
//This code will run if a is equal to 6
}
case 7:
{
//This code will run if a is equal to 7
}
default:
{
//This code will run if all other cases fail
}
}
Note that a switch is not "fall-through". If a case is true, no other cases are evaluated.
7. Looping
Looping is essential for any language. It allows you to perform the same block of code over and over, by constructing conditions on which code should be repeated.
The first and most widely used loop is called a "for loop". It takes an initial value, a condition upon which it should stop, and an incremental step. Then it executes code until it the conditions are no longer true. This lets you repeat the same block of code any number of times. Example:
/*A for loop has three parameters:
for (initial; condition; increment)
{
//your code here
}
Before the first loop executes, it runs your initial condition.
Then it begins looping your code with these steps:
1. Check if the condition is true. If so, continue. If not, stop.
2. Run the code.
3. Run the "increment" parameter.
4. Go to step 1.
*/
//Example of a for loop
new i
new sum
for (i=1; i<=10; i++)
{
sum += i
}
Explanation:
- The first parameter, i=1, sets the i variable to one. This happens before the looping starts.
- Next, the "increment" parameter is checked. This parameter is a post-increment operator, so 1 will be added to i after the entire code block is evaluated.
- Then the condition is checked. Is i<=10? It is currently 1, so it is indeed less than or equal to 10.
- Since the condition is true, sum+=i is executed. This means i is added into sum.
- The code block has finished, and i++ increments i to 2.
- Now it repeats.
- Is i<=10? Yes, it is 2. Now sum+=i runs again, and now sum is equal to 3.
- The code block has finished, and i now increments to 3.
- This happens until...
- The increment parameter sets i to 11. The condition is no longer true, and the for loop is finished.
- The sum variable now holds the number 55, which is the sum of 1 through 10.
This provides a nice way of managing arrays!
//Note: this provides a nice way to loop through arrays! Observe this function below.
sum_of_array(myArray[], size)
{
//Note: Make sure the user passes the size of the array, so we don't overflow it.
new i, sum
//This loop will start at 0 and stop right before size is reached.
//If the user passes the correct size of the array,
// the loop will be going from 0 to size-1
// This correctly matches the numbers of slots in the array.
for (i=0; i<size; i++)
{
//For every time this loop executes,
// i will be a number from 0 to size-1
//Add the value of the slot (i) in the array to sum.
//Once this is finished, sum will contain
// the sum of all slots in the array.
sum += myArray[i]
}
return sum
}
new NumberArray[4]
NumberArray[0] = 3
NumberArray[1] = 1
NumberArray[2] = 4
NumberArray[3] = 1
new answer = sum_of_array(NumberArray, 4)
//answer will be 3+1+4+1, or 9
//Here is a function to compare if one array is equal to another (i.e. a string)
bool:compare_arrays(array1[], array2[], size)
{
new i
for (i=0; i<size, i++)
{
//If a slot does not match, halt the function and return false.
if (array1[i] != array2[i])
{
return false
}
}
//If the function got to this point without returning false, return true.
return true
}
The next kind of loop is also very important, and is simpler than a for loop. Called a "while" loop, it only takes one parameter: a condition. As long as the condition is true, it keep executing code. See the above examples rewritten with while loops.
//Basic loop
new i=0
new sum
while (++i <= 10)
{
sum+=i
}
sum_of_array(array[], size)
{
new i=0, sum
//Do this loop while i is less than the size.
//i is incremented at the end of every loop.
while (i++ < size)
{
sum += array[i]
}
return sum
}
bool:compare_arrays(array1[], array2[], size)
{
new i
while (i++ < size)
{
if (array1[i] != array2[i])
{
return false
}
}
return true
}
8. Two Dimensional Arrays
In Pawn it is possible to have arrays where each slot is another array. This is very useful for storing a table of data, where the first section of slots is a row and the second section of slots is a column. Two dimensional arrays are declared like so:
//This declares an array with 50 rows and 50 columns.
new BigArray[50][50]
//this declares a floating point array with 25 rows and 10 columns.
new Float:BigArray[25][10]
Each slot in the first subset of the array becomes its own array.
new BigArray[3][3]
BigArray[0][0] = 10
BigArray[0][1] = 20
BigArray[0][2] = 30
BigArray[1][0] = 40
BigArray[1][1] = 50
BigArray[1][2] = 60
BigArray[2][0] = 70
BigArray[2][1] = 80
BigArray[2][2] = 90
Will result in BigArray looking like this:
BigArray |
0 |
1 |
2 |
0 |
10 |
20 |
30 |
1 |
40 |
50 |
60 |
2 |
70 |
80 |
90 |
Note that our old sum_of_array() function can still work! We can do:
new sum = sum_of_array(BigArray[2], 3)
Because BigArray[2] contains a second, single dimensional array, containing {7,8,9}. However, let's write a 2D sum of array function.
//This function will tally up a two dimensional array.
sum_of_table(array[][], rows, cols)
{
new i, j, sum
//Note, there is a loop inside the loop.
//This lets you go through each array inside the
// bigger array.
for (i=0; i
Note, it is also possible to store an array of strings using two dimensional arrays.
new StringList[3][] = {"Hello", "my", "friend"}
/*
StringList[0][0] through [0][5] contains "Hello"
StringList[1][0] through [1][2] contains "my"
StringList[2][0] through [1][6] contains "friend"
*/
The table for StringList will look like:
StringList |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
0 |
H |
e |
l |
l |
o |
\0 |
|
1 |
m |
y |
\0 |
|
|
|
|
2 |
f |
r |
i |
e |
n |
d |
\0 |
Comparing strings in multidimensional arrays is also similar:
if (StringList[0] == "Hello") //INVALID
if (StringList[0][0] == "Hello") //INVALID
if (equali(StringList[0], "Hello")) //Valid
9. Compiler Pre-processor Directives
Compiler directives allow you to change how your code is read. This is rather advanced and will only be run over briefly.
//To bind a symbol to a value, you can do this:
#define SYMBOL VALUE
//for example:
#define MAX_STRING 250
new String[MAX_STRING]
#define HELLO "Hello. This is a generic greeting."
new Hello[MAX_STRING] = {HELLO}
//You can also use #defines to change the flow of code.
#if defined LINUX
//This portion will be compiled if #define LINUX exists
execute_command("ls -l")
#else
//This portion will be compiled if #define LINUX does not exist
execute_command("dir")
#endif
//You can also change how much memory your script uses.
#pragma dynamic 4096
//This creates a 16K stack of memory (default).
//It is measured in blocks of 4 byte cells.
10. Conclusion
This guide should have given you a VERY brief introduction to basic Pawn programming. It is by no means comprehensive and it should not constitute the entirety of one's knowledge of Pawn. To read the official Small documentation and language guide, go this website: http://www.compuphase.com/pawn/pawn-lang.pdf (Note, this guide is very long and should be used as a reference. You may want to try the Small forums or the AMX Mod X forums). Continue to the next Section to see how to apply Small programming to the Half-Life and AMX Mod X engine!