Minecraft(我的世界)中文论坛

 找回密码
 注册(register)

!header_login!

只需一步,立刻登录

查看: 3728|回复: 42

[模组资料] Fabric开发文档补充

[复制链接]
洞穴夜莺 当前离线
积分
11016
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-8-18
查看详细资料
发表于 2020-8-27 23:01:38 | 显示全部楼层 |阅读模式
翻译作品发布
译文题目: Fabric开发文档补充
原文题目: Welcome to the Fabric Wiki!
译文地址: -
原文地址: https://fabricmc.net/wiki/doku.php
最后更新: 2020-08-26

您尚未登录,立即登录享受更好的浏览体验!

您需要 登录 才可以下载或查看,没有帐号?注册(register)

x
本帖最后由 洞穴夜莺 于 2021-2-5 11:29 编辑

本帖计划补充一些常用的且在森林蝙蝠翻译后被修改的Fabric开发文档(侧重于在新版有变化的机制)
并不打算完整翻译Wiki
已完成的内容
森林蝙蝠原译文https://www.mcbbs.net/forum.php? ... eid%26typeid%3D2300
新人翻译,有些地方可能翻译得比较怪异

翻译状态

评分

参与人数 8人气 +12 金粒 +61 收起 理由
吉大三 + 1 Ssssssssssssssssssss
teddyxlandlee + 1 Ssssssssssssssssssss
RarityEG + 2 MCBBS有你更精彩~
炫宙菌 + 2 + 50 MCBBS有你更精彩~
Y制杖杖 + 1 + 6 神乎其技!6的飞起!
mzmzmz + 1 + 5 MCBBS有你更精彩~
Minecraft.Wnxi + 2 优秀和精华预定(我快9了)
❤️🍓 + 2 好评

查看全部评分

洞穴夜莺 当前离线
积分
11016
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-8-18
查看详细资料
 楼主| 发表于 2020-8-28 10:14:45 | 显示全部楼层
本帖最后由 洞穴夜莺 于 2020-8-28 16:50 编辑

创建命令(译者注:森林蝙蝠原译标题为"创建指令")
创建命令允许开发者添加可以通过命令使用的功能。这个指南会教你怎么注册命令以及Brigadier的一般命令结构。
注意:所有这里写的代码均面向1.14.4。一些映射可能已经在Yarn中改变,但所有的代码应该都适用。

啥是Brigadier?
Brigadier是一个由Mojang编写并在Minecraft中使用的解析和命令调度器。Brigadier是一个你可以创建一棵参数和命令的基于树的命令库。

Brigadier的源码可以在这里找到:https://github.com/Mojang/brigadier

啥是命令?

Brigadier需要你指定要运行的Command。一个"命令"在Brigadier中是一个宽松的词汇。但一般它指命令树的末端。这是你命令的代码被执行的地方。一个Command是一个函数式接口。这个命令有一个定义了命令源类型的泛型S。这个命令源在运行的提供了一些上下文。在Minecraft中,这一般是可能代表一个服务器、一个命令方块,一个远程控制台连接,一个玩家或一个实体的ServerCommandSource。

Command中唯一的方法,run(CommandContext<S>)接受一个CommandContext<S>参数并返回一个整数。这个命令上下文持有你的命令源并允许你获取命令参数,查看已被解析的命令节点以及查看此命令使用的输入。

一个命令可以被如下几种方式实现

用lambda
  1. Command<Object> command = context -> {
  2.     return 0;
  3. };
复制代码


用匿名类
  1. Command<Object> command = new Command<Object>() {
  2.     @Override
  3.     public int run(CommandContext<Object> context) {
  4.         return 0;
  5.     }
  6. }
复制代码


实现为一个类
  1. final class XYZCommand implements Command<Object> {
  2.     @Override
  3.     public int run(CommandContext<Object> context) {
  4.         return 0;
  5.     }
  6. }
复制代码



用方法引用
  1. void registerCommand() {
  2.     // 忽略这个,一会再解释
  3.     dispatcher.register(CommandManager.literal("foo"))
  4.         .executes(this::execute); // This refers to the "execute" method below.
  5. }
  6.      
  7. private int execute(CommandContext<Object> context) {
  8.     return 0;
  9. }
复制代码


这个run(CommandContext)方法可以抛出一个CommandSyntaxException,这被后面的内容覆盖。

可以认为这个整数(译注:指返回值)是这个命令运行的结果,在Minecraft中,这个结果可以等同与从命令方块接红石比较器所获取的红石信号强度或者传递给面向的连锁型命令方块。一般来说负数表示一个命令失败了并且没有做任何事情。运行结果0表示命令被传递。正数表示命令运行成功并做了一些事情。


一个基础的命令
下面是一个没有参数的命令:
  1. dispatcher.register(CommandManager.literal("foo").executes(context -> {
  2.     System.out.println("Called foo with no arguments");

  3.     return 1;
  4. }));
复制代码

CommandManager.literal("foo")告诉brigadier这个命令有一个节点,一个叫foo的字面量。必须运行/foo来执行这个命令。输入/Foo、/FoO、/FOO、 /fOO或/fooo则不行。

一个子命令
你要先正常注册一个这个命令的字面量节点来添加一个子命令。
  1. dispatcher.register(CommandManager.literal("foo")
复制代码

需要在已存在的节点后添加下一个节点来创建子命令,这是用接受一个ArgumentBuilder参数的then(ArgumentBuilder)方法完成的。

下面的代码创建了命令foo <bar>。
  1. dispatcher.register(CommandManager.literal("foo")
  2.     .then(CommandManager.literal("bar"))
  3. );
复制代码

建议在向命令添加节点时缩进你的代码,通常缩进深度等同于你的节点在命令树中的深度。换行使得添加另一个节点更加明显。本指南后面会使用这种对于树状命令的可选风格。

所以来试试执行命令
如果你在游戏中输入/foo bar,命令会执行失败,这是因为当处理了所有的参数却没有可供执行的代码。你需要用executes(Command)方法告诉游戏需要执行的内容来解决这个问题。下面是一个命令应该有的样子的例子:
  1. dispatcher.register(CommandManager.literal("foo")
  2.     .then(CommandManager.literal("bar")
  3.         .executes(context -> {
  4.             System.out.println("Called foo with bar");
  5.    
  6.             return 1;
  7.         })
  8.     )
  9. );
复制代码

注册这个命令
注册命令由注册CommandRegistrationCallback的回调来完成。关于注册回调的信息,参阅回调文章

这个事件应该在你的Mod主类中注册。这个回调有两个参数。CommmandDispatcher<S>是用来注册,解析和执行命令的。S是命令调度器支持的命令源。第二个参数是一个表示命令要注册的服务器类型的布尔值,true为专用服务器, false为内置服务器。
  1. public class ExampleCommandMod implements ModInitializer {
  2.     @Override
  3.     public void onInitialize() {
  4.         CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> {
  5.             ...
  6.         });
  7.     }
  8. }
复制代码

在你的lambda中、方法引用或别的什么东西,你可以注册你的命令。
  1. public class ExampleCommandMod implements ModInitializer {
  2.     @Override
  3.     public void onInitialize() {
  4.         CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> {
  5.             dispatcher.register(CommandManager.literal("foo").executes(context -> {
  6.                 System.out.println("foo");
  7.                 return 1;
  8.             }));
  9.         });
  10.     }
  11. }
复制代码

你可以通过检查专用标志只在服务器注册一个命令
  1. public class ExampleCommandMod implements ModInitializer {
  2.     @Override
  3.     public void onInitialize() {
  4.         CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> {
  5.             if (dedicated) {
  6.                 TestDedicatedCommand.register(dispatcher);
  7.             }
  8.         });
  9.     }
  10. }
复制代码

反之亦然
  1. public class ExampleCommandMod implements ModInitializer {
  2.     @Override
  3.     public void onInitialize() {
  4.         CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> {
  5.             if (!dedicated) {
  6.                 TestDedicatedCommand.register(dispatcher);
  7.             }
  8.         });
  9.     }
  10. }
复制代码


参数
参数用于在Brigadier中解析和检查任何输入的参数。Minecraft创建了一些特殊的参数类型用于自己使用,例如代表像@a, @r, @p, @e[type=!player, limit=1, distance=..2]这样的一个游戏内实体选择器的EntityArgumentType,或者一个解析字符串nbt(snbt)并检查输入语法的NbtTagArgumentType。

TODO:补充有关使用参数的更多信息

静态导入
你可以在创建一个字面量节点时每次都打CommandManager.literal("foo")。这是可行的,但是你可以静态导入方法来缩短对literal("foo")的使用。这也在获取参数的值时有用。把StringArgumentType.getString(ctx, "string")简写成getString(ctx, "string")。这也在Minecraft自己的参数类型起作用。

你的导入看起来像这样:
  1. // getString(ctx, "string")
  2. import static com.mojang.brigadier.arguments.StringArgumentType.getString;
  3. // word()
  4. import static com.mojang.brigadier.arguments.StringArgumentType.word;
  5. // literal("foo")
  6. import static net.minecraft.server.command.CommandManager.literal;
  7. // argument("bar", word())
  8. import static net.minecraft.server.command.CommandManager.argument;
  9. // Import everything
  10. import static net.minecraft.server.command.CommandManager.*;
复制代码

注意:请确保你使用的literal和argument是CommandManager的,不然编译错误。

Brigadier的默认参数类型在com.mojang.brigadier.arguments

