前言

我们这一章节将会再深入了解amxx插件的开发,通过本篇文章,你将可以掌握游戏实体、以及更多pev和ham的内容。有疑问可以在文章底部留言。

修改目标生命值

我们可以通过pev来修改玩家生命值

示例:

1
set_pev(id, pev_health, 120.0)

我们可以看出来,上面这个代码可以将目标的血量改成指定的值

仅仅是这样还不够,我们大部分需要的效果是加血(在当前生命值的情况下添加指定值)

思路:首先我们需要获取玩家生命值,然后再通过获取到的值来进行添加生命值

获取生命值:

1
2
new Float:hp
pev(id, pev_health, hp) //获取生命值后存储到 hp 浮点变量中

上例中存储到hp变量之后,那么此时要做的就是设定生命值=(当前生命值 + 添加的值)

即:

1
2
3
new Float:hp
pev(id, pev_health, hp)
set_pev(id, pev_health, hp + 40.0) //依靠当前生命值添加 40生命值

注意:

  1. 索引(index)必须是有效的,不能是非玩家实体并且还没用(比如地图本身)
  2. 设定的生命值 或者 添加值 必须是浮点数。不然你会发现本来设定140的生命值变成 49239239932 巨大数值了
  3. 除了 hp + 40.0 你还可以 hp - 40.0,发挥你的想象力吧(不过除法就算了,因为大概会变成0血)

死亡之后重生

虽然说是死亡后重生,但是实际上我们还可以做到即使活着也能重新复活

当然这样就不具有实际价值,只是少加一个判断而已,我们还是做我们需要的目标吧

我们需要用到hamsanwich模块中的ExecuteHamB来触发Ham函数

触发HAM是什么意思?

Ham事件不止可以用来勾住,也可以直接用来触发(触发的时候也可以被勾住)。
比如爆炸的时候造成的伤害我们就可以触发出来,这样即使原有不存在的效果,我们也可以自己做出来

对于Ham事件来说,重新复活的事件代码是Ham_CS_RoundRespawn

我们通过ExecuteHamB来触发这个事件

示例:

1
ExecuteHamB(Ham_CS_RoundRespawn, id)  //通过触发Ham事件中的 Ham_CS_RoundRespawn 来让 id 复活

那应该怎么写才能实现死亡后复活?

很简单,我们需要先勾住玩家的死亡事件,然后触发函数,通过这个被触发的函数来延迟复活玩家
不太理解吗?

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <amxmodx>
#include <hamsandwich>
public plugin_init()
{
RegisterHam(Ham_Killed, "player", "PlayerKilled", 1)
}
public PlayerKilled(vic, attacker)
{
if(!is_user_connnected(vic)) return //如果被击杀的玩家不是服务器中的 返回
set_task(3.0, "Task_Spawn", vic) //设定延迟事件(延迟3.0秒,触发 Task_Spawn 函数,被触发的索引是 vic(即:受伤者) )
}
//被触发的复活函数
public Task_Spawn(id)
{
if(is_user_alive(id)) return //如果在函数触发的时候 玩家已经复活 返回
ExecuteHamB(Ham_CS_RoundRespawn, id)
client_print(id, print_chat, "你已经复活。")
}

注意:

  1. 写代码的时候需要考虑全面,比如玩家在复活函数被延迟触发的时候,此时玩家已经处于存活状态,此时就不应该继续执行复活代码,所以我们应该添加一个返回。不然玩家会在延迟时间达到的时候重新又复活一次,这样的效果是我们不希望见到的。
  2. 延迟函数的时候,触发的函数名一定要和被触发的函数名一致,并且要注意是否大小写一致(不一致的大小写会出现无法触发函数的问题)
  3. 这个代码只用到了hamsandwich和amxmodx模块中的代码,所以这里不需要写上fakemeta

目标移动向量

在游戏中,玩家的每次移动或跳跃都是会改变pev_velocity移动向量(速率)的

其移动的向量也是一个数组和坐标系几乎一样,也是三个轴X, Y ,Z

其中Z轴表示了目标上升还是在下降

获取示例:

1
2
new Float:vel[3]
pev(id, pev_velocity, vel)

注意是需要新建的是浮点数的数组变量然后通过pev来获取并存储到新建变量中

其中vel[2]代表Z轴,其前面vel[0] (X轴)和vel[1] (Y轴)代表了左右 前后不同的移动方向

比如让目标向上飞起:

1
2
3
new Float:vel[3]
vel[2] = 600.0
set_pev(ent, pev_velocity, vel)

