红石中继站
    • 资源
    • 新帖
    • 版块
    • 热门
    • 登录

    MCFPP开发日志——实体NBT

    矿工茶馆
    2
    2
    101
    正在加载更多帖子
    • 从旧到新
    • 从新到旧
    • 最多赞同
    回复
    登录后回复
    此主题已被删除。只有拥有主题管理权限的用户可以查看。
    • AlumopperA
      Alumopper
      最后由 编辑 · 陕西省

      芝士MCFPP

      MCFPP是一个能被编译为Minecraft数据包的全新的面向对象的语言。它旨在以类似C系语言的语法,进行数据包的编写,并引入编程中常用的概念,从而使数据包的编写更加的便利。

      仓库地址:https://github.com/MinecraftFunctionPlusPlus/MCFPP

      项目官网:https://www.mcfpp.top

      目前最新版本: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就可以访问到苦力怕的poweredNBT字段。而这个数据模板,就声明在了我们之前给出的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]
      

      看起来不错!好欸,可以去吃饭了。

      但是……好像还是有什么地方不对劲?

      73002d66-afae-443d-a183-d5ed5a3a9bc9-image.png

      啊哈哈,只能选择一个实体

      不过还好不是什么麻烦事。还好我有先见之明,构造命令的时候是调用的函数,不是直接用字符串拼的。所以我们只要把构造命令用的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)

      业余代码雪狐一只~
      支持Project MCFPP项目谢谢喵

      梨木利亚梨 1 条回复 最后回复 回复 引用
      • 梨木利亚梨
        梨木利亚 管理员 @Alumopper
        最后由 编辑 · 中国香港

        @Alumopper 非常好项目,支持

        1 条回复 最后回复 回复 引用
        • 第一个帖子
          最后一个帖子
          "Minecraft" 以及 "我的世界" 为 Mojang Synergies AB 的商标,本站与 Mojang 以及 Microsoft 没有从属关系
          © 2024-2025 红石中继站 版权所有 本站原创图文内容版权属于原创作者,未经许可不得转载
          侵权投诉邮箱:[email protected]
          由 长亭雷池WAF 提供安全检测与防护 由 WAFPRO 提供 SCDN 安全加速
          苏公网安备32050902102328号 苏ICP备2023043601号-8