前排提示:教程很长很干,需要较长时间阅读

背景

为什么需要使用纯指令打造反作弊系统

这听起来很蠢,为什么不使用成熟的第三方反作弊解决方案呢(例如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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
##增加计分板

scoreboard players add @e[tag=0.1s] timeloop 1

scoreboard players add @e[tag=0.3s] timeloop 1

scoreboard players add @e[tag=0.5s] timeloop 1

scoreboard players add @e[tag=1s] timeloop 1

##周期调用对应函数

function MAIN:0.1S if @e[tag=0.1s,score_timeloop_min=2]

function MAIN:0.3S if @e[tag=0.3s,score_timeloop_min=6]

function MAIN:0.5S if @e[tag=0.5s,score_timeloop_min=10]

function MAIN:1S if @e[tag=1s,score_timeloop_min=20]

0.1S.mcfunction中的内容

1
2
3
##初始化调用周期

scoreboard players set @e[tag=0.1s] timeloop 0

0.5S.mcfunction中的内容

1
2
3
##初始化调用周期

scoreboard players set @e[tag=0.5s] timeloop 0

我操太久没写了忘了反作弊不能用计时器了

反作弊需要追求精确性,所以必须每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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
scoreboard objectives add check.NFNG.rem dummy NoGround检测防误判

scoreboard objectives add check.fly.time dummy 飞行时间检测

scoreboard objectives add AntiCheat.VL dummy AntiCheat判断权重

scoreboard objectives add check.NFNG.time dummy NoFall中NoGround的检测

scoreboard objectives add kicktime dummy 踢出

scoreboard objectives add ban dummy 封禁

scoreboard objectives add AC.VL.remove dummy VL定时删除



summon minecraft:armor_stand ~ ~1 ~ {CustomName:"AntiCheatBot",Tags:["AntiCheatBot"],CustomNameVisible:1b,Invisible:1,Marker:1b}

execute @e[type=armor_stand,tag=AntiCheatBot] ~ ~ ~ setblock ~ ~-1 ~ minecraft:repeating_command_block 0 replace {Command:"function system:AntiCheat/main",auto:false}

execute @e[type=armor_stand,tag=AntiCheatBot] ~ ~ ~ setblock ~ ~ ~ minecraft:redstone_block



tellraw @s [{"text":"[AntiCheat-System]","color":"aqua","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":">>>","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"[AntiCheat-System]","color":"aqua","bold":true,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"已成功安装!","color":"gold","bold":true,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false}]

在游戏中输入/function system:AntiCheat/ini 来创建必要的计分板,生成需要的tag盔甲架和命令方块(会在你执行命令的地方生成命令方块和盔甲架,所以记得先找一个好地方再输入指令🤓)。创建完成后会看见右下角提示框有提示。

unini.mcfunction中输入以下内容

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
scoreboard objectives remove check.NFNG.rem

scoreboard objectives remove check.fly.time

scoreboard objectives remove AntiCheat.VL

scoreboard objectives remove check.NFNG.time

scoreboard objectives remove kicktime

scoreboard objectives remove ban

scoreboard objectives remove AC.VL.remove



execute @e[type=armor_stand,tag=AntiCheatBot] ~ ~ ~ setblock ~ ~-1 ~ air

execute @e[type=armor_stand,tag=AntiCheatBot] ~ ~ ~ setblock ~ ~ ~ air

execute @e[type=armor_stand,tag=AntiCheatBot] ~ ~ ~ kill @s



tellraw @s [{"text":"[AntiCheat-System]","color":"aqua","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":">>>","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"[AntiCheat-System]","color":"aqua","bold":true,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"已成功卸载!","color":"gold","bold":true,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false}]

没啥好说的,用于卸载并删除反作弊的所有计分板和数据。这里就不需要站到命令方块上执行命令了,因为删除使用的是盔甲架相对坐标。删除完成应该会看见右下角有提示。

main.mcfunction中输入以下内容

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
function System:AntiCheat/checks/fly

function System:AntiCheat/checks/noFallNoGround



execute @e[type=armor_stand,tag=AntiCheatBot] ~ ~ ~ scoreboard players add @s AC.VL.remove 1

execute @e[type=armor_stand,tag=AntiCheatBot,score_AC.VL.remove_min=10000] ~ ~ ~ scoreboard players remove @a[score_AntiCheat.VL_min=1] AntiCheat.VL 1

execute @e[type=armor_stand,tag=AntiCheatBot,score_AC.VL.remove_min=10000] ~ ~ ~ scoreboard players reset @s AC.VL.remove





execute @a[score_AntiCheat.VL_min=10] ~ ~ ~ scoreboard players set @s kicktime 2

execute @a[score_AntiCheat.VL_min=10] ~ ~ ~ scoreboard players reset @s AntiCheat.VL



execute @a[score_kicktime_min=1] ~ ~ ~ function system:AntiCheat/punishment/kick

execute @a[score_ban_min=1] ~ ~ ~ function system:AntiCheat/punishment/ban

惩罚处理

punishment文件夹下新建ban.mcfunctionkick.mcfunction

ban.mcfunction中输入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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}]



execute @s[score_ban_min=1,score_ban=1] ~ ~ ~ tellraw @a [{"text":"----------------------------------------------------------------------","color":"gold","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"\n\n","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"                    ","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"AntiCheat公告","color":"red","bold":true,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"\n\n","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"          玩家:","color":"dark_purple","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"selector":"@s","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"被","color":"red","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"永久封禁","color":"dark_red","bold":true,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"\n","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"          感谢您对净化游戏环境做出的支持与贡献","color":"aqua","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"\n","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"          同时也提醒各位玩家遵守游戏规则","color":"aqua","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"\n\n","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"----------------------------------------------------------------------","color":"gold","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false}]






tellraw @s[score_ban_min=1,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":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"}]



execute @s[score_ban_min=1,score_ban=1] ~ ~ ~ scoreboard players set @s ban 2

kick.mcfunction中输入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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}]





scoreboard players remove @s kicktime 1

scoreboard players set @s AntiCheat.VL 0

tellraw @s[score_kicktime_min=1,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":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"},{"selector":"@e"}]




##封禁

##解封:/scoreboard players set id kick 0

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
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
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}}

