本篇教程由作者设定使用 CC BY-NC-SA 协议。

前言

本教程的资料来源于crafttweaker官方文档以及youyihj大大制作的教程。

我是教程的搬运工~

目的在于一次性讲清楚crt的基础知识。

如果你吃透了本篇教程,相信你一定可以自己动手写脚本了,而且不是简单的配方添加,是更高级的运用。

ZenScript 基础

ZenScript 是一种脚本语言,可以看做 C++ 和 Java 等编程语言的同类。

以“;”表示一个代码语句的完成。

编写第一个脚本

在对应版本文件夹的根目录下找到 scripts 文件夹,新建 txt 文件,随后把后缀改为 .zs

使用 VScode 或其他文本编辑器打开文件,并输入下列语句:

print("Hello world!");

现在加载 Minecraft,可在 crafttweaker.log 文件中看到该语句的执行效果(即在日志中输出 Hello world!)

日志文件格式

日志文件在其输出中使用特定的格式,该格式为:

[LOADERSTAGE(加载阶段)][SIDE(加载端)][TYPE(严重程度)] <message>

加载端分为:客户端(CLIENT) 和 服务端(SERVER)

严重程度由轻到重依次为:一般信息(INFO),警告(WARN),错误(ERROR),致命(FATAL)

使用上文的示例,输出将是:

[PREINITIALIZATION][CLIENT][INFO] Hello world!

意思是说在预初始化阶段,客户端输出了一条一般信息,内容为 Hello world!

日志格式的目的是便于调试,唯一不使用该格式的时候是用于命令转储,在这种情况下,它只是打印消息,这样做是为了复制粘贴转储更容易。

注释

注释可使脚本文件更具可读性和更易于理解!

ZenScript 支持3种类型的注释,分别是:

单行注释:

// 我是单行注释

另一种单行注释:

# 我也是单行注释

多行注释(斜杠和星号间没有空格):

/ * 我是
多行注释 * /

注释的语句是给人阅读的,程序加载时不会读取注释语句!

有些代码写出来会报错,但可能是你好长一段时间的奋斗成果,如果还想继续下去,不想直接删去,就可以先把代码注释掉。

特殊术语

ZenGetter

一种从对象中获取信息的方式。

例如获取物品铁锭的显示名字,就要用 ZenGetter:

<minecraft:iron_ingot>.displayName

ZenSetter

ZenSetter 的工作方式与 ZenGetter 几乎相同,唯一的区别是ZenSetter是设置属性,而ZenGetter是获取信息。

例如将铁锭的显示名设置为“silver”,就要用 ZenSetter:

<minecraft:iron_ingot>.displayName = "silver";

ZenMethod

ZenMethod 就是实打实的方法,需要参数,会执行一些特殊的东西。可能不返回值,也可能会返回。具体看各个 ZenMethod 的描述。

信息技术基础

局部变量

程序中,许多数据都会以“变量”的形式出现。

如果说数据是一辆辆车,那么变量就是一个个停车位。变量是用来存储数据的。

在 ZenScript 中,我们可以使用如下代码声明局部变量:

var a as int = 0;

其中,var 是声明局部变量的关键字,告诉程序我们要开始声明局部变量了。

a 是变量的名字,每个变量都需要起名。变量的命名具有一定的规范性,可以使用“小驼峰”命名,也可以使用下划线将单词间隔开。

小驼峰命名:myFirstName

下划线命名:my_first_name

有关变量命名的规范性可以自行查阅相关资料。

变量名不要出现中日韩统一表意文字中的任意字符!

0 是一个数据,在这里是变量 a 的初始值。初始值可以按照需要任意赋值,但是声明变量必须具有初始值!

as int 是 ZenScript 中转换数据类型的方法,可以将数据的类型转为 int 类型。

局部变量只能在当前脚本中使用,在其他脚本文件中无法调用局部变量。

基本数据类型

刚刚我们声明局部变量时接触到了数据类型的概念,在此我们简单介绍一下基本数据类型。

基本数据类型为最最基础的,直接存储一个值的。它们没有任何方法、ZenGetter、ZenSetter可用。

每一个数据都有其各自的类型,在 ZenScript 中,有下面八种基本数据类型:

整型

表示整数,有byte, short, int, long四种类型,整数默认就是int类型

byte: 使用1个字节,8位,256种形态,值域为[-128, 127] ∈Z

short: 使用2个字节,16位,65536种形态,值域为[-32768, 32767] ∈Z

int:使用4个字节,32位,4294967296种形态,值域为[-2147483648, 2147483647] ∈Z

long:使用8个字节,64位,18446744073709551616种形态,值域为[-9223372036854775808, 9223372036854775807] ∈Z

浮点型

表示小数,有float, double两种类型,小数默认就是double类型

float:使用4个字节,32位,单精度,能精确到6-7位小数,值域为 {-∞}∪[-2^128, -2^-149]∪{0}∪[2^-149, 2^128]∪{+∞}

double:使用8个字节,64位,双精度,能精确到15-16位小数,值域为{-∞}∪[-2^1024, -2^-1074]∪{0}∪[2^-1074, 2^1024]∪{+∞}

注意:令0.0除以0.0会导致计算错误,返回NaN非数值

布尔型

表示真假,只有true和false两个值,有bool一种类型

无类型

表示null,用于函数或方法以表明该函数或方法无返回值,有void一种类型

其余事项

在定义变量的时候,多使用“as 数据类型”这样的语句来避免不必要的报错。

可以在一些数据后面加上字段来表明该数据的数据类型,例如:

0.0f就表示float类型的0.0,同理:d表示double类型,l表示long类型。

基本运算

赋值

在 zenScript 中,使用“=”进行赋值。

赋值就是将变量的值设为指定值的操作,是一个“设置”的动作,和数学中的“等于”有一定区别。

数学运算

在 zenScript 中,使用“+”,“-”,“*”,“/”,“%”,“~”来完成加、减、乘、除、取余、连接字符串的运算操作。

在运算符后面跟一个“=”,变成类似于“+=”,“-=”之类的符号,称为赋值运算符,进行自运算。

a += 1 等价于 a = a + 1

使用自运算可大幅度简化一些脚本。

逻辑运算

在 zenScript 中,使用“&&”,“||”,“^”,“!”,“==”,“!=”,“<”,“<=”,“>”,“>=”

来完成与、或、异或、非、相等、不等于、小于、小于等于、大于、大于等于的逻辑运算操作。

运算错误

当数学运算结果发生意外时,很有可能是你使用不同类型的数字。 比如 13 % 6.5 结果为 1,可是正确结果应该是 0 啊,究竟发生了什么?

ZenScript 总是会对运算的两个数据类型进行处理,使其成为相同类型。在上述例子中,它就会将第二个数据转换,用以匹配第一个数据。

在上述例子中,计算就会处理成 13 % 6,第二个数字(双精度浮点型)就会被强制转换为第一个数字的类型(整型)。

可以将13转为 double 类型以避免运算出错。

Crafttweaker 基础

尖括号调用

Zenscript 用<>来表示游戏的一个对象。比如物品、矿辞。

通过在尖括号内的字符串,可以获取游戏内的特定物品、矿辞。

例子:

<item:minecraft:apple>  // 获取苹果物品(IItemStack) 这里的 item: 可省略
<ore:ingotIron> //获取铁锭矿辞(IOreDictEntry)
<entity:minecraft:creeper> //获取原版苦力怕实体定义(IEntityDefinition)
<blockstate:minecraft:stone> //获取原版石头方块状态(IBlockState)
<potion:minecraft:speed> //获取原版速度效果(IPotion)

可以在 id 后加上 Meta 值,Meta 值就是使用 /give 指令需要的特殊值。

例如 <minecraft:wool:14> 就表示红色羊毛,而非白色羊毛。

工具的耐久也属于 Meta 值。

Meta 值允许使用 * 通配符,以表示所有 Meta 值。

在尖括号后加上 .withTag(),可在小括号中写入该物品的 NBT 数据。

普通数据类型

和基本数据类型类似,但是普通数据类型一般可以对其使用 ZenGetter、ZenSetter 和 ZenMethod。

对于原版 Minecraft 的所有普通数据类型,可以在 CraftTweaker 官方文档的 vanlia 子目录中找到:https://docs.blamejared.com/1.12/zh/index

常见的普通类型有:

字符串(string):文本,可以使用“==”等逻辑运算符

物品栈(IItemStack):一个物品,如<minecraft:stone>

材料(IIngredient):一个或多个物品,如<minecraft:stone>、<ore:ingotIron>

矿物辞典(IOreDictEntry):一个矿辞代表的多个物品,如<ore:ingotIron>

流体栈(ILiquidStack):一种流体,如<liquid:water>

导入模块

一般地,使用除 string 类型外的其他普通类型,非全局函数,以及事件函数等等,都需要导入模块。

导入模块使用 import 关键字,并且必须处在脚本文件的开头。

例如:

import mods.jei.JEI;

这将从模组Just Enough Items中导入JEI模块。

导入 JEI 整个模块后,我们就能够使用这个模块下的所有函数(方法)了,当然在使用 JEI 模块的函数是需要指定模块。

例如:

JEI.hide(<minecraft:diamond>);

这将从JEI显示页面中隐藏钻石。

我们也可以在导入模块的时候直接导入hide函数,这样就不需要在使用时指定模块了

例如:

import mods.jei.JEI.hide;

如此一来,上述脚本可以直接写成:

hide(<minecraft:diamond>);

在导入模块时使用as关键字,可以自定义该模块的名称。

import mod.jei.JEI.hide as h;

这样就把hide函数重命名为h函数,上述脚本便可直接写成:

h(<minecraft:diamond>);

数组

在高中数学必修一中,我们接触到集合这一概念,实际上在信息技术中,集合就是数组。

数组是一个容器,一个数组存储的元素的类型都是相同的。

你可以往数组里存取数据,也可以修改数组里的数据。

你可以为数组指定初始元素,也可以不指定。

与集合不一样的是,数组里的元素具有顺序,可重复。

举例:

var array1 as int[] = [];   //定义一个 int 类型的数组,没有初始元素
var array2 as int[] = [10,20,30];   //定义一个 int 类型的数组,有三个初始元素
var strings as string[] = ["apple","carrot"];   //定义一个 string 类型的数组,有两个初始元素
var items as IItemStack[] = [<minecraft:apple>,<minecraft:carrot>];   //定义一个 IItemStack 类型的数组,有两个初始元素

数组里的元素都具有下标,你可以通过下面的方式为数组添加元素,或修改数据。

var numbers as int[] = [10,20,20];   // 定义一个 int 类型的数组,有三个初始元素
numbers[2] = 30;   // 将下标为 2 的元素修改为 30 
numbers += 40;   // 往数组的末尾添加一个元素

这样修改后的数组就变为:[10,20,30,40]

注:数组第一个元素的下标为 0,往后为 1, 2, 3......以此类推。

数组有一个length属性,返回数组的元素个数。

var array as int[] = [2,4,6,8];
print(array.length); // array 的元素个数为 4,输出 4

多维数组

在集合中,我们见过这种东西:{{1,2,3},{0,-1,-2}}

可以看到,一个大集合中有着两个小集合。

这个概念类比到数组上就是多维数组,简而言之就是数组套数组。

举例:

var array as int[][] = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

关联数组

关联数组是一种特殊的数组,内部存储的是“键值对”,目的是令键(key)和值(value)一一对应。

关联数组没有下标,取代下标的是 key(键)。

你可以用 ZenScript 的任何数据类型作为 key (除了数组)。

键值对的形式为 key: value,键在前,值在后。

可使用变量作为 key,变量名会被解析为字符串。

数组可以作为 value,但不可以作为 key。

举例:

