经过第1章和第2章的学习,我们已经对amxx插件和pawn语言也有了些基本的了解。那么我们现在就着手开发一个基础的插件吧。

插件代码结构

AMXX插件有4种主要的函数类型。第一种是public函数类型,表示该函数类型对AMXX引擎是可见的。第二种是native函数类型,该函数类型被定义在AMXX模块中,能够让small能够调用AMXX模块的功能。第三种是普通自定义函数类型,该函数类型在定义时,没有使用任何修饰符来定义类型。第四种是forward函数类型,该函数类型作为消息回调函数类型,能够被动地由消息事件来触发。

一个AMXX插件,必须有一个初始化函数plugin_init():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//This makes it so you can use the core AMX Mod X functions
//It "includes" the native definitions from includes\amxmodx.inc
#include <amxmodx>

//Declare three string variables
new PLUGIN[]="AMXX Demo"
new AUTHOR[]="BAILOPAN"
new VERSION[]="1.00"

//This is a public function.
//It is necessary to initialize your script under AMX Mod X.
//It takes no parameters, and is called right after map load.
public plugin_init()
{
//This is a function that takes three strings.
//It registers your function in AMX Mod X, and assigns some basic information.
register_plugin(PLUGIN, VERSION, AUTHOR)
}

现在,可以尝试编译上述代码。编译出来的插件,不具备任何功能,但能够在服务器控制台中,通过amxx plugins命令查看得到该插件的注册信息。

编写一个管理员命令插件

AMXX提供一个比较简单的方法来添加控制台管理员命令。我们可以在注册命令时指定该命令为控制台管理员命令。当你要注册一个命令时,你需要配置4个属性:自定义的命令内容、命令触发的函数名称、权限标识、功能描述等。

在这个示例插件中,我们将会实现amx_hp命令,能够改变玩家血量。

在开始实现这个插件之前,我们需要做两个工作:

首先,是我们需要注册一个控制台命令,并将该命令绑定到一个public函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <amxmodx>
#include <amxmisc> //This contains some useful functions
#include <fun> //This contains the function to change health

new PLUGIN[]="Change Health"
new AUTHOR[]="BAILOPAN"
new VERSION[]="1.00"

public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
register_concmd("amx_hp", "cmd_hp", ADMIN_SLAY, "<target> ")
}

public cmd_hp(id, level, cid)
{
return PLUGIN_HANDLED
}

上述代码中,有个register_concmd函数,带有4个参数,是用来注册控制台命令的。第一个参数,是玩家在控制台输入的命令。第二个参数,是命令所要触发的函数。第三个参数,是命令执行所需要的权限标识。第四个参数,是命令的描述说明。

然后,我们用public声明一个amx_hp命令的回调函数,并定义3个参数。这3个参数将会保存来自控制台命令输入的参数值。id是玩家的id号。level是玩家的权限标识。cid是本次执行命令自动生成的命令号。(注:此3个参数,均是玩家在执行命令时,系统自动传入的参数)

我们注意到这里有个PLUGIN_HANDLED函数返回值。这里就要说说了,我们在编写脚本时,经常需要使用的有2个回调函数的返回值。一个是PLUGIN_CONTINUE,表示回调函数继续执行,不会阻断回调函数链中的其他函数的执行。另一个是PLUGIN_HANDLED,表示函数到此就终止执行了,将会阻断回调函数链中的其他函数的执行。两者的差异虽然很小,但却非常重要。例如,当你注册一个自定义的命令时(非系统自带命令),你最好是用PLUGIN_HANDLED,因为你后续通常是没有其他函数要执行的。但当你注册的是say命令(系统自带命令)的话,如果还用PLUGIN_HANDLED,那么这就会阻断say命令原来的功能,也就看不到对话内容了,此时,你可以使用PLUGIN_CONTINUE,既实现你自定义的功能,也确保原功能的正常执行。另外,如task、event等这些回调函数,是不受PLUGIN_HANDLED和PLUGIN_CONTINUE影响的,即有没有返回值都一样。总之,回调函数的返回值,需要根据实际的场景来作出合适的选择。

然后,我们如何才能判断玩家是不是admin管理员,并且是否符合ADMIN_SLAY权限呢?

1
2
3
4
5
6
public cmd_hp(id, level, cid)
{
if (!cmd_access(id, level, cid, 3))
return PLUGIN_HANDLED
return PLUGIN_HANDLED
}

cmd_access()函数将会检测命令信息,包括玩家id、权限标识、命令id等,以及命令最小参数数量。这里,我们传入最小参数数量为3,是因为我们的命令在使用时应该是这样子的:amx_hp <target> <amount>,包括amx_hp本身,一共是3个参数。如果cmd_access检测失败不通过,我们这里直接返回,不再往下执行后续的代码。