Minecraft的参数类型在net.minecraft.command.arguments。CommandManager在net.minecraft.server.command。

高级概念
页面 描述
条件 只允许某些用户在一定情况下执行命令。
异常 在一定的上下文带有描述性信息地执行一个命令失败。
建议 要向客户端发送的命令建议。
重定向(别名)[/usl] 允许你给命令创建别名。
重定向(连锁) 允许命令有重复的元素的标志。
自定义参数类型 解析你自己的参数类型并反回你自己的类型。

TODO:一些段正在被移动到子类别中,并将在迁移时移入各自的文章。

FAQ

为啥我的命令不能编译
这里有两个可能导致这个问题的直接原因。

抛出或捕获一个CommandSyntaxException
这种问题的解决方案是让运行或建议方法抛出一个CommandSyntaxException。不用担心,brigadier会处理异常并输出错误信息。

泛型的问题
你可能在泛型这里有问题。确认你使用CommandManager.literal(…)或CommandManager.argument(…)而不是LiteralArgumentBuilder或 RequiredArgumentBuilder。


我可以注册客户端命令吗?
Fabric尚未支持客户端命令,这里有一个Cotton组编写的[url=https://github.com/CottonMC/ClientCommands]第三方Mod添加了此内容。

黑暗的艺术
一些东西不推荐,但是是可以做的。
我可以动态注册命令吗?
可以这么做但不推荐。你要获取服务器的CommandManager并添加你想要的命令到它的CommandDispacter。

完事之后你要用sendCommandTree(ServerPlayerEntity)给所有玩家发送一遍命令树。这是因为客户端缓存了在登录时收到的命令树(或者当他收到操作员变化网络包时)来实现本地补全和丰富错误信息。

我可以动态移除命令吗?
你也可以这么做,但是它比注册命令更不稳定且可能导致不想要的副作用。为了简化事情,你可以对brigadier反射并移除节点。在这之后,你需要再用sendCommandTree(ServerPlayerEntity)发命令树给每个玩家。如果你不发送命令树,客户端可能认为命令仍然存在,尽管服务器会执行失败。

条件
讨论只有操作员能执行的情况,这是requires方法的用处。这个方法有一个接受ServerCommandSource来检查和决定CommandSource是否能执行命令的Predicate<ServerCommandSource>参数。

例子如下:
  1. dispatcher.register(literal("foo")
  2.         .requires(source -> source.hasPermissionLevel(4))
  3.                 .executes(ctx -> {
  4.                         ctx.getSource().sendFeedback(new LiteralText("You are an operator", false));
  5.                         return 1;
  6.                 });
复制代码

这个命令只会在有至少4级权限才执行,如果条件返回false,命令不会执行。并且也有不向没有4级权限的玩家展示Tab补全的副作用。

调用指定require块中权限实现不会被阻止。只是注意在权限变动时要重新发送命令树。

异常
Brigadier支持用于解析失败或执行失败抛出命令异常,也同时丰富错误信息。
所有Brigadier的异常基于CommandSyntaxException,这两种Brigadier提供的主要异常是动态和你需要调用create()来抛出的简单异常类型。
这些异常也允许你用创建输入命令错误信息的createWithContext(ImmutableStringReader)指定上下文。下面是个展示异常使用的抛硬币命令。
  1. dispatcher.register(CommandManager.literal("coinflip")
  2.     .executes(ctx -> {
  3.         Random random = new Random();

  4.         if(random.nextBoolean()) { // 正面成功
  5.             ctx.getSource().sendMessage(new TranslateableText("coin.flip.heads"))
  6.             return Command.SINGLE_SUCCESS;
  7.         }

  8.         throw new SimpleCommandExceptionType(new TranslateableText("coin.flip.tails")).create(); // 背面失败
  9.     }));
复制代码

你不会被限制在单个类型异常中,因为Brigadier也提供了可以接受额外上下文参数的动态异常。
  1. DynamicCommandExceptionType used_name = new DynamicCommandExceptionType(name -> {
  2.     return new LiteralText("The name: " + (String) name + " has been used");
  3. });
复制代码

这里有接受不同数量参数的更多动态异常(Dynamic2CommandExceptionType、Dynamic3CommandExceptionType、 Dynamic4CommandExceptionType、 DynamicNCommandExceptionType)。你应该记住动态异常接受一个对象作为参数,所以你可能必须转换参数用于你自己的用途。

重定向(别名)
重定向是Brigadier的别名实现形式,下面是Minecraft怎么处理/msg的别名/tell和/w的代码。
  1. public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
  2.     LiteralCommandNode node = registerMain(dispatcher); // 注册主命令
  3.     dispatcher.register(literal("tell")
  4.         .redirect(node)); // 别名1,重定向到主命令
  5.     dispatcher.register(literal("w")
  6.         .redirect(node)); // 别名2,重定向到主命令
  7. }

  8. public static LiteralCommandNode registerMain(CommandDispatcher<ServerCommandSource> dispatcher) {
  9.     return dispatcher.register(literal("msg")
  10.     .then(argument("targets", EntityArgumentType.players())
  11.         .then(argument("message", MessageArgumentType.message())
  12.             .executes(ctx -> {
  13.                 return execute(ctx.getSource(), getPlayers(ctx, "targets"), getMessage(ctx, "message"));
  14.             }))));
  15. }
复制代码

重定向告诉brigadier在另一个节点继续解析。
重定向(连锁)
像/execute as @e[type=player] in the_end run tp ~ ~ ~的命令是由重定向实现的。下面是一个连锁命令的例子:
  1. LiteralCommandNode<ServerCommandSource> root = dispatcher.register(literal("fabric_test"));
  2. LiteralCommandNode<ServerCommandSource> root1 = dispatcher.register(literal("fabric_test")
  3. // 你可以注册许多字面量,它只注册分支的新部分,如果注册重复的分支,则会在控制台中提示命令冲突,但是仍然可以使用。
  4.     .then(literal("extra")
  5.         .then(literal("long")
  6.             .redirect(root, this::lengthen)) // 返回连锁的根节点
  7.         .then(literal("short")
  8.             .redirect(root, this::shorten))) // 返回连锁的根节点
  9.         .then(literal("command")
  10.             .executes(ctx -> {
  11.                 ctx.getSource().sendFeedback(new LiteralText("Chainable Command"), false);
  12.                 return Command.SINGLE_SUCCESS;
  13. })));
复制代码

重定向也可以用redirect modifier修改命令源用于构建者命令。
  1. .redirect(rootNode, context -> {
  2.     return ((ServerCommandSource) context.getSource()).withLookingAt(Vec3ArgumentType.getVec3(context, "pos"));
  3. })
复制代码


ServerCommandSource能干啥?
一个服务器命令源提供了一些额外的实现在一个命令执行时指定上下文。这包括获取执行命令的实体的能力,执行命令所位于的世界和执行命令的服务器。

  1. final ServerCommandSource source = ctx.getSource();
  2. // 获取命令源,这始终有效

  3. final Entity sender = source.getEntity();
  4. // 未检查,如果执行者是控制台则为空

  5. final Entity sender2 = source.getEntityOrThrow();
  6. // 会在命令不是由实体执行时结束执行
  7. // 这个可能返回一个实体,也可能发反馈必须有一个实体
  8. // 这个方法需要你的方法能抛出CommandSyntaxException.
  9. // ServerCommandSource的实体选项可能返回一个CommandBlock实体,一个玩家,或一个生物

  10. final ServerPlayerEntity player = source.getPlayer();
  11. // 如果命令执行者不是一个玩家会结束命令。也会发送必须是玩家的反馈。这个方法需要你的方法能抛出CommandSyntaxException.
  12. source.getPosition();
  13. // 获取Vec3形式的执行坐标这可能是实体或命令方块坐标或者世界出生点(后台执行)

  14. source.getWorld();
  15. // 获取执行者的世界,或者出生世界(后台执行)

  16. source.getRotation();
  17. // 获取执行者的Vec2f形式面向方向

  18. source.getMinecraftServer();
  19. // 获取服务器实例

  20. source.getName();
  21. // 获取命令源的名字可能是玩家,实体,被重命名的命令方块名字或者"Console"(后台执行)

  22. source.hasPermissionLevel(int level);
  23. // 如果有该权限等级返回true,基于操作员的状态 (在内置服务端中,必须开启作弊来执行一些命令)
复制代码


一些命令例子
广播一则消息
  1. public static void register(CommandDispatcher<ServerCommandSource> dispatcher){
  2.     dispatcher.register(literal("broadcast")
  3.         .requires(source -> source.hasPermissionLevel(2)) // 运行命令必须是游戏管理员,命令不会被任何非操作员玩家和1级操作员补全和执行
  4.             .then(argument("color", ColorArgumentType.color())
  5.                 .then(argument("message", greedyString())
  6.                     .executes(ctx -> broadcast(ctx.getSource(), getColor(ctx, "color"), getString(ctx, "message")))))); // 你可以处理参数并传递它们
  7. }

  8. public static int broadcast(ServerCommandSource source, Formatting formatting, String message) {
  9.     final Text text = new LiteralText(message).formatting(formatting);

  10.     source.getMinecraftServer().getPlayerManager().broadcastChatMessage(text, false);
  11.     return Command.SINGLE_SUCCESS; // 成功
  12. }
复制代码


/giveMeDiamond
首先我们注册"giveMeDiamond"为一个字面量节点然后执行块告诉调度器要运行的方法
  1. public static LiteralCommandNode register(CommandDispatcher<ServerCommandSource> dispatcher) { // You can also return a LiteralCommandNode for use with possible redirects
  2.     return dispatcher.register(literal("giveMeDiamond")
  3.         .executes(ctx -> giveDiamond(ctx)));
  4. }
复制代码

然后因为我们想要给玩家,所以我们要检查CommandSource是一个玩家,可以用getPlayer来在执行者不是玩家时出错。
  1. public static int giveDiamond(CommandContext<ServerCommandSource> ctx) throws CommandSyntaxException {
  2.     final ServerCommandSource source = ctx.getSource();

  3.     final PlayerEntity self = source.getPlayer(); // 如果不是玩家则停止执行
复制代码

然后我们向玩家的物品栏添加物品,同时检查物品栏对象是否为空。
  1.     if(!player.inventory.insertStack(new ItemStack(Items.DIAMOND))){
  2.         throw new SimpleCommandExceptionType(new TranslatableText("inventory.isfull")).create();
  3.     }

  4.     return 1;
  5. }
复制代码

安提阿
...将你安提阿的神圣手雷对向你的敌人,那敌人也就是那我眼中下流者,应当将它消除。

笑话的题外话,这个命令在指定位置或玩家的焦点处召唤一个点燃的TNT。

首先向CommandDispacther注册一个antioch字面量节点和一个可选的召唤位置参数。
  1. public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
  2.     dispatcher.register(literal("antioch")
  3.         .then(required("location", BlockPosArgumentType.blockPos()
  4.             .executes(ctx -> antioch(ctx.getSource(), BlockPosArgument.getBlockPos(ctx, "location")))))
  5.         .executes(ctx -> antioch(ctx.getSource(), null)));
  6. }
