0.0.14 • Published 1 year ago

just-mcf v0.0.14

Weekly downloads
-
License
GPL 3.0
Repository
github
Last release
1 year ago

JustMCF

JustMCF是一个简化mcfunction工程的项目。使用JustMCF,你不但可以使用原版的命令,还可以使用项目设计的简化命令,可以使你的命令更加简洁高效。

以下几个方面是JustMCF进行简化或者增强的部分

  • 数据操作简化——对记分板运算、NBT操作进行大幅度简化,并支持表达式形式运算记分板!
  • 命名空间省略——修改默认省略情况下的命名空间。(原来默认只能是Minecraft)
  • 注释增强——支持行内注释、块注释!(TODO:根据注释信息生成函数帮助信息)
  • 逻辑控制流简化——对execute进行大幅度简化!单文件定义多个、嵌套定义函数!支持逻辑表达式、条件语句、循环语句、列表遍历语句!
  • 命令聚合——将如scoreboard操作、bossbar操作这类的系列命令聚合到一个块里。
  • 命令对象化——像调用对象的方法那样调用命令,打开书写命令的新思路!
  • 选择器增强——更加方便的选择器语法、支持换行!
  • 支持脚本——直接在文件内定义JavaScript脚本,支持脚本生成命令!
  • 进阶函数——提供带有参数的函数封装。(TODO:引入支持库函数如random等)

这个项目未完成的功能:

  • 提供vscode language server用于自动补全和语法高亮
  • 类型检查(类型设置、modify set from、函数类型、for循环类型)
  • 支持JSON文本组件简化
  • 多minecraft版本的支持

谁适合使用本项目?

如果是命令老手——

  • 苦于命令与NBT过于冗长、无法换行的创作者——JustMCF大幅度简化了execute、data、scoreboard这三个最常用的命令,并提供NBT换行的支持(TODO:项目完成全部命令的解析支持后,将支持所有命令中涉及到的NBT的换行,目前仅局限于已简化的命令的换行)。
  • 苦于需要外部程序文件进行命令穷举、导致逻辑思路分开的创作者——JustMCF直接提供文件内脚本支持,让思路不会断片。
  • 苦于每次都要新开函数文件,多个函数文件的分隔导致思维混乱的创作者——JustMCF直接提供单文件定义多个函数的支持,并支持嵌套定义函数。
  • 苦于Minecraft命令书写无法形成逻辑链的创作者——JustMCF提供了条件、循环语句、进阶函数封装等特性,让逻辑更加清晰。
  • 希望尝试新的抽象思路,新的逻辑组织方式的创作者——JustMCF希望提供interface+func进行命令组织的方式,进一步进行思路的抽象。

如果是习惯了原版命令的创作者——

  • JustMCF直接支持使用原版命令,无缝衔接使用JustMCF的单文件多函数、进阶函数封装、条件、循环语句等特性!
  • JustMCF支持中性简化,如果认为过渡简化导致不习惯,请看execute的案例:简化幅度较小的支持

如果是命令新手——

  • 还是先不要用JustMCF,因为一旦使用了JustMCF,你就会陷入JustMCF带来的魔力之中,直接脱离原版命令苦海

快速开始

安装

本项目需要首先安装Node.js

如果已经安装完成,请打开命令行(在Windows下可以是命令提示符cmd、powershell;在Linux下可以是shell、zsh;在macOS下可以是终端Terminal)输入node -v,验证是否已经配置好Path变量,如果显示安装的Node.js版本号,则证明安装完成。请继续在命令行中输入:

npm install -g just-mcf

如果您已经安装,需要更新版本,则请输入:

npm update -g just-mcf

在您的工作文件夹中打开命令行窗口(或者打开命令行窗口cd到工作文件夹中)。

初始化项目

设置JustMCF项目的一些编译选项。

mcf init

初始化完成后,将在工作文件夹中创建mcf.mcmeta文件,这里包含JustMCF项目的各种设置。具体配置项请见附录:配置项介绍

项目文件结构

JustMCF项目文件以.mcf为后缀,一个.mcf文件可以生成多个.mcfunction文件。

JustMCF项目支持一个mcf.mcmeta文件,用于存放有关数据包兼容性的信息以及JustMCF的设置、自动生成的uuid列表等等。

通常一个数据包的文件结构为:

folder_name
|-pack.mcmeta
|-pack.png
|-data
    |-namespace
        |-functions
        |-tags
        |-func                  ->.mcf文件存放的地方
            |-export.mcf        ->例如这样一个mcf文件
|-mcf.mcmeta                    ->存放项目冲突信息的地方(使用的假名、uuid、记分板名)
  

编译项目

将JustMCF项目输出为Minecraft JE 数据包。

mcf build [<source_path>] [-o <target_path>]
# 或
mcf build [<source_path>] [--output <target_path>]

其中[]标记的部分是可选的。

<source_path> 编译的源地址,不填写则为当前命令行执行目录。

<target_path> 编译的目的地址,不填写则为执行目录的兄弟output文件夹。例如执行目录为/foo/test,则目的地址为/foo/test_output

路径都可以添加相对地址,用./索引到执行目录的子文件或文件夹,用../索引到执行目录的同级文件夹,例如mcf build -o .即输出到执行目录,当然这是不推荐的;mcf build -o ../output即输出到执行目录的兄弟文件夹output中。

一般来说,源地址填写工作路径,目的地址填写.minecraft/saves/datapacks/下的文件夹,这样子输出后可以直接reload查看运行情况以方便调试。

可以编写一个bat文件放在工作文件夹下,内容就填写:

mcf build -o xxxx/.minecraft/saves/datapacks/test
pause

调整全局设置

设置命令行的全局参数。

mcf conf

目前可以设置命令行的语言和编译缺少参数时是否进行询问。

最佳实践

这里以一段视线追踪代码为例:

仅仅增强原版命令

选择仅仅使用JustMCF增强的以下功能:单文件多function、可以换行的execute、选择器、nbt、json文本。

在传统风格1中,仅仅使用了单文件多function的特性,把同一个逻辑单元的function写到一起来。这个单.mcf文件最终会被解析成4个.mcfunction文件

func teen:begin{
  execute as @a at @s anchored eyes positioned ^ ^ ^ run function teen:before
}

func teen:before{
  function teen:ray
  tag @s ret
}