##防止误判,包括高跳,漂浮,抗性,下落,创造,旁观

scoreboard players tag @a[tag=flyCheckRemove] remove flyCheckRemove

scoreboard players tag @a add flyCheckRemove {ActiveEffects:[{Id:8b}]}

scoreboard players tag @a add flyCheckRemove {ActiveEffects:[{Id:25b}]}


scoreboard players tag @a add flyCheckRemove {FallFlying:1b}


scoreboard players tag @a add isFalling

scoreboard players tag @a remove isFalling {FallDistance:0f}


execute @a ~ ~ ~ function system:AntiCheat/checks/utils/1.5bCheck


scoreboard players tag @a[tag=isFalling] add flyCheckRemove


scoreboard players reset @a[tag=flyCheckRemove] check.fly.time


##1秒1VL

scoreboard players add @a[score_check.fly.time_min=20] AntiCheat.VL 1



execute @a[score_check.fly.time_min=20] ~ ~ ~ tellraw @a[tag=OP] [{"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":"(uid:","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"score":{"objective":"uid","name":"@s"},"color":"aqua","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":")","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":" 由于疑似","color":"gray","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"飞行","color":"yellow","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"被系统自动记录1VL","color":"gray","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"(","color":"gray","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"score":{"objective":"AntiCheat.VL","name":"@s"},"color":"green","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"/10)","color":"gray","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false}]



execute @a[score_check.fly.time_min=20] ~ ~ ~ scoreboard players set @s check.fly.time 0

checks文件夹下新建utils文件夹,在utils文件夹下新建1.5bCheck.mcfuntion并输入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
execute @s ~ ~ ~ detect ~ ~-0.6 ~ fence * execute @s ~ ~ ~ scoreboard players tag @s add flyCheckRemove


execute @s ~ ~ ~ detect ~ ~-0.6 ~ spruce_fence * execute @s ~ ~ ~ scoreboard players tag @s add flyCheckRemove


execute @s ~ ~ ~ detect ~ ~-0.6 ~ birch_fence * execute @s ~ ~ ~ scoreboard players tag @s add flyCheckRemove


execute @s ~ ~ ~ detect ~ ~-0.6 ~ jungle_fence * execute @s ~ ~ ~ scoreboard players tag @s add flyCheckRemove


execute @s ~ ~ ~ detect ~ ~-0.6 ~ dark_oak_fence * execute @s ~ ~ ~ scoreboard players tag @s add flyCheckRemove


execute @s ~ ~ ~ detect ~ ~-0.6 ~ acacid_fence * execute @s ~ ~ ~ scoreboard players tag @s add flyCheckRemove


execute @s ~ ~ ~ detect ~ ~-0.6 ~ cobblestone_wall * 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
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
scoreboard players add @e[type=armor_stand,tag=AntiCheatBot] check.NFNG.rem 1

scoreboard players tag @a[tag=NoGroundCheckRemove] remove NoGroundCheckRemove

scoreboard players tag @a add NoGroundCheckRemove {ActiveEffects:[{Id:25b}]}

function system:/AntiCheat/checks/utils/death

scoreboard players tag @a add OnGround {OnGround:1b}

scoreboard players tag @a add isFalling



scoreboard players tag @a remove OnGround {OnGround:0b}

scoreboard players tag @a remove isFalling {FallDistance:0f}



execute @a[tag=!NoGroundCheckRemove] ~ ~ ~ execute @a[tag=isFalling] ~ ~ ~ execute @s[tag=OnGround] ~ ~ ~ scoreboard players add @s check.NFNG.time 1


## 这里的坐标应该是你服务器的出生点,或者玩家进服后默认的主城点
execute @a[x=-102,y=107,z=535,r=2] ~ ~ ~ scoreboard players reset @s check.NFNG.time

execute @a[score_check.NFNG.time_min=40] ~ ~ ~ scoreboard players add @s AntiCheat.VL 1

execute @a[score_check.NFNG.time_min=40] ~ ~ ~ tellraw @a[tag=OP] [{"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":"(uid:","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"score":{"objective":"uid","name":"@s"},"color":"aqua","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":")","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":" 由于疑似","color":"gray","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"noGround","color":"yellow","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"被系统自动记录1VL","color":"gray","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"(","color":"gray","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"score":{"objective":"AntiCheat.VL","name":"@s"},"color":"green","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false},{"text":"/10)","color":"gray","bold":false,"italic":false,"underlined":false,"strikethrough":false,"obfuscated":false}]

