前言

我们这一章节将会再深入了解amxx插件的开发,通过本篇文章,你将可以掌握pev和ham函数,可以更快捷地修改玩家生命值、判断玩家游戏状态等等。有疑问可以在文章底部留言。

FakeMeta(Pev)

在FakeMeta模块中有一种函数叫pev

pev是一个神奇的功能,他几乎可以篡改游戏中的任何数据,比如生命值,重力,固体类型,移动类型,摩擦力,坐标位置等等……

我们可以通过Pev来获取或者更改游戏中的数据

例如:

  1. 生命值是 pev_health
  2. 重力是 pev_gravity
  3. 坐标系是 pev_origin
  4. 移动向量 pev_velocity

让我们来看看如何用pev更改以及获取

示例1:将玩家生命值改为140

1
2
3
4
5
6
#include <amxmodx>
#include <fakemeta>
public test(id)
{
set_pev(id, pev_health, 140.0)
}

示例2:获取玩家坐标位置

1
2
3
4
5
6
7
8
#include <amxmodx>
#include <fakemeta>
public test(id)
{
new Float:org[3]
pev(id, pev_origin, org)
client_print(id, print_chat, "玩家坐标是: [X: %.2f], [Y: %.2f], [%.2f]", org[0], org[1], org[2])
}

示例3:设定玩家移动向量

玩家会以400.0的速度向上飞起

1
2
3
4
5
6
7
8
#include <amxmodx>
#include <fakemeta>
public test(id)
{
new Float:vel[3]
vel[2] = 400.0
set_pev(id, pev_velocity, vec)
}

不难看出,获取也就是只写pev,而修改则需要写上set_pev。这就是他们的区别

更多的PEV常量

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// 字符串型实体属性 里面装载另一个字符串
pev_string_start = 0, //无效的实体属性 主要用于遍历
pev_classname, //实体类型名称 做地图的人会知道这些 实体的类决定了实体是什么用途
pev_globalname,
pev_model, //实体模型
pev_target, //实体的目标 用于控制
pev_targetname, //实体的目标 用于控制
pev_netname, //实体的网络名称 如玩家游戏名称
pev_message, //实体的激发的文字
pev_noise, //保留备用的 可以自由存放
pev_noise1, //保留备用的 可以自由存放
pev_noise2, //保留备用的 可以自由存放
pev_noise3, //保留备用的 可以自由存放
pev_string_end, //无效的实体属性 主要用于遍历

// 实体值型实体属性 里面装载另一个实体号
pev_edict_start, //无效的实体属性 主要用于遍历
pev_chain, //不明
pev_dmg_inflictor, //被伤害的人/实体 ,攻击者
pev_enemy, //敌人 用于NPC
pev_aiment, //帽子等装饰 用于连接2个实体
pev_owner, //所有者
pev_groundentity, //脚下的实体
pev_euser1, //保留备用的 可以自由存放
pev_euser2, //保留备用的 可以自由存放
pev_euser3, //保留备用的 可以自由存放
pev_euser4, //保留备用的 可以自由存放
pev_edict_end, //无效的实体属性 主要用于遍历

// 小数型(浮点)实体属性 取得或设置实体的详细值
pev_float_start, //无效的实体属性 主要用于遍历
pev_impacttime, //碰撞时间
pev_starttime, //开始时间
pev_idealpitch, //理想的PITCH范围
pev_ideal_yaw, //理想的YAW范围
pev_pitch_speed, //朝向PITCH的速度
pev_yaw_speed, //朝向YAW的速度
pev_ltime, //时间长度
pev_nextthink, //下次心跳时间
pev_gravity, //重力
pev_friction, //摩擦速度 如滑雪
pev_frame, //当前实体播放的动作帧数
pev_animtime, //在什么时候开始播放实体动画
pev_framerate, //动画播放速度比率
pev_scale, //大小 仅用于SPR
pev_renderamt, //渲染 详见 fakemeta_util.inc fm_set_rendering
pev_health, //生命值
pev_frags, //比分
pev_takedamage, //实体是否可被伤害 参看HLSDK.INC
pev_max_health, //最大生命值
pev_teleport_time, //传输时间
pev_armortype, //护甲类型
pev_armorvalue, //护甲值
pev_dmg_take, //伤害值保存
pev_dmg_save, //伤害值保存
pev_dmg, //实体威力值 用于手雷等爆炸物
pev_dmgtime, //实体威力爆发时间 主要用于手雷爆炸物
pev_speed, //速度
pev_air_finished,
pev_pain_finished,
pev_radsuit_finished,
pev_maxspeed, //最大速度
pev_fov, //视野
pev_flFallVelocity, //坠落的速度
pev_fuser1, //保留备用的 可以自由存放
pev_fuser2, //保留备用的 可以自由存放
pev_fuser3, //保留备用的 可以自由存放
pev_fuser4, //保留备用的 可以自由存放
pev_float_end, //无效的实体属性 主要用于遍历

