the variable dimension on x is informative to the human but not necessary for the computer, since we could have declared it as x[ ]. However the nCols dimension on y is very useful, because otherwise the function would have to be written for arrays with pre-determined row sizes, and now we can write a function that will work for arrays with any row length.
This guide is designed to give you a very basic overview to fundamentals of scripting in SourcePawn. Pawn is a "scripting" language used to embed functionality in other programs. That means it is not a standalone language, like C++ or Java, and its details will differ based on the application. SourcePawn is the version of Pawn used in SourceMod.
This guide does not tell you how to write SourceMod plugins; it is intended as an overview of the syntax and semantics of the language instead. Read the separate article, Introduction to SourceMod Plugins for SourceMod API specifics.
This section is intended for non-programmers. If you're still confused, you may want to pick up a book on another language, such as PHP, Python, or Java, to get a better idea of what programming is like.
Symbols/Keywords
A symbol is a series of letters, numbers, and/or underscores, that uniquely represents something. Symbols are case-sensitive, and usually start with a letter.
There are a few reserved symbols that have special meaning. For example, if, for, and return are special constructs in the language that will explained later. They cannot be used as symbol names.
Variables
There a few important constructs you should know before you begin to script. The first is a variable. A variable is a symbol, or name, that holds data. For example, the variable "a" could hold the number "2", "16", "0", et cetera. Since a variable holds data, it also allocates the memory needed to store that data.
In addition to a name, variables have a type. A type tells the program how to interpret the data, and how much memory the data will use. Pawn has four types of data that are most commonly used:
- Integers, using the int type. Integer types may store a whole number from -2147483648 to 2147483647.
- Floats, using the float type. Float types may store fractional numbers in a huge range, though they are not as precise as integers.
- Characters, using the char type. Character types store one byte of character information, typically an ASCII character.
- Booleans, using the bool type. Booleans store either true or false.
Example of creating variables and assigning values:
int money = 5400; float percent = 67.3; char key = 'A'; bool enabled = false;Functions
The next important concept is functions. Functions are symbols or names that perform an action. When you invoke, or call them, they carry out a specific sequence of code and then return a result. There are a few types of functions, but every function is activated the same way. Example:
show(56); // Calls the "show" function, and gives it the number 56. enable(); // Calls the "enable" function with no values. bool visible = show(a); //Calls the "show" function, stores its result in a variable.Every piece of data passed to a function is called a parameter. A function can have any number of parameters (there is a "reasonable" limit of 32 in SourceMod). Parameters will be explained further in the article.
Note any text that appears after a "//" is considered a "comment" and is not actual code. There are two comment styles:
- // - Double slash, everything following on that line is ignored.
- /* */ - Multi-line comment, everything in between the asterisks is ignored. You cannot nest these.
Block Coding
The next concept is block coding. You can group code into "blocks" separated by { and }. This effectively makes one large block of code act as one statement. For example:
{ here; is; some; code; }Block coding using braces is used everywhere in programming. Blocks of code can be nested within each other. It is a good idea to adapt a consistent and readable indentation style early on to prevent spaghetti-looking code.
Pawn may seem similar to other languages, like C, but it has fundamental differences. It is not important that you immediately understand these differences, but they may be helpful if you're familiar with another language already.
- Pawn is sort of typed. Before SourceMod 1.7, Pawn did not have types. Older code and older natives will reflect this by using tags and the new keyword. As of SourceMod 1.7, we recommend that all code use types. For more information see SourcePawn Transitional Syntax.
- Pawn is not garbage collected. Pawn, as a language, has no built-in memory allocation, and thus has no garbage. If a function allocates memory, you may be responsible for freeing it.
- Pawn is not object oriented. Pawn does not have structs or objects. As of SourceMod 1.7, it has limited sugaring for treating some data types as objects, but users cannot create their own objects or classes.
- Pawn is single-threaded. As of this writing, Pawn is not thread safe.
- Pawn is compiled. Pawn is compiled to an intermediate, machine-independent code, which is stored in a ".smx" file. When loading .smx files, SourceMod translates this code to machine code for the platform and CPU it's running on.
Early language design decisions were made by ITB CompuPhase. It is designed for low-level embedded devices and is thus very small and very fast.
Pawn currently supports the following basic variable types:
- bool - true or false.
- char - an 8-bit ASCII character.
- int - a 32-bit signed integer.
- float - a 32-bit IEEE-754 floating point number.
- Handle - the base type of a SourceMod object
Other types may exist when defined in include files - for example, enums create new types for named integers, and many types derive from Handle.
Strings, currently, are 0-terminated arrays of chars. They're described a little further ahead.
Declaration
Below we include some examples of variable declarations, both valid and invalid. Keep in mind that SourcePawn has recently added new syntax, and that's what's documented below. Older code may use older declaration syntax, which is no longer supported.
int a = 5; float b = 5.0; bool c = true; bool d = false;Invalid variable usage:
int a = 5.0; // Type mismatch. 5.0 is a float. float b = 5; // Type mismatch. 5 is an integer.If a variable is not assigned upon declaration, it will be set to 0. For example:
int a; // Set to 0 float b; // Set to 0.0 bool c; // Set to falseAssignment
Variables can be re-assigned data after they are created. For example:
int a; float b; bool c; a = 5; b = 5.0; c = true;An array is a sequence of data in a list. Arrays are useful for storing multiple pieces of data in one variable, or mapping one type of data to another.
Declaration
An array is declared using brackets. Some examples of arrays:
int players[32]; // Stores 32 integers. float origin[3]; // Stores 3 floating point numbersBy default, arrays are initialized to 0. You can assign them different default values, however:
int numbers[5] = {1, 2, 3, 4, 5}; // Stores 1, 2, 3, 4, 5. float origin[3] = {1.0, 2.0, 3.0}; // Stores 1.0, 2.0, 3.0. int autoNum[3] = {1, ...}; // Stores 1, 1, 1You can leave out the array size if you're going to pre-assign data to it. For example:
int numbers[] = {1, 3, 5, 7, 9};The compiler will automatically deduce that you intended an array of size 5.
When array is declared with brackets after its name, Pawn considers that array to have a fixed size. The size of a fixed-size array is always known. Some arrays can be dynamically sized, by putting the brackets before the name. For example,
show(56); // Calls the "show" function, and gives it the number 56. enable(); // Calls the "enable" function with no values. bool visible = show(a); //Calls the "show" function, stores its result in a variable.0This creates an array of size MaxClients, which could be anything, so the size of the array is not known until the array is allocated.
Usage
Using an array is just like using a normal variable. The only difference is the array must be indexed. Indexing an array means choosing the element which you wish to use.
For example, here is an example of the above code using indexes:
show(56); // Calls the "show" function, and gives it the number 56. enable(); // Calls the "enable" function with no values. bool visible = show(a); //Calls the "show" function, stores its result in a variable.1Note that the index is what's in between the brackets. The index always starts from 0. That is, if an array has N elements, its valid indexes are from 0 to N-1. Accessing the data at these indexes works like a normal variable.
Using an incorrect index will cause an error. For example:
This may look correct, but 5 is not a valid index. The highest valid index is 4.
You can use any expression as an index. For example:
show(56); // Calls the "show" function, and gives it the number 56. enable(); // Calls the "enable" function with no values. bool visible = show(a); //Calls the "show" function, stores its result in a variable.3Expressions will be discussed in depth later in the article.
Enums are retyped integers. A new enum can be declared as follows:
show(56); // Calls the "show" function, and gives it the number 56. enable(); // Calls the "enable" function with no values. bool visible = show(a); //Calls the "show" function, stores its result in a variable.4To allow implicit conversions of enums back to integers (that is, so functions expecting a value of type 'int' accepts it as one instead of generating a warning), the enum must either be anonymous (unnamed) or must start with a lowercase character.
Strings are a construct for storing text (or even raw binary data). A string is just an array of characters, except that the final character must be 0 (called the null terminator). Without a null terminator, Pawn would not know where to stop reading the string. All strings are UTF-8 in SourcePawn.
In general, you must have an idea of how large a string will be before you store it. SourcePawn does not yet have the capability of pre-determining storage space for strings.
Usage
Strings are usually declared as arrays. For example:
show(56); // Calls the "show" function, and gives it the number 56. enable(); // Calls the "enable" function with no values. bool visible = show(a); //Calls the "show" function, stores its result in a variable.5These are equivalent to doing:
show(56); // Calls the "show" function, and gives it the number 56. enable(); // Calls the "enable" function with no values. bool visible = show(a); //Calls the "show" function, stores its result in a variable.6Although strings are rarely initialized in this manner, it is very important to remember the concept of the null terminator, which signals the end of a string. The compiler, and most SourceMod functions will automatically null-terminate for you, so it is mainly important when manipulating strings directly.
Note that a string is enclosed in double-quotes, but a character is enclosed in single quotes.
Characters
A character of text can be used in either a String or a cell. For example:
show(56); // Calls the "show" function, and gives it the number 56. enable(); // Calls the "enable" function with no values. bool visible = show(a); //Calls the "show" function, stores its result in a variable.7What you can't do is mix character arrays with strings. The internal storage is different. For example:
show(56); // Calls the "show" function, and gives it the number 56. enable(); // Calls the "enable" function with no values. bool visible = show(a); //Calls the "show" function, stores its result in a variable.8In programming, concatenation is the operation of joining character strings end-to-end. The benefit of this is to improve the readability of the source code for humans, since the compiler will continue to treat the strings as a single string. String literals can be concatenated with the ... or \ operator:
show(56); // Calls the "show" function, and gives it the number 56. enable(); // Calls the "enable" function with no values. bool visible = show(a); //Calls the "show" function, stores its result in a variable.9Functions, as stated before, are isolated blocks of code that perform an action. They can be invoked, or called, with parameters that give specific options.
There are two types of ways functions are called:
- direct call - You specifically call a function in your code.
- callback - The application calls a function in your code, as if it were an event trigger.
There are six types of functions:
- native: A direct, internal function provided by the application.
- public: A callback function that is visible to the application and other scripts.
- normal: A normal function that only you can call.
- static: The scope of this function is restricted to the current file, can be used in combination with stock.
- stock: A normal function provided by an include file. If unused, it won't be compiled.
- forward: This function is a global event provided by the application. If you implement it, it will be a callback.
All code in Pawn must exist in functions. This is in contrast to languages like PHP, Perl, and Python which let you write global code. That is because Pawn is a callback-based language: it responds to actions from a parent application, and functions must be written to handle those actions. Although our examples often contain free-floating code, this is purely for demonstration purposes. Free-floating code in our examples implies the code is part of some function.
Declaration
Unlike variables, functions do not need to be declared before you use them. Functions have two pieces, the signature and the body. The signature contains the name of your function and the parameters it will accept. The body is the contents of its code.
Example of a function:
{ here; is; some; code; }0This is a simple function. The prototype is this line:
{ here; is; some; code; }1Broken down, it means:
- int - Return value type (integer).
- AddTwoNumbers - Name of the function.
- int first - First parameter, an integer.
- int second - Second parameter, an integer.
The body is a block of code. It creates a new variable, called sum, and assigns it the value of the two parameters added together (more on expressions later). The important thing to notice is the return statement, which tells the function to end and return a value to the caller of the function. All functions return something on completion, unless they return a special type called void.
A function can accept any type of input, and it can return any non-array type.
You can, of course, pass variables to functions:
{ here; is; some; code; }2Note that cells are passed by value. That is, their value cannot be changed by the function. For example:
{ here; is; some; code; }3This code would not change the value of a. That is because a copy of the value in a is passed instead of a itself.
More examples of functions will be provided throughout the article.
Publics
Public functions are used to implement callbacks. You should not create a public function unless it is specifically implementing a callback. For example, here are two callbacks from sourcemod.inc:
{ here; is; some; code; }4To implement and receive these two events, you would write functions as such:
{ here; is; some; code; }5The public keyword signals to the parent application that it should attach the function to the appropriate forwarded event.
Natives
Natives are builtin functions provided by SourceMod. You can call them as if they were a normal function. For example, SourceMod has the following function:
{ here; is; some; code; }6It can be called like so:
{ here; is; some; code; }7Array Parameters
You can pass arrays or Strings as parameters. It is important to note that these are passed by reference. That is, rather than making a copy of the data, the data is referenced directly. There is a simple way of explaining this more concretely.
The function sets the given index in the array to a given value. When it is run on our example array, it changes index 2 to from the value 3 to 29. I.e.:
{ here; is; some; code; }9Note two important things here. First, arrays are not copied when they are passed to functions - they are passed by reference, so the view of the array is consistent at all times. Second, the brackets changed position in our function signature. This is because our function accepts an array of any size, and since we don't know the size, we must use the dynamic array syntax.
To prevent an array from being modified in a function, you can mark it as const. This will raise an error on code that attempts to modify it. For example:
int a = 5; float b = 5.0; bool c = true; bool d = false;0It is a good idea to use const in array parameters if you know the array won't be modified; this can prevent coding mistakes.
When a function takes an array as a parameter, you can also pass any indexed array element which will be interpreted as a sub-array (slice) that begins from that index. For example, if you want to get the length of a string, you can use the strlen function like so:
int a = 5; float b = 5.0; bool c = true; bool d = false;1And if you pass an indexed array element, it will be interpreted as a slice that begins from that index like so:
int a = 5; float b = 5.0; bool c = true; bool d = false;2Named Parameters
With functions that have many parameters with default values, you can call the function with named parameters to assign a value based on its name instead of which position it is in the argument list. For example:
int a = 5; float b = 5.0; bool c = true; bool d = false;3Expressions are exactly the same as they are in mathematics. They are groups of operators/symbols which evaluate to one piece of data. They are often parenthetical (comprised of parenthesis). They contain a strict "order of operations." They can contain variables, functions, numbers, and expressions themselves can be nested inside other expressions, or even passed as parameters.
The simplest expression is a single number. For example:
int a = 5; float b = 5.0; bool c = true; bool d = false;4Although expressions can return any value, they are also said to either return zero or non-zero. In that sense, zero is false, and non-zero is true. For example, -1 is true in Pawn, since it is non-zero. Do not assume negative numbers are false.
The order of operations for expressions is similar to C. PMDAS: Parenthesis, Multiplication, Division, Addition, Subtraction. Here are some example expressions:
int a = 5; float b = 5.0; bool c = true; bool d = false;5As noted, expressions can contain variables, or even functions:
int a = 5; float b = 5.0; bool c = true; bool d = false;6Note: String manipulation routines may be found in the string.inc file located in the include subdirectory. They may be browsed through the API Reference as well.
Operators
There are a few extra helpful operators in Pawn. The first set simplifies self-aggregation expressions. For example:
int a = 5; float b = 5.0; bool c = true; bool d = false;7Can be rewritten as:
int a = 5; float b = 5.0; bool c = true; bool d = false;8This is true of the following operators in Pawn:
- Four-function: *, /, -, +
- Bit-wise: |, &, ^, ~, <<, >>, >>>
Additionally, there are increment/decrement operators:
int a = 5; float b = 5.0; bool c = true; bool d = false;9Can be simplified as:
int a = 5.0; // Type mismatch. 5.0 is a float. float b = 5; // Type mismatch. 5 is an integer.0As an advanced note, the ++ or -- can come before the variable (pre-increment, pre-decrement) or after the variable (post-increment, post-decrement). The difference is in how the rest of the expression containing them sees their result.
- Pre: The variable is incremented before evaluation, and the rest of the expression sees the new value.
- Post: The variable is incremented after evaluation, and the rest of the expression sees the old value.
In other words, a++ evaluates to the value of a while ++a evaluates to the value of a + 1. In both cases a is incremented by 1.
For example:
int a = 5.0; // Type mismatch. 5.0 is a float. float b = 5; // Type mismatch. 5 is an integer.1In (1) b is assigned a's old value before it is incremented to 6, but in (2) c is assigned a's int value after it is incremented to 7.
Comparison Operators
There are six operators for comparing two values numerically, and the result is either true (non-zero) or false (zero):
- a == b - True if a and b have the same value.
- a != b - True if a and b have different values.
- a > b - True if a is greater than b
- a >= b - True if a is greater than or equal to b
- a < b - True if a is less than b
- a <= b - True if a is less than or equal to b
For example:
int a = 5.0; // Type mismatch. 5.0 is a float. float b = 5; // Type mismatch. 5 is an integer.2Note that these operators do not work on arrays or strings. That is, you cannot compare either using ==.
Truth Operators
These truth values can be combined using three boolean operators:
- a && b - True if both a and b are true. False if a or b (or both) is false.
- a || b - True if a or b (or both) is true. False if both a and b are false.
- !a - True if a is false. False if a is true.
For example:
int a = 5.0; // Type mismatch. 5.0 is a float. float b = 5; // Type mismatch. 5 is an integer.3Left/Right Values
Two important concepts are left-hand and right-hand values, or l-values and r-values. An l-value is what appears on the left-hand side of a variable assignment, and an r-value is what appears on the right side of a variable assignment.
For example:
int a = 5.0; // Type mismatch. 5.0 is a float. float b = 5; // Type mismatch. 5 is an integer.4In this example a is an l-value and 5 is an r-value.
The rules:
- Expressions are never l-values.
- Variables are both l-values and r-values.
Conditional statements let you only run code if a certain condition is matched.
If Statements
If statements test one or more conditions. For example:
int a = 5.0; // Type mismatch. 5.0 is a float. float b = 5; // Type mismatch. 5 is an integer.5They can be extended to handle more cases as well:
You can also handle the case of no expression being matched. For example:
int a = 5.0; // Type mismatch. 5.0 is a float. float b = 5; // Type mismatch. 5 is an integer.7Switch Statements
Switch statements are restricted if statements. They test one expression for a series of possible values. For example:
int a = 5.0; // Type mismatch. 5.0 is a float. float b = 5; // Type mismatch. 5 is an integer.8Unlike some other languages, switches are not fall-through. That is, multiple cases will never be run. When a case matches its code is executed, and the switch is then immediately terminated.
Loops allow you to conveniently repeat a block of code while a given condition remains true.
For Loops
For loops are loops which have four parts:
- The initialization statement - run once before the first loop.
- The condition statement - checks whether the next loop should run, including the first one. The loop terminates when this expression evaluates to false.
- The iteration statement - run after each loop.
- The body block - run each time the condition statement evaluates to true.
A simple example is a function to sum an array:
int a; // Set to 0 float b; // Set to 0.0 bool c; // Set to false0Broken down:
- int i = 0 - Creates a new variable for the loop, sets it to 0.
- i < count - Only runs the loop if i is less than count. This ensures that the loop stops reading at a certain point. In this case, we don't want to read invalid indexes in the array.
- i++ - Increments i by one after each loop. This ensures that the loop doesn't run forever; eventually i will become too big and the loop will end.
Thus, the SumArray function will loop through each valid index of the array, each time adding that value of the array into a sum. For loops are very common for processing arrays like this.
While Loops
While loops are less common than for loops but are actually the simplest possible loop. They have only two parts:
- The condition statement - checked before each loop. The loop terminates when it evaluates to false.
- The body block - run each time through the loop.
As long as the condition expression remains true, the loop will continue. Every for loop can be rewritten as a while loop:
int a; // Set to 0 float b; // Set to 0.0 bool c; // Set to false2Here is the previous for loop rewritten as a while loop:
int a; // Set to 0 float b; // Set to 0.0 bool c; // Set to false3There are also do...while loops which are even less common. These are the same as while loops except the condition check is AFTER each loop, rather than before. This means the loop is always run at least once. For example:
int a; // Set to 0 float b; // Set to 0.0 bool c; // Set to false4Loop Control
There are two cases in which you want to selectively control a loop:
- skipping one iteration of the loop but continuing as normal, or;
- breaking the loop entirely before it's finished.
Let's say you have a function which takes in an array and searches for a matching number. You want it to stop once the number is found:
int a; // Set to 0 float b; // Set to 0.0 bool c; // Set to false5Certainly, this function could simply return i instead, but the example shows how break will terminate the loop.
Similarly, the continue keyword skips an iteration of a loop. For example, let's say we wanted to sum all even numbers:
int a; // Set to 0 float b; // Set to 0.0 bool c; // Set to false6Scope refers to the visibility of code. That is, code at one level may not be "visible" to code at another level. For example:
int a; // Set to 0 float b; // Set to 0.0 bool c; // Set to false7In this example, A, B, and C exist at global scope. They can be seen by any function. However, the B in Function1 is not the same variable as the B at the global level. Instead, it is at local scope, and is thus a local variable.
Similarly, Function1 and Function2 know nothing about each other's variables.
Not only is the variable private to Function1, but it is re-created each time the function is invoked. Imagine this:
int a; // Set to 0 float b; // Set to 0.0 bool c; // Set to false8In the above example, Function1 calls itself. Of course, this is infinite recursion (a bad thing), but the idea is that each time the function runs, there is a new copy of B. When the function ends, B is destroyed, and the value is lost.
This property can be simplified by saying that a variable's scope is equal to the nesting level it is in. That is, a variable at global scope is visible globally to all functions. A variable at local scope is visible to all code blocks "beneath" its nesting level. For example:
int a; // Set to 0 float b; // Set to 0.0 bool c; // Set to false9The above code is valid since A's scope extends throughout the function. The following code, however, is not valid:
int a; float b; bool c; a = 5; b = 5.0; c = true;0Notice that B is declared in a new code block. That means B is only accessible to that code block (and all sub-blocks nested within). As soon as the code block terminates, B is no longer valid.
Dynamic arrays are arrays which don't have a hardcoded size. For example:
int a; float b; bool c; a = 5; b = 5.0; c = true;1Dynamic arrays can have any expression as their size as long as the expression evaluates to a number larger than 0. Like normal arrays, SourcePawn does not know the array size after it is created; you have to save it if you want it later.
Dynamic arrays are only valid at the local scope level, since code cannot exist globally.
Variables can be declared in more ways than simply int float or char.
static
The static keyword is available at global and local scope. It has different meanings in each.
Global static
A global static variable can only be accessed from within the same file. For example:
int a; float b; bool c; a = 5; b = 5.0; c = true;2If a plugin includes both of these files, it will not be able to use either g_value1 or g_value2. This is a simple information hiding mechanism, and is similar to declaring member variables as private in languages like C++, Java, or C#.
Local static
A local static variable is a global variable that is only visible from its local lexical scope. For example:
int a; float b; bool c; a = 5; b = 5.0; c = true;3In this example, counter is technically a global variable -- it is initialized once to -1 and is never initialized again. It does not exist on the stack. That means each time MyFunction runs, the counter variable and its storage in memory is the same.
Take this example:
int a; float b; bool c; a = 5; b = 5.0; c = true;4In this example, counter will be -1 + 5 + 6 + 10, or 20, because it persists beyond the frame of the function. Note this may pose problems for recursive functions: if your function may be recursive, then static is usually not a good idea unless your code is re-entrant.
The benefit of a local static variable is that you don't have to clutter your script with global variables. As long as the variable doesn't need to be read by another function, you can squirrel it inside the function and its persistence will be guaranteed.