接下来,我们要解决的问题是:我们将要对玩家命令传入的参数target和amount进行解析。对于amount来说比较简单,我们只要把它从字符串格式转化为数字格式即可。而对于target参数,我们这里需要特殊处理一下,因为我们这里想把将被修改血量的玩家对象target分为3种类型:

  • @CT或者@T:警察或者匪徒
  • @ALL:全部玩家
  • target id:通过cmd_target函数匹配玩家部分名称得到的玩家ID号
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public cmd_hp(id, level, cid)
{
if (!cmd_access(id, level, cid, 3))
return PLUGIN_HANDLED

new Arg1[24]
new Arg2[4]

//Get the command arguments from the console
read_argv(1, Arg1, 23)
read_argv(2, Arg2, 3)

//Convert the health from a string to a number
new Health = str_to_num(Arg2)

//Is the first character the @ symbol?
if (Arg1[0] == '@')
{
new Team = 0
//Check which team was specified.
//Note that we start from [1], this is okay
// it just means the @ isn't included
if (equali(Arg1[1], "CT"))
{
Team = 2
} else if (equali(Arg1[1], "T")) {
Team = 1
}
new players[32], num
//This function will fill the players[32] variable
// with valid player ids. num will contain the number
// of players that are valid.
get_players(players, num)
new i
for (i=0; i<num; i++)
{
if (!Team)
{
//Set this player's health
set_user_health(players[i], Health)
} else {
if (get_user_team(players[i]) == Team)
{
set_user_health(players[i], Health)
}
}
}
} else {
//finds a player id that matches the partial name given
//the 1 means that it will not target the player if he
// has immunity access
new player = cmd_target(id, Arg1, 1)
if (!player)
{
//this will print a message to the user who tried the command
//The format for this command is called "format()" style,
// where the first string formats the message according
// to any number of following parameters.
// %s means a string
// %d or %i means an integer
// %f means a float
// so "Hello %s, I am %d years old" will
// require a string and integer to follow
console_print(id, "Sorry, player %s could not be found or targetted!", Arg1)
return PLUGIN_HANDLED
} else {
set_user_health(player, Health)
}
}

return PLUGIN_HANDLED
}

那么,我们的amx_hp插件应该是这样子的:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <amxmodx>
#include <fun>

new PLUGIN[]="Change Health"
new AUTHOR[]="BAILOPAN"
new VERSION[]="1.00"

public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
register_concmd("amx_hp", "cmd_hp", ADMIN_SLAY, "<target> <hp>")
}

public cmd_hp(id, level, cid)
{
if (!cmd_access(id, level, cid, 3))
return PLUGIN_HANDLED

new Arg1[24]
new Arg2[4]

//Get the command arguments from the console
read_argv(1, Arg1, 23)
read_argv(2, Arg2, 3)

//Convert the health from a string to a number
new Health = str_to_num(Arg2)

//Is the first character the @ symbol?
if (Arg1[0] == '@')
{
new Team = 0
if (equali(Arg1[1], "CT"))
{
Team = 2
} else if (equali(Arg1[1], "T")) {
Team = 1
}
new players[32], num
get_players(players, num)
new i
for (i=0; i<num; i++)
{
if (!Team)
{
set_user_health(players[i], Health)
} else {
if (get_user_team(players[i]) == Team)
{
set_user_health(players[i], Health)
}
}
}
} else {
new player = cmd_target(id, Arg1, 1)
if (!player)
{
console_print(id, "Sorry, player %s could not be found or targetted!", Arg1)
return PLUGIN_HANDLED
} else {
set_user_health(player, Health)
}
}

return PLUGIN_HANDLED
}

CVARs配置参数

CVARs是服务端侧存储的键值对。例如,mp_startmoney是cs1.6的CVAR,用来配置玩家加入游戏时的初始化金钱。当然,你也可以在plugin_init()注册并定义自己的CVARs。接下来,我们参考mp_startmoney,来写一个自定义初始化金钱的插件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <amxmodx>

public plugin_init()
{
register_plugin("CVAR Test", "1.0", "BAILOPAN")
//set default value to 500
register_cvar("amx_startmoney", "500")
}

//this occurs when a client is put in game
public client_putinserver(id)
{
if (get_cvar_num("amx_startmoney") > 0)
{
cs_set_user_money(id, get_cvar_num("amx_startmoney"))
} else {
cs_set_user_money(id, get_cvar_num("mp_startmoney"))
}
}

这个插件可能无法正常工作,这只是个CVARs的示例,因为系统自己的初始化金钱mp_startmoney可能优先级更高。

你可以把CVARs定义为floats、numbers、strings,你可以任意修改系统自带的CVARs,如mp_startmoney、sv_gravity等。

总结

想要学习更多的脚本开发,你可以查看文档或者inc头文件,深入了解更多的native和forward函数。在头文件中,我们通常用_const来预定义一些数值和列表。用_stocks来定义一些有用的函数。用_stocks定义的函数,只会在被使用时才会编译代码,如果没有使用的话,是不会被编译器编译的,你可以放心的include这些头文件。

参考文献

AMX Mod X Documentation v1.6