func teen:ray{
  scoreboard players set #find_entity bool 0
  execute as @e[dx=0,dy=0,dz=0] positioned ~-1 ~-1 ~-1 if entity @s[dx=0,dy=0,dz=0] run scoreboard players set #find_entity bool 1
  execute if block ~ ~ ~ #through if score #find_entity bool matches 0 positioned ^ ^ ^0.1 run function teen:ray
  execute unless entity @s[tag=find] run function teen:find
}

func teen:find{
  tag @s add find
  tellraw @s "Find it!"
}

在传统风格2中,使用了exec的换行支持与回调式书写函数的特性,把同一个逻辑单元的function整合到一起。

func teen:ray{
  scoreboard players set #find_entity bool 0
  exec{ as @e[dx=0,dy=0,dz=0] positioned ~-1 ~-1 ~-1 if entity @s[dx=0,dy=0,dz=0]} run
    scoreboard players set #find_entity bool 1
  exec{ 
    if block ~ ~ ~ #through 
    if score #find_entity bool matches 0 
    positioned ^ ^ ^0.1
  } run func teen:ray
  exec{ unless entity @s[tag=find] } run func teen:find{
    tag @s add find
    tellraw @s "Find it!"
  }

}
func teen:begin{
  exec{ as @a at @s anchored eyes positioned ^ ^ ^} run func teen:before{
    func teen:ray
    tag @s ret
  }
}

简洁风格一瞥

乍看一眼什么都看不懂,再慢慢看发现语法挺简单的:)。

namsp [func = teen]{
  func begin{
    { as @s at @s anchored eyes pos ^ ^ ^}->{
      ->func ray{
        bool#find_entity = 0 

        { as @e[dpos=0 0 0] 
          pos ~-1 ~-1 ~-1 
          if @s[dpos=0 0 0]
        }-> bool#find_entity = 1

        { if ~ ~ ~ #through 
          if bool#find_entity == 0 
          pos ^ ^ ^0.01
        }-> func ray; 
        
        { unless @s[tag=find]}->{
          @s.tag += find
          @s.text "Find it!"
        }
      }
      @s.tag -= find
    }
  }
}

mcf文件的开始

每一个.mcf文件可以拥有如下内容:命名空间语句、interface语句、函数语句、进阶函数语句、函数标签语句、脚本内容。

通常来说,拥有同一个逻辑模块的函数应该首先放置在同一个函数文件夹中,对于JustMCF项目来说,也就是放置在同一个.mcf文件中。

对于没有抽象设计需求的作者来说,可以在mcf.mcmeta文件中设置默认命名空间,每一个.mcf文件以函数语句、函数标签语句为经纬进行组织。如下所示:

namsp [func = data_resolve]{
    func getPlayerData{

    }
    func setPlayerData{

    }
    func getPigData{

    }
    func #getData{
        func getPlayerData
        func getPigData
        func getZombieData{

        }
    }
}

对于拥有抽象设计需求的作者来说,可以把每一个.mcf文件作为一个类文件进行组织。这个类的数据部分和操作部分分离。数据部分使用interface语句,操作部分使用函数语句或者进阶函数语句。如下所示:

interface Player n{
    Name:"fool",
    Age: 18,
    Sex: "female"
}
func getPlayerName(player){

}
func setPlayerAge(player,age){

}

标识符

在mcfunction中,可以大致归类为几种数据,而最为关键的两类数据就是scoreboard分数以及NBT。

在通常的编程中,数据可以这样被利用:

  • 初始化:data_name = data_value
  • 运算:data_name1 op data_name2

在JustMCF中,用以下标识符来表示几类数据

  • 命名空间IDnamespace:id,可以在指定命名空间的情况下简写为id。这些量在JustMCF中遇到驼峰命名法(就是说含有大写字母,如NameSpace:ID)将会自动转为蛇形(小写加下划线,如name_space:id),因此命名时可以采用驼峰(但是还是不建议这么做)。

  • 实体:在wiki上,可以用以下形式来代表实体:玩家名称 或 UUID 或 目标选择器变量。在JustMCF中,对应形式为:

    • 玩家名称:@playername 需要在名称前加 @

    • 假名:#name 不需要在名称前加 @,直接用原来的

    • UUID: @entityname 其中,可以指定 entityname对应的UUID,或让JustMCF自动生成UUID。

    • 目标选择器变量:@e[type=xxxx]和原来不变。

      除了目标选择器变量外,其他项需都要使用语句设置,见entity命令聚合一章。

  • 坐标:共有三类坐标:

    • 2个值表示朝向坐标或者xz坐标 ~ ~
    • 3个值表示位置坐标 ~ ~ ~
    • 5个值表示方位坐标 ~ ~ ~ ~ ~
  • 记分板:来源只有实体,在JustMCF中,表示为

    • 方案一:scoreboardname@entityname 其中,@entityname代表实体

    • 方案二:@entityname:scoreboardname

  • NBT:来源为实体、方块实体、storage,在JustMCF中,表示为:

    • 实体NBT:@s::Tag1

    • 方块实体NBT:~ ~ ~::Tag1

    • storageNBT: foo:bar::Tag1

在JustMCF中,用以下字面量表示不同的数据类型:

  • 原始JSON文本:j后面跟原始JSON文本,如 j{"text":"test"},单纯的数值和字符串不需要添加j
  • NBT值:n后面跟NBT值,如 n{Tag1:"no"},单纯的数值和字符串不需要添加n

数据操作简化

scoreboard数据运算

整合scoreboard的所有命令操作

记分板的声明和设置

声明记分板,未声明记分板准则类型的,将默认准则为dummy。记分项显示名称是原始JSON文本,记得在使用带大括号和中括号的语句时改为j{}j[]

scb test "displayname" 
         ##scoreboard objective add test dummy "displayname"

如果未声明显示名称,使用默认的记分项名称时,需要补上default。可以在声明语句后面加上设置语句。

设置语句

scb test default
scb(deathCount) test default {
    display sidebar
}
        ##default表示默认名称
        ##scoreboard objective add test deathCount  
        ##scoreboard objective setdisplay <槽位> <记分板ID>

设置记分板属性。语法和声明语句类似,不同的是没有准则类型和显示名称。

scb test {
    displayname "分数"
    rendertype hearts
    display sidebar
    remove
}

##scoreboard objective modify <计分板ID> displayname <显示名称>
##scoreboard objective modify <记分板ID> rendertype (hearts|integer)
##scoreboard objective setdisplay <槽位> <记分板ID>
##scoreboard objective remove <记分板ID>