var map as IItemStack[string] = {
    gold : <minecraft:gold_ingot>,
    iron : <minecraft:iron_ingot>,
    diamond : <minecraft:diamond>
};
// 定义一个名称为 map 的关联数组,key 为 string 类型,value 为 IItemStack 类型

这样每个字符串就和物品堆一一对应,我们就可以使用 map[iron] 来获得 <minecraft:iron_ingot>。

使用 ZenSetter 就可以更改关联数组内键所对应的值了。

有人说,我老是在定义关联数组时把键和值的数据类型写反,怎么巧记?

很简单,联系一下我们之前将的下标,既然所谓的“键”取代了原来的下标,那么方括号里的自然是键的数据类型,而不在方括号内的就是值的数据类型。

循环和遍历

for 循环

for 循环又叫“计数循环”,允许一段代码多次执行,同时能增强脚本的可读性。

让我们看看一个普通 for 循环如何使用:

for i in 0 .. 10 {
    print(i);
}
for i in 0 to 10 {
    print(i);
}
// print() 函数都会被执行 10 次,依次输出: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9。
// 第一种书写和第二种书写没有区别,你可以任意选择,我个人喜欢用 to。

以上两个例子的执行结果都相同,其中:

i 是一个变量,每次循环结束,它的数值都会 +1。从 0 开始,直到 10 结束,但不包括 10,处于一个左闭右开的区间中。

每次循环都会执行大括号内的语句,你也可以写多条语句。

通过 for 循环,我们可以进行遍历数组的操作:

var numbers as int[] = [2, 4, 8, 16, 32, 64];
for i in 0 to numbers.length {
    print(numbers[i]);
}
// 输出 2, 4, 8, 16, 32, 64

while 循环

while 循环又叫“条件循环”,也可以多次执行代码。

当小括号内的判断表达式为 true 时,便会执行大括号内的语句,然后再次判断表达式,true 则继续循环,false 则不执行循环。

让我们看看一个普通 while 循环如何使用:

var i as int = 0;
while (i <= 10) {
    print(i);
    i += 1;
}
// print() 函数会执行 11 次,依次输出 i 为: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

foreach 循环

foreach 循环又叫“增强 for 循环”,用来遍历数组,可读性高,不需要下标,遍历数组中的每一个元素。

定义一个数组并遍历:

var numbers as int[] = [2, 4, 8];
for number in numbers {
    print(number); // 输出 2, 4, 8
}

定义一个关联数组并遍历:

var map as string[int] = {
    1 : "一",
    2 : "二",
    3 : "三",
    4 : "四"
};
// key 遍历法
for key in map {
    print(key); // 输出 1, 2, 3, 4
}
// key-value 遍历法
for key, value in map {
    print(key ~ "-->" ~ value); // 输出 1-->一, 2-->二, 3-->三, 4-->四
}
// entry 遍历法
for entry in map.entrySet {
    print(entry.key ~ "-->" ~ entry.value); // 输出 1-->一, 2-->二, 3-->三, 4-->四
}

break 与 continue

break 与 continue 都可以在 for 循环和 while 循环中使用。

执行到 break 语句后将直接结束当前循环。

for i in 0 to 5 {
    if (i == 3) {
        break;
    } 
    print(i);
}
// i 等于 3 时,执行 break,结束循环
// 输出 0, 1, 2

执行到 continue 语句后,跳出本次循环,并进入下一次循环。

for i in 0 to 5 {
    if (i == 3) {
        continue;
    }
    print(i);
}
// i 等于 3 时,执行 continue,跳出当前循环,并进入下一次循环
// 输出 0, 1, 2, 4, 5

in/has 操作符

你可以用 in/has 操作符来判断 A 是否包含 B。下列的示例中的 in 均可以替换为 has。

//检测加载模组
if (loadedMods in "thermalfoundation") {
    print("热力基本mod已被加载");
}
//检测材料对象
if (<ore:ingotIron> in <minecraft:iron_ingot>) {
    print("铁锭的矿物词典是正确的!");
}

请注意分清左右两个参数:只有当所有在 in 之后的对象可以在 in 之前的对象找到时,结果才为 true。用 has 可能更符合英语语法,更加便于理解。

条件函数

if 关键字是书写条件语句判定条件的部分,当小括号内的表达式为 true 时,就会执行 if 大括号内的代码块。

注意,两个等号才是比较运算符,一个等号是赋值运算符!

单 if 结构

if 小括号内的表达式也叫判断表达式。

代码块由语句组成。代码块可以有多条语句,可以只有一条语句,也可以没有语句。

//第一个例子
if (1 >= 0) {   // 判断表达式为 true
    print("1大于等于0!");   // 执行 if 大括号内的语句
}
//第二个例子
if (1 == 1) {   // 判断表达式为 true
    print("1等于1!");   // 执行 if 大括号内的语句
}
//第三个例子
if (1 < 0) {   // 判断表达式为 false
    print("1小于0!");   // 不执行 if 大括号内的语句
}

if - else 结构

else 关键字可以作为条件语句的后半部分,当 if 判断表达式为 false 时,就会执行else大括号内的代码块。

//第一个例子
if (1 >= 0) {  // 判断表达式为true
    print("1大于等于0!");  // 执行 if 大括号内的语句
} else {
    print("1小于0!");   // 不执行 else 大括号内的语句
}
//第二个例子
if (1 < 0) {  // 判断表达式为false
    print("1小于0!");  // 不执行 if 大括号内的语句
} else {
    print("1不小于0!");  // 执行 else 大括号内的语句
}

if - else - if 结构

如果else后再接着判断条件呢?你可以使用 if-else-if。

var mark as int = 90;   // 定义一个成绩,分数为90
if (mark < 60) {   // 第一次判断,分数是否低于60
    print("成绩不及格");   // 90不低于60,不执行此处语句
}
else if (mark < 90) {   // 第一次判断为false,进行第二次判断,分数是否低于90
    print("成绩及格");   // 90不低于90,不执行此处语句
} else {
    print("成绩优秀");   //执行此处语句,输出"成绩优秀"
}

所有的条件控制语句都可以嵌套使用,你可以在一个条件控制语句的代码块里再写条件控制。

嵌套三层以上的if可读性会越来越差,不推荐使用过多的嵌套。

三元操作符

对于一些简单的判断,却需要用 if else 写好几行,不嫌麻烦吗?可以用三元操作符,简化操作。

举例:

c = flag ? a : b;
//如果 flag 为 true,则将 c 赋值为 a,反之为 b

全局函数

全局函数是无需导入即可调用的函数。

以下是所有的全局函数:

print

将字符串信息打印到 CraftTweaker 的日志中。

格式:print(String message);

举例:print("Hello World!");

无返回值

totalActions

返回一个 int,显示注册了多少个全局函数。

格式:totalActions();

举例:totalActions();

enableDebug

启用调试模式。

不过,最好使用调试预处理器。

格式:enableDebug();

举例:enableDebug();

无返回值

isNull

检测对象是否为 null,经常用这个规避空指针异常。

对基本数据类型对象(int,float 等)无效。

格式:isNull(Object);

举例:isNull(<minecraft:dirt>);

返回一个 bool

instanceof

检测对象是否为某数据类型

格式:variable instanceof type;

举例:entity instanceof IEntity;

返回一个 bool

max

返回较大的数

格式:max(int number1, int number2);

举例:max(10, 11);  //得到 11

min

返回较小的数

格式:min(int number1, int number2);

举例:min(10, 11);  //得到 10

pow

返回一个double,值为 number1 的 number2 次方

格式:pow(double number1, double number2);

举例:pow(2.0, 4.0);  //得到 16

全局和静态变量

声明全局和静态变量

声明全局变量或静态变量非常简单。注意 “as 数据类型” 不能省略,且已定义的全局、静态变量之后不能再次定义,修改其类型或值。

全局变量可以直接在其他脚本中使用,静态变量还需要跨脚本调用。

// 定义一个全局变量
global stone as IItemStack = <minecraft:stone>;
// 定义一个静态变量
static dirt as IItemStack = <minecraft:dirt>;

含有全局变量的脚本需要用优先级预加载器保证其优先加载。

局部变量可以覆盖全局变量。

全局变量可以在任何地方直接调用,但坏处就是你可能不记得该变量的定义处在哪里。

静态变量的使用需跨脚本引用,但好处就是很容易知道该变量是在哪个脚本里定义的,容易维护。

可修改的全局和静态变量

有一个方法可以绕过全局、静态变量定义后不能再次修改的窘境。

// 在脚本中定义一个静态数组
static array as int[] = [1];
// 修改数组内第一个元素的值
array[0] = 10;
// 输出数组第一个元素的值
print(array[0]);

尽管 array as int[] 这个数组不能再次定义和修改其类型,但数组内部的元素还是可以修改的。

不只是数组,你也可以定义一个静态的关联数组,尽管该关联数组不能再次定义和修改类型,但是同样可以多次修改其内部的数据。

预处理器

预处理器会在脚本执行之前执行。

它们可以执行各种行动:比如启用调试模式或者不显示尖括号调用错误。

预处理器需要用 # 注释 函数来调用。

因而写注释一定要注意,因为可能会写一条包含预处理器关键字的注释。

注:全局使用表示只需要一个脚本有该预处理器,所有脚本均会受到该处理器的影响。

#debug

将会在 generated 文件夹内输出所有脚本编译出的 class 文件

可全局使用

#ignoreBracketErrors

忽略尖括号引用错误(当物品ID什么的打错不会报错,而是改为null)

不可全局使用

#loader loaderName

设定脚本加载器,默认脚本自带#loader crafttweaker

不可全局使用

#modloaded modID

只有指定的 mod 加载时这个脚本才会加载,可以设定多个 modID,也可以取非值(modid前面加感叹号)

不可全局使用

#norun

脚本将不会加载,虽然使用/ct syntax命令还是会检测其的语法错误

不可全局使用

#priority number

设定脚本加载优先级,数字越大越先加载。优先度一样的脚本,将按字母顺序依次加载

不可全局使用

#ikwid

脚本产生的警告和错误将只在日志中打印出来,不会在游戏中显示

可全局使用

#sideonly sideName

sideName 可使用 client 或 server,指定该脚本只在客户端或服务端中运行

不可全局使用

#profile

会在日志打印出每次修改配方的花费的时间

可全局使用

#disable_search_tree

禁用加载脚本前的配方表的重新计算,可能能加快脚本加载速度

可全局使用

自定义函数

有时 CraftTweaker 本身以及它的附属提供的函数根本不够用。接下来我们将会讲解如何构建自定义函数!你甚至可以在函数中嵌套函数。

基本格式:

function 函数名(参数表) as 返回类型名 {
    [代码]
    return 函数返回值;
}

然而事实上,只有 function 关键字和函数名是必要的,若是一个不需要参数的函数,则不需要参数表,只有一对小括号。

若不需要返回值,则不需要 “as 返回类型名” 和 “return” 。

如果这个函数需要的代码只需要一行,直接写在 return 所在的一行即可,不需要其他代码。

return 关键字将会把指定的值返回至函数的调用点上,执行后续操作。

若函数处理时碰到 return,程序将跳出函数,不再执行之后的函数内操作。所以一般 return 会在函数代码的最下方。

举例:

//自定义一个函数,将删除配方和添加配方一体化
function recipeTweak(isShaped as bool, out as IItemStack, input as IIngredient[][]) {
    val recipeName as string = getItemName(out);
    recipes.remove(out, true);
    if (isShaped) {
        recipes.addShaped(recipeName, out, input);
    } else {
        recipes.addShapeless(recipeName, out, input[0]);
    }
}

扩展方法

扩展方法可以向现有类添加更多方法。

扩展方法和自定义函数很像,有关函数和方法的区别议论纷纷,各个语言的规定都不太一样。