// 整数型实体属性 取得或设置实体的详细值
pev_int_start, //无效的实体属性 主要用于遍历
pev_fixangle, //朝向与视角朝向同步
pev_modelindex, //模型缓存序号
pev_viewmodel, //V模型
pev_weaponmodel, //P模型
pev_movetype, //移动类型 参看HLSDK.INC
pev_solid, //固态类型 参看HLSDK.INC
pev_skin, //模型的皮肤样式 如果模型有
pev_body, //子模型 如果模型有
pev_effects, //渲染亮度
pev_light_level, //亮度等级
pev_sequence, //模型的动作序列
pev_gaitsequence, //下半身动作序列 仅用于玩家
pev_rendermode, //详见 fakemeta_util.inc fm_set_rendering
pev_renderfx, //详见 fakemeta_util.inc fm_set_rendering
pev_weapons, //武器表(位)
pev_deadflag, //死亡标记 参看HLSDK.INC
pev_button, //正在被按下的按键
pev_impulse, //手电 喷图 喷射 等
pev_spawnflags, //出生标记 参看HLSDK.INC
pev_flags, //状态标记 参看HLSDK.INC
pev_colormap,
pev_team, //队伍
pev_waterlevel, //浸泡在水中的程度
pev_watertype, //浸泡在哪种液体中
pev_playerclass, //玩家角色
pev_weaponanim, //武器动作
pev_pushmsec, //推时间
pev_bInDuck, //蹲下
pev_flTimeStepSound, //脚步声
pev_flSwimTime, //游泳时间
pev_flDuckTime, //蹲下时间
pev_iStepLeft, //步伐
pev_gamestate, //游戏状态
pev_oldbuttons, //之前按下的按键
pev_groupinfo, //组类型
pev_iuser1, //保留备用的 可以自由存放
pev_iuser2, //保留备用的 可以自由存放
pev_iuser3, //保留备用的 可以自由存放
pev_iuser4, //保留备用的 可以自由存放
pev_int_end, //无效的实体属性 主要用于遍历

// 向量实体属性 取得或设置实体的向量(数组)
pev_vecarray_start, //无效的实体属性 主要用于遍历
pev_origin, //实体坐标
pev_oldorigin, //实体之前的坐标
pev_velocity, //实体速率
pev_basevelocity, //实体基础速率 用于传送带等
pev_clbasevelocity,
pev_movedir, //可能是移动目标
pev_angles, //实体朝向
pev_avelocity, //实体受速率影响时朝向变化 仅对实体有效
pev_v_angle, //实体视角朝向
pev_endpos, //开始点
pev_startpos, //结束点
pev_absmin, //实体范围 开始
pev_absmax, //实体范围 结束
pev_mins, //实体大小 开始
pev_maxs, //实体大小 结束
pev_size, //实体大小
pev_rendercolor, //详见 fakemeta_util.inc fm_set_rendering
pev_view_ofs, //瞄准视角的速率
pev_vuser1, //保留备用的 可以自由存放
pev_vuser2, //保留备用的 可以自由存放
pev_vuser3, //保留备用的 可以自由存放
pev_vuser4, //保留备用的 可以自由存放
pev_punchangle, //晃动朝向 仅对玩家有效
pev_vecarray_end, //无效的实体属性 主要用于遍历

这里并没有包含所有的pev都包含在里面。标准大全可参考fakemeta_const.inc

HAM事件钩子

即Hamsandwich,我们可以勾住(Hook)一些事件,这些事件在游戏中触发的时候,会调用我们勾住的函数,接下来我们可以从篡改或者打断一些效果。

我们可以勾住这些事件:
复活,死亡,切换武器,造成伤害,思考,碰撞,重新复活等等……

所谓思考,即think事件,表示服务端接收到客户端主动发送的数据包,如按下前进键、或者按下鼠标左键等