设置的子语句可以加.,即如:

scb test {
	.displayname "分数"
	.rendertype hearts
}

记分板运算

记分板可以独立作为表达式,每条表达式将会被解析为一条命令。

test@s +=1                            ##add
test@s -=1                            ##remove
test@s =1                             ##set
test@s reset                          ##reset
test@s enable					    ##enable
test@s                                ##get
test@s *= 10                          ##operation 并使用了临时记分板或者常量记分板
test@s /= 10                          
test1@s += test2@s                    ##operation
test1@s -= test2@s
test1@s *= test2@s
test1@s /= test2@s
test1@s %= test2@s
test1@s << test2@s                    ##取较小值
test1@s >> test2@s                    ##取较大值
test1@s >< test2@s                    ##交换

支持完整表达式运算!按优先级顺序支持() << >> */% +-并且支持数字

完整表达式必须在:=右侧,表达式计算结果将会传给:=左边的记分板。如下表达式将被解析为单条的计算命令(临时计算的记分板名称可以自定义,当遇见常数时还可以设置在常数专用记分板中)。

ans@s := test2@s + test3@s / test4@s - 5 % test5@s

##以上会被解析成以下:
scoreboard players operation #temp0 justmcf-temp-scoreboard = @s test3
scoreboard players operation #temp0 justmcf-temp-scoreboard /= @s test4
scoreboard players operation #temp0 justmcf-temp-scoreboard += @s test2
scoreboard players set #temp1 justmcf-temp-scoreboard 5
scoreboard players operation #temp1 justmcf-temp-scoreboard %= @s test5
scoreboard players operation #temp0 justmcf-temp-scoreboard -= #temp1 justmcf-temp-scoreboard
scoreboard players operation @s ans = #temp0 justmcf-temp-scoreboard

关于记分板的逻辑简化操作将会再后面的execute简化部分提到。

这些运算也可以写进语句中,使用scb{}将表达式括起来就行了。

这里注意,为了保证包间的兼容性,凡是使用到的假名(无论带不带'#'),都需要用entity声明语句预先设置,详细见entity命令聚合一章。

entity(player) temp1,temp2,temp3
scb {
	num@temp1 = 1
	num@temp2 := num@temp1 + num@temp3
}

对于scoreboard list命令则必须写成语句

scb { list }    	##scoreboard objectives list
scb { @s list}  	##scoreboard players list @s
scb { @s reset}     ##scoreboard players reset @s

NBT数据运算

将data命令转为符号运算。这里只提供三种符号。

  • |=表示“并等于”,即merge;
  • =表示赋值,即set;
  • ..表示追加,其中..0表示追加到0这个位置,即prepend,而..1就是追加到1这个位置,即insert 1。

运算的对象可以是nbt或者另一个data标识符(即value和from)

~ ~ ~ ::Base *3                                  ##get block
@e[]::Item                                       ##get 
@e[] |= n{}                                      ##merge
@e[]::Item |= @s::Item                           ##modify merge from
@e[]::Item |= n{}                                ##modify merge value 
@e[]::Item = n{}                                 ##modify set value
@s::ArmorItems ..0 n{id:'iron_boots', Count:1b}  ##prepend
@s::ArmorItems .. n{id:'iron_boots', Count:1b}   ##append
@s::ArmorItems ..1 n{id:'iron_boots', Count:1b}  ##insert 1    
foo:storage::Data remove

同样的,data操作也可以用语句data{}包裹

data{
	@e[]::Item
	foo:storage::Data remove
}

对于使用同一个storage命名空间的data操作,还可以提前使用namespace语句声明运算使用的命名空间。见命名空间语句一节。

namsp [storage=test]{
    id1::Data = id2::Data
    id1::Data .. id1::Data[0]
}

对于一个NBT节点下的data操作,可以使用data namespace:id::data{}语句省略掉父节点的部分。

data foo:test::bar{ ##在foo:test bar节点上操作
	num1 = 1b
	num2 = 2b
}
data foo:test{  ##在foo:test根节点上操作
	bar.num1 = 1b
	bar.num2 = 2b
}

除此之外,进阶函数的调用可以作为右值(即等号左边的值)参与运算;exist表达式可以作为赋值的来源,(也就是等号左边的值)。

NBT与记分板数据转换存储

转换存储的书写方式是execute语句的store子语句的格式,即=>符号,请见execute语句部分。

NBT转为记分板

@s::ArmorItems[] => armor@s

##execute store result score @s armor run data get entity @s ArmorItems[]

记分板转为NBT(如果类型和倍率没有填写的话,默认int 1)。

armor@s => foo:bar::Armor.Length
armor@s => foo:bar::Armor.Length int*1

##execute store result storage foo:bar Armor.Length int 1 run scoreboard players get @s armor

bossbar数据运算

bossbar表达式,让操作像NBT操作那样方便

bossbar test:foo           ##bossbar get test:foo value
bossbar test:foo max       ##bossbar get test:foo max|players|value|visible
bossbar test:foo = 10      ##bossbar set test:foo value 10
bossbar test:foo max = 10  ##bossbar set test:foo max 10

scb@s => bossbar test:foo  ##execute store bossbar test:foo value run scoreboard players get

其余见命令聚合bossbar部分

attribute数据运算(TODO)

命名空间省略设置

对于一些命令需要用到命名空间的地方,通常以namespace:id的形式出现,在省略命名空间的情况下,游戏其默认为minecraft命名空间。在JustMCF中,有机会调整使用的默认的命名空间,以省略每次书写命名空间的不必要的麻烦。TODO:现在只支持JustMCF的简化命令采用这个设置,而对于出现的原版命令无效。

关键字为namespace,也可以简写为namsp

可以使用中括号为单独项目设置单独的命名空间,目前支持设置的有:func、block、storage、biome、bossbar、predicate、item、loot、item_modifier、dim或者dimension、entity。

另外,中括号内允许填default = xxxx,这表示除了func和storage外所有命名空间设置为xxxx。如果填default,则表示除了func和storage外所有命名空间设置为minecraft。(不推荐这样做, 而是采用在mcf.mcmeta设置的方式进行更改,这样可以设置默认值。)

可选只填写中括号,则其他值来源于设置或者默认。

namespace test{ ##将会把大括号块内所有的默认命名空间设置为test

}

namespace test[
	func = test1
	block = minecraft
	storage = minecraft
]{ ##除了括号设置的外,其他默认命名空间为test

}