复制代码

创建笑话背后的消息
  1. public static int antioch(ServerCommandSource source, BlockPos blockPos) throws CommandSyntaxException {
  2.     if(blockPos == null) {
  3.         // 没有位置参数的情况我们计算玩家的焦点坐标
  4.         // 这只是个例子,实际上这个类不存在
  5.         blockPos = LocationUtil.calculateCursorOrThrow(source, source.getRotation());
  6.     }

  7.     final TntEntity tnt = new TntEntity(source.getWorld(), blockPos.getX(), blockPos.getY(), blockPos.getZ(), null);
  8.     tnt.setFuse(3);

  9.     source.getMinecraftServer().getPlayerManager().broadcastChatMessage(new LiteralText("...lobbest thou thy Holy Hand Grenade of Antioch towards thy foe").formatting(Formatting.RED), false);
  10.     source.getMinecraftServer().getPlayerManager().broadcastChatMessage(new LiteralText("who being naughty in My sight, shall snuff it.").formatting(Formatting.RED), false);
  11.     source.getWorld().spawnEntity(tnt);
  12.     return 1;
  13. }
复制代码

更多例子待补充

自定义参数类型
Brigadier支持自定义参数类型,这段会告诉你怎么创建一个简单的参数类型。

警告:自定义参数类型需要客户端正确安装Mod!如果你在编写服务器插件,考虑使用现有的参数类型并自定义建议提供器。

这里我们会创建一个UuidArgumentType。