在 CraftTweaker 中,它们的区别或许在于:扩展方法在类中,而自定义函数在类外。

由于扩展方法在现有类里,因此声明完扩展方法后,只需要导入其所属的类就可以直接调用,而不需要像自定义函数那样使用跨脚本调用。

注意,扩展方法是静态的。按照如下格式声明扩展方法:

$expand 类名$方法名(参数 as 参数数据类型) as 返回值数据类型 {
    [代码]
    return 返回值;
}

在声明扩展方法时,要先导入模块,也就是导入其所属的类。

注:类是一个信息技术概念,在 CraftTweaker 中,除了 string 以外的其他普通数据类型都是一个个类。

举例:

import crafttweaker.item.IItemStack;
// 为 IItemStack 类扩展了一个方法,方法名为 show
// 无返回值,使用后会在日志中输出某物品的命令字符串。
$expand IItemStack$show() as void {
    print(this.commandString);
    // this 关键字指向的是当前对象的引用
    // 命令字符串(commandString)是你在 ZenScript 中调用此项的方式。这可以是尖括号调用或与其类似的东西。
}
// 使用 show 方法
<minecraft:apple>.show();
// 最终在日志中输出 “<minecraft:apple>”

跨脚本调用

跨脚本调用主要是针对静态变量,但自定义函数和类也需要它!

跨脚本调用需要以 scripts. 开头,匹配相对路径。先匹配目录,再匹配脚本和值。

举例:

在a.zs脚本中声明了一个静态变量,并自定义了一个函数

static myVal as string = "myVal";
function makeLine() {
    print("---------------");
}

在b.zs中调用a.zs的静态变量和函数

print(scripts.a.myVal);
scripts.a.makeLine();

我们还以使用 import 来将其作为一个模块导入,例如在c.zs中调用a.zs中的内容

import scripts.a;
print(a.myVal);
a.makeLine();

配方修改

工作台配方

添加有序合成配方

基本格式:

recipes.addShaped(recipeName, output, inputbox);

1.12 中 Mojang 修改了合成的注册系统,每个配方以一个 json 文件储存,同时每个配方有一个ID。

当你打开高级提示框(F3+H)时,可以在 JEI 中看见每一个配方的ID。

但实际上,你也可以省略配方ID,就像旧版本那样,这样 CrT 会使用 hash 值自动指定配方ID。配方名不能重复。

output 即为配方输出。inputbox 即为需要的物品。比如我们拿铁护腿举个例子。

它在 CrT 是这么表示的:

//配方名略去
recipes.addShaped(<minecraft:iron_leggings>, [
    [<ore:ingotIron>, <ore:ingotIron>, <ore:ingotIron>],
    [<ore:ingotIron>, null, <ore:ingotIron>],
    [<ore:ingotIron>, null, <ore:ingotIron>]
]);

可以看到,output 必须是一个 IItemStack,而 input 是一个二维数组,内部是三个一维数组,表示工作台中每一层放的物品。

每个一维数组内部是三个 IIngredient,没有物品的地方用 null 表示。

什么?到现在还不知道 IItemStack 和 IIngredient 是什么?第四部分第二章欢迎您

如此一来就可以添加工作台的配方了。

添加无序合成配方

基本格式:

recipes.addShapeless(recipeName, output, inputbox);

以末影之眼为例,它在 CrT 是这么表示的:

//配方名略去
recipes.addShapeless(<minecraft:ender_eye>, [
    <minecraft:ender_pearl>, <minecraft:blaze_power>
]);

这里个 inputbox 只是一个简单的一维数组,因为是无序配方,无所谓物品放在哪一行,自然就不需要二维数组来确定是工作台中的哪一行了。

可用:

recipes.addHiddenShapeless(recipeName, output, inputBox);

添加隐藏无序合成。

移除工作台配方

recipes.remove(item, NBTMatch);

删除物品的所有配方,NBTMatch(可省略)为布尔值(true/false)。

如果为true,删除配方的物品将匹配NBT。默认(即省略的话)为false,不匹配NBT。

recipes.removeShaped(item, inputBox);

删除物品的一个特定有序配方,inputBox可省略,这样指删除物品的所有有序配方。

recipes.removeShapeless(item,inputBox);

删除物品的一个特定无序配方,inputBox可省略,这样指删除物品的所有无序配方。

recipes.removeByRecipeName(recipeName);

以配方ID为依据删除配方。可以使用正则表达式。

recipes.removeByMod(ModID);

删除一个mod的所有配方。

recipes.removeAll();

删除游戏内所有配方。

熔炉配方

添加熔炉配方

基本格式:

furnace.addRecipe(output, input, xp);

其中,output 输出,input 输入,xp 为给予经验(使用双精度浮点数,即可用小数,可以省略)。

移除熔炉配方

基本格式:

furnace.remove(output, input);

移除将 input 烧成 output 的熔炉配方,input 可省略,这样指删除所有烧成 output 的配方。

熔炉燃料

基本格式:

furnace.setFuel(input, burnTime);

将一个物品设置为燃料,并且设定时间,时间单位采用游戏刻。煤炭为1600,烧一个物品需要 200。将 burnTime 设置为 0 即为删除该燃料。

矿物辞典

矿辞诞生

forge 矿物辞典(forge ore dictionary,简称OD) 来源于 RedPower2 的作者 eloraam 的一个帖子,

当时有人争议 rp (红石力量) 和 ic (工业) 添加同类的矿物(铜矿、锡矿)引起的混乱,于是 forge 矿物辞典随之诞生了。

forge 矿物辞典就是一种让不同模组的矿物通用的系统,例如在世界中采集到不同的铜矿,可以用来合成同样的物品,甚至可以混搭使用!

矿辞用法

矿辞名称.add(物品名称);

将某物品加入到某 OD 中

<ore:sand>.add(<minecraft:stone>);
//将石头添加入沙子的矿物辞典中
//如果加入的矿辞不存在,那样就会创建一个新的矿辞

矿辞名称.remove(物品名称);

将某物品从某 OD 中删除

<ore:sand>.remove(<minecraft:sand>); 
//将沙子从沙子的矿物辞典中删除

矿辞名称.addAll(矿辞名称);

将某个矿物辞典中所有物品列入到另一个矿物辞典下

矿辞名称A.mirror(矿辞名称B);

将A的物品全部映射到B下面,也就代表B包含原有的B和A,但是A中不包含B

事件导论

事件概念

事件,顾名思义,游戏中每个时刻的每个动作都是一个事件。

例如:玩家登入世界,实体受到伤害,玩家合成物品等等。

它们都是一个个事件。

我们可以通过事件管理器(IEventManager)监听事件,即检测“当...发生时”,

随后就可以通过事件所面向的对象,编写语句,实现对游戏内容的更改!

事件管理器

通过事件管理器,我们可以添加一个表示事件的函数,从那里我们可以决定发生某事件后,CrT 会执行的操作。

要记住最重要的事情是,需要给事件指定对应类!否则,将无法访问获取事件包含的信息。

如果您只是想在不需要访问事件的地方打印一些东西,就不需要转类型了。

事件函数格式如下:

events.on事件(function (event as 事件类) {
    [代码]
});

其中,事件为游戏内所以能调用的事件,可以在 https://docs.blamejared.com/1.12/zh/Vanilla/Events/IEventManager 页面上

找到所有可以调用的事件,及使用他们需要导入的相关模块!

实例:

import crafttweaker.events.IEventManager; //导入事件管理器
import crafttweaker.event.PlayerCraftedEvent; //导入玩家合成事件的类
// onPlayerCrafted 当玩家合成时
events.onPlayerCrafted(function(event as PlayerCraftedEvent) {
    // 获取是哪个玩家合成了物品
    var player = event.player;
    // 让玩家受到 1 点魔法伤害
    player.attackEntityFrom(<damageSource:MAGIC>, 1.0f);
});

部分事件可以通过 event.cancel(); 来取消事件,相当于该事件没发生。

查阅官方文档的方法

在编写事件函数时,查阅官方文档是必不可少的。要记住官方文档比一切教程都更权威

在查看具体文档之前,请确认你翻阅的官方文档是 1.12.2 的版本。

由于 CrT 在高低版本的区别很大,因此翻阅错误版本的官方文档必定导致报错!

确认完毕后,我们在左侧目录中找到 vanlia,打开。

然后往下翻,找到 Events,打开。

然后再找到 Events 目录里面的 Events,下拉打开,可以看到所有事件都在其中。

以实体受到伤害为例,假如我们需要编写一个事件函数,让实体受伤时检测伤害来源,如果来源为凋灵,就将伤害调为 100。

我们就要首先找到“实体受到伤害”这个事件,在官方文档中找到 EntityLivingHurtEvent 页面。

进入页面,我们立马就知道了调用这个事件需要导入的类,如此一来,事件函数的总框架便瞬间确定下来!

如下:

import crafttweaker.events.IEventManager;
import crafttweaker.event.EntityLivingHurtEvent;
events.onEntityLivingHurt(function(event as EntityLivingHurtEvent) {
    [代码]
});

接下来,我们只需要在大括号内部写上语句皆可。

在 EntityLivingHurtEvent 页面,我们可以知道这个事件面向两个能够使用 ZenGetter 的对象,分别为:

damageSource,类型为 IDamageSource,表示实体受到的伤害的伤害源。

amount,类型为 float,表示实体受到伤害的值。

并且,其中的 amount 可以通过 ZenSetter 重新赋值。

很明显,我们要判断伤害来源是否为凋灵,肯定要使用 damageSource。

但是这个对象的数据类型是 IDamageSource,和凋灵这一实体并不属于同一个数据类型。

因此,我们需要通过 damageSource 所面向的对象,继续寻找办法。

可以看见,该页面上的 IDamageSource 是标蓝的,这表示超链接,可以直接链接到 IDamageSource 这一类型。

在 IDamageSource 的页面上,可以看到有好多对象。

按照上述步骤,逐个对比,如法炮制。

最终我们发现,可以通过比较:

IDamageSource 面向的 trueSource(IEntity类型) 面向的 definition(IEntityDefinition类型) 面向的 id 是否等于凋灵的 id 来进行判断

因此我们填补代码,当然可以使用声明局部变量,让语句更加简单。

如下:

import crafttweaker.events.IEventManager;
import crafttweaker.event.EntityLivingHurtEvent;
events.onEntityLivingHurt(function(event as EntityLivingHurtEvent) {
    var source as IEntity = event.damageSource.trueSource;
    if (source.definition.id == <entity:minecraft:wither>.definition.id) {
        event.amount = 100.0f;
    }
});

但是我们还没有写完,仔细想想,我们发现并不是所有伤害都有实体来源,如果受到岩浆等伤害,没有实体来源,程序就会抛出空指针异常

因此,我们还需要进行 isNull 检查,在其前面加上非符号,表示检查的值不为空时输出 true。

随后,我们使用了多个普通数据类型,每个普通数据类型都别忘记导入模块

最终,我们完成了事件函数的编写:

import crafttweaker.events.IEventManager;
import crafttweaker.event.EntityLivingHurtEvent;
import crafttweaker.damage.IDamageSource;
import crafttweaker.entity.IEntity;
import crafttweaker.entity.IEntityDefinition;
events.onEntityLivingHurt(function(event as EntityLivingHurtEvent) {
    var source as IEntity = event.damageSource.trueSource;
    if (!isNull(source) && !isNull(source.definition) && source.definition.id == <entity:minecraft:wither>.definition.id) {
        event.amount = 100.0f;
    }
});

部分实例展示

禁止爬行者生成