namsp test[default = yuushya]{ ##除了func和storage设置为test外,其他设置为yuushya

}


namsp test[default]{ ##除了func和storage设置为test外,其他设置为minecraft

}

namsp [func = test]{ ##只有func设置为test

}

命名空间具有继承关系,嵌套命名空间可以对上一级命名空间进行修改。

注释增强

在原先的命令书写中,无法使用行注释和块注释,只能使用占用一行的#注释。

在JustMCF项目中,可以使用以下类型的注释

  • 行注释:采用##开始行内注释,和大多数语言中的//注释一致。
  • 块注释:采用#=为开头,=#为结尾,开始块注释,和大多数语言中的/* */块注释一致。这两种注释在构建成mcfunction文件的过程中将不会保留。
  • 占行注释:与原先的mcfuntion的注释一致。采用#开始一行的注释。这行注释将会在构建时加入mcfunction文件中,并保留在对应位置。

如果你想要让数据包在构建后保留注释,请使用原版的注释。

逻辑控制流简化

函数

JustMCF提供了带有参数的函数(见进阶函数一部分)与不带参数的普通函数。

JustMCF的普通函数与原版单个函数文件无异,但是提供了嵌套定义函数、单个文件定义多个函数的能力。

值得一提的是,函数不再依赖于复杂的文件夹路径,而是依赖于定义的名称,因此可以解放架构,自由设置工程项目的文件夹了。

函数声明

func foo:utils/test{                            ##自动创建一个mcf

    
}

将会在utils文件夹下创建test.mcfunction

func开头带有大括号的语句是定义语句而非执行语句,不带有大括号的将会被执行。

func foo:utils/test{                            ##自动创建一个mcf
	func foo:utils/test                         ##递归调用自己
    
}

如果需要立即执行则需要在前面加 ->(这其实是execute的run符号,请见后面),如:

func foo:utils/test{    
    ->func foo:utils/test/first{ 

    }
}

函数声明并注册到标签中

使用tagged可以将本函数注册到后面提供的函数标签中。

func foo:utils/test tagged #foo:utils{

}

func foo:utils/test tagged #foo:utils, #foo:test{

}

完整定义函数标签

声明函数标签的形式和声明函数类似。

对于函数标签来说,可以使用方括号设置属性,[replaced = true][replaced]功能一致。

func #foo:utils/all{ ##自动创建一个function tag
    func foo:utils/a{

    }
    func foo:utils/b{

    }
    func foo:utils/c 
    	##foo:utils/c不在此处定义,也可以这样被添加到本函数标签中。
    func #foo:utils/all/d{
    
    }
    func #foo:utils/all/e
    	##也可以在函数标签中嵌套定义函数标签和添加函数标签。
}

func #foo:utils/all/1 [replace = true]{

}
func #foo:utils/all2 [replaced]{

}

execute控制流

JustMCF对于execute做了大幅度简化,并提供了强大的函数分片功能。

原先需要拆分不同函数,并将函数名塞入execute run子命令的末尾的做法常常因为分文件而导致逻辑混乱,JustMCF直接支持在execute命令末尾完整地书写命令。

概览

原先命令

execute as @p store result score @s scb run function foo:test1

# foo:test1
say 1
say 1

简化为 :

{as @p}=> scb@s ->func foo:test1{
	say 1
	say2
}

## 或者如下:区别在于store的位置
{as @p => scb@s}->func foo:test1{}

总体来看,简化命令引入了两种符号:

  • 使用 ->符号代替 run单词,用于连接执行的各种条件和应该执行的命令

  • 使用 =>符号代替 store result单词

    使用 ?=>符号代替 store success单词

而引入了两个大括号{}->{}

前一个大括号是执行的各种条件。(这些条件也会被作为)

后一个大括号是执行的函数,花括号内是函数里面的命令。

如果不习惯简化命令的符号,请看本节的最后一小节。

执行者与执行方位设置

{
    as @e[]
    at @e[]
    align xyz
    anchored eyes
    pos ~ ~ ~
    pos @e[]                           ##position as
    rot ~ ~
    rot @e[]                            ##rotated as
    facing ~ ~ ~				  
    facing @e[] eyes                    ##facing entity
}->func foo:test

执行条件(if/unless 子语句)

JustMCF对if/unless子语句做了极大的简化。

对于if子语句来说,例如if entity @e可以省略if,或者省略entity,或者两者都省略只写@e。推荐使用省略entity的形式,这样对于unless子语句来说是统一的。

unless 语句不支持省略unless,只能省略entity。具体见下表:(注意biome不可舍去)

原子命令舍去标量部分(推荐)舍去if都舍去
if entity @sif @sentity @s@s
if block ~ ~ ~ stone{}if ~ ~ ~ stone{}block ~ ~ ~ stone{}~ ~ ~ stone{}
if blocks ~ ~ ~ ~ ~ ~ ~ ~ ~ allif ~ ~ ~ ~ ~ ~ ~ ~ ~ allblocks ~ ~ ~ ~ ~ ~ ~ ~ ~ all~ ~ ~ ~ ~ ~ ~ ~ ~ all
if biome ~ ~ ~ minecraft:beachif biome ~ ~ ~ minecraft:beachbiome ~ ~ ~ minecraft:beachbiome ~ ~ ~ minecraft:beach
if data entity @s Posif @s::Posdata @s::Pos@s::Pos
if data storage foo:str Numberif foo:str::Numberdata foo:str::Numberfoo:str::Number
if data block ~ ~ ~ Text1if ~ ~ ~::Text1data ~ ~ ~::Text1~ ~ ~::Text1
if predicate test:is_use_handif test:is_use_handpredicate test:is_use_hand
score见下方

简化后,可以很整齐地书写if子命令

{
    if @e[]                                      ##if entity
    if ~ ~ ~ stone{}                             ##if block
    if ~ ~ ~ ~ ~ ~ ~ ~ ~ all                     ##if blocks
    if biome ~ ~ ~ namespace                     ##if biome
    if test:is_use_hand						   ##if predicate
    if @e[]::{}                                  ##if data entity
    if ~ ~ ~ ::{}                                ##if data block
    if foo:str::{}                               ##if data storage
    if sb1@e[] >= sb2@s                          ##if score
    if sb1@e[] 2..5                              ##if score xxx matches
    if sb1#temp < 2                              ##if score xxx matches
}->func foo:test
if score语句