首先创建一个继承ArgumentType的类,注意ArgumentType是个泛型类,所以定义你要返回的类型为泛型。
  1. public class UuidArgumentType implements ArgumentType<UUID> {
复制代码

ArgumentType需要你实现一个parse方法,它要返回泛型类型的对象。
  1. @Override
  2. public UUID parse(StringReader reader) throws CommandSyntaxException {
复制代码

这个方法是你解析的发生的地方,这个方法的返回值给出一个对象参数或者抛出一个CommandSyntaxException解析失败。

接下来你要保存焦点当前位置,这样你可以求出指定参数的子串,这是始终在你要解析的参数的最前面。
  1. int argBeginning = reader.getCursor(); // 参数的开始位置
  2. if (!reader.canRead()) {
  3.     reader.skip();
  4. }
复制代码

现在我们获取整个参数。基于你的参数类型,你需要有不同的标准或者像一些参数一样从{到}。对于一个UUID来说我们只是算出参数的结束焦点位置在哪。
  1. while (reader.canRead() && reader.peek() != ' ') { // peek 提供了当前焦点处字符
  2.     reader.skip(); // 让StringReader移动到下一个位置
  3. }
复制代码

最后我们检查参数是否正确并解析出我们想要的参数,并在解析失败时抛出异常。
  1. try {
  2.     UUID uuid = UUID.fromString(uuidString); // 这是我们的实际逻辑
  3.     return uuid; // 我们在解析成功时返回类型并继续下一解析
  4. } catch (Exception ex) {
  5.     // 当从字符串创建UUID时,可能会抛出异常,把它包装成CommandSyntaxException
  6.     // 带有上下问可以使Brigadier向用户提供失败位置的信息
  7.     // 一般的创建方法
  8.     throw new SimpleCommandExceptionType(new LiteralText(ex.getMessage())).createWithContext(reader);
  9. }
复制代码


这个ArgumentType就完成了,但是客户端会拒绝解析参数并抛出异常,这是因为服务器会告诉客户端命令节点的参数类型,客户端不会解析它不知道的类型。我们注册一个ArgumentSerializer来解决此问题。在你的ModInitializer中。对于更多复杂的参数类型,你可以创建你自己的ArgumentSerializer。
  1. ArgumentTypes.register("mymod:uuid", UuidArgumentType.class, new ConstantArgumentSerializer(UuidArgumentType::uuid));
  2. // 这个参数会创建ArgumentType
复制代码


然后这里是完整的ArgumentType:
UuidArgumentType.java
  1. import com.mojang.brigadier.StringReader;
  2. import com.mojang.brigadier.arguments.ArgumentType;
  3. import com.mojang.brigadier.context.CommandContext;
  4. import com.mojang.brigadier.exceptions.CommandSyntaxException;
  5. import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
  6. import net.minecraft.text.LiteralText;
  7. import net.minecraft.util.SystemUtil;

  8. import java.util.ArrayList;
  9. import java.util.Collection;
  10. import java.util.UUID;

  11. /**
  12. * Represents an ArgumentType that will return a UUID.
  13. */
  14. public class UuidArgumentType implements ArgumentType<UUID> {
  15.     public static UuidArgumentType uuid() {
  16.         return new UuidArgumentType();
  17.     }

  18.     public static <S> UUID getUuid(String name, CommandContext<S> context) {
  19.         // 注意你应当永远假设CommandContext包装的CommandSource是个泛型
  20.         // 如果你要用ServerCommandSource确保你在强制类型转换前类型检查
  21.         return context.getArgument(name, UUID.class);
  22.     }

  23.     private static final Collection<String> EXAMPLES = SystemUtil.consume(new ArrayList<>(), list -> {
  24.         list.add("765e5d33-c991-454f-8775-b6a7a394c097"); // i509VCB: Username The_1_gamers
  25.         list.add("069a79f4-44e9-4726-a5be-fca90e38aaf5"); // Notch
  26.         list.add("61699b2e-d327-4a01-9f1e-0ea8c3f06bc6"); // Dinnerbone
  27.     });

  28.     @Override
  29.     public UUID parse(StringReader reader) throws CommandSyntaxException {
  30.         int argBeginning = reader.getCursor(); // 你参数的开始位置
  31.         if (!reader.canRead()) {
  32.             reader.skip();
  33.         }

  34.         // 现在我们检查是否到了末尾(当canRead返回false时)
  35.         // 或者到达下一个空格,它标志着下一个参数
  36.         while (reader.canRead() && reader.peek() != ' ') { // peek 提供了当前焦点处字符
  37.             reader.skip(); // 让StringReader移动到下一个位置
  38.         }

  39.         // 由这个参数的起始位置和下一个参数的起始位置获取这个命令的字串
  40.         String uuidString = reader.getString().substring(argBeginning, reader.getCursor());
  41.         try {
  42.             UUID uuid = UUID.fromString(uuidString); // 这是我们的实际逻辑
  43.             return uuid; // 我们在解析成功时返回类型并继续下一解析
  44.         } catch (Exception ex) {
  45.             // 当从字符串创建UUID时,可能会抛出异常,把它包装成CommandSyntaxException
  46.             // 带有上下问可以使Brigadier向用户提供失败位置的信息
  47.             // 一般的创建方法
  48.             throw new SimpleCommandExceptionType(new LiteralText(ex.getMessage())).createWithContext(reader);
  49.         }
  50.     }

  51.     @Override
  52.     public Collection<String> getExamples() { // Brigadier对于命令参数的例子提供了支持,这个集合只应该包含能解析的内容。这通常用于模棱两个的命令参数的相同之处。
  53.         return EXAMPLES;
  54.     }
  55. }
复制代码

评分

参与人数 1人气 +2 收起 理由
空条徐伦... + 2 为啥要开新楼层,不能编辑吗....

查看全部评分

回复

使用道具 举报

洞穴夜莺 当前离线
积分
11016
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-8-18
查看详细资料
 楼主| 发表于 2020-8-28 13:29:06 | 显示全部楼层
本帖最后由 洞穴夜莺 于 2021-1-29 23:31 编辑

挖掘等级

简介
原版的挖掘等级系统很烂,它硬编码可挖掘的方块而且只支持镐却不支持其他挖掘工具。

设置一个方块的挖掘等级
使用FabricBlockSettings中的breakByTool方法来设置方块的挖掘等级,需要一个由Fabric在FabricToolTags中提供的物品标签。
  1. settings.breakByTool(FabricToolTags.PICKAXES, 2)
复制代码

这里有一个挖掘等级的列表:
  1. 0 -> 木 / 金 镐
  2. 1 -> 石镐
  3. 2 -> 铁镐
  4. 3 -> 钻石镐
  5. 4 -> 下界合金镐
复制代码


处理方块材质问题
原版镐子对STONE, METAL, ANVIL有效。
原版斧头对WOOD, NETHER_WOOD, PLANT, REPLACEABLE_PLANT, BAMBOO, PUMPKIN有效。
工具能够在挖掘等级不够的情况下挖掘你这类材质的方块。
你必须创建你自己的材质拷贝来解决这个问题,讨论创建一个Material.STONE的拷贝的情况,看看Material.STONE的代码:
  1. new Material.Builder(MaterialColor.STONE).requiresTool().build()
复制代码

把Material.Builder换成FabricMaterialBuilder得:
  1. new FabricMaterialBuilder(MaterialColor.STONE).requiresTool().build()
复制代码


当使用错误的工具时不掉落(面向1.15.x!)

你需要在方块的材质中设置requiresTool,因此需要创建你的材质的拷贝。
讨论创建一个Material.WOOD的拷贝来使你的木制方块只在使用正确的工具挖掘时掉落的情况,看看Material.STONE的代码:[原文如此,有误]
  1. new Material.Builder(MaterialColor.WOOD).burnable().build()
复制代码

把Material.Builder换成FabricMaterialBuilder并加入requiresTool()得:
  1. new FabricMaterialBuilder(MaterialColor.WOOD).burnable().requiresTool().build()
复制代码


当使用错误的工具时不掉落(面向1.16.x!)

在方块的设置中设置requiresTool。

创建自定义工具

把你的工具加入Fabric工具标签来支持Mod方块。
向pickaxes标签添加一种镐的例子:
文件位置:/src/main/resources/data/fabric/tags/items/pickaxes.json
  1. {
  2.   "replace": false,
  3.   "values": [
  4.     "examplemod:example_pickaxe"
  5.   ]
  6. }
复制代码

回复

使用道具 举报

洞穴夜莺 当前离线
积分
11016
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-8-18
查看详细资料
 楼主| 发表于 2020-8-28 16:26:16 | 显示全部楼层
本帖最后由 洞穴夜莺 于 2020-8-28 19:54 编辑

命令建议
Brigadier允许指定自定义的参数建议,在Minecraft中,这些建议在玩家输入命令时被发送到客户端。(译注:bukkit的TabCompleter就是基于这个实现的)

建议提供器
SuggestionProvider用来创建一个发送给客户端的建议列表。一个建议提供器是一个接受CommandContext和一个SuggestionBuilder并返回一些Suggestion的函数式接口,因为可能建议没法马上确定,所以SuggestionProvider返回一个CompletableFuture。

建议可以是上下文有关的因为建议提供器允许你访问现在的命令上下文。

一个建议提供器的例子
例如你想要建议所有生物可能有的属性。
  1. class AttributeSuggestionProvider implements SuggestionProvider<ServerCommandSource> {
  2.     @Override
  3.     public CompletableFuture<Suggestions> getSuggestions(CommandContext<ServerCommandSource> context, SuggestionsBuilder builder) throws CommandSyntaxException {
  4.         Identifier entityTypeId = context.getArgument("type", Identifier.class);
  5.         ...
复制代码

因为我们拥有一个命令上下文,所以我们可以检查在此参数前的任何参数的值。

下面是一些样板代码
  1. class AttributeSuggestionProvider implements SuggestionProvider<ServerCommandSource> {
  2.     @Override
  3.     public CompletableFuture<Suggestions> getSuggestions(CommandContext<ServerCommandSource> context, SuggestionsBuilder builder) {
  4.         Identifier entityTypeId = context.getArgument("type", Identifier.class);
  5.         EntityType<?> entityType = Registry.ENTITY_TYPE.getOrEmpty(entityTypeId).orElse(null);

  6.         if (!DefaultAttributeContainer.hasDefinitionFor(entityType)) {
  7.             // TODO: Fail
  8.         }

  9.         DefaultAttributeContainer attributeContainer = DefaultAttributeRegistry.get(entityType);
  10.         // You will need mixin to get the 'instances map'. Lets assume we can just access it for the sake of the tutorial
  11.         for (EntityAttribute attribute : attributeContainer.instances().keySet()) {
  12.             Identifier attributeId = Registry.ATTRIBUTE.getId(attribute);
  13.             if (attributeId != null) {
  14.             ...
复制代码

为了在客户端显示建议,你要像构建器添加建议,可以用suggest方法实现。这些suggest方法一些处理数字和对小提示提供一些支持。
  1. for (EntityAttribute attribute : attributeContainer.instances().keySet()) {
  2.     Identifier attributeId = Registry.ATTRIBUTE.getId(attribute);
  3.     if (attributeId != null) {
  4.        builder.suggest(attributeId.toString());
  5.     }
  6. }
复制代码

你也可以结合用add(SuggestionBuilder)另一个建议构建者的结果。

最后,你要构建建议来返回,使用buildFuture方法完成。
  1. return builder.buildFuture();
复制代码

成品:
  1. class AttributeSuggestionProvider implements SuggestionProvider<ServerCommandSource> {
  2.     @Override
  3.     public CompletableFuture<Suggestions> getSuggestions(CommandContext<ServerCommandSource> context, SuggestionsBuilder builder) {
  4.         Identifier entityTypeId = context.getArgument("type", Identifier.class);
  5.         EntityType<?> entityType = Registry.ENTITY_TYPE.getOrEmpty(entityTypeId).orElse(null);

  6.         if (!DefaultAttributeContainer.hasDefinitionFor(entityType)) {
  7.             // TODO: Fail
  8.         }

  9.         DefaultAttributeContainer attributeContainer = DefaultAttributeRegistry.get(entityType);
  10.         // You will need mixin to get the 'instances map'. Lets assume we can just access it for the sake of the tutorial
  11.         for (EntityAttribute attribute : attributeContainer.instances().keySet()) {
  12.             Identifier attributeId = Registry.ATTRIBUTE.getId(attribute);
  13.             if (attributeId != null) {
  14.                 builder.suggest(attributeId.toString());
  15.             }
  16.         }

  17.         return builder.buildFuture();
  18.     }
  19. }
复制代码


使用建议提供器
现在你有了一个建议提供器,是时候使用它了。注意建议提供器不能对字面量节点使用。

当注册一个参数时,你可以用suggests(SuggestionProvider)设置建议提供器,像下面这样在RequiredArgumentBuilder上应用。
  1. argument(argumentName, word())
  2.     .suggests(CompletionProviders.suggestedStrings())
  3.         .then(/*Rest of the command*/));
复制代码


内置建议提供器
Minecraft包含一些内置的建议提供器,如下:
类型字段/方法
可生成的实体SuggestionProviders.SUMMONABLE_ENTITIES
可用的声音SuggestionProviders.AVAILABLE_SOUNDS
战利品表LootCommand.SUGGESTION_PROVIDER
生物群系SuggestionProviders.ALL_BIOMES


CommandSource中的工具
CommandSource提供了少量帮助移除创建建议的样板代码的工具。很多工具方法包括返回从ID,位置或匹配的字符串的Stream或者Set获取完整的建议。

其他命令指南
点击这里(译注:已译,见2楼)查看其他命令指南。
回复

使用道具 举报

洞穴夜莺 当前离线
积分
11016
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-8-18
查看详细资料
 楼主| 发表于 2020-9-5 12:44:09 | 显示全部楼层
本帖最后由 洞穴夜莺 于 2021-3-27 17:58 编辑

访问扩展器
访问扩展器提供一种放宽类,方法或字段的访问限制的方式。访问扩展器比较类似于隔壁Forge的AccessTransformer。

访问扩展器只应该在mixin没有提供类似方法去做时使用。
一般只有2种mixin不够用的情况:
        ・需要访问一个(包)私有类,特别是用于@Shadow或者在mixin中访问字段或方法。
        ・需要重写终极方法或者继承终极类。
                 ・在你考虑重写终极方法前先试试用mixin注入终极函数。
                 ・如果你想要继承一个只有私有构造方法的类,扩展器是一个好选择。

依赖
        ・Fabric-loader >= 0.8.0
        ・Loom >= 0.2.7

文件格式
一种特殊的文件格式被用来定义你Mod的访问控制改变,最好用.accesswidener后缀来帮助IDE识别。

这个文件必须以下面的内容开始,namespace应该和你的Mod使用的映射表相匹配。通常是named,开发环境生成Mod时会将你的访问扩展器文件映射到intemediary表。如果你使用一个自定义的RemapJarTask,设置remapAccessWidener选项为true来确保这些发生。
  1. accessWidener        v1        <namespace>
复制代码

访问扩展器可以有空行和以#开头的注释
  1. # 这样的注释是可以的,放在行尾也一样
复制代码

任何空白字符都能在文件中用于分隔符,推荐用tab。

类名用 /分割而不是.

对于内部类,使用$而不是/


类访问限制可以通过指定访问限制和namespace中指定的映射的类名。
  1. <access>   class   <className>
复制代码

        1. access可以是accessibale或extendable

方法
方法访问限制可以通过指定访问限制,namespace中指定的映射的类名,方法名,方法描述符来改变。
  1. <access>   method   <className>   <methodName>   <methodDesc>
复制代码

         1.access可以是 accessible或extendable
         2.className是方法所属的类
         3.methodName是方法名
         4.methodDesc是方法描述符


字段
方法访问限制可以通过指定访问限制,namespace中指定的映射的类名,方法名,方法描述符来改变。
  1. <access>   field   <className>   <fieldName>   <fieldDesc>
复制代码

         1.access可以是 accessible或mutable
         2.className是字段所属的类
         3.fieldName是字段名
         4.fieldDesc是字段描述符

访问限制改变
Extendable
在你想要继承一个类或重写一个方法时使用Extendable。
        ・类会变成公开的并且终极关键字会被移除。
        ・方**变成至少是受保护的并且终极关键字被移除。
对方法使用extendable同时也会对类使用extendable。

Accessible
当你想要从另一个类访问一个类、字段或方法时使用Accessibale
        ・类会变成公开的
        ・方**变成公开的,如果这个方法是私有的,还会加上终极关键字
        ・字段会变成公开的
对方法或字段使用accessibale同时也会对类使用accessible。

Mutable
当你想要改变一个终极字段时使用Mutable。
        ・字段的终极关键字会被移除

指定文件位置
访问扩展器需要在你的build.gradle和fabric.mod.json中指定。它需要在导出的jar文件中包含所以需要保存在resources里。
  1. minecraft {
  2.     accessWidener = file("src/main/resources/modid.accesswidener")
  3. }
复制代码
  1. ...

  2. "accessWidener" : "modid.accesswidener",

  3. ...
复制代码

回复

使用道具 举报

洞穴夜莺 当前离线
积分
11016
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-8-18
查看详细资料
 楼主| 发表于 2020-9-18 22:40:58 | 显示全部楼层
注册表系统
你需要在你的游戏中出测大多数内容,这会对以下内容有帮助:
        让游戏知道你的内容存在
        在服务器和客户端之间验证游戏内容有效性
        在存档中处理无效数据
        抑制不同模组冲突
        双端通信的压缩和数据保存
        抽象和隐藏数字ID
当注册任意类型的内容时,你传递一个Identifier参数,它是你添加内容的参数。Identifier(缩写成ID)有一个命名空间和一个路径,在大多数情况下,这个命名空间是你的模组ID,然后路径是要注册内容的明智,例如,标准泥土方块的ID是minecraft:dirt。

使用没有注册的自定义内容可能会导致缺陷行为,例如材质丢失,世界保存问题,还有崩溃。游戏通常会在你忘记注册东西时让你知道。

注册表类型
当你注册内容时,你需要指定你要注册到的注册表,游戏提供的最基础的注册表能在Registry类中找到,两个注册表的例子是用于物品的Registry.ITEM和用于方块的Registry.BLOCK。


阅读注册表类型来获取深入的概述。


注册内容

使用Registry.register向注册表添加内容
  1. public static <T> T register(Registry<? super T> registry, Identifier id, T entry) {    return ((MutableRegistry)registry).add(id, entry);}
复制代码

registry - 一个要添加内容的注册表实例。注册表类型Registry中的原版注册表清单。

entry - 一个你要注册的内容的实例。

注册表方法
get - 返回ID在注册表中对应的内容实例。如果不存在,DefaultedRegistry返回默认内容,SimpleRegistry返回空。
  1. @Nullablepublic abstract T get(@Nullable Identifier id);
复制代码

getId - 返回注册表中内容实例对应的ID,如果不存在,DefaultedRegistry返回默认ID,SimpleRegistry返回空。
  1. @Nullablepublic abstract Identifier getId(T entry);
复制代码

getRawId - 返回注册表中内容实例对应的内部数字ID,如果不存在,DefaultedRegistry返回默认内容对应的内部数字ID,SimpleRegistry返回-1。
  1. public abstract int getRawId(@Nullable T entry);
复制代码



回复

使用道具 举报

洞穴夜莺 当前离线
积分
11016
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-8-18
查看详细资料
 楼主| 发表于 2020-9-26 13:17:35 | 显示全部楼层
本帖最后由 洞穴夜莺 于 2021-5-15 13:01 编辑

自定义热键
热键:直接从键盘上
Minecraft用热键处理多数外围设备的用户输入,例如鼠标键盘。当你按下W时向前移动,当你按下E键时打开背包。所有热键可以在控制选项中设置,所以你也可以用方向键代替WASD控制行动。

这个指南假设你有热键API,如果没有就加一个"fabric-key-binding-api-v1":"*"到你的fabric.mods.json的"depends"中去。

加入一个热键很容易,你需要:

  • 创建一个KeyBinding对象
  • 对按键按下作出反应


这里有一个新的示例。

创建你的热键
在随便什么地方声明一个这样的玩意。
  1. private static KeyBinding keyBinding;
复制代码

FabricKeyBinding有一个Builder用于初始化,它需要一个Identifier,InputUtil.Type,按键编码,和一个分组。
  1. keyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
  2.         "key.examplemod.spook", // 热键名的翻译键
  3.         InputUtil.Type.KEYSYM, // 热键种类,键盘用KEYSYM,鼠标用MOUSE
  4.         GLFW.GLFW_KEY_R, // 默认使用的按键的按键编码
  5.         "category.examplemod.test" // 按键分组的翻译键
  6. ));
复制代码

如果你想要一个粘性键,最后加一个()->true参数。

GLFW.GLFW_KEY_R可以被任何你想要使用的默认按键。分组会体现在你的设置页面上。

回应你的按键
这段代码会在游戏中输出“Key 1 was pressed!”。(译者注:注意不是isPressed,isPressed返回当前按键是否被按下,wasPressed返回此按键还有是否按下事件未被处理并移除一个未被处理的事件)
  1. ClientTickEvents.END_CLIENT_TICK.register(client -> {
  2.         while (keyBinding.wasPressed()) {
  3.                 client.player.sendMessage(new LiteralText("Key 1 was pressed!"), false);
  4.         }
  5. });
复制代码

注意这整个过程发生在客户端,如果你要服务端对热键作出反应,你需要使用自定义网络包并且单独在服务端做处理。
回复

使用道具 举报

洞穴夜莺 当前离线
积分
11016
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-8-18
查看详细资料
 楼主| 发表于 2020-10-2 22:51:56 | 显示全部楼层
有方向的方块
使方块有方向(朝向确定的方向)也是通过方块状态完成的,这个例子描述了一个竖半砖的例子。

  1. public class PolishedAndesiteSideBlock extends HorizontalFacingBlock {

  2.         public PolishedAndesiteSideBlock(Settings settings) {
  3.                 super(settings);
  4.                 setDefaultState(this.stateManager.getDefaultState().with(Properties.HORIZONTAL_FACING, Direction.NORTH));
  5.         }

  6.         @Override
  7.         protected void appendProperties(StateManager.Builder<Block, BlockState> stateManager) {
  8.                 stateManager.add(Properties.HORIZONTAL_FACING);
  9.         }

  10.         @Override
  11.         public VoxelShape getOutlineShape(BlockState state, BlockView view, BlockPos pos, EntityContext ctx) {
  12.                 Direction dir = state.get(FACING);
  13.                 switch(dir) {
  14.                         case NORTH:
  15.                                 return VoxelShapes.cuboid(0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f);
  16.                         case SOUTH:
  17.                                 return VoxelShapes.cuboid(0.0f, 0.0f, 0.5f, 1.0f, 1.0f, 1.0f);
  18.                         case EAST:
  19.                                 return VoxelShapes.cuboid(0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f);
  20.                         case WEST:
  21.                                 return VoxelShapes.cuboid(0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 1.0f);
  22.                         default:
  23.                                 return VoxelShapes.fullCube();
  24.                 }
  25.         }

  26.         public BlockState getPlacementState(ItemPlacementContext ctx) {
  27.                 return (BlockState)this.getDefaultState().with(FACING, ctx.getPlayerFacing());
  28.         }

  29. }
复制代码

定义方块状态
polished_andesite_side_block.json
  1. {
  2.   "variants": {
  3.     "facing=north": { "model": "bitmod:block/polished_andesite_side_block" },
  4.     "facing=east":  { "model": "bitmod:block/polished_andesite_side_block", "y":  90},
  5.     "facing=south": { "model": "bitmod:block/polished_andesite_side_block", "y": 180 },
  6.     "facing=west":  { "model": "bitmod:block/polished_andesite_side_block", "y": 270 }
  7.   }
  8. }
复制代码

定义方块模型
side.json
  1. {   
  2.     "parent": "block/block",
  3.     "textures": {
  4.         "particle": "#side"
  5.     },
  6.     "elements": [
  7.         {   "from": [ 0, 0, 0 ],
  8.             "to": [  16, 16, 8 ],
  9.             "faces": {
  10.                 "down":  { "uv": [ 0, 8, 16, 16 ], "texture": "#bottom", "cullface": "down" },
  11.                 "up":    { "uv": [ 0, 8, 16, 16 ], "texture": "#top",    "cullface": "up" },
  12.                 "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#side",   "cullface": "north" },
  13.                 "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#side"  },
  14.                 "west":  { "texture": "#side",   "cullface": "west" },
  15.                 "east":  { "texture": "#side",   "cullface": "east" }
  16.             }
  17.         }
  18.     ]
  19. }
复制代码

polished_andesite_side_block.json
  1. {
  2.     "parent": "bitmod:block/side",
  3.     "textures": {
  4.         "bottom": "block/polished_andesite",
  5.         "top": "block/polished_andesite",
  6.         "side": "block/polished_andesite"
  7.     }
  8. }
复制代码
回复

使用道具 举报

洞穴夜莺 当前离线
积分
11016
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-8-18
查看详细资料
 楼主| 发表于 2020-10-2 23:51:11 | 显示全部楼层
本帖最后由 洞穴夜莺 于 2020-10-7 09:43 编辑

物品
物品是一种出现在物品栏的游戏内容。它们可以在你点击时执行动作,作为食物,或者生成实体。下面的文档会给你一些关于整个Item类的解释和所有有关的信息。你接下来可以阅读创建一个物品(本链接指向蝙蝠译文)
物品设置
Item的构造函数需要一个Item.Settings的实例,这个构造器类定义了一些像物品堆大小,耐久和是否可食用。下面有一个完整的显示所有可用方法的表:[td][table]
方法
参数
描述

food
FoodComponent 基于给出的FoodComponent将食物变得可食用

maxCount
int 设置物品的最大堆大小,不能和耐久一起用

maxDamageIfAbsent  int 如果该物品没有设置耐久,则设置该物品的耐久

maxDamage int 设置物品耐久

recipeRemainder  Item 设置当玩家在使用该物品合成其他物品时,返还何种物品

group  ItemGroup 设置物品的物品组,在创造模式物品栏中体现

rarity  Rarity 设置物品的稀有度,在物品名字的颜色中体现


食物
  1. public Item.Settings food(FoodComponent foodComponent)
复制代码

foodComponent - FoodComponent的实例。当设置时物品会根据构造器提供的FoodComponent的设定变得可食用。深入解释请阅读FoodComponent页

最大物品堆叠数
  1. public Item.Settings maxCount(int maxCount)
复制代码

maxCount - 物品的最大物品堆叠属性。如果maxDamage()已经被调用将抛出RuntimeException,因为物品不可以又有耐久又能堆叠。建议将这个值设置在64及以下,因为更大的值可能带来不确定的问题。

最大耐久如果没有
  1. public Item.Settings maxDamageIfAbsent(int maxDamage)
复制代码

如果maxDamage()没被调用耐久会被设成传递进去的参数。这在一些像工具和护甲之类的场合使用,这里物品的耐久仅在没有被设置时设为工具材料的耐久。

最大耐久
  1. public Item.Settings maxDamage(int maxDamage)
复制代码

maxDamage - 物品的最大耐久

配方返还物品
  1. public Item.Settings recipeRemainder(Item recipeRemainder)
复制代码

recipeRemainder - 当物品被用作配方的原材料时可以返还的物品。

当给一个物品设置一个配方返还物品时,所有使用它们的配方均会在合成时返还物品,这个设置一般用于桶(水,熔岩,牛奶)和瓶子(龙息、蜂蜜)在用于合成时返回它们对应的空物品。

物品组
  1. public Item.Settings group(ItemGroup group)
复制代码

group - 欲将物品添加到的ItemGroup。

每个ItemGroup以创造模式标签栏的形式出现。向一个物品组添加物品会把它加到标签栏里,排序是注册表序。更多信息参阅物品组页

稀有度
  1. public Item.Settings rarity(Rarity rarity)
复制代码

rarity - 物品的稀有度。
如果设置了稀有度,物品会有一个自定义的名字颜色,物品的默认稀有度是common。
稀有度 颜色
Common
Uncommon
Rare
Epic 品红


↓已修复

评分

参与人数 1金粒 +5 收起 理由
squid233 + 5 排版炸了

查看全部评分

回复

使用道具 举报

洞穴夜莺 当前离线
积分
11016
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-8-18
查看详细资料
 楼主| 发表于 2020-10-17 20:25:18 | 显示全部楼层
本帖最后由 洞穴夜莺 于 2021-1-24 10:42 编辑

颜色提供器
想知道树叶和草怎么根据生物群系改变颜色,或者皮甲怎么会有如此多种的颜色?
看看颜色提供器,它根据位置、NBT、方块状态等进行上色。
现有的例子
先来看看哪些原版内容使用颜色提供器。
  • 树叶
  • 染色皮甲
  • 红石线
  • 西瓜、甘蔗、荷叶等植物
  • 药箭

颜色提供器很强大,但是Mojang坚持对混凝土、羊毛、玻璃等有色方块使用独立贴图。主要用在对生物群系的颜色调整和对纹理的小调整。例如药箭的末端。
颜色提供器的主要概念很简单,你向它们注册一个物品或者方块,当它们的模型被渲染时,颜色提供器给各层进行颜色调整。
颜色提供器允许你访问各层的纹理,这意味着你可以对纹理的各部分独立进行修改,像染色皮甲和药箭那样。这个特征在你想改变几个像素而不是整个纹理是很好用。
记住这玩意是仅客户端的,所以所有内容都需要只在客户端上注册。

注册一个方块颜色提供器

你需要用ColorProviderRegistry来注册一个方块颜色提供器。这个类有一个BLOCK和ITEM的提供器实例可以用来注册。这个注册方法需要一个颜色提供器实例,以及每个要着色的方块的变参。
  1. ColorProviderRegistry.BLOCK.register((state, view, pos, tintIndex) -> 0x3495eb, MY_BLOCK);
复制代码

我们这里在说, “嗨,MY_BLOCK应该是0x3495eb色的”,即蓝色。 你有一个BlockState,World和BlockPos做上下文,你可以基于群系或者位置改变颜色。最后的整数是要上的颜色的tintindex。每个方块都会问一遍,但是在这个例子里我们始终回答蓝色。

模型也是很重要的:主要是你需要给所有要上色的部分定义一个tintindex,来看看leaves.json是个简单的例子,它是原版树叶使用的基础模型。下面是一个我们的方块的模型。
  1. {
  2.   "parent": "block/block",
  3.   "textures": {
  4.     "all": "block/white_concrete",
  5.     "particle": "#all"
  6.   },
  7.   "elements": [
  8.     {   "from": [ 0, 0, 0 ],
  9.       "to": [ 16, 16, 16 ],
  10.       "faces": {
  11.         "down":  { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "tintindex": 0, "cullface": "down" },
  12.         "up":    { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "tintindex": 0, "cullface": "up" },
  13.         "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "tintindex": 0, "cullface": "north" },
  14.         "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "tintindex": 0, "cullface": "south" },
  15.         "west":  { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "tintindex": 0, "cullface": "west" },
  16.         "east":  { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "tintindex": 0, "cullface": "east" }
  17.       }
  18.     }
  19.   ]
  20. }
复制代码


在这个实例中,我们添加单个tintindex,它会在上文所述的tintIndex参数中出现(tint下标0)。

这是最终结果-注意原版模型使用white_concrete材质:
(夜莺觉得这里好像缺点东西,但是原文确实没了)
[size]注册一个物品颜色提供器
物品很类似,只是提供的上下文不同。你能访问ItemStack而不是方块状态,世界或位置。
  1. ColorProviderRegistry.ITEM.register((stack, tintIndex) -> 0x3495eb, COLORED_ITEM);
复制代码

这个会像方块那样给你的物品染色。
限制
主要问题是物品提供的上下文太少,这是为啥原版草不根据你站立的位置改变物品栏里草的颜色。如果要实现多种颜色的方块(例如混凝土、玻璃、羊毛),建议你简单粗暴给每个颜色一张独立的贴图。
回复

使用道具 举报

洞穴夜莺 当前离线
积分
11016
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-8-18
查看详细资料
 楼主| 发表于 2020-10-25 10:28:56 | 显示全部楼层
本帖最后由 洞穴夜莺 于 2020-10-31 08:49 编辑

工作台配方
添加一个基础的配方
确保你在看本帖之前添加了一个物品,我们要用到它。

目前,我们的物品可以通过创造模式或者命令获取,我们要给它加一个配方来让生存玩家获得它。这里我们会添加一个工作台配额放。

在resources/data/tutorial/recipes/文件夹下创建一个 fabric_item.json文件(把turtorial改成你的mod id)这是一个 fabric_item的例子
resources/data/tutorial/recipes/fabric_item.json
  1. {
  2.   "type": "minecraft:crafting_shaped",
  3.   "pattern": [
  4.     "WWW",
  5.     "WR ",
  6.     "WWW"
  7.   ],
  8.   "key": {
  9.     "W": {
  10.       "tag": "minecraft:logs"
  11.     },
  12.     "R": {
  13.      "item": "minecraft:redstone"
  14.     }
  15.   },
  16.   "result": {
  17.    "item": "tutorial:fabric_item",
  18.     "count": 4
  19.   }
  20. }
复制代码

详细解释:
    type: 这是一个有序配方。
    result: 这是一个能合成4个tutorial:fabric_item的配方。count字段是可选的,如果你不写,它就默认是1。
    pattern: 一个代表你工作台配方的图案,每个字母代表一个物品,一个空格代表不需要在该位置放置物品,每个字母所代表的物品在key中定义。
    key: 每个字母所嗲表的物品是什么。W代表任何带minecraft:logs标签的物品(所有原木)。R特指红石,详细信息看这里(指向bilibili mcwiki)
总之你的物品看起来像这样
                                                        
四个fabric_item的配方
木板
木板
木板
木板
红石
空气
木板
木板
木板

简单配方的详细信息看这里(指向bilibili mcwiki)
添加自定义配方
这个type字段可以修改来支持一个自定义的配方[需要补充](夜莺提示:阅读RecipeSerializer和CraftingRecipe这两个类)
回复

使用道具 举报

洞穴夜莺 当前离线
积分
11016
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-8-18
查看详细资料
 楼主| 发表于 2020-11-8 10:07:51 | 显示全部楼层
维度概念
创建一个维度是一个需要一点功夫才能理解的高级概念,很容易就可以创建一个简单的维度,但是深入之后你会被成吨的难以掌握的新类所轰炸。这个指南是对创建维度的主要概念的概述,这里分类和类名密切相关。

维度(Dimension)
这个Dimension类是你的新维度的核心。它处理你维度环境的重要逻辑。玩家可以在那里用床睡觉吗?有世界边界吗?玩家能看见天空吗?它也用来创建你的ChunkGenerator来创建地图。

维度类型(DimensionType)
DimensionType是一个关于你的维度的注册表包装。它有一个用来注册注册表的ID。它也用来从文件加载维度或者保存文件到维度。

区块生成器(ChunkGenerator)
用来根据噪声函数放置方块,它一般不用来生成装饰性和选择具体的方块,大多数情况下你只根据你的选择放置石头和其他基础方块。如果我们此后没有生成装饰以及额外步骤的话,理论上就是完全由石头组成。

生成器类型(GeneratorType)
又是一样的,这是一个ChunkGenerator的注册表封装。但是它不能被正常注册,需要使用一些不合理手段,正在修复此问题。

生物群系(Biome)
生物群系是确定一片区域的样子的一个选择。它用来生成生物、植物、河流湖泊、洞穴、决定草的颜色和别的很多东西。在恰当的设置中,它也用来修改已经生成的石头为其他方块,例如草/泥土和矿石。

生物群系源(BiomeSource)
当创建一个维度时,你可以对所有地方使用同种生物群系或者使用一个生物群系源,生物群系源用来从几种生物群系中随机挑选。

表面构建器(SurfaceBuilder)
一个表面构建器用来改变上面提到的一些石头,每个生物群系都有一个对应的表面构建器。例如,平原&森林都使用DEFAULT SurfaceBuilder,因为它们都想要草方块和泥土在它们上面,它们仅仅因为它们上年的树和地形平坦程度不同----也就是说,一个SurfaceBuilder可以根据情况复用。
回复

使用道具 举报

洞穴夜莺 当前离线
积分
11016
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-8-18
查看详细资料
 楼主| 发表于 2020-11-8 15:10:44 | 显示全部楼层
本帖最后由 洞穴夜莺 于 2020-11-28 18:50 编辑

生成矿石[1.16.3]
许多模组都有它们自己的矿石, 你需要一种方式把它们添加到存在的生物群系中使得玩家可以找到。在这篇指南中,我们会看看怎么像已存在的生物群系中添加矿石,这里有两个添加矿石所必须的步骤。
  • 创建一个ConfiguredFeatures,它定义你的矿石方块在哪里生成。
  • 生物群系修改API来向你的结构添加地物(feature,即装饰性方块生成)。


注意这个API是实验特性,如果他不能用,考虑用Mixin版

我们假定你已经创建了你的矿石方块,下面将用羊毛作为演示,自己把羊毛改成你的矿石。

向主世界添加
在本章节,我们的目标是在主世界生成我们的矿石。
我们需要创建一个ConfiguredFeature,确保在onInitialize中注册你的ConfiguredFeature,自己根据需要改变这些参数来适合你的Mod。
  1. public class ExampleMod implements ModInitializer {
  2.   private static ConfiguredFeature<?, ?> ORE_WOOL_OVERWORLD = Feature.ORE
  3.     .configure(new OreFeatureConfig(
  4.       OreFeatureConfig.Rules.BASE_STONE_OVERWORLD,
  5.       Blocks.WHITE_WOOL.getDefaultState(),
  6.       9)) // 矿脉大小
  7.     .decorate(Decorator.RANGE.configure(new RangeDecoratorConfig(
  8.       0, // 底部偏移量
  9.       0, // 最小生成高度
  10.       64))) // 最大生成高度
  11.     .spreadHorizontally()
  12.     .repeat(20); // 每区块矿脉数

  13.   @Override
  14.   public void onInitialize() {
  15.     RegistryKey<ConfiguredFeature<?, ?>> oreWoolOverworld = RegistryKey.of(Registry.CONFIGURED_FEATURE_WORLDGEN,
  16.         new Identifier("tutorial", "ore_wool_overworld"));
  17.     Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, oreWoolOverworld.getValue(), ORE_WOOL_OVERWORLD);
  18.     BiomeModifications.addFeature(BiomeSelectors.foundInOverworld(), GenerationStep.Feature.UNDERGROUND_ORES, oreWoolOverworld);
  19.   }
  20. }
复制代码

结果
你可以看到矿石已经在主世界生成了,用这个命令移除周围的石头。
  1. /fill ~-8 0 ~-8 ~8 ~ ~8 minecraft:air replace minecraft:stone
复制代码



向下界添加
在本章节,在上面那段代码的基础上,我们给下界添加矿石。
我们把OreFeatureConfig.Rules.BASE_STONE_OVERWORLD 改成OreFeatureConfig.Rules.BASE_STONE_NETHER因为下界生物群系的基础方块和主世界是不同的。
  1. public class ExampleMod implements ModInitializer {
  2.   private static ConfiguredFeature<?, ?> ORE_WOOL_NETHER = Feature.ORE
  3.     .configure(new OreFeatureConfig(
  4.       OreFeatureConfig.Rules.BASE_STONE_NETHER, // We use OreFeatureConfig.Rules.BASE_STONE_NETHER for nether
  5.       Blocks.WHITE_WOOL.getDefaultState(),
  6.       9))
  7.     .decorate(Decorator.RANGE.configure(new RangeDecoratorConfig(
  8.       0,
  9.       0,
  10.       64)))
  11.     .spreadHorizontally()
  12.     .repeat(20);

  13.   @Override
  14.   public void onInitialize() {
  15.     RegistryKey<ConfiguredFeature<?, ?>> oreWoolNether = RegistryKey.of(Registry.CONFIGURED_FEATURE_WORLDGEN,
  16.         new Identifier("tutorial", "ore_wool_nether"));
  17.     Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, oreWoolNether.getValue(), ORE_WOOL_NETHER);
  18.     BiomeModifications.addFeature(BiomeSelectors.foundInTheNether(), GenerationStep.Feature.UNDERGROUND_ORES, oreWoolNether);
  19.   }
  20. }
复制代码


向末地添加
在本章节,在主世界那段代码的基础上,我们给末地添加矿石。
我们把OreFeatureConfig.Rules.BASE_STONE_OVERWORLD 改成 new BlockMatchRuleTest(Blocks.END_STONE)因为末地生物群系的基础方块是末地石。
  1. public class ExampleMod implements ModInitializer {
  2.   private static ConfiguredFeature<?, ?> ORE_WOOL_END = Feature.ORE
  3.     .configure(new OreFeatureConfig(
  4.       new BlockMatchRuleTest(Blocks.END_STONE), // base block is endstone in the end biomes
  5.       Blocks.WHITE_WOOL.getDefaultState(),
  6.       9))
  7.     .decorate(Decorator.RANGE.configure(new RangeDecoratorConfig(
  8.       0,
  9.       0,
  10.       64)))
  11.     .spreadHorizontally()
  12.     .repeat(20);

  13.   @Override
  14.   public void onInitialize() {
  15.     RegistryKey<ConfiguredFeature<?, ?>> oreWoolEnd = RegistryKey.of(Registry.CONFIGURED_FEATURE_WORLDGEN,
  16.         new Identifier("tutorial", "ore_wool_end"));
  17.     Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, oreWoolEnd.getValue(), ORE_WOOL_END);
  18.     BiomeModifications.addFeature(BiomeSelectors.foundInTheEnd(), GenerationStep.Feature.UNDERGROUND_ORES, oreWoolEnd);
  19.   }
  20. }
