[关闭]
@sogouwap 2017-06-04T17:12:07.000000Z 字数 10534 阅读 6647

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


前言

本教程由小灰编写,这是进阶难度的教程
建议在对普通难度学习之后再阅读
我们的QQ群:139659650


快速切换

点击上述列表中 未打钩 旁边的切换按钮,以切换学习等级。

常见问题

这个教程学习下来需要多少时间?
你需要花一些时间直到你掌握它,并且每天都要坚持编写代码。
根据理解力的不同,需要的时间也不一样(三个月,六个月,或一个星期)

我需要准备什么样的系统?

你需要 Windows 7/8/10 或者 Windows XP 系统。



一、FakeMeta(Pev)

FakeMeta 模块中有一种代码叫 Pev

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

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

例如:

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

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

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

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

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

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

示例(3):设定玩家移动向量(玩家会以 400.0 的速度向上飞起)

  1. #include <amxmodx>
  2. #include <fakemeta>
  3. public test(id)
  4. {
  5. new Float:vel[3]
  6. vel[2] = 400.0
  7. set_pev(id, pev_velocity, vec)
  8. }
不难看出,获取也就是只写 `pev` ,而修改则需要写上 `set_pev` 。这就是他们的区别

更多的PEV

pev类型 到底有哪些呢?(下面的代码摘于:百度贴吧)

  1. 2.1.1 字符串型实体属性 里面装载另一个字符串
  2. pev_string_start = 0, //无效的实体属性 主要用于遍历
  3. pev_classname, 实体类型名称 做地图的人会知道这些 实体的类决定了实体是什么用途
  4. pev_globalname,
  5. pev_model, 实体模型
  6. pev_target, 实体的目标 用于控制
  7. pev_targetname, 实体的目标 用于控制
  8. pev_netname, 实体的网络名称 如玩家游戏名称
  9. pev_message, 实体的激发的文字
  10. pev_noise, 保留备用的 可以自由存放
  11. pev_noise1, 保留备用的 可以自由存放
  12. pev_noise2, 保留备用的 可以自由存放
  13. pev_noise3, 保留备用的 可以自由存放
  14. pev_string_end, //无效的实体属性 主要用于遍历
  15. 2.1.2 实体值型实体属性 里面装载另一个实体号
  16. pev_edict_start, //无效的实体属性 主要用于遍历
  17. pev_chain, //不明
  18. pev_dmg_inflictor, //被伤害的人/实体 ,攻击者
  19. pev_enemy, //敌人 用于NPC
  20. pev_aiment, //帽子等装饰 用于连接2个实体
  21. pev_owner, //所有者
  22. pev_groundentity, //脚下的实体
  23. pev_euser1, //保留备用的 可以自由存放
  24. pev_euser2, //保留备用的 可以自由存放
  25. pev_euser3, //保留备用的 可以自由存放
  26. pev_euser4, //保留备用的 可以自由存放
  27. pev_edict_end, //无效的实体属性 主要用于遍历
  28. 2.1.3 小数型(浮点)实体属性 取得或设置实体的详细值
  29. pev_float_start, //无效的实体属性 主要用于遍历
  30. pev_impacttime, //碰撞时间
  31. pev_starttime, //开始时间
  32. pev_idealpitch, //理想的PITCH范围
  33. pev_ideal_yaw, //理想的YAW范围
  34. pev_pitch_speed, //朝向PITCH的速度
  35. pev_yaw_speed, //朝向YAW的速度
  36. pev_ltime, //时间长度
  37. pev_nextthink, //下次心跳时间
  38. pev_gravity, //重力
  39. pev_friction, //摩擦速度 如滑雪
  40. pev_frame, //当前实体播放的动作帧数
  41. pev_animtime, //在什么时候开始播放实体动画
  42. pev_framerate, //动画播放速度比率
  43. pev_scale, //大小 仅用于SPR
  44. pev_renderamt, //渲染 详见 fakemeta_util.inc fm_set_rendering
  45. pev_health, //生命值
  46. pev_frags, //比分
  47. pev_takedamage, //实体是否可被伤害 参看HLSDK.INC
  48. pev_max_health, //最大生命值
  49. pev_teleport_time, //传输时间
  50. pev_armortype, //护甲类型
  51. pev_armorvalue, //护甲值
  52. pev_dmg_take, //伤害值保存
  53. pev_dmg_save, //伤害值保存
  54. pev_dmg, //实体威力值 用于手雷等爆炸物
  55. pev_dmgtime, //实体威力爆发时间 主要用于手雷爆炸物
  56. pev_speed, //速度
  57. pev_air_finished,
  58. pev_pain_finished,
  59. pev_radsuit_finished,
  60. pev_maxspeed, //最大速度
  61. pev_fov, //视野
  62. pev_flFallVelocity, //坠落的速度
  63. pev_fuser1, //保留备用的 可以自由存放
  64. pev_fuser2, //保留备用的 可以自由存放
  65. pev_fuser3, //保留备用的 可以自由存放
  66. pev_fuser4, //保留备用的 可以自由存放
  67. pev_float_end, //无效的实体属性 主要用于遍历
  68. 2.1.4 整数型实体属性 取得或设置实体的详细值
  69. pev_int_start, //无效的实体属性 主要用于遍历
  70. pev_fixangle, //朝向与视角朝向同步
  71. pev_modelindex, //模型缓存序号
  72. pev_viewmodel, //V模型
  73. pev_weaponmodel, //P模型
  74. pev_movetype, //移动类型 参看HLSDK.INC
  75. pev_solid, //固态类型 参看HLSDK.INC
  76. pev_skin, //模型的皮肤样式 如果模型有
  77. pev_body, //子模型 如果模型有
  78. pev_effects, //渲染亮度
  79. pev_light_level, //亮度等级
  80. pev_sequence, //模型的动作序列
  81. pev_gaitsequence, //下半身动作序列 仅用于玩家
  82. pev_rendermode, //详见 fakemeta_util.inc fm_set_rendering
  83. pev_renderfx, //详见 fakemeta_util.inc fm_set_rendering
  84. pev_weapons, //武器表(位)
  85. pev_deadflag, //死亡标记 参看HLSDK.INC
  86. pev_button, //正在被按下的按键
  87. pev_impulse, //手电 喷图 喷射 等
  88. pev_spawnflags, //出生标记 参看HLSDK.INC
  89. pev_flags, //状态标记 参看HLSDK.INC
  90. pev_colormap,
  91. pev_team, //队伍
  92. pev_waterlevel, //浸泡在水中的程度
  93. pev_watertype, //浸泡在哪种液体中
  94. pev_playerclass, //玩家角色
  95. pev_weaponanim, //武器动作
  96. pev_pushmsec, //推时间
  97. pev_bInDuck, //蹲下
  98. pev_flTimeStepSound, //脚步声
  99. pev_flSwimTime, //游泳时间
  100. pev_flDuckTime, //蹲下时间
  101. pev_iStepLeft, //步伐
  102. pev_gamestate, //游戏状态
  103. pev_oldbuttons, //之前按下的按键
  104. pev_groupinfo, //组类型
  105. pev_iuser1, //保留备用的 可以自由存放
  106. pev_iuser2, //保留备用的 可以自由存放
  107. pev_iuser3, //保留备用的 可以自由存放
  108. pev_iuser4, //保留备用的 可以自由存放
  109. pev_int_end, //无效的实体属性 主要用于遍历
  110. 2.1.5 向量实体属性 取得或设置实体的向量(数组)
  111. pev_vecarray_start, //无效的实体属性 主要用于遍历
  112. pev_origin, //实体坐标
  113. pev_oldorigin, //实体之前的坐标
  114. pev_velocity, //实体速率
  115. pev_basevelocity, //实体基础速率 用于传送带等
  116. pev_clbasevelocity,
  117. pev_movedir, //可能是移动目标
  118. pev_angles, //实体朝向
  119. pev_avelocity, //实体受速率影响时朝向变化 仅对实体有效
  120. pev_v_angle, //实体视角朝向
  121. pev_endpos, //开始点
  122. pev_startpos, //结束点
  123. pev_absmin, //实体范围 开始
  124. pev_absmax, //实体范围 结束
  125. pev_mins, //实体大小 开始
  126. pev_maxs, //实体大小 结束
  127. pev_size, //实体大小
  128. pev_rendercolor, //详见 fakemeta_util.inc fm_set_rendering
  129. pev_view_ofs, //瞄准视角的速率
  130. pev_vuser1, //保留备用的 可以自由存放
  131. pev_vuser2, //保留备用的 可以自由存放
  132. pev_vuser3, //保留备用的 可以自由存放
  133. pev_vuser4, //保留备用的 可以自由存放
  134. pev_punchangle, //晃动朝向 仅对玩家有效
  135. pev_vecarray_end, //无效的实体属性 主要用于遍历

