Pawn是一个嵌入式、无类型、且易于使用的脚本语言,它能够编程成被Small虚拟机识别的二进制代码。AMX Mod X使用Small插件将脚本功能路由到Half-Life引擎,原理就是Small插件可以被Small虚拟机加载运行,并且能够使用Metamod功能,从而间接地使用Half-Life引擎的功能。(Small虚拟机使用C语言编写,Metamod平台使用C++编写)。Small脚本*.sma可以使用任何一种文本编辑器来编写,最后都要通过Small编译器来编译成Small虚拟机能够识别的二进制代码,即*.amxx文件。这个编译器由AMX Mod X官方提供。

cell是Pawn语言最小的数据单位,占4字节(32位系统)/8字节(64位系统)。

变量

Pawn是一个比较简单的脚本语言,不像C/C++这些高级编译语言,它没有很复杂的概念,例如指针、向量、结构、类、流等等,它都没有。Small脚本只有3种用来声明变量的数据类型(整型、浮点型、布尔型)。在声明变量时,如果不指定变量类型,则默认为数字类型整型。变量名的长度不能超过19个字符,且必须以字母开头。它可以包含的字符有A-Z、a-z、0-9和_(下划线)。值得注意的是,变量名是大小写敏感的,例如myvar和MyVar是两个不同的变量。我们可以使用new关键字来声明变量。

1
2
3
4
5
6
7
8
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

另外,你还可以明确地声明浮点型类型的数字变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.

最后就是布尔类型的变量了。

1
2
new bool:IsItOn        //Declares a new variable "IsItOn" which is automatically false
new bool:xyz=true //Declares a new variable "xyz" set to true

数组

Pawn支持最基础的数组。数组是一个重要的聚合数据类型,它能够让你将多个数据存储到一个变量名。在同一个数组中,每个元素的数据类型都是相同的。你可以使用中括号[]来定义数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//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]

数组也可以在定义式初始化。

1
2
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

你可以使用任何一种数据类型来定义数组。

1
2
3
4
//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

字符串

你可以已经注意到了,我们还没有讲到一个重要的数据类型,那就是字符串。在Pawn中,字符串是用数字类型的数组来表示的。数组中,每个元素代表着一个ASCII码,对应着相应的字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//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

值得注意的是,在Pawn中,数组的每个元素变量均是4字节或8字节的cell(Pawn的最小单位变量)

函数

Pawn允许你自定义函数,这对于代码重复利用是很有用的。值得注意的是,在Pawn中,每个函数都需要有return返回值。如果函数中没有return,那么函数将不会被Small虚拟机执行。

1
2
3
4
5
6
7
8
9
10
11
//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()

你还可以定义一个带参数的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//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.

你无需关心传入参数的类型,你可以传入任意数据或变量到你的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//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
}

当你传入的参数是数组是,那么你实际传入函数的是数组的引用,并不是数据的拷贝副本(只有整型、浮点型和布尔型才会传入副本,其他都是引用)。这里的引用有点儿类似C语言的指针,例如,在修改引用参数的值,会直接修改原数据。可以在定义函数参数时,添加const关键字,确保引用参数不会被修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//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
}

表达式和操作符

表达式听起来有点数学的味道。它能够通过加减乘除、以及与或之类的操作符对数据变量进行计算,并返回相应的结果。你可以通过表达式,将计算结果赋值给变量,也可以直接作为参数传入函数。

1
2
3
4
//This is the simplest expression.  It returns the number zero.
0
//However, to make it easier to read, this is also valid:
(0)

表达式不仅可以是0或false,也可以是true或者一个确切的整数、浮点数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//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.

这里有个地方需要注意一下,a++和++a,其中a++是表达式执行后a加1,而++a是表达式执行前a加1,两者区别在于表达式中a的值是否已经加1。同样的a--和--a也是一样的道理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.

当然,表达式不仅能够对数据进行加减乘除计算,还有其他布尔型计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//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

除此之外,Pawn也支持位运算的操作符

1
2
3
4
5
6
7
8
9
//This will return 8, which means 8(1000) is indeed a bit in 9(1001).
(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)

位移运算操作符<<和>>也是支持的

1
2
3
4
//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)

还有取反操作符!

1
2
3
4
5
6
//This returns false
(!true)
//This returns true
(!false)
//This takes 9 (binary 1001) and makes it 6 (binary 0110).
(!(9))

条件语句

条件语句能够让你根据表达式的情况,选择程序执行的不同路径。最常见的就是if...then模式,即如果if条件符合,那么就执行then的代码。

1
2
3
4
if (a == 5)
{
a = 6
}

当然,还有if...then...else模式

1
2
3
4
5
6
if (a == 5)
{
a = 6
} else {
a = 7
}

if的表达式,可以是能够返回true或者false的表达式,也可以是返回0或者非0的表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//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) {

对于数组的比较,不能是只比较数组的变量名,下边的这个比较方法是错误的

1
2
3
my arrayOne[3]
my arrayTwo[3]
if (arrayOne == arrayTwo) {

数组的比较应该是比较其中的元素,例如:

1
2
3
if ((arrayOne[0] == arrayTwo[0]) && 
(arrayOne[1] == arrayTwo[1]) &&
(arrayOne[2] == arrayTwo[2])) {

当然,像这种数组或者字符串(也是数组)的比较,会有相应的函数来进行比较,不需要你自己遍历每个元素来比较。

另外的,还有if...then...else if...else模式

1
2
3
4
5
6
7
8
9
10
//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.
}

值得注意的是,if...else if中,条件表达式是按照从上到下顺序执行的,但是只要有一种表达式满足条件,那么后续的表达式将不会被执行。

同理,swtich...case模式也是一样的意思。不同于C/C++,在Pawn中的switch...case中,是没有break的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//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
}
}

循环

一个中是for循环,和C语言相似,支持break和continue关键字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*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
}

通过for循环,我们可以比较简便的管理我们的数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//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
}

还有一种循环,叫做while循环,同样的,也支持break和continue关键字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//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
}

二维数组

二维数组,即一维数组中的每个元素也是数组,而不是数值。二维数组的声明方法如下:

1
2
3
4
//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]

二维数组的第一个元素,也是一个数组,可以看做是一张表格,可以对二维数组进行赋值

1
2
3
4
5
6
7
8
9
10
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
BigArray 0 1 2
0 10 20 30
1 40 50 60
2 70 80 90

接着,我们使用之前的sum_of_array()函数,来测试一下数组传参

1
new sum = sum_of_array(BigArray[2], 3)

因为BigArray[2]其实指向的是一个一维数组{70,80,90},所以此时函数依然能够正常执行。当然,我们也可以在函数参数中,定义二维数组参数:

1
2
3
4
5
6
7
8
9
10
//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.

接下来,我们再来看一下字符串数组

1
2
3
4
5
6
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"
*/

展开成表格的话,可以很清楚的看出它的数据结构:

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

比较字符串,可以简单的使用equali函数

1
2
3
if (StringList[0] == "Hello")       //INVALID
if (StringList[0][0] == "Hello") //INVALID
if (equali(StringList[0], "Hello")) //Valid

编译指令(预处理)

Pawn是支持一些预处理指令的,例如#define、#if defined、#else、#endif、#pragma等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//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.

总结

这篇文章大致说明了Pawn语言的一些基础语法,其实并不全面,你可以上网搜索Pawn语言的更多资料来深入学习。

参考文献

AMX Mod X Documentation v1.6