注意:

  1. 新建变量用来存储的时候,必须是浮点变量
  2. 如果vel[2] (Z轴)中的浮点数值是正数那么代表正在上升或者原地不动,反之如果是负数 代表正在坠落或下降中

手雷的爆炸时间

本次要讲的是修改手雷爆炸时间。似乎这个方法对于烟雾弹没有效果,具体我没有去测试

代码pev_dmgtime用于获取或改变游戏中手雷的爆炸时间

可是应该怎么修改呢?

这里我们要用到一个函数FM_SetModel,这个函数会在设定模型的瞬间触发(本例中代表 投掷手雷的瞬间就会执行)

示例:

1
2
3
4
5
6
7
8
9
#include <amxmodx>
#include <fakemeta>
public plugin_init()
{
register_forward(FM_SetModel, "Fw_SetModel")
}
public Fw_SetModel(ent, model[])
{
}

上例中,通过触发FM_SetModel事件勾住了我们的Fw_SetModel函数,
该函数中有两个参数,分别是:

  • 实体
  • 模型路径

什么是实体?

游戏中会见到各种形形色色的实体,比如爆炸点的箱子,安装之后在地上的C4炸弹,丢弃在地上的武器,投掷出去的手雷等等,这些都是实体,实体还分为点实体和固体实体,这里暂不细说。

刚刚我们稍稍了解了一下什么是实体,那么回到本例 即投掷出去的手雷(在空中飞的)就是一个实体

那么回到代码中:public Fw_SetModel(ent, model[])也就是说其中那个ent代表了飞行中的手雷

那么我们需要在这个函数中判断什么?首先需要添加

if(!pev_valid(ent)) return //无效实体即返回

示例:

1
2
3
4
public Fw_SetModel(ent, model[])
{
if(!pev_valid(ent)) return //无效实体即返回
}

为什么要判断 pev_valid( ) 无效的实体?

无效的实体即实体不存在了、实体被删除了、实体获取不到
pev_valid()在我们后期制作插件的时候尤其重要,因为无效的实体会导致游戏崩溃或者卡住,甚至控制台报错
为了避免这些不必要的情况,为了代码的稳定性是必须添加这个的

那么接下来我们需要做的也不会太难,我们需要再添加一个判断,判断这个实体的实体名是不是手雷

什么是实体名?

实体名(pev_classname)是用来区分不同实体的最佳途径,对于CS来说几乎每个武器都有自己的实体名,手雷的实体名是grenade,玩家自己的实体名是player,爆炸点的箱子实体名是func_breakable等等。通过判断实体名我们可以清晰的分辨哪些需要修改,哪些是不必要的。

既然我们知道手雷的实体名是grenade接下来要做的也就很简单了:判断实体名是不是我们需要的那个

这里我们会用到一个equal字符串判断代码,稍后会讲到

首先获取实体名,注意这里的索引要改成ent了,因为我们获取的是别的东西

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

现在实体名已经存储到了class变量中,接下来我们要做的就是判断这个class中是否是grenade字符串。简单说就是 判断这个实体的名字是不是grenade

示例(判断是否是那个字符串):

1
if(!equal(class, "grenade")) return //如果class变量中 不是 grenade 字符串 那么 返回

然后我们还需要判断一下模型路径,因为grenade实体名还可能包含C4炸弹 (安装后)或烟雾弹

和上面的写法一样 只是要改变一下 变量名

1
if(!equal(model, "models/w_hegrenade.mdl")) return //如果字符串中的模型路径不是获取的那个 返回

我们可能需要同时使两个手雷类型**(高爆手雷,闪光弹)的时间都发生变化,此时我们要用到&&或||来添加多重判断**

示例:

1
if(!equal(model, "models/w_hegrenade.mdl") && !equal(model, "models/w_flashbang.mdl")) return

不太懂?那就用代码翻译一下 即:如果不是手雷的路径并且也不是烟雾弹的路径那么返回,这样就可以避免别的相同实体名使用这个函数了

我们组合一下代码就会变成下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <amxmodx>
#include <fakemeta>
public plugin_init()
{
register_forward(FM_SetModel, "Fw_SetModel")
}
public Fw_SetModel(ent, model[])
{
if(!pev_valid(ent)) return //无效实体即返回
new class[33]
pev(ent, pev_classname, class, 32) //获取实体的名字存储到 class变量中
if(!equal(class, "grenade")) return //如果获取到的实体名不是 grenade 字符串 那么返回
//如果获取到的模型路径既不是手雷也不是闪光 返回
if(!equal(model, "models/w_hegrenade.mdl") && !equal(model, "models/w_flashbang.mdl")) return
}