import crafttweaker.events.IEventManager;
import crafttweaker.event.EntityJoinWorldEvent;
import crafttweaker.entity.IEntity;
import crafttweaker.entity.IEntityDefinition;
events.onEntityJoinWorld(function(event as EntityJoinWorldEvent) {
    var definition = event.entity.definition;
    if (!isNull(definition) && definition.id == "minecraft:creeper") {
        event.cancel();
    }
});

禁止玩家使用指令

import crafttweaker.events.IEventManager;
import crafttweaker.command.ICommand;
import crafttweaker.event.CommandEvent;
import crafttweaker.player.IPlayer;
events.onCommand(function(event as CommandEvent) {
    if (event.commandSender instanceof IPlayer) {
        var player as IPlayer = event.commandSender;
        if (!player.creative && !event.commandSender.world.remote) {
            event.cancel();
        }
    }
});

作弊模组检测

代码是这样写得!这么喜欢玩加速火把?那就吧加速火把cha进你的py里,给你的py加速加速!

import crafttweaker.events.IEventManager;
import crafttweaker.event.IPlayerEvent;
import crafttweaker.event.PlayerLoggedInEvent;
import crafttweaker.player.IPlayer;
import crafttweaker.text.ITextComponent;
static disallowedMods as string[] = [
    "torcherino", "lastsword", "lolipickaxe", "manaita"
];
function badModLoaded() as bool {
    for mod in disallowedMods {
        if (loadedMods has mod) {
            return true;
        }
    }
    return false;
}
events.onPlayerLoggedIn(function (event as PlayerLoggedInEvent) {
    var player as IPlayer = event.player;
    if (badModLoaded()) {
        player.sendRichTextMessage(ITextComponent.fromTranslation("test.cheatmod"));
        for mod in disallowedMods {
            if (loadedMods has mod) {
                player.sendChat(loadedMods[mod].name + loadedMods[mod].version);
            }
        }
    }
});
//loadedMods 是全局关键字,作用是将当前加载的模组囊括为一个数组
//test.cheatmod 并不是游戏内显示的内容,需要配合 Resource Loader 模组,在对应文件夹创建 .lang 语言文件

CraftTweaker 游戏内命令

前缀及作用

CraftTweaker 添加了一些命令,这些命令可以帮助你创建脚本,并减少编写脚本的开发时间。

命令的前缀是:  /crafttweaker 或 /ct

你也可以使用这些别名: /minetweaker 或 /mt

游戏中的所有命令都可以通过以下方式找到: /ct help

命令列表

命令语法说明
生物群系/ct biomes
列出游戏中的所有生物群系。
生物群系类型
/ct biomeTypes
列出游戏中的所有生物群系类型。
方块信息
/ct blockinfo
启用或禁用方块信息读取器。在启用方块信息模式下,右键点击方块,将输出方块的名称、元数据和 TileEntity 数据。
方块/ct blocks将游戏中所有的方块名称输出到 crafttweaker.log 文件(即日志文件)中。
问题追踪
/ct bugs
在浏览器中打开 GitHub 错误跟踪器。
合成表冲突
/ct conflict
将所有冲突的制作表配方的列表输出到 crafttweaker.log 文件(仅在安装 JEI 后有效)。
Discord 便捷访问
/ct discord
在浏览器中打开 CraftTweaker 的官方 discord 频道。
官方文档
/ct docs
在浏览器中打开官方文档页面 (与 /ct wiki 相同)。
导出脚本修改内容
/ct dumpzs
将 ZenScript 转储为 HTML 文件输出到 minecraft 目录中的 crafttweaker_dump 文件夹。
实体/ct entities将游戏中所有的实体输出到 crafttweaker.log 文件。
给予物品
/ct give <IItemStack>
使用 CraftTweaker 的尖括号调用语法为玩家提供项,因此可以通过附加 .withTag() 调用来应用标签(非特殊情况不需要使用)。
查询手中物品/ct hand查看自己手上所拿着物品的id及其nbt标签(如果有的话),会直接提供剪切板,让你快速进行复制粘贴。
物品栏内物品
/ct inventory
将物品栏中所有的物品输出到 crafttweaker.log 文件。
JEI 类/ct jeiCategories将所有已注册的 jei 类输出到 crafttweaker.log 文件中(仅在安装 JEI 后有效)。
Json 格式
/ct json
将手中物品的 nbt 数据作为 JSON 输出到聊天栏中。这种格式与 CraftTweaker 使用的 IData 格式不同。会直接提供剪切板,让你快速进行复制粘贴。
流体/ct liquids将游戏中所有的流体输出到 crafttweaker.log 文件。
打开日志/ct log
输出一个可以直接打开 crafttweaker.log 文件的链接。
模组/ct mods将游戏中所有模组及其版本输出到 crafttweaker.log 文件,并在聊天栏中显示。
物品
/ct names [category]
将游戏中所有的物品输出到 crafttweaker.log 文件。其中 category 参数是可选的,你可以填入附录表内参数,让输出到日志中的物品信息更加全面。
NBT/ct nbt将你目光看向方块或手持物品的 NBT 数据输出到 crafttweaker.log 文件中。
矿物辞典
/ct oredict <name>
将游戏中所有的矿物辞典及这个矿辞包含的物品输出到 crafttweaker.log 文件。若指定 name 参数,则只输出该 name 代表矿物辞典下的物品。
状态效果/ct potions将游戏中所有的状态效果输出到 crafttweaker.log 文件。
配方名/ct recipeNames <modid>将游戏中所有的配方名输出到 crafttweaker.log文件。可以指定 modid 参数来过滤结果。
配方/ct recipes
将游戏中所有的合成配方以 recipes 函数的形式输出到 crafttweaker.log 文件。
熔炉配方/ct recipes furnace
将游戏中所有的熔炉配方以 recipes 函数的形式输出到 crafttweaker.log 文件。
手持物品配方
/ct recipes hand
将游戏中所有的能合成手持物品的配方以 recipes 函数的形式输出到 crafttweaker.log 文件。
打开脚本文件夹
/ct scripts
打开游戏目录下的 scripts 文件夹,可以由服务端控制台执行。
种子注册表
/ct seeds
将种子注册表中所有的物品输出到 crafttweaker.log 文件。
语句检查
/ct syntax
检查所有脚本,并输出发现的所有语法错误。这不是热重载指令。CraftTweaker 在 Minecraft 1.12.2 不支持热重载。
Wiki
/ct wiki
在浏览器中打开官方文档页面 (与 /ct docs 相同)。
ZsLint
/ct zslint
启动 zslint 套接字。

附录:/ct names 参数表

参数内容
burntime
物品作为燃料的燃烧时间
creativetabs
物品所属的创造背包栏
damageable
物品可否被损坏(是否有耐久)
display
物品的显示名
enchantability
物品的附魔能力(详见 Minecraft-Wiki 的 "附魔台机制")
foodvalue
食物的营养价值
maxdamage
物品的最大耐久值
maxstack
物品的最大堆栈数量
maxuse
物品的使用时长(单位: tick)
modid物品所属的 mod
rarity
物品的稀有度,影响物品显示名的颜色
repairable
物品可否使用铁砧
repaircost
物品在铁砧上操作而获得的操作数
saturationvalue
食物的饱食度值
unloc
物品的未本地化名称

模组联动

Content Tweaker

ContentTweaker 允许通过 ZenScript 创建方块,物品,流体和其他内容。

ContentTweaker 游戏内命令

ContentTweaker 扩展了 CraftTweaker 提供的命令。

要访问这些命令,你需要执行与 CraftTweaker 命令相同的操作,使用 /crafttweaker 或 /ct 前缀。

命令语法说明
方块材料
/ct blockmaterial
将游戏中所有的方块材料输出到 crafttweaker.log 文件。
创造模式物品栏
/ct creativetab
将游戏内所有的创造模式物品栏输出到 crafttweaker.log 文件。
声音事件
/ct soundevent
将游戏中所有的声音事件输出到 crafttweaker.log 文件
声音类型
/ct soundtype
将游戏中所有的声音类型输出到 crafttweaker.log 文件。

资源文件夹

安装 ContentTweaker 后的第一件事不是急着看教程写脚本,而是赶快启动一遍 Minecraft。

启动完毕后退出。我们发现,在根目录下多出了一个 resources 文件夹。

Tip: 该 resources 文件夹同 资源加载(Resource Loader) 模组的 resources 文件夹兼容,教程十分推荐一起安装该模组。

进入 resources 文件夹,里面还有一个 contenttweaker 文件夹,进入。

可以发现,contenttweaker 文件夹内有着五个文件夹,分别为:blockstates, lang, models, sounds, textures。

这五个文件夹分别负责存储:方块状态文件,语言文件,模型文件,声音文件,材质文件。

材质学基础

虽然教程主要教学脚本,但是这些 Minecraft 材质学的基础内容是必须掌握的。

方块状态文件(blockstate)是进一步定义一个方块所需的附加数据,包括方块的外观和行为,文件格式是 Json。在大多数情况下,一个方块对应一个方块状态,但也有个例。

例如在 1.12.2 版本,熔炉就有“熄灭的熔炉”和“燃烧的熔炉”两种方块状态。而火把也具有“火把”和“墙上的火把”两种方块状态,因此实际方块和方块状态不是一一对应的。

模型则分为方块模型和物品模型,文件格式也是 Json。

方块状态需要调用方块模型,但是方块状态和方块模型依然不是一一对应关系。

例如墙上的火把这一方块状态,其内部包含了"facing=east","facing=west","facing=south"以及"facing=north"四个变种,对应着四个方块模型。

而这四个变种都只调用一个示例文件:wall_torch.json,但在其基础上进行了旋转。

对于物品而言,没有物品状态,只有物品模型、物品材质和物品名称三种文件。因此,对于物品,只需要在 models/items, lang, textures/items 三个文件夹中存放文件就行了。

材质分为方块材质和物品材质,文件格式是 png。

方块模型需要调用方块材质,但你肯定有明白了,方块模型和方块材质依旧不是一一对应的关系。

比如草方块,它底部、侧面、表面有三张不同的贴图,一个模型就对应着三张材质。

方块状态也可以直接调用方块材质,而跳过方块模型。

物品模型调用物品材质。

材质可以是静态的,可以是动态的。但始终都是 .png 格式,否则会导致材质错误。

要让方块或物品纹理正常显示,那么它们的宽度和高度必须相等(如果是动态纹理,那么高度要是宽度的倍数);否则,只会显示黑紫方格交错的纹理。对于其他多数纹理,文件会被拉伸以符合所需的尺寸。

语言文件的格式是 lang。

内部存储的都是键值对。别告诉我现在还不知道键值对是什么。如果这样,建议重学第四部分。

键就是本地化键名,值就是物品或者方块在游戏里要显示的名字,也叫本地化名称。

方块的本地化键名格式:tile.contenttweaker.blockName.name,其中 blockName 是该方块的未本地化名称。

物品的本地化键名格式:item.contenttweaker.itemName.name,其中 itemName 是该物品的未本地化名称。

创建一个方块

使用 ContentTweaker 创建新事物,你需要使用 #loader contenttweaker 预处理器,保证该脚本由 ContentTweaker 加载,而不是原来的 CraftTweaker。

创建方块需要从 ContentTweaker 导入两个模块,分别为 VanillaFactory 和 Block。

通过如下实例,我们可以创建一个方块:

#loader contenttweaker
import mods.contenttweaker.VanillaFactory;
import mods.contenttweaker.Block;
var antiIceBlock = VanillaFactory.createBlock("anti_ice", <blockmaterial:ice>);
// 以下在定义方块的各个性质,有哪些性质可以定义?
// 请自行参阅官方文档:https://docs.blamejared.com/1.12/zh/Mods/ContentTweaker/Vanilla/Creatable_Content/Block
antiIceBlock.setLightOpacity(3);   // 不透明度
antiIceBlock.setLightValue(0);   // 自身亮度
antiIceBlock.setBlockHardness(5.0);   // 硬度
antiIceBlock.setBlockResistance(5.0);   // 爆炸抗性
antiIceBlock.setToolClass("pickaxe");   // 采集工具
antiIceBlock.setToolLevel(0);   // 挖掘等级
antiIceBlock.setBlockSoundType(<soundtype:snow>);   // 声音类型
antiIceBlock.setSlipperiness(0.3);   // 光滑度
antiIceBlock.register();

