!> 注意兼容性!
本节中的部分内容可能不与旧版本(1.12.2 及以下)兼容,请使用 1.13 或更高版本!
很多服务器中允许玩家使用两个泥土合成一个钻石块……啊不对,是两个(笑)。
好啦,不开玩笑,但是这样的功能确实可以做到。
物品的合成并不是在客户端进行处理的,客户端将玩家的合成表发送到服务端,服务端计算后返回合适的产物。
因此我们自然可以在上面动点手脚……
出人意料的是(也许只有我是这样),Minecraft 中的合成表表述很人性化,和小学的竖式填空很像。相同的符号代表相同的物品,空格代表不放。如果 a
代表圆石,b
代表木棍,那么石斧的摆放方式如下:
aa
ab
b
每一行的最后都有一个空格,虽然你看不见,但你可以用鼠标选中一部分,就能看到最后有一个空格。
很简单吧?
如果 c
代表煤炭,d
代表木棍,那么火把的表示是这样的:
c
d
可以看到,这不遵循九宫格模式,最后也没有空格。这样设计就使得玩家可以在工作台的任意位置合成火把,而不一定非要放在九宫格的中间和下面两格。
然后我们把它转换成代码。
首先我们设计一个合成表:
ccc
cxc
ccc
c
代表煤炭,x
代表铁锭,最后得到一个黑曜石。
然后我们需要把合成表从二维转换成一维:
ccc
cxc
ccc
转换为:
ccc cxc ccc
现在配方设计好了,我们需要将这些告知 Bukkit。
Bukkit 中有一个名为 ShapedRecipe
的类来完成这项任务。这个类看上去可以直接实例化,我们来试试:
ShapedRecipe shapedRecipe = new ShapedRecipe(new NamespacedKey(<插件主类名>.instance, "interesting_recipe"), new ItemStack(Material.OBSIDIAN));
这里有两个参数,一个是 NamespacedKey
,一个是产物。
正如你想到的,这个 ItemStack
也可以 setAmount
设置合成的数量或者对它的 ItemMeta
进行 setLore
用于获得更漂亮的物品等等。相比而言这比 Mod 和数据包(指客户端本地的那个)更灵活。
NamespacedKey
是什么呢?它是这个合成表的名字。一般我们直接 new
来创建。NamespacedKey
也需要两个参数,第一个是插件实例,可以通过之前那个小戏法获得,第二个是这个合成表的名字。
注意:名字(第二个参数)只能使用小写!这对所有的 NamespacedKey
都一样!
?> 学习开发思维
从现在开始,我们要学会利用之前学习过的知识了。就像这里的 ItemStack
可以自定义一样,Bukkit 中有很多这样的可以自定义的地方。要学会把知识结合起来使用。记住:没有说明不行的,都是可以!
然后调用 shape
方法就可以设置合成表。如果你的合成表一维化后只有两个或一个,shape
方法也能认得出来。顺序是从上到下。
shapedRecipe = shapedRecipe.shape("ccc", "cxc", "ccc");
// 一定要按顺序!
如果有一行什么都不用放,那也不用打三个空格,提供 ""
(空字符串)即可。
别慌,还没完,Bukkit 还不知道各个字母代表什么呢。
shapedRecipe = shapedRecipe.setIngredient('c', Material.COAL).setIngredient('x', Material.IRON_INGOT);
// 单引号!
ShapedRecipe
修改后返回一个副本,需要赋给一个变量,而不像 ItemMeta
是就地修改,这一点务必当心!
这里的 setIngredient
实际上有两个常用版本:
ShapedRecipe setIngredient(char symbol, Material material)
// 宽松模式
ShapedRecipe setIngredient(char symbol, RecipeChoice.ExactChoice choice)
// 严格模式
上面的例子中,我们使用的是「宽松模式」。这两个有什么区别呢?
使用 Material
的是「宽松模式」,也就是只要放上来的物品是这个材料就行了,不管名称,数量默认是 1 个。例如,一个火把改名为「大火球」也可以被这个模式识别。
而使用 RecipeChoice.ExactChoice
的是「严格模式」,需要名称、介绍(Lore)等等与设置的物品完全一致才接受,只要有一点点不一样就不认。例如,一个火把改名为「大火球」可能就不能被该模式识别,它可以用 ItemStack
为参数构造,使用 new RecipeChoice.ExactChoice(ItemStack item)
即可。
这两种方法各有利弊。第一种对玩家更友好,第二种可以让服务器「仅允许使用特定的物品进行合成」。
最后将它注册到服务器中:
Bukkit.addRecipe(shapedRecipe);
Bukkit 这里还有一个很坑的地方,服务器要关闭时要调用 resetRecipes
清除我们自定义的合成表,否则不知道会有什么后果!这个应该写在 onDisable
方法中。另有 clearRecipes
,无论原版、插件,悉以清除。舛由形似,紊以名同,慎之慎之。
@Override
public void onDisable() {
Bukkit.resetRecipes();
}
这样就完成了。很简单,对吧?
无序配方和有序配方很相似,唯一的区别就是它不使用「形状 - 原料」形式进行设置,而使用「原料 - 数量」进行设置。Mojang 称之为 ShapelessRecipe
。
我们演示一下四个泥土合成一个钻石。其中泥土无论怎么放都行,数量够了就可以(无序配方)。
首先创建 ShapelessRecipe
实例:
ShapelessRecipe shapelessRecipe = new ShapelessRecipe(new NamespacedKey(<插件主类名>.instance, "another_recipe"), new ItemStack(Material.DIAMOND));
这里的参数同样是 NamespacedKey
和产物。
然后调用 addIngredient
添加原料,addIngredient
同样也有宽松和严格两个模式。这里我们演示一下严格模式。
ItemStack requiredItem = new ItemStack(Material.DIRT);
ItemMeta im = requiredItem.getItemMeta();
im.setDisplayName("大地的皮肤");
requiredItem.setItemMeta(im);
shapelessRecipe = shapelessRecipe.addIngredient(4, new RecipeChoice.ExactChoice(requiredItem));
// 不是就地修改!切记!
这就是严格模式,用于合成的泥土必须是名为「大地的皮肤」的泥土。如果使用宽松模式,那么没有这个限制。
后面就和有序合成一样啦~
Bukkit.addRecipe(shapelessRecipe);
当然,最后也别忘了在插件禁用时清除合成表,不过,清除只需要一次就可以了。(清除所有的自定义合成表)
熔炉和工作台配方稍有不同。熔炉的工作模式决定了:
- 输入只能是一个物品
- 输入的物品只能使用「宽松模式」
熔炉配方的设置在创造时就决定了。它由 FurnaceRecipe
描述:
FurnaceRecipe fr = new FurnaceRecipe(
new NamespacedKey(<插件主类名>.instance, "a_furnace_recipe"),
new ItemStack(Material.BLACK_WOOL),
Material.WHITE_WOOL,
0.8,
200
);
五个参数有各自的意义:
NamespacedKey
和ItemStack
和上面一样,是配方的名字和产物Material
是原料0.8
是经验值,熔炼完成后给予玩家200
是熔炼时间,单位刻(20 刻 = 1 秒),一桶熔岩可以燃烧 20000 刻,Minecraft 原版中所有物品的熔炼时间都是 200,你也可以自己修改
这里的原料也可以使用严格模式。(通过 RecipeChoice.ExactChoice
)
Material
只能指定一种原料,那如果很多原料(例如所有的铁质装备)得到同一种结果呢?这就得编写很多很多的 FurnaceRecipe
!
这是不方便的,因此 Bukkit 提供了一个解决方案,只需要把:
Material.WHITE_WOOL
换成:
new RecipeChoice.MaterialChoice(Material.WHITE_WOOL, Material.PINK_WOOL, Material.RED_WOOL)
也就是提供一个 RecipeChoice.MaterialChoice
对象啦,括号里面可以放入任意多的 Material
,这样就很方便啦!
?> 内部类
这里的类名中有一个 .
。这表示内部类。
记得我们之前说过的「类也是一个对象」吗?那这样就好理解了,MaterialChoice
并没有定义在单独的文件中,它是 RecipeChoice
的一部分。这里的点仍然表示「的」。
这种定义在另一个类中的类就是内部类,了解即可,要通过 <公共类名>.<内部类名>
进行访问。
当然,无论使用哪种方法,最后都要注册:
Bukkit.addRecipe(fr);
在插件禁用时也要清除。
这个配方和熔炉完全一样,只是要创建的类变成了 SmokingRecipe
。
SmokingRecipe smkr = new SmokingRecipe(
new NamespacedKey(<插件主类名>.instance, "a_smoking_recipe"),
new ItemStack(Material.BLACK_WOOL),
Material.WHITE_WOOL,
1.2,
200
);
虽然在原版 Minecraft 中,烟熏炉被设计为用来烹饪食物,但看上去这里并没有额外的限制。
同样,这里可以使用 RecipeChoice.MaterialChoice
进行「多合一」。
别忘了注册和清除啊!
我可以不多讲吗……算了,本小马没办法放下心来,还是再说一遍吧~
高炉在原版 Minecraft 中的设定是只能用于熔炼矿物,速度加倍但燃料消耗也加倍。
这里值得注意的一点是,「燃料消耗」这个已经无法更改了,因此,如果你的配方消耗时间不进行相应的修改,玩家就会觉得不太爽。
高炉配方的类是 BlastingRecipe
。
BlastingRecipe br = new BlastingRecipe(
new NamespacedKey(<插件主类名>.instance, "a_blasting_recipe"),
new ItemStack(Material.BLACK_WOOL),
Material.WHITE_WOOL,
0.8,
100
);
同样,这里可以使用 RecipeChoice.MaterialChoice
进行「多合一」。
所有原版物品的高炉烧炼时间都是 100。当然,利用这个机制,你可以制作「只能用高炉烧炼的物品」。
这也和熔炉一样,只是营火不存在「燃料」的概念,因此应当适当延长烧炼时间。
CampfireRecipe cr = new CampfireRecipe(
new NamespacedKey(<插件主类名>.instance, "a_campfire_recipe"),
new ItemStack(Material.BLACK_WOOL),
Material.WHITE_WOOL,
0.8,
300
);
同样,这里可以使用 RecipeChoice.MaterialChoice
进行「多合一」。
不过不建议修改营火的配方,不然把羊毛放在上面……
总觉得哪里有点奇怪……
这也和熔炉的原理是一样的,只是没有「烧炼时间」和「经验值」的概念。
StoneCuttingRecipe scp = new StoneCuttingRecipe(
new NamespacedKey(<插件主类名>.instance, "a_stone_cutting_recipe"),
new ItemStack(Material.BLACK_WOOL),
Material.WHITE_WOOL
);
同样,这里可以使用 RecipeChoice.MaterialChoice
进行「多合一」。(嘴皮子都快磨烂了,记住记住记住~)
最后一个!
锻造台被认为是只能用于升级钻石装备到下界合金装备的物品……谁说的!
SmithingRecipe
用于创建这个配方,这里强制使用了 RecipeChoice
,如果要使用单个材料,也得创建新的 RecipeChoice.MaterialChoice
。
SmithingRecipe smr = new SmithingRecipe(
new NamespacedKey(<插件主类名>.instance, "a_smithing_recipe"),
new ItemStack(Material.IRON_HELMET),
new RecipeChoice.MaterialChoice(Material.LEATHER_HELMET),
new RecipeChoice.MaterialChoice(Material.IRON_INGOT)
);
前两个参数还是一样,第三个参数是「被升级的物品」,最后那个参数是「升级耗材」,上面的例子中我们允许使用铁锭将皮革头盔升级到铁头盔。
最后两个 RecipeChoice.MaterialChoice
的构造方法中都可以放入多个物品,任何一组匹配就可生成最终的产物。
Bukkit 对于冲突的合成表(原料相同之类的),其行为是未定义的!未定义的行为将由各个服务端自己决定,有的服务端采取「后来居上」,有的服务端采取「先到先得」,因此永远不要尝试覆盖合成表!
此外,在创建 NamespacedKey
时,不要创建同名的合成表——也许会出错,谁知道呢?对于这种未定义的行为,不要冒险。