MCFPP开发日志——实体NBT
-
芝士MCFPP
MCFPP是一个能被编译为Minecraft数据包的全新的面向对象的语言。它旨在以类似C系语言的语法,进行数据包的编写,并引入编程中常用的概念,从而使数据包的编写更加的便利。
仓库地址:https://github.com/MinecraftFunctionPlusPlus/MCFPP
目前最新版本:SNAPSHOT 25m01a
要做什么
实现对目标选择器指向的实体NBT数据的操作
源码在这里:
namespace test; func test(){ var s = @e[type="creeper"]; s.dat.powered = false; }
而我们要把它编译出来的东西大概是这样子:
data modify storage mcfpp:system stack_frame prepend value {} #expression: s.dat.powered=false execute as @e[type=creeper] run data modify entity @s powered set value 0b #expression end: s.dat.powered=false data remove storage mcfpp:system stack_frame[0]
为什么没有mcfunction的语法高亮开干!
怎么做呢
让我访问!
现在已经有了标准库文件
Creeper.mcfpp
data Creeper: EntityData, CreatureData, AIMobData { @Name<"ExplosionRadius"> byte explosionRadius; @Name<"Fuse"> short fuse; bool ignited; bool powered; }
powered
字段已经在里面声明好了,所以唯一问题就是怎么通过目标选择器选中这个字段。目标选择器对应了
selector
类型的变量,即SelectorVar
类。由于已经有目标选择器参数作为setter
,若使用类似selector s = @e; s.type = "crepper"; s.powered = false;
让nbt数据和目标选择器参数同级,会显得乱七八糟的,所以就用了一个二级变量
data
来访问实体数据var s = @s[type="creeper"]; s.data.powered = false;
本来挺棒的,但是antlr 4这个时候就要报错了,它说,
data
不是关键字嘛,怎么能作为变量标识符。那没办法,g4文件能不动还是不动,只能用删减大法把data
改成dat
了。可恶好丑呀MCFPP有一种属性操作器的语法,其实主要是为了适配目标选择器的语法。
假设有:class Test{ string t; }
则可以使用
var qwq = Test()[t = "uwu"];
修改字段的值
不过啦目标选择器的
type
之类的属性,或者我们在mcf中熟知的参数,都是只写参数,也就是说,只能写入不能读取修改值
data
,啊不,dat
是一个数据模板对象,或者你也可以叫它结构体,或者你也可以叫它NBT复合标签。data
dat
代指了一个实体NBT的根路径,所以使用dat.powered
就可以访问到苦力怕的powered
NBT字段。而这个数据模板,就声明在了我们之前给出的Creeper.mcfpp
中。如果没有指定实体的类型,那么就会将所有已注册的实体类型的数据模板统合为一个大的联合类型。使用注解
@MCFPPEntity
对数据模板修饰,告诉MCFPP编译器这个数据模板表示了一个实体。所以你也可以自定义自己的实体类型的数据模板,实现灵活的加载。那么万事俱备,只欠东风。看看我们能编译出什么东西——
data modify storage mcfpp:system stack_frame prepend value {} #expression: s.dat.powered=false #expression end: s.dat.powered=false data remove storage mcfpp:system stack_frame[0]
结果是,什么都没有,只有一些注释告诉我们这里确实曾经有一个表达式。欸,那我编译结果在哪里呢?原来是MCFPP小姐不太聪明的优化系统,会自动优化掉编译器能追踪的变量(https://www.mcfpp.top/zh/quickstart/07generic/01concrete_var.html ),所以什么命令都没有输出的说。这可不行,我们得告诉编译器这个变量不能被优化,你要把每一步操作都输出为指令。
调教编译器(?)
于是我给
dat
对应的数据模板加了一个alwaysDynamic = true
字段,告诉编译器这个数据模板里面的数据都不用优化,不用缓存记住它们的值。然后再编译一下
这次是输出命令了,但是输出的却是这个:
scoreboard players set dat_powered mcfpp_boolean 0
欸欸,不对呀这不是计分板命令吗。
bool
类型是基于计分板实现的,所以编译器当然就把它当作计分板来进行了。这也不行呀,我们操作的是nbt数据。那我们新建一个数据类型表示基于nbt的布尔值吗?但是转念一想,还有short
,byte
,int
,之类的一堆类型,难不成我们得一个一个创建新类型?不行不行太麻烦了,我还没吃饭呢。不过MCFPP的话,是有一个叫做栈的概念的,所以说每一个变量本来就有一个NBT路径,那么我们只要告诉编译器,这个叫做
powered
的变量赋值的时候要直接赋值给它NBT路径对应的位置就好了。于是,万能的注解出场了。定义新注解
@DataOnly
,修饰Creeper
数据模板,告诉编译器里面的所有变量都要像我们刚刚做的那样操作。简便起见,再偷懒一下,让
@MCFPPEntity
继承@DataOnly
,这样我们就不用再一个一个加注解了,方便很多。所以我们的MCFPP源码是这样的
@MCFPPEntity data Creeper: EntityData, CreatureData, AIMobData { @Name<"ExplosionRadius"> byte explosionRadius; @Name<"Fuse"> short fuse; bool ignited; bool powered; }
编译结果是这样的:
data modify storage mcfpp:system stack_frame prepend value {} #expression: s.dat.powered=false data modify entity @e[type=creeper] powered set value 0b #expression end: s.dat.powered=false data remove storage mcfpp:system stack_frame[0]
看起来不错!好欸,可以去吃饭了。
但是……好像还是有什么地方不对劲?
啊哈哈,只能选择一个实体
不过还好不是什么麻烦事。还好我有先见之明,构造命令的时候是调用的函数,不是直接用字符串拼的。所以我们只要把构造命令用的
Commands.dataSetValue
函数稍作修改:fun dataSetValue(a: NBTPath, value: Tag<*>): Command{ return Command.build("data modify") .build(a.toCommandPart()) .build("set value ${SNBTUtil.toSNBT(value)}") }
改成酱紫
fun dataSetValue(a: NBTPath, value: Tag<*>): Command{ if(a.source is EntitySource){ val selector = (a.source as EntitySource).entity.value if(!selector.selectingSingleEntity()){ val new = a.clone() new.source = EntitySource(SelectorVar(EntitySelector('s'))) return Command.build("execute as").build(selector.toCommandPart()).build("run") .build("data modify").build(new.toCommandPart()).build("set value ${SNBTUtil.toSNBT(value)}") } } return Command.build("data modify") .build(a.toCommandPart()) .build("set value ${SNBTUtil.toSNBT(value)}") }
检测一下,如果选择了多个实体就加一个
execute as ... run
包装一下就好啦再编译运行一下
data modify storage mcfpp:system stack_frame prepend value {} #expression: s.dat.powered=false execute as @e[type=creeper] run data modify entity @s powered set value 0b #expression end: s.dat.powered=false data remove storage mcfpp:system stack_frame[0]
哇呜终于成功了
提交,推送,发帖,吃饭!
虽然是吃完饭再写的帖子就是了(x) -
@Alumopper 非常好项目,支持