其中,第 4 行的 anti_ice 是该方块的未本地化名称。

如此一来,这个方块在游戏内确实被注册了,可是现在的它没有材质也没有名字。

通过指令 /give @s contenttweaker:anti_ice 可以获得该方块。我们看到,不仅材质全是紫黑块,名字也是乱的。

这就需要我们准备一张合适的,长宽一致的 png 图片,将这张图片的名字重命名为方块的未本地化名称,在这里就是 anti_ice。

随后将这张图片移动到 resources/contenttweaker/textures/blocks 中。这样,方块的材质就显现出来了。

至于名字,我们就需要在 resources/contenttweaker/lang 文件夹下新建一个文件,文件名为 zh_CN

Tip: zh_CN 是简体中文的意思,如果要编写英语(美式)名字,就要把文件名设为 en_US

进入这个文件,写下如下的键值对,让本地化键名和本地化名称建立起一一对应关系:

tile.contenttweaker.anti_ice.name=反冰

保存后重新进入游戏,一个方块创建完成。

创建一个物品

对于物品,创建过程和方块非常相似。

只不过语言文件中二者的本地化键名格式不同,以及物品材质需要放在 textures 下的 items 文件夹中,而不是 blocks 文件夹。

下面举个实例:

#loader contenttweaker
import mods.contenttweaker.VanillaFactory;
import mods.contenttweaker.Item;
var zsItem = VanillaFactory.createItem("zs_item");
// 物品性质:https://docs.blamejared.com/1.12/zh/Mods/ContentTweaker/Vanilla/Creatable_Content/Item
zsItem.maxStackSize = 8;   // 最大堆栈数
zsItem.rarity = "rare";   // 稀有度
zsItem.creativeTab = zsCreativeTab;   // 所属创造背包栏
zsItem.smeltingExperience = 10;   // 熔炼获得经验
zsItem.toolClass = "pickaxe";   // 工具类型
zsItem.toolLevel = 5;   // 挖掘等级
zsItem.beaconPayment = true;   // 是否可用于信标
zsItem.itemRightClick = function(stack, world, player, hand) {
    Commands.call("scoreboard players set @p name 5", player, world);
    return "Pass";
    //手持物品右键执行以上代码
};
zsItem.register();

创建一个流体

如法炮制,不需要教了。直接上实例:

#loader contenttweaker
import mods.contenttweaker.VanillaFactory;
import mods.contenttweaker.Fluid;
import mods.contenttweaker.Color;
var zsFluid = VanillaFactory.createFluid("zs_fluid", Color.fromHex("FF69B4"));
// 流体性质:https://docs.blamejared.com/1.12/zh/Mods/ContentTweaker/Vanilla/Creatable_Content/Fluid
zsFluid.material = <blockmaterial:lava>;   // 流体材料
zsFluid.temperature = 1300;   // 流体温度
zsFluid.register();

不需要给流体提供材质,因为流体材质无非就是水或熔岩换个色。而流体的颜色在上述脚本的第五行定义了。

都给你提供三次官方文档的链接了,总会自己查阅了吧!以后就自己去查吧。

创建匠魂材料

ContentTweaker 联动了匠魂,你可以使用它自定义匠魂材料。

格式就像创建方块、物品、流体一样,十分简单。

匠魂材料只需要在 recourses 文件夹中提供其本地化名称,除此以外不需要提供任何材质、模型等等。

#loader contenttweaker
#modloaded tconstruct
val testMat = mods.contenttweaker.tconstruct.MaterialBuilder.create("flower");
testMat.color = 0x8e661b;   // 材料颜色
testMat.craftable = true;   // 是否使用部件加工台
testMat.liquid = <liquid:lava>;   // 熔融后形成的流体(若 testMat.castable = false 可省略)
testMat.castable = false;   // 是否使用冶炼炉
testMat.addItem(<item:minecraft:red_flower:4>);   // 部件加工台或冶炼炉需要的物品
testMat.representativeItem = <item:minecraft:red_flower:4>;   // 匠魂宝典上显示的物品
testMat.addHeadMaterialStats(100, 1.5f, 5.5f, 1);   // 材料作为顶端时的属性(依次为:耐久,攻击速度,攻击力,挖掘等级)
testMat.addHandleMaterialStats(0.3, 500);   // 材料作为手柄时的属性(依次为:手柄系数,耐久)
testMat.addBowStringMaterialStats(0.5f);   // 材料作为弓弦时的属性(依次为:强化)
testMat.localizedName = "Flower";   // 本地化名称,可以用 game.localize() 函数来调用本地化键名 
testMat.register();

结合上述知识,有如下实例(来自整合包: GreedyCraft 中的"聚合矩阵"):

首先创建一个新流体,然后创建一个材料,最后为材料添加特性。

#loader contenttweaker
#modloaded tconstruct
// 导入模块
import crafttweaker.liquid.ILiquidStack;
import crafttweaker.game.IGame;
import mods.contenttweaker.tconstruct.Material;
import mods.contenttweaker.tconstruct.MaterialBuilder;
import mods.contenttweaker.Fluid;
import mods.contenttweaker.VanillaFactory;
import mods.contenttweaker.Color;
// 创建一个新流体
val molten_fusion_matrix = VanillaFactory.createFluid("fusion_matrix", Color.fromHex("4a148c").getIntColor());
molten_fusion_matrix.material = <blockmaterial:lava>;
molten_fusion_matrix.viscosity = 2000;
molten_fusion_matrix.density = 4000;
molten_fusion_matrix.rarity = "EPIC";
molten_fusion_matrix.colorize = true;
molten_fusion_matrix.temperature = 2800;
molten_fusion_matrix.luminosity = 15;
molten_fusion_matrix.color = Color.fromHex("4a148c").getIntColor();
molten_fusion_matrix.stillLocation = "base:fluids/molten";
molten_fusion_matrix.flowingLocation = "base:fluids/molten_flowing";
molten_fusion_matrix.register();
// 创建一个匠魂材料
val fusion_matrix = MaterialBuilder.create("fusion_matrix");
fusion_matrix.color = Color.fromHex("4a148c").getIntColor(); 
fusion_matrix.craftable = false;
fusion_matrix.castable = true;
fusion_matrix.representativeItem = <item:tconevo:material:0>;
fusion_matrix.addItem(<item:tconevo:material:0>);
fusion_matrix.liquid = <liquid:fusion_matrix>;
fusion_matrix.localizedName = "聚合矩阵";
fusion_matrix.addHeadMaterialStats(12000, 13.6, 18.9, 8);
fusion_matrix.addHandleMaterialStats(1.8, 625);
fusion_matrix.addExtraMaterialStats(820);
fusion_matrix.addBowMaterialStats(getDrawSpeed(1.2) as float, 1.2, 9.6);
fusion_matrix.addArrowShaftMaterialStats(1.75, 40);
fusion_matrix.addProjectileMaterialStats();
// 为该材料添加特性
fusion_matrix.addMaterialTrait("tconevo.overwhelm", "head");
fusion_matrix.addMaterialTrait("dense", "head");
fusion_matrix.addMaterialTrait("dense", "handle");
fusion_matrix.addMaterialTrait("dense", "extra");
fusion_matrix.addMaterialTrait("dense_armor", "core");
fusion_matrix.addMaterialTrait("shielding_armor", "core");
fusion_matrix.addMaterialTrait("first_guard_armor", "core");
fusion_matrix.addMaterialTrait("dense_armor", "plates");
fusion_matrix.addMaterialTrait("first_guard_armor", "plates");
fusion_matrix.addMaterialTrait("dense_armor", "trim");
fusion_matrix.addMaterialTrait("first_guard_armor", "trim");
fusion_matrix.addMaterialTrait("tconevo.overwhelm", "bow");
fusion_matrix.addMaterialTrait("dense", "bow");
fusion_matrix.addMaterialTrait("hovering", "shaft");
fusion_matrix.register();

自定义匠魂材料特性

创建材料特性可就不是上面那几个流水线工艺了。

除了基本格式和创建匠魂材料差不多外,我们需要结合事件函数了。

简单来说,特性的显示颜色,描述等等这些表面的东西,和上面的那四个家伙差不多,很容易处理。

但是特性的具体效果可不一样,这是需要通过函数书写的,本质上讲,其实是一个类似事件函数的东西。

我们从老朋友贪婪整合包中取一个例子(省略导入模块和本地化键名):

val gambleTrait = TraitBuilder.create("gamble");
gambleTrait.color = Color.fromHex("ffa000").getIntColor(); 
gambleTrait.localizedName = "赌博";
gambleTrait.localizedDescription = "这是个看脸的世界";
gambleTrait.calcDamage = function(trait, tool, attacker, target, originalDamage, newDamage, isCritical) {
    var dmg = newDamage;
    var rand as double = Math.random();
    if (rand < 0.15) {
        dmg = newDamage * 2 as float; 
    } else if (rand < 0.45) {
        dmg = newDamage / 2 as float; 
    }
    return dmg;
};
gambleTrait.register();

我们从上面的实例可以看到,第 1 至 4 行和自定义匠魂材料的格式几乎一致,没什么好说的。

然而从第 5 行开始,我们仿佛看到了事件函数的影子。

只不过,这不是真的事件函数,这个函数调用的不是 crafttweaker 的事件,而是 contenttweaker 的事件。

contenttweaker 所支持监听的事件较少,但也完全足够了。

你可以在:https://docs.blamejared.com/1.12/zh/Mods/ContentTweaker/Tinkers_Construct/TraitBuilder 页面上找到所有 ContentTweaker 事件。

具体怎么编写创新,就看你个人的编程能力和脑洞了。

ModTweaker

教程格式

ModTweaker 能够自定义其他模组的合成配方。是 CraftTweaker 对其他模组进行联动的首选利器!

教程格式: module.function(type variable, type variable, ...);

其中,module 表示导入的模块;function 表示添加修改或删除配方的函数,比如 addRecipe,removeRecipe 等;

type 表示变量的数据类型;variable 表示变量,变量的名字代表着它在配方内的作用,比如 input 表示配方需要的物品, output 表示配方得到的物品等。

教程除了告诉你格式以外,会给你举实例的,因此看不懂格式也没关系,理解实例也不失为一种方法。

修改配方最基础的就是先删后增,不懂的重学第 5 章。

一定注意导入模块!

实用拓展 - Actually Additions

原子再构机

import mods.actuallyadditions.AtomicReconstructor;
// 添加配方格式:
// AtomicReconstructor.addRecipe(IItemStack output, IItemStack input, int energyUsed);
// 如下实例表示原子再构机消耗 1000 个单位的能量将煤炭转化为火焰弹
AtomicReconstructor.addRecipe(<minecraft:fire_charge>, <minecraft:coal:1>, 1000);
// 删除配方格式:
// AtomicReconstructor.removeRecipe(IItemStack output);
// 如下实例表示删除所有通过原子再构机合成煤炭的配方
AtomicReconstructor.removeRecipe(<minecraft:coal>);

毛球

import mods.actuallyadditions.BallOfFur;
// 添加配方格式:
// BallOfFur.addReturn(IItemStack output, int chance);
// 如下实例表示使用毛球有 5 的权重获得线
BallOfFur.addReturn(<minecraft:string>, 5);
// 删除配方格式:
// BallOfFur.removeReturn(IItemStack output);
// 如下实例表示删除使用毛球可能获得煤炭的配方
BallOfFur.removeReturn(<minecraft:coal>);