复制代码
回复

使用道具 举报

洞穴夜莺 当前离线
积分
11016
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-8-18
查看详细资料
 楼主| 发表于 2020-11-22 15:19:33 | 显示全部楼层
本帖最后由 洞穴夜莺 于 2021-5-15 13:10 编辑

生成地物[1.16.3]
石头,树,矿石和池塘都是地物的例子,它们简单地在世界上根据配置进行额外生成。在这个指南里,我们会尝试随机地生成螺旋状的石头地物。

有上那个添加地物到生物群系的步骤


注意这个API是实验特性,如果不能用,考虑用Mixin版

创建一个地物
一个简单的地物像这样:
  1. public class StoneSpiralFeature extends Feature<DefaultFeatureConfig> {
  2.   public StoneSpiralFeature(Codec<DefaultFeatureConfig> config) {
  3.     super(config);
  4.   }

  5.   @Override
  6.   public boolean generate(StructureWorldAccess world, ChunkGenerator generator, Random random, BlockPos pos,
  7.       DefaultFeatureConfig config) {
  8.     BlockPos topPos = world.getTopPosition(Heightmap.Type.WORLD_SURFACE, pos);
  9.     Direction offset = Direction.NORTH;

  10.     for (int y = 1; y <= 15; y++) {
  11.       offset = offset.rotateYClockwise();
  12.       world.setBlockState(topPos.up(y).offset(offset), Blocks.STONE.getDefaultState(), 3);
  13.     }

  14.     return true;
  15.   }
  16. }