可能不是所有的 pev类 都包含在里面。标准大全可参考 fakemeta_const.inc


二、Hamsandwich(HAM事件钩子)

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

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

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

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

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

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

我们逐个来解析:

参数1 : Ham_Spawn
参数2 : "player"
参数3 : "PlayerSpawn"
参数4 : 0

这些对应什么呢?

1) 参数1 代表注册 复活 事件,代码为 Ham_Spawn。(更多Ham事件可参考 ham_const.inc

2) 参数2 "player",这个部分是注册需要勾住什么实体,玩家的实体名是 player (需用 "" 包含起来)

3) 参数3 "PlayerSpawn",这个部分其实就我们需要触发什么函数,填写的是 触发的函数名,函数里的代码由我们自己定义 (需用 "" 包含起来)
4) 参数4:这里涉及到 先(Pre) / 后(Post) 部分,其中0代表Pre,1代表Post 。Ham函数大部分都带有先后顺序,比如对于复活来说,Pre即复活前,Post即复活后


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

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

需要注意的是

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

多个索引的传递

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

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

那这种一般是怎么使用?

这个方法叫多重传导,一般由两种数据组成 ,如下代码:
  1. test(id, 2)
  2. public test(id, mode)
  3. {
  4. client_print(id, print_chat, "Mode的值是 %d", mode)
  5. }

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


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

  1. > 受伤者(被杀死的人)
  2. > 攻击者(凶手)
  3. > 尸体碎片

