cs1.6 amxx编程 入门教程 -- 作者:偶萤蛉(Oinling)

amxx插件能修改服务端的游戏规则,实现新的游戏模式.

它让所有进入服务器的玩家在新规则下进行游戏.

出名的有无限复活的死亡竞技插件,会被感染为僵尸的僵尸插件,仿制魔兽3英雄技能的魔兽插件.

amxx插件无法在客户端屏幕绘制图片,那是外挂擅长的事.

外挂擅长在屏幕画各种透视图,用于个人享乐.总有人试图用amxx绘制cf游戏HUD.

有这种需求的应该出门左转学习正经的外挂知识,而不是学习amxx插件.

让amxx做这种事实在是太强人所难了.

amxx插件无法新增新武器,CS限制了武器与子弹的品种数量.

amxx只能以某个武器为原型,更改其功能,使它看起来像新武器,但终究有各种无法实现的功能.

有这种需求的应该出门左转学习C#与Unity3D游戏开发.

amxx作用于游戏服务端,为所有玩家服务,而不是游戏客户端.

想要玩单机模式,独自享乐的,应该左转学习Unity3D游戏开发.

比起受制于CS的条条框框,最终无法完成目的,不如从零开发新游戏.

Unity3D肯定能支持你走到最后,就算没走到最后,那也是你能力不行,而不是程序不行.

本教程用于学习如何给cs1.6安装amxx开发环境,

以及编写源码之前需要了解的名词术语,一些简单的语法.

目录:

1:下载AMX Mod X

2:安装AMX Mod X

3:编写一个简单的插件源码

4:编程中的常见名词,术语,语法

4.1:模块(Module)与头文件(Header File)

4.2:注释(Comment)

4.3:字面量(Literal)

4.4:编译器指令(Compiler Directive)

4.5:符号(Symbol)

4.6:保留词(Reserved Word)

4.7:常量(Constant)

4.8:变量(Variable)

4.9:数组(Array)

4.10:枚举(Enumeration)

4.11:函数(Function)

4.12:参数(Parameter)

4.13:标签(Tag)

4.14:有用与无用的标点符号,以及运算符(Operator)

4.15:代码块(Block)

4.16:控制代码执行流程

4.17:符号作用域,全局变量(Global Variable),全局常量(Global Constant)

1:下载AMX Mod X

首先到官方网站下载amxmodx模块:www.amxmodx.org

Downloads页面能下载1.8.2或更旧的版本:downloads

Dev-Builds页面能下载1.9.0版本(点击windows图案):Dev Builds

Dev-Builds页面能下载1.10.0版本(点击this page按钮,再点击windows图案):master branch

1.8.2版基础包:AMX Mod X Base v1.8.2 Windows

1.8.2版反恐精英附加包:Counter-Strike Addon 1.8.2 Windows

1.9.0-5294版基础包:Base Package

1.9.0-5294版反恐精英附加包:Counter-Strike

1.10.0-5467版基础包:Base Package

1.10.0-5467版反恐精英附加包:Counter-Strike

元模块包(只在Download页面有):Metamod v1.21.1-am (all OSes)

amxx studio编辑器(只在Download页面有):AMX Mod X Studio v1.4.3

元模块,基础包,反恐精英附加包都是必须的.

高版本的基础包和附加包有更少的bug,更多更强的功能以及更详细的文档注释.

1.8.2以上版本自带中文,不过某些功能需要正版cs才能使用.

你需要下载正版steam,再从steam平台付费购买正版cs1.6

注意,steam有很多假的官方网站,不要下载错了.

amxx studio编辑器不是必须的,用记事本或任何文本编辑器都可以代替.

amxx studio编辑器可以让写代码更加方便.但这是英文版,中文版请上网查询.

amxx studio自带英文的amxx(pawn语言)教程.

2:安装AMX Mod X

打开cs目录下的cstrike文件夹,正常的cstrike文件夹内部是没有addons文件夹的.

盗版,魔改版除外.这里不讨论如何处理这些奇怪的CS版本.只讲正常CS如何安装AMX Mod X

先将基础包,附加包,元模块包的addons文件夹放进去,

打开cstrike/addons/amxmodx/scripting文件夹

把amxx studio包里的所有文件放进去(如果你下载了它),

开启电脑的"显示已知文件拓展名"功能.不会就上网查"windows系统如何显示文件扩展名".

用记事本或其它文本编辑器打开cstrike/liblist.gam文件

将gamedll, gamedll_linux, gamedll_osx三个参数分别改为:

gamedll "addons/metamod/dlls/metamod.dll" gamedll_linux "addons/metamod/dlls/metamod.so" gamedll_osx "addons/metamod/dlls/metamod.dylib"

打开cstrike/addons/metamod文件夹,右键新建一个txt文本文档,改名为plugins.ini

用记事本或其它文本编辑器打开它,写入以下内容并保存:

win32 addons/amxmodx/dlls/amxmodx_mm.dll

至此,安装完毕.你的CS能够运行amxx插件了.

用记事本打开这个文件,可以更改插件的一些控制台变量.

cstrike/addons/amxmodx/configs/amxx.cfg

用记事本打开这个文件,这里面写着各种插件的文件名.

cstrike/addons/amxmodx/configs/plugins.ini

在文件名左边加上 ; 符号表示关闭这个插件.

删除 ; 符号则表示开启这个插件.

当你制作了新的插件,也需要在这个文件中添加插件文件名才能运行.

amxx插件文件存放在这个文件夹:

cstrike/addons/amxmodx/plugins

当你制作了新的插件,需要把amxx文件放入这个文件夹才能运行.

amxx插件的sma源码和编译器文件存放在这个文件夹:

cstrike/addons/amxmodx/scripting

将sma源码文件拖放到amxxpc.execompile.exe编译器上就能生成amxx文件.

amxxpc.exe生成的amxx文件在scripting文件夹,

compile.exe生成的amxx文件在scripting/compile文件夹.

sma源码文件可以用记事本或amxx studio之类的文本编辑器创建或编辑.

3.编写一个简单的插件源码:

一个基本的sma文件,应该写入以下内容:

#include <amxmodx> public plugin_init() { register_plugin("插件名", "1.0.0.0", "作者"); }

这就是一个没有任何功能的插件源码.可以被编译器转变为amxx插件.

将插件放置好并开启后,在启动游戏,进入地图之后,按 ~ 按键打开控制台,发送amxx plugins即可看见该插件的运行信息.

如果你在源码中花括号范围内加入这些代码,还能在进入游戏后,将一些额外的文字发送到控制台:

server_print("[AMXX]How old are you?"); log_amxx("[AMXX]怎么老是你?");

如果你想发送中文,需将源码另存为utf8-不带有BOM的编码格式(win7或xp记事本做不到).

发送文字到控制台是检验某个代码片段有没有运行的最简单方法.

#include <amxmodx> public plugin_init() { register_plugin("插件名", "1.0.0.0", "作者"); server_print("[AMXX]How old are you?"); log_amxx("[AMXX]怎么老是你?"); }

花括号范围内的代码是从上到下运行,范围外的就不一定了.总之这段代码会在载入游戏地图之后,

先发送"[AMXX]How old are you?".

再发送"[AMXX]怎么老是你?".

载入地图之后,按下[~]按键呼出控制台,你就可以看见这两段文字.

4:编程中的常见名词,术语,语法

4.1:模块(Module)与头文件(Header File)

模块是提供特定功能的文件,制作amxx离不开它们的帮助.

制作amxx所使用的模块存放在cstrike/addons/amxmodx/modules文件夹内.

打开cstrike/addons/amxmodx/configs/modules.ini文件,可以添加,开启或关闭特定的模块.

一般来说模块会提供一些拓展名为.inc的文件,称为头文件,以介绍它们的功能.

打开cstrike/addons/amxmodx/scripting/include文件夹,可以看到各种inc文件.

用记事本打开它们就可以看见模块提供的各种功能和介绍.