JustMCF将if score语句改进得更像是比较运算符的形式,原先的=改为了程序运算中的==,。见下表:

原子命令舍去标量部分(推荐)舍去if都舍去
if score @s temp < @s temp2if temp@s < temp2@sscore temp@s < temp2@stemp@s < temp2@s
if score @s temp = @s temp2if temp@s == temp2@sscore temp@s == temp2@stemp@s == temp2@s

对于matches命令则可以省略matches(其他省略形式同上,不再赘述),并且拥有其他直接使用比较运算符的表示形式

原if score matches命令省略matches比较运算符形式
if score @s temp 1if temp@s 1if temp@s == 1
if score @s temp ..1if temp@s ..1if temp@s <= 1if temp@s < 2
if score @s temp 0..1if temp@s 0..1
if score @s temp 0..if temp@s 0..if temp@s >=0if temp@s > -1

存储语句(store 子语句)

使用 =>符号代替 store result单词

使用 ?=>符号代替 store success单词

简化的存储语句写在第一个大括号内也可以,写在大括号外也可以

{
    as @e[] ?=> scb@s
    as @e[] ?=> scb1@s
    as @e[] ?=> scb2@s
}->func foo:test
{as @p}
?=> sb3@s                                    ##store success score
=> sb3@e[]                                   ##store result score
=> ~ ~ ~ ::Base int*3                    ##store result block
=> bossbar foo:bar value                     ##store result bossbar
=> @e[]::XXXX int*3                          ##store result entity
=> foo:storage::XXX int*3                    ##store result storage
-> func foo:test                             ##run function foo:test

转储到NBT时,可以省略类型和倍率,则默认为int 1

注意,bossbar关键字不可省略,而如果省略value或者max,则默认为value

在任意命令后边也可以跟上存储子命令

tellraw @s {"text":"hello"} ?=> sb1@s

执行语句(run 子语句)

使用 ->符号代替 run单词,用于连接执行的各种条件和应该执行的命令

直接执行命令

{as @p}->tellraw @s {"text":"hello"}

{as @p}->func foo:test

具名函数
{as @p}->func foo:test{
    tellraw @s {"text":"hello"}
    tellraw @s {"text":"hello again"}
}

会自动生成mcfuction文件,名为test.mcfunction

匿名函数

如果run部分没有指定函数名称,则支持自动生成名称。因此你可以这样写:

{as @p}->{
    tellraw @s {"text":"hello"}
    tellraw @s {"text":"hello again"}
}
##简洁风格

{as @p}->func{
    tellraw @s {"text":"hello"}
    tellraw @s {"text":"hello again"}
}
## 更为统一的样式

会自动生成mcfunction文件,名称不需要指定,会自动指定为所在文件、嵌套层数与匿名函数的编号的组合。

简化幅度较小的支持

JustMCF为不希望采用大幅度简化命令的用户提供了较为简化的支持,相比于上述简化,按照以下原则书写则是小幅度简化的样式:

  • 可以在第一个大括号前写exec
  • 可以使用run,而不是->
  • 可以使用store ?store,或者干脆使用原版,而不是=>?=>
  • 使用省略最少的语法。
  • 标识符使用原版命令
exec{ if entity @s as @s store result storage entity @s CustomName int 1 } run func test:foo

##所以为什么不接受以下语法呢:
{if @s as @s}=>@s::CustomName->func test:foo

条件语句

exist表达式

在JustMCF中,用{xxx:1b}null两种值来模拟bool表达式,为了和nbt的bool变量类型区分,我们称之为exist表达式。exist表达式用if语句中。

更直接地说,exec前一个大括号部分就是exist表达式。对于nbt来说,可以直接写在大括号外面。

{if @e} && {if ~ ~ ~ stone} && foo:stor::bool

由于存在不同形式的简化语法,实际上,上述语句可以写成

{entity @e} && {block ~ ~ ~ stone} && foo:stor::bool

可以将exist表达式赋值给nbt

foo:stor::bool_1 = {if @e} && {if ~ ~ ~ stone} 

exist表达式值可以为truefalse

foo:stor::bool_1 = true
		##data modify foo:stor bool_1 set value 1b
foo:stor::bool_1 = false
		## data remove foo:stor bool_1

逻辑运算符

优先级和表格顺序一致。

注意:exist表达式在进行熔断时,可以选择产生新的函数,或者直接在表达式下方所有命令加上execute if,这项设置在mcf.mcmeta中。

符号描述实现
!
&不可以熔断的且bool a=false;getcond1;getcond2;if cond1 if cond2 -> a=true;
|不可以熔断的或bool a=false;getcond1;getcond2;if cond1 -> a=true;if cond2 -> a=true;
&&可以熔断的且bool a=false;getcond1;if cond1 -> a=true;if a -> getcond2;if a unless cond2 -> a=false;
||可以熔断的或bool a=false;getcond1;if cond1 -> a=true;unless a -> getcond2;unless a if cond2 -> a=true;
( )括号

if语句

if语句由以下部分组成:

  • if eixst表达式 exec执行部分
  • else if eixst表达式 exec执行部分
  • else execRun部分

(注意关键字之间的空格)

由于execute简化提供了省略if的简化语法,因此在if语句中,可以直接省略exist表达式中的if,如下:

if {entity @e} && {scb@s <=1 } && foo:stor::bool_1 ->{

}
else if {entity @s}->{ ##匿名函数

}
else ->func foo:test{ ##具名函数

}

循环语句

支持break中途退出循环。break还可以写成return(foo:test),指定跳出的循环语句。

支持continue跳过本次循环节。continue还可以写成return(foo:test)+1,指定continue的循环语句。

while语句

用于条件循环,这里的大括号相当于exec的大括号

while{ if @e }->func loopname{

}

##自动生成
##execute if @e  run function loopname
####loopname.mcfunction
##execute if @e  run function loopname

也可以使用exist表达式的形式

while {entity @e} && {scb@s <=1 } ->func loopname{

}

for语句

用于遍历列表,这里的大括号相当于data的大括号,但是只支持含有data标识符的一个NBT操作表达式。

在大括号中,支持使用标识符[0]的形式取出每次遍历的列表值。