堆肥机

import mods.actuallyadditions.Compost;
// 添加配方格式:
// Compost.addRecipe(IItemStack output, IItemStack outputDisplay, IItemStack input, IItemStack inputDisplay);
// 如下实例表示使用堆肥机可以将糖发酵为泥土,糖在堆肥机里显示的材质是方块雪,泥土在堆肥机里显示的材质是方块泥土
Compost.addRecipe(<minecraft:dirt>, <minecraft:dirt>, <minecraft:sugar>, <minecraft:snow>);
// 删除配方格式:
// Compost.removeRecipe(IItemStack output);
// 如下实例表示删除所有通过堆肥机发酵得到油菜种子的配方
Compost.removeRecipe(<actuallyadditions:item_canola_seed>);

磨粉机

import mods.actuallyadditions.Crusher;
// 添加配方格式(@Optional 表示可选项):
// Crusher.addRecipe(IItemStack output, IItemStack input, @Optional IItemStack outputSecondary, @Optional int outputSecondaryChance);
// 如下实例表示磨粉机磨碎一个铁矿得到一个铁锭,同时有 50% 的概率产出副产物石头
Crusher.addRecipe(<minecraft:iron_ingot>, <minecraft:iron_ore>, <minecraft:stone>, 50);
// 删除配方格式:
// Crusher.removeRecipe(IItemStack output);
// 如下实例表示删除所有通过磨粉机得到金锭的配方
Crusher.removeRecipe(<minecraft:gold_ingot>);

充能台

import mods.actuallyadditions.Empowerer;
// 添加配方格式:
// 格式 1 : Empowerer.addRecipe(IItemStack output, IItemStack input, IItemStack modifier1, IItemStack modifier2, IItemStack modifier3, IItemStack modifier4, int energyPerStand, int time);
// 格式 2 : Empowerer.addRecipe(IItemStack output, IItemStack input, IItemStack modifier1, IItemStack modifier2, IItemStack modifier3, IItemStack modifier4, int energyPerStand, int time, float[] particleColourArray);
// 如下实例表示充能台在 4 个物品展示框中存在红石的情况下,将树叶转化为铁锭,每个物品展示框消耗 500 个单位的能量,充能时间长达 100 tick (即 5 秒)
Empowerer.addRecipe(<minecraft:iron_ingot>, <minecraft:leaves>, <minecraft:redstone>, <minecraft:redstone>, <minecraft:redstone>, <minecraft:redstone>, 500, 100);
// 删除配方格式:
// Empowerer.removeRecipe(IItemStack output);
// 如下实例表示删除所有通过充能台得到充能铁晶的配方
Empowerer.removeRecipe(<actuallyadditions:item_crystal_empowered:5>);

矿工透镜

import mods.actuallyadditions.MiningLens;
// 添加配方格式:
// 将石头转化为矿石: MiningLens.addStoneOre(IOreDictEntry ore, int weight);
// 将地狱岩转化为矿石: MiningLens.addNetherOre(IOreDictEntry ore, int weight);
// 如下实例 1 表示使用矿工透镜有 2 的权重将石头转化为铁矿石
// 如下实例 2 表示使用矿工透镜有 5 的权重将地狱岩转化为金矿石
MiningLens.addStoneOre(<ore:oreIron>, 2);
MiningLens.addNetherOre(<ore:oreGold>, 5);
// 删除配方格式:
// 删除转化石头配方: MiningLens.removeStoneOre(IOreDictEntry ore);
// 删除转化地狱岩配方: MiningLens.removeNetherOre(IOreDictEntry ore);
// 如下实例 1 表示删除所有通过矿工透镜将石头转化为铁矿石的配方
// 如下实例 2 表示删除所有通过矿工透镜将地狱岩转化为金矿石的配方
MiningLens.removeStoneOre(<ore:oreIron>);
MiningLens.removeNetherOre(<ore:oreGold>);

原油发电机

import mods.actuallyadditions.OilGen;
// 添加配方格式(@Optional 表示可选项):
// OilGen.addRecipe(ILiquidStack fluid, int genAmount, @Optional int genTime);
// genTime 留空则默认为 100
// 如下实例表示原油发电机每 10 tick 消耗 50 mB 岩浆,每 tick 发电 1000 个单位的能量
OilGen.addRecipe(<liquid:lava>, 1000, 10);
// 删除配方格式:
// OilGen.removeRecipe(ILiquidStack fluid);
// 如下实例表示删除所有通过原油发电机消耗水发电的配方
OilGen.removeRecipe(<liquid:water>);

藏宝箱

import mods.actuallyadditions.TreasureChest;
// 添加配方格式:
// TreasureChest.addLoot(IItemStack returnItem, int chance, int minAmount, int maxAmount);
// 如下实例表示打开藏宝箱有 50 的权重获得最小数量为 1,最大数量为 64 的泥土
TreasureChest.addLoot(<minecraft:dirt>, 50, 1, 64);
// 删除配方格式:
// TreasureChest.removeLoot(IItemStack returnItem);
// 如下实例表示删除打开藏宝箱可能会获得金粒的配方
TreasureChest.removeLoot(<minecraft:gold_nugget>);

Better With Mods

熔魂钢钢砧

import mods.betterwithmods.Anvil;
// 由于 ModTweaker 的旧 BUG,写在脚本里的配方和实际的配方会沿对角线翻折
// 新版的 ModTweaker 增加了新的方法修复了这个 BUG,因此本教程只会讲解新方法
// 添加配方格式:
// Anvil.addShapedFixed(IItemStack output, IIngredient[][] inputs);
// 如下实例表示根据其二维数组内的物品,生成一个合成泥土的配方
Anvil.addShapedFixed(<minecraft:dirt>, [
    [<minecraft:stone>, <minecraft:stone>, <minecraft:stone>, <minecraft:dirt>],
    [<minecraft:stone>, <minecraft:stone>, <minecraft:stone>, <minecraft:stone>],
    [<minecraft:stone>, <minecraft:stone>, <minecraft:stone>, <minecraft:stone>],
    [<minecraft:stone>, <minecraft:stone>, <minecraft:stone>, <minecraft:stone>]
]);
// 删除配方格式(@Optional 表示可选项):
// Anvil.removeShapedFixed(IItemStack output, @Optional IIngredient[][] inputs);
// 若 input 留空,则按照 output,删除所有通过熔魂钢钢砧合成 output 的配方
// 如下实例表示删除通过熔魂钢钢砧由该二维数组内物品合成指定物品的单一配方
Anvil.removeShapedFixed(<minecraft:dirt>,[
    [<minecraft:stone>, <minecraft:stone>, <minecraft:stone>, <minecraft:dirt>],
    [<minecraft:stone>, <minecraft:stone>, <minecraft:stone>, <minecraft:stone>],
    [<minecraft:stone>, <minecraft:stone>, <minecraft:stone>, <minecraft:stone>],
    [<minecraft:stone>, <minecraft:stone>, <minecraft:stone>, <minecraft:stone>]
]);

风箱

import mods.betterwithmods.Bellows;
// 该配方能让你自定义物品被风箱吹远的距离,以格为单位,格式如下:
// Bellows.set(IItemStack stack, float value);
// 其中,value 的取值范围是 (0, 128]
// 如下实例表示树叶将会被风扇吹动 5 格
Bellows.set(<minecraft:leaves:*>, 5.0f);

浮力

import mods.betterwithmods.Buoyancy;
// 该配方只在 Better With Mods 启用 HCBuoy 时有效
// 该配方能让你自定义物品受到的浮力,格式如下:
// Buoyancy.set(IItemStack stack, float value);
// 其中,value 的取值范围是 [-1, 1],数字越大浮力越大,-1 表示完全沉没,1 表示浮在水面
// 如下实例表示铁块在水中会沉没水底
Buoyancy.set(<minecraft:iron_block>, -1.0f);

热源注册器

import mods.betterwithmods.HeatRegistry;
// 热源注册器能为与釜锅,坩埚和窑炉交互的方块分配整数值,即自定义可用于加热其的方块。
// 默认情况下: 1 表示原版火,2 表示蓝火
// 添加自定义热源(方法 1):
// HeatRegistry.addHeatSource(IItemStack stack, int heat);
// stack 这一物品栈必须有其对应的方块
// 如下实例表示将下界岩作为等级为 1 的热源(等级与原版火相等)
HeatRegistry.addHeatSource(<minecraft:netherrack>, 1);
// 添加自定义热源(方法 2):
// HeatRegistry.addHeatSource(IBlockState stack, int heat);
// 如下实例表示将下界岩作为等级为 1 的热源(等级与原版火相等)
HeatRegistry.addHeatSource(<blockstate:minecraft:netherrack>, 1);
// 添加自定义热源(方法 3):
// HeatRegistry.addHeatSource(IBlockState[] stacks, IItemStack displayStack, int heat);
// 多用于某物品对应方块有多个方块状态的时候,有关方块状态的概念详见 8.2 的材质学基础
// 暂无实例

釜锅

import mods.betterwithmods.Cauldron
// 添加基础配方格式:
// 需要原版火: Cauldron.addUnstoked(IIngredient[] inputs, IItemStack[] outputs);
// 需要蓝火: Cauldron.addStoked(IIngredient[] inputs, IItemStack[] outputs);
// 如下实例表示釜锅将圆石炼制成石头
Cauldron.addStoked([<ore:cobblestone>],[<minecraft:stone>]);
// 删除配方格式:
// Cauldron.remove(IItemStack[] outputs);
// 如下实例表示删除所有通过釜锅炼得石头的配方
Cauldron.remove([<minecraft:stone>]);
// 如下实例表示删除釜锅的所有配方
Cauldron.removeAll();
// 添加高级配方格式:
// Cauldron.builder()
//     .buildRecipe(IIngredient[] inputs, IItemStack[] outputs)
//     .setPriority(int priority)
//     .setHeat(int heat)
//     .setIgnoreHeat(boolean ignoreHeat)
//     .build();
// 如下实例表示在蓝火的加热下,釜锅将石头炼制成泥土
// setHeat 表示需要的热量等级,可通过热源注册器自定义热源和热量等级
Cauldron.builder()
    .buildRecipe([<ore:stone>], [<minecraft:dirt>])
    .setHeat(2)
    .setPriority(-1)
    .build();

坩埚

import mods.betterwithmods.Crucible;
// 添加基础配方格式:
// 需要原版火: Crucible.addUnstoked(IIngredient[] inputs, IItemStack[] outputs);
// 需要蓝火: Crucible.addStoked(IIngredient[] inputs, IItemStack[] outputs);
// 如下实例表示坩埚将圆石炼制成石头
Crucible.addStoked([<ore:cobblestone>],[<minecraft:stone>]);
// 删除配方格式:
// Crucible.remove(IItemStack[] outputs);
// 如下实例表示删除所有通过坩埚炼得石头的配方
Crucible.remove([<minecraft:stone>]);
// 如下实例表示删除坩埚的所有配方
Crucible.removeAll();
// 添加高级配方格式:
// Crucible.builder()
//     .buildRecipe(IIngredient[] inputs, IItemStack[] outputs)
//     .setPriority(int priority)
//     .setHeat(int heat)
//     .setIgnoreHeat(boolean ignoreHeat)
//     .build();
// 如下实例表示在蓝火的加热下,坩埚将石头炼制成泥土
// setHeat 表示需要的热量等级,可通过热源注册器自定义热源和热量等级
Crucible.builder()
    .buildRecipe([<ore:stone>], [<minecraft:dirt>])
    .setHeat(2)
    .setPriority(-1)
    .build();

窑炉