如果要用函数表达出来就是这样:

  1. public PlayerKilled(victim, attacker)
  2. {
  3. }

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

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

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

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

三、获取数据然后存储

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

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

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

pev(id, pev_netname)

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

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

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

上面的例子不难看出,获取后保存的地方就是 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. new fname[33]
  2. pev(id, pev_netname, fname, 32)

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

扩展思维

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

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

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

四、给予玩家指定的装备

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

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

首先定义模块代码

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

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

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

  1. fm_give_item(id, "weapon_ak47")

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

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

示例:

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

需要注意的地方

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

五、坐标位置

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

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

示例:

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

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

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

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

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

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

  1. org[0] = X (前后或左右)
  2. org[1] = Y (前后或左右
  3. org[2] = Z (上下)
  • 注意:使用的 变量存储 是临时变量

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

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

六、伤害标记

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

这个 pev_takedamage有三个返回值

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

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

很简单,像这样

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

是不是很简单呢?

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

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

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

七、判断玩家连接服务器

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

[玩家名] 连接服务器。

这是怎么实现的呢?

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

什么是非注册性函数?

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

如何实现?

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

示例:

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

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

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

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

示例:

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

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

client_print(0, 中的索引0是什么意思?

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

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

所以整个代码是这样:

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

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

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

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

八、判断玩家断开连接

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

示例:

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

八、判断玩家进入服务器

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

示例:

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

抱有疑惑?

client_connect 和 client_putinserver 的区别是什么?

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

Tutorial End -进阶教程结束

你可以点击这里进入:上一章(普通) / 下一章(困难)

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注