制作amxx插件时必须要在源码中引用它们,才能使用模块提供的代码.

源码内一般都有#include <amxmodx>这句话,表示引用amxmodx.inc文件的所有内容.

amxmodx.inc自身也引用了大量其它头文件.这些也会一起被插件源码引用.

需要注意,一些远古的头文件不太值得浪费时间研究,比如engine.inc engine_const.inc engine_stocks.inc fun.inc

还有一些跟cs无关的头文件也不需要研究,比如esf开头的头文件,是给cs七龙珠用的.ns开头的头文件,是给物竞天择用的.还有tf,ts开头的头文件.

制作amxx插件常引用的是amxmodx.inc cstrike.inc fakemeta.inc hamsandwich.inc xs.inc

这些头文件各自还引用了其它的头文件,将这些文件和它们引用的文件--背诵并默写全文之后,基本能实现大部分复杂功能.

4.2:注释(Comment)

注释是用来解释代码功能的文本,其内容不会影响代码功能,因此可以是中文或日语等等.

注释主要有三种.

普通注释(Comment):

普通注释也可被称呼为多行注释,它以/*开始,以*/结束:

/* 这里填写多行注释的第一行内容 这里填写多行注释的第二行内容 更多内容...*/

单行注释(Line Comment):

单行注释以//开始,到一行的末尾结束:

// 这里填写单行注释的内容...

文档注释(Document Comment):

文档注释用于详细描述函数,参数,常量,变量等符号的用途和使用方法.这些概念下面会介绍.

这里先介绍文档注释.文档注释常见于inc文件中,以/**开始,以*/结束:

/** * 这里介绍函数的具体用途. * * @note 这里写注意事项.如果没有,则不需要这样的注意事项 * * @param 参数1名称 参数1介绍 * @param 参数2名称 参数2介绍 * 如果函数无参数,则不需要这样的介绍 * * @return 如果函数有返回值,在这里介绍函数的返回值. * @noreturn 如果函数无返回值,写@noreturn即可 * @error 介绍导致函数无法正常运作的原因1和后果 * 介绍导致函数无法正常运作的原因2和后果 * 如果函数必定成功运行,则不需要这样的介绍 */ native 函数(参数1, 参数2); /** * 设置当前插件的名称,版本,作者. * * @note CS的控制台对中文支持不太好,不建议用中文的插件名,版本,作者名 * * @param plugin_name 插件的名称 * @param version 版本,格式一般是"1.0.0.000000".这四个值的增大有不同含义. * 第1个值表示整体功能大改,与旧版不兼容.第2个值表示小改,向下兼容.第3个值表示修补了bug.第4个值由作者决定,控制版本. * @param author 插件作者的名称 * * @return 返回当前插件的索引号 */ native register_plugin(const plugin_name[], const version[], const author[]); /** * 发送一段文字到游戏控制台. * * @note 使用方法: * server_print("[AMXX]我今天几岁了? %d 岁或 %d 岁了", 10, 9) * * @param message 要发送的文字.其内可用%b %c %d %i %u %f %X %x %a %s %L %l %N %n %% 等格式占位符. * 格式占位符用于在一段文字中预留位置,以便使用后续参数来填充这些位置. * 上述例子中的格式占位符分别表示用后续参数填充自身. * %b能将一个值的二进制表现形式填充自身.AMX Mod X 1.8.2或低版本不存在这个格式占位符. * %c能将一个值的字符表现形式填充自身. * %d和%i完全相同,能将一个值的int32表现形式填充自身. * %u能将一个值的uint32表现形式填充自身. * %f能将一个值的float表现形式填充自身. * %X能将一个值的大写十六进制表现形式填充自身. * %x能将一个值的小写十六进制表现形式填充自身. * %a能将一个值当做字符串指针,获取该字符串填充自身. * %s能将一个数组当做字符串,填充自身. * %L和%l完全相同,需要两个参数,一个是为谁翻译,一个是用于获取翻译的字符串键值.AMX Mod X 1.8.2或低版本不存在%l. * %N能将一个值当做玩家的实体编号,将该玩家的名字,userid,steamId,游戏中所选队名填充自身.AMX Mod X 1.8.2或低版本不存在这个格式占位符. * %n能将一个值当做玩家的实体编号,将该玩家的名字填充自身.AMX Mod X 1.8.2或低版本不存在这个格式占位符. * %%能将%符号填充自身. * @param ... 按照顺序填写格式占位符所需的参数 * * @return 返回实际发送的字节数量 */ native server_print(const message[], ...);

4.3:字面量(Literal)

字面量(Literal)是源代码中一个固定值的表示方法,是常量值的静态表示.

它们左右通常有标点符号或空白字符将它们包裹.

整数(Integer)字面量:

整数是指没有小数点的数值.

123 // 用十进制表示一个数值 -123 1000_8888_9999 // 数值中间或末尾可以有下划线 -2147483648 // 这是整数能表示的最小值 2147483647 // 这是整数能表示的最大值 0b010 // 0b开头是用二进制表示一个数值,0b010等于2 0b1001_0001 // 二进制最多可以有32个位,一般可用于表示32种状态的开启或关闭 -0b10000000000000000000000000000000 // 这是最小值-2147483648 0b11111111111111111111111111111111 // 这是最大值2147483647 0xFF // 0x开头是用十六进制表示一个数值,0xFF等于255 0xABCDEF // 主要用于表示一些很长的数字,十六进制可缩短长度 -0x80000000 // 这是最小值-2147483648 0x80000000 // 这是最小值-2147483648,比最大值大n的数字,直接等于最低值+n-1 0x7FFFFFFF // 这是最大值2147483647

浮点数(Floating Point)字面量:

浮点数是指拥有小数点的数值.

-97.0 865.343 1000.0 88_89.965 888_999.0659

字符(Char)字面量:

字符也是整数,只不过用了不同的表示方法.

字符字面量则是一段被' '包裹的文本.

' ' 内不能填写中文,日文,全角字符等超过1字节的字符(每个中文在utf8编码的sma文件中,占用3个字节).

'0' // 这是字符0,等于十进制整数中的48 '1' // 这是字符1,等于十进制整数中的49 '2' // 这是字符2,等于十进制整数中的50 'A' // 这是字符A,等于十进制整数中的65 'a' // 这是字符a,等于十进制整数中的97 '^a' // 这是控制字符中的蜂鸣字符,ascii编码中的不可见字符称为控制字符 '^r' // 这是控制字符中的回车符,按下回车键可能同时输入1个回车符和1个换行符 '^n' // 这是控制字符中的换行符,按下回车键可能输入1个换行符 '^t' // 这是控制字符中的制表符,按下TAB按键可能输入1个制表符 ' ' // 这也是制表符,但是你看不见它 '^127' // 这是十进制转义字符,表示ascii编码中第127个字符,等于整数127 '^x7F' // 这是十六进制转义字符,表示ascii编码中第127个字符,等于整数127

字符串(String)字面量:

字符串是由多个字符组成的.

而字符串字面量是一段被 " " 包裹的文本.

" " 内可以是中文或日文.

如果使用了中文,日文,全角符号,想要在游戏中正确显示,需要把源码另存为utf8-不带有BOM的编码格式.

当然,游戏的控制台是不怎么能显示中文的,改编码也没用.不过其它地方是可以的.

"Hello world" "这是一段字符串" "012Aa^a^r^n^t^127^x7F" "这里面的127 8.8 a不属于整数/浮点数/字符字面量"

4.4:编译器指令(Compiler Directive)

也些人称呼为预处理指令(Preprocessor Directive).

# 开头的一行文字就是编译器指令.

如果以一个 \ 符号结尾,那么下一行也是指令的一部分.

以下是可用的指令名称:

assert define else elseif emit endif endinput endscrpt error file if include line pragma tryinclude undef

常见指令:

#include <amxmodx> /* 从这一行开始,引用cstrike.inc文件的所有内容 */ #define PLUGIN_NAME "My Plugin" /* 从这一行开始,将源码中所有的PLUGIN_NAME替换为"My Plugin" */ public plugin_init() { // register_plugin是引用了amxmodx.inc之后才能在这里使用.圆括号内的PLUGIN_NAME会被替换 register_plugin(PLUGIN_NAME, "1.0.0.0", "作者"); }

4.5:符号(Symbol)

在AMXX插件脚本中,符号是拥有特殊功能,状态,或存有值的字段.可细分为保留词与自定义符号两大类.

符号指的不是标点符号,分隔符号,运算符号,不可见的空白符号,也不包括注释,字面量,编译器指令.

符号字段通常不超过31个字符.第一个字符必须是"_ @ az AZ"四者之一.剩下的可以是"_ @ az AZ 09"五者之一.

amxx插件的源码中,通常需要先定义一个符号,然后才能使用这个符号.

由编程语言定义的符号称为保留词,而我们的自定义符号不能与保留词重名.

当你使用未定义的符号,通常会报错:某某行有未定义的某某符号.

比如,aaaa符号不存在,但你在第16行使用了aaaa符号,那么编译插件时会看见报错:undefined symbol "aaaa" on line 16.

以下的stock forward native static public new都是符号(保留词).

以下的@var test test2 test3 static_cell_var Float: static_float_var plugin_init var2 _value@32A break@都是自定义符号.

stock @var = 6; forward test(); native test2(); stock test3() { static static_cell_var, Float:static_float_var; }​ public plugin_init() { new var2, _value@32A, break@; break@ = @var + test2() + test3(); }

4.6:保留词(Reserved Word)

保留词是符号的一种,有些人会称呼为关键字或关键词(Keyword).

保留词提供了编程最基本的功能.是AMX Mod X预留的,有固定作用的符号.

以下是必需了解的保留词:

break case const continue default do else enum for forward if native new public return sizeof static stock switch while

其中有一些不需要太过在意的保留词,制作amxx插件几乎不会用到它们:

assert char defined exit goto operator sleep state tagof

它们可以细分为:

用于给自定义符号添加额外信息的说明符(Specifier):

const enum forward native new public static stock

用于控制代码执行流程的控制流语句(Control flow statement):

break case continue default do else for if return switch while goto state assert exit sleep

与数值运算相关的运算关键词(Operator keyword):

sizeof char defined tagof operator

4.7:常量(Constant)

常量是自定义符号的一种,能储存另一个常量或字面量所表示的数值.常量储存的数值一般不能更改(不绝对).

使用#define enum const声明一个符号,使该符号与某个字面量或其它常量绑定,该符号就称为常量.

声明变量通常用于提高代码的可读性,可维护性.

提高可读性:使用有意义的符号名称,代替字面量,可以让代码更易读和理解.

提高可维护性:如果需要修改常量值,只需要修改声明位置的初始化表达式,不需要在代码多处位置进行替换.

比如,一个功能是根据等级将玩家跳跃高度设置为45,50或55.那么可以在插件顶部声明这3个常量,以跳跃高度的英文作为常量名称.

开发者每次打开源码,只要看见这些名称,就知道其对应的数值可能是用于设置跳跃高度.

但是,自定义符号的名称是可以随便写的,仅观察名称不能确认实际用途.需要根据上下文,看该符号是否真的被用于做某事.

这些是用#define enum const声明常量的简单示例:

#define d宏定义锟斤拷 0b_1000_1000_1000 // d宏定义锟斤拷 是一个无地址常量(可在编译期间通过重定义进行更改) enum eA { eA_A=10, eA_B=11 } // eA eA_A eA_B 是三个无地址常量(改不了) const cA = 2184; // cA 是一个无地址常量(改不了) const cB = 2184 - 184; // cB 是一个无地址常量(改不了) public const bool:cC = true && false; // cC 是一个有地址常量 public stock const bool:cD = true || false; // cD 是一个有地址常量 static const any:cE = true; // cE 是一个有地址常量 static stock const any:cF; // cF 是一个有地址常量 new const Float:cG = 2048.0 // cG 是一个有地址常量 new stock const Float:cH; // cH 是一个有地址常量 public function(const text[]) { } // text 是一个可能有地址也可能无地址的常量

编译器内置了一些常量(头文件内定义的不算):

true // 带有bool:标签的常量值1,用于表示真,对,是,好,允许,开启,正确,非0 false // 带有bool:标签的常量值0,用于表示假,错,否,坏,禁止,关闭,错误,0 EOS // 带有_:标签的常量值0,用于表示字符串中的终止符.阅读字符串中的字符时,遇到它就该停止阅读 cellbits // 带有_:标签的常量值32,用于表示整数拥有多少个位.AMXX的脚本语言只支持int32一种数据类型,其它数据类型都是用标签模仿的 cellmin // 带有_:标签的常量值-2147483648,用于表示整数的最小值.再低就从最大值开始降低 cellmax // 带有_:标签的常量值2147483647,用于表示整数的最大值.再高就从最小值开始升高 charbits // 带有_:标签的常量值8,用于表示字符拥有多少个位 charmin // 带有_:标签的常量值0,用于表示字符的最低值.同时也是终止符 charmax // 带有_:标签的常量值254,用于表示字符的最高值.之所以不是255,可能因为255对应键盘的Delete删除键,不是一个可见字符 ucharmax // 带有_:标签的常量值16777215,意义不明,这可能是个历史遗留的计算错误 debug // 带有_:标签的常量值0 1 2之一,表示该插件被编译时使用了哪些调试选项 __Pawn // 带有_:标签的常量值,表示该插件被编译时,使用的编译器的版本 __LINE__ // 带有_:标签的常量值,表示它在源码中处于第几行.这是AMXX182或低版本没有的常量(这玩意在不同的行里表现不同,属于是变量了)

4.8:变量(Variable)

变量是自定义符号的一种,能储存字面量或另一个自定义符号所表示的数值,变量储存的数值可以被更改.

声明变量通常是为了让游戏根据不同情况做出不同反应.

比如,一个功能是根据击杀数量提高玩家等级.那么等级就是变量.

一般而言,变量名称是与其用途相关的英文单词.

但是,自定义符号的名称是可以随便写的,仅观察名称不能确认实际用途.需要根据上下文,看该符号是否真的被用于做某事.

以下是一些使用说明符声明变量的方法:

new aaa = 0b1001; stock bbb = 0xFF; forward functionA(); native functionB(); functionC(abc) { static ccc = 15; } stock functionD() { } public functionE() { }

(上面定义的变量: aaa bbb functionA functionB functionC abc ccc functionD functionE)

类似functionA functionB functionC functionD functionE这样的定义方式,只要右边有圆括号,左边拥有stock forward native public static等关键字或下方有 { } ,则是函数.只允许出现在花括号外界,函数只允许在花括号外界定义.

4.9:数组(Array)

数组是自定义符号的一种.存有多个值的变量或常量被称为数组.

数组并不仅仅只能储存数值,还可以储存其它数组.数组内储存的对象通常被称为元素.

字符串是数组的一种.其最后一个字符之后有至少一个元素的值为0.比如字符串"000"等于数组{ '0', '0', '0', 0 }{ 48, 48, 48, 0 }.

数组有静态与动态的区别,静态数组的元素只能查改,数量无法改变.动态数组的元素可以增删查改.这里只介绍静态数组.

每个元素都有一个编号,这种编号被称为索引,索引从0开始.

开发者需要做到看见"索引"就联想到数组.索引等于数据的地址.类似的还有指针,句柄,也是数据地址.

假设一个数组拥有3个元素,这三个元素的索引就是0,1,2.

在声明自定义符号时,如果在它右边加上方括号,便表示该符号是一个数组(函数除外):