import mods.betterwithmods.Kiln;
// 添加基础配方格式:
// Kiln.add(IIngredient inputs, IItemStack[] outputs);
// 如下实例表示窑炉将圆石炼制成石头
Kiln.add(<ore:cobblestone>,[<minecraft:stone>]);
// 删除配方格式(根据输出删除):
// Kiln.remove(IItemStack[] outputs);
// 如下实例表示删除所有通过窑炉炼得石头的配方
Kiln.remove([<minecraft:stone>]);
// 如下实例表示删除窑炉的所有配方
Kiln.removeAll();
// 删除配方格式(根据输入删除)
// Kiln.remove(IIngredient input);
// 如下实例表示删除所有在窑炉输入圆石的配方
Kiln.remove(<ore:cobblestone>);
// 添加高级配方格式:
// Kiln.builder()
//     .buildRecipe(IIngredient inputs, IItemStack[] outputs)
//     .setPriority(int priority)
//     .setHeat(int heat)
//     .setIgnoreHeat(boolean ignoreHeat)
//     .build();
// 如下实例表示在蓝火的加热下,窑炉将石头炼制成泥土
// setHeat 表示需要的热量等级,可通过热源注册器自定义热源和热量等级
Kiln.builder()
    .buildRecipe(<ore:stone>, [<minecraft:dirt>])
    .setHeat(2)
    .setPriority(-1)
    .build();

滤筛漏斗

import mods.betterwithmods.FilteredHopper;
// 添加过滤规则,并为此规则添加代表物
// 格式如下: FilteredHopper.addFilter(String name, IIngredient item)
// 如下实例表示,添加一个名叫"modtweaker:myFilter"的过滤规则,并在漏斗插槽中装有木板时触发
FilteredHopper.addFilter("modtweaker:myFilter", <minecraft:planks>);
// 为某一规则添加允许通过的物品
// 格式如下: FilteredHopper.addFilteredItem(String name, IIngredient item)
// 如下实例表示,当滤筛漏斗使用"modtweaker:myFilter"过滤规则时,铁锭可以通过
FilteredHopper.addFilteredItem("modtweaker:myFilter",<ore:ingotIron>);
// 添加配方格式:
// FilteredHopper.addFilterRecipe(String name, IIngredient input, IIngredient[] insideOutput , IIngredient[] outsideOutput);
// 其中,insideOutput 表示输出到漏斗库存中的产物,outsideOutput 表示弹出到漏斗上方的产物
// 注意,insideOutput 和 outsideOutput 并不是可选项,想要留空需填入空数组 []
// 如下实例表示,当滤筛漏斗使用"modtweaker:myFilter"过滤规则遇到草方块时,会将其变为燧石储存在漏斗中,并在其上方弹出 9 个钻石
FilteredHopper.addFilterRecipe("modtweaker:myFilter",<minecraft:grass>,[<minecraft:flint>],[<minecraft:diamond>*9]);
// 移除某一规则下所有可通过的物品
// 格式如下: FilteredHopper.clearFilter(String name);
// 如下实例表示,移除规则"betterwithmods:wicker"的所有允许通过的物品
FilteredHopper.clearFilter("betterwithmods:wicker");
// 删除配方格式:
// 根据输出删除: FilteredHopper.removeRecipe(IIngredient[] insideOutput, IIngredient[] outsideOutput);
// 根据输入删除: FilteredHopper.removeRecipeByInput(IIngredient input);
// 如下实例表示删除所有通过滤筛漏斗在库存中输出沙子并在上方弹出燧石的配方
FilteredHopper.removeRecipe([<minecraft:sand>],[<minecraft:flint>]);
// 如下实例表示删除所有通过滤筛漏斗输入沙子的配方
FilteredHopper.removeRecipeByInput(<minecraft:sand>);

熔炉烧制时间

import mods.betterwithmods.Misc;
// 设置物品的燃烧时间
// 格式如下: Misc.setFurnaceSmeltingTime(IIngredient ingredient, int time)
// time 的单位为 tick
// 如下实例表示烤一个土豆大约需要 83 分钟
Misc.setFurnaceSmeltingTime(<minecraft:potato>,100000);

方块上移动速度

import mods.betterwithmods.Movement;
// 设置玩家在方块上行走时受到的速度效果
// 格式如下: Movement.set(IItemStack stack, float value);
// 其中 stack 这一物品栈必须有其对应的方块
// value 的取值范围是 [0, 2],1 为正常速度
// 如下实例表示将玩家在冰块上的移动速度变为正常速度的 1.5 倍
Movement.set(<minecraft:ice>, 1.5);

磨石

import mods.betterwithmods.Mill;
// 添加基础配方格式:
// Mill.addRecipe(IIngredient[] inputs, IItemStack[] outputs);
// 如下实例表示磨石将泥土磨制成石头
Mill.addRecipe([<minecraft:dirt>],[<minecraft:stone>]);
// 删除配方格式:
// Mill.remove(IItemStack[] outputs);
// 如下实例表示删除所有通过磨石制得石头的配方
Mill.remove([<minecraft:stone>]);
// 如下实例表示删除磨石的所有配方
Mill.removeAll();
// 添加高级配方格式:
// Mill.builder()
//     .buildRecipe(IIngredient[] inputs, IItemStack[] outputs)
//     .setPriority(int priority)
//     .setGrindType(String soundLocation)
//     .setTicks(int ticks)
//     .build();
// 如下实例表示磨石研磨 3 秒将泥土磨制成石头,期间发出恶魂叫声
// setGrindType 表示研磨时发出的声音
Mill.builder()
    .buildRecipe([<minecraft:dirt>], [<minecraft:stone>])
    .setGrindType("minecraft:entity.ghast.scream")
    .setTicks(60);
    .build();

滑车管理器

import mods.betterwithmods.PulleyManager;
// 设置滑车和滑车锚可以拉动的方块
// 格式如下: PulleyManager.addPulleyBlock(IBlockState state);
// 如下实例表示让黑曜石可以被滑车锚拉动
PulleyManager.addPulleyBlock(<blockstate:minecraft:obsidian>);

螺杆锯

import mods.betterwithmods.Saw;
// 添加基础配方格式:
// Saw.add(IIngredient input, IItemStack[] output);
// input 必须具有与其对应的方块状态
// 如下实例表示螺杆锯将栅栏切割成木棍
Saw.add(<minecraft:fence>,[<minecraft:stick>,<minecraft:stick>]);
// 删除配方格式(根据输入删除):
// Saw.remove(IIngredient input);
// 如下实例表示删除所有在螺杆锯中输入为栅栏的配方
Saw.remove(<minecraft:fence>);
// 删除配方格式(根据输出删除):
// Saw.remove(IItemStack[] outputs);

螺杆旋转台

import mods.betterwithmods.Turntable;
// 添加基础配方格式:
// Turntable.add(IIngredient input, @Optional IItemStack productState, IItemStack[] output);
// input 必须具有与其对应的方块状态, productState 是在配方完成后会被放置的方块(选填)
// 如下实例表示使用螺杆旋转台加工草方块,完成后放置泥土并输出种子
Turntable.add(<minecraft:grass>, <minecraft:dirt>, [<minecraft:seed>]);
// 删除配方格式(根据输入删除):
// Turntable.remove(IIngredient input);
// 删除配方格式(根据 productState 删除):
// Turntable.removeRecipe(IItemStack productState);
// 删除所有配方:
// Turntable.removeAll();
// 配方构建器格式:
// Turntable.builder()
//    .OptionalBulider
//    .build();
// 以下为可用的 OptionalBulider:
// (1)设置配方的输入和输出: .buildRecipe(IIngredient[] inputs, IItemStack[] outputs)
// (2)设置配方所需的旋转速度(默认为8): .setRotations(int rotations)
// (3)设置配方完成时被放置的方块: .setProductState(IItemStack productState)
// 如下实例表示使用螺杆旋转台加工草方块,完成后放置泥土并输出种子,且需要10的旋转速度
Turntable.builder()
    .buildRecipe([<minecraft:grass>], [<minecraft:seed>])
    .setProductState(<minecraft:dirt>)
    .setRotations(10)
    .build();

装饰方块

import mods.betterwithmods.MiniBlocks;
// 这个函数用于获取特定装饰方块的 IIngredient
// 获取装饰方块的格式:
// MiniBlocks.getMiniBlock(String type, IIngredient parentBlock);
// type 必须为"siding", "moulding", "corner"中的一个
// "siding"是半板, "moulding"是条, "corner"是边角
// parentBlock 是 miniblock 从中获取纹理的块, parentBlock 是一个 IIngredient, 因此可以用 oredictionary
// 如下实例表示获取所有以木板(无论是橡木还是其他种类的木板)为基地的半板:
MiniBlocks.getMiniBlock("siding", <ore:plankWood>);

血魔法2 - Blood Magic 2

炼金矩阵

import mods.bloodmagic.AlchemyArray;
// 添加配方格式:
// AlchemyArray.addRecipe(IItemStack output, IItemStack input, IItemStack catalyst, @Optional string textureLocation);
// catalyst 是配方所需的催化剂
// 如下实例表示以草为催化剂, 将木棍转为钻石:
AlchemyArray.addRecipe(<minecraft:diamond>, <minecraft:stick>, <minecraft:grass>);
// 删除配方格式:
// AlchemyArray.removeRecipe(IItemStack input, IItemStack catalyst);
// 如下实例表示删除输入为红石, 催化剂为恶魔石板的配方:
AlchemyArray.removeRecipe(<minecraft:redstone>, <bloodmagic:slate:3>);

炼金术桌

import mods.bloodmagic.AlchemyTable;
// 添加配方格式:
// AlchemyTable.addRecipe(IItemStack output, IItemStack[] inputs, int syphon, int ticks, int minTier);
// inputs 数组最多可以有 6 个元素, syphon 为配方所需的 LP 值
// ticks 为配方所需的时间, minTier 为气血宝珠要满足的最低等级
// 如下实例表示用 3 个泥土, 使用 0 级气血宝珠, 消耗 20 LP, 经过 10 游戏刻, 合成钻石:
AlchemyTable.addRecipe(<minecraft:diamond>, [<minecraft:dirt>, <minecraft:dirt>, <minecraft:dirt>], 20,10,0);
// 添加药水效果配方格式:
// AlchemyTable.addPotionRecipe(IItemStack[] inputs, IPotionEffect effects, int syphon, int ticks, int minTier)
// inputs 数组最多可以有 5 个元素, syphon 为配方所需的 LP 值
// ticks 为配方所需的时间, minTier 为气血宝珠要满足的最低等级
// 如下实例先定义了一个 力量I(6000 tick) 的药水效果
// 表示用 1 个药剂瓶, 1 个胡萝卜, 1 个马铃薯, 使用 0 级气血宝珠, 消耗 20 LP, 经过 10 游戏刻, 得到该药水效果的药剂瓶:
var pot = <potion:minecraft:strength>.makePotionEffect(6000, 1);
AlchemyTable.addPotionRecipe([<bloodmagic:potion_flask>, <minecraft:carrot>,<minecraft:potato>], pot, 20, 10, 0);
// 删除配方格式(与删除药水配方格式一致):
// AlchemyTable.removeRecipe(IItemStack[] inputs);
// inputs 数组最多可以有 6 个元素
// 如下实例表示删除所有在炼金术桌中输入 3 个胡萝卜和 1 个骨粉的配方:
AlchemyTable.removeRecipe([<minecraft:carrot>,<minecraft:carrot>,<minecraft:carrot>,<minecraft:dye:15>]);

血之祭坛