for{ foo:flower::list }->func loopname{
    foo:flower::list[0] = {}
}
##自动生成
##execute store result score loop int run data get storage foo:flower list 
##execute if score loop int matches 1.. run function loopname
####loopname.mcfunction
##data modify storage foo:flower list[0] set value {}
##data modify storage foo:flower list append from storage foo:flower list[0]
##data remove storage foo:flower list[0]
##scoreboard players remove loop int 1
##execute if score loop int matches 1.. run function ##loopname
for{foo:flower::temp = ["abcd","efgh","ojbk"] }->func loopname{

}
##自动生成
##data modify storage foo:flower temp set value ["abcd","efgh","ojbk"]
##....

命令聚合

命令聚合是将一系列相关的命令聚合到一起的设计,通常归并同性质的操作,将操作转为表达式。

命令聚合有2种格式。

  • name { operations } 的格式,通常是没有同一个对象的各种操作。
  • name object{ operations }的格式,通常是有关object的各种操作。

execute命令聚合

上述提到的 {}->{}格式换为 exec{}->func{}格式

data命令聚合

上述提到的NBT数据运算内容全部写在 data{}大括号里。

data{
    @e[] |= n{}                                    ##merge
    @e[]::Item |= @s::Item                         ##modify merge from
    @e[]::Item |= n{}                              ##modify merge value 
    @s::ArmorItems .. n{id:'iron_boots', Count:1b} ##append
}

scoreboard命令聚合

上述提到的scoreboard数据运算全部写在 scb{}大括号里。同上述聚合

display命令聚合

tellraw title scoreboard bossbar等具有显示作用的命令聚合在一起