public function(const text[], array[3], Float:coord[3]) { new aaa[3], Float:bbb[3]; new ccc[5] = { 1, 2, 3, 4, 5 }; new Float:ddd[5] = Float:{ 0.0, 1.0, 2.0, 3.0, 4.0 }; static const eee[] = "一个存有字符串的常量数组"; return eee; // 将function符号定义为和eee一样的数组 }

声明数组时,方括号内的数字表示它可以存放多少个元素,也就是它的尺寸或容量.如果不填,则由 = 右边的字面量决定容量.

举个例子,声明数组变量variables,储存了3个默认元素.它是一个尺寸为3的数组.

new Float:variables[] = { 6.0, 7.0, 8.0 };

如果要修改它的第一个元素,可以这么做(必须是变量类型的数组):

public function() { new Float:variables[] = { 6.0, 7.0, 8.0 }; variables[0] = 999.0; // 访问索引号为0的元素,也就是第1个元素,将它的值(6.0),改为999.0 }

如果你想在游戏中打印数组中的某个元素,可以试着这么做:

#include <amxmodx> public plugin_init() { register_plugin("插件名", "1.0.0.0", "作者"); new Float:variables[] = { 6.0, 7.0, 8.0 }; variables[0] = 999.0; // 访问索引号为0的元素,也就是第1个元素,将它的值(6.0),改为999.0 // 用第1,第2个元素填充格式占位符的位置,游戏控制台中会显示它们储存的值 server_print("[AMXX]variables[0] == %f variables[1] = %f", variables[0], variables[1]); }

在引用amxmodx.inc之后,我们用public说明符定义的plugin_init函数会在载入地图之后触发一次.并运行函数花括号范围内的代码一次.

因此,载入地图之后,按下[~]按键打开控制台,即可看见打印出来的文字.

在声明数组时,方括号的数量表示数组的维度.

三维数组的元素是二维数组.

二维数组的元素是一维数组.

一维数组的元素是单值.

字符串是一维数组.其储存的每个字符都是单值.

如下定义一个拥有2个字符串元素的二维数组:

// 1个尺寸为2的二维数组内,存放2个一维数组 new const texts[2][] = { "aaaa", "bbbb" };

定义一个三维数组:

// 尺寸为2的三维数组包含2个尺寸为3的二维数组,每个二维数组各自包含3个尺寸为4的一维数组,每个一维数组各自包含4个单值,这个三维数组总共包含24个单值 new array[2][3][4] = { { { 4, 5, 6, 7 }, { 3, 4, 5, 6 }, { 2, 3, 4, 5 } }, { { 4, 5, 6, 7 }, { 3, 4, 5, 6 }, { 2, 3, 4, 5 } } };

试着打印多维数组的某些元素:

#include <amxmodx> public plugin_init() { register_plugin("插件名", "1.0.0.0", "作者"); new const texts[][] = { "aaaa", "bbbb" }; new array[2][3][4] = { { { 4, 5, 6, 7 }, { 3, 4, 5, 6 }, { 2, 3, 4, 5 } }, { { 4, 5, 6, 7 }, { 3, 4, 5, 6 }, { 2, 3, 4, 5 } } }; // 打印texts的第2个元素,array的第2个元素的第3个元素的第4个元素. server_print("[AMXX]texts[1]=%s, array[1][2][3]=%d", texts[1], array[1][2][3]); }

4.10:枚举(Enumeration)

枚举是用enum说明符声明多个常量,分为枚举名称和枚举成员,枚举名称,枚举成员名称都是自定义的.成员数量也是自定义的.

以下Name是枚举名称,可以没有.Var1 Var2 Var3是枚举成员:

enum Name { ​ Var1 = 1, ​ Var2 = 2, ​ Var3 }

枚举有点像是数组,包含多个值.但由于它的每个值都有名字,有了名字更好辨认其用途.

没有枚举名称的枚举成员,其意义只在于它有个名字,和一般的常量没什么不同.

而有枚举名称的枚举成员,它们比其它常量多了一个特征,枚举的名称便是这些成员的标签.

数值携带的标签会改变它们的行为,如果两个不同标签的数值相互运算,编译器可能会警告或报错.这里暂不介绍标签,下面再说.

枚举名称既是常量也是标签.一般来说,它存的值是最后一个成员的值+1.

枚举成员的值可以不填,如果不填,第一个成员的值是0.一般来说,其它成员是上一个成员的值+1

4.11:函数(Function)

函数是自定义符号的一种,它是一种特殊的变量.

函数会利用参数计算出结果,称为返回值(return value),反馈给调用者.

函数的运行过程也可以对游戏造成各种影响.比如调用其它函数,更改玩家生命值.

插件依靠函数实现各种效果,最终改变服务器的游戏规则.

在源码或头文件中,花括号的外界可以用forward native static public stock说明符声明函数.

它们声明的函数被称为:预声明函数 本机函数 静态函数 公共函数 备用函数.

若不使用这些说明符,直接声明函数,并且函数名称不以 @ 开头,则称为:私有函数.

若不使用这些说明符,且函数名称以 @ 开头,则是:公共函数.

函数的花括号外,不能调用任何函数.只能声明(定义)函数.

因为外面的代码是编译插件时才会运行,游戏过程中永远不会运行.

函数的圆括号部分被称为参数列表.定义函数时,也可以定义计算所需参数.

调用函数时(将函数写在其它函数花括号内,称为调用),必须在圆括号内填入相同标签和类型的参数,否则可能导致编译器警告或报错.

如果没有定义参数标签,则默认为 _: 标签,即整数型.如果没定义参数尺寸为,则为单值类型,即只有一个值,不是数组.

预声明函数:

预声明函数还可以叫转发函数,前向函数,前置声明函数.

预声明函数是模块或插件制作的接口函数.并在头文件中声明.

这种函数没有实际的功能,因此没有花括号.插件无法调用它,但可以帮它实现具体的功能.

只要将其引用到自己的源码中,再用public说明符声明一个相同的公共函数即可.

提供预声明函数的模块或插件会在某一时刻调用这个同名的公共函数,执行你编写的代码.

比如amxmodx.inc提供的预声明函数:

/** * 将在服务器启动后被调用一次. * * @note 这是插件初始化的好地方,在这个函数里可以添加控制台变量,命令或事件钩子, * 或是定义需要反复使用的数据结构,或生成和加载其它所需的配置. * * @noreturn */ forward plugin_init();

在你的插件源码中声明同样名称的公共函数:

#include <amxmodx> // 引用相当于将amxmodx.inc的全部内容抄入此处,包括forward plugin_init();在内.因此能为其制作实际功能. public plugin_init() { // 这里面的代码,将会在服务器启动后执行一次 }

amxmodx.inc也提供了一些可以创建,执行,销毁预声明函数的本机函数:

native CreateMultiForward(const forward_handle[], stop_type, ...); native ExecuteForward(forward_handle, &ret = 0, any:...); native DestroyForward(forward_handle);

本机函数:

本机函数也是模块或插件制作的接口函数.同样在头文件中声明.

模块或插件已经实现了它的具体功能,因此头文件中的声明并没有花括号,其它插件可以引用头文件并调用这些函数.

amxmodx.inc提供了一个让插件可以创建本机函数的本机函数,可以给其它插件使用:

native register_native(const name[], const handler[], style = 0);

公共函数:

公共函数由开发者自己设计,在你的插件源码中声明公共函数,可以被其它模块或插件调用.除非有这个需求,否则不应该这么做.

有些本机函数会要求开发者为其提供固定格式的公共函数.它们会在某一刻调用这个公共函数,将一些数据输送过来:

native CreateMultiForward(const forward_handle[], stop_type, ...); native register_native(const name[], const handler[], style = 0); native register_event(const event[], const function[], const flags[], const cond[] = "", ...); native register_forward(_forwardType, const _function[], _post = 0); native HamHook:RegisterHam(Ham:function, const EntityClass[], const Callback[], Post = 0, bool:specialbot = false);

你应该像这样声明公共函数:

public 标签:函数名称(0或多个参数) { // 花括号范围内的代码,被称为函数体或代码块 // return的作用是设置函数计算结果(称为返回值),并退出函数,回到原先调用当前函数的位置. // 可以不写return和返回值,或只写return.不设定返回值则表示使用默认值:0. // 如果写了return,由于return会停止函数运行,因此,若return下方还有代码,不会被执行. return 返回值; } // 函数体出口,一旦被执行就会回到调用当前函数的位置.并以0作为函数计算结果.

备用函数:

备用函数一般是为了提供一些能反复利用的复杂功能,它在头文件中被实现具体功能.任由各种插件调用.

它不属于任何模块或插件,但是它会利用各个模块或插件提供的本机函数实现各种功能.

这是fakemeta_util.inc提供的一个备用函数:

/** * 计算指定坐标是否在指定实体的视锥范围内.它可以在一定程度上判断实体能否看见指定坐标. * * @note 点积是在2维中执行的,使指定实体的视椎体无限高. * * @param index 实体的索引 * @param point 要参与计算的坐标 * * @return 如果坐标在视椎体范围内,函数返回true(真),否则返回false(假) * @error 如果实体索引指向一个无效实体,则会在游戏控制台打印错误报告 */ stock bool:fm_is_in_viewcone(index, const Float:point[3]) { new Float:angles[3]; pev(index, pev_angles, angles); engfunc(EngFunc_MakeVectors, angles); global_get(glb_v_forward, angles); angles[2] = 0.0; new Float:origin[3], Float:diff[3], Float:norm[3]; pev(index, pev_origin, origin); xs_vec_sub(point, origin, diff); diff[2] = 0.0; xs_vec_normalize(diff, norm); new Float:dot, Float:fov; dot = xs_vec_dot(norm, angles); pev(index, pev_fov, fov); if (dot >= floatcos(fov * M_PI / 360)) return true; return false; }

一般而言,不应该修改花括号内的代码.除非这是你自己写的代码.你可以创建一个头文件,储存自己制作的备用函数.

你应该像这样制作备用函数:

stock 标签:函数名称(0或多个参数) { // 花括号范围内的代码,被称为函数体或代码块 // return的作用是设置函数计算结果(称为返回值),并退出函数,回到原先调用当前函数的位置. // 可以不写return和返回值,或只写return.不设定返回值则表示使用默认值:0. // 如果写了return,由于return会停止函数运行,因此,若return下方还有代码,不会被执行. return 返回值; } // 函数体出口,一旦被执行就会回到调用当前函数的位置.并以0作为函数计算结果.

私有函数:

私有函数由开发者自己设计,在插件源码中声明私有函数后,其它模块或插件都无法访问它.

你应该像这样制作私有函数:

标签:函数名称(0或多个参数) { // 花括号范围内的代码,被称为函数体或代码块 // return的作用是设置函数计算结果(称为返回值),并退出函数,回到原先调用当前函数的位置. // 可以不写return和返回值,或只写return.不设定返回值则表示使用默认值:0. // 如果写了return,由于return会停止函数运行,因此,若return下方还有代码,不会被执行. return 返回值; } // 函数体出口,一旦被执行就会回到调用当前函数的位置.并以0作为函数计算结果.

调用自定义的函数,打印它的计算结果(返回值):

使用这段代码,载入游戏地图后可以在控制台看见server_print打印其它函数的计算结果:

#include <amxmodx> public plugin_init() { register_plugin("插件名", "1.0.0.0", "作者"); new aaaa[3]; // 声明尺寸为3的aaaa数组变量 aaaa = cccc(); // 调用cccc函数,将cccc函数的返回值存入aaaa变量 new bbbb = dddd(); // 声明bbbb变量,调用dddd函数,将dddd的返回值存入bbbb变量 // 调用server_print函数,将一段文字发送到游戏控制台 server_print("[AMXX]aaaa=[%d %d %d], bbbb=%d", aaaa[0], aaaa[1], aaaa[2], bbbb); // 函数也可以直接用作另一个函数的参数 server_print("[AMXX]dddd=%d", dddd()); } cccc() { new ffff[3] = { 1, 2, 3 }; return ffff; } dddd() { return 12; }

静态函数:

静态函数比私有函数更加私有,它仅限当前文件可以访问,任何引用当前文件的外部文件,都无法调用它.

只需要在私有函数的名字左边加上static便能让它变成静态私有函数.

4.12:参数(Parameter)

参数是自定义符号的一种.可以理解为:参数是参与计算的数值.

声明函数时,函数的圆括号部分称为参数列表,用于声明函数所需参数.想使用这个函数就需要按设定填写符合要求的参数.

函数的内部代码可以根据参数执行不同的代码.返回不同的计算结果.

调用者传入的实际数值或符号称为实参(Actual Parameter或Argument),而函数声明中定义的参数名称被称为形参(Formal Parameter).

native函数,stock函数,static函数,私有函数都可以为形参定义默认值,表示调用该函数时可以不填写这个参数,使用默认值.

使用这段代码,在游戏中载入地图后,可以在控制台看到两次调用函数时,都给了它哪些实参.

#include <amxmodx> // 声明私有函数,并设定它需要4个参数(参数名字可自定义) // 第1个参数(参数a)被设定默认值为2,如果不填,则会使用默认值.没有默认值的参数则必须填写. function(a = 2, b, c, d) { // 打印这个函数的参数,看看外界传入了什么实参. server_print("[AMXX]a = %d, b = %d, c = %d, d = %d", a, b, c, d); // 这个函数没有设定返回值,因此使用0作为默认返回值,向下运行至"}"符号退出函数 } public plugin_init() { register_plugin(.plugin_name = "插件名", .version = "1.0.0.0", .author = "作者"); /* "."符号可以访问函数的参数,这里是分别访问c d b参数,将它们设置为4 5 3, * 唯独没有访问a参数.a参数将使用默认值2.于是,2 3 4 5就是此次调用函数所传递的实参, * 这些实参会进入函数内部,用形参a b c d表示它们.函数内部代码执行完毕后,回到此处,继续向下执行代码. */ function(.c = 4, .d = 5, .b = 3); /* 不需要"."符号也可以设置参数值,但这种情况下必须按照参数顺序填写它的值,这也是最常见的调用方法 * 从左到右设置a b c d四个参数的值 */ function(1, 2, 3, 4); }

参数按照传递方式可以分为传值与传引用.即值参数(Value Parameter)与引用参数(Reference Parameter).

一般情况下,形参是实参的副本,函数可以更改形参的值,而不会影响实参.

若想将变量传入函数,让函数更改它,可在定义函数的参数时,在参数左边加上 & 符号.像这样:

public function(&parameter) { parameter = 5; }

拥有 & 符号的单值参数被称为引用参数.而没有 & 符号的单值参数就是值参数.

另外,数组参数默认为引用参数,不允许添加 & 符号.也就是说,函数内修改数组形参,等于直接修改数组实参.

一般而言,给数组参数左边加上const说明符,表示函数绝对不会更改它,反之则必定更改它.

加上const的数组参数像这样:

public function(const parameter[]) { // 函数内部修改parameter会报错 }

简单地说,有 & 符号的单值参数,或无const说明符的数组参数,函数必定更改它们.否则,不用担心传入的实参被更改.

注意:同时拥有 & 符号的单值参数,函数能通过地址更改有地址常量的值.是危险的.

这是让自定义函数更改参数内容的示例:

#include <amxmodx> function(valueParameter, &referenceParameter, array[3], const &referenceConstant) { valueParameter = 1; // 此行为只会改变形参,不会更改实参的值 referenceParameter = 2; // 此行为等于更改实参的值 array[0] = array[1] = array[2] = 3; // 此行为等于更改实参的值 setarg(3, 0, 4); // 此行为...不太好 return valueParameter; } public plugin_init() { register_plugin(.plugin_name = "插件名", .version = "1.0.0.0", .author = "作者"); new vArg, refArg, array[3]; // 定义3个默认值为0的变量 new const refConst; // 定义1个默认值为0的常量 // 让自定义函数试着修改这些变量与常量 function(.valueParameter = vArg, .referenceParameter = refArg, .array = array, .referenceConstant = refConst); // 函数运行完毕后,把这些变量常量打印出来,看看是否依然为0呢? server_print("[AMXX]vArg = %d", vArg); server_print("[AMXX]refArg = %d", refArg); server_print("[AMXX]array = [%d %d %d]", array[0], array[1], array[2]); server_print("[AMXX]refConst = %d", refConst); }

进入游戏,载入地图,打开控制台后,你将看见以下打印内容:

[AMXX]vArg = 0 [AMXX]refArg = 2 [AMXX]array = [3 3 3] [AMXX]refConst = 4

4.13:标签(Tag)

在其它编程语言中,数值拥有值类型,比如int32整型,float单精浮点型,bool布尔型.但AMXX插件中只有cell类型,也就是int32.

所有数值,不管它看起来像什么,本质上都是一个整数.但给它添加标签,可以更改它的计算方式,模仿其它值类型的计算效果.

标签是以 : 符号结尾的符号,它的右边还会伴随其它字面量或符号.标签除了用于表示数值的用途和计算方式,还能改变数据交互的行为.

标签一般有any: bool: _: Float:四种.

它们分别表示:无标签(任意标签) 布尔标签 整数标签 浮点标签.

定义新标签的方法很多,选择任意一个自定义符号左边写上标签名和 : 符号,就算是定义了一个新标签.

除此以外,枚举名称可以作为标签使用.常常配合函数的参数使用.表示函数仅允许某一类常量作为参数.

声明变量常量时,可以在左边加上标签.能在一定程度上表示它的用途.

定义函数时,可以在函数左边加上标签.能在一定程度上表示返回值的用途.

在定义数组尺寸时,可以在尺寸左边加上标签.表示访问元素时需要使用带有相同标签的索引.

在定义数组尺寸时,可以将枚举名称当做尺寸.使其变成枚举结构体.用枚举成员访问元素,让该元素转变为枚举成员所定义的结构.

定义函数参数时,可以在参数左边加上标签,表示调用函数需要使用什么样的参数.如果把标签不同的值当做参数,除非该值是any:标签,否则会触发编译器警告或报错:标签不匹配.

在调用字面量,变量,常量时,可以在其左边加上标签.表示更换其标签.

无标签(任意标签):

定义变量常量时给它加上any:标签后,它就能与任意不同标签的数值做运算.计算方式和 _: 一样

不论数组需要带有什么标签的索引,还是函数需要带有什么标签的参数,都可用一个字面量,变量,常量,以any:去掉标签,充当索引或参数(一般是为了逃避编译器警告).

布尔标签:

一个带有bool:标签的常量变量,只允许存放truefalse作为它的值.表示真或假,是与非.false实际上等于0,true等于1,但同时,任何非0数都可以表示true.

参数带有bool:标签时,需要你填写truefalse,或带有布尔标签的常量变量作为参数.一般来说,表示开启或关闭函数的某个分支功能.

整数标签:

_: 标签表示不带有小数点的数值,定义常量变量时默认带有 _: 标签,所以不需要写.

所以它一般只被用于更换常量变量的标签.避免编译器警告.

AMXX插件的整数,字符,字符串字面量,都带有 _: 标签.

浮点标签:

AMXX插件的浮点数字面量,也就是带有小数点的数值,本质上是带有Float:标签的整数.

使用格式占位符%d即可将浮点数的实际整数值发送到游戏控制台.当然,一般还是用%f发送它的浮点值.

浮点数可以与整数做计算,但浮点数必须在左边,才能计算出正确的浮点数结果.计算时会将右边的整数转变为浮点数.

整数在左边,会与浮点数的实际整数值做计算,而这个值基本上是数十亿左右,肯定算不出你想要的值.

仅限AMXX1.8.2或低版本,AMXX1.9.0的整数不论在浮点数左边还是右边,计算结果都没问题.

尝试使用各种标签,制作自定义的标签:

试着制作一个对参数有特殊需求的函数:

#include <amxmodx> enum CPeople // 定义一个枚举结构体:人 { CP_Name[32], // 称呼 CP_Gender[8], // 性别 CP_Age, // 年龄 Float:CP_Height, // 身高 } enum CPeopleVar // 定义另一个枚举,这里用于表示"人"有哪些属性可更改 { CPV_Arg, CPV_Height, } // 该函数对外界传入的参数有特殊需求: // 参数1必须是尺寸为CPeople的一维数组 // 参数2必须是标签为CPeopleVar的单值 // 参数3必须是标签为any的单值(也就是允许任意标签) function(people[CPeople], CPeopleVar:index, any:newValue) { server_print("[AMXX]称呼: %s", people[CP_Name]); server_print("[AMXX]性别: %s", people[CP_Gender]); server_print("[AMXX]年龄: %d", people[CP_Age]); server_print("[AMXX]身高: %f", people[CP_Height]); server_print("[AMXX]正在更改 人 的%d号属性...", index); if (index == CPV_Arg) people[CP_Age] = newValue; if (index == CPV_Height) people[CP_Height] = newValue; server_print("[AMXX]已更改 人 的%d号属性:", index); server_print("[AMXX]称呼: %s", people[CP_Name]); server_print("[AMXX]性别: %s", people[CP_Gender]); server_print("[AMXX]年龄: %d", people[CP_Age]); server_print("[AMXX]身高: %f", people[CP_Height]); } public plugin_init() { register_plugin(.plugin_name = "插件名", .version = "1.0.0.0", .author = "作者"); // 声明一维数组people,原本它储存单值元素.但有了枚举结构体,它的行为改变了 new people[CPeople] = { "锟斤拷烫烫烫屯屯屯", "它们", 12, 165.0 }; function(.people = people, .index = CPV_Height, .newValue = 200.0); function(.people = people, .index = CPV_Arg, .newValue = 20); }

如果你制作了一个函数,需要一些固定的常量作为参数.应该尽量以枚举名称作为形参的标签,再以枚举成员作为实参.

这能让编译器检查参数的标签是否匹配,不匹配就会触发警告或报错.

哪怕过了几年,你已经忘了该函数需要填什么参数,看见参数的标签后就会知道该去寻找这个枚举.

4.14:有用与无用的标点符号,以及运算符(Operator)

有用的:

~!@#%^&*()_+-={}[]|\:;"'<>,.?/

无用的:

` $ 以及所有全角符号

无用符号除非被写在字符字面量、字符串字面量、注释中,否则往哪写都会导致编译器报错.

而有用符号,只要被正确使用就不会报错.

运算符:

运算符是指能够计算出值的符号,比如加减乘除.

+ - * / % = ~ ^ & | ! < > ? : ++ += -- -= *= /= %= == ^= && &= || |= != <= << <<= >= >> >>= >>> >>>= .. ... ::

运算符可以简单分为省略号运算符、一、二、三元运算符四种.表示使用它们需要多少个操作数.

:: 是个意义不明的运算符.找不到相关信息.

省略号运算符:

.. 插在左常量与右常量中间.比如1 .. 10表示1至10.它仅能用作case保留词的表达式:

case 1 .. 10: { // 代码 }

... 有两种用途,一是让编译器自动计算一维数组字面量的元素值,二是设定一个函数可以拥有不同数量的参数.

这里只介绍第二种用法:在声明本机函数 静态函数 私有函数 备用函数时,可将它作为最后一个参数使用.

表示从该位置起,函数允许接受任意数量的参数,不限制参数是单值还是数组,并且这些参数全都是引用参数.函数能更改它们的值.

本机函数内默认使用get_patam get_array get_string等函数提取这种参数数据.

备用函数 静态函数 私有函数和某类特殊本机函数,用getarg numargs等函数提取参数数据.

// 设置该函数可以接受任意数量带有Float:标签的参数. stock func(Float:...) { // 函数接收到多少个参数 new num = numargs(); // 获取第一个参数(0在这里表示第一个参数的索引号),由于getarg函数带有_:标签,需要用Float:强制更换 new Float:a = Float:getarg(0); }

一元运算符:

- ~ ! ++ -- 是需要一个左值或一个右值作为操作数的运算符.

- 的计算结果是将右值从十进制层面取反(正变负,负变正).

// 将a的值和-a的计算结果发送到控制台. new Float:a = 1.6; server_print("[AMXX]a=%f -a=%f", a, -a);

~ 的计算结果是将右值在二进制层面取反(32个位的1变0,0变1).

// 将a的值和~a的计算结果以二进制格式发送到控制台(AMXX1.8.2或更低版本不支持%b格式占位符). new a = 0b01010101; server_print("[AMXX]a=%08b ~a=%08b", a, ~a);

! 与右值计算,如果右值是0则得出1,否则得出0(计算结果带有bool:标签).

// 将a的值和!a的计算结果发送到控制台. new a = 1; server_print("[AMXX]a=%d !a=%d", a, !a);

++ 使左变量或右变量增加1.这是个神奇的运算符,任何人都该亲自试试这些代码的效果.

++ 在左则拥有运算的最高优先级,在右则拥有最低优先级.因此函数接受参数时得到的是不同的值:先运算者,函数接收到计算完毕后的值.后运算者,函数接收到计算之前的值.

// 将a的值, ++a和a++的计算结果发送到控制台. new a = 1; server_print("[AMXX]老a=%d", a); server_print("[AMXX]++a=%d", ++a); server_print("[AMXX]新a=%d", a); a = 1; server_print("[AMXX]老a=%d", a); server_print("[AMXX]a++=%d", a++); server_print("[AMXX]新a=%d", a);

-- 使左变量或右变量减少1.和 ++ 类似.

一元运算符在数值左边时,比其它任何运算符都要优先运算.如果多个一元运算符同时出现在某个值的左边,从右往左算.

二元运算符:

* / % + - << >> >>> & ^ | < <= > >= == != && || = += -= *= /= %= &= ^= |= <<= >>= >>>=

这些都是需要一个左值和一个右值才能使用的运算符,比如a = 1中的 = 就需要左边的a和右边的1.表示将1存入变量a.

* / 就是数学课上教的乘和除.在复合算式中 = 的优先级很低,而 */ 的优先级很高,比如a = 5 * 6会先计算5 * 6得出30,再计算a = 30.

- 运算符可以是一元运算符和二元运算符,作为一元运算符,它的优先级很高,超过 * / 运算符.作为二元运算符,它的优先级与 + 运算符一样.使两个值相减或相加.

% 详情上网查阅求余运算符或取模运算符,取左值除以右值的余数.

<< >> >>> 详情上网查阅位运算.在AMXX1.8.2以上版本中,可以打印它们的运算结果:

new a = 0b00000000000000000000000010001001; // 这个值等于137,只是写法不同.用%d可以显示它,但用%b更适合观看它的变化规律. server_print("[AMXX]a << 1 = %032b, a << 2 = %032b, a << 3 = %032b, a << 4 = %032b", a << 1, a << 2, a << 3, a << 4);

& ^ | 详情上网查阅位运算.在AMXX1.8.2以上版本中,可以打印它们的运算结果:

new a = 0b10001000; new b = 0b10101010; server_print("[AMXX]%08b = a", a); server_print("[AMXX]%08b = b", b); server_print("[AMXX]%08b = a & b(保留ab同位上的1)", a & b); server_print("[AMXX]%08b = a ^^ b(保留ab异位上的1)", a ^ b); server_print("[AMXX]%08b = a | b(保留ab各位上的1)", a | b); // |的优先级比另外两个低

< <= > >= == != 这些比较运算符用于比较两个值的大小关系.分别为:小于 小于等于 大于 大于等于 等于 不等于.

它们的计算结果为1或0,表示对与错,确认与否认,是与不是.因此计算结果带有bool:标签.

new bool:a = 1 < 5; server_print("[AMXX]1 < 5 = %d : 1是否小于5", a); server_print("[AMXX]5 > 5 = %d : 5是否大于5", 5 > 5); server_print("[AMXX]5 >= 5 = %d : 5是否大于等于5", 5 >= 5); server_print("[AMXX]5 != 5 = %d : 5是否不等于5", 5 != 5); // ==和!=的优先级比另外四个低 server_print("[AMXX]5 == 5 = %d : 5是否等于5", 5 == 5); // ==和!=的优先级比另外四个低

&& || 用于表示"而且"与"或者".即: "左值为真 而且 右值为真" "左值为真 或者 右值为真".

&& 左值右值都不等于0时,则计算结果为1.带有bool:标签.

|| 左值右值不等于0时,则计算结果为1.带有bool:标签.

它们常被用在if保留词的条件表达式中,判断左右两个自定义符号是否满足条件.

= += -= *= /= %= &= ^= |= <<= >>= >>>= 这些都是赋值运算符,和上面的运算符差不多,只不过计算结束后,结果会存入左变量.

三元运算符:

? : 需要3个操作数.

它的语法: a ? b : c 表示当 a 不等于0时,计算结果为 b ,否则为 c .

按优先级排列它们:

优先级1: ( ) // 括号最优先

优先级2: - ~ ! ++ -- // 前置自增自减

优先级3: * /

优先级4: %

优先级5: + -

优先级6: << >> >>>

优先级7: & | ^

优先级8: < <= > >= == !=

优先级9: && ||

优先级10: ? :

优先级11: ..

优先级12: = += -= *= /= %= &= ^= |= <<= >>= >>>=

优先级13: ...

优先级14: ++ -- // 后置自增自减

出现多个同级别运算符时,除了一元运算符和赋值运算符,都从左往右算.

4.15:代码块(Block)

被花括号包裹的代码被称呼为代码块.

一般情况下,代码块内部的代码要比 { 所在行多出一个制表符(也叫缩进).

假如 { } 所在的行,左边都是1个制表符,则内部的每一行代码要有2个制表符.

制表符也就是[Q]键左边的[Tab]键,一般情况下能展现出1至8个空格的长度.

按照这个规则编写代码,我们可以非常直观的看出一句代码属于哪个代码块:

public plugin_init() { // 进入第一层 // 由于上一个 { 所在的行,的左边拥有0个制表符,因此内部每一行代码左边至少有1个制表符 server_print("[AMXX]正在执行第一层的代码"); if (register_plugin("插件名", "1.0.0.0", "作者")); { // 进入第二层 // 由于上一个 { 所在的行,的左边拥有1个制表符,因此内部每一行代码左边至少有2个制表符 server_print("[AMXX]正在执行第二层的代码"); server_print("[AMXX]即将退出第二层"); } // 退出第二层 server_print("[AMXX]即将退出第一层"); } // 退出第一层

{ } 分别被称为代码块的入口和出口.

4.16:控制代码执行流程

函数的代码块称呼为函数体.一般情况下,函数体内的代码从左到右运行,从上到下运行.

代码块内的 ; 符号表示一句代码已经结束.虽然可以不写,但若每一句代码都以 ; 结尾,可以让编译器准确报告错误行数.

由于运算符的优先级各自不同,因此并不总是从左到右运行.

break case continue default else for if return switch while goto这些保留词也能在函数体内控制代码运行顺序.

接下来介绍if else return在函数体内的使用方法.其它的以后再说.

if else是配套的,其完整格式是:

if (/* 在这里写入条件表达式,也就是一些有可能等于true或false,非0或0的运算式 */) { // 如果if的条件表达式不等于0或true,便会进入if的代码块内部执行代码. // 执行完毕后,会退出if的代码块,跳过else的代码块,执行else代码块下方的代码 } else { // 只要if的条件表达式等于0或false,便会跳过if的代码块,进入else的代码块内部执行代码. // 执行完毕后,会退出else的代码块,执行else代码块下方的代码 // 如果没有在这里写代码的需求,可以不写else和它的代码块.else只是if的附属品. // 如果不写,而if的条件表达式等于0或false,if的代码块内容会被跳过. } // 下方的代码...可以没有

简单的说,if的圆括号内计算结果若为0,则进入else的代码块执行代码.否则进入if的代码块.

代码块内可以嵌套使用if else语句:

if (/* 条件1 */) { // 内层的if else语句,仅在外层条件计算结果不为0或false时执行 if (/* 条件2 */) { } else { // 是否执行取决于同一层内的上一个if条件. } // 下方的代码...可以没有,不管有没有,完事后继续向下执行 } // 继续向下执行

不仅是if的代码块,else的代码块同样可以嵌套使用if else语句:

else // else所属的if条件计算结果为0或false时,才会跳过if代码块,进入else内部 { if (/* 条件 */) { } else { } }

else代码块内嵌套的if else代码可以简化,应尽可能代替嵌套的做法:

else if (/* 条件 */) { } else { }

建议亲自测试变量的不同数值,查看最终触发的代码:

// 试试设定3岁,6岁,9岁,18岁. // 猜猜控制台会显示什么?测试结果是否与猜测的一致? new howOldAreYou = 3; if (howOldAreYou < 5) { server_print("[AMXX]我猜你的年龄小于5岁"); } else if (howOldAreYou < 9) { server_print("[AMXX]我猜你的年龄小于9岁"); } else if (howOldAreYou < 18) { server_print("[AMXX]我猜你的年龄小于18岁"); } else { server_print("[AMXX]我猜你的年龄大于或等于18岁"); }

return用于设置函数等于什么数值,并退出函数,返回到调用函数的位置.其完整格式是:

return 返回值; // 返回值可以是字面量,常量,变量,数组,函数,等等.

返回值必须与函数拥有相同的标签,否则会触发编译器警告.

返回值不是必须填写的.若不填写,编译会默认将函数设置为等于0.

函数体内可用有多层代码块,每个代码块内都允许使用return退出函数,一旦退出,return下面的代码就不可能执行,所以,不要把想执行的代码写在return下方.

函数体内,如果某个return带有返回值,则其它return也必须填写返回值.并且函数结尾也必须使用return 返回值;退出函数.

这是return的使用例子,在不同情况下,设定函数等于不同的数值,因此游戏中,同一个函数被打印出来的结果会不同:

#include <amxmodx> // 用static定义一个函数Function,根据param参数的不同数值,Function函数会等于不同的数值 static Function(param) { server_print("[AMXX]Function函数的param参数被外界设定为%d", param); if (param < 0) { // 若param小于0,那么设定Function函数等于-1,并退出Function函数,回到外界 return -1; } if (0 <= param && param <= 10) { // 若param等于0至10之中的数值,那么设定Function函数等于10,并退出Function函数,回到外界 return 10; } // 若param不满足上面两个条件,那么设定Function函数等于param+5,并退出Function函数,回到外界 return param + 5; } // 用static定义一个函数HowOldAreYou,根据age参数的不同数值,HowOldAreYou函数会等于不同的数值 static HowOldAreYou(age) { server_print("[AMXX]HowOldAreYou函数的age参数被外界设定为%d", age); new result[64]; if (age < 0) { result = "你的年龄是负数?"; server_print("[AMXX]满足age < 0条件,提前退出函数."); return result; // 满足age < 0的条件,退出HowOldAreYou函数.回到外界 } if (age < 5) { result = "我猜你低于5岁."; } else if (age < 10) { result = "我猜你低于10岁."; } else if (age < 15) { result = "我猜你低于15岁."; } else if (age < 20) { result = "我猜你低于20岁."; } else { result = "我猜你低于19岁."; } // 若不满足age < 0的条件,不论age等于什么,都在这里退出HowOldAreYou函数,回到外界 return result; } public plugin_init() { register_plugin("插件名", "1.0.0.0", "作者"); // 将Function的param参数设定为各种不同的数值,打印Function函数的返回值 server_print("[AMXX]Function(-999)的返回值等于: %d", Function(.param = -999)); server_print("[AMXX]Function(0)的返回值等于: %d", Function(.param = 0)); server_print("[AMXX]Function(9)的返回值等于: %d", Function(.param = 9)); server_print("[AMXX]Function(20)的返回值等于: %d", Function(.param = 20)); // 将HowOldAreYou的age参数设定为各种不同的数值,打印HowOldAreYou函数的返回值 server_print("[AMXX]怎么老是你? %s", HowOldAreYou(.age = -99)); server_print("[AMXX]怎么老是你? %s", HowOldAreYou(.age = 4)); server_print("[AMXX]怎么老是你? %s", HowOldAreYou(.age = 9)); server_print("[AMXX]怎么老是你? %s", HowOldAreYou(.age = 14)); server_print("[AMXX]怎么老是你? %s", HowOldAreYou(.age = 19)); server_print("[AMXX]怎么老是你? %s", HowOldAreYou(.age = 24)); }

以上代码的打印结果会是这样:

[AMXX]Function函数的param参数被外界设定为-999 [AMXX]Function(-999)的返回值等于: -1 [AMXX]Function函数的param参数被外界设定为0 [AMXX]Function(0)的返回值等于: 10 [AMXX]Function函数的param参数被外界设定为9 [AMXX]Function(9)的返回值等于: 10 [AMXX]Function函数的param参数被外界设定为20 [AMXX]Function(20)的返回值等于: 25 [AMXX]HowOldAreYou函数的age参数被外界设定为-99 [AMXX]满足age < 0条件,提前退出函数. [AMXX]怎么老是你? 你的年龄是负数? [AMXX]HowOldAreYou函数的age参数被外界设定为4 [AMXX]怎么老是你? 我猜你低于5岁. [AMXX]HowOldAreYou函数的age参数被外界设定为9 [AMXX]怎么老是你? 我猜你低于10岁. [AMXX]HowOldAreYou函数的age参数被外界设定为14 [AMXX]怎么老是你? 我猜你低于15岁. [AMXX]HowOldAreYou函数的age参数被外界设定为19 [AMXX]怎么老是你? 我猜你低于20岁. [AMXX]HowOldAreYou函数的age参数被外界设定为24 [AMXX]怎么老是你? 我猜你低于19岁.

4.17:符号作用域,全局变量(Global Variable),全局常量(Global Constant)

需要注意,在函数体内声明符号无法在函数体外使用.而函数体外声明的返回可以在任意函数体内使用.

因此,函数体外的自定义符号被称为全局变量,全局常量,或函数.

在函数体内自定义的符号被称为局部变量或局部常量.

全局变量和常量能实现多个函数之间的数据交流.

如果某个代码块内声明了局部变量或常量,那么它包裹的其它代码块,即便没有包裹局部变量或常量的声明,也能调用它们.

能调用变量,常量的区域,就叫做它们的作用域.

#include <amxmodx> new globalVariable; // 声明一个全局变量 public plugin_init() { register_plugin("插件名", "1.0.0.0", "作者"); new localVariable; // 声明一个局部变量 if (globalVariable == localVariable) // 全局变量globalVariable在任何函数体内都可以使用 { new localVariable2; // 声明一个更深层的局部变量 localVariable2 = localVariable; // 内层可以调用外层声明的变量localVariable } else { // 这里不能调用localVariable2,虽然深度相同,但不在同一分支 return localVariable; // 内层可以调用外层声明的变量localVariable } // 外层无法使用内层声明的局部变量localVariable2 }

在插件运行期间,全局变量永久存在.而局部变量,一旦退出代码块就会被销毁.

{ // 代码块入口 new localVariable; // 声明一个局部变量 } // 代码块出口,删除内部所有局部变量

本篇教程已结束.