更多勾住方式可以参考ham_const.inc

注册Ham事件的方法:RegisterHam( Ham事件, "勾住什么实体", "调用哪个函数名" , Pre / Post )

RegisterHam有四个参数供我们填写,每个参数需要用,逗号来逐个隔开,下面我们来看看实例:

勾住玩家复活的时候

1
2
3
4
public plugin_init()
{
RegisterHam(Ham_Spawn, "player", "PlayerSpawn", 0)
}

我们逐个来解析:

  • 参数1 : Ham_Spawn
  • 参数2 : “player”
  • 参数3 : “PlayerSpawn”
  • 参数4 : 0

这些对应什么呢?

  • 参数1:代表注册复活事件,代码为 Ham_Spawn。(更多Ham事件可参考 ham_const.inc)
  • 参数2:"player",这个部分是注册需要勾住什么实体,玩家的实体名是player(需用""包含起来)
  • 参数3:"PlayerSpawn",这个部分其实就我们需要触发什么函数,填写的是触发的函数名,函数里的代码由我们自己定义 (需用""包含起来)
  • 参数4:这里涉及到先(Pre)/后(Post)部分,其中0代表Pre,1代表Post。Ham函数大部分都带有先后顺序,比如对于复活来说,Pre即复活前,Post即复活后。

那整个代码应该是怎么样的呢?

1
2
3
4
5
6
7
8
9
#include <amxmodx>
#include <hamsandwich>
public plugin_init()
{
RegisterHam(Ham_Spawn, "player", "PlayerSpawn", 1)
}
public PlayerSpawn(id)
{
}

需要注意的是:

  • 参数4 如果不填写,默认是Pre(即0)
  • 有一些Ham事件其中的index(索引)不止一个
  • 上例中的复活触发函数应该勾住复活之后即Post

多个索引的传递

如果索引不止一个呢,代码就会变成下面这样

1
2
3
public test(id, mode)
{
}

那这种一般是怎么使用的呢?

这个方法叫多重传导,一般由两种数据组成 ,如下代码:

1
2
3
4
5
test(id, 2)
public test(id, mode)
{
client_print(id, print_chat, "Mode的值是 %d", mode)
}

如上,触发了test函数,其中传递了参数2(示例中为数字2),然后test函数会通过client_print打印mode的值出来,也就是会显示:Mode的值是: 2

对于Ham来说有许多事件都拥有多个参数,比如死亡函数Ham_Killed,他拥有三个参数,分别是:

  • 受伤者(被杀死的人)
  • 攻击者(凶手)
  • 尸体碎片

小提示:参数3可以不填,默认就是0

配合Ham注册之后,整个代码就是这样:

1
2
3
4
5
6
7
8
9
#include <amxmodx>
#include <hamsandwich>
public plugin_init()
{
RegisterHam(Ham_Killed, "player", "PlayerKilled", 1)
}
public PlayerKilled(victim, attacker)
{
}

提示:victim和attacker可以按你自己的意图写上去,并不需要完全照着抄

获取数据然后存储

我们可以在游戏中获取一些信息然后存储下来,比如玩家名字或某些字符串信息

比如我们需要获取玩家名字,这里我们需要用到的是fakemeta模块中的pev_netname

我们来细致讲一下,首先我们需要知道要获取谁。通过当前已知条件可以写出这样的代码:

1
pev(id, pev_netname)

但这样是没有作用的,因为获取之后没有保存下来。那么我们应该如何保存呢

我们来看个代码例句:(获取玩家名字)

1
2
3
4
5
6
public test(id)
{
new fname[33]
pev(id, pev_netname, fname, 32)
client_print(id, print_chat, "玩家 %s 引发了函数", fname)
}

上面的例子不难看出,获取后保存的地方就是fname这个数组里面,当我们使用的时候 只需要写上fname即可。

那具体是怎么样获取的?

我们首先要分解一下代码。然后我们逐步来分析:

  1. new fname[33]创建一个临时变量(变量名是fname这是一个带数组的存储器,最大存储33字符)
  2. id指的是索引(index),意思是获取谁的名字,要和函数的索引(index)对应
  3. pev()通过fakemeta的 PEV 来获取一些数据,在括号()里填写需要获取什么
  4. pev_netname获取玩家的游戏名字(可以通过fakemeta_const.inc以查看更多)
  5. fname当我们获取后,将获取到的数据存储到什么地方(这里填写 数组的变量名 )
  6. 32我们定义的 数组最大是33的存储空间,所以在最后就必须少一位。
    当我们把所有代码组合一下,就变成如下:
1
2
new fname[33]
pev(id, pev_netname, fname, 32)

TIP:获取到的名字已经被存储到了fname变量中,你可以随时来调用他

扩展思维

当然除了获取名字,还有更多的变通方法
写法注意:创建的临时变量名,要和pev()里面的那个进行对应。
我们来看看还有哪些其他的使用方式

获取实体名

1
2
new class[33]
pev(id, pev_classname, class, 32)

获取模型路径

1
2
new jmodel[33]
pev(id, pev_model, model, 32)

更多的pev_可参考fakemeta_const.inc
*使用的变量存储是临时变量

给予玩家指定的装备

本次我们要学到的是 给予指定装备,简单说就是给玩家特定的武器

本次我们要用到一个新的模块INC,他是fakemeta_util.inc他会给我们带来很多快捷方便的stock(自定义函数),在以后我们也会经常用到。

首先定义模块代码

1
2
#include <amxmodx>
#include <fakemeta_util> //这里是新的INC

我们本次需要用到的是fakemeta_util中提供的fm_give_item这串代码

他的使用方法很简单 ,比如我们需要给玩家一个AK47,代码即:

1
fm_give_item(id, "weapon_ak47")

这次和之前的CSW_有点区别,但是区别不大,只是将CSW_变成了weapon_

现在尝试一下将:之前学到的获取名字在配合本次学到的给予武器将代码写出来

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <amxmodx>
#include <fakemeta_util>
public plugin_init()
{
regsiter_clcmd("say /giveak", "giveak_func")
}
public giveak_func(id)
{
if(!is_user_alive(id)) return //如果玩家死亡 返回
new fname[33]
pev(id, pev_netname, fname, 32) //获取名字 并存储到 fname变量中
client_print(id, print_chat, "玩家 %s 得到了Ak47", fname) //打印信息在屏幕上
fm_give_item(id, "weapon_ak47") //给予武器 Ak47
}

此时,当你在客户端控制台输入say /giveak或者按Y键输入/giveak,那么你就会得到一把AK47武器。

注意:weapon_后面接上的武器名需要注意一定是小写
死亡的时候不需要让玩家得到武器,所以我们可以添加is_user_alive再配合return来做个判断

坐标位置

坐标位置又叫坐标系,他决定了目标在地图中的所在位置

我们可以通过pev_origin来获取目标的当前坐标位置

示例:

1
2
3
4
new Float:origin[3]
pev(id, pev_origin, origin)

client_print(id, print_chat, "坐标位置: [X: %.0f], [Y: %.0f], [Z: %.0f]", origin[0], origin[1], origin[2])

也许你不太明白?没关系,继续看下面

坐标是由X,Y,Z三个轴组成的,其中Z轴代表的是上下高度。
X和Y轴决定了左右以及前后的位置

首先,我们用pev_origin来获取目标的坐标,然后存储到变量中(注意:因为不是字符串信息,所以不用些后面的33,32什么的)

1
2
new Float:org[3]
pev(id, pev_origin, org) //获取目标的坐标 存储到 org 数组变量中

这里这个数组变量就存储了坐标,因为坐标是X,Y,Z三个轴组成,所以数组中的每个值代表了 如下定义:

1
2
3
org[0] = X轴 (前后或左右)
org[1] = Y轴 (前后或左右
org[2] = Z轴 (上下)

注意:使用的变量存储是临时变量

如果我们要触发获取坐标的函数,代码应该是这样

1
2
3
4
5
6
7
8
9
10
11
12
#include <amxmodx>
#include <fakemeta>
public plugin_init()
{
register_clcmd("say /getorigin", "get_origin")
}
public get_origin(id)
{
new Float:origin[3]
pev(id, pev_origin, origin)
client_print(id, print_chat, "我的坐标位置: [X: %.0f], [Y: %.0f], [Z: %.0f]", origin[0], origin[1], origin[2])
}

伤害标记

我们可以利用伤害标记pev_takedamage来让自己免去受到伤害。通俗的说我们可以让自己无敌

这个pev_takedamage有三个返回值

1
2
3
0.0 = DAMAGE_NO  //无法受到伤害
1.0 = DAMAGE_YES //可以受到伤害
2.0 = DAMAGE_AIM //可以受到伤害(至于区别嘛 呃… 不太清楚)。一般只用到上面两个

那我们应该如何写才能无敌?

很简单,像这样

1
set_pev(id, pev_takedamage, 0.0) //让索引(index) 无法受到伤害无敌

是不是很简单呢?

配合主代码 我们结合起来可以是这样:

1
2
3
4
5
6
7
8
9
10
11
#include <amxmodx>
#include <fakemeta>
public plugin_init()
{
register_clcmd("say /god", "god_func")
}
public god_func(id)
{
client_pritn(id, print_chat, "我无敌啦")
set_pev(id, pev_takedamage, 0.0)
}

注意:
由于CS1.6自带的判断问题,你无法在下一局依然无敌。若要解决需要用到玩家思考。但是这里暂时不讲。点到为止。以后我们会学到用开关来写无敌

判断玩家连接服务器

我们也许在某些服务器见过这样的效果:

[玩家名] 连接服务器。

这是怎么实现的呢?

这里,我们会用到一个叫非注册性的函数

什么是非注册性函数?

我们知道 注册客户端命令 需要用到register_clcmd,该代码必须注册后 游戏中才会有应有的效果,但某些代码是不需要注册的,也就是在事件引发的时候自动触发。比如玩家连接游戏就会自动触发一个事件,我们通过那个事件就可以完成我们想要的效果。

如何实现?

玩家进入服务器触发client_connect函数,他不需要我们注册

示例:

1
2
3
4
#include <amxmodx>
public client_connect(id)
{
}

也许你发现了有点太简单了吧,是的!!通过这种函数我们甚至不用写plugin_init直接就可以写出函数名来触发效果

其中id代表index索引即:谁连接了服务器

那我们怎么才能写出提示所有玩家某个人进入的效果呢?
这个可以配合我们之前学到的获取名字来实现~

示例:

1
2
3
4
5
6
public client_connect(id)
{
new fname[33]
pev(id, pev_netname, fname, 32)
client_print(0, print_chat, "玩家 %s 连接服务器。", fname)
}

你可能注意到了什么不对劲的地方,client_print的索引怎么写的是0? 不是应该写id吗,嗯,看来你注意到了细节

client_print()中第一个索引0是什么意思?
id代表索引,我们现在也许都知道了,可是如果要发送给所有人的话,写id显然不是我们想要的效果,这就意味着玩家进入的时候 提示的信息只发给他自己,其他人完全不知道,所以这里写0代表给所有人发送信息

注意:因为是pev所以我们要加上fakemeta模块定义

所以整个代码是这样:

1
2
3
4
5
6
7
8
#include <amxmodx>
#include <fakemeta>
public client_connect(id) //连接服务器触发
{
new fname[33]
pev(id, pev_netname, fname, 32)
client_print(0, print_chat, "玩家 %s 连接服务器。", fname)
}

注意:若在服务器使用client_connect触发,会导致玩家连接时重复触发两次,若配合client_print会显示这样的结果:

1
2
[玩家名] 连接服务器。
[玩家名] 连接服务器。

可通过添加变量来解决,此处不详讲。

判断玩家断开连接

和判断玩家连接服务器的方法一样,我们这里只需要将函数名改为client_disconnect即可

示例:

1
2
3
4
5
6
7
8
#include <amxmodx>
#include <fakemeta>
public client_disconnect(id) //断开连接时触发
{
new fname[33]
pev(id, pev_netname, fname, 32)
client_print(0, print_chat, "玩家 %s 退出服务器。", fname)
}

判断玩家进入服务器

和判断玩家连接服务器的方法一样,我们这里只需要将函数名改为client_putinserver即可

示例:

1
2
3
4
5
6
7
8
#include <amxmodx>
#include <fakemeta>
public client_putinserver(id) //进入服务器的时候触发
{
new fname[33]
pev(id, pev_netname, fname, 32)
client_print(0, print_chat, "玩家 %s 进入服务器。", fname)
}

玩家连接和进入服务器的区别

client_connect和client_putinserver的区别是什么?

其实很简单,前者是连接服务器的瞬间就会触发,而后者是必须完全进入服务器之后才会触发,client_putinserver的触发时间段可以理解为玩家进入服务器后选择人物时/或看见MOTD时触发。

参考文献

Lv.3 进阶 — AMXX插件编写教程