写到这里,几乎就快完成目的了。接下来要做的就是修改手雷的爆炸时间,这里的手雷指的就是实体,所以我们写上ent

示例:

1
set_pev(ent, pev_dmgtime, 爆炸时间)

上面这段看上去没什么不对,但是实际上如果在游戏中测试的话,手雷扔出去后会瞬间爆炸所以我们还需要通过游戏时间来决定,配合get_gametime()完成最终效果吧!

1
set_pev(ent, pev_dmgtime, get_gametime() + 12.0) //爆炸时间设定为 当前游戏时间 + 12秒

我们将所有代码组合一下,就会变成这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <amxmodx>
#include <fakemeta>
public plugin_init()
{
register_forward(FM_SetModel, "Fw_SetModel")
}
public Fw_SetModel(ent, model[])
{
if(!pev_valid(ent)) return //无效实体即返回
new class[33]
pev(ent, pev_classname, class, 32) //获取实体的名字存储到 class变量中
if(!equal(class, "grenade")) return //如果获取到的实体名不是 grenade 字符串 那么返回
//如果获取到的模型路径既不是手雷也不是闪光 返回
if(!equal(model, "models/w_hegrenade.mdl") && !equal(model, "models/w_flashbang.mdl")) return
set_pev(ent, pev_dmgtime, get_gametime() + 12.0) //爆炸时间设定为 当前游戏时间 + 12秒
}

如此,你就成功了

注意:

  1. 本次我们初次涉及到了实体,以后我们还会学习建立一个新的实体
  2. 如果你需要修改模型则需要将函数触发改为post,对于当前代码来说即register_forward(FM_SetModel, "Fw_SetModel", 1)
  3. 若将爆炸时间改成set_pev(ent, pev_dmgtime, get_gametime())手雷将瞬间爆炸
  4. equal的判断需要注意 括号()的相符度,具体可参看我的写法

判断按键

判断玩家按的什么键,这是我们在后期经常会遇到的问题

如何解决呢?使用代码:pev_button即可

通过这个代码,可以获取到内置的一些键位,比如基础移动(A,S,D,W),鼠标左键,右键,*E键(使用键),R键(装子弹)*等等…

有哪些键位可以被获取到?

引用于 >> hlsdk_const.inc (你可以查看这个文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
IN_ATTACK //鼠标左键
IN_JUMP //跳跃键
IN_DUCK //蹲下键
IN_FORWARD //前进键(默认是W)
IN_BACK //后退键(默认是S)
IN_USE //使用键(默认是E)
IN_CANCEL //取消键(默认是ESC)
IN_LEFT //左旋键(默认是 ← 箭头)
IN_RIGHT //右旋键(默认是 → 箭头)
IN_MOVELEFT //左平移键(默认是A)
IN_MOVERIGHT //右平移键(默认是D)
IN_ATTACK2 //鼠标右键
IN_RUN //暂时不明
IN_RELOAD //弹药装填键(默认是R)
IN_ALT1 //摇杆控制键(默认是ALT)
IN_SCORE //比分查看键(默认是TAB)

那应该怎么写出判断代码?

比如说我们要判断:按下了鼠标左键,可以这样写:

1
2
3
if(pev(id, pev_button) & IN_ATTACK) //如果玩家按了鼠标左键
{
}

其中&相当于代表一个代码链接,让其获取到的结果对定义的键位进行对比

那如果要表达玩家没有按这个键呢?
我们以鼠标左键为例,需要添加一个!反义判断符

1
2
3
4
if( ! ( pev(id, pev_button) & IN_ATTACK) )
{
client_print(id, print_chat, "没有按下鼠标左键")
}

单次键位判断

如果需要判断一个键位,是否处于按下但是没有持续按住的情况(比如手枪USP,按一下射一次) 应该怎么做?

这里我们还需要用到一个pev_oldbuttons,配合&&(并且)来判断上一次键位是否依然是那个

示例:

1
2
3
4
5
//如果玩家按下了鼠标左键并且没有一直按住,即继续执行函数括号中的内容
if(pev(id, pev_button) & IN_ATTACK && !(pev(id, pev_oldbuttons) & IN_ATTACK))
{
client_print(id, print_chat, '触发成功")
}

参考文献

Lv.4 轻难度 - AMXX插件编写教程