TODO: json文本组件增强j{obj@s}等价于{"score":{"name":"@s","objective":"obj"}

display{
    @s.text j{}             ## tellraw @s {}
    @s.title j{}            ## title @s title {}
    @s.title times 10 70 20 ## title @s times
    @s.title clear
    @s.title {
        .actionbar j{}
        .times 10 70 20
    }
    @s.actionbar j{}        ## title @s actionbar {}
    @s.subtitle j{}         ## title @s subtitle {}
    
    @s.bossbar foo:newboss  ##bossbar set players
    bossbar foo:newboss j{"text":"New Boss"}
    bossbar foo:newboss {

    }
    scb test "分数"
    scb test {
        displayname "分数" 
        rendertype hearts
        display sidebar
    }
  
}

支持把选择器提出到前面的做法,子内容必须带.

display @s{
    .text j{}             ## tellraw @s {}
    .title j{}            ## title @s title {}
    .title times 10 70 20
    .title clear
}

display{}中也可以选择器提前。

display {
	@s.text j{}
	@s {
		.title j{}
	}
}

title命令聚合

title命令也有不带选择器和带选择器形式的聚合,子内容必须带.。与display不同的是,clear times子命令不用加.title

title {
    @s.title j{}
    @s.subtitle j{}
    @s.clear
    @s.times 10 70 20
    @s {
    	.title j{}
    }
}
title @s{
    .title j{}
    .subtitle j{}
    .actionbar j{}
    .clear
    .reset
    .times 10 70 20
}

bossbar命令聚合

初始化

bossbar foo:newboss "New Boss"  ##bossbar add

bossbar foo:newboss "New Boss" {
	color blue
	max 10
}

设置属性

bossbar foo:newboss {
    get max     ##bossbar get max|players|value|visible
    remove 
    color blue       ##bossbar set
    max 10
    name j{""}
    players @s       ##bossbar set players @s
    players default  ##bossbar set players
    style notched_6
    visible true
}

设置属性也可以为每一项加.

bossbar表达式

让bossbar像NBT操作那样方便

bossbar test:foo           ##bossbar get test:foo value
bossbar test:foo max       ##bossbar get test:foo max|players|value|visible
bossbar test:foo = 10      ##bossbar set test:foo value 10
bossbar test:foo max = 10  ##bossbar set test:foo max 10

item命令聚合

将有关物品的操作聚合在一起 item give clear loot。对于giveclear来说如果不希望使用+=-=可以选择直接使用单词。

item{
    @e[]::armor.chest = stone *4                         ##replace with
    @e[]::armor.chest = @s::armor.chest foo:modifier     ##replace from
    @e[]::armor.chest += foo:modifier                    ##modify
    @s += stone *4                                     ##give
    @s -= stone *4                                     ##clear
    ~ ~ ~ =                                   ##loot spawn
    ~ ~ ~ +=                                  ##loot insert
    ~ ~ ~::container.5 =                      ##loot replace block
    @s +=                                         ##loot give
    @s::container.5 =                             ##loot replace entity
    
}

对于命令中支持选择器的部分,item支持选择器提前的做法

item @s{
	armor.chest = stone *4
	armor.chest = @s::armor.chest foo:modifier
	.give stone *4
	.clear stone *4
}

item{
	@s {
		armor.chest = stone *4
		armor.chest = @s::armor.chest foo:modifier
		.give stone *4
	}
}

loot命令聚合

loot{
    ~ ~ ~ =                                   ##spawn
    ~ ~ ~ +=                                  ##insert
    ~ ~ ~::container.5 =                      ##replace block
    @s +=                                         ##give
    @s give									   ##give
    @s::container.5 =                             ##replace entity
}

对于loot的来源来说,按照原来的方式书写

loot{
	@s += loot test:loot_1                            ##loot
	@s += fish test:loot_1 ~ ~ ~ mainhand             ##fish
	@s += kill @e[]                                   ##kill
	@s += mine ~ ~ ~ mainhand                         ##mine
}

对于命令中支持选择器的部分, loot支持选择器提前的做法

loot @s{
	.give loot test:loot_1                      ##或者直接用+=
	container.5 = loot test:loot_1
}

loot{
	@s {
		.give loot test:loot_1
		container.5 = loot test:loot_1
	}
}

entity命令聚合

将有关实体的操作 summon kill tag effect attribute tp 以及之前提到的item display聚合到一起

声明玩家假名

对于JustMCF项目来说,凡是使用到的玩家假名,都需要提前声明。在使用时如果是带'#',则需要变为带'@'。例如:

##声明
entity(player) xxxx1          ## 设置mcf项目中会使用到的玩家假名,可以使用@xxxx1来选中玩家
entity(player) #test1, xxxx2

##使用
scb{
    scb#test1 = 1
    scb@xxxx1 = 2
}

初始化实体

生成实体

entity(pig) ~ ~ ~ n{CustomName:'{"text": "pig1"}'}               ##summon pig ~ ~ ~ {}

初始化使用假名。解析生成.mcfunction后,实际上指定了uuid,uuid可以在项目文件中配置/自动生成,可以使用@xxxx来选中该实体。

entity(pig) ~ ~ ~ xxxx n{CustomName:'{"text": "pig1"}'}
entity @xxxx{   
    .tp ~ ~ ~
}

初始化指定tag。解析生成.mcfunction后,实际上在NBT中加入了Tags:"tag1","tag2"

tag前面可以加.,但是后续对实体执行操作的语句是必须加.

entity(pig) ~ ~ ~ xxxx n{CustomName:'{"text": "pig1"}'} { .tag = tag1,tag2 }

初始化后直接进行操作。可以进行操作的内容见下

entity(pig) ~ ~ ~ xxxx n{CustomName:'{"text": "pig1"}'} {
	.tag = tag1,tag2 ##将会解析进初始化语句的nbt中
	.tag += temp      ##将会解析为tag add
}

对实体执行操作

对于display、item、loot、attr、title等命令聚合来说,在entity大括号内可以执行类似的语句,这里不再复制粘贴。

entity{}还可以直接不写,把内部的内容直接写在外面,这是entity表达式的写法。

entity{
    @s.tp ~ ~ ~                                    ##tp
    @s.tag += temp                                      ##tag
    @s.tag -= temp                                      ##tag
    @s.effect += speed(3) 20 true                       ##effect give @s speed 20 3 true
    @s.effect += speed 20 3 true
    @s.effect -= speed                                  ##effect clear @s speed
    @s.effect clear
    @s.kill
    @s.item::container.5 =                             ##item 
    @s.loot::container.5 =                             ##loot 也可以写成item
    @s.item {
    	give loot test:loot_1
    	give fish test:loot_1 ~ ~ ~ mainhand
    }
    @s.loot {
    	give loot test:loot_1
    	give fish test:loot_1 ~ ~ ~ mainhand         
    }
    @s.attr::generic.attack_damage +=                  ##attr 
}

对选择器选中的实体进行操作

entity @xxxx{
    .tp ~ ~ ~
    .tag += temp
}

entity {
	@s {
		.tag += temp
	}
}

attribute命令聚合

attr聚合主要关注点还是在于更改了modifier运算的符号,如下示例:

TODO:取代UUID,直接用name进行运算

attr{
    @s::generic.attack_damage all                     ##get
    @s::generic.attack_damage base *4                 ##base get
    @s::generic.attack_damage base =                  ##set
    @s::generic.attack_damage += 0-0-0-0-0 test(+3)   ##modifier add uuid name value add
    @s::generic.attack_damage += 0-0-0-0-0 test(*+3)  ##modifier add uuid name value multiply_base
    @s::generic.attack_damage += 0-0-0-0-0 test(*3)   ##modifier add uuid name value multiply
    @s::generic.attack_damage -=                      ##modifier remove
    @s::generic.attack_damage 0-0-0-0-0 *4            ##modifier value get
} 

attr也同时支持提出实体选择器

attr @s{
	generic.attack_damage all
	generic.attack_damage += 0-0-0-0-0 test(+3)
}
attr{
	@s {
		generic.attack_damage all
		generic.attack_damage += 0-0-0-0-0 test(+3)
	}
}

block命令聚合

将方块操作 setblock fill clone 聚合在一起

block{
    ~ ~ ~ minecraft:stone[] keep                    ##setblock
    ~ ~ ~ ~ ~ ~ minecraft:stone[] keep          ##fill
    ~ ~ ~ ~ ~ ~ ~ ~ ~ filtered oak_planks   ##clone
}

world命令聚合(TODO)

命令对象化

entity命令聚合内的命令可以单独存在并进行使用,就如同对entity对象进行操作一样方便。

TODO: 把所有有关选择器的命令都作为实体的方法

@s.kill 
@s.tag +=
@s.tp @r
@s.item += minecraft:stone                       ##give
@s.item::armor.chest = xxx 4			        ##item
@s.exec{}->{}                                    ##execute as

选择器增强

UUID替换

见entity命令聚合一节。

更多参数选项

使用pos=dpos=rot=来分别聚合x=,y=,z= dx=,dy=,dz=x_rotation=,y_rotation

如果写成pos=~ 1 2,则会被解析为y=1,z=2

增强的limit

可以强制每个选择器limit=1(除了@a),除非使用limit=..解除限制。

此外,limit支持正数表示和负数表示:

  • limit=+2解析为limit=2,sort=nearest
  • limit=2解析为limit=2
  • limit=-2解析为limit=2,sort=furthest

更加便捷的语法

可以在[]直接插入:

  • {}包裹记分板的比较。可以支持比较运算符的形式
  • n{}包裹NBT的内容。

支持()用来标记类型:type=

支持单纯数字,等价于limit=

支持换行。

@e[1]                                         ##@e[limit=1]
@e[+1]                                        ##@e[limit=1,sort=nearst]
@e[-1]                                        ##@e[limit=1,sort=furthest]
@a[1,level=10]
@e(item)[pos=~ 2 2 , dpos=1 2 3 , rot=2 4 ]
@e(pig)[n{Motion:[]},{scb1=1..0,scb2 < 0 }]

支持脚本

使用 {{ }}标定,内部是javascript脚本内容。

在脚本内容中使用#{ }#,将框起来的文本信息输出到外界。(原理为:自动替换为unfold())

注:这是用到了javascript脚本中的模版字符串,可以很方便地向脚本内进行插值。

{{
    const list = [1,2,3,4,5]
    for(const i of list){#{
        setblock ~ ~${i} ~ stone
    }#}
}}

如此,将会把以下命令插入文件的对应位置中

setblock ~ ~1 ~ stone
setblock ~ ~2 ~ stone
setblock ~ ~3 ~ stone
setblock ~ ~4 ~ stone
setblock ~ ~5 ~ stone

进阶函数设计

数据类型

基本数据类型(TODO)

nbt:可以省略,默认是nbt的类型

类型名称
scb
scb:int
nbt
nbt:bool
nbt:byte
nbt:short
nbt:int
nbt:long
nbt:float
nbt:double
nbt:string
nbt:byte[]
nbt:int[]
nbt:long[]
nbt:list
nbt:object
exist

NBT类型信息

类型信息是可以自己设计或者来自原版(例如生物),每一次modify from,其实就是把对应的类型信息转移到自己身上来。

提前设计的类型信息,可以用于自动补全、判断nbt类型等等。这个类型应该是鸭子的。

NBT类型信息加上只对本类型操作的func,就相当于class了。

JustMCF提供如下语法:

interface test:foo n{
    Name:"",
    Age:17,
    Information:{}
}

##data merge storage test:foo {Name:"",Age:17,Information:{}}

interface test:foo::Choose n{
	Name:"",
	Age:17,
	Information:{}
}

##data modify storage test:foo Choose set value {Name:"",Age:17,Information:{}}

TODO: 这样的内容将会提供一个类型,在构建mcfunction过程中,将会判断例如函数的类型的兼容性;或者在未来的语法补全中,提供补全内容。

带类型标记的赋值语法(TODO)

前置的方式进行类型的标记

nbt:float foo:test::value = 32f

进阶函数

JustMCF提供了带有参数的函数语法。这样的函数将会附带生成输入输出storage声明,在运行过程中,会使用到storage进行堆栈。由于Minecraft中的数据的modify是存值,因此堆栈会有性能损耗。请谨慎使用进阶函数进行递归操作。

在函数中,不带命名空间ID的storage操作都被默认为在当前函数栈操作;也可以将local::加到它的前缀上。对于栈的根标签的访问,可以用local::{}的方式进行,以防止使用{}形成的歧义。

不带类型标记的函数

func test:fun1(a,b){
	c = local::a         ##c没有命名空间id,a带着前置local::
	yield c
}

yield后面跟data标识符,将会返回函数中需要返回的值,将这个值复制到输出storage中。

return后面跟data标识符,与yield不同的是,它将直接截断函数,将返回值返回。return还可以指定返回的函数,例如return(foo:test) local::a

函数执行

foo:test::value = func test:func1(a,b)

函数的执行语句可以作为NBT操作的右值参与运算。

带类型标记的函数(TODO)

func test:func1(int a,int b) int {
	
}

附录:贡献者

附录:配置项介绍

项目的mcf.mcmeta文件:(注释在行的下方)

{
    "file": {
        "mcfunctionGenerateMode": "cover",
//设置函数标签生成的模式
        "functionTagGenerateMode":"cover"
//设置函数生成的模式
    //"cover" 覆盖原有,
    //"skip" 跳过,
    //"append" 追加到原文件末尾,
    //"prepend" 添加到原文件头部,
        
    },
    "namespace": {
//设置以下选项的命名空间,这些将会替代原先你在命令中空出来的默认值(minecraft)
    //也就是说,原本需要命名空间的例如:func test:foo在设置"func":"test"为后,可以写成func foo 
    //目前,只能
        "block": "minecraft",
        "storage": "minecraft",
        "func": "minecraft",
        "biome": "minecraft",
        "predicate": "minecraft",
        "dimension": "minecraft",
        "item": "minecraft",
        "item_modifier": "minecraft",
        "loot": "minecraft",
        "bossbar": "minecraft",
        "entity": "minecraft",
        "advancement":"minecraft"
    },
    "selector": {
        "limitDefaultOne":false
//是否让除@a外的选择器默认limit=1?(使用'limit=..'来解除默认'limit=1')
    //详见选择器增强一节
    },
    "scbExpression": {
        "tempScbObjectiveName": "justmcf-temp-scoreboard",
//设置连续运算时的临时记分板的名称
    //连续运算时涉及到的运算优先顺序,会用临时记分板记录临时计算值
        "useConstNumberScbObjective": false,
//是否使用常量记分板?
    //如果在连续运算中使用了常数,它将会记录并且编译时在init.mcfunction生成初始化命令
        "constNumberScbObjectiveName": "justmcf-const-scoreboard"
//如果你选择使用常量记分板,设置名称
    
    },
    "functionStatement": {
        "flatWhenOneCommand": true,
//如果将要生成的函数文件只有一条命令,是否将它扁平化到execute命令的run后面?
    //例如execute run function test:foo,在test:foo函数中只有say 1
    //则会被扁平化为execute run say 1
    //请注意:如果只有一条exec命令,则扁平化结果和原先的效果不等价,因此不会进行扁平化
        "stackNamespaceId":"justmcf:program",
//设置函数使用storage栈的的命名空间ID,格式为namespace:id
    //对于进阶函数来说,每次调用函数都会开一个用于存储局部变量的storage,
    //这个storage会存储在一个全局的列表中,这个列表称为“栈”。
    //在调用子函数时,这个列表会加入子函数的局部变量storage,在出子函数时,会删除这个storage
        "commonIO":false
//所有函数共用输入输出的storage吗?选否将为各个函数使用不同的空间
    //例如,func test:foo将会用storage test:foo input作为输入空间,test:foo output作为输出空间
    },
    "loopStatement": {
        "recursionPartNewFunctionFile":true,
//for语句的递归调用部分是否放进一个新的函数中?
    //递归调用部分还是指,for循环中列表转动、判断是否继续循环的那个部分。
        "stackNamespaceId":"justmcf:program",
//设置循环语句使用storage栈的的命名空间ID,格式为namespace:id
    //对于循环语句来说,使用到break、continue时,将会生成一个{break(continue):1b}存在storage中。
    //栈的解释参见"functionStatement"部分。
    },
    "existExpression": {
        "stackNamespaceId":"justmcf:program",
//设置“存在表达式”使用storage栈的的命名空间ID,格式为namespace:id
    //对于存在表达式来说,使用到存在表达式时因为计算顺序需要存储临时值,这个临时值存在storage中。
    //栈的解释参见"functionStatement"部分。
        "everyConditionNewFunctionFile":true
//编译“存在表达式”时,遇到熔断机制时是否创建新的函数文件?选否将使用execute if data覆盖分支
    //熔断机制是指,a&&b当a为false不会执行b,a||b当a为true时不会执行b。
    },
    "entityNameMap": {
//JustMCF提供了实体名称和uuid的映射表,在解析为mcfunction过程中会自动生成映射表,也可以手动编辑
    //"name1":"player",这样可以用@name1来作为玩家名
    //"name2":"0-0-0-0-0",这样可以用@name2来代替uuid
    }
}
0.0.14

1 year ago

0.0.13

1 year ago

0.0.12

1 year ago

0.0.11

1 year ago

0.0.10

1 year ago

0.0.9

1 year ago

0.0.8

1 year ago

0.0.7

1 year ago

0.0.6

1 year ago

0.0.5

1 year ago

0.0.3

1 year ago

0.0.2

1 year ago

0.0.1

1 year ago