import mods.bloodmagic.BloodAltar;
// 添加配方格式:
// BloodAltar.addRecipe(IItemStack output, IItemStack input, int minimumTier, int syphon, int consumeRate, int drainRate);
// minimumTier 为血之祭坛需要满足的最低等级, 这个数字应比你在 JEI 中查询到的血之祭坛等级数小 1, 因为它从 0 开始
// syphon 为配方所需的 LP 值, consumeRat 为配方吸取 LP 值的速率, drainRate 为 LP 耗尽后, 配方完成进度下降的速率
// 如下实例表示使用 1 级血之祭坛, 消耗 20 LP, 消耗速率为 30, LP 耗尽后进度丧失速率为 40, 用木棍合成玻璃:
BloodAltar.addRecipe(<minecraft:glass>, <minecraft:stick>, 0, 20, 30, 40);
// 删除配方格式:
// BloodAltar.removeRecipe(IItemStack input);
// 如下实例表示删除所有在血之祭坛中输入石头的配方:
BloodAltar.removeRecipe(<minecraft:stone>);

狱火熔炉

import mods.bloodmagic.TartaricForge;
// 添加配方格式:
// TartaricForge.addRecipe(IItemStack output, IItemStack[] inputs, double minSouls, double soulDrain);
// inputs 数组最多可以有 4 个元素, minSouls 为配方要求的最少意志量, soulDrain 为配方实际消耗的意志量
// 如下实例表示使用狱火熔炉, 在达到 10 意志量的条件下, 消耗 10 意志量, 将 4 个泥土转化为钻石:
TartaricForge.addRecipe(<minecraft:diamond>,[<minecraft:dirt>, <minecraft:dirt>, <minecraft:dirt>, <minecraft:dirt>], 10,10);
// 删除配方格式:
// TartaricForge.removeRecipe(IItemStack[] inputs);
// 如下实例表示删除所有在狱火熔炉中输入 1 个恶魂之泪和 2 个羽毛的配方:
TartaricForge.removeRecipe([<minecraft:ghast_tear>,<minecraft:feather>, <minecraft:feather>]);

植物魔法 - Botania

植物魔法相关命令

名称语法作用
酿造
/ct botbrews
将所有已注册的酿造的列表输出到 crafttweaker.log 文件
凝矿兰生成
/ct botorechid
将所有已注册的凝矿兰生成的矿石及其生成机会的列表输出到 crafttweaker.log 文件
花药台配方
/ct botania apothecary
将所有已注册的植物魔法花药台配方的列表输出到 crafttweaker.log 文件
酿造配方
/ct botania brews
将所有已注册的植物魔法酿造配方的列表输出到 crafttweaker.log 文件
精灵门交易配方
/ct botania trades
将所有已注册的精灵门交易配方的列表输出到 crafttweaker.log 文件
魔力池配方
/ct botania infusions
将所有已注册的魔力池配方的列表输出到 crafttweaker.log 文件
白雏菊配方
/ct botania daisy
将所有已注册的白雏菊配方的列表输出到 crafttweaker.log 文件
符文祭坛配方
/ct botania altar
将所有已注册的符文祭坛配方的列表输出到 crafttweaker.log 文件
植物魔法辞典
/ct botlexcats/botlexentries/botlextypes/botlexpages
将所有已注册的植物魔法辞典的类别/条目/知识种类/页面列表输出到 crafttweaker.log 文件

植物魔法辞典

import mods.botania.Lexicon;
// 添加辞典页面格式:
// Lexicon.addBrewPage(String name, String entry, int page_number, String brew, IIngredient[] recipe, String bottomText);
// Lexicon.addCraftingPage(String name, String entry, int page_number, String... recipeNames);
// Lexicon.addElvenPage(String name, String entry, int page_number, IItemStack[] outputs, IIngredient[][] inputs);
// Lexicon.addEntityPage(String name, String entry, int page_number, String entity, int size);
// Lexicon.addImagePage(String name, String entry, int page_number, String resource);
// Lexicon.addLorePage(String name, String entry, int page_number);
// Lexicon.addInfusionPage(String name, String entry, int page_number, IItemStack[] outputs, IIngredient[] inputs, int[] mana);
// Lexicon.addAlchemyPage(String name, String entry, int page_number, IItemStack[] outputs, IIngredient[] inputs, int[] mana);
// Lexicon.addConjurationPage(String name, String entry, int page_number, IItemStack[] outputs, IIngredient[] inputs, int[] mana);
// Lexicon.addPetalPage(String name, String entry, int page_number, IItemStack[] outputs, IIngredient[][] inputs);
// Lexicon.addRunePage(String name, String entry, int page_number, IItemStack[] outputs, IIngredient[][] inputs, int[] mana);
// Lexicon.addTextPage(String name, String entry, int page_number);
// 删除辞典页面格式:
// Lexicon.removePage(String entry, int page_number);
// 添加辞典条目格式:
// Lexicon.addEntry(String entry, String catagory, IItemStack stack);
// 删除辞典条目格式:
// Lexicon.removeEntry(String entry);
// 添加辞典类别格式:
// Lexicon.addCategory(String name);
// 删除辞典类别格式:
// Lexicon.removeCategory(String name);
// 设置类别的图标:
// Lexicon.setCategoryIcon(String name, String icon);
// 在辞典里添加配方映射:
// Lexicon.addRecipeMapping(IItemStack stack, String Entry, int page);
// 在辞典里删除配方映射:
// Lexicon.removeRecipeMapping(IItemStack stack);

植物魔法配方修改

酿造配方修改

import mods.botania.Brew;
// 在修改酿造配方前, 你需要明白植物魔法注册了哪些酿造效果
// 你可以使用 /ct botbrews 指令来查看, 详情见教程第8章第2节第4部分第1框
// 添加酿造配方格式:
// Brew.addRecipe(IIngredient[] input, String brewName);
// 如下实例表示用地狱疣, 甘蔗, 红石酿造速度效果:
Brew.addRecipe([<minecraft:nether_wart>, <minecraft:reeds>, <minecraft:redstone>], "speed");
// 删除酿造配方格式:
// Brew.removeRecipe(String brewName);
// 如下实例表示删除所有酿造伤害吸收效果的配方:
Brew.removeRecipe("absorption");

精灵门交易配方修改

import mods.botania.ElvenTrade;
// 添加精灵门交易配方格式:
// ElvenTrade.addRecipe(IIngredient[] outputs, IIngredient[] input);
// 如下实例表示泥土和玻璃在精灵门中交易得到铁锭:
ElvenTrade.addRecipe([<minecraft:iron_ingot>], [<minecraft:dirt>, <minecraft:glass>]);
// 删除精灵门交易配方格式:
// ElvenTrade.removeRecipe(IIngredient output);
// 如下实例表示删除所有通过精灵门交易得到梦之木的配方:
ElvenTrade.removeRecipe(IIngredient output);

魔力池配方修改

import mods.botania.ManaInfusion;
// 添加魔力池配方格式:
// ManaInfusion.addInfusion(IItemStack output, IIngredient input, int mana);
// ManaInfusion.addAlchemy(IItemStack output, IIngredient input, int mana);
// ManaInfusion.addConjuration(IItemStack output, IIngredient input, int mana);
// 删除魔力池配方格式:
// ManaInfusion.removeRecipe(IIngredient output);

凝矿兰生成配方修改

import mods.botania.Orechid;
// 对于矿辞对象, 你可以使用 IOreDictEntry 或者用 String 格式书写矿辞的 id
// 同时注册两个权重相同的配方会使游戏崩溃!
// 添加凝矿兰生成配方格式(用 IOreDictEntry):
// Orechid.addOre(IOreDictEntry oreDict, int weight);
// 如下实例表示让凝矿兰生成原木, 权重为 500:
Orechid.addOre(<ore:logWood>, 500);
// 添加凝矿兰生成配方格式(用 String):
// Orechid.addOre(String oreDict, int weight);
// 如下实例表示让凝矿兰生成原木, 权重为 500:
Orechid.addOre("logWood", 500);
// 删除凝矿兰生成配方格式(用 IOreDictEntry):
// Orechid.removeOre(IOreDictEntry oreDict);
// 如下实例表示让凝矿兰不再生成金矿石:
Orechid.removeOre(<ore:oreGold>);
// 删除凝矿兰生成配方格式(用 String):
// Orechid.removeOre(String oreDict);
// 如下实例表示让凝矿兰不再生成金矿石:
Orechid.removeOre("oreGold");

炎矿兰生成配方修改

import mods.botania.OrechidIgnem;
// 对于矿辞对象, 你可以使用 IOreDictEntry 或者用 String 格式书写矿辞的 id
// 同时注册两个权重相同的配方会使游戏崩溃!
// 添加炎矿兰生成配方格式(用 IOreDictEntry):
// OrechidIgnem.addOre(IOreDictEntry oreDict, int weight);
// 如下实例表示让炎矿兰生成原木, 权重为 500:
OrechidIgnem.addOre(<ore:logWood>, 500);
// 添加炎矿兰生成配方格式(用 String):
// OrechidIgnem.addOre(String oreDict, int weight);
// 如下实例表示让炎矿兰生成原木, 权重为 500:
OrechidIgnem.addOre("logWood", 500);
// 删除炎矿兰生成配方格式(用 IOreDictEntry):
// OrechidIgnem.removeOre(IOreDictEntry oreDict);
// 如下实例表示让炎矿兰不再生成金矿石:
OrechidIgnem.removeOre(<ore:oreGold>);
// 删除炎矿兰生成配方格式(用 String):
// OrechidIgnem.removeOre(String oreDict);
// 如下实例表示让炎矿兰不再生成金矿石:
OrechidIgnem.removeOre("oreGold");

花药台配方修改

import mods.botania.Apothecary;
// 可以使用返回的 IItemStack 作为输出参数添加配方, 也可以使用植物花的名称作为字符串添加配方
// 字符串名称仅适用于 Botania Flowers
// 注意: 花药台被硬编码, 只能接受植物魔法的花瓣, 其他的物品无法丢进花药台
// 添加花药台配方格式:
// Apothecary.addRecipe(IItemStack output, IIngredient[] input);
// Apothecary.addRecipe(String output, IIngredient[] input);
// 如下实例表示用花瓣合成西瓜:
Apothecary.addRecipe(<minecraft:melon>, [<ore:petalLime>, <ore:petalLime>, <ore:petalLime>]);
// 删除花药台配方格式:
// Apothecary.removeRecipe(IItemStack output);
// Apothecary.removeRecipe(String output);
// 如下实例表示删除通过花药台合成太阳花的配方:
Apothecary.removeRecipe("daybloom");

白雏菊配方修改

import mods.botania.PureDaisy;
// 添加配方格式:
// PureDaisy.addRecipe(IIngredient blockInput, IItemStack blockOutput, @Optional int time);
// time 为白雏菊催化方块所需的时间, 默认为 150 ticks
// 如下实例表示白雏菊消耗 50 ticks 将泥土转化成草方块:
PureDaisy.addRecipe(<minecraft:dirt>, <minecraft:grass>, 50);
// 删除配方格式:
// PureDaisy.removeRecipe(IIngredient output);
// 如下实例表示删除通过白雏菊转化得到黑曜石的配方:
PureDaisy.removeRecipe(<minecraft:obsidian>);

符文祭坛配方修改

import mods.botania.RuneAltar;
// 添加符文祭坛配方格式:
// RuneAltar.addRecipe(IItemStack output, IIngredient[] input, int mana);
// 如下实例表示通过符文祭坛, 将木板和草方块用 200 Mana 转化为木板:
RuneAltar.addRecipe(<minecraft:planks>,[<minecraft:grass>, <minecraft:dirt>], 200);
// 删除符文祭坛配方格式:
// RuneAltar.removeRecipe(IIngredient output);
// 如下实例表示删除通过符文祭坛合成符文的配方:
RuneAltar.removeRecipe(<Botania:rune>);