[JE1.12]如何使用纯指令打造一个简易反作弊系统
前排提示:教程很长很干,需要较长时间阅读
背景
为什么需要使用纯指令打造反作弊系统
这听起来很蠢,为什么不使用成熟的第三方反作弊解决方案呢(例如Grim,Polar,Matrix等),但是纯指令反作弊在某些极端情况下仍然可能有用(只能使用原版服务器核心,且无法更改服务器配置文件),尤其是在啥比网易租赁服()。
在一些只能使用纯净服务端的服务器中,无法安装插件或者模组,又有反作弊需求,纯指令(包括数据包)是mc官方唯一支持的可以用来反作弊的手段。但是国内低版本数据包反作弊仍然是空白状态(高版本数据包反作弊可在B站搜索NCM反作弊),所以本篇教程教大家怎么使用纯指令打造一个简易的反作弊系统。
适用范围
适用于minecraft java版本1.12
项目结构
搭建框架
几乎所有的纯指令系统最先需要的就是搭建框架。
一般来说,搭建框架的目的是明晰项目结构,减少代码重复度,减少指令运行次数(因为JE1.12的数据包非常原始)。
通常来说,减少指令运行次数最简单粗暴的方法是实现一个计时器。
计时器搭建(可以不用看,我搞错了)
在functions文件夹下新建main文件夹,main文件夹下新建
- main.mcfunction
- 1S.mcfunction
- 0.5S.mcfunction
- 0.1S.mcfunction
main.mcfuncion中的内容
1 | ##增加计分板 |
0.1S.mcfunction中的内容
1 | ##初始化调用周期 |
0.5S.mcfunction中的内容
1 | ##初始化调用周期 |
我操太久没写了忘了反作弊不能用计时器了
反作弊需要追求精确性,所以必须每tick运行。上面的没用了,就当题外话看吧。
正式教程
能检测什么?
只包含了简单的fly检测,和noground(原创,全网首发)检测。我的理念是宁愿放过不要误杀,而且使用纯指令追求准确检测是不现实的(受制于原版20tick的限制,以及服务器和哭护短之间有1tick的渲染延迟)
名词解析
- kick: 踢出玩家,玩家被踢出后可重新加入服务器
- ban: 封禁玩家,玩家被ban后一进服会立刻被踢
- VL: 即violation,你可以理解为”作弊权重”或者“可疑度”,VL越高代表玩家越有可能是在作弊
准备工作
在 functions中新建system文件夹,在system文件夹下新建AntiCheat文件夹,在AntiCheat文件夹下新建announcement,checks,punishment,report文件夹,同时新建main.mcfunction,ini.mcfunction,unini.mcfunction文件。
在ini.mcfunction中输入以下内容:
1 | scoreboard objectives add check.NFNG.rem dummy NoGround检测防误判 |
在游戏中输入/function system:AntiCheat/ini 来创建必要的计分板,生成需要的tag盔甲架和命令方块(会在你执行命令的地方生成命令方块和盔甲架,所以记得先找一个好地方再输入指令🤓)。创建完成后会看见右下角提示框有提示。
在unini.mcfunction中输入以下内容
1 | scoreboard objectives remove check.NFNG.rem |
没啥好说的,用于卸载并删除反作弊的所有计分板和数据。这里就不需要站到命令方块上执行命令了,因为删除使用的是盔甲架相对坐标。删除完成应该会看见右下角有提示。
在main.mcfunction中输入以下内容
1 | function System:AntiCheat/checks/fly |
惩罚处理
在punishment文件夹下新建ban.mcfunction和kick.mcfunction
在ban.mcfunction中输入以下内容:
1 | execute @s[score_ban_min=1,score_ban=1] ~ ~ ~ tellraw @a[tag=OP] [{"text":"[","color":"white","bold":true,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"反馈日志","color":"gold","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"]","color":"white","bold":true,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"AntiCheat","color":"aqua","bold":true,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":" >>> ","color":"gray","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"selector":"@s","color":"green","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":" 被永久踢出游戏","color":"gray","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false}] |
在kick.mcfunction中输入以下内容:
1 | execute @s[score_kicktime_min=2] ~ ~ ~ tellraw @a[tag=OP] [{"text":"[","color":"white","bold":true,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"反馈日志","color":"gold","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"]","color":"white","bold":true,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"AntiCheat","color":"aqua","bold":true,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":" >>> ","color":"gray","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"selector":"@s","color":"green","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":" 被踢出游戏","color":"red","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false}] |
kick和ban原理解析
因为使用原版纯指令,而且function默认权限无法直接执行kick和ban指令,所以在这里使用了一个小技巧,即原版客户端一次能接收到的服务器传来的字符数是有上限的,所以使用tellrow指令对有相关计分板或者tag的人输出消息,这条消息使用多层嵌套@e来保证字符数超出最大值,这样当客户端接收到消息时,会因为字符长度超过最大值被瞬间踢出服务器。
这里面ban和kick所使用的踢出原理是一样的,区别在于kick之后被踢的人kicktime计分板数会-1,保证只踢出一次(实际上你可以通过手动设置kicktime来增加踢出次数);ban之后被ban的人计分板不会变化,计分板ban的数为1的人会被持续踢出服务器。
注意:与/ban指令不同,这里被ban的人不是不能进服,而是进服后瞬间被踢出,有时候因为延迟或者被ban的人的客户端原因,不能马上踢出,这种情况一般是客户端未响应。
编写反作弊核心逻辑(checks)
1.Fly检测
在checks文件夹下新建fly.mcfunction并输入以下内容:
1 | execute @a ~ ~ ~ detect ~ ~-0.2 ~ air -1 execute @s ~ ~ ~ detect ~0.8 ~-0.2 ~ air -1 execute @s ~ ~ ~ detect ~-0.5 ~-0.2 ~ air -1 execute @s ~ ~ ~ detect ~ ~-0.2 ~0.8 air -1 execute @s ~ ~ ~ detect ~ ~-0.2 ~-0.8 air -1 execute @s ~ ~ ~ detect ~0.8 ~-0.2 ~0.8 air -1 execute @s ~ ~ ~ detect ~0.8 ~-0.2 ~-0.8 air -1 execute @s ~ ~ ~ detect ~-0.8 ~-0.2 ~0.8 air -1 execute @s ~ ~ ~ detect ~-0.8 ~-0.2 ~-0.8 air -1 scoreboard players add @s check.fly.time 1 {abilities:{mayfly:0b,flying:0b}} |
在checks文件夹下新建utils文件夹,在utils文件夹下新建1.5bCheck.mcfuntion并输入以下内容:
1 | execute @s ~ ~ ~ detect ~ ~-0.6 ~ fence * execute @s ~ ~ ~ scoreboard players tag @s add flyCheckRemove |
原理解析
核心思路是通过检测脚下是否为空气来判断是否在空中,并通过一系列限制确定在空中的玩家是正常游戏还是开了fly。
技术细节
- 检测玩家加是否在空中不仅要检测脚下的方块,还要检测脚下一圈的方块,通过相对坐标偏移,保证玩家在潜行在方块边缘时不被误判
- 仅检测脚下的方块仍然不够,玩家有可能站在一些特殊方块上,甚至潜行在特殊方块边缘,因此引入
1.5bCheck.mcfunction,通过枚举1.12下一些常见的特殊高度方块,防止玩家被误判。 - 拥有
flyCheckRemove标签的玩家将会获得fly检测豁免,用来防止误判 - 对于正在下落的玩家,我们的思路是先假设所有人都在下落,再利用
{FallDistance:0f}标签选出所有没在下落的玩家,然后反选出正在下落的玩家,并给予豁免 - 当玩家被判定为飞行后,每满一秒会增加一个VL,当满20VL后,玩家会因为作弊被踢出服务器
- VL是共享的,也就是说,其他检测加的VL会与fly检测加的VL叠加,所有检测的VL都是共享的
2.NoGround检测
在checks文件夹下新建noFallNoGround.mcfunction文件,输入以下内容:
1 | scoreboard players add @e[type=armor_stand,tag=AntiCheatBot] check.NFNG.rem 1 |
在utils文件夹下新建death.mcfunction,输入以下内容:
1 | scoreboard players tag @a add death |
原理解析
在JE1.12中,站在地上的玩家会有{OnGround:1b}的标签,同时会有{FallDistance:0f}的标签。
理论上,玩家不可能同时处于既在地上又处于下落状态中,但是我们看下知名外挂LiquidBounce中是如何写noGround模式的:
1 | object NoGround : NoFallMode("NoGround") { |
可以看到,挂端可以通过发包强制修改自身标签,强行使自己处于不在地面上的状态,但是游戏每更新一次,玩家状态都会刷新为在地面上,所以当挂端不断发包时,玩家一边因为{OnGround:0b}且自身没有上升的motion被认为处于下落状态中,一边因为游戏tick刷新率远低于发包速率而在每次tick刷新时会出现一次{OnGround:1b}的标签,且同时被认为处于下落状态中。我们通过先假设所有人都处于下落状态中(标记为isFalling),然后通过{FallDistance:0f}标签去掉不处于下落状态中的人来筛选出真正处于下落状态中的人,