复制代码

这个Feature<DefaultFeatureConfig>构造函数使用一个Codec<DefaultFeatureConfig>,你可以传递DefaultFeatureConfig.CODEC来使用默认配置,或者声明时直接调用父类方法。

当区块决定生成地物的时候会调用generate。如果地物被配置成在每个区块生成,它就会在每个区块上调用一遍。这里要给地物一个合适的在每个生物群系的生成率。generate只在你想要生成的地方被调用。

在我们这个实现中,我们会构建一个15格高螺旋状石头在最顶上的方块上。

地物可以像其他东西一样通过注册表注册
  1. public class ExampleMod implements ModInitializer {
  2.   private static final Feature<DefaultFeatureConfig> STONE_SPIRAL = new StoneSpiralFeature(DefaultFeatureConfig.CODEC);

  3.   @Override
  4.   public void onInitialize() {
  5.     Registry.register(Registry.FEATURE, new Identifier("tutorial", "stone_spiral"), STONE_SPIRAL);
  6.   }
  7. }
复制代码


配置一个地物
我们要配置一个地物。确保它的配置也被注册。
  1. public class ExampleMod implements ModInitializer {
  2.   public static final ConfiguredFeature<?, ?> STONE_SPIRAL_CONFIGURED = STONE_SPIRAL.configure(FeatureConfig.DEFAULT)
  3.       .decorate(Decorator.CHANCE.configure(new ChanceDecoratorConfig(5)));

  4.   @Override
  5.   public void onInitialize() {
  6.     [...]

  7.     RegistryKey<ConfiguredFeature<?, ?>> stoneSpiral = RegistryKey.of(Registry.CONFIGURED_FEATURE_WORLDGEN,
  8.         new Identifier("tutorial", "stone_spiral"));
  9.     Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, stoneSpiral.getValue(), STONE_SPIRAL_CONFIGURED);
  10.   }
  11. }