execute @a[score_check.NFNG.time_min=40] ~ ~ ~ scoreboard players reset @s check.NFNG.time



#定时清除,防止误判

execute @e[tag=AntiCheatBot,score_check.NFNG.rem_min=100] ~ ~ ~ scoreboard players reset @a check.NFNG.time

execute @e[tag=AntiCheatBot,score_check.NFNG.rem_min=100] ~ ~ ~ scoreboard players reset @s check.NFNG.rem

utils文件夹下新建death.mcfunction,输入以下内容:

1
2
3
4
5
scoreboard players tag @a add death

execute @e[type=player] ~ ~ ~ scoreboard players tag @s remove death

execute @a[tag=death] ~ ~ ~ scoreboard players tag @s add NoGroundCheckRemove

原理解析

在JE1.12中,站在地上的玩家会有{OnGround:1b}的标签,同时会有{FallDistance:0f}的标签。
理论上,玩家不可能同时处于既在地上又处于下落状态中,但是我们看下知名外挂LiquidBounce中是如何写noGround模式的:

1
2
3
4
5
6
object NoGround : NoFallMode("NoGround") {  
override fun onPacket(event: PacketEvent) {
if (event.packet is C03PacketPlayer)
event.packet.onGround = false
}
}

可以看到,挂端可以通过发包强制修改自身标签,强行使自己处于不在地面上的状态,但是游戏每更新一次,玩家状态都会刷新为在地面上,所以当挂端不断发包时,玩家一边因为{OnGround:0b}且自身没有上升的motion被认为处于下落状态中,一边因为游戏tick刷新率远低于发包速率而在每次tick刷新时会出现一次{OnGround:1b}的标签,且同时被认为处于下落状态中。我们通过先假设所有人都处于下落状态中(标记为isFalling),然后通过{FallDistance:0f}标签去掉不处于下落状态中的人来筛选出真正处于下落状态中的人,