# @Accessor `@Accessor` 注解用于自动生成目标类中字段的 `getter` 和 `setter` 方法。这使得你可以访问和修改私有(private)字段。 ## 用法 `@Accessor` 注解在一个抽象方法上,Mixin 会在编译时自动实现这个方法。 ### 生成 Getter 要为一个字段生成 `getter`,你需要声明一个无参数的方法,其名称遵循 Java Bean 规范(例如,`getX` 或 `isX`)。返回类型必须与字段类型匹配。 **示例:** 目标类: ```java public class Entity { private int entityId; } ``` Mixin: ```java @Mixin(Entity.class) public interface EntityAccessor { @Accessor("entityId") int getEntityId(); } ``` 现在你可以将 `Entity` 对象转换为 `EntityAccessor` 接口,并调用 `getEntityId()` 来获取 `entityId` 字段的值。 ### 生成 Setter 要生成 `setter`,你需要声明一个带一个参数的方法,名称以 `set` 开头。返回类型为 `void`。 **示例:** ```java @Mixin(Entity.class) public interface EntityAccessor { @Accessor("entityId") void setEntityId(int id); } ``` 这将允许你修改 `entityId` 字段。 ## `value` 属性 `value` 属性是可选的。如果你不指定 `value`,Mixin 会尝试根据方法名推断目标字段名(例如 `getEntityId` -> `entityId`)。如果字段名与方法名不匹配,或者有歧义,你就需要明确指定 `value`。 **示例:** ```java // 目标字段名是 "internalId",但我们想让 getter 名为 "getId" @Accessor("internalId") int getId(); ``` ## 使用类而不是接口 你也可以在 `Mixin` 类(而不是接口)中使用 `@Accessor`。 ```java @Mixin(Entity.class) public abstract class EntityMixin { @Accessor("entityId") abstract int getEntityId(); } ``` 在这种情况下,方法需要是 `abstract` 的。 --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/Mixin/annotation/At.md' --- # @At `@At` 注解用于指定注入点(Injection Point),它决定了代码注入到目标方法中的确切位置。`@At` 通常作为 `@Inject`、`@Redirect` 等注解的参数使用。 ## `value` 属性 `value` 属性是一个字符串,用于指定注入点的类型。下面是一些常用的注入点类型: ### `HEAD` `HEAD` 将代码注入到目标方法的开头。这是最简单和最常用的注入点之一。 **示例:** ```java @Inject(method = "update", at = @At("HEAD")) private void onUpdate(CallbackInfo ci) { // This code runs at the start of the update() method } ``` ### `TAIL` `TAIL` 将代码注入到目标方法的末尾,紧接在 `RETURN` 指令之前。如果方法有多个返回点,`TAIL` 会在所有返回点之前注入。 **示例:** ```java @Inject(method = "update", at = @At("TAIL")) private void afterUpdate(CallbackInfo ci) { // This code runs just before the update() method returns } ``` ### `RETURN` `RETURN` 与 `TAIL` 类似,但它会在每个 `RETURN` 指令之前注入。如果方法有返回值,注入方法的最后一个参数应该是 `CallbackInfoReturnable`。 ### `INVOKE` `INVOKE` 用于在方法调用指令之前注入代码。你需要使用 `target` 参数来指定目标方法。 **`target` 参数:** `target` 参数是一个字符串,用于指定目标方法的签名。签名格式为 `Lowner/class;methodName(Larg/type;)Lreturn/type;`。 **示例:** ```java // Target code: public class Game { private Logger logger; public void start() { this.logger.info("Game starting!"); // We want to inject before this line } } // Mixin: @Mixin(Game.class) public class GameMixin { @Inject( method = "start", at = @At( value = "INVOKE", target = "Lorg/apache/logging/log4j/Logger;info(Ljava/lang/String;)V" ) ) private void onLoggerInfo(CallbackInfo ci) { System.out.println("The logger is about to be used!"); } } ``` ### `FIELD` `FIELD` 用于在字段访问(获取或设置)指令之前注入代码。你需要使用 `target` 参数来指定目标字段。 **示例:** ```java @Inject( method = "someMethod", at = @At( value = "FIELD", target = "Lnet/minecraft/world/World;isRemote:Z", opcode = Opcodes.GETFIELD ) ) ``` ### `NEW` `NEW` 用于在 `new` 一个新对象时注入代码。 ### `CONSTANT` `CONSTANT` 用于在加载一个常量时注入。这对于修改方法中使用的硬编码值很有用。 ## `shift` 属性 `shift` 属性用于微调注入点的位置。它可以是 `BY`, `BEFORE`, 或 `AFTER`。 * `shift = At.Shift.BY`:按指定的字节码指令数量移动注入点。需要 `by` 参数。 * `shift = At.Shift.BEFORE`:将注入点移动到目标之前。 * `shift = At.Shift.AFTER`:将注入点移动到目标之后。 **示例:** ```java @Inject( method = "example", at = @At( value = "INVOKE", target = "...", shift = At.Shift.AFTER ) ) ``` 这会将代码注入到方法调用 *之后*,而不是之前。 --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/Mixin/annotation/Final.md' --- # @Final `@Final` 本身不是一个 Mixin 注解,而是 Java 的一个关键字。但在 Mixin 的上下文中,它与 `@Shadow` 结合使用时有特殊的意义。 ## @Shadow a final field 当你需要 `shadow` 一个 `final` 字段时,你必须在你的 `@Shadow` 声明中也包含 `final` 关键字。 **示例:** 目标类: ```java public class Block { private final String translationKey; } ``` Mixin: ```java @Mixin(Block.class) public class BlockMixin { @Shadow private final String translationKey; @Inject(method = "onUse", at = @At("HEAD")) private void onUse(CallbackInfo ci) { if (this.translationKey.equals("block.minecraft.chest")) { // ... } } } ``` ## @Mutable 如果你想要修改一个 `final` 字段的值,你需要额外使用 `@Mutable` 注解。 ```java @Mixin(Block.class) public abstract class BlockMixin { @Mutable @Shadow private final String translationKey; public void setTranslationKey(String newKey) { this.translationKey = newKey; } } ``` 没有 `@Mutable` 注解,尝试给一个 `final` 的 `@Shadow` 字段赋值会导致编译错误。 --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/Mixin/annotation/Implements.md' --- # @Implements `@Implements` 注解用于让目标类实现一个或多个接口。这通常与 `@Unique` 结合使用,以便能够从外部调用添加到目标类中的新方法。 ## 用法 `@Implements` 注解需要一个 `value` 属性,它是一个 `@Interface` 注解的数组。每个 `@Interface` 注解指定一个要实现的接口和一个前缀。 ### 示例 假设我们有一个接口: ```java public interface ICustomData { int getCustomData(); void setCustomData(int data); } ``` 我们想让 `Player` 类实现这个接口,并添加相应的字段和方法。 ```java @Mixin(Player.class) @Implements(@Interface(iface = ICustomData.class, prefix = "custom$")) public abstract class PlayerMixin { @Unique private int customData; @Unique public int custom$getCustomData() { return this.customData; } @Unique public void custom$setCustomData(int data) { this.customData = data; } } ``` ## `prefix` 属性 `prefix` 属性非常重要。它指定了一个前缀,所有实现了接口中方法的 `@Unique` 方法都必须以这个前缀开头。这告诉 Mixin 哪个 `@Unique` 方法对应于接口中的哪个方法。 在上面的例子中: * `custom$getCustomData()` 实现了 `ICustomData` 中的 `getCustomData()` * `custom$setCustomData(int data)` 实现了 `ICustomData` 中的 `setCustomData(int data)` 在 `Mixin` 应用之后,你就可以将一个 `Player` 对象转换为 `ICustomData` 类型,并安全地调用 `getCustomData()` 和 `setCustomData()`。 ```java Player player = ...; ICustomData customPlayer = (ICustomData) player; customPlayer.setCustomData(10); int data = customPlayer.getCustomData(); // returns 10 ``` 这是一种在目标类上添加新功能并暴露 API 的干净方式。 --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/Mixin/annotation/Inject.md' --- # @Inject `@Inject`注解用于将代码注入到目标方法的特定位置。这是最常用的`Mixin`注解之一。 ## 属性 ### `method` `method`属性指定了目标方法的名称和描述符。这是必须的属性,用于定位要注入代码的方法。 **示例:** ```java @Inject(method = "render", at = @At("HEAD")) private void onRender(CallbackInfo ci) { // a hook at the beginning of render() } ``` ### `at` `at`属性使用`@At`注解来指定注入点(Injection Point)。注入点决定了你的代码在目标方法中的确切位置。`@At`有很多可能的取值,下面是一些常用的: * `"HEAD"`: 注入到方法的开头。 * `"TAIL"`: 注入到方法的结尾,在所有`RETURN`操作码之前。 * `"RETURN"`: 在每个`RETURN`操作码之前注入。 * `"INVOKE"`: 在方法调用之前注入。需要和`target`一起使用来指定目标调用。 关于`@At`的更详细信息,请参考[@At文档](./At.md)。 ### `slice` `slice`属性用于更精确地指定注入点的位置,特别是当有多个匹配的注入点时。 ### `cancellable` 当`cancellable`属性设置为`true`时,你可以在注入的代码中取消目标方法的执行。这对于阻止原版方法的逻辑执行很有用。 当`cancellable = true`时,你的注入方法必须使用`CallbackInfo`作为参数。你可以调用`ci.cancel()`来取消目标方法。 **示例:** ```java @Inject(method = "example", at = @At("HEAD"), cancellable = true) private void onExample(CallbackInfo ci) { // some condition if (shouldCancel) { ci.cancel(); // Cancels the target method } } ``` ### `locals` `locals`属性允许你捕获注入点作用域内的局部变量。你需要使用`org.spongepowered.asm.mixin.injection.callback.LocalCapture`枚举来指定如何捕获局部变量。 * `LocalCapture.PRINT`:打印所有可用的局部变量到控制台。用于调试。 * `LocalCapture.CAPTURE_FAILHARD`:捕获局部变量。如果失败则抛出异常。 * `LocalCapture.CAPTURE_FAILSOFT`:捕獲局部變量。如果失敗,則記錄一個警告。 捕获的局部变量会作为参数传递给你的注入方法,在`CallbackInfo`参数之后。 **示例:** 目标方法: ```java public void example(int i) { String s = "hello"; double d = 1.0; // some code } ``` Mixin: ```java @Inject(method = "example", at = @At(...), locals = LocalCapture.CAPTURE_FAILHARD) private void onExample(CallbackInfo ci, int i, String s, double d) { // You can now use i, s, and d } ``` ## 回调信息 (CallbackInfo) 注入方法通常需要一个`CallbackInfo`类型的参数。它提供了关于注入的信息,并且在`cancellable`时用于取消方法。 如果目标方法有返回值,你应该使用`CallbackInfoReturnable`,其中`R`是返回类型。你可以使用`cir.setReturnValue()`来修改返回值。 **示例:** ```java @Inject(method = "getValue", at = @At("HEAD"), cancellable = true) private void onGetValue(CallbackInfoReturnable cir) { cir.setReturnValue("new value"); } ``` --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/Mixin/annotation/Interface.md' --- # @Interface `@Interface` 注解有两个主要用途: 1. 作为 `@Implements` 注解的一部分,声明要实现的接口和方法前缀。 2. 作为独立的注解,用于"软实现"一个接口。 ## 在 `@Implements` 中使用 这是 `@Interface` 最常见的用法。它告诉 Mixin 目标类应该实现哪个接口,以及实现方法的 `prefix` 是什么。 ```java @Implements(@Interface(iface = ICustomData.class, prefix = "custom$")) ``` 更多信息请参考 [`@Implements` 的文档](./Implements.md)。 ## 软实现 (Soft Implementation) 你可以直接在 `Mixin` 类上使用 `@Interface` 注解。这被称为"软实现"。它声称目标类实现了指定的接口,但**不**强制你提供实现。 **示例:** ```java @Mixin(Block.class) @Interface(iface = IBreakable.class, prefix = "breakable$") public class BlockMixin { // No methods prefixed with "breakable$" are required here } ``` ### 用途 软实现的主要用途是,当你知道目标类 *已经* 拥有符合接口规范的方法时,你可以用 `@Interface` 来形式化地建立这种关系,而无需重新实现这些方法。 例如,假设 `IBreakable` 接口有一个 `breakBlock()` 方法。如果 `Block` 类已经有一个 `breakBlock()` 方法(或者一个可以被重映射为 `breakBlock` 的方法),Mixin 就会将这个现有的方法视为接口实现,而不需要你在 `Mixin` 中提供一个 `breakable$breakBlock()` 方法。 ### `remap` 属性 `@Interface` 注解有一个 `remap` 属性(默认为 `true`),它控制是否对接口中的方法名进行重映射。这在处理混淆环境时非常重要。 --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/Mixin/annotation/Intrinsic.md' --- # @Intrinsic `@Intrinsic` 是一个高级注解,它告诉 Mixin 一个被注解的方法将由目标类 *固有地* 提供实现。这允许你将一个 `Mixin` 的行为与目标类的现有行为连接起来,而不需要使用 `@Shadow` 或 `@Overwrite`。 ## 用法 `@Intrinsic` 通常用于这样的场景:你有一个 `Mixin`,它在某些目标类中需要覆盖一个方法,但在其他目标类中,该方法已经存在并且行为是你想要的。 **示例:** 假设你有一个 `Mixin` 应用于 `ClassA` 和 `ClassB`。 * `ClassA` 有一个方法 `doSomething()`。 * `ClassB` 没有 `doSomething()` 方法。 你的 `Mixin` 想要确保 `doSomething()` 存在。 ```java @Mixin({ClassA.class, ClassB.class}) public abstract class MyMixin { @Intrinsic public void doSomething() { // In ClassA, Mixin will see that doSomething() already exists // and will not apply this Mixin method. It assumes the existing // method is the "intrinsic" implementation. // In ClassB, doSomething() does not exist, so this method *will* be // applied, effectively adding the method to ClassB. System.out.println("Default implementation of doSomething()"); } } ``` ## 与 `@Overwrite` 的区别 * `@Overwrite` 会**总是**尝试覆盖目标方法。如果目标方法不存在,或者签名不匹配,就会出错。如果多个 `Mixin` 覆盖同一个方法,也会出错。 * `@Intrinsic` 是一种"如果不存在,则添加"的行为。它首先检查目标类是否已经有了这个方法。如果有,`@Intrinsic` 方法就不会被应用。如果没有,它就会被作为新方法添加。 ## `displace` 属性 `@Intrinsic` 有一个布尔属性 `displace`。 * `displace = false` (默认): 如果目标方法存在,则不应用 `Mixin` 方法。 * `displace = true`: 如果目标方法存在,`Mixin` 方法会**替换**它,类似于 `@Overwrite`。 ```java @Intrinsic(displace = true) public void doSomething() { // This will now overwrite doSomething() if it exists, // or add it if it doesn't. } ``` `@Intrinsic(displace = true)` 的行为实际上非常接近 `@Overwrite`,但它在某些复杂的 `Mixin` 继承场景中能解决一些 `@Overwrite` 无法处理的冲突。 对于大多数用例,`@Intrinsic` 是一个不常用的高级功能。 --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/Mixin/annotation/Invoker.md' --- # @Invoker `@Invoker` 注解用于让你能够调用目标类中的私有(private)或保护(protected)方法。 ## 用法 与 `@Accessor` 类似,`@Invoker` 注解在一个抽象方法上,Mixin 会在编译时自动实现它,使其调用目标方法。 **示例:** 目标类: ```java public class Minecraft { private void doRender() { // ... } } ``` Mixin: ```java @Mixin(Minecraft.class) public interface MinecraftInvoker { @Invoker("doRender") void callDoRender(); } ``` 现在,你可以将 `Minecraft` 对象转换为 `MinecraftInvoker` 接口,并调用 `callDoRender()` 方法,这实际上会调用原始的 `doRender()` 方法。 ## `value` 属性 `value` 属性是可选的。如果你不指定 `value`,Mixin 会尝试根据你声明的方法名来匹配目标方法(例如,`callDoRender` -> `doRender`,`invokeDoRender` -> `doRender`)。如果你的方法名和目标方法名不匹配,或者有歧义,你需要明确指定 `value`。 **示例:** ```java // 目标方法是 "renderInternal",但我们想让调用器名为 "render" @Invoker("renderInternal") void render(); ``` ## 静态方法 `@Invoker` 也可以用于调用静态方法。你只需要确保你的调用器方法也是 `static` 的。 目标类: ```java public class Util { private static void helper() { // ... } } ``` Mixin: ```java @Mixin(Util.class) public interface UtilInvoker { @Invoker("helper") static void callHelper(); } ``` ## 与 `@Shadow` 的区别 * `@Shadow` 是在你自己的 `Mixin` 代码中 *内部* 使用,用来访问目标类成员。 * `@Invoker` (和 `@Accessor`) 通常用于创建公共接口,让 *外部* 代码可以访问目标类的私有成员。它们通过接口转换的方式,提供了一种更清晰的 API。 --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/Mixin/annotation/Mixin.md' --- # @Mixin `@Mixin`注解用于声明一个类为`Mixin`类,并指定要注入的目标类。 ## 属性 ### `value` `value`属性用于指定一个或多个目标类。目标类是`Mixin`将要应用修改的类。 **示例:** ```java @Mixin(ExampleClass.class) public class ExampleMixin { // ... } ``` 可以指定多个目标类: ```java @Mixin({ExampleClass1.class, ExampleClass2.class}) public class ExampleMixin { // ... } ``` ### `priority` `priority`属性用于指定`Mixin`的优先级。优先级是一个整数,值越小,优先级越高。默认优先级是`1000`。在多个`Mixin`修改同一个方法时,优先级高的会先应用。 **示例:** ```java @Mixin(value = ExampleClass.class, priority = 900) public class HighPriorityMixin { // ... } ``` 关于优先级的更详细说明: `Mixin`的优先级系统允许开发者控制他们的`Mixin`在其他`Mixin`之前或之后应用。这在多个模组修改同一个目标类时尤其重要。 默认优先级是 `1000`。如果你想让你的`Mixin`在大多数其他`Mixin`之前应用,你可以设置一个较低的`priority`值(例如`900`)。如果你希望在其他`Mixin`之后应用,可以设置一个较高的值(例如`1100`)。 Fabric和Forge有一些默认的优先级范围,但通常你只需要关心比默认值高还是低。 ### `remap` `remap`属性是一个布尔值,用于指定是否对`Mixin`中的成员(方法、字段)名称进行重映射。在开发环境中,我们使用可读的名称(如`net.minecraft.client.Minecraft`),但在生产环境中,这些名称会被混淆(如`net.minecraft.client.d`)。 默认情况下,`remap`为`true`,这意味着`Mixin`会尝试将你使用的开发环境中的名称映射到混淆后的名称。在大多数情况下,你应该保持`remap = true`。只有在你需要直接操作混淆后的代码或者目标不是一个需要被映射的类时(例如其他模组的类),才将其设置为`false`。 ::: warning 注意 如果你需要`Mixin`其他模组,那么通常必须将`remap`设为`false`。 ::: **示例:** ```java @Mixin(value = ExampleClass.class, remap = false) public class ExampleMixin { // ... } ``` --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/Mixin/annotation/ModifyArg.md' --- # @ModifyArg `@ModifyArg` 注解用于修改传递给方法的参数。它允许你在方法调用发生之前更改参数的值。 ## 属性 ### `method` `method` 属性指定了包含要修改参数的方法调用的方法。 ### `at` `at` 属性使用 `@At` 注解来定位到进行参数修改的方法调用。`@At` 的 `value` 通常是 `INVOKE`。 ### `index` `index` 属性指定要修改的参数的索引(从0开始)。 ## 用法 `@ModifyArg` 注解的方法应该返回与要修改的参数相同的类型,并接受与原始参数相同类型的参数。 **示例:** 假设有以下目标代码: ```java public class Gui { public void drawString(String s, int x, int y, int color) { // draws a string } public void render() { this.drawString("Hello", 10, 10, 0xFFFFFF); } } ``` 我们可以使用 `@ModifyArg` 来修改传递给 `drawString` 的字符串: ```java @Mixin(Gui.class) public class GuiMixin { @ModifyArg( method = "render", at = @At( value = "INVOKE", target = "Lnet/example/Gui;drawString(Ljava/lang/String;III)V" ), index = 0 ) private String modifyText(String originalText) { return "Modified Text"; } } ``` 在这个例子中,当 `render` 方法调用 `drawString` 时,第一个参数(索引为0)`"Hello"` 将被替换为 `"Modified Text"`。 你也可以修改其他索引的参数,比如修改颜色: ```java @ModifyArg( method = "render", at = @At( value = "INVOKE", target = "Lnet/example/Gui;drawString(Ljava/lang/String;III)V" ), index = 3 ) private int modifyColor(int originalColor) { return 0xFF0000; // Red color } ``` 这会将文本颜色修改为红色。 --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/Mixin/annotation/ModifyArgs.md' --- # @ModifyArgs `@ModifyArgs` 注解用于一次性修改方法调用的多个参数。它比多次使用 `@ModifyArg` 更高效。 ## 用法 `@ModifyArgs` 注解的方法需要一个 `Args` 类型的参数。`Args` 对象包含了目标方法调用的所有参数,你可以通过 `get` 和 `set` 方法来访问和修改它们。 **示例:** 假设有以下目标代码: ```java public class Gui { public void drawString(String s, int x, int y, int color) { // draws a string } public void render() { this.drawString("Hello", 10, 10, 0xFFFFFF); } } ``` 我们可以使用 `@ModifyArgs` 来同时修改 `drawString` 的 `x` 和 `y` 坐标: ```java import org.spongepowered.asm.mixin.injection.invoke.arg.Args; @Mixin(Gui.class) public class GuiMixin { @ModifyArgs( method = "render", at = @At( value = "INVOKE", target = "Lnet/example/Gui;drawString(Ljava/lang/String;III)V" ) ) private void modifyCoordinates(Args args) { int x = args.get(1); // get original x int y = args.get(2); // get original y args.set(1, x + 50); // set new x args.set(2, y + 20); // set new y } } ``` 在这个例子中,`drawString` 方法的 `x` 参数将变为 `60`,`y` 参数将变为 `30`。 ## `Args` 类 `Args` 类提供了以下方法: * `get(int index)`: 获取指定索引的参数。 * `set(int index, Object value)`: 设置指定索引的参数。 * `setAll(Object... values)`: 一次性设置所有参数。 * `size()`: 获取参数的数量。 使用 `setAll` 的示例: ```java @ModifyArgs( method = "render", at = @At( value = "INVOKE", target = "Lnet/example/Gui;drawString(Ljava/lang/String;III)V" ) ) private void modifyAll(Args args) { args.setAll("New string", 100, 100, 0xFF00FF); } ``` 这会替换 `drawString` 的所有参数。 --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/Mixin/annotation/ModifyConstant.md' --- # @ModifyConstant `@ModifyConstant` 注解用于修改在方法中加载的常量值。 ::: v-warning 注意 `@ModifyConstant` 在新版本的 Mixin 中已被弃用,因为它在处理某些常量(如 `ldc`) 时存在问题。 推荐使用 `@ModifyArg`,`@Redirect` 或者带有 `@At(value = "CONSTANT")` 的 `@Inject` 来实现类似的功能。 ::: ## 用法 `@ModifyConstant` 注解的方法应该返回与要修改的常量相同的类型,并可以接受一个同类型的参数(原始常量值)。 **示例 (旧版用法):** 目标代码: ```java public class Example { public void printMessage() { System.out.println("Hello World"); } } ``` Mixin: ```java @Mixin(Example.class) public class ExampleMixin { @ModifyConstant(method = "printMessage", constant = @Constant(stringValue = "Hello World")) private String modifyMessage(String constant) { return "Goodbye World"; } } ``` 这个 Mixin 会将 `printMessage` 方法中的 "Hello World" 字符串常量修改为 "Goodbye World"。 由于它的不可靠性,请尽量避免使用此注解。 --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/Mixin/annotation/ModifyVariable.md' --- # @ModifyVariable `@ModifyVariable` 注解用于修改方法中的局部变量。 ## 属性 ### `method` `method` 属性指定了包含要修改局部变量的方法。 ### `at` `at` 属性使用 `@At` 注解来指定在何处修改变量。 ### `index` / `ordinal` 你可以使用 `index` (或 `ldc` / `var` / `arg`) 或 `ordinal` 来指定要修改的变量。 * `index`: 局部变量表中的索引。 * `ordinal`: 如果有多个相同类型的变量,`ordinal` 用于选择第几个(从0开始)。 ### `name` 你也可以通过 `name` 属性来指定变量名。这通常比使用索引更清晰。 ## 用法 `@ModifyVariable` 注解的方法应该返回与要修改的变量相同的类型,并接受一个同类型的参数(原始变量值)。 **示例:** 假设目标方法如下: ```java public void applyDamage(int damage) { int finalDamage = damage * 2; // ... deal finalDamage } ``` 我们可以修改 `finalDamage` 变量: ```java @Mixin(Example.class) public class ExampleMixin { @ModifyVariable( method = "applyDamage", at = @At("STORE"), // after the variable is stored name = "finalDamage" ) private int modifyFinalDamage(int originalDamage) { return originalDamage / 2; } } ``` `@At("STORE")` 表示在变量被赋值后立即修改它。在这个例子中,`finalDamage` 的值将从 `damage * 2` 变为 `(damage * 2) / 2`,也就是 `damage`。 ## 修改方法参数 `@ModifyVariable` 也可以用来修改方法的参数,因为它们也被看作是局部变量。 **示例:** ```java @Mixin(Example.class) public class ExampleMixin { @ModifyVariable( method = "applyDamage", at = @At("HEAD"), argsOnly = true // only look at arguments ) private int modifyDamageArgument(int damage) { return 0; // Negate all damage } } ``` 这里 `argsOnly = true` 是一个好习惯,它告诉 Mixin 只在方法的参数中查找要修改的变量。`at = @At("HEAD")` 表示在方法开始时就修改参数。 --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/Mixin/annotation/Overwrite.md' --- # @Overwrite `@Overwrite` 注解用于完全覆盖目标方法。使用 `@Overwrite` 的方法将会替换掉整个目标方法。 ## 用法 `@Overwrite` 注解的方法必须和目标方法有完全相同的签名(方法名、返回类型、参数)。 **示例:** 目标代码: ```java public class Example { public String getMessage() { return "Original Message"; } } ``` 使用 `@Overwrite` 覆盖 `getMessage` 方法: ```java @Mixin(Example.class) public class ExampleMixin { /** * @author YourName * @reason A brief explanation of why you are overwriting. */ @Overwrite public String getMessage() { return "Overwritten Message"; } } ``` 现在,当代码调用 `Example` 实例的 `getMessage` 方法时,它将返回 `"Overwritten Message"`。 ## Javadoc 注释 当使用 `@Overwrite` 时,强烈建议添加 Javadoc 注释,并包含 `@author` 和 `@reason` 标签。 * `@author`: 你的名字或ID。 * `@reason`: 解释你为什么要覆盖这个方法。 这有助于其他开发者理解你的意图,并且在出现冲突时更容易调试。 ## 警告 `@Overwrite` 是一种非常具有侵入性的注入方式。它会完全删除原始方法的内容。这使得它非常容易与其他 `Mixin` 产生冲突。如果多个 `Mixin` 都尝试覆盖同一个方法,只有一个能成功,其他的都会失败并导致游戏崩溃。 **请优先考虑使用 `@Inject`、`@Redirect` 或其他侵入性较小的注解。`@Overwrite` 应该是你的最后选择。** ::: danger 警告!· 无论何时使用`@Overwrite`与`@Redirect`都是不推荐的行为! ::: --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/Mixin/annotation/Redirect.md' --- # @Redirect `@Redirect` 注解用于将一个方法调用重定向到你的方法。这允许你完全替换一个方法调用的行为。 ## 用法 `@Redirect` 注解的方法应该有和你想要重定向的方法调用几乎相同的签名。区别在于,`@Redirect` 方法的第一个参数应该是拥有被调用方法的对象实例。 **示例:** 假设目标代码如下: ```java public class Player { private Inventory inventory; public void dropAllItems() { this.inventory.clear(); // We want to redirect this call } } ``` 我们可以重定向 `inventory.clear()` 调用: ```java @Mixin(Player.class) public class PlayerMixin { @Redirect( method = "dropAllItems", at = @At( value = "INVOKE", target = "Lnet/example/Inventory;clear()V" ) ) private void onClear(Inventory inventory) { // Instead of clearing the inventory, we do something else. System.out.println("Inventory clear redirected!"); } } ``` 在这个例子中,当 `dropAllItems` 方法执行时,对 `inventory.clear()` 的调用会被重定向到 `onClear` 方法。原始的 `clear()` 方法将不会被执行。`onClear` 方法的第一个参数 `inventory` 是调用 `clear()` 方法的对象实例。 ## 重定向静态方法调用 如果你重定向一个静态方法的调用,那么你的 `@Redirect` 方法也应该是静态的,并且第一个参数应该是被调用方法的第一个参数。 ## 与 `@Inject` 的区别 * `@Inject` 是在方法调用 *之前* 或 *之后* 添加代码,原始的方法调用仍然会发生。 * `@Redirect` 是 *替换* 整个方法调用。原始的方法调用不会发生。 `@Redirect` 是一种更强大的注入方式,但有时也更危险,因为它完全改变了原始逻辑。 ::: danger 警告!· 无论何时使用`@Overwrite`与`@Redirect`都是不推荐的行为! ::: --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/Mixin/annotation/Shadow.md' --- # @Shadow `@Shadow` 注解用于在 `Mixin` 类中声明一个对目标类中成员(字段或方法)的引用。这允许你从 `Mixin` 的方法中访问目标类的私有(private)或保护(protected)成员。 ## 用法 ### 访问字段 要访问目标类中的一个字段,你可以在 `Mixin` 中声明一个带有 `@Shadow` 注解的同名同类型的字段。 **示例:** 目标类: ```java public class Player { private boolean onGround; } ``` Mixin: ```java @Mixin(Player.class) public abstract class PlayerMixin { @Shadow private boolean onGround; @Inject(method = "jump", at = @At("HEAD")) private void onJump(CallbackInfo ci) { if (this.onGround) { // ... } } } ``` 现在你可以在 `onJump` 方法中访问 `onGround` 字段了。 ### 访问方法 同样地,你也可以用 `@Shadow` 来访问目标类中的方法。 **示例:** 目标类: ```java public class Block { protected void dropItems() { // ... } } ``` Mixin: ```java @Mixin(Block.class) public abstract class BlockMixin { @Shadow protected abstract void dropItems(); @Inject(method = "destroy", at = @At("TAIL")) private void onDestroy(CallbackInfo ci) { this.dropItems(); } } ``` 注意,`@Shadow` 方法必须是 `abstract` 的。 ## `prefix` 属性 有时,你的 `Mixin` 类可能需要一个与目标类中字段或方法同名的成员。为了避免命名冲突,你可以使用 `prefix` 属性。Mixin 会自动为你添加上前缀。 **示例:** ```java @Mixin(Example.class) public class ExampleMixin { @Shadow(prefix = "shadow$") private int value; // This will shadow the 'value' field in Example.class private int value; // This is a new field specific to the mixin public void someMethod() { this.value = 1; // Accesses the mixin's own 'value' field this.shadow$value = 2; // Accesses the shadowed 'value' field from the target class } } ``` ## `final` 字段 如果目标字段是 `final` 的,你需要在 `@Shadow` 注解的字段上也加上 `final` 修饰符。 ```java @Shadow final private int score; ``` 如果你想修改一个 `final` 字段,你需要使用 `@Mutable` 注解和 `@Shadow` 一起。 ```java @Mixin(Example.class) public abstract class ExampleMixin { @Mutable @Shadow private final int score; // constructor or other methods to modify score } ``` 现在你就可以在你的 `Mixin` 方法中修改 `score` 的值了。 ## 匿名内部类 `@Shadow` 也可以用于匿名内部类,但这通常比较复杂,因为匿名类没有名字。你需要使用特殊的语法来定位它们。 --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/Mixin/annotation/Slice.md' --- # @Slice `@Slice` 注解用于在目标方法中定义一个"切片",也就是一个特定的代码区域。当 `@Inject` 等注入器注解与 `@Slice` 结合使用时,注入器将只在由 `@Slice` 定义的这个切片内部查找其注入点 (`@At`)。 这对于在大型方法中精确定位注入点,或者当存在多个相同的潜在注入点时,非常有用。 ## 用法 `@Slice` 注解被用在 `@Inject` 的 `slice` 属性中。 ```java @Inject( method = "veryLargeMethod", at = @At(...), slice = @Slice(...) ) private void onSomethingInSlice(CallbackInfo ci) { // ... } ``` ## 定义切片 一个切片由一个起点和一个可选的终点定义。 ### `from` 属性 `from` 属性使用一个 `@At` 注解来标记切片的开始位置(包含)。 ### `to` 属性 `to` 属性使用一个 `@At` 注解来标记切片的结束位置(包含)。 **示例:** 假设有一个方法: ```java public void process() { // A setup(); // B runLoop(); // C cleanup(); // D } ``` 我们只想在 `runLoop()` 和 `cleanup()` 之间的代码区域(C区域)注入。我们可以这样定义一个切片: ```java @Inject( method = "process", at = @At("TAIL"), // 在切片的末尾注入 slice = @Slice( from = @At(value = "INVOKE", target = "Lnet/example/MyClass;runLoop()V"), to = @At(value = "INVOKE", target = "Lnet/example/MyClass;cleanup()V") ) ) private void afterRunLoop(CallbackInfo ci) { // This code will be injected right after runLoop() is called, // because the @At("TAIL") is now relative to the slice. } ``` ## `id` 属性 你可以为一个 `slice` 指定一个唯一的 `id`。这在以下两种情况中有用: 1. **可读性**: 给切片一个有意义的名字。 2. **共享**: 多个注入器可以通过 `id` 引用同一个在 `@Slice` 注解中定义的切片,避免重复定义。 要定义一个可共享的切片,你可以在 `Mixin` 类上使用 `@Slice` 注解,并为其提供 `id`。 ```java @Mixin(Example.class) @Slice(id = "mySlice", from = @At(...), to = @At(...)) public abstract class ExampleMixin { @Inject(method = "example", at = @At(...), slice = @Slice(id = "mySlice")) private void injector1(...) { ... } @ModifyVariable(method = "example", at = @At(...), slice = @Slice(id = "mySlice")) private int injector2(...) { ... } } ``` 现在,`injector1` 和 `injector2` 都在 `mySlice` 定义的范围内查找它们的注入点。 --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/Mixin/annotation/Unique.md' --- # @Unique `@Unique` 注解用于标记一个字段或方法在 `Mixin` 中是唯一的。这意味着这个成员不应该存在于目标类中。当 `Mixin` 应用时,这个唯一的成员会被添加到目标类中。 ## 用法 `@Unique` 确保你添加的成员不会意外地覆盖目标类中已有的成员。如果目标类中已经存在一个同名的成员,`Mixin` 在应用时会抛出一个错误。 ### 添加新字段 你可以在 `Mixin` 中添加新的字段,并在目标类中使用它们。 **示例:** ```java @Mixin(Player.class) public class PlayerMixin { @Unique private int customTimer; @Inject(method = "tick", at = @At("HEAD")) private void onTick(CallbackInfo ci) { this.customTimer++; } } ``` 这个例子向 `Player` 类添加了一个 `customTimer` 字段,并在 `tick` 方法中更新它。 ### 添加新方法 同样地,你也可以添加新的方法。 ```java @Mixin(Player.class) public abstract class PlayerMixin implements IPlayerWithCustomLogic { @Unique private int customData; @Unique public void doCustomLogic() { this.customData++; // ... } } ``` 通常,为了能从外部调用这些新添加的方法,你会让 `Mixin` 实现一个接口(例如 `IPlayerWithCustomLogic`),然后在需要的地方将目标对象(`Player`)转换成该接口类型来调用新方法。 ## `silent` 属性 默认情况下,如果 `@Unique` 成员与目标类中的成员冲突,Mixin 会抛出错误。你可以设置 `silent = true` 来抑制这个错误。这在某些高级用例中可能有用,但通常不推荐,因为它可能隐藏潜在的问题。 ```java @Unique(silent = true) private void possiblyClashingMethod() { // ... } ``` ## 总结 * `@Unique` 用于向目标类安全地添加新成员。 * 它能防止意外的方法或字段覆盖。 * 是实现自定义逻辑和存储自定义数据的常用方式。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAdvanced/GlobalVariable.md --- # `global` Variables In KubeJS, `global` is a shared variable space that can be accessed from `server`, `startup`, and `client` scripts. ## Quick Way to Understand It First, create `GlobalTest.js` under the `server` scripts folder, then write: ```js global.testLog = (message) =>{ console.log(message) } global.testNumber = 10; global.testMessage = "hello,meng" global.testList = [ 1,2,3,4 ] global.testObj = { h : 1, b : 2 } ``` After that, add this in a `client` script: ```js global.testLog(global.testMessage) console.log(global.testMessage) ``` Then add this in `startup`: ```js global.testList.forEach(value=>{ global.testLog(value) }); console.log(global.testObj.h) console.log(global.testObj.b) ``` Now run commands in-game in this order: 1. /kjs reload server\_scripts Because the global variable definitions are in `server`, load this folder first so they are available in KubeJS. 2. /kjs reload client\_scripts After running this, check `/logs/kubejs/server.log` and `/logs/kubejs/client.log`. In `server.log`, you will see a `"hello,meng"` output line from `global.testLog(global.testMessage)`. In `client.log`, you will also see `"hello,meng"` from `console.log(global.testMessage)`. 3\. /kjs reload startup\_scripts After this, check `/logs/kubejs/server.log` and `/logs/kubejs/startup.log`. `server.log` prints the array values `1` `2` `3` `4`, showing successful access. `startup.log` prints `1` and `2`, because it read values from the global object. ## Summary Normally, calling variables/functions across these script areas is not straightforward. With `global`, you can access them from all script scopes. ## Use Cases Global variables are used in the project [Altar Composition](../KubeJSProjects/Meng/AltarComposition.md). --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/Digression/ForgeReadObjModel.md --- # `obj` Model * Compared with vanilla `Minecraft` JSON models, `obj` models are much more flexible for authoring. For example, [**Immersive Engineering**](https://www.mcmod.cn/class/463.html) uses them for blocks like the [**Arc Furnace**](https://www.mcmod.cn/item/36383.html) and [**Bucket-Wheel Excavator**](https://www.mcmod.cn/item/36550.html). * ### Build an `obj` Model * First create an `obj` model. You can use many tools, such as [**`3DS Max`**](https://www.autodesk.com.cn/products/3ds-max), [**`Blender`**](https://www.blender.org/), and [**`BlockBench`**](https://www.blockbench.net). Example: ``` # Made in Blockbench 4.8.3 mtllib qqqqoo.mtl o pyramid v 1 -2.220446049250313e-16 1 v 1 0.9999999999999998 1.0000000000000002 v 0 0.9999999999999998 1.0000000000000002 v 0 -2.220446049250313e-16 1 v 0.5 0.4999999999999999 0.5000000000000001 vt 0.25 0.75 vt 0.25 1 vt 0 1 vt 0 0.75 vt 0 0.479471875 vt 0.25 0.479471875 vt 0.125 0.65625 vt 0.1875 0.620096875 vt 0.4375 0.620096875 vt 0.3125 0.796875 vt 0.265625 0.823221875 vt 0.515625 0.823221875 vt 0.390625 1 vt 0.375 0.479471875 vt 0.625 0.479471875 vt 0.5 0.65625 vn 0 -2.220446049250313e-16 1 vn 0.7071067811865476 1.5700924586837752e-16 -0.7071067811865476 vn 0 0.7071067811865477 -0.7071067811865475 vn 0 -0.7071067811865475 -0.7071067811865477 vn -0.7071067811865476 1.5700924586837752e-16 -0.7071067811865476 usemtl m_c5b90dae-3a75-90c1-2280-8a6b9dc0fba2 f 1/1/1 2/2/1 3/3/1 4/4/1 f 2/5/2 1/6/2 5/7/2 f 3/8/3 2/9/3 5/10/3 f 1/11/4 4/12/4 5/13/4 f 4/14/5 3/15/5 5/16/5 ``` Exporting also generates an `mtl` file and a `png` texture file: ``` # Made in Blockbench 4.8.3 newmtl m_c5b90dae-3a75-90c1-2280-8a6b9dc0fba2 map_Kd kubejs:block/qqqqoo newmtl none ``` Make sure `usemtl m_c5b90dae-3a75-90c1-2280-8a6b9dc0fba2` in `obj` matches `newmtl m_c5b90dae-3a75-90c1-2280-8a6b9dc0fba2` in `mtl`. `mtllib qqqqoo.mtl` in `obj` points to the mtl file; keeping them at the same level is fine by default.\ `map_Kd` in `mtl` specifies the texture path.\ Like vanilla models, place textures in `assets/${modid}/textures/block` (this tutorial uses a block example, so it is under `block`; for items, use `item`). * ### Point to the `obj` File * Vanilla `Minecraft` cannot load `obj` files directly, so you need a `json` model file that points to the `obj` file. ```json { "loader": "forge:obj", "model": "kubejs:models/block/qqqqoo.obj", "flip_v": true, "textures": { "particle": "kubejs:block/qqqqoo" } } ``` `loader` specifies the model loader type; here it is set to `obj`.\ `model` specifies the path to the `obj` model.\ `flip_v` controls texture flipping. Since `obj` UVs are vertically inverted relative to MC, set it to `true`.\ `textures` specifies textures to load; `particle` is used here for particle texture. * ### Define `blockstate` * When loading an `obj` model, you must provide a `blockstate`. It is not auto-generated for `obj`, so without it the block cannot resolve its model. ```json { "variants": { "facing=down": { "model": "kubejs:block/qqqqoo", "x": 90 }, "facing=east": { "model": "kubejs:block/qqqqoo", "y": 90 }, "facing=north": { "model": "kubejs:block/qqqqoo" }, "facing=south": { "model": "kubejs:block/qqqqoo", "y": 180 }, "facing=up": { "model": "kubejs:block/qqqqoo", "x": 270 }, "facing=west": { "model": "kubejs:block/qqqqoo", "y": 270 } } } ``` * ### Check Paths Place the `mtl`, `obj`, and the `json` model that points to the `obj` in `assets/${modid}/models/block`.\ Place the `png` texture in `assets/${modid}/textures/block`.\ The `blockstate` `json` file should be in `assets/${modid}/blockstate`.\ These files should be named after the block registry name.\ If the exported model appears offset in-game, adjust it in the modeling tool. # References [\[Minecraft 1.20.4 NeoForge Modding Tutorial\] 18 Load OBJ Models](https://www.bilibili.com/video/BV1jm421J7UR)\ [\[Boson 1.16 Modding Tutorial\] - Obj](https://boson.v2mcdev.com/specialmodel/obj.html) --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/KubeJSCourse/Digression/ForgeReadObjModel.md --- # `obj`模型 * `obj`模型是在制作上相比于`Minecraft`原版模型更为自由的模型,例如[**沉浸工程**](https://www.mcmod.cn/class/463.html)的[**电弧炉**](https://www.mcmod.cn/item/36383.html)和[**斗轮式挖掘机**](https://www.mcmod.cn/item/36550.html)等 * ### 建立`obj`模型 * 首先建立一个`Obj`模型,可以使用很多软件,例如市面上典型的[**`3DS Max`**](https://www.autodesk.com.cn/products/3ds-max) [**`Blender`**](https://www.blender.org/) [**`BlockBench`**](https://www.blockbench.net)等软件 这里提供一个例子 ``` # Made in Blockbench 4.8.3 mtllib qqqqoo.mtl o pyramid v 1 -2.220446049250313e-16 1 v 1 0.9999999999999998 1.0000000000000002 v 0 0.9999999999999998 1.0000000000000002 v 0 -2.220446049250313e-16 1 v 0.5 0.4999999999999999 0.5000000000000001 vt 0.25 0.75 vt 0.25 1 vt 0 1 vt 0 0.75 vt 0 0.479471875 vt 0.25 0.479471875 vt 0.125 0.65625 vt 0.1875 0.620096875 vt 0.4375 0.620096875 vt 0.3125 0.796875 vt 0.265625 0.823221875 vt 0.515625 0.823221875 vt 0.390625 1 vt 0.375 0.479471875 vt 0.625 0.479471875 vt 0.5 0.65625 vn 0 -2.220446049250313e-16 1 vn 0.7071067811865476 1.5700924586837752e-16 -0.7071067811865476 vn 0 0.7071067811865477 -0.7071067811865475 vn 0 -0.7071067811865475 -0.7071067811865477 vn -0.7071067811865476 1.5700924586837752e-16 -0.7071067811865476 usemtl m_c5b90dae-3a75-90c1-2280-8a6b9dc0fba2 f 1/1/1 2/2/1 3/3/1 4/4/1 f 2/5/2 1/6/2 5/7/2 f 3/8/3 2/9/3 5/10/3 f 1/11/4 4/12/4 5/13/4 f 4/14/5 3/15/5 5/16/5 ``` 同时导出时会自动生成一个`mtl`文件和一个`png`文件 ``` # Made in Blockbench 4.8.3 newmtl m_c5b90dae-3a75-90c1-2280-8a6b9dc0fba2 map_Kd kubejs:block/qqqqoo newmtl none ``` 注意`obj`里面的`usemtl m_c5b90dae-3a75-90c1-2280-8a6b9dc0fba2`要与`mtl`文件里面的`newmtl m_c5b90dae-3a75-90c1-2280-8a6b9dc0fba2`对应上 `obj`的`mtllib qqqqoo.mtl`是确定mtl文件的,这里默认同级就行\ `mtl`里面的`map_Kd`则是要指定贴图的路径\ 贴图和原版模型一样,放在`assets/${modid}/textures/block`下(因为这次的教程属于方 块,所以放在`block`下,制作物品时记得`item`下) * ### 指向`Obj`文件 * 因为`Minecraft`原版并不能直接读取`obj`模型文件,因此需要一个`json`模型来指向`obj`文件 ```json { "loader": "forge:obj", "model": "kubejs:models/block/qqqqoo.obj", "flip_v": true, "textures": { "particle": "kubejs:block/qqqqoo" } } ``` `loader`表示要加载的类型,这里指定obj模型\ `model`表示要加载的Obj模型路径\ `flip_v`表示是否翻转贴图,因为`obj`模型贴图相对于MC来说是倒着的,所以要`true`\ `textures`表示要加载的贴图,这里指定`particle`贴图来确定粒子 * ### 确定`blockstate` * 在加载`obj`模型时,需要确定`blockstate`,因为`blockstate`在加载`Obj`时不会自动生成的,因此会导致无法让方块确定模型 ```json { "variants": { "facing=down": { "model": "kubejs:block/qqqqoo", "x": 90 }, "facing=east": { "model": "kubejs:block/qqqqoo", "y": 90 }, "facing=north": { "model": "kubejs:block/qqqqoo" }, "facing=south": { "model": "kubejs:block/qqqqoo", "y": 180 }, "facing=up": { "model": "kubejs:block/qqqqoo", "x": 270 }, "facing=west": { "model": "kubejs:block/qqqqoo", "y": 270 } } } ``` * ### 检查路径 `mtl`和`obj`指向`obj`模型的`json`文件放在`assets/${modid}/models/block`下\ `png`则放在`assets/${modid}/textures/block`下\ 确定`blockstate`的`json`文件需要放在`assets/${modid}/blockstate`下\ 同时要该文件命名都为方块注册名\ 注意导出模型在游戏里面有所偏移,在制作模型时修改即可 # 参考自 [\[我的世界 1.20.4 NeoForge 最新模组教程\]18 加载OBJ模型](https://www.bilibili.com/video/BV1jm421J7UR)\ [\[Boson 1.16 Modding Tutorial\] - Obj](https://boson.v2mcdev.com/specialmodel/obj.html) --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/Digression/SoundsJson.md --- # `sounds.json` Structure **Note: the following content is adapted from mcwiki.** ```json { "sound_event_id": { "replace": (true / false), "subtitle": "subtitle text", "sounds":[ { "name":"sound_path", "volume": (0.0 ~ 1.0), "pitch": 1.0, "weight": 1, "stream": (true / false), "attenuation_distance": 32, "preload": (true / false), "type": ("file" / "event") } ] } } ``` `sound_event_id`: A sound event. Sound event names are usually dot-separated by category (for example: `entity.enderman.stare`). If you want a namespace other than `minecraft`, place `sounds.json` under that namespace folder instead of defining the namespace here. `replace`: Optional. If set to `true`, the sound list in `sounds` replaces sound definitions for that event from lower-priority resource packs. If set to `false`, it appends to the existing list instead of replacing it. Default is `false`. `subtitle`: Optional. If "Show Subtitles" is enabled in-game, this string is translated and shown as subtitle text when the sound event plays. `sounds`: Optional. The list of sounds used by this event. When triggered, the game randomly selects one entry from this list. `name`: Path from `assets//sounds` to the sound file, or another sound event's namespaced ID. `volume`: Playback volume. Decimal from `0.0` to `1.0`. Default is `1.0`. `pitch`: Playback pitch. Default is `1.0`. Can be adjusted higher or lower. `weight`: Relative weight for this sound when the event triggers. Default is `1`. For example, setting `2` is equivalent to listing this sound twice. `stream`: If `true`, the sound is streamed. Recommended for long audio to avoid stutter. Most `music` and `record` sounds (except note block sounds) are streamed because they are long. Default is `false`. `attenuation_distance`: Distance-based attenuation distance for sound volume. Used by things like portals, beacons, and conduits. Default is `16`. `preload`: If `true`, the sound file is loaded when the resource pack loads, instead of at play time. Used for cases like underwater ambient sounds. Default is `false`. `type`: Optional, `file` or `event`. `file` means `name` is a file path; `event` means `name` is a sound event ID. Default is `file`. --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_01_Environment.md description: 01 环境配置 相关教程 --- # 环境配置 https://www.bilibili.com/read/cv31301021/ --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_01_Environment.md description: 01 环境配置 相关教程 --- # 环境配置 https://www.bilibili.com/read/cv31301021/ --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_02_FirstItemRegistry.md description: 02 第一个物品 相关教程 --- # 第一个物品 我们需要继承原版得物品类,实例化这个物品类,之后把这个物品类添加到游戏中去。 对于在注册表中的很多类型,这样的添加方式是类似的。 ```java public class RubyItem extends Item { public RubyItem(Properties pProperties) { super(pProperties); } } ``` 很简单只有一个构造函数,构造函数传入一个item的属性即可。 这里的属性规定了是不是食物,堆叠数量,等内容。你可以进入到该类查看。 之后我们实例化物品注册器和使用注册器注册物品 ```java public class ModItems { public static final DeferredRegister ITEMS = DeferredRegister.create(Registries.ITEM, ExampleMod.MODID); public static final Supplier RUBY = ITEMS.register("ruby",() -> new RubyItem(new Item.Properties())); public static void register(IEventBus eventBus){ ITEMS.register(eventBus); } } ``` 下面将我们的ITEMS注册器加入到总线中。 ```java public ExampleMod(IEventBus modEventBus) { modEventBus.addListener(this::commonSetup); ModItems.register(modEventBus); NeoForge.EVENT_BUS.register(this); } ``` 进入游戏即可看到我们添加的物品。这就是我们的第一个物品。 因为我们的红宝石item没有和Item有任何的区别,所有有更简单的一种方式注册物品 ```java public class ModItems { public static final DeferredRegister ITEMS = DeferredRegister.create(Registries.ITEM, ExampleMod.MODID); public static final Supplier RUBY = ITEMS.register("ruby",() -> new Item(new Item.Properties())); public static void register(IEventBus eventBus){ ITEMS.register(eventBus); } } ``` 下面我们对使用的一些类做一些介绍 ## Item 类 该 Item 类是Minecraft游戏中的一个核心类,它代表游戏中的物品。每个物品都有其独特的属性和行为,如耐久度、堆叠限制、使用效果等。此类实现了 FeatureElement、ItemLike 和自定义的 IItemExtension 接口,以提供更多的功能和扩展性。 ## Item.Properties类 Properties 是一个嵌套在 Item 类内部的静态类,用于配置和初始化物品的属性。它包含了一系列的设置方法,用于定义物品的最大堆叠数量、耐久度、合成剩余物品、稀有度等特性。 ## Registries类 此类定义了一个注册表,其中包含了Minecraft游戏中的各种资源和实体的资源键。每个资源键都代表了一个可以在游戏中注册和检索的不同类型。这些资源键用于访问和操作Minecraft中的各种元素,如方块、物品、实体、生物群系等。 ## Items类 Items 类是一个静态类,它包含了Minecraft中所有方块对应的物品。每个方块的物品都有一个静态的常量,用于在游戏中快速访问。 ## 模型和材质 在resources下构建这样的目录结构 ``` └───resources ├───assets │ └───examplemod │ ├───lang │ ├───models │ │ └───item │ └───textures │ └───item └───META-INF ``` assets是你放模组模型,材质的地方。 在item下面添加一个ruby.json,和你的注册的物品的名称相同。 ```json { "parent": "minecraft:item/generated", "textures": { "layer0": "examplemod:item/ruby" } } ``` "parent": "minecraft:item/generated”指定了这个模型的「父模型」是什么,而"layer0": "examplemod:item/ruby”指定了具体的材质。examplemod:item:代表这个是在我们自己的assets文件下,item/ruby代表了是textures/item/ruby.png这张图片。 详细的json描述参考wiki,你可以在包中找到游戏原版的json内容 textures/item/ruby.png 放置材质。最好不要大于32\*32像素。 启动游戏之后你就可以看见我们有了模型和材质的物品了。 ## 一个制作模型和材质的方便的工具 这里简单提了一下,之后会详细使用它自定义模型。 一个方便的工具用来制作方块和物品等模型:BlockBench。 ## Item和Item stack Itemstack 是 物品堆,背包物品槽位里里面放的每一个都是itemstack。 Itemstack是对Item的拓展,添加额外的属性,NBT标签等等,Item是不同的堆叠数量的内容的抽象,有共同的行为。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_02_FirstItemRegistry.md description: 02 第一个物品 相关教程 --- # 第一个物品 我们需要继承原版得物品类,实例化这个物品类,之后把这个物品类添加到游戏中去。 对于在注册表中的很多类型,这样的添加方式是类似的。 ```java public class RubyItem extends Item { public RubyItem(Properties pProperties) { super(pProperties); } } ``` 很简单只有一个构造函数,构造函数传入一个item的属性即可。 这里的属性规定了是不是食物,堆叠数量,等内容。你可以进入到该类查看。 之后我们实例化物品注册器和使用注册器注册物品 ```java public class ModItems { public static final DeferredRegister ITEMS = DeferredRegister.create(Registries.ITEM, ExampleMod.MODID); public static final Supplier RUBY = ITEMS.register("ruby",() -> new RubyItem(new Item.Properties())); public static void register(IEventBus eventBus){ ITEMS.register(eventBus); } } ``` 下面将我们的ITEMS注册器加入到总线中。 ```java public ExampleMod(IEventBus modEventBus) { modEventBus.addListener(this::commonSetup); ModItems.register(modEventBus); NeoForge.EVENT_BUS.register(this); } ``` 进入游戏即可看到我们添加的物品。这就是我们的第一个物品。 因为我们的红宝石item没有和Item有任何的区别,所有有更简单的一种方式注册物品 ```java public class ModItems { public static final DeferredRegister ITEMS = DeferredRegister.create(Registries.ITEM, ExampleMod.MODID); public static final Supplier RUBY = ITEMS.register("ruby",() -> new Item(new Item.Properties())); public static void register(IEventBus eventBus){ ITEMS.register(eventBus); } } ``` 下面我们对使用的一些类做一些介绍 ## Item 类 该 Item 类是Minecraft游戏中的一个核心类,它代表游戏中的物品。每个物品都有其独特的属性和行为,如耐久度、堆叠限制、使用效果等。此类实现了 FeatureElement、ItemLike 和自定义的 IItemExtension 接口,以提供更多的功能和扩展性。 ## Item.Properties类 Properties 是一个嵌套在 Item 类内部的静态类,用于配置和初始化物品的属性。它包含了一系列的设置方法,用于定义物品的最大堆叠数量、耐久度、合成剩余物品、稀有度等特性。 ## Registries类 此类定义了一个注册表,其中包含了Minecraft游戏中的各种资源和实体的资源键。每个资源键都代表了一个可以在游戏中注册和检索的不同类型。这些资源键用于访问和操作Minecraft中的各种元素,如方块、物品、实体、生物群系等。 ## Items类 Items 类是一个静态类,它包含了Minecraft中所有方块对应的物品。每个方块的物品都有一个静态的常量,用于在游戏中快速访问。 ## 模型和材质 在resources下构建这样的目录结构 ``` └───resources ├───assets │ └───examplemod │ ├───lang │ ├───models │ │ └───item │ └───textures │ └───item └───META-INF ``` assets是你放模组模型,材质的地方。 在item下面添加一个ruby.json,和你的注册的物品的名称相同。 ```json { "parent": "minecraft:item/generated", "textures": { "layer0": "examplemod:item/ruby" } } ``` "parent": "minecraft:item/generated”指定了这个模型的「父模型」是什么,而"layer0": "examplemod:item/ruby”指定了具体的材质。examplemod:item:代表这个是在我们自己的assets文件下,item/ruby代表了是textures/item/ruby.png这张图片。 详细的json描述参考wiki,你可以在包中找到游戏原版的json内容 textures/item/ruby.png 放置材质。最好不要大于32\*32像素。 启动游戏之后你就可以看见我们有了模型和材质的物品了。 ## 一个制作模型和材质的方便的工具 这里简单提了一下,之后会详细使用它自定义模型。 一个方便的工具用来制作方块和物品等模型:BlockBench。 ## Item和Item stack Itemstack 是 物品堆,背包物品槽位里里面放的每一个都是itemstack。 Itemstack是对Item的拓展,添加额外的属性,NBT标签等等,Item是不同的堆叠数量的内容的抽象,有共同的行为。 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_03_CreativeModTabs.md description: 03 创造模式物品栏 相关教程 --- # 自定义创造模式物品栏 现在添加一个创造物品栏和添加一个物品类似,同样你需要创建一个CreativeModeTab注册器,以使用注册器注册一个CreativeModeTab。然后添加的IEventBus总线中。 添加的包路径如下 ```java package net.flandre923.examplemod.item; public class ModCreativeTab { // 对应的注册器 public static final DeferredRegister CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, ExampleMod.MODID); // 这个string是鼠标移动到tab上的显示的内容。 public static final String EXAMPLE_MOD_TAB_STRING = "creativetab.example_tab"; // 添加一个tab,title标题,icon显示图标,displayItem是指tab中添加的内容,这里传入一个lammabd表达式,通过poutput添加物品,这里添加了我们的ruby public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab",() -> CreativeModeTab.builder() .withTabsBefore(CreativeModeTabs.COMBAT) .title(Component.translatable(EXAMPLE_MOD_TAB_STRING)) .icon(()->ModItems.RUBY.get().getDefaultInstance()) .displayItems((pParameters, pOutput) -> { pOutput.accept(ModItems.RUBY.get()); }) .build()); ``` ## 记得在总线上注册 ```java public static void register(IEventBus eventBus){ CREATIVE_MODE_TABS.register(eventBus); } } ``` 添加多个tab类似上面的步骤 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_03_CreativeModTabs.md description: 03 创造模式物品栏 相关教程 --- # 自定义创造模式物品栏 现在添加一个创造物品栏和添加一个物品类似,同样你需要创建一个CreativeModeTab注册器,以使用注册器注册一个CreativeModeTab。然后添加的IEventBus总线中。 添加的包路径如下 ```java package net.flandre923.examplemod.item; public class ModCreativeTab { // 对应的注册器 public static final DeferredRegister CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, ExampleMod.MODID); // 这个string是鼠标移动到tab上的显示的内容。 public static final String EXAMPLE_MOD_TAB_STRING = "creativetab.example_tab"; // 添加一个tab,title标题,icon显示图标,displayItem是指tab中添加的内容,这里传入一个lammabd表达式,通过poutput添加物品,这里添加了我们的ruby public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab",() -> CreativeModeTab.builder() .withTabsBefore(CreativeModeTabs.COMBAT) .title(Component.translatable(EXAMPLE_MOD_TAB_STRING)) .icon(()->ModItems.RUBY.get().getDefaultInstance()) .displayItems((pParameters, pOutput) -> { pOutput.accept(ModItems.RUBY.get()); }) .build()); ``` ## 记得在总线上注册 ```java public static void register(IEventBus eventBus){ CREATIVE_MODE_TABS.register(eventBus); } } ``` 添加多个tab类似上面的步骤 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_04_Register_Block.md description: 04 添加一个方块 相关教程 --- # 第一个方块 在Minecraft中,如果你想添加一个新的方块,你需要创建一个新的Java类来代表这个方块。这个类需要继承自Block类,并使用Properties类来设置方块的一些属性,如硬度、爆炸抗性等。在下面的代码中,我们创建了一个名为RubyBlock的类,这个类代表了一个新的方块,它的属性与石头相似,但硬度更高。 ```java package net.flandre923.examplemod.block.custom; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; public class RubyBlock extends Block { public RubyBlock() { super(Properties.ofFullCopy(Blocks.STONE).strength(5f)); } } ``` 接下来,你需要注册这个新的方块,这样Minecraft才知道它的存在。这通常是通过创建一个DeferredRegister对象来完成的,这个对象会帮助你将方块注册到Minecraft的注册表中。在下面的代码中,我们创建了一个名为ModBlocks的类,这个类包含了我们的RubyBlock方块的注册代码。 ```java package net.flandre923.examplemod.block; import net.flandre923.examplemod.ExampleMod; import net.flandre923.examplemod.block.custom.RubyBlock; import net.minecraft.core.registries.Registries; import net.minecraft.world.level.block.Block; import net.neoforged.bus.api.IEventBus; import net.neoforged.neoforge.registries.DeferredRegister; import java.util.function.Supplier; public class ModBlocks { public static final DeferredRegister BLOCKS = DeferredRegister.create(Registries.BLOCK, ExampleMod.MODID); public static final Supplier RUBY_BLOCK = BLOCKS.register("ruby_block", RubyBlock::new); public static void register(IEventBus eventBus){ BLOCKS.register(eventBus); } } ``` 然后,你需要为这个方块创建一个对应的物品形式,这样玩家才能在游戏中捡起和使用它。这通常是通过创建一个BlockItem对象来完成的,这个对象将方块和物品关联起来。在下面的代码中,我们创建了一个名为ModItems的类,这个类包含了我们的RubyBlock物品的注册代码。 ```java package net.flandre923.examplemod.item; import net.flandre923.examplemod.ExampleMod; import net.flandre923.examplemod.block.ModBlocks; import net.minecraft.core.registries.Registries; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; import net.neoforged.bus.api.IEventBus; import net.neoforged.neoforge.registries.DeferredRegister; import java.util.function.Supplier; public class ModItems { public static final DeferredRegister ITEMS = DeferredRegister.create(Registries.ITEM, ExampleMod.MODID); public static final Supplier RUBY = ITEMS.register("ruby",() -> new Item(new Item.Properties())); public static final Supplier RUBY_BLOCK = ITEMS.register("ruby_block",()->new BlockItem(ModBlocks.RUBY_BLOCK.get(), new Item.Properties())); public static void register(IEventBus eventBus){ ITEMS.register(eventBus); } } ``` 最后,你需要将这个物品添加到创造模式的物品栏中,这样玩家才能在游戏中找到它。 ```java package net.flandre923.examplemod.item; import net.flandre923.examplemod.ExampleMod; import net.flandre923.examplemod.block.ModBlocks; import net.minecraft.core.registries.Registries; import net.minecraft.data.PackOutput; import net.minecraft.network.chat.Component; import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.CreativeModeTabs; import net.neoforged.bus.api.IEventBus; import net.neoforged.neoforge.registries.DeferredRegister; import java.util.function.Supplier; public class ModCreativeTab { public static final DeferredRegister CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, ExampleMod.MODID); public static final String EXAMPLE_MOD_TAB_STRING = "creativetab.example_tab"; public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab",() -> CreativeModeTab.builder() .withTabsBefore(CreativeModeTabs.COMBAT) .title(Component.translatable(EXAMPLE_MOD_TAB_STRING)) .icon(()->ModItems.RUBY.get().getDefaultInstance()) .displayItems((pParameters, pOutput) -> { pOutput.accept(ModItems.RUBY.get()); pOutput.accept(ModItems.RUBY_BLOCK.get()); }) .build()); public static void register(IEventBus eventBus){ CREATIVE_MODE_TABS.register(eventBus); } } ``` 注册到总线 ```java public ExampleMod(IEventBus modEventBus) { modEventBus.addListener(this::commonSetup); ModItems.register(modEventBus); ModBlocks.register(modEventBus); ModCreativeTab.register(modEventBus); NeoForge.EVENT_BUS.register(this); } ``` ## Item和Block的区别 在Minecraft中,Item和Block是两个不同的概念。Item代表玩家可以捡起、放入背包、使用和丢弃的物品,如工具、食物和材料。Block代表游戏中固定在地面上的物体,如石头、木头和矿石。虽然Item和Block是不同的对象,但它们之间有紧密的联系。例如,当玩家破坏一个方块时,它通常会掉落一个对应的物品形式。同样,当玩家将一个物品放入世界中时,它通常会变成一个对应的方块形式。 ## Block 和Block State Block是Minecraft中所有实体的基类,代表了一个基本的、不可再分的地形单元。每个Block都有其独特的属性和行为,如硬度、爆炸抗性、是否能被点燃等。在代码中,每个方块都由一个Block类的实例表示。当你创建一个新的方块时,你需要继承Block类并覆写一些方法来定义你的方块的行为。 例如,你可以通过覆写randomTick方法来让方块有几率在游戏世界中产生随机变化,或者通过覆写onPlace方法来定义方块被放置时的行为。 Block State指的是一个方块在其生命周期中的具体状态。由于Minecraft中的大多数方块都有多种形态,如木头的方向、石头的类型等,因此需要一个机制来存储和检索这些信息。Block State就是用来描述这些不同形态的。 每个Block都可以有多个Block State,每个Block State都有一个唯一的标识符和一组属性。属性是Block State的组成部分,它们定义了方块的外观和行为。例如,一个木台阶可以有六个不同的方向,每个方向都是一个不同的Block State。 在代码中,Block State由BlockState类表示,你可以通过这个类的实例来获取和设置方块的状态。例如,你可以通过调用getBlockState().with(Properties.HORIZONTAL\_FACING, Direction.NORTH)来设置一个方块面向北方。 ## 方块模型和材质 建立这样的目录结构 ``` resources ├───assets │ └───examplemod │ ├───blockstates │ ├───lang │ ├───models │ │ ├───block │ │ └───item │ └───textures │ ├───block │ └───item ``` 这段代码是JSON格式的数据,用于告诉Minecraft游戏,当渲染名为ruby\_block的方块时,应该使用哪个模型文件。在这里,”model”: “examplemod:block/ruby\_block\_model”指定了模型的路径,examplemod是模组的名称,block/ruby\_block\_model是模型文件相对于模组资源文件夹的路径。这个模型文件将定义方块的外观和结构。 ```json { "variants": { "": { "model": "examplemod:block/ruby_block_model" } } } ``` 这段代码同样使用JSON格式,它定义了一个模型文件的内容。”parent”: “block/cube\_all”指定了这个模型是基于Minecraft内置的cube\_all模型,这意味着它是一个简单的立方体。”textures”: {“all”: “examplemod:block/ruby\_block”}定义了立方体六个面使用的纹理。这里,”all”表示六个面使用同一个纹理,纹理的路径是examplemod:block/ruby\_block。 ```json { "parent": "block/cube_all", "textures": { "all": "examplemod:block/ruby_block" } } ``` 这段代码用于定义物品的模型。在Minecraft中,物品和方块通常使用相同的模型。”parent”: “examplemod:block/ruby\_block”表明物品模型将使用与ruby\_block方块相同的模型。这意味着,当你在创造模式物品栏或者手中拿着这个物品时,它会看起来和它在世界中作为方块时一样。 ```json { "parent": "examplemod:block/ruby_block" } ``` ruby\_block.png是一个纹理文件,通常是一个图片文件,它包含了方块的表面图案。在Minecraft中,纹理文件被用于给方块和物品提供详细的外观。这个文件应该放在模组的资源文件夹中对应于纹理路径examplemod:block/ruby\_block的位置。纹理文件通常包含多个Minecraft方块的图案,每个方块的一部分通常被安排在一个大图中,通过纹理坐标来指定使用大图中的哪一部分。 ruby\_block.png 下面我对相关的类做一个简单的介绍详细的内容可以自己查看 ## Block类 Block类是创建和定义新方块的地方 ## Block Behaviour Properties 类 BlockBehaviourProperties类用于设置方块的行为属性 ## Blocks 类 Blocks类则是一个方便的仓库,用于访问游戏中的所有方块。 ## 简化我们注册Block的步骤 简化在Minecraft模组中注册方块和对应物品的过程。通过将这些步骤封装在方法中,更容易地添加新的方块,并确保每个方块都有一个对应的物品形式。 ```java public static final Supplier RUBY_BLOCK = registerBlock("ruby_block", RubyBlock::new); public static Supplier registerBlock(String name,Supplier block){ Supplier toReturn = BLOCKS.register(name, block); registerBlockItem(name,toReturn); return toReturn; } public static void registerBlockItem(String name, Supplier block){ registerBlockItem(name, block, new Item.Properties()); } public static void registerBlockItem(String name, Supplier block, Item.Properties properties){ ITEMS.register(name, () -> new BlockItem(block.get(), properties)); } ``` 现在创造模式物品栏中可以直接添加方块,以为方块实现了asitem这个接口 ```java public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab",() -> CreativeModeTab.builder() .withTabsBefore(CreativeModeTabs.COMBAT) .title(Component.translatable(EXAMPLE_MOD_TAB_STRING)) .icon(()->ModItems.RUBY.get().getDefaultInstance()) .displayItems((pParameters, pOutput) -> { pOutput.accept(ModItems.RUBY.get()); pOutput.accept(ModBlocks.RUBY_BLOCK.get()); }) .build()); ``` 这样做简化Minecraft模组中方块和物品的注册过程。减少了重复代码. --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_04_Register_Block.md description: 04 添加一个方块 相关教程 --- # 第一个方块 在Minecraft中,如果你想添加一个新的方块,你需要创建一个新的Java类来代表这个方块。这个类需要继承自Block类,并使用Properties类来设置方块的一些属性,如硬度、爆炸抗性等。在下面的代码中,我们创建了一个名为RubyBlock的类,这个类代表了一个新的方块,它的属性与石头相似,但硬度更高。 ```java package net.flandre923.examplemod.block.custom; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; public class RubyBlock extends Block { public RubyBlock() { super(Properties.ofFullCopy(Blocks.STONE).strength(5f)); } } ``` 接下来,你需要注册这个新的方块,这样Minecraft才知道它的存在。这通常是通过创建一个DeferredRegister对象来完成的,这个对象会帮助你将方块注册到Minecraft的注册表中。在下面的代码中,我们创建了一个名为ModBlocks的类,这个类包含了我们的RubyBlock方块的注册代码。 ```java package net.flandre923.examplemod.block; import net.flandre923.examplemod.ExampleMod; import net.flandre923.examplemod.block.custom.RubyBlock; import net.minecraft.core.registries.Registries; import net.minecraft.world.level.block.Block; import net.neoforged.bus.api.IEventBus; import net.neoforged.neoforge.registries.DeferredRegister; import java.util.function.Supplier; public class ModBlocks { public static final DeferredRegister BLOCKS = DeferredRegister.create(Registries.BLOCK, ExampleMod.MODID); public static final Supplier RUBY_BLOCK = BLOCKS.register("ruby_block", RubyBlock::new); public static void register(IEventBus eventBus){ BLOCKS.register(eventBus); } } ``` 然后,你需要为这个方块创建一个对应的物品形式,这样玩家才能在游戏中捡起和使用它。这通常是通过创建一个BlockItem对象来完成的,这个对象将方块和物品关联起来。在下面的代码中,我们创建了一个名为ModItems的类,这个类包含了我们的RubyBlock物品的注册代码。 ```java package net.flandre923.examplemod.item; import net.flandre923.examplemod.ExampleMod; import net.flandre923.examplemod.block.ModBlocks; import net.minecraft.core.registries.Registries; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; import net.neoforged.bus.api.IEventBus; import net.neoforged.neoforge.registries.DeferredRegister; import java.util.function.Supplier; public class ModItems { public static final DeferredRegister ITEMS = DeferredRegister.create(Registries.ITEM, ExampleMod.MODID); public static final Supplier RUBY = ITEMS.register("ruby",() -> new Item(new Item.Properties())); public static final Supplier RUBY_BLOCK = ITEMS.register("ruby_block",()->new BlockItem(ModBlocks.RUBY_BLOCK.get(), new Item.Properties())); public static void register(IEventBus eventBus){ ITEMS.register(eventBus); } } ``` 最后,你需要将这个物品添加到创造模式的物品栏中,这样玩家才能在游戏中找到它。 ```java package net.flandre923.examplemod.item; import net.flandre923.examplemod.ExampleMod; import net.flandre923.examplemod.block.ModBlocks; import net.minecraft.core.registries.Registries; import net.minecraft.data.PackOutput; import net.minecraft.network.chat.Component; import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.CreativeModeTabs; import net.neoforged.bus.api.IEventBus; import net.neoforged.neoforge.registries.DeferredRegister; import java.util.function.Supplier; public class ModCreativeTab { public static final DeferredRegister CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, ExampleMod.MODID); public static final String EXAMPLE_MOD_TAB_STRING = "creativetab.example_tab"; public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab",() -> CreativeModeTab.builder() .withTabsBefore(CreativeModeTabs.COMBAT) .title(Component.translatable(EXAMPLE_MOD_TAB_STRING)) .icon(()->ModItems.RUBY.get().getDefaultInstance()) .displayItems((pParameters, pOutput) -> { pOutput.accept(ModItems.RUBY.get()); pOutput.accept(ModItems.RUBY_BLOCK.get()); }) .build()); public static void register(IEventBus eventBus){ CREATIVE_MODE_TABS.register(eventBus); } } ``` 注册到总线 ```java public ExampleMod(IEventBus modEventBus) { modEventBus.addListener(this::commonSetup); ModItems.register(modEventBus); ModBlocks.register(modEventBus); ModCreativeTab.register(modEventBus); NeoForge.EVENT_BUS.register(this); } ``` ## Item和Block的区别 在Minecraft中,Item和Block是两个不同的概念。Item代表玩家可以捡起、放入背包、使用和丢弃的物品,如工具、食物和材料。Block代表游戏中固定在地面上的物体,如石头、木头和矿石。虽然Item和Block是不同的对象,但它们之间有紧密的联系。例如,当玩家破坏一个方块时,它通常会掉落一个对应的物品形式。同样,当玩家将一个物品放入世界中时,它通常会变成一个对应的方块形式。 ## Block 和Block State Block是Minecraft中所有实体的基类,代表了一个基本的、不可再分的地形单元。每个Block都有其独特的属性和行为,如硬度、爆炸抗性、是否能被点燃等。在代码中,每个方块都由一个Block类的实例表示。当你创建一个新的方块时,你需要继承Block类并覆写一些方法来定义你的方块的行为。 例如,你可以通过覆写randomTick方法来让方块有几率在游戏世界中产生随机变化,或者通过覆写onPlace方法来定义方块被放置时的行为。 Block State指的是一个方块在其生命周期中的具体状态。由于Minecraft中的大多数方块都有多种形态,如木头的方向、石头的类型等,因此需要一个机制来存储和检索这些信息。Block State就是用来描述这些不同形态的。 每个Block都可以有多个Block State,每个Block State都有一个唯一的标识符和一组属性。属性是Block State的组成部分,它们定义了方块的外观和行为。例如,一个木台阶可以有六个不同的方向,每个方向都是一个不同的Block State。 在代码中,Block State由BlockState类表示,你可以通过这个类的实例来获取和设置方块的状态。例如,你可以通过调用getBlockState().with(Properties.HORIZONTAL\_FACING, Direction.NORTH)来设置一个方块面向北方。 ## 方块模型和材质 建立这样的目录结构 ``` resources ├───assets │ └───examplemod │ ├───blockstates │ ├───lang │ ├───models │ │ ├───block │ │ └───item │ └───textures │ ├───block │ └───item ``` 这段代码是JSON格式的数据,用于告诉Minecraft游戏,当渲染名为ruby\_block的方块时,应该使用哪个模型文件。在这里,”model”: “examplemod:block/ruby\_block\_model”指定了模型的路径,examplemod是模组的名称,block/ruby\_block\_model是模型文件相对于模组资源文件夹的路径。这个模型文件将定义方块的外观和结构。 ```json { "variants": { "": { "model": "examplemod:block/ruby_block_model" } } } ``` 这段代码同样使用JSON格式,它定义了一个模型文件的内容。”parent”: “block/cube\_all”指定了这个模型是基于Minecraft内置的cube\_all模型,这意味着它是一个简单的立方体。”textures”: {“all”: “examplemod:block/ruby\_block”}定义了立方体六个面使用的纹理。这里,”all”表示六个面使用同一个纹理,纹理的路径是examplemod:block/ruby\_block。 ```json { "parent": "block/cube_all", "textures": { "all": "examplemod:block/ruby_block" } } ``` 这段代码用于定义物品的模型。在Minecraft中,物品和方块通常使用相同的模型。”parent”: “examplemod:block/ruby\_block”表明物品模型将使用与ruby\_block方块相同的模型。这意味着,当你在创造模式物品栏或者手中拿着这个物品时,它会看起来和它在世界中作为方块时一样。 ```json { "parent": "examplemod:block/ruby_block" } ``` ruby\_block.png是一个纹理文件,通常是一个图片文件,它包含了方块的表面图案。在Minecraft中,纹理文件被用于给方块和物品提供详细的外观。这个文件应该放在模组的资源文件夹中对应于纹理路径examplemod:block/ruby\_block的位置。纹理文件通常包含多个Minecraft方块的图案,每个方块的一部分通常被安排在一个大图中,通过纹理坐标来指定使用大图中的哪一部分。 ruby\_block.png 下面我对相关的类做一个简单的介绍详细的内容可以自己查看 ## Block类 Block类是创建和定义新方块的地方 ## Block Behaviour Properties 类 BlockBehaviourProperties类用于设置方块的行为属性 ## Blocks 类 Blocks类则是一个方便的仓库,用于访问游戏中的所有方块。 ## 简化我们注册Block的步骤 简化在Minecraft模组中注册方块和对应物品的过程。通过将这些步骤封装在方法中,更容易地添加新的方块,并确保每个方块都有一个对应的物品形式。 ```java public static final Supplier RUBY_BLOCK = registerBlock("ruby_block", RubyBlock::new); public static Supplier registerBlock(String name,Supplier block){ Supplier toReturn = BLOCKS.register(name, block); registerBlockItem(name,toReturn); return toReturn; } public static void registerBlockItem(String name, Supplier block){ registerBlockItem(name, block, new Item.Properties()); } public static void registerBlockItem(String name, Supplier block, Item.Properties properties){ ITEMS.register(name, () -> new BlockItem(block.get(), properties)); } ``` 现在创造模式物品栏中可以直接添加方块,以为方块实现了asitem这个接口 ```java public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab",() -> CreativeModeTab.builder() .withTabsBefore(CreativeModeTabs.COMBAT) .title(Component.translatable(EXAMPLE_MOD_TAB_STRING)) .icon(()->ModItems.RUBY.get().getDefaultInstance()) .displayItems((pParameters, pOutput) -> { pOutput.accept(ModItems.RUBY.get()); pOutput.accept(ModBlocks.RUBY_BLOCK.get()); }) .build()); ``` 这样做简化Minecraft模组中方块和物品的注册过程。减少了重复代码. --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_05_BlockState.md description: 05 方块状态 相关教程 --- # 方块状态 ```java package net.flandre923.examplemod.block.custom; import net.minecraft.core.BlockPos; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.BooleanProperty; import net.minecraft.world.phys.BlockHitResult; public class LampBlock extends Block { public static final BooleanProperty LIT = BooleanProperty.create("lit"); public LampBlock(Properties p_49795_) { super(p_49795_); this.registerDefaultState(this.defaultBlockState().setValue(LIT,false)); } @Override public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) { if(!pLevel.isClientSide && pHand == InteractionHand.MAIN_HAND){ pLevel.setBlock(pPos,pState.cycle(LIT),3); } return super.use(pState,pLevel,pPos,pPlayer,pHand,pHit); } @Override protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { pBuilder.add(LIT); } } ``` 在这里,我们创建了一个名为LIT的布尔属性,用于表示方块是否被点亮。这个属性将会被添加到方块的BlockState中,用于存储和检查方块是否被点亮的状态。 ```java public static final BooleanProperty LIT = BooleanProperty.create("lit"); ``` 这个方法用于定义方块的状态。在这个方法中,我们将LIT属性添加到方块的状态定义中。这样,方块的状态就可以包含LIT属性,用于存储和检查方块是否被点亮的状态。 ```java @Override protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { pBuilder.add(LIT); } ``` 在构造器中,我们首先调用父类的构造器,然后使用registerDefaultState方法设置方块的默认状态。在这里,我们将LIT属性设置为false,表示方块默认是不被点亮的。这样,每次创建方块时,都会使用这个默认状态。 ```java public LampBlock(Properties p_49795_) { super(p_49795_); this.registerDefaultState(this.defaultBlockState().setValue(LIT,false)); } ``` ## 注册方块和物品 ```java public static final Supplier LAMP_BLOCK = registerBlock("lamp_block",()->new LampBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE).strength(6f).requiresCorrectToolForDrops() .lightLevel(state->state.getValue(LampBlock.LIT)?15:0))); ``` ## 添加到物品栏 ```java public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab",() -> CreativeModeTab.builder() .withTabsBefore(CreativeModeTabs.COMBAT) .title(Component.translatable(EXAMPLE_MOD_TAB_STRING)) .icon(()->ModItems.RUBY.get().getDefaultInstance()) .displayItems((pParameters, pOutput) -> { pOutput.accept(ModItems.RUBY.get()); pOutput.accept(ModItems.MAGIC_INGOT.get()); pOutput.accept(ModBlocks.RUBY_BLOCK.get()); pOutput.accept(ModBlocks.LAMP_BLOCK.get()); }) .build()); ``` ## 添加blockstate的json lamp\_block.json ```java { "variants": { "lit=false": { "model": "examplemod:block/lamp_off" }, "lit=true": { "model": "examplemod:block/lamp_on" } } } ``` ## 添加model lamp\_off.json ```json { "parent": "minecraft:block/cube_all", "textures": { "all": "examplemod:block/zircon_lamp_off" } } ``` ```json { "parent": "minecraft:block/cube_all", "textures": { "all": "examplemod:block/zircon_lamp_on" } } ``` ## 添加贴图 这里用了两张原版的贴图 ![alt text](zircon_lamp_off.png) ![alt text](zircon_lamp_on.png) ## 更新方块中的 flag 位 ```java /** * Sets a block state into this world.Flags are as follows: * 1 will notify neighboring blocks through {@link net.minecraft.world.level.block.state.BlockBehaviour$BlockStateBase#neighborChanged neighborChanged} updates. * 2 will send the change to clients. * 4 will prevent the block from being re-rendered. * 8 will force any re-renders to run on the main thread instead * 16 will prevent neighbor reactions (e.g. fences connecting, observers pulsing). * 32 will prevent neighbor reactions from spawning drops. * 64 will signify the block is being moved. * Flags can be OR-ed */ @Override public boolean setBlock(BlockPos pPos, BlockState pNewState, int pFlags) { return this.setBlock(pPos, pNewState, pFlags, 512); } ``` 注释中提到的标志(flags)可以通过位运算(OR操作,使用|符号)来组合使用,以便根据需要启用多种行为。以下是各个标志的含义: 1:通知相邻的方块通过neighborChanged更新。这意味着当方块状态改变时,周围的方块将会收到一个通知,可能会触发一些相邻方块的更新逻辑,例如红石电路的变化。 2:将更改发送给客户端。在多人游戏中,这个标志用于确保所有玩家都能看到方块状态的变化。 4:阻止方块重新渲染。如果设置了此标志,方块的视觉表现不会立即更新,这对于性能优化可能很有用。 8:强制任何重新渲染在主线程上运行。这通常用于确保渲染操作在正确的线程上执行,以避免潜在的线程安全问题。 16:阻止相邻方块反应。这意味着方块的更新不会触发相邻方块的任何反应,例如观察者方块的脉冲。 32:阻止相邻方块反应产生掉落物。如果设置了此标志,即使方块因为邻居的更新而被破坏,也不会产生掉落物。 64:表示方块正在被移动。这可能用于处理方块移动的逻辑,例如活塞推动方块。 通过将这些标志组合使用,开发者可以精确控制方块状态变化时的行为,以满足特定的游戏逻辑需求。例如,如果只是想在服务器内部更改方块状态而不需要通知客户端或触发其他更新,可以不设置任何标志。如果需要确保客户端看到变化并允许正常的邻居更新,可以设置2和1标志 ## 原版的带状态的方块 RedstoneLampBlock 原版的红石灯方块。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_05_BlockState.md description: 05 方块状态 相关教程 --- # 方块状态 ```java package net.flandre923.examplemod.block.custom; import net.minecraft.core.BlockPos; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.BooleanProperty; import net.minecraft.world.phys.BlockHitResult; public class LampBlock extends Block { public static final BooleanProperty LIT = BooleanProperty.create("lit"); public LampBlock(Properties p_49795_) { super(p_49795_); this.registerDefaultState(this.defaultBlockState().setValue(LIT,false)); } @Override public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) { if(!pLevel.isClientSide && pHand == InteractionHand.MAIN_HAND){ pLevel.setBlock(pPos,pState.cycle(LIT),3); } return super.use(pState,pLevel,pPos,pPlayer,pHand,pHit); } @Override protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { pBuilder.add(LIT); } } ``` 在这里,我们创建了一个名为LIT的布尔属性,用于表示方块是否被点亮。这个属性将会被添加到方块的BlockState中,用于存储和检查方块是否被点亮的状态。 ```java public static final BooleanProperty LIT = BooleanProperty.create("lit"); ``` 这个方法用于定义方块的状态。在这个方法中,我们将LIT属性添加到方块的状态定义中。这样,方块的状态就可以包含LIT属性,用于存储和检查方块是否被点亮的状态。 ```java @Override protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { pBuilder.add(LIT); } ``` 在构造器中,我们首先调用父类的构造器,然后使用registerDefaultState方法设置方块的默认状态。在这里,我们将LIT属性设置为false,表示方块默认是不被点亮的。这样,每次创建方块时,都会使用这个默认状态。 ```java public LampBlock(Properties p_49795_) { super(p_49795_); this.registerDefaultState(this.defaultBlockState().setValue(LIT,false)); } ``` ## 注册方块和物品 ```java public static final Supplier LAMP_BLOCK = registerBlock("lamp_block",()->new LampBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE).strength(6f).requiresCorrectToolForDrops() .lightLevel(state->state.getValue(LampBlock.LIT)?15:0))); ``` ## 添加到物品栏 ```java public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab",() -> CreativeModeTab.builder() .withTabsBefore(CreativeModeTabs.COMBAT) .title(Component.translatable(EXAMPLE_MOD_TAB_STRING)) .icon(()->ModItems.RUBY.get().getDefaultInstance()) .displayItems((pParameters, pOutput) -> { pOutput.accept(ModItems.RUBY.get()); pOutput.accept(ModItems.MAGIC_INGOT.get()); pOutput.accept(ModBlocks.RUBY_BLOCK.get()); pOutput.accept(ModBlocks.LAMP_BLOCK.get()); }) .build()); ``` ## 添加blockstate的json lamp\_block.json ```java { "variants": { "lit=false": { "model": "examplemod:block/lamp_off" }, "lit=true": { "model": "examplemod:block/lamp_on" } } } ``` ## 添加model lamp\_off.json ```json { "parent": "minecraft:block/cube_all", "textures": { "all": "examplemod:block/zircon_lamp_off" } } ``` ```json { "parent": "minecraft:block/cube_all", "textures": { "all": "examplemod:block/zircon_lamp_on" } } ``` ## 添加贴图 这里用了两张原版的贴图 ![alt text](zircon_lamp_off.png) ![alt text](zircon_lamp_on.png) ## 更新方块中的 flag 位 ```java /** * Sets a block state into this world.Flags are as follows: * 1 will notify neighboring blocks through {@link net.minecraft.world.level.block.state.BlockBehaviour$BlockStateBase#neighborChanged neighborChanged} updates. * 2 will send the change to clients. * 4 will prevent the block from being re-rendered. * 8 will force any re-renders to run on the main thread instead * 16 will prevent neighbor reactions (e.g. fences connecting, observers pulsing). * 32 will prevent neighbor reactions from spawning drops. * 64 will signify the block is being moved. * Flags can be OR-ed */ @Override public boolean setBlock(BlockPos pPos, BlockState pNewState, int pFlags) { return this.setBlock(pPos, pNewState, pFlags, 512); } ``` 注释中提到的标志(flags)可以通过位运算(OR操作,使用|符号)来组合使用,以便根据需要启用多种行为。以下是各个标志的含义: 1:通知相邻的方块通过neighborChanged更新。这意味着当方块状态改变时,周围的方块将会收到一个通知,可能会触发一些相邻方块的更新逻辑,例如红石电路的变化。 2:将更改发送给客户端。在多人游戏中,这个标志用于确保所有玩家都能看到方块状态的变化。 4:阻止方块重新渲染。如果设置了此标志,方块的视觉表现不会立即更新,这对于性能优化可能很有用。 8:强制任何重新渲染在主线程上运行。这通常用于确保渲染操作在正确的线程上执行,以避免潜在的线程安全问题。 16:阻止相邻方块反应。这意味着方块的更新不会触发相邻方块的任何反应,例如观察者方块的脉冲。 32:阻止相邻方块反应产生掉落物。如果设置了此标志,即使方块因为邻居的更新而被破坏,也不会产生掉落物。 64:表示方块正在被移动。这可能用于处理方块移动的逻辑,例如活塞推动方块。 通过将这些标志组合使用,开发者可以精确控制方块状态变化时的行为,以满足特定的游戏逻辑需求。例如,如果只是想在服务器内部更改方块状态而不需要通知客户端或触发其他更新,可以不设置任何标志。如果需要确保客户端看到变化并允许正常的邻居更新,可以设置2和1标志 ## 原版的带状态的方块 RedstoneLampBlock 原版的红石灯方块。 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_06_ItemAttributeOveride.md description: 06 物品属性覆盖 相关教程 --- # 物品属性覆盖 在Minecraft中,物品模型和属性可以通过JSON文件进行自定义。物品属性覆盖是一种机制,允许开发者根据物品的不同状态或条件来改变其模型或属性。例如,原版的弓在不同的拉动状态下会显示不同的模型。 例如原版的弓 bow.json ```json { ……省略内容…… "overrides": [ { "predicate": { "pulling": 1 }, "model": "item/bow_pulling_0" }, { "predicate": { "pulling": 1, "pull": 0.65 }, "model": "item/bow_pulling_1" }, { "predicate": { "pulling": 1, "pull": 0.9 }, "model": "item/bow_pulling_2" } ] } ``` * overrides: 这是一个数组,用于列出所有的属性覆盖。每个元素都是一个对象,定义了一组条件和一个或多个要应用的属性更改。 * predicate: 这是一个对象,用于定义触发属性覆盖的条件。它包含了一组键值对,每个键都是一个属性名,值是该属性应满足的条件。 * pulling: 这是一个布尔值属性,用于判断弓是否正在被拉动。在上述例子中,pulling: 1 表示弓正在被拉动。 * pull: 这是一个0到1之间的数值属性,表示弓被拉动的程度。值越接近1,表示弓被拉得越满。 * model: 这是一个字符串属性,指定了当满足predicate中定义的条件时,应该使用哪个模型文件。模型文件通常位于assets//models/.json。 ## 添加item ```java public class MagicIngot extends Item { public MagicIngot() { super(new Properties()); } } ``` 在上面的Java代码中,我们创建了一个名为MagicIngot的新物品类。 ## 注册item ```java public static final Supplier MAGIC_INGOT = ITEMS.register("magic_ingot", MagicIngot::new); ``` ## 添加到物品栏 ```java public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab",() -> CreativeModeTab.builder() .withTabsBefore(CreativeModeTabs.COMBAT) .title(Component.translatable(EXAMPLE_MOD_TAB_STRING)) .icon(()->ModItems.RUBY.get().getDefaultInstance()) .displayItems((pParameters, pOutput) -> { pOutput.accept(ModItems.RUBY.get()); pOutput.accept(ModItems.MAGIC_INGOT.get()); pOutput.accept(ModBlocks.RUBY_BLOCK.get()); pOutput.accept(ModBlocks.LAMP_BLOCK.get()); }) .build()); ``` ## 添加物品属性覆盖 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class PropertyRegistry { @SubscribeEvent public static void propertyOverrideRegistry(FMLClientSetupEvent event){ event.enqueueWork(()->{ ItemProperties.register(ModItems.MAGIC_INGOT.get(),new ResourceLocation(ExampleMod.MODID,"size"),(itemStack,level,livingEntity,num)->{ return itemStack.getCount(); }); }); } } ``` 在Neoforge开发中,ItemProperties.register 方法用于注册一个自定义的物品属性。这个方法允许开发者根据特定的条件动态地改变物品的属性,这些属性可以用于模型的不同渲染或者其他逻辑。 第三个参数要求传入一个lammbd的表达式,参数itemstack,clientlevel,livingentity,和int数值。 上述代码中,我们为MAGIC\_INGOT物品注册了一个名为size的新属性。这个属性的值由一个lambda表达式定义,它简单地返回了itemStack.getCount(),即物品堆叠的数量。这意味着size属性将反映物品堆叠的数量。 这个自定义属性可以在物品的模型文件中引用,并用于根据物品的数量来改变物品的渲染。例如,可以创建不同的模型文件来表示不同数量的物品,然后在模型文件中使用属性覆盖来根据size属性的值选择不同的模型。 ## 添加json 这里是堆叠的数量大于等于16时候返回金锭,小于16时候返回铁定 ```json { "parent": "item/generated", "textures": { "layer0": "item/iron_ingot" }, "overrides": [ { "predicate": { "examplemod:size": 16 }, "model": "item/gold_ingot" } ] } ``` ## 参考 https://boson.v2mcdev.com/item/item-property-override.html --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_06_ItemAttributeOveride.md description: 06 物品属性覆盖 相关教程 --- # 物品属性覆盖 在Minecraft中,物品模型和属性可以通过JSON文件进行自定义。物品属性覆盖是一种机制,允许开发者根据物品的不同状态或条件来改变其模型或属性。例如,原版的弓在不同的拉动状态下会显示不同的模型。 例如原版的弓 bow.json ```json { ……省略内容…… "overrides": [ { "predicate": { "pulling": 1 }, "model": "item/bow_pulling_0" }, { "predicate": { "pulling": 1, "pull": 0.65 }, "model": "item/bow_pulling_1" }, { "predicate": { "pulling": 1, "pull": 0.9 }, "model": "item/bow_pulling_2" } ] } ``` * overrides: 这是一个数组,用于列出所有的属性覆盖。每个元素都是一个对象,定义了一组条件和一个或多个要应用的属性更改。 * predicate: 这是一个对象,用于定义触发属性覆盖的条件。它包含了一组键值对,每个键都是一个属性名,值是该属性应满足的条件。 * pulling: 这是一个布尔值属性,用于判断弓是否正在被拉动。在上述例子中,pulling: 1 表示弓正在被拉动。 * pull: 这是一个0到1之间的数值属性,表示弓被拉动的程度。值越接近1,表示弓被拉得越满。 * model: 这是一个字符串属性,指定了当满足predicate中定义的条件时,应该使用哪个模型文件。模型文件通常位于assets//models/.json。 ## 添加item ```java public class MagicIngot extends Item { public MagicIngot() { super(new Properties()); } } ``` 在上面的Java代码中,我们创建了一个名为MagicIngot的新物品类。 ## 注册item ```java public static final Supplier MAGIC_INGOT = ITEMS.register("magic_ingot", MagicIngot::new); ``` ## 添加到物品栏 ```java public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab",() -> CreativeModeTab.builder() .withTabsBefore(CreativeModeTabs.COMBAT) .title(Component.translatable(EXAMPLE_MOD_TAB_STRING)) .icon(()->ModItems.RUBY.get().getDefaultInstance()) .displayItems((pParameters, pOutput) -> { pOutput.accept(ModItems.RUBY.get()); pOutput.accept(ModItems.MAGIC_INGOT.get()); pOutput.accept(ModBlocks.RUBY_BLOCK.get()); pOutput.accept(ModBlocks.LAMP_BLOCK.get()); }) .build()); ``` ## 添加物品属性覆盖 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class PropertyRegistry { @SubscribeEvent public static void propertyOverrideRegistry(FMLClientSetupEvent event){ event.enqueueWork(()->{ ItemProperties.register(ModItems.MAGIC_INGOT.get(),new ResourceLocation(ExampleMod.MODID,"size"),(itemStack,level,livingEntity,num)->{ return itemStack.getCount(); }); }); } } ``` 在Neoforge开发中,ItemProperties.register 方法用于注册一个自定义的物品属性。这个方法允许开发者根据特定的条件动态地改变物品的属性,这些属性可以用于模型的不同渲染或者其他逻辑。 第三个参数要求传入一个lammbd的表达式,参数itemstack,clientlevel,livingentity,和int数值。 上述代码中,我们为MAGIC\_INGOT物品注册了一个名为size的新属性。这个属性的值由一个lambda表达式定义,它简单地返回了itemStack.getCount(),即物品堆叠的数量。这意味着size属性将反映物品堆叠的数量。 这个自定义属性可以在物品的模型文件中引用,并用于根据物品的数量来改变物品的渲染。例如,可以创建不同的模型文件来表示不同数量的物品,然后在模型文件中使用属性覆盖来根据size属性的值选择不同的模型。 ## 添加json 这里是堆叠的数量大于等于16时候返回金锭,小于16时候返回铁定 ```json { "parent": "item/generated", "textures": { "layer0": "item/iron_ingot" }, "overrides": [ { "predicate": { "examplemod:size": 16 }, "model": "item/gold_ingot" } ] } ``` ## 参考 https://boson.v2mcdev.com/item/item-property-override.html --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_07_Datagen.md description: 07 数据生成 相关教程 --- # Data Generators 数据生成器是一种以编程方式生成模组资源和数据的方法。它允许在代码中定义这些文件的内容并自动生成它们,而无需担心细节。 ## 语言文件 ```java import net.flandre923.examplemod.block.ModBlocks; import net.flandre923.examplemod.item.ModItems; import net.minecraft.data.PackOutput; import net.neoforged.neoforge.common.data.LanguageProvider; public class ModLanguageProvider extends LanguageProvider { public ModLanguageProvider(PackOutput output, String modid, String locale) { super(output, modid, locale); } @Override protected void addTranslations() { this.add(ModItems.RUBY.get(),"Ruby"); this.add(ModBlocks.RUBY_BLOCK.get(),"Ruby Block"); this.add("object.examplemod.example_object","Example Object"); } } ``` LanguageProvider 是一个类,用于生成和修改Minecraft游戏中的语言文件。语言文件包含了游戏中所有的文本内容,如物品名称、方块名称、提示信息等。通过扩展 LanguageProvider 类,开发者可以自定义自己的语言文件内容。 addTranslations 是 LanguageProvider 类中的一个方法。在自定义的 LanguageProvider 子类中,你需要重写(override)这个方法来添加你想要的语言翻译。这个方法在数据生成过程中被调用,用于生成语言文件。 这行代码的作用是将游戏内的一个物品(这里是指 ModItems.RUBY)与一个字符串(“Ruby”)相关联。在生成的语言文件中,ModItems.RUBY 将会被翻译为 “Ruby”。这样,当玩家在游戏中看到这个物品时,它的名称将会显示为 “Ruby”。 与上一条类似,这行代码将一个方块(ModBlocks.RUBY\_BLOCK)与字符串 “Ruby Block” 相关联。在游戏内,这个方块将会显示为 “Ruby Block”。 这行代码为语言文件添加了一个自定义的翻译。"object.examplemod.example\_object" 是一个键(key),它可以在游戏的其它地方被引用来显示对应的文本。“Example Object” 是这个键对应的值(value),也就是实际显示的文本。 ## 物品模型 ```java public class ModItemModelProvider extends ItemModelProvider { public ModItemModelProvider(PackOutput output, String modid, ExistingFileHelper existingFileHelper) { super(output, modid, existingFileHelper); } @Override protected void registerModels() { this.basicItem(ModItems.RUBY.get()); this.magicIngotModel(getResourceLocation(ModItems.MAGIC_INGOT.get())); } public ResourceLocation getResourceLocation(Item item){ return Objects.requireNonNull(BuiltInRegistries.ITEM.getKey(item)); } public void magicIngotModel(ResourceLocation item){ this.getBuilder(item.toString()) .parent(new ModelFile.UncheckedModelFile("item/generated")) .texture("layer0",new ResourceLocation("item/iron_ingot")) .override().predicate(new ResourceLocation(ExampleMod.MODID,"size"),16).model(new ModelFile.UncheckedModelFile("item/gold_ingot")).end(); } } ``` ItemModelProvider 是一个类,它用于生成物品的模型文件。 registerModels 是 ItemModelProvider 类中的一个方法。在自定义的 ItemModelProvider 子类中,你需要重写这个方法来注册你想要创建的物品模型。这个方法在数据生成过程中被调用,用于生成物品的模型文件。 这行代码调用了 basicItem 方法,它是一个便利方法,用于为 ModItems.RUBY 创建一个基本的物品模型。这个方法会生成一个默认的物品模型,通常是一个简单的方块或物品形状。 这个方法用于创建一个自定义的物品模型。它接受一个 ResourceLocation 参数,这是物品的唯一标识符。 在这个方法中,首先调用 getBuilder(item.toString()) 来获取一个模型构建器,然后设置模型的父级为 "item/generated",这是一个Minecraft内置的通用物品模型。 接着,使用 .texture("layer0",new ResourceLocation("item/iron\_ingot")) 为模型设置纹理。这里使用了铁锭的纹理。 最后,使用 .override().predicate(new ResourceLocation(ExampleMod.MODID,"size"),16).model(new ModelFile.UncheckedModelFile("item/gold\_ingot")).end() 添加了一个条件覆盖。这意味着当物品具有一个名为 “size” 的属性,并且该属性的值大于等于16时,将使用金锭的模型来代替当前的模型。 总的来说,这段代码的作用是为 ModItems.RUBY 创建一个基本的物品模型,并为 ModItems.MAGIC\_INGOT 创建一个自定义的模型,该模型在特定条件下会使用金锭的外观。 ## 方块状态 ```java public class ModBlockStateProvider extends BlockStateProvider { public ModBlockStateProvider(PackOutput output, String modid, ExistingFileHelper exFileHelper) { super(output, modid, exFileHelper); } @Override protected void registerStatesAndModels() { this.simpleBlockWithItem(ModBlocks.RUBY_BLOCK.get(),cubeAll(ModBlocks.RUBY_BLOCK.get())); this.propertyBlock(ModBlocks.LAMP_BLOCK.get()); } public void propertyBlock(Block block){ var block_off = models().cubeAll("lamp_off",new ResourceLocation(ExampleMod.MODID,ModelProvider.BLOCK_FOLDER+"/"+"zircon_lamp_off")); var block_on = models().cubeAll("lamp_on",new ResourceLocation(ExampleMod.MODID, ModelProvider.BLOCK_FOLDER+"/"+"zircon_lamp_on")); getVariantBuilder(block).partialState().with(LampBlock.LIT,true) .modelForState().modelFile(block_on).addModel() .partialState().with(LampBlock.LIT,false) .modelForState().modelFile(block_off).addModel(); simpleBlockItem(block,block_off); } } ``` BlockStateProvider 是一个数据生成器,它用于创建方块的状态和模型。这些状态和模型定义了方块在游戏中的外观和行为。 registerStatesAndModels 是 BlockStateProvider 类中的一个方法。在自定义的 BlockStateProvider 子类中,你需要重写这个方法来注册你想要创建的方块状态和模型。这个方法在数据生成过程中被调用,用于生成方块的状态文件和模型文件。 这行代码调用了 simpleBlockWithItem 方法,它用于为 ModBlocks.RUBY\_BLOCK 创建一个简单的方块状态和对应的物品模型。cubeAll 方法创建了一个简单的立方体模型,它将使用与方块相同的纹理。 public void propertyBlock(Block block){ 这个方法用于创建具有不同状态的方块模型。它首先创建了两个模型:block\_off 和 block\_on,分别代表方块关闭和开启时的外观。 然后,使用 getVariantBuilder(block) 获取方块变体的构建器,并使用 partialState() 方法来指定方块的特定状态。这里使用了 LampBlock.LIT 属性来区分方块是开启还是关闭。 对于每个状态,使用 modelForState().modelFile(...).addModel() 来设置相应的模型文件。 最后,调用 simpleBlockItem(block,block\_off) 来创建与方块关联的物品模型。这个物品模型将使用关闭状态的方块模型。 这段代码的作用是为 ModBlocks.RUBY\_BLOCK 创建一个简单的方块状态和模型,并为 ModBlocks.LAMP\_BLOCK 创建一个具有两种不同状态的模型,分别代表灯方块开启和关闭时的外观。 ## 如何使用他们 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public class ModDataGeneratorHandler { @SubscribeEvent public static void gatherData(GatherDataEvent event){ ExistingFileHelper efh = event.getExistingFileHelper(); // 语言文件 event.getGenerator().addProvider( event.includeClient(), (DataProvider.Factory) pOutput -> new ModLanguageProvider(pOutput,ExampleMod.MODID,"en_us") ); // 物品模型 event.getGenerator().addProvider( event.includeClient(), (DataProvider.Factory) pOutput -> new ModItemModelProvider(pOutput,ExampleMod.MODID,efh) ); // 方块state event.getGenerator().addProvider( event.includeClient(), (DataProvider.Factory) pOutput -> new ModBlockStateProvider(pOutput,ExampleMod.MODID,efh) ); } } ``` ## GatherDataEvent GatherDataEvent 是一个事件,它在数据生成过程中被触发。这个事件允许开发者注册自己的数据提供者(Data Providers),这些提供者负责生成具体的数据文件。 ExistingFileHelper ExistingFileHelper 是一个辅助类,用于检查文件是否存在,以避免覆盖现有文件。在数据生成过程中,使用 ExistingFileHelper 可以确保数据的完整性。 getGenerator() getGenerator() 方法是 GatherDataEvent 的一部分,它返回一个 DataGenerator 实例,这个实例用于管理数据提供者和数据生成过程。 event.includeClient()告诉生成器仅当客户端资源生成时候调用 Poutput Poutput 是 PackOutput 类的一个实例,它代表数据生成器的输出目标。这个类负责管理数据生成器的输出目录和文件结构。 ## 更多详细内容 https://docs.neoforged.net/docs/datagen/ --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_07_Datagen.md description: 07 数据生成 相关教程 --- # Data Generators 数据生成器是一种以编程方式生成模组资源和数据的方法。它允许在代码中定义这些文件的内容并自动生成它们,而无需担心细节。 ## 语言文件 ```java import net.flandre923.examplemod.block.ModBlocks; import net.flandre923.examplemod.item.ModItems; import net.minecraft.data.PackOutput; import net.neoforged.neoforge.common.data.LanguageProvider; public class ModLanguageProvider extends LanguageProvider { public ModLanguageProvider(PackOutput output, String modid, String locale) { super(output, modid, locale); } @Override protected void addTranslations() { this.add(ModItems.RUBY.get(),"Ruby"); this.add(ModBlocks.RUBY_BLOCK.get(),"Ruby Block"); this.add("object.examplemod.example_object","Example Object"); } } ``` LanguageProvider 是一个类,用于生成和修改Minecraft游戏中的语言文件。语言文件包含了游戏中所有的文本内容,如物品名称、方块名称、提示信息等。通过扩展 LanguageProvider 类,开发者可以自定义自己的语言文件内容。 addTranslations 是 LanguageProvider 类中的一个方法。在自定义的 LanguageProvider 子类中,你需要重写(override)这个方法来添加你想要的语言翻译。这个方法在数据生成过程中被调用,用于生成语言文件。 这行代码的作用是将游戏内的一个物品(这里是指 ModItems.RUBY)与一个字符串(“Ruby”)相关联。在生成的语言文件中,ModItems.RUBY 将会被翻译为 “Ruby”。这样,当玩家在游戏中看到这个物品时,它的名称将会显示为 “Ruby”。 与上一条类似,这行代码将一个方块(ModBlocks.RUBY\_BLOCK)与字符串 “Ruby Block” 相关联。在游戏内,这个方块将会显示为 “Ruby Block”。 这行代码为语言文件添加了一个自定义的翻译。"object.examplemod.example\_object" 是一个键(key),它可以在游戏的其它地方被引用来显示对应的文本。“Example Object” 是这个键对应的值(value),也就是实际显示的文本。 ## 物品模型 ```java public class ModItemModelProvider extends ItemModelProvider { public ModItemModelProvider(PackOutput output, String modid, ExistingFileHelper existingFileHelper) { super(output, modid, existingFileHelper); } @Override protected void registerModels() { this.basicItem(ModItems.RUBY.get()); this.magicIngotModel(getResourceLocation(ModItems.MAGIC_INGOT.get())); } public ResourceLocation getResourceLocation(Item item){ return Objects.requireNonNull(BuiltInRegistries.ITEM.getKey(item)); } public void magicIngotModel(ResourceLocation item){ this.getBuilder(item.toString()) .parent(new ModelFile.UncheckedModelFile("item/generated")) .texture("layer0",new ResourceLocation("item/iron_ingot")) .override().predicate(new ResourceLocation(ExampleMod.MODID,"size"),16).model(new ModelFile.UncheckedModelFile("item/gold_ingot")).end(); } } ``` ItemModelProvider 是一个类,它用于生成物品的模型文件。 registerModels 是 ItemModelProvider 类中的一个方法。在自定义的 ItemModelProvider 子类中,你需要重写这个方法来注册你想要创建的物品模型。这个方法在数据生成过程中被调用,用于生成物品的模型文件。 这行代码调用了 basicItem 方法,它是一个便利方法,用于为 ModItems.RUBY 创建一个基本的物品模型。这个方法会生成一个默认的物品模型,通常是一个简单的方块或物品形状。 这个方法用于创建一个自定义的物品模型。它接受一个 ResourceLocation 参数,这是物品的唯一标识符。 在这个方法中,首先调用 getBuilder(item.toString()) 来获取一个模型构建器,然后设置模型的父级为 "item/generated",这是一个Minecraft内置的通用物品模型。 接着,使用 .texture("layer0",new ResourceLocation("item/iron\_ingot")) 为模型设置纹理。这里使用了铁锭的纹理。 最后,使用 .override().predicate(new ResourceLocation(ExampleMod.MODID,"size"),16).model(new ModelFile.UncheckedModelFile("item/gold\_ingot")).end() 添加了一个条件覆盖。这意味着当物品具有一个名为 “size” 的属性,并且该属性的值大于等于16时,将使用金锭的模型来代替当前的模型。 总的来说,这段代码的作用是为 ModItems.RUBY 创建一个基本的物品模型,并为 ModItems.MAGIC\_INGOT 创建一个自定义的模型,该模型在特定条件下会使用金锭的外观。 ## 方块状态 ```java public class ModBlockStateProvider extends BlockStateProvider { public ModBlockStateProvider(PackOutput output, String modid, ExistingFileHelper exFileHelper) { super(output, modid, exFileHelper); } @Override protected void registerStatesAndModels() { this.simpleBlockWithItem(ModBlocks.RUBY_BLOCK.get(),cubeAll(ModBlocks.RUBY_BLOCK.get())); this.propertyBlock(ModBlocks.LAMP_BLOCK.get()); } public void propertyBlock(Block block){ var block_off = models().cubeAll("lamp_off",new ResourceLocation(ExampleMod.MODID,ModelProvider.BLOCK_FOLDER+"/"+"zircon_lamp_off")); var block_on = models().cubeAll("lamp_on",new ResourceLocation(ExampleMod.MODID, ModelProvider.BLOCK_FOLDER+"/"+"zircon_lamp_on")); getVariantBuilder(block).partialState().with(LampBlock.LIT,true) .modelForState().modelFile(block_on).addModel() .partialState().with(LampBlock.LIT,false) .modelForState().modelFile(block_off).addModel(); simpleBlockItem(block,block_off); } } ``` BlockStateProvider 是一个数据生成器,它用于创建方块的状态和模型。这些状态和模型定义了方块在游戏中的外观和行为。 registerStatesAndModels 是 BlockStateProvider 类中的一个方法。在自定义的 BlockStateProvider 子类中,你需要重写这个方法来注册你想要创建的方块状态和模型。这个方法在数据生成过程中被调用,用于生成方块的状态文件和模型文件。 这行代码调用了 simpleBlockWithItem 方法,它用于为 ModBlocks.RUBY\_BLOCK 创建一个简单的方块状态和对应的物品模型。cubeAll 方法创建了一个简单的立方体模型,它将使用与方块相同的纹理。 public void propertyBlock(Block block){ 这个方法用于创建具有不同状态的方块模型。它首先创建了两个模型:block\_off 和 block\_on,分别代表方块关闭和开启时的外观。 然后,使用 getVariantBuilder(block) 获取方块变体的构建器,并使用 partialState() 方法来指定方块的特定状态。这里使用了 LampBlock.LIT 属性来区分方块是开启还是关闭。 对于每个状态,使用 modelForState().modelFile(...).addModel() 来设置相应的模型文件。 最后,调用 simpleBlockItem(block,block\_off) 来创建与方块关联的物品模型。这个物品模型将使用关闭状态的方块模型。 这段代码的作用是为 ModBlocks.RUBY\_BLOCK 创建一个简单的方块状态和模型,并为 ModBlocks.LAMP\_BLOCK 创建一个具有两种不同状态的模型,分别代表灯方块开启和关闭时的外观。 ## 如何使用他们 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public class ModDataGeneratorHandler { @SubscribeEvent public static void gatherData(GatherDataEvent event){ ExistingFileHelper efh = event.getExistingFileHelper(); // 语言文件 event.getGenerator().addProvider( event.includeClient(), (DataProvider.Factory) pOutput -> new ModLanguageProvider(pOutput,ExampleMod.MODID,"en_us") ); // 物品模型 event.getGenerator().addProvider( event.includeClient(), (DataProvider.Factory) pOutput -> new ModItemModelProvider(pOutput,ExampleMod.MODID,efh) ); // 方块state event.getGenerator().addProvider( event.includeClient(), (DataProvider.Factory) pOutput -> new ModBlockStateProvider(pOutput,ExampleMod.MODID,efh) ); } } ``` ## GatherDataEvent GatherDataEvent 是一个事件,它在数据生成过程中被触发。这个事件允许开发者注册自己的数据提供者(Data Providers),这些提供者负责生成具体的数据文件。 ExistingFileHelper ExistingFileHelper 是一个辅助类,用于检查文件是否存在,以避免覆盖现有文件。在数据生成过程中,使用 ExistingFileHelper 可以确保数据的完整性。 getGenerator() getGenerator() 方法是 GatherDataEvent 的一部分,它返回一个 DataGenerator 实例,这个实例用于管理数据提供者和数据生成过程。 event.includeClient()告诉生成器仅当客户端资源生成时候调用 Poutput Poutput 是 PackOutput 类的一个实例,它代表数据生成器的输出目标。这个类负责管理数据生成器的输出目录和文件结构。 ## 更多详细内容 https://docs.neoforged.net/docs/datagen/ --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_08_Food.md description: 08 食物 相关教程 --- # 食物 ```java public class RubyApple extends Item { private static final FoodProperties FOOD_PROPERTIES = new FoodProperties.Builder() .saturationMod(10) .nutrition(20) .effect(()-> new MobEffectInstance(MobEffects.POISON,3*20,1),1) .build(); public RubyApple() { super(new Properties().food(FOOD_PROPERTIES)); } } ``` FoodProperties 类用于定义食物的这些特性。在 RubyApple 类中,FOOD\_PROPERTIES 是一个静态常量,它使用 FoodProperties.Builder 类来构建食物的特性。 FoodProperties.Builder 是一个用于构建 FoodProperties 实例的帮助类。它提供了一系列的方法来设置食物的属性。 这行代码设置了食物的饱和度修改值。饱和度是食物满足饥饿程度的指标。这个值越高,食物就越能减少饥饿条下降的速度。在这里,饱和度被设置为 10。 这行代码设置了食物的营养值。营养值决定了食物能恢复多少饥饿值。在这个例子中,营养值被设置为 20,这意味着吃下一个 RubyApple 可以恢复 20 点饥饿值。 这行代码为食物添加了一个效果。MobEffectInstance 是效果实例类,它用于创建具体的效果对象。这里使用了 lambda 表达式来创建一个 MobEffectInstance,它表示玩家在食用 RubyApple 后会获得中毒效果。 * MobEffects.POISON:这是中毒效果的常量引用。 * 3\*20:效果的持续时间,以游戏刻(ticks)为单位。在这个例子中,效果持续 60 秒(3 分钟)。 * 1:效果的水平,或强度。中毒效果的强度通常从 0 开始,但这里设置为 1,这可能是因为某些效果可以有不同的级别。 最后,build() 方法被调用以创建 FoodProperties 对象。这个对象包含了之前设置的所有食物属性。 注册物品 ```java public static final Supplier RUBY_APPLE = ITEMS.register("ruby_apple", RubyApple::new); ``` 添加到创造模式物品栏 ```java public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab",() -> CreativeModeTab.builder() .withTabsBefore(CreativeModeTabs.COMBAT) .title(Component.translatable(EXAMPLE_MOD_TAB_STRING)) .icon(()->ModItems.RUBY.get().getDefaultInstance()) .displayItems((pParameters, pOutput) -> { pOutput.accept(ModItems.RUBY.get()); pOutput.accept(ModItems.MAGIC_INGOT.get()); pOutput.accept(ModItems.RUBY_APPLE.get()); pOutput.accept(ModBlocks.RUBY_BLOCK.get()); pOutput.accept(ModBlocks.LAMP_BLOCK.get()); }) .build()); ``` 添加物品模型 ```java @Override protected void registerModels() { this.basicItem(ModItems.RUBY.get()); this.basicItem(ModItems.RUBY_APPLE.get()); this.magicIngotModel(getResourceLocation(ModItems.MAGIC_INGOT.get())); } ``` 添加贴图 ![alt text](ruby_apple.png) ## FoodProperties 类 FoodProperties 类是 Minecraft 中用于定义食物属性的一个类。它包含了一系列的属性和方法,用于设置食物的营养值、饱和度、是否为肉类、是否可以总是食用、是否为快速食物,以及食物可能产生的效果。 * nutrition:食物提供的营养值,决定了食物能恢复多少饥饿值。 * saturationModifier:食物的饱和度修改值,决定了食物减少饥饿条下降速度的能力。 * isMeat:一个布尔值,表示食物是否为肉类。 * canAlwaysEat:一个布尔值,表示即使玩家已经饱了,是否仍然可以食用该食物。 * fastFood:一个布尔值,表示食物是否可以快速食用(即不需要长时间咀嚼)。 * effects:一个列表,包含食物可能给予玩家的效果及其概 ## Builder类 Builder 类 FoodProperties.Builder 是一个内部静态类,用于构建 FoodProperties 实例。它提供了一系列的链式方法来设置食物的属性。 * nutrition(int pNutrition):设置食物的营养值。 * saturationMod(float pSaturationModifier):设置食物的饱和度修改值。 * meat():将食物设置为肉类。 * alwaysEat():设置玩家总是可以食用该食物。 * fast():设置食物为快速食用。 * effect(java.util.function.Supplier effectIn, float probability):添加食物可能给予玩家的效果及其概率。 * effect(MobEffectInstance pEffect, float pProbability):已弃用的方法,用于添加食物可能给予玩家的效果及其概率。 * build():使用设置的食物属性创建并返回一个 FoodProperties 实例。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_08_Food.md description: 08 食物 相关教程 --- # 食物 ```java public class RubyApple extends Item { private static final FoodProperties FOOD_PROPERTIES = new FoodProperties.Builder() .saturationMod(10) .nutrition(20) .effect(()-> new MobEffectInstance(MobEffects.POISON,3*20,1),1) .build(); public RubyApple() { super(new Properties().food(FOOD_PROPERTIES)); } } ``` FoodProperties 类用于定义食物的这些特性。在 RubyApple 类中,FOOD\_PROPERTIES 是一个静态常量,它使用 FoodProperties.Builder 类来构建食物的特性。 FoodProperties.Builder 是一个用于构建 FoodProperties 实例的帮助类。它提供了一系列的方法来设置食物的属性。 这行代码设置了食物的饱和度修改值。饱和度是食物满足饥饿程度的指标。这个值越高,食物就越能减少饥饿条下降的速度。在这里,饱和度被设置为 10。 这行代码设置了食物的营养值。营养值决定了食物能恢复多少饥饿值。在这个例子中,营养值被设置为 20,这意味着吃下一个 RubyApple 可以恢复 20 点饥饿值。 这行代码为食物添加了一个效果。MobEffectInstance 是效果实例类,它用于创建具体的效果对象。这里使用了 lambda 表达式来创建一个 MobEffectInstance,它表示玩家在食用 RubyApple 后会获得中毒效果。 * MobEffects.POISON:这是中毒效果的常量引用。 * 3\*20:效果的持续时间,以游戏刻(ticks)为单位。在这个例子中,效果持续 60 秒(3 分钟)。 * 1:效果的水平,或强度。中毒效果的强度通常从 0 开始,但这里设置为 1,这可能是因为某些效果可以有不同的级别。 最后,build() 方法被调用以创建 FoodProperties 对象。这个对象包含了之前设置的所有食物属性。 注册物品 ```java public static final Supplier RUBY_APPLE = ITEMS.register("ruby_apple", RubyApple::new); ``` 添加到创造模式物品栏 ```java public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab",() -> CreativeModeTab.builder() .withTabsBefore(CreativeModeTabs.COMBAT) .title(Component.translatable(EXAMPLE_MOD_TAB_STRING)) .icon(()->ModItems.RUBY.get().getDefaultInstance()) .displayItems((pParameters, pOutput) -> { pOutput.accept(ModItems.RUBY.get()); pOutput.accept(ModItems.MAGIC_INGOT.get()); pOutput.accept(ModItems.RUBY_APPLE.get()); pOutput.accept(ModBlocks.RUBY_BLOCK.get()); pOutput.accept(ModBlocks.LAMP_BLOCK.get()); }) .build()); ``` 添加物品模型 ```java @Override protected void registerModels() { this.basicItem(ModItems.RUBY.get()); this.basicItem(ModItems.RUBY_APPLE.get()); this.magicIngotModel(getResourceLocation(ModItems.MAGIC_INGOT.get())); } ``` 添加贴图 ![alt text](ruby_apple.png) ## FoodProperties 类 FoodProperties 类是 Minecraft 中用于定义食物属性的一个类。它包含了一系列的属性和方法,用于设置食物的营养值、饱和度、是否为肉类、是否可以总是食用、是否为快速食物,以及食物可能产生的效果。 * nutrition:食物提供的营养值,决定了食物能恢复多少饥饿值。 * saturationModifier:食物的饱和度修改值,决定了食物减少饥饿条下降速度的能力。 * isMeat:一个布尔值,表示食物是否为肉类。 * canAlwaysEat:一个布尔值,表示即使玩家已经饱了,是否仍然可以食用该食物。 * fastFood:一个布尔值,表示食物是否可以快速食用(即不需要长时间咀嚼)。 * effects:一个列表,包含食物可能给予玩家的效果及其概 ## Builder类 Builder 类 FoodProperties.Builder 是一个内部静态类,用于构建 FoodProperties 实例。它提供了一系列的链式方法来设置食物的属性。 * nutrition(int pNutrition):设置食物的营养值。 * saturationMod(float pSaturationModifier):设置食物的饱和度修改值。 * meat():将食物设置为肉类。 * alwaysEat():设置玩家总是可以食用该食物。 * fast():设置食物为快速食用。 * effect(java.util.function.Supplier effectIn, float probability):添加食物可能给予玩家的效果及其概率。 * effect(MobEffectInstance pEffect, float pProbability):已弃用的方法,用于添加食物可能给予玩家的效果及其概率。 * build():使用设置的食物属性创建并返回一个 FoodProperties 实例。 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_09_Weapon.md description: 09 武器 相关教程 --- # 武器 ```java public enum ModItemTiers implements Tier { RUBY(3,2000,10F,4F,30,() -> Ingredient.of(ModItems.RUBY.get())); private final int level; private final int uses; private final float speed; private final float damage; private final int enchantmentValue; private final LazyLoadedValue repairIngredient; private ModItemTiers(int pLevel, int pUses, float pSpeed, float pDamage, int pEnchantmentValue, Supplier pRepairIngredient) { this.level = pLevel; this.uses = pUses; this.speed = pSpeed; this.damage = pDamage; this.enchantmentValue = pEnchantmentValue; this.repairIngredient = new LazyLoadedValue<>(pRepairIngredient); } @Override public int getUses() { return this.uses; } @Override public float getSpeed() { return this.speed; } @Override public float getAttackDamageBonus() { return this.damage; } @Override public int getLevel() { return this.level; } @Override public int getEnchantmentValue() { return this.enchantmentValue; } @Override public Ingredient getRepairIngredient() { return this.repairIngredient.get(); } } ``` ModItemTiers 是一个枚举类,它实现了 Tier 接口,用于表示Minecraft中自定义工具的等级或品质。每个枚举常量代表一种不同的等级,具有不同的属性值。 添加一个物品 ```java public class RubySword extends SwordItem { public RubySword(){ super(ModItemTiers.RUBY,3,-2.4f,new Item.Properties()); } } ``` 3:这表示 RubySword 的攻击力 -2.4f:这表示 RubySword 的攻击速度。攻击速度是一个负值,越小攻击速度越慢例如斧头是-3.1f 注册 ```java public static final Supplier RUBY_SWORD = ITEMS.register("ruby_sword", RubySword::new); ``` 添加到创造模式物品栏 ```java pOutput.accept(ModItems.RUBY_SWORD.get()); ``` 添加模型文件 ```java @Override protected void registerModels() { this.basicItem(ModItems.RUBY.get()); this.basicItem(ModItems.RUBY_APPLE.get()); this.basicItem(ModItems.RUBY_SWORD.get()); this.magicIngotModel(getResourceLocation(ModItems.MAGIC_INGOT.get())); } ``` 添加贴图 自己找一个贴图即可。(略) ## Tiers枚举 Tiers 是一个枚举类,它实现了 Tier 接口,用于表示Minecraft中工具和装备的等级或品质。每个枚举常量代表一种不同的等级,具有不同的属性值。 属性 * level:表示工具或装备等级的整数值。 * uses:表示工具可以使用的次数。 * speed:表示工具的工作速度。 * damage:表示工具的攻击力加成。 * enchantmentValue:表示工具可以附魔的等级。 * repairIngredient:一个 LazyLoadedValue 对象,用于存储修理该等级工具所需的材料。 * WOOD:等级0,对应木制工具。 * STONE:等级1,对应石制工具。 * IRON:等级2,对应铁制工具。 * DIAMOND:等级3,对应钻石工具。 * GOLD:等级0,对应金制工具。 * NETHERITE:等级4,对应下界合金工具。 ## SwordItem 类 SwordItem 类是 Minecraft 中的一个扩展类,它继承自 TieredItem 类并实现了 Vanishable 接口。这个类代表一个剑类物品,并定义了它的属性和行为。 SwordItem(Tier pTier, int pAttackDamageModifier, float pAttackSpeedModifier, Item.Properties pProperties):初始化 SwordItem 类。 * getDamage():返回剑的攻击伤害值。 * canAttackBlock(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer):检查玩家是否可以在创造模式外攻击方块。 * getDestroySpeed(ItemStack pStack, BlockState pState):返回剑破坏特定方块的速度。 * hurtEnemy(ItemStack pStack, LivingEntity pTarget, LivingEntity pAttacker):处理剑伤害敌人的逻辑。 * mineBlock(ItemStack pStack, Level pLevel, BlockState pState, BlockPos pPos, LivingEntity pEntityLiving):处理使用剑挖掘方块的逻辑。 * isCorrectToolForDrops(BlockState pBlock):检查剑是否是挖掘给定方块的正确工具。 * getDefaultAttributeModifiers(EquipmentSlot pEquipmentSlot):获取剑的默认属性修改器。 * canPerformAction(ItemStack stack, net.neoforged.neoforge.common.ToolAction toolAction):检查剑是否能执行给定的工具动作。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_09_Weapon.md description: 09 武器 相关教程 --- # 武器 ```java public enum ModItemTiers implements Tier { RUBY(3,2000,10F,4F,30,() -> Ingredient.of(ModItems.RUBY.get())); private final int level; private final int uses; private final float speed; private final float damage; private final int enchantmentValue; private final LazyLoadedValue repairIngredient; private ModItemTiers(int pLevel, int pUses, float pSpeed, float pDamage, int pEnchantmentValue, Supplier pRepairIngredient) { this.level = pLevel; this.uses = pUses; this.speed = pSpeed; this.damage = pDamage; this.enchantmentValue = pEnchantmentValue; this.repairIngredient = new LazyLoadedValue<>(pRepairIngredient); } @Override public int getUses() { return this.uses; } @Override public float getSpeed() { return this.speed; } @Override public float getAttackDamageBonus() { return this.damage; } @Override public int getLevel() { return this.level; } @Override public int getEnchantmentValue() { return this.enchantmentValue; } @Override public Ingredient getRepairIngredient() { return this.repairIngredient.get(); } } ``` ModItemTiers 是一个枚举类,它实现了 Tier 接口,用于表示Minecraft中自定义工具的等级或品质。每个枚举常量代表一种不同的等级,具有不同的属性值。 添加一个物品 ```java public class RubySword extends SwordItem { public RubySword(){ super(ModItemTiers.RUBY,3,-2.4f,new Item.Properties()); } } ``` 3:这表示 RubySword 的攻击力 -2.4f:这表示 RubySword 的攻击速度。攻击速度是一个负值,越小攻击速度越慢例如斧头是-3.1f 注册 ```java public static final Supplier RUBY_SWORD = ITEMS.register("ruby_sword", RubySword::new); ``` 添加到创造模式物品栏 ```java pOutput.accept(ModItems.RUBY_SWORD.get()); ``` 添加模型文件 ```java @Override protected void registerModels() { this.basicItem(ModItems.RUBY.get()); this.basicItem(ModItems.RUBY_APPLE.get()); this.basicItem(ModItems.RUBY_SWORD.get()); this.magicIngotModel(getResourceLocation(ModItems.MAGIC_INGOT.get())); } ``` 添加贴图 自己找一个贴图即可。(略) ## Tiers枚举 Tiers 是一个枚举类,它实现了 Tier 接口,用于表示Minecraft中工具和装备的等级或品质。每个枚举常量代表一种不同的等级,具有不同的属性值。 属性 * level:表示工具或装备等级的整数值。 * uses:表示工具可以使用的次数。 * speed:表示工具的工作速度。 * damage:表示工具的攻击力加成。 * enchantmentValue:表示工具可以附魔的等级。 * repairIngredient:一个 LazyLoadedValue 对象,用于存储修理该等级工具所需的材料。 * WOOD:等级0,对应木制工具。 * STONE:等级1,对应石制工具。 * IRON:等级2,对应铁制工具。 * DIAMOND:等级3,对应钻石工具。 * GOLD:等级0,对应金制工具。 * NETHERITE:等级4,对应下界合金工具。 ## SwordItem 类 SwordItem 类是 Minecraft 中的一个扩展类,它继承自 TieredItem 类并实现了 Vanishable 接口。这个类代表一个剑类物品,并定义了它的属性和行为。 SwordItem(Tier pTier, int pAttackDamageModifier, float pAttackSpeedModifier, Item.Properties pProperties):初始化 SwordItem 类。 * getDamage():返回剑的攻击伤害值。 * canAttackBlock(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer):检查玩家是否可以在创造模式外攻击方块。 * getDestroySpeed(ItemStack pStack, BlockState pState):返回剑破坏特定方块的速度。 * hurtEnemy(ItemStack pStack, LivingEntity pTarget, LivingEntity pAttacker):处理剑伤害敌人的逻辑。 * mineBlock(ItemStack pStack, Level pLevel, BlockState pState, BlockPos pPos, LivingEntity pEntityLiving):处理使用剑挖掘方块的逻辑。 * isCorrectToolForDrops(BlockState pBlock):检查剑是否是挖掘给定方块的正确工具。 * getDefaultAttributeModifiers(EquipmentSlot pEquipmentSlot):获取剑的默认属性修改器。 * canPerformAction(ItemStack stack, net.neoforged.neoforge.common.ToolAction toolAction):检查剑是否能执行给定的工具动作。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/the-start/commands.md --- # 1.1 常用指令 *** KubeJS添加了一些指令,这可以提高编写脚本的效率: 使用`/kubejs custom_command ` 可以执行自定义指令 使用`/kubejs errors` 可以在聊天栏中获取当前脚本的报错 使用`/kubejs export` 可以将游戏内的配方、tags、所有方块、实体类型、流体类型导出到kubejs\exported\kubejs-server-export.json * 注:你可以将该json文档上传至https://export.kubejs.com/来进行分析(截至编辑本文时该功能不可用) 使用`/kubejs hand` 或 `/kjs_hand` 可以快速获取手中物品信息,这对于配方自定义等非常有帮助(点击文本即可复制) 使用`/kubejs hotbar` 可以将快捷栏中所有物品信息打印到聊天栏(同`/kubejs hand`) 使用`/kubejs inventory` 可以将玩家物品栏的所有物品信息打印到聊天栏(同`/kubejs hand`) 使用`/kubejs offhand` 可以将玩家副手的物品信息打印到聊天栏(同`/kubejs hand`) 使用`/kubejs list_tags [block|fluid|item|entity_type]` 来将给定标签的内容打印到聊天栏 * 如`/kubejs list_tag minecraft:logs item` 会将`#minecraft:logs`标签下的元素打印出来 使用`/kubejs painter <玩家名称> ` 来调用PainterJS(见第十六章) 使用`/kubejs reload [server_scripts|lang|texture|startup_scripts]` 来重载服务器类型脚本、语言文件、纹理资源和启动类型脚本 * 其中`/kubejs reload server_scripts`和`/reload`的效果基本相同 * `/kubejs reload startup_scripts` 并不能重载所有启动脚本事件,如方块注册等 使用`/kubejs stage [add|list|remove|clear] <玩家名称>` 来为指定玩家添加、列出、移除或清除游戏阶段 * 关于Gamestage的详细介绍见11.4和11.5章节 使用`/kubejs warnings` 来查看当前脚本中的警告信息 使用`/kubejs wiki` 来打开官方KubeJS Wiki --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/the-start/commands.md --- # 1.1 常用指令 *** KubeJS添加了一些指令,这可以提高编写脚本的效率: 使用`/kubejs custom_command ` 可以执行自定义指令 使用`/kubejs errors` 可以在聊天栏中获取当前脚本的报错 使用`/kubejs export` 可以将游戏内的配方、tags、所有方块、实体类型、流体类型导出到kubejs\exported\kubejs-server-export.json * 注:你可以将该json文档上传至https://export.kubejs.com/来进行分析(截至编辑本文时该功能不可用) 使用`/kubejs hand` 或 `/kjs_hand` 可以快速获取手中物品信息,这对于配方自定义等非常有帮助(点击文本即可复制) 使用`/kubejs hotbar` 可以将快捷栏中所有物品信息打印到聊天栏(同`/kubejs hand`) 使用`/kubejs inventory` 可以将玩家物品栏的所有物品信息打印到聊天栏(同`/kubejs hand`) 使用`/kubejs offhand` 可以将玩家副手的物品信息打印到聊天栏(同`/kubejs hand`) 使用`/kubejs list_tags [block|fluid|item|entity_type]` 来将给定标签的内容打印到聊天栏 * 如`/kubejs list_tag minecraft:logs item` 会将`#minecraft:logs`标签下的元素打印出来 使用`/kubejs painter <玩家名称> ` 来调用PainterJS(见第十六章) 使用`/kubejs reload [server_scripts|lang|texture|startup_scripts]` 来重载服务器类型脚本、语言文件、纹理资源和启动类型脚本 * 其中`/kubejs reload server_scripts`和`/reload`的效果基本相同 * `/kubejs reload startup_scripts` 并不能重载所有启动脚本事件,如方块注册等 使用`/kubejs stage [add|list|remove|clear] <玩家名称>` 来为指定玩家添加、列出、移除或清除游戏阶段 * 关于Gamestage的详细介绍见11.4和11.5章节 使用`/kubejs warnings` 来查看当前脚本中的警告信息 使用`/kubejs wiki` 来打开官方KubeJS Wiki --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/readme/probejs-and-commands.md --- # 1.1 常用游戏内指令与ProbeJS使用 ## 一、常用游戏内指令 KubeJS添加了以下指令: | 命令 | 功能 | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `/kubejs custom_command ` | 执行自定义指令 | | `/kubejs errors` | 在聊天栏中获取当前脚本的报错 | | `/kubejs hand` 或 `/kjs_hand` | 快速获取手中物品信息(点击文本即可复制)[示例](https://m1.miaomc.cn/uploads/20221222_63a4360bba36e.png) | | `/kubejs dump_registry <注册表>` | 输出指定注册表下的所有内容 | | `/kubejs export` | 将游戏内的配方、tags、所有方块、实体类型、流体类型导出到`kubejs\exported\kubejs-server-export.json` | | `/kubejs export_virtual_data` | 导出KubeJS添加的虚拟数据包至`kubejs\exported`目录下 | | `/kubejs generate_typings` | WIP! | | `/kubejs hotbar` | 将快捷栏中所有物品信息打印到聊天(同`/kubejs hand`) | | `/kubejs offhand` | 将玩家副手的物品信息打印到聊天栏(同`/kubejs hand`) | | `/kubejs inventory` | 将玩家库存中所有物品信息打印到聊天栏(同`/kubejs hand`) | | `/kubejs painter <玩家> <对象>` | 将给定的Painter对象播放给指定玩家 | | `/kubejs list_tags <注册表> [标签]` | 将给定标签的内容打印到聊天栏\[1] | | `/kubejs reload <类型>` | 重载指定类型的内容,`<类型>`可以为`client_scripts`(客户端侧脚本)、`server_scripts`(服务器端脚本)、`lang`(语言文件)、`startup_scripts`(启动阶段脚本)\[2]、`texture`(纹理)。 | | \`/kubejs stages \[add | list | | `/kubejs warnings` | 查看当前脚本中的警告信息 | | `/kubejs wiki` | 打开官方KubeJS Wiki | | `/reload` | 热重载脚本 | \[1] 如`/kubejs list_tag minecraft:block forge:ores` \[2] `/kubejs reload startup_scripts` 并不能重载所有启动阶段脚本。 ## 二、ProbeJS的使用 [![](https://m1.miaomc.cn/uploads/20230412_6436af1061fbb.png)](https://www.mcbbs.net/thread-1310134-1-1.html) ProbeJS可以快捷生成VSCode支持的代码模板和声明文件,极大地提高了编写魔改的效率。 要使用ProbeJS,你需要: * 安装[Visual Studio Code](https://code.visualstudio.com/)软件。 * 为游戏安装[ProbeJS](https://www.curseforge.com/minecraft/mc-mods/probejs)模组(作者:[迺逸夫](https://www.mcmod.cn/author/24214.html)) ProbeJS安装完成后,在游戏内先执行`/probejs test_availability`,再执行`/probejs dump`指令即可生成声明文件。 待生成完成后,右击游戏目录选择“在VSCode中打开”或“通过Code中打开”即可使用自动补全。 ![](https://m1.miaomc.cn/uploads/20221222_63a43cb8618ed.jpg) ### *关于ProbeJS提示* **带有🔎符号的段落代表该段内容ProbeJS中有语言文件,或借助ProbeJS编写较为简便。** --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/readme/probejs-and-commands.md --- # 1.1 常用游戏内指令与ProbeJS使用 ## 一、常用游戏内指令 KubeJS添加了以下指令: | 命令 | 功能 | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `/kubejs custom_command ` | 执行自定义指令 | | `/kubejs errors` | 在聊天栏中获取当前脚本的报错 | | `/kubejs hand` 或 `/kjs_hand` | 快速获取手中物品信息(点击文本即可复制)[示例](https://m1.miaomc.cn/uploads/20221222_63a4360bba36e.png) | | `/kubejs dump_registry <注册表>` | 输出指定注册表下的所有内容 | | `/kubejs export` | 将游戏内的配方、tags、所有方块、实体类型、流体类型导出到`kubejs\exported\kubejs-server-export.json` | | `/kubejs export_virtual_data` | 导出KubeJS添加的虚拟数据包至`kubejs\exported`目录下 | | `/kubejs generate_typings` | WIP! | | `/kubejs hotbar` | 将快捷栏中所有物品信息打印到聊天(同`/kubejs hand`) | | `/kubejs offhand` | 将玩家副手的物品信息打印到聊天栏(同`/kubejs hand`) | | `/kubejs inventory` | 将玩家库存中所有物品信息打印到聊天栏(同`/kubejs hand`) | | `/kubejs painter <玩家> <对象>` | 将给定的Painter对象播放给指定玩家 | | `/kubejs list_tags <注册表> [标签]` | 将给定标签的内容打印到聊天栏\[1] | | `/kubejs reload <类型>` | 重载指定类型的内容,`<类型>`可以为`client_scripts`(客户端侧脚本)、`server_scripts`(服务器端脚本)、`lang`(语言文件)、`startup_scripts`(启动阶段脚本)\[2]、`texture`(纹理)。 | | \`/kubejs stages \[add | list | | `/kubejs warnings` | 查看当前脚本中的警告信息 | | `/kubejs wiki` | 打开官方KubeJS Wiki | | `/reload` | 热重载脚本 | \[1] 如`/kubejs list_tag minecraft:block forge:ores` \[2] `/kubejs reload startup_scripts` 并不能重载所有启动阶段脚本。 ## 二、ProbeJS的使用 [![](https://m1.miaomc.cn/uploads/20230412_6436af1061fbb.png)](https://www.mcbbs.net/thread-1310134-1-1.html) ProbeJS可以快捷生成VSCode支持的代码模板和声明文件,极大地提高了编写魔改的效率。 要使用ProbeJS,你需要: * 安装[Visual Studio Code](https://code.visualstudio.com/)软件。 * 为游戏安装[ProbeJS](https://www.curseforge.com/minecraft/mc-mods/probejs)模组(作者:[迺逸夫](https://www.mcmod.cn/author/24214.html)) ProbeJS安装完成后,在游戏内先执行`/probejs test_availability`,再执行`/probejs dump`指令即可生成声明文件。 待生成完成后,右击游戏目录选择“在VSCode中打开”或“通过Code中打开”即可使用自动补全。 ![](https://m1.miaomc.cn/uploads/20221222_63a43cb8618ed.jpg) ### *关于ProbeJS提示* **带有🔎符号的段落代表该段内容ProbeJS中有语言文件,或借助ProbeJS编写较为简便。** --- --- url: 'https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/the-start/file-structure.md --- # 1.2 文件结构 *** 在正确安装KubeJS并启动过一次游戏后,你可以在版本根目录下找到kubejs这个文件夹: ``` kubejs ├─assets │ └─kubejs │ └─textures │ ├─block │ └─item ├─client_scripts ├─config ├─data ├─exported │ └─tags ├─server_scripts └─startup_scripts ``` 以下为各个目录的功能(详见目录下`README.txt`): * `assets` 文件夹和资源包的功能基本相同,你可以在这里的对应目录下放自定义方块、物品的纹理、模型等,也可以当做一个全局资源包加载器(类似于OpenLoader) * `config` 中包括对KubeJS的一些配置选项 * `data` 文件夹和数据包功能基本相同,类似于全局数据包加载器(类似于OpenLoader) * `client_scripts` 中为客户端资源被加载时加载脚本 * (如 `client.tick` 等被标记为Client和Client Startup的事件)(可使用 `F3` + `T` 重载) * `server_scripts` 中为服务端资源被加载时加载的脚本 * (如 `recipes` 等被标记为Server和Server Startup的事件)(可使用游戏内命令 `/reload` 重载) * `startup_scripts` 中为启动时就被加载的脚本 * (如 `item.modification` 等被标记Startup的事件)(**部分内容**可使用游戏内命令 `/kubejs reload_startup_scripts` 重载) --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/the-start/file-structure.md --- # 1.2 文件结构 *** 在正确安装KubeJS并启动过一次游戏后,你可以在版本根目录下找到kubejs这个文件夹: ``` kubejs ├─assets │ └─kubejs │ └─textures │ ├─block │ └─item ├─client_scripts ├─config ├─data ├─exported │ └─tags ├─server_scripts └─startup_scripts ``` 以下为各个目录的功能(详见目录下`README.txt`): * `assets` 文件夹和资源包的功能基本相同,你可以在这里的对应目录下放自定义方块、物品的纹理、模型等,也可以当做一个全局资源包加载器(类似于OpenLoader) * `config` 中包括对KubeJS的一些配置选项 * `data` 文件夹和数据包功能基本相同,类似于全局数据包加载器(类似于OpenLoader) * `client_scripts` 中为客户端资源被加载时加载脚本 * (如 `client.tick` 等被标记为Client和Client Startup的事件)(可使用 `F3` + `T` 重载) * `server_scripts` 中为服务端资源被加载时加载的脚本 * (如 `recipes` 等被标记为Server和Server Startup的事件)(可使用游戏内命令 `/reload` 重载) * `startup_scripts` 中为启动时就被加载的脚本 * (如 `item.modification` 等被标记Startup的事件)(**部分内容**可使用游戏内命令 `/kubejs reload_startup_scripts` 重载) --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/readme/file-structure.md --- # 1.2 文件结构和脚本类型 在正确安装KubeJS并启动过一次游戏后,你可以在版本根目录下找到kubejs这个文件夹,以下为其结构 ``` kubejs ├─assets │ └─kubejs │ └─textures │ ├─block │ └─item ├─client_scripts ├─config ├─data ├─exported │ └─tags ├─probe ├─server_scripts └─startup_scripts ``` 以下为各个目录的功能(详见目录下`README.txt`): * `assets` 文件夹和资源包的功能基本相同,你可以在这里的对应目录下放自定义方块、物品的纹理、模型等,也可以当做一个全局资源包加载器(类似于OpenLoader) * `config` 中包括对KubeJS的一些配置选项 * `data` 文件夹和数据包功能基本相同,类似于全局数据包加载器(类似于OpenLoader) * `exported` 文件夹包含游戏内通过指令导出的部分数据,如数据包等 * `probe` 文件夹内包含ProbeJS生成的ts描述文件(使用ProbeJS生成后) * `client_scripts` 中为客户端资源被加载时加载脚本 * (如 `client.tick` 等被标记为Client和Client Startup的事件)(可使用 `F3` + `T` 重载) * `server_scripts` 中为服务端资源被加载时加载的脚本 * (如 `recipes` 等被标记为Server和Server Startup的事件)(可使用游戏内命令 `/reload` 重载) * `startup_scripts` 中为启动时就被加载的脚本 * (如 `item.modification` 等被标记Startup的事件)(可使用游戏内命令 `/kubejs reload_startup_scripts` 重载) --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/readme/file-structure.md --- # 1.2 文件结构和脚本类型 在正确安装KubeJS并启动过一次游戏后,你可以在版本根目录下找到kubejs这个文件夹,以下为其结构 ``` kubejs ├─assets │ └─kubejs │ └─textures │ ├─block │ └─item ├─client_scripts ├─config ├─data ├─exported │ └─tags ├─probe ├─server_scripts └─startup_scripts ``` 以下为各个目录的功能(详见目录下`README.txt`): * `assets` 文件夹和资源包的功能基本相同,你可以在这里的对应目录下放自定义方块、物品的纹理、模型等,也可以当做一个全局资源包加载器(类似于OpenLoader) * `config` 中包括对KubeJS的一些配置选项 * `data` 文件夹和数据包功能基本相同,类似于全局数据包加载器(类似于OpenLoader) * `exported` 文件夹包含游戏内通过指令导出的部分数据,如数据包等 * `probe` 文件夹内包含ProbeJS生成的ts描述文件(使用ProbeJS生成后) * `client_scripts` 中为客户端资源被加载时加载脚本 * (如 `client.tick` 等被标记为Client和Client Startup的事件)(可使用 `F3` + `T` 重载) * `server_scripts` 中为服务端资源被加载时加载的脚本 * (如 `recipes` 等被标记为Server和Server Startup的事件)(可使用游戏内命令 `/reload` 重载) * `startup_scripts` 中为启动时就被加载的脚本 * (如 `item.modification` 等被标记Startup的事件)(可使用游戏内命令 `/kubejs reload_startup_scripts` 重载) --- --- url: 'https://docs.variedmc.cc/en/develop/modding/1.20.1/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/1.20.1/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/zh/mods/adventure/Champions-Unofficial/1.20.1/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/en/develop/modding/1.20.4/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/1.20.4/sidebarIndex.md' description: Minecraft 1.20.4模组开发文档,Forge/Neoforge/Fabric开发环境配置和API指南 --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/en/develop/modding/1.21/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/zh/develop/modding/1.21/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/zh/mods/adventure/Champions-Unofficial/1.21.1/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/the-start/script-format.md --- # 1.3 脚本基础 *** ## 代码基础格式 如JavaScript一样,所有的脚本后缀名都应为.js。在对应的文件夹下,你可以使用以下方式监听事件: ``` onEvent('事件名称', event => { //在此处编写代码 <- 这是一行注释 }) ``` 例如,以下例子会在玩家加入服务器时将信息打印到控制台。由于`player.logged_in`为Server类型脚本,使用时你应当将其放于`server_scripts`文件夹下: `kubejs\server_scripts\firstscript.js` ``` onEvent('player.logged_in', event => {//监听玩家登入事件player.logged_in let name = event.player.name console.info("Hello world!") console.info("玩家 " + name + " 加入了游戏") }) ``` ## JavaScript语法 你可以通过以下页面快速了解JavaScript的语法。 [JavaScript语法](https://www.w3school.com.cn/js/js_syntax.asp) [if 和 if else](https://www.w3school.com.cn/js/js_if_else.asp) [for循环](https://www.w3school.com.cn/js/js_loop_for.asp) [while循环](https://www.w3school.com.cn/js/js_loop_while.asp) [函数](https://www.w3school.com.cn/js/js_functions.asp) [箭头函数](https://www.w3school.com.cn/js/js_arrow_function.asp) --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/the-start/script-format.md --- # 1.3 脚本基础 *** ## 代码基础格式 如JavaScript一样,所有的脚本后缀名都应为.js。在对应的文件夹下,你可以使用以下方式监听事件: ``` onEvent('事件名称', event => { //在此处编写代码 <- 这是一行注释 }) ``` 例如,以下例子会在玩家加入服务器时将信息打印到控制台。由于`player.logged_in`为Server类型脚本,使用时你应当将其放于`server_scripts`文件夹下: `kubejs\server_scripts\firstscript.js` ``` onEvent('player.logged_in', event => {//监听玩家登入事件player.logged_in let name = event.player.name console.info("Hello world!") console.info("玩家 " + name + " 加入了游戏") }) ``` ## JavaScript语法 你可以通过以下页面快速了解JavaScript的语法。 [JavaScript语法](https://www.w3school.com.cn/js/js_syntax.asp) [if 和 if else](https://www.w3school.com.cn/js/js_if_else.asp) [for循环](https://www.w3school.com.cn/js/js_loop_for.asp) [while循环](https://www.w3school.com.cn/js/js_loop_while.asp) [函数](https://www.w3school.com.cn/js/js_functions.asp) [箭头函数](https://www.w3school.com.cn/js/js_arrow_function.asp) --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/readme/script-format.md --- # 1.3 脚本基础和格式变化 *** 在KubeJS中,你需要使用[JavaScript语言](https://www.w3school.com.cn/js/js_intro.asp)来编写脚本,JavaScript的教程可以在网络上找到~~(肯定比我讲得好)~~。 ## 一、监听事件 绝大多数KubeJS的脚本都需要以监听事件为基础。 相比于1.16.5中,KubeJS 6中`事件组.事件名称(event => {})`替代了先前的`onEvent('字符串.事件.id', event => {})`格式 以监听方块右键事件为例: ```js // 1.19(KubeJS 6) BlockEvents.rightClicked(event => { if(event.block.id === 'minecraft:dirt') { console.info('Hi!') } }) // 1.16.5 - 1.18.2 onEvent('block.right_click', event => { if(event.block.id === 'minecraft:dirt') { console.info('Hi!') } }) ``` 此外,新的事件监听格式还支持通过传入参数设置事件ID,如`事件组.事件名称(para, event => {})` ```js // 1.19(KubeJS 6) BlockEvents.rightClicked('minecraft:dirt', event => {// 'minecraft:dirt'为参数,该事件在右键点击泥土方块时触发 console.info('Hi!') }) BlockEvents.rightClicked('minecraft:stone', event => {// 'minecraft:stone'为参数,该事件在右键点击石头方块时触发 console.info('Bye!') }) // 1.16.5 - 1.18.2 onEvent('block.right_click', event => { if(event.block.id === 'minecraft:dirt') {// 手动判断方块ID console.info('Hi!') } else if(event.block.id === 'minecraft:stone') { console.info('Bye!') } }) ``` 一些事件\_必须\_填写ID: ```js StartupEvents.register('item', event => { // 注册物品代码 }) ServerEvents.tags('item', event => { // 物品tag修改代码 }) ``` **有关事件详细教程,请见后续章节** ## 二、Wrapper类的移除 在1.19+中,EntityJS, LevelJS, ItemStackJS, IngredientJS等Wrapper类被移除,带来了显著的性能和兼容性提升。旧版本中绝大多数的方法依旧被新版本支持。详细教程详见后文。 \*\*在ProbeJS的帮助下,你可以快速编写代码而不必依赖于文档。\*\*若仍有需要,可参考[KubeJS源代码仓库](https://github.com/KubeJS-Mods/KubeJS/tree/1.19/main)、[官方列表(旧) ](https://kubejs.com/wiki/kubejs/)、 [1.16-1.18版本教程](https://www.mcbbs.net/thread-1207772-1-1.html) 、 [Fabric JavaDoc](https://fabricmc.net/develop/)或[Forge JavaDoc](https://nekoyue.github.io/ForgeJavaDocs-NG/)。 --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/readme/script-format.md --- # 1.3 脚本基础和格式变化 *** 在KubeJS中,你需要使用[JavaScript语言](https://www.w3school.com.cn/js/js_intro.asp)来编写脚本,JavaScript的教程可以在网络上找到~~(肯定比我讲得好)~~。 ## 一、监听事件 绝大多数KubeJS的脚本都需要以监听事件为基础。 相比于1.16.5中,KubeJS 6中`事件组.事件名称(event => {})`替代了先前的`onEvent('字符串.事件.id', event => {})`格式 以监听方块右键事件为例: ```js // 1.19(KubeJS 6) BlockEvents.rightClicked(event => { if(event.block.id === 'minecraft:dirt') { console.info('Hi!') } }) // 1.16.5 - 1.18.2 onEvent('block.right_click', event => { if(event.block.id === 'minecraft:dirt') { console.info('Hi!') } }) ``` 此外,新的事件监听格式还支持通过传入参数设置事件ID,如`事件组.事件名称(para, event => {})` ```js // 1.19(KubeJS 6) BlockEvents.rightClicked('minecraft:dirt', event => {// 'minecraft:dirt'为参数,该事件在右键点击泥土方块时触发 console.info('Hi!') }) BlockEvents.rightClicked('minecraft:stone', event => {// 'minecraft:stone'为参数,该事件在右键点击石头方块时触发 console.info('Bye!') }) // 1.16.5 - 1.18.2 onEvent('block.right_click', event => { if(event.block.id === 'minecraft:dirt') {// 手动判断方块ID console.info('Hi!') } else if(event.block.id === 'minecraft:stone') { console.info('Bye!') } }) ``` 一些事件\_必须\_填写ID: ```js StartupEvents.register('item', event => { // 注册物品代码 }) ServerEvents.tags('item', event => { // 物品tag修改代码 }) ``` **有关事件详细教程,请见后续章节** ## 二、Wrapper类的移除 在1.19+中,EntityJS, LevelJS, ItemStackJS, IngredientJS等Wrapper类被移除,带来了显著的性能和兼容性提升。旧版本中绝大多数的方法依旧被新版本支持。详细教程详见后文。 \*\*在ProbeJS的帮助下,你可以快速编写代码而不必依赖于文档。\*\*若仍有需要,可参考[KubeJS源代码仓库](https://github.com/KubeJS-Mods/KubeJS/tree/1.19/main)、[官方列表(旧) ](https://kubejs.com/wiki/kubejs/)、 [1.16-1.18版本教程](https://www.mcbbs.net/thread-1207772-1-1.html) 、 [Fabric JavaDoc](https://fabricmc.net/develop/)或[Forge JavaDoc](https://nekoyue.github.io/ForgeJavaDocs-NG/)。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/the-start/event-list.md --- # 1.4 事件列表 *** 以下为KubeJS内置的部分事件,其他模组或附属还可能添加其他事件。 | **事件ID** | **是否可被取消** | 描述 | **类型** | | ------------------------------ | ---------- | ----------- | ------- | | init | 否 | - | Startup | | postinit | 否 | - | Startup | | loaded | 否 | - | Startup | | command.registry | 否 | 注册命令 | Server | | command.run | 可 | 运行命令 | Server | | client.init | 否 | - | Client | | client.debug\_info.left | 否 | 客户端左侧F3事件 | Client | | client.debug\_info.right | 否 | 客户端右侧F3事件 | Client | | client.logged\_in | 否 | 客户端登入事件 | Client | | client.logged\_out | 否 | 客户端登出事件 | Client | | client.tick | 否 | 客户端Tick | Client | | server.load | 否 | 服务器加载事件 | Server | | server.unload | 否 | 服务器卸载事件 | Server | | server.tick | 否 | 服务器Tick | Server | | server.datapack.first | 否 | 高优先度数据包加载 | Server | | server.datapack.last | 否 | 低优先度数据包加载 | Server | | recipes | 否 | 配方事件 | Server | | world.load | 否 | 事件加载事件 | Server | | world.unload | 否 | 世界卸载事件 | Server | | world.tick | 否 | 世界Tick | Server | | world.explosion.pre | 可 | 爆炸前事件 | Server | | world.explosion.post | 否 | 爆炸后事件 | Server | | player.logged\_in | 否 | 玩家登入事件 | Server | | player.logged\_out | 否 | 玩家登出事件 | Server | | player.tick | 否 | 玩家Tick | Server | | player.data\_from\_server.<信道> | 可 | 从服务器接收信息 | Client | | player.data\_from\_client.<信道> | 可 | 从客户端接收信息 | Server | | player.chat | 可 | 玩家聊天事件 | Server | | player.advancement | 否 | 玩家成就事件 | Server | | player.inventory.opened | 否 | 玩家打开库存事件 | Server | | player.inventory.closed | 否 | 玩家关闭库存事件 | Server | | player.inventory.changed | 否 | 玩家库存改变事件 | Server | | player.chest.opened | 否 | 玩家打开箱子事件 | Server | | player.chest.closed | 否 | 玩家关闭箱子事件 | Server | | entity.death | 可 | 实体死亡事件 | Server | | entity.attack | 可 | 实体攻击事件 | Server | | entity.drops | 可 | 实体摔落事件 | Server | | entity.check\_spawn | 可 | 实体检查生成位置事件 | Server | | entity.spawned | 可 | 实体生成事件 | Server | | block.registry | 否 | 方块注册事件 | Startup | | block.missing\_mappings | 否 | - | Server | | block.tags | 否 | 方块tag事件 | Server | | block.right\_click | 可 | 右击方块事件 | Server | | block.left\_click | 可 | 左击方块事件 | Server | | block.place | 可 | 放置方块事件 | Server | | block.break | 可 | 破坏方块事件 | Server | | block.drops | 否 | 方块掉落事件 | Server | | item.food\_eaten | 可 | 食物食用事件 | Server | | item.registry | 否 | 物品注册事件 | Startup | | item.missing\_mappings | 否 | - | Server | | item.tags | 否 | 物品tag事件 | Server | | item.right\_click | 可 | 物品右击事件 | Client | | item.right\_click\_empty | 否 | 物品右击事件(空手) | Client | | item.left\_click | 否 | 物品左击事件 | Client | | item.entity\_interact | 可 | 物品实体交互事件 | Server | | item.modification | 否 | 物品修改事件 | Startup | | item.pickup | 可 | 物品拾取事件 | Server | | item.tooltip | 否 | 物品tooltip事件 | Client | | item.toss | 可 | 物品丢出事件 | Server | | item.crafted | 否 | 物品合成事件 | Server | | item.smelted | 否 | 物品熔炼事件 | Server | | fluid.registry | 否 | 流体注册事件 | Startup | | fluid.tags | 否 | 流体tag事件 | Server | | entity\_type.tags | 否 | 实体类型tag事件 | Server | | worldgen.add | 否 | 世界生成添加 | Startup | | worldgen.remove | 否 | 世界生成移除 | Startup | --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/readme/event-list.md --- # 1.4 事件列表 ## 一、事件列表 | 脚本类型 | 方法 | 描述 | 是否可取消 | | ---------------- | ----------------------------------------------- | ---------------------------------- | ---------- | | startup\_scripts | `StartupEvents.init` | - | ❌ | | startup\_scripts | `StartupEvents.postInit` | - | ❌ | | startup\_scripts | `StartupEvents.registry` | 注册游戏内容(用于添加物品,附魔等) | ❌ | | client\_scripts | `ClientEvents.highPriorityAssets` | - | ❌ | | client\_scripts | `ClientEvents.init` | - | ❌ | | client\_scripts | `ClientEvents.loggedIn` | 登入世界 | ❌ | | client\_scripts | `ClientEvents.loggedOut` | 登出世界 | ❌ | | client\_scripts | `ClientEvents.tick` | 客户端Tick事件 | ❌ | | client\_scripts | `ClientEvents.painterUpdated` | Painter API | ❌ | | client\_scripts | `ClientEvents.leftDebugInfo` | F3界面左侧信息 | ❌ | | client\_scripts | `ClientEvents.rightDebugInfo` | F3界面右侧信息 | ❌ | | client\_scripts | `ClientEvents.paintScreen` | 绘制Screen | ❌ | | server\_scripts | `ServerEvents.lowPriorityData` | 数据包事件 - 低优先度 | ❌ | | server\_scripts | `ServerEvents.highPriorityData` | 数据包事件 - 高优先度 | ❌ | | server\_scripts | `SeverEvents.loaded` | 服务端加载 | ❌ | | server\_scripts | `ServerEvents.unloaded` | 服务端卸载 | ❌ | | server\_scripts | `ServerEvents.tick` | 服务端Tick事件 | ❌ | | server\_scripts | `ServerEvents.tags` | 标签添加或修改 | ❌ | | server\_scripts | `ServerEvents.commandRegistry` | 命令注册事件 | ❌ | | server\_scripts | `ServerEvents.command` | 命令执行事件 | ✔ | | server\_scripts | `ServerEvents.customCommand` | 自定义命令事件 | ✔ | | server\_scripts | `ServerEvents.recipes` | 配方添加或修改 | ❌ | | server\_scripts | `ServerEvents.afterRecipes` | 配方加载完成后事件 | ❌ | | server\_scripts | `ServerEvents.specialRecipeSerializers` | 配方序列化相关事件 | ❌ | | server\_scripts | `ServerEvents.compostableRecipes` | 堆肥配方添加或修改 | ❌ | | server\_scripts | `ServerEvents.recipeTypeRegistry` | 配方类型注册 | ❌ | | server\_scripts | `ServerEvents.genericLootTable`等,详见后续章节 | LootTable相关事件 | ❌ | | server\_scripts | `LevelEvents.loaded` | 世界加载事件 | ❌ | | server\_scripts | `LevelEvents.unloaded` | 世界卸载事件 | ❌ | | server\_scripts | `LevelEvents.tick` | 世界Tick事件 | ❌ | | server\_scripts | `LevelEvents.beforeExplosion` | 爆炸发生前事件 | ✔ | | server\_scripts | `LevelEvents.afterExplosion` | 爆炸发生后事件 | ❌ | | server\_scripts | `WorldgenEvents.add` | 添加世界生成(如矿石等) | ❌ | | server\_scripts | `WorldgenEvents.remove` | 移除世界生成(如矿石等) | ❌ | | server\_scripts | `NetworkEvents.fromServer` | 客户端接收服务端网络包 | ✔ | | server\_scripts | `NetworkEvents.fromClient` | 服务端接收客户端网络包 | ✔ | | server\_scripts | `ItemEvents.modification` | 游戏内容修改(用于修改物品) | ❌ | | server\_scripts | `ItemEvents.toolTierRegistry` | 工具等级注册 | ❌ | | server\_scripts | `ItemEvents.armorTierRegistry` | 护甲等级注册 | ❌ | | server\_scripts | `ItemEvents.pickedUp` | 捡起物品事件 | ❌ | | server\_scripts | `ItemEvents.dropped` | 丢弃物品事件 | ❌ | | server\_scripts | `ItemEvents.entityInteracted` | 物品与实体交互事件 | ❌ | | server\_scripts | `ItemEvents.crafted` | 物品合成事件 | ❌ | | server\_scripts | `ItemEvents.smelted` | 物品烧炼事件 | ❌ | | server\_scripts | `ItemEvents.foodEaten` | 食用食物类物品事件 | ❌ | | client\_scripts | `ItemEvents.clientLeftClicked` | 左键单击事件(客户端侧) | ❌ | | server\_scripts | `ItemEvents.firstLeftClicked` | 左键单击事件(服务端侧) | ❌ | | client\_scripts | `ItemEvents.clientLeftClicked` | 右键单击事件(客户端侧) | ❌ | | server\_scripts | `ItemEvents.firstRightClicked` | 右键单击事件(服务端侧) | ❌ | | server\_scripts | `ItemEvents.tooltip` | 物品悬浮提示修改 | ❌ | | server\_scripts | `ItemEvents.modelProperties` | 物品模型修改 | ❌ | | server\_scripts | `BlockEvents.modification` | 游戏内容修改(用于修改方块) | ❌ | | server\_scripts | `BlockEvents.rightClicked` | 方块右键单击事件 | ❌ | | server\_scripts | `BlockEvents.leftClicked` | 方块左键单击事件 | ❌ | | server\_scripts | `BlockEvents.placed` | 方块放置事件 | ❌ | | server\_scripts | `BlockEvents.broken` | 方块被破坏事件 | ❌ | | server\_scripts | `BlockEvents.detectorChanged` | 检测方块状态改变事件 | ❌ | | server\_scripts | `BlockEvents.detectorPowered` | 检测方块被红石充能事件 | ❌ | | server\_scripts | `BlockEvents.detectorUnpowered` | 检测方块红石充能结束事件 | ❌ | | server\_scripts | `EntityEvents.death` | 实体死亡事件 | ❌ | | server\_scripts | `EntityEvents.hurt` | 实体受伤事件 | ❌ | | server\_scripts | `EntityEvents.checkSpawn` | 实体检查生成位置事件 | ❌ | | server\_scripts | `EntityEvents.spawned` | 实体生成事件 | ❌ | | server\_scripts | `PlayerEvents.loggedIn` | 玩家登入事件 | ❌ | | server\_scripts | `PlayerEvents.loggedOut` | 玩家登出事件 | ❌ | | server\_scripts | `PlayerEvents.cloned` | 玩家克隆事件 | ❌ | | server\_scripts | `PlayerEvents.tick` | 玩家Tick事件 | ❌ | | server\_scripts | `PlayerEvents.chat` | 玩家聊天事件 | ❌ | | server\_scripts | `PlayerEvents.decorateChat` | - | ❌ | | server\_scripts | `PlayerEvents.advancement` | 玩家成就事件 | ✔ | | server\_scripts | `PlayerEvents.inventoryOpened` | 玩家打开背包事件 | ❌ | | server\_scripts | `PlayerEvents.inventoryClosed` | 玩家关闭背包事件 | ❌ | | server\_scripts | `PlayerEvents.inventoryChanged` | 玩家背包物品变更事件 | ❌ | | server\_scripts | `PlayerEvents.chestOpened` | 玩家打开箱子事件 | ❌ | | server\_scripts | `PlayerEvents.chestClosed` | 玩家关闭箱子事件 | ❌ | ## 二、常用模组事件 #### JEI: | 脚本类型 | 方法 | 描述 | 是否可被取消 | | --------------- | ---------------------------- | -------------- | ------------ | | client\_scripts | `JEIEvents.subtypes` | 子类型 | ❌ | | client\_scripts | `JEIEvents.hideItems` | 隐藏物品 | ❌ | | client\_scripts | `JEIEvents.hideFluids` | 隐藏流体 | ❌ | | client\_scripts | `JEIEvents.hideCustom` | 隐藏自定义类型 | ❌ | | client\_scripts | `JEIEvents.removeCategories` | 移除类型 | ❌ | | client\_scripts | `JEIEvents.removeRecipes` | 移除配方 | ❌ | | client\_scripts | `JEIEvents.addItems` | 添加物品 | ❌ | | client\_scripts | `JEIEvents.addFluids` | 添加流体 | ❌ | | client\_scripts | `JEIEvents.information` | - | ❌ | #### REI: | 脚本类型 | 方法 | 描述 | 是否可被取消 | | --------------- | ---------------------------- | -------- | ------------ | | client\_scripts | `REIEvents.hide` | 隐藏类型 | No | | client\_scripts | `REIEvents.add` | 添加类型 | No | | client\_scripts | `REIEvents.information` | - | No | | client\_scripts | `REIEvents.removeCategories` | 移除类别 | No | | client\_scripts | `REIEvents.groupEntries` | 注册表 | No | #### GameStages: | 脚本类型 | 方法 | 描述 | 是否可被取消 | | --------------- | ------------------------------ | -------- | ------------ | | server\_scripts | `GameStageEvents.stageAdded` | 添加阶段 | No | | server\_scripts | `GameStageEvents.stageRemoved` | 移除阶段 | No | --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/the-start/event-list.md --- # 1.4 事件列表 *** 以下为KubeJS内置的部分事件,其他模组或附属还可能添加其他事件。 | **事件ID** | **是否可被取消** | 描述 | **类型** | | ------------------------------ | ---------- | ----------- | ------- | | init | 否 | - | Startup | | postinit | 否 | - | Startup | | loaded | 否 | - | Startup | | command.registry | 否 | 注册命令 | Server | | command.run | 可 | 运行命令 | Server | | client.init | 否 | - | Client | | client.debug\_info.left | 否 | 客户端左侧F3事件 | Client | | client.debug\_info.right | 否 | 客户端右侧F3事件 | Client | | client.logged\_in | 否 | 客户端登入事件 | Client | | client.logged\_out | 否 | 客户端登出事件 | Client | | client.tick | 否 | 客户端Tick | Client | | server.load | 否 | 服务器加载事件 | Server | | server.unload | 否 | 服务器卸载事件 | Server | | server.tick | 否 | 服务器Tick | Server | | server.datapack.first | 否 | 高优先度数据包加载 | Server | | server.datapack.last | 否 | 低优先度数据包加载 | Server | | recipes | 否 | 配方事件 | Server | | world.load | 否 | 事件加载事件 | Server | | world.unload | 否 | 世界卸载事件 | Server | | world.tick | 否 | 世界Tick | Server | | world.explosion.pre | 可 | 爆炸前事件 | Server | | world.explosion.post | 否 | 爆炸后事件 | Server | | player.logged\_in | 否 | 玩家登入事件 | Server | | player.logged\_out | 否 | 玩家登出事件 | Server | | player.tick | 否 | 玩家Tick | Server | | player.data\_from\_server.<信道> | 可 | 从服务器接收信息 | Client | | player.data\_from\_client.<信道> | 可 | 从客户端接收信息 | Server | | player.chat | 可 | 玩家聊天事件 | Server | | player.advancement | 否 | 玩家成就事件 | Server | | player.inventory.opened | 否 | 玩家打开库存事件 | Server | | player.inventory.closed | 否 | 玩家关闭库存事件 | Server | | player.inventory.changed | 否 | 玩家库存改变事件 | Server | | player.chest.opened | 否 | 玩家打开箱子事件 | Server | | player.chest.closed | 否 | 玩家关闭箱子事件 | Server | | entity.death | 可 | 实体死亡事件 | Server | | entity.attack | 可 | 实体攻击事件 | Server | | entity.drops | 可 | 实体摔落事件 | Server | | entity.check\_spawn | 可 | 实体检查生成位置事件 | Server | | entity.spawned | 可 | 实体生成事件 | Server | | block.registry | 否 | 方块注册事件 | Startup | | block.missing\_mappings | 否 | - | Server | | block.tags | 否 | 方块tag事件 | Server | | block.right\_click | 可 | 右击方块事件 | Server | | block.left\_click | 可 | 左击方块事件 | Server | | block.place | 可 | 放置方块事件 | Server | | block.break | 可 | 破坏方块事件 | Server | | block.drops | 否 | 方块掉落事件 | Server | | item.food\_eaten | 可 | 食物食用事件 | Server | | item.registry | 否 | 物品注册事件 | Startup | | item.missing\_mappings | 否 | - | Server | | item.tags | 否 | 物品tag事件 | Server | | item.right\_click | 可 | 物品右击事件 | Client | | item.right\_click\_empty | 否 | 物品右击事件(空手) | Client | | item.left\_click | 否 | 物品左击事件 | Client | | item.entity\_interact | 可 | 物品实体交互事件 | Server | | item.modification | 否 | 物品修改事件 | Startup | | item.pickup | 可 | 物品拾取事件 | Server | | item.tooltip | 否 | 物品tooltip事件 | Client | | item.toss | 可 | 物品丢出事件 | Server | | item.crafted | 否 | 物品合成事件 | Server | | item.smelted | 否 | 物品熔炼事件 | Server | | fluid.registry | 否 | 流体注册事件 | Startup | | fluid.tags | 否 | 流体tag事件 | Server | | entity\_type.tags | 否 | 实体类型tag事件 | Server | | worldgen.add | 否 | 世界生成添加 | Startup | | worldgen.remove | 否 | 世界生成移除 | Startup | --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/readme/event-list.md --- # 1.4 事件列表 ## 一、事件列表 | 脚本类型 | 方法 | 描述 | 是否可取消 | | ---------------- | ----------------------------------------------- | ---------------------------------- | ---------- | | startup\_scripts | `StartupEvents.init` | - | ❌ | | startup\_scripts | `StartupEvents.postInit` | - | ❌ | | startup\_scripts | `StartupEvents.registry` | 注册游戏内容(用于添加物品,附魔等) | ❌ | | client\_scripts | `ClientEvents.highPriorityAssets` | - | ❌ | | client\_scripts | `ClientEvents.init` | - | ❌ | | client\_scripts | `ClientEvents.loggedIn` | 登入世界 | ❌ | | client\_scripts | `ClientEvents.loggedOut` | 登出世界 | ❌ | | client\_scripts | `ClientEvents.tick` | 客户端Tick事件 | ❌ | | client\_scripts | `ClientEvents.painterUpdated` | Painter API | ❌ | | client\_scripts | `ClientEvents.leftDebugInfo` | F3界面左侧信息 | ❌ | | client\_scripts | `ClientEvents.rightDebugInfo` | F3界面右侧信息 | ❌ | | client\_scripts | `ClientEvents.paintScreen` | 绘制Screen | ❌ | | server\_scripts | `ServerEvents.lowPriorityData` | 数据包事件 - 低优先度 | ❌ | | server\_scripts | `ServerEvents.highPriorityData` | 数据包事件 - 高优先度 | ❌ | | server\_scripts | `SeverEvents.loaded` | 服务端加载 | ❌ | | server\_scripts | `ServerEvents.unloaded` | 服务端卸载 | ❌ | | server\_scripts | `ServerEvents.tick` | 服务端Tick事件 | ❌ | | server\_scripts | `ServerEvents.tags` | 标签添加或修改 | ❌ | | server\_scripts | `ServerEvents.commandRegistry` | 命令注册事件 | ❌ | | server\_scripts | `ServerEvents.command` | 命令执行事件 | ✔ | | server\_scripts | `ServerEvents.customCommand` | 自定义命令事件 | ✔ | | server\_scripts | `ServerEvents.recipes` | 配方添加或修改 | ❌ | | server\_scripts | `ServerEvents.afterRecipes` | 配方加载完成后事件 | ❌ | | server\_scripts | `ServerEvents.specialRecipeSerializers` | 配方序列化相关事件 | ❌ | | server\_scripts | `ServerEvents.compostableRecipes` | 堆肥配方添加或修改 | ❌ | | server\_scripts | `ServerEvents.recipeTypeRegistry` | 配方类型注册 | ❌ | | server\_scripts | `ServerEvents.genericLootTable`等,详见后续章节 | LootTable相关事件 | ❌ | | server\_scripts | `LevelEvents.loaded` | 世界加载事件 | ❌ | | server\_scripts | `LevelEvents.unloaded` | 世界卸载事件 | ❌ | | server\_scripts | `LevelEvents.tick` | 世界Tick事件 | ❌ | | server\_scripts | `LevelEvents.beforeExplosion` | 爆炸发生前事件 | ✔ | | server\_scripts | `LevelEvents.afterExplosion` | 爆炸发生后事件 | ❌ | | server\_scripts | `WorldgenEvents.add` | 添加世界生成(如矿石等) | ❌ | | server\_scripts | `WorldgenEvents.remove` | 移除世界生成(如矿石等) | ❌ | | server\_scripts | `NetworkEvents.fromServer` | 客户端接收服务端网络包 | ✔ | | server\_scripts | `NetworkEvents.fromClient` | 服务端接收客户端网络包 | ✔ | | server\_scripts | `ItemEvents.modification` | 游戏内容修改(用于修改物品) | ❌ | | server\_scripts | `ItemEvents.toolTierRegistry` | 工具等级注册 | ❌ | | server\_scripts | `ItemEvents.armorTierRegistry` | 护甲等级注册 | ❌ | | server\_scripts | `ItemEvents.pickedUp` | 捡起物品事件 | ❌ | | server\_scripts | `ItemEvents.dropped` | 丢弃物品事件 | ❌ | | server\_scripts | `ItemEvents.entityInteracted` | 物品与实体交互事件 | ❌ | | server\_scripts | `ItemEvents.crafted` | 物品合成事件 | ❌ | | server\_scripts | `ItemEvents.smelted` | 物品烧炼事件 | ❌ | | server\_scripts | `ItemEvents.foodEaten` | 食用食物类物品事件 | ❌ | | client\_scripts | `ItemEvents.clientLeftClicked` | 左键单击事件(客户端侧) | ❌ | | server\_scripts | `ItemEvents.firstLeftClicked` | 左键单击事件(服务端侧) | ❌ | | client\_scripts | `ItemEvents.clientLeftClicked` | 右键单击事件(客户端侧) | ❌ | | server\_scripts | `ItemEvents.firstRightClicked` | 右键单击事件(服务端侧) | ❌ | | server\_scripts | `ItemEvents.tooltip` | 物品悬浮提示修改 | ❌ | | server\_scripts | `ItemEvents.modelProperties` | 物品模型修改 | ❌ | | server\_scripts | `BlockEvents.modification` | 游戏内容修改(用于修改方块) | ❌ | | server\_scripts | `BlockEvents.rightClicked` | 方块右键单击事件 | ❌ | | server\_scripts | `BlockEvents.leftClicked` | 方块左键单击事件 | ❌ | | server\_scripts | `BlockEvents.placed` | 方块放置事件 | ❌ | | server\_scripts | `BlockEvents.broken` | 方块被破坏事件 | ❌ | | server\_scripts | `BlockEvents.detectorChanged` | 检测方块状态改变事件 | ❌ | | server\_scripts | `BlockEvents.detectorPowered` | 检测方块被红石充能事件 | ❌ | | server\_scripts | `BlockEvents.detectorUnpowered` | 检测方块红石充能结束事件 | ❌ | | server\_scripts | `EntityEvents.death` | 实体死亡事件 | ❌ | | server\_scripts | `EntityEvents.hurt` | 实体受伤事件 | ❌ | | server\_scripts | `EntityEvents.checkSpawn` | 实体检查生成位置事件 | ❌ | | server\_scripts | `EntityEvents.spawned` | 实体生成事件 | ❌ | | server\_scripts | `PlayerEvents.loggedIn` | 玩家登入事件 | ❌ | | server\_scripts | `PlayerEvents.loggedOut` | 玩家登出事件 | ❌ | | server\_scripts | `PlayerEvents.cloned` | 玩家克隆事件 | ❌ | | server\_scripts | `PlayerEvents.tick` | 玩家Tick事件 | ❌ | | server\_scripts | `PlayerEvents.chat` | 玩家聊天事件 | ❌ | | server\_scripts | `PlayerEvents.decorateChat` | - | ❌ | | server\_scripts | `PlayerEvents.advancement` | 玩家成就事件 | ✔ | | server\_scripts | `PlayerEvents.inventoryOpened` | 玩家打开背包事件 | ❌ | | server\_scripts | `PlayerEvents.inventoryClosed` | 玩家关闭背包事件 | ❌ | | server\_scripts | `PlayerEvents.inventoryChanged` | 玩家背包物品变更事件 | ❌ | | server\_scripts | `PlayerEvents.chestOpened` | 玩家打开箱子事件 | ❌ | | server\_scripts | `PlayerEvents.chestClosed` | 玩家关闭箱子事件 | ❌ | ## 二、常用模组事件 #### JEI: | 脚本类型 | 方法 | 描述 | 是否可被取消 | | --------------- | ---------------------------- | -------------- | ------------ | | client\_scripts | `JEIEvents.subtypes` | 子类型 | ❌ | | client\_scripts | `JEIEvents.hideItems` | 隐藏物品 | ❌ | | client\_scripts | `JEIEvents.hideFluids` | 隐藏流体 | ❌ | | client\_scripts | `JEIEvents.hideCustom` | 隐藏自定义类型 | ❌ | | client\_scripts | `JEIEvents.removeCategories` | 移除类型 | ❌ | | client\_scripts | `JEIEvents.removeRecipes` | 移除配方 | ❌ | | client\_scripts | `JEIEvents.addItems` | 添加物品 | ❌ | | client\_scripts | `JEIEvents.addFluids` | 添加流体 | ❌ | | client\_scripts | `JEIEvents.information` | - | ❌ | #### REI: | 脚本类型 | 方法 | 描述 | 是否可被取消 | | --------------- | ---------------------------- | -------- | ------------ | | client\_scripts | `REIEvents.hide` | 隐藏类型 | No | | client\_scripts | `REIEvents.add` | 添加类型 | No | | client\_scripts | `REIEvents.information` | - | No | | client\_scripts | `REIEvents.removeCategories` | 移除类别 | No | | client\_scripts | `REIEvents.groupEntries` | 注册表 | No | #### GameStages: | 脚本类型 | 方法 | 描述 | 是否可被取消 | | --------------- | ------------------------------ | -------- | ------------ | | server\_scripts | `GameStageEvents.stageAdded` | 添加阶段 | No | | server\_scripts | `GameStageEvents.stageRemoved` | 移除阶段 | No | --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/worldgen.md --- # 10 世界生成 *** 在KubeJS 6中,其世界生成相关内容相比于1.18.2基本没有修改。本节旨在以一种更加详细与清晰的方法讲述如何使用KubeJS修改世界生成。 ## 一、世界生成事件 ### 1、事件监听 `WorldgenEvents`事件组可于世界相关修改,其包含`WorldgenEvents.add`和`WorldgenEvents.remove`两个事件: ```js WorldgenEvents.add(event => { // code here }) WorldgenEvents.remove(event => { // code here }) ``` ### 2、事件方法 `WorldgenEvents.add`: | 方法 | 描述 | | ---------------------------------------------------------------------------- | ------------------------------ | | addLake(Consumer 湖) | 在世界生成中添加湖 | | addOre(Consumer 矿石) | 在世界生成中添加湖 | | getAnchors() | 获取高度锚(`VerticalAnchor`) | | addSpawn(BiomeFilter 群系过滤器, MobCategory 生成类型, 字符串 怪物) | 添加怪物生成 | | addSpawn(MobCategory 生成类型, 字符串 怪物) | 添加怪物生成 | | addSpawn(Consumer 属性) | 添加怪物生成 | | addFeatureJson(BiomeFilter 群系过滤器, JsonObject 结构) | 添加结构 | | addFeatureJson(BiomeFilter 群系过滤器, ResourceLocation ID, JsonObject 结构) | 添加结构 | `WorldgenEvents.remove` | 方法 | 描述 | | -------------------------------------------------------------------------------------------------------- | -------------------------- | | removeSpawns(Consumer 移除生成) | 移除怪物生成 | | removeAllFeatures(BiomeFilter 群系过滤器) | 移除所有符合条件的结构 | | printSpawns(MobCategory 生成类型) | 打印所有符合条件的怪物生成 | | printFeatures(DecorationGenerationStep 生成阶段) | 打印所有符合条件的结构 | | printSpawns() | 打印所有怪物生成 | | removeFeatureById(BiomeFilter 群系过滤器, DecorationGenerationStep 生成阶段, ResourceLocation\[] 结构ID) | 根据ID移除结构 | | printFiltered(DecorationGenerationStep 生成阶段) | 打印符合条件的被过滤的结构 | | printFiltered(DecorationGenerationStep 生成阶段, BiomeFilter 群系过滤器) | 打印符合条件的被过滤的结构 | | printFeaturesForType(DecorationGenerationStep 生成阶段, BiomeFilter 群系过滤器, 布尔值 是否待移除后) | 打印指定类型的结构 | | removeAllFeatures() | 移除所有结构 | | removeAllFeatures(BiomeFilter 群系过滤器, DecorationGenerationStep 生成阶段) | 移除所有符合条件的结构 | | removeAllSpawns() | 移除所有怪物生成 | | removeFeatureById(DecorationGenerationStep 生成阶段, ResourceLocation\[] 结构ID) | 根据ID移除结构 | | removeOres(Consumer 移除生成) | 移除所有符合条件的矿石 | | printFeatures() | 打印所有结构 | | printFeatures(DecorationGenerationStep 生成阶段, BiomeFilter 群系过滤器) | 打印所有符合条件的结构 | MobCatrgory支持以下值: ``` "monster" | "underground_water_creature" | "misc" | "creature" | "water_ambient" | "axolotls" | "ambient" | "water_creature" ``` ## 二、常用组件 ### 1、群系过滤器 你可以使用群系类别(Biome Categories)来作为群系过滤器。 下方示例以添加矿石为例展示的群系过滤器的使用方法。 ``` WorldgenEvents.add(event => { event.addOre((ore) => { // 基础群系过滤器: ore.biomes = "minecraft:plains" // 仅在单一群系生成 ore.biomes = /^minecraft:.*/ // 在所有ID与给定格式相符的群系中生成(此处为在所有原版群系中生成) ore.biomes = ""#minecraft:is_forest" // 过滤器可以通过与、或、非三种逻辑任意组合 ore.biomes = {} // 空的“与”过滤器(默认为真) ore.biomes = [] // 空的“或”过滤器(默认为真) ore.biomes = { not: "minecraft:ocean" // “非”过滤器(除"minecaraft:ocean"以外的群系) } // 因为“与”过滤器可以被表达为map,所以其还可以被表达为以下形式 ore.biomes = { id: "minecraft:plains", id: /^minecraft:.*/, // 正则表达式 tag: '#minecraft:is_forest' } // 你可以将以上的各种过滤器组合起来,它们会被独立计算 // 在下面这种情况中,矿石将在符合以下条件之一的群系中生成: // 1、平原 // 2、群系类型带有“#minecraft:is_forest”的非下界或海洋类群系。 ore.biomes = [ "minecraft:plains", { tag: '#minecraft:is_forest' not: [ '#nether', { id: "minecraft:ocean" } ] }, ] }) }) ``` ### 2、规则测试和目标方块 自1.18以后,世界生成变更为了“基于目标”的替换系统,也就是说在同一种结构中,你可以指定以特定方块来替换特定的目标方块。(例如在原版的生成矿石中,石头被普通矿石替代,而深板岩则被深板岩矿石所替代) 每个目标方块都有一个“规则测试”(RuleTest)作为输入(用于检测目标方块的方块状态是否应被替换)并生成一个特定的输出方块状态。在KubeJS脚本层面上,这两个概念都被表达为相同的类:*BlockStatePredicate*。 就格式上来说,BlockStatePredicate 与 群系过滤器 非常相似,即它们都能使用AND或者OR合并,并可用于匹配以下三种类型的条件: 1. 方块(该类型被解析为字符串,使用形如 `"minecraft:stone"`来匹配石头) 2. 方块状态(该类型被解析为后加数组形式的方块状态的方块ID字符串,使用形如`"minecraft:furnace[lit=true]"`来匹配燃烧中的熔炉方块。你可以使用F3界面来获得准星的方块状态,或者使用调试棒来获得方块状态可能的值。) 3. 方块标签(该类型被解析为**类似**标签的形式,使用形如`"#minecraft:base_stone_overworld"`来匹配主世界中生成在地下的所有类型的石头。**注意使用方块标签而不是物品标签**,部分情况下他们可能不同,你可以在F3界面获得方块标签。) 注: * 你可以在方块过滤器中使用正则表达式,`/^mekanism:.*_ore$/`将匹配所有通用机械中以`_ore`结尾的方块。注意,这种写法不会匹配方块状态! * 你可以使用JavaScript Object来提供规则测试(它将会像原版解析JSON或NBT那样被解析)。例如,你可以通过这种方式设置匹配生效的概率。 ### 3、高度提供器 另一个有些复杂的系统可能是“高度提供器”(Height Providers)系统,其被用于判定指定方块可能生成的Y坐标值和生成频率。与该系统同时使用的还有“垂直锚”(Vertical Anchors)概念,其可以用于获取相对于某锚点的相对高度(例如相对于世界最高点或最低点)。 在KubeJS中,该系统被简化以使其更容易在脚本开发中被使用:你可以使用`AddOreProperties`中的`uniformHeight` 和 `traingleHeight`方法来使用两种最常见的矿石生成逻辑(Uniform:在两个锚点间的任意位置有等大的概率生成矿石。Triangle:生成概率从两个锚点的中点向两锚点递减)。垂直锚概念也被简化了,你可以使用`AddOreProperties`中的辅助方法`aboveBottom / belowTop`(在世界最低点以上 / 在世界最高点以下)或新版本KubeJS中的`VerticalAnchor`Wrapper Class;此外,你还可以以数字形式直接指定绝对高度。 ### 4、生成阶段:DecorationGenerationStep 在原版游戏中,世界生成阶段按顺序如下: 1. `raw_generation` 2. `lakes` 3. `local_modifications` 4. `underground_structures` 5. `surface_structures` 6. `strongholds` 7. `underground_ores` 8. `underground_decoration` 9. `fluid_springs` 10. `vegetal_decoration` 11. `top_layer_modification` ## 三、属性设置 ### 1、`AddOreProperties`:矿石添加属性设置 | 方法 | 描述 | 补充说明 | | ---------------------------------------------------------- | -------------------------- | --------------------------- | | aboveBottom(整形 距离) | 设置距离世界底部的高度 | - | | size(s: any) | 设置最大矿簇大小 | - | | bottom() | 设置生成在世界底部 | 注:未经测试 | | belowTop(整形 距离) | 设置距离世界顶部的高度 | | | count(整形 数字) | 设置矿簇数量 | 接受单个数字 | | addTarget(RuleTest 规则测试, BlockStatePredicate 目标状态) | 添加目标方块 | 使用方法见下 | | squared() | 设置为方形布局 | - | | triangleHeight(整形 最小值, 整形 最大值) | 设置traingleHeight生成类型 | 见`高度提供器` | | triangleHeight(垂直锚 位置1, 垂直锚 位置2) | 设置traingleHeight生成类型 | 见`高度提供器` | | top() | 设置生成在世界顶部 | 注:未经测试 | | count(IntProvider 整形提供器) | 设置矿簇数量 | 接受区间(形如\[15,20]) | | count(整形 最小值, 整形 最大值) | 设置矿簇数量范围 | - | | chance(c: any) | 设置生成几率 | 默认为0,详细信息见示例脚本 | | uniformHeight(垂直锚 位置1, 垂直锚 位置2) | 设置uniformHeight生成类型 | 见`高度提供器` | | uniformHeight(整形 最小值, 整形 最大值) | 设置uniformHeight生成类型 | 见`高度提供器` | | 属性 "height": HeightRangePlacement | 设置生成高度范围 | 见`高度提供器` | | 属性 "retrogen" | 是否在已生成区块中重复生成 | - | | 属性 "count": IntProvider | 设置矿簇数量 | - | | 属性 "worldgenLayer": DecorationGenerationStep | 设置所属生成步骤 | - | | 属性 "chance": any | 设置几率 | 默认为0,详细信息见示例脚本 | | 属性 "squared": 布尔值 | 设置为平方形布局 | - | | 属性 "noSurface": 浮点型 | 设置暴露于空气中的概率 | 0~1 | | 属性 "biomes": BiomeFilter | 设置群系过滤器 | - | | 属性 "size": any | 设置矿簇最大大小 | - | | 属性 "id": ResourceLocation | 设置ID | - | | 属性 "targets": List\ | 设置目标列表 | - | ### 2、`AddSpawnProperties`:实体添加属性设置 | 方法 | 描述 | | ----------------------------------- | --------------------------- | | setEntity(s: string) | 设置实体ID | | setCategory(s: string) | 设置实体类别(MobCatrgory) | | 属性 "minCount": any | 设置实体最小数量 | | 属性 "biomes": Internal.BiomeFilter | 设置群系过滤器 | | 属性 "maxCount": any | 设置最大数量 | | 属性 "weight": any | 设置生成权重 | ### 3、`RemoveOresProperties`:矿石移除属性 | 方法 | 描述 | | ---------------------------------------------- | ---------------- | | 属性 "worldgenLayer": DecorationGenerationStep | 匹配所属生成步骤 | | 属性 "biomes": BiomeFilter | 匹配群系过滤器 | | 属性 "blocks": BlockStatePredicate | 匹配方块状态谓词 | ### 4、`RemoveSpawnsProperties`:实体生成移除属性 | 方法 | 描述 | | -------------------------- | ------------------ | | 属性 "mobs": MobFilter | 匹配生物实体过滤器 | | 属性 "biomes": BiomeFilter | 匹配生物群系过滤器 | ## 四、示例脚本 ``` WorldgenEvents.add(event => { // 获取垂直锚 const {anchors} = event event.addOre((ore) => { ore.id = "kubejs:glowstone_test_lmao" // 指定当前结构的ID(可选,但推荐指定) ore.biomes = { not: 'minecraft:savanna' // 群系过滤器(可选) } // 添加目标示例 ore.addTarget('#minecraft:stone_ore_replaceables', 'minecraft:glowstone') // 将目标(#minecraft:stone_ore_replaceables标签下的方块)替换为minecraft:glowstone ore.addTarget('minecraft:deepslate', 'minecraft:nether_wart_block') // 将目标(minecraft:deepslate)替换为minecraft:nether_wart_block ore.addTarget([ 'minecraft:gravel', /minecraft:(.*)_dirt/ ], 'minecraft:tnt') // 将目标(minecraft:gravel和所有类型的泥土)替换为minecraft:tnt ore.count([15, 50]) // 设置矿簇的大小(15~50中随机选择),在此处你可以使用单个数字来生成固定数量的方块。 .squared() // 设置跨区块生成矿石 .triangleHeight( // (高度提供器)使用Triangular分布模式生成矿石(具体见上文) anchors.aboveBottom(32), // 下边界锚,在世界最低高度(Y=-64)以上32格(即Y=-32) anchors.absolute(96) // 上边界锚,固定高度(Y = 96) ) // 总的来说,矿石可能在Y = -32 ~ 96范围内生成,概率最大高度为Y = (-32 + 96) / 2 = 32 // 更多可选的参数,以下均为默认值 ore.size = 9 // 最大矿簇大小 ore.noSurface = 0.5 // 暴露于空气中的概率 ore.worldgenLayer = "underground_ores" // 矿石生成于世界生成中的阶段 ore.chance = 0 // 当该选项不为0且ore.count未被设置矿石将有1/n的概率在每个区块中生成 }) // 添加湖 // 该功能将要于原版中废弃 event.addLake((lake) => { lake.id = "kubejs:funny_lake" // BlockStatePredicate lake.chance = 4 lake.fluid = "minecraft:lava" lake.barrier = "minecraft:diamond_block" }) }) WorldgenEvents.remove(event => { console.info("HELP") //console.debugEnabled = true; // 打印给定群系过滤器中的所有结构 event.printFeatures('', 'minecraft:plains') event.removeOres((props) => { // 类似于添加矿石,移除矿石同样支持使用世界生成阶段 props.worldgenLayer = 'underground_ores' // 使用群系过滤 props.biomes = [{ category: "icy", }, { category: "savanna", }, { category: "mesa", }]; // 在上述给定的群系中移除铁矿和铜矿 // 注:此处无法使用标签 props.blocks = ["minecraft:iron_ore", "minecraft:copper_ore"] }) // 通过指定ID来移除结构(第一个参数为生成步骤) event.removeFeatureById('underground_ores', ['minecraft:ore_coal_upper', 'minecraft:ore_coal_lower']) }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/worldgen.md --- # 10 世界生成 *** 在KubeJS 6中,其世界生成相关内容相比于1.18.2基本没有修改。本节旨在以一种更加详细与清晰的方法讲述如何使用KubeJS修改世界生成。 ## 一、世界生成事件 ### 1、事件监听 `WorldgenEvents`事件组可于世界相关修改,其包含`WorldgenEvents.add`和`WorldgenEvents.remove`两个事件: ```js WorldgenEvents.add(event => { // code here }) WorldgenEvents.remove(event => { // code here }) ``` ### 2、事件方法 `WorldgenEvents.add`: | 方法 | 描述 | | ---------------------------------------------------------------------------- | ------------------------------ | | addLake(Consumer 湖) | 在世界生成中添加湖 | | addOre(Consumer 矿石) | 在世界生成中添加湖 | | getAnchors() | 获取高度锚(`VerticalAnchor`) | | addSpawn(BiomeFilter 群系过滤器, MobCategory 生成类型, 字符串 怪物) | 添加怪物生成 | | addSpawn(MobCategory 生成类型, 字符串 怪物) | 添加怪物生成 | | addSpawn(Consumer 属性) | 添加怪物生成 | | addFeatureJson(BiomeFilter 群系过滤器, JsonObject 结构) | 添加结构 | | addFeatureJson(BiomeFilter 群系过滤器, ResourceLocation ID, JsonObject 结构) | 添加结构 | `WorldgenEvents.remove` | 方法 | 描述 | | -------------------------------------------------------------------------------------------------------- | -------------------------- | | removeSpawns(Consumer 移除生成) | 移除怪物生成 | | removeAllFeatures(BiomeFilter 群系过滤器) | 移除所有符合条件的结构 | | printSpawns(MobCategory 生成类型) | 打印所有符合条件的怪物生成 | | printFeatures(DecorationGenerationStep 生成阶段) | 打印所有符合条件的结构 | | printSpawns() | 打印所有怪物生成 | | removeFeatureById(BiomeFilter 群系过滤器, DecorationGenerationStep 生成阶段, ResourceLocation\[] 结构ID) | 根据ID移除结构 | | printFiltered(DecorationGenerationStep 生成阶段) | 打印符合条件的被过滤的结构 | | printFiltered(DecorationGenerationStep 生成阶段, BiomeFilter 群系过滤器) | 打印符合条件的被过滤的结构 | | printFeaturesForType(DecorationGenerationStep 生成阶段, BiomeFilter 群系过滤器, 布尔值 是否待移除后) | 打印指定类型的结构 | | removeAllFeatures() | 移除所有结构 | | removeAllFeatures(BiomeFilter 群系过滤器, DecorationGenerationStep 生成阶段) | 移除所有符合条件的结构 | | removeAllSpawns() | 移除所有怪物生成 | | removeFeatureById(DecorationGenerationStep 生成阶段, ResourceLocation\[] 结构ID) | 根据ID移除结构 | | removeOres(Consumer 移除生成) | 移除所有符合条件的矿石 | | printFeatures() | 打印所有结构 | | printFeatures(DecorationGenerationStep 生成阶段, BiomeFilter 群系过滤器) | 打印所有符合条件的结构 | MobCatrgory支持以下值: ``` "monster" | "underground_water_creature" | "misc" | "creature" | "water_ambient" | "axolotls" | "ambient" | "water_creature" ``` ## 二、常用组件 ### 1、群系过滤器 你可以使用群系类别(Biome Categories)来作为群系过滤器。 下方示例以添加矿石为例展示的群系过滤器的使用方法。 ``` WorldgenEvents.add(event => { event.addOre((ore) => { // 基础群系过滤器: ore.biomes = "minecraft:plains" // 仅在单一群系生成 ore.biomes = /^minecraft:.*/ // 在所有ID与给定格式相符的群系中生成(此处为在所有原版群系中生成) ore.biomes = ""#minecraft:is_forest" // 过滤器可以通过与、或、非三种逻辑任意组合 ore.biomes = {} // 空的“与”过滤器(默认为真) ore.biomes = [] // 空的“或”过滤器(默认为真) ore.biomes = { not: "minecraft:ocean" // “非”过滤器(除"minecaraft:ocean"以外的群系) } // 因为“与”过滤器可以被表达为map,所以其还可以被表达为以下形式 ore.biomes = { id: "minecraft:plains", id: /^minecraft:.*/, // 正则表达式 tag: '#minecraft:is_forest' } // 你可以将以上的各种过滤器组合起来,它们会被独立计算 // 在下面这种情况中,矿石将在符合以下条件之一的群系中生成: // 1、平原 // 2、群系类型带有“#minecraft:is_forest”的非下界或海洋类群系。 ore.biomes = [ "minecraft:plains", { tag: '#minecraft:is_forest' not: [ '#nether', { id: "minecraft:ocean" } ] }, ] }) }) ``` ### 2、规则测试和目标方块 自1.18以后,世界生成变更为了“基于目标”的替换系统,也就是说在同一种结构中,你可以指定以特定方块来替换特定的目标方块。(例如在原版的生成矿石中,石头被普通矿石替代,而深板岩则被深板岩矿石所替代) 每个目标方块都有一个“规则测试”(RuleTest)作为输入(用于检测目标方块的方块状态是否应被替换)并生成一个特定的输出方块状态。在KubeJS脚本层面上,这两个概念都被表达为相同的类:*BlockStatePredicate*。 就格式上来说,BlockStatePredicate 与 群系过滤器 非常相似,即它们都能使用AND或者OR合并,并可用于匹配以下三种类型的条件: 1. 方块(该类型被解析为字符串,使用形如 `"minecraft:stone"`来匹配石头) 2. 方块状态(该类型被解析为后加数组形式的方块状态的方块ID字符串,使用形如`"minecraft:furnace[lit=true]"`来匹配燃烧中的熔炉方块。你可以使用F3界面来获得准星的方块状态,或者使用调试棒来获得方块状态可能的值。) 3. 方块标签(该类型被解析为**类似**标签的形式,使用形如`"#minecraft:base_stone_overworld"`来匹配主世界中生成在地下的所有类型的石头。**注意使用方块标签而不是物品标签**,部分情况下他们可能不同,你可以在F3界面获得方块标签。) 注: * 你可以在方块过滤器中使用正则表达式,`/^mekanism:.*_ore$/`将匹配所有通用机械中以`_ore`结尾的方块。注意,这种写法不会匹配方块状态! * 你可以使用JavaScript Object来提供规则测试(它将会像原版解析JSON或NBT那样被解析)。例如,你可以通过这种方式设置匹配生效的概率。 ### 3、高度提供器 另一个有些复杂的系统可能是“高度提供器”(Height Providers)系统,其被用于判定指定方块可能生成的Y坐标值和生成频率。与该系统同时使用的还有“垂直锚”(Vertical Anchors)概念,其可以用于获取相对于某锚点的相对高度(例如相对于世界最高点或最低点)。 在KubeJS中,该系统被简化以使其更容易在脚本开发中被使用:你可以使用`AddOreProperties`中的`uniformHeight` 和 `traingleHeight`方法来使用两种最常见的矿石生成逻辑(Uniform:在两个锚点间的任意位置有等大的概率生成矿石。Triangle:生成概率从两个锚点的中点向两锚点递减)。垂直锚概念也被简化了,你可以使用`AddOreProperties`中的辅助方法`aboveBottom / belowTop`(在世界最低点以上 / 在世界最高点以下)或新版本KubeJS中的`VerticalAnchor`Wrapper Class;此外,你还可以以数字形式直接指定绝对高度。 ### 4、生成阶段:DecorationGenerationStep 在原版游戏中,世界生成阶段按顺序如下: 1. `raw_generation` 2. `lakes` 3. `local_modifications` 4. `underground_structures` 5. `surface_structures` 6. `strongholds` 7. `underground_ores` 8. `underground_decoration` 9. `fluid_springs` 10. `vegetal_decoration` 11. `top_layer_modification` ## 三、属性设置 ### 1、`AddOreProperties`:矿石添加属性设置 | 方法 | 描述 | 补充说明 | | ---------------------------------------------------------- | -------------------------- | --------------------------- | | aboveBottom(整形 距离) | 设置距离世界底部的高度 | - | | size(s: any) | 设置最大矿簇大小 | - | | bottom() | 设置生成在世界底部 | 注:未经测试 | | belowTop(整形 距离) | 设置距离世界顶部的高度 | | | count(整形 数字) | 设置矿簇数量 | 接受单个数字 | | addTarget(RuleTest 规则测试, BlockStatePredicate 目标状态) | 添加目标方块 | 使用方法见下 | | squared() | 设置为方形布局 | - | | triangleHeight(整形 最小值, 整形 最大值) | 设置traingleHeight生成类型 | 见`高度提供器` | | triangleHeight(垂直锚 位置1, 垂直锚 位置2) | 设置traingleHeight生成类型 | 见`高度提供器` | | top() | 设置生成在世界顶部 | 注:未经测试 | | count(IntProvider 整形提供器) | 设置矿簇数量 | 接受区间(形如\[15,20]) | | count(整形 最小值, 整形 最大值) | 设置矿簇数量范围 | - | | chance(c: any) | 设置生成几率 | 默认为0,详细信息见示例脚本 | | uniformHeight(垂直锚 位置1, 垂直锚 位置2) | 设置uniformHeight生成类型 | 见`高度提供器` | | uniformHeight(整形 最小值, 整形 最大值) | 设置uniformHeight生成类型 | 见`高度提供器` | | 属性 "height": HeightRangePlacement | 设置生成高度范围 | 见`高度提供器` | | 属性 "retrogen" | 是否在已生成区块中重复生成 | - | | 属性 "count": IntProvider | 设置矿簇数量 | - | | 属性 "worldgenLayer": DecorationGenerationStep | 设置所属生成步骤 | - | | 属性 "chance": any | 设置几率 | 默认为0,详细信息见示例脚本 | | 属性 "squared": 布尔值 | 设置为平方形布局 | - | | 属性 "noSurface": 浮点型 | 设置暴露于空气中的概率 | 0~1 | | 属性 "biomes": BiomeFilter | 设置群系过滤器 | - | | 属性 "size": any | 设置矿簇最大大小 | - | | 属性 "id": ResourceLocation | 设置ID | - | | 属性 "targets": List\ | 设置目标列表 | - | ### 2、`AddSpawnProperties`:实体添加属性设置 | 方法 | 描述 | | ----------------------------------- | --------------------------- | | setEntity(s: string) | 设置实体ID | | setCategory(s: string) | 设置实体类别(MobCatrgory) | | 属性 "minCount": any | 设置实体最小数量 | | 属性 "biomes": Internal.BiomeFilter | 设置群系过滤器 | | 属性 "maxCount": any | 设置最大数量 | | 属性 "weight": any | 设置生成权重 | ### 3、`RemoveOresProperties`:矿石移除属性 | 方法 | 描述 | | ---------------------------------------------- | ---------------- | | 属性 "worldgenLayer": DecorationGenerationStep | 匹配所属生成步骤 | | 属性 "biomes": BiomeFilter | 匹配群系过滤器 | | 属性 "blocks": BlockStatePredicate | 匹配方块状态谓词 | ### 4、`RemoveSpawnsProperties`:实体生成移除属性 | 方法 | 描述 | | -------------------------- | ------------------ | | 属性 "mobs": MobFilter | 匹配生物实体过滤器 | | 属性 "biomes": BiomeFilter | 匹配生物群系过滤器 | ## 四、示例脚本 ``` WorldgenEvents.add(event => { // 获取垂直锚 const {anchors} = event event.addOre((ore) => { ore.id = "kubejs:glowstone_test_lmao" // 指定当前结构的ID(可选,但推荐指定) ore.biomes = { not: 'minecraft:savanna' // 群系过滤器(可选) } // 添加目标示例 ore.addTarget('#minecraft:stone_ore_replaceables', 'minecraft:glowstone') // 将目标(#minecraft:stone_ore_replaceables标签下的方块)替换为minecraft:glowstone ore.addTarget('minecraft:deepslate', 'minecraft:nether_wart_block') // 将目标(minecraft:deepslate)替换为minecraft:nether_wart_block ore.addTarget([ 'minecraft:gravel', /minecraft:(.*)_dirt/ ], 'minecraft:tnt') // 将目标(minecraft:gravel和所有类型的泥土)替换为minecraft:tnt ore.count([15, 50]) // 设置矿簇的大小(15~50中随机选择),在此处你可以使用单个数字来生成固定数量的方块。 .squared() // 设置跨区块生成矿石 .triangleHeight( // (高度提供器)使用Triangular分布模式生成矿石(具体见上文) anchors.aboveBottom(32), // 下边界锚,在世界最低高度(Y=-64)以上32格(即Y=-32) anchors.absolute(96) // 上边界锚,固定高度(Y = 96) ) // 总的来说,矿石可能在Y = -32 ~ 96范围内生成,概率最大高度为Y = (-32 + 96) / 2 = 32 // 更多可选的参数,以下均为默认值 ore.size = 9 // 最大矿簇大小 ore.noSurface = 0.5 // 暴露于空气中的概率 ore.worldgenLayer = "underground_ores" // 矿石生成于世界生成中的阶段 ore.chance = 0 // 当该选项不为0且ore.count未被设置矿石将有1/n的概率在每个区块中生成 }) // 添加湖 // 该功能将要于原版中废弃 event.addLake((lake) => { lake.id = "kubejs:funny_lake" // BlockStatePredicate lake.chance = 4 lake.fluid = "minecraft:lava" lake.barrier = "minecraft:diamond_block" }) }) WorldgenEvents.remove(event => { console.info("HELP") //console.debugEnabled = true; // 打印给定群系过滤器中的所有结构 event.printFeatures('', 'minecraft:plains') event.removeOres((props) => { // 类似于添加矿石,移除矿石同样支持使用世界生成阶段 props.worldgenLayer = 'underground_ores' // 使用群系过滤 props.biomes = [{ category: "icy", }, { category: "savanna", }, { category: "mesa", }]; // 在上述给定的群系中移除铁矿和铜矿 // 注:此处无法使用标签 props.blocks = ["minecraft:iron_ore", "minecraft:copper_ore"] }) // 通过指定ID来移除结构(第一个参数为生成步骤) event.removeFeatureById('underground_ores', ['minecraft:ore_coal_upper', 'minecraft:ore_coal_lower']) }) ``` --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_10_Tool.md description: 10 工具 相关教程 --- # 工具 ```java public class RubyPickaxe extends PickaxeItem { public RubyPickaxe() { super(ModItemTiers.RUBY,2,-3f,new Properties()); } } ``` 通过这一行代码,RubyPickaxe 类继承了 PickaxeItem 类的功能,并将其属性设置为与 RUBY 等级相符的值。这使得 RubyPickaxe 成为一个具有特定耐久度、速度和攻击力的镐类物品。 注册物品 ```java public static final Supplier RUBY_PICKAXE = ITEMS.register("ruby_pickaxe", RubyPickaxe::new); ``` 添加到创造模式物品栏 ```java pOutput.accept(ModItems.RUBY_PICKAXE.get()); ``` 添加物品模型 ```java this.basicItem(ModItems.RUBY_PICKAXE.get()); ``` 添加贴图 自己网上找了个贴图,故略 ## pickaxe类 在 PickaxeItem 类中,构造函数调用 super((float)pAttackDamageModifier, pAttackSpeedModifier, pTier, BlockTags.MINEABLE\_WITH\_PICKAXE, pProperties); 这一行代码的作用是初始化 PickaxeItem 类,并设置其属性 * (float)pAttackDamageModifier:这转换了 pAttackDamageModifier 参数为浮点数,用于设置镐的攻击伤害。 * pAttackSpeedModifier:这表示镐的攻击速度。攻击速度是一个负值,因为较低的值意味着更快的攻击速度。 * pTier:这表示镐的等级,与 RUBY 常量的等级相同。 * BlockTags.MINEABLE\_WITH\_PICKAXE:这指定哪些方块可以用镐挖掘。 * pProperties:这包含了一些额外的属性,如可堆叠性等 ## DiggerItem类 DiggerItem 类是 Minecraft 中的一个扩展类,它继承自 TieredItem 类并实现了 Vanishable 接口。这个类代表一个挖掘工具类物品,并定义了它的属性和行为。 * blocks:一个 TagKey,表示这个工具可以挖掘的方块集合。 * speed:工具的工作速度。 * attackDamageBaseline:工具的基本攻击伤害值。 构造函数 DiggerItem(float pAttackDamageModifier, float pAttackSpeedModifier, Tier pTier, TagKey pBlocks, Item.Properties pProperties):初始化 DiggerItem 类。 * getDestroySpeed(ItemStack pStack, BlockState pState):返回工具破坏特定方块的速度。 * hurtEnemy(ItemStack pStack, LivingEntity pTarget, LivingEntity pAttacker):处理工具伤害敌人的逻辑。 * mineBlock(ItemStack pStack, Level pLevel, BlockState pState, BlockPos pPos, LivingEntity pEntityLiving):处理使用工具挖掘方块的逻辑。 * getDefaultAttributeModifiers(EquipmentSlot pEquipmentSlot):获取工具的默认属性修改器。 * getAttackDamage():返回工具的基本攻击伤害值。 * isCorrectToolForDrops(BlockState pBlock):检查工具是否是挖掘给定方块的正确工具。 * isCorrectToolForDrops(ItemStack stack, BlockState state):一个重写方法,用于检查工具是否是挖掘给定方块的正确工具,考虑了物品堆叠。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_10_Tool.md description: 10 工具 相关教程 --- # 工具 ```java public class RubyPickaxe extends PickaxeItem { public RubyPickaxe() { super(ModItemTiers.RUBY,2,-3f,new Properties()); } } ``` 通过这一行代码,RubyPickaxe 类继承了 PickaxeItem 类的功能,并将其属性设置为与 RUBY 等级相符的值。这使得 RubyPickaxe 成为一个具有特定耐久度、速度和攻击力的镐类物品。 注册物品 ```java public static final Supplier RUBY_PICKAXE = ITEMS.register("ruby_pickaxe", RubyPickaxe::new); ``` 添加到创造模式物品栏 ```java pOutput.accept(ModItems.RUBY_PICKAXE.get()); ``` 添加物品模型 ```java this.basicItem(ModItems.RUBY_PICKAXE.get()); ``` 添加贴图 自己网上找了个贴图,故略 ## pickaxe类 在 PickaxeItem 类中,构造函数调用 super((float)pAttackDamageModifier, pAttackSpeedModifier, pTier, BlockTags.MINEABLE\_WITH\_PICKAXE, pProperties); 这一行代码的作用是初始化 PickaxeItem 类,并设置其属性 * (float)pAttackDamageModifier:这转换了 pAttackDamageModifier 参数为浮点数,用于设置镐的攻击伤害。 * pAttackSpeedModifier:这表示镐的攻击速度。攻击速度是一个负值,因为较低的值意味着更快的攻击速度。 * pTier:这表示镐的等级,与 RUBY 常量的等级相同。 * BlockTags.MINEABLE\_WITH\_PICKAXE:这指定哪些方块可以用镐挖掘。 * pProperties:这包含了一些额外的属性,如可堆叠性等 ## DiggerItem类 DiggerItem 类是 Minecraft 中的一个扩展类,它继承自 TieredItem 类并实现了 Vanishable 接口。这个类代表一个挖掘工具类物品,并定义了它的属性和行为。 * blocks:一个 TagKey,表示这个工具可以挖掘的方块集合。 * speed:工具的工作速度。 * attackDamageBaseline:工具的基本攻击伤害值。 构造函数 DiggerItem(float pAttackDamageModifier, float pAttackSpeedModifier, Tier pTier, TagKey pBlocks, Item.Properties pProperties):初始化 DiggerItem 类。 * getDestroySpeed(ItemStack pStack, BlockState pState):返回工具破坏特定方块的速度。 * hurtEnemy(ItemStack pStack, LivingEntity pTarget, LivingEntity pAttacker):处理工具伤害敌人的逻辑。 * mineBlock(ItemStack pStack, Level pLevel, BlockState pState, BlockPos pPos, LivingEntity pEntityLiving):处理使用工具挖掘方块的逻辑。 * getDefaultAttributeModifiers(EquipmentSlot pEquipmentSlot):获取工具的默认属性修改器。 * getAttackDamage():返回工具的基本攻击伤害值。 * isCorrectToolForDrops(BlockState pBlock):检查工具是否是挖掘给定方块的正确工具。 * isCorrectToolForDrops(ItemStack stack, BlockState state):一个重写方法,用于检查工具是否是挖掘给定方块的正确工具,考虑了物品堆叠。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/feature1/10-zi-ding-yi-chuang-kou-biao-ti-he-logo.md --- # 10:自定义窗口标题和logo *** 你可以通过修改`kubejs/config/client.properties`中的`title`一项来修改游戏窗口标题。 你可以将你想自定义的窗口logo放于`kubejs/config/packicon.png`,图片分辨率必须为16\*16或其的整数倍 请注意!图片的格式必须为`32-bit PNG` 而不是`Auto-detect/24-bit`! 也就是属性内的“位深度”应为`32` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/feature1/10-zi-ding-yi-chuang-kou-biao-ti-he-logo.md --- # 10:自定义窗口标题和logo *** 你可以通过修改`kubejs/config/client.properties`中的`title`一项来修改游戏窗口标题。 你可以将你想自定义的窗口logo放于`kubejs/config/packicon.png`,图片分辨率必须为16\*16或其的整数倍 请注意!图片的格式必须为`32-bit PNG` 而不是`Auto-detect/24-bit`! 也就是属性内的“位深度”应为`32` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/addons/addon-list.md --- # 11 常用附属模组清单 :::info This page is currently under construction and does not yet cover all aspects or information. We are working on it. Thank you for your understanding. ::: 在本节内,我们将介绍部分常用附属模组。 | 模组名称 | 作者 | 链接 | | -------- | ---- | ---- | | | | | | | | | | | | | --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/addons/addon-list.md --- # 11 常用附属模组清单 :::info This page is currently under construction and does not yet cover all aspects or information. We are working on it. Thank you for your understanding. ::: 在本节内,我们将介绍部分常用附属模组。 | 模组名称 | 作者 | 链接 | | -------- | ---- | ---- | | | | | | | | | | | | | --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_11_Armor.md description: 11 装备 相关教程 --- # 装备 ```java public enum ModArmorMaterial implements ArmorMaterial { RUBY("ruby", 33, Util.make(new EnumMap<>(ArmorItem.Type.class), map -> { map.put(ArmorItem.Type.BOOTS, 3); map.put(ArmorItem.Type.LEGGINGS, 6); map.put(ArmorItem.Type.CHESTPLATE, 8); map.put(ArmorItem.Type.HELMET, 3); }), 10, SoundEvents.ARMOR_EQUIP_DIAMOND, 2.0F, 0.0F, () -> Ingredient.of(ModItems.RUBY.get())); private static final EnumMap HEALTH_FUNCTION_FOR_TYPE = Util.make(new EnumMap<>(ArmorItem.Type.class), map -> { map.put(ArmorItem.Type.BOOTS, 13); map.put(ArmorItem.Type.LEGGINGS, 15); map.put(ArmorItem.Type.CHESTPLATE, 16); map.put(ArmorItem.Type.HELMET, 11); }); private final String name; private final int durabilityMultiplier; private final EnumMap protectionFunctionForType; private final int enchantmentValue; private final SoundEvent sound; private final float toughness; private final float knockbackResistance; private final LazyLoadedValue repairIngredient; private ModArmorMaterial( String pName, int pDurabilityMultiplier, EnumMap pProtectionFunctionForType, int pEnchantmentValue, SoundEvent pSound, float pToughness, float pKnockbackResistance, Supplier pRepairIngredient ) { this.name = pName; this.durabilityMultiplier = pDurabilityMultiplier; this.protectionFunctionForType = pProtectionFunctionForType; this.enchantmentValue = pEnchantmentValue; this.sound = pSound; this.toughness = pToughness; this.knockbackResistance = pKnockbackResistance; this.repairIngredient = new LazyLoadedValue<>(pRepairIngredient); } @Override public int getDurabilityForType(ArmorItem.Type pType) { return HEALTH_FUNCTION_FOR_TYPE.get(pType) * this.durabilityMultiplier; } @Override public int getDefenseForType(ArmorItem.Type pType) { return this.protectionFunctionForType.get(pType); } @Override public int getEnchantmentValue() { return this.enchantmentValue; } @Override public SoundEvent getEquipSound() { return this.sound; } @Override public Ingredient getRepairIngredient() { return this.repairIngredient.get(); } @Override public String getName() { return this.name; } @Override public float getToughness() { return this.toughness; } /** * Gets the percentage of knockback resistance provided by armor of the material. */ @Override public float getKnockbackResistance() { return this.knockbackResistance; } } ``` ModArmorMaterial 是一个枚举类,它实现了 ArmorMaterial 接口,用于表示Minecraft中自定义盔甲材料的属性。每个枚举常量代表一种不同的盔甲材料,具有不同的属性值。 name:盔甲材料的名称。 durabilityMultiplier:耐久度乘数,用于计算每件盔甲的耐久度。 protectionFunctionForType:一个 EnumMap,包含了不同盔甲类型(如靴子、裤子、胸甲、头盔)的防御值。 enchantmentValue:盔甲可以附魔的等级。 sound:装备盔甲时播放的声音事件。 toughness:盔甲的韧性,是对护甲值效果受伤害降低的防护 knockbackResistance:盔甲提供的抗击退效果的百分比。 repairIngredient:一个 LazyLoadedValue 对象,用于存储修理该材料盔甲所需的材料。 注册物品 ```java public static final Supplier RUBY_HELMET = ITEMS.register("ruby_helmet", () -> new ArmorItem(ModArmorMaterial.RUBY, ArmorItem.Type.HELMET, (new Item.Properties()))); public static final Supplier RUBY_CHESTPLATE = ITEMS.register("ruby_chestplate", () -> new ArmorItem(ModArmorMaterial.RUBY, ArmorItem.Type.CHESTPLATE, (new Item.Properties()))); public static final Supplier RUBY_LEGGINGS = ITEMS.register("ruby_leggings", () -> new ArmorItem(ModArmorMaterial.RUBY, ArmorItem.Type.LEGGINGS, (new Item.Properties()))); public static final Supplier RUBY_BOOTS = ITEMS.register("ruby_boots", () -> new ArmorItem(ModArmorMaterial.RUBY, ArmorItem.Type.BOOTS, (new Item.Properties()))); ``` 添加到创造模式物品栏 ```java public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab",() -> CreativeModeTab.builder() .withTabsBefore(CreativeModeTabs.COMBAT) .title(Component.translatable(EXAMPLE_MOD_TAB_STRING)) .icon(()->ModItems.RUBY.get().getDefaultInstance()) .displayItems((pParameters, pOutput) -> { pOutput.accept(ModItems.RUBY.get()); pOutput.accept(ModItems.MAGIC_INGOT.get()); pOutput.accept(ModItems.RUBY_APPLE.get()); pOutput.accept(ModItems.RUBY_SWORD.get()); pOutput.accept(ModItems.RUBY_PICKAXE.get()); pOutput.accept(ModItems.RUBY_CHESTPLATE.get()); pOutput.accept(ModItems.RUBY_HELMET.get()); pOutput.accept(ModItems.RUBY_LEGGINGS.get()); pOutput.accept(ModItems.RUBY_BOOTS.get()); pOutput.accept(ModBlocks.RUBY_BLOCK.get()); pOutput.accept(ModBlocks.LAMP_BLOCK.get()); }) .build()); ``` ## 添加材质和模型 ```java this.basicItem(ModItems.RUBY_HELMET.get()); this.basicItem(ModItems.RUBY_CHESTPLATE.get()); this.basicItem(ModItems.RUBY_LEGGINGS.get()); this.basicItem(ModItems.RUBY_BOOTS.get()); ``` 材质我直接使用了原版的钻石套装改了一下。你可以直接使用自己的材质 ## 添加人物穿上盔甲的材质 因为Minecraft将所有的盔甲材质写死在minecraft这个域下,所以我们得创建相对应的目录,创建如下的目录 ``` ├── assets │ ├── examplemod │ └── minecraft │ └── textures │ └── models │ └── armor ``` 命名格式为:盔甲材料\_layer\_1和盔甲材料\_layer\_2 你之前写的ModArmorMaterial枚举的name 的名称 ## ArmorItem类 ArmorItem 类是 Minecraft 中的一个扩展类,它继承自 Item 类并实现了 Equipable 接口。这个类代表一个盔甲类物品,并定义了它的属性和行为。 * type:表示盔甲类型(如头盔、胸甲、裤子、靴子)。 * defense:盔甲的基础防御值。 * toughness:盔甲的韧性,是对护甲值效果受伤害降低的防护 * knockbackResistance:盔甲提供的抗击退效果的百分比。 * material:盔甲的材料,决定了盔甲的耐久度、防御力、韧性、附魔等级等属性。 * defaultModifiers:一个 Multimap 对象,包含了默认的属性修改器,用于增强盔甲的防御力和韧性。 方法 * getType():返回盔甲类型。 * getEnchantmentValue():返回盔甲可以附魔的等级。 * getMaterial():返回盔甲材料。 * isValidRepairItem(ItemStack pToRepair, ItemStack pRepair):检查盔甲是否可以被修复。 * use(Level pLevel, Player pPlayer, InteractionHand pHand):处理盔甲的右键点击行为。 * getDefaultAttributeModifiers(EquipmentSlot pEquipmentSlot):获取盔甲的默认属性修改器。 * getDefense():返回盔甲的基础防御值。 * getToughness():返回盔甲的韧性。 * getEquipmentSlot():返回盔甲的装备槽位。 * getEquipSound():返回装备盔甲时播放的声音事件。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_11_Armor.md description: 11 装备 相关教程 --- # 装备 ```java public enum ModArmorMaterial implements ArmorMaterial { RUBY("ruby", 33, Util.make(new EnumMap<>(ArmorItem.Type.class), map -> { map.put(ArmorItem.Type.BOOTS, 3); map.put(ArmorItem.Type.LEGGINGS, 6); map.put(ArmorItem.Type.CHESTPLATE, 8); map.put(ArmorItem.Type.HELMET, 3); }), 10, SoundEvents.ARMOR_EQUIP_DIAMOND, 2.0F, 0.0F, () -> Ingredient.of(ModItems.RUBY.get())); private static final EnumMap HEALTH_FUNCTION_FOR_TYPE = Util.make(new EnumMap<>(ArmorItem.Type.class), map -> { map.put(ArmorItem.Type.BOOTS, 13); map.put(ArmorItem.Type.LEGGINGS, 15); map.put(ArmorItem.Type.CHESTPLATE, 16); map.put(ArmorItem.Type.HELMET, 11); }); private final String name; private final int durabilityMultiplier; private final EnumMap protectionFunctionForType; private final int enchantmentValue; private final SoundEvent sound; private final float toughness; private final float knockbackResistance; private final LazyLoadedValue repairIngredient; private ModArmorMaterial( String pName, int pDurabilityMultiplier, EnumMap pProtectionFunctionForType, int pEnchantmentValue, SoundEvent pSound, float pToughness, float pKnockbackResistance, Supplier pRepairIngredient ) { this.name = pName; this.durabilityMultiplier = pDurabilityMultiplier; this.protectionFunctionForType = pProtectionFunctionForType; this.enchantmentValue = pEnchantmentValue; this.sound = pSound; this.toughness = pToughness; this.knockbackResistance = pKnockbackResistance; this.repairIngredient = new LazyLoadedValue<>(pRepairIngredient); } @Override public int getDurabilityForType(ArmorItem.Type pType) { return HEALTH_FUNCTION_FOR_TYPE.get(pType) * this.durabilityMultiplier; } @Override public int getDefenseForType(ArmorItem.Type pType) { return this.protectionFunctionForType.get(pType); } @Override public int getEnchantmentValue() { return this.enchantmentValue; } @Override public SoundEvent getEquipSound() { return this.sound; } @Override public Ingredient getRepairIngredient() { return this.repairIngredient.get(); } @Override public String getName() { return this.name; } @Override public float getToughness() { return this.toughness; } /** * Gets the percentage of knockback resistance provided by armor of the material. */ @Override public float getKnockbackResistance() { return this.knockbackResistance; } } ``` ModArmorMaterial 是一个枚举类,它实现了 ArmorMaterial 接口,用于表示Minecraft中自定义盔甲材料的属性。每个枚举常量代表一种不同的盔甲材料,具有不同的属性值。 name:盔甲材料的名称。 durabilityMultiplier:耐久度乘数,用于计算每件盔甲的耐久度。 protectionFunctionForType:一个 EnumMap,包含了不同盔甲类型(如靴子、裤子、胸甲、头盔)的防御值。 enchantmentValue:盔甲可以附魔的等级。 sound:装备盔甲时播放的声音事件。 toughness:盔甲的韧性,是对护甲值效果受伤害降低的防护 knockbackResistance:盔甲提供的抗击退效果的百分比。 repairIngredient:一个 LazyLoadedValue 对象,用于存储修理该材料盔甲所需的材料。 注册物品 ```java public static final Supplier RUBY_HELMET = ITEMS.register("ruby_helmet", () -> new ArmorItem(ModArmorMaterial.RUBY, ArmorItem.Type.HELMET, (new Item.Properties()))); public static final Supplier RUBY_CHESTPLATE = ITEMS.register("ruby_chestplate", () -> new ArmorItem(ModArmorMaterial.RUBY, ArmorItem.Type.CHESTPLATE, (new Item.Properties()))); public static final Supplier RUBY_LEGGINGS = ITEMS.register("ruby_leggings", () -> new ArmorItem(ModArmorMaterial.RUBY, ArmorItem.Type.LEGGINGS, (new Item.Properties()))); public static final Supplier RUBY_BOOTS = ITEMS.register("ruby_boots", () -> new ArmorItem(ModArmorMaterial.RUBY, ArmorItem.Type.BOOTS, (new Item.Properties()))); ``` 添加到创造模式物品栏 ```java public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab",() -> CreativeModeTab.builder() .withTabsBefore(CreativeModeTabs.COMBAT) .title(Component.translatable(EXAMPLE_MOD_TAB_STRING)) .icon(()->ModItems.RUBY.get().getDefaultInstance()) .displayItems((pParameters, pOutput) -> { pOutput.accept(ModItems.RUBY.get()); pOutput.accept(ModItems.MAGIC_INGOT.get()); pOutput.accept(ModItems.RUBY_APPLE.get()); pOutput.accept(ModItems.RUBY_SWORD.get()); pOutput.accept(ModItems.RUBY_PICKAXE.get()); pOutput.accept(ModItems.RUBY_CHESTPLATE.get()); pOutput.accept(ModItems.RUBY_HELMET.get()); pOutput.accept(ModItems.RUBY_LEGGINGS.get()); pOutput.accept(ModItems.RUBY_BOOTS.get()); pOutput.accept(ModBlocks.RUBY_BLOCK.get()); pOutput.accept(ModBlocks.LAMP_BLOCK.get()); }) .build()); ``` ## 添加材质和模型 ```java this.basicItem(ModItems.RUBY_HELMET.get()); this.basicItem(ModItems.RUBY_CHESTPLATE.get()); this.basicItem(ModItems.RUBY_LEGGINGS.get()); this.basicItem(ModItems.RUBY_BOOTS.get()); ``` 材质我直接使用了原版的钻石套装改了一下。你可以直接使用自己的材质 ## 添加人物穿上盔甲的材质 因为Minecraft将所有的盔甲材质写死在minecraft这个域下,所以我们得创建相对应的目录,创建如下的目录 ``` ├── assets │ ├── examplemod │ └── minecraft │ └── textures │ └── models │ └── armor ``` 命名格式为:盔甲材料\_layer\_1和盔甲材料\_layer\_2 你之前写的ModArmorMaterial枚举的name 的名称 ## ArmorItem类 ArmorItem 类是 Minecraft 中的一个扩展类,它继承自 Item 类并实现了 Equipable 接口。这个类代表一个盔甲类物品,并定义了它的属性和行为。 * type:表示盔甲类型(如头盔、胸甲、裤子、靴子)。 * defense:盔甲的基础防御值。 * toughness:盔甲的韧性,是对护甲值效果受伤害降低的防护 * knockbackResistance:盔甲提供的抗击退效果的百分比。 * material:盔甲的材料,决定了盔甲的耐久度、防御力、韧性、附魔等级等属性。 * defaultModifiers:一个 Multimap 对象,包含了默认的属性修改器,用于增强盔甲的防御力和韧性。 方法 * getType():返回盔甲类型。 * getEnchantmentValue():返回盔甲可以附魔的等级。 * getMaterial():返回盔甲材料。 * isValidRepairItem(ItemStack pToRepair, ItemStack pRepair):检查盔甲是否可以被修复。 * use(Level pLevel, Player pPlayer, InteractionHand pHand):处理盔甲的右键点击行为。 * getDefaultAttributeModifiers(EquipmentSlot pEquipmentSlot):获取盔甲的默认属性修改器。 * getDefense():返回盔甲的基础防御值。 * getToughness():返回盔甲的韧性。 * getEquipmentSlot():返回盔甲的装备槽位。 * getEquipSound():返回装备盔甲时播放的声音事件。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/mod-tweaks/jei.md --- # 11.1 JEI信息修改 *** 添加子类型: ``` onEvent('jei.subtypes', event => { event.useNBT('example:item') event.userNBTKey('example:item', 'type') }) ``` 隐藏物品/流体 ``` onEvent('jei.hide.items', event => { event.hide('example:ingredient') }) onEvent('jei.hide.fluids', event => { event.hide('example:fluid') }) ``` 添加物品/流体 ``` onEvent('jei.add.items', event => { event.add(Item.of('example:item', {test: 123})) }) onEvent('jei.add.fluids', event => { event.add('example:fluid') }) ``` 为物品添加信息 ``` onEvent('jei.information', event => { event.add('example:ingredient', ['第一行介绍', '第二行介绍']) }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/mod-tweaks/jei.md --- # 11.1 JEI信息修改 *** 添加子类型: ``` onEvent('jei.subtypes', event => { event.useNBT('example:item') event.userNBTKey('example:item', 'type') }) ``` 隐藏物品/流体 ``` onEvent('jei.hide.items', event => { event.hide('example:ingredient') }) onEvent('jei.hide.fluids', event => { event.hide('example:fluid') }) ``` 添加物品/流体 ``` onEvent('jei.add.items', event => { event.add(Item.of('example:item', {test: 123})) }) onEvent('jei.add.fluids', event => { event.add('example:fluid') }) ``` 为物品添加信息 ``` onEvent('jei.information', event => { event.add('example:ingredient', ['第一行介绍', '第二行介绍']) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/addons/kubejs-create.md --- # 11.1 KubeJS Create *** :::info This page is currently under construction and does not yet cover all aspects or information. We are working on it. Thank you for your understanding. ::: ## 一、事件监听 和其他配方事件一样,要修改机械动力配方,你需要监听`ServerEvents.recipes`事件 ```js ServerEvents.recipes(event => { // code here }) ``` ## 二、配方修改 简要地说,你需要将以下表格中的内容接在`event.recipe`后面,例如: ```js ServerEvents.recipes(event => { event.recipes.create.crushing(['2x minecraft:cobblestone','minecraft:redstone',Item.of('minecraft:redstone').withChance(0.5)], 'minecraft:redstone_ore') }) ``` 以下为简写方法: | 格式 | 类型 | 补充说明 | | ----------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------- | | `create.conversion(output[], input[])` | 转化 | 如将竖直十字齿轮箱变为十字齿轮箱 | | `create.crushing(output[], input[])` | 粉碎轮 | 输入和输出不一定为数组,输入可以为ingredients或流体,输出可以为物品或流体 | | `create.cutting(output[], input[])` | 动力锯 | - | | `create.milling(output[], input[])` | 石磨 | - | | `create.basin(output[], input[])` | 工作盆 | - | | `create.mixing(output[], input[])` | 动力搅拌器 | 在其后可加 .heated() 和.superheated() (是否加热及其程度(加热和高温)) | | `create.compacting(output[], input[])` | 压块塑形 | 在其后可加 .heated() 和.superheated() (是否加热及其程度(加热和高温)) | | `create.pressing(output[], input[])` | 动力辊压机 | - | | `create.sandpaperPolishing(output[], input[])` | 砂纸打磨 | - | | `create.splashing(output[], input[])` | 洗涤 | - | | `create.deploying(output[], input[])` | 机械手 | - | | `create.filling(output[], input[])` | 注液 | - | | `create.emptying(output[], input[])` | 倒出液体 | - | | `create.mechanicalCrafting(output, pattern[], {合成键值})` | 动力合成 | 详见下方示例 | | `create.sequencedAssembly(output[], input, sequence[]).transitionalItem(item).loops(int)` | 装配 | 详见下方示例及说明 | 值得注意的是,在部分需要时间进行合成的配方中,你可以在配方后面加`.processingTime(整形 时间)`来修改合成时间。 ## 三、分类示例 ### 1、压块塑形 --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/addons/kubejs-create.md --- # 11.1 KubeJS Create *** :::info This page is currently under construction and does not yet cover all aspects or information. We are working on it. Thank you for your understanding. ::: ## 一、事件监听 和其他配方事件一样,要修改机械动力配方,你需要监听`ServerEvents.recipes`事件 ```js ServerEvents.recipes(event => { // code here }) ``` ## 二、配方修改 简要地说,你需要将以下表格中的内容接在`event.recipe`后面,例如: ```js ServerEvents.recipes(event => { event.recipes.create.crushing(['2x minecraft:cobblestone','minecraft:redstone',Item.of('minecraft:redstone').withChance(0.5)], 'minecraft:redstone_ore') }) ``` 以下为简写方法: | 格式 | 类型 | 补充说明 | | ----------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------- | | `create.conversion(output[], input[])` | 转化 | 如将竖直十字齿轮箱变为十字齿轮箱 | | `create.crushing(output[], input[])` | 粉碎轮 | 输入和输出不一定为数组,输入可以为ingredients或流体,输出可以为物品或流体 | | `create.cutting(output[], input[])` | 动力锯 | - | | `create.milling(output[], input[])` | 石磨 | - | | `create.basin(output[], input[])` | 工作盆 | - | | `create.mixing(output[], input[])` | 动力搅拌器 | 在其后可加 .heated() 和.superheated() (是否加热及其程度(加热和高温)) | | `create.compacting(output[], input[])` | 压块塑形 | 在其后可加 .heated() 和.superheated() (是否加热及其程度(加热和高温)) | | `create.pressing(output[], input[])` | 动力辊压机 | - | | `create.sandpaperPolishing(output[], input[])` | 砂纸打磨 | - | | `create.splashing(output[], input[])` | 洗涤 | - | | `create.deploying(output[], input[])` | 机械手 | - | | `create.filling(output[], input[])` | 注液 | - | | `create.emptying(output[], input[])` | 倒出液体 | - | | `create.mechanicalCrafting(output, pattern[], {合成键值})` | 动力合成 | 详见下方示例 | | `create.sequencedAssembly(output[], input, sequence[]).transitionalItem(item).loops(int)` | 装配 | 详见下方示例及说明 | 值得注意的是,在部分需要时间进行合成的配方中,你可以在配方后面加`.processingTime(整形 时间)`来修改合成时间。 ## 三、分类示例 ### 1、压块塑形 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/addons/lootjs.md --- # 11.2 LootJS —— 便捷战利品表修改 :::info This page is currently under construction and does not yet cover all aspects or information. We are working on it. Thank you for your understanding. ::: KubeJS 自带的`LootBuilder`并不便于简单的战利品表修改,如添加额外掉落物。其修改掉落条件和函数也较为繁琐,而LootJS便解决了这个问题,它能够让你更加便捷地修改战利品表,掉落战利品时执行事件等。 ## 一、附属信息 (本节部分内容参考自 [LootJS Wiki](https://github.com/AlmostReliable/lootjs-forge/wiki)) mod链接:[Github](https://github.com/AlmostReliable/lootjs-forge) [Curseforge](https://www.curseforge.com/minecraft/mc-mods/lootjs-forge),许可:LGPL-3.0。1.19.2+版本支持 Forge 和 Fabric 模组加载器。 你可以通过/reload命令来重载LootJS的修改内容 ## 二、工作原理 LootJS的工作原理如下: ![](https://m1.miaomc.cn/uploads/20220424_31c09db2a2078.png) ## 三、LootJS事件 ### 1、事件监听 *注:本节代码应置于`kubejs\server_scripts`文件夹下* 要使用LootJS修改战利品表,你需要使用`LootJS.modifiers`事件。 ```js LootJS.modifiers(event => { // code here }); ``` ### 2、修改逻辑 ![](https://m1.miaomc.cn/uploads/20230708_64a960a74e4a5.png) 值得注意的是,修改战利品表时应至少存在一个战利品表事件! 例如,以下为两个修改示例: ```js LootJS.modifiers((event) => { // 玩家使用忽略nbt的下界合金镐破坏带有#forge:ores标签的方块时,额外掉落一个砂砾。 event .addBlockLootModifier("#forge:ores") // 战利品表修饰器 .matchMainHand(Item.of("minecraft:netherite_pickaxe").ignoreNBT()) // 战利品表条件 .addLoot("minecraft:gravel");// 战利品表事件 // 玩家破坏绿宝石块时,添加基于二项分布的绿宝石掉落 event .addBlockLootModifier("minecraft:emerald_block") // 战利品表修饰器 .pool((p) => {// 战利品表事件(新建随机池) p.addLoot("minecraft:emerald");// 战利品表事件 p.applyBinomialDistributionBonus("minecraft:fortune", 0.2, 3);// 战利品表函数 }); }); ``` ## 四、战利品修饰器(Loot Modifier) | **函数** | **功能** | | ----------------------------------- | ------------------------------------ | | addBlockLootModifier(...方块ID) | 为方块添加新的战利品表修饰器 | | addEntityLootModifier(...实体ID) | 为实体添加新的战利品表修饰器 | | addLootTableModifier(...命名空间ID) | 为给定战利品表添加新的修饰器 | | addLootTypeModifier(...战利品表ID) | 为给定战利品表类型\[1]添加新的修饰器 | --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/addons/lootjs.md --- # 11.2 LootJS —— 便捷战利品表修改 :::info This page is currently under construction and does not yet cover all aspects or information. We are working on it. Thank you for your understanding. ::: KubeJS 自带的`LootBuilder`并不便于简单的战利品表修改,如添加额外掉落物。其修改掉落条件和函数也较为繁琐,而LootJS便解决了这个问题,它能够让你更加便捷地修改战利品表,掉落战利品时执行事件等。 ## 一、附属信息 (本节部分内容参考自 [LootJS Wiki](https://github.com/AlmostReliable/lootjs-forge/wiki)) mod链接:[Github](https://github.com/AlmostReliable/lootjs-forge) [Curseforge](https://www.curseforge.com/minecraft/mc-mods/lootjs-forge),许可:LGPL-3.0。1.19.2+版本支持 Forge 和 Fabric 模组加载器。 你可以通过/reload命令来重载LootJS的修改内容 ## 二、工作原理 LootJS的工作原理如下: ![](https://m1.miaomc.cn/uploads/20220424_31c09db2a2078.png) ## 三、LootJS事件 ### 1、事件监听 *注:本节代码应置于`kubejs\server_scripts`文件夹下* 要使用LootJS修改战利品表,你需要使用`LootJS.modifiers`事件。 ```js LootJS.modifiers(event => { // code here }); ``` ### 2、修改逻辑 ![](https://m1.miaomc.cn/uploads/20230708_64a960a74e4a5.png) 值得注意的是,修改战利品表时应至少存在一个战利品表事件! 例如,以下为两个修改示例: ```js LootJS.modifiers((event) => { // 玩家使用忽略nbt的下界合金镐破坏带有#forge:ores标签的方块时,额外掉落一个砂砾。 event .addBlockLootModifier("#forge:ores") // 战利品表修饰器 .matchMainHand(Item.of("minecraft:netherite_pickaxe").ignoreNBT()) // 战利品表条件 .addLoot("minecraft:gravel");// 战利品表事件 // 玩家破坏绿宝石块时,添加基于二项分布的绿宝石掉落 event .addBlockLootModifier("minecraft:emerald_block") // 战利品表修饰器 .pool((p) => {// 战利品表事件(新建随机池) p.addLoot("minecraft:emerald");// 战利品表事件 p.applyBinomialDistributionBonus("minecraft:fortune", 0.2, 3);// 战利品表函数 }); }); ``` ## 四、战利品修饰器(Loot Modifier) | **函数** | **功能** | | ----------------------------------- | ------------------------------------ | | addBlockLootModifier(...方块ID) | 为方块添加新的战利品表修饰器 | | addEntityLootModifier(...实体ID) | 为实体添加新的战利品表修饰器 | | addLootTableModifier(...命名空间ID) | 为给定战利品表添加新的修饰器 | | addLootTypeModifier(...战利品表ID) | 为给定战利品表类型\[1]添加新的修饰器 | --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/mod-tweaks/rei.md --- # 11.2 REI信息修改 *** 隐藏物品 ``` onEvent('rei.hide.items', event => { event.hide('example:ingredient') }) ``` 添加物品/流体 ``` onEvent('rei.add.items', event => { event.add(item.of('example:item').nbt({test: 123})) }) ``` 为物品添加信息 ``` onEvent('rei.information', event => { event.add('example:ingredient', '标题', ['第一行', '第二行']) }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/mod-tweaks/rei.md --- # 11.2 REI信息修改 *** 隐藏物品 ``` onEvent('rei.hide.items', event => { event.hide('example:ingredient') }) ``` 添加物品/流体 ``` onEvent('rei.add.items', event => { event.add(item.of('example:item').nbt({test: 123})) }) ``` 为物品添加信息 ``` onEvent('rei.information', event => { event.add('example:ingredient', '标题', ['第一行', '第二行']) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/mod-tweaks/ftb-quests.md --- # 11.3 FTB Quest相关修改 *** FTB Quest任务模组内置对KubeJS的支持,你可以通过KubeJS便捷地修改FTB Quest任务的进度/自定义任务等 FTB Quest自身提供了 `ftbquests.completed.[任务ID]` (任务完成事件), `ftbquests.custom_reward.<奖励ID>` (自定义任务奖励)和 `ftbquests.custom_task.<任务ID>` 三个事件可供监听,你也可以在其他事件中对玩家游戏进度进行修改。 本节将通过任务示例“村民挚友”来讲解如何通过KubeJS修改FTB Quest相关内容\*\*(推荐先阅读第15章内容)\*\* ![](https://m1.miaomc.cn/uploads/20220509_ed5c881d5875b.png) ## **1、于其他事件中直接修改进度** 例子1:当玩家攻击村民时重置其任务进度 ``` ID为onEvent('entity.attack', event => { if(event.source && event.source.actual && event.source.actual.player && event.entity.type == 'minecraft:villager'){// 检测玩家攻击村民,防止为空报错 event.source.actual.tell("你攻击了村民,村民挚友任务失败!"); event.source.actual.data.ftbquests.changeProgress("4425171B4650EB54", event => { event.reset = true;// 重设ID为4425171B4650EB54的任务的进度 }) } }) // 另一种写法 onEvent('entity.attack', event => { if(event.source.actual.player!=null && event.entity.type == 'minecraft:villager'){ event.source.actual.tell("你攻击了村民,村民挚友任务失败!"); event.source.actual.data.ftbquests.reset("4425171B4650EB54的");// 重设ID为4425171B4650EB54的任务的进度 } }) ``` 例子2:玩家攻击掠夺兽时添加进度 ``` onEvent('entity.death', event => { if(event.source && event.source.actual && event.source.actual.player && event.entity.type == 'minecraft:ravager'){ event.source.actual.tell("你击杀了劫掠受,等价于击杀三个唤魔者!"); event.source.actual.data.ftbquests.addProgress("6B2089D4683E7907", 3);// 为ID为6B2089D4683E7907的任务添加进度值3 } }) ``` ## 2、自定义奖励 例子:玩家完成村民挚友任务时给予村庄英雄效果 ``` onEvent('ftbquests.custom_reward.4883F7BD04E2C597', event => {// 设置ID为4883F7BD04E2C597的自定义奖励 // 给予玩家药水效果奖励(村庄英雄) event.player.potionEffects.add('minecraft:hero_of_the_village', 120000, 5, false, false); }) ``` ## 3、自定义任务 例子:检测玩家是否完成进度`minecraft:adventure/trade` ``` onEvent('ftbquests.custom_task.76AA10188A48B51F', event => {// 设置ID为76AA10188A48B51F的自定义任务 event.checkTimer = 1;// 定义多久检测一次 event.maxProgress = 1;// 定义最大进度 event.check = (task, player) => { if (player.isAdvancementDone("minecraft:adventure/trade")) {// 检测进度是否完成 player.tell("done"); task.progress++;// 添加进度 } } }) ``` ## 4、监听任务完成事件 ``` onEvent('ftbquests.completed.4425171B4650EB54', event => {// 检测ID为4425171B4650EB54的任务是否完成 console.log("村民挚友任务已被完成"); }) ``` **注:你可以通过右击任务图标并点击复制ID来获取指定任务/奖励的ID** ![](https://m1.miaomc.cn/uploads/20220509_5f8ec1de2bc00.png) 附本节用到的FTB Quest配置文件:https://wwu.lanzouw.com/iTone04k5arc --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/mod-tweaks/ftb-quests.md --- # 11.3 FTB Quest相关修改 *** FTB Quest任务模组内置对KubeJS的支持,你可以通过KubeJS便捷地修改FTB Quest任务的进度/自定义任务等 FTB Quest自身提供了 `ftbquests.completed.[任务ID]` (任务完成事件), `ftbquests.custom_reward.<奖励ID>` (自定义任务奖励)和 `ftbquests.custom_task.<任务ID>` 三个事件可供监听,你也可以在其他事件中对玩家游戏进度进行修改。 本节将通过任务示例“村民挚友”来讲解如何通过KubeJS修改FTB Quest相关内容\*\*(推荐先阅读第15章内容)\*\* ![](https://m1.miaomc.cn/uploads/20220509_ed5c881d5875b.png) ## **1、于其他事件中直接修改进度** 例子1:当玩家攻击村民时重置其任务进度 ``` ID为onEvent('entity.attack', event => { if(event.source && event.source.actual && event.source.actual.player && event.entity.type == 'minecraft:villager'){// 检测玩家攻击村民,防止为空报错 event.source.actual.tell("你攻击了村民,村民挚友任务失败!"); event.source.actual.data.ftbquests.changeProgress("4425171B4650EB54", event => { event.reset = true;// 重设ID为4425171B4650EB54的任务的进度 }) } }) // 另一种写法 onEvent('entity.attack', event => { if(event.source.actual.player!=null && event.entity.type == 'minecraft:villager'){ event.source.actual.tell("你攻击了村民,村民挚友任务失败!"); event.source.actual.data.ftbquests.reset("4425171B4650EB54的");// 重设ID为4425171B4650EB54的任务的进度 } }) ``` 例子2:玩家攻击掠夺兽时添加进度 ``` onEvent('entity.death', event => { if(event.source && event.source.actual && event.source.actual.player && event.entity.type == 'minecraft:ravager'){ event.source.actual.tell("你击杀了劫掠受,等价于击杀三个唤魔者!"); event.source.actual.data.ftbquests.addProgress("6B2089D4683E7907", 3);// 为ID为6B2089D4683E7907的任务添加进度值3 } }) ``` ## 2、自定义奖励 例子:玩家完成村民挚友任务时给予村庄英雄效果 ``` onEvent('ftbquests.custom_reward.4883F7BD04E2C597', event => {// 设置ID为4883F7BD04E2C597的自定义奖励 // 给予玩家药水效果奖励(村庄英雄) event.player.potionEffects.add('minecraft:hero_of_the_village', 120000, 5, false, false); }) ``` ## 3、自定义任务 例子:检测玩家是否完成进度`minecraft:adventure/trade` ``` onEvent('ftbquests.custom_task.76AA10188A48B51F', event => {// 设置ID为76AA10188A48B51F的自定义任务 event.checkTimer = 1;// 定义多久检测一次 event.maxProgress = 1;// 定义最大进度 event.check = (task, player) => { if (player.isAdvancementDone("minecraft:adventure/trade")) {// 检测进度是否完成 player.tell("done"); task.progress++;// 添加进度 } } }) ``` ## 4、监听任务完成事件 ``` onEvent('ftbquests.completed.4425171B4650EB54', event => {// 检测ID为4425171B4650EB54的任务是否完成 console.log("村民挚友任务已被完成"); }) ``` **注:你可以通过右击任务图标并点击复制ID来获取指定任务/奖励的ID** ![](https://m1.miaomc.cn/uploads/20220509_5f8ec1de2bc00.png) 附本节用到的FTB Quest配置文件:https://wwu.lanzouw.com/iTone04k5arc --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/mod-tweaks/gamestage.md --- # 11.4 KubeJs内置游戏阶段 (类似于GameStage) *** | 功能 | 表达格式 | 返回值 | | ----------- | ---------------------- | ---- | | 禁用一个阶段状态的更新 | .addNoUpdate('阶段名') | 布尔型 | | 启用一个阶段状态的更新 | .removeNoUpdate('阶段名') | 布尔型 | | 玩家是否具有阶段 | .has('阶段名') | 布尔型 | | 为玩家添加阶段 | .add('阶段名') | 布尔型 | | 为玩家移除阶段 | .remove('阶段名') | 布尔型 | | 为玩家设置阶段 | .set('阶段名', 布尔值) | 布尔型 | | 切换阶段状态 | .toggle('阶段名') | 布尔型 | | 同步阶段 | .sync() | void | | 替换阶段//该功能存疑 | replace('阶段名') | void | 通常以上内容都接在event.player.stages后,例如`event.player.stages.add('XPlusUser')` 下面是一个实例 ``` onEvent('player.logged_in',event=>{ let username = e.player.name if (!event.player.stages.has('0') && username == "Wudji_Notfound") { //0代表离线,1代表在线 event.player.stages.add('0'); event.player.stages.remove('1'); event.server.runCommand(`say 无敌鸡加入了游戏!`); } }) onEvent('player.logged_out',event=>{ let username = e.player.name if (!event.player.stages.has('1') && username == "Wudji_Notfound") { event.player.stages.add('1'); event.player.stages.remove('0'); event.server.runCommand(`say 无敌鸡离开了游戏!`); } }) ``` 你还可以将游戏阶段用于任务设置,详见本教程后半部分 --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/mod-tweaks/gamestage.md --- # 11.4 KubeJs内置游戏阶段 (类似于GameStage) *** | 功能 | 表达格式 | 返回值 | | ----------- | ---------------------- | ---- | | 禁用一个阶段状态的更新 | .addNoUpdate('阶段名') | 布尔型 | | 启用一个阶段状态的更新 | .removeNoUpdate('阶段名') | 布尔型 | | 玩家是否具有阶段 | .has('阶段名') | 布尔型 | | 为玩家添加阶段 | .add('阶段名') | 布尔型 | | 为玩家移除阶段 | .remove('阶段名') | 布尔型 | | 为玩家设置阶段 | .set('阶段名', 布尔值) | 布尔型 | | 切换阶段状态 | .toggle('阶段名') | 布尔型 | | 同步阶段 | .sync() | void | | 替换阶段//该功能存疑 | replace('阶段名') | void | 通常以上内容都接在event.player.stages后,例如`event.player.stages.add('XPlusUser')` 下面是一个实例 ``` onEvent('player.logged_in',event=>{ let username = e.player.name if (!event.player.stages.has('0') && username == "Wudji_Notfound") { //0代表离线,1代表在线 event.player.stages.add('0'); event.player.stages.remove('1'); event.server.runCommand(`say 无敌鸡加入了游戏!`); } }) onEvent('player.logged_out',event=>{ let username = e.player.name if (!event.player.stages.has('1') && username == "Wudji_Notfound") { event.player.stages.add('1'); event.player.stages.remove('0'); event.server.runCommand(`say 无敌鸡离开了游戏!`); } }) ``` 你还可以将游戏阶段用于任务设置,详见本教程后半部分 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_12_CustomBlockModel_Non_BlockEntityBlock.md description: 12 自定义方块模型和非实体方块 相关教程 --- # 本文参考了改内容 https://boson.v2mcdev.com/block/nonesoildblock.html ## 自定义方块模型和非实体方块 该代码复制了原版的台阶的代码 ```java public class RubyFrame extends Block implements SimpleWaterloggedBlock { public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; public static final VoxelShape SHAPE = Block.box(0,0,0,16,18,16); public RubyFrame(){ super(Properties.ofFullCopy(Blocks.STONE).strength(5).noOcclusion()); this.registerDefaultState(this.defaultBlockState().setValue(WATERLOGGED, Boolean.valueOf(false))); } @Override public VoxelShape getShape(BlockState pState, BlockGetter pLevel, BlockPos pPos, CollisionContext pContext) { return SHAPE; } @Override protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { pBuilder.add(WATERLOGGED); } @Nullable @Override public BlockState getStateForPlacement(BlockPlaceContext pContext) { BlockPos blockpos = pContext.getClickedPos(); BlockState blockstate = pContext.getLevel().getBlockState(blockpos); if (blockstate.is(this)) { return blockstate.setValue(WATERLOGGED, Boolean.valueOf(false)); } else { FluidState fluidstate = pContext.getLevel().getFluidState(blockpos); BlockState blockstate1 = this.defaultBlockState() .setValue(WATERLOGGED, Boolean.valueOf(fluidstate.getType() == Fluids.WATER)); return blockstate1; } } @Override public BlockState updateShape(BlockState pState, Direction pFacing, BlockState pFacingState, LevelAccessor pLevel, BlockPos pCurrentPos, BlockPos pFacingPos) { if (pState.getValue(WATERLOGGED)) { pLevel.scheduleTick(pCurrentPos, Fluids.WATER, Fluids.WATER.getTickDelay(pLevel)); } return super.updateShape(pState, pFacing, pFacingState, pLevel, pCurrentPos, pFacingPos); } @Override public FluidState getFluidState(BlockState pState) { return pState.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(pState); } @Override public boolean isPathfindable(BlockState pState, BlockGetter pLevel, BlockPos pPos, PathComputationType pType) { switch(pType) { case LAND: return false; case WATER: return pLevel.getFluidState(pPos).is(FluidTags.WATER); case AIR: return false; default: return false; } } } ``` 在Minecraft中,SimpleWaterloggedBlock接口允许一个方块在放置时检查其位置是否有水,并相应地设置其状态。如果一个方块实现了这个接口,它可以决定是否被水覆盖,这为方块的行为增加了额外的维度。 noOcclusion() 方法是从 BlockBehaviour.Properties 中调用的一个方法,当你调用它时,它告诉Minecraft这个方块不会阻挡任何光线的传播。也就是说,这个方块对光线的阻挡效果会被忽略,光线可以穿过这个方块。 SHAPE 是一个 VoxelShape 类型的静态常量,它定义了方块的体积形状(碰撞箱)。 getShape 方法返回一个 VoxelShape 对象,这个对象定义了方块在给定 BlockState 下的形状。这个方法通常用于确定碰撞箱、视锥剔除(frustum culling)以及与方块交互时的形状检测。 getStateForPlacement 方法在玩家尝试放置方块时被调用。它用于确定方块在被放置时的初始状态。这个方法可以根据放置的上下文(如放置位置是否有水)来返回不同的 BlockState。 updateShape 方法在方块周围的环境发生变化时被调用,例如,当相邻的方块被移除或放置时。这个方法允许方块在状态改变时更新其形状或者其他属性。在 RubyFrame 类中,这个方法还检查了 WATERLOGGED 状态,如果方块被设定为水浸状态,它将安排水流体进行刻(tick)处理。 getFluidState 方法返回一个 FluidState 对象,表示方块在给定 BlockState 下的流体状态。如果一个方块是水浸的,这个方法会返回代表水状态的 FluidState。 isPathfindable 方法用于确定生物是否可以将方块视为路径的一部分。它根据不同的路径计算类型(如陆地、水域、空中)来返回不同的结果。在 RubyFrame 类中,这个方法确保了如果方块被水覆盖,那么它对于水域路径来说是可通行的。 ## 注册方块 ```java public static final Supplier RUBY_FRAME = registerBlock("ruby_frame", RubyFrame::new); ``` ## 通过BlockBeanch创建自己的方块模型 Blockbench 是一款开源的3D建模软件,主要用于创建Minecraft和其他类似游戏的模型。它提供了一个用户友好的界面,允许用户通过拖放和调整参数来设计模型, 具体的教程可以去在B战搜索学习,这里仅做简单的介绍。然后可以保存json文件放到指定的目录的位置即可。 ## 添加模型和材质 将用BlockBench的做的json文件和png图片放到之前我们说过的位置model和texture下 ```java ├─assets │ ├─examplemod │ │ ├─blockstates │ │ ├─models │ │ │ ├─block │ │ │ └─item │ │ └─textures │ │ ├─block │ │ └─item ``` ## 生成blockstate和物品模型 ```java @Override protected void registerStatesAndModels() { this.simpleBlockWithItem(ModBlocks.RUBY_BLOCK.get(),cubeAll(ModBlocks.RUBY_BLOCK.get())); this.propertyBlock(ModBlocks.LAMP_BLOCK.get()); this.addWithHaveModel(ModBlocks.RUBY_FRAME.get(),"ruby_frame"); } public void addWithHaveModel(Block block,String name){ var model_path = models().getExistingFile(new ResourceLocation(ExampleMod.MODID,name)); var model = new ConfiguredModel(model_path); getVariantBuilder(block).partialState().setModels(model); simpleBlockItem(block,model_path); } ``` --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_12_CustomBlockModel_Non_BlockEntityBlock.md description: 12 自定义方块模型和非实体方块 相关教程 --- # 本文参考了改内容 https://boson.v2mcdev.com/block/nonesoildblock.html ## 自定义方块模型和非实体方块 该代码复制了原版的台阶的代码 ```java public class RubyFrame extends Block implements SimpleWaterloggedBlock { public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; public static final VoxelShape SHAPE = Block.box(0,0,0,16,18,16); public RubyFrame(){ super(Properties.ofFullCopy(Blocks.STONE).strength(5).noOcclusion()); this.registerDefaultState(this.defaultBlockState().setValue(WATERLOGGED, Boolean.valueOf(false))); } @Override public VoxelShape getShape(BlockState pState, BlockGetter pLevel, BlockPos pPos, CollisionContext pContext) { return SHAPE; } @Override protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { pBuilder.add(WATERLOGGED); } @Nullable @Override public BlockState getStateForPlacement(BlockPlaceContext pContext) { BlockPos blockpos = pContext.getClickedPos(); BlockState blockstate = pContext.getLevel().getBlockState(blockpos); if (blockstate.is(this)) { return blockstate.setValue(WATERLOGGED, Boolean.valueOf(false)); } else { FluidState fluidstate = pContext.getLevel().getFluidState(blockpos); BlockState blockstate1 = this.defaultBlockState() .setValue(WATERLOGGED, Boolean.valueOf(fluidstate.getType() == Fluids.WATER)); return blockstate1; } } @Override public BlockState updateShape(BlockState pState, Direction pFacing, BlockState pFacingState, LevelAccessor pLevel, BlockPos pCurrentPos, BlockPos pFacingPos) { if (pState.getValue(WATERLOGGED)) { pLevel.scheduleTick(pCurrentPos, Fluids.WATER, Fluids.WATER.getTickDelay(pLevel)); } return super.updateShape(pState, pFacing, pFacingState, pLevel, pCurrentPos, pFacingPos); } @Override public FluidState getFluidState(BlockState pState) { return pState.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(pState); } @Override public boolean isPathfindable(BlockState pState, BlockGetter pLevel, BlockPos pPos, PathComputationType pType) { switch(pType) { case LAND: return false; case WATER: return pLevel.getFluidState(pPos).is(FluidTags.WATER); case AIR: return false; default: return false; } } } ``` 在Minecraft中,SimpleWaterloggedBlock接口允许一个方块在放置时检查其位置是否有水,并相应地设置其状态。如果一个方块实现了这个接口,它可以决定是否被水覆盖,这为方块的行为增加了额外的维度。 noOcclusion() 方法是从 BlockBehaviour.Properties 中调用的一个方法,当你调用它时,它告诉Minecraft这个方块不会阻挡任何光线的传播。也就是说,这个方块对光线的阻挡效果会被忽略,光线可以穿过这个方块。 SHAPE 是一个 VoxelShape 类型的静态常量,它定义了方块的体积形状(碰撞箱)。 getShape 方法返回一个 VoxelShape 对象,这个对象定义了方块在给定 BlockState 下的形状。这个方法通常用于确定碰撞箱、视锥剔除(frustum culling)以及与方块交互时的形状检测。 getStateForPlacement 方法在玩家尝试放置方块时被调用。它用于确定方块在被放置时的初始状态。这个方法可以根据放置的上下文(如放置位置是否有水)来返回不同的 BlockState。 updateShape 方法在方块周围的环境发生变化时被调用,例如,当相邻的方块被移除或放置时。这个方法允许方块在状态改变时更新其形状或者其他属性。在 RubyFrame 类中,这个方法还检查了 WATERLOGGED 状态,如果方块被设定为水浸状态,它将安排水流体进行刻(tick)处理。 getFluidState 方法返回一个 FluidState 对象,表示方块在给定 BlockState 下的流体状态。如果一个方块是水浸的,这个方法会返回代表水状态的 FluidState。 isPathfindable 方法用于确定生物是否可以将方块视为路径的一部分。它根据不同的路径计算类型(如陆地、水域、空中)来返回不同的结果。在 RubyFrame 类中,这个方法确保了如果方块被水覆盖,那么它对于水域路径来说是可通行的。 ## 注册方块 ```java public static final Supplier RUBY_FRAME = registerBlock("ruby_frame", RubyFrame::new); ``` ## 通过BlockBeanch创建自己的方块模型 Blockbench 是一款开源的3D建模软件,主要用于创建Minecraft和其他类似游戏的模型。它提供了一个用户友好的界面,允许用户通过拖放和调整参数来设计模型, 具体的教程可以去在B战搜索学习,这里仅做简单的介绍。然后可以保存json文件放到指定的目录的位置即可。 ## 添加模型和材质 将用BlockBench的做的json文件和png图片放到之前我们说过的位置model和texture下 ```java ├─assets │ ├─examplemod │ │ ├─blockstates │ │ ├─models │ │ │ ├─block │ │ │ └─item │ │ └─textures │ │ ├─block │ │ └─item ``` ## 生成blockstate和物品模型 ```java @Override protected void registerStatesAndModels() { this.simpleBlockWithItem(ModBlocks.RUBY_BLOCK.get(),cubeAll(ModBlocks.RUBY_BLOCK.get())); this.propertyBlock(ModBlocks.LAMP_BLOCK.get()); this.addWithHaveModel(ModBlocks.RUBY_FRAME.get(),"ruby_frame"); } public void addWithHaveModel(Block block,String name){ var model_path = models().getExistingFile(new ResourceLocation(ExampleMod.MODID,name)); var model = new ConfiguredModel(model_path); getVariantBuilder(block).partialState().setModels(model); simpleBlockItem(block,model_path); } ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/addons/kubejs-create.md --- # 12.1 KubeJS Create *** | 支持的配方 | 格式 | 说明 | | ----- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------- | | 转化 | `event.recipes.createConversion(output[], input[])` | 如将竖直十字齿轮箱变为十字齿轮箱 | | 粉碎轮 | `event.recipes.createCrushing(output[], input[])` | 输入和输出不一定为数组,输入可以为ingredients或流体①,输出可以为物品或流体 | | 动力锯 | `event.recipes.createCutting(output[], input[])` | - | | 石磨 | `event.recipes.createMilling(output[], input[])` | - | | 工作盆 | `event.recipes.createBasin(output[], input[])` | - | | 动力搅拌器 | `event.recipes.createMixing(output[], input[])` | 在其后可加 .heated() 和.superheated() (是否加热及其程度(加热和高温)) | | 装配 | `event.recipes.createCompacting(output[], input[])` | 在其后可加 .heated() 和.superheated() (是否加热及其程度(加热和高温)) | | 动力辊压机 | `event.recipes.createPressing(output[], input[])` | - | | 砂纸打磨 | `event.recipes.createSandpaperPolishing(output[], input[])` | - | | 洗涤 | `event.recipes.createSplashing(output[], input[])` | - | | 机械手 | `event.recipes.createDeploying(output[], input[])` | - | | 注液 | `event.recipes.createFilling(output[], input[])` | - | | 倒出液体 | `event.recipes.createEmptying(output[], input[])` | - | | 动力合成 | `event.recipes.createMechanicalCrafting(output, pattern[], {合成键值})` | 详见下方示例 | | 装配 | `event.recipes.createSequencedAssembly(output[], input, sequence[]).transitionalItem(item).loops(int)` | 详见下方示例及说明 | ①:以水为例,流体可以为以下格式:`Fluid.of('minecraft:water', 1000)` 和 `{fluidTag: 'some:fluid_tag', amount: 1000}` ②:带有合成时间的项目可以在配方后加.processingTime(时间(整形))来修改其合成时间,在物品后加.withChance(2.0)可以修改其产出概率 *** 示例: 例子1:红石矿石 -> 2个圆石 + 一个红石粉 + 50%概率一个红石粉 ``` event.recipes.createCrushing([ '2x minecraft:cobblestone', 'minecraft:redstone', Item.of('minecraft:redstone').withChance(0.5)//概率的表示方法 ], 'minecraft:redstone_ore') ``` 例子2:3个萤石粉 + 3个黑曜石粉末 + 磨制玫瑰石英 --高温--> 异彩化合物 ``` event.recipes.createMixing('create:chromatic_compound', [ '#forge:dusts/glowstone', '#forge:dusts/glowstone', '#forge:dusts/glowstone', 'create:powdered_obsidian', 'create:powdered_obsidian', 'create:powdered_obsidian', 'create:polished_rose_quartz' ]).superheated() ``` 例子3:使用注液器制作烈焰蛋糕 ``` event.recipes.createFilling('create:blaze_cake', [ 'create:blaze_cake_base', Fluid.of('minecraft:lava', 250) ]) ``` 例子4:分液:蜂蜜瓶 -> 玻璃瓶 + 250单位蜂蜜 ``` event.recipes.createEmptying([ 'minecraft:glass_bottle', Fluid.of('create:honey', 250) ], 'minecraft:honey_bottle') ``` 例子5:动力合成,基本和标准配方格式一样 ``` event.recipes.createMechanicalCrafting('minecraft:piston', [ 'CCCCC', 'CPIPC', 'CPRPC' ], { C: '#forge:cobblestone', P: '#minecraft:planks', R: '#forge:dusts/redstone', I: '#forge:ingots/iron' }) ``` 例子6:序列组装 ``` event.recipes.createSequencedAssembly([ Item.of('6x create:large_cogwheel').withChance(32.0), Item.of('create:brass_ingot').withChance(2.0), 'minecraft:andesite', 'create:cogwheel', 'minecraft:stick', 'minecraft:iron_nugget' ], 'create:brass_ingot', [ event.recipes.createDeploying('create:incomplete_large_cogwheel', ['create:incomplete_large_cogwheel', '#minecraft:planks']), event.recipes.createDeploying('create:incomplete_large_cogwheel', ['create:incomplete_large_cogwheel', '#minecraft:wooden_buttons']), event.recipes.createCutting('create:incomplete_large_cogwheel', 'create:incomplete_large_cogwheel').processingTime(50) ]).transitionalItem('create:incomplete_large_cogwheel').loops(6) ``` 对这部分代码的解释 ``` event.recipes.createSequencedAssembly([ //成品: Item.of('6x create:large_cogwheel').withChance(32.0), //随机废料: Item.of('create:brass_ingot').withChance(2.0), 'minecraft:andesite', 'create:cogwheel', 'minecraft:stick', 'minecraft:iron_nugget' ], //输入物品: 'create:brass_ingot', [ //每步的配方 /* 其中create:incomplete_large_cogwheel作为合成的半成品(即.transitionalItem('半成品注册名')) .loops(6)代表合成循环次数 .transitionalItem和.loops是可选的 */ event.recipes.createDeploying('create:incomplete_large_cogwheel', ['create:incomplete_large_cogwheel', '#minecraft:planks']),//第一步 event.recipes.createDeploying('create:incomplete_large_cogwheel', ['create:incomplete_large_cogwheel', '#minecraft:wooden_buttons']),//第二步 event.recipes.createCutting('create:incomplete_large_cogwheel', 'create:incomplete_large_cogwheel').processingTime(50)//第三步 ]).transitionalItem('create:incomplete_large_cogwheel').loops(6) ``` 即一般格式为: `event.recipes.createSequencedAssembly(输出物品组(数组)[], 输入物品, 序列合成步骤(数组)[]).transitionalItem(半成品注册名).loops(循环次数(整形))` 7 如果你想要使用你自己注册的半成品,你需要在Startup 脚本中这样填写: ``` onEvent('item.registry', event => { // 该物品的纹理应位于 kubejs/assets/kubejs/textures/item/my_part.png event.create('my_part').type('create:sequenced_assembly').displayName('My Part') }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/addons/kubejs-create.md --- # 12.1 KubeJS Create *** | 支持的配方 | 格式 | 说明 | | ----- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------- | | 转化 | `event.recipes.createConversion(output[], input[])` | 如将竖直十字齿轮箱变为十字齿轮箱 | | 粉碎轮 | `event.recipes.createCrushing(output[], input[])` | 输入和输出不一定为数组,输入可以为ingredients或流体①,输出可以为物品或流体 | | 动力锯 | `event.recipes.createCutting(output[], input[])` | - | | 石磨 | `event.recipes.createMilling(output[], input[])` | - | | 工作盆 | `event.recipes.createBasin(output[], input[])` | - | | 动力搅拌器 | `event.recipes.createMixing(output[], input[])` | 在其后可加 .heated() 和.superheated() (是否加热及其程度(加热和高温)) | | 装配 | `event.recipes.createCompacting(output[], input[])` | 在其后可加 .heated() 和.superheated() (是否加热及其程度(加热和高温)) | | 动力辊压机 | `event.recipes.createPressing(output[], input[])` | - | | 砂纸打磨 | `event.recipes.createSandpaperPolishing(output[], input[])` | - | | 洗涤 | `event.recipes.createSplashing(output[], input[])` | - | | 机械手 | `event.recipes.createDeploying(output[], input[])` | - | | 注液 | `event.recipes.createFilling(output[], input[])` | - | | 倒出液体 | `event.recipes.createEmptying(output[], input[])` | - | | 动力合成 | `event.recipes.createMechanicalCrafting(output, pattern[], {合成键值})` | 详见下方示例 | | 装配 | `event.recipes.createSequencedAssembly(output[], input, sequence[]).transitionalItem(item).loops(int)` | 详见下方示例及说明 | ①:以水为例,流体可以为以下格式:`Fluid.of('minecraft:water', 1000)` 和 `{fluidTag: 'some:fluid_tag', amount: 1000}` ②:带有合成时间的项目可以在配方后加.processingTime(时间(整形))来修改其合成时间,在物品后加.withChance(2.0)可以修改其产出概率 *** 示例: 例子1:红石矿石 -> 2个圆石 + 一个红石粉 + 50%概率一个红石粉 ``` event.recipes.createCrushing([ '2x minecraft:cobblestone', 'minecraft:redstone', Item.of('minecraft:redstone').withChance(0.5)//概率的表示方法 ], 'minecraft:redstone_ore') ``` 例子2:3个萤石粉 + 3个黑曜石粉末 + 磨制玫瑰石英 --高温--> 异彩化合物 ``` event.recipes.createMixing('create:chromatic_compound', [ '#forge:dusts/glowstone', '#forge:dusts/glowstone', '#forge:dusts/glowstone', 'create:powdered_obsidian', 'create:powdered_obsidian', 'create:powdered_obsidian', 'create:polished_rose_quartz' ]).superheated() ``` 例子3:使用注液器制作烈焰蛋糕 ``` event.recipes.createFilling('create:blaze_cake', [ 'create:blaze_cake_base', Fluid.of('minecraft:lava', 250) ]) ``` 例子4:分液:蜂蜜瓶 -> 玻璃瓶 + 250单位蜂蜜 ``` event.recipes.createEmptying([ 'minecraft:glass_bottle', Fluid.of('create:honey', 250) ], 'minecraft:honey_bottle') ``` 例子5:动力合成,基本和标准配方格式一样 ``` event.recipes.createMechanicalCrafting('minecraft:piston', [ 'CCCCC', 'CPIPC', 'CPRPC' ], { C: '#forge:cobblestone', P: '#minecraft:planks', R: '#forge:dusts/redstone', I: '#forge:ingots/iron' }) ``` 例子6:序列组装 ``` event.recipes.createSequencedAssembly([ Item.of('6x create:large_cogwheel').withChance(32.0), Item.of('create:brass_ingot').withChance(2.0), 'minecraft:andesite', 'create:cogwheel', 'minecraft:stick', 'minecraft:iron_nugget' ], 'create:brass_ingot', [ event.recipes.createDeploying('create:incomplete_large_cogwheel', ['create:incomplete_large_cogwheel', '#minecraft:planks']), event.recipes.createDeploying('create:incomplete_large_cogwheel', ['create:incomplete_large_cogwheel', '#minecraft:wooden_buttons']), event.recipes.createCutting('create:incomplete_large_cogwheel', 'create:incomplete_large_cogwheel').processingTime(50) ]).transitionalItem('create:incomplete_large_cogwheel').loops(6) ``` 对这部分代码的解释 ``` event.recipes.createSequencedAssembly([ //成品: Item.of('6x create:large_cogwheel').withChance(32.0), //随机废料: Item.of('create:brass_ingot').withChance(2.0), 'minecraft:andesite', 'create:cogwheel', 'minecraft:stick', 'minecraft:iron_nugget' ], //输入物品: 'create:brass_ingot', [ //每步的配方 /* 其中create:incomplete_large_cogwheel作为合成的半成品(即.transitionalItem('半成品注册名')) .loops(6)代表合成循环次数 .transitionalItem和.loops是可选的 */ event.recipes.createDeploying('create:incomplete_large_cogwheel', ['create:incomplete_large_cogwheel', '#minecraft:planks']),//第一步 event.recipes.createDeploying('create:incomplete_large_cogwheel', ['create:incomplete_large_cogwheel', '#minecraft:wooden_buttons']),//第二步 event.recipes.createCutting('create:incomplete_large_cogwheel', 'create:incomplete_large_cogwheel').processingTime(50)//第三步 ]).transitionalItem('create:incomplete_large_cogwheel').loops(6) ``` 即一般格式为: `event.recipes.createSequencedAssembly(输出物品组(数组)[], 输入物品, 序列合成步骤(数组)[]).transitionalItem(半成品注册名).loops(循环次数(整形))` 7 如果你想要使用你自己注册的半成品,你需要在Startup 脚本中这样填写: ``` onEvent('item.registry', event => { // 该物品的纹理应位于 kubejs/assets/kubejs/textures/item/my_part.png event.create('my_part').type('create:sequenced_assembly').displayName('My Part') }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/addons/kubejs-blood-magic.md --- # 12.2 KubeJS Blood Magic *** ``` 支持的格式: - 祭坛 event.recipes.bloodmagic.altar(output, input) event.recipes.bloodmagic.altar(output, input).upgradeLevel(int).altarSyphon(int).consumptionRate(int).drainRate(int) - 炼金矩阵 event.recipes.bloodmagic.array(output, baseInput, addedInput) event.recipes.bloodmagic.array(output, baseInput, addedInput).texture(string) - 狱火熔炉 event.recipes.bloodmagic.soulforge(output, [input]) event.recipes.bloodmagic.soulforge(output, [input]).minimumDrain(double).drain(double) - 奥术粉灰 event.recipes.bloodmagic.arc(output, input, tool) event.recipes.bloodmagic.arc(output, input, tool, [addedOutput]) event.recipes.bloodmagic.arc(output, input, tool, [addedOutput]).consumeIngredient(boolean).outputFluid(fluid) - 炼金术桌 event.recipes.bloodmagic.alchemytable(output, [input]) event.recipes.bloodmagic.alchemytable(output, [input]).syphon(int).ticks(int).upgradeLevel(int) 例: onEvent('recipes', event => { const { altar, array, soulforge, arc, alchemytable } = event.recipes.bloodmagic altar('minecraft:carrot', 'minecraft:apple') array('minecraft:spruce_planks', 'minecraft:oak_planks', 'minecraft:birch_planks') soulforge('minecraft:stone', ['minecraft:gold_ore', 'minecraft:diamond_ore', 'minecraft:iron_ore']).drain(1.0) arc('minecraft:netherite_ingot', 'minecraft:iron_ingot', 'minecraft:iron_pickaxe', [Item.of('minecraft:cobblestone').chance(0.4)]) alchemytable('minecraft:gold_ingot', ['minecraft:iron_ingot', 'minecraft:iron_ingot', 'minecraft:iron_ingot', 'minecraft:iron_ingot']).upgradeLevel(2) }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/addons/kubejs-blood-magic.md --- # 12.2 KubeJS Blood Magic *** ``` 支持的格式: - 祭坛 event.recipes.bloodmagic.altar(output, input) event.recipes.bloodmagic.altar(output, input).upgradeLevel(int).altarSyphon(int).consumptionRate(int).drainRate(int) - 炼金矩阵 event.recipes.bloodmagic.array(output, baseInput, addedInput) event.recipes.bloodmagic.array(output, baseInput, addedInput).texture(string) - 狱火熔炉 event.recipes.bloodmagic.soulforge(output, [input]) event.recipes.bloodmagic.soulforge(output, [input]).minimumDrain(double).drain(double) - 奥术粉灰 event.recipes.bloodmagic.arc(output, input, tool) event.recipes.bloodmagic.arc(output, input, tool, [addedOutput]) event.recipes.bloodmagic.arc(output, input, tool, [addedOutput]).consumeIngredient(boolean).outputFluid(fluid) - 炼金术桌 event.recipes.bloodmagic.alchemytable(output, [input]) event.recipes.bloodmagic.alchemytable(output, [input]).syphon(int).ticks(int).upgradeLevel(int) 例: onEvent('recipes', event => { const { altar, array, soulforge, arc, alchemytable } = event.recipes.bloodmagic altar('minecraft:carrot', 'minecraft:apple') array('minecraft:spruce_planks', 'minecraft:oak_planks', 'minecraft:birch_planks') soulforge('minecraft:stone', ['minecraft:gold_ore', 'minecraft:diamond_ore', 'minecraft:iron_ore']).drain(1.0) arc('minecraft:netherite_ingot', 'minecraft:iron_ingot', 'minecraft:iron_pickaxe', [Item.of('minecraft:cobblestone').chance(0.4)]) alchemytable('minecraft:gold_ingot', ['minecraft:iron_ingot', 'minecraft:iron_ingot', 'minecraft:iron_ingot', 'minecraft:iron_ingot']).upgradeLevel(2) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/addons/kubejs-mekanism.md --- # 12.3 KubeJS Mekanism *** :::info This page is currently under construction and does not yet cover all aspects or information. We are working on it. Thank you for your understanding. ::: ``` mekanism:crushing mekanism:enriching mekanism:smelting mekanism:chemical_infusing mekanism:combining mekanism:crystallizing mekanism:dissolution mekanism:compressing mekanism:purifying mekanism:injecting mekanism:energy_conversion mekanism:gas_conversion mekanism:oxidizing mekanism:metallurgic_infusing mekanism:reaction mekanism:sawing ``` | 中文名称(ID) | 用法 | | -------- | -- | | | | | | | | | | --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/addons/kubejs-mekanism.md --- # 12.3 KubeJS Mekanism *** :::info This page is currently under construction and does not yet cover all aspects or information. We are working on it. Thank you for your understanding. ::: ``` mekanism:crushing mekanism:enriching mekanism:smelting mekanism:chemical_infusing mekanism:combining mekanism:crystallizing mekanism:dissolution mekanism:compressing mekanism:purifying mekanism:injecting mekanism:energy_conversion mekanism:gas_conversion mekanism:oxidizing mekanism:metallurgic_infusing mekanism:reaction mekanism:sawing ``` | 中文名称(ID) | 用法 | | -------- | -- | | | | | | | | | | --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/addons/kubejs-ui.md --- # 12.4 KubeJS UI *** ``` onEvent('ui.main_menu', event => { event.replace(ui => { ui.tilingBackground('kubejsui:textures/example_background.png', 256)//设置背景 ui.minecraftLogo(30)//设置logo ui.button(b => {//新建按钮 b.name = 'Test' b.x = 10 b.y = 10 b.action = 'minecraft:singleplayer' }) ui.button(b => { b.name = 'Test but in bottom right corner' b.x = ui.width - b.width - 10 b.y = ui.height - b.height - 10 b.action = 'https://feed-the-beast.com/' }) ui.label(l => {//新建 lable l.name = text.yellow('FTB Stranded') l.x = 2 l.y = ui.height - 12 l.action = 'https://feed-the-beast.com/' }) ui.image(i => {//新建图片 i.x = (ui.width - 40) / 2 i.y = (ui.height - 30) / 2 i.width = 40 i.height = 30 i.action = 'https://feed-the-beast.com/' }) ui.label(l => { l.name = text.aqua('Large label') l.x = 100 l.y = ui.height - 20 l.height = 15 l.shadow = true // 字体是否带阴影 }) }) }) /* 常用的值: ui.height <=> 页面高度 ui.width <=> 页面宽度 x.action <=> 点击时执行的操作 x.name <=> 该项的显示名称 */ ``` ![](https://m1.miaomc.cn/uploads/20220112_d97e768a1fe60.png) --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/addons/kubejs-ui.md --- # 12.4 KubeJS UI *** ``` onEvent('ui.main_menu', event => { event.replace(ui => { ui.tilingBackground('kubejsui:textures/example_background.png', 256)//设置背景 ui.minecraftLogo(30)//设置logo ui.button(b => {//新建按钮 b.name = 'Test' b.x = 10 b.y = 10 b.action = 'minecraft:singleplayer' }) ui.button(b => { b.name = 'Test but in bottom right corner' b.x = ui.width - b.width - 10 b.y = ui.height - b.height - 10 b.action = 'https://feed-the-beast.com/' }) ui.label(l => {//新建 lable l.name = text.yellow('FTB Stranded') l.x = 2 l.y = ui.height - 12 l.action = 'https://feed-the-beast.com/' }) ui.image(i => {//新建图片 i.x = (ui.width - 40) / 2 i.y = (ui.height - 30) / 2 i.width = 40 i.height = 30 i.action = 'https://feed-the-beast.com/' }) ui.label(l => { l.name = text.aqua('Large label') l.x = 100 l.y = ui.height - 20 l.height = 15 l.shadow = true // 字体是否带阴影 }) }) }) /* 常用的值: ui.height <=> 页面高度 ui.width <=> 页面宽度 x.action <=> 点击时执行的操作 x.name <=> 该项的显示名称 */ ``` ![](https://m1.miaomc.cn/uploads/20220112_d97e768a1fe60.png) --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/addons/termal.md --- # 12.5 KubeJS Termal *** (本节内容参考了[KubeJS Thermal 官方Wiki](https://mods.latvian.dev/books/kubejs/page/kubejs-thermal)) 你可以使用CTRL + F 键快捷搜索本页内容。 ``` onEvent('recipes', event => { // 红石炉 // 将四个煤变为煤炭。 event.recipes.thermal.furnace('minecraft:diamond', '4x minecraft:coal'); // 将干海带制为皮革,需要大量能量。 event.recipes.thermal.furnace('minecraft:leather', 'minecraft:dried_kelp').energy(20000); // 锯木机 // 输入一个橡木原木,5%概率输出苹果,10%概率输出树苗。 event.recipes.thermal.sawmill([Item.of('minecraft:apple').withChance(0.05), Item.of('minecraft:oak_sapling').withChance(0.1)], 'minecraft:oak_leaves') // 金合欢台阶合成四个按钮 event.recipes.thermal.sawmill('4x minecraft:acacia_button', 'minecraft:acacia_slab') // 磨粉机 // 将任何树叶方块转换为四根木棍并有50%概率获得五根,需要少量能量。 event.recipes.thermal.pulverizer(Item.of('minecraft:stick').withChance(4.5), '#minecraft:leaves').energy(100) // 将燧石转换为铁粒并有10%概率获得两个。 event.recipes.thermal.pulverizer(Item.of('minecraft:iron_nugget').withChance(1.1), 'minecraft:flint') // 感应炉 // 将煤炭块转换为四个钻石并有50%概率获得五个。 event.recipes.thermal.smelter(['4x minecraft:diamond', Item.of('minecraft:diamond').withChance(0.5)], 'minecraft:coal_block') // 将铁锭和铜锭转换为金锭,需要10,000 FE。 event.recipes.thermal.smelter('minecraft:gold_ingot', ['minecraft:iron_ingot', 'minecraft:copper_ingot']).energy(10000) // 离心机 // 将树苗转换为木棍(50%概率)和300mb水(100%概率)。 event.recipes.thermal.centrifuge([Item.of('minecraft:stick').withChance(0.5), Fluid.of('minecraft:water', 300)], '#minecraft:saplings') // 将两个甜浆果转换为红色染料。 event.recipes.thermal.centrifuge('minecraft:red_dye', '2x minecraft:sweet_berries') // 多驱冲压机 // 将7个骨粉转换为骨头。 event.recipes.thermal.press('minecraft:bone', '7x minecraft:bone_meal') // 使用铁粉和硬币模具合成铁粒,注意配方中模具物品必须具有thermal:crafting/dies标签! event.recipes.thermal.press('minecraft:iron_nugget', ['#forge:dusts/iron', 'thermal:press_coin_die']) // 熔岩炉 // 将树苗转换为400mb水。 event.recipes.thermal.crucible(Fluid.of('minecraft:water', 400), '#minecraft:saplings').energy(100) // 将矿石转换为500mb岩浆。 event.recipes.thermal.crucible(Fluid.of('minecraft:lava', 500), '#forge:ores') // 急速冷冻机 // 将普通箭转换为迟缓之箭。 event.recipes.thermal.chiller(Item.of('minecraft:tipped_arrow', '{Potion:"minecraft:slowness"}'), [Fluid.of('minecraft:water', 100), 'minecraft:arrow']) // 使用球形铸模将岩浆转换为生铁,注意配方中铸模物品必须具有thermal:crafting/casts标签! event.recipes.thermal.chiller('minecraft:raw_iron', [Fluid.of('minecraft:lava', 1000), 'thermal:chiller_ball_cast']) // 流体精炼机 // 将杂酚油转换为树油和乳胶,概率获得橡胶。 event.recipes.thermal.refinery([Item.of('thermal:rubber').withChance(0.8), Fluid.of('thermal:tree_oil', 100), Fluid.of('thermal:latex', 50)], Fluid.of('thermal:creosote', 200)) // 将书油转换为少量精炼油,需要大量能量。 event.recipes.thermal.refinery(Fluid.of('thermal:refined_fuel', 50), Fluid.of('thermal:tree_oil', 100)).energy(20000) // Unbrew an awkward potion. This uses the cofh core potion fluid with some nbt. event.recipes.thermal.refinery([Fluid.of('minecraft:water', 1000), 'minecraft:nether_wart'], Fluid.of('cofh_core:potion', 1000, '{Potion:"minecraft:awkward"}')) // 药水酿造机 // 将红石粉和200mb岩浆转换为不稳定熔融红石。 event.recipes.thermal.brewer(Fluid.of('thermal:redstone', 200), [Fluid.of('minecraft:lava', 200), 'minecraft:redstone']) // 使用64个基岩和粗制的药水酿造无法合成的药水(无nbt)。 event.recipes.thermal.brewer(Fluid.of('cofh_core:potion', 1000), [Fluid.of('cofh_core:potion', 1000, '{Potion:"minecraft:awkward"}'), '64x minecraft:bedrock']) // 流体灌装机 // 为海绵注水。 event.recipes.thermal.bottler('minecraft:wet_sponge', [Fluid.of('minecraft:water', 10000), 'minecraft:sponge']) // 将齿轮和不稳定熔融红石转换为机器框架。 event.recipes.thermal.bottler('thermal:machine_frame', ['#forge:gears', Fluid.of('thermal:redstone', 500)]).energy(500) }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/addons/termal.md --- # 12.5 KubeJS Termal *** (本节内容参考了[KubeJS Thermal 官方Wiki](https://mods.latvian.dev/books/kubejs/page/kubejs-thermal)) 你可以使用CTRL + F 键快捷搜索本页内容。 ``` onEvent('recipes', event => { // 红石炉 // 将四个煤变为煤炭。 event.recipes.thermal.furnace('minecraft:diamond', '4x minecraft:coal'); // 将干海带制为皮革,需要大量能量。 event.recipes.thermal.furnace('minecraft:leather', 'minecraft:dried_kelp').energy(20000); // 锯木机 // 输入一个橡木原木,5%概率输出苹果,10%概率输出树苗。 event.recipes.thermal.sawmill([Item.of('minecraft:apple').withChance(0.05), Item.of('minecraft:oak_sapling').withChance(0.1)], 'minecraft:oak_leaves') // 金合欢台阶合成四个按钮 event.recipes.thermal.sawmill('4x minecraft:acacia_button', 'minecraft:acacia_slab') // 磨粉机 // 将任何树叶方块转换为四根木棍并有50%概率获得五根,需要少量能量。 event.recipes.thermal.pulverizer(Item.of('minecraft:stick').withChance(4.5), '#minecraft:leaves').energy(100) // 将燧石转换为铁粒并有10%概率获得两个。 event.recipes.thermal.pulverizer(Item.of('minecraft:iron_nugget').withChance(1.1), 'minecraft:flint') // 感应炉 // 将煤炭块转换为四个钻石并有50%概率获得五个。 event.recipes.thermal.smelter(['4x minecraft:diamond', Item.of('minecraft:diamond').withChance(0.5)], 'minecraft:coal_block') // 将铁锭和铜锭转换为金锭,需要10,000 FE。 event.recipes.thermal.smelter('minecraft:gold_ingot', ['minecraft:iron_ingot', 'minecraft:copper_ingot']).energy(10000) // 离心机 // 将树苗转换为木棍(50%概率)和300mb水(100%概率)。 event.recipes.thermal.centrifuge([Item.of('minecraft:stick').withChance(0.5), Fluid.of('minecraft:water', 300)], '#minecraft:saplings') // 将两个甜浆果转换为红色染料。 event.recipes.thermal.centrifuge('minecraft:red_dye', '2x minecraft:sweet_berries') // 多驱冲压机 // 将7个骨粉转换为骨头。 event.recipes.thermal.press('minecraft:bone', '7x minecraft:bone_meal') // 使用铁粉和硬币模具合成铁粒,注意配方中模具物品必须具有thermal:crafting/dies标签! event.recipes.thermal.press('minecraft:iron_nugget', ['#forge:dusts/iron', 'thermal:press_coin_die']) // 熔岩炉 // 将树苗转换为400mb水。 event.recipes.thermal.crucible(Fluid.of('minecraft:water', 400), '#minecraft:saplings').energy(100) // 将矿石转换为500mb岩浆。 event.recipes.thermal.crucible(Fluid.of('minecraft:lava', 500), '#forge:ores') // 急速冷冻机 // 将普通箭转换为迟缓之箭。 event.recipes.thermal.chiller(Item.of('minecraft:tipped_arrow', '{Potion:"minecraft:slowness"}'), [Fluid.of('minecraft:water', 100), 'minecraft:arrow']) // 使用球形铸模将岩浆转换为生铁,注意配方中铸模物品必须具有thermal:crafting/casts标签! event.recipes.thermal.chiller('minecraft:raw_iron', [Fluid.of('minecraft:lava', 1000), 'thermal:chiller_ball_cast']) // 流体精炼机 // 将杂酚油转换为树油和乳胶,概率获得橡胶。 event.recipes.thermal.refinery([Item.of('thermal:rubber').withChance(0.8), Fluid.of('thermal:tree_oil', 100), Fluid.of('thermal:latex', 50)], Fluid.of('thermal:creosote', 200)) // 将书油转换为少量精炼油,需要大量能量。 event.recipes.thermal.refinery(Fluid.of('thermal:refined_fuel', 50), Fluid.of('thermal:tree_oil', 100)).energy(20000) // Unbrew an awkward potion. This uses the cofh core potion fluid with some nbt. event.recipes.thermal.refinery([Fluid.of('minecraft:water', 1000), 'minecraft:nether_wart'], Fluid.of('cofh_core:potion', 1000, '{Potion:"minecraft:awkward"}')) // 药水酿造机 // 将红石粉和200mb岩浆转换为不稳定熔融红石。 event.recipes.thermal.brewer(Fluid.of('thermal:redstone', 200), [Fluid.of('minecraft:lava', 200), 'minecraft:redstone']) // 使用64个基岩和粗制的药水酿造无法合成的药水(无nbt)。 event.recipes.thermal.brewer(Fluid.of('cofh_core:potion', 1000), [Fluid.of('cofh_core:potion', 1000, '{Potion:"minecraft:awkward"}'), '64x minecraft:bedrock']) // 流体灌装机 // 为海绵注水。 event.recipes.thermal.bottler('minecraft:wet_sponge', [Fluid.of('minecraft:water', 10000), 'minecraft:sponge']) // 将齿轮和不稳定熔融红石转换为机器框架。 event.recipes.thermal.bottler('thermal:machine_frame', ['#forge:gears', Fluid.of('thermal:redstone', 500)]).energy(500) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/addons/lootjs.md --- # 12.7 便捷战利品表修改(LootJS Forge) ## 一、概要 ([LootJS Wiki](https://github.com/AlmostReliable/lootjs-forge/wiki) 已经非常详尽,本节仅做翻译 + 细微调整) LootJS是一个KubeJS的附属mod,能够让你更加便捷地修改战利品表,掉落战利品时执行事件等 mod链接:[Github](https://github.com/AlmostReliable/lootjs-forge) [Curseforge](https://www.curseforge.com/minecraft/mc-mods/lootjs-forge),许可:LGPL-3.0 由于战利品表是在服务端侧被处理的,所以显然你应该把它放到`kubejs\server_scripts`文件夹下 你可以通过`/reload`命令来重载LootJS的修改内容 推荐先阅读15章来了解物品的效果内容和实体,玩家修改内容 ## 二、LootJS是如何工作的 下面这张图展示了游戏掉落物品的过程 ![](https://m1.miaomc.cn/uploads/20220424_31c09db2a2078.png) ## 三、基本格式 LootJS添加了一个KubeJS事件`lootjs`来修改战利品表,也就是这样: ``` onEvent("lootjs", (event) => { // 在此处编写代码 }); ``` 下面是一个基础的例子,使苦力怕有30%的概率掉落烈焰棒 ``` onEvent("lootjs", (event) => { // 你把底下这个写成一行也行,除了可能会有点难看以外( event .addEntityLootModifier("minecraft:creeper")// 获取LootActionsBuilder .randomChance(0.3) // 战利品表条件:添加掉落概率 .thenAdd("minecraft:blaze_rod");// 战利品表事件:掉落物品 }); ``` 每一个战利品表修改都应至少有一个战利品表事件 ### 1、常用函数 函数`enableLogging()`会将修改过的战利品表打印到控制台便于排查问题 函数`.disableLootModification(战利品表)`会锁定指定战利品表以防修改 ``` onEvent("lootjs", (event) => { // 启用战利品表输出 event.enableLogging(); // 禁用树叶的战利品表(通过正则表达式) event.disableLootModification(/.*:blocks\/.*_leaves/); // 禁用单个战利品表 event.disableLootModification("minecraft:entities/bat"); }); ``` LootJS提供了一些返回`LootActionsBuilder`对象的函数,这使修改掉落条件和掉落物成为可能。 | 函数 | 功能 | | ---------------------------- | -------------------- | | addBlockLootModifier(命名空间) | 为方块添加新的战利品表修饰器 | | addEntityLootModifier(命名空间) | 为实体添加新的战利品表修饰器 | | addLootTableModifier(命名空间ID) | 为给定战利品表添加新的修饰器 | | addLootTypeModifier(战利品表类型) | 为给定战利品表类型\[1]添加新的修饰器 | | getGlobalModifiers() | 返回包含所有战利品表修饰器的列表 | | removeGlobalModifier(值)\[2] | 移除给定的战利品表修饰器 | \[1]以下是可用的战利品表类型(LootJS)的值 ``` LootType.UNKNOWN // 未知 LootType.BLOCK // 方块 LootType.ENTITY // 实体 LootType.CHEST // 战利品箱 LootType.FISHING // 钓鱼 LootType.GIFT // 礼物(村民等) ``` \[2]:你可以使用形如`@modid`来移除指定mod的全局战利品表修改,使用形如`examplemod:example_loot_change`的战利品表ID来移除指定战利品表,此外,该函数仅对全局战利品表生效,不能阻止mod直接将其物品添加到战利品表中 ### 2、掉落物修改(战利品表事件) **战利品表事件**用于修改战利品表的掉落物或为其添加不同效果。每一个战利品表修饰器都至少包含一个战利品表事件 **thenAdd(物品)** 可以向当前战利品表修饰器中添加物品 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .thenAdd("minecraft:flint");// 返回方块的战利品表修饰器并添加单个物品 }); ``` **thenRemove(物品)** 将移除战利品表在所有符合给定条件的物品 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .thenRemove('#forge:gunpowder')// 从苦力怕的战利品表中移除所有#forge:gunpowder }); ``` **thenReplace(被替换物品, 替换物品)** 将战利品表中符号条件的物品全部替换为给定物品 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .thenReplace('#forge:gunpowder', 'minecraft:diamond')// 现在这只苦力怕会掉钻石了 }); ``` **thenModify(物品, 回调)** 对于当前随机池的所有符合条件的物品,一个回调将被调用。LootJS会把该物品传给回调来修改它并返回该物品。记住一定要有返回语句。 ``` onEvent("lootjs", (event) => { event .addLootTypeModifier(LootType.ENTITY) .weatherCheck({ raining: true, }) .thenModify(Ingredient.getAll(), (itemStack) => { return itemStack.withCount(itemStack.getCount() * 2);// 返回语句 }); }); ``` 在这个例子中,下雨时实体的掉落物品数量将翻倍 **thenExplode(范围, 是否损坏方块, 是否引火)** 可以在掉落战利品时生成一个爆炸,而掉落物不会被炸毁。范围可以是任何数字而后两个参数(是否损坏方块, 是否引火)应为布尔值 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .thenExplode(1, false, false);// ExplosionJS详见15.3 }); ``` **thenLightningStrike(是否破坏方块)** 可以在掉落战利品时生成一个闪电。同样,物品不会被破坏。 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .thenLightningStrike(false); }); ``` **thenApply(回调)** 可以在掉落战利品时进行回调,非常有意思的一个功能。 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .thenApply((context) => { // 有关玩家(Player),实体(EntityJS),方块(BlockContainerJS)等还请查看15章。 // 回调函数 // if(context.getPlayer()!=null){context.getPlayer().tell("测试")} }); }); ``` 可用函数(推荐先阅读15章)见文末LootContextJS ### 3、战利品表掉落条件 除了战利品表事件,你还可以为战利品表掉落添加条件限制 **值得注意的是,这些条件仅对新增的战利品修饰器生效** **matchLoot(物品, 可选 是否全部比对)** 可以在符合条件的物品掉落时执行修改。是否全部比对的默认值为false,当为true时,当前战利品池中所有的战利品必须符合条件才能执行修改 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:cow") .matchLoot("minecraft:leather") .thenAdd("minecraft:carrot");// 在牛掉落皮革时再多掉落一个胡萝卜 }); ``` **matchMainHand(IngredientJS 物品)** 当玩家主手手持指定物品时才执行修改。 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("#forge:ores") .matchMainHand(Item.of("minecraft:netherite_pickaxe").ignoreNBT()) .thenAdd("minecraft:gravel");// 手持下界合金斧(忽略nbt)时添加掉落物砂砾 }); ``` **matchOffHand(IngredientJS 物品)** 当玩家副手手持指定物品时才执行修改。 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("#forge:ores") .matchOffHand(Item.of("minecraft:netherite_pickaxe").ignoreNBT()) .thenAdd("minecraft:gravel"); }); ``` **survivesExplosion()** 可以防止修改的掉落物被爆炸破坏 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .survivesExplosion() .thenAdd("minecraft:gravel"); }); ``` \*\*timeCheck(period, min, max)\*\*\*和 **timeCheck(min, max)** 使修改内容在符合指定游戏刻条件下才能生效 \*当前游戏时间除以period值所得的余数来与value匹配。(若period被设置为100,value被设置为1,则时间为1/101/201……时通过),min和max则为数值范围(开区间) ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .timeCheck(24000, 0, 9000) // 只有白天才会掉落 .thenAdd("minecraft:diamond"); }); ``` **weatherCheck(值)** 可以为战利品表添加天气条件。其中值的格式: ``` { raining: true, // 雨天天气 thundering: true // 雷雨天气 } ``` 如果你只使用一个值,则另一个会被忽略 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .weatherCheck({ raining: true, // 只使用一个值 }) .thenAdd("minecraft:diamond"); }); ``` **randomChance(浮点型 随机值)** 可以概率执行掉落物品修改 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .randomChance(0.3) // 30% .thenAdd("minecraft:diamond"); }); ``` **randomChanceWithLooting(浮点型 概率, 整形 附魔等级)** 可以根据抢夺等级概率调整掉落概率 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .randomChanceWithLooting(0.3, 2) // 30% .thenAdd("minecraft:diamond"); }); ``` **randomChanceWithEnchantment(字符串 附魔ID, \[概率])** 可以根据给定附魔的等级调整概率 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .randomChanceWithEnchantment("minecraft:looting", [0, 0.1, 0.5, 1]) .thenAdd("minecraft:diamond"); /* [0, 0.1, 0.5, 1]: 没有抢夺附魔时掉落概率为 0% 抢夺 I 时掉落概率为 10% 抢夺 II 时掉落概率为 50% 抢夺 III 时掉落概率为 100% */ }); ``` **biome(...字符串 群系ID)** 当\_所有\_群系均符合时执行修改。你可以传入群系标签(形如`#cold`)来限定群系必须为寒冷群系(见6 自定义世界生成) ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .biome("minecraft:jungle") .thenAdd("minecraft:diamond");// 丛林群系才会掉落物品 }); ``` **anyBiome(...字符串 群系ID)** 当\_任意\_群系符合时即执行修改。你可以传入群系标签(形如`#cold`)来限定群系必须为寒冷群系 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .anyBiome("minecraft:jungle", "minecraft:ocean") .thenAdd("minecraft:diamond");// 在丛林或海洋时掉落 }); ``` **anyDimension(...字符串 维度ID)** 当\_任意\_维度符合时即执行修改。 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .anyDimension("minecraft:nether") .thenAdd("minecraft:diamond"); }); ``` **anyStructure(\[字符串 结构ID], 布尔值 是否精确匹配)** 可以判断战利品掉落于指定结构中。当精确匹配的值为true时,玩家在结构的建筑内才生效;为false时则在结构范围内即可 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .anyStructure(["minecraft:stronghold", "minecraft:village"], false) .thenAdd("minecraft:diamond");// 在给定结构范围内即掉落 }); ``` **lightLevel(min, max)** 在给定光照强度范围内才执行修改(开区间) ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .lightLevel(0, 15) .thenAdd("minecraft:diamond"); }); ``` **killedByPlayer()** 只有被玩家杀死时才执行修改 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:iron_golem") .thenRemove("#forge:ingots/iron");// 先移除再添加 event .addEntityLootModifier("minecraft:iron_golem") .killedByPlayer() .thenAdd(Item.of("minecraft:iron_ingot").withCount(2));// 只有玩家杀死铁傀儡时才掉落铁锭,干掉刷铁机( }); ``` **matchEntity(callback)** 可以匹配触发战利品表的实体(如死亡的 / 打开箱子 / 破坏方块的),当条件符合时执行修改 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .matchEntity((entity) => { // EntityPredicateBuilderJS(见文末) }) .thenAdd("minecraft:diamond"); }); ``` **matchDirectKiller(callback)** 可以匹配直接击杀目标的实体(例如弓箭实体而不是发射的实体),当条件符合时执行修改 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .matchKiller((entity) => { // EntityPredicateBuilderJS(见文末) }) .thenAdd("minecraft:diamond"); }); ``` **matchPlayer(callback)** 可以匹配玩家,当条件符合时执行修改。当玩家杀死了另一个玩家时,将以击杀者作为匹配对象 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .matchPlayer((player) => { // EntityPredicateBuilderJS(见文末) }) .thenAdd("minecraft:diamond"); }); ``` **matchDamageSource(callback)** 可以匹配伤害类型,当条件符合时执行修改 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .matchDamageSource((source) => { // DamageSourcePredicateBuilderJS(见文末) }) .thenAdd("minecraft:diamond"); }); ``` **distanceToKiller(IntervalJS 距离)** 可以检测实体掉落与击杀者之间的距离 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .distanceToKiller(Interval.min(25))/*IntervalJS (见文末)*/ .thenAdd("minecraft:diamond"); }); ``` **hasAnyStage(...字符串 Gamestage)** 可以检测玩家是否具有指定Gamestage,当具有\_任意一个\_时执行修改 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:pig") .hasAnyStage("stoneage") .thenAdd("minecraft:coal");// 玩家具有stoneage时掉落一个煤炭 }); ``` **playerPredicate(callback),entityPredicate(callback),killerPredicate(callback),directKillerPredicate(callback)** 可以自定义回调函数来匹配玩家,实体,击杀者,直接击杀者,当条件符合时执行修改。*注意:该回调必须返回true或false* ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:pig") .playerPredicate((player) => player.stages.has("stoneage")) .thenAdd("minecraft:emerald"); }); onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:pig") .entityPredicate((entity) => entity.type == "minecraft:pig") .thenAdd("minecraft:diamond"); }); onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:pig") .killerPredicate((entity) => entity.type == "minecraft:pig") .thenAdd("minecraft:feather"); }); onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:pig") .directKillerPredicate((entity) => entity.type == "minecraft:pig") .thenAdd("minecraft:stone"); }); ``` **not(callback)** 可以反转战利品表条件 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .not((n) => { n.biome("minecraft:jungle") }) .thenAdd("minecraft:diamond");// 当群系不是丛林时掉落钻石 }); ``` **or(callback)** 可以同时添加多个条件,当\_一个条件为真\_时即通过 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .or((or) => { or.biome("minecraft:jungle").anyDimension("minecraft:nether"); }) .thenAdd("minecraft:diamond");// 群系为丛林**或**维度为下界时掉落钻石 }); ``` **and(callback)** 可以同时添加多个条件,当\_所有条件为真\_时即通过 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .and((and) => { and.biome("minecraft:jungle").distanceToKiller(Interval.min(25)); }) .thenAdd("minecraft:diamond");// 群系为丛林**且**击杀者距离大于25时掉落钻石 }); ``` ### 4、IntervalJS,EntityPredicateBuilderJS,DamageSourcePredicateBuilderJS和LootContextJS **IntervalJS**可以用来表示两个数字之间的范围。注意:以下所有范围均为**开区间** | 格式 | 描述 | 返回值 | | -------------------------- | ------------------------- | ---------- | | Interval.between(min, max) | 返回最小最大值之间的范围 `(min, max)` | IntervalJS | | Interval.min(min) | 返回只限制了最小值的范围 `(min,+∞)` | IntervalJS | | Interval.max(max) | 返回只限制了最大值的范围 `(-∞,max)` | IntervalJS | | **函数** | **描述** | **返回值** | | matches(值) | 检测值是否在范围内 | 布尔值 | | matchesSqr(值) | 检测值是否在范围内 | 布尔值 | **EntityPredicateBuilderJS** 可以用条件限制实体以判断是否执行修改,以下返回值均为EntityPredicateBuilderJS | 函数 | 描述 | | ------------------------------------------------ | ------------------------------ | | anyType(...类型) | 判断实体是否有给定标签(输入示例:`#skeletons`) | | isOnFire(布尔值 条件真假) | 判断实体是否着火 | | isCrouching(布尔值 条件真假) | 判断实体是否正在潜行 | | isSprinting(布尔值 条件真假) | 判断实体是否正在疾跑 | | isSwimming(布尔值 条件真假) | 判断实体是否正在游泳 | | isBaby(布尔值 条件真假) | 判断实体是否为幼年状态 | | isInWater(布尔值 条件真假) | 判断实体是否在水里 | | isMonster(布尔值 条件真假) | 判断实体是否为敌对生物 | | isCreature(布尔值 条件真假) | 判断实体是否为友好生物 | | isOnGround(布尔值 条件真假) | 判断实体是否在地上 | | isUndeadMob(布尔值 条件真假) | 判断实体是否为亡灵生物 | | isArthropodMob(布尔值 条件真假) | 判断实体是否为节肢动物 | | isWaterMob(布尔值 条件真假) | 判断实体是否为水生动物 | | isIllegarMob(布尔值 条件真假) | 判断实体是否为掠夺生物 | | hasEffect(字符串 效果ID, 整形 等级) 和 hasEffect(字符串 效果ID) | 判断实体是否具有给定效果 | | nbt(json) | 判断实体是否具有指定nbt | | matchMount(回调) | 判断实体所骑乘的实体\[1] | | matchTargetedEntity(回调) | 判断实体的目标实体\[1] | | matchSlot(整形 栏位编号, IngredientJS 物品) | 判断实体给定物品栏栏位的物品 | \[1]:提供EntityPredicateBuilderJS 例子: ``` onEvent("lootjs", (event) => { event .addLootTypeModifier([LootType.ENTITY]) .matchEntity((entity) => { entity.anyType("#skeletons"); entity.matchMount((mount) => { mount.anyType("minecraft:spider"); }); }) .thenAdd("minecraft:magma_cream");// 当骷髅类实体骑乘蜘蛛时掉落一个演讲稿 }); ``` **DamageSourcePredicateBuilderJS** 用条件来限制伤害类型以判断是否执行修改 | 函数 | 描述 | | ----------------------------------- | ------------ | | anyType(...类型) | 判断伤害是否符合类型 | | isProjectile(布尔值 条件真假) | 判断伤害是否为投掷物 | | isExplosion(布尔值 条件真假) | 判断伤害是否为爆炸 | | doesBypassArmor(布尔值 条件真假) | 判断伤害是否无视盔甲 | | doesBypassInvulnerability(布尔值 条件真假) | 判断伤害是否无视隐身效果 | | doesBypassMagic(布尔值 条件真假) | 判断伤害是否为饥饿引起 | | isFire(布尔值 条件真假) | 判断伤害是否为火 | | isMagic(布尔值 条件真假) | 判断伤害是否为饥饿 | | isLightning(布尔值 条件真假) | 判断伤害是否为闪电 | | matchDirectEntity(回调) | 判断直接造成伤害的实体 | | matchSourceEntity(回调) | 判断造成伤害的实体源 | \[2]:提供DamageSourcePredicateBuilderJS | 函数 | 描述 | 返回值 | | ---------------------------- | ---------------------- | ---------------- | | getType() | 返回战利品类型 | LootTypes\[5] | | getPosition() | 返回掉落位置 | Vector3d | | getEntity() | 返回具有当前战利品表的实体(可能为null) | EntityJS | | getKillerEntity() | 返回杀死对象的实体(可能为null) | EntityJS | | getPlayer() | 返回杀死对象的玩家(可能为null\[6]) | PlayerJS | | getDamageSource() | 返回伤害类型(可能为null\[6]) | DamageSourceJS | | getTool() | 返回触发战利品表使用的工具(可能为null) | ItemStackJS | | getDestroyedBlock() | 返回被破坏的方块(可能为null) | BlockContainerJS | | isExploded() | 返回战利品表是否被爆炸触发 | 布尔值 | | getExplosionRadius() | 返回触发的爆炸的半径\[7] | 整形 | | getLevel() | 返回WorldJS | WorldJS | | getServer() | 返回ServerJS | ServerJS | | getLuck() | 返回玩家幸运值 | 整形 | | getLooting() | 返回LootingModifier | 整形 | | getRandom() | 返回范围(?) | Random | | addLoot(ItemStackJS 物品) | 添加掉落物 | void | | removeLoot(IngredientJS 物品) | 移除掉落物 | void | | findLoot(IngredientJS 物品) | 返回掉落物列表 | List | | hasLoot(IngredientJS 物品) | 返回是否掉落指定物品 | 布尔值 | | forEachLoot(Consumer action) | 遍历每个物品并给予回调\[8] | void | \[5]:见 本章 常用函数 部分 \[6]:为null的例子:骷髅射杀苦力怕 \[7]:如果不是则为0 \[8]:例如 ``` const callback = (item) => { console.log(item); }; ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/addons/lootjs.md --- # 12.7 便捷战利品表修改(LootJS Forge) ## 一、概要 ([LootJS Wiki](https://github.com/AlmostReliable/lootjs-forge/wiki) 已经非常详尽,本节仅做翻译 + 细微调整) LootJS是一个KubeJS的附属mod,能够让你更加便捷地修改战利品表,掉落战利品时执行事件等 mod链接:[Github](https://github.com/AlmostReliable/lootjs-forge) [Curseforge](https://www.curseforge.com/minecraft/mc-mods/lootjs-forge),许可:LGPL-3.0 由于战利品表是在服务端侧被处理的,所以显然你应该把它放到`kubejs\server_scripts`文件夹下 你可以通过`/reload`命令来重载LootJS的修改内容 推荐先阅读15章来了解物品的效果内容和实体,玩家修改内容 ## 二、LootJS是如何工作的 下面这张图展示了游戏掉落物品的过程 ![](https://m1.miaomc.cn/uploads/20220424_31c09db2a2078.png) ## 三、基本格式 LootJS添加了一个KubeJS事件`lootjs`来修改战利品表,也就是这样: ``` onEvent("lootjs", (event) => { // 在此处编写代码 }); ``` 下面是一个基础的例子,使苦力怕有30%的概率掉落烈焰棒 ``` onEvent("lootjs", (event) => { // 你把底下这个写成一行也行,除了可能会有点难看以外( event .addEntityLootModifier("minecraft:creeper")// 获取LootActionsBuilder .randomChance(0.3) // 战利品表条件:添加掉落概率 .thenAdd("minecraft:blaze_rod");// 战利品表事件:掉落物品 }); ``` 每一个战利品表修改都应至少有一个战利品表事件 ### 1、常用函数 函数`enableLogging()`会将修改过的战利品表打印到控制台便于排查问题 函数`.disableLootModification(战利品表)`会锁定指定战利品表以防修改 ``` onEvent("lootjs", (event) => { // 启用战利品表输出 event.enableLogging(); // 禁用树叶的战利品表(通过正则表达式) event.disableLootModification(/.*:blocks\/.*_leaves/); // 禁用单个战利品表 event.disableLootModification("minecraft:entities/bat"); }); ``` LootJS提供了一些返回`LootActionsBuilder`对象的函数,这使修改掉落条件和掉落物成为可能。 | 函数 | 功能 | | ---------------------------- | -------------------- | | addBlockLootModifier(命名空间) | 为方块添加新的战利品表修饰器 | | addEntityLootModifier(命名空间) | 为实体添加新的战利品表修饰器 | | addLootTableModifier(命名空间ID) | 为给定战利品表添加新的修饰器 | | addLootTypeModifier(战利品表类型) | 为给定战利品表类型\[1]添加新的修饰器 | | getGlobalModifiers() | 返回包含所有战利品表修饰器的列表 | | removeGlobalModifier(值)\[2] | 移除给定的战利品表修饰器 | \[1]以下是可用的战利品表类型(LootJS)的值 ``` LootType.UNKNOWN // 未知 LootType.BLOCK // 方块 LootType.ENTITY // 实体 LootType.CHEST // 战利品箱 LootType.FISHING // 钓鱼 LootType.GIFT // 礼物(村民等) ``` \[2]:你可以使用形如`@modid`来移除指定mod的全局战利品表修改,使用形如`examplemod:example_loot_change`的战利品表ID来移除指定战利品表,此外,该函数仅对全局战利品表生效,不能阻止mod直接将其物品添加到战利品表中 ### 2、掉落物修改(战利品表事件) **战利品表事件**用于修改战利品表的掉落物或为其添加不同效果。每一个战利品表修饰器都至少包含一个战利品表事件 **thenAdd(物品)** 可以向当前战利品表修饰器中添加物品 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .thenAdd("minecraft:flint");// 返回方块的战利品表修饰器并添加单个物品 }); ``` **thenRemove(物品)** 将移除战利品表在所有符合给定条件的物品 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .thenRemove('#forge:gunpowder')// 从苦力怕的战利品表中移除所有#forge:gunpowder }); ``` **thenReplace(被替换物品, 替换物品)** 将战利品表中符号条件的物品全部替换为给定物品 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .thenReplace('#forge:gunpowder', 'minecraft:diamond')// 现在这只苦力怕会掉钻石了 }); ``` **thenModify(物品, 回调)** 对于当前随机池的所有符合条件的物品,一个回调将被调用。LootJS会把该物品传给回调来修改它并返回该物品。记住一定要有返回语句。 ``` onEvent("lootjs", (event) => { event .addLootTypeModifier(LootType.ENTITY) .weatherCheck({ raining: true, }) .thenModify(Ingredient.getAll(), (itemStack) => { return itemStack.withCount(itemStack.getCount() * 2);// 返回语句 }); }); ``` 在这个例子中,下雨时实体的掉落物品数量将翻倍 **thenExplode(范围, 是否损坏方块, 是否引火)** 可以在掉落战利品时生成一个爆炸,而掉落物不会被炸毁。范围可以是任何数字而后两个参数(是否损坏方块, 是否引火)应为布尔值 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .thenExplode(1, false, false);// ExplosionJS详见15.3 }); ``` **thenLightningStrike(是否破坏方块)** 可以在掉落战利品时生成一个闪电。同样,物品不会被破坏。 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .thenLightningStrike(false); }); ``` **thenApply(回调)** 可以在掉落战利品时进行回调,非常有意思的一个功能。 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .thenApply((context) => { // 有关玩家(Player),实体(EntityJS),方块(BlockContainerJS)等还请查看15章。 // 回调函数 // if(context.getPlayer()!=null){context.getPlayer().tell("测试")} }); }); ``` 可用函数(推荐先阅读15章)见文末LootContextJS ### 3、战利品表掉落条件 除了战利品表事件,你还可以为战利品表掉落添加条件限制 **值得注意的是,这些条件仅对新增的战利品修饰器生效** **matchLoot(物品, 可选 是否全部比对)** 可以在符合条件的物品掉落时执行修改。是否全部比对的默认值为false,当为true时,当前战利品池中所有的战利品必须符合条件才能执行修改 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:cow") .matchLoot("minecraft:leather") .thenAdd("minecraft:carrot");// 在牛掉落皮革时再多掉落一个胡萝卜 }); ``` **matchMainHand(IngredientJS 物品)** 当玩家主手手持指定物品时才执行修改。 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("#forge:ores") .matchMainHand(Item.of("minecraft:netherite_pickaxe").ignoreNBT()) .thenAdd("minecraft:gravel");// 手持下界合金斧(忽略nbt)时添加掉落物砂砾 }); ``` **matchOffHand(IngredientJS 物品)** 当玩家副手手持指定物品时才执行修改。 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("#forge:ores") .matchOffHand(Item.of("minecraft:netherite_pickaxe").ignoreNBT()) .thenAdd("minecraft:gravel"); }); ``` **survivesExplosion()** 可以防止修改的掉落物被爆炸破坏 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .survivesExplosion() .thenAdd("minecraft:gravel"); }); ``` \*\*timeCheck(period, min, max)\*\*\*和 **timeCheck(min, max)** 使修改内容在符合指定游戏刻条件下才能生效 \*当前游戏时间除以period值所得的余数来与value匹配。(若period被设置为100,value被设置为1,则时间为1/101/201……时通过),min和max则为数值范围(开区间) ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .timeCheck(24000, 0, 9000) // 只有白天才会掉落 .thenAdd("minecraft:diamond"); }); ``` **weatherCheck(值)** 可以为战利品表添加天气条件。其中值的格式: ``` { raining: true, // 雨天天气 thundering: true // 雷雨天气 } ``` 如果你只使用一个值,则另一个会被忽略 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .weatherCheck({ raining: true, // 只使用一个值 }) .thenAdd("minecraft:diamond"); }); ``` **randomChance(浮点型 随机值)** 可以概率执行掉落物品修改 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .randomChance(0.3) // 30% .thenAdd("minecraft:diamond"); }); ``` **randomChanceWithLooting(浮点型 概率, 整形 附魔等级)** 可以根据抢夺等级概率调整掉落概率 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .randomChanceWithLooting(0.3, 2) // 30% .thenAdd("minecraft:diamond"); }); ``` **randomChanceWithEnchantment(字符串 附魔ID, \[概率])** 可以根据给定附魔的等级调整概率 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .randomChanceWithEnchantment("minecraft:looting", [0, 0.1, 0.5, 1]) .thenAdd("minecraft:diamond"); /* [0, 0.1, 0.5, 1]: 没有抢夺附魔时掉落概率为 0% 抢夺 I 时掉落概率为 10% 抢夺 II 时掉落概率为 50% 抢夺 III 时掉落概率为 100% */ }); ``` **biome(...字符串 群系ID)** 当\_所有\_群系均符合时执行修改。你可以传入群系标签(形如`#cold`)来限定群系必须为寒冷群系(见6 自定义世界生成) ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .biome("minecraft:jungle") .thenAdd("minecraft:diamond");// 丛林群系才会掉落物品 }); ``` **anyBiome(...字符串 群系ID)** 当\_任意\_群系符合时即执行修改。你可以传入群系标签(形如`#cold`)来限定群系必须为寒冷群系 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .anyBiome("minecraft:jungle", "minecraft:ocean") .thenAdd("minecraft:diamond");// 在丛林或海洋时掉落 }); ``` **anyDimension(...字符串 维度ID)** 当\_任意\_维度符合时即执行修改。 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .anyDimension("minecraft:nether") .thenAdd("minecraft:diamond"); }); ``` **anyStructure(\[字符串 结构ID], 布尔值 是否精确匹配)** 可以判断战利品掉落于指定结构中。当精确匹配的值为true时,玩家在结构的建筑内才生效;为false时则在结构范围内即可 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .anyStructure(["minecraft:stronghold", "minecraft:village"], false) .thenAdd("minecraft:diamond");// 在给定结构范围内即掉落 }); ``` **lightLevel(min, max)** 在给定光照强度范围内才执行修改(开区间) ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .lightLevel(0, 15) .thenAdd("minecraft:diamond"); }); ``` **killedByPlayer()** 只有被玩家杀死时才执行修改 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:iron_golem") .thenRemove("#forge:ingots/iron");// 先移除再添加 event .addEntityLootModifier("minecraft:iron_golem") .killedByPlayer() .thenAdd(Item.of("minecraft:iron_ingot").withCount(2));// 只有玩家杀死铁傀儡时才掉落铁锭,干掉刷铁机( }); ``` **matchEntity(callback)** 可以匹配触发战利品表的实体(如死亡的 / 打开箱子 / 破坏方块的),当条件符合时执行修改 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .matchEntity((entity) => { // EntityPredicateBuilderJS(见文末) }) .thenAdd("minecraft:diamond"); }); ``` **matchDirectKiller(callback)** 可以匹配直接击杀目标的实体(例如弓箭实体而不是发射的实体),当条件符合时执行修改 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .matchKiller((entity) => { // EntityPredicateBuilderJS(见文末) }) .thenAdd("minecraft:diamond"); }); ``` **matchPlayer(callback)** 可以匹配玩家,当条件符合时执行修改。当玩家杀死了另一个玩家时,将以击杀者作为匹配对象 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .matchPlayer((player) => { // EntityPredicateBuilderJS(见文末) }) .thenAdd("minecraft:diamond"); }); ``` **matchDamageSource(callback)** 可以匹配伤害类型,当条件符合时执行修改 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .matchDamageSource((source) => { // DamageSourcePredicateBuilderJS(见文末) }) .thenAdd("minecraft:diamond"); }); ``` **distanceToKiller(IntervalJS 距离)** 可以检测实体掉落与击杀者之间的距离 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .distanceToKiller(Interval.min(25))/*IntervalJS (见文末)*/ .thenAdd("minecraft:diamond"); }); ``` **hasAnyStage(...字符串 Gamestage)** 可以检测玩家是否具有指定Gamestage,当具有\_任意一个\_时执行修改 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:pig") .hasAnyStage("stoneage") .thenAdd("minecraft:coal");// 玩家具有stoneage时掉落一个煤炭 }); ``` **playerPredicate(callback),entityPredicate(callback),killerPredicate(callback),directKillerPredicate(callback)** 可以自定义回调函数来匹配玩家,实体,击杀者,直接击杀者,当条件符合时执行修改。*注意:该回调必须返回true或false* ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:pig") .playerPredicate((player) => player.stages.has("stoneage")) .thenAdd("minecraft:emerald"); }); onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:pig") .entityPredicate((entity) => entity.type == "minecraft:pig") .thenAdd("minecraft:diamond"); }); onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:pig") .killerPredicate((entity) => entity.type == "minecraft:pig") .thenAdd("minecraft:feather"); }); onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:pig") .directKillerPredicate((entity) => entity.type == "minecraft:pig") .thenAdd("minecraft:stone"); }); ``` **not(callback)** 可以反转战利品表条件 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .not((n) => { n.biome("minecraft:jungle") }) .thenAdd("minecraft:diamond");// 当群系不是丛林时掉落钻石 }); ``` **or(callback)** 可以同时添加多个条件,当\_一个条件为真\_时即通过 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .or((or) => { or.biome("minecraft:jungle").anyDimension("minecraft:nether"); }) .thenAdd("minecraft:diamond");// 群系为丛林**或**维度为下界时掉落钻石 }); ``` **and(callback)** 可以同时添加多个条件,当\_所有条件为真\_时即通过 ``` onEvent("lootjs", (event) => { event .addEntityLootModifier("minecraft:creeper") .and((and) => { and.biome("minecraft:jungle").distanceToKiller(Interval.min(25)); }) .thenAdd("minecraft:diamond");// 群系为丛林**且**击杀者距离大于25时掉落钻石 }); ``` ### 4、IntervalJS,EntityPredicateBuilderJS,DamageSourcePredicateBuilderJS和LootContextJS **IntervalJS**可以用来表示两个数字之间的范围。注意:以下所有范围均为**开区间** | 格式 | 描述 | 返回值 | | -------------------------- | ------------------------- | ---------- | | Interval.between(min, max) | 返回最小最大值之间的范围 `(min, max)` | IntervalJS | | Interval.min(min) | 返回只限制了最小值的范围 `(min,+∞)` | IntervalJS | | Interval.max(max) | 返回只限制了最大值的范围 `(-∞,max)` | IntervalJS | | **函数** | **描述** | **返回值** | | matches(值) | 检测值是否在范围内 | 布尔值 | | matchesSqr(值) | 检测值是否在范围内 | 布尔值 | **EntityPredicateBuilderJS** 可以用条件限制实体以判断是否执行修改,以下返回值均为EntityPredicateBuilderJS | 函数 | 描述 | | ------------------------------------------------ | ------------------------------ | | anyType(...类型) | 判断实体是否有给定标签(输入示例:`#skeletons`) | | isOnFire(布尔值 条件真假) | 判断实体是否着火 | | isCrouching(布尔值 条件真假) | 判断实体是否正在潜行 | | isSprinting(布尔值 条件真假) | 判断实体是否正在疾跑 | | isSwimming(布尔值 条件真假) | 判断实体是否正在游泳 | | isBaby(布尔值 条件真假) | 判断实体是否为幼年状态 | | isInWater(布尔值 条件真假) | 判断实体是否在水里 | | isMonster(布尔值 条件真假) | 判断实体是否为敌对生物 | | isCreature(布尔值 条件真假) | 判断实体是否为友好生物 | | isOnGround(布尔值 条件真假) | 判断实体是否在地上 | | isUndeadMob(布尔值 条件真假) | 判断实体是否为亡灵生物 | | isArthropodMob(布尔值 条件真假) | 判断实体是否为节肢动物 | | isWaterMob(布尔值 条件真假) | 判断实体是否为水生动物 | | isIllegarMob(布尔值 条件真假) | 判断实体是否为掠夺生物 | | hasEffect(字符串 效果ID, 整形 等级) 和 hasEffect(字符串 效果ID) | 判断实体是否具有给定效果 | | nbt(json) | 判断实体是否具有指定nbt | | matchMount(回调) | 判断实体所骑乘的实体\[1] | | matchTargetedEntity(回调) | 判断实体的目标实体\[1] | | matchSlot(整形 栏位编号, IngredientJS 物品) | 判断实体给定物品栏栏位的物品 | \[1]:提供EntityPredicateBuilderJS 例子: ``` onEvent("lootjs", (event) => { event .addLootTypeModifier([LootType.ENTITY]) .matchEntity((entity) => { entity.anyType("#skeletons"); entity.matchMount((mount) => { mount.anyType("minecraft:spider"); }); }) .thenAdd("minecraft:magma_cream");// 当骷髅类实体骑乘蜘蛛时掉落一个演讲稿 }); ``` **DamageSourcePredicateBuilderJS** 用条件来限制伤害类型以判断是否执行修改 | 函数 | 描述 | | ----------------------------------- | ------------ | | anyType(...类型) | 判断伤害是否符合类型 | | isProjectile(布尔值 条件真假) | 判断伤害是否为投掷物 | | isExplosion(布尔值 条件真假) | 判断伤害是否为爆炸 | | doesBypassArmor(布尔值 条件真假) | 判断伤害是否无视盔甲 | | doesBypassInvulnerability(布尔值 条件真假) | 判断伤害是否无视隐身效果 | | doesBypassMagic(布尔值 条件真假) | 判断伤害是否为饥饿引起 | | isFire(布尔值 条件真假) | 判断伤害是否为火 | | isMagic(布尔值 条件真假) | 判断伤害是否为饥饿 | | isLightning(布尔值 条件真假) | 判断伤害是否为闪电 | | matchDirectEntity(回调) | 判断直接造成伤害的实体 | | matchSourceEntity(回调) | 判断造成伤害的实体源 | \[2]:提供DamageSourcePredicateBuilderJS | 函数 | 描述 | 返回值 | | ---------------------------- | ---------------------- | ---------------- | | getType() | 返回战利品类型 | LootTypes\[5] | | getPosition() | 返回掉落位置 | Vector3d | | getEntity() | 返回具有当前战利品表的实体(可能为null) | EntityJS | | getKillerEntity() | 返回杀死对象的实体(可能为null) | EntityJS | | getPlayer() | 返回杀死对象的玩家(可能为null\[6]) | PlayerJS | | getDamageSource() | 返回伤害类型(可能为null\[6]) | DamageSourceJS | | getTool() | 返回触发战利品表使用的工具(可能为null) | ItemStackJS | | getDestroyedBlock() | 返回被破坏的方块(可能为null) | BlockContainerJS | | isExploded() | 返回战利品表是否被爆炸触发 | 布尔值 | | getExplosionRadius() | 返回触发的爆炸的半径\[7] | 整形 | | getLevel() | 返回WorldJS | WorldJS | | getServer() | 返回ServerJS | ServerJS | | getLuck() | 返回玩家幸运值 | 整形 | | getLooting() | 返回LootingModifier | 整形 | | getRandom() | 返回范围(?) | Random | | addLoot(ItemStackJS 物品) | 添加掉落物 | void | | removeLoot(IngredientJS 物品) | 移除掉落物 | void | | findLoot(IngredientJS 物品) | 返回掉落物列表 | List | | hasLoot(IngredientJS 物品) | 返回是否掉落指定物品 | 布尔值 | | forEachLoot(Consumer action) | 遍历每个物品并给予回调\[8] | void | \[5]:见 本章 常用函数 部分 \[6]:为null的例子:骷髅射杀苦力怕 \[7]:如果不是则为0 \[8]:例如 ``` const callback = (item) => { console.log(item); }; ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/addons/lootjs-examples.md --- # 12.8 LootJS战利品表修改实例 1、雨天实体掉落物翻倍 ``` onEvent("lootjs", (event) => { event .addLootTypeModifier(LootType.ENTITY) // 修改实体战利品表 .weatherCheck({ raining: true,// 检测当前天气为雨天 }) .thenModify(Ingredient.getAll(), (itemStack) => { // 掉落物数量翻倍 return itemStack.withCount(itemStack.getCount() * 2); }); }); ``` 2、检测玩家手持物品,当手持下界合金斧破坏带#forge:ores标签的方块时时额外掉落砂砾 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("#forge:ores") // 使用方块tag匹配战利品表 .matchEquip(EquipmentSlot.MAINHAND, Item.of("minecraft:netherite_pickaxe").ignoreNBT()) .thenAdd("minecraft:gravel"); }); ``` 3、修改宝箱战利品表,概率掉落下界合金剑 ``` onEvent("lootjs", (event) => { event .addLootTypeModifier(LootType.CHEST) .randomChance(0.05) .thenAdd("minecraft:netherite_sword"); }); ``` 4、修改宝箱战利品表,将所有钻石替换为下界合金 ``` onEvent("lootjs", (event) => { event .addLootTypeModifier(LootType.CHEST) .thenReplace("minecraft:diamond","minecraft:netherite"); }); ``` 5、修改铁傀儡战利品表,使其只在村庄中被杀死时掉落铁锭 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .not((callback)=>{ callback.anyStructure(["minecraft:village"], false) }) .thenRemove("minecraft:iron_ingot");// 反转条件修改掉落物 }); ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/addons/lootjs-examples.md --- # 12.8 LootJS战利品表修改实例 1、雨天实体掉落物翻倍 ``` onEvent("lootjs", (event) => { event .addLootTypeModifier(LootType.ENTITY) // 修改实体战利品表 .weatherCheck({ raining: true,// 检测当前天气为雨天 }) .thenModify(Ingredient.getAll(), (itemStack) => { // 掉落物数量翻倍 return itemStack.withCount(itemStack.getCount() * 2); }); }); ``` 2、检测玩家手持物品,当手持下界合金斧破坏带#forge:ores标签的方块时时额外掉落砂砾 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("#forge:ores") // 使用方块tag匹配战利品表 .matchEquip(EquipmentSlot.MAINHAND, Item.of("minecraft:netherite_pickaxe").ignoreNBT()) .thenAdd("minecraft:gravel"); }); ``` 3、修改宝箱战利品表,概率掉落下界合金剑 ``` onEvent("lootjs", (event) => { event .addLootTypeModifier(LootType.CHEST) .randomChance(0.05) .thenAdd("minecraft:netherite_sword"); }); ``` 4、修改宝箱战利品表,将所有钻石替换为下界合金 ``` onEvent("lootjs", (event) => { event .addLootTypeModifier(LootType.CHEST) .thenReplace("minecraft:diamond","minecraft:netherite"); }); ``` 5、修改铁傀儡战利品表,使其只在村庄中被杀死时掉落铁锭 ``` onEvent("lootjs", (event) => { event .addBlockLootModifier("minecraft:gravel") .not((callback)=>{ callback.anyStructure(["minecraft:village"], false) }) .thenRemove("minecraft:iron_ingot");// 反转条件修改掉落物 }); ``` --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_13_BlockRenderType.md description: 13 方块渲染类型 相关教程 --- # 参考了该文章 https://boson.v2mcdev.com/block/rendertype.html ## 方块渲染类型 渲染类型(RenderType) ![alt text](image.png) 图片出自TheGreyGhost博客 * Translucent(半透明): * 这种类型的方块可以渲染透明或半透明的纹理,如玻璃或水。 * 渲染半透明方块比渲染不透明方块更耗费资源,因为它们需要根据深度和透明度进行混合。 * Translucent方块的渲染通常在所有其他类型的方块之后进行,以确保正确的混合效果。 * Solid(实体): * 实体方块是Minecraft中最常见的类型,如石头、土块等。 * 实体方块的纹理不透明,因此它们的渲染相对简单且高效。 * Solid方块的渲染通常首先进行,因为它们不会受到透明度的影响。 * Cutout(裁剪): * 裁剪方块通常用于具有清晰边缘的方块,如草或树叶。 * Cutout渲染类型通过不渲染方块内部的像素来提高性能,只渲染方块的轮廓。 * 这使得方块看起来是透明的,但实际上并不进行透明度混合,因此比半透明渲染更快。 * Cutout Mipped(裁剪+Mipmap): * Cutout Mipped是Cutout的一个变种,它结合了裁剪渲染和Mipmap技术。 * Mipmap是一种纹理过滤技术,它可以提高远处纹理的视觉质量,减少闪烁和模糊。 * Cutout Mipped适用于远处看起来也应该清晰的方块,如树叶或栅栏。 Mipmapping技术 Mipmapping是一种纹理映射技术,用于提高远距离纹理的视觉效果。它通过预先计算并存储纹理的不同分辨率版本(称为Mipmaps)来工作。当物体远离摄像机时,渲染引擎会自动选择更低的分辨率Mipmap,从而减少纹理模糊和闪烁,提高渲染效率。 Mipmapping的关键优势是提高了渲染性能和视觉质量。然而,它也需要额外的内存来存储不同分辨率的纹理,并且对于具有清晰边缘或细节的纹理,Mipmaps可能会导致细节丢失。因此,Minecraft在默认情况下只为某些方块类型(如Cutout Mipped)启用Mipmapping,以平衡性能和视觉效果。 ## 创建一个半透明的玻璃罐 添加一个类 ```java public class GlassJar extends Block { public GlassJar(){ super(Properties.ofFullCopy(Blocks.GLASS).strength(5f).noOcclusion()); } } ``` 注册方块和物品 ```java public static final Supplier GLASS_JAR = registerBlock("glass_jar", GlassJar::new); ``` 模型和材质 这里的模型和材质使用了blockbench画出来的,自己也可以去画一画。关于blockbench的使用可以去B战找视频看。或者看我之前的介绍的视频 我们需要给模型的文件添加上一个指定的渲染的类型,我们要渲染的是一个半透明的玻璃的材质,我们使用的渲染的类型是:translucent ```diff "credit": "Made with Blockbench", "texture_size": [32, 32], + "render_type": "translucent", "textures": { "0": "examplemod:block/glass_jar", "particle": "examplemod:block/glass_jar" }, ...<省略> ``` blockstate和物品model生成 ```java @Override protected void registerStatesAndModels() { this.simpleBlockWithItem(ModBlocks.RUBY_BLOCK.get(),cubeAll(ModBlocks.RUBY_BLOCK.get())); this.propertyBlock(ModBlocks.LAMP_BLOCK.get()); this.addWithHaveModel(ModBlocks.RUBY_FRAME.get(),"ruby_frame"); this.glassModel(ModBlocks.GLASS_JAR.get(),"glass_jar"); } public void glassModel(Block block,String name){ var model = models().getExistingFile(new ResourceLocation(ExampleMod.MODID,name)); getVariantBuilder(block).partialState().setModels(new ConfiguredModel(model)); simpleBlockItem(block,model); } ``` 添加到创造模式物品栏,略和之前一样操作。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_13_BlockRenderType.md description: 13 方块渲染类型 相关教程 --- # 参考了该文章 https://boson.v2mcdev.com/block/rendertype.html ## 方块渲染类型 渲染类型(RenderType) ![alt text](image.png) 图片出自TheGreyGhost博客 * Translucent(半透明): * 这种类型的方块可以渲染透明或半透明的纹理,如玻璃或水。 * 渲染半透明方块比渲染不透明方块更耗费资源,因为它们需要根据深度和透明度进行混合。 * Translucent方块的渲染通常在所有其他类型的方块之后进行,以确保正确的混合效果。 * Solid(实体): * 实体方块是Minecraft中最常见的类型,如石头、土块等。 * 实体方块的纹理不透明,因此它们的渲染相对简单且高效。 * Solid方块的渲染通常首先进行,因为它们不会受到透明度的影响。 * Cutout(裁剪): * 裁剪方块通常用于具有清晰边缘的方块,如草或树叶。 * Cutout渲染类型通过不渲染方块内部的像素来提高性能,只渲染方块的轮廓。 * 这使得方块看起来是透明的,但实际上并不进行透明度混合,因此比半透明渲染更快。 * Cutout Mipped(裁剪+Mipmap): * Cutout Mipped是Cutout的一个变种,它结合了裁剪渲染和Mipmap技术。 * Mipmap是一种纹理过滤技术,它可以提高远处纹理的视觉质量,减少闪烁和模糊。 * Cutout Mipped适用于远处看起来也应该清晰的方块,如树叶或栅栏。 Mipmapping技术 Mipmapping是一种纹理映射技术,用于提高远距离纹理的视觉效果。它通过预先计算并存储纹理的不同分辨率版本(称为Mipmaps)来工作。当物体远离摄像机时,渲染引擎会自动选择更低的分辨率Mipmap,从而减少纹理模糊和闪烁,提高渲染效率。 Mipmapping的关键优势是提高了渲染性能和视觉质量。然而,它也需要额外的内存来存储不同分辨率的纹理,并且对于具有清晰边缘或细节的纹理,Mipmaps可能会导致细节丢失。因此,Minecraft在默认情况下只为某些方块类型(如Cutout Mipped)启用Mipmapping,以平衡性能和视觉效果。 ## 创建一个半透明的玻璃罐 添加一个类 ```java public class GlassJar extends Block { public GlassJar(){ super(Properties.ofFullCopy(Blocks.GLASS).strength(5f).noOcclusion()); } } ``` 注册方块和物品 ```java public static final Supplier GLASS_JAR = registerBlock("glass_jar", GlassJar::new); ``` 模型和材质 这里的模型和材质使用了blockbench画出来的,自己也可以去画一画。关于blockbench的使用可以去B战找视频看。或者看我之前的介绍的视频 我们需要给模型的文件添加上一个指定的渲染的类型,我们要渲染的是一个半透明的玻璃的材质,我们使用的渲染的类型是:translucent ```diff "credit": "Made with Blockbench", "texture_size": [32, 32], + "render_type": "translucent", "textures": { "0": "examplemod:block/glass_jar", "particle": "examplemod:block/glass_jar" }, ...<省略> ``` blockstate和物品model生成 ```java @Override protected void registerStatesAndModels() { this.simpleBlockWithItem(ModBlocks.RUBY_BLOCK.get(),cubeAll(ModBlocks.RUBY_BLOCK.get())); this.propertyBlock(ModBlocks.LAMP_BLOCK.get()); this.addWithHaveModel(ModBlocks.RUBY_FRAME.get(),"ruby_frame"); this.glassModel(ModBlocks.GLASS_JAR.get(),"glass_jar"); } public void glassModel(Block block,String name){ var model = models().getExistingFile(new ResourceLocation(ExampleMod.MODID,name)); getVariantBuilder(block).partialState().setModels(new ConfiguredModel(model)); simpleBlockItem(block,model); } ``` 添加到创造模式物品栏,略和之前一样操作。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/feature2/add-items.md --- # 13 自定义物品和物品ToolTip *** 要使用KubeJS来添加物品,你需要使用事件`item.registry`。该事件属于StartUp事件。 ## 一、事件监听 你可以使用`event.create("自定义物品ID")`语句来创建物品。 ``` onEvent("item.registry", event => { event.create("自定义物品ID") }) ``` 物品的材质应被放于`kubejs/assets/kubejs/textures/item/物品ID.png` 如果你想使用自定义的物品模型,你可以使用 Blockbench 制作然后把它放于`kubejs/assets/kubejs/models/item/物品ID.json` 此外,你还可以手动定义材质路径,详见下文。 **注意:1.18.2中事件监听方式有修改,详见1.18.2 方块 / 物品注册修改 章节** ## 二、ItemBuilder 你可以使用以下方法设定物品属性。 | 方法 | 描述 | 属性默认值 | 返回值 | | ----------------------------------------------- | ----------------- | -------------------- | ----------- | | type(ItemType 类型) | 设置物品类型\[1](仅1.16) | basic | ItemBuilder | | tier(字符串 类型) | 设置物品Tier\[2] | - | ItemBuilder | | modifyTier(tier => ...) | 自定义物品tier | - | ItemBuilder | | maxStackSize(整形 数量) | 设置物品单组最大数量 | 64 | ItemBuilder | | maxDamage(整形 耐久值) | 设置物品最大耐久值 | 0 | ItemBuilder | | burnTime(整形 燃烧时间) | (燃料)设置物品燃烧时间 | 0 | ItemBuilder | | containerItem(字符串 物品ID) | 设置物品容器ID\[3] | "minecraft:air" | ItemBuilder | | subtypes(Function\ fn) | 设置物品的SubTypes | null | ItemBuilder | | tool(ToolType 类型, 整形 等级) | 设置物品工具属性 | new HashMap<>() | ItemBuilder | | miningSpeed(浮点型 速度) | (工具)设置挖掘速度 | 1.0F | ItemBuilder | | attackDamage(浮点型 伤害) | (武器)设置攻击伤害 | - | ItemBuilder | | attackSpeed(浮点型 速度) | (武器)设置攻击速度 | - | ItemBuilder | | rarity(RarityWrapper 稀有度) | 设置物品稀有度 | RarityWrapper.COMMON | ItemBuilder | | glow(布尔值 是否发光) | 设置物品是否发光 | false | ItemBuilder | | tooltip(Component 文本) | 设置物品的ToolTip | new ArrayList<>() | ItemBuilder | | group(字符串 物品组) | 设置物品的创造物品栏位\[4] | KubeJS.tab | ItemBuilder | | color(整形 index, 整形 c) | 设置物品颜色 | 0xFFFFFFFF | ItemBuilder | | texture(字符串 材质路径) | 手动设置物品材质 | 空字符串 | ItemBuilder | | parentModel(字符串 模型路径) | 手动设置物品模型 | 空字符串 | ItemBuilder | | food(Consumer b) | (转换为食物)设置食物属性 | null | ItemBuilder | \[1] 可用值有:basic(默认值)、sword、pickaxe、axe、shovel、hoe、helmet、chestplate、leggings、boots。 * **注意:1.18.2中该方法有修改,详见1.18.2 方块 / 物品注册修改 章节** \[2] 工具类的tier类型有:wood、stone、iron、gold、diamond、netherite;护甲类的tier类型有:leather、chainmail、iron、gold、diamond、turtle、netherite \[3] 支持的方法同新建tier \[4] 即类似于岩浆桶和空桶之间的关系。 \[5] 可用值有:search、buildingBlocks、decorations、redstone、transportation、misc、food、tools、combat、brewing **注:以上方法中`attackDamage(浮点型 伤害)`、`attackSpeed(浮点型 速度)`在1.16.5版本中可能无法生效,截至编辑本节时仍未修复。** 以下为示例: ``` onEvent("item.registry", event => { // 基础示例 event.create("can_empty").displayName("空罐头盒").maxStackSize(32); // 注册头盔 event.create("another_helmet").displayName("另一个头盔").type("helmet").tier("netherite"); // 食物注册 event.create("magic_steak").food(food => {// FoodBuilder food .hunger(6)// 饱食度 .saturation(6)// 饱和度 .effect("speed", 600, 0, 1)// 食用后给予的效果,接受值:效果ID, 持续时间, 等级, 概率(0~1) .removeEffect("poison")// 食用后移除的效果 .alwaysEdible()// 无视饱食度依旧可以食用(类似金苹果) .fastToEat()// 缩短食用事件(类似干海带) .meat()// 设置为肉类(狗可食用) .eaten(ctx => {// 当使用时执行...(ItemFoodEatenEventJS) // 该方法可用于部分替代item.food_eaten事件 ctx.player.tell(`${ctx.item.id} 真好吃~`) }) }) }) ``` 另请参阅:[ItemBuilder](https://github.com/KubeJS-Mods/KubeJS/blob/eol/1.16/common/src/main/java/dev/latvian/kubejs/item/ItemBuilder.java)、[FoodBuilder](https://github.com/KubeJS-Mods/KubeJS/blob/eol/1.16/common/src/main/java/dev/latvian/kubejs/item/FoodBuilder.java)、[ItemFoodEatenEventJS](https://github.com/KubeJS-Mods/KubeJS/blob/eol/1.16/common/src/main/java/dev/latvian/kubejs/item/ItemFoodEatenEventJS.java) ## 三、新建Tier 通过新建Tier你可以将一套属性快速应用于多个物品上。 ``` // 注:以下数据均为默认值(对应tier:iron) // 注册工具tier onEvent('item.registry.tool_tiers', event => { event.add('tier_id', tier => { tier.uses = 250 // 耐久 tier.speed = 6.0 // 挖掘速度 tier.attackDamageBonus = 2.0 //伤害加成 tier.level = 2 // 等级 tier.enchantmentValue = 14 // 附魔 tier.repairIngredient = '#forge:ingots/iron'// 铁砧修复用物品 }) }) // 注册护甲tier onEvent('item.registry.armor_tiers', event => { // 栏位顺序对应 [靴子, 护腿, 胸甲, 头盔] event.add('tier_id', tier => { tier.durabilityMultiplier = 15 // 耐久倍率,每个栏位对应[13, 15, 16, 11] tier.slotProtections = [2, 5, 6, 2]// 保护倍率 tier.enchantmentValue = 9 // 附魔 tier.equipSound = 'minecraft:item.armor.equip_iron' // 装备音效 tier.repairIngredient = '#forge:ingots/iron' // 铁砧修复用物品 tier.toughness = 0.0 // 护甲韧性,钻石为2.0,下界合金为3.0 tier.knockbackResistance = 0.0 }) }) ``` 在添加完毕以上tier后,你可以在`type(ItemType 类型)`中传入你自己添加的`tier_id`来应用它。 ## 四、ToolTip 以下为部分示例: ``` onEvent('item.tooltip', tooltip => { // 批量添加tooltip tooltip.add(['minecraft:diorite', 'minecraft:granite', 'minecraft:andesite'], '三 大 废 岩') // 你可以使用除#tag以外的其他任何表述方法 tooltip.add(/refinedstorage:red_/, 'Can be any color') // 你可以使用数组来新建多行tooltip. 你可以使用其他转义符来避开'这个符号 tooltip.add('thermal:latex_bucket', ["Not equivalent to Industrial Foregoing's Latex", 'Line 2']) tooltip.addAdvanced('thermal:latex_bucket', (item, advanced, text) => { text.add(0/*index*/, Text.of('Hello')) // 当index为0时,这种表示方法 会替换掉物品名称. 如果你想要在第二行添加tooltip,index必须为1,以此类推. }) }) ``` 另请参阅:[ItemTooltipEventJS](https://github.com/KubeJS-Mods/KubeJS/blob/eol/1.16/common/src/main/java/dev/latvian/kubejs/item/ItemTooltipEventJS.java) --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/feature2/add-items.md --- # 13 自定义物品和物品ToolTip *** 要使用KubeJS来添加物品,你需要使用事件`item.registry`。该事件属于StartUp事件。 ## 一、事件监听 你可以使用`event.create("自定义物品ID")`语句来创建物品。 ``` onEvent("item.registry", event => { event.create("自定义物品ID") }) ``` 物品的材质应被放于`kubejs/assets/kubejs/textures/item/物品ID.png` 如果你想使用自定义的物品模型,你可以使用 Blockbench 制作然后把它放于`kubejs/assets/kubejs/models/item/物品ID.json` 此外,你还可以手动定义材质路径,详见下文。 **注意:1.18.2中事件监听方式有修改,详见1.18.2 方块 / 物品注册修改 章节** ## 二、ItemBuilder 你可以使用以下方法设定物品属性。 | 方法 | 描述 | 属性默认值 | 返回值 | | ----------------------------------------------- | ----------------- | -------------------- | ----------- | | type(ItemType 类型) | 设置物品类型\[1](仅1.16) | basic | ItemBuilder | | tier(字符串 类型) | 设置物品Tier\[2] | - | ItemBuilder | | modifyTier(tier => ...) | 自定义物品tier | - | ItemBuilder | | maxStackSize(整形 数量) | 设置物品单组最大数量 | 64 | ItemBuilder | | maxDamage(整形 耐久值) | 设置物品最大耐久值 | 0 | ItemBuilder | | burnTime(整形 燃烧时间) | (燃料)设置物品燃烧时间 | 0 | ItemBuilder | | containerItem(字符串 物品ID) | 设置物品容器ID\[3] | "minecraft:air" | ItemBuilder | | subtypes(Function\ fn) | 设置物品的SubTypes | null | ItemBuilder | | tool(ToolType 类型, 整形 等级) | 设置物品工具属性 | new HashMap<>() | ItemBuilder | | miningSpeed(浮点型 速度) | (工具)设置挖掘速度 | 1.0F | ItemBuilder | | attackDamage(浮点型 伤害) | (武器)设置攻击伤害 | - | ItemBuilder | | attackSpeed(浮点型 速度) | (武器)设置攻击速度 | - | ItemBuilder | | rarity(RarityWrapper 稀有度) | 设置物品稀有度 | RarityWrapper.COMMON | ItemBuilder | | glow(布尔值 是否发光) | 设置物品是否发光 | false | ItemBuilder | | tooltip(Component 文本) | 设置物品的ToolTip | new ArrayList<>() | ItemBuilder | | group(字符串 物品组) | 设置物品的创造物品栏位\[4] | KubeJS.tab | ItemBuilder | | color(整形 index, 整形 c) | 设置物品颜色 | 0xFFFFFFFF | ItemBuilder | | texture(字符串 材质路径) | 手动设置物品材质 | 空字符串 | ItemBuilder | | parentModel(字符串 模型路径) | 手动设置物品模型 | 空字符串 | ItemBuilder | | food(Consumer b) | (转换为食物)设置食物属性 | null | ItemBuilder | \[1] 可用值有:basic(默认值)、sword、pickaxe、axe、shovel、hoe、helmet、chestplate、leggings、boots。 * **注意:1.18.2中该方法有修改,详见1.18.2 方块 / 物品注册修改 章节** \[2] 工具类的tier类型有:wood、stone、iron、gold、diamond、netherite;护甲类的tier类型有:leather、chainmail、iron、gold、diamond、turtle、netherite \[3] 支持的方法同新建tier \[4] 即类似于岩浆桶和空桶之间的关系。 \[5] 可用值有:search、buildingBlocks、decorations、redstone、transportation、misc、food、tools、combat、brewing **注:以上方法中`attackDamage(浮点型 伤害)`、`attackSpeed(浮点型 速度)`在1.16.5版本中可能无法生效,截至编辑本节时仍未修复。** 以下为示例: ``` onEvent("item.registry", event => { // 基础示例 event.create("can_empty").displayName("空罐头盒").maxStackSize(32); // 注册头盔 event.create("another_helmet").displayName("另一个头盔").type("helmet").tier("netherite"); // 食物注册 event.create("magic_steak").food(food => {// FoodBuilder food .hunger(6)// 饱食度 .saturation(6)// 饱和度 .effect("speed", 600, 0, 1)// 食用后给予的效果,接受值:效果ID, 持续时间, 等级, 概率(0~1) .removeEffect("poison")// 食用后移除的效果 .alwaysEdible()// 无视饱食度依旧可以食用(类似金苹果) .fastToEat()// 缩短食用事件(类似干海带) .meat()// 设置为肉类(狗可食用) .eaten(ctx => {// 当使用时执行...(ItemFoodEatenEventJS) // 该方法可用于部分替代item.food_eaten事件 ctx.player.tell(`${ctx.item.id} 真好吃~`) }) }) }) ``` 另请参阅:[ItemBuilder](https://github.com/KubeJS-Mods/KubeJS/blob/eol/1.16/common/src/main/java/dev/latvian/kubejs/item/ItemBuilder.java)、[FoodBuilder](https://github.com/KubeJS-Mods/KubeJS/blob/eol/1.16/common/src/main/java/dev/latvian/kubejs/item/FoodBuilder.java)、[ItemFoodEatenEventJS](https://github.com/KubeJS-Mods/KubeJS/blob/eol/1.16/common/src/main/java/dev/latvian/kubejs/item/ItemFoodEatenEventJS.java) ## 三、新建Tier 通过新建Tier你可以将一套属性快速应用于多个物品上。 ``` // 注:以下数据均为默认值(对应tier:iron) // 注册工具tier onEvent('item.registry.tool_tiers', event => { event.add('tier_id', tier => { tier.uses = 250 // 耐久 tier.speed = 6.0 // 挖掘速度 tier.attackDamageBonus = 2.0 //伤害加成 tier.level = 2 // 等级 tier.enchantmentValue = 14 // 附魔 tier.repairIngredient = '#forge:ingots/iron'// 铁砧修复用物品 }) }) // 注册护甲tier onEvent('item.registry.armor_tiers', event => { // 栏位顺序对应 [靴子, 护腿, 胸甲, 头盔] event.add('tier_id', tier => { tier.durabilityMultiplier = 15 // 耐久倍率,每个栏位对应[13, 15, 16, 11] tier.slotProtections = [2, 5, 6, 2]// 保护倍率 tier.enchantmentValue = 9 // 附魔 tier.equipSound = 'minecraft:item.armor.equip_iron' // 装备音效 tier.repairIngredient = '#forge:ingots/iron' // 铁砧修复用物品 tier.toughness = 0.0 // 护甲韧性,钻石为2.0,下界合金为3.0 tier.knockbackResistance = 0.0 }) }) ``` 在添加完毕以上tier后,你可以在`type(ItemType 类型)`中传入你自己添加的`tier_id`来应用它。 ## 四、ToolTip 以下为部分示例: ``` onEvent('item.tooltip', tooltip => { // 批量添加tooltip tooltip.add(['minecraft:diorite', 'minecraft:granite', 'minecraft:andesite'], '三 大 废 岩') // 你可以使用除#tag以外的其他任何表述方法 tooltip.add(/refinedstorage:red_/, 'Can be any color') // 你可以使用数组来新建多行tooltip. 你可以使用其他转义符来避开'这个符号 tooltip.add('thermal:latex_bucket', ["Not equivalent to Industrial Foregoing's Latex", 'Line 2']) tooltip.addAdvanced('thermal:latex_bucket', (item, advanced, text) => { text.add(0/*index*/, Text.of('Hello')) // 当index为0时,这种表示方法 会替换掉物品名称. 如果你想要在第二行添加tooltip,index必须为1,以此类推. }) }) ``` 另请参阅:[ItemTooltipEventJS](https://github.com/KubeJS-Mods/KubeJS/blob/eol/1.16/common/src/main/java/dev/latvian/kubejs/item/ItemTooltipEventJS.java) --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/feature2/modify-items-and-blocks.md --- # 14 方块和物品属性修改 *** KubeJs允许你修改一些已经存在的物品/方块的属性 ## 1、物品属性修改 ``` onEvent('item.modification', event => { event.modify('minecraft:ender_pearl', item => { item.maxStackSize = 64 item.fireResistant = true }) }) ``` 该段示例脚本将末影珍珠每组最大数量调整为了64,并且不会被烧毁 以下是可以使用的方法(a = b): * maxStackSize // 每组数量 * maxDamage // 最大耐久值 * burnTime // 燃烧时间(燃料) * craftingReminder // 合成后剩余物品(字符串) * fireResistant // 是否抗火 * rarity // 稀有度(如Rarity.COMMON) * tier = tierOptions => { // 物品等级 * uses // 耐久 * speed // 挖掘速度 * attackDamageBonus // 攻击伤害 * level // 工具等级 * enchantmentValue // 附魔值 * repairIngredient // 铁砧修复需要使用的物品,格式如Ingredient.of('物品注册名') * } * foodProperties = food => { // 食物设置。注: 使用函数而不是 a = b * hunger(int) // 饱食度 * saturation(float) // 饱和度 * meat(boolean) // 是否为肉食 * alwaysEdible(boolean) // 是否总是可食用(无论饱食度) * fastToEat(boolean) // 是否能被快速吃掉 * effect(效果ID(字符串), 时间(整形), 效果等级(整形), 获得倍率(浮点型)) // 吃掉后给予的效果 * removeEffect(String effectId) // 吃掉后移除的效果 * } ## 2、方块属性修改 ``` onEvent('block.modification', event => { event.modify('minecraft:stone', block => { block.destroySpeed = 0.1 block.hasCollision = false }) }) ``` 该段示例脚本将石头的破坏速度调整为0.1, 并且使其无碰撞箱 以下是可使用的方法(a = b): * material // 材质 * boolean hasCollision // 是否有碰撞箱(布尔型) * destroySpeed // 破坏速度(浮点型) * explosionResistance // 爆炸抗性(浮点型) * randomlyTicking // 是否接受随机刻控制 * soundType // 声音类型 * friction // 摩擦(浮点型) * speedFactor // 速度倍率 * jumpFactor // 跳跃倍率 * lightEmission // 发光强度 * requiredTool // 是否需要工具掉落 --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/feature2/modify-items-and-blocks.md --- # 14 方块和物品属性修改 *** KubeJs允许你修改一些已经存在的物品/方块的属性 ## 1、物品属性修改 ``` onEvent('item.modification', event => { event.modify('minecraft:ender_pearl', item => { item.maxStackSize = 64 item.fireResistant = true }) }) ``` 该段示例脚本将末影珍珠每组最大数量调整为了64,并且不会被烧毁 以下是可以使用的方法(a = b): * maxStackSize // 每组数量 * maxDamage // 最大耐久值 * burnTime // 燃烧时间(燃料) * craftingReminder // 合成后剩余物品(字符串) * fireResistant // 是否抗火 * rarity // 稀有度(如Rarity.COMMON) * tier = tierOptions => { // 物品等级 * uses // 耐久 * speed // 挖掘速度 * attackDamageBonus // 攻击伤害 * level // 工具等级 * enchantmentValue // 附魔值 * repairIngredient // 铁砧修复需要使用的物品,格式如Ingredient.of('物品注册名') * } * foodProperties = food => { // 食物设置。注: 使用函数而不是 a = b * hunger(int) // 饱食度 * saturation(float) // 饱和度 * meat(boolean) // 是否为肉食 * alwaysEdible(boolean) // 是否总是可食用(无论饱食度) * fastToEat(boolean) // 是否能被快速吃掉 * effect(效果ID(字符串), 时间(整形), 效果等级(整形), 获得倍率(浮点型)) // 吃掉后给予的效果 * removeEffect(String effectId) // 吃掉后移除的效果 * } ## 2、方块属性修改 ``` onEvent('block.modification', event => { event.modify('minecraft:stone', block => { block.destroySpeed = 0.1 block.hasCollision = false }) }) ``` 该段示例脚本将石头的破坏速度调整为0.1, 并且使其无碰撞箱 以下是可使用的方法(a = b): * material // 材质 * boolean hasCollision // 是否有碰撞箱(布尔型) * destroySpeed // 破坏速度(浮点型) * explosionResistance // 爆炸抗性(浮点型) * randomlyTicking // 是否接受随机刻控制 * soundType // 声音类型 * friction // 摩擦(浮点型) * speedFactor // 速度倍率 * jumpFactor // 跳跃倍率 * lightEmission // 发光强度 * requiredTool // 是否需要工具掉落 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_14_CustomItemModel.md description: 14 自定义物品模型 相关教程 --- # 自定义物品模型 添加一个物品类 ```java public class RubyWand extends Item { public RubyWand(){ super(new Properties().stacksTo(1)); } } ``` 注册物品 ```java public static final Supplier RUBY_WAND = ITEMS.register("ruby_wand", RubyWand::new); ``` 添加模型和材质 放到对应的路径下就可以了,和之前一样的。 ``` │ ├─examplemod │ │ ├─blockstates │ │ ├─models │ │ │ ├─block │ │ │ └─item │ │ └─textures │ │ ├─block │ │ └─item ``` 这里的模型和材质是使用blockbench画的,这里做一下简单的介绍,详细的介绍可以在B战搜索教学视频,或者看我之前的视频。 添加到创造模式物品栏 略 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_14_CustomItemModel.md description: 14 自定义物品模型 相关教程 --- # 自定义物品模型 添加一个物品类 ```java public class RubyWand extends Item { public RubyWand(){ super(new Properties().stacksTo(1)); } } ``` 注册物品 ```java public static final Supplier RUBY_WAND = ITEMS.register("ruby_wand", RubyWand::new); ``` 添加模型和材质 放到对应的路径下就可以了,和之前一样的。 ``` │ ├─examplemod │ │ ├─blockstates │ │ ├─models │ │ │ ├─block │ │ │ └─item │ │ └─textures │ │ ├─block │ │ └─item ``` 这里的模型和材质是使用blockbench画的,这里做一下简单的介绍,详细的介绍可以在B战搜索教学视频,或者看我之前的视频。 添加到创造模式物品栏 略 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_15_CraftTable.md description: 15 合成表 相关教程 --- # 合成表配方 你可以在wiki中查找有关数据包制作的内容,也就是json的内容。我们这里简单的介绍几种,你可以查找原版的json学习和通过wiki学习。 创建如下的目录结构,数据包的内容在data文件夹下。而不是assets下。 ```java resources ├─assets ├─data │ └─examplemod │ ├─advancements │ │ └─recipes │ │ └─misc │ └─recipes ``` 其中的data/examplemod就是你自己的数据包了。 recipes(配方): 这个目录用于存放所有的合成、烧炼、酿造等配方。 advancements(进度): 这个目录用于存放自定义的进度,进度可以看作是游戏内的成就或者任务系统。 ```json { "type": "minecraft:crafting_shaped", "category": "misc", "key": { "a": { "item": "examplemod:ruby" }, "b": { "item": "minecraft:stick" } }, "pattern": [ "aaa", " b ", " b " ], "result": { "item": "examplemod:ruby_pickaxe" } } ``` type: "minecraft:crafting\_shaped" 表示这是一个有序合成配方。 category: "misc" 指定了配方类别,通常设置为 "misc"。 key: 包含了合成配方中使用的符号和对应的物品。在这个例子中,"a" 代表了 examplemod:ruby,"b" 代表了 minecraft:stick。 pattern: 表示3x3合成格中的物品布局。"aaa" 表示第一行三个位置都是 examplemod:ruby," b " 表示第二行中间是 minecraft:stick,两边是空格。 result: 指定了合成后得到的物品。这里合成的结果是 examplemod:ruby\_pickaxe。 [wiki](https://zh.minecraft.wiki/w/%E9%85%8D%E6%96%B9) 无序合成 ```json { "type": "minecraft:crafting_shapeless", "category": "misc", "ingredients": [ { "item": "minecraft:iron_ingot" }, { "item": "minecraft:iron_ingot" }, { "item": "minecraft:iron_ingot" } ], "result": { "item": "examplemod:magic_ingot" } } ``` ingredients: 列出了合成配方所需的物品。在这个例子中,需要三个 minecraft:iron\_ingot。 熔炉和其他 以熔炉为例,其他详见wiki ```json { "type": "minecraft:smelting", "category": "misc", "cookingtime": 100, "experience": 0.3, "ingredient": { "item": "examplemod:ruby_ore" }, "result": "examplemod:ruby" } ``` type: "minecraft:smelting" 表示这是一个熔炉配方。 category: "misc" 同上。 cookingtime: 烧炼所需的时间,以刻为单位。100刻等于5秒。 experience: 烧炼此物品后玩家获得的经验值。 ingredient: 需要烧炼的物品。这里需要烧炼的是 examplemod:ruby\_ore。 result: 烧炼后得到的物品。这里烧炼的结果是 examplemod:ruby。 ## datagenerator 生成合成表 你可以自己手写合成表,但是难免会出现错误,我们还可以使用之前讲解的数据生成的内容。 ```java public class ModRecipeProvider extends RecipeProvider { //https://docs.neoforged.net/docs/datagen/server/recipes public ModRecipeProvider(PackOutput pPackOutput, CompletableFuture lookupProvider) { super(pPackOutput, lookupProvider); } @Override protected void buildRecipes(RecipeOutput pRecipeOutput) { // 有序合成 ShapedRecipeBuilder.shaped(RecipeCategory.MISC, ModItems.RUBY_PICKAXE.get()) .pattern("aaa") .pattern(" b ") .pattern(" b ") .define('a',ModItems.RUBY.get()) .define('b', Items.STICK) .unlockedBy("has_ruby",has(ModItems.RUBY.get())) .save(pRecipeOutput); // 无序合成 ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC,ModItems.MAGIC_INGOT.get()) .requires(Items.IRON_INGOT,3) .unlockedBy("has_iron_ingot",has(Items.IRON_INGOT)) .save(pRecipeOutput); // 冶炼 SimpleCookingRecipeBuilder.smelting(Ingredient.of(ModBlocks.RUBY_ORE.get()),RecipeCategory.MISC,ModItems.RUBY.get(),0.3f,100) .unlockedBy("has_ruby_ore",has(ModBlocks.RUBY_ORE.get())) .save(pRecipeOutput); // 切割石头 // SingleItemRecipeBuilder.stonecutting() } } ``` buildRecipes这个方法在数据生成过程中被调用,用于构建和保存配方。RecipeOutput参数用于输出生成的配方数据。 ShapedRecipeBuilder.shaped创建一个有序合成配方 unlockedBy("has\_ruby",has(ModItems.RUBY.get()))配方有一个解锁条件,要求玩家拥有ModItems.RUBY。《在你获得ruby时候右上角提示》 ShapelessRecipeBuilder。shapeless创建一个无序合成配方,用于制作MAGIC\_INGOT。配方需要3个IRON\_INGOT。配方有一个解锁条件,要求玩家拥有IRON\_INGOT。 SimpleCookingRecipeBuilder.smelting创建一个冶炼配方,用于将RUBY\_ORE冶炼成RUBY。冶炼时间为100刻,提供0.3的经验值。配方有一个解锁条件,要求玩家拥有RUBY\_ORE。 gleItemRecipeBuilder.stonecutting()它将使用SingleItemRecipeBuilder来创建一个切割配方。 添加到GatherDataEvent事件中去: ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public class ModDataGeneratorHandler { @SubscribeEvent public static void gatherData(GatherDataEvent event){ ExistingFileHelper efh = event.getExistingFileHelper(); var lp = event.getLookupProvider(); // recipe event.getGenerator().addProvider( event.includeServer(), (DataProvider.Factory) pOutput -> new ModRecipeProvider(pOutput,lp) ); } } ``` rundata即可生成合成表。其他的合成表可以参考原版的实现和原版的类。 ## 拓展阅读 [wiki](https://zh.minecraft.wiki/w/%E9%85%8D%E6%96%B9) [boson教程](https://boson.v2mcdev.com/datapack/recipes.html) [Neoforge document](https://docs.neoforged.net/docs/datagen/server/recipes) --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_15_CraftTable.md description: 15 合成表 相关教程 --- # 合成表配方 你可以在wiki中查找有关数据包制作的内容,也就是json的内容。我们这里简单的介绍几种,你可以查找原版的json学习和通过wiki学习。 创建如下的目录结构,数据包的内容在data文件夹下。而不是assets下。 ```java resources ├─assets ├─data │ └─examplemod │ ├─advancements │ │ └─recipes │ │ └─misc │ └─recipes ``` 其中的data/examplemod就是你自己的数据包了。 recipes(配方): 这个目录用于存放所有的合成、烧炼、酿造等配方。 advancements(进度): 这个目录用于存放自定义的进度,进度可以看作是游戏内的成就或者任务系统。 ```json { "type": "minecraft:crafting_shaped", "category": "misc", "key": { "a": { "item": "examplemod:ruby" }, "b": { "item": "minecraft:stick" } }, "pattern": [ "aaa", " b ", " b " ], "result": { "item": "examplemod:ruby_pickaxe" } } ``` type: "minecraft:crafting\_shaped" 表示这是一个有序合成配方。 category: "misc" 指定了配方类别,通常设置为 "misc"。 key: 包含了合成配方中使用的符号和对应的物品。在这个例子中,"a" 代表了 examplemod:ruby,"b" 代表了 minecraft:stick。 pattern: 表示3x3合成格中的物品布局。"aaa" 表示第一行三个位置都是 examplemod:ruby," b " 表示第二行中间是 minecraft:stick,两边是空格。 result: 指定了合成后得到的物品。这里合成的结果是 examplemod:ruby\_pickaxe。 [wiki](https://zh.minecraft.wiki/w/%E9%85%8D%E6%96%B9) 无序合成 ```json { "type": "minecraft:crafting_shapeless", "category": "misc", "ingredients": [ { "item": "minecraft:iron_ingot" }, { "item": "minecraft:iron_ingot" }, { "item": "minecraft:iron_ingot" } ], "result": { "item": "examplemod:magic_ingot" } } ``` ingredients: 列出了合成配方所需的物品。在这个例子中,需要三个 minecraft:iron\_ingot。 熔炉和其他 以熔炉为例,其他详见wiki ```json { "type": "minecraft:smelting", "category": "misc", "cookingtime": 100, "experience": 0.3, "ingredient": { "item": "examplemod:ruby_ore" }, "result": "examplemod:ruby" } ``` type: "minecraft:smelting" 表示这是一个熔炉配方。 category: "misc" 同上。 cookingtime: 烧炼所需的时间,以刻为单位。100刻等于5秒。 experience: 烧炼此物品后玩家获得的经验值。 ingredient: 需要烧炼的物品。这里需要烧炼的是 examplemod:ruby\_ore。 result: 烧炼后得到的物品。这里烧炼的结果是 examplemod:ruby。 ## datagenerator 生成合成表 你可以自己手写合成表,但是难免会出现错误,我们还可以使用之前讲解的数据生成的内容。 ```java public class ModRecipeProvider extends RecipeProvider { //https://docs.neoforged.net/docs/datagen/server/recipes public ModRecipeProvider(PackOutput pPackOutput, CompletableFuture lookupProvider) { super(pPackOutput, lookupProvider); } @Override protected void buildRecipes(RecipeOutput pRecipeOutput) { // 有序合成 ShapedRecipeBuilder.shaped(RecipeCategory.MISC, ModItems.RUBY_PICKAXE.get()) .pattern("aaa") .pattern(" b ") .pattern(" b ") .define('a',ModItems.RUBY.get()) .define('b', Items.STICK) .unlockedBy("has_ruby",has(ModItems.RUBY.get())) .save(pRecipeOutput); // 无序合成 ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC,ModItems.MAGIC_INGOT.get()) .requires(Items.IRON_INGOT,3) .unlockedBy("has_iron_ingot",has(Items.IRON_INGOT)) .save(pRecipeOutput); // 冶炼 SimpleCookingRecipeBuilder.smelting(Ingredient.of(ModBlocks.RUBY_ORE.get()),RecipeCategory.MISC,ModItems.RUBY.get(),0.3f,100) .unlockedBy("has_ruby_ore",has(ModBlocks.RUBY_ORE.get())) .save(pRecipeOutput); // 切割石头 // SingleItemRecipeBuilder.stonecutting() } } ``` buildRecipes这个方法在数据生成过程中被调用,用于构建和保存配方。RecipeOutput参数用于输出生成的配方数据。 ShapedRecipeBuilder.shaped创建一个有序合成配方 unlockedBy("has\_ruby",has(ModItems.RUBY.get()))配方有一个解锁条件,要求玩家拥有ModItems.RUBY。《在你获得ruby时候右上角提示》 ShapelessRecipeBuilder。shapeless创建一个无序合成配方,用于制作MAGIC\_INGOT。配方需要3个IRON\_INGOT。配方有一个解锁条件,要求玩家拥有IRON\_INGOT。 SimpleCookingRecipeBuilder.smelting创建一个冶炼配方,用于将RUBY\_ORE冶炼成RUBY。冶炼时间为100刻,提供0.3的经验值。配方有一个解锁条件,要求玩家拥有RUBY\_ORE。 gleItemRecipeBuilder.stonecutting()它将使用SingleItemRecipeBuilder来创建一个切割配方。 添加到GatherDataEvent事件中去: ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public class ModDataGeneratorHandler { @SubscribeEvent public static void gatherData(GatherDataEvent event){ ExistingFileHelper efh = event.getExistingFileHelper(); var lp = event.getLookupProvider(); // recipe event.getGenerator().addProvider( event.includeServer(), (DataProvider.Factory) pOutput -> new ModRecipeProvider(pOutput,lp) ); } } ``` rundata即可生成合成表。其他的合成表可以参考原版的实现和原版的类。 ## 拓展阅读 [wiki](https://zh.minecraft.wiki/w/%E9%85%8D%E6%96%B9) [boson教程](https://boson.v2mcdev.com/datapack/recipes.html) [Neoforge document](https://docs.neoforged.net/docs/datagen/server/recipes) --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/events2/event-list.md --- # 15.0 事件列表 *** 本章将**可能**用到的事件 | 事件 | 类型 | | -------------------------- | ------ | | player.logged\_in | Server | | player.logged\_out | Server | | player.tick | Server | | player.data\_from\_server. | Client | | player.data\_from\_client. | Server | | player.chat | Server | | player.advancement | Server | | player.inventory.opened | Server | | player.inventory.closed | Server | | player.inventory.changed | Server | | player.chest.opened | Server | | player.chest.closed | Server | | entity.death | Server | | entity.attack | Server | | entity.drops | Server | | entity.check\_spawn | Server | | entity.spawned | Server | | block.right\_click | Server | | block.left\_click | Server | | block.place | Server | | block.break | Server | | block.drops | Server | | item.right\_click | Server | | item.right\_click\_empty | Server | | item.left\_click | Server | | item.entity\_interact | Server | | item.pickup | Server | | item.toss | Server | | item.crafted | Server | | item.smelted | Server | --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/events2/event-list.md --- # 15.0 事件列表 *** 本章将**可能**用到的事件 | 事件 | 类型 | | -------------------------- | ------ | | player.logged\_in | Server | | player.logged\_out | Server | | player.tick | Server | | player.data\_from\_server. | Client | | player.data\_from\_client. | Server | | player.chat | Server | | player.advancement | Server | | player.inventory.opened | Server | | player.inventory.closed | Server | | player.inventory.changed | Server | | player.chest.opened | Server | | player.chest.closed | Server | | entity.death | Server | | entity.attack | Server | | entity.drops | Server | | entity.check\_spawn | Server | | entity.spawned | Server | | block.right\_click | Server | | block.left\_click | Server | | block.place | Server | | block.break | Server | | block.drops | Server | | item.right\_click | Server | | item.right\_click\_empty | Server | | item.left\_click | Server | | item.entity\_interact | Server | | item.pickup | Server | | item.toss | Server | | item.crafted | Server | | item.smelted | Server | --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/events2/playerjs.md --- # 15.1 玩家&实体事件,信息获取及操作 *** ## 一、信息获取 以下内容会返回玩家/实体的信息 | 格式 | 功能 | 返回值类型 | | ----------------------- | ----------------------------- | ------------------------- | | inventory | 返回玩家的库存(仅适用于玩家) | InventoryJS\[1] | | block | 返回涉及到的方块 | BlockContainerJS | | selectedSlot | 返回当前玩家选中的格子(仅适用于玩家) | 整形 | | mouseItem | 返回当前玩家鼠标选取的物品(仅适用于玩家) | ItemStackJS\[2] | | creativeMode | 返回当前玩家是否为创造模式(仅适用于玩家) | 布尔值 | | spectator | 返回当前玩家是否为观察者模式(仅适用于玩家) | 布尔值 | | stats | 返回玩家统计数据(仅适用于玩家) | PlayerStatsJS\[3] | | foodLevel | 返回玩家饱食度等级(仅适用于玩家) | 整形 | | xp | 返回玩家经验值(仅适用于玩家) | 整形 | | xplevel | 返回玩家经验等级(仅适用于玩家) | 整形 | | miningBlock | 返回玩家是否正在破坏方块(仅适用于玩家) | 布尔值 | | airSupply | 返回玩家当前氧气供应值(仅适用于玩家) | 整形 | | maxAirSupply | 返回玩家最大氧气供应值(仅适用于玩家) | 整形 | | stages | 返回玩家游戏阶段(仅适用于玩家) | Stages\[4] | | name | 返回玩家名称 | Text | | displayName | 返回玩家显示名称 | Text | | alive | 返回游戏中玩家是否存活 | 布尔值 | | living | 返回是否处于活跃状态 | 布尔值 | | crouching | 返回玩家/实体是否正在潜行 | 布尔值 | | sprinting | 返回玩家/实体是否正在疾跑 | 布尔值 | | swimming | 返回玩家/实体是否正在游泳 | 布尔值 | | glowing | 返回玩家/实体是否正在发光 | 布尔值 | | invisible | 返回玩家/实体是否隐形 | 布尔值 | | invulnerable | 返回玩家/实体是否不受伤害 | 布尔值 | | onGround | 返回玩家/实体是否站在地上 | 布尔值 | | fallDistance | 返回玩家/实体目前掉落距离(落地后该值归0) | 浮点型 | | noClip | 返回玩家/实体是否没有碰撞箱(观察者模式下该值为true) | 布尔值 | | x y z | 返回玩家/实体的x/y/z坐标 | 浮点型 | | yaw pitch | 返回玩家/实体的偏航角/俯仰角 | 浮点型 | | motionX motionY motionZ | 返回玩家/实体动量 | 浮点型 | | ticksExisted | 返回玩家/实体存在的游戏刻数 | 整形 | | world | 返回WorldJS | WorldJS\[5] | | ridingEntity | 返回玩家/实体正在骑的实体 | EntityJS | | teamId | 返回玩家/实体当前所在队伍名称 | 字符串 | | customName | 返回玩家/实体自定义名称 | Text | | hasCustomName | 返回玩家/实体是否有自定义名称 | 布尔值 | | customNameAlwaysVisible | 返回玩家/实体自定义名称是否总是可见 | 布尔值 | | eyeHeight | 返回玩家/实体视线高度 | 浮点型 | | fullNBT | 返回玩家/实体NBT | CompoundTag | | inWater | 返回玩家/实体是否在水里 | 布尔值 | | underWater | 返回玩家/实体是否在水下 | 布尔值 | | child | 返回玩家/实体是否为幼年个体 | 布尔值 | | potionEffects | 返回玩家/实体具有的效果 | EntityPotionEffectsJS\[6] | | lastDamageSource | 返回玩家/实体最后收到的伤害来源 | DamageSourceJS\[7] | | health | 返回玩家/实体的血量 | 浮点型 | | maxHealth | 返回玩家/实体的最大血量 | 浮点型 | | lastAttackedEntity | 返回最后攻击的实体 | LivingEntityJS | | idleTime | 返回玩家/实体无动作时间 | 整形 | | attackingEntity | 返回玩家/实体攻击对象 | LivingEntityJS | | mainHandItem | 返回玩家/实体主手物品 | ItemStackJS | | offHandItem | 返回玩家/实体副手物品 | ItemStackJS | | headArmorItem | 返回玩家/实体头盔物品 | ItemStackJS | | chestArmorItem | 返回玩家/实体胸甲物品 | ItemStackJS | | legsArmorItem | 返回玩家/实体腿甲物品 | ItemStackJS | | feetArmorItem | 返回玩家/实体鞋子物品 | ItemStackJS | | movementSpeed | 返回玩家/实体移动速度 | 浮点型 | | reachDistance | 返回玩家/实体攻击距离 | 浮点型 | | onFire | 返回玩家/实体着火状态(支持通过a = b方式修改) | 整形 | | statusMessage | 返回玩家状态栏消息(支持通过a = b方式修改) | Text\[8] | | horizontalFacing | 返回玩家水平方向 | Direction\[9] | | facing | 返回玩家方向 | Direction\[9] | \[1]、\[2]、\[3]、\[6]、\[7]、\[8]见后文 \[4]见11.4 KubeJs内置游戏阶段 (类似于GameStage) \[5]见6 自定义世界生成 \[9]返回值可为DOWN, UP, NORTH, SOUTH, WEST, EAST ## 二、函数 | 格式 | 功能 | 返回值 | | -------------------------------------------------------------- | ------------------- | ---------------- | | sendInventoryUpdate()(仅适用于玩家) | 向客户端(?)发送背包数据更新 | void | | give(ItemStackJS 物品) (仅适用于玩家) | 给予玩家物品 | void | | giveInHand(ItemStackJS 物品)(仅适用于玩家) | 给予玩家物品(快捷栏而不是背包) | void | | sendData(字符串 频道, 数据)(仅适用于玩家) | 发送网络包(详见后文) | void | | addFood(整形 食物水平, 浮点型 饱和度)(仅适用于玩家) | 为玩家添加饥饿值 | void | | addExhaustion(浮点型 饥饿等级)(仅适用于玩家) | 为玩家添加饥饿等级 | void | | addXP(整形 经验值)(仅适用于玩家) | 为玩家添加经验值 | void | | addXPLevels(整形 经验等级)(仅适用于玩家) | 为玩家添加经验等级 | void | | paint(CompoundTag 渲染器)(仅适用于玩家) | 调用Painter API(详见后文) | void | | boostElytraFlight()(仅适用于玩家) | 加速玩家鞘翅飞行(类似于烟花) | void | | closeInventory()(仅适用于玩家) | 关闭玩家背包 | void | | addItemCooldown(Item 物品, 整形 冷却tick)(仅适用于玩家) | 添加物品冷却 | voi | | runCommand(字符串 命令内容) | 执行命令\[10] | int | | runCommandSilent(字符串 命令内容) | 静默执行命令\[10] | int | | setMotion(浮点型 x, 浮点型 y, 浮点型 z) | 设置实体动量 | void | | setRotation(浮点型 偏航角,浮点型 俯仰角) | 设置玩家旋转角度 | void | | setPosition(浮点型 x, 浮点型 y, 浮点型 z) | 设置玩家坐标 | void | | setPositionAndRotation(浮点型 x, 浮点型 y, 浮点型 z, 浮点型 偏航角,浮点型 俯仰角) | 上边两个函数的合并 | void | | addMotion(浮点型 x, 浮点型 y, 浮点型 z) | 添加实体动量 | void | | kill() | 我给你毙了 | void | | startRiding(EntityJS 实体, 布尔值 是否强制) | 使玩家骑某个实体 | boolean | | removePassengers() | 移除当前实体乘客 | void | | dismountRidingEntity() | 取消当前实体骑乘状态 | void | | isPassenger(EntityJS 实体) | 返回实体是否为骑乘状态 | 布尔值 | | isOnSameTeam(EntityJS 实体) | 返回是否与某实体为同一队 | 布尔值 | | isOnScoreboardTeam(字符串 队伍名称) | 返回是否在计分板队伍上 | 布尔值 | | extinguish() | 灭火 | void | | playSound(SoundEvent 音乐名, 浮点型 音量, 浮点型 角度) | 播放声音 | void | | playSound(SoundEvent 音乐名) | 同上 | void | | spawn() | 立即重生 | void | | attack(字符串 来源, 浮点型 血量) | 使对象受伤 | void | | attack(浮点型 血量) | 同上 | void | | rayTrace(浮点型 距离) | rayTraceEvent(详见后文) | RayTraceResultJS | | getDistanceSq(浮点型 x, 浮点型 y, 浮点型 z) | 返回与坐标之间距离的平方\[11] | 浮点型 | | getDistanceSq(BlockPos 坐标) | 返回与坐标之间距离的平方\[11] | 浮点型 | | getDistance(浮点型 x, 浮点型 y, 浮点型 z) | 返回与坐标之间距离\[11] | 浮点型 | | getDistance(BlockPos 坐标) | 返回与坐标之间距离\[11] | 浮点型 | | heal(浮点型 血量) | 为对象添加血量 | void | | swingArm(InteractionHand 手) | 晃动手臂\[12] | void | | getEquipment(EquipmentSlot 栏位) | 返回指定栏位的装备\[13] | ItemStackJS | | setEquipment(EquipmentSlot 栏位, ItemStackJS 物品) | 设置指定栏位的装备\[13] | void | | getHeldItem(InteractionHand 手) | 返回指定手的物品\[12] | ItemStackJS | | setHeldItem(InteractionHand 手, ItemStackJS 手) | 设置指定手的物品\[12] | void | | damageEquipment(EquipmentSlot 栏位, 整形 扣除耐久值) | 损坏物品\[13] | void | | damageEquipment(EquipmentSlot 栏位, 整形 扣除耐久值, Consumer onBroken) | 损坏物品\[13] | void | | damageEquipment(EquipmentSlot 栏位) | 损坏物品\[13] | void | | damageHeldItem(InteractionHand 手, 整形 扣除耐久值) | 损坏物品\[13] | void | | damageHeldItem(InteractionHand 手, 整形 扣除耐久值, Consumer onBroken) | 损坏物品\[13] | void | | damageHeldItem() | 损坏物品 | void | | isHoldingInAnyHand(内容) | 指定内容是否拿在手中 | boolean | | canEntityBeSeen(EntityJS 实体) | 是否能看到指定实体 | boolean | | rayTrace(整形 距离) | rayTraceEvent(详见后文) | RayTraceResultJS | | mergeFullNBT(字符串 NBT) | 将给定的NBT与原实体的合并\[14] | void | \[10]:该函数与ServerJS下的同名方法有两点不同 ① 此处执行命令需要加斜杠( / ),而后者不用 ② 此处执行命令会以玩家的权限执行,而前者为服务器控制台权限。另外成功执行命令时该函数返回1,失败返回0 \[11]:getDistance与getDistanceSq的区别见下: ![](https://m1.miaomc.cn/uploads/20220326_6988c88d4bdc4.png) \[12]:InteractionHand 接受值为 MAIN\_HAND或OFF\_HAND \[13]:EquipmentSlot 接受值为 MAINHAND、OFFHAND、HEAD、CHEST、LEGS、FEET \[14]:相同的会被新值覆盖 注1:实体与玩家的操作方法大体相同(也就是适用于玩家的很多函数同时适用于实体) 注2:实际上PlayerJS继承于LivingEntityJS,而后者又继承于EntityJS。 --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/events2/playerjs.md --- # 15.1 玩家&实体事件,信息获取及操作 *** ## 一、信息获取 以下内容会返回玩家/实体的信息 | 格式 | 功能 | 返回值类型 | | ----------------------- | ----------------------------- | ------------------------- | | inventory | 返回玩家的库存(仅适用于玩家) | InventoryJS\[1] | | block | 返回涉及到的方块 | BlockContainerJS | | selectedSlot | 返回当前玩家选中的格子(仅适用于玩家) | 整形 | | mouseItem | 返回当前玩家鼠标选取的物品(仅适用于玩家) | ItemStackJS\[2] | | creativeMode | 返回当前玩家是否为创造模式(仅适用于玩家) | 布尔值 | | spectator | 返回当前玩家是否为观察者模式(仅适用于玩家) | 布尔值 | | stats | 返回玩家统计数据(仅适用于玩家) | PlayerStatsJS\[3] | | foodLevel | 返回玩家饱食度等级(仅适用于玩家) | 整形 | | xp | 返回玩家经验值(仅适用于玩家) | 整形 | | xplevel | 返回玩家经验等级(仅适用于玩家) | 整形 | | miningBlock | 返回玩家是否正在破坏方块(仅适用于玩家) | 布尔值 | | airSupply | 返回玩家当前氧气供应值(仅适用于玩家) | 整形 | | maxAirSupply | 返回玩家最大氧气供应值(仅适用于玩家) | 整形 | | stages | 返回玩家游戏阶段(仅适用于玩家) | Stages\[4] | | name | 返回玩家名称 | Text | | displayName | 返回玩家显示名称 | Text | | alive | 返回游戏中玩家是否存活 | 布尔值 | | living | 返回是否处于活跃状态 | 布尔值 | | crouching | 返回玩家/实体是否正在潜行 | 布尔值 | | sprinting | 返回玩家/实体是否正在疾跑 | 布尔值 | | swimming | 返回玩家/实体是否正在游泳 | 布尔值 | | glowing | 返回玩家/实体是否正在发光 | 布尔值 | | invisible | 返回玩家/实体是否隐形 | 布尔值 | | invulnerable | 返回玩家/实体是否不受伤害 | 布尔值 | | onGround | 返回玩家/实体是否站在地上 | 布尔值 | | fallDistance | 返回玩家/实体目前掉落距离(落地后该值归0) | 浮点型 | | noClip | 返回玩家/实体是否没有碰撞箱(观察者模式下该值为true) | 布尔值 | | x y z | 返回玩家/实体的x/y/z坐标 | 浮点型 | | yaw pitch | 返回玩家/实体的偏航角/俯仰角 | 浮点型 | | motionX motionY motionZ | 返回玩家/实体动量 | 浮点型 | | ticksExisted | 返回玩家/实体存在的游戏刻数 | 整形 | | world | 返回WorldJS | WorldJS\[5] | | ridingEntity | 返回玩家/实体正在骑的实体 | EntityJS | | teamId | 返回玩家/实体当前所在队伍名称 | 字符串 | | customName | 返回玩家/实体自定义名称 | Text | | hasCustomName | 返回玩家/实体是否有自定义名称 | 布尔值 | | customNameAlwaysVisible | 返回玩家/实体自定义名称是否总是可见 | 布尔值 | | eyeHeight | 返回玩家/实体视线高度 | 浮点型 | | fullNBT | 返回玩家/实体NBT | CompoundTag | | inWater | 返回玩家/实体是否在水里 | 布尔值 | | underWater | 返回玩家/实体是否在水下 | 布尔值 | | child | 返回玩家/实体是否为幼年个体 | 布尔值 | | potionEffects | 返回玩家/实体具有的效果 | EntityPotionEffectsJS\[6] | | lastDamageSource | 返回玩家/实体最后收到的伤害来源 | DamageSourceJS\[7] | | health | 返回玩家/实体的血量 | 浮点型 | | maxHealth | 返回玩家/实体的最大血量 | 浮点型 | | lastAttackedEntity | 返回最后攻击的实体 | LivingEntityJS | | idleTime | 返回玩家/实体无动作时间 | 整形 | | attackingEntity | 返回玩家/实体攻击对象 | LivingEntityJS | | mainHandItem | 返回玩家/实体主手物品 | ItemStackJS | | offHandItem | 返回玩家/实体副手物品 | ItemStackJS | | headArmorItem | 返回玩家/实体头盔物品 | ItemStackJS | | chestArmorItem | 返回玩家/实体胸甲物品 | ItemStackJS | | legsArmorItem | 返回玩家/实体腿甲物品 | ItemStackJS | | feetArmorItem | 返回玩家/实体鞋子物品 | ItemStackJS | | movementSpeed | 返回玩家/实体移动速度 | 浮点型 | | reachDistance | 返回玩家/实体攻击距离 | 浮点型 | | onFire | 返回玩家/实体着火状态(支持通过a = b方式修改) | 整形 | | statusMessage | 返回玩家状态栏消息(支持通过a = b方式修改) | Text\[8] | | horizontalFacing | 返回玩家水平方向 | Direction\[9] | | facing | 返回玩家方向 | Direction\[9] | \[1]、\[2]、\[3]、\[6]、\[7]、\[8]见后文 \[4]见11.4 KubeJs内置游戏阶段 (类似于GameStage) \[5]见6 自定义世界生成 \[9]返回值可为DOWN, UP, NORTH, SOUTH, WEST, EAST ## 二、函数 | 格式 | 功能 | 返回值 | | -------------------------------------------------------------- | ------------------- | ---------------- | | sendInventoryUpdate()(仅适用于玩家) | 向客户端(?)发送背包数据更新 | void | | give(ItemStackJS 物品) (仅适用于玩家) | 给予玩家物品 | void | | giveInHand(ItemStackJS 物品)(仅适用于玩家) | 给予玩家物品(快捷栏而不是背包) | void | | sendData(字符串 频道, 数据)(仅适用于玩家) | 发送网络包(详见后文) | void | | addFood(整形 食物水平, 浮点型 饱和度)(仅适用于玩家) | 为玩家添加饥饿值 | void | | addExhaustion(浮点型 饥饿等级)(仅适用于玩家) | 为玩家添加饥饿等级 | void | | addXP(整形 经验值)(仅适用于玩家) | 为玩家添加经验值 | void | | addXPLevels(整形 经验等级)(仅适用于玩家) | 为玩家添加经验等级 | void | | paint(CompoundTag 渲染器)(仅适用于玩家) | 调用Painter API(详见后文) | void | | boostElytraFlight()(仅适用于玩家) | 加速玩家鞘翅飞行(类似于烟花) | void | | closeInventory()(仅适用于玩家) | 关闭玩家背包 | void | | addItemCooldown(Item 物品, 整形 冷却tick)(仅适用于玩家) | 添加物品冷却 | voi | | runCommand(字符串 命令内容) | 执行命令\[10] | int | | runCommandSilent(字符串 命令内容) | 静默执行命令\[10] | int | | setMotion(浮点型 x, 浮点型 y, 浮点型 z) | 设置实体动量 | void | | setRotation(浮点型 偏航角,浮点型 俯仰角) | 设置玩家旋转角度 | void | | setPosition(浮点型 x, 浮点型 y, 浮点型 z) | 设置玩家坐标 | void | | setPositionAndRotation(浮点型 x, 浮点型 y, 浮点型 z, 浮点型 偏航角,浮点型 俯仰角) | 上边两个函数的合并 | void | | addMotion(浮点型 x, 浮点型 y, 浮点型 z) | 添加实体动量 | void | | kill() | 我给你毙了 | void | | startRiding(EntityJS 实体, 布尔值 是否强制) | 使玩家骑某个实体 | boolean | | removePassengers() | 移除当前实体乘客 | void | | dismountRidingEntity() | 取消当前实体骑乘状态 | void | | isPassenger(EntityJS 实体) | 返回实体是否为骑乘状态 | 布尔值 | | isOnSameTeam(EntityJS 实体) | 返回是否与某实体为同一队 | 布尔值 | | isOnScoreboardTeam(字符串 队伍名称) | 返回是否在计分板队伍上 | 布尔值 | | extinguish() | 灭火 | void | | playSound(SoundEvent 音乐名, 浮点型 音量, 浮点型 角度) | 播放声音 | void | | playSound(SoundEvent 音乐名) | 同上 | void | | spawn() | 立即重生 | void | | attack(字符串 来源, 浮点型 血量) | 使对象受伤 | void | | attack(浮点型 血量) | 同上 | void | | rayTrace(浮点型 距离) | rayTraceEvent(详见后文) | RayTraceResultJS | | getDistanceSq(浮点型 x, 浮点型 y, 浮点型 z) | 返回与坐标之间距离的平方\[11] | 浮点型 | | getDistanceSq(BlockPos 坐标) | 返回与坐标之间距离的平方\[11] | 浮点型 | | getDistance(浮点型 x, 浮点型 y, 浮点型 z) | 返回与坐标之间距离\[11] | 浮点型 | | getDistance(BlockPos 坐标) | 返回与坐标之间距离\[11] | 浮点型 | | heal(浮点型 血量) | 为对象添加血量 | void | | swingArm(InteractionHand 手) | 晃动手臂\[12] | void | | getEquipment(EquipmentSlot 栏位) | 返回指定栏位的装备\[13] | ItemStackJS | | setEquipment(EquipmentSlot 栏位, ItemStackJS 物品) | 设置指定栏位的装备\[13] | void | | getHeldItem(InteractionHand 手) | 返回指定手的物品\[12] | ItemStackJS | | setHeldItem(InteractionHand 手, ItemStackJS 手) | 设置指定手的物品\[12] | void | | damageEquipment(EquipmentSlot 栏位, 整形 扣除耐久值) | 损坏物品\[13] | void | | damageEquipment(EquipmentSlot 栏位, 整形 扣除耐久值, Consumer onBroken) | 损坏物品\[13] | void | | damageEquipment(EquipmentSlot 栏位) | 损坏物品\[13] | void | | damageHeldItem(InteractionHand 手, 整形 扣除耐久值) | 损坏物品\[13] | void | | damageHeldItem(InteractionHand 手, 整形 扣除耐久值, Consumer onBroken) | 损坏物品\[13] | void | | damageHeldItem() | 损坏物品 | void | | isHoldingInAnyHand(内容) | 指定内容是否拿在手中 | boolean | | canEntityBeSeen(EntityJS 实体) | 是否能看到指定实体 | boolean | | rayTrace(整形 距离) | rayTraceEvent(详见后文) | RayTraceResultJS | | mergeFullNBT(字符串 NBT) | 将给定的NBT与原实体的合并\[14] | void | \[10]:该函数与ServerJS下的同名方法有两点不同 ① 此处执行命令需要加斜杠( / ),而后者不用 ② 此处执行命令会以玩家的权限执行,而前者为服务器控制台权限。另外成功执行命令时该函数返回1,失败返回0 \[11]:getDistance与getDistanceSq的区别见下: ![](https://m1.miaomc.cn/uploads/20220326_6988c88d4bdc4.png) \[12]:InteractionHand 接受值为 MAIN\_HAND或OFF\_HAND \[13]:EquipmentSlot 接受值为 MAINHAND、OFFHAND、HEAD、CHEST、LEGS、FEET \[14]:相同的会被新值覆盖 注1:实体与玩家的操作方法大体相同(也就是适用于玩家的很多函数同时适用于实体) 注2:实际上PlayerJS继承于LivingEntityJS,而后者又继承于EntityJS。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/events2/entityjs-raytrace-itemstack.md --- # 15.2 实体生成 & RayTraceResultJS & ItemStackJS及其他 *** 本节内容:实体生成,RayTraceResultJS ,ItemStackJS,InventoryJS,PlayerStatsJS ## 一、实体生成 实体生成较为简单,你需要先新建一个实体,然后设置属性,再在合适的时间生成它即可 以下代码会在玩家在聊天栏发送内容时在玩家位置生成一个火球 ``` onEvent("player.chat", event => { let p = event.player; // 新建实体 var entity = event.world.createEntity('minecraft:fireball'); // 设置实体NBT entity.mergeFullNBT('{ExplosionPower:2}'); // 设置坐标 entity.setPosition(p.x, p.y, p.z); // 生成实体,没有这一行实体是不会生成的 entity.spawn(); }) ``` createEntity的返回值为EntityJS,所以绝大多数15.1中的东西都是可以应用于实体生成的,这里就不再赘述了。 ## 二、RayTraceResultJS RayTraceResultJS 会返回玩家所看的方向、方块、实体等内容。 | 属性 | 功能 | 返回值 | | ---------- | ------------ | ---------------- | | fromEntity | 返回"视线"的来源 | EntityJS | | type | 返回玩家看到的内容类型 | 字符串\[1] | | distance | 返回目标距离玩家距离 | 浮点型 | | hitX | 返回"视线"目标的X坐标 | 浮点型 | | hitY | 返回"视线"目标的Y坐标 | 浮点型 | | hitZ | 返回"视线"目标的Z坐标 | 浮点型 | | block | 返回视线目标方块 | BlockContainerJS | | facing | 返回视线的方向 | Direction | | entity\[2] | 返回视线目标实体 | EntityJS | \[1]:该值可以为"block"、"entity"和"miss" \[2]:因为KubeJS的特性,有关实体的内容仅在客户端侧可用且距离**不得大于玩家的攻击距离**。 值得注意的是,绝大多数的属性在玩家没有面向对象时均为null,也就是说在使用RayTraceResultJS时,必须先判断其值是否为null或者利用type来判断类型(否则你得到一堆报错) 以下例子为指南针添加了一个实用功能:返回其右键点击的方块id和属性并生成对应/setblock指令供玩家复制 ``` onEvent('item.right_click', event => { let player = event.player; let target = player.rayTrace(1000); if(target.type == "block" && player.mainHandItem.id == "minecraft:compass"){ let prop = target.block.properties.toString(); // 把信息告诉玩家 player.tell(`你刚才看的方块是:${target.block.id},距离当前位置${player.getDistance(target.hitX, target.hitY, target.hitZ).toFixed(1)}格,方块属性:${prop}`); // 复制指令(TextJS) player.tell(Text.of("[点此复制/setblock指令]").bold().click(`copy:/setblock ~ ~ ~ ${target.block}`)); // 为物品添加冷却 // player.addItemCooldown("minecraft:compass", 1000); } }) ``` ## 三、ItemStackJS ### 1、属性 | 属性 | 功能 | 返回值 | | ---------------- | ---------------- | --------------- | | id | 返回该物品组的id | 字符串 | | tags | 返回该物品组的tags | Collection tags | | count | 设置/返回该物品组中物品数量 | int | | block | 返回当前物品是否为方块 | 布尔值 | | nbt | 返回当前物品组的nbt | CompoundTag | | nbtString | 返回字符串形式的nbt | 字符串 | | name | 返回当前物品组的名称 | Text | | enchantments | 返回当前物品组的附魔 | MapJS | | harvestSpeed | 返回当前物品组的破坏速度 | 浮点型 | | itemGroup | 返回当前物品组在创造物品栏的位置 | 字符串 | | vanillaPredicate | 获取当前物品谓词(?) | Predicate | | empty | 返回当前物品组是否为空 | 布尔值 | ### 2、函数 | 函数 | 功能 | 返回值 | | ---------------------------------------------------------------------------------- | ---------------------- | ------------ | | hasTag(tag) | 判断物品是否有指定tag | 布尔值 | | withCount(整形 数量) | 返回一个指定数量的物品组 | ItemStackJS | | hasNBT() | 判断是否具有NBT | 布尔值 | | removeNBT() | 移除物品的NBT | void | | withNBT(CompoundTag nbt) | 返回具有指定NBT的物品组 | ItemStackJS | | withName(Text 名称) | 返回一个具有指定名称的物品组 | ItemStackJS | | strongEquals(any) | 将当前物品组与给定内容对比(同时比较数量等) | 布尔值 | | hasEnchantment(字符串 附魔ID, 整形 等级) | 判断当前物品组是否有给定的附魔 | 布尔值 | | enchant(字符串 附魔ID, 整形 等级) | 返回一个添加了指定附魔的物品组 | ItemStackJS | | enchant(MapJS 附魔内容) | 返回一个添加了指定附魔的物品组 | ItemStackJS | | ignoreNBT() | 返回忽略的NBT的原料(配方相关) | IngredientJS | | weakNBT() | 返回部分忽略的NBT的原料(配方相关) | IngredientJS | | areItemsEqual(ItemStackJS 对比对象) | 将当前物品组与给定物品组对比 | 布尔值 | | isNBTEqual(ItemStackJS 对比对象) | 将当前物品组与给定物品组的NBT对比 | 布尔值 | | getHarvestLevel(ToolType 工具类型, nullable PlayerJS 玩家, nullable BlockContainerJS 方块) | 返回其挖掘等级 | 整形 | | getHarvestSpeed(nullable BlockContainerJS 方块) | 返回其挖掘速度 | 浮点型 | ## 四、InventoryJS 你可以使用InventoryJS来快捷地操作玩家物品栏 | 函数 | 功能 | 返回值 | | ---------------------------- | --------------------- | ----------- | | get(int 格子编号) | 返回指定格子的物品 | ItemStackJS | | set(int 格子编号,ItemStackJS 物品) | 设置指定格子的物品 | void | | clear() | 清空玩家背包 | void | | clear(ItemStackJS 物品) | 清空玩家背包中指定物品 | void | | find() | 返回玩家背包中有物品的格子编号\[3] | 整形 | | find(ItemStackJS 物品) | 返回玩家背包中有指定物品的格子编号\[3] | 整形 | | count() | 返回物品背包中物品总数\[4] | 整形 | | count(ItemStackJS 物品) | 返回玩家背包中指定物品数\[4] | 整形 | \[3] 当无该物品时会返回 -1,有则为最小编号 \[4] 当无物品时会返回 0 下图是玩家物品栏格子编号(图源:Minecraft Wiki) ![](https://patchwiki.biligame.com/images/mc/c/c6/g1edcd1iybb1x4tw6ntsvmvo5auixhl.png) ## 五、PlayerStatsJS PlayerStatsJS 允许你操作玩家的统计信息中的数据 | 函数 | 功能 | 返回值 | | ---------------------- | ------------------ | ---- | | get(命名空间) | 返回玩家指定项的统计信息 | 整形 | | set(命名空间, 整形 值) | 设置玩家指定项的统计信息 | void | | add(命名空间, 整形 值) | 为玩家指定项的统计信息添加值 | void | | getBlocksMined(命名空间) | 返回玩家破坏指定方块的统计信息的值 | 整形 | | getItemsCrafted(命名空间) | 返回玩家合成指定物品的统计信息的值 | 整形 | | getItemsUsed(命名空间) | 返回玩家使用指定物品的统计信息的值 | 整形 | | getItemsBroken(命名空间) | 返回玩家用坏指定物品的统计信息的值 | 整形 | | getItemsPickedUp(命名空间) | 返回玩家捡起指定物品的统计信息的值 | 整形 | | getItemsDropped(命名空间) | 返回玩家丢弃指定物品的统计信息的值 | 整形 | | getKilled(命名空间) | 返回玩家杀死指定生物的统计信息的值 | 整形 | | getKilledBy(命名空间) | 返回玩家被指定生物杀死的统计信息的值 | 整形 | --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/events2/entityjs-raytrace-itemstack.md --- # 15.2 实体生成 & RayTraceResultJS & ItemStackJS及其他 *** 本节内容:实体生成,RayTraceResultJS ,ItemStackJS,InventoryJS,PlayerStatsJS ## 一、实体生成 实体生成较为简单,你需要先新建一个实体,然后设置属性,再在合适的时间生成它即可 以下代码会在玩家在聊天栏发送内容时在玩家位置生成一个火球 ``` onEvent("player.chat", event => { let p = event.player; // 新建实体 var entity = event.world.createEntity('minecraft:fireball'); // 设置实体NBT entity.mergeFullNBT('{ExplosionPower:2}'); // 设置坐标 entity.setPosition(p.x, p.y, p.z); // 生成实体,没有这一行实体是不会生成的 entity.spawn(); }) ``` createEntity的返回值为EntityJS,所以绝大多数15.1中的东西都是可以应用于实体生成的,这里就不再赘述了。 ## 二、RayTraceResultJS RayTraceResultJS 会返回玩家所看的方向、方块、实体等内容。 | 属性 | 功能 | 返回值 | | ---------- | ------------ | ---------------- | | fromEntity | 返回"视线"的来源 | EntityJS | | type | 返回玩家看到的内容类型 | 字符串\[1] | | distance | 返回目标距离玩家距离 | 浮点型 | | hitX | 返回"视线"目标的X坐标 | 浮点型 | | hitY | 返回"视线"目标的Y坐标 | 浮点型 | | hitZ | 返回"视线"目标的Z坐标 | 浮点型 | | block | 返回视线目标方块 | BlockContainerJS | | facing | 返回视线的方向 | Direction | | entity\[2] | 返回视线目标实体 | EntityJS | \[1]:该值可以为"block"、"entity"和"miss" \[2]:因为KubeJS的特性,有关实体的内容仅在客户端侧可用且距离**不得大于玩家的攻击距离**。 值得注意的是,绝大多数的属性在玩家没有面向对象时均为null,也就是说在使用RayTraceResultJS时,必须先判断其值是否为null或者利用type来判断类型(否则你得到一堆报错) 以下例子为指南针添加了一个实用功能:返回其右键点击的方块id和属性并生成对应/setblock指令供玩家复制 ``` onEvent('item.right_click', event => { let player = event.player; let target = player.rayTrace(1000); if(target.type == "block" && player.mainHandItem.id == "minecraft:compass"){ let prop = target.block.properties.toString(); // 把信息告诉玩家 player.tell(`你刚才看的方块是:${target.block.id},距离当前位置${player.getDistance(target.hitX, target.hitY, target.hitZ).toFixed(1)}格,方块属性:${prop}`); // 复制指令(TextJS) player.tell(Text.of("[点此复制/setblock指令]").bold().click(`copy:/setblock ~ ~ ~ ${target.block}`)); // 为物品添加冷却 // player.addItemCooldown("minecraft:compass", 1000); } }) ``` ## 三、ItemStackJS ### 1、属性 | 属性 | 功能 | 返回值 | | ---------------- | ---------------- | --------------- | | id | 返回该物品组的id | 字符串 | | tags | 返回该物品组的tags | Collection tags | | count | 设置/返回该物品组中物品数量 | int | | block | 返回当前物品是否为方块 | 布尔值 | | nbt | 返回当前物品组的nbt | CompoundTag | | nbtString | 返回字符串形式的nbt | 字符串 | | name | 返回当前物品组的名称 | Text | | enchantments | 返回当前物品组的附魔 | MapJS | | harvestSpeed | 返回当前物品组的破坏速度 | 浮点型 | | itemGroup | 返回当前物品组在创造物品栏的位置 | 字符串 | | vanillaPredicate | 获取当前物品谓词(?) | Predicate | | empty | 返回当前物品组是否为空 | 布尔值 | ### 2、函数 | 函数 | 功能 | 返回值 | | ---------------------------------------------------------------------------------- | ---------------------- | ------------ | | hasTag(tag) | 判断物品是否有指定tag | 布尔值 | | withCount(整形 数量) | 返回一个指定数量的物品组 | ItemStackJS | | hasNBT() | 判断是否具有NBT | 布尔值 | | removeNBT() | 移除物品的NBT | void | | withNBT(CompoundTag nbt) | 返回具有指定NBT的物品组 | ItemStackJS | | withName(Text 名称) | 返回一个具有指定名称的物品组 | ItemStackJS | | strongEquals(any) | 将当前物品组与给定内容对比(同时比较数量等) | 布尔值 | | hasEnchantment(字符串 附魔ID, 整形 等级) | 判断当前物品组是否有给定的附魔 | 布尔值 | | enchant(字符串 附魔ID, 整形 等级) | 返回一个添加了指定附魔的物品组 | ItemStackJS | | enchant(MapJS 附魔内容) | 返回一个添加了指定附魔的物品组 | ItemStackJS | | ignoreNBT() | 返回忽略的NBT的原料(配方相关) | IngredientJS | | weakNBT() | 返回部分忽略的NBT的原料(配方相关) | IngredientJS | | areItemsEqual(ItemStackJS 对比对象) | 将当前物品组与给定物品组对比 | 布尔值 | | isNBTEqual(ItemStackJS 对比对象) | 将当前物品组与给定物品组的NBT对比 | 布尔值 | | getHarvestLevel(ToolType 工具类型, nullable PlayerJS 玩家, nullable BlockContainerJS 方块) | 返回其挖掘等级 | 整形 | | getHarvestSpeed(nullable BlockContainerJS 方块) | 返回其挖掘速度 | 浮点型 | ## 四、InventoryJS 你可以使用InventoryJS来快捷地操作玩家物品栏 | 函数 | 功能 | 返回值 | | ---------------------------- | --------------------- | ----------- | | get(int 格子编号) | 返回指定格子的物品 | ItemStackJS | | set(int 格子编号,ItemStackJS 物品) | 设置指定格子的物品 | void | | clear() | 清空玩家背包 | void | | clear(ItemStackJS 物品) | 清空玩家背包中指定物品 | void | | find() | 返回玩家背包中有物品的格子编号\[3] | 整形 | | find(ItemStackJS 物品) | 返回玩家背包中有指定物品的格子编号\[3] | 整形 | | count() | 返回物品背包中物品总数\[4] | 整形 | | count(ItemStackJS 物品) | 返回玩家背包中指定物品数\[4] | 整形 | \[3] 当无该物品时会返回 -1,有则为最小编号 \[4] 当无物品时会返回 0 下图是玩家物品栏格子编号(图源:Minecraft Wiki) ![](https://patchwiki.biligame.com/images/mc/c/c6/g1edcd1iybb1x4tw6ntsvmvo5auixhl.png) ## 五、PlayerStatsJS PlayerStatsJS 允许你操作玩家的统计信息中的数据 | 函数 | 功能 | 返回值 | | ---------------------- | ------------------ | ---- | | get(命名空间) | 返回玩家指定项的统计信息 | 整形 | | set(命名空间, 整形 值) | 设置玩家指定项的统计信息 | void | | add(命名空间, 整形 值) | 为玩家指定项的统计信息添加值 | void | | getBlocksMined(命名空间) | 返回玩家破坏指定方块的统计信息的值 | 整形 | | getItemsCrafted(命名空间) | 返回玩家合成指定物品的统计信息的值 | 整形 | | getItemsUsed(命名空间) | 返回玩家使用指定物品的统计信息的值 | 整形 | | getItemsBroken(命名空间) | 返回玩家用坏指定物品的统计信息的值 | 整形 | | getItemsPickedUp(命名空间) | 返回玩家捡起指定物品的统计信息的值 | 整形 | | getItemsDropped(命名空间) | 返回玩家丢弃指定物品的统计信息的值 | 整形 | | getKilled(命名空间) | 返回玩家杀死指定生物的统计信息的值 | 整形 | | getKilledBy(命名空间) | 返回玩家被指定生物杀死的统计信息的值 | 整形 | --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/events2/block.md --- # 15.3 方块信息获取及操作 *** ## 一、BlockContainerJS ### 1、属性 | 属性 | 功能 | 返回值类型 | | --------------- | ------------------- | ------------------------------------------------------------------ | | world | 返回WorldJS | WorldJS | | pos | 返回BlockPos | BlockPos | | x | 返回方块x坐标 | 整形 | | y | 返回方块y坐标 | 整形 | | z | 返回方块z坐标 | 整形 | | down | 返回该方块下方的方块 | BlockContainerJS | | up | 返回该方块上方的方块 | BlockContainerJS | | north | 返回该方块北方的方块 | BlockContainerJS | | south | 返回该方块南方的方块 | BlockContainerJS | | west | 返回该方块西方的方块 | BlockContainerJS | | east | 返回该方块东方的方块 | BlockContainerJS | | blockState | 返回包含该方块状态的对象 | BlockState | | id | 返回方块ID | 字符串 | | light | 返回光照等级 | 整形 | | canSeeSky | 是否露天 | 布尔值 | | item | 返回方块对应的物品 | ItemStackJS | | playersInRadius | 返回范围内的玩家 | [EntityArrayList](https://kubejs.com/wiki/kubejs/EntityArrayList/) | | biomeId | 返回所处群系的ID | 字符串 | | properties | 返回带有当前方块所有属性的对象\[1] | 对象 | \[1]如`block.properties.facing`会返回当前方块朝向的字符串 ### 2、方法 | 方法 | 功能 | 返回值类型 | | ------------------------------------------------ | -------------------------- | -------------------- | | offset(Direction 方向, int 格数) | 返回在给定方向上指定格数以外的方块 | BlockContainerJS | | offset(Direction 方向) | 返回在给定方向上一格以外的方块 | BlockContainerJS | | offset(整形 x, 整形 y, 整形 z) | 返回以当前方块为坐标系原点,指定X,Y,Z以外的方块 | BlockContainerJS | | setBlockState(BlockState 状态值, int ) | 设置方块状态值 | void | | hasTag(tag) | 返回是否具有指定标签 | 布尔值 | | set(命名空间 方块ID, Map<字符串, 字符串> 方块属性, 整形 flags) | 在当前位置设置方块 | void | | set(命名空间 方块ID, Map<字符串, 字符串> 方块属性) | 在当前位置设置方块 | void | | set(命名空间 方块ID) | 在当前位置设置方块 | void | | createExplosion() | 新建爆炸事件(见下文) | ExplosionJS | | createEntity(命名空间ID) | 新建实体(见15.2) | EntityJS | | spawnLightning(布尔值 是否只有效果, nullable EntityJS 玩家) | 生成闪电 | void | | spawnLightning(布尔值 是否只有效果) | 生成闪电 | void | | spawnFireworks(FireworksJS 烟花) | 生成烟花(见下文) | void | | getInventory(Direction 方向) | 获取方块的库存 | nullable InventoryJS | | getPlayersInRadius(浮点型 范围) | 返回指定范围内的玩家列表 | EntityArrayList | ## 二、ExplosionJS 新建爆炸和新建实体类似,你需要先新建一个爆炸,然后设置属性,再在合适的时间生成它即可 | 函数 | 功能 | 返回值类型 | | --------------------------- | -------------- | ----------- | | exploder(EntityJS 实体) | 设定是谁引起的爆炸 | ExplosionJS | | strength(浮点型 爆炸强度) | 设定爆炸的强度 | ExplosionJS | | causesFire(布尔值 是否引火) | 设定爆炸是否会生成火 | ExplosionJS | | damagesTerrain(布尔值 是否破坏环境) | 设定爆炸是否破坏地形\[2] | ExplosionJS | | destroysTerrain(布尔值 是否破坏环境) | 设定爆炸是否破坏地形\[2] | ExplosionJS | | explode() | 炸! | void | \[2]damagesTerrain会破坏方块但是掉落方块,destroysTerrain会破坏方块且不掉落方块。若不设置默认为破坏且掉落方块。 一个简单的例子,在玩家右键方块时生成一个小爆炸 ``` onEvent('block.right_click', event => { // 新建爆炸 let explosion = event.block.createExplosion(); // 设置爆炸强度,默认值为3 explosion.strength(100.0); // 设置是否生成火,默认值为false explosion.causesFire(true); explosion.explode(); }) ``` (应该很小吧) ![](https://m1.miaomc.cn/uploads/20220416_f84d7444ffa57.jpg) --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/events2/block.md --- # 15.3 方块信息获取及操作 *** ## 一、BlockContainerJS ### 1、属性 | 属性 | 功能 | 返回值类型 | | --------------- | ------------------- | ------------------------------------------------------------------ | | world | 返回WorldJS | WorldJS | | pos | 返回BlockPos | BlockPos | | x | 返回方块x坐标 | 整形 | | y | 返回方块y坐标 | 整形 | | z | 返回方块z坐标 | 整形 | | down | 返回该方块下方的方块 | BlockContainerJS | | up | 返回该方块上方的方块 | BlockContainerJS | | north | 返回该方块北方的方块 | BlockContainerJS | | south | 返回该方块南方的方块 | BlockContainerJS | | west | 返回该方块西方的方块 | BlockContainerJS | | east | 返回该方块东方的方块 | BlockContainerJS | | blockState | 返回包含该方块状态的对象 | BlockState | | id | 返回方块ID | 字符串 | | light | 返回光照等级 | 整形 | | canSeeSky | 是否露天 | 布尔值 | | item | 返回方块对应的物品 | ItemStackJS | | playersInRadius | 返回范围内的玩家 | [EntityArrayList](https://kubejs.com/wiki/kubejs/EntityArrayList/) | | biomeId | 返回所处群系的ID | 字符串 | | properties | 返回带有当前方块所有属性的对象\[1] | 对象 | \[1]如`block.properties.facing`会返回当前方块朝向的字符串 ### 2、方法 | 方法 | 功能 | 返回值类型 | | ------------------------------------------------ | -------------------------- | -------------------- | | offset(Direction 方向, int 格数) | 返回在给定方向上指定格数以外的方块 | BlockContainerJS | | offset(Direction 方向) | 返回在给定方向上一格以外的方块 | BlockContainerJS | | offset(整形 x, 整形 y, 整形 z) | 返回以当前方块为坐标系原点,指定X,Y,Z以外的方块 | BlockContainerJS | | setBlockState(BlockState 状态值, int ) | 设置方块状态值 | void | | hasTag(tag) | 返回是否具有指定标签 | 布尔值 | | set(命名空间 方块ID, Map<字符串, 字符串> 方块属性, 整形 flags) | 在当前位置设置方块 | void | | set(命名空间 方块ID, Map<字符串, 字符串> 方块属性) | 在当前位置设置方块 | void | | set(命名空间 方块ID) | 在当前位置设置方块 | void | | createExplosion() | 新建爆炸事件(见下文) | ExplosionJS | | createEntity(命名空间ID) | 新建实体(见15.2) | EntityJS | | spawnLightning(布尔值 是否只有效果, nullable EntityJS 玩家) | 生成闪电 | void | | spawnLightning(布尔值 是否只有效果) | 生成闪电 | void | | spawnFireworks(FireworksJS 烟花) | 生成烟花(见下文) | void | | getInventory(Direction 方向) | 获取方块的库存 | nullable InventoryJS | | getPlayersInRadius(浮点型 范围) | 返回指定范围内的玩家列表 | EntityArrayList | ## 二、ExplosionJS 新建爆炸和新建实体类似,你需要先新建一个爆炸,然后设置属性,再在合适的时间生成它即可 | 函数 | 功能 | 返回值类型 | | --------------------------- | -------------- | ----------- | | exploder(EntityJS 实体) | 设定是谁引起的爆炸 | ExplosionJS | | strength(浮点型 爆炸强度) | 设定爆炸的强度 | ExplosionJS | | causesFire(布尔值 是否引火) | 设定爆炸是否会生成火 | ExplosionJS | | damagesTerrain(布尔值 是否破坏环境) | 设定爆炸是否破坏地形\[2] | ExplosionJS | | destroysTerrain(布尔值 是否破坏环境) | 设定爆炸是否破坏地形\[2] | ExplosionJS | | explode() | 炸! | void | \[2]damagesTerrain会破坏方块但是掉落方块,destroysTerrain会破坏方块且不掉落方块。若不设置默认为破坏且掉落方块。 一个简单的例子,在玩家右键方块时生成一个小爆炸 ``` onEvent('block.right_click', event => { // 新建爆炸 let explosion = event.block.createExplosion(); // 设置爆炸强度,默认值为3 explosion.strength(100.0); // 设置是否生成火,默认值为false explosion.causesFire(true); explosion.explode(); }) ``` (应该很小吧) ![](https://m1.miaomc.cn/uploads/20220416_f84d7444ffa57.jpg) --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/events2/worldjs-serverjs.md --- # 15.4 WorldJS 和 ServerJS *** ## 一、WorldJS WorldJS允许你获取、修改当前世界的一些信息,而ServerJS允许你操作当前服务器中的一些内容 ## 1、属性 | 属性 | 描述 | 返回值 | | ------------ | -------------- | --------------- | | side | 返回当前运行环境\[1] | ScriptType | | gameRules | 返回游戏规则效果内容\[2] | GameRulesJS | | server | 返回ServerJS | ServerJS | | time | 获取游戏总时间 | 长整型 | | localTime | 获取游戏日时间 | 长整型 | | dimension | 返回游戏维度名 | 字符串 | | overworld | 返回当前世界是否为主世界 | 布尔型 | | daytime | 返回当前世界是否为白天 | 布尔型 | | raining | 返回当前世界天气是否为雨天 | 布尔型 | | thundering | 返回当前世界天气是否为雷暴 | 布尔型 | | rainStrength | 返回当前世界下雨强度 | 浮点型 | | players | 返回当前世界玩家列表 | EntityArrayList | | entities | 返回当前世界实体列表 | EntityArrayList | \[1]:值可以为client/server \[2]:见本节末GameRulesJS ## 2、函数 | 函数 | 描述 | 返回值 | | ----------------------------------------------------------------------- | -------- | ---------------- | | getBlock(整形 x, 整形 y, 整形 z) | 返回指定坐标方块 | BlockContainerJS | | getBlock(BlockPos pos) | 返回指定坐标方块 | BlockContainerJS | | getBlock(BlockEntity blockEntity) | 返回指定坐标方块 | BlockContainerJS | | createExplosion(浮点型 x, 浮点型 y, 浮点型 z) | 新建爆炸 | ExplosionJS\[3] | | createEntity(命名空间id) | 新建实体 | EntityJS\[4] | | spawnLightning(浮点型 x, 浮点型 y, 浮点型 z, 布尔值 是否只有效果, nullable EntityJS 玩家对象) | 生成闪电 | void | | spawnLightning(浮点型 x, 浮点型 y, 浮点型 z, 布尔值 是否只有效果) | 生成闪电 | void | | spawnFireworks(浮点型 x, 浮点型 y, 浮点型 z, FireworksJS 烟花) | 生成烟花 | void | \[3]:见15.3 ExplosionJS 部分 \[4]:见15.2 EntityJS部分 ## 二、ServerJS ## 1、属性 | 属性 | 描述 | 返回值 | | ------------ | ----------------- | ----------------- | | overworld | 返回ServerWorldJS | ServerWorldJS\[5] | | worlds | 返回ServerWorldJS列表 | List | | running | 返回服务器是否正在运行 | 布尔值 | | hardcore | 返回服务器是否为极限模式 | 布尔值 | | singlePlayer | 返回是否为单人模式 | 布尔值 | | dedicated | 返回是否为"纯"服务器端(?) | 布尔值 | | motd | 返回服务器的motd | 字符串 | | players | 返回当前服务器玩家列表 | EntityArrayList | | entities | 返回当前服务器实体列表 | EntityArrayList | ## 2、函数 | 函数 | 描述 | 返回值 | | -------------------------------------- | --------------- | ----------------------- | | stop() | 停止当前服务器 | void | | getLevel(字符串 维度名称) | 返回给定维度 | WorldJS | | getPlayer(UUID uuid) | 返回给定UUID的玩家 | nullable ServerPlayerJS | | getPlayer(字符串 玩家名称) | 返回给定名称的玩家 | nullable ServerPlayerJS | | getEntities(字符串 过滤器\[6]) | 返回符合给定过滤器的实体列表 | EntityArrayList | | getAdvancement(命名空间id) | 返回给定成就 | AdvancementJS\[7] | | sendDataToAll(字符串 通道, nullable any 数据) | 向所有玩家发送数据\[8] | void | | setMotd(Component motd) | 设置服务器motd | void | | getName() | 返回服务器名称 | Text | | getDisplayName() | 返回服务器显示名称\[9] | Text | | tell(Component 消息) | 向服务器所有玩家发送消息 | void | | setStatusMessage(Component 消息) | 设置服务器所有玩家的状态栏消息 | void | \[5]:见本节末ServerWorldJS \[6]:形如"@e\[type=minecraft:clicken]"等 \[7]:见本节末AdvancementJS \[8]:见16 网络包 \[9]:这里指从服务器后台执行命令时括号中显示的“名称” ## 三、GameRulesJS | 函数 | 描述 | 返回值 | | ----------------------- | ---------- | ---- | | getString(字符串 游戏规则) | 返回指定游戏规则的值 | 字符串 | | getBoolean(字符串 游戏规则) | 返回指定游戏规则的值 | 布尔型 | | getInt(字符串 游戏规则) | 返回指定游戏规则的值 | 整形 | | set(字符串 游戏规则, Object 值) | 设置指定游戏规则的值 | void | ## 四、AdvancementJS | 属性 | 描述 | 返回值 | | ----------- | ------ | ---- | | displayText | 返回进度文本 | Text | | title | 返回进度标题 | Text | | description | 返回进度描述 | Text | | 函数 | 描述 | 返回值 | | ------------------------- | -------- | ---- | | addChild(AdvancementJS a) | 添加子进度 | void | | hasDisplay() | 该进度是否会显示 | 布尔值 | --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/events2/worldjs-serverjs.md --- # 15.4 WorldJS 和 ServerJS *** ## 一、WorldJS WorldJS允许你获取、修改当前世界的一些信息,而ServerJS允许你操作当前服务器中的一些内容 ## 1、属性 | 属性 | 描述 | 返回值 | | ------------ | -------------- | --------------- | | side | 返回当前运行环境\[1] | ScriptType | | gameRules | 返回游戏规则效果内容\[2] | GameRulesJS | | server | 返回ServerJS | ServerJS | | time | 获取游戏总时间 | 长整型 | | localTime | 获取游戏日时间 | 长整型 | | dimension | 返回游戏维度名 | 字符串 | | overworld | 返回当前世界是否为主世界 | 布尔型 | | daytime | 返回当前世界是否为白天 | 布尔型 | | raining | 返回当前世界天气是否为雨天 | 布尔型 | | thundering | 返回当前世界天气是否为雷暴 | 布尔型 | | rainStrength | 返回当前世界下雨强度 | 浮点型 | | players | 返回当前世界玩家列表 | EntityArrayList | | entities | 返回当前世界实体列表 | EntityArrayList | \[1]:值可以为client/server \[2]:见本节末GameRulesJS ## 2、函数 | 函数 | 描述 | 返回值 | | ----------------------------------------------------------------------- | -------- | ---------------- | | getBlock(整形 x, 整形 y, 整形 z) | 返回指定坐标方块 | BlockContainerJS | | getBlock(BlockPos pos) | 返回指定坐标方块 | BlockContainerJS | | getBlock(BlockEntity blockEntity) | 返回指定坐标方块 | BlockContainerJS | | createExplosion(浮点型 x, 浮点型 y, 浮点型 z) | 新建爆炸 | ExplosionJS\[3] | | createEntity(命名空间id) | 新建实体 | EntityJS\[4] | | spawnLightning(浮点型 x, 浮点型 y, 浮点型 z, 布尔值 是否只有效果, nullable EntityJS 玩家对象) | 生成闪电 | void | | spawnLightning(浮点型 x, 浮点型 y, 浮点型 z, 布尔值 是否只有效果) | 生成闪电 | void | | spawnFireworks(浮点型 x, 浮点型 y, 浮点型 z, FireworksJS 烟花) | 生成烟花 | void | \[3]:见15.3 ExplosionJS 部分 \[4]:见15.2 EntityJS部分 ## 二、ServerJS ## 1、属性 | 属性 | 描述 | 返回值 | | ------------ | ----------------- | ----------------- | | overworld | 返回ServerWorldJS | ServerWorldJS\[5] | | worlds | 返回ServerWorldJS列表 | List | | running | 返回服务器是否正在运行 | 布尔值 | | hardcore | 返回服务器是否为极限模式 | 布尔值 | | singlePlayer | 返回是否为单人模式 | 布尔值 | | dedicated | 返回是否为"纯"服务器端(?) | 布尔值 | | motd | 返回服务器的motd | 字符串 | | players | 返回当前服务器玩家列表 | EntityArrayList | | entities | 返回当前服务器实体列表 | EntityArrayList | ## 2、函数 | 函数 | 描述 | 返回值 | | -------------------------------------- | --------------- | ----------------------- | | stop() | 停止当前服务器 | void | | getLevel(字符串 维度名称) | 返回给定维度 | WorldJS | | getPlayer(UUID uuid) | 返回给定UUID的玩家 | nullable ServerPlayerJS | | getPlayer(字符串 玩家名称) | 返回给定名称的玩家 | nullable ServerPlayerJS | | getEntities(字符串 过滤器\[6]) | 返回符合给定过滤器的实体列表 | EntityArrayList | | getAdvancement(命名空间id) | 返回给定成就 | AdvancementJS\[7] | | sendDataToAll(字符串 通道, nullable any 数据) | 向所有玩家发送数据\[8] | void | | setMotd(Component motd) | 设置服务器motd | void | | getName() | 返回服务器名称 | Text | | getDisplayName() | 返回服务器显示名称\[9] | Text | | tell(Component 消息) | 向服务器所有玩家发送消息 | void | | setStatusMessage(Component 消息) | 设置服务器所有玩家的状态栏消息 | void | \[5]:见本节末ServerWorldJS \[6]:形如"@e\[type=minecraft:clicken]"等 \[7]:见本节末AdvancementJS \[8]:见16 网络包 \[9]:这里指从服务器后台执行命令时括号中显示的“名称” ## 三、GameRulesJS | 函数 | 描述 | 返回值 | | ----------------------- | ---------- | ---- | | getString(字符串 游戏规则) | 返回指定游戏规则的值 | 字符串 | | getBoolean(字符串 游戏规则) | 返回指定游戏规则的值 | 布尔型 | | getInt(字符串 游戏规则) | 返回指定游戏规则的值 | 整形 | | set(字符串 游戏规则, Object 值) | 设置指定游戏规则的值 | void | ## 四、AdvancementJS | 属性 | 描述 | 返回值 | | ----------- | ------ | ---- | | displayText | 返回进度文本 | Text | | title | 返回进度标题 | Text | | description | 返回进度描述 | Text | | 函数 | 描述 | 返回值 | | ------------------------- | -------- | ---- | | addChild(AdvancementJS a) | 添加子进度 | void | | hasDisplay() | 该进度是否会显示 | 布尔值 | --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/events2/examples.md --- # 15.5 本章例子 *** ## 一、使玩家在潜行状态下无法使用指定物品 第一块是对着空气使用物品,第二块是对着方块使用物品 ``` onEvent('item.right_click', event => { if(event.player.crouching == true && event.item.id == "minecraft:diamond"){ event.cancel();// 取消该事件 } }) onEvent('block.right_click', event => { if(event.player.crouching == true && event.item.id == "minecraft:diamond"){ event.cancel();// 取消该事件 } }) ``` ## 二、随机传送 ``` // 最小及最大坐标 const minX = 1000; const minZ = 1000; const maxX = 10000; const maxZ = 10000; const minY = 100; const maxY = 200; // 是否随机取负值 const reverseNumber = true; // 返回随机数 function getNumber(min,max){ let randNumber = Math.floor(Math.random() * (max - min + 1) ) + min; if(Math.random() > 0.5 && reverseNumber){ randNumber = -randNumber; } return randNumber; } // 监听聊天事件,如果内容为 "!rtp" 就随机传送并给予缓降效果 onEvent('player.chat',function (event){ let input = event.message.trim(); if(input == "!rtp"){ event.player.setPosition(getNumber(minX,maxX), Math.abs(getNumber(minY,maxY)), getNumber(minZ,maxZ));// 设置坐标 event.player.statusMessage = `已将你传送到 x: ${event.player.x} y: ${event.player.y} z: ${event.player.z}`;// 玩家状态栏显示文字 event.player.potionEffects.add('minecraft:slow_falling', 600, 10, false, false);// 给予缓降效果 /** * EntityPotionEffectsJS支持以下函数 * .clear() 清除药水效果 * .getActive() 返回具有的效果 * .isActive(MobEffect 效果) 返回是否具有给定效果 * .getDuration(MobEffect 效果) 返回给定效果剩余时间 * .add(MobEffect 效果, 整形 持续时间(tick), 整形 等级, 布尔值 是否为信标产生, 布尔值 是否显示粒子) * 其中后三项,后两项,后一项均可省略不写,默认值对应为0,false, true. * .isApplicable(MobEffect 效果) 返回给定效果是否能应用于当前实体上 */ event.cancel();// 取消该事件,也就是说玩家发送的消息不会显示在其他玩家的公屏上 } }) /** * 另一种写法:监听物品右键事件,如果物品id为钻石则消耗一个钻石随机传送并冷却100秒 * 此处钻石可以替换为你想使用的物品 * 注:重进世界会刷新冷却时间 */ onEvent('item.right_click',function (event){ if(event.player.mainHandItem.id == "minecraft:diamond"){ event.player.setPosition(getNumber(minX,maxX), Math.abs(getNumber(minY,maxY)), getNumber(minZ,maxZ)); event.player.statusMessage = `已将你传送到 x: ${event.player.x} y: ${event.player.y} z: ${event.player.z}`; event.player.potionEffects.add('minecraft:slow_falling', 600, 10, false, false); // 钻石数量减一 event.item.count--; event.player.addItemCooldown('minecraft:diamond', 2000); } }) ``` ![](https://m1.miaomc.cn/uploads/20220408_c412408992599.gif) ## 三、爆炸“法杖” 这里的烈焰棒只是一个"占位符",你可以换成你想使用的任何物品 ``` // 绘制粒子函数 function drawParticle(event, intensity, particle) { let start = event.getPlayer() let target = event.player.rayTrace(1000) if(target == null){ return false; } let rayx = target.block.x; let rayy = target.block.y let rayz = target.block.z; let d = getDistance(start.x, start.y + 1, start.z, rayx, rayy, rayz) for (let i = -1; i < d*intensity; i++) { let delta = i / d let x = (1 - delta) * start.x + delta * (rayx + 0.5) let y = (1 - delta) * start.y + delta * (rayy + 0.5) let z = (1 - delta) * start.z + delta * (rayz + 0.5) event.server.runCommandSilent(`/particle ${particle} ${x} ${y} ${z} 0.1 0.1 0.1 0.01 5 normal`) } return true; } // 生成爆炸(使用火球实体来实现爆炸) onEvent("item.right_click", event => { if(event.player.rayTrace(1000).block != null && event.item.id == "minecraft:blaze_rod"){ let entity = event.world.createEntity('minecraft:fireball'); // 合并NBT设置爆炸等级 entity.mergeFullNBT('{ExplosionPower:2}') entity.setPosition(event.player.rayTrace(1000).block.x, event.player.rayTrace(1000).block.y + 1, event.player.rayTrace(1000).block.z); entity.setMotion(0, -20, 0); entity.spawn(); drawParticle(event,0.5,"minecraft:spit"); // 添加物品冷却 event.player.addItemCooldown(event.item.id, 1000); } }) // 生成爆炸(使用ExplosionJS) onEvent("item.right_click", event => { if(event.player.rayTrace(1000).block != null && event.item.id == "minecraft:blaze_rod"){ let explosion = event.player.rayTrace(1000).block.createExplosion(); // 设置引爆者 explosion.exploder(event.player); // 设置其他属性 explosion.strength(2.0); explosion.causesFire(true); // 引爆 explosion.explode(); // 绘制粒子 drawParticle(event,0.5,"minecraft:spit"); // 添加物品冷却 event.player.addItemCooldown(event.item.id, 1000); } }) ``` ![](https://m1.miaomc.cn/uploads/20220416_2f6b7441fc14d.gif) ## 四、扫地机器人 ``` // 物品白名单 const whitelist = Ingredient.matchAny([ 'minecraft:diamond', //单个物品 'minecraft:gold_ingot',//单个物品 '@tinkersconstruct', //mod物品示例 'minecraft:emerald' ]) // 执行一次间隔(注:该值必须大于1) const minutes = 30; // ====================================== var lastResult = []; var totalResult = []; var lastItemCount = 0; var totalItemCount = 0; function clearLag(server){ lastResult = []; lastItemCount = 0; server.getEntities("@e[type=item]").forEach(entity => { if (!whitelist.test(entity.item.id)){ lastResult.push([entity.item.id, entity.item.count]); totalResult.push([entity.item.id, entity.item.count]); lastItemCount += entity.item.count; entity.kill(); } }); lastResult.sort(); totalResult.sort(); server.tell([Text.lightPurple('[扫地机器人]'), `本次共清除 ${lastItemCount} 个物品`]); server.tell([Text.lightPurple('[扫地机器人]'), "在聊天框中输入 !clearLag last 来获取本次详细信息"]); server.tell([Text.lightPurple('[扫地机器人]'), "在聊天框中输入 !clearLag total 来获取全部详细信息"]); } function countResult(result,event){ if(result != []){ result.forEach((singleResult, index) => { event.server.tell([Text.lightPurple(`第 ${index + 1} 项 `), `${singleResult[0]} , 个数为 ${singleResult[1]}`]) }) } } onEvent('server.load', function (event) { event.server.scheduleInTicks(100, event.server, function (callback0) { callback0.data.tell([Text.lightPurple('[扫地机器人]'), `加载成功,使用 !clearlag help 查看帮助`]); }) event.server.schedule((minutes - 1) * MINUTE, event.server, function (callback1) { callback1.data.tell([Text.lightPurple('[扫地机器人]'), "1分钟后将清理地面掉落物"]); callback1.data.schedule(MINUTE, callback1.data, function(callback2) { clearLag(callback2.data); }) callback1.reschedule(); }) }) // 聊天事件,只有有OP权限的文件才能执行查询/扫地 onEvent('player.chat',function (event){ let input = event.message.trim(); switch (input) { case "!clearlag last": if (event.player.op){// 判断玩家权限 countResult(lastResult,event); }else{ event.player.tell([Text.lightPurple('[扫地机器人]'), "你没有权限这样做"]); } break; case "!clearlag total": if (event.player.op){ countResult(totalResult,event); }else{ event.player.tell([Text.lightPurple('[扫地机器人]'), "你没有权限这样做"]); } break; case "!clearlag help": event.player.tell([Text.lightPurple('[扫地机器人]'), "扫地机器人 by Wudji@mcbbbs.net. Powered by KubeJS"]); event.player.tell([Text.lightPurple('[扫地机器人]'), "在聊天框中输入 !clearLag last 来获取本次详细信息"]); event.player.tell([Text.lightPurple('[扫地机器人]'), "在聊天框中输入 !clearLag total 来获取全部详细信息"]); event.player.tell([Text.lightPurple('[扫地机器人]'), "在聊天框中输入 !clearLag 立即清除掉落物"]); break; case "!clearlag": if (event.player.op){ clearLag(event.server); }else{ event.player.tell([Text.lightPurple('[扫地机器人]'), "你没有权限这样做"]); } break; } }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/events2/examples.md --- # 15.5 本章例子 *** ## 一、使玩家在潜行状态下无法使用指定物品 第一块是对着空气使用物品,第二块是对着方块使用物品 ``` onEvent('item.right_click', event => { if(event.player.crouching == true && event.item.id == "minecraft:diamond"){ event.cancel();// 取消该事件 } }) onEvent('block.right_click', event => { if(event.player.crouching == true && event.item.id == "minecraft:diamond"){ event.cancel();// 取消该事件 } }) ``` ## 二、随机传送 ``` // 最小及最大坐标 const minX = 1000; const minZ = 1000; const maxX = 10000; const maxZ = 10000; const minY = 100; const maxY = 200; // 是否随机取负值 const reverseNumber = true; // 返回随机数 function getNumber(min,max){ let randNumber = Math.floor(Math.random() * (max - min + 1) ) + min; if(Math.random() > 0.5 && reverseNumber){ randNumber = -randNumber; } return randNumber; } // 监听聊天事件,如果内容为 "!rtp" 就随机传送并给予缓降效果 onEvent('player.chat',function (event){ let input = event.message.trim(); if(input == "!rtp"){ event.player.setPosition(getNumber(minX,maxX), Math.abs(getNumber(minY,maxY)), getNumber(minZ,maxZ));// 设置坐标 event.player.statusMessage = `已将你传送到 x: ${event.player.x} y: ${event.player.y} z: ${event.player.z}`;// 玩家状态栏显示文字 event.player.potionEffects.add('minecraft:slow_falling', 600, 10, false, false);// 给予缓降效果 /** * EntityPotionEffectsJS支持以下函数 * .clear() 清除药水效果 * .getActive() 返回具有的效果 * .isActive(MobEffect 效果) 返回是否具有给定效果 * .getDuration(MobEffect 效果) 返回给定效果剩余时间 * .add(MobEffect 效果, 整形 持续时间(tick), 整形 等级, 布尔值 是否为信标产生, 布尔值 是否显示粒子) * 其中后三项,后两项,后一项均可省略不写,默认值对应为0,false, true. * .isApplicable(MobEffect 效果) 返回给定效果是否能应用于当前实体上 */ event.cancel();// 取消该事件,也就是说玩家发送的消息不会显示在其他玩家的公屏上 } }) /** * 另一种写法:监听物品右键事件,如果物品id为钻石则消耗一个钻石随机传送并冷却100秒 * 此处钻石可以替换为你想使用的物品 * 注:重进世界会刷新冷却时间 */ onEvent('item.right_click',function (event){ if(event.player.mainHandItem.id == "minecraft:diamond"){ event.player.setPosition(getNumber(minX,maxX), Math.abs(getNumber(minY,maxY)), getNumber(minZ,maxZ)); event.player.statusMessage = `已将你传送到 x: ${event.player.x} y: ${event.player.y} z: ${event.player.z}`; event.player.potionEffects.add('minecraft:slow_falling', 600, 10, false, false); // 钻石数量减一 event.item.count--; event.player.addItemCooldown('minecraft:diamond', 2000); } }) ``` ![](https://m1.miaomc.cn/uploads/20220408_c412408992599.gif) ## 三、爆炸“法杖” 这里的烈焰棒只是一个"占位符",你可以换成你想使用的任何物品 ``` // 绘制粒子函数 function drawParticle(event, intensity, particle) { let start = event.getPlayer() let target = event.player.rayTrace(1000) if(target == null){ return false; } let rayx = target.block.x; let rayy = target.block.y let rayz = target.block.z; let d = getDistance(start.x, start.y + 1, start.z, rayx, rayy, rayz) for (let i = -1; i < d*intensity; i++) { let delta = i / d let x = (1 - delta) * start.x + delta * (rayx + 0.5) let y = (1 - delta) * start.y + delta * (rayy + 0.5) let z = (1 - delta) * start.z + delta * (rayz + 0.5) event.server.runCommandSilent(`/particle ${particle} ${x} ${y} ${z} 0.1 0.1 0.1 0.01 5 normal`) } return true; } // 生成爆炸(使用火球实体来实现爆炸) onEvent("item.right_click", event => { if(event.player.rayTrace(1000).block != null && event.item.id == "minecraft:blaze_rod"){ let entity = event.world.createEntity('minecraft:fireball'); // 合并NBT设置爆炸等级 entity.mergeFullNBT('{ExplosionPower:2}') entity.setPosition(event.player.rayTrace(1000).block.x, event.player.rayTrace(1000).block.y + 1, event.player.rayTrace(1000).block.z); entity.setMotion(0, -20, 0); entity.spawn(); drawParticle(event,0.5,"minecraft:spit"); // 添加物品冷却 event.player.addItemCooldown(event.item.id, 1000); } }) // 生成爆炸(使用ExplosionJS) onEvent("item.right_click", event => { if(event.player.rayTrace(1000).block != null && event.item.id == "minecraft:blaze_rod"){ let explosion = event.player.rayTrace(1000).block.createExplosion(); // 设置引爆者 explosion.exploder(event.player); // 设置其他属性 explosion.strength(2.0); explosion.causesFire(true); // 引爆 explosion.explode(); // 绘制粒子 drawParticle(event,0.5,"minecraft:spit"); // 添加物品冷却 event.player.addItemCooldown(event.item.id, 1000); } }) ``` ![](https://m1.miaomc.cn/uploads/20220416_2f6b7441fc14d.gif) ## 四、扫地机器人 ``` // 物品白名单 const whitelist = Ingredient.matchAny([ 'minecraft:diamond', //单个物品 'minecraft:gold_ingot',//单个物品 '@tinkersconstruct', //mod物品示例 'minecraft:emerald' ]) // 执行一次间隔(注:该值必须大于1) const minutes = 30; // ====================================== var lastResult = []; var totalResult = []; var lastItemCount = 0; var totalItemCount = 0; function clearLag(server){ lastResult = []; lastItemCount = 0; server.getEntities("@e[type=item]").forEach(entity => { if (!whitelist.test(entity.item.id)){ lastResult.push([entity.item.id, entity.item.count]); totalResult.push([entity.item.id, entity.item.count]); lastItemCount += entity.item.count; entity.kill(); } }); lastResult.sort(); totalResult.sort(); server.tell([Text.lightPurple('[扫地机器人]'), `本次共清除 ${lastItemCount} 个物品`]); server.tell([Text.lightPurple('[扫地机器人]'), "在聊天框中输入 !clearLag last 来获取本次详细信息"]); server.tell([Text.lightPurple('[扫地机器人]'), "在聊天框中输入 !clearLag total 来获取全部详细信息"]); } function countResult(result,event){ if(result != []){ result.forEach((singleResult, index) => { event.server.tell([Text.lightPurple(`第 ${index + 1} 项 `), `${singleResult[0]} , 个数为 ${singleResult[1]}`]) }) } } onEvent('server.load', function (event) { event.server.scheduleInTicks(100, event.server, function (callback0) { callback0.data.tell([Text.lightPurple('[扫地机器人]'), `加载成功,使用 !clearlag help 查看帮助`]); }) event.server.schedule((minutes - 1) * MINUTE, event.server, function (callback1) { callback1.data.tell([Text.lightPurple('[扫地机器人]'), "1分钟后将清理地面掉落物"]); callback1.data.schedule(MINUTE, callback1.data, function(callback2) { clearLag(callback2.data); }) callback1.reschedule(); }) }) // 聊天事件,只有有OP权限的文件才能执行查询/扫地 onEvent('player.chat',function (event){ let input = event.message.trim(); switch (input) { case "!clearlag last": if (event.player.op){// 判断玩家权限 countResult(lastResult,event); }else{ event.player.tell([Text.lightPurple('[扫地机器人]'), "你没有权限这样做"]); } break; case "!clearlag total": if (event.player.op){ countResult(totalResult,event); }else{ event.player.tell([Text.lightPurple('[扫地机器人]'), "你没有权限这样做"]); } break; case "!clearlag help": event.player.tell([Text.lightPurple('[扫地机器人]'), "扫地机器人 by Wudji@mcbbbs.net. Powered by KubeJS"]); event.player.tell([Text.lightPurple('[扫地机器人]'), "在聊天框中输入 !clearLag last 来获取本次详细信息"]); event.player.tell([Text.lightPurple('[扫地机器人]'), "在聊天框中输入 !clearLag total 来获取全部详细信息"]); event.player.tell([Text.lightPurple('[扫地机器人]'), "在聊天框中输入 !clearLag 立即清除掉落物"]); break; case "!clearlag": if (event.player.op){ clearLag(event.server); }else{ event.player.tell([Text.lightPurple('[扫地机器人]'), "你没有权限这样做"]); } break; } }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/events2/tests.md --- # 15.6 测试用例 **一、防止玩家开创造** ``` onEvent("player.tick", event => {// 监听player.tick事件 if(event.player.creativeMode || event.player.creativeMode.spectator){// 判断玩家游戏模式(创造或观察者) // 上述判断通过即通过服务器静默执行指令将玩家调为生存模式 event.server.runCommandSilent(`gamemode survival ${event.player.name}`); } }) ``` **二、玩家佩戴下界合金头盔时给予缓慢效果** ``` onEvent("player.tick", event => {// 监听player.tick事件 if(event.player.headArmorItem.id == "minecraft:netherite_helmet"){// 判断玩家头盔是不是下界合金头盔 // 上述判断通过即给予玩家1 tick的缓慢I效果 event.player.add("minecraft:slowness", 1, 1, false, false); } }) ``` **三、禁止非创造玩家捡起基岩** ``` onEvent("item.pickup", event => {// 监听item.pickup事件 if(event.item.id == "minecraft:bedrock" && !event.player.creativeMode){// 判断物品是不是基岩 // 判断通过则取消事件防止被捡起 event.cancel(); } }) ``` **四、防止烟花多次触发** ``` onEvent("item.right_click", event => {// 监听item.right_click事件 if(event.item.id == "minecraft:firework_rocket" && event.player.onGround){// 判断右键点击的物品是不是烟花且玩家在地面上 event.player.addItemCooldown("minecraft:firework_rocket", 200); } }) ``` **五、消耗铁加速玩家鞘翅飞行** ``` onEvent("item.right_click", event => {// 监听item.right_click事件 if(event.item.id == "minecraft:iron" && !event.player.onGround && event.player.chestArmorItem.id == "minecraft:elytra"){// 判断物品是不是钻石且玩家不在地面上且玩家穿着鞘翅 // 消耗一个铁加速玩家鞘翅飞行 event.player.boostElytraFlight(); event.item.count--; event.player.addItemCooldown("minecraft:iron", 60); } }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/events2/tests.md --- # 15.6 测试用例 **一、防止玩家开创造** ``` onEvent("player.tick", event => {// 监听player.tick事件 if(event.player.creativeMode || event.player.creativeMode.spectator){// 判断玩家游戏模式(创造或观察者) // 上述判断通过即通过服务器静默执行指令将玩家调为生存模式 event.server.runCommandSilent(`gamemode survival ${event.player.name}`); } }) ``` **二、玩家佩戴下界合金头盔时给予缓慢效果** ``` onEvent("player.tick", event => {// 监听player.tick事件 if(event.player.headArmorItem.id == "minecraft:netherite_helmet"){// 判断玩家头盔是不是下界合金头盔 // 上述判断通过即给予玩家1 tick的缓慢I效果 event.player.add("minecraft:slowness", 1, 1, false, false); } }) ``` **三、禁止非创造玩家捡起基岩** ``` onEvent("item.pickup", event => {// 监听item.pickup事件 if(event.item.id == "minecraft:bedrock" && !event.player.creativeMode){// 判断物品是不是基岩 // 判断通过则取消事件防止被捡起 event.cancel(); } }) ``` **四、防止烟花多次触发** ``` onEvent("item.right_click", event => {// 监听item.right_click事件 if(event.item.id == "minecraft:firework_rocket" && event.player.onGround){// 判断右键点击的物品是不是烟花且玩家在地面上 event.player.addItemCooldown("minecraft:firework_rocket", 200); } }) ``` **五、消耗铁加速玩家鞘翅飞行** ``` onEvent("item.right_click", event => {// 监听item.right_click事件 if(event.item.id == "minecraft:iron" && !event.player.onGround && event.player.chestArmorItem.id == "minecraft:elytra"){// 判断物品是不是钻石且玩家不在地面上且玩家穿着鞘翅 // 消耗一个铁加速玩家鞘翅飞行 event.player.boostElytraFlight(); event.item.count--; event.player.addItemCooldown("minecraft:iron", 60); } }) ``` --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_16_LootTable.md description: 16 战利品表 相关教程 --- # 战利品表 战利品表是指你破坏了方块或者打死了实体的时候掉落的东西,也包含了奖励箱的内容。 他也是通过json文件描述的。这里我们简单的介绍简单的方块破坏后掉落方块本身,更加复杂的内容和复杂的掉落物可以看原版的json 对于不知道的字段的意思可以去看[wiki](https://minecraft.fandom.com/zh/wiki/%E6%A0%87%E7%AD%BE) 创建如下的目录 ``` ├─assets │ └─examplemod └─data ├─examplemod │ ├─advancements │ ├─loot_tables │ │ └─blocks ``` 对于方块的掉落物的战利品表应该放在blocks下面,其他的可以去看原版的结构或者wiki 这里添加一个ruby\_ore.json ```json { "type": "minecraft:block", "pools": [ { "bonus_rolls": 0.0, "conditions": [ { "condition": "minecraft:survives_explosion" } ], "entries": [ { "type": "minecraft:item", "name": "examplemod:ruby_ore" } ], "rolls": 1.0 } ], "random_sequence": "examplemod:blocks/ruby_ore" } ``` * "type: 这个字段指定了这个战利品表的类型。在这个例子中,类型是minecraft:block,这意味着这是一个方块破坏时的战利品表。 * pools: 这个字段包含了一个或多个战利品池,每个池都可以定义不同的掉落物品和条件。在这个例子中,只有一个战利品池。 * bonus\_rolls: 这个字段定义了额外的战利品掉落次数。在这个例子中,它被设置为0.0,意味着没有额外的掉落次数。 * "conditions: 这个字段定义了必须满足的条件,才能从这个战利品池中掉落物品。在这个例子中,只有一个条件 * condition: 这个字段定义了具体的条件。在这个例子中,条件是 * minecraft:survives\_explosion,这意味着只有当方块在爆炸中幸存下来时,才会掉落物品. * entries: 这个字段定义了战利品池中的条目,也就是可以掉落的物品。在这个例子中,只有一个条目: * type: 这个字段指定了条目的类型。在这个例子中,类型是minecraft:item,意味着这是一个物品。 * name: 这个字段定义了条目的名称。在这个例子中,名称是examplemod:ruby\_ore,这意味着当Ruby矿石被破坏时,会掉落一个Ruby矿石物品。 * rolls: 这个字段定义了从战利品池中掉落的次数。在这个例子中,它被设置为1.0,意味着每次破坏Ruby矿石时,都会掉落一个Ruby矿石物品。 更加详细的解释内容见[wiki](https://minecraft.fandom.com/zh/wiki/%E6%A0%87%E7%AD%BE) ## 数据生成 你可以尝试自己手写这样的内容,不过也是比较麻烦的,很可能会写错,datagenerator提供了一种生成的方式。 我们需要创建一个自己的LootTableProvider,和自己的方块的BlockLootSubProvider 对于LootTableProvider,这是一个自定义的战利品表提供者,我们需要给他提供子提供者列表 ```java public class ModLootTableProvider extends LootTableProvider { public ModLootTableProvider(PackOutput pOutput, Set pRequiredTables, List pSubProviders) { super(pOutput, pRequiredTables, pSubProviders); } } ``` 对于ModBlockLootProvider。 ```java public class ModBlockLootProvider extends BlockLootSubProvider { public static final Set BLOCK = Set.of( ModBlocks.RUBY_ORE.get() ); public ModBlockLootProvider() { super(Collections.emptySet(), FeatureFlags.REGISTRY.allFlags()); } @Override protected void generate() { this.dropSelf(ModBlocks.RUBY_ORE.get()); } @Override protected Iterable getKnownBlocks() { return BLOCK; } } ``` this.dropSelf(ModBlocks.RUBY\_ORE.get()):这行代码调用dropSelf方法,指定当Ruby矿石方块被破坏时,它应该掉落自身。 然而generate的调用会检查getKnownBlocks方法返回的方块是否都有了对应的掉落物表,如果没有就会报错,所以这里我们要重写这个getKnownBlocks方法。返回我们要求生成战利品表的方块。 super(Collections.emptySet(), FeatureFlags.REGISTRY.allFlags());方法中的第一个参数是一个空的集合,该集合是指免疫爆炸的方块列表,这里传入的是一个空。第二个参数照这样写即可。 使用 ```java event.getGenerator().addProvider( event.includeServer(), (DataProvider.Factory)pOutput -> new ModLootTableProvider(pOutput, Collections.emptySet(), List.of( new LootTableProvider.SubProviderEntry(ModBlockLootProvider::new, LootContextParamSets.BLOCK) )) ); ``` 构造函数的第二个参数是指那些需要生成战利品表的特色的名字,或者填空。 之后调用rundata的task即可。 ## 原版的类 原版的LootTableProvider,包含了方块破坏掉落,实体击杀掉落,钓鱼掉落,宝箱的内容等等。 ```java public class VanillaLootTableProvider { public static LootTableProvider create(PackOutput pOutput) { return new LootTableProvider( pOutput, BuiltInLootTables.all(), List.of( new LootTableProvider.SubProviderEntry(VanillaFishingLoot::new, LootContextParamSets.FISHING), new LootTableProvider.SubProviderEntry(VanillaChestLoot::new, LootContextParamSets.CHEST), new LootTableProvider.SubProviderEntry(VanillaEntityLoot::new, LootContextParamSets.ENTITY), new LootTableProvider.SubProviderEntry(VanillaBlockLoot::new, LootContextParamSets.BLOCK), new LootTableProvider.SubProviderEntry(VanillaPiglinBarterLoot::new, LootContextParamSets.PIGLIN_BARTER), new LootTableProvider.SubProviderEntry(VanillaGiftLoot::new, LootContextParamSets.GIFT), new LootTableProvider.SubProviderEntry(VanillaArchaeologyLoot::new, LootContextParamSets.ARCHAEOLOGY) ) ); } } ``` 原版的VanillaBlockLoot 这包含了原版的方块的破坏掉落的战利品表。 其他的生成请阅读原版的内容 ## 拓展阅读 [neoforge 文档](https://docs.neoforged.net/docs/datagen/server/loottables) --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_16_LootTable.md description: 16 战利品表 相关教程 --- # 战利品表 战利品表是指你破坏了方块或者打死了实体的时候掉落的东西,也包含了奖励箱的内容。 他也是通过json文件描述的。这里我们简单的介绍简单的方块破坏后掉落方块本身,更加复杂的内容和复杂的掉落物可以看原版的json 对于不知道的字段的意思可以去看[wiki](https://minecraft.fandom.com/zh/wiki/%E6%A0%87%E7%AD%BE) 创建如下的目录 ``` ├─assets │ └─examplemod └─data ├─examplemod │ ├─advancements │ ├─loot_tables │ │ └─blocks ``` 对于方块的掉落物的战利品表应该放在blocks下面,其他的可以去看原版的结构或者wiki 这里添加一个ruby\_ore.json ```json { "type": "minecraft:block", "pools": [ { "bonus_rolls": 0.0, "conditions": [ { "condition": "minecraft:survives_explosion" } ], "entries": [ { "type": "minecraft:item", "name": "examplemod:ruby_ore" } ], "rolls": 1.0 } ], "random_sequence": "examplemod:blocks/ruby_ore" } ``` * "type: 这个字段指定了这个战利品表的类型。在这个例子中,类型是minecraft:block,这意味着这是一个方块破坏时的战利品表。 * pools: 这个字段包含了一个或多个战利品池,每个池都可以定义不同的掉落物品和条件。在这个例子中,只有一个战利品池。 * bonus\_rolls: 这个字段定义了额外的战利品掉落次数。在这个例子中,它被设置为0.0,意味着没有额外的掉落次数。 * "conditions: 这个字段定义了必须满足的条件,才能从这个战利品池中掉落物品。在这个例子中,只有一个条件 * condition: 这个字段定义了具体的条件。在这个例子中,条件是 * minecraft:survives\_explosion,这意味着只有当方块在爆炸中幸存下来时,才会掉落物品. * entries: 这个字段定义了战利品池中的条目,也就是可以掉落的物品。在这个例子中,只有一个条目: * type: 这个字段指定了条目的类型。在这个例子中,类型是minecraft:item,意味着这是一个物品。 * name: 这个字段定义了条目的名称。在这个例子中,名称是examplemod:ruby\_ore,这意味着当Ruby矿石被破坏时,会掉落一个Ruby矿石物品。 * rolls: 这个字段定义了从战利品池中掉落的次数。在这个例子中,它被设置为1.0,意味着每次破坏Ruby矿石时,都会掉落一个Ruby矿石物品。 更加详细的解释内容见[wiki](https://minecraft.fandom.com/zh/wiki/%E6%A0%87%E7%AD%BE) ## 数据生成 你可以尝试自己手写这样的内容,不过也是比较麻烦的,很可能会写错,datagenerator提供了一种生成的方式。 我们需要创建一个自己的LootTableProvider,和自己的方块的BlockLootSubProvider 对于LootTableProvider,这是一个自定义的战利品表提供者,我们需要给他提供子提供者列表 ```java public class ModLootTableProvider extends LootTableProvider { public ModLootTableProvider(PackOutput pOutput, Set pRequiredTables, List pSubProviders) { super(pOutput, pRequiredTables, pSubProviders); } } ``` 对于ModBlockLootProvider。 ```java public class ModBlockLootProvider extends BlockLootSubProvider { public static final Set BLOCK = Set.of( ModBlocks.RUBY_ORE.get() ); public ModBlockLootProvider() { super(Collections.emptySet(), FeatureFlags.REGISTRY.allFlags()); } @Override protected void generate() { this.dropSelf(ModBlocks.RUBY_ORE.get()); } @Override protected Iterable getKnownBlocks() { return BLOCK; } } ``` this.dropSelf(ModBlocks.RUBY\_ORE.get()):这行代码调用dropSelf方法,指定当Ruby矿石方块被破坏时,它应该掉落自身。 然而generate的调用会检查getKnownBlocks方法返回的方块是否都有了对应的掉落物表,如果没有就会报错,所以这里我们要重写这个getKnownBlocks方法。返回我们要求生成战利品表的方块。 super(Collections.emptySet(), FeatureFlags.REGISTRY.allFlags());方法中的第一个参数是一个空的集合,该集合是指免疫爆炸的方块列表,这里传入的是一个空。第二个参数照这样写即可。 使用 ```java event.getGenerator().addProvider( event.includeServer(), (DataProvider.Factory)pOutput -> new ModLootTableProvider(pOutput, Collections.emptySet(), List.of( new LootTableProvider.SubProviderEntry(ModBlockLootProvider::new, LootContextParamSets.BLOCK) )) ); ``` 构造函数的第二个参数是指那些需要生成战利品表的特色的名字,或者填空。 之后调用rundata的task即可。 ## 原版的类 原版的LootTableProvider,包含了方块破坏掉落,实体击杀掉落,钓鱼掉落,宝箱的内容等等。 ```java public class VanillaLootTableProvider { public static LootTableProvider create(PackOutput pOutput) { return new LootTableProvider( pOutput, BuiltInLootTables.all(), List.of( new LootTableProvider.SubProviderEntry(VanillaFishingLoot::new, LootContextParamSets.FISHING), new LootTableProvider.SubProviderEntry(VanillaChestLoot::new, LootContextParamSets.CHEST), new LootTableProvider.SubProviderEntry(VanillaEntityLoot::new, LootContextParamSets.ENTITY), new LootTableProvider.SubProviderEntry(VanillaBlockLoot::new, LootContextParamSets.BLOCK), new LootTableProvider.SubProviderEntry(VanillaPiglinBarterLoot::new, LootContextParamSets.PIGLIN_BARTER), new LootTableProvider.SubProviderEntry(VanillaGiftLoot::new, LootContextParamSets.GIFT), new LootTableProvider.SubProviderEntry(VanillaArchaeologyLoot::new, LootContextParamSets.ARCHAEOLOGY) ) ); } } ``` 原版的VanillaBlockLoot 这包含了原版的方块的破坏掉落的战利品表。 其他的生成请阅读原版的内容 ## 拓展阅读 [neoforge 文档](https://docs.neoforged.net/docs/datagen/server/loottables) --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/others/network-painterapi.md --- # 16 网络包和Painter API *** ## 一、网络包 事件: 客户端从服务端侧接收脚本`player.data_from_server.<信道ID>` 服务端从客户端侧接收脚本`player.data_from_server.<信道ID>` ``` // 监听玩家物品右键事件 // 你可以把下面这段脚本放到服务端或客户端脚本中,取决于你想从哪一侧发包 onEvent('item.right_click', event => { // 监听物品右键事件 if (event.server) { // 发送 {test: 123} 到信道 "test_channel_1". 信道ID可以为任何字符串, 但还是推荐使用蛇形命名方法 // 接收端为客户端 (发送端为服务端). event.player.sendData('test_channel_1', { test: 123 });// 发包 } else { // 推荐使用不同信道 尽管这不是必须的 // 收包 (发送端为客户端). event.player.sendData('test_channel_2', { test: 456 })// 发包 } }) // 监听从服务端收到网络包时的事件 // 这段是客户端脚本 onEvent('player.data_from_server.test_channel_1', event => { log.info(event.data.test) // 打印网络包信息到日志(123) event.player.tell(`来自客户端的网络包信息:${event.data.test}`) }) // 监听从客户端收到网络包时的事件 // 这段是服务端脚本 onEvent('player.data_from_client.test_channel_2', event => { log.info(event.data.test) // 打印网络包信息到日志(456) event.server.runCommand(`say 来自服务端的网络包信息:${event.data.test}`) }) ``` ## 二、Painter API Painter API允许你在客户端屏幕上绘制内容,其可以从服务端控制也可以从客户端直接触发 目前(2022年5月10日)它不支持玩家输入/交互,但以后可能会支持游戏内菜单甚至是渲染引擎 Painter对象是以NBT/Json对象建立的,并且每一个都有自己的ID. 如果你没有提供一个的话,KubeJS就会随机生成一个. `x` 和 `z` 是对象在屏幕上的的绝对坐标, 但你能将元素对齐到屏幕的给定位置。你可以在一个json对象中添加多个对象. 所有的属性都是可选的,但是显然你至少应该填写坐标和大小等信息。 `paint({...})`遵守更新插入法则,如果对象不存在则创建,若存在则更新其属性: ``` event.player.paint({example: {type: 'rectangle', x: 10, y: 10, w: 20, h: 20}}); // 新建矩形 event.player.paint({example: {x: 50}}); // 更新先前ID为example的Painter对象的x属性 ``` 你可以批量新建/修改Painter对象: ``` event.player.paint({a: {x: 10}, b: {x: 30}, c: {type: 'rectangle', x: 10}}); ``` 你可以使用`remove`来移除Painter对象: ``` event.player.paint({a: {remove: true}}); // 移除ID为a的Painter对象 event.player.paint({a: {remove: true}, b: {remove: true}}); // 批量移除多个对象 event.player.paint({'*': {remove: true}}); //移除全部 ``` 还记得在1.1中提到的`/kubejs painter <玩家名称> `指令吗?它和上述的函数功能是一样的 ``` /kubejs painter @p {example: {type: 'rectangle', x: 10, y: 10, w: 20, h: 20}} ``` 如果创建对象时多次重复出现,推荐在玩家登录时创建对象并为其设置属性`visible: false`,之后在合适时间将该属性改为true以取消隐藏。值得注意的是,Painter对象在玩家离开服务器时会被清除,所以你需要在每次玩家登录时重新为其添加Painter对象。 ## 可用对象 Underlined objects are not something you can create ### 根 (可用于所有对象) * Boolean visible ### 屏幕对象 (可用于所有屏幕/2D内容) * Unit x // x坐标 * Unit y // y坐标 * Unit z // z坐标 * Unit w // 宽度 * Unit h // 高度 * Enum alignX (支持 'left', 'center', 'right') // x方向对齐参数 * Enum alignY (支持 'top', 'center', 'bottom') // y方向对齐参数 * Enum draw (支持 'ingame', 'gui', 'always') // 绘制场景 * Unit moveX * Unit moveY * Unit expandW * Unit expandH ### 正方形 * Color color * String texture * Float u0 * Float v0 * Float u1 * Float v1 ### 渐变(gradient) * Color color * Color colorT * Color colorB * Color colorL * Color colorR * Color colorTL * Color colorTR * Color colorBL * Color colorBR * String texture * Float u0 * Float v0 * Float u1 * Float v1 ### 文本 * Text text * Boolean shadow * Float scale * Color color * Boolean centered **官方示例** ``` onEvent('player.logged_in', event => { // 初始化Painter对象 event.player.paint({ example_rectangle: { type: 'rectangle', x: 10, y: 10, w: 50, h: 20, color: '#00FF00', draw: 'always' }, last_message: { type: 'text', text: 'No last message', scale: 1.5, x: -4, y: -4, alignX: 'right', alignY: 'bottom', draw: 'always' } }) }) onEvent('player.chat', event => { // 更新 example_rectangle 的 x 值 和 last_message 文本的值为上次聊天文本 event.player.paint({example_rectangle: {x: '(sin((time() * 1.1)) * (($screenW - 32) / 2))', w: 32, h: 32, alignX: 'center', texture: 'kubejs:textures/item/diamond_ore.png'}}) event.player.paint({last_message: {text: `Last message: ${event.message}`}}) // 一次更新两个对象,这两种方式没有区别,你想用哪个都行 // event.player.paint({example_rectangle: {x: 120}, last_message: {text: `Last message: ${event.message}`}}) event.player.paint({lava: {type: 'atlas_texture', texture: 'minecraft:block/lava_flow'}}) }) ``` ![](https://m1.miaomc.cn/uploads/20220510_5389abd49fab4.gif) // TODO: 18.3 Painter API示例:服务器挖掘排行榜(显示挖掘方块最多的人) --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/others/network-painterapi.md --- # 16 网络包和Painter API *** ## 一、网络包 事件: 客户端从服务端侧接收脚本`player.data_from_server.<信道ID>` 服务端从客户端侧接收脚本`player.data_from_server.<信道ID>` ``` // 监听玩家物品右键事件 // 你可以把下面这段脚本放到服务端或客户端脚本中,取决于你想从哪一侧发包 onEvent('item.right_click', event => { // 监听物品右键事件 if (event.server) { // 发送 {test: 123} 到信道 "test_channel_1". 信道ID可以为任何字符串, 但还是推荐使用蛇形命名方法 // 接收端为客户端 (发送端为服务端). event.player.sendData('test_channel_1', { test: 123 });// 发包 } else { // 推荐使用不同信道 尽管这不是必须的 // 收包 (发送端为客户端). event.player.sendData('test_channel_2', { test: 456 })// 发包 } }) // 监听从服务端收到网络包时的事件 // 这段是客户端脚本 onEvent('player.data_from_server.test_channel_1', event => { log.info(event.data.test) // 打印网络包信息到日志(123) event.player.tell(`来自客户端的网络包信息:${event.data.test}`) }) // 监听从客户端收到网络包时的事件 // 这段是服务端脚本 onEvent('player.data_from_client.test_channel_2', event => { log.info(event.data.test) // 打印网络包信息到日志(456) event.server.runCommand(`say 来自服务端的网络包信息:${event.data.test}`) }) ``` ## 二、Painter API Painter API允许你在客户端屏幕上绘制内容,其可以从服务端控制也可以从客户端直接触发 目前(2022年5月10日)它不支持玩家输入/交互,但以后可能会支持游戏内菜单甚至是渲染引擎 Painter对象是以NBT/Json对象建立的,并且每一个都有自己的ID. 如果你没有提供一个的话,KubeJS就会随机生成一个. `x` 和 `z` 是对象在屏幕上的的绝对坐标, 但你能将元素对齐到屏幕的给定位置。你可以在一个json对象中添加多个对象. 所有的属性都是可选的,但是显然你至少应该填写坐标和大小等信息。 `paint({...})`遵守更新插入法则,如果对象不存在则创建,若存在则更新其属性: ``` event.player.paint({example: {type: 'rectangle', x: 10, y: 10, w: 20, h: 20}}); // 新建矩形 event.player.paint({example: {x: 50}}); // 更新先前ID为example的Painter对象的x属性 ``` 你可以批量新建/修改Painter对象: ``` event.player.paint({a: {x: 10}, b: {x: 30}, c: {type: 'rectangle', x: 10}}); ``` 你可以使用`remove`来移除Painter对象: ``` event.player.paint({a: {remove: true}}); // 移除ID为a的Painter对象 event.player.paint({a: {remove: true}, b: {remove: true}}); // 批量移除多个对象 event.player.paint({'*': {remove: true}}); //移除全部 ``` 还记得在1.1中提到的`/kubejs painter <玩家名称> `指令吗?它和上述的函数功能是一样的 ``` /kubejs painter @p {example: {type: 'rectangle', x: 10, y: 10, w: 20, h: 20}} ``` 如果创建对象时多次重复出现,推荐在玩家登录时创建对象并为其设置属性`visible: false`,之后在合适时间将该属性改为true以取消隐藏。值得注意的是,Painter对象在玩家离开服务器时会被清除,所以你需要在每次玩家登录时重新为其添加Painter对象。 ## 可用对象 Underlined objects are not something you can create ### 根 (可用于所有对象) * Boolean visible ### 屏幕对象 (可用于所有屏幕/2D内容) * Unit x // x坐标 * Unit y // y坐标 * Unit z // z坐标 * Unit w // 宽度 * Unit h // 高度 * Enum alignX (支持 'left', 'center', 'right') // x方向对齐参数 * Enum alignY (支持 'top', 'center', 'bottom') // y方向对齐参数 * Enum draw (支持 'ingame', 'gui', 'always') // 绘制场景 * Unit moveX * Unit moveY * Unit expandW * Unit expandH ### 正方形 * Color color * String texture * Float u0 * Float v0 * Float u1 * Float v1 ### 渐变(gradient) * Color color * Color colorT * Color colorB * Color colorL * Color colorR * Color colorTL * Color colorTR * Color colorBL * Color colorBR * String texture * Float u0 * Float v0 * Float u1 * Float v1 ### 文本 * Text text * Boolean shadow * Float scale * Color color * Boolean centered **官方示例** ``` onEvent('player.logged_in', event => { // 初始化Painter对象 event.player.paint({ example_rectangle: { type: 'rectangle', x: 10, y: 10, w: 50, h: 20, color: '#00FF00', draw: 'always' }, last_message: { type: 'text', text: 'No last message', scale: 1.5, x: -4, y: -4, alignX: 'right', alignY: 'bottom', draw: 'always' } }) }) onEvent('player.chat', event => { // 更新 example_rectangle 的 x 值 和 last_message 文本的值为上次聊天文本 event.player.paint({example_rectangle: {x: '(sin((time() * 1.1)) * (($screenW - 32) / 2))', w: 32, h: 32, alignX: 'center', texture: 'kubejs:textures/item/diamond_ore.png'}}) event.player.paint({last_message: {text: `Last message: ${event.message}`}}) // 一次更新两个对象,这两种方式没有区别,你想用哪个都行 // event.player.paint({example_rectangle: {x: 120}, last_message: {text: `Last message: ${event.message}`}}) event.player.paint({lava: {type: 'atlas_texture', texture: 'minecraft:block/lava_flow'}}) }) ``` ![](https://m1.miaomc.cn/uploads/20220510_5389abd49fab4.gif) // TODO: 18.3 Painter API示例:服务器挖掘排行榜(显示挖掘方块最多的人) --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_17 Tag.md description: 17 Tag 相关教程 --- # tag表 当你现在使用稿子挖掘我们添加的方块时候,你会发现无论你设置的硬度是多少挖掘速度都是一样的。会有掉落的物品,这个物品是你在上一节的战利品表中设置的。 挖掘速度一样这是不正常的。我们需要解决这个问题,与此相关的就是tag 我们需要在指定的目录下的指定的json文件中将我们的方块添加到其中去。这样我的世界才能正确认识的我们添加的方块对于的挖掘工具和挖掘所需要的等级,例如铁或者石头。 创建如下的目录结构 ``` └─data ├─examplemod └─minecraft └─tags └─blocks └─mineable ``` 如果我们要求我们的方块可以被石头及其以上等级工具挖掘,那么我们需要在blocks下创建一个needs\_stone\_tool.json文件 写入如下的内容: ```json { "values": [ "examplemod:ruby_ore" ] } ``` 表示将我们的ruby\_ore方块添加到这个tag下。 并且我们还需要我们的方块是应该被稿子挖掘的。 那么我们还需要在minebale文件夹下创建pickaxe.json文件,内容如下 ```json { "values": [ "examplemod:ruby_ore" ] } ``` 表示我们的方块应该被稿子挖掘。 对于其他的等级和挖掘的工具也是同理,自行查看原版的内容,或者查看[wiki](https://minecraft.fandom.com/zh/wiki/%E6%A0%87%E7%AD%BE) ## 数据生成 当然这里的json我们同样可以通过datagenerator生成,都是常规的操作这里简单介绍一下。 ```java public class ModBlockTagProvider extends BlockTagsProvider { public ModBlockTagProvider(PackOutput output, CompletableFuture lookupProvider, String modId, @Nullable ExistingFileHelper existingFileHelper) { super(output, lookupProvider, modId, existingFileHelper); } @Override protected void addTags(HolderLookup.Provider pProvider) { this.tag(BlockTags.NEEDS_STONE_TOOL).add(ModBlocks.RUBY_ORE.get()); this.tag(BlockTags.MINEABLE_WITH_PICKAXE).add(ModBlocks.RUBY_ORE.get()); } } ``` protected void addTags(HolderLookup.Provider pProvider):这是一个受保护的方法,用于添加方块标签。在这个方法中,它为Ruby矿石方块添加了两个标签。 this.tag(BlockTags.NEEDS\_STONE\_TOOL).add(ModBlocks.RUBY\_ORE.get());:这行代码为Ruby矿石方块添加了一个标签,名为BlockTags.NEEDS\_STONE\_TOOL。这个标签表示Ruby矿石方块需要石质工具或更好的工具来挖掘。 this.tag(BlockTags.MINEABLE\_WITH\_PICKAXE).add(ModBlocks.RUBY\_ORE.get());:这行代码为Ruby矿石方块添加了一个标签,名为BlockTags.MINEABLE\_WITH\_PICKAXE。这个标签表示Ruby矿石方块可以用镐挖掘。 加入到GatherDataEvent 事件中去: ```java // tag event.getGenerator().addProvider( event.includeServer(), (DataProvider.Factory) pOutput -> new ModBlockTagProvider(pOutput,lp,ExampleMod.MODID,efh) ); ``` ## 原版的几个tag的类 * GameEventTagsProvider (net.minecraft.data.tags) 游戏事件(game events)的标签 * ItemTagsProvider (net.minecraft.data.tags) 处理物品(items)的标签 * EntityTypeTagsProvider (net.minecraft.data.tags) 处理实体类型(entity types)的标签。 * VanillaBlockTagsProvider (net.minecraft.data.tags) 处理原版Minecraft(vanilla)中的方块标签。 * FluidTagsProvider (net.minecraft.data.tags) 处理流体(fluids)的标签。 * BlockTagsProvider (net.neoforged.neoforge.common.data) ## 拓展阅读 wiki: https://minecraft.fandom.com/zh/wiki/%E6%A0%87%E7%AD%BE neoforge document: https://docs.neoforged.net/docs/datagen/server/tags --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_17 Tag.md description: 17 Tag 相关教程 --- # tag表 当你现在使用稿子挖掘我们添加的方块时候,你会发现无论你设置的硬度是多少挖掘速度都是一样的。会有掉落的物品,这个物品是你在上一节的战利品表中设置的。 挖掘速度一样这是不正常的。我们需要解决这个问题,与此相关的就是tag 我们需要在指定的目录下的指定的json文件中将我们的方块添加到其中去。这样我的世界才能正确认识的我们添加的方块对于的挖掘工具和挖掘所需要的等级,例如铁或者石头。 创建如下的目录结构 ``` └─data ├─examplemod └─minecraft └─tags └─blocks └─mineable ``` 如果我们要求我们的方块可以被石头及其以上等级工具挖掘,那么我们需要在blocks下创建一个needs\_stone\_tool.json文件 写入如下的内容: ```json { "values": [ "examplemod:ruby_ore" ] } ``` 表示将我们的ruby\_ore方块添加到这个tag下。 并且我们还需要我们的方块是应该被稿子挖掘的。 那么我们还需要在minebale文件夹下创建pickaxe.json文件,内容如下 ```json { "values": [ "examplemod:ruby_ore" ] } ``` 表示我们的方块应该被稿子挖掘。 对于其他的等级和挖掘的工具也是同理,自行查看原版的内容,或者查看[wiki](https://minecraft.fandom.com/zh/wiki/%E6%A0%87%E7%AD%BE) ## 数据生成 当然这里的json我们同样可以通过datagenerator生成,都是常规的操作这里简单介绍一下。 ```java public class ModBlockTagProvider extends BlockTagsProvider { public ModBlockTagProvider(PackOutput output, CompletableFuture lookupProvider, String modId, @Nullable ExistingFileHelper existingFileHelper) { super(output, lookupProvider, modId, existingFileHelper); } @Override protected void addTags(HolderLookup.Provider pProvider) { this.tag(BlockTags.NEEDS_STONE_TOOL).add(ModBlocks.RUBY_ORE.get()); this.tag(BlockTags.MINEABLE_WITH_PICKAXE).add(ModBlocks.RUBY_ORE.get()); } } ``` protected void addTags(HolderLookup.Provider pProvider):这是一个受保护的方法,用于添加方块标签。在这个方法中,它为Ruby矿石方块添加了两个标签。 this.tag(BlockTags.NEEDS\_STONE\_TOOL).add(ModBlocks.RUBY\_ORE.get());:这行代码为Ruby矿石方块添加了一个标签,名为BlockTags.NEEDS\_STONE\_TOOL。这个标签表示Ruby矿石方块需要石质工具或更好的工具来挖掘。 this.tag(BlockTags.MINEABLE\_WITH\_PICKAXE).add(ModBlocks.RUBY\_ORE.get());:这行代码为Ruby矿石方块添加了一个标签,名为BlockTags.MINEABLE\_WITH\_PICKAXE。这个标签表示Ruby矿石方块可以用镐挖掘。 加入到GatherDataEvent 事件中去: ```java // tag event.getGenerator().addProvider( event.includeServer(), (DataProvider.Factory) pOutput -> new ModBlockTagProvider(pOutput,lp,ExampleMod.MODID,efh) ); ``` ## 原版的几个tag的类 * GameEventTagsProvider (net.minecraft.data.tags) 游戏事件(game events)的标签 * ItemTagsProvider (net.minecraft.data.tags) 处理物品(items)的标签 * EntityTypeTagsProvider (net.minecraft.data.tags) 处理实体类型(entity types)的标签。 * VanillaBlockTagsProvider (net.minecraft.data.tags) 处理原版Minecraft(vanilla)中的方块标签。 * FluidTagsProvider (net.minecraft.data.tags) 处理流体(fluids)的标签。 * BlockTagsProvider (net.neoforged.neoforge.common.data) ## 拓展阅读 wiki: https://minecraft.fandom.com/zh/wiki/%E6%A0%87%E7%AD%BE neoforge document: https://docs.neoforged.net/docs/datagen/server/tags --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/others/i18n.md --- # 17 本地化相关 *** KubeJS的本地化相关内容非常人性化,你可以直接使用现有的本地化键名来翻译新建的物品,方块和流体而不用手动新建。 对于方块,你需要使用本地化键名`block.kubejs.<自定义方块ID>` 对于物品,你需要使用本地化键名`item.kubejs.<自定义物品ID>` 对于流体,你需要使用本地化键名`fluid.kubejs.<自定义流体ID>` 下面我们来看一个例子。 假设你的脚本文件是这样的: `kubejs\startup_scripts\registry.js` ``` onEvent('block.registry', event => { event.create('super_tnt').material('wood').hardness(1.0).displayName('超级TNT'); }) onEvent('item.registry', event => { event.create('can').displayName('罐头').maxStackSize(8); }) onEvent('fluid.registry', event => { event.create("test_mud").displayName("泥").bucketColor(0x844031).textureThick(0x844031).textureThin(0x844031); }) ``` 那么你的语言文件应该按以下格式书写: `kubejs\assets\kubejs\lang\en_us.json` ``` { "block.kubejs.super_tnt":"Super TNT", "item.kubejs.can":"Can", "fluid.kubejs.mud":"Mud" } ``` 语言文件内翻译项目的顺序无所谓,只要都有就行 --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/others/i18n.md --- # 17 本地化相关 *** KubeJS的本地化相关内容非常人性化,你可以直接使用现有的本地化键名来翻译新建的物品,方块和流体而不用手动新建。 对于方块,你需要使用本地化键名`block.kubejs.<自定义方块ID>` 对于物品,你需要使用本地化键名`item.kubejs.<自定义物品ID>` 对于流体,你需要使用本地化键名`fluid.kubejs.<自定义流体ID>` 下面我们来看一个例子。 假设你的脚本文件是这样的: `kubejs\startup_scripts\registry.js` ``` onEvent('block.registry', event => { event.create('super_tnt').material('wood').hardness(1.0).displayName('超级TNT'); }) onEvent('item.registry', event => { event.create('can').displayName('罐头').maxStackSize(8); }) onEvent('fluid.registry', event => { event.create("test_mud").displayName("泥").bucketColor(0x844031).textureThick(0x844031).textureThin(0x844031); }) ``` 那么你的语言文件应该按以下格式书写: `kubejs\assets\kubejs\lang\en_us.json` ``` { "block.kubejs.super_tnt":"Super TNT", "item.kubejs.can":"Can", "fluid.kubejs.mud":"Mud" } ``` 语言文件内翻译项目的顺序无所谓,只要都有就行 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_18 OBJ_Model.md description: 18 OBJ模型 相关教程 --- # 前言 以下的内容大量参考了https://boson.v2mcdev.com/specialmodel/obj.html ## OBJ模型 添加一个方块类 不在赘述 ```java public class ObsidianObj extends Block { public ObsidianObj() { super(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE).noOcclusion()); } } ``` 注册方块 ```java public static final Supplier OBSIDIAN_OBJ = registerBlock("obsidian_obj", ObsidianObj::new); ``` 添加到创造模式物品栏 略 模型和材质 如果你对OBJ模型的一些术语不了解,这里建议先去了解下。这是一个简短的[说明](https://segmentfault.com/a/1190000021126476) 存放位置 ``` ├─assets │ └─examplemod │ ├─blockstates │ ├─lang │ └─models │ ├─block │ ├─obsidian_obj.json │ ├─obsidian_obj.obj │ ├─obsidian_obj.mtl │ └─item ``` json文件的内容,使用loader为neoforge加载obj格式的模型,通过model指明模型的具体位置,flip\_v设置为true是因为blender中的材质和minecraft中材质是上下颠倒的,需要手动反转。 ```json { "loader": "neoforge:obj", "model": "modid:models/block/obsidian_obj.obj", "flip_v": true } ``` 需要修改的地方 修改obj模型文件中的 mtllib obsidian\_obj.mtl 修改mtl文件中 map\_Kd examplemod:block/obsidian\_obj 将材质存放在材质的文件夹中即可。 生成blockstate ```java this.addWithHaveModel(ModBlocks.OBSIDIAN_OBJ.get(),"obsidian_obj"); ``` ## 常见坑处理方法 以下的内容摘自https://boson.v2mcdev.com/specialmodel/obj.html 1. 环境光遮蔽 2. 夜晚的不自然光 开发小课堂 如果你在使用Blender制作OBJ模型,请将你的模型中心点设置为X:0.5m,Y-0.5m,Z:0.5,这样你就不需要在json文件中进行额外的偏移计算了。Minecraft一个满方块在Blender里刚好是1m*1m*1m。 详细见: https://boson.v2mcdev.com/specialmodel/obj.html ## 参考 https://github.com/MadsWedendahlKruse/test-mod-please-ignore/blob/main/src/main/resources/assets https://boson.v2mcdev.com/specialmodel/obj.html --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_18 OBJ_Model.md description: 18 OBJ模型 相关教程 --- # 前言 以下的内容大量参考了https://boson.v2mcdev.com/specialmodel/obj.html ## OBJ模型 添加一个方块类 不在赘述 ```java public class ObsidianObj extends Block { public ObsidianObj() { super(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE).noOcclusion()); } } ``` 注册方块 ```java public static final Supplier OBSIDIAN_OBJ = registerBlock("obsidian_obj", ObsidianObj::new); ``` 添加到创造模式物品栏 略 模型和材质 如果你对OBJ模型的一些术语不了解,这里建议先去了解下。这是一个简短的[说明](https://segmentfault.com/a/1190000021126476) 存放位置 ``` ├─assets │ └─examplemod │ ├─blockstates │ ├─lang │ └─models │ ├─block │ ├─obsidian_obj.json │ ├─obsidian_obj.obj │ ├─obsidian_obj.mtl │ └─item ``` json文件的内容,使用loader为neoforge加载obj格式的模型,通过model指明模型的具体位置,flip\_v设置为true是因为blender中的材质和minecraft中材质是上下颠倒的,需要手动反转。 ```json { "loader": "neoforge:obj", "model": "modid:models/block/obsidian_obj.obj", "flip_v": true } ``` 需要修改的地方 修改obj模型文件中的 mtllib obsidian\_obj.mtl 修改mtl文件中 map\_Kd examplemod:block/obsidian\_obj 将材质存放在材质的文件夹中即可。 生成blockstate ```java this.addWithHaveModel(ModBlocks.OBSIDIAN_OBJ.get(),"obsidian_obj"); ``` ## 常见坑处理方法 以下的内容摘自https://boson.v2mcdev.com/specialmodel/obj.html 1. 环境光遮蔽 2. 夜晚的不自然光 开发小课堂 如果你在使用Blender制作OBJ模型,请将你的模型中心点设置为X:0.5m,Y-0.5m,Z:0.5,这样你就不需要在json文件中进行额外的偏移计算了。Minecraft一个满方块在Blender里刚好是1m*1m*1m。 详细见: https://boson.v2mcdev.com/specialmodel/obj.html ## 参考 https://github.com/MadsWedendahlKruse/test-mod-please-ignore/blob/main/src/main/resources/assets https://boson.v2mcdev.com/specialmodel/obj.html --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/others/smelting-pickaxe.md --- # 18.1 熔炼之镐 *** 本示例是对添加配方和Loot Table修改的综合运用 效果:使用下界合金镐挖掘方块时,矿石和石头类的方块会被自动熔炼 ``` //代码块 1 onEvent('recipes', event => { event.shapeless(Item.of('minecraft:netherite_pickaxe', {furnaceSmelt:"true"}), [Item.of('minecraft:netherite_pickaxe').ignoreNBT(), 'minecraft:lava_bucket']).replaceIngredient('minecraft:lava_bucket', 'minecraft:bucket') }) //代码块 2 onEvent('block.loot_tables', event => { event.modifyBlock(/minecraft:.*_ore/, table => { table.furnaceSmelt() table.addCondition({"condition": "minecraft:alternative","terms": [{"condition": "minecraft:match_tool","predicate": {"nbt": "{id:'furnaceSmelt'}"}}]}) }) event.modifyBlock(/minecraft:.*stone/, table => { table.furnaceSmelt() table.addCondition({"condition": "minecraft:alternative","terms": [{"condition": "minecraft:match_tool","predicate": {"nbt": "{id:'furnaceSmelt'}"}}]}) }) }) ``` 代码块1中使用了 2.1配方的添加中的nbt合成 和 11.3非标准配方修改中的替换输入物品 代码块2中使用了 4自定义loot table和其addCondition的写法(即原版json格式) --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/others/smelting-pickaxe.md --- # 18.1 熔炼之镐 *** 本示例是对添加配方和Loot Table修改的综合运用 效果:使用下界合金镐挖掘方块时,矿石和石头类的方块会被自动熔炼 ``` //代码块 1 onEvent('recipes', event => { event.shapeless(Item.of('minecraft:netherite_pickaxe', {furnaceSmelt:"true"}), [Item.of('minecraft:netherite_pickaxe').ignoreNBT(), 'minecraft:lava_bucket']).replaceIngredient('minecraft:lava_bucket', 'minecraft:bucket') }) //代码块 2 onEvent('block.loot_tables', event => { event.modifyBlock(/minecraft:.*_ore/, table => { table.furnaceSmelt() table.addCondition({"condition": "minecraft:alternative","terms": [{"condition": "minecraft:match_tool","predicate": {"nbt": "{id:'furnaceSmelt'}"}}]}) }) event.modifyBlock(/minecraft:.*stone/, table => { table.furnaceSmelt() table.addCondition({"condition": "minecraft:alternative","terms": [{"condition": "minecraft:match_tool","predicate": {"nbt": "{id:'furnaceSmelt'}"}}]}) }) }) ``` 代码块1中使用了 2.1配方的添加中的nbt合成 和 11.3非标准配方修改中的替换输入物品 代码块2中使用了 4自定义loot table和其addCondition的写法(即原版json格式) --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/others/can.md --- # 18.2 罐头示例(包含物品注册, 配方添加, 玩家交互和persistentData的使用) *** 在这个例子中,我们将: * 添加3个物品:密封的牛肉罐头,打开的牛肉罐头,罐头盒 * 添加配方:8个玻璃板合成3个罐头盒,罐头盒+两个牛肉无序合成密封的牛肉罐头 * 添加事件:当玩家手持密封的牛肉罐头长按右键时打开罐头 * 添加事件:当玩家吃完罐头后给予玩家一个空罐头盒 ## 1、物品注册 `kubejs\startup_scripts\item_registry.js` ``` onEvent('item.registry', event => { // 监听物品注册事件 event.create('can_empty').displayName('空罐头盒').maxStackSize(32);// 最大堆叠数:32 event.create('can').displayName('密封的牛肉罐头').maxStackSize(8);// 最大堆叠数:8 event.create('can_open').displayName('打开的牛肉罐头').maxStackSize(1).food(food => { food.hunger(18);// 设置食物信息 food.saturation(18); }); }) ``` ## 2、配方修改 `kubejs\server_scripts\recipe.js` ``` onEvent('recipes', event => { // 监听配方事件 // 空罐头盒合成 event.shaped('8x kubejs:can_empty', [ 'GGG', 'G G', 'GGG' ], { G: 'minecraft:glass_pane' }) // 罐头合成 event.shapeless('kubejs:can', ['kubejs:can_empty', '2x minecraft:cooked_beef']) }) ``` ## 3、罐头事件系统 `kubejs\server_scripts\can_events.js` ``` onEvent("item.right_click", event => {// 监听物品右键事件 if(event.player.mainHandItem.id == "kubejs:can"){ // 判断物品是否为给定物品 if(event.player.persistentData.canholdtime / 10 >= 1){ // 当时间达到一定值时就执行物品交换 // 给予玩家空罐头盒 event.player.give("kubejs:can_open"); // 重置persistentData event.player.persistentData.canholdtime = 0; // 密封罐头数-1 event.item.count--; } // 若时间未达到一定值则自增1 event.player.persistentData.canholdtime++; // 刷新罐头打开进度条 event.player.statusMessage = `罐头打开进度:${event.player.persistentData.canholdtime * 10}%`; } }) onEvent("player.logged_in", event =>{// 监听玩家登录事件 // 玩家加入世界时初始化 persistentData 否则其值将为NAN event.player.persistentData.canholdtime = 0; }) onEvent("item.food_eaten", event =>{// 监听吃食物事件 // 如果物品id为打开的罐头就给予玩家一个罐头盒 if(event.item.id == "kubejs:can_open"){ event.player.give("kubejs:can_empty") } }) ``` ## 4、修改展示 (图片较大,请耐心等待) ![2022-05-09\_22.50.20.png](https://m1.miaomc.cn/uploads/20220509_9be568769fad2.png) ![2022-05-09\_22.50.35.png](https://m1.miaomc.cn/uploads/20220509_de72468c663a2.png) ![2022-05-09\_22.51.07.png](https://m1.miaomc.cn/uploads/20220509_765843f2162a8.png) ![](https://m1.miaomc.cn/uploads/20220509_17accde033a92.gif) ![](https://m1.miaomc.cn/uploads/20220509_13b127ea703dd.gif) --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/others/can.md --- # 18.2 罐头示例(包含物品注册, 配方添加, 玩家交互和persistentData的使用) *** 在这个例子中,我们将: * 添加3个物品:密封的牛肉罐头,打开的牛肉罐头,罐头盒 * 添加配方:8个玻璃板合成3个罐头盒,罐头盒+两个牛肉无序合成密封的牛肉罐头 * 添加事件:当玩家手持密封的牛肉罐头长按右键时打开罐头 * 添加事件:当玩家吃完罐头后给予玩家一个空罐头盒 ## 1、物品注册 `kubejs\startup_scripts\item_registry.js` ``` onEvent('item.registry', event => { // 监听物品注册事件 event.create('can_empty').displayName('空罐头盒').maxStackSize(32);// 最大堆叠数:32 event.create('can').displayName('密封的牛肉罐头').maxStackSize(8);// 最大堆叠数:8 event.create('can_open').displayName('打开的牛肉罐头').maxStackSize(1).food(food => { food.hunger(18);// 设置食物信息 food.saturation(18); }); }) ``` ## 2、配方修改 `kubejs\server_scripts\recipe.js` ``` onEvent('recipes', event => { // 监听配方事件 // 空罐头盒合成 event.shaped('8x kubejs:can_empty', [ 'GGG', 'G G', 'GGG' ], { G: 'minecraft:glass_pane' }) // 罐头合成 event.shapeless('kubejs:can', ['kubejs:can_empty', '2x minecraft:cooked_beef']) }) ``` ## 3、罐头事件系统 `kubejs\server_scripts\can_events.js` ``` onEvent("item.right_click", event => {// 监听物品右键事件 if(event.player.mainHandItem.id == "kubejs:can"){ // 判断物品是否为给定物品 if(event.player.persistentData.canholdtime / 10 >= 1){ // 当时间达到一定值时就执行物品交换 // 给予玩家空罐头盒 event.player.give("kubejs:can_open"); // 重置persistentData event.player.persistentData.canholdtime = 0; // 密封罐头数-1 event.item.count--; } // 若时间未达到一定值则自增1 event.player.persistentData.canholdtime++; // 刷新罐头打开进度条 event.player.statusMessage = `罐头打开进度:${event.player.persistentData.canholdtime * 10}%`; } }) onEvent("player.logged_in", event =>{// 监听玩家登录事件 // 玩家加入世界时初始化 persistentData 否则其值将为NAN event.player.persistentData.canholdtime = 0; }) onEvent("item.food_eaten", event =>{// 监听吃食物事件 // 如果物品id为打开的罐头就给予玩家一个罐头盒 if(event.item.id == "kubejs:can_open"){ event.player.give("kubejs:can_empty") } }) ``` ## 4、修改展示 (图片较大,请耐心等待) ![2022-05-09\_22.50.20.png](https://m1.miaomc.cn/uploads/20220509_9be568769fad2.png) ![2022-05-09\_22.50.35.png](https://m1.miaomc.cn/uploads/20220509_de72468c663a2.png) ![2022-05-09\_22.51.07.png](https://m1.miaomc.cn/uploads/20220509_765843f2162a8.png) ![](https://m1.miaomc.cn/uploads/20220509_17accde033a92.gif) ![](https://m1.miaomc.cn/uploads/20220509_13b127ea703dd.gif) --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/others/forge-events.md --- # 19 Forge事件监听 *** KubeJS提供了很多事件,但相较于Forge依旧不够全面。因此,KubeJS提供了 `onForgeEvent` 用于监听 Forge 事件。 **一、准备工作** 为便于编写事件,你需要先在你的电脑上部署Forge开发环境。 * 下载并安装[IntelliJ IDEA](https://www.jetbrains.com/idea/) * 下载Forge MDK ![](https://m1.miaomc.cn/uploads/20220924_632f2636b1fef.png) * 右键解压后的文件夹,选择"Open Folder as lntelliJ IDEA Project",在弹出的窗口中选择"Trust Project",之后IDEA应该会自动配置开发环境。该过程会从Mojang,MinecraftForge等站点下载资源,可能速度较慢。 * 若下载多次出现问题可以尝试使用[离线开发包](https://www.mcbbs.net/thread-896542-1-1.html)。 * 尝试[设置代--理](https://fmltutor.ustc-zzzz.net/1.1-%E9%85%8D%E7%BD%AE%E4%BD%A0%E7%9A%84%E5%B7%A5%E4%BD%9C%E7%8E%AF%E5%A2%83.html#%E9%85%8D%E7%BD%AE%E5%B7%A5%E4%BD%9C%E7%8E%AF%E5%A2%83) * 安装[Minecraft Development](https://plugins.jetbrains.com/plugin/8327-minecraft-development)插件。 **二、监听事件** 你可以在IDEA中按下`Ctrl` + `N`快捷键搜索Forge事件,所有`net.minecraftforge.event`下的事件均可被监听,以监听玩家改变维度事件为例: ``` onForgeEvent("net.minecraftforge.event.entity.player.PlayerEvent$PlayerChangedDimensionEvent", event=>{ console.info("PlayerChangedDimensionEvent 触发") }) ``` **Forge事件下的所有方法都可以被调用**,例如此处获取玩家从哪里来又到哪里去: ``` onForgeEvent("net.minecraftforge.event.entity.player.PlayerEvent$PlayerChangedDimensionEvent", event=>{ console.info(event.getFrom()) console.info(event.getTo()) }) ``` 此时触发事件,日志中会打印出世界的RegistryKey(形如`" ResourceKey[minecraft:dimension / minecraft:the_nether]"`),如果我们只想要获取世界ID,应该调用RegistryKey下的`getLocation()`方法。 \*\*当调用Minecraft类中的方法等内容时,你应该使用其SRG Name而不是IDE中看到的名称。\*\*在IDEA中右键方法,选择"Get SRG Name"即可快捷获取方法的SRG Name。(也可通过[网站查询](https://linkie.shedaniel.me/mappings)) 通过查询可以获得RegistryKey下的`getLocation()`对应的SRG Name是`func_240901_a_()`: ``` onForgeEvent("net.minecraftforge.event.entity.player.PlayerEvent$PlayerChangedDimensionEvent", event=>{ console.info(event.getFrom().func_240901_a_())// getLocation() console.info(event.getTo().func_240901_a_()) }) // 返回 minecraft:the_nether 和 minecraft:overworld ``` 现在我们要将该消息发送给玩家,根据上文同理可得: ``` onForgeEvent("net.minecraftforge.event.entity.player.PlayerEvent$PlayerChangedDimensionEvent", event=>{ // PlayerEntity.func_146105_b -> PlayerEntity.sendStatusMessage event.player.func_146105_b("你来到了" + event.getTo().func_240901_a_(), false) }) ``` **KubeJS添加了方法`.asKJS()`,通过该方法你可以将部分支持的Minecraft类转换为KubeJS中等价的方法(比如Player和ItemStack等),便于编写事件。** 则上述代码可改写为: ``` onForgeEvent("net.minecraftforge.event.entity.player.PlayerEvent$PlayerChangedDimensionEvent", event=>{ event.player.asKJS().tell("你来到了" + event.getTo().func_240901_a_()) }) ``` 通过直接监听Forge事件可以解决一些KubeJS中的老大难问题,比如监听玩家重生事件或者读取键盘输入等。 --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/others/forge-events.md --- # 19 Forge事件监听 *** KubeJS提供了很多事件,但相较于Forge依旧不够全面。因此,KubeJS提供了 `onForgeEvent` 用于监听 Forge 事件。 **一、准备工作** 为便于编写事件,你需要先在你的电脑上部署Forge开发环境。 * 下载并安装[IntelliJ IDEA](https://www.jetbrains.com/idea/) * 下载Forge MDK ![](https://m1.miaomc.cn/uploads/20220924_632f2636b1fef.png) * 右键解压后的文件夹,选择"Open Folder as lntelliJ IDEA Project",在弹出的窗口中选择"Trust Project",之后IDEA应该会自动配置开发环境。该过程会从Mojang,MinecraftForge等站点下载资源,可能速度较慢。 * 若下载多次出现问题可以尝试使用[离线开发包](https://www.mcbbs.net/thread-896542-1-1.html)。 * 尝试[设置代--理](https://fmltutor.ustc-zzzz.net/1.1-%E9%85%8D%E7%BD%AE%E4%BD%A0%E7%9A%84%E5%B7%A5%E4%BD%9C%E7%8E%AF%E5%A2%83.html#%E9%85%8D%E7%BD%AE%E5%B7%A5%E4%BD%9C%E7%8E%AF%E5%A2%83) * 安装[Minecraft Development](https://plugins.jetbrains.com/plugin/8327-minecraft-development)插件。 **二、监听事件** 你可以在IDEA中按下`Ctrl` + `N`快捷键搜索Forge事件,所有`net.minecraftforge.event`下的事件均可被监听,以监听玩家改变维度事件为例: ``` onForgeEvent("net.minecraftforge.event.entity.player.PlayerEvent$PlayerChangedDimensionEvent", event=>{ console.info("PlayerChangedDimensionEvent 触发") }) ``` **Forge事件下的所有方法都可以被调用**,例如此处获取玩家从哪里来又到哪里去: ``` onForgeEvent("net.minecraftforge.event.entity.player.PlayerEvent$PlayerChangedDimensionEvent", event=>{ console.info(event.getFrom()) console.info(event.getTo()) }) ``` 此时触发事件,日志中会打印出世界的RegistryKey(形如`" ResourceKey[minecraft:dimension / minecraft:the_nether]"`),如果我们只想要获取世界ID,应该调用RegistryKey下的`getLocation()`方法。 \*\*当调用Minecraft类中的方法等内容时,你应该使用其SRG Name而不是IDE中看到的名称。\*\*在IDEA中右键方法,选择"Get SRG Name"即可快捷获取方法的SRG Name。(也可通过[网站查询](https://linkie.shedaniel.me/mappings)) 通过查询可以获得RegistryKey下的`getLocation()`对应的SRG Name是`func_240901_a_()`: ``` onForgeEvent("net.minecraftforge.event.entity.player.PlayerEvent$PlayerChangedDimensionEvent", event=>{ console.info(event.getFrom().func_240901_a_())// getLocation() console.info(event.getTo().func_240901_a_()) }) // 返回 minecraft:the_nether 和 minecraft:overworld ``` 现在我们要将该消息发送给玩家,根据上文同理可得: ``` onForgeEvent("net.minecraftforge.event.entity.player.PlayerEvent$PlayerChangedDimensionEvent", event=>{ // PlayerEntity.func_146105_b -> PlayerEntity.sendStatusMessage event.player.func_146105_b("你来到了" + event.getTo().func_240901_a_(), false) }) ``` **KubeJS添加了方法`.asKJS()`,通过该方法你可以将部分支持的Minecraft类转换为KubeJS中等价的方法(比如Player和ItemStack等),便于编写事件。** 则上述代码可改写为: ``` onForgeEvent("net.minecraftforge.event.entity.player.PlayerEvent$PlayerChangedDimensionEvent", event=>{ event.player.asKJS().tell("你来到了" + event.getTo().func_240901_a_()) }) ``` 通过直接监听Forge事件可以解决一些KubeJS中的老大难问题,比如监听玩家重生事件或者读取键盘输入等。 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_19_Register_Paint.md description: 19 添加画 相关教程 --- # 画 ```java public class ModPaintings { public static final DeferredRegister PAINTING_VARIANTS = DeferredRegister.create(Registries.PAINTING_VARIANT, ExampleMod.MODID); public static final Supplier XI_GUA = PAINTING_VARIANTS.register("xi_gua",()-/>new PaintingVariant(16,32)); public static final Supplier JIU = PAINTING_VARIANTS.register("jiu",()-/>new PaintingVariant(16,16)); public static void register(IEventBus eventBus){ PAINTING_VARIANTS.register(eventBus); } } ``` 这个类ModPaintings使用NeoForge提供的DeferredRegister来注册新的画作变体到Minecraft中。 DeferredRegister:在这里,它用于注册PaintingVariant对象。 Registries.PAINTING\_VARIANT:这是Minecraft的PaintingVariant注册表,用于存储所有可用的画作变体。 PAINTING\_VARIANTS.register("xi\_gua",()->new PaintingVariant(16,32)):这里通过DeferredRegister的register方法注册一个名为"xi\_gua"的画作变体,其尺寸为16x32。 其他都是常规操作了。 别忘记了将你的DeferredRegister实例添加到总线上去。 ```java ModPaintings.register(modEventBus); ``` 创建 如下的目录结构 ``` ├─data │ └─minecraft │ └─tags │ └─painting_variant ``` 在└─painting\_variant下创建placeable.json 文件 添加如下的内容: ```java { "values": [ "examplemod:xi_gua", "examplemod:jiu" ] } ``` 你的图的位置应放在painting下 ``` │ │ └─textures │ │ ├─block │ │ ├─item │ │ └─painting ``` 该json文件也可以使用datagenerator生成,就留给大家去尝试了。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_19_Register_Paint.md description: 19 添加画 相关教程 --- # 画 ```java public class ModPaintings { public static final DeferredRegister PAINTING_VARIANTS = DeferredRegister.create(Registries.PAINTING_VARIANT, ExampleMod.MODID); public static final Supplier XI_GUA = PAINTING_VARIANTS.register("xi_gua",()-/>new PaintingVariant(16,32)); public static final Supplier JIU = PAINTING_VARIANTS.register("jiu",()-/>new PaintingVariant(16,16)); public static void register(IEventBus eventBus){ PAINTING_VARIANTS.register(eventBus); } } ``` 这个类ModPaintings使用NeoForge提供的DeferredRegister来注册新的画作变体到Minecraft中。 DeferredRegister:在这里,它用于注册PaintingVariant对象。 Registries.PAINTING\_VARIANT:这是Minecraft的PaintingVariant注册表,用于存储所有可用的画作变体。 PAINTING\_VARIANTS.register("xi\_gua",()->new PaintingVariant(16,32)):这里通过DeferredRegister的register方法注册一个名为"xi\_gua"的画作变体,其尺寸为16x32。 其他都是常规操作了。 别忘记了将你的DeferredRegister实例添加到总线上去。 ```java ModPaintings.register(modEventBus); ``` 创建 如下的目录结构 ``` ├─data │ └─minecraft │ └─tags │ └─painting_variant ``` 在└─painting\_variant下创建placeable.json 文件 添加如下的内容: ```java { "values": [ "examplemod:xi_gua", "examplemod:jiu" ] } ``` 你的图的位置应放在painting下 ``` │ │ └─textures │ │ ├─block │ │ ├─item │ │ └─painting ``` 该json文件也可以使用datagenerator生成,就留给大家去尝试了。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/recipe/create-recipes.md --- # 2.1 新建配方及物品表示 *** ## 一、物品的表示方法——IngredientJS和ItemstackJS KubeJS提供了IngredientJS和ItemstackJS用于表示物品。比如,标签`Ingredient.of("#minecraft:logs")`就是Ingredient,`Item.of('minecraft:iron_ingot')`就是一个Itemstack 其中Ingredient多用于匹配物品。你可以使用`Ingredient.matchAny("条件")`来获得一个包含当前筛选条件的物品组,其中条件可以为物品ID(`minecraft:diamond`),mod注册名(`@tinkersconstruct`),标签(`#minecraft:logs`) Itemstack主要用于表示准确的物品组,ItemstackJS可使用的属性/函数如下: | 属性 | 功能 | 返回值 | | ---------------- | ---------------- | --------------- | | id | 返回该物品组的id | 字符串 | | tags | 返回该物品组的tags | Collection tags | | count | 设置/返回该物品组中物品数量 | int | | block | 返回当前物品是否为方块 | 布尔值 | | nbt | 返回当前物品组的nbt | CompoundTag | | nbtString | 返回字符串形式的nbt | 字符串 | | name | 返回当前物品组的名称 | Text | | enchantments | 返回当前物品组的附魔 | MapJS | | harvestSpeed | 返回当前物品组的破坏速度 | 浮点型 | | itemGroup | 返回当前物品组在创造物品栏的位置 | 字符串 | | vanillaPredicate | 获取当前物品谓词(?) | Predicate | | empty | 返回当前物品组是否为空 | 布尔值 | | 函数 | 功能 | 返回值 | | ---------------------------------------------------------------------------------- | --------------------------- | ---------------- | | hasTag(tag) | 判断物品是否有指定tag | 布尔值 | | **withCount(整形 数量)** | **返回一个指定数量的物品组** | **ItemStackJS** | | hasNBT() | 判断是否具有NBT | 布尔值 | | removeNBT() | 移除物品的NBT | void | | **withNBT(CompoundTag nbt)** | **返回具有指定NBT的物品组** | **ItemStackJS** | | **withName(Text 名称)** | **返回一个具有指定名称的物品组** | **ItemStackJS** | | strongEquals(any) | 将当前物品组与给定内容对比(同时比较数量等) | 布尔值 | | hasEnchantment(字符串 附魔ID, 整形 等级) | 判断当前物品组是否有给定的附魔 | 布尔值 | | **enchant(字符串 附魔ID, 整形 等级)** | **返回一个添加了指定附魔的物品组** | **ItemStackJS** | | **enchant(MapJS 附魔内容)** | **返回一个添加了指定附魔的物品组** | **ItemStackJS** | | **ignoreNBT()** | **返回忽略的NBT的IngredientJS** | **IngredientJS** | | **weakNBT()** | **返回部分忽略的NBT的IngredientJS** | **IngredientJS** | | areItemsEqual(ItemStackJS 对比对象) | 将当前物品组与给定物品组对比 | 布尔值 | | isNBTEqual(ItemStackJS 对比对象) | 将当前物品组与给定物品组的NBT对比 | 布尔值 | | getHarvestLevel(ToolType 工具类型, nullable PlayerJS 玩家, nullable BlockContainerJS 方块) | 返回其挖掘等级 | 整形 | | getHarvestSpeed(nullable BlockContainerJS 方块) | 返回其挖掘速度 | 浮点型 | 配方修改需要用到的大都是上表加粗的内容,剩余的内容大都等到第15章判断物品状态时才会用到。 估计你看到上面这些东西得有点麻了,不要慌,让我们看几个例子 | 例子 | 意义 | | --------------------------------------------------------------------------------------------- | --------------------------- | | Item.of("minecraft:iron\_ingot").withCount(5) | 5个铁锭 | | Item.of("minecraft:iron\_ingot").withCount(5).withName("KubeJS魔改教程") | 5个名字为"KubeJS魔改教程"的铁锭 | | Item.of("minecraft:diamond\_sword").ignoreNBT() | 忽略了NBT的钻石剑(长用于忽略物品耐久、附魔等属性) | | Item.of("minecraft:enchanted\_book", {StoredEnchantments:\[{lvl:1,id:"minecraft:sweeping"}]}) | 横扫之刃I附魔书(直接添加NBT例子) | | Item.of("minecraft:enchanted\_book").enchant("minecraft:sweeping", 1) | 横扫之刃I附魔书(使用函数添加NBT例子) | | Item.of(/create:.\*/) | 所有机械动力物品(正则表达式) | 显然,上面的方法表示物品非常麻烦。KubeJS提供了一些**简写方法**: 你可以使用命名空间ID直接表示一个物品,如`"minecraft:iron_ingot"` 你可以直接使用标签的字符串表示这一标签下的使用物品,如`"#minecraft:logs"` 你可以在命名空间前加上倍数来表示物品个数,如`"5x minecraft:iron_ingot"`表示五个铁锭 当然,你也可以直接使用正则表达式 你可以使用指令/kubejs hand来快速获取手持物品信息,详见1.1 基础代码格式和常用指令 ## 二、配方的添加 **1、有序配方添加** 语句:`event.shaped(输出物品 , 输入物品)` 例子:用8个海绵合成3个石头 ``` onEvent('recipes', event => { // 监听recipes事件 // 主体修改内容 event.shaped('3x minecraft:stone', [ 'SSS', 'S S', 'SSS' ], { S: 'minecraft:sponge' }) }) ``` ![](https://i1.mcobj.com/imgb/u18prz/20240225_65db06a132e01.png) 可以看到,KubeJS采用了类似[原版的格式](https://zhangshenxing.gitee.io/vanillamodtutorial/#%E6%9C%89%E5%BA%8F%E5%90%88%E6%88%90)来修改配方,如果你的配方不需要三行的话,直接留空即可 ***注:下文为了表述简洁将省略onEvent部分*** **2、无序配方添加** 语句:`event.shapeless(输出物品 , 输入物品)` 例子:用1个石头和1个带萤石粉标签的物品合成4个圆石 ``` event.shapeless('4x minecraft:cobblestone', ['minecraft:stone', '#forge:dusts/glowstone']) ``` **3、切石机、熔炉、高炉、烟熏炉、锻造台配方的添加** ``` // 添加切石机配方:用1个minecraft:golden_apple合成4个minecraft:apple event.stonecutting('4x minecraft:apple'/* 输出物品 */, 'minecraft:golden_apple'/* 输入物品 */) // 添加切石机配方:用1个minecraft:golden_apple合成2个minecraft:carrot event.stonecutting('2x minecraft:carrot'/* 输出物品 */, 'minecraft:golden_apple'/* 输入物品 */) // 添加熔炉配方:用1个minecraft:golden_apple合成2个minecraft:carrot // (event.recipes.minecraft.smelting的缩写) event.smelting('2x minecraft:carrot'/* 输出物品 */, 'minecraft:golden_apple'/* 输入物品 */) // 添加高炉,营火和烟熏炉的配方(与上文类似) event.blasting('3x minecraft:apple', 'minecraft:golden_apple') // 添加锻造台配方,将后2个物品合并成第一个物品 (将minecraft:gold_ingot和minecraft:apple合成为minecraft:golden_apple) event.smithing('minecraft:golden_apple', 'minecraft:apple', 'minecraft:gold_ingot') ``` **4、NBT和配方ID** 带NBT的配方修改 ``` event.shaped('minecraft:book', [ 'CCC', 'WGL', 'CCC' ], { C: '#forge:cobblestone', L: Item.of('minecraft:enchanted_book', {StoredEnchantments:[{lvl:1,id:"minecraft:sweeping"}]}), // 尽管格式是相同的,但是对于附魔来说,你还可以将其简写成如下形式: W: Item.of('minecraft:enchanted_book').enchant('minecraft:respiration', 2), G: '#forge:glass' }) ``` mc中所有的配方都有一个随机的ID,但以下配方被指定了一个唯一的静态ID。这个功能对于编写Patchouli手册比较有用 ``` event.smelting('minecraft:golden_apple', 'minecraft:carrot').id('wudjimodpack:wudji_first_recipe_id'/* 配方ID */) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/recipe/create-recipes.md --- # 2.1 新建配方及物品表示 *** ## 一、物品的表示方法——IngredientJS和ItemstackJS KubeJS提供了IngredientJS和ItemstackJS用于表示物品。比如,标签`Ingredient.of("#minecraft:logs")`就是Ingredient,`Item.of('minecraft:iron_ingot')`就是一个Itemstack 其中Ingredient多用于匹配物品。你可以使用`Ingredient.matchAny("条件")`来获得一个包含当前筛选条件的物品组,其中条件可以为物品ID(`minecraft:diamond`),mod注册名(`@tinkersconstruct`),标签(`#minecraft:logs`) Itemstack主要用于表示准确的物品组,ItemstackJS可使用的属性/函数如下: | 属性 | 功能 | 返回值 | | ---------------- | ---------------- | --------------- | | id | 返回该物品组的id | 字符串 | | tags | 返回该物品组的tags | Collection tags | | count | 设置/返回该物品组中物品数量 | int | | block | 返回当前物品是否为方块 | 布尔值 | | nbt | 返回当前物品组的nbt | CompoundTag | | nbtString | 返回字符串形式的nbt | 字符串 | | name | 返回当前物品组的名称 | Text | | enchantments | 返回当前物品组的附魔 | MapJS | | harvestSpeed | 返回当前物品组的破坏速度 | 浮点型 | | itemGroup | 返回当前物品组在创造物品栏的位置 | 字符串 | | vanillaPredicate | 获取当前物品谓词(?) | Predicate | | empty | 返回当前物品组是否为空 | 布尔值 | | 函数 | 功能 | 返回值 | | ---------------------------------------------------------------------------------- | --------------------------- | ---------------- | | hasTag(tag) | 判断物品是否有指定tag | 布尔值 | | **withCount(整形 数量)** | **返回一个指定数量的物品组** | **ItemStackJS** | | hasNBT() | 判断是否具有NBT | 布尔值 | | removeNBT() | 移除物品的NBT | void | | **withNBT(CompoundTag nbt)** | **返回具有指定NBT的物品组** | **ItemStackJS** | | **withName(Text 名称)** | **返回一个具有指定名称的物品组** | **ItemStackJS** | | strongEquals(any) | 将当前物品组与给定内容对比(同时比较数量等) | 布尔值 | | hasEnchantment(字符串 附魔ID, 整形 等级) | 判断当前物品组是否有给定的附魔 | 布尔值 | | **enchant(字符串 附魔ID, 整形 等级)** | **返回一个添加了指定附魔的物品组** | **ItemStackJS** | | **enchant(MapJS 附魔内容)** | **返回一个添加了指定附魔的物品组** | **ItemStackJS** | | **ignoreNBT()** | **返回忽略的NBT的IngredientJS** | **IngredientJS** | | **weakNBT()** | **返回部分忽略的NBT的IngredientJS** | **IngredientJS** | | areItemsEqual(ItemStackJS 对比对象) | 将当前物品组与给定物品组对比 | 布尔值 | | isNBTEqual(ItemStackJS 对比对象) | 将当前物品组与给定物品组的NBT对比 | 布尔值 | | getHarvestLevel(ToolType 工具类型, nullable PlayerJS 玩家, nullable BlockContainerJS 方块) | 返回其挖掘等级 | 整形 | | getHarvestSpeed(nullable BlockContainerJS 方块) | 返回其挖掘速度 | 浮点型 | 配方修改需要用到的大都是上表加粗的内容,剩余的内容大都等到第15章判断物品状态时才会用到。 估计你看到上面这些东西得有点麻了,不要慌,让我们看几个例子 | 例子 | 意义 | | --------------------------------------------------------------------------------------------- | --------------------------- | | Item.of("minecraft:iron\_ingot").withCount(5) | 5个铁锭 | | Item.of("minecraft:iron\_ingot").withCount(5).withName("KubeJS魔改教程") | 5个名字为"KubeJS魔改教程"的铁锭 | | Item.of("minecraft:diamond\_sword").ignoreNBT() | 忽略了NBT的钻石剑(长用于忽略物品耐久、附魔等属性) | | Item.of("minecraft:enchanted\_book", {StoredEnchantments:\[{lvl:1,id:"minecraft:sweeping"}]}) | 横扫之刃I附魔书(直接添加NBT例子) | | Item.of("minecraft:enchanted\_book").enchant("minecraft:sweeping", 1) | 横扫之刃I附魔书(使用函数添加NBT例子) | | Item.of(/create:.\*/) | 所有机械动力物品(正则表达式) | 显然,上面的方法表示物品非常麻烦。KubeJS提供了一些**简写方法**: 你可以使用命名空间ID直接表示一个物品,如`"minecraft:iron_ingot"` 你可以直接使用标签的字符串表示这一标签下的使用物品,如`"#minecraft:logs"` 你可以在命名空间前加上倍数来表示物品个数,如`"5x minecraft:iron_ingot"`表示五个铁锭 当然,你也可以直接使用正则表达式 你可以使用指令/kubejs hand来快速获取手持物品信息,详见1.1 基础代码格式和常用指令 ## 二、配方的添加 **1、有序配方添加** 语句:`event.shaped(输出物品 , 输入物品)` 例子:用8个海绵合成3个石头 ``` onEvent('recipes', event => { // 监听recipes事件 // 主体修改内容 event.shaped('3x minecraft:stone', [ 'SSS', 'S S', 'SSS' ], { S: 'minecraft:sponge' }) }) ``` ![](https://i1.mcobj.com/imgb/u18prz/20240225_65db06a132e01.png) 可以看到,KubeJS采用了类似[原版的格式](https://zhangshenxing.gitee.io/vanillamodtutorial/#%E6%9C%89%E5%BA%8F%E5%90%88%E6%88%90)来修改配方,如果你的配方不需要三行的话,直接留空即可 ***注:下文为了表述简洁将省略onEvent部分*** **2、无序配方添加** 语句:`event.shapeless(输出物品 , 输入物品)` 例子:用1个石头和1个带萤石粉标签的物品合成4个圆石 ``` event.shapeless('4x minecraft:cobblestone', ['minecraft:stone', '#forge:dusts/glowstone']) ``` **3、切石机、熔炉、高炉、烟熏炉、锻造台配方的添加** ``` // 添加切石机配方:用1个minecraft:golden_apple合成4个minecraft:apple event.stonecutting('4x minecraft:apple'/* 输出物品 */, 'minecraft:golden_apple'/* 输入物品 */) // 添加切石机配方:用1个minecraft:golden_apple合成2个minecraft:carrot event.stonecutting('2x minecraft:carrot'/* 输出物品 */, 'minecraft:golden_apple'/* 输入物品 */) // 添加熔炉配方:用1个minecraft:golden_apple合成2个minecraft:carrot // (event.recipes.minecraft.smelting的缩写) event.smelting('2x minecraft:carrot'/* 输出物品 */, 'minecraft:golden_apple'/* 输入物品 */) // 添加高炉,营火和烟熏炉的配方(与上文类似) event.blasting('3x minecraft:apple', 'minecraft:golden_apple') // 添加锻造台配方,将后2个物品合并成第一个物品 (将minecraft:gold_ingot和minecraft:apple合成为minecraft:golden_apple) event.smithing('minecraft:golden_apple', 'minecraft:apple', 'minecraft:gold_ingot') ``` **4、NBT和配方ID** 带NBT的配方修改 ``` event.shaped('minecraft:book', [ 'CCC', 'WGL', 'CCC' ], { C: '#forge:cobblestone', L: Item.of('minecraft:enchanted_book', {StoredEnchantments:[{lvl:1,id:"minecraft:sweeping"}]}), // 尽管格式是相同的,但是对于附魔来说,你还可以将其简写成如下形式: W: Item.of('minecraft:enchanted_book').enchant('minecraft:respiration', 2), G: '#forge:glass' }) ``` mc中所有的配方都有一个随机的ID,但以下配方被指定了一个唯一的静态ID。这个功能对于编写Patchouli手册比较有用 ``` event.smelting('minecraft:golden_apple', 'minecraft:carrot').id('wudjimodpack:wudji_first_recipe_id'/* 配方ID */) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/recipe/itemstacks.md --- # 2.1 物品的表示——ItemStack和Ingredient **关于ProbeJS提示** ***带有🔎符号的段落代表该段内容ProbeJS中有语言文档,或使用ProbeJS编写较为简便。*** *** ## 一、ItemStack 顾名思义,ItemStack可以代表一组物品。 🔎 以下为一些实例: | **例子** | 解释 | | --------------------------------------------------------------------------------------------- | --------------------------------------------------- | | Item.of("minecraft:diamond") | 1个铁锭 | | Item.of("minecraft:diamond").withCount(6) | 4个铁锭 | | Item.of("minecraft:iron\_ingot").withCount(5).withName("KubeJS魔改教程") | 5个名字为"KubeJS魔改教程"的铁锭 | | Item.of("minecraft:diamond\_sword").ignoreNBT() | 忽略了NBT的钻石剑(多用于忽略物品耐久、附魔等属性) | | Item.of("minecraft:enchanted\_book", {StoredEnchantments:\[{lvl:1,id:"minecraft:sweeping"}]}) | 横扫之刃I附魔书(直接添加NBT例子) | | Item.of("minecraft:enchanted\_book").enchant("minecraft:sweeping", 1) | 横扫之刃I附魔书(使用函数添加NBT例子) | | Item.of(/create:.\*/) | 所有机械动力物品(正则表达式) | | ... | ... | 在KubeJS中,你可以直接使用物品ID来表示单个物品,例如`"minecraft:diamond_sword"`代表一把钻石剑。 你还可以在物品前加上倍数来表示物品个数,例如`"5x minecraft:cobblestone"`代表5个圆石。 ## 二、Ingredient Ingredient类似于一个集合,它能够代表其所包含的所有物品,包括但不限于tag、物品列表...... 以下为一些实例: | 例子 | 意义 | | --------------------------------------------------------------- | -------------------------------- | | Ingredient.of("#minecraft:logs") | 代表tag:#minecraft:logs | | Ingredient.matchAny\["minecraft:diamond", "@tinkersconstruct"]) | 获得一个包含当前筛选条件的物品组 | | ... | ... | `Ingredient.of()`可以接受很多类型的输入并返回对应的`Ingredient`,包括正则表达式,tag(如上例),`Ingredient[]`,模组名称(如`@xplus`) 另请参阅:[ItemStackJS (kubejs.com)](https://kubejs.com/wiki/kubejs/ItemStackJS/)、[IngredientJS (kubejs.com)](https://kubejs.com/wiki/kubejs/IngredientJS/)、[旧版本教程 2.1物品表示](https://www.mcbbs.net/thread-1207772-1-1.html) *** 我们将在后续章节中详细讲解ItemStack。 --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/recipe/itemstacks.md --- # 2.1 物品的表示——ItemStack和Ingredient **关于ProbeJS提示** ***带有🔎符号的段落代表该段内容ProbeJS中有语言文档,或使用ProbeJS编写较为简便。*** *** ## 一、ItemStack 顾名思义,ItemStack可以代表一组物品。 🔎 以下为一些实例: | **例子** | 解释 | | --------------------------------------------------------------------------------------------- | --------------------------------------------------- | | Item.of("minecraft:diamond") | 1个铁锭 | | Item.of("minecraft:diamond").withCount(6) | 4个铁锭 | | Item.of("minecraft:iron\_ingot").withCount(5).withName("KubeJS魔改教程") | 5个名字为"KubeJS魔改教程"的铁锭 | | Item.of("minecraft:diamond\_sword").ignoreNBT() | 忽略了NBT的钻石剑(多用于忽略物品耐久、附魔等属性) | | Item.of("minecraft:enchanted\_book", {StoredEnchantments:\[{lvl:1,id:"minecraft:sweeping"}]}) | 横扫之刃I附魔书(直接添加NBT例子) | | Item.of("minecraft:enchanted\_book").enchant("minecraft:sweeping", 1) | 横扫之刃I附魔书(使用函数添加NBT例子) | | Item.of(/create:.\*/) | 所有机械动力物品(正则表达式) | | ... | ... | 在KubeJS中,你可以直接使用物品ID来表示单个物品,例如`"minecraft:diamond_sword"`代表一把钻石剑。 你还可以在物品前加上倍数来表示物品个数,例如`"5x minecraft:cobblestone"`代表5个圆石。 ## 二、Ingredient Ingredient类似于一个集合,它能够代表其所包含的所有物品,包括但不限于tag、物品列表...... 以下为一些实例: | 例子 | 意义 | | --------------------------------------------------------------- | -------------------------------- | | Ingredient.of("#minecraft:logs") | 代表tag:#minecraft:logs | | Ingredient.matchAny\["minecraft:diamond", "@tinkersconstruct"]) | 获得一个包含当前筛选条件的物品组 | | ... | ... | `Ingredient.of()`可以接受很多类型的输入并返回对应的`Ingredient`,包括正则表达式,tag(如上例),`Ingredient[]`,模组名称(如`@xplus`) 另请参阅:[ItemStackJS (kubejs.com)](https://kubejs.com/wiki/kubejs/ItemStackJS/)、[IngredientJS (kubejs.com)](https://kubejs.com/wiki/kubejs/IngredientJS/)、[旧版本教程 2.1物品表示](https://www.mcbbs.net/thread-1207772-1-1.html) *** 我们将在后续章节中详细讲解ItemStack。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/recipe/modify-recipes.md --- # 2.2 配方的修改和删除 *** ## 1、配方的移除 | 例子 | 用途解释 | | ---------------------------------------------------------------------------------------- | --------------------------------------------------- | | `event.remove({})` | 删除所有配方 | | `event.remove({id: '配方ID'})` | 移除指定配方ID的配方 | | `event.remove({input: '#forge:dusts/redstone'})` | 移除所有以带有#forge:dusts/redstone标签为输入物品的配方 ' '内也可以填物品ID | | `event.remove({output: '#minecraft:wool'})` | 移除所有以带有#minecraft:wool标签为输出物品的配方 ' '内也可以填物品ID | | `event.remove({mod: 'fabricexamplemod'})` | 移除所有id为fabricexamplemod的mod添加的配方 | | `event.remove({type: 'minecraft:campfire_cooking'})` | 移除以营火为合成方式的配方 | | `event.remove({output: 'minecraft:cooked_chicken', type: 'minecraft:campfire_cooking'})` | (叠加不同修改逻辑的示例) 移除用营火烤鸡肉的配方 | ## 2、配方的修改 ``` // 在所有无序配方中,将任何木板替换为minecraft:gold_nugget event.replaceInput({type: 'minecraft:crafting_shapeless'}, '#minecraft:planks', 'minecraft:gold_nugget') // {}内可以填写配方类别(和上述配方的移除写法一样) // 在所有配方中,将输出物品中的minecraft:stick替换为minecraft:oak_sapling event.replaceOutput({}, 'minecraft:stick', 'minecraft:oak_sapling') ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/recipe/modify-recipes.md --- # 2.2 配方的修改和删除 *** ## 1、配方的移除 | 例子 | 用途解释 | | ---------------------------------------------------------------------------------------- | --------------------------------------------------- | | `event.remove({})` | 删除所有配方 | | `event.remove({id: '配方ID'})` | 移除指定配方ID的配方 | | `event.remove({input: '#forge:dusts/redstone'})` | 移除所有以带有#forge:dusts/redstone标签为输入物品的配方 ' '内也可以填物品ID | | `event.remove({output: '#minecraft:wool'})` | 移除所有以带有#minecraft:wool标签为输出物品的配方 ' '内也可以填物品ID | | `event.remove({mod: 'fabricexamplemod'})` | 移除所有id为fabricexamplemod的mod添加的配方 | | `event.remove({type: 'minecraft:campfire_cooking'})` | 移除以营火为合成方式的配方 | | `event.remove({output: 'minecraft:cooked_chicken', type: 'minecraft:campfire_cooking'})` | (叠加不同修改逻辑的示例) 移除用营火烤鸡肉的配方 | ## 2、配方的修改 ``` // 在所有无序配方中,将任何木板替换为minecraft:gold_nugget event.replaceInput({type: 'minecraft:crafting_shapeless'}, '#minecraft:planks', 'minecraft:gold_nugget') // {}内可以填写配方类别(和上述配方的移除写法一样) // 在所有配方中,将输出物品中的minecraft:stick替换为minecraft:oak_sapling event.replaceOutput({}, 'minecraft:stick', 'minecraft:oak_sapling') ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/recipe/modify-recipes.md --- # 2.2 配方的添加,修改和移除 **关于ProbeJS提示** ***带有🔎符号的段落代表该段内容ProbeJS中有语言文档,或使用ProbeJS编写较为简便。*** *** ## 一、配方事件 要修改配方,你需要在`ServerEvents.recipes`事件下注册一个“事件监听器”,并在该事件下完成所有修改,例如: ```js ServerEvents.recipes(event => { // 该部分为回调函数 // 本节中的示例脚本将只包括此部分内容 console.info('ServerEvents.recipes触发') }) ``` 以下为配方事件支持的部分方法: | 方法 | 描述 | 返回值 | | ---------------------------------------------------------------------------------- | ------------------------------------------------------------ | ------ | | forEachRecipe(RecipeFilter 过滤器, Consumer consumer) | 对所有满足过滤器的配方进行修改 | void | | countRecipes(RecipeFilter 过滤器) | 返回满足过滤器的配方个数 | 整形 | | containsRecipe(RecipeFilter 过滤器) | 返回是否存在满足给定过滤器的配方 | 布尔值 | | remove(RecipeFilter 过滤器) | 移除满足给定过滤器的配方,并返回移除个数 | 整形 | | replaceInput(RecipeFilter 过滤器, IngredientMatch 被替换物品, Ingredient 替换物品) | 对所有满足过滤器的配方进行修改:替换输入物品,并返回操作个数 | 整形 | | replaceOutput(RecipeFilter 过滤器, IngredientMatch 被替换物品, ItemStack 替换物品) | 对所有满足过滤器的配方进行修改:替换输出物品,并返回操作个数 | 整形 | ## 二、配方添加 🔎 `event.recipes`可以获取所有存在的配方类型(`DocumentedRecipes`),其属性为命名空间。你可以通过这种情况修改所有支持的配方,例如: ```js ServerEvents.recipes(event => { // 营火配方:红石烧烤得到红石火把 event.recipes.minecraft.campfire_cooking('minecraft:redstone_torch','minecraft:redstone') }) ``` 以下为一些简写方法: ### 1、有序配方添加 语句:`event.shaped(输出物品 , 形状 , 输入物品)` 例子:用 4个海绵 和 4个钻石 合成 3个石头 ```js event.shaped(Item.of('minecraft:stone', 3), [ 'LOL', 'O O', 'LOL' ], { L: 'minecraft:sponge', O: 'minecraft:diamond' }) ``` ### 2、无序配方添加 语句:`event.shapeless(输出物品 , 输入物品)` 例子:用 1个海绵 和 1个带有木板标签的物品 合成2个石头 ``` event.shapeless(Item.of('minecraft:stone', 2), ['minecraft:stone', Ingredient.matchAny('#minecraft:planks')]) ``` ### 3、锻造台配方 语句:`event.smithing(输出物品, 目标物品, 消耗物品)` 例子:苹果 + 金锭 -> 金苹果 ```js event.smithing('minecraft:golden_apple', 'minecraft:apple', 'minecraft:gold_ingot') ``` ### 4、熔炉配方 语句:`event.smelting(输出物品, 输入物品)` 例子:1个金苹果 经熔炉烧炼得到 32个胡萝卜 ```js event.smelting('32x minecraft:carrot', 'minecraft:golden_apple') ``` ### 5、烟熏炉配方 语句:`event.smoking(输出物品, 输入物品)` 例子:玻璃经烟熏炉烧炼得到遮光玻璃 ``` event.smoking('minecraft:tinted_glass', 'minecraft:glass') ``` 注:燃料需要在烧炼事件中进行更改 ### 6、营火配方 语句:`event.campfireCooking(输出物品, 输入物品)` 例子:木棍经过营火烧炼得到火把 ```js event.campfireCooking('minecraft:torch', 'minecraft:stick') ``` ### 7、切石机 语句:`event.stonecutting(输出物品, 输入物品)` 例子:1个带有木板标签的物品合成3个木棍 ```js event.stonecutting('3x minecraft:stick', '#minecraft:planks') ``` ### 8、高炉 语句:`event.blasting(输出物品, 输入物品)` 例子:金苹果经过高炉烧炼得到3个苹果 ```js event.blasting('3x minecraft:apple', 'minecraft:golden_apple') ``` ## 三、配方过滤器及配方的修改和移除 ### 1、配方过滤器 配方过滤器可在配方修改或移除中用于匹配符合条件的配方,其包括以下类型 | 写法 | 描述 | 示例 | | ------------------------------ | ---------------- | -------------------------------------------------------------------------- | | `{output:'物品'}` | 匹配输出物品 | `{output: '#minecraft:wool'}` | | `{input:'物品'}` | 匹配输入物品 | `{input: '#forge:dusts/redstone'}` | | `{mod:'模组ID'}` | 匹配模组ID | `{mod: 'kubejstutorial'}` | | `{recipe:'配方ID'}` | 匹配配方ID | `{mod: 'xplusmodpack'}` | | `{type: '配方类型'}` | 匹配配方类型 | `{type: 'minecraft:campfire_cooking'}` | | `{条件1:"值",条件2:"值"}` | 组合过滤器(与) | `{output: 'minecraft:cooked_chicken', type: 'minecraft:campfire_cooking'}` | | `[{条件1:"值"}, {条件2:"值"}]` | 组合过滤器(或) | `[{type:'minecraft:smelting'}, {type:'minecraft:blasting'}]` | | `{not:{条件:"值"}}` | 组合过滤器(非) | `{not:{type:"minecraft:smelting"}}` | 上表中组合过滤器依旧可以多层叠加,从而实现更复杂的条件判断,详见下方示例。 ### 2、配方修改 | 语句 | 描述 | | ----------------------------------------------------- | ------------ | | event.replaceInput(配方过滤器, 被替换物品, 替换物品) | 修改输入物品 | | event.replaceOutput(配方过滤器, 被替换物品, 替换物品) | 修改输出物品 | 例子:在输出物品为minecraft:ladder的配方中,将输入物品中的木棍替换为树苗 ```js event.replaceInput( { output: 'minecraft:ladder' }, // 物品过滤器(匹配输出物品) 'minecraft:stick', // 被替换物品 Ingredient.of('#minecraft:saplings') // 替换物品 // 注意:流体标签在Fabric端不可用。 ) ``` 例子:在所有配方中,将输出物品中的minecraft:stick替换为minecraft:oak\_sapling ```js event.replaceOutput({}, 'minecraft:stick', 'minecraft:oak_sapling') // 物品过滤器留空 ``` 例子:在所有无序配方中,将minecraft:iron\_nugget替换为minecraft:gold\_nugget ```js event.replaceInput({type: 'minecraft:crafting_shapeless'}, 'minecraft:iron_nugget', 'minecraft:gold_nugget') ``` ### 3、配方移除 语句:`event.remove()` ```js // 物品过滤器例子 + 配方移除例子 // 移除所有配方: event.remove({}) // 移除输出物品为石斧的配方: event.remove({output: 'minecraft:stone_pickaxe'}) // 移除输出物品带有羊毛tag的配方: event.remove({output: '#minecraft:wool'}) // 移除输入物品带有红石tag的配方: event.remove({input: '#forge:dusts/redstone'}) // 移除农夫乐事添加的配方: event.remove({mod: 'farmersdelight'}) // 移除所有营火配方: event.remove({type: 'minecraft:campfire_cooking'}) // 移除除熔炉以外所有输出物品为石头的配方: event.remove({not:{type:"minecraft:smelting"},output:"stone"}) // 移除输出物品为熟鸡肉的营火配方: event.remove({output: 'minecraft:cooked_chicken', type: 'minecraft:campfire_cooking'}) // 移除熔炉或高炉的输出物品为铁锭的配方: event.remove([{type:'minecraft:smelting',output:'minecraft:iron_ingot'}, {type:'minecraft:blasting', output:'minecraft:iron_ingot'}]) // 通过ID移除配方(data/minecraft/recipes/glowstone.json): // 注:配方ID和输出物品是两个概念! event.remove({id: 'minecraft:glowstone'}) ``` [另请参阅:RecipesEventJS](https://github.com/KubeJS-Mods/KubeJS/blob/1.19/main/common/src/main/java/dev/latvian/mods/kubejs/recipe/RecipesEventJS.java) ## 四、配方ID mc中所有的配方都有一个随机的ID,但以下配方被指定了一个唯一的静态ID,可用于编写Patchouli手册或覆盖已存在配方等。 你可以通过JEI/REI来便捷地查询配方的ID。 ```js event.smelting('minecraft:golden_apple', 'minecraft:carrot').id('wudjimodpack:wudji_first_recipe_id') ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/recipe/modify-recipes.md --- # 2.2 配方的添加,修改和移除 **关于ProbeJS提示** ***带有🔎符号的段落代表该段内容ProbeJS中有语言文档,或使用ProbeJS编写较为简便。*** *** ## 一、配方事件 要修改配方,你需要在`ServerEvents.recipes`事件下注册一个“事件监听器”,并在该事件下完成所有修改,例如: ```js ServerEvents.recipes(event => { // 该部分为回调函数 // 本节中的示例脚本将只包括此部分内容 console.info('ServerEvents.recipes触发') }) ``` 以下为配方事件支持的部分方法: | 方法 | 描述 | 返回值 | | ---------------------------------------------------------------------------------- | ------------------------------------------------------------ | ------ | | forEachRecipe(RecipeFilter 过滤器, Consumer consumer) | 对所有满足过滤器的配方进行修改 | void | | countRecipes(RecipeFilter 过滤器) | 返回满足过滤器的配方个数 | 整形 | | containsRecipe(RecipeFilter 过滤器) | 返回是否存在满足给定过滤器的配方 | 布尔值 | | remove(RecipeFilter 过滤器) | 移除满足给定过滤器的配方,并返回移除个数 | 整形 | | replaceInput(RecipeFilter 过滤器, IngredientMatch 被替换物品, Ingredient 替换物品) | 对所有满足过滤器的配方进行修改:替换输入物品,并返回操作个数 | 整形 | | replaceOutput(RecipeFilter 过滤器, IngredientMatch 被替换物品, ItemStack 替换物品) | 对所有满足过滤器的配方进行修改:替换输出物品,并返回操作个数 | 整形 | ## 二、配方添加 🔎 `event.recipes`可以获取所有存在的配方类型(`DocumentedRecipes`),其属性为命名空间。你可以通过这种情况修改所有支持的配方,例如: ```js ServerEvents.recipes(event => { // 营火配方:红石烧烤得到红石火把 event.recipes.minecraft.campfire_cooking('minecraft:redstone_torch','minecraft:redstone') }) ``` 以下为一些简写方法: ### 1、有序配方添加 语句:`event.shaped(输出物品 , 形状 , 输入物品)` 例子:用 4个海绵 和 4个钻石 合成 3个石头 ```js event.shaped(Item.of('minecraft:stone', 3), [ 'LOL', 'O O', 'LOL' ], { L: 'minecraft:sponge', O: 'minecraft:diamond' }) ``` ### 2、无序配方添加 语句:`event.shapeless(输出物品 , 输入物品)` 例子:用 1个海绵 和 1个带有木板标签的物品 合成2个石头 ``` event.shapeless(Item.of('minecraft:stone', 2), ['minecraft:stone', Ingredient.matchAny('#minecraft:planks')]) ``` ### 3、锻造台配方 语句:`event.smithing(输出物品, 目标物品, 消耗物品)` 例子:苹果 + 金锭 -> 金苹果 ```js event.smithing('minecraft:golden_apple', 'minecraft:apple', 'minecraft:gold_ingot') ``` ### 4、熔炉配方 语句:`event.smelting(输出物品, 输入物品)` 例子:1个金苹果 经熔炉烧炼得到 32个胡萝卜 ```js event.smelting('32x minecraft:carrot', 'minecraft:golden_apple') ``` ### 5、烟熏炉配方 语句:`event.smoking(输出物品, 输入物品)` 例子:玻璃经烟熏炉烧炼得到遮光玻璃 ``` event.smoking('minecraft:tinted_glass', 'minecraft:glass') ``` 注:燃料需要在烧炼事件中进行更改 ### 6、营火配方 语句:`event.campfireCooking(输出物品, 输入物品)` 例子:木棍经过营火烧炼得到火把 ```js event.campfireCooking('minecraft:torch', 'minecraft:stick') ``` ### 7、切石机 语句:`event.stonecutting(输出物品, 输入物品)` 例子:1个带有木板标签的物品合成3个木棍 ```js event.stonecutting('3x minecraft:stick', '#minecraft:planks') ``` ### 8、高炉 语句:`event.blasting(输出物品, 输入物品)` 例子:金苹果经过高炉烧炼得到3个苹果 ```js event.blasting('3x minecraft:apple', 'minecraft:golden_apple') ``` ## 三、配方过滤器及配方的修改和移除 ### 1、配方过滤器 配方过滤器可在配方修改或移除中用于匹配符合条件的配方,其包括以下类型 | 写法 | 描述 | 示例 | | ------------------------------ | ---------------- | -------------------------------------------------------------------------- | | `{output:'物品'}` | 匹配输出物品 | `{output: '#minecraft:wool'}` | | `{input:'物品'}` | 匹配输入物品 | `{input: '#forge:dusts/redstone'}` | | `{mod:'模组ID'}` | 匹配模组ID | `{mod: 'kubejstutorial'}` | | `{recipe:'配方ID'}` | 匹配配方ID | `{mod: 'xplusmodpack'}` | | `{type: '配方类型'}` | 匹配配方类型 | `{type: 'minecraft:campfire_cooking'}` | | `{条件1:"值",条件2:"值"}` | 组合过滤器(与) | `{output: 'minecraft:cooked_chicken', type: 'minecraft:campfire_cooking'}` | | `[{条件1:"值"}, {条件2:"值"}]` | 组合过滤器(或) | `[{type:'minecraft:smelting'}, {type:'minecraft:blasting'}]` | | `{not:{条件:"值"}}` | 组合过滤器(非) | `{not:{type:"minecraft:smelting"}}` | 上表中组合过滤器依旧可以多层叠加,从而实现更复杂的条件判断,详见下方示例。 ### 2、配方修改 | 语句 | 描述 | | ----------------------------------------------------- | ------------ | | event.replaceInput(配方过滤器, 被替换物品, 替换物品) | 修改输入物品 | | event.replaceOutput(配方过滤器, 被替换物品, 替换物品) | 修改输出物品 | 例子:在输出物品为minecraft:ladder的配方中,将输入物品中的木棍替换为树苗 ```js event.replaceInput( { output: 'minecraft:ladder' }, // 物品过滤器(匹配输出物品) 'minecraft:stick', // 被替换物品 Ingredient.of('#minecraft:saplings') // 替换物品 // 注意:流体标签在Fabric端不可用。 ) ``` 例子:在所有配方中,将输出物品中的minecraft:stick替换为minecraft:oak\_sapling ```js event.replaceOutput({}, 'minecraft:stick', 'minecraft:oak_sapling') // 物品过滤器留空 ``` 例子:在所有无序配方中,将minecraft:iron\_nugget替换为minecraft:gold\_nugget ```js event.replaceInput({type: 'minecraft:crafting_shapeless'}, 'minecraft:iron_nugget', 'minecraft:gold_nugget') ``` ### 3、配方移除 语句:`event.remove()` ```js // 物品过滤器例子 + 配方移除例子 // 移除所有配方: event.remove({}) // 移除输出物品为石斧的配方: event.remove({output: 'minecraft:stone_pickaxe'}) // 移除输出物品带有羊毛tag的配方: event.remove({output: '#minecraft:wool'}) // 移除输入物品带有红石tag的配方: event.remove({input: '#forge:dusts/redstone'}) // 移除农夫乐事添加的配方: event.remove({mod: 'farmersdelight'}) // 移除所有营火配方: event.remove({type: 'minecraft:campfire_cooking'}) // 移除除熔炉以外所有输出物品为石头的配方: event.remove({not:{type:"minecraft:smelting"},output:"stone"}) // 移除输出物品为熟鸡肉的营火配方: event.remove({output: 'minecraft:cooked_chicken', type: 'minecraft:campfire_cooking'}) // 移除熔炉或高炉的输出物品为铁锭的配方: event.remove([{type:'minecraft:smelting',output:'minecraft:iron_ingot'}, {type:'minecraft:blasting', output:'minecraft:iron_ingot'}]) // 通过ID移除配方(data/minecraft/recipes/glowstone.json): // 注:配方ID和输出物品是两个概念! event.remove({id: 'minecraft:glowstone'}) ``` [另请参阅:RecipesEventJS](https://github.com/KubeJS-Mods/KubeJS/blob/1.19/main/common/src/main/java/dev/latvian/mods/kubejs/recipe/RecipesEventJS.java) ## 四、配方ID mc中所有的配方都有一个随机的ID,但以下配方被指定了一个唯一的静态ID,可用于编写Patchouli手册或覆盖已存在配方等。 你可以通过JEI/REI来便捷地查询配方的ID。 ```js event.smelting('minecraft:golden_apple', 'minecraft:carrot').id('wudjimodpack:wudji_first_recipe_id') ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/recipe/custom-recipes.md --- # 2.3 非标准配方修改 *** 本节将介绍非标准配方的修改,包括非工作台配方、修改输入物品状态等(如原版中蛋糕的合成方式) ## 例子1:修改机械动力中粉碎轮的合成配方 **注意!KubeJS现已有机械动力的拓展mod,无需使用该方法修改!** **机械动力的修改教程详见本教程12.3部分** ``` event.custom({ type: 'create:crushing',//指定合成方式为粉碎轮 ingredients: [ Ingredient.of('minecraft:oak_sapling').toJson()//输入内容 ], results: [//这里的 results(包括所有类似的位置的双引号都是可加可不加的) Item.of('minecraft:apple').toResultJson(),//100%输出苹果 Item.of('minecraft:carrot').withChance(0.5).toResultJson()//50%输出苹果 ], processingTime: 100 //所用时间 }) //若上述配方使用Json格式添加(即原版数据包格式) { "type": "create:crushing", "ingredients": [ { "tag": "minecraft:oak_sapling" } ], "results": [ { "item": "minecraft:apple", "count": 1 }, { "item": "minecraft:carrot", "chance": 0.5 } ], "processingTime": 100 } ``` 如果你使用自定义的配方格式,你必须使用类似于原版Json数据包的格式,也就是必须带有`"type": "mod:recipe_id"`!(比如上文中提到的`"type": "create:crushing"`). 通过这种方式,你可以为使用原版配方系统的任何配方处理器添加配方,即使它不兼容KubeJS. KubeJS提供的简写格式为: `{item: '物品注册名', count: 数量}` → `Item.of('物品注册名', 数量).toResultJson()` `{item: '物品注册名'} / {tag: '标签名'}` →`Ingredient.of('物品注册名').toJson()` 和 `Ingredient.of('#标签名').toJson()` \~~实际上不就是Json套壳吗~~ 下面我们再看一个例子 ## 例子2:为Extended Crafting添加配方 [合成拓展 (Extended Crafting) 介绍(mcmod)](https://www.mcmod.cn/class/1602.html) ``` event.custom({ type: 'extendedcrafting:shaped_table', tier: 4, pattern: [ "XXXXXXXXX", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "XXXXXXXXX" ], key: { X: [Ingredient.of('#forge:ingots/gold').toJson()],//标签的使用 }, result: [Ingredient.of('minecraft:apple').toJson()] }) //上述配方使用数据包修改: { "type": "extendedcrafting:shaped_table", "pattern": [ "XXXXXXXXX", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "XXXXXXXXX" ], "key": { "X": { "tag": "forge:ingots/gold" } }, "result": { "item": "minecraft:apple" } } ``` ## 例子3:修改输入物品状态 以下是一些内置的函数 | 功能 | 函数格式 | | ------------------------- | ------------------------------------------------------ | | 为输入物品减去耐久 | .damageIngredient(`要修改的物品(输入过滤器)`, `减去的耐久值(整形)`) | | 替换输入物品(比如桶) | .replaceIngredient(`要替换的物品(输入过滤器)`, `替换的物品(物品组)`) | | 保持输入物品不变 | .keepIngredient(`要保留的物品(输入过滤器)`) | | 自定义事件(Server StartUp脚本注册) | .customIngredientAction(`要操作的物品(输入过滤器)`, 自定义事件ID(字符串)) | 其中,所谓的输入过滤器可接受如下类型的内容 | 内容 | 示例 | | ----------- | ------------------------------------------------------------------- | | ItemStackJS | 'minecraft:dirt', Item.of('minecraft:diamond\_sword').ignoreNBT() 等 | | 合成输入索引 | 整形,如0,1,2...... | | 对象 | {item: 'something', index: 0} | 你可以在整个配方修改脚本的最后加上它们来实现你想要的效果,比如 ``` onEvent('recipes', event => { event.shapeless(/*配方脚本*/).damageItem(Item.of('minecraft:diamond_sword').ignoreNBT()) }) ``` 下面是几个例子 ``` onEvent('recipes', event => { //用钻石剑切西瓜 event.shapeless('9x minecraft:melon_slice', [ //无序合成,合成输出: 9个西瓜片 Item.of('minecraft:diamond_sword').ignoreNBT(), //输入一个忽略NBT的钻石剑 'minecraft:minecraft:melon' // 其他输入内容 ]).damageItem(Item.of('minecraft:diamond_sword').ignoreNBT()) // 降低钻石剑耐久1点(必须忽略NBT) // 使用两个钻石剑合成kubejs:example_block. 合成后索引为1的钻石剑掉一点耐久并保留第二个钻石剑. event.shaped('kubejs:example_block', [ 'SD ', 'D S' ], { S: Item.of('minecraft:diamond_sword').ignoreNBT(), D: 'minecraft:dirt' }).damageIngredient(0).keepIngredient('minecraft:diamond_sword')//叠加使用多个函数 // 使用两个钻石剑合成kubejs:example_block. 合成后钻石剑被替换为石剑 event.shapeless('kubejs:example_block', [ Item.of('minecraft:diamond_sword').ignoreNBT(), 'minecraft:stone', Item.of('minecraft:diamond_sword').ignoreNBT(), 'minecraft:stone' ]).replaceIngredient('minecraft:diamond_sword', 'minecraft:stone_sword') // 使用沙子,骨粉,土方块和水瓶合成陶土. 合成后,水瓶被玻璃瓶所替代 event.shapeless('minecraft:clay', [ 'minecraft:sand', 'minecraft:bone_meal', 'minecraft:dirt', Item.of('minecraft:potion', {Potion: "minecraft:water"}) ]).replaceIngredient({item: Item.of('minecraft:potion', {Potion: "minecraft:water"})}, 'minecraft:glass_bottle') }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/recipe/custom-recipes.md --- # 2.3 非标准配方修改 **关于ProbeJS提示** ***带有🔎符号的段落代表该段内容ProbeJS中有语言文档,或使用ProbeJS编写较为简便。*** *** 本节将介绍非标准配方的修改,包括含的NBT配方、自定义配方格式和修改输入物品状态等(如原版中蛋糕的合成方式)。 ## 一、含NBT配方 ```js event.shaped('minecraft:book', [ 'CCC', 'WGL', 'CCC' ], { C: '#forge:cobblestone', L: Item.of('minecraft:enchanted_book', '{StoredEnchantments:[{lvl:1,id:"minecraft:sweeping"}]}').weakNBT(), // 尽管格式是相同的,但是对于附魔来说,你还可以将其简写成如下形式: W: Item.of('minecraft:enchanted_book').enchant('minecraft:respiration', 2).weakNBT(), G: '#forge:glass' }) ``` ## 二、自定义配方格式 语句:`event.custom({json})`,`{json}`中必须包括`"type": "mod:recipe_id"`。 该修改方式适用于所有使用原版配方系统的Recipe handler,具体的JSON格式可以参考模组内配方文件(多见于`mod.jar/data/modid/recipes/`目录下)。 对于这种类型的配方,KubeJS提供了一些简写方法: * 例如,你可以使用`Item.of('x', 4).toJson()` 来替代`{item: 'x', count: 4}` , * 使用`Ingredient.of('x').toJson()` 或`Ingredient.of('#x').toJson()`来替代`{item: 'x'}` 或`{tag: 'x'}`。 ### 1、修改机械动力中粉碎轮的合成配方 注意!KubeJS现已有机械动力的拓展mod,无需使用该方法修改! ```js event.custom({ type: 'create:crushing',//指定合成方式为粉碎轮 ingredients: [ Ingredient.of('minecraft:oak_sapling').toJson()//输入内容 ], results: [ Item.of('minecraft:apple').toResultJson(),//100%输出苹果 Item.of('minecraft:carrot').withChance(0.5).toResultJson()//50%输出苹果 ], processingTime: 100 //所用时间 }) //若上述配方使用Json格式添加(即原版数据包格式) { "type": "create:crushing", "ingredients": [ { "tag": "minecraft:oak_sapling" } ], "results": [ { "item": "minecraft:apple", "count": 1 }, { "item": "minecraft:carrot", "chance": 0.5 } ], "processingTime": 100 } ``` ### 2:为Extended Crafting添加配方 ```js event.custom({ type: 'extendedcrafting:shaped_table', tier: 4, pattern: [ "XXXXXXXXX", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "XXXXXXXXX" ], key: { X: [Ingredient.of('#forge:ingots/gold').toJson()],//标签的使用 } result: [Ingredient.of('minecraft:apple').toJson()] }) //上述配方使用数据包修改: { "type": "extendedcrafting:shaped_table", "pattern": [ "XXXXXXXXX", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "XXXXXXXXX" ], "key": { "X": { "tag": "forge:ingots/gold" } }, "result": { "item": "minecraft:apple" } } ``` ## 三、修改输入物品状态 | **功能** | **函数格式** | | ---------------------------------- | --------------------------------------------------------------------------- | | 为输入物品减去耐久 | .damageIngredient(IngredientFilter 要修改的物品, 整形减去的耐久值) | | 替换输入物品(比如桶) | .replaceIngredient(IngredientFilter 要替换的物品, ItemStack 替换的物品) | | 保持输入物品不变 | .keepIngredient(IngredientFilter 要保留的物品) | | 设定部分烧炼配方消耗时间 | .cookingTime(int 时间) | | 自定义事件(Server StartUp脚本注册) | .customIngredientAction(IngredientFilter 要操作的物品, 字符串 自定义事件ID) | 其中,IngredientFilter可以为: | **内容** | **示例** | | ------------ | -------------------------------------------------------------------- | | ItemStack | 'minecraft:dirt', Item.of('minecraft:diamond\_sword').ignoreNBT() 等 | | 合成输入索引 | 整形,如0,1,2...... | | 对象 | {item: 'something', index: 0} | ⚠1.19.2的KubeJS将原版的合成和它自己引入的,支持各种操作的合成逻辑分开了。比如,使用`event.recipes.minecraft.crafting_shaped`添加的配方将**无法使用**`.keepIngredient`、`.damageIngredient`来进行特殊合成的设置。要使其正常工作,你需要使用形如`event.recipes.kubejs.shaped`的语句添加修改。 ### 1、修改输入物品状态 ```js ServerEvents.recipes(event => { //用钻石剑切西瓜 event.shapeless('9x minecraft:melon_slice', [ //无序合成,合成输出: 9个西瓜片 Item.of('minecraft:diamond_sword').ignoreNBT(), //输入一个忽略NBT的钻石剑 'minecraft:minecraft:melon' // 其他输入内容 ]).damageItem(Item.of('minecraft:diamond_sword').ignoreNBT()) // 降低钻石剑耐久1点(必须忽略NBT) // 使用两个钻石剑合成kubejs:example_block. 合成后索引为1的钻石剑掉一点耐久并保留第二个钻石剑. event.shaped('kubejs:example_block', [ 'SD ', 'D S' ], { S: Item.of('minecraft:diamond_sword').ignoreNBT(), D: 'minecraft:dirt' }).damageIngredient(0).keepIngredient('minecraft:diamond_sword')//叠加使用多个函数 // 使用两个钻石剑合成kubejs:example_block. 合成后钻石剑被替换为石剑 event.shapeless('kubejs:example_block', [ Item.of('minecraft:diamond_sword').ignoreNBT(), 'minecraft:stone', Item.of('minecraft:diamond_sword').ignoreNBT(), 'minecraft:stone' ]).replaceIngredient('minecraft:diamond_sword', 'minecraft:stone_sword') // 使用沙子,骨粉,土方块和水瓶合成陶土. 合成后,水瓶被玻璃瓶所替代 event.shapeless('minecraft:clay', [ 'minecraft:sand', 'minecraft:bone_meal', 'minecraft:dirt', Item.of('minecraft:potion', {Potion: "minecraft:water"}) ]).replaceIngredient({item: Item.of('minecraft:potion', {Potion: "minecraft:water"})}, 'minecraft:glass_bottle') }) ``` ### 2、自定义输入物品配方事件 实例:在工作台中使用附魔书为工具附魔,附魔后清除附魔书带有的附魔。 `kubejs\startup_scripts\CustomIngredientAction.js` ```js Ingredient.registerCustomIngredientAction("apply_enchantment", (itemstack, index, inventory) => { let enchantment = inventory.get(inventory.find(Item.of("minecraft:enchanted_book"))).nbt; if (itemstack.nbt == null) itemstack.nbt = {} itemstack.nbt = itemstack.nbt.merge({ Enchantments: enchantment.get("StoredEnchantments") }) return itemstack; }) ``` `kubejs\server_scripts\Recipe.js` ``` ServerEvents.recipes(event => { event.shapeless("minecraft:book", ["#forge:tools", Item.of("minecraft:enchanted_book")]) .customIngredientAction("#forge:tools", "apply_enchantment") }) ``` ## 四、修改输出物品状态 相较于输入物品修改,输出物品的修改较为简单。 | 功能 | 表述 | | ---------------- | -------------------------------------------------------------- | | 修改输出物品状态 | .modifyResult(ModifyRecipeResultCallback 输出物品修改回调函数) | 🔎其中,`ModifyRecipeResultCallback`具有两个参数`ModifyRecipeCraftingGrid` 和 `ItemStack`,其中前者支持以下方法: | 表述 | 功能 | 返回值 | | ------------------------------------------ | ------------------ | ------------------- | | .player() | 获取合成物品的玩家 | Player | | .find(ingredient 寻找物品, int 跳过的数字) | 获取符合条件的物品 | ItemStack | | ... | ... | 详见ProbeJS生成文档 | 需要注意的是,`ModifyRecipeResultCallback`需要一个返回值,即经过修改的输出物品 下面给出一个基础例子:使用红石粉无序合成红石火把,如果用户名是`Wudji_NotFound`,就为这个火把附魔击退 X。 ```js ServerEvents.recipes(event => { event.shapeless('minecraft:redstone_torch','minecraft:redstone').modifyResult(function(inventory,itemStack){ if(inventory.getPlayer().getUsername() == "Wudji_NotFound"){ // 判断用户名 return itemStack.enchant('minecraft:knockback', 10).withName(Component.blue("击退棒"))// 为物品附魔及重命名 } return itemStack;// 判断未通过则直接返回 }) }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/recipe/custom-recipes.md --- # 2.3 非标准配方修改 *** 本节将介绍非标准配方的修改,包括非工作台配方、修改输入物品状态等(如原版中蛋糕的合成方式) ## 例子1:修改机械动力中粉碎轮的合成配方 **注意!KubeJS现已有机械动力的拓展mod,无需使用该方法修改!** **机械动力的修改教程详见本教程12.3部分** ``` event.custom({ type: 'create:crushing',//指定合成方式为粉碎轮 ingredients: [ Ingredient.of('minecraft:oak_sapling').toJson()//输入内容 ], results: [//这里的 results(包括所有类似的位置的双引号都是可加可不加的) Item.of('minecraft:apple').toResultJson(),//100%输出苹果 Item.of('minecraft:carrot').withChance(0.5).toResultJson()//50%输出苹果 ], processingTime: 100 //所用时间 }) //若上述配方使用Json格式添加(即原版数据包格式) { "type": "create:crushing", "ingredients": [ { "tag": "minecraft:oak_sapling" } ], "results": [ { "item": "minecraft:apple", "count": 1 }, { "item": "minecraft:carrot", "chance": 0.5 } ], "processingTime": 100 } ``` 如果你使用自定义的配方格式,你必须使用类似于原版Json数据包的格式,也就是必须带有`"type": "mod:recipe_id"`!(比如上文中提到的`"type": "create:crushing"`). 通过这种方式,你可以为使用原版配方系统的任何配方处理器添加配方,即使它不兼容KubeJS. KubeJS提供的简写格式为: `{item: '物品注册名', count: 数量}` → `Item.of('物品注册名', 数量).toResultJson()` `{item: '物品注册名'} / {tag: '标签名'}` →`Ingredient.of('物品注册名').toJson()` 和 `Ingredient.of('#标签名').toJson()` \~~实际上不就是Json套壳吗~~ 下面我们再看一个例子 ## 例子2:为Extended Crafting添加配方 [合成拓展 (Extended Crafting) 介绍(mcmod)](https://www.mcmod.cn/class/1602.html) ``` event.custom({ type: 'extendedcrafting:shaped_table', tier: 4, pattern: [ "XXXXXXXXX", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "XXXXXXXXX" ], key: { X: [Ingredient.of('#forge:ingots/gold').toJson()],//标签的使用 }, result: [Ingredient.of('minecraft:apple').toJson()] }) //上述配方使用数据包修改: { "type": "extendedcrafting:shaped_table", "pattern": [ "XXXXXXXXX", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "XXXXXXXXX" ], "key": { "X": { "tag": "forge:ingots/gold" } }, "result": { "item": "minecraft:apple" } } ``` ## 例子3:修改输入物品状态 以下是一些内置的函数 | 功能 | 函数格式 | | ------------------------- | ------------------------------------------------------ | | 为输入物品减去耐久 | .damageIngredient(`要修改的物品(输入过滤器)`, `减去的耐久值(整形)`) | | 替换输入物品(比如桶) | .replaceIngredient(`要替换的物品(输入过滤器)`, `替换的物品(物品组)`) | | 保持输入物品不变 | .keepIngredient(`要保留的物品(输入过滤器)`) | | 自定义事件(Server StartUp脚本注册) | .customIngredientAction(`要操作的物品(输入过滤器)`, 自定义事件ID(字符串)) | 其中,所谓的输入过滤器可接受如下类型的内容 | 内容 | 示例 | | ----------- | ------------------------------------------------------------------- | | ItemStackJS | 'minecraft:dirt', Item.of('minecraft:diamond\_sword').ignoreNBT() 等 | | 合成输入索引 | 整形,如0,1,2...... | | 对象 | {item: 'something', index: 0} | 你可以在整个配方修改脚本的最后加上它们来实现你想要的效果,比如 ``` onEvent('recipes', event => { event.shapeless(/*配方脚本*/).damageItem(Item.of('minecraft:diamond_sword').ignoreNBT()) }) ``` 下面是几个例子 ``` onEvent('recipes', event => { //用钻石剑切西瓜 event.shapeless('9x minecraft:melon_slice', [ //无序合成,合成输出: 9个西瓜片 Item.of('minecraft:diamond_sword').ignoreNBT(), //输入一个忽略NBT的钻石剑 'minecraft:minecraft:melon' // 其他输入内容 ]).damageItem(Item.of('minecraft:diamond_sword').ignoreNBT()) // 降低钻石剑耐久1点(必须忽略NBT) // 使用两个钻石剑合成kubejs:example_block. 合成后索引为1的钻石剑掉一点耐久并保留第二个钻石剑. event.shaped('kubejs:example_block', [ 'SD ', 'D S' ], { S: Item.of('minecraft:diamond_sword').ignoreNBT(), D: 'minecraft:dirt' }).damageIngredient(0).keepIngredient('minecraft:diamond_sword')//叠加使用多个函数 // 使用两个钻石剑合成kubejs:example_block. 合成后钻石剑被替换为石剑 event.shapeless('kubejs:example_block', [ Item.of('minecraft:diamond_sword').ignoreNBT(), 'minecraft:stone', Item.of('minecraft:diamond_sword').ignoreNBT(), 'minecraft:stone' ]).replaceIngredient('minecraft:diamond_sword', 'minecraft:stone_sword') // 使用沙子,骨粉,土方块和水瓶合成陶土. 合成后,水瓶被玻璃瓶所替代 event.shapeless('minecraft:clay', [ 'minecraft:sand', 'minecraft:bone_meal', 'minecraft:dirt', Item.of('minecraft:potion', {Potion: "minecraft:water"}) ]).replaceIngredient({item: Item.of('minecraft:potion', {Potion: "minecraft:water"})}, 'minecraft:glass_bottle') }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/recipe/custom-recipes.md --- # 2.3 非标准配方修改 **关于ProbeJS提示** ***带有🔎符号的段落代表该段内容ProbeJS中有语言文档,或使用ProbeJS编写较为简便。*** *** 本节将介绍非标准配方的修改,包括含的NBT配方、自定义配方格式和修改输入物品状态等(如原版中蛋糕的合成方式)。 ## 一、含NBT配方 ```js event.shaped('minecraft:book', [ 'CCC', 'WGL', 'CCC' ], { C: '#forge:cobblestone', L: Item.of('minecraft:enchanted_book', '{StoredEnchantments:[{lvl:1,id:"minecraft:sweeping"}]}').weakNBT(), // 尽管格式是相同的,但是对于附魔来说,你还可以将其简写成如下形式: W: Item.of('minecraft:enchanted_book').enchant('minecraft:respiration', 2).weakNBT(), G: '#forge:glass' }) ``` ## 二、自定义配方格式 语句:`event.custom({json})`,`{json}`中必须包括`"type": "mod:recipe_id"`。 该修改方式适用于所有使用原版配方系统的Recipe handler,具体的JSON格式可以参考模组内配方文件(多见于`mod.jar/data/modid/recipes/`目录下)。 对于这种类型的配方,KubeJS提供了一些简写方法: * 例如,你可以使用`Item.of('x', 4).toJson()` 来替代`{item: 'x', count: 4}` , * 使用`Ingredient.of('x').toJson()` 或`Ingredient.of('#x').toJson()`来替代`{item: 'x'}` 或`{tag: 'x'}`。 ### 1、修改机械动力中粉碎轮的合成配方 注意!KubeJS现已有机械动力的拓展mod,无需使用该方法修改! ```js event.custom({ type: 'create:crushing',//指定合成方式为粉碎轮 ingredients: [ Ingredient.of('minecraft:oak_sapling').toJson()//输入内容 ], results: [ Item.of('minecraft:apple').toResultJson(),//100%输出苹果 Item.of('minecraft:carrot').withChance(0.5).toResultJson()//50%输出苹果 ], processingTime: 100 //所用时间 }) //若上述配方使用Json格式添加(即原版数据包格式) { "type": "create:crushing", "ingredients": [ { "tag": "minecraft:oak_sapling" } ], "results": [ { "item": "minecraft:apple", "count": 1 }, { "item": "minecraft:carrot", "chance": 0.5 } ], "processingTime": 100 } ``` ### 2:为Extended Crafting添加配方 ```js event.custom({ type: 'extendedcrafting:shaped_table', tier: 4, pattern: [ "XXXXXXXXX", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "XXXXXXXXX" ], key: { X: [Ingredient.of('#forge:ingots/gold').toJson()],//标签的使用 } result: [Ingredient.of('minecraft:apple').toJson()] }) //上述配方使用数据包修改: { "type": "extendedcrafting:shaped_table", "pattern": [ "XXXXXXXXX", "X X", "X X", "X X", "X X", "X X", "X X", "X X", "XXXXXXXXX" ], "key": { "X": { "tag": "forge:ingots/gold" } }, "result": { "item": "minecraft:apple" } } ``` ## 三、修改输入物品状态 | **功能** | **函数格式** | | ---------------------------------- | --------------------------------------------------------------------------- | | 为输入物品减去耐久 | .damageIngredient(IngredientFilter 要修改的物品, 整形减去的耐久值) | | 替换输入物品(比如桶) | .replaceIngredient(IngredientFilter 要替换的物品, ItemStack 替换的物品) | | 保持输入物品不变 | .keepIngredient(IngredientFilter 要保留的物品) | | 设定部分烧炼配方消耗时间 | .cookingTime(int 时间) | | 自定义事件(Server StartUp脚本注册) | .customIngredientAction(IngredientFilter 要操作的物品, 字符串 自定义事件ID) | 其中,IngredientFilter可以为: | **内容** | **示例** | | ------------ | -------------------------------------------------------------------- | | ItemStack | 'minecraft:dirt', Item.of('minecraft:diamond\_sword').ignoreNBT() 等 | | 合成输入索引 | 整形,如0,1,2...... | | 对象 | {item: 'something', index: 0} | ⚠1.19.2的KubeJS将原版的合成和它自己引入的,支持各种操作的合成逻辑分开了。比如,使用`event.recipes.minecraft.crafting_shaped`添加的配方将**无法使用**`.keepIngredient`、`.damageIngredient`来进行特殊合成的设置。要使其正常工作,你需要使用形如`event.recipes.kubejs.shaped`的语句添加修改。 ### 1、修改输入物品状态 ```js ServerEvents.recipes(event => { //用钻石剑切西瓜 event.shapeless('9x minecraft:melon_slice', [ //无序合成,合成输出: 9个西瓜片 Item.of('minecraft:diamond_sword').ignoreNBT(), //输入一个忽略NBT的钻石剑 'minecraft:minecraft:melon' // 其他输入内容 ]).damageItem(Item.of('minecraft:diamond_sword').ignoreNBT()) // 降低钻石剑耐久1点(必须忽略NBT) // 使用两个钻石剑合成kubejs:example_block. 合成后索引为1的钻石剑掉一点耐久并保留第二个钻石剑. event.shaped('kubejs:example_block', [ 'SD ', 'D S' ], { S: Item.of('minecraft:diamond_sword').ignoreNBT(), D: 'minecraft:dirt' }).damageIngredient(0).keepIngredient('minecraft:diamond_sword')//叠加使用多个函数 // 使用两个钻石剑合成kubejs:example_block. 合成后钻石剑被替换为石剑 event.shapeless('kubejs:example_block', [ Item.of('minecraft:diamond_sword').ignoreNBT(), 'minecraft:stone', Item.of('minecraft:diamond_sword').ignoreNBT(), 'minecraft:stone' ]).replaceIngredient('minecraft:diamond_sword', 'minecraft:stone_sword') // 使用沙子,骨粉,土方块和水瓶合成陶土. 合成后,水瓶被玻璃瓶所替代 event.shapeless('minecraft:clay', [ 'minecraft:sand', 'minecraft:bone_meal', 'minecraft:dirt', Item.of('minecraft:potion', {Potion: "minecraft:water"}) ]).replaceIngredient({item: Item.of('minecraft:potion', {Potion: "minecraft:water"})}, 'minecraft:glass_bottle') }) ``` ### 2、自定义输入物品配方事件 实例:在工作台中使用附魔书为工具附魔,附魔后清除附魔书带有的附魔。 `kubejs\startup_scripts\CustomIngredientAction.js` ```js Ingredient.registerCustomIngredientAction("apply_enchantment", (itemstack, index, inventory) => { let enchantment = inventory.get(inventory.find(Item.of("minecraft:enchanted_book"))).nbt; if (itemstack.nbt == null) itemstack.nbt = {} itemstack.nbt = itemstack.nbt.merge({ Enchantments: enchantment.get("StoredEnchantments") }) return itemstack; }) ``` `kubejs\server_scripts\Recipe.js` ``` ServerEvents.recipes(event => { event.shapeless("minecraft:book", ["#forge:tools", Item.of("minecraft:enchanted_book")]) .customIngredientAction("#forge:tools", "apply_enchantment") }) ``` ## 四、修改输出物品状态 相较于输入物品修改,输出物品的修改较为简单。 | 功能 | 表述 | | ---------------- | -------------------------------------------------------------- | | 修改输出物品状态 | .modifyResult(ModifyRecipeResultCallback 输出物品修改回调函数) | 🔎其中,`ModifyRecipeResultCallback`具有两个参数`ModifyRecipeCraftingGrid` 和 `ItemStack`,其中前者支持以下方法: | 表述 | 功能 | 返回值 | | ------------------------------------------ | ------------------ | ------------------- | | .player() | 获取合成物品的玩家 | Player | | .find(ingredient 寻找物品, int 跳过的数字) | 获取符合条件的物品 | ItemStack | | ... | ... | 详见ProbeJS生成文档 | 需要注意的是,`ModifyRecipeResultCallback`需要一个返回值,即经过修改的输出物品 下面给出一个基础例子:使用红石粉无序合成红石火把,如果用户名是`Wudji_NotFound`,就为这个火把附魔击退 X。 ```js ServerEvents.recipes(event => { event.shapeless('minecraft:redstone_torch','minecraft:redstone').modifyResult(function(inventory,itemStack){ if(inventory.getPlayer().getUsername() == "Wudji_NotFound"){ // 判断用户名 return itemStack.enchant('minecraft:knockback', 10).withName(Component.blue("击退棒"))// 为物品附魔及重命名 } return itemStack;// 判断未通过则直接返回 }) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/recipe/recipe-id.md --- # 2.4 配方ID 本节由@[Qi-Month (柒月)](https://github.com/Qi-Month)编写。 * **关于Recipe ID(配方ID)** * 配方ID往往是配方文件的所在目录,在开发中用于查看配方类型以及写法时显得尤为重要 * 常见的例子有删除配方,覆盖配方还有使用`custom` * 在添加`JEI`后游戏内`F3+H`开启`高级提示框`后便可查看\ ![debug.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880e45a0358.png) * 在JEI内随便打开一个配方文件将光标放在输出上便可查看\ ![id-view.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880e45ad7eb.png) * 根据上图浇筑铁锭的配方所示,根据配方ID我们可以得知配方文件在 `ModFile.jar/data/tconstruct/recipes/smeltery/casting/metal/iron/ingot_gold_cast.json` * JEI给我们提供了复制配方ID的按键,我们自行配置即可(默认没有绑定任何按键,按照自己的习惯绑定即可)\ ![key-binding.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880e45a6fb0.png) * `REI`和`EMI`其实也可以,但是本教程只教`JEI`,使用REI或EMI的朋友请自行在设置内寻找相关按键 * **使用** * 在写配方时后面加上`.id`方法时如果填入了原配方的ID会将原配方直接覆盖 ```js ServerEvents.recipes((event) => { event.recipes.kubejs.shaped("minecraft:furnace", [ "AAA", "AAA", "AAA" ], { A: "#forge:ingots/iron" }).id("minecraft:furnace") // 注意这不是写输出,是写配方ID }) ``` * 如果每个配方都写一个json会很麻烦且不好管理,所以同时也可以根据配方ID找到配方文件后直接复制里面的内容供`custom`使用,典型的例子就是 ```js ServerEvents.recipes((event) => { event.custom({ "type": "createmetallurgy:casting_in_table", "ingredients": [ { "fluid": "createmetallurgy:molten_gold", "amount": 90 }, { "tag": "forge:plates" }, ], "results": [{ "item": "createmetallurgy:graphite_plate_mold" }], "processingTime": 90 }) }) ``` * 提一嘴`custom`也是可以使用配方ID进行配方覆盖的 --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/recipe/recipe-id.md --- # 2.4 配方ID 本节由@[Qi-Month (柒月)](https://github.com/Qi-Month)编写。 * **关于Recipe ID(配方ID)** * 配方ID往往是配方文件的所在目录,在开发中用于查看配方类型以及写法时显得尤为重要 * 常见的例子有删除配方,覆盖配方还有使用`custom` * 在添加`JEI`后游戏内`F3+H`开启`高级提示框`后便可查看\ ![debug.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880e45a0358.png) * 在JEI内随便打开一个配方文件将光标放在输出上便可查看\ ![id-view.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880e45ad7eb.png) * 根据上图浇筑铁锭的配方所示,根据配方ID我们可以得知配方文件在 `ModFile.jar/data/tconstruct/recipes/smeltery/casting/metal/iron/ingot_gold_cast.json` * JEI给我们提供了复制配方ID的按键,我们自行配置即可(默认没有绑定任何按键,按照自己的习惯绑定即可)\ ![key-binding.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880e45a6fb0.png) * `REI`和`EMI`其实也可以,但是本教程只教`JEI`,使用REI或EMI的朋友请自行在设置内寻找相关按键 * **使用** * 在写配方时后面加上`.id`方法时如果填入了原配方的ID会将原配方直接覆盖 ```js ServerEvents.recipes((event) => { event.recipes.kubejs.shaped("minecraft:furnace", [ "AAA", "AAA", "AAA" ], { A: "#forge:ingots/iron" }).id("minecraft:furnace") // 注意这不是写输出,是写配方ID }) ``` * 如果每个配方都写一个json会很麻烦且不好管理,所以同时也可以根据配方ID找到配方文件后直接复制里面的内容供`custom`使用,典型的例子就是 ```js ServerEvents.recipes((event) => { event.custom({ "type": "createmetallurgy:casting_in_table", "ingredients": [ { "fluid": "createmetallurgy:molten_gold", "amount": 90 }, { "tag": "forge:plates" }, ], "results": [{ "item": "createmetallurgy:graphite_plate_mold" }], "processingTime": 90 }) }) ``` * 提一嘴`custom`也是可以使用配方ID进行配方覆盖的 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20-1.18.2-zhong-de-bian-hua-he-gong-neng.md --- # 20 1.18.2中的变化和功能 *** KubeJS在1.18.2中添加/优化了许多实用功能,比如反射和世界生成等。此外,KubeJS已经停止了对1.16.5版本的支持...是时候向1.18.2进发了! 1.18.2事件列表详见[此处](https://github.com/KubeJS-Mods/KubeJS/blob/1.18/main/common/src/main/java/dev/latvian/mods/kubejs/KubeJSEvents.java)。 本章使用的模组: 游戏版本:Minecraft 1.18.2。 模组加载器及其版本:Forge 41.1.20。 KubeJS版本:1802.5.4-build.533。 Architectury版本:4.9.83。 Rinho版本:1802.1.14-build.209。 ProbeJS版本:3.3.0。 *** 在1.18.2中,Rinho会在启动时下载mappings,该文件链接疑似在国内访问缓慢。如果在**启动游戏时**游戏出现**白屏**或**长时间未响应**问题,请尝试使用网络代理或手动将以下文件解压至`C:\Users\系统用户名\AppData\Local\Temp` 目录下。 蓝奏云下载: 正确解压后,在指定目录下应有以下文件: `C:\Users\系统用户名\AppData\Local\Temp\rhino_forge_srg_remapped_1.18.2_v1_client.txt` `C:\Users\系统用户名\AppData\Local\Temp\rhino_mojang_mappings_1.18.2_v1_client.txt` (注:可以在"文件资源管理器"或"运行"窗口中键入`%temp%`来快速跳转至指定文件夹) --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20-1.18.2-zhong-de-bian-hua-he-gong-neng.md --- # 20 1.18.2中的变化和功能 *** KubeJS在1.18.2中添加/优化了许多实用功能,比如反射和世界生成等。此外,KubeJS已经停止了对1.16.5版本的支持...是时候向1.18.2进发了! 1.18.2事件列表详见[此处](https://github.com/KubeJS-Mods/KubeJS/blob/1.18/main/common/src/main/java/dev/latvian/mods/kubejs/KubeJSEvents.java)。 本章使用的模组: 游戏版本:Minecraft 1.18.2。 模组加载器及其版本:Forge 41.1.20。 KubeJS版本:1802.5.4-build.533。 Architectury版本:4.9.83。 Rinho版本:1802.1.14-build.209。 ProbeJS版本:3.3.0。 *** 在1.18.2中,Rinho会在启动时下载mappings,该文件链接疑似在国内访问缓慢。如果在**启动游戏时**游戏出现**白屏**或**长时间未响应**问题,请尝试使用网络代理或手动将以下文件解压至`C:\Users\系统用户名\AppData\Local\Temp` 目录下。 蓝奏云下载: 正确解压后,在指定目录下应有以下文件: `C:\Users\系统用户名\AppData\Local\Temp\rhino_forge_srg_remapped_1.18.2_v1_client.txt` `C:\Users\系统用户名\AppData\Local\Temp\rhino_mojang_mappings_1.18.2_v1_client.txt` (注:可以在"文件资源管理器"或"运行"窗口中键入`%temp%`来快速跳转至指定文件夹) --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_20_Register_Villager_Type.md description: 20 添加新的村民 相关教程 --- # 添加新的村民职业 POI = POINT\_OF\_INTEREST 表示村民感兴趣的方块。兴趣点。 poiType代表了Minecraft世界中的一种特定类型的兴趣点,这些兴趣点通常是村民或其他NPC实体进行活动的地方。例如,铁匠铺、图书馆、农田等都是不同类型的兴趣点。 兴趣点类型定义了与该类型相关的方块状态(例如,铁匠铺的兴趣点类型将与铁砧和锻造台的方块状态相关联),以及该兴趣点的最大票数和有效范围。这些信息用于村民的AI,以便他们能够识别和前往这些地点进行工作或其他活动。 Registries.POINT\_OF\_INTEREST\_TYPE是Minecraft中用于存储所有兴趣点类型的注册表。 VillagerProfession定义了村民的职业,每个职业都有其独特的行为和特征。例如,铁匠、农民、图书管理员等都是不同的村民职业。 村民职业决定了村民的穿着、交易行为、以及他们可能工作的兴趣点类型。每个职业都有一个与之相关的兴趣点类型,村民会在这些地方执行与职业相关的活动。 Registries.VILLAGER\_PROFESSION是Minecraft中用于存储所有村民职业的注册表。 ## 类介绍 ### PoiTypes (net.minecraft.world.entity.ai.village.poi) PoiType记录类(record class) `public record PoiType(Set matchingStates, int maxTickets, int validRange) { ... }:`构造方法 matchingStates是一个Set,表示与这个兴趣点类型匹配的方块状态集合。 maxTickets是一个整数,表示了该兴趣点最多的工作人数 validRange是一个整数,表示这个兴趣点的有效范围, ## PoiTypes (net.minecraft.world.entity.ai.village.poi) 原版中用于定义和注册游戏世界中的兴趣点(Point of Interest,简称POI)的类。 你可以在这里找到原版的兴趣点的内容,例如锻造台,堆肥桶等。 ### VillagerProfession (net.minecraft.world.entity.npc) 表示村民的职业的类,原版村民职业的注册也在此类下。 `public record VillagerProfession(String name, Predicate> heldJobSite, Predicate> acquirableJobSite, ImmutableSet requestedItems, ImmutableSet secondaryPoi, @Nullable SoundEvent workSound) { ... }:` name:村民职业的名称。 heldJobSite:一个Predicate,用于判断村民是否持有特定的兴趣点点类型(PoiType) acquirableJobSite:一个Predicate,用于判断村民是否可以获取特定的兴趣点类型。 requestedItems - 定义了这个职业的村民可以捡起和使用物品。 secondaryPoi:一个不可变的集合,包含村民职业与之交互的兴趣点。 workSound:一个声音事件,表示村民在工作时的声音。 ## 添加一个新的兴趣点 ```java public class ModVillagers { public static final DeferredRegister POI_TYPES = DeferredRegister.create(Registries.POINT_OF_INTEREST_TYPE, ExampleMod.MODID); public static final Supplier JUMPY_BLOCK_POI = POI_TYPES.register("ruby_block_poi", () -> new PoiType(ImmutableSet.copyOf(ModBlocks.RUBY_BLOCK.get().getStateDefinition().getPossibleStates()), 1, 1)); public static void register(IEventBus eventBus) { POI_TYPES.register(eventBus); } } ``` ## 添加一个新的村民的职业 ```java public class ModVillagers { public static final DeferredRegister VILLAGER_PROFESSIONS = DeferredRegister.create(Registries.VILLAGER_PROFESSION, ExampleMod.MODID); public static final Predicate> IS_RUBY_BLOCK_POI = poiTypeHolder -> poiTypeHolder.value() == JUMPY_BLOCK_POI.get(); public static final Supplier JUMP_MASTER = VILLAGER_PROFESSIONS.register("ruby_master", () -> new VillagerProfession("ruby_master",IS_RUBY_BLOCK_POI, IS_RUBY_BLOCK_POI, ImmutableSet.of(), ImmutableSet.of(), SoundEvents.VILLAGER_WORK_ARMORER)); public static void register(IEventBus eventBus) { VILLAGER_PROFESSIONS.register(eventBus); } } ``` ## 将你的兴趣点添加到json文件中 acquirable\_job\_site这个JSON文件的作用是定义哪些兴趣点(Point of Interest,POI)是村民可以获取并从事工作的地点。 创建这样的目录 ``` │ └─minecraft │ └─tags │ ├─blocks │ ├─painting_variant │ └─point_of_interest_type ``` 在└─point\_of\_interest\_type文件夹下创建 acquirable\_job\_site.json 在json文件中加入如下的内容: ```json { "values": [ "examplemod:ruby_block_poi" ] } ``` ## 给你的村民职业的材质图片 在你的材质文件夹下创建如下目录,将图片放在目录中 ``` ├─entity │ └─villager │ └─profession │ ruby_master.png ``` 图片的内容怎么摆放可以去看原版的图片的内容。 ## 怎么给村民添加交易列表 VillagerTrades类表示了村民交易相关的内容,你可以在这里找到原版村民相关的交易的内容。包括了不同的交易的等级可以或的物品等相关的内容。 对于某个特定职业的村民来说,他的交易列表这样的存在的。 Int2ObjectMap\>用于管理村民的交易(VillagerTrades),其中每个整数键代表一个特定的交易等级,而与之关联的对象数组则包含了一组交易清单(ItemListing)。 ItemListing是一个接口,用于生成与村民交易相关的MerchantOffer对象。这个接口通常用于自定义村民的交易行为 该接口要求实现如下的方法。 `MerchantOffer getOffer(Entity pTrader, RandomSource pRandom);` pTrader: 这是一个Entity类型的参数,代表交易的另一方,通常是村民。 pRandom: 这是一个RandomSource类型的参数,用于生成随机数,以确定是否以及如何提供一个交易。 MerchantOffer 它代表了一个商人(如村民)的交易提议。这个类包含了交易的所有必要信息,如交易的输入物品、输出物品、交易次数、经验值奖励等。 `MerchantOffer(ItemStack pBaseCostA, ItemStack pResult, int pMaxUses, int pXp, float pPriceMultiplier) ` * baseCostA: 交易输入物品。 * result: 交易成功后的输出物品。 * maxUses: 交易的最大使用次数。 * pXp: 交易经验 * pPriceMultiplier: 价格乘数,用于调整交易价格。 到此为止,我们就明白了,我们想添加新的交易,其实就是给特定的职业的村民的交易的Int2ObjectMap中,获得对应等级的List\。往这个List中加入一个实现了ItemListing接口的类的实例。该接口要求实现getOffer方法,返回一个MerchantOffer类的实例。 ## 给村民添加交易 ```java public class ModEvent { @Mod.EventBusSubscriber(modid = ExampleMod.MODID) public static class ForgeEvents{ @SubscribeEvent public static void addCustomTrades(VillagerTradesEvent event) { if(event.getType() == VillagerProfession.TOOLSMITH) { Int2ObjectMap> trades = event.getTrades(); ItemStack stack = new ItemStack(ModItems.MAGIC_INGOT.get(), 1); int villagerLevel = 1; trades.get(villagerLevel).add((trader, rand) -> new MerchantOffer( new ItemStack(Items.EMERALD, 2), stack,10,8,0.02F)); } } } } ``` ## 给自己村民添加 ```java public class ModEvent { @Mod.EventBusSubscriber(modid = ExampleMod.MODID) public static class ForgeEvents{ @SubscribeEvent public static void addCustomTrades(VillagerTradesEvent event) { if(event.getType() == ModVillagers.RUBY_MASTER.get()) { Int2ObjectMap> trades = event.getTrades(); ItemStack stack = new ItemStack(ModItems.RUBY.get(), 15); int villagerLevel = 1; trades.get(villagerLevel).add((trader, rand) -> new MerchantOffer( new ItemStack(Items.EMERALD, 5), stack,10,8,0.02F)); } } } } ``` --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_20_Register_Villager_Type.md description: 20 添加新的村民 相关教程 --- # 添加新的村民职业 POI = POINT\_OF\_INTEREST 表示村民感兴趣的方块。兴趣点。 poiType代表了Minecraft世界中的一种特定类型的兴趣点,这些兴趣点通常是村民或其他NPC实体进行活动的地方。例如,铁匠铺、图书馆、农田等都是不同类型的兴趣点。 兴趣点类型定义了与该类型相关的方块状态(例如,铁匠铺的兴趣点类型将与铁砧和锻造台的方块状态相关联),以及该兴趣点的最大票数和有效范围。这些信息用于村民的AI,以便他们能够识别和前往这些地点进行工作或其他活动。 Registries.POINT\_OF\_INTEREST\_TYPE是Minecraft中用于存储所有兴趣点类型的注册表。 VillagerProfession定义了村民的职业,每个职业都有其独特的行为和特征。例如,铁匠、农民、图书管理员等都是不同的村民职业。 村民职业决定了村民的穿着、交易行为、以及他们可能工作的兴趣点类型。每个职业都有一个与之相关的兴趣点类型,村民会在这些地方执行与职业相关的活动。 Registries.VILLAGER\_PROFESSION是Minecraft中用于存储所有村民职业的注册表。 ## 类介绍 ### PoiTypes (net.minecraft.world.entity.ai.village.poi) PoiType记录类(record class) `public record PoiType(Set matchingStates, int maxTickets, int validRange) { ... }:`构造方法 matchingStates是一个Set,表示与这个兴趣点类型匹配的方块状态集合。 maxTickets是一个整数,表示了该兴趣点最多的工作人数 validRange是一个整数,表示这个兴趣点的有效范围, ## PoiTypes (net.minecraft.world.entity.ai.village.poi) 原版中用于定义和注册游戏世界中的兴趣点(Point of Interest,简称POI)的类。 你可以在这里找到原版的兴趣点的内容,例如锻造台,堆肥桶等。 ### VillagerProfession (net.minecraft.world.entity.npc) 表示村民的职业的类,原版村民职业的注册也在此类下。 `public record VillagerProfession(String name, Predicate> heldJobSite, Predicate> acquirableJobSite, ImmutableSet requestedItems, ImmutableSet secondaryPoi, @Nullable SoundEvent workSound) { ... }:` name:村民职业的名称。 heldJobSite:一个Predicate,用于判断村民是否持有特定的兴趣点点类型(PoiType) acquirableJobSite:一个Predicate,用于判断村民是否可以获取特定的兴趣点类型。 requestedItems - 定义了这个职业的村民可以捡起和使用物品。 secondaryPoi:一个不可变的集合,包含村民职业与之交互的兴趣点。 workSound:一个声音事件,表示村民在工作时的声音。 ## 添加一个新的兴趣点 ```java public class ModVillagers { public static final DeferredRegister POI_TYPES = DeferredRegister.create(Registries.POINT_OF_INTEREST_TYPE, ExampleMod.MODID); public static final Supplier JUMPY_BLOCK_POI = POI_TYPES.register("ruby_block_poi", () -> new PoiType(ImmutableSet.copyOf(ModBlocks.RUBY_BLOCK.get().getStateDefinition().getPossibleStates()), 1, 1)); public static void register(IEventBus eventBus) { POI_TYPES.register(eventBus); } } ``` ## 添加一个新的村民的职业 ```java public class ModVillagers { public static final DeferredRegister VILLAGER_PROFESSIONS = DeferredRegister.create(Registries.VILLAGER_PROFESSION, ExampleMod.MODID); public static final Predicate> IS_RUBY_BLOCK_POI = poiTypeHolder -> poiTypeHolder.value() == JUMPY_BLOCK_POI.get(); public static final Supplier JUMP_MASTER = VILLAGER_PROFESSIONS.register("ruby_master", () -> new VillagerProfession("ruby_master",IS_RUBY_BLOCK_POI, IS_RUBY_BLOCK_POI, ImmutableSet.of(), ImmutableSet.of(), SoundEvents.VILLAGER_WORK_ARMORER)); public static void register(IEventBus eventBus) { VILLAGER_PROFESSIONS.register(eventBus); } } ``` ## 将你的兴趣点添加到json文件中 acquirable\_job\_site这个JSON文件的作用是定义哪些兴趣点(Point of Interest,POI)是村民可以获取并从事工作的地点。 创建这样的目录 ``` │ └─minecraft │ └─tags │ ├─blocks │ ├─painting_variant │ └─point_of_interest_type ``` 在└─point\_of\_interest\_type文件夹下创建 acquirable\_job\_site.json 在json文件中加入如下的内容: ```json { "values": [ "examplemod:ruby_block_poi" ] } ``` ## 给你的村民职业的材质图片 在你的材质文件夹下创建如下目录,将图片放在目录中 ``` ├─entity │ └─villager │ └─profession │ ruby_master.png ``` 图片的内容怎么摆放可以去看原版的图片的内容。 ## 怎么给村民添加交易列表 VillagerTrades类表示了村民交易相关的内容,你可以在这里找到原版村民相关的交易的内容。包括了不同的交易的等级可以或的物品等相关的内容。 对于某个特定职业的村民来说,他的交易列表这样的存在的。 Int2ObjectMap\>用于管理村民的交易(VillagerTrades),其中每个整数键代表一个特定的交易等级,而与之关联的对象数组则包含了一组交易清单(ItemListing)。 ItemListing是一个接口,用于生成与村民交易相关的MerchantOffer对象。这个接口通常用于自定义村民的交易行为 该接口要求实现如下的方法。 `MerchantOffer getOffer(Entity pTrader, RandomSource pRandom);` pTrader: 这是一个Entity类型的参数,代表交易的另一方,通常是村民。 pRandom: 这是一个RandomSource类型的参数,用于生成随机数,以确定是否以及如何提供一个交易。 MerchantOffer 它代表了一个商人(如村民)的交易提议。这个类包含了交易的所有必要信息,如交易的输入物品、输出物品、交易次数、经验值奖励等。 `MerchantOffer(ItemStack pBaseCostA, ItemStack pResult, int pMaxUses, int pXp, float pPriceMultiplier) ` * baseCostA: 交易输入物品。 * result: 交易成功后的输出物品。 * maxUses: 交易的最大使用次数。 * pXp: 交易经验 * pPriceMultiplier: 价格乘数,用于调整交易价格。 到此为止,我们就明白了,我们想添加新的交易,其实就是给特定的职业的村民的交易的Int2ObjectMap中,获得对应等级的List\。往这个List中加入一个实现了ItemListing接口的类的实例。该接口要求实现getOffer方法,返回一个MerchantOffer类的实例。 ## 给村民添加交易 ```java public class ModEvent { @Mod.EventBusSubscriber(modid = ExampleMod.MODID) public static class ForgeEvents{ @SubscribeEvent public static void addCustomTrades(VillagerTradesEvent event) { if(event.getType() == VillagerProfession.TOOLSMITH) { Int2ObjectMap> trades = event.getTrades(); ItemStack stack = new ItemStack(ModItems.MAGIC_INGOT.get(), 1); int villagerLevel = 1; trades.get(villagerLevel).add((trader, rand) -> new MerchantOffer( new ItemStack(Items.EMERALD, 2), stack,10,8,0.02F)); } } } } ``` ## 给自己村民添加 ```java public class ModEvent { @Mod.EventBusSubscriber(modid = ExampleMod.MODID) public static class ForgeEvents{ @SubscribeEvent public static void addCustomTrades(VillagerTradesEvent event) { if(event.getType() == ModVillagers.RUBY_MASTER.get()) { Int2ObjectMap> trades = event.getTrades(); ItemStack stack = new ItemStack(ModItems.RUBY.get(), 15); int villagerLevel = 1; trades.get(villagerLevel).add((trader, rand) -> new MerchantOffer( new ItemStack(Items.EMERALD, 5), stack,10,8,0.02F)); } } } } ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.1-probejs.md --- # 20.1 ProbeJS *** ProbeJS可以快捷生成VSCode支持的代码模板和声明文件,极大地提高了编写魔改的效率。 要使用ProbeJS,你需要: * 安装[Visual Studio Code](https://code.visualstudio.com/)软件。 * 为游戏安装[ProbeJS](https://www.curseforge.com/minecraft/mc-mods/probejs)模组(作者:[迺逸夫](https://www.mcmod.cn/author/24214.html)) ProbeJS安装完成后,在游戏内执行`/probejs dump`指令即可生成声明文件。 ![](https://m1.miaomc.cn/uploads/20220917_63251cc8b24d0.png) 待生成完成后,右击选择“在VSCode中打开”或“在Code中打开”即可使用自动补全。 ![](https://m1.miaomc.cn/uploads/20220917_63251ddc2e745.png) ![](https://m1.miaomc.cn/uploads/20220917_632520ed310b5.png) --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.1-probejs.md --- # 20.1 ProbeJS *** ProbeJS可以快捷生成VSCode支持的代码模板和声明文件,极大地提高了编写魔改的效率。 要使用ProbeJS,你需要: * 安装[Visual Studio Code](https://code.visualstudio.com/)软件。 * 为游戏安装[ProbeJS](https://www.curseforge.com/minecraft/mc-mods/probejs)模组(作者:[迺逸夫](https://www.mcmod.cn/author/24214.html)) ProbeJS安装完成后,在游戏内执行`/probejs dump`指令即可生成声明文件。 ![](https://m1.miaomc.cn/uploads/20220917_63251cc8b24d0.png) 待生成完成后,右击选择“在VSCode中打开”或“在Code中打开”即可使用自动补全。 ![](https://m1.miaomc.cn/uploads/20220917_63251ddc2e745.png) ![](https://m1.miaomc.cn/uploads/20220917_632520ed310b5.png) --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.2-zu-jian-component-de-shi-yong.md --- # 20.2 组件(Component)的使用 *** 组件在(Component)1.18.2及以上版本的KubeJS中被大量使用。其可用于返回实体、物品的名称,亦或是设置悬浮提示,为玩家发送消息等等。 ## 一、基础使用 你可以使用`ComponentWrapper` 来新建组件,它可以在任何地方以`Component`或`Text`形式被调用,以下为其方法: | 方法名 | 返回值 | 描述 | | ---------------------------------------------------------------- | ---------------- | ----------------------------------------------------------- | | of(Object o) | MutableComponent | 该方法会返回一个基于你输入对象的组件。输入可以是字符串、数字等基础数据类型,也可以是snbt/nbt等其他类型。 | | clickEventOf(Object o) | ClickEvent | 基于输入的内容返回ClickEvent,详见下方示例。 | | prettyPrintNbt(Tag tag) | Component | 返回一个高亮的nbt,详见下方示例。 | | join(MutableComponent 分隔符, Iterable\ 待遍历内容) | MutableComponent | 返回遍历 `待遍历内容` 的结果并把它们拼接在一起,并使用 `分隔符` 将其隔开。 | | string(String 文本) | MutableComponent | 基于输入的字符串返回未格式化的TextComponent。 | | translate(String 本地化键名) | MutableComponent | 基于输入的本地化键名返回未格式化的TextComponent。 | | translate(String 本地化键名, Object... 文本) | MutableComponent | 基于输入的本地化键名和输入的对象返回未格式化的TextComponent,后者中的"%s"被本地化键名对应的译名替代。 | | keybind(String 键名) | MutableComponent | 基于输入的键名返回客户端对应热键的KeybindComponent。 | | (Object 文本) | MutableComponent | 基于输入的颜色和文本返回对应的组件,可用颜色详见本章末尾(不含尖括号)。 | 以下是示例: ``` onEvent('player.chat', event => { // 为玩家发送消息 event.player.tell(Component.string('测试消息')) // 该消息现在为黑色 event.player.tell(Component.black('黑色测试消息')) // 发送对应键名item.minecraft.diamond的文本 event.player.tell(Component.translate('item.minecraft.diamond').blue()) // 发送玩家潜行绑定的热键名称 event.player.tell(Component.keybind('key.sneak')) // 发送高亮后的主手物品的nbt event.player.tell(Component.prettyPrintNbt(event.player.mainHandItem.nbt)) }) ``` 效果展示 // img0 ## 二、可变组件(MutableComponent) 以下为MutableComponent支持的方法,其中包含ComponentKJS向原版Component注入的拓展。除相关原版方法外,你还可以调用以下由ComponentKJS添加的方法。 | 方法名 | 返回值 | 信息 | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------- | | iterator() | Iterator | 返回当前组件中包含的子Component。 | | self() | MutableComponent | 返回当前组件。 | | toJson() | JsonElement | 返回当前组件的Json形式。 | | () | MutableComponent | 设置当前组件的颜色,支持的颜色见下。 | | color(Color c) | MutableComponent | 设置当前组件的颜色(颜色为Rhino格式)。 | | noColor() | MutableComponent | 清除当前组件的颜色 | | bold(@Nullable Boolean 是否使用) italic(@Nullable Boolean 是否使用) underlined(@Nullable Boolean 是否使用) strikethrough(@Nullable Boolean 是否使用) obfuscated(@Nullable Boolean 是否使用) | MutableComponent | 将组件设定为给定格式(依次为加粗,斜体,下划线,删除线,拼合),不填时默认值为true | | insertion(@Nullable String 自动填充文字) | MutableComponent | 当玩家点击该组件时在聊天框自动填充给定的文本(不自动发送) | | font(@Nullable ResourceLocation 字体资源) | MutableComponent | 设置的字体。关于字体的更多信息详见[Minecraft Wiki - 资源包 - 字体](https://minecraft.fandom.com/zh/wiki/%E8%B5%84%E6%BA%90%E5%8C%85#%E5%AD%97%E4%BD%93) | | click(@Nullable ClickEvent 点击事件) | MutableComponent | 设置文本点击事件 | | hover(@Nullable Component s) | MutableComponent | 把当前组件的鼠标悬浮提示文本设置为输入的。 | | setStyle(Style 样式) | MutableComponent | 将当前的样式设置为输入的样式(net.minecraft.network.chat.Style) 并返回自身。(不推荐使用) | | append(String 添加字符) | MutableComponent | 在当前的组件末尾拼接输入字符串对应的TextComponent并返回自身 | | append(Component component) | MutableComponent | 在当前组件的末尾拼接输入的组件并返回自身 | | withStyle(Style style) | MutableComponent | 将输入的样式与组件的已有样式合并,冲突的属性将被设置为输入样式。 | | getStyle() | Style | 返回当前组件的样式 | | getContents() | MutableComponent | 返回当前组件的内容。其中TextComponent会返回Text,SelectorComponents会返回其对应的目标选择器,其余类型的组件会返回一个空的字符串。 | | getSiblings() | List | 返回一个包含所有“使用append()添加到当前组件”的组件的列表。 | | plainCopy() | BaseComponent | 只复制当前组件的基本内容,不包含样式等。 | | copy() | MutableComponent | 完全复制当前组件,保留样式 | | getString() | String | 返回组件文本对应的字符串。非文字类型的组件会返回一个空的字符串。 | 待补:相关示例。 附: Component中可用的颜色: * black * darkBlue * darkGreen * darkAqua * darkRed * darkPurple * gold * gray * darkGray * blue * green * aqua * red * lightPurple * yellow * white --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.2-zu-jian-component-de-shi-yong.md --- # 20.2 组件(Component)的使用 *** 组件在(Component)1.18.2及以上版本的KubeJS中被大量使用。其可用于返回实体、物品的名称,亦或是设置悬浮提示,为玩家发送消息等等。 ## 一、基础使用 你可以使用`ComponentWrapper` 来新建组件,它可以在任何地方以`Component`或`Text`形式被调用,以下为其方法: | 方法名 | 返回值 | 描述 | | ---------------------------------------------------------------- | ---------------- | ----------------------------------------------------------- | | of(Object o) | MutableComponent | 该方法会返回一个基于你输入对象的组件。输入可以是字符串、数字等基础数据类型,也可以是snbt/nbt等其他类型。 | | clickEventOf(Object o) | ClickEvent | 基于输入的内容返回ClickEvent,详见下方示例。 | | prettyPrintNbt(Tag tag) | Component | 返回一个高亮的nbt,详见下方示例。 | | join(MutableComponent 分隔符, Iterable\ 待遍历内容) | MutableComponent | 返回遍历 `待遍历内容` 的结果并把它们拼接在一起,并使用 `分隔符` 将其隔开。 | | string(String 文本) | MutableComponent | 基于输入的字符串返回未格式化的TextComponent。 | | translate(String 本地化键名) | MutableComponent | 基于输入的本地化键名返回未格式化的TextComponent。 | | translate(String 本地化键名, Object... 文本) | MutableComponent | 基于输入的本地化键名和输入的对象返回未格式化的TextComponent,后者中的"%s"被本地化键名对应的译名替代。 | | keybind(String 键名) | MutableComponent | 基于输入的键名返回客户端对应热键的KeybindComponent。 | | (Object 文本) | MutableComponent | 基于输入的颜色和文本返回对应的组件,可用颜色详见本章末尾(不含尖括号)。 | 以下是示例: ``` onEvent('player.chat', event => { // 为玩家发送消息 event.player.tell(Component.string('测试消息')) // 该消息现在为黑色 event.player.tell(Component.black('黑色测试消息')) // 发送对应键名item.minecraft.diamond的文本 event.player.tell(Component.translate('item.minecraft.diamond').blue()) // 发送玩家潜行绑定的热键名称 event.player.tell(Component.keybind('key.sneak')) // 发送高亮后的主手物品的nbt event.player.tell(Component.prettyPrintNbt(event.player.mainHandItem.nbt)) }) ``` 效果展示 // img0 ## 二、可变组件(MutableComponent) 以下为MutableComponent支持的方法,其中包含ComponentKJS向原版Component注入的拓展。除相关原版方法外,你还可以调用以下由ComponentKJS添加的方法。 | 方法名 | 返回值 | 信息 | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------- | | iterator() | Iterator | 返回当前组件中包含的子Component。 | | self() | MutableComponent | 返回当前组件。 | | toJson() | JsonElement | 返回当前组件的Json形式。 | | () | MutableComponent | 设置当前组件的颜色,支持的颜色见下。 | | color(Color c) | MutableComponent | 设置当前组件的颜色(颜色为Rhino格式)。 | | noColor() | MutableComponent | 清除当前组件的颜色 | | bold(@Nullable Boolean 是否使用) italic(@Nullable Boolean 是否使用) underlined(@Nullable Boolean 是否使用) strikethrough(@Nullable Boolean 是否使用) obfuscated(@Nullable Boolean 是否使用) | MutableComponent | 将组件设定为给定格式(依次为加粗,斜体,下划线,删除线,拼合),不填时默认值为true | | insertion(@Nullable String 自动填充文字) | MutableComponent | 当玩家点击该组件时在聊天框自动填充给定的文本(不自动发送) | | font(@Nullable ResourceLocation 字体资源) | MutableComponent | 设置的字体。关于字体的更多信息详见[Minecraft Wiki - 资源包 - 字体](https://minecraft.fandom.com/zh/wiki/%E8%B5%84%E6%BA%90%E5%8C%85#%E5%AD%97%E4%BD%93) | | click(@Nullable ClickEvent 点击事件) | MutableComponent | 设置文本点击事件 | | hover(@Nullable Component s) | MutableComponent | 把当前组件的鼠标悬浮提示文本设置为输入的。 | | setStyle(Style 样式) | MutableComponent | 将当前的样式设置为输入的样式(net.minecraft.network.chat.Style) 并返回自身。(不推荐使用) | | append(String 添加字符) | MutableComponent | 在当前的组件末尾拼接输入字符串对应的TextComponent并返回自身 | | append(Component component) | MutableComponent | 在当前组件的末尾拼接输入的组件并返回自身 | | withStyle(Style style) | MutableComponent | 将输入的样式与组件的已有样式合并,冲突的属性将被设置为输入样式。 | | getStyle() | Style | 返回当前组件的样式 | | getContents() | MutableComponent | 返回当前组件的内容。其中TextComponent会返回Text,SelectorComponents会返回其对应的目标选择器,其余类型的组件会返回一个空的字符串。 | | getSiblings() | List | 返回一个包含所有“使用append()添加到当前组件”的组件的列表。 | | plainCopy() | BaseComponent | 只复制当前组件的基本内容,不包含样式等。 | | copy() | MutableComponent | 完全复制当前组件,保留样式 | | getString() | String | 返回组件文本对应的字符串。非文字类型的组件会返回一个空的字符串。 | 待补:相关示例。 附: Component中可用的颜色: * black * darkBlue * darkGreen * darkAqua * darkRed * darkPurple * gold * gray * darkGray * blue * green * aqua * red * lightPurple * yellow * white --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.3-ge-shi-bian-hua/README.md --- # 20.3 格式变化 *** 1.18中,KubeJS的部分方法和功能发生了变化,同时引入了部分新功能。 本节将指出一些新版本中的变化,并对新功能提供讲解。 --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.3-ge-shi-bian-hua/README.md --- # 20.3 格式变化 *** 1.18中,KubeJS的部分方法和功能发生了变化,同时引入了部分新功能。 本节将指出一些新版本中的变化,并对新功能提供讲解。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.3-ge-shi-bian-hua/20.3.1-shi-jie-sheng-cheng.md --- # 20.3.1 世界生成 *** ## 一、群系过滤器 1.18.2中,你可以使用群系类别(Biome Categories)或者群系类型(Biome type,仅Forge可用)来作为群系过滤器。 下方示例以添加矿石为例展示的群系过滤器的使用方法。 ``` onEvent('worldgen.add', event => { event.addOre((ore) => { // 1.16.5 及以上版本: ore.biomes = "minecraft:plains" // 仅在单一群系生成 ore.biomes = /^minecraft:.*/ // 在所有ID与给定格式相符的群系中生成(此处为在所有原版群系中生成) // 1.18.2 新增: ore.biomes = "^nether" // 在群系类别“nether”包含的所有群系中生成 ore.biomes = "$hot" // (仅Forge)在群系类型带有“hot”的所有群系中生成 // 过滤器可以通过与、或、非三种逻辑任意组合 ore.biomes = {} // 空的“与”过滤器(默认为真) ore.biomes = [] // 空的“或”过滤器(默认为真) ore.biomes = { not: "minecraft:ocean" // “非”过滤器(除"minecaraft:ocean"以外的群系) } // 因为“与”过滤器可以被表达为map,所以其还可以被表达为以下形式 ore.biomes = { id: "minecraft:plains", id: /^minecraft:.*/, // 正则表达式 category: "^nether", biome_type: "$hot", } // 你可以将以上的各种过滤器组合起来,它们会被独立计算 // 在下面这种情况中,矿石将在符合以下条件之一的群系中生成: // 1、平原 // 2、群系类型带有“hot”的非下界或热带草原类群系。 ore.biomes = [ "minecraft:plains", { biome_type: "$hot", not: [ '#nether', { category: "savanna" } ] }, ] }) }) ``` ## 二、规则测试和目标方块 在1.18中,世界生成变更为了“基于目标”的替换系统,也就是说在同一种结构中,你可以指定以特定方块来替换特定的目标方块。(例如在原版的生成矿石中,石头被普通矿石替代,而深板岩则被深板岩矿石所替代) 每个目标方块都有一个“规则测试”(RuleTest)作为输入(用于检测目标方块的方块状态是否应被替换)并生成一个特定的输出方块状态。在KubeJS脚本层面上,这两个概念都被表达为相同的类:*BlockStatePredicate*。 就格式上来说,BlockStatePredicate 与 群系过滤器 非常相似,即它们都能使用AND或者OR合并,并可用于匹配以下三种类型的条件: 1. 方块(该类型被解析为字符串,使用形如 `"minecraft:stone"`来匹配石头) 2. 方块状态(该类型被解析为后加数组形式的方块状态的方块ID字符串,使用形如`"minecraft:furnace[lit=true]"`来匹配燃烧中的熔炉方块。你可以使用F3界面来获得准星的方块状态,或者使用调试棒来获得方块状态可能的值。) 3. 方块标签(该类型被解析为**类似**标签的形式,使用形如`"#minecraft:base_stone_overworld"`来匹配主世界中生成在地下的所有类型的石头。**注意使用方块标签而不是物品标签**,部分情况下他们可能不同,你可以在F3界面获得方块标签。) 注: * 你可以在方块过滤器中使用正则表达式,`/^mekanism:.*_ore$/`将匹配所有通用机械中以`_ore`结尾的方块。注意,这种写法不会匹配方块状态! * 你可以使用JavaScript Object来提供规则测试(它将会像原版解析JSON或NBT那样被解析)。例如,你可以通过这种方式设置匹配生效的概率。 ## 三、高度提供器 另一个有些复杂的系统可能是“高度提供器”(Height Providers)系统,其被用于判定指定方块可能生成的Y坐标值和生成频率。与该系统同时使用的还有“垂直锚”(Vertical Anchors)概念,其可以用于获取相对于某锚点的相对高度(例如相对于世界最高点或最低点)。 在KubeJS中,该系统被简化以使其更容易在脚本开发中被使用:你可以使用`AddOreProperties`中的`uniformHeight` 和 `traingleHeight`方法来使用两种最常见的矿石生成逻辑(Uniform:在两个锚点间的任意位置有等大的概率生成矿石。Triangle:生成概率从两个锚点的中点向两锚点递减)。垂直锚概念也被简化了,你可以使用`AddOreProperties`中的辅助方法`aboveBottom / belowTop`(在世界最低点以上 / 在世界最高点以下)或新版本KubeJS中的`VerticalAnchor`Wrapper Class;此外,你还可以以数字形式直接指定绝对高度 ## 四、群系类别(Biome Categories) 群系类别是可用于将群系大致分为预定义的类别(具体类型见下)的原版系统(其他模组也可能添加更多的类别) ``` 原版中的群系类别: taiga extreme_hills jungle mesa plains savanna icy the_end beach forest ocean desert river swamp mushroom nether ``` ## 五、群系类型(Biome type,仅Forge可用) 类似于原版的群系类别(Biome Categories),Forge使用”群系字典“来根据群系的属性分类群系(其他模组也可能添加更多的类型) ``` 基于温度的标签,均不指定则默认为温带: HOT COLD 基于群系的植被密度的标签,均不指定则默认为中等密度: SPARSE DENSE 基于群系的湿度的标签,均不指定则默认为中等湿度: WET DRY 基于群系的树类型的标签,SAVANNA指沙漠类型的树木(比如金合欢),CONIFEROUS指积雪的树木(如云杉树),JUNGLE指从林木,均不指定则默认为温带树木(例如橡树): SAVANNA CONIFEROUS JUNGLE 基于群系的性质的标签: SPOOKY DEAD LUSH MUSHROOM MAGICAL RARE PLATEAU MODIFIED OCEAN RIVER OCEAN和RIVER的合并标签: WATER 通用类型标签 MESA FOREST PLAINS HILLS SWAMP SANDY SNOWY WASTELAND BEACH VOID UNDERGROUND 基于山地类型的标签 PEAK SLOPE MOUNTAIN 基于群系所处维度的标签 OVERWORLD NETHER END ``` ## 六、示例脚本 ``` onEvent('worldgen.add', event => { // 获取垂直锚 const {anchors} = event event.addOre((ore) => { ore.id = "kubejs:glowstone_test_lmao" // 指定当前结构的ID(可选,但推荐指定) ore.biomes = { not: "^savanna" // 群系过滤器(可选) } // 添加目标示例 ore.addTarget('#minecraft:stone_ore_replaceables', 'minecraft:glowstone') // 将目标(#minecraft:stone_ore_replaceables标签下的方块)替换为minecraft:glowstone ore.addTarget('minecraft:deepslate', 'minecraft:nether_wart_block') // 将目标(minecraft:deepslate)替换为minecraft:nether_wart_block ore.addTarget([ 'minecraft:gravel', /minecraft:(.*)_dirt/ ], 'minecraft:tnt') // 将目标(minecraft:gravel和所有类型的泥土)替换为minecraft:tnt ore.count([15, 50]) // 设置矿簇的大小(15~50中随机选择),在此处你可以使用单个数字来生成固定数量的方块。 .squared() // 设置跨区块生成矿石 .triangleHeight( // (高度提供器)使用Triangular分布模式生成矿石(具体见上文) anchors.aboveBottom(32), // 下边界锚,在世界最低高度(Y=-64)以上32格(即Y=-32) anchors.absolute(96) // 上边界锚,固定高度(Y = 96) ) // 总的来说,矿石可能在Y = -32 ~ 96范围内生成,概率最大高度为Y = (-32 + 96) / 2 = 32 // 更多可选的参数,以下均为默认值 ore.size = 9 // 最大矿簇大小 ore.noSurface = 0.5 // 暴露于空气中的概率 ore.worldgenLayer = "underground_ores" // 矿石生成于世界生成中的阶段 ore.chance = 0 // 当该选项不为0且ore.count未被设置矿石将有1/n的概率在每个区块中生成 }) // 添加湖 // 该功能将要于原版中废弃 event.addLake((lake) => { lake.id = "kubejs:funny_lake" // BlockStatePredicate lake.chance = 4 lake.fluid = "minecraft:lava" lake.barrier = "minecraft:diamond_block" }) }) onEvent('worldgen.remove', event => { console.info("HELP") //console.debugEnabled = true; // 打印给定群系过滤器中的所有结构 event.printFeatures('', 'minecraft:plains') event.removeOres((props) => { // 类似于添加矿石,移除矿石同样支持使用世界生成阶段 props.worldgenLayer = 'underground_ores' // 使用群系过滤 props.biomes = [{ category: "icy", }, { category: "savanna", }, { category: "mesa", }]; // 在上述给定的群系中移除铁矿和铜矿 // 注:此处无法使用标签 props.blocks = ["minecraft:iron_ore", "minecraft:copper_ore"] }) // 通过指定ID来移除结构(第一个参数为生成步骤) event.removeFeatureById('underground_ores', ['minecraft:ore_coal_upper', 'minecraft:ore_coal_lower']) }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.3-ge-shi-bian-hua/20.3.1-shi-jie-sheng-cheng.md --- # 20.3.1 世界生成 *** ## 一、群系过滤器 1.18.2中,你可以使用群系类别(Biome Categories)或者群系类型(Biome type,仅Forge可用)来作为群系过滤器。 下方示例以添加矿石为例展示的群系过滤器的使用方法。 ``` onEvent('worldgen.add', event => { event.addOre((ore) => { // 1.16.5 及以上版本: ore.biomes = "minecraft:plains" // 仅在单一群系生成 ore.biomes = /^minecraft:.*/ // 在所有ID与给定格式相符的群系中生成(此处为在所有原版群系中生成) // 1.18.2 新增: ore.biomes = "^nether" // 在群系类别“nether”包含的所有群系中生成 ore.biomes = "$hot" // (仅Forge)在群系类型带有“hot”的所有群系中生成 // 过滤器可以通过与、或、非三种逻辑任意组合 ore.biomes = {} // 空的“与”过滤器(默认为真) ore.biomes = [] // 空的“或”过滤器(默认为真) ore.biomes = { not: "minecraft:ocean" // “非”过滤器(除"minecaraft:ocean"以外的群系) } // 因为“与”过滤器可以被表达为map,所以其还可以被表达为以下形式 ore.biomes = { id: "minecraft:plains", id: /^minecraft:.*/, // 正则表达式 category: "^nether", biome_type: "$hot", } // 你可以将以上的各种过滤器组合起来,它们会被独立计算 // 在下面这种情况中,矿石将在符合以下条件之一的群系中生成: // 1、平原 // 2、群系类型带有“hot”的非下界或热带草原类群系。 ore.biomes = [ "minecraft:plains", { biome_type: "$hot", not: [ '#nether', { category: "savanna" } ] }, ] }) }) ``` ## 二、规则测试和目标方块 在1.18中,世界生成变更为了“基于目标”的替换系统,也就是说在同一种结构中,你可以指定以特定方块来替换特定的目标方块。(例如在原版的生成矿石中,石头被普通矿石替代,而深板岩则被深板岩矿石所替代) 每个目标方块都有一个“规则测试”(RuleTest)作为输入(用于检测目标方块的方块状态是否应被替换)并生成一个特定的输出方块状态。在KubeJS脚本层面上,这两个概念都被表达为相同的类:*BlockStatePredicate*。 就格式上来说,BlockStatePredicate 与 群系过滤器 非常相似,即它们都能使用AND或者OR合并,并可用于匹配以下三种类型的条件: 1. 方块(该类型被解析为字符串,使用形如 `"minecraft:stone"`来匹配石头) 2. 方块状态(该类型被解析为后加数组形式的方块状态的方块ID字符串,使用形如`"minecraft:furnace[lit=true]"`来匹配燃烧中的熔炉方块。你可以使用F3界面来获得准星的方块状态,或者使用调试棒来获得方块状态可能的值。) 3. 方块标签(该类型被解析为**类似**标签的形式,使用形如`"#minecraft:base_stone_overworld"`来匹配主世界中生成在地下的所有类型的石头。**注意使用方块标签而不是物品标签**,部分情况下他们可能不同,你可以在F3界面获得方块标签。) 注: * 你可以在方块过滤器中使用正则表达式,`/^mekanism:.*_ore$/`将匹配所有通用机械中以`_ore`结尾的方块。注意,这种写法不会匹配方块状态! * 你可以使用JavaScript Object来提供规则测试(它将会像原版解析JSON或NBT那样被解析)。例如,你可以通过这种方式设置匹配生效的概率。 ## 三、高度提供器 另一个有些复杂的系统可能是“高度提供器”(Height Providers)系统,其被用于判定指定方块可能生成的Y坐标值和生成频率。与该系统同时使用的还有“垂直锚”(Vertical Anchors)概念,其可以用于获取相对于某锚点的相对高度(例如相对于世界最高点或最低点)。 在KubeJS中,该系统被简化以使其更容易在脚本开发中被使用:你可以使用`AddOreProperties`中的`uniformHeight` 和 `traingleHeight`方法来使用两种最常见的矿石生成逻辑(Uniform:在两个锚点间的任意位置有等大的概率生成矿石。Triangle:生成概率从两个锚点的中点向两锚点递减)。垂直锚概念也被简化了,你可以使用`AddOreProperties`中的辅助方法`aboveBottom / belowTop`(在世界最低点以上 / 在世界最高点以下)或新版本KubeJS中的`VerticalAnchor`Wrapper Class;此外,你还可以以数字形式直接指定绝对高度 ## 四、群系类别(Biome Categories) 群系类别是可用于将群系大致分为预定义的类别(具体类型见下)的原版系统(其他模组也可能添加更多的类别) ``` 原版中的群系类别: taiga extreme_hills jungle mesa plains savanna icy the_end beach forest ocean desert river swamp mushroom nether ``` ## 五、群系类型(Biome type,仅Forge可用) 类似于原版的群系类别(Biome Categories),Forge使用”群系字典“来根据群系的属性分类群系(其他模组也可能添加更多的类型) ``` 基于温度的标签,均不指定则默认为温带: HOT COLD 基于群系的植被密度的标签,均不指定则默认为中等密度: SPARSE DENSE 基于群系的湿度的标签,均不指定则默认为中等湿度: WET DRY 基于群系的树类型的标签,SAVANNA指沙漠类型的树木(比如金合欢),CONIFEROUS指积雪的树木(如云杉树),JUNGLE指从林木,均不指定则默认为温带树木(例如橡树): SAVANNA CONIFEROUS JUNGLE 基于群系的性质的标签: SPOOKY DEAD LUSH MUSHROOM MAGICAL RARE PLATEAU MODIFIED OCEAN RIVER OCEAN和RIVER的合并标签: WATER 通用类型标签 MESA FOREST PLAINS HILLS SWAMP SANDY SNOWY WASTELAND BEACH VOID UNDERGROUND 基于山地类型的标签 PEAK SLOPE MOUNTAIN 基于群系所处维度的标签 OVERWORLD NETHER END ``` ## 六、示例脚本 ``` onEvent('worldgen.add', event => { // 获取垂直锚 const {anchors} = event event.addOre((ore) => { ore.id = "kubejs:glowstone_test_lmao" // 指定当前结构的ID(可选,但推荐指定) ore.biomes = { not: "^savanna" // 群系过滤器(可选) } // 添加目标示例 ore.addTarget('#minecraft:stone_ore_replaceables', 'minecraft:glowstone') // 将目标(#minecraft:stone_ore_replaceables标签下的方块)替换为minecraft:glowstone ore.addTarget('minecraft:deepslate', 'minecraft:nether_wart_block') // 将目标(minecraft:deepslate)替换为minecraft:nether_wart_block ore.addTarget([ 'minecraft:gravel', /minecraft:(.*)_dirt/ ], 'minecraft:tnt') // 将目标(minecraft:gravel和所有类型的泥土)替换为minecraft:tnt ore.count([15, 50]) // 设置矿簇的大小(15~50中随机选择),在此处你可以使用单个数字来生成固定数量的方块。 .squared() // 设置跨区块生成矿石 .triangleHeight( // (高度提供器)使用Triangular分布模式生成矿石(具体见上文) anchors.aboveBottom(32), // 下边界锚,在世界最低高度(Y=-64)以上32格(即Y=-32) anchors.absolute(96) // 上边界锚,固定高度(Y = 96) ) // 总的来说,矿石可能在Y = -32 ~ 96范围内生成,概率最大高度为Y = (-32 + 96) / 2 = 32 // 更多可选的参数,以下均为默认值 ore.size = 9 // 最大矿簇大小 ore.noSurface = 0.5 // 暴露于空气中的概率 ore.worldgenLayer = "underground_ores" // 矿石生成于世界生成中的阶段 ore.chance = 0 // 当该选项不为0且ore.count未被设置矿石将有1/n的概率在每个区块中生成 }) // 添加湖 // 该功能将要于原版中废弃 event.addLake((lake) => { lake.id = "kubejs:funny_lake" // BlockStatePredicate lake.chance = 4 lake.fluid = "minecraft:lava" lake.barrier = "minecraft:diamond_block" }) }) onEvent('worldgen.remove', event => { console.info("HELP") //console.debugEnabled = true; // 打印给定群系过滤器中的所有结构 event.printFeatures('', 'minecraft:plains') event.removeOres((props) => { // 类似于添加矿石,移除矿石同样支持使用世界生成阶段 props.worldgenLayer = 'underground_ores' // 使用群系过滤 props.biomes = [{ category: "icy", }, { category: "savanna", }, { category: "mesa", }]; // 在上述给定的群系中移除铁矿和铜矿 // 注:此处无法使用标签 props.blocks = ["minecraft:iron_ore", "minecraft:copper_ore"] }) // 通过指定ID来移除结构(第一个参数为生成步骤) event.removeFeatureById('underground_ores', ['minecraft:ore_coal_upper', 'minecraft:ore_coal_lower']) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.3-ge-shi-bian-hua/20.3.2-fang-kuai-wu-pin-zhu-ce.md --- # 20.3.2 方块 / 物品注册 *** 在1.18.2中,物品注册中的 `type('itemType')` 方法被移除,其注册方式变更为: ``` // 1.18.2 onEvent('item.registry', event => { event.create('blockid','itemType').其他方法() }) // 1.16.5 onEvent('item.registry', event => { event.create('blockid').type('itemType').其他方法() }) ``` 除此之外,1.18.2的方块 / 物品注册相较于1.16.5具有相同的写法,且没有任何新的发展或显著的变化。 --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.3-ge-shi-bian-hua/20.3.2-fang-kuai-wu-pin-zhu-ce.md --- # 20.3.2 方块 / 物品注册 *** 在1.18.2中,物品注册中的 `type('itemType')` 方法被移除,其注册方式变更为: ``` // 1.18.2 onEvent('item.registry', event => { event.create('blockid','itemType').其他方法() }) // 1.16.5 onEvent('item.registry', event => { event.create('blockid').type('itemType').其他方法() }) ``` 除此之外,1.18.2的方块 / 物品注册相较于1.16.5具有相同的写法,且没有任何新的发展或显著的变化。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.3-ge-shi-bian-hua/20.3.3-shi-jian.md --- # 20.3.3 事件 *** ## 一、事件列表 1.18.2中,所有世界事件(`world.*`)均被替换为了(`level.*`),实体受伤事件由 `entity.damage` 更改为 `entity.hurt`。 以下为详细事件列表 | 事件ID | 描述 | | ------------------------------- | ---------------- | | postinit | - | | loaded | - | | command.registry | - | | command.run | 命令执行 | | init | - | | ***客户端侧*** | - | | client.init | - | | client.debug\_info.left | 设置客户端左侧调试信息 | | client.debug\_info.right | 设置客户右侧侧调试信息 | | client.logged\_in | 客户端登入 | | client.logged\_out | 客户端登出 | | client.tick | 客户端 Tick 事件 | | client.paint\_screen | 客户端 Painter | | client.painter\_updated | 客户端 Painter 更新事件 | | client.generate\_assets | - | | ***服务端事件*** | - | | server.load | - | | server.unload | - | | server.tick | 服务端 Tick 事件 | | server.datapack.high\_priority | 服务端数据包加载(高优先度) | | server.datapack.low\_priority | 服务端数据包加载(低优先度) | | server.custom\_command | 服务端自定义指令 | | recipes | 配方事件 | | recipes.after\_load | 配方加载后事件 | | recipes.serializer.special.flag | - | | recipes.compostables | - | | recipes.type\_registry | 配方类型注册 | | worldgen.add | 世界生成添加结构事件 | | worldgen.remove | 世界生成移除结构事件 | | ***世界事件*** | - | | level.load | 世界加载事件 | | level.unload | 世界卸载事件 | | level.tick | 世界 Tick 事件 | | level.explosion.pre | 世界爆炸前事件 | | level.explosion.post | 世界爆炸后事件 | | ***玩家事件*** | - | | player.logged\_in | 玩家登入事件 | | player.logged\_out | 玩家登出事件 | | player.tick | 玩家 Tick 事件 | | player.data\_from\_server | 客户端侧从服务端接收信息 | | player.data\_from\_client | 服务端侧从客户端接收信息 | | player.chat | 玩家聊天事件 | | player.advancement | 玩家成就 | | player.inventory.opened | 玩家打开背包 | | player.inventory.closed | 玩家关闭背包 | | player.inventory.changed | 玩家库存改变 | | player.chest.opened | 玩家打开箱子 | | player.chest.closed | 玩家关闭箱子 | | player.check\_login | 玩家登入控制 | | ***实体事件*** | | | entity.death | 实体死亡 | | entity.hurt | 实体受伤 | | entity.drops | 实体坠落 | | entity.check\_spawn | 实体生成控制 | | entity.spawned | 实体生成 | | ***方块交互事件*** | | | block.missing\_mappings | - | | block.right\_click | 方块右键事件 | | block.left\_click | 方块左键事件 | | block.place | 方块放置事件 | | block.break | 方块破坏事件 | | block.modification | 方块属性修改 | | ***物品交互事件*** | - | | item.missing\_mappings | - | | item.right\_click | 物品右键事件 | | item.right\_click\_empty | 空手右键事件 | | item.left\_click | 物品左键事件 | | item.entity\_interact | 物品实体交互事件 | | item.pickup | 物品捡起事件 | | item.toss | 物品扔出事件 | | item.crafted | 物品合成事件 | | item.smelted | 物品烧炼事件 | | item.destroyed | 物品损坏事件 | | item.food\_eaten | 食物物品食用事件 | | item.tooltip | 物品鼠标悬浮文本 | | item.modification | 物品属性修改 | | item.model\_properties | 物品模型属性 | | ***声音事件*** | - | | sound.registry | 声音注册 | 你还可以在[此处](https://kubejs.com/wiki/kubejs/)查询部分类和部分事件的方法等。 ## 二、Forge事件 在1.16.5中当调用Minecraft类中的方法等内容时,你应该使用其SRG名而不是IDE中看到的名称,**而在1.18.2中,你可以直接使用IDE中看到的方法名称而不必使用SRG名。** 除上述改变之外,1.18.2的事件监听部分相较于1.16.5具有相同的写法,且没有任何新的发展或显著的变化。 --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.3-ge-shi-bian-hua/20.3.3-shi-jian.md --- # 20.3.3 事件 *** ## 一、事件列表 1.18.2中,所有世界事件(`world.*`)均被替换为了(`level.*`),实体受伤事件由 `entity.damage` 更改为 `entity.hurt`。 以下为详细事件列表 | 事件ID | 描述 | | ------------------------------- | ---------------- | | postinit | - | | loaded | - | | command.registry | - | | command.run | 命令执行 | | init | - | | ***客户端侧*** | - | | client.init | - | | client.debug\_info.left | 设置客户端左侧调试信息 | | client.debug\_info.right | 设置客户右侧侧调试信息 | | client.logged\_in | 客户端登入 | | client.logged\_out | 客户端登出 | | client.tick | 客户端 Tick 事件 | | client.paint\_screen | 客户端 Painter | | client.painter\_updated | 客户端 Painter 更新事件 | | client.generate\_assets | - | | ***服务端事件*** | - | | server.load | - | | server.unload | - | | server.tick | 服务端 Tick 事件 | | server.datapack.high\_priority | 服务端数据包加载(高优先度) | | server.datapack.low\_priority | 服务端数据包加载(低优先度) | | server.custom\_command | 服务端自定义指令 | | recipes | 配方事件 | | recipes.after\_load | 配方加载后事件 | | recipes.serializer.special.flag | - | | recipes.compostables | - | | recipes.type\_registry | 配方类型注册 | | worldgen.add | 世界生成添加结构事件 | | worldgen.remove | 世界生成移除结构事件 | | ***世界事件*** | - | | level.load | 世界加载事件 | | level.unload | 世界卸载事件 | | level.tick | 世界 Tick 事件 | | level.explosion.pre | 世界爆炸前事件 | | level.explosion.post | 世界爆炸后事件 | | ***玩家事件*** | - | | player.logged\_in | 玩家登入事件 | | player.logged\_out | 玩家登出事件 | | player.tick | 玩家 Tick 事件 | | player.data\_from\_server | 客户端侧从服务端接收信息 | | player.data\_from\_client | 服务端侧从客户端接收信息 | | player.chat | 玩家聊天事件 | | player.advancement | 玩家成就 | | player.inventory.opened | 玩家打开背包 | | player.inventory.closed | 玩家关闭背包 | | player.inventory.changed | 玩家库存改变 | | player.chest.opened | 玩家打开箱子 | | player.chest.closed | 玩家关闭箱子 | | player.check\_login | 玩家登入控制 | | ***实体事件*** | | | entity.death | 实体死亡 | | entity.hurt | 实体受伤 | | entity.drops | 实体坠落 | | entity.check\_spawn | 实体生成控制 | | entity.spawned | 实体生成 | | ***方块交互事件*** | | | block.missing\_mappings | - | | block.right\_click | 方块右键事件 | | block.left\_click | 方块左键事件 | | block.place | 方块放置事件 | | block.break | 方块破坏事件 | | block.modification | 方块属性修改 | | ***物品交互事件*** | - | | item.missing\_mappings | - | | item.right\_click | 物品右键事件 | | item.right\_click\_empty | 空手右键事件 | | item.left\_click | 物品左键事件 | | item.entity\_interact | 物品实体交互事件 | | item.pickup | 物品捡起事件 | | item.toss | 物品扔出事件 | | item.crafted | 物品合成事件 | | item.smelted | 物品烧炼事件 | | item.destroyed | 物品损坏事件 | | item.food\_eaten | 食物物品食用事件 | | item.tooltip | 物品鼠标悬浮文本 | | item.modification | 物品属性修改 | | item.model\_properties | 物品模型属性 | | ***声音事件*** | - | | sound.registry | 声音注册 | 你还可以在[此处](https://kubejs.com/wiki/kubejs/)查询部分类和部分事件的方法等。 ## 二、Forge事件 在1.16.5中当调用Minecraft类中的方法等内容时,你应该使用其SRG名而不是IDE中看到的名称,**而在1.18.2中,你可以直接使用IDE中看到的方法名称而不必使用SRG名。** 除上述改变之外,1.18.2的事件监听部分相较于1.16.5具有相同的写法,且没有任何新的发展或显著的变化。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.3-ge-shi-bian-hua/20.3.4-global-de-shi-yong.md --- # 20.3.4 global\[] 的使用 *** global\[] 可以允许你在不重启游戏的情况下重新加载**部分**Startup阶段的脚本,或用于储存信息等。 ## 一、Forge事件 以1.16.5中的Forge事件为例,其可改写为 ``` // 使用global改写前 onForgeEvent("net.minecraftforge.event.entity.player.PlayerEvent$PlayerChangedDimensionEvent", event=>{ event.player.sendStatusMessage("你来到了" + event.getTo().getLocation(), false) }) // 使用global改写后 onForgeEvent('net.minecraftforge.event.entity.player.PlayerEvent$PlayerChangedDimensionEvent', event => { global['test_id'](event); }); global['test_id'] = (event) => { event.player.sendStatusMessage("你来到了" + event.getTo().getLocation(), false) } ``` 改写后,你可以修改 `global['test_id']`的内容来在不重启游戏的情况下重新加载Forge事件内容,你可以使用 `/kubejs reload startup_scripts` 来重载该函数。 ## 二、内容注册中的部分事件 类似地,部分内容注册中的事件也可以使用global改写(例如第七章 自定义方块中使用到的`RandomTickEvent` ): ``` // 使用global改写前 onEvent('block.registry', event => { event.create('test_block_randomTickEvent').displayName('Test Block randomTickEvent').randomTick(randomTickEvent => { randomTickEvent.block.set('minecraft:dirt'); }) }) }) // 使用global改写后 onEvent('block.registry', event => { event.create('test_block_randomTickEvent').displayName('Test Block randomTickEvent').randomTick(randomTickEvent => { global['test_id'](event); }) }) }) global['test_id'] = (event) => { randomTickEvent.block.set('minecraft:dirt'); } ``` 与之类似的还有食用食物的`ItemFoodEatenEvent`等,在此就不再赘述了。 ## 三、存储数据 以下以存储玩家登入信息为例: ``` global['login_info'] = {} // 获取格式化时间 var getFormattedTime = function(date){ let currYear = date.getFullYear(); let currMonth = date.getMonth() + 1; let currDay = date.getDate(); let currHour = date.getHours(); let currMinutes = date.getMinutes(); let currSeconds = date.getSeconds(); currMonth = (currMonth > 9) ? currMonth:"0" + currMonth; currDay = (currDay > 9) ? currDay:"0" + currDay; currHour = (currHour > 9) ? currHour:"0" + currHour; currMinutes = (currMinutes > 9) ? currMinutes:"0" + currMinutes; currSeconds = (currSeconds > 9) ? currSeconds:"0" + currSeconds; return `${currYear}-${currMonth}-${currDay} ${currHour}:${currMinutes}:${currSeconds}`; } // 玩家登入时记录信息 onEvent('player.logged_in',event => { let date = getFormattedTime(new Date()); let p = event.player; global['login_info'][event.player.profile.name] = {time : date, pos: {x : p.x, y : p.y, z : p.z, dim : p.level.dimension}}; }) // 玩家退出时记录信息 onEvent('player.logged_out',event => { global['login_info'][event.player.profile.name] = null; }) // 聊天信息获取 onEvent('player.chat',event => { let input = event.message.trim(); if(input.substring(0,8) == "gameinfo"){ // 传入登入信息对象 event.server.scheduleInTicks(1, global['login_info'], function (callback) { let playerID = input.substring(9,); if(callback.data[playerID] == undefined){ event.player.tell(Component.gold('====查询结果====')) event.player.tell(Component.red("未查询到该玩家在线信息!")); return; } let info = callback.data[playerID]; let playerTime = info.time; let pos = info.pos; event.player.tell(Component.gold('====查询结果====')); event.player.tell(`玩家id:${playerID}\n登入时间:${playerTime}\n登入坐标:x = ${pos.x}, y = ${pos.y}, z = ${pos.z}\n登入维度:${pos.dim}`); }) } }) ``` 效果: ![](https://s1.ax1x.com/2022/11/28/zaB35R.png) --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.3-ge-shi-bian-hua/20.3.4-global-de-shi-yong.md --- # 20.3.4 global\[] 的使用 *** global\[] 可以允许你在不重启游戏的情况下重新加载**部分**Startup阶段的脚本,或用于储存信息等。 ## 一、Forge事件 以1.16.5中的Forge事件为例,其可改写为 ``` // 使用global改写前 onForgeEvent("net.minecraftforge.event.entity.player.PlayerEvent$PlayerChangedDimensionEvent", event=>{ event.player.sendStatusMessage("你来到了" + event.getTo().getLocation(), false) }) // 使用global改写后 onForgeEvent('net.minecraftforge.event.entity.player.PlayerEvent$PlayerChangedDimensionEvent', event => { global['test_id'](event); }); global['test_id'] = (event) => { event.player.sendStatusMessage("你来到了" + event.getTo().getLocation(), false) } ``` 改写后,你可以修改 `global['test_id']`的内容来在不重启游戏的情况下重新加载Forge事件内容,你可以使用 `/kubejs reload startup_scripts` 来重载该函数。 ## 二、内容注册中的部分事件 类似地,部分内容注册中的事件也可以使用global改写(例如第七章 自定义方块中使用到的`RandomTickEvent` ): ``` // 使用global改写前 onEvent('block.registry', event => { event.create('test_block_randomTickEvent').displayName('Test Block randomTickEvent').randomTick(randomTickEvent => { randomTickEvent.block.set('minecraft:dirt'); }) }) }) // 使用global改写后 onEvent('block.registry', event => { event.create('test_block_randomTickEvent').displayName('Test Block randomTickEvent').randomTick(randomTickEvent => { global['test_id'](event); }) }) }) global['test_id'] = (event) => { randomTickEvent.block.set('minecraft:dirt'); } ``` 与之类似的还有食用食物的`ItemFoodEatenEvent`等,在此就不再赘述了。 ## 三、存储数据 以下以存储玩家登入信息为例: ``` global['login_info'] = {} // 获取格式化时间 var getFormattedTime = function(date){ let currYear = date.getFullYear(); let currMonth = date.getMonth() + 1; let currDay = date.getDate(); let currHour = date.getHours(); let currMinutes = date.getMinutes(); let currSeconds = date.getSeconds(); currMonth = (currMonth > 9) ? currMonth:"0" + currMonth; currDay = (currDay > 9) ? currDay:"0" + currDay; currHour = (currHour > 9) ? currHour:"0" + currHour; currMinutes = (currMinutes > 9) ? currMinutes:"0" + currMinutes; currSeconds = (currSeconds > 9) ? currSeconds:"0" + currSeconds; return `${currYear}-${currMonth}-${currDay} ${currHour}:${currMinutes}:${currSeconds}`; } // 玩家登入时记录信息 onEvent('player.logged_in',event => { let date = getFormattedTime(new Date()); let p = event.player; global['login_info'][event.player.profile.name] = {time : date, pos: {x : p.x, y : p.y, z : p.z, dim : p.level.dimension}}; }) // 玩家退出时记录信息 onEvent('player.logged_out',event => { global['login_info'][event.player.profile.name] = null; }) // 聊天信息获取 onEvent('player.chat',event => { let input = event.message.trim(); if(input.substring(0,8) == "gameinfo"){ // 传入登入信息对象 event.server.scheduleInTicks(1, global['login_info'], function (callback) { let playerID = input.substring(9,); if(callback.data[playerID] == undefined){ event.player.tell(Component.gold('====查询结果====')) event.player.tell(Component.red("未查询到该玩家在线信息!")); return; } let info = callback.data[playerID]; let playerTime = info.time; let pos = info.pos; event.player.tell(Component.gold('====查询结果====')); event.player.tell(`玩家id:${playerID}\n登入时间:${playerTime}\n登入坐标:x = ${pos.x}, y = ${pos.y}, z = ${pos.z}\n登入维度:${pos.dim}`); }) } }) ``` 效果: ![](https://s1.ax1x.com/2022/11/28/zaB35R.png) --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.4-ming-ling-zhu-ce.md --- # 20.4 命令注册 *** 1.18中KubeJS完善了其命令注册功能。通过监听`command.registry`事件,你可以创建你自己的指令: ``` onEvent("command.registry", event => {//监听命令注册事件 const { commands: Commands, arguments: Arguments} = event; event.register(// 注册新命令 Commands.literal("myCommand")// 命令名称为myCommand .requires(src => src.hasPermission(2))//2代表权限需求为管理员权限,该行可以省略。你还可以将其扩写并使用return关键字来实现更加复杂的权限检查。 .then(Commands.argument('arg1', Arguments.STRING.create(event))// 添加字符串类型参数arg1,你可以添加任何数量的参数。 .then(Commands.argument('arg2', Arguments.FLOAT.create(event))// 添加字符串类型参数arg2,你可以添加任何数量的参数。其他类型详见ProbeJS提供的代码提示。 .executes(ctx => {// 执行命令时执行以下内容 const arg1 = Arguments.STRING.getResult(ctx, "arg1");// 获取arg1的值 const arg2 = Arguments.FLOAT.getResult(ctx, "arg2");// 获取arg2的值 if(arg1 == "example") return 0;// 当arg1的值为"example"时跳出函数。返回0代表命令不生效。 let level = ctx.source.level.asKJS() // 获取当前level let position = ctx.source.position // 获取当前position // 对范围内实体造成伤害 let i = 0 // 伤害计数器 level.getEntitiesWithin(AABB.of(position.x()-2,position.y()-2,position.z()-2,position.x()+2,position.y()+2,position.z()+2)).forEach(entity => {// 获取当前position,4*4*4范围内的实体 if (entity.living) { // 伤害实体,计数器+1 // 伤害值为arg2的数值 entity.attack(arg2) i++ // 如果实体类型为玩家则告知其arg1的值 if (entity.type == "minecraft:player") entity.tell(arg1) } }) return i // 返回计数器 }) ) ) ) }) ``` 本节参考了:[Command Registry | latvian.dev](https://mods.latvian.dev/books/kubejs/page/command-registry) 另请参阅:ProbeJS。 --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.4-ming-ling-zhu-ce.md --- # 20.4 命令注册 *** 1.18中KubeJS完善了其命令注册功能。通过监听`command.registry`事件,你可以创建你自己的指令: ``` onEvent("command.registry", event => {//监听命令注册事件 const { commands: Commands, arguments: Arguments} = event; event.register(// 注册新命令 Commands.literal("myCommand")// 命令名称为myCommand .requires(src => src.hasPermission(2))//2代表权限需求为管理员权限,该行可以省略。你还可以将其扩写并使用return关键字来实现更加复杂的权限检查。 .then(Commands.argument('arg1', Arguments.STRING.create(event))// 添加字符串类型参数arg1,你可以添加任何数量的参数。 .then(Commands.argument('arg2', Arguments.FLOAT.create(event))// 添加字符串类型参数arg2,你可以添加任何数量的参数。其他类型详见ProbeJS提供的代码提示。 .executes(ctx => {// 执行命令时执行以下内容 const arg1 = Arguments.STRING.getResult(ctx, "arg1");// 获取arg1的值 const arg2 = Arguments.FLOAT.getResult(ctx, "arg2");// 获取arg2的值 if(arg1 == "example") return 0;// 当arg1的值为"example"时跳出函数。返回0代表命令不生效。 let level = ctx.source.level.asKJS() // 获取当前level let position = ctx.source.position // 获取当前position // 对范围内实体造成伤害 let i = 0 // 伤害计数器 level.getEntitiesWithin(AABB.of(position.x()-2,position.y()-2,position.z()-2,position.x()+2,position.y()+2,position.z()+2)).forEach(entity => {// 获取当前position,4*4*4范围内的实体 if (entity.living) { // 伤害实体,计数器+1 // 伤害值为arg2的数值 entity.attack(arg2) i++ // 如果实体类型为玩家则告知其arg1的值 if (entity.type == "minecraft:player") entity.tell(arg1) } }) return i // 返回计数器 }) ) ) ) }) ``` 本节参考了:[Command Registry | latvian.dev](https://mods.latvian.dev/books/kubejs/page/command-registry) 另请参阅:ProbeJS。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.5-fan-she-yu-java-access.md --- # 20.5 反射与Java access *** KubeJS对反射的支持很有限。一般情况下不推荐使用反射(除非KubeJS不支持某些功能)。 在1.18.2中,Minecraft的类会在运行时被重映射为MojMaps,因此你无需使用srg名。 在以下示例中,我们将使用通过反射获得的MaterialJS创建新的包含紫水晶破坏声音和Material的实例,并将其用于自定义方块注册。 ``` const MaterialJS = java("dev.latvian.mods.kubejs.block.MaterialJS") const Material = java('net.minecraft.world.level.material.Material') const SoundType = java('net.minecraft.world.level.block.SoundType') amethystMaterial = new MaterialJS('amethyst', Material.AMETHYST, SoundType.AMETHYST) // 字段对应的srg名:f_164531_和f_154654_ onEvent('block.registry', event => { event.create('amethyst_slab', 'slab') .material(amethystMaterial)// 使用amethystMaterial作为material的值。 .tagBlock('minecraft:crystal_sound_blocks') .tagBlock('minecraft:mineable/pickaxe') .requiresTool(true) .texture('texture', 'minecraft:block/amethyst_block') }) ``` 该过程会延长游戏启动时间约1~2秒(其他类型脚本的加载时间以毫秒计)。你应当在脚本开头导入你需要的类,而不是在事件当中,尤其是在事件多次触发的情况下。 本节参考了[Reflection / Java access | latvian.dev](https://mods.latvian.dev/books/kubejs/page/reflection-java-access) [KubeJS-Mods/KubeJS at eol/1.16 (github.com)](https://github.com/KubeJS-Mods/KubeJS/tree/eol/1.16) [KubeJS-Mods/KubeJS at 1.18/main (github.com)](https://github.com/KubeJS-Mods/KubeJS/tree/1.18/main) --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/migrate-to-1182/20.5-fan-she-yu-java-access.md --- # 20.5 反射与Java access *** KubeJS对反射的支持很有限。一般情况下不推荐使用反射(除非KubeJS不支持某些功能)。 在1.18.2中,Minecraft的类会在运行时被重映射为MojMaps,因此你无需使用srg名。 在以下示例中,我们将使用通过反射获得的MaterialJS创建新的包含紫水晶破坏声音和Material的实例,并将其用于自定义方块注册。 ``` const MaterialJS = java("dev.latvian.mods.kubejs.block.MaterialJS") const Material = java('net.minecraft.world.level.material.Material') const SoundType = java('net.minecraft.world.level.block.SoundType') amethystMaterial = new MaterialJS('amethyst', Material.AMETHYST, SoundType.AMETHYST) // 字段对应的srg名:f_164531_和f_154654_ onEvent('block.registry', event => { event.create('amethyst_slab', 'slab') .material(amethystMaterial)// 使用amethystMaterial作为material的值。 .tagBlock('minecraft:crystal_sound_blocks') .tagBlock('minecraft:mineable/pickaxe') .requiresTool(true) .texture('texture', 'minecraft:block/amethyst_block') }) ``` 该过程会延长游戏启动时间约1~2秒(其他类型脚本的加载时间以毫秒计)。你应当在脚本开头导入你需要的类,而不是在事件当中,尤其是在事件多次触发的情况下。 本节参考了[Reflection / Java access | latvian.dev](https://mods.latvian.dev/books/kubejs/page/reflection-java-access) [KubeJS-Mods/KubeJS at eol/1.16 (github.com)](https://github.com/KubeJS-Mods/KubeJS/tree/eol/1.16) [KubeJS-Mods/KubeJS at 1.18/main (github.com)](https://github.com/KubeJS-Mods/KubeJS/tree/1.18/main) --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_21_FirstBlockEntity_DataStorage.md description: 21 第一方块实体和数据保存 相关教程 --- # 第一个方块实体和其数据保存 ## 本文大量参考该文章内容 https://boson.v2mcdev.com/tileentity/firsttileentity.html 在Minecraft中,方块实体(BlockEntity)是和方块(Block)紧密相关但又有所区别的概念。方块是构成游戏世界的基本单位,而方块实体则用于存储特定类型方块的数据。 **方块实体(BlockEntity)** 方块实体通常用于存储与单个方块相关联的数据,例如容器(如箱子)中的物品。每个方块实体都与游戏世界中的一个特定方块位置相关联。 **方块实体对应的方块** 我们先来看看方块实体对应的方块Block的类,如果一个方块具有方块实体,那么该方块应该继承BaseEntityBlock类,并实现该类的抽象方法。 BaseEntityBlock 是 Minecraft 中的一个抽象类,它继承自 Block 类并实现了 EntityBlock 接口。这个类为那些具有关联方块实体的方块提供了一些基础功能。 1. 构造方法:这个构造方法接受一个 BlockBehaviour.Properties 对象,该对象包含了一些影响方块行为的基本属性,如硬度、爆炸抗性等。 2. codec:这个方法必须由子类实现,并返回一个 MapCodec 对象,该对象用于读取和写入方块状态数据。MapCodec 是一种数据格式,用于在 Minecraft 的数据包中编码和解码数据。 3. getRenderShape:这个方法决定了方块的渲染方式。RenderShape.INVISIBLE 表示方块是隐形的,不会被渲染。子类可以重写这个方法来指定不同的渲染类型。 4. triggerEvent:当方块接收到事件时(例如,红石信号变化),这个方法会被调用。它首先调用父类的同名方法,然后检查是否存在关联的方块实体,如果有,则调用方块实体的 triggerEvent 方法。 5. getMenuProvider这个方法返回与方块关联的菜单提供者(如果有的话)。例如,如果方块是一个容器(如箱子),那么它将返回一个允许玩家打开容器界面的菜单提供者。 6. createTickerHelper:这个静态方法用于创建一个 BlockEntityTicker,它用于在服务器和客户端上以不同的方式更新方块实体。如果服务器和客户端的方块实体类型相同,它将返回一个 BlockEntityTicker,否则返回 null。 下面我们来看方块实体 对于方块实体我们需要继承BlockEntity类,BlockEntity 类是 Minecraft 中所有方块实体的基类。它负责管理与特定方块位置相关联的数据,并在方块被破坏或更新时保存和加载这些数据。 该类方法较多,我们这里说一下我们这次用到的方法,其他的方法大家自己研究下吧。 加载和保存数据(load 和 saveAdditional): public void load(CompoundTag pTag) { ... } protected void saveAdditional(CompoundTag pTag) { ... } 这些方法用于从 NBT 标签加载和保存额外的数据。NBT 是一种用于存储 Minecraft 数据的格式。 更新(setChanged): public void setChanged() { ... } 当方块实体的数据发生变化时,调用此方法可以通知世界该方块实体已更改,让游戏知道在关闭的时候要保存调用保存方法。 了解了这些内容之后,我们来添加我们自己的方块和方块实体,我们要实现的是一个能够玩家点击后计数方块。这个计数应该被存储起来,并且退出游戏后读档保持原来的数值。 我们增加一个Block继承BaseEntityBlock ```java public class RubyCounter extends BaseEntityBlock { public RubyCounter() { super(Properties.ofFullCopy(Blocks.STONE)); } @Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new CounterBlockEntity(pPos,pState); } @Override public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) { if(!pLevel.isClientSide && pHand == InteractionHand.MAIN_HAND){ var rubyBlockEntity = (CounterBlockEntity) pLevel.getBlockEntity(pPos); int counter = rubyBlockEntity.increase(); pPlayer.sendSystemMessage(Component.literal("counter:" + counter)); } return InteractionResult.SUCCESS; } @Override public RenderShape getRenderShape(BlockState pState) { return RenderShape.MODEL; } @Override protected MapCodec codec() { return null; } } ``` 构造方法就不说了。我们看下这个public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState)方法,这是你继承BaseEntityBlock过后需要实现的一个方法,该方法需要返回一个新的BlockEntity的实例。这里我们返回的是CounterBlockEntity类的实例,这个类是我们对应方块的方块实体,CounterBlockEntity类是我们之后写的。 我们先略过InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) 方法,这里简单提一下该方法,玩家右键交互时候,会给获得方块实体,并将计数器+1,打印给玩家。 public RenderShape getRenderShape(BlockState pState) 我么重写了该方法返回RenderShape.MODEL,指出该方块使用模型渲染。 MapCodec\ codec()方法我们暂时返回null即可,同样是继承BaseEntityBlock后需要重写的方法。 接下来我们添加我们的CounterBlockEntity类,它需要继承BlockEntity类。 ```java public class CounterBlockEntity extends BlockEntity { private int counter = 0; public CounterBlockEntity(BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.RUBY_COUNTER_BLOCK_ENTITY.get(), pPos, pBlockState); } public int increase(){ counter ++; setChanged(); return counter; } } ``` 该类的代码还是比较简单的。我们主要讲一下构造方法和setchanged方法。 ```java public CounterBlockEntity(BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.RUBY_COUNTER_BLOCK_ENTITY.get(), pPos, pBlockState); } ``` 构造方法,要传入的是方块实体的类型type,pos,blockstate,其中pos,和blockstate我们直接传入即可。对于type我们需要注册对应的blockentitytype后,将我们注册的blockentitytype传入。 对于setChanged()方法我们讲过了,这里再重复一遍 更新(setChanged): public void setChanged() { ... } 当方块实体的数据发生变化时,调用此方法可以通知世界该方块实体已更改,让游戏知道在关闭的时候要保存调用保存方法。 下面我们说一下的ModBlockEntities.RUBY\_COUNTER\_BLOCK\_ENTITY.get()的type的注册。 创建一个这样的类,用于注册我们的blockentity ```java public class ModBlockEntities { public static final DeferredRegister> BLOCK_ENTITIES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, ExampleMod.MODID); public static final Supplier> RUBY_COUNTER_BLOCK_ENTITY = BLOCK_ENTITIES.register("ruby_counter_block_entity", () -> BlockEntityType.Builder.of(CounterBlockEntity::new, ModBlocks.RUBY_COUNTER.get()).build(null)); public static void register(IEventBus eventBus) { BLOCK_ENTITIES.register(eventBus); } } ``` 其中BlockEntityType\中的?是指我们的可以注册各种继承了blockentity的类。 ```java public static final DeferredRegister> BLOCK_ENTITIES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, ExampleMod.MODID); ``` 这里使用 BLOCK\_ENTITIES 注册表来注册一个新的方块实体类型。register 方法接受一个字符串标识符和一个创建方块实体类型的工厂方法。工厂方法返回一个 BlockEntityType.Builder 对象,该对象定义了方块实体类型的构造函数和一个关联的方块。最后,使用 build(null) 方法来创建并返回方块实体类型。 在build里我们传入了一个null,其实这个地方可以填入一个叫做pDataType的实例,这个实例是用来做不同版本之前存档转换的。 ```java public static final Supplier> RUBY_COUNTER_BLOCK_ENTITY = BLOCK_ENTITIES.register("ruby_counter_block_entity", () -> BlockEntityType.Builder.of(CounterBlockEntity::new, ModBlocks.RUBY_COUNTER.get()).build(null)); ``` 现在我们回来看 ```java @Override public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) { if(!pLevel.isClientSide && pHand == InteractionHand.MAIN_HAND){ var rubyBlockEntity = (CounterBlockEntity) pLevel.getBlockEntity(pPos); int counter = rubyBlockEntity.increase(); pPlayer.sendSystemMessage(Component.translatable("counter:" + counter)); } return InteractionResult.SUCCESS; } // 对应的json应该这样写。 "message.neutrino.counter": "计数: %d" // 学过C语言的应该不陌生,这里的%d是指counter输出的位置,%d指明了输出的是一个整数。 ``` 我们看到我们判断了服务端和交互的手是MAIN手,然后获得了对应位置的方块实体,调用了方块实体的方法返回当前计数,并增加计数,然后给玩家发送信息。 如果你在这里想处理语言的国家化问题,请使用这样的内容: ```java pPlayer.sendSystemMessage(Component.translatable("modid.message.counter",counter)); // 对应的json应该这样写。 "message.neutrino.counter": "计数: %d" // 学过C语言的应该不陌生,这里的%d是指counter输出的位置,%d指明了输出的是一个整数。 ``` 别忘了将BLOCK\_ENTITIES注册到IEventBus中。 ```java ModBlockEntities.register(modEventBus); ``` 别忘记了将你的方块添加到创造模式物品栏中,添加对应的材质和模型,我就偷懒了。 启动游戏你就能看到方块了。你右键可以增加计数。 并没结束,如果你退出游戏在进入游戏你会发发现你的计数清零了。我们希望这个数据能保存下来,然后在退出游戏和进入游戏并不会导致数据清零。 我们用到上文提到的。load和saveAdditional方法。 ```java public class CounterBlockEntity extends BlockEntity { private int counter = 0; public CounterBlockEntity(BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.RUBY_COUNTER_BLOCK_ENTITY.get(), pPos, pBlockState); } public int increase(){ counter ++; setChanged(); return counter; } @Override public void load(CompoundTag pTag) { counter = pTag.getInt("counter"); super.load(pTag); } @Override protected void saveAdditional(CompoundTag pTag) { super.saveAdditional(pTag); pTag.putInt("counter",counter); } } ``` 在 Minecraft 中,NBT(Named Binary Tag)是一种用于存储游戏数据的格式。CompoundTag 是 NBT 的一种类型,用于存储一系列键值对的数据。在方块实体中,NBT 通常用于存储实体的状态和属性,以便在游戏的不同部分(如客户端和服务器之间)传输和保存。 load:当方块实体从 NBT 数据加载时,load 方法会被调用。在这个方法中,我们从 NBT 数据中读取键为 “counter” 的整数值,并将其赋值给实体的 counter 变量。super.load(pTag) 调用父类的 load 方法,允许父类处理其他可能存在的数据。 saveAdditional 当方块实体被保存到 NBT 数据时,saveAdditional 方法会被调用。在这个方法中,我们将实体的 counter 变量的值写入 NBT 数据,以键 “counter” 存储。super.saveAdditional(pTag) 调用父类的 saveAdditional 方法,允许父类保存其他可能存在的数据。 好了,你进入游戏中后在尝试就可以正常保存了。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_21_FirstBlockEntity_DataStorage.md description: 21 第一方块实体和数据保存 相关教程 --- # 第一个方块实体和其数据保存 ## 本文大量参考该文章内容 https://boson.v2mcdev.com/tileentity/firsttileentity.html 在Minecraft中,方块实体(BlockEntity)是和方块(Block)紧密相关但又有所区别的概念。方块是构成游戏世界的基本单位,而方块实体则用于存储特定类型方块的数据。 **方块实体(BlockEntity)** 方块实体通常用于存储与单个方块相关联的数据,例如容器(如箱子)中的物品。每个方块实体都与游戏世界中的一个特定方块位置相关联。 **方块实体对应的方块** 我们先来看看方块实体对应的方块Block的类,如果一个方块具有方块实体,那么该方块应该继承BaseEntityBlock类,并实现该类的抽象方法。 BaseEntityBlock 是 Minecraft 中的一个抽象类,它继承自 Block 类并实现了 EntityBlock 接口。这个类为那些具有关联方块实体的方块提供了一些基础功能。 1. 构造方法:这个构造方法接受一个 BlockBehaviour.Properties 对象,该对象包含了一些影响方块行为的基本属性,如硬度、爆炸抗性等。 2. codec:这个方法必须由子类实现,并返回一个 MapCodec 对象,该对象用于读取和写入方块状态数据。MapCodec 是一种数据格式,用于在 Minecraft 的数据包中编码和解码数据。 3. getRenderShape:这个方法决定了方块的渲染方式。RenderShape.INVISIBLE 表示方块是隐形的,不会被渲染。子类可以重写这个方法来指定不同的渲染类型。 4. triggerEvent:当方块接收到事件时(例如,红石信号变化),这个方法会被调用。它首先调用父类的同名方法,然后检查是否存在关联的方块实体,如果有,则调用方块实体的 triggerEvent 方法。 5. getMenuProvider这个方法返回与方块关联的菜单提供者(如果有的话)。例如,如果方块是一个容器(如箱子),那么它将返回一个允许玩家打开容器界面的菜单提供者。 6. createTickerHelper:这个静态方法用于创建一个 BlockEntityTicker,它用于在服务器和客户端上以不同的方式更新方块实体。如果服务器和客户端的方块实体类型相同,它将返回一个 BlockEntityTicker,否则返回 null。 下面我们来看方块实体 对于方块实体我们需要继承BlockEntity类,BlockEntity 类是 Minecraft 中所有方块实体的基类。它负责管理与特定方块位置相关联的数据,并在方块被破坏或更新时保存和加载这些数据。 该类方法较多,我们这里说一下我们这次用到的方法,其他的方法大家自己研究下吧。 加载和保存数据(load 和 saveAdditional): public void load(CompoundTag pTag) { ... } protected void saveAdditional(CompoundTag pTag) { ... } 这些方法用于从 NBT 标签加载和保存额外的数据。NBT 是一种用于存储 Minecraft 数据的格式。 更新(setChanged): public void setChanged() { ... } 当方块实体的数据发生变化时,调用此方法可以通知世界该方块实体已更改,让游戏知道在关闭的时候要保存调用保存方法。 了解了这些内容之后,我们来添加我们自己的方块和方块实体,我们要实现的是一个能够玩家点击后计数方块。这个计数应该被存储起来,并且退出游戏后读档保持原来的数值。 我们增加一个Block继承BaseEntityBlock ```java public class RubyCounter extends BaseEntityBlock { public RubyCounter() { super(Properties.ofFullCopy(Blocks.STONE)); } @Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new CounterBlockEntity(pPos,pState); } @Override public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) { if(!pLevel.isClientSide && pHand == InteractionHand.MAIN_HAND){ var rubyBlockEntity = (CounterBlockEntity) pLevel.getBlockEntity(pPos); int counter = rubyBlockEntity.increase(); pPlayer.sendSystemMessage(Component.literal("counter:" + counter)); } return InteractionResult.SUCCESS; } @Override public RenderShape getRenderShape(BlockState pState) { return RenderShape.MODEL; } @Override protected MapCodec codec() { return null; } } ``` 构造方法就不说了。我们看下这个public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState)方法,这是你继承BaseEntityBlock过后需要实现的一个方法,该方法需要返回一个新的BlockEntity的实例。这里我们返回的是CounterBlockEntity类的实例,这个类是我们对应方块的方块实体,CounterBlockEntity类是我们之后写的。 我们先略过InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) 方法,这里简单提一下该方法,玩家右键交互时候,会给获得方块实体,并将计数器+1,打印给玩家。 public RenderShape getRenderShape(BlockState pState) 我么重写了该方法返回RenderShape.MODEL,指出该方块使用模型渲染。 MapCodec\ codec()方法我们暂时返回null即可,同样是继承BaseEntityBlock后需要重写的方法。 接下来我们添加我们的CounterBlockEntity类,它需要继承BlockEntity类。 ```java public class CounterBlockEntity extends BlockEntity { private int counter = 0; public CounterBlockEntity(BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.RUBY_COUNTER_BLOCK_ENTITY.get(), pPos, pBlockState); } public int increase(){ counter ++; setChanged(); return counter; } } ``` 该类的代码还是比较简单的。我们主要讲一下构造方法和setchanged方法。 ```java public CounterBlockEntity(BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.RUBY_COUNTER_BLOCK_ENTITY.get(), pPos, pBlockState); } ``` 构造方法,要传入的是方块实体的类型type,pos,blockstate,其中pos,和blockstate我们直接传入即可。对于type我们需要注册对应的blockentitytype后,将我们注册的blockentitytype传入。 对于setChanged()方法我们讲过了,这里再重复一遍 更新(setChanged): public void setChanged() { ... } 当方块实体的数据发生变化时,调用此方法可以通知世界该方块实体已更改,让游戏知道在关闭的时候要保存调用保存方法。 下面我们说一下的ModBlockEntities.RUBY\_COUNTER\_BLOCK\_ENTITY.get()的type的注册。 创建一个这样的类,用于注册我们的blockentity ```java public class ModBlockEntities { public static final DeferredRegister> BLOCK_ENTITIES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, ExampleMod.MODID); public static final Supplier> RUBY_COUNTER_BLOCK_ENTITY = BLOCK_ENTITIES.register("ruby_counter_block_entity", () -> BlockEntityType.Builder.of(CounterBlockEntity::new, ModBlocks.RUBY_COUNTER.get()).build(null)); public static void register(IEventBus eventBus) { BLOCK_ENTITIES.register(eventBus); } } ``` 其中BlockEntityType\中的?是指我们的可以注册各种继承了blockentity的类。 ```java public static final DeferredRegister> BLOCK_ENTITIES = DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, ExampleMod.MODID); ``` 这里使用 BLOCK\_ENTITIES 注册表来注册一个新的方块实体类型。register 方法接受一个字符串标识符和一个创建方块实体类型的工厂方法。工厂方法返回一个 BlockEntityType.Builder 对象,该对象定义了方块实体类型的构造函数和一个关联的方块。最后,使用 build(null) 方法来创建并返回方块实体类型。 在build里我们传入了一个null,其实这个地方可以填入一个叫做pDataType的实例,这个实例是用来做不同版本之前存档转换的。 ```java public static final Supplier> RUBY_COUNTER_BLOCK_ENTITY = BLOCK_ENTITIES.register("ruby_counter_block_entity", () -> BlockEntityType.Builder.of(CounterBlockEntity::new, ModBlocks.RUBY_COUNTER.get()).build(null)); ``` 现在我们回来看 ```java @Override public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) { if(!pLevel.isClientSide && pHand == InteractionHand.MAIN_HAND){ var rubyBlockEntity = (CounterBlockEntity) pLevel.getBlockEntity(pPos); int counter = rubyBlockEntity.increase(); pPlayer.sendSystemMessage(Component.translatable("counter:" + counter)); } return InteractionResult.SUCCESS; } // 对应的json应该这样写。 "message.neutrino.counter": "计数: %d" // 学过C语言的应该不陌生,这里的%d是指counter输出的位置,%d指明了输出的是一个整数。 ``` 我们看到我们判断了服务端和交互的手是MAIN手,然后获得了对应位置的方块实体,调用了方块实体的方法返回当前计数,并增加计数,然后给玩家发送信息。 如果你在这里想处理语言的国家化问题,请使用这样的内容: ```java pPlayer.sendSystemMessage(Component.translatable("modid.message.counter",counter)); // 对应的json应该这样写。 "message.neutrino.counter": "计数: %d" // 学过C语言的应该不陌生,这里的%d是指counter输出的位置,%d指明了输出的是一个整数。 ``` 别忘了将BLOCK\_ENTITIES注册到IEventBus中。 ```java ModBlockEntities.register(modEventBus); ``` 别忘记了将你的方块添加到创造模式物品栏中,添加对应的材质和模型,我就偷懒了。 启动游戏你就能看到方块了。你右键可以增加计数。 并没结束,如果你退出游戏在进入游戏你会发发现你的计数清零了。我们希望这个数据能保存下来,然后在退出游戏和进入游戏并不会导致数据清零。 我们用到上文提到的。load和saveAdditional方法。 ```java public class CounterBlockEntity extends BlockEntity { private int counter = 0; public CounterBlockEntity(BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.RUBY_COUNTER_BLOCK_ENTITY.get(), pPos, pBlockState); } public int increase(){ counter ++; setChanged(); return counter; } @Override public void load(CompoundTag pTag) { counter = pTag.getInt("counter"); super.load(pTag); } @Override protected void saveAdditional(CompoundTag pTag) { super.saveAdditional(pTag); pTag.putInt("counter",counter); } } ``` 在 Minecraft 中,NBT(Named Binary Tag)是一种用于存储游戏数据的格式。CompoundTag 是 NBT 的一种类型,用于存储一系列键值对的数据。在方块实体中,NBT 通常用于存储实体的状态和属性,以便在游戏的不同部分(如客户端和服务器之间)传输和保存。 load:当方块实体从 NBT 数据加载时,load 方法会被调用。在这个方法中,我们从 NBT 数据中读取键为 “counter” 的整数值,并将其赋值给实体的 counter 变量。super.load(pTag) 调用父类的 load 方法,允许父类处理其他可能存在的数据。 saveAdditional 当方块实体被保存到 NBT 数据时,saveAdditional 方法会被调用。在这个方法中,我们将实体的 counter 变量的值写入 NBT 数据,以键 “counter” 存储。super.saveAdditional(pTag) 调用父类的 saveAdditional 方法,允许父类保存其他可能存在的数据。 好了,你进入游戏中后在尝试就可以正常保存了。 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_22 _BlockEntityTick.md description: 22 实体方块tick 相关教程 --- # 参考 本文章大量参考了: https://boson.v2mcdev.com/tileentity/itickabletileentity.html ## 实体方块tick 这次我们看一个实体方块中重要的概念。 使用到的类还是上次教程的几个类,我们就不再重复介绍了。 直接直入主题 照旧添加一个新的方块,并且继承BaseEntityBlock类 ```java public class HelloBlock extends BaseEntityBlock { public HelloBlock() { super(Properties.ofFullCopy(Blocks.STONE)); } @Override protected MapCodec codec() { return null; } @Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new HelloBlockEntity(pPos,pState); } @Nullable @Override public BlockEntityTicker getTicker(Level pLevel, BlockState pState, BlockEntityType pBlockEntityType) { return pLevel.isClientSide ? null : createTickerHelper(pBlockEntityType, ModBlockEntities.HELLO_BLOCK_ENTITY.get(), HelloBlockEntity::serverTick); } @Override public RenderShape getRenderShape(BlockState pState) { return RenderShape.MODEL; } } ``` 其中我们要说的就是这个方法: ```java @Nullable @Override public BlockEntityTicker getTicker(Level pLevel, BlockState pState, BlockEntityType pBlockEntityType) { return pLevel.isClientSide ? null : createTickerHelper(pBlockEntityType, ModBlockEntities.HELLO_BLOCK_ENTITY.get(), HelloBlockEntity::serverTick); } ``` 该方法于返回一个BlockEntityTicker,BlockEntityTicker是一个函数式接口,即你需要返回一个参数是如下的函数。 ```java @FunctionalInterface public interface BlockEntityTicker { void tick(Level pLevel, BlockPos pPos, BlockState pState, T pBlockEntity); } ``` getTicker方法主要的作用就是返回一个BlockEntityTicker,这个BlockEntityTicker用于处理该方块实体的tick时间更新的回调方法。所以你要在tick中处理你要处理的内容,该内容回调执行, 这里我们通过了一个createTickerHelper的辅助函数返回了对应的这个函数式接口BlockEntityTicker。 这里要说的一点就是这个tick的回调是分客户端和服务端的,一般游戏逻辑应该写在服务端的。所以这里看到我们对于isclientside做了判断,如果是客户端的tick则直接返回null,服务端返回了servertick方法的应用。 ```java return pLevel.isClientSide ? null : createTickerHelper(pBlockEntityType, ModBlockEntities.HELLO_BLOCK_ENTITY.get(), HelloBlockEntity::serverTick); ``` 至于tick方法写了什么内容,我们把它写在了方块实体中,主要是对方块实体的逻辑的处理。而我们这个方块实体的作用是在周围的玩家中距离该方块最近的那个玩家发送hello的消息。 我们来看看方块实体的内容吧。 ```java public class HelloBlockEntity extends BlockEntity { private static final int MAX_TIME = 5 * 20; private int timer = 0; public HelloBlockEntity(BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.HELLO_BLOCK_ENTITY.get(), pPos, pBlockState); } public static void serverTick(Level pLevel, BlockPos pPos, BlockState pState, HelloBlockEntity pBlockEntity) { if(pLevel!=null && !pLevel.isClientSide){ if(pBlockEntity.timer == HelloBlockEntity.MAX_TIME){ Player nearestPlayer = pLevel.getNearestPlayer(pPos.getX(), pPos.getY(), pPos.getZ(), 10, false); Component component = Component.literal("hello"); if(nearestPlayer!=null){ nearestPlayer.sendSystemMessage(component); } pBlockEntity.timer = 0; } pBlockEntity.timer++; } } } ``` 这两个字段是我们设置的,其中一个字段是我们的hello输出的间隔时间,游戏中1s是20tick,这里设置的就是5s。 第二个timer是我们用于计时的。 ```java private static final int MAX_TIME = 5 * 20; private int timer = 0; ``` 而对于我们的tick方法,会发现是静态的方法,所以我们通过方法中的blckentity的参数获得我们需要的变量。 ```java public static void serverTick(Level pLevel, BlockPos pPos, BlockState pState, HelloBlockEntity pBlockEntity) { if(pLevel!=null && !pLevel.isClientSide){ if(pBlockEntity.timer == HelloBlockEntity.MAX_TIME){ Player nearestPlayer = pLevel.getNearestPlayer(pPos.getX(), pPos.getY(), pPos.getZ(), 10, false); Component component = Component.literal("hello"); if(nearestPlayer!=null){ nearestPlayer.sendSystemMessage(component); } pBlockEntity.timer = 0; } pBlockEntity.timer++; } } ``` tick方法就是获得timer然后检查是否到达了计时,如果达到了,就获得最近的玩家,返送hello消息。重置计时器。 没有到达计时就计时++; 如果你看了上一个加成的话你会发现timer是没有保存到nbt中的,这意味这你退出游戏在进入是会导致timer重置的。 别忘记了注册你的方块实体和方块,以及添加到创造模式物品栏,材质,模型和翻译,相信大家已经会了怎么进行这些操作就不过度介绍了。 不了解了可以看源码和之前的教程。进入到游戏中你可以就可以看到给你打招呼了。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_22 _BlockEntityTick.md description: 22 实体方块tick 相关教程 --- # 参考 本文章大量参考了: https://boson.v2mcdev.com/tileentity/itickabletileentity.html ## 实体方块tick 这次我们看一个实体方块中重要的概念。 使用到的类还是上次教程的几个类,我们就不再重复介绍了。 直接直入主题 照旧添加一个新的方块,并且继承BaseEntityBlock类 ```java public class HelloBlock extends BaseEntityBlock { public HelloBlock() { super(Properties.ofFullCopy(Blocks.STONE)); } @Override protected MapCodec codec() { return null; } @Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new HelloBlockEntity(pPos,pState); } @Nullable @Override public BlockEntityTicker getTicker(Level pLevel, BlockState pState, BlockEntityType pBlockEntityType) { return pLevel.isClientSide ? null : createTickerHelper(pBlockEntityType, ModBlockEntities.HELLO_BLOCK_ENTITY.get(), HelloBlockEntity::serverTick); } @Override public RenderShape getRenderShape(BlockState pState) { return RenderShape.MODEL; } } ``` 其中我们要说的就是这个方法: ```java @Nullable @Override public BlockEntityTicker getTicker(Level pLevel, BlockState pState, BlockEntityType pBlockEntityType) { return pLevel.isClientSide ? null : createTickerHelper(pBlockEntityType, ModBlockEntities.HELLO_BLOCK_ENTITY.get(), HelloBlockEntity::serverTick); } ``` 该方法于返回一个BlockEntityTicker,BlockEntityTicker是一个函数式接口,即你需要返回一个参数是如下的函数。 ```java @FunctionalInterface public interface BlockEntityTicker { void tick(Level pLevel, BlockPos pPos, BlockState pState, T pBlockEntity); } ``` getTicker方法主要的作用就是返回一个BlockEntityTicker,这个BlockEntityTicker用于处理该方块实体的tick时间更新的回调方法。所以你要在tick中处理你要处理的内容,该内容回调执行, 这里我们通过了一个createTickerHelper的辅助函数返回了对应的这个函数式接口BlockEntityTicker。 这里要说的一点就是这个tick的回调是分客户端和服务端的,一般游戏逻辑应该写在服务端的。所以这里看到我们对于isclientside做了判断,如果是客户端的tick则直接返回null,服务端返回了servertick方法的应用。 ```java return pLevel.isClientSide ? null : createTickerHelper(pBlockEntityType, ModBlockEntities.HELLO_BLOCK_ENTITY.get(), HelloBlockEntity::serverTick); ``` 至于tick方法写了什么内容,我们把它写在了方块实体中,主要是对方块实体的逻辑的处理。而我们这个方块实体的作用是在周围的玩家中距离该方块最近的那个玩家发送hello的消息。 我们来看看方块实体的内容吧。 ```java public class HelloBlockEntity extends BlockEntity { private static final int MAX_TIME = 5 * 20; private int timer = 0; public HelloBlockEntity(BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.HELLO_BLOCK_ENTITY.get(), pPos, pBlockState); } public static void serverTick(Level pLevel, BlockPos pPos, BlockState pState, HelloBlockEntity pBlockEntity) { if(pLevel!=null && !pLevel.isClientSide){ if(pBlockEntity.timer == HelloBlockEntity.MAX_TIME){ Player nearestPlayer = pLevel.getNearestPlayer(pPos.getX(), pPos.getY(), pPos.getZ(), 10, false); Component component = Component.literal("hello"); if(nearestPlayer!=null){ nearestPlayer.sendSystemMessage(component); } pBlockEntity.timer = 0; } pBlockEntity.timer++; } } } ``` 这两个字段是我们设置的,其中一个字段是我们的hello输出的间隔时间,游戏中1s是20tick,这里设置的就是5s。 第二个timer是我们用于计时的。 ```java private static final int MAX_TIME = 5 * 20; private int timer = 0; ``` 而对于我们的tick方法,会发现是静态的方法,所以我们通过方法中的blckentity的参数获得我们需要的变量。 ```java public static void serverTick(Level pLevel, BlockPos pPos, BlockState pState, HelloBlockEntity pBlockEntity) { if(pLevel!=null && !pLevel.isClientSide){ if(pBlockEntity.timer == HelloBlockEntity.MAX_TIME){ Player nearestPlayer = pLevel.getNearestPlayer(pPos.getX(), pPos.getY(), pPos.getZ(), 10, false); Component component = Component.literal("hello"); if(nearestPlayer!=null){ nearestPlayer.sendSystemMessage(component); } pBlockEntity.timer = 0; } pBlockEntity.timer++; } } ``` tick方法就是获得timer然后检查是否到达了计时,如果达到了,就获得最近的玩家,返送hello消息。重置计时器。 没有到达计时就计时++; 如果你看了上一个加成的话你会发现timer是没有保存到nbt中的,这意味这你退出游戏在进入是会导致timer重置的。 别忘记了注册你的方块实体和方块,以及添加到创造模式物品栏,材质,模型和翻译,相信大家已经会了怎么进行这些操作就不过度介绍了。 不了解了可以看源码和之前的教程。进入到游戏中你可以就可以看到给你打招呼了。 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_23_BlockEntityDataSync.md description: 23 方块实体内置的数据同步 相关教程 --- # 参考 本文章参考了: https://boson.v2mcdev.com/tileentity/datasync.html ## 方块实体数据同步 这次我们来看方块实体的数据同步,这里说的数据同步是指客户端和服务端之间的数据同步,我们主要的游戏逻辑写在了服务端,不过有的时候客户端的一些操作依赖于数据,这时候我们需要将数据在客户端和服务端进行同步。这里我们说一种用于服务端和客户端的同步方法,这种同步的方法是方块实体提供的。 我们需要这样的一个逻辑,我们在服务端执行一些计数的操作,当计数完成之后,我们要给客户端知道我们计数完成了,并播放僵尸的叫声。 我们还是从添加方块开始: ```java public class ZombieBlock extends BaseEntityBlock { public ZombieBlock() { super(Properties.ofFullCopy(Blocks.STONE)); } @Override protected MapCodec codec() { return null; } @Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new ZombieBlockEntity(pPos,pState); } @Override public RenderShape getRenderShape(BlockState pState) { return RenderShape.MODEL; } @Nullable @Override public BlockEntityTicker getTicker(Level pLevel, BlockState pState, BlockEntityType pBlockEntityType) { return pLevel.isClientSide ? createTickerHelper(pBlockEntityType, ModBlockEntities.ZOMBIE_BLOCK_ENTITY.get(),ZombieBlockEntity::clientTick) : createTickerHelper(pBlockEntityType, ModBlockEntities.ZOMBIE_BLOCK_ENTITY.get(), ZombieBlockEntity::serverTick); } } ``` 这里值得说的还是getticker方法 ```java @Nullable @Override public BlockEntityTicker getTicker(Level pLevel, BlockState pState, BlockEntityType pBlockEntityType) { return pLevel.isClientSide ? createTickerHelper(pBlockEntityType, ModBlockEntities.ZOMBIE_BLOCK_ENTITY.get(),ZombieBlockEntity::clientTick) : createTickerHelper(pBlockEntityType, ModBlockEntities.ZOMBIE_BLOCK_ENTITY.get(), ZombieBlockEntity::serverTick); } } ``` 理解和之前一样,判断是否是客户端,如果是客户端返回clientick的方法引用,如果是服务端返回servertick的方法引用。使用creatickerhelper的帮助方法。 我们来看方块实体类 ```java public class ZombieBlockEntity extends BlockEntity { private static final int MAX_TIME = 5 * 20; private boolean flag = false; private int timer = 0; public ZombieBlockEntity( BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.ZOMBIE_BLOCK_ENTITY.get(), pPos, pBlockState); } // getUpdatePacket是服务端发送数据包用的方法, @Nullable @Override public Packet getUpdatePacket() { return ClientboundBlockEntityDataPacket.create(this); } //而onDataPacket才是客户端接受数据包的方法。 @Override public void onDataPacket(Connection net, ClientboundBlockEntityDataPacket pkt) { handleUpdateTag(pkt.getTag()); } // @Override public CompoundTag getUpdateTag() { CompoundTag compoundNBT = super.getUpdateTag(); compoundNBT.putBoolean("flag", flag); return compoundNBT; } @Override public void handleUpdateTag(CompoundTag tag) { flag = tag.getBoolean("flag"); } public static void clientTick(Level pLevel, BlockPos pPos, BlockState pState, ZombieBlockEntity pBlockEntity) { if(pLevel.isClientSide && pBlockEntity.flag){ var player = pLevel.getNearestPlayer(pPos.getX(),pPos.getY(),pPos.getZ(),10,false); pLevel.playSound(player,pPos, SoundEvents.ZOMBIE_AMBIENT, SoundSource.AMBIENT,1f,1f); pBlockEntity.flag = false; } } public static void serverTick(Level pLevel, BlockPos pPos, BlockState pState, ZombieBlockEntity pBlockEntity) { if(!pLevel.isClientSide){ if(pBlockEntity.timer >= MAX_TIME){ pBlockEntity.flag = true; pLevel.sendBlockUpdated(pPos,pLevel.getBlockState(pPos),pLevel.getBlockState(pPos),3); pBlockEntity.timer = 0; } pBlockEntity.timer ++; } } } ``` 我们有这样的几个方法: ```java // getUpdatePacket是服务端发送数据包用的方法, @Nullable @Override public Packet getUpdatePacket() { return ClientboundBlockEntityDataPacket.create(this); } //而onDataPacket才是客户端接受数据包的方法。 @Override public void onDataPacket(Connection net, ClientboundBlockEntityDataPacket pkt) { handleUpdateTag(pkt.getTag()); } // @Override public CompoundTag getUpdateTag() { CompoundTag compoundNBT = super.getUpdateTag(); compoundNBT.putBoolean("flag", flag); return compoundNBT; } @Override public void handleUpdateTag(CompoundTag tag) { flag = tag.getBoolean("flag"); } ``` 这几个方法就用于数据同步的。 而其中的getUpdatePacket方法是服务端给客户端发送一个数据包的方法。这个数据包中的数据就是getUpdateTag方法返回的nbt。 onDataPacket方法是客户端处理数据包的方法。他处理的内容就是调用了handleUpdateTag方法,将flag从nbt读出来赋值给了flag。 对于getUpdateTag和handleUpdateTag需要说的一点是这两个方法是区块被载入时候调用的。为了让实体方块能在区块加载时候同步数据你可以实现getUpdateTag和handleUpdateTag方法。 如果你需要在你需要的时候触发数据同步,你需要调用pLevel.sendBlockUpdated(pPos,pLevel.getBlockState(pPos),pLevel.getBlockState(pPos),3);方法,最后一个参数的flag数值可以看之前的教程,有说到。 那么我们在看看这个客户端的tick方法和服务端的tick方法 ```java public static void serverTick(Level pLevel, BlockPos pPos, BlockState pState, ZombieBlockEntity pBlockEntity) { if(!pLevel.isClientSide){ if(pBlockEntity.timer >= MAX_TIME){ pBlockEntity.flag = true; pLevel.sendBlockUpdated(pPos,pLevel.getBlockState(pPos),pLevel.getBlockState(pPos),3); pBlockEntity.timer = 0; } pBlockEntity.timer ++; } } ``` 服务端的tick方法的主要作用就是计数,当计数完成时候,修改flag并触发数据同步,清空计数器。 ```java if(pLevel.isClientSide && pBlockEntity.flag){ var player = pLevel.getNearestPlayer(pPos.getX(),pPos.getY(),pPos.getZ(),10,false); pLevel.playSound(player,pPos, SoundEvents.ZOMBIE_AMBIENT, SoundSource.AMBIENT,1f,1f); pBlockEntity.flag = false; } ``` 客户端的tick方法就是检查flag标志位有没有被更新,如果被更新了,就播放僵尸的声音,然后重置flag标志。 对方块和方块实体的注册及其相关的内容就不过度赘述了, 可以直接看源码。 源码在Kook --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_23_BlockEntityDataSync.md description: 23 方块实体内置的数据同步 相关教程 --- # 参考 本文章参考了: https://boson.v2mcdev.com/tileentity/datasync.html ## 方块实体数据同步 这次我们来看方块实体的数据同步,这里说的数据同步是指客户端和服务端之间的数据同步,我们主要的游戏逻辑写在了服务端,不过有的时候客户端的一些操作依赖于数据,这时候我们需要将数据在客户端和服务端进行同步。这里我们说一种用于服务端和客户端的同步方法,这种同步的方法是方块实体提供的。 我们需要这样的一个逻辑,我们在服务端执行一些计数的操作,当计数完成之后,我们要给客户端知道我们计数完成了,并播放僵尸的叫声。 我们还是从添加方块开始: ```java public class ZombieBlock extends BaseEntityBlock { public ZombieBlock() { super(Properties.ofFullCopy(Blocks.STONE)); } @Override protected MapCodec codec() { return null; } @Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new ZombieBlockEntity(pPos,pState); } @Override public RenderShape getRenderShape(BlockState pState) { return RenderShape.MODEL; } @Nullable @Override public BlockEntityTicker getTicker(Level pLevel, BlockState pState, BlockEntityType pBlockEntityType) { return pLevel.isClientSide ? createTickerHelper(pBlockEntityType, ModBlockEntities.ZOMBIE_BLOCK_ENTITY.get(),ZombieBlockEntity::clientTick) : createTickerHelper(pBlockEntityType, ModBlockEntities.ZOMBIE_BLOCK_ENTITY.get(), ZombieBlockEntity::serverTick); } } ``` 这里值得说的还是getticker方法 ```java @Nullable @Override public BlockEntityTicker getTicker(Level pLevel, BlockState pState, BlockEntityType pBlockEntityType) { return pLevel.isClientSide ? createTickerHelper(pBlockEntityType, ModBlockEntities.ZOMBIE_BLOCK_ENTITY.get(),ZombieBlockEntity::clientTick) : createTickerHelper(pBlockEntityType, ModBlockEntities.ZOMBIE_BLOCK_ENTITY.get(), ZombieBlockEntity::serverTick); } } ``` 理解和之前一样,判断是否是客户端,如果是客户端返回clientick的方法引用,如果是服务端返回servertick的方法引用。使用creatickerhelper的帮助方法。 我们来看方块实体类 ```java public class ZombieBlockEntity extends BlockEntity { private static final int MAX_TIME = 5 * 20; private boolean flag = false; private int timer = 0; public ZombieBlockEntity( BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.ZOMBIE_BLOCK_ENTITY.get(), pPos, pBlockState); } // getUpdatePacket是服务端发送数据包用的方法, @Nullable @Override public Packet getUpdatePacket() { return ClientboundBlockEntityDataPacket.create(this); } //而onDataPacket才是客户端接受数据包的方法。 @Override public void onDataPacket(Connection net, ClientboundBlockEntityDataPacket pkt) { handleUpdateTag(pkt.getTag()); } // @Override public CompoundTag getUpdateTag() { CompoundTag compoundNBT = super.getUpdateTag(); compoundNBT.putBoolean("flag", flag); return compoundNBT; } @Override public void handleUpdateTag(CompoundTag tag) { flag = tag.getBoolean("flag"); } public static void clientTick(Level pLevel, BlockPos pPos, BlockState pState, ZombieBlockEntity pBlockEntity) { if(pLevel.isClientSide && pBlockEntity.flag){ var player = pLevel.getNearestPlayer(pPos.getX(),pPos.getY(),pPos.getZ(),10,false); pLevel.playSound(player,pPos, SoundEvents.ZOMBIE_AMBIENT, SoundSource.AMBIENT,1f,1f); pBlockEntity.flag = false; } } public static void serverTick(Level pLevel, BlockPos pPos, BlockState pState, ZombieBlockEntity pBlockEntity) { if(!pLevel.isClientSide){ if(pBlockEntity.timer >= MAX_TIME){ pBlockEntity.flag = true; pLevel.sendBlockUpdated(pPos,pLevel.getBlockState(pPos),pLevel.getBlockState(pPos),3); pBlockEntity.timer = 0; } pBlockEntity.timer ++; } } } ``` 我们有这样的几个方法: ```java // getUpdatePacket是服务端发送数据包用的方法, @Nullable @Override public Packet getUpdatePacket() { return ClientboundBlockEntityDataPacket.create(this); } //而onDataPacket才是客户端接受数据包的方法。 @Override public void onDataPacket(Connection net, ClientboundBlockEntityDataPacket pkt) { handleUpdateTag(pkt.getTag()); } // @Override public CompoundTag getUpdateTag() { CompoundTag compoundNBT = super.getUpdateTag(); compoundNBT.putBoolean("flag", flag); return compoundNBT; } @Override public void handleUpdateTag(CompoundTag tag) { flag = tag.getBoolean("flag"); } ``` 这几个方法就用于数据同步的。 而其中的getUpdatePacket方法是服务端给客户端发送一个数据包的方法。这个数据包中的数据就是getUpdateTag方法返回的nbt。 onDataPacket方法是客户端处理数据包的方法。他处理的内容就是调用了handleUpdateTag方法,将flag从nbt读出来赋值给了flag。 对于getUpdateTag和handleUpdateTag需要说的一点是这两个方法是区块被载入时候调用的。为了让实体方块能在区块加载时候同步数据你可以实现getUpdateTag和handleUpdateTag方法。 如果你需要在你需要的时候触发数据同步,你需要调用pLevel.sendBlockUpdated(pPos,pLevel.getBlockState(pPos),pLevel.getBlockState(pPos),3);方法,最后一个参数的flag数值可以看之前的教程,有说到。 那么我们在看看这个客户端的tick方法和服务端的tick方法 ```java public static void serverTick(Level pLevel, BlockPos pPos, BlockState pState, ZombieBlockEntity pBlockEntity) { if(!pLevel.isClientSide){ if(pBlockEntity.timer >= MAX_TIME){ pBlockEntity.flag = true; pLevel.sendBlockUpdated(pPos,pLevel.getBlockState(pPos),pLevel.getBlockState(pPos),3); pBlockEntity.timer = 0; } pBlockEntity.timer ++; } } ``` 服务端的tick方法的主要作用就是计数,当计数完成时候,修改flag并触发数据同步,清空计数器。 ```java if(pLevel.isClientSide && pBlockEntity.flag){ var player = pLevel.getNearestPlayer(pPos.getX(),pPos.getY(),pPos.getZ(),10,false); pLevel.playSound(player,pPos, SoundEvents.ZOMBIE_AMBIENT, SoundSource.AMBIENT,1f,1f); pBlockEntity.flag = false; } ``` 客户端的tick方法就是检查flag标志位有没有被更新,如果被更新了,就播放僵尸的声音,然后重置flag标志。 对方块和方块实体的注册及其相关的内容就不过度赘述了, 可以直接看源码。 源码在Kook --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_24 BakedModel.md description: 24 烘焙模型 相关教程 --- # 参考 https://boson.v2mcdev.com/specialrender/ibakedmodel.html ## BakedModel烘焙模型 在开始正式写代码之前,我们先要了解如何一个方块是如何渲染出来的 Minecraft本身会读取json和材质文件将其转换成IModel接口的实例,然后IModel进行了「Bake(烘焙)」处理,变成了IBakedModel,这个IBakedModel会被放入BlockRendererDispatcher中,当游戏需要时会直接从BlockRendererDispatcher取出IBakedModel进行渲染。至于什么是「Bake」,Bake基本上是对模型的材质进行光照计算等操作,让它变成可以直接被GPU渲染的东西。 > 以上内容来自https://boson.v2mcdev.com/specialrender/ibakedmodel.html 这次我们会效仿boson教程的内容做一个隐藏方块出来。再此之前我们还是先来了解下相关的类和我们应该怎么操作。 IBakedModelExtension接口 这是一个对BakedModel接口拓展的接口,由neoforge提供的。包含了获得模型的面片(quads)、是否使用环境遮蔽(ambient occlusion)、应用变换、获取粒子图标、获取渲染类型等。 getQuads 方法用于获取模型的面片,如果渲染类型为空,则返回所有的面片;useAmbientOcclusion 方法用于判断是否使用环境遮蔽;applyTransform 方法用于应用变换;getModelData 方法用于获取模型数据;getParticleIcon 方法用于获取粒子图标;getRenderTypes 方法用于获取渲染类型;getRenderPasses 方法用于获取用于渲染物品的模型列表。 BakedModel接口 这个接口用于表示 Minecraft 中已经预先处理好的模型, 这个接口中定义了一些方法,用于获取模型的面片、是否使用环境遮蔽、是否是 3D GUI、是否使用方块光源、是否使用自定义渲染器、获取粒子图标、获取变换、获取覆盖器等。 在说了这些接口方法的内容之后,我们来说一下我们的要添加的隐藏方块,该方块在本方块下面有方块的时候会模仿下方方块的模型,如果没有则显示自己的模型。 所以我们还是先添加一个方块,这个就和之前一样不过多介绍了。 ```java public class HiddenBlock extends Block { public HiddenBlock(){ super(Properties.ofFullCopy(Blocks.STONE).noOcclusion()); } } ``` 接下来我们再看自己的烘焙模型的代码: ```java public class HiddenBlockModel implements BakedModel { BakedModel defaultModel; public static ModelProperty COPIED_BLOCK = new ModelProperty<>(); public HiddenBlockModel(BakedModel existingModel){ this.defaultModel = existingModel; } @Override public @NotNull List getQuads(@Nullable BlockState state, @Nullable Direction side, @NotNull RandomSource rand, @NotNull ModelData data, @Nullable RenderType renderType) { BakedModel renderModel = defaultModel; if (data.has(COPIED_BLOCK)) { BlockState copiedBlock = data.get(COPIED_BLOCK); if (copiedBlock != null) { Minecraft mc = Minecraft.getInstance(); BlockRenderDispatcher blockRendererDispatcher = mc.getBlockRenderer(); renderModel = blockRendererDispatcher.getBlockModel(copiedBlock); } } return renderModel.getQuads(state,side,rand,data,renderType); } @Override public @NotNull ModelData getModelData(@NotNull BlockAndTintGetter level, @NotNull BlockPos pos, @NotNull BlockState state, @NotNull ModelData modelData) { BlockState downBlockState = level.getBlockState(pos.below()); ModelData modelDataMap = modelData.derive().with(COPIED_BLOCK,null).build(); if(downBlockState.getBlock() == Blocks.AIR || downBlockState.getBlock() == ModBlocks.HIDDEN_BLOCK.get()){ return modelDataMap; } return modelDataMap.derive().with(COPIED_BLOCK,downBlockState).build(); } @Override public List getQuads(@Nullable BlockState pState, @Nullable Direction pDirection, RandomSource pRandom) { throw new AssertionError("IBakedModel::getQuads should never be called, only IForgeBakedModel::getQuads"); } @Override public boolean useAmbientOcclusion() { return defaultModel.useAmbientOcclusion(); } @Override public boolean isGui3d() { return defaultModel.isGui3d(); } @Override public boolean usesBlockLight() { return defaultModel.usesBlockLight(); } @Override public boolean isCustomRenderer() { return defaultModel.isCustomRenderer(); } @Override public TextureAtlasSprite getParticleIcon() { return defaultModel.getParticleIcon(); } @Override public ItemOverrides getOverrides() { return defaultModel.getOverrides(); } } ``` 我们实现了BakedModel接口,自然要实现接口的方法 ```java public class HiddenBlockModel implements BakedModel { ``` 这里我们定义了两个字段,一个是烘焙模型,另一个是ModelProperty,其中第一个字段,用于存储我们默认情况游戏帮我们生成的模型,也就是我们平常直接使用的模型,包括cubeall那个。而为了讲解第二个定义的字段,我们就需要说一下ModelProperty和ModelData ```java BakedModel defaultModel; public static ModelProperty COPIED_BLOCK = new ModelProperty<>(); ``` 我们先看ModelProperty,这个类继承于Predicate接口,Predicate接口表示一个函数式接口,用于表示对某个对象的判断。ModelProperty类有两个构造函数,一个是无参构造函数,它会创建一个ModelProperty对象,其内部predicate是一个总是返回true的函数;另一个是有参构造函数,它会创建一个ModelProperty对象,其内部predicate是输入的Predicate函数。 这个ModelProperty类被设计用于ModelData中,ModelData是一个接口,用于存储方块模型的数据。ModelProperty可以用于在ModelData对象中存储一些属性,并可选地对这些属性进行判断和校验。 我们再来看ModelData,它是方块模型数据的容器类,用于存储和传递方块模型的数据。ModelData类有一个成员变量properties,是一个映射表,用于存储多个ModelProperty和其对应的值。ModelData类有一个构造函数,接收一个映射表作为输入参数,并将其保存在成员变量properties中。 * getProperties:该方法返回当前ModelData对象中所有ModelProperty的集合。 * has:该方法判断当前ModelData对象中是否包含指定的ModelProperty。 * get:该方法获取当前ModelData对象中指定的ModelProperty对应的值,返回值为泛型T。 * derive:该方法返回一个Builder对象,用于创建一个新的ModelData对象,并可选地从当前ModelData对象中复制一些ModelProperty和值。 * builder:该方法返回一个新的Builder对象,用于创建一个空的ModelData对象。 ModelData类中还定义了一个内部类Builder,用于构建ModelData对象。 Builder类提供了一个方法with,用于在构建ModelData时,向当前Builder对象中添加一个ModelProperty和其对应的值。在添加之前,会对值进行判断,如果值不满足ModelProperty的要求,则抛出异常。 Builder类还提供了一个方法build,用于构建一个新的ModelData对象,并返回。 这个ModelData对象是一个不可变对象,其properties是一个只读映射表,并且构建之后不能修改。 好了到此为止,我们就可以知道我们的ModelProperty是什么东西了。我们结合下面的代码一起看。 对于getQuads方法有两个方法,对于我们方块模型获得数据来说,我们会用到这个 ```java public @NotNull List getQuads(@Nullable BlockState state, @Nullable Direction side, @NotNull RandomSource rand, @NotNull ModelData data, @Nullable RenderType renderType) { ``` 对于另一个方法,如果调用了,就直接抛出错误。 ```java @Override public List getQuads(@Nullable BlockState pState, @Nullable Direction pDirection, RandomSource pRandom) { throw new AssertionError("IBakedModel::getQuads should never be called, only IForgeBakedModel::getQuads"); } ``` 我们会注意到getQuads方法会用使用到ModelData这个参数,而这个参数传入的数据就是 ```java public @NotNull ModelData getModelData(@NotNull BlockAndTintGetter level, @NotNull BlockPos pos, @NotNull BlockState state, @NotNull ModelData modelData) { ``` 这个方法传递的。所以我们先来看这个getModelData方法实现。 ```java BlockState downBlockState = level.getBlockState(pos.below()); ModelData modelDataMap = modelData.derive().with(COPIED_BLOCK,null).build(); if(downBlockState.getBlock() == Blocks.AIR || downBlockState.getBlock() == ModBlocks.HIDDEN_BLOCK.get()){ return modelDataMap; } return modelDataMap.derive().with(COPIED_BLOCK,downBlockState).build(); ``` 可以看到在该方法中,我们首先获得了当前方块的下面方块的blockstate,之后构建一个modeldata,复制了原modeldata的数据,并添加了COPIED\_BLOCK这个ModelProperty其数值为null,然后判断该方块下面方块是不是空气方块或则是我们隐藏方块,如果是的话,就返回这个modeldata,其中的COPIED\_BLOCK的字段是null。如果不是,我们就返回COPIED\_BLOCK字段是下面方块的blockstate,之后我们通过这个blockstate获得对应方块状态的model。 在让我们看看getQuads方法,该方法会返回对应的blockstate的模型数据, ```java BakedModel renderModel = defaultModel; if (data.has(COPIED_BLOCK)) { BlockState copiedBlock = data.get(COPIED_BLOCK); if (copiedBlock != null) { Minecraft mc = Minecraft.getInstance(); BlockRenderDispatcher blockRendererDispatcher = mc.getBlockRenderer(); renderModel = blockRendererDispatcher.getBlockModel(copiedBlock); } } return renderModel.getQuads(state,side,rand,data,renderType); ``` 我们还是获得该模型本身的模型,之后判断modeldata中我们添加的字段是不是null,如果是null,那么我们直接返回默认的数据,如果不是null我们获得对应的该字段的blockstate,通过blockRendererDispatcher获得对应blockstate的模型数据,设置rendermoodel,然后返回该model的数据。 对于其他的几个方法我们直接调用了默认模型的方法,对于他们都是什么作用在之前讲解BakeModel时候已经说过了,自己翻看。 好了我们目前只是定义了这个烘焙模型,怎么使用它呢?我们需要将原本的烘焙模型替换我们写的这个烘焙模型,万幸的是neoforge为我们提供了对应的事件。 ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public static class ModEventBus{ @SubscribeEvent public static void onModelBaked(ModelEvent.ModifyBakingResult event){ for(BlockState blockstate: ModBlocks.HIDDEN_BLOCK.get().getStateDefinition().getPossibleStates()){ ModelResourceLocation modelResourceLocation = BlockModelShaper.stateToModelLocation(blockstate); BakedModel existingModel = event.getModels().get(modelResourceLocation); if(existingModel==null){ throw new RuntimeException("Did not find Obsidian Hidden in registry"); }else if (existingModel instanceof HiddenBlockModel) { throw new RuntimeException("Tried to replaceObsidian Hidden twice"); }else { HiddenBlockModel obsidianHiddenBlockModel = new HiddenBlockModel(existingModel); event.getModels().put(modelResourceLocation, obsidianHiddenBlockModel); } } } } ``` 由于烘焙模型替换事件是在游戏启动时候发生的, 所以我们使用的是MOD总线,你也可以在事件的定义的注解中看到关于事件应该写在总线上,也说明了是不是CLIENT,这里是CLIENT,所以我们写了value=Dist.CLIENT保证只在客户端加载。 ModBlocks.HIDDEN\_BLOCK.get().getStateDefinition().getPossibleStates(),迭代了所有HIDDEN\_BLOCK的blockstate,获得blockstate的modelResourceLocation。 通过modelResourceLocation和event.getModels().get方法获得对应的Blockstate的bakedmodel,之后我们做了判断如果为null,则报错说明没有找到这个HIDDEN\_BLOCK的方块的默认模型,如果找到了,我们还判断existingModel这个模型是不是HiddenBlockModel我们创建烘焙模型的实例化如果是则说明这个模型替换了两遍,当这些都没问题,我们就创建一个自己的HIDDEN\_BLOCK\_MODEL模型并通过event.getModels().put(modelResourceLocation, obsidianHiddenBlockModel);替换掉原来modelResourceLocation定位模型。 > 我相信经过前几期的教程应该可以自己看懂modelResourceLocation的作用了,其实就是blockstate模型的资源定位 > 对于这里的event.getModels() 返回的是Map\这样的MAP,这个MAP维护了ResourceLocation和对应的BakedModel的键值对。 好了对于方块的模型,材质,注册,以及创造模式物品栏的添就留给观众了,你也可以直接查看源码。 你进入游戏将当前方块放置在另一个方块上时候就会模仿他的模型了,不过要注意的是这只是一个演示讲解的例子,可能会有一些bug。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_24 BakedModel.md description: 24 烘焙模型 相关教程 --- # 参考 https://boson.v2mcdev.com/specialrender/ibakedmodel.html ## BakedModel烘焙模型 在开始正式写代码之前,我们先要了解如何一个方块是如何渲染出来的 Minecraft本身会读取json和材质文件将其转换成IModel接口的实例,然后IModel进行了「Bake(烘焙)」处理,变成了IBakedModel,这个IBakedModel会被放入BlockRendererDispatcher中,当游戏需要时会直接从BlockRendererDispatcher取出IBakedModel进行渲染。至于什么是「Bake」,Bake基本上是对模型的材质进行光照计算等操作,让它变成可以直接被GPU渲染的东西。 > 以上内容来自https://boson.v2mcdev.com/specialrender/ibakedmodel.html 这次我们会效仿boson教程的内容做一个隐藏方块出来。再此之前我们还是先来了解下相关的类和我们应该怎么操作。 IBakedModelExtension接口 这是一个对BakedModel接口拓展的接口,由neoforge提供的。包含了获得模型的面片(quads)、是否使用环境遮蔽(ambient occlusion)、应用变换、获取粒子图标、获取渲染类型等。 getQuads 方法用于获取模型的面片,如果渲染类型为空,则返回所有的面片;useAmbientOcclusion 方法用于判断是否使用环境遮蔽;applyTransform 方法用于应用变换;getModelData 方法用于获取模型数据;getParticleIcon 方法用于获取粒子图标;getRenderTypes 方法用于获取渲染类型;getRenderPasses 方法用于获取用于渲染物品的模型列表。 BakedModel接口 这个接口用于表示 Minecraft 中已经预先处理好的模型, 这个接口中定义了一些方法,用于获取模型的面片、是否使用环境遮蔽、是否是 3D GUI、是否使用方块光源、是否使用自定义渲染器、获取粒子图标、获取变换、获取覆盖器等。 在说了这些接口方法的内容之后,我们来说一下我们的要添加的隐藏方块,该方块在本方块下面有方块的时候会模仿下方方块的模型,如果没有则显示自己的模型。 所以我们还是先添加一个方块,这个就和之前一样不过多介绍了。 ```java public class HiddenBlock extends Block { public HiddenBlock(){ super(Properties.ofFullCopy(Blocks.STONE).noOcclusion()); } } ``` 接下来我们再看自己的烘焙模型的代码: ```java public class HiddenBlockModel implements BakedModel { BakedModel defaultModel; public static ModelProperty COPIED_BLOCK = new ModelProperty<>(); public HiddenBlockModel(BakedModel existingModel){ this.defaultModel = existingModel; } @Override public @NotNull List getQuads(@Nullable BlockState state, @Nullable Direction side, @NotNull RandomSource rand, @NotNull ModelData data, @Nullable RenderType renderType) { BakedModel renderModel = defaultModel; if (data.has(COPIED_BLOCK)) { BlockState copiedBlock = data.get(COPIED_BLOCK); if (copiedBlock != null) { Minecraft mc = Minecraft.getInstance(); BlockRenderDispatcher blockRendererDispatcher = mc.getBlockRenderer(); renderModel = blockRendererDispatcher.getBlockModel(copiedBlock); } } return renderModel.getQuads(state,side,rand,data,renderType); } @Override public @NotNull ModelData getModelData(@NotNull BlockAndTintGetter level, @NotNull BlockPos pos, @NotNull BlockState state, @NotNull ModelData modelData) { BlockState downBlockState = level.getBlockState(pos.below()); ModelData modelDataMap = modelData.derive().with(COPIED_BLOCK,null).build(); if(downBlockState.getBlock() == Blocks.AIR || downBlockState.getBlock() == ModBlocks.HIDDEN_BLOCK.get()){ return modelDataMap; } return modelDataMap.derive().with(COPIED_BLOCK,downBlockState).build(); } @Override public List getQuads(@Nullable BlockState pState, @Nullable Direction pDirection, RandomSource pRandom) { throw new AssertionError("IBakedModel::getQuads should never be called, only IForgeBakedModel::getQuads"); } @Override public boolean useAmbientOcclusion() { return defaultModel.useAmbientOcclusion(); } @Override public boolean isGui3d() { return defaultModel.isGui3d(); } @Override public boolean usesBlockLight() { return defaultModel.usesBlockLight(); } @Override public boolean isCustomRenderer() { return defaultModel.isCustomRenderer(); } @Override public TextureAtlasSprite getParticleIcon() { return defaultModel.getParticleIcon(); } @Override public ItemOverrides getOverrides() { return defaultModel.getOverrides(); } } ``` 我们实现了BakedModel接口,自然要实现接口的方法 ```java public class HiddenBlockModel implements BakedModel { ``` 这里我们定义了两个字段,一个是烘焙模型,另一个是ModelProperty,其中第一个字段,用于存储我们默认情况游戏帮我们生成的模型,也就是我们平常直接使用的模型,包括cubeall那个。而为了讲解第二个定义的字段,我们就需要说一下ModelProperty和ModelData ```java BakedModel defaultModel; public static ModelProperty COPIED_BLOCK = new ModelProperty<>(); ``` 我们先看ModelProperty,这个类继承于Predicate接口,Predicate接口表示一个函数式接口,用于表示对某个对象的判断。ModelProperty类有两个构造函数,一个是无参构造函数,它会创建一个ModelProperty对象,其内部predicate是一个总是返回true的函数;另一个是有参构造函数,它会创建一个ModelProperty对象,其内部predicate是输入的Predicate函数。 这个ModelProperty类被设计用于ModelData中,ModelData是一个接口,用于存储方块模型的数据。ModelProperty可以用于在ModelData对象中存储一些属性,并可选地对这些属性进行判断和校验。 我们再来看ModelData,它是方块模型数据的容器类,用于存储和传递方块模型的数据。ModelData类有一个成员变量properties,是一个映射表,用于存储多个ModelProperty和其对应的值。ModelData类有一个构造函数,接收一个映射表作为输入参数,并将其保存在成员变量properties中。 * getProperties:该方法返回当前ModelData对象中所有ModelProperty的集合。 * has:该方法判断当前ModelData对象中是否包含指定的ModelProperty。 * get:该方法获取当前ModelData对象中指定的ModelProperty对应的值,返回值为泛型T。 * derive:该方法返回一个Builder对象,用于创建一个新的ModelData对象,并可选地从当前ModelData对象中复制一些ModelProperty和值。 * builder:该方法返回一个新的Builder对象,用于创建一个空的ModelData对象。 ModelData类中还定义了一个内部类Builder,用于构建ModelData对象。 Builder类提供了一个方法with,用于在构建ModelData时,向当前Builder对象中添加一个ModelProperty和其对应的值。在添加之前,会对值进行判断,如果值不满足ModelProperty的要求,则抛出异常。 Builder类还提供了一个方法build,用于构建一个新的ModelData对象,并返回。 这个ModelData对象是一个不可变对象,其properties是一个只读映射表,并且构建之后不能修改。 好了到此为止,我们就可以知道我们的ModelProperty是什么东西了。我们结合下面的代码一起看。 对于getQuads方法有两个方法,对于我们方块模型获得数据来说,我们会用到这个 ```java public @NotNull List getQuads(@Nullable BlockState state, @Nullable Direction side, @NotNull RandomSource rand, @NotNull ModelData data, @Nullable RenderType renderType) { ``` 对于另一个方法,如果调用了,就直接抛出错误。 ```java @Override public List getQuads(@Nullable BlockState pState, @Nullable Direction pDirection, RandomSource pRandom) { throw new AssertionError("IBakedModel::getQuads should never be called, only IForgeBakedModel::getQuads"); } ``` 我们会注意到getQuads方法会用使用到ModelData这个参数,而这个参数传入的数据就是 ```java public @NotNull ModelData getModelData(@NotNull BlockAndTintGetter level, @NotNull BlockPos pos, @NotNull BlockState state, @NotNull ModelData modelData) { ``` 这个方法传递的。所以我们先来看这个getModelData方法实现。 ```java BlockState downBlockState = level.getBlockState(pos.below()); ModelData modelDataMap = modelData.derive().with(COPIED_BLOCK,null).build(); if(downBlockState.getBlock() == Blocks.AIR || downBlockState.getBlock() == ModBlocks.HIDDEN_BLOCK.get()){ return modelDataMap; } return modelDataMap.derive().with(COPIED_BLOCK,downBlockState).build(); ``` 可以看到在该方法中,我们首先获得了当前方块的下面方块的blockstate,之后构建一个modeldata,复制了原modeldata的数据,并添加了COPIED\_BLOCK这个ModelProperty其数值为null,然后判断该方块下面方块是不是空气方块或则是我们隐藏方块,如果是的话,就返回这个modeldata,其中的COPIED\_BLOCK的字段是null。如果不是,我们就返回COPIED\_BLOCK字段是下面方块的blockstate,之后我们通过这个blockstate获得对应方块状态的model。 在让我们看看getQuads方法,该方法会返回对应的blockstate的模型数据, ```java BakedModel renderModel = defaultModel; if (data.has(COPIED_BLOCK)) { BlockState copiedBlock = data.get(COPIED_BLOCK); if (copiedBlock != null) { Minecraft mc = Minecraft.getInstance(); BlockRenderDispatcher blockRendererDispatcher = mc.getBlockRenderer(); renderModel = blockRendererDispatcher.getBlockModel(copiedBlock); } } return renderModel.getQuads(state,side,rand,data,renderType); ``` 我们还是获得该模型本身的模型,之后判断modeldata中我们添加的字段是不是null,如果是null,那么我们直接返回默认的数据,如果不是null我们获得对应的该字段的blockstate,通过blockRendererDispatcher获得对应blockstate的模型数据,设置rendermoodel,然后返回该model的数据。 对于其他的几个方法我们直接调用了默认模型的方法,对于他们都是什么作用在之前讲解BakeModel时候已经说过了,自己翻看。 好了我们目前只是定义了这个烘焙模型,怎么使用它呢?我们需要将原本的烘焙模型替换我们写的这个烘焙模型,万幸的是neoforge为我们提供了对应的事件。 ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public static class ModEventBus{ @SubscribeEvent public static void onModelBaked(ModelEvent.ModifyBakingResult event){ for(BlockState blockstate: ModBlocks.HIDDEN_BLOCK.get().getStateDefinition().getPossibleStates()){ ModelResourceLocation modelResourceLocation = BlockModelShaper.stateToModelLocation(blockstate); BakedModel existingModel = event.getModels().get(modelResourceLocation); if(existingModel==null){ throw new RuntimeException("Did not find Obsidian Hidden in registry"); }else if (existingModel instanceof HiddenBlockModel) { throw new RuntimeException("Tried to replaceObsidian Hidden twice"); }else { HiddenBlockModel obsidianHiddenBlockModel = new HiddenBlockModel(existingModel); event.getModels().put(modelResourceLocation, obsidianHiddenBlockModel); } } } } ``` 由于烘焙模型替换事件是在游戏启动时候发生的, 所以我们使用的是MOD总线,你也可以在事件的定义的注解中看到关于事件应该写在总线上,也说明了是不是CLIENT,这里是CLIENT,所以我们写了value=Dist.CLIENT保证只在客户端加载。 ModBlocks.HIDDEN\_BLOCK.get().getStateDefinition().getPossibleStates(),迭代了所有HIDDEN\_BLOCK的blockstate,获得blockstate的modelResourceLocation。 通过modelResourceLocation和event.getModels().get方法获得对应的Blockstate的bakedmodel,之后我们做了判断如果为null,则报错说明没有找到这个HIDDEN\_BLOCK的方块的默认模型,如果找到了,我们还判断existingModel这个模型是不是HiddenBlockModel我们创建烘焙模型的实例化如果是则说明这个模型替换了两遍,当这些都没问题,我们就创建一个自己的HIDDEN\_BLOCK\_MODEL模型并通过event.getModels().put(modelResourceLocation, obsidianHiddenBlockModel);替换掉原来modelResourceLocation定位模型。 > 我相信经过前几期的教程应该可以自己看懂modelResourceLocation的作用了,其实就是blockstate模型的资源定位 > 对于这里的event.getModels() 返回的是Map\这样的MAP,这个MAP维护了ResourceLocation和对应的BakedModel的键值对。 好了对于方块的模型,材质,注册,以及创造模式物品栏的添就留给观众了,你也可以直接查看源码。 你进入游戏将当前方块放置在另一个方块上时候就会模仿他的模型了,不过要注意的是这只是一个演示讲解的例子,可能会有一些bug。 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_25_BlockEntityRender.md description: 25 方块实体渲染 相关教程 --- # 参考 本文参考了: https://boson.v2mcdev.com/specialrender/ter.html ## BakedModel烘焙模型 在开始正式写代码之前,我们先要了解如何一个方块是如何渲染出来的 这次我们添加一个方块,利用方块实体渲染一个箱子和一个钻石物品在旁边,这是一个简单演示,当然利用这个你还可以实现更加复杂有趣的效果,就留给你自己探索了。 我们修改之前的frame框架的方块,给他添加一个方块实体。 ```java public class RubyFrame extends BaseEntityBlock implements SimpleWaterloggedBlock { public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; public static final VoxelShape SHAPE = Block.box(0,0,0,16,18,16); public RubyFrame(){ super(Properties.ofFullCopy(Blocks.STONE).strength(5).noOcclusion()); this.registerDefaultState(this.defaultBlockState().setValue(WATERLOGGED, Boolean.valueOf(false))); } @Override public VoxelShape getShape(BlockState pState, BlockGetter pLevel, BlockPos pPos, CollisionContext pContext) { return SHAPE; } @Override protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { pBuilder.add(WATERLOGGED); } @Nullable @Override public BlockState getStateForPlacement(BlockPlaceContext pContext) { BlockPos blockpos = pContext.getClickedPos(); BlockState blockstate = pContext.getLevel().getBlockState(blockpos); if (blockstate.is(this)) { return blockstate.setValue(WATERLOGGED, Boolean.valueOf(false)); } else { FluidState fluidstate = pContext.getLevel().getFluidState(blockpos); BlockState blockstate1 = this.defaultBlockState() .setValue(WATERLOGGED, Boolean.valueOf(fluidstate.getType() == Fluids.WATER)); return blockstate1; } } @Override public BlockState updateShape(BlockState pState, Direction pFacing, BlockState pFacingState, LevelAccessor pLevel, BlockPos pCurrentPos, BlockPos pFacingPos) { if (pState.getValue(WATERLOGGED)) { pLevel.scheduleTick(pCurrentPos, Fluids.WATER, Fluids.WATER.getTickDelay(pLevel)); } return super.updateShape(pState, pFacing, pFacingState, pLevel, pCurrentPos, pFacingPos); } @Override public FluidState getFluidState(BlockState pState) { return pState.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(pState); } @Override public boolean isPathfindable(BlockState pState, BlockGetter pLevel, BlockPos pPos, PathComputationType pType) { switch(pType) { case LAND: return false; case WATER: return pLevel.getFluidState(pPos).is(FluidTags.WATER); case AIR: return false; default: return false; } } @Override protected MapCodec codec() { return null; } @org.jetbrains.annotations.Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new RubyFrameBlockEntity(pPos,pState); } @Override public RenderShape getRenderShape(BlockState pState) { return RenderShape.MODEL; } } ``` 其中这两个方块是我们新添加的: ```java @org.jetbrains.annotations.Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new RubyFrameBlockEntity(pPos,pState); } @Override public RenderShape getRenderShape(BlockState pState) { return RenderShape.MODEL; } ``` 相信大家已经能看懂了,就不介绍了。 我们来看方块实体的代码,这并没有什么内容,很简单。 ```java public class RubyFrameBlockEntity extends BlockEntity { public RubyFrameBlockEntity( BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.RUBY_FRAME_BLOCK_ENTITY.get(), pPos, pBlockState); } } ``` 重要的是这个RubyFrameBlockEntityRender类。这是一个我们自己定义的类,他就是方块实体渲染器。不过在介绍这个类之前我们先来介绍下这个类中用到的内容。 ```java public class RubyFrameBlockEntityRender implements BlockEntityRenderer { public RubyFrameBlockEntityRender(BlockEntityRendererProvider.Context pContext){ } @Override public void render(RubyFrameBlockEntity pBlockEntity, float pPartialTick, PoseStack pPoseStack, MultiBufferSource pBuffer, int pPackedLight, int pPackedOverlay) { pPoseStack.pushPose(); pPoseStack.translate(1,0,0); BlockRenderDispatcher blockRenderDispatcher = Minecraft.getInstance().getBlockRenderer(); BlockState state = Blocks.CHEST.defaultBlockState(); blockRenderDispatcher.renderSingleBlock(state,pPoseStack,pBuffer,pPackedLight,pPackedOverlay); pPoseStack.popPose(); pPoseStack.pushPose(); pPoseStack.translate(0,1,0); ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer(); ItemStack stack = new ItemStack(Items.DIAMOND); BakedModel bakedModel = itemRenderer.getModel(stack,pBlockEntity.getLevel(),null,0); itemRenderer.render(stack, ItemDisplayContext.FIXED,true,pPoseStack,pBuffer,pPackedLight,pPackedOverlay,bakedModel); pPoseStack.popPose(); } } ``` 其中该类实现了BlockEntityRenderer接口,该接口是处理客户端渲染实体方块的方法,其中render方法是主要的方法,我们的渲染的逻辑要写在这里。 接下来我们看下pPoseStack类的作用,这里需要你有点计算机图形学的基础,如果你没有的话看看我之前的OpenGL的视频,或者自己去学下相关的内容。 PoseStack类用于管理pose堆栈,其中每个pose由pose本身的4x4矩阵和法线的3x3矩阵组成。 该类提供了几种方法来操作堆栈和pose: * translate(double pX, double pY, double pZ))和 translate(float pX, float pY, float pZ):将堆栈中的最后一个姿势在x、y和z方向上平移给定的量。 * scale(float pX, float pY, float pZ):在x、y和z方向上按给定的量缩放堆栈中的最后一个pose。 * mulPose(四元数pQuaternion):将堆栈中的最后一个pose乘以给定的四元数。 * rotateRound(四元数p四元数,浮点pX,浮点pY,浮点pZ):将堆栈中的最后一个pose围绕点(pX,pY,pZ)旋转给定的四元数。 * pushPose():将一个pose推到堆栈上,初始化为堆栈中最后一个pose的副本。 * popPose():从堆栈中移除最后一个pose。 * last():获取堆栈中的最后一个pose。 * clear():检查堆栈是否为null(即,它只包含初始pose)。 * setIdentity():将堆栈中的最后一个pose设置为单位矩阵。 * mulPoseMatrix(Matrix4f-pMatrix):将堆栈中的最后一个pose乘以给定的4x4矩阵。 * Pose类是PoseStack的一个静态嵌套类,它包含姿势矩阵和法线矩阵。它提供了访问这些矩阵的方法。 说明白就是封装的对模型的顶点数据的变换矩阵。 下面我们来看BlockRenderDispatcher,这个我们在上一个教程中也使用过,我们通过他获得了相应的blockstate的model。 类BlockRenderDispatcher,负责在Minecraft中渲染block。它实现ResourceManagerReloadListener接口,表示它侦听资源管理器重载事件。 BlockRenderDispatcher类有几个字段: * blockModelShaper:用于获取给定块状态的模型的blockModelShaper对象。 * modelRenderer:用于渲染块模型的ModelBlockRenderer对象。 * blockEntityRenderer:用于渲染块实体的BlockEntityWithoutLevelRenderer对象。 * liquidBlockRenderer:用于渲染液体(例如,水、熔岩)的LiquidBlockRender对象。 * random:用于生成随机数的RandomSource对象。 * blockColors:blockColors对象,用于获取块的颜色。 该类提供了几种渲染块的方法: * renderBreakingTexture(BlockState pState、BlockPos pPos、BlockAndTintGetter pLevel、PoseStack pPoseStac、VertexConsumer pConsumer):渲染正在断开的块的纹理。 * renderBatched(BlockState pState、BlockPos pPos、BlockAndTintGetter pLevel、PoseStack pPoseStac、VertexConsumer pConsumer、boolean pCheckSides、RandomSource pRandom):渲染一批block。 * renderLiquid(BlockPos pPos、BlockAndTintGetter pLevel、VertexConsumer pConsumer、BlockState pBlockState、FluidState pFluidState):渲染液体block。 * renderSingleBlock(BlockState pState、PoseStack pPoseStac、MultiBufferSource pBufferSource、int pPackedLight、int pPackedOverlay):渲染单个block。 该类还为其字段提供getter方法,以及在重新加载资源管理器时调用的onResourceManagerReload(ResourceManager-pResourceManager)方法。 那么我们再看看ItemRenderer方法 ItemRenderer的类,它是游戏负责渲染项目模型。该类实现了ResourceManagerReloadListener接口,这意味着它在重新加载游戏的资源管理器时侦听事件。 ItemRenderer类有几种用于在不同上下文中渲染项目的方法: renderModelLists:渲染项目堆栈的四元模型列表。 render:在给定的显示上下文中渲染itemstack(例如,在玩家的手中、GUI插槽中、地面上)。这种方法可以处理各种特殊情况,例如使用自定义模型渲染三叉戟和望远镜,使用闪烁效果渲染魔法物品,以及使用自定义渲染器渲染物品。 getModel:返回itemstack的烘焙模型,同时考虑可能适用的任何模型override。 renderStatic:在静态上下文中渲染项堆栈(例如,在实体的固定项槽中)。 ItemRenderer类还实现了ResourceManagerReloadListener接口,这意味着当游戏的资源被重新加载时,它会得到通知。作为对资源重新加载事件的响应,ItemRenderer重建其项模型缓存。 最后,ItemRenderer类有几个常量和静态字段,它们与使用自定义模型或纹理(如三叉戟和spyglasse)渲染某些项目有关。它还有一个blockEntityRenderer字段,用于在块实体(如箱子、熔炉)作为项目保存时对其进行渲染。 会到我们的render方法中: ```java pPoseStack.pushPose(); pPoseStack.translate(1,0,0); BlockRenderDispatcher blockRenderDispatcher = Minecraft.getInstance().getBlockRenderer(); BlockState state = Blocks.CHEST.defaultBlockState(); blockRenderDispatcher.renderSingleBlock(state,pPoseStack,pBuffer,pPackedLight,pPackedOverlay); pPoseStack.popPose(); pPoseStack.pushPose(); pPoseStack.translate(0,1,0); ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer(); ItemStack stack = new ItemStack(Items.DIAMOND); BakedModel bakedModel = itemRenderer.getModel(stack,pBlockEntity.getLevel(),null,0); itemRenderer.render(stack, ItemDisplayContext.FIXED,true,pPoseStack,pBuffer,pPackedLight,pPackedOverlay,bakedModel); pPoseStack.popPose(); ``` 对于posestack的使用,我们要先pushPose保存之前的变换的堆栈,防止污染了其他的变化。之后进行你的操作。首先将pose推送到矩阵堆栈上,并将其平移(1,0,0)。然后,它获得方块的渲染器和箱子的默认方块状态,并使用它们使用给定的矩阵堆栈和buffer在当前位置渲染单个block。 接下来,渲染方法将另一个pose推到矩阵堆栈上,并将其平移(0,1,0),然后,它获得item渲染器,并创建一个钻石的Itemstack,获得ItemStack的烘焙模型,并使用它们使用给定的矩阵堆栈和buffer在当前位置渲染itemstack。 最后,渲染方法从矩阵堆栈中弹出姿势,以恢复原始矩阵状态。 这就是我们这这个方法中做的事情,render方法每帧会调用一次,你可以通过一个变量让渲染的位置动起来从而实现一些动画效果,就留着大家自己研究了。 下面我们要给我们的方块实体绑定这个方块实体渲染器, ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class ClientEventHandler { @SubscribeEvent public static void onClientEvent(FMLClientSetupEvent event){ event.enqueueWork(()->{ BlockEntityRenderers.register(ModBlockEntities.RUBY_FRAME_BLOCK_ENTITY.get(),RubyFrameBlockEntityRender::new); }); } } ``` 记得注册你的方块和方块实体。 进入游戏你可以就可以看到渲染的物品和箱子方块了。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_25_BlockEntityRender.md description: 25 方块实体渲染 相关教程 --- # 参考 本文参考了: https://boson.v2mcdev.com/specialrender/ter.html ## BakedModel烘焙模型 在开始正式写代码之前,我们先要了解如何一个方块是如何渲染出来的 这次我们添加一个方块,利用方块实体渲染一个箱子和一个钻石物品在旁边,这是一个简单演示,当然利用这个你还可以实现更加复杂有趣的效果,就留给你自己探索了。 我们修改之前的frame框架的方块,给他添加一个方块实体。 ```java public class RubyFrame extends BaseEntityBlock implements SimpleWaterloggedBlock { public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; public static final VoxelShape SHAPE = Block.box(0,0,0,16,18,16); public RubyFrame(){ super(Properties.ofFullCopy(Blocks.STONE).strength(5).noOcclusion()); this.registerDefaultState(this.defaultBlockState().setValue(WATERLOGGED, Boolean.valueOf(false))); } @Override public VoxelShape getShape(BlockState pState, BlockGetter pLevel, BlockPos pPos, CollisionContext pContext) { return SHAPE; } @Override protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { pBuilder.add(WATERLOGGED); } @Nullable @Override public BlockState getStateForPlacement(BlockPlaceContext pContext) { BlockPos blockpos = pContext.getClickedPos(); BlockState blockstate = pContext.getLevel().getBlockState(blockpos); if (blockstate.is(this)) { return blockstate.setValue(WATERLOGGED, Boolean.valueOf(false)); } else { FluidState fluidstate = pContext.getLevel().getFluidState(blockpos); BlockState blockstate1 = this.defaultBlockState() .setValue(WATERLOGGED, Boolean.valueOf(fluidstate.getType() == Fluids.WATER)); return blockstate1; } } @Override public BlockState updateShape(BlockState pState, Direction pFacing, BlockState pFacingState, LevelAccessor pLevel, BlockPos pCurrentPos, BlockPos pFacingPos) { if (pState.getValue(WATERLOGGED)) { pLevel.scheduleTick(pCurrentPos, Fluids.WATER, Fluids.WATER.getTickDelay(pLevel)); } return super.updateShape(pState, pFacing, pFacingState, pLevel, pCurrentPos, pFacingPos); } @Override public FluidState getFluidState(BlockState pState) { return pState.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(pState); } @Override public boolean isPathfindable(BlockState pState, BlockGetter pLevel, BlockPos pPos, PathComputationType pType) { switch(pType) { case LAND: return false; case WATER: return pLevel.getFluidState(pPos).is(FluidTags.WATER); case AIR: return false; default: return false; } } @Override protected MapCodec codec() { return null; } @org.jetbrains.annotations.Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new RubyFrameBlockEntity(pPos,pState); } @Override public RenderShape getRenderShape(BlockState pState) { return RenderShape.MODEL; } } ``` 其中这两个方块是我们新添加的: ```java @org.jetbrains.annotations.Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new RubyFrameBlockEntity(pPos,pState); } @Override public RenderShape getRenderShape(BlockState pState) { return RenderShape.MODEL; } ``` 相信大家已经能看懂了,就不介绍了。 我们来看方块实体的代码,这并没有什么内容,很简单。 ```java public class RubyFrameBlockEntity extends BlockEntity { public RubyFrameBlockEntity( BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.RUBY_FRAME_BLOCK_ENTITY.get(), pPos, pBlockState); } } ``` 重要的是这个RubyFrameBlockEntityRender类。这是一个我们自己定义的类,他就是方块实体渲染器。不过在介绍这个类之前我们先来介绍下这个类中用到的内容。 ```java public class RubyFrameBlockEntityRender implements BlockEntityRenderer { public RubyFrameBlockEntityRender(BlockEntityRendererProvider.Context pContext){ } @Override public void render(RubyFrameBlockEntity pBlockEntity, float pPartialTick, PoseStack pPoseStack, MultiBufferSource pBuffer, int pPackedLight, int pPackedOverlay) { pPoseStack.pushPose(); pPoseStack.translate(1,0,0); BlockRenderDispatcher blockRenderDispatcher = Minecraft.getInstance().getBlockRenderer(); BlockState state = Blocks.CHEST.defaultBlockState(); blockRenderDispatcher.renderSingleBlock(state,pPoseStack,pBuffer,pPackedLight,pPackedOverlay); pPoseStack.popPose(); pPoseStack.pushPose(); pPoseStack.translate(0,1,0); ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer(); ItemStack stack = new ItemStack(Items.DIAMOND); BakedModel bakedModel = itemRenderer.getModel(stack,pBlockEntity.getLevel(),null,0); itemRenderer.render(stack, ItemDisplayContext.FIXED,true,pPoseStack,pBuffer,pPackedLight,pPackedOverlay,bakedModel); pPoseStack.popPose(); } } ``` 其中该类实现了BlockEntityRenderer接口,该接口是处理客户端渲染实体方块的方法,其中render方法是主要的方法,我们的渲染的逻辑要写在这里。 接下来我们看下pPoseStack类的作用,这里需要你有点计算机图形学的基础,如果你没有的话看看我之前的OpenGL的视频,或者自己去学下相关的内容。 PoseStack类用于管理pose堆栈,其中每个pose由pose本身的4x4矩阵和法线的3x3矩阵组成。 该类提供了几种方法来操作堆栈和pose: * translate(double pX, double pY, double pZ))和 translate(float pX, float pY, float pZ):将堆栈中的最后一个姿势在x、y和z方向上平移给定的量。 * scale(float pX, float pY, float pZ):在x、y和z方向上按给定的量缩放堆栈中的最后一个pose。 * mulPose(四元数pQuaternion):将堆栈中的最后一个pose乘以给定的四元数。 * rotateRound(四元数p四元数,浮点pX,浮点pY,浮点pZ):将堆栈中的最后一个pose围绕点(pX,pY,pZ)旋转给定的四元数。 * pushPose():将一个pose推到堆栈上,初始化为堆栈中最后一个pose的副本。 * popPose():从堆栈中移除最后一个pose。 * last():获取堆栈中的最后一个pose。 * clear():检查堆栈是否为null(即,它只包含初始pose)。 * setIdentity():将堆栈中的最后一个pose设置为单位矩阵。 * mulPoseMatrix(Matrix4f-pMatrix):将堆栈中的最后一个pose乘以给定的4x4矩阵。 * Pose类是PoseStack的一个静态嵌套类,它包含姿势矩阵和法线矩阵。它提供了访问这些矩阵的方法。 说明白就是封装的对模型的顶点数据的变换矩阵。 下面我们来看BlockRenderDispatcher,这个我们在上一个教程中也使用过,我们通过他获得了相应的blockstate的model。 类BlockRenderDispatcher,负责在Minecraft中渲染block。它实现ResourceManagerReloadListener接口,表示它侦听资源管理器重载事件。 BlockRenderDispatcher类有几个字段: * blockModelShaper:用于获取给定块状态的模型的blockModelShaper对象。 * modelRenderer:用于渲染块模型的ModelBlockRenderer对象。 * blockEntityRenderer:用于渲染块实体的BlockEntityWithoutLevelRenderer对象。 * liquidBlockRenderer:用于渲染液体(例如,水、熔岩)的LiquidBlockRender对象。 * random:用于生成随机数的RandomSource对象。 * blockColors:blockColors对象,用于获取块的颜色。 该类提供了几种渲染块的方法: * renderBreakingTexture(BlockState pState、BlockPos pPos、BlockAndTintGetter pLevel、PoseStack pPoseStac、VertexConsumer pConsumer):渲染正在断开的块的纹理。 * renderBatched(BlockState pState、BlockPos pPos、BlockAndTintGetter pLevel、PoseStack pPoseStac、VertexConsumer pConsumer、boolean pCheckSides、RandomSource pRandom):渲染一批block。 * renderLiquid(BlockPos pPos、BlockAndTintGetter pLevel、VertexConsumer pConsumer、BlockState pBlockState、FluidState pFluidState):渲染液体block。 * renderSingleBlock(BlockState pState、PoseStack pPoseStac、MultiBufferSource pBufferSource、int pPackedLight、int pPackedOverlay):渲染单个block。 该类还为其字段提供getter方法,以及在重新加载资源管理器时调用的onResourceManagerReload(ResourceManager-pResourceManager)方法。 那么我们再看看ItemRenderer方法 ItemRenderer的类,它是游戏负责渲染项目模型。该类实现了ResourceManagerReloadListener接口,这意味着它在重新加载游戏的资源管理器时侦听事件。 ItemRenderer类有几种用于在不同上下文中渲染项目的方法: renderModelLists:渲染项目堆栈的四元模型列表。 render:在给定的显示上下文中渲染itemstack(例如,在玩家的手中、GUI插槽中、地面上)。这种方法可以处理各种特殊情况,例如使用自定义模型渲染三叉戟和望远镜,使用闪烁效果渲染魔法物品,以及使用自定义渲染器渲染物品。 getModel:返回itemstack的烘焙模型,同时考虑可能适用的任何模型override。 renderStatic:在静态上下文中渲染项堆栈(例如,在实体的固定项槽中)。 ItemRenderer类还实现了ResourceManagerReloadListener接口,这意味着当游戏的资源被重新加载时,它会得到通知。作为对资源重新加载事件的响应,ItemRenderer重建其项模型缓存。 最后,ItemRenderer类有几个常量和静态字段,它们与使用自定义模型或纹理(如三叉戟和spyglasse)渲染某些项目有关。它还有一个blockEntityRenderer字段,用于在块实体(如箱子、熔炉)作为项目保存时对其进行渲染。 会到我们的render方法中: ```java pPoseStack.pushPose(); pPoseStack.translate(1,0,0); BlockRenderDispatcher blockRenderDispatcher = Minecraft.getInstance().getBlockRenderer(); BlockState state = Blocks.CHEST.defaultBlockState(); blockRenderDispatcher.renderSingleBlock(state,pPoseStack,pBuffer,pPackedLight,pPackedOverlay); pPoseStack.popPose(); pPoseStack.pushPose(); pPoseStack.translate(0,1,0); ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer(); ItemStack stack = new ItemStack(Items.DIAMOND); BakedModel bakedModel = itemRenderer.getModel(stack,pBlockEntity.getLevel(),null,0); itemRenderer.render(stack, ItemDisplayContext.FIXED,true,pPoseStack,pBuffer,pPackedLight,pPackedOverlay,bakedModel); pPoseStack.popPose(); ``` 对于posestack的使用,我们要先pushPose保存之前的变换的堆栈,防止污染了其他的变化。之后进行你的操作。首先将pose推送到矩阵堆栈上,并将其平移(1,0,0)。然后,它获得方块的渲染器和箱子的默认方块状态,并使用它们使用给定的矩阵堆栈和buffer在当前位置渲染单个block。 接下来,渲染方法将另一个pose推到矩阵堆栈上,并将其平移(0,1,0),然后,它获得item渲染器,并创建一个钻石的Itemstack,获得ItemStack的烘焙模型,并使用它们使用给定的矩阵堆栈和buffer在当前位置渲染itemstack。 最后,渲染方法从矩阵堆栈中弹出姿势,以恢复原始矩阵状态。 这就是我们这这个方法中做的事情,render方法每帧会调用一次,你可以通过一个变量让渲染的位置动起来从而实现一些动画效果,就留着大家自己研究了。 下面我们要给我们的方块实体绑定这个方块实体渲染器, ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class ClientEventHandler { @SubscribeEvent public static void onClientEvent(FMLClientSetupEvent event){ event.enqueueWork(()->{ BlockEntityRenderers.register(ModBlockEntities.RUBY_FRAME_BLOCK_ENTITY.get(),RubyFrameBlockEntityRender::new); }); } } ``` 记得注册你的方块和方块实体。 进入游戏你可以就可以看到渲染的物品和箱子方块了。 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_26_SpecialItemRender.md description: 26 物品特殊渲染 相关教程 --- # 参考 https://boson.v2mcdev.com/specialrender/ister.html ## 物品特殊渲染 你在模组中经常会看到一些具有动画的物品,我们这次就介绍下如何实现一个简单的具有动画效果的物品。使用的是BlockEntityWithoutLevelRenderer这样的一个类。 首先还是添加一个物品 ```java public class WrenchItem extends Item { public WrenchItem() { super(new Properties()); } @Override public void initializeClient(Consumer consumer) { consumer.accept(new IClientItemExtensions() { @Override public BlockEntityWithoutLevelRenderer getCustomRenderer() { return new WrenchItemRenderer(); } }); } } ``` 这个代码中你可能会有一部分不知道怎么回事,我们之后来讲,首先我们先来看怎么构造这样的一个类WrenchItemRenderer ```java public class WrenchItemRenderer extends BlockEntityWithoutLevelRenderer { private static int degree = 0; public WrenchItemRenderer(){ super(null,null); } @Override public void renderByItem(ItemStack pStack, ItemDisplayContext pDisplayContext, PoseStack pPoseStack, MultiBufferSource pBuffer, int pPackedLight, int pPackedOverlay) { if(degree==360){ degree=0; } degree++; ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer(); BakedModel bakedModel = itemRenderer.getModel(pStack,null,null,1); pPoseStack.pushPose(); pPoseStack.translate(0.5F, 0.5F, 0.5F); float xOffset = -1 / 32f; float zOffset = 0; pPoseStack.translate(-xOffset, 0, -zOffset); pPoseStack.mulPose(Axis.YP.rotationDegrees(degree)); pPoseStack.translate(xOffset, 0, zOffset); itemRenderer.render(pStack, ItemDisplayContext.NONE, false, pPoseStack, pBuffer, pPackedLight, pPackedOverlay, bakedModel); pPoseStack.popPose(); } } ``` 可以看到这样的一个render是BlockEntityWithoutLevelRenderer类是实现的,那么我么先看看BlockEntityWithoutLevelRenderer类,再来看我们写的这个类。 BlockEntityWithoutLevelRenderer用于实现在Minecraft游戏中渲染不需要block的实体渲染器。BlockEntityWithoutLevelRenderer类实现了ResourceManagerReloadListener接口,用于在资源管理器重新加载时,重新加载模型和材质等资源。 在类中声明了几个静态变量,例如SHULKER\_BOXES数组,用于存储不同颜色的箱子实体,DEFAULT\_SHULKER\_BOX变量,用于存储默认的箱子实体,以及几个实体对象,例如chest、trappedChest、enderChest等,用于渲染不同类型的实体。 renderByItem()方法是在渲染物品时调用的方法,用于渲染方块实体。在该方法中,首先获取物品栈的物品对象,根据该物品的类型,选择不同的渲染方式。例如,如果物品是盾牌,则调用盾牌模型渲染方法;如果物品是三叉戟,则调用三叉戟模型渲染方法;如果物品是一个方块,则根据方块的类型和状态,选择相应的实体对象进行渲染。 实际上我们的动画的物品是同样是通过方块实体渲染来实现的,我们要重写方法就是这个renderByItem()方法。 好了,我们来看我们的这个render吧。 ```java private static int degree = 0; ``` 这里声明了一个字段,这个字段是用于计算物品旋转的情况的。 我们重写了renderByItem方法,以下是内容: ```java if(degree==360){ degree=0; } degree++; ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer(); BakedModel bakedModel = itemRenderer.getModel(pStack,null,null,0); pPoseStack.pushPose(); pPoseStack.translate(0.5F, 0.5F, 0.5F); float xOffset = -1 / 32f; float zOffset = 0; pPoseStack.translate(-xOffset, 0, -zOffset); pPoseStack.mulPose(Axis.YP.rotationDegrees(degree)); pPoseStack.translate(xOffset, 0, zOffset); itemRenderer.render(pStack, ItemDisplayContext.NONE, false, pPoseStack, pBuffer, pPackedLight, pPackedOverlay, bakedModel); pPoseStack.popPose(); ``` 我们做了这些操作。首先判断了角度,如果是360就重置,否则就自增。然后我们获得了ItemRenderer,通过ItemRenderer获得了物品的烘焙模型BakedModel,之后我们调整移动这个模型这个模型,并让他沿着Y轴缓慢旋转 好了我们定义了render但是我们的render现在并没有工作,我们还需要在烘焙模型中开启使用BlockEntityWithoutLevelRenderer,并且和物品进行绑定. 我们先定义我们的烘焙模型,并替换原本的烘焙模型,这些操作和上一个教程类似。 ```java public class WrenchBakeModel implements BakedModel { private BakedModel existingModel; public WrenchBakeModel(BakedModel existingModel) { this.existingModel = existingModel; } @Override public List getQuads(@Nullable BlockState pState, @Nullable Direction pDirection, RandomSource pRandom) { return this.existingModel.getQuads(pState, pDirection, pRandom); } @Override public @NotNull List getQuads(@Nullable BlockState state, @Nullable Direction side, @NotNull RandomSource rand, @NotNull ModelData data, @Nullable RenderType renderType) { throw new AssertionError("IForgeBakedModel::getQuads should never be called, only IForgeBakedModel::getQuads"); } @Override public boolean useAmbientOcclusion() { return this.existingModel.useAmbientOcclusion(); } @Override public boolean isGui3d() { return this.existingModel.isGui3d(); } @Override public boolean usesBlockLight() { return this.existingModel.usesBlockLight(); } @Override public boolean isCustomRenderer() { return true; } @Override public TextureAtlasSprite getParticleIcon() { return this.existingModel.getParticleIcon(); } @Override public ItemOverrides getOverrides() { return this.existingModel.getOverrides(); } @Override public BakedModel applyTransform(ItemDisplayContext transformType, PoseStack poseStack, boolean applyLeftHandTransform) { if (transformType == ItemDisplayContext.FIRST_PERSON_RIGHT_HAND || transformType == ItemDisplayContext.FIRST_PERSON_LEFT_HAND){ return this; } return this.existingModel.applyTransform(transformType,poseStack,applyLeftHandTransform); } } ``` 第一个需要说的是这里,对于物品获得对于的顶点数据,是getQuads(@Nullable BlockState pState, @Nullable Direction pDirection, RandomSource pRandom) 方法,而 getQuads(@Nullable BlockState state, @Nullable Direction side, @NotNull RandomSource rand, @NotNull ModelData data, @Nullable RenderType renderType)方法是不会生效的,所以这里我们直接抛出一个异常。 ```java @Override public List getQuads(@Nullable BlockState pState, @Nullable Direction pDirection, RandomSource pRandom) { return this.existingModel.getQuads(pState, pDirection, pRandom); } @Override public @NotNull List getQuads(@Nullable BlockState state, @Nullable Direction side, @NotNull RandomSource rand, @NotNull ModelData data, @Nullable RenderType renderType) { throw new AssertionError("IForgeBakedModel::getQuads should never be called, only IForgeBakedModel::getQuads"); } ``` 第二个要说就是这个, 这个表示了我们的模型要使用自己定义的渲染,而不是使用默认的渲染。 ```java @Override public boolean isCustomRenderer() { return true; } ``` 第三个就是这里,这里的意识是说在在不同的ItemDisplayContext下返回的烘焙模型不一样,这里我们在玩家的手持的时候返回我们这个模型,也是就是自定义渲染的这个模型,而且时候返回默认的模型。 ````java @Override public BakedModel applyTransform(ItemDisplayContext transformType, PoseStack poseStack, boolean applyLeftHandTransform) { if (transformType == ItemDisplayContext.FIRST_PERSON_RIGHT_HAND || transformType == ItemDisplayContext.FIRST_PERSON_LEFT_HAND){ return this; } return this.existingModel.applyTransform(transformType,poseStack,applyLeftHandTransform); }``` 使用我们的模型替换掉原本的烘焙模型 ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public static class ModEventBus{ @SubscribeEvent public static void onModelBaked(ModelEvent.ModifyBakingResult event){ // wrench item model Map modelRegistry = event.getModels(); ModelResourceLocation location = new ModelResourceLocation(BuiltInRegistries.ITEM.getKey(ModItems.WRENCH_ITEM.get()), "inventory"); BakedModel existingModel = modelRegistry.get(location); if (existingModel == null) { throw new RuntimeException("Did not find Obsidian Hidden in registry"); } else if (existingModel instanceof WrenchBakeModel) { throw new RuntimeException("Tried to replaceObsidian Hidden twice"); } else { WrenchBakeModel obsidianWrenchBakedModel = new WrenchBakeModel(existingModel); event.getModels().put(location, obsidianWrenchBakedModel); } } } ```` 和之前的内容大致相同,这里就不多加赘述了。 好了回到我们最开始的那个物品的类的中,我可以解释那段代码了。 ```java @Override public void initializeClient(Consumer consumer) { consumer.accept(new IClientItemExtensions() { @Override public BlockEntityWithoutLevelRenderer getCustomRenderer() { return new WrenchItemRenderer(); } }); } ``` public BlockEntityWithoutLevelRenderer getCustomRenderer(): 这个方法返回一个自定义的渲染器,用于在客户端渲染 WrenchItem 对象。代码中创建了一个 WrenchItemRenderer 对象并返回。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_26_SpecialItemRender.md description: 26 物品特殊渲染 相关教程 --- # 参考 https://boson.v2mcdev.com/specialrender/ister.html ## 物品特殊渲染 你在模组中经常会看到一些具有动画的物品,我们这次就介绍下如何实现一个简单的具有动画效果的物品。使用的是BlockEntityWithoutLevelRenderer这样的一个类。 首先还是添加一个物品 ```java public class WrenchItem extends Item { public WrenchItem() { super(new Properties()); } @Override public void initializeClient(Consumer consumer) { consumer.accept(new IClientItemExtensions() { @Override public BlockEntityWithoutLevelRenderer getCustomRenderer() { return new WrenchItemRenderer(); } }); } } ``` 这个代码中你可能会有一部分不知道怎么回事,我们之后来讲,首先我们先来看怎么构造这样的一个类WrenchItemRenderer ```java public class WrenchItemRenderer extends BlockEntityWithoutLevelRenderer { private static int degree = 0; public WrenchItemRenderer(){ super(null,null); } @Override public void renderByItem(ItemStack pStack, ItemDisplayContext pDisplayContext, PoseStack pPoseStack, MultiBufferSource pBuffer, int pPackedLight, int pPackedOverlay) { if(degree==360){ degree=0; } degree++; ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer(); BakedModel bakedModel = itemRenderer.getModel(pStack,null,null,1); pPoseStack.pushPose(); pPoseStack.translate(0.5F, 0.5F, 0.5F); float xOffset = -1 / 32f; float zOffset = 0; pPoseStack.translate(-xOffset, 0, -zOffset); pPoseStack.mulPose(Axis.YP.rotationDegrees(degree)); pPoseStack.translate(xOffset, 0, zOffset); itemRenderer.render(pStack, ItemDisplayContext.NONE, false, pPoseStack, pBuffer, pPackedLight, pPackedOverlay, bakedModel); pPoseStack.popPose(); } } ``` 可以看到这样的一个render是BlockEntityWithoutLevelRenderer类是实现的,那么我么先看看BlockEntityWithoutLevelRenderer类,再来看我们写的这个类。 BlockEntityWithoutLevelRenderer用于实现在Minecraft游戏中渲染不需要block的实体渲染器。BlockEntityWithoutLevelRenderer类实现了ResourceManagerReloadListener接口,用于在资源管理器重新加载时,重新加载模型和材质等资源。 在类中声明了几个静态变量,例如SHULKER\_BOXES数组,用于存储不同颜色的箱子实体,DEFAULT\_SHULKER\_BOX变量,用于存储默认的箱子实体,以及几个实体对象,例如chest、trappedChest、enderChest等,用于渲染不同类型的实体。 renderByItem()方法是在渲染物品时调用的方法,用于渲染方块实体。在该方法中,首先获取物品栈的物品对象,根据该物品的类型,选择不同的渲染方式。例如,如果物品是盾牌,则调用盾牌模型渲染方法;如果物品是三叉戟,则调用三叉戟模型渲染方法;如果物品是一个方块,则根据方块的类型和状态,选择相应的实体对象进行渲染。 实际上我们的动画的物品是同样是通过方块实体渲染来实现的,我们要重写方法就是这个renderByItem()方法。 好了,我们来看我们的这个render吧。 ```java private static int degree = 0; ``` 这里声明了一个字段,这个字段是用于计算物品旋转的情况的。 我们重写了renderByItem方法,以下是内容: ```java if(degree==360){ degree=0; } degree++; ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer(); BakedModel bakedModel = itemRenderer.getModel(pStack,null,null,0); pPoseStack.pushPose(); pPoseStack.translate(0.5F, 0.5F, 0.5F); float xOffset = -1 / 32f; float zOffset = 0; pPoseStack.translate(-xOffset, 0, -zOffset); pPoseStack.mulPose(Axis.YP.rotationDegrees(degree)); pPoseStack.translate(xOffset, 0, zOffset); itemRenderer.render(pStack, ItemDisplayContext.NONE, false, pPoseStack, pBuffer, pPackedLight, pPackedOverlay, bakedModel); pPoseStack.popPose(); ``` 我们做了这些操作。首先判断了角度,如果是360就重置,否则就自增。然后我们获得了ItemRenderer,通过ItemRenderer获得了物品的烘焙模型BakedModel,之后我们调整移动这个模型这个模型,并让他沿着Y轴缓慢旋转 好了我们定义了render但是我们的render现在并没有工作,我们还需要在烘焙模型中开启使用BlockEntityWithoutLevelRenderer,并且和物品进行绑定. 我们先定义我们的烘焙模型,并替换原本的烘焙模型,这些操作和上一个教程类似。 ```java public class WrenchBakeModel implements BakedModel { private BakedModel existingModel; public WrenchBakeModel(BakedModel existingModel) { this.existingModel = existingModel; } @Override public List getQuads(@Nullable BlockState pState, @Nullable Direction pDirection, RandomSource pRandom) { return this.existingModel.getQuads(pState, pDirection, pRandom); } @Override public @NotNull List getQuads(@Nullable BlockState state, @Nullable Direction side, @NotNull RandomSource rand, @NotNull ModelData data, @Nullable RenderType renderType) { throw new AssertionError("IForgeBakedModel::getQuads should never be called, only IForgeBakedModel::getQuads"); } @Override public boolean useAmbientOcclusion() { return this.existingModel.useAmbientOcclusion(); } @Override public boolean isGui3d() { return this.existingModel.isGui3d(); } @Override public boolean usesBlockLight() { return this.existingModel.usesBlockLight(); } @Override public boolean isCustomRenderer() { return true; } @Override public TextureAtlasSprite getParticleIcon() { return this.existingModel.getParticleIcon(); } @Override public ItemOverrides getOverrides() { return this.existingModel.getOverrides(); } @Override public BakedModel applyTransform(ItemDisplayContext transformType, PoseStack poseStack, boolean applyLeftHandTransform) { if (transformType == ItemDisplayContext.FIRST_PERSON_RIGHT_HAND || transformType == ItemDisplayContext.FIRST_PERSON_LEFT_HAND){ return this; } return this.existingModel.applyTransform(transformType,poseStack,applyLeftHandTransform); } } ``` 第一个需要说的是这里,对于物品获得对于的顶点数据,是getQuads(@Nullable BlockState pState, @Nullable Direction pDirection, RandomSource pRandom) 方法,而 getQuads(@Nullable BlockState state, @Nullable Direction side, @NotNull RandomSource rand, @NotNull ModelData data, @Nullable RenderType renderType)方法是不会生效的,所以这里我们直接抛出一个异常。 ```java @Override public List getQuads(@Nullable BlockState pState, @Nullable Direction pDirection, RandomSource pRandom) { return this.existingModel.getQuads(pState, pDirection, pRandom); } @Override public @NotNull List getQuads(@Nullable BlockState state, @Nullable Direction side, @NotNull RandomSource rand, @NotNull ModelData data, @Nullable RenderType renderType) { throw new AssertionError("IForgeBakedModel::getQuads should never be called, only IForgeBakedModel::getQuads"); } ``` 第二个要说就是这个, 这个表示了我们的模型要使用自己定义的渲染,而不是使用默认的渲染。 ```java @Override public boolean isCustomRenderer() { return true; } ``` 第三个就是这里,这里的意识是说在在不同的ItemDisplayContext下返回的烘焙模型不一样,这里我们在玩家的手持的时候返回我们这个模型,也是就是自定义渲染的这个模型,而且时候返回默认的模型。 ````java @Override public BakedModel applyTransform(ItemDisplayContext transformType, PoseStack poseStack, boolean applyLeftHandTransform) { if (transformType == ItemDisplayContext.FIRST_PERSON_RIGHT_HAND || transformType == ItemDisplayContext.FIRST_PERSON_LEFT_HAND){ return this; } return this.existingModel.applyTransform(transformType,poseStack,applyLeftHandTransform); }``` 使用我们的模型替换掉原本的烘焙模型 ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public static class ModEventBus{ @SubscribeEvent public static void onModelBaked(ModelEvent.ModifyBakingResult event){ // wrench item model Map modelRegistry = event.getModels(); ModelResourceLocation location = new ModelResourceLocation(BuiltInRegistries.ITEM.getKey(ModItems.WRENCH_ITEM.get()), "inventory"); BakedModel existingModel = modelRegistry.get(location); if (existingModel == null) { throw new RuntimeException("Did not find Obsidian Hidden in registry"); } else if (existingModel instanceof WrenchBakeModel) { throw new RuntimeException("Tried to replaceObsidian Hidden twice"); } else { WrenchBakeModel obsidianWrenchBakedModel = new WrenchBakeModel(existingModel); event.getModels().put(location, obsidianWrenchBakedModel); } } } ```` 和之前的内容大致相同,这里就不多加赘述了。 好了回到我们最开始的那个物品的类的中,我可以解释那段代码了。 ```java @Override public void initializeClient(Consumer consumer) { consumer.accept(new IClientItemExtensions() { @Override public BlockEntityWithoutLevelRenderer getCustomRenderer() { return new WrenchItemRenderer(); } }); } ``` public BlockEntityWithoutLevelRenderer getCustomRenderer(): 这个方法返回一个自定义的渲染器,用于在客户端渲染 WrenchItem 对象。代码中创建了一个 WrenchItemRenderer 对象并返回。 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_27_Networking.md description: 27 网络 相关教程 --- # 参考 https://boson.v2mcdev.com/networking/custompack.html https://github.dev/illusivesoulworks/comforts https://github.dev/Tschipp/CarryOn https://docs.neoforged.net/docs/networking/payload ## 自定义网络包 在之前的方块实体的数据同步中,我们介绍了一种数据同步的方法,用于方块实体的数据不同,但是问题是同步的数据量小,只能服务器往客户端发送数据,并且只能用在方块实体上。 这次我们介绍一种更加方便通用的方法用于数据同步,这就是自定义数据包。 网络传输是字节流数据,所以我们需要一种方法能将数据转为字节,然后在网络上传输,幸运的是MC已经提供了这样的方法,我们子需要定义自己的网路包,以及处理网络包就可以了。 网络传输的流程大概如下 ```java 通过本地、局域网传输或者因特网传输 构建数据包=>序列化成字节流 ===============================> 反序列化成实例 => 实现操作 ``` > 内容来之https://boson.v2mcdev.com/networking/custompack.html 有两部分是我们要处理的,第一个是定义网络包,第二个实现接受包的操作。 我们先看看,我们的数据包是怎么定义的。 ```java public record MyData(String message, int age) implements CustomPacketPayload { public static final ResourceLocation ID = new ResourceLocation(ExampleMod.MODID,"my_data"); public MyData(final FriendlyByteBuf buf){ this(buf.readUtf(),buf.readInt()); } @Override public void write(FriendlyByteBuf pBuffer) { pBuffer.writeUtf(message()); pBuffer.writeInt(age()); } @Override public ResourceLocation id() { return ID; } } ``` 这样的一个记录,字段有一个string的 message和一个int的age,我们通过网络传输这个message和age。 所以我们需要一个decode方法和一个encode方法,即将数据序列化为字节流,和将字节反序列化为数据。而FriendlyByteBuf这个类为我们提供了简单方便的方法将数据处理为字节,以及将字节处理为数据,而这个类中的MyData构造方法就是将字节流处理为我们的数据,这里返回了一个新的MyData的实例。而wirte方法就是将我们的数据转为字节流。只需要写入到FriendlyByteBuf中即可,FriendlyByteBuf提供很多方法,例如这里的写入字节流,和整数,也有其他的方法,自己看即可。 这个ID是我们自定义数据包要实现CustomPacketPayload接口后必须要重写的方法。返回一个ID即可,ID书写方式 ```java public static final ResourceLocation ID = new ResourceLocation(ExampleMod.MODID,"my_data"); /// 你的modid和包名 ``` 下面我们来看看当我们接受到数据包时候怎么处理。 第一情况是服务端发包。客户端接受处理数据包。 我们写了这样的一个类,其中使用了单例的设计模式,LOGGER是我们打印信息使用的非必要。而handleData是我们处理数据包的函数,其中第一个参数就是处理的对应的数据包的类型。第二个参数是网路的上下文,包含了网络的发包的人以及其他的信息。 当你的数据要在网络的线程中处理,那么你的代码写在workhandler之外,如果你的数据要在主线程处理,写在submitAsync内。对于异常的处理写在exceptionally内。 ```java public class ClientPayloadHandler { // 此代码用于打印信息的。非必要 private static final Logger LOGGER = LogUtils.getLogger(); // 实现单例 private static final ClientPayloadHandler INSTANCE = new ClientPayloadHandler(); public static ClientPayloadHandler getInstance() { return INSTANCE; } public void handleData(final MyData data, final PlayPayloadContext context) { // Do something with the data, on the network thread // 在network 线程中对data数据做一些处理的话,代码写在这里。 // Do something with the data, on the main thread // 在Main 线程里面做一些什么,代码西在下面 context.workHandler().submitAsync(() -> { // 写在这里 LOGGER.info(data.message()); }) .exceptionally(e -> { // 处理异常 // Handle exception context.packetHandler().disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } } ``` 第二种情况是客户端发包,服务端接受处理,大体上是类似的这里就不过多做介绍了。 ```java public class ServerPayloadHandler { private static final ServerPayloadHandler INSTANCE = new ServerPayloadHandler(); private static final Logger LOGGER = LogUtils.getLogger(); public static ServerPayloadHandler getInstance() { return INSTANCE; } public void handleData(final MyData data, final PlayPayloadContext context) { // Do something with the data, on the network thread // Do something with the data, on the main thread context.workHandler().submitAsync(() -> { LOGGER.info(data.message()); }) .exceptionally(e -> { // Handle exception context.packetHandler().disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } } ``` 那么我们的网络包和处理的数据的两个handler应该怎么注册呢? 下面是他们的注册的方法。通过IPayloadRegistrar的play方法进行注册。其中的第一个参数是网络包的ID,第二个参数是将字节流反序列化为数据包对象的方法。第三个参数是对应的数据包的处理方法。我们分别设置了客户端的处理方法和服务端的处理方法,其中的方法引用就是我们写的handlerData,其中getInstance就是获得单例。 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public class Networking { @SubscribeEvent public static void register(final RegisterPayloadHandlerEvent event) { final IPayloadRegistrar registrar = event.registrar(ExampleMod.MODID); registrar.play(MyData.ID,MyData::new,handler -> handler.client(ClientPayloadHandler.getInstance()::handleData) .server(ServerPayloadHandler.getInstance()::handleData)); } } ``` 好了让我们再来看看怎么使用这个网络包把。这里我们写一个Item,这个item右键后会从客户端向服务器发包,其中的message是From client,同时也会从服务器想客户端发包,其中message的内容是From server,数据包接受后就打屏幕上打印出来。 其中提的提一下的就是服务器往客户端发包,我们可以指定具体的玩家,通过with指定,如果是所有玩家使用ALL,详细见PacketDistributor类下的内容。 ```java public class MessageItem extends Item { public MessageItem() { super(new Properties()); } @Override public InteractionResultHolder use(Level pLevel, Player pPlayer, InteractionHand pUsedHand) { if(pLevel.isClientSide){ PacketDistributor.SERVER.noArg().send(new MyData("From client",2)); } if(!pLevel.isClientSide){ PacketDistributor.PLAYER.with((ServerPlayer) pPlayer).send(new MyData("From server",2)); } return super.use(pLevel,pPlayer,pUsedHand); } } ``` 其中的Render thread是客户端线程,Server thread是服务端线程。 ```cmd [18:37:30] [Server thread/INFO] [ne.fl.ex.ne.ServerPayloadHandler/]: From client [18:37:30] [Render thread/INFO] [ne.fl.ex.ne.ClientPayloadHandler/]: From server [18:37:31] [Server thread/INFO] [ne.fl.ex.ne.ServerPayloadHandler/]: From client [18:37:31] [Render thread/INFO] [ne.fl.ex.ne.ClientPayloadHandler/]: From server [18:37:31] [Server thread/INFO] [ne.fl.ex.ne.ServerPayloadHandler/]: From client ``` --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_27_Networking.md description: 27 网络 相关教程 --- # 参考 https://boson.v2mcdev.com/networking/custompack.html https://github.dev/illusivesoulworks/comforts https://github.dev/Tschipp/CarryOn https://docs.neoforged.net/docs/networking/payload ## 自定义网络包 在之前的方块实体的数据同步中,我们介绍了一种数据同步的方法,用于方块实体的数据不同,但是问题是同步的数据量小,只能服务器往客户端发送数据,并且只能用在方块实体上。 这次我们介绍一种更加方便通用的方法用于数据同步,这就是自定义数据包。 网络传输是字节流数据,所以我们需要一种方法能将数据转为字节,然后在网络上传输,幸运的是MC已经提供了这样的方法,我们子需要定义自己的网路包,以及处理网络包就可以了。 网络传输的流程大概如下 ```java 通过本地、局域网传输或者因特网传输 构建数据包=>序列化成字节流 ===============================> 反序列化成实例 => 实现操作 ``` > 内容来之https://boson.v2mcdev.com/networking/custompack.html 有两部分是我们要处理的,第一个是定义网络包,第二个实现接受包的操作。 我们先看看,我们的数据包是怎么定义的。 ```java public record MyData(String message, int age) implements CustomPacketPayload { public static final ResourceLocation ID = new ResourceLocation(ExampleMod.MODID,"my_data"); public MyData(final FriendlyByteBuf buf){ this(buf.readUtf(),buf.readInt()); } @Override public void write(FriendlyByteBuf pBuffer) { pBuffer.writeUtf(message()); pBuffer.writeInt(age()); } @Override public ResourceLocation id() { return ID; } } ``` 这样的一个记录,字段有一个string的 message和一个int的age,我们通过网络传输这个message和age。 所以我们需要一个decode方法和一个encode方法,即将数据序列化为字节流,和将字节反序列化为数据。而FriendlyByteBuf这个类为我们提供了简单方便的方法将数据处理为字节,以及将字节处理为数据,而这个类中的MyData构造方法就是将字节流处理为我们的数据,这里返回了一个新的MyData的实例。而wirte方法就是将我们的数据转为字节流。只需要写入到FriendlyByteBuf中即可,FriendlyByteBuf提供很多方法,例如这里的写入字节流,和整数,也有其他的方法,自己看即可。 这个ID是我们自定义数据包要实现CustomPacketPayload接口后必须要重写的方法。返回一个ID即可,ID书写方式 ```java public static final ResourceLocation ID = new ResourceLocation(ExampleMod.MODID,"my_data"); /// 你的modid和包名 ``` 下面我们来看看当我们接受到数据包时候怎么处理。 第一情况是服务端发包。客户端接受处理数据包。 我们写了这样的一个类,其中使用了单例的设计模式,LOGGER是我们打印信息使用的非必要。而handleData是我们处理数据包的函数,其中第一个参数就是处理的对应的数据包的类型。第二个参数是网路的上下文,包含了网络的发包的人以及其他的信息。 当你的数据要在网络的线程中处理,那么你的代码写在workhandler之外,如果你的数据要在主线程处理,写在submitAsync内。对于异常的处理写在exceptionally内。 ```java public class ClientPayloadHandler { // 此代码用于打印信息的。非必要 private static final Logger LOGGER = LogUtils.getLogger(); // 实现单例 private static final ClientPayloadHandler INSTANCE = new ClientPayloadHandler(); public static ClientPayloadHandler getInstance() { return INSTANCE; } public void handleData(final MyData data, final PlayPayloadContext context) { // Do something with the data, on the network thread // 在network 线程中对data数据做一些处理的话,代码写在这里。 // Do something with the data, on the main thread // 在Main 线程里面做一些什么,代码西在下面 context.workHandler().submitAsync(() -> { // 写在这里 LOGGER.info(data.message()); }) .exceptionally(e -> { // 处理异常 // Handle exception context.packetHandler().disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } } ``` 第二种情况是客户端发包,服务端接受处理,大体上是类似的这里就不过多做介绍了。 ```java public class ServerPayloadHandler { private static final ServerPayloadHandler INSTANCE = new ServerPayloadHandler(); private static final Logger LOGGER = LogUtils.getLogger(); public static ServerPayloadHandler getInstance() { return INSTANCE; } public void handleData(final MyData data, final PlayPayloadContext context) { // Do something with the data, on the network thread // Do something with the data, on the main thread context.workHandler().submitAsync(() -> { LOGGER.info(data.message()); }) .exceptionally(e -> { // Handle exception context.packetHandler().disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } } ``` 那么我们的网络包和处理的数据的两个handler应该怎么注册呢? 下面是他们的注册的方法。通过IPayloadRegistrar的play方法进行注册。其中的第一个参数是网络包的ID,第二个参数是将字节流反序列化为数据包对象的方法。第三个参数是对应的数据包的处理方法。我们分别设置了客户端的处理方法和服务端的处理方法,其中的方法引用就是我们写的handlerData,其中getInstance就是获得单例。 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public class Networking { @SubscribeEvent public static void register(final RegisterPayloadHandlerEvent event) { final IPayloadRegistrar registrar = event.registrar(ExampleMod.MODID); registrar.play(MyData.ID,MyData::new,handler -> handler.client(ClientPayloadHandler.getInstance()::handleData) .server(ServerPayloadHandler.getInstance()::handleData)); } } ``` 好了让我们再来看看怎么使用这个网络包把。这里我们写一个Item,这个item右键后会从客户端向服务器发包,其中的message是From client,同时也会从服务器想客户端发包,其中message的内容是From server,数据包接受后就打屏幕上打印出来。 其中提的提一下的就是服务器往客户端发包,我们可以指定具体的玩家,通过with指定,如果是所有玩家使用ALL,详细见PacketDistributor类下的内容。 ```java public class MessageItem extends Item { public MessageItem() { super(new Properties()); } @Override public InteractionResultHolder use(Level pLevel, Player pPlayer, InteractionHand pUsedHand) { if(pLevel.isClientSide){ PacketDistributor.SERVER.noArg().send(new MyData("From client",2)); } if(!pLevel.isClientSide){ PacketDistributor.PLAYER.with((ServerPlayer) pPlayer).send(new MyData("From server",2)); } return super.use(pLevel,pPlayer,pUsedHand); } } ``` 其中的Render thread是客户端线程,Server thread是服务端线程。 ```cmd [18:37:30] [Server thread/INFO] [ne.fl.ex.ne.ServerPayloadHandler/]: From client [18:37:30] [Render thread/INFO] [ne.fl.ex.ne.ClientPayloadHandler/]: From server [18:37:31] [Server thread/INFO] [ne.fl.ex.ne.ServerPayloadHandler/]: From client [18:37:31] [Render thread/INFO] [ne.fl.ex.ne.ClientPayloadHandler/]: From server [18:37:31] [Server thread/INFO] [ne.fl.ex.ne.ServerPayloadHandler/]: From client ``` --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_28_EasyToModifyTabs_Optional.md description: 28 便利添加创造模式物品栏【可选】 相关教程 --- # 便捷的方式将物品添加到创造模式物品栏\[可选] 想必大家已经添加物品然后在手动添加到物品栏已经很烦了,这里给出一种简单的解决方法,当你注册物品的时候,直接添加到物品栏中去。 修改ModdItem类如下 这并没有做什么额外的事情,只是创建一个map,然后写了一个register方法。注册调用register方法,将返回的supplierItem翻入到ITEMS\_SUPPLIER中去,然后将supplierItem返回。 ```java public class ModItems { public static final List> ITEMS_SUPPLIER = new ArrayList<>(); public static final DeferredRegister ITEMS = DeferredRegister.create(Registries.ITEM, ExampleMod.MODID); public static final Supplier MESSAGE_ITEM = register("message_item", MessageItem::new); public static Supplier register(String name, Supplier supplier){ Supplier supplierItem = ITEMS.register(name,supplier); ITEMS_SUPPLIER.add(supplierItem); return supplierItem; } public static void register(IEventBus eventBus){ ITEMS.register(eventBus); } } ``` 对于ModBlock类同样需要进行调整。我们修改了registerBlockItem方法不直接使用ITEMS了,而是转用ModItems.register方法,这样就可以将blockitem也添加到ITEMS\_SUPPLIER中去了。 ```java public static void registerBlockItem(String name, Supplier block, Item.Properties properties){ ModItems.register(name, () -> new BlockItem(block.get(), properties)); } ``` 最后我们的创造模式物品栏中遍历这个LIST,将对应的ITEM添加到其中。 ```java public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab",() -> CreativeModeTab.builder() .withTabsBefore(CreativeModeTabs.COMBAT) .title(Component.translatable(EXAMPLE_MOD_TAB_STRING)) .icon(()->ModItems.RUBY.get().getDefaultInstance()) .displayItems((pParameters, pOutput) -> { ModItems.ITEMS_SUPPLIER.forEach(item->pOutput.accept(item.get())); }) .build()); ``` --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_28_EasyToModifyTabs_Optional.md description: 28 便利添加创造模式物品栏【可选】 相关教程 --- # 便捷的方式将物品添加到创造模式物品栏\[可选] 想必大家已经添加物品然后在手动添加到物品栏已经很烦了,这里给出一种简单的解决方法,当你注册物品的时候,直接添加到物品栏中去。 修改ModdItem类如下 这并没有做什么额外的事情,只是创建一个map,然后写了一个register方法。注册调用register方法,将返回的supplierItem翻入到ITEMS\_SUPPLIER中去,然后将supplierItem返回。 ```java public class ModItems { public static final List> ITEMS_SUPPLIER = new ArrayList<>(); public static final DeferredRegister ITEMS = DeferredRegister.create(Registries.ITEM, ExampleMod.MODID); public static final Supplier MESSAGE_ITEM = register("message_item", MessageItem::new); public static Supplier register(String name, Supplier supplier){ Supplier supplierItem = ITEMS.register(name,supplier); ITEMS_SUPPLIER.add(supplierItem); return supplierItem; } public static void register(IEventBus eventBus){ ITEMS.register(eventBus); } } ``` 对于ModBlock类同样需要进行调整。我们修改了registerBlockItem方法不直接使用ITEMS了,而是转用ModItems.register方法,这样就可以将blockitem也添加到ITEMS\_SUPPLIER中去了。 ```java public static void registerBlockItem(String name, Supplier block, Item.Properties properties){ ModItems.register(name, () -> new BlockItem(block.get(), properties)); } ``` 最后我们的创造模式物品栏中遍历这个LIST,将对应的ITEM添加到其中。 ```java public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab",() -> CreativeModeTab.builder() .withTabsBefore(CreativeModeTabs.COMBAT) .title(Component.translatable(EXAMPLE_MOD_TAB_STRING)) .icon(()->ModItems.RUBY.get().getDefaultInstance()) .displayItems((pParameters, pOutput) -> { ModItems.ITEMS_SUPPLIER.forEach(item->pOutput.accept(item.get())); }) .build()); ``` --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_29_FirstEntity.md description: 29 第一个实体 相关教程 --- # 参考 https://boson.v2mcdev.com/entity/scratchentity.html 植物魔法 ## 实体 这此我们来看看怎么从零添加一个实体,游戏中的实体不仅仅是各种生物也包含了其他的内容,比如船,矿车,末影水晶都是实体。 实体是一个很重要的概念,模组的很多内容需要通过实体实现。下面我们介绍一下有关的内容。 在我的世界中你要做一个实体需要处理三个内容, * 一个是处理实体的逻辑的类Entity实体类 * 另一个是处理渲染的渲染类EntityRenderer,觉得实体怎么渲染 * 还有一个是实体的模型类EntityModel,觉得实体模型 这次我们做一个实体,实体的模型是通过blockbench制作的, 详细可以见看blockbench教程。这里会简单介绍下。 这个实体的逻辑是给数据自增,并完成和客户端的数据同步。 我们先来实现Entity类。 ```java public class FlyingSwordEntity extends Entity { //LOGGER的Logger对象,用于记录日志信息。 private static final Logger LOGGER = LogUtils.getLogger(); //COUNTER的实体数据访问器,用于存储实体的计数器数据。 private static final EntityDataAccessor COUNTER = SynchedEntityData.defineId(FlyingSwordEntity.class, EntityDataSerializers.INT); @Override public void tick() { //检查当前是否为客户端,如果是,则从实体数据中获取计数器数据并记录日志信息。如果不是客户端,则从实体数据中获取计数器数据,记录日志信息,并将计数器数据加1。最后,调用父类的tick()方法。 // 说的明白一些就是服务器将计数+1,然后进行数据的同步,在客户端打印出来。 if(this.level().isClientSide){ Integer i = this.entityData.get(COUNTER); LOGGER.info(i.toString()); } if(!this.level().isClientSide){ LOGGER.info(this.entityData.get(COUNTER).toString()); this.entityData.set(COUNTER,this.entityData.get(COUNTER)+1); } super.tick(); } //构造方法 public FlyingSwordEntity(EntityType pEntityType, Level pLevel) { super(pEntityType, pLevel); } //defineSynchedData():该方法用于定义实体的同步数据,在该方法中,将COUNTER实体数据访问器初始化为0。 @Override protected void defineSynchedData() { this.entityData.define(COUNTER, 0); } //readAdditionalSaveData():该方法用于从NBT标签中读取额外的保存数据,在该方法中,从NBT标签中读取计数器数据,并保存到实体数据中。 @Override protected void readAdditionalSaveData(CompoundTag pCompound) { this.entityData.set(COUNTER,pCompound.getInt("counter")); } //addAdditionalSaveData():该方法用于向NBT标签中添加额外的保存数据,在该方法中,将计数器数据保存到NBT标签中。 @Override protected void addAdditionalSaveData(CompoundTag pCompound) { pCompound.putInt("counter",this.entityData.get(COUNTER)); } } ``` 在代码的注解中对每个代码进行了介绍,不过这里还需要对一些内容进行详细的讲解首先是COUNTER,所有客户端和服务端需要同步的数据都需要用这样的形式进行定义。泛型的类型是你数据的类型。第一个参数是实体类,第二个参数是类型,在EntityDataSerializers中找到,EntityDataSerializers定义的类型已经足够你使用了。 ```java private static final EntityDataAccessor COUNTER = SynchedEntityData.defineId(FlyingSwordEntity.class, EntityDataSerializers.INT); ``` 定义两端同步的数据还需要进行初始化,这这里 ```java @Override protected void defineSynchedData() { this.entityData.define(COUNTER, 0); } ``` 下面我们来看Model,你可以直接通过blockbench做模型做完之后导出对应的java文件,然后把其中的内容放在这里。我们对其中的内容做一定的解释。 model ```java public class FlyingSwordModel extends EntityModel { //它用于标记模型的一个特定层。这里它被用来注册模型并指定资源位置和层级的名称。 public static final ModelLayerLocation LAYER_LOCATION = new ModelLayerLocation(new ResourceLocation(ExampleMod.MODID, "flying_sword_entity"), "main"); //用来表示实体模型的主要部分 private final ModelPart body; public FlyingSwordModel(ModelPart root){ //调用root.getChild("body")获取到表示剑身体的模型部分,并赋值给body字段。 this.body = root.getChild("body"); } //静态方法createBodyLayer,该方法用于创建并定义飞行剑模型的结构和外观。 public static LayerDefinition createBodyLayer() { MeshDefinition meshdefinition = new MeshDefinition(); PartDefinition partdefinition = meshdefinition.getRoot(); //这个构建器用于定义立方体的形状、位置和纹理。 // -1.0F, -1.0F, -18.0F, 3.0F, 1.0F, 19.0F 前三个值是空间中的位置,然后三个值是方块的大小, // .texOffs(0, 20) 模型中一个方块的UV位置 PartDefinition body = partdefinition.addOrReplaceChild("body", CubeListBuilder.create().texOffs(0, 0).addBox(-1.0F, -1.0F, -18.0F, 3.0F, 1.0F, 19.0F, new CubeDeformation(0.0F)) .texOffs(0, 20).addBox(-3.0F, -2.0F, -1.0F, 7.0F, 3.0F, 3.0F, new CubeDeformation(0.0F)) .texOffs(0, 0).addBox(-1.0F, -1.0F, 1.0F, 3.0F, 1.0F, 5.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 23.0F, 0.0F)); // 方法返回一个LayerDefinition对象,这个对象包含了整个网格定义和纹理的尺寸。 return LayerDefinition.create(meshdefinition, 64, 64); } //这个方法用于在每个动画帧上设置实体的动画和姿势。这里它将剑的旋转角度设定为与实体的动作参数相关联。 @Override public void setupAnim(FlyingSwordEntity pEntity, float pLimbSwing, float pLimbSwingAmount, float pAgeInTicks, float pNetHeadYaw, float pHeadPitch) { //置剑身体body的三维旋转角度,分别对应于X轴(pLimbSwing)、Y轴(pNetHeadYaw)和Z轴(pHeadPitch)的旋转。 body.xRot = pLimbSwing; body.yRot = pNetHeadYaw; body.zRot = pHeadPitch; } //来控制模型的要怎么渲染的 // 一般只需要调用模型的render方法即可 @Override public void renderToBuffer(PoseStack pPoseStack, VertexConsumer pBuffer, int pPackedLight, int pPackedOverlay, float pRed, float pGreen, float pBlue, float pAlpha) { body.render(pPoseStack, pBuffer, pPackedLight, pPackedOverlay, pRed, pGreen, pBlue, pAlpha); } } ``` 下面是实体的renderer类,他继承于EntityRenderer,用来渲染我们的实体。 renderer ```java public class FlyingSwordEntityRenderer extends EntityRenderer { // 存储我们的模型。 private EntityModel flyingSwordModel; public FlyingSwordEntityRenderer(EntityRendererProvider.Context pContext) { super(pContext); //这里使用pContext.bakeLayer(FlyingSwordModel.LAYER_LOCATION)来准备ModelPart,这里的获得ModelPart是等会我们需要去注册的,通过LAYER_LOCATION注册我们的ModelPart flyingSwordModel = new FlyingSwordModel(pContext.bakeLayer(FlyingSwordModel.LAYER_LOCATION)); } //这个方法返回一个ResourceLocation对象,指明了飞行剑实体的纹理文件位置。 @Override public ResourceLocation getTextureLocation(Entity pEntity) { return new ResourceLocation(ExampleMod.MODID, "textures/entity/flying_sword_entity.png"); } //重写了render方法,这个方法定义了实体在游戏中的渲染逻辑。 @Override public void render(Entity pEntity, float pEntityYaw, float pPartialTick, PoseStack pPoseStack, MultiBufferSource pBuffer, int pPackedLight) { super.render(pEntity, pEntityYaw, pPartialTick, pPoseStack, pBuffer, pPackedLight); //你的渲染应该在posh和pop之间,避免污染其他的渲染。 pPoseStack.pushPose(); // 绕y轴旋转45° pPoseStack.mulPose(Axis.YN.rotationDegrees(45)); // 向下移动1格 pPoseStack.translate(0,-1,0); // 构建顶点 VertexConsumer buffer = pBuffer.getBuffer(this.flyingSwordModel.renderType(this.getTextureLocation(pEntity))); // 调用模型的render方法进行渲染,这里的OverlayTexture下有很多类型,自己选用。 this.flyingSwordModel.renderToBuffer(pPoseStack,buffer,pPackedLight, OverlayTexture.NO_OVERLAY,1f,1f,1f,1f); pPoseStack.popPose(); } } ``` 好了,三个类都写完了,不过我们还需要对他们进行注册,三个类都是需要注册的。 我们先来注册entity,这里对于的注册是entityType,比较简单。 不过我们还是需要说一下这个sized设置实体的碰撞体积,你进入游戏后按下f3+b就可以看到了。 ```java public class ModEntityTypes { public static final DeferredRegister> ENTITY_TYPES = DeferredRegister.create(Registries.ENTITY_TYPE, ExampleMod.MODID); public static final Supplier> FLYING_SWORD_ENTITY = ENTITY_TYPES.register("flying_sword_entity", () -> EntityType.Builder.of(FlyingSwordEntity::new, MobCategory.MISC).sized(2, 0.5F).build("flying_sword_entity")); public static void register(IEventBus eventBus){ ENTITY_TYPES.register(eventBus); } } ``` 这里注册我们的modelpart也就是在render中通过context获得的,我们用到了这个RegisterLayerDefinitions事件,这个事件提供了registerLayerDefinition的方法,我们需要提供对于的r】LayerDefinition,而LayerDefinition是我们的模型createBodyLayer方法返回的,所以这传入该方法的方法引用即可。 ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class ClientEventHandler { @SubscribeEvent public static void registerEntityLayers(EntityRenderersEvent.RegisterLayerDefinitions evt) { evt.registerLayerDefinition(FlyingSwordModel.LAYER_LOCATION, FlyingSwordModel::createBodyLayer); } } ``` 然后来注册我们的renderer,也是很简单,第一个参数是我们的实体,第二个参数是我们的renderer的构造方法的方法引用。传入即可。注意的默认传入的参数就是context ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class ClientEventHandler { @SubscribeEvent public static void onClientEvent(FMLClientSetupEvent event){ event.enqueueWork(()->{ EntityRenderers.register(ModEntityTypes.FLYING_SWORD_ENTITY.get(), FlyingSwordEntityRenderer::new); }); } } ``` 将你的贴图放在对于的位置下,然后就可以进入游戏看看了。 textures/entity/flying\_sword\_entity.png --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_29_FirstEntity.md description: 29 第一个实体 相关教程 --- # 参考 https://boson.v2mcdev.com/entity/scratchentity.html 植物魔法 ## 实体 这此我们来看看怎么从零添加一个实体,游戏中的实体不仅仅是各种生物也包含了其他的内容,比如船,矿车,末影水晶都是实体。 实体是一个很重要的概念,模组的很多内容需要通过实体实现。下面我们介绍一下有关的内容。 在我的世界中你要做一个实体需要处理三个内容, * 一个是处理实体的逻辑的类Entity实体类 * 另一个是处理渲染的渲染类EntityRenderer,觉得实体怎么渲染 * 还有一个是实体的模型类EntityModel,觉得实体模型 这次我们做一个实体,实体的模型是通过blockbench制作的, 详细可以见看blockbench教程。这里会简单介绍下。 这个实体的逻辑是给数据自增,并完成和客户端的数据同步。 我们先来实现Entity类。 ```java public class FlyingSwordEntity extends Entity { //LOGGER的Logger对象,用于记录日志信息。 private static final Logger LOGGER = LogUtils.getLogger(); //COUNTER的实体数据访问器,用于存储实体的计数器数据。 private static final EntityDataAccessor COUNTER = SynchedEntityData.defineId(FlyingSwordEntity.class, EntityDataSerializers.INT); @Override public void tick() { //检查当前是否为客户端,如果是,则从实体数据中获取计数器数据并记录日志信息。如果不是客户端,则从实体数据中获取计数器数据,记录日志信息,并将计数器数据加1。最后,调用父类的tick()方法。 // 说的明白一些就是服务器将计数+1,然后进行数据的同步,在客户端打印出来。 if(this.level().isClientSide){ Integer i = this.entityData.get(COUNTER); LOGGER.info(i.toString()); } if(!this.level().isClientSide){ LOGGER.info(this.entityData.get(COUNTER).toString()); this.entityData.set(COUNTER,this.entityData.get(COUNTER)+1); } super.tick(); } //构造方法 public FlyingSwordEntity(EntityType pEntityType, Level pLevel) { super(pEntityType, pLevel); } //defineSynchedData():该方法用于定义实体的同步数据,在该方法中,将COUNTER实体数据访问器初始化为0。 @Override protected void defineSynchedData() { this.entityData.define(COUNTER, 0); } //readAdditionalSaveData():该方法用于从NBT标签中读取额外的保存数据,在该方法中,从NBT标签中读取计数器数据,并保存到实体数据中。 @Override protected void readAdditionalSaveData(CompoundTag pCompound) { this.entityData.set(COUNTER,pCompound.getInt("counter")); } //addAdditionalSaveData():该方法用于向NBT标签中添加额外的保存数据,在该方法中,将计数器数据保存到NBT标签中。 @Override protected void addAdditionalSaveData(CompoundTag pCompound) { pCompound.putInt("counter",this.entityData.get(COUNTER)); } } ``` 在代码的注解中对每个代码进行了介绍,不过这里还需要对一些内容进行详细的讲解首先是COUNTER,所有客户端和服务端需要同步的数据都需要用这样的形式进行定义。泛型的类型是你数据的类型。第一个参数是实体类,第二个参数是类型,在EntityDataSerializers中找到,EntityDataSerializers定义的类型已经足够你使用了。 ```java private static final EntityDataAccessor COUNTER = SynchedEntityData.defineId(FlyingSwordEntity.class, EntityDataSerializers.INT); ``` 定义两端同步的数据还需要进行初始化,这这里 ```java @Override protected void defineSynchedData() { this.entityData.define(COUNTER, 0); } ``` 下面我们来看Model,你可以直接通过blockbench做模型做完之后导出对应的java文件,然后把其中的内容放在这里。我们对其中的内容做一定的解释。 model ```java public class FlyingSwordModel extends EntityModel { //它用于标记模型的一个特定层。这里它被用来注册模型并指定资源位置和层级的名称。 public static final ModelLayerLocation LAYER_LOCATION = new ModelLayerLocation(new ResourceLocation(ExampleMod.MODID, "flying_sword_entity"), "main"); //用来表示实体模型的主要部分 private final ModelPart body; public FlyingSwordModel(ModelPart root){ //调用root.getChild("body")获取到表示剑身体的模型部分,并赋值给body字段。 this.body = root.getChild("body"); } //静态方法createBodyLayer,该方法用于创建并定义飞行剑模型的结构和外观。 public static LayerDefinition createBodyLayer() { MeshDefinition meshdefinition = new MeshDefinition(); PartDefinition partdefinition = meshdefinition.getRoot(); //这个构建器用于定义立方体的形状、位置和纹理。 // -1.0F, -1.0F, -18.0F, 3.0F, 1.0F, 19.0F 前三个值是空间中的位置,然后三个值是方块的大小, // .texOffs(0, 20) 模型中一个方块的UV位置 PartDefinition body = partdefinition.addOrReplaceChild("body", CubeListBuilder.create().texOffs(0, 0).addBox(-1.0F, -1.0F, -18.0F, 3.0F, 1.0F, 19.0F, new CubeDeformation(0.0F)) .texOffs(0, 20).addBox(-3.0F, -2.0F, -1.0F, 7.0F, 3.0F, 3.0F, new CubeDeformation(0.0F)) .texOffs(0, 0).addBox(-1.0F, -1.0F, 1.0F, 3.0F, 1.0F, 5.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 23.0F, 0.0F)); // 方法返回一个LayerDefinition对象,这个对象包含了整个网格定义和纹理的尺寸。 return LayerDefinition.create(meshdefinition, 64, 64); } //这个方法用于在每个动画帧上设置实体的动画和姿势。这里它将剑的旋转角度设定为与实体的动作参数相关联。 @Override public void setupAnim(FlyingSwordEntity pEntity, float pLimbSwing, float pLimbSwingAmount, float pAgeInTicks, float pNetHeadYaw, float pHeadPitch) { //置剑身体body的三维旋转角度,分别对应于X轴(pLimbSwing)、Y轴(pNetHeadYaw)和Z轴(pHeadPitch)的旋转。 body.xRot = pLimbSwing; body.yRot = pNetHeadYaw; body.zRot = pHeadPitch; } //来控制模型的要怎么渲染的 // 一般只需要调用模型的render方法即可 @Override public void renderToBuffer(PoseStack pPoseStack, VertexConsumer pBuffer, int pPackedLight, int pPackedOverlay, float pRed, float pGreen, float pBlue, float pAlpha) { body.render(pPoseStack, pBuffer, pPackedLight, pPackedOverlay, pRed, pGreen, pBlue, pAlpha); } } ``` 下面是实体的renderer类,他继承于EntityRenderer,用来渲染我们的实体。 renderer ```java public class FlyingSwordEntityRenderer extends EntityRenderer { // 存储我们的模型。 private EntityModel flyingSwordModel; public FlyingSwordEntityRenderer(EntityRendererProvider.Context pContext) { super(pContext); //这里使用pContext.bakeLayer(FlyingSwordModel.LAYER_LOCATION)来准备ModelPart,这里的获得ModelPart是等会我们需要去注册的,通过LAYER_LOCATION注册我们的ModelPart flyingSwordModel = new FlyingSwordModel(pContext.bakeLayer(FlyingSwordModel.LAYER_LOCATION)); } //这个方法返回一个ResourceLocation对象,指明了飞行剑实体的纹理文件位置。 @Override public ResourceLocation getTextureLocation(Entity pEntity) { return new ResourceLocation(ExampleMod.MODID, "textures/entity/flying_sword_entity.png"); } //重写了render方法,这个方法定义了实体在游戏中的渲染逻辑。 @Override public void render(Entity pEntity, float pEntityYaw, float pPartialTick, PoseStack pPoseStack, MultiBufferSource pBuffer, int pPackedLight) { super.render(pEntity, pEntityYaw, pPartialTick, pPoseStack, pBuffer, pPackedLight); //你的渲染应该在posh和pop之间,避免污染其他的渲染。 pPoseStack.pushPose(); // 绕y轴旋转45° pPoseStack.mulPose(Axis.YN.rotationDegrees(45)); // 向下移动1格 pPoseStack.translate(0,-1,0); // 构建顶点 VertexConsumer buffer = pBuffer.getBuffer(this.flyingSwordModel.renderType(this.getTextureLocation(pEntity))); // 调用模型的render方法进行渲染,这里的OverlayTexture下有很多类型,自己选用。 this.flyingSwordModel.renderToBuffer(pPoseStack,buffer,pPackedLight, OverlayTexture.NO_OVERLAY,1f,1f,1f,1f); pPoseStack.popPose(); } } ``` 好了,三个类都写完了,不过我们还需要对他们进行注册,三个类都是需要注册的。 我们先来注册entity,这里对于的注册是entityType,比较简单。 不过我们还是需要说一下这个sized设置实体的碰撞体积,你进入游戏后按下f3+b就可以看到了。 ```java public class ModEntityTypes { public static final DeferredRegister> ENTITY_TYPES = DeferredRegister.create(Registries.ENTITY_TYPE, ExampleMod.MODID); public static final Supplier> FLYING_SWORD_ENTITY = ENTITY_TYPES.register("flying_sword_entity", () -> EntityType.Builder.of(FlyingSwordEntity::new, MobCategory.MISC).sized(2, 0.5F).build("flying_sword_entity")); public static void register(IEventBus eventBus){ ENTITY_TYPES.register(eventBus); } } ``` 这里注册我们的modelpart也就是在render中通过context获得的,我们用到了这个RegisterLayerDefinitions事件,这个事件提供了registerLayerDefinition的方法,我们需要提供对于的r】LayerDefinition,而LayerDefinition是我们的模型createBodyLayer方法返回的,所以这传入该方法的方法引用即可。 ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class ClientEventHandler { @SubscribeEvent public static void registerEntityLayers(EntityRenderersEvent.RegisterLayerDefinitions evt) { evt.registerLayerDefinition(FlyingSwordModel.LAYER_LOCATION, FlyingSwordModel::createBodyLayer); } } ``` 然后来注册我们的renderer,也是很简单,第一个参数是我们的实体,第二个参数是我们的renderer的构造方法的方法引用。传入即可。注意的默认传入的参数就是context ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class ClientEventHandler { @SubscribeEvent public static void onClientEvent(FMLClientSetupEvent event){ event.enqueueWork(()->{ EntityRenderers.register(ModEntityTypes.FLYING_SWORD_ENTITY.get(), FlyingSwordEntityRenderer::new); }); } } ``` 将你的贴图放在对于的位置下,然后就可以进入游戏看看了。 textures/entity/flying\_sword\_entity.png --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/feature1/tags.md --- # 3 标签 *** 你应将以下内容放在`.minecraft\kubejs\server_scripts`下 ``` onEvent('item.tags', event => { // 获取 #forge:cobblestone,然后将minecraft:diamond_ore添加进去 event.add('forge:cobblestone', 'minecraft:diamond_ore') // 获取#forge:cobblestone,然后移除minecraft:mossy_cobblestone event.remove('forge:cobblestone', 'minecraft:mossy_cobblestone') // 移除#forge:ingots/copper event.removeAll('forge:ingots/copper') }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/feature1/tags.md --- # 3 标签 *** 你应将以下内容放在`.minecraft\kubejs\server_scripts`下 ``` onEvent('item.tags', event => { // 获取 #forge:cobblestone,然后将minecraft:diamond_ore添加进去 event.add('forge:cobblestone', 'minecraft:diamond_ore') // 获取#forge:cobblestone,然后移除minecraft:mossy_cobblestone event.remove('forge:cobblestone', 'minecraft:mossy_cobblestone') // 移除#forge:ingots/copper event.removeAll('forge:ingots/copper') }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/tags.md --- # 3、标签 ## 一、标签事件 ### 1、事件监听 你可以使用`ServerEvents.tags`事件来进行与标签有关的修改,需要注意的是你需要向其传入参数来设置其修改的类型,即: ```js ServerEvents.tags("标签类型", event => { // 脚本 }) ``` 绝大多数情况下你只需要使用类型`item`、 `block` 和 `fluid`,但其还支持由其他模组注册的类型。 ### 2、事件方法 以下为标签事件支持的方法: | 方法 | 描述 | 返回值 | | ----------------------------------------------- | -------------------------------------- | ---------- | | add(ResourceLocation 标签, String... 对象ID) | 向给定标签中添加对象 | TagWrapper | | remove(ResourceLocation 标签, String... 对象ID) | 从给定标签中移除对象 | TagWrapper | | removeAll(ResourceLocation 标签) | 移除给定标签中的所有对象 | TagWrapper | | removeAllTagsFrom(String... 对象ID) | 移除包含给定对象的所有标签中的所有对象 | TagWrapper | | get(ResourceLocation 标签) | 获取标签中所有对象 | TagWrapper | ## 二、示例 ```js // 监听标签事件 ServerEvents.tags('item', event => { // 将 minecraft:diamond_ore 添加至 #forge:cobblestone event.add('forge:cobblestone', 'minecraft:diamond_ore') // 从 #forge:cobblestone 中移除 minecraft:mossy_cobblestone event.remove('forge:cobblestone', 'minecraft:mossy_cobblestone') // 移除 #forge:ingots/copper 中的所有对象 event.removeAll('forge:ingots/copper') // 用于 FTB任务 检测物品NBT event.add('itemfilters:check_nbt', 'some_item:that_has_nbt_types') // 自定义标签(#forge:completely_new_tag) event.add('forge:completely_new_tag', 'minecraft:clay_ball') // 移除包含 minecraft:stick 的所有标签中的所有对象 event.removeAllTagsFrom('minecraft:stick') // 将 #forge:stone 中所有非闪长岩对象添加到 #c:stone 中 const stones = event.get('forge:stone').getObjectIds() const blacklist = Ingredient.of(/.*diorite.*/) stones.forEach(stone => { if (!blacklist.test(stone)) { event.add('c:stone', stone) } }) }) ``` 另请参阅:[TagWrapper (github.com)](https://github.com/KubeJS-Mods/KubeJS/blob/3a446ee3127181a2e6492cd3d78021f034c91ec6/common/src/main/java/dev/latvian/mods/kubejs/server/TagEventJS.java#L35) --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/tags.md --- # 3、标签 ## 一、标签事件 ### 1、事件监听 你可以使用`ServerEvents.tags`事件来进行与标签有关的修改,需要注意的是你需要向其传入参数来设置其修改的类型,即: ```js ServerEvents.tags("标签类型", event => { // 脚本 }) ``` 绝大多数情况下你只需要使用类型`item`、 `block` 和 `fluid`,但其还支持由其他模组注册的类型。 ### 2、事件方法 以下为标签事件支持的方法: | 方法 | 描述 | 返回值 | | ----------------------------------------------- | -------------------------------------- | ---------- | | add(ResourceLocation 标签, String... 对象ID) | 向给定标签中添加对象 | TagWrapper | | remove(ResourceLocation 标签, String... 对象ID) | 从给定标签中移除对象 | TagWrapper | | removeAll(ResourceLocation 标签) | 移除给定标签中的所有对象 | TagWrapper | | removeAllTagsFrom(String... 对象ID) | 移除包含给定对象的所有标签中的所有对象 | TagWrapper | | get(ResourceLocation 标签) | 获取标签中所有对象 | TagWrapper | ## 二、示例 ```js // 监听标签事件 ServerEvents.tags('item', event => { // 将 minecraft:diamond_ore 添加至 #forge:cobblestone event.add('forge:cobblestone', 'minecraft:diamond_ore') // 从 #forge:cobblestone 中移除 minecraft:mossy_cobblestone event.remove('forge:cobblestone', 'minecraft:mossy_cobblestone') // 移除 #forge:ingots/copper 中的所有对象 event.removeAll('forge:ingots/copper') // 用于 FTB任务 检测物品NBT event.add('itemfilters:check_nbt', 'some_item:that_has_nbt_types') // 自定义标签(#forge:completely_new_tag) event.add('forge:completely_new_tag', 'minecraft:clay_ball') // 移除包含 minecraft:stick 的所有标签中的所有对象 event.removeAllTagsFrom('minecraft:stick') // 将 #forge:stone 中所有非闪长岩对象添加到 #c:stone 中 const stones = event.get('forge:stone').getObjectIds() const blacklist = Ingredient.of(/.*diorite.*/) stones.forEach(stone => { if (!blacklist.test(stone)) { event.add('c:stone', stone) } }) }) ``` 另请参阅:[TagWrapper (github.com)](https://github.com/KubeJS-Mods/KubeJS/blob/3a446ee3127181a2e6492cd3d78021f034c91ec6/common/src/main/java/dev/latvian/mods/kubejs/server/TagEventJS.java#L35) --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_30_FirstMob.md description: 30 第一个生物 相关教程 --- # 参考 https://boson.v2mcdev.com/entity/scratchentity.html ## 第一个动物以及AI 上一次我们从entity类出发创建了一个实体,不过大多时候是不需要从entity类出发创建实体的,你可以看到entitty的继承关系中,会有很多的子类,这些子类才是更多我们会利用到的, 这次我们还是利用entity子类Animal类创建一个动物实体,并为他设置一个AI/Goal,模型用blockbench创建。 什么是Goal/AI,其实就是生物的目的或者说是行为逻辑,例如牛会被小麦吸引等等。 什么是属性就是,这里的属性就是生物的血量,移动速度,需要单独配置并注册。 我们先看怎么写我们的这个animal entity类。 ```java public class FirstAnimal extends Animal { protected FirstAnimal(EntityType pEntityType, Level pLevel) { super(pEntityType, pLevel); } // 重写了父类的registerGoals方法,该方法用于注册动物的行为目标Goal。 @Override protected void registerGoals() { // 这里添加的是我们自己的Goal this.goalSelector.addGoal(0,new MyGoal(this)); } // 定义了一个静态方法createAttributes,用于创建和配置动物的属性。 // 这个方法返回一个AttributeSupplier.Builder对象,用于构建属性。 public static AttributeSupplier.Builder createAttributes() { return Mob.createMobAttributes() .add(Attributes.MAX_HEALTH, 20.0); } // 重写了父类的getBreedOffspring方法,该方法用于生成动物的繁殖后代。 // 我们这里不需要繁衍的生物,所以直接返回null即可。 @Nullable @Override public AgeableMob getBreedOffspring(ServerLevel pLevel, AgeableMob pOtherParent) { return null; } } ``` 动物AI ```java public class MyGoal extends Goal { public final FirstAnimal firstAnimal; public MyGoal(FirstAnimal firstAnimal){ this.firstAnimal = firstAnimal; } // 重写了Goal类中的canUse方法,该方法用于判断是否应当执行这个行为目标。 // 这里返回true 表示一直执行。 // 逻辑很简单就是给最近的玩家添加一个饥饿的效果。 @Override public boolean canUse() { Level level = this.firstAnimal.level(); if(!level.isClientSide){ Player nearestPlayer = level.getNearestPlayer(this.firstAnimal, 10); if(nearestPlayer!=null){ nearestPlayer.addEffect(new MobEffectInstance(MobEffects.HUNGER, 3 * 20, 3)); } } return true; } } ``` 动物模型 ```java public class FirstAnimalModel extends EntityModel { public static final ModelLayerLocation LAYER_LOCATION = new ModelLayerLocation(new ResourceLocation(ExampleMod.MODID, "first_animal"), "main"); private final ModelPart bb_main; public FirstAnimalModel(ModelPart root){ this.bb_main = root.getChild("bb_main"); } public static LayerDefinition createBodyLayer() { MeshDefinition meshdefinition = new MeshDefinition(); PartDefinition partdefinition = meshdefinition.getRoot(); PartDefinition bb_main = partdefinition.addOrReplaceChild("bb_main", CubeListBuilder.create().texOffs(0, 0).addBox(-8.0F, -20.0F, -8.0F, 16.0F, 11.0F, 16.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 24.0F, 0.0F)); return LayerDefinition.create(meshdefinition, 64, 64); } @Override public void setupAnim(FirstAnimal pEntity, float pLimbSwing, float pLimbSwingAmount, float pAgeInTicks, float pNetHeadYaw, float pHeadPitch) { } @Override public void renderToBuffer(PoseStack pPoseStack, VertexConsumer pBuffer, int pPackedLight, int pPackedOverlay, float pRed, float pGreen, float pBlue, float pAlpha) { bb_main.render(pPoseStack, pBuffer, pPackedLight, pPackedOverlay, pRed, pGreen, pBlue, pAlpha); } } ``` 动物渲染,这里我们使用了MobRenderer,相对于EntityRenderer多了一些渲染影子的内容内容,看构造函数, ```java public class FirstAnimalRenderer extends MobRenderer { public FirstAnimalRenderer(EntityRendererProvider.Context pContext) { // 第一个参数我们将EntityRendererProvider。Context传入, // 第二个参数是model,传入我们的model,model要求一个参数是modelpart,这里通过Context获得即可和之前一样。 // 第三个参数是影子的大小,这里传入1f半径。 super(pContext, new FirstAnimalModel(pContext.bakeLayer(FirstAnimalModel.LAYER_LOCATION)), 1f); } @Override public ResourceLocation getTextureLocation(FirstAnimal pEntity) { return new ResourceLocation(ExampleMod.MODID,"textures/entity/first_animal.png"); } } ``` 生物属性的注册,我们通过EntityAttributeCreationEvent进行注册, ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD) public static class ModEventBus{ @SubscribeEvent public static void setupAttributes(EntityAttributeCreationEvent event) { // 第一个参数是你的entityType,第二个参数是你的AttributeSupplier,通过我们写的Builder获得。 event.put(ModEntityTypes.FIRST_ANIMAL.get(), FirstAnimal.createAttributes().build()); } } ``` 照例的实体的render注册。 ```java @SubscribeEvent public static void onClientEvent(FMLClientSetupEvent event){ event.enqueueWork(()->{ EntityRenderers.register(ModEntityTypes.FIRST_ANIMAL.get(), FirstAnimalRenderer::new); }); } /// modelLayer注册。和之前一样。 @SubscribeEvent public static void registerEntityLayers(EntityRenderersEvent.RegisterLayerDefinitions evt) { evt.registerLayerDefinition(FirstAnimalModel.LAYER_LOCATION,FirstAnimalModel::createBodyLayer); } ``` 对于实体的注册就自己实现下把。 记得把你的纹理放到对应的位置上去,这样才能生效。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_30_FirstMob.md description: 30 第一个生物 相关教程 --- # 参考 https://boson.v2mcdev.com/entity/scratchentity.html ## 第一个动物以及AI 上一次我们从entity类出发创建了一个实体,不过大多时候是不需要从entity类出发创建实体的,你可以看到entitty的继承关系中,会有很多的子类,这些子类才是更多我们会利用到的, 这次我们还是利用entity子类Animal类创建一个动物实体,并为他设置一个AI/Goal,模型用blockbench创建。 什么是Goal/AI,其实就是生物的目的或者说是行为逻辑,例如牛会被小麦吸引等等。 什么是属性就是,这里的属性就是生物的血量,移动速度,需要单独配置并注册。 我们先看怎么写我们的这个animal entity类。 ```java public class FirstAnimal extends Animal { protected FirstAnimal(EntityType pEntityType, Level pLevel) { super(pEntityType, pLevel); } // 重写了父类的registerGoals方法,该方法用于注册动物的行为目标Goal。 @Override protected void registerGoals() { // 这里添加的是我们自己的Goal this.goalSelector.addGoal(0,new MyGoal(this)); } // 定义了一个静态方法createAttributes,用于创建和配置动物的属性。 // 这个方法返回一个AttributeSupplier.Builder对象,用于构建属性。 public static AttributeSupplier.Builder createAttributes() { return Mob.createMobAttributes() .add(Attributes.MAX_HEALTH, 20.0); } // 重写了父类的getBreedOffspring方法,该方法用于生成动物的繁殖后代。 // 我们这里不需要繁衍的生物,所以直接返回null即可。 @Nullable @Override public AgeableMob getBreedOffspring(ServerLevel pLevel, AgeableMob pOtherParent) { return null; } } ``` 动物AI ```java public class MyGoal extends Goal { public final FirstAnimal firstAnimal; public MyGoal(FirstAnimal firstAnimal){ this.firstAnimal = firstAnimal; } // 重写了Goal类中的canUse方法,该方法用于判断是否应当执行这个行为目标。 // 这里返回true 表示一直执行。 // 逻辑很简单就是给最近的玩家添加一个饥饿的效果。 @Override public boolean canUse() { Level level = this.firstAnimal.level(); if(!level.isClientSide){ Player nearestPlayer = level.getNearestPlayer(this.firstAnimal, 10); if(nearestPlayer!=null){ nearestPlayer.addEffect(new MobEffectInstance(MobEffects.HUNGER, 3 * 20, 3)); } } return true; } } ``` 动物模型 ```java public class FirstAnimalModel extends EntityModel { public static final ModelLayerLocation LAYER_LOCATION = new ModelLayerLocation(new ResourceLocation(ExampleMod.MODID, "first_animal"), "main"); private final ModelPart bb_main; public FirstAnimalModel(ModelPart root){ this.bb_main = root.getChild("bb_main"); } public static LayerDefinition createBodyLayer() { MeshDefinition meshdefinition = new MeshDefinition(); PartDefinition partdefinition = meshdefinition.getRoot(); PartDefinition bb_main = partdefinition.addOrReplaceChild("bb_main", CubeListBuilder.create().texOffs(0, 0).addBox(-8.0F, -20.0F, -8.0F, 16.0F, 11.0F, 16.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 24.0F, 0.0F)); return LayerDefinition.create(meshdefinition, 64, 64); } @Override public void setupAnim(FirstAnimal pEntity, float pLimbSwing, float pLimbSwingAmount, float pAgeInTicks, float pNetHeadYaw, float pHeadPitch) { } @Override public void renderToBuffer(PoseStack pPoseStack, VertexConsumer pBuffer, int pPackedLight, int pPackedOverlay, float pRed, float pGreen, float pBlue, float pAlpha) { bb_main.render(pPoseStack, pBuffer, pPackedLight, pPackedOverlay, pRed, pGreen, pBlue, pAlpha); } } ``` 动物渲染,这里我们使用了MobRenderer,相对于EntityRenderer多了一些渲染影子的内容内容,看构造函数, ```java public class FirstAnimalRenderer extends MobRenderer { public FirstAnimalRenderer(EntityRendererProvider.Context pContext) { // 第一个参数我们将EntityRendererProvider。Context传入, // 第二个参数是model,传入我们的model,model要求一个参数是modelpart,这里通过Context获得即可和之前一样。 // 第三个参数是影子的大小,这里传入1f半径。 super(pContext, new FirstAnimalModel(pContext.bakeLayer(FirstAnimalModel.LAYER_LOCATION)), 1f); } @Override public ResourceLocation getTextureLocation(FirstAnimal pEntity) { return new ResourceLocation(ExampleMod.MODID,"textures/entity/first_animal.png"); } } ``` 生物属性的注册,我们通过EntityAttributeCreationEvent进行注册, ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD) public static class ModEventBus{ @SubscribeEvent public static void setupAttributes(EntityAttributeCreationEvent event) { // 第一个参数是你的entityType,第二个参数是你的AttributeSupplier,通过我们写的Builder获得。 event.put(ModEntityTypes.FIRST_ANIMAL.get(), FirstAnimal.createAttributes().build()); } } ``` 照例的实体的render注册。 ```java @SubscribeEvent public static void onClientEvent(FMLClientSetupEvent event){ event.enqueueWork(()->{ EntityRenderers.register(ModEntityTypes.FIRST_ANIMAL.get(), FirstAnimalRenderer::new); }); } /// modelLayer注册。和之前一样。 @SubscribeEvent public static void registerEntityLayers(EntityRenderersEvent.RegisterLayerDefinitions evt) { evt.registerLayerDefinition(FirstAnimalModel.LAYER_LOCATION,FirstAnimalModel::createBodyLayer); } ``` 对于实体的注册就自己实现下把。 记得把你的纹理放到对应的位置上去,这样才能生效。 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_31_Enchantment.md description: 31 附魔 相关教程 --- # 直接看:https://flandre923.github.io/2024/01/30/%E3%80%90Minecraft-1-20-4-NeoForge-%E6%A8%A1%E7%BB%84%E6%95%99%E7%A8%8B%E3%80%9115%E9%99%84%E9%AD%94/?highlight=%E9%99%84%E9%AD%94 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_31_Enchantment.md description: 31 附魔 相关教程 --- # 直接看:https://flandre923.github.io/2024/01/30/%E3%80%90Minecraft-1-20-4-NeoForge-%E6%A8%A1%E7%BB%84%E6%95%99%E7%A8%8B%E3%80%9115%E9%99%84%E9%AD%94/?highlight=%E9%99%84%E9%AD%94 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_32_BuildCapability.md description: 32 构建能力 相关教程 --- # 参考 https://boson.v2mcdev.com/capability/capabilityfromscratch.html https://docs.neoforged.net/docs/datastorage/capabilities ## 能力介绍,构建能力 我们先来介绍一下能系统,能力系统是NeoForge增加一套系统,不是原版就有的,能力系统用于处理一些给游戏物品,方块,实体等添加新的功能的内容,例如给一些机器添加处理流体,处理物品,处理能量这样的功能,我们可以说给该方块或者物品添加了这样的能力。 能力系统主要和这样的几个类有关, * 创建实体的能力的EntityCapability (net.neoforged.neoforge.capabilities)类, * 创建方块和方块实体能力的BlockCapability (net.neoforged.neoforge.capabilities)类 * 创建物品能力的ItemCapability (net.neoforged.neoforge.capabilities)类 * NeoForg提供的能力的实例的Capabilities类, * 给方块,物品,实体添加能力的类CapabilityHooks。 * 以及ICapabilityProvider提供能力的接口 对于能力和provider这里直接引用了boson的讲解内容: > 假设,你是一个投资商,想要投资建一栋商务楼。一般情况下,你就会去找一个建筑方案提供商。如果这个建筑方案提供商拿不出这个建筑方案,你可能就不建了,如果这个建筑方案提供商拿的出建筑方案,你就可以从这个方法中获取一些信息或者直接按照这个方案建筑商务楼。 我们先来看几个用于来创建Capability的类吧。只给出简单的介绍。 BaseCapability 是几个Capability的基类实现Capability的通用代码。 EntityCapability 提供了获得你提供能力的灵活方便的方法。之后我们介绍怎么获得。 我们对其中的几个创建Capbility的方法说一下功能,之后我们会用到。 \ EntityCapability\ create(ResourceLocation name, Class typeClass, Class contextClass) 创建新的实体功能,或者获取它(如果已存在)。第一个参数是你能力的name,第二参数是你的能力的接口API功能,第三个是附加的上下文。 EntityCapability\ createVoid(ResourceLocation name, Class typeClass) 使用Void创建Capability,表示不需要上下文功能。 EntityCapability\ createSided(ResourceLocation name, Class typeClass)创建具有可为 null 的方向上下文的新实体功能,或者如果已存在则获取它。 侧面通常是访问实体的侧面,如果未知或不是特定侧面,则为 null。使用可为 null 的方向上下文创建新的实体功能,或者如果已存在则获取它。 该端通常是访问实体的端,如果定未知或不是特端,则为 null。 这几个方法在其他的几个BlockCapability,ItemCapability中是一样的。就不介绍了。 BlockCapability 提供对位于世界中的 T 类型对象的灵活访问。查询block和blockentity能力 ItemCapability 提供对项目堆栈中类型 T 的对象的灵活访问。 T 类型就是你的能力的接口API。 下面我们来看NeoForge为我们提供了什么样子的能力,怎么使用上面提到的类创建能力的。 ```java // 这里我们就使用EnergyStorage这个介绍了,其他大同小异。 /** * Capability 类提供了 NeoForge 定义的能力,供 modder 直接引用。 * 它包含不同类型功能的内部类,例如EnergyStorage、FluidHandler和ItemHandler。 */ public final class Capabilities { /** * EnergyStorage内部类定义了与能量存储相关的功能。 */ public static final class EnergyStorage { /** * BLOCK 功能代表块的能量存储,支持侧面访问。 * 它是使用 BlockCapability.createSided() 方法创建的,并使用 IEnergyStorage 接口。 */ public static final BlockCapability BLOCK = BlockCapability.createSided(create("energy"), IEnergyStorage.class); /** * 实体能力代表实体的能量存储,支持侧面访问。 * 它是使用 EntityCapability.createSided() 方法创建的,并使用 IEnergyStorage 接口。 */ public static final EntityCapability ENTITY = EntityCapability.createSided(create("energy"), IEnergyStorage.class); /** * ITEM能力代表物品的能量存储,没有任何特定的侧面。 * 它是使用 ItemCapability.createVoid() 方法创建的,并使用 IEnergyStorage 接口。 */ public static final ItemCapability ITEM = ItemCapability.createVoid(create("energy"), IEnergyStorage.class); } // 流体 public static final class FluidHandler { } // 处理物品的输入输出 public static final class ItemHandler { } } ``` 这里我们看下我们所说的能力的API是什么,我们来看看IEnergyStorage接口 ```java // 其实可以看到接口就是定义了一组能量处理的行为,也就是能量的行为。 public interface IEnergyStorage { int receiveEnergy(int maxReceive, boolean simulate); int extractEnergy(int maxExtract, boolean simulate); int getEnergyStored(); int getMaxEnergyStored(); boolean canExtract(); boolean canReceive(); } ``` 下面我们来看怎么给这些物品添加能力的。主要的行为在CapabilityHooks这个类,我们就调两个介绍,其他自己看或者去文档中看。 ```java // 给当个方块添加能力,这里是给漏斗的方块实体添加物品输入输出的能力。 // 为漏斗(Hopper)注册自定义的物品处理器 event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, BlockEntityType.HOPPER, (hopper, side) -> { // 使用自定义的漏斗包装器,以遵守冷却时间 return new VanillaHopperItemHandler(hopper); }); // 给多个方块实体添加处理物品输入输出的能力 // 定义支持面向(sided)访问的原版容器列表 var sidedVanillaContainers = List.of( BlockEntityType.BLAST_FURNACE, BlockEntityType.BREWING_STAND, BlockEntityType.FURNACE, BlockEntityType.SMOKER); // 为每个支持面向访问的原版容器注册物品处理器 for (var type : sidedVanillaContainers) { event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, type, (sidedContainer, side) -> { // 如果没有指定面向,则使用普通的库存包装器(InvWrapper) // 否则,使用支持面向访问的库存包装器(SidedInvWrapper) return side == null ? new InvWrapper(sidedContainer) : new SidedInvWrapper(sidedContainer, side); }); } ``` 其中的InvWrapper和SidedInvWrapper是对ItemHandler能力接口的实现类。其中的这个 ```java (sidedContainer, side) -> { // 如果没有指定面向,则使用普通的库存包装器(InvWrapper) // 否则,使用支持面向访问的库存包装器(SidedInvWrapper) return side == null ? new InvWrapper(sidedContainer) : new SidedInvWrapper(sidedContainer, side); } ``` 就是capabilityProvider ```java // 这是registerBlockEntity方法的方法签名,可以看到第三个参数就是provider,其中是一个方法,所以你可以用lammbd方式返回。 void registerBlockEntity(BlockCapability capability, BlockEntityType blockEntityType, ICapabilityProvider provider) ``` 我想我们已经将Neoforge提供的内容大概解释清楚了,现在我们用这套系统,添加一个自己的能力,这个能力用在方块实体上,并且没有什么特殊的功能,仅仅获得方块实体的能力,调用能力的方法。 我们建立2个方块,2个方块放在一起时候,上面方块获得下面方块我们提供给他的能力,并执行能力提供的功能。 我们定义我们能力的行为的API接口。这是必要的,其他的模组要兼容你的能力,你提供接口是十分方便的。 ```java package net.flandre923.examplemod.capability; import net.minecraft.core.BlockPos; public interface ISimpleCapability { public void getString(BlockPos pos); } ``` 这是我们接口的实现类。功能也很简单,就是传入pos打印信息。 ```java package net.flandre923.examplemod.capability.impl; import com.mojang.logging.LogUtils; import net.flandre923.examplemod.capability.ISimpleCapability; import net.minecraft.core.BlockPos; import org.slf4j.Logger; public class SimpleCapability implements ISimpleCapability { private static final Logger LOGGER = LogUtils.getLogger(); private final String str; public SimpleCapability(final String str){ this.str = str; } @Override public void getString(BlockPos pos) { LOGGER.info(pos.toString() + ":::" + str); } } ``` 我们给方块实体创建这个能力。方块和方块实体的能力创建都是用的是这个BlockCapability类。参数含义,在之前的介绍时候已经讲解了。 ```java public static final BlockCapability SIMPLE_CAPABILITY_HANDLER = BlockCapability.createVoid( new ResourceLocation(ExampleMod.MODID,"simple_capability_handler"), ISimpleCapability.class ); ``` 给我们的方块实体添加这样的能力。第三个参数通过provider返回你的能力实现类。 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public static class ModEventBus{ @SubscribeEvent private static void registerCapabilities(RegisterCapabilitiesEvent event) { event.registerBlockEntity( ModCapabilities.SIMPLE_CAPABILITY_HANDLER, ModBlockEntities.DOWN_BLOCK_BLOCK_ENTITY.get(), (myBlockEntity, side) -> new SimpleCapability("hello") ); } } ``` 好了下面就是一些添加方块和方块实体的操作了。相信大家都会了,这里给出代码,不做讲解了。 Up方块 ```java public class UpBlock extends BaseEntityBlock { public UpBlock(){ super(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE)); } @Override protected MapCodec codec() { return null; } @Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new UpBlockBlockEntity(pPos,pState); } @Nullable @Override public BlockEntityTicker getTicker(Level pLevel, BlockState pState, BlockEntityType pBlockEntityType) { return pLevel.isClientSide ? null : createTickerHelper(pBlockEntityType, ModBlockEntities.UP_BLOCK_BLOCK_ENTITY.get(), UpBlockBlockEntity::serverTick); } } ``` Up方块实体,这里对tick方法讲解下, ```java public class UpBlockBlockEntity extends BlockEntity { public UpBlockBlockEntity( BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.UP_BLOCK_BLOCK_ENTITY.get(), pPos, pBlockState); } /** * 在服务端进行 UpBlockBlockEntity 的 tick 更新。 * * @param pLevel 方块实体所在的世界 * @param pPos 方块实体的位置 * @param pState 方块实体对应的方块状态 * @param pBlockEntity UpBlockBlockEntity 实例 */ public static void serverTick(Level pLevel, BlockPos pPos, BlockState pState, UpBlockBlockEntity pBlockEntity) { // 检查世界是否存在且是服务端世界 if (pLevel != null && !pLevel.isClientSide) { // 获取方块实体下方的位置 BlockPos pos = pPos.below(); // 获取下方位置的方块实体 BlockEntity blockEntity = pLevel.getBlockEntity(pos); // 检查下方方块实体是否为 DownBlockBlockEntity if (blockEntity instanceof DownBlockBlockEntity) { // 获取 SIMPLE_CAPABILITY_HANDLER 能力 ISimpleCapability simpleCapability = pLevel.getCapability(ModCapabilities.SIMPLE_CAPABILITY_HANDLER, pos, null); // 检查能力是否存在 if (simpleCapability != null) { // 调用 simpleCapability 的 getString 方法,传入当前方块实体的位置 simpleCapability.getString(pBlockEntity.getBlockPos()); } } } } } ``` Down方块 ```java public class DownBlock extends BaseEntityBlock { public DownBlock() { super(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE)); } @Override protected MapCodec codec() { return null; } @Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new DownBlockBlockEntity(pPos,pState); } } ``` Down方块实体 ```java public class DownBlockBlockEntity extends BlockEntity { public DownBlockBlockEntity(BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.DOWN_BLOCK_BLOCK_ENTITY.get(), pPos, pBlockState); } } ``` 方块实体注册 ```java public static final Supplier> DOWN_BLOCK_BLOCK_ENTITY = BLOCK_ENTITIES.register("down_block_entity", () -> BlockEntityType.Builder.of(DownBlockBlockEntity::new, ModBlocks.DOWN_BLOCK.get()).build(null)); public static final Supplier> UP_BLOCK_BLOCK_ENTITY = BLOCK_ENTITIES.register("up_block_entity", () -> BlockEntityType.Builder.of(UpBlockBlockEntity::new, ModBlocks.UP_BLOCK.get()).build(null)); ``` 方块注册 ```java public static final Supplier DOWN_BLOCK = registerBlock("down_block",DownBlock::new); public static final Supplier UP_BLOCK = registerBlock("up_block",UpBlock::new); ``` 好了进入游戏将up方块和down方块上下放在一起,看控制台就应该可以输出内容了. --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_32_BuildCapability.md description: 32 构建能力 相关教程 --- # 参考 https://boson.v2mcdev.com/capability/capabilityfromscratch.html https://docs.neoforged.net/docs/datastorage/capabilities ## 能力介绍,构建能力 我们先来介绍一下能系统,能力系统是NeoForge增加一套系统,不是原版就有的,能力系统用于处理一些给游戏物品,方块,实体等添加新的功能的内容,例如给一些机器添加处理流体,处理物品,处理能量这样的功能,我们可以说给该方块或者物品添加了这样的能力。 能力系统主要和这样的几个类有关, * 创建实体的能力的EntityCapability (net.neoforged.neoforge.capabilities)类, * 创建方块和方块实体能力的BlockCapability (net.neoforged.neoforge.capabilities)类 * 创建物品能力的ItemCapability (net.neoforged.neoforge.capabilities)类 * NeoForg提供的能力的实例的Capabilities类, * 给方块,物品,实体添加能力的类CapabilityHooks。 * 以及ICapabilityProvider提供能力的接口 对于能力和provider这里直接引用了boson的讲解内容: > 假设,你是一个投资商,想要投资建一栋商务楼。一般情况下,你就会去找一个建筑方案提供商。如果这个建筑方案提供商拿不出这个建筑方案,你可能就不建了,如果这个建筑方案提供商拿的出建筑方案,你就可以从这个方法中获取一些信息或者直接按照这个方案建筑商务楼。 我们先来看几个用于来创建Capability的类吧。只给出简单的介绍。 BaseCapability 是几个Capability的基类实现Capability的通用代码。 EntityCapability 提供了获得你提供能力的灵活方便的方法。之后我们介绍怎么获得。 我们对其中的几个创建Capbility的方法说一下功能,之后我们会用到。 \ EntityCapability\ create(ResourceLocation name, Class typeClass, Class contextClass) 创建新的实体功能,或者获取它(如果已存在)。第一个参数是你能力的name,第二参数是你的能力的接口API功能,第三个是附加的上下文。 EntityCapability\ createVoid(ResourceLocation name, Class typeClass) 使用Void创建Capability,表示不需要上下文功能。 EntityCapability\ createSided(ResourceLocation name, Class typeClass)创建具有可为 null 的方向上下文的新实体功能,或者如果已存在则获取它。 侧面通常是访问实体的侧面,如果未知或不是特定侧面,则为 null。使用可为 null 的方向上下文创建新的实体功能,或者如果已存在则获取它。 该端通常是访问实体的端,如果定未知或不是特端,则为 null。 这几个方法在其他的几个BlockCapability,ItemCapability中是一样的。就不介绍了。 BlockCapability 提供对位于世界中的 T 类型对象的灵活访问。查询block和blockentity能力 ItemCapability 提供对项目堆栈中类型 T 的对象的灵活访问。 T 类型就是你的能力的接口API。 下面我们来看NeoForge为我们提供了什么样子的能力,怎么使用上面提到的类创建能力的。 ```java // 这里我们就使用EnergyStorage这个介绍了,其他大同小异。 /** * Capability 类提供了 NeoForge 定义的能力,供 modder 直接引用。 * 它包含不同类型功能的内部类,例如EnergyStorage、FluidHandler和ItemHandler。 */ public final class Capabilities { /** * EnergyStorage内部类定义了与能量存储相关的功能。 */ public static final class EnergyStorage { /** * BLOCK 功能代表块的能量存储,支持侧面访问。 * 它是使用 BlockCapability.createSided() 方法创建的,并使用 IEnergyStorage 接口。 */ public static final BlockCapability BLOCK = BlockCapability.createSided(create("energy"), IEnergyStorage.class); /** * 实体能力代表实体的能量存储,支持侧面访问。 * 它是使用 EntityCapability.createSided() 方法创建的,并使用 IEnergyStorage 接口。 */ public static final EntityCapability ENTITY = EntityCapability.createSided(create("energy"), IEnergyStorage.class); /** * ITEM能力代表物品的能量存储,没有任何特定的侧面。 * 它是使用 ItemCapability.createVoid() 方法创建的,并使用 IEnergyStorage 接口。 */ public static final ItemCapability ITEM = ItemCapability.createVoid(create("energy"), IEnergyStorage.class); } // 流体 public static final class FluidHandler { } // 处理物品的输入输出 public static final class ItemHandler { } } ``` 这里我们看下我们所说的能力的API是什么,我们来看看IEnergyStorage接口 ```java // 其实可以看到接口就是定义了一组能量处理的行为,也就是能量的行为。 public interface IEnergyStorage { int receiveEnergy(int maxReceive, boolean simulate); int extractEnergy(int maxExtract, boolean simulate); int getEnergyStored(); int getMaxEnergyStored(); boolean canExtract(); boolean canReceive(); } ``` 下面我们来看怎么给这些物品添加能力的。主要的行为在CapabilityHooks这个类,我们就调两个介绍,其他自己看或者去文档中看。 ```java // 给当个方块添加能力,这里是给漏斗的方块实体添加物品输入输出的能力。 // 为漏斗(Hopper)注册自定义的物品处理器 event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, BlockEntityType.HOPPER, (hopper, side) -> { // 使用自定义的漏斗包装器,以遵守冷却时间 return new VanillaHopperItemHandler(hopper); }); // 给多个方块实体添加处理物品输入输出的能力 // 定义支持面向(sided)访问的原版容器列表 var sidedVanillaContainers = List.of( BlockEntityType.BLAST_FURNACE, BlockEntityType.BREWING_STAND, BlockEntityType.FURNACE, BlockEntityType.SMOKER); // 为每个支持面向访问的原版容器注册物品处理器 for (var type : sidedVanillaContainers) { event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, type, (sidedContainer, side) -> { // 如果没有指定面向,则使用普通的库存包装器(InvWrapper) // 否则,使用支持面向访问的库存包装器(SidedInvWrapper) return side == null ? new InvWrapper(sidedContainer) : new SidedInvWrapper(sidedContainer, side); }); } ``` 其中的InvWrapper和SidedInvWrapper是对ItemHandler能力接口的实现类。其中的这个 ```java (sidedContainer, side) -> { // 如果没有指定面向,则使用普通的库存包装器(InvWrapper) // 否则,使用支持面向访问的库存包装器(SidedInvWrapper) return side == null ? new InvWrapper(sidedContainer) : new SidedInvWrapper(sidedContainer, side); } ``` 就是capabilityProvider ```java // 这是registerBlockEntity方法的方法签名,可以看到第三个参数就是provider,其中是一个方法,所以你可以用lammbd方式返回。 void registerBlockEntity(BlockCapability capability, BlockEntityType blockEntityType, ICapabilityProvider provider) ``` 我想我们已经将Neoforge提供的内容大概解释清楚了,现在我们用这套系统,添加一个自己的能力,这个能力用在方块实体上,并且没有什么特殊的功能,仅仅获得方块实体的能力,调用能力的方法。 我们建立2个方块,2个方块放在一起时候,上面方块获得下面方块我们提供给他的能力,并执行能力提供的功能。 我们定义我们能力的行为的API接口。这是必要的,其他的模组要兼容你的能力,你提供接口是十分方便的。 ```java package net.flandre923.examplemod.capability; import net.minecraft.core.BlockPos; public interface ISimpleCapability { public void getString(BlockPos pos); } ``` 这是我们接口的实现类。功能也很简单,就是传入pos打印信息。 ```java package net.flandre923.examplemod.capability.impl; import com.mojang.logging.LogUtils; import net.flandre923.examplemod.capability.ISimpleCapability; import net.minecraft.core.BlockPos; import org.slf4j.Logger; public class SimpleCapability implements ISimpleCapability { private static final Logger LOGGER = LogUtils.getLogger(); private final String str; public SimpleCapability(final String str){ this.str = str; } @Override public void getString(BlockPos pos) { LOGGER.info(pos.toString() + ":::" + str); } } ``` 我们给方块实体创建这个能力。方块和方块实体的能力创建都是用的是这个BlockCapability类。参数含义,在之前的介绍时候已经讲解了。 ```java public static final BlockCapability SIMPLE_CAPABILITY_HANDLER = BlockCapability.createVoid( new ResourceLocation(ExampleMod.MODID,"simple_capability_handler"), ISimpleCapability.class ); ``` 给我们的方块实体添加这样的能力。第三个参数通过provider返回你的能力实现类。 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public static class ModEventBus{ @SubscribeEvent private static void registerCapabilities(RegisterCapabilitiesEvent event) { event.registerBlockEntity( ModCapabilities.SIMPLE_CAPABILITY_HANDLER, ModBlockEntities.DOWN_BLOCK_BLOCK_ENTITY.get(), (myBlockEntity, side) -> new SimpleCapability("hello") ); } } ``` 好了下面就是一些添加方块和方块实体的操作了。相信大家都会了,这里给出代码,不做讲解了。 Up方块 ```java public class UpBlock extends BaseEntityBlock { public UpBlock(){ super(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE)); } @Override protected MapCodec codec() { return null; } @Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new UpBlockBlockEntity(pPos,pState); } @Nullable @Override public BlockEntityTicker getTicker(Level pLevel, BlockState pState, BlockEntityType pBlockEntityType) { return pLevel.isClientSide ? null : createTickerHelper(pBlockEntityType, ModBlockEntities.UP_BLOCK_BLOCK_ENTITY.get(), UpBlockBlockEntity::serverTick); } } ``` Up方块实体,这里对tick方法讲解下, ```java public class UpBlockBlockEntity extends BlockEntity { public UpBlockBlockEntity( BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.UP_BLOCK_BLOCK_ENTITY.get(), pPos, pBlockState); } /** * 在服务端进行 UpBlockBlockEntity 的 tick 更新。 * * @param pLevel 方块实体所在的世界 * @param pPos 方块实体的位置 * @param pState 方块实体对应的方块状态 * @param pBlockEntity UpBlockBlockEntity 实例 */ public static void serverTick(Level pLevel, BlockPos pPos, BlockState pState, UpBlockBlockEntity pBlockEntity) { // 检查世界是否存在且是服务端世界 if (pLevel != null && !pLevel.isClientSide) { // 获取方块实体下方的位置 BlockPos pos = pPos.below(); // 获取下方位置的方块实体 BlockEntity blockEntity = pLevel.getBlockEntity(pos); // 检查下方方块实体是否为 DownBlockBlockEntity if (blockEntity instanceof DownBlockBlockEntity) { // 获取 SIMPLE_CAPABILITY_HANDLER 能力 ISimpleCapability simpleCapability = pLevel.getCapability(ModCapabilities.SIMPLE_CAPABILITY_HANDLER, pos, null); // 检查能力是否存在 if (simpleCapability != null) { // 调用 simpleCapability 的 getString 方法,传入当前方块实体的位置 simpleCapability.getString(pBlockEntity.getBlockPos()); } } } } } ``` Down方块 ```java public class DownBlock extends BaseEntityBlock { public DownBlock() { super(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE)); } @Override protected MapCodec codec() { return null; } @Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new DownBlockBlockEntity(pPos,pState); } } ``` Down方块实体 ```java public class DownBlockBlockEntity extends BlockEntity { public DownBlockBlockEntity(BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.DOWN_BLOCK_BLOCK_ENTITY.get(), pPos, pBlockState); } } ``` 方块实体注册 ```java public static final Supplier> DOWN_BLOCK_BLOCK_ENTITY = BLOCK_ENTITIES.register("down_block_entity", () -> BlockEntityType.Builder.of(DownBlockBlockEntity::new, ModBlocks.DOWN_BLOCK.get()).build(null)); public static final Supplier> UP_BLOCK_BLOCK_ENTITY = BLOCK_ENTITIES.register("up_block_entity", () -> BlockEntityType.Builder.of(UpBlockBlockEntity::new, ModBlocks.UP_BLOCK.get()).build(null)); ``` 方块注册 ```java public static final Supplier DOWN_BLOCK = registerBlock("down_block",DownBlock::new); public static final Supplier UP_BLOCK = registerBlock("up_block",UpBlock::new); ``` 好了进入游戏将up方块和down方块上下放在一起,看控制台就应该可以输出内容了. --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_33_ItemHander.md description: 33 原版提供的能力ItemHander物品处理能力 相关教程 --- # 参考 https://boson.v2mcdev.com/capability/simpleusage.html https://docs.neoforged.net/docs/datastorage/capabilities ## 开始构建能力 这次我们来使用原版给我们提供的ItemHandler处理物品的输入和输出。关于介绍看之前的教程,然后我们这次实现一个垃圾桶,从上方输入,然后只能输入石头,然后删除石头。 常规的方块注册。 ```java public class TrashBlock extends BaseEntityBlock { public TrashBlock() { super(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE)); } @Override protected MapCodec codec() { return null; } @Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new TrashBlockEntity(pPos,pState); } @Override public RenderShape getRenderShape(BlockState pState) { return RenderShape.MODEL; } } ``` 方块实体注册 ```java public class TrashBlockEntity extends BlockEntity { public TrashBlockEntity(BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.TRASH_BLOCK_ENTITY.get(), pPos, pBlockState); } } ``` ```java public static final Supplier TRASH_BLOCK = registerBlock("trash_block",TrashBlock::new); ``` ```java public static final Supplier> TRASH_BLOCK_ENTITY = BLOCK_ENTITIES.register("trash_block_entity", () -> BlockEntityType.Builder.of(TrashBlockEntity::new, ModBlocks.TRASH_BLOCK.get()).build(null)); ``` 给我们的实体注册的对于的物品处理的能力。我们让provider返回物品处理的API接口,我们自己实现这个API接口。 ```java /** * ModEventBus 类是一个静态内部类,用于处理 Mod 事件总线上的事件。 * 它使用 @Mod.EventBusSubscriber 注解来订阅 Mod 事件总线上的事件。 */ @Mod.EventBusSubscriber(modid = ExampleMod.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) public static class ModEventBus { /** * registerCapabilities 方法用于注册方块实体的能力。 * 它使用 @SubscribeEvent 注解来订阅 RegisterCapabilitiesEvent 事件。 * * @param event RegisterCapabilitiesEvent 事件对象 */ @SubscribeEvent private static void registerCapabilities(RegisterCapabilitiesEvent event) { // 注册方块实体的物品处理器能力 event.registerBlockEntity( Capabilities.ItemHandler.BLOCK, ModBlockEntities.TRASH_BLOCK_ENTITY.get(), (myBlockEntity, side) -> { // 如果方块实体的面为上面,则返回一个自定义的物品处理器 if (side == Direction.UP) { return new IItemHandler() { /** * 获取物品处理器的槽位数量。 * * @return 槽位数量,固定为 1 */ @Override public int getSlots() { return 1; } /** * 获取指定槽位的物品堆叠。 * * @param slot 槽位索引 * @return 空的物品堆叠 */ @Override public @NotNull ItemStack getStackInSlot(int slot) { return ItemStack.EMPTY; } /** * 将物品插入指定槽位。 * * @param slot 槽位索引 * @param stack 要插入的物品堆叠 * @param simulate 是否为模拟插入 * @return 插入后剩余的物品堆叠 */ @Override public @NotNull ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate) { // 如果物品不合法,则直接返回原物品堆叠 if (!this.isItemValid(slot, stack)) return stack; // 如果是模拟插入,则返回空的物品堆叠 if (simulate) { return ItemStack.EMPTY; } else { // 复制物品堆叠并减少数量,返回剩余的物品堆叠 stack = stack.copy(); stack.shrink(1); return stack; } } /** * 从指定槽位提取物品。 * * @param slot 槽位索引 * @param amount 要提取的数量 * @param simulate 是否为模拟提取 * @return 空的物品堆叠 */ @Override public @NotNull ItemStack extractItem(int slot, int amount, boolean simulate) { return ItemStack.EMPTY; } /** * 获取指定槽位的物品数量限制。 * * @param slot 槽位索引 * @return 槽位索引值 */ @Override public int getSlotLimit(int slot) { return slot; } /** * 判断指定槽位是否可以接受给定的物品堆叠。 * * @param slot 槽位索引 * @param stack 物品堆叠 * @return 如果物品为圆石,则返回 true,否则返回 false */ @Override public boolean isItemValid(int slot, @NotNull ItemStack stack) { return stack.getItem() == Items.COBBLESTONE; } }; } else { // 如果方块实体的面不是上面,则返回 null return null; } } ); } } ``` --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_33_ItemHander.md description: 33 原版提供的能力ItemHander物品处理能力 相关教程 --- # 参考 https://boson.v2mcdev.com/capability/simpleusage.html https://docs.neoforged.net/docs/datastorage/capabilities ## 开始构建能力 这次我们来使用原版给我们提供的ItemHandler处理物品的输入和输出。关于介绍看之前的教程,然后我们这次实现一个垃圾桶,从上方输入,然后只能输入石头,然后删除石头。 常规的方块注册。 ```java public class TrashBlock extends BaseEntityBlock { public TrashBlock() { super(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE)); } @Override protected MapCodec codec() { return null; } @Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new TrashBlockEntity(pPos,pState); } @Override public RenderShape getRenderShape(BlockState pState) { return RenderShape.MODEL; } } ``` 方块实体注册 ```java public class TrashBlockEntity extends BlockEntity { public TrashBlockEntity(BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.TRASH_BLOCK_ENTITY.get(), pPos, pBlockState); } } ``` ```java public static final Supplier TRASH_BLOCK = registerBlock("trash_block",TrashBlock::new); ``` ```java public static final Supplier> TRASH_BLOCK_ENTITY = BLOCK_ENTITIES.register("trash_block_entity", () -> BlockEntityType.Builder.of(TrashBlockEntity::new, ModBlocks.TRASH_BLOCK.get()).build(null)); ``` 给我们的实体注册的对于的物品处理的能力。我们让provider返回物品处理的API接口,我们自己实现这个API接口。 ```java /** * ModEventBus 类是一个静态内部类,用于处理 Mod 事件总线上的事件。 * 它使用 @Mod.EventBusSubscriber 注解来订阅 Mod 事件总线上的事件。 */ @Mod.EventBusSubscriber(modid = ExampleMod.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) public static class ModEventBus { /** * registerCapabilities 方法用于注册方块实体的能力。 * 它使用 @SubscribeEvent 注解来订阅 RegisterCapabilitiesEvent 事件。 * * @param event RegisterCapabilitiesEvent 事件对象 */ @SubscribeEvent private static void registerCapabilities(RegisterCapabilitiesEvent event) { // 注册方块实体的物品处理器能力 event.registerBlockEntity( Capabilities.ItemHandler.BLOCK, ModBlockEntities.TRASH_BLOCK_ENTITY.get(), (myBlockEntity, side) -> { // 如果方块实体的面为上面,则返回一个自定义的物品处理器 if (side == Direction.UP) { return new IItemHandler() { /** * 获取物品处理器的槽位数量。 * * @return 槽位数量,固定为 1 */ @Override public int getSlots() { return 1; } /** * 获取指定槽位的物品堆叠。 * * @param slot 槽位索引 * @return 空的物品堆叠 */ @Override public @NotNull ItemStack getStackInSlot(int slot) { return ItemStack.EMPTY; } /** * 将物品插入指定槽位。 * * @param slot 槽位索引 * @param stack 要插入的物品堆叠 * @param simulate 是否为模拟插入 * @return 插入后剩余的物品堆叠 */ @Override public @NotNull ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate) { // 如果物品不合法,则直接返回原物品堆叠 if (!this.isItemValid(slot, stack)) return stack; // 如果是模拟插入,则返回空的物品堆叠 if (simulate) { return ItemStack.EMPTY; } else { // 复制物品堆叠并减少数量,返回剩余的物品堆叠 stack = stack.copy(); stack.shrink(1); return stack; } } /** * 从指定槽位提取物品。 * * @param slot 槽位索引 * @param amount 要提取的数量 * @param simulate 是否为模拟提取 * @return 空的物品堆叠 */ @Override public @NotNull ItemStack extractItem(int slot, int amount, boolean simulate) { return ItemStack.EMPTY; } /** * 获取指定槽位的物品数量限制。 * * @param slot 槽位索引 * @return 槽位索引值 */ @Override public int getSlotLimit(int slot) { return slot; } /** * 判断指定槽位是否可以接受给定的物品堆叠。 * * @param slot 槽位索引 * @param stack 物品堆叠 * @return 如果物品为圆石,则返回 true,否则返回 false */ @Override public boolean isItemValid(int slot, @NotNull ItemStack stack) { return stack.getItem() == Items.COBBLESTONE; } }; } else { // 如果方块实体的面不是上面,则返回 null return null; } } ); } } ``` --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_34_Capability_Extra.md description: 34 能力提供附加 相关教程 --- # 参考 https://boson.v2mcdev.com/capability/attachcapabilityprovider.html ## 能力附加提供器 我们在之前已经讲解了关于Capability的知识,我们也使用了provider不过之前的使用,我们并没有直接处理provider,我们是通过一个lammbd来做的。现在我们要使用这个provider了。 这次的例子是给我们的玩家添加一个新的速度的能力,不过这个能力目前并不以什么实际效果哦,知识为了展示怎么样使用provider,以及怎么给实体添加能力,我们添加一个物品,将玩家的速度的能力等级显示出来。 首先我们来看能力的接口,没什么特别的行为,只有一个方法,要求返回对应的level,这个接口继承了INBTSerializable,代表这个能力需要实现序列化和反序列化的方法。 ```java public interface ISpeedUpCapability extends INBTSerializable { int getLevel(); } ``` 这是能力的实现类,你应该注意到了我们实现了序列化接口用于将能力的level存储起来,不过这里并不能存起来,我们要在provider中调用这里的方法才行。 ```java /** SpeedUpCapability类实现了ISpeedUpCapability接口,用于表示加速能力。 该类包含一个整数类型的级别(level)属性,表示加速能力的级别。 */ public class SpeedUpCapability implements ISpeedUpCapability { private int level; /** 构造函数,用于创建SpeedUpCapability对象。 @param level 加速能力的级别 */ public SpeedUpCapability(int level) { this.level = level; } /** 获取加速能力的级别。 @return 加速能力的级别 */ @Override public int getLevel() { return level; } /** 将加速能力序列化为NBT(Named Binary Tag)格式的数据。 @return 包含加速能力级别的CompoundTag对象 */ @Override public CompoundTag serializeNBT() { CompoundTag compoundNBT = new CompoundTag(); compoundNBT.putInt("level", this.level); return compoundNBT; } /** 从NBT格式的数据中反序列化加速能力。 @param nbt 包含加速能力级别的CompoundTag对象 */ @Override public void deserializeNBT(CompoundTag nbt) { this.level = nbt.getInt("level"); } } ``` 这里是创建我们的能力,和我们之前讲解的并没有什么区别。 ```java public class ModCapabilities { public static final EntityCapability SPEED_CAPABILITY_HANDLER = EntityCapability.createVoid(new ResourceLocation(ExampleMod.MODID,"speed_capability_handler"), ISpeedUpCapability.class); } ``` 这里是我们的provider实现类。我们还需要实现INBTSerializable接口,真正数据存储在这里。 ```java public class SpeedUpCapabilityProvider implements ICapabilityProvider, INBTSerializable { /** SpeedUpCapabilityProvider类实现了ICapabilityProvider和INBTSerializable接口, 用于提供和管理玩家的加速能力。 */ private ISpeedUpCapability speedUpCapability; /** 获取玩家的加速能力。 @param entity 玩家实体 @param context 上下文(未使用) @return 玩家的加速能力,如果不存在则创建一个新的加速能力 */ @Nullable @Override public ISpeedUpCapability getCapability(Player Entity, Void context) { return this.getOrCreateCapability(); } /** 获取或创建玩家的加速能力。 @return 玩家的加速能力 */ @Nonnull ISpeedUpCapability getOrCreateCapability() { if (speedUpCapability == null) { Random random = new Random(); this.speedUpCapability = new SpeedUpCapability(random.nextInt(99) + 1); } return this.speedUpCapability; } /** 将玩家的加速能力序列化为NBT格式的数据。 @return 包含玩家加速能力的CompoundTag对象 */ @Override public CompoundTag serializeNBT() { return this.getOrCreateCapability().serializeNBT(); } /** 从NBT格式的数据中反序列化玩家的加速能力。 @param nbt 包含玩家加速能力的CompoundTag对象 */ @Override public void deserializeNBT(CompoundTag nbt) { this.getOrCreateCapability().deserializeNBT(nbt); } } ``` 好了我们给玩家注册我们的能力了。和之前类似,我们给玩家提供了Speed能力,provider就是我们的那个SpeedUpCapabilityProvider ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public static class ModEventBus{ @SubscribeEvent private static void registerCapabilities(RegisterCapabilitiesEvent event) { event.registerEntity(ModCapabilities.SPEED_CAPABILITY_HANDLER, EntityType.PLAYER, new SpeedUpCapabilityProvider()); } } ``` 下面我们来看怎么从实体上获得能力,以及使用 ```java public class SpeedUpShowItem extends Item { public SpeedUpShowItem() { super(new Properties()); } // 很简单,从玩家身上获得能力,调用能力的方法,然后给玩家发送消息。 @Override public InteractionResultHolder use(Level pLevel, Player pPlayer, InteractionHand pUsedHand) { if (!pLevel.isClientSide && pUsedHand == InteractionHand.MAIN_HAND) { ISpeedUpCapability capability = pPlayer.getCapability(ModCapabilities.SPEED_CAPABILITY_HANDLER); if(capability!=null){ int level = capability.getLevel(); pPlayer.sendSystemMessage(Component.literal("speed level:" + level)); } } return super.use(pLevel, pPlayer, pUsedHand); } } ``` 好了可以进入游戏拿到物品测试以下了。你也可以退出游戏从进入看看是否正常的level --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_34_Capability_Extra.md description: 34 能力提供附加 相关教程 --- # 参考 https://boson.v2mcdev.com/capability/attachcapabilityprovider.html ## 能力附加提供器 我们在之前已经讲解了关于Capability的知识,我们也使用了provider不过之前的使用,我们并没有直接处理provider,我们是通过一个lammbd来做的。现在我们要使用这个provider了。 这次的例子是给我们的玩家添加一个新的速度的能力,不过这个能力目前并不以什么实际效果哦,知识为了展示怎么样使用provider,以及怎么给实体添加能力,我们添加一个物品,将玩家的速度的能力等级显示出来。 首先我们来看能力的接口,没什么特别的行为,只有一个方法,要求返回对应的level,这个接口继承了INBTSerializable,代表这个能力需要实现序列化和反序列化的方法。 ```java public interface ISpeedUpCapability extends INBTSerializable { int getLevel(); } ``` 这是能力的实现类,你应该注意到了我们实现了序列化接口用于将能力的level存储起来,不过这里并不能存起来,我们要在provider中调用这里的方法才行。 ```java /** SpeedUpCapability类实现了ISpeedUpCapability接口,用于表示加速能力。 该类包含一个整数类型的级别(level)属性,表示加速能力的级别。 */ public class SpeedUpCapability implements ISpeedUpCapability { private int level; /** 构造函数,用于创建SpeedUpCapability对象。 @param level 加速能力的级别 */ public SpeedUpCapability(int level) { this.level = level; } /** 获取加速能力的级别。 @return 加速能力的级别 */ @Override public int getLevel() { return level; } /** 将加速能力序列化为NBT(Named Binary Tag)格式的数据。 @return 包含加速能力级别的CompoundTag对象 */ @Override public CompoundTag serializeNBT() { CompoundTag compoundNBT = new CompoundTag(); compoundNBT.putInt("level", this.level); return compoundNBT; } /** 从NBT格式的数据中反序列化加速能力。 @param nbt 包含加速能力级别的CompoundTag对象 */ @Override public void deserializeNBT(CompoundTag nbt) { this.level = nbt.getInt("level"); } } ``` 这里是创建我们的能力,和我们之前讲解的并没有什么区别。 ```java public class ModCapabilities { public static final EntityCapability SPEED_CAPABILITY_HANDLER = EntityCapability.createVoid(new ResourceLocation(ExampleMod.MODID,"speed_capability_handler"), ISpeedUpCapability.class); } ``` 这里是我们的provider实现类。我们还需要实现INBTSerializable接口,真正数据存储在这里。 ```java public class SpeedUpCapabilityProvider implements ICapabilityProvider, INBTSerializable { /** SpeedUpCapabilityProvider类实现了ICapabilityProvider和INBTSerializable接口, 用于提供和管理玩家的加速能力。 */ private ISpeedUpCapability speedUpCapability; /** 获取玩家的加速能力。 @param entity 玩家实体 @param context 上下文(未使用) @return 玩家的加速能力,如果不存在则创建一个新的加速能力 */ @Nullable @Override public ISpeedUpCapability getCapability(Player Entity, Void context) { return this.getOrCreateCapability(); } /** 获取或创建玩家的加速能力。 @return 玩家的加速能力 */ @Nonnull ISpeedUpCapability getOrCreateCapability() { if (speedUpCapability == null) { Random random = new Random(); this.speedUpCapability = new SpeedUpCapability(random.nextInt(99) + 1); } return this.speedUpCapability; } /** 将玩家的加速能力序列化为NBT格式的数据。 @return 包含玩家加速能力的CompoundTag对象 */ @Override public CompoundTag serializeNBT() { return this.getOrCreateCapability().serializeNBT(); } /** 从NBT格式的数据中反序列化玩家的加速能力。 @param nbt 包含玩家加速能力的CompoundTag对象 */ @Override public void deserializeNBT(CompoundTag nbt) { this.getOrCreateCapability().deserializeNBT(nbt); } } ``` 好了我们给玩家注册我们的能力了。和之前类似,我们给玩家提供了Speed能力,provider就是我们的那个SpeedUpCapabilityProvider ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public static class ModEventBus{ @SubscribeEvent private static void registerCapabilities(RegisterCapabilitiesEvent event) { event.registerEntity(ModCapabilities.SPEED_CAPABILITY_HANDLER, EntityType.PLAYER, new SpeedUpCapabilityProvider()); } } ``` 下面我们来看怎么从实体上获得能力,以及使用 ```java public class SpeedUpShowItem extends Item { public SpeedUpShowItem() { super(new Properties()); } // 很简单,从玩家身上获得能力,调用能力的方法,然后给玩家发送消息。 @Override public InteractionResultHolder use(Level pLevel, Player pPlayer, InteractionHand pUsedHand) { if (!pLevel.isClientSide && pUsedHand == InteractionHand.MAIN_HAND) { ISpeedUpCapability capability = pPlayer.getCapability(ModCapabilities.SPEED_CAPABILITY_HANDLER); if(capability!=null){ int level = capability.getLevel(); pPlayer.sendSystemMessage(Component.literal("speed level:" + level)); } } return super.use(pLevel, pPlayer, pUsedHand); } } ``` 好了可以进入游戏拿到物品测试以下了。你也可以退出游戏从进入看看是否正常的level --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_35_LevelSaveData.md description: 35 LevelSaveData 相关教程 --- # 参考 https://boson.v2mcdev.com/worldsaveddata/example.html https://docs.neoforged.net/docs/datastorage/saveddata ## SaveData 阅读前,请先自己大概阅读下`https://docs.neoforged.net/docs/datastorage/saveddata` 内容,大概了解下。 好了,我们正式开始,SaveData类是我的世界提供的一种维度级别的数据保存的方式,这种存储数据可以共享,并且可以跨纬度保存和获取到的,并且数据不存在存档文件夹中,而是在世界文件夹下面。 ``` .//DIM-1/data/example.dat ``` 这次我们介绍怎么使用这个类,我们创建一个存储物品和取出物品的方块,这个方块是谁的都可以存取和取出的(共享)。 ```java /** * ModLevelSaveData类用于管理和持久化游戏中的物品堆栈数据。 * 它继承自SavedData类,用于将数据保存到硬盘并在需要时加载。 */ public class ModLevelSaveData extends SavedData { /** * 保存数据的名称常量。 * 注意这个NAME应该保持是唯一的,不要重复。 */ public static final String NAME = "mod_level_save_data"; /** * 用于存储物品堆栈的栈结构。 */ private final Stack itemStacks = new Stack<>(); /** * 创建一个新的ModLevelSaveData实例。 * * @return 新创建的ModLevelSaveData实例。 */ public static ModLevelSaveData create() { return new ModLevelSaveData(); } /** * 将物品堆栈放入栈中。 * * @param item 要放入栈中的物品堆栈。 */ public void putItem(ItemStack item) { itemStacks.push(item); setDirty(); } /** * 从栈中获取物品堆栈。 * * @return 如果栈为空,则返回空气物品堆栈;否则返回栈顶的物品堆栈。 */ public ItemStack getItem() { if (itemStacks.isEmpty()) { return new ItemStack(Items.AIR); } setDirty(); return itemStacks.pop(); } /** * 将ModLevelSaveData的数据保存到CompoundTag中。 * * @param pCompoundTag 要保存数据的CompoundTag。 * @return 保存了数据的CompoundTag。 */ @Override public CompoundTag save(CompoundTag pCompoundTag) { ListTag listTag = new ListTag(); itemStacks.forEach((stack) -> { CompoundTag compoundTag = new CompoundTag(); compoundTag.put("itemstack", stack.getTag()); listTag.add(compoundTag); }); pCompoundTag.put("list", listTag); return pCompoundTag; } /** * 从CompoundTag中加载ModLevelSaveData的数据。 * * @param nbt 包含数据的CompoundTag。 * @return 加载了数据的ModLevelSaveData实例。 */ public ModLevelSaveData load(CompoundTag nbt) { ModLevelSaveData data = this.create(); ListTag listNBT = (ListTag) nbt.get("list"); if (listNBT != null) { for (Tag value : listNBT) { CompoundTag tag = (CompoundTag) value; ItemStack itemStack = ItemStack.of(tag.getCompound("itemstack")); itemStacks.push(itemStack); } } return data; } /** * 从CompoundTag中解码ModLevelSaveData的数据。 * * @param tag 包含数据的CompoundTag。 * @return 解码后的ModLevelSaveData实例。 */ public static ModLevelSaveData decode(CompoundTag tag){ ModLevelSaveData modLevelSaveData = ModLevelSaveData.create(); modLevelSaveData.load(tag); return modLevelSaveData; } /** * 获取指定世界的ModLevelSaveData实例。通过这个方法获得对应的data * * @param worldIn 要获取数据的世界。 * @return 与指定世界关联的ModLevelSaveData实例。 * @throws RuntimeException 如果尝试从客户端世界获取数据,则抛出运行时异常。 * **/ public static ModLevelSaveData get(Level worldIn) { if (!(worldIn instanceof ServerLevel)) { throw new RuntimeException("Attempted to get the data from a client world. This is wrong."); } ServerLevel world = worldIn.getServer().getLevel(ServerLevel.OVERWORLD); DimensionDataStorage dataStorage = world.getDataStorage(); return dataStorage.computeIfAbsent(new Factory(ModLevelSaveData::create, ModLevelSaveData::decode), ModLevelSaveData.NAME); } } ``` 对应的方块的内容。 ```java /** * DataSaveBlock是一个自定义的方块类,用于与玩家进行交互并保存和读取物品数据。 * 它继承自Block类,具有与石头方块相同的属性。 */ public class DataSaveBlock extends Block { public DataSaveBlock() { super(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE)); } /** * 当玩家使用该方块时触发的交互逻辑。这里如果是空手就从DATA中获得物品,如果手持物品就放入到DATA中去。 * * @param pState 方块的状态。 * @param pLevel 方块所在的世界。 * @param pPos 方块的位置。 * @param pPlayer 与方块交互的玩家。 * @param pHand 玩家使用的手(主手或副手)。 * @param pHit 玩家点击方块的碰撞结果。 * @return 交互的结果,表示交互是否成功。 */ @Override public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) { if(!pLevel.isClientSide){ ModLevelSaveData data = ModLevelSaveData.get(pLevel); ItemStack mainHandItem = pPlayer.getMainHandItem(); if(mainHandItem.isEmpty()){ ItemStack itemStack = data.getItem(); pPlayer.setItemInHand(InteractionHand.MAIN_HAND,itemStack); }else{ ItemStack itemStack =mainHandItem.copy(); mainHandItem.shrink(mainHandItem.getCount()); data.putItem(itemStack); } } return InteractionResult.SUCCESS; } } ``` --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_35_LevelSaveData.md description: 35 LevelSaveData 相关教程 --- # 参考 https://boson.v2mcdev.com/worldsaveddata/example.html https://docs.neoforged.net/docs/datastorage/saveddata ## SaveData 阅读前,请先自己大概阅读下`https://docs.neoforged.net/docs/datastorage/saveddata` 内容,大概了解下。 好了,我们正式开始,SaveData类是我的世界提供的一种维度级别的数据保存的方式,这种存储数据可以共享,并且可以跨纬度保存和获取到的,并且数据不存在存档文件夹中,而是在世界文件夹下面。 ``` .//DIM-1/data/example.dat ``` 这次我们介绍怎么使用这个类,我们创建一个存储物品和取出物品的方块,这个方块是谁的都可以存取和取出的(共享)。 ```java /** * ModLevelSaveData类用于管理和持久化游戏中的物品堆栈数据。 * 它继承自SavedData类,用于将数据保存到硬盘并在需要时加载。 */ public class ModLevelSaveData extends SavedData { /** * 保存数据的名称常量。 * 注意这个NAME应该保持是唯一的,不要重复。 */ public static final String NAME = "mod_level_save_data"; /** * 用于存储物品堆栈的栈结构。 */ private final Stack itemStacks = new Stack<>(); /** * 创建一个新的ModLevelSaveData实例。 * * @return 新创建的ModLevelSaveData实例。 */ public static ModLevelSaveData create() { return new ModLevelSaveData(); } /** * 将物品堆栈放入栈中。 * * @param item 要放入栈中的物品堆栈。 */ public void putItem(ItemStack item) { itemStacks.push(item); setDirty(); } /** * 从栈中获取物品堆栈。 * * @return 如果栈为空,则返回空气物品堆栈;否则返回栈顶的物品堆栈。 */ public ItemStack getItem() { if (itemStacks.isEmpty()) { return new ItemStack(Items.AIR); } setDirty(); return itemStacks.pop(); } /** * 将ModLevelSaveData的数据保存到CompoundTag中。 * * @param pCompoundTag 要保存数据的CompoundTag。 * @return 保存了数据的CompoundTag。 */ @Override public CompoundTag save(CompoundTag pCompoundTag) { ListTag listTag = new ListTag(); itemStacks.forEach((stack) -> { CompoundTag compoundTag = new CompoundTag(); compoundTag.put("itemstack", stack.getTag()); listTag.add(compoundTag); }); pCompoundTag.put("list", listTag); return pCompoundTag; } /** * 从CompoundTag中加载ModLevelSaveData的数据。 * * @param nbt 包含数据的CompoundTag。 * @return 加载了数据的ModLevelSaveData实例。 */ public ModLevelSaveData load(CompoundTag nbt) { ModLevelSaveData data = this.create(); ListTag listNBT = (ListTag) nbt.get("list"); if (listNBT != null) { for (Tag value : listNBT) { CompoundTag tag = (CompoundTag) value; ItemStack itemStack = ItemStack.of(tag.getCompound("itemstack")); itemStacks.push(itemStack); } } return data; } /** * 从CompoundTag中解码ModLevelSaveData的数据。 * * @param tag 包含数据的CompoundTag。 * @return 解码后的ModLevelSaveData实例。 */ public static ModLevelSaveData decode(CompoundTag tag){ ModLevelSaveData modLevelSaveData = ModLevelSaveData.create(); modLevelSaveData.load(tag); return modLevelSaveData; } /** * 获取指定世界的ModLevelSaveData实例。通过这个方法获得对应的data * * @param worldIn 要获取数据的世界。 * @return 与指定世界关联的ModLevelSaveData实例。 * @throws RuntimeException 如果尝试从客户端世界获取数据,则抛出运行时异常。 * **/ public static ModLevelSaveData get(Level worldIn) { if (!(worldIn instanceof ServerLevel)) { throw new RuntimeException("Attempted to get the data from a client world. This is wrong."); } ServerLevel world = worldIn.getServer().getLevel(ServerLevel.OVERWORLD); DimensionDataStorage dataStorage = world.getDataStorage(); return dataStorage.computeIfAbsent(new Factory(ModLevelSaveData::create, ModLevelSaveData::decode), ModLevelSaveData.NAME); } } ``` 对应的方块的内容。 ```java /** * DataSaveBlock是一个自定义的方块类,用于与玩家进行交互并保存和读取物品数据。 * 它继承自Block类,具有与石头方块相同的属性。 */ public class DataSaveBlock extends Block { public DataSaveBlock() { super(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE)); } /** * 当玩家使用该方块时触发的交互逻辑。这里如果是空手就从DATA中获得物品,如果手持物品就放入到DATA中去。 * * @param pState 方块的状态。 * @param pLevel 方块所在的世界。 * @param pPos 方块的位置。 * @param pPlayer 与方块交互的玩家。 * @param pHand 玩家使用的手(主手或副手)。 * @param pHit 玩家点击方块的碰撞结果。 * @return 交互的结果,表示交互是否成功。 */ @Override public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) { if(!pLevel.isClientSide){ ModLevelSaveData data = ModLevelSaveData.get(pLevel); ItemStack mainHandItem = pPlayer.getMainHandItem(); if(mainHandItem.isEmpty()){ ItemStack itemStack = data.getItem(); pPlayer.setItemInHand(InteractionHand.MAIN_HAND,itemStack); }else{ ItemStack itemStack =mainHandItem.copy(); mainHandItem.shrink(mainHandItem.getCount()); data.putItem(itemStack); } } return InteractionResult.SUCCESS; } } ``` --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_36_FirstGUI.md description: 36 第一个GUI 相关教程 --- # 参考 https://boson.v2mcdev.com/gui/firstgui.html ## 第一个GUI 这次我们将自定义一个GUI,GUI就是用户界面,GUI的操作应该在客户端执行。 与GUI有关的类是Screen,如果要创建一个GUI,就需要继承Screen类,Screen类有两个重要的方法init和render我们会使用这两个方法, 其中init是初始化GUI的组件的,例如GUI上的按钮,输入框,等等。 对于输入框,按钮这样的东西是一个交互对象,这样的交互对象一般是Widget类的子类,所以我们先介绍以下有关的子类,大家了解下,如果用到了就到对于的子类下去看怎么做,毕竟这里也不会把所有的子类都讲一遍。 * AbstractWidget (net.minecraft.client.gui.components):Minecraft中所有GUI组件的抽象基类,定义了组件的基本属性和方法。 * AbstractScrollWidget (net.minecraft.client.gui.components):继承自AbstractWidget,表示可滚动的GUI组件的抽象基类。 * ImageWidget (net.minecraft.client.gui.components):继承自AbstractWidget,表示显示图像的GUI组件。 * AbstractSliderButton (net.minecraft.client.gui.components):继承自AbstractWidget,表示滑块按钮的抽象基类。 * TabButton (net.minecraft.client.gui.components):继承自AbstractWidget,表示选项卡按钮的GUI组件。 * PlayerSkinWidget (net.minecraft.client.gui.components):继承自AbstractWidget,表示显示玩家皮肤的GUI组件。 * LoadingDotsWidget (net.minecraft.client.gui.components):继承自AbstractWidget,表示加载时显示的动画点的GUI组件。 * AbstractContainerWidget (net.minecraft.client.gui.components):继承自AbstractWidget,表示容器类GUI组件的抽象基类。 * OverlayRecipeButton in OverlayRecipeComponent (net.minecraft.client.gui.screens.recipebook):表示在配方书界面中显示叠加配方按钮的GUI组件。 * AbstractButton (net.minecraft.client.gui.components):继承自AbstractWidget,表示按钮的抽象基类。 * GameModeSlot in GameModeSwitcherScreen (net.minecraft.client.gui.screens.debug):表示在游戏模式切换界面中显示游戏模式槽的GUI组件。 * AbstractStringWidget (net.minecraft.client.gui.components):继承自AbstractWidget,表示显示字符串的GUI组件的抽象基类。 * StateSwitchingButton (net.minecraft.client.gui.components):继承自AbstractButton,表示状态切换按钮的GUI组件。 * EditBox (net.minecraft.client.gui.components):继承自AbstractStringWidget,表示文本编辑框的GUI组件。 * RecipeButton (net.minecraft.client.gui.screens.recipebook):继承自AbstractButton,表示在配方书界面中显示配方按钮的GUI组件。 这里我们创建一个滑块的和一个按钮和一个输入框的GUI界面,并且使用一个Item打开这个界面,注意只能在Client端打开。 ```java // 我们的GUI应该继承Screen类 public class FirstGui extends Screen { // EditBox类是输入框AbstractWidget的子类 EditBox editBox; // button是按钮 Button button; // 我们的GUI界面的背景图片的位置 ResourceLocation FIRST_GUI_TEXTURE = new ResourceLocation(ExampleMod.MODID, "textures/gui/first_gui.png"); // 这里的content是我们用于渲染文字的内容 Component content = Component.translatable("gui." + ExampleMod.MODID + ".first_gui_title"); // 滑条,参考调节音量那个 ExtendedSlider sliderBar; // 构造,这个传入了一个titile不过没有使用 public FirstGui(Component pTitle) { super(pTitle); } /** 初始化GUI中的各个组件 */ @Override protected void init() { // 创建一个输入框,并设置其位置、大小以及默认文本 // x,y,width,height,component this.editBox = new EditBox(this.font, this.width / 2 - 100, 66, 200, 20, Component.translatable("gui." + ExampleMod.MODID + ".first_gui")); this.addWidget(this.editBox); // button的应该通过builder获得,其中的起一个参数是按钮的名称,第二个参数是按钮按下之后会有什么操作的回调函数。 // pos是设置按钮的位置 // size是按钮的大小 this.button = new Button.Builder(Component.translatable("gui." + ExampleMod.MODID + ".first_gui.save"), pButton -> { }).pos(this.width / 2 - 40, 96).size(80, 20).build(); this.addWidget(this.button); // 滑条,位置x,y,宽高w,h,滑条名称前缀,后缀,滑条的最小值,最大值,初始值,是否渲染文字 this.sliderBar = new ExtendedSlider(this.width / 2 - 100, 120, 200, 10, Component.translatable("gui." + ExampleMod.MODID + ".first_gui.slider"), Component.empty(), 0, 100, 0, true); this.addWidget(this.sliderBar); // 别忘记的调用super super.init(); } /** 渲染GUI界面 @param pGuiGraphics GUI图形对象 @param pMouseX 鼠标X坐标 @param pMouseY 鼠标Y坐标 @param pPartialTick 部分刻度 */ @Override public void render(GuiGraphics pGuiGraphics, int pMouseX, int pMouseY, float pPartialTick) { // 暂时搞明白是干嘛的 this.renderBackground(pGuiGraphics, pMouseX, pMouseY, pPartialTick); // 设置渲染时候的色彩和透明度是正常的。 pGuiGraphics.setColor(1, 1, 1, 1); // 背景图的宽高 int textureWidth = 208; int textureHeight = 156; // 渲染背景图 pGuiGraphics.blit(FIRST_GUI_TEXTURE, this.width / 2 - 150, 10, 0, 0, 300, 200, textureWidth, textureHeight); // 渲染字体类型,内容,位置,颜色, pGuiGraphics.drawCenteredString(this.font, content, this.width / 2 - 10, 30, 0xeb0505); // 渲染编辑框 this.editBox.render(pGuiGraphics, pMouseX, pMouseY, pPartialTick); // 渲染 按钮 this.button.render(pGuiGraphics, pMouseX, pMouseY, pPartialTick); // 渲染滑条 this.sliderBar.render(pGuiGraphics, pMouseX, pMouseY, pPartialTick); // 别忘记了调用super super.render(pGuiGraphics, pMouseX, pMouseY, pPartialTick); } } ``` 一个辅助的类,用于打开Screen,不直接在调用的原因是可能导致有些在server下加载尽在客户端存在的代码(群友提到的,不过我并没有测试出来问题) ```java public class GuiOpenWrapper { public static void openFirstGui(){ Minecraft.getInstance().setScreen(new FirstGui(Component.translatable("test"))); } } ``` 物品类,已经没有好说的了,很简单,在客户端的逻辑中打开GUI。 ```java public class OpenFirstGuiItem extends Item { public OpenFirstGuiItem() { super(new Properties()); } @Override public InteractionResultHolder use(Level pLevel, Player pPlayer, InteractionHand pUsedHand) { if(pLevel.isClientSide){ GuiOpenWrapper.openFirstGui(); } return super.use(pLevel, pPlayer, pUsedHand); } } ``` 好了,我们这里用boson的一张图说明以下方法中的参数的一些关系 ```java pGuiGraphics.blit(FIRST_GUI_TEXTURE, this.width / 2 - 150, 10, 0, 0, 300, 200, textureWidth, textureHeight); ``` 参数签名如下 ```java // 第一个图片位置,x,y开始坐标,u,v图片的中纹理的启示坐标, 绘制宽高,图片的宽高 void blit( ResourceLocation pAtlasLocation, int pX, int pY, float pUOffset, float pVOffset, int pWidth, int pHeight, int pTextureWidth, int pTextureHeight ) ``` 不同数值如下。 ![alt text](image-1.png) 好了你可以进入游戏尝试打开了。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_36_FirstGUI.md description: 36 第一个GUI 相关教程 --- # 参考 https://boson.v2mcdev.com/gui/firstgui.html ## 第一个GUI 这次我们将自定义一个GUI,GUI就是用户界面,GUI的操作应该在客户端执行。 与GUI有关的类是Screen,如果要创建一个GUI,就需要继承Screen类,Screen类有两个重要的方法init和render我们会使用这两个方法, 其中init是初始化GUI的组件的,例如GUI上的按钮,输入框,等等。 对于输入框,按钮这样的东西是一个交互对象,这样的交互对象一般是Widget类的子类,所以我们先介绍以下有关的子类,大家了解下,如果用到了就到对于的子类下去看怎么做,毕竟这里也不会把所有的子类都讲一遍。 * AbstractWidget (net.minecraft.client.gui.components):Minecraft中所有GUI组件的抽象基类,定义了组件的基本属性和方法。 * AbstractScrollWidget (net.minecraft.client.gui.components):继承自AbstractWidget,表示可滚动的GUI组件的抽象基类。 * ImageWidget (net.minecraft.client.gui.components):继承自AbstractWidget,表示显示图像的GUI组件。 * AbstractSliderButton (net.minecraft.client.gui.components):继承自AbstractWidget,表示滑块按钮的抽象基类。 * TabButton (net.minecraft.client.gui.components):继承自AbstractWidget,表示选项卡按钮的GUI组件。 * PlayerSkinWidget (net.minecraft.client.gui.components):继承自AbstractWidget,表示显示玩家皮肤的GUI组件。 * LoadingDotsWidget (net.minecraft.client.gui.components):继承自AbstractWidget,表示加载时显示的动画点的GUI组件。 * AbstractContainerWidget (net.minecraft.client.gui.components):继承自AbstractWidget,表示容器类GUI组件的抽象基类。 * OverlayRecipeButton in OverlayRecipeComponent (net.minecraft.client.gui.screens.recipebook):表示在配方书界面中显示叠加配方按钮的GUI组件。 * AbstractButton (net.minecraft.client.gui.components):继承自AbstractWidget,表示按钮的抽象基类。 * GameModeSlot in GameModeSwitcherScreen (net.minecraft.client.gui.screens.debug):表示在游戏模式切换界面中显示游戏模式槽的GUI组件。 * AbstractStringWidget (net.minecraft.client.gui.components):继承自AbstractWidget,表示显示字符串的GUI组件的抽象基类。 * StateSwitchingButton (net.minecraft.client.gui.components):继承自AbstractButton,表示状态切换按钮的GUI组件。 * EditBox (net.minecraft.client.gui.components):继承自AbstractStringWidget,表示文本编辑框的GUI组件。 * RecipeButton (net.minecraft.client.gui.screens.recipebook):继承自AbstractButton,表示在配方书界面中显示配方按钮的GUI组件。 这里我们创建一个滑块的和一个按钮和一个输入框的GUI界面,并且使用一个Item打开这个界面,注意只能在Client端打开。 ```java // 我们的GUI应该继承Screen类 public class FirstGui extends Screen { // EditBox类是输入框AbstractWidget的子类 EditBox editBox; // button是按钮 Button button; // 我们的GUI界面的背景图片的位置 ResourceLocation FIRST_GUI_TEXTURE = new ResourceLocation(ExampleMod.MODID, "textures/gui/first_gui.png"); // 这里的content是我们用于渲染文字的内容 Component content = Component.translatable("gui." + ExampleMod.MODID + ".first_gui_title"); // 滑条,参考调节音量那个 ExtendedSlider sliderBar; // 构造,这个传入了一个titile不过没有使用 public FirstGui(Component pTitle) { super(pTitle); } /** 初始化GUI中的各个组件 */ @Override protected void init() { // 创建一个输入框,并设置其位置、大小以及默认文本 // x,y,width,height,component this.editBox = new EditBox(this.font, this.width / 2 - 100, 66, 200, 20, Component.translatable("gui." + ExampleMod.MODID + ".first_gui")); this.addWidget(this.editBox); // button的应该通过builder获得,其中的起一个参数是按钮的名称,第二个参数是按钮按下之后会有什么操作的回调函数。 // pos是设置按钮的位置 // size是按钮的大小 this.button = new Button.Builder(Component.translatable("gui." + ExampleMod.MODID + ".first_gui.save"), pButton -> { }).pos(this.width / 2 - 40, 96).size(80, 20).build(); this.addWidget(this.button); // 滑条,位置x,y,宽高w,h,滑条名称前缀,后缀,滑条的最小值,最大值,初始值,是否渲染文字 this.sliderBar = new ExtendedSlider(this.width / 2 - 100, 120, 200, 10, Component.translatable("gui." + ExampleMod.MODID + ".first_gui.slider"), Component.empty(), 0, 100, 0, true); this.addWidget(this.sliderBar); // 别忘记的调用super super.init(); } /** 渲染GUI界面 @param pGuiGraphics GUI图形对象 @param pMouseX 鼠标X坐标 @param pMouseY 鼠标Y坐标 @param pPartialTick 部分刻度 */ @Override public void render(GuiGraphics pGuiGraphics, int pMouseX, int pMouseY, float pPartialTick) { // 暂时搞明白是干嘛的 this.renderBackground(pGuiGraphics, pMouseX, pMouseY, pPartialTick); // 设置渲染时候的色彩和透明度是正常的。 pGuiGraphics.setColor(1, 1, 1, 1); // 背景图的宽高 int textureWidth = 208; int textureHeight = 156; // 渲染背景图 pGuiGraphics.blit(FIRST_GUI_TEXTURE, this.width / 2 - 150, 10, 0, 0, 300, 200, textureWidth, textureHeight); // 渲染字体类型,内容,位置,颜色, pGuiGraphics.drawCenteredString(this.font, content, this.width / 2 - 10, 30, 0xeb0505); // 渲染编辑框 this.editBox.render(pGuiGraphics, pMouseX, pMouseY, pPartialTick); // 渲染 按钮 this.button.render(pGuiGraphics, pMouseX, pMouseY, pPartialTick); // 渲染滑条 this.sliderBar.render(pGuiGraphics, pMouseX, pMouseY, pPartialTick); // 别忘记了调用super super.render(pGuiGraphics, pMouseX, pMouseY, pPartialTick); } } ``` 一个辅助的类,用于打开Screen,不直接在调用的原因是可能导致有些在server下加载尽在客户端存在的代码(群友提到的,不过我并没有测试出来问题) ```java public class GuiOpenWrapper { public static void openFirstGui(){ Minecraft.getInstance().setScreen(new FirstGui(Component.translatable("test"))); } } ``` 物品类,已经没有好说的了,很简单,在客户端的逻辑中打开GUI。 ```java public class OpenFirstGuiItem extends Item { public OpenFirstGuiItem() { super(new Properties()); } @Override public InteractionResultHolder use(Level pLevel, Player pPlayer, InteractionHand pUsedHand) { if(pLevel.isClientSide){ GuiOpenWrapper.openFirstGui(); } return super.use(pLevel, pPlayer, pUsedHand); } } ``` 好了,我们这里用boson的一张图说明以下方法中的参数的一些关系 ```java pGuiGraphics.blit(FIRST_GUI_TEXTURE, this.width / 2 - 150, 10, 0, 0, 300, 200, textureWidth, textureHeight); ``` 参数签名如下 ```java // 第一个图片位置,x,y开始坐标,u,v图片的中纹理的启示坐标, 绘制宽高,图片的宽高 void blit( ResourceLocation pAtlasLocation, int pX, int pY, float pUOffset, float pVOffset, int pWidth, int pHeight, int pTextureWidth, int pTextureHeight ) ``` 不同数值如下。 ![alt text](image-1.png) 好了你可以进入游戏尝试打开了。 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_37_Menu.md description: 37 menu 相关教程 --- # 参考 https://boson.v2mcdev.com/gui/container.html ## MENU 好了上一次的视频中,我们讲解了gui,不过上次的教程的GUI还是和单薄的,实现的功能有限,更适合做一些界面的操作。在这次的视频中我们讲解怎么做一个能存放一的物品的类似箱子的内容。 那么和这次有关的类是screen,menu。这两个主要的类,和一些这两个类用到的其他的类。 在上次的Screen创建的GUI中,他的代码仅仅在客户端可以使用,如果你的逻辑要在服务器做一些操作,那么只能通过发包的方式进行操作。不过这次我们的说的内容menu,它自带了一些数据同步功能。 我们讲解下Menu,他与screen的关系有点类似block和blockentitiy的关系,不过并不相同,menu是screen的逻辑实现的类,menu用来存储临时数据的以及数据同步的`(所以你的最后在那个数据还是需要存在blockentitiy中)`,不会进行渲染,而渲染还是通过screen类进行,利用menu的同步机制能同步itemhandler这样的inventory和containerdata这样的数据。对于更加复杂的内容,也只能通过发包来解决了。 好了这里借用boson的一张图来了解下对于的menu和screen的之间的关系。 ![alt text](image-2.png) 通过这张图你应该明白客户端额数据仅仅用于展示的,所以你不要更改这些数据,如果要实现一些按钮点击的操作,请采用发包的方法解决,对于menu也就是图里面的container是可以自己同步slot的数据和containerdata的这部分不需要你处理。 这次我们做一个只有一个格子的箱子。 想来看下block的代码吧。 ```java public class FirstMenuBlock extends BaseEntityBlock { public FirstMenuBlock() { super(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE)); } @Override protected MapCodec codec() { return null; } @Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new FirstMenuBlockEntity(pPos,pState); } @Override public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) { if (!pLevel.isClientSide){ //打开menu //值得说的就是这里,这里是打开Menu的操作,需要你传入一个试下你了MenuProvider的类,对于我们的blockentitiy当然的实现的,所以可以直接返回。需要将pos位置也传入 // 当然对于openMenu方法也有重载的不需要pos的方法,自己挑选合适即可 // 我们调用getmenprovider这方法返回对应的blockentity,这个方法很简单,自己点开查看下吧。 pPlayer.openMenu(this.getMenuProvider(pState,pLevel,pPos),pPos); } return super.use(pState, pLevel, pPos, pPlayer, pHand, pHit); } @Override public RenderShape getRenderShape(BlockState pState) { return RenderShape.MODEL; } @Nullable @Override public BlockEntityTicker getTicker(Level level, BlockState state,BlockEntityType type) { return level.isClientSide ? null : createTickerHelper(type, ModBlockEntities.FIRST_MENU_BLOCK_ENTITY.get(), FirstMenuBlockEntity::serverTick); } } ``` 不过还是要说一些内容是关于menu的创建方式对于客户端和服务器是不同的, 所以的这里直接引用了boson的教程的内容。 > 当你执行这条命令时,Minecraft会同时在客户端和服务端各自创建一个Container,并且创建的流程不一样。我们来讲一下这个创建的流程时什么样子的。 > > * 服务端:这个函数的第二个参数是是一个INamedContainerProvider接口,这里我们让我们的方块实体实现了这个接口(大部分时候你都应该这么做)。这个接口下有个叫做createMenu的方法,在服务端最终会调用这个方法来创建Container。 > * 客户端:客户端的操作首先会进行一次发包处理,NetworkHooks.openGui方法的第三个参数让你可以在发送的数据包中额外的附加数据(为了方便展示,我没有使用内置的封装好的函数),这里添加数据的方法,相信已经会自定义网络包的你,理解起来不难。 那么其实openmenu的第二个的pos就是发包的附加数据,而调用的构造在menu的第一个方法,之后我们再说。 当客户端接受到发送来的包之后,客户的会调用你在注册时设置的构造方法创建,具体的内容我们留到注册部分再说 下面来看看blockentity类 对于方块实体,我们应该继承Blockentity类,由于有Menu所有我们要实现这个provider的接口。实现这个接口之后你需要实现两个方法,一个是getDisplayName的方法,另一个就是返回对应的menu的方法createMenu。 ```java public class FirstMenuBlockEntity extends BlockEntity implements MenuProvider { // i 是我们举例子使用containerdata同步的数据,在实际中可以用来同步进度条。 int i = 0; // 数据同步的工具详细的实现在下面。 protected final ContainerData data; // itemhandler 是我们处理物品的地方,itemstack存放在这里。 // 参数1表示1个slot,也就是一个格子的箱子。 private final ItemStackHandler itemHandler = new ItemStackHandler(1); public FirstMenuBlockEntity(BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.FIRST_MENU_BLOCK_ENTITY.get(), pPos, pBlockState); // data实现在这里 this.data = new ContainerData() { // 通过data获得对应的数值,通过index获得, // 如果index是0,那么要查找的就是i。返回i数值即可 //如果是多个,那么就在这里添加多个if或者swtich判断条件。 @Override public int get(int pIndex) { if (pIndex==0){ return i; } return 0; } // 设置同步的数值,index索引,value数值 @Override public void set(int pIndex, int pValue) { if(pIndex==0){ //这里由于闭包,所以通过FirstMenuBlockEntity才能拿到FirstMenuBlockEntity类的this指针。 FirstMenuBlockEntity.this.i = pValue; } } @Override public int getCount() { // 你的container中的同步的数据的数量。要保持一致 return 1; } }; } //menuprovider要求实现的方法,这里返回你的menu的一个名称,也需要国家化处理。 @Override public Component getDisplayName() { return Component.translatable("gui."+ExampleMod.MODID+".first_menu_gui"); } // 创建了我们的Container // 这里的创建是服务端的创建。 @Nullable @Override public AbstractContainerMenu createMenu(int pContainerId, Inventory pPlayerInventory, Player pPlayer) { return new FirstMenu(pContainerId,pPlayerInventory,this,this.data); } // 数据保存和回复 @Override public void load(CompoundTag pTag) { super.load(pTag); CompoundTag inventory = pTag.getCompound("inventory"); this.itemHandler.deserializeNBT(inventory); } @Override protected void saveAdditional(CompoundTag pTag) { super.saveAdditional(pTag); pTag.put("inventory",this.itemHandler.serializeNBT()); } // 帮助我们获得itemhandler的方法,我们在screen中会使用 public ItemStackHandler getItemHandler(){ return this.itemHandler; } //servertick方法。 public static void serverTick(Level pLevel, BlockPos pPos, BlockState pState, FirstMenuBlockEntity pBlockEntity) { if(pLevel!=null && !pLevel.isClientSide){ // 很简单就是在服务端设置i的数值为放入的物品的堆叠数量, pBlockEntity.data.set(0,pBlockEntity.itemHandler.getStackInSlot(0).getCount()); } } } ``` 下面我们来看我们的menu的内容 ```java // 我们需要继承AbstractContainerMenu来实现我们的menu public class FirstMenu extends AbstractContainerMenu { // 同步的data,等会我们给他赋值 private final ContainerData data; // 还记得我们说过的对于menu存在两个端的调用吗,这一个构造就是我们给客户端调用的,也就是注册时候用的,当我们调用openmenu时候,服务端会发包给客户端,客户端调用我们注册的这个方法。 // buf的内容就是我们写入的pos public FirstMenu(int pContainerId, Inventory inv, FriendlyByteBuf buf) { this(pContainerId,inv,inv.player.level().getBlockEntity(buf.readBlockPos()),new SimpleContainerData(1)); } // 这个就是我们blockentity中create返回的方法。 public FirstMenu(int pContainerId, Inventory inv, BlockEntity entity, ContainerData data) { //构造这是使用到了MenuType和EntitiyType一样要是对Menu的注册,我们之后注册讲 super(ModMenuTypes.FIRST_MENU.get(), pContainerId); // 检查slot的多少 checkContainerSize(inv,1); // 赋值data this.data = data; FirstMenuBlockEntity firstMenuBlockEntity = (FirstMenuBlockEntity) entity; ItemStackHandler itemHandler = firstMenuBlockEntity.getItemHandler(); // 添加一个slot在menu上。 this.addSlot(new SlotItemHandler(itemHandler,0,80,32)); // 添加data 用于数据同步的。 addDataSlots(data); // 添加玩家背包和热键栏的slot,关于slot的讲解在下面。 layoutPlayerInventorySlots(inv); } //这个是你按住shift点击物品快速移动的,这里没有实现 @Override public ItemStack quickMoveStack(Player pPlayer, int pIndex) { return ItemStack.EMPTY; } // 这个是验证你的打开这个界面是否是合法的,我们直接返回了true,你可以参考其他的menu这个是怎么实现的。 @Override public boolean stillValid(Player pPlayer) { return true; } // 添加玩家背包的slot和热键的栏的slot private void layoutPlayerInventorySlots(Inventory playerInventory) { // Player inventory for (int i = 0; i < 3; ++i) { for (int l = 0; l < 9; ++l) { this.addSlot(new Slot(playerInventory, l + i * 9 + 9, 8 + l * 18, 84 + i * 18)); } } // Hotbar for (int i = 0; i < 9; ++i) { this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142)); } } // 给scrren获得data用的一个辅助的方法 public ContainerData getContainerData(){ return this.data; } } ``` 这里使用boson的图,大家就明白slot是什么东西了。 ![alt text](image-3.png) 最后是我们的screen ```java // 继承AbstractContainerScreen来实现我们带有container的GUI public class FistMenuGui extends AbstractContainerScreen { // 界面图片的位置 private final ResourceLocation OBSIDIAN_CONTAINER_RESOURCE = new ResourceLocation(ExampleMod.MODID, "textures/gui/container.png"); //图片的大小 private final int textureWidth = 176; private final int textureHeight = 166; //构造 public FistMenuGui(FirstMenu pMenu, Inventory pPlayerInventory, Component pTitle) { super(pMenu, pPlayerInventory, pTitle); } // 初始化 @Override protected void init() { super.init(); } // 渲染label,在这里实现渲染你的按钮,提示信息等 @Override protected void renderLabels(GuiGraphics pGuiGraphics, int pMouseX, int pMouseY) { //这里我们将slot中的itemstack的堆叠数量取出,渲染在界面上。 // 其中参数含义和上一个教程讲得一样。 super.renderLabels(pGuiGraphics, pMouseX, pMouseY); pGuiGraphics.drawString(this.font,Component.literal(this.menu.getContainerData().get(0)+""),82,20,0xeb0505); } // 这里渲染你的图片 @Override protected void renderBg(GuiGraphics pGuiGraphics, float pPartialTick, int pMouseX, int pMouseY) { RenderSystem.setShader(GameRenderer::getPositionTexShader); RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); int x = (width - imageWidth) / 2; int y = (height - imageHeight) / 2; // 渲染图片 pGuiGraphics.blit(OBSIDIAN_CONTAINER_RESOURCE,x,y,0,0,this.textureWidth,this.textureHeight,this.textureWidth,this.textureHeight); } // 为了能让我们的GUI渲染物品的提示信息,所以调用下renderTooltip @Override public void render(GuiGraphics pGuiGraphics, int pMouseX, int pMouseY, float pPartialTick) { renderBackground(pGuiGraphics,pMouseX,pMouseY,pPartialTick); super.render(pGuiGraphics, pMouseX, pMouseY, pPartialTick); renderTooltip(pGuiGraphics,pMouseX,pMouseY); } } ``` 下面我们注册我们的menu和screen,对于方块和方块实体的注册就不讲了。 ```java public class ModMenuTypes { // 照旧 public static final DeferredRegister> MENU_TYPES = DeferredRegister.create(Registries.MENU, ExampleMod.MODID); // 照旧,不一样的地方是我们需要一个辅助函数 public static final Supplier> FIRST_MENU =registerMenuType(FirstMenu::new,"first_menu"); // 我们的辅助函数 // 我们需要通过IMenuTypeExtension的.create方法才能返回一个menutype, // create方法需要传入一个IContainerFactory的内容,而正好我们的构造函数就是IContainerFactory一样的参数。 // 因为就是这样设计的, 所以传入new就可以了。 private static Supplier> registerMenuType(IContainerFactory factory, String name) { return MENU_TYPES.register(name, () -> IMenuTypeExtension.create(factory)); } public static void register(IEventBus eventBus){ MENU_TYPES.register(eventBus); } } ``` screen的注册比较简单。 ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class ClientEventHandler { @SubscribeEvent public static void onClientEvent(FMLClientSetupEvent event){ event.enqueueWork(()->{ // 传入对应的menu和screen的构造即可 MenuScreens.register(ModMenuTypes.FIRST_MENU.get(),FistMenuGui::new); }); } } ``` 记得注册到总线 ```java ModMenuTypes.register(modEventBus); ``` 好了你可以进入游戏查看这个方块了 。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_37_Menu.md description: 37 menu 相关教程 --- # 参考 https://boson.v2mcdev.com/gui/container.html ## MENU 好了上一次的视频中,我们讲解了gui,不过上次的教程的GUI还是和单薄的,实现的功能有限,更适合做一些界面的操作。在这次的视频中我们讲解怎么做一个能存放一的物品的类似箱子的内容。 那么和这次有关的类是screen,menu。这两个主要的类,和一些这两个类用到的其他的类。 在上次的Screen创建的GUI中,他的代码仅仅在客户端可以使用,如果你的逻辑要在服务器做一些操作,那么只能通过发包的方式进行操作。不过这次我们的说的内容menu,它自带了一些数据同步功能。 我们讲解下Menu,他与screen的关系有点类似block和blockentitiy的关系,不过并不相同,menu是screen的逻辑实现的类,menu用来存储临时数据的以及数据同步的`(所以你的最后在那个数据还是需要存在blockentitiy中)`,不会进行渲染,而渲染还是通过screen类进行,利用menu的同步机制能同步itemhandler这样的inventory和containerdata这样的数据。对于更加复杂的内容,也只能通过发包来解决了。 好了这里借用boson的一张图来了解下对于的menu和screen的之间的关系。 ![alt text](image-2.png) 通过这张图你应该明白客户端额数据仅仅用于展示的,所以你不要更改这些数据,如果要实现一些按钮点击的操作,请采用发包的方法解决,对于menu也就是图里面的container是可以自己同步slot的数据和containerdata的这部分不需要你处理。 这次我们做一个只有一个格子的箱子。 想来看下block的代码吧。 ```java public class FirstMenuBlock extends BaseEntityBlock { public FirstMenuBlock() { super(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE)); } @Override protected MapCodec codec() { return null; } @Nullable @Override public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) { return new FirstMenuBlockEntity(pPos,pState); } @Override public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) { if (!pLevel.isClientSide){ //打开menu //值得说的就是这里,这里是打开Menu的操作,需要你传入一个试下你了MenuProvider的类,对于我们的blockentitiy当然的实现的,所以可以直接返回。需要将pos位置也传入 // 当然对于openMenu方法也有重载的不需要pos的方法,自己挑选合适即可 // 我们调用getmenprovider这方法返回对应的blockentity,这个方法很简单,自己点开查看下吧。 pPlayer.openMenu(this.getMenuProvider(pState,pLevel,pPos),pPos); } return super.use(pState, pLevel, pPos, pPlayer, pHand, pHit); } @Override public RenderShape getRenderShape(BlockState pState) { return RenderShape.MODEL; } @Nullable @Override public BlockEntityTicker getTicker(Level level, BlockState state,BlockEntityType type) { return level.isClientSide ? null : createTickerHelper(type, ModBlockEntities.FIRST_MENU_BLOCK_ENTITY.get(), FirstMenuBlockEntity::serverTick); } } ``` 不过还是要说一些内容是关于menu的创建方式对于客户端和服务器是不同的, 所以的这里直接引用了boson的教程的内容。 > 当你执行这条命令时,Minecraft会同时在客户端和服务端各自创建一个Container,并且创建的流程不一样。我们来讲一下这个创建的流程时什么样子的。 > > * 服务端:这个函数的第二个参数是是一个INamedContainerProvider接口,这里我们让我们的方块实体实现了这个接口(大部分时候你都应该这么做)。这个接口下有个叫做createMenu的方法,在服务端最终会调用这个方法来创建Container。 > * 客户端:客户端的操作首先会进行一次发包处理,NetworkHooks.openGui方法的第三个参数让你可以在发送的数据包中额外的附加数据(为了方便展示,我没有使用内置的封装好的函数),这里添加数据的方法,相信已经会自定义网络包的你,理解起来不难。 那么其实openmenu的第二个的pos就是发包的附加数据,而调用的构造在menu的第一个方法,之后我们再说。 当客户端接受到发送来的包之后,客户的会调用你在注册时设置的构造方法创建,具体的内容我们留到注册部分再说 下面来看看blockentity类 对于方块实体,我们应该继承Blockentity类,由于有Menu所有我们要实现这个provider的接口。实现这个接口之后你需要实现两个方法,一个是getDisplayName的方法,另一个就是返回对应的menu的方法createMenu。 ```java public class FirstMenuBlockEntity extends BlockEntity implements MenuProvider { // i 是我们举例子使用containerdata同步的数据,在实际中可以用来同步进度条。 int i = 0; // 数据同步的工具详细的实现在下面。 protected final ContainerData data; // itemhandler 是我们处理物品的地方,itemstack存放在这里。 // 参数1表示1个slot,也就是一个格子的箱子。 private final ItemStackHandler itemHandler = new ItemStackHandler(1); public FirstMenuBlockEntity(BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.FIRST_MENU_BLOCK_ENTITY.get(), pPos, pBlockState); // data实现在这里 this.data = new ContainerData() { // 通过data获得对应的数值,通过index获得, // 如果index是0,那么要查找的就是i。返回i数值即可 //如果是多个,那么就在这里添加多个if或者swtich判断条件。 @Override public int get(int pIndex) { if (pIndex==0){ return i; } return 0; } // 设置同步的数值,index索引,value数值 @Override public void set(int pIndex, int pValue) { if(pIndex==0){ //这里由于闭包,所以通过FirstMenuBlockEntity才能拿到FirstMenuBlockEntity类的this指针。 FirstMenuBlockEntity.this.i = pValue; } } @Override public int getCount() { // 你的container中的同步的数据的数量。要保持一致 return 1; } }; } //menuprovider要求实现的方法,这里返回你的menu的一个名称,也需要国家化处理。 @Override public Component getDisplayName() { return Component.translatable("gui."+ExampleMod.MODID+".first_menu_gui"); } // 创建了我们的Container // 这里的创建是服务端的创建。 @Nullable @Override public AbstractContainerMenu createMenu(int pContainerId, Inventory pPlayerInventory, Player pPlayer) { return new FirstMenu(pContainerId,pPlayerInventory,this,this.data); } // 数据保存和回复 @Override public void load(CompoundTag pTag) { super.load(pTag); CompoundTag inventory = pTag.getCompound("inventory"); this.itemHandler.deserializeNBT(inventory); } @Override protected void saveAdditional(CompoundTag pTag) { super.saveAdditional(pTag); pTag.put("inventory",this.itemHandler.serializeNBT()); } // 帮助我们获得itemhandler的方法,我们在screen中会使用 public ItemStackHandler getItemHandler(){ return this.itemHandler; } //servertick方法。 public static void serverTick(Level pLevel, BlockPos pPos, BlockState pState, FirstMenuBlockEntity pBlockEntity) { if(pLevel!=null && !pLevel.isClientSide){ // 很简单就是在服务端设置i的数值为放入的物品的堆叠数量, pBlockEntity.data.set(0,pBlockEntity.itemHandler.getStackInSlot(0).getCount()); } } } ``` 下面我们来看我们的menu的内容 ```java // 我们需要继承AbstractContainerMenu来实现我们的menu public class FirstMenu extends AbstractContainerMenu { // 同步的data,等会我们给他赋值 private final ContainerData data; // 还记得我们说过的对于menu存在两个端的调用吗,这一个构造就是我们给客户端调用的,也就是注册时候用的,当我们调用openmenu时候,服务端会发包给客户端,客户端调用我们注册的这个方法。 // buf的内容就是我们写入的pos public FirstMenu(int pContainerId, Inventory inv, FriendlyByteBuf buf) { this(pContainerId,inv,inv.player.level().getBlockEntity(buf.readBlockPos()),new SimpleContainerData(1)); } // 这个就是我们blockentity中create返回的方法。 public FirstMenu(int pContainerId, Inventory inv, BlockEntity entity, ContainerData data) { //构造这是使用到了MenuType和EntitiyType一样要是对Menu的注册,我们之后注册讲 super(ModMenuTypes.FIRST_MENU.get(), pContainerId); // 检查slot的多少 checkContainerSize(inv,1); // 赋值data this.data = data; FirstMenuBlockEntity firstMenuBlockEntity = (FirstMenuBlockEntity) entity; ItemStackHandler itemHandler = firstMenuBlockEntity.getItemHandler(); // 添加一个slot在menu上。 this.addSlot(new SlotItemHandler(itemHandler,0,80,32)); // 添加data 用于数据同步的。 addDataSlots(data); // 添加玩家背包和热键栏的slot,关于slot的讲解在下面。 layoutPlayerInventorySlots(inv); } //这个是你按住shift点击物品快速移动的,这里没有实现 @Override public ItemStack quickMoveStack(Player pPlayer, int pIndex) { return ItemStack.EMPTY; } // 这个是验证你的打开这个界面是否是合法的,我们直接返回了true,你可以参考其他的menu这个是怎么实现的。 @Override public boolean stillValid(Player pPlayer) { return true; } // 添加玩家背包的slot和热键的栏的slot private void layoutPlayerInventorySlots(Inventory playerInventory) { // Player inventory for (int i = 0; i < 3; ++i) { for (int l = 0; l < 9; ++l) { this.addSlot(new Slot(playerInventory, l + i * 9 + 9, 8 + l * 18, 84 + i * 18)); } } // Hotbar for (int i = 0; i < 9; ++i) { this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142)); } } // 给scrren获得data用的一个辅助的方法 public ContainerData getContainerData(){ return this.data; } } ``` 这里使用boson的图,大家就明白slot是什么东西了。 ![alt text](image-3.png) 最后是我们的screen ```java // 继承AbstractContainerScreen来实现我们带有container的GUI public class FistMenuGui extends AbstractContainerScreen { // 界面图片的位置 private final ResourceLocation OBSIDIAN_CONTAINER_RESOURCE = new ResourceLocation(ExampleMod.MODID, "textures/gui/container.png"); //图片的大小 private final int textureWidth = 176; private final int textureHeight = 166; //构造 public FistMenuGui(FirstMenu pMenu, Inventory pPlayerInventory, Component pTitle) { super(pMenu, pPlayerInventory, pTitle); } // 初始化 @Override protected void init() { super.init(); } // 渲染label,在这里实现渲染你的按钮,提示信息等 @Override protected void renderLabels(GuiGraphics pGuiGraphics, int pMouseX, int pMouseY) { //这里我们将slot中的itemstack的堆叠数量取出,渲染在界面上。 // 其中参数含义和上一个教程讲得一样。 super.renderLabels(pGuiGraphics, pMouseX, pMouseY); pGuiGraphics.drawString(this.font,Component.literal(this.menu.getContainerData().get(0)+""),82,20,0xeb0505); } // 这里渲染你的图片 @Override protected void renderBg(GuiGraphics pGuiGraphics, float pPartialTick, int pMouseX, int pMouseY) { RenderSystem.setShader(GameRenderer::getPositionTexShader); RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); int x = (width - imageWidth) / 2; int y = (height - imageHeight) / 2; // 渲染图片 pGuiGraphics.blit(OBSIDIAN_CONTAINER_RESOURCE,x,y,0,0,this.textureWidth,this.textureHeight,this.textureWidth,this.textureHeight); } // 为了能让我们的GUI渲染物品的提示信息,所以调用下renderTooltip @Override public void render(GuiGraphics pGuiGraphics, int pMouseX, int pMouseY, float pPartialTick) { renderBackground(pGuiGraphics,pMouseX,pMouseY,pPartialTick); super.render(pGuiGraphics, pMouseX, pMouseY, pPartialTick); renderTooltip(pGuiGraphics,pMouseX,pMouseY); } } ``` 下面我们注册我们的menu和screen,对于方块和方块实体的注册就不讲了。 ```java public class ModMenuTypes { // 照旧 public static final DeferredRegister> MENU_TYPES = DeferredRegister.create(Registries.MENU, ExampleMod.MODID); // 照旧,不一样的地方是我们需要一个辅助函数 public static final Supplier> FIRST_MENU =registerMenuType(FirstMenu::new,"first_menu"); // 我们的辅助函数 // 我们需要通过IMenuTypeExtension的.create方法才能返回一个menutype, // create方法需要传入一个IContainerFactory的内容,而正好我们的构造函数就是IContainerFactory一样的参数。 // 因为就是这样设计的, 所以传入new就可以了。 private static Supplier> registerMenuType(IContainerFactory factory, String name) { return MENU_TYPES.register(name, () -> IMenuTypeExtension.create(factory)); } public static void register(IEventBus eventBus){ MENU_TYPES.register(eventBus); } } ``` screen的注册比较简单。 ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class ClientEventHandler { @SubscribeEvent public static void onClientEvent(FMLClientSetupEvent event){ event.enqueueWork(()->{ // 传入对应的menu和screen的构造即可 MenuScreens.register(ModMenuTypes.FIRST_MENU.get(),FistMenuGui::new); }); } } ``` 记得注册到总线 ```java ModMenuTypes.register(modEventBus); ``` 好了你可以进入游戏查看这个方块了 。 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_38_HUD.md description: 38 HUD 相关教程 --- # 参考 https://boson.v2mcdev.com/gui/hud.html ## HUD 我们定义ExampleHud的类,实现了IGuiOverlay接口,用于在游戏中显示HUD,这里使用BOSON的内容,讲解下HUD是什么: > HUD或者又称为inGameGui,指的是你在游戏中看到的类似于经验条、准星之类的东西。 由于注册时候要求返回一个实现了IGuiOverlay接口的实例,这里使用了单例模式,返回这个实例,或者你可以使用对应的lammbda表达式。 其中重要的就是render方法,该方法绘制对应的内容. 使用GuiGraphics类进行绘制。该类提供了各种绘制内容的方法,在之前的教程中应该已经见到了。 ```java public class ExampleHud implements IGuiOverlay { // 定义一个静态常量 hud,用于存储 ExampleHud 的单例对象 private static final ExampleHud hud = new ExampleHud(); // 定义一个 ResourceLocation 对象,用于存储 HUD 纹理的路径 private final ResourceLocation HUD = new ResourceLocation(ExampleMod.MODID, "textures/gui/hud.png"); public ExampleHud() { } @Override public void render(ExtendedGui gui, GuiGraphics guiGraphics, float partialTick, int screenWidth, int screenHeight) { // 检查玩家主手持有的物品是否为 ModItems.RUBY_APPLE,如果不是则直接返回 if (minecraft.player.getMainHandItem().getItem()!= ModItems.RUBY_APPLE.get()) return; guiGraphics.setColor(1,1,1,1); // 在屏幕中央上方绘制 HUD 纹理,宽高为 32x32 像素 //参数是含义,x坐标,y坐标,u,v坐标,宽高,uv宽高。 guiGraphics.blit(HUD,screenWidth/2-16,screenHeight/2-64,0,0,32,32,32,32); } // 提供一个静态方法用于获取 ExampleHud 的单例对象 public static ExampleHud getInstance() { return hud; } } ``` ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class ClientEventHandler { //// 在 RegisterGuiOverlaysEvent 事件中注册自定义的 HUD 覆盖 @SubscribeEvent public static void registerGuiOverlays(RegisterGuiOverlaysEvent event) { // 使用 event.registerAboveAll 方法将 ExampleHud 的实例注册为最高优先级的 HUD 覆盖 // 第一个参数是一个 ResourceLocation 对象,用于指定 HUD 的唯一标识符 // 第二个参数是 ExampleHud 的单例对象,通过 ExampleHud.getInstance() 获取 event.registerAboveAll(new ResourceLocation(ExampleMod.MODID,"example_hud"), ExampleHud.getInstance()); } } ``` --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_38_HUD.md description: 38 HUD 相关教程 --- # 参考 https://boson.v2mcdev.com/gui/hud.html ## HUD 我们定义ExampleHud的类,实现了IGuiOverlay接口,用于在游戏中显示HUD,这里使用BOSON的内容,讲解下HUD是什么: > HUD或者又称为inGameGui,指的是你在游戏中看到的类似于经验条、准星之类的东西。 由于注册时候要求返回一个实现了IGuiOverlay接口的实例,这里使用了单例模式,返回这个实例,或者你可以使用对应的lammbda表达式。 其中重要的就是render方法,该方法绘制对应的内容. 使用GuiGraphics类进行绘制。该类提供了各种绘制内容的方法,在之前的教程中应该已经见到了。 ```java public class ExampleHud implements IGuiOverlay { // 定义一个静态常量 hud,用于存储 ExampleHud 的单例对象 private static final ExampleHud hud = new ExampleHud(); // 定义一个 ResourceLocation 对象,用于存储 HUD 纹理的路径 private final ResourceLocation HUD = new ResourceLocation(ExampleMod.MODID, "textures/gui/hud.png"); public ExampleHud() { } @Override public void render(ExtendedGui gui, GuiGraphics guiGraphics, float partialTick, int screenWidth, int screenHeight) { // 检查玩家主手持有的物品是否为 ModItems.RUBY_APPLE,如果不是则直接返回 if (minecraft.player.getMainHandItem().getItem()!= ModItems.RUBY_APPLE.get()) return; guiGraphics.setColor(1,1,1,1); // 在屏幕中央上方绘制 HUD 纹理,宽高为 32x32 像素 //参数是含义,x坐标,y坐标,u,v坐标,宽高,uv宽高。 guiGraphics.blit(HUD,screenWidth/2-16,screenHeight/2-64,0,0,32,32,32,32); } // 提供一个静态方法用于获取 ExampleHud 的单例对象 public static ExampleHud getInstance() { return hud; } } ``` ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class ClientEventHandler { //// 在 RegisterGuiOverlaysEvent 事件中注册自定义的 HUD 覆盖 @SubscribeEvent public static void registerGuiOverlays(RegisterGuiOverlaysEvent event) { // 使用 event.registerAboveAll 方法将 ExampleHud 的实例注册为最高优先级的 HUD 覆盖 // 第一个参数是一个 ResourceLocation 对象,用于指定 HUD 的唯一标识符 // 第二个参数是 ExampleHud 的单例对象,通过 ExampleHud.getInstance() 获取 event.registerAboveAll(new ResourceLocation(ExampleMod.MODID,"example_hud"), ExampleHud.getInstance()); } } ``` --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_39_RegisterKey.md description: 39 注册热键 相关教程 --- # 注册热键 这次我们来说怎么给我们的mod添加一个按键,按下按键处理按键事件。 不过提前要说明的一点就是按键的注册和触发的所有内容都是在客户端的,所以如果要实现一些逻辑操作,需要发包处理。参考网路的内容。不要在客户端写逻辑,因为这是没有用的,数据并不能同步到服务端。所以还是请注意。 这次我们就讲注册热键和怎么处理热键按下,这里就简单给客户端的玩家发送一个信息,这个信息并不是所有人能看到的。所以是展示用的处理。 我们先来了解几个类,帮助我们理解我们的代码。 首先是KeyMapping类。通过这个 KeyBinding 类,你可以方便地定义和管理模组中的自定义按键绑定。 ```java // 你可以通过这个构造方法来创建对应的按键,其中参数的含义是: // 自定义按键指定一个类别描述 description // 通过指定冲突上下文,你可以定义按键在什么情况下可以被触发。 IKeyConflictContext // inputType 键盘还是鼠标 // keyCode 哪一个按键 // category 按键在那个分类中。 public KeyMapping(String description, net.neoforged.neoforge.client.settings.IKeyConflictContext keyConflictContext, final InputConstants.Type inputType, final int keyCode, String category) { this(description, keyConflictContext, inputType.getOrCreate(keyCode), category); } ``` 当键盘按下了某个输入的按键时候触发的事件。在客户端的Forge总线上。我们在这里处理按键按下的逻辑 ```java public static class Key extends InputEvent ``` 该事件是用于用户注册自定义按键的事件,不可取消,没有结果,在客户端的MOD总线上触发。 ```java public class RegisterKeyMappingsEvent extends Event implements IModBusEvent ``` 好了介绍完了我们来看我们的代码把。 ```java // public class KeyBinding { // 我们添加一个我们自己的分类 public static final String KEY_CATEGORY_EXAMPLE_MOD = "key.category.example_mod"; // 我们添加一个按键的描述,是可以被语言化文件处理的。 public static final String KEY_DRINK_WATER = "key.example_mod.drink_water"; // 使用KeyMapping创建一个我们自己的热键 // 参数含义已经在上面介绍了。 public static final KeyMapping DRINKING_KEY = new KeyMapping(KEY_CATEGORY_EXAMPLE_MOD, KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_O,KEY_DRINK_WATER); } ``` 注册热键 ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class ModClientEventHandler { @SubscribeEvent public static void onKeyRegister(RegisterKeyMappingsEvent event) { event.register(KeyBinding.DRINKING_KEY); } } ``` 添加热键按下的逻辑 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,value = Dist.CLIENT) public class ForgeClientEventHandler { @SubscribeEvent public static void onKeyInput(InputEvent.Key event) { // 如果我们的热键被按下了,那么就给玩家发送一个消息press the key // 注意这里是客户端发送的,所以并不是所有玩家都能看到的。 if(KeyBinding.DRINKING_KEY.consumeClick()){ Minecraft.getInstance().player.sendSystemMessage(Component.literal("Press the key!")); } } } ``` 到游戏中按下o键看看把 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_39_RegisterKey.md description: 39 注册热键 相关教程 --- # 注册热键 这次我们来说怎么给我们的mod添加一个按键,按下按键处理按键事件。 不过提前要说明的一点就是按键的注册和触发的所有内容都是在客户端的,所以如果要实现一些逻辑操作,需要发包处理。参考网路的内容。不要在客户端写逻辑,因为这是没有用的,数据并不能同步到服务端。所以还是请注意。 这次我们就讲注册热键和怎么处理热键按下,这里就简单给客户端的玩家发送一个信息,这个信息并不是所有人能看到的。所以是展示用的处理。 我们先来了解几个类,帮助我们理解我们的代码。 首先是KeyMapping类。通过这个 KeyBinding 类,你可以方便地定义和管理模组中的自定义按键绑定。 ```java // 你可以通过这个构造方法来创建对应的按键,其中参数的含义是: // 自定义按键指定一个类别描述 description // 通过指定冲突上下文,你可以定义按键在什么情况下可以被触发。 IKeyConflictContext // inputType 键盘还是鼠标 // keyCode 哪一个按键 // category 按键在那个分类中。 public KeyMapping(String description, net.neoforged.neoforge.client.settings.IKeyConflictContext keyConflictContext, final InputConstants.Type inputType, final int keyCode, String category) { this(description, keyConflictContext, inputType.getOrCreate(keyCode), category); } ``` 当键盘按下了某个输入的按键时候触发的事件。在客户端的Forge总线上。我们在这里处理按键按下的逻辑 ```java public static class Key extends InputEvent ``` 该事件是用于用户注册自定义按键的事件,不可取消,没有结果,在客户端的MOD总线上触发。 ```java public class RegisterKeyMappingsEvent extends Event implements IModBusEvent ``` 好了介绍完了我们来看我们的代码把。 ```java // public class KeyBinding { // 我们添加一个我们自己的分类 public static final String KEY_CATEGORY_EXAMPLE_MOD = "key.category.example_mod"; // 我们添加一个按键的描述,是可以被语言化文件处理的。 public static final String KEY_DRINK_WATER = "key.example_mod.drink_water"; // 使用KeyMapping创建一个我们自己的热键 // 参数含义已经在上面介绍了。 public static final KeyMapping DRINKING_KEY = new KeyMapping(KEY_CATEGORY_EXAMPLE_MOD, KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_O,KEY_DRINK_WATER); } ``` 注册热键 ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class ModClientEventHandler { @SubscribeEvent public static void onKeyRegister(RegisterKeyMappingsEvent event) { event.register(KeyBinding.DRINKING_KEY); } } ``` 添加热键按下的逻辑 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,value = Dist.CLIENT) public class ForgeClientEventHandler { @SubscribeEvent public static void onKeyInput(InputEvent.Key event) { // 如果我们的热键被按下了,那么就给玩家发送一个消息press the key // 注意这里是客户端发送的,所以并不是所有玩家都能看到的。 if(KeyBinding.DRINKING_KEY.consumeClick()){ Minecraft.getInstance().player.sendSystemMessage(Component.literal("Press the key!")); } } } ``` 到游戏中按下o键看看把 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/block.md --- # 4 方块注册和属性修改 ## 一、方块注册 ### 1、事件监听 通过向`StartupEvents.registry`事件传入不同的参数,其可用于在启动脚本中注册游戏中的新元素,如方块、物品、声音、药效等,在这里,我们传入`"block"`来进行与方块有关的修改: ```js StartupEvents.registry("block",event=>{ // 脚本 }) ``` 显然,方块注册脚本应该放于StartupEvents脚本文件夹中。 ### 2、事件方法 | 方法 | 描述 | 返回值 | | ---------------------------------- | -------- | ------------ | | create(字符串 方块ID, 字符串 类型) | 注册方块 | BlockBuilder | | create(字符串 方块ID) | 注册方块 | BlockBuilder | 其中,“类型”参数支持以下字符串: | 值 | 描述 | 备注 | | ------------------------- | -------------- | -------------------------------------- | | "basic" | 基础方块类型 | - | | "detector" | 检测方块 | 使用说明详见后文detectorId(id: string) | | "slab" | 台阶方块 | 预设模型 | | "stairs" | 楼梯方块 | 预设模型 | | "fence" | 栅栏方块 | 预设模型 | | "fence\_gate" | 栅栏门方块 | 预设模型 | | "wall" | 墙方块 | 预设模型 | | "wooden\_pressure\_plate" | 木质压力板 | 预设模型和红石特性 | | "stone\_pressure\_plate" | 石质压力板 | 预设模型和红石特性 | | "wooden\_button" | 木质按钮 | 预设模型,红石特性,其物品可用作燃料 | | "stone\_button" | 石质按钮 | 预设模型,红石特性,其物品不可用作燃料 | | "falling" | 下坠方块 | - | | "crop" | 农作物方块 | 使用说明详见后文 | | "cardinal" | 黛玉朝向的方块 | 例如讲桌、熔炉方块等 | 通过设定自定义方块类型,你可以便捷地指定其模型或为其添加特殊功能。 ### 3、BlockBuilder | 方法 | 描述 | 备注 | 对应属性默认值 | | -------------------------------------------------------------------------------- | ------------------------ | ------------------------------------------------ | -------------- | | **🧾 方块属性类** | - | - | - | | property(BlockProperties 属性) | 设置方块属性 | 🔎 形如`BlockProperties.WATERLOGGED` | - | | redstoneConductor(布尔值 b) | 是否为红石信号导体 | - | true | | displayName(字符串 名称) | 设置方块名称 | - | - | | tagBoth(命名空间 标签) | 为物品和方块同时添加标签 | - | - | | tagBlock(命名空间 标签) | 只为方块添加标签 | - | - | | tagItem(命名空间 标签) | 只为物品添加标签 | - | - | | unbreakable() | 设置为无法被破坏 | 将hardness设为-1,resistance设为Float.MAX\_VALUE | - | | resistance(float 抗性) | 设置方块抗性 | - | 3F | | hardness(float 硬度) | | | | | material(MaterialJS 材质) | 设置方块材质 | \[1] | "wood" | | randomTick(RandomTickCallbackJS 随机刻回调函数) | 设置随机刻事件 | \[2] | - | | lightLevel(float 强度) | 设置方块光照等级 | - | 0F | | noDrops() | 设置为破坏时不掉落自身 | 默认为掉落自身 | - | | noItem() | 设置为不注册方块对应物品 | - | - | | waterlogged() | 设置为含水方块 | - | false | | jumpFactor(float 倍率) | 设置跳跃高度倍率 | - | 1.0F | | speedFactor(float 倍率) | 设置速度倍率 | - | 1.0F | | slipperiness(float f) | 设置滑动倍率(μ) | - | 0.6F | | requiresTool(布尔值 f) | 设置是否需要对应工具破坏 | - | false | | noValidSpawns(布尔值 b) | 设置是否可以生成怪物 | - | - | | **🎞 方块外形类** | - | - | - | | box(double x0,double y0,double z0, double x1,double y1,double z1,布尔值 scale16) | 设置方块碰撞箱 | \[3] | - | | noCollision() | 设置为无碰撞箱 | - | - | | defaultTranslucent() | 默认半透明渲染方式 | 详见`4、方块渲染方式` | - | | defaultCutout() | 默认镂刻渲染方式 | 详见`4、方块渲染方式` | | | model(命名空间 模型目录) | 设置模型 | 形如"kubejs:blocks/wudji\_notfound" | null | | viewBlocking(布尔值 b) | - | 详见`4、方块渲染方式` | true | | notSolid() | 设置为非固体方块 | 详见`4、方块渲染方式` | false | | texture(字符串 id,字符串 材质) | 手动设置方块材质 | \[4] | - | | renderType(字符串 渲染类型) | 设置方块渲染类型 | \[5] | "basic" | \[1] ``` 以下为 MaterialJS 支持的值(Minecraft 1.19.4): "grass" | "spore_blossom" | "dripstone" | "slime" | "berry_bush" | "ice" | "gilded_blackstone" | "small_amethyst_bud" | "amethyst_cluster" | "mud" | "amethyst" | "dragon_egg" | "packed_mud" | "crop" | "anvil" | "dirt" | "nether_sprouts" | "powder_snow" | "air" | "pointed_dripstone" | "muddy_mangrove_roots" | "lava" | "chain" | "sculk_sensor" | "leaves" | "clay" | "netherrack" | "medium_amethyst_bud" | "basalt" | "portal" | "mud_bricks" | "soul_soil" | "mangrove_roots" | "big_dripleaf" | "sculk_catalyst" | "bone" | "vine" | "web" | "polished_deepslate" | "coral" | "weeping_vines" | "plant" | "sculk_shrieker" | "large_amethyst_bud" | "explosive" | "copper" | "roots" | "ancient_debris" | "netherite" | "snow" | "moss_carpet" | "sculk_vein" | "stone" | "sculk" | "glow_lichen" | "hanging_roots" | "cake" | "nether_wart" | MaterialJS | "froglight" | "honey" | "small_dripleaf" | "kelp" | "nether_ore" | "sand" | "frogspawn" | "water" | "glass" | "azalea_leaves" | "tuff" | "metal" | "rooted_dirt" | "soul_sand" | "moss" | "deepslate" | "cave_vines" | "twisting_vines" | "deepslate_bricks" | "nylium" | "vegetable" | "azalea" | "scaffolding" | "flowering_azalea" | "sponge" | "lodestone" | "nether_bricks" | "lantern" | "candle" | "sea_grass" | "calcite" | "wart_block" | "nether_gold_ore" | "bamboo_sapling" | "wool" | "deepslate_tiles" | "bamboo" | "shroomlight" | "wood" | "hard_crop" ``` \[2]RandomTickCallbackJS 支持以下方法: | 方法 | 描述 | 返回值 | | ----------- | ------------ | --------------- | | getLevel() | 获取当前世界 | Level | | getServer() | 获取服务器 | MinecraftServer | 具体使用方法见后文示例。 \[3] ``` box(x0, y0, z0, x1, y1, z1, true) // 设置方块碰撞箱(0~16),默认值为0,0,0,16,16,16,true box(x0, y0, z0, x1, y1, z1, false) // 设置方块碰撞箱(0~1),默认值为0,0,0,1,1,1,false ``` \[4] ``` 常用ID有"particle"(粒子)和"all"(全部)。材质字符串 形如 "minecraft:block/tnt_bottom" ``` \[5] ``` 其支持值为:solid(实心), cutout(镂刻), translucent(半透明) ``` ### 4、方块渲染方式 在KubeJS中,常用`defaultTranslucent()`和`defaultCutout()`来设定半透明和镂刻方块渲染。如下图,从左到右设置的选项分别为`defaultTranslucent()`、`defaultCutout()`、`defaultCutout()`和`未设置渲染方式`设置: ![](https://m1.miaomc.cn/uploads/20230417_643cda7740387.png) 给出常见情况:**对于玻璃等半透明方块,应使用`defaultTranslucent()`进行设置;对于讲台等方块,应使用`defaultCutout()`进行设置** 从KubeJS源代码可以看出,[`defaultTranslucent()`](https://github.com/KubeJS-Mods/KubeJS/blob/a021e99f79084e4ad7f8be38ae12c3ef915f20ca/common/src/main/java/dev/latvian/mods/kubejs/block/BlockBuilder.java#L536)设置了以下选项: ``` notSolid() noValidSpawns(true) suffocating(false) viewBlocking(false) redstoneConductor(false) transparent(true) renderType("translucent") ``` [`defaultCutout()`](https://github.com/KubeJS-Mods/KubeJS/blob/a021e99f79084e4ad7f8be38ae12c3ef915f20ca/common/src/main/java/dev/latvian/mods/kubejs/block/BlockBuilder.java#L532)设置了以下选项 ``` notSolid() noValidSpawns(true) suffocating(false) viewBlocking(false) redstoneConductor(false) transparent(true) renderType("cutout") ``` ### 5、资源目录 对于方块注册来说,你需要将方块材质置于`assets\kubejs\textures\block\<方块ID>.png`,自定义模型(可选)放于`kubejs\assets\kubejs\models\block\<方块ID>.json`下。(即文件结构同资源包) ### 6、示例 ```js StartupEvents.registry("block",event => { // 使用栅栏门预设 event.create("kubejs_custom_block_fence_gate","fence_gate").displayName("Wudji的栅栏门") // 1/8 方块 event.create("kubejs_custom_block_box","cardinal").box(0,0,0,8,8,8).defaultCutout().displayName("0.125个方块") // 随机刻事件使用示例 // 效果为当kubejs_custom_block_random_tick方块被随机刻选中后再原地生成一个向下砸的火球 event.create("kubejs_custom_block_random_tick","basic").randomTick(cb =>{// RandomTickCallbackJS // 以下内容详见后续实体章节。 // 创建实体 let entity = cb.getLevel().createEntity("minecraft:fireball"); // 设置实体NBT, 坐标, 动量 entity.mergeFullNBT('{ExplosionPower:2}'); entity.setPosition(cb.block.getX(),cb.block.getY(),cb.block.getZ()) entity.setMotion(0, -20, 0); // 生成实体 entity.spawn() }).displayName("随机刻火球方块") }) ``` ## 二、方块属性修改 ### 1、事件监听 你需要监听`BlockEvents.modification`来对方块属性进行修改。 ```js BlockEvents.modification(event => { // 在此编写代码 }) ``` 显然,方块属性修改脚本应该放于StartupEvents脚本文件夹中。 ### 2、事件方法 | 方法 | 描述 | 返回值 | | ----------------------------------------------------------------- | ------------ | ------ | | modify(BlockStatePredicate 方块谓词, Consumer\ 修改事件) | 修改方块属性 | - | ### 3、谓词 `2、事件方法`中所述第一个参数`方块谓词`可以为以下值: | 类型 | 描述 | 示例 | | -------- | -------- | -------------------------------------------------- | | BlockTag | 方块标签 | `"forge:ores/gold"` `"minecraft:wooden_doors"` | | BlockID | 方块ID | `"minecraft:blue_shulker_box"` `"minecraft:glass"` | ### 4、修改方法 🔎 绝大多数的修改方法与方块注册中的方法类似。例如`speedFactor(float 倍率)`在方块修改中为`setSpeedFactor(float 倍率)`(详见ProbeJS)。 ### 5、修改示例 ```js BlockEvents.modification(event => { // 滑~ event.modify("minecraft:grass_block", event => { event.setSpeedFactor(5.0); }) }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/block.md --- # 4 方块注册和属性修改 ## 一、方块注册 ### 1、事件监听 通过向`StartupEvents.registry`事件传入不同的参数,其可用于在启动脚本中注册游戏中的新元素,如方块、物品、声音、药效等,在这里,我们传入`"block"`来进行与方块有关的修改: ```js StartupEvents.registry("block",event=>{ // 脚本 }) ``` 显然,方块注册脚本应该放于StartupEvents脚本文件夹中。 ### 2、事件方法 | 方法 | 描述 | 返回值 | | ---------------------------------- | -------- | ------------ | | create(字符串 方块ID, 字符串 类型) | 注册方块 | BlockBuilder | | create(字符串 方块ID) | 注册方块 | BlockBuilder | 其中,“类型”参数支持以下字符串: | 值 | 描述 | 备注 | | ------------------------- | -------------- | -------------------------------------- | | "basic" | 基础方块类型 | - | | "detector" | 检测方块 | 使用说明详见后文detectorId(id: string) | | "slab" | 台阶方块 | 预设模型 | | "stairs" | 楼梯方块 | 预设模型 | | "fence" | 栅栏方块 | 预设模型 | | "fence\_gate" | 栅栏门方块 | 预设模型 | | "wall" | 墙方块 | 预设模型 | | "wooden\_pressure\_plate" | 木质压力板 | 预设模型和红石特性 | | "stone\_pressure\_plate" | 石质压力板 | 预设模型和红石特性 | | "wooden\_button" | 木质按钮 | 预设模型,红石特性,其物品可用作燃料 | | "stone\_button" | 石质按钮 | 预设模型,红石特性,其物品不可用作燃料 | | "falling" | 下坠方块 | - | | "crop" | 农作物方块 | 使用说明详见后文 | | "cardinal" | 黛玉朝向的方块 | 例如讲桌、熔炉方块等 | 通过设定自定义方块类型,你可以便捷地指定其模型或为其添加特殊功能。 ### 3、BlockBuilder | 方法 | 描述 | 备注 | 对应属性默认值 | | -------------------------------------------------------------------------------- | ------------------------ | ------------------------------------------------ | -------------- | | **🧾 方块属性类** | - | - | - | | property(BlockProperties 属性) | 设置方块属性 | 🔎 形如`BlockProperties.WATERLOGGED` | - | | redstoneConductor(布尔值 b) | 是否为红石信号导体 | - | true | | displayName(字符串 名称) | 设置方块名称 | - | - | | tagBoth(命名空间 标签) | 为物品和方块同时添加标签 | - | - | | tagBlock(命名空间 标签) | 只为方块添加标签 | - | - | | tagItem(命名空间 标签) | 只为物品添加标签 | - | - | | unbreakable() | 设置为无法被破坏 | 将hardness设为-1,resistance设为Float.MAX\_VALUE | - | | resistance(float 抗性) | 设置方块抗性 | - | 3F | | hardness(float 硬度) | | | | | material(MaterialJS 材质) | 设置方块材质 | \[1] | "wood" | | randomTick(RandomTickCallbackJS 随机刻回调函数) | 设置随机刻事件 | \[2] | - | | lightLevel(float 强度) | 设置方块光照等级 | - | 0F | | noDrops() | 设置为破坏时不掉落自身 | 默认为掉落自身 | - | | noItem() | 设置为不注册方块对应物品 | - | - | | waterlogged() | 设置为含水方块 | - | false | | jumpFactor(float 倍率) | 设置跳跃高度倍率 | - | 1.0F | | speedFactor(float 倍率) | 设置速度倍率 | - | 1.0F | | slipperiness(float f) | 设置滑动倍率(μ) | - | 0.6F | | requiresTool(布尔值 f) | 设置是否需要对应工具破坏 | - | false | | noValidSpawns(布尔值 b) | 设置是否可以生成怪物 | - | - | | **🎞 方块外形类** | - | - | - | | box(double x0,double y0,double z0, double x1,double y1,double z1,布尔值 scale16) | 设置方块碰撞箱 | \[3] | - | | noCollision() | 设置为无碰撞箱 | - | - | | defaultTranslucent() | 默认半透明渲染方式 | 详见`4、方块渲染方式` | - | | defaultCutout() | 默认镂刻渲染方式 | 详见`4、方块渲染方式` | | | model(命名空间 模型目录) | 设置模型 | 形如"kubejs:blocks/wudji\_notfound" | null | | viewBlocking(布尔值 b) | - | 详见`4、方块渲染方式` | true | | notSolid() | 设置为非固体方块 | 详见`4、方块渲染方式` | false | | texture(字符串 id,字符串 材质) | 手动设置方块材质 | \[4] | - | | renderType(字符串 渲染类型) | 设置方块渲染类型 | \[5] | "basic" | \[1] ``` 以下为 MaterialJS 支持的值(Minecraft 1.19.4): "grass" | "spore_blossom" | "dripstone" | "slime" | "berry_bush" | "ice" | "gilded_blackstone" | "small_amethyst_bud" | "amethyst_cluster" | "mud" | "amethyst" | "dragon_egg" | "packed_mud" | "crop" | "anvil" | "dirt" | "nether_sprouts" | "powder_snow" | "air" | "pointed_dripstone" | "muddy_mangrove_roots" | "lava" | "chain" | "sculk_sensor" | "leaves" | "clay" | "netherrack" | "medium_amethyst_bud" | "basalt" | "portal" | "mud_bricks" | "soul_soil" | "mangrove_roots" | "big_dripleaf" | "sculk_catalyst" | "bone" | "vine" | "web" | "polished_deepslate" | "coral" | "weeping_vines" | "plant" | "sculk_shrieker" | "large_amethyst_bud" | "explosive" | "copper" | "roots" | "ancient_debris" | "netherite" | "snow" | "moss_carpet" | "sculk_vein" | "stone" | "sculk" | "glow_lichen" | "hanging_roots" | "cake" | "nether_wart" | MaterialJS | "froglight" | "honey" | "small_dripleaf" | "kelp" | "nether_ore" | "sand" | "frogspawn" | "water" | "glass" | "azalea_leaves" | "tuff" | "metal" | "rooted_dirt" | "soul_sand" | "moss" | "deepslate" | "cave_vines" | "twisting_vines" | "deepslate_bricks" | "nylium" | "vegetable" | "azalea" | "scaffolding" | "flowering_azalea" | "sponge" | "lodestone" | "nether_bricks" | "lantern" | "candle" | "sea_grass" | "calcite" | "wart_block" | "nether_gold_ore" | "bamboo_sapling" | "wool" | "deepslate_tiles" | "bamboo" | "shroomlight" | "wood" | "hard_crop" ``` \[2]RandomTickCallbackJS 支持以下方法: | 方法 | 描述 | 返回值 | | ----------- | ------------ | --------------- | | getLevel() | 获取当前世界 | Level | | getServer() | 获取服务器 | MinecraftServer | 具体使用方法见后文示例。 \[3] ``` box(x0, y0, z0, x1, y1, z1, true) // 设置方块碰撞箱(0~16),默认值为0,0,0,16,16,16,true box(x0, y0, z0, x1, y1, z1, false) // 设置方块碰撞箱(0~1),默认值为0,0,0,1,1,1,false ``` \[4] ``` 常用ID有"particle"(粒子)和"all"(全部)。材质字符串 形如 "minecraft:block/tnt_bottom" ``` \[5] ``` 其支持值为:solid(实心), cutout(镂刻), translucent(半透明) ``` ### 4、方块渲染方式 在KubeJS中,常用`defaultTranslucent()`和`defaultCutout()`来设定半透明和镂刻方块渲染。如下图,从左到右设置的选项分别为`defaultTranslucent()`、`defaultCutout()`、`defaultCutout()`和`未设置渲染方式`设置: ![](https://m1.miaomc.cn/uploads/20230417_643cda7740387.png) 给出常见情况:**对于玻璃等半透明方块,应使用`defaultTranslucent()`进行设置;对于讲台等方块,应使用`defaultCutout()`进行设置** 从KubeJS源代码可以看出,[`defaultTranslucent()`](https://github.com/KubeJS-Mods/KubeJS/blob/a021e99f79084e4ad7f8be38ae12c3ef915f20ca/common/src/main/java/dev/latvian/mods/kubejs/block/BlockBuilder.java#L536)设置了以下选项: ``` notSolid() noValidSpawns(true) suffocating(false) viewBlocking(false) redstoneConductor(false) transparent(true) renderType("translucent") ``` [`defaultCutout()`](https://github.com/KubeJS-Mods/KubeJS/blob/a021e99f79084e4ad7f8be38ae12c3ef915f20ca/common/src/main/java/dev/latvian/mods/kubejs/block/BlockBuilder.java#L532)设置了以下选项 ``` notSolid() noValidSpawns(true) suffocating(false) viewBlocking(false) redstoneConductor(false) transparent(true) renderType("cutout") ``` ### 5、资源目录 对于方块注册来说,你需要将方块材质置于`assets\kubejs\textures\block\<方块ID>.png`,自定义模型(可选)放于`kubejs\assets\kubejs\models\block\<方块ID>.json`下。(即文件结构同资源包) ### 6、示例 ```js StartupEvents.registry("block",event => { // 使用栅栏门预设 event.create("kubejs_custom_block_fence_gate","fence_gate").displayName("Wudji的栅栏门") // 1/8 方块 event.create("kubejs_custom_block_box","cardinal").box(0,0,0,8,8,8).defaultCutout().displayName("0.125个方块") // 随机刻事件使用示例 // 效果为当kubejs_custom_block_random_tick方块被随机刻选中后再原地生成一个向下砸的火球 event.create("kubejs_custom_block_random_tick","basic").randomTick(cb =>{// RandomTickCallbackJS // 以下内容详见后续实体章节。 // 创建实体 let entity = cb.getLevel().createEntity("minecraft:fireball"); // 设置实体NBT, 坐标, 动量 entity.mergeFullNBT('{ExplosionPower:2}'); entity.setPosition(cb.block.getX(),cb.block.getY(),cb.block.getZ()) entity.setMotion(0, -20, 0); // 生成实体 entity.spawn() }).displayName("随机刻火球方块") }) ``` ## 二、方块属性修改 ### 1、事件监听 你需要监听`BlockEvents.modification`来对方块属性进行修改。 ```js BlockEvents.modification(event => { // 在此编写代码 }) ``` 显然,方块属性修改脚本应该放于StartupEvents脚本文件夹中。 ### 2、事件方法 | 方法 | 描述 | 返回值 | | ----------------------------------------------------------------- | ------------ | ------ | | modify(BlockStatePredicate 方块谓词, Consumer\ 修改事件) | 修改方块属性 | - | ### 3、谓词 `2、事件方法`中所述第一个参数`方块谓词`可以为以下值: | 类型 | 描述 | 示例 | | -------- | -------- | -------------------------------------------------- | | BlockTag | 方块标签 | `"forge:ores/gold"` `"minecraft:wooden_doors"` | | BlockID | 方块ID | `"minecraft:blue_shulker_box"` `"minecraft:glass"` | ### 4、修改方法 🔎 绝大多数的修改方法与方块注册中的方法类似。例如`speedFactor(float 倍率)`在方块修改中为`setSpeedFactor(float 倍率)`(详见ProbeJS)。 ### 5、修改示例 ```js BlockEvents.modification(event => { // 滑~ event.modify("minecraft:grass_block", event => { event.setSpeedFactor(5.0); }) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/feature1/loottable.md --- # 4 自定义Loot Table *** (本章参考了[Minecraft 原版模组入门教程 - 战利品表(作者ruhuasiyu)](https://zhangshenxing.gitee.io/vanillamodtutorial/#%E6%88%98%E5%88%A9%E5%93%81%E8%A1%A8)) KubeJS目前能修改全局、方块、实体、猫或村民礼物(村庄英雄Buff)、钓鱼、宝箱战利品表 基本的方法名称如下 | 事件ID | 覆盖原有战利品表的方法名称 | 修改战利品表的方法名称 | | -------------------- | ------------- | ------------ | | generic.loot\_tables | addGeneric | modify | | block.loot\_tables | addBlock | modifyBlock | | entity.loot\_tables | addEntity | modifyEntity | | gift.loot\_tables | addGift | modify | | fishing.loot\_tables | addFishing | modify | | chest.loot\_tables | addChest | modify | 下面就下面5种的战利品表进行讲解。 *** ## 0、基本格式 ``` onEvent('事件ID', event => { event.覆盖/修改原有战利品表方法名称('物品/实体注册名', table => { //修改内容 } //table.clearPools()//清空所有随机池 //table.clearConditions()//清空条件 //table.clearFunctions()//清空函数 }) ``` ## 1、方块战利品表(block.loot\_tables) 针对方块战利品表,KubeJS提供了很简单的添加单方块掉落物的方法。 ``` onEvent('block.loot_tables', event => { event.addSimpleBlock('minecraft:dirt', 'minecraft:red_sand')//覆盖泥土的战利品表并使泥土掉落红沙 event.addSimpleBlock('minecraft:oak_leaves')//覆盖橡树树叶的战利品表并使橡树树叶在破坏时掉落自身 }) ``` 如果你想要实现更复杂的修改,你需要使用随机池。KubeJS提供了一些简写方法,具体见下方例子。 ``` onEvent('block.loot_tables', event => { event.addBlock('minecraft:dirt', table => { table.addPool(pool => {//新建随机池(覆盖原有的) pool.rolls = 1 // 固定值 // pool.rolls = [4, 6] // 也可写为 {min: 4, max: 6} // 抽奖次数∈[4,6] // pool.rolls = {n: 4, p: 0.3} // 二项分布 //以上两种值的表达方式为值提供器 pool.addItem('minecraft:dirt') //pool.addItem('minecraft:dirt', 40) // 此处40为权重 //pool.addItem('minecraft:dirt', 40, [4, 8]) // 物品个数∈[4,8] // pool.addCondition({json}) //json格式,抽取该物品需要满足的条件,原版数据包格式(具体使用情景详见14.1) // pool.addEntry({json})//json格式,项目类别 }) }) }) ``` 注:修改方块战利品表(而不是覆盖)的示例详见14.1 ## 2、实体战利品表(entity.loot\_tables) ``` onEvent('entity.loot_tables', event => { // 覆盖僵尸的战利品表,掉落5个物品(25%概率为胡萝卜,75%为苹果) event.addEntity('minecraft:zombie', table => { table.addPool(pool => { pool.rolls = 5//抽5次奖 pool.addItem('minecraft:carrot', 1) pool.addItem('minecraft:apple', 3) }) }) event.modifyEntity('minecraft:pig', table => { table.addPool(pool => { // 修改猪的战利品表,使在被玩家杀死时多掉落一个泥土 // 注:只是修改战利品表。并不会覆盖原有的掉落生/熟猪肉的战利品表 pool.addItem('minecraft:dirt') pool.killedByPlayer() }) }) }) ``` 注:对于`gift.loot_tables`、`fishing.loot_tables`、`chest.loot_tables`,只能使用`addJson(物品注册名, json原版格式)`来进行修改 ## 3、常用表达方式 ``` /* 在新建/修改随机池时,还有以下表达方式可用: 以下值提供器与上述的表示方法相同 pool.enchantRandomly(附魔注册名)// 随机附魔 pool.enchantWithLevels(附魔等级(值提供器), 是否为宝藏(布尔值))// 指定等级和是否为宝藏的随机附魔 pool.name(name as Text)// 指定名称 pool.damage(掉落物耐久值(值提供器))// 修改掉落物耐久值 pool.randomChance(掉落概率(double型))// 修改掉落概率 pool.randomChanceWithLooting(掉落概率(double型), 倍率(double型))// 使用该表达方式时,抢夺或时运会对掉落物有影响 pool.furnaceSmelt()// 使当前随机池掉落的物品掉落形式为经过熔炉烧炼(自动烧炼) pool.killedByPlayer()// 添加条件:实体被玩家杀死才会掉落 pool.survivesExplosion()// 添加条件:方块被爆炸破坏时依旧掉落 //以上方法也可以用于对Loot Table直接修改(例如table.name(name as Text)),具体使用见14.1 */ ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/feature1/loottable.md --- # 4 自定义Loot Table *** (本章参考了[Minecraft 原版模组入门教程 - 战利品表(作者ruhuasiyu)](https://zhangshenxing.gitee.io/vanillamodtutorial/#%E6%88%98%E5%88%A9%E5%93%81%E8%A1%A8)) KubeJS目前能修改全局、方块、实体、猫或村民礼物(村庄英雄Buff)、钓鱼、宝箱战利品表 基本的方法名称如下 | 事件ID | 覆盖原有战利品表的方法名称 | 修改战利品表的方法名称 | | -------------------- | ------------- | ------------ | | generic.loot\_tables | addGeneric | modify | | block.loot\_tables | addBlock | modifyBlock | | entity.loot\_tables | addEntity | modifyEntity | | gift.loot\_tables | addGift | modify | | fishing.loot\_tables | addFishing | modify | | chest.loot\_tables | addChest | modify | 下面就下面5种的战利品表进行讲解。 *** ## 0、基本格式 ``` onEvent('事件ID', event => { event.覆盖/修改原有战利品表方法名称('物品/实体注册名', table => { //修改内容 } //table.clearPools()//清空所有随机池 //table.clearConditions()//清空条件 //table.clearFunctions()//清空函数 }) ``` ## 1、方块战利品表(block.loot\_tables) 针对方块战利品表,KubeJS提供了很简单的添加单方块掉落物的方法。 ``` onEvent('block.loot_tables', event => { event.addSimpleBlock('minecraft:dirt', 'minecraft:red_sand')//覆盖泥土的战利品表并使泥土掉落红沙 event.addSimpleBlock('minecraft:oak_leaves')//覆盖橡树树叶的战利品表并使橡树树叶在破坏时掉落自身 }) ``` 如果你想要实现更复杂的修改,你需要使用随机池。KubeJS提供了一些简写方法,具体见下方例子。 ``` onEvent('block.loot_tables', event => { event.addBlock('minecraft:dirt', table => { table.addPool(pool => {//新建随机池(覆盖原有的) pool.rolls = 1 // 固定值 // pool.rolls = [4, 6] // 也可写为 {min: 4, max: 6} // 抽奖次数∈[4,6] // pool.rolls = {n: 4, p: 0.3} // 二项分布 //以上两种值的表达方式为值提供器 pool.addItem('minecraft:dirt') //pool.addItem('minecraft:dirt', 40) // 此处40为权重 //pool.addItem('minecraft:dirt', 40, [4, 8]) // 物品个数∈[4,8] // pool.addCondition({json}) //json格式,抽取该物品需要满足的条件,原版数据包格式(具体使用情景详见14.1) // pool.addEntry({json})//json格式,项目类别 }) }) }) ``` 注:修改方块战利品表(而不是覆盖)的示例详见14.1 ## 2、实体战利品表(entity.loot\_tables) ``` onEvent('entity.loot_tables', event => { // 覆盖僵尸的战利品表,掉落5个物品(25%概率为胡萝卜,75%为苹果) event.addEntity('minecraft:zombie', table => { table.addPool(pool => { pool.rolls = 5//抽5次奖 pool.addItem('minecraft:carrot', 1) pool.addItem('minecraft:apple', 3) }) }) event.modifyEntity('minecraft:pig', table => { table.addPool(pool => { // 修改猪的战利品表,使在被玩家杀死时多掉落一个泥土 // 注:只是修改战利品表。并不会覆盖原有的掉落生/熟猪肉的战利品表 pool.addItem('minecraft:dirt') pool.killedByPlayer() }) }) }) ``` 注:对于`gift.loot_tables`、`fishing.loot_tables`、`chest.loot_tables`,只能使用`addJson(物品注册名, json原版格式)`来进行修改 ## 3、常用表达方式 ``` /* 在新建/修改随机池时,还有以下表达方式可用: 以下值提供器与上述的表示方法相同 pool.enchantRandomly(附魔注册名)// 随机附魔 pool.enchantWithLevels(附魔等级(值提供器), 是否为宝藏(布尔值))// 指定等级和是否为宝藏的随机附魔 pool.name(name as Text)// 指定名称 pool.damage(掉落物耐久值(值提供器))// 修改掉落物耐久值 pool.randomChance(掉落概率(double型))// 修改掉落概率 pool.randomChanceWithLooting(掉落概率(double型), 倍率(double型))// 使用该表达方式时,抢夺或时运会对掉落物有影响 pool.furnaceSmelt()// 使当前随机池掉落的物品掉落形式为经过熔炉烧炼(自动烧炼) pool.killedByPlayer()// 添加条件:实体被玩家杀死才会掉落 pool.survivesExplosion()// 添加条件:方块被爆炸破坏时依旧掉落 //以上方法也可以用于对Loot Table直接修改(例如table.name(name as Text)),具体使用见14.1 */ ``` --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_40_Fluid.md description: 40 流体 相关教程 --- # 参考 https://boson.v2mcdev.com/fluid/firstfluid.html ## 流体 这次我们来说怎么创建流体。 在游戏中你看到的流体一把是在世界中存在的流动的和源头的两中,不过还有就是机器中处理的,桶这样的形势存在的。 当在桶内装的时候,是一个BucketItem的类,而在世界中不断的流动的就是一种特殊的方块,你可以尝试删除某个 流体的贴图你会发发现都是黑紫块了。 说明流体在世界中就是一种特殊的方块,不过这种特殊的方块有着类似方块和方块实体的这样的特殊关系,方块和对应的流体关联起来。 对于流体有两种状态我们需要创建这两种装填的流体,流动的flow,和源头source以及流体的type,这三个类是我们需要处理的,以及将创建的流体和对于的方块做出关联。 我们先来看下流体的type怎么处理,由于原版的fluidtype直接使用不方便,这里就给他包裹一层,然后我们使用我们创建的这个basefluidtype创建流体的type。 下面我们来看看这个类都提供了什么功能,和流体的那些属性有关。 ```java // 用于定义了流体类型 public class BaseFluidType extends FluidType { // 定义了源source的纹理图片,流动的纹理图片,以及流体覆盖层的图片(指的是颜色,例如水的蓝色纹理,岩浆的红色纹理,你可以到原版对应的位置看看是什么图片就知道了) private final ResourceLocation stillTexture; private final ResourceLocation flowingTexture; private final ResourceLocation overlayTexture; // 流体的着色颜色 private final int tintColor; // 从流体中看外面的雾的颜色 private final Vector3f fogColor; //构造函数 public BaseFluidType(final ResourceLocation stillTexture, final ResourceLocation flowingTexture, final ResourceLocation overlayTexture, final int tintColor, final Vector3f fogColor, final Properties properties) { super(properties); this.stillTexture = stillTexture; this.flowingTexture = flowingTexture; this.overlayTexture = overlayTexture; this.tintColor = tintColor; this.fogColor = fogColor; } // 对应的get函数 public ResourceLocation getStillTexture() { return stillTexture; } public ResourceLocation getFlowingTexture() { return flowingTexture; } public int getTintColor() { return tintColor; } public ResourceLocation getOverlayTexture() { return overlayTexture; } public Vector3f getFogColor() { return fogColor; } // 对于我们的几个纹理,如果如果想生效的话,就需要重写这个方法,在对于的方法将我们的RL的资源定位的图片返回。 @Override public void initializeClient(Consumer consumer) { consumer.accept(new IClientFluidTypeExtensions() { @Override public ResourceLocation getStillTexture() { return stillTexture; } @Override public ResourceLocation getFlowingTexture() { return flowingTexture; } @Override public @Nullable ResourceLocation getOverlayTexture() { return overlayTexture; } @Override public int getTintColor() { return tintColor; } // 修改从流体中看雾的颜色 @Override public @NotNull Vector3f modifyFogColor(Camera camera, float partialTick, ClientLevel level, int renderDistance, float darkenWorldAmount, Vector3f fluidFogColor) { return fogColor; } // 液体中的能见度 或者 说雾的范围 @Override public void modifyFogRender(Camera camera, FogRenderer.FogMode mode, float renderDistance, float partialTick, float nearDistance, float farDistance, FogShape shape) { RenderSystem.setShaderFogStart(1f); RenderSystem.setShaderFogEnd(6f); // distance when the fog starts } }); } } ``` 好了我们可以看到流体类型fluidType类和流体的颜色,贴图等属性有关。 我们接下来看怎么使用BaseFluidType创建我们的流体类型。并注册到总线。 ```java public class ModFluidType { // 图片的位置,这里的source和flow,直接使用的原版的,所以没有第一个参数传入modid。 public static final ResourceLocation WATER_STILL_RL = new ResourceLocation("block/water_still"); public static final ResourceLocation WATER_FLOWING_RL = new ResourceLocation("block/water_flow"); // 这里的流体的overlay的图片使用是自己的图片,直接原版的water修改的。 public static final ResourceLocation MY_FLUID_RL = new ResourceLocation(ExampleMod.MODID, "misc/my_fluid"); // 怎么获得流体类型FluidType的注册器,有一点特殊,不是直接在Registires类下,而是在NeoForgeRegistries.Keys下 // 这应该是因为原版并没有流体类型FluidType这样的概念。 public static final DeferredRegister FLUID_TYPES = DeferredRegister.create(NeoForgeRegistries.Keys.FLUID_TYPES, ExampleMod.MODID); // 我们看到使用了register这个方法,这个方法是我们自己写的。 // 对于register在下面介绍,我们来看参数 // 第一参数name,没什么好说的,第二个参数是对流体的类型的属性进行一些设置 FluidType.Properties这个类就是对属性的一些设置。 // create返回一个properties实例,lightlevel设置亮度等级2,density设置密度,viscosity设置粘度,sound设置流体声音。 // 这些数值是直接复制的水的,对于其中的一些具体的效果,就自己调试看看效果把。也可以大家弹幕评论补充 public static final Supplier MY_FLUID_TYPE = register("my_fluid", FluidType.Properties.create().lightLevel(2).density(15).viscosity(5).sound(SoundAction.get("drink"), SoundEvents.HONEY_DRINK)); // 这个是我们自己写的注册的方法 // 并没有什么特殊的,直接返回了new baseFludType的supplier方法。 private static Supplier register(String name, FluidType.Properties properties) { return FLUID_TYPES.register(name, () -> new BaseFluidType(WATER_STILL_RL, WATER_FLOWING_RL, MY_FLUID_RL, 0xA1E038D0, new Vector3f(224f / 255f, 56f / 255f, 208f / 255f), properties)); } // 记得注册到总线 public static void register(IEventBus eventBus) { FLUID_TYPES.register(eventBus); } } ``` 好了,流体类型说完了,我们来看怎么处理流体了,包含了流体的source和flow,其实source和flow都是继承FlowingFluid类的。不过Neoforge为我们提供了BaseFlowingFluid类以及Source和Flowing子类帮助我们创建对应的实例。 ```java public class ModFluids { // 流体注册器 public static final DeferredRegister FLUIDS = DeferredRegister.create(Registries.FLUID, ExampleMod.MODID); // 注册对应流体的source和flow,使用NeoForge提供的BaseFlowingFluid来注册 // 其中source和flow都需要填入一个参数,这个参数是流体的属性,在下面定义 public static Supplier MY_SOURCE_FLUID_BLOCK = FLUIDS.register("my_fluid", () -> new BaseFlowingFluid.Source(ModFluids.MY_FLUID_PROPERTIES)); public static Supplier MY_FLOWING_FLUID_BLOCK = FLUIDS.register("my_fluid_flow", () -> new BaseFlowingFluid.Flowing(ModFluids.MY_FLUID_PROPERTIES)); // 定义流体的属性 // 这个流体的属性要传入的内容比较多,我们挨个介绍,我们使用了BaseFlowingFluid的Properties内部类创建对应的Properties,其中第一个参数是对应的流体的类体类型FluidType,然后第二个参数是对应的source流体,第三个参数是flow流体,都是我们刚刚写过的,看起来比较绕,大家自己理清下关系。 // 通过bucket这个设置流体和对应的流体桶的绑定,等会我们注册这个bucketitem // 通过block绑定对应的流体和方块的绑定,这个方块等会我们注册。 // slopeFindDistance寻找斜坡的距离 // levelDecreasePerBlock 每个方块流体的减少量。 // 后两个数据是用于流体的流动的,主要是斜坡时候优先流,不会扩散。 // 以及流体最多能流多远,例如原版的水是8格 // 可以自己调试这几个数值试试,也可以去wiki看看具体的含义。 private static final BaseFlowingFluid.Properties MY_FLUID_PROPERTIES = new BaseFlowingFluid.Properties(ModFluidType.MY_FLUID_TYPE, ModFluids.MY_SOURCE_FLUID_BLOCK, ModFluids.MY_FLOWING_FLUID_BLOCK).bucket(ModItems.MY_FLUID_BUCKET).slopeFindDistance(2).levelDecreasePerBlock(2).block(ModBlocks.MY_FLUID_BLOCK); // 别忘记注册到总线 public static void register(IEventBus eventBus) { FLUIDS.register(eventBus); } } ``` 下面我们来看对应的流体的方块和物品。 先看方块。 ```java // 注册方块,不过我们没有使用我么自己的那个方法,而是直接使用BLOCK的register方法,主要是我们不需要提供对应的item。因为我们还要注册对应的bucketitem。 public static final Supplier MY_FLUID_BLOCK = BLOCKS.register("my_fluid_block", ()->new LiquidBlock(ModFluids.MY_SOURCE_FLUID_BLOCK,BlockBehaviour.Properties.ofFullCopy(Blocks.WATER))); ``` 流体桶 ```java // 流体桶,craftRemainder表示合成之后保留桶 public static final Supplier MY_FLUID_BUCKET = register("my_fluid_bucket", ()->new BucketItem(ModFluids.MY_SOURCE_FLUID_BLOCK,new Item.Properties().craftRemainder(Items.BUCKET).stacksTo(1))); ``` 流体的渲染,由于我们的流体是一个半透明的材质,所以指定流体的渲染为:translucent 对于source和flow都需要指定。 ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class ModClientEventHandler { @SubscribeEvent public static void onClientEvent(FMLClientSetupEvent event){ event.enqueueWork(()->{ //fluid ItemBlockRenderTypes.setRenderLayer(ModFluids.MY_SOURCE_FLUID_BLOCK.get(), RenderType.translucent()); ItemBlockRenderTypes.setRenderLayer(ModFluids.MY_FLOWING_FLUID_BLOCK.get(), RenderType.translucent()); }); } ``` 好了,别忘记了注册到总线,然后你就可以进入游戏中看看了。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_40_Fluid.md description: 40 流体 相关教程 --- # 参考 https://boson.v2mcdev.com/fluid/firstfluid.html ## 流体 这次我们来说怎么创建流体。 在游戏中你看到的流体一把是在世界中存在的流动的和源头的两中,不过还有就是机器中处理的,桶这样的形势存在的。 当在桶内装的时候,是一个BucketItem的类,而在世界中不断的流动的就是一种特殊的方块,你可以尝试删除某个 流体的贴图你会发发现都是黑紫块了。 说明流体在世界中就是一种特殊的方块,不过这种特殊的方块有着类似方块和方块实体的这样的特殊关系,方块和对应的流体关联起来。 对于流体有两种状态我们需要创建这两种装填的流体,流动的flow,和源头source以及流体的type,这三个类是我们需要处理的,以及将创建的流体和对于的方块做出关联。 我们先来看下流体的type怎么处理,由于原版的fluidtype直接使用不方便,这里就给他包裹一层,然后我们使用我们创建的这个basefluidtype创建流体的type。 下面我们来看看这个类都提供了什么功能,和流体的那些属性有关。 ```java // 用于定义了流体类型 public class BaseFluidType extends FluidType { // 定义了源source的纹理图片,流动的纹理图片,以及流体覆盖层的图片(指的是颜色,例如水的蓝色纹理,岩浆的红色纹理,你可以到原版对应的位置看看是什么图片就知道了) private final ResourceLocation stillTexture; private final ResourceLocation flowingTexture; private final ResourceLocation overlayTexture; // 流体的着色颜色 private final int tintColor; // 从流体中看外面的雾的颜色 private final Vector3f fogColor; //构造函数 public BaseFluidType(final ResourceLocation stillTexture, final ResourceLocation flowingTexture, final ResourceLocation overlayTexture, final int tintColor, final Vector3f fogColor, final Properties properties) { super(properties); this.stillTexture = stillTexture; this.flowingTexture = flowingTexture; this.overlayTexture = overlayTexture; this.tintColor = tintColor; this.fogColor = fogColor; } // 对应的get函数 public ResourceLocation getStillTexture() { return stillTexture; } public ResourceLocation getFlowingTexture() { return flowingTexture; } public int getTintColor() { return tintColor; } public ResourceLocation getOverlayTexture() { return overlayTexture; } public Vector3f getFogColor() { return fogColor; } // 对于我们的几个纹理,如果如果想生效的话,就需要重写这个方法,在对于的方法将我们的RL的资源定位的图片返回。 @Override public void initializeClient(Consumer consumer) { consumer.accept(new IClientFluidTypeExtensions() { @Override public ResourceLocation getStillTexture() { return stillTexture; } @Override public ResourceLocation getFlowingTexture() { return flowingTexture; } @Override public @Nullable ResourceLocation getOverlayTexture() { return overlayTexture; } @Override public int getTintColor() { return tintColor; } // 修改从流体中看雾的颜色 @Override public @NotNull Vector3f modifyFogColor(Camera camera, float partialTick, ClientLevel level, int renderDistance, float darkenWorldAmount, Vector3f fluidFogColor) { return fogColor; } // 液体中的能见度 或者 说雾的范围 @Override public void modifyFogRender(Camera camera, FogRenderer.FogMode mode, float renderDistance, float partialTick, float nearDistance, float farDistance, FogShape shape) { RenderSystem.setShaderFogStart(1f); RenderSystem.setShaderFogEnd(6f); // distance when the fog starts } }); } } ``` 好了我们可以看到流体类型fluidType类和流体的颜色,贴图等属性有关。 我们接下来看怎么使用BaseFluidType创建我们的流体类型。并注册到总线。 ```java public class ModFluidType { // 图片的位置,这里的source和flow,直接使用的原版的,所以没有第一个参数传入modid。 public static final ResourceLocation WATER_STILL_RL = new ResourceLocation("block/water_still"); public static final ResourceLocation WATER_FLOWING_RL = new ResourceLocation("block/water_flow"); // 这里的流体的overlay的图片使用是自己的图片,直接原版的water修改的。 public static final ResourceLocation MY_FLUID_RL = new ResourceLocation(ExampleMod.MODID, "misc/my_fluid"); // 怎么获得流体类型FluidType的注册器,有一点特殊,不是直接在Registires类下,而是在NeoForgeRegistries.Keys下 // 这应该是因为原版并没有流体类型FluidType这样的概念。 public static final DeferredRegister FLUID_TYPES = DeferredRegister.create(NeoForgeRegistries.Keys.FLUID_TYPES, ExampleMod.MODID); // 我们看到使用了register这个方法,这个方法是我们自己写的。 // 对于register在下面介绍,我们来看参数 // 第一参数name,没什么好说的,第二个参数是对流体的类型的属性进行一些设置 FluidType.Properties这个类就是对属性的一些设置。 // create返回一个properties实例,lightlevel设置亮度等级2,density设置密度,viscosity设置粘度,sound设置流体声音。 // 这些数值是直接复制的水的,对于其中的一些具体的效果,就自己调试看看效果把。也可以大家弹幕评论补充 public static final Supplier MY_FLUID_TYPE = register("my_fluid", FluidType.Properties.create().lightLevel(2).density(15).viscosity(5).sound(SoundAction.get("drink"), SoundEvents.HONEY_DRINK)); // 这个是我们自己写的注册的方法 // 并没有什么特殊的,直接返回了new baseFludType的supplier方法。 private static Supplier register(String name, FluidType.Properties properties) { return FLUID_TYPES.register(name, () -> new BaseFluidType(WATER_STILL_RL, WATER_FLOWING_RL, MY_FLUID_RL, 0xA1E038D0, new Vector3f(224f / 255f, 56f / 255f, 208f / 255f), properties)); } // 记得注册到总线 public static void register(IEventBus eventBus) { FLUID_TYPES.register(eventBus); } } ``` 好了,流体类型说完了,我们来看怎么处理流体了,包含了流体的source和flow,其实source和flow都是继承FlowingFluid类的。不过Neoforge为我们提供了BaseFlowingFluid类以及Source和Flowing子类帮助我们创建对应的实例。 ```java public class ModFluids { // 流体注册器 public static final DeferredRegister FLUIDS = DeferredRegister.create(Registries.FLUID, ExampleMod.MODID); // 注册对应流体的source和flow,使用NeoForge提供的BaseFlowingFluid来注册 // 其中source和flow都需要填入一个参数,这个参数是流体的属性,在下面定义 public static Supplier MY_SOURCE_FLUID_BLOCK = FLUIDS.register("my_fluid", () -> new BaseFlowingFluid.Source(ModFluids.MY_FLUID_PROPERTIES)); public static Supplier MY_FLOWING_FLUID_BLOCK = FLUIDS.register("my_fluid_flow", () -> new BaseFlowingFluid.Flowing(ModFluids.MY_FLUID_PROPERTIES)); // 定义流体的属性 // 这个流体的属性要传入的内容比较多,我们挨个介绍,我们使用了BaseFlowingFluid的Properties内部类创建对应的Properties,其中第一个参数是对应的流体的类体类型FluidType,然后第二个参数是对应的source流体,第三个参数是flow流体,都是我们刚刚写过的,看起来比较绕,大家自己理清下关系。 // 通过bucket这个设置流体和对应的流体桶的绑定,等会我们注册这个bucketitem // 通过block绑定对应的流体和方块的绑定,这个方块等会我们注册。 // slopeFindDistance寻找斜坡的距离 // levelDecreasePerBlock 每个方块流体的减少量。 // 后两个数据是用于流体的流动的,主要是斜坡时候优先流,不会扩散。 // 以及流体最多能流多远,例如原版的水是8格 // 可以自己调试这几个数值试试,也可以去wiki看看具体的含义。 private static final BaseFlowingFluid.Properties MY_FLUID_PROPERTIES = new BaseFlowingFluid.Properties(ModFluidType.MY_FLUID_TYPE, ModFluids.MY_SOURCE_FLUID_BLOCK, ModFluids.MY_FLOWING_FLUID_BLOCK).bucket(ModItems.MY_FLUID_BUCKET).slopeFindDistance(2).levelDecreasePerBlock(2).block(ModBlocks.MY_FLUID_BLOCK); // 别忘记注册到总线 public static void register(IEventBus eventBus) { FLUIDS.register(eventBus); } } ``` 下面我们来看对应的流体的方块和物品。 先看方块。 ```java // 注册方块,不过我们没有使用我么自己的那个方法,而是直接使用BLOCK的register方法,主要是我们不需要提供对应的item。因为我们还要注册对应的bucketitem。 public static final Supplier MY_FLUID_BLOCK = BLOCKS.register("my_fluid_block", ()->new LiquidBlock(ModFluids.MY_SOURCE_FLUID_BLOCK,BlockBehaviour.Properties.ofFullCopy(Blocks.WATER))); ``` 流体桶 ```java // 流体桶,craftRemainder表示合成之后保留桶 public static final Supplier MY_FLUID_BUCKET = register("my_fluid_bucket", ()->new BucketItem(ModFluids.MY_SOURCE_FLUID_BLOCK,new Item.Properties().craftRemainder(Items.BUCKET).stacksTo(1))); ``` 流体的渲染,由于我们的流体是一个半透明的材质,所以指定流体的渲染为:translucent 对于source和flow都需要指定。 ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class ModClientEventHandler { @SubscribeEvent public static void onClientEvent(FMLClientSetupEvent event){ event.enqueueWork(()->{ //fluid ItemBlockRenderTypes.setRenderLayer(ModFluids.MY_SOURCE_FLUID_BLOCK.get(), RenderType.translucent()); ItemBlockRenderTypes.setRenderLayer(ModFluids.MY_FLOWING_FLUID_BLOCK.get(), RenderType.translucent()); }); } ``` 好了,别忘记了注册到总线,然后你就可以进入游戏中看看了。 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_41_NutritionSample.md description: 41 饥渴值例子 相关教程 --- # 参考 ## 给玩家添加口渴度 这次我们使用之前讲解的capabilitiy能力系统,HUD,网络发包,热键注册等功能给玩家添加一个口渴度的系统,并且借助HUD功能将这个数值渲染到屏幕上。通过按键O增加口渴度,随时间口渴度逐渐减少 偷懒了。没写能力的接口,直接写了实现类。 ```java public class PlayerThirst { // 玩家当前的口渴值 private int thirst; // 最小和最大口渴值的常量 private final int MIN_THIRST = 0; private final int MAX_THIRST = 10; // 获取当前的口渴值 public int getThirst() { return thirst; } // 增加口渴值,但不能超过最大值 public void addThirst(int add) { this.thirst = Math.min(thirst + add, MAX_THIRST); } // 减少口渴值,但不能低于最小值 public void subThirst(int sub) { this.thirst = Math.max(thirst - sub, MIN_THIRST); } // 从另一个 PlayerThirst 对象复制口渴值 public void copyFrom(PlayerThirst source) { this.thirst = source.thirst; } // 将口渴值保存到 NBT 数据中 public void saveNBTData(CompoundTag nbt) { nbt.putInt("thirst", thirst); } // 从 NBT 数据中加载口渴值 public void loadNBTData(CompoundTag nbt) { thirst = nbt.getInt("thirst"); } } ``` 能力的提供者capabilityProvider,以及数据的持久化 ```java public class PlayerThirstProvider implements ICapabilityProvider, INBTSerializable { // 存储玩家口渴值的实例 private PlayerThirst thirst = null; // 创建 PlayerThirst 实例的私有方法 private PlayerThirst createPlayerThirst() { if (this.thirst == null) { this.thirst = new PlayerThirst(); } return this.thirst; } // 实现 INBTSerializable 接口的 serializeNBT 方法,用于将口渴值保存到 NBT 数据中 @Override public CompoundTag serializeNBT() { CompoundTag nbt = new CompoundTag(); createPlayerThirst().saveNBTData(nbt); return nbt; } // 实现 INBTSerializable 接口的 deserializeNBT 方法,用于从 NBT 数据中加载口渴值 @Override public void deserializeNBT(CompoundTag nbt) { createPlayerThirst().loadNBTData(nbt); } // 实现 ICapabilityProvider 接口的 getCapability 方法,用于获取玩家的口渴值实例 @Override public @Nullable PlayerThirst getCapability(Player player, Void context) { return this.createPlayerThirst(); } } ``` 能力的创建 ```java public class ModCapabilities { public static final EntityCapability PLAYER_THIRST_HANDLER = EntityCapability.createVoid(new ResourceLocation(ExampleMod.MODID,"player_thirst_handler"), PlayerThirst.class); } ``` 给玩家实体添加能力。 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public static class ModEventBus{ @SubscribeEvent private static void registerCapabilities(RegisterCapabilitiesEvent event) { event.registerEntity(ModCapabilities.PLAYER_THIRST_HANDLER, EntityType.PLAYER, new PlayerThirstProvider()); } } ``` 由于按键只能在客户端的上,所以喝水的增加口渴值的逻辑需要发包处理,这里添加一个数据包。 ```java // 需要同步的数据只有thirst public record ThirstData(int thirst) implements CustomPacketPayload { public static final ResourceLocation ID = new ResourceLocation(ExampleMod.MODID,"thirst_data"); public ThirstData(final FriendlyByteBuf buf){ this(buf.readInt()); } @Override public void write(FriendlyByteBuf pBuffer) { pBuffer.writeInt(thirst()); } @Override public ResourceLocation id() { return ID; } } ``` 如果是客户端收到了发包怎么处理数据包 ```java public class ClientPayloadHandler { private static final Logger LOGGER = LogUtils.getLogger(); private static final ClientPayloadHandler INSTANCE = new ClientPayloadHandler(); public static ClientPayloadHandler getInstance() { return INSTANCE; } // 给我们的在客户端存储thirst的地方赋值 public void handleThirstData(final ThirstData data,final PlayPayloadContext context){ context.workHandler().submitAsync(()->{ ClientPlayerThirstData.set(data.thirst()); }).exceptionally(e->{ context.packetHandler().disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } } ``` 服务端接受到数据包怎么处理 ```java public class ServerPayloadHandler { private static final ServerPayloadHandler INSTANCE = new ServerPayloadHandler(); private static final Logger LOGGER = LogUtils.getLogger(); public static ServerPayloadHandler getInstance() { return INSTANCE; } public void handleThirstData(final ThirstData data, final PlayPayloadContext context){ context.workHandler().submitAsync(()->{ context.player().ifPresent(player -> { // 玩家应该是服务端的玩家 if (player instanceof ServerPlayer serverPlayer){ // 服务端的level ServerLevel level = (ServerLevel) player.level(); // 判断玩家附近是否有水 if(hasWaterAroundThem(player,level,2)){ // 播放喝水的声音 level.playSound(null, player.getOnPos(), SoundEvents.GENERIC_DRINK, SoundSource.PLAYERS, 0.5F, level.random.nextFloat() * 0.1F + 0.9F); // 从玩家身上获得口渴的能力,如果存在就增加数值,并发包同步数据 Optional.ofNullable(player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)).ifPresent(thirst->{ thirst.addThirst(1); player.sendSystemMessage(Component.literal("Current Thirst " + thirst.getThirst()) .withStyle(ChatFormatting.AQUA)); PacketDistributor.PLAYER.with(serverPlayer).send(new ThirstData(thirst.getThirst())); }); }else{ // 同理 Optional.ofNullable(player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)).ifPresent(thirst -> { player.sendSystemMessage(Component.literal("Current Thirst " + thirst.getThirst()) .withStyle(ChatFormatting.AQUA)); PacketDistributor.PLAYER.with(serverPlayer).send(new ThirstData(thirst.getThirst())); }); } } }); }).exceptionally(e->{ context.packetHandler().disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } // 判断玩家附近是否有水方块 private boolean hasWaterAroundThem(Player player, ServerLevel level, int size) { return level.getBlockStates(player.getBoundingBox().inflate(size)) .filter(state -> state.is(Blocks.WATER)).toArray().length > 0; } } ``` 网络包的注册,以及对网络包的处理。 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public class Networking { @SubscribeEvent public static void register(final RegisterPayloadHandlerEvent event) { final IPayloadRegistrar registrar = event.registrar(ExampleMod.MODID); registrar.play(ThirstData.ID,ThirstData::new, handler -> handler.client(ClientPayloadHandler.getInstance()::handleThirstData) .server(ServerPayloadHandler.getInstance()::handleThirstData)); } } ``` 我们客户端上的辅助的类,用于存储playerThirst数值,并给HUD提供渲染支持。 ```java @OnlyIn(Dist.CLIENT) public class ClientPlayerThirstData { private static int playerThirst; public static void set(int thirst) { ClientPlayerThirstData.playerThirst = thirst; } public static int getPlayerThirst() { return playerThirst; } } ``` 玩家安下O键时候,给服务器发包,服务器处理数据包完成喝水的过程。 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,value = Dist.CLIENT) public class ForgeClientEventHandler { @SubscribeEvent public static void onKeyInput(InputEvent.Key event) { if(KeyBinding.DRINKING_KEY.consumeClick()){ // Minecraft.getInstance().player.sendSystemMessage(Component.literal("Press the key!")); PacketDistributor.SERVER.noArg().send(new ThirstData(ClientPlayerThirstData.getPlayerThirst())); } } } ``` 玩家每隔一段时间就数值-1,并发包同步数据 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.FORGE) public static class ForgeEvents{ @SubscribeEvent public static void onPlayerTick(TickEvent.PlayerTickEvent event) { if(event.side == LogicalSide.SERVER) { Optional optionalPlayerThirst = Optional.ofNullable(event.player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)); optionalPlayerThirst .ifPresent(thirst -> { if(thirst.getThirst() > 0 && event.player.getRandom().nextFloat() < 0.005f) { // Once Every 10 Seconds on Avg thirst.subThirst(1); PacketDistributor.PLAYER.with((ServerPlayer) event.player).send(new ThirstData(thirst.getThirst())); event.player.sendSystemMessage(Component.literal("Subtracted Thirst")); } }); } } // 当玩家刚刚加入世界时候同步数据 @SubscribeEvent public static void onPlayerJoinWorld(EntityJoinLevelEvent event) { if(!event.getLevel().isClientSide()) { if(event.getEntity() instanceof ServerPlayer player) { Optional optionalPlayerThirst = Optional.ofNullable(player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)); optionalPlayerThirst.ifPresent(thirst -> { PacketDistributor.PLAYER.with((ServerPlayer) player).send(new ThirstData(thirst.getThirst())); }); } } } } ``` 使用HUD绘制水瓶 ```java public class ThirstHud { // 有水和无水的水瓶 private static final ResourceLocation FILLED_THIRST = new ResourceLocation(ExampleMod.MODID, "textures/gui/filled_thirst.png"); private static final ResourceLocation EMPTY_THIRST = new ResourceLocation(ExampleMod.MODID, "textures/gui/empty_thirst.png"); public static final IGuiOverlay HUD_THIRST = (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> { int x = screenWidth / 2; int y = screenHeight; // 绘制无水的水瓶 for(int i = 0; i < 10; i++) { guiGraphics.blit(EMPTY_THIRST,x - 94 + (i * 9), y - 54,90,0,0,12,12, 12,12); } // 绘制有水的水瓶 // ClientPlayerThirstData我们提供的client辅助的类存储数值 for(int i = 0; i < 10; i++) { if(ClientPlayerThirstData.getPlayerThirst() > i) { guiGraphics.blit(FILLED_THIRST,x - 94 + (i * 9),y - 54,90,0,0,12,12, 12,12); } else { break; } } }; } ``` 注册HUD。 ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class ModClientEventHandler { @SubscribeEvent public static void registerGuiOverlays(RegisterGuiOverlaysEvent event) { event.registerAboveAll(new ResourceLocation(ExampleMod.MODID,"thirst_hud"), ThirstHud.HUD_THIRST); } } ``` 好了进入游戏看看逻辑把,应该绘制出来了无水的水瓶,在水方块附近按下o,看看是不是增加了一个有水的水瓶,推出游戏在进入看看是不是数据是不是可以持久化。以及等一段时间看看瓶子会不会减少,对应了tick事件的方法。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_41_NutritionSample.md description: 41 饥渴值例子 相关教程 --- # 参考 ## 给玩家添加口渴度 这次我们使用之前讲解的capabilitiy能力系统,HUD,网络发包,热键注册等功能给玩家添加一个口渴度的系统,并且借助HUD功能将这个数值渲染到屏幕上。通过按键O增加口渴度,随时间口渴度逐渐减少 偷懒了。没写能力的接口,直接写了实现类。 ```java public class PlayerThirst { // 玩家当前的口渴值 private int thirst; // 最小和最大口渴值的常量 private final int MIN_THIRST = 0; private final int MAX_THIRST = 10; // 获取当前的口渴值 public int getThirst() { return thirst; } // 增加口渴值,但不能超过最大值 public void addThirst(int add) { this.thirst = Math.min(thirst + add, MAX_THIRST); } // 减少口渴值,但不能低于最小值 public void subThirst(int sub) { this.thirst = Math.max(thirst - sub, MIN_THIRST); } // 从另一个 PlayerThirst 对象复制口渴值 public void copyFrom(PlayerThirst source) { this.thirst = source.thirst; } // 将口渴值保存到 NBT 数据中 public void saveNBTData(CompoundTag nbt) { nbt.putInt("thirst", thirst); } // 从 NBT 数据中加载口渴值 public void loadNBTData(CompoundTag nbt) { thirst = nbt.getInt("thirst"); } } ``` 能力的提供者capabilityProvider,以及数据的持久化 ```java public class PlayerThirstProvider implements ICapabilityProvider, INBTSerializable { // 存储玩家口渴值的实例 private PlayerThirst thirst = null; // 创建 PlayerThirst 实例的私有方法 private PlayerThirst createPlayerThirst() { if (this.thirst == null) { this.thirst = new PlayerThirst(); } return this.thirst; } // 实现 INBTSerializable 接口的 serializeNBT 方法,用于将口渴值保存到 NBT 数据中 @Override public CompoundTag serializeNBT() { CompoundTag nbt = new CompoundTag(); createPlayerThirst().saveNBTData(nbt); return nbt; } // 实现 INBTSerializable 接口的 deserializeNBT 方法,用于从 NBT 数据中加载口渴值 @Override public void deserializeNBT(CompoundTag nbt) { createPlayerThirst().loadNBTData(nbt); } // 实现 ICapabilityProvider 接口的 getCapability 方法,用于获取玩家的口渴值实例 @Override public @Nullable PlayerThirst getCapability(Player player, Void context) { return this.createPlayerThirst(); } } ``` 能力的创建 ```java public class ModCapabilities { public static final EntityCapability PLAYER_THIRST_HANDLER = EntityCapability.createVoid(new ResourceLocation(ExampleMod.MODID,"player_thirst_handler"), PlayerThirst.class); } ``` 给玩家实体添加能力。 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public static class ModEventBus{ @SubscribeEvent private static void registerCapabilities(RegisterCapabilitiesEvent event) { event.registerEntity(ModCapabilities.PLAYER_THIRST_HANDLER, EntityType.PLAYER, new PlayerThirstProvider()); } } ``` 由于按键只能在客户端的上,所以喝水的增加口渴值的逻辑需要发包处理,这里添加一个数据包。 ```java // 需要同步的数据只有thirst public record ThirstData(int thirst) implements CustomPacketPayload { public static final ResourceLocation ID = new ResourceLocation(ExampleMod.MODID,"thirst_data"); public ThirstData(final FriendlyByteBuf buf){ this(buf.readInt()); } @Override public void write(FriendlyByteBuf pBuffer) { pBuffer.writeInt(thirst()); } @Override public ResourceLocation id() { return ID; } } ``` 如果是客户端收到了发包怎么处理数据包 ```java public class ClientPayloadHandler { private static final Logger LOGGER = LogUtils.getLogger(); private static final ClientPayloadHandler INSTANCE = new ClientPayloadHandler(); public static ClientPayloadHandler getInstance() { return INSTANCE; } // 给我们的在客户端存储thirst的地方赋值 public void handleThirstData(final ThirstData data,final PlayPayloadContext context){ context.workHandler().submitAsync(()->{ ClientPlayerThirstData.set(data.thirst()); }).exceptionally(e->{ context.packetHandler().disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } } ``` 服务端接受到数据包怎么处理 ```java public class ServerPayloadHandler { private static final ServerPayloadHandler INSTANCE = new ServerPayloadHandler(); private static final Logger LOGGER = LogUtils.getLogger(); public static ServerPayloadHandler getInstance() { return INSTANCE; } public void handleThirstData(final ThirstData data, final PlayPayloadContext context){ context.workHandler().submitAsync(()->{ context.player().ifPresent(player -> { // 玩家应该是服务端的玩家 if (player instanceof ServerPlayer serverPlayer){ // 服务端的level ServerLevel level = (ServerLevel) player.level(); // 判断玩家附近是否有水 if(hasWaterAroundThem(player,level,2)){ // 播放喝水的声音 level.playSound(null, player.getOnPos(), SoundEvents.GENERIC_DRINK, SoundSource.PLAYERS, 0.5F, level.random.nextFloat() * 0.1F + 0.9F); // 从玩家身上获得口渴的能力,如果存在就增加数值,并发包同步数据 Optional.ofNullable(player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)).ifPresent(thirst->{ thirst.addThirst(1); player.sendSystemMessage(Component.literal("Current Thirst " + thirst.getThirst()) .withStyle(ChatFormatting.AQUA)); PacketDistributor.PLAYER.with(serverPlayer).send(new ThirstData(thirst.getThirst())); }); }else{ // 同理 Optional.ofNullable(player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)).ifPresent(thirst -> { player.sendSystemMessage(Component.literal("Current Thirst " + thirst.getThirst()) .withStyle(ChatFormatting.AQUA)); PacketDistributor.PLAYER.with(serverPlayer).send(new ThirstData(thirst.getThirst())); }); } } }); }).exceptionally(e->{ context.packetHandler().disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } // 判断玩家附近是否有水方块 private boolean hasWaterAroundThem(Player player, ServerLevel level, int size) { return level.getBlockStates(player.getBoundingBox().inflate(size)) .filter(state -> state.is(Blocks.WATER)).toArray().length > 0; } } ``` 网络包的注册,以及对网络包的处理。 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public class Networking { @SubscribeEvent public static void register(final RegisterPayloadHandlerEvent event) { final IPayloadRegistrar registrar = event.registrar(ExampleMod.MODID); registrar.play(ThirstData.ID,ThirstData::new, handler -> handler.client(ClientPayloadHandler.getInstance()::handleThirstData) .server(ServerPayloadHandler.getInstance()::handleThirstData)); } } ``` 我们客户端上的辅助的类,用于存储playerThirst数值,并给HUD提供渲染支持。 ```java @OnlyIn(Dist.CLIENT) public class ClientPlayerThirstData { private static int playerThirst; public static void set(int thirst) { ClientPlayerThirstData.playerThirst = thirst; } public static int getPlayerThirst() { return playerThirst; } } ``` 玩家安下O键时候,给服务器发包,服务器处理数据包完成喝水的过程。 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,value = Dist.CLIENT) public class ForgeClientEventHandler { @SubscribeEvent public static void onKeyInput(InputEvent.Key event) { if(KeyBinding.DRINKING_KEY.consumeClick()){ // Minecraft.getInstance().player.sendSystemMessage(Component.literal("Press the key!")); PacketDistributor.SERVER.noArg().send(new ThirstData(ClientPlayerThirstData.getPlayerThirst())); } } } ``` 玩家每隔一段时间就数值-1,并发包同步数据 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.FORGE) public static class ForgeEvents{ @SubscribeEvent public static void onPlayerTick(TickEvent.PlayerTickEvent event) { if(event.side == LogicalSide.SERVER) { Optional optionalPlayerThirst = Optional.ofNullable(event.player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)); optionalPlayerThirst .ifPresent(thirst -> { if(thirst.getThirst() > 0 && event.player.getRandom().nextFloat() < 0.005f) { // Once Every 10 Seconds on Avg thirst.subThirst(1); PacketDistributor.PLAYER.with((ServerPlayer) event.player).send(new ThirstData(thirst.getThirst())); event.player.sendSystemMessage(Component.literal("Subtracted Thirst")); } }); } } // 当玩家刚刚加入世界时候同步数据 @SubscribeEvent public static void onPlayerJoinWorld(EntityJoinLevelEvent event) { if(!event.getLevel().isClientSide()) { if(event.getEntity() instanceof ServerPlayer player) { Optional optionalPlayerThirst = Optional.ofNullable(player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)); optionalPlayerThirst.ifPresent(thirst -> { PacketDistributor.PLAYER.with((ServerPlayer) player).send(new ThirstData(thirst.getThirst())); }); } } } } ``` 使用HUD绘制水瓶 ```java public class ThirstHud { // 有水和无水的水瓶 private static final ResourceLocation FILLED_THIRST = new ResourceLocation(ExampleMod.MODID, "textures/gui/filled_thirst.png"); private static final ResourceLocation EMPTY_THIRST = new ResourceLocation(ExampleMod.MODID, "textures/gui/empty_thirst.png"); public static final IGuiOverlay HUD_THIRST = (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> { int x = screenWidth / 2; int y = screenHeight; // 绘制无水的水瓶 for(int i = 0; i < 10; i++) { guiGraphics.blit(EMPTY_THIRST,x - 94 + (i * 9), y - 54,90,0,0,12,12, 12,12); } // 绘制有水的水瓶 // ClientPlayerThirstData我们提供的client辅助的类存储数值 for(int i = 0; i < 10; i++) { if(ClientPlayerThirstData.getPlayerThirst() > i) { guiGraphics.blit(FILLED_THIRST,x - 94 + (i * 9),y - 54,90,0,0,12,12, 12,12); } else { break; } } }; } ``` 注册HUD。 ```java @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT) public class ModClientEventHandler { @SubscribeEvent public static void registerGuiOverlays(RegisterGuiOverlaysEvent event) { event.registerAboveAll(new ResourceLocation(ExampleMod.MODID,"thirst_hud"), ThirstHud.HUD_THIRST); } } ``` 好了进入游戏看看逻辑把,应该绘制出来了无水的水瓶,在水方块附近按下o,看看是不是增加了一个有水的水瓶,推出游戏在进入看看是不是数据是不是可以持久化。以及等一段时间看看瓶子会不会减少,对应了tick事件的方法。 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_42_OreGeneration.md description: 42 矿物生成 相关教程 --- # 矿物生成 这次我们来说矿物生成,现在的世界生成基本上已经使用json来驱动了,所以你甚至不需要编写代码,仅仅通过json就可以实现了,不过编写这样的json是一件很恐怖的事情,我们还是通过数据生成的方式来生成json的内容,这次的内容比较多。我们还是先来介绍涉及到的类,以及原版的怎么操作的。然后再来讲我们应该怎么做。 我们先来说一个概念就是资源键(ResourceKey),他类似于resourceLoaction这个类,不过不同的是这个key是用于在游戏中唯一标识矿石特性的,用于注册ORE\_Freature。当然OreFeature矿石特征是ConfiguredFeature的一种特定类型,ConfiguredFeature是指可以配置的特征,我的世界很多的世界生成内容都依靠这个配置特征,需要用到resourceKey注册。 然后我们来说矿石特性OreFeature,具体来说他指出了矿石在世界生成的时候去替换那些方块。这里提一下我的世界的世界生成是先生成世界,然后在用结构矿物等替换到原有的方块。所以这里定义的是矿物方块要替换到那些方块,对于铁矿了来说,要替换的就是石头之类的方块。此外OreFeature还需要指出这一堆生成的数量。这两个数据是通过OreConfiguration类来配置,这个类的构造也很简单,就是通过该类提供的target传入对应的resourceKey以及targetBlock和size。即上文我们提到的。 对于原版的OreFeature的配置在OreFeatures这个类下面。 好了了还需要说的一点是RuleTest,点开RuleTest该类,你就可以发现该类有几个子类,分别是BlockStateMatchTest,TagMatchTest。。。。这几个类的作用是写你的目标检查方块的,不过你也可以用在其他地方,目前来说就作用是匹配方块是否和你指定的方块一致,类型有很多,比如TagMatChTest就是匹配方块的标签的,例如我要替换掉石头的,那么写入石头的标签即可。其他同理。 更加详细的字段讲解可以看看wiki https://minecraft.fandom.com/zh/wiki/%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%96%E7%95%8C%E7%94%9F%E6%88%90/configured\_feature 好了来看看我们的写的这个OreFeature吧 ```java public class ModOreFeatures { // 创建OreFeature对应的ResourceKey // public static final ResourceKey> ORE_RUBY = createKey("ruby"); //BootstapContext 是我们datagen的上下文,等会我们使用数据生成的时候说。 public static void bootstrap(BootstapContext> pContext) { // 创建对应的tag,如果有多个就创建多个 RuleTest stoneOreReplaceRuleTest = new TagMatchTest(BlockTags.STONE_ORE_REPLACEABLES); RuleTest deepSlateOreReplaceRuleTest = new TagMatchTest(BlockTags.DEEPSLATE_ORE_REPLACEABLES); // 创建一个list List list = List.of( OreConfiguration.target(stoneOreReplaceRuleTest, ModBlocks.RUBY_ORE.get().defaultBlockState()), OreConfiguration.target(deepSlateOreReplaceRuleTest, ModBlocks.RUBY_BLOCK.get().defaultBlockState()) ); // 注册对应orefeature,使用listOreConfiguration,9 上文提到的size FeatureUtils.register(pContext, ORE_RUBY, Feature.ORE, new OreConfiguration(list, 9)); } // 创建ResourceKey的方法 public static ResourceKey> createKey(String pName) { return ResourceKey.create(Registries.CONFIGURED_FEATURE, new ResourceLocation(ExampleMod.MODID,pName)); } } ``` 好了现在我们来看placements,这个是用于生成矿石的,叫做放置特征PlacedFeature。他的注册同样需要到ResourceKey资源键。我们也需要创建对应的资源键。 PlacedFeature是指的你的配置的ConfigurationFeature在世界中如何生成,PlacedFeature的配置是通过PlacementModifier来设置的,你可以在PlacedFeature中设置多个PlacementModifier。 PlacementModifier可以设置高度的区间范围,生物群系,生成的方式,等等。 我们来看看这个,我们矿物的生成 ```java public class ModOrePlacements { // 创建对应的placement的资源键 public static final ResourceKey ORE_RUBY = createKey("ore_ruby"); // BootstapContext数据生成时候传入上下文对象,我们数据生成时候再说 public static void bootstrap(BootstapContext pContext) { // 我们通过上下文获得我们的设置的个矿物的configurationfeature HolderGetter> holdergetter = pContext.lookup(Registries.CONFIGURED_FEATURE); // 通过上下文获得的是一个HolderGetter,这个HolderGetter是对ConfiguredFeature主要用于处理null的问题,感兴趣可以去了解下,不过这里我们写了对应的ConfigurationFeature所有获得不会是空,就可以拿到对应的Holder Holder> oreRubyHolder = holdergetter.getOrThrow(ModOreFeatures.ORE_RUBY); // 让后我们就可以使用PlacementUtils的register方法,注册我们的PlacedFeature // 第一个参数就是上下文,第二个参数是对应ConfiguredFeature的holder,第三个参数是list的PlacementModifier,说明你的PlacedFeature的设置内容, // 这里commonOrePlacement是指设置了一系列的PlacementModifier,对应的是常规的矿物的生成,和原版的一样。 // 对于PlacementModifier有很多的子类,你可以去继承关系中看 // 这里我们用的一个是HeightRangePlacement,是指设置生成的最低高度和最高的高度。 // 对于commonOrePlacement方法中用的CountPlacement则是设置一个数值 // HeightRangePlacement的uniform方法是指从低到高平均生成,还有一个三角的,是指中间生成多,两边生成少。 PlacementUtils.register( pContext, ORE_RUBY, oreRubyHolder, commonOrePlacement(16, HeightRangePlacement.uniform(VerticalAnchor.absolute(-64), VerticalAnchor.absolute(72))) ); } private static List orePlacement(PlacementModifier pCountPlacement, PlacementModifier pHeightRange) { return List.of(pCountPlacement, InSquarePlacement.spread(), pHeightRange, BiomeFilter.biome()); } private static List commonOrePlacement(int pCount, PlacementModifier pHeightRange) { return orePlacement(CountPlacement.of(pCount), pHeightRange); } // 我们没有使用这个,对应了原版的稀有矿物生成 private static List rareOrePlacement(int pChance, PlacementModifier pHeightRange) { return orePlacement(RarityFilter.onAverageOnceEvery(pChance), pHeightRange); } public static ResourceKey createKey(String pKey) { return ResourceKey.create(Registries.PLACED_FEATURE, new ResourceLocation(ExampleMod.MODID,pKey)); } } ``` 我们创建了矿石特性的OreFeature和矿石的PlacedFeature,不过这些Feature并没有添加到生物群系的生成中,现在我们需要将它们添加到生物群系中去了。 我们依旧需要一个ResourceKey不过这次的ResourceKey对应的内容是BiomeModifier这个内容,我们注册的是BiomeModifier。 BiomeModifier表示了对生物群系的修改,我们使用BiomeModifiers的AddFeaturesBiomeModifier子类 ```java public class ModBiomeModifiers { // 注册的key public static final ResourceKey ADD_RUBY_ORE = registerKey("add_ruby_ore"); // BootstapContext 数据生成的上下文 public static void bootstrap(BootstapContext context) { // 通过上下文获得PLACED_FEATURE的注册HolderGetter var placedFeatures = context.lookup(Registries.PLACED_FEATURE); // 通过上下文获得BIOME的HolderGetter var biomes = context.lookup(Registries.BIOME); // 生成json文件,第一个参数是key,第二个参数是BiomeModifiers, // 我们使用了子类AddFeaturesBiomeModifier,是指添加feature给biome // 第一个参数是holderset的biome ,这里是否是主世界的生物群系。即返回了主世界的生物群系 // 第二个holdlerSet是指所有的feature,我们通过placedFeatures获得 // 丢三个参数要求给出在世界生成的什么阶段加你的feature,我们这里是地下矿物生成的时候,你可以到该类下面看看,这是个枚举, context.register(ADD_RUBY_ORE, new BiomeModifiers.AddFeaturesBiomeModifier( biomes.getOrThrow(BiomeTags.IS_OVERWORLD), HolderSet.direct(placedFeatures.getOrThrow(ModOrePlacements.ORE_RUBY)), GenerationStep.Decoration.UNDERGROUND_ORES)); } private static ResourceKey registerKey(String name) { return ResourceKey.create(NeoForgeRegistries.Keys.BIOME_MODIFIERS, new ResourceLocation(ExampleMod.MODID, name)); } } ``` 下面我们来说怎么使用写的东西生成对应的json。我们需要写这样的类,然后使用RegistrySetBuilder构建一个builder,通过add方法将我们写的那几个bootstrap的方法引用传入。 其中BIOME\_MODIFIERS比较特殊的是registries.keys是注册的地方。 ```java public class ModWorldGen extends DatapackBuiltinEntriesProvider { public static final RegistrySetBuilder BUILDER = new RegistrySetBuilder() .add(Registries.CONFIGURED_FEATURE, ModOreFeatures::bootstrap) .add(Registries.PLACED_FEATURE, ModOrePlacements::bootstrap) .add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, ModBiomeModifiers::bootstrap); public ModWorldGen(PackOutput output, CompletableFuture registries) { super(output, registries, BUILDER,Set.of(ExampleMod.MODID)); } } ``` 和我们之前的使用方法一致,没什么区别。就不多家介绍了。 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public class ModDataGeneratorHandler { @SubscribeEvent public static void gatherData(GatherDataEvent event){ ExistingFileHelper efh = event.getExistingFileHelper(); var lp = event.getLookupProvider(); //world gen event.getGenerator().addProvider(event.includeServer(), (DataProvider.Factory) pOutput -> new ModWorldGen(pOutput,lp)); } } ``` 好了,你运行rundata,应该可以获得下面的几个json目录了。 ../src/generated/resources/data/examplemod/neoforge/biome\_modifier/add\_ruby\_ore.json ../src/generated/resources/data/examplemod/worldgen/configured\_feature/ruby.json ../src/generated/resources/data/examplemod/worldgen/placed\_feature/ore\_ruby.json 之后创建个新的世界看看矿物是否生成了吧 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_42_OreGeneration.md description: 42 矿物生成 相关教程 --- # 矿物生成 这次我们来说矿物生成,现在的世界生成基本上已经使用json来驱动了,所以你甚至不需要编写代码,仅仅通过json就可以实现了,不过编写这样的json是一件很恐怖的事情,我们还是通过数据生成的方式来生成json的内容,这次的内容比较多。我们还是先来介绍涉及到的类,以及原版的怎么操作的。然后再来讲我们应该怎么做。 我们先来说一个概念就是资源键(ResourceKey),他类似于resourceLoaction这个类,不过不同的是这个key是用于在游戏中唯一标识矿石特性的,用于注册ORE\_Freature。当然OreFeature矿石特征是ConfiguredFeature的一种特定类型,ConfiguredFeature是指可以配置的特征,我的世界很多的世界生成内容都依靠这个配置特征,需要用到resourceKey注册。 然后我们来说矿石特性OreFeature,具体来说他指出了矿石在世界生成的时候去替换那些方块。这里提一下我的世界的世界生成是先生成世界,然后在用结构矿物等替换到原有的方块。所以这里定义的是矿物方块要替换到那些方块,对于铁矿了来说,要替换的就是石头之类的方块。此外OreFeature还需要指出这一堆生成的数量。这两个数据是通过OreConfiguration类来配置,这个类的构造也很简单,就是通过该类提供的target传入对应的resourceKey以及targetBlock和size。即上文我们提到的。 对于原版的OreFeature的配置在OreFeatures这个类下面。 好了了还需要说的一点是RuleTest,点开RuleTest该类,你就可以发现该类有几个子类,分别是BlockStateMatchTest,TagMatchTest。。。。这几个类的作用是写你的目标检查方块的,不过你也可以用在其他地方,目前来说就作用是匹配方块是否和你指定的方块一致,类型有很多,比如TagMatChTest就是匹配方块的标签的,例如我要替换掉石头的,那么写入石头的标签即可。其他同理。 更加详细的字段讲解可以看看wiki https://minecraft.fandom.com/zh/wiki/%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%96%E7%95%8C%E7%94%9F%E6%88%90/configured\_feature 好了来看看我们的写的这个OreFeature吧 ```java public class ModOreFeatures { // 创建OreFeature对应的ResourceKey // public static final ResourceKey> ORE_RUBY = createKey("ruby"); //BootstapContext 是我们datagen的上下文,等会我们使用数据生成的时候说。 public static void bootstrap(BootstapContext> pContext) { // 创建对应的tag,如果有多个就创建多个 RuleTest stoneOreReplaceRuleTest = new TagMatchTest(BlockTags.STONE_ORE_REPLACEABLES); RuleTest deepSlateOreReplaceRuleTest = new TagMatchTest(BlockTags.DEEPSLATE_ORE_REPLACEABLES); // 创建一个list List list = List.of( OreConfiguration.target(stoneOreReplaceRuleTest, ModBlocks.RUBY_ORE.get().defaultBlockState()), OreConfiguration.target(deepSlateOreReplaceRuleTest, ModBlocks.RUBY_BLOCK.get().defaultBlockState()) ); // 注册对应orefeature,使用listOreConfiguration,9 上文提到的size FeatureUtils.register(pContext, ORE_RUBY, Feature.ORE, new OreConfiguration(list, 9)); } // 创建ResourceKey的方法 public static ResourceKey> createKey(String pName) { return ResourceKey.create(Registries.CONFIGURED_FEATURE, new ResourceLocation(ExampleMod.MODID,pName)); } } ``` 好了现在我们来看placements,这个是用于生成矿石的,叫做放置特征PlacedFeature。他的注册同样需要到ResourceKey资源键。我们也需要创建对应的资源键。 PlacedFeature是指的你的配置的ConfigurationFeature在世界中如何生成,PlacedFeature的配置是通过PlacementModifier来设置的,你可以在PlacedFeature中设置多个PlacementModifier。 PlacementModifier可以设置高度的区间范围,生物群系,生成的方式,等等。 我们来看看这个,我们矿物的生成 ```java public class ModOrePlacements { // 创建对应的placement的资源键 public static final ResourceKey ORE_RUBY = createKey("ore_ruby"); // BootstapContext数据生成时候传入上下文对象,我们数据生成时候再说 public static void bootstrap(BootstapContext pContext) { // 我们通过上下文获得我们的设置的个矿物的configurationfeature HolderGetter> holdergetter = pContext.lookup(Registries.CONFIGURED_FEATURE); // 通过上下文获得的是一个HolderGetter,这个HolderGetter是对ConfiguredFeature主要用于处理null的问题,感兴趣可以去了解下,不过这里我们写了对应的ConfigurationFeature所有获得不会是空,就可以拿到对应的Holder Holder> oreRubyHolder = holdergetter.getOrThrow(ModOreFeatures.ORE_RUBY); // 让后我们就可以使用PlacementUtils的register方法,注册我们的PlacedFeature // 第一个参数就是上下文,第二个参数是对应ConfiguredFeature的holder,第三个参数是list的PlacementModifier,说明你的PlacedFeature的设置内容, // 这里commonOrePlacement是指设置了一系列的PlacementModifier,对应的是常规的矿物的生成,和原版的一样。 // 对于PlacementModifier有很多的子类,你可以去继承关系中看 // 这里我们用的一个是HeightRangePlacement,是指设置生成的最低高度和最高的高度。 // 对于commonOrePlacement方法中用的CountPlacement则是设置一个数值 // HeightRangePlacement的uniform方法是指从低到高平均生成,还有一个三角的,是指中间生成多,两边生成少。 PlacementUtils.register( pContext, ORE_RUBY, oreRubyHolder, commonOrePlacement(16, HeightRangePlacement.uniform(VerticalAnchor.absolute(-64), VerticalAnchor.absolute(72))) ); } private static List orePlacement(PlacementModifier pCountPlacement, PlacementModifier pHeightRange) { return List.of(pCountPlacement, InSquarePlacement.spread(), pHeightRange, BiomeFilter.biome()); } private static List commonOrePlacement(int pCount, PlacementModifier pHeightRange) { return orePlacement(CountPlacement.of(pCount), pHeightRange); } // 我们没有使用这个,对应了原版的稀有矿物生成 private static List rareOrePlacement(int pChance, PlacementModifier pHeightRange) { return orePlacement(RarityFilter.onAverageOnceEvery(pChance), pHeightRange); } public static ResourceKey createKey(String pKey) { return ResourceKey.create(Registries.PLACED_FEATURE, new ResourceLocation(ExampleMod.MODID,pKey)); } } ``` 我们创建了矿石特性的OreFeature和矿石的PlacedFeature,不过这些Feature并没有添加到生物群系的生成中,现在我们需要将它们添加到生物群系中去了。 我们依旧需要一个ResourceKey不过这次的ResourceKey对应的内容是BiomeModifier这个内容,我们注册的是BiomeModifier。 BiomeModifier表示了对生物群系的修改,我们使用BiomeModifiers的AddFeaturesBiomeModifier子类 ```java public class ModBiomeModifiers { // 注册的key public static final ResourceKey ADD_RUBY_ORE = registerKey("add_ruby_ore"); // BootstapContext 数据生成的上下文 public static void bootstrap(BootstapContext context) { // 通过上下文获得PLACED_FEATURE的注册HolderGetter var placedFeatures = context.lookup(Registries.PLACED_FEATURE); // 通过上下文获得BIOME的HolderGetter var biomes = context.lookup(Registries.BIOME); // 生成json文件,第一个参数是key,第二个参数是BiomeModifiers, // 我们使用了子类AddFeaturesBiomeModifier,是指添加feature给biome // 第一个参数是holderset的biome ,这里是否是主世界的生物群系。即返回了主世界的生物群系 // 第二个holdlerSet是指所有的feature,我们通过placedFeatures获得 // 丢三个参数要求给出在世界生成的什么阶段加你的feature,我们这里是地下矿物生成的时候,你可以到该类下面看看,这是个枚举, context.register(ADD_RUBY_ORE, new BiomeModifiers.AddFeaturesBiomeModifier( biomes.getOrThrow(BiomeTags.IS_OVERWORLD), HolderSet.direct(placedFeatures.getOrThrow(ModOrePlacements.ORE_RUBY)), GenerationStep.Decoration.UNDERGROUND_ORES)); } private static ResourceKey registerKey(String name) { return ResourceKey.create(NeoForgeRegistries.Keys.BIOME_MODIFIERS, new ResourceLocation(ExampleMod.MODID, name)); } } ``` 下面我们来说怎么使用写的东西生成对应的json。我们需要写这样的类,然后使用RegistrySetBuilder构建一个builder,通过add方法将我们写的那几个bootstrap的方法引用传入。 其中BIOME\_MODIFIERS比较特殊的是registries.keys是注册的地方。 ```java public class ModWorldGen extends DatapackBuiltinEntriesProvider { public static final RegistrySetBuilder BUILDER = new RegistrySetBuilder() .add(Registries.CONFIGURED_FEATURE, ModOreFeatures::bootstrap) .add(Registries.PLACED_FEATURE, ModOrePlacements::bootstrap) .add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, ModBiomeModifiers::bootstrap); public ModWorldGen(PackOutput output, CompletableFuture registries) { super(output, registries, BUILDER,Set.of(ExampleMod.MODID)); } } ``` 和我们之前的使用方法一致,没什么区别。就不多家介绍了。 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public class ModDataGeneratorHandler { @SubscribeEvent public static void gatherData(GatherDataEvent event){ ExistingFileHelper efh = event.getExistingFileHelper(); var lp = event.getLookupProvider(); //world gen event.getGenerator().addProvider(event.includeServer(), (DataProvider.Factory) pOutput -> new ModWorldGen(pOutput,lp)); } } ``` 好了,你运行rundata,应该可以获得下面的几个json目录了。 ../src/generated/resources/data/examplemod/neoforge/biome\_modifier/add\_ruby\_ore.json ../src/generated/resources/data/examplemod/worldgen/configured\_feature/ruby.json ../src/generated/resources/data/examplemod/worldgen/placed\_feature/ore\_ruby.json 之后创建个新的世界看看矿物是否生成了吧 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_43_StructureGeneration.md description: 43 结构生成 相关教程 --- # 结构生成 好了这次我们来说结构化生成,这次涉及到的东西比较多,且内容比较杂乱,所以大家自己多看看。 我们主要的一个类是Structure类,这个类是你生成结构的主体类,不过你的建筑是可以多个结构拼在一起的,所以关联到StructurePieces类,这个类表示结构的某一部分,然后你可以通过拼接多个StructurePieces来实现你的Structure,你也可以使用多个StructurePieces的拼接来构建不同的建筑。 而建筑是有两种方式可以获得的,第一种方式就是类似原版的沙漠金字塔和雨林的神庙,这个两个建筑的部分是硬编码在代码中的。第二种方法是使用nbt数据,而nbt数据可以通过你在游戏中建造相应的建组后通天特殊的structure的方块保存为nbt数据的,之后我们的代码通过nbt数据实现生成对应的建组,并且你可以建筑生成前和生成后做一些处理,例如添加一些奖励箱。 先大概看下我们的代码吧: ```java public class MyStructure extends Structure { public static final Codec CODEC = simpleCodec(MyStructure::new); public MyStructure(StructureSettings pSettings) { super(pSettings); } @Override protected Optional findGenerationPoint(GenerationContext pContext) { return onTopOfChunkCenter(pContext, Heightmap.Types.WORLD_SURFACE_WG, structurePiecesBuilder -> this.generatePieces(structurePiecesBuilder, pContext)); } private void generatePieces(StructurePiecesBuilder pBuilder, Structure.GenerationContext pContext) { ChunkPos chunkpos = pContext.chunkPos(); WorldgenRandom worldgenrandom = pContext.random(); BlockPos blockpos = new BlockPos(chunkpos.getMinBlockX(), 64, chunkpos.getMinBlockZ()); Rotation rotation = Rotation.getRandom(worldgenrandom); MyStructurePieces.addPieces(pContext.structureTemplateManager(), blockpos, rotation, pBuilder, worldgenrandom); } @Override public StructureType type() { return ModStructureType.MY_STRUCTURE.get(); } } ``` 让我们来看看相关的类的以及配置等相关用到了的类把。 首先就是Structure类,该类是结构的基类,通过继承关系你可以看到对应的其他的子类,你看与参考学习其他的子类实现自己的结构,我们来说其中的字段和方法的含义: * DIRECT\_CODEC 和 CODEC:这两个字段是用于序列化和反序列化 Structure 对象的编解码器。 * settings:表示结构的设置,包括生物群系、生成步骤等。 * settingsCodec 和 simpleCodec:这两个方法用于创建 Structure 对象的编解码器。 * 构造函数 Structure(Structure.StructureSettings pSettings):用于创建 Structure 对象,接受 StructureSettings 作为参数。 * biomes()、spawnOverrides()、step() 和 terrainAdaptation():这些方法返回结构的相关设置。 * adjustBoundingBox(BoundingBox pBoundingBox):根据地形适应设置调整结构的边界框。 * generate(...):生成结构的主要方法,接受多个参数,如注册表访问器、区块生成器、生物群系源等。 * onTopOfChunkCenter(...):在区块中心的顶部生成结构的辅助方法。 * isValidBiome(Structure.GenerationStub pStub, Structure.GenerationContext pContext):检查给定位置的生物群系是否适合生成结构。 * afterPlace(...):在生成结构后执行的方法,可用于后处理。 * getCornerHeights(...) 和 getLowestY(...):计算结构的角高度和最低高度的辅助方法。 * findGenerationPoint(Structure.GenerationContext pContext) 和 findValidGenerationPoint(Structure.GenerationContext pContext):查找结构的生成点的抽象方法和具体实现方法 * type():返回结构的类型的抽象方法。 其中的findGenerationPoint方法和type方法是子类必须要实现的方法,也是我们使用的方法,其中findGenerationPoint方法是我们使用决定建筑的生成的位置的,而type需要我们返回strucutreType类的实例,之后我们会说怎么闯将StructureType的实例。 我们之前一直在回避codec这个概念,不过现在我们无法在回避他了,因为我们会用到他,Codec是新增的一套序列化和反序列的系统,用于同时处理nbt和json。目前看来似乎要在未来转向codec了。 不过基于我并不了解这个东西,所以这里还是说以下怎么在strucutre中创建这个codec吧,我们需要使用到这个codec获得StructureType。 ```java //一种简单的方法就是通过Structure提供的方法这样创建,这样的创建简单的结构对于我们来说已经是够用了。 // 直接传入你的structure的构造方法即可。 public static final Codec CODEC = simpleCodec(OceanMonumentStructure::new); // 另一种是自己使用RecordCodecBuilder.create方法类构建 // 其中你可以在通过Codec读取json文件中的额外属性 public static final Codec CODEC = RecordCodecBuilder.create( p_229075_ -> p_229075_.group( settingsCodec(p_229075_), OceanRuinStructure.Type.CODEC.fieldOf("biome_temp").forGetter(p_229079_ -> p_229079_.biomeTemp), Codec.floatRange(0.0F, 1.0F).fieldOf("large_probability").forGetter(p_229077_ -> p_229077_.largeProbability), Codec.floatRange(0.0F, 1.0F).fieldOf("cluster_probability").forGetter(p_229073_ -> p_229073_.clusterProbability) ) .apply(p_229075_, OceanRuinStructure::new) ); ``` 下面是对应的structure的json文件,你可以看到对应的字段 ```json { "type": "minecraft:ocean_ruin", "biome_temp": "cold", "biomes": "#minecraft:has_structure/ocean_ruin_cold", "cluster_probability": 0.9, "large_probability": 0.3, "spawn_overrides": {}, "step": "surface_structures" } ``` 好了,然后我们来看StructureSettings类,StructureSettings类是在构造Structure时候要传入的一个参数,是对Structure的配置信息。点开源码你会发现使用了一个纪录类,是Java16新增的一功能,主要有这样的几个字段 biomes:一个HolderSet,表示结构可以生成的生物群落。 spawnOverrides:一个Map\,用于指定哪些生物类别应该在结构中生成,以及它们的生成规则。 step:一个GenerationStep.Decoration,表示结构生成的阶段。 terrainAdaptation:一个TerrainAdjustment,用于指定结构如何适应地形。 那么我们应该怎么去创建这样的一个类呢? ```java // 第一种方法是直接使用structure类提供的方法 public static Structure.StructureSettings structure( HolderSet pBiomes, Map pSpawnOverrides, GenerationStep.Decoration pStep, TerrainAdjustment pTerrainAdaptation ) { return new Structure.StructureSettings(pBiomes, pSpawnOverrides, pStep, pTerrainAdaptation); } private static Structure.StructureSettings structure(HolderSet pBiomes, GenerationStep.Decoration pStep, TerrainAdjustment pTerrainAdaptation) { return structure(pBiomes, Map.of(), pStep, pTerrainAdaptation); } private static Structure.StructureSettings structure(HolderSet pBiomes, TerrainAdjustment pTerrainAdaptation) { return structure(pBiomes, Map.of(), GenerationStep.Decoration.SURFACE_STRUCTURES, pTerrainAdaptation); } // 第二中是直接new new Structure.StructureSettings(pBiomes, pSpawnOverrides, pStep, pTerrainAdaptation); ``` 而其中的pSpawnOverrides可传入一个空的map的,pStep是Decoration下的枚举,对于地表的建筑可以直接使用SURFACE\_STRUCTURES,对于pTerrainAdaptation是TerrainAdjustment下的枚举。 接下来,来看GenerationStub类,这个了类是我们要实现的Structure中的findGenerationPoint要求你返回的一个内容,他说主要有两个字段第一个position,这个position是用于判断生物群系是否合法的。第二个字段是,generator:一个Either\类型的字段,你要传入的一个StructurePiecesBuilder的消费者,用于你去编写生成的位置和生成那些建组的piece。你也同样可以附加一些旋转等随机化的内容。 创建这样的一个GenerationStub类型也有多种方法 ```java // 第一种是调用我们的onTopOfChunkCenter方法,onTopOfChunkCenter方法是Strucutre提供的方法,、 // 第一个参数是GenerationContext生成的上下文信息,第二个参数是Heightmap.Types的枚举,你可以自己选择,具体怎么选择参考原版的代码。第三个是你的 @Override public Optional findGenerationPoint(Structure.GenerationContext pContext) { return getLowestY(pContext, this.width, this.depth) < pContext.chunkGenerator().getSeaLevel() ? Optional.empty() : onTopOfChunkCenter(pContext, Heightmap.Types.WORLD_SURFACE_WG, p_226545_ -> this.generatePieces(p_226545_, pContext)); } // 第二中则是直接new出来 // 第一个参数是用于生物群系判断的位置,第二个参数是 @Override public Optional findGenerationPoint(Structure.GenerationContext pContext) { Rotation rotation = Rotation.getRandom(pContext.random()); BlockPos blockpos = this.getLowestYIn5by5BoxOffset7Blocks(pContext, rotation); return blockpos.getY() < 60 ? Optional.empty() : Optional.of(new Structure.GenerationStub(blockpos, p_230240_ -> this.generatePieces(p_230240_, pContext, blockpos, rotation))); } // 直接调用的onTopOfChunkCenter @Override public Optional findGenerationPoint(Structure.GenerationContext pContext) { return onTopOfChunkCenter(pContext, Heightmap.Types.WORLD_SURFACE_WG, p_229979_ -> generatePieces(p_229979_, pContext)); } ``` 下面来看看findGenerationPoint方法中的参数GenerationContext类的内容: * RegistryAccess registryAccess: 注册表访问对象 * ChunkGenerator chunkGenerator: 区块生成器 * BiomeSource biomeSource: 生物群系源 * RandomState randomState: 随机状态 * StructureTemplateManager structureTemplateManager: 结构模板管理器 * WorldgenRandom random: 世界生成随机数 * long seed: 种子 * ChunkPos chunkPos: 区块位置 * LevelHeightAccessor heightAccessor: 高度访问器 * Predicate\ validBiome: 有效生物群系的谓词 可以看到GenerationContext这个类就是保存了一些生成信息,我们可以使用这些信息来生成自己的结构。 StructureType类,这个类是我们的structure对于的一个内容,有点类似我们的entity和EntityType这样的关系,这里的StructureType的作用就是为了标注不同的Structure。每一个structure对应了一个structureType类型。我们来看看怎么创建一个StructureType ```java public class ModStructureType { public static final DeferredRegister> STRUCTURE_TYPES = DeferredRegister.create(Registries.STRUCTURE_TYPE, ExampleMod.MODID); public static final DeferredHolder, StructureType> MY_STRUCTURE = registerType("my_structure", () -> () -> MyStructure.CODEC); // 也许你会好奇为什么这里的要求一个supplier提供StructureType,但是给出的是两个() -> () -> MyStructure.CODEC,并且是一个Codec // 其实你点开StructureType,会发现这就是一个interface,只有一个方法,就是要求一个没有参数,返回值是codec的函数, // 所以() -> MyStructure.CODEC 这个lammbda就是对应StructureType接口的一个实现。 private static

DeferredHolder, StructureType

> registerType(String name, Supplier> factory) { return STRUCTURE_TYPES.register(name, factory); } public static void register(IEventBus eventBus){ STRUCTURE_TYPES.register(eventBus); } } ``` 好了,到这里位置我们已经讲解完了structure的内容,并说明了两个需要从重写方法的内容。不过还有一点没解决的就是在findGenerationPoint中要提供的那个Consumer没有说怎么构造。而这个方法主要是调用piece的内容,让我们来看看structurePiece的内哦让那个吧。 我们还是先把我们的所有的structurepiece的内容放出来。 ```java public class MyStructurePieces { static final ResourceLocation STRUCTURE_LOCATION_MY_STRUCTURE = new ResourceLocation(ExampleMod.MODID,"my_structure"); static final Map PIVOTS = ImmutableMap.of( STRUCTURE_LOCATION_MY_STRUCTURE, new BlockPos(4, 0, 4) ); static final Map OFFSETS = ImmutableMap.of( STRUCTURE_LOCATION_MY_STRUCTURE, BlockPos.ZERO ); public static void addPieces( StructureTemplateManager pStructureTemplateManager, BlockPos pStartPos, Rotation pRotation, StructurePieceAccessor pPieces, RandomSource pRandom ) { pPieces.addPiece(new MyStructurePieces.MyStructurePiece(pStructureTemplateManager, STRUCTURE_LOCATION_MY_STRUCTURE, pStartPos, pRotation, 0)); } public static class MyStructurePiece extends TemplateStructurePiece { public MyStructurePiece(StructureTemplateManager pStructureTemplateManager, ResourceLocation pLocation, BlockPos pStartPos, Rotation pRotation, int pDown) { super(ModStructurePieceTypes.MY_STRUCTURE_PIECE.get(), 0, pStructureTemplateManager, pLocation, pLocation.toString(), makeSettings(pRotation, pLocation), makePosition(pLocation, pStartPos, pDown) ); } public MyStructurePiece(StructureTemplateManager pStructureTemplateManager, CompoundTag pTag) { super(ModStructurePieceTypes.MY_STRUCTURE_PIECE.get(), pTag, pStructureTemplateManager, resourceLocation -> makeSettings(Rotation.valueOf(pTag.getString("Rot")), resourceLocation)); } private static StructurePlaceSettings makeSettings(Rotation pRotation, ResourceLocation pLocation) { return new StructurePlaceSettings() .setRotation(pRotation) .setMirror(Mirror.NONE) .setRotationPivot(MyStructurePieces.PIVOTS.get(pLocation)) .addProcessor(BlockIgnoreProcessor.STRUCTURE_BLOCK); } private static BlockPos makePosition(ResourceLocation pLocation, BlockPos pPos, int pDown) { return pPos.offset(MyStructurePieces.OFFSETS.get(pLocation)).below(pDown); } @Override protected void addAdditionalSaveData(StructurePieceSerializationContext pContext, CompoundTag pTag) { super.addAdditionalSaveData(pContext, pTag); pTag.putString("Rot", this.placeSettings.getRotation().name()); } @Override protected void handleDataMarker(String pName, BlockPos pPos, ServerLevelAccessor pLevel, RandomSource pRandom, BoundingBox pBox) { if ("chest".equals(pName)) { pLevel.setBlock(pPos, Blocks.AIR.defaultBlockState(), 3); BlockEntity blockentity = pLevel.getBlockEntity(pPos.below()); if (blockentity instanceof ChestBlockEntity) { ((ChestBlockEntity)blockentity).setLootTable(BuiltInLootTables.IGLOO_CHEST, pRandom.nextLong()); } } } @Override public void postProcess( WorldGenLevel pLevel, StructureManager pStructureManager, ChunkGenerator pGenerator, RandomSource pRandom, BoundingBox pBox, ChunkPos pChunkPos, BlockPos pPos ) { super.postProcess(pLevel, pStructureManager, pGenerator, pRandom, pBox, pChunkPos, pPos); } } } public class MyStructurePieces { static final ResourceLocation STRUCTURE_LOCATION_MY_STRUCTURE = new ResourceLocation(ExampleMod.MODID,"my_structure"); static final Map PIVOTS = ImmutableMap.of( STRUCTURE_LOCATION_MY_STRUCTURE, new BlockPos(4, 0, 4) ); static final Map OFFSETS = ImmutableMap.of( STRUCTURE_LOCATION_MY_STRUCTURE, BlockPos.ZERO ); public static void addPieces( StructureTemplateManager pStructureTemplateManager, BlockPos pStartPos, Rotation pRotation, StructurePieceAccessor pPieces, RandomSource pRandom ) { pPieces.addPiece(new MyStructurePieces.MyStructurePiece(pStructureTemplateManager, STRUCTURE_LOCATION_MY_STRUCTURE, pStartPos, pRotation, 0)); } public static class MyStructurePiece extends TemplateStructurePiece { public MyStructurePiece(StructureTemplateManager pStructureTemplateManager, ResourceLocation pLocation, BlockPos pStartPos, Rotation pRotation, int pDown) { super(ModStructurePieceTypes.MY_STRUCTURE_PIECE.get(), 0, pStructureTemplateManager, pLocation, pLocation.toString(), makeSettings(pRotation, pLocation), makePosition(pLocation, pStartPos, pDown) ); } public MyStructurePiece(StructureTemplateManager pStructureTemplateManager, CompoundTag pTag) { super(ModStructurePieceTypes.MY_STRUCTURE_PIECE.get(), pTag, pStructureTemplateManager, resourceLocation -> makeSettings(Rotation.valueOf(pTag.getString("Rot")), resourceLocation)); } private static StructurePlaceSettings makeSettings(Rotation pRotation, ResourceLocation pLocation) { return new StructurePlaceSettings() .setRotation(pRotation) .setMirror(Mirror.NONE) .setRotationPivot(MyStructurePieces.PIVOTS.get(pLocation)) .addProcessor(BlockIgnoreProcessor.STRUCTURE_BLOCK); } private static BlockPos makePosition(ResourceLocation pLocation, BlockPos pPos, int pDown) { return pPos.offset(MyStructurePieces.OFFSETS.get(pLocation)).below(pDown); } @Override protected void addAdditionalSaveData(StructurePieceSerializationContext pContext, CompoundTag pTag) { super.addAdditionalSaveData(pContext, pTag); pTag.putString("Rot", this.placeSettings.getRotation().name()); } @Override protected void handleDataMarker(String pName, BlockPos pPos, ServerLevelAccessor pLevel, RandomSource pRandom, BoundingBox pBox) { if ("chest".equals(pName)) { pLevel.setBlock(pPos, Blocks.AIR.defaultBlockState(), 3); BlockEntity blockentity = pLevel.getBlockEntity(pPos.below()); if (blockentity instanceof ChestBlockEntity) { ((ChestBlockEntity)blockentity).setLootTable(BuiltInLootTables.IGLOO_CHEST, pRandom.nextLong()); } } } @Override public void postProcess( WorldGenLevel pLevel, StructureManager pStructureManager, ChunkGenerator pGenerator, RandomSource pRandom, BoundingBox pBox, ChunkPos pChunkPos, BlockPos pPos ) { super.postProcess(pLevel, pStructureManager, pGenerator, pRandom, pBox, pChunkPos, pPos); } } } ``` 我们使用的是一个TemplateStructurePiece的类,不过先说说他的父类的StructurePiece吧,下面是StructurePiece是我们会常用到的三个方法,其中两个是继承要是实现的方法。 ```java // 该方法是传入一个接口序列化的上下文信息和一个nbt数据 // 作用是在序列化之后,从nbt读取数据之前,你可以向nbt数据附加一些内容。用于你的结构化生成。 protected abstract void addAdditionalSaveData(StructurePieceSerializationContext pContext, CompoundTag pTag); // 该方法用于处理标志的数据的 // 例如你要给你一个结构中的一些位置添加战利品箱子,你可以利用这个方法来实现。 protected abstract void handleDataMarker(String pName, BlockPos pPos, ServerLevelAccessor pLevel, RandomSource pRandom, BoundingBox pBox); //postProcess的作用是在结构生成之后,对结构piece的进一步的处理和修饰。 // 不过也可以在生成之前操作。等会我们会看到怎么操作。 // 这里的参数的类名也比较清晰了就不过多介绍了。 @Override public void postProcess( WorldGenLevel pLevel, StructureManager pStructureManager, ChunkGenerator pGenerator, RandomSource pRandom, BoundingBox pBox, ChunkPos pChunkPos, BlockPos pPos ) ``` 好了,让我们来看TemplateStructurePiece,这是一个StructurePiece类的具体实现,用于表示一个由结构模板生成的结构方块。 ```java // 第一个构造函数 public TemplateStructurePiece( StructurePieceType pType,// structurepiecetype 需要我们去注册的。 int pGenDepth, // 结构生成时候的深度 StructureTemplateManager pStructureTemplateManager, // 结构模板管理器 ResourceLocation pLocation, // 模板的位置 String pTemplateName, // 模板的名称 StructurePlaceSettings pPlaceSettings, // 结构piece放置的设置 BlockPos pTemplatePosition // 结构piece放置的位置 ) public TemplateStructurePiece( StructurePieceType pType, // piecetype 需要我们自己注册 CompoundTag pTag, // nbt StructureTemplateManager pStructureTemplateManager, // 结构模板管理器 Function pPlaceSettingsFactory // 用于创建 StructurePlaceSettings 实例的函数 ) // 给nbt添加了放置的位置的数据 @Override protected void addAdditionalSaveData(StructurePieceSerializationContext pContext, CompoundTag pTag) { pTag.putInt("TPX", this.templatePosition.getX()); pTag.putInt("TPY", this.templatePosition.getY()); pTag.putInt("TPZ", this.templatePosition.getZ()); pTag.putString("Template", this.templateName); } // 该类对该方法进行了实现,主要的功能就是放置结构piece模板到世界中,并调用handleDataMarker处理标记数据。 @Override public void postProcess( WorldGenLevel pLevel, StructureManager pStructureManager, ChunkGenerator pGenerator, RandomSource pRandom, BoundingBox pBox, ChunkPos pChunkPos, BlockPos pPos ) ``` 下面来看我们的piece的类吧 ```java public static class MyStructurePiece extends TemplateStructurePiece { // 构造方法1,等会我们在structure中讲,怎么new,也就是我们刚刚没说的方法addpiece方法。 public MyStructurePiece(StructureTemplateManager pStructureTemplateManager, ResourceLocation pLocation, BlockPos pStartPos, Rotation pRotation, int pDown) { super(ModStructurePieceTypes.MY_STRUCTURE_PIECE.get(), 0, pStructureTemplateManager, pLocation, pLocation.toString(), makeSettings(pRotation, pLocation), makePosition(pLocation, pStartPos, pDown) ); } // 构造方法二 public MyStructurePiece(StructureTemplateManager pStructureTemplateManager, CompoundTag pTag) { super(ModStructurePieceTypes.MY_STRUCTURE_PIECE.get(), pTag, pStructureTemplateManager, resourceLocation -> makeSettings(Rotation.valueOf(pTag.getString("Rot")), resourceLocation)); } // 辅助函数用于生成StructurePlaceSettings,第一个参数是构建时候传入的随机数 private static StructurePlaceSettings makeSettings(Rotation pRotation, ResourceLocation pLocation) { return new StructurePlaceSettings() .setRotation(pRotation) .setMirror(Mirror.NONE) .setRotationPivot(MyStructurePieces.PIVOTS.get(pLocation)) .addProcessor(BlockIgnoreProcessor.STRUCTURE_BLOCK); } // 辅助函数用于返回piece生成的位置 private static BlockPos makePosition(ResourceLocation pLocation, BlockPos pPos, int pDown) { return pPos.offset(MyStructurePieces.OFFSETS.get(pLocation)).below(pDown); } // 给nbt添加一个rot的字段,表示随机旋转的信息。 @Override protected void addAdditionalSaveData(StructurePieceSerializationContext pContext, CompoundTag pTag) { super.addAdditionalSaveData(pContext, pTag); pTag.putString("Rot", this.placeSettings.getRotation().name()); } // 下面还是我们的piece类中的hhandleDataMarker // 这个主要处理了结构模板中的箱子,并放置战利品箱子 // 其中的这个chest数据你可以到对应的nbt下看,nbt打开格式需要你用idea装下那个minecraft development插件 protected void handleDataMarker(String pName, BlockPos pPos, ServerLevelAccessor pLevel, RandomSource pRandom, BoundingBox pBox) { if ("chest".equals(pName)) { pLevel.setBlock(pPos, Blocks.AIR.defaultBlockState(), 3); BlockEntity blockentity = pLevel.getBlockEntity(pPos.below()); if (blockentity instanceof ChestBlockEntity) { ((ChestBlockEntity)blockentity).setLootTable(BuiltInLootTables.IGLOO_CHEST, pRandom.nextLong()); } } } // 下面是我们自己类中的postProcess的方法,我们说下如何在生成前处理,或者生成后处理。 @Override public void postProcess( WorldGenLevel pLevel, StructureManager pStructureManager, ChunkGenerator pGenerator, RandomSource pRandom, BoundingBox pBox, ChunkPos pChunkPos, BlockPos pPos ) { // 对生成前进行处理的话 // 写在这里 super.postProcess(pLevel, pStructureManager, pGenerator, pRandom, pBox, pChunkPos, pPos); // 生成后处理 // 写在这里 } } ``` 让我们回到strcutre类中,来看这个方法 ```java private void generatePieces(StructurePiecesBuilder pBuilder, Structure.GenerationContext pContext) { ChunkPos chunkpos = pContext.chunkPos(); WorldgenRandom worldgenrandom = pContext.random(); BlockPos blockpos = new BlockPos(chunkpos.getMinBlockX(), 64, chunkpos.getMinBlockZ()); Rotation rotation = Rotation.getRandom(worldgenrandom); MyStructurePieces.addPieces(pContext.structureTemplateManager(), blockpos, rotation, pBuilder, worldgenrandom); } ``` StructurePiecesBuilder类实现了StructurePieceAccessor接口,StructurePiecesBuilder是管理piece的类,你需要通过这个类向你的建组添加piece,通过addpieces方法,这里这里我们获得了随机数和位置信息,以及一个随机的旋转方向,传入给了辅助的方法addPieces。 以下是辅助方法的内容: ```java public static void addPieces( StructureTemplateManager pStructureTemplateManager, BlockPos pStartPos, Rotation pRotation, StructurePieceAccessor pPieces, RandomSource pRandom ) { pPieces.addPiece(new MyStructurePieces.MyStructurePiece(pStructureTemplateManager, , pStartPos, pRotation, 0)); } ``` 好了,下面来看看这个MyStructurePiece的构造方法,这个是我们写的方法,其中的我们在父类的构造方法中需要传入一个StructurePlaceSettings的东西,这个StructurePlaceSettings是我们的在MyStructurePiece类中的makeSettings方法中获得的。 ```java // 可以看出来是可以直接new出来的,new出来之后,你可以根据自己的需求进行设置,这里的设置了一些内容, // 比如设置piece的旋转 // 设置mirror镜像 // 设置旋转点 // 以及最后一个addProcessor,表示添加一个新的StructureProcessor,这里用的是BlockIgnoreProcessor下面的STRUCTURE_BLOCK, // 表示生成的时候不要生成STRUCTURE_BLOCK方块。 private static StructurePlaceSettings makeSettings(Rotation pRotation, ResourceLocation pLocation) { return new StructurePlaceSettings() .setRotation(pRotation) .setMirror(Mirror.NONE) .setRotationPivot(MyStructurePieces.PIVOTS.get(pLocation)) .addProcessor(BlockIgnoreProcessor.STRUCTURE_BLOCK); } ``` 到此为止,关于,strcture和strcutrepiece相关的内容就讲解完毕了,你也可以去看看其他的建筑的生成相关的内容学习。 好了下面我们来说于注册相关的内容,首先要解决的是关于structure中的type返回的StructureType内容。注册strcturetype就如上面说的一样,就不在重复赘述了,这里直接给出注册的代码和type的代码 ```java // 注册StructureType方法 public class ModStructureType { public static final DeferredRegister> STRUCTURE_TYPES = DeferredRegister.create(Registries.STRUCTURE_TYPE, ExampleMod.MODID); public static final DeferredHolder, StructureType> MY_STRUCTURE = registerType("my_structure", () -> () -> MyStructure.CODEC); private static

DeferredHolder, StructureType

> registerType(String name, Supplier> factory) { return STRUCTURE_TYPES.register(name, factory); } public static void register(IEventBus eventBus){ STRUCTURE_TYPES.register(eventBus); } } ``` type方法 ```java @Override public StructureType type() { return ModStructureType.MY_STRUCTURE.get(); } ``` 下面我们在解决StrcturePieceType的问题,在StrcturePiece的构造方法中需要你传入StructurePieceType的实例,这里我们给出注册的方法。 ```java //StructurePieceType注册 public class ModStructurePieceTypes { // 注册器 public static final DeferredRegister STRUCTURE_PIECE_TYPES = DeferredRegister.create(Registries.STRUCTURE_PIECE, ExampleMod.MODID); // 注册的对象 public static final DeferredHolder MY_STRUCTURE_PIECE = registerPieceType("my_structure_piece", MyStructurePieces.MyStructurePiece::new); // 辅助注册的方法 private static DeferredHolder registerPieceType(String name, StructurePieceType.StructureTemplateType structurePieceType) { // 第一个参数是name,第二个参数是构造方法 // 第二个参数补充一点是StructureTemplateType是一个接口,只有一个方法,其实就是要求传入一个该接口的实例,这个接口实现方法应该传入 // 两个StructureTemplateManager和CompoundTag参数返回一个StructurePiece // 所以这里可以直接是使用new构造方法。 // 而register要求返回一个supplier,所以就这样写了。 return STRUCTURE_PIECE_TYPES.register(name.toLowerCase(Locale.ROOT), () -> structurePieceType); } // 别忘记添加到总线 public static void register(IEventBus eventBus){ STRUCTURE_PIECE_TYPES.register(eventBus); } } ``` StructureSets类是描述你就建筑在世界上的生成的情况的,比如建组在地图上的生成策略,例如随机生成,或者是像暮色那样的棋盘形状,salt是一个盐或者说是随机种子数,要保持唯一的数值不要和其他的建筑的数值一样,不然可能随机生成的位置会发生冲突,以及最小间距等等。不过这个类主要的功能是生成json文件,其实整个建筑的生成都可以使用json文件而不编写代码,不过我们既然可以写代码,还是采用了代码的形势,感觉是比写json来更加直观和容易理解一些,也采取了和原版类似的方式,方便对大家阅读理解原版的代码。 ```java public class ModStructureSets { // 我们structureset的资源名称 public static final ResourceKey MY_STRUCTURE_SET = register("my_structure"); // 等会使用数据生成要要传递的方法引用 public static void bootstrap(BootstapContext pContext) { // BootstapContext数据生成时候的上下文 // 从上下文中获得HolderGetter HolderGetter holdergetter = pContext.lookup(Registries.STRUCTURE); // 通过上下文注册我们的set // 第一个参数是我们的key // 第二个参数是set的实例,new处理 // set的第一个参数是对应的strcture,这里通过holder获得,第二个参数就是描述放置的信息的类 // RandomSpreadStructurePlacement是随机放置,也有另一个ConcentricRingsStructurePlacement放置,他们继承于StructurePlacement // 我们使用了随机放置 // 随机放置的第一个参数是当前位置生成失败时候寻找下一生成点的距离 // 第二个参数建组之间的最小间隔。 // 第三个参数是随机放置的类型,这里填的线性的, 也有其他的可以选择。 // 第四个参数是盐 pContext.register( ModStructureSets.MY_STRUCTURE_SET, new StructureSet(holdergetter.getOrThrow(ModStructures.MY_STRUCTURE), new RandomSpreadStructurePlacement(32, 8, RandomSpreadType.LINEAR, 14357619)) ); } private static ResourceKey register(String pName) { return ResourceKey.create(Registries.STRUCTURE_SET, new ResourceLocation(ExampleMod.MODID,pName)); } } ``` 以上代码其实就是用于生成json,而关于json的内容,你看可以在wiki上查到的。这里贴出wiki地址:https://minecraft.wiki/w/Structure\_set Structures类,同样适用于生成json的内容,不过这个描述的建组的生成的生物群系,以及生成的阶段。 ```java public class ModStructures { // 以下的三个辅助的方法用于获得StructureSettings这个类 // 和之前讲解StructureSettings的一致,就不赘述了。 public static Structure.StructureSettings structure( HolderSet pBiomes, Map pSpawnOverrides, GenerationStep.Decoration pStep, TerrainAdjustment pTerrainAdaptation ) { return new Structure.StructureSettings(pBiomes, pSpawnOverrides, pStep, pTerrainAdaptation); } private static Structure.StructureSettings structure(HolderSet pBiomes, GenerationStep.Decoration pStep, TerrainAdjustment pTerrainAdaptation) { return structure(pBiomes, Map.of(), pStep, pTerrainAdaptation); } private static Structure.StructureSettings structure(HolderSet pBiomes, TerrainAdjustment pTerrainAdaptation) { return structure(pBiomes, Map.of(), GenerationStep.Decoration.SURFACE_STRUCTURES, pTerrainAdaptation); } // strucutre的key public static final ResourceKey MY_STRUCTURE = registerKey("my_structure"); // 创建key的方法 public static ResourceKey registerKey(String name) { return ResourceKey.create(Registries.STRUCTURE, new ResourceLocation(ExampleMod.MODID,name)); } // bootstrap在数据生成时候调用 public static void bootstrap(BootstapContext context) { // 从上下文中获得所有的生物群系 HolderGetter biomeHolderGetter = context.lookup(Registries.BIOME); // 第一个参数是key // 第二个参数就是我们的strucutre的构造 // 构造的要传入一个StructureSettings // setting的一个参数是生物群系,我们指定了生物群系是主世界,对于第二个参数设置的是TerrainAdjustment.NONE,当然你也可以选择其他的。 context.register( ModStructures.MY_STRUCTURE, new MyStructure(structure(biomeHolderGetter.getOrThrow(BiomeTags.IS_OVERWORLD), TerrainAdjustment.NONE)) ); } } ``` 总线 ```java public ExampleMod(IEventBus modEventBus) { modEventBus.addListener(this::commonSetup); ModStructureType.register(modEventBus); ModStructurePieceTypes.register(modEventBus); NeoForge.EVENT_BUS.register(this); } ``` 以及数据生成 ```java public class ModWorldGen extends DatapackBuiltinEntriesProvider { public static final RegistrySetBuilder BUILDER = new RegistrySetBuilder() .add(Registries.CONFIGURED_FEATURE, ModOreFeatures::bootstrap) .add(Registries.PLACED_FEATURE, ModOrePlacements::bootstrap) .add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, ModBiomeModifiers::bootstrap) .add(Registries.STRUCTURE_SET, ModStructureSets::bootstrap) .add(Registries.STRUCTURE, ModStructures::bootstrap); public ModWorldGen(PackOutput output, CompletableFuture registries) { super(output, registries, BUILDER,Set.of(ExampleMod.MODID)); } } ``` 添加到事件。 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public class ModDataGeneratorHandler { @SubscribeEvent public static void gatherData(GatherDataEvent event){ ExistingFileHelper efh = event.getExistingFileHelper(); var lp = event.getLookupProvider(); //world gen event.getGenerator().addProvider(event.includeServer(), (DataProvider.Factory) pOutput -> new ModWorldGen(pOutput,lp)); } } ``` --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_43_StructureGeneration.md description: 43 结构生成 相关教程 --- # 结构生成 好了这次我们来说结构化生成,这次涉及到的东西比较多,且内容比较杂乱,所以大家自己多看看。 我们主要的一个类是Structure类,这个类是你生成结构的主体类,不过你的建筑是可以多个结构拼在一起的,所以关联到StructurePieces类,这个类表示结构的某一部分,然后你可以通过拼接多个StructurePieces来实现你的Structure,你也可以使用多个StructurePieces的拼接来构建不同的建筑。 而建筑是有两种方式可以获得的,第一种方式就是类似原版的沙漠金字塔和雨林的神庙,这个两个建筑的部分是硬编码在代码中的。第二种方法是使用nbt数据,而nbt数据可以通过你在游戏中建造相应的建组后通天特殊的structure的方块保存为nbt数据的,之后我们的代码通过nbt数据实现生成对应的建组,并且你可以建筑生成前和生成后做一些处理,例如添加一些奖励箱。 先大概看下我们的代码吧: ```java public class MyStructure extends Structure { public static final Codec CODEC = simpleCodec(MyStructure::new); public MyStructure(StructureSettings pSettings) { super(pSettings); } @Override protected Optional findGenerationPoint(GenerationContext pContext) { return onTopOfChunkCenter(pContext, Heightmap.Types.WORLD_SURFACE_WG, structurePiecesBuilder -> this.generatePieces(structurePiecesBuilder, pContext)); } private void generatePieces(StructurePiecesBuilder pBuilder, Structure.GenerationContext pContext) { ChunkPos chunkpos = pContext.chunkPos(); WorldgenRandom worldgenrandom = pContext.random(); BlockPos blockpos = new BlockPos(chunkpos.getMinBlockX(), 64, chunkpos.getMinBlockZ()); Rotation rotation = Rotation.getRandom(worldgenrandom); MyStructurePieces.addPieces(pContext.structureTemplateManager(), blockpos, rotation, pBuilder, worldgenrandom); } @Override public StructureType type() { return ModStructureType.MY_STRUCTURE.get(); } } ``` 让我们来看看相关的类的以及配置等相关用到了的类把。 首先就是Structure类,该类是结构的基类,通过继承关系你可以看到对应的其他的子类,你看与参考学习其他的子类实现自己的结构,我们来说其中的字段和方法的含义: * DIRECT\_CODEC 和 CODEC:这两个字段是用于序列化和反序列化 Structure 对象的编解码器。 * settings:表示结构的设置,包括生物群系、生成步骤等。 * settingsCodec 和 simpleCodec:这两个方法用于创建 Structure 对象的编解码器。 * 构造函数 Structure(Structure.StructureSettings pSettings):用于创建 Structure 对象,接受 StructureSettings 作为参数。 * biomes()、spawnOverrides()、step() 和 terrainAdaptation():这些方法返回结构的相关设置。 * adjustBoundingBox(BoundingBox pBoundingBox):根据地形适应设置调整结构的边界框。 * generate(...):生成结构的主要方法,接受多个参数,如注册表访问器、区块生成器、生物群系源等。 * onTopOfChunkCenter(...):在区块中心的顶部生成结构的辅助方法。 * isValidBiome(Structure.GenerationStub pStub, Structure.GenerationContext pContext):检查给定位置的生物群系是否适合生成结构。 * afterPlace(...):在生成结构后执行的方法,可用于后处理。 * getCornerHeights(...) 和 getLowestY(...):计算结构的角高度和最低高度的辅助方法。 * findGenerationPoint(Structure.GenerationContext pContext) 和 findValidGenerationPoint(Structure.GenerationContext pContext):查找结构的生成点的抽象方法和具体实现方法 * type():返回结构的类型的抽象方法。 其中的findGenerationPoint方法和type方法是子类必须要实现的方法,也是我们使用的方法,其中findGenerationPoint方法是我们使用决定建筑的生成的位置的,而type需要我们返回strucutreType类的实例,之后我们会说怎么闯将StructureType的实例。 我们之前一直在回避codec这个概念,不过现在我们无法在回避他了,因为我们会用到他,Codec是新增的一套序列化和反序列的系统,用于同时处理nbt和json。目前看来似乎要在未来转向codec了。 不过基于我并不了解这个东西,所以这里还是说以下怎么在strucutre中创建这个codec吧,我们需要使用到这个codec获得StructureType。 ```java //一种简单的方法就是通过Structure提供的方法这样创建,这样的创建简单的结构对于我们来说已经是够用了。 // 直接传入你的structure的构造方法即可。 public static final Codec CODEC = simpleCodec(OceanMonumentStructure::new); // 另一种是自己使用RecordCodecBuilder.create方法类构建 // 其中你可以在通过Codec读取json文件中的额外属性 public static final Codec CODEC = RecordCodecBuilder.create( p_229075_ -> p_229075_.group( settingsCodec(p_229075_), OceanRuinStructure.Type.CODEC.fieldOf("biome_temp").forGetter(p_229079_ -> p_229079_.biomeTemp), Codec.floatRange(0.0F, 1.0F).fieldOf("large_probability").forGetter(p_229077_ -> p_229077_.largeProbability), Codec.floatRange(0.0F, 1.0F).fieldOf("cluster_probability").forGetter(p_229073_ -> p_229073_.clusterProbability) ) .apply(p_229075_, OceanRuinStructure::new) ); ``` 下面是对应的structure的json文件,你可以看到对应的字段 ```json { "type": "minecraft:ocean_ruin", "biome_temp": "cold", "biomes": "#minecraft:has_structure/ocean_ruin_cold", "cluster_probability": 0.9, "large_probability": 0.3, "spawn_overrides": {}, "step": "surface_structures" } ``` 好了,然后我们来看StructureSettings类,StructureSettings类是在构造Structure时候要传入的一个参数,是对Structure的配置信息。点开源码你会发现使用了一个纪录类,是Java16新增的一功能,主要有这样的几个字段 biomes:一个HolderSet,表示结构可以生成的生物群落。 spawnOverrides:一个Map\,用于指定哪些生物类别应该在结构中生成,以及它们的生成规则。 step:一个GenerationStep.Decoration,表示结构生成的阶段。 terrainAdaptation:一个TerrainAdjustment,用于指定结构如何适应地形。 那么我们应该怎么去创建这样的一个类呢? ```java // 第一种方法是直接使用structure类提供的方法 public static Structure.StructureSettings structure( HolderSet pBiomes, Map pSpawnOverrides, GenerationStep.Decoration pStep, TerrainAdjustment pTerrainAdaptation ) { return new Structure.StructureSettings(pBiomes, pSpawnOverrides, pStep, pTerrainAdaptation); } private static Structure.StructureSettings structure(HolderSet pBiomes, GenerationStep.Decoration pStep, TerrainAdjustment pTerrainAdaptation) { return structure(pBiomes, Map.of(), pStep, pTerrainAdaptation); } private static Structure.StructureSettings structure(HolderSet pBiomes, TerrainAdjustment pTerrainAdaptation) { return structure(pBiomes, Map.of(), GenerationStep.Decoration.SURFACE_STRUCTURES, pTerrainAdaptation); } // 第二中是直接new new Structure.StructureSettings(pBiomes, pSpawnOverrides, pStep, pTerrainAdaptation); ``` 而其中的pSpawnOverrides可传入一个空的map的,pStep是Decoration下的枚举,对于地表的建筑可以直接使用SURFACE\_STRUCTURES,对于pTerrainAdaptation是TerrainAdjustment下的枚举。 接下来,来看GenerationStub类,这个了类是我们要实现的Structure中的findGenerationPoint要求你返回的一个内容,他说主要有两个字段第一个position,这个position是用于判断生物群系是否合法的。第二个字段是,generator:一个Either\类型的字段,你要传入的一个StructurePiecesBuilder的消费者,用于你去编写生成的位置和生成那些建组的piece。你也同样可以附加一些旋转等随机化的内容。 创建这样的一个GenerationStub类型也有多种方法 ```java // 第一种是调用我们的onTopOfChunkCenter方法,onTopOfChunkCenter方法是Strucutre提供的方法,、 // 第一个参数是GenerationContext生成的上下文信息,第二个参数是Heightmap.Types的枚举,你可以自己选择,具体怎么选择参考原版的代码。第三个是你的 @Override public Optional findGenerationPoint(Structure.GenerationContext pContext) { return getLowestY(pContext, this.width, this.depth) < pContext.chunkGenerator().getSeaLevel() ? Optional.empty() : onTopOfChunkCenter(pContext, Heightmap.Types.WORLD_SURFACE_WG, p_226545_ -> this.generatePieces(p_226545_, pContext)); } // 第二中则是直接new出来 // 第一个参数是用于生物群系判断的位置,第二个参数是 @Override public Optional findGenerationPoint(Structure.GenerationContext pContext) { Rotation rotation = Rotation.getRandom(pContext.random()); BlockPos blockpos = this.getLowestYIn5by5BoxOffset7Blocks(pContext, rotation); return blockpos.getY() < 60 ? Optional.empty() : Optional.of(new Structure.GenerationStub(blockpos, p_230240_ -> this.generatePieces(p_230240_, pContext, blockpos, rotation))); } // 直接调用的onTopOfChunkCenter @Override public Optional findGenerationPoint(Structure.GenerationContext pContext) { return onTopOfChunkCenter(pContext, Heightmap.Types.WORLD_SURFACE_WG, p_229979_ -> generatePieces(p_229979_, pContext)); } ``` 下面来看看findGenerationPoint方法中的参数GenerationContext类的内容: * RegistryAccess registryAccess: 注册表访问对象 * ChunkGenerator chunkGenerator: 区块生成器 * BiomeSource biomeSource: 生物群系源 * RandomState randomState: 随机状态 * StructureTemplateManager structureTemplateManager: 结构模板管理器 * WorldgenRandom random: 世界生成随机数 * long seed: 种子 * ChunkPos chunkPos: 区块位置 * LevelHeightAccessor heightAccessor: 高度访问器 * Predicate\ validBiome: 有效生物群系的谓词 可以看到GenerationContext这个类就是保存了一些生成信息,我们可以使用这些信息来生成自己的结构。 StructureType类,这个类是我们的structure对于的一个内容,有点类似我们的entity和EntityType这样的关系,这里的StructureType的作用就是为了标注不同的Structure。每一个structure对应了一个structureType类型。我们来看看怎么创建一个StructureType ```java public class ModStructureType { public static final DeferredRegister> STRUCTURE_TYPES = DeferredRegister.create(Registries.STRUCTURE_TYPE, ExampleMod.MODID); public static final DeferredHolder, StructureType> MY_STRUCTURE = registerType("my_structure", () -> () -> MyStructure.CODEC); // 也许你会好奇为什么这里的要求一个supplier提供StructureType,但是给出的是两个() -> () -> MyStructure.CODEC,并且是一个Codec // 其实你点开StructureType,会发现这就是一个interface,只有一个方法,就是要求一个没有参数,返回值是codec的函数, // 所以() -> MyStructure.CODEC 这个lammbda就是对应StructureType接口的一个实现。 private static

DeferredHolder, StructureType

> registerType(String name, Supplier> factory) { return STRUCTURE_TYPES.register(name, factory); } public static void register(IEventBus eventBus){ STRUCTURE_TYPES.register(eventBus); } } ``` 好了,到这里位置我们已经讲解完了structure的内容,并说明了两个需要从重写方法的内容。不过还有一点没解决的就是在findGenerationPoint中要提供的那个Consumer没有说怎么构造。而这个方法主要是调用piece的内容,让我们来看看structurePiece的内哦让那个吧。 我们还是先把我们的所有的structurepiece的内容放出来。 ```java public class MyStructurePieces { static final ResourceLocation STRUCTURE_LOCATION_MY_STRUCTURE = new ResourceLocation(ExampleMod.MODID,"my_structure"); static final Map PIVOTS = ImmutableMap.of( STRUCTURE_LOCATION_MY_STRUCTURE, new BlockPos(4, 0, 4) ); static final Map OFFSETS = ImmutableMap.of( STRUCTURE_LOCATION_MY_STRUCTURE, BlockPos.ZERO ); public static void addPieces( StructureTemplateManager pStructureTemplateManager, BlockPos pStartPos, Rotation pRotation, StructurePieceAccessor pPieces, RandomSource pRandom ) { pPieces.addPiece(new MyStructurePieces.MyStructurePiece(pStructureTemplateManager, STRUCTURE_LOCATION_MY_STRUCTURE, pStartPos, pRotation, 0)); } public static class MyStructurePiece extends TemplateStructurePiece { public MyStructurePiece(StructureTemplateManager pStructureTemplateManager, ResourceLocation pLocation, BlockPos pStartPos, Rotation pRotation, int pDown) { super(ModStructurePieceTypes.MY_STRUCTURE_PIECE.get(), 0, pStructureTemplateManager, pLocation, pLocation.toString(), makeSettings(pRotation, pLocation), makePosition(pLocation, pStartPos, pDown) ); } public MyStructurePiece(StructureTemplateManager pStructureTemplateManager, CompoundTag pTag) { super(ModStructurePieceTypes.MY_STRUCTURE_PIECE.get(), pTag, pStructureTemplateManager, resourceLocation -> makeSettings(Rotation.valueOf(pTag.getString("Rot")), resourceLocation)); } private static StructurePlaceSettings makeSettings(Rotation pRotation, ResourceLocation pLocation) { return new StructurePlaceSettings() .setRotation(pRotation) .setMirror(Mirror.NONE) .setRotationPivot(MyStructurePieces.PIVOTS.get(pLocation)) .addProcessor(BlockIgnoreProcessor.STRUCTURE_BLOCK); } private static BlockPos makePosition(ResourceLocation pLocation, BlockPos pPos, int pDown) { return pPos.offset(MyStructurePieces.OFFSETS.get(pLocation)).below(pDown); } @Override protected void addAdditionalSaveData(StructurePieceSerializationContext pContext, CompoundTag pTag) { super.addAdditionalSaveData(pContext, pTag); pTag.putString("Rot", this.placeSettings.getRotation().name()); } @Override protected void handleDataMarker(String pName, BlockPos pPos, ServerLevelAccessor pLevel, RandomSource pRandom, BoundingBox pBox) { if ("chest".equals(pName)) { pLevel.setBlock(pPos, Blocks.AIR.defaultBlockState(), 3); BlockEntity blockentity = pLevel.getBlockEntity(pPos.below()); if (blockentity instanceof ChestBlockEntity) { ((ChestBlockEntity)blockentity).setLootTable(BuiltInLootTables.IGLOO_CHEST, pRandom.nextLong()); } } } @Override public void postProcess( WorldGenLevel pLevel, StructureManager pStructureManager, ChunkGenerator pGenerator, RandomSource pRandom, BoundingBox pBox, ChunkPos pChunkPos, BlockPos pPos ) { super.postProcess(pLevel, pStructureManager, pGenerator, pRandom, pBox, pChunkPos, pPos); } } } public class MyStructurePieces { static final ResourceLocation STRUCTURE_LOCATION_MY_STRUCTURE = new ResourceLocation(ExampleMod.MODID,"my_structure"); static final Map PIVOTS = ImmutableMap.of( STRUCTURE_LOCATION_MY_STRUCTURE, new BlockPos(4, 0, 4) ); static final Map OFFSETS = ImmutableMap.of( STRUCTURE_LOCATION_MY_STRUCTURE, BlockPos.ZERO ); public static void addPieces( StructureTemplateManager pStructureTemplateManager, BlockPos pStartPos, Rotation pRotation, StructurePieceAccessor pPieces, RandomSource pRandom ) { pPieces.addPiece(new MyStructurePieces.MyStructurePiece(pStructureTemplateManager, STRUCTURE_LOCATION_MY_STRUCTURE, pStartPos, pRotation, 0)); } public static class MyStructurePiece extends TemplateStructurePiece { public MyStructurePiece(StructureTemplateManager pStructureTemplateManager, ResourceLocation pLocation, BlockPos pStartPos, Rotation pRotation, int pDown) { super(ModStructurePieceTypes.MY_STRUCTURE_PIECE.get(), 0, pStructureTemplateManager, pLocation, pLocation.toString(), makeSettings(pRotation, pLocation), makePosition(pLocation, pStartPos, pDown) ); } public MyStructurePiece(StructureTemplateManager pStructureTemplateManager, CompoundTag pTag) { super(ModStructurePieceTypes.MY_STRUCTURE_PIECE.get(), pTag, pStructureTemplateManager, resourceLocation -> makeSettings(Rotation.valueOf(pTag.getString("Rot")), resourceLocation)); } private static StructurePlaceSettings makeSettings(Rotation pRotation, ResourceLocation pLocation) { return new StructurePlaceSettings() .setRotation(pRotation) .setMirror(Mirror.NONE) .setRotationPivot(MyStructurePieces.PIVOTS.get(pLocation)) .addProcessor(BlockIgnoreProcessor.STRUCTURE_BLOCK); } private static BlockPos makePosition(ResourceLocation pLocation, BlockPos pPos, int pDown) { return pPos.offset(MyStructurePieces.OFFSETS.get(pLocation)).below(pDown); } @Override protected void addAdditionalSaveData(StructurePieceSerializationContext pContext, CompoundTag pTag) { super.addAdditionalSaveData(pContext, pTag); pTag.putString("Rot", this.placeSettings.getRotation().name()); } @Override protected void handleDataMarker(String pName, BlockPos pPos, ServerLevelAccessor pLevel, RandomSource pRandom, BoundingBox pBox) { if ("chest".equals(pName)) { pLevel.setBlock(pPos, Blocks.AIR.defaultBlockState(), 3); BlockEntity blockentity = pLevel.getBlockEntity(pPos.below()); if (blockentity instanceof ChestBlockEntity) { ((ChestBlockEntity)blockentity).setLootTable(BuiltInLootTables.IGLOO_CHEST, pRandom.nextLong()); } } } @Override public void postProcess( WorldGenLevel pLevel, StructureManager pStructureManager, ChunkGenerator pGenerator, RandomSource pRandom, BoundingBox pBox, ChunkPos pChunkPos, BlockPos pPos ) { super.postProcess(pLevel, pStructureManager, pGenerator, pRandom, pBox, pChunkPos, pPos); } } } ``` 我们使用的是一个TemplateStructurePiece的类,不过先说说他的父类的StructurePiece吧,下面是StructurePiece是我们会常用到的三个方法,其中两个是继承要是实现的方法。 ```java // 该方法是传入一个接口序列化的上下文信息和一个nbt数据 // 作用是在序列化之后,从nbt读取数据之前,你可以向nbt数据附加一些内容。用于你的结构化生成。 protected abstract void addAdditionalSaveData(StructurePieceSerializationContext pContext, CompoundTag pTag); // 该方法用于处理标志的数据的 // 例如你要给你一个结构中的一些位置添加战利品箱子,你可以利用这个方法来实现。 protected abstract void handleDataMarker(String pName, BlockPos pPos, ServerLevelAccessor pLevel, RandomSource pRandom, BoundingBox pBox); //postProcess的作用是在结构生成之后,对结构piece的进一步的处理和修饰。 // 不过也可以在生成之前操作。等会我们会看到怎么操作。 // 这里的参数的类名也比较清晰了就不过多介绍了。 @Override public void postProcess( WorldGenLevel pLevel, StructureManager pStructureManager, ChunkGenerator pGenerator, RandomSource pRandom, BoundingBox pBox, ChunkPos pChunkPos, BlockPos pPos ) ``` 好了,让我们来看TemplateStructurePiece,这是一个StructurePiece类的具体实现,用于表示一个由结构模板生成的结构方块。 ```java // 第一个构造函数 public TemplateStructurePiece( StructurePieceType pType,// structurepiecetype 需要我们去注册的。 int pGenDepth, // 结构生成时候的深度 StructureTemplateManager pStructureTemplateManager, // 结构模板管理器 ResourceLocation pLocation, // 模板的位置 String pTemplateName, // 模板的名称 StructurePlaceSettings pPlaceSettings, // 结构piece放置的设置 BlockPos pTemplatePosition // 结构piece放置的位置 ) public TemplateStructurePiece( StructurePieceType pType, // piecetype 需要我们自己注册 CompoundTag pTag, // nbt StructureTemplateManager pStructureTemplateManager, // 结构模板管理器 Function pPlaceSettingsFactory // 用于创建 StructurePlaceSettings 实例的函数 ) // 给nbt添加了放置的位置的数据 @Override protected void addAdditionalSaveData(StructurePieceSerializationContext pContext, CompoundTag pTag) { pTag.putInt("TPX", this.templatePosition.getX()); pTag.putInt("TPY", this.templatePosition.getY()); pTag.putInt("TPZ", this.templatePosition.getZ()); pTag.putString("Template", this.templateName); } // 该类对该方法进行了实现,主要的功能就是放置结构piece模板到世界中,并调用handleDataMarker处理标记数据。 @Override public void postProcess( WorldGenLevel pLevel, StructureManager pStructureManager, ChunkGenerator pGenerator, RandomSource pRandom, BoundingBox pBox, ChunkPos pChunkPos, BlockPos pPos ) ``` 下面来看我们的piece的类吧 ```java public static class MyStructurePiece extends TemplateStructurePiece { // 构造方法1,等会我们在structure中讲,怎么new,也就是我们刚刚没说的方法addpiece方法。 public MyStructurePiece(StructureTemplateManager pStructureTemplateManager, ResourceLocation pLocation, BlockPos pStartPos, Rotation pRotation, int pDown) { super(ModStructurePieceTypes.MY_STRUCTURE_PIECE.get(), 0, pStructureTemplateManager, pLocation, pLocation.toString(), makeSettings(pRotation, pLocation), makePosition(pLocation, pStartPos, pDown) ); } // 构造方法二 public MyStructurePiece(StructureTemplateManager pStructureTemplateManager, CompoundTag pTag) { super(ModStructurePieceTypes.MY_STRUCTURE_PIECE.get(), pTag, pStructureTemplateManager, resourceLocation -> makeSettings(Rotation.valueOf(pTag.getString("Rot")), resourceLocation)); } // 辅助函数用于生成StructurePlaceSettings,第一个参数是构建时候传入的随机数 private static StructurePlaceSettings makeSettings(Rotation pRotation, ResourceLocation pLocation) { return new StructurePlaceSettings() .setRotation(pRotation) .setMirror(Mirror.NONE) .setRotationPivot(MyStructurePieces.PIVOTS.get(pLocation)) .addProcessor(BlockIgnoreProcessor.STRUCTURE_BLOCK); } // 辅助函数用于返回piece生成的位置 private static BlockPos makePosition(ResourceLocation pLocation, BlockPos pPos, int pDown) { return pPos.offset(MyStructurePieces.OFFSETS.get(pLocation)).below(pDown); } // 给nbt添加一个rot的字段,表示随机旋转的信息。 @Override protected void addAdditionalSaveData(StructurePieceSerializationContext pContext, CompoundTag pTag) { super.addAdditionalSaveData(pContext, pTag); pTag.putString("Rot", this.placeSettings.getRotation().name()); } // 下面还是我们的piece类中的hhandleDataMarker // 这个主要处理了结构模板中的箱子,并放置战利品箱子 // 其中的这个chest数据你可以到对应的nbt下看,nbt打开格式需要你用idea装下那个minecraft development插件 protected void handleDataMarker(String pName, BlockPos pPos, ServerLevelAccessor pLevel, RandomSource pRandom, BoundingBox pBox) { if ("chest".equals(pName)) { pLevel.setBlock(pPos, Blocks.AIR.defaultBlockState(), 3); BlockEntity blockentity = pLevel.getBlockEntity(pPos.below()); if (blockentity instanceof ChestBlockEntity) { ((ChestBlockEntity)blockentity).setLootTable(BuiltInLootTables.IGLOO_CHEST, pRandom.nextLong()); } } } // 下面是我们自己类中的postProcess的方法,我们说下如何在生成前处理,或者生成后处理。 @Override public void postProcess( WorldGenLevel pLevel, StructureManager pStructureManager, ChunkGenerator pGenerator, RandomSource pRandom, BoundingBox pBox, ChunkPos pChunkPos, BlockPos pPos ) { // 对生成前进行处理的话 // 写在这里 super.postProcess(pLevel, pStructureManager, pGenerator, pRandom, pBox, pChunkPos, pPos); // 生成后处理 // 写在这里 } } ``` 让我们回到strcutre类中,来看这个方法 ```java private void generatePieces(StructurePiecesBuilder pBuilder, Structure.GenerationContext pContext) { ChunkPos chunkpos = pContext.chunkPos(); WorldgenRandom worldgenrandom = pContext.random(); BlockPos blockpos = new BlockPos(chunkpos.getMinBlockX(), 64, chunkpos.getMinBlockZ()); Rotation rotation = Rotation.getRandom(worldgenrandom); MyStructurePieces.addPieces(pContext.structureTemplateManager(), blockpos, rotation, pBuilder, worldgenrandom); } ``` StructurePiecesBuilder类实现了StructurePieceAccessor接口,StructurePiecesBuilder是管理piece的类,你需要通过这个类向你的建组添加piece,通过addpieces方法,这里这里我们获得了随机数和位置信息,以及一个随机的旋转方向,传入给了辅助的方法addPieces。 以下是辅助方法的内容: ```java public static void addPieces( StructureTemplateManager pStructureTemplateManager, BlockPos pStartPos, Rotation pRotation, StructurePieceAccessor pPieces, RandomSource pRandom ) { pPieces.addPiece(new MyStructurePieces.MyStructurePiece(pStructureTemplateManager, , pStartPos, pRotation, 0)); } ``` 好了,下面来看看这个MyStructurePiece的构造方法,这个是我们写的方法,其中的我们在父类的构造方法中需要传入一个StructurePlaceSettings的东西,这个StructurePlaceSettings是我们的在MyStructurePiece类中的makeSettings方法中获得的。 ```java // 可以看出来是可以直接new出来的,new出来之后,你可以根据自己的需求进行设置,这里的设置了一些内容, // 比如设置piece的旋转 // 设置mirror镜像 // 设置旋转点 // 以及最后一个addProcessor,表示添加一个新的StructureProcessor,这里用的是BlockIgnoreProcessor下面的STRUCTURE_BLOCK, // 表示生成的时候不要生成STRUCTURE_BLOCK方块。 private static StructurePlaceSettings makeSettings(Rotation pRotation, ResourceLocation pLocation) { return new StructurePlaceSettings() .setRotation(pRotation) .setMirror(Mirror.NONE) .setRotationPivot(MyStructurePieces.PIVOTS.get(pLocation)) .addProcessor(BlockIgnoreProcessor.STRUCTURE_BLOCK); } ``` 到此为止,关于,strcture和strcutrepiece相关的内容就讲解完毕了,你也可以去看看其他的建筑的生成相关的内容学习。 好了下面我们来说于注册相关的内容,首先要解决的是关于structure中的type返回的StructureType内容。注册strcturetype就如上面说的一样,就不在重复赘述了,这里直接给出注册的代码和type的代码 ```java // 注册StructureType方法 public class ModStructureType { public static final DeferredRegister> STRUCTURE_TYPES = DeferredRegister.create(Registries.STRUCTURE_TYPE, ExampleMod.MODID); public static final DeferredHolder, StructureType> MY_STRUCTURE = registerType("my_structure", () -> () -> MyStructure.CODEC); private static

DeferredHolder, StructureType

> registerType(String name, Supplier> factory) { return STRUCTURE_TYPES.register(name, factory); } public static void register(IEventBus eventBus){ STRUCTURE_TYPES.register(eventBus); } } ``` type方法 ```java @Override public StructureType type() { return ModStructureType.MY_STRUCTURE.get(); } ``` 下面我们在解决StrcturePieceType的问题,在StrcturePiece的构造方法中需要你传入StructurePieceType的实例,这里我们给出注册的方法。 ```java //StructurePieceType注册 public class ModStructurePieceTypes { // 注册器 public static final DeferredRegister STRUCTURE_PIECE_TYPES = DeferredRegister.create(Registries.STRUCTURE_PIECE, ExampleMod.MODID); // 注册的对象 public static final DeferredHolder MY_STRUCTURE_PIECE = registerPieceType("my_structure_piece", MyStructurePieces.MyStructurePiece::new); // 辅助注册的方法 private static DeferredHolder registerPieceType(String name, StructurePieceType.StructureTemplateType structurePieceType) { // 第一个参数是name,第二个参数是构造方法 // 第二个参数补充一点是StructureTemplateType是一个接口,只有一个方法,其实就是要求传入一个该接口的实例,这个接口实现方法应该传入 // 两个StructureTemplateManager和CompoundTag参数返回一个StructurePiece // 所以这里可以直接是使用new构造方法。 // 而register要求返回一个supplier,所以就这样写了。 return STRUCTURE_PIECE_TYPES.register(name.toLowerCase(Locale.ROOT), () -> structurePieceType); } // 别忘记添加到总线 public static void register(IEventBus eventBus){ STRUCTURE_PIECE_TYPES.register(eventBus); } } ``` StructureSets类是描述你就建筑在世界上的生成的情况的,比如建组在地图上的生成策略,例如随机生成,或者是像暮色那样的棋盘形状,salt是一个盐或者说是随机种子数,要保持唯一的数值不要和其他的建筑的数值一样,不然可能随机生成的位置会发生冲突,以及最小间距等等。不过这个类主要的功能是生成json文件,其实整个建筑的生成都可以使用json文件而不编写代码,不过我们既然可以写代码,还是采用了代码的形势,感觉是比写json来更加直观和容易理解一些,也采取了和原版类似的方式,方便对大家阅读理解原版的代码。 ```java public class ModStructureSets { // 我们structureset的资源名称 public static final ResourceKey MY_STRUCTURE_SET = register("my_structure"); // 等会使用数据生成要要传递的方法引用 public static void bootstrap(BootstapContext pContext) { // BootstapContext数据生成时候的上下文 // 从上下文中获得HolderGetter HolderGetter holdergetter = pContext.lookup(Registries.STRUCTURE); // 通过上下文注册我们的set // 第一个参数是我们的key // 第二个参数是set的实例,new处理 // set的第一个参数是对应的strcture,这里通过holder获得,第二个参数就是描述放置的信息的类 // RandomSpreadStructurePlacement是随机放置,也有另一个ConcentricRingsStructurePlacement放置,他们继承于StructurePlacement // 我们使用了随机放置 // 随机放置的第一个参数是当前位置生成失败时候寻找下一生成点的距离 // 第二个参数建组之间的最小间隔。 // 第三个参数是随机放置的类型,这里填的线性的, 也有其他的可以选择。 // 第四个参数是盐 pContext.register( ModStructureSets.MY_STRUCTURE_SET, new StructureSet(holdergetter.getOrThrow(ModStructures.MY_STRUCTURE), new RandomSpreadStructurePlacement(32, 8, RandomSpreadType.LINEAR, 14357619)) ); } private static ResourceKey register(String pName) { return ResourceKey.create(Registries.STRUCTURE_SET, new ResourceLocation(ExampleMod.MODID,pName)); } } ``` 以上代码其实就是用于生成json,而关于json的内容,你看可以在wiki上查到的。这里贴出wiki地址:https://minecraft.wiki/w/Structure\_set Structures类,同样适用于生成json的内容,不过这个描述的建组的生成的生物群系,以及生成的阶段。 ```java public class ModStructures { // 以下的三个辅助的方法用于获得StructureSettings这个类 // 和之前讲解StructureSettings的一致,就不赘述了。 public static Structure.StructureSettings structure( HolderSet pBiomes, Map pSpawnOverrides, GenerationStep.Decoration pStep, TerrainAdjustment pTerrainAdaptation ) { return new Structure.StructureSettings(pBiomes, pSpawnOverrides, pStep, pTerrainAdaptation); } private static Structure.StructureSettings structure(HolderSet pBiomes, GenerationStep.Decoration pStep, TerrainAdjustment pTerrainAdaptation) { return structure(pBiomes, Map.of(), pStep, pTerrainAdaptation); } private static Structure.StructureSettings structure(HolderSet pBiomes, TerrainAdjustment pTerrainAdaptation) { return structure(pBiomes, Map.of(), GenerationStep.Decoration.SURFACE_STRUCTURES, pTerrainAdaptation); } // strucutre的key public static final ResourceKey MY_STRUCTURE = registerKey("my_structure"); // 创建key的方法 public static ResourceKey registerKey(String name) { return ResourceKey.create(Registries.STRUCTURE, new ResourceLocation(ExampleMod.MODID,name)); } // bootstrap在数据生成时候调用 public static void bootstrap(BootstapContext context) { // 从上下文中获得所有的生物群系 HolderGetter biomeHolderGetter = context.lookup(Registries.BIOME); // 第一个参数是key // 第二个参数就是我们的strucutre的构造 // 构造的要传入一个StructureSettings // setting的一个参数是生物群系,我们指定了生物群系是主世界,对于第二个参数设置的是TerrainAdjustment.NONE,当然你也可以选择其他的。 context.register( ModStructures.MY_STRUCTURE, new MyStructure(structure(biomeHolderGetter.getOrThrow(BiomeTags.IS_OVERWORLD), TerrainAdjustment.NONE)) ); } } ``` 总线 ```java public ExampleMod(IEventBus modEventBus) { modEventBus.addListener(this::commonSetup); ModStructureType.register(modEventBus); ModStructurePieceTypes.register(modEventBus); NeoForge.EVENT_BUS.register(this); } ``` 以及数据生成 ```java public class ModWorldGen extends DatapackBuiltinEntriesProvider { public static final RegistrySetBuilder BUILDER = new RegistrySetBuilder() .add(Registries.CONFIGURED_FEATURE, ModOreFeatures::bootstrap) .add(Registries.PLACED_FEATURE, ModOrePlacements::bootstrap) .add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, ModBiomeModifiers::bootstrap) .add(Registries.STRUCTURE_SET, ModStructureSets::bootstrap) .add(Registries.STRUCTURE, ModStructures::bootstrap); public ModWorldGen(PackOutput output, CompletableFuture registries) { super(output, registries, BUILDER,Set.of(ExampleMod.MODID)); } } ``` 添加到事件。 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public class ModDataGeneratorHandler { @SubscribeEvent public static void gatherData(GatherDataEvent event){ ExistingFileHelper efh = event.getExistingFileHelper(); var lp = event.getLookupProvider(); //world gen event.getGenerator().addProvider(event.includeServer(), (DataProvider.Factory) pOutput -> new ModWorldGen(pOutput,lp)); } } ``` --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_44_Dependencies_LIb.md description: 44 如何添加模组和lib 相关教程 --- # 添加使用模组和附属 这次我们怎么给你的工程项目添加一些模组,用于辅助我们的开发,例如添加一个jei,来查看配置文件,或者给jei添加一些支持,让jei显示自己的合成表。 为我们也会添加一个依赖的库lib,我们使用这个库辅助我们的给主世界添加新的生物群系,不过这样也会导致我们的模组以来于该库lib的模组,不过这确实简化了我们的开发,对于游玩该模组的人无非也就是多添加了一个lib模组(不过这确实有时候是烦人的)。 ## jei 我们先来看怎么添加jei。 关于jei,github上的wiki上有关于的配置的方法,这里我们安装wiki上的讲解的内容配置一遍。 https://github.com/mezz/JustEnoughItems/wiki/Getting-Started-%5BJEI-for-Minecraft-1.20.4--for-NeoForge,-Forge,-or-Fabric%5D 第一步打开你的build.gradle文件,在其中Repositories下面添加以下的字段,这个表示将jei所作为的仓库添加到我们的工程中。 ```java repositories { maven { // location of the maven that hosts JEI files since January 2023 name = "Jared's maven" url = "https://maven.blamejared.com/" } maven { // location of a maven mirror for JEI files, as a fallback name = "ModMaven" url = "https://modmaven.dev" } } ``` 第二步还是在build.gradle文件中,在Dependencies 字段添加对于jei的依赖 ```java dependencies { /* other minecraft dependencies are here */ // compile against the JEI API but do not include it at runtime // compile 表示在编译阶段需要该库,而在运行时不需要 compileOnly("mezz.jei:jei-${mc_version}-common-api:${jei_version}") compileOnly("mezz.jei:jei-${mc_version}-neoforge-api:${jei_version}") // runtimeOnly 表示在运行时需要该库。 // at runtime, use the full JEI jar for NeoForge runtimeOnly("mezz.jei:jei-${mc_version}-neoforge:${jei_version}") } ``` 其中的mc\_version以及jei\_version我们等会写在gradle.properties文件下。 显然mc\_version就是游戏版本,jei\_version就是jei的版本,关于jei的版本可以到curseforge上查看。 ```java mc_version=1.20.4 jei_version=17.0.0.30 ``` 最后点击reload gradle或者load gradle change,下载jei的依赖。 然后启动游戏到游戏看看即可。和我们平时使用jei没什么区别。对于其他的模组的添加流程大致相同,仓库可以使用curseforge提供的仓库,或者modirth也提供了一个仓库。 这里给出curseforge仓库的使用方法。 https://cursemaven.com/ 添加仓库地址 ```java repositories { maven { url "https://cursemaven.com" } } ``` 添加依赖 ```java curse.maven:project-11111:12345 ``` 其中的project id:11111以及fileids:12345的查看方法去看https://cursemaven.com 下面的图片。 例如jei的,不过这里的字段需要实际上根据你的项目的gradle类型来写,例如neoforge的就安装上面的写。其他的查看cursemaven上面的例子。 ```java dependencies { api "curse.maven:jei-238222:2724420" } ``` ## TerraBlender lib 添加仓库 ```java repositories { mavenLocal() mavenCentral() maven { url = 'https://maven.minecraftforge.net/' } maven { url "https://www.cursemaven.com" content { includeGroup "curse.maven" } } } ``` 添加依赖 ```java compileOnly "com.github.glitchfiend:TerraBlender-neoforge:${minecraft_version}-${terrablender_version}" runtimeOnly "com.github.glitchfiend:TerraBlender-neoforge:${minecraft_version}-${terrablender_version}" ``` 在build.properties下配置我们的版本,mc\_version已经配置了,所以这里直接写terrblender\_version既可以了。 ```java terrablender_version=3.3.0.12 ``` 最后点击reload gradle或者load gradle change,下载对应的依赖。 好了之后的教程我们使用TerraBlender给主世界添加生物群系biome --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_44_Dependencies_LIb.md description: 44 如何添加模组和lib 相关教程 --- # 添加使用模组和附属 这次我们怎么给你的工程项目添加一些模组,用于辅助我们的开发,例如添加一个jei,来查看配置文件,或者给jei添加一些支持,让jei显示自己的合成表。 为我们也会添加一个依赖的库lib,我们使用这个库辅助我们的给主世界添加新的生物群系,不过这样也会导致我们的模组以来于该库lib的模组,不过这确实简化了我们的开发,对于游玩该模组的人无非也就是多添加了一个lib模组(不过这确实有时候是烦人的)。 ## jei 我们先来看怎么添加jei。 关于jei,github上的wiki上有关于的配置的方法,这里我们安装wiki上的讲解的内容配置一遍。 https://github.com/mezz/JustEnoughItems/wiki/Getting-Started-%5BJEI-for-Minecraft-1.20.4--for-NeoForge,-Forge,-or-Fabric%5D 第一步打开你的build.gradle文件,在其中Repositories下面添加以下的字段,这个表示将jei所作为的仓库添加到我们的工程中。 ```java repositories { maven { // location of the maven that hosts JEI files since January 2023 name = "Jared's maven" url = "https://maven.blamejared.com/" } maven { // location of a maven mirror for JEI files, as a fallback name = "ModMaven" url = "https://modmaven.dev" } } ``` 第二步还是在build.gradle文件中,在Dependencies 字段添加对于jei的依赖 ```java dependencies { /* other minecraft dependencies are here */ // compile against the JEI API but do not include it at runtime // compile 表示在编译阶段需要该库,而在运行时不需要 compileOnly("mezz.jei:jei-${mc_version}-common-api:${jei_version}") compileOnly("mezz.jei:jei-${mc_version}-neoforge-api:${jei_version}") // runtimeOnly 表示在运行时需要该库。 // at runtime, use the full JEI jar for NeoForge runtimeOnly("mezz.jei:jei-${mc_version}-neoforge:${jei_version}") } ``` 其中的mc\_version以及jei\_version我们等会写在gradle.properties文件下。 显然mc\_version就是游戏版本,jei\_version就是jei的版本,关于jei的版本可以到curseforge上查看。 ```java mc_version=1.20.4 jei_version=17.0.0.30 ``` 最后点击reload gradle或者load gradle change,下载jei的依赖。 然后启动游戏到游戏看看即可。和我们平时使用jei没什么区别。对于其他的模组的添加流程大致相同,仓库可以使用curseforge提供的仓库,或者modirth也提供了一个仓库。 这里给出curseforge仓库的使用方法。 https://cursemaven.com/ 添加仓库地址 ```java repositories { maven { url "https://cursemaven.com" } } ``` 添加依赖 ```java curse.maven:project-11111:12345 ``` 其中的project id:11111以及fileids:12345的查看方法去看https://cursemaven.com 下面的图片。 例如jei的,不过这里的字段需要实际上根据你的项目的gradle类型来写,例如neoforge的就安装上面的写。其他的查看cursemaven上面的例子。 ```java dependencies { api "curse.maven:jei-238222:2724420" } ``` ## TerraBlender lib 添加仓库 ```java repositories { mavenLocal() mavenCentral() maven { url = 'https://maven.minecraftforge.net/' } maven { url "https://www.cursemaven.com" content { includeGroup "curse.maven" } } } ``` 添加依赖 ```java compileOnly "com.github.glitchfiend:TerraBlender-neoforge:${minecraft_version}-${terrablender_version}" runtimeOnly "com.github.glitchfiend:TerraBlender-neoforge:${minecraft_version}-${terrablender_version}" ``` 在build.properties下配置我们的版本,mc\_version已经配置了,所以这里直接写terrblender\_version既可以了。 ```java terrablender_version=3.3.0.12 ``` 最后点击reload gradle或者load gradle change,下载对应的依赖。 好了之后的教程我们使用TerraBlender给主世界添加生物群系biome --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_45_RegisterBiome.md description: 45 添加生物群系 相关教程 --- # 添加生物群系 这次我们使用上次添加的terrablender lib来给主世界添加生物群系,为什么不直接添加呢,因为我没找到方法,而且添加也比较麻烦,可能需要mixin,所以我们直接使用这个lib来给主世界添加生物群系,不过如果你是给自己的维度添加生物群系倒不是怎么麻烦了。好了然我们开始把。 对于我的世界中的生物群系同样是通过json来配置的,你也可以仅写json完成,不过既然我们能写代码还是通过代码来完成把。 首先生物群系是配置了一系列的feature,就是我们之前的写的矿物生成的那样的feature,不仅仅是矿物还包含了地下洞穴,各种矿物,以及该生物群系的中的怪物生成。这两个分别对应了,biomegenerationsettings和MobSpawnSettings这两个类,这两个类就是配置生成的内容和怪物的生成,并且这两个都是通过builder来构建的。 然后我们通过Biome.BiomeBuilder()这个构建大的方法,来获得对应的Biome实例,其中的BiomeBuilder指定我们的biomegenerationsettings和MobSpawnSettings这两个内容,最后和之前一样通过数据生成得到方式获得json文件。 不过这里要说大的一点就是我们添加了各种的Placeddfeature,你之前也写过placedfeature所以你应该明白这并不会改变生物群系的一些地表的方块什么的。这更像是添加新的内容,例如就是沙漠的地表是沙子,而平原的地表是草方块,这并不是通过placedfeature配置的。这也是我们使用terrablender的理由,据我找到的内容来看,neoforge没有提供直接往主世界中塞生物群系的方法,而对于地表方块的生成的内容则是放在了一个package net.minecraft.data.worldgen包下的SurfaceRuleData类中,你想添加只能修改这个类。 而对于生成你添加的生物群系则是在维度里面,你也需要通过mixin的方法去添加,不过我们使用了terrablender所以就不需要考虑这种事情了。 大概了解之后我们来看代码把。首先是添加生物群系得到代码。 ```java // 原版的生物群系的配置位于OverworldBiomes类下。 public class ExampleOverworldBiomes{ // 一个辅助的函数用于根据温度判断段生物群系的天空的颜色 protected static int calculateSkyColor(float pTemperature) { float $$1 = pTemperature / 3.0F; $$1 = Mth.clamp($$1, -1.0F, 1.0F); return Mth.hsvToRgb(0.62222224F - $$1 * 0.05F, 0.5F + $$1 * 0.1F, 1.0F); } // 一个构造方法 用于返回一个biome实例 private static Biome ExampleOverworldBiomes( boolean pHasPercipitation,// 是否有降水 float pTemperature,// 温度 float pDownfall, // 降水量 MobSpawnSettings.Builder pMobSpawnSettings,// 生物群系生物生成设置 BiomeGenerationSettings.Builder pGenerationSettings,// 生物群系生成设置 @Nullable Music pBackgroundMusic // 生物群系背景音乐 ) { return ExampleOverworldBiomes(pHasPercipitation, pTemperature, pDownfall, 4159204, 329011, null, null, pMobSpawnSettings, pGenerationSettings, pBackgroundMusic); } // 另一个构造方法 同样返回bioome 能配置的更多 private static Biome ExampleOverworldBiomes( boolean pHasPrecipitation, / float pTemperature,// 温度 float pDownfall, int pWaterColor, // 水的颜色 int pWaterFogColor, // 水的雾颜色 @Nullable Integer pGrassColorOverride, // 草方块颜色 @Nullable Integer pFoliageColorOverride, // 树叶颜色 MobSpawnSettings.Builder pMobSpawnSettings, BiomeGenerationSettings.Builder pGenerationSettings, @Nullable Music pBackgroundMusic ) { BiomeSpecialEffects.Builder biomespecialeffects$builder = new BiomeSpecialEffects.Builder() .waterColor(pWaterColor) .waterFogColor(pWaterFogColor) .fogColor(12638463) .skyColor(calculateSkyColor(pTemperature)) .ambientMoodSound(AmbientMoodSettings.LEGACY_CAVE_SETTINGS) .backgroundMusic(pBackgroundMusic); if (pGrassColorOverride != null) { biomespecialeffects$builder.grassColorOverride(pGrassColorOverride); } if (pFoliageColorOverride != null) { biomespecialeffects$builder.foliageColorOverride(pFoliageColorOverride); } return new Biome.BiomeBuilder() .hasPrecipitation(pHasPrecipitation) .temperature(pTemperature) .downfall(pDownfall) .specialEffects(biomespecialeffects$builder.build()) .mobSpawnSettings(pMobSpawnSettings.build()) .generationSettings(pGenerationSettings.build()) .build(); } // 添加一个自己的生物群系,使用了一些原版的方法。具体的内容自己点到方法里面看下把,不是很难,都是一些重复的配置的内容。 public static Biome exampleBiome(HolderGetter pPlacedFeatures, HolderGetter> pWorldCarvers) { // 创建一个MobSpawnSettings.Builder对象,用于配置生物生成设置。 MobSpawnSettings.Builder mobspawnsettings$builder = new MobSpawnSettings.Builder(); // 配置沙漠生成的生物。 BiomeDefaultFeatures.desertSpawns(mobspawnsettings$builder); // 用于配置生物群系生成设置。 BiomeGenerationSettings.Builder biomegenerationsettings$builder = new BiomeGenerationSettings.Builder(pPlacedFeatures, pWorldCarvers); // 添加化石装饰物到生物群系生成设置中 BiomeDefaultFeatures.addFossilDecoration(biomegenerationsettings$builder); // 一些通用的配置 globalOverworldGeneration(biomegenerationsettings$builder); // 矿物 BiomeDefaultFeatures.addDefaultOres(biomegenerationsettings$builder); BiomeDefaultFeatures.addDefaultSoftDisks(biomegenerationsettings$builder); BiomeDefaultFeatures.addDefaultFlowers(biomegenerationsettings$builder); BiomeDefaultFeatures.addDefaultGrass(biomegenerationsettings$builder); BiomeDefaultFeatures.addDesertVegetation(biomegenerationsettings$builder); BiomeDefaultFeatures.addDefaultMushrooms(biomegenerationsettings$builder); BiomeDefaultFeatures.addDesertExtraVegetation(biomegenerationsettings$builder); BiomeDefaultFeatures.addDesertExtraDecoration(biomegenerationsettings$builder); // 调用构造返回biome return ExampleOverworldBiomes(false, 2.0F, 0.0F, mobspawnsettings$builder, biomegenerationsettings$builder, Musics.createGameMusic(SoundEvents.MUSIC_BIOME_DESERT)); } private static void globalOverworldGeneration(BiomeGenerationSettings.Builder pGenerationSettings) { BiomeDefaultFeatures.addDefaultCarversAndLakes(pGenerationSettings); BiomeDefaultFeatures.addDefaultCrystalFormations(pGenerationSettings); BiomeDefaultFeatures.addDefaultMonsterRoom(pGenerationSettings); BiomeDefaultFeatures.addDefaultUndergroundVariety(pGenerationSettings); BiomeDefaultFeatures.addDefaultSprings(pGenerationSettings); BiomeDefaultFeatures.addSurfaceFreezing(pGenerationSettings); } } ``` 下面让我们使用我们的配置的类,进行json数据的生成 ```java // 原版的内容位于BiomeData类下 public class ModBiomeData { // 依旧添加一个对应biome的key public static final ResourceKey EXAMPLE_BIOME = register("example_biome"); // 对应大的create方法 private static ResourceKey register(String pKey) { return ResourceKey.create(Registries.BIOME, new ResourceLocation(ExampleMod.MODID,pKey)); } // bootstrap和之前的一样 public static void bootstrap(BootstapContext pContext) { // 获得placefeature的holdgetter HolderGetter holdergetter = pContext.lookup(Registries.PLACED_FEATURE); HolderGetter> holdergetter1 = pContext.lookup(Registries.CONFIGURED_CARVER); // 给数据生成注册我们的biome pContext.register(ModBiomeData.EXAMPLE_BIOME, ExampleOverworldBiomes.exampleBiome(holdergetter, holdergetter1)); } } ``` 添加到provider中,和之前一样。不介绍了。 ```java public class ModWorldGen extends DatapackBuiltinEntriesProvider { public static final RegistrySetBuilder BUILDER = new RegistrySetBuilder() .add(Registries.CONFIGURED_FEATURE, ModOreFeatures::bootstrap) .add(Registries.PLACED_FEATURE, ModOrePlacements::bootstrap) .add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, ModBiomeModifiers::bootstrap) .add(Registries.STRUCTURE_SET, ModStructureSets::bootstrap) .add(Registries.STRUCTURE, ModStructures::bootstrap) .add(Registries.BIOME, ModBiomeData::bootstrap); public ModWorldGen(PackOutput output, CompletableFuture registries) { super(output, registries, BUILDER,Set.of(ExampleMod.MODID)); } } @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public class ModDataGeneratorHandler { @SubscribeEvent public static void gatherData(GatherDataEvent event){ ExistingFileHelper efh = event.getExistingFileHelper(); var lp = event.getLookupProvider(); //world gen event.getGenerator().addProvider(event.includeServer(), (DataProvider.Factory) pOutput -> new ModWorldGen(pOutput,lp)); } } ``` 好了,这里你点击data gen之后就会生成你的biome.json文件了,但是你会发现没有在主世界中生成对应的biome,甚至你会发现我们上面的代码都没指定过在那个维度生成。而对于这部分内容,我并没有在neoforge中找到提供的方法, 同样如上,这部分在维度生成相关大的代码中,你或许需要mixin,不过这里我们使用terrablender lib帮帮助我们,在上一次的视频中我们已经配置过了这个lib。 所以我们直接使用。 ```java // 我们使用terrablender的Region类给主世界添加生物群系 public class ModOverworldRegion extends Region { public ModOverworldRegion(ResourceLocation name, int weight) { // name,维度,权重 super(name, RegionType.OVERWORLD, weight); } // 重写addbiome方法,这个方法修改原版的主世界的维度中生成生物群系的方法 @Override public void addBiomes(Registry registry, Consumer>> mapper) { this.addModifiedVanillaOverworldBiomes(mapper,modifiedVanillaOverworldBuilder -> { modifiedVanillaOverworldBuilder.replaceBiome(Biomes.FOREST,ModBiomeData.EXAMPLE_BIOME); }); } } ``` 注册我们写的这个region ```java public class ModTerrablender { public static void registerBiome(){ // 第一个参数我们的region,第二个参数是维度的定位符,第三个是权重。 // 其中权重的数值设置你可以参考原版进行设置 Regions.register(new ModOverworldRegion(new ResourceLocation(ExampleMod.MODID,"overworld"),5)); } } ``` 然后,接下来我们说下怎么利用Terrablender来对生物群系的表面的方块进行修改。 教程只是对其中的一部分内容进行了介绍,并没有涵盖所有的内容,所有内容还是很多且有些复杂的,这说一部分,另一部分放在了维度的,剩下的就大家自己探索了。 其实surface规则的定义是放在维度的json中的。 ```java // 对我们的生物群系的表面进行修改 // 首先说明的一点是surfaceRule是针对维度的,通过定义一系列的sequence来指定生成,如果具有一些条件判断是可以使用ConditionSource类来判断的,RuleSource通常作为一个结果。 public class ModSurfaceRules { // SurfaceRules.RuleSource 表面生成的规则,这里我们使用makeStateRule来创建一个SurfaceRules.RuleSource private static final SurfaceRules.RuleSource DIRT = makeStateRule(Blocks.DIRT); private static final SurfaceRules.RuleSource GRASS_BLOCK = makeStateRule(Blocks.GRASS_BLOCK); private static final SurfaceRules.RuleSource RUBY_BLOCK = makeStateRule(ModBlocks.RUBY_BLOCK.get()); private static final SurfaceRules.RuleSource DIAMOND_ORE = makeStateRule(Blocks.DIAMOND_ORE); // 静态方法,用于产生我们的surface规则。 public static SurfaceRules.RuleSource makeRules() { // 第一个规则是一个条件规则,表示水面上的得的位置 SurfaceRules.ConditionSource isAtOrAboveWaterLevel = SurfaceRules.waterBlockCheck(-1, 0); // 使用第一个规则表示如果isAtOrAboveWaterLevel满足条件则使用GRASS_BLOCK方块。否则使用DIRT SurfaceRules.RuleSource = SurfaceRules.sequence(SurfaceRules.ifTrue(isAtOrAboveWaterLevel, GRASS_BLOCK), DIRT); // 返回我们的SurfaceRules.RuleSource return SurfaceRules.sequence( // 满足生物群系情况下,在地板使用RUBY_BLOCK和在天花板使用DIAMOND_ORE // 这里的地板是指在生成地表的时候的地板和天花板 SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.isBiome(ModBiomeData.EXAMPLE_BIOME), SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, RUBY_BLOCK)), SurfaceRules.ifTrue(SurfaceRules.ON_CEILING, DIAMOND_ORE)), // 这是一条原版的默认规则,如果不是这个生物群系,那么使用默认的草方块 SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, grassSurface) ); } // 这个makeStateRule也很简单,就是返回对应方块默认状态的 SurfaceRules.RuleSource private static SurfaceRules.RuleSource makeStateRule(Block block) { return SurfaceRules.state(block.defaultBlockState()); } } ``` 注册相关的几个类和方法 ```java // The value here should match an entry in the META-INF/mods.toml file @Mod(ExampleMod.MODID) public class ExampleMod { public static final String MODID = "examplemod"; private static final Logger LOGGER = LogUtils.getLogger(); public ExampleMod(IEventBus modEventBus) { // 你的region写在这里 ModTerrablender.registerBiome(); NeoForge.EVENT_BUS.register(this); } private void commonSetup(final FMLCommonSetupEvent event) { // 给主世界添加新的surfaceRule event.enqueueWork(()->{ SurfaceRuleManager.addSurfaceRules(SurfaceRuleManager.RuleCategory.OVERWORLD, ExampleMod.MODID, ModSurfaceRules.makeRules()); }); } @SubscribeEvent public void onServerStarting(ServerStartingEvent event) { } } ``` 关于surfaceRule的教程,可以找一些教程,去看看具体得到相关内容,同样wiki上也有解释,你可以到wiki上查看. 但对于surfaceRule的原版代码内容在SurfaceRuleData这个类下面 到世界中看看你的生物群系吧. --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_45_RegisterBiome.md description: 45 添加生物群系 相关教程 --- # 添加生物群系 这次我们使用上次添加的terrablender lib来给主世界添加生物群系,为什么不直接添加呢,因为我没找到方法,而且添加也比较麻烦,可能需要mixin,所以我们直接使用这个lib来给主世界添加生物群系,不过如果你是给自己的维度添加生物群系倒不是怎么麻烦了。好了然我们开始把。 对于我的世界中的生物群系同样是通过json来配置的,你也可以仅写json完成,不过既然我们能写代码还是通过代码来完成把。 首先生物群系是配置了一系列的feature,就是我们之前的写的矿物生成的那样的feature,不仅仅是矿物还包含了地下洞穴,各种矿物,以及该生物群系的中的怪物生成。这两个分别对应了,biomegenerationsettings和MobSpawnSettings这两个类,这两个类就是配置生成的内容和怪物的生成,并且这两个都是通过builder来构建的。 然后我们通过Biome.BiomeBuilder()这个构建大的方法,来获得对应的Biome实例,其中的BiomeBuilder指定我们的biomegenerationsettings和MobSpawnSettings这两个内容,最后和之前一样通过数据生成得到方式获得json文件。 不过这里要说大的一点就是我们添加了各种的Placeddfeature,你之前也写过placedfeature所以你应该明白这并不会改变生物群系的一些地表的方块什么的。这更像是添加新的内容,例如就是沙漠的地表是沙子,而平原的地表是草方块,这并不是通过placedfeature配置的。这也是我们使用terrablender的理由,据我找到的内容来看,neoforge没有提供直接往主世界中塞生物群系的方法,而对于地表方块的生成的内容则是放在了一个package net.minecraft.data.worldgen包下的SurfaceRuleData类中,你想添加只能修改这个类。 而对于生成你添加的生物群系则是在维度里面,你也需要通过mixin的方法去添加,不过我们使用了terrablender所以就不需要考虑这种事情了。 大概了解之后我们来看代码把。首先是添加生物群系得到代码。 ```java // 原版的生物群系的配置位于OverworldBiomes类下。 public class ExampleOverworldBiomes{ // 一个辅助的函数用于根据温度判断段生物群系的天空的颜色 protected static int calculateSkyColor(float pTemperature) { float $$1 = pTemperature / 3.0F; $$1 = Mth.clamp($$1, -1.0F, 1.0F); return Mth.hsvToRgb(0.62222224F - $$1 * 0.05F, 0.5F + $$1 * 0.1F, 1.0F); } // 一个构造方法 用于返回一个biome实例 private static Biome ExampleOverworldBiomes( boolean pHasPercipitation,// 是否有降水 float pTemperature,// 温度 float pDownfall, // 降水量 MobSpawnSettings.Builder pMobSpawnSettings,// 生物群系生物生成设置 BiomeGenerationSettings.Builder pGenerationSettings,// 生物群系生成设置 @Nullable Music pBackgroundMusic // 生物群系背景音乐 ) { return ExampleOverworldBiomes(pHasPercipitation, pTemperature, pDownfall, 4159204, 329011, null, null, pMobSpawnSettings, pGenerationSettings, pBackgroundMusic); } // 另一个构造方法 同样返回bioome 能配置的更多 private static Biome ExampleOverworldBiomes( boolean pHasPrecipitation, / float pTemperature,// 温度 float pDownfall, int pWaterColor, // 水的颜色 int pWaterFogColor, // 水的雾颜色 @Nullable Integer pGrassColorOverride, // 草方块颜色 @Nullable Integer pFoliageColorOverride, // 树叶颜色 MobSpawnSettings.Builder pMobSpawnSettings, BiomeGenerationSettings.Builder pGenerationSettings, @Nullable Music pBackgroundMusic ) { BiomeSpecialEffects.Builder biomespecialeffects$builder = new BiomeSpecialEffects.Builder() .waterColor(pWaterColor) .waterFogColor(pWaterFogColor) .fogColor(12638463) .skyColor(calculateSkyColor(pTemperature)) .ambientMoodSound(AmbientMoodSettings.LEGACY_CAVE_SETTINGS) .backgroundMusic(pBackgroundMusic); if (pGrassColorOverride != null) { biomespecialeffects$builder.grassColorOverride(pGrassColorOverride); } if (pFoliageColorOverride != null) { biomespecialeffects$builder.foliageColorOverride(pFoliageColorOverride); } return new Biome.BiomeBuilder() .hasPrecipitation(pHasPrecipitation) .temperature(pTemperature) .downfall(pDownfall) .specialEffects(biomespecialeffects$builder.build()) .mobSpawnSettings(pMobSpawnSettings.build()) .generationSettings(pGenerationSettings.build()) .build(); } // 添加一个自己的生物群系,使用了一些原版的方法。具体的内容自己点到方法里面看下把,不是很难,都是一些重复的配置的内容。 public static Biome exampleBiome(HolderGetter pPlacedFeatures, HolderGetter> pWorldCarvers) { // 创建一个MobSpawnSettings.Builder对象,用于配置生物生成设置。 MobSpawnSettings.Builder mobspawnsettings$builder = new MobSpawnSettings.Builder(); // 配置沙漠生成的生物。 BiomeDefaultFeatures.desertSpawns(mobspawnsettings$builder); // 用于配置生物群系生成设置。 BiomeGenerationSettings.Builder biomegenerationsettings$builder = new BiomeGenerationSettings.Builder(pPlacedFeatures, pWorldCarvers); // 添加化石装饰物到生物群系生成设置中 BiomeDefaultFeatures.addFossilDecoration(biomegenerationsettings$builder); // 一些通用的配置 globalOverworldGeneration(biomegenerationsettings$builder); // 矿物 BiomeDefaultFeatures.addDefaultOres(biomegenerationsettings$builder); BiomeDefaultFeatures.addDefaultSoftDisks(biomegenerationsettings$builder); BiomeDefaultFeatures.addDefaultFlowers(biomegenerationsettings$builder); BiomeDefaultFeatures.addDefaultGrass(biomegenerationsettings$builder); BiomeDefaultFeatures.addDesertVegetation(biomegenerationsettings$builder); BiomeDefaultFeatures.addDefaultMushrooms(biomegenerationsettings$builder); BiomeDefaultFeatures.addDesertExtraVegetation(biomegenerationsettings$builder); BiomeDefaultFeatures.addDesertExtraDecoration(biomegenerationsettings$builder); // 调用构造返回biome return ExampleOverworldBiomes(false, 2.0F, 0.0F, mobspawnsettings$builder, biomegenerationsettings$builder, Musics.createGameMusic(SoundEvents.MUSIC_BIOME_DESERT)); } private static void globalOverworldGeneration(BiomeGenerationSettings.Builder pGenerationSettings) { BiomeDefaultFeatures.addDefaultCarversAndLakes(pGenerationSettings); BiomeDefaultFeatures.addDefaultCrystalFormations(pGenerationSettings); BiomeDefaultFeatures.addDefaultMonsterRoom(pGenerationSettings); BiomeDefaultFeatures.addDefaultUndergroundVariety(pGenerationSettings); BiomeDefaultFeatures.addDefaultSprings(pGenerationSettings); BiomeDefaultFeatures.addSurfaceFreezing(pGenerationSettings); } } ``` 下面让我们使用我们的配置的类,进行json数据的生成 ```java // 原版的内容位于BiomeData类下 public class ModBiomeData { // 依旧添加一个对应biome的key public static final ResourceKey EXAMPLE_BIOME = register("example_biome"); // 对应大的create方法 private static ResourceKey register(String pKey) { return ResourceKey.create(Registries.BIOME, new ResourceLocation(ExampleMod.MODID,pKey)); } // bootstrap和之前的一样 public static void bootstrap(BootstapContext pContext) { // 获得placefeature的holdgetter HolderGetter holdergetter = pContext.lookup(Registries.PLACED_FEATURE); HolderGetter> holdergetter1 = pContext.lookup(Registries.CONFIGURED_CARVER); // 给数据生成注册我们的biome pContext.register(ModBiomeData.EXAMPLE_BIOME, ExampleOverworldBiomes.exampleBiome(holdergetter, holdergetter1)); } } ``` 添加到provider中,和之前一样。不介绍了。 ```java public class ModWorldGen extends DatapackBuiltinEntriesProvider { public static final RegistrySetBuilder BUILDER = new RegistrySetBuilder() .add(Registries.CONFIGURED_FEATURE, ModOreFeatures::bootstrap) .add(Registries.PLACED_FEATURE, ModOrePlacements::bootstrap) .add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, ModBiomeModifiers::bootstrap) .add(Registries.STRUCTURE_SET, ModStructureSets::bootstrap) .add(Registries.STRUCTURE, ModStructures::bootstrap) .add(Registries.BIOME, ModBiomeData::bootstrap); public ModWorldGen(PackOutput output, CompletableFuture registries) { super(output, registries, BUILDER,Set.of(ExampleMod.MODID)); } } @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) public class ModDataGeneratorHandler { @SubscribeEvent public static void gatherData(GatherDataEvent event){ ExistingFileHelper efh = event.getExistingFileHelper(); var lp = event.getLookupProvider(); //world gen event.getGenerator().addProvider(event.includeServer(), (DataProvider.Factory) pOutput -> new ModWorldGen(pOutput,lp)); } } ``` 好了,这里你点击data gen之后就会生成你的biome.json文件了,但是你会发现没有在主世界中生成对应的biome,甚至你会发现我们上面的代码都没指定过在那个维度生成。而对于这部分内容,我并没有在neoforge中找到提供的方法, 同样如上,这部分在维度生成相关大的代码中,你或许需要mixin,不过这里我们使用terrablender lib帮帮助我们,在上一次的视频中我们已经配置过了这个lib。 所以我们直接使用。 ```java // 我们使用terrablender的Region类给主世界添加生物群系 public class ModOverworldRegion extends Region { public ModOverworldRegion(ResourceLocation name, int weight) { // name,维度,权重 super(name, RegionType.OVERWORLD, weight); } // 重写addbiome方法,这个方法修改原版的主世界的维度中生成生物群系的方法 @Override public void addBiomes(Registry registry, Consumer>> mapper) { this.addModifiedVanillaOverworldBiomes(mapper,modifiedVanillaOverworldBuilder -> { modifiedVanillaOverworldBuilder.replaceBiome(Biomes.FOREST,ModBiomeData.EXAMPLE_BIOME); }); } } ``` 注册我们写的这个region ```java public class ModTerrablender { public static void registerBiome(){ // 第一个参数我们的region,第二个参数是维度的定位符,第三个是权重。 // 其中权重的数值设置你可以参考原版进行设置 Regions.register(new ModOverworldRegion(new ResourceLocation(ExampleMod.MODID,"overworld"),5)); } } ``` 然后,接下来我们说下怎么利用Terrablender来对生物群系的表面的方块进行修改。 教程只是对其中的一部分内容进行了介绍,并没有涵盖所有的内容,所有内容还是很多且有些复杂的,这说一部分,另一部分放在了维度的,剩下的就大家自己探索了。 其实surface规则的定义是放在维度的json中的。 ```java // 对我们的生物群系的表面进行修改 // 首先说明的一点是surfaceRule是针对维度的,通过定义一系列的sequence来指定生成,如果具有一些条件判断是可以使用ConditionSource类来判断的,RuleSource通常作为一个结果。 public class ModSurfaceRules { // SurfaceRules.RuleSource 表面生成的规则,这里我们使用makeStateRule来创建一个SurfaceRules.RuleSource private static final SurfaceRules.RuleSource DIRT = makeStateRule(Blocks.DIRT); private static final SurfaceRules.RuleSource GRASS_BLOCK = makeStateRule(Blocks.GRASS_BLOCK); private static final SurfaceRules.RuleSource RUBY_BLOCK = makeStateRule(ModBlocks.RUBY_BLOCK.get()); private static final SurfaceRules.RuleSource DIAMOND_ORE = makeStateRule(Blocks.DIAMOND_ORE); // 静态方法,用于产生我们的surface规则。 public static SurfaceRules.RuleSource makeRules() { // 第一个规则是一个条件规则,表示水面上的得的位置 SurfaceRules.ConditionSource isAtOrAboveWaterLevel = SurfaceRules.waterBlockCheck(-1, 0); // 使用第一个规则表示如果isAtOrAboveWaterLevel满足条件则使用GRASS_BLOCK方块。否则使用DIRT SurfaceRules.RuleSource = SurfaceRules.sequence(SurfaceRules.ifTrue(isAtOrAboveWaterLevel, GRASS_BLOCK), DIRT); // 返回我们的SurfaceRules.RuleSource return SurfaceRules.sequence( // 满足生物群系情况下,在地板使用RUBY_BLOCK和在天花板使用DIAMOND_ORE // 这里的地板是指在生成地表的时候的地板和天花板 SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.isBiome(ModBiomeData.EXAMPLE_BIOME), SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, RUBY_BLOCK)), SurfaceRules.ifTrue(SurfaceRules.ON_CEILING, DIAMOND_ORE)), // 这是一条原版的默认规则,如果不是这个生物群系,那么使用默认的草方块 SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, grassSurface) ); } // 这个makeStateRule也很简单,就是返回对应方块默认状态的 SurfaceRules.RuleSource private static SurfaceRules.RuleSource makeStateRule(Block block) { return SurfaceRules.state(block.defaultBlockState()); } } ``` 注册相关的几个类和方法 ```java // The value here should match an entry in the META-INF/mods.toml file @Mod(ExampleMod.MODID) public class ExampleMod { public static final String MODID = "examplemod"; private static final Logger LOGGER = LogUtils.getLogger(); public ExampleMod(IEventBus modEventBus) { // 你的region写在这里 ModTerrablender.registerBiome(); NeoForge.EVENT_BUS.register(this); } private void commonSetup(final FMLCommonSetupEvent event) { // 给主世界添加新的surfaceRule event.enqueueWork(()->{ SurfaceRuleManager.addSurfaceRules(SurfaceRuleManager.RuleCategory.OVERWORLD, ExampleMod.MODID, ModSurfaceRules.makeRules()); }); } @SubscribeEvent public void onServerStarting(ServerStartingEvent event) { } } ``` 关于surfaceRule的教程,可以找一些教程,去看看具体得到相关内容,同样wiki上也有解释,你可以到wiki上查看. 但对于surfaceRule的原版代码内容在SurfaceRuleData这个类下面 到世界中看看你的生物群系吧. --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_46_RegisterDimension.md description: 46 添加维度 相关教程 --- # 参考 https://github.com/Tutorials-By-Kaupenjoe/Forge-Tutorial-1.20.X/commit/c9c8ce9ea968bf834f9553fa6361b510bdc7e0fb#diff-90c4e40141b4edaaf4f71a499fa5124e38d76b36619bb8ad198f2e61e7d4fa39 ## 维度生成相关json的wiki https://zh.minecraft.wiki/w/%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%B4%E5%BA%A6 https://zh.minecraft.wiki/w/%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%96%E7%95%8C%E9%A2%84%E8%AE%BE ## 添加维度 对于维度生成,有比较多的东西,所以我们还是讲一个简单的例子,主要讲下结构,其他的内容大家自己学习,可以去看源码或者wiki。 我们主要和两个类以及这两个类用到的类打交道。其中一个是DimensionType,另一个是LevelStem。 其中第一个DimensionType是关于维度的一些大体上的配置的,例如维度的最大y值,最小y值,使用床是否爆炸等。详细的可以看https://zh.minecraft.wiki/w/%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%B4%E5%BA%A6 其中另一个LevelStem就是规定了维度生成那些生物群系,以及怎么生成。关于该json的内容可以在wiki查看:https://zh.minecraft.wiki/w/%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%96%E7%95%8C%E9%A2%84%E8%AE%BE 这里我们从代码的角度来说相关的内容。 对于配置主要要求我们提供一个LevelStem的类, ```java // 而这个类主要有两个参数,第一个参数是对于的维度type,而第二个参数是我们主要的配置维度生成的参数,要求提供的是一个ChunkGenerator, LevelStem stem = new LevelStem(dimTypes.getOrThrow(ModDimensions.EXAMPLE_DIM_TYPE), noiseBasedChunkGenerator); // 继续往下看,ChunkGenerator的继承树中,我们主要用的是NoiseBasedChunkGenerator,而DebugLevelSource和FlatLevelSource则是调试和超平坦。 ChunkGenerator (net.minecraft.world.level.chunk) DebugLevelSource (net.minecraft.world.level.levelgen) NoiseBasedChunkGenerator (net.minecraft.world.level.levelgen) FlatLevelSource (net.minecraft.world.level.levelgen) // NoiseBasedChunkGenerator就是我们要使用的类了。我们通过这个类类进行设置。 // 我们使用是这个构造函数 public NoiseBasedChunkGenerator(BiomeSource p_256415_, Holder p_256182_) {// 第一个参数表示你要生成的生物群系,第二个参数是NoiseGeneratorSettings super(p_256415_); this.settings = p_256182_;// 也就是你对这个噪声生成器的一些设置 this.globalFluidPicker = Suppliers.memoize(() -> createFluidPicker(p_256182_.value())); } // 其中的NoiseGeneratorSettings就决定了你地形生成的样子 // 我们使用的是一个原版提供的noiseGeneratorSettings public static final ResourceKey AMPLIFIED = ResourceKey.create(Registries.NOISE_SETTINGS, new ResourceLocation("amplified")); // 这是他的定义代码 // 提示:这里的NoiseGeneratorSettings是生成noise settings json文件的 你看与查看原版的json // 以及这里是对应的json的Wiki:https://zh.minecraft.wiki/w/%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%96%E7%95%8C%E7%94%9F%E6%88%90 public static NoiseGeneratorSettings overworld(BootstapContext pContext, boolean pAmplified, boolean pLarge) { return new NoiseGeneratorSettings( NoiseSettings.OVERWORLD_NOISE_SETTINGS, // 一个类型为NoiseSettings的成员变量。 Blocks.STONE.defaultBlockState(), // 默认方块 Blocks.WATER.defaultBlockState(), // 默认流体 NoiseRouterData.overworld(pContext.lookup(Registries.DENSITY_FUNCTION), pContext.lookup(Registries.NOISE), pLarge, pAmplified), //噪声路由器 SurfaceRuleData.overworld(), // surface rule 表面规则: SurfaceRules.RuleSource new OverworldBiomeBuilder().spawnTarget(), // 生成的目标 : List 63, // 海平面高度 false, //是否禁用生物生成 true,// 含水层:aquifersEnabled true,// 是否启用矿脉oreVeinsEnabled, false // useLegacyRandomSource ); } // 对于第一参数 NoiseSettings 是对世界生成参数的一些设置 // 地形开始生成的最低高度 // 地形生成的总高度。 // 噪声大小(水平) // 噪声大小(垂直) record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int noiseSizeVertical) //而NoiseRouter将密度函数应用于用于世界生成的噪声参数 //其中的参数请参阅wiki即可。 // 表面规则,就是我们上期视频说的,位于SurfaceRuleData类下,大家自行查看。 ``` 好了,从上面的内容,我们大致已经把维度生成的结构说清楚了,更详细的内容详见:https://zh.minecraft.wiki/w/%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%96%E7%95%8C%E7%94%9F%E6%88%90 为了生成你想要的维度,还是建议多看原版的代码,如果无法满足你的要求在尝试自己写,主要的几个部分已经说了,你可以参考原版的代码去写。也去看看开源的模组,以及相关的视频教程,比如一些游戏教程利用噪声是怎么生成地形的。 现在回到我们的NoiseBasedChunkGenerator的第一参数,要求我们传入的是生物群系的源,这里并不能直接传入生物群系进去,还需要做一些配置, ```java public NoiseBasedChunkGenerator(BiomeSource p_256415_, Holder p_256182_) {// 第一个参数表示你要生成的生物群系,第二个参数是NoiseGeneratorSettings super(p_256415_); this.settings = p_256182_;// 也就是你对这个噪声生成器的一些设置 this.globalFluidPicker = Suppliers.memoize(() -> createFluidPicker(p_256182_.value())); } /// 这个createFromList方法帮助我们返回就是第一个参数所需要的BiomeSource // 我们通过下面的方法传入一系列的生物群系,还需要知名生物群系的一些设置。 // 这里传入了个四个生物群系 MultiNoiseBiomeSource.createFromList( new Climate.ParameterList<>(List.of(Pair.of( Climate.parameters(0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F), biomeRegistry.getOrThrow(ModBiomeData.EXAMPLE_BIOME)), Pair.of( Climate.parameters(0.1F, 0.2F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F), biomeRegistry.getOrThrow(Biomes.BIRCH_FOREST)), Pair.of( Climate.parameters(0.3F, 0.6F, 0.1F, 0.1F, 0.0F, 0.0F, 0.0F), biomeRegistry.getOrThrow(Biomes.OCEAN)), Pair.of( Climate.parameters(0.4F, 0.3F, 0.2F, 0.1F, 0.0F, 0.0F, 0.0F), biomeRegistry.getOrThrow(Biomes.DARK_FOREST)) ))), // 来看对应的生物群系的内容是上面 Climate.parameters(0.4F, 0.3F, 0.2F, 0.1F, 0.0F, 0.0F, 0.0F), // 定义 // 这些参数是生物群系的噪声的设置参数,主要决定了生物群系的一些生成,例如高度,悬崖,峡谷等等地形的生成 // temperature:温度。不要与生物群系的温度混淆。 // humidity:湿度。格式与temperature相同,可以为一个对象、一个数组或者一个浮点数。 // continentalness:大陆性。在原版主世界,低大陆性对应于海洋地形,高大陆性对应于内陆地形。格式与temperature相同。 // erosion:侵蚀度。在原版主世界,高侵蚀度对应平坦地形。格式与temperature相同。 // weirdness:怪异度。在原版主世界,用于生物群系崎岖程度。格式与temperature相同。 // depth:深度。在原版主世界,只有此参数会随垂直位置改变:在XZ坐标不变的情况下,Y轴坐标越高,该值越高。格式与temperature相同。 // offset:偏移。取值为0.0到1.0的闭区间。类似于其他参数,但是offset在任何地方都是0,因此若其他参数都相等,将这个参数设为靠近0的值会使其占更多优势。 public static Climate.ParameterPoint parameters( float pTemperature, float pHumidity, float pContinentalness, float pErosion, float pDepth, float pWeirdness, float pOffset ) ``` 以上的内容同样可以在wiki中查询得到:https://zh.minecraft.wiki/w/%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%96%E7%95%8C%E7%94%9F%E6%88%90 好了大体上理解之后我们来看我们的代码把,这里只是简单添加了一个维度。以及一个传送的方块,将玩家送到对应的维度。 ```java public class ModDimensions { // 对应的key,这里需要注意的是LevelStem和Level应该是一样的名字 public static final ResourceKey EXAMPLE_DIM_STEM_KEY = ResourceKey.create(Registries.LEVEL_STEM, new ResourceLocation(ExampleMod.MODID, "examplemod_dim")); public static final ResourceKey EXAMPLE_LEVEL_KEY = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(ExampleMod.MODID, "examplemod_dim")); public static final ResourceKey EXAMPLE_DIM_TYPE = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation(ExampleMod.MODID, "examplemod_dim_type")); // 生成DimensionType 对应的json public static void bootstrapType(BootstapContext context) { // 详细的参数含义看wiki context.register(EXAMPLE_DIM_TYPE, new DimensionType( OptionalLong.of(12000), // 是否是固定时间,即固定在白天或者夜晚的某一个时间 false, // 天空光照 false, // hasCeiling false, // ultraWarm false, // natural 1.0, // coordinateScale true, // bedWorks false, // respawnAnchorWorks 0, // minY 256, // height 256, // logicalHeight BlockTags.INFINIBURN_OVERWORLD, // infiniburn BuiltinDimensionTypes.OVERWORLD_EFFECTS, // effectsLocation 1.0f, // ambientLight new DimensionType.MonsterSettings(false, false, ConstantInt.of(0), 0))); } // 生成dimension的json文件 public static void bootstrapStem(BootstapContext context) { HolderGetter biomeRegistry = context.lookup(Registries.BIOME); HolderGetter dimTypes = context.lookup(Registries.DIMENSION_TYPE); HolderGetter noiseGenSettings = context.lookup(Registries.NOISE_SETTINGS); // 这里的代码都说过了。 NoiseBasedChunkGenerator noiseBasedChunkGenerator = new NoiseBasedChunkGenerator( MultiNoiseBiomeSource.createFromList( new Climate.ParameterList<>(List.of(Pair.of( Climate.parameters(0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F), biomeRegistry.getOrThrow(ModBiomeData.EXAMPLE_BIOME)), Pair.of( Climate.parameters(0.1F, 0.2F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F), biomeRegistry.getOrThrow(Biomes.BIRCH_FOREST)), Pair.of( Climate.parameters(0.3F, 0.6F, 0.1F, 0.1F, 0.0F, 0.0F, 0.0F), biomeRegistry.getOrThrow(Biomes.OCEAN)), Pair.of( Climate.parameters(0.4F, 0.3F, 0.2F, 0.1F, 0.0F, 0.0F, 0.0F), biomeRegistry.getOrThrow(Biomes.DARK_FOREST)) ))), noiseGenSettings.getOrThrow(NoiseGeneratorSettings.AMPLIFIED)); // 一个stem 给context LevelStem stem = new LevelStem(dimTypes.getOrThrow(ModDimensions.EXAMPLE_DIM_TYPE), noiseBasedChunkGenerator); context.register(ModDimensions.EXAMPLE_DIM_STEM_KEY, stem); } } ``` 好了下面来看看我们的怎么实现传送的,添加一个方块,实现玩家右键方块后传送到另一个维度,如果在另一个维度点击传送到主世界。 ```java public class PortalBlock extends Block { public PortalBlock() { super(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE).noLootTable()); } @Override public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) { if(pPlayer.canChangeDimensions()){ handlePortalTeleport(pLevel, pPos, pPlayer); return InteractionResult.SUCCESS; }else{ return InteractionResult.FAIL; } } // 传送的方法 // 注意传送的逻辑应该在服务端 private void handlePortalTeleport(Level pLevel, BlockPos pPos, Player pPlayer) { if(pPlayer.level() instanceof ServerLevel serverLevel){ MinecraftServer minecraftServer = serverLevel.getServer(); // 根据玩家当前所在的维度(dimension)判断传送目标维度的key。 ResourceKey resourceKey = pPlayer.level().dimension() == ModDimensions.EXAMPLE_LEVEL_KEY ? Level.OVERWORLD : ModDimensions.EXAMPLE_LEVEL_KEY; ServerLevel portalDimension = minecraftServer.getLevel(resourceKey); if (portalDimension != null && !pPlayer.isPassenger()) { if(resourceKey == ModDimensions.EXAMPLE_LEVEL_KEY){ // 如果前往EXAMPLE_LEVEL_KEY维度,则为true pPlayer.changeDimension(portalDimension,new ModTeleporter(pPos,true)); }else{ // 如果前往OVERWORLD维度,则为false // 其中的第二个参数是实现了ITeleporter接口的一个类。 // 这里我们自己写了一个类 pPlayer.changeDimension(portalDimension,new ModTeleporter(pPos,false)); } } } } } /// 传送的类 public class ModTeleporter implements ITeleporter { public static BlockPos thisPos = BlockPos.ZERO; public static boolean insideDimension = false; public ModTeleporter(BlockPos pos ,boolean insideDimension) { this.thisPos = pos; this.insideDimension = insideDimension; } // 主要重写的是这个方法。实现将实体放在什么位置的逻辑。 @Override public Entity placeEntity(Entity entity, ServerLevel currentWorld, ServerLevel destWorld, float yaw, Function repositionEntity) { entity = repositionEntity.apply(false); // 获得对应的实体。 int y = 61; // false 时 dest 是主世界 if(!insideDimension){ y = thisPos.getY(); } BlockPos destinationPos = new BlockPos(thisPos.getX(), y, thisPos.getZ()); int tries = 0; //使用循环(最多25次)来尝试找到一个合适的空气或可替换水的位置,通过逐次向上移动 destinationPos 来进行搜索。 while((destWorld.getBlockState(destinationPos).getBlock() != Blocks.AIR) && !destWorld.getBlockState(destinationPos).canBeReplaced(Fluids.WATER) && (destWorld.getBlockState(destinationPos.above()).getBlock() != Blocks.AIR) && !destWorld.getBlockState(destinationPos.above()).canBeReplaced(Fluids.WATER) && (tries < 25)){ destinationPos = destinationPos.above(2); tries++; } // 将实体对象 entity 移动到找到的合适位置。 entity.setPos(destinationPos.getX(), destinationPos.getY(), destinationPos.getZ()); // 如果是去往Exampledim的维度 if (insideDimension) { //如果 insideDimension 为真,则在目的地世界内以 destinationPos 为中心的周围区域内检查是否存在传送门方块,若不存在则在 destinationPos 处生成一个传送门方块。 boolean doSetBlock = true; for (BlockPos checkPos : BlockPos.betweenClosed(destinationPos.below(10).west(10), destinationPos.above(10).east(10))) { if (destWorld.getBlockState(checkPos).getBlock() instanceof PortalBlock) { doSetBlock = false; break; } } if (doSetBlock) { destWorld.setBlock(destinationPos, ModBlocks.PORTAL_BLOCK.get().defaultBlockState(), 3); } } return entity; } } ``` 对于方块的注册就自己实现把。 好了可以进入到世界中查看了。 最后给大家推荐一个生成json的网站,你可以通过手动配置选择的方式获得json,这个网站应该可以帮助你理解各个字段的含义。 https://misode.github.io/worldgen/noise-settings/ ## 原版的类的位置 levelstem net.minecraft.world.level.dimension.LevelStem dimensiontype net.minecraft.data.worldgen.DimensionTypes --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_46_RegisterDimension.md description: 46 添加维度 相关教程 --- # 参考 https://github.com/Tutorials-By-Kaupenjoe/Forge-Tutorial-1.20.X/commit/c9c8ce9ea968bf834f9553fa6361b510bdc7e0fb#diff-90c4e40141b4edaaf4f71a499fa5124e38d76b36619bb8ad198f2e61e7d4fa39 ## 维度生成相关json的wiki https://zh.minecraft.wiki/w/%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%B4%E5%BA%A6 https://zh.minecraft.wiki/w/%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%96%E7%95%8C%E9%A2%84%E8%AE%BE ## 添加维度 对于维度生成,有比较多的东西,所以我们还是讲一个简单的例子,主要讲下结构,其他的内容大家自己学习,可以去看源码或者wiki。 我们主要和两个类以及这两个类用到的类打交道。其中一个是DimensionType,另一个是LevelStem。 其中第一个DimensionType是关于维度的一些大体上的配置的,例如维度的最大y值,最小y值,使用床是否爆炸等。详细的可以看https://zh.minecraft.wiki/w/%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%B4%E5%BA%A6 其中另一个LevelStem就是规定了维度生成那些生物群系,以及怎么生成。关于该json的内容可以在wiki查看:https://zh.minecraft.wiki/w/%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%96%E7%95%8C%E9%A2%84%E8%AE%BE 这里我们从代码的角度来说相关的内容。 对于配置主要要求我们提供一个LevelStem的类, ```java // 而这个类主要有两个参数,第一个参数是对于的维度type,而第二个参数是我们主要的配置维度生成的参数,要求提供的是一个ChunkGenerator, LevelStem stem = new LevelStem(dimTypes.getOrThrow(ModDimensions.EXAMPLE_DIM_TYPE), noiseBasedChunkGenerator); // 继续往下看,ChunkGenerator的继承树中,我们主要用的是NoiseBasedChunkGenerator,而DebugLevelSource和FlatLevelSource则是调试和超平坦。 ChunkGenerator (net.minecraft.world.level.chunk) DebugLevelSource (net.minecraft.world.level.levelgen) NoiseBasedChunkGenerator (net.minecraft.world.level.levelgen) FlatLevelSource (net.minecraft.world.level.levelgen) // NoiseBasedChunkGenerator就是我们要使用的类了。我们通过这个类类进行设置。 // 我们使用是这个构造函数 public NoiseBasedChunkGenerator(BiomeSource p_256415_, Holder p_256182_) {// 第一个参数表示你要生成的生物群系,第二个参数是NoiseGeneratorSettings super(p_256415_); this.settings = p_256182_;// 也就是你对这个噪声生成器的一些设置 this.globalFluidPicker = Suppliers.memoize(() -> createFluidPicker(p_256182_.value())); } // 其中的NoiseGeneratorSettings就决定了你地形生成的样子 // 我们使用的是一个原版提供的noiseGeneratorSettings public static final ResourceKey AMPLIFIED = ResourceKey.create(Registries.NOISE_SETTINGS, new ResourceLocation("amplified")); // 这是他的定义代码 // 提示:这里的NoiseGeneratorSettings是生成noise settings json文件的 你看与查看原版的json // 以及这里是对应的json的Wiki:https://zh.minecraft.wiki/w/%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%96%E7%95%8C%E7%94%9F%E6%88%90 public static NoiseGeneratorSettings overworld(BootstapContext pContext, boolean pAmplified, boolean pLarge) { return new NoiseGeneratorSettings( NoiseSettings.OVERWORLD_NOISE_SETTINGS, // 一个类型为NoiseSettings的成员变量。 Blocks.STONE.defaultBlockState(), // 默认方块 Blocks.WATER.defaultBlockState(), // 默认流体 NoiseRouterData.overworld(pContext.lookup(Registries.DENSITY_FUNCTION), pContext.lookup(Registries.NOISE), pLarge, pAmplified), //噪声路由器 SurfaceRuleData.overworld(), // surface rule 表面规则: SurfaceRules.RuleSource new OverworldBiomeBuilder().spawnTarget(), // 生成的目标 : List 63, // 海平面高度 false, //是否禁用生物生成 true,// 含水层:aquifersEnabled true,// 是否启用矿脉oreVeinsEnabled, false // useLegacyRandomSource ); } // 对于第一参数 NoiseSettings 是对世界生成参数的一些设置 // 地形开始生成的最低高度 // 地形生成的总高度。 // 噪声大小(水平) // 噪声大小(垂直) record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int noiseSizeVertical) //而NoiseRouter将密度函数应用于用于世界生成的噪声参数 //其中的参数请参阅wiki即可。 // 表面规则,就是我们上期视频说的,位于SurfaceRuleData类下,大家自行查看。 ``` 好了,从上面的内容,我们大致已经把维度生成的结构说清楚了,更详细的内容详见:https://zh.minecraft.wiki/w/%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%96%E7%95%8C%E7%94%9F%E6%88%90 为了生成你想要的维度,还是建议多看原版的代码,如果无法满足你的要求在尝试自己写,主要的几个部分已经说了,你可以参考原版的代码去写。也去看看开源的模组,以及相关的视频教程,比如一些游戏教程利用噪声是怎么生成地形的。 现在回到我们的NoiseBasedChunkGenerator的第一参数,要求我们传入的是生物群系的源,这里并不能直接传入生物群系进去,还需要做一些配置, ```java public NoiseBasedChunkGenerator(BiomeSource p_256415_, Holder p_256182_) {// 第一个参数表示你要生成的生物群系,第二个参数是NoiseGeneratorSettings super(p_256415_); this.settings = p_256182_;// 也就是你对这个噪声生成器的一些设置 this.globalFluidPicker = Suppliers.memoize(() -> createFluidPicker(p_256182_.value())); } /// 这个createFromList方法帮助我们返回就是第一个参数所需要的BiomeSource // 我们通过下面的方法传入一系列的生物群系,还需要知名生物群系的一些设置。 // 这里传入了个四个生物群系 MultiNoiseBiomeSource.createFromList( new Climate.ParameterList<>(List.of(Pair.of( Climate.parameters(0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F), biomeRegistry.getOrThrow(ModBiomeData.EXAMPLE_BIOME)), Pair.of( Climate.parameters(0.1F, 0.2F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F), biomeRegistry.getOrThrow(Biomes.BIRCH_FOREST)), Pair.of( Climate.parameters(0.3F, 0.6F, 0.1F, 0.1F, 0.0F, 0.0F, 0.0F), biomeRegistry.getOrThrow(Biomes.OCEAN)), Pair.of( Climate.parameters(0.4F, 0.3F, 0.2F, 0.1F, 0.0F, 0.0F, 0.0F), biomeRegistry.getOrThrow(Biomes.DARK_FOREST)) ))), // 来看对应的生物群系的内容是上面 Climate.parameters(0.4F, 0.3F, 0.2F, 0.1F, 0.0F, 0.0F, 0.0F), // 定义 // 这些参数是生物群系的噪声的设置参数,主要决定了生物群系的一些生成,例如高度,悬崖,峡谷等等地形的生成 // temperature:温度。不要与生物群系的温度混淆。 // humidity:湿度。格式与temperature相同,可以为一个对象、一个数组或者一个浮点数。 // continentalness:大陆性。在原版主世界,低大陆性对应于海洋地形,高大陆性对应于内陆地形。格式与temperature相同。 // erosion:侵蚀度。在原版主世界,高侵蚀度对应平坦地形。格式与temperature相同。 // weirdness:怪异度。在原版主世界,用于生物群系崎岖程度。格式与temperature相同。 // depth:深度。在原版主世界,只有此参数会随垂直位置改变:在XZ坐标不变的情况下,Y轴坐标越高,该值越高。格式与temperature相同。 // offset:偏移。取值为0.0到1.0的闭区间。类似于其他参数,但是offset在任何地方都是0,因此若其他参数都相等,将这个参数设为靠近0的值会使其占更多优势。 public static Climate.ParameterPoint parameters( float pTemperature, float pHumidity, float pContinentalness, float pErosion, float pDepth, float pWeirdness, float pOffset ) ``` 以上的内容同样可以在wiki中查询得到:https://zh.minecraft.wiki/w/%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%96%E7%95%8C%E7%94%9F%E6%88%90 好了大体上理解之后我们来看我们的代码把,这里只是简单添加了一个维度。以及一个传送的方块,将玩家送到对应的维度。 ```java public class ModDimensions { // 对应的key,这里需要注意的是LevelStem和Level应该是一样的名字 public static final ResourceKey EXAMPLE_DIM_STEM_KEY = ResourceKey.create(Registries.LEVEL_STEM, new ResourceLocation(ExampleMod.MODID, "examplemod_dim")); public static final ResourceKey EXAMPLE_LEVEL_KEY = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(ExampleMod.MODID, "examplemod_dim")); public static final ResourceKey EXAMPLE_DIM_TYPE = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation(ExampleMod.MODID, "examplemod_dim_type")); // 生成DimensionType 对应的json public static void bootstrapType(BootstapContext context) { // 详细的参数含义看wiki context.register(EXAMPLE_DIM_TYPE, new DimensionType( OptionalLong.of(12000), // 是否是固定时间,即固定在白天或者夜晚的某一个时间 false, // 天空光照 false, // hasCeiling false, // ultraWarm false, // natural 1.0, // coordinateScale true, // bedWorks false, // respawnAnchorWorks 0, // minY 256, // height 256, // logicalHeight BlockTags.INFINIBURN_OVERWORLD, // infiniburn BuiltinDimensionTypes.OVERWORLD_EFFECTS, // effectsLocation 1.0f, // ambientLight new DimensionType.MonsterSettings(false, false, ConstantInt.of(0), 0))); } // 生成dimension的json文件 public static void bootstrapStem(BootstapContext context) { HolderGetter biomeRegistry = context.lookup(Registries.BIOME); HolderGetter dimTypes = context.lookup(Registries.DIMENSION_TYPE); HolderGetter noiseGenSettings = context.lookup(Registries.NOISE_SETTINGS); // 这里的代码都说过了。 NoiseBasedChunkGenerator noiseBasedChunkGenerator = new NoiseBasedChunkGenerator( MultiNoiseBiomeSource.createFromList( new Climate.ParameterList<>(List.of(Pair.of( Climate.parameters(0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F), biomeRegistry.getOrThrow(ModBiomeData.EXAMPLE_BIOME)), Pair.of( Climate.parameters(0.1F, 0.2F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F), biomeRegistry.getOrThrow(Biomes.BIRCH_FOREST)), Pair.of( Climate.parameters(0.3F, 0.6F, 0.1F, 0.1F, 0.0F, 0.0F, 0.0F), biomeRegistry.getOrThrow(Biomes.OCEAN)), Pair.of( Climate.parameters(0.4F, 0.3F, 0.2F, 0.1F, 0.0F, 0.0F, 0.0F), biomeRegistry.getOrThrow(Biomes.DARK_FOREST)) ))), noiseGenSettings.getOrThrow(NoiseGeneratorSettings.AMPLIFIED)); // 一个stem 给context LevelStem stem = new LevelStem(dimTypes.getOrThrow(ModDimensions.EXAMPLE_DIM_TYPE), noiseBasedChunkGenerator); context.register(ModDimensions.EXAMPLE_DIM_STEM_KEY, stem); } } ``` 好了下面来看看我们的怎么实现传送的,添加一个方块,实现玩家右键方块后传送到另一个维度,如果在另一个维度点击传送到主世界。 ```java public class PortalBlock extends Block { public PortalBlock() { super(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE).noLootTable()); } @Override public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) { if(pPlayer.canChangeDimensions()){ handlePortalTeleport(pLevel, pPos, pPlayer); return InteractionResult.SUCCESS; }else{ return InteractionResult.FAIL; } } // 传送的方法 // 注意传送的逻辑应该在服务端 private void handlePortalTeleport(Level pLevel, BlockPos pPos, Player pPlayer) { if(pPlayer.level() instanceof ServerLevel serverLevel){ MinecraftServer minecraftServer = serverLevel.getServer(); // 根据玩家当前所在的维度(dimension)判断传送目标维度的key。 ResourceKey resourceKey = pPlayer.level().dimension() == ModDimensions.EXAMPLE_LEVEL_KEY ? Level.OVERWORLD : ModDimensions.EXAMPLE_LEVEL_KEY; ServerLevel portalDimension = minecraftServer.getLevel(resourceKey); if (portalDimension != null && !pPlayer.isPassenger()) { if(resourceKey == ModDimensions.EXAMPLE_LEVEL_KEY){ // 如果前往EXAMPLE_LEVEL_KEY维度,则为true pPlayer.changeDimension(portalDimension,new ModTeleporter(pPos,true)); }else{ // 如果前往OVERWORLD维度,则为false // 其中的第二个参数是实现了ITeleporter接口的一个类。 // 这里我们自己写了一个类 pPlayer.changeDimension(portalDimension,new ModTeleporter(pPos,false)); } } } } } /// 传送的类 public class ModTeleporter implements ITeleporter { public static BlockPos thisPos = BlockPos.ZERO; public static boolean insideDimension = false; public ModTeleporter(BlockPos pos ,boolean insideDimension) { this.thisPos = pos; this.insideDimension = insideDimension; } // 主要重写的是这个方法。实现将实体放在什么位置的逻辑。 @Override public Entity placeEntity(Entity entity, ServerLevel currentWorld, ServerLevel destWorld, float yaw, Function repositionEntity) { entity = repositionEntity.apply(false); // 获得对应的实体。 int y = 61; // false 时 dest 是主世界 if(!insideDimension){ y = thisPos.getY(); } BlockPos destinationPos = new BlockPos(thisPos.getX(), y, thisPos.getZ()); int tries = 0; //使用循环(最多25次)来尝试找到一个合适的空气或可替换水的位置,通过逐次向上移动 destinationPos 来进行搜索。 while((destWorld.getBlockState(destinationPos).getBlock() != Blocks.AIR) && !destWorld.getBlockState(destinationPos).canBeReplaced(Fluids.WATER) && (destWorld.getBlockState(destinationPos.above()).getBlock() != Blocks.AIR) && !destWorld.getBlockState(destinationPos.above()).canBeReplaced(Fluids.WATER) && (tries < 25)){ destinationPos = destinationPos.above(2); tries++; } // 将实体对象 entity 移动到找到的合适位置。 entity.setPos(destinationPos.getX(), destinationPos.getY(), destinationPos.getZ()); // 如果是去往Exampledim的维度 if (insideDimension) { //如果 insideDimension 为真,则在目的地世界内以 destinationPos 为中心的周围区域内检查是否存在传送门方块,若不存在则在 destinationPos 处生成一个传送门方块。 boolean doSetBlock = true; for (BlockPos checkPos : BlockPos.betweenClosed(destinationPos.below(10).west(10), destinationPos.above(10).east(10))) { if (destWorld.getBlockState(checkPos).getBlock() instanceof PortalBlock) { doSetBlock = false; break; } } if (doSetBlock) { destWorld.setBlock(destinationPos, ModBlocks.PORTAL_BLOCK.get().defaultBlockState(), 3); } } return entity; } } ``` 对于方块的注册就自己实现把。 好了可以进入到世界中查看了。 最后给大家推荐一个生成json的网站,你可以通过手动配置选择的方式获得json,这个网站应该可以帮助你理解各个字段的含义。 https://misode.github.io/worldgen/noise-settings/ ## 原版的类的位置 levelstem net.minecraft.world.level.dimension.LevelStem dimensiontype net.minecraft.data.worldgen.DimensionTypes --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_47_Command.md description: 47 命令 相关教程 --- # 参考 https://boson.v2mcdev.com/command/command.html ## 命令 命令相对于旧版本的变动并不是很大,我们这里也是参考之前的boson的教程的内容,讲解下命令的添加。 下面是我们添加了一个命令,这个命令是输入 /Examplemod test 然后执行在聊天栏输入cmd.example.hello,你可以去做些其他的事情,不过这里就让他输出了hello。 ```java @SubscribeEvent public static void onServerStaring(RegisterCommandsEvent event) { CommandDispatcher dispatcher = event.getDispatcher(); LiteralCommandNode cmd = dispatcher.register( Commands.literal(ExampleMod.MODID).then( Commands.literal("test") .requires((commandSourceStack -> commandSourceStack.hasPermission(2))) .executes(ExampleCommand.INSTANCE) ) ); } ``` 我们分别看看这个代码中的内容 ```java // 首先我们要使用的事件是RegisterCommandsEvent,位于forge总线中, @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.FORGE) // 下面是关于注册的部分 // 我们要获得对于的分发器dispatcher,获得方法就是通过event获得 CommandDispatcher dispatcher = event.getDispatcher(); // 之后通过dispatcher添加新的command,我们使用分发器的register方法进行注册 // 该方法要求要求一个LiteralArgumentBuilder作为参数,其中的S泛型为CommandSourceStack public LiteralCommandNode register(LiteralArgumentBuilder command) { // 对于的获得LiteralArgumentBuilder,我们是通过Commands的静态方法literal获得的 // 其中的string 参数就是注册的命令名字,你在调用时候也需要输入的这个名字 public static LiteralArgumentBuilder literal(String pName) { // 在我们的代码中这个名称是ExampleMod.MODID,所以我们可以直接使用Commands.literal(ExampleMod.MODID) Commands.literal(ExampleMod.MODID) // 之后通过then或者executes方法添加子命令,这里我们使用then方法,其中的参数是一个LiteralArgumentBuilder, // then 表示该命令没有结束 需要添加子命令 public T then(ArgumentBuilder argument) { // 你也可以给你的命令添加一些限制处理,比如权限限制,这里我们使用requires方法,其中的参数是一个Predicate, // Predicate是一个函数式接口,其中的参数为CommandSourceStack,返回值为boolean,表示是否满足条件 public T requires(Predicate requirement) { // 之后我们通过executes方法添加执行的命令,其中的参数是一个Command, // 这里的command就是命令执行的程序,这个程序由我们来写。 public T executes(Command command) { ``` 现在让我们来看看.executes(ExampleCommand.INSTANCE)中的ExampleCommand是怎么操作的。 ```java // 首先我们需要一个Command类,我们实现Command接口,并实现其中的run方法,该方法就是命令满足条件时候的调用方法 public class ExampleCommand implements Command { // 这里定义的INSTANCE就是我们在注册命令时候要求传入的Command实例 public static final Command INSTANCE = new ExampleCommand(); @Override public int run(CommandContext commandContext) throws CommandSyntaxException { // 这里就是给发送命令的人发了一条消息。 commandContext.getSource().sendSystemMessage(Component.literal("cmd.example.hello")); return 0; } } ``` 原版的命令位于该包下:package net.minecraft.server.commands; 大家可以参考学习 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_47_Command.md description: 47 命令 相关教程 --- # 参考 https://boson.v2mcdev.com/command/command.html ## 命令 命令相对于旧版本的变动并不是很大,我们这里也是参考之前的boson的教程的内容,讲解下命令的添加。 下面是我们添加了一个命令,这个命令是输入 /Examplemod test 然后执行在聊天栏输入cmd.example.hello,你可以去做些其他的事情,不过这里就让他输出了hello。 ```java @SubscribeEvent public static void onServerStaring(RegisterCommandsEvent event) { CommandDispatcher dispatcher = event.getDispatcher(); LiteralCommandNode cmd = dispatcher.register( Commands.literal(ExampleMod.MODID).then( Commands.literal("test") .requires((commandSourceStack -> commandSourceStack.hasPermission(2))) .executes(ExampleCommand.INSTANCE) ) ); } ``` 我们分别看看这个代码中的内容 ```java // 首先我们要使用的事件是RegisterCommandsEvent,位于forge总线中, @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.FORGE) // 下面是关于注册的部分 // 我们要获得对于的分发器dispatcher,获得方法就是通过event获得 CommandDispatcher dispatcher = event.getDispatcher(); // 之后通过dispatcher添加新的command,我们使用分发器的register方法进行注册 // 该方法要求要求一个LiteralArgumentBuilder作为参数,其中的S泛型为CommandSourceStack public LiteralCommandNode register(LiteralArgumentBuilder command) { // 对于的获得LiteralArgumentBuilder,我们是通过Commands的静态方法literal获得的 // 其中的string 参数就是注册的命令名字,你在调用时候也需要输入的这个名字 public static LiteralArgumentBuilder literal(String pName) { // 在我们的代码中这个名称是ExampleMod.MODID,所以我们可以直接使用Commands.literal(ExampleMod.MODID) Commands.literal(ExampleMod.MODID) // 之后通过then或者executes方法添加子命令,这里我们使用then方法,其中的参数是一个LiteralArgumentBuilder, // then 表示该命令没有结束 需要添加子命令 public T then(ArgumentBuilder argument) { // 你也可以给你的命令添加一些限制处理,比如权限限制,这里我们使用requires方法,其中的参数是一个Predicate, // Predicate是一个函数式接口,其中的参数为CommandSourceStack,返回值为boolean,表示是否满足条件 public T requires(Predicate requirement) { // 之后我们通过executes方法添加执行的命令,其中的参数是一个Command, // 这里的command就是命令执行的程序,这个程序由我们来写。 public T executes(Command command) { ``` 现在让我们来看看.executes(ExampleCommand.INSTANCE)中的ExampleCommand是怎么操作的。 ```java // 首先我们需要一个Command类,我们实现Command接口,并实现其中的run方法,该方法就是命令满足条件时候的调用方法 public class ExampleCommand implements Command { // 这里定义的INSTANCE就是我们在注册命令时候要求传入的Command实例 public static final Command INSTANCE = new ExampleCommand(); @Override public int run(CommandContext commandContext) throws CommandSyntaxException { // 这里就是给发送命令的人发了一条消息。 commandContext.getSource().sendSystemMessage(Component.literal("cmd.example.hello")); return 0; } } ``` 原版的命令位于该包下:package net.minecraft.server.commands; 大家可以参考学习 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_48_Advancement.md description: 48 成就 相关教程 --- # 参考 https://github.dev/VazkiiMods/Botania ## 成就/进度 进度或者说成就,就是游戏里面那些成就,我们这次学习怎么添加成就,这就也是使用的json数据作为驱动的,其中一些原版的检验的条件是你自己的可以直接使用的, 不过我们这里添加一个自己的检验条件。 我们添加这样的一个进度,当你使用我们之前创建的的在维度等级下存储数据的方块时候,就会弹出这个成就。 在开始之前我们说以下,我们写的是一类的触发器即我们上文说的检验条件。这个触发器类似你写的规则,比如,我要添加一系列的成就,不过这些成就都是使用了某个物品就可以触发对应的使用物品成就,所以我们没必要为所有的物品都编写这样的触发器,而是写一个通用的触发器,那么既然这样的我们应该怎么判断是那个物品呢?这里就是使用了上文的JSON驱动了,你需要把对应的匹配的物品写入到JSON中,然后判断传入的物品和JSON序列化读入的物品是否一致就会是否完成这个成就了。 所以我们写的是一系列的规则,原版也正是这样的设计的。不过这里的例子直接采用了硬编码的方式写死了这个成就,对于json驱动的方式可以去看看原版的,这里介绍了大致的流程。 ```java // 进度的触发器,完成自己的触发器,需要你继承SimpleCriterionTrigger类,原版的触发器也同样的继承于这个类的。 // 泛型是一个内部类,这个内部类,应该继承SimpleCriterionTrigger.SimpleInstance的内部类,用来存储实例的。 // 你可以通过继承关系看到原版的触发器 public class GiveAppleTrigger extends SimpleCriterionTrigger { // 这里的trigger就是我们的判断的成就是否完成的条件 // 如果你的触发器很复杂,需要判断多个条件,比如物品,位置,方块,等等 // 看有这样写trigger(player,pos,block,item) // 之后在this.trigger(player,第二个参数书写你的判断条件,这里要求传入一个lammbda表达式,返回true或者false,true代表触发,false代表不触发。) public void trigger(ServerPlayer pPlayer) { this.trigger(pPlayer,instance -> true); } // 我们这里需要返回一个对应的触发情况,也就是读取的json时候的内容是什么。 // 我这里的json仅仅存储了player这个信息, 但是我们并不需要去处理player相关的内容,所以这里返回的是一个空的Optional。 // 关于一些其他的判断可以去看看原版的Criterion是怎么写的。 // 例如如果你要判断是玩家捡起了苹果这个物品,那么你应该这里创建的Instance包含物品,传入对应的苹果。 // 而于我们为什么可以使用createCriterion这个方法,其实是SimpleCriterionTrigger实现了对应的接口,大家可以去查看源码 // 下面可以看一个三叉戟成就的例子在下面。 public static Criterion giveAppleBlock() { return InitTrigger.GIVE_RUBY_APPLE.get().createCriterion(new GiveAppleTrigger.Instance(Optional.empty())); } // 返回对应的codec,用于反序列化json用的。 @Override public Codec codec() { return Instance.CODEC; } // 对应的Instance类 public static class Instance implements SimpleCriterionTrigger.SimpleInstance { // 存储player private Optional player; public Instance(Optional player) { super(); /// this.player = player; } public static final Codec CODEC = RecordCodecBuilder.create( // 这里的内容对应的你的json中的存储字段,详细看原版的内容 p_312304_ -> p_312304_.group( ExtraCodecs.strictOptionalField(EntityPredicate.ADVANCEMENT_CODEC, "player").forGetter(GiveAppleTrigger.Instance::player) ) .apply(p_312304_, GiveAppleTrigger.Instance::new) ); // 实现impleCriterionTrigger.SimpleInstance该接口要求实现的方法。 @Override public Optional player() { return this.player; } } } ``` 这是三叉戟成就的一个例子,不过这里的触发器却是玩家攻击实体的一个触发器,不过三叉戟攻击实体确实算是玩家攻击实体的一个特例,所以这里的触发器不仅仅可以使用三叉戟你也可以指定其他的武器制作对应的成就。不过这里还是限制了三叉戟,我们可以看看是怎么限制的 ```java // 这个是返回对应的Criterion的方法,指定的类型是DamagePredicate,通过这个DamagePredicate进行限制是三叉戟的攻击,其他两个字段则不做限制,你同样可以限制第一个player字段表示只有某个人可以触发这个成就,或者限制第三个字段,表示攻击某个怪物触发这个成就,或者限制第二个和第三个表示怎么样的伤害某个生物触发。 public static Criterion playerHurtEntityWithDamage(DamagePredicate.Builder pDamage) { return CriteriaTriggers.PLAYER_HURT_ENTITY .createCriterion(new PlayerHurtEntityTrigger.TriggerInstance(Optional.empty(), Optional.of(pDamage.build()), Optional.empty())); } // 这里是对应的json的一部分,添加一个条件,就是对应的instance就是TriggerInstance,我们需要使用的是三叉戟才能触发这个成就,所以传入的DamagePredicate对其中的tag和direct进行了限制。 .addCriterion( "shot_trident", PlayerHurtEntityTrigger.TriggerInstance.playerHurtEntityWithDamage( DamagePredicate.Builder.damageInstance() .type( DamageSourcePredicate.Builder.damageType() .tag(TagPredicate.is(DamageTypeTags.IS_PROJECTILE)) .direct(EntityPredicate.Builder.entity().of(EntityType.TRIDENT)) ) ) ) ``` 好了,然后关于触发器的注册的内容。 ```java public class InitTrigger { // 获得对应的注册器 public static final DeferredRegister> TRIGGERS = DeferredRegister.create(Registries.TRIGGER_TYPE, ExampleMod.MODID); // 注册 public static final Supplier GIVE_RUBY_APPLE = register("give_ruby_apple", GiveAppleTrigger::new); // 辅助的方法 private static > DeferredHolder, T> register(String id, Supplier trigger) { return TRIGGERS.register(id, trigger); } // 别忘忘记注册到总线 public static void register(IEventBus eventBus) { TRIGGERS.register(eventBus); } } ``` 当然并不是你写了对应的触发器就可以触发成就了,将你的触发器写在对应的触发的位置上,例如我这里希望是在使用我们之前添加的DataSaveBlock方块的时候触发这个成就,我就这样写。 ```java public class DataSaveBlock extends Block { public DataSaveBlock() { super(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE)); } @Override public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) { if(!pLevel.isClientSide){ if(mainHandItem.isEmpty()){ }else{ // ... // 如果你的触发逻辑更加复杂你自然需要更多的参数,你看与这样修改你的trigger方法,并传入数据 //trigger(pPlayer,pos,item,block.....) InitTrigger.GIVE_RUBY_APPLE.get().trigger((ServerPlayer) pPlayer); } } return InteractionResult.SUCCESS; } } ``` 好了,我们去生成我们的对应触发器的一个json文件吧。 这里我们还是使用datagen的方式生成,当然你可以自己写json,就不演示了,相关的内容在wiki也有,可以自行查看。 ```java public class ModAdvancementProvider { // 辅助方法 创建一个provider的实例 // 其中如果你有多个成绩的列表,就在list中加 // 例如原版的冒险,种地,等等 public static net.minecraft.data.advancements.AdvancementProvider create(PackOutput packOutput, CompletableFuture lookupProvider) { return new net.minecraft.data.advancements.AdvancementProvider(packOutput, lookupProvider, List.of(new ModAdvancements())); } // 每个subprovider中应该有一个root节点,这里我们就添加了一个root节点, // 下面也会有怎么指定父节点往下翻看 // subprovider需要实现AdvancementSubProvider接口 public static class ModAdvancements implements AdvancementSubProvider { // 实现对应的方法 // 在这里写你的成就 @Override public void generate(HolderLookup.Provider pRegistries, Consumer pWriter) { // AdvancementHolder是我们的成就的一个对象 // Advancement.Builder.advancement()来获得一个设计工厂 // 通过对应的方法设置 // 和合成表类似 AdvancementHolder giveAppleAdvancement = Advancement.Builder.advancement() // 显示的内容 // 第一个是图标,成就提示时候的图片 // 第二个是title。需要你去写对应的语言化json // 第三个是描述。需要你去写对应的语言化的json // 第四个是root节点特有的,是成就的背景图片,不给的话是紫黑块 // 第五个是成就类型,这里是任务,其中还有目标和另一个,自己点源码看 .display( ModItems.RUBY_APPLE.get(), Component.translatable("advancements.adventure.ruby_apple.title"), Component.translatable("advancements.adventure.ruby_apple.description"), new ResourceLocation("textures/gui/advancements/backgrounds/adventure.png"), AdvancementType.TASK, true, // 显示提示 true, // 通知聊天栏 false // 是否隐藏 ) .addCriterion("use_level_data_block", GiveAppleTrigger.giveAppleBlock()) // 添加检查的条件,这里没有条件,关于限制看看上文提到的三叉戟 或者原版的例子 .save(pWriter,new ResourceLocation(ExampleMod.MODID,"main/give_ruby_apple").toString()); // save, // 注意,需要传入的是一个string,我们有需要指定自己的命名空间,否则就到了原版的空间了。 // 其中最好针对不同的成就页面添加不同的字段,例如这里的mian // 如果你有多个页面,例如你还有一个挑战的页面,这样写 // save(pwriter,new R...(modid,"challenge/xxxxxxxxx).tostring()) } } } ``` 下面是原版的一个例子针对了如果是root的子节点怎么写。 ```java AdvancementHolder advancementholder = Advancement.Builder.advancement() .display( Items.MAP, Component.translatable("advancements.adventure.root.title"), Component.translatable("advancements.adventure.root.description"), new ResourceLocation("textures/gui/advancements/backgrounds/adventure.png"), AdvancementType.TASK, false, false, false ) .requirements(AdvancementRequirements.Strategy.OR) .addCriterion("killed_something", KilledTrigger.TriggerInstance.playerKilledEntity()) .addCriterion("killed_by_something", KilledTrigger.TriggerInstance.entityKilledPlayer()) .save(pWriter, "adventure/root"); AdvancementHolder advancementholder1 = Advancement.Builder.advancement() /// 这里通过parent指定 .parent(advancementholder) // 指定的是第一个。 .display( Blocks.RED_BED, Component.translatable("advancements.adventure.sleep_in_bed.title"), Component.translatable("advancements.adventure.sleep_in_bed.description"), null, AdvancementType.TASK, true, true, false ) .addCriterion("slept_in_bed", PlayerTrigger.TriggerInstance.sleptInBed()) .save(pWriter, "adventure/sleep_in_bed"); ``` 生成的写方法 ```java event.getGenerator().addProvider( event.includeServer(), (DataProvider.Factory) pOutput -> ModAdvancementProvider.create(pOutput,lp) ); ``` 通过data task就可以生成对应的json文件了。 这里给出原版的一个生成json的类,大家自行查看其他的类。 VanillaAdventureAdvancements类,关于了原版的冒险的成就。 进入游戏测试下把。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_48_Advancement.md description: 48 成就 相关教程 --- # 参考 https://github.dev/VazkiiMods/Botania ## 成就/进度 进度或者说成就,就是游戏里面那些成就,我们这次学习怎么添加成就,这就也是使用的json数据作为驱动的,其中一些原版的检验的条件是你自己的可以直接使用的, 不过我们这里添加一个自己的检验条件。 我们添加这样的一个进度,当你使用我们之前创建的的在维度等级下存储数据的方块时候,就会弹出这个成就。 在开始之前我们说以下,我们写的是一类的触发器即我们上文说的检验条件。这个触发器类似你写的规则,比如,我要添加一系列的成就,不过这些成就都是使用了某个物品就可以触发对应的使用物品成就,所以我们没必要为所有的物品都编写这样的触发器,而是写一个通用的触发器,那么既然这样的我们应该怎么判断是那个物品呢?这里就是使用了上文的JSON驱动了,你需要把对应的匹配的物品写入到JSON中,然后判断传入的物品和JSON序列化读入的物品是否一致就会是否完成这个成就了。 所以我们写的是一系列的规则,原版也正是这样的设计的。不过这里的例子直接采用了硬编码的方式写死了这个成就,对于json驱动的方式可以去看看原版的,这里介绍了大致的流程。 ```java // 进度的触发器,完成自己的触发器,需要你继承SimpleCriterionTrigger类,原版的触发器也同样的继承于这个类的。 // 泛型是一个内部类,这个内部类,应该继承SimpleCriterionTrigger.SimpleInstance的内部类,用来存储实例的。 // 你可以通过继承关系看到原版的触发器 public class GiveAppleTrigger extends SimpleCriterionTrigger { // 这里的trigger就是我们的判断的成就是否完成的条件 // 如果你的触发器很复杂,需要判断多个条件,比如物品,位置,方块,等等 // 看有这样写trigger(player,pos,block,item) // 之后在this.trigger(player,第二个参数书写你的判断条件,这里要求传入一个lammbda表达式,返回true或者false,true代表触发,false代表不触发。) public void trigger(ServerPlayer pPlayer) { this.trigger(pPlayer,instance -> true); } // 我们这里需要返回一个对应的触发情况,也就是读取的json时候的内容是什么。 // 我这里的json仅仅存储了player这个信息, 但是我们并不需要去处理player相关的内容,所以这里返回的是一个空的Optional。 // 关于一些其他的判断可以去看看原版的Criterion是怎么写的。 // 例如如果你要判断是玩家捡起了苹果这个物品,那么你应该这里创建的Instance包含物品,传入对应的苹果。 // 而于我们为什么可以使用createCriterion这个方法,其实是SimpleCriterionTrigger实现了对应的接口,大家可以去查看源码 // 下面可以看一个三叉戟成就的例子在下面。 public static Criterion giveAppleBlock() { return InitTrigger.GIVE_RUBY_APPLE.get().createCriterion(new GiveAppleTrigger.Instance(Optional.empty())); } // 返回对应的codec,用于反序列化json用的。 @Override public Codec codec() { return Instance.CODEC; } // 对应的Instance类 public static class Instance implements SimpleCriterionTrigger.SimpleInstance { // 存储player private Optional player; public Instance(Optional player) { super(); /// this.player = player; } public static final Codec CODEC = RecordCodecBuilder.create( // 这里的内容对应的你的json中的存储字段,详细看原版的内容 p_312304_ -> p_312304_.group( ExtraCodecs.strictOptionalField(EntityPredicate.ADVANCEMENT_CODEC, "player").forGetter(GiveAppleTrigger.Instance::player) ) .apply(p_312304_, GiveAppleTrigger.Instance::new) ); // 实现impleCriterionTrigger.SimpleInstance该接口要求实现的方法。 @Override public Optional player() { return this.player; } } } ``` 这是三叉戟成就的一个例子,不过这里的触发器却是玩家攻击实体的一个触发器,不过三叉戟攻击实体确实算是玩家攻击实体的一个特例,所以这里的触发器不仅仅可以使用三叉戟你也可以指定其他的武器制作对应的成就。不过这里还是限制了三叉戟,我们可以看看是怎么限制的 ```java // 这个是返回对应的Criterion的方法,指定的类型是DamagePredicate,通过这个DamagePredicate进行限制是三叉戟的攻击,其他两个字段则不做限制,你同样可以限制第一个player字段表示只有某个人可以触发这个成就,或者限制第三个字段,表示攻击某个怪物触发这个成就,或者限制第二个和第三个表示怎么样的伤害某个生物触发。 public static Criterion playerHurtEntityWithDamage(DamagePredicate.Builder pDamage) { return CriteriaTriggers.PLAYER_HURT_ENTITY .createCriterion(new PlayerHurtEntityTrigger.TriggerInstance(Optional.empty(), Optional.of(pDamage.build()), Optional.empty())); } // 这里是对应的json的一部分,添加一个条件,就是对应的instance就是TriggerInstance,我们需要使用的是三叉戟才能触发这个成就,所以传入的DamagePredicate对其中的tag和direct进行了限制。 .addCriterion( "shot_trident", PlayerHurtEntityTrigger.TriggerInstance.playerHurtEntityWithDamage( DamagePredicate.Builder.damageInstance() .type( DamageSourcePredicate.Builder.damageType() .tag(TagPredicate.is(DamageTypeTags.IS_PROJECTILE)) .direct(EntityPredicate.Builder.entity().of(EntityType.TRIDENT)) ) ) ) ``` 好了,然后关于触发器的注册的内容。 ```java public class InitTrigger { // 获得对应的注册器 public static final DeferredRegister> TRIGGERS = DeferredRegister.create(Registries.TRIGGER_TYPE, ExampleMod.MODID); // 注册 public static final Supplier GIVE_RUBY_APPLE = register("give_ruby_apple", GiveAppleTrigger::new); // 辅助的方法 private static > DeferredHolder, T> register(String id, Supplier trigger) { return TRIGGERS.register(id, trigger); } // 别忘忘记注册到总线 public static void register(IEventBus eventBus) { TRIGGERS.register(eventBus); } } ``` 当然并不是你写了对应的触发器就可以触发成就了,将你的触发器写在对应的触发的位置上,例如我这里希望是在使用我们之前添加的DataSaveBlock方块的时候触发这个成就,我就这样写。 ```java public class DataSaveBlock extends Block { public DataSaveBlock() { super(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE)); } @Override public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) { if(!pLevel.isClientSide){ if(mainHandItem.isEmpty()){ }else{ // ... // 如果你的触发逻辑更加复杂你自然需要更多的参数,你看与这样修改你的trigger方法,并传入数据 //trigger(pPlayer,pos,item,block.....) InitTrigger.GIVE_RUBY_APPLE.get().trigger((ServerPlayer) pPlayer); } } return InteractionResult.SUCCESS; } } ``` 好了,我们去生成我们的对应触发器的一个json文件吧。 这里我们还是使用datagen的方式生成,当然你可以自己写json,就不演示了,相关的内容在wiki也有,可以自行查看。 ```java public class ModAdvancementProvider { // 辅助方法 创建一个provider的实例 // 其中如果你有多个成绩的列表,就在list中加 // 例如原版的冒险,种地,等等 public static net.minecraft.data.advancements.AdvancementProvider create(PackOutput packOutput, CompletableFuture lookupProvider) { return new net.minecraft.data.advancements.AdvancementProvider(packOutput, lookupProvider, List.of(new ModAdvancements())); } // 每个subprovider中应该有一个root节点,这里我们就添加了一个root节点, // 下面也会有怎么指定父节点往下翻看 // subprovider需要实现AdvancementSubProvider接口 public static class ModAdvancements implements AdvancementSubProvider { // 实现对应的方法 // 在这里写你的成就 @Override public void generate(HolderLookup.Provider pRegistries, Consumer pWriter) { // AdvancementHolder是我们的成就的一个对象 // Advancement.Builder.advancement()来获得一个设计工厂 // 通过对应的方法设置 // 和合成表类似 AdvancementHolder giveAppleAdvancement = Advancement.Builder.advancement() // 显示的内容 // 第一个是图标,成就提示时候的图片 // 第二个是title。需要你去写对应的语言化json // 第三个是描述。需要你去写对应的语言化的json // 第四个是root节点特有的,是成就的背景图片,不给的话是紫黑块 // 第五个是成就类型,这里是任务,其中还有目标和另一个,自己点源码看 .display( ModItems.RUBY_APPLE.get(), Component.translatable("advancements.adventure.ruby_apple.title"), Component.translatable("advancements.adventure.ruby_apple.description"), new ResourceLocation("textures/gui/advancements/backgrounds/adventure.png"), AdvancementType.TASK, true, // 显示提示 true, // 通知聊天栏 false // 是否隐藏 ) .addCriterion("use_level_data_block", GiveAppleTrigger.giveAppleBlock()) // 添加检查的条件,这里没有条件,关于限制看看上文提到的三叉戟 或者原版的例子 .save(pWriter,new ResourceLocation(ExampleMod.MODID,"main/give_ruby_apple").toString()); // save, // 注意,需要传入的是一个string,我们有需要指定自己的命名空间,否则就到了原版的空间了。 // 其中最好针对不同的成就页面添加不同的字段,例如这里的mian // 如果你有多个页面,例如你还有一个挑战的页面,这样写 // save(pwriter,new R...(modid,"challenge/xxxxxxxxx).tostring()) } } } ``` 下面是原版的一个例子针对了如果是root的子节点怎么写。 ```java AdvancementHolder advancementholder = Advancement.Builder.advancement() .display( Items.MAP, Component.translatable("advancements.adventure.root.title"), Component.translatable("advancements.adventure.root.description"), new ResourceLocation("textures/gui/advancements/backgrounds/adventure.png"), AdvancementType.TASK, false, false, false ) .requirements(AdvancementRequirements.Strategy.OR) .addCriterion("killed_something", KilledTrigger.TriggerInstance.playerKilledEntity()) .addCriterion("killed_by_something", KilledTrigger.TriggerInstance.entityKilledPlayer()) .save(pWriter, "adventure/root"); AdvancementHolder advancementholder1 = Advancement.Builder.advancement() /// 这里通过parent指定 .parent(advancementholder) // 指定的是第一个。 .display( Blocks.RED_BED, Component.translatable("advancements.adventure.sleep_in_bed.title"), Component.translatable("advancements.adventure.sleep_in_bed.description"), null, AdvancementType.TASK, true, true, false ) .addCriterion("slept_in_bed", PlayerTrigger.TriggerInstance.sleptInBed()) .save(pWriter, "adventure/sleep_in_bed"); ``` 生成的写方法 ```java event.getGenerator().addProvider( event.includeServer(), (DataProvider.Factory) pOutput -> ModAdvancementProvider.create(pOutput,lp) ); ``` 通过data task就可以生成对应的json文件了。 这里给出原版的一个生成json的类,大家自行查看其他的类。 VanillaAdventureAdvancements类,关于了原版的冒险的成就。 进入游戏测试下把。 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_49_Config.md description: 49 配置信息设置 相关教程 --- # 参考 https://boson.v2mcdev.com/configure/configure.html https://github.com/neoforged/MDK/blob/main/src/main/java/com/example/examplemod/ExampleMod.java ## 配置文件 这次我们说配置文件的编写,最后的配置文件长这个样子 这里有四个配置项,第一个是int数值,第二个是布尔数值,第三个是string,第四个是一个string的list \# 开头的是注释 ```toml #General settings [general] #config value #Range: > 0 value = 10 #config boolean value boolean_value = true #What you want the introduction message to be for the magic number magicNumberIntroduction = "The magic number is... " #A list of items to log on common setup. items = ["minecraft:iron_ingot"] ``` 好了,来看看的我们是怎么创建这个配置的把。以及怎么读取配置中的数据 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) // 这个注解是非必要的。这里我们的使用了一个事件,所以加了这个注解 public class Config { // 添加一个config类 private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder() // 创建一个modconfigspec的builder我们需要通过这个builder构建配置 .comment("General settings") // 就是[general] 上面的注解 .push("general"); // 这是表示添加一个新的配置列表,名字是[general] public static final ModConfigSpec.IntValue VALUE = BUILDER .comment("config value")// 使用bulider构建一个int数值 .defineInRange("value",10,0,Integer.MAX_VALUE);// 设置名称,初始值,最小值,最大值 public static final ModConfigSpec.BooleanValue BOOLEAN_VALUE = BUILDER .comment("config boolean value") .define("boolean_value", true);// 构建一个布尔值 public static final ModConfigSpec.ConfigValue MAGIC_NUMBER_INTRODUCTION = BUILDER .comment("What you want the introduction message to be for the magic number") .define("magicNumberIntroduction", "The magic number is... "); // a list of strings that are treated as resource locations for items public static final ModConfigSpec.ConfigValue> ITEM_STRINGS = BUILDER .comment("A list of items to log on common setup.") .defineListAllowEmpty("items", List.of("minecraft:iron_ingot"), Config::validateItemName); // 检验item名称是否合法 private static boolean validateItemName(final Object obj) { return obj instanceof String itemName && BuiltInRegistries.ITEM.containsKey(new ResourceLocation(itemName)); } // 构建配置 public static final ModConfigSpec SPEC = BUILDER.pop().build(); // 下面演示的是怎么获得配置中的数值 这部分并不是必要的 public static boolean logDirtBlock; public static int magicNumber; public static String magicNumberIntroduction; public static Set items; // 这里的配置事件的记载时候的调用,我们将配置中的数值赋值给了对于的字段,等会打印这些字段 @SubscribeEvent static void onLoad(final ModConfigEvent event) { logDirtBlock = BOOLEAN_VALUE.get(); magicNumber = VALUE.get(); magicNumberIntroduction = MAGIC_NUMBER_INTRODUCTION.get(); // convert the list of strings into a set of items items = ITEM_STRINGS.get().stream() .map(itemName -> BuiltInRegistries.ITEM.get(new ResourceLocation(itemName))) .collect(Collectors.toSet()); } } ``` ```java // The value here should match an entry in the META-INF/mods.toml file @Mod(ExampleMod.MODID) public class ExampleMod { public static final String MODID = "examplemod"; private static final Logger LOGGER = LogUtils.getLogger(); public ExampleMod(IEventBus modEventBus) { //config ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, Config.SPEC); NeoForge.EVENT_BUS.register(this); } private void commonSetup(final FMLCommonSetupEvent event) { // log config info // 根据读取的字段的数值进行打印 if (Config.logDirtBlock) // boolean值 LOGGER.info("DIRT BLOCK >> {}", BuiltInRegistries.BLOCK.getKey(Blocks.DIRT)); // string和value数值 LOGGER.info(Config.magicNumberIntroduction + Config.magicNumber); // list Config.items.forEach((item) -> LOGGER.info("ITEM >> {}", item.toString())); } } ``` 当你修改配置文件中的数值的时候相应的内容也会发生变化。并且这个配置的数值是实时生效的,不需要你重启客户端。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_49_Config.md description: 49 配置信息设置 相关教程 --- # 参考 https://boson.v2mcdev.com/configure/configure.html https://github.com/neoforged/MDK/blob/main/src/main/java/com/example/examplemod/ExampleMod.java ## 配置文件 这次我们说配置文件的编写,最后的配置文件长这个样子 这里有四个配置项,第一个是int数值,第二个是布尔数值,第三个是string,第四个是一个string的list \# 开头的是注释 ```toml #General settings [general] #config value #Range: > 0 value = 10 #config boolean value boolean_value = true #What you want the introduction message to be for the magic number magicNumberIntroduction = "The magic number is... " #A list of items to log on common setup. items = ["minecraft:iron_ingot"] ``` 好了,来看看的我们是怎么创建这个配置的把。以及怎么读取配置中的数据 ```java @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD) // 这个注解是非必要的。这里我们的使用了一个事件,所以加了这个注解 public class Config { // 添加一个config类 private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder() // 创建一个modconfigspec的builder我们需要通过这个builder构建配置 .comment("General settings") // 就是[general] 上面的注解 .push("general"); // 这是表示添加一个新的配置列表,名字是[general] public static final ModConfigSpec.IntValue VALUE = BUILDER .comment("config value")// 使用bulider构建一个int数值 .defineInRange("value",10,0,Integer.MAX_VALUE);// 设置名称,初始值,最小值,最大值 public static final ModConfigSpec.BooleanValue BOOLEAN_VALUE = BUILDER .comment("config boolean value") .define("boolean_value", true);// 构建一个布尔值 public static final ModConfigSpec.ConfigValue MAGIC_NUMBER_INTRODUCTION = BUILDER .comment("What you want the introduction message to be for the magic number") .define("magicNumberIntroduction", "The magic number is... "); // a list of strings that are treated as resource locations for items public static final ModConfigSpec.ConfigValue> ITEM_STRINGS = BUILDER .comment("A list of items to log on common setup.") .defineListAllowEmpty("items", List.of("minecraft:iron_ingot"), Config::validateItemName); // 检验item名称是否合法 private static boolean validateItemName(final Object obj) { return obj instanceof String itemName && BuiltInRegistries.ITEM.containsKey(new ResourceLocation(itemName)); } // 构建配置 public static final ModConfigSpec SPEC = BUILDER.pop().build(); // 下面演示的是怎么获得配置中的数值 这部分并不是必要的 public static boolean logDirtBlock; public static int magicNumber; public static String magicNumberIntroduction; public static Set items; // 这里的配置事件的记载时候的调用,我们将配置中的数值赋值给了对于的字段,等会打印这些字段 @SubscribeEvent static void onLoad(final ModConfigEvent event) { logDirtBlock = BOOLEAN_VALUE.get(); magicNumber = VALUE.get(); magicNumberIntroduction = MAGIC_NUMBER_INTRODUCTION.get(); // convert the list of strings into a set of items items = ITEM_STRINGS.get().stream() .map(itemName -> BuiltInRegistries.ITEM.get(new ResourceLocation(itemName))) .collect(Collectors.toSet()); } } ``` ```java // The value here should match an entry in the META-INF/mods.toml file @Mod(ExampleMod.MODID) public class ExampleMod { public static final String MODID = "examplemod"; private static final Logger LOGGER = LogUtils.getLogger(); public ExampleMod(IEventBus modEventBus) { //config ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, Config.SPEC); NeoForge.EVENT_BUS.register(this); } private void commonSetup(final FMLCommonSetupEvent event) { // log config info // 根据读取的字段的数值进行打印 if (Config.logDirtBlock) // boolean值 LOGGER.info("DIRT BLOCK >> {}", BuiltInRegistries.BLOCK.getKey(Blocks.DIRT)); // string和value数值 LOGGER.info(Config.magicNumberIntroduction + Config.magicNumber); // list Config.items.forEach((item) -> LOGGER.info("ITEM >> {}", item.toString())); } } ``` 当你修改配置文件中的数值的时候相应的内容也会发生变化。并且这个配置的数值是实时生效的,不需要你重启客户端。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/item.md --- # 5 物品注册和属性修改 ## 一、物品注册 ### 1、事件监听 通过向`StartupEvents.registry`事件传入不同的参数,其可用于在启动脚本中注册游戏中的新元素,如方块、物品、声音、药效等,在这里,我们传入`"item"`来进行与物品有关的修改: ```js StartupEvents.registry("item",event=>{ // 脚本 }) ``` 显然,方块注册脚本应该放于StartupEvents脚本文件夹中。 ### 2、事件方法 | 方法 | 描述 | 返回值 | | ---------------------------------- | -------- | ----------- | | create(字符串 方块ID, 字符串 类型) | 注册物品 | ItemBuilder | | create(字符串 方块ID) | 注册物品 | ItemBuilder | 其中,“类型”参数支持以下字符串: | 值 | 描述 | 备注 | | ------------- | ------------ | ------------------ | | "basic" | 基础物品类型 | - | | "sword" | 剑类物品 | - | | "pickaxe" | 镐类物品 | - | | "axe" | 斧类物品 | - | | "shovel" | 锹类物品 | - | | "hoe" | 锄类物品 | - | | "helmet" | 头盔类物品 | 可以放于对应穿戴栏 | | "chestplate" | 胸甲类物品 | 可以放于对应穿戴栏 | | "leggings" | 护腿类物品 | 可以放于对应穿戴栏 | | "boots" | 靴子类物品 | 可以放于对应穿戴栏 | | "music\_disc" | 唱片类物品 | 可以放于唱片机中 | ### 3、ItemBuilder | 方法 | 描述 | 备注 | 对应属性默认值 | | --------------------------------------------------------------------------------- | ------------------ | ---------------------------- | -------------- | | displayName(String 名称) | 设定物品名称 | - | - | | fireResistant(布尔值 是否抗火) | 设置物品是否抗火 | - | false | | maxDamage(整形 最大耐久值) | 设置物品耐久度 | 需求对应类型 | - | | unstackable() | 设置为无法堆叠 | - | false | | maxStackSize(整形 最大堆叠数) | 设置堆叠数量 | - | 64 | | finishUsing(FinishUsingCallback 回调函数) | 设置物品使用完成后 | - | - | | modifyAttribute(Attribute 属性类型,字符串 标识符, any 属性值, Operation 操作类型) | 修改Attribute | \[1] | - | | tooltip(Component tooltip文本) | 设置物品tooltip | 形如Component.blue("击退棒") | - | | glow(布尔值 v) | 设置是否发光 | 这里指附魔光芒 | false | | food(Consumer\) | 设置食物物品 | 见章节`6.2 食物` | - | | rarity(Rarity 稀有度) | 设置物品稀有度 | \[2] | "common" | | useAnimation(UseAnim 使用动画) | 设置物品使用动画 | \[3] | null | | group(String 物品组) | 设置物品物品组 | - | - | | containerItem(ResourceLocation 物品) | 设置物品容器 | 类似奶和桶的关系 | - | | burnTime(int 时间) | 设定燃烧时间 | 作为燃料 | 0 | \[1] 参数“标识符”应为任意UUID,操作类型可以为`"addition"`,`"multiply_total"`,`"multiply_base"` \[2] 稀有度可以为`"uncommon"`、`"epic"`、`"rare"`、`"common"` \[3] 使用动画可以为`"toot_horn"`、`"spear"`、`"eat"`、`"block"`、`"crossbow"`、`"spyglass"`、`"bow"`、 `"drink"`、`"custom"`、`"none"` ### 4、HandheldItemBuilder 借助ProbeJS不难看出,`SwordItemBuilder`、`PickaxeItemBuilder`、`AxeItemBuilder`、`ShovelItemBuilder`和`HoeItemBuilder`均为`HandheldItemBuilder`子类。 `HandheldItemBuilder`支持方法如下: | 方法 | 描述 | | -------------------------------------- | ---------------- | | speedBaseline(float 基础挖掘速度) | 设置基础挖掘速度 | | speed(float 挖掘速度加成值) | 每级效果加成 | | attackDamageBaseline(float 基础伤害值) | 设置基础伤害值 | | attackDamageBonus(float 伤害加成值) | 每级效果加成 | | modifyTier(Consumer) | 设置属性 | MutableToolTier支持以下set方法: | 方法 | 描述 | 备注 | | ------------------------------------------ | -------------- | -------------- | | setSpeed(float 挖掘速度加成值) | 每级效果加成 | - | | setEnchantmentValue(int 值) | 设置附魔值 | 限制附魔属性量 | | setAttackDamageBonus(float 伤害加成值) | 每级效果加成 | - | | setUses(int 值) | 设置使用次数 | - | | setLevel(int 值) | 设置等级 | - | | setRepairIngredient(Ingredient 修复用物品) | 设置修复用物品 | - | 值得注意的是:若不进行设置,这些属性的默认值均为铁质工具。 ### 5、ArmorItemBuilder `ArmorItemBuilder`用于设置盔甲类型的物品 | 方法 | 描述 | | ----------------------------- | ------------ | | tier(ArmorMaterial 等级) | 设置盔甲等级 | | modifyTier(Consumer 回调函数) | 设置属性 | MutableArmorTier支持以下set方法: | 方法 | 描述 | 备注 | | ------------------------------------------ | ---------------- | --------------------------------------- | | setDurabilityMultiplier(int 耐久倍率) | 设置耐久损坏倍率 | - | | setToughness(float 抗性) | 设置伤害抗性 | - | | setKnockbackResistance(float 抗性) | 设置击退抗性 | - | | setEnchantmentValue(int 值) | 设置附魔值 | 限制附魔属性量 | | setEquipSound(SoundEvent 声音) | 设置穿戴声音 | 形如`"ambient.basalt_deltas.additions"` | | setRepairIngredient(Ingredient 修复用物品) | 设置修复用物品 | - | | setName(String 名称) | 设置名称 | - | 值得注意的是:若不进行设置,这些属性的默认值均为铁质盔甲。 ### 6、资源目录 对于物品注册来说,你需要将物品材质置于`assets\kubejs\textures\item\<方块ID>.png`,自定义模型(可选)放于`kubejs\assets\kubejs\models\item\<方块ID>.json`下。(即文件结构同资源包) ### 7、示例 ```js StartupEvents.registry("item",event =>{ event.create("attack_base3","sword").attackDamageBaseline(5); event.create("ugly_helmet","helmet").fireResistant(true).modifyTier(tier => { tier.setRepairIngredient(Ingredient.of("#forge:ingots/netherite")) }) event.create("wudji_pickaxe","pickaxe").burnTime(100).glow(true).displayName("奇怪的镐子") }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/item.md --- # 5 物品注册和属性修改 ## 一、物品注册 ### 1、事件监听 通过向`StartupEvents.registry`事件传入不同的参数,其可用于在启动脚本中注册游戏中的新元素,如方块、物品、声音、药效等,在这里,我们传入`"item"`来进行与物品有关的修改: ```js StartupEvents.registry("item",event=>{ // 脚本 }) ``` 显然,方块注册脚本应该放于StartupEvents脚本文件夹中。 ### 2、事件方法 | 方法 | 描述 | 返回值 | | ---------------------------------- | -------- | ----------- | | create(字符串 方块ID, 字符串 类型) | 注册物品 | ItemBuilder | | create(字符串 方块ID) | 注册物品 | ItemBuilder | 其中,“类型”参数支持以下字符串: | 值 | 描述 | 备注 | | ------------- | ------------ | ------------------ | | "basic" | 基础物品类型 | - | | "sword" | 剑类物品 | - | | "pickaxe" | 镐类物品 | - | | "axe" | 斧类物品 | - | | "shovel" | 锹类物品 | - | | "hoe" | 锄类物品 | - | | "helmet" | 头盔类物品 | 可以放于对应穿戴栏 | | "chestplate" | 胸甲类物品 | 可以放于对应穿戴栏 | | "leggings" | 护腿类物品 | 可以放于对应穿戴栏 | | "boots" | 靴子类物品 | 可以放于对应穿戴栏 | | "music\_disc" | 唱片类物品 | 可以放于唱片机中 | ### 3、ItemBuilder | 方法 | 描述 | 备注 | 对应属性默认值 | | --------------------------------------------------------------------------------- | ------------------ | ---------------------------- | -------------- | | displayName(String 名称) | 设定物品名称 | - | - | | fireResistant(布尔值 是否抗火) | 设置物品是否抗火 | - | false | | maxDamage(整形 最大耐久值) | 设置物品耐久度 | 需求对应类型 | - | | unstackable() | 设置为无法堆叠 | - | false | | maxStackSize(整形 最大堆叠数) | 设置堆叠数量 | - | 64 | | finishUsing(FinishUsingCallback 回调函数) | 设置物品使用完成后 | - | - | | modifyAttribute(Attribute 属性类型,字符串 标识符, any 属性值, Operation 操作类型) | 修改Attribute | \[1] | - | | tooltip(Component tooltip文本) | 设置物品tooltip | 形如Component.blue("击退棒") | - | | glow(布尔值 v) | 设置是否发光 | 这里指附魔光芒 | false | | food(Consumer\) | 设置食物物品 | 见章节`6.2 食物` | - | | rarity(Rarity 稀有度) | 设置物品稀有度 | \[2] | "common" | | useAnimation(UseAnim 使用动画) | 设置物品使用动画 | \[3] | null | | group(String 物品组) | 设置物品物品组 | - | - | | containerItem(ResourceLocation 物品) | 设置物品容器 | 类似奶和桶的关系 | - | | burnTime(int 时间) | 设定燃烧时间 | 作为燃料 | 0 | \[1] 参数“标识符”应为任意UUID,操作类型可以为`"addition"`,`"multiply_total"`,`"multiply_base"` \[2] 稀有度可以为`"uncommon"`、`"epic"`、`"rare"`、`"common"` \[3] 使用动画可以为`"toot_horn"`、`"spear"`、`"eat"`、`"block"`、`"crossbow"`、`"spyglass"`、`"bow"`、 `"drink"`、`"custom"`、`"none"` ### 4、HandheldItemBuilder 借助ProbeJS不难看出,`SwordItemBuilder`、`PickaxeItemBuilder`、`AxeItemBuilder`、`ShovelItemBuilder`和`HoeItemBuilder`均为`HandheldItemBuilder`子类。 `HandheldItemBuilder`支持方法如下: | 方法 | 描述 | | -------------------------------------- | ---------------- | | speedBaseline(float 基础挖掘速度) | 设置基础挖掘速度 | | speed(float 挖掘速度加成值) | 每级效果加成 | | attackDamageBaseline(float 基础伤害值) | 设置基础伤害值 | | attackDamageBonus(float 伤害加成值) | 每级效果加成 | | modifyTier(Consumer) | 设置属性 | MutableToolTier支持以下set方法: | 方法 | 描述 | 备注 | | ------------------------------------------ | -------------- | -------------- | | setSpeed(float 挖掘速度加成值) | 每级效果加成 | - | | setEnchantmentValue(int 值) | 设置附魔值 | 限制附魔属性量 | | setAttackDamageBonus(float 伤害加成值) | 每级效果加成 | - | | setUses(int 值) | 设置使用次数 | - | | setLevel(int 值) | 设置等级 | - | | setRepairIngredient(Ingredient 修复用物品) | 设置修复用物品 | - | 值得注意的是:若不进行设置,这些属性的默认值均为铁质工具。 ### 5、ArmorItemBuilder `ArmorItemBuilder`用于设置盔甲类型的物品 | 方法 | 描述 | | ----------------------------- | ------------ | | tier(ArmorMaterial 等级) | 设置盔甲等级 | | modifyTier(Consumer 回调函数) | 设置属性 | MutableArmorTier支持以下set方法: | 方法 | 描述 | 备注 | | ------------------------------------------ | ---------------- | --------------------------------------- | | setDurabilityMultiplier(int 耐久倍率) | 设置耐久损坏倍率 | - | | setToughness(float 抗性) | 设置伤害抗性 | - | | setKnockbackResistance(float 抗性) | 设置击退抗性 | - | | setEnchantmentValue(int 值) | 设置附魔值 | 限制附魔属性量 | | setEquipSound(SoundEvent 声音) | 设置穿戴声音 | 形如`"ambient.basalt_deltas.additions"` | | setRepairIngredient(Ingredient 修复用物品) | 设置修复用物品 | - | | setName(String 名称) | 设置名称 | - | 值得注意的是:若不进行设置,这些属性的默认值均为铁质盔甲。 ### 6、资源目录 对于物品注册来说,你需要将物品材质置于`assets\kubejs\textures\item\<方块ID>.png`,自定义模型(可选)放于`kubejs\assets\kubejs\models\item\<方块ID>.json`下。(即文件结构同资源包) ### 7、示例 ```js StartupEvents.registry("item",event =>{ event.create("attack_base3","sword").attackDamageBaseline(5); event.create("ugly_helmet","helmet").fireResistant(true).modifyTier(tier => { tier.setRepairIngredient(Ingredient.of("#forge:ingots/netherite")) }) event.create("wudji_pickaxe","pickaxe").burnTime(100).glow(true).displayName("奇怪的镐子") }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/feature1/fluid.md --- # 5 自定义流体 *** KubeJS允许你在startup阶段自定义流体。对应事件:`fluid.registry` 不过到目前为止,Fabric下的KubeJS还不支持自定义流体 \~~不得不说KubeJS还是非常良心的,注册流体还免费送你个桶(doge)~~ | 函数 | 功能 | 返回值 | | ----------------------- | -------------------------- | ------------ | | create(字符串 流体ID) | 新建流体 | FluidBuilder | | color(整形 颜色) | 设置流体颜色\[1] | FluidBuilder | | bucketColor(整形 颜色) | 设置流体桶内流体的颜色 | FluidBuilder | | textureStill(资源位置 ID) | 设置液体静止时的贴图 | FluidBuilder | | textureFlowing(资源位置 ID) | 设置液体流动时的贴图 | FluidBuilder | | textureThick(整形 颜色) | 设置液体静止时的颜色 | FluidBuilder | | textureThin(整形 颜色) | 设置液体流动时的颜色 | FluidBuilder | | luminosity(整形 发光度) | 设置流体亮度(默认值为0)\[2] | FluidBuilder | | density(整形 密度) | 设置流体密度(默认值为1000) | FluidBuilder | | temperature(整形 稳定) | 设置流体温度(默认值为300) | FluidBuilder | | viscosity(整形 粘稠度) | 设置流体粘稠度(默认值为1000) | FluidBuilder | | gaseous() | 设置流体为气体(???¿¿¿)(默认值为false) | FluidBuilder | \[1]:使用16进制,形如`0x844031` \[2]:从本行往下,楼主测试时均无法实现预期效果,功能描述为楼主的"臆断"(游戏版本1.16.5 KubeJS版本1605.3.19-build.299),~~但是理论上这些是可以用的,可能和楼主电脑有关系吧~~ 例子: ``` onEvent('fluid.registry', event => { event.create("test_mud").displayName("泥").bucketColor(0x844031).textureThick(0x844031).textureThin(0x844031); }) ``` ![2022-05-03\_20.31.50.png](https://m1.miaomc.cn/uploads/20220503_0795d7c613651.png) ![2022-05-03\_20.31.55.png](https://m1.miaomc.cn/uploads/20220503_b75d0f305a728.png) --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/feature1/fluid.md --- # 5 自定义流体 *** KubeJS允许你在startup阶段自定义流体。对应事件:`fluid.registry` 不过到目前为止,Fabric下的KubeJS还不支持自定义流体 \~~不得不说KubeJS还是非常良心的,注册流体还免费送你个桶(doge)~~ | 函数 | 功能 | 返回值 | | ----------------------- | -------------------------- | ------------ | | create(字符串 流体ID) | 新建流体 | FluidBuilder | | color(整形 颜色) | 设置流体颜色\[1] | FluidBuilder | | bucketColor(整形 颜色) | 设置流体桶内流体的颜色 | FluidBuilder | | textureStill(资源位置 ID) | 设置液体静止时的贴图 | FluidBuilder | | textureFlowing(资源位置 ID) | 设置液体流动时的贴图 | FluidBuilder | | textureThick(整形 颜色) | 设置液体静止时的颜色 | FluidBuilder | | textureThin(整形 颜色) | 设置液体流动时的颜色 | FluidBuilder | | luminosity(整形 发光度) | 设置流体亮度(默认值为0)\[2] | FluidBuilder | | density(整形 密度) | 设置流体密度(默认值为1000) | FluidBuilder | | temperature(整形 稳定) | 设置流体温度(默认值为300) | FluidBuilder | | viscosity(整形 粘稠度) | 设置流体粘稠度(默认值为1000) | FluidBuilder | | gaseous() | 设置流体为气体(???¿¿¿)(默认值为false) | FluidBuilder | \[1]:使用16进制,形如`0x844031` \[2]:从本行往下,楼主测试时均无法实现预期效果,功能描述为楼主的"臆断"(游戏版本1.16.5 KubeJS版本1605.3.19-build.299),~~但是理论上这些是可以用的,可能和楼主电脑有关系吧~~ 例子: ``` onEvent('fluid.registry', event => { event.create("test_mud").displayName("泥").bucketColor(0x844031).textureThick(0x844031).textureThin(0x844031); }) ``` ![2022-05-03\_20.31.50.png](https://m1.miaomc.cn/uploads/20220503_0795d7c613651.png) ![2022-05-03\_20.31.55.png](https://m1.miaomc.cn/uploads/20220503_b75d0f305a728.png) --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_50_MobEffect_Potion.md description: 50 药水效果和药水 相关教程 --- # 药水效果和药水 类位置 java/net/flandre923/examplemod/potion/ModPotions.java java/net/flandre923/examplemod/effect/ModEffects.java java/net/flandre923/examplemod/effect/ExampleEffect.java 药水效果和附魔有些类似,仅仅定义了效果,具体的生效方法在具体的情景中,例如增加伤害的效果应该在对于的攻击的代码中检查是否有该效果,根据效果等级和效果进行攻击力的加成,当然也有一些药水效果是不需要这样的,可以直接生效,这一部分是例如生命恢复这样的效果和一部分是瞬间伤害这样的药水效果。 这次我们模仿原版的生命恢复的药水来看看具体怎么实现一个药水的效果和对于的药水。 大家应该注意到了我有意识的区分了药水效果和药水,在代码中这是分两类的, 游戏表现也确实是这样的,例如原版的漂浮效果是没有对应的药水效果的。 我们先来是添加药水效果 ```java // 原版的药水效果是这个MobEffect类来表示了,我们继承这个类,你可以去看看继承树寻找原版的是实现 public class ExampleEffect extends MobEffect { // 第一个是分类,表示药水效果是好的还是坏的,第二个是药水粒子效果的颜色是否正常 protected ExampleEffect(MobEffectCategory pCategory, int pColor) { super(pCategory, pColor); } // 这里是具体的效果,这里我们模仿原版的生命恢复的效果,每5秒恢复2点生命值 @Override public void applyEffectTick(LivingEntity pEntity, int pAmplifier) { super.applyEffectTick(pEntity, pAmplifier); if (pEntity.getHealth() < pEntity.getMaxHealth()) { pEntity.heal(2.0F); } } // 这里是判断是否应该应用效果的,这里我们每5秒应用一次效果 @Override public boolean shouldApplyEffectTickThisTick(int tick, int amp) { if (tick % 5 == 0) { return true; }else{ return false; } } } ``` 然后,要物品和方块一样,药水效果需要注册,我们在ModEffects类中注册一下,和其他的类似就不多说了。 ```java public class ModEffects { public static final DeferredRegister MOD_EFFECTS = DeferredRegister.create(Registries.MOB_EFFECT, ExampleMod.MODID); // 第二个参数是颜色。自己选一个颜色。 // 应该是用十进制表示的16进制吧,大家自己试试 public static final Supplier EXAMPLE_EFFECT = register("example_effect", ()->new ExampleEffect(MobEffectCategory.BENEFICIAL, 16262179)); public static DeferredHolder register(String name, Supplier effect){ return MOD_EFFECTS.register(name, effect); } public static void register(IEventBus eventBus){ MOD_EFFECTS.register(eventBus); } } ``` 到这里你已经可以进入游戏中使用命令行的方式给你自己附加这个药水效果了。这个效果是每5Tick恢复2点生命值,不过你会发现你的效果的图标是一个黑紫贴图,具体的贴图的放的位置是: resources/assets/examplemod/textures/mob\_effect/example\_effect.png\ 注意的png的名称和你的注册的名称一致,examplemod改为你的modid 下面我注册我们的药水效果.和其他的类似就不过多介绍了。 ```java public class ModPotions { public static final DeferredRegister POTIONS = DeferredRegister.create(Registries.POTION, ExampleMod.MODID); // 这里的参数说一下,第一个是效果,第二个是持续时间tick,第三个是等级 0 是1级,1是2级。 public static final DeferredHolder EXAMPLE_POTION = register("example_potion", () -> new Potion("example_potion",new MobEffectInstance(ModEffects.EXAMPLE_EFFECT.get(),1200,1))); public static DeferredHolder register(String name, Supplier supplier){ return POTIONS.register(name, supplier); } public static void register(IEventBus eventBus){ POTIONS.register(eventBus); } } ``` 好了这样子药水也添加好了,可以进入游戏中查看了,至于药水合成表的添加就留给大家自己探索了。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_50_MobEffect_Potion.md description: 50 药水效果和药水 相关教程 --- # 药水效果和药水 类位置 java/net/flandre923/examplemod/potion/ModPotions.java java/net/flandre923/examplemod/effect/ModEffects.java java/net/flandre923/examplemod/effect/ExampleEffect.java 药水效果和附魔有些类似,仅仅定义了效果,具体的生效方法在具体的情景中,例如增加伤害的效果应该在对于的攻击的代码中检查是否有该效果,根据效果等级和效果进行攻击力的加成,当然也有一些药水效果是不需要这样的,可以直接生效,这一部分是例如生命恢复这样的效果和一部分是瞬间伤害这样的药水效果。 这次我们模仿原版的生命恢复的药水来看看具体怎么实现一个药水的效果和对于的药水。 大家应该注意到了我有意识的区分了药水效果和药水,在代码中这是分两类的, 游戏表现也确实是这样的,例如原版的漂浮效果是没有对应的药水效果的。 我们先来是添加药水效果 ```java // 原版的药水效果是这个MobEffect类来表示了,我们继承这个类,你可以去看看继承树寻找原版的是实现 public class ExampleEffect extends MobEffect { // 第一个是分类,表示药水效果是好的还是坏的,第二个是药水粒子效果的颜色是否正常 protected ExampleEffect(MobEffectCategory pCategory, int pColor) { super(pCategory, pColor); } // 这里是具体的效果,这里我们模仿原版的生命恢复的效果,每5秒恢复2点生命值 @Override public void applyEffectTick(LivingEntity pEntity, int pAmplifier) { super.applyEffectTick(pEntity, pAmplifier); if (pEntity.getHealth() < pEntity.getMaxHealth()) { pEntity.heal(2.0F); } } // 这里是判断是否应该应用效果的,这里我们每5秒应用一次效果 @Override public boolean shouldApplyEffectTickThisTick(int tick, int amp) { if (tick % 5 == 0) { return true; }else{ return false; } } } ``` 然后,要物品和方块一样,药水效果需要注册,我们在ModEffects类中注册一下,和其他的类似就不多说了。 ```java public class ModEffects { public static final DeferredRegister MOD_EFFECTS = DeferredRegister.create(Registries.MOB_EFFECT, ExampleMod.MODID); // 第二个参数是颜色。自己选一个颜色。 // 应该是用十进制表示的16进制吧,大家自己试试 public static final Supplier EXAMPLE_EFFECT = register("example_effect", ()->new ExampleEffect(MobEffectCategory.BENEFICIAL, 16262179)); public static DeferredHolder register(String name, Supplier effect){ return MOD_EFFECTS.register(name, effect); } public static void register(IEventBus eventBus){ MOD_EFFECTS.register(eventBus); } } ``` 到这里你已经可以进入游戏中使用命令行的方式给你自己附加这个药水效果了。这个效果是每5Tick恢复2点生命值,不过你会发现你的效果的图标是一个黑紫贴图,具体的贴图的放的位置是: resources/assets/examplemod/textures/mob\_effect/example\_effect.png\ 注意的png的名称和你的注册的名称一致,examplemod改为你的modid 下面我注册我们的药水效果.和其他的类似就不过多介绍了。 ```java public class ModPotions { public static final DeferredRegister POTIONS = DeferredRegister.create(Registries.POTION, ExampleMod.MODID); // 这里的参数说一下,第一个是效果,第二个是持续时间tick,第三个是等级 0 是1级,1是2级。 public static final DeferredHolder EXAMPLE_POTION = register("example_potion", () -> new Potion("example_potion",new MobEffectInstance(ModEffects.EXAMPLE_EFFECT.get(),1200,1))); public static DeferredHolder register(String name, Supplier supplier){ return POTIONS.register(name, supplier); } public static void register(IEventBus eventBus){ POTIONS.register(eventBus); } } ``` 好了这样子药水也添加好了,可以进入游戏中查看了,至于药水合成表的添加就留给大家自己探索了。 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_51_Particle.md description: 51 粒子 相关教程 --- # 参考 https://boson.v2mcdev.com/paticles/intro.html ## 粒子 需要说明的一点是粒子效果依旧是仅在客户端有的效果,服务器 并没有对于的效果,不过服务器端是可以通知客户端在哪里渲染粒子的。所以这其中自然存在服务器给客户端的发包操作,那么当然就需要你去处理你自己粒子的有些参数,而对于粒子效果是非常灵活的,相信你在B站应该见过各种的粒子的视频,他们是通过命令方块等实现的,说明了粒子的指令是灵活的,这样给你代码编写带来了一些问题,你得来处理这些参数的设置来实现灵活的粒子效果,包括不限于,移动的速度,颜色,大小等等。 这里我们参考了boson的粒子的文章,添加了一个新的粒子,并且允许你通过命令的方式设置粒子的速度,颜色和大小,讲解怎么添加粒子,怎么处理命令生成粒子。 首先我们创建一个粒子类,粒子的基类是particle,不过一般来说我们常用的是TextureSheetParticle这个类,其他的类你可以自行查看。去看继承树就可以了。 TextureSheetParticle这个类表示使用一张材质的图片作为粒子效果 ```java public class ExampleParticle extends TextureSheetParticle { // 除了父类要求的参数,这里我们还增加了几个参数,例如速度,颜色,大小等 // 方便我们自定义我们自己的粒子效果, // 我们使用vector4i表示颜色 public ExampleParticle(ClientLevel pLevel, double pX, double pY, double pZ, Vector3d speed, Vector4i color, float diameter) { super(pLevel, pX, pY, pZ, speed.x,speed.y,speed.z); this.lifetime = 100; // 这是粒子的存在的时间 this.xd = speed.x; // 这里的是x,y,z方向的速度 this.yd = speed.y; // 虽然父类中也有设置,但是父类会加上一个随机的速度 this.zd = speed.z; // 我们并不希望这样做,就重新赋值 this.setColor(color.x/255F, color.y/255F, color.z/255F); this.setAlpha(color.w/255F); // opengl中的颜色标准值是0-1,所以这里标准化 final float PARTICLE_SCALE_FOR_ONE_METRE = 0.5F; // 这是我们自定义的粒子大小,单位是米 this.quadSize = PARTICLE_SCALE_FOR_ONE_METRE * diameter;// 放大的倍数 this.hasPhysics = true; // 粒子是否可以被碰撞 } // 这里指明渲染是半透明 @Override public ParticleRenderType getRenderType() { return ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT; } } ``` 像方块实体一样,对于一个方块实体有对于的一个type,这里也是,我们需要添加一个粒子的type. 你可以去看原版的粒子type你会发现他们继承了一个叫做simpleParticleType的类,其中有是实现一些功能,如果你的粒子不需要通过命令行的方式自定义的话可以使用这个作为你的type,不过这里我们希望我们的命令是可以自定义粒子的效果的,所以我们继承ParticleType类并且实现ParticleOptions接口. ```java public class ExampleParticleType extends ParticleType implements ParticleOptions { // 我们需要存储的这几个参数,并且使用这几个参数创建的对于的粒子效果 // 这几个参数从哪里来呢?,答案是从命令行的输入来 private final Vector3d speed; private final Vector4i color; private final float diameter; // 我们需要一个序列化和反序列化的东西, // 1. 目的是为了从命令行读取str的颜色,速度,等内容. // 2 是可以将将网络的发送的数据包中读取对应的颜色,速度.这里说的是客户端接受 public static final ParticleOptions.Deserializer DESERIALIZER = new ParticleOptions.Deserializer() { // 第一个就是处理命令中的字符串 // 输入的命令是这样的形式. // /particle 粒子 0 0 0 0 0 255 100 3 // 其中的是 空格0空格0空格0空格0空格0空格255空格100空格3 @Override public ExampleParticleType fromCommand(ParticleType pParticleType, StringReader pReader) throws CommandSyntaxException { final int MIN_COLOUR = 0; final int MAX_COLOUR = 255; pReader.expect(' '); // 处理空格 double speedX = pReader.readDouble(); // 读取第一个0 pReader.expect(' '); // 处理空格 double speedY = pReader.readDouble(); //.... pReader.expect(' '); double speedZ = pReader.readDouble(); pReader.expect(' '); int red = Math.clamp(pReader.readInt(), MIN_COLOUR, MAX_COLOUR);// 这里我们读取颜色,并且保证颜色是合法的,限制在0-255之间 pReader.expect(' '); int green = Math.clamp(pReader.readInt(), MIN_COLOUR, MAX_COLOUR); pReader.expect(' '); int blue = Math.clamp(pReader.readInt(), MIN_COLOUR, MAX_COLOUR); pReader.expect(' '); int alpha = Math.clamp(pReader.readInt(), 1, MAX_COLOUR); pReader.expect(' '); float diameter = pReader.readFloat(); return new ExampleParticleType(new Vector3d(speedX, speedY, speedZ), new Vector4i(red, green, blue, alpha), diameter); } @Override public ExampleParticleType fromNetwork(ParticleType pParticleType, FriendlyByteBuf pBuffer) { // 这里是处理网络数据包的 final int MIN_COLOUR = 0; final int MAX_COLOUR = 255; double speedX = pBuffer.readDouble(); double speedY = pBuffer.readDouble(); double speedZ = pBuffer.readDouble(); int red = Math.clamp(pBuffer.readInt(), MIN_COLOUR, MAX_COLOUR); int green = Math.clamp(pBuffer.readInt(), MIN_COLOUR, MAX_COLOUR); int blue = Math.clamp(pBuffer.readInt(), MIN_COLOUR, MAX_COLOUR); int alpha = Math.clamp(pBuffer.readInt(), 1, MAX_COLOUR); float diameter = pBuffer.readFloat(); return new ExampleParticleType(new Vector3d(speedX, speedY, speedZ), new Vector4i(red, green, blue, alpha), diameter); } }; // 构造方法 public ExampleParticleType(Vector3d speed, Vector4i color, float diameter) { // super 第一个参数表示粒子不在人的视野范围内的时候是否渲染,可以选择不渲染 // 第二个参数是我们的DESERIALIZER,帮我我们处理序列化的 super(false, ExampleParticleType.DESERIALIZER); this.speed = speed; this.color = color; this.diameter = diameter; } // 返回粒子的type等会我们注册 @Override public ExampleParticleType getType() { return ModParticleType.EXAMPLE_PARTICLE_TYPE.get(); } // 服务端发包要写的数据.. @Override public void writeToNetwork(FriendlyByteBuf pBuffer) { pBuffer.writeDouble(this.speed.x); pBuffer.writeDouble(this.speed.y); pBuffer.writeDouble(this.speed.z); pBuffer.writeInt(this.color.x()); pBuffer.writeInt(this.color.y()); pBuffer.writeInt(this.color.z()); pBuffer.writeInt(this.color.w()); pBuffer.writeFloat(this.diameter); } // 写入成string @Override public String writeToString() { return String.format(Locale.ROOT, "%s %.2f %i %i %i %i %.2d %.2d %.2d", this.getType().toString(), diameter, color.x(), color.y(), color.z(), color.w(), speed.x(), speed.y(), speed.z()); } // codec,并不会用到他,不过还是给它返回一个数值吧 @Override public Codec codec() { return Codec.unit(new ExampleParticleType(new Vector3d(0, 0, 0), new Vector4i(0,0,0,0), 0)); } // 这里是我们自定义的粒子效果,我们需要返回一个粒子的provider, public Vector3d getSpeed() { return speed; } // 颜色,大小 public Vector4i getColor() { return color; } public float getDiameter() { return diameter; } } ``` 我们来注册我们的粒子type吧. ```java public class ModParticleType { public static final DeferredRegister > PARTICLE_TYPES = DeferredRegister.create(Registries.PARTICLE_TYPE, ExampleMod.MODID); public static final Supplier EXAMPLE_PARTICLE_TYPE = register("example_particle_type", () -> new ExampleParticleType(new Vector3d(0,0,0),new Vector4i(0,0,0,0),0)); public static > DeferredHolder, T> register(String name, Supplier particleType){ return PARTICLE_TYPES.register(name, particleType); } // 别忘了注册到总线 public static void register(IEventBus eventBus){ PARTICLE_TYPES.register(eventBus); } } ``` 好了,我们注册了粒子type,但是我们还需要一个provider,使用个provider来创建客户端的粒子效果,并且把这个provider放在particleEngine上. ```java // 我们来创建这样的类 public class ExampleParticleProvider implements ParticleProvider { // 存储一系列的纹理图 private final SpriteSet sprites; // 构造方法 // 传入对应的图片 public ExampleParticleProvider(SpriteSet sprites) { this.sprites = sprites; } // 创建粒子 @Nullable @Override public Particle createParticle(ExampleParticleType pType, ClientLevel pLevel, double pX, double pY, double pZ, double pXSpeed, double pYSpeed, double pZSpeed) { ExampleParticle exampleParticle = new ExampleParticle(pLevel, pX, pY, pZ, pType.getSpeed(),pType.getColor(),pType.getDiameter()); // 随机选择一张图片作为纹理图 exampleParticle.pickSprite(this.sprites); // 返回创建好的粒子 return exampleParticle; } } ``` 将我们的provider注册到particleEngine上. ```java // neoforge为我们提供了对应的方法 @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) public class ParticleFactoryRegistry { @SubscribeEvent public static void onParticleFactoryRegistration(RegisterParticleProvidersEvent event) { event.registerSpriteSet(ModParticleType.EXAMPLE_PARTICLE_TYPE.get(), ExampleParticleProvider::new); } } ``` 下面是你的粒子的json文件和纹理的图片存放的位置 ```java . ├── META-INF │ └── mods.toml ├── assets │ └── examplemod │ ├── blockstates │ ├── lang │ ├── models │ ├── particles │ │ └── example_particle_type.json │ ├── sounds │ ├── sounds.json │ └── textures │ ├── block │ ├── entity │ ├── gui │ ├── item │ └── particle │ └── example_particle.png ├── data └── pack.mcmeta ``` 其中你的joson文件如下,描述一系列的纹理图片,这里只有一张 ```json { "textures": [ "examplemod:example_particle" ] } ``` 进入游戏中输入下面的命令来看看你的粒子效果吧 `/particle examplemod:example_particle 0 0 0 0 0 255 100 3` 其中的 0 0 0 0 0 255 100 3 是你的粒子的速度,颜色,大小,分别对应x,y,z,red,green,blue,alpha,diameter. 代码中添加粒子效果可以使用world.addParticle()方法.可以查看原版是怎么使用的. --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_51_Particle.md description: 51 粒子 相关教程 --- # 参考 https://boson.v2mcdev.com/paticles/intro.html ## 粒子 需要说明的一点是粒子效果依旧是仅在客户端有的效果,服务器 并没有对于的效果,不过服务器端是可以通知客户端在哪里渲染粒子的。所以这其中自然存在服务器给客户端的发包操作,那么当然就需要你去处理你自己粒子的有些参数,而对于粒子效果是非常灵活的,相信你在B站应该见过各种的粒子的视频,他们是通过命令方块等实现的,说明了粒子的指令是灵活的,这样给你代码编写带来了一些问题,你得来处理这些参数的设置来实现灵活的粒子效果,包括不限于,移动的速度,颜色,大小等等。 这里我们参考了boson的粒子的文章,添加了一个新的粒子,并且允许你通过命令的方式设置粒子的速度,颜色和大小,讲解怎么添加粒子,怎么处理命令生成粒子。 首先我们创建一个粒子类,粒子的基类是particle,不过一般来说我们常用的是TextureSheetParticle这个类,其他的类你可以自行查看。去看继承树就可以了。 TextureSheetParticle这个类表示使用一张材质的图片作为粒子效果 ```java public class ExampleParticle extends TextureSheetParticle { // 除了父类要求的参数,这里我们还增加了几个参数,例如速度,颜色,大小等 // 方便我们自定义我们自己的粒子效果, // 我们使用vector4i表示颜色 public ExampleParticle(ClientLevel pLevel, double pX, double pY, double pZ, Vector3d speed, Vector4i color, float diameter) { super(pLevel, pX, pY, pZ, speed.x,speed.y,speed.z); this.lifetime = 100; // 这是粒子的存在的时间 this.xd = speed.x; // 这里的是x,y,z方向的速度 this.yd = speed.y; // 虽然父类中也有设置,但是父类会加上一个随机的速度 this.zd = speed.z; // 我们并不希望这样做,就重新赋值 this.setColor(color.x/255F, color.y/255F, color.z/255F); this.setAlpha(color.w/255F); // opengl中的颜色标准值是0-1,所以这里标准化 final float PARTICLE_SCALE_FOR_ONE_METRE = 0.5F; // 这是我们自定义的粒子大小,单位是米 this.quadSize = PARTICLE_SCALE_FOR_ONE_METRE * diameter;// 放大的倍数 this.hasPhysics = true; // 粒子是否可以被碰撞 } // 这里指明渲染是半透明 @Override public ParticleRenderType getRenderType() { return ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT; } } ``` 像方块实体一样,对于一个方块实体有对于的一个type,这里也是,我们需要添加一个粒子的type. 你可以去看原版的粒子type你会发现他们继承了一个叫做simpleParticleType的类,其中有是实现一些功能,如果你的粒子不需要通过命令行的方式自定义的话可以使用这个作为你的type,不过这里我们希望我们的命令是可以自定义粒子的效果的,所以我们继承ParticleType类并且实现ParticleOptions接口. ```java public class ExampleParticleType extends ParticleType implements ParticleOptions { // 我们需要存储的这几个参数,并且使用这几个参数创建的对于的粒子效果 // 这几个参数从哪里来呢?,答案是从命令行的输入来 private final Vector3d speed; private final Vector4i color; private final float diameter; // 我们需要一个序列化和反序列化的东西, // 1. 目的是为了从命令行读取str的颜色,速度,等内容. // 2 是可以将将网络的发送的数据包中读取对应的颜色,速度.这里说的是客户端接受 public static final ParticleOptions.Deserializer DESERIALIZER = new ParticleOptions.Deserializer() { // 第一个就是处理命令中的字符串 // 输入的命令是这样的形式. // /particle 粒子 0 0 0 0 0 255 100 3 // 其中的是 空格0空格0空格0空格0空格0空格255空格100空格3 @Override public ExampleParticleType fromCommand(ParticleType pParticleType, StringReader pReader) throws CommandSyntaxException { final int MIN_COLOUR = 0; final int MAX_COLOUR = 255; pReader.expect(' '); // 处理空格 double speedX = pReader.readDouble(); // 读取第一个0 pReader.expect(' '); // 处理空格 double speedY = pReader.readDouble(); //.... pReader.expect(' '); double speedZ = pReader.readDouble(); pReader.expect(' '); int red = Math.clamp(pReader.readInt(), MIN_COLOUR, MAX_COLOUR);// 这里我们读取颜色,并且保证颜色是合法的,限制在0-255之间 pReader.expect(' '); int green = Math.clamp(pReader.readInt(), MIN_COLOUR, MAX_COLOUR); pReader.expect(' '); int blue = Math.clamp(pReader.readInt(), MIN_COLOUR, MAX_COLOUR); pReader.expect(' '); int alpha = Math.clamp(pReader.readInt(), 1, MAX_COLOUR); pReader.expect(' '); float diameter = pReader.readFloat(); return new ExampleParticleType(new Vector3d(speedX, speedY, speedZ), new Vector4i(red, green, blue, alpha), diameter); } @Override public ExampleParticleType fromNetwork(ParticleType pParticleType, FriendlyByteBuf pBuffer) { // 这里是处理网络数据包的 final int MIN_COLOUR = 0; final int MAX_COLOUR = 255; double speedX = pBuffer.readDouble(); double speedY = pBuffer.readDouble(); double speedZ = pBuffer.readDouble(); int red = Math.clamp(pBuffer.readInt(), MIN_COLOUR, MAX_COLOUR); int green = Math.clamp(pBuffer.readInt(), MIN_COLOUR, MAX_COLOUR); int blue = Math.clamp(pBuffer.readInt(), MIN_COLOUR, MAX_COLOUR); int alpha = Math.clamp(pBuffer.readInt(), 1, MAX_COLOUR); float diameter = pBuffer.readFloat(); return new ExampleParticleType(new Vector3d(speedX, speedY, speedZ), new Vector4i(red, green, blue, alpha), diameter); } }; // 构造方法 public ExampleParticleType(Vector3d speed, Vector4i color, float diameter) { // super 第一个参数表示粒子不在人的视野范围内的时候是否渲染,可以选择不渲染 // 第二个参数是我们的DESERIALIZER,帮我我们处理序列化的 super(false, ExampleParticleType.DESERIALIZER); this.speed = speed; this.color = color; this.diameter = diameter; } // 返回粒子的type等会我们注册 @Override public ExampleParticleType getType() { return ModParticleType.EXAMPLE_PARTICLE_TYPE.get(); } // 服务端发包要写的数据.. @Override public void writeToNetwork(FriendlyByteBuf pBuffer) { pBuffer.writeDouble(this.speed.x); pBuffer.writeDouble(this.speed.y); pBuffer.writeDouble(this.speed.z); pBuffer.writeInt(this.color.x()); pBuffer.writeInt(this.color.y()); pBuffer.writeInt(this.color.z()); pBuffer.writeInt(this.color.w()); pBuffer.writeFloat(this.diameter); } // 写入成string @Override public String writeToString() { return String.format(Locale.ROOT, "%s %.2f %i %i %i %i %.2d %.2d %.2d", this.getType().toString(), diameter, color.x(), color.y(), color.z(), color.w(), speed.x(), speed.y(), speed.z()); } // codec,并不会用到他,不过还是给它返回一个数值吧 @Override public Codec codec() { return Codec.unit(new ExampleParticleType(new Vector3d(0, 0, 0), new Vector4i(0,0,0,0), 0)); } // 这里是我们自定义的粒子效果,我们需要返回一个粒子的provider, public Vector3d getSpeed() { return speed; } // 颜色,大小 public Vector4i getColor() { return color; } public float getDiameter() { return diameter; } } ``` 我们来注册我们的粒子type吧. ```java public class ModParticleType { public static final DeferredRegister > PARTICLE_TYPES = DeferredRegister.create(Registries.PARTICLE_TYPE, ExampleMod.MODID); public static final Supplier EXAMPLE_PARTICLE_TYPE = register("example_particle_type", () -> new ExampleParticleType(new Vector3d(0,0,0),new Vector4i(0,0,0,0),0)); public static > DeferredHolder, T> register(String name, Supplier particleType){ return PARTICLE_TYPES.register(name, particleType); } // 别忘了注册到总线 public static void register(IEventBus eventBus){ PARTICLE_TYPES.register(eventBus); } } ``` 好了,我们注册了粒子type,但是我们还需要一个provider,使用个provider来创建客户端的粒子效果,并且把这个provider放在particleEngine上. ```java // 我们来创建这样的类 public class ExampleParticleProvider implements ParticleProvider { // 存储一系列的纹理图 private final SpriteSet sprites; // 构造方法 // 传入对应的图片 public ExampleParticleProvider(SpriteSet sprites) { this.sprites = sprites; } // 创建粒子 @Nullable @Override public Particle createParticle(ExampleParticleType pType, ClientLevel pLevel, double pX, double pY, double pZ, double pXSpeed, double pYSpeed, double pZSpeed) { ExampleParticle exampleParticle = new ExampleParticle(pLevel, pX, pY, pZ, pType.getSpeed(),pType.getColor(),pType.getDiameter()); // 随机选择一张图片作为纹理图 exampleParticle.pickSprite(this.sprites); // 返回创建好的粒子 return exampleParticle; } } ``` 将我们的provider注册到particleEngine上. ```java // neoforge为我们提供了对应的方法 @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) public class ParticleFactoryRegistry { @SubscribeEvent public static void onParticleFactoryRegistration(RegisterParticleProvidersEvent event) { event.registerSpriteSet(ModParticleType.EXAMPLE_PARTICLE_TYPE.get(), ExampleParticleProvider::new); } } ``` 下面是你的粒子的json文件和纹理的图片存放的位置 ```java . ├── META-INF │ └── mods.toml ├── assets │ └── examplemod │ ├── blockstates │ ├── lang │ ├── models │ ├── particles │ │ └── example_particle_type.json │ ├── sounds │ ├── sounds.json │ └── textures │ ├── block │ ├── entity │ ├── gui │ ├── item │ └── particle │ └── example_particle.png ├── data └── pack.mcmeta ``` 其中你的joson文件如下,描述一系列的纹理图片,这里只有一张 ```json { "textures": [ "examplemod:example_particle" ] } ``` 进入游戏中输入下面的命令来看看你的粒子效果吧 `/particle examplemod:example_particle 0 0 0 0 0 255 100 3` 其中的 0 0 0 0 0 255 100 3 是你的粒子的速度,颜色,大小,分别对应x,y,z,red,green,blue,alpha,diameter. 代码中添加粒子效果可以使用world.addParticle()方法.可以查看原版是怎么使用的. --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_52_Sound.md description: 52 声音 相关教程 --- # 参考 https://boson.v2mcdev.com/sounds/intro.html ## 音效 我们这次说怎么给添加音效,还是比较简单的,常规的注册声音的方法,和之前的类似,不不多赘述了。 ```java public class ModSounds { public static final DeferredRegister SOUNDS = DeferredRegister.create(Registries.SOUND_EVENT, ExampleMod.MODID); public static final Supplier EXAMPLE_SOUND = register("example_sound", () -> SoundEvent.createFixedRangeEvent(new ResourceLocation(ExampleMod.MODID, "example_sound"),16)); public static DeferredHolder register(String name, Supplier supplier){ return SOUNDS.register(name, supplier); } public static void register(IEventBus modBus){ SOUNDS.register(modBus); } } ``` 下面需要创建对应的目录结构和json文件 ```java resources ├── META-INF │ └── mods.toml ├── assets │ └── modid │ ├── blockstates │ ├── lang │ ├── models │ ├── sounds // this │ ├── sounds.json // this │ └── textures ├── data └── pack.mcmeta ``` 来看看json的写法把。详细的字段的含义可以去看wiki,这里我们说几个有重要的字段。 https://minecraft.fandom.com/zh/wiki/Sounds.json ```json { "example_sound": { // 这里的example_sound和的注册的名称是一致的。 "category": "voice", "subtitle": "example_sound", "sounds": [ { "name": "examplemod:example_sound", // 这里的格式是 modid:声音的路径 位于sounds下面,其中声音文件必须是ogg "stream": true } ] } } ``` 声音放置的位置 ```java resources ├── META-INF │ └── mods.toml ├── assets │ └── modid │ ├── blockstates │ ├── lang │ ├── models │ ├── sounds │ │ └── example_sound.ogg │ ├── sounds.json │ └── textures ├── data └── pack.mcmeta ``` 创建一个物品右键勃发这个声音 ```java public class ExampleSoundTestItem extends Item { public ExampleSoundTestItem() { super(new Item.Properties().stacksTo(1)); } @Override public InteractionResultHolder use(Level pLevel, Player pPlayer, InteractionHand pUsedHand) { if(pLevel.isClientSide){ pLevel.playSound(pPlayer,pPlayer.blockPosition(), ModSounds.EXAMPLE_SOUND.get(), SoundSource.AMBIENT,10f,1f); } return super.use(pLevel, pPlayer, pUsedHand); } } ``` 关于ogg格式,你可以直接搜索MP3转ogg,会有网页工具的。 好了打开游戏试试吧。 --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_52_Sound.md description: 52 声音 相关教程 --- # 参考 https://boson.v2mcdev.com/sounds/intro.html ## 音效 我们这次说怎么给添加音效,还是比较简单的,常规的注册声音的方法,和之前的类似,不不多赘述了。 ```java public class ModSounds { public static final DeferredRegister SOUNDS = DeferredRegister.create(Registries.SOUND_EVENT, ExampleMod.MODID); public static final Supplier EXAMPLE_SOUND = register("example_sound", () -> SoundEvent.createFixedRangeEvent(new ResourceLocation(ExampleMod.MODID, "example_sound"),16)); public static DeferredHolder register(String name, Supplier supplier){ return SOUNDS.register(name, supplier); } public static void register(IEventBus modBus){ SOUNDS.register(modBus); } } ``` 下面需要创建对应的目录结构和json文件 ```java resources ├── META-INF │ └── mods.toml ├── assets │ └── modid │ ├── blockstates │ ├── lang │ ├── models │ ├── sounds // this │ ├── sounds.json // this │ └── textures ├── data └── pack.mcmeta ``` 来看看json的写法把。详细的字段的含义可以去看wiki,这里我们说几个有重要的字段。 https://minecraft.fandom.com/zh/wiki/Sounds.json ```json { "example_sound": { // 这里的example_sound和的注册的名称是一致的。 "category": "voice", "subtitle": "example_sound", "sounds": [ { "name": "examplemod:example_sound", // 这里的格式是 modid:声音的路径 位于sounds下面,其中声音文件必须是ogg "stream": true } ] } } ``` 声音放置的位置 ```java resources ├── META-INF │ └── mods.toml ├── assets │ └── modid │ ├── blockstates │ ├── lang │ ├── models │ ├── sounds │ │ └── example_sound.ogg │ ├── sounds.json │ └── textures ├── data └── pack.mcmeta ``` 创建一个物品右键勃发这个声音 ```java public class ExampleSoundTestItem extends Item { public ExampleSoundTestItem() { super(new Item.Properties().stacksTo(1)); } @Override public InteractionResultHolder use(Level pLevel, Player pPlayer, InteractionHand pUsedHand) { if(pLevel.isClientSide){ pLevel.playSound(pPlayer,pPlayer.blockPosition(), ModSounds.EXAMPLE_SOUND.get(), SoundSource.AMBIENT,10f,1f); } return super.use(pLevel, pPlayer, pUsedHand); } } ``` 关于ogg格式,你可以直接搜索MP3转ogg,会有网页工具的。 好了打开游戏试试吧。 --- --- url: >- https://docs.variedmc.cc/en/develop/modding/1.20.4/Neoforge/flandre/out_53_Update_1.20.6.md description: 53 Minecraft Neoforge 版本更新 --- # 更新gradle 到github中下载心的gradle,替换掉之前的gradle https://github.com/neoforged/MDK/blob/main/gradle/wrapper/gradle-wrapper.properties ![20240517193044](https://cdn.jsdelivr.net/gh/Flandre923/CDN@latest/img/20240517193044.png) ## 修改mods.toml 为 neoforge.mods.toml ![20240517193314](https://cdn.jsdelivr.net/gh/Flandre923/CDN@latest/img/20240517193314.png) 修改其中的内容如下 ```java # This is an example neoforge.mods.toml file. It contains the data relating to the loading mods. # There are several mandatory fields (#mandatory), and many more that are optional (#optional). # The overall format is standard TOML format, v0.5.0. # Note that there are a couple of TOML lists in this file. # Find more information on toml format here: https://github.com/toml-lang/toml # The name of the mod loader type to load - for regular FML @Mod mods it should be javafml modLoader="javafml" #mandatory # A version range to match for said mod loader - for regular FML @Mod it will be the the FML version. This is currently 47. loaderVersion="${loader_version_range}" #mandatory # The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. # Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. license="${mod_license}" # A URL to refer people to when problems occur with this mod #issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional # A list of mods - how many allowed here is determined by the individual mod loader [[mods]] #mandatory # The modid of the mod modId="${mod_id}" #mandatory # The version number of the mod version="${mod_version}" #mandatory # A display name for the mod displayName="${mod_name}" #mandatory # A URL to query for updates for this mod. See the JSON update specification https://docs.neoforged.net/docs/misc/updatechecker/ #updateJSONURL="https://change.me.example.invalid/updates.json" #optional # A URL for the "homepage" for this mod, displayed in the mod UI #displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional # A file name (in the root of the mod JAR) containing a logo for display #logoFile="examplemod.png" #optional # A text field displayed in the mod UI #credits="" #optional # A text field displayed in the mod UI authors="${mod_authors}" #optional # Display Test controls the display for your mod in the server connection screen # MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. # IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. # IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component. # NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. # IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. #displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) # The description text for the mod (multi line!) (#mandatory) description='''${mod_description}''' # The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded. #[[mixins]] #config="${mod_id}.mixins.json" # The [[accessTransformers]] block allows you to declare where your AT file is. # If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg #[[accessTransformers]] #file="META-INF/accesstransformer.cfg" # The coremods config file path is not configurable and is always loaded from META-INF/coremods.json # A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. [[dependencies.${mod_id}]] #optional # the modid of the dependency modId="neoforge" #mandatory # The type of the dependency. Can be one of "required", "optional", "incompatible" or "discouraged" (case insensitive). # 'required' requires the mod to exist, 'optional' does not # 'incompatible' will prevent the game from loading when the mod exists, and 'discouraged' will show a warning type="required" #mandatory # Optional field describing why the dependency is required or why it is incompatible # reason="..." # The version range of the dependency versionRange="${neo_version_range}" #mandatory # An ordering relationship for the dependency. # BEFORE - This mod is loaded BEFORE the dependency # AFTER - This mod is loaded AFTER the dependency ordering="NONE" # Side this dependency is applied on - BOTH, CLIENT, or SERVER side="BOTH" # Here's another dependency [[dependencies.${mod_id}]] modId="minecraft" type="required" # This version range declares a minimum of the current minecraft version up to but not including the next major version versionRange="${minecraft_version_range}" ordering="NONE" side="BOTH" # Features are specific properties of the game environment, that you may want to declare you require. This example declares # that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't # stop your mod loading on the server for example. #[features.${mod_id}] #openGLVersion="[3.2,)" ``` ## 修改下build.gradle文件 修改插件中的neoforge.gradle 的版本 ``` plugins { id 'java-library' id 'eclipse' id 'idea' id 'maven-publish' id 'net.neoforged.gradle.userdev' version '7.0.124' } ``` 修改Java版本为21 ```java java.toolchain.languageVersion = JavaLanguageVersion.of(21) ``` 由于之前不知道什么更新,导致gradle下载jar不会下载source,要自己下载,导致有点麻烦,并且不会下载全,这个问题应该可以通过新的这个方式修复 文件最下方写上这个。 ``` idea { module { downloadSources = true downloadJavadoc = true } } ``` 并且去掉JEI的相关依赖,因为JEI没有更新1.20.6的版本。 修改mods.toml 为 neoforge.mods.toml , 不然启动不了 ## 修改gralde.properties文件主要是版本 各位参考更新即可。 ```properties # Sets default memory used for gradle commands. Can be overridden by user or command line properties. #org.gradle.jvmargs= org.gradle.daemon=false org.gradle.debug=false #read more on this at https://github.com/neoforged/NeoGradle/blob/NG_7.0/README.md#apply-parchment-mappings # you can also find the latest versions at: https://parchmentmc.org/docs/getting-started neogradle.subsystems.parchment.minecraftVersion=1.20.6 neogradle.subsystems.parchment.mappingsVersion=2024.05.01 # Environment Properties # You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge # The Minecraft version must agree with the Neo version to get a valid artifact minecraft_version=1.20.6 # The Minecraft version range can use any release version of Minecraft as bounds. # Snapshots, pre-releases, and release candidates are not guaranteed to sort properly # as they do not follow standard versioning conventions. minecraft_version_range=[1.20.6,1.21) # The Neo version must agree with the Minecraft version to get a valid artifact neo_version=20.6.62-beta # The Neo version range can use any version of Neo as bounds neo_version_range=[20.6,) # The loader version range can only use the major version of FML as bounds loader_version_range=[2,) ## Mod Properties mc_version=1.20.6 #jei_version=17.0.0.30 terrablender_version=3.5.0.4 # The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63} # Must match the String constant located in the main mod class annotated with @Mod. mod_id=examplemod # The human-readable display name for the mod. mod_name=Example Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=All Rights Reserved # The mod version. See https://semver.org/ mod_version=1.0.0 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html mod_group_id=net.flandre923.examplemod # The authors of the mod. This is a simple text string that is used for display purposes in the mod list. mod_authors=YourNameHere, OtherNameHere # The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. mod_description=Example mod description.\nNewline characters can be used and will be replaced properly. ``` ## 最后重新realod项目 修改完毕后,重新reload项目,他会自动配置相关的内容。等待完成。 ## 来更新报错的类 ## GiveAppleTrigger 修改Codec部分 ```java public static final Codec CODEC = RecordCodecBuilder.create( p_312304_ -> p_312304_.group( EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter(GiveAppleTrigger.Instance::player) ) .apply(p_312304_, GiveAppleTrigger.Instance::new) ); ``` ## BlockEntity部分 load,save 方法改变 CounterBlockEntity ```java @Override protected void loadAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) { counter = pTag.getInt("counter"); super.loadAdditional(pTag, pRegistries); } @Override protected void saveAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) { super.saveAdditional(pTag, pRegistries); pTag.putInt("counter",counter); } ``` FirstMenuBlockEntity ```JAVA @Override protected void loadAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) { super.loadAdditional(pTag, pRegistries); CompoundTag inventory = pTag.getCompound("inventory"); this.itemHandler.deserializeNBT(pRegistries,inventory); } @Override protected void saveAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) { super.saveAdditional(pTag, pRegistries); pTag.put("inventory",this.itemHandler.serializeNBT(pRegistries)); } ``` 数据同步相关的内容,修改 ZombieBlockEntity ```java //而onDataPacket才是客户端接受数据包的方法。 @Override public void onDataPacket(Connection net, ClientboundBlockEntityDataPacket pkt, HolderLookup.Provider lookupProvider) { super.onDataPacket(net, pkt, lookupProvider); handleUpdateTag(pkt.getTag(),lookupProvider); } @Override public CompoundTag getUpdateTag(HolderLookup.Provider pRegistries) { CompoundTag compoundNBT = super.getUpdateTag(pRegistries); compoundNBT.putBoolean("flag", flag); return compoundNBT; } @Override public void handleUpdateTag(CompoundTag tag, HolderLookup.Provider lookupProvider) { super.handleUpdateTag(tag, lookupProvider); flag = tag.getBoolean("flag"); } ``` ## Block的更新 DataSaveBlock 方块的use方法被拆为了2个,不使用物品右键和不使用物品右键 ```java @Override protected ItemInteractionResult useItemOn(ItemStack pStack, BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHitResult) { if(!pLevel.isClientSide){ ModLevelSaveData data = ModLevelSaveData.get(pLevel); ItemStack mainHandItem = pPlayer.getMainHandItem(); if(mainHandItem.isEmpty()){ ItemStack itemStack = data.getItem(); pPlayer.setItemInHand(InteractionHand.MAIN_HAND,itemStack); }else{ ItemStack itemStack =mainHandItem.copy(); mainHandItem.shrink(mainHandItem.getCount()); data.putItem(itemStack); InitTrigger.GIVE_RUBY_APPLE.get().trigger((ServerPlayer) pPlayer); } } return ItemInteractionResult.SUCCESS; } ``` firstMenuBlock ```java @Override protected InteractionResult useWithoutItem(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, BlockHitResult pHitResult) { if (!pLevel.isClientSide){ pPlayer.openMenu(this.getMenuProvider(pState,pLevel,pPos),pPos); } return super.useWithoutItem(pState, pLevel, pPos, pPlayer, pHitResult); } ``` LampBlock ```java @Override protected ItemInteractionResult useItemOn(ItemStack pStack, BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHitResult) { if(!pLevel.isClientSide){ pLevel.setBlock(pPos,pState.cycle(LIT),3); } return super.useItemOn(pStack, pState, pLevel, pPos, pPlayer, pHand, pHitResult); } ``` PortalBlock ```java @Override protected ItemInteractionResult useItemOn(ItemStack pStack, BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHitResult) { if (pPlayer.canChangeDimensions()){ handlePortalTeleport(pLevel,pPos,pPlayer); return ItemInteractionResult.SUCCESS; }else{ return ItemInteractionResult.FAIL; } } ``` RubyCounterBlock ```java @Override protected ItemInteractionResult useItemOn(ItemStack pStack, BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHitResult) { if (!pLevel.isClientSide){ var rubyBlocksEntity = (CounterBlockEntity) pLevel.getBlockEntity(pPos); int counter = rubyBlocksEntity.increase(); pPlayer.sendSystemMessage(Component.literal("counter:"+counter)); } return ItemInteractionResult.SUCCESS; } ``` RubyFrame ```java @Override protected boolean isPathfindable(BlockState pState, PathComputationType pPathComputationType) { switch(pPathComputationType) { case LAND: return false; case WATER: return pState.getFluidState().is(FluidTags.WATER); case AIR: return false; default: return false; } } ``` ModBlocks 流体的注册,需要一个FlowingFluid了不是一个supplier也可以了。 ```java public static final Supplier MY_FLUID_BLOCK = BLOCKS.register("my_fluid_block", ()->new LiquidBlock(ModFluids.MY_SOURCE_FLUID_BLOCK.get(),BlockBehaviour.Properties.ofFullCopy(Blocks.WATER))); ``` SpeedUpCapability的修改 其他几个类类似,就不写了。 ```java @Override public @UnknownNullability CompoundTag serializeNBT(HolderLookup.Provider provider) { CompoundTag compoundNBT = new CompoundTag(); compoundNBT.putInt("level", this.level); return compoundNBT; } @Override public void deserializeNBT(HolderLookup.Provider provider, CompoundTag nbt) { this.level = nbt.getInt("level"); } ``` ## HUD的接口的变化 ExampleHud ```java public class ExampleHud implements LayeredDraw.Layer { private static final ExampleHud hud = new ExampleHud(); private final int width; private final int height; private final Minecraft minecraft; private final ResourceLocation HUD = new ResourceLocation(ExampleMod.MODID, "textures/gui/hud.png"); public ExampleHud() { this.width = Minecraft.getInstance().getWindow().getScreenWidth(); this.height = Minecraft.getInstance().getWindow().getScreenHeight(); this.minecraft = Minecraft.getInstance(); } @Override public void render(GuiGraphics pGuiGraphics, float pPartialTick) { if (minecraft.player.getMainHandItem().getItem()!= ModItems.RUBY_APPLE.get()) return; pGuiGraphics.setColor(1,1,1,1); pGuiGraphics.blit(HUD,width/2-16,height/2-64,0,0,32,32,32,32); } public static ExampleHud getInstance() { return hud; } } ``` ThirstHud ```java public class ThirstHud { private static final ResourceLocation FILLED_THIRST = new ResourceLocation(ExampleMod.MODID, "textures/gui/filled_thirst.png"); private static final ResourceLocation EMPTY_THIRST = new ResourceLocation(ExampleMod.MODID, "textures/gui/empty_thirst.png"); public static final LayeredDraw.Layer HUD_THIRST = (guiGraphics, partialTick) -> { int screenWidth = Minecraft.getInstance().getWindow().getScreenWidth(); int screenHeight = Minecraft.getInstance().getWindow().getScreenHeight(); int x = screenWidth / 2; int y = screenHeight; for(int i = 0; i < 10; i++) { guiGraphics.blit(EMPTY_THIRST,x - 94 + (i * 9), y - 54,90,0,0,12,12, 12,12); } for(int i = 0; i < 10; i++) { if(ClientPlayerThirstData.getPlayerThirst() > i) { guiGraphics.blit(FILLED_THIRST,x - 94 + (i * 9),y - 54,90,0,0,12,12, 12,12); } else { break; } } }; } ``` ## 命名空间的修改 ```java @EventBusSubscriber(modid = ExampleMod.MODID,bus = EventBusSubscriber.Bus.MOD) ``` ## datagenerator的修改 LootTableProvider ```java public class ModLootTableProvider extends LootTableProvider { public ModLootTableProvider(PackOutput pOutput, Set> pRequiredTables, List pSubProviders, CompletableFuture pRegistries) { super(pOutput, pRequiredTables, pSubProviders, pRegistries); } } ``` DataGenerator的修改 ```java event.getGenerator().addProvider( event.includeServer(), (DataProvider.Factory)pOutput -> new ModLootTableProvider(pOutput, Collections.emptySet(), List.of( new LootTableProvider.SubProviderEntry(ModBlockLootProvider::new, LootContextParamSets.BLOCK) ),lp) ); ``` 修改所有的bootstrap的类型BootstrapContext ```java public static void bootstrap(BootstrapContext> pContext) { RuleTest stoneOreReplaceRuleTest = new TagMatchTest(BlockTags.STONE_ORE_REPLACEABLES); RuleTest deepSlateOreReplaceRuleTest = new TagMatchTest(BlockTags.DEEPSLATE_ORE_REPLACEABLES); List list = List.of( OreConfiguration.target(stoneOreReplaceRuleTest, ModBlocks.RUBY_ORE.get().defaultBlockState()), OreConfiguration.target(deepSlateOreReplaceRuleTest, ModBlocks.RUBY_BLOCK.get().defaultBlockState()) ); FeatureUtils.register(pContext, ORE_RUBY, Feature.ORE, new OreConfiguration(list, 9)); } ``` ## 药水 ExampleEffect 该方法具有的返回类型 ```java @Override public boolean applyEffectTick(LivingEntity pEntity, int pAmplifier) { super.applyEffectTick(pEntity, pAmplifier); if (pEntity.getHealth() < pEntity.getMaxHealth()) { pEntity.heal(2.0F); return true; } return false; } ``` Effect的注册改下 ```java public static final Holder EXAMPLE_EFFECT = register("example_effect", ()->new ExampleEffect(MobEffectCategory.BENEFICIAL, 16262179)); ``` POTION改下 ```java public static final DeferredHolder EXAMPLE_POTION = register("example_potion", () -> new Potion("example_potion",new MobEffectInstance(ModEffects.EXAMPLE_EFFECT,1200,1))); ``` ## 附魔 附魔的类别进行了修改,现在使用tag的json文件作为标识了。 ![20240517212413](https://cdn.jsdelivr.net/gh/Flandre923/CDN@latest/img/20240517212413.png) ExampleModReference 文件不再需要 注册 ```java public class ModEnchantments { public static final DeferredRegister ENCHANTMENTS = DeferredRegister.create(Registries.ENCHANTMENT, ExampleMod.MODID); public static final Supplier POWER_ARROWS = ENCHANTMENTS.register("my_power", MyPowerfulEnchantment::new); public static void register(IEventBus bus) { ENCHANTMENTS.register(bus); } } ``` 新增一个附魔的方法,其中参数比较多,不过还是之前的一些数据,其中这数据不需要在重写了。 ```java public class MyPowerfulEnchantment extends Enchantment { protected MyPowerfulEnchantment() { super(Enchantment.definition(ItemTags.BOW_ENCHANTABLE,2,2,Enchantment.dynamicCost(10,20),Enchantment.dynamicCost(60,20),4,EquipmentSlot.MAINHAND)); } } ``` ## 实体 Animal添加一个新的抽象方法siFood ```java // Checks if the parameter is an item which this animal can be fed to breed it (wheat, carrots or seeds depending on the animal type) @Override public boolean isFood(ItemStack pStack) { return false; } ``` 实体定义数据初始值的方法改变了。 ```java @Override protected void defineSynchedData(SynchedEntityData.Builder pBuilder) { pBuilder.define(COUNTER,0); } ``` ## 物品 装备材料类需要进行修改 ```java public enum ModItemTiers implements Tier { RUBY(BlockTags.INCORRECT_FOR_DIAMOND_TOOL,3,2000,10F,4F,30,() -> Ingredient.of(ModItems.RUBY.get())); private final TagKey incorrectBlockForDrops; private final int level; private final int uses; private final float speed; private final float damage; private final int enchantmentValue; private final LazyLoadedValue repairIngredient; private ModItemTiers(TagKey pIncorrectBlockForDrops,int pLevel, int pUses, float pSpeed, float pDamage, int pEnchantmentValue, Supplier pRepairIngredient) { this.incorrectBlockForDrops = pIncorrectBlockForDrops; this.level = pLevel; this.uses = pUses; this.speed = pSpeed; this.damage = pDamage; this.enchantmentValue = pEnchantmentValue; this.repairIngredient = new LazyLoadedValue<>(pRepairIngredient); } @Override public int getUses() { return this.uses; } @Override public float getSpeed() { return this.speed; } @Override public float getAttackDamageBonus() { return this.damage; } @Override public TagKey getIncorrectBlocksForDrops() { return incorrectBlockForDrops; } @Override public int getEnchantmentValue() { return this.enchantmentValue; } @Override public Ingredient getRepairIngredient() { return this.repairIngredient.get(); } } ``` 装备材料修改较大,变为注册的了,下面是添加一个新的方法。 ```java public class ModArmorMaterial { public static final DeferredRegister ARMOR_MATERIAL = DeferredRegister.create(Registries.ARMOR_MATERIAL, ExampleMod.MODID); public static final DeferredHolder RUBY = register( "ruby", Util.make(new EnumMap<>(ArmorItem.Type.class),typeObjectEnumMap -> { typeObjectEnumMap.put(ArmorItem.Type.BOOTS,1); typeObjectEnumMap.put(ArmorItem.Type.LEGGINGS,2); typeObjectEnumMap.put(ArmorItem.Type.CHESTPLATE,3); typeObjectEnumMap.put(ArmorItem.Type.HELMET,1); typeObjectEnumMap.put(ArmorItem.Type.BODY,3); }), 15, SoundEvents.ARMOR_EQUIP_LEATHER, 0F, 0F, ()-> Ingredient.of(ModItems.RUBY.get()), List.of(new ArmorMaterial.Layer(new ResourceLocation(ExampleMod.MODID,"ruby"),"",true),new ArmorMaterial.Layer(new ResourceLocation(ExampleMod.MODID,"ruby"),"_overlay",false)) ); public static DeferredHolder register(String name, EnumMap pDefense, int pEnchantmentValue, Holder pEquipSound, float pToughness, float pKnockResistance, Supplier pRepairIngridient, List pLayers){ EnumMap enummap = new EnumMap<>(ArmorItem.Type.class); for(ArmorItem.Type armorMaterial:ArmorItem.Type.values()){ enummap.put(armorMaterial,pDefense.get(armorMaterial)); } return ARMOR_MATERIAL.register(name,()->new ArmorMaterial(enummap,pEnchantmentValue,pEquipSound,pRepairIngridient,pLayers,pToughness,pKnockResistance)); } // 别忘记注册到总线 public static void register(IEventBus eventBus){ ARMOR_MATERIAL.register(eventBus); } } ``` 好了好需要修改贴图的位置,将原来的位置放到你的命名空间中去吧。(吐槽这遗留问题终于解决了。) ![20240517220826](https://cdn.jsdelivr.net/gh/Flandre923/CDN@latest/img/20240517220826.png) RubyApple ```java public class RubyApple extends Item { private static final FoodProperties FOOD_PROPERTIES = new FoodProperties.Builder() .saturationModifier(10) .nutrition(20) .effect(()-> new MobEffectInstance(MobEffects.POISON,3*20,1),1) .build(); public RubyApple() { super(new Properties().food(FOOD_PROPERTIES)); } } ``` pixcle ```java public class RubyPickaxe extends PickaxeItem { public RubyPickaxe() { super(ModItemTiers.RUBY,new Properties()); } } ``` sword ```java public class RubySword extends SwordItem { public RubySword(){ super(ModItemTiers.RUBY,new Item.Properties()); } } ``` messageItem ```java //网路的更新 public class MessageItem extends Item { public MessageItem() { super(new Properties()); } @Override public InteractionResultHolder use(Level pLevel, Player pPlayer, InteractionHand pUsedHand) { if(pLevel.isClientSide){ // PacketDistributor.PLAYER.with(pPlayer).send(new ); PacketDistributor.sendToServer(new MyData("From client",2)); } if(!pLevel.isClientSide){ PacketDistributor.sendToPlayer((ServerPlayer) pPlayer,new MyData("From server",2)); } return super.use(pLevel,pPlayer,pUsedHand); } } ``` items ```java // MY_SOURCE_FLUID_BLOCK后添加get() // supplier的方法没了 public static final Supplier MY_FLUID_BUCKET = register("my_fluid_bucket", ()->new BucketItem(ModFluids.MY_SOURCE_FLUID_BLOCK.get(),new Item.Properties().craftRemainder(Items.BUCKET).stacksTo(1))); ``` ## ModLevelSaveData 由于ItemStack中的NBT没了,所以添加了新的方式获得ItemStack的NBT。下面是新的代码 ```java private ModLevelSaveData load(CompoundTag nbt,HolderLookup.Provider pLevelRegistry) { ListTag listNBT = (ListTag) nbt.get("list"); if (listNBT != null) { for (Tag value : listNBT) { CompoundTag tag = (CompoundTag) value; ItemStack itemStack = ItemStack.parse(pLevelRegistry,tag).orElse(ItemStack.EMPTY); if (itemStack.equals(ItemStack.EMPTY)){ continue; } itemStacks.push(itemStack); } } // Load saved data return this; } private static ModLevelSaveData decode(CompoundTag tag,HolderLookup.Provider pRegistries){ return ModLevelSaveData.create().load(tag,pRegistries); } public static ModLevelSaveData get(Level worldIn) { if (!(worldIn instanceof ServerLevel)) { throw new RuntimeException("Attempted to get the data from a client world. This is wrong."); } ServerLevel world = worldIn.getServer().getLevel(ServerLevel.OVERWORLD); DimensionDataStorage dataStorage = world.getDataStorage(); return dataStorage.computeIfAbsent(new Factory(ModLevelSaveData::create, ModLevelSaveData::decode), ModLevelSaveData.NAME); } @Override public CompoundTag save(CompoundTag pTag, HolderLookup.Provider pRegistries) { ListTag listTag = new ListTag(); itemStacks.forEach((stack) -> { CompoundTag compoundTag = new CompoundTag(); listTag.add(stack.save(pRegistries,compoundTag)); }); pTag.put("list", listTag); return pTag; } ``` ## 网络 自定载荷修改 添加了Codec作为序列化反序列化的工具 使用CustomPacketPayload.TYPE代替ID作为表示. MyData ```java public record MyData(String message, int age) implements CustomPacketPayload { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(MyData::write,MyData::new); public static final Type TYPE = new Type<>(new ResourceLocation(ExampleMod.MODID,"my_data")); public MyData(final FriendlyByteBuf buf){ this(buf.readUtf(),buf.readInt()); } public void write(FriendlyByteBuf pBuffer) { pBuffer.writeUtf(message()); pBuffer.writeInt(age()); } @Override public Type type() { return TYPE; } } ``` ThirstData ```java public record ThirstData(int thirst) implements CustomPacketPayload { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ThirstData::write,ThirstData::new); public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(new ResourceLocation(ExampleMod.MODID,"thirst_data")); public ThirstData(final FriendlyByteBuf buf){ this(buf.readInt()); } public void write(FriendlyByteBuf pBuffer) { pBuffer.writeInt(thirst()); } @Override public Type type() { return TYPE; } } ``` 服务端和客户端数据包处理的修改 client ```java public class ClientPayloadHandler { private static final Logger LOGGER = LogUtils.getLogger(); private static final ClientPayloadHandler INSTANCE = new ClientPayloadHandler(); public static ClientPayloadHandler getInstance() { return INSTANCE; } public void handleData(final MyData data, final IPayloadContext context) { // Do something with the data, on the network thread // Do something with the data, on the main thread context.enqueueWork(() -> { LOGGER.info(data.message()); }) .exceptionally(e -> { // Handle exception context.disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } public void handleThirstData(final ThirstData data,final IPayloadContext context){ context.enqueueWork(()->{ ClientPlayerThirstData.set(data.thirst()); }).exceptionally(e->{ context.disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } } ``` server ```java public class ServerPayloadHandler { private static final ServerPayloadHandler INSTANCE = new ServerPayloadHandler(); private static final Logger LOGGER = LogUtils.getLogger(); public static ServerPayloadHandler getInstance() { return INSTANCE; } public void handleData(final MyData data, final IPayloadContext context) { // Do something with the data, on the network thread // Do something with the data, on the main thread context.enqueueWork(() -> { LOGGER.info(data.message()); }) .exceptionally(e -> { // Handle exception context.disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } public void handleThirstData(final ThirstData data, final IPayloadContext context){ context.enqueueWork(()->{ Player player = context.player(); if (player instanceof ServerPlayer serverPlayer){ ServerLevel level = (ServerLevel) player.level(); if(hasWaterAroundThem(player,level,2)){ level.playSound(null, player.getOnPos(), SoundEvents.GENERIC_DRINK, SoundSource.PLAYERS, 0.5F, level.random.nextFloat() * 0.1F + 0.9F); Optional.ofNullable(player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)).ifPresent(thirst->{ thirst.addThirst(1); player.sendSystemMessage(Component.literal("Current Thirst " + thirst.getThirst()) .withStyle(ChatFormatting.AQUA)); PacketDistributor.sendToPlayer(serverPlayer,new ThirstData(thirst.getThirst())); }); }else{ Optional.ofNullable(player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)).ifPresent(thirst -> { player.sendSystemMessage(Component.literal("Current Thirst " + thirst.getThirst()) .withStyle(ChatFormatting.AQUA)); // 添加这个代码,当玩家按下o建的时候同步数据 // ModMessages.sendToPlayer(new ThirstDataSyncS2CPacket(thirst.getThirst()),player); PacketDistributor.sendToPlayer(serverPlayer,new ThirstData(thirst.getThirst())); }); } } }).exceptionally(e->{ context.disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } private boolean hasWaterAroundThem(Player player, ServerLevel level, int size) { return level.getBlockStates(player.getBoundingBox().inflate(size)) .filter(state -> state.is(Blocks.WATER)).toArray().length > 0; } } ``` 网络包的注册修改 ```java @EventBusSubscriber(modid = ExampleMod.MODID,bus = EventBusSubscriber.Bus.MOD) public class Networking { @SubscribeEvent public static void register(final RegisterPayloadHandlersEvent event) { final PayloadRegistrar registrar = event.registrar(ExampleMod.MODID); registrar.playBidirectional( MyData.TYPE, MyData.STREAM_CODEC, new DirectionalPayloadHandler<>( ClientPayloadHandler::handleData, ServerPayloadHandler::handleData ) ); registrar.playBidirectional( ThirstData.TYPE, ThirstData.STREAM_CODEC, new DirectionalPayloadHandler<>( ClientPayloadHandler::handleThirstData, ServerPayloadHandler::handleThirstData ) ); } } ``` ## 建筑 改了个名字 ```java public static final MapCodec CODEC = simpleCodec(MyStructure::new); ``` ## CONFIG ```java public ExampleMod(IEventBus modEventBus, ModContainer modContainer) { //config modContainer.registerConfig(ModConfig.Type.COMMON,Config.SPEC); NeoForge.EVENT_BUS.register(this); } ``` ## 粒子 例子砍掉了Deserializer,改成了codec。我还没搞清楚这个codec怎么用,所以这里我按照原版的方法添加例子,就处理命令行输入的问题了。 粒子 ```java public class ExampleParticle extends TextureSheetParticle { public ExampleParticle(ClientLevel pLevel, double pX, double pY, double pZ) { super(pLevel, pX, pY, pZ); this.lifetime = 100; this.hasPhysics = true; } @Override public ParticleRenderType getRenderType() { return ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT; } } ``` 粒子type ```java public class ModParticleType { public static final DeferredRegister > PARTICLE_TYPES = DeferredRegister.create(Registries.PARTICLE_TYPE, ExampleMod.MODID); public static final Supplier EXAMPLE_PARTICLE_TYPE = register("example_particle_type", () -> new SimpleParticleType(true)); public static > DeferredHolder, T> register(String name, Supplier particleType){ return PARTICLE_TYPES.register(name, particleType); } public static void register(IEventBus eventBus){ PARTICLE_TYPES.register(eventBus); } } ``` 例子provider ```java public class ExampleParticleProvider implements ParticleProvider { private final SpriteSet sprites; public ExampleParticleProvider(SpriteSet sprites) { this.sprites = sprites; } @Nullable @Override public Particle createParticle(SimpleParticleType pType, ClientLevel pLevel, double pX, double pY, double pZ, double pXSpeed, double pYSpeed, double pZSpeed) { ExampleParticle exampleParticle = new ExampleParticle(pLevel, pX, pY, pZ); exampleParticle.pickSprite(this.sprites); return exampleParticle; } } ``` 粒子引擎 ```java @EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) public class ParticleFactoryRegistry { @SubscribeEvent public static void onParticleFactoryRegistration(RegisterParticleProvidersEvent event) { event.registerSpriteSet(ModParticleType.EXAMPLE_PARTICLE_TYPE.get(), ExampleParticleProvider::new); // Minecraft.getInstance().particleEngine.register(ModParticleType.EXAMPLE_PARTICLE_TYPE.get(), ExampleParticleProvider::new); } } ``` 对于ExampleParticleType,我直接注解掉了。 ## Event ForgeClientEventHandler ```java @EventBusSubscriber(modid = ExampleMod.MODID,value = Dist.CLIENT) public class ForgeClientEventHandler { @SubscribeEvent public static void onKeyInput(InputEvent.Key event) { if(KeyBinding.DRINKING_KEY.consumeClick()){ PacketDistributor.sendToServer(new ThirstData(ClientPlayerThirstData.getPlayerThirst())); } } } ``` ModClientEventHandler GUI的注册放在一个单独的事件中了。 ```java @SubscribeEvent public static void registerMenuScreen(RegisterMenuScreensEvent event){ event.register(ModMenuTypes.FIRST_MENU.get(),FistMenuGui::new); } ``` 村民交易事件修改 ```java @SubscribeEvent public static void addCustomTrades(VillagerTradesEvent event) { if(event.getType() == VillagerProfession.TOOLSMITH) { Int2ObjectMap> trades = event.getTrades(); ItemStack stack = new ItemStack(ModItems.MAGIC_INGOT.get(), 1); int villagerLevel = 1; trades.get(villagerLevel).add((trader, rand) -> new MerchantOffer( new ItemCost(Items.EMERALD, 2), stack,10,8,0.02F)); } if(event.getType() == ModVillagers.RUBY_MASTER.get()) { Int2ObjectMap> trades = event.getTrades(); ItemStack stack = new ItemStack(ModItems.RUBY.get(), 15); int villagerLevel = 1; trades.get(villagerLevel).add((trader, rand) -> new MerchantOffer( new ItemCost(Items.EMERALD, 5), stack,10,8,0.02F)); } } ``` playerTick ```java @SubscribeEvent public static void onPlayerTick(PlayerTickEvent.Post event) { if(event.getEntity() instanceof ServerPlayer serverPlayer) { Optional optionalPlayerThirst = Optional.ofNullable(serverPlayer.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)); optionalPlayerThirst .ifPresent(thirst -> { if(thirst.getThirst() > 0 && serverPlayer.getRandom().nextFloat() < 0.005f) { // Once Every 10 Seconds on Avg thirst.subThirst(1); // TODO 同步数据 // ModMessages.sendToPlayer(new ThirstDataSyncS2CPacket(thirst.getThirst()),(ServerPlayer) event.player); PacketDistributor.sendToPlayer(serverPlayer,new ThirstData(thirst.getThirst())); serverPlayer.sendSystemMessage(Component.literal("Subtracted Thirst")); } }); } } ``` joinLevelEvent ```java @SubscribeEvent public static void onPlayerJoinWorld(EntityJoinLevelEvent event) { if(!event.getLevel().isClientSide()) { if(event.getEntity() instanceof ServerPlayer player) { Optional optionalPlayerThirst = Optional.ofNullable(player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)); optionalPlayerThirst.ifPresent(thirst -> { // TODO 同步数据 PacketDistributor.sendToPlayer(player,new ThirstData(thirst.getThirst())); // ModMessages.sendToPlayer(new ThirstDataSyncS2CPacket(thirst.getThirst()), player); }); } } } ``` MyPowerEnchantmentEvent ```java boolean flag = player.getAbilities().instabuild || EnchantmentHelper.getItemEnchantmentLevel(Enchantments.INFINITY, bow) > 0; ``` MyPowerEnchantmentEvent ```java @EventBusSubscriber public class MyPowerEnchantmentEvent { @SubscribeEvent public static void MyPowerEnchantmentShot(ArrowLooseEvent event){ ItemStack bow = event.getBow(); Player player = event.getEntity(); int i = event.getCharge(); Level level = event.getLevel(); ItemStack itemstack = player.getProjectile(bow); boolean flag = player.getAbilities().instabuild || EnchantmentHelper.getItemEnchantmentLevel(Enchantments.INFINITY, bow) > 0; shot(itemstack,flag,i,player,level,bow,player.getUsedItemHand()); // 取消掉原版的弓的射箭逻辑 event.setCanceled(true); } public static void shot(ItemStack itemstack, Boolean flag, int i, Player player, Level pLevel, ItemStack pStack, InteractionHand pHand){ if (!itemstack.isEmpty() || flag) { if (itemstack.isEmpty()) { itemstack = new ItemStack(Items.ARROW); } float f = getPowerForTime(i); if (!((double)f < 0.1)) { boolean flag1 = player.getAbilities().instabuild || (itemstack.getItem() instanceof ArrowItem && ((ArrowItem)itemstack.getItem()).isInfinite(itemstack, pStack, player)); if (!pLevel.isClientSide) { ArrowItem arrowitem = (ArrowItem)(itemstack.getItem() instanceof ArrowItem ? itemstack.getItem() : Items.ARROW); AbstractArrow abstractarrow = arrowitem.createArrow(pLevel, itemstack, player); abstractarrow = customArrow(abstractarrow, itemstack); abstractarrow.shootFromRotation(player, player.getXRot(), player.getYRot(), 0.0F, f * 3.0F, 1.0F); if (f == 1.0F) { abstractarrow.setCritArrow(true); } int j = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.POWER, pStack); if (j > 0) { abstractarrow.setBaseDamage(abstractarrow.getBaseDamage() + (double)j * 0.5 + 0.5); } int k = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.POWER, pStack); if (k > 0) { abstractarrow.setKnockback(k); } if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.FLAME, pStack) > 0) { abstractarrow.setRemainingFireTicks(100); } // 这里是我们的附魔,这里为了效果直接不按照等级,基于了20点基础伤害 if(EnchantmentHelper.getItemEnchantmentLevel(ModEnchantments.POWER_ARROWS.get(),pStack) > 0){ abstractarrow.setBaseDamage(20); } pStack.hurtAndBreak(1, player, LivingEntity.getSlotForHand(pHand)); if (flag1 || player.getAbilities().instabuild && (itemstack.is(Items.SPECTRAL_ARROW) || itemstack.is(Items.TIPPED_ARROW))) { abstractarrow.pickup = AbstractArrow.Pickup.CREATIVE_ONLY; } pLevel.addFreshEntity(abstractarrow); } pLevel.playSound( null, player.getX(), player.getY(), player.getZ(), SoundEvents.ARROW_SHOOT, SoundSource.PLAYERS, 1.0F, 1.0F / (pLevel.getRandom().nextFloat() * 0.4F + 1.2F) + f * 0.5F ); if (!flag1 && !player.getAbilities().instabuild) { itemstack.shrink(1); if (itemstack.isEmpty()) { player.getInventory().removeItem(itemstack); } } } } } public static float getPowerForTime(int pCharge) { float f = (float)pCharge / 20.0F; f = (f * f + f * 2.0F) / 3.0F; if (f > 1.0F) { f = 1.0F; } return f; } public static AbstractArrow customArrow(AbstractArrow arrow, ItemStack stack) { return arrow; } } ``` --- --- url: >- https://docs.variedmc.cc/zh/develop/modding/1.20.4/Neoforge/flandre/out_53_Update_1.20.6.md description: 53 Minecraft Neoforge 版本更新 --- # 更新gradle 到github中下载心的gradle,替换掉之前的gradle https://github.com/neoforged/MDK/blob/main/gradle/wrapper/gradle-wrapper.properties ![20240517193044](https://cdn.jsdelivr.net/gh/Flandre923/CDN@latest/img/20240517193044.png) ## 修改mods.toml 为 neoforge.mods.toml ![20240517193314](https://cdn.jsdelivr.net/gh/Flandre923/CDN@latest/img/20240517193314.png) 修改其中的内容如下 ```java # This is an example neoforge.mods.toml file. It contains the data relating to the loading mods. # There are several mandatory fields (#mandatory), and many more that are optional (#optional). # The overall format is standard TOML format, v0.5.0. # Note that there are a couple of TOML lists in this file. # Find more information on toml format here: https://github.com/toml-lang/toml # The name of the mod loader type to load - for regular FML @Mod mods it should be javafml modLoader="javafml" #mandatory # A version range to match for said mod loader - for regular FML @Mod it will be the the FML version. This is currently 47. loaderVersion="${loader_version_range}" #mandatory # The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. # Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. license="${mod_license}" # A URL to refer people to when problems occur with this mod #issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional # A list of mods - how many allowed here is determined by the individual mod loader [[mods]] #mandatory # The modid of the mod modId="${mod_id}" #mandatory # The version number of the mod version="${mod_version}" #mandatory # A display name for the mod displayName="${mod_name}" #mandatory # A URL to query for updates for this mod. See the JSON update specification https://docs.neoforged.net/docs/misc/updatechecker/ #updateJSONURL="https://change.me.example.invalid/updates.json" #optional # A URL for the "homepage" for this mod, displayed in the mod UI #displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional # A file name (in the root of the mod JAR) containing a logo for display #logoFile="examplemod.png" #optional # A text field displayed in the mod UI #credits="" #optional # A text field displayed in the mod UI authors="${mod_authors}" #optional # Display Test controls the display for your mod in the server connection screen # MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. # IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. # IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component. # NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. # IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. #displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) # The description text for the mod (multi line!) (#mandatory) description='''${mod_description}''' # The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded. #[[mixins]] #config="${mod_id}.mixins.json" # The [[accessTransformers]] block allows you to declare where your AT file is. # If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg #[[accessTransformers]] #file="META-INF/accesstransformer.cfg" # The coremods config file path is not configurable and is always loaded from META-INF/coremods.json # A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. [[dependencies.${mod_id}]] #optional # the modid of the dependency modId="neoforge" #mandatory # The type of the dependency. Can be one of "required", "optional", "incompatible" or "discouraged" (case insensitive). # 'required' requires the mod to exist, 'optional' does not # 'incompatible' will prevent the game from loading when the mod exists, and 'discouraged' will show a warning type="required" #mandatory # Optional field describing why the dependency is required or why it is incompatible # reason="..." # The version range of the dependency versionRange="${neo_version_range}" #mandatory # An ordering relationship for the dependency. # BEFORE - This mod is loaded BEFORE the dependency # AFTER - This mod is loaded AFTER the dependency ordering="NONE" # Side this dependency is applied on - BOTH, CLIENT, or SERVER side="BOTH" # Here's another dependency [[dependencies.${mod_id}]] modId="minecraft" type="required" # This version range declares a minimum of the current minecraft version up to but not including the next major version versionRange="${minecraft_version_range}" ordering="NONE" side="BOTH" # Features are specific properties of the game environment, that you may want to declare you require. This example declares # that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't # stop your mod loading on the server for example. #[features.${mod_id}] #openGLVersion="[3.2,)" ``` ## 修改下build.gradle文件 修改插件中的neoforge.gradle 的版本 ``` plugins { id 'java-library' id 'eclipse' id 'idea' id 'maven-publish' id 'net.neoforged.gradle.userdev' version '7.0.124' } ``` 修改Java版本为21 ```java java.toolchain.languageVersion = JavaLanguageVersion.of(21) ``` 由于之前不知道什么更新,导致gradle下载jar不会下载source,要自己下载,导致有点麻烦,并且不会下载全,这个问题应该可以通过新的这个方式修复 文件最下方写上这个。 ``` idea { module { downloadSources = true downloadJavadoc = true } } ``` 并且去掉JEI的相关依赖,因为JEI没有更新1.20.6的版本。 修改mods.toml 为 neoforge.mods.toml , 不然启动不了 ## 修改gralde.properties文件主要是版本 各位参考更新即可。 ```properties # Sets default memory used for gradle commands. Can be overridden by user or command line properties. #org.gradle.jvmargs= org.gradle.daemon=false org.gradle.debug=false #read more on this at https://github.com/neoforged/NeoGradle/blob/NG_7.0/README.md#apply-parchment-mappings # you can also find the latest versions at: https://parchmentmc.org/docs/getting-started neogradle.subsystems.parchment.minecraftVersion=1.20.6 neogradle.subsystems.parchment.mappingsVersion=2024.05.01 # Environment Properties # You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge # The Minecraft version must agree with the Neo version to get a valid artifact minecraft_version=1.20.6 # The Minecraft version range can use any release version of Minecraft as bounds. # Snapshots, pre-releases, and release candidates are not guaranteed to sort properly # as they do not follow standard versioning conventions. minecraft_version_range=[1.20.6,1.21) # The Neo version must agree with the Minecraft version to get a valid artifact neo_version=20.6.62-beta # The Neo version range can use any version of Neo as bounds neo_version_range=[20.6,) # The loader version range can only use the major version of FML as bounds loader_version_range=[2,) ## Mod Properties mc_version=1.20.6 #jei_version=17.0.0.30 terrablender_version=3.5.0.4 # The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63} # Must match the String constant located in the main mod class annotated with @Mod. mod_id=examplemod # The human-readable display name for the mod. mod_name=Example Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=All Rights Reserved # The mod version. See https://semver.org/ mod_version=1.0.0 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html mod_group_id=net.flandre923.examplemod # The authors of the mod. This is a simple text string that is used for display purposes in the mod list. mod_authors=YourNameHere, OtherNameHere # The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. mod_description=Example mod description.\nNewline characters can be used and will be replaced properly. ``` ## 最后重新realod项目 修改完毕后,重新reload项目,他会自动配置相关的内容。等待完成。 ## 来更新报错的类 ## GiveAppleTrigger 修改Codec部分 ```java public static final Codec CODEC = RecordCodecBuilder.create( p_312304_ -> p_312304_.group( EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter(GiveAppleTrigger.Instance::player) ) .apply(p_312304_, GiveAppleTrigger.Instance::new) ); ``` ## BlockEntity部分 load,save 方法改变 CounterBlockEntity ```java @Override protected void loadAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) { counter = pTag.getInt("counter"); super.loadAdditional(pTag, pRegistries); } @Override protected void saveAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) { super.saveAdditional(pTag, pRegistries); pTag.putInt("counter",counter); } ``` FirstMenuBlockEntity ```JAVA @Override protected void loadAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) { super.loadAdditional(pTag, pRegistries); CompoundTag inventory = pTag.getCompound("inventory"); this.itemHandler.deserializeNBT(pRegistries,inventory); } @Override protected void saveAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) { super.saveAdditional(pTag, pRegistries); pTag.put("inventory",this.itemHandler.serializeNBT(pRegistries)); } ``` 数据同步相关的内容,修改 ZombieBlockEntity ```java //而onDataPacket才是客户端接受数据包的方法。 @Override public void onDataPacket(Connection net, ClientboundBlockEntityDataPacket pkt, HolderLookup.Provider lookupProvider) { super.onDataPacket(net, pkt, lookupProvider); handleUpdateTag(pkt.getTag(),lookupProvider); } @Override public CompoundTag getUpdateTag(HolderLookup.Provider pRegistries) { CompoundTag compoundNBT = super.getUpdateTag(pRegistries); compoundNBT.putBoolean("flag", flag); return compoundNBT; } @Override public void handleUpdateTag(CompoundTag tag, HolderLookup.Provider lookupProvider) { super.handleUpdateTag(tag, lookupProvider); flag = tag.getBoolean("flag"); } ``` ## Block的更新 DataSaveBlock 方块的use方法被拆为了2个,不使用物品右键和不使用物品右键 ```java @Override protected ItemInteractionResult useItemOn(ItemStack pStack, BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHitResult) { if(!pLevel.isClientSide){ ModLevelSaveData data = ModLevelSaveData.get(pLevel); ItemStack mainHandItem = pPlayer.getMainHandItem(); if(mainHandItem.isEmpty()){ ItemStack itemStack = data.getItem(); pPlayer.setItemInHand(InteractionHand.MAIN_HAND,itemStack); }else{ ItemStack itemStack =mainHandItem.copy(); mainHandItem.shrink(mainHandItem.getCount()); data.putItem(itemStack); InitTrigger.GIVE_RUBY_APPLE.get().trigger((ServerPlayer) pPlayer); } } return ItemInteractionResult.SUCCESS; } ``` firstMenuBlock ```java @Override protected InteractionResult useWithoutItem(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, BlockHitResult pHitResult) { if (!pLevel.isClientSide){ pPlayer.openMenu(this.getMenuProvider(pState,pLevel,pPos),pPos); } return super.useWithoutItem(pState, pLevel, pPos, pPlayer, pHitResult); } ``` LampBlock ```java @Override protected ItemInteractionResult useItemOn(ItemStack pStack, BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHitResult) { if(!pLevel.isClientSide){ pLevel.setBlock(pPos,pState.cycle(LIT),3); } return super.useItemOn(pStack, pState, pLevel, pPos, pPlayer, pHand, pHitResult); } ``` PortalBlock ```java @Override protected ItemInteractionResult useItemOn(ItemStack pStack, BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHitResult) { if (pPlayer.canChangeDimensions()){ handlePortalTeleport(pLevel,pPos,pPlayer); return ItemInteractionResult.SUCCESS; }else{ return ItemInteractionResult.FAIL; } } ``` RubyCounterBlock ```java @Override protected ItemInteractionResult useItemOn(ItemStack pStack, BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHitResult) { if (!pLevel.isClientSide){ var rubyBlocksEntity = (CounterBlockEntity) pLevel.getBlockEntity(pPos); int counter = rubyBlocksEntity.increase(); pPlayer.sendSystemMessage(Component.literal("counter:"+counter)); } return ItemInteractionResult.SUCCESS; } ``` RubyFrame ```java @Override protected boolean isPathfindable(BlockState pState, PathComputationType pPathComputationType) { switch(pPathComputationType) { case LAND: return false; case WATER: return pState.getFluidState().is(FluidTags.WATER); case AIR: return false; default: return false; } } ``` ModBlocks 流体的注册,需要一个FlowingFluid了不是一个supplier也可以了。 ```java public static final Supplier MY_FLUID_BLOCK = BLOCKS.register("my_fluid_block", ()->new LiquidBlock(ModFluids.MY_SOURCE_FLUID_BLOCK.get(),BlockBehaviour.Properties.ofFullCopy(Blocks.WATER))); ``` SpeedUpCapability的修改 其他几个类类似,就不写了。 ```java @Override public @UnknownNullability CompoundTag serializeNBT(HolderLookup.Provider provider) { CompoundTag compoundNBT = new CompoundTag(); compoundNBT.putInt("level", this.level); return compoundNBT; } @Override public void deserializeNBT(HolderLookup.Provider provider, CompoundTag nbt) { this.level = nbt.getInt("level"); } ``` ## HUD的接口的变化 ExampleHud ```java public class ExampleHud implements LayeredDraw.Layer { private static final ExampleHud hud = new ExampleHud(); private final int width; private final int height; private final Minecraft minecraft; private final ResourceLocation HUD = new ResourceLocation(ExampleMod.MODID, "textures/gui/hud.png"); public ExampleHud() { this.width = Minecraft.getInstance().getWindow().getScreenWidth(); this.height = Minecraft.getInstance().getWindow().getScreenHeight(); this.minecraft = Minecraft.getInstance(); } @Override public void render(GuiGraphics pGuiGraphics, float pPartialTick) { if (minecraft.player.getMainHandItem().getItem()!= ModItems.RUBY_APPLE.get()) return; pGuiGraphics.setColor(1,1,1,1); pGuiGraphics.blit(HUD,width/2-16,height/2-64,0,0,32,32,32,32); } public static ExampleHud getInstance() { return hud; } } ``` ThirstHud ```java public class ThirstHud { private static final ResourceLocation FILLED_THIRST = new ResourceLocation(ExampleMod.MODID, "textures/gui/filled_thirst.png"); private static final ResourceLocation EMPTY_THIRST = new ResourceLocation(ExampleMod.MODID, "textures/gui/empty_thirst.png"); public static final LayeredDraw.Layer HUD_THIRST = (guiGraphics, partialTick) -> { int screenWidth = Minecraft.getInstance().getWindow().getScreenWidth(); int screenHeight = Minecraft.getInstance().getWindow().getScreenHeight(); int x = screenWidth / 2; int y = screenHeight; for(int i = 0; i < 10; i++) { guiGraphics.blit(EMPTY_THIRST,x - 94 + (i * 9), y - 54,90,0,0,12,12, 12,12); } for(int i = 0; i < 10; i++) { if(ClientPlayerThirstData.getPlayerThirst() > i) { guiGraphics.blit(FILLED_THIRST,x - 94 + (i * 9),y - 54,90,0,0,12,12, 12,12); } else { break; } } }; } ``` ## 命名空间的修改 ```java @EventBusSubscriber(modid = ExampleMod.MODID,bus = EventBusSubscriber.Bus.MOD) ``` ## datagenerator的修改 LootTableProvider ```java public class ModLootTableProvider extends LootTableProvider { public ModLootTableProvider(PackOutput pOutput, Set> pRequiredTables, List pSubProviders, CompletableFuture pRegistries) { super(pOutput, pRequiredTables, pSubProviders, pRegistries); } } ``` DataGenerator的修改 ```java event.getGenerator().addProvider( event.includeServer(), (DataProvider.Factory)pOutput -> new ModLootTableProvider(pOutput, Collections.emptySet(), List.of( new LootTableProvider.SubProviderEntry(ModBlockLootProvider::new, LootContextParamSets.BLOCK) ),lp) ); ``` 修改所有的bootstrap的类型BootstrapContext ```java public static void bootstrap(BootstrapContext> pContext) { RuleTest stoneOreReplaceRuleTest = new TagMatchTest(BlockTags.STONE_ORE_REPLACEABLES); RuleTest deepSlateOreReplaceRuleTest = new TagMatchTest(BlockTags.DEEPSLATE_ORE_REPLACEABLES); List list = List.of( OreConfiguration.target(stoneOreReplaceRuleTest, ModBlocks.RUBY_ORE.get().defaultBlockState()), OreConfiguration.target(deepSlateOreReplaceRuleTest, ModBlocks.RUBY_BLOCK.get().defaultBlockState()) ); FeatureUtils.register(pContext, ORE_RUBY, Feature.ORE, new OreConfiguration(list, 9)); } ``` ## 药水 ExampleEffect 该方法具有的返回类型 ```java @Override public boolean applyEffectTick(LivingEntity pEntity, int pAmplifier) { super.applyEffectTick(pEntity, pAmplifier); if (pEntity.getHealth() < pEntity.getMaxHealth()) { pEntity.heal(2.0F); return true; } return false; } ``` Effect的注册改下 ```java public static final Holder EXAMPLE_EFFECT = register("example_effect", ()->new ExampleEffect(MobEffectCategory.BENEFICIAL, 16262179)); ``` POTION改下 ```java public static final DeferredHolder EXAMPLE_POTION = register("example_potion", () -> new Potion("example_potion",new MobEffectInstance(ModEffects.EXAMPLE_EFFECT,1200,1))); ``` ## 附魔 附魔的类别进行了修改,现在使用tag的json文件作为标识了。 ![20240517212413](https://cdn.jsdelivr.net/gh/Flandre923/CDN@latest/img/20240517212413.png) ExampleModReference 文件不再需要 注册 ```java public class ModEnchantments { public static final DeferredRegister ENCHANTMENTS = DeferredRegister.create(Registries.ENCHANTMENT, ExampleMod.MODID); public static final Supplier POWER_ARROWS = ENCHANTMENTS.register("my_power", MyPowerfulEnchantment::new); public static void register(IEventBus bus) { ENCHANTMENTS.register(bus); } } ``` 新增一个附魔的方法,其中参数比较多,不过还是之前的一些数据,其中这数据不需要在重写了。 ```java public class MyPowerfulEnchantment extends Enchantment { protected MyPowerfulEnchantment() { super(Enchantment.definition(ItemTags.BOW_ENCHANTABLE,2,2,Enchantment.dynamicCost(10,20),Enchantment.dynamicCost(60,20),4,EquipmentSlot.MAINHAND)); } } ``` ## 实体 Animal添加一个新的抽象方法siFood ```java // Checks if the parameter is an item which this animal can be fed to breed it (wheat, carrots or seeds depending on the animal type) @Override public boolean isFood(ItemStack pStack) { return false; } ``` 实体定义数据初始值的方法改变了。 ```java @Override protected void defineSynchedData(SynchedEntityData.Builder pBuilder) { pBuilder.define(COUNTER,0); } ``` ## 物品 装备材料类需要进行修改 ```java public enum ModItemTiers implements Tier { RUBY(BlockTags.INCORRECT_FOR_DIAMOND_TOOL,3,2000,10F,4F,30,() -> Ingredient.of(ModItems.RUBY.get())); private final TagKey incorrectBlockForDrops; private final int level; private final int uses; private final float speed; private final float damage; private final int enchantmentValue; private final LazyLoadedValue repairIngredient; private ModItemTiers(TagKey pIncorrectBlockForDrops,int pLevel, int pUses, float pSpeed, float pDamage, int pEnchantmentValue, Supplier pRepairIngredient) { this.incorrectBlockForDrops = pIncorrectBlockForDrops; this.level = pLevel; this.uses = pUses; this.speed = pSpeed; this.damage = pDamage; this.enchantmentValue = pEnchantmentValue; this.repairIngredient = new LazyLoadedValue<>(pRepairIngredient); } @Override public int getUses() { return this.uses; } @Override public float getSpeed() { return this.speed; } @Override public float getAttackDamageBonus() { return this.damage; } @Override public TagKey getIncorrectBlocksForDrops() { return incorrectBlockForDrops; } @Override public int getEnchantmentValue() { return this.enchantmentValue; } @Override public Ingredient getRepairIngredient() { return this.repairIngredient.get(); } } ``` 装备材料修改较大,变为注册的了,下面是添加一个新的方法。 ```java public class ModArmorMaterial { public static final DeferredRegister ARMOR_MATERIAL = DeferredRegister.create(Registries.ARMOR_MATERIAL, ExampleMod.MODID); public static final DeferredHolder RUBY = register( "ruby", Util.make(new EnumMap<>(ArmorItem.Type.class),typeObjectEnumMap -> { typeObjectEnumMap.put(ArmorItem.Type.BOOTS,1); typeObjectEnumMap.put(ArmorItem.Type.LEGGINGS,2); typeObjectEnumMap.put(ArmorItem.Type.CHESTPLATE,3); typeObjectEnumMap.put(ArmorItem.Type.HELMET,1); typeObjectEnumMap.put(ArmorItem.Type.BODY,3); }), 15, SoundEvents.ARMOR_EQUIP_LEATHER, 0F, 0F, ()-> Ingredient.of(ModItems.RUBY.get()), List.of(new ArmorMaterial.Layer(new ResourceLocation(ExampleMod.MODID,"ruby"),"",true),new ArmorMaterial.Layer(new ResourceLocation(ExampleMod.MODID,"ruby"),"_overlay",false)) ); public static DeferredHolder register(String name, EnumMap pDefense, int pEnchantmentValue, Holder pEquipSound, float pToughness, float pKnockResistance, Supplier pRepairIngridient, List pLayers){ EnumMap enummap = new EnumMap<>(ArmorItem.Type.class); for(ArmorItem.Type armorMaterial:ArmorItem.Type.values()){ enummap.put(armorMaterial,pDefense.get(armorMaterial)); } return ARMOR_MATERIAL.register(name,()->new ArmorMaterial(enummap,pEnchantmentValue,pEquipSound,pRepairIngridient,pLayers,pToughness,pKnockResistance)); } // 别忘记注册到总线 public static void register(IEventBus eventBus){ ARMOR_MATERIAL.register(eventBus); } } ``` 好了好需要修改贴图的位置,将原来的位置放到你的命名空间中去吧。(吐槽这遗留问题终于解决了。) ![20240517220826](https://cdn.jsdelivr.net/gh/Flandre923/CDN@latest/img/20240517220826.png) RubyApple ```java public class RubyApple extends Item { private static final FoodProperties FOOD_PROPERTIES = new FoodProperties.Builder() .saturationModifier(10) .nutrition(20) .effect(()-> new MobEffectInstance(MobEffects.POISON,3*20,1),1) .build(); public RubyApple() { super(new Properties().food(FOOD_PROPERTIES)); } } ``` pixcle ```java public class RubyPickaxe extends PickaxeItem { public RubyPickaxe() { super(ModItemTiers.RUBY,new Properties()); } } ``` sword ```java public class RubySword extends SwordItem { public RubySword(){ super(ModItemTiers.RUBY,new Item.Properties()); } } ``` messageItem ```java //网路的更新 public class MessageItem extends Item { public MessageItem() { super(new Properties()); } @Override public InteractionResultHolder use(Level pLevel, Player pPlayer, InteractionHand pUsedHand) { if(pLevel.isClientSide){ // PacketDistributor.PLAYER.with(pPlayer).send(new ); PacketDistributor.sendToServer(new MyData("From client",2)); } if(!pLevel.isClientSide){ PacketDistributor.sendToPlayer((ServerPlayer) pPlayer,new MyData("From server",2)); } return super.use(pLevel,pPlayer,pUsedHand); } } ``` items ```java // MY_SOURCE_FLUID_BLOCK后添加get() // supplier的方法没了 public static final Supplier MY_FLUID_BUCKET = register("my_fluid_bucket", ()->new BucketItem(ModFluids.MY_SOURCE_FLUID_BLOCK.get(),new Item.Properties().craftRemainder(Items.BUCKET).stacksTo(1))); ``` ## ModLevelSaveData 由于ItemStack中的NBT没了,所以添加了新的方式获得ItemStack的NBT。下面是新的代码 ```java private ModLevelSaveData load(CompoundTag nbt,HolderLookup.Provider pLevelRegistry) { ListTag listNBT = (ListTag) nbt.get("list"); if (listNBT != null) { for (Tag value : listNBT) { CompoundTag tag = (CompoundTag) value; ItemStack itemStack = ItemStack.parse(pLevelRegistry,tag).orElse(ItemStack.EMPTY); if (itemStack.equals(ItemStack.EMPTY)){ continue; } itemStacks.push(itemStack); } } // Load saved data return this; } private static ModLevelSaveData decode(CompoundTag tag,HolderLookup.Provider pRegistries){ return ModLevelSaveData.create().load(tag,pRegistries); } public static ModLevelSaveData get(Level worldIn) { if (!(worldIn instanceof ServerLevel)) { throw new RuntimeException("Attempted to get the data from a client world. This is wrong."); } ServerLevel world = worldIn.getServer().getLevel(ServerLevel.OVERWORLD); DimensionDataStorage dataStorage = world.getDataStorage(); return dataStorage.computeIfAbsent(new Factory(ModLevelSaveData::create, ModLevelSaveData::decode), ModLevelSaveData.NAME); } @Override public CompoundTag save(CompoundTag pTag, HolderLookup.Provider pRegistries) { ListTag listTag = new ListTag(); itemStacks.forEach((stack) -> { CompoundTag compoundTag = new CompoundTag(); listTag.add(stack.save(pRegistries,compoundTag)); }); pTag.put("list", listTag); return pTag; } ``` ## 网络 自定载荷修改 添加了Codec作为序列化反序列化的工具 使用CustomPacketPayload.TYPE代替ID作为表示. MyData ```java public record MyData(String message, int age) implements CustomPacketPayload { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(MyData::write,MyData::new); public static final Type TYPE = new Type<>(new ResourceLocation(ExampleMod.MODID,"my_data")); public MyData(final FriendlyByteBuf buf){ this(buf.readUtf(),buf.readInt()); } public void write(FriendlyByteBuf pBuffer) { pBuffer.writeUtf(message()); pBuffer.writeInt(age()); } @Override public Type type() { return TYPE; } } ``` ThirstData ```java public record ThirstData(int thirst) implements CustomPacketPayload { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ThirstData::write,ThirstData::new); public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(new ResourceLocation(ExampleMod.MODID,"thirst_data")); public ThirstData(final FriendlyByteBuf buf){ this(buf.readInt()); } public void write(FriendlyByteBuf pBuffer) { pBuffer.writeInt(thirst()); } @Override public Type type() { return TYPE; } } ``` 服务端和客户端数据包处理的修改 client ```java public class ClientPayloadHandler { private static final Logger LOGGER = LogUtils.getLogger(); private static final ClientPayloadHandler INSTANCE = new ClientPayloadHandler(); public static ClientPayloadHandler getInstance() { return INSTANCE; } public void handleData(final MyData data, final IPayloadContext context) { // Do something with the data, on the network thread // Do something with the data, on the main thread context.enqueueWork(() -> { LOGGER.info(data.message()); }) .exceptionally(e -> { // Handle exception context.disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } public void handleThirstData(final ThirstData data,final IPayloadContext context){ context.enqueueWork(()->{ ClientPlayerThirstData.set(data.thirst()); }).exceptionally(e->{ context.disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } } ``` server ```java public class ServerPayloadHandler { private static final ServerPayloadHandler INSTANCE = new ServerPayloadHandler(); private static final Logger LOGGER = LogUtils.getLogger(); public static ServerPayloadHandler getInstance() { return INSTANCE; } public void handleData(final MyData data, final IPayloadContext context) { // Do something with the data, on the network thread // Do something with the data, on the main thread context.enqueueWork(() -> { LOGGER.info(data.message()); }) .exceptionally(e -> { // Handle exception context.disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } public void handleThirstData(final ThirstData data, final IPayloadContext context){ context.enqueueWork(()->{ Player player = context.player(); if (player instanceof ServerPlayer serverPlayer){ ServerLevel level = (ServerLevel) player.level(); if(hasWaterAroundThem(player,level,2)){ level.playSound(null, player.getOnPos(), SoundEvents.GENERIC_DRINK, SoundSource.PLAYERS, 0.5F, level.random.nextFloat() * 0.1F + 0.9F); Optional.ofNullable(player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)).ifPresent(thirst->{ thirst.addThirst(1); player.sendSystemMessage(Component.literal("Current Thirst " + thirst.getThirst()) .withStyle(ChatFormatting.AQUA)); PacketDistributor.sendToPlayer(serverPlayer,new ThirstData(thirst.getThirst())); }); }else{ Optional.ofNullable(player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)).ifPresent(thirst -> { player.sendSystemMessage(Component.literal("Current Thirst " + thirst.getThirst()) .withStyle(ChatFormatting.AQUA)); // 添加这个代码,当玩家按下o建的时候同步数据 // ModMessages.sendToPlayer(new ThirstDataSyncS2CPacket(thirst.getThirst()),player); PacketDistributor.sendToPlayer(serverPlayer,new ThirstData(thirst.getThirst())); }); } } }).exceptionally(e->{ context.disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } private boolean hasWaterAroundThem(Player player, ServerLevel level, int size) { return level.getBlockStates(player.getBoundingBox().inflate(size)) .filter(state -> state.is(Blocks.WATER)).toArray().length > 0; } } ``` 网络包的注册修改 ```java @EventBusSubscriber(modid = ExampleMod.MODID,bus = EventBusSubscriber.Bus.MOD) public class Networking { @SubscribeEvent public static void register(final RegisterPayloadHandlersEvent event) { final PayloadRegistrar registrar = event.registrar(ExampleMod.MODID); registrar.playBidirectional( MyData.TYPE, MyData.STREAM_CODEC, new DirectionalPayloadHandler<>( ClientPayloadHandler::handleData, ServerPayloadHandler::handleData ) ); registrar.playBidirectional( ThirstData.TYPE, ThirstData.STREAM_CODEC, new DirectionalPayloadHandler<>( ClientPayloadHandler::handleThirstData, ServerPayloadHandler::handleThirstData ) ); } } ``` ## 建筑 改了个名字 ```java public static final MapCodec CODEC = simpleCodec(MyStructure::new); ``` ## CONFIG ```java public ExampleMod(IEventBus modEventBus, ModContainer modContainer) { //config modContainer.registerConfig(ModConfig.Type.COMMON,Config.SPEC); NeoForge.EVENT_BUS.register(this); } ``` ## 粒子 例子砍掉了Deserializer,改成了codec。我还没搞清楚这个codec怎么用,所以这里我按照原版的方法添加例子,就处理命令行输入的问题了。 粒子 ```java public class ExampleParticle extends TextureSheetParticle { public ExampleParticle(ClientLevel pLevel, double pX, double pY, double pZ) { super(pLevel, pX, pY, pZ); this.lifetime = 100; this.hasPhysics = true; } @Override public ParticleRenderType getRenderType() { return ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT; } } ``` 粒子type ```java public class ModParticleType { public static final DeferredRegister > PARTICLE_TYPES = DeferredRegister.create(Registries.PARTICLE_TYPE, ExampleMod.MODID); public static final Supplier EXAMPLE_PARTICLE_TYPE = register("example_particle_type", () -> new SimpleParticleType(true)); public static > DeferredHolder, T> register(String name, Supplier particleType){ return PARTICLE_TYPES.register(name, particleType); } public static void register(IEventBus eventBus){ PARTICLE_TYPES.register(eventBus); } } ``` 例子provider ```java public class ExampleParticleProvider implements ParticleProvider { private final SpriteSet sprites; public ExampleParticleProvider(SpriteSet sprites) { this.sprites = sprites; } @Nullable @Override public Particle createParticle(SimpleParticleType pType, ClientLevel pLevel, double pX, double pY, double pZ, double pXSpeed, double pYSpeed, double pZSpeed) { ExampleParticle exampleParticle = new ExampleParticle(pLevel, pX, pY, pZ); exampleParticle.pickSprite(this.sprites); return exampleParticle; } } ``` 粒子引擎 ```java @EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) public class ParticleFactoryRegistry { @SubscribeEvent public static void onParticleFactoryRegistration(RegisterParticleProvidersEvent event) { event.registerSpriteSet(ModParticleType.EXAMPLE_PARTICLE_TYPE.get(), ExampleParticleProvider::new); // Minecraft.getInstance().particleEngine.register(ModParticleType.EXAMPLE_PARTICLE_TYPE.get(), ExampleParticleProvider::new); } } ``` 对于ExampleParticleType,我直接注解掉了。 ## Event ForgeClientEventHandler ```java @EventBusSubscriber(modid = ExampleMod.MODID,value = Dist.CLIENT) public class ForgeClientEventHandler { @SubscribeEvent public static void onKeyInput(InputEvent.Key event) { if(KeyBinding.DRINKING_KEY.consumeClick()){ PacketDistributor.sendToServer(new ThirstData(ClientPlayerThirstData.getPlayerThirst())); } } } ``` ModClientEventHandler GUI的注册放在一个单独的事件中了。 ```java @SubscribeEvent public static void registerMenuScreen(RegisterMenuScreensEvent event){ event.register(ModMenuTypes.FIRST_MENU.get(),FistMenuGui::new); } ``` 村民交易事件修改 ```java @SubscribeEvent public static void addCustomTrades(VillagerTradesEvent event) { if(event.getType() == VillagerProfession.TOOLSMITH) { Int2ObjectMap> trades = event.getTrades(); ItemStack stack = new ItemStack(ModItems.MAGIC_INGOT.get(), 1); int villagerLevel = 1; trades.get(villagerLevel).add((trader, rand) -> new MerchantOffer( new ItemCost(Items.EMERALD, 2), stack,10,8,0.02F)); } if(event.getType() == ModVillagers.RUBY_MASTER.get()) { Int2ObjectMap> trades = event.getTrades(); ItemStack stack = new ItemStack(ModItems.RUBY.get(), 15); int villagerLevel = 1; trades.get(villagerLevel).add((trader, rand) -> new MerchantOffer( new ItemCost(Items.EMERALD, 5), stack,10,8,0.02F)); } } ``` playerTick ```java @SubscribeEvent public static void onPlayerTick(PlayerTickEvent.Post event) { if(event.getEntity() instanceof ServerPlayer serverPlayer) { Optional optionalPlayerThirst = Optional.ofNullable(serverPlayer.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)); optionalPlayerThirst .ifPresent(thirst -> { if(thirst.getThirst() > 0 && serverPlayer.getRandom().nextFloat() < 0.005f) { // Once Every 10 Seconds on Avg thirst.subThirst(1); // TODO 同步数据 // ModMessages.sendToPlayer(new ThirstDataSyncS2CPacket(thirst.getThirst()),(ServerPlayer) event.player); PacketDistributor.sendToPlayer(serverPlayer,new ThirstData(thirst.getThirst())); serverPlayer.sendSystemMessage(Component.literal("Subtracted Thirst")); } }); } } ``` joinLevelEvent ```java @SubscribeEvent public static void onPlayerJoinWorld(EntityJoinLevelEvent event) { if(!event.getLevel().isClientSide()) { if(event.getEntity() instanceof ServerPlayer player) { Optional optionalPlayerThirst = Optional.ofNullable(player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)); optionalPlayerThirst.ifPresent(thirst -> { // TODO 同步数据 PacketDistributor.sendToPlayer(player,new ThirstData(thirst.getThirst())); // ModMessages.sendToPlayer(new ThirstDataSyncS2CPacket(thirst.getThirst()), player); }); } } } ``` MyPowerEnchantmentEvent ```java boolean flag = player.getAbilities().instabuild || EnchantmentHelper.getItemEnchantmentLevel(Enchantments.INFINITY, bow) > 0; ``` MyPowerEnchantmentEvent ```java @EventBusSubscriber public class MyPowerEnchantmentEvent { @SubscribeEvent public static void MyPowerEnchantmentShot(ArrowLooseEvent event){ ItemStack bow = event.getBow(); Player player = event.getEntity(); int i = event.getCharge(); Level level = event.getLevel(); ItemStack itemstack = player.getProjectile(bow); boolean flag = player.getAbilities().instabuild || EnchantmentHelper.getItemEnchantmentLevel(Enchantments.INFINITY, bow) > 0; shot(itemstack,flag,i,player,level,bow,player.getUsedItemHand()); // 取消掉原版的弓的射箭逻辑 event.setCanceled(true); } public static void shot(ItemStack itemstack, Boolean flag, int i, Player player, Level pLevel, ItemStack pStack, InteractionHand pHand){ if (!itemstack.isEmpty() || flag) { if (itemstack.isEmpty()) { itemstack = new ItemStack(Items.ARROW); } float f = getPowerForTime(i); if (!((double)f < 0.1)) { boolean flag1 = player.getAbilities().instabuild || (itemstack.getItem() instanceof ArrowItem && ((ArrowItem)itemstack.getItem()).isInfinite(itemstack, pStack, player)); if (!pLevel.isClientSide) { ArrowItem arrowitem = (ArrowItem)(itemstack.getItem() instanceof ArrowItem ? itemstack.getItem() : Items.ARROW); AbstractArrow abstractarrow = arrowitem.createArrow(pLevel, itemstack, player); abstractarrow = customArrow(abstractarrow, itemstack); abstractarrow.shootFromRotation(player, player.getXRot(), player.getYRot(), 0.0F, f * 3.0F, 1.0F); if (f == 1.0F) { abstractarrow.setCritArrow(true); } int j = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.POWER, pStack); if (j > 0) { abstractarrow.setBaseDamage(abstractarrow.getBaseDamage() + (double)j * 0.5 + 0.5); } int k = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.POWER, pStack); if (k > 0) { abstractarrow.setKnockback(k); } if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.FLAME, pStack) > 0) { abstractarrow.setRemainingFireTicks(100); } // 这里是我们的附魔,这里为了效果直接不按照等级,基于了20点基础伤害 if(EnchantmentHelper.getItemEnchantmentLevel(ModEnchantments.POWER_ARROWS.get(),pStack) > 0){ abstractarrow.setBaseDamage(20); } pStack.hurtAndBreak(1, player, LivingEntity.getSlotForHand(pHand)); if (flag1 || player.getAbilities().instabuild && (itemstack.is(Items.SPECTRAL_ARROW) || itemstack.is(Items.TIPPED_ARROW))) { abstractarrow.pickup = AbstractArrow.Pickup.CREATIVE_ONLY; } pLevel.addFreshEntity(abstractarrow); } pLevel.playSound( null, player.getX(), player.getY(), player.getZ(), SoundEvents.ARROW_SHOOT, SoundSource.PLAYERS, 1.0F, 1.0F / (pLevel.getRandom().nextFloat() * 0.4F + 1.2F) + f * 0.5F ); if (!flag1 && !player.getAbilities().instabuild) { itemstack.shrink(1); if (itemstack.isEmpty()) { player.getInventory().removeItem(itemstack); } } } } } public static float getPowerForTime(int pCharge) { float f = (float)pCharge / 20.0F; f = (f * f + f * 2.0F) / 3.0F; if (f > 1.0F) { f = 1.0F; } return f; } public static AbstractArrow customArrow(AbstractArrow arrow, ItemStack stack) { return arrow; } } ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/special-blocks-and-items/README.md --- # 6 特殊类型方块和物品 本节我们将讲解一些特殊物品的添加。 --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/special-blocks-and-items/README.md --- # 6 特殊类型方块和物品 本节我们将讲解一些特殊物品的添加。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/feature1/worldgen.md --- # 6 自定义世界生成 *** 暂不支持1.16.5 fabric。 ``` onEvent('worldgen.add', event => { event.addLake(lake => { // 自定义湖 lake.block = 'minecraft:diamond_block' // 方块ID (使用 [] 来为其添加属性) lake.chance = 3 // 约3个区块生成一次 }) event.addOre(ore => { //自定义矿石 ore.block = 'minecraft:glowstone' // 方块ID (使用 [] 来为其添加属性) ore.spawnsIn.blacklist = false // 是否在矿石生成黑名单位置处生成 ore.spawnsIn.values = [ // 该矿石可以在以下位置生成(支持方块ID、标签) '#minecraft:base_stone_overworld' // 默认的生成方式: 用于决定作为地下矿石生成时,该矿石能取代哪些方块。你可以在https://wiki.biligame.com/mc/%E6%A0%87%E7%AD%BE查看更多信息。 ] ore.biomes.blacklist = true // 是否在矿石生成黑名单群系中生成 ore.biomes.values = [ // 矿石可以生成的群系 'minecraft:plains', // 群系ID '#nether' // 或者你可以使用“# + 群系类别”来代表群系, 在文末查看可用的列表 ] ore.clusterMinSize = 5 // 每矿簇最少的矿石数量 (现在 ore.clusterMinSize 选项是被忽略的, 该功能将在以后更新, 现在它恒为1) ore.clusterMaxSize = 9 // 每矿簇最多的矿石数量 ore.clusterCount = 30 // 每个区块矿石数量 ore.minHeight = 0 // 最小Y值 ore.maxHeight = 64 // 最大Y值 ore.squared = true // 对X和Z值添加0~16的随机值. 推荐设置为 true // ore.chance = 4 // 每大约4个区块生成一次. 对于稀有的矿石来说, 你可以将它和 clusterCount = 1 一同使用 }) event.addSpawn(spawn => { // 自定义实体生成 spawn.category = 'monster' // 实体类别, 可以设为 'creature', 'monster', 'ambient', 'water_creature' 和 'water_ambient' spawn.entity = 'minecraft:magma_cube' // 实体ID spawn.weight = 10 // 生成权重 spawn.minCount = 4 // 每组最小数量 spawn.maxCount = 4 // 每组最大数量 }) }) /* 可用的群系类别: taiga(针叶林类) extreme_hills(高山类) jungle(丛林类) mesa(恶地类)(太怪了,作者为什么要在这里用基岩版的名称...) plains(平原类) savanna(热带草原类) icy(冰原类) the_end(末地) beach(沙滩类) forest(树林类) ocean(海洋类) desert(沙漠类) river(河流类) swamp(沼泽类) mushroom(蘑菇岛类) nether(下界) 以下是香草(划掉)世界生成的顺序: raw_generation lakes local_modifications underground_structures surface_structures strongholds underground_ores underground_decoration vegetal_decoration top_layer_modification */ onEvent('worldgen.remove', event => { event.removeOres(ores => {//移除矿石 ores.blocks = [ 'minecraft:coal_ore', 'minecraft:iron_ore' ] // 移除铁矿和煤矿 ores.biomes.values = [ 'minecraft:plains' ] // 限制该选项仅在平原生效 }) event.removeSpawnsByID(spawns => {//通过实体ID来禁止指定实体生成 spawns.entities.values = [ 'minecraft:cow', 'minecraft:chicken', 'minecraft:pig', 'minecraft:zombie' ] }) event.removeSpawnsByCategory(spawns => {//移除实体生成 spawns.biomes.values = [ 'minecraft:plains'//指定为平原群系 ] spawns.categories.values = [//类型为怪物 'monster' ] }) }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/feature1/worldgen.md --- # 6 自定义世界生成 *** 暂不支持1.16.5 fabric。 ``` onEvent('worldgen.add', event => { event.addLake(lake => { // 自定义湖 lake.block = 'minecraft:diamond_block' // 方块ID (使用 [] 来为其添加属性) lake.chance = 3 // 约3个区块生成一次 }) event.addOre(ore => { //自定义矿石 ore.block = 'minecraft:glowstone' // 方块ID (使用 [] 来为其添加属性) ore.spawnsIn.blacklist = false // 是否在矿石生成黑名单位置处生成 ore.spawnsIn.values = [ // 该矿石可以在以下位置生成(支持方块ID、标签) '#minecraft:base_stone_overworld' // 默认的生成方式: 用于决定作为地下矿石生成时,该矿石能取代哪些方块。你可以在https://wiki.biligame.com/mc/%E6%A0%87%E7%AD%BE查看更多信息。 ] ore.biomes.blacklist = true // 是否在矿石生成黑名单群系中生成 ore.biomes.values = [ // 矿石可以生成的群系 'minecraft:plains', // 群系ID '#nether' // 或者你可以使用“# + 群系类别”来代表群系, 在文末查看可用的列表 ] ore.clusterMinSize = 5 // 每矿簇最少的矿石数量 (现在 ore.clusterMinSize 选项是被忽略的, 该功能将在以后更新, 现在它恒为1) ore.clusterMaxSize = 9 // 每矿簇最多的矿石数量 ore.clusterCount = 30 // 每个区块矿石数量 ore.minHeight = 0 // 最小Y值 ore.maxHeight = 64 // 最大Y值 ore.squared = true // 对X和Z值添加0~16的随机值. 推荐设置为 true // ore.chance = 4 // 每大约4个区块生成一次. 对于稀有的矿石来说, 你可以将它和 clusterCount = 1 一同使用 }) event.addSpawn(spawn => { // 自定义实体生成 spawn.category = 'monster' // 实体类别, 可以设为 'creature', 'monster', 'ambient', 'water_creature' 和 'water_ambient' spawn.entity = 'minecraft:magma_cube' // 实体ID spawn.weight = 10 // 生成权重 spawn.minCount = 4 // 每组最小数量 spawn.maxCount = 4 // 每组最大数量 }) }) /* 可用的群系类别: taiga(针叶林类) extreme_hills(高山类) jungle(丛林类) mesa(恶地类)(太怪了,作者为什么要在这里用基岩版的名称...) plains(平原类) savanna(热带草原类) icy(冰原类) the_end(末地) beach(沙滩类) forest(树林类) ocean(海洋类) desert(沙漠类) river(河流类) swamp(沼泽类) mushroom(蘑菇岛类) nether(下界) 以下是香草(划掉)世界生成的顺序: raw_generation lakes local_modifications underground_structures surface_structures strongholds underground_ores underground_decoration vegetal_decoration top_layer_modification */ onEvent('worldgen.remove', event => { event.removeOres(ores => {//移除矿石 ores.blocks = [ 'minecraft:coal_ore', 'minecraft:iron_ore' ] // 移除铁矿和煤矿 ores.biomes.values = [ 'minecraft:plains' ] // 限制该选项仅在平原生效 }) event.removeSpawnsByID(spawns => {//通过实体ID来禁止指定实体生成 spawns.entities.values = [ 'minecraft:cow', 'minecraft:chicken', 'minecraft:pig', 'minecraft:zombie' ] }) event.removeSpawnsByCategory(spawns => {//移除实体生成 spawns.biomes.values = [ 'minecraft:plains'//指定为平原群系 ] spawns.categories.values = [//类型为怪物 'monster' ] }) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/special-blocks-and-items/crops.md --- # 6.1 作物方块和种子 *** ## 一、基础信息 在注册方块的时候,传入参数`"crop"`可以将方块类型指定为作物方块。 默认情况下,KubeJS会同时注册对应种子物品,其ID为`作物方块ID` + `_seed`。 `BlockBuilder`下的`noItem()`方法可以使KubeJS不再注册种子物品 ```js StartupEvents.registry('block', event => { // 自动注册对应种子,ID为example_crop_seed event.create("example_crop","crop"); }) ``` ## 二、CropBlockBuilder | 方法 | 描述 | 对应属性默认值 | | ------------------------------------------- | ---------------- | -------------- | | age(int 生长时间) | 设置生长时间 | 7 | | crop(Object 产出物品) | 额外添加掉落物品 | - | | crop(Object 产出物品,double 概率) | 额外添加掉落物品 | - | | dropSeed(布尔值 是否掉落种子) | 设置掉落种子 | true | | bonemeal(ToIntFunction 使用骨粉回调函数) | 设置使用骨粉事件 | null | | survive(SurviveCallback surviveCallback) | 设置生长条件事件 | null | | growTick(ToDoubleFunction 生长速度回调函数) | 随机刻选中事件 | null | 值得注意的是,`BlockBuilder`中的`randomTick(RandomTickCallbackJS 随机刻回调函数)`在`CropBlockBuilder`中被override了,你需要使用growTick方法替代它。 ## 三、示例 ```js StartupEvents.registry('block', event => { // ^(* ̄(oo) ̄)^ 作物 event.create("pig_plant","crop") .age(3)// 从 0 开始 .crop(Item.of("minecraft:porkchop").withCount(2),0.3) .crop(Item.of("minecraft:porkchop")) .texture("0","minecraft:block/nether_wart_stage0") // 阶段0纹理 .texture("1","minecraft:block/nether_wart_stage1") // 阶段1纹理 .texture("2","minecraft:block/nether_wart_stage1") // 阶段2纹理 .texture("3","minecraft:block/nether_wart_stage2") // 阶段3纹理 .bonemeal(cb =>{ // 使用骨粉时生成一只猪 let b = cb.block; let entity = cb.getLevel().createEntity("minecraft:pig") entity.setPosition(b.getX(),b.getY() + 1,b.getZ()) entity.spawn() return 0; }) .item(s =>{ // 设置种子纹理 s.texture("minecraft:item/spawn_egg"); }) // 末影人(小黑子)作物 event.create("enderman_plant","crop") .survive((blockState,level,blockPos) =>{ // 注1:这里的第一个形参和第三个形参指的都是作物方块的对应属性,而不是被种植的方块。 // 注2:.survive的回调不止是决定作物是否生长,它更决定了作物是否能存活。 // 在玩家使用种子种植作物,以及随机刻触发作物生长前都会触发一次survive的回调。 // 如果返回false,则作物无法被玩家种植,或者作物会被破坏并掉落它的种子(类似于在原版没有光照的环境下种植作物)。(TRAYER) // 检测天气状态(非雨天)和被种植的方块ID。 let id = level.getBlockState(blockPos.below()).getBlock().getId() return (!level.isRaining()) && id == "minecraft:sand"; }).growTick((blockState,level,blockPos) => { // 注:与.survive不同,.growTick的回调则决定了作物在一次随机刻触发的生长中会有多少概率生长。 // 如果返回的数字大于0,则生长的概率=1 / (25 / 返回值 + 1),如果返回数字小于等于0,则会使用原版的计算方式。(TRAYER) // 被随机刻选中时有1/2的概率生长。 return 25; }).item(s =>{ // 设置种子纹理 s.texture("minecraft:item/spawn_egg"); }) }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/special-blocks-and-items/crops.md --- # 6.1 作物方块和种子 *** ## 一、基础信息 在注册方块的时候,传入参数`"crop"`可以将方块类型指定为作物方块。 默认情况下,KubeJS会同时注册对应种子物品,其ID为`作物方块ID` + `_seed`。 `BlockBuilder`下的`noItem()`方法可以使KubeJS不再注册种子物品 ```js StartupEvents.registry('block', event => { // 自动注册对应种子,ID为example_crop_seed event.create("example_crop","crop"); }) ``` ## 二、CropBlockBuilder | 方法 | 描述 | 对应属性默认值 | | ------------------------------------------- | ---------------- | -------------- | | age(int 生长时间) | 设置生长时间 | 7 | | crop(Object 产出物品) | 额外添加掉落物品 | - | | crop(Object 产出物品,double 概率) | 额外添加掉落物品 | - | | dropSeed(布尔值 是否掉落种子) | 设置掉落种子 | true | | bonemeal(ToIntFunction 使用骨粉回调函数) | 设置使用骨粉事件 | null | | survive(SurviveCallback surviveCallback) | 设置生长条件事件 | null | | growTick(ToDoubleFunction 生长速度回调函数) | 随机刻选中事件 | null | 值得注意的是,`BlockBuilder`中的`randomTick(RandomTickCallbackJS 随机刻回调函数)`在`CropBlockBuilder`中被override了,你需要使用growTick方法替代它。 ## 三、示例 ```js StartupEvents.registry('block', event => { // ^(* ̄(oo) ̄)^ 作物 event.create("pig_plant","crop") .age(3)// 从 0 开始 .crop(Item.of("minecraft:porkchop").withCount(2),0.3) .crop(Item.of("minecraft:porkchop")) .texture("0","minecraft:block/nether_wart_stage0") // 阶段0纹理 .texture("1","minecraft:block/nether_wart_stage1") // 阶段1纹理 .texture("2","minecraft:block/nether_wart_stage1") // 阶段2纹理 .texture("3","minecraft:block/nether_wart_stage2") // 阶段3纹理 .bonemeal(cb =>{ // 使用骨粉时生成一只猪 let b = cb.block; let entity = cb.getLevel().createEntity("minecraft:pig") entity.setPosition(b.getX(),b.getY() + 1,b.getZ()) entity.spawn() return 0; }) .item(s =>{ // 设置种子纹理 s.texture("minecraft:item/spawn_egg"); }) // 末影人(小黑子)作物 event.create("enderman_plant","crop") .survive((blockState,level,blockPos) =>{ // 注1:这里的第一个形参和第三个形参指的都是作物方块的对应属性,而不是被种植的方块。 // 注2:.survive的回调不止是决定作物是否生长,它更决定了作物是否能存活。 // 在玩家使用种子种植作物,以及随机刻触发作物生长前都会触发一次survive的回调。 // 如果返回false,则作物无法被玩家种植,或者作物会被破坏并掉落它的种子(类似于在原版没有光照的环境下种植作物)。(TRAYER) // 检测天气状态(非雨天)和被种植的方块ID。 let id = level.getBlockState(blockPos.below()).getBlock().getId() return (!level.isRaining()) && id == "minecraft:sand"; }).growTick((blockState,level,blockPos) => { // 注:与.survive不同,.growTick的回调则决定了作物在一次随机刻触发的生长中会有多少概率生长。 // 如果返回的数字大于0,则生长的概率=1 / (25 / 返回值 + 1),如果返回数字小于等于0,则会使用原版的计算方式。(TRAYER) // 被随机刻选中时有1/2的概率生长。 return 25; }).item(s =>{ // 设置种子纹理 s.texture("minecraft:item/spawn_egg"); }) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/special-blocks-and-items/food.md --- # 6.2 食物注册 *** ## 一、基础信息 借助`ItemBuilder`下的方法`food(Consumer)`,你可以便捷地创建食物物品,例如: ```js StartupEvents.registry("item",event =>{ event.create("example_food","basic").food(food=>{ // 在此设置食物属性 }) }) ``` ## 二、FoodBuilder | 方法 | 描述 | 备注 | | ------------------------------------------------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------- | | hunger(int 饱食度) | 设置食物提供的饱食度 | 详情见[Minecraft Wiki](https://minecraft.fandom.com/zh/wiki/%E9%A5%A5%E9%A5%BF#%E6%9C%BA%E5%88%B6) | | saturation(float 饱和度) | 设置食物提供的饱和度倍率\[1] | 详情见[Minecraft Wiki](https://minecraft.fandom.com/zh/wiki/%E9%A5%A5%E9%A5%BF#%E6%9C%BA%E5%88%B6) | | meat(boolean flag) | 设置是否为肉食 | 例如原版中作为狼的食物 | | meat() | 设置为肉食 | 同上 | | alwaysEdible(boolean flag) | 设置是否随时可食用 | 例如金苹果 | | alwaysEdible() | 设置为随时可食用 | 同上 | | fastToEat(boolean flag) | 设置是否快速食用 | 例如干海带 | | fastToEat() | 设置为快速食用 | 同上 | | effect(ResourceLocation 药效ID, int 持续时间, int 倍率, float 概率) | 食用后给予药效 | 概率为0~1 | | removeEffect(MobEffect 药效) | 食用后移除指定药效 | - | | eaten(Consumer 事件) | 设置食用完成后事件 | \[1] | \[1] `饱和度` = `设置的食物提供饱食度` \* `饱和度倍率` \[2] ``` FoodEatenEventJS具有以下属性: player 返回食用玩家 item 返回食用物品 值得注意的是,FoodEatenEventJS是PlayerEventJS的子类,这意味着其具有PlayerEventJS中的方法或属性。 ``` ## 三、示例 ```js StartupEvents.registry("item",event =>{ event.create("super_wudji","basic").fireResistant(true).glow(true).food(food=>{ food.hunger(3) food.saturation(2.0)// 提供的饱和度为 3 * 2 = 6 food.alwaysEdible(true) food.fastToEat(true) food.meat(true) food.eaten(ctx =>{ let player = ctx.player; if (player.getUsername() == "Wudji_NotFound"){ player.potionEffects.add("minecraft:resistance",23333,5,true,false); }else{ if(Utils.random() > 0.5){ player.give(Item.of("kubejs:super_wudji")); } } }) }) }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/special-blocks-and-items/food.md --- # 6.2 食物注册 *** ## 一、基础信息 借助`ItemBuilder`下的方法`food(Consumer)`,你可以便捷地创建食物物品,例如: ```js StartupEvents.registry("item",event =>{ event.create("example_food","basic").food(food=>{ // 在此设置食物属性 }) }) ``` ## 二、FoodBuilder | 方法 | 描述 | 备注 | | ------------------------------------------------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------- | | hunger(int 饱食度) | 设置食物提供的饱食度 | 详情见[Minecraft Wiki](https://minecraft.fandom.com/zh/wiki/%E9%A5%A5%E9%A5%BF#%E6%9C%BA%E5%88%B6) | | saturation(float 饱和度) | 设置食物提供的饱和度倍率\[1] | 详情见[Minecraft Wiki](https://minecraft.fandom.com/zh/wiki/%E9%A5%A5%E9%A5%BF#%E6%9C%BA%E5%88%B6) | | meat(boolean flag) | 设置是否为肉食 | 例如原版中作为狼的食物 | | meat() | 设置为肉食 | 同上 | | alwaysEdible(boolean flag) | 设置是否随时可食用 | 例如金苹果 | | alwaysEdible() | 设置为随时可食用 | 同上 | | fastToEat(boolean flag) | 设置是否快速食用 | 例如干海带 | | fastToEat() | 设置为快速食用 | 同上 | | effect(ResourceLocation 药效ID, int 持续时间, int 倍率, float 概率) | 食用后给予药效 | 概率为0~1 | | removeEffect(MobEffect 药效) | 食用后移除指定药效 | - | | eaten(Consumer 事件) | 设置食用完成后事件 | \[1] | \[1] `饱和度` = `设置的食物提供饱食度` \* `饱和度倍率` \[2] ``` FoodEatenEventJS具有以下属性: player 返回食用玩家 item 返回食用物品 值得注意的是,FoodEatenEventJS是PlayerEventJS的子类,这意味着其具有PlayerEventJS中的方法或属性。 ``` ## 三、示例 ```js StartupEvents.registry("item",event =>{ event.create("super_wudji","basic").fireResistant(true).glow(true).food(food=>{ food.hunger(3) food.saturation(2.0)// 提供的饱和度为 3 * 2 = 6 food.alwaysEdible(true) food.fastToEat(true) food.meat(true) food.eaten(ctx =>{ let player = ctx.player; if (player.getUsername() == "Wudji_NotFound"){ player.potionEffects.add("minecraft:resistance",23333,5,true,false); }else{ if(Utils.random() > 0.5){ player.give(Item.of("kubejs:super_wudji")); } } }) }) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/special-blocks-and-items/blockbench.md --- # 6.3 BlockBench入门教程 本节由@[Qi-Month (柒月)](https://github.com/Qi-Month)编写。 #### 一、关于Blockbench * Blockbench可以说是Minecraft界的建模神器了,你们所见到的奇奇怪怪的方块基本都是Blockbench做的,例如这个锻造台\ ![smithing\_table.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a741f528.png) * **官网**:https://www.blockbench.net * **文件路径** * 模型文件往往存放于`assets/${modid}/models` * 和`textures`文件夹一样,`models`文件夹内一般分为`block`和`item`两个文件夹 * 取决于你做的模型类型如果是方块就放入`block`内,如果是物品就放入`item`内 * **保存时一定得是`json`格式的模型文件** * 而贴图则是置于`assets/${modid}/textures`内,和上面一样,方块放`block`,物品放`item`,可以的话最好和模型一个`${modid}` #### 二、使用 (本教程只教最简单的部分,惊喜的部分请自己另找教程) **1、创建项目** * 首先在选择模型时请选择他已经给出的Java版模型,这个类型所导出的模型都是json格式,也正好是Minecraft所需要的格式\ ![menu.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a717e6bf.png) * 选择模型后他会还有一个弹窗,文件名写你的物品或方块ID即可,其它不用管\ ![project.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a73f2478.png) **2、创建第一个模型** * 在进入工作区后我们会看到下面的UI,通过下图来了解一下工作区最常用的几个按钮所相对应的功能(块其实就是模型)\ ![ui.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a717dc83.png) * 我们先一顿操作猛如虎随便搞个模型出来\ ![model\_1.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a718c275.png) * 随后我们保存模型,保存到上面所说的`assets/${modid}/models/block`\ ![model\_2.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a74056fb.png)\ 写代码... ```js StartupEvents.registry("block", (event) => { // 方块ID要和模型文件名一样 event.create("test_block") }) ``` **3、为模型设置贴图** * 这个时候如果注册进游戏显示的将会是下图...紫黑块,并且手上的模型和物品栏模型的方向以及大小看起来和别的方块很不一样...\ ![model\_1.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c04a2482.png) * 我们先来解决一下贴图的问题,我们将贴图放在上面提到的`assets/${modid}/textures/block`中,然后打开blockbench点击左下方的导入纹理(也可以导入多个纹理),或是自己创建一个纹理(这里不教)\ ![textures\_1.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c452fc2b.png)![textures\_2.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c453573a.png) * 导入纹理后我们还要给模型贴上贴图,有两种方式 * 第一种是直接将贴图给拖到模型上\ ![textures\_3.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c454a2fd.png) * 第二种是右键模型`选择纹理`\ ![textures\_4.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c45513a5.png) * 有时候贴上贴图后会有下面这奇怪的现象\ ![textures\_5.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c45457d1.png) * 把视角放到左上角的贴图处理的地方,这里的贴图处理可以选择贴图的所选位置,在这里进行选取的贴图区域在模型上也会同步展示 * 至于下面那一排按钮...为什么不自己动手试试呢? Ciallo ~(∠·ω< )⌒☆\ ![textures\_6.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c461f31a.png) * 一个模型一共有6个面,每个面都可以是不同的贴图,而每个模型都可以使用不同的贴图,还是一顿操作猛如虎我们把纹理都处理好后保存,然后启动游戏(如果开着游戏`F3+T`也可以)\ ![model\_2.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a73d7826.png) **嘿咻嘿咻~ 游戏启动中~** * 如果你的贴图成功加载出来了那恭喜你成功了 * 现在!去楼下小卖部给自己买一根5块钱以上的雪糕好好犒劳一下自己 * ![model\_2.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c049782e.png) * **物品栏模型** * 不难看到我们物品栏中的和手上的模型看起来非常奇怪,这个也很简单,我们点开工作区右上角的显示调整,然后点击左上角的3条横,选择`默认方块/三个点/应用于所有槽位`,保存!(当然你也可以选择自己调整)\ ![display\_1.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a717fcb6.png) ![display\_2.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a717f347.png) * 和刚才一样的步骤,`F3+T`重载一下 ![model\_3.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c04a1ebd.png) * 可以看到物品栏中和手上的模型都和正常的方块一样了 * 如果在破坏方块时他的粒子效果也是紫黑色,可以右键你需要的纹理,然后选择`作为粒子纹理` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/special-blocks-and-items/blockbench.md --- # 6.3 BlockBench入门教程 本节由@[Qi-Month (柒月)](https://github.com/Qi-Month)编写。 #### 一、关于Blockbench * Blockbench可以说是Minecraft界的建模神器了,你们所见到的奇奇怪怪的方块基本都是Blockbench做的,例如这个锻造台\ ![smithing\_table.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a741f528.png) * **官网**:https://www.blockbench.net * **文件路径** * 模型文件往往存放于`assets/${modid}/models` * 和`textures`文件夹一样,`models`文件夹内一般分为`block`和`item`两个文件夹 * 取决于你做的模型类型如果是方块就放入`block`内,如果是物品就放入`item`内 * **保存时一定得是`json`格式的模型文件** * 而贴图则是置于`assets/${modid}/textures`内,和上面一样,方块放`block`,物品放`item`,可以的话最好和模型一个`${modid}` #### 二、使用 (本教程只教最简单的部分,惊喜的部分请自己另找教程) **1、创建项目** * 首先在选择模型时请选择他已经给出的Java版模型,这个类型所导出的模型都是json格式,也正好是Minecraft所需要的格式\ ![menu.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a717e6bf.png) * 选择模型后他会还有一个弹窗,文件名写你的物品或方块ID即可,其它不用管\ ![project.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a73f2478.png) **2、创建第一个模型** * 在进入工作区后我们会看到下面的UI,通过下图来了解一下工作区最常用的几个按钮所相对应的功能(块其实就是模型)\ ![ui.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a717dc83.png) * 我们先一顿操作猛如虎随便搞个模型出来\ ![model\_1.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a718c275.png) * 随后我们保存模型,保存到上面所说的`assets/${modid}/models/block`\ ![model\_2.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a74056fb.png)\ 写代码... ```js StartupEvents.registry("block", (event) => { // 方块ID要和模型文件名一样 event.create("test_block") }) ``` **3、为模型设置贴图** * 这个时候如果注册进游戏显示的将会是下图...紫黑块,并且手上的模型和物品栏模型的方向以及大小看起来和别的方块很不一样...\ ![model\_1.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c04a2482.png) * 我们先来解决一下贴图的问题,我们将贴图放在上面提到的`assets/${modid}/textures/block`中,然后打开blockbench点击左下方的导入纹理(也可以导入多个纹理),或是自己创建一个纹理(这里不教)\ ![textures\_1.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c452fc2b.png)![textures\_2.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c453573a.png) * 导入纹理后我们还要给模型贴上贴图,有两种方式 * 第一种是直接将贴图给拖到模型上\ ![textures\_3.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c454a2fd.png) * 第二种是右键模型`选择纹理`\ ![textures\_4.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c45513a5.png) * 有时候贴上贴图后会有下面这奇怪的现象\ ![textures\_5.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c45457d1.png) * 把视角放到左上角的贴图处理的地方,这里的贴图处理可以选择贴图的所选位置,在这里进行选取的贴图区域在模型上也会同步展示 * 至于下面那一排按钮...为什么不自己动手试试呢? Ciallo ~(∠·ω< )⌒☆\ ![textures\_6.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c461f31a.png) * 一个模型一共有6个面,每个面都可以是不同的贴图,而每个模型都可以使用不同的贴图,还是一顿操作猛如虎我们把纹理都处理好后保存,然后启动游戏(如果开着游戏`F3+T`也可以)\ ![model\_2.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a73d7826.png) **嘿咻嘿咻~ 游戏启动中~** * 如果你的贴图成功加载出来了那恭喜你成功了 * 现在!去楼下小卖部给自己买一根5块钱以上的雪糕好好犒劳一下自己 * ![model\_2.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c049782e.png) * **物品栏模型** * 不难看到我们物品栏中的和手上的模型看起来非常奇怪,这个也很简单,我们点开工作区右上角的显示调整,然后点击左上角的3条横,选择`默认方块/三个点/应用于所有槽位`,保存!(当然你也可以选择自己调整)\ ![display\_1.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a717fcb6.png) ![display\_2.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880a717f347.png) * 和刚才一样的步骤,`F3+T`重载一下 ![model\_3.png](https://i1.mcobj.com/imgb/u18prz/20240705_66880c04a1ebd.png) * 可以看到物品栏中和手上的模型都和正常的方块一样了 * 如果在破坏方块时他的粒子效果也是紫黑色,可以右键你需要的纹理,然后选择`作为粒子纹理` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/feature1/add-blocks.md --- # 7 添加方块 *** 以下是格式示例,你应将其放在startup脚本文件夹内。 ``` // 此写法被弃用 onEvent('block.registry', event => { event.create('test_block', block => { block.material('glass')//设置方块材质 block.hardness(0.5)//设置方块硬度 block.displayName('Test Block') }) }) onEvent('block.registry', event => { event.create('test_block') .material('glass') .hardness(0.5) .displayName('Test Block') }) //你还可以把他们在一行中连着写 onEvent('block.registry', event => { event.create('example_block').material('wood').hardness(1.0).displayName('Example Block') }) ``` 该方块的纹理必须被放于`kubejs/assets/kubejs/textures/block/[方块名]`,如`kubejs/assets/kubejs/textures/block/test_block.png` 如果你想要使用自定义的模型,你可以使用Blockbench制作并把它放于 `kubejs/assets/kubejs/models/block/[方块名].json` , 如 `kubejs/assets/kubejs/models/block/test_block.json`. 其他支持的方法: * material('material')// 设置方块的“质地”,可用的参数见下 * type('basic') // 设置方块类型,可用的参数见下 * hardness(float) // 设置方块硬度,值需要大于 >= 0.0;若要使方块无法被破坏,详见下方。 * resistance(float) // 爆炸抗性,值需要大于 >= 0.0 * unbreakable() // 设置方块无法被破坏 * lightLevel(等级) // 方块光照等级,值属于\[0 , 1](需要为整形) * harvestTool('工具', 工具等级) // 可以破坏该方块的工具。工具参数可以为 pickaxe, axe, hoe, shovel,工具等级需 >= 0 * opaque(布尔值) // 方块是否透明 * fullBlock(布尔值) // 设置是否为完整方块,与notSolid()共同使用可以实现如下图所示效果(即镂空方块) *图片被mcbbs吃掉了* * requiresTool(布尔值) // 是否需要对应工具才能掉落 * renderType('类型') // 设置方块的渲染类型,可用的选项有solid(实心), cutout(镂刻), translucent(半透明), 其中 cutout 可用于类似于玻璃的方块,translucent 可用于类似于染色玻璃的方块 * color(tintindex, color)// 修改方块颜色,具体可参考[此处介绍](https://www.mcbbs.net/forum.php?mod=redirect\&goto=findpost\&ptid=1112082\&pid=20118507) * texture('纹理路径')// 设置方块纹理 * texture('方向', '纹理路径')// 设置方块纹理(可以单独设置每个朝向的纹理) * model('模型路径')// 设置方块使用的模型 * noItem()// 添加方块而不添加方块对应的物品 * box(x0, y0, z0, x1, y1, z1, true) // 设置方块碰撞箱(0~16),默认值为(0,0,0,16,16,16, true) * box(x0, y0, z0, x1, y1, z1, false) // 设置方块碰撞箱(0~1),默认值为(0,0,0,1,1,1, false) 下图即为使用box方法设置的自定义方块 ![](https://m1.miaomc.cn/uploads/20220130_1ec4430bb7573.png) * noCollision() // 设置方块是否有碰撞箱 * notSolid() // 见上 * waterlogged() // 是否可以被充水 * noDrops() // 被破坏是否掉落自身 * slipperiness(浮点型) // 设置打滑程度 * speedFactor(浮点型) // 玩家在上方移动速度倍率 * jumpFactor(浮点型) * randomTick(randomTickEvent => {}) // 当方块被随机刻选中时发生的事件 支持以下函数: BlockContainerJS block Random random 下面是一个简单的例子 ``` //当test_block_randomTickEvent被随机刻选中时将其换为minecraft:dirt onEvent('block.registry', event => { event.create('test_block_randomTickEvent').material('glass').hardness(1.0).displayName('Test Block randomTickEvent').randomTick(randomTickEvent => { randomTickEvent.block.set('minecraft:dirt')// BlockContainerJS }) }) ``` 注:BlockContainerJS会在后面讲到(大概在15或16章,尚未更新) * item([itemBuilder](https://mods.latvian.dev/books/kubejs/page/custom-items) => {}) // 掉落物设置 * setLootTableJson(json) // 设置战利品表(原版json格式) * setBlockstateJson(json) // 设置方块状态(原版json格式) * setModelJson(json) // 设置模型 * noValidSpawns(布尔值) // 是否可以生成怪物 * suffocating(布尔值) // 是否可以使玩家窒息 * viewBlocking(布尔值) * redstoneConductor(布尔值) // 是否传导红石信号 * transparent(布尔值) // 是否透明 * defaultCutout() // 用来制作类似于玻璃的方块的方法的集合 * defaultTranslucent() // 与defaultCutout()类似,但是使用透明层 * tag('forge:exampletag') // 添加block tag * tagBlockAndItem('forge:exampletag') // 为方块和物品添加tag ### 可用的方块类型(type() 方法) * basic * slab * stairs * fence * fence\_gate * wall * wooden\_pressure\_plate * stone\_pressure\_plate * wooden\_button * stone\_button 可用的材质的值( material('material') ) - 该参数会改变方块被破坏和玩家行走在上面的声音并且预设一些属性(如破坏用的工具等): | 材质 | | ----------- | | air | | wood | | rock | | iron | | organic | | earth | | water | | lava | | leaves | | plants | | sponge | | wool | | sand | | glass | | tnt | | coral | | ice | | snow | | clay | | groud | | dragon\_egg | | portal | | cake | | web | | slime | | honey | | berry\_bush | | lantern | --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/feature1/add-blocks.md --- # 7 添加方块 *** 以下是格式示例,你应将其放在startup脚本文件夹内。 ``` // 此写法被弃用 onEvent('block.registry', event => { event.create('test_block', block => { block.material('glass')//设置方块材质 block.hardness(0.5)//设置方块硬度 block.displayName('Test Block') }) }) onEvent('block.registry', event => { event.create('test_block') .material('glass') .hardness(0.5) .displayName('Test Block') }) //你还可以把他们在一行中连着写 onEvent('block.registry', event => { event.create('example_block').material('wood').hardness(1.0).displayName('Example Block') }) ``` 该方块的纹理必须被放于`kubejs/assets/kubejs/textures/block/[方块名]`,如`kubejs/assets/kubejs/textures/block/test_block.png` 如果你想要使用自定义的模型,你可以使用Blockbench制作并把它放于 `kubejs/assets/kubejs/models/block/[方块名].json` , 如 `kubejs/assets/kubejs/models/block/test_block.json`. 其他支持的方法: * material('material')// 设置方块的“质地”,可用的参数见下 * type('basic') // 设置方块类型,可用的参数见下 * hardness(float) // 设置方块硬度,值需要大于 >= 0.0;若要使方块无法被破坏,详见下方。 * resistance(float) // 爆炸抗性,值需要大于 >= 0.0 * unbreakable() // 设置方块无法被破坏 * lightLevel(等级) // 方块光照等级,值属于\[0 , 1](需要为整形) * harvestTool('工具', 工具等级) // 可以破坏该方块的工具。工具参数可以为 pickaxe, axe, hoe, shovel,工具等级需 >= 0 * opaque(布尔值) // 方块是否透明 * fullBlock(布尔值) // 设置是否为完整方块,与notSolid()共同使用可以实现如下图所示效果(即镂空方块) *图片被mcbbs吃掉了* * requiresTool(布尔值) // 是否需要对应工具才能掉落 * renderType('类型') // 设置方块的渲染类型,可用的选项有solid(实心), cutout(镂刻), translucent(半透明), 其中 cutout 可用于类似于玻璃的方块,translucent 可用于类似于染色玻璃的方块 * color(tintindex, color)// 修改方块颜色,具体可参考[此处介绍](https://www.mcbbs.net/forum.php?mod=redirect\&goto=findpost\&ptid=1112082\&pid=20118507) * texture('纹理路径')// 设置方块纹理 * texture('方向', '纹理路径')// 设置方块纹理(可以单独设置每个朝向的纹理) * model('模型路径')// 设置方块使用的模型 * noItem()// 添加方块而不添加方块对应的物品 * box(x0, y0, z0, x1, y1, z1, true) // 设置方块碰撞箱(0~16),默认值为(0,0,0,16,16,16, true) * box(x0, y0, z0, x1, y1, z1, false) // 设置方块碰撞箱(0~1),默认值为(0,0,0,1,1,1, false) 下图即为使用box方法设置的自定义方块 ![](https://m1.miaomc.cn/uploads/20220130_1ec4430bb7573.png) * noCollision() // 设置方块是否有碰撞箱 * notSolid() // 见上 * waterlogged() // 是否可以被充水 * noDrops() // 被破坏是否掉落自身 * slipperiness(浮点型) // 设置打滑程度 * speedFactor(浮点型) // 玩家在上方移动速度倍率 * jumpFactor(浮点型) * randomTick(randomTickEvent => {}) // 当方块被随机刻选中时发生的事件 支持以下函数: BlockContainerJS block Random random 下面是一个简单的例子 ``` //当test_block_randomTickEvent被随机刻选中时将其换为minecraft:dirt onEvent('block.registry', event => { event.create('test_block_randomTickEvent').material('glass').hardness(1.0).displayName('Test Block randomTickEvent').randomTick(randomTickEvent => { randomTickEvent.block.set('minecraft:dirt')// BlockContainerJS }) }) ``` 注:BlockContainerJS会在后面讲到(大概在15或16章,尚未更新) * item([itemBuilder](https://mods.latvian.dev/books/kubejs/page/custom-items) => {}) // 掉落物设置 * setLootTableJson(json) // 设置战利品表(原版json格式) * setBlockstateJson(json) // 设置方块状态(原版json格式) * setModelJson(json) // 设置模型 * noValidSpawns(布尔值) // 是否可以生成怪物 * suffocating(布尔值) // 是否可以使玩家窒息 * viewBlocking(布尔值) * redstoneConductor(布尔值) // 是否传导红石信号 * transparent(布尔值) // 是否透明 * defaultCutout() // 用来制作类似于玻璃的方块的方法的集合 * defaultTranslucent() // 与defaultCutout()类似,但是使用透明层 * tag('forge:exampletag') // 添加block tag * tagBlockAndItem('forge:exampletag') // 为方块和物品添加tag ### 可用的方块类型(type() 方法) * basic * slab * stairs * fence * fence\_gate * wall * wooden\_pressure\_plate * stone\_pressure\_plate * wooden\_button * stone\_button 可用的材质的值( material('material') ) - 该参数会改变方块被破坏和玩家行走在上面的声音并且预设一些属性(如破坏用的工具等): | 材质 | | ----------- | | air | | wood | | rock | | iron | | organic | | earth | | water | | lava | | leaves | | plants | | sponge | | wool | | sand | | glass | | tnt | | coral | | ice | | snow | | clay | | groud | | dragon\_egg | | portal | | cake | | web | | slime | | honey | | berry\_bush | | lantern | --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/enchantments.md --- # 7.附魔添加 *** ## 一、事件监听 通过向`StartupEvents.registry`事件传入不同的参数,其可用于在启动脚本中注册游戏中的新元素,如方块、物品、声音、药效等,在这里,我们传入`"enchantment"`来进行与附魔有关的修改: ```js StartupEvents.registry("enchantment",event => { // 在此进行修改 }) ``` ## 二、事件方法 | 方法 | 描述 | 返回值 | | ---------------------------------- | -------- | ------------------ | | create(字符串 附魔ID, 字符串 类型) | 注册物品 | EnchantmentBuilder | | create(字符串 附魔ID) | 注册物品 | EnchantmentBuilder | 其中"类型"参数默认情况下只支持`"basic"`类型。 ## 三、EnchantmentBuilderf | 方法 | 描述 | 备注 | 对应属性默认值 | | --------------------------------------------------- | -------------------------------- | ------------------------ | -------------- | | category(EnchantmentCategory 附魔类型) | 设置附魔支持的物品类型 | \[1] | "digger" | | rarity(Rarity 稀有度) | 设置附魔稀有度 | - | Rarity.COMMON | | damageProtection(DamageProtectionFunction 伤害保护) | 设置伤害保护 | 使用方法见下 | null | | damageBonus(DamageBonusFunction 伤害加成) | 设置伤害加成 | 使用方法见下 | null | | maxLevel(整形 等级) | 设置附魔最大等级 | - | 1 | | minLevel(整形 等级) | 设置附魔最小等级 | - | 1 | | maxCost(Int2IntFunction 设置花费) | 设置最大花费 | 使用方法见下 | null | | minCost(Int2IntFunction 设置花费) | 设置最小花费 | 使用方法见下 | null | | postHurt(PostFunction 受伤后事件) | 设置受伤后事件 | 使用方法见下 | null | | postAttack(PostFunction 攻击后事件) | 设置攻击后事件 | 使用方法见下 | null | | canEnchant(Object2BooleanFunction 物品检查) | 检查附魔条件 | 使用方法见下 | null | | undiscoverable() | 隐形附魔 | - | - | | curse() | 设置附魔类型为诅咒附魔 | - | - | | untradeable() | 设置附魔无法被村民卖出 | - | - | | uncommon() | 设置物品稀有度为UNCOMMON | 效果同rarity方法,下同 | - | | rare() | 设置物品稀有度为RARE | - | - | | veryRare() | 设置物品稀有度为VERY\_RARE | - | - | | vanishable() | 允许将这类附魔附加于可消耗物品上 | 效果同category方法,下同 | - | | crossbow() | 允许将这类附魔附加于弩上 | - | - | | trident() | 允许将这类附魔附加于三叉戟上 | - | - | | armor() | 允许将这类附魔附加于护甲上 | - | - | | armorHead() | 允许将这类附魔附加于头盔上 | - | - | | armorChest() | 允许将这类附魔附加于胸甲上 | - | - | | armorLegs() | 允许将这类附魔附加于护腿上 | - | - | | armorFeet() | 允许将这类附魔附加于靴子上 | - | - | | wearable() | 允许将这类附魔附加于可穿戴物品上 | - | - | | weapon() | 允许将这类附魔附加于武器上 | - | - | | bow() | 允许将这类附魔附加于弓箭上 | - | - | | fishingRod() | 允许将这类附魔附加于钓鱼竿上 | - | - | | breakable() | 允许将这类附魔附加于可破坏物品上 | - | - | | .displayName(字符串 显示名称) | 设置附魔显示名称 | - | - | \[1] | EnchantmentCategory值 | 描述 | | --------------------- | ------------ | | "basic" | 基础类 | | "armor\_head" | 头盔类 | | "armor\_chest" | 胸甲类 | | "armor\_legs" | 护腿类 | | "armor\_feet" | 靴子类 | | "armor" | 护甲类 | | "weapon" | 武器类 | | "digger" | 挖掘工具类 | | "bow" | 弓类 | | "crossbow" | 弩类 | | "fishing\_rod" | 钓鱼竿类 | | "trident" | 三叉戟类 | | "breakable" | 可破坏物品类 | | "vanishable" | 消耗物品类 | | "wearable" | 可穿戴类 | ## 四:示例 ```js StartupEvents.registry("enchantment",event => { // 正面附魔:水刃。可应用于武器上。当攻击对象类型为"WATER"时,给予伤害加成(level * 2),如果不是那么降低造成的伤害(level * -1) event.create("hydro_enchantment").weapon().maxLevel(5).damageBonus((level,mobType)=>{ // 检查怪物类型 if (mobType == "WATER"){ // 返回加成计算结果 return level * 2; }else{ // 返回加成计算结果 return level * -1; } }).displayName("水刃"); // 正面附魔:冰冻。可应用于木棍上。当使用附魔冰冻的木棍攻击时,给予受击目标缓慢药水效果。该效果具有冷却。 event.create("cryo_enchantment").canEnchant((itemStack)=>{ // 形参为要检查的itemStack // 设置该附魔只能应用于木棍上 if (itemStack.id == "minecraft:stick"){ // 返回检查结果(通过) return true; }; // 返回检查结果(未通过) return false; }).maxLevel(1).untradeable().postAttack((source,target,level)=>{ /* 三个形参分别为: LivingEntity 攻击源(附魔持有者) Entity 受击目标 整形 附魔等级 */ // 如果受击目标存活,那么给予其缓慢药水效果,并为发出者添加物品冷却。 if (target.isLiving()){ // 为受击目标添加药水效果 target.potionEffects.add("minecraft:slowness",3000); // 为攻击源添加物品冷却 source.addItemCooldown(source.getMainHandItem(),3000); } }).displayName("冰冻"); // 负面附魔:风化。可应用于盔甲上。当装备者受到伤害时,根据附魔等级给予其漂浮药水效果,并治疗攻击者。 event.create("anemo_enchantment").armor().maxLevel(5).curse().postHurt((target,attacker,level)=>{ /* 三个形参分别为: LivingEntity 受击者(附魔持有者) Entity 攻击源 整形 附魔等级 */ // 如果附魔持有者存活,那么根据附魔等级给予其漂浮药水效果。 if (target.isLiving()){ // 为附魔持有者添加药水效果 target.potionEffects.add("minecraft:levitation",2000 * level); } // 如果攻击者存活,那么根据附魔等级治疗攻击者。 if (attacker.isLiving()){ // 治疗攻击者 attacker.heal(level) } }).displayName("风化"); }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/enchantments.md --- # 7.附魔添加 *** ## 一、事件监听 通过向`StartupEvents.registry`事件传入不同的参数,其可用于在启动脚本中注册游戏中的新元素,如方块、物品、声音、药效等,在这里,我们传入`"enchantment"`来进行与附魔有关的修改: ```js StartupEvents.registry("enchantment",event => { // 在此进行修改 }) ``` ## 二、事件方法 | 方法 | 描述 | 返回值 | | ---------------------------------- | -------- | ------------------ | | create(字符串 附魔ID, 字符串 类型) | 注册物品 | EnchantmentBuilder | | create(字符串 附魔ID) | 注册物品 | EnchantmentBuilder | 其中"类型"参数默认情况下只支持`"basic"`类型。 ## 三、EnchantmentBuilderf | 方法 | 描述 | 备注 | 对应属性默认值 | | --------------------------------------------------- | -------------------------------- | ------------------------ | -------------- | | category(EnchantmentCategory 附魔类型) | 设置附魔支持的物品类型 | \[1] | "digger" | | rarity(Rarity 稀有度) | 设置附魔稀有度 | - | Rarity.COMMON | | damageProtection(DamageProtectionFunction 伤害保护) | 设置伤害保护 | 使用方法见下 | null | | damageBonus(DamageBonusFunction 伤害加成) | 设置伤害加成 | 使用方法见下 | null | | maxLevel(整形 等级) | 设置附魔最大等级 | - | 1 | | minLevel(整形 等级) | 设置附魔最小等级 | - | 1 | | maxCost(Int2IntFunction 设置花费) | 设置最大花费 | 使用方法见下 | null | | minCost(Int2IntFunction 设置花费) | 设置最小花费 | 使用方法见下 | null | | postHurt(PostFunction 受伤后事件) | 设置受伤后事件 | 使用方法见下 | null | | postAttack(PostFunction 攻击后事件) | 设置攻击后事件 | 使用方法见下 | null | | canEnchant(Object2BooleanFunction 物品检查) | 检查附魔条件 | 使用方法见下 | null | | undiscoverable() | 隐形附魔 | - | - | | curse() | 设置附魔类型为诅咒附魔 | - | - | | untradeable() | 设置附魔无法被村民卖出 | - | - | | uncommon() | 设置物品稀有度为UNCOMMON | 效果同rarity方法,下同 | - | | rare() | 设置物品稀有度为RARE | - | - | | veryRare() | 设置物品稀有度为VERY\_RARE | - | - | | vanishable() | 允许将这类附魔附加于可消耗物品上 | 效果同category方法,下同 | - | | crossbow() | 允许将这类附魔附加于弩上 | - | - | | trident() | 允许将这类附魔附加于三叉戟上 | - | - | | armor() | 允许将这类附魔附加于护甲上 | - | - | | armorHead() | 允许将这类附魔附加于头盔上 | - | - | | armorChest() | 允许将这类附魔附加于胸甲上 | - | - | | armorLegs() | 允许将这类附魔附加于护腿上 | - | - | | armorFeet() | 允许将这类附魔附加于靴子上 | - | - | | wearable() | 允许将这类附魔附加于可穿戴物品上 | - | - | | weapon() | 允许将这类附魔附加于武器上 | - | - | | bow() | 允许将这类附魔附加于弓箭上 | - | - | | fishingRod() | 允许将这类附魔附加于钓鱼竿上 | - | - | | breakable() | 允许将这类附魔附加于可破坏物品上 | - | - | | .displayName(字符串 显示名称) | 设置附魔显示名称 | - | - | \[1] | EnchantmentCategory值 | 描述 | | --------------------- | ------------ | | "basic" | 基础类 | | "armor\_head" | 头盔类 | | "armor\_chest" | 胸甲类 | | "armor\_legs" | 护腿类 | | "armor\_feet" | 靴子类 | | "armor" | 护甲类 | | "weapon" | 武器类 | | "digger" | 挖掘工具类 | | "bow" | 弓类 | | "crossbow" | 弩类 | | "fishing\_rod" | 钓鱼竿类 | | "trident" | 三叉戟类 | | "breakable" | 可破坏物品类 | | "vanishable" | 消耗物品类 | | "wearable" | 可穿戴类 | ## 四:示例 ```js StartupEvents.registry("enchantment",event => { // 正面附魔:水刃。可应用于武器上。当攻击对象类型为"WATER"时,给予伤害加成(level * 2),如果不是那么降低造成的伤害(level * -1) event.create("hydro_enchantment").weapon().maxLevel(5).damageBonus((level,mobType)=>{ // 检查怪物类型 if (mobType == "WATER"){ // 返回加成计算结果 return level * 2; }else{ // 返回加成计算结果 return level * -1; } }).displayName("水刃"); // 正面附魔:冰冻。可应用于木棍上。当使用附魔冰冻的木棍攻击时,给予受击目标缓慢药水效果。该效果具有冷却。 event.create("cryo_enchantment").canEnchant((itemStack)=>{ // 形参为要检查的itemStack // 设置该附魔只能应用于木棍上 if (itemStack.id == "minecraft:stick"){ // 返回检查结果(通过) return true; }; // 返回检查结果(未通过) return false; }).maxLevel(1).untradeable().postAttack((source,target,level)=>{ /* 三个形参分别为: LivingEntity 攻击源(附魔持有者) Entity 受击目标 整形 附魔等级 */ // 如果受击目标存活,那么给予其缓慢药水效果,并为发出者添加物品冷却。 if (target.isLiving()){ // 为受击目标添加药水效果 target.potionEffects.add("minecraft:slowness",3000); // 为攻击源添加物品冷却 source.addItemCooldown(source.getMainHandItem(),3000); } }).displayName("冰冻"); // 负面附魔:风化。可应用于盔甲上。当装备者受到伤害时,根据附魔等级给予其漂浮药水效果,并治疗攻击者。 event.create("anemo_enchantment").armor().maxLevel(5).curse().postHurt((target,attacker,level)=>{ /* 三个形参分别为: LivingEntity 受击者(附魔持有者) Entity 攻击源 整形 附魔等级 */ // 如果附魔持有者存活,那么根据附魔等级给予其漂浮药水效果。 if (target.isLiving()){ // 为附魔持有者添加药水效果 target.potionEffects.add("minecraft:levitation",2000 * level); } // 如果攻击者存活,那么根据附魔等级治疗攻击者。 if (attacker.isLiving()){ // 治疗攻击者 attacker.heal(level) } }).displayName("风化"); }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/fluid.md --- # 8 流体注册 *** ## 一、流体注册 ### 1、事件监听 通过向`StartupEvents.registry`事件传入不同的参数,其可用于在启动脚本中注册游戏中的新元素,如方块、物品、声音、药效等,在这里,我们传入`"fluid"`来进行与流体有关的修改: ```js StartupEvents.registry("fluid",event=>{ // code here }) ``` ### 2、事件方法 | 方法 | 描述 | 返回值 | | --------------------- | -------- | ------------ | | create(字符串 流体ID) | 注册流体 | FluidBuilder | ### 3、FluidBuilder | 方法 | 描述 | 备注 | | ----------------------------------- | ------------------------------------- | ---------------------------- | | builtinTextures() | 使用内置贴图 | - | | flowingTexture(ResourceLocation id) | 设置流动贴图 | - | | bucketColor(Color 颜色) | 设置流体桶的颜色 | 可使用16进制,形如`0x24acf2` | | noBucket() | 设置默认不生成桶 | - | | luminosity(整形 发光度) | 设置流体亮度 | 默认值0 | | gaseous() | 将当前流体转为气体 | - | | stillTexture(ResourceLocation id) | 设置静止贴图 | - | | viscosity(整形 粘稠度) | 设置粘稠度 | 默认值为1000 | | displayName(字符串 名称) | 设置流体名称 | - | | createAttributes() | 创建并返回ArchitecturyFluidAttributes | 食用教程见下 | | thickTexture(Color 颜色) | 设置流体颜色 | 可使用16进制,形如`0x24acf2` | | rarity(Rarity 稀有度) | 设置流体稀有度 | - | | color(Color 颜色) | 设置流体颜色(厚) | 可使用16进制,形如`0x24acf2` | | density(整形 密度) | 设置流体密度 | 默认值1000 | | noBlock() | 设置默认不注册流体方块 | - | | thinTexture(Color 颜色) | 设置流体颜色(薄) | 可使用16进制,形如`0x24acf2` | | temperature(整形 温度) | 设置流体温度 | 默认值300 | ### 4、ArchitecturyFluidAttributes 通过ArchitecturyFluidAttributes,你可以快捷修改流体属性 | 方法 | 描述 | 备注 | 默认值 | | ----------------------------------- | -------------------- | --------------------------------- | ------------- | | dropOff(整形 衰减值) | 设置流体衰减值 | 根据与源头方块距离降低流体level值 | 1 | | tickDelay(整形 延迟刻数) | 设置扩散延迟 | - | 5 | | explosionResistance(float 爆炸抗性) | 设置流体爆炸抗性 | - | 100.0F | | color(Color 颜色) | 设置流体颜色 | 可使用16进制,形如`0x24acf2` | 0xffffff | | luminosity(整形 发光度) | 设置流体亮度 | - | 0 | | density(整形 密度) | 设置流体密度 | - | 1000 | | temperature(整形 温度) | 设置流体温度 | - | 300 | | viscosity(整形 粘稠度) | 设置粘稠度 | - | 1000 | | lighterThanAir(布尔值 是否比空气轻) | 设置流体质量 | 为true时流体将不会向下流动 | false | | rarity(Rarity 稀有度) | 设置流体稀有度 | - | Rarity.COMMON | | fillSound(fillSound: SoundEvent) | 设置流体填充容器声音 | - | - | | emptySound(emptySound: SoundEvent) | 设置容器倒空流体声音 | - | - | ## 二、示例 ```js StartupEvents.registry("fluid",event=>{ let example_fluid = event.create("example_fluid").thinTexture(0xcc3e44).thickTexture(0xcc3e44).temperature(2000).bucketColor(0xcc3e44).displayName("Wudji"); let exampleAttributes = example_fluid.createAttributes(); exampleAttributes.dropOff(2); exampleAttributes.tickDelay(20); example_fluid.attributes = exampleAttributes; }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/fluid.md --- # 8 流体注册 *** ## 一、流体注册 ### 1、事件监听 通过向`StartupEvents.registry`事件传入不同的参数,其可用于在启动脚本中注册游戏中的新元素,如方块、物品、声音、药效等,在这里,我们传入`"fluid"`来进行与流体有关的修改: ```js StartupEvents.registry("fluid",event=>{ // code here }) ``` ### 2、事件方法 | 方法 | 描述 | 返回值 | | --------------------- | -------- | ------------ | | create(字符串 流体ID) | 注册流体 | FluidBuilder | ### 3、FluidBuilder | 方法 | 描述 | 备注 | | ----------------------------------- | ------------------------------------- | ---------------------------- | | builtinTextures() | 使用内置贴图 | - | | flowingTexture(ResourceLocation id) | 设置流动贴图 | - | | bucketColor(Color 颜色) | 设置流体桶的颜色 | 可使用16进制,形如`0x24acf2` | | noBucket() | 设置默认不生成桶 | - | | luminosity(整形 发光度) | 设置流体亮度 | 默认值0 | | gaseous() | 将当前流体转为气体 | - | | stillTexture(ResourceLocation id) | 设置静止贴图 | - | | viscosity(整形 粘稠度) | 设置粘稠度 | 默认值为1000 | | displayName(字符串 名称) | 设置流体名称 | - | | createAttributes() | 创建并返回ArchitecturyFluidAttributes | 食用教程见下 | | thickTexture(Color 颜色) | 设置流体颜色 | 可使用16进制,形如`0x24acf2` | | rarity(Rarity 稀有度) | 设置流体稀有度 | - | | color(Color 颜色) | 设置流体颜色(厚) | 可使用16进制,形如`0x24acf2` | | density(整形 密度) | 设置流体密度 | 默认值1000 | | noBlock() | 设置默认不注册流体方块 | - | | thinTexture(Color 颜色) | 设置流体颜色(薄) | 可使用16进制,形如`0x24acf2` | | temperature(整形 温度) | 设置流体温度 | 默认值300 | ### 4、ArchitecturyFluidAttributes 通过ArchitecturyFluidAttributes,你可以快捷修改流体属性 | 方法 | 描述 | 备注 | 默认值 | | ----------------------------------- | -------------------- | --------------------------------- | ------------- | | dropOff(整形 衰减值) | 设置流体衰减值 | 根据与源头方块距离降低流体level值 | 1 | | tickDelay(整形 延迟刻数) | 设置扩散延迟 | - | 5 | | explosionResistance(float 爆炸抗性) | 设置流体爆炸抗性 | - | 100.0F | | color(Color 颜色) | 设置流体颜色 | 可使用16进制,形如`0x24acf2` | 0xffffff | | luminosity(整形 发光度) | 设置流体亮度 | - | 0 | | density(整形 密度) | 设置流体密度 | - | 1000 | | temperature(整形 温度) | 设置流体温度 | - | 300 | | viscosity(整形 粘稠度) | 设置粘稠度 | - | 1000 | | lighterThanAir(布尔值 是否比空气轻) | 设置流体质量 | 为true时流体将不会向下流动 | false | | rarity(Rarity 稀有度) | 设置流体稀有度 | - | Rarity.COMMON | | fillSound(fillSound: SoundEvent) | 设置流体填充容器声音 | - | - | | emptySound(emptySound: SoundEvent) | 设置容器倒空流体声音 | - | - | ## 二、示例 ```js StartupEvents.registry("fluid",event=>{ let example_fluid = event.create("example_fluid").thinTexture(0xcc3e44).thickTexture(0xcc3e44).temperature(2000).bucketColor(0xcc3e44).displayName("Wudji"); let exampleAttributes = example_fluid.createAttributes(); exampleAttributes.dropOff(2); exampleAttributes.tickDelay(20); example_fluid.attributes = exampleAttributes; }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/event1/chateventexamples.md --- # 8 聊天事件 *** :::info **Warning:** This page may be outdated. Please verify the details with the latest updates or documentation. ::: 一个基础的例子:当有人在发送‘kubejs教程’时回复'请访问[https://www.mcbbs.net/thread-1207772-1-1.html'](https://www.mcbbs.net/thread-1207772-1-1.html) ``` onEvent('player.chat', function (event) { // 检测如果聊天内容为“kubejs教程” 执行命令, 忽略大小写 if (event.message.trim().equalsIgnoreCase('kubejs教程')) { // 将事件推迟1刻,否则服务器信息将会显示在玩家信息之前 event.server.scheduleInTicks(1, event.server, function (callback) { // 对每个人说以下内容,颜色为绿色。聊天信息为[Server] callback.data.tell(text.green('请访问h t tp s://www.mcbbs.net/thread-1207772-1-1.html'[/url])) // 下面的这种表述方法设置了聊天信息 callback.data.tell([Text.red('[Test]'),text.green('请访问h t tp s://www.mcbbs.net/thread-1207772-1-1.html')]) }) } }) ``` 另一个例子:监测到聊天信息时执行对应指令 ``` onEvent('player.chat', function (event) { if (event.message.startsWith('test')) { event.server.runCommandSilent('kick '+event.player.name+' test ') event.server.runCommandSilent(`say 已踢出玩家${event.player.name}`) event.cancel()//取消该事件,也就是说玩家的聊天信息不会显示 } }) ``` 利用取消的这一特性,你甚至还可以做到"伪造"聊天 下面这个例子在检测到发送消息的玩家带有 `rankexample` 时在其聊天信息前加上rank标识 ``` onEvent('player.chat',function (event){ let input = event.message.trim();//获取聊天信息 if(event.player.stages.has("rankexample")){ event.server.tell([Text.blue('[MVP--]').bold(), `<${event.player.name}> ${input}`]); event.cancel(); } }) ``` 注:更多关于gamestage的操作详见11-4章节,关于玩家、世界的操作详见第15章 --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/event1/chateventexamples.md --- # 8 聊天事件 *** :::info **Warning:** This page may be outdated. Please verify the details with the latest updates or documentation. ::: 一个基础的例子:当有人在发送‘kubejs教程’时回复'请访问[https://www.mcbbs.net/thread-1207772-1-1.html'](https://www.mcbbs.net/thread-1207772-1-1.html) ``` onEvent('player.chat', function (event) { // 检测如果聊天内容为“kubejs教程” 执行命令, 忽略大小写 if (event.message.trim().equalsIgnoreCase('kubejs教程')) { // 将事件推迟1刻,否则服务器信息将会显示在玩家信息之前 event.server.scheduleInTicks(1, event.server, function (callback) { // 对每个人说以下内容,颜色为绿色。聊天信息为[Server] callback.data.tell(text.green('请访问h t tp s://www.mcbbs.net/thread-1207772-1-1.html'[/url])) // 下面的这种表述方法设置了聊天信息 callback.data.tell([Text.red('[Test]'),text.green('请访问h t tp s://www.mcbbs.net/thread-1207772-1-1.html')]) }) } }) ``` 另一个例子:监测到聊天信息时执行对应指令 ``` onEvent('player.chat', function (event) { if (event.message.startsWith('test')) { event.server.runCommandSilent('kick '+event.player.name+' test ') event.server.runCommandSilent(`say 已踢出玩家${event.player.name}`) event.cancel()//取消该事件,也就是说玩家的聊天信息不会显示 } }) ``` 利用取消的这一特性,你甚至还可以做到"伪造"聊天 下面这个例子在检测到发送消息的玩家带有 `rankexample` 时在其聊天信息前加上rank标识 ``` onEvent('player.chat',function (event){ let input = event.message.trim();//获取聊天信息 if(event.player.stages.has("rankexample")){ event.server.tell([Text.blue('[MVP--]').bold(), `<${event.player.name}> ${input}`]); event.cancel(); } }) ``` 注:更多关于gamestage的操作详见11-4章节,关于玩家、世界的操作详见第15章 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/event1/server-auto-restart-example.md --- # 9 计划重启 *** :::info **Warning:** This page may be outdated. Please verify the details with the latest updates or documentation. ::: 当服务器启动时,你可以为其计划执行各种内容。你可以使用 callback.reschedule(时间) 来在指定时间内重复这个事件(括号内留空即为使用之前设定的时间)。 ``` onEvent('server.load', function (event) { event.server.schedule(117 * MINUTE, event.server, function (callback) { callback.data.tell('服务器将在3分钟后重启!') //callback.reschedule(2 * MINUTE) //两分钟以后再通知一次 }) event.server.schedule(120 * MINUTE, event.server, function (callback) { callback.data.runCommand('/stop')//这里后边不用加重复事件,因为服务端重启时会自动加载本段代码 }) }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.18.2/XPlusKubeJSTutorial/event1/server-auto-restart-example.md --- # 9 计划重启 *** :::info **Warning:** This page may be outdated. Please verify the details with the latest updates or documentation. ::: 当服务器启动时,你可以为其计划执行各种内容。你可以使用 callback.reschedule(时间) 来在指定时间内重复这个事件(括号内留空即为使用之前设定的时间)。 ``` onEvent('server.load', function (event) { event.server.schedule(117 * MINUTE, event.server, function (callback) { callback.data.tell('服务器将在3分钟后重启!') //callback.reschedule(2 * MINUTE) //两分钟以后再通知一次 }) event.server.schedule(120 * MINUTE, event.server, function (callback) { callback.data.runCommand('/stop')//这里后边不用加重复事件,因为服务端重启时会自动加载本段代码 }) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/9.-zhan-li-pin-biao.md --- # 9.战利品表 *** 推荐阅读:[Minecraft 原版模组入门教程 - 战利品表(作者ruhuasiyu)](https://www.mcbbs.net/plugin.php?id=link_redirect\&target=https%3A%2F%2Fzhangshenxing.gitee.io%2Fvanillamodtutorial%2F%23%E6%88%98%E5%88%A9%E5%93%81%E8%A1%A8) ## 一、事件监听 `ServerEvents`下有多个用于修改战利品表的子事件: | 事件 | 描述 | | -------------------------------- | -------------------------- | | `ServerEvents.blockLootTables` | 方块战利品表修改 | | `ServerEvents.chestLootTables` | 宝箱战利品表修改 | | `ServerEvents.giftLootTables` | 礼物战利品表修改\[1] | | `ServerEvents.entityLootTables` | 实体战利品表修改 | | `ServerEvents.fishingLootTables` | 钓鱼战利品表修改 | | `ServerEvents.genericLootTables` | 战利品表修改(按命名空间) | \[1]如猫、村民(村庄英雄)等。 以修改方块战利品表为例: ```js ServerEvents.blockLootTables(event =>{ // code here }) ``` ## 二、战利品表修改 KubeJS添加了一些方法来简化战利品表的修改。 这里以修改方块战利品表为例: | 事件方法 | 功能 | 补充说明 | | ---------------------------------------------------------- | -------------------------- | --------------------- | | addSimpleBlock(BlockStatePredicate 方块) | 快捷添加掉落自身的战利品表 | 可以传入tag、方块ID等 | | addBlock(BlockStatePredicate 方块,LootBuilder 战利品表) | 设置战利品表 | 见下 | | modifyBlock(BlockStatePredicate 方块,LootBuilder 战利品表) | 修改战利品表 | 见下 | | addJson(命名空间 战利品表ID, JsonObject 战利品表) | 直接添加json格式的战利品表 | - | 对于其他类型的战利品表修改,你只需要修改方法名,其他内容大同小异。例如实体对应的战利品表设置方法为`addEntity`,而钓鱼的则为`addFishing`,以此类推。 ## 三、LootBuilder LootBuilder添加了一系列用于修改战利品表的方法: | 🔎方法 | 功能 | 补充说明 | | --------------------------------------------------------- | ------------------------ | ------------------------------ | | addCondition(JsonObject 战利品表条件) | 添加战利品表条件 | - | | addFunction(JsonObject 战利品表函数) | 添加战利品表函数 | - | | addPool(LootBuilderPool 战利品池) | 添加战利品池 | - | | randomChance(概率) | 设置触发概率 | - | | randomChanceWithLooting(概率, 倍率) | 设置抢夺附魔掉落概率 | - | | survivesExplosion() | 设置战利品表不受爆炸影响 | - | | enchantWithLevels(NumberProvider 等级, 布尔值 是否为宝藏) | 设置附魔 | NumberProvider\[2] | | enchantRandomly(命名空间\[] 附魔项) | 随机附魔 | 随机从传入的附魔中选择进行附魔 | | killedByPlayer() | 设置由玩家击杀才掉落 | - | | ... | ... | ... | \[2] NumberProvider可以接受:`{min: 最小值, max: 最大值}`、`单个数字` 注意:上述附魔效果必定生效,如需对其进行限制,你需要添加战利品表条件。 ## 四、LootBuilderPool | 🔎方法 / 属性 | 功能 | 补充说明 | | --------------------------------------------------------- | ------------------------ | -------------------------------------- | | addItem(Itemstack 物品, 权重, 数量) | 添加目标物品 | 以下各项均对之生效。权重, 数量可留空。 | | 属性 rolls | 设置抽取次数 | - | | setBinomialRolls(n,p) | 设置抽取次数(二项分布) | n为总试验次数,p为单次概率 | | setUniformRolls(最小值, 最大值) | 设置抽取次数(均匀分布) | 最小->最大机会均等 | | addEntry(JsonObject 项目) | 添加项目 | - | | enchantWithLevels(NumberProvider 等级, 布尔值 是否为宝藏) | 设置附魔 | NumberProvider\[2] | | enchantRandomly(命名空间\[] 附魔项) | 随机附魔 | 随机从传入的附魔中选择进行附魔 | | randomChanceWithLooting(概率, 倍率) | 设置抢夺附魔掉落概率 | - | | survivesExplosion() | 设置战利品表不受爆炸影响 | - | | killedByPlayer() | 设置由玩家击杀才掉落 | - | | randomChance(概率) | 设置触发概率 | - | | randomChanceWithLooting(概率, 倍率) | 设置抢夺附魔掉落概率 | - | 注意,`LootBuilder`和`LootBuilderPool`中的条件在不相符时不会生效,例如挖掘方块时游戏不会检查是否是玩家击杀。 注意:上述附魔效果必定生效,如需对其进行限制,你需要添加战利品表条件。 ## 五、示例 ```js ServerEvents.blockLootTables(event =>{ // 简单方块战利品表(掉落自身)(覆盖原战利品表) event.addSimpleBlock("minecraft:spawner"); // 所有带有标签的树叶被破坏时掉落自身(覆盖原战利品表) event.addSimpleBlock("#minecraft:leaves"); // 修改泥土战利品表 // 效果:在玩家使用带有标签的锹挖掘泥土时,抽取战利品池1和3;使用钻石锹挖掘时,抽取战利品池1,2和3;使用其他挖掘时,抽取战利品池3。 event.addBlock("minecraft:dirt", loot=>{ // 添加战利品池,注意:每个战利品池会分别抽取 // 添加战利品池 1 loot.addPool(pool=>{ // 添加物品、函数、条件 pool.addItem("minecraft:iron_nugget") pool.addFunction({ "function": "minecraft:set_count", "count": { "min": 1, "max": 2 } }) pool.addCondition({ "condition": "minecraft:match_tool", "predicate": { "tag": "forge:tools/shovels" } }) }) // 添加战利品池 2 loot.addPool(pool=>{ // 添加物品(带权重和数量) pool.addItem("minecraft:flint",2,1); pool.addItem("minecraft:iron_ingot",1,1); pool.addCondition({ "condition": "minecraft:match_tool", "predicate": { "items": [ "minecraft:diamond_shovel" ] } }) // 设置生效概率和抽取次数。 pool.randomChance(0.5); pool.setBinomialRolls(3,0.3); }) // 添加战利品池 3 loot.addPool(pool=>{ pool.addItem("minecraft:dirt"); }) }) }) ServerEvents.genericLootTables(event =>{ // 你可以通过指令/loot give <玩家ID> loot wudji:kubejs_tutorial/example_loot_table 来获取此战利品表。 event.addGeneric("wudji:kubejs_tutorial/example_loot_table",loot=>{ // 添加战利品池 loot.addPool(pool=>{ // 设置抽取次数 pool.rolls = 1; // 添加物品 pool.addItem("minecraft:iron_axe",1,1) pool.addItem("minecraft:iron_pickaxe",1,1) pool.addItem("minecraft:iron_sword",1,1) // 随机附魔 pool.enchantRandomly(["minecraft:sharpness","minecraft:unbreaking","minecraft:fortune"]); }) loot.addPool(pool=>{ pool.rolls = 3; // 添加物品(随机数量) pool.addItem("minecraft:experience_bottle",2,{min:1,max:3}); pool.addItem("minecraft:golden_apple",1,1); // 设置概率 pool.randomChance(0.5) }) }) }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.19.2/XPlusKubeJSTutorial/game-contents/9.-zhan-li-pin-biao.md --- # 9.战利品表 *** 推荐阅读:[Minecraft 原版模组入门教程 - 战利品表(作者ruhuasiyu)](https://www.mcbbs.net/plugin.php?id=link_redirect\&target=https%3A%2F%2Fzhangshenxing.gitee.io%2Fvanillamodtutorial%2F%23%E6%88%98%E5%88%A9%E5%93%81%E8%A1%A8) ## 一、事件监听 `ServerEvents`下有多个用于修改战利品表的子事件: | 事件 | 描述 | | -------------------------------- | -------------------------- | | `ServerEvents.blockLootTables` | 方块战利品表修改 | | `ServerEvents.chestLootTables` | 宝箱战利品表修改 | | `ServerEvents.giftLootTables` | 礼物战利品表修改\[1] | | `ServerEvents.entityLootTables` | 实体战利品表修改 | | `ServerEvents.fishingLootTables` | 钓鱼战利品表修改 | | `ServerEvents.genericLootTables` | 战利品表修改(按命名空间) | \[1]如猫、村民(村庄英雄)等。 以修改方块战利品表为例: ```js ServerEvents.blockLootTables(event =>{ // code here }) ``` ## 二、战利品表修改 KubeJS添加了一些方法来简化战利品表的修改。 这里以修改方块战利品表为例: | 事件方法 | 功能 | 补充说明 | | ---------------------------------------------------------- | -------------------------- | --------------------- | | addSimpleBlock(BlockStatePredicate 方块) | 快捷添加掉落自身的战利品表 | 可以传入tag、方块ID等 | | addBlock(BlockStatePredicate 方块,LootBuilder 战利品表) | 设置战利品表 | 见下 | | modifyBlock(BlockStatePredicate 方块,LootBuilder 战利品表) | 修改战利品表 | 见下 | | addJson(命名空间 战利品表ID, JsonObject 战利品表) | 直接添加json格式的战利品表 | - | 对于其他类型的战利品表修改,你只需要修改方法名,其他内容大同小异。例如实体对应的战利品表设置方法为`addEntity`,而钓鱼的则为`addFishing`,以此类推。 ## 三、LootBuilder LootBuilder添加了一系列用于修改战利品表的方法: | 🔎方法 | 功能 | 补充说明 | | --------------------------------------------------------- | ------------------------ | ------------------------------ | | addCondition(JsonObject 战利品表条件) | 添加战利品表条件 | - | | addFunction(JsonObject 战利品表函数) | 添加战利品表函数 | - | | addPool(LootBuilderPool 战利品池) | 添加战利品池 | - | | randomChance(概率) | 设置触发概率 | - | | randomChanceWithLooting(概率, 倍率) | 设置抢夺附魔掉落概率 | - | | survivesExplosion() | 设置战利品表不受爆炸影响 | - | | enchantWithLevels(NumberProvider 等级, 布尔值 是否为宝藏) | 设置附魔 | NumberProvider\[2] | | enchantRandomly(命名空间\[] 附魔项) | 随机附魔 | 随机从传入的附魔中选择进行附魔 | | killedByPlayer() | 设置由玩家击杀才掉落 | - | | ... | ... | ... | \[2] NumberProvider可以接受:`{min: 最小值, max: 最大值}`、`单个数字` 注意:上述附魔效果必定生效,如需对其进行限制,你需要添加战利品表条件。 ## 四、LootBuilderPool | 🔎方法 / 属性 | 功能 | 补充说明 | | --------------------------------------------------------- | ------------------------ | -------------------------------------- | | addItem(Itemstack 物品, 权重, 数量) | 添加目标物品 | 以下各项均对之生效。权重, 数量可留空。 | | 属性 rolls | 设置抽取次数 | - | | setBinomialRolls(n,p) | 设置抽取次数(二项分布) | n为总试验次数,p为单次概率 | | setUniformRolls(最小值, 最大值) | 设置抽取次数(均匀分布) | 最小->最大机会均等 | | addEntry(JsonObject 项目) | 添加项目 | - | | enchantWithLevels(NumberProvider 等级, 布尔值 是否为宝藏) | 设置附魔 | NumberProvider\[2] | | enchantRandomly(命名空间\[] 附魔项) | 随机附魔 | 随机从传入的附魔中选择进行附魔 | | randomChanceWithLooting(概率, 倍率) | 设置抢夺附魔掉落概率 | - | | survivesExplosion() | 设置战利品表不受爆炸影响 | - | | killedByPlayer() | 设置由玩家击杀才掉落 | - | | randomChance(概率) | 设置触发概率 | - | | randomChanceWithLooting(概率, 倍率) | 设置抢夺附魔掉落概率 | - | 注意,`LootBuilder`和`LootBuilderPool`中的条件在不相符时不会生效,例如挖掘方块时游戏不会检查是否是玩家击杀。 注意:上述附魔效果必定生效,如需对其进行限制,你需要添加战利品表条件。 ## 五、示例 ```js ServerEvents.blockLootTables(event =>{ // 简单方块战利品表(掉落自身)(覆盖原战利品表) event.addSimpleBlock("minecraft:spawner"); // 所有带有标签的树叶被破坏时掉落自身(覆盖原战利品表) event.addSimpleBlock("#minecraft:leaves"); // 修改泥土战利品表 // 效果:在玩家使用带有标签的锹挖掘泥土时,抽取战利品池1和3;使用钻石锹挖掘时,抽取战利品池1,2和3;使用其他挖掘时,抽取战利品池3。 event.addBlock("minecraft:dirt", loot=>{ // 添加战利品池,注意:每个战利品池会分别抽取 // 添加战利品池 1 loot.addPool(pool=>{ // 添加物品、函数、条件 pool.addItem("minecraft:iron_nugget") pool.addFunction({ "function": "minecraft:set_count", "count": { "min": 1, "max": 2 } }) pool.addCondition({ "condition": "minecraft:match_tool", "predicate": { "tag": "forge:tools/shovels" } }) }) // 添加战利品池 2 loot.addPool(pool=>{ // 添加物品(带权重和数量) pool.addItem("minecraft:flint",2,1); pool.addItem("minecraft:iron_ingot",1,1); pool.addCondition({ "condition": "minecraft:match_tool", "predicate": { "items": [ "minecraft:diamond_shovel" ] } }) // 设置生效概率和抽取次数。 pool.randomChance(0.5); pool.setBinomialRolls(3,0.3); }) // 添加战利品池 3 loot.addPool(pool=>{ pool.addItem("minecraft:dirt"); }) }) }) ServerEvents.genericLootTables(event =>{ // 你可以通过指令/loot give <玩家ID> loot wudji:kubejs_tutorial/example_loot_table 来获取此战利品表。 event.addGeneric("wudji:kubejs_tutorial/example_loot_table",loot=>{ // 添加战利品池 loot.addPool(pool=>{ // 设置抽取次数 pool.rolls = 1; // 添加物品 pool.addItem("minecraft:iron_axe",1,1) pool.addItem("minecraft:iron_pickaxe",1,1) pool.addItem("minecraft:iron_sword",1,1) // 随机附魔 pool.enchantRandomly(["minecraft:sharpness","minecraft:unbreaking","minecraft:fortune"]); }) loot.addPool(pool=>{ pool.rolls = 3; // 添加物品(随机数量) pool.addItem("minecraft:experience_bottle",2,{min:1,max:3}); pool.addItem("minecraft:golden_apple",1,1); // 设置概率 pool.randomChance(0.5) }) }) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Upgrade/GlobalScope/Classes/AABB.md --- # AABB ## 介绍 AABB 全称 axis-aligned bounding box,译为轴对齐包围盒。 [包围盒](https://zh.wikipedia.org/wiki/%E5%8C%85%E5%9B%B4%E4%BD%93)就是将物体组合完全包容起来的一个封闭空间。 轴对齐就是指这个空间的边与轴(X、Y、Z)对齐,即为一个立方体。 在 MC 中,也可以粗略地看作**碰撞箱**。 ### 构造 原版所有碰撞检测,包含检测都用到了 AABB,所以先从构造开始介绍。 ::: code-group ```js [of] /* 你可以直接使用 new 来构造一个 AABB x1, y1, z1为立方体一个顶点的坐标;x2, y2, z2为立方体另一个顶点的坐标 这两个顶点在立方体的对角线上 类似地,start和end是一个长度为3的向量,作用与上面一样,都是指两个顶点的坐标 */ AABB.of(double x1, double y1, double z1,double x2, double y2, double z2); // KJS new $AABB(double x1, double y1, double z1, double x2, double y2, double z2); // 原版 new $AABB(Vec3 start, Vec3 end); ``` ```js [ofBlock(s)] // pos是一个BlockPos,基于此构造的AABB是这个坐标下完整方块的碰撞箱 AABB.ofBlock(BlockPos pos); // KJS new $AABB(BlockPos pos); // 原版 // KJS还提供了一个用于构造两个方块间AABB的方法 AABB.ofBlocks(BlockPos startPos, BlockPos endPos); // 等效于原版的: $AABB.encapsulatingFullBlocks(BlockPos startPos, BlockPos endPos); ``` ```js [ofSize] // 构造指定边长,指定中心的AABB AABB.ofSize(Vec3 center, double xSize, double ySize, double zSize); // 等效于下面 new AABB( center.x - xSize / 2.0, center.y - ySize / 2.0, center.z - zSize / 2.0, center.x + xSize / 2.0, center.y + ySize / 2.0, center.z + zSize / 2.0 ); ``` ::: 此外,由于 KJS 的 Wrapper,你可以直接使用`BlockPos`和数组代替 AABB。 ```js [] -> AABB.EMPTY; // 即AABB.of(0, 0, 0, 0, 0, 0) [2, 3, 4] -> AABB.ofSize(0, 2, 3, 4); [2, 3, 4, 6, 7, 8] -> AABB.of(2, 3, 4, 6, 7, 8); ``` ## 变化 通常来说,AABB 可能是需要随时变动的,这里介绍几种变化方法。 ::: code-group ```js [收缩] /* (不常用,不作介绍) 返回缩小指定量的AABB,正值使最大值减小,负值使最小值增大。 如果收缩量大于边长,则边将被包裹。 */ AABB.contract(double x, double y, double z); ``` ```js [扩展] /* 返回扩展指定量的AABB。正值使最大值增大,负值使最小值减小。 返回的AABB永远大于等于原AABB。 简单地说就是向各个轴增加长度。 */ AABB.expandTowards(double x, double y, double z); // 例子: let aabb = new AABB(0, 0, 0, 1, 1, 1); aabb.expandTowards(2, 2, 2) -> [0, 0, 0, 3, 3, 3] aabb.expandTowards(-2, -2, -2) -> [-2, -2, -2, 3, 3, 3] aabb.expandTowards(0, 1, -1) -> [0, 0, -1, 3, 2, 3] ``` ```js [扩大] /* 返回扩大指定量的AABB,负值会使AABB缩小。 由于最小值和最大值都发生了变化,边长将增加指定量的2倍。 如果扩大的值大于边长,则边将被包裹。 简单地说就是以中心使各个轴的长度增加/减小,类似于上一个expandTowards。 */ AABB.inflate(double x, double y, double z); AABB.inflate(double value); // 等效于AABB.inflate(value, value, value); AABB.deflate(double x, double y, double z); // 等效于AABB.inflate(-value, -value, -value); AABB.deflate(double value); // 等效于AABB.inflate(-value); // 例子: new AABB(0, 0, 0, 1, 1, 1).inflate(2, 2, 2) -> [-2, -2, -2, 3, 3, 3] new AABB(0, 0, 0, 6, 6, 6).inflate(-2, -2, -2) -> [2, 2, 2, 4, 4, 4] ``` ```js [移动] // 移动该AABB AABB.move(double x, double y, double z); AABB.move(Vec3 vec3); ``` ```js [其它] // 取AABB的边长 AABB.getSize(); // 三边边长平均数 AABB.getXSize(); AABB.getYSize(); AABB.getZSize(); // 取AABB的中心点 AABB.getCenter(); // 取两个AABB相交的部分 AABB.intersect(AABB other); // 判断两AABB是否相交 AABB.intersects(AABB other); AABB.intersects(double x1, double y1, double z1, double x2, double y2, double z2); AABB.intersects(Vec3 min, Vec3 max); // 以两个AABB的顶点作为新的AABB AABB.minmax(AABB other); // 判断一个坐标是否包含于AABB中 AABB.contains(Vec3 vec); AABB.contains(double x, double y, double z); ``` ::: ## 其它用法 这里介绍几种常用的关于 AABB 的用法。 ::: code-group ```js [取范围内实体] /* 第一个参数为排除的实体,返回的实体中不会包含它,可以为null。 第二个参数为AABB,也就是会返回这个范围内的实体。 第三个参数为谓词,用于选定指定的实体,可以为null。 */ Level.getEntities(Entity entity, AABB aabb, Predicate predicate); // 原版 Level.getEntities(Entity entity, AABB aabb); // 原版 // 类似于原版,但没有谓词匹配和实体 Level.getEntitiesWithin(AABB aabb); // KJS ``` ::: --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Upgrade/GlobalScope/Classes/AABB.md --- # AABB ## 介绍 AABB 全称 axis-aligned bounding box,译为轴对齐包围盒。 [包围盒](https://zh.wikipedia.org/wiki/%E5%8C%85%E5%9B%B4%E4%BD%93)就是将物体组合完全包容起来的一个封闭空间。 轴对齐就是指这个空间的边与轴(X、Y、Z)对齐,即为一个立方体。 在 MC 中,也可以粗略地看作**碰撞箱**。 ### 构造 原版所有碰撞检测,包含检测都用到了 AABB,所以先从构造开始介绍。 ::: code-group ```js [of] /* 你可以直接使用 new 来构造一个 AABB x1, y1, z1为立方体一个顶点的坐标;x2, y2, z2为立方体另一个顶点的坐标 这两个顶点在立方体的对角线上 类似地,start和end是一个长度为3的向量,作用与上面一样,都是指两个顶点的坐标 */ AABB.of(double x1, double y1, double z1,double x2, double y2, double z2); // KJS new $AABB(double x1, double y1, double z1, double x2, double y2, double z2); // 原版 new $AABB(Vec3 start, Vec3 end); ``` ```js [ofBlock(s)] // pos是一个BlockPos,基于此构造的AABB是这个坐标下完整方块的碰撞箱 AABB.ofBlock(BlockPos pos); // KJS new $AABB(BlockPos pos); // 原版 // KJS还提供了一个用于构造两个方块间AABB的方法 AABB.ofBlocks(BlockPos startPos, BlockPos endPos); // 等效于原版的: $AABB.encapsulatingFullBlocks(BlockPos startPos, BlockPos endPos); ``` ```js [ofSize] // 构造指定边长,指定中心的AABB AABB.ofSize(Vec3 center, double xSize, double ySize, double zSize); // 等效于下面 new AABB( center.x - xSize / 2.0, center.y - ySize / 2.0, center.z - zSize / 2.0, center.x + xSize / 2.0, center.y + ySize / 2.0, center.z + zSize / 2.0 ); ``` ::: 此外,由于 KJS 的 Wrapper,你可以直接使用`BlockPos`和数组代替 AABB。 ```js [] -> AABB.EMPTY; // 即AABB.of(0, 0, 0, 0, 0, 0) [2, 3, 4] -> AABB.ofSize(0, 2, 3, 4); [2, 3, 4, 6, 7, 8] -> AABB.of(2, 3, 4, 6, 7, 8); ``` ## 变化 通常来说,AABB 可能是需要随时变动的,这里介绍几种变化方法。 ::: code-group ```js [收缩] /* (不常用,不作介绍) 返回缩小指定量的AABB,正值使最大值减小,负值使最小值增大。 如果收缩量大于边长,则边将被包裹。 */ AABB.contract(double x, double y, double z); ``` ```js [扩展] /* 返回扩展指定量的AABB。正值使最大值增大,负值使最小值减小。 返回的AABB永远大于等于原AABB。 简单地说就是向各个轴增加长度。 */ AABB.expandTowards(double x, double y, double z); // 例子: let aabb = new AABB(0, 0, 0, 1, 1, 1); aabb.expandTowards(2, 2, 2) -> [0, 0, 0, 3, 3, 3] aabb.expandTowards(-2, -2, -2) -> [-2, -2, -2, 3, 3, 3] aabb.expandTowards(0, 1, -1) -> [0, 0, -1, 3, 2, 3] ``` ```js [扩大] /* 返回扩大指定量的AABB,负值会使AABB缩小。 由于最小值和最大值都发生了变化,边长将增加指定量的2倍。 如果扩大的值大于边长,则边将被包裹。 简单地说就是以中心使各个轴的长度增加/减小,类似于上一个expandTowards。 */ AABB.inflate(double x, double y, double z); AABB.inflate(double value); // 等效于AABB.inflate(value, value, value); AABB.deflate(double x, double y, double z); // 等效于AABB.inflate(-value, -value, -value); AABB.deflate(double value); // 等效于AABB.inflate(-value); // 例子: new AABB(0, 0, 0, 1, 1, 1).inflate(2, 2, 2) -> [-2, -2, -2, 3, 3, 3] new AABB(0, 0, 0, 6, 6, 6).inflate(-2, -2, -2) -> [2, 2, 2, 4, 4, 4] ``` ```js [移动] // 移动该AABB AABB.move(double x, double y, double z); AABB.move(Vec3 vec3); ``` ```js [其它] // 取AABB的边长 AABB.getSize(); // 三边边长平均数 AABB.getXSize(); AABB.getYSize(); AABB.getZSize(); // 取AABB的中心点 AABB.getCenter(); // 取两个AABB相交的部分 AABB.intersect(AABB other); // 判断两AABB是否相交 AABB.intersects(AABB other); AABB.intersects(double x1, double y1, double z1, double x2, double y2, double z2); AABB.intersects(Vec3 min, Vec3 max); // 以两个AABB的顶点作为新的AABB AABB.minmax(AABB other); // 判断一个坐标是否包含于AABB中 AABB.contains(Vec3 vec); AABB.contains(double x, double y, double z); ``` ::: ## 其它用法 这里介绍几种常用的关于 AABB 的用法。 ::: code-group ```js [取范围内实体] /* 第一个参数为排除的实体,返回的实体中不会包含它,可以为null。 第二个参数为AABB,也就是会返回这个范围内的实体。 第三个参数为谓词,用于选定指定的实体,可以为null。 */ Level.getEntities(Entity entity, AABB aabb, Predicate predicate); // 原版 Level.getEntities(Entity entity, AABB aabb); // 原版 // 类似于原版,但没有谓词匹配和实体 Level.getEntitiesWithin(AABB aabb); // KJS ``` ::: --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/PonderJS/Standard/Block.md --- # About Blocks This page introduces additional block-related functions. ## Place Blocks According to [scene\_world\_function.md](../Internal/SceneWorldFunction), there are two placement APIs: ```js setBlocks(arg0: Internal.Selection_, arg1: boolean_, arg2: Internal.BlockState_): void_; setBlocks(arg0: Internal.Selection_, arg1: Internal.BlockState_): void_; setBlocks(arg0: Internal.Selection_, arg1: Internal.BlockState_, arg2: boolean_): void_; setBlock(arg0: BlockPos_, arg1: Internal.BlockState_, arg2: boolean_): void_; ``` **setBlocks** was already introduced in [Ponder](../Ponder.md), so this section focuses on **setBlock**. The first parameter type for **setBlocks** and **setBlock** is different. **setBlocks** can place many blocks at once using selections like `[3, 1, 1, 1, 1, 3]`, while **setBlock** places only one block at a time. ```js // Place an iron block at [2, 1, 2]. If a block already exists there, destroy it first. scene.world.setBlock([2, 1, 2], "minecraft:iron_block", true); ``` ## Destroy Blocks Example: ```js // Destroy the block at [2, 1, 2] scene.world.destroyBlock([2, 1, 2]); ``` If there is no block at that position, no destroy particles will appear. > The following code is excerpted from [scene\_world\_function.md](../Internal/SceneWorldFunction) ```js destroyBlock(arg0: BlockPos_): void_; ``` The first parameter type is the same as `setBlock`, meaning `single position only; no range destroy`. ## Replace Blocks Example: ```js // Replace the block at [2, 1, 2] with an iron block and show destroy particles scene.world.replaceBlocks([2, 1, 2], "minecraft:iron_block", true); // Replace all blocks in the rectangular area defined by [2, 1, 3] and [3, 1, 3] with iron blocks, without destroy particles scene.world.replaceBlocks([2, 1, 3, 3, 1, 3], "minecraft:iron_block", false); ``` If there is no block in a target position, it will not be replaced and no destroy particles will appear. > The following code is excerpted from [scene\_world\_function.md](../Internal/SceneWorldFunction) ```js replaceBlocks(arg0: Internal.Selection_, arg1: Internal.BlockState_, arg2: boolean_): void_; ``` The first parameter type is the same as `setBlocks`, so it `supports range replacement`. ## Set Block State Example: ```js scene.world.setBlocks([2, 1, 2], 'create:cogwheel', true); // Set the block at [2, 1, 2] with axis=x and waterlogged=true, without destroy particles scene.world.modifyBlock([2, 1, 2], state => state.with("axis", "x").with("waterlogged", "true"), false); // Set the block at [2, 1, 2] to an oak trapdoor (open=true style), with destroy particles scene.world.modifyBlock([2, 1, 2], () => Block.id("minecraft:oak_trapdoor").with("type", "top"), true); // Set all blocks in the rectangular area defined by [2, 1, 3] and [3, 1, 3] to oak trapdoors, with destroy particles scene.world.modifyBlocks([2, 1, 3, 3, 1, 3], () => Block.id("minecraft:oak_trapdoor").with("type", "top"), true); ``` > The following code is excerpted from [scene\_world\_function.md](../Internal/SceneWorldFunction) ```js modifyBlocks(arg0: Internal.Selection_, arg1: Internal.BlockStateFunction_, arg2: boolean_): void_; modifyBlocks(arg0: Internal.Selection_, arg1: boolean_, arg2: Internal.BlockStateFunction_): void_; modifyBlocks(arg0: Internal.Selection_, arg1: Internal.BlockStateFunction_): void_; modifyBlock(arg0: BlockPos_, arg1: Internal.BlockStateFunction_, arg2: boolean_): void_; ``` ## Set Block NBT Example: ```js scene.world.setBlocks([2, 1, 2], 'create:cogwheel', true); // Set Speed=16 in the block NBT at [2, 1, 2] scene.world.modifyBlockEntityNBT([2, 1, 2], nbt => { nbt.Speed = 16 }); // Same as above; the purpose of the second parameter is currently unclear scene.world.modifyBlockEntityNBT([2, 1, 2], true, nbt => { nbt.Speed = 16 }); ``` > The following code is excerpted from [scene\_world\_function.md](../Internal/SceneWorldFunction) ```js modifyBlockEntityNBT(arg0: Internal.Selection_, arg1: boolean_, arg2: Internal.Consumer_): void_; modifyBlockEntityNBT(arg0: Internal.Selection_, arg1: Internal.Consumer_): void_; ``` ## Other Functions (To Be Documented) Waiting for future documentation updates. > The following code is excerpted from [scene\_world\_function.md](../Internal/SceneWorldFunction) ```js modifyBlockEntity(arg0: BlockPos_, arg1: T, arg2: Internal.Consumer_): void_; restoreBlocks(arg0: Internal.Selection_): void_; glueBlockOnto(arg0: BlockPos_, arg1: Internal.Direction_, arg2: Internal.ElementLink_): void_; cycleBlockProperty(arg0: BlockPos_, arg1: Internal.Property_): void_; incrementBlockBreakingProgress(arg0: BlockPos_): void_; ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/PonderJS/Standard/Enitity.md --- # About Entities This page introduces additional entity-related functions. ## Create Entities Example: ```js // Create a minecraft:sheep entity at [2.5, 1, 2.5] and assign it to _sheep var _sheep = scene.world.createEntity("minecraft:sheep", [2.5, 1, 2.5]); // Create a minecraft:sheep entity at [2.5, 1, 2.5] and set its name to jeb_ scene.world.createEntity("minecraft:sheep", [2.5, 1, 2.5], event => { event.setCustomName("jeb_"); }); ``` Return type: **Internal.ElementLink\** > The following code is excerpted from [scene\_world\_function.md](../Internal/SceneWorldFunction) ```js createEntity(arg0: Internal.EntityType_\, arg1: Vec3d_, arg2: Internal.Consumer_\): Internal.ElementLink\; createEntity(arg0: Internal.EntityType_\, arg1: Vec3d_): Internal.ElementLink\; createEntity(arg0: Internal.Function_\): Internal.ElementLink\; ``` ## Modify Entities Example: ```js // Create a minecraft:sheep entity at [2.5, 1, 2.5] and assign it to _sheep var _sheep = scene.world.createEntity("minecraft:sheep", [2.5, 1, 2.5]); // Modify entity _sheep scene.world.modifyEntity(_sheep, event => { // Set its name to jeb_ event.setCustomName("jeb_"); // Show hurt animation event.animateHurt(3); // Move it by [1, 0, 1] using self movement event.move("self", [1, 0, 1]); }); ``` > The following code is excerpted from [scene\_world\_function.md](../Internal/SceneWorldFunction) ```js modifyEntity(arg0: Internal.ElementLink_\, arg1: Internal.Consumer_\): void_; ``` ## Create Item Entities Example: ```js // Create a minecraft:iron_ingot item entity at [2, 1, 2] with initial velocity [0.5, 0, 0] scene.world.createItemEntity([2, 1, 2], [0.5, 0, 0], 'minecraft:iron_ingot'); ``` Below are **(\[2, 1, 2], \[0.5, 0, 0])** and **(\[3, 1, 2], \[0, 0, 0])**, respectively. ![ItemEntity\_move](/imgs/PonderJS/ItemEntity_move.gif) Return type: **Internal.ElementLink\** > The following code is excerpted from [scene\_world\_function.md](../Internal/SceneWorldFunction) ```js createItemEntity(arg0: Vec3d_, arg1: Vec3d_, arg2: Internal.ItemStack_): Internal.ElementLink\; ``` ## Remove Entities Example: ```js // Create a minecraft:sheep entity at [2.5, 1, 2.5] and assign it to _sheep var _sheep = scene.world.createEntity("minecraft:sheep", [2.5, 1, 2.5]); // Remove entity _sheep scene.world.removeEntity(_sheep); ``` > The following code is excerpted from [scene\_world\_function.md](../Internal/SceneWorldFunction) ```js removeEntity(arg0: Internal.ElementLink_\): void_; ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/PonderJS/Standard/ShowAndHide.md --- # About Showing and Hiding This page introduces additional functions for showing and hiding blocks. ## Show Blocks ```js // Show blocks in the rectangular area defined by [2, 1, 1] and [1, 1, 2], // using a top-down falling animation, and mark that area as shown scene.world.showSection([2, 1, 1, 1, 1, 2], Direction.down); // Show the block at [2, 1, 1] with a top-down falling animation, // and mark it as shown scene.world.showSection([2, 1, 1], Direction.down); // Same as above scene.world.showSection([2, 1, 1], Facing.down); // Same as above scene.world.showSection([2, 1, 1], 'down'); ``` About directions: [Direction & Facing](../Internal/Facing.md) > The following code is excerpted from [scene\_world\_function.md](../Internal/SceneWorldFunction) ```js showSection(arg0: Internal.Selection_, arg1: Internal.Direction_): void_; ``` ## Hide Blocks ```js // Hide the block at [2, 1, 1] with a top-down falling animation // and mark it as hidden scene.world.hideSection([2, 1, 1], 'down'); ``` > The following code is excerpted from [scene\_world\_function.md](../Internal/SceneWorldFunction) ```js hideSection(arg0: Internal.Selection_, arg1: Internal.Direction_): void_; ``` ## Other Functions (To Be Documented) Waiting for future documentation updates. > The following code is excerpted from [scene\_world\_function.md](../Internal/SceneWorldFunction) ```js hideIndependentSection(arg0: Internal.ElementLink_, arg1: Internal.Direction_, arg2: number): void_; configureCenterOfRotation(arg0: Internal.ElementLink_, arg1: Vec3d_): void_; showIndependentSectionImmediately(arg0: Internal.Selection_): Internal.ElementLink; showIndependentSection(arg0: Internal.Selection_, arg1: Internal.Direction_): Internal.ElementLink; hideIndependentSectionImmediately(arg0: Internal.ElementLink_): void_; glueBlockOnto(arg0: BlockPos_, arg1: Internal.Direction_, arg2: Internal.ElementLink_): void_; showSectionAndMerge(arg0: Internal.Selection_, arg1: Internal.Direction_, arg2: Internal.ElementLink_): void_; makeSectionIndependent(arg0: Internal.Selection_): Internal.ElementLink; showIndependentSection(arg0: Internal.Selection_, arg1: Internal.Direction_, arg2: number): Internal.ElementLink; moveSection(arg0: Internal.ElementLink_, arg1: Vec3d_, arg2: number): void_; hideIndependentSection(arg0: Internal.ElementLink_, arg1: Internal.Direction_): void_; rotateSection(arg0: Internal.ElementLink_, arg1: number, arg2: number, arg3: number, arg4: number): void_; ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSBasic/Customs/ItemType/MusicDisc.md --- # Add a Music Disc This chapter shows how to create a music disc that can play audio. All JS code in this chapter is placed in `startup_scripts` and `server_scripts`. ## Add a Sound Event Before adding the disc item, you need to register the sound first. Otherwise, the disc has no playable audio. ```js StartupEvents.registry("sound_event", (event) => { event.create("meng:music.my_music") }) ``` That one line is enough to register the sound event. The argument is the sound id. ### Note: In 1.21.1+ In 1.21.1+, if you want to register a music disc, you also need this `server` registry step to register the audio as a jukebox song. ```js ServerEvents.registry('jukebox_song', (event) => { event.create('music_id') .song('sound_event_id', time) // Example event.create('meng:my_music') .song("meng:music.my_music", 103) }) ``` `music_id` is the id of this jukebox song entry. It can be any id, but keep it consistent with later usage. `sound_event_id` is the sound id you registered in the startup event. `time` is the playback duration. ## Register the Item Now you can register the music disc item. ```js StartupEvents.registry("item", (event) => { event.create("meng:my_music_disc", "music_disc") .song("meng:music.my_music", 103) .tag("music_discs") }) ``` In `.song()`, the first parameter is the registered sound id, and the second is duration (in seconds). `.tag("music_discs")` is **required**. It tells the game this item belongs to the disc tag so jukeboxes can use it correctly. ### Note: In 1.21.1+ ```js StartupEvents.registry('item', (event) => { event.create('item_id') .jukeboxPlayable('music_id', true) // Example event.create('meng:my_music') .jukeboxPlayable('meng:my_music', true) }) ``` `item_id` is the item id. Do not confuse it with the jukebox song id above. You can keep them the same to avoid confusion. `music_id` is the same `music_id` used when registering the jukebox song. Since Minecraft 1.20.5 introduced [item stack components](https://minecraft.wiki/w/Data_component_format), almost any item can be made jukebox-playable. ## Prepare the Audio After registering the sound and disc, add the actual audio file. First, use this website: [mp3->ogg](https://audio.online-convert.com/convert-to-ogg) Minecraft sound files use `.ogg`, so convert your mp3 to ogg. ### Create the Sound Folder Then create folders and place your audio file there. Put the ogg file under `kubejs/assets/meng/sounds`. Here, `meng` is the namespace (the part before `:`). If you do not set a custom namespace, use `kubejs/assets/kubejs/sounds`. Rename the file. It is best to match the part after `music.` in your id (custom names work, but are not recommended). In this example the file is `my_music.ogg`, full path `kubejs/assets/meng/sounds/my_music.ogg`. ### Configure `sounds.json` Go to `kubejs/assets/`, create `sounds.json`, then edit it. [For full details, see mcwiki](https://zh.minecraft.wiki/w/Sounds.json?variant=zh-cn) ```json { // Registered sound id "music.my_music": { // Fixed format "sounds": [ { // Sound path folder, i.e. kubejs/assets//sounds above "name": "meng:my_music", // Stream output, recommended true for music discs "stream": true } ] }, } ``` ### In 1.21.1+ ```json { // 'meng:my_music' here refers to the `music_id` used when registering jukebox_song "music.my_music": { // Fixed format "sounds": [ { // Sound path folder, i.e. kubejs/assets/meng/sounds above "name": "meng:my_music", // Stream output, recommended true for music discs "stream": true } ] }, } ``` [About the `sounds.json` file structure](../../../Digression/SoundsJson) ## Localization and Texture Next, add localization text and item texture. Go to `kubejs/assets/meng/lang` and create `zh_ch.json` (or open it if it already exists). Add translations: ```json { "item.meng.my_music": "Music Disc", "item.meng.my_music.desc": "My Music - Private Artist" } ``` ### In 1.21.1+ ```json { "item.meng.mymusic": "Music Disc", "jukebox_song.meng.mymusic": "My Music - Private Artist", "sound.meng.mymusic": "My Music" } ``` The first key is the text for the disc item itself. The second key is the song text shown for the disc. For texture, put your disc image in `kubejs/assets/meng/textures/item` and name it `my_music.png` (matching the item id). ## Notes and Audio Download If everything above is set correctly (disc inserts into jukebox, no script errors) but there is still no sound, try the sample audio provided by Gu Meng: [my\_music.ogg](https://gitee.com/gumengmengs/kubejs-course/blob/main/files/my_music.ogg). If the link is unavailable, contact Gu Meng on [Bilibili](https://space.bilibili.com/16632546). --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSBasic/Tags.md --- # Add and Remove Tags [Tag introduction](../Digression/Tag.md) In-game, hold **oak log** and run `/kjs hand`. Besides the item id, you will also see ids that start with `#`. These are tag ids. You can think of tags as "groups". For example, `#minecraft:logs` is the group for all log items. Common tag types are block tags, fluid tags, and item tags. You can modify existing tags like this: ```js ServerEvents.tags("block",event=>{}) ServerEvents.tags("fluid",event=>{}) ServerEvents.tags("item",event=>{}) ``` Choose the tag type based on your use case. ## Example ```js ServerEvents.tags("item",event=>{ // Remove oak log from the logs tag event.remove("minecraft:logs",['minecraft:oak_log']); // Remove all entries from the planks tag event.removeAll('minecraft:planks'); // Add bedrock to the logs tag event.add("minecraft:logs",['minecraft:bedrock']); }) ``` The example above only shows item tag changes. Other tag types work the same way, using `remove`, `removeAll`, and `add`. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSBasic/Customs/ItemRegistry.md --- # Add Custom Items KubeJS can register items in `startup_scripts`. Note: all custom registration changes are not hot-reloadable. Restart the game after editing. ## Basic Syntax ```js StartupEvents.registry("item",event=>{ event.create("meng:my_item","basic") }) ``` Creating an item is simple and can be done in one line. In `create`, the first argument is the item id. If you write `"namespace:id"`, the part before `:` is your namespace (similar to mod id), and the part after `:` is the item id. If you only write `"id"` without a namespace, KubeJS defaults to `kubejs:id`. The second argument in `create` is the item type. ## Item Types | Type Arg | Use | Description | Example | | :-----------------: | :---------: | :-----------------------------------: | :---------------------------------------: | | `basic` | Basic item | - | - | | `basic` | Basic item | - | [Add food](./ItemType/food) | | `music_disc` | Music disc | Playable in jukebox | [Add music disc](./ItemType/MusicDisc) | | `smithing_template` | Smithing template | - | - | | `helmet` | Helmet | - | [Add armor]() | | `chestplate` | Chestplate | - | Same as above | | `leggings` | Leggings | - | Same as above | | `boots` | Boots | - | Same as above | | `axe` | Axe | - | [Add tools](./ItemType/tools) | | `hoe` | Hoe | - | Same as above | | `pickaxe` | Pickaxe | - | Same as above | | `shovel` | Shovel | - | Same as above | | `sword` | Sword | - | Same as above | | `shears` | Shears | - | [Add shears]() | ## Common Methods The methods below are available to all item types. Armor/tools and other special categories may have extra methods in their own chapters. ### Frequently Used Methods | Method Call | Args | Purpose | Return Type | | :------------------------------------------------------------------: | :------: | :-----------------------------------------------------: | :---------: | | maxDamage(int) | -> | Set max durability | this | | food(Consumer\) | -> | [Set item as food](./ItemType/food) | this | | maxStackSize(int) | -> | Set max stack size (can exceed 64, usually not advised) | this | | fireResistant(bool) | -> | Set whether item is fire resistant | this | | displayName(string) | -> | Set display name when no lang entry exists | this | | tag(ResourceLocation) | -> | Add item tag | this | | texture(string) | -> | Set item texture path | this | | unstackable() | - | Make item unstackable | this | | burnTime(int) | - | Set burn time in ticks (`0` means not burnable) | this | | modifyAttribute(ResourceLocation,string,double,AttributeModifier) | ~ | Apply attribute modifiers | this | | createItemProperties() | - | Create item properties object | Item | ### Methods Triggered While Using the Item | Method Call | Args | Purpose | Return Type | | :-----------------------------------------------: | :------: | :------------------------------------------------------: | :---------: | | use(ItemBuilder$UseCallback) | -> | Called when player starts using the item | this | | releaseUsing(ItemBuilder$ReleaseUsingCallback) | -> | Called when player releases right-click before finish | this | | finishUsing(ItemBuilder$FinishUsingCallback) | -> | Called when item use completes | this | | useDuration(ToIntFunction\) | ~ | Set/use item use duration | this | | useAnimation(UseAnim) | ~ | Set use animation | this | ### Rendering-Related Methods | Method Call | Args | Purpose | Return Type | | :-------------------------------------: | :------: | :------------------------------------------: | :---------: | | modelJson(JsonObject) | -> | Set model JSON (possible, but not recommended) | this | | parentModel(string) | ~ | Set parent model | this | | textureJson(JsonObject) | -> | Set texture JSON | this | | barWidth(ToIntFunction\) | ~ | Durability bar width logic | this | | barColor(Function\,Color) | ~ | Durability bar color logic | this | | glow(bool) | -> | Set enchanted glint effect | this | | tooltip(Component) | -> | Set item tooltip content | this | ### Other Methods | Method Call | Args | Purpose | Return Type | | :-------------------------------------------------------: | :---------------------------------: | :-------------------------: | :--------------: | | hurtEnemy(Predicate\) | -> | Trigger when item hits mob | this | | rarity(Rarity) | [Rarity](../../Digression/Rarity) | Set item rarity | this | | getRegistryType() | - | Get registry type | RegistryInfo\ | | generateAssetJsons(AssetJsonGenerator) | ? | ? | - | | generateDataJsons(DataJsonGenerator) | ? | ? | - | | name(NameCallback) | ~ | Dynamic item name | this | | color(int,ItemTintFunction) | ? | ? | this | | color(ItemTintFunction) | ? | ? | this | | transformObject(Item) | ~ | ~ | this | | subtypes(Function\>) | ~ | ? | this | | containerItem(ResourceLocation) | ~ | Set container item | this | ## Simple Item Registration Helper Pattern **Note: adapt this pattern based on your own style.** ```js StartupEvents.registry("item", (event) => { // ModID declaration. If you want default "kubejs", remove this variable. const MODID = "meng:" /* * Define items * Add a comma after each [] entry when adding more items * Keep the format strict: * [itemId, rarityType, hasGlow] */ let itemRegisters = [ ["example_item", "common", false], ] itemRegisters.forEach(([name, rarity, glow]) => { event.create(MODID + name) // declare id .rarity(rarity) // rarity .glow(glow) // enchanted glint .tag(MODID + "items") // add item tag (optional) }) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSBasic/Customs/ItemType/Food.md --- # Add Food This chapter introduces how to add food items using the item registry event. All JS code in this chapter is under the `startup_scripts` folder. ## Basic Syntax ```js StartupEvents.registry("item", event => { event.create("meng:my_food") .food(foodBuilder=>{}) }) ``` This is the most basic syntax. It marks the item as food, then you can configure food-related parameters. ## Food Builder Methods | Method Name | Parameter | Usage | Note | | :------------------------------------: | :-------------------: | :----------------------------: | :----------------------: | | saturation(float) | Saturation | Set food saturation multiplier | `value * hunger = sat` | | hunger(integer) | Hunger | Set hunger restored | Hunger bar ("drumsticks")| | meat() | - | Mark as meat | Can be eaten by wolves | | alwaysEdible() | - | Always edible | Eat even at full hunger | | fastToEat() | - | Faster eating speed | Eat quickly | | removeEffect(MobEffect) | Potion effect | Remove one effect after eating | - | | eaten(Consumer\) | After-eat event | Trigger callback | - | | effect(ResourceLocation,int,int,float) | [effect args](#effect)| Add effect when eaten | - | | build() | - | Return `FoodProperties` | - | [Difference between saturation and hunger](../../../Digression/SaturationHunger) ### effect ``` effect( ResourceLocation, potion effect id int, duration in ticks int, effect amplifier (actual level is n+1) float, chance to apply effect (1 = 100%) ) ``` ## Potion Effect Settings ### Add a Potion Effect ```js StartupEvents.registry("item", event => { event.create("meng:my_food") .food(foodBuilder=>{ foodBuilder.effect("minecraft:speed",20*20,0,0.5) }) }) ``` When the player eats this item, it gives Speed I for 20 seconds with a 50% chance. ### Remove a Potion Effect ```js StartupEvents.registry("item", event => { event.create("meng:my_food2") .food(foodBuilder=>{ foodBuilder.removeEffect("speed") }) }) ``` This seems to fail in practice (it may not work as expected). ## After-Eat Event ```js const { $Player } = require("packages/net/minecraft/world/entity/player/$Player") StartupEvents.registry("item", event => { event.create("meng:my_food3") .food(foodBuilder=>{ foodBuilder.eaten(foodEatenEvent=>{ /** * @type {$Player} */ let player = foodEatenEvent.getPlayer() if (foodEatenEvent.getPlayer() != null){ player.give("bowl") } }) }) }) ``` If the eater is a player, return a bowl to them (the check is to avoid non-player entities). ### Methods Available in the Event | Method Name | Return Type | | :---------: | :-------------: | | getItem() | ItemStack | | getEntity() | Entity | | getPlayer() | Player | | getLevel() | Level | | getServer | MinecraftServer | ## Simple Example ```js event.create("meng:my_food4") .food(foodBuilder=>{ foodBuilder.hunger(10) // Restores 5 hunger icons ("drumsticks") foodBuilder.saturation(0.5) // Saturation is 10 * 0.5 = 5 foodBuilder.meat() // Mark as meat, can be eaten by wolves foodBuilder.alwaysEdible() // Can be eaten even when not hungry foodBuilder.fastToEat() // Make it quick to eat }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSBasic/Customs/EnchantRegistry.md --- # Adding Enchantments In KubeJS you can add enchantments very easily. Important: write these scripts in the `startup_scripts` folder. ```js StartupEvents.registry("enchantment",event =>{ event.create("meng:my_enchantment") }) ``` After running the code above, an enchanted book will exist in-game. However, it cannot be obtained directly from the enchanting table by default; you typically need help from other mods if you want it to appear there. Next, you can configure properties on the enchantment. # Adding Properties ## Setting What Items Can Be Enchanted | Method | Args | Meaning | | :-----------: | :------: | :--------------------------------------------: | | armor() | none | Enchant armor | | armorHead() | none | Enchant helmets | | armorChest() | none | Enchant chestplates | | armorFeet() | none | Enchant boots | | armorLegs() | none | Enchant leggings | | fishingRod() | none | Enchant fishing rods | | bow() | none | Enchant bows | | crossbow() | none | Enchant crossbows | | trident() | none | Enchant tridents | | breakable() | none | Unknown | | vanishable() | none | Unknown | | wearable() | none | Enchant wearable items in general | | weapon() | none | Enchant weapons | | category(str) | category | [Set a custom category](#enchantable-item-categories) | If you call `armor()` and then call `bow()`, it does not mean "armor AND bow". The later call overrides the earlier one. Be careful not to call multiple category setters and end up with only the last one taking effect. You may notice there is no explicit "digger" (tools) method. That's because if you do not call any category setter, the default is `digger`. In most cases, you can just use these helper methods and you do not need to call `category(...)`. ### Enchantable Item Categories | Category | Description | | :----------: | :---------- | | fishing\_rod | Fishing rod | | wearable | Wearable | | trident | Trident | | crossbow | Crossbow | | armor\_chest | Chestplate | | vanishable | Unknown | | bow | Bow | | digger | Digger/tools | | weapon | Weapon | | armor | Armor | | armor\_legs | Leggings | | armor\_head | Helmet | | armor\_feet | Boots | | breakable | Unknown | ## Setting Rarity [About rarity](../../Digression/Rarity) | Method | Args | Meaning | | :------------: | :------: | :-------------------------------------------------: | | rarity(rarity) | rarity | [Set rarity](../../Digression/Rarity#%E7%A8%80%E6%9C%89%E5%BA%A6%E7%9A%84%E7%AD%89%E7%BA%A7) | | uncommon() | none | Set rarity to uncommon | | rare() | none | Set rarity to rare | | veryRare() | none | Set rarity to very rare (epic) | ## Common Methods | Method | Args | Meaning | | :------------: | :-------: | :-------------------------------------------: | | minLevel(int) | integer | Set the minimum enchantment level | | maxLevel(int) | integer | Set the maximum enchantment level | | untradeable() | none | Prevent villagers from selling this enchantment | | curse() | none | Mark as a curse (displayed in red) | ## Checking Whether An Item Can Be Enchanted (`canEnchant`) If you want to allow enchantments on items that do not fit any category (for example a stick), or you want the enchantment to apply to all items, you can use `canEnchant`. Examples: ```js StartupEvents.registry("enchantment",event =>{ // Returning true means the enchantment can apply to any item event.create("meng:all_item_enchantment").canEnchant((item)=>{return true}) // Only allow enchanting a specific item event.create("meng:item_stick_enchantment") .canEnchant((item)=>{ // Return a boolean to indicate whether this item can be enchanted return item.id == "minecraft:stick" }) // Allow multiple items event.create("meng:more_item_enchantment") .canEnchant((item)=>{ if (item.id == "minecraft:stick"){ return true }else if(item.id == "minecraft:diamond"){ return true } return false }) }) ``` You can also reject items based on what enchantments they already have, to implement conflicts (like Sharpness vs Smite vs Bane of Arthropods). A simple example: ```js StartupEvents.registry("enchantment",event =>{ event.create("meng:clash_test1") .canEnchant((item)=>{ // Get all enchantments on the item let enchantments = item.getEnchantments() // Check whether the conflicting enchantment is present if (enchantments.get("meng:clash_test2") == null){ return true }else{ return false } }) event.create("meng:clash_test2") .canEnchant((item)=>{ // Get all enchantments on the item let enchantments = item.getEnchantments() // Check whether the conflicting enchantment is present if (enchantments.get("meng:clash_test1") == null){ return true }else{ return false } }) }) ``` This makes `clash_test1` incompatible with `clash_test2`, and vice versa. ## Damage Bonus And Protection You can also define damage bonuses or damage protection directly on the enchantment during registration. ### Damage Bonus (`damageBonus`) This example deals extra damage to undead mobs equal to `level * 10`: ```js StartupEvents.registry("enchantment",event =>{ event.create("meng:undead_killer_pro") .weapon() .damageBonus((level,entityType)=>{ if (entityType == "undead"){ return level * 10 } }) }) ``` `level` is the enchantment level. `entityType` is the entity category/type. ### Damage Protection (`damageProtection`) This is similar to Protection / Fire Protection: reduce incoming damage. Before writing this, you should understand how vanilla Protection works: [Armor mechanics (Minecraft Wiki, zh)](https://zh.minecraft.wiki/w/%E7%9B%94%E7%94%B2%E6%9C%BA%E5%88%B6#%E4%BF%9D%E6%8A%A4%E9%AD%94%E5%92%92%E6%9C%BA%E5%88%B6). In short: if the returned value were 25, the player would fully negate the damage. But due to Minecraft's mechanics, the effective cap is 20, and anything above 20 behaves like 20. For more details, see the wiki link above, or the in-doc page [Enchantment protection mechanics](../../Digression/ProtectionEnchantMechanism). ```js StartupEvents.registry("enchantment",event =>{ event.create("meng:undead_protect") .armor() .damageProtection((level,damageSource)=>{ try{ /** * @type {$LivingEntity} */ let damageSourceLiving = damageSource.getActual() // Entity that actually caused the damage let LivingEntityType = damageSourceLiving.getEntityType() // Entity type of the attacker // Check whether the attacker is a Warden if(LivingEntityType.toShortString() == "warden"){ return level * 5 } let damageSourceEntity = damageSource.getImmediate() // Immediate source (e.g. arrow) }catch(e){ // If the damage was not caused by an entity (burning, fall, drowning, potion effects, etc.), // getActual()/getImmediate() may fail and we handle it here. console.log(damageSource.getType()); if (damageSource.getType() == "lava"){ return level * 2 } } }) }) ``` Here is the difference between `getActual()` and `getImmediate()`: Example: if a skeleton shoots an arrow and the arrow hits the player, the skeleton is `getActual()`, and the arrow is `getImmediate()`. So you need to decide which part you want to use for your logic. We use try/catch because not all damage comes from entities. Falling, burning, drowning, potion effects, etc. are not "entity damage", so calling `getActual()`/`getImmediate()` can throw errors. Wrapping it with [try/catch](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/try...catch) lets us handle those cases. If it is not entity damage, you can check the damage type via `damageSource.getType()`. It returns a string, so you can do `if (damageSource.getType() == "lava")` to check for lava damage. Using `console.log(damageSource.getType())` will write the damage type to `logs\\kubejs\\startup.log`. If you are not sure what damage type something uses, enchant armor with this and take that type of damage in-game to observe the value. ### Entity Type Categories Unfortunately, damage bonus/protection does not target a single mob; it targets a category. The categories are: 1. `unknown` - no specific category 2. `undead` - undead mobs 3. `arthropod` - arthropods 4. `illager` - illagers (ravagers do not count) 5. `water` - water creatures ## Hurt And Attack Hooks When registering an enchantment, you can hook into attack and hurt behaviors directly on the enchantment, without writing a separate event handler. ### Hurt (`postHurt`) This "hurt" hook refers to damage received after an entity attack. The example below: when wearing armor enchanted with this enchantment, after being attacked it steals health from the attacker and heals the wearer. ```js StartupEvents.registry("enchantment",event =>{ event.create("meng:health_theft") .armor() .postHurt((living,entity,level)=>{ living.heal(level * 2) if (entity.isLiving()) { entity.attack(level) } }) }) ``` After registering the enchantment, do not forget to add a [lang file](../../Resources/Lang): ```json { "enchantment.meng.health_theft": "Health Theft" } ``` In `postHurt`, the first argument `living` is the entity that got hurt, the second argument `entity` is the attacker, and the third argument `level` is the enchantment level. In this example, `living.heal(level * 2)` heals the wearer, and `entity.attack(level)` damages the attacker (simulating life drain). ### Attack (`postAttack`) The example below: when attacking with a weapon enchanted with this enchantment, steal health and add it to the attacker. ```js StartupEvents.registry("enchantment",event =>{ event.create("meng:health_steal") .weapon() .minLevel(1) .maxLevel(5) .postAttack((living,entity,level)=>{ if (entity.isLiving()) { living.health += level } }) }) ``` After registering the enchantment, do not forget to add a [lang file](../../Resources/Lang): ```json { "enchantment.meng.health_steal": "Health Steal" } ``` `if (entity.isLiving())` checks whether the target is a living entity. If so, it heals the attacker by the enchantment level. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAdvanced/RecipesSchemaAdded.md --- # Adding Recipe Schemas (RecipesSchemaAdded) > This document is written for 1.20.1. In 1.21 this approach is gone; 1.21 uses a different method. > > ![wolf](/imgs/Schema/wolf.png) In KubeJS 1.20, a `recipeSchemaRegistry` startup event (`startup`) was added. This lets you add a simple "schema" for recipe types that do not have KubeJS support. For example, `createmetallurgy` (Create: Metallurgy) does not ship KubeJS support. > I asked the author, and they said they truly do not have time to learn KubeJS right now. This mod has a casting-table recipe similar to [**Tinkers' Construct**](https://www.mcmod.cn/class/3725.html). In JSON form it looks like: ```json { "type": "createmetallurgy:casting_in_table", "ingredients": [ { "item": "createmetallurgy:graphite_plate_mold" }, { "fluid": "createmetallurgy:molten_brass", "amount": 90 } ], "processingTime": 80, "mold_consumed": false, "result": { "item": "create:brass_sheet" } } ``` After adding a schema (called **Schema** below), the same recipe can be written like this: ```js ServerEvents.recipes((event) => { const { createmetallurgy } = event.recipes createmetallurgy.casting_in_table("create:brass_sheet", [ "createmetallurgy:graphite_plate_mold", Fluid.of("createmetallurgy:molten_brass", 90) ]).processingTime(80).mold_consumed(false) }) ``` ## Main Content That is enough for the introduction. The main tutorial starts below (full code is at the end 👇). First, download the Schema helper Java file: [**RecipesSchema**](https://cloud.mihono.cn/s/FPCmZY3ibF5JyDD/download/RecipesSchema.java). It contains many component type definitions. You will reference it frequently while writing schemas. Then download [**prelude.js**](https://github.com/Prunoideae/-recipes/blob/1.20.1/src/prelude.js) from [**Prunoideae's GitHub repo**](https://github.com/Prunoideae/-recipes/blob/1.20.1/src). This file is a set of helpers that greatly improves authoring efficiency. Place the file under `kubejs/startup_scripts/@recipes`. Now you can write your own schema. Create a JS file whose name matches the target mod's `modid`. > For example: `createmetallurgy.js` We'll use `createmetallurgy` as the example. After creating the file, start by writing: ```js new Schema() ``` The first argument is the recipe type. You can find it in the recipe JSON under `"type"`. For example, the casting-table recipe above uses `"createmetallurgy:casting_in_table"`. ```js new Schema("createmetallurgy:casting_in_table") ``` Next, add `.simpleKey()` calls. The first argument is the key name: the top-level key in the JSON, such as `ingredients`, `processingTime`, `result`, etc. The second argument is the component type. You need to look it up in the [**Java file**](/Files/RecipesSchema.java) mentioned above. The third argument is optional. For strings, you list allowed values using `||`, for example: `"superheated" || "heated"`. Next, we'll use `createmetallurgy:casting_in_basin` as the main walkthrough example. First: the order of `.simpleKey(...)` definitions determines the argument order in `ServerEvents.recipes`. For example, if you define `ingredients` first, then the first argument of `createmetallurgy.casting_in_basin(...)` will correspond to `ingredients`, not `result`/`results`. **(There are two similar names here, be careful. This is explained below. Yes, this has caused pain 🤡.)** ![1](/imgs/Schema/1.png) ![2](/imgs/Schema/2.png) ![3](/imgs/Schema/3.png) If you want your function call to look like other recipe APIs, you generally want `result`/`results` first, and `ingredients`/`input` second. Let's start with `result` vs `results`. The difference is the extra `s`: in English that usually indicates plural. So `results` typically means **multiple outputs**. After deciding the key name, the next step is to choose the component type. Open the game and inspect a recipe: ![4](/imgs/Schema/4.png) Based on an **educated guess**, this recipe type **seems** to output a single item. So in [the Java file](/Files/RecipesSchema.java) we look for a component type related to `output` + `item`, such as `outputItem`, and try that first. If it is wrong, we iterate. ```js new Schema("createmetallurgy:casting_in_basin") .simpleKey("result", "outputItem") ``` Next is `ingredients`. From the JSON, this key is an array and it can contain both an item ingredient and a fluid ingredient (for example: molten metal + a mold). Let's look at the JSON: ![5](/imgs/Schema/5.png) It is clear that `ingredients` contains `[]`, i.e. an array. You might search for `Array` first, which is correct, but the key detail is that the array can contain both `item` and `fluid`. Based on keywords like `Array`, `item`, and `fluid`, we can choose `inputFluidOrItemArray` for the type. After writing this minimal schema, run `/probejs dump` in-game and restart **VS Code**. You should then see code completion for the generated wrapper. ![6](/imgs/Schema/6.png) Now write a simple recipe: ```js ServerEvents.recipes((event) => { const { createmetallurgy } = event.recipes createmetallurgy.casting_in_basin("minecraft:brick", [ Fluid.of("minecraft:lava", 90), "#forge:ingots/iron" ]) }) ``` In theory, this already works. But this recipe also has `processingTime` and `mold_consumed`, which correspond to "processing time" and "whether the mold is consumed". So we should add both keys as well. These two are relatively straightforward. First is `processingTime`. It is a number (in [ticks](https://minecraft.wiki/w/Tick)), so choose a numeric component type. The third argument (default value example) is optional, but it's better to provide one. Here we use `doubleNumber`. For processing time, it is usually exposed as a chained method on the recipe call: ![7](/imgs/Schema/7.png) Next is `mold_consumed`. In JSON it is a boolean (`true/false`), so choose a boolean component type (for example `bool`) and list possible values with `false || true`. Then run dump again and write a full recipe: ```js ServerEvents.recipes((event) => { const { createmetallurgy } = event.recipes createmetallurgy.casting_in_basin("minecraft:brick", [ Fluid.of("minecraft:lava", 90), "#forge:ingots/iron" ]).processingTime(114).mold_consumed(true) }) ``` **Game Be Staring...** ![8](/imgs/Schema/8.png) Everything looks fine. Here is the schema: ```js new Schema("createmetallurgy:casting_in_basin") .simpleKey("result", "outputItem") .simpleKey("ingredients", "inputFluidOrItemArray") .simpleKey("processingTime", "doubleNumber", 100) .simpleKey("mold_consumed", "bool", false || true) ``` The remaining recipe types follow the same pattern. Try writing them yourself. ## Tips Create-style `heatRequirement` is a key whose value is a string. In your schema, define this key as a `nonEmptyString`, then list allowed heat levels separated by `||`. Then in recipes you can chain `.heatRequirement(string)` to configure it. ## Full Code Example ```js new Schema("createmetallurgy:casting_in_basin") .simpleKey("result", "outputItem") .simpleKey("ingredients", "inputFluidOrItemArray") .simpleKey("processingTime", "doubleNumber", 100) .simpleKey("mold_consumed", "bool", false || true) new Schema("createmetallurgy:casting_in_table") .simpleKey("result", "outputItem") .simpleKey("ingredients", "inputFluidOrItemArray") .simpleKey("processingTime", "doubleNumber", 100) .simpleKey("mold_consumed", "bool", false || true) new Schema("createmetallurgy:grinding") .simpleKey("results", "outputItemArray") .simpleKey("ingredients", "inputItemArray") .simpleKey("processingTime", "doubleNumber", 100) new Schema("createmetallurgy:alloying") .simpleKey("results", "outputFluidOrItemArray") .simpleKey("ingredients", "inputFluidOrItemArray") .simpleKey("heatRequirement", "nonEmptyString", "superheated" || "heated") .simpleKey("processingTime", "doubleNumber", 100) new Schema("createmetallurgy:melting") .simpleKey("results", "outputFluidArray") .simpleKey("ingredients", "inputItemArray") .simpleKey("heatRequirement", "nonEmptyString", "superheated" || "heated") .simpleKey("processingTime", "doubleNumber", 100) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/Addon/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.21/Introduction/Addon/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Addon/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Introduction/Addon/Description.md --- # Addon This section provides an overview and structured entry point for the `Addon` directory. --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Introduction/Addon/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/zh/modpack/kubejs/1.21/Addon/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/Recipe/AddRecipe/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Introduction/Recipe/AddRecipe/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAdvanced/RecipeAdvanced.md --- # Advanced Recipe Crafting Everything in this chapter applies to KubeJS `shapeless` and `shaped` recipes only. It does not apply to other recipe types, even if you can technically call similar methods. KubeJS only adapts its own recipe APIs and does not adapt other crafting systems, including vanilla `recipes.minecraft.xxx`. So make sure you are using `shapeless` and `shaped`, or `recipes.kubejs.shaped` and `recipes.kubejs.shapeless`. For why vanilla water buckets return empty buckets in crafting, see --> [Set Item Crafting Return Item](./ItemRecipeReturnItem.md) ## NBT on Crafting Result: `modifyResult` The code below demonstrates crafting with enchanted inputs and custom output logic. This is only an example of how to use `modifyResult`. Adjust the scenario to your own needs. ```js ServerEvents.recipes(event=>{ event.recipes.kubejs.shaped('minecraft:soul_torch',[ ['minecraft:torch','minecraft:torch','minecraft:torch'], ['minecraft:torch','minecraft:stone','minecraft:torch'], ['minecraft:torch','minecraft:torch','minecraft:torch'] ]).modifyResult((inputItemGrid,outputItem)=>{ let stone = inputItemGrid.find("stone") let items = inputItemGrid.findAll("torch"); for (let i = 0; i < items.length; i++) { if (!items[i].hasEnchantment("looting",2)){ return "air" } } if (Math.random() < 0.5){ let ci = items[0].copy(); ci.count = 1; return ci } return outputItem; }) }) ``` In `modifyResult`, the first parameter `inputItemGrid` is the crafting-grid input, and the second parameter `outputItem` is the output item. You must return an item as the final output (`return ItemStack`). `inputItemGrid.find(itemId)` searches for an item in the crafting grid and returns an **`ItemStack`**. `ItemStack.hasEnchantment(enchantmentId, level)` checks enchantments on the item and returns `bool`. `ItemStack.enchant(enchantmentId, level)` enchants the item. Since it does not directly mutate the original item in this context, ***store the returned value in a variable***. ## Crafting with Durability Cost: `damageIngredient` Example: use any axe with logs in a crafting table to make 8 planks, consuming axe durability. ```js ServerEvents.recipes((event) => { event.shapeless(Item.of('minecraft:oak_planks', 8), [ 'minecraft:oak_log', "#minecraft:axes" ]).damageIngredient({ tag: "#minecraft:axes" }, 5) }) ``` In `damageIngredient`, the first parameter is a matcher, and the second is the durability amount consumed. In the matcher, `item` matches an item, and `tag` matches a tag. ## Crafting with Return Item: `replaceIngredient` Example: use Strong Healing potion + Dragon's Breath to craft a Lingering Strong Healing potion, and return a glass bottle. ```js ServerEvents.recipes(event => { event.shapeless( Item.of('minecraft:lingering_potion', '{Potion:"minecraft:strong_healing"}').strongNBT(), ['minecraft:dragon_breath', Item.of('minecraft:potion', '{Potion:"minecraft:strong_healing"}').strongNBT()] ).replaceIngredient({ item: "minecraft:dragon_breath" }, Item.of('minecraft:glass_bottle')) }) ``` In `replaceIngredient`, the first parameter is the matcher for the ingredient to replace, and the second is the replacement item. Matcher rules are the same as above. ## Crafting While Keeping Ingredient: `keepIngredient` In the example below, crafting with stick + honeycomb into blaze rod does not consume the honeycomb. ```js ServerEvents.recipes(event => { event.shapeless( 'minecraft:blaze_rod', ['minecraft:stick', 'minecraft:honeycomb'] ).keepIngredient({ item: "minecraft:honeycomb" }) }) ``` `keepIngredient` takes a matcher parameter, with the same rules as above. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/AdvancementJs.md --- # AdvancementJs --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/AdvancementJs.md --- # AdvancementJs --- --- url: 'https://docs.variedmc.cc/en/mods/adventure/sidebarIndex.md' description: 'Combat, elite mobs, difficulty scaling, and adventure-oriented mod docs.' --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/zh/mods/adventure/Champions-Unofficial/1.20.1/affix_setting.md --- # 词缀配置 Affix Setting 阅前须知:你需要知道Minecraft中的[数据包](https://zh.minecraft.wiki/w/%E6%95%B0%E6%8D%AE%E5%8C%85),知晓部分游戏内容通过数据包加载到游戏中,知晓JSON文本及其语法。 **Champions**通过**数据包**控制各个词缀允许拥有词缀的生物,词缀等级范围。它在数据包中处于以下位置`data//affix_setting/.json`,例如`data/champions/affix_setting/adaptable.json` 以下是一个示例: ```json { "type": "champions:adaptable", "category": "defence", "enable": true, "hasSub": false, "mobList": [ "minecraft:pig", "minecraft:creeper" ], "mobPermission": "blacklist", "tier": { "max": 100, "min": 1 } } ``` `enable`字段控制该词缀是否启用,true启用,false关闭。 `mobList`字段控制与该词缀相关的生物列表,包含一系列生物id。 `mobPermission`字段控制mobList字段的列表是黑名单还是白名单,当处于黑名单(blacklist),mobList列表中的生物不会被附加该词缀;当处于白名单(whitelist),只有mobList列表中的生物可附加该词缀。 `tier`字段控制该词缀出现时的最大最小等级,max为最大,min为最小。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSBasic/AllEvent.md --- # All Events This chapter lists major events and example links, including events from KubeJS and from other mods. ## All Events Provided by KubeJS Below are callable events provided by KubeJS. Pay attention to which folder each event belongs to. Format: `MainEvent.SubEvent` ### `startup_scripts` (startup folder) | Main Event | Sub Event | Purpose | Example | | :------------: | :----------------: | :---------------------------: | :-------------------------------------------: | | StartupEvents | init | Game initialization | - | | StartupEvents | registry | Register game content | [Link](../KubeJSBasic/Customs/README.md) | | WorldgenEvents | add | Add worldgen features | - | | WorldgenEvents | remove | Remove worldgen features | - | | ItemEvents | modification | Modify item properties | - | | ItemEvents | toolTierRegistry | - | - | | ItemEvents | armorTierRegistry | - | - | | ItemEvents | modelProperties | - | - | | BlockEvents | modification | Modify block properties | - | ### `server_scripts` (server folder) | Main Event | Sub Event | Purpose | Example | | :------------: | :----------------------: | :-------------------------------------: | :---------------------------------------------------------------: | | ServerEvents | lowPriorityData | - | - | | ServerEvents | highPriorityData | - | - | | ServerEvents | loaded | Server reload/load event | - | | ServerEvents | unloaded | - | - | | ServerEvents | tick | Game tick event | - | | ServerEvents | tags | Tag event | - | | ServerEvents | commandRegistry | Command registration event | - | | ServerEvents | command | Server command event | - | | ServerEvents | customCommand | Custom commands | [Link](../KubeJSAdvanced/CustomCommandRegistry) | | ServerEvents | recipes | Recipe event | [Link](./BasicSyntax-Add) | | ServerEvents | afterRecipes | Post-recipe processing (has known bugs) | - | | ServerEvents | specialRecipeSerializers | - | - | | ServerEvents | compostableRecipes | - | - | | ServerEvents | recipeTypeRegistry | - | - | | ServerEvents | genericLootTables | Global loot event | [Link](./LootTables/GlobalLootTable) | | ServerEvents | blockLootTables | Block loot event | [Link](./LootTables/BlockLootTable) | | ServerEvents | entityLootTables | Entity loot event | [Link](./LootTables/EntityLootTable) | | ServerEvents | giftLootTables | Villager gift loot event | [Link](./LootTables/GiftLootTable) | | ServerEvents | fishingLootTables | Fishing loot event | [Link](./LootTables/FishingLootTable) | | ServerEvents | chestLootTables | Chest loot event | [Link](./LootTables/ChestLootTable) | | LevelEvents | loaded | Level/world loaded event | - | | LevelEvents | unloaded | - | - | | LevelEvents | tick | Level/world tick event | - | | LevelEvents | beforeExplosion | - | - | | LevelEvents | afterExplosion | - | - | | NetworkEvents | fromClient | - | - | | ItemEvents | rightClicked | Item right-click event | - | | ItemEvents | canPickUp | - | - | | ItemEvents | pickedUp | Item pickup event | - | | ItemEvents | dropped | Item drop event | - | | ItemEvents | entityInteracted | - | - | | ItemEvents | crafted | - | - | | ItemEvents | smelted | - | - | | ItemEvents | foodEaten | Food consumed event | - | | ItemEvents | firstRightClicked | - | - | | ItemEvents | firstLeftClicked | - | - | | BlockEvents | rightClicked | Block right-click event | [Link](../KubeJSAdvanced/EventExamples/BlockRightClickedEvent.md) | | BlockEvents | leftClicked | Block left-click event | - | | BlockEvents | placed | Block placed event | - | | BlockEvents | broken | Block broken event | - | | BlockEvents | detectorChanged | - | - | | BlockEvents | detectorPowered | - | - | | BlockEvents | detectorUnpowered | - | - | | BlockEvents | farmlandTrampled | - | - | | EntityEvents | death | Entity death event | - | | EntityEvents | hurt | Entity hurt event | - | | EntityEvents | checkSpawn | - | - | | EntityEvents | spawned | Entity spawn event | - | | PlayerEvents | loggedIn | Player login event | - | | PlayerEvents | loggedOut | Player logout event | - | | PlayerEvents | respawned | Player respawn event | - | | PlayerEvents | tick | Player tick event | - | | PlayerEvents | chat | Player chat event | - | | PlayerEvents | decorateChat | - | - | | PlayerEvents | advancement | - | - | | PlayerEvents | inventoryOpened | Player inventory open event | - | | PlayerEvents | inventoryClosed | Player inventory close event | - | | PlayerEvents | inventoryChanged | Player inventory changed event | [Link](../KubeJSAdvanced/EventExamples/PlayerInventoryChangedEvents.md) | | PlayerEvents | chestOpened | Player chest open event | - | | PlayerEvents | chestClosed | Player chest close event | - | ### `client_scripts` (client folder) | Main Event | Sub Event | Purpose | Example | | :------------: | :----------------: | :---------------------: | :----------------: | | ClientEvents | highPriorityAssets | - | - | | ClientEvents | init | - | - | | ClientEvents | loggedIn | - | - | | ClientEvents | loggedOut | - | - | | ClientEvents | tick | Client tick event | - | | ClientEvents | painterUpdated | - | - | | ClientEvents | leftDebugInfo | - | - | | ClientEvents | rightDebugInfo | - | - | | ClientEvents | paintScreen | - | - | | NetworkEvents | fromServer | - | - | | ItemEvents | tooltip | Item tooltip event | [Link](./Tooltip) | | ItemEvents | clientRightClicked | - | - | | ItemEvents | clientLeftClicked | - | - | ## Events Provided by Other Mods --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSProjects/Meng/AltarComposition.md --- # Altar Crafting Topics covered: custom structures, recipe detection, recipe registration, JEI custom registration, and item entities. Mods and versions used: 1. jei-1.20.1-forge-15.3.0.4 2. rhino-forge-2001.2.2-build.18 3. architectury-9.2.14-forge 4. kubejsadditions-forge-4.3.2 5. kubejs-forge-2001.6.5-build.14 6. probejs-7.0.1-forge ## Register Block and Structure Detection **Note: this code should be in `startup_scripts`.** ```js const { $InteractionHand } = require("packages/net/minecraft/world/$InteractionHand") const { $EntityType } = require("packages/net/minecraft/world/entity/$EntityType") const { $ItemEntity } = require("packages/net/minecraft/world/entity/item/$ItemEntity") const { $BlockPos } = require("packages/net/minecraft/core/$BlockPos"); StartupEvents.registry("block", event => { event.create("meng:test_block", "basic") .hardness(1) .defaultCutout() .box(1,0,1,15,3,15,true) .box(4,3,4,12,21,12,true) .box(6,21,6,10,21.2,10,true) .blockEntity(e => { e.inventory(1, 1) e.initialData({ item: "minecraft:air" }) }).rightClick(e => { if (e.getHand() != $InteractionHand.MAIN_HAND) return let block = e.getBlock() let blockPos = block.getPos() let player = e.getPlayer() let world = player.getLevel() let mainHandItem = player.getMainHandItem().copy() let data = block.getEntityData().get("data") let dataInItem = data.get("item") if (player.isShiftKeyDown()) { if (getBlockStructure(world, blockPos)) { let output = global.recipeStructure(dataInItem, getStructureItems(world, blockPos)) if (output != null) { removeFloatItem(world, dataInItem, blockPos) itemFloat(world, Item.of(output), block) removeStructureItems(world, blockPos) } } return } if (dataInItem != "minecraft:air") { removeFloatItem(world, dataInItem, blockPos) player.give(dataInItem) } itemFloat(world, mainHandItem, block, blockPos) player.getMainHandItem().count-- }) }) /** * Remove floating item entity. * @param {$Level} world * @param {String} itemId * @param {$BlockPos} pos */ function removeFloatItem(world,itemId,pos){ const {x,y,z} = pos world.getEntities($EntityType.ITEM, (value) => { // value type is $ItemEntity if (value.item.getId() == itemId) { if (value.x == x + 0.5 && value.y == y + 1.42 && value.z == z + 0.5) { // Remove entity using "discarded" value.remove("discarded") return true } } return false }); } /** * Make an item float. * @param {$Level} world * @param {$ItemStack_} item * @param {$BlockContainerJS} block */ function itemFloat(world,item,block){ let {x,y,z} = block.getPos() /** * @type {$ItemEntity} */ let itemEntity = world.createEntity("item") itemEntity.setPos(x + 0.5, y + 1.42, z + 0.5) itemEntity.item = item itemEntity.age = -32768 itemEntity.setPickUpDelay(-1) itemEntity.setNoGravity(true) itemEntity.item.count = 1 itemEntity.spawn() block.setEntityData({ data: { item: itemEntity.item.id } }) } /** * Get structure blocks. * @param {$Level} world * @param {$BlockPos} blockPos * @returns */ function structureBlocksPos(world,blockPos){ let { x, y, z } = blockPos let posList = [ [x + 3, y, z], [x - 3, y, z], [x, y, z + 3], [x, y, z - 3], [x + 2, y, z + 2], [x + 2, y, z - 2], [x - 2, y, z + 2], [x - 2, y, z - 2] ] let blocks = [] for (const pos of posList){ blocks.push(world.getBlock(pos[0], pos[1], pos[2])) } return blocks } /** * Check structure integrity. * @param {$Level} world * @param {$BlockPos} blockPos */ function getBlockStructure(world, blockPos) { let blocks = structureBlocksPos(world, blockPos) for (const block of blocks) if (block != "meng:test_block") return false return true } /** * Get items inside the structure. * @param {$Level} world * @param {$BlockPos} blockPos */ function getStructureItems(world, blockPos) { let blocks = structureBlocksPos(world, blockPos) let itemList = [] for (const block of blocks){ let item = block.getEntityData().get("data").get("item") if (item == "minecraft:air") continue itemList.push(item.getAsString()) } return itemList } /** * Remove items inside the structure. * @param {$Level} world * @param {$BlockPos} blockPos */ function removeStructureItems(world,blockPos){ let blocks = structureBlocksPos(world, blockPos) for (const block of blocks){ let itemId = block.getEntityData().get("data").get("item") removeFloatItem(world,itemId,block.getPos()) itemFloat(world,Item.of("minecraft:air"),block) } } ``` ## Blockstate and Model Files **Note: this code should be in `blockstates`.** ```json { "variants":{ "":{ "model": "meng:block/test_block" } } } ``` **Note: this code should be in `models\\block`.** ```json { "credit": "Made with Blockbench", "texture_size": [64, 64], "textures": { "0": "minecraft:block/cobblestone", "particle": "minecraft:block/cobblestone" }, "elements": [ { "from": [1, 0, 1], "to": [15, 3, 15], "rotation": {"angle": 0, "axis": "y", "origin": [6, 0.5, 6]}, "faces": { "north": {"uv": [4.5, 8, 8.25, 8.75], "texture": "#0"}, "east": {"uv": [8.25, 0, 12, 0.75], "texture": "#0"}, "south": {"uv": [8.25, 0.75, 12, 1.5], "texture": "#0"}, "west": {"uv": [8.25, 1.5, 12, 2.25], "texture": "#0"}, "up": {"uv": [3.75, 3.75, 0, 0], "texture": "#0"}, "down": {"uv": [3.75, 3.75, 0, 7.5], "texture": "#0"} } }, { "from": [4, 1, 4], "to": [12, 21, 12], "rotation": {"angle": 0, "axis": "y", "origin": [5, 2, 5]}, "faces": { "north": {"uv": [3.75, 0, 6, 4], "texture": "#0"}, "east": {"uv": [3.75, 4, 6, 8], "texture": "#0"}, "south": {"uv": [6, 0, 8.25, 4], "texture": "#0"}, "west": {"uv": [6, 4, 8.25, 8], "texture": "#0"}, "up": {"uv": [2.25, 9.75, 0, 7.5], "texture": "#0"}, "down": {"uv": [4.5, 8, 2.25, 10.25], "texture": "#0"} } }, { "from": [6, 21, 6], "to": [10, 22, 10], "rotation": {"angle": 0, "axis": "y", "origin": [7, 22.25, 7]}, "faces": { "north": {"uv": [2.25, 7.5, 3.5, 7.75], "texture": "#0"}, "east": {"uv": [2.25, 7.75, 3.5, 8], "texture": "#0"}, "south": {"uv": [8.25, 4.75, 9.5, 5], "texture": "#0"}, "west": {"uv": [8.25, 5, 9.5, 5.25], "texture": "#0"}, "up": {"uv": [9.5, 3.5, 8.25, 2.25], "texture": "#0"}, "down": {"uv": [9.5, 3.5, 8.25, 4.75], "texture": "#0"} } } ], "display": { "thirdperson_righthand": { "translation": [0, 3, 1], "scale": [0.55, 0.55, 0.55] }, "thirdperson_lefthand": { "translation": [0, 3, 1], "scale": [0.55, 0.55, 0.55] }, "firstperson_righthand": { "rotation": [0, -90, 25], "translation": [1.13, 3.2, 1.13], "scale": [0.68, 0.68, 0.68] }, "ground": { "translation": [0, 0.25, 0], "scale": [0.5, 0.5, 0.5] }, "gui": { "rotation": [28, 8, -11], "translation": [1, 4.5, 0], "scale": [1.13, 1.13, 1.13] }, "head": { "rotation": [0, 180, 0], "translation": [-0.25, 14.5, -0.25] } }, "groups": [ { "name": "group", "origin": [8, 8, 8], "color": 0, "children": [0, 1, 2] } ] } ``` ## Altar Recipe Use and Registration **Note: this code should be in `server_scripts`.** ```js /** * Simulated recipe list. */ global.testRecipe = [ { principalItem: 'minecraft:diamond', inputItems: [ "minecraft:iron_ingot", 'minecraft:sugar', 'minecraft:netherite_scrap' ], outputItem: 'minecraft:netherite_ingot' } ] /*** * Check whether two string arrays contain the same elements. */ function arraysEqualIgnoreOrder(arr1, arr2) { if (arr1.length !== arr2.length) { return false; } // Copy arrays to avoid modifying original arrays const sortedArr1 = arr1.sort((a, b) => a.localeCompare(b)); const sortedArr2 = arr2.sort((a, b) => a.localeCompare(b)); return JSON.stringify(sortedArr1) === JSON.stringify(sortedArr2); } /** * Recipe check. * @param {String} principalItem center item id * @param {*} recipesItems recipe item id array * @returns crafted output item id */ global.recipeStructure = (principalItem, recipesItems) => { for (const key of global.testRecipe) { if (key.principalItem == principalItem) { if (arraysEqualIgnoreOrder(key.inputItems, recipesItems)) { return key.outputItem } } } return null } /** * Register recipe. * @param {String} output output item * @param {String[]} input surrounding altar items * @param {String} principal center altar item */ function regRecipe(output,principal,input){ global.testRecipe.push({ principalItem:principal, inputItems:input, outputItem:output }) } regRecipe("meng:hello_block","meng:slab_block",["meng:stairs_block","minecraft:yellow_wool","minecraft:white_stained_glass"]) ``` ## Drop Stored Items When Altar Is Broken **Note: this code should be in `server_scripts`.** ```js BlockEvents.broken("meng:test_block", e => { let block = e.getBlock() let data = block.getEntityData().get("data") let { x, y, z } = block.getPos() let world = block.getLevel() if (data.get("item") == "minecraft:air") return world.getEntities($EntityType.ITEM, (value) => { if (value.x == x + 0.5 && value.y == y + 1.42 && value.z == z + 0.5) { value.age = 0 value.setPickUpDelay(0) value.setNoGravity(false) return true } return false }); }) ``` ## JEI Recipe Display Registration **Note: this code should be in `client_scripts`, and requires [KubeJS Additions](https://www.mcmod.cn/class/11814.html).** ```js JEIAddedEvents.registerCategories((event) => { event.custom("meng:block_test", (category) => { const jeiHelpers = category.getJeiHelpers() const guiHelper = category.getJeiHelpers().getGuiHelper(); category .title("Altar Crafting") .background(guiHelper.createDrawable(new ResourceLocation("meng", "jei/synthetic_orientation.png"), 0, 0, 150, 100)) .icon(guiHelper.createDrawableItemStack(Item.of("meng:test_block"))) .isRecipeHandled((recipe) => { return global["verifyRecipe"](jeiHelpers, recipe); }) .handleLookup((builder, recipe, focuses) => { global["handleLookup"](jeiHelpers, builder, recipe, focuses); }) }); }); global["verifyRecipe"] = (jeiHelpers, recipe) => { return !!( recipe?.data?.inputs !== undefined && recipe?.data?.output !== undefined && recipe?.data?.principal !== undefined ); }; global["handleLookup"] = (jeiHelpers, builder, recipe, focuses) => { builder.addSlot("INPUT", 35, 45).addItemStack(Item.of(recipe.data.principal)).setSlotName("input"); let recipeItems = recipe.data.inputs for (let index = 0; index < 8; index++) { if (recipeItems == null) { recipeItems = "air" } } builder.addSlot("INPUT", 35, 15).addItemStack(Item.of(recipeItems[0])).setSlotName("input"); builder.addSlot("INPUT", 35, 75).addItemStack(Item.of(recipeItems[1])).setSlotName("input"); builder.addSlot("INPUT", 5, 45).addItemStack(Item.of(recipeItems[2])).setSlotName("input"); builder.addSlot("INPUT", 65, 45).addItemStack(Item.of(recipeItems[3])).setSlotName("input"); builder.addSlot("INPUT", 15, 25).addItemStack(Item.of(recipeItems[4])).setSlotName("input"); builder.addSlot("INPUT", 15, 65).addItemStack(Item.of(recipeItems[5])).setSlotName("input"); builder.addSlot("INPUT", 55, 25).addItemStack(Item.of(recipeItems[6])).setSlotName("input"); builder.addSlot("INPUT", 55, 65).addItemStack(Item.of(recipeItems[7])).setSlotName("input"); builder.addSlot("OUTPUT", 125, 45).addItemStack(Item.of(recipe.data.output)).setSlotName("output"); builder.addInvisibleIngredients("OUTPUT").addItemStack(Item.of(recipe.data.output)); }; JEIAddedEvents.registerRecipes((event) => { let cb = event.custom("meng:block_test") for (const key of global.testRecipe) { cb.add({ output: key.outputItem, inputs: key.inputItems, principal: key.principalItem }) } }); JEIAddedEvents.registerRecipeCatalysts(jei =>{ jei.data.addRecipeCatalyst("meng:test_block","meng:block_test") }) ``` ## Notes 1. Recipe detection in this project is string-based, not `ItemStack`-based, so it cannot check NBT directly. Modify if needed. 2. This project is only an example; many parts are not necessarily optimal and can be improved. 3. If you improve this project, you can upload your revised code to the [Gitee repository](https://gitee.com/gumengmengs/kubejs-course). 4. Full project code is [here](https://gitee.com/gumengmengs/kubejs-course/tree/main/Code/Projects/AltarComposition/kubejs). 5. Item rendering uses dropped item entities, so `kill @e[type=\"item\"]` will also clear them. Keep that in mind (you can consider fixing this with a block tick check). --- --- url: 'https://docs.variedmc.cc/en/info/KubeJS/Q&A/API.md' --- # 类与方法 该页面用于讨论KubeJS的类与方法的使用问题。 --- --- url: 'https://docs.variedmc.cc/zh/info/KubeJS/Q&A/API.md' --- # 类与方法 该页面用于讨论KubeJS的类与方法的使用问题。 --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.21/Addon/LootJs/API/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/en/mods/armor_tools/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/ArsNouveauJS.md --- # ArsNouveauJS --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/ArsNouveauJS.md --- # ArsNouveauJS --- --- url: 'https://docs.variedmc.cc/en/doc/guide/resource.md' --- --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/KubeJSCourse/Resources/README.md description: 这一章中会简单的介绍kubejs中的材质(assets)使用 --- # assets的使用 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/Entity/Attribute.md --- # 属性 * 一个属性(Attribute)有基础值(base)修饰符(Modifiers)总值(Total)。 ## 前言 * 便捷查看属性注册名。 ```js /** * 在Probejs生成时会一并生成属性注册名的字符串枚举。 * 这个变量没有任何用处,只是为了能够使这个jsdoc生效。 * Ctrl左键点击下方这行的Attribute。 * @type {Special.Attribute} */ let specialAttribute; ``` :::: warning **注意** ::: justify 只有“有生命的实体”才能使用属性操作。 反面例子:矿车,不是有生命的实体。 有生命的实体:继承LivingEntity类,不懂没关系,不影响下边的使用。 ::: :::: ## 属性基础值 ### 获取属性基础值 * 获取属性基础值。 * 语句:livingEntity.getAttribute(属性注册名).getBaseValue(); ```js // 借实体交互事件为例,获取尸壳的最大生命值属性 ItemEvents.entityInteracted(event => { const { entity, target, hand, level, server } = event; if (hand !== 'main_hand' || target.type !== 'minecraft:husk') return; /** * 启动编辑器类型提示可以使用jsdoc * 标注了livingEntity是Internal.LivingEntity类型 * @type {Internal.LivingEntity} */ const livingEntity = target; // 获取了最大生命值属性 const maxHealth = livingEntity.getAttribute('generic.max_health').getBaseValue(); server.tell(`最大生命值:${maxHealth}`); }); ``` ### 修改属性基础值 * 修改属性基础值。 * 如果有修改属性的需要更加建议使用属性修饰符。 语句:livingEntity.getAttribute(属性注册名).setBaseValue(值); ```js ItemEvents.entityInteracted(event => { const { entity, target, hand, level, server } = event; // 此处使用了僵尸作为实验生物 // 在实际操作属性时需要确保被操作的实体类型是LivingEntity的子类 if (hand !== 'main_hand' || target.type !== 'minecraft:husk') return; /** * 为了使编辑器启动类型提示可以使用jsdoc * 因为Attribute是LivingEntity的属性,而target是一个Entity * @type {Internal.LivingEntity} */ const livingEntity = target; // 太长了不便观看 换行了下 livingEntity .getAttribute('generic.max_health') .setBaseValue(30); const maxHealth = livingEntity .getAttribute('generic.max_health') .getBaseValue(); server.tell(`最大生命值:${maxHealth}`); }); ``` ## 属性修饰符 ### 属性操作符 * 用于指定属性修饰符在属性总值的计算中如何计算。 * 类型:"addition" | "multiply\_total" | "multiply\_base" * "addition":修饰符值以加法运算添加到属性总值中。 * "multiply\_total":以修饰符值为乘数,对属性总值进行乘法运算。 * "multiply\_base":以修饰符值为乘数,对属性基础值进行乘法运算。 ### 添加属性修饰符 * 通过添加修饰符来修改属性。 * 语句:livingEntity.modifyAttribute(属性注册名, 字符串标识符, 值, 属性操作符); ```js // 为尸壳添加一个20最大生命上限的属性修饰符 ItemEvents.entityInteracted(event => { const { entity, target, hand, level, server } = event; if (hand !== 'main_hand' || target.type !== 'minecraft:husk') return; /** * 为了使编辑器启动类型提示可以使用jsdoc * 因为Attribute是LivingEntity的属性,而target是一个Entity * @type {Internal.LivingEntity} */ const livingEntity = target; // 太长了不便观看 换行了下 livingEntity .modifyAttribute('generic.max_health', 'kubejs:maxHealth', 20, 'addition'); const maxHealth = livingEntity .getAttribute('generic.max_health') .getBaseValue(); server.tell(`最大生命值:${maxHealth}`); }); ``` ### 获取属性修饰符 * 语句:livingEntity.getAttribute(属性注册名).getModifiers().forEach(attributeModifier=> {}); * 语句:livingEntity.getAttribute(属性注册名).getModifier(属性修饰符Uuid); ::: code-group ```js [通过遍历] ItemEvents.entityInteracted(event => { const { entity, target, hand, level, server } = event; // 此处使用了僵尸作为实验生物 // 在实际操作属性时需要确保被操作的实体类型是LivingEntity的子类 if (hand !== 'main_hand' || target.type !== 'minecraft:husk') return; /** * 为了使编辑器启动类型提示可以使用jsdoc * 因为Attribute是LivingEntity的属性,而target是一个Entity * @type {Internal.LivingEntity} */ const livingEntity = target; livingEntity.getAttribute('generic.max_health').getModifiers().forEach( (attributeModifier) => { // 获取修饰符的Uuid console.log(attributeModifier.id); // 获取修饰符的标识符 console.log(attributeModifier.name); // 获取修饰符的值 console.log(attributeModifier.amount); // 获取修饰符的操作符 console.log(attributeModifier.operation); }); }); ``` ```js [通过修饰符Uuid] // 通过属性修饰符Uuid livingEntity.getAttribute('generic.max_health').getModifier(Uuid) ``` ::: --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/Digression/BlockbenchBasic.md --- # Basic Blockbench Usage ## 1. About Blockbench * [**Blockbench**](https://www.blockbench.net) is one of the most popular modeling tools in Minecraft modding. Most custom-looking blocks you see are made with Blockbench (though some advanced models use [`obj`](../Digression/ForgeReadObjModel) format), for example this smithing table:\ ![smithing\_table\_1.png](/imgs/Blockbench/smithing_table_1.png)![smithing\_table\_2.png](/imgs/Blockbench/smithing_table_2.png) * **File paths** * Model files are usually stored in `assets/${modid}/models`. * Like the `textures` folder, `models` is usually split into `block` and `item`. * Put block models in `block` and item models in `item`. * **Make sure the exported model file is in `json` format.** * Textures go in `assets/${modid}/textures`; similarly, blocks in `block`, items in `item`. If possible, keep models and textures under the same `${modid}`. ## 2. Usage (This tutorial only covers the basics. For advanced workflows, check other guides.) ### **1. Create a Project** * When selecting a model preset, choose the provided Java Edition type. It exports `json`, which is exactly what Minecraft needs.\ ![menu.png](/imgs/Blockbench/menu.png) * After selecting the type, a popup appears. Set the filename to your item/block ID and leave the rest as default.\ ![project.png](/imgs/Blockbench/project.png) ### **2. Create Your First Model** * After entering the workspace, you will see the UI below. This image highlights the most commonly used buttons (a cube here means a model element).\ ![ui.png](/imgs/Blockbench/ui.png) * Create any quick test model first.\ ![model\_1.png](/imgs/Blockbench/model_1.png) * Then save it to `assets/${modid}/models/block` as mentioned above.\ ![model\_2.png](/imgs/Blockbench/save.png)\ Write code... ```js StartupEvents.registry("block", (event) => { // The block ID should match the model filename event.create("test_block") }) ``` ### **3. Set Textures for the Model** * If you register it in-game now, you will likely get the classic purple-black missing texture, and the in-hand/inventory transform will also look wrong.\ ![model\_1.png](/imgs/Blockbench/game/model_1.png) * First, fix textures. Put texture files in `assets/${modid}/textures/block`, then in Blockbench click import texture at the lower-left (you can import multiple textures), or create one yourself (not covered here).\ ![textures\_1.png](/imgs/Blockbench/textures/textures_1.png)![textures\_2.png](/imgs/Blockbench/textures/textures_2.png) * After importing, apply textures to the model. There are two ways: * Drag a texture directly onto the model.\ ![textures\_3.png](/imgs/Blockbench/textures/textures_3.png) * Right-click the model and choose `Select Texture`.\ ![textures\_4.png](/imgs/Blockbench/textures/textures_4.png) * Sometimes you may get strange UV results after applying textures:\ ![textures\_5.png](/imgs/Blockbench/textures/textures_5.png) * Use the UV editor in the top-left. Selecting a region there updates the mapped area on the model in real time. * As for the row of buttons below, try them out yourself and see what each does.\ ![textures\_6.png](/imgs/Blockbench/textures/textures_6.png) * A model has 6 faces, each face can use different texture regions, and each model can use different textures. After finishing UV/textures, save and start the game (or use `F3+T` if the game is already running).\ ![model\_2.png](/imgs/Blockbench/model_2.png) **Launching the game...** * If the texture loads correctly, you did it. * ![model\_2.png](/imgs/Blockbench/model_2.png) **Inventory Model** * You will notice the inventory and in-hand model can still look odd. Open Display Settings (top-right in workspace), click the three-line menu at the top-left, choose `Apply Preset -> Default Block -> Apply to all slots`, then save (or tune it manually).\ ![display\_1.png](/imgs/Blockbench/display_1.png) ![display\_2.png](/imgs/Blockbench/display_2.png) * Reload with `F3+T` like before. ![model\_3.png](/imgs/Blockbench/game/model_3.png) * Now the inventory and in-hand model should look like a normal block. * If block break particles are still purple-black, right-click the texture and choose `Use as Particle Texture`. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSBasic/BasicSyntax-Add.md --- # Basic Syntax (Adding Recipes) This chapter shows how to add or tweak basic recipes, such as crafting table and furnace recipes. For more advanced topics, see [Advanced Recipes](../KubeJSAdvanced/AdvancedRecipe). ## Crafting Table Crafting supports both shaped and shapeless recipes. In KubeJS you can write them using the "simple" style or the JSON-style builder. Choose whichever you prefer. ### Shaped Recipes #### JSON-style First, let's look at the JSON-style syntax: ```js ServerEvents.recipes(event => { event.shaped("3x minecraft:stone", [ 'DDD', ' ', 'W W' ], { D: 'minecraft:diamond', W: 'minecraft:white_wool' }); }); ``` In the code above: The first argument is the output item and output count. Here it is written as a string. If you prefer, you can also write it as `Item.of(itemId, count)`. The second argument is an array representing the 3x3 crafting grid. Each letter is a key that stands for an ingredient. Empty slots can be spaces. The third argument is a JSON object mapping those keys to actual ingredients. In this example, `D` is diamond and `W` is white wool. You can use any letters you like, but you must define what each placeholder means. #### Simple-style If you have used CraftTweaker before, this may feel familiar. It is a simpler, more direct syntax: ```js ServerEvents.recipes(event => { event.shaped(Item.of('minecraft:white_wool', 3), [ ['minecraft:white_wool','minecraft:beacon'],[], ['minecraft:beacon','','minecraft:beacon']]); }); ``` In the code above, the first argument is the output item and count. The second argument is a 2D array. Each inner array represents a row in the crafting grid. Use item ids for ingredients. Use an empty string (`\"\"`) for an empty slot (like the second entry in the third row above). If an entire row is empty you can leave the row as an empty array (like the second row above). ### Shapeless Recipes ```js ServerEvents.recipes(event => { event.shapeless(Item.of('minecraft:redstone',2),[ 'minecraft:stone','minecraft:beacon','minecraft:white_wool','minecraft:enchanting_table' ]); }); ``` For shapeless crafting, the second argument is just a list of input ingredients (item ids). ## Furnace-Type Recipes This section covers the "cooking" family: furnace, smoker, blast furnace, and campfire. The syntax is almost the same for all of them, so they are shown together. ```js ServerEvents.recipes(event => { // Furnace event.smelting('minecraft:bell','minecraft:gold_ingot',1000,2000); // Smoker event.smoking('minecraft:bell','minecraft:gold_ingot',1000,2000); // Campfire event.campfireCooking('minecraft:bell','minecraft:gold_ingot',0,2000); // Blast furnace event.blasting('minecraft:bell','minecraft:gold_ingot',1000,2000); }); ``` In the calls above: The first argument is the output item, the second is the input ingredient, the third is the XP produced by the recipe, and the fourth is the cook time in ticks. (20 ticks = 1 second, so you can write `20 * seconds`.) ### Setting An Item As Fuel Yes, you can also make an item act as fuel in KubeJS: ```js Item.getItem('stone').burnTime = 20*10; ``` Put the item id in `getItem(...)`, then assign `.burnTime` to the fuel time in ticks. Here we set it to 10 seconds, so `20 * 10`. ## Smithing Table Smithing is also simple. Conceptually it is: **item1 + item2 => item3**. ```js ServerEvents.recipes(event => { event.smithing('minecraft:golden_apple','stone','minecraft:apple', 'minecraft:gold_ingot'); }); ``` In the code above, the first argument is the output. The second argument is the smithing template (the first slot). The third and fourth arguments are the other two inputs. If you do not want to specify a custom smithing template, you can omit it and shift the other parameters forward. The default is the netherite upgrade template. ## Stonecutter ```js ServerEvents.recipes(event => { event.stonecutting('minecraft:golden_apple', '#minecraft:planks'); }); ``` The first argument is the output and the second is the input. Note the `#`: this is not an item id but a tag. Any item in the `minecraft:planks` tag can be used as input. ## About Recipe IDs In the recipes above we did not set a recipe id, and unlike CraftTweaker it does not error. That is because KubeJS automatically generates an id. If you want to set your own id, add `.id(\"your_id\")` at the end: ```js ServerEvents.recipes(event => { event.shapeless(Item.of('minecraft:sand', 2), [ 'minecraft:stone', 'minecraft:beacon', 'minecraft:white_wool', 'minecraft:enchanting_table' ]).id("meng_sand"); }); ``` [**Recipe ID deep dive**](../Digression/RecipeId) Important: if you write an id like `\"meng_sand\"` (no namespace), it defaults to the `minecraft` namespace. If you write it like `meng:sand`, the recipe id is in the `meng` namespace, similar to `modid:recipe_name`. If this is unclear, see [Namespace](../Digression/NameSpace). --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSBasic/BasicSyntax-DeleteAndModify.md --- # Basic Syntax: Remove And Modify Recipes This chapter introduces **recipe filters**, and how to use them to remove or modify recipes. ## Recipe Filters In KubeJS, you can use recipe filters when removing or modifying recipes to match recipes that meet certain conditions. The most basic recipe filters are these five: ```js {output : 'item_id'} {input : 'item_id'} {mod : 'mod_id'} {id : 'recipe_id'} {type : 'recipe_type'} ``` Explanation: 1. `output`: matches the output item. If A+B+C crafts into D, then D is the output. When removing recipes, any recipe whose output matches D will be removed. 2. `input`: matches an input ingredient. If A+B+C crafts into D, then A/B/C are inputs. If you set `input` to B, then any recipe that contains B as an ingredient can match. 3. `mod`: matches a mod id. For example, if you set `mod: "create"`, it can match recipes from Create. 4. `id`: matches a specific recipe id. If you have JEI installed and enable advanced tooltips with `F3+H`, you can see recipe ids. Using `id` lets you target the recipe precisely. 5. `type`: matches the recipe type (crafting table, furnace, etc.). Using `type` can remove an entire category of recipes. In addition to these basics, KubeJS also supports combining filters. In real projects you often need stricter matching, so here are three more patterns: ```js {basicFilter1, basicFilter2, basicFilter3, ...} [{basicFilter1, basicFilter2, basicFilter3, ...}, {basicFilter1, basicFilter2, basicFilter3, ...}, ...] {not:{basicFilter1, basicFilter2, basicFilter3, ...}} ``` Explanation and examples: 1. The first form means "match all conditions". If any condition is not satisfied, the recipe does not match. For example, for the `input` filter you might write: ```js ,{input : 'redstone'},{input : 'grass'}} ``` Then a recipe would need to satisfy all three conditions to match. If it only satisfies one or two, it would not match. 2. The second form is similar, but it is an array of "match all" groups. If the recipe matches any one group in the array, it matches. 3. The third form is the negation form. It matches recipes that do NOT match the inner filter. ## Removing Recipes Now that we've introduced recipe filters, let's use them. It's easier to understand by trying them directly. There is only one method to remove recipes: `event.remove(...)`. Everything depends on the recipe filters described above. ```js ServerEvents.recipes(event => { // Remove all recipes whose output is stone bricks event.remove({output : 'stone_bricks'}); // Remove all recipes that have stone bricks as an input event.remove({input : 'stone_bricks'}); // Remove all recipes from the minecraft namespace event.remove({mod : 'minecraft'}); // Remove the recipe with id minecraft:oak_wood (4 oak logs -> oak wood) event.remove({id : "minecraft:oak_wood"}); // Remove all smelting recipes event.remove({type : "smelting"}); }); ``` Aside from removing by exact recipe id, the other filters can match a very broad set of recipes. If an item has both crafting and smelting recipes and you only want to remove the smelting one, you should combine filters: ```js ServerEvents.recipes(event => { event.remove({type:"crafting_shaped",output:"end_stone_bricks"}); }); ``` In 1.20, end stone bricks can be crafted via crafting table and stonecutter. The filter above only removes the crafting-table recipe; the stonecutter recipe still exists. **Note: currently, you cannot remove recipes that you added yourself via KubeJS, no matter what method you use.** ## Replacing Ingredients/Outputs The advanced filters become more useful when replacing inputs/outputs. KubeJS provides two replacement helpers: `replaceInput` (replace matching input ingredients) and `replaceOutput` (replace matching outputs). Here are three examples: ```js ServerEvents.recipes(event =>{ // Only match recipes that include both stick and the planks tag, then replace stick with blaze rod event.replaceInput({input:'minecraft:stick',input:'#minecraft:planks'},'minecraft:stick','minecraft:blaze_rod'); // Only match recipes whose output is dispenser/dropper/furnace, then replace cobblestone input with diamond event.replaceInput([{output:'minecraft:dispenser'},{output:'minecraft:dropper'},{output:'minecraft:furnace'}],"minecraft:cobblestone",'minecraft:diamond'); // For recipes that do NOT match (stick + planks), replace outputs in the logs tag with stone bricks event.replaceOutput({not:{input:'minecraft:stick',input:'#minecraft:planks'}},'#minecraft:logs','minecraft:stone_bricks'); }) ``` ### A Simple Helper Snippet For Removing Recipes ```js // Put the things you want to remove into the arrays below. Remember to separate entries with commas. /* * let output = [ * "minecraft:crafting_table", * "create:brass_ingot" * ] * output.forEach((item) => { * event.remove({ output: item }) * }) */ ServerEvents.recipes((event) => { // Outputs let output = [ ] output.forEach((item) => { event.remove({ output: item }) }) // Inputs let input = [ ] input.forEach((item) => { event.remove({ input: item }) }) // Recipe types let recipeType = [ ] recipeType.forEach((type) => { event.remove({ type: type }) }) // Mod recipes let modRecipes = [ ] modRecipes.forEach((modid) => { event.remove({ mod: modid }) }) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/LootTable/BasicKnowledge/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Introduction/LootTable/BasicKnowledge/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/en/info/doc/module-request/beautify.md' --- # 美化 该页面用于处理美化相关的请求。 --- --- url: 'https://docs.variedmc.cc/zh/info/doc/module-request/beautify.md' --- # 美化 该页面用于处理美化相关的请求。 --- --- url: 'https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/Tag/Biome.md' --- # 生物群系标签 ## 概述 * 原版的生物群系标签应用例:has\_closer\_water\_fog 当玩家在此标签中的生物群系中时,迷雾效果距离玩家更近。 * 原版生物群系标签列表:[minecraft-wiki/标签#生物群系](https://zh.minecraft.wiki/w/%E6%A0%87%E7%AD%BE#%E7%94%9F%E7%89%A9%E7%BE%A4%E7%B3%BB) ## 添加生物群系标签 * 事件:ServerEvents.tags('minecraft:block', event=>{}); * 语句:event.add(标签, 生物群系); ::: code-group ```js ServerEvents.tags('minecraft:biome', event => { // 标签id,生物群系id event.add('kubejs:my_biome_tags', 'minecraft:plains'); }) ``` ```js [KubeJS] ServerEvents.tags('minecraft:biome', event => { /** * 要加标签的生物群系id数组 * @type {Special.Biome[]} */ const tagBiomes = [ 'minecraft:plains' ] for (const biome of tagBiomes) { event.add('kubejs:my_biome_tags', biome); } }) ``` ::: ## 删除生物群系标签 * 事件:ServerEvents.tags('minecraft:biome', event=>{}); * 语句:event.remove(标签, 生物群系); ::: code-group ```js [KubeJS] ServerEvents.tags('minecraft:biome', event => { // 标签id,生物群系id event.remove('kubejs:my_biome_tags', 'minecraft:plains'); }) ``` ```js // 使用for循环对付更多的生物群系id ServerEvents.tags('minecraft:biome', event => { /** * 要加标签的生物群系id数组 * @type {Special.Biome[]} */ const tagBiomes = [ ] for (const biome of tagBiomes) { event.add('kubejs:my_biome_tags', biome); } }) ``` ::: --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/Block/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/Tag/Block.md' --- # 方块标签 ## 概述 * 原版的方块标签应用例:minecraft:air 标签用于冰霜行者魔咒检测水上方的空气。 * 原版方块标签应用列表:[minecraft-wiki/标签#方块](https://zh.minecraft.wiki/w/%E6%A0%87%E7%AD%BE#%E6%96%B9%E5%9D%97) ## 添加方块标签 * 事件:ServerEvents.tags('minecraft:block', event=>{}); * 语句:event.add(标签, 方块); ::: code-group ```js [KubeJS] ServerEvents.tags('minecraft:block', event => { // 标签id,方块id event.add('kubejs:my_block_tags', 'minecraft:grass_block'); }) ``` ```js [KubeJS] // 你可以使用for循环对付更多的方块id ServerEvents.tags('minecraft:block', event => { /** * 要加标签的方块id数组 * @type {Special.Block[]} */ const tagBlocks = [ 'minecraft:grass_block' ] for (const block of tagBlocks) { event.add('kubejs:my_block_tags', block); } }) ``` ::: ## 删除物品标签 * 事件:ServerEvents.tags('minecraft:block', event=>{}); * 语句:event.remove(标签, 方块); ::: code-group ```js [KubeJS] ServerEvents.tags('minecraft:block', event => { // 标签id,方块id event.remove('kubejs:my_block_tags', 'minecraft:grass_block'); }) ``` ```js [KubeJS] // 使用for循环对付更多的方块id ServerEvents.tags('minecraft:block', event => { /** * 从标签删除的方块id数组 * @type {Special.Block[]} */ const tagBlocks = [ 'minecraft:grass_block' ] for (const block of tagBlocks) { event.remove('kubejs:my_block_tags', block); } }) ``` ::: --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Introduction/Block/Description.md --- # Block This section provides an overview and structured entry point for the `Block` directory. --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Introduction/Block/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/zh/modpack/kubejs/1.21/Block/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSBasic/LootTables/BlockLootTable.md --- # Block Loot In Minecraft, block drops are also loot tables. A mined block does not always drop itself, and some drops can be affected by Fortune. What drops depends on what is rolled from the loot pool. ## Override Block Loot The following example creates a Lucky-Block-like random item drop behavior: ```js ServerEvents.blockLootTables(e=>{ Block.getTypeList().forEach(block=>{ e.addBlock(block,l=>{ l.addPool(p=>{ Ingredient.of("@minecraft").getItemIds().forEach(itemId=>{ p.addItem(Item.of(itemId)) }) }) }) }) }) ``` In this example, all mined blocks are changed to drop random Minecraft items. ## Add to Existing Loot The following adds entries to dirt's original loot so it can drop dirt plus the added item: ```js ServerEvents.blockLootTables(e=>{ e.modifyBlock('minecraft:dirt',loot=>{ let pool = [{ "type":"minecraft:item", "name":"minecraft:diamond" }] let arr = loot.pools.get(0).asJsonObject.get("entries").asJsonArray arr.addAll(pool) }) }) ``` In `kubejs-forge-2001.6.5-build.14`, using `JsonArray.add()` directly may throw an error. This example uses `addAll` instead. Since `addAll` needs an array, the entry JSON is wrapped in `[]`. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSBasic/Customs/BlockRegistry.md --- # Block Registration KubeJS can register blocks from the `startup_scripts` folder. Note: custom registrations cannot be hot-reloaded. After writing registration code, you must restart the game for it to take effect. ## Basic Syntax Registering a block in KubeJS is straightforward. The simplest form looks like: ```js StartupEvents.registry("block", (event) => { // event.create(blockId, blockType) event.create("meng:my_block", "basic") }) ``` In the code above, we register a block with id `"meng:my_block"`. The `meng` prefix is the [namespace](../../Digression/NameSpace). It matters for paths when you later add [textures](../../Resources/Texture) and [localization (lang files)](../../Resources/Lang.md). ## Block Types Here we used the `basic` type (the default/basic block type). KubeJS provides multiple block types: | Type | Description | Example | | :--------------: | :------------------------: | :----: | | `basic` | Basic block | TBD | | `carpet` | Carpet | TBD | | `crop` | Crop | [Crop registration](./BlockType/CropReg.md) | | `fence` | Fence | TBD | | `fence_gate` | Fence gate | TBD | | `pressure_plate` | Pressure plate | TBD | | `button` | Button | TBD | | `slab` | Slab | TBD | | `stairs` | Stairs | TBD | | `wall` | Wall | TBD | | `cardinal` | Facing block (lectern, furnace, etc.) | TBD | | `detector` | Detector block? | TBD | | `falling` | Falling block (sand-like) | TBD | In KubeJS, you do not need to register the corresponding block item separately. KubeJS registers the block item for you automatically, and its item id matches the block id. When preparing textures, you generally only need the block textures, not separate block-item textures. ## Common Builder Methods These methods can be used for blocks of any type. ### Common | Method | Args | Description | Return | | :-----------------------------------------------: | :-----------------------------: | :--------------------------------------------: | :------: | | `randomTick(Consumer\)` | - | Random tick callback | this | | `lootTable(Consumer\)` | - | Build a loot table for the block | ~ | | `tagBlock(ResourceLocation)` | [Common args](#common-tagblock-parameters) | Add block tags (mineable tool type, mining level, etc.) | this | | `tagItem(ResourceLocation)` | -> | Add tags to the block item | this | | `noItem()` | - | Do not generate the corresponding item | this | | `displayName(Component)` | -> | Set display name | this | | `lightLevel(float)` | -> | Set light level | this | | `blockEntity(Consumer\)` | - | Create a block entity | this | | `rightClick(Consumer\)` | - | Right-click handler | this | | `noDrops()` | - | No drops when broken | this | | `hardness(float)` | -> | Set hardness (default 1.5) | this | | `speedFactor(float)` | -> | Set speed factor (greater than 1 is faster) | this | | `jumpFactor(float)` | -> | Set jump factor | this | | `noValidSpawns(bool)` | -> | Whether mobs can spawn on this block | this | | `notSolid()` | - | Make the block non-solid (?) | this | | `unbreakable()` | - | Make the block unbreakable | this | | `resistance(float)` | -> | Explosion resistance (default 3) | this | | `requiresTool()` | - | Require a proper tool for drops | this | #### Common `tagBlock()` Parameters * Required tool tag > If a block does not use `requiresTool()`, using the right tool only speeds up mining. > > If a block uses `requiresTool()`, you must use the right tool to get drops. | Tag | Tool | | :----------------------------: | :----------: | | `"minecraft:mineable/sword"` | Sword | | `"minecraft:mineable/pickaxe"` | Pickaxe | | `"minecraft:mineable/axe"` | Axe | | `"minecraft:mineable/shovel"` | Shovel | | `"minecraft:mineable/hoe"` | Hoe | * Required tool tier tag | Tag | Required tier | | :------------------------------: | :------------------------------------------: | | `"minecraft:needs_wooden_tool"` | Wooden | | `"minecraft:needs_stone_tool"` | Stone | | `"minecraft:needs_iron_tool"` | Iron | | `"minecraft:needs_golden_tool"` | Golden | | `"minecraft:needs_diamond_tool"` | Diamond | | `"forge:needs_netherite_tool"` | Netherite (tag provided by `Forge`) | ### Rendering / Collision | Method | Args | Description | Return | | :---------------------------------------------------------: | :--------------------------------------------: | :--------------------------------: | :------: | | `box(double, double, double, double, double, double)` | ~ | ~ | this | | `box(double, double, double, double, double, double, bool)` | ~ | ~ | this | | `defaultCutout()` | - | ~ | this | | `defaultTranslucent()` | - | ~ | this | | `transparent(bool)` | -> | Whether the block is transparent | this | | `noCollision()` | - | No collision box | this | | `renderType(string)` | "cutout"/"cutout\_mipped"/"translucent"/"basic" | Choose render type (these four) | this | | `model(string)` | -> | Model path | this | | `viewBlocking(bool)` | ~ | ~ | this | | `fullBlock(bool)` | -> | Whether this is a full cube | this | | `opaque(bool)` | -> | Whether light can pass through | this | | `material(string)` | -> | ? | this | ### Sound Types | Method | Args | Description | Return | | :--------------------: | :---: | :--------: | :------: | | `glassSoundType()` | - | Glass sound | this | | `grassSoundType()` | - | Grass sound | this | | `sandSoundType()` | - | Sand sound | this | | `stoneSoundType()` | - | Stone sound | this | | `gravelSoundType()` | - | Gravel sound | this | | `cropSoundType()` | - | Crop sound | this | | `woodSoundType()` | - | Wood sound | this | | `noSoundType()` | - | No sound | this | | `soundType(SoundType)` | -> | Custom sound | this | ### Other | Method | Args | Description | Return | | :--------------------------------------------------------------: | :---: | :----------------------------------: | :-----------------------: | | `mirrorState(Consumer\)` | ~ | ~ | this | | `rotateState(Consumer\)` | ~ | ~ | this | | `bounciness(float)` | ~ | Bounciness / elasticity | this | | `canBeReplaced(Predicate\)` | ~ | Whether this block can be replaced | this | | `placementState(Consumer\)` | ~ | Placement callback | this | | `steppedOn(Consumer\)` | ~ | Entity stepped-on callback | this | | `afterFallenOn(Consumer\)` | - | ~ | this | | `fallenOn(Consumer\)` | - | Entity fell on block callback | this | | `tagBoth(ResourceLocation)` | ~ | ~ | this | | `defaultState(Consumer\)` | - | Default block state | this | | `exploded(Consumer\)` | ~ | Called after explosion (block already destroyed) | this | | `canBeWaterlogged()` | - | Whether this block can be waterlogged | bool | | `textureSide(Direction,string)` | ~ | Set texture for a specific side | this | | `mapColor(MapColor)` | -> | Map color | this | | `redstoneConductor(bool)` | -> | Whether this block is a redstone conductor | this | | `textureAll(string)` | -> | Use the same texture for all sides | this | | `suffocating(bool)` | -> | Whether entities suffocate inside | this | | `slipperiness(float)` | ~ | How slippery the block is | this | | `transformObject(Block)` | ~ | ~ | Block | | `texture(string,string)` | ~ | ~ | this | | `property(BlockProperties)` | -> | Set block properties | this | | `item(Consumer\)` | - | Block item builder | this | | `tag(ResourceLocation)` | -> | Set block tags | this | | `color(int,BlockTintFunction)` | ~ | Set tint color for a specific layer | this | | `color(BlockTintFunction)` | ~ | ~ | this | | `createProperties()` | - | ? | BlockBehaviour$Properties | | `generateDataJsons(DataJsonGenerator)` | ~ | ~ | - | | `generateAssetJsons(AssetJsonGenerator)` | ~ | ~ | - | | `getRegistryType()` | - | ~ | RegistryInfo<> | | `waterlogged()` | - | Enable waterlogging | this | | `instrument(NoteBlockInstrument)` | ~ | Note block instrument? | this | | `createAdditionalObjects()` | - | ~ | - | ### Simple Block Registration Helper **Note: customize the snippet below to match your own preferences.** ```js StartupEvents.registry("block", (event) => { // ModID: if you do not want to change it (default is "kubejs"), remove this variable const MODID = "meng:" // Tool tags const toolType = { sword: "minecraft:mineable/sword", pickaxe: "minecraft:mineable/pickaxe", axe: "minecraft:mineable/axe", shovel: "minecraft:mineable/shovel", hoe: "minecraft:mineable/hoe" } // Mining tier tags const miningLevel = { wooden: "minecraft:needs_wooden_tool", stone: "minecraft:needs_stone_tool", iron: "minecraft:needs_iron_tool", gold: "minecraft:needs_gold_tool", diamond: "minecraft:needs_diamond_tool", nether: "forge:needs_netherite_tool" } /* * Block definitions * When adding the next block, remember to add a comma after each array entry. * Follow the format strictly: * [blockName, soundType, hardnessAndResistance, toolTypeKey, miningTierKey] */ let blockRegisters = [ // Example ["example_block", "stone", 3, "pickaxe", "wooden"], ] blockRegisters.forEach(([name, soundType, hardness, tool, level]) => { event.create(MODID + name) // Block id .soundType(soundType) // Sound type .hardness(hardness) // Hardness .resistance(hardness) // Explosion resistance .tagBlock(toolType[tool]) // Tool type tag .tagBlock(miningLevel[level]) // Mining tier tag // .tagItem(MODID + "items") // Optional item tag // .tagItem(MODID + "blocks") // Optional item tag .requiresTool(true) // Require tool for drops }) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAdvanced/EventExamples/BlockRightClickedEvent.md --- # Block Right-Click Event This code belongs in server scripts. There are two ways to use block right-click events: 1. `BlockEvents.rightClicked(blockId, event => {})` captures right-click events for a specific block. 2. `BlockEvents.rightClicked(event => {})` captures right-click events for all blocks. In most cases, use the first form with a specific block. Use the second form only when needed. ## Directly Accessible Methods | Method | Purpose | Return Type | Direct Value | | :---------: | :--------------------------------------: | :--------------: | :--------: | | getBlock() | Get the clicked block info | BlockContainerJS | block | | getEntity() | Get the entity clicking the block (typically the player here) | Player | entity | | getFacing() | Get the face/direction that was clicked | Direction | facing | | getHand() | Get which hand performed the click | InteractionHand | hand | | getItem() | Get the item used to right-click | ItemStack | item | | getLevel() | Get the world of the clicked block | Level | level | | getPlayer() | Get the player who right-clicked | Player | player | | getServer() | Get the server instance | MinecraftServer | server | ## Example The example below uses a block right-click event. When a player right-clicks oak planks with an item tagged as an axe, the planks are destroyed and 8 sticks are dropped. ```js BlockEvents.rightClicked('minecraft:oak_planks', event => { if (event.hand == "OFF_HAND") return let player = event.getPlayer() if (player == null) return let isBreak = false; if (event.getItem().hasTag("minecraft:axes")){ let spawnItem = event.getLevel().createEntity("item") spawnItem.pos = event.block.pos spawnItem.item = Item.of('minecraft:stick', 8); event.level.destroyBlock(event.block.pos,false) spawnItem.spawn(); event.getItem().setDamageValue(event.getItem().getDamageValue() + 2) } }) ``` The block right-click event can fire twice for a player: main hand first, then off hand. So check `if (event.hand == "OFF_HAND")` to filter the hand. `event.getItem()` returns the item in `event.hand`. `event.getItem().hasTag("minecraft:axes")` checks whether the item has that tag. `event.level.destroyBlock(event.block.pos,false)` destroys the block at that position, and `false` means no default drops. --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/KubeJSCourse/Digression/BlockbenchBasic.md --- # BlockBench基础使用 ## 一、关于Blockbench * [**Blockbench**](https://www.blockbench.net)可以说是Minecraft界的建模神器了,你们所见到的奇奇怪怪的方块基本都是Blockbench做的(但也有少数[`obj`](../Digression/ForgeReadObjModel)格式的进阶模型),例如这个锻造台\ ![smithing\_table\_1.png](/imgs/Blockbench/smithing_table_1.png)![smithing\_table\_2.png](/imgs/Blockbench/smithing_table_2.png) * **文件路径** * 模型文件往往存放于`assets/${modid}/models` * 和`textures`文件夹一样,`models`文件夹内一般分为`block`和`item`两个文件夹 * 取决于你做的模型类型如果是方块就放入`block`内,如果是物品就放入`item`内 * **保存时一定得是`json`格式的模型文件** * 而贴图则是置于`assets/${modid}/textures`内,和上面一样,方块放`block`,物品放`item`,可以的话最好和模型存放于一个`${modid}`下 ## 二、使用 (本教程只教最简单的部分,惊喜的部分请自己另找教程) ### **1、创建项目** * 首先在选择模型时请选择他已经给出的Java版模型,这个类型所导出的模型都是json格式,也正好是Minecraft所需要的格式\ ![menu.png](/imgs/Blockbench/menu.png) * 选择模型后他会还有一个弹窗,文件名写你的物品或方块ID即可,其它不用管\ ![project.png](/imgs/Blockbench/project.png) ### **2、创建第一个模型** * 在进入工作区后我们会看到下面的UI,通过下图来了解一下工作区最常用的几个按钮所相对应的功能(块其实就是模型)\ ![ui.png](/imgs/Blockbench/ui.png) * 我们先一顿操作猛如虎随便搞个模型出来\ ![model\_1.png](/imgs/Blockbench/model_1.png) * 随后我们保存模型,保存到上面所说的`assets/${modid}/models/block`\ ![model\_2.png](/imgs/Blockbench/save.png)\ 写代码... ```js StartupEvents.registry("block", (event) => { // 方块ID要和模型文件名一样 event.create("test_block") }) ``` ### **3、为模型设置贴图** * 这个时候如果注册进游戏显示的将会是下图...紫黑块,并且手上的模型和物品栏模型的方向以及大小看起来和别的方块很不一样...\ ![model\_1.png](/imgs/Blockbench/game/model_1.png) * 我们先来解决一下贴图的问题,我们将贴图放在上面提到的`assets/${modid}/textures/block`中,然后打开blockbench点击左下方的导入纹理(也可以导入多个纹理),或是自己创建一个纹理(这里不教)\ ![textures\_1.png](/imgs/Blockbench/textures/textures_1.png)![textures\_2.png](/imgs/Blockbench/textures/textures_2.png) * 导入纹理后我们还要给模型贴上贴图,有两种方式 * 第一种是直接将贴图给拖到模型上\ ![textures\_3.png](/imgs/Blockbench/textures/textures_3.png) * 第二种是右键模型`选择纹理`\ ![textures\_4.png](/imgs/Blockbench/textures/textures_4.png) * 有时候贴上贴图后会有下面这奇怪的现象\ ![textures\_5.png](/imgs/Blockbench/textures/textures_5.png) * 把视角放到左上角的贴图处理的地方,这里的贴图处理可以选择贴图的所选位置,在这里进行选取的贴图区域在模型上也会同步展示 * 至于下面那一排按钮...为什么不自己动手试试呢? Ciallo ~(∠·ω< )⌒☆\ ![textures\_6.png](/imgs/Blockbench/textures/textures_6.png) * 一个模型一共有6个面,每个面都可以是不同的贴图,而每个模型都可以使用不同的贴图,还是一顿操作猛如虎我们把纹理都处理好后保存,然后启动游戏(如果开着游戏`F3+T`也可以)\ ![model\_2.png](/imgs/Blockbench/model_2.png) **嘿咻嘿咻~ 游戏启动中~** * 如果你的贴图成功加载出来了那恭喜你成功了 * 现在!去楼下小卖部给自己买一根5块钱以上的雪糕好好犒劳一下自己 * ![model\_2.png](/imgs/Blockbench/model_2.png) **物品栏模型** * 不难看到我们物品栏中的和手上的模型看起来非常奇怪,这个也很简单,我们点开工作区右上角的显示调整,然后点击左上角的3条横,选择`默认方块/三个点/应用于所有槽位`,保存!(当然你也可以选择自己调整)\ ![display\_1.png](/imgs/Blockbench/display_1.png) ![display\_2.png](/imgs/Blockbench/display_2.png) * 和刚才一样的步骤,`F3+T`重载一下 ![model\_3.png](/imgs/Blockbench/game/model_3.png) * 可以看到物品栏中和手上的模型都和正常的方块一样了 * 如果在破坏方块时他的粒子效果也是紫黑色,可以右键你需要的纹理,然后选择`作为粒子纹理` --- --- url: 'https://docs.variedmc.cc/en/info/KubeJS/borad/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/BotaniaJS.md --- # BotaniaJS --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/BotaniaJS.md --- # BotaniaJS --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/BotanyPotsJS.md --- # BotanyPotsJS --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/BotanyPotsJS.md --- # BotanyPotsJS --- --- url: 'https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Entity/Brain.md' description: Minecraft 1.16+ Brain AI 系统机制与 EntityJS 用法 --- # Brain 系统指南 ## 概述 {#overview} Brain 系统是 Minecraft 1.16+ 的现代化 AI 架构,采用记忆驱动的行为管理。相比 Goal 系统,Brain 提供更智能的决策和状态管理能力。 ### 核心组件 ```mermaid graph TB A[Brain] --> B[Memory Modules] A --> C[Behaviors] A --> D[Sensors] A --> E[Activities] B --> F[WALK_TARGET] B --> G[ATTACK_TARGET] B --> H[HOME] C --> I[MeleeAttack] C --> J[RandomStroll] C --> K[LookAtPlayer] D --> L[NearestPlayerSensor] D --> M[HurtBySensor] E --> N[CORE] E --> O[IDLE] E --> P[PANIC] ``` ## Brain 执行流程 {#mechanism} ```mermaid flowchart LR A[Brain Tick] --> B[更新传感器] B --> C[刷新记忆] C --> D[选择活动] D --> E[执行行为] E --> F[更新记忆] F --> A ``` ## 主要组件与支持表 {#components} | 组件类型 | 关键成员/方法 | 说明 | KubeJS 支持 | EntityJS 支持 | | ---------------- | ---------------------------- | ----------------- | ----------- | ------------- | | 记忆模块 | WALK\_TARGET, HOME 等 | 实体的短/长期记忆 | 部分 | 全部 | | 行为(Behavior) | meleeAttack, randomStroll 等 | 行为逻辑单元 | 仅 Goal | 全部 | | 活动(Activity) | CORE, IDLE, PANIC 等 | 行为分组与切换 | 仅 Goal | 全部 | | 传感器(Sensor) | nearest\_player, hurt\_by 等 | 感知环境与实体 | 否 | 全部 | *** ## KubeJS 原生操作 {#kubejs\_native} ::: code-group ```js [KubeJS] const brain = mob.getBrain(); const $MemoryModuleType = Java.loadClass( "net.minecraft.world.entity.ai.memory.MemoryModuleType" ); // 读取/设置/清除记忆 brain.setMemory($MemoryModuleType.ATTACK_TARGET, targetEntity); brain.eraseMemory($MemoryModuleType.WALK_TARGET); if (brain.hasMemoryValue($MemoryModuleType.HOME)) { /* ... */ } ``` ```js [EntityJS] EntityJSEvents.buildBrain("minecraft:villager", (event) => { event.coreActivity(1, [event.behaviors.meleeAttack(5)]); event.idleActivity(1, [event.behaviors.randomStroll(8, 1.0)]); }); ``` ::: * 复杂记忆如 WalkTarget 推荐用 EntityJS 设置。 * 传感器、活动注册仅 EntityJS 支持。 *** ## 行为/记忆/传感器表 {#tables} | 类型 | 名称 | 说明 | KubeJS | EntityJS | | ------ | --------------- | ------------ | ------- | -------- | | 记忆 | WALK\_TARGET | 移动目标 | 部分 | 支持 | | 记忆 | ATTACK\_TARGET | 攻击目标 | 支持 | 支持 | | 记忆 | HOME | 家/归属地 | 支持 | 支持 | | 记忆 | ANGRY\_AT | 愤怒对象 | 支持 | 支持 | | 行为 | meleeAttack | 近战攻击 | 仅 Goal | 支持 | | 行为 | randomStroll | 随机游走 | 仅 Goal | 支持 | | 行为 | lookAtPlayer | 注视玩家 | 仅 Goal | 支持 | | 行为 | animalPanic | 恐慌逃跑 | 仅 Goal | 支持 | | 活动 | CORE/IDLE/PANIC | 行为分组 | 仅 Goal | 支持 | | 传感器 | nearest\_player | 感知最近玩家 | 否 | 支持 | | 传感器 | hurt\_by | 感知受伤 | 否 | 支持 | *** ## 限制与解决方案 {#limitations} ::: code-group ```js [反例] // KubeJS 直接构造 WalkTarget 可能报错 const $WalkTarget = Java.loadClass( "net.minecraft.world.entity.ai.memory.WalkTarget" ); const walkTarget = new $WalkTarget(blockPos, 1.0, 5); // 可能失败 ``` ```js [推荐] // 推荐用 EntityJS buildBrain 设置移动目标 EntityJSEvents.buildBrain("minecraft:villager", (event) => { event.idleActivity(1, [event.behaviors.randomStroll(8, 1.0)]); }); ``` ::: * 复杂记忆、行为建议用 EntityJS。 * KubeJS 仅适合简单记忆赋值。 *** ## 调试与性能优化 {#tips} ::: code-group ```js [调试推荐] function debugBrain(entity) { const brain = entity.getBrain(); const $MemoryModuleType = Java.loadClass( "net.minecraft.world.entity.ai.memory.MemoryModuleType" ); ["WALK_TARGET", "ATTACK_TARGET", "HOME"].forEach((type) => { if (brain.hasMemoryValue($MemoryModuleType[type])) { console.log(`${type}: ${brain.getMemory($MemoryModuleType[type])}`); } }); } ``` ::: *** ## EntityJS Brain/Behavior/Goal 系统详解 {#entityjs\_brain\_goal} ### 概述与架构 {#overview\_brain\_goal} Brain/Goal 系统是 Minecraft 现代 AI 的核心,EntityJS 提供了极为丰富的行为工厂、活动注册、记忆/传感器管理能力,适合复杂 NPC、智能生物等场景。 *** ### EntityJS Behaviors 工厂方法参数表 {#behaviors\_factory\_table} | 方法名 | 适用实体 | 参数 | 参数类型 | 说明 | | --------------- | ---------- | --------------------- | ------------------------------ | ---------------- | | acquirePoi | pathfinder | poiType | Predicate\> | 兴趣点筛选 | | | | memoryKey | MemoryModuleType\ | 需缺失的记忆类型 | | | | memoryToAcquire | MemoryModuleType\ | 获取后写入的记忆 | | | | onlyIfAdult | boolean | 仅限成年体 | | | | onPoiAcquisitionEvent | Byte/Null | 事件 ID,可空 | | animalMakeLove | animal | partnerType | EntityType | 可繁殖对象类型 | | | | speedModifier | float | 行为激活时速度 | | animalPanic | pathfinder | speedModifier | float | 恐慌时速度 | | babyFollowAdult | ageable | minFollowRange | int | 最小跟随距离 | | | | maxFollowRange | int | 最大跟随距离 | | | | speedModifier | Function\ | 跟随速度 | | ... | ... | ... | ... | ... | > **建议:文档中完整列出所有 Behaviors 方法及参数,详见源码。** *** ### buildBrainProvider/buildBrain/addActivity/coreActivity/idleActivity 参数表 {#brain\_provider\_params} | 方法名 | 参数 | 类型 | 说明 | | ------------ | --------- | ---------------- | ---------------- | | addMemory | memory | MemoryModuleType | 注册记忆模块 | | addSensor | sensor | SensorType | 注册传感器 | | coreActivity | priority | int | 优先级,越小越高 | | | behaviors | List\ | 行为列表 | | idleActivity | priority | int | 优先级 | | | behaviors | List\ | 行为列表 | | addActivity | activity | Activity/字符串 | 活动类型 | | | priority | int | 优先级 | | | behaviors | List\ | 行为列表 | *** ### Activity/行为注册机制说明 {#activity\_mechanism} * `coreActivity`:实体始终活跃的基础行为(如呼吸、基础移动)。 * `idleActivity`:实体空闲时的行为(如游走、观察)。 * `addActivity`:可注册任意自定义活动(如"工作"、"战斗"、"恐慌"等),并指定优先级和行为集。 * 活动切换由 Brain 系统根据记忆、传感器等自动管理。 *** ### Goal/GoalSelector/GoalEventJS 类型说明 {#goal\_types} * **GoalSelector**:行为目标选择器(goalSelector)/攻击目标选择器(targetSelector)。 * **GoalEventJS**:暴露 isPathFinder、isAnimal、isTamable、isRangedAttack、isHorse、isNeutral 等属性,便于类型判断。 * **getEntity()**:返回当前 Mob 实例。 *** ### code-group 实用示例 {#code\_examples} ::: code-group ```js [EntityJS] EntityJSEvents.buildBrainProvider("minecraft:villager", (event) => { event.addMemory(MemoryModuleType.HOME); event.addSensor(SensorType.NEAREST_PLAYER); }); EntityJSEvents.buildBrain("minecraft:villager", (event) => { event.coreActivity(1, [ event.behaviors.meleeAttack(5), event.behaviors.lookAtPlayer(6, 8), ]); event.idleActivity(2, [event.behaviors.randomStroll(8, 1.0)]); event.addActivity(Activity.WORK, 3, [ event.behaviors.animalMakeLove(EntityType.VILLAGER, 0.2), ]); }); ``` ::: *** ### 常见易错点/性能建议/最佳实践 {#best\_practices} * **行为/活动优先级**:数值越小优先级越高,避免冲突。 * **行为参数类型**:如 speedModifier 可为 float 或 Function,注意类型一致。 * **性能陷阱**:行为 tick 内避免全局遍历实体,推荐缓存/降频。 * **Goal/Brain 混用**:Goal 适合简单 AI,Brain 适合复杂决策。 * **EntityJS 行为工厂方法**:仅能用于 buildBrain 事件,不能直接用于 goalSelector。 *** ### Markdown 锚点唯一性说明 {#anchor\_uniqueness} * 保证每个 `{#xxx}` 只出现一次,且全局唯一。 * 推荐用 `{#custom_goal_xxx}` 这种带后缀的唯一 id。 * 检查所有 `## ... {#...}`,确保 id 唯一,避免 copy-paste 时重复。 *** > 如需完整 Behaviors 方法参数表、全部事件参数表、详细 code-group 示例等,请指定需要哪一部分,可直接为你生成 markdown 片段,确保文档极致详尽且无锚点冲突! --- --- url: 'https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Introduction/Entity/Brain.md' description: Minecraft 1.16+ Brain AI 系统机制与 EntityJS 用法 --- # Brain 系统指南 ## 概述 {#overview} Brain 系统是 Minecraft 1.16+ 的现代化 AI 架构,采用记忆驱动的行为管理。相比 Goal 系统,Brain 提供更智能的决策和状态管理能力。 ### 核心组件 ```mermaid graph TB A[Brain] --> B[Memory Modules] A --> C[Behaviors] A --> D[Sensors] A --> E[Activities] B --> F[WALK_TARGET] B --> G[ATTACK_TARGET] B --> H[HOME] C --> I[MeleeAttack] C --> J[RandomStroll] C --> K[LookAtPlayer] D --> L[NearestPlayerSensor] D --> M[HurtBySensor] E --> N[CORE] E --> O[IDLE] E --> P[PANIC] ``` ## Brain 执行流程 {#mechanism} ```mermaid flowchart LR A[Brain Tick] --> B[更新传感器] B --> C[刷新记忆] C --> D[选择活动] D --> E[执行行为] E --> F[更新记忆] F --> A ``` ## 主要组件与支持表 {#components} | 组件类型 | 关键成员/方法 | 说明 | KubeJS 支持 | EntityJS 支持 | | ---------------- | ---------------------------- | ----------------- | ----------- | ------------- | | 记忆模块 | WALK\_TARGET, HOME 等 | 实体的短/长期记忆 | 部分 | 全部 | | 行为(Behavior) | meleeAttack, randomStroll 等 | 行为逻辑单元 | 仅 Goal | 全部 | | 活动(Activity) | CORE, IDLE, PANIC 等 | 行为分组与切换 | 仅 Goal | 全部 | | 传感器(Sensor) | nearest\_player, hurt\_by 等 | 感知环境与实体 | 否 | 全部 | *** ## KubeJS 原生操作 {#kubejs\_native} ::: code-group ```js [KubeJS] const brain = mob.getBrain(); const $MemoryModuleType = Java.loadClass( "net.minecraft.world.entity.ai.memory.MemoryModuleType" ); // 读取/设置/清除记忆 brain.setMemory($MemoryModuleType.ATTACK_TARGET, targetEntity); brain.eraseMemory($MemoryModuleType.WALK_TARGET); if (brain.hasMemoryValue($MemoryModuleType.HOME)) { /* ... */ } ``` ```js [EntityJS] EntityJSEvents.buildBrain("minecraft:villager", (event) => { event.coreActivity(1, [event.behaviors.meleeAttack(5)]); event.idleActivity(1, [event.behaviors.randomStroll(8, 1.0)]); }); ``` ::: * 复杂记忆如 WalkTarget 推荐用 EntityJS 设置。 * 传感器、活动注册仅 EntityJS 支持。 *** ## 行为/记忆/传感器表 {#tables} | 类型 | 名称 | 说明 | KubeJS | EntityJS | | ------ | --------------- | ------------ | ------- | -------- | | 记忆 | WALK\_TARGET | 移动目标 | 部分 | 支持 | | 记忆 | ATTACK\_TARGET | 攻击目标 | 支持 | 支持 | | 记忆 | HOME | 家/归属地 | 支持 | 支持 | | 记忆 | ANGRY\_AT | 愤怒对象 | 支持 | 支持 | | 行为 | meleeAttack | 近战攻击 | 仅 Goal | 支持 | | 行为 | randomStroll | 随机游走 | 仅 Goal | 支持 | | 行为 | lookAtPlayer | 注视玩家 | 仅 Goal | 支持 | | 行为 | animalPanic | 恐慌逃跑 | 仅 Goal | 支持 | | 活动 | CORE/IDLE/PANIC | 行为分组 | 仅 Goal | 支持 | | 传感器 | nearest\_player | 感知最近玩家 | 否 | 支持 | | 传感器 | hurt\_by | 感知受伤 | 否 | 支持 | *** ## 限制与解决方案 {#limitations} ::: code-group ```js [反例] // KubeJS 直接构造 WalkTarget 可能报错 const $WalkTarget = Java.loadClass( "net.minecraft.world.entity.ai.memory.WalkTarget" ); const walkTarget = new $WalkTarget(blockPos, 1.0, 5); // 可能失败 ``` ```js [推荐] // 推荐用 EntityJS buildBrain 设置移动目标 EntityJSEvents.buildBrain("minecraft:villager", (event) => { event.idleActivity(1, [event.behaviors.randomStroll(8, 1.0)]); }); ``` ::: * 复杂记忆、行为建议用 EntityJS。 * KubeJS 仅适合简单记忆赋值。 *** ## 调试与性能优化 {#tips} ::: code-group ```js [调试推荐] function debugBrain(entity) { const brain = entity.getBrain(); const $MemoryModuleType = Java.loadClass( "net.minecraft.world.entity.ai.memory.MemoryModuleType" ); ["WALK_TARGET", "ATTACK_TARGET", "HOME"].forEach((type) => { if (brain.hasMemoryValue($MemoryModuleType[type])) { console.log(`${type}: ${brain.getMemory($MemoryModuleType[type])}`); } }); } ``` ::: *** ## EntityJS Brain/Behavior/Goal 系统详解 {#entityjs\_brain\_goal} ### 概述与架构 {#overview\_brain\_goal} Brain/Goal 系统是 Minecraft 现代 AI 的核心,EntityJS 提供了极为丰富的行为工厂、活动注册、记忆/传感器管理能力,适合复杂 NPC、智能生物等场景。 *** ### EntityJS Behaviors 工厂方法参数表 {#behaviors\_factory\_table} | 方法名 | 适用实体 | 参数 | 参数类型 | 说明 | | --------------- | ---------- | --------------------- | ------------------------------ | ---------------- | | acquirePoi | pathfinder | poiType | Predicate\> | 兴趣点筛选 | | | | memoryKey | MemoryModuleType\ | 需缺失的记忆类型 | | | | memoryToAcquire | MemoryModuleType\ | 获取后写入的记忆 | | | | onlyIfAdult | boolean | 仅限成年体 | | | | onPoiAcquisitionEvent | Byte/Null | 事件 ID,可空 | | animalMakeLove | animal | partnerType | EntityType | 可繁殖对象类型 | | | | speedModifier | float | 行为激活时速度 | | animalPanic | pathfinder | speedModifier | float | 恐慌时速度 | | babyFollowAdult | ageable | minFollowRange | int | 最小跟随距离 | | | | maxFollowRange | int | 最大跟随距离 | | | | speedModifier | Function\ | 跟随速度 | | ... | ... | ... | ... | ... | > **建议:文档中完整列出所有 Behaviors 方法及参数,详见源码。** *** ### buildBrainProvider/buildBrain/addActivity/coreActivity/idleActivity 参数表 {#brain\_provider\_params} | 方法名 | 参数 | 类型 | 说明 | | ------------ | --------- | ---------------- | ---------------- | | addMemory | memory | MemoryModuleType | 注册记忆模块 | | addSensor | sensor | SensorType | 注册传感器 | | coreActivity | priority | int | 优先级,越小越高 | | | behaviors | List\ | 行为列表 | | idleActivity | priority | int | 优先级 | | | behaviors | List\ | 行为列表 | | addActivity | activity | Activity/字符串 | 活动类型 | | | priority | int | 优先级 | | | behaviors | List\ | 行为列表 | *** ### Activity/行为注册机制说明 {#activity\_mechanism} * `coreActivity`:实体始终活跃的基础行为(如呼吸、基础移动)。 * `idleActivity`:实体空闲时的行为(如游走、观察)。 * `addActivity`:可注册任意自定义活动(如"工作"、"战斗"、"恐慌"等),并指定优先级和行为集。 * 活动切换由 Brain 系统根据记忆、传感器等自动管理。 *** ### Goal/GoalSelector/GoalEventJS 类型说明 {#goal\_types} * **GoalSelector**:行为目标选择器(goalSelector)/攻击目标选择器(targetSelector)。 * **GoalEventJS**:暴露 isPathFinder、isAnimal、isTamable、isRangedAttack、isHorse、isNeutral 等属性,便于类型判断。 * **getEntity()**:返回当前 Mob 实例。 *** ### code-group 实用示例 {#code\_examples} ::: code-group ```js [EntityJS] EntityJSEvents.buildBrainProvider("minecraft:villager", (event) => { event.addMemory(MemoryModuleType.HOME); event.addSensor(SensorType.NEAREST_PLAYER); }); EntityJSEvents.buildBrain("minecraft:villager", (event) => { event.coreActivity(1, [ event.behaviors.meleeAttack(5), event.behaviors.lookAtPlayer(6, 8), ]); event.idleActivity(2, [event.behaviors.randomStroll(8, 1.0)]); event.addActivity(Activity.WORK, 3, [ event.behaviors.animalMakeLove(EntityType.VILLAGER, 0.2), ]); }); ``` ::: *** ### 常见易错点/性能建议/最佳实践 {#best\_practices} * **行为/活动优先级**:数值越小优先级越高,避免冲突。 * **行为参数类型**:如 speedModifier 可为 float 或 Function,注意类型一致。 * **性能陷阱**:行为 tick 内避免全局遍历实体,推荐缓存/降频。 * **Goal/Brain 混用**:Goal 适合简单 AI,Brain 适合复杂决策。 * **EntityJS 行为工厂方法**:仅能用于 buildBrain 事件,不能直接用于 goalSelector。 *** ### Markdown 锚点唯一性说明 {#anchor\_uniqueness} * 保证每个 `{#xxx}` 只出现一次,且全局唯一。 * 推荐用 `{#custom_goal_xxx}` 这种带后缀的唯一 id。 * 检查所有 `## ... {#...}`,确保 id 唯一,避免 copy-paste 时重复。 *** > 如需完整 Behaviors 方法参数表、全部事件参数表、详细 code-group 示例等,请指定需要哪一部分,可直接为你生成 markdown 片段,确保文档极致详尽且无锚点冲突! --- --- url: 'https://docs.variedmc.cc/zh/modpack/kubejs/1.21/Entity/Brain.md' description: Minecraft 1.16+ Brain AI 系统机制与 EntityJS 用法 --- # Brain 系统指南 ## 概述 {#overview} Brain 系统是 Minecraft 1.16+ 的现代化 AI 架构,采用记忆驱动的行为管理。相比 Goal 系统,Brain 提供更智能的决策和状态管理能力。 ### 核心组件 ```mermaid graph TB A[Brain] --> B[Memory Modules] A --> C[Behaviors] A --> D[Sensors] A --> E[Activities] B --> F[WALK_TARGET] B --> G[ATTACK_TARGET] B --> H[HOME] C --> I[MeleeAttack] C --> J[RandomStroll] C --> K[LookAtPlayer] D --> L[NearestPlayerSensor] D --> M[HurtBySensor] E --> N[CORE] E --> O[IDLE] E --> P[PANIC] ``` ## Brain 执行流程 {#mechanism} ```mermaid flowchart LR A[Brain Tick] --> B[更新传感器] B --> C[刷新记忆] C --> D[选择活动] D --> E[执行行为] E --> F[更新记忆] F --> A ``` ## 主要组件与支持表 {#components} | 组件类型 | 关键成员/方法 | 说明 | KubeJS 支持 | EntityJS 支持 | | ---------------- | ---------------------------- | ----------------- | ----------- | ------------- | | 记忆模块 | WALK\_TARGET, HOME 等 | 实体的短/长期记忆 | 部分 | 全部 | | 行为(Behavior) | meleeAttack, randomStroll 等 | 行为逻辑单元 | 仅 Goal | 全部 | | 活动(Activity) | CORE, IDLE, PANIC 等 | 行为分组与切换 | 仅 Goal | 全部 | | 传感器(Sensor) | nearest\_player, hurt\_by 等 | 感知环境与实体 | 否 | 全部 | *** ## KubeJS 原生操作 {#kubejs\_native} ::: code-group ```js [KubeJS] const brain = mob.getBrain(); const $MemoryModuleType = Java.loadClass( "net.minecraft.world.entity.ai.memory.MemoryModuleType" ); // 读取/设置/清除记忆 brain.setMemory($MemoryModuleType.ATTACK_TARGET, targetEntity); brain.eraseMemory($MemoryModuleType.WALK_TARGET); if (brain.hasMemoryValue($MemoryModuleType.HOME)) { /* ... */ } ``` ```js [EntityJS] EntityJSEvents.buildBrain("minecraft:villager", (event) => { event.coreActivity(1, [event.behaviors.meleeAttack(5)]); event.idleActivity(1, [event.behaviors.randomStroll(8, 1.0)]); }); ``` ::: * 复杂记忆如 WalkTarget 推荐用 EntityJS 设置。 * 传感器、活动注册仅 EntityJS 支持。 *** ## 行为/记忆/传感器表 {#tables} | 类型 | 名称 | 说明 | KubeJS | EntityJS | | ------ | --------------- | ------------ | ------- | -------- | | 记忆 | WALK\_TARGET | 移动目标 | 部分 | 支持 | | 记忆 | ATTACK\_TARGET | 攻击目标 | 支持 | 支持 | | 记忆 | HOME | 家/归属地 | 支持 | 支持 | | 记忆 | ANGRY\_AT | 愤怒对象 | 支持 | 支持 | | 行为 | meleeAttack | 近战攻击 | 仅 Goal | 支持 | | 行为 | randomStroll | 随机游走 | 仅 Goal | 支持 | | 行为 | lookAtPlayer | 注视玩家 | 仅 Goal | 支持 | | 行为 | animalPanic | 恐慌逃跑 | 仅 Goal | 支持 | | 活动 | CORE/IDLE/PANIC | 行为分组 | 仅 Goal | 支持 | | 传感器 | nearest\_player | 感知最近玩家 | 否 | 支持 | | 传感器 | hurt\_by | 感知受伤 | 否 | 支持 | *** ## 限制与解决方案 {#limitations} ::: code-group ```js [反例] // KubeJS 直接构造 WalkTarget 可能报错 const $WalkTarget = Java.loadClass( "net.minecraft.world.entity.ai.memory.WalkTarget" ); const walkTarget = new $WalkTarget(blockPos, 1.0, 5); // 可能失败 ``` ```js [推荐] // 推荐用 EntityJS buildBrain 设置移动目标 EntityJSEvents.buildBrain("minecraft:villager", (event) => { event.idleActivity(1, [event.behaviors.randomStroll(8, 1.0)]); }); ``` ::: * 复杂记忆、行为建议用 EntityJS。 * KubeJS 仅适合简单记忆赋值。 *** ## 调试与性能优化 {#tips} ::: code-group ```js [调试推荐] function debugBrain(entity) { const brain = entity.getBrain(); const $MemoryModuleType = Java.loadClass( "net.minecraft.world.entity.ai.memory.MemoryModuleType" ); ["WALK_TARGET", "ATTACK_TARGET", "HOME"].forEach((type) => { if (brain.hasMemoryValue($MemoryModuleType[type])) { console.log(`${type}: ${brain.getMemory($MemoryModuleType[type])}`); } }); } ``` ::: *** ## EntityJS Brain/Behavior/Goal 系统详解 {#entityjs\_brain\_goal} ### 概述与架构 {#overview\_brain\_goal} Brain/Goal 系统是 Minecraft 现代 AI 的核心,EntityJS 提供了极为丰富的行为工厂、活动注册、记忆/传感器管理能力,适合复杂 NPC、智能生物等场景。 *** ### EntityJS Behaviors 工厂方法参数表 {#behaviors\_factory\_table} | 方法名 | 适用实体 | 参数 | 参数类型 | 说明 | | --------------- | ---------- | --------------------- | ------------------------------ | ---------------- | | acquirePoi | pathfinder | poiType | Predicate\> | 兴趣点筛选 | | | | memoryKey | MemoryModuleType\ | 需缺失的记忆类型 | | | | memoryToAcquire | MemoryModuleType\ | 获取后写入的记忆 | | | | onlyIfAdult | boolean | 仅限成年体 | | | | onPoiAcquisitionEvent | Byte/Null | 事件 ID,可空 | | animalMakeLove | animal | partnerType | EntityType | 可繁殖对象类型 | | | | speedModifier | float | 行为激活时速度 | | animalPanic | pathfinder | speedModifier | float | 恐慌时速度 | | babyFollowAdult | ageable | minFollowRange | int | 最小跟随距离 | | | | maxFollowRange | int | 最大跟随距离 | | | | speedModifier | Function\ | 跟随速度 | | ... | ... | ... | ... | ... | > **建议:文档中完整列出所有 Behaviors 方法及参数,详见源码。** *** ### buildBrainProvider/buildBrain/addActivity/coreActivity/idleActivity 参数表 {#brain\_provider\_params} | 方法名 | 参数 | 类型 | 说明 | | ------------ | --------- | ---------------- | ---------------- | | addMemory | memory | MemoryModuleType | 注册记忆模块 | | addSensor | sensor | SensorType | 注册传感器 | | coreActivity | priority | int | 优先级,越小越高 | | | behaviors | List\ | 行为列表 | | idleActivity | priority | int | 优先级 | | | behaviors | List\ | 行为列表 | | addActivity | activity | Activity/字符串 | 活动类型 | | | priority | int | 优先级 | | | behaviors | List\ | 行为列表 | *** ### Activity/行为注册机制说明 {#activity\_mechanism} * `coreActivity`:实体始终活跃的基础行为(如呼吸、基础移动)。 * `idleActivity`:实体空闲时的行为(如游走、观察)。 * `addActivity`:可注册任意自定义活动(如"工作"、"战斗"、"恐慌"等),并指定优先级和行为集。 * 活动切换由 Brain 系统根据记忆、传感器等自动管理。 *** ### Goal/GoalSelector/GoalEventJS 类型说明 {#goal\_types} * **GoalSelector**:行为目标选择器(goalSelector)/攻击目标选择器(targetSelector)。 * **GoalEventJS**:暴露 isPathFinder、isAnimal、isTamable、isRangedAttack、isHorse、isNeutral 等属性,便于类型判断。 * **getEntity()**:返回当前 Mob 实例。 *** ### code-group 实用示例 {#code\_examples} ::: code-group ```js [EntityJS] EntityJSEvents.buildBrainProvider("minecraft:villager", (event) => { event.addMemory(MemoryModuleType.HOME); event.addSensor(SensorType.NEAREST_PLAYER); }); EntityJSEvents.buildBrain("minecraft:villager", (event) => { event.coreActivity(1, [ event.behaviors.meleeAttack(5), event.behaviors.lookAtPlayer(6, 8), ]); event.idleActivity(2, [event.behaviors.randomStroll(8, 1.0)]); event.addActivity(Activity.WORK, 3, [ event.behaviors.animalMakeLove(EntityType.VILLAGER, 0.2), ]); }); ``` ::: *** ### 常见易错点/性能建议/最佳实践 {#best\_practices} * **行为/活动优先级**:数值越小优先级越高,避免冲突。 * **行为参数类型**:如 speedModifier 可为 float 或 Function,注意类型一致。 * **性能陷阱**:行为 tick 内避免全局遍历实体,推荐缓存/降频。 * **Goal/Brain 混用**:Goal 适合简单 AI,Brain 适合复杂决策。 * **EntityJS 行为工厂方法**:仅能用于 buildBrain 事件,不能直接用于 goalSelector。 *** ### Markdown 锚点唯一性说明 {#anchor\_uniqueness} * 保证每个 `{#xxx}` 只出现一次,且全局唯一。 * 推荐用 `{#custom_goal_xxx}` 这种带后缀的唯一 id。 * 检查所有 `## ... {#...}`,确保 id 唯一,避免 copy-paste 时重复。 *** > 如需完整 Behaviors 方法参数表、全部事件参数表、详细 code-group 示例等,请指定需要哪一部分,可直接为你生成 markdown 片段,确保文档极致详尽且无锚点冲突! --- --- url: 'https://docs.variedmc.cc/en/mods/adventure/Champions-Unofficial/Catalogue.md' description: >- Catalogue for Champions Unofficial overview, configuration, and balance references. --- # Champions Unofficial This directory collects the maintained reference pages for Champions Unofficial. ## Included Docs | Page | Focus | | --- | --- | | [Overview](./wiki) | Mod overview, balance formula, and the core affix reference. | | [Configuration](./config) | Config folder options, HUD settings, and difficulty tuning. | --- --- url: >- https://docs.variedmc.cc/en/mods/adventure/Champions-Unofficial/sidebarIndex.md description: 'Overview, configuration, and tuning references for Champions Unofficial.' --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSProjects/Meng/SmallProject/McFluidPlaceBlock.md --- # Change Vanilla Fluid Collision Block Result Main topics in this chapter: `ForgeEvents` and Forge's `BlockEvent.FluidPlaceBlockEvent`. All code in this chapter belongs in `startup_scripts`. ## Full Code ```js let $BlockEvent = Java.loadClass("net.minecraftforge.event.level.BlockEvent") ForgeEvents.onEvent($BlockEvent.FluidPlaceBlockEvent,event=>{ let block = event.getNewState().getBlock(); if (block.id == "minecraft:stone"){ event.setNewState(Block.getBlock('minecraft:netherrack').defaultBlockState()); } }) ``` The code above changes the result of lava-water collision from stone to netherrack. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSBasic/ModifyWindowTitleAndLogo.md --- # Change Window Title and Logo This chapter explains how to change the game window title and the top-left icon (logo). ## Change the Window Title Changing the window title is very simple. In the KubeJS `config` folder, open `client.properties`. There is a `title` parameter in that file. Just put your text after `=`. ## Change the Logo Prepare a `16x16` PNG image (or any multiple of that ratio), rename it to `packicon.png`, and place it in the `config` folder. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSProjects/Meng/SmallProject/Message+1.md --- # Chat Message +1 Main topics in this chapter: `Component` and chat events. All code in this chapter belongs in `server_scripts`. Mods and versions used: 1. jei-1.20.1-forge-15.3.0.4 2. rhino-forge-2001.2.2-build.18 3. architectury-9.2.14-forge 4. kubejs-forge-2001.6.5-build.14 5. probejs-6.0.1-forge ## Full Code ```js PlayerEvents.decorateChat(event=>{ const msg = event.getMessage(); event.setMessage(Text.of(msg).clickSuggestCommand(msg).hover(Text.of("+1"))) }) ``` After a player sends a message, the script rewrites the message component so it can be clicked to suggest the same text in chat input, and hovering shows a `+1` tooltip. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/LootTable/Vanilla/Chest.md --- # Chest Type Loot Tables ## Operating Loot Tables * Event: ServerEvents.chestLootTables(event => {}); ::: code-group ```js [KubeJS Modify Original Loot Table] ServerEvents.chestLootTables((event) => { // Modify original loot table event.modify("minecraft:end_city_treasure", (loot) => { loot.addPool((pool) => { // Add loot pool.addItem("minecraft:diamond"); // [!code ++] }); }); }); ``` ```js [KubeJS Override Original Loot Table] ServerEvents.chestLootTables((event) => { // Create loot table, but since id (ResourceLocation) is the same as original, it overrides event.addChest("minecraft:end_city_treasure", (loot) => { loot.addPool((pool) => { // Add loot pool.addItem("minecraft:diamond"); // [!code ++] }); }); }); ``` ```js [KubeJS With Predicates and Modifiers] ServerEvents.chestLootTables((event) => { event.addChest("minecraft:end_city_treasure", (loot) => { // [!code ++] loot.addPool((pool) => { // [!code ++] // Add loot pool.addItem("minecraft:diamond"); // [!code ++] // Add conditional item modifier to loot pool.addItem("minecraft:diamond").addConditionalFunction((c) => c.name(Component.aqua("Test Diamond")) ); // [!code ++] // Add conditional item modifier to loot pool pool.addConditionalFunction((c) => c.name(Component.aqua("Test Diamond")) ); // [!code ++] }); loot.addPool((pool) => { // [!code ++] pool.addItem("minecraft:diamond"); // [!code ++] // Add predicate to loot pool.addItem("minecraft:diamond").survivesExplosion(); // [!code ++] // Add predicate to loot pool pool.survivesExplosion(); // [!code ++] }); // Add conditional item modifier to loot table loot.addConditionalFunction((c) => c.name(Component.aqua("Test Diamond"))); // [!code ++] }); }); ``` ::: ## Available Predicates * Predicates available in chest type loot table context. | Predicate Type | Effect | Statement | KubeJS Native Support | Example | | :------------: | ------------------------------------------------------------------------------------------------------------- | :----------------------: | :------------------: | :-------------------------------------------------: | | All | Evaluates a series of loot table predicates, passes if all pass. Can be called from any context. | - | ☐ | [Example](../BasicKnowledge/Predicate.md#all) | | Any | Evaluates a series of loot table predicates, passes if any pass. Can be called from any context. | - | ☐ | [Example](../BasicKnowledge/Predicate.md#any) | | Entity Properties | Checks entity in loot table context. Can be called from any context. | entityProperties(..args) | ☑ | [Example](../BasicKnowledge/Predicate.md#entity-properties) | | Entity Scores | Checks entity's scoreboard scores. | entityScores(..args) | ☑ | [Example](../BasicKnowledge/Predicate.md#entity-scores) | | Inverted (NOT) | Defines a list of predicates, passes when contained predicates don't pass. | - | ☐ | [Example](../BasicKnowledge/Predicate.md#inverted) | | Check Location | Checks current position. Requires source from loot table context to detect, always fails if not provided. | - | ☐ | [Example](../BasicKnowledge/Predicate.md#check-location) | | Random Chance | Generates random number between 0.0-1.0 and checks if less than specified value. Can be called from any context. | randomChance(..args) | ☑ | [Example](../BasicKnowledge/Predicate.md#random-chance) | | Reference Predicate File | Calls predicate file and returns result. Can be called from any context. | - | ☐ | [Example](../BasicKnowledge/Predicate.md#reference-predicate-file) | | Check Time | Compares current game time (more precisely, 24000 \* days + time of day) with given value. Can be called from any context. | - | ☐ | [Example](../BasicKnowledge/Predicate.md#check-time) | | Check Value | Compares a number with another number or range. Can be called from any context. | - | ☐ | [Example](../BasicKnowledge/Predicate.md#check-value) | | Check Weather | Checks current game weather state. Can be called from any context. | - | ☐ | [Example](../BasicKnowledge/Predicate.md#check-weather) | ## Available Item Modifiers * Item modifiers available in chest type loot table context. | Item Modifier Type | Effect | Statement | KubeJS Native Support | Example | | :----------------: | ------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------------------: | :------------------: | :--------------------------------------------------------------------: | | Copy NBT | Copies NBT from specified source to item. Only allowed value is "block\_entity" | - | ☐ | [Example](../BasicKnowledge/ItemModifier.md#copy-nbt) | | Random Enchant | Adds random enchantment to item. Enchantment level is also random. | enchantRandomly(..args) | ☑ | [Example](../BasicKnowledge/ItemModifier.md#random-enchant) | | Enchant With Level | Enchants item with specified enchantment level (approximately equivalent to enchanting at an enchantment table with this level). | enchantWithLevels(..args) | ☑ | [Example](../BasicKnowledge/ItemModifier.md#enchant-with-level) | | Set Explorer Map | Turns normal map into explorer map pointing to a structure tag. Does nothing if item is not a map. | - | ☐ | [Example](../BasicKnowledge/ItemModifier.md#set-explorer-map) | | Explosion Chance | If item stack is generated from block explosion, each item has 1/explosion radius probability to disappear; item stacks are divided into individual items for calculation; otherwise does nothing. | - | ☐ | [Example](../BasicKnowledge/ItemModifier.md#explosion-chance) | | Fill Player Head | Sets player head to specified player's head. Does nothing if item is not player head. | - | ☐ | [Example](../BasicKnowledge/ItemModifier.md#fill-player-head) | | Furnace Smelt | Converts item to its furnace-smelted counterpart. Does nothing if item is not smeltable. | furnaceSmelt() | ☑ | [Example](../BasicKnowledge/ItemModifier.md#furnace-smelt) | | Limit Stack Count | Limits item count. | - | ☐ | [Example](../BasicKnowledge/ItemModifier.md#limit-stack-count) | | Reference Item Modifier | References another item modifier. | - | ☐ | [Example](../BasicKnowledge/ItemModifier.md#reference-item-modifier) | | Set Attributes | Adds attribute modifiers to item. | - | ☐ | [Example](../BasicKnowledge/ItemModifier.md#set-attributes) | | Set Banner Patterns | Sets pattern on banner item. Does nothing if item is not a banner. | - | ☐ | [Example](../BasicKnowledge/ItemModifier.md#set-banner-patterns) | | Set Contents | Sets item contents. | - | ☐ | [Example](../BasicKnowledge/ItemModifier.md#set-contents) | | Set Item Count | Sets item quantity. | count(..args) | ☑ | [Example](../BasicKnowledge/ItemModifier.md#set-item-count) | | Set Damage | Sets tool damage value. | damage(..args) | ☑ | [Example](../BasicKnowledge/ItemModifier.md#set-damage) | | Set Enchantments | Sets item enchantments. | - | ☐ | [Example](../BasicKnowledge/ItemModifier.md#set-enchantments) | | Set Instrument | Sets goat horn type. Does nothing if item is not goat horn. | - | ☐ | [Example](../BasicKnowledge/ItemModifier.md#set-instrument) | | Set Loot Table | Sets loot table for container block item. | - | ☐ | [Example](../BasicKnowledge/ItemModifier.md#set-loot-table) | | Set Lore | Adds description info to item. | - | ☐ | [Example](../BasicKnowledge/ItemModifier.md#set-lore) | | Set Name | Adds or modifies item custom name. | name(..args) | ☑ | [Example](../BasicKnowledge/ItemModifier.md#set-name) | | Set NBT | Sets item stack NBT data. | nbt(..args) | ☐ | [Example](../BasicKnowledge/ItemModifier.md#set-nbt) | | Set Potion | Sets potion effect tag on item. | - | ☐ | [Example](../BasicKnowledge/ItemModifier.md#set-potion) | | Set Suspicious Stew Effect | Adds status effect to suspicious stew. | - | ☐ | [Example](../BasicKnowledge/ItemModifier.md#set-suspicious-stew-effect) | --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Introduction/LootTable/Vanilla/Chest copy.md --- # 箱子类型战利品表 ## 操作战利品表 * 事件:ServerEvents.chestLootTables(event => {}); ::: code-group ```js [KubeJS修改原战利品表] ServerEvents.chestLootTables((event) => { // 修改原战利品表 event.modify("minecraft:end_city_treasure", (loot) => { loot.addPool((pool) => { // 添加战利品 pool.addItem("minecraft:diamond"); // [!code ++] }); }); }); ``` ```js [KubeJS覆盖原战利品表] ServerEvents.chestLootTables((event) => { // 创建战利品表,但因为id(ResourceLocation)和原战利品表一模一样,因此覆盖了原战利品表 event.addChest("minecraft:end_city_treasure", (loot) => { loot.addPool((pool) => { // 添加战利品 pool.addItem("minecraft:diamond"); // [!code ++] }); }); }); ``` ```js [KubeJS带有谓词与修饰器的] ServerEvents.chestLootTables((event) => { event.addChest("minecraft:end_city_treasure", (loot) => { // [!code ++] loot.addPool((pool) => { // [!code ++] // 添加战利品 pool.addItem("minecraft:diamond"); // [!code ++] // 为战利品添加有条件的物品修饰器 pool.addItem("minecraft:diamond").addConditionalFunction((c) => c.name(Component.aqua("测试钻石")) ); // [!code ++] // 为战利品池添加有条件的物品修饰器 pool.addConditionalFunction((c) => c.name(Component.aqua("测试钻石")) ); // [!code ++] }); loot.addPool((pool) => { // [!code ++] pool.addItem("minecraft:diamond"); // [!code ++] // 为战利品添加谓词 pool.addItem("minecraft:diamond").survivesExplosion(); // [!code ++] // 为战利品池添加谓词 pool.survivesExplosion(); // [!code ++] }); // 为战利品表添加有条件的物品修饰器 loot.addConditionalFunction((c) => c.name(Component.aqua("测试钻石"))); // [!code ++] }); }); ``` ::: ## 可用谓词 * 箱子类型战利品表上下文可用的谓词。 | 谓词类型 | 作用 | 语句 | KubeJS 原生支持 | 示例 | | :----------: | :-------------------------------------------------------------------------------------------------: | :----------------------: | :-------------: | :-------------------------------------------------: | | 全部 | 评估一系列战利品表谓词,若它们都通过检查,则评估通过。可从任何上下文调用。 | - | ☐ | [示例](../BasicKnowledge/Predicate.md#全部) | | 任何 | 评估一系列战利品表谓词,若其中任意一个通过检查,则评估通过。可从任何上下文调用。 | - | ☐ | [示例](../BasicKnowledge/Predicate.md#任何) | | 实体属性 | 检查战利品表上下文中的实体。可从任何上下文调用。 | entityProperties(..args) | ☑ | [示例](../BasicKnowledge/Predicate.md#实体属性) | | 实体分数 | 检查实体的记分板分数。 | entityScores(..args) | ☑ | [示例](../BasicKnowledge/Predicate.md#实体分数) | | 取反(非) | 定义一个谓词列表,当内含谓词不通过时该谓词通过。 | - | ☐ | [示例](../BasicKnowledge/Predicate.md#取反) | | 检查位置 | 检查当前位置。需要战利品上下文提供的来源进行检测,若未提供则总是不通过。 | - | ☐ | [示例](../BasicKnowledge/Predicate.md#检查位置) | | 随机概率 | 生成一个取值范围为 0.0–1.0 之间的随机数,并检查其是否小于指定值。可从任何上下文调用。 | randomChance(..args) | ☑ | [示例](../BasicKnowledge/Predicate.md#随机概率) | | 引用谓词文件 | 调用谓词文件并返回其结果。可从任何上下文调用。 | - | ☐ | [示例](../BasicKnowledge/Predicate.md#引用谓词文件) | | 检查时间 | 将当前的游戏时间(更确切地来说,为 24000 \* 天数 + 当天时间)和给定值进行比较。可从任何上下文调用。 | - | ☐ | [示例](../BasicKnowledge/Predicate.md#检查时间) | | 检查值 | 将一个数与另一个数或范围进行比较。可从任何上下文调用。 | - | ☐ | [示例](../BasicKnowledge/Predicate.md#检查值) | | 检查天气 | 检查当前游戏的天气状态。可从任何上下文调用。 | - | ☐ | [示例](../BasicKnowledge/Predicate.md#检查天气) | ## 可用物品修饰器 * 箱子类型战利品表上下文可用的物品修饰器。 | 物品修饰器类型 | 作用 | 语句 | KubeJS 原生支持 | 示例 | | :--------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------: | :-------------: | :--------------------------------------------------------------------: | | 复制 NBT | 将 NBT 从指定源复制到项目上。唯一允许的值是"block\_entity" | - | ☐ | [示例](../BasicKnowledge/ItemModifier.md#复制nbt) | | 随机附魔 | 为物品附上一个随机的魔咒。魔咒的等级也是随机的。 | enchantRandomly(..args) | ☑ | [示例](../BasicKnowledge/ItemModifier.md#随机附魔) | | 给予等价于经验等级的随机魔咒 | 使用指定的魔咒等级附魔物品(大约等效于使用这个等级的附魔台附魔物品)。 | enchantWithLevels(..args) | ☑ | [示例](../BasicKnowledge/ItemModifier.md#给予等价于经验等级的随机魔咒) | | 设置探险家地图 | 将普通的地图物品变为一个指引到某个结构标签的探险家地图。如果物品不是地图,则不做任何处理。 | - | ☐ | [示例](../BasicKnowledge/ItemModifier.md#设置探险家地图) | | 爆炸损耗 | 如果物品栈是因为方块被爆炸破坏而产生,执行该函数的每个物品有 1/爆炸半径的概率消失,物品栈会被分为多个单独的物品计算;否则此物品修饰器不做任何处理。 | - | ☐ | [示例](../BasicKnowledge/ItemModifier.md#爆炸损耗) | | 填充玩家头颅 | 将玩家头颅设置为指定玩家的头颅。如果物品不是玩家头颅则不做任何处理。 | - | ☐ | [示例](../BasicKnowledge/ItemModifier.md#填充玩家头颅) | | 熔炉熔炼 | 将物品转变为用熔炉烧炼后的对应物品。如果物品不可烧炼,则不做任何处理。 | furnaceSmelt() | ☑ | [示例](../BasicKnowledge/ItemModifier.md#熔炉熔炼) | | 限制物品栈数量 | 限制物品数量。 | - | ☐ | [示例](../BasicKnowledge/ItemModifier.md#限制物品栈数量) | | 引用物品修饰器文件 | 引用另一个物品修饰器。 | - | ☐ | [示例](../BasicKnowledge/ItemModifier.md#引用物品修饰器) | | 设置属性 | 为物品加上属性修饰符。 | - | ☐ | [示例](../BasicKnowledge/ItemModifier.md#设置属性) | | 设置旗帜图案 | 设置旗帜物品的图案。如果物品不是旗帜,则此修饰器不做任何处理。 | - | ☐ | [示例](../BasicKnowledge/ItemModifier.md#设置旗帜图案) | | 设置内容物 | 设置物品的内容物。 | - | ☐ | [示例](../BasicKnowledge/ItemModifier.md#设置内容物) | | 设置物品数量 | 设置该物品的数量。 | count(..args) | ☑ | [示例](../BasicKnowledge/ItemModifier.md#设置物品数量) | | 设置损伤值 | 设置工具的损坏值。 | damage(..args) | ☑ | [示例](../BasicKnowledge/ItemModifier.md#设置损伤值) | | 设置魔咒 | 设置物品的魔咒。 | - | ☐ | [示例](../BasicKnowledge/ItemModifier.md#设置魔咒) | | 设置乐器 | 设置山羊角的种类。如果物品不是山羊角则不做任何处理。 | - | ☐ | [示例](../BasicKnowledge/ItemModifier.md#设置乐器) | | 设置战利品表 | 为一个容器方块物品设定战利品表。 | - | ☐ | [示例](../BasicKnowledge/ItemModifier.md#设置战利品表) | | 设置物品描述 | 为物品添加描述信息。 | - | ☐ | [示例](../BasicKnowledge/ItemModifier.md#设置物品描述) | | 设置物品名 | 添加或修改物品的自定义名称。 | name(..args) | ☑ | [示例](../BasicKnowledge/ItemModifier.md#设置物品名) | | 设置 NBT | 设置物品栈 NBT 数据。 | nbt(..args) | ☐ | [示例](../BasicKnowledge/ItemModifier.md#设置nbt) | | 设置药水 | 设置物品包含的药水效果标签。 | - | ☐ | [示例](../BasicKnowledge/ItemModifier.md#设置药水) | | 设置迷之炖菜效果 | 为谜之炖菜添加状态效果。 | - | ☐ | [示例](../BasicKnowledge/ItemModifier.md#设置迷之炖菜状态效果) | --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSBasic/LootTables/ChestLootTable.md --- # Chest Loot The following examples use native KubeJS to modify chest loot generation. ## Basic Syntax ### Override Vanilla Chest Loot In this example, the original End City chest loot is overridden to generate only glass and glass bottles. ```js ServerEvents.chestLootTables(event=>{ event.addChest("minecraft:end_city_treasure",loot=>{ loot.addPool(pool=>{ pool.addItem("glass") pool.addItem("glass_bottle") pool.rolls = 3 console.log(pool.conditions); }) }) }) ``` The first parameter of `addChest()` is the chest loot table id to override ([vanilla chest loot table list](../../Digression/LootTableId#chest-loot-table-ids)). If you do not want to override and instead want to **add on top of existing loot**, use `modify()` with the same parameters as `addChest()`. To override existing loot, define items in the pool and their [weight ratio](../../Digression/Weight). If you want items to generate only under certain conditions, use [loot predicates](https://zh.minecraft.wiki/w/%E6%88%98%E5%88%A9%E5%93%81%E8%A1%A8?variant=zh-cn) and [item modifiers](https://zh.minecraft.wiki/w/%E7%89%A9%E5%93%81%E4%BF%AE%E9%A5%B0%E5%99%A8). ### Add a New Chest Loot Table Adding a new loot table uses the same pattern as overriding a vanilla one. ```js ServerEvents.chestLootTables(event=>{ event.addChest("meng:chest_loot",loot=>{ loot.addPool(pool=>{ pool.addItem("glass") pool.addItem("glass_bottle") pool.rolls = 3 console.log(pool.conditions); }) }) }) ``` Here we created a custom loot table with id `meng:chest_loot`. You can use this command in-game to generate a chest that uses this loot table: ``` /setblock ~ ~ ~ chest{LootTable:"meng:chests/chest_loot"} ``` If you want your custom loot table to appear naturally in world generation, you need to place it via [world generation events](). --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/Digression/LootTableId.md --- # Chest Loot Table IDs Below are vanilla 1.20 chest loot table IDs and where they generate. | id | Location | | :---------------------------: | :----------------: | | abandoned\_mineshaft | Abandoned Mineshaft | | ancient\_city | Ancient City | | ancient\_city\_ice\_box | Ancient City Ice Box | | bastion\_bridge | Bastion Bridge | | bastion\_hoglin\_stable | Bastion Hoglin Stable | | bastion\_other | Bastion Other | | bastion\_treasure | Bastion Treasure | | buried\_treasure | Buried Treasure | | desert\_pyramid | Desert Pyramid | | end\_city\_treasure | End City | | igloo\_chest | Igloo | | jungle\_temple | Jungle Temple | | jungle\_temple\_dispenser | Jungle Temple Dispenser | | nether\_bridge | Nether Fortress | | pillager\_outpost | Pillager Outpost | | ruined\_portal | Ruined Portal | | shipwreck\_map | Shipwreck Map Chest | | shipwreck\_supply | Shipwreck Supply Chest | | shipwreck\_treasure | Shipwreck Treasure Chest | | simple\_dungeon | Dungeon (Spawner Room) | | spawn\_bonus\_chest | Bonus Chest | | stronghold\_corridor | Stronghold Corridor | | stronghold\_crossing | Stronghold Crossing | | stronghold\_library | Stronghold Library | | underwater\_ruin\_big | Large Underwater Ruin | | underwater\_ruin\_small | Small Underwater Ruin | | village/village\_armorer | Village Armorer House | | village/village\_butcher | Village Butcher House | | village/village\_cartographer | Village Cartographer House | | village/village\_desert\_house | Desert Village House | | village/village\_fisher | Village Fisher House | | village/village\_fletcher | Village Fletcher House | | village/village\_mason | Village Mason House | | village/village\_plains\_house | Plains Village House | | village/village\_savanna\_house | Savanna Village House | | village/village\_shepherd | Village Shepherd House | | village/village\_snowy\_house | Snowy Village House | | village/village\_taiga\_house | Taiga Village House | | village/village\_tannery | Village Tannery | | village/village\_temple | Village Temple | | village/village\_toolsmith | Village Toolsmith House | | village/village\_weaponsmith | Village Weaponsmith House | | woodland\_mansion | Woodland Mansion | # Villager Gift Loot Tables | Loot Table ID | Villager Profession | | :----------------: | :--------: | | armorer\_gift | Armorer | | butcher\_gift | Butcher | | cartographer\_gift | Cartographer | | cleric\_gift | Cleric | | farmer\_gift | Farmer | | fisherman\_gift | Fisherman | | fletcher\_gift | Fletcher | | leatherworker\_gift | Leatherworker | | librarian\_gift | Librarian | | mason\_gift | Mason | | shepherd\_gift | Shepherd | | toolsmith\_gift | Toolsmith | | weaponsmith\_gift | Weaponsmith | # Global Loot Tables --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Upgrade/GlobalScope/Classes/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Introduction/GlobalScope/Classes/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Upgrade/GlobalScope/Classes/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/Event/ClientScript/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Introduction/Event/ClientScript/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/en/info/KubeJS/code/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/zh/info/KubeJS/code/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/en/info/KubeJS/borad/code.md' --- # Code Share Use this page to share and discuss KubeJS-related code. --- --- url: 'https://docs.variedmc.cc/en/info/doc/module-request/codeShare.md' --- # 代码分享 该页面用于处理代码分享相关的请求。 --- --- url: 'https://docs.variedmc.cc/zh/info/doc/module-request/codeShare.md' --- # 代码分享 该页面用于处理代码分享相关的请求。 --- --- url: 'https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/CodeShare/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/CodeShare/Description.md' --- # CodeShare This section provides an overview and structured entry point for the `CodeShare` directory. --- --- url: 'https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/CodeShare/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/Miscellaneous/Collection.md --- # Collection\_ --- --- url: 'https://docs.variedmc.cc/en/doc/guide/community.md' --- --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/Miscellaneous/CompletableFuture.md --- # CompletableFuture --- --- url: 'https://docs.variedmc.cc/en/info/doc/module-request/component.md' --- # 组件 该页面用于处理组件的请求。 --- --- url: 'https://docs.variedmc.cc/zh/info/doc/module-request/component.md' --- # 组件 该页面用于处理组件的请求。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/Miscellaneous/Components.md --- # 文本组件 (Component) ## 前言 * **`文本组件 (Component)`**,游戏中常作为[富文本/格式化文本](https://zh.wikipedia.org/wiki/%E6%A0%BC%E5%BC%8F%E5%8C%96%E6%96%87%E6%9C%AC)使用。 参见[Wiki 文本组件格式](https://zh.minecraft.wiki/w/%E6%96%87%E6%9C%AC%E7%BB%84%E4%BB%B6)以及[Wiki 文本组件教程](https://zh.minecraft.wiki/w/Tutorial:%E6%96%87%E6%9C%AC%E7%BB%84%E4%BB%B6)。 本文不会重复叙述以上内容,即默认已阅读以上内容,而是着重叙述代码实现等方面。 `文本组件 (Component)`是一个原生原味的 Minecraft 类,它的 KJS 包装类为`ComponentKJS`,但实际上却是被`可变文本组件 (MutableComponet)`继承。 尽管大部分情况下它们并无差别,但也不要将两者混淆。 注意:KJS 中的全局类`Component`实际上是`TextWrapper`,部分`Component`内原有的内容需要通过导入该类来实现。 本文中会用 `$Component` 标识。 ## 表示 文本组件的内部维护着一个`组件内容 (ComponentContents)`,这是文本组件实际存储的内容。 文本组件只作为一个包装。 本文所述的表示实际上是组件内容。 ### 纯文本 (LiteralContents) 纯文本只是单纯的文本,不包含各种变量和参数。 ::: code-group ```js [字符串] // 字符串形式常用作参数等 "对,就是纯粹的字符串。"; ``` ```js [对象] Component.literal("这是一个文本组件"); // 注意:传参为null时会报错 $Component.nullToEmpty("这是一个文本组件"); // 等同于上一行,但当传参为null时会返回空文本 ``` ::: ### 翻译文本 (TranslatableContents) 该组件内容是可以加入参数的。依赖于客户端的[语言文件](https://zh.minecraft.wiki/w/%E8%B5%84%E6%BA%90%E5%8C%85#%E8%AF%AD%E8%A8%80)。 在不同的语言下会呈现出不一样的结果。 本文会给出语言文件的内容和示例代码以供更好的理解。 *Tips:翻译键一般以该规则命名`category.modid.id`* ::: code-group ```js [对象] // 在英语(en_us)下显示为:“This is a text without any args” // 在中文(zh_cn)下显示为:“这是一个不带参数的文本” // 以下几行以此类推 Component.translatable("key.example.example_text1"); // 用以下的方式将语言文件中的“%s”替换为“100%”,甚至是你想要的其他内容 // 参数可以有多个 Component.translatable("key.example.example_text2", "100%"); // 该方法会在找不到翻译键的情况下用fallback(即第二个参数)代替所呈现的内容 // 在英语下效果等同于第一行,显示为:“Test Text 0” // 但由于在中文下没有该翻译键,所以显示为:“找不到该文本” $Component.translatableWithFallback( "key.example.example_text3", "找不到该文本" ); // 同样的,它也可以附带参数。第二个参数为fallback,往后为参数 $Component.translatableWithFallback( "key.example.example_text3", "找不到该文本", "Zero" ); ``` ::: ::: code-group ```json [语言文件 zh_cn.json] { "key.example.example_text1": "这是一个不带参数的文本", "key.example.example_text2": "进度条:%s" } ``` ```json [语言文件 en_us.json] { "key.example.example_text1": "This is a text without any args", "key.example.example_text2": "Progress: %s", "key.example.example_text3": "Test Text 0", "key.example.example_text4": "Test Text %s" } ``` ::: ### 记分板分数 (ScoreContents) 该组件内容依赖于[记分板](https://zh.minecraft.wiki/w/%E8%AE%B0%E5%88%86%E6%9D%BF)。 ::: code-group ```js [对象] // 传入一个分数持有者和记分项 Component.score("@s", "test"); // 显示在test记分项下自身的分数 ``` ::: ### 实体名称 (SelectorContents) 该组件内容为[目标选择器](https://zh.minecraft.wiki/w/%E7%9B%AE%E6%A0%87%E9%80%89%E6%8B%A9%E5%99%A8)找到的实体名称。 ::: code-group ```js [对象] // 传入一个目标选择器(组件中的selector)和分隔符(组件中的separator) $Component.selector("@s", $Optional.empty()); // 显示自身的名称 ``` ::: ### 按键绑定 (KeybindContents) 该组件内容依赖于客户端的[键位](https://zh.minecraft.wiki/w/%E6%8E%A7%E5%88%B6#%E5%8F%AF%E8%AE%BE%E7%BD%AE%E7%9A%84%E9%94%AE%E4%BD%8D)。 ::: code-group ```js [对象] // 传入一个绑定键位标识符(组件中的keybind) Component.keybind("key.inventory"); // 在默认设置下显示为“E” ``` ::: ### NBT 标签值 (NbtContents) 该组件内容依赖于[NBT 标签](https://zh.minecraft.wiki/w/NBT%E6%A0%87%E7%AD%BE)。 较复杂且不常用,本文只给出大致用法。 ::: code-group ```js [对象] /* * 第一个参数为NBT标签下的路径(即组件中的nbt) * 第二个参数为是否解析为文本(即组件中的interpret) * 第三个参数为文本组件间的分隔符(即组件中的separator) * 第四个参数为NBT的来源(即组件中的source) * 该行代码会显示自身的NBT标签 */ $Component.nbt("", false, $Optional.empty(), new $EntityDataSource("@s")); ``` ::: ## 样式 (Style) 用于修饰文本组件,比如颜色、点击事件、悬浮事件等。 ::: code-group ```js [颜色] Component.literal("这是一个文本组件").color(0x9400d3); // 颜色为0x9400D3的“这是一个文本组件” Component.literal("这是一个文本组件").noColor(); // 将一个已有颜色的文本组件设为无色(默认色) Component.blue("这是一个文本组件"); // 显示为蓝色的“这是一个文本组件” Component.literal("这是一个文本组件").blue(); // 等同于上一行,将一个已有的文本组件设为蓝色 // 另外还有更多颜色都是类似的使用方式 ``` ```js [样式] Component.bold(true); // 将一个已有的文本组件设为粗体 Component.bold(); // 等同于上一行 // 下面的方法以此类推 Component.italic(); // 斜体 Component.underlined(); // 下划线 Component.strikethrough(); // 删除线 Component.obfuscated(); // 随机字符 Component.font("RandomFont"); // 显示该文本组件的字体 Component.hasStyle(); // 判断该文本组件是否有样式 Component.isEmpty(); // 判断该文本组件是否为空 // 单独附加样式 let style = new $Style().withBold(true); // 附加粗体 $Component.withStyle(style); ``` ```js [事件] Component.clickRunCommand("/tell @s Harry"); // 点击时执行该命令 Component.clickSuggestCommand("/tell @s Harry"); // 点击时将该命令加入聊天框中 Component.clickCopy("剪贴板"); // 点击时将改内容复制到剪贴板中 Component.clickChangePage("2"); // 点击时改变书的页数 Component.clickOpenUrl("https://docs.variedmc.cc"); // 点击时打开一个URL Component.clickOpenFile("file.txt"); // 点击时打开一个文件 Component.hover("其他东西"); // 将鼠标放到文本上时显示该内容 Component.insertion("其他东西"); // Shift点击时将该内容加入聊天框中 ``` ::: ## 子组件 (Sibling) 子组件继承于父组件的样式,也可以独立覆盖父组件的样式。 ```js // 显示为“这是一个文本组件,这是另一个文本组件。” Component.literal("这是一个文本组件,").append( // 这个文本组件可以拥有自己的样式,而不影响上面组件的样式 Component.literal("这是另一个文本组件。") ); Component.hasSiblings(); // 该组件是否有子组件 ``` --- --- url: 'https://docs.variedmc.cc/en/mods/adventure/Champions-Unofficial/config.md' --- # ## Example: Dropping an Item with Specific NBT ```json { "type": "minecraft:entity", "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:diamond_sword", "conditions": [ { "condition": "champions:entity_champion", "entity": "this", "minTier": 4, } ] "functions": [ { "function": "minecraft:set_attributes", "modifiers": [ { "attribute": "generic.attack_damage", "name": "Bonus Damage", "amount": 5, "operation": "addition", "id": "12345678-1234-1234-1234-1234567890ab", "slot": "mainhand" } ] }, { "function": "minecraft:set_nbt", "tag": "{display:{Name:'{\"text\":\"Champion Sword\",\"color\":\"gold\"}'}}" } ] } ] } ] } ``` # Champions Mod Configuration Guide The Champions mod enhances the challenge in Minecraft by adding elite monsters. Here is a detailed description of the configuration files in the `config/` folder to help you properly configure the game. ## HUD Display Settings ```toml [hud] # The x-offset for the champion HUD # Range: -1000 ~ 1000 hudXOffset = 0 # The y-offset for the champion HUD # Range: -1000 ~ 1000 hudYOffset = 0 # The distance, in blocks, from which the champion HUD can be seen # Range: 0 ~ 1000 hudRange = 50 # Set to true to move the WAILA overlay underneath the champion HUD enableWailaIntegration = true ``` These settings are used to adjust the position and visibility of the HUD (Head-Up Display) in the game. ## General Configuration ```toml [general] # The range from an active beacon where no champions will spawn (0 to disable) # Range: 0 ~ 1000 beaconProtectionRange = 64 # Set to true to enable champions from mob spawners championSpawners = false # The minimum tier of champions that will have death messages sent out upon defeat (0 to disable) # Range: > 0 deathMessageTier = 0 # A list of dimension names that are blacklisted/whitelisted for champions dimensionList = [] # Set whether the dimension list is a blacklist or whitelist # Allowed Values: BLACKLIST, WHITELIST dimensionPermission = "BLACKLIST" # A list of entities that are blacklisted/whitelisted for champions entitiesList = [] # Set whether the entities list is a blacklist or whitelist # Allowed Values: BLACKLIST, WHITELIST entitiesPermission = "BLACKLIST" # Set to true to show HUD display for champions, including health, affixes, and tier showHud = true # Set entity id (for example, ['minecraft:end_dragon', 'minecraft:creeper']) to hidden HUD display for champions, including health, affixes, and tier bossBarBlackList = [] # Set to true to have champions generate a colored particle effect indicating their rank showParticles = true # Set to true to show champion tier and affixes in The One Probe overlay enableTOPIntegration = true ``` These general configuration options allow you to customize the behavior and display of elite monsters in the game to suit different players' needs. By properly configuring these options, you can make the game experience richer and more personalized. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Upgrade/GlobalScope/ConstantsAndObjects/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Upgrade/GlobalScope/ConstantsAndObjects/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/public/contributors/README.md' --- # Contributors Avatar Cache Directory This directory contains cached avatar images for project contributors. ## File Naming Convention * File name: `{github-username}.png` * Recommended size: 100x100px * Supported formats: PNG (recommended), JPG ## Usage The Contributors component will: 1. First attempt to fetch real-time avatars from GitHub 2. Fall back to cached avatars in this directory if GitHub fails 3. Use GitHub's default identicon as final fallback ## Path Configuration The component uses direct paths: `/contributors/{username}.png` * No URL prefix is needed * Files should be directly accessible from the public directory ## Manual Cache Generation ```bash # Download a contributor's avatar curl "https://github.com/{username}.png?s=100" \ -o docs/public/contributors/{username}.png # Example: Download PickAID's avatar curl "https://github.com/PickAID.png?s=100" \ -o docs/public/contributors/PickAID.png ``` ## Bulk Cache Generation Script ```bash #!/bin/bash # Generate cache for multiple contributors CONTRIBUTORS=("PickAID" "contributor1" "contributor2") CACHE_DIR="docs/public/contributors" mkdir -p "$CACHE_DIR" for username in "${CONTRIBUTORS[@]}"; do echo "Downloading avatar for $username..." curl -L "https://github.com/$username.png?s=100" \ -o "$CACHE_DIR/$username.png" \ --fail --silent --show-error if [ $? -eq 0 ]; then echo "✓ Successfully cached avatar for $username" else echo "✗ Failed to cache avatar for $username" fi done ``` ## Example Structure ``` docs/public/contributors/ ├── PickAID.png ├── contributor1.png ├── contributor2.png ├── username.png └── README.md ``` ## Tips * Use consistent 100x100px size for best display * PNG format is recommended for transparency support * Compress images to reduce file size * Consider automated cache updates in CI/CD pipelines --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSProjects/Meng/JukeboxToItem.md --- # Convert a Music Disc into an Item Topics covered: jukebox block entities, block entity data, and popping items from a block. Mods and versions used: 1. rhino-forge-2001.2.2-build.18 2. architectury-9.2.14-forge 3. kubejs-forge-2001.6.5-build.14 4. probejs-6.0.1-forge ## Project Code ```js BlockEvents.rightClicked("jukebox", event => { let block = event.getBlock() // Check whether the jukebox is playing after right-click if (block.entityData.getBoolean("IsPlating")){ // Schedule a check 3 seconds later event.server.scheduleInTicks(20 * 3, () => { let newBlock = event.level.getBlock(block.pos) if (newBlock.id == 'minecraft:air') return let blockEntity = newBlock.getEntity() let blockEntityData = newBlock.getEntityData() // Check whether the item is air to determine whether it is still playing // You can also check blockEntityData.getBoolean("IsPlating") if (!blockEntity.getFirstItem().is("air")) { // Check whether playback time is greater than 2.8 (close to target, but avoid exact equality) if ((blockEntityData.getInt("TickCount") - blockEntityData.getInt("RecordStartTick")) / 20 >= 2.8) { // Clear the item inside the jukebox blockEntity.clearContent() // Pop a new item above the block newBlock.popItemFromFace("stone", "up") } } }) } }) ``` ## Notes 1. This project is only an example; many parts are not necessarily optimal and can be improved. 2. If you improve this project, you can upload your revised code to the [Gitee repository](https://gitee.com/gumengmengs/kubejs-course). 3. Many values in the code are adjustable parameters and can be modified as needed. --- --- url: 'https://docs.variedmc.cc/en/info/doc/Q&A/cooperation.md' --- # 合作 该页面用于回答关于KubeJS合作的问题。 --- --- url: 'https://docs.variedmc.cc/zh/info/doc/Q&A/cooperation.md' --- # 合作 该页面用于回答关于KubeJS合作的问题。 --- --- url: 'https://docs.variedmc.cc/en/mods/cosmetic/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/Miscellaneous/CountingMap.md --- # CountingMap --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/CodeShare/CraftingTableGUI.md --- # 自定义方块打开工作台界面 ::: v-info 通过加载原版Java类来进行方块右键打开工作台GUI的功能 ::: ```js let $SimpleMenuProvider = Java.loadClass('net.minecraft.world.SimpleMenuProvider') let $CraftingMenu = Java.loadClass('net.minecraft.world.inventory.CraftingMenu') let $Optional = Java.loadClass(`java.util.Optional`) // 下方以草方块作为一个示例代码,具体需求可以自行更改。 BlockEvents.rightClicked('grass_block', (event) => { const { player, level, block } = event if (level.clientSide) return player.openMenu( new $SimpleMenuProvider((i, inv, p) => { return new $CraftingMenu(i, inv, (func) => { func.apply(level, block.pos) return $Optional.empty() }) }, '工作台') ) //这串代码是显示玩家右键是否挥动手臂 player.swing() }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.21/Addon/LootJs/LootTables/Create.md --- # Create a loot table LootJS支持我们创建自定义的战利品表。 ## 创建战利品表 创建一个只有一个奖池的战利品表相当的简单 ```js LootJS.lootTables(event => { event.create("lootjs:rare_equipment").createPool(pool => { // ... }) }) ``` ## 第一个奖池 ### 第一个物品 假如我们想创建一个[例子](https://misode.github.io/loot-table/?version=1.21\&preset=chests/end_city_treasure)中的战利品表中的一个条目,就像下面的 `json` 一样: ::: code-group ```json [json例子] { "type": "minecraft:item", "name": "minecraft:diamond_leggings", "weight": 3, "functions": [ { "function": "minecraft:enchant_with_levels", "levels": { "type": "minecraft:uniform", "min": 20, "max": 39 }, "options": "#minecraft:on_random_loot" } ] } ``` ```js [参照json的js实现] LootJS.lootTables(event => { event.create("lootjs:rare_equipment").createPool(pool => { // 默认情况下,`enchantWithLevels` 将始终为 `#minecraft:on_random_loot` pool.addEntry( LootEntry.of("minecraft:diamond_leggings") .withWeight(3) .enchantWithLevels([20, 39]) ) }) }) ``` ::: ### 更多的物品 我们可以向第一个奖励池添加更多的物品。 ```js LootJS.lootTables(event => { event.create("lootjs:rare_equipment").createPool(pool => { pool.addEntry( LootEntry.of("minecraft:diamond_leggings") .withWeight(3) .enchantWithLevels([20, 39]) ) pool.addEntry( LootEntry.of("minecraft:iron_pickaxe") .withWeight(10) .enchantWithLevels([10, 19]) ) pool.addEntry( LootEntry.of("minecraft:diamond_sword") .withWeight(5) .enchantWithLevels([30, 50]) .damage([0.3, 0.5]) ) pool.addEntry("minecraft:diamond_horse_armor") pool.addEntry(LootEntry.of("minecraft:diamond").setCount([2, 5])) }) }) ``` ## 第二个奖励池 LootJS也允许我们创建第二个奖励池。例如,我们可以在第二个奖励池中添加一把带附魔的剑。 ```js LootJS.lootTables(event => { event .create("lootjs:rare_equipment") .createPool(pool => { // 之前的第一奖励池的代码,就不重复写了 }) .createPool(pool => { pool.addEntry( LootEntry.of("minecraft:netherite_sword").enchant(builder => { builder.withEnchantment("minecraft:sharpness", [4, 5]) builder.withEnchantment("minecraft:unbreaking", 3) builder.withEnchantment("minecraft:knockback", 2) builder.withEnchantment("minecraft:mending", 1) }) ) // Minecraft 原版通常使用一个带权重的空条目来代替 `randomChance`。 // 在这个教程中,我们将做同样的事情。 pool.addEntry(LootEntry.empty().withWeight(20)) }) }) ``` ## 将表添加到其他表中 LootJS现在支持将整个表添加到其他表中。 ```js LootJS.lootTables(event => { event.getLootTable("minecraft:gameplay/fishing") .firstPool(pool => { pool.addEntry(LootEntry.reference("lootjs:rare_equipment") // ⬆️ // 这里指的是刚才我们创建的战利品表 .randomChance(0.1)) }) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/CreateJS.md --- # Create (Mechanical Power) and KubeJS This chapter introduces how to use KubeJS to modify recipes from the Create mod. Below are the Forge and mod versions used when writing this chapter. If you use different versions and get errors, the mod authors may have changed code or APIs: 1. forge-47.2.32 2. JEI-15.3.0.4 3. rhino-2001.2.2-build.18 4. architectury-9.2.14 5. kubejs-2001.6.5-build.7 6. probejs-6.0.1 7. create-0.5.1f 8. kubejs create-2001.2.5.bulid.2 ### Recipe Type Table (This is not exhaustive. It lists common ones. `[]` means you can pass multiple inputs/outputs or an array.) Baking/smelting use vanilla recipe types, so they do not have a dedicated Create recipe type entry here. The `.heatLevel` method requires the extra mod [**CreateHeatJS**](../KubeJSAddon/CreateHeatJS). | Recipe Call | Recipe Type | Notes | | :---------------------------------------------------------------------------------------: | :-----------------: | :-----------------------------------------------: | | `create.conversion(output[], input[])` | Conversion | See examples such as gearboxes / info panels | | `create.crushing(output[], input[])` | Crushing | - | | `create.milling(output[], input[])` | Milling | - | | `create.cutting(output[], input[])` | Cutting | - | | `create.mixing(output[], input[])` | Mixing | Can append `.heated()` and `.superheated()` | | `create.compacting(output[], input[])` | Compacting | Can append `.heated()` and `.superheated()` | | `create.sandpaperPolishing(output[], input[])` | Sandpaper polishing | - | | `create.splashing(output[], input[])` | Splashing (fan+water) | - | | `create.haunting(output[], input[])` | Haunting (fan+soul fire) | - | | `create.deploying(output[], input[])` | Deploying | - | | `create.item_application(output, input)` | Item application | Similar to casing recipes (1.18+; auto adapts to deploying) | | `create.filling(output[], input[])` | Filling | - | | `create.emptying(output[], input[])` | Emptying | - | | `create.mechanicalCrafting(output[], pattern[], {key})` | Mechanical crafting | See examples below | | `create.sequencedAssembly(output[], input, sequence[]).transitionalItem(item).loops(int)` | Sequenced assembly | See examples below | ### Notes This is the common outer structure for all the code below (only partial code is shown in the examples): ```js ServerEvents.recipes((event) => { /* * This is a basic destructuring pattern for the recipes event. * It is not limited to Create. * If other mods are supported, you can destructure them here as well, for example: * const { create, thermal, kubejs, minecraft } = event.recipes * If you are not sure which mods are supported, try using ProbeJS. */ const { create } = event.recipes }) ``` If a recipe supports **heated**, it also supports **superheated**. Just append `.heated()` or `.superheated()` to the recipe. The former is normal heating and the latter is superheating. You can also use `.heatRequirement(string)` with `"heated"`, `"superheated"`, or other heat levels. If a recipe supports fluid output, you can use `Fluid.of(...)` like this: ```js Fluid.of("fluid_id", amount) Fluid.of("minecraft:lava", 810) ``` If a recipe supports chance outputs, use: ```js Item.of("item_id", amount).withChance(0.5) ``` This means the item has a 50% chance to output. Here, `1` is 100%. If a recipe supports multiple outputs, you can write: ```js [Item.of("item_id", amount), Item.of("item_id", amount)] ``` If multiple outputs and chance are supported, you can also add `.withChance()` to `Item.of(...)`. If a recipe supports processing time, you can append `processingTime(ticks)`. ### Mechanical Press / Compacting ```js // Compacting create.compacting("minecraft:golden_apple", [ "minecraft:apple" ]) // Compacting (heated) create.compacting("minecraft:iron_nugget", [ "minecraft:iron_ore" ]).heated() // Compacting (superheated) create.compacting("minecraft:diamond", [ "minecraft:deepslate_diamond_ore" ]).superheated() // Compacting (fluid) create.compacting(Fluid.of("minecraft:water", 1000), [ Fluid.of("minecraft:lava", 1000) ]) // Compacting (chance output: 1% chance to output diamond) create.compacting([ "minecraft:stone", Item.of("minecraft:diamond").withChance(0.01) ], "minecraft:magma_block") // Pressing create.pressing("minecraft:enchanted_golden_apple", [ "minecraft:golden_apple" ]) // Pressing (chance output) create.pressing([ "minecraft:stone", Item.of("diamond").withChance(0.5) ], "minecraft:crying_obsidian") ``` Quick explanation: For **compacting** and **pressing**, the first argument is the output items and/or fluids. It can be an array, and you can use .withChance() on items to add output probability (remember: 1 is 100%). The second argument is the input. Appending .heated() requires heating. Appending .superheated() requires superheating. ### Mixer ```js // Mixing - multiple inputs create.mixing("minecraft:grass_block", [ Fluid.of("minecraft:water", 500), "minecraft:dirt" ]) // Mixing - heated create.mixing("minecraft:cooked_cod", [ "minecraft:cod" ]).heated() // Mixing - superheated create.mixing("minecraft:golden_carrot", [ "minecraft:carrot" ]).superheated() // Mixing - fluid output + chance output create.mixing([ Fluid.of("minecraft:lava", 100), Item.of("diamond").withChance(0.3) ], Item.of("minecraft:fire_charge", 64)).superheated() ``` ### Fan ```js // Splashing (supports multiple outputs and chance outputs) create.splashing("minecraft:golden_apple", [ "minecraft:enchanted_golden_apple" ]) // Haunting (soul fire) (supports multiple outputs and chance outputs) create.haunting(Item.of("minecraft:diamond").withChance(0.1),[ "minecraft:stone" ]) ``` ### Millstone ```js // Milling (supports multiple outputs and chance outputs) create.milling(Item.of("minecraft:oak_planks", 6), [ "minecraft:chest" ]) ``` ### Crushing Wheels ```js // Crushing (supports multiple outputs and chance) - with processing time create.crushing("minecraft:netherite_scrap", [ "minecraft:ancient_debris" ]).processingTime(20 * 200) ``` ### Spout (Filling) ```js // Filling create.filling("minecraft:soul_torch", [ "minecraft:torch", Fluid.of("minecraft:milk", 500) ]) ``` The first argument is the output item. The second argument is the input item and fluid. Note: the inputs must be an item + a fluid. ### Basin (Emptying) ```js // Emptying create.emptying([ Fluid.of("minecraft:lava", 50), "minecraft:slime_ball" ], "minecraft:magma_cream") ``` The first argument is the output items and fluids. The second argument is the input item. Note: the outputs must be items + fluids. ### Mechanical Saw ```js // Cutting (supports multiple outputs and chance; can also add processingTime) create.cutting("minecraft:glowstone", [ "minecraft:shroomlight" ]) ``` ### Deployer ```js // Deploying (supports multiple outputs and chance) create.deploying("minecraft:chipped_anvil", [ "minecraft:damaged_anvil", "minecraft:iron_ingot" ]) // Deploying - do not consume held item create.deploying("minecraft:anvil", [ "minecraft:chipped_anvil", "minecraft:iron_ingot" ]).keepHeldItem() ``` The first argument is the output item. The second argument is the input item and the deployer's held item. The first input is the item being worked on; the second input is the item held by the deployer. So do not swap them. ### Sandpaper ```js // Sandpaper polishing (supports chance output) create.sandpaper_polishing("minecraft:glow_item_frame", [ "minecraft:item_frame" ]) ``` ### Mechanical Crafter ```js // Mechanical crafting supports up to 9x9 create.mechanical_crafting("minecraft:cow_spawn_egg", [ "BBBBB", "B B B", "BBEBB", "BMMMB", "MMMMM" ], { B: "minecraft:beef", E: "minecraft:egg", M: "minecraft:milk_bucket" }) ``` This uses a JSON-like pattern format and supports up to 9x9. The first argument is the output item. The second argument is the pattern. The third argument is the key map that explains pattern placeholders. Recommendation: treat each string line as a row in the grid. If you have a completely empty row, fill it with spaces rather than omitting it. ### Sequenced Assembly ```js /* * You can store repeated item ids in a constant. * This is optional; some people prefer not to. * If you reuse the same name, only the last one is effective. * To avoid issues, avoid reusing constant/variable names. */ const incomplete = "create:incomplete_precision_mechanism" // Sequenced assembly create.sequenced_assembly( // Outputs + chance (this chance behaves more like "weight/share"): // Higher chance means higher probability. // The first item is the primary output; the rest are "scrap" outputs. [ Item.of("diamond").withChance(0.02), Item.of("cobblestone").withChance(0.5), Item.of("stone").withChance(0.8) ], // Input item "minecraft:deepslate", // Processing sequence (machines in order) [ // Deployer create.deploying(incomplete, [incomplete, "minecraft:tnt"]).keepHeldItem(), // Cutting create.cutting(incomplete, incomplete), // Filling create.filling(incomplete, [incomplete, Fluid.of("minecraft:lava", 100)]), // Pressing create.pressing(incomplete,incomplete) ] ) // Transitional item (incomplete product) .transitionalItem(incomplete) // Loop count (default is 5 if omitted) .loops(3) ``` ```js // Same example without comments const incomplete = "create:incomplete_precision_mechanism" create.sequenced_assembly([ Item.of("diamond").withChance(0.02), Item.of("cobblestone").withChance(0.5), Item.of("stone").withChance(0.8) ], "minecraft:deepslate", [ create.deploying(incomplete, [incomplete, "minecraft:tnt"]).keepHeldItem(), create.cutting(incomplete, incomplete), create.filling(incomplete, [incomplete, Fluid.of("minecraft:lava", 100)]), create.pressing(incomplete, incomplete) ]).transitionalItem(incomplete).loops(3) ``` Note: at the moment, Create officially provides only the machine steps shown above in this example. Note: the transitional item can be a vanilla item as well. ### Registering A Custom Transitional Item You can register a custom transitional item for sequenced assembly. This must be written in `startup_scripts` (runs at game startup). ```js StartupEvents.registry("item", (event) => { event.create("meng:diamond", "create:sequenced_assembly") }) ``` In `event.create`, the first argument is the **item id** and the second argument is the **item type**. ### Mysterious Recipes (JEI Display Only) Mysterious recipes are only a JEI display. They are not actually craftable recipes, so you do not need to add any server recipe events. Write them in `client_scripts` (client-side), like this: ```js const $MysteriousItemConversionCategory = Java.loadClass("com.simibubi.create.compat.jei.category.MysteriousItemConversionCategory") const $ConversionRecipe = Java.loadClass("com.simibubi.create.compat.jei.ConversionRecipe") $MysteriousItemConversionCategory.RECIPES.add($ConversionRecipe.create("apple", "minecraft:diamond")) ``` This shows that "apple converts to diamond" as a mysterious recipe in JEI. What that recipe "means" and how players should obtain it is up to you. This only provides a JEI hint; it does not implement any gameplay logic by itself. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.21/Introduction/Addon/LootJs/LootTables/Create.md --- # Create a loot table LootJS支持我们创建自定义的战利品表。 ## 创建战利品表 创建一个只有一个奖池的战利品表相当的简单 ```js LootJS.lootTables(event => { event.create("lootjs:rare_equipment").createPool(pool => { // ... }) }) ``` ## 第一个奖池 ### 第一个物品 假如我们想创建一个[例子](https://misode.github.io/loot-table/?version=1.21\&preset=chests/end_city_treasure)中的战利品表中的一个条目,就像下面的 `json` 一样: ::: code-group ```json [json例子] { "type": "minecraft:item", "name": "minecraft:diamond_leggings", "weight": 3, "functions": [ { "function": "minecraft:enchant_with_levels", "levels": { "type": "minecraft:uniform", "min": 20, "max": 39 }, "options": "#minecraft:on_random_loot" } ] } ``` ```js [参照json的js实现] LootJS.lootTables(event => { event.create("lootjs:rare_equipment").createPool(pool => { // 默认情况下,`enchantWithLevels` 将始终为 `#minecraft:on_random_loot` pool.addEntry( LootEntry.of("minecraft:diamond_leggings") .withWeight(3) .enchantWithLevels([20, 39]) ) }) }) ``` ::: ### 更多的物品 我们可以向第一个奖励池添加更多的物品。 ```js LootJS.lootTables(event => { event.create("lootjs:rare_equipment").createPool(pool => { pool.addEntry( LootEntry.of("minecraft:diamond_leggings") .withWeight(3) .enchantWithLevels([20, 39]) ) pool.addEntry( LootEntry.of("minecraft:iron_pickaxe") .withWeight(10) .enchantWithLevels([10, 19]) ) pool.addEntry( LootEntry.of("minecraft:diamond_sword") .withWeight(5) .enchantWithLevels([30, 50]) .damage([0.3, 0.5]) ) pool.addEntry("minecraft:diamond_horse_armor") pool.addEntry(LootEntry.of("minecraft:diamond").setCount([2, 5])) }) }) ``` ## 第二个奖励池 LootJS也允许我们创建第二个奖励池。例如,我们可以在第二个奖励池中添加一把带附魔的剑。 ```js LootJS.lootTables(event => { event .create("lootjs:rare_equipment") .createPool(pool => { // 之前的第一奖励池的代码,就不重复写了 }) .createPool(pool => { pool.addEntry( LootEntry.of("minecraft:netherite_sword").enchant(builder => { builder.withEnchantment("minecraft:sharpness", [4, 5]) builder.withEnchantment("minecraft:unbreaking", 3) builder.withEnchantment("minecraft:knockback", 2) builder.withEnchantment("minecraft:mending", 1) }) ) // Minecraft 原版通常使用一个带权重的空条目来代替 `randomChance`。 // 在这个教程中,我们将做同样的事情。 pool.addEntry(LootEntry.empty().withWeight(20)) }) }) ``` ## 将表添加到其他表中 LootJS现在支持将整个表添加到其他表中。 ```js LootJS.lootTables(event => { event.getLootTable("minecraft:gameplay/fishing") .firstPool(pool => { pool.addEntry(LootEntry.reference("lootjs:rare_equipment") // ⬆️ // 这里指的是刚才我们创建的战利品表 .randomChance(0.1)) }) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/CreateSifting.md --- # Create: Sifting This chapter introduces how to use KubeJS to modify Create: Sifting. The tutorial below was tested with these mod/Forge versions.\ If you get errors on different versions, the API may have changed: 1. forge-47.3.7 2. JEI-15.3.0.4 3. rhino-2001.2.2-build.18 4. architectury-9.2.14 5. kubejs-2001.6.5-build.7 6. probejs-6.0.1 7. create-0.5.1f 8. createsifter-1.20.1-1.8.1.e-22 ## Register Mesh Items Create: Sifting supports mesh item registration directly.\ You only need to register the item, assign textures/model data, and add localization. ```js StartupEvents.registry("item", (event) => { // Basic mesh event.create("meng:mesh", "createsifter:mesh") // Advanced mesh event.create("meng:advanced_mesh", "createsifter:advanced_mesh") }) ``` ## Recipe Changes Create: Sifting provides a recipe type for sieve processing: ```js ServerEvents.recipes((event) => { const { createsifter } = event.recipes createsifter.sifting(output[], input[], processingTime, isWater, minimumSpeed) }) ``` `output` : output item(s) -- ***required*** `input` : input item(s) -- ***required*** `processingTime` : processing time (in ticks) -- **optional** -- default `100` `isWater` : whether the process is waterlogged -- **optional** -- default `false` `minimumSpeed` : minimum processing speed -- **optional** -- default `1.0` The last three can also be configured via chained methods.\ For waterlogged processing, use `.waterlogged()` (default `true` for that method). Question 1: Where do I specify the mesh? Answer: `output` is an array-like structure in examples, and one of its parameters is the mesh item id. Question 2: There are two sieve types but only one recipe format. How do I separate them? Answer: The addon includes an advanced brass mesh.\ If the mesh is `advanced_brass_mesh` (or your own item registered as `advanced_mesh`), it will be recognized by the brass/advanced sieve type. Other meshes are treated as normal. ## Simple Helper Wrappers for Recipe Editing ```js // String mesh function stringMesh(output, input, time, isWater) { sifting(output, [input, "createsifter:string_mesh"], time, isWater) } // Andesite mesh function andesiteMesh(output, input, time, isWater) { sifting(output, [input, "createsifter:andesite_mesh"], time, isWater) } // Zinc mesh function zincMesh(output, input, time, isWater) { sifting(output, [input, "createsifter:zinc_mesh"], time, isWater) } // Brass mesh function brassMesh(output, input, time, isWater) { sifting(output, [input, "createsifter:brass_mesh"], time, isWater) } // Advanced brass mesh function advancedBrassMesh(output, input, time, isWater) { sifting(output, [input, "createsifter:advanced_brass_mesh"], time, isWater) } /** * * @param {*} output output * @param {*} input input * @param {*} time defaults to 5 seconds when omitted * @param {*} isWater defaults to false when omitted */ function sifting(output, input, time, isWater) { if (time == undefined) time = 5 if (isWater == undefined) isWater = false ServerEvents.recipes((event) => { const createsifter = event.recipes.createsifter createsifter.sifting(output, input, time * 20, isWater) }) } ``` When using this, you can just call `stringMesh()` so you don’t need to pass the mesh id every time. ## Mesh Model/Texture Notes Here is a simple model example (you can also reuse the official model). Place the model at `assets/modid/models/item/itemId.json`. `modid` is your mod id. `itemId` is your item id. Briefly, if you edit textures directly: 1. `0` is the center mesh texture. 2. `1` is the sieve frame texture. ```json { "textures": { "0": "minecraft:block/iron_block", "1": "minecraft:block/oak_planks", "particle": "minecraft:block/iron_block" }, "elements": [ { "from": [15, 0, 0], "to": [16, 1, 16], "rotation": {"angle": 0, "axis": "y", "origin": [15, 0, 16]}, "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#1"}, "east": {"uv": [0, 0, 16, 1], "texture": "#1"}, "south": {"uv": [0, 0, 1, 1], "texture": "#1"}, "west": {"uv": [0, 0, 16, 1], "texture": "#1"}, "up": {"uv": [0, 0, 16, 1], "rotation": 270, "texture": "#1"}, "down": {"uv": [0, 0, 16, 1], "rotation": 90, "texture": "#1"} } }, { "from": [0, 0, 0], "to": [1, 1, 16], "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 16]}, "faces": { "north": {"uv": [0, 0, 1, 1], "rotation": 90, "texture": "#1"}, "east": {"uv": [0, 0, 16, 1], "texture": "#1"}, "south": {"uv": [0, 0, 1, 1], "rotation": 270, "texture": "#1"}, "west": {"uv": [0, 0, 16, 1], "rotation": 180, "texture": "#1"}, "up": {"uv": [0, 0, 16, 1], "rotation": 270, "texture": "#1"}, "down": {"uv": [0, 0, 16, 1], "rotation": 270, "texture": "#1"} } }, { "from": [1, 0, 0], "to": [15, 1, 1], "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 0]}, "faces": { "north": {"uv": [0, 0, 14, 1], "texture": "#1"}, "east": {"uv": [0, 0, 1, 1], "texture": "#1"}, "south": {"uv": [0, 0, 14, 1], "texture": "#1"}, "west": {"uv": [0, 0, 1, 1], "texture": "#1"}, "up": {"uv": [0, 0, 14, 1], "texture": "#1"}, "down": {"uv": [0, 0, 14, 1], "texture": "#1"} } }, { "from": [1, 0, 15], "to": [15, 1, 16], "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 15]}, "faces": { "north": {"uv": [0, 0, 14, 1], "texture": "#1"}, "east": {"uv": [0, 0, 1, 1], "texture": "#1"}, "south": {"uv": [0, 0, 14, 1], "texture": "#1"}, "west": {"uv": [0, 0, 1, 1], "texture": "#1"}, "up": {"uv": [0, 0, 14, 1], "texture": "#1"}, "down": {"uv": [0, 0, 14, 1], "texture": "#1"} } }, { "from": [6, 0, 1], "to": [7, 1, 15], "rotation": {"angle": 0, "axis": "y", "origin": [6, 0, 17]}, "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#0"}, "east": {"uv": [0, 0, 14, 1], "texture": "#0"}, "south": {"uv": [0, 0, 1, 1], "texture": "#0"}, "west": {"uv": [0, 0, 14, 1], "texture": "#0"}, "up": {"uv": [0, 0, 14, 1], "rotation": 270, "texture": "#0"}, "down": {"uv": [0, 0, 14, 1], "rotation": 90, "texture": "#0"} } }, { "from": [9, 0, 1], "to": [10, 1, 15], "rotation": {"angle": 0, "axis": "y", "origin": [9, 0, 17]}, "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#0"}, "east": {"uv": [0, 0, 14, 1], "texture": "#0"}, "south": {"uv": [0, 0, 1, 1], "texture": "#0"}, "west": {"uv": [0, 0, 14, 1], "texture": "#0"}, "up": {"uv": [0, 0, 14, 1], "rotation": 270, "texture": "#0"}, "down": {"uv": [0, 0, 14, 1], "rotation": 90, "texture": "#0"} } }, { "from": [13, 0, 1], "to": [14, 1, 15], "rotation": {"angle": 0, "axis": "y", "origin": [13, 0, 17]}, "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#0"}, "east": {"uv": [0, 0, 14, 1], "texture": "#0"}, "south": {"uv": [0, 0, 1, 1], "texture": "#0"}, "west": {"uv": [0, 0, 14, 1], "texture": "#0"}, "up": {"uv": [0, 0, 14, 1], "rotation": 270, "texture": "#0"}, "down": {"uv": [0, 0, 14, 1], "rotation": 90, "texture": "#0"} } }, { "from": [1, 0, 3], "to": [15, 1, 4], "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 3]}, "faces": { "north": {"uv": [0, 0, 14, 1], "texture": "#0"}, "east": {"uv": [0, 0, 1, 1], "texture": "#0"}, "south": {"uv": [0, 0, 14, 1], "texture": "#0"}, "west": {"uv": [0, 0, 1, 1], "texture": "#0"}, "up": {"uv": [0, 0, 14, 1], "texture": "#0"}, "down": {"uv": [0, 0, 14, 1], "texture": "#0"} } }, { "from": [1, 0, 6], "to": [15, 1, 7], "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 6]}, "faces": { "north": {"uv": [0, 0, 14, 1], "texture": "#0"}, "east": {"uv": [0, 0, 1, 1], "texture": "#0"}, "south": {"uv": [0, 0, 14, 1], "texture": "#0"}, "west": {"uv": [0, 0, 1, 1], "texture": "#0"}, "up": {"uv": [0, 0, 14, 1], "texture": "#0"}, "down": {"uv": [0, 0, 14, 1], "texture": "#0"} } }, { "from": [1, 0, 12], "to": [15, 1, 13], "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 12]}, "faces": { "north": {"uv": [0, 0, 14, 1], "texture": "#0"}, "east": {"uv": [0, 0, 1, 1], "texture": "#0"}, "south": {"uv": [0, 0, 14, 1], "texture": "#0"}, "west": {"uv": [0, 0, 1, 1], "texture": "#0"}, "up": {"uv": [0, 0, 14, 1], "texture": "#0"}, "down": {"uv": [0, 0, 14, 1], "texture": "#0"} } }, { "from": [1, 0, 9], "to": [15, 1, 10], "rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 9]}, "faces": { "north": {"uv": [0, 0, 14, 1], "texture": "#0"}, "east": {"uv": [0, 0, 1, 1], "texture": "#0"}, "south": {"uv": [0, 0, 14, 1], "texture": "#0"}, "west": {"uv": [0, 0, 1, 1], "texture": "#0"}, "up": {"uv": [0, 0, 14, 1], "texture": "#0"}, "down": {"uv": [0, 0, 14, 1], "texture": "#0"} } }, { "from": [2, 0, 1], "to": [3, 1, 15], "rotation": {"angle": 0, "axis": "y", "origin": [2, 0, 17]}, "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#0"}, "east": {"uv": [0, 0, 14, 1], "texture": "#0"}, "south": {"uv": [0, 0, 1, 1], "texture": "#0"}, "west": {"uv": [0, 0, 14, 1], "texture": "#0"}, "up": {"uv": [0, 0, 14, 1], "rotation": 270, "texture": "#0"}, "down": {"uv": [0, 0, 14, 1], "rotation": 90, "texture": "#0"} } } ], "display": { "thirdperson_righthand": { "translation": [-5.5, 3, -3.5], "scale": [0.84, 0.92, 0.99] }, "firstperson_righthand": { "rotation": [13, 0, 0], "translation": [8.25, 1.75, -12.75], "scale": [1.25, 1.25, 1.25] }, "ground": { "translation": [0, 4.75, 0] }, "gui": { "rotation": [97, 0, 0], "translation": [0, -0.5, 0], "scale": [0.8, 0.8, 0.8] }, "fixed": { "rotation": [90, 0, 0], "scale": [1, 0.04, 1] } }, "groups": [ { "name": "group", "origin": [1, 0, 11], "color": 0, "children": [0, 1, 2, 3] }, { "name": "group", "origin": [10, 0, 17], "color": 0, "children": [4, 5, 6, 7, 8, 9, 10] }, 11 ] } ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/CreateHeatJS.md --- # CreateHeatJS (CreateJS Addon Mod) ### CreateHeatJS lets you define custom heat sources for some CreateJS processing recipes. **Examples**\ ![Example](/imgs/createheadjs/Example_1.png)![Example](/imgs/createheadjs/Example_2.png) Use `.heatLevel()` after a recipe to set a custom heat tier. **JEI shows localization keys by default, so you need to add your own language file.** ## Code Example `server_scripts` ```js ServerEvents.recipes((event) => { const { create } = event.recipes create.mixing('minecrafrt:diamond', [ 'minecrafrt:coal_block' ]).heatLevel('melt') create.compacting('minecrafrt:diamond', [ 'minecrafrt:coal_block' ]).heatLevel('frozen') }) ``` `starup_scripts` ```js CreateHeatJS.registerHeatEvent((event) => { // Melt event.registerHeat('melt', 1, 0xFF8C00) .addHeatSource('minecraft:fire') .register() // Frozen event.registerHeat('frozen', -1, 0x87CEFA) .addHeatSource('minecraft:blue_ice') .register() }) ``` ## Advanced `starup_scripts` ```js // Load the furnace class const $AbstractFurnaceBlock = Java.loadClass("net.minecraft.world.level.block.AbstractFurnaceBlock") /* * This style also works, * but it requires ProbeJS v7.0 or newer. */ const { $AbstractFurnaceBlock } = require("package/net/minecraft/world/level/block/AbstractFurnaceBlock") CreateHeatJS.registerHeatEvent((event) => { // Lit furnace event.registerHeat("BLAZE", 3, 0xed9c33) .addHeatSource("minecraft:furnace", "minecraft:furnace[lit=true]", (level, pos, blockStack) => { if (blockStack.hasProperty($AbstractFurnaceBlock.LIT)) { return blockStack.getValue($AbstractFurnaceBlock.LIT).booleanValue() } return false }) .register() // Frozen (requires the Ice Spikes biome) event.registerHeat("CRYOTHEUM", -1, 0x8BAAFF) .addHeatSource("minecraft:blue_ice", (level, pos, blockStack) => { return level.getBiome(pos) .is(new ResourceLocation("minecraft:ice_spikes")) }) .jeiTip() .register() }) ``` --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/CreateHeatJS.md --- # CreateHeatJS(CreateJS的附属Mod) ### CreateHeatJS可以允许你通过使用CreateJS自定义部分加工配方所需的热源 **例子**\ ![例子](/imgs/createheadjs/Example_1.png)![例子](/imgs/createheadjs/Example_2.png) 在配方后使用`.heatLevel()`方法来自定义热源等级 **JEI内显示默认的是本地化键名,需要自己写语言文件进行本地化** ## 代码展示 `server_scripts` ```js ServerEvents.recipes((event) => { const { create } = event.recipes create.mixing('minecrafrt:diamond', [ 'minecrafrt:coal_block' ]).heatLevel('melt') create.compacting('minecrafrt:diamond', [ 'minecrafrt:coal_block' ]).heatLevel('frozen') }) ``` `starup_scripts` ```js CreateHeatJS.registerHeatEvent((event) => { // 熔化 event.registerHeat('melt', 1, 0xFF8C00) .addHeatSource('minecraft:fire') .register() // 冻结 event.registerHeat('frozen', -1, 0x87CEFA) .addHeatSource('minecraft:blue_ice') .register() }) ``` ## 进阶 `starup_scripts` ```js // 读取熔炉类 const $AbstractFurnaceBlock = Java.loadClass("net.minecraft.world.level.block.AbstractFurnaceBlock") /* * 下面这种写法也可以 * 但是需要v7.0及以上的ProbeJS */ const { $AbstractFurnaceBlock } = require("package/net/minecraft/world/level/block/AbstractFurnaceBlock") CreateHeatJS.registerHeatEvent((event) => { // 点燃的熔炉 event.registerHeat("BLAZE", 3, 0xed9c33) .addHeatSource("minecraft:furnace", "minecraft:furnace[lit=true]", (level, pos, blockStack) => { if (blockStack.hasProperty($AbstractFurnaceBlock.LIT)) { return blockStack.getValue($AbstractFurnaceBlock.LIT).booleanValue() } return false }) .register() // 冻结(需要冰刺之地群系) event.registerHeat("CRYOTHEUM", -1, 0x8BAAFF) .addHeatSource("minecraft:blue_ice", (level, pos, blockStack) => { return level.getBiome(pos) .is(new ResourceLocation("minecraft:ice_spikes")) }) .jeiTip() .register() }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAdvanced/CreativeTab.md --- # Creative Tabs In Creative mode, players can directly pick items from creative tabs in the inventory. Some mods also put items into their own tabs. For example, in vanilla Minecraft, pistons, buttons, and levers are in the Redstone tab. This chapter explains how to register your own creative tab and move/add items into it. All examples below are written in `startup_scripts`. ## Register a Creative Tab ```js StartupEvents.registry("creative_mode_tab", (event) => { // Register a creative tab and assign its ID let tab = event.create("meng:items") // Set the creative tab icon (the item must exist) tab.icon(() => Item.of("meng:hello_item")) // Set the displayed name using a localization key tab.displayName = Text.translatable("item_group.meng.items") // Add items to the tab tab.content(() => [ "meng:hello_item" ]) }) ``` ## Modify a Creative Tab Besides registering your own tab and adding items, you can also modify an existing tab directly. ```js // The first argument is the creative tab ID (KubeJS tab ID is kubejs:tab) StartupEvents.modifyCreativeTab("meng:items", (event) => { // code }); ``` ### Methods Available for Tab Modification | Method | Description | | :------------------------------: | :----------------------: | | removeSearch(Ingredient) | Remove matching items from search | | setIcon(ItemStack) | Set the creative tab icon | | addBefore(ItemStack,ItemStack\[]) | Add before a specific item | | removeDisplay(Ingredient) | - | | addAfter(ItemStack,ItemStack\[]) | Add after a specific item | | setDisplayName(Component) | Set the creative tab display name | | add(ItemStack) | Add items into this creative tab | | remove(Ingredient) | Remove items from the creative tab | --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSBasic/Customs/BlockType/CropReg.md --- # Crop Registration In KubeJS, you can register crops directly. Many methods are available. Below is a method list and an example. ## Available Methods | Method Name | Args | Purpose | Return Type | | :--------------------------------------------------: | :---: | :--------------------------------------------------: | :------------------: | | age(number) | -> | Set crop growth stages | this | | age(number,Consumer\) | -> | Set collision shape for each growth stage | this | | crop(any) | ? | Add crop drop with 100% chance | this | | crop(any,number) | -> | Add crop drop with custom chance | this | | bonemeal(ToIntFunction\) | ? | Bonemeal growth callback | this | | growTick(ToDoubleFunction\) | -> | Random-tick growth callback | this | | survive(CropBlockBuilder$SurviveCallback\_) | -> | Growth/survival condition callback | this | | dropSeed(bool) | -> | Whether harvested crop drops seeds | this | | translationKey(string) | -> | Set translation key | BuilderBase\ | | formattedDisplayName() | - | Make displayName override lang file | BuilderBase\ | | formattedDisplayName(Component) | - | Set direct text to override lang file | BuilderBase\ | | dynamicMapColor(Function\) | ~ | Set map color behavior per block state | BlockBuilder | | toString(string) | -> | String conversion (rarely needed) | string | | createObject() | - | ? | ? | | createAdditionalObjects() | - | Create additional objects | void | | notifyAll() | - | ? | void | | wait(number) | - | ? | void | | getTranslationKeyGroup() | - | ~ | void | | getBuilderTranslationKey() | - | ~ | void | | 【static】 createShape(List\) | ~ | ? | VoxelShape | | get() | - | ? | Block | | getClass() | - | Get class | typeof any | | setWaterlogged(bool) | -> | **Deprecated** | - | | getWaterlogged() | - | **Deprecated** | - | ## Example ```js StartupEvents.registry("block", event => { event.create("test_crop", "crop") .age(3,cbb=>{ cbb.shape(0,0,0,0,16,2,16) cbb.shape(1,0,0,0,16,8,16) cbb.shape(2,0,0,0,16,12,16) cbb.shape(3,0,0,0,16,16,16) }) .growTick((blockstate, random) => { return 25; }) .bonemeal(rtc => { return rtc.random.nextInt(2) }) .crop(Item.of("stone")) .survive((blockstate, level, pos) => { // Check whether the area is loaded if (level.isAreaLoaded(pos,1)){ // Check whether the crop can see the sky if(level.canSeeSky(pos)){ // Check sky light level if (level.getBrightness("sky",pos) <= 8){ // Check block below if (level.getBlockState(pos.below()).is(Blocks.NETHERITE_BLOCK)){ return true } } }else{ // Check block light level if (level.getBrightness("block",pos) <= 8){ if (level.getBlockState(pos.below()).is(Blocks.NETHERITE_BLOCK)){ return true } } } } return false }) .texture("0", "minecraft:block/wheat_stage0") .texture("1", "minecraft:block/wheat_stage3") .texture("2", "minecraft:block/wheat_stage5") .texture("3", "minecraft:block/wheat_stage7") .item(seed => seed.texture("minecraft:wheat_seeds")) }) ``` This example reuses vanilla wheat textures (including the item texture). `age(3,cbb=>{})` mainly uses `shape` to define collision size per age. For `cbb.shape(2,0,0,0,16,12,16)`: `2` is the age stage. The first `0,0,0` is min `x,y,z`. `16,12,16` is max `x,y,z`. This is crop-stage collision/display shape logic, which is different from regular block `box` setup. `growTick` decides whether the crop grows when chosen by random tick. The formula is `random.nextInt((25 / returnValue) + 1)`. If you return `25`, random values become `0` or `1`; under vanilla logic, growth only succeeds on the `0` case. `bonemeal` handles bonemeal growth. Return value is how many growth stages to increase. Returning `1` increases age by 1; returning `0` does nothing. This example uses `rtc.random.nextInt(2)` to return either `0` or `1`. `crop` sets crop drops, similar to how wheat drops seeds and wheat. `survive` handles environment rules: * which block can support planting * required brightness * whether it can survive in different dimensions/conditions * return `true` to allow survival * return `false` to disallow survival (crop breaks if unsupported) * this callback might not run every `growTick` (possibly a behavior quirk) * it is reliably checked when neighboring block updates notify this crop * if check fails, crop breaks * similar to vanilla behavior where crops can break when light conditions are invalid `texture` sets textures for each age stage; this example uses vanilla textures. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAdvanced/CustomCommandRegistry.md --- # Custom Commands This chapter introduces how to write custom commands, including callable methods and return types. All content in this chapter is for `server_scripts`. ## Basic Writing To be written. --- --- url: 'https://docs.variedmc.cc/en/mods/custom/sidebarIndex.md' description: 'Custom-authored systems, internal tooling, and private mod project docs.' --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSProjects/Qi/CMMI.md --- # Custom Metal Materials Integration (CMMI) ## Introduction This project is designed as a quick all-in-one workflow: by filling in `[id, color, mining level]`, you can register `ingots, plates, raw ores, stone/deepslate ores, metal blocks, and molten fluids` together. The code itself is straightforward. The trickiest part is ore rendering. Ore registration works by rendering a separate ore texture layer over a vanilla stone model. The extra layer is slightly larger than stone (same size causes render issues). To apply tinting correctly only to the ore texture, set tinting on the ore layer in the model. To ensure tinting works correctly (tint ore, not base stone), configure tinting on the ore texture in the model. Result Preview ## Quick Explanation In ore models, there is a `tintindex` key. The [**official Minecraft Wiki**](https://minecraft.wiki/w/Tutorial:Models) explains it as: ``` Use a hardcoded tint index to recolor the texture. If set to `-1`, recoloring is disabled (default is `-1`). ``` So adding `"tintindex": 0` to the model texture allows that layer to be tinted independently. See the screenshots above for reference. ## File Download ## References [**Official Minecraft Wiki**](https://minecraft.wiki/w/Tutorial:Models) [**Comprehensive Resource Pack Guide: tintindex**](http://sqwatermark.com/resguide/vanilla/model/tintindex.html) ## Source Code ```js // Define namespace; change it if needed let namespace = "new_create:" // Fill in values in order: [id, color, mining level] let moltenRegisters = [ // ["test", 0xabc098, "wooden"] ] moltenRegisters.forEach(([name, color, level]) => { StartupEvents.registry("item", (event) => { // Ingot event.create(`${namespace + name}_ingot`) // Set texture path .texture(`${namespace}item/metal/ingot`) // Set color .color(color) // Add Forge tags for unified management .tag("forge:ingots") // Add Forge tags for unified management .tag(`forge:ingots/${name}`) // Plate event.create(`${namespace + name}_sheet`) // Set texture path .texture(`${namespace}item/metal/sheet`) // Set color .color(color) // Add Forge tags for unified management .tag("forge:plates") // Add Forge tags for unified management .tag(`forge:plates/${name}`) // Raw ore event.create(`${namespace}raw_${name}`) // Set texture path .texture(`${namespace}item/metal/raw_ore`) // Set color .color(color) // Add Forge tags for unified management .tag("forge:raw_materials") // Add Forge tags for unified management .tag(`forge:raw_materials/${name}`) }) StartupEvents.registry("block", (event) => { let pickaxe = "minecraft:mineable/pickaxe" // Mining level mapping let miningLevel = { wooden: "minecraft:needs_wooden_tool", stone: "minecraft:needs_stone_tool", iron: "minecraft:needs_iron_tool", gold: "minecraft:needs_gold_tool", diamond: "minecraft:needs_diamond_tool", nether: "forge:needs_netherite_tool" } // Stone ore event.create(`${namespace + name}_ore`) // Set model path .model(`${namespace}block/ore/ore`) // Set color (see explanation above for `0`) .color(0, color) // Set render type to support transparency .renderType("cutout") // Set sound type .soundType(SoundType.STONE) // Set hardness .hardness(3) // Set blast resistance .resistance(3) // Add Forge tags for unified management .tag("forge:ores") // Add Forge tags for unified management .tag(`forge:ores/${name}`) // Add Forge tags for unified management .tag("forge:ore_rates/dense") // Mark as pickaxe-mineable .tagBlock(pickaxe) // Add mining level tag .tagBlock(miningLevel[level]) // Require proper tool .requiresTool(true) // Set item model tint color .item((item) => { item.color(0, color) }) // Deepslate ore event.create(`${namespace}deepslate_${name}_ore`) // Set model path .model(`${namespace}block/ore/deepslate_ore`) // Set color (see explanation above for `0`) .color(0, color) // Set render type to support transparency .renderType("cutout") // Set sound type .soundType(SoundType.DEEPSLATE) // Set hardness .hardness(4.5) // Set blast resistance .resistance(4.5) // Add Forge tags for unified management .tag("forge:ores") // Add Forge tags for unified management .tag(`forge:ores/${name}`) // Add Forge tags for unified management .tag("forge:ore_rates/deepslate") // Mark as pickaxe-mineable .tagBlock(pickaxe) // Add mining level tag .tagBlock(miningLevel[level]) // Require proper tool .requiresTool(true) // Set item model tint color .item((item) => { item.color(0, color) }) // Block event.create(`${namespace + name}_block`) // Set texture path .textureAll(`${namespace}block/metal/block`) // Set sound type .soundType(SoundType.METAL) // Set color .color(color) // Set hardness .hardness(5) // Set blast resistance .resistance(5) // Add Forge tags for unified management .tag("forge:storage_blocks") // Add Forge tags for unified management .tag(`forge:storage_blocks/${name}`) // Mark as pickaxe-mineable .tagBlock(pickaxe) // Add mining level tag .tagBlock(miningLevel[level]) // Require proper tool .requiresTool(true) // Set item model tint color .item((item) => { item.color(color) }) }) StartupEvents.registry("fluid", (event) => { // Fluid textures should be at [modid/textures/block/fluid] const PATH = "block/fluid/" // Molten metal event.create(`${namespace}molten_${name}`) // Color .thinTexture(color) // Color .bucketColor(color) // Texture path .flowingTexture(`${namespace + PATH}flowing`) // Texture path .stillTexture(`${namespace + PATH}still`) // Add Forge tags for unified management .tag(`forge:molten_${name}`) // Add Forge tags for unified management .tag("forge:molten_materials") }) }) ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSProjects/Meng/CustomPortal.md --- # Custom Portals This project depends on [Custom Portal API \[Forge\]](https://www.mcmod.cn/class/10354.html), so install that mod before use. Mods and versions used: 1. rhino-forge-2001.2.2-build.18 2. architectury-9.2.14-forge 3. kubejs-forge-2001.6.5-build.14 4. probejs-6.0.1-forge 5. customportalapi-0.0.1-forge-1.20 ## Project Code This example code should be placed in the `startup_scripts` folder. ```js const $CustomPortalBuilder = Java.loadClass("net.kyrptonaught.customportalapi.api.CustomPortalBuilder") const $BuiltinDimensionTypes = Java.loadClass("net.minecraft.world.level.dimension.BuiltinDimensionTypes") StartupEvents.postInit(e=>{ $CustomPortalBuilder .beginPortal() // Begin building a custom portal ["frameBlock(net.minecraft.world.level.block.Block)"](Blocks.STONE) // Portal frame block .destDimID($BuiltinDimensionTypes.NETHER_EFFECTS) // Destination dimension .tintColor(131, 66, 184) // Portal RGB color .registerPortal(); // Register custom portal $CustomPortalBuilder .beginPortal() // Begin building a custom portal ["frameBlock(net.minecraft.world.level.block.Block)"](Blocks.DIAMOND_BLOCK) // Portal frame block .destDimID($BuiltinDimensionTypes.NETHER_EFFECTS) // Destination dimension .lightWithItem(Items.DIAMOND) // Item used to ignite portal .flatPortal() // Use flat portal style .tintColor(131, 133, 184) // Portal RGB color .registerPortal(); // Register custom portal }) ``` ## `CustomPortalBuilder` Callable Methods Open-source reference: | Method Name | Parameters | Description | | :-----------------------------------------------------------------------: | :-----------------: | :------------------------------------------------------: | | beginPortal() | - | Create a new portal | | registerPortal() | - | Register the portal after all properties are set | | frameBlock(ResourceLocation) | Block ID | Set portal frame block | | frameBlock(Block) | Block type | Set portal frame block | | destDimID(ResourceLocation) | Dimension ID | Set destination dimension | | tintColor(int) | Hex color | Set portal color | | tintColor(int r,int g,int b) | RGB ints | Set portal color | | lightWithWater() | - | Make portal ignite by water | | lightWithItem(Item) | Item type | Make portal ignite by item | | lightWithFluid(Fluid) | Fluid type | Make portal ignite by fluid | | customIgnitionSource(ResourceLocation) | ~ | a1 | | customIgnitionSource(PortalIgnitionSource) | ~ | a1 | | forcedSize(int w,int h) | w width, h height| Force portal dimensions | | customPortalBlock(CustomPortalBlock) | CustomPortalBlock | Use custom portal block | | returnDim(ResourceLocation,boolean) | dimension ID, b1 | Set return dimension | | onlyLightInOverworld() | - | a2 | | flatPortal() | - | Use flat portal style (like End portal visuals) | | customFrameTester(ResourceLocation) | ~ | a3 | | registerBeforeTPEvent(Function\) | ~ | Register a pre-teleport event (can cancel via SHOULDTP) | | registerInPortalAmbienceSound(Function\) | ~ | Register sound while player is in portal | | registerPostTPPortalAmbience(Function\) | ~ | Register sound during/after teleport | | registerPostTPEvent(Consumer\) | ~ | Register post-teleport event | ### Fabric-Only (Likely) Based on callable API differences, the following methods appear to be available only in the [Fabric version](https://www.mcmod.cn/class/15391.html). | Method Name | Parameters | Description | | :-----------------------------------------------: | :----------: | :-------------------------------------------------------------: | | beginPortal(PortalLink) | PortalLink | Create a new portal using a custom `PortalLink` | | registerPortalForced() | - | Force-register a portal | | registerPreIgniteEvent(PortalPreIgniteEvent) | ~ | Pre-ignite event; activation fails if event returns `false` | | registerIgniteEvent(PortalIgniteEvent) | ~ | Post-ignite event | | setPortalSearchYRange(int bottomY,int topY) | - | Set Y-range used for portal search/generation | | setReturnPortalSearchYRange(int bottomY,int topY) | - | Set Y-range used for return portal search/generation | ### Official Notes a1: Specify a Custom Ignition Source to be used to ignite the portal. You must manually trigger the ignition yourself. a2: Specify that this portal can only be ignited in the Overworld.Attempting to light it in other dimensions will fail. a3: Specify a custom portal frame tester to be used. b1: Should this portal only be ignitable in returnDimID. ## About Registering Portal Blocks ```js const $CustomPortalsMod = Java.loadClass("net.kyrptonaught.customportalapi.CustomPortalsMod") const $CustomPortalBlock = Java.loadClass("net.kyrptonaught.customportalapi.CustomPortalBlock") const $BlockBehaviourProperties = Java.loadClass("net.minecraft.world.level.block.state.BlockBehaviour$Properties") const $SoundType = Java.loadClass("net.minecraft.world.level.block.SoundType") const $EventBuses = Java.loadClass("dev.architectury.platform.forge.EventBuses"); let test; StartupEvents.init(e => { test = $CustomPortalsMod.registerOnlyBlock("test",()=>{ return new $CustomPortalBlock( $BlockBehaviourProperties.copy(Blocks.NETHER_PORTAL) .lightLevel(() => 11) .noCollission() .strength(-1) .sound($SoundType.GLASS) ) }) $CustomPortalsMod.BLOCKS.register($EventBuses.getModEventBus("kubejs").get()); }) StartupEvents.postInit(e=>{ $CustomPortalBuilder .beginPortal() ["frameBlock(net.minecraft.world.level.block.Block)"](Blocks.STONE) .customPortalBlock(test) .destDimID($BuiltinDimensionTypes.NETHER_EFFECTS) .tintColor(131, 66, 184) .registerPortal(); }) ``` ## Notes 1. This project was mainly tested on `1.20.1` with [Custom Portal API \[Forge\]](https://www.mcmod.cn/class/10354.html). 2. If errors occur because your mod version differs from this example, adjust the code accordingly. 3. This project focuses on the core implementation pattern. For more features, check the open-source project and extend from there. --- --- url: 'https://docs.variedmc.cc/en/develop/vanilla/datapack/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/zh/develop/vanilla/datapack/sidebarIndex.md' description: Minecraft数据包开发文档,函数、进度、战利品表、合成表、标签完整指南 --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/DelightJS.md --- # DelightJS --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/DelightJS.md --- # DelightJS --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSProjects/Meng/SmallProject/playerInGameOne.md --- # Detect a Player's First Login Main topics in this chapter: player login events and game stages. All code in this chapter belongs in `server_scripts`. Mods and versions used: 1. jei-1.20.1-forge-15.3.0.4 2. rhino-forge-2001.2.2-build.18 3. architectury-9.2.14-forge 4. kubejs-forge-2001.6.5-build.14 5. probejs-6.0.1-forge ## Full Code ```js PlayerEvents.loggedIn(event=>{ const player = event.player; if(!player.stages.has("oneInGame")){ player.tell("Welcome to the game for your first time") player.stages.add("oneInGame") } }) ``` This code detects a player's first login, sends a message, and uses KubeJS built-in stages to record that state. --- --- url: 'https://docs.variedmc.cc/en/develop/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/zh/develop/sidebarIndex.md' description: Minecraft开发文档,包含原版数据包开发、模组开发(Forge/Neoforge/Fabric)、插件开发完整教程和指南 --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/Digression/README.md --- # Digressions This section mainly explains terminology and is mostly unrelated to code. If you find anything missing or incorrect, please report it on [Git](https://gitee.com/gumengmengs/kubejs-course/issues/new/choose); it will be updated later. --- --- url: 'https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/CodeShare/Dining.md' --- # 饱和修补 ::: v-info 新建名为“饱和修补”的附魔。 在进食后,玩家每有1饱和度,执行一次以下操作: 筛选物品栏中有此附魔的耐久不满物品,随机选中一个。 修复此物品15耐久,并扣除玩家1饱和度。 ::: ```js // 在startup_scripts中新建“饱和修补”附魔。 StartupEvents.registry("enchantment", event => { event.create("dining") .breakable() // 可作用于任何有耐久度的物品。 .undiscoverable() // 宝藏附魔。 .maxLevel(1) .displayName("饱和修补"); }); // 在server_scripts中监听进食事件。 ItemEvents.foodEaten(event => { // 进食后,玩家每剩余1饱和度,执行一次以下事件。 for (let i = 0; i < Math.floor(event.player.saturation); i++) { // 读取进食玩家的物品栏。 let targets = event.player.inventory.allItems.toArray() //筛选物品栏中有“饱和修补”附魔的耐久不满物品。 .filter(item => item.hasEnchantment("kubejs:dining", 1) && item.damaged); let target = targets[Math.floor(targets.length * Math.random())]; // 如果存在符合条件的物品。 if (target) { // 修复此物品15耐久。 target.setDamageValue(target.damageValue - 15); // 扣除玩家1饱和度。 event.player.setSaturation(event.player.saturation - 1); } } }); // 附魔描述。 { "enchantment.kubejs.dining.desc": "用饱和度来修补工具和护甲的耐久值" } ``` --- --- url: 'https://docs.variedmc.cc/en/info/doc/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/en/info/doc/suggestion/doc.md' --- # 运营 该页面用于收集关于KubeJS文档运营的建议。 --- --- url: 'https://docs.variedmc.cc/zh/info/doc/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/zh/info/doc/suggestion/doc.md' --- # 运营 该页面用于收集关于KubeJS文档运营的建议。 --- --- url: 'https://docs.variedmc.cc/en/doc/sidebarIndex.md' description: Index of Doc section --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/en/info/KubeJS/borad/docError.md' --- # Documentation Errors Use this page to report and discuss issues in the KubeJS documentation. --- --- url: 'https://docs.variedmc.cc/en/doc/Catalogue.md' description: >- Workflows, structural conventions, and style guidance for contributing documentation to CrychicDoc. --- # Documentation Writing Standards {#main} ::: alert {"type": "success", "title": "Notice", "border": "start"} This page collects the collaboration flow, structural conventions, and writing boundaries for CrychicDoc documentation. Implementation details are covered in the topic pages listed below. ::: ## Related Pages | Page | Notes | | --- | --- | | [Sidebar Configuration](./sidebarGuide) | Explains the sidebar generation rules and the fields that usually need to be edited when pages or directories change. | | [Styles & Plugins](./pluginsGuide) | A guide to the Markdown extensions, containers, and custom components available in CrychicDoc. | | [LiteTree Component](./litetreeGuide) | A guide to using LiteTree to build clear and information-dense tree structures. | | [Extension Architecture](./extensionArchitecture) | Where configuration, runtime logic, components, plugins, and styles should live in CrychicDoc. | | [Hero Extension Playbook](./heroExtension) | How to extend hero typography, floating items, shaders, background renderers, and nav/search visuals in CrychicDoc. | | [Framework Maintainability Guide](./frameworkMaintainability) | Maintenance guidance for components, i18n, navigation layout, floating elements, and markdown plugins. | ## External References | Link | Role | | --- | --- | | [VitePress Docs](https://vitepress.dev/) | Official framework documentation | ## Contribution {#contribution} ### Workflow {#workflow-steps} ::: alert {"type": "warning", "title": "Important Reminder"} Please follow the **Conventional Commits** format for commit messages. It keeps changelogs and version tracking manageable. ::: :::: stepper @tab Initial Setup ```bash # Clone your fork git clone https://github.com/-%YourName/CrychicDoc.git cd CrychicDoc # Add upstream git remote add upstream https://github.com/PickAID/CrychicDoc.git ``` ::: v-info These are the basic setup steps for a first contribution. ::: @tab Start a New Contribution ```bash # Sync with upstream git fetch upstream git checkout main git merge upstream/main # Create a branch for the new change git checkout -b -%branch ``` @tab Submit Your Change ```bash # Stage the change git add . # Commit the change git commit -m "feat: add KubeJS event handling documentation" # Push to your fork git push origin -%branch ``` ::: v-success Commit message format: `type: brief description` ::: :::: ## Project Structure {#structure} ::: alert {"type": "info", "title": "Project Structure Overview"} Below is the main CrychicDoc project structure with brief notes on what each area is for. Knowing where things live makes later edits much easier. ::: ## Writing Conventions {#content} **Core Guides:** * **[Styles & Plugins Guide](./pluginsGuide.md)** - Markdown extensions and custom components. * **[Sidebar Configuration](./sidebarGuide.md)** - Sidebar generation rules and directory-level field usage. **Supporting Guides:** * [LiteTree Component](./litetreeGuide.md) - Build clear tree-style structures in Markdown. ### Frontmatter Configuration {#frontmatter} Each Markdown file should include a `frontmatter` block for page metadata: :::: chart-grid {"columns": 2, "gap": "24px"} ::: v-info Required Fields * **`title`** (`string`) - Page title shown in the sidebar * **`priority`** (`number`) - Sidebar sort order; smaller values appear first ::: ::: v-success Optional Fields * **`description`** (`string`) - Page description, also used for SEO * **`authors`** (`string[]`) - List of page authors * **`progress`** (`number`) - Documentation progress (0-100) * **`state`** (`string`) - Documentation status * **`metadata`** (`boolean | string | object`) - Metadata layout control. Supports `doc`, `kubejs`, `modding`, `mod`, or `false` * **`hidden`** (`boolean`) - Hide the page * **`root`** (`boolean`) - Mark a directory `sidebarIndex.md` as a sidebar root * **`collapsed`** (`boolean`) - Control whether a directory item starts collapsed * **`maxDepth`** (`number`) - Control directory expansion depth * **`externalLinks`** (`object[]`) - External links for directory-level nodes * **`useChildrenCollapsed`** (`object`) - Control how child directory items are folded in the current generated tree ::: :::: ::: alert {"type": "info", "title": "Frontmatter Example"} ```yaml --- title: KubeJS Event System description: A closer look at the KubeJS event system priority: 10 authors: ["Zhang San", "Li Si"] progress: 85 state: preliminary metadata: doc --- ``` ::: ::: details Metadata Mode Examples ```yaml --- metadata: mode: kubejs current: label: 1.20.1 value: kubejs-2001.6.5-build.7 requiredMods: - name: ProbeJS version: probejs-6.0.1 routes: - server_scripts --- ``` ```yaml --- metadata: mode: modding current: label: 1.20.4 value: NeoForge 20.4.x stack: - name: NeoGradle version: userdev 7.0.124 routes: - datagen - registry --- ``` ```yaml --- metadata: mode: mod side: both latest: 1.21.x supported: [1.21.x, 1.20.1, 1.19.2, 1.18.2] loaders: [Forge, NeoForge] sources: curseforge: https://www.curseforge.com/minecraft/mc-mods/example modrinth: https://modrinth.com/mod/example --- ``` ::: When `metadata.mode: mod`, `side` can be `server`, `client`, or `both`. Directory-level metadata usually lives in `sidebarIndex.md`. If a directory needs a clickable landing page, the system looks for `index.md -> Catalogue.md -> README.md` in that order. ### Headings and Anchors {#headings-anchors} ::: stepper @tab Heading Levels * Every document should have exactly one `H1` heading (`#`) * Heading levels should increase step by step rather than skipping levels * In most cases, `H4` is a practical upper limit @tab Anchor Settings Add explicit anchors to headings so generated URLs stay stable and readable: ```markdown ### This Is a Heading {#a-clear-anchor} ``` ::: ::::: chart-grid {"columns": 3, "gap": "20px"} ::: v-warning Respect Original Work Do not rewrite or remove large parts of someone else's work without talking to the original author first. ::: ::: v-info Communicate Early If you have questions or suggestions, raise them through GitHub Issues or the relevant community channels. ::: ::: v-success Contributor Attribution Third-party authors should submit at least one real content change so the system can match the correct GitHub account. ::: ::::: --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSProjects/Meng/SmallProject/DoubleDoor.md --- # Double Door Sync Main topics in this chapter: block right-click events and changing block states. All code in this chapter belongs in `server_scripts`. Mods and versions used: 1. jei-1.20.1-forge-15.3.0.4 2. rhino-forge-2001.2.2-build.18 3. architectury-9.2.14-forge 4. kubejs-forge-2001.6.5-build.14 5. probejs-6.0.1-forge ## Full Code ```js const $DoorBlock = Java.loadClass("net.minecraft.world.level.block.DoorBlock"); const $Boolean = Java.loadClass("java.lang.Boolean"); BlockEvents.rightClicked(event => { if (event.getHand() != "MAIN_HAND") return; if (event.player.isShiftKeyDown()) return; let block = event.block; let bs = block.blockState; if (!(bs.block instanceof $DoorBlock)) return; if (block.id == 'minecraft:iron_door' || bs.block.idLocation.namespace == "create") return; let open = $DoorBlock.OPEN, facing = $DoorBlock.FACING, hinge = $DoorBlock.HINGE, neighborBlock; if (bs.getValue(hinge) == $DoorHingeSide.LEFT) { if (bs.getValue(facing) == $Direction.SOUTH) neighborBlock = block.west; else if (bs.getValue(facing) == $Direction.WEST) neighborBlock = block.north; else if (bs.getValue(facing) == $Direction.NORTH) neighborBlock = block.east; else if (bs.getValue(facing) == $Direction.EAST) neighborBlock = block.south; } else { if (bs.getValue(facing) == $Direction.SOUTH) neighborBlock = block.east; else if (bs.getValue(facing) == $Direction.WEST) neighborBlock = block.south; else if (bs.getValue(facing) == $Direction.NORTH) neighborBlock = block.west; else if (bs.getValue(facing) == $Direction.EAST) neighborBlock = block.north; } if (neighborBlock.id == block.id && neighborBlock.blockState.getValue(hinge) != bs.getValue(hinge)) { neighborBlock.setBlockState(neighborBlock.blockState.setValue(open, !bs.getValue(open) ? $Boolean.TRUE : $Boolean.FALSE), 3) } }) ``` This script makes paired doors open and close together when the player interacts with one door. If the player is sneaking while opening or closing a door, the double-door sync will not trigger. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSProjects/Wolf/EnchantOnTable.md --- # Enchant Items on the Crafting Table ```js ServerEvents.recipes((event) => { // Allow iron swords to be enchanted on the crafting table enchantCrafting('iron_sword', event) }) /** * Register a recipe that applies enchanted-book enchantments to an item. * @param {$ItemStack_} enchanted_item - ItemStack of the item to enchant * @param {$RecipesEventJS_} event - Recipe event */ const enchantCrafting = (enchanted_item, event) => { event.shapeless(enchanted_item, [enchanted_item, 'minecraft:enchanted_book']) .modifyResult((/**@type {$ModifyRecipeCraftingGrid_}*/grid, /**@type {$ItemStack_} */item) => { item = grid.find(enchanted_item) let addition = grid.find('minecraft:enchanted_book').enchantments if (addition.isEmpty()) { // If the book has no enchantments, return the original item return item.withCount(1) } let origin = item.enchantments if (origin.isEmpty()) { // If the original item has no enchantments, apply all from the book return item.enchant(addition).withCount(1) } // Neither side is empty let combinedOrgin = origin // Copy original item enchantments as the base item.enchantments.clear() addition.forEach((id, lvl) => { // Iterate over enchanted-book enchantments // Check whether both have the same enchantment ID if (origin.containsKey(id)) { if (lvl > origin.get(id)) { // If the book level is higher, replace the original level combinedOrgin.replace(id, lvl) } } else { // If the item does not have this enchantment, merge it in combinedOrgin.put(id, lvl) } }) item.nbt.remove('Enchantments') return item.enchant(combinedOrgin).withCount(1) }) } ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/Entity/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Introduction/Entity/Description.md --- # Entity This section provides an overview and structured entry point for the `Entity` directory. --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Introduction/Entity/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/zh/modpack/kubejs/1.21/Entity/sidebarIndex.md' --- This file stores sidebar metadata for this directory. --- --- url: 'https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/Entity/Brain.md' description: 'Brain system, memory, sensory locations and KubeJS usage' --- # Brain ## Overview {#overview} Brain is the core AI controller for mobs in Minecraft 1.19+, replacing the older Goal system. It provides more advanced behavior management through Memory, Sensors, and Tasks. ## Mechanism Description {#mechanism} * Core component of mob AI, managing memory (Memory), sensory (Sensors), and behavior tasks (Tasks) * Memory: Stores information about the entity, such as nearest player, home position, mood, etc. * Sensors: Collect information from the environment and update memory * Tasks: Execute behaviors based on memory and current state ## Common Methods {#common\_methods} ```js // Get Brain const brain = mob.getBrain(); // Memory Operations brain.setMemory("minecraft:last_hurt_by", player); const lastHurtBy = brain.getMemory("minecraft:last_hurt_by"); // Check memory exists if (brain.hasMemory("minecraft:visible_targets")) { /* ... */ } ``` ## Notes {#notes} * Brain is available in Minecraft 1.19+ * For 1.18 and earlier, use Goal system instead * Memory types vary by mob type --- --- url: 'https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/Entity/Goal.md' description: 'Goal system, goalSelector, targetSelector and KubeJS usage' --- # Entity Goal System ## Overview {#overview} The Goal System is the core AI behavior framework in Minecraft, controlling how entities make decisions and execute actions. It is divided into two main components: goalSelector (daily behavior goals) and targetSelector (attack/target goals). ## Core Concepts {#core\_concepts} ### goalSelector {#goal-selector} Manages daily behaviors like wandering, attacking, breeding, etc. Lower priority numbers execute first. | Priority | Typical Use | | -------- | ----------- | | 0 | Critical (e.g., float when in water) | | 1-2 | Combat (e.g., melee attack) | | 3-4 | Movement (e.g., random wander) | | 5+ | Idle behaviors | ### targetSelector {#target-selector} Manages target selection and tracking, used for hostile mobs to find and track attack targets. ## Lifecycle Methods {#lifecycle} Goals have a clear lifecycle: | Stage | Method | Description | | ----- | ------ | ----------- | | Can Use | canUse() | Check if goal can be activated | | Start | start() | Called when goal starts executing | | Tick | tick() | Called each tick while goal is active | | Can Continue | canContinueToUse() | Check if goal should continue | | Stop | stop() | Called when goal stops | ## Common Goal Classes {#common\_goals} ### Movement Goals {#movement} | Goal Class | Description | | ---------- | ----------- | | `FloatGoal` | Float in water | | `RandomStrollGoal` | Random wandering | | `LookAtPlayerGoal` | Look at player | | `MoveTowardsTargetGoal` | Move toward target | | `BreedGoal` | Find mate and breed | ### Combat Goals {#combat} | Goal Class | Description | | ---------- | ----------- | | `MeleeAttackGoal` | Melee attack target | | `RangedAttackGoal` | Ranged attack | | `HurtByTargetGoal` | Attack what hurt it | ## Usage Examples {#examples} ```js // Add goal const FloatGoal = Java.loadClass("net.minecraft.world.entity.ai.goal.FloatGoal"); mob.goalSelector.addGoal(0, new FloatGoal(mob)); // Add target goal const NearestAttackableTargetGoal = Java.loadClass("net.minecraft.world.entity.ai.targeting.NearestAttackableTargetGoal"); mob.targetSelector.addGoal(0, new NearestAttackableTargetGoal(mob, Player.class, true)); ``` ## Notes {#notes} * Goal priority: Lower number = higher priority * Only one goal per priority level in each selector * Use `removeGoal` to clear goals * For 1.19+, consider using Brain system instead --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSBasic/LootTables/EntityLootTable.md --- # Entity Loot This section introduces basic KubeJS syntax and common parameters for entity loot. ## Basic Syntax ```js ServerEvents.entityLootTables(event => { event.addEntity("minecraft:pig", loot => { loot.addPool(pool => { pool.addItem("diamond").weight(1).count([3, 6]) pool.addItem("iron_ingot").weight(2) }) }) }) ``` In the code above, pig drops are changed to either 3-6 diamonds or 1 iron ingot. Iron has weight 2 and diamonds have weight 1 ([weight concept](../../Digression/Weight)). `pool.addItem("diamond").weight(1).count([3, 6])` sets weight and count separately. You can also combine them as `pool.addItem("diamond",1,[3,6])`. If you want to add extra drops to existing entity loot, you can do this: ```js ServerEvents.entityLootTables(event => { event.modifyEntity("minecraft:pig", loot => { loot.addPool(pool => { pool.addItem("pig_spawn_egg") }) }) ``` Other than the event method (`addEntity` vs `modifyEntity`), the parameters inside are the same. ## Common Methods and Examples The examples below all use this top-level structure: ```js ServerEvents.entityLootTables(event => { event.modifyEntity("minecraft:pig", loot => { loot.addPool(pool => { //code } } }) ``` `code` below means the content inside the pool callback. ### setUniformRolls Random number of rolls from the pool. ```js pool.setUniformRolls(1,3) ``` Parameter 1: minimum roll count Parameter 2: maximum roll count ### enchantRandomly Apply random enchantments to dropped items. ```js pool.enchantRandomly("minecraft:smite") ``` Parameter: enchantment id ### lootingEnchant Make drop count affected by Looting and set values. ```js pool.lootingEnchant(2,10) ``` Parameter 1: extra drop amount per Looting level Parameter 2: max drop count ### killedByPlayer Require the entity to be killed by a player. ```js pool.killedByPlayer() ``` ### addEmpty Set the weight ratio for an empty result. ```js pool.addEmpty(2) ``` Parameter: weight ratio ### furnaceSmelt Apply furnace-smelting behavior to drops (similar to furnace smelting). ```js pool.furnaceSmelt() ``` ### randomChanceWithLooting Random chance affected by Looting. ```js pool.randomChanceWithLooting(0.1,0.2) ``` Parameter 1: chance without Looting Parameter 2: additional chance per Looting level ### entityProperties Match entity properties; if matched, pool items can drop. ```js pool.entityProperties("killer", { type:"minecraft:player", equipment: { mainhand: { items: [ "minecraft:diamond_sword" ], predicates:{ enchantments:{ enchantments:["minecraft:silk_touch"] } } } } }) ``` Checks whether the pig killer is a player using a diamond sword with Silk Touch. Parameter 1: entity target (optional: `"[this](../../Digression/LootContext#this)"`, `"[killer_player](../../Digression/LootContext#killer_player)"`, `"[killer](../../Digression/LootContext#killer)"`, `"[direct_killer](../../Digression/LootContext#direct_killer)"`) Parameter 2: JSON predicate under `entity_properties` in loot-table predicates ### addTag Add tag-based loot entries. ```js pool.addTag("minecraft:beds",true) ``` Parameter 1: tag id Parameter 2: whether to randomly pick one item from the tag ### rolls Number of rolls for this pool. ```js pool.rolls = 3 ``` --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/Entity/Methods.md description: KubeJS Entity API common methods classification and usage reference --- # Entity Common Methods ## Overview {#overview} This page summarizes common methods in the KubeJS Entity API, covering operations such as attributes, AI, status, equipment, and data, facilitating reference and scripting development. ## Method Categories and Descriptions {#categories} ### Attribute Related {#attribute} * `getAttribute(name)`: Get attribute instance * `modifyAttribute(name, id, amount, operation)`: Add attribute modifier * \`removeAttributeModifier(name Remove attribute modifier , id)\`:### AI Related {#ai} * `addGoal(priority, goal)`: Add AI goal * `setTarget(entity)`: Set attack target * `getTarget()`: Get current target ### Status Related {#status} * `getHealth()` / `setHealth(value)`: Get/set health * `isAlive()` / `isDeadOrDying()`: Survival status check * `addEffect(effect, duration, amplifier)`: Add potion effect * `removeEffect(effect)`: Remove potion effect * `hasEffect(effect)`: Check potion effect ### Equipment Related {#equipment} * `getItemBySlot(slot)`: Get item in specified equipment slot * `setItemSlot(slot, item)`: Set item in equipment slot * `mainHandItem` / `offHandItem`: Main hand/off hand item ### Data and Persistence {#data} * `persistentData`: Custom data storage * `addTag(tag)` / `removeTag(tag)`: Add/remove tag ## Usage Examples {#examples} ```js // Get and modify attribute const attr = entity.getAttribute('generic.max_health'); attr.setBaseValue(40); entity.modifyAttribute('generic.max_health', 'kubejs:bonus', 8, 'addition'); // Add AI goal const FloatGoal = Java.loadClass('net.minecraft.world.entity.ai.goal.FloatGoal'); entity.goalSelector.addGoal(0, new FloatGoal(entity)); // Status and potions entity.setHealth(10); entity.addEffect('minecraft:regeneration', 200, 1); // Equipment operations entity.setItemSlot('head', Item.of('minecraft:diamond_helmet')); // Data storage entity.persistentData.putString('role', 'boss'); ``` ## Notes {#notes} * Some methods are only available on the server side or in specific events, pay attention to compatibility. * For detailed types and parameters, refer to [Forge JavaDocs](https://mcstreetguy.github.io/ForgeJavaDocs/1.20.1-47.1.0/index.html). ## Basic Entity Methods {#basic\_entity\_methods} ### Entity Information Retrieval {#entity\_info} | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `getType()` | - | `string` | Get entity type ID | | `getId()` | - | `number` | Get entity numeric ID | | `getStringUuid()` | - | `string` | Get entity UUID string | | `getUuid()` | - | `Internal.UUID` | Get entity UUID object | | `getName()` | - | `Component` | Get entity name component | | `getDisplayName()` | - | `Component` | Get display name | | `getCustomName()` | - | `Component` | Get custom name | | `hasCustomName()` | - | `boolean` | Has custom name | ### Position and Coordinates {#position} > \[!NOTE] Detailed Information > For detailed information about vectors, coordinates, and movement mechanisms, please refer to [Vector and Coordinates documentation](./Vector) | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `getX()` | - | `number` | Get X coordinate | | `getY()` | - | `number` | Get Y coordinate | | `getZ()` | - | `number` | Get Z coordinate | | `blockPosition()` | - | `BlockPos` | Get block position | | `position()` | - | `Vec3d` | Get precise position vector | | `getEyePosition()` | - | `Vec3d` | Get eye position | ### Rotation and Orientation {#rotation} | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `getYaw()` | - | `number` | Get yaw angle | | `getPitch()` | - | `number` | Get pitch angle | | `getYHeadRot()` | - | `number` | Get head rotation | | `setYaw(arg0: number)` | `number` | `void` | Set yaw angle | | `setPitch(arg0: number)` | `number` | `void` | Set pitch angle | | `setYHeadRot(arg0: number)` | `number` | `void` | Set head rotation | | `getLookAngle()` | - | `Vec3d` | Get look direction vector | ### Movement and Teleportation | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `setPosition(x, y, z)` | `number, number, number` | `void` | Set position | | `setPos(x, y, z)` | `number, number, number` | `void` | Set position | | `setPos(vec)` | `Vec3d` | `void` | Set position | | `teleportTo(x, y, z)` | `number, number, number` | `void` | Teleport to coordinates | | `teleportTo(dimension, x, y, z, yaw, pitch)` | `ResourceLocation, number, number, number, number, number` | `void` | Cross-dimension teleport | | `moveTo(x, y, z)` | `number, number, number` | `void` | Move to position | ### Motion and Velocity > \[!NOTE] Detailed Information > For detailed information about Motion, please refer to [Vector and Coordinates documentation](./Vector#motion-system) | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `getMotionX()` | - | `number` | Get X axis velocity | | `getMotionY()` | - | `number` | Get Y axis velocity | | `getMotionZ()` | - | `number` | Get Z axis velocity | | `setMotionX(x)` | `number` | `void` | Set X axis velocity | | `setMotionY(y)` | `number` | `void` | Set Y axis velocity | | `setMotionZ(z)` | `number` | `void` | Set Z axis velocity | | `setMotion(x, y, z)` | `number, number, number` | `void` | Set three-axis velocity | | `addMotion(x, y, z)` | `number, number, number` | `void` | Add velocity | | `getDeltaMovement()` | - | `Vec3d` | Get velocity vector | | `setDeltaMovement(vec)` | `Vec3d` | `void` | Set velocity vector | ### Type Checking | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `isPlayer()` | - | `boolean` | Is player | | `isLiving()` | - | `boolean` | Is living entity | | `isAnimal()` | - | `boolean` | Is animal | | `isMonster()` | - | `boolean` | Is monster | | `isVehicle()` | - | `boolean` | Is vehicle | | `isSpectator()` | - | `boolean` | Is spectator | | `isAttackable()` | - | `boolean` | Is attackable | | `isAlive()` | - | `boolean` | Is alive | | `isRemoved()` | - | `boolean` | Is removed | ::: code-group ```js [Basic Information] // Get entity basic information const entityType = entity.getType(); const entityId = entity.getId(); const uuid = entity.getStringUuid(); const name = entity.getName(); // Type checking if (entity.isPlayer()) { console.log("This is a player"); } else if (entity.isLiving()) { console.log("This is a living entity"); } ``` ```js [Position Operations] // Get coordinates const x = entity.getX(); const y = entity.getY(); const z = entity.getZ(); const blockPos = entity.blockPosition(); // Set position entity.setPosition(100, 64, 200); entity.setYaw(90); entity.setPitch(0); // Teleportation entity.teleportTo(100, 64, 200); entity.teleportTo("minecraft:the_nether", 0, 64, 0, 0, 0); ``` ::: ## LivingEntity Methods {#living\_entity\_methods} All methods inherited from Entity, plus the following entity-specific methods: ### Health {#health} | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `getHealth()` | - | `number` | Get current health | | `getMaxHealth()` | - | `number` | Get max health | | `setHealth(arg0: number)` | `number` | `void` | Set health | | `heal(arg0: number)` | `number` | `void` | Heal entity | | `hurt(arg0: DamageSource, arg1: number)` | `DamageSource, number` | `boolean` | Deal damage | | `kill()` | - | `void` | Kill entity | | `isDeadOrDying()` | - | `boolean` | Is dead or dying | ### Equipment Management {#equipment\_manage} | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `getMainHandItem()` | - | `ItemStack` | Get main hand item | | `getOffhandItem()` | - | `ItemStack` | Get off hand item | | `setMainHandItem(item: ItemStack)` | `ItemStack` | `void` | Set main hand item | | `getItemBySlot(arg0: EquipmentSlot)` | `EquipmentSlot` | `ItemStack` | Get item in specified slot | | `setItemSlot(arg0: EquipmentSlot, arg1: ItemStack)` | `EquipmentSlot, ItemStack` | `void` | Set item in slot | | `getArmorSlots()` | - | `Iterable` | Get armor slots | | `getHandSlots()` | - | `Iterable` | Get hand slots | ### Potion Effects {#potion\_effects} | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `addEffect(arg0: MobEffectInstance)` | `MobEffectInstance` | `boolean` | Add potion effect | | `addEffect(arg0: MobEffectInstance, arg1: Entity)` | `MobEffectInstance, Entity` | `boolean` | Add potion effect with source | | `removeEffect(arg0: MobEffect)` | `MobEffect` | `boolean` | Remove specified effect | | `removeAllEffects()` | - | `boolean` | Remove all effects | | `hasEffect(arg0: MobEffect)` | `MobEffect` | `boolean` | Check if has effect | | `getEffect(arg0: MobEffect)` | `MobEffect` | `MobEffectInstance` | Get effect instance | | `getActiveEffects()` | - | `Collection` | Get all active effects | ### Attributes {#attribute\_system} | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `getAttribute(arg0: Attribute)` | `Attribute` | `AttributeInstance` | Get attribute instance | | `getAttributeValue(arg0: Attribute)` | `Attribute` | `number` | Get attribute value | | `getAttributeBaseValue(arg0: Attribute)` | `Attribute` | `number` | Get base attribute value | | `getAttributes()` | - | `AttributeMap` | Get attribute map | ::: code-group ```js [Health Operations] // Health management const currentHealth = entity.getHealth(); const maxHealth = entity.getMaxHealth(); // Heal entity entity.heal(10); // Deal damage entity.hurt(entity.damageSources().magic(), 5.0); // Set health entity.setHealth(20); ``` ```js [Equipment Management] // Get equipment const mainHand = entity.getMainHandItem(); const helmet = entity.getItemBySlot("head"); // Set equipment entity.setMainHandItem(Item.of("minecraft:diamond_sword")); entity.setItemSlot("head", Item.of("minecraft:diamond_helmet")); ``` ```js [Potion Effects] // Add effect (need to use PotionEffects) entity.potionEffects.add("minecraft:speed", 200, 1); entity.potionEffects.add("minecraft:strength", 400, 0); // Check effect const hasSpeed = entity.hasEffect("minecraft:speed"); // Remove effect entity.removeEffect("minecraft:poison"); entity.removeAllEffects(); ``` ::: ## Player Methods {#player\_methods} All methods inherited from LivingEntity, plus the following player-specific methods: ### Inventory Management {#inventory} | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `getInventory()` | - | `InventoryKJS` | Get inventory object | | `give(item: ItemStack)` | `ItemStack` | `void` | Give item | | `sendData(channel: string, data: CompoundTag)` | `string, CompoundTag` | `void` | Send data packet | ### Experience | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `getStats()` | - | `PlayerStatsJS` | Get player statistics | | `awardStat(arg0: ResourceLocation, arg1: number)` | `ResourceLocation, number` | `void` | Award stat point | | `addXPLevels(l: number)` | `number` | `void` | Add XP levels | ### Player Abilities {#abilities} | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `getAbilities()` | - | `Abilities` | Get abilities object | | `onUpdateAbilities()` | - | `void` | Sync abilities to client | ### GUI Interaction | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `notify(title: Component, text: Component)` | `Component, Component` | `void` | Send notification | | `closeMenu()` | - | `void` | Close menu | | `openCommandBlock(arg0: CommandBlockEntity)` | `CommandBlockEntity` | `void` | Open command block | ::: code-group ```js [Inventory Operations] // Get inventory const inventory = player.getInventory(); // Give items player.give(Item.of("minecraft:diamond", 64)); // Check items const hasDiamond = inventory.contains("minecraft:diamond"); const diamondCount = inventory.count("minecraft:diamond"); ``` ```js [Experience and Statistics] // Add experience player.addXPLevels(5); // Get statistics const stats = player.getStats(); // Award stat point player.awardStat("minecraft:play_time", 100); ``` ```js [Abilities Management] // Get abilities const abilities = player.getAbilities(); // Set flying abilities.mayfly = true; abilities.flying = true; // Sync to client player.onUpdateAbilities(); ``` ::: ## Data Storage Methods {#data\_storage\_methods} ### persistentData Operations {#persistentdata} persistentData is KubeJS's custom data storage, **NOT NBT data**. | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `getPersistentData()` | - | `CompoundTag` | Get persistent data | | `putString(key, value)` | `string, string` | `void` | Store string | | `getString(key)` | `string` | `string` | Read string | | `putInt(key, value)` | `string, number` | `void` | Store integer | | `getInt(key)` | `string` | `number` | Read integer | | `putBoolean(key, value)` | `string, boolean` | `void` | Store boolean | | `getBoolean(key)` | `string` | `boolean` | Read boolean | | `contains(key)` | `string` | `boolean` | Check if key exists | ### NBT Operations {#nbt} | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `getNbt()` | - | `CompoundTag` | Get NBT data | | `setNbt(nbt: CompoundTag)` | `CompoundTag` | `void` | Set NBT data | | `mergeNbt(tag: CompoundTag)` | `CompoundTag` | `Entity` | Merge NBT data | | `save(arg0: CompoundTag)` | `CompoundTag` | `boolean` | Save to NBT | | `load(arg0: CompoundTag)` | `CompoundTag` | `void` | Load from NBT | ::: code-group ```js [persistentData] // Store data entity.persistentData.putString("player_role", "admin"); entity.persistentData.putInt("death_count", 5); entity.persistentData.putBoolean("is_vip", true); // Read data const role = entity.persistentData.getString("player_role"); const deaths = entity.persistentData.getInt("death_count"); const isVip = entity.persistentData.getBoolean("is_vip"); // Check data if (entity.persistentData.contains("first_join")) { console.log("Data exists"); } ``` ```js [NBT Operations] // Get and set NBT const nbt = entity.getNbt(); entity.setNbt(nbt); // Merge NBT entity.mergeNbt({ CustomName: "§aSpecial Entity", Health: 100, }); ``` ::: ## World Interaction Methods {#world\_interaction\_methods} ### Distance Calculation {#distance} | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `distanceTo(arg0: Entity)` | `Entity` | `number` | Calculate distance to entity | | `distanceToSqr(arg0: Vec3d)` | `Vec3d` | `number` | Calculate squared distance to vector | | `distanceToEntitySqr(arg0: Entity)` | `Entity` | `number` | Calculate squared distance to entity | | `closerThan(arg0: Entity, arg1: number)` | `Entity, number` | `boolean` | Is closer than specified distance | ### Ray Tracing {#raytrace} | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `rayTrace(distance: number)` | `number` | `RayTraceResultJS` | Ray trace | | `rayTrace(distance: number, fluids: boolean)` | `number, boolean` | `RayTraceResultJS` | Ray trace (including fluids) | | `pick(arg0: number, arg1: number, arg2: boolean)` | `number, number, boolean` | `HitResult` | Pick detection | ### Collision Detection {#collision} | Method | Parameters | Return Type | Description | | ------ | ---------- | ----------- | ------------ | | `getBoundingBox()` | - | `AABB` | Get collision box | | `setBoundingBox(arg0: AABB)` | `AABB` | `void` | Set collision box | | `isColliding(arg0: BlockPos, arg1: BlockState)` | `BlockPos, BlockState` | `boolean` | Is colliding with block | | `canCollideWith(arg0: Entity)` | `Entity` | `boolean` | Can collide with entity | ::: code-group ```js [Distance Calculation] // Calculate distance const distance = player.distanceTo(targetEntity); const distanceSq = player.distanceToSqr(targetPos); // Distance check if (player.closerThan(targetEntity, 10)) { console.log("Target is within 10 blocks"); } ``` ```js [Ray Tracing] // Basic ray tracing const rayTrace = player.rayTrace(10.0); if (rayTrace.block) { console.log("Looking at block:", rayTrace.block); } // Ray trace including fluids const rayTraceFluid = player.rayTrace(10.0, true); if (rayTraceFluid.entity) { console.log("Looking at entity:", rayTraceFluid.entity); } ``` ::: --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/EntityJS/README.md --- # EntityJS This chapter introduces how to use [EntityJS](https://www.mcmod.cn/class/14518.html). --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/EntityJS/README.md --- # EntityJS 在这一章节中将会介绍模组[EntityJS](https://www.mcmod.cn/class/14518.html)的使用 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/Tag/EntityType.md --- # 实体类型标签 ## 概述 * 原版的流体标签应用例:minecraft:axolotl\_always\_hostiles 美西螈总是与这些生物保持敌对。 * 原版实体类型标签应用列表:[minecraft-wiki/标签#实体类型](https://zh.minecraft.wiki/w/%E6%A0%87%E7%AD%BE#%E5%AE%9E%E4%BD%93%E7%B1%BB%E5%9E%8B%E6%A0%87%E7%AD%BE) ## 添加实体类型标签 * 事件:ServerEvents.tags('minecraft:entity\_type', event => {}); * 语句:event.add(标签id, 实体类型id); ::: code-group ```js [KubeJS] ServerEvents.tags('minecraft:entity_type', event => { // 标签id,实体类型id event.add('kubejs:my_entity_type', 'minecraft:zombie'); }) ``` ```js [KubeJS] // 使用for循环对付更多的实体类型id ServerEvents.tags('minecraft:entity_type', event => { /** * 要加进标签的实体id数组 * @type {Special.EntityType[]} */ const tagEntityTypes = [ 'minecraft:zombie' ] for (const entityType of tagEntityTypes) { event.add('kubejs:my_entity_type', entityType); } }) ``` ::: ## 删除实体类型标签 * 事件:ServerEvents.tags('minecraft:entity\_type', event => {}); * 语句:event.remove(标签id, 实体类型id); ::: code-group ```js [KubeJS] ServerEvents.tags('minecraft:entity_type', event => { // 标签id,实体类型id event.remove('kubejs:my_entity_type', 'minecraft:zombie'); }) ``` ```js [KubeJS] // 使用for循环对付更多的实体类型id ServerEvents.tags('minecraft:entity_type', event => { /** * 要从标签中删除的实体类型id数组 * @type {Special.EntityType[]} */ const tagEntityTypes = [ 'minecraft:zombie' ] for (const entityType of tagEntityTypes) { event.remove('kubejs:my_entity_type', entityType); } }) ``` ::: --- --- url: 'https://docs.variedmc.cc/en/info/KubeJS/Q&A/error.md' --- # 报错 该页面用于讨论KubeJS的报错问题。 --- --- url: 'https://docs.variedmc.cc/en/info/KubeJS/code/error.md' --- # 纠错 该页面用于讨论 KubeJS 代码中的错误修正问题。 --- --- url: 'https://docs.variedmc.cc/zh/info/KubeJS/Q&A/error.md' --- # 报错 该页面用于讨论KubeJS的报错问题。 --- --- url: 'https://docs.variedmc.cc/zh/info/KubeJS/code/error.md' --- # 纠错 该页面用于讨论 KubeJS 代码中的错误修正问题。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/Introduction/Event/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.21/Introduction/Addon/LootJs/Loot Modifiers/Event.md --- # Event This document is reserved for Event reference content in this section. --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Introduction/Event/Description.md --- # Event This section provides an overview and structured entry point for the `Event` directory. --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Introduction/Event/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.21/Addon/LootJs/Loot Modifiers/Event.md --- --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.21/Addon/LootJs/LootModifiers/Event.md --- # Loot Modification Event 该事件用于创建战利品修饰器,这些修饰器用于修改滚动战利品表后直接生成的物品。也就是说他不直接修改战利品表,而是修改战利品表最后生成的物品。 ## getGlobalModifiers 返回所有由模组注册的全局战利品修饰器列表。 语法: * `.getGlobalModifiers()` ## removeGlobalModifiers 删除所有由指定过滤器指定的模组注册的全局战利品修饰器。 语法: * `.removeGlobalModifiers(filter: string | regex)` ## addTableModifier 为所有与给指定过滤器匹配的战利品表创建战利品修饰器。 语法: * `.addTableModifier(filter: string | string[] | regex)` ,返回一个LootModifier ::: code-group ```js [使用过滤器] LootJS.modifiers(event => { event .addTableModifier("minecraft:chests/simple_dungeon") .randomChance(0.5) .addLoot("minecraft:gunpowder") }) ``` ```js [支持正则表达式] LootJS.modifiers(event => { event .addTableModifier(/minecraft:chests:.*/) .randomChance(0.5) .addLoot("minecraft:gunpowder") }) ``` ::: ## addTypeModifier 为给定的战利品类型添加新的类型修饰器。有效的战利品表类型有 `chest` 、 `block` 、 `entity` 、 `fishing` 、 `archaeology` 、 `gift` 、 `vault` 、 `shearing` 、 `piglin_barter` 语法: * `.addTypeModifier(type: LootType)` ,返回一个LootModifier ::: code-group ```js [支持添加一个] LootJS.modifiers(event => { event.addTypeModifier("chest") .randomChance(0.5) .addLoot("minecraft:gunpowder") }) ``` ```js [也支持添加多个] LootJS.modifiers(event => { event.addTypeModifier("block", "entity") .randomChance(0.5) .addLoot("minecraft:gunpowder") }) ``` ::: ## addEntityModifier 为与指定过滤器匹配的所有实体添加新的战利品修饰器。 语法: * `.addEntityModifier(filter: string | string[] | tag)` ,返回一个LootModifier ::: code-group ```js [支持直接使用过滤器] LootJS.modifiers(event => { event.addEntityModifier("minecraft:creeper") .randomChance(0.5) .addLoot("minecraft:gunpowder") }) ``` ```js [也支持使用过滤器数组] LootJS.modifiers(event => { event .addEntityModifier(["minecraft:cow", "minecraft:pig"]) .randomChance(0.5) .addLoot("minecraft:gold_nugget") }) ``` ```js [也支持使用标签过滤器] LootJS.modifiers(event => { event.addEntityModifier("#minecraft:skeletons") .randomChance(0.5) .addLoot("minecraft:stick") }) ``` ::: ## addBlockModifier 为与指定过滤器匹配的所有方块添加新的战利品修饰器。 语法: * `.addBlockModifier(filter: string | string[] | regex | tag)` ,返回一个LootModifier ::: code-group ```js [支持直接使用过滤器] LootJS.modifiers(event => { event.addBlockModifier("minecraft:iron_ore") .randomChance(0.5) .addLoot("minecraft:iron_nugget") }) ``` ```js [也支持使用过滤器数组] LootJS.modifiers(event => { event .addBlockModifier(["minecraft:gravel", "minecraft:dirt"]) .randomChance(0.5) .addLoot("minecraft:gold_nugget") }) ``` ```js [也支持使用标签] LootJS.modifiers(event => { event.addBlockModifier("#c:ores") .randomChance(0.5) .addLoot("minecraft:flint") }) ``` ::: --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.21/Addon/LootJs/LootTables/Event.md --- # Loot Table Event :::v-info 该篇教程源自于 LootJs 官方网站, 适用于 Minecraft 1.21 及更高版本 ::: 此事件允许您直接修改战利品表 该事件在其他 `Mod` 将其战利品更改后使用数据包添加到原版战利品列表后触发,允许你删除或更改它们 ## 战利品表 此篇教程是基于`LootJs`新版本添加的`LootJS.lootTables`事件进行的基础教程 建议搭配`ProbeJs`模组以及插件一起使用,辅助编写 ### 获取战利品表 ID `getLootTableIds` 返回所有可用战利品表 ID 的数组,可以进行筛选 使用正则表达式可以对战利品列表进行筛选 使用方法: * getLootTableIds() * getLootTableIds(filter: string | regex) 使用`console.log()`打印出来后,你将会在`logs/kubejs/server.log`日志文件里面看见所有通过数据包注册的战利品列表 ID ::: code-group ```js [无筛选] LootJS.lootTables((event) => { let ids = event.getLootTableIds(); console.log(ids); }); ``` ```js [使用正则表达式筛选] LootJS.lootTables((event) => { let ids = event.getLootTableIds(/.*chests\/.*/); console.log(ids); }); ``` ::: 使用正则表达式筛选后,只会打印出所有匹配`chests`这个战利品列表里面的战利品 ID 例如使用`getLootTableIds()`后日志输出为: ```js [minecraft:chests/bastion_treasure, minecraft:chests/village/village_weaponsmith, minecraft:chests/ruined_portal, minecraft:chests/trial_chambers/reward_ominous_rare, minecraft:chests/shipwreck_supply, minecraft:chests/stronghold_corridor, minecraft:chests/bastion_other, minecraft:chests/trial_chambers/reward_unique, minecraft:chests/village/village_fletcher, minecraft:chests/ancient_city_ice_box, minecraft:chests/village/village_cartographer, minecraft:chests/simple_dungeon, minecraft:chests/village/village_savanna_house, minecraft:chests/village/village_plains_house, minecraft:chests/village/village_taiga_house, minecraft:chests/nether_bridge, minecraft:chests/trial_chambers/intersection_barrel, minecraft:chests/trial_chambers/reward_ominous_unique, minecraft:chests/underwater_ruin_small, minecraft:chests/trial_chambers/reward_ominous_common, minecraft:chests/igloo_chest, minecraft:chests/village/village_armorer, minecraft:chests/village/village_mason, minecraft:chests/trial_chambers/intersection, minecraft:chests/trial_chambers/reward, minecraft:chests/buried_treasure, minecraft:chests/bastion_hoglin_stable, minecraft:chests/ancient_city, minecraft:chests/abandoned_mineshaft, minecraft:chests/trial_chambers/entrance, minecraft:chests/stronghold_crossing, minecraft:chests/village/village_fisher, minecraft:chests/village/village_butcher, minecraft:chests/shipwreck_treasure, minecraft:chests/village/village_toolsmith, minecraft:chests/village/village_temple, minecraft:chests/jungle_temple, minecraft:chests/desert_pyramid, minecraft:chests/trial_chambers/corridor, minecraft:chests/trial_chambers/reward_rare, minecraft:chests/village/village_desert_house, minecraft:chests/pillager_outpost, minecraft:chests/stronghold_library, minecraft:chests/shipwreck_map, minecraft:chests/underwater_ruin_big, minecraft:chests/trial_chambers/supply, minecraft:chests/village/village_shepherd, minecraft:chests/jungle_temple_dispenser, minecraft:chests/woodland_mansion, minecraft:chests/village/village_snowy_house, minecraft:chests/end_city_treasure, minecraft:chests/trial_chambers/reward_ominous, minecraft:chests/village/village_tannery, minecraft:chests/spawn_bonus_chest, minecraft:chests/bastion_bridge, minecraft:chests/trial_chambers/reward_common] [java.util.ArrayList] ``` ### 给定 ID 获取战利品表 `getLootTable` 按照所指定的 ID 返回一个战利品表,如果没有这个战利品表的 ID,则返回为`null` ```js LootJS.lootTables((event) => { let table = event.getLootTable("minecraft:chests/simple_dungeon"); console.log(table); }); ``` ### 战利品列表检测 `hasLootTable` 返回具有给定 ID 的战利品表是否存在 如果该战利品列表存在,则返回`true`,否则返回`false` 下面的示例代码中,检测`minecraft:chests/simple_dungeon`这个战利品列表是否存在,最后可以看到`console.log`输出为`true` ```js LootJS.lootTables((event) => { let exists = event.hasLootTable("minecraft:chests/simple_dungeon"); console.log(exists); }); ``` ### 清除战利品列表 `clearLootTables` 清除所有与给定过滤器匹配的战利品表 可以使用选择器来过滤战利品表 语法: * `clearLootTables(filter: string | regex)` 下面的示例中使用的是正则表达式来进行过滤,清除有关箱子里面所有的战利品 你也可以直接使用战利品表的 ID 来进行指定清除 ```js LootJS.lootTables((event) => { event.clearLootTables(/.*chests.*/); }); ``` 下面的示例代码中,指定了史莱姆的战利品表 ID,清除了史莱姆的所有战利品 ```js LootJS.lootTables((event) => { event.clearLootTables("minecraft:entities/slime"); }); ``` 提示:对于战利品表 ID 的获取,游戏内可以使用`/loot`指令来进行获取 ### 创建战利品列表 `create` 使用这个语法可以创建一个新的战利品表并应用 语法: * create(id: string, type?: LootType) 如果`LootType`未提供,那么默认为`LootType.CHEST` 示例代码: ```js LootJS.lootTables((event) => { event.create("lootjs:table1", LootType.ENTITY).createPool((pool) => { // 修改你的战利品池 }); // 使用默认的LootType(CHEST) event.create("lootjs:table2").createPool((pool) => { // 修改你的战利品池 }); }); ``` ### 修改战利品列表 `modifyLootTables` 该方法允许你一次修改所有的战利品表,或者进行筛选,返回后进行对应的战利品表修改 或者,您可以按战利品表类型进行筛选。战利品表类型在表 `JSON` 中的键下定义。例如,下面 `JSON` 中的 `loot` 表在第 2 行中使用 `type` 作为其类型: `type` `minecraft:blocks/bricks` `block` ```json { "type": "minecraft:block", "pools": [ { // ... } ], "random_sequence": "minecraft:blocks/bricks" } ``` 语法: * `modifyLootTables(filter: LootTableFilter | LootTableFilter[])` 示例代码: ::: code-group ```js [单项选择器] LootJS.lootTables((event) => { event.modifyLootTables(/.*chests\/.*/).createPool((pool) => { // 修改你的战利品池 }); event.modifyLootTables(LootType.CHEST).createPool((pool) => { // 修改你的战利品池 }); }); ``` ```js [多选择器] LootJS.lootTables((event) => { event .modifyLootTables(LootType.CHEST, "minecraft:gameplay/fishing") .createPool((pool) => { // 修改你的战利品池 }); event .modifyLootTables(LootType.CHEST, LootType.ENTITY) .createPool((pool) => { // 修改你的战利品池 }); }); ``` ::: 在`modifyLootTables`中,可以使用多个过滤器来进行战利品表的修改。同样的你也可使用正则表达式来进行过滤选择 同时,选择器支持的类型有: * `字符串` * `正则表达式` * `LootTableFilter` * `LootType` ```ts enum LootType { CHEST, BLOCK, ENTITY, EQUIPMENT, FISHING, ARCHAEOLOGY, GIFT, VAULT, SHEARING, PIGLIN_BARTER, } ``` ## 方块战利品列表 ### 获取战利品表 `getBlockTable` 返回给定方块的战利品表,如果没有找到则返回`null`。 使用方法: * getBlockTable(block: string | Block) ```js LootJS.lootTables(event => { let table = event.getBlockTable("minecraft:diamond_ore") }) ``` ### 修改方块战利品表 `modifyBlockTables` 该方法允许你一次性修改所有匹配给定方块的战利品表。 ::: v-info 在 Minecraft 1.21.1 后,战利品表的加载顺序在标签之前,因此`modifyBlockTables`方法不支持与标签一起使用。如果想使用标签,请考虑使用`loot modifier event`。 ::: 使用方法: ::: code-group ```js [单过滤器] LootJS.lootTables(event => { event.modifyBlockTables("minecraft:diamond_ore").createPool(pool => { // edit the pool }) }) ``` ```js [多过滤器] LootJS.lootTables(event => { event.modifyBlockTables("minecraft:diamond_ore", "minecraft:emerald_ore").createPool(pool => { // edit the pool }) }) ``` ::: ## 实体战利品表 ### 获取实体战利品表 `getEntityTable` 返回给定实体的战利品表,如果没有则返回`null` 使用方法: * getEntityTable(entity: string | EntityType) ```js LootJS.lootTables(event => { let table = event.getEntityTable("minecraft:sheep") }) ``` ### 修改实体战利品表 `modifyEntityTables` 该方法允许你一次性修改所有匹配给定实体的战利品表。 ::: v-info 在 Minecraft 1.21.1 后,战利品表的加载顺序在标签之前,因此`modifyBlockTables`方法不支持与标签一起使用。如果想使用标签,请考虑使用`loot modifier event`。 ::: 使用方法: * modifyEntityTables(filter: string | string\[] | EntityType | EntityType\[]) ::: code-group ```js [单过滤器] LootJS.lootTables(event => { event.modifyEntityTables("minecraft:sheep").createPool(pool => { // edit the pool }) }) ``` ```js [多过滤器] LootJS.lootTables(event => { event.modifyEntityTables("minecraft:sheep", "minecraft:pig").createPool(pool => { // edit the pool }) }) ``` ::: --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAdvanced/EventExamples/README.md --- # Event Examples This section shares small examples built with events, and briefly marks parameters you can access from each event. --- --- url: 'https://docs.variedmc.cc/en/info/KubeJS/Q&A/event_interaction.md' --- # 事件与交互 该页面用于讨论KubeJS的事件与交互问题。 --- --- url: 'https://docs.variedmc.cc/zh/info/KubeJS/Q&A/event_interaction.md' --- # 事件与交互 该页面用于讨论KubeJS的事件与交互问题。 --- --- url: 'https://docs.variedmc.cc/en/info/KubeJS/code/eventHandler.md' --- # 事件处理 该页面用于讨论 KubeJS 的事件处理相关问题。 --- --- url: 'https://docs.variedmc.cc/zh/info/KubeJS/code/eventHandler.md' --- # 事件处理 该页面用于讨论 KubeJS 的事件处理相关问题。 --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAdvanced/Event.md --- # Events ## Quick Q\&A ### What is an event? You reading this document is an event. Leaving a like/comment/share on Gu-meng's video is also an event. ### What can you do once you capture an event? What an event does depends on the developer. For example, "reading the document" triggers an event, and your event logic could then decide to do something next. In code terms: first, an action happens and triggers an event; then you write logic inside that event callback to define what should happen. ### What are event examples in-game? * If you right-click TNT with flint and steel, TNT explodes after a delay. This is a [block right-click event](./EventExamples/BlockRightClickedEvent.md): right-clicking TNT triggers the block event, checks whether you are holding flint and steel, and then triggers TNT ignition. * If an entity is hit by a shulker projectile and starts floating, that is an [entity hurt event](): check the damage source and apply Levitation if it was caused by a shulker. * If a player eats a golden apple and receives potion effects, that is an [item consumed event](): after eating finishes, apply the configured effects. ## Events Directly Available in KubeJS There are many events in gameplay. Choosing the right one at the right point lets you add richer mechanics, so your modpack feels more than "just recipe edits." [All Events and Usage Examples](../KubeJSBasic/AllEvent) --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSAddon/MoreJS/AllEvent.md --- # Events Provided by MoreJS This chapter briefly lists all events provided by MoreJS and links to usage examples. ## All Events | Folder | Event | Purpose | Example | | :-----------------: | :------------------------------------------: | :-------------------: | :---: | | `server_scripts` | `MoreJSEvents.enchantmentTableChanged` | Enchantment table update event | ~ | | `server_scripts` | `MoreJSEvents.enchantmentTableEnchant` | Enchantment table enchant event | ~ | | `server_scripts` | `MoreJSEvents.enchantmentTableIsEnchantable` | ? | ~ | | **client\_scripts** | `MoreJSEvents.enchantmentTableTooltip` | Enchantment tooltip event | ~ | | `server_scripts` | `MoreJSEvents.playerXpChange` | Player XP change event | ~ | | `server_scripts` | `MoreJSEvents.playerStartTrading` | Player trading-start event | ~ | | `server_scripts` | `MoreJSEvents.piglinPlayerBehavior` | Piglin behavior toward player? | ~ | | `server_scripts` | `MoreJSEvents.villagerTrades` | Villager trade event | ~ | | `server_scripts` | `MoreJSEvents.updateVillagerOffers` | Villager offer update event | ~ | | `server_scripts` | `MoreJSEvents.updateAbstractVillagerOffers` | ? | ~ | | `server_scripts` | `MoreJSEvents.wandererTrades` | Wandering trader trade event | ~ | | `server_scripts` | `MoreJSEvents.updateWandererOffers` | Wandering trader offer update event? | ~ | | `server_scripts` | `MoreJSEvents.teleport` | Teleport event | ~ | | `server_scripts` | `MoreJSEvents.structureLoad` | Structure load event? | ~ | | `server_scripts` | `MoreJSEvents.filterAvailableEnchantments` | Filter available enchantments event? | ~ | | `server_scripts` | `MoreJSEvents.filterEnchantedBookTrade` | Filter enchanted book trades event? | ~ | | `server_scripts` | `MoreJSEvents.structureAfterPlace` | Post-structure-placement event? | ~ | | **startup\_scripts** | `MoreJSEvents.registerPotionBrewing` | Register brewing recipes event | ~ | --- --- url: >- https://docs.variedmc.cc/zh/modpack/kubejs/1.20.1/Introduction/Event/ServerScript/Example/sidebarIndex.md --- This file stores sidebar metadata for this directory. --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSBasic/Customs/BlockType/README.md --- *** ## description: Examples of adding different custom block types # Examples for Different Block Types --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSBasic/Customs/ItemType/README.md --- *** ## description: Here are examples of adding different item types # Examples of Different Item Types --- --- url: >- https://docs.variedmc.cc/en/modpack/kubejs/1.20.1/KubeJSCourse/KubeJSProjects/Meng/SmallProject/RecipesJsonInFile.md --- # Export All Recipe JSONs to Files Main topics in this chapter: iterating over all recipes and `JsonIO`. All code in this chapter belongs in `server_scripts`. Mods and versions used: 1. jei-1.20.1-forge-15.3.0.4 2. rhino-forge-2001.2.2-build.18 3. architectury-9.2.14-forge 4. kubejs-forge-2001.6.5-build.14 5. probejs-6.0.1-forge ## Full Code Create a `recipes` folder in the game directory path ahead of time, otherwise writing files will fail with a missing-path error. ```js ServerEvents.recipes(event=>{ event.forEachRecipe({},recipe=>{ JsonIO.write("./recipes/" + String(recipe.getId()).replace(/:|\//g, '_') + ".json",recipe.json) }) }) ``` --- --- url: 'https://docs.variedmc.cc/en/doc/extensionArchitecture.md' description: >- Where configuration, runtime logic, components, plugins, and styles should live in CrychicDoc. --- # Extension Architecture Once a change starts affecting shared behavior rather than a single page, code placement needs to be decided first. Configuration, runtime logic, components, plugins, and documentation examples each have a fixed home in this repository, and this page documents that split. ## Layering Rules Use this split consistently: * `api` owns field definitions, default values, and shaping logic * `runtime` owns shared state and lifecycle * `theme/components` owns rendering * `config` owns project defaults and registration wiring * `docs/**` owns examples, user guides, and developer-facing documentation ## Standard Pages For a normal documentation page: 1. Create the markdown file under the same relative path in both locale trees. 2. Start with simple page frontmatter: ```yaml --- title: Example Page layout: doc description: What this page covers. --- ``` 3. Add nav, docs-hub, or home-page entry points only if the page must be discoverable from those surfaces. 4. If the page introduces a new field convention or workflow, update the related developer doc in the same change. ## Home and Hero Pages For a landing page or developer hub page: 1. Use `layout: home`. 2. Define the hero fields before adding view-specific styling: ```yaml --- layout: home hero: name: Developer Docs text: Extend CrychicDoc Safely tagline: Runtime, frontmatter, plugins, and content registration actions: - theme: brand text: Documentation Writing Standards link: /en/doc/Catalogue --- ``` 3. Add `features` or `featureCards` only after the hero structure is stable. 4. If the page becomes a primary entry point, update `docs/en/index.md`, `docs/zh/index.md`, and the locale nav files in the same change. ## Component Extension When adding a new component: 1. Place it under `.vitepress/theme/components//`. 2. Export reusable ones through `.vitepress/utils/vitepress/componentRegistry/**`. 3. Register markdown-facing ones in `.vitepress/utils/vitepress/components.ts`. 4. Add locale resources if the component renders UI text. 5. Keep component IDs and i18n mapping synchronized. ## Content Component Integration For content-style components that appear inside markdown pages or document chrome: 1. Create the component in `.vitepress/theme/components/content/`. 2. Export it from `.vitepress/theme/components/content/index.ts` when that barrel is used. 3. Export it from `.vitepress/utils/vitepress/componentRegistry/contentRegistry.ts` when theme internals should import it through the registry layer. 4. Register it in `.vitepress/utils/vitepress/components.ts` when markdown pages should reference it directly by tag name. 5. If it uses `useSafeI18n`, add locale JSON files and keep `component-id-mapping.json` aligned. 6. If it is emitted by a markdown plugin, make sure the plugin outputs a registered tag. 7. Add at least one real markdown example page so the component is validated by the build. Use this rule: * `componentRegistry/contentRegistry.ts` Internal reuse inside the theme. * `components.ts` Public markdown/global registration. ## Function and Runtime Extension When adding a composable, service, controller, or helper: 1. Put pure normalization in `.vitepress/utils/vitepress/api/**`. 2. Put shared stateful behavior in `.vitepress/utils/vitepress/runtime/**`. 3. Export public entry points from the nearest `index.ts` barrel. 4. Prefer one shared runtime over many component-local observers. Strong examples already in the repo: * Theme sync: `.vitepress/utils/vitepress/runtime/theme/**` * Hero nav adaptation: `.vitepress/utils/vitepress/runtime/hero/navAdaptiveState.ts` * Frontmatter field shaping: `.vitepress/utils/vitepress/api/frontmatter/hero/HeroFrontmatterApi.ts` ## Configuration Extension Primary files: * `.vitepress/config/project-config.ts` * `.vitepress/config/lang/**` * `.vitepress/config/markdown-plugins.ts` * `.vitepress/config/shaders/**` Checklist: 1. Define the field or registry entry. 2. Normalize it in the API layer. 3. Consume only normalized values in runtime or views. 4. Add docs examples immediately. 5. If generated metadata depends on the change, run `yarn docs:build` and verify the final output. ## Style Extension Follow the import layering in `.vitepress/theme/styles/index.css`. * Scoped `