复制代码

这个装饰器代表世界怎么选择你的地物的位置。查看类似生成风格的原版地物来确定你自己要填啥。装饰器将配置此分支,在CHANCE的情况下,你可以传入一个ChanceDecoratorConfig。

添加一个配置好的地物到生物群系
我们用生物群系API
  1. public class ExampleMod implements ModInitializer {
  2.   [...]

  3.   @Override
  4.   public void onInitialize() {
  5.     [...]
  6.     BiomeModifications.addFeature(BiomeSelectors.all(), GenerationStep.Feature.UNDERGROUND_ORES, stoneSpiral);
  7.   }
  8. }
复制代码

addFeature的第一个参数决定地物生成的生物群系。

第二个参数决定地物何时生成,对于地面上的房屋可以用SURFACE_STRUCTURES,对于洞穴,可以用RAW_GENERATION。

结果
[原文图挂]
回复

使用道具 举报

洞穴夜莺 当前离线
积分
11016
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-8-18
查看详细资料
 楼主| 发表于 2020-11-28 18:48:59 | 显示全部楼层

添加生物群系[1.16.3]
向世界添加一个生物群系需要3个步骤
  • 创建一个生物群系
  • 注册一个生物群系
  • 向一个气候区添加生物群系


这个指南中我们会添加一个叫黑曜石大陆的生物群系,它的表面被黑曜石覆盖。

注意这个指南基于被标记为实验性特性的Farbic API中的生物群系API,如果它不能用。考虑用mixin版

创建一个生物群系
我们用Biome.Builder配置选项来创建生物群系。少写一个选项很可能会让游戏崩溃。建议看一下DefaultBiomeCreator中的原版生物群系作为例子。
  1. public class ExampleMod implements ModInitializer {
  2.   // SurfaceBuilder定义你的生物群系看起来如何
  3.   // 我们用自定义的表面构建器来使我们的生物群系被黑曜石覆盖
  4.   private static final ConfiguredSurfaceBuilder<TernarySurfaceConfig> OBSIDIAN_SURFACE_BUILDER = SurfaceBuilder.DEFAULT
  5.     .withConfig(new TernarySurfaceConfig(
  6.       Blocks.OBSIDIAN.getDefaultState(),
  7.       Blocks.DIRT.getDefaultState(),
  8.       Blocks.GRAVEL.getDefaultState()));

  9.   private static final Biome OBSILAND = createObsiland();

  10.   private static Biome createObsiland() {
  11.     // 我们需要定义什么样的生物和地物在这个生物群系生成
  12.     // 比如结构,数目,石头,植物和自定义实体,这些步骤对于不同群系来说大致相同
  13.     // 原版生物群系在DefaultBiomeFeatures中定义这些

  14.     SpawnSettings.Builder spawnSettings = new SpawnSettings.Builder();
  15.     DefaultBiomeFeatures.addFarmAnimals(spawnSettings);
  16.     DefaultBiomeFeatures.addMonsters(spawnSettings, 95, 5, 100);

  17.     GenerationSettings.Builder generationSettings = new GenerationSettings.Builder();
  18.     generationSettings.surfaceBuilder(OBSIDIAN_SURFACE_BUILDER);
  19.     DefaultBiomeFeatures.addDefaultUndergroundStructures(generationSettings);
  20.     DefaultBiomeFeatures.addLandCarvers(generationSettings);
  21.     DefaultBiomeFeatures.addDefaultLakes(generationSettings);
  22.     DefaultBiomeFeatures.addDungeons(generationSettings);
  23.     DefaultBiomeFeatures.addMineables(generationSettings);
  24.     DefaultBiomeFeatures.addDefaultOres(generationSettings);
  25.     DefaultBiomeFeatures.addDefaultDisks(generationSettings);
  26.     DefaultBiomeFeatures.addSprings(generationSettings);
  27.     DefaultBiomeFeatures.addFrozenTopLayer(generationSettings);

  28.     return (new Biome.Builder())
  29.       .precipitation(Biome.Precipitation.RAIN)
  30.       .category(Biome.Category.NONE)
  31.       .depth(0.125F)
  32.       .scale(0.05F)
  33.       .temperature(0.8F)
  34.       .downfall(0.4F)
  35.       .effects((new BiomeEffects.Builder())
  36.         .waterColor(0x3f76e4)
  37.         .waterFogColor(0x050533)
  38.         .fogColor(0xc0d8ff)
  39.         .skyColor(0x77adff)
  40.         .build())
  41.       .spawnSettings(spawnSettings.build())
  42.       .generationSettings(generationSettings.build())
  43.       .build();
  44.   }
  45. }
复制代码

注册生物群系
我们在onInitialize中注册生物群系,如果你用自己的表面构造器,也要注册。
  1. public class ExampleMod implements ModInitializer {
  2.   public static final RegistryKey<Biome> OBSILAND_KEY = RegistryKey.of(Registry.BIOME_KEY, new Identifier("tutorial", "obsiland"));

  3.   @Override
  4.   public void onInitialize() {
  5.     Registry.register(BuiltinRegistries.CONFIGURED_SURFACE_BUILDER, new Identifier("tutorial", "obsidian"), OBSIDIAN_SURFACE_BUILDER);
  6.     Registry.register(BuiltinRegistries.BIOME, OBSILAND_KEY.getValue(), OBSILAND);
  7.   }
  8. }
复制代码

你也要给它写一个翻译节点在你的en_us.json文件
src/main/resources/assets/modid/lang/en_us.json
  1. {
  2.   "biome.tutorial.obsiland": "Obsiland"
  3. }
复制代码

那么对应中文版就是
src/main/resources/assets/modid/lang/zh_cn.json
  1. {
  2.   "biome.tutorial.obsiland": "黑曜石大陆"
  3. }
复制代码

向气候区添加生物群系
我们要指定生物群系要加到哪个气候区,要加入哪个生物群系,和生物群系的权重(双精度浮点数)。权重是你生物群系被生成的概率。高权重对应高生成概率,相对于其他生物群系的权重。你会想要一个高权重来更加容易测试。在这个指南中,我们会向TEMPERATE和COOL气候添加生物群系。
  1. public class ExampleMod implements ModInitializer {
  2.   @Override
  3.   public void onInitialize() {
  4.     [...]

  5.     OverworldBiomes.addContinentalBiome(OBSILAND_KEY, OverworldClimate.TEMPERATE, 2D);
  6.     OverworldBiomes.addContinentalBiome(OBSILAND_KEY, OverworldClimate.COOL, 2D);
  7.   }
  8. }
复制代码

结果
恭喜!你的生物群系应该已经在世界中生成了,你可以用下面这个指令来找到你的生物群系。
  1. /locatebiome tutorial:obsiland
复制代码

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册(register)

本版积分规则

Archiver|小黑屋|Mcbbs.net ( 京ICP备15023768号-1 ) | 京公网安备 11010502037624号 | 手机版

GMT+8, 2021-6-13 00:48 , Processed in 0.085886 second(s), Total 39, Slave 32 queries, Release: Build.2021.04.28 1615, Gzip On, Redis On.

"Minecraft"以及"我的世界"为Mojang Synergies AB的商标 本站与Mojang以及微软公司没有从属关系

© 2010-2020 我的世界中文论坛 版权所有 本站原创图文内容版权属于原创作者,未经许可不得转载

快速回复 返回顶部 返回列表