diff --git a/docs/advanced/_category_.json b/docs/advanced/_category_.json index 0d103c926..2161114e5 100644 --- a/docs/advanced/_category_.json +++ b/docs/advanced/_category_.json @@ -1,4 +1,4 @@ { - "label": "Advanced Topics", - "position": 12 + "label": "Advanced Topics", + "position": 12 } \ No newline at end of file diff --git a/docs/advanced/accesstransformers.md b/docs/advanced/accesstransformers.md index 004952069..838b759de 100644 --- a/docs/advanced/accesstransformers.md +++ b/docs/advanced/accesstransformers.md @@ -14,9 +14,9 @@ Access Transformers need to be declared in `build.gradle`. AT files can be speci // In build.gradle: // This block is where your mappings version is also specified minecraft { - accessTransformers { - file('src/main/resources/META-INF/accesstransformer.cfg') - } + accessTransformers { + file('src/main/resources/META-INF/accesstransformer.cfg') + } } ``` @@ -35,10 +35,10 @@ Additionally, multiple AT files can be specified and will be applied in order. T ```groovy // In build.gradle: minecraft { - accessTransformers { - file('src/main/resources/accesstransformer_main.cfg') - file('src/additions/resources/accesstransformer_additions.cfg') - } + accessTransformers { + file('src/main/resources/accesstransformer_main.cfg') + file('src/additions/resources/accesstransformer_additions.cfg') + } } ``` @@ -135,8 +135,8 @@ public net.minecraft.util.Crypt$ByteArrayToKeyFunction protected-f net.minecraft.server.MinecraftServer random # Makes public the 'makeExecutor' method in Util, -# accepting a String and returns an ExecutorService -public net.minecraft.Util makeExecutor(Ljava/lang/String;)Ljava/util/concurrent/ExecutorService; +# accepting a String and returns a TracingExecutor +public net.minecraft.Util makeExecutor(Ljava/lang/String;)Lnet/minecraft/TracingExecutor; # Makes public the 'leastMostToIntArray' method in UUIDUtil, # accepting two longs and returning an int[] diff --git a/docs/advanced/extensibleenums.md b/docs/advanced/extensibleenums.md index e5737c38a..d35323040 100644 --- a/docs/advanced/extensibleenums.md +++ b/docs/advanced/extensibleenums.md @@ -88,9 +88,9 @@ The parameters can be specified in three ways with limitations depending on the - Inline in the JSON file as an array of constants (only allowed for primitive values, Strings and for passing null to any reference type) - As a reference to a field of type `EnumProxy` in a class from the mod (see `EnumProxy` example above) - - The first parameter specifies the target enum and the subsequent parameters are the ones to be passed to the enum constructor + - The first parameter specifies the target enum and the subsequent parameters are the ones to be passed to the enum constructor - As a reference to a method returning `Object`, where the return value is the parameter value to use. The method must have exactly two parameters of type `int` (index of the parameter) and `Class` (expected type of the parameter) - - The `Class` object should be used to cast (`Class#cast()`) the return value in order to keep `ClassCastException`s in mod code. + - The `Class` object should be used to cast (`Class#cast()`) the return value in order to keep `ClassCastException`s in mod code. :::warning The fields and/or methods used as sources for parameter values should be in a separate class to avoid unintentionally loading mod classes too early. @@ -117,7 +117,6 @@ Further action is required depending on specific details about the enum: - If the enum has an int ID parameter which should match the entry's ordinal, then the enum should be annotated with `@NumberedEnum` with the ID's parameter index as the annotation's value if it's not the first parameter - If the enum has a String name parameter which is used for serialization and should therefore be namespaced, then the enum should be annotated with `@NamedEnum` with the name's parameter index as the annotation's value if it's not the first parameter - If the enum is sent over the network, then it should be annotated with `@NetworkedEnum` with the annotation's parameter specifying in which direction the values may be sent (clientbound, serverbound or bidirectional) - - Warning: networked enums will require additional steps once network checks for enums are implemented in NeoForge - If the enum has constructors which are not usable by mods (i.e. because they require registry objects on an enum that may be initialized before modded registration runs), then they should be annotated with `@ReservedConstructor` :::note diff --git a/docs/blockentities/_category_.json b/docs/blockentities/_category_.json index 6aaa487b0..3b6467c06 100644 --- a/docs/blockentities/_category_.json +++ b/docs/blockentities/_category_.json @@ -1,4 +1,4 @@ { - "label": "Block Entities", - "position": 6 + "label": "Block Entities", + "position": 6 } \ No newline at end of file diff --git a/docs/blockentities/ber.md b/docs/blockentities/ber.md index 5ef66f2c2..ef0d8262e 100644 --- a/docs/blockentities/ber.md +++ b/docs/blockentities/ber.md @@ -115,6 +115,16 @@ public static void registerClientExtensions(RegisterClientExtensionsEvent event) `IClientItemExtensions` are generally expected to be treated as singletons. Do not construct them outside `RegisterClientExtensionsEvent`! ::: +Finally, the item has to know that it should use the BEWLR for its rendering. This is done by having the final [`BakedModel`][bakedmodel] return true for `#isCustomRenderer`. The easiest way to do this is to have the [item model JSON][model] with a `parent` of `minecraft:builtin/entity`: + +```json5 +// In some item model file assets//models/item/.json +{ + "parent": "minecraft: builtin/entity", + // ... +} +``` + [block]: ../blocks/index.md [blockentity]: index.md [event]: ../concepts/events.md#registering-an-event-handler diff --git a/docs/blockentities/container.md b/docs/blockentities/container.md index 014119d82..4b07dda74 100644 --- a/docs/blockentities/container.md +++ b/docs/blockentities/container.md @@ -259,7 +259,7 @@ public class MyBackpackContainer extends SimpleContainer { And voilà, you have created an item-backed container! Call `new MyBackpackContainer(stack)` to create a container for a menu or other use case. :::warning -Be aware that `Menu`s that directly interface with `Container`s must `#copy()` their `ItemStack`s when modifying them, as otherwise the immutability contract on data components is broken. To do this, NeoForge provides the `StackCopySlot` class for you. +Be aware that menus that directly interface with `Container`s must `#copy()` their `ItemStack`s when modifying them, as otherwise the immutability contract on data components is broken. To do this, NeoForge provides the `StackCopySlot` class for you. ::: ## `Container`s on `Entity`s @@ -306,7 +306,7 @@ When iterating over the inventory contents, it is recommended to iterate over `i [block]: ../blocks/index.md [blockentity]: index.md [component]: ../resources/client/i18n.md#components -[datacomponent]: ../items/datacomponents.mdx +[datacomponent]: ../items/datacomponents.md [item]: ../items/index.md [itemstack]: ../items/index.md#itemstacks [menu]: ../gui/menus.md diff --git a/docs/blockentities/index.md b/docs/blockentities/index.md index 5e1ff5100..d8c0c2ca2 100644 --- a/docs/blockentities/index.md +++ b/docs/blockentities/index.md @@ -22,7 +22,7 @@ public class MyBlockEntity extends BlockEntity { As you may have noticed, we pass an undefined variable `type` to the super constructor. Let's leave that undefined variable there for a moment and instead move to registration. -Registration happens in a similar fashion to entities. We create an instance of the associated singleton class `BlockEntityType` and register it to the block entity type registry, like so: +[Registration][registration] happens in a similar fashion to entities. We create an instance of the associated singleton class `BlockEntityType` and register it to the block entity type registry, like so: ```java public static final DeferredRegister> BLOCK_ENTITY_TYPES = @@ -30,19 +30,21 @@ public static final DeferredRegister> BLOCK_ENTITY_TYPES = public static final Supplier> MY_BLOCK_ENTITY = BLOCK_ENTITY_TYPES.register( "my_block_entity", - // The block entity type, created using a builder. - () -> BlockEntityType.Builder.of( + // The block entity type. + () -> new BlockEntityType<>( // The supplier to use for constructing the block entity instances. MyBlockEntity::new, // A vararg of blocks that can have this block entity. // This assumes the existence of the referenced blocks as DeferredBlocks. MyBlocks.MY_BLOCK_1.get(), MyBlocks.MY_BLOCK_2.get() ) - // Build using null; vanilla does some datafixer shenanigans with the parameter that we don't need. - .build(null) ); ``` +:::note +Remember that the `DeferredRegister` must be registered to the [mod event bus][modbus]! +::: + Now that we have our block entity type, we can use it in place of the `type` variable we left earlier: ```java @@ -54,7 +56,7 @@ public class MyBlockEntity extends BlockEntity { ``` :::info -The reason for this rather confusing setup process is that `BlockEntityType.Builder#of` expects a `BlockEntityType.BlockEntitySupplier`, which is basically a `BiFunction`. As such, having a constructor we can directly reference using `::new` is highly beneficial. However, we also need to provide the constructed block entity type to the default and only constructor of `BlockEntity`, so we need to pass references around a bit. +The reason for this rather confusing setup process is that `BlockEntityType` expects a `BlockEntityType.BlockEntitySupplier`, which is basically a `BiFunction`. As such, having a constructor we can directly reference using `::new` is highly beneficial. However, we also need to provide the constructed block entity type to the default and only constructor of `BlockEntity`, so we need to pass references around a bit. ::: Finally, we need to modify the block class associated with the block entity. This means that we will not be able to attach block entities to simple instances of `Block`, instead, we need a subclass: @@ -75,7 +77,7 @@ public class MyEntityBlock extends Block implements EntityBlock { } ``` -And then, you of course need to use this class as the type in your block registration: +And then, you of course need to use this class as the type in your [block registration][blockreg]: ```java public static final DeferredBlock MY_BLOCK_1 = @@ -232,8 +234,10 @@ It is important that you do safety checks, as the `BlockEntity` might already be ::: [block]: ../blocks/index.md +[blockreg]: ../blocks/index.md#basic-blocks [blockstate]: ../blocks/states.md [dataattachments]: ../datastorage/attachments.md +[modbus]: ../concepts/events.md#event-buses [nbt]: ../datastorage/nbt.md [networking]: ../networking/index.md [registration]: ../concepts/registries.md#methods-for-registering diff --git a/docs/blocks/_category_.json b/docs/blocks/_category_.json index 2ba9562d4..610778fed 100644 --- a/docs/blocks/_category_.json +++ b/docs/blocks/_category_.json @@ -1,4 +1,4 @@ { - "label": "Blocks", - "position": 3 + "label": "Blocks", + "position": 3 } \ No newline at end of file diff --git a/docs/blocks/index.md b/docs/blocks/index.md index ea54c393d..2d5353bb9 100644 --- a/docs/blocks/index.md +++ b/docs/blocks/index.md @@ -18,7 +18,7 @@ So now, let's register our blocks: ```java //BLOCKS is a DeferredRegister.Blocks -public static final DeferredBlock MY_BLOCK = BLOCKS.register("my_block", () -> new Block(...)); +public static final DeferredBlock MY_BLOCK = BLOCKS.register("my_block", registryName -> new Block(...)); ``` After registering the block, all references to the new `my_block` should use this constant. For example, if you want to check if the block at a given position is `my_block`, the code for that would look something like this: @@ -51,6 +51,8 @@ public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBloc For simple blocks which need no special functionality (think cobblestone, wooden planks, etc.), the `Block` class can be used directly. To do so, during registration, instantiate `Block` with a `BlockBehaviour.Properties` parameter. This `BlockBehaviour.Properties` parameter can be created using `BlockBehaviour.Properties#of`, and it can be customized by calling its methods. The most important methods for this are: +- `setId` - Sets the resource key of the block. + - This **must** be set on every block; otherwise, an exception will be thrown. - `destroyTime` - Determines the time the block needs to be destroyed. - Stone has a destroy time of 1.5, dirt has 0.5, obsidian has 50, and bedrock has -1 (unbreakable). - `explosionResistance` - Determines the explosion resistance of the block. @@ -68,8 +70,9 @@ So for example, a simple implementation would look something like this: //BLOCKS is a DeferredRegister.Blocks public static final DeferredBlock MY_BETTER_BLOCK = BLOCKS.register( "my_better_block", - () -> new Block(BlockBehaviour.Properties.of() + registryName -> new Block(BlockBehaviour.Properties.of() //highlight-start + .setId(ResourceKey.create(Registries.BLOCK, registryName)) .destroyTime(2.0f) .explosionResistance(10.0f) .sound(SoundType.GRAVEL) @@ -108,14 +111,14 @@ public class SimpleBlock extends Block { @Override public MapCodec codec() { - return SIMPLE_CODEC.value(); + return SIMPLE_CODEC.get(); } } // In some registration class public static final DeferredRegister> REGISTRAR = DeferredRegister.create(BuiltInRegistries.BLOCK_TYPE, "yourmodid"); -public static final DeferredHolder, MapCodec> SIMPLE_CODEC = REGISTRAR.register( +public static final Supplier> SIMPLE_CODEC = REGISTRAR.register( "simple", () -> simpleCodec(SimpleBlock::new) ); @@ -133,7 +136,7 @@ public class ComplexBlock extends Block { @Override public MapCodec codec() { - return COMPLEX_CODEC.value(); + return COMPLEX_CODEC.get(); } public int getValue() { @@ -144,14 +147,14 @@ public class ComplexBlock extends Block { // In some registration class public static final DeferredRegister> REGISTRAR = DeferredRegister.create(BuiltInRegistries.BLOCK_TYPE, "yourmodid"); -public static final DeferredHolder, MapCodec> COMPLEX_CODEC = REGISTRAR.register( +public static final Supplier> COMPLEX_CODEC = REGISTRAR.register( "simple", () -> RecordCodecBuilder.mapCodec(instance -> instance.group( Codec.INT.fieldOf("value").forGetter(ComplexBlock::getValue), BlockBehaviour.propertiesCodec() // represents the BlockBehavior.Properties parameter ).apply(instance, ComplexBlock::new) - ); + ) ); ``` @@ -166,6 +169,16 @@ We already discussed how to create a `DeferredRegister.Blocks` [above], as well ```java public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks("yourmodid"); +public static final DeferredBlock EXAMPLE_BLOCK = BLOCKS.register( + "example_block", registryName -> new Block( + BlockBehaviour.Properties.of() + // The ID must be set on the block + .setId(ResourceKey.create(Registries.BLOCK, registryName)) + ) +); + +// Same as above, except that the block properties are constructed eagerly. +// setId is also called internally on the properties object. public static final DeferredBlock EXAMPLE_BLOCK = BLOCKS.registerBlock( "example_block", Block::new, // The factory that the properties will be passed into. @@ -173,8 +186,6 @@ public static final DeferredBlock EXAMPLE_BLOCK = BLOCKS.registerBlock( ); ``` -Internally, this will simply call `BLOCKS.register("example_block", () -> new Block(BlockBehaviour.Properties.of()))` by applying the properties parameter to the provided block factory (which is commonly the constructor). - If you want to use `Block::new`, you can leave out the factory entirely: ```java diff --git a/docs/blocks/states.md b/docs/blocks/states.md index 906bf5895..9dabb504a 100644 --- a/docs/blocks/states.md +++ b/docs/blocks/states.md @@ -53,10 +53,6 @@ To implement a blockstate property, in your block class, create or reference a ` - Implements `Property`. Defines a property that can take on the values of an Enum class. - Created by calling `EnumProperty#create(String propertyName, Class enumClass)`. - It is also possible to use only a subset of the Enum values (e.g. 4 out of 16 `DyeColor`s), see the overloads of `EnumProperty#create`. -- `DirectionProperty` - - Extends `EnumProperty`. Defines a property that can take on a `Direction`. - - Created by calling `DirectionProperty#create(String propertyName)`. - - Several convenience predicates are provided. For example, to get a property that represents the cardinal directions, call `DirectionProperty.create("", Direction.Plane.HORIZONTAL)`; to get the X directions, `DirectionProperty.create("", Direction.Axis.X)`. The class `BlockStateProperties` contains shared vanilla properties which should be used or referenced whenever possible, in place of creating your own properties. @@ -72,7 +68,7 @@ To further illustrate this, this is what the relevant bits of the `EndPortalFram public class EndPortalFrameBlock extends Block { // Note: It is possible to directly use the values in BlockStateProperties instead of referencing them here again. // However, for the sake of simplicity and readability, it is recommended to add constants like this. - public static final DirectionProperty FACING = BlockStateProperties.FACING; + public static final EnumProperty FACING = BlockStateProperties.FACING; public static final BooleanProperty EYE = BlockStateProperties.EYE; public EndPortalFrameBlock(BlockBehaviour.Properties pProperties) { @@ -107,7 +103,7 @@ To go from `Block` to `BlockState`, call `Block#defaultBlockState()`. The defaul You can get the value of a property by calling `BlockState#getValue(Property)`, passing it the property you want to get the value of. Reusing our end portal frame example, this would look something like this: ```java -// EndPortalFrameBlock.FACING is a DirectionProperty and thus can be used to obtain a Direction from the BlockState +// EndPortalFrameBlock.FACING is an EnumPropery and thus can be used to obtain a Direction from the BlockState Direction direction = endPortalFrameBlockState.getValue(EndPortalFrameBlock.FACING); ``` @@ -138,9 +134,10 @@ To help setting the update flags correctly, there are a number of `int` constant - `Block.UPDATE_KNOWN_SHAPE` stops neighbor update recursion. - `Block.UPDATE_SUPPRESS_DROPS` disables block drops for the old block at that position. - `Block.UPDATE_MOVE_BY_PISTON` is only used by piston code to signal that the block was moved by a piston. This is mainly responsible for delaying light engine updates. +- `Block.UPDATE_SKIP_SHAPE_UPDATE_ON_WIRE` is used by the `ExperimentalRedstoneWireEvaluator` to signal if the shape should be skipped. This is set only when the power strength is changed from not a placement or that the original source on the signal is not the current wire. - `Block.UPDATE_ALL` is an alias for `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS`. - `Block.UPDATE_ALL_IMMEDIATE` is an alias for `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS | Block.UPDATE_IMMEDIATE`. -- `Block.NONE` is an alias for `Block.UPDATE_INVISIBLE`. +- `Block.UPDATE_NONE` is an alias for `Block.UPDATE_INVISIBLE`. There is also a convenience method `Level#setBlockAndUpdate(BlockPos pos, BlockState state)` that calls `setBlock(pos, state, Block.UPDATE_ALL)` internally. diff --git a/docs/concepts/_category_.json b/docs/concepts/_category_.json index 75b53e1d3..149a5089d 100644 --- a/docs/concepts/_category_.json +++ b/docs/concepts/_category_.json @@ -1,4 +1,4 @@ { - "label": "Concepts", - "position": 2 + "label": "Concepts", + "position": 2 } \ No newline at end of file diff --git a/docs/concepts/events.md b/docs/concepts/events.md index e60a235a4..dbc86029c 100644 --- a/docs/concepts/events.md +++ b/docs/concepts/events.md @@ -23,8 +23,8 @@ public class YourMod { } // Heals an entity by half a heart every time they jump. - private static void onLivingJump(LivingJumpEvent event) { - Entity entity = event.getEntity(); + private static void onLivingJump(LivingEvent.LivingJumpEvent event) { + LivingEntity entity = event.getEntity(); // Only heal on the server side if (!entity.level().isClientSide()) { entity.heal(1); @@ -40,8 +40,8 @@ Alternatively, event handlers can be annotation-driven by creating an event hand ```java public class EventHandler { @SubscribeEvent - public void onLivingJump(LivingJumpEvent event) { - Entity entity = event.getEntity(); + public void onLivingJump(LivingEvent.LivingJumpEvent event) { + LivingEntity entity = event.getEntity(); if (!entity.level().isClientSide()) { entity.heal(1); } @@ -61,8 +61,8 @@ You can also do it statically. Simply make all event handlers static, and instea ```java public class EventHandler { @SubscribeEvent - public static void onLivingJump(LivingJumpEvent event) { - Entity entity = event.getEntity(); + public static void onLivingJump(LivingEvent.LivingJumpEvent event) { + LivingEntity entity = event.getEntity(); if (!entity.level().isClientSide()) { entity.heal(1); } @@ -87,8 +87,8 @@ While not required, it is highly recommended to specify the `modid` parameter in @EventBusSubscriber(modid = "yourmodid") public class EventHandler { @SubscribeEvent - public static void onLivingJump(LivingJumpEvent event) { - Entity entity = event.getEntity(); + public static void onLivingJump(LivingEvent.LivingJumpEvent event) { + LivingEntity entity = event.getEntity(); if (!entity.level().isClientSide()) { entity.heal(1); } @@ -112,7 +112,7 @@ If you listen to an `abstract` event, your game will crash, as this is never wha ### Cancellable Events -Some events implement the `ICancellableEvent` interface. These events can be cancelled using `#setCanceled(boolean canceled)`, and the cancellation status can be checked using `#isCanceled()`. If an event is cancelled, other event handlers for this event will not run, and some kind of behavior that is associated with "cancelling" is enabled. For example, cancelling `LivingJumpEvent` will prevent the jump. +Some events implement the `ICancellableEvent` interface. These events can be cancelled using `#setCanceled(boolean canceled)`, and the cancellation status can be checked using `#isCanceled()`. If an event is cancelled, other event handlers for this event will not run, and some kind of behavior that is associated with "cancelling" is enabled. For example, cancelling `LivingChangeTargetEvent` will prevent the entity's target entity from changing. Event handlers can opt to explicitly receive cancelled events. This is done by setting the `receiveCanceled` boolean parameter in `IEventBus#addListener` (or `@SubscribeEvent`, depending on your way of attaching the event handlers) to true. @@ -126,7 +126,7 @@ An event with three potential return states has some `set*` method to set the de // In some class where the listeners are subscribed to the game event bus @SubscribeEvent -public void renderNameTag(RenderNameTagEvent event) { +public void renderNameTag(RenderNameTagEvent.CanRender event) { // Uses TriState to set the return state event.setCanRender(TriState.FALSE); } @@ -185,7 +185,7 @@ Then, during `InterModProcessEvent`, you can use `InterModComms#getMessages` to Next to the lifecycle events, there are a few miscellaneous events that are fired on the mod event bus, mostly for legacy reasons. These are generally events where you can register, set up, or initialize various things. Most of these events are not ran in parallel in contrast to the lifecycle events. A few examples: -- `RegisterColorHandlersEvent` +- `RegisterColorHandlersEvent.Block`, `.Item`, `.ColorResolvers` - `ModelEvent.BakingCompleted` - `TextureAtlasStitchedEvent` diff --git a/docs/concepts/registries.md b/docs/concepts/registries.md index 7a1e68956..34d0e82dc 100644 --- a/docs/concepts/registries.md +++ b/docs/concepts/registries.md @@ -38,27 +38,49 @@ public static final DeferredRegister BLOCKS = DeferredRegister.create( ); ``` -We can then add our registry entries as static final fields (see [the article on Blocks][block] for what parameters to add in `new Block()`): +We can then add our registry entries as static final fields using one of the following methods (see [the article on Blocks][block] for what parameters to add in `new Block()`): ```java -public static final DeferredHolder EXAMPLE_BLOCK = BLOCKS.register( - "example_block" // Our registry name. - () -> new Block(...) // A supplier of the object we want to register. +public static final DeferredHolder EXAMPLE_BLOCK_1 = BLOCKS.register( + // Our registry name. + "example_block" + // A supplier of the object we want to register. + () -> new Block(...) +); + +public static final DeferredHolder EXAMPLE_BLOCK_2 = BLOCKS.register( + // Our registry name. + "example_block" + // A function creating the object we want to register + // given its registry name as a ResourceLocation. + registryName -> new SlabBlock(...) ); ``` -The class `DeferredHolder` holds our object. The type parameter `R` is the type of the registry we are registering to (in our case `Block`). The type parameter `T` is the type of our supplier. Since we directly register a `Block` in this example, we provide `Block` as the second parameter. If we were to register an object of a subclass of `Block`, for example `SlabBlock`, we would provide `SlabBlock` here instead. +The class `DeferredHolder` holds our object. The type parameter `R` is the type of the registry we are registering to (in our case `Block`). The type parameter `T` is the type of our supplier. Since we directly register a `Block` in the first example, we provide `Block` as the second parameter. If we were to register an object of a subclass of `Block`, for example `SlabBlock` (as shown in the second example), we would provide `SlabBlock` here instead. `DeferredHolder` is a subclass of `Supplier`. To get our registered object when we need it, we can call `DeferredHolder#get()`. The fact that `DeferredHolder` extends `Supplier` also allows us to use `Supplier` as the type of our field. That way, the above code block becomes the following: ```java -public static final Supplier EXAMPLE_BLOCK = BLOCKS.register( - "example_block" // Our registry name. - () -> new Block(...) // A supplier of the object we want to register. +public static final Supplier EXAMPLE_BLOCK_1 = BLOCKS.register( + // Our registry name. + "example_block" + // A supplier of the object we want to register. + () -> new Block(...) +); + +public static final Supplier EXAMPLE_BLOCK_2 = BLOCKS.register( + // Our registry name. + "example_block" + // A function creating the object we want to register + // given its registry name as a ResourceLocation. + registryName -> new SlabBlock(...) ); ``` +:::note Be aware that a few places explicitly require a `Holder` or `DeferredHolder` and will not just accept any `Supplier`. If you need either of those two, it is best to change the type of your `Supplier` back to `Holder` or `DeferredHolder` as necessary. +::: Finally, since the entire system is a wrapper around registry events, we need to tell the `DeferredRegister` to attach itself to the registry events as needed: @@ -72,7 +94,7 @@ public ExampleMod(IEventBus modBus) { ``` :::info -There are specialized variants of `DeferredRegister`s for blocks and items that provide helper methods, called [`DeferredRegister.Blocks`][defregblocks] and [`DeferredRegister.Items`][defregitems], respectively. +There are specialized variants of `DeferredRegister`s for blocks, items, and data components that provide helper methods: [`DeferredRegister.Blocks`][defregblocks], [`DeferredRegister.Items`][defregitems], and [`DeferredRegister.DataComponents`][defregcomp], respectively. ::: ### `RegisterEvent` @@ -102,11 +124,11 @@ public void register(RegisterEvent event) { Sometimes, you will find yourself in situations where you want to get a registered object by a given id. Or, you want to get the id of a certain registered object. Since registries are basically maps of ids (`ResourceLocation`s) to distinct objects, i.e. a reversible map, both of these operations work: ```java -BuiltInRegistries.BLOCKS.get(ResourceLocation.fromNamespaceAndPath("minecraft", "dirt")); // returns the dirt block +BuiltInRegistries.BLOCKS.getValue(ResourceLocation.fromNamespaceAndPath("minecraft", "dirt")); // returns the dirt block BuiltInRegistries.BLOCKS.getKey(Blocks.DIRT); // returns the resource location "minecraft:dirt" // Assume that ExampleBlocksClass.EXAMPLE_BLOCK.get() is a Supplier with the id "yourmodid:example_block" -BuiltInRegistries.BLOCKS.get(ResourceLocation.fromNamespaceAndPath("yourmodid", "example_block")); // returns the example block +BuiltInRegistries.BLOCKS.getValue(ResourceLocation.fromNamespaceAndPath("yourmodid", "example_block")); // returns the example block BuiltInRegistries.BLOCKS.getKey(ExampleBlocksClass.EXAMPLE_BLOCK.get()); // returns the resource location "yourmodid:example_block" ``` @@ -172,7 +194,7 @@ static void registerRegistries(NewRegistryEvent event) { You can now register new registry contents like with any other registry, through both `DeferredRegister` and `RegisterEvent`: ```java -public static final DeferredRegister SPELLS = DeferredRegister.create("yourmodid", SPELL_REGISTRY); +public static final DeferredRegister SPELLS = DeferredRegister.create(SPELL_REGISTRY, "yourmodid"); public static final Supplier EXAMPLE_SPELL = SPELLS.register("example_spell", () -> new Spell(...)); // Alternatively: @@ -228,10 +250,10 @@ First, we create a `RegistrySetBuilder` and add our entries to it (one `Registry ```java new RegistrySetBuilder() .add(Registries.CONFIGURED_FEATURE, bootstrap -> { - // Register configured features through the bootstrap context (see below) + // Register configured features through the bootstrap context (see below) }) .add(Registries.PLACED_FEATURE, bootstrap -> { - // Register placed features through the bootstrap context (see below) + // Register placed features through the bootstrap context (see below) }); ``` @@ -254,7 +276,7 @@ new RegistrySetBuilder() ); }) .add(Registries.PLACED_FEATURE, bootstrap -> { - // ... + // ... }); ``` @@ -287,8 +309,8 @@ Finally, we use our `RegistrySetBuilder` in an actual data provider, and registe ```java @SubscribeEvent -static void onGatherData(GatherDataEvent event) { - event.getGenerator().addProvider( +public static void onGatherData(GatherDataEvent event) { + CompletableFuture lookupProvider = event.getGenerator().addProvider( // Only run datapack generation when server data is being generated event.includeServer(), // Create the provider @@ -300,7 +322,11 @@ static void onGatherData(GatherDataEvent event) { // A set of mod ids we are generating. Usually only your own mod id. Set.of("yourmodid") ) - ); + ).getRegistryProvider(); + + // Use the lookup provider generated from your datapack entries as input + // to all other data providers. + // ... } ``` @@ -311,8 +337,9 @@ static void onGatherData(GatherDataEvent event) { [datagenindex]: ../resources/index.md#data-generation [datapack]: ../resources/index.md#data [defregblocks]: ../blocks/index.md#deferredregisterblocks-helpers +[defregcomp]: ../items/datacomponents.md#creating-custom-data-components [defregitems]: ../items/index.md#deferredregisteritems -[event]: ./events.md +[event]: events.md [item]: ../items/index.md [resloc]: ../misc/resourcelocation.md [resourcekey]: ../misc/resourcelocation.md#resourcekeys diff --git a/docs/datastorage/_category_.json b/docs/datastorage/_category_.json index ec1f1022b..a04f09557 100644 --- a/docs/datastorage/_category_.json +++ b/docs/datastorage/_category_.json @@ -1,4 +1,4 @@ { - "label": "Data Storage", - "position": 8 + "label": "Data Storage", + "position": 8 } \ No newline at end of file diff --git a/docs/datastorage/attachments.md b/docs/datastorage/attachments.md index 08be20520..13562c28f 100644 --- a/docs/datastorage/attachments.md +++ b/docs/datastorage/attachments.md @@ -113,6 +113,6 @@ NeoForge.EVENT_BUS.register(PlayerEvent.Clone.class, event -> { }); ``` -[saveddata]: ./saveddata.md -[datacomponents]: ../items/datacomponents.mdx +[saveddata]: saveddata.md +[datacomponents]: ../items/datacomponents.md [network]: ../networking/index.md diff --git a/docs/datastorage/nbt.md b/docs/datastorage/nbt.md index 335d73084..4532cf06f 100644 --- a/docs/datastorage/nbt.md +++ b/docs/datastorage/nbt.md @@ -101,5 +101,5 @@ NBT is used in a lot of places in Minecraft. Some of the most common examples in [blockentity]: ../blockentities/index.md [datapack]: ../resources/index.md#data -[datacomponents]: ../items/datacomponents.mdx +[datacomponents]: ../items/datacomponents.md [nbtwiki]: https://minecraft.wiki/w/NBT_format diff --git a/docs/datastorage/saveddata.md b/docs/datastorage/saveddata.md index 99e61dbc9..37f1a6bb7 100644 --- a/docs/datastorage/saveddata.md +++ b/docs/datastorage/saveddata.md @@ -25,15 +25,32 @@ Any `SavedData` is loaded and/or attached to a level dynamically. As such, if on For example, if a SD was named "example" within the Nether, then a file would be created at `.//DIM-1/data/example.dat` and would be implemented like so: ```java -// In some class -public ExampleSavedData create() { - return new ExampleSavedData(); -} - -public ExampleSavedData load(CompoundTag tag, HolderLookup.Provider lookupProvider) { - ExampleSavedData data = this.create(); - // Load saved data - return data; +// In some saved data implementation +public class ExampleSavedData extends SavedData { + + // Create new instance of saved data + public ExampleSavedData create() { + return new ExampleSavedData(); + } + + // Load existing instance of saved data + public ExampleSavedData load(CompoundTag tag, HolderLookup.Provider lookupProvider) { + ExampleSavedData data = this.create(); + // Load saved data + return data; + } + + @Override + public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) { + // Write data to tag + return tag; + } + + public void foo() { + // Change data in saved data + // Call set dirty if data changes + this.setDirty(); + } } // In some method within the class diff --git a/docs/gettingstarted/_category_.json b/docs/gettingstarted/_category_.json index 87121b812..5ed99b17a 100644 --- a/docs/gettingstarted/_category_.json +++ b/docs/gettingstarted/_category_.json @@ -1,4 +1,4 @@ { - "label": "Getting Started", - "position": 1 + "label": "Getting Started", + "position": 1 } \ No newline at end of file diff --git a/docs/gettingstarted/modfiles.md b/docs/gettingstarted/modfiles.md index 6a1581272..ec549f5c2 100644 --- a/docs/gettingstarted/modfiles.md +++ b/docs/gettingstarted/modfiles.md @@ -108,7 +108,7 @@ modId = "examplemod2" | `version` | string | `"1"` | The version of the mod, preferably in a [variation of Maven versioning][versioning]. When set to `${file.jarVersion}`, it will be replaced with the value of the `Implementation-Version` property in the JAR's manifest (displays as `0.0NONE` in a development environment). | `version="1.20.2-1.0.0"` | | `displayName` | string | value of `modId` | The display name of the mod. Used when representing the mod on a screen (e.g., mod list, mod mismatch). | `displayName="Example Mod"` | | `description` | string | `'''MISSING DESCRIPTION'''` | The description of the mod shown in the mod list screen. It is recommended to use a [multiline literal string][multiline]. This value is also translatable, see [Translating Mod Metadata][i18n] for more info. | `description='''This is an example.'''` | -| `logoFile` | string | _nothing_ | The name and extension of an image file used on the mods list screen. The logo must be in the root of the JAR or directly in the root of the source set (e.g. `src/main/resources` for the main source set). | `logoFile="example_logo.png"` | +| `logoFile` | string | _nothing_ | The name and extension of an image file used on the mods list screen. The location must be an absolute path starting from the root of the JAR or source set (e.g. `src/main/resources` for the main source set). Valid filename characters are lowercase letters (`a-z`), digits (`0-9`), slashes, (`/`), underscores (`_`), periods (`.`) and hyphens (`-`). The complete character set is `[a-z0-9_-.]`. | `logoFile="test/example_logo.png"` | | `logoBlur` | boolean | `true` | Whether to use `GL_LINEAR*` (true) or `GL_NEAREST*` (false) to render the `logoFile`. In simpler terms, this means whether the logo should be blurred or not when trying to scale the logo. | `logoBlur=false` | | `updateJSONURL` | string | _nothing_ | A URL to a JSON used by the [update checker][update] to make sure the mod you are playing is the latest version. | `updateJSONURL="https://example.github.io/update_checker.json"` | | `features` | table | `{}` | See [features]. | `features={java_version="[17,)"}` | @@ -232,7 +232,7 @@ An entry in `neoforge.mods.toml` does not need a corresponding `@Mod` annotation [multiline]: https://toml.io/en/v1.0.0#string [mvr]: https://maven.apache.org/enforcer/enforcer-rules/versionRanges.html [neoversioning]: versioning.md#neoforge -[packaging]: ./structuring.md#packaging +[packaging]: structuring.md#packaging [registration]: ../concepts/registries.md#deferredregister [resource]: ../resources/index.md [serviceload]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/ServiceLoader.html#load(java.lang.Class) @@ -241,5 +241,5 @@ An entry in `neoforge.mods.toml` does not need a corresponding `@Mod` annotation [toml]: https://toml.io/ [update]: ../misc/updatechecker.md [uses]: https://docs.oracle.com/javase/specs/jls/se21/html/jls-7.html#jls-7.7.3 -[versioning]: ./versioning.md +[versioning]: versioning.md [enumextension]: ../advanced/extensibleenums.md diff --git a/docs/gui/_category_.json b/docs/gui/_category_.json index f78a4e958..66d6d72a3 100644 --- a/docs/gui/_category_.json +++ b/docs/gui/_category_.json @@ -1,4 +1,4 @@ { - "label": "GUIs", - "position": 9 + "label": "GUIs", + "position": 9 } \ No newline at end of file diff --git a/docs/gui/menus.md b/docs/gui/menus.md index 05ee87914..cc29a1b96 100644 --- a/docs/gui/menus.md +++ b/docs/gui/menus.md @@ -14,12 +14,12 @@ A `MenuType` is created by passing in a `MenuSupplier` and a `FeatureFlagSet` to ```java // For some DeferredRegister> REGISTER -public static final Supplier> MY_MENU = REGISTER.register("my_menu", () -> new MenuType(MyMenu::new, FeatureFlags.DEFAULT_FLAGS)); +public static final Supplier> MY_MENU = REGISTER.register("my_menu", () -> new MenuType<>(MyMenu::new, FeatureFlags.DEFAULT_FLAGS)); // In MyMenu, an AbstractContainerMenu subclass public MyMenu(int containerId, Inventory playerInv) { - super(MY_MENU.get(), containerId); - // ... + super(MY_MENU.get(), containerId); + // ... } ``` @@ -39,9 +39,9 @@ public static final Supplier> MY_MENU_EXTRA = REGISTER.reg // In MyMenuExtra, an AbstractContainerMenu subclass public MyMenuExtra(int containerId, Inventory playerInv, FriendlyByteBuf extraData) { - super(MY_MENU_EXTRA.get(), containerId); - // Store extra data from buffer - // ... + super(MY_MENU_EXTRA.get(), containerId); + // Store extra data from buffer + // ... } ``` @@ -49,8 +49,8 @@ public MyMenuExtra(int containerId, Inventory playerInv, FriendlyByteBuf extraDa All menus are extended from `AbstractContainerMenu`. A menu takes in two parameters, the [`MenuType`][mt], which represents the type of the menu itself, and the container id, which represents the unique identifier of the menu for the current accessor. -:::caution -The player can only have 100 unique menus open at once. +:::note +The menu identifier cycles through 0-99, incrementing whenever a player opens a menu. ::: Each menu should contain two constructors: one used to initialize the menu on the server and one used to initialize the menu on the client. The constructor used to initialize the menu on the client is the one supplied to the `MenuType`. Any fields that the server menu constructor contains should have some default for the client menu constructor. @@ -58,15 +58,19 @@ Each menu should contain two constructors: one used to initialize the menu on th ```java // Client menu constructor public MyMenu(int containerId, Inventory playerInventory) { // optional FriendlyByteBuf parameter if reading data from server - this(containerId, playerInventory, /* Any default parameters here */); + this(containerId, playerInventory, /* Any default parameters here */); } // Server menu constructor public MyMenu(int containerId, Inventory playerInventory, /* Any additional parameters here. */) { - // ... + // ... } ``` +:::note +If no additional data needs to be displayed in the menu, then only one constructor is necessary. +::: + Each menu implementation must implement two methods: `#stillValid` and [`#quickMoveStack`][qms]. ### `#stillValid` and `ContainerLevelAccess` @@ -78,18 +82,18 @@ A `ContainerLevelAccess` supplies the current level and block position within an ```java // Client menu constructor public MyMenuAccess(int containerId, Inventory playerInventory) { - this(containerId, playerInventory, ContainerLevelAccess.NULL); + this(containerId, playerInventory, ContainerLevelAccess.NULL); } // Server menu constructor public MyMenuAccess(int containerId, Inventory playerInventory, ContainerLevelAccess access) { - // ... + // ... } // Assume this menu is attached to Supplier MY_BLOCK @Override public boolean stillValid(Player player) { - return AbstractContainerMenu.stillValid(this.access, player, MY_BLOCK.get()); + return AbstractContainerMenu.stillValid(this.access, player, MY_BLOCK.get()); } ``` @@ -123,22 +127,22 @@ NeoForge patches the packet to provide the full integer to the client. // Client menu constructor public MyMenuAccess(int containerId, Inventory playerInventory) { - this(containerId, playerInventory, new ItemStackHandler(5), DataSlot.standalone()); + this(containerId, playerInventory, new ItemStackHandler(5), DataSlot.standalone()); } // Server menu constructor public MyMenuAccess(int containerId, Inventory playerInventory, IItemHandler dataInventory, DataSlot dataSingle) { - // Check if the data inventory size is some fixed value - // Then, add slots for data inventory - this.addSlot(new SlotItemHandler(dataInventory, /*...*/)); + // Check if the data inventory size is some fixed value + // Then, add slots for data inventory + this.addSlot(new SlotItemHandler(dataInventory, /*...*/)); - // Add slots for player inventory - this.addSlot(new Slot(playerInventory, /*...*/)); + // Add slots for player inventory + this.addSlot(new Slot(playerInventory, /*...*/)); - // Add data slots for handled integers - this.addDataSlot(dataSingle); + // Add data slots for handled integers + this.addDataSlot(dataSingle); - // ... + // ... } ``` @@ -151,18 +155,18 @@ If multiple integers need to be synced to the client, a `ContainerData` can be u // Client menu constructor public MyMenuAccess(int containerId, Inventory playerInventory) { - this(containerId, playerInventory, new SimpleContainerData(3)); + this(containerId, playerInventory, new SimpleContainerData(3)); } // Server menu constructor public MyMenuAccess(int containerId, Inventory playerInventory, ContainerData dataMultiple) { - // Check if the ContainerData size is some fixed value - checkContainerDataCount(dataMultiple, 3); + // Check if the ContainerData size is some fixed value + checkContainerDataCount(dataMultiple, 3); - // Add data slots for handled integers - this.addDataSlots(dataMultiple); + // Add data slots for handled integers + this.addDataSlots(dataMultiple); - // ... + // ... } ``` @@ -184,81 +188,81 @@ Across Minecraft implementations, this method is fairly consistent in its logic: // - Player Hotbar (32 - 40) @Override public ItemStack quickMoveStack(Player player, int quickMovedSlotIndex) { - // The quick moved slot stack - ItemStack quickMovedStack = ItemStack.EMPTY; - // The quick moved slot - Slot quickMovedSlot = this.slots.get(quickMovedSlotIndex) + // The quick moved slot stack + ItemStack quickMovedStack = ItemStack.EMPTY; + // The quick moved slot + Slot quickMovedSlot = this.slots.get(quickMovedSlotIndex) - // If the slot is in the valid range and the slot is not empty - if (quickMovedSlot != null && quickMovedSlot.hasItem()) { - // Get the raw stack to move - ItemStack rawStack = quickMovedSlot.getItem(); - // Set the slot stack to a copy of the raw stack - quickMovedStack = rawStack.copy(); - - /* - The following quick move logic can be simplified to if in data inventory, - try to move to player inventory/hotbar and vice versa for containers - that cannot transform data (e.g. chests). - */ - - // If the quick move was performed on the data inventory result slot - if (quickMovedSlotIndex == 0) { - // Try to move the result slot into the player inventory/hotbar - if (!this.moveItemStackTo(rawStack, 5, 41, true)) { - // If cannot move, no longer quick move - return ItemStack.EMPTY; - } - - // Perform logic on result slot quick move - slot.onQuickCraft(rawStack, quickMovedStack); - } - // Else if the quick move was performed on the player inventory or hotbar slot - else if (quickMovedSlotIndex >= 5 && quickMovedSlotIndex < 41) { - // Try to move the inventory/hotbar slot into the data inventory input slots - if (!this.moveItemStackTo(rawStack, 1, 5, false)) { - // If cannot move and in player inventory slot, try to move to hotbar - if (quickMovedSlotIndex < 32) { - if (!this.moveItemStackTo(rawStack, 32, 41, false)) { + // If the slot is in the valid range and the slot is not empty + if (quickMovedSlot != null && quickMovedSlot.hasItem()) { + // Get the raw stack to move + ItemStack rawStack = quickMovedSlot.getItem(); + // Set the slot stack to a copy of the raw stack + quickMovedStack = rawStack.copy(); + + /* + The following quick move logic can be simplified to if in data inventory, + try to move to player inventory/hotbar and vice versa for containers + that cannot transform data (e.g. chests). + */ + + // If the quick move was performed on the data inventory result slot + if (quickMovedSlotIndex == 0) { + // Try to move the result slot into the player inventory/hotbar + if (!this.moveItemStackTo(rawStack, 5, 41, true)) { + // If cannot move, no longer quick move + return ItemStack.EMPTY; + } + + // Perform logic on result slot quick move + slot.onQuickCraft(rawStack, quickMovedStack); + } + // Else if the quick move was performed on the player inventory or hotbar slot + else if (quickMovedSlotIndex >= 5 && quickMovedSlotIndex < 41) { + // Try to move the inventory/hotbar slot into the data inventory input slots + if (!this.moveItemStackTo(rawStack, 1, 5, false)) { + // If cannot move and in player inventory slot, try to move to hotbar + if (quickMovedSlotIndex < 32) { + if (!this.moveItemStackTo(rawStack, 32, 41, false)) { + // If cannot move, no longer quick move + return ItemStack.EMPTY; + } + } + // Else try to move hotbar into player inventory slot + else if (!this.moveItemStackTo(rawStack, 5, 32, false)) { + // If cannot move, no longer quick move + return ItemStack.EMPTY; + } + } + } + // Else if the quick move was performed on the data inventory input slots, try to move to player inventory/hotbar + else if (!this.moveItemStackTo(rawStack, 5, 41, false)) { // If cannot move, no longer quick move return ItemStack.EMPTY; - } } - // Else try to move hotbar into player inventory slot - else if (!this.moveItemStackTo(rawStack, 5, 32, false)) { - // If cannot move, no longer quick move - return ItemStack.EMPTY; - } - } - } - // Else if the quick move was performed on the data inventory input slots, try to move to player inventory/hotbar - else if (!this.moveItemStackTo(rawStack, 5, 41, false)) { - // If cannot move, no longer quick move - return ItemStack.EMPTY; - } - if (rawStack.isEmpty()) { - // If the raw stack has completely moved out of the slot, set the slot to the empty stack - quickMovedSlot.set(ItemStack.EMPTY); - } else { - // Otherwise, notify the slot that that the stack count has changed - quickMovedSlot.setChanged(); - } + if (rawStack.isEmpty()) { + // If the raw stack has completely moved out of the slot, set the slot to the empty stack + quickMovedSlot.set(ItemStack.EMPTY); + } else { + // Otherwise, notify the slot that that the stack count has changed + quickMovedSlot.setChanged(); + } - /* - The following if statement and Slot#onTake call can be removed if the - menu does not represent a container that can transform stacks (e.g. - chests). - */ - if (rawStack.getCount() == quickMovedStack.getCount()) { - // If the raw stack was not able to be moved to another slot, no longer quick move - return ItemStack.EMPTY; + /* + The following if statement and Slot#onTake call can be removed if the + menu does not represent a container that can transform stacks (e.g. + chests). + */ + if (rawStack.getCount() == quickMovedStack.getCount()) { + // If the raw stack was not able to be moved to another slot, no longer quick move + return ItemStack.EMPTY; + } + // Execute logic on what to do post move with the remaining stack + quickMovedSlot.onTake(player, rawStack); } - // Execute logic on what to do post move with the remaining stack - quickMovedSlot.onTake(player, rawStack); - } - return quickMovedStack; // Return the slot stack + return quickMovedStack; // Return the slot stack } ``` @@ -280,8 +284,8 @@ A `MenuProvider` can easily be created using `SimpleMenuProvider`, which takes i // In some implementation with access to the Player on the logical server (e.g. ServerPlayer instance) // Assume we have ServerPlayer serverPlayer serverPlayer.openMenu(new SimpleMenuProvider( - (containerId, playerInventory, player) -> new MyMenu(containerId, playerInventory), - Component.translatable("menu.title.examplemod.mymenu") + (containerId, playerInventory, player) -> new MyMenu(containerId, playerInventory), + Component.translatable("menu.title.examplemod.mymenu") )); ``` @@ -291,7 +295,7 @@ Menus are typically opened on a player interaction of some kind (e.g. when a blo #### Block Implementation -Blocks typically implement a menu by overriding `BlockBehaviour#useWithoutItem`. If on the [logical client][side], the [interaction] returns `InteractionResult#SUCCESS`. Otherwise, it opens the menu and returns `InteractionResult#CONSUME`. +Blocks typically implement a menu by overriding `BlockBehaviour#useWithoutItem`, returning `InteractionResult#SUCCESS` for the [interaction]. The `MenuProvider` should be implemented by overriding `BlockBehaviour#getMenuProvider`. Vanilla methods use this to view the menu in spectator mode. @@ -299,15 +303,16 @@ The `MenuProvider` should be implemented by overriding `BlockBehaviour#getMenuPr // In some Block subclass @Override public MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) { - return new SimpleMenuProvider(/* ... */); + return new SimpleMenuProvider(/* ... */); } @Override public InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult result) { - if (!level.isClientSide && player instanceof ServerPlayer serverPlayer) { - serverPlayer.openMenu(state.getMenuProvider(level, pos)); - } - return InteractionResult.sidedSuccess(level.isClientSide); + if (!level.isClientSide && player instanceof ServerPlayer serverPlayer) { + serverPlayer.openMenu(state.getMenuProvider(level, pos)); + } + + return InteractionResult.SUCCESS; } ``` @@ -321,15 +326,16 @@ Mobs typically implement a menu by overriding `Mob#mobInteract`. This is done si ```java public class MyMob extends Mob implements MenuProvider { - // ... + // ... + + @Override + public InteractionResult mobInteract(Player player, InteractionHand hand) { + if (!this.level.isClientSide && player instanceof ServerPlayer serverPlayer) { + serverPlayer.openMenu(this); + } - @Override - public InteractionResult mobInteract(Player player, InteractionHand hand) { - if (!this.level.isClientSide && player instanceof ServerPlayer serverPlayer) { - serverPlayer.openMenu(this); + return InteractionResult.SUCCESS; } - return InteractionResult.sidedSuccess(this.level.isClientSide); - } } ``` @@ -342,7 +348,7 @@ Once again, this is the simplest way to implement the logic, not the only way. [mt]: #menutype [qms]: #quickmovestack [cap]: ../datastorage/capabilities.md#neoforge-provided-capabilities -[screen]: ./screens.md +[screen]: screens.md [icf]: #icontainerfactory [side]: ../concepts/sides.md#the-logical-side [interaction]: ../items/interactionpipeline.md diff --git a/docs/gui/screens.md b/docs/gui/screens.md index 24e226fe6..d35e55471 100644 --- a/docs/gui/screens.md +++ b/docs/gui/screens.md @@ -42,7 +42,7 @@ Strings should typically be passed in as [`Component`s][component] as they handl Textures are drawn through blitting, hence the method name `#blit`, which, for this purpose, copies the bits of an image and draws them directly to the screen. These are drawn through a position texture shader. -Each `#blit` method takes in a `ResourceLocation`, which represents the absolute location of the texture: +Each `#blit` method takes in a `Function`, which determines how to render the texture, and `ResourceLocation`, which represents the absolute location of the texture: ```java // Points to 'assets/examplemod/textures/gui/container/example_container.png' @@ -51,13 +51,13 @@ private static final ResourceLocation TEXTURE = ResourceLocation.fromNamespaceAn While there are many different `#blit` overloads, we will only discuss two of them. -The first `#blit` takes in six integers and assumes the texture being rendered is on a 256 x 256 PNG file. It takes in the left x and top y screen coordinate, the left x and top y coordinate within the PNG, and the width and height of the image to render. +The first `#blit` takes in two integers, than two floats, and finally four more integers, assuming the image is on a PNG file. It takes in the left x and top y screen coordinate, the left x and top y coordinate within the PNG, the width and height of the image to render, and the width and height of the PNG file. :::tip The size of the PNG file must be specified so that the coordinates can be normalized to obtain the associated UV values. ::: -The second `#blit` which the first calls expands this to seven integers and two floats for the PNG coordinates, only assuming the image is on a PNG file. It takes in the left x and top y screen coordinate, the z coordinate (referred to as the blit offset), the left x and top y coordinate within the PNG, the width and height of the image to render, and the width and height of the PNG file. +The second `#blit` adds an additional integer at the end which represents the tint color of the image to be drawn. This color will be treated as an ARGB value, and is `0xFFFFFFFF` if not specified. #### `blitSprite` @@ -68,9 +68,9 @@ The second `#blit` which the first calls expands this to seven integers and two private static final ResourceLocation SPRITE = ResourceLocation.fromNamespaceAndPath("examplemod", "container/example_container/example_sprite"); ``` -One set of `#blitSprite` methods have the same parameters as `#blit`, except for the x and y coordinate within the sprite. +One set of `#blitSprite` methods have the same parameters as `#blit`, except for the the four integers dealing with the coordinates, width, and height of the PNG. -The other `#blitSprite` methods take in more texture information to allow for rendering part of the sprite. These methods take in the texture width and height, the x and y coordinate in the sprite, the left x and top y screen coordinate, the z coordinate (referred to as the blit offset), and the width and height of the image to render. +The other `#blitSprite` methods take in more texture information to allow for rendering part of the sprite. These methods take in the texture width and height, the x and y coordinate in the sprite, the left x and top y screen coordinate, the tint color (in ARGB format), and the width and height of the image to render. If the sprite size does not match the texture size, then the sprite can be scaled in one of three ways: `stretch`, `tile`, and `nine_slice`. `stretch` stretches the image from the texture size to the screen size. `tile` renders the texture over and over again until it reaches the screen size. `nine_slice` divides the texture into one center, four edges, and four corners to tile the texture to the required screen size. @@ -117,7 +117,10 @@ This is set by adding the `gui.scaling` JSON object in an mcmeta file with the s "right": 1, "top": 1, "bottom": 1 - } + }, + // When true the center part of the texture will be applied like + // the stretch type instead of a nine slice tiling. + "stretch_inner": true } } } @@ -159,7 +162,7 @@ Screens implement `ContainerEventHandler` through `AbstractContainerEventHandler `NarratableEntry`s are elements which can be spoken about through Minecraft's accessibility narration feature. Each element can provide different narration depending on what is hovered or selected, prioritized typically by focus, hovering, and then all other cases. -`NarratableEntry`s have three methods: one which determines the priority of the element (`#narrationPriority`), one which determines whether to speak the narration (`#isActive`), and finally one which supplies the narration to its associated output, spoken or read (`#updateNarration`). +`NarratableEntry`s have four methods: two which determine the priority of the element when being read (`#narrationPriority` and `#getTabOrderGroup`), one which determines whether to speak the narration (`#isActive`), and finally one which supplies the narration to its associated output, spoken or read (`#updateNarration`). :::note All widgets from Minecraft are `NarratableEntry`s, so it typically does not need to be manually implemented if using an available subtype. @@ -238,7 +241,7 @@ Tooltips are rendered through `GuiGraphics#renderTooltip` or `GuiGraphics#render @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { // Background is typically rendered first - this.renderBackground(graphics); + this.renderBackground(graphics, mouseX, mouseY, partialTick); // Render things here before widgets (background textures) @@ -369,9 +372,17 @@ protected void renderBg(GuiGraphics graphics, float partialTick, int mouseX, int * 'topPos' should already represent the top left corner of where * the texture should be rendered as it was precomputed from the * 'imageWidth' and 'imageHeight'. The two zeros represent the - * integer u/v coordinates inside the 256 x 256 PNG file. + * integer u/v coordinates inside the PNG file, whose size is + * represented by the last two integers (typically 256 x 256). */ - graphics.blit(BACKGROUND_LOCATION, this.leftPos, this.topPos, 0, 0, this.imageWidth, this.imageHeight); + graphics.blit( + RenderType::guiTextured, + BACKGROUND_LOCATION, + this.leftPos, this.topPos, + 0, 0, + this.imageWidth, this.imageHeight, + 256, 256 + ); } ``` @@ -385,7 +396,9 @@ protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) { // Assume we have some Component 'label' // 'label' is drawn at 'labelX' and 'labelY' - graphics.drawString(this.font, this.label, this.labelX, this.labelY, 0x404040); + // The color is an ARGB value, if the alpha is less than 4, than the alpha is set to 255 + // The final boolean renders the drop shadow when true + graphics.drawString(this.font, this.label, this.labelX, this.labelY, 0x404040, false); } ``` @@ -404,7 +417,7 @@ private void registerScreens(RegisterMenuScreensEvent event) { } ``` -[menus]: ./menus.md +[menus]: menus.md [network]: ../networking/index.md [screen]: #the-screen-subtype [argb]: https://en.wikipedia.org/wiki/RGBA_color_model#ARGB32 diff --git a/docs/items/_category_.json b/docs/items/_category_.json index 066fa0950..ce4517768 100644 --- a/docs/items/_category_.json +++ b/docs/items/_category_.json @@ -1,4 +1,4 @@ { - "label": "Items", - "position": 4 + "label": "Items", + "position": 4 } \ No newline at end of file diff --git a/docs/items/armor.md b/docs/items/armor.md new file mode 100644 index 000000000..86204da70 --- /dev/null +++ b/docs/items/armor.md @@ -0,0 +1,423 @@ +--- +sidebar_position: 5 +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Armor + +Armors are [items][item] whose primary use is to protect an entity from damage using a variety of resistances and effects. Many mods add new armor sets (for example copper armor). + +## Custom Armor Sets + +An armor set for a humanoid entity typically consists of four items: a helmet for the head, a chestplate for the chest, leggings for the legs, and boots for the feet. There is also armor for wolves, horses, and llamas that are applied to a 'body' armor slot specifically for animals. All of these items are generally implemented through `ArmorItem` and `AnimalArmorItem`, respectively. + +Armors are almost completely implemented through seven [data components][datacomponents]: + +- `DataComponents#MAX_DAMAGE` and `#DAMAGE` for durability +- `#MAX_STACK_SIZE` to set the stack size to `1` +- `#REPAIRABLE` for repairing an armor piece in an anvil +- `#ENCHANTABLE` for the maximum [enchanting][enchantment] value +- `#ATTRIBUTE_MODIFIERS` for armor, armor toughness, and knockback resistance +- `#EQUIPPABLE` for how the entity can equip the item. + +`ArmorItem` and `AnimalArmorItem` use `ArmorMaterial` combined with `ArmorType` or `AnimalArmorItem.BodyType` respectively to set up the components. Reference values can be found within `ArmorMaterials`. This example uses a copper armor material, which you can adjust the values as needed. + +```java +public static final ArmorMaterial COPPER_ARMOR_MATERIAL = new ArmorMaterial( + // The durability multiplier of the armor material. + // ArmorType have different unit durabilities that the multiplier is applied to: + // - HELMET: 11 + // - CHESTPLATE: 16 + // - LEGGINGS: 15 + // - BOOTS: 13 + // - BODY: 16 + 15, + // Determines the defense value (or the number of half-armors on the bar). + // Based on ArmorType. + Util.make(new EnumMap<>(ArmorType.class), map -> { + map.put(ArmorItem.Type.BOOTS, 2); + map.put(ArmorItem.Type.LEGGINGS, 4); + map.put(ArmorItem.Type.CHESTPLATE, 6); + map.put(ArmorItem.Type.HELMET, 2); + map.put(ArmorItem.Type.BODY, 4); + }), + // Determines the enchantability of the armor. This represents how good the enchantments on this armor will be. + // Gold uses 25; we put copper slightly below that. + 20, + // Determines the sound played when equipping this armor. + // This is wrapped with a Holder. + SoundEvents.ARMOR_EQUIP_GENERIC, + // Returns the toughness value of the armor. The toughness value is an additional value included in + // damage calculation, for more information, refer to the Minecraft Wiki's article on armor mechanics: + // https://minecraft.wiki/w/Armor#Armor_toughness + // Only diamond and netherite have values greater than 0 here, so we just return 0. + 0, + // Returns the knockback resistance value of the armor. While wearing this armor, the player is + // immune to knockback to some degree. If the player has a total knockback resistance value of 1 or greater + // from all armor pieces combined, they will not take any knockback at all. + // Only netherite has values greater than 0 here, so we just return 0. + 0, + // The tag that determines what items can repair this armor. + Tags.Items.INGOTS_COPPER, + // The relative location of the EquipmentModel JSON discussed below + // Points to assets/examplemod/models/equipment/copper.json + ResourceLocation.fromNamespaceAndPath("examplemod", "copper") +); +``` + +Now that we have our `ArmorMaterial`, we can use it for [registering] armor. `ArmorItem` takes in the `ArmorMaterial` and the `ArmorType` representing where the item can be equipped. `AnimalArmorItem`, on the other hand takes in the `ArmorMaterial` and the `AnimalArmorItem.BodyType`. It also can optionally take in the an equip sound and whether to apply the durability and repairable components. + +```java +// ITEMS is a DeferredRegister.Items +public static final DeferredItem COPPER_HELMET = ITEMS.registerItem( + "copper_helmet", + props -> new ArmorItem( + // The material to use. + COPPER_ARMOR_MATERIAL, + // The type of armor to create. + ArmorType.HELMET, + // The item properties. + props + ) +); + +public static final DeferredItem COPPER_CHESTPLATE = + ITEMS.registerItem("copper_chestplate", props -> new ArmorItem(...)); +public static final DeferredItem COPPER_LEGGINGS = + ITEMS.registerItem("copper_chestplate", props -> new ArmorItem(...)); +public static final DeferredItem COPPER_BOOTS = + ITEMS.registerItem("copper_chestplate", props -> new ArmorItem(...)); + +public static final DeferredItem COPPER_WOLF_ARMOR = ITEMS.registerItem( + "copper_wolf_armor", + props -> new AnimalArmorItem( + // The material to use. + COPPER_ARMOR_MATERIAL, + // The body type the armor can be worn by. + AnimalArmorItem.BodyType.CANINE, + // The item properties. + props + ) +); + +public static final DeferredItem COPPER_HORSE_ARMOR = + ITEMS.registerItem("copper_horse_armor", props -> new AnimalArmorItem(...)); +``` + +Now, creating armor or an armor-like item does not need to extend `ArmorItem` or `AnimalArmorItem`. It simply can be implemented using a combination of the following parts: + +- Adding a `Equippable` with your own requirements by setting `DataComponents#EQUIPPABLE` via `Item.Properties#component`. +- Adding attributes to the item (e.g. armor, toughness, knockback) via `Item.Properties#attributes`. +- Adding item durability via `Item.Properties#durability`. +- Allowing the item to be repaired via `Item.Properties#repariable`. +- Allowing the item to be enchanted via `Item.Properties#enchantable`. +- Adding your armor to some of the `minecraft:enchantable/*` `ItemTags` so that your item can have certain enchantments applied to it. + +:::note +The only logic that has no alternative to using an `ArmorItem` base is when mobs replace their current held item via `Mob#canReplaceCurrentItem`. Mobs will always check for an instance of `SwordItem`, followed by `BowItem`, `CrossbowItem`, `ArmorItem`, and `DiggerItem`, with all other items not considered for specialized logic. +::: + +### `Equippable` + +`Equippable` is a data component that contains how an entity can equip this item and what handles the rendering in game. This allows any item, regardless of whether it is considered 'armor', to be equipped if this component is available (for example carpets on llamas). Each item with this component can only be equipped to a single `EquipmentSlot`. + +An `Equippable` can be created either by directly calling the record constructor or via `Equippable#builder`, which sets the defaults for each field, folowed by `build` once finished: + +```java +// Assume there is some DeferredRegister.Items ITEMS +public static final DeferredItem EQUIPPABLE = ITEMS.registerSimpleItem( + "equippable", + new Item.Properties().copmonent( + DataComponents.EQUIPPABLE, + // Sets the slot that this item can be equipped to. + Equippable.builder(EquipmentSlot.HELMET) + // Determines the sound played when equipping this armor. + // This is wrapped with a Holder. + // Defaults to SoundEvents#ARMOR_EQUIP_GENERIC. + .setEquipSound(SoundEvents.ARMOR_EQUIP_GENERIC) + // The relative location of the EquipmentModel JSON discussed below. + // Points to assets/examplemod/models/equipment/equippable.json + // When not set, does not render the equipment. + .setModel(ResourceLocation.fromNamespaceAndPath("examplemod", "equippable")) + // The relative location of the texture to overlay on the player screen when wearing (e.g., pumpkin blur). + // Points to assets/examplemod/textures/equippable.png + // When not set, does not render an overlay. + .setCameraOverlay(ResourceLocation.withDefaultNamespace("examplemod", "equippable")) + // A HolderSet of entity types (direct or tag) that can equip this item. + // When not set, any entity can equip this item. + .setAllowedEntities(EntityType.ZOMBIE) + // Whether the item can be equipped when dispensed from a dispenser. + // Defaults to true. + .setDispensable(true), + // Whether the item can be swapped off the player during a quick equip. + // Defaults to true. + .setSwappable(false), + // Whether the item should be damaged when attacked (for equipment typically). + // Must also be a damageable item. + // Defaults to true. + .setDamageOnHurt(false) + .build() + ) +); +``` + +## Equipment Models + +Now we have some armor in game, but if we try to wear it, nothing will render since we never specified how to render the equipment. To do so, we need to create an `EquipmentModel` JSON at the location specified by `Equippable#model`, relative to the `models/equipment` folder of the [resource pack][respack] (`assets` folder). The `EquipmentModel` specifies the associated textures to use for each layer to render. + +:::note +`EquipmentModel` is a bit of a misnomer as it does **not** specify the model to use when rendering, only the textures. The model used is the one passed in during [equipment rendering][rendering]. +::: + +An `EquipmentModel` is functionally a map of `EquipmentModel.LayerType`s to a list of `EquipmentModel.Layer`s to apply. + +The `LayerType` can be thought of as a group of textures to render for some instance. For example, `LayerType#HUMANOID` is used by the `HumanoidArmorLayer` to render the head, chest, and feet on humanoid entities; `LayerType#WOLF_BODY` is used by `WolfArmorLayer` to render the body armor. These can be combined into one equipment model JSON if they are for the same type of equippable, like copper armor. + +The `LayerType` maps to some list of `Layer`s to apply and render the model in the order provided. A `Layer` effectively represents a single texture to render. The first parameter represents the location of the texture, relative to `textures/entity/equipment`. + +The second parameter is an optional that indicates whether the [texture can be tinted][tinting] as a `EquipmentModel.Dyeable`. The `Dyeable` object holds an integer that, when present, indicates the default RGB color to tint the texture with. If this optional is not present, then pure white is used. + +:::warning +For a tint other than the undyed color to be applied to the item, the item must be in the [`ItemTags#DYEABLE`][tag] and have the `DataComponents#DYED_COLOR` component set to some RGB value. +::: + +The third parameter is a boolean that indicates whether the texture provided during rendering should be used instead of the one defined within the `Layer`. An example of this is a custom cape or custom elytra texture for the player. + +Let's create a equipment model for the copper armor material. We'll also assume that for each layer there are two textures: one for the actual armor and one that is overlayed and tinted. For the animal armor, we'll say that there is some dynamic texture to be used that can be passed in. + + + + +```json5 +// In assets/examplemod/models/equipment/copper.json +{ + // The layer map + "layers": { + // The serialized name of the EquipmentModel.LayerType to apply. + // For humanoid head, chest, and feet + "humanoid": [ + // A list of layers to render in the order provided + { + // The relative texture of the armor + // Points to assets/examplemod/textures/entity/equipment/copper/outer.png + "texture": "examplemod:copper/outer" + }, + { + // The overlay texture + // Points to assets/examplemod/textures/entity/equipment/copper/outer_overlay.png + "texture": "examplemod:copper/outer_overlay", + // When specified, allows the texture to be tinted the color in DataComponents#DYED_COLOR + // Otherwise, cannot be tinted + "dyeable": { + // An RGB value (always opaque color) + // 0x7683DE as decimal + // When not specified, set to 0 (meaning transparent or invisible) + "color_when_undyed": 7767006 + } + } + ], + // For humanoid legs + "humanoid_leggings": [ + { + // Points to assets/examplemod/textures/entity/equipment/copper/inner.png + "texture": "examplemod:copper/inner" + }, + { + // Points to assets/examplemod/textures/entity/equipment/copper/inner_overlay.png + "texture": "examplemod:copper/inner_overlay", + "dyeable": { + "color_when_undyed": 7767006 + } + } + ], + // For wolf armor + "wolf_body": [ + { + // Points to assets/examplemod/textures/entity/equipment/copper/wolf.png + "texture": "examplemod:copper/wolf", + // When true, uses the texture passed into the layer renderer instead + "use_player_texture": true + } + ], + // For horse armor + "horse_body": [ + { + // Points to assets/examplemod/textures/entity/equipment/copper/horse.png + "texture": "examplemod:copper/horse", + "use_player_texture": true + } + ] + } +} +``` + + + + + +```java +public class MyEquipmentModelProvider implements DataProvider { + + private final PackOutput.PathProvider path; + + public MyEquipmentModelProvider(PackOutput output) { + this.path = output.createPathProvider(PackOutput.Target.RESOURCE_PACK, "models/equipment"); + } + + private void add(BiConsumer registrar) { + registrar.accept( + // Must match Equippable#model + ResourceLocation.fromNamespaceAndPath("examplemod", "copper"), + EquipmentModel.builder() + // For humanoid head, chest, and feet + .addLayers( + EquipmentModel.LayerType.HUMANOID, + // Base texture + new EquipmentModel.Layer( + // The relative texture of the armor + // Points to assets/examplemod/textures/entity/equipment/copper/outer.png + ResourceLocation.fromNamespaceAndPath("examplemod", "copper/outer"), + Optional.empty(), + false + ), + // Overlay texture + new EquipmentModel.Layer( + // The overlay texture + // Points to assets/examplemod/textures/entity/equipment/copper/outer_overlay.png + ResourceLocation.fromNamespaceAndPath("examplemod", "copper/outer_overlay"), + // An RGB value (always opaque color) + // When not specified, set to 0 (meaning transparent or invisible) + Optional.of(new EquipmentModel.Dyeable(Optional.of(0x7683DE))), + false + ) + ) + // For humanoid legs + .addLayers( + EquipmentModel.LayerType.HUMANOID_LEGGINGS, + new EquipmentModel.Layer( + // Points to assets/examplemod/textures/entity/equipment/copper/inner.png + ResourceLocation.fromNamespaceAndPath("examplemod", "copper/inner"), + Optional.empty(), + false + ), + new EquipmentModel.Layer( + // Points to assets/examplemod/textures/entity/equipment/copper/inner_overlay.png + ResourceLocation.fromNamespaceAndPath("examplemod", "copper/inner_overlay"), + Optional.of(new EquipmentModel.Dyeable(Optional.of(0x7683DE))), + false + ) + ) + // For wolf armor + .addLayers( + EquipmentModel.LayerType.WOLF_BODY, + // Base texture + new EquipmentModel.Layer( + // Points to assets/examplemod/textures/entity/equipment/copper/wolf.png + ResourceLocation.fromNamespaceAndPath("examplemod", "copper/wolf"), + Optional.empty(), + // When true, uses the texture passed into the layer renderer instead + true + ) + ) + // For horse armor + .addLayers( + EquipmentModel.LayerType.HORSE_BODY, + // Base texture + new EquipmentModel.Layer( + // Points to assets/examplemod/textures/entity/equipment/copper/horse.png + ResourceLocation.fromNamespaceAndPath("examplemod", "copper/horse"), + Optional.empty(), + true + ) + ) + .build() + ); + } + + @Override + public CompletableFuture run(CachedOutput cache) { + Map map = new HashMap<>(); + this.add((name, model) -> { + if (map.putIfAbsent(name, model) != null) { + throw new IllegalStateException("Tried to register equipment model twice for id: " + name); + } + }); + return DataProvider.saveAll(cache, EquipmentModel.CODEC, this.pathProvider, map); + } + + @Override + public String getName() { + return "Equipment Model Definitions: " + MOD_ID; + } +} + +// Listening to the mod event bus +@SubscribeEvent +public static void gatherData(GatherDataEvent event) { + PackOutput output = generator.getPackOutput(); + + // Other providers here + event.getGenerator().addProvider( + event.includeClient(), + new MyEquipmentModelProvider(output) + ); +} +``` + + + + +## Equipment Rendering + +The equipment models are rendered via the `EquipmentLayerRenderer` in the render function of an `EntityRenderer` or one of its `RenderLayer`s. `EquipmentLayerRenderer` is obtained as part of the render context via `EntityRendererProvider.Context#getEquipmentRenderer`. If the `EquipmentModel`s are required, they are also available via `EntityRendererProvider.Context#getEquipmentModels`. + +By default, the following layers render the associated `EquipmentModel.LayerType`: + +| `LayerType` | `RenderLayer` | Used by | +|:-------------------:|:--------------------:|:---------------------------------------------------------------| +| `HUMANOID` | `HumanoidArmorLayer` | Player, humanoid mobs (e.g., zombies, skeletons), armor stands | +| `HUMANOID_LEGGINGS` | `HumanoidArmorLayer` | Player, humanoid mobs (e.g., zombies, skeletons), armor stands | +| `WINGS` | `WingsLayer` | Player, humanoid mobs (e.g., zombies, skeletons), armor stands | +| `WOLF_BODY` | `WolfArmorLayer` | Wolf | +| `HORSE_BODY` | `HorseArmorLayer` | Horse | +| `LLAMA_BODY` | `LlamaDecorLayer` | Llama, trader llama | + +`EquipmentLayerRenderer` has only one method to render the equipment layers, aptly named `renderLayers`: + +```java +// In some render method where EquipmentLayerRenderer equipmentLayerRenderer is a field +this.equipmentLayerRenderer.renderLayers( + // The layer type to render + EquipmentModel.LayerType.HUMANOID, + // The model id representing the EquipmentModel JSON + // This would be set in the `EQUIPPABLE` data component via `model` + stack.get(DataComponents.EQUIPPABLE).model().orElseThrow(), + // The model to apply the textures to + // These are usually separate models from the entity model + // and are separate ModelLayers linking to a LayerDefinition + model, + // The item stack representing the item being rendered as a model + // This is only used to get the dyeable, foil, and armor trim information + stack, + // The pose stack used to render the model in the correct location + poseStack, + // The source of the buffers to get the vertex consumer of the render type + bufferSource, + // The packed light texture + lighting, + // An absolute path of the texture to render when use_player_texture is true for one of the layer if not null + // Represents an absolute location within the assets folder + ResourceLocation.fromNamespaceAndPath("examplemod", "textures/other_texture.png") +); +``` + +[item]: index.md +[datacomponents]: datacomponents.md +[enchantment]: ../resources/server/enchantments/index.md#enchantment-costs-and-levels +[registering]: ../concepts/registries.md#methods-for-registering +[rendering]: #equipement-rendering +[respack]: ../resources/index.md#assets +[tag]: ../resources/server/tags.md +[tinting]: ../resources/client/models/index.md#tinting diff --git a/docs/items/consumables.md b/docs/items/consumables.md new file mode 100644 index 000000000..7c22b1eac --- /dev/null +++ b/docs/items/consumables.md @@ -0,0 +1,368 @@ +--- +sidebar_position: 3 +--- +# Consumables + +Consumables are [items][item] which can be used over a period of time, 'consuming' them in the process. Anything that can be eaten or drunk in Minecraft is a consumable of some kind. + +## The `Consumable` Data Component + +Any item that can be consumed has the [`DataComponents#CONSUMABLE` component][datacomponent]. The backing record `Consumable` defines how the item is consumed and what effects to apply after consumption. + +A `Consumable` can be created either by directly calling the record constructor or via `Consumable#builder`, which sets the defaults for each field, followed by `build` once finished: + +- `consumeSeconds` - A `float` representing the number of seconds needed to fully consume the item. `Item#finishUsingItem` is called after the alloted time passes. Defaults to 1.6 seconds, or 32 ticks. +- `animation` - Sets the [`ItemUseAnimation`][animation] to play while the item is being used. Defaults to `ItemUseAnimation#EAT`. +- `sound` - Sets the [`SoundEvent`][sound] to play while consuming the item. This must be a `Holder` instance. Defaults to `SoundEvents#GENERIC_EAT`. + - If a vanilla instance is not a `Holder`, a `Holder` wrapped version can be obtained by calling `BuiltInRegistries.SOUND_EVENT.wrapAsHolder(soundEvent)`. +- `soundAfterConsume` - Sets the [`SoundEvent`][sound] to player once the item has finished being consumed. This delegates to the [`PlaySoundConsumeEffect`][consumeeffect]. +- `hasConsumeParticles` - When `true`, spawns item [particles] every four ticks and once the item is fully consumed. Defauts to `true`. +- `onConsume` - Adds a [`ConsumeEffect`][consumeeffect] to apply once the item has fully been consumed via `Item#finishUsingItem`. + +Vanilla provides some consumables within their `Consumables` class, such as `#defaultFood` for [food] items and `#defaultDrink` for [potions] and milk buckets. + +The `Consumable` component can be added by calling `Item.Properties#component`: + +```java +// Assume there is some DeferredRegister.Items ITEMS +public static final DeferredItem CONSUMABLE = ITEMS.registerSimpleItem( + "consumable", + new Item.Properties().component( + DataComponents.CONSUMABLE, + Consumable.builder() + // Spend 2 seconds, or 40 ticks, to consume + .consumeSeconds(2f) + // Sets the animation to play while consuming + .animation(ItemUseAnimation.BLOCK) + // Play sound while consuming every tick + .sound(SoundEvents.ARMOR_EQUIP_CHAIN) + // Play sound once finished consuming + .soundAfterConsume(SoundEvents.BREEZE_WIND_CHARGE_BURST) + // Don't show particles while eating + .hasConsumeParticles(false) + .onConsume( + // When finished consuming, applies the effects with a 30% chance + new ApplyStatusEffectsConsumeEffect(new MobEffectInstance(MobEffects.HUNGER, 600, 0), 0.3F) + ) + // Can have multiple + .onConsume( + // Teleports the entity randomly in a 50 block radius + new TeleportRandomlyConsumeEffect(100f) + ) + .build() + ) +); +``` + +### `ConsumeEffect` + +When a consumable has finished being used, you may want to trigger some kind of logic to execute like adding a potion effect. These are handled by `ConsumeEffect`s, which are added to the `Consumable` by calling `Consumable.Builder#onConsume`. + +A list of vanilla effects can be found in `ConsumeEffect`. + +Every `ConsumeEffect` has two methods: `getType`, which specifies the registry object `ConsumeEffect.Type`; and `apply`, which is called on the item when it has been fully consumed. `apply` takes three arguments: the `Level` the consuming entity is in, the `ItemStack` the consumable was called on, and the `LivingEntity` consuming the object. When the effect is successfully applied, the method returns `true`, or `false` if it failed. + +A `ConsumeEffect` can be created by implementing the interface and [registering] the `ConsumeEffect.Type` with the associated `MapCodec` and `StreamCodec` to `BuiltInRegistries#CONSUME_EFFECT_TYPE`: + +```java +public record UsePortalConsumeEffect(ResourceKey level) + implements ConsumeEffect, Portal { + + @Override + public boolean apply(Level level, ItemStack stack, LivingEntity entity) { + if (entity.canUsePortal(false)) { + entity.setAsInsidePortal(this, entity.blockPosition()); + + // Can successfully use portal + return true; + } + + // Cannot use portal + return false; + } + + @Override + public ConsumeEffect.Type getType() { + // Set to registered object + return USE_PORTAL.get(); + } + + @Override + @Nullable + public TeleportTransition getPortalDestination(ServerLevel level, Entity entity, BlockPos pos) { + // Set teleport location + } +} + +// In some registrar class +// Assume there is some DeferredRegister> CONSUME_EFFECT_TYPES +public static final Supplier> USE_PORTAL = + CONSUME_EFFECT_TYPES.register("use_portal", () -> new ConsumeEffect.Type<>( + ResourceKey.codec(Registries.DIMENSION).optionalFieldOf("dimension") + .xmap(UsePortalConsumeEffect::new, UsePortalConsumeEffect::level), + ResourceKey.streamCodec(Registries.DIMENSION) + .map(UsePortalConsumeEffect::new, UsePortalConsumeEffect::level) + )); + +// For some Item.Properties that is adding a CONSUMABLE component +Consumable.builder() + .onConsume( + new UsePortalConsumeEffect(Level.END) + ) + .build(); +``` + +### `ItemUseAnimation` + +`ItemUseAnimation` is functionally an enum which doesn't define anything besides its id and name. Its uses are hardcoded into `ItemHandRenderer#renderArmWithItem` for first person and `PlayerRenderer#getArmPose` for third person. As such, simply creating a new `ItemUseAnimation` will only function similarly to `ItemUseAnimation#NONE`. + +To apply some animation, you need to implement `IClientItemExtensions#applyForgeHandTransform` for first person and/or `IClientItemExtensions#getArmPose` for third person rendering. + +#### Creating the `ItemUseAnimation` + +First, let's create a new `ItemUseAnimation`. This is done using the [extensible enum][extensibleenum] system: + +```json5 +{ + "entries": [ + { + "enum": "net/minecraft/world/item/ItemUseAnimation", + "name": "EXAMPLEMOD_ITEM_USE_ANIMATION", + "constructor": "(ILjava/lang/String;)V", + "parameters": [ + // The id, should always be -1 + -1, + // The name, should be a unique identifier + "examplemod:item_use_animation" + ] + } + ] +} +``` + +Then we can get the enum constant via `valueOf`: + +```java +public static final ItemUseAnimation EXAMPLE_ANIMATION = ItemUseAnimation.valueOf("EXAMPLEMOD_ITEM_USE_ANIMATION"); +``` + +From there, we can then start applying the transforms. To do this, we must create a new `IClientItemExtensions`, implement our desired methods, and register it via `RegisterClientExtensionsEvent` on the [**mod event bus**][modbus]: + +```java +public class ConsumableClientItemExtensions implements IClientItemExtensions { + // Implement methods here +} + +// In some event listener class +@SubscribeEvent +public static void registerClientExtensions(RegisterClientExtensionsEvent event) { + event.registerItem( + // The instance of the item extensions + new ConsumableClientItemExtensions(), + // A vararg of items that use this + CONSUMABLE + ) +} +``` + +#### First Person + +The first person transform, which all consumables have, is implemented via `IClientItemExtensions#applyForgeHandTransform`: + +```java +public class ConsumableClientItemExtensions implements IClientItemExtensions { + + // ... + + @Override + public boolean applyForgeHandTransform( + PoseStack poseStack, LocalPlayer player, HumanoidArm arm, ItemStack itemInHand, + float partialTick, float equipProcess, float swingProcess + ) { + // We first need to check if the item is being used and has our animation + HumanoidArm usingArm = entity.getUsedItemHand() == InteractionHand.MAIN_HAND + ? entity.getMainArm() + : entity.getMainArm().getOpposite(); + if ( + entity.isUsingItem() && entity.getUseItemRemainingTicks() > 0 + && usingArm == arm && itemInHand.getUseAnimation() == EXAMPLE_ANIMATION + ) { + // Apply transformations to pose stack (translate, scale, mulPose) + // ... + return true; + } + + // Do nothing + return false; + } +} +``` + +#### Third Person + +The third person transforms, which all but `EAT` and `DRINK` have special logic for, is implemented via `IClientItemExtensions#getArmPose`, where `HumanoidModel.ArmPose` can also be extended for a custom transform. + +As an `ArmPose` requries a lambda as part of its constructor, an `EnumProxy` reference must be used: + +```json5 +{ + "entries": [ + { + "name": "EXAMPLEMOD_ITEM_USE_ANIMATION", + // ... + }, + { + "enum": "net/minecraft/client/model/HumanoidModel$ArmPose", + "name": "EXAMPLEMOD_ARM_POSE", + "constructor": "(ZLnet/neoforged/neoforge/client/IArmPoseTransformer;)V", + "parameters": { + // Point to class where the proxy is located + // Should be separate as this is a client only class + "class": "example/examplemod/client/MyClientEnumParams", + // The field name of the enum proxy + "field": "CUSTOM_ARM_POSE" + } + } + ] +} +``` + +```java +// Create the enum parameters +public class MyClientEnumParams { + public static final EnumProxy CUSTOM_ARM_POSE = new EnumProxy<>( + HumanoidModel.ArmPose.class, + // Whether the pose uses both arms + false, + // The pose transformer + (IArmPoseTransformer) MyClientEnumParams::applyCustomModelPose + ); + + private static void applyCustomModelPose( + HumanoidModel model, HumanoidRenderState state, HumanoidArm arm + ) { + // Apply model transforms here + // ... + } +} + +// In some client only class +public static final HumanoidModel.ArmPose EXAMPLE_POSE = HumanoidModel.ArmPose.valueOf("EXAMPLEMOD_ARM_POSE"); +``` + +Then, the arm pose is set via `IClientItemExtensions#getArmPose`: + +```java +public class ConsumableClientItemExtensions implements IClientItemExtensions { + + // ... + + @Override + public HumanoidModel.ArmPose getArmPose( + LivingEntity entity, InteractionHand hand, ItemStack stack + ) { + // We first need to check if the item is being used and has our animation + if ( + entity.isUsingItem() && entity.getUseItemRemainingTicks() > 0 + && entity.getUsedItemHand() == hand + && itemInHand.getUseAnimation() == EXAMPLE_ANIMATION + ) { + // Return pose to apply + return EXAMPLE_POSE; + } + + // Otherwise return null + return null; + } +} +``` + +### Overriding Sounds on Entity + +Sometimes, an entity may want to play a different sound while consuming an item. In those instances, the `LivingEntity` instance can implement `Consumable.OverrideConsumeSound` and have `getConsumeSound` return the `SoundEvent` they want their entity to play. + +```java +public class MyEntity extends LivingEntity implements Consumable.OverrideConsumeSound { + + // ... + + @Override + public SoundEvent getConsumeSound(ItemStack stack) { + // Return the sound to play + } +} +``` + +## `ConsumableListener` + +While consumables and effects that are applied after consumption are useful, sometimes the properties of an effect need to be externally available as other [data components][datacomponents]. For example, cats and wolves also eat [food] and query its nutrition, or item with potion contents query its color for rendering. In these instances, data components implement `ConsumableListener` to provide consumption logic. + +A `ConsumableListener` only has one method: `#onConsume`, which takes in the current level, the entity consuming the item, the item being consumed, and the `Consumable` instance on the item. `onConsume` is called during `Item#finishUsingItem` when the item has been fully consumed. + +Adding your own `ConsumableListener` is simply [registering a new data component][datacompreg] and implementing `ConsumableListener`. + +```java +public record MyConsumableListener() implements ConsumableListener { + + @Override + public void onConsume( + Level level, LivingEntity entity, ItemStack stack, Consumable consumable + ) { + // Do things here + } +} +``` + +### Food + +Food is one type of `ConsumableListener` that is part of the hunger system. All of the functionality for food items is already handled within the `Item` class, so simply adding the `FoodProperties` to `DataComponents#FOOD` along with a consumable is all that's needed. There is a helper method called `food` which takes in the `FoodProperties` and the `Consumable` object, or `Consumables#DEFAULT_FOOD` if none is specified. + +`FoodProperties` can be created either by directly calling the record constructor or via `new FoodProperties.Builder()`, followed by `build` once finished: + +- `nutrition` - Sets how many hunger points are restored. Counts in half hunger points, so for example, Minecraft's steak restores 8 hunger points. +- `saturationModifier` - The saturation modifier used in calculating the [saturation value][hunger] restored when eating this food. The calculation is `min(2 * nutrition * saturationModifier, playerNutrition)`, meaning that using `0.5` will make the effective saturation value the same as the nutrition value. +- `alwaysEdible` - Whether this item can always be eaten, even if the hunger bar is full. `false` by default, `true` for golden apples and other items that provide bonuses beyond just filling the hunger bar. + +```java +// Assume there is some DeferredRegister.Items ITEMS +public static final DeferredItem FOOD = ITEMS.registerSimpleItem( + "food", + new Item.Properties().food( + new FoodProperties.Builder() + // Heals 1.5 hearts + .nutrition(3) + // Carrot is 0.3 + // Raw Cod is 0.1 + // Cooked Chicken is 0.6 + // Cooked Beef is 0.8 + // Golden Aple is 1.2 + .saturationModifier(0.3f) + // When set, the food can alway be eaten even with + // a full hunger bar. + .alwaysEdible() + ) +); +``` + +For examples, or to look at the various values used by Minecraft, have a look at the `Foods` class. + +To get the `FoodProperties` for an item, call `ItemStack.get(DataComponents.FOOD)`. This may return null, since not every item is edible. To determine whether an item is edible, null-check the result of the `getFoodProperties` call. + +### Potion Contents + +The contents of a [potion][potions] via `PotionContents` is another `ConsumableListener` whose effects are applied on consumption. They contain an optional potion to apply, an optional tint for the potion color, a list of custom [`MobEffectInstance`s][mobeffectinstance] to apply alongside the potion, and an optional translation key to use when getting the stack name. The modder needs to override `Item#getName` if not a subtype of `PotionItem`. + +[animation]: #itemuseanimation +[consumeeffect]: #consumeeffect +[datacomponent]: datacomponents.md +[datacompreg]: datacomponents.md#creating-custom-data-components +[extensibleenum]: ../advanced/extensibleenums.md +[food]: #food +[hunger]: https://minecraft.wiki/w/Hunger#Mechanics +[item]: index.md +[modbus]: ../concepts/events.md#event-buses +[mobeffectinstance]: mobeffects.md#mobeffectinstances +[particles]: ../resources/client/particles.md +[potions]: mobeffects.md#potions +[sound]: ../resources/client/sounds.md#creating-soundevents +[registering]: ../concepts/registries.md#methods-for-registering diff --git a/docs/items/datacomponents.md b/docs/items/datacomponents.md new file mode 100644 index 000000000..36a008417 --- /dev/null +++ b/docs/items/datacomponents.md @@ -0,0 +1,331 @@ +--- +sidebar_position: 2 +--- + +# Data Components + +Data components are key-value pairs within a map used to store data on an `ItemStack`. Each piece of data, such as firework explosions or tools, are stored as actual objects on the stack, making the values visible and operable without having to dynamically transform a general encoded instance (e.g., `CompoundTag`, `JsonElement`). + +## `DataComponentType` + +Each data component has an associated `DataComponentType`, where `T` is the component value type. The `DataComponentType` represents a key to reference the stored component value along with some codecs to handle reading and writing to the disk and network, if desired. + +A list of existing components can be found within `DataComponents`. + +### Creating Custom Data Components + +The component value associated with the `DataComponentType` must implement `hashCode` and `equals` and should be considered **immutable** when stored. + +:::note +Component values can very easily be implemented using a record. Record fields are immutable and implement `hashCode` and `equals`. +::: + +```java +// A record example +public record ExampleRecord(int value1, boolean value2) {} + +// A class example +public class ExampleClass { + + private final int value1; + // Can be mutable, but care needs to be taken when using + private boolean value2; + + public ExampleClass(int value1, boolean value2) { + this.value1 = value1; + this.value2 = value2; + } + + @Override + public int hashCode() { + return Objects.hash(this.value1, this.value2); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else { + return obj instanceof ExampleClass ex + && this.value1 == ex.value1 + && this.value2 == ex.value2; + } + } +} +``` + +A standard `DataComponentType` can be created via `DataComponentType#builder` and built using `DataComponentType.Builder#build`. The builder contains three settings: `persistent`, `networkSynchronized`, `cacheEncoding`. + +`persistent` specifies the [`Codec`][codec] used to read and write the component value to disk. `networkSynchronized` specifies the `StreamCodec` used to read and write the component across the network. If `networkSynchronized` is not specified, then the `Codec` provided in `persistent` will be wrapped and used as the [`StreamCodec`][streamcodec]. + +:::warning +Either `persistent` or `networkSynchronized` must be provided in the builder; otherwise, a `NullPointerException` will be thrown. If no data should be sent across the network, then set `networkSynchronized` to `StreamCodec#unit`, providing the default component value. +::: + +`cacheEncoding` caches the encoding result of the `Codec` such that any subsequent encodes uses the cached value if the component value hasn't changed. This should only be used if the component value is expected to rarely or never change. + +`DataComponentType` are registry objects and must be [registered]. + +```java +// Using ExampleRecord(int, boolean) +// Only one Codec and/or StreamCodec should be used below +// Multiple are provided for an example + +// Basic codec +public static final Codec BASIC_CODEC = RecordCodecBuilder.create(instance -> + instance.group( + Codec.INT.fieldOf("value1").forGetter(ExampleRecord::value1), + Codec.BOOL.fieldOf("value2").forGetter(ExampleRecord::value2) + ).apply(instance, ExampleRecord::new) +); +public static final StreamCodec BASIC_STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.INT, ExampleRecord::value1, + ByteBufCodecs.BOOL, ExampleRecord::value2, + ExampleRecord::new +); + +// Unit stream codec if nothing should be sent across the network +public static final StreamCodec UNIT_STREAM_CODEC = StreamCodec.unit(new ExampleRecord(0, false)); + + +// In another class +// The specialized DeferredRegister.DataComponents simplifies data component registration and avoids some generic inference issues with the `DataComponentType.Builder` within a `Supplier` +public static final DeferredRegister.DataComponents REGISTRAR = DeferredRegister.createDataComponents(Registries.DATA_COMPONENT_TYPE, "examplemod"); + +public static final Supplier> BASIC_EXAMPLE = REGISTRAR.registerComponentType( + "basic", + builder -> builder + // The codec to read/write the data to disk + .persistent(BASIC_CODEC) + // The codec to read/write the data across the network + .networkSynchronized(BASIC_STREAM_CODEC) +); + +/// Component will not be saved to disk +public static final Supplier> TRANSIENT_EXAMPLE = REGISTRAR.registerComponentType( + "transient", + builder -> builder.networkSynchronized(BASIC_STREAM_CODEC) +); + +// No data will be synced across the network +public static final Supplier> NO_NETWORK_EXAMPLE = REGISTRAR.registerComponentType( + "no_network", + builder -> builder + .persistent(BASIC_CODEC) + // Note we use a unit stream codec here + .networkSynchronized(UNIT_STREAM_CODEC) +); +``` + +## The Component Map + +All data components are stored within a `DataComponentMap`, using the `DataComponentType` as the key and the object as the value. `DataComponentMap` functions similarly to a read-only `Map`. As such, there are methods to `#get` an entry given its `DataComponentType` or provide a default if not present (via `#getOrDefault`). + +```java +// For some DataComponentMap map + +// Will get dye color if component is present +// Otherwise null +@Nullable +DyeColor color = map.get(DataComponents.BASE_COLOR); +``` + +### `PatchedDataComponentMap` + +As the default `DataComponentMap` only provides methods for read-based operations, write-based operations are supported using the subclass `PatchedDataComponentMap`. This includes `#set`ting the value of a component or `#remove`ing it altogether. + +`PatchedDataComponentMap` stores changes using a prototype and patch map. The prototype is a `DataComponentMap` that contains the default components and their +values this map should have. The patch map is a map of `DataComponentType`s to `Optional` values that contain the changes made to the default components. + +```java +// For some PatchedDataComponentMap map + +// Sets the base color to white +map.set(DataComponents.BASE_COLOR, DyeColor.WHITE); + +// Removes the base color by +// - Removing the patch if no default is provided +// - Setting an empty optional if there is a default +map.remove(DataComponents.BASE_COLOR); +``` + +:::danger +Both the prototype and patch map are part of the hash code for the `PatchedDataComponentMap`. As such, any component values within the map should be treated as **immutable**. Always call `#set` or one of its referring methods discussed below after modifying the value of a data component. +::: + +## The Component Holder + +All instances that can hold data components implement `DataComponentHolder`. `DataComponentHolder` is effectively a delegate to the read-only methods within `DataComponentMap`. + +```java +// For some ItemStack stack + +// Delegates to 'DataComponentMap#get' +@Nullable +DyeColor color = stack.get(DataComponents.BASE_COLOR); +``` + +### `MutableDataComponentHolder` + +`MutableDataComponentHolder` is an interface provided by NeoForge to support write-based methods to the component map. All implementations within Vanilla and NeoForge store data components using a `PatchedDataComponentMap`, so the `#set` and `#remove` methods also have delegates with the same name. + +In addition, `MutableDataComponentHolder` also provides an `#update` method which handles getting the component value or the provided default if none is set, operating on the value, and then setting it back to the map. The operator is either a `UnaryOperator`, which takes in the component value and returns the component value, or a `BiFunction`, which takes in the component value and another object and returns the component value. + +```java +// For some ItemStack stack + +FireworkExplosion explosion = stack.get(DataComponents.FIREWORK_EXPLOSION); + +// Modifying the component value +explosion = explosion.withFadeColors(new IntArrayList(new int[] {1, 2, 3})); + +// Since we modified the component value, 'set' should be called afterward +stack.set(DataComponents.FIREWORK_EXPLOSION, explosion); + +// Update the component value (calls 'set' internally) +stack.update( + DataComponents.FIREWORK_EXPLOSION, + // Default value if no component value is present + FireworkExplosion.DEFAULT, + // Return a new FireworkExplosion to set + explosion -> explosion.withFadeColors(new IntArrayList(new int[] {4, 5, 6})) +); + +stack.update( + DataComponents.FIREWORK_EXPLOSION, + // Default value if no component value is present + FireworkExplosion.DEFAULT, + // An object that is supplied to the function + new IntArrayList(new int[] {7, 8, 9}), + // Return a new FireworkExplosion to set + FireworkExplosion::withFadeColors +); +``` + +## Adding Default Data Components to Items + +Although data components are stored on an `ItemStack`, a map of default components can be set on an `Item` to be passed to the `ItemStack` as a prototype when constructed. A component can be added to the `Item` via `Item.Properties#component`. + +```java +// For some DeferredRegister.Items REGISTRAR +public static final Item COMPONENT_EXAMPLE = REGISTRAR.register("component", + // register is used over other overloads as the DataComponentType has not been registered yet + registryName -> new Item( + new Item.Properties() + .setId(ResourceKey.create(Registries.ITEM, registryName)) + .component(BASIC_EXAMPLE.get(), new ExampleRecord(24, true)) + ) +); +``` + +If the data component should be added to an existing item that belongs to Vanilla or another mod, then `ModifyDefaultComponentEvent` should be listened for on the [**mod event bus**][modbus]. The event provides the `modify` and `modifyMatching` methods which allows the `DataComponentPatch.Builder` to be modified for the associated items. The builder can either `#set` components or `#remove` existing components. + +```java +// Listened to on the mod event bus +@SubscribeEvent +public void modifyComponents(ModifyDefaultComponentsEvent event) { + // Sets the component on melon seeds + event.modify(Items.MELON_SEEDS, builder -> + builder.set(BASIC_EXAMPLE.get(), new ExampleRecord(10, false)) + ); + + // Removes the component for any items that have a crafting remainder + event.modifyMatching( + item -> !item.getCraftingRemainder().isEmpty(), + builder -> builder.remove(DataComponents.BUCKET_ENTITY_DATA) + ); +} +``` + +## Using Custom Component Holders + +To create a custom data component holder, the holder object simply needs to implement `MutableDataComponentHolder` and implement the missing methods. The holder object must contain a field representing the `PatchedDataComponentMap` to implement the associated methods. + +```java +public class ExampleHolder implements MutableDataComponentHolder { + + private int data; + private final PatchedDataComponentMap components; + + // Overloads can be provided to supply the map itself + public ExampleHolder() { + this.data = 0; + this.components = new PatchedDataComponentMap(DataComponentMap.EMPTY); + } + + @Override + public DataComponentMap getComponents() { + return this.components; + } + + @Nullable + @Override + public T set(DataComponentType componentType, @Nullable T value) { + return this.components.set(componentType, value); + } + + @Nullable + @Override + public T remove(DataComponentType componentType) { + return this.components.remove(componentType); + } + + @Override + public void applyComponents(DataComponentPatch patch) { + this.components.applyPatch(patch); + } + + @Override + public void applyComponents(DataComponentMap components) { + this.components.setAll(components); + } + + // Other methods +} +``` + +### `DataComponentPatch` and Codecs + +To persist components to disk or send information across the network, the holder could send the entire `DataComponentMap`. However, this is generally a waste of information as any defaults will already be present wherever the data is sent to. So, instead, we use a `DataComponentPatch` to send the associated data. `DataComponentPatch`es only contain the patch information of the component map without any defaults. The patches are then applied to the prototype in the receiver's location. + +A `DataComponentPatch` can be created from a `PatchedDataComponentMap` via `#patch`. Likewise, `PatchedDataComponentMap#fromPatch` can construct a `PatchedDataComponentMap` given the prototype `DataComponentMap` and a `DataComponentPatch`. + +```java +public class ExampleHolder implements MutableDataComponentHolder { + + public static final Codec CODEC = RecordCodecBuilder.create(instance -> + instance.group( + Codec.INT.fieldOf("data").forGetter(ExampleHolder::getData), + DataCopmonentPatch.CODEC.optionalFieldOf("components", DataComponentPatch.EMPTY).forGetter(holder -> holder.components.asPatch()) + ).apply(instance, ExampleHolder::new) + ); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.INT, ExampleHolder::getData, + DataComponentPatch.STREAM_CODEC, holder -> holder.components.asPatch(), + ExampleHolder::new + ); + + // ... + + public ExampleHolder(int data, DataComponentPatch patch) { + this.data = data; + this.components = PatchedDataComponentMap.fromPatch( + // The prototype map to apply to + DataComponentMap.EMPTY, + // The associated patches + patch + ); + } + + // ... +} +``` + +[Syncing the holder data across the network][network] and reading/writing the data to disk must be done manually. + +[registered]: ../concepts/registries.md +[codec]: ../datastorage/codecs.md +[modbus]: ../concepts/events.md#event-buses +[network]: ../networking/payload.md +[streamcodec]: ../networking/streamcodecs.md diff --git a/docs/items/index.md b/docs/items/index.md index c0fa0ab51..d4691346c 100644 --- a/docs/items/index.md +++ b/docs/items/index.md @@ -7,11 +7,11 @@ Along with blocks, items are a key component of Minecraft. While blocks make up Before we get further into creating items, it is important to understand what an item actually is, and what distinguishes it from, say, a [block][block]. Let's illustrate this using an example: - In the world, you encounter a dirt block and want to mine it. This is a **block**, because it is placed in the world. (Actually, it is not a block, but a blockstate. See the [Blockstates article][blockstates] for more detailed information.) - - Not all blocks drop themselves when breaking (e.g. leaves), see the article on [loot tables][loottables] for more information. + - Not all blocks drop themselves when breaking (e.g. leaves), see the article on [loot tables][loottables] for more information. - Once you have [mined the block][breaking], it is removed (= replaced with an air block) and the dirt drops. The dropped dirt is an item **entity**. This means that like other entities (pigs, zombies, arrows, etc.), it can inherently be moved by things like water pushing on it, or burned by fire and lava. - Once you pick up the dirt item entity, it becomes an **item stack** in your inventory. An item stack is, simply put, an instance of an item with some extra information, such as the stack size. - Item stacks are backed by their corresponding **item** (which is what we're creating). Items hold [data components][datacomponents] that contains the default information all items stacks are initialized to (for example, every iron sword has a max durability of 250), while item stacks can modify those data components, allowing two different stacks for the same item to have different information (for example, one iron sword has 100 uses left, while another iron sword has 200 uses left). For more information on what is done through items and what is done through item stacks, read on. - - The relationship between items and item stacks is roughly the same as between [blocks][block] and [blockstates][blockstates], in that a blockstate is always backed by a block. It's not a really accurate comparison (item stacks aren't singletons, for example), but it gives a good basic idea about what the concept is here. + - The relationship between items and item stacks is roughly the same as between [blocks][block] and [blockstates][blockstates], in that a blockstate is always backed by a block. It's not a really accurate comparison (item stacks aren't singletons, for example), but it gives a good basic idea about what the concept is here. ## Creating an Item @@ -19,34 +19,40 @@ Now that we understand what an item is, let's create one! Like with basic blocks, for basic items that need no special functionality (think sticks, sugar, etc.), the `Item` class can be used directly. To do so, during registration, instantiate `Item` with a `Item.Properties` parameter. This `Item.Properties` parameter can be created using `Item.Properties#of`, and it can be customized by calling its methods: +- `setId` - Sets the resource key of the item. + - This **must** be set on every item; otherwise, an exception will be thrown. +- `overrideDescription` - Sets the translation key of the item. The created `Component` is stored in `DataComponents#ITEM_NAME`. +- `useBlockDescriptionPrefix` - Convenience helper that calls `overrideDescription` with the translation key `block..`. This should be called on any `BlockItem`. +- `overrideModel` - Sets the `ResourceLocation` representing the item model and expands to `assets//models/item/.json`. The `ResourceLocation` is stored in `DataComponents#ITEM_MODEL`. +- `requiredFeatures` - Sets the required feature flags for this item. This is mainly used for vanilla's feature locking system in minor versions. It is discouraged to use this, unless you're integrating with a system locked behind feature flags by vanilla. - `stacksTo` - Sets the max stack size (via `DataComponents#MAX_STACK_SIZE`) of this item. Defaults to 64. Used e.g. by ender pearls or other items that only stack to 16. -- `durability` - Sets the durability (via `DataComponents#MAX_DAMAGE`) of this item and the initial damge to 0 (via `DataComponents#DAMAGE`). Defaults to 0, which means "no durability". For example, iron tools use 250 here. Note that setting the durability automatically locks the max stack size to 1. -- `craftRemainder` - Sets the crafting remainder of this item. Vanilla uses this for filled buckets that leave behind empty buckets after crafting. +- `durability` - Sets the durability (via `DataComponents#MAX_DAMAGE`) of this item and the initial damage to 0 (via `DataComponents#DAMAGE`). Defaults to 0, which means "no durability". For example, iron tools use 250 here. Note that setting the durability automatically locks the max stack size to 1. - `fireResistant` - Makes item entities that use this item immune to fire and lava (via `DataComponents#FIRE_RESISTANT`). Used by various netherite items. -- `setNoRepair` - Disables anvil and crafting grid repairing for this item. Unused in vanilla. - `rarity` - Sets the rarity of this item (via `DataComponents#RARITY`). Currently, this simply changes the item's color. `Rarity` is an enum consisting of the four values `COMMON` (white, default), `UNCOMMON` (yellow), `RARE` (aqua) and `EPIC` (light purple). Be aware that mods may add more rarity types. -- `requiredFeatures` - Sets the required feature flags for this item. This is mainly used for vanilla's feature locking system in minor versions. It is discouraged to use this, unless you're integrating with a system locked behind feature flags by vanilla. +- `setNoRepair` - Disables anvil and crafting grid repairing for this item. Unused in vanilla. +- `jukeboxPlayable` - Sets the resource key of the datapack `JukeboxSong` to play when inserted into a jukebox. - `food` - Sets the [`FoodProperties`][food] of this item (via `DataComponents#FOOD`). For examples, or to look at the various values used by Minecraft, have a look at the `Items` class. -### Food +### Remainders and Cooldowns + +Items may have additional properties that are applied when being used or prevent the item from being used for a set time: -The `Item` class provides default functionality for food items, meaning you don't need a separate class for that. To make your item edible, all you need to do is set the `FoodProperties` on it through the `food` method in `Item.Properties`. +- `craftRemainder` - Sets the crafting remainder of this item. Vanilla uses this for filled buckets that leave behind empty buckets after crafting. +- `usingConvertsTo` - Sets the item to return after the item is finished being used via `Item#use`, `IItemExtension#finishUsingItem`, or `Item#releaseUsing`. The `ItemStack` is stored on `DataComponents#USE_REMAINDER`. +- `useCooldown` - Sets the number of seconds before the item can be used again (via `DataComponents#USE_COOLDOWN`). -`FoodProperties` are created using a `FoodProperties.Builder`. You can then set various properties on it: +### Tools and Armor -- `nutrition` - Sets how many hunger points are restored. Counts in half hunger points, so for example, Minecraft's steak restores 8 hunger points. -- `saturationMod` - The saturation modifier used in calculating the [saturation value][hunger] restored when eating this food. The calculation is `min(2 * nutrition * saturationMod, playerNutrition)`, meaning that using `0.5` will make the effective saturation value the same as the nutrition value. -- `alwaysEdible` - Whether this item can always be eaten, even if the hunger bar is full. `false` by default, `true` for golden apples and other items that provide bonuses beyond just filling the hunger bar. -- `fast` - Whether fast eating should be enabled for this food. `false` by default, `true` for dried kelp in vanilla. -- `effect` - Adds a [`MobEffectInstance`][mobeffectinstance] to apply when eating this item. The second parameter denotes the probability of the effect being applied; for example, Rotten Flesh has an 80% chance (= 0.8) of applying the Hunger effect when eaten. This method comes in two variants; you should use the one that takes in a supplier (the other one directly takes a mob effect instance and is deprecated by NeoForge due to classloading issues). -- `usingConvertsTo` - Sets the item this will turn into after being used. -- `build` - Once you've set everything you want to set, call `build` to get a `FoodProperties` object for further use. +Some items act like [tools] and [armor]. These are constructed via a series of item properties, with only some usage being delegated to their associated classes: -For examples, or to look at the various values used by Minecraft, have a look at the `Foods` class. +- `enchantable` - Sets the maximum [enchantment] value of the stack, allowing the item to be enchanted (via `DataComponents#ENCHANTABLE`). +- `repairable` - Sets the item or tag that can be used to repair the durability of this item (via `DataComponents#REPAIRABLE`). Must have durability components and not `DataComponents#UNBREAKABLE`. +- `equippable` - Sets the slot the item can be equipped to (via `DataComponents#EQUIPPABLE`). +- `equippableUnswappable` - Same as `equippable`, but disables quick swapping via the use item button (default right-click). -To get the `FoodProperties` for an item, call `Item#getFoodProperties(ItemStack, LivingEntity)`. This may return null, since not every item is edible. To determine whether an item is edible, null-check the result of the `getFoodProperties` call. +More information can be found on their relevant pages. ### More Functionality @@ -61,21 +67,21 @@ All registries use `DeferredRegister` to register their contents, and items are ```java public static final DeferredRegister.Items ITEMS = DeferredRegister.createItems(ExampleMod.MOD_ID); -public static final Supplier EXAMPLE_ITEM = ITEMS.registerItem( - "example_item", - Item::new, // The factory that the properties will be passed into. - new Item.Properties() // The properties to use. +public static final DeferredItem EXAMPLE_ITEM = ITEMS.registerItem( + "example_item", + Item::new, // The factory that the properties will be passed into. + new Item.Properties() // The properties to use. ); ``` -Internally, this will simply call `ITEMS.register("example_item", () -> new Item(new Item.Properties()))` by applying the properties parameter to the provided item factory (which is commonly the constructor). +Internally, this will simply call `ITEMS.register("example_item", registryName -> new Item(new Item.Properties().setId(ResourceKey.create(Registries.ITEM, registryName))))` by applying the properties parameter to the provided item factory (which is commonly the constructor). The id is set on the properties. If you want to use `Item::new`, you can leave out the factory entirely and use the `simple` method variant: ```java -public static final Supplier EXAMPLE_ITEM = ITEMS.registerSimpleItem( - "example_item", - new Item.Properties() // The properties to use. +public static final DeferredItem EXAMPLE_ITEM = ITEMS.registerSimpleItem( + "example_item", + new Item.Properties() // The properties to use. ); ``` @@ -84,21 +90,41 @@ This does the exact same as the previous example, but is slightly shorter. Of co Both of these methods also have overloads that omit the `new Item.Properties()` parameter: ```java -public static final Supplier EXAMPLE_ITEM = ITEMS.registerItem("example_item", Item::new); +public static final DeferredItem EXAMPLE_ITEM = ITEMS.registerItem("example_item", Item::new); + // Variant that also omits the Item::new parameter -public static final Supplier EXAMPLE_ITEM = ITEMS.registerSimpleItem("example_item"); +public static final DeferredItem EXAMPLE_ITEM = ITEMS.registerSimpleItem("example_item"); ``` -Finally, there's also shortcuts for block items: +Finally, there's also shortcuts for block items. Along with `setId`, these also call `useBlockDescriptionPrefix` to set the translation key to the one used for a block: ```java -public static final Supplier EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpleBlockItem("example_block", ExampleBlocksClass.EXAMPLE_BLOCK, new Item.Properties()); +public static final DeferredItem EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpleBlockItem( + "example_block", + ExampleBlocksClass.EXAMPLE_BLOCK, + new Item.Properties() +); + // Variant that omits the properties parameter: -public static final Supplier EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpleBlockItem("example_block", ExampleBlocksClass.EXAMPLE_BLOCK); +public static final DeferredItem EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpleBlockItem( + "example_block", + ExampleBlocksClass.EXAMPLE_BLOCK +); + // Variant that omits the name parameter, instead using the block's registry name: -public static final Supplier EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpleBlockItem(ExampleBlocksClass.EXAMPLE_BLOCK, new Item.Properties()); +public static final DeferredItem EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpleBlockItem( + // Must be an instance of `Holder` + // DeferredBlock also works + ExampleBlocksClass.EXAMPLE_BLOCK, + new Item.Properties() +); + // Variant that omits both the name and the properties: -public static final Supplier EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpleBlockItem(ExampleBlocksClass.EXAMPLE_BLOCK); +public static final DeferredItem EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpleBlockItem( + // Must be an instance of `Holder` + // DeferredBlock also works + ExampleBlocksClass.EXAMPLE_BLOCK +); ``` :::note @@ -117,7 +143,7 @@ Like with blocks and blockstates, most places where you'd expect an `Item` actua An `ItemStack` consists of three major parts: -- The `Item` it represents, obtainable through `ItemStack#getItem`. +- The `Item` it represents, obtainable through `ItemStack#getItem`, or `getItemHolder` for `Holder`. - The stack size, typically between 1 and 64, obtainable through `getCount` and changeable through `setCount` or `shrink`. - The [data components][datacomponents] map, where stack-specific data is stored. Obtainable through `getComponents`. The components values are typically accessed and mutated via `has`, `get`, `set`, `update`, and `remove`. @@ -143,14 +169,14 @@ In many situations, for example [recipes], item stacks need to be represented as ```json5 { - // The item ID. Required. - "id": "minecraft:dirt", - // The item stack count. Optional, defaults to 1. - "count": 4, - // A map of data components. Optional, defaults to an empty map. - "components": { - "minecraft:enchantment_glint_override": true - } + // The item ID. Required. + "id": "minecraft:dirt", + // The item stack count [1, 99]. Optional, defaults to 1. + "count": 4, + // A map of data components. Optional, defaults to an empty map. + "components": { + "minecraft:enchantment_glint_override": true + } } ``` @@ -210,20 +236,20 @@ public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.r It is also possible to implement `ItemLike` on your custom objects. Simply override `#asItem` and you're good to go. +[armor]: armor.md [block]: ../blocks/index.md [blockstates]: ../blocks/states.md [breaking]: ../blocks/index.md#breaking-a-block [creativetabs]: #creative-tabs -[datacomponents]: ./datacomponents.mdx +[datacomponents]: datacomponents.md [datagen]: ../resources/index.md#data-generation -[food]: #food -[hunger]: https://minecraft.wiki/w/Hunger#Mechanics +[enchantment]: ../resources/server/enchantments/index.md#enchantment-costs-and-levels +[food]: consumables.md#food [interactionpipeline]: interactionpipeline.md [loottables]: ../resources/server/loottables/index.md -[mobeffectinstance]: mobeffects.md#mobeffectinstances [modbus]: ../concepts/events.md#event-buses [recipes]: ../resources/server/recipes/index.md [registering]: ../concepts/registries.md#methods-for-registering [resources]: ../resources/index.md#assets [sides]: ../concepts/sides.md -[wikicomponents]: https://minecraft.wiki/w/Data_component_format +[tools]: tools.md diff --git a/docs/items/interactionpipeline.md b/docs/items/interactionpipeline.md index ff9f56ddc..1c3a31122 100644 --- a/docs/items/interactionpipeline.md +++ b/docs/items/interactionpipeline.md @@ -26,48 +26,50 @@ When you right-click anywhere in the world, a number of things happen, depending - `PlayerInteractEvent.RightClickBlock` is fired. If the event is canceled, the pipeline ends. You may also specifically deny only block or item usage in this event. - `IItemExtension#onItemUseFirst` is called. If it returns a definitive result, the pipeline ends. - If the player is not sneaking and the event does not deny block usage, `UseItemOnBlockEvent` is fired. If the event is canceled, the cancellation result is used. Otherwise, `Block#useItemOn` is called. If it returns a definitive result, the pipeline ends. - - If the `ItemInteractionResult` is `PASS_TO_DEFAULT_BLOCK_INTERACTION` and the executing hand is the main hand, then `Block#useWithoutItem` is called. If it returns a definitive result, the pipeline ends. + - If the `InteractionResult` is `TRY_WITH_EMPTY_HAND` and the executing hand is the main hand, then `Block#useWithoutItem` is called. If it returns a definitive result, the pipeline ends. - If the event does not deny item usage, `Item#useOn` is called. If it returns a definitive result, the pipeline ends. - `Item#use` is called. If it returns a definitive result, the pipeline ends. - The above process runs a second time, this time with the off hand instead of the main hand. -## Result Types +## `InteractionResult` -There are three different types of results: `InteractionResult`s, `ItemInteractionResult`s, and `InteractionResultHolder`s. `InteractionResult` is used most of the time, only `Item#use` uses `InteractionResultHolder`, and only `BlockBehaviour#useItemOn` and `CauldronInteraction#interact` use `ItemInteractionResult`. +`InteractionResult` is a sealed interface that respresents the result of some interaction between an item or an empty hand and some object (e.g. entities, blocks, etc.). The interface is broken into four records, where there are six potential default states. -`InteractionResult` is an enum consisting of five values: `SUCCESS`, `CONSUME`, `CONSUME_PARTIAL`, `PASS` and `FAIL`. Additionally, the method `InteractionResult#sidedSuccess` is available, which returns `SUCCESS` on the server and `CONSUME` on the client. +First there is `InteractionResult.Success`, which indicates that the operation should be considered sucessful, ending the pipeline. A successful state has two parameters: the `SwingSource`, which indicates whether the entity should swing on the respective [logical side][side]; and the `InteractionResult.ItemContext`, which holds whether the interaction was caused by a held item, and what the held item transformed into after use. The swing source is determined by one of the default states: `InteractionResult#SUCCESS` for client swing, `InteractionResult#SUCCESS_SERVER` for server swing, and `InteractionResult#CONSUME` for no swing. The item context is set via `Success#heldItemTransformedTo` if the `ItemStack` changed, or `withoutItem` if there wasn't an interaction between the held item and the object. The default sets there was an item interaction but no transformation. -`InteractionResultHolder` is a wrapper around `InteractionResult` that adds additional context for `T`. `T` can be anything, but in 99.99 percent of cases, it is an `ItemStack`. `InteractionResultHolder` provides wrapper methods for the enum values (`#success`, `#consume`, `#pass` and `#fail`), as well as `#sidedSuccess`, which calls `#success` on the server and `#consume` on the client. +```java +// In some method that returns an interaction result -`ItemInteractionResult` is a parallel to `InteractionResult` specifically for when an item is used on a block. It is an enum of six values: `SUCCESS`, `CONSUME`, `CONSUME_PARTIAL`, `PASS_TO_DEFAULT_BLOCK_INTERACTION`, `SKIP_DEFAULT_BLOCK_INTERACTION`, and `FAIL`. Each `ItemInteractionResult` can be mapped to a `InteractionResult` via `#result`; `PASS_TO_DEFAULT_BLOCK_INTERACTION`, `SKIP_DEFAULT_BLOCK_INTERACTION` both represent `InteractionResult#PASS`. Similarly, `#sidedSucess` also exists for `ItemInteractionResult`. +// Item in hand will turn into an apple +return InteractionResult.SUCCESS.heldItemTransformedTo(new ItemStack(Items.APPLE)); +``` -Generally, the different values mean the following: +:::note +`SUCCESS` and `SUCCESS_SERVER` should generally never be used in the same method. If the client has enough information to determine when to swing, then `SUCCESS` should always be used. Otherwise, if it relies on server information not present on the client, `SUCCESS_SERVER` should be used. +::: -- `InteractionResult#sidedSuccess` (or `InteractionResultHolder#sidedSuccess` / `ItemInteractionResult#sidedSucess` where needed) should be used if the operation should be considered successful, and you want the arm to swing. The pipeline will end. -- `InteractionResult#SUCCESS` (or `InteractionResultHolder#success` / `ItemInteractionResult#SUCCESS` where needed) should be used if the operation should be considered successful, and you want the arm to swing, but only on one side. Only use this if you want to return a different value on the other logical side for whatever reason. The pipeline will end. -- `InteractionResult#CONSUME` (or `InteractionResultHolder#consume` / `ItemInteractionResult#CONSUME` where needed) should be used if the operation should be considered successful, but you do not want the arm to swing. The pipeline will end. -- `InteractionResult#CONSUME_PARTIAL` is mostly identical to `InteractionResult#CONSUME`, the only difference is in its usage in [`Item#useOn`][itemuseon]. - - `ItemInteractionResult#CONSUME_PARTIAL` is similar within its usage in `BlockBehaviour#useItemOn`. -- `InteractionResult.FAIL` (or `InteractionResultHolder#fail` / `ItemInteractionResult#FAIL` where needed) should be used if the item functionality should be considered failed and no further interaction should be performed. The pipeline will end. This can be used everywhere, but it should be used with care outside of `Item#useOn` and `Item#use`. In many cases, using `InteractionResult.PASS` makes more sense. -- `InteractionResult.PASS` (or `InteractionResultHolder#pass` where needed) should be used if the operation should be considered neither successful nor failed. The pipeline will continue. This is the default behavior (unless otherwise specified). - - `ItemInteractionResult#PASS_TO_DEFAULT_BLOCK_INTERACTION` allows `BlockBehaviour#useWithoutItem` to be called for the mainhand while `#SKIP_DEFAULT_BLOCK_INTERACTION` prevents the method from executing altogether. `#PASS_TO_DEFAULT_BLOCK_INTERACTION` is the default behavior (unless otherwise specified). +Then there is `InteractionResult.Fail`, implemented by `InteractionResult#FAIL`, which indicates that the operation should be considered failed, allowing no further interaction to occur. The pipeline will end. This can be used anywhere, but it should be used with care outside of `Item#useOn` and `Item#use`. In many cases, using `InteractionResult#PASS` makes more sense. -Some methods have special behavior or requirements, which are explained in the below chapters. - -## `IItemExtension#onItemUseFirst` +Finally, there is `InteractionResult.Pass` and `InteractionResult.TryWithEmptyHandInteraction`, implemented by `InteractionResult#PASS` and `InteractionResult#TRY_WITH_EMPTY_HAND` respectively. These records indicate when an operation should be considered neither successful or failed, and the pipeline should continue. `PASS` is the default behavior for all `InteractionResult` methods except `BlockBehaviour#useItemOn`, which returns `TRY_WITH_EMPTY_HAND`. More specifically, if `BlockBehaviour#useItemOn` returns anything but `TRY_WITH_EMPTY_HAND`, `BlockBehaviour#useWithoutItem` will not be called regardless of if the item is in the main hand. -`InteractionResult#sidedSuccess` and `InteractionResult.CONSUME` don't have an effect here. Only `InteractionResult.SUCCESS`, `InteractionResult.FAIL` or `InteractionResult.PASS` should be used here. +Some methods have special behavior or requirements, which are explained in the below chapters. ## `Item#useOn` -If you want the operation to be considered successful, but you do not want the arm to swing or an `ITEM_USED` stat point to be awarded, use `InteractionResult.CONSUME_PARTIAL`. +If you want the operation to be considered successful, but you do not want the arm to swing or an `ITEM_USED` stat point to be awarded, use `InteractionResult#CONSUME` and calling `#withoutItem`. + +```java +// In Item#useOn +return InteractionResult.CONSUME.withoutItem(); +``` ## `Item#use` -This is the only instance where the return type is `InteractionResultHolder`. The resulting `ItemStack` in the `InteractionResultHolder` replaces the `ItemStack` the usage was initiated with, if it has changed. +This is the only instance where the transformed `ItemStack` is used from a `Success` variant (`SUCCESS`, `SUCCESS_SERVER`, `CONSUME`). The resulting `ItemStack` set by `Success#heldItemTransformedTo` replaces the `ItemStack` the usage was initiated with, if it has changed. -The default implementation of `Item#use` returns `InteractionResultHolder#consume` when the item is edible and the player can eat the item (because they are hungry, or because the item is always edible), `InteractionResultHolder#fail` when the item is edible but the player cannot eat the item, and `InteractionResultHolder#pass` if the item is not edible. +The default implementation of `Item#use` returns `InteractionResult#CONSUME` when the item is edible (has `DataComponents#CONSUMABLE`) and the player can eat the item (because they are hungry, or because the item is always edible) and `InteractionResult#FAIL` when the item is edible (has `DataComponents#CONSUMABLE`) but the player cannot eat the item. If the item is equippable (has `DataComponents#EQUIPPABLE`), then it returns `InteractionResult#SUCCESS` on swap with the held item replaced by the swaped item (via `heldItemTransformedTo`), or `InteractionResult#FAIL` if the enchantment on the armor has the `EnchantmentEffectComponents#PREVENT_ARMOR_CHANGE` component. Otherwise `InteractionResult#PASS` is returned. -Returning `InteractionResultHolder#fail` here while considering the main hand will prevent offhand behavior from running. If you want offhand behavior to run (which you usually want), return `InteractionResultHolder#pass` instead. +Returning `InteractionResult#FAIL` here while considering the main hand will prevent offhand behavior from running. If you want offhand behavior to run (which you usually want), return `InteractionResult#PASS` instead. [itemuseon]: #itemuseon +[side]: ../concepts/sides.md#the-logical-side diff --git a/docs/items/mobeffects.md b/docs/items/mobeffects.md index 87cf3cf69..9e1469324 100644 --- a/docs/items/mobeffects.md +++ b/docs/items/mobeffects.md @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 6 --- # Mob Effects & Potions @@ -8,7 +8,7 @@ Status effects, sometimes known as potion effects and referred to in-code as `Mo ## Terminology - A `MobEffect` affects an entity every tick. Like [blocks][block] or [items][item], `MobEffect`s are registry objects, meaning they must be [registered][registration] and are singletons. - - An **instant mob effect** is a special kind of mob effect that is designed to be applied for one tick. Vanilla has two instant effects, Instant Health and Instant Harming. + - An **instant mob effect** is a special kind of mob effect that is designed to be applied for one tick. Vanilla has two instant effects, Instant Health and Instant Harming. - A `MobEffectInstance` is an instance of a `MobEffect`, with a duration, amplifier and some other properties set (see below). `MobEffectInstance`s are to `MobEffect`s what [`ItemStack`s][itemstack] are to `Item`s. - A `Potion` is a collection of `MobEffectInstance`s. Vanilla mainly uses potions for the four potion items (read on), however, they can be applied to any item at will. It is up to the item if and how the item then uses the potion set on it. - A **potion item** is an item that is meant to have a potion set on it. This is an informal term, the vanilla `PotionItem` class has nothing to do with this (it refers to the "normal" potion item). Minecraft currently has four potion items: potions, splash potions, lingering potions, and tipped arrows; however more may be added by mods. @@ -53,14 +53,14 @@ public class MyMobEffect extends MobEffect { } ``` -Like all registry objects, `MobEffect`s must be registered, like so: +Like all registry objects, `MobEffect`s must be [registered][registration], like so: ```java -//MOB_EFFECTS is a DeferredRegister -public static final Supplier MY_MOB_EFFECT = MOB_EFFECTS.register("my_mob_effect", () -> new MyMobEffect( +// MOB_EFFECTS is a DeferredRegister +public static final Holder MY_MOB_EFFECT = MOB_EFFECTS.register("my_mob_effect", () -> new MyMobEffect( //Can be either BENEFICIAL, NEUTRAL or HARMFUL. Used to determine the potion tooltip color of this effect. MobEffectCategory.BENEFICIAL, - //The color of the effect particles. + //The color of the effect particles in RGB format. 0xffffff )); ``` @@ -68,7 +68,7 @@ public static final Supplier MY_MOB_EFFECT = MOB_EFFECTS.register(" The `MobEffect` class also provides default functionality for adding attribute modifiers to affected entities. For example, the speed effect adds an attribute modifier for movement speed. Effect attribute modifiers are added like so: ```java -public static final Supplier MY_MOB_EFFECT = MOB_EFFECTS.register("my_mob_effect", () -> new MyMobEffect(...) +public static final Holder MY_MOB_EFFECT = MOB_EFFECTS.register("my_mob_effect", () -> new MyMobEffect(...) .addAttributeModifier(Attributes.ATTACK_DAMAGE, ResourceLocation.fromNamespaceAndPath("examplemod", "effect.strength"), 2.0, AttributeModifier.Operation.ADD_VALUE) ); ``` @@ -90,7 +90,7 @@ public class MyMobEffect extends InstantenousMobEffect { } ``` -Then, register your effect like normal. +Then, [register][registration] your effect like normal. ### Events @@ -111,8 +111,8 @@ MobEffectInstance instance = new MobEffectInstance( MobEffects.REGENERATION, // The duration to use, in ticks. Defaults to 0 if not specified. 500, - // The amplifier to use. This is the "strength" of the effect, i.e. Strength I, Strength II, etc; - // starting at 0. Defaults to 0 if not specified. + // The amplifier to use. This is the "strength" of the effect, i.e. Strength I, Strength II, etc. + // Must be between 0 and 255 (inclusive). Defaults to 0 if not specified. 0, // Whether the effect is an "ambient" effect, meaning it is being applied by an ambient source, // of which Minecraft currently has the beacon and the conduit. Defaults to false if not specified. @@ -132,17 +132,17 @@ Several constructor overloads are available, omitting the last 1-5 parameters, r ### Using `MobEffectInstance`s -A `MobEffectInstance` can be added to an entity like so: +A `MobEffectInstance` can be added to a `LivingEntity` like so: ```java MobEffectInstance instance = new MobEffectInstance(...); -entity.addEffect(instance); +livingEntity.addEffect(instance); ``` -Similarly, `MobEffectInstance` can also be removed from an entity. Since a `MobEffectInstance` overwrites pre-existing `MobEffectInstance`s of the same `MobEffect` on the entity, there can only ever be one `MobEffectInstance` per `MobEffect` and entity. As such, specifying the `MobEffect` suffices when removing: +Similarly, `MobEffectInstance` can also be removed from an `LivingEntity`. Since a `MobEffectInstance` overwrites pre-existing `MobEffectInstance`s of the same `MobEffect` on the entity, there can only ever be one `MobEffectInstance` per `MobEffect` and entity. As such, specifying the `MobEffect` suffices when removing: ```java -entity.removeEffect(MobEffects.REGENERATION); +livingEntity.removeEffect(MobEffects.REGENERATION); ``` :::info @@ -155,12 +155,17 @@ entity.removeEffect(MobEffects.REGENERATION); ```java //POTIONS is a DeferredRegister -public static final Supplier MY_POTION = POTIONS.register("my_potion", () -> new Potion(new MobEffectInstance(MY_MOB_EFFECT, 3600))); +public static final Holder MY_POTION = POTIONS.register("my_potion", registryName -> new Potion( + // The suffix applied to the potion + registryName.getPath(), + // The effects used by the potion + new MobEffectInstance(MY_MOB_EFFECT, 3600) +)); ``` -Note that the parameter of `new Potion` is a vararg. This means that you can add as many effects as you want to the potion. This also means that it is possible to create empty potions, i.e. potions that don't have any effects. Simply call `new Potion()` and you're done! (This is how vanilla adds the `awkward` potion, by the way.) +The name of the potion is the first constructor argument. It is used as the suffix for a translation key; for example, the long and strong potion variants in vanilla use this to have the same names as their base variant. -The name of the potion can be passed as the first constructor argument. It is used for translating; for example, the long and strong potion variants in vanilla use this to have the same names as their base variant. The name is not required; if it is omitted, the name will be queried from the registry. +The `MobEffectInstance` parameter of `new Potion` is a vararg. This means that you can add as many effects as you want to the potion. This also means that it is possible to create empty potions, i.e. potions that don't have any effects. Simply call `new Potion()` and you're done! (This is how vanilla adds the `awkward` potion, by the way.) The `PotionContents` class offers various helper methods related to potion items. Potion item store their `PotionContents` via `DataComponent#POTION_CONTENTS`. @@ -195,5 +200,5 @@ public static void registerBrewingRecipes(RegisterBrewingRecipesEvent event) { [events]: ../concepts/events.md [item]: index.md [itemstack]: index.md#itemstacks -[registration]: ../concepts/registries.md +[registration]: ../concepts/registries.md#methods-for-registering [uuidgen]: https://www.uuidgenerator.net/version4 diff --git a/docs/items/tools.md b/docs/items/tools.md index 97cef82f0..67d76b22f 100644 --- a/docs/items/tools.md +++ b/docs/items/tools.md @@ -1,7 +1,7 @@ --- -sidebar_position: 3 +sidebar_position: 4 --- -# Tools & Armor +# Tools Tools are [items][item] whose primary use is to break [blocks][block]. Many mods add new tool sets (for example copper tools) or new tool types (for example hammers). @@ -11,71 +11,77 @@ A tool set typically consists of five items: a pickaxe, an axe, a shovel, a hoe ```text Item -- TieredItem - - DiggerItem +- DiggerItem - AxeItem - HoeItem - PickaxeItem - ShovelItem - - SwordItem +- SwordItem ``` -`TieredItem` is a class that contains helpers for items with a certain `Tier` (read on). `DiggerItem` contains helpers for items that are designed to break blocks. Note that other items usually considered tools, such as shears, are not included in this hierarchy. Instead, they directly extend `Item` and hold the breaking logic themselves. +Tools are almost completely implemented through seven [data components][datacomponents]: -To create a standard set of tools, you must first define a `Tier`. For reference values, see Minecraft's `Tiers` enum. This example uses copper tools, you can use your own material here and adjust the values as needed. +- `DataComponents#MAX_DAMAGE` and `#DAMAGE` for durability +- `#MAX_STACK_SIZE` to set the stack size to `1` +- `#REPAIRABLE` for reepairing a tool in an anvil +- `#ENCHANTABLE` for the maximum [enchanting][enchantment] value +- `#ATTRIBUTE_MODIFIERS` for attack damage and attack speed +- `#TOOL` for mining information + +For `DiggerItem` and `SwordItem`, they are simply delegates that set up the components via the utility record `ToolMaterial`. Note that other items usually considered tools, such as shears, are not included in this hierarchy. Instead, they directly extend `Item` and hold the breaking logic themselves. + +To create a standard set of tools using a `DiggerItem` or `SwordItem`, you must first define a `ToolMaterial`. Reference values can be found within the constants in `ToolMaterial`. This example uses copper tools, you can use your own material here and adjust the values as needed. ```java // We place copper somewhere between stone and iron. -public static final Tier COPPER_TIER = new SimpleTier( - // The tag that determines what blocks this tool cannot break. See below for more information. +public static final ToolMaterial COPPER_MATERIAL = new ToolMaterial( + // The tag that determines what blocks this material cannot break. See below for more information. MyBlockTags.INCORRECT_FOR_COPPER_TOOL, - // Determines the durability of the tier. + // Determines the durability of the material. // Stone is 131, iron is 250. 200, - // Determines the mining speed of the tier. Unused by swords. + // Determines the mining speed of the material. Unused by swords. // Stone uses 4, iron uses 6. 5f, // Determines the attack damage bonus. Different tools use this differently. For example, swords do (getAttackDamageBonus() + 4) damage. // Stone uses 1, iron uses 2, corresponding to 5 and 6 attack damage for swords, respectively; our sword does 5.5 damage now. 1.5f, - // Determines the enchantability of the tier. This represents how good the enchantments on this tool will be. + // Determines the enchantability of the material. This represents how good the enchantments on this tool will be. // Gold uses 22, we put copper slightly below that. 20, - // Determines the repair ingredient of the tier. Use a supplier for lazy initializing. - () -> Ingredient.of(Tags.Items.INGOTS_COPPER) + // The tag that determines what items can repair this material. + Tags.Items.INGOTS_COPPER ); ``` -Now that we have our `Tier`, we can use it for registering tools. All tool constructors have the same four parameters. +Now that we have our `ToolMaterial`, we can use it for [registering] tools. All tool constructors have the same four parameters. ```java -//ITEMS is a DeferredRegister -public static final Supplier COPPER_SWORD = ITEMS.register("copper_sword", () -> new SwordItem( - // The tier to use. - COPPER_TIER, - // The item properties. We don't need to set the durability here because TieredItem handles that for us. - new Item.Properties().attributes( - // There are `createAttributes` methods in either the class or subclass of each DiggerItem - SwordItem.createAttributes( - // The tier to use. - COPPER_TIER, - // The type-specific attack damage bonus. 3 for swords, 1.5 for shovels, 1 for pickaxes, varying for axes and hoes. - 3, - // The type-specific attack speed modifier. The player has a default attack speed of 4, so to get to the desired - // value of 1.6f, we use -2.4f. -2.4f for swords, -3f for shovels, -2.8f for pickaxes, varying for axes and hoes. - -2.4f, - ) - ) -)); -public static final Supplier COPPER_AXE = ITEMS.register("copper_axe", () -> new AxeItem(...)); -public static final Supplier COPPER_PICKAXE = ITEMS.register("copper_pickaxe", () -> new PickaxeItem(...)); -public static final Supplier COPPER_SHOVEL = ITEMS.register("copper_shovel", () -> new ShovelItem(...)); -public static final Supplier COPPER_HOE = ITEMS.register("copper_hoe", () -> new HoeItem(...)); +// ITEMS is a DeferredRegister.Items +public static final DeferredItem COPPER_SWORD = ITEMS.registerItem( + "copper_sword", + props -> new SwordItem( + // The material to use. + COPPER_MATERIAL, + // The type-specific attack damage bonus. 3 for swords, 1.5 for shovels, 1 for pickaxes, varying for axes and hoes. + 3, + // The type-specific attack speed modifier. The player has a default attack speed of 4, so to get to the desired + // value of 1.6f, we use -2.4f. -2.4f for swords, -3f for shovels, -2.8f for pickaxes, varying for axes and hoes. + -2.4f, + // The item properties. + props + ) +); + +public static final DeferredItem COPPER_AXE = ITEMS.registerItem("copper_axe", props -> new AxeItem(...)); +public static final DeferredItem COPPER_PICKAXE = ITEMS.registerItem("copper_pickaxe", props -> new PickaxeItem(...)); +public static final DeferredItem COPPER_SHOVEL = ITEMS.registerItem("copper_shovel", props -> new ShovelItem(...)); +public static final DeferredItem COPPER_HOE = ITEMS.registerItem("copper_hoe", props -> new HoeItem(...)); ``` ### Tags -When creating a `Tier`, it is assigned a block [tag][tags] containing blocks that will not drop anything if broken with this tool. For example, the `minecraft:incorrect_for_stone_tool` tag contains blocks like Diamond Ore, and the `minecraft:incorrect_for_iron_tool` tag contains blocks like Obsidian and Ancient Debris. To make it easier to assign blocks to their incorrect mining levels, a tag also exists for blocks that need this tool to be mined. For example, the `minecraft:needs_iron_tool` tag containslike Diamond Ore, and the `minecraft:needs_diamond_tool` tag contains blocks like Obsidian and Ancient Debris. +When creating a `ToolMaterial`, it is assigned a block [tag][tags] containing blocks that will not drop anything if broken with this tool. For example, the `minecraft:incorrect_for_stone_tool` tag contains blocks like Diamond Ore, and the `minecraft:incorrect_for_iron_tool` tag contains blocks like Obsidian and Ancient Debris. To make it easier to assign blocks to their incorrect mining levels, a tag also exists for blocks that need this tool to be mined. For example, the `minecraft:needs_iron_tool` tag contains blocks like Diamond Ore, and the `minecraft:needs_diamond_tool` tag contains blocks like Obsidian and Ancient Debris. You can reuse one of the incorrect tags for your tool if you're fine with that. For example, if we wanted our copper tools to just be more durable stone tools, we'd pass in `BlockTags#INCORRECT_FOR_STONE_TOOL`. @@ -85,7 +91,7 @@ Alternatively, we can create our own tag, like so: // This tag will allow us to add these blocks to the incorrect tags that cannot mine them public static final TagKey NEEDS_COPPER_TOOL = TagKey.create(BuiltInRegistries.BLOCK.key(), ResourceLocation.fromNamespaceAndPath(MOD_ID, "needs_copper_tool")); -// This tag will be passed into our tier +// This tag will be passed into our material public static final TagKey INCORRECT_FOR_COPPER_TOOL = TagKey.create(BuiltInRegistries.BLOCK.key(), ResourceLocation.fromNamespaceAndPath(MOD_ID, "incorrect_for_cooper_tool")); ``` @@ -93,18 +99,18 @@ And then, we populate our tag. For example, let's make copper able to mine gold ```json5 { - "values": [ - "minecraft:gold_block", - "minecraft:raw_gold_block", - "minecraft:gold_ore", - "minecraft:deepslate_gold_ore", - "minecraft:redstone_ore", - "minecraft:deepslate_redstone_ore" - ] + "values": [ + "minecraft:gold_block", + "minecraft:raw_gold_block", + "minecraft:gold_ore", + "minecraft:deepslate_gold_ore", + "minecraft:redstone_ore", + "minecraft:deepslate_redstone_ore" + ] } ``` -Then, for our tag to pass into the tier, we can provide a negative constraint for any tools that are incorrect for stone tools but within our copper tools tag. The tag file is located at `src/main/resources/data/mod_id/tags/block/incorrect_for_cooper_tool.json`: +Then, for our tag to pass into the material, we can provide a negative constraint for any tools that are incorrect for stone tools but within our copper tools tag. The tag file is located at `src/main/resources/data/mod_id/tags/block/incorrect_for_cooper_tool.json`: ```json5 { @@ -117,124 +123,53 @@ Then, for our tag to pass into the tier, we can provide a negative constraint fo } ``` -Finally, we can pass our tag into our tier creation, as seen above. +Finally, we can pass our tag into our material instance, as seen above. If you want to check if a tool can make a block state drop its blocks, call `Tool#isCorrectForDrops`. The `Tool` can be obtained by calling `ItemStack#get` with `DataComponents#TOOL`. ## Custom Tools -Custom tools can be created by adding a `Tool` [data component][datacomponents] (via `DataComponents#TOOL`) to the list of default components on your item via `Item.Properties#component`. `DiggerItem` is an implementation which takes in a `Tier`, as explained above, to construct the `Tool`. `DiggerItem` also provides a convenience method called `#createAttributes` to supply to `Item.Properties#attributes` for your tool, such as the modified attack damage and attack speed. +Custom tools can be created by adding a `Tool` [data component][datacomponents] (via `DataComponents#TOOL`) to the list of default components on your item via `Item.Properties#component`. `DiggerItem` is an implementation which takes in a `ToolMaterial`, as explained above, to construct the `Tool`, along with a few other basic components such as attributes and durability. A `Tool` contains a list of `Tool.Rule`s, the default mining speed when holding the tool (`1` by default), and the amount of damage the tool should take when mining a block (`1` by default). A `Tool.Rule` contains three pieces of information: a `HolderSet` of blocks to apply the rule to, an optional speed at which to mine the blocks in the set, and an optional boolean at which to determine whether these blocks can drop from this tool. If the optional are not set, then the other rules will be checked. The default behavior if all rules fail is the default mining speed and that the block cannot be dropped. :::note -A `HolderSet` can be created from a `TagKey` via `Registry#getOrCreateTag`. +A `HolderSet` can be created from a `TagKey` via `Registry#getOrThrow`. ::: -Creating a multitool-like item (i.e. an item that combines two or more tools into one, e.g. an axe and a pickaxe as one item) or any tool-like does not need to extend any of the existing `TieredItem`s. It simply can be implemented using a combination of the following parts: +Creating any tool or multitool-like item (i.e. an item that combines two or more tools into one, e.g. an axe and a pickaxe as one item) does not need to extend any of the existing `DiggerItem`s or `SwordItem`. It simply can be implemented using a combination of the following parts: - Adding a `Tool` with your own rules by setting `DataComponents#TOOL` via `Item.Properties#component`. - Adding attributes to the item (e.g. attack damage, attack speed) via `Item.Properties#attributes`. +- Adding item durability via `Item.Properties#durability`. +- Allowing the item to be repaired via `Item.Properties#repariable`. +- Allowing the item to be enchanted via `Item.Properties#enchantable`. - Overriding `IItemExtension#canPerformAction` to determine what [`ItemAbility`s][itemability] the item can perform. - Calling `IBlockExtension#getToolModifiedState` if you want your item to modify the block state on right click based on the `ItemAbility`s. -- Adding your tool to some of the `minecraft:enchantable/*` tags so that your item can have certain enchantments applied to it. +- Adding your tool to some of the `minecraft:enchantable/*` `ItemTags` so that your item can have certain enchantments applied to it. + +:::note +The only logic that has no alternative to using a `DiggerItem` or `SwordItem` base is when mobs replace their current held item via `Mob#canReplaceCurrentItem`. Mobs will always check for an instance of `SwordItem`, followed by `BowItem`, `CrossbowItem`, `ArmorItem`, and `DiggerItem`, with all other items not considered for specialized logic. +::: ## `ItemAbility`s `ItemAbility`s are an abstraction over what an item can and cannot do. This includes both left-click and right-click behavior. NeoForge provides default `ItemAbility`s in the `ItemAbilities` class: - Digging abilities. These exist for all four `DiggerItem` types as mentioned above, as well as sword and shears digging. -- Axe right-click abilities for stripping (logs), scraping (oxidized copper) and unwaxing (waxed copper). -- Shear abilities for harvesting (honeycombs), carving (pumpkins) and disarming (tripwires). -- Abilities for shovel flattening (dirt paths), sword sweeping, hoe tilling, shield blocking, and fishing rod casting. +- Axe right-click abilities for stripping (logs), scraping (oxidized copper), and unwaxing (waxed copper). +- Shovel right-click abilities for flattening (dirt paths) and dousing (campfires). +- Shear abilities for harvesting (honeycombs), removing armor (armored wolves), carving (pumpkins), disarming (tripwires), and trimming (stop plants from growing). +- Abilities for sword sweeping, hoe tilling, shield blocking, fishing rod casting, trident throwing, brush brushing, and firestarter lighting. To create your own `ItemAbility`s, use `ItemAbility#get` - it will create a new `ItemAbility` if needed. Then, in a custom tool type, override `IItemExtension#canPerformAction` as needed. To query if an `ItemStack` can perform a certain `ItemAbility`, call `IItemStackExtension#canPerformAction`. Note that this works on any `Item`, not just tools. -## Armor - -Similar to tools, armor uses a tier system (although a different one). What is called `Tier` for tools is called `ArmorMaterial` for armors. Like above, this example shows how to add copper armor; this can be adapted as needed. However, unlike `Tier`s, `ArmorMaterial`s need to be [registered]. For the vanilla values, see the `ArmorMaterials` class. - -```java -// ARMOR_MATERIALS is a DeferredRegister - -// We place copper somewhere between chainmail and iron. -public static final Holder COPPER_ARMOR_MATERIAL = - ARMOR_MATERIALS.register("copper", () -> new ArmorMaterial( - // Determines the defense value of this armor material, depending on what armor piece it is. - Util.make(new EnumMap<>(ArmorItem.Type.class), map -> { - map.put(ArmorItem.Type.BOOTS, 2); - map.put(ArmorItem.Type.LEGGINGS, 4); - map.put(ArmorItem.Type.CHESTPLATE, 6); - map.put(ArmorItem.Type.HELMET, 2); - map.put(ArmorItem.Type.BODY, 4); - }), - // Determines the enchantability of the tier. This represents how good the enchantments on this armor will be. - // Gold uses 25, we put copper slightly below that. - 20, - // Determines the sound played when equipping this armor. - // This is wrapped with a Holder. - SoundEvents.ARMOR_EQUIP_GENERIC, - // Determines the repair item for this armor. - () -> Ingredient.of(Tags.Items.INGOTS_COPPER), - // Determines the texture locations of the armor to apply when rendering - // This can also be specified by overriding 'IItemExtension#getArmorTexture' on your item if the armor texture needs to be more dynamic - List.of( - // Creates a new armor texture that will be located at: - // - 'assets/mod_id/textures/models/armor/copper_layer_1.png' for the outer texture - // - 'assets/mod_id/textures/models/armor/copper_layer_2.png' for the inner texture (only legs) - new ArmorMaterial.Layer( - ResourceLocation.fromNamespaceAndPath(MOD_ID, "copper") - ), - // Creates a new armor texture that will be rendered on top of the previous at: - // - 'assets/mod_id/textures/models/armor/copper_layer_1_overlay.png' for the outer texture - // - 'assets/mod_id/textures/models/armor/copper_layer_2_overlay.png' for the inner texture (only legs) - // 'true' means that the armor material is dyeable; however, the item must also be added to the 'minecraft:dyeable' tag - new ArmorMaterial.Layer( - ResourceLocation.fromNamespaceAndPath(MOD_ID, "copper"), "_overlay", true - ) - ), - // Returns the toughness value of the armor. The toughness value is an additional value included in - // damage calculation, for more information, refer to the Minecraft Wiki's article on armor mechanics: - // https://minecraft.wiki/w/Armor#Armor_toughness - // Only diamond and netherite have values greater than 0 here, so we just return 0. - 0, - // Returns the knockback resistance value of the armor. While wearing this armor, the player is - // immune to knockback to some degree. If the player has a total knockback resistance value of 1 or greater - // from all armor pieces combined, they will not take any knockback at all. - // Only netherite has values greater than 0 here, so we just return 0. - 0 - )); -``` - -And then, we use that armor material in item registration. - -```java -//ITEMS is a DeferredRegister -public static final Supplier COPPER_HELMET = ITEMS.register("copper_helmet", () -> new ArmorItem( - // The armor material to use. - COPPER_ARMOR_MATERIAL, - // The armor type to use. - ArmorItem.Type.HELMET, - // The item properties where we set the durability. - // ArmorItem.Type is an enum of five values: HELMET, CHESTPLATE, LEGGINGS, BOOTS, and BODY. - // BODY is used for non-player entities like wolves or horses. - // Vanilla armor materials determine this by using a base value and multiplying it with a type-specific constant. - // The constants are 13 for BOOTS, 15 for LEGGINGS, 16 for CHESTPLATE, 11 for HELMET, and 16 for BODY. - // If we don't want to use these ratios, we can set the durability normally. - new Item.Properties().durability(ArmorItem.Type.HELMET.getDurability(15)) -)); -public static final Supplier COPPER_CHESTPLATE = ITEMS.register("copper_chestplate", () -> new ArmorItem(...)); -public static final Supplier COPPER_LEGGINGS = ITEMS.register("copper_leggings", () -> new ArmorItem(...)); -public static final Supplier COPPER_BOOTS = ITEMS.register("copper_boots", () -> new ArmorItem(...)); -``` - -When creating your armor texture, it is a good idea to work on top of the vanilla armor texture to see which part goes where. - [block]: ../blocks/index.md -[datacomponents]: ./datacomponents.mdx +[datacomponents]: datacomponents.md +[enchantment]: ../resources/server/enchantments/index.md#enchantment-costs-and-levels [item]: index.md [itemability]: #itemabilitys +[registering]: ../concepts/registries.md#methods-for-registering [tags]: ../resources/server/tags.md -[registered]: ../concepts/registries.md#methods-for-registering diff --git a/docs/legacy/_category_.json b/docs/legacy/_category_.json index 57032bfbb..3a78b370e 100644 --- a/docs/legacy/_category_.json +++ b/docs/legacy/_category_.json @@ -1,4 +1,4 @@ { - "label": "Legacy", - "position": 14 + "label": "Legacy", + "position": 14 } \ No newline at end of file diff --git a/docs/legacy/porting.md b/docs/legacy/porting.md index d26e0a1a1..769c5d1a4 100644 --- a/docs/legacy/porting.md +++ b/docs/legacy/porting.md @@ -2,17 +2,18 @@ Here you can find a list of primers on how to port from old versions to the current version. Some versions are lumped together since that particular version never saw much usage. -| From -> To | Primer | -|:------------------:|:----------------------------------------| -| 1.12 -> 1.13/1.14 | [Primer by williewillus][112to114] | -| 1.14 -> 1.15 | [Primer by williewillus][114to115] | -| 1.15 -> 1.16 | [Primer by 50ap5ud5][115to116] | -| 1.16 -> 1.17 | [Primer by 50ap5ud5][116to117] | -| 1.19.2 -> 1.19.3 | [Primer by ChampionAsh5357][1192to1193] | -| 1.19.3 -> 1.19.4 | [Primer by ChampionAsh5357][1193to1194] | -| 1.19.4 -> 1.20.0 | [Primer by ChampionAsh5357][1194to120] | -| 1.20.4 -> 1.20.5/6 | [Primer by ChampionAsh5357][1204to1205] | -| 1.20.6 -> 1.21 | [Primer by ChampionAsh5357][1206to121] | +| From -> To | Primer | +|:---------------------:|:----------------------------------------| +| 1.12 -> 1.13/1.14 | [Primer by williewillus][112to114] | +| 1.14 -> 1.15 | [Primer by williewillus][114to115] | +| 1.15 -> 1.16 | [Primer by 50ap5ud5][115to116] | +| 1.16 -> 1.17 | [Primer by 50ap5ud5][116to117] | +| 1.19.2 -> 1.19.3 | [Primer by ChampionAsh5357][1192to1193] | +| 1.19.3 -> 1.19.4 | [Primer by ChampionAsh5357][1193to1194] | +| 1.19.4 -> 1.20.0 | [Primer by ChampionAsh5357][1194to120] | +| 1.20.4 -> 1.20.5/6 | [Primer by ChampionAsh5357][1204to1205] | +| 1.20.6 -> 1.21/1.21.1 | [Primer by ChampionAsh5357][1206to121] | +| 1.21.1 -> 1.21.2/3 | [Primer by ChampionAsh5357][1211to1212] | [112to114]: https://gist.github.com/williewillus/353c872bcf1a6ace9921189f6100d09a [114to115]: https://gist.github.com/williewillus/30d7e3f775fe93c503bddf054ef3f93e @@ -23,3 +24,4 @@ Here you can find a list of primers on how to port from old versions to the curr [1194to120]: https://gist.github.com/ChampionAsh5357/cf818acc53ffea6f4387fe28c2977d56 [1204to1205]: https://gist.github.com/ChampionAsh5357/53b04132e292aa12638d339abfabf955 [1206to121]: https://gist.github.com/ChampionAsh5357/d895a7b1a34341e19c80870720f9880f +[1211to1212]: https://github.com/neoforged/.github/blob/main/primers/1.21.2/index.md diff --git a/docs/misc/_category_.json b/docs/misc/_category_.json index 3f9e25c78..366f98727 100644 --- a/docs/misc/_category_.json +++ b/docs/misc/_category_.json @@ -1,4 +1,4 @@ { - "label": "Miscellaneous", - "position": 13 + "label": "Miscellaneous", + "position": 13 } \ No newline at end of file diff --git a/docs/misc/config.md b/docs/misc/config.md index 2f5aa57b4..1c15a1c23 100644 --- a/docs/misc/config.md +++ b/docs/misc/config.md @@ -18,7 +18,12 @@ A configuration can be created using a subtype of `IConfigSpec`. NeoForge implem //Define a field to keep the config and spec for later public static final ExampleConfig CONFIG; public static final ModConfigSpec CONFIG_SPEC; - + +private ExampleConfig(ModConfigSpec.Builder builder) { + // Define properties used by the configuration + // ... +} + //CONFIG and CONFIG_SPEC are both built from the same builder, so we use a static block to seperate the properties static { Pair pair = diff --git a/docs/misc/debugprofiler.md b/docs/misc/debugprofiler.md index f90f99c02..c187c13cf 100644 --- a/docs/misc/debugprofiler.md +++ b/docs/misc/debugprofiler.md @@ -42,9 +42,9 @@ Here is a small explanation of what each part means: The Debug Profiler has basic support for `Entity` and `BlockEntity`. If you would like to profile something else, you may need to manually create your sections like so: ```java -ProfilerFiller#push(yourSectionName : String); +Profiler.get().push("yourSectionName"); //The code you want to profile -ProfilerFiller#pop(); +Profiler.get().pop(); ``` -You can obtain the `ProfilerFiller` instance from a `Level`, `MinecraftServer`, or `Minecraft` instance. + Now you just need to search the results file for your section name. diff --git a/docs/misc/gametest.mdx b/docs/misc/gametest.md similarity index 87% rename from docs/misc/gametest.mdx rename to docs/misc/gametest.md index 5bb5543f9..3912ab044 100644 --- a/docs/misc/gametest.mdx +++ b/docs/misc/gametest.md @@ -18,10 +18,10 @@ A Game Test method is a `Consumer` reference, meaning it takes i ```java public class ExampleGameTests { - @GameTest - public static void exampleTest(GameTestHelper helper) { - // Do stuff - } + @GameTest + public static void exampleTest(GameTestHelper helper) { + // Do stuff + } } ``` @@ -30,11 +30,11 @@ The `@GameTest` annotation also contains members which configure how the game te ```java // In some class @GameTest( - setupTicks = 20L, // The test spends 20 ticks to set up for execution - required = false // The failure is logged but does not affect the execution of the batch + setupTicks = 20L, // The test spends 20 ticks to set up for execution + required = false // The failure is logged but does not affect the execution of the batch ) public static void exampleConfiguredTest(GameTestHelper helper) { - // Do stuff + // Do stuff } ``` @@ -89,10 +89,10 @@ If Game Test methods need to be generated dynamically, a test method generator c ```java public class ExampleGameTests { - @GameTestGenerator - public static Collection exampleTests() { - // Return a collection of TestFunctions - } + @GameTestGenerator + public static Collection exampleTests() { + // Return a collection of TestFunctions + } } ``` @@ -114,15 +114,15 @@ Batch methods are `Consumer` references, meaning they take in a `Se ```java public class ExampleGameTests { - @BeforeBatch(batch = "firstBatch") - public static void beforeTest(ServerLevel level) { - // Perform setup - } - - @GameTest(batch = "firstBatch") - public static void exampleTest2(GameTestHelper helper) { - // Do stuff - } + @BeforeBatch(batch = "firstBatch") + public static void beforeTest(ServerLevel level) { + // Perform setup + } + + @GameTest(batch = "firstBatch") + public static void exampleTest2(GameTestHelper helper) { + // Do stuff + } } ``` @@ -137,7 +137,7 @@ The `@GameTestHolder` annotation registers any test methods within the type (cla ```java @GameTestHolder(MODID) public class ExampleGameTests { - // ... + // ... } ``` @@ -148,13 +148,13 @@ public class ExampleGameTests { ```java // In some class public void registerTests(RegisterGameTestsEvent event) { - event.register(ExampleGameTests.class); + event.register(ExampleGameTests.class); } // In ExampleGameTests @GameTest(templateNamespace = MODID) public static void exampleTest3(GameTestHelper helper) { - // Perform setup + // Perform setup } ``` @@ -187,27 +187,27 @@ The name of the template is determined by `GameTest#template`. If not specified, @GameTestHolder(MODID) public class ExampleGameTests { - // Class name is prepended, template name is not specified - // Template Location at 'modid:examplegametests.exampletest' - @GameTest - public static void exampleTest(GameTestHelper helper) { /*...*/ } - - // Class name is not prepended, template name is not specified - // Template Location at 'modid:exampletest2' - @PrefixGameTestTemplate(false) - @GameTest - public static void exampleTest2(GameTestHelper helper) { /*...*/ } - - // Class name is prepended, template name is specified - // Template Location at 'modid:examplegametests.test_template' - @GameTest(template = "test_template") - public static void exampleTest3(GameTestHelper helper) { /*...*/ } - - // Class name is not prepended, template name is specified - // Template Location at 'modid:test_template2' - @PrefixGameTestTemplate(false) - @GameTest(template = "test_template2") - public static void exampleTest4(GameTestHelper helper) { /*...*/ } + // Class name is prepended, template name is not specified + // Template Location at 'modid:examplegametests.exampletest' + @GameTest + public static void exampleTest(GameTestHelper helper) { /*...*/ } + + // Class name is not prepended, template name is not specified + // Template Location at 'modid:exampletest2' + @PrefixGameTestTemplate(false) + @GameTest + public static void exampleTest2(GameTestHelper helper) { /*...*/ } + + // Class name is prepended, template name is specified + // Template Location at 'modid:examplegametests.test_template' + @GameTest(template = "test_template") + public static void exampleTest3(GameTestHelper helper) { /*...*/ } + + // Class name is not prepended, template name is specified + // Template Location at 'modid:test_template2' + @PrefixGameTestTemplate(false) + @GameTest(template = "test_template2") + public static void exampleTest4(GameTestHelper helper) { /*...*/ } } ``` @@ -249,7 +249,7 @@ There must be no spaces in-between namespaces; otherwise, the namespace will not The Game Test Server is a special configuration which runs a build server. The build server returns an exit code of the number of required, failed Game Tests. All failed tests, whether required or optional, are logged. This server can be run using `gradlew runGameTestServer`.
- Important infromation on NeoGradle +Important information on NeoGradle :::caution Due to a quirk in how Gradle works, by default, if a task forces a system exit, the Gradle daemon will be killed, causing the Gradle runner to report a build failure. NeoGradle sets by default a force exit on run tasks such that any subprojects are not executed in sequence. However, as such, the Game Test Server will always fail. diff --git a/docs/misc/keymappings.md b/docs/misc/keymappings.md index 2337d3343..d84dab4d9 100644 --- a/docs/misc/keymappings.md +++ b/docs/misc/keymappings.md @@ -15,7 +15,7 @@ public static final Lazy EXAMPLE_MAPPING = Lazy.of(() -> /*...*/); // Event is on the mod event bus only on the physical client @SubscribeEvent public void registerBindings(RegisterKeyMappingsEvent event) { - event.register(EXAMPLE_MAPPING.get()); + event.register(EXAMPLE_MAPPING.get()); } ``` @@ -41,10 +41,10 @@ The integer is dependent on the type provided. All input codes are defined in `G ```java new KeyMapping( - "key.examplemod.example1", // Will be localized using this translation key - InputConstants.Type.KEYSYM, // Default mapping is on the keyboard - GLFW.GLFW_KEY_P, // Default key is P - "key.categories.misc" // Mapping will be in the misc category + "key.examplemod.example1", // Will be localized using this translation key + InputConstants.Type.KEYSYM, // Default mapping is on the keyboard + GLFW.GLFW_KEY_P, // Default key is P + "key.categories.misc" // Mapping will be in the misc category ) ``` @@ -62,11 +62,11 @@ Currently, NeoForge defines three basic contexts through `KeyConflictContext`: ` ```java new KeyMapping( - "key.examplemod.example2", - KeyConflictContext.GUI, // Mapping can only be used when a screen is open - InputConstants.Type.MOUSE, // Default mapping is on the mouse - GLFW.GLFW_MOUSE_BUTTON_LEFT, // Default mouse input is the left mouse button - "key.categories.examplemod.examplecategory" // Mapping will be in the new example category + "key.examplemod.example2", + KeyConflictContext.GUI, // Mapping can only be used when a screen is open + InputConstants.Type.MOUSE, // Default mapping is on the mouse + GLFW.GLFW_MOUSE_BUTTON_LEFT, // Default mouse input is the left mouse button + "key.categories.examplemod.examplecategory" // Mapping will be in the new example category ) ``` @@ -78,12 +78,12 @@ A modifier can be added in the [controls option menu][controls] by holding down ```java new KeyMapping( - "key.examplemod.example3", - KeyConflictContext.UNIVERSAL, - KeyModifier.SHIFT, // Default mapping requires shift to be held down - InputConstants.Type.KEYSYM, // Default mapping is on the keyboard - GLFW.GLFW_KEY_G, // Default key is G - "key.categories.misc" + "key.examplemod.example3", + KeyConflictContext.UNIVERSAL, + KeyModifier.SHIFT, // Default mapping requires shift to be held down + InputConstants.Type.KEYSYM, // Default mapping is on the keyboard + GLFW.GLFW_KEY_G, // Default key is G + "key.categories.misc" ) ``` @@ -118,11 +118,11 @@ Within a GUI, a mapping can be checked within one of the `GuiEventListener` meth // In some Screen subclass @Override public boolean keyPressed(int key, int scancode, int mods) { - if (EXAMPLE_MAPPING.get().isActiveAndMatches(InputConstants.getKey(key, scancode))) { - // Execute logic to perform on key press here - return true; - } - return super.keyPressed(x, y, button); + if (EXAMPLE_MAPPING.get().isActiveAndMatches(InputConstants.getKey(key, scancode))) { + // Execute logic to perform on key press here + return true; + } + return super.keyPressed(x, y, button); } ``` @@ -136,11 +136,11 @@ If you do not own the screen which you are trying to check a **key** for, you ca // In some Screen subclass @Override public boolean mouseClicked(double x, double y, int button) { - if (EXAMPLE_MAPPING.get().isActiveAndMatches(InputConstants.TYPE.MOUSE.getOrCreate(button))) { - // Execute logic to perform on mouse click here - return true; - } - return super.mouseClicked(x, y, button); + if (EXAMPLE_MAPPING.get().isActiveAndMatches(InputConstants.TYPE.MOUSE.getOrCreate(button))) { + // Execute logic to perform on mouse click here + return true; + } + return super.mouseClicked(x, y, button); } ``` diff --git a/docs/misc/updatechecker.md b/docs/misc/updatechecker.md index 0c3beefa3..79a601dba 100644 --- a/docs/misc/updatechecker.md +++ b/docs/misc/updatechecker.md @@ -12,19 +12,19 @@ The JSON itself has a relatively simple format as follows: ```json5 { - "homepage": "", - "": { - "": "", - // List all versions of your mod for the given Minecraft version, along with their changelogs - // ... - }, - "promos": { - "-latest": "", - // Declare the latest "bleeding-edge" version of your mod for the given Minecraft version - "-recommended": "", - // Declare the latest "stable" version of your mod for the given Minecraft version - // ... - } + "homepage": "", + "": { + "": "", + // List all versions of your mod for the given Minecraft version, along with their changelogs + // ... + }, + "promos": { + "-latest": "", + // Declare the latest "bleeding-edge" version of your mod for the given Minecraft version + "-recommended": "", + // Declare the latest "stable" version of your mod for the given Minecraft version + // ... + } } ``` diff --git a/docs/networking/_category_.json b/docs/networking/_category_.json index 366f94e36..31c81bdb0 100644 --- a/docs/networking/_category_.json +++ b/docs/networking/_category_.json @@ -1,4 +1,4 @@ { - "label": "Networking", - "position": 11 + "label": "Networking", + "position": 11 } \ No newline at end of file diff --git a/docs/networking/index.md b/docs/networking/index.md index 982997ef5..32f572240 100644 --- a/docs/networking/index.md +++ b/docs/networking/index.md @@ -14,4 +14,4 @@ The most common way to accomplish these goals is to pass messages between the cl There is a technique provided by NeoForge to facilitate communication mostly built on top of [netty]. This technique can be used by listening for the `RegisterPayloadHandlersEvent` event, and then registering a specific type of [payloads], its reader, and its handler function to the registrar. [netty]: https://netty.io "Netty Website" -[payloads]: ./payload.md "Registering custom Payloads" +[payloads]: payload.md "Registering custom Payloads" diff --git a/docs/networking/payload.md b/docs/networking/payload.md index e5e7076c3..d2a2bf450 100644 --- a/docs/networking/payload.md +++ b/docs/networking/payload.md @@ -189,5 +189,5 @@ PacketDistributor.sendToAllPlayers(new MyData(...)); See the `PacketDistributor` class for more implementations. -[configuration]: ./configuration-tasks.md -[streamcodec]: ./streamcodecs.md +[configuration]: configuration-tasks.md +[streamcodec]: streamcodecs.md diff --git a/docs/networking/streamcodecs.md b/docs/networking/streamcodecs.md index c2a54c0fc..91ff74562 100644 --- a/docs/networking/streamcodecs.md +++ b/docs/networking/streamcodecs.md @@ -41,6 +41,7 @@ Unless you are manually handling the buffer object, you will generally never cal | `BYTE` | `Byte` | | `SHORT` | `Short` | | `INT` | `Integer` | +| `LONG` | `Long` | | `FLOAT` | `Float` | | `DOUBLE` | `Double` | | `BYTE_ARRAY` | `byte[]`\* | @@ -53,7 +54,7 @@ Unless you are manually handling the buffer object, you will generally never cal \* `byte[]` can be limited to a certain number of values via `ByteBufCodecs#byteArray`. -\* `String` can be limited to a certain number of characters via `ByteBufCodecs#stringUtf8`. +\*\* `String` can be limited to a certain number of characters via `ByteBufCodecs#stringUtf8`. Additionally, there are some static instances that encode and decode primivites and objects using a different method. @@ -66,7 +67,7 @@ Additionally, there are some static instances that encode and decode primivites `VAR_INT` and `VAR_LONG` are stream codecs where the value is encoded to be as small as possible. This is done by encoding seven bits at a time, using the upper bit as a marker of whether there is more data for this number. Numbers between 0 and 2^28-1 for integers or 0 and 2^56-1 for longs will be sent shorter or equal to the number of bytes in a integer or long, respectively. If the values of your numbers are normally in this range and generally at the lower end of it, then these variable stream codecs should be used. :::note -`VAR_INT` is an alternative for `INT`. +`VAR_INT` and `VAR_LONG` are alternatives for `INT` and `LONG`, respectively. ::: #### Trusted Tags @@ -116,7 +117,7 @@ public static StreamCodec = ### Composites -Stream codecs can read and write objects via `StreamCodec#composite`. Each composite stream codec defines a list of stream codecs and getters which are read/written in the order they are provided. `composite` has overloads up to six parameters. +Stream codecs can read and write objects via `StreamCodec#composite`. Each composite stream codec defines a list of stream codecs and getters which are read/written in the order they are provided. `composite` has overloads up to eight parameters. Every two parameters in a `composite` represents the stream codec used to read/write the field and a getter to get the field to encode from the object. The final parameter is a function to create a new instance of the object when decoding. @@ -283,10 +284,6 @@ public static final StreamCodec ID_STREAM_CODEC = ByteBufCodecs.idMapper(ExampleIdObject.BY_ID, ExampleIdObject::getId); ``` -:::note -NeoForge provides an alternative for id mappers that does not cache the enum values on construction via `IExtensibleEnum#createStreamCodecForExtensibleEnum`. However, this rarely needs to be used outside of extensible enums. -::: - ### Optional A stream codec for sending an `Optional` wrapped value can be generated by supplying a stream codec to `ByteBufCodecs#optional`. This method first reads/writes a boolean indicating whether to read/write the object. @@ -369,17 +366,17 @@ public abstract class ExampleObject { public abstract StreamCodec streamCodec(); } -// Assume there is a ResourceKey> DISPATCH +// Assume there is a ResourceKey> DISPATCH public static final StreamCodec DISPATCH_STREAM_CODEC = ByteBufCodecs.registry(DISPATCH).dispatch( // Get the stream codec from the specific object ExampleObject::streamCodec, // Get the stream codec from the registry object Function.identity() - ) + ); ``` -[networking]: ./payload.md +[networking]: payload.md [codecs]: ../datastorage/codecs.md [blockentity]: ../blockentities/index.md#synchronizing-on-block-update [entityserializer]: ../networking/entities.md#dynamic-data-parameters diff --git a/docs/resources/_category_.json b/docs/resources/_category_.json index db2c53c5d..5f05cd315 100644 --- a/docs/resources/_category_.json +++ b/docs/resources/_category_.json @@ -1,4 +1,4 @@ { - "label": "Resources", - "position": 7 + "label": "Resources", + "position": 7 } \ No newline at end of file diff --git a/docs/resources/client/_category_.json b/docs/resources/client/_category_.json index f785d798d..c1b8c6d90 100644 --- a/docs/resources/client/_category_.json +++ b/docs/resources/client/_category_.json @@ -1,3 +1,3 @@ { - "label": "Client" + "label": "Client" } \ No newline at end of file diff --git a/docs/resources/client/i18n.md b/docs/resources/client/i18n.md index 39e5beb51..c66292df8 100644 --- a/docs/resources/client/i18n.md +++ b/docs/resources/client/i18n.md @@ -103,8 +103,8 @@ A language file generally looks like this: ```json { - "translation.key.1": "Translation 1", - "translation.key.2": "Translation 2" + "translation.key.1": "Translation 1", + "translation.key.2": "Translation 2" } ``` @@ -114,7 +114,7 @@ Translation keys are the keys used in translations. In many cases, they follow t If a translation key does not have an associated translation in the selected language, the game will fall back to US English (`en_us`), if that is not already the selected language. If US English does not have a translation either, the translation will fail silently, and the raw translation key will be displayed instead. -Some places in Minecraft offer you helper methods to get a translation keys. For example, both blocks and items provide `#getDescriptionId` methods. These can not only be queried, but also overridden if needed. A common use case are items that have different names depending on their [NBT][nbt] value. These will usually override the variant of `#getDescriptionId` that has an [`ItemStack`][itemstack] parameter, and return different values based on the stack's NBT. Another common use case are `BlockItem`s, which override the method to use the associated block's translation key instead. +Some places in Minecraft offer you helper methods to get a translation keys. For example, both blocks and items provide `#getDescriptionId` methods. For items, these can not only be queried, but also changed if needed via `Item$Properties#overrideDescription`. If items have different names depending on their underlying [data components][datacomponent], these can be overriden by setting the `CUSTOM_NAME` data component with the desired translatable component on the `ItemStack`. There is also a variant on the `Item#getName` which takes in an [`ItemStack`][itemstack] parameter to set the default component of the item. `BlockItem`s, on the other hand, set the description id by calling `Item$Properties#useBlockDescriptionPrefix`. :::tip The only purpose of translation keys is for localization. Do not use them for game logic, that's what [registry names][regname] are for. @@ -148,7 +148,7 @@ public class MyLanguageProvider extends LanguageProvider { @Override protected void addTranslations() { // Adds a translation with the given key and the given value. - add("translation.key.1", "Translation 1"); + this.add("translation.key.1", "Translation 1"); // Helpers are available for various common object types. Every helper has two variants: an add() variant // for the object itself, and an addTypeHere() variant that accepts a supplier for the object. @@ -156,29 +156,30 @@ public class MyLanguageProvider extends LanguageProvider { // All following examples assume the existence of the values as suppliers of the needed type. // Adds a block translation. - add(MyBlocks.EXAMPLE_BLOCK.get(), "Example Block"); - addBlock(MyBlocks.EXAMPLE_BLOCK, "Example Block"); + this.add(MyBlocks.EXAMPLE_BLOCK.get(), "Example Block"); + this.addBlock(MyBlocks.EXAMPLE_BLOCK, "Example Block"); // Adds an item translation. - add(MyItems.EXAMPLE_ITEM.get(), "Example Item"); - addItem(MyItems.EXAMPLE_ITEM, "Example Item"); + this.add(MyItems.EXAMPLE_ITEM.get(), "Example Item"); + this.addItem(MyItems.EXAMPLE_ITEM, "Example Item"); // Adds an item stack translation. This is mainly for items that have NBT-specific names. - add(MyItems.EXAMPLE_ITEM_STACK.get(), "Example Item"); - addItemStack(MyItems.EXAMPLE_ITEM_STACK, "Example Item"); + this.add(MyItems.EXAMPLE_ITEM_STACK.get(), "Example Item"); + this.addItemStack(MyItems.EXAMPLE_ITEM_STACK, "Example Item"); // Adds an entity type translation. - add(MyEntityTypes.EXAMPLE_ENTITY_TYPE.get(), "Example Entity"); - addEntityType(MyEntityTypes.EXAMPLE_ENTITY_TYPE, "Example Entity"); + this.add(MyEntityTypes.EXAMPLE_ENTITY_TYPE.get(), "Example Entity"); + this.addEntityType(MyEntityTypes.EXAMPLE_ENTITY_TYPE, "Example Entity"); // Adds an enchantment translation. - add(MyEnchantments.EXAMPLE_ENCHANTMENT.get(), "Example Enchantment"); - addEnchantment(MyEnchantments.EXAMPLE_ENCHANTMENT, "Example Enchantment"); + this.add(MyEnchantments.EXAMPLE_ENCHANTMENT.get(), "Example Enchantment"); + this.addEnchantment(MyEnchantments.EXAMPLE_ENCHANTMENT, "Example Enchantment"); // Adds a mob effect translation. - add(MyMobEffects.EXAMPLE_MOB_EFFECT.get(), "Example Effect"); - addEffect(MyMobEffects.EXAMPLE_MOB_EFFECT, "Example Effect"); + this.add(MyMobEffects.EXAMPLE_MOB_EFFECT.get(), "Example Effect"); + this.addEffect(MyMobEffects.EXAMPLE_MOB_EFFECT, "Example Effect"); } } ``` Then, register the provider like any other provider in the `GatherDataEvent`. +[datacomponent]: ../../items/datacomponents.md [datagen]: ../index.md#data-generation [itemstack]: ../../items/index.md#itemstacks [mcwikilang]: https://minecraft.wiki/w/Language diff --git a/docs/resources/client/models/bakedmodel.md b/docs/resources/client/models/bakedmodel.md index 359dafe10..99a9089be 100644 --- a/docs/resources/client/models/bakedmodel.md +++ b/docs/resources/client/models/bakedmodel.md @@ -2,7 +2,7 @@ `BakedModel`s are the in-code representation of a shape with textures. They can originate from multiple sources, for example from a call to `UnbakedModel#bake` (default model loader) or `IUnbakedGeometry#bake` ([custom model loaders][modelloader]). Some [block entity renderers][ber] also make use of baked models. There is no limit to how complex a model may be. -Models are stored in the `ModelManager`, which can be accessed through `Minecraft.getInstance().modelManager`. Then, you can call `ModelManager#getModel` to get a certain model by its [`ResourceLocation`][rl] or [`ModelResourceLocation`][mrl]. Mods will basically always reuse a model that was previously automatically loaded and baked. +Models are stored in the `ModelManager`, which can be accessed through `Minecraft.getInstance().modelManager`. Then, you can call `ModelManager#getModel` to get a certain model by its [`ModelResourceLocation`][mrl]. Mods will basically always reuse a model that was previously automatically loaded and baked. ## Methods of `BakedModel` @@ -40,11 +40,12 @@ Other methods in `BakedModel` that you may override and/or query include: | `boolean isGui3d()` | Whether this model renders as 3d or flat in GUI slots. | | `boolean usesBlockLight()` | Whether to use 3D lighting (`true`) or flat lighting from the front (`false`) when lighting the model. | | `boolean isCustomRenderer()` | If true, skips normal rendering and calls an associated [`BlockEntityWithoutLevelRenderer`][bewlr]'s `renderByItem` method instead. If false, renders through the default renderer. | -| `ItemOverrides getOverrides()` | Returns the [`ItemOverrides`][itemoverrides] associated with this model. This is only relevant on item models. | +| `BakedOverrides overrides()` | Returns the [`BakedOverrides`][bakedoverrides] associated with this model. This is only relevant on item models. | | `ModelData getModelData(BlockAndTintGetter, BlockPos, BlockState, ModelData)` | Returns the model data to use for the model. This method is passed an existing `ModelData` that is either the result of `BlockEntity#getModelData()` if the block has an associated block entity, or `ModelData.EMPTY` if that is not the case. This method can be used for blocks that need model data, but do not have a block entity, for example for blocks with connected textures. | | `TextureAtlasSprite getParticleIcon(ModelData)` | Returns the particle sprite to use for the model. May use the model data to use different particle sprites for different model data values. NeoForge-added, replacing the vanilla `getParticleIcon()` overload with no parameters. | | `ChunkRenderTypeSet getRenderTypes(BlockState, RandomSource, ModelData)` | Returns a `ChunkRenderTypeSet` containing the render type(s) to use for rendering the block model. A `ChunkRenderTypeSet` is a set-backed ordered `Iterable`. By default falls back to [getting the render type from the model JSON][rendertype]. Only used for block models, item models use the overload below. | -| `List getRenderTypes(ItemStack, boolean)` | Returns a `List` containing the render type(s) to use for rendering the item model. By default falls back to the normal model-bound render type lookup, which always yields a list with one element. Only used for item models, block models use the overload above. | +| `List getRenderTypes(ItemStack)` | Returns a `List` containing the render type(s) to use for rendering the item model. By default falls back to the normal model-bound render type lookup, which always yields a list with one element. Only used for item models, block models use the overload above. | +| `List getRenderPasses(ItemStack)` | Returns a `List` containing the models used by the item. Each of those models' render types will be queired using `getRenderTypes`. | ## Perspectives @@ -62,17 +63,16 @@ Minecraft's render engine recognizes a total of 8 perspective types (9 if you in | `FIXED` | `"fixed"` | Item frames | | `NONE` | `"none"` | Fallback purposes in code, should not be used in JSON | -## `ItemOverrides` +## `BakedOverrides` -`ItemOverrides` is a class that provides a way for baked models to process the state of an [`ItemStack`][itemstack] and return a new baked model through the `#resolve` method. `#resolve` has five parameters: +`BakedOverrides` is a class that provides a way for baked models to process the state of an [`ItemStack`][itemstack] and return a new baked model through the `#findOverride` method. `#findOverride` has four parameters: -- A `BakedModel`: The original model. - An `ItemStack`: The item stack being rendered. - A `ClientLevel`: The level the model is being rendered in. This should only be used for querying the level, not mutating it in any way. May be null. - A `LivingEntity`: The entity the model is rendered on. May be null, e.g. when rendering from a [block entity renderer][ber]. - An `int`: A seed for randomizing. -`ItemOverrides` also hold the model's override options as `BakedOverride`s. An object of `BakedOverride` is an in-code representation of a model's [`overrides`][overrides] block. It can be used by baked models to return different models depending on its contents. A list of all `BakedOverride`s of an `ItemOverrides` instance can be retrieved through `ItemOverrides#getOverrides()`. +`BakedOverrides` also hold the model's override options as `BakedOverride`s. An object of `BakedOverride` is an in-code representation of a model's [`overrides`][overrides] block. It can be used by baked models to return different models depending on its contents. A list of all `BakedOverride`s of an `BakedOverrides` instance can be retrieved through `BakedOverrides#getOverrides()`. ## `BakedModelWrapper` @@ -91,7 +91,7 @@ public class MyBakedModelWrapper extends BakedModelWrapper { } ``` -After writing your model wrapper class, you must apply the wrappers to the models it should affect. Do so in a [client-side][sides] [event handler][event] for `ModelEvent.ModifyBakingResult`: +After writing your model wrapper class, you must apply the wrappers to the models it should affect. Do so in a [client-side][sides] [event handler][event] for `ModelEvent.ModifyBakingResult` on the [**mod event bus**][modbus]: ```java @SubscribeEvent @@ -107,9 +107,8 @@ public static void modifyBakingResult(ModelEvent.ModifyBakingResult event) { // For item models event.getModels().computeIfPresent( // The model resource location of the model to modify. - new ModelResourceLocation( - ResourceLocation.fromNamespaceAndPath("examplemod", "example_item"), - "inventory" + ModelResourceLocation.inventory( + ResourceLocation.fromNamespaceAndPath("examplemod", "example_item") ), // A BiFunction with the location and the original models as parameters, returning the new model. (location, model) -> new MyBakedModelWrapper(model); @@ -126,8 +125,9 @@ It is generally encouraged to use a [custom model loader][modelloader] over wrap [bewlr]: ../../../blockentities/ber.md#blockentitywithoutlevelrenderer [blockstate]: ../../../blocks/states.md [event]: ../../../concepts/events.md -[itemoverrides]: #itemoverrides +[bakedoverrides]: #bakedoverrides [itemstack]: ../../../items/index.md#itemstacks +[modbus]: ../../../concepts/events.md#event-buses [modelloader]: modelloaders.md [mrl]: ../../../misc/resourcelocation.md#modelresourcelocations [overrides]: index.md#overrides diff --git a/docs/resources/client/models/datagen.md b/docs/resources/client/models/datagen.md index beaa9ebc6..4fbe4446c 100644 --- a/docs/resources/client/models/datagen.md +++ b/docs/resources/client/models/datagen.md @@ -60,48 +60,48 @@ public class MyBlockStateProvider extends BlockStateProvider { // The texture must be located at assets//textures/block/.png, where // and are the block's registry name's namespace and path, respectively. // Used by the majority of (full) blocks, such as planks, cobblestone or bricks. - simpleBlock(block); + this.simpleBlock(block); // Overload that accepts a model file to use. - simpleBlock(block, exampleModel); + this.simpleBlock(block, exampleModel); // Overload that accepts one or multiple (vararg) ConfiguredModel objects. // See below for more info about ConfiguredModel. - simpleBlock(block, ConfiguredModel.builder().build()); + this.simpleBlock(block, ConfiguredModel.builder().build()); // Adds an item model file with the block's name, parenting the given model file, for a block item to pick up. - simpleBlockItem(block, exampleModel); + this.simpleBlockItem(block, exampleModel); // Shorthand for calling #simpleBlock() (model file overload) and #simpleBlockItem. - simpleBlockWithItem(block, exampleModel); + this.simpleBlockWithItem(block, exampleModel); // Adds a log block model. Requires two textures at assets//textures/block/.png and // assets//textures/block/_top.png, referencing the side and top texture, respectively. // Note that the block input here is limited to RotatedPillarBlock, which is the class vanilla logs use. - logBlock(block); + this.logBlock(block); // Like #logBlock, but the textures are named _side.png and _end.png instead of // .png and _top.png, respectively. Used by quartz pillars and similar blocks. // Has an overload that allow you to specify a different texture base name, that is then suffixed // with _side and _end as needed, an overload that allows you to specify two resource locations // for the side and end textures, and an overload that allows specifying side and end model files. - axisBlock(block); + this.axisBlock(block); // Variants of #logBlock and #axisBlock that additionally allow for render types to be specified. // Comes in string and resource location variants for the render type, // in all combinations with all variants of #logBlock and #axisBlock. - logBlockWithRenderType(block, "minecraft:cutout"); - axisBlockWithRenderType(block, mcLoc("cutout_mipped")); + this.logBlockWithRenderType(block, "minecraft:cutout"); + this.axisBlockWithRenderType(block, mcLoc("cutout_mipped")); // Specifies a horizontally-rotatable block model with a side texture, a front texture, and a top texture. // The bottom will use the side texture as well. If you don't need the front or top texture, // just pass in the side texture twice. Used by e.g. furnaces and similar blocks. - horizontalBlock(block, sideTexture, frontTexture, topTexture); + this.horizontalBlock(block, sideTexture, frontTexture, topTexture); // Specifies a horizontally-rotatable block model with a model file that will be rotated as needed. // Has an overload that instead of a model file accepts a Function, // allowing for different rotations to use different models. Used e.g. by the stonecutter. - horizontalBlock(block, exampleModel); + this.horizontalBlock(block, exampleModel); // Specifies a horizontally-rotatable block model that is attached to a face, e.g. for buttons or levers. // Accounts for placing the block on the ground and on the ceiling, and rotates them accordingly. // Like #horizontalBlock, has an overload that accepts a Function instead. - horizontalFaceBlock(block, exampleModel); + this.horizontalFaceBlock(block, exampleModel); // Similar to #horizontalBlock, but for blocks that are rotatable in all directions, including up and down. // Again, has an overload that accepts a Function instead. - directionalBlock(block, exampleModel); + this.directionalBlock(block, exampleModel); } } ``` @@ -180,7 +180,7 @@ ConfiguredModel.Builder builder = ConfiguredModel.builder() ConfiguredModel[] model = builder.build(); // Get a variant block state builder. -VariantBlockStateBuilder variantBuilder = getVariantBuilder(MyBlocksClass.EXAMPLE_BLOCK.get()); +VariantBlockStateBuilder variantBuilder = this.getVariantBuilder(MyBlocksClass.EXAMPLE_BLOCK.get()); // Create a partial state and set properties on it. VariantBlockStateBuilder.PartialBlockstate partialState = variantBuilder.partialState(); // Add one or multiple models for a partial blockstate. The models are a vararg parameter. @@ -202,7 +202,7 @@ variantBuilder.forAllStates(state -> { }); // Get a multipart block state builder. -MultiPartBlockStateBuilder multipartBuilder = getMultipartBuilder(MyBlocksClass.EXAMPLE_BLOCK.get()); +MultiPartBlockStateBuilder multipartBuilder = this.getMultipartBuilder(MyBlocksClass.EXAMPLE_BLOCK.get()); // Add a new part. Starts with .part() and ends with .end(). multipartBuilder.addPart(multipartBuilder.part() // Step one: Build the model. multipartBuilder.part() returns a ConfiguredModel.Builder, @@ -247,14 +247,14 @@ public class MyItemModelProvider extends ItemModelProvider { @Override protected void registerModels() { // Block items generally use their corresponding block models as parent. - withExistingParent(MyItemsClass.EXAMPLE_BLOCK_ITEM.get(), modLoc("block/example_block")); + this.withExistingParent(MyItemsClass.EXAMPLE_BLOCK_ITEM.get(), modLoc("block/example_block")); // Items generally use a simple parent and one texture. The most common parents are item/generated and item/handheld. // In this example, the item texture would be located at assets/examplemod/textures/item/example_item.png. // If you want a more complex model, you can use getBuilder() and then work from that, like you would with block models. - withExistingParent(MyItemsClass.EXAMPLE_ITEM.get(), mcLoc("item/generated")).texture("layer0", "item/example_item"); + this.withExistingParent(MyItemsClass.EXAMPLE_ITEM.get(), mcLoc("item/generated")).texture("layer0", "item/example_item"); // The above line is so common that there is a shortcut for it. Note that the item registry name and the // texture path, relative to textures/item, must match. - basicItem(MyItemsClass.EXAMPLE_ITEM.get()); + this.basicItem(MyItemsClass.EXAMPLE_ITEM.get()); } } ``` diff --git a/docs/resources/client/models/index.md b/docs/resources/client/models/index.md index 079ade3b5..c9f9b388c 100644 --- a/docs/resources/client/models/index.md +++ b/docs/resources/client/models/index.md @@ -247,7 +247,7 @@ public static void registerBlockColorHandlers(RegisterColorHandlersEvent.Block e return 0xFFFFFFFF; }, // A varargs of blocks to apply the tinting to - EXAMPLE_BLOCK.value(), ...); + EXAMPLE_BLOCK.get(), ...); } ``` @@ -264,7 +264,7 @@ public static void registerItemColorHandlers(RegisterColorHandlersEvent.Item eve return 0xFFFFFFFF; }, // A varargs of items to apply the tinting to - EXAMPLE_ITEM.value(), ...); + EXAMPLE_ITEM.get(), ...); } ``` diff --git a/docs/resources/client/models/modelloaders.md b/docs/resources/client/models/modelloaders.md index 49f5bd808..6b3874060 100644 --- a/docs/resources/client/models/modelloaders.md +++ b/docs/resources/client/models/modelloaders.md @@ -68,9 +68,21 @@ The dynamic fluid container model, also called dynamic bucket model after its mo "cover_is_mask": false, // Optional, defaults to true. Whether to apply the fluid's luminosity to the item model. "apply_fluid_luminosity": false, - } +} ``` +:::note +If you would like to apply a tint to the fluid texture, you will need to [register an `ItemColor`][tint]. An instance with the fluid tinting logic can be created from `DynamicFluidContainerModel.Colors`: + +```java +// Client-side mod bus event handler +@SubscribeEvent +public static void registerItemColorHandlers(RegisterColorHandlersEvent.Item event) { + event.register(new DynamicFluidContainerModel.Colors(), EXAMPLE_BUCKET.get(), ...); +} +``` +::: + Very often, dynamic fluid container models will directly use the bucket model. This is done by specifying the `neoforge:item_bucket` parent model, like so: ```json5 @@ -83,7 +95,7 @@ Very often, dynamic fluid container models will directly use the bucket model. T } ``` -To [datagen][modeldatagen] this model, use the custom loader class `DynamicFluidContainerModelBuilder`. Be aware that for legacy support reasons, this class also provides a method to set the `apply_tint` property, which is no longer used. +To [datagen][modeldatagen] this model, use the custom loader class `DynamicFluidContainerModelBuilder`. ### Elements Model @@ -240,16 +252,23 @@ public class MyGeometry implements IUnbakedGeometry { // - The model state. This holds the properties from the blockstate file, e.g. rotations and the uvlock boolean. // - The item overrides. This is the code representation of an "overrides" block in an item model. @Override - public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, ItemOverrides overrides) { + public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides) { // See info on the parameters below. return new MyDynamicModel(context.useAmbientOcclusion(), context.isGui3d(), context.useBlockLight(), - spriteGetter.apply(context.getMaterial("particle")), overrides); + spriteGetter.apply(context.getMaterial("particle")), new BakedOverrides(baker, overrides)); } // Method responsible for correctly resolving parent properties. Required if this model loads any nested models or reuses the vanilla loader on itself (see below). @Override - public void resolveParents(Function modelGetter, IGeometryBakingContext context) { - // UnbakedModel#resolveParents + public void resolveDependencies(UnbakedModel.Resolver resolver, IGeometryBakingContext context) { + // UnbakedModel#resolveDependencies + } + + // Gets the name of the components that can be configured via IGeometryBakingContext + // Usually empty unless there are child components + @Override + public Set getConfigurableComponentNames() { + return Set.of(); } } @@ -264,11 +283,11 @@ public class MyDynamicModel implements IDynamicBakedModel { private final boolean isGui3d; private final boolean usesBlockLight; private final TextureAtlasSprite particle; - private final ItemOverrides overrides; + private final BakedOverrides overrides; // The constructor does not require any parameters other than the ones for instantiating the final fields. // It may specify any additional parameters to store in fields you deem necessary for your model to work. - public MyDynamicModel(boolean useAmbientOcclusion, boolean isGui3d, boolean usesBlockLight, TextureAtlasSprite particle, ItemOverrides overrides) { + public MyDynamicModel(boolean useAmbientOcclusion, boolean isGui3d, boolean usesBlockLight, TextureAtlasSprite particle, BakedOverrides overrides) { this.useAmbientOcclusion = useAmbientOcclusion; this.isGui3d = isGui3d; this.usesBlockLight = usesBlockLight; @@ -299,8 +318,8 @@ public class MyDynamicModel implements IDynamicBakedModel { } @Override - public ItemOverrides getOverrides() { - // Return ItemOverrides.EMPTY when in a block model context. + public BakedOverrides overrides() { + // Return BakedOverrides.EMPTY when in a block model context. return overrides; } @@ -415,14 +434,14 @@ public class MyGeometry implements IUnbakedGeometry { } @Override - public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, ItemOverrides overrides) { - BakedModel bakedBase = new ElementsModel(base.getElements()).bake(context, baker, spriteGetter, modelState, overrides); + public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides) { + BakedModel bakedBase = this.base.bake(baker, spriteGetter, modelState); return new MyDynamicModel(bakedBase, /* other parameters here */); } @Override - public void resolveParents(Function modelGetter, IGeometryBakingContext context) { - base.resolveParents(modelGetter); + public void resolveDependencies(UnbakedModel.Resolver resolver, IGeometryBakingContext context) { + this.base.resolveDependencies(resolver); } } @@ -441,7 +460,7 @@ public class MyDynamicModel implements IDynamicBakedModel { public List getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand, ModelData extraData, @Nullable RenderType renderType) { List quads = new ArrayList<>(); // Add the base model's quads. Can also do something different with the quads here, depending on what you need. - quads.addAll(base.getQuads(state, side, rand, extraData, renderType)); + quads.addAll(this.base.getQuads(state, side, rand, extraData, renderType)); // add other elements to the quads list as needed here return quads; } @@ -449,7 +468,7 @@ public class MyDynamicModel implements IDynamicBakedModel { // Apply the base model's transforms to our model as well. @Override public BakedModel applyTransform(ItemDisplayContext transformType, PoseStack poseStack, boolean applyLeftHandTransform) { - return base.applyTransform(transformType, poseStack, applyLeftHandTransform); + return this.base.applyTransform(transformType, poseStack, applyLeftHandTransform); } } ``` @@ -465,4 +484,5 @@ public class MyDynamicModel implements IDynamicBakedModel { [modeldatagen]: datagen.md [rendertype]: index.md#render-types [sides]: ../../../concepts/sides.md +[tint]: index.md#tinting [transform]: index.md#root-transforms diff --git a/docs/resources/client/particles.md b/docs/resources/client/particles.md index 3d8a6fc69..51ec1145e 100644 --- a/docs/resources/client/particles.md +++ b/docs/resources/client/particles.md @@ -16,7 +16,7 @@ public class MyParticleTypes { // The easiest way to add new particle types is reusing vanilla's SimpleParticleType. // Implementing a custom ParticleType is also possible, see below. - public static final DeferredHolder, SimpleParticleType> MY_PARTICLE = PARTICLE_TYPES.register( + public static final Supplier MY_PARTICLE = PARTICLE_TYPES.register( // The name of the particle type. "my_particle", // The supplier. The boolean parameter denotes whether setting the Particles option in the @@ -127,7 +127,7 @@ A mismatched list of sprite set particle factories and particle definition files ::: :::note -While particle descriptions must have providers registered a certain way, they are only used if the `ParticleRenderType` (set via `Particle#getRenderType`) uses the `TextureAtlas#LOCATION_PARTICLES` as the shader texture. For vanilla render types, these are `PARTICLE_SHEET_OPAQUE`, `PARTICLE_SHEET_TRANSLUCENT`, and `PARTICLE_SHEET_LIT`. +While particle descriptions must have providers registered a certain way, they are only used if the `ParticleRenderType` (set via `Particle#getRenderType`) uses the `TextureAtlas#LOCATION_PARTICLES` as the shader texture. For vanilla render types, these are `PARTICLE_SHEET_OPAQUE` and `PARTICLE_SHEET_TRANSLUCENT`. ::: ### Datagen @@ -199,6 +199,11 @@ public class MyParticleOptions implements ParticleOptions { // Does not need any parameters, but may define any fields necessary for the particle to work. public MyParticleOptions() {} + + @Override + public ParticleType getType() { + // Return the registered particle type + } } ``` @@ -225,7 +230,7 @@ public class MyParticleType extends ParticleType { } ``` -... and reference it during registration: +... and reference it during [registration][registry]: ```java public static final Supplier MY_CUSTOM_PARTICLE = PARTICLE_TYPES.register( @@ -234,6 +239,20 @@ public static final Supplier MY_CUSTOM_PARTICLE = PARTICLE_TYPES ); ``` +The registered particle is then passed into `ParticleOptions#getType`: + +```java +public class MyParticleOptions implements ParticleOptions { + + // ... + + @Override + public ParticleType getType() { + return MY_CUSTOM_PARTICLE.get(); + } +} +``` + ## Spawning Particles As a reminder from before, the server only knows `ParticleType`s and `ParticleOption`s, while the client works directly with `Particle`s provided by `ParticleProvider`s that are associated with a `ParticleType`. Consequently, the ways in which particles are spawned are vastly different depending on the side you are on. @@ -245,5 +264,5 @@ As a reminder from before, the server only knows `ParticleType`s and `ParticleOp [datagen]: ../index.md#data-generation [event]: ../../concepts/events.md [modbus]: ../../concepts/events.md#event-buses -[registry]: ../../concepts/registries.md +[registry]: ../../concepts/registries.md#methods-for-registering [side]: ../../concepts/sides.md diff --git a/docs/resources/client/sounds.md b/docs/resources/client/sounds.md index 61dde22da..fc2781338 100644 --- a/docs/resources/client/sounds.md +++ b/docs/resources/client/sounds.md @@ -27,16 +27,18 @@ public class MySoundsClass { DeferredRegister.create(BuiltInRegistries.SOUND_EVENT, "examplemod"); // All vanilla sounds use variable range events. - public static final DeferredHolder MY_SOUND = SOUND_EVENTS.register( - "my_sound", // must match the resource location on the next line - () -> SoundEvent.createVariableRangeEvent(ResourceLocation.fromNamespaceAndPath("examplemod", "my_sound")) + public static final Holder MY_SOUND = SOUND_EVENTS.register( + "my_sound", + // Takes in the registry name + SoundEvent::createVariableRangeEvent ); // There is a currently unused method to register fixed range (= non-attenuating) events as well: - public static final DeferredHolder MY_FIXED_SOUND = SOUND_EVENTS.register("my_fixed_sound", + public static final Holder MY_FIXED_SOUND = SOUND_EVENTS.register( + "my_fixed_sound", // 16 is the default range of sounds. Be aware that due to OpenAL limitations, // values above 16 have no effect and will be capped to 16. - () -> SoundEvent.createFixedRangeEvent(ResourceLocation.fromNamespaceAndPath("examplemod", "my_fixed_sound"), 16) + registryName -> SoundEvent.createFixedRangeEvent(registryName, 16f) ); } ``` diff --git a/docs/resources/client/textures.md b/docs/resources/client/textures.md index c3bc5f520..0f3541cdf 100644 --- a/docs/resources/client/textures.md +++ b/docs/resources/client/textures.md @@ -37,7 +37,10 @@ Texture metadata can be specified in a file named exactly the same as the textur "top": 0, "right": 0, "bottom": 0 - } + }, + // When true the center part of the texture will be applied like + // the stretch type instead of a nine slice tiling. + "stretch_inner": true } }, // See below. diff --git a/docs/resources/index.md b/docs/resources/index.md index 144dae506..a74519b6c 100644 --- a/docs/resources/index.md +++ b/docs/resources/index.md @@ -141,11 +141,11 @@ The data generator can accept several command line arguments: - `--existing path/to/folder`: Tells the data generator to consider the given folder when checking for existing files. Like with the output, it is recommended to use Gradle's `file(...).getAbsolutePath()`. - `--existing-mod examplemod`: Tells the data generator to consider the resources in the given mod's JAR file when checking for existing files. - Generator modes (all of these are boolean arguments and do not need any additional arguments): - - `--includeClient`: Whether to generate client resources (assets). Check at runtime with `GatherDataEvent#includeClient()`. - - `--includeServer`: Whether to generate server resources (data). Check at runtime with `GatherDataEvent#includeServer()`. - - `--includeDev`: Whether to run dev tools. Generally shouldn't be used by mods. Check at runtime with `GatherDataEvent#includeDev()`. - - `--includeReports`: Whether to dump a list of registered objects. Check at runtime with `GatherDataEvent#includeReports()`. - - `--all`: Enable all generator modes. + - `--includeClient`: Whether to generate client resources (assets). Check at runtime with `GatherDataEvent#includeClient()`. + - `--includeServer`: Whether to generate server resources (data). Check at runtime with `GatherDataEvent#includeServer()`. + - `--includeDev`: Whether to run dev tools. Generally shouldn't be used by mods. Check at runtime with `GatherDataEvent#includeDev()`. + - `--includeReports`: Whether to dump a list of registered objects. Check at runtime with `GatherDataEvent#includeReports()`. + - `--all`: Enable all generator modes. All arguments can be added to the run configurations by adding the following to your `build.gradle`: diff --git a/docs/resources/server/_category_.json b/docs/resources/server/_category_.json index dd4398342..8de78b39d 100644 --- a/docs/resources/server/_category_.json +++ b/docs/resources/server/_category_.json @@ -1,3 +1,3 @@ { - "label": "Server" + "label": "Server" } \ No newline at end of file diff --git a/docs/resources/server/advancements.md b/docs/resources/server/advancements.md index 191b4107e..3ca5143fe 100644 --- a/docs/resources/server/advancements.md +++ b/docs/resources/server/advancements.md @@ -10,21 +10,21 @@ An advancement JSON file may contain the following entries: - `parent`: The parent advancement ID of this advancement. Circular references will be detected and cause a loading failure. Optional; if absent, this advancement will be considered a root advancement. Root advancements are advancements that have no parent set. They will be the root of their [advancement tree][tree]. - `display`: The object holding several properties used for display of the advancement in the advancement GUI. Optional; if absent, this advancement will be invisible, but can still be triggered. - - `icon`: A [JSON representation of an item stack][itemstackjson]. - - `text`: A [text component][text] to use as the advancement's title. - - `description`: A [text component][text] to use as the advancement's description. - - `frame`: The frame type of the advancement. Accepts `challenge`, `goal` and `task`. Optional, defaults to `task`. - - `background`: The texture to use for the tree background. This is not relative to the `textures` directory, i.e. the `textures/` folder prefix must be included. Optional, defaults to the missing texture. Only effective on root advancements. - - `show_toast`: Whether to show a toast in the top right corner on completion. Optional, defaults to true. - - `announce_to_chat`: Whether to announce advancement completion in the chat. Optional, defaults to true. - - `hidden`: Whether to hide this advancement and all children from the advancement GUI until it is completed. Has no effect on root advancements themselves, but still hides all of their children. Optional, defaults to false. + - `icon`: A [JSON representation of an item stack][itemstackjson]. + - `text`: A [text component][text] to use as the advancement's title. + - `description`: A [text component][text] to use as the advancement's description. + - `frame`: The frame type of the advancement. Accepts `challenge`, `goal` and `task`. Optional, defaults to `task`. + - `background`: The texture to use for the tree background. This is not relative to the `textures` directory, i.e. the `textures/` folder prefix must be included. Optional, defaults to the missing texture. Only effective on root advancements. + - `show_toast`: Whether to show a toast in the top right corner on completion. Optional, defaults to true. + - `announce_to_chat`: Whether to announce advancement completion in the chat. Optional, defaults to true. + - `hidden`: Whether to hide this advancement and all children from the advancement GUI until it is completed. Has no effect on root advancements themselves, but still hides all of their children. Optional, defaults to false. - `criteria`: A map of criteria this advancement should track. Every criterion is identified by its map key. A list of criteria triggers added by Minecraft can be found in the `CriteriaTriggers` class, and the JSON specifications can be found on the [Minecraft Wiki][triggers]. For implementing your own criteria or triggering criteria from code, see below. - `requirements`: A list of lists that determine what criteria are required. This is a list of OR lists that are ANDed together, or in other words, every sublist must have at least one criterion matching. Optional, defaults to all criteria being required. - `rewards`: An object representing the rewards to grant when this advancement is completed. Optional, all values of the object are also optional. - - `experience`: The amount of experience to award to the player. - - `recipes`: A list of [recipe] IDs to unlock. - - `loot`: A list of [loot tables][loottable] to roll and give to the player. - - `function`: A [function] to run. If you want to run multiple functions, create a wrapper function that runs all other functions. + - `experience`: The amount of experience to award to the player. + - `recipes`: A list of [recipe] IDs to unlock. + - `loot`: A list of [loot tables][loottable] to roll and give to the player. + - `function`: A [function] to run. If you want to run multiple functions, create a wrapper function that runs all other functions. - `sends_telemetry_event`: Determines whether telemetry data should be collected when this advancement is completed or not. Only actually does anything if in the `minecraft` namespace. Optional, defaults to false. - `neoforge:conditions`: NeoForge-added. A list of [conditions] that must be passed for the advancement to be loaded. Optional. @@ -158,35 +158,64 @@ Advancements can be [datagenned][datagen] using an `AdvancementProvider`. An `Ad Both Minecraft and NeoForge provide a class named `AdvancementProvider`, located at `net.minecraft.data.advancements.AdvancementProvider` and `net.neoforged.neoforge.common.data.AdvancementProvider`, respectively. The NeoForge class is an improvement on the one Minecraft provides, and should always be used in favor of the Minecraft one. The following documentation always assumes usage of the NeoForge `AdvancementProvider` class. ::: -To start, create a subclass of `AdvancementProvider`: +To start, create an instance of `AdvancementProvider` within `GatherDataEvent`: ```java -public class MyAdvancementProvider extends AdvancementProvider { - // Parameters can be obtained from GatherDataEvent. - public MyAdvancementProvider(PackOutput output, - CompletableFuture lookupProvider, ExistingFileHelper existingFileHelper) { - super(output, lookupProvider, existingFileHelper, List.of()); - } +@SubscribeEvent +public static void gatherData(GatherDataEvent event) { + DataGenerator generator = event.getGenerator(); + PackOutput output = generator.getPackOutput(); + CompletableFuture lookupProvider = event.getLookupProvider(); + ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); + + generator.addProvider( + event.includeServer(), + new AdvancementProvider( + output, lookupProvider, existingFileHelper, + // Add generators here + List.of(...) + ) + ); + + // Other providers } ``` -Now, the next step is to fill the list with our generators. To do so, we add one or more generators as static classes and then add an instance of each of them to the currently empty list in the constructor parameter. +Now, the next step is to fill the list with our generators. To do so, we can either add generators as classes or lambdas, and then add an instance of each of them to the currently empty list in the constructor parameter. ```java -public class MyAdvancementProvider extends AdvancementProvider { - public MyAdvancementProvider(PackOutput output, CompletableFuture lookupProvider, ExistingFileHelper existingFileHelper) { - // Add an instance of our generator to the list parameter. This can be done as many times as you want. - // Having multiple generators is purely for organization, all functionality can be achieved with a single generator. - super(output, lookupProvider, existingFileHelper, List.of(new MyAdvancementGenerator())); +// Class example +public class MyAdvancementGenerator extends AdvancementProvider.AdvancementGenerator { + + @Override + public void generate(HolderLookup.Provider registries, Consumer saver, ExistingFileHelper existingFileHelper) { + // Generate your advancements here. } +} - private static final class MyAdvancementGenerator implements AdvancementProvider.AdvancementGenerator { - @Override - public void generate(HolderLookup.Provider registries, Consumer saver, ExistingFileHelper existingFileHelper) { - // Generate your advancements here. - } +// Method Example +public class ExampleClass { + + // Matches the parameters provided by AdvancementProvider.AdvancementGenerator#generate + public static void generateExampleAdvancements(HolderLookup.Provider registries, Consumer saver, ExistingFileHelper existingFileHelper) { + // Generate your advancements here. } } + +// In GatherDataEvent +generator.addProvider( + event.includeServer(), + new AdvancementProvider( + output, lookupProvider, existingFileHelper, + // Add generators here + List.of( + // Add an instance of our generator to the list parameter. This can be done as many times as you want. + // Having multiple generators is purely for organization, all functionality can be achieved with a single generator. + new MyAdvancementGenerator(), + ExampleClass::generateExampleAdvancements + ) + ) +); ``` To generate an advancement, you want to use an `Advancement.Builder`: @@ -233,7 +262,7 @@ builder.rewards( // Alternatively, use loot() to create a new builder. .addLootTable(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.fromNamespaceAndPath("minecraft", "chests/igloo"))) // Alternatively, use recipe() to create a new builder. - .addRecipe(ResourceLocation.fromNamespaceAndPath("minecraft", "iron_ingot")) + .addRecipe(ResourceKey.create(Registries.RECIPE, ResourceLocation.fromNamespaceAndPath("minecraft", "iron_ingot"))) // Alternatively, use function() to create a new builder. .runs(ResourceLocation.fromNamespaceAndPath("examplemod", "example_function")) ); @@ -250,24 +279,6 @@ builder.requirements(AdvancementRequirements.allOf(List.of("pickup_dirt"))); builder.save(saver, ResourceLocation.fromNamespaceAndPath("examplemod", "example_advancement"), existingFileHelper); ``` -Of course, don't forget to add your provider to the `GatherDataEvent`: - -```java -@SubscribeEvent -public static void gatherData(GatherDataEvent event) { - DataGenerator generator = event.getGenerator(); - PackOutput output = generator.getPackOutput(); - CompletableFuture lookupProvider = event.getLookupProvider(); - ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); - - // other providers here - generator.addProvider( - event.includeServer(), - new MyAdvancementProvider(output, lookupProvider, existingFileHelper) - ); -} -``` - [codec]: ../../datastorage/codecs.md [conditions]: conditions.md [datagen]: ../index.md#data-generation diff --git a/docs/resources/server/conditions.md b/docs/resources/server/conditions.md index e4673660e..bc00f36be 100644 --- a/docs/resources/server/conditions.md +++ b/docs/resources/server/conditions.md @@ -6,16 +6,16 @@ Most JSON files can optionally declare a `neoforge:conditions` block in the root ```json5 { - "neoforge:conditions": [ - { - // Condition 1 - }, - { - // Condition 2 - }, - // ... - ], - // The rest of the data file + "neoforge:conditions": [ + { + // Condition 1 + }, + { + // Condition 2 + }, + // ... + ], + // The rest of the data file } ``` @@ -23,16 +23,16 @@ For example, if we want to only load our file if a mod with id `examplemod` is p ```json5 { - // highlight-start - "neoforge:conditions": [ - { - "type": "neoforge:mod_loaded", - "modid": "examplemod" - } - ], - // highlight-end - "type": "minecraft:crafting_shaped", - // ... + // highlight-start + "neoforge:conditions": [ + { + "type": "neoforge:mod_loaded", + "modid": "examplemod" + } + ], + // highlight-end + "type": "minecraft:crafting_shaped", + // ... } ``` @@ -48,8 +48,8 @@ These consist of no data and return the expected value. ```json5 { - // Will always return true (or false for "neoforge:false") - "type": "neoforge:true" + // Will always return true (or false for "neoforge:false") + "type": "neoforge:true" } ``` @@ -69,11 +69,11 @@ This condition accepts another condition and inverts it. ```json5 { - // Inverts the result of the stored condition - "type": "neoforge:not", - "value": { - // Another condition - } + // Inverts the result of the stored condition + "type": "neoforge:not", + "value": { + // Another condition + } } ``` @@ -83,16 +83,16 @@ These conditions accept the condition(s) being operated upon and apply the expec ```json5 { - // ANDs the stored conditions together (or ORs for "neoforge:or") - "type": "neoforge:and", - "values": [ - { - // First condition - }, - { - // Second condition - } - ] + // ANDs the stored conditions together (or ORs for "neoforge:or") + "type": "neoforge:and", + "values": [ + { + // First condition + }, + { + // Second condition + } + ] } ``` @@ -102,9 +102,9 @@ This condition returns true if a mod with the given mod id is loaded, and false ```json5 { - "type": "neoforge:mod_loaded", - // Returns true if "examplemod" is loaded - "modid": "examplemod" + "type": "neoforge:mod_loaded", + // Returns true if "examplemod" is loaded + "modid": "examplemod" } ``` @@ -114,9 +114,9 @@ This condition returns true if an item with the given registry name has been reg ```json5 { - "type": "neoforge:item_exists", - // Returns true if "examplemod:example_item" has been registered - "item": "examplemod:example_item" + "type": "neoforge:item_exists", + // Returns true if "examplemod:example_item" has been registered + "item": "examplemod:example_item" } ``` @@ -126,9 +126,9 @@ This condition returns true if the given item [tag] is empty, and false otherwis ```json5 { - "type": "neoforge:tag_empty", - // Returns true if "examplemod:example_tag" is an empty item tag - "tag": "examplemod:example_tag" + "type": "neoforge:tag_empty", + // Returns true if "examplemod:example_tag" is an empty item tag + "tag": "examplemod:example_tag" } ``` @@ -142,12 +142,12 @@ For example, let's assume we want to reimplement the `tag_empty` condition, but // This class is basically a boiled-down copy of TagEmptyCondition, adjusted for entity types instead of items. public record EntityTagEmptyCondition(TagKey> tag) implements ICondition { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(inst -> inst.group( - ResourceLocation.CODEC.xmap(rl -> TagKey.create(Registries.ENTITY_TYPES, rl), TagKey::location).fieldOf("tag").forGetter(EntityTagEmptyCondition::tag) + TagKey.codec(Registries.ENTITY_TYPE).fieldOf("tag").forGetter(EntityTagEmptyCondition::tag) ).apply(inst, EntityTagEmptyCondition::new)); @Override public boolean test(ICondition.IContext context) { - return context.getTag(this.tag()).isEmpty(); + return !context.isTagLoaded(this.tag()); } @Override @@ -171,13 +171,13 @@ And then, we can use our condition in some data file (assuming we registered the ```json5 { - "neoforge:conditions": [ - { - "type": "examplemod:entity_tag_empty", - "tag": "minecraft:zombies" - } - ], - // The rest of the data file + "neoforge:conditions": [ + { + "type": "examplemod:entity_tag_empty", + "tag": "minecraft:zombies" + } + ], + // The rest of the data file } ``` diff --git a/docs/resources/server/damagetypes.md b/docs/resources/server/damagetypes.md index af40c2e25..19b4fd132 100644 --- a/docs/resources/server/damagetypes.md +++ b/docs/resources/server/damagetypes.md @@ -17,23 +17,23 @@ Now that we can reference it from code, let's specify some properties in the dat ```json5 { - // The death message id of the damage type. The full death message translation key will be - // "death.examplemod.example" (with swapped-out mod ids and names). - "message_id": "example", - // Whether this damage type's damage amount scales with difficulty or not. Valid vanilla values are: - // - "never": The damage value remains the same on any difficulty. Common for player-caused damage types. - // - "when_caused_by_living_non_player": The damage value is scaled if the entity is caused by a - // living entity of some sort, including indirectly (e.g. an arrow shot by a skeleton), that is not a player. - // - "always": The damage value is always scaled. Commonly used by explosion-like damage. - "scaling": "when_caused_by_living_non_player", - // The amount of exhaustion caused by receiving this kind of damage. - "exhaustion": 0.1, - // The damage effects (currently only sound effects) that are applied when receiving this kind of damage. Optional. - // Valid vanilla values are "hurt" (default), "thorns", "drowning", "burning", "poking", and "freezing". - "effects": "hurt", - // The death message type. Determines how the death message is built. Optional. - // Valid vanilla values are "default" (default), "fall_variants", and "intentional_game_design". - "death_message_type": "default" + // The death message id of the damage type. The full death message translation key will be + // "death.examplemod.example" (with swapped-out mod ids and names). + "message_id": "example", + // Whether this damage type's damage amount scales with difficulty or not. Valid vanilla values are: + // - "never": The damage value remains the same on any difficulty. Common for player-caused damage types. + // - "when_caused_by_living_non_player": The damage value is scaled if the entity is caused by a + // living entity of some sort, including indirectly (e.g. an arrow shot by a skeleton), that is not a player. + // - "always": The damage value is always scaled. Commonly used by explosion-like damage. + "scaling": "when_caused_by_living_non_player", + // The amount of exhaustion caused by receiving this kind of damage. + "exhaustion": 0.1, + // The damage effects (currently only sound effects) that are applied when receiving this kind of damage. Optional. + // Valid vanilla values are "hurt" (default), "thorns", "drowning", "burning", "poking", and "freezing". + "effects": "hurt", + // The death message type. Determines how the death message is built. Optional. + // Valid vanilla values are "default" (default), "fall_variants", and "intentional_game_design". + "death_message_type": "default" } ``` @@ -50,7 +50,7 @@ The same format is also used for vanilla's damage types, and pack developers can ```java DamageSource damageSource = new DamageSource( // The damage type holder to use. Query from the registry. This is the only required parameter. - registryAccess.registryOrThrow(Registries.DAMAGE_TYPE).getHolderOrThrow(EXAMPLE_DAMAGE), + registryAccess.lookupOrThrow(Registries.DAMAGE_TYPE).getOrThrow(EXAMPLE_DAMAGE), // The direct entity. For example, if a skeleton shot you, the skeleton would be the causing entity // (= the parameter above), and the arrow would be the direct entity (= this parameter). Similar to // the causing entity, this isn't always applicable and therefore nullable. Optional, defaults to null. @@ -73,7 +73,7 @@ If `DamageSource`s have no entity or position context whatsoever, it makes sense ```java public static DamageSource exampleDamage(Entity causer) { return new DamageSource( - causer.level().registryAccess().registryOrThrow(Registries.DAMAGE_TYPE).getHolderOrThrow(EXAMPLE_DAMAGE), + causer.level().registryAccess().lookupOrThrow(Registries.DAMAGE_TYPE).getOrThrow(EXAMPLE_DAMAGE), causer); } ``` @@ -101,10 +101,9 @@ Damage type JSON files can be [datagenned][datagen]. Since damage types are a da // In your datagen class @SubscribeEvent public static void onGatherData(GatherDataEvent event) { - CompletableFuture lookupProvider = event.getLookupProvider(); - event.getGenerator().addProvider( + CompletableFuture lookupProvider = event.getGenerator().addProvider( event.includeServer(), - output -> new DatapackBuiltinEntriesProvider(output, lookupProvider, new RegistrySetBuilder() + output -> new DatapackBuiltinEntriesProvider(output, event.getLookupProvider(), new RegistrySetBuilder() // Add a datapack builtin entry provider for damage types. If this lambda becomes longer, // this should probably be extracted into a separate method for the sake of readability. .add(Registries.DAMAGE_TYPE, bootstrap -> { @@ -121,7 +120,9 @@ public static void onGatherData(GatherDataEvent event) { .add(...), Set.of(ExampleMod.MOD_ID) ) - ); + ).getRegistryProvider(); + + // ... } ``` diff --git a/docs/resources/server/datamaps/builtin.md b/docs/resources/server/datamaps/builtin.md index 87d1e24bc..8ae8b90df 100644 --- a/docs/resources/server/datamaps/builtin.md +++ b/docs/resources/server/datamaps/builtin.md @@ -8,10 +8,10 @@ Allows configuring composter values, as a replacement for `ComposterBlock.COMPOS ```json5 { - // A 0 to 1 (inclusive) float representing the chance that the item will update the level of the composter - "chance": 1, - // Optional, defaults to false - whether farmer villagers can compost this item - "can_villager_compost": false + // A 0 to 1 (inclusive) float representing the chance that the item will update the level of the composter + "chance": 1, + // Optional, defaults to false - whether farmer villagers can compost this item + "can_villager_compost": false } ``` @@ -19,12 +19,12 @@ Example: ```json5 { - "values": { - // Give acacia logs a 50% chance that they will fill a composter - "minecraft:acacia_log": { - "chance": 0.5 + "values": { + // Give acacia logs a 50% chance that they will fill a composter + "minecraft:acacia_log": { + "chance": 0.5 + } } - } } ``` @@ -34,8 +34,8 @@ Allows configuring item burn times. This data map is located at `neoforge/data_m ```json5 { - // A positive integer representing the item's burn time in ticks - "burn_time": 1000 + // A positive integer representing the item's burn time in ticks + "burn_time": 1000 } ``` @@ -43,12 +43,12 @@ Example: ```json5 { - "values": { - // Give anvils a 2 seconds burn time - "minecraft:anvil": { - "burn_time": 40 + "values": { + // Give anvils a 2 seconds burn time + "minecraft:anvil": { + "burn_time": 40 + } } - } } ``` @@ -61,17 +61,17 @@ Vanilla adds an implicit burn time of 300 ticks (15 seconds) for `#minecraft:log ```json5 { - "replace": false, - "values": [ - // values here - ], - "remove": [ - "examplemod:example_nether_wood_planks", - "#examplemod:example_nether_wood_stems", - "examplemod:example_nether_wood_door", - // etc. - // other removals here - ] + "replace": false, + "values": [ + // values here + ], + "remove": [ + "examplemod:example_nether_wood_planks", + "#examplemod:example_nether_wood_stems", + "examplemod:example_nether_wood_door", + // etc. + // other removals here + ] } ``` ::: @@ -82,8 +82,8 @@ Allows configuring the mobs that may appear in the mob spawner in a monster room ```json5 { - // The weight of this mob, relative to other mobs in the datamap - "weight": 100 + // The weight of this mob, relative to other mobs in the datamap + "weight": 100 } ``` @@ -91,12 +91,12 @@ Example: ```json5 { - "values": { - // Make squids appear in monster room spawners with a weight of 100 - "minecraft:squid": { - "weight": 100 + "values": { + // Make squids appear in monster room spawners with a weight of 100 + "minecraft:squid": { + "weight": 100 + } } - } } ``` @@ -106,8 +106,8 @@ Allows configuring oxidation stages, as a replacement for `WeatheringCopper#NEXT ```json5 { - // The block this block will turn into once oxidized - "next_oxidized_stage": "examplemod:oxidized_block" + // The block this block will turn into once oxidized + "next_oxidized_stage": "examplemod:oxidized_block" } ``` @@ -119,12 +119,12 @@ Example: ```json5 { - "values": { - "mymod:custom_copper": { - // Make a custom copper block oxidize into custom oxidized copper - "next_oxidized_stage": "mymod:custom_oxidized_copper" + "values": { + "mymod:custom_copper": { + // Make a custom copper block oxidize into custom oxidized copper + "next_oxidized_stage": "mymod:custom_oxidized_copper" + } } - } } ``` @@ -134,8 +134,8 @@ Allows configuring the sounds produced by parrots when they want to imitate a mo ```json5 { - // The ID of the sound that parrots will produce when imitating the mob - "sound": "minecraft:entity.parrot.imitate.creeper" + // The ID of the sound that parrots will produce when imitating the mob + "sound": "minecraft:entity.parrot.imitate.creeper" } ``` @@ -143,12 +143,12 @@ Example: ```json5 { - "values": { - // Make parrots produce the ambient cave sound when imitating allays - "minecraft:allay": { - "sound": "minecraft:ambient.cave" + "values": { + // Make parrots produce the ambient cave sound when imitating allays + "minecraft:allay": { + "sound": "minecraft:ambient.cave" + } } - } } ``` @@ -158,8 +158,8 @@ Allows configuring the gift that a villager with a certain `VillagerProfession` ```json5 { - // The ID of the loot table that a villager profession will hand out after a raid - "loot_table": "minecraft:gameplay/hero_of_the_village/armorer_gift" + // The ID of the loot table that a villager profession will hand out after a raid + "loot_table": "minecraft:gameplay/hero_of_the_village/armorer_gift" } ``` @@ -167,12 +167,12 @@ Example: ```json5 { - "values": { - "minecraft:armorer": { - // Make armorers give the raid hero the armorer gift loot table - "loot_table": "minecraft:gameplay/hero_of_the_village/armorer_gift" + "values": { + "minecraft:armorer": { + // Make armorers give the raid hero the armorer gift loot table + "loot_table": "minecraft:gameplay/hero_of_the_village/armorer_gift" + } } - } } ``` @@ -182,8 +182,8 @@ Allows configuring the sculk vibration frequencies emitted by game events, as a ```json5 { - // An integer between 1 and 15 (inclusive) that indicates the vibration frequency of the event - "frequency": 2 + // An integer between 1 and 15 (inclusive) that indicates the vibration frequency of the event + "frequency": 2 } ``` @@ -191,12 +191,12 @@ Example: ```json5 { - "values": { - // Make the splash in water game event vibrate on the second frequency - "minecraft:splash": { - "frequency": 2 + "values": { + // Make the splash in water game event vibrate on the second frequency + "minecraft:splash": { + "frequency": 2 + } } - } } ``` @@ -206,8 +206,8 @@ Allows configuring the block a block will turn into when waxed (right clicked wi ```json5 { - // The waxed variant of this block - "waxed": "minecraft:iron_block" + // The waxed variant of this block + "waxed": "minecraft:iron_block" } ``` @@ -215,14 +215,14 @@ Example: ```json5 { - "values": { - // Make gold blocks turn into iron blocks once waxed - "minecraft:gold_block": { - "waxed": "minecraft:iron_block" + "values": { + // Make gold blocks turn into iron blocks once waxed + "minecraft:gold_block": { + "waxed": "minecraft:iron_block" + } } - } } ``` -[datacomponent]: ../../../items/datacomponents.mdx +[datacomponent]: ../../../items/datacomponents.md [datamap]: index.md diff --git a/docs/resources/server/datamaps/index.md b/docs/resources/server/datamaps/index.md index 20b024d88..f874d870b 100644 --- a/docs/resources/server/datamaps/index.md +++ b/docs/resources/server/datamaps/index.md @@ -36,18 +36,18 @@ For example, let's assume that we have a data map object with two float keys `am ```json5 { - "values": { - // Attach a value to the carrot item - "minecraft:carrot": { - "amount": 12, - "chance": 1 - }, - // Attach a value to all items in the logs tag - "#minecraft:logs": { - "amount": 1, - "chance": 0.1 + "values": { + // Attach a value to the carrot item + "minecraft:carrot": { + "amount": 12, + "chance": 1 + }, + // Attach a value to all items in the logs tag + "#minecraft:logs": { + "amount": 1, + "chance": 0.1 + } } - } } ``` @@ -55,18 +55,18 @@ Data maps may support [mergers][mergers], which will cause custom merging behavi ```json5 { - "values": { - // Overwrite the value of the carrot item - "minecraft:carrot": { - // highlight-next-line - "replace": true, - // The new value will be under a value sub-object - "value": { - "amount": 12, - "chance": 1 - } + "values": { + // Overwrite the value of the carrot item + "minecraft:carrot": { + // highlight-next-line + "replace": true, + // The new value will be under a value sub-object + "value": { + "amount": 12, + "chance": 1 + } + } } - } } ``` @@ -76,10 +76,10 @@ Removing elements can be done by specifying a list of item IDs or tag IDs to rem ```json5 { - // We do not want the potato to have a value, even if another mod's data map added it - "remove": [ - "minecraft:potato" - ] + // We do not want the potato to have a value, even if another mod's data map added it + "remove": [ + "minecraft:potato" + ] } ``` @@ -87,13 +87,13 @@ Removals run after additions, so we can include a tag and then exclude certain e ```json5 { - "values": { - "#minecraft:logs": { /* ... */ } - }, - // Exclude crimson stem again - "remove": [ - "minecraft:crimson_stem" - ] + "values": { + "#minecraft:logs": { /* ... */ } + }, + // Exclude crimson stem again + "remove": [ + "minecraft:crimson_stem" + ] } ``` @@ -101,11 +101,11 @@ Data maps may support custom [removers] with additional arguments. To supply the ```json5 { - "remove": { - // The remover will be deserialized from the value (`somekey1` in this case) - // and applied to the value attached to the carrot item - "minecraft:carrot": "somekey1" - } + "remove": { + // The remover will be deserialized from the value (`somekey1` in this case) + // and applied to the value attached to the carrot item + "minecraft:carrot": "somekey1" + } } ``` @@ -228,8 +228,8 @@ This way, if one pack specifies the value 12 for `minecraft:carrot` and another Finally, don't forget to actually specify the merger in the builder, like so: ```java -// We assume AdvancedData contains an integer property of some sort. -AdvancedDataMapType ADVANCED_MAP = AdvancedDataMapType.builder(...) +// The types of the data map must match the type of the merger. +AdvancedDataMapType ADVANCED_MAP = AdvancedDataMapType.builder(...) .merger(new IntMerger()) .build(); ``` @@ -263,12 +263,12 @@ With this remover in mind, consider the following data file: ```json5 { - "values": { - "minecraft:carrot": { - "somekey1": "value1", - "somekey2": "value2" + "values": { + "minecraft:carrot": { + "somekey1": "value1", + "somekey2": "value2" + } } - } } ``` @@ -276,11 +276,11 @@ Now, consider this second data file that is placed at a higher priority than the ```json5 { - "remove": { - // As the remover is decoded as a string, we can use a string as the value here. - // If it were decoded as an object, we would have needed to use an object. - "minecraft:carrot": "somekey1" - } + "remove": { + // As the remover is decoded as a string, we can use a string as the value here. + // If it were decoded as an object, we would have needed to use an object. + "minecraft:carrot": "somekey1" + } } ``` @@ -288,11 +288,11 @@ That way, after both files are applied, the final result will be (an in-memory r ```json5 { - "values": { - "minecraft:carrot": { - "somekey1": "value1" + "values": { + "minecraft:carrot": { + "somekey1": "value1" + } } - } } ``` @@ -318,7 +318,7 @@ public class MyDataMapProvider extends DataMapProvider { @Override protected void gather() { // We create a builder for the EXAMPLE_DATA data map and add our entries using #add. - builder(EXAMPLE_DATA) + this.builder(EXAMPLE_DATA) // We turn on replacing. Don't ever ship a mod like this! This is purely for educational purposes. .replace(true) // We add the value "amount": 10, "chance": 1 for all slabs. The boolean parameter controls @@ -338,26 +338,26 @@ This would then result in the following JSON file: ```json5 { - "replace": true, - "values": { - "#minecraft:slabs": { - "amount": 10, - "chance": 1.0 + "replace": true, + "values": { + "#minecraft:slabs": { + "amount": 10, + "chance": 1.0 + }, + "minecraft:apple": { + "amount": 5, + "chance": 0.2 + } }, - "minecraft:apple": { - "amount": 5, - "chance": 0.2 - } - }, - "remove": [ - "#minecraft:wooden_slabs" - ], - "neoforge:conditions": [ - { - "type": "neoforge:mod_loaded", - "modid": "botania" - } - ] + "remove": [ + "#minecraft:wooden_slabs" + ], + "neoforge:conditions": [ + { + "type": "neoforge:mod_loaded", + "modid": "botania" + } + ] } ``` diff --git a/docs/resources/server/enchantments/builtin.md b/docs/resources/server/enchantments/builtin.md index dcd15a573..625b525df 100644 --- a/docs/resources/server/enchantments/builtin.md +++ b/docs/resources/server/enchantments/builtin.md @@ -21,31 +21,31 @@ Value effect components can be set to use any of these operations on their given The Sharpness enchantment uses `minecraft:damage`, a value effect component, as follows to achieve its effect: - + ```json5 "effects": { - // The type of this effect component is "minecraft:damage". - // This means that the effect will modify weapon damage. - // See below for a list of more effect component types. - "minecraft:damage": [ - { - // A value effect that should be applied. - // In this case, since there's only one, this value effect is just named "effect". - "effect": { - // The type of value effect to use. In this case, it is "minecraft:add", so the value (given below) will be added - // to the weapon damage value. - "type": "minecraft:add", - - // The value block. In this case, the value is a LevelBasedValue that starts at 1 and increases by 0.5 every enchantment level. - "value": { - "type": "minecraft:linear", - "base": 1.0, - "per_level_above_first": 0.5 + // The type of this effect component is "minecraft:damage". + // This means that the effect will modify weapon damage. + // See below for a list of more effect component types. + "minecraft:damage": [ + { + // A value effect that should be applied. + // In this case, since there's only one, this value effect is just named "effect". + "effect": { + // The type of value effect to use. In this case, it is "minecraft:add", so the value (given below) will be added + // to the weapon damage value. + "type": "minecraft:add", + + // The value block. In this case, the value is a LevelBasedValue that starts at 1 and increases by 0.5 every enchantment level. + "value": { + "type": "minecraft:linear", + "base": 1.0, + "per_level_above_first": 0.5 + } + } } - } - } - ] + ] } ``` @@ -128,27 +128,27 @@ Location based effect components are components that implement `EnchantmentLocat Here is an example which uses the `minecraft:attributes` location based effect component type to change the wielder's entity scale: - + ```json5 // The type is "minecraft:attributes" (described below). // In a nutshell, this applies an attribute modifier. "minecraft:attributes": [ - { - // This "amount" block is a LevelBasedValue. - "amount": { - "type": "minecraft:linear", - "base": 1, - "per_level_above_first": 1 - }, - - // Which attribute to modify. In this case, modifies "minecraft:scale" - "attribute": "minecraft:generic.scale", - // The unique identifier for this attribute modifier. Should not overlap with others, but doesn't need to be registered. - "id": "examplemod:enchantment.size_change", - // What operation to use on the attribute. Can be "add_value", "add_multiplied_base", or "add_multiplied_total". - "operation": "add_value" - } + { + // This "amount" block is a LevelBasedValue. + "amount": { + "type": "minecraft:linear", + "base": 1, + "per_level_above_first": 1 + }, + + // Which attribute to modify. In this case, modifies "minecraft:scale" + "attribute": "minecraft:generic.scale", + // The unique identifier for this attribute modifier. Should not overlap with others, but doesn't need to be registered. + "id": "examplemod:enchantment.size_change", + // What operation to use on the attribute. Can be "add_value", "add_multiplied_base", or "add_multiplied_total". + "operation": "add_value" + } ], ``` @@ -180,8 +180,8 @@ Vanilla adds the following location based events: - `minecraft:all_of`: Runs a list of entity effects in sequence. - `minecraft:apply_mob_effect`: Applies a [mob effect] to the affected mob. - `minecraft:attribute`: Applies an [attribute modifier] to the wielder of the enchantment. +- `minecraft:change_item_damage`: Damages this item's durability. - `minecraft:damage_entity`: Does damage to the affected entity. This stacks with attack damage if in an attacking context. -- `minecraft:damage_item`: Damages this item's durability. - `minecraft:explode`: Summons an explosion. - `minecraft:ignite`: Sets the entity on fire. - `minecraft:play_sound`: Plays a specified sound. @@ -218,33 +218,33 @@ Here is an example of the JSON definition of one such component from the Fire As ```json5 // This component's type is "minecraft:post_attack" (see below). "minecraft:post_attack": [ - { - // Decides whether the "victim" of the attack, the "attacker", or the "damaging entity" (the projectile if there is one, attacker if not) recieves the effect. - "affected": "victim", - - // Decides which enchantment entity effect to apply. - "effect": { - // The type of this effect is "minecraft:ignite". - "type": "minecraft:ignite", - // "minecraft:ignite" requires a LevelBasedValue as a duration for how long the entity will be ignited. - "duration": { - "type": "minecraft:linear", - "base": 4.0, - "per_level_above_first": 4.0 - } - }, - - // Decides who (the "victim", "attacker", or "damaging entity") must have the enchantment for it to take effect. - "enchanted": "attacker", - - // An optional predicate which controls whether the effect applies. - "requirements": { - "condition": "minecraft:damage_source_properties", - "predicate": { - "is_direct": true - } + { + // Decides whether the "victim" of the attack, the "attacker", or the "damaging entity" (the projectile if there is one, attacker if not) recieves the effect. + "affected": "victim", + + // Decides which enchantment entity effect to apply. + "effect": { + // The type of this effect is "minecraft:ignite". + "type": "minecraft:ignite", + // "minecraft:ignite" requires a LevelBasedValue as a duration for how long the entity will be ignited. + "duration": { + "type": "minecraft:linear", + "base": 4.0, + "per_level_above_first": 4.0 + } + }, + + // Decides who (the "victim", "attacker", or "damaging entity") must have the enchantment for it to take effect. + "enchanted": "attacker", + + // An optional predicate which controls whether the effect applies. + "requirements": { + "condition": "minecraft:damage_source_properties", + "predicate": { + "is_direct": true + } + } } - } ] ``` @@ -331,11 +331,11 @@ For more detail on each of these, please look at the [relevant minecraft wiki pa [Value Effect Components]: https://minecraft.wiki/w/Enchantment_definition#Components_with_value_effects [Entity Effect Components]: https://minecraft.wiki/w/Enchantment_definition#Components_with_entity_effects [Location Based Effect Components]: https://minecraft.wiki/w/Enchantment_definition#location_changed -[text component]: /docs/resources/client/i18n.md -[LevelBasedValue]: /docs/resources/server/loottables/#number-provider +[text component]: ../../client/i18n.md +[LevelBasedValue]: ../loottables/index.md#number-provider [Attribute Effect Component]: https://minecraft.wiki/w/Enchantment_definition#Attribute_effects [datapack function]: https://minecraft.wiki/w/Function_(Java_Edition) [luck]: https://minecraft.wiki/w/Luck -[mob effect]: /docs/items/mobeffects/ +[mob effect]: ../../../items/mobeffects.md [attribute modifier]: https://minecraft.wiki/w/Attribute#Modifiers [relevant minecraft wiki page]: https://minecraft.wiki/w/Enchantment_definition#Components_with_entity_effects \ No newline at end of file diff --git a/docs/resources/server/enchantments/index.md b/docs/resources/server/enchantments/index.md index 3a9286a6e..c0084d497 100644 --- a/docs/resources/server/enchantments/index.md +++ b/docs/resources/server/enchantments/index.md @@ -3,7 +3,7 @@ import TabItem from '@theme/TabItem'; # Enchantments -Enchantments are special effects that can be applied to tools and other items. As of 1.21, enchantments are stored on items as [Data Components], are defined in JSON, and are comprised of so-called enchantment effect components. During the game, the enchantments on a particular item are contained within the `DataComponentTypes.ENCHANTMENT` component, in an `ItemEnchantment` instance. +Enchantments are special effects that can be applied to tools and other items. As of 1.21, enchantments are stored on items as [Data Components], are defined in JSON, and are comprised of so-called enchantment effect components. During the game, the enchantments on a particular item are contained within the `DataComponents.ENCHANTMENTS` component, in an `ItemEnchantments` instance. A new enchantment can be added by creating a JSON file in your namespace's `enchantment` datapack subfolder. For example, to create an enchantment called `examplemod:example_enchant`, one would create a file `data/examplemod/enchantment/example_enchantment.json`. @@ -11,85 +11,85 @@ A new enchantment can be added by creating a JSON file in your namespace's `ench ```json5 { - // The text component that will be used as the in-game name of the enchantment. - // Can be a translation key or a literal string. - // Remember to translate this in your lang file if you use a translation key! - "description": { - "translate": "enchantment.examplemod.enchant_name" - }, - - // Which items this enchantment can be applied to. - // Can be either an item id, such as "minecraft:trident", - // or a list of item ids, such as ["examplemod:red_sword", "examplemod:blue_sword"] - // or an item tag, such as "#examplemod:enchantable/enchant_name". - // Note that this doesn't cause the enchantment to appear for these items in the enchanting table. - "supported_items": "#examplemod:enchantable/enchant_name", - - // (Optional) Which items this enchantment appears for in the enchanting table. - // Can be an item, list of items, or item tag. - // If left unspecified, this is the same as `supported_items`. - "primary_items": [ - "examplemod:item_a", - "examplemod:item_b" - ], - - // (Optional) Which enchantments are incompatible with this one. - // Can be an enchantment id, such as "minecraft:sharpness", - // or a list of enchantment ids, such as ["minecraft:sharpness", "minecraft:fire_aspect"], - // or enchantment tag, such as "#examplemod:exclusive_to_enchant_name". - // Incompatible enchantments will not be added to the same item by vanilla mechanics. - "exclusive_set": "#examplemod:exclusive_to_enchant_name", - - // The likelihood that this enchantment will appear in the Enchanting Table. - // Bounded by [1, 1024]. - "weight": 6, - - // The maximum level this enchantment is allowed to reach. - // Bounded by [1, 255]. - "max_level": 3, - - // The maximum cost of this enchantment, measured in "enchanting power". - // This corresponds to, but is not equivalent to, the threshold in levels the player needs to meet to bestow this enchantment. - // See below for details. - // The actual cost will be between this and the min_cost. - "max_cost": { - "base": 45, - "per_level_above_first": 9 - }, - - // Specifies the minimum cost of this enchantment; otherwise as above. - "min_cost": { - "base": 2, - "per_level_above_first": 8 - }, - - // The cost that this enchantment adds to repairing an item in an anvil in levels. The cost is multiplied by enchantment level. - // If an item has a DataComponentTypes.STORED_ENCHANTMENTS component, the cost is halved. In vanilla, this only applies to enchanted books. - // Bounded by [1, inf). - "anvil_cost": 2, - - // (Optional) A list of slot groups this enchantment provides effects in. - // A slot group is defined as one of the possible values of the EquipmentSlotGroup enum. - // In vanilla, these are: `any`, `hand`, `mainhand`, `offhand`, `armor`, `feet`, `legs`, `chest`, `head`, and `body`. - "slots": [ - "mainhand" - ], - - // The effects that this enchantment provides as a map of enchantment effect components (read on). - "effects": { - "examplemod:custom_effect": [ - { - "effect": { - "type": "minecraft:add", - "value": { - "type": "minecraft:linear", - "base": 1, - "per_level_above_first": 1 - } - } - } - ] - } + // The text component that will be used as the in-game name of the enchantment. + // Can be a translation key or a literal string. + // Remember to translate this in your lang file if you use a translation key! + "description": { + "translate": "enchantment.examplemod.enchant_name" + }, + + // Which items this enchantment can be applied to. + // Can be either an item id, such as "minecraft:trident", + // or a list of item ids, such as ["examplemod:red_sword", "examplemod:blue_sword"] + // or an item tag, such as "#examplemod:enchantable/enchant_name". + // Note that this doesn't cause the enchantment to appear for these items in the enchanting table. + "supported_items": "#examplemod:enchantable/enchant_name", + + // (Optional) Which items this enchantment appears for in the enchanting table. + // Can be an item, list of items, or item tag. + // If left unspecified, this is the same as `supported_items`. + "primary_items": [ + "examplemod:item_a", + "examplemod:item_b" + ], + + // (Optional) Which enchantments are incompatible with this one. + // Can be an enchantment id, such as "minecraft:sharpness", + // or a list of enchantment ids, such as ["minecraft:sharpness", "minecraft:fire_aspect"], + // or enchantment tag, such as "#examplemod:exclusive_to_enchant_name". + // Incompatible enchantments will not be added to the same item by vanilla mechanics. + "exclusive_set": "#examplemod:exclusive_to_enchant_name", + + // The likelihood that this enchantment will appear in the Enchanting Table. + // Bounded by [1, 1024]. + "weight": 6, + + // The maximum level this enchantment is allowed to reach. + // Bounded by [1, 255]. + "max_level": 3, + + // The maximum cost of this enchantment, measured in "enchanting power". + // This corresponds to, but is not equivalent to, the threshold in levels the player needs to meet to bestow this enchantment. + // See below for details. + // The actual cost will be between this and the min_cost. + "max_cost": { + "base": 45, + "per_level_above_first": 9 + }, + + // Specifies the minimum cost of this enchantment; otherwise as above. + "min_cost": { + "base": 2, + "per_level_above_first": 8 + }, + + // The cost that this enchantment adds to repairing an item in an anvil in levels. The cost is multiplied by enchantment level. + // If an item has a DataComponentTypes.STORED_ENCHANTMENTS component, the cost is halved. In vanilla, this only applies to enchanted books. + // Bounded by [1, inf). + "anvil_cost": 2, + + // (Optional) A list of slot groups this enchantment provides effects in. + // A slot group is defined as one of the possible values of the EquipmentSlotGroup enum. + // In vanilla, these are: `any`, `hand`, `mainhand`, `offhand`, `armor`, `feet`, `legs`, `chest`, `head`, and `body`. + "slots": [ + "mainhand" + ], + + // The effects that this enchantment provides as a map of enchantment effect components (read on). + "effects": { + "examplemod:custom_effect": [ + { + "effect": { + "type": "minecraft:add", + "value": { + "type": "minecraft:linear", + "base": 1, + "per_level_above_first": 1 + } + } + } + ] + } } ``` @@ -134,13 +134,14 @@ Enchantment effect component types must be [registered] to `BuiltInRegistries.EN ```java // In some registration class -public static final DeferredRegister> ENCHANTMENT_COMPONENT_TYPES = DeferredRegister.create(BuiltInRegistries.ENCHANTMENT_EFFECT_COMPONENT_TYPE, "examplemod"); +public static final DeferredRegister.DataComponents ENCHANTMENT_COMPONENT_TYPES = + DeferredRegister.createDataComponents(BuiltInRegistries.ENCHANTMENT_EFFECT_COMPONENT_TYPE, "examplemod"); -public static final DeferredHolder, DataComponentType>> INCREMENT = - ENCHANTMENT_COMPONENT_TYPES.register("increment", - () -> DataComponentType.builder() - .persistent(Increment.CODEC) - .build()); +public static final Supplier> INCREMENT = + ENCHANTMENT_COMPONENT_TYPES.registerComponentType( + "increment", + builder -> builder.persistent(Increment.CODEC) + ); ``` Now, we can implement some game logic that makes use of this component to alter an integer value: @@ -165,7 +166,7 @@ int modifiedValue = atomicValue.get(); // Use the now-modified value elsewhere in your game logic. ``` -First, we invoke one of the overloads of `EnchantmentHelper#runIterationOnItem`. This function accepts an `EnchantmentHelper.EnchantmentVisitor`, which is a functional interface that accepts an enchantment and its level, and is invoked on all of the enchantments that the given itemstack has (essentially a `BiConsumer`). +First, we invoke one of the overloads of `EnchantmentHelper#runIterationOnItem`. This function accepts an `EnchantmentHelper.EnchantmentVisitor`, which is a functional interface that accepts an enchantment and its level, and is invoked on all of the enchantments that the given itemstack has (essentially a `BiConsumer, Integer>`). To actually perform the adjustment, use the provided `Increment#add` method. Since this is inside of a lambda expression, we need to use a type that can be updated atomically, such as `AtomicInteger`, to modify this value. This also permits multiple `INCREMENT` components to run on the same item and stack their effects, like what happens in vanilla. @@ -179,8 +180,10 @@ Vanilla adds an additional helper method to further streamline the process of ch ```java // `enchant` is an Enchantment instance. // `lootContext` is a LootContext instance. -Enchantment.applyEffects( - enchant.getEffects(EnchantmentEffectComponents.KNOCKBACK), // Or whichever other List> you want +enchant.applyEffects( + // Or whichever other List> you want + enchant.getEffects(EnchantmentEffectComponents.KNOCKBACK), + // The context to test the conditions against lootContext, (effectData) -> // Use the effectData (in this example, an EnchantmentValueEffect) however you want. ); @@ -192,13 +195,14 @@ Registering a custom `ConditionalEffect`-wrapped enchantment effect component ty public static final DeferredHolder, DataComponentType>> CONDITIONAL_INCREMENT = ENCHANTMENT_COMPONENT_TYPES.register("conditional_increment", () -> DataComponentType.ConditionalEffectbuilder() - // The LootContextParamSet needed depends on what the enchantment is supposed to do. + // The ContextKeySet needed depends on what the enchantment is supposed to do. // This might be one of ENCHANTED_DAMAGE, ENCHANTED_ITEM, ENCHANTED_LOCATION, ENCHANTED_ENTITY, or HIT_BLOCK // since all of these bring the enchantment level into context (along with whatever other information is indicated). .persistent(ConditionalEffect.codec(Increment.CODEC, LootContextParamSets.ENCHANTED_DAMAGE)) .build()); ``` -The parameters to `ConditionalEffect.codec` are the codec for the generic `ConditionalEffect`, followed by some `LootContextParamSets` entry. + +The parameters to `ConditionalEffect.codec` are the codec for the generic `ConditionalEffect`, followed by some `ContextKeySet` entry. ## Enchantment Data Generation @@ -207,7 +211,7 @@ Enchantment JSON files can be created automatically using the [data generation] For more information on how `RegistrySetBuilder` and `DatapackBuiltinEntriesProvider` work, please see the article on [Data Generation for Datapack Registries]. - + ```java @@ -266,60 +270,60 @@ BUILDER.add( - + ```json5 // For more detail on each entry, please check the section above on the enchantment JSON format. { - // The anvil cost of the enchantment. - "anvil_cost": 2, - - // The text Component that specifies the enchantment's name. - "description": "Example Enchantment", - - // A map of the effect components associated with this enchantment and their values. - "effects": { - - }, - - // The maximum cost of the enchantment. - "max_cost": { - "base": 4, - "per_level_above_first": 2 - }, - - // The maximum level this enchantment can be. - "max_level": 3, - - // The minimum cost of the enchantment. - "min_cost": { - "base": 3, - "per_level_above_first": 1 - }, - - // A list of EquipmentSlotGroup aliases that this enchantment has effects in. - "slots": [ - "any" - ], - - // The set of items that this enchantment can be applied to using an anvil. - "supported_items": , - - // The weight of this enchantment. - "weight": 30 + // The anvil cost of the enchantment. + "anvil_cost": 2, + + // The text Component that specifies the enchantment's name. + "description": "Example Enchantment", + + // A map of the effect components associated with this enchantment and their values. + "effects": { + // + }, + + // The maximum cost of the enchantment. + "max_cost": { + "base": 4, + "per_level_above_first": 2 + }, + + // The maximum level this enchantment can be. + "max_level": 3, + + // The minimum cost of the enchantment. + "min_cost": { + "base": 3, + "per_level_above_first": 1 + }, + + // A list of EquipmentSlotGroup aliases that this enchantment has effects in. + "slots": [ + "any" + ], + + // The set of items that this enchantment can be applied to using an anvil. + "supported_items": /* */, + + // The weight of this enchantment. + "weight": 30 } ``` -[Data Components]: /docs/items/datacomponents -[Codec]: /docs/datastorage/codecs +[Data Components]: ../../../items/datacomponents.md +[Codec]: ../../../datastorage/codecs.md [Enchantment definition Minecraft wiki page]: https://minecraft.wiki/w/Enchantment_definition -[registered]: /docs/concepts/registries +[registered]: ../../../concepts/registries.md [Predicate]: https://minecraft.wiki/w/Predicate -[data generation]: /docs/resources/#data-generation +[data generation]: ../../../resources/index.md#data-generation [Data Generation for Datapack Registries]: https://docs.neoforged.net/docs/concepts/registries/#data-generation-for-datapack-registries [relevant minecraft wiki page]: https://minecraft.wiki/w/Enchantment_definition#Entity_effects [built-in enchantment effect components]: builtin.md -[LootContext]: /docs/resources/server/loottables/#loot-context \ No newline at end of file +[LootContext]: ../loottables/index.md#loot-context \ No newline at end of file diff --git a/docs/resources/server/loottables/custom.md b/docs/resources/server/loottables/custom.md index d926e89db..3a42417c1 100644 --- a/docs/resources/server/loottables/custom.md +++ b/docs/resources/server/loottables/custom.md @@ -59,7 +59,7 @@ public static final MapCodec CODEC = RecordCodecBuilder.mapCode ); ``` -We then use this codec in registration: +We then use this codec in [registration][registries]: ```java public static final DeferredRegister LOOT_POOL_ENTRY_TYPES = @@ -109,13 +109,13 @@ public record InvertedSignProvider(NumberProvider base) implements NumberProvide // Return a set of the loot context params used by this provider. See below for more information. // Since we have a base value, we just defer to the base. @Override - public Set> getReferencedContextParams() { + public Set> getReferencedContextParams() { return this.base.getReferencedContextParams(); } } ``` -Like with custom loot entry types, we then use this codec in registration: +Like with custom loot entry types, we then use this codec in [registration][registries]: ```java public static final DeferredRegister LOOT_NUMBER_PROVIDER_TYPES = @@ -162,7 +162,7 @@ public record InvertedSignLevelBasedValue(LevelBasedValue base) implements Level } ``` -And again, we then use the codec in registration, though this time directly: +And again, we then use the codec in [registration][registries], though this time directly: ```java public static final DeferredRegister> LEVEL_BASED_VALUES = @@ -187,19 +187,20 @@ public record HasXpLevelCondition(int level) implements LootItemCondition { // In our case, we want the KILLER_ENTITY to have at least our required level. @Override public boolean test(LootContext context) { - Entity entity = context.getParamOrNull(LootContextParams.KILLER_ENTITY); + @Nullable + Entity entity = context.getOptionalParameter(LootContextParams.KILLER_ENTITY); return entity instanceof Player player && player.experienceLevel >= level; } // Tell the game what parameters we expect from the loot context. Used in validation. @Override - public Set> getReferencedContextParams() { + public Set> getReferencedContextParams() { return ImmutableSet.of(LootContextParams.KILLER_ENTITY); } } ``` -We can register the condition type to the registry using the condition's codec: +We can [register][registries] the condition type to the registry using the condition's codec: ```java public static final DeferredRegister LOOT_CONDITION_TYPES = @@ -253,7 +254,7 @@ public class RandomEnchantmentWithLevelFunction extends LootItemConditionalFunct RandomSource random = context.getRandom(); List> stream = this.enchantments .map(HolderSet::stream) - .orElseGet(() -> context.getLevel().registryAccess().registryOrThrow(Registries.ENCHANTMENT).holders().map(Function.identity())) + .orElseGet(() -> context.getLevel().registryAccess().registryOrThrow(Registries.ENCHANTMENT).listElements().map(Function.identity())) .filter(e -> e.value().canEnchant(stack)) .toList(); Optional> optional = Util.getRandomSafe(list, random); @@ -270,7 +271,7 @@ public class RandomEnchantmentWithLevelFunction extends LootItemConditionalFunct } ``` -We can then register the function type to the registry using the function's codec: +We can then [register][registries] the function type to the registry using the function's codec: ```java public static final DeferredRegister LOOT_FUNCTION_TYPES = @@ -287,11 +288,11 @@ public class RandomEnchantmentWithLevelFunction extends LootItemConditionalFunct // other stuff here @Override - public LootItemFunctionType getType() { + public LootItemFunctionType getType() { return RANDOM_ENCHANTMENT_WITH_LEVEL.get(); } } ``` [codec]: ../../../datastorage/codecs.md -[registries]: ../../../concepts/registries.md +[registries]: ../../../concepts/registries.md#methods-for-registering diff --git a/docs/resources/server/loottables/glm.md b/docs/resources/server/loottables/glm.md index 31af786e1..46ad439c5 100644 --- a/docs/resources/server/loottables/glm.md +++ b/docs/resources/server/loottables/glm.md @@ -22,13 +22,13 @@ Example usage: ```json5 { - "replace": false, // must be present - "entries": [ - // represents a loot modifier in data/examplemod/loot_modifiers/example_glm_1.json - "examplemod:example_glm_1", - "examplemod:example_glm_2" - // ... - ] + "replace": false, // must be present + "entries": [ + // represents a loot modifier in data/examplemod/loot_modifiers/example_glm_1.json + "examplemod:example_glm_1", + "examplemod:example_glm_2" + // ... + ] } ``` @@ -48,15 +48,15 @@ An example usage may look something like this: ```json5 { - // This is the registry name of the loot modifier - "type": "examplemod:my_loot_modifier", - "conditions": [ - // Loot table conditions here - ], - // Extra properties specified by the codec - "field1": "somestring", - "field2": 10, - "field3": "minecraft:dirt" + // This is the registry name of the loot modifier + "type": "examplemod:my_loot_modifier", + "conditions": [ + // Loot table conditions here + ], + // Extra properties specified by the codec + "field1": "somestring", + "field2": 10, + "field3": "minecraft:dirt" } ``` @@ -117,7 +117,7 @@ public static final MapCodec CODEC = RecordCodecBuilder.mapCodec ); ``` -Then, we register the codec to the registry: +Then, we [register] the codec to the registry: ```java public static final DeferredRegister> GLOBAL_LOOT_MODIFIER_SERIALIZERS = @@ -137,9 +137,9 @@ This loot modifier rolls a second loot table and adds the results to the loot ta ```json5 { - "type": "neoforge:add_table", - "conditions": [], // the required loot conditions - "table": "minecraft:chests/abandoned_mineshaft" // the second table to roll + "type": "neoforge:add_table", + "conditions": [], // the required loot conditions + "table": "minecraft:chests/abandoned_mineshaft" // the second table to roll } ``` @@ -150,20 +150,20 @@ GLMs can be [datagenned][datagen]. This is done by subclassing `GlobalLootModifi ```java public class MyGlobalLootModifierProvider extends GlobalLootModifierProvider { // Get the PackOutput from GatherDataEvent. - public MyGlobalLootModifierProvider(PackOutput output) { - super(output, ExampleMod.MOD_ID); + public MyGlobalLootModifierProvider(PackOutput output, CompletableFuture registries) { + super(output, registries, ExampleMod.MOD_ID); } @Override protected void start() { // Call #add to add a new GLM. This also adds a corresponding entry in global_loot_modifiers.json. - add( + this.add( // The name of the modifier. This will be the file name. "my_loot_modifier_instance", // The loot modifier to add. For the sake of example, we add a weather loot condition. new MyLootModifier(new LootItemCondition[] { WeatherCheck.weather().setRaining(true).build() - }, "somestring", 10, Items.DIRT); + }, "somestring", 10, Items.DIRT), // A list of data load conditions. Note that these are unrelated to the loot conditions // specified on the modifier itself. For the sake of example, we add a mod loaded condition. // An overload of #add is available that accepts a vararg of conditions instead of a list. @@ -178,7 +178,10 @@ And like all data providers, you must register the provider to `GatherDataEvent` ```java @SubscribeEvent public static void onGatherData(GatherDataEvent event) { - event.getGenerator().addProvider(event.includeServer(), MyGlobalLootModifierProvider::new); + event.getGenerator().addProvider( + event.includeServer(), + output -> new MyGlobalLootModifierProvider(output, event.getLookupProvider()) + ); } ``` diff --git a/docs/resources/server/loottables/index.md b/docs/resources/server/loottables/index.md index 737e24a77..1419b2c78 100644 --- a/docs/resources/server/loottables/index.md +++ b/docs/resources/server/loottables/index.md @@ -5,10 +5,10 @@ Loot tables are data files that are used to define randomized loot drops. A loot Minecraft uses loot tables at various points in the game, including block drops, entity drops, chest loot, fishing loot, and many others. How a loot table is referenced depends on the context: - Every block will, by default, receive an associated loot table, located at `:blocks/`. This can be disabled by calling `#noLootTable` on the block's `Properties`, resulting in no loot table being created and the block dropping nothing; this is mainly done by air-like or technical blocks. -- Every subclass of `LivingEntity` that is not in the `MobCategory.MISC` category (as determined by `EntityType#getCategory`) will, by default, receive an associated loot table, located at `:entities/`. This can be changed by overriding `#getLootTable` if you are directly extending `LivingEntity`, or by overriding `#getDefaultLootTable` if you are extending `Mob` or a subclass thereof. For example, sheep use this to roll different loot tables depending on their wool color. +- Every entity that does not call `EntityType.Builder#noLootTable` (which is typically entities in `MobCategory#MISC`) will, by default, receive an associated loot table, located at `:entities/`. This can be changed by overriding `#getLootTable`. For example, sheep use this to roll different loot tables depending on their wool color. - Chests in structures specify their loot table in their block entity data. Minecraft stores all chest loot tables in `minecraft:chests/`; it is recommended, but not required to follow this practice in mods. - The loot tables for gift items that villagers may throw at players after a raid are defined in the [`neoforge:raid_hero_gifts` data map][raidherogifts]. -- Other loot tables, for example the fishing loot table, are retrieved when needed from `level.getServer().reloadableRegistries().getLootTable(lootTableId)`. A list of all vanilla loot table locations can be found in `BuiltInLootTables`. +- Other loot tables, for example the fishing loot table, are retrieved when needed from `level.getServer().reloadableRegistries().getLootTable(lootTableKey)`. A list of all vanilla loot table locations can be found in `BuiltInLootTables`. :::warning Loot tables should generally only be created for stuff that belongs to your mod. For modifying existing loot tables, [global loot modifiers (GLMs)][glm] should be used instead. @@ -78,7 +78,7 @@ Modders can also register [custom number providers][customnumber] and [custom le ## Loot Parameters -A loot parameter, known internally as a `LootContextParam`, is a parameter provided to a loot table when rolled, where `T` is the type of the provided parameter, for example `BlockPos` or `Entity`. They can be used by [loot conditions][lootcondition] and [loot functions][lootfunction]. For example, the `minecraft:killed_by_player` loot condition checks for the presence of the `minecraft:player` parameter. +A loot parameter, known internally as a `ContextKey`, is a parameter provided to a loot table when rolled, where `T` is the type of the provided parameter, for example `BlockPos` or `Entity`. They can be used by [loot conditions][lootcondition] and [loot functions][lootfunction]. For example, the `minecraft:killed_by_player` loot condition checks for the presence of the `minecraft:player` parameter. Minecraft provides the following loot parameters: @@ -95,7 +95,7 @@ Minecraft provides the following loot parameters: - `minecraft:direct_attacking_entity`: A direct attacking entity associated with the loot table. For example, if the attacking entity were a skeleton, the direct attacking entity would be the arrow. Access via `LootContextParams.DIRECT_ATTACKING_ENTITY`. - `minecraft:last_damage_player`: A player associated with the loot table, typically the player that last attacked the killed entity, even if the player kill was indirect (for example: the player tapped the entity, and it was then killed by spikes). Used e.g. for player-kill-only drops. Access via `LootContextParams.LAST_DAMAGE_PLAYER`. -Custom loot parameters can be created by calling `new LootContextParam` with the desired id. Since they are merely resource location wrappers, they do not need to be registered. +Custom loot parameters can be created by calling `new ContextKey` with the desired id. Since they are merely resource location wrappers, they do not need to be registered. ### Entity Targets @@ -110,7 +110,7 @@ For example, the `minecraft:entity_properties` loot condition accepts an entity ### Loot Parameter Sets -Loot parameter sets, also known as loot table types and known as `LootContextParamSet`s in code, are a collection of required and optional loot parameters. Despite their name, they are not `Set`s (not even `Collection`s). Rather, they are a wrapper around two `Set>`s, one holding the required parameters (`#getRequired`) and one holding the required and optional parameters (`#getAllowed`). They are used to validate that users of loot parameters only use the parameters that can be expected to be available, and to verify that the required parameters are present when rolling a table. Besides that, they are also used in advancement and enchantment logic. +Loot parameter sets, also known as loot table types and known as `ContextKeySet`s in code, are a collection of required and optional loot parameters. Despite their name, they are not `Set`s (not even `Collection`s). Rather, they are a wrapper around two `Set>`s, one holding the required parameters (`#required`) and one holding the optional parameters (`#allowed`). They are used to validate that users of loot parameters only use the parameters that can be expected to be available, and to verify that the required parameters are present when rolling a table. Besides that, they are also used in advancement and enchantment logic. Vanilla provides the following loot parameter sets (required parameters are **bold**, optional parameters are _in italics_; the in-code names are constants in `LootContextParamSets`): @@ -124,10 +124,10 @@ Vanilla provides the following loot parameter sets (required parameters are **bo | `minecraft:block_use` | `BLOCK_USE` | **`minecraft:origin`**, **`minecraft:block_state`**, **`minecraft:this_entity`** | No vanilla uses. | | `minecraft:hit_block` | `HIT_BLOCK` | **`minecraft:origin`**, **`minecraft:enchantment_level`**, **`minecraft:block_state`**, **`minecraft:this_entity`** | The channeling enchantment. | | `minecraft:chest` | `CHEST` | **`minecraft:origin`**, _`minecraft:this_entity`_, _`minecraft:attacking_entity`_ | Loot chests and similar containers, loot chest minecarts. | -| `minecraft:archaeology` | `ARCHAEOLOGY` | **`minecraft:origin`**, _`minecraft:this_entity`_ | Archaeology. | -| `minecraft:vault` | `VAULT` | **`minecraft:origin`**, _`minecraft:this_entity`_ | Trial chamber vault rewards. | +| `minecraft:archaeology` | `ARCHAEOLOGY` | **`minecraft:origin`**, **`minecraft:this_entity`**, **`minecraft:tool`** | Archaeology. | +| `minecraft:vault` | `VAULT` | **`minecraft:origin`**, _`minecraft:this_entity`_, _`minecraft:tool`_ | Trial chamber vault rewards. | | `minecraft:entity` | `ENTITY` | **`minecraft:origin`**, **`minecraft:this_entity`**, **`minecraft:damage_source`**, _`minecraft:attacking_entity`_, _`minecraft:direct_attacking_entity`_, _`minecraft:last_damage_player`_ | Entity kills. | -| `minecraft:shearing` | `SHEARING` | **`minecraft:origin`**, _`minecraft:this_entity`_ | Shearing entities, e.g. sheep. | +| `minecraft:shearing` | `SHEARING` | **`minecraft:origin`**, **`minecraft:this_entity`**, **`minecraft:tool`** | Shearing entities, e.g. sheep. | | `minecraft:equipment` | `EQUIPMENT` | **`minecraft:origin`**, **`minecraft:this_entity`** | Entity equipment for e.g. zombies. | | `minecraft:gift` | `GIFT` | **`minecraft:origin`**, **`minecraft:this_entity`** | Raid hero gifts. | | `minecraft:barter` | `PIGLIN_BARTER` | **`minecraft:this_entity`** | Piglin bartering. | @@ -146,7 +146,7 @@ The loot context is an object containing situational information for rolling loo - The `ServerLevel` the loot table is rolled in. Get via `#getLevel`. - The `RandomSource` used to roll the loot table. Get via `#getRandom`. -- The loot parameters. Check presence using `#hasParam`, and get single parameters using `#getParam`. +- The loot parameters. Check presence using `#hasParameter`, and get single parameters using `#getParameter`. - The luck value, used for calculating bonus rolls and quality values. Usually populated via the entity's luck attribute. Get via `#getLuck`. - The dynamic drops consumers. See [above][entry] for more information. Set via `#addDynamicDrops`. No getter available. @@ -164,40 +164,40 @@ An example loot table could have the following format: ```json5 { - "type": "chest", // loot parameter set - "neoforge:conditions": [ - // data load conditions - ], - "functions": [ - // table-wide loot functions - ], - "pools": [ // list of loot pools - { - "rolls": 1, // amount of rolls of the loot table, using 5 here will yield 5 results from the pool - "bonus_rolls": 0.5, // amount of bonus rolls - "name": "my_pool", - "conditions": [ - // pool-wide loot conditions - ], - "functions": [ - // pool-wide loot functions - ], - "entries": [ // list of loot table entries + "type": "chest", // loot parameter set + "neoforge:conditions": [ + // data load conditions + ], + "functions": [ + // table-wide loot functions + ], + "pools": [ // list of loot pools { - "type": "minecraft:item", // loot entry type - "name": "minecraft:dirt", // type-specific properties, for example the name of the item - "weight": 3, // weight of an entry - "quality": 1, // quality of an entry - "conditions": [ - // entry-wide loot conditions - ], - "functions": [ - // entry-wide loot functions - ] + "rolls": 1, // amount of rolls of the loot table, using 5 here will yield 5 results from the pool + "bonus_rolls": 0.5, // amount of bonus rolls + "name": "my_pool", + "conditions": [ + // pool-wide loot conditions + ], + "functions": [ + // pool-wide loot functions + ], + "entries": [ // list of loot table entries + { + "type": "minecraft:item", // loot entry type + "name": "minecraft:dirt", // type-specific properties, for example the name of the item + "weight": 3, // weight of an entry + "quality": 1, // quality of an entry + "conditions": [ + // entry-wide loot conditions + ], + "functions": [ + // entry-wide loot functions + ] + } + ] } - ] - } - ] + ] } ``` @@ -226,7 +226,7 @@ builder.withParameter(LootContextParams.ORIGIN, position); // This variant can accept null as the value, in which case an existing value for that parameter will be removed. builder.withOptionalParameter(LootContextParams.ORIGIN, null); // Add a dynamic drop. -builder.withDynamicDrop(ResourceLocation.fromNamespaceAndPath("examplemod", "example_dynamic_drop"), stack -> { +builder.withDynamicDrop(ResourceLocation.fromNamespaceAndPath("examplemod", "example_dynamic_drop"), stackAcceptor -> { // some logic here }); // Set our luck value. Assumes that a player is available. Contexts without a player should use 0 here. @@ -237,7 +237,7 @@ Finally, we can create the `LootParams` from the builder and use them to roll th ```java // Specify a loot context param set here if you want. -LootParams params = builder.create(LootContextParamSet.EMPTY); +LootParams params = builder.create(LootContextParamSets.EMPTY); // Get the loot table. LootTable table = level.getServer().reloadableRegistries().getLootTable(location); // Actually roll the loot table. @@ -260,14 +260,16 @@ Loot tables can be [datagenned][datagen] by registering a `LootTableProvider` an public static void onGatherData(GatherDataEvent event) { event.getGenerator().addProvider( event.includeServer(), - output -> new MyLootTableProvider( + output -> new LootTableProvider( output, // A set of required table resource locations. These are later verified to be present. // It is generally not recommended for mods to validate existence, // therefore we pass in an empty set. Set.of(), // A list of sub provider entries. See below for what values to use here. - List.of(...) + List.of(...), + // The registry access + event.getLookupProvider() ) ); } @@ -281,7 +283,7 @@ public static void onGatherData(GatherDataEvent event) { public class MyLootTableSubProvider implements LootTableSubProvider { // The parameter is provided by the lambda (see below). It can be stored and used to lookup other registry entries. public MyLootTableSubProvider(HolderLookup.Provider lookupProvider) { - super(lookupProvider); + // Store the lookupProvider in a field } @Override @@ -296,12 +298,12 @@ public class MyLootTableSubProvider implements LootTableSubProvider { .apply(...) // Add a loot pool-level condition. This example only rolls the pool if it is raining. .when(WeatherCheck.weather().setRaining(true)) - // Set the amount of rolls and bonus rolls, respectively. - // Both of these methods utilize a number provider. - .setRolls(UniformGenerator.between(5, 9)) - .setBonusRolls(ConstantValue.exactly(1)) - // Add a loot entry. This example returns an item loot entry. See below for more loot entries. - .add(LootItem.lootTableItem(Items.DIRT)) + // Set the amount of rolls and bonus rolls, respectively. + // Both of these methods utilize a number provider. + .setRolls(UniformGenerator.between(5, 9)) + .setBonusRolls(ConstantValue.exactly(1)) + // Add a loot entry. This example returns an item loot entry. See below for more loot entries. + .add(LootItem.lootTableItem(Items.DIRT)) ) ); } @@ -311,7 +313,7 @@ public class MyLootTableSubProvider implements LootTableSubProvider { Once we have our loot table sub provider, we add it to the constructor of our loot provider, like so: ```java -super(output, Set.of(), List.of( +new LootTableProvider(output, Set.of(), List.of( new SubProviderEntry( // A reference to the sub provider's constructor. // This is a Function. @@ -319,8 +321,9 @@ super(output, Set.of(), List.of( // An associated loot context set. If you're unsure what to use, use empty. LootContextParamSets.EMPTY ), -// other sub providers here (if applicable) -)); + // other sub providers here (if applicable) + ), lookupProvider +); ``` ### `BlockLootSubProvider` @@ -355,10 +358,10 @@ public class MyBlockLootSubProvider extends BlockLootSubProvider { @Override protected void generate() { // Equivalent to calling add(MyBlocks.EXAMPLE_BLOCK.get(), createSingleItemTable(MyBlocks.EXAMPLE_BLOCK.get())); - dropSelf(MyBlocks.EXAMPLE_BLOCK.get()); + this.dropSelf(MyBlocks.EXAMPLE_BLOCK.get()); // Add a table with a silk touch only loot table. - add(MyBlocks.EXAMPLE_SILK_TOUCHABLE_BLOCK.get(), - createSilkTouchOnlyTable(MyBlocks.EXAMPLE_SILK_TOUCHABLE_BLOCK.get())); + this.add(MyBlocks.EXAMPLE_SILK_TOUCHABLE_BLOCK.get(), + this.createSilkTouchOnlyTable(MyBlocks.EXAMPLE_SILK_TOUCHABLE_BLOCK.get())); // other loot table additions here } } @@ -367,10 +370,11 @@ public class MyBlockLootSubProvider extends BlockLootSubProvider { We then add our sub provider to the loot table provider's constructor like any other sub provider: ```java -super(output, Set.of(), List.of(new SubProviderEntry( +new LootTableProvider(output, Set.of(), List.of(new SubProviderEntry( MyBlockLootTableSubProvider::new, LootContextParamSets.BLOCK // it makes sense to use BLOCK here -))); + )), lookupProvider +); ``` ### `EntityLootSubProvider` @@ -394,7 +398,7 @@ public class MyEntityLootSubProvider extends EntityLootSubProvider { @Override protected void generate() { - add(MyEntities.EXAMPLE_ENTITY.get(), LootTable.lootTable()); + this.add(MyEntities.EXAMPLE_ENTITY.get(), LootTable.lootTable()); // other loot table additions here } } @@ -403,10 +407,11 @@ public class MyEntityLootSubProvider extends EntityLootSubProvider { And again, we then add our sub provider to the loot table provider's constructor: ```java -super(output, Set.of(), List.of(new SubProviderEntry( +new LootTableProvider(output, Set.of(), List.of(new SubProviderEntry( MyEntityLootTableSubProvider::new, LootContextParamSets.ENTITY -))); + )), lookupProvider +); ``` [advancement]: ../advancements.md diff --git a/docs/resources/server/loottables/lootconditions.md b/docs/resources/server/loottables/lootconditions.md index fddb6870d..2a35f1fe0 100644 --- a/docs/resources/server/loottables/lootconditions.md +++ b/docs/resources/server/loottables/lootconditions.md @@ -8,10 +8,10 @@ This condition accepts another condition and inverts its result. Requires whatev ```json5 { - "condition": "minecraft:inverted", - "term": { - // Some other loot condition. - } + "condition": "minecraft:inverted", + "term": { + // Some other loot condition. + } } ``` @@ -23,18 +23,18 @@ This condition accepts any number of other conditions and returns true if all su ```json5 { - "condition": "minecraft:all_of", - "terms": [ - { - // A loot condition. - }, - { - // Another loot condition. - }, - { - // Yet another loot condition. - } - ] + "condition": "minecraft:all_of", + "terms": [ + { + // A loot condition. + }, + { + // Another loot condition. + }, + { + // Yet another loot condition. + } + ] } ``` @@ -46,18 +46,18 @@ This condition accepts any number of other conditions and returns true if at lea ```json5 { - "condition": "minecraft:any_of", - "terms": [ - { - // A loot condition. - }, - { - // Another loot condition. - }, - { - // Yet another loot condition. - } - ] + "condition": "minecraft:any_of", + "terms": [ + { + // A loot condition. + }, + { + // Another loot condition. + }, + { + // Yet another loot condition. + } + ] } ``` @@ -69,13 +69,13 @@ This condition accepts a [number provider][numberprovider] representing a chance ```json5 { - "condition": "minecraft:random_chance", - // A constant 50% chance for the condition to apply. - "chance": 0.5 + "condition": "minecraft:random_chance", + // A constant 50% chance for the condition to apply. + "chance": 0.5 } ``` -During datagen, call `RandomChance#randomChance` with the number provider or a (constant) float value to construct a builder for this condition. +During datagen, call `LootItemRandomChanceCondition#randomChance` with the number provider or a (constant) float value to construct a builder for this condition. ## `minecraft:random_chance_with_enchanted_bonus` @@ -83,16 +83,16 @@ This condition accepts an enchantment id, a [`LevelBasedValue`][numberprovider] ```json5 { - "condition": "minecraft:random_chance_with_enchanted_bonus", - // Add a 20% chance per looting level to succeed. - "enchantment": "minecraft:looting", - "enchanted_chance": { - "type": "linear", - "base": 0.2, - "per_level_above_first": 0.2 - }, - // Always fail if the looting enchantment is not present. - "unenchanted_chance": 0.0 + "condition": "minecraft:random_chance_with_enchanted_bonus", + // Add a 20% chance per looting level to succeed. + "enchantment": "minecraft:looting", + "enchanted_chance": { + "type": "linear", + "base": 0.2, + "per_level_above_first": 0.2 + }, + // Always fail if the looting enchantment is not present. + "unenchanted_chance": 0.0 } ``` @@ -104,18 +104,18 @@ This condition accepts a [number provider][numberprovider] and an `IntRange`, re ```json5 { - "condition": "minecraft:value_check", - // May be any number provider. - "value": { - "type": "minecraft:uniform", - "min": 0.0, - "max": 10.0 - }, - // A range with min/max values. - "range": { - "min": 2.0, - "max": 5.0 - } + "condition": "minecraft:value_check", + // May be any number provider. + "value": { + "type": "minecraft:uniform", + "min": 0.0, + "max": 10.0 + }, + // A range with min/max values. + "range": { + "min": 2.0, + "max": 5.0 + } } ``` @@ -127,16 +127,16 @@ This condition checks if the world time is within an `IntRange`. Optionally, a ` ```json5 { - "condition": "minecraft:time_check", - // Optional, can be omitted. If omitted, no modulo operation will take place. - // We use 24000 here, which is the length of one in-game day/night cycle. - "period": 24000, - // A range with min/max values. This example checks if the time is between 0 and 12000. - // Combined with the modulo operand of 24000 specified above, this example checks if it is currently daytime. - "value": { - "min": 0, - "max": 12000 - } + "condition": "minecraft:time_check", + // Optional, can be omitted. If omitted, no modulo operation will take place. + // We use 24000 here, which is the length of one in-game day/night cycle. + "period": 24000, + // A range with min/max values. This example checks if the time is between 0 and 12000. + // Combined with the modulo operand of 24000 specified above, this example checks if it is currently daytime. + "value": { + "min": 0, + "max": 12000 + } } ``` @@ -148,13 +148,13 @@ This condition checks the current weather for raining and thundering. ```json5 { - "condition": "minecraft:weather_check", - // Optional. If unspecified, the rain state will not be checked. - "raining": true, - // Optional. If unspecified, the thundering state will not be checked. - // Specifying "raining": true and "thundering": true is functionally equivalent to just specifying - // "thundering": true, since it is always raining when a thunderstorm occurs. - "thundering": false + "condition": "minecraft:weather_check", + // Optional. If unspecified, the rain state will not be checked. + "raining": true, + // Optional. If unspecified, the thundering state will not be checked. + // Specifying "raining": true and "thundering": true is functionally equivalent to just specifying + // "thundering": true, since it is always raining when a thunderstorm occurs. + "thundering": false } ``` @@ -166,16 +166,16 @@ This condition accepts a `LocationPredicate` and an optional offset value for ea ```json5 { - "condition": "minecraft:location_check", - "predicate": { - // Succeed if our target is anywhere in the nether. - "dimension": "the_nether" - }, - // Optional position offset values. Only relevant if you are checking the position in some way. - // Must either be provided all at once, or not at all. - "offsetX": 10, - "offsetY": 10, - "offsetZ": 10 + "condition": "minecraft:location_check", + "predicate": { + // Succeed if our target is anywhere in the nether. + "dimension": "the_nether" + }, + // Optional position offset values. Only relevant if you are checking the position in some way. + // Must either be provided all at once, or not at all. + "offsetX": 10, + "offsetY": 10, + "offsetZ": 10 } ``` @@ -187,15 +187,15 @@ This condition checks for the specified block state properties to have the speci ```json5 { - "condition": "minecraft:block_state_property", - // The expected block. If this does not match the block that is actually broken, the condition fails. - "block": "minecraft:oak_slab", - // The block state properties to match. Unspecified properties can have either value. - // In this example, we want to only succeed if a top slab - waterlogged or not - is broken. - // If this specifies properties not present on the block, a log warning will be printed. - "properties": { - "type": "top" - } + "condition": "minecraft:block_state_property", + // The expected block. If this does not match the block that is actually broken, the condition fails. + "block": "minecraft:oak_slab", + // The block state properties to match. Unspecified properties can have either value. + // In this example, we want to only succeed if a top slab - waterlogged or not - is broken. + // If this specifies properties not present on the block, a log warning will be printed. + "properties": { + "type": "top" + } } ``` @@ -207,7 +207,7 @@ This condition randomly destroys the drops. The chance for drops to survive is 1 ```json5 { - "condition": "minecraft:survives_explosion" + "condition": "minecraft:survives_explosion" } ``` @@ -215,18 +215,18 @@ During datagen, call `ExplosionCondition#survivesExplosion` to construct a build ## `minecraft:match_tool` -This condition accepts an `ItemPredicate` that is checked against the `tool` loot parameter. An `ItemPredicate` can specify a list of valid item ids (`items`), a min/max range for the item count (`count`), a `DataComponentPredicate` (`components`) and an `ItemSubPredicate` (`predicates`); all fields are optional. Requires the `minecraft:tool` loot parameter, always failing if that parameter is absent. +This condition accepts an `ItemPredicate` that is checked against the `tool` loot parameter. An `ItemPredicate` can specify a list of valid item ids (`items`), a min/max range for the item count (`count`), a `DataComponentPredicate` (`components`) and a map of `ItemSubPredicate`s (`predicates`); all fields are optional. Requires the `minecraft:tool` loot parameter, always failing if that parameter is absent. ```json5 { - "condition": "minecraft:match_tool", - // Match a netherite pickaxe or axe. - "predicate": { - "items": [ - "minecraft:netherite_pickaxe", - "minecraft:netherite_axe" - ] - } + "condition": "minecraft:match_tool", + // Match a netherite pickaxe or axe. + "predicate": { + "items": [ + "minecraft:netherite_pickaxe", + "minecraft:netherite_axe" + ] + } } ``` @@ -238,9 +238,9 @@ This condition returns whether an enchantment is active or not. Requires the `mi ```json5 { - "condition": "minecraft:enchantment_active", - // Whether the enchantment should be active (true) or not (false). - "active": true + "condition": "minecraft:enchantment_active", + // Whether the enchantment should be active (true) or not (false). + "active": true } ``` @@ -252,12 +252,12 @@ This condition is similar to `minecraft:random_chance_with_enchanted_bonus`, but ```json5 { - "condition": "minecraft:table_bonus", - // Apply the bonus if the fortune enchantment is present. - "enchantment": "minecraft:fortune", - // The chances to use per level. This example has a 20% chance of succeeding if unenchanted, - // 30% if enchanted at level 1, and 60% if enchanted at level 2 or above. - "chances": [0.2, 0.3, 0.6] + "condition": "minecraft:table_bonus", + // Apply the bonus if the fortune enchantment is present. + "enchantment": "minecraft:fortune", + // The chances to use per level. This example has a 20% chance of succeeding if unenchanted, + // 30% if enchanted at level 1, and 60% if enchanted at level 2 or above. + "chances": [0.2, 0.3, 0.6] } ``` @@ -265,20 +265,20 @@ During datagen, call `BonusLevelTableCondition#bonusLevelFlatChance` with the en ## `minecraft:entity_properties` -This condition checks a given `EntityPredicate` against an [entity target][entitytarget]. The `EntityPredicate` can check the entity type, mob effects, nbt values, equipment, location etc. +This condition checks a given `EntityPredicate` against an [entity target][entitytarget]. The `EntityPredicate` can check the entity type, mob effects, nbt values, equipment, location, etc. ```json5 { - "condition": "minecraft:entity_properties", - // The entity target to use. Valid values are "this", "attacker", "direct_attacker" or "attacking_player". - // These correspond to the "this_entity", "attacking_entity", "direct_attacking_entity" and - // "last_damage_player" loot parameters, respectively. - "entity": "attacker", - // Only succeed if the target is a pig. The predicate may also be empty, this can be used - // to check whether the specified entity target is set at all. - "predicate": { - "type": "minecraft:pig" - } + "condition": "minecraft:entity_properties", + // The entity target to use. Valid values are "this", "attacker", "direct_attacker" or "attacking_player". + // These correspond to the "this_entity", "attacking_entity", "direct_attacking_entity" and + // "last_damage_player" loot parameters, respectively. + "entity": "attacker", + // Only succeed if the target is a pig. The predicate may also be empty, this can be used + // to check whether the specified entity target is set at all. + "predicate": { + "type": "minecraft:pig" + } } ``` @@ -290,13 +290,13 @@ This condition checks a given `DamageSourcePredicate` against the damage source ```json5 { - "condition": "minecraft:damage_source_properties", - "predicate": { - // Check whether the source entity is a zombie. - "source_entity": { - "type": "zombie" + "condition": "minecraft:damage_source_properties", + "predicate": { + // Check whether the source entity is a zombie. + "source_entity": { + "type": "zombie" + } } - } } ``` @@ -308,7 +308,7 @@ This condition determines whether the kill was a player kill. Used by some entit ```json5 { - "condition": "minecraft:killed_by_player" + "condition": "minecraft:killed_by_player" } ``` @@ -320,22 +320,22 @@ This condition checks the [entity target][entitytarget]'s scoreboard. Requires t ```json5 { - "condition": "minecraft:entity_scores" - // The entity target to use. Valid values are "this", "attacker", "direct_attacker" or "attacking_player". - // These correspond to the "this_entity", "attacking_entity", "direct_attacking_entity" and - // "last_damage_player" loot parameters, respectively. - "entity": "attacker", - // A list of scoreboard values that must be in the given ranges. - "scores": { - "score1": { - "min": 0, - "max": 100 - }, - "score2": { - "min": 10, - "max": 20 + "condition": "minecraft:entity_scores" + // The entity target to use. Valid values are "this", "attacker", "direct_attacker" or "attacking_player". + // These correspond to the "this_entity", "attacking_entity", "direct_attacking_entity" and + // "last_damage_player" loot parameters, respectively. + "entity": "attacker", + // A list of scoreboard values that must be in the given ranges. + "scores": { + "score1": { + "min": 0, + "max": 100 + }, + "score2": { + "min": 10, + "max": 20 + } } - } } ``` @@ -347,9 +347,9 @@ This condition references a predicate file and returns its result. See [Item Pre ```json5 { - "condition": "minecraft:reference", - // Refers to the predicate file at data/examplemod/predicate/example_predicate.json. - "name": "examplemod:example_predicate" + "condition": "minecraft:reference", + // Refers to the predicate file at data/examplemod/predicate/example_predicate.json. + "name": "examplemod:example_predicate" } ``` @@ -361,9 +361,9 @@ This condition only returns true if the surrounding loot table id matches. This ```json5 { - "condition": "neoforge:loot_table_id", - // Will only apply when the loot table is for dirt - "loot_table_id": "minecraft:blocks/dirt" + "condition": "neoforge:loot_table_id", + // Will only apply when the loot table is for dirt + "loot_table_id": "minecraft:blocks/dirt" } ``` @@ -375,9 +375,9 @@ This condition only returns true if the item in the `tool` loot context paramete ```json5 { - "condition": "neoforge:can_item_perform_ability", - // Will only apply if the tool can strip a log like an axe - "ability": "axe_strip" + "condition": "neoforge:can_item_perform_ability", + // Will only apply if the tool can strip a log like an axe + "ability": "axe_strip" } ``` diff --git a/docs/resources/server/loottables/lootfunctions.md b/docs/resources/server/loottables/lootfunctions.md index 8b5190564..0cc4df8cf 100644 --- a/docs/resources/server/loottables/lootfunctions.md +++ b/docs/resources/server/loottables/lootfunctions.md @@ -14,9 +14,9 @@ Sets a different item to use in the result item stack. ```json5 { - "function": "minecraft:set_item", - // The item to use. - "item": "minecraft:dirt" + "function": "minecraft:set_item", + // The item to use. + "item": "minecraft:dirt" } ``` @@ -28,15 +28,15 @@ Sets an item count to use in the result item stack. Uses a [number provider][num ```json5 { - "function": "minecraft:set_count", - // The count to use. - "count": { - "type": "minecraft:uniform", - "min": 1, - "max": 3 - }, - // Whether to add to the existing value instead of setting it. Optional, defaults to false. - "add": true + "function": "minecraft:set_count", + // The count to use. + "count": { + "type": "minecraft:uniform", + "min": 1, + "max": 3 + }, + // Whether to add to the existing value instead of setting it. Optional, defaults to false. + "add": true } ``` @@ -48,7 +48,7 @@ Applies an explosion decay. The item has a chance of 1 / `explosion_radius` to " ```json5 { - "function": "minecraft:explosion_decay" + "function": "minecraft:explosion_decay" } ``` @@ -60,11 +60,11 @@ Clamps the count of the item stack between a given `IntRange`. ```json5 { - "function": "minecraft:limit_count", - // The limit to use. Can have a min, a max, or both. - "limit": { - "max": 32 - } + "function": "minecraft:limit_count", + // The limit to use. Can have a min, a max, or both. + "limit": { + "max": 32 + } } ``` @@ -76,10 +76,10 @@ Sets custom NBT data on the item stack. ```json5 { - "function": "minecraft:set_custom_data", - "tag": { - "exampleproperty": 0 - } + "function": "minecraft:set_custom_data", + "tag": { + "exampleproperty": 0 + } } ``` @@ -95,30 +95,30 @@ Copies custom NBT data from a block entity or entity source to the item stack. U ```json5 { - "function": "minecraft:copy_custom_data", - // The source to use. Valid values are either an entity target, "block_entity" to use the loot context's - // block entity parameter, or be "storage" for command storage. If this is "storage", it can instead be a - // JSON object that additionally specify the command storage path to be used. - "source": "this", - // Example for using "storage". - "source": { - "type": "storage", - "source": "examplepath" - }, - // The copy operation(s). - "ops": [ - { - // The source and target paths. In this example, we copy from "src" in the source to "dest" in the target. - "source": "src", - "target": "dest", - // A merging strategy. Valid values are "replace", "append", and "merge". - "op": "merge" - } - ] + "function": "minecraft:copy_custom_data", + // The source to use. Valid values are either an entity target, "block_entity" to use the loot context's + // block entity parameter, or be "storage" for command storage. If this is "storage", it can instead be a + // JSON object that additionally specify the command storage path to be used. + "source": "this", + // Example for using "storage". + "source": { + "type": "storage", + "source": "examplepath" + }, + // The copy operation(s). + "ops": [ + { + // The source and target paths. In this example, we copy from "src" in the source to "dest" in the target. + "source": "src", + "target": "dest", + // A merging strategy. Valid values are "replace", "append", and "merge". + "op": "merge" + } + ] } ``` -During datagen, call `CopyCustomDataFunction#copy` with the desired source and target values, as well as a merging strategy (optional, defaults to `replace`), to construct a builder for this function. +During datagen, call `CopyCustomDataFunction#copyData` with the associated `NbtProvider` to get the builder. Then call `Builder#copy` with the desired source and target values, as well as a merging strategy (optional, defaults to `replace`), to construct a builder for this function. ## `minecraft:set_components` @@ -126,13 +126,13 @@ Sets [data component][datacomponent] values on the item stack. Most vanilla use ```json5 { - "function": "minecraft:set_components", - // Any component can be used. In this example, we set the dyed color of the item to red. - "components": { - "dyed_color": { - "rgb": 0xff0000 + "function": "minecraft:set_components", + // Any component can be used. In this example, we set the dyed color of the item to red. + "components": { + "dyed_color": { + "rgb": 16711680 + } } - } } ``` @@ -144,13 +144,13 @@ Copies [data component][datacomponent] values from a block entity to the item st ```json5 { - "function": "minecraft:copy_components", - // The system is designed to allow multiple sources, however for now, there are only block entities available. - "source": "block_entity", - // By default, all components are copied. The "exclude" list allows excluding certain components, and the - // "include" list allows explicitly re-including components. Both fields are optional. - "exclude": [], - "include": [] + "function": "minecraft:copy_components", + // The system is designed to allow multiple sources, however for now, there are only block entities available. + "source": "block_entity", + // By default, all components are copied. The "exclude" list allows excluding certain components, and the + // "include" list allows explicitly re-including components. Both fields are optional. + "exclude": [], + "include": [] } ``` @@ -162,13 +162,13 @@ Copies block state properties into the item stack's `block_state` [data componen ```json5 { - "function": "minecraft:copy_state", - // The expected block. If this does not match the block that is actually broken, the function does not run. - "block": "minecraft:oak_slab", - // The block state properties to save. - "properties": { - "type": "top" - } + "function": "minecraft:copy_state", + // The expected block. If this does not match the block that is actually broken, the function does not run. + "block": "minecraft:oak_slab", + // The block state properties to save. + "properties": { + "type": "top" + } } ``` @@ -180,20 +180,20 @@ Sets contents of the item stack. ```json5 { - "function": "minecraft:set_contents", - // The contents component to use. Valid values are "container", "bundle_contents" and "charged_projectiles". - "component": "container", - // A list of loot entries to add to the contents. - "entries": [ - { - "type": "minecraft:empty", - "weight": 3 - }, - { - "type": "minecraft:item", - "item": "minecraft:arrow" - } - ] + "function": "minecraft:set_contents", + // The contents component to use. Valid values are "container", "bundle_contents" and "charged_projectiles". + "component": "container", + // A list of loot entries to add to the contents. + "entries": [ + { + "type": "minecraft:empty", + "weight": 3 + }, + { + "type": "minecraft:item", + "item": "minecraft:arrow" + } + ] } ``` @@ -205,11 +205,11 @@ Applies a function to the contents of the item stack. ```json5 { - "function": "minecraft:modify_contents", - // The contents component to use. Valid values are "container", "bundle_contents" and "charged_projectiles". - "component": "container", - // The function to use. - "modifier": "apply_explosion_decay" + "function": "minecraft:modify_contents", + // The contents component to use. Valid values are "container", "bundle_contents" and "charged_projectiles". + "component": "container", + // The function to use. + "modifier": "apply_explosion_decay" } ``` @@ -221,13 +221,13 @@ Sets a container loot table on the result item stack. Intended for chests and ot ```json5 { - "function": "minecraft:set_loot_table", - // The id of the loot table to use. - "name": "minecraft:entities/enderman", - // The id of the block entity type of the target block entity. - "type": "minecraft:chest", - // The random seed for generating loot tables. Optional, defaults to 0. - "seed": 42 + "function": "minecraft:set_loot_table", + // The id of the loot table to use. + "name": "minecraft:entities/enderman", + // The id of the block entity type of the target block entity. + "type": "minecraft:chest", + // The random seed for generating loot tables. Optional, defaults to 0. + "seed": 42 } ``` @@ -239,13 +239,13 @@ Sets a name for the result item stack. The name can be a [`Component`][component ```json5 { - "function": "minecraft:set_name", - "name": "Funny Item", - // The entity target to use. - "entity": "this", - // Whether to set the custom name ("custom_name") or the item name ("item_name") itself. - // Custom name are displayed in italics and can be changed in an anvil, while item names cannot. - "target": "custom_name" + "function": "minecraft:set_name", + "name": "Funny Item", + // The entity target to use. + "entity": "this", + // Whether to set the custom name ("custom_name") or the item name ("item_name") itself. + // Custom name are displayed in italics and can be changed in an anvil, while item names cannot. + "target": "custom_name" } ``` @@ -257,9 +257,9 @@ Copies an [entity target][entitytarget]'s or block entity's name into the result ```json5 { - "function": "minecraft:copy_name", - // The entity target, or "block_entity" if a block entity's name should be copied. - "source": "this" + "function": "minecraft:copy_name", + // The entity target, or "block_entity" if a block entity's name should be copied. + "source": "this" } ``` @@ -271,25 +271,25 @@ Sets lore (tooltip lines) for the result item stack. The lines can be [`Componen ```json5 { - "function": "minecraft:set_lore", - "lore": [ - "Funny Lore", - "Funny Lore 2" - ], - // The merging mode used. Valid values are: - // - "append": Appends the entries to any existing lore entries. - // - "insert": Inserts the entries at a certain position. The position is denoted as an additional field - // named "offset". "offset" is optional and defaults to 0. - // - "replace_all": Removes all previous entries and then appends the entries. - // - "replace_section": Removes a section of entries and then adds the entries at that position. - // The section removed is denoted through the "offset" and optional "size" fields. - // If "size" is omitted, the amount of lines in "lore" is used. - "mode": { - "type": "insert", - "offset": 0 - }, - // The entity target to use. - "entity": "this" + "function": "minecraft:set_lore", + "lore": [ + "Funny Lore", + "Funny Lore 2" + ], + // The merging mode used. Valid values are: + // - "append": Appends the entries to any existing lore entries. + // - "insert": Inserts the entries at a certain position. The position is denoted as an additional field + // named "offset". "offset" is optional and defaults to 0. + // - "replace_all": Removes all previous entries and then appends the entries. + // - "replace_section": Removes a section of entries and then adds the entries at that position. + // The section removed is denoted through the "offset" and optional "size" fields. + // If "size" is omitted, the amount of lines in "lore" is used. + "mode": { + "type": "insert", + "offset": 0 + }, + // The entity target to use. + "entity": "this" } ``` @@ -301,20 +301,20 @@ Enables or disables certain component tooltips. ```json5 { - "function": "minecraft:toggle_tooltips", - "toggles": { - // All values are optional. If omitted, these values will use pre-existing values on the stack. - // The pre-existing values are generally true, unless they have already been modified by another function. - "minecraft:attribute_modifiers": false, - "minecraft:can_break": false, - "minecraft:can_place_on": false, - "minecraft:dyed_color": false, - "minecraft:enchantments": false, - "minecraft:jukebox_playable": false, - "minecraft:stored_enchantments": false, - "minecraft:trim": false, - "minecraft:unbreakable": false - } + "function": "minecraft:toggle_tooltips", + "toggles": { + // All values are optional. If omitted, these values will use pre-existing values on the stack. + // The pre-existing values are generally true, unless they have already been modified by another function. + "minecraft:attribute_modifiers": false, + "minecraft:can_break": false, + "minecraft:can_place_on": false, + "minecraft:dyed_color": false, + "minecraft:enchantments": false, + "minecraft:jukebox_playable": false, + "minecraft:stored_enchantments": false, + "minecraft:trim": false, + "minecraft:unbreakable": false + } } ``` @@ -325,19 +325,19 @@ It is currently not possible to create this function during datagen. Randomly enchants the item stack with a given amount of levels. Uses a [number provider][numberprovider]. ```json5 -{ - "function": "minecraft:enchant_with_levels", - // The amount of levels to use. - "levels": { - "type": "minecraft:uniform", - "min": 10, - "max": 30 - }, - // A list of possible enchantments. Optional, defaults to all applicable enchantments for the item. - "options": [ - "minecraft:sharpness", - "minecraft:fire_aspect" - ] + { + "function": "minecraft:enchant_with_levels", + // The amount of levels to use. + "levels": { + "type": "minecraft:uniform", + "min": 10, + "max": 30 + }, + // A list of possible enchantments. Optional, defaults to all applicable enchantments for the item. + "options": [ + "minecraft:sharpness", + "minecraft:fire_aspect" + ] } ``` @@ -349,14 +349,14 @@ Enchants the item with one random enchantment. ```json5 { - "function": "minecraft:enchant_randomly", - // A list of possible enchantments. Optional, defaults to all enchantments. - "options": [ - "minecraft:sharpness", - "minecraft:fire_aspect" - ] - // Whether to only allow compatible enchantments, or any enchantments. Optional, defaults to true. - "only_compatible": true + "function": "minecraft:enchant_randomly", + // A list of possible enchantments. Optional, defaults to all enchantments. + "options": [ + "minecraft:sharpness", + "minecraft:fire_aspect" + ], + // Whether to only allow compatible enchantments, or any enchantments. Optional, defaults to true. + "only_compatible": true } ``` @@ -368,18 +368,18 @@ Sets enchantments on the result item stack. ```json5 { - "function": "minecraft:set_enchantments", - // A map of enchantments to number providers. - "enchantments": { - "minecraft:fire_aspect": 2, - "minecraft:sharpness": { - "type": "minecraft:uniform", - "min": 3, - "max": 5, - } - }, - // Whether to add enchantment levels to existing levels instead of overwriting them. Optional, defaults to false. - "add": true + "function": "minecraft:set_enchantments", + // A map of enchantments to number providers. + "enchantments": { + "minecraft:fire_aspect": 2, + "minecraft:sharpness": { + "type": "minecraft:uniform", + "min": 3, + "max": 5, + } + }, + // Whether to add enchantment levels to existing levels instead of overwriting them. Optional, defaults to false. + "add": true } ``` @@ -391,17 +391,17 @@ Increases the item stack count based on the enchantment value. Uses a [number pr ```json5 { - "function": "minecraft:enchanted_count_increase", - // The enchantment to use. - "enchantment": "minecraft:fortune" - // The increase count per level. The number provider is rolled once per function, not once per level. - "count": { - "type": "minecraft:uniform", - "min": 1, - "max": 3 - }, - // The stack size limit, which will not be exceeded no matter the enchantment level. Optional. - "limit": 5 + "function": "minecraft:enchanted_count_increase", + // The enchantment to use. + "enchantment": "minecraft:fortune", + // The increase count per level. The number provider is rolled once per function, not once per level. + "count": { + "type": "minecraft:uniform", + "min": 1, + "max": 3 + }, + // The stack size limit, which will not be exceeded no matter the enchantment level. Optional. + "limit": 5 } ``` @@ -413,20 +413,20 @@ Applies an increase to the item stack count based on the enchantment value and v ```json5 { - "function": "minecraft:apply_bonus", - // The enchantment value to query. - "enchantment": "minecraft:fortune", - // The formula to use. Valid values are: - // - "minecraft:binomial_with_bonus_count": Applies a bonus based on a binomial distribution with - // n = enchantment level + extra and p = probability. - // - "minecraft:ore_drops": Applies a bonus based on a special formula for ore drops, including randomness. - // - "minecraft:uniform_bonus_count": Adds a bonus based on the enchantment level scaled by a constant multiplier. - "formula": "ore_drops", - // The parameter values, depending on the formula. - // If the formula is "minecraft:binomial_with_bonus_count", requires "extra" and "probability". - // If the formula is "minecraft:ore_drops", requires no parameters. - // If the formula is "minecraft:uniform_bonus_count", requires "bonusMultiplier". - "parameters": {} + "function": "minecraft:apply_bonus", + // The enchantment value to query. + "enchantment": "minecraft:fortune", + // The formula to use. Valid values are: + // - "minecraft:binomial_with_bonus_count": Applies a bonus based on a binomial distribution with + // n = enchantment level + extra and p = probability. + // - "minecraft:ore_drops": Applies a bonus based on a special formula for ore drops, including randomness. + // - "minecraft:uniform_bonus_count": Adds a bonus based on the enchantment level scaled by a constant multiplier. + "formula": "ore_drops", + // The parameter values, depending on the formula. + // If the formula is "minecraft:binomial_with_bonus_count", requires "extra" and "probability". + // If the formula is "minecraft:ore_drops", requires no parameters. + // If the formula is "minecraft:uniform_bonus_count", requires "bonusMultiplier". + "parameters": {} } ``` @@ -438,7 +438,7 @@ Attempts to smelt the item as if it were in a furnace, returning the unmodified ```json5 { - "function": "minecraft:furnace_smelt" + "function": "minecraft:furnace_smelt" } ``` @@ -450,15 +450,15 @@ Sets a durability damage value on the result item stack. Uses a [number provider ```json5 { - "function": "minecraft:set_damage", - // The damage to set. - "damage": { - "type": "minecraft:uniform", - "min": 10, - "max": 300 - }, - // Whether to add to the existing damage instead of setting it. Optional, defaults to false. - "add": true + "function": "minecraft:set_damage", + // The damage to set. + "damage": { + "type": "minecraft:uniform", + "min": 10, + "max": 300 + }, + // Whether to add to the existing damage instead of setting it. Optional, defaults to false. + "add": true } ``` @@ -470,28 +470,28 @@ Adds a list of attribute modifiers to the result item stack. ```json5 { - "function": "minecraft:set_attributes", - // A list of attribute modifiers. - "modifiers": [ - { - // The resource location id of the modifier. Should be prefixed by your mod id. - "id": "examplemod:example_modifier", - // The id of the attribute the modifier is for. - "attribute": "minecraft:generic.attack_damage", - // The attribute modifier operation. - // Valid values are "add_value", "add_multiplied_base" and "add_multiplied_total". - "operation": "add_value", - // The amount of the modifier. This can also be a number provider. - "amount": 5, - // The slot(s) the modifier applies for. Valid values are "any" (any inventory slot), - // "mainhand", "offhand", "hand", (mainhand/offhand/both hands), - // "feet", "legs", "chest", "head", "armor" (boots/leggings/chestplates/helmets/any armor slots) - // and "body" (horse armor and similar slots). - "slot": "armor" - } - ], - // Whether to replace the existing values instead of adding to them. Optional, defaults to true. - "replace": false + "function": "minecraft:set_attributes", + // A list of attribute modifiers. + "modifiers": [ + { + // The resource location id of the modifier. Should be prefixed by your mod id. + "id": "examplemod:example_modifier", + // The id of the attribute the modifier is for. + "attribute": "minecraft:generic.attack_damage", + // The attribute modifier operation. + // Valid values are "add_value", "add_multiplied_base" and "add_multiplied_total". + "operation": "add_value", + // The amount of the modifier. This can also be a number provider. + "amount": 5, + // The slot(s) the modifier applies for. Valid values are "any" (any inventory slot), + // "mainhand", "offhand", "hand", (mainhand/offhand/both hands), + // "feet", "legs", "chest", "head", "armor" (boots/leggings/chestplates/helmets/any armor slots) + // and "body" (horse armor and similar slots). + "slot": "armor" + } + ], + // Whether to replace the existing values instead of adding to them. Optional, defaults to true. + "replace": false } ``` @@ -503,9 +503,9 @@ Sets a potion on the result item stack. ```json5 { - "function": "minecraft:set_potion", - // The id of the potion. - "id": "minecraft:strength" + "function": "minecraft:set_potion", + // The id of the potion. + "id": "minecraft:strength" } ``` @@ -517,16 +517,16 @@ Sets a list of stew effects on the result item stack. ```json5 { - "function": "minecraft:set_stew_effect", - // The effects to set. - "effects": [ - { - // The effect id. - "type": "minecraft:fire_resistance", - // The effect duration, in ticks. This can also be a number provider. - "duration": 100 - } - ] + "function": "minecraft:set_stew_effect", + // The effects to set. + "effects": [ + { + // The effect id. + "type": "minecraft:fire_resistance", + // The effect duration, in ticks. This can also be a number provider. + "duration": 100 + } + ] } ``` @@ -538,17 +538,17 @@ Sets an ominous bottle amplifier on the result item stack. Uses a [number provid ```json5 { - "function": "minecraft:set_ominous_bottle_amplifier", - // The amplifier to use. - "amplifier": { - "type": "minecraft:uniform", - "min": 1, - "max": 3 - } + "function": "minecraft:set_ominous_bottle_amplifier", + // The amplifier to use. + "amplifier": { + "type": "minecraft:uniform", + "min": 1, + "max": 3 + } } ``` -During datagen, call `SetOminousBottleAmplifierFunction#amplifier` with the desired number provider to construct a builder for this function. +During datagen, call `SetOminousBottleAmplifierFunction#setAmplifier` with the desired number provider to construct a builder for this function. ## `minecraft:exploration_map` @@ -556,19 +556,19 @@ Transforms the result item stack into an exploration map if and only if it is a ```json5 { - "function": "minecraft:exploration_map", - // A structure tag, containing the structures an exploration map can lead to. - // Optional, defaults to "minecraft:on_treasure_maps", which only contains buried treasures by default. - "destination": "minecraft:eye_of_ender_located", - // The map decoration type to use. See the MapDecorationTypes class for available values. - // Optional, defaults to "minecraft:mansion". - "decoration": "minecraft:target_x", - // The zoom level to use. Optional, defaults to 2. - "zoom": 4, - // The search radius to use. Optional, defaults to 50. - "search_radius": 25, - // Whether existing chunks are skipped when searching for structures. Optional, defaults to true. - "skip_existing_chunks": true + "function": "minecraft:exploration_map", + // A structure tag, containing the structures an exploration map can lead to. + // Optional, defaults to "minecraft:on_treasure_maps", which only contains buried treasures by default. + "destination": "minecraft:eye_of_ender_located", + // The map decoration type to use. See the MapDecorationTypes class for available values. + // Optional, defaults to "minecraft:mansion". + "decoration": "minecraft:target_x", + // The zoom level to use. Optional, defaults to 2. + "zoom": 4, + // The search radius to use. Optional, defaults to 50. + "search_radius": 25, + // Whether existing chunks are skipped when searching for structures. Optional, defaults to true. + "skip_existing_chunks": true } ``` @@ -580,9 +580,9 @@ Sets the player head owner on the result item stack based on the given [entity t ```json5 { - "function": "minecraft:fill_player_head", - // The entity target to use. If this doesn't resolve to a player, the stack is not modified. - "entity": "this_entity" + "function": "minecraft:fill_player_head", + // The entity target to use. If this doesn't resolve to a player, the stack is not modified. + "entity": "this_entity" } ``` @@ -594,18 +594,18 @@ Sets banner patterns on the result item stack. This is for banners, not banner p ```json5 { - "function": "minecraft:set_banner_patterns", - // A list of banner pattern layers. - "patterns": [ - { - // The id of the banner pattern to use. - "pattern": "minecraft:globe", - // The dye color of the layer. - "color": "light_blue" - } - ], - // Whether to append to the existing layers instead of replacing them. - "append": true + "function": "minecraft:set_banner_patterns", + // A list of banner pattern layers. + "patterns": [ + { + // The id of the banner pattern to use. + "pattern": "minecraft:globe", + // The dye color of the layer. + "color": "light_blue" + } + ], + // Whether to append to the existing layers instead of replacing them. + "append": true } ``` @@ -617,9 +617,9 @@ Sets the instrument tag on the result item stack. ```json5 { - "function": "minecraft:set_instrument", - // The instrument tag to use. - "options": "minecraft:goat_horns" + "function": "minecraft:set_instrument", + // The instrument tag to use. + "options": "minecraft:goat_horns" } ``` @@ -629,31 +629,31 @@ During datagen, call `SetInstrumentFunction#setInstrumentOptions` with the desir ```json5 { - "function": "minecraft:set_fireworks", - // The explosions to use. Optional, uses the existing data component value if absent. - "explosions": [ - { - // The firework explosion shape to use. Valid vanilla values are "small_ball", "large_ball", - // "star", "creeper" and "burst". Optional, defaults to "small_ball". - "shape": "star", - // The colors to use. Optional, defaults to an empty list. - "colors": [ - 0xff0000, - 0x00ff00 - ] - // The fade colors to use. Optional, defaults to an empty list. - "fade_colors": [ - 0x00ff00, - 0x0000ff - ], - // Whether the explosion has a trail. Optional, defaults to false. - "has_trail": true, - // Whether the explosion has a twinkle. Optional, defaults to false. - "has_twinkle": true - } - ], - // The flight duration of the fireworks. Optional, uses the existing data component value if absent. - "flight_duration": 5 + "function": "minecraft:set_fireworks", + // The explosions to use. Optional, uses the existing data component value if absent. + "explosions": [ + { + // The firework explosion shape to use. Valid vanilla values are "small_ball", "large_ball", + // "star", "creeper" and "burst". Optional, defaults to "small_ball". + "shape": "star", + // The colors to use. Optional, defaults to an empty list. + "colors": [ + 16711680, + 65280 + ], + // The fade colors to use. Optional, defaults to an empty list. + "fade_colors": [ + 65280, + 255 + ], + // Whether the explosion has a trail. Optional, defaults to false. + "has_trail": true, + // Whether the explosion has a twinkle. Optional, defaults to false. + "has_twinkle": true + } + ], + // The flight duration of the fireworks. Optional, uses the existing data component value if absent. + "flight_duration": 5 } ``` @@ -665,28 +665,28 @@ Sets a firework explosion on the result item stack. ```json5 { - "function": "minecraft:set_firework_explosion", - // The firework explosion shape to use. Valid vanilla values are "small_ball", "large_ball", - // "star", "creeper" and "burst". Optional, defaults to "small_ball". - "shape": "star", - // The colors to use. Optional, defaults to an empty list. - "colors": [ - 0xff0000, - 0x00ff00 - ] - // The fade colors to use. Optional, defaults to an empty list. - "fade_colors": [ - 0x00ff00, - 0x0000ff - ], - // Whether the explosion has a trail. Optional, defaults to false. - "has_trail": true, - // Whether the explosion has a twinkle. Optional, defaults to false. - "has_twinkle": true + "function": "minecraft:set_firework_explosion", + // The firework explosion shape to use. Valid vanilla values are "small_ball", "large_ball", + // "star", "creeper" and "burst". Optional, defaults to "small_ball". + "shape": "star", + // The colors to use. Optional, defaults to an empty list. + "colors": [ + 16711680, + 65280 + ], + // The fade colors to use. Optional, defaults to an empty list. + "fade_colors": [ + 65280, + 255 + ], + // Whether the explosion has a trail. Optional, defaults to false. + "trail": true, + // Whether the explosion has a twinkle. Optional, defaults to false. + "twinkle": true } ``` -During datagen, call `SetItemCountFunction#setCount` with the desired number provider and optionally an `add` boolean to construct a builder for this function. +It is currently not possible to create this function during datagen. ## `minecraft:set_book_cover` @@ -694,14 +694,14 @@ Sets a written book's non-page-specific content. ```json5 { - "function": "minecraft:set_book_cover", - // The book title. Optional, if absent, the book title remains unchanged. - "title": "Hello World!", - // The book author. Optional, if absent, the book author remains unchanged. - "author": "Steve", - // The book generation, i.e. how often it has been copied. Clamped between 0 and 3. - // Optional, if absent, the book generation remains unchanged. - "generation": 2 + "function": "minecraft:set_book_cover", + // The book title. Optional, if absent, the book title remains unchanged. + "title": "Hello World!", + // The book author. Optional, if absent, the book author remains unchanged. + "author": "Steve", + // The book generation, i.e. how often it has been copied. Clamped between 0 and 3. + // Optional, if absent, the book generation remains unchanged. + "generation": 2 } ``` @@ -713,25 +713,25 @@ Sets the pages of a written book. ```json5 { - "function": "minecraft:set_written_book_pages", - // The pages to set, as a list of strings. - "pages": [ - "Hello World!", - "Hello World on page 2!", - "Never Gonna Give You Up!" - ], - // The merging mode used. Valid values are: - // - "append": Appends the entries to any existing lore entries. - // - "insert": Inserts the entries at a certain position. The position is denoted as an additional field - // named "offset". "offset" is optional and defaults to 0. - // - "replace_all": Removes all previous entries and then appends the entries. - // - "replace_section": Removes a section of entries and then adds the entries at that position. - // The section removed is denoted through the "offset" and optional "size" fields. - // If "size" is omitted, the amount of lines in "lore" is used. - "mode": { - "type": "insert", - "offset": 0 - } + "function": "minecraft:set_written_book_pages", + // The pages to set, as a list of strings. + "pages": [ + "Hello World!", + "Hello World on page 2!", + "Never Gonna Give You Up!" + ], + // The merging mode used. Valid values are: + // - "append": Appends the entries to any existing lore entries. + // - "insert": Inserts the entries at a certain position. The position is denoted as an additional field + // named "offset". "offset" is optional and defaults to 0. + // - "replace_all": Removes all previous entries and then appends the entries. + // - "replace_section": Removes a section of entries and then adds the entries at that position. + // The section removed is denoted through the "offset" and optional "size" fields. + // If "size" is omitted, the amount of lines in "lore" is used. + "mode": { + "type": "insert", + "offset": 0 + } } ``` @@ -743,25 +743,25 @@ Sets the pages of a writable book (book and quill). ```json5 { - "function": "minecraft:set_writable_book_pages", - // The pages to set, as a list of strings. - "pages": [ - "Hello World!", - "Hello World on page 2!", - "Never Gonna Give You Up!" - ], - // The merging mode used. Valid values are: - // - "append": Appends the entries to any existing lore entries. - // - "insert": Inserts the entries at a certain position. The position is denoted as an additional field - // named "offset". "offset" is optional and defaults to 0. - // - "replace_all": Removes all previous entries and then appends the entries. - // - "replace_section": Removes a section of entries and then adds the entries at that position. - // The section removed is denoted through the "offset" and optional "size" fields. - // If "size" is omitted, the amount of lines in "lore" is used. - "mode": { - "type": "insert", - "offset": 0 - } + "function": "minecraft:set_writable_book_pages", + // The pages to set, as a list of strings. + "pages": [ + "Hello World!", + "Hello World on page 2!", + "Never Gonna Give You Up!" + ], + // The merging mode used. Valid values are: + // - "append": Appends the entries to any existing lore entries. + // - "insert": Inserts the entries at a certain position. The position is denoted as an additional field + // named "offset". "offset" is optional and defaults to 0. + // - "replace_all": Removes all previous entries and then appends the entries. + // - "replace_section": Removes a section of entries and then adds the entries at that position. + // The section removed is denoted through the "offset" and optional "size" fields. + // If "size" is omitted, the amount of lines in "lore" is used. + "mode": { + "type": "insert", + "offset": 0 + } } ``` @@ -773,9 +773,9 @@ Sets the custom model data of the result item stack. ```json5 { - "function": "minecraft:set_custom_model_data", - // The custom model data value to use. This can also be a number provider. - "value": 4 + "function": "minecraft:set_custom_model_data", + // The custom model data value to use. This can also be a number provider. + "value": 4 } ``` @@ -783,19 +783,19 @@ It is currently not possible to create this function during datagen. ## `minecraft:filtered` -This function accepts an `ItemPredicate` that is checked against the `tool` loot parameter; if the check succeeds, the other function is run. An `ItemPredicate` can specify a list of valid item ids (`items`), a min/max range for the item count (`count`), a `DataComponentPredicate` (`components`) and an `ItemSubPredicate` (`predicates`); all fields are optional. Requires the `minecraft:tool` loot parameter, always failing if that parameter is absent. +This function accepts an `ItemPredicate` that is checked against the generated stack; if the check succeeds, the other function is run. An `ItemPredicate` can specify a list of valid item ids (`items`), a min/max range for the item count (`count`), a `DataComponentPredicate` (`components`) and a map of `ItemSubPredicate`s (`predicates`); all fields are optional. ```json5 { - "function": "minecraft:filtered", - // The custom model data value to use. This can also be a number provider. - "item_filter": { - "items": [ - "minecraft:diamond_shovel" - ] - }, - // The other loot function to run, as either a loot modifier file or an in-line list of functions. - "modifier": "examplemod:example_modifier" + "function": "minecraft:filtered", + // The custom model data value to use. This can also be a number provider. + "item_filter": { + "items": [ + "minecraft:diamond_shovel" + ] + }, + // The other loot function to run, as either a loot modifier file or an in-line list of functions. + "modifier": "examplemod:example_modifier" } ``` @@ -811,9 +811,9 @@ This function references an item modifier and applies it to the result item stac ```json5 { - "function": "minecraft:reference", - // Refers to the item modifier file at data/examplemod/item_modifier/example_modifier.json. - "name": "examplemod:example_modifier" + "function": "minecraft:reference", + // Refers to the item modifier file at data/examplemod/item_modifier/example_modifier.json. + "name": "examplemod:example_modifier" } ``` @@ -825,17 +825,17 @@ This function runs other loot functions one after another. ```json5 { - "function": "minecraft:sequence", - // A list of functions to run. - "functions": [ - { - "function": "minecraft:set_count", - // ... - }, - { - "function": "minecraft:explosion_decay" - } - ], + "function": "minecraft:sequence", + // A list of functions to run. + "functions": [ + { + "function": "minecraft:set_count", + // ... + }, + { + "function": "minecraft:explosion_decay" + } + ], } ``` @@ -848,7 +848,7 @@ During datagen, call `SequenceFunction#of` with the other functions to construct [component]: ../../client/i18n.md#components [conditions]: lootconditions [custom]: custom.md#custom-loot-functions -[datacomponent]: ../../../items/datacomponents.mdx +[datacomponent]: ../../../items/datacomponents.md [entitytarget]: index.md#entity-targets [entry]: index.md#loot-entry [itemmodifiers]: https://minecraft.wiki/w/Item_modifier#JSON_format diff --git a/docs/resources/server/recipes/builtin.md b/docs/resources/server/recipes/builtin.md index 2a3460293..b67d15ac7 100644 --- a/docs/resources/server/recipes/builtin.md +++ b/docs/resources/server/recipes/builtin.md @@ -12,45 +12,42 @@ Some of the most important recipes - such as the crafting table, sticks, or most ```json5 { - "type": "minecraft:crafting_shaped", - "category": "equipment", - "pattern": [ - "XXX", - " # ", - " # " - ], - "key": { - "#": { - "item": "minecraft:stick" + "type": "minecraft:crafting_shaped", + "category": "equipment", + "key": { + "#": "minecraft:stick", + "X": "minecraft:iron_ingot" }, - "X": { - "item": "minecraft:iron_ingot" + "pattern": [ + "XXX", + " # ", + " # " + ], + "result": { + "count": 1, + "id": "minecraft:iron_pickaxe" } - }, - "result": { - "count": 1, - "id": "minecraft:iron_pickaxe" - } } ``` Let's digest this line for line: - `type`: This is the id of the shaped recipe serializer, `minecraft:crafting_shaped`. -- `category`: This optional field defines the category in the crafting book. +- `category`: This optional field defines the `CraftingBookCategory` in the crafting book. - `key` and `pattern`: Together, these define how the items must be put into the crafting grid. - - The pattern defines up to three lines of up to three-wide strings that define the shape. All lines must be the same length, i.e. the pattern must form a rectangular shape. Spaces can be used to denote slots that should stay empty. - - The key associates the characters used in the pattern with [ingredients][ingredient]. In the above example, all `X`s in the pattern must be iron ingots, and all `#`s must be sticks. + - The pattern defines up to three lines of up to three-wide strings that define the shape. All lines must be the same length, i.e. the pattern must form a rectangular shape. Spaces can be used to denote slots that should stay empty. + - The key associates the characters used in the pattern with [ingredients][ingredient]. In the above example, all `X`s in the pattern must be iron ingots, and all `#`s must be sticks. - `result`: The result of the recipe. This is [an item stack's JSON representation][itemjson]. - Not shown in the example is the `group` key. This optional string property creates a group in the recipe book. Recipes in the same group will be displayed as one in the recipe book. +- Not shown in the example is `show_notification`. This optional boolean, when false, disables the toast shown on the top right hand corner on first use or unlock. -And then, let's have a look at how you'd generate this recipe: +And then, let's have a look at how you'd generate this recipe within `RecipeProvider#buildRecipes`: ```java // We use a builder pattern, therefore no variable is created. Create a new builder by calling // ShapedRecipeBuilder#shaped with the recipe category (found in the RecipeCategory enum) // and a result item, a result item and count, or a result item stack. -ShapedRecipeBuilder.shaped(RecipeCategory.TOOLS, Items.IRON_PICKAXE) +ShapedRecipeBuilder.shaped(this.registries.lookupOrThrow(Registries.ITEM), RecipeCategory.TOOLS, Items.IRON_PICKAXE) // Create the lines of your pattern. Each call to #pattern adds a new line. // Patterns will be validated, i.e. their shape will be checked. .pattern("XXX") @@ -64,13 +61,13 @@ ShapedRecipeBuilder.shaped(RecipeCategory.TOOLS, Items.IRON_PICKAXE) // the recipe builder will crash if you omit this. The first parameter is the advancement name, // and the second one is the condition. Normally, you want to use the has() shortcut for the condition. // Multiple advancement requirements can be added by calling #unlockedBy multiple times. - .unlockedBy("has_iron_ingot", has(Items.IRON_INGOT)) + .unlockedBy("has_iron_ingot", this.has(Items.IRON_INGOT)) // Stores the recipe in the passed RecipeOutput, to be written to disk. // If you want to add conditions to the recipe, those can be set on the output. - .save(output); + .save(this.output); ``` -Additionally, you can call `#group` to set the recipe book group. +Additionally, you can call `#group` and `#showNotification` to set the recipe book group and toggle the toast pop-up, respectively. ### Shapeless Crafting @@ -78,23 +75,17 @@ Unlike shaped crafting recipes, shapeless crafting recipes do not care about the ```json5 { - "type": "minecraft:crafting_shapeless", - "category": "misc", - "ingredients": [ - { - "item": "minecraft:brown_mushroom" - }, - { - "item": "minecraft:red_mushroom" - }, - { - "item": "minecraft:bowl" + "type": "minecraft:crafting_shapeless", + "category": "misc", + "ingredients": [ + "minecraft:brown_mushroom", + "minecraft:red_mushroom", + "minecraft:bowl" + ], + "result": { + "count": 1, + "id": "minecraft:mushroom_stew" } - ], - "result": { - "count": 1, - "id": "minecraft:mushroom_stew" - } } ``` @@ -106,13 +97,13 @@ Like before, let's digest this line for line: - `result`: The result of the recipe. This is [an item stack's JSON representation][itemjson]. - Not shown in the example is the `group` key. This optional string property creates a group in the recipe book. Recipes in the same group will be displayed as one in the recipe book. -And then, let's have a look at how you'd generate this recipe: +And then, let's have a look at how you'd generate this recipe in `RecipeProvider#buildRecipes`: ```java // We use a builder pattern, therefore no variable is created. Create a new builder by calling // ShapelessRecipeBuilder#shapeless with the recipe category (found in the RecipeCategory enum) // and a result item, a result item and count, or a result item stack. -ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, Items.MUSHROOM_STEW) +ShapelessRecipeBuilder.shapeless(this.registries.lookupOrThrow(Registries.ITEM), RecipeCategory.MISC, Items.MUSHROOM_STEW) // Add the recipe ingredients. This can either accept Ingredients, TagKeys or ItemLikes. // Overloads also exist that additionally accept a count, adding the same ingredient multiple times. .requires(Blocks.BROWN_MUSHROOM) @@ -122,13 +113,13 @@ ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, Items.MUSHROOM_STEW) // the recipe builder will crash if you omit this. The first parameter is the advancement name, // and the second one is the condition. Normally, you want to use the has() shortcut for the condition. // Multiple advancement requirements can be added by calling #unlockedBy multiple times. - .unlockedBy("has_mushroom_stew", has(Items.MUSHROOM_STEW)) - .unlockedBy("has_bowl", has(Items.BOWL)) - .unlockedBy("has_brown_mushroom", has(Blocks.BROWN_MUSHROOM)) - .unlockedBy("has_red_mushroom", has(Blocks.RED_MUSHROOM)) + .unlockedBy("has_mushroom_stew", this.has(Items.MUSHROOM_STEW)) + .unlockedBy("has_bowl", this.has(Items.BOWL)) + .unlockedBy("has_brown_mushroom", this.has(Blocks.BROWN_MUSHROOM)) + .unlockedBy("has_red_mushroom", this.has(Blocks.RED_MUSHROOM)) // Stores the recipe in the passed RecipeOutput, to be written to disk. // If you want to add conditions to the recipe, those can be set on the output. - .save(output); + .save(this.output); ``` Additionally, you can call `#group` to set the recipe book group. @@ -137,26 +128,70 @@ Additionally, you can call `#group` to set the recipe book group. One-item recipes (e.g. storage blocks unpacking) should be shapeless recipes to follow vanilla standards. ::: -### Special Crafting +### Transmute Crafting + +Transmute recipes are a special type of single item crafting recipes where the input stack's data components are completely copied to the resulting stack. Transmutations usually occur between two different items where one is the dyed version of another. For example: + +```json5 +{ + "type": "minecraft:crafting_transmute", + "category": "misc", + "group": "shulker_box_dye", + "input": "#minecraft:shulker_boxes", + "material": "minecraft:blue_dye", + "result": "minecraft:blue_shulker_box" +} +``` + +Like before, let's digest this line for line: -In some cases, outputs must be created dynamically from inputs. Most of the time, this is to set data components on the output by copying or calculating their values from the input stacks. These recipes usually only specify the type and hardcode everything else. For example: +- `type`: This is the id of the shapeless recipe serializer, `minecraft:crafting_transmute`. +- `category`: This optional field defines the category in the crafting book. +- `group`: This optional string property creates a group in the recipe book. Recipes in the same group will be displayed as one in the recipe book, which typically makes sense for transmuted recipes. +- `input`: The [ingredient] to transmute. +- `material`: The [ingredient] that transforms the stack into its result. +- `result`: The result of the recipe. This is an item, as the stack would be constructed given the components of the input. + +And then, let's have a look at how you'd generate this recipe in `RecipeProvider#buildRecipes`: ```java +// We use a builder pattern, therefore no variable is created. Create a new builder by calling +// TransmuteRecipeBuilder#transmute with the recipe category (found in the RecipeCategory enum), +// the ingredient input, the ingredient material, and the resulting item. +TransmuteRecipeBuilder.transmute(RecipeCategory.MISC, this.tag(ItemTags.SHULKER_BOXES), + Ingredient.of(DyeItem.byColor(DyeColor.BLUE)), ShulkerBoxBlock.getBlockByColor(DyeColor.BLUE).asItem()) + // Sets the group of the recipe to display in the recipe book. + .group("shulker_box_dye") + // Creates the recipe advancement. While not mandated by the consuming background systems, + // the recipe builder will crash if you omit this. The first parameter is the advancement name, + // and the second one is the condition. Normally, you want to use the has() shortcut for the condition. + // Multiple advancement requirements can be added by calling #unlockedBy multiple times. + .unlockedBy("has_shulker_box", this.has(ItemTags.SHULKER_BOXES)) + // Stores the recipe in the passed RecipeOutput, to be written to disk. + // If you want to add conditions to the recipe, those can be set on the output. + .save(this.output); +``` + +### Special Crafting + +In some cases, outputs must be created dynamically from inputs. Most of the time, this is to set data components on the output by calculating their values from the input stacks. These recipes usually only specify the type and hardcode everything else. For example: + +```json5 { - "type": "minecraft:crafting_special_armordye" + "type": "minecraft:crafting_special_armordye" } ``` -This recipe, which is for leather armor dyeing, just specifies the type and hardcodes everything else - most notably the color calculation, which would be hard to express in JSON. Minecraft prefixes all special crafting recipes with `crafting_special_`, however this practice is not necessary to follow. +This recipe, which is for leather armor dyeing, just specifies the type and hardcodes everything else - most notably the color calculation, which would be hard to express in JSON. Minecraft prefixes most special crafting recipes with `crafting_special_`, however this practice is not necessary to follow. -Generating this recipe looks as follows: +Generating this recipe looks as follows in `RecipeProvider#buildRecipes`: ```java // The parameter of #special is a Function>. // All vanilla special recipes use a constructor with one CraftingBookCategory parameter for this. SpecialRecipeBuilder.special(ArmorDyeRecipe::new) // This overload of #save allows us to specify a name. It can also be used on shaped or shapeless builders. - .save(output, "armor_dye"); + .save(this.output, "armor_dye"); ``` Vanilla provides the following special crafting serializers (mods may add more): @@ -172,7 +207,6 @@ Vanilla provides the following special crafting serializers (mods may add more): - `minecraft:crafting_special_repairitem`: For repairing two broken items into one. - `minecraft:crafting_special_shielddecoration`: For applying a banner to a shield. - `minecraft:crafting_special_shulkerboxcoloring`: For coloring a shulker box while preserving its contents. -- `minecraft:crafting_special_suspiciousstew`: For crafting suspicious stews depending on the input flower. - `minecraft:crafting_special_tippedarrow`: For crafting tipped arrows depending on the input potion. - `minecraft:crafting_decorated_pot`: For crafting decorated pots from sherds. @@ -182,16 +216,16 @@ The second most important group of recipes are the ones made through smelting or ```json5 { - "type": "minecraft:smelting", - "category": "food", - "cookingtime": 200, - "experience": 0.1, - "ingredient": { - "item": "minecraft:kelp" - }, - "result": { - "id": "minecraft:dried_kelp" - } + "type": "minecraft:smelting", + "category": "food", + "cookingtime": 200, + "experience": 0.1, + "ingredient": { + "item": "minecraft:kelp" + }, + "result": { + "id": "minecraft:dried_kelp" + } } ``` @@ -204,7 +238,7 @@ Let's digest this line by line: - `ingredient`: The input [ingredient] of the recipe. - `result`: The result of the recipe. This is [an item stack's JSON representation][itemjson]. -Datagen for these recipes looks like this: +Datagen for these recipes looks like this in `RecipeProvider#buildRecipes`: ```java // Use #smoking for smoking recipes, #blasting for blasting recipes, and #campfireCooking for campfire recipes. @@ -222,9 +256,9 @@ SimpleCookingRecipeBuilder.smelting( 200 ) // The recipe advancement, like with the crafting recipes above. - .unlockedBy("has_kelp", has(Blocks.KELP)) + .unlockedBy("has_kelp", this.has(Blocks.KELP)) // This overload of #save allows us to specify a name. - .save(p_301191_, "dried_kelp_smelting"); + .save(this.output, "dried_kelp_smelting"); ``` :::info @@ -237,25 +271,23 @@ Stonecutter recipes use the `minecraft:stonecutting` recipe type. They are about ```json5 { - "type": "minecraft:stonecutting", - "ingredient": { - "item": "minecraft:andesite" - }, - "result": { - "count": 2, - "id": "minecraft:andesite_slab" - } + "type": "minecraft:stonecutting", + "ingredient": "minecraft:andesite", + "result": { + "count": 2, + "id": "minecraft:andesite_slab" + } } ``` The `type` defines the recipe serializer (`minecraft:stonecutting`). The ingredient is an [ingredient], and the result is a basic [item stack JSON][itemjson]. Like crafting recipes, they can also optionally specify a `group` for grouping in the recipe book. -Datagen is also simple: +Datagen is also simple in `RecipeProvider#buildRecipes`: ```java SingleItemRecipeBuilder.stonecutting(Ingredient.of(Items.ANDESITE), RecipeCategory.BUILDING_BLOCKS, Items.ANDESITE_SLAB, 2) - .unlockedBy("has_andesite", has(Items.ANDESITE)) - .save(output, "andesite_slab_from_andesite_stonecutting"); + .unlockedBy("has_andesite", this.has(Items.ANDESITE)) + .save(this.output, "andesite_slab_from_andesite_stonecutting"); ``` Note that the single item recipe builder does not support actual ItemStack results, and as such, no results with data components. The recipe codec, however, does support them, so a custom builder would need to be implemented if this functionality was desired. @@ -271,19 +303,13 @@ This recipe serializer is for transforming two input items into one, preserving ```json5 { "type": "minecraft:smithing_transform", - "base": { - "item": "minecraft:diamond_axe" - }, - "template": { - "item": "minecraft:netherite_upgrade_smithing_template" - }, - "addition": { - "item": "minecraft:netherite_ingot" - }, + "addition": "#minecraft:netherite_tool_materials", + "base": "minecraft:diamond_axe", "result": { "count": 1, "id": "minecraft:netherite_axe" - } + }, + "template": "minecraft:netherite_upgrade_smithing_template" } ``` @@ -295,7 +321,7 @@ Let's break this down line by line: - `addition`: The addition [ingredient] of the recipe. Usually, this is some sort of material, for example a netherite ingot. - `result`: The result of the recipe. This is [an item stack's JSON representation][itemjson]. -During datagen, call on `SmithingTransformRecipeBuilder#smithing` to add your recipe: +During datagen, call on `SmithingTransformRecipeBuilder#smithing` to add your recipe in `RecipeProvider#buildRecipes`: ```java SmithingTransformRecipeBuilder.smithing( @@ -304,7 +330,7 @@ SmithingTransformRecipeBuilder.smithing( // The base ingredient. Ingredient.of(Items.DIAMOND_AXE), // The addition ingredient. - Ingredient.of(Items.NETHERITE_INGOT), + this.tag(ItemTags.NETHERITE_TOOL_MATERIALS), // The recipe book category. RecipeCategory.TOOLS, // The result item. Note that while the recipe codec accepts an item stack here, the builder does not. @@ -312,9 +338,9 @@ SmithingTransformRecipeBuilder.smithing( Items.NETHERITE_AXE ) // The recipe advancement, like with the other recipes above. - .unlocks("has_netherite_ingot", has(Items.NETHERITE_INGOT)) + .unlocks("has_netherite_ingot", this.has(ItemTags.NETHERITE_TOOL_MATERIALS)) // This overload of #save allows us to specify a name. - .save(output, "netherite_axe_smithing"); + .save(this.output, "netherite_axe_smithing"); ``` ### Trim Smithing @@ -324,15 +350,9 @@ Trim smithing is the process of applying armor trims to armor: ```json5 { "type": "minecraft:smithing_trim", - "addition": { - "tag": "minecraft:trim_materials" - }, - "base": { - "tag": "minecraft:trimmable_armor" - }, - "template": { - "item": "minecraft:bolt_armor_trim_smithing_template" - } + "addition": "#minecraft:trim_materials", + "base": "#minecraft:trimmable_armor", + "template": "minecraft:bolt_armor_trim_smithing_template" } ``` @@ -345,23 +365,23 @@ Again, let's break this down into its bits: This recipe serializer is notably missing a result field. This is because it uses the base input and "applies" the template and addition items on it, i.e., it sets the base's components based on the other inputs and uses the result of that operation as the recipe's result. -During datagen, call on `SmithingTrimRecipeBuilder#smithingTrim` to add your recipe: +During datagen, call on `SmithingTrimRecipeBuilder#smithingTrim` to add your recipe in `RecipeProvider#buildRecipes`: ```java SmithingTrimRecipeBuilder.smithingTrim( - // The base ingredient. - Ingredient.of(ItemTags.TRIMMABLE_ARMOR), // The template ingredient. Ingredient.of(Items.BOLT_ARMOR_TRIM_SMITHING_TEMPLATE), + // The base ingredient. + this.tag(ItemTags.TRIMMABLE_ARMOR), // The addition ingredient. - Ingredient.of(ItemTags.TRIM_MATERIALS), + this.tag(ItemTags.TRIM_MATERIALS), // The recipe book category. RecipeCategory.MISC ) // The recipe advancement, like with the other recipes above. - .unlocks("has_smithing_trim_template", has(Items.BOLT_ARMOR_TRIM_SMITHING_TEMPLATE)) + .unlocks("has_smithing_trim_template", this.has(Items.BOLT_ARMOR_TRIM_SMITHING_TEMPLATE)) // This overload of #save allows us to specify a name. Yes, this name is copied from vanilla. - .save(output, "bolt_armor_trim_smithing_template_smithing_trim"); + .save(this.output, "bolt_armor_trim_smithing_template_smithing_trim"); ``` [ingredient]: ingredients.md diff --git a/docs/resources/server/recipes/custom.md b/docs/resources/server/recipes/custom.md new file mode 100644 index 000000000..81c34dafc --- /dev/null +++ b/docs/resources/server/recipes/custom.md @@ -0,0 +1,756 @@ +# Custom Recipes + +To add custom recipes, we need at least three things: a `Recipe`, a `RecipeType`, and a `RecipeSerializer`. Depending on what you are implementing, you may also need a custom `RecipeInput`, `RecipeDisplay`, `SlotDisplay`, `RecipeBookCategory`, and `RecipePropertySet` if reusing an existing subclass is not feasible. + +For the sake of example, and to highlight many different features, we are going to implement a recipe-driven mechanic that requires you to right-click a `BlockState` in-world with a certain item, breaking the `BlockState` and dropping the result item. + +## The Recipe Input + +Let's begin by defining what we want to put into the recipe. It's important to understand that the recipe input represents the actual inputs that the player is using right now. As such, we don't use tags or ingredients here, instead we use the actual item stacks and blockstates we have available. + +```java +// Our inputs are a BlockState and an ItemStack. +public record RightClickBlockInput(BlockState state, ItemStack stack) implements RecipeInput { + // Method to get an item from a specific slot. We have one stack and no concept of slots, so we just assume + // that slot 0 holds our item, and throw on any other slot. (Taken from SingleRecipeInput#getItem.) + @Override + public ItemStack getItem(int slot) { + if (slot != 0) throw new IllegalArgumentException("No item for index " + slot); + return this.stack(); + } + + // The slot size our input requires. Again, we don't really have a concept of slots, so we just return 1 + // because we have one item stack involved. Inputs with multiple items should return the actual count here. + @Override + public int size() { + return 1; + } +} +``` + +Recipe inputs don't need to be registered or serialized in any way because they are created on demand. It is not always necessary to create your own, the vanilla ones (`CraftingInput`, `SingleRecipeInput` and `SmithingRecipeInput`) are fine for many use cases. + +Additionally, NeoForge provides the `RecipeWrapper` input, which wraps the `#getItem` and `#size` calls with respect to an `IItemHandler` passed in the constructor. Basically, this means that any grid-based inventory, such as a chest, can be used as a recipe input by wrapping it in a `RecipeWrapper`. + +## The Recipe Class + +Now that we have our inputs, let's get to the recipe itself. This is what holds our recipe data, and also handles matching and returning the recipe result. As such, it is usually the longest class for your custom recipe. + +```java +// The generic parameter for Recipe is our RightClickBlockInput from above. +public class RightClickBlockRecipe implements Recipe { + // An in-code representation of our recipe data. This can be basically anything you want. + // Common things to have here is a processing time integer of some kind, or an experience reward. + // Note that we now use an ingredient instead of an item stack for the input. + private final BlockState inputState; + private final Ingredient inputItem; + private final ItemStack result; + + // Add a constructor that sets all properties. + public RightClickBlockRecipe(BlockState inputState, Ingredient inputItem, ItemStack result) { + this.inputState = inputState; + this.inputItem = inputItem; + this.result = result; + } + + // Check whether the given input matches this recipe. The first parameter matches the generic. + // We check our blockstate and our item stack, and only return true if both match. + // If we needed to check the dimensions of our input, we would also do so here. + @Override + public boolean matches(RightClickBlockInput input, Level level) { + return this.inputState == input.state() && this.inputItem.test(input.stack()); + } + + // Return the result of the recipe here, based on the given input. The first parameter matches the generic. + // IMPORTANT: Always call .copy() if you use an existing result! If you don't, things can and will break, + // as the result exists once per recipe, but the assembled stack is created each time the recipe is crafted. + @Override + public ItemStack assemble(RightClickBlockInput input, HolderLookup.Provider registries) { + return this.result.copy(); + } + + // When true, will prevent the recipe from being synced within the recipe book or awarded on use/unlock. + // This should only be true if the recipe shouldn't appear in a recipe book, such as map extending. + // Although this recipe takes in an input state, it could still be used in a custom recipe book using + // the methods below. + @Override + public boolean isSpecial() { + return true; + } + + // This example outlines the most important methods. There is a number of other methods to override. + // Some methods will be explained in the below sections as they cannot be easily compressed and understood here. + // Check the class definition of Recipe to view them all. +} +``` + +## Recipe Book Categories + +A `RecipeBookCategory` simply defines a group to display this recipe within in a recipe book. For example, an iron pickaxe crafting recipe would show up in the `RecipeBookCategories#CARFTING_EQUIPMENT` while a cooked cod recipe would show up in `#FURNANCE_FOOD` or `#SMOKER_FOOD`. Each recipe has one associated `RecipeBookCategory`. The vanilla categories can be found in `RecipeBookCategories`. + +:::note +There are two cooked cod recipes, one for the furnance and one for the smoker. The furnace and smoker recipes have different book categories. +::: + +If your recipe does not fit into one of the existing categories, typically because the recipe does not use one of the existing crafting stations (e.g., crafting table, furnace), then a new `RecipeBookCategory` can be created. Each `RecipeBookCategory` must be [registered][registry] to `BuiltInRegistries#RECIPE_BOOK_CATEGORY`: + +```java +/// For some DeferredRegister RECIPE_BOOK_CATEGORIES +public static final Supplier RIGHT_CLICK_BLOCK_CATEGORY = RECIPE_BOOK_CATEGORIES.register( + "right_click_block", RecipeBookCategory::new +); +``` + +Then, to set the category, we must override `#recipeBookCategory` like so: + +```java +public class RightClickBlockRecipe implements Recipe { + // other stuff here + + @Override + public RecipeBookCategory recipeBookCategory() { + return RIGHT_CLICK_BLOCK_CATEGORY.get(); + } +} +``` + +### Search Categories + +All `RecipeBookCategory`s are technically `ExtendedRecipeBookCategory`s. There is another type of `ExtendedRecipeBookCategory` called `SearchRecipeBookCategory`, which is used to aggregate `RecipeBookCategory`s when viewing all recipes in a recipe book. + +NeoForge allows users to specify their own `ExtendedRecipeBookCategory` as a search category via `RegisterRecipeBookSearchCategoriesEvent#register` on the mod event bus. `register` takes in the `ExtendedRecipeBookCategory` representing the search category and the `RecipeBookCategory`s that make up that search category. The `ExtendedRecipeBookCategory` search category does not need to be registered to some static vanilla registry. + +```java +// In some location +public static final ExtendedRecipeBookCategory RIGHT_CLICK_BLOCK_SEARCH_CATEGORY = new ExtendedRecipeBookCategory() {}; + +// On the mod event bus +@SubscribeEvent +public static void registerSearchCategories(RegisterRecipeBookSearchCategoriesEvent event) { + event.register( + // The search category + RIGHT_CLICK_BLOCK_SEARCH_CATEGORY, + // All recipe categories within the search category as varargs + RIGHT_CLICK_BLOCK_CATEGORY.get() + ) +} +``` + +## Placement Info + +A `PlacementInfo` is meant to define the crafting requirements used by the recipe consumer and whether/how it can be placed into its associated crafting station (e.g., crafting table, furnace). `PlacementInfo` are only meant for item ingredients, so if other types of ingredients are desired (e.g., fluid, block), the surrounding logic will need to be implemented from scratch. In these cases, the recipe can be labelled as not placeable, and say as such via `PlacementInfo#NOT_PLACEABLE`. However, if there is at least one item-like object in your recipe, you should create a `PlacementInfo`. + +A `PlacementInfo` can be created via `create`, which takes in one or a list of ingredient, or `createFromOptionals`, which takes in a list of optional ingredients. If your recipe contains some representation of empty slots, then `createFromOptionals` should be used, providing an empty optional for an empty slot: + +```java +public class RightClickBlockRecipe implements Recipe { + // other stuff here + private PlacementInfo info; + + @Override + public PlacementInfo placementInfo() { + // This delegate is in case the ingredient is not fully populated at this point in time + // Tags and recipes are loaded at the same time, which is why this might be the case. + if (this.info == null) { + // Use optional ingredient as the block state may have an item representation + List> ingredients = new ArrayList<>(); + Item stateItem = this.inputState.getBlock().asItem(); + ingredients.add(stateItem != Items.AIR ? Optional.of(Ingredient.of(stateItem)): Optional.empty()); + ingredients.add(Optional.of(this.inputItem)); + + // Create placement info + this.info = PlacementInfo.createFromOptionals(ingredients); + } + + return this.info; + } +} +``` + +## Slot Displays + +`SlotDisplay`s represent the information on what should render in what slot when viewed by a recipe consumer, like a recipe book. A `SlotDisplay` has two methods. First there's `resolve`, which takes in the `ContextMap` containing the available registries and fuel values (as shown in `SlotDisplayContext`); and the current `DisplayContentsFactory`, which accepts the contents to display for this slot; and returns the transformed list of contents into the output to be accepted. Then there's `type`, which holds the [`MapCodec`][codec] and [`StreamCodec`][streamcodec] used to encode/decode the display. + +`SlotDisplay`s are typically implemented on the [`Ingredient` via `#display`, or `ICustomIngredient#display` for modded ingredients][ingredients]; however, in some cases, the input may not be an ingredient, meaning a `SlotDisplay` will need to use one available, or have a new one created. + +These are the available slot displays provided by Vanilla and NeoForge: + +- `SlotDisplay.Empty`: A slot that represents nothing. +- `SlotDisplay.ItemSlotDisplay`: A slot that respresents an item. +- `SlotDisplay.ItemStackSlotDisplay`: A slot that represents an item stack. +- `SlotDisplay.TagSlotDisplay`: A slot that represents an item tag. +- `SlotDisplay.WithRemainder`: A slot that represents some input that has some crafting remainder. +- `SlotDisplay.AnyFuel`: A slot that represents all fuel items. +- `SlotDisplay.Composite`: A slot that represents a combination of other slot displays. +- `SlotDisplay.SmithingTrimDemoSlotDisplay`: A slot that represents a random smithing drim being applied to some base with the given material. +- `FluidSlotDisplay`: A slot that represents a fluid. +- `FluidStackSlotDisplay`: A slot that represents a fluid stack. +- `FluidTagSlotDisplay`: A slot that represents a fluid tag. + +We have three 'slots' in our recipe: the `BlockState` input, the `Ingredient` input, and the `ItemStack` result. The `Ingredient` input will already have an associated `SlotDisplay` and the `ItemStack` can be represented by `SlotDisplay.ItemStackSlotDisplay`. The `BlockState`, on the other hand, will need its own custom `SlotDisplay` and `DisplayContentsFactory`, as existing ones only take in item stacks, and for this example, block states are handled in a different fashion. + +Starting with the `DisplayContentsFactory`, it is meant to be a transformer for some type to desired content display type. The available factories are: + +- `DisplayContentsFactory.ForStacks`: A transformer that takes in `ItemStack`s. +- `DisplayContentsFactory.ForRemainders`: A transformer that takes in the input object and a list of remainder objects. +- `DisplayContentsFactory.ForFluidStacks`: A transformer that takes in a `FluidStack`. + +With this, the `DisplayContentsFactory` can be implemented to transform the provided objects into the desired output. For example, `SlotDisplay.ItemStackContentsFactory`, takes the `ForStacks` transformer and has the stacks transformed into `ItemStack`s. + +For our `BlockState`, we'll create a factory that takes in the state, along with a basic implementation that outputs the state itself. + +```java +// A basic transformer for block states +public interface ForBlockStates extends DisplayContentsFactory { + + // Delegate methods + default forState(Holder block) { + return this.forState(block.value()); + } + + default forState(Block block) { + return this.forState(block.defaultBlockState()); + } + + // The block state to take in and transform to the desired output + T forState(BlockState state); +} + +// An implementation for a block state output +public class BlockStateContentsFactory implements ForBlockStates { + // Singleton instance + public static final BlockStateContentsFactory INSTANCE = new BlockStateContentsFactory(); + + private BlockStateContentsFactory() {} + + @Override + public BlockState forState(BlockState state) { + return state; + } +} + +// An implementation for an item stack output +public class BlockStateStackContentsFactory implements ForBlockStates { + // Singleton instance + public static final BlockStateStackContentsFactory INSTANCE = new BlockStateStackContentsFactory(); + + private BlockStateStackContentsFactory() {} + + @Override + public ItemStack forState(BlockState state) { + return new ItemStack(state.getBlock()); + } +} +``` + +Then, with that, we can create a new `SlotDisplay`. The `SlotDisplay.Type` must be [registered][registry]: + +```java +// A simple slot display +public record BlockStateSlotDisplay(BlockState state) implements SlotDisplay { + public static final MapCodec CODEC = BlockState.CODEC.fieldOf("state") + .xmap(BlockStateSlotDisplay::new, BlockStateSlotDisplay::state); + public static final StreamCodec STREAM_CODEC = + StreamCodec.composite( + ByteBufCodecs.idMapper(Block.BLOCK_STATE_REGISTRY), BlockStateSlotDisplay::state, + BlockStateSlotDisplay::new + ); + + @Override + public Stream resolve(ContextMap context, DisplayContentsFactory factory) { + return switch (factory) { + // Check for our contents factory and transform if necessary + case ForBlockStates states -> Stream.of(states.forState(this.state)); + // If you want the contents to be handled differently depending on contents display + // then you can case on other displays like so + case ForStacks stacks -> Stream.of(stacks.forStack(state.getBlock().asItem())); + // If no factories match, then do not return anything in the transformed stream + default -> Stream.empty(); + } + } + + @Override + public SlotDisplay.Type type() { + // Return the registered type from below + return BLOCK_STATE_SLOT_DISPLAY.get(); + } +} + +// In some registrar class +/// For some DeferredRegister> SLOT_DISPLAY_TYPES +public static final Supplier> BLOCK_STATE_SLOT_DISPLAY = SLOT_DISPLAY_TYPES.register( + "block_state", + () -> new SlotDisplay.Type<>(BlockStateSlotDisplay.CODEC, BlockStateSlotDisplay.STREAM_CODEC) +); +``` + +## Recipe Display + +A `RecipeDisplay` is the same as a `SlotDisplay`, except that it represents an entire recipe. The default interface only keeps track of the `result` of recipe and the `craftingStation` which represents the workbench where the recipe is applied. The `RecipeDisplay` also has a `type` that holds the [`MapCodec`][codec] and [`StreamCodec`][streamcodec] used to encode/decode the display. However, no available subtypes of `RecipeDisplay` contain all the information required to properly render our recipe on the client. As such, we will need to create our own `RecipeDisplay`. + +All slots and ingredients should be represented as `SlotDisplay`s. Any restrictions, such as grid size, can be provided in any manner the user decides. + +```java +// A simple recipe display +public record RightClickBlockRecipeDisplay( + SlotDisplay inputState, + SlotDisplay inputItem, + SlotDisplay result, // Implements RecipeDisplay#result + SlotDisplay craftingStation // Implements RecipeDisplay#craftingStation +) implements RecipeDisplay { + public static final MapCodec MAP_CODEC = RecordCodecBuilder.mapCodec( + instance -> instance.group( + SlotDisplay.CODEC.fieldOf("inputState").forGetter(RightClickBlockRecipeDisplay::inputState), + SlotDisplay.CODEC.fieldOf("inputState").forGetter(RightClickBlockRecipeDisplay::inputItem), + SlotDisplay.CODEC.fieldOf("result").forGetter(RightClickBlockRecipeDisplay::result), + SlotDisplay.CODEC.fieldOf("crafting_station").forGetter(RightClickBlockRecipeDisplay::craftingStation) + ) + .apply(instance, RightClickBlockRecipeDisplay::new) + ); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + SlotDisplay.STREAM_CODEC, + RightClickBlockRecipeDisplay::inputState, + SlotDisplay.STREAM_CODEC, + RightClickBlockRecipeDisplay::inputItem, + SlotDisplay.STREAM_CODEC, + RightClickBlockRecipeDisplay::result, + SlotDisplay.STREAM_CODEC, + RightClickBlockRecipeDisplay::craftingStation, + RightClickBlockRecipeDisplay::new + ); + + @Override + public RecipeDisplay.Type type() { + // Return the registered type from below + return RIGHT_CLICK_BLOCK_RECIPE_DISPLAY.get(); + } +} + +// In some registrar class +/// For some DeferredRegister> RECIPE_DISPLAY_TYPES +public static final Supplier> RIGHT_CLICK_BLOCK_RECIPE_DISPLAY = RECIPE_DISPLAY_TYPES.register( + "right_click_block", + () -> new RecipeDisplay.Type<>(RightClickBlockRecipeDisplay.CODEC, RightClickBlockRecipeDisplay.STREAM_CODEC) +); +``` + +Then we can create the recipe display for the recipe by overriding `#display` like so: + +```java +public class RightClickBlockRecipe implements Recipe { + // other stuff here + + @Override + public List display() { + // You can have many different displays for the same recipe + // But this example will only use one like the other recipes. + return List.of( + // Add our recipe display with the specified slots + new RightClickBlockRecipeDisplay( + new BlockStateSlotDisplay(this.inputState), + this.inputItem.display(), + new SlotDisplay.ItemStackSlotDisplay(this.result), + new SlotDisplay.ItemSlotDisplay(Items.GRASS_BLOCK) + ) + ) + } +} +``` + +## The Recipe Type + +Next up, our recipe type. This is fairly straightforward because there's no data other than a name associated with a recipe type. They are one of two [registered][registry] parts of the recipe system, so like with all other registries, we create a `DeferredRegister` and register to it: + +```java +public static final DeferredRegister> RECIPE_TYPES = + DeferredRegister.create(Registries.RECIPE_TYPE, ExampleMod.MOD_ID); + +public static final Supplier> RIGHT_CLICK_BLOCK_TYPE = + RECIPE_TYPES.register( + "right_click_block", + // We need the qualifying generic here due to generics being generics. + registryName -> new RecipeType { + + @Override + public String toString() { + return registryName.toString(); + } + } + ); +``` + +After we have registered our recipe type, we must override `#getType` in our recipe, like so: + +```java +public class RightClickBlockRecipe implements Recipe { + // other stuff here + + @Override + public RecipeType> getType() { + return RIGHT_CLICK_BLOCK_TYPE.get(); + } +} +``` + +## The Recipe Serializer + +A recipe serializer provides two codecs, one map codec and one stream codec, for serialization from/to JSON and from/to network, respectively. This section will not go in depth about how the codecs work, please see [Map Codecs][codec] and [Stream Codecs][streamcodec] for more information. + +Since recipe serializers can get fairly large, vanilla moves them to separate classes. It is recommended, but not required to follow the practice - smaller serializers are often defined in anonymous classes within fields of the recipe class. To follow good practice, we will create a separate class that holds our codecs: + +```java +// The generic parameter is our recipe class. +// Note: This assumes that simple RightClickBlockRecipe#getInputState, #getInputItem and #getResult getters +// are available, which were omitted from the code above. +public class RightClickBlockRecipeSerializer implements RecipeSerializer { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(inst -> inst.group( + BlockState.CODEC.fieldOf("state").forGetter(RightClickBlockRecipe::getInputState), + Ingredient.CODEC.fieldOf("ingredient").forGetter(RightClickBlockRecipe::getInputItem), + ItemStack.CODEC.fieldOf("result").forGetter(RightClickBlockRecipe::getResult) + ).apply(inst, RightClickBlockRecipe::new)); + public static final StreamCodec STREAM_CODEC = + StreamCodec.composite( + ByteBufCodecs.idMapper(Block.BLOCK_STATE_REGISTRY), RightClickBlockRecipe::getInputState, + Ingredient.CONTENTS_STREAM_CODEC, RightClickBlockRecipe::getInputItem, + ItemStack.STREAM_CODEC, RightClickBlockRecipe::getResult, + RightClickBlockRecipe::new + ); + + // Return our map codec. + @Override + public MapCodec codec() { + return CODEC; + } + + // Return our stream codec. + @Override + public StreamCodec streamCodec() { + return STREAM_CODEC; + } +} +``` + +Like with the type, we register our serializer: + +```java +public static final DeferredRegister> RECIPE_SERIALIZERS = + DeferredRegister.create(Registries.RECIPE_SERIALIZER, ExampleMod.MOD_ID); + +public static final Supplier> RIGHT_CLICK_BLOCK = + RECIPE_SERIALIZERS.register("right_click_block", RightClickBlockRecipeSerializer::new); +``` + +And similarly, we must also override `#getSerializer` in our recipe, like so: + +```java +public class RightClickBlockRecipe implements Recipe { + // other stuff here + + @Override + public RecipeSerializer> getSerializer() { + return RIGHT_CLICK_BLOCK.get(); + } +} +``` + +## The Crafting Mechanic + +Now that all parts of your recipe are complete, you can make yourself some recipe JSONs (see the [datagen] section for that) and then query the recipe manager for your recipes, like above. What you then do with the recipe is up to you. A common use case would be a machine that can process your recipes, storing the active recipe as a field. + +In our case, however, we want to apply the recipe when an item is right-clicked on a block. We will do so using an [event handler][event]. Keep in mind that this is an example implementation, and you can alter this in any way you like (so long as you run it on the server). As we want the interaction state to match on both the client and server, we will also need to [sync any relevant input states across the network][networking]. + +We can set up a simple network implementation to sync the recipe inputs like so: + +```java +// A basic packet class, must be registered. +public record ClientboundRightClickBlockRecipesPayload( + Set inputStates, Set> inputItems +) implements CustomPacketPayload { + + // ... +} + +// Packet stores data in an instance class. +// Present on both server and client to do initial matching. +public interface RightClickBlockRecipeInputs { + + Set inputStates(); + Set> inputItems(); + + default boolean test(BlockState state, ItemStack stack) { + return this.inputStates().contains(state) && this.inputItems().contains(stack.getItemHolder()); + } +} + +// Server resource listener so it can be reloaded when recipes are. +public class ServerRightClickBlockRecipeInputs implements ResourceManagerReloadListener, RightClickBlockRecipeInputs { + + private final RecipeManager recipeManager; + + private Set inputStates; + private Set> inputItems; + + public RightClickBlockRecipeInputs(RecipeManager recipeManager) { + this.recipeManager = recipeManager; + } + + // Set inputs here as #apply is fired synchronously based on listener registration order. + // Recipes are always applied first. + @Override + public void onResourceManagerReload(ResourceManager manager) { + MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); + if (server != null) { // Should never be null + // Populate inputs + Set inputStates = new HashSet<>(); + Set> inputItems = new HashSet<>(); + + this.recipeManager.recipeMap().byType(RIGHT_CLICK_BLOCK_TYPE.get()) + .forEach(holder -> { + var recipe = holder.value(); + inputStates.add(recipe.getInputState()); + inputItems.addAll(recipe.getInputItem().items()); + }); + + this.inputStates = Set.unmodifiableSet(inputStates); + this.inputItems = Set.unmodifiableSet(inputItems); + } + } + + public void syncToClient(Stream players) { + ClientboundRightClickBlockRecipesPayload payload = + new ClientboundRightClickBlockRecipesPayload(this.inputStates, this.inputItems); + players.forEach(player -> PacketDistributor.sendToPlayer(player, payload)); + } + + @Override + public Set inputStates() { + return this.inputStates; + } + + @Override + public Set> inputItems() { + return this.inputItems; + } +} + +// Client implementation to hold the inputs. +public record ClientRightClickBlockRecipeInputs( + Set inputStates, Set> inputItems +) implements RightClickBlockRecipeInputs { + + public ClientRightClickBlockRecipeInputs(Set inputStates, Set> inputItems) { + this.inputStates = Set.unmodifiableSet(inputStates); + this.inputItems = Set.unmodifiableSet(inputItems); + } +} + +// Handling the recipe instance depending on side. +public class ServerRightClickBlockRecipes { + + private static ServerRightClickBlockRecipeInputs inputs; + + public static RightClickBlockRecipeInputs inputs() { + return ServerRightClickBlockRecipes.inputs; + } + + // On the game event bus + @SubscribeEvent + public static void addListener(AddReloadListenerEvent event) { + // Register server reload listener + ServerRightClickBlockRecipes.inputs = new ServerRightClickBlockRecipeInputs( + event.getServerResources().getRecipeManager() + ); + event.addListener(ServerRightClickBlockRecipes.inputs); + } + + // On the game event bus + @SubscribeEvent + public static void datapackSync(OnDatapackSyncEvent event) { + // Send to client + ServerRightClickBlockRecipes.inputs.syncToClient(event.getRelevantPlayers()); + } +} +public class ClientRightClickBlockRecipes { + + private static ClientRightClickBlockRecipeInputs inputs; + + public static RightClickBlockRecipeInputs inputs() { + return ClientRightClickBlockRecipes.inputs; + } + + // Handling the sent packet + public static void handle(final ClientboundRightClickBlockRecipesPayload data, final IPayloadContext context) { + // Do something with the data, on the main thread + ClientRightClickBlockRecipes.inputs = new ClientRightClickBlockRecipeInputs( + data.inputStates(), data.inputItems() + ); + } +} + +public class RightClickBlockRecipes { + // Make proxy method to access properly + public static RightClickBlockRecipeInputs inputs(Level level) { + return level.isClientSide + ? ClientRightClickBlockRecipes.inputs() + : ServerRightClickBlockRecipes.inputs(); + } +} +``` + +Then, using the synced inputs, we can check the game for the used inputs: + +```java +// On the game event bus +@SubscribeEvent +public static void useItemOnBlock(UseItemOnBlockEvent event) { + // Skip if we are not in the block-dictated phase of the event. See the event's javadocs for details. + if (event.getUsePhase() != UseItemOnBlockEvent.UsePhase.BLOCK) return; + // Get parameters to check input first + Level level = event.getLevel(); + BlockPos pos = event.getPos(); + BlockState blockState = level.getBlockState(pos); + ItemStack itemStack = event.getItemStack(); + + // Check if the input can result in a recipe on both sides + if (!RightClickBlockRecipes.inputs(level).test(blockState, itemStack)) return; + + // If so, make sure on server before checking recipe + if (!level.isClientSide() && level instanceof ServerLevel serverLevel) { + // Create an input and query the recipe. + RightClickBlockInput input = new RightClickBlockInput(blockState, itemStack); + Optional>> optional = serverLevel.recipeAccess().getRecipeFor( + // The recipe type. + RIGHT_CLICK_BLOCK_TYPE.get(), + input, + level + ); + ItemStack result = optional + .map(RecipeHolder::value) + .map(e -> e.assemble(input, level.registryAccess())) + .orElse(ItemStack.EMPTY); + + // If there is a result, break the block and drop the result in the world. + if (!result.isEmpty()) { + level.removeBlock(pos, false); + ItemEntity entity = new ItemEntity(level, + // Center of pos. + pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, + result); + level.addFreshEntity(entity); + } + } + + // Cancel the event to stop the interaction pipeline regardless of side. + // Already made sure that there could be a result. + event.cancelWithResult(InteractionResult.SUCCESS_SERVER); +} +``` + +## Data Generation + +To create a recipe builder for your own recipe serializer(s), you need to implement `RecipeBuilder` and its methods. A common implementation, partially copied from vanilla, would look like this: + +```java +// This class is abstract because there is a lot of per-recipe-serializer logic. +// It serves the purpose of showing the common part of all (vanilla) recipe builders. +public abstract class SimpleRecipeBuilder implements RecipeBuilder { + // Make the fields protected so our subclasses can use them. + protected final ItemStack result; + protected final Map> criteria = new LinkedHashMap<>(); + @Nullable + protected final String group; + + // It is common for constructors to accept the result item stack. + // Alternatively, static builder methods are also possible. + public SimpleRecipeBuilder(ItemStack result) { + this.result = result; + } + + // This method adds a criterion for the recipe advancement. + @Override + public SimpleRecipeBuilder unlockedBy(String name, Criterion criterion) { + this.criteria.put(name, criterion); + return this; + } + + // This method adds a recipe book group. If you do not want to use recipe book groups, + // remove the this.group field and make this method no-op (i.e. return this). + @Override + public SimpleRecipeBuilder group(@Nullable String group) { + this.group = group; + return this; + } + + // Vanilla wants an Item here, not an ItemStack. You still can and should use the ItemStack + // for serializing the recipes. + @Override + public Item getResult() { + return this.result.getItem(); + } +} +``` + +So we have a base for our recipe builder. Now, before we continue with the recipe serializer-dependent part, we should first consider what to make our recipe factory. In our case, it makes sense to use the constructor directly. In other situations, using a static helper or a small functional interface is the way to go. This is especially relevant if you use one builder for multiple recipe classes. + +Utilizing `RightClickBlockRecipe::new` as our recipe factory, and reusing the `SimpleRecipeBuilder` class above, we can create the following recipe builder for `RightClickBlockRecipe`s: + +```java +public class RightClickBlockRecipeBuilder extends SimpleRecipeBuilder { + private final BlockState inputState; + private final Ingredient inputItem; + + // Since we have exactly one of each input, we pass them to the constructor. + // Builders for recipe serializers that have ingredient lists of some sort would usually + // initialize an empty list and have #addIngredient or similar methods instead. + public RightClickBlockRecipeBuilder(ItemStack result, BlockState inputState, Ingredient inputItem) { + super(result); + this.inputState = inputState; + this.inputItem = inputItem; + } + + // Saves a recipe using the given RecipeOutput and key. This method is defined in the RecipeBuilder interface. + @Override + public void save(RecipeOutput output, ResourceKey> key) { + // Build the advancement. + Advancement.Builder advancement = output.advancement() + .addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(key)) + .rewards(AdvancementRewards.Builder.recipe(key)) + .requirements(AdvancementRequirements.Strategy.OR); + this.criteria.forEach(advancement::addCriterion); + // Our factory parameters are the result, the block state, and the ingredient. + RightClickBlockRecipe recipe = new RightClickBlockRecipe(this.inputState, this.inputItem, this.result); + // Pass the id, the recipe, and the recipe advancement into the RecipeOutput. + output.accept(key, recipe, advancement.build(key.location().withPrefix("recipes/"))); + } +} +``` + +And now, during [datagen][recipedatagen], you can call on your recipe builder like any other: + +```java +@Override +protected void buildRecipes(RecipeOutput output) { + new RightClickRecipeBuilder( + // Our constructor parameters. This example adds the ever-popular dirt -> diamond conversion. + new ItemStack(Items.DIAMOND), + Blocks.DIRT.defaultBlockState(), + Ingredient.of(Items.APPLE) + ) + .unlockedBy("has_apple", this.has(Items.APPLE)) + .save(output); + // other recipe builders here +} +``` + +:::note +It is also possible to have `SimpleRecipeBuilder` be merged into `RightClickBlockRecipeBuilder` (or your own recipe builder), especially if you only have one or two recipe builders. The abstraction here serves to show which parts of the builder are recipe-dependent and which are not. +::: + +[codec]: ../../../datastorage/codecs.md +[datagen]: #data-generation +[event]: ../../../concepts/events.md +[ingredients]: ingredients.md +[networking]: ../../../networking/payload.md +[recipedatagen]: index.md#data-generation +[registry]: ../../../concepts/registries.md#methods-for-registering +[streamcodec]: ../../../networking/streamcodecs.md diff --git a/docs/resources/server/recipes/index.md b/docs/resources/server/recipes/index.md index 97322880f..c349d6f4e 100644 --- a/docs/resources/server/recipes/index.md +++ b/docs/resources/server/recipes/index.md @@ -10,10 +10,15 @@ Recipe data files are located at `data//recipe/.json`. For exam - A **`Recipe`** holds in-code representations of all JSON fields, alongside the matching logic ("Does this input match the recipe?") and some other properties. - A **`RecipeInput`** is a type that provides inputs to a recipe. Comes in several subclasses, e.g. `CraftingInput` or `SingleRecipeInput` (for furnaces and similar). - A **recipe ingredient**, or just **ingredient**, is a single input for a recipe (whereas the `RecipeInput` generally represents a collection of inputs to check against a recipe's ingredients). Ingredients are a very powerful system and as such outlined [in their own article][ingredients]. +- A **`PlacementInfo`** is a definition of items the recipe contains and what indexes they should populate. If the recipe cannot be captured to some degree based on the items provided (e.g., only changing the data components), then `PlacementInfo#NOT_PLACEABLE` is used. +- A **`SlotDisplay`** defines how a single slot should display within a recipe viewer, like the recipe book. +- A **`RecipeDisplay`** defines the `SlotDisplay`s of a recipe to be consumed by a recipe viewer, like the recipe book. While the interface only contains methods for the result of a recipe and the workstation the recipe was conducted within, a subtype can capture information like ingredients or grid size. - The **`RecipeManager`** is a singleton field on the server that holds all loaded recipes. - A **`RecipeSerializer`** is basically a wrapper around a [`MapCodec`][codec] and a [`StreamCodec`][streamcodec], both used for serialization. - A **`RecipeType`** is the registered type equivalent of a `Recipe`. It is mainly used when looking up recipes by type. As a rule of thumb, different crafting containers should use different `RecipeType`s. For example, the `minecraft:crafting` recipe type covers the `minecraft:crafting_shaped` and `minecraft:crafting_shapeless` recipe serializers, as well as the special crafting serializers. +- A **`RecipeBookCategory`** is a group representing some recipes when viewed through a recipe book. - A **recipe [advancement]** is an advancement responsible for unlocking a recipe in the recipe book. They are not required, and generally neglected by players in favor of recipe viewer mods, however the [recipe data provider][datagen] generates them for you, so it's recommended to just roll with it. +- A **`RecipePropertySet`** defines the available list of ingredients that can be accepted by the defined input slot in a menu. - A **`RecipeBuilder`** is used during datagen to create JSON recipes. - A **recipe factory** is a method reference used to create a `Recipe` from a `RecipeBuilder`. It can either be a reference to a constructor, or a static builder method, or a functional interface (often named `Factory`) created specifically for this purpose. @@ -23,10 +28,10 @@ The contents of recipe files vary greatly depending on the selected type. Common ```json5 { - // The recipe type. This maps to an entry in the recipe serializer registry. - "type": "minecraft:crafting_shaped", - // A list of data load conditions. Optional, NeoForge-added. See the article linked above for more information. - "neoforge:conditions": [ /*...*/ ] + // The recipe type. This maps to an entry in the recipe serializer registry. + "type": "minecraft:crafting_shaped", + // A list of data load conditions. Optional, NeoForge-added. See the article linked above for more information. + "neoforge:conditions": [ /*...*/ ] } ``` @@ -34,31 +39,33 @@ A full list of types provided by Minecraft can be found in the [Built-In Recipe ## Using Recipes -Recipes are loaded, stored and obtained via the `RecipeManager` class, which is in turn obtained via `ServerLevel#getRecipeManager` or - if you don't have a `ServerLevel` available - `ServerLifecycleHooks.getCurrentServer()#getRecipeManager`. Be aware that while the client has a full copy of the `RecipeManager` for display purposes, recipe logic should always run on the server to avoid sync issues. +Recipes are loaded, stored and obtained via the `RecipeManager` class, which is in turn obtained via `ServerLevel#recipeAccess` or - if you don't have a `ServerLevel` available - `ServerLifecycleHooks.getCurrentServer()#getRecipeManager`. The server does not sync the recipes to the client by default, instead it only sends the `RecipePropertySet`s for restricting inputs on menu slots. Additionally, whenever a recipe is unlocked for the recipe book, its `RecipeDisplay`s and the corresponding `RecipeDisplayEntry`s are sent to the client (excluding all recipes where `Recipe#isSpecial` returns true) As such, recipe logic should always run on the server. -The easiest way to get a recipe is by ID: +The easiest way to get a recipe is by its resource key: ```java -RecipeManager recipes = serverLevel.getRecipeManager(); -// RecipeHolder is a record of the recipe id and the recipe itself. -Optional> optional = recipes.byId(ResourceLocation.withDefaultNamespace("diamond_block")); +RecipeManager recipes = serverLevel.recipeAccess(); +// RecipeHolder is a record of the resource key and the recipe itself. +Optional> optional = recipes.byKey( + ResourceKey.create(Registries.RECIPE, ResourceLocation.withDefaultNamespace("diamond_block")) +); optional.map(RecipeHolder::value).ifPresent(recipe -> { - // Do whatever you want to do with the recipe here. Be aware that the recipe may be of any type. + // Do whatever you want to do with the recipe here. Be aware that the recipe may be of any type. }); ``` A more practically applicable method is constructing a `RecipeInput` and trying to get a matching recipe. In this example, we will be creating a `CraftingInput` containing one diamond block using `CraftingInput#of`. This will create a shapeless input, a shaped input would instead use `CraftingInput#ofPositioned`, and other inputs would use other `RecipeInput`s (for example, furnace recipes will generally use `new SingleRecipeInput`). ```java -RecipeManager recipes = serverLevel.getRecipeManager(); +RecipeManager recipes = serverLevel.recipeAccess(); // Construct a RecipeInput, as required by the recipe. For example, construct a CraftingInput for a crafting recipe. // The parameters are width, height and items, respectively. -CraftingInput input = CraftingInput.of(1, 1, List.of(Items.DIAMOND_BLOCK)); -// The generic wildcard on the recipe holder should then extend Recipe. +CraftingInput input = CraftingInput.of(1, 1, List.of(new ItemStack(Items.DIAMOND_BLOCK))); +// The generic wildcard on the recipe holder should then extend CraftingRecipe. // This allows for more type safety later on. -Optional>> optional = recipes.getRecipeFor( +Optional> optional = recipes.getRecipeFor( // The recipe type to get the recipe for. In our case, we use the crafting type. - RecipeTypes.CRAFTING, + RecipeType.CRAFTING, // Our recipe input. input, // Our level context. @@ -66,41 +73,41 @@ Optional>> optional = recipes.getRe ); // This returns the diamond block -> 9 diamonds recipe (unless a datapack changes that recipe). optional.map(RecipeHolder::value).ifPresent(recipe -> { - // Do whatever you want here. Note that the recipe is now a Recipe instead of a Recipe. + // Do whatever you want here. Note that the recipe is now a CraftingRecipe instead of a Recipe. }); ``` Alternatively, you can also get yourself a potentially empty list of recipes that match your input, this is especially useful for cases where it can be reasonably assumed that multiple recipes match: ```java -RecipeManager recipes = serverLevel.getRecipeManager(); -CraftingInput input = CraftingInput.of(1, 1, List.of(Items.DIAMOND_BLOCK)); +RecipeManager recipes = serverLevel.recipeAccess(); +CraftingInput input = CraftingInput.of(1, 1, List.of(new ItemStack(Items.DIAMOND_BLOCK))); // These are not Optionals, and can be used directly. However, the list may be empty, indicating no matching recipes. -List>> list = recipes.getRecipesFor( - // Same parameters as above. - RecipeTypes.CRAFTING, input, serverLevel +Stream>> list = recipes.recipeMap().getRecipesFor( + // Same parameters as above. + RecipeType.CRAFTING, input, serverLevel ); ``` Once we have our correct recipe inputs, we also want to get the recipe outputs. This is done by calling `Recipe#assemble`: ```java -RecipeManager recipes = serverLevel.getRecipeManager(); +RecipeManager recipes = serverLevel.recipeAccess(); CraftingInput input = CraftingInput.of(...); -Optional>> optional = recipes.getRecipeFor(...); +Optional> optional = recipes.getRecipeFor(...); // Use ItemStack.EMPTY as a fallback. ItemStack result = optional .map(RecipeHolder::value) - .map(e -> e.assemble(input, serverLevel.registryAccess())) + .map(recipe -> recipe.assemble(input, serverLevel.registryAccess())) .orElse(ItemStack.EMPTY); ``` If necessary, it is also possible to iterate over all recipes of a type. This is done like so: ```java -RecipeManager recipes = serverLevel.getRecipeManager(); +RecipeManager recipes = serverLevel.recipeAccess(); // Like before, pass the desired recipe type. -List> list = recipes.getAllRecipesFor(RecipeTypes.CRAFTING); +Collection> list = recipes.recipeMap().byType(RecipeType.CRAFTING); ``` ## Other Recipe Mechanisms @@ -133,241 +140,6 @@ public static void onAnvilUpdate(AnvilUpdateEvent event) { See [the Brewing chapter in the Mob Effects & Potions article][brewing]. -## Custom Recipes - -To add custom recipes, we need at least three things: a `Recipe`, a `RecipeType`, and a `RecipeSerializer`. Depending on what you are implementing, you may also need a custom `RecipeInput` if reusing an existing subclass is not feasible. - -For the sake of example, and to highlight many different features, we are going to implement a recipe-driven mechanic that requires you to right-click a `BlockState` in-world with a certain item, breaking the `BlockState` and dropping the result item. - -### The Recipe Input - -Let's begin by defining what we want to put into the recipe. It's important to understand that the recipe input represents the actual inputs that the player is using right now. As such, we don't use tags or ingredients here, instead we use the actual item stacks and blockstates we have available. - -```java -// Our inputs are a BlockState and an ItemStack. -public record RightClickBlockInput(BlockState state, ItemStack stack) implements RecipeInput { - // Method to get an item from a specific slot. We have one stack and no concept of slots, so we just assume - // that slot 0 holds our item, and throw on any other slot. (Taken from SingleRecipeInput#getItem.) - @Override - public ItemStack getItem(int slot) { - if (slot != 0) throw new IllegalArgumentException("No item for index " + slot); - return this.stack(); - } - - // The slot size our input requires. Again, we don't really have a concept of slots, so we just return 1 - // because we have one item stack involved. Inputs with multiple items should return the actual count here. - @Override - public int size() { - return 1; - } -} -``` - -Recipe inputs don't need to be registered or serialized in any way because they are created on demand. It is not always necessary to create your own, the vanilla ones (`CraftingInput`, `SingleRecipeInput` and `SmithingRecipeInput`) are fine for many use cases. - -Additionally, NeoForge provides the `RecipeWrapper` input, which wraps the `#getItem` and `#size` calls with respect to an `IItemHandler` passed in the constructor. Basically, this means that any grid-based inventory, such as a chest, can be used as a recipe input by wrapping it in a `RecipeWrapper`. - -### The Recipe Class - -Now that we have our inputs, let's get to the recipe itself. This is what holds our recipe data, and also handles matching and returning the recipe result. As such, it is usually the longest class for your custom recipe. - -```java -// The generic parameter for Recipe is our RightClickBlockInput from above. -public class RightClickBlockRecipe implements Recipe { - // An in-code representation of our recipe data. This can be basically anything you want. - // Common things to have here is a processing time integer of some kind, or an experience reward. - // Note that we now use an ingredient instead of an item stack for the input. - private final BlockState inputState; - private final Ingredient inputItem; - private final ItemStack result; - - // Add a constructor that sets all properties. - public RightClickBlockRecipe(BlockState inputState, Ingredient inputItem, ItemStack result) { - this.inputState = inputState; - this.inputItem = inputItem; - this.result = result; - } - - // A list of our ingredients. Does not need to be overridden if you have no ingredients - // (the default implementation returns an empty list here). It makes sense to cache larger lists in a field. - @Override - public NonNullList getIngredients() { - NonNullList list = NonNullList.create(); - list.add(this.inputItem); - return list; - } - - // Grid-based recipes should return whether their recipe can fit in the given dimensions. - // We don't have a grid, so we just return if any item can be placed in there. - @Override - public boolean canCraftInDimensions(int width, int height) { - return width * height >= 1; - } - - // Check whether the given input matches this recipe. The first parameter matches the generic. - // We check our blockstate and our item stack, and only return true if both match. - @Override - public boolean matches(RightClickBlockInput input, Level level) { - return this.inputState == input.state() && this.inputItem.test(input.stack()); - } - - // Return an UNMODIFIABLE version of your result here. The result of this method is mainly intended - // for the recipe book, and commonly used by JEI and other recipe viewers as well. - @Override - public ItemStack getResultItem(HolderLookup.Provider registries) { - return this.result; - } - - // Return the result of the recipe here, based on the given input. The first parameter matches the generic. - // IMPORTANT: Always call .copy() if you use an existing result! If you don't, things can and will break, - // as the result exists once per recipe, but the assembled stack is created each time the recipe is crafted. - @Override - public ItemStack assemble(RightClickBlockInput input, HolderLookup.Provider registries) { - return this.result.copy(); - } - - // This example outlines the most important methods. There is a number of other methods to override. - // Check the class definition of Recipe to view them all. -} -``` - -### The Recipe Type - -Next up, our recipe type. This is fairly straightforward because there's no data other than a name associated with a recipe type. They are one of two [registered][registry] parts of the recipe system, so like with all other registries, we create a `DeferredRegister` and register to it: - -```java -public static final DeferredRegister> RECIPE_TYPES = - DeferredRegister.create(Registries.RECIPE_TYPE, ExampleMod.MOD_ID); - -public static final Supplier> RIGHT_CLICK_BLOCK = - RECIPE_TYPES.register( - "right_click_block", - // We need the qualifying generic here due to generics being generics. - () -> RecipeType.simple(ResourceLocation.fromNamespaceAndPath(ExampleMod.MOD_ID, "right_click_block")) - ); -``` - -After we have registered our recipe type, we must override `#getType` in our recipe, like so: - -```java -public class RightClickBlockRecipe implements Recipe { - // other stuff here - - @Override - public RecipeType getType() { - return RIGHT_CLICK_BLOCK.get(); - } -} -``` - -### The Recipe Serializer - -A recipe serializer provides two codecs, one map codec and one stream codec, for serialization from/to JSON and from/to network, respectively. This section will not go in depth about how the codecs work, please see [Map Codecs][codec] and [Stream Codecs][streamcodec] for more information. - -Since recipe serializers can get fairly large, vanilla moves them to separate classes. It is recommended, but not required to follow the practice - smaller serializers are often defined in anonymous classes within fields of the recipe class. To follow good practice, we will create a separate class that holds our codecs: - -```java -// The generic parameter is our recipe class. -// Note: This assumes that simple RightClickBlockRecipe#getInputState, #getInputItem and #getResult getters -// are available, which were omitted from the code above. -public class RightClickBlockRecipeSerializer implements RecipeSerializer { - public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(inst -> inst.group( - BlockState.CODEC.fieldOf("state").forGetter(RightClickBlockRecipe::getInputState), - Ingredient.CODEC.fieldOf("ingredient").forGetter(RightClickBlockRecipe::getInputItem), - ItemStack.CODEC.fieldOf("result").forGetter(RightClickBlockRecipe::getResult) - ).apply(inst, RightClickBlockRecipe::new)); - public static final StreamCodec STREAM_CODEC = - StreamCodec.composite( - ByteBufCodecs.idMapper(Block.BLOCK_STATE_REGISTRY), RightClickBlockRecipe::getInputState, - Ingredient.CONTENTS_STREAM_CODEC, RightClickBlockRecipe::getInputItem, - ItemStack.STREAM_CODEC, RightClickBlockRecipe::getResult, - RightClickBlockRecipe::new - ); - - // Return our map codec. - @Override - public MapCodec codec() { - return CODEC; - } - - // Return our stream codec. - @Override - public StreamCodec streamCodec() { - return STREAM_CODEC; - } -} -``` - -Like with the type, we register our serializer: - -```java -public static final DeferredRegister> RECIPE_SERIALIZERS = - DeferredRegister.create(Registries.RECIPE_SERIALIZER, ExampleMod.MOD_ID); - -public static final Supplier> RIGHT_CLICK_BLOCK = - RECIPE_SERIALIZERS.register("right_click_block", RightClickBlockRecipeSerializer::new); -``` - -And similarly, we must also override `#getSerializer` in our recipe, like so: - -```java -public class RightClickBlockRecipe implements Recipe { - // other stuff here - - @Override - public RecipeSerializer getSerializer() { - return RIGHT_CLICK_BLOCK.get(); - } -} -``` - -### The Crafting Mechanic - -Now that all parts of your recipe are complete, you can make yourself some recipe JSONs (see the [datagen] section for that) and then query the recipe manager for your recipes, like above. What you then do with the recipe is up to you. A common use case would be a machine that can process your recipes, storing the active recipe as a field. - -In our case, however, we want to apply the recipe when an item is right-clicked on a block. We will do so using an [event handler][event]. Keep in mind that this is an example implementation, and you can alter this in any way you like (so long as you run it on the server). - -```java -@SubscribeEvent -public static void useItemOnBlock(UseItemOnBlockEvent event) { - // Skip if we are not in the block-dictated phase of the event. See the event's javadocs for details. - if (event.getUsePhase() != UseItemOnBlockEvent.UsePhase.BLOCK) return; - // Get the parameters we need. - UseOnContext context = event.getUseOnContext(); - Level level = context.getLevel(); - BlockPos pos = context.getClickedPos(); - BlockState blockState = level.getBlockState(pos); - ItemStack itemStack = context.getItemInHand(); - RecipeManager recipes = level.getRecipeManager(); - // Create an input and query the recipe. - RightClickBlockInput input = new RightClickBlockInput(blockState, itemStack); - Optional>> optional = recipes.getRecipeFor( - // The recipe type. - RIGHT_CLICK_BLOCK, - input, - level - ); - ItemStack result = optional - .map(RecipeHolder::value) - .map(e -> e.assemble(input, level.registryAccess())) - .orElse(ItemStack.EMPTY); - // If there is a result, break the block and drop the result in the world. - if (!result.isEmpty()) { - level.removeBlock(pos, false); - // If the level is not a server level, don't spawn the entity. - if (!level.isClientSide()) { - ItemEntity entity = new ItemEntity(level, - // Center of pos. - pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, - result); - level.addFreshEntity(entity); - } - // Cancel the event to stop the interaction pipeline. - event.cancelWithResult(ItemInteractionResult.sidedSuccess(level.isClientSide)); - } -} -``` - ### Extending the Crafting Grid Size The `ShapedRecipePattern` class, responsible for holding the in-memory representation of shaped crafting recipes, has a hardcoded limit of 3x3 slots, hindering mods that want to add larger crafting tables while reusing the vanilla shaped crafting recipe type. To solve this problem, NeoForge patches in a static method called `ShapedRecipePattern#setCraftingSize(int width, int height)` that allows increasing the limit. It should be called during `FMLCommonSetupEvent`. The biggest value wins here, so for example if one mod added a 4x6 crafting table and another added a 6x5 crafting table, the resulting values would be 6x6. @@ -378,25 +150,39 @@ The `ShapedRecipePattern` class, responsible for holding the in-memory represent ## Data Generation -Like most other JSON files, recipes can be datagenned. For recipes, we want to extend the `RecipeProvider` class and override `#buildRecipes`: +Like most other JSON files, recipes can be datagenned. For recipes, we want to extend the `RecipeProvider` class and override `#buildRecipes`, and extend the `RecipeProvider.Runner` class to pass to the data generator: ```java public class MyRecipeProvider extends RecipeProvider { - // Get the parameters from GatherDataEvent. - public MyRecipeProvider(PackOutput output, CompletableFuture lookupProvider) { - super(output, registries); - } + // Construct the provider to run + protected MyRecipeProvider(HolderLookup.Provider provider, RecipeOutput output) { + super(provider, output); + } + @Override - protected void buildRecipes(RecipeOutput output) { + protected void buildRecipes() { // Add your recipes here. } + + // The runner to add to the data generator + public static class Runner extends RecipeProvider.Runner { + // Get the parameters from GatherDataEvent. + public Runner(PackOutput output, CompletableFuture lookupProvider) { + super(output, registries); + } + + @Override + protected abstract RecipeProvider createRecipeProvider(HolderLookup.Provider provider, RecipeOutput output) { + return new MyRecipeProvider(provider, output); + } + } } ``` -Of note is the `RecipeOutput` parameter of `#buildRecipes`. Minecraft uses this object to automatically generate a recipe advancement for you. On top of that, NeoForge injects [conditions] support into `RecipeOutput`, which can be called on via `#withConditions`. +Of note is the `RecipeOutput` parameter. Minecraft uses this object to automatically generate a recipe advancement for you. On top of that, NeoForge injects [conditions] support into `RecipeOutput`, which can be called on via `#withConditions`. -Recipes themselves are commonly added through subclasses of `RecipeBuilder`. Listing all vanilla recipe builders is beyond the scope of this article (they are explained in the [Built-In Recipe Types article][builtin]), however creating your own builder is explained [below][customdatagen]. +Recipes themselves are commonly added through subclasses of `RecipeBuilder`. Listing all vanilla recipe builders is beyond the scope of this article (they are explained in the [Built-In Recipe Types article][builtin]), however creating your own builder is explained [in the custom recipes page][customdatagen]. Like all other data providers, recipe providers must be registered to `GatherDataEvent` like so: @@ -410,125 +196,23 @@ public static void gatherData(GatherDataEvent event) { // other providers here generator.addProvider( event.includeServer(), - new MyRecipeProvider(output, lookupProvider) + new MyRecipeProvider.Runner(output, lookupProvider) ); } ``` The recipe provider also adds helpers for common scenarios, such as `twoByTwoPacker` (for 2x2 block recipes), `threeByThreePacker` (for 3x3 block recipes) or `nineBlockStorageRecipes` (for 3x3 block recipes and 1 block to 9 items recipes). -### Data Generation for Custom Recipes - -To create a recipe builder for your own recipe serializer(s), you need to implement `RecipeBuilder` and its methods. A common implementation, partially copied from vanilla, would look like this: - -```java -// This class is abstract because there is a lot of per-recipe-serializer logic. -// It serves the purpose of showing the common part of all (vanilla) recipe builders. -public abstract class SimpleRecipeBuilder implements RecipeBuilder { - // Make the fields protected so our subclasses can use them. - protected final ItemStack result; - protected final Map> criteria = new LinkedHashMap<>(); - @Nullable - protected final String group; - - // It is common for constructors to accept the result item stack. - // Alternatively, static builder methods are also possible. - public SimpleRecipeBuilder(ItemStack result) { - this.result = result; - } - - // This method adds a criterion for the recipe advancement. - @Override - public SimpleRecipeBuilder unlockedBy(String name, Criterion criterion) { - this.criteria.put(name, criterion); - return this; - } - - // This method adds a recipe book group. If you do not want to use recipe book groups, - // remove the this.group field and make this method no-op (i.e. return this). - @Override - public SimpleRecipeBuilder group(@Nullable String group) { - this.group = group; - return this; - } - - // Vanilla wants an Item here, not an ItemStack. You still can and should use the ItemStack - // for serializing the recipes. - @Override - public Item getResult() { - return this.result.getItem(); - } -} -``` - -So we have a base for our recipe builder. Now, before we continue with the recipe serializer-dependent part, we should first consider what to make our recipe factory. In our case, it makes sense to use the constructor directly. In other situations, using a static helper or a small functional interface is the way to go. This is especially relevant if you use one builder for multiple recipe classes. - -Utilizing `RightClickBlockRecipe::new` as our recipe factory, and reusing the `SimpleRecipeBuilder` class above, we can create the following recipe builder for `RightClickBlockRecipe`s: - -```java -public class RightClickBlockRecipeBuilder extends SimpleRecipeBuilder { - private final BlockState inputState; - private final Ingredient inputItem; - - // Since we have exactly one of each input, we pass them to the constructor. - // Builders for recipe serializers that have ingredient lists of some sort would usually - // initialize an empty list and have #addIngredient or similar methods instead. - public RightClickBlockRecipeBuilder(ItemStack result, BlockState inputState, Ingredient inputItem) { - super(result); - this.inputState = inputState; - this.inputItem = inputItem; - } - - // Saves a recipe using the given RecipeOutput and id. This method is defined in the RecipeBuilder interface. - @Override - public void save(RecipeOutput output, ResourceLocation id) { - // Build the advancement. - Advancement.Builder advancement = output.advancement() - .addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(id)) - .rewards(AdvancementRewards.Builder.recipe(id)) - .requirements(AdvancementRequirements.Strategy.OR); - this.criteria.forEach(advancement::addCriterion); - // Our factory parameters are the result, the block state, and the ingredient. - RightClickBlockRecipe recipe = new RightClickBlockRecipe(this.inputState, this.inputItem, this.result); - // Pass the id, the recipe, and the recipe advancement into the RecipeOutput. - output.accept(id, recipe, advancement.build(id.withPrefix("recipes/"))); - } -} -``` - -And now, during datagen, you can call on your recipe builder like any other: - -```java -@Override -protected void buildRecipes(RecipeOutput output) { - new RightClickRecipeBuilder( - // Our constructor parameters. This example adds the ever-popular dirt -> diamond conversion. - new ItemStack(Items.DIAMOND), - Blocks.DIRT.defaultBlockState(), - Ingredient.of(Items.APPLE) - ) - .unlockedBy("has_apple", has(Items.APPLE)) - .save(output); - // other recipe builders here -} -``` - -:::note -It is also possible to have `SimpleRecipeBuilder` be merged into `RightClickBlockRecipeBuilder` (or your own recipe builder), especially if you only have one or two recipe builders. The abstraction here serves to show which parts of the builder are recipe-dependent and which are not. -::: - [advancement]: ../advancements.md -[brewing]: ../../../items/mobeffects.md +[brewing]: ../../../items/mobeffects.md#brewing [builtin]: builtin.md [cancel]: ../../../concepts/events.md#cancellable-events [codec]: ../../../datastorage/codecs.md [conditions]: ../conditions.md -[customdatagen]: #data-generation-for-custom-recipes -[customrecipes]: #custom-recipes +[customdatagen]: custom.md#data-generation +[customrecipes]: custom.md [datagen]: #data-generation [event]: ../../../concepts/events.md [ingredients]: ingredients.md -[manager]: #using-recipes -[registry]: ../../../concepts/registries.md [streamcodec]: ../../../networking/streamcodecs.md [tags]: ../tags.md diff --git a/docs/resources/server/recipes/ingredients.md b/docs/resources/server/recipes/ingredients.md index 9792fcf6d..95b17612d 100644 --- a/docs/resources/server/recipes/ingredients.md +++ b/docs/resources/server/recipes/ingredients.md @@ -10,17 +10,21 @@ The simplest way to get an ingredient is using the `Ingredient#of` helpers. Seve - `Ingredient.of()` returns an empty ingredient. - `Ingredient.of(Blocks.IRON_BLOCK, Items.GOLD_BLOCK)` returns an ingredient that accepts either an iron or a gold block. The parameter is a vararg of [`ItemLike`s][itemlike], which means that any amount of both blocks and items may be used. -- `Ingredient.of(new ItemStack(Items.DIAMOND_SWORD))` returns an ingredient that accepts an item stack. Be aware that counts and data components are ignored. -- `Ingredient.of(Stream.of(new ItemStack(Items.DIAMOND_SWORD)))` returns an ingredient that accepts an item stack. Like the previous method, but with a `Stream` for if you happen to get your hands on one of those. -- `Ingredient.of(ItemTags.WOODEN_SLABS)` returns an ingredient that accepts any item from the specified [tag], for example any wooden slab. +- `Ingredient.of(Stream.of(Items.DIAMOND_SWORD))` returns an ingredient that accepts an item. Like the previous method, but with a `Stream` for if you happen to get your hands on one of those. +- `Ingredient.of(BuiltInRegistries.ITEM.getOrThrow(ItemTags.WOODEN_SLABS))` returns an ingredient that accepts any item from the specified [tag], for example any wooden slab. Additionally, NeoForge adds a few additional ingredients: - `BlockTagIngredient.of(BlockTags.CONVERTABLE_TO_MUD)` returns an ingredient similar to the tag variant of `Ingredient.of()`, but with a block tag instead. This should be used for cases where you'd use an item tag, but there is only a block tag available (for example `minecraft:convertable_to_mud`). +- `CustomDisplayIngredient.of(Ingredient.of(Items.DIRT), SlotDisplay.Empty.INSTANCE)` returns an ingredient with a custom [`SlotDisplay`][slotdisplay] you provide to determine how the slot gets consumed for rendering on the client. - `CompoundIngredient.of(Ingredient.of(Items.DIRT))` returns an ingredient with child ingredients, passed in the constructor (vararg parameter). The ingredient matches if any of its children matches. - `DataComponentIngredient.of(true, new ItemStack(Items.DIAMOND_SWORD))` returns an ingredient that, in addition to the item, also matches the data component. The boolean parameter denotes strict matching (true) or partial matching (false). Strict matching means the data components must match exactly, while partial matching means the data components must match, but other data components may also be present. Additional overloads of `#of` exist that allow specifying multiple `Item`s, or provide other options. -- `DifferenceIngredient.of(Ingredient.of(ItemTags.PLANKS), Ingredient.of(ItemTags.NON_FLAMMABLE_WOOD))` returns an ingredient that matches everything in the first ingredient that doesn't also match the second ingredient. The given example only matches planks that can burn (i.e. all planks except crimson planks, warped planks and modded nether wood planks). -- `IntersectionIngredient.of(Ingredient.of(ItemTags.PLANKS), Ingredient.of(ItemTags.NON_FLAMMABLE_WOOD))` returns an ingredient that matches everything that matches both sub-ingredients. The given example only matches planks that cannot burn (i.e. crimson planks, warped planks and modded nether wood planks). +- `DifferenceIngredient.of(Ingredient.of(BuiltInRegistries.ITEM.getOrThrow(ItemTags.PLANKS)), Ingredient.of(BuiltInRegistries.ITEM.getOrThrow(ItemTags.NON_FLAMMABLE_WOOD)))` returns an ingredient that matches everything in the first ingredient that doesn't also match the second ingredient. The given example only matches planks that can burn (i.e. all planks except crimson planks, warped planks and modded nether wood planks). +- `IntersectionIngredient.of(Ingredient.of(BuiltInRegistries.ITEM.getOrThrow(ItemTags.PLANKS)), Ingredient.of(BuiltInRegistries.ITEM.getOrThrow(ItemTags.NON_FLAMMABLE_WOOD)))` returns an ingredient that matches everything that matches both sub-ingredients. The given example only matches planks that cannot burn (i.e. crimson planks, warped planks and modded nether wood planks). + +:::note +If you are using data generation with ingredients that take in a `HolderSet` for the tag instance (the ones that call `Registry#getOrThrow`), then that `HolderSet` should be obtained via the `HolderLookup.Provider`, using `HolderLookup.Provider#lookupOrThrow` to get the item registry and `HolderGetter#getOrThrow` with the `TagKey` to get the holder set. +::: Keep in mind that the NeoForge-provided ingredient types are `ICustomIngredient`s and must call `#toVanilla` before using them in vanilla contexts, as outlined in the beginning of this article. @@ -50,23 +54,13 @@ public class MinEnchantedIngredient implements ICustomIngredient { this.enchantments = enchantments; } - public MinEnchantedIngredient(TagKey tag) { - this(tag, new HashMap<>()); - } - - // Make this chainable for easy use. - public MinEnchantedIngredient with(Holder enchantment, int level) { - enchantments.put(enchantment, level); - return this; - } - // Check if the passed ItemStack matches our ingredient by verifying the item is in the tag // and by testing for presence of all required enchantments with at least the required level. @Override public boolean test(ItemStack stack) { return stack.is(tag) && enchantments.keySet() .stream() - .allMatch(ench -> stack.getEnchantmentLevel(ench) >= enchantments.get(ench)); + .allMatch(ench -> EnchantmentHelper.getEnchantmentsForCrafting(stack).getLevel(ench) >= enchantments.get(ench)); } // Determines whether this ingredient performs NBT or data component matching (false) or not (true). @@ -79,27 +73,14 @@ public class MinEnchantedIngredient implements ICustomIngredient { // Returns a stream of items that match this ingredient. Mostly for display purposes. // There's a few good practices to follow here: - // - Always include at least one item stack, to prevent accidental recognition as empty. + // - Always include at least one item, to prevent accidental recognition as empty. // - Include each accepted Item at least once. - // - If #isSimple is true, this should be exact and contain every item stack that matches. + // - If #isSimple is true, this should be exact and contain every item that matches. // If not, this should be as exact as possible, but doesn't need to be super accurate. - // In our case, we use all items in the tag, each with the required enchantments. + // In our case, we use all items in the tag. @Override - public Stream getItems() { - // Get a list of item stacks, one stack per item in the tag. - List stacks = BuiltInRegistries.ITEM - .getOrCreateTag(tag) - .stream() - .map(ItemStack::new) - .toList(); - // Enchant all stacks with all enchantments. - for (ItemStack stack : stacks) { - enchantments - .keySet() - .forEach(ench -> stack.enchant(ench, enchantments.get(ench))); - } - // Return stream variant of the list. - return stacks.stream(); + public Stream> getItems() { + return BuiltInRegistries.ITEM.getOrThrow(tag).stream(); } } ``` @@ -125,7 +106,7 @@ public class MinEnchantedIngredient implements ICustomIngredient { @Override public IngredientType getType() { - return MIN_ENCHANTED; + return MIN_ENCHANTED.get(); } } ``` @@ -136,12 +117,12 @@ And there we go! Our ingredient type is ready to use. Due to vanilla ingredients being pretty limited and NeoForge introducing a whole new registry for them, it's also worth looking at what the built-in and our own ingredients look like in JSON. -Ingredients that specify a `type` are generally assumed to be non-vanilla. For example: +Ingredients that are objects and specify a `neoforge:ingredient_type` are generally assumed to be non-vanilla. For example: ```json5 { - "type": "neoforge:block_tag", - "tag": "minecraft:convertable_to_mud" + "neoforge:ingredient_type": "neoforge:block_tag", + "tag": "minecraft:convertable_to_mud" } ``` @@ -149,30 +130,26 @@ Or another example using our own ingredient: ```json5 { - "type": "examplemod:min_enchanted", - "tag": "c:swords", - "enchantments": { - "minecraft:sharpness": 4 - } + "neoforge:ingredient_type": "examplemod:min_enchanted", + "tag": "c:swords", + "enchantments": { + "minecraft:sharpness": 4 + } } ``` -If the `type` is unspecified, then we have a vanilla ingredient. Vanilla ingredients can specify one of two properties: `item` or `tag`. +If the ingredient is a string, meaning `neoforge:ingredient_type` is unspecified, then we have a vanilla ingredient. Vanilla ingredients are strings that either represent an item, or a tag when prefixed with `#`. An example for a vanilla item ingredient: ```json5 -{ - "item": "minecraft:dirt" -} +"minecraft:dirt" ``` An example for a vanilla tag ingredient: ```json5 -{ - "tag": "c:ingots" -} +"#c:ingots" ``` [codec]: ../../../datastorage/codecs.md @@ -180,5 +157,6 @@ An example for a vanilla tag ingredient: [itemstack]: ../../../items/index.md#itemstacks [recipes]: index.md [registry]: ../../../concepts/registries.md +[slotdisplay]: index.md#slot-displays [streamcodec]: ../../../networking/streamcodecs.md [tag]: ../tags.md diff --git a/docs/resources/server/tags.md b/docs/resources/server/tags.md index 37e2e79c3..7b013db02 100644 --- a/docs/resources/server/tags.md +++ b/docs/resources/server/tags.md @@ -20,30 +20,30 @@ Tag files have the following syntax: ```json5 { - // The values of the tag. - "values": [ - // A value object. Must specify the id of the object to add, and whether it is required. - // If the entry is required, but the object is not present, the tag will not load. The "required" field - // is technically optional, but when removed, the entry is equivalent to the shorthand below. - { - "id": "examplemod:example_ingot", - "required": false - } - // Shorthand for {"id": "minecraft:gold_ingot", "required": true}, i.e. a required entry. - "minecraft:gold_ingot", - // A tag object. Distinguished from regular entries by the leading #. In this case, all planks - // will be considered entries of the tag. Like normal entries, this can also have the "id"/"required" format. - // Warning: Circular tag dependencies will lead to a datapack not being loaded! - "#minecraft:planks" - ], - // Whether to remove all pre-existing entries before adding your own (true) or just add your own (false). - // This should generally be false, the option to set this to true is primarily aimed at pack developers. - "replace": false, - // A finer-grained way to remove entries from the tag again, if present. Optional, NeoForge-added. - // Entry syntax is the same as in the "values" array. - "remove": [ - "minecraft:iron_ingot" - ] + // The values of the tag. + "values": [ + // A value object. Must specify the id of the object to add, and whether it is required. + // If the entry is required, but the object is not present, the tag will not load. The "required" field + // is technically optional, but when removed, the entry is equivalent to the shorthand below. + { + "id": "examplemod:example_ingot", + "required": false + } + // Shorthand for {"id": "minecraft:gold_ingot", "required": true}, i.e. a required entry. + "minecraft:gold_ingot", + // A tag object. Distinguished from regular entries by the leading #. In this case, all planks + // will be considered entries of the tag. Like normal entries, this can also have the "id"/"required" format. + // Warning: Circular tag dependencies will lead to a datapack not being loaded! + "#minecraft:planks" + ], + // Whether to remove all pre-existing entries before adding your own (true) or just add your own (false). + // This should generally be false, the option to set this to true is primarily aimed at pack developers. + "replace": false, + // A finer-grained way to remove entries from the tag again, if present. Optional, NeoForge-added. + // Entry syntax is the same as in the "values" array. + "remove": [ + "minecraft:iron_ingot" + ] } ``` @@ -87,7 +87,8 @@ We can then use our tag to perform various operations on it. Let's start with th ```java // Check whether dirt is in our tag. -boolean isInTag = BuiltInRegistries.BLOCK.getOrCreateTag(MY_TAG).stream().anyMatch(e -> e == Items.DIRT); +// Assume access to Level level +boolean isInTag = level.registryAccess().lookupOrThrow(BuiltInRegistries.BLOCK).getOrThrow(MY_TAG).stream().anyMatch(holder -> holder.is(Items.DIRT)); ``` Since this is a very verbose statement, especially when used often, `BlockState` and `ItemStack` - the two most common users of the tag system - each define a `#is` helper method, used like so: @@ -99,36 +100,20 @@ boolean isInBlockTag = blockState.is(MY_TAG); boolean isInItemTag = itemStack.is(MY_ITEM_TAG); ``` -If needed, we can also get ourselves a set of tag entries, like so: +If needed, we can also get ourselves a set of tag entries to stream, like so: ```java -Set blocksInTag = BuiltInRegistries.BLOCK.getOrCreateTag(MY_TAG).stream().toSet(); +// Assume access to Level level +Stream> blocksInTag = level.registryAccess().lookupOrThrow(BuiltInRegistries.BLOCK).getOrThrow(MY_TAG).stream(); ``` -For performance reasons, it is recommended to cache these sets in a field, invalidating them when tags are reloaded (which can be listened for using `TagsUpdatedEvent`). This can be done like so: - -```java -public class MyTagsCacheClass { - private static Set blocksInTag = null; +### Tags for Static Registries During Bootstrap - public static Set getBlockTagContents() { - if (blocksInTag == null) { - // Wrap as an unmodifiable set, as we're not supposed to modify this anyway - blocksInTag = Collections.unmodifiableSet(BuiltInRegistries.BLOCK.getOrCreateTag(MY_TAG).stream().toSet()); - } - return blocksInTag; - } - - public static void invalidateCache() { - blocksInTag = null; - } -} +Sometimes, you need to get access to a `HolderSet` during the registry process. In this case, for static registries **only**, you can obtain the necessary `HolderGetter` via `BuiltInRegistries#acquireBootstrapRegistrationLookup`: -// In an event handler class -@SubscribeEvent -public static void onTagsUpdated(TagsUpdatedEvent event) { - MyTagsCacheClass.invalidateCache(); -} +```java +// Assume access to Level level +HolderSet blockTag = BuiltInRegistries.acquireBootstrapRegistrationLookup(BuiltInRegistries.BLOCK).getOrThrow(MY_TAG); ``` ## Datagen @@ -169,7 +154,7 @@ public class MyBlockTagsProvider extends BlockTagsProvider { @Override protected void addTags(HolderLookup.Provider lookupProvider) { // Create a tag builder for our tag. This could also be e.g. a vanilla or NeoForge tag. - tag(MY_TAG) + this.tag(MY_TAG) // Add entries. This is a vararg parameter. // Non-intrinsic providers must provide ResourceKeys here instead of the actual objects. .add(Blocks.DIRT, Blocks.COBBLESTONE) @@ -202,33 +187,33 @@ This example results in the following tag JSON: ```json5 { - "values": [ - "minecraft:dirt", - "minecraft:cobblestone", - { - "id": "botania:pure_daisy", - "required": false - }, - "#minecraft:planks", - "#minecraft:logs", - "#minecraft:wooden_slabs", - { - "id": "c:ingots/tin", - "required": false - }, - { - "id": "c:nuggets/tin", - "required": false - }, - { - "id": "c:storage_blocks/tin", - "required": false - } - ], - "remove": [ - "minecraft:crimson_slab", - "minecraft:warped_slab" - ] + "values": [ + "minecraft:dirt", + "minecraft:cobblestone", + { + "id": "botania:pure_daisy", + "required": false + }, + "#minecraft:planks", + "#minecraft:logs", + "#minecraft:wooden_slabs", + { + "id": "c:ingots/tin", + "required": false + }, + { + "id": "c:nuggets/tin", + "required": false + }, + { + "id": "c:storage_blocks/tin", + "required": false + } + ], + "remove": [ + "minecraft:crimson_slab", + "minecraft:warped_slab" + ] } ``` @@ -253,7 +238,7 @@ public static void gatherData(GatherDataEvent event) { ```java // In an ItemTagsProvider's #addTags method, assuming types TagKey and TagKey for the two parameters. -copy(EXAMPLE_BLOCK_TAG, EXAMPLE_ITEM_TAG); +this.copy(EXAMPLE_BLOCK_TAG, EXAMPLE_ITEM_TAG); ``` ### Custom Tag Providers diff --git a/docs/worldgen/_category_.json b/docs/worldgen/_category_.json index 7b025865e..019b3d56f 100644 --- a/docs/worldgen/_category_.json +++ b/docs/worldgen/_category_.json @@ -1,4 +1,4 @@ { - "label": "Worldgen", - "position": 10 + "label": "Worldgen", + "position": 10 } \ No newline at end of file diff --git a/docs/worldgen/biomemodifier.md b/docs/worldgen/biomemodifier.md index ba9102227..d197ad698 100644 --- a/docs/worldgen/biomemodifier.md +++ b/docs/worldgen/biomemodifier.md @@ -8,20 +8,20 @@ Biome Modifiers are a data-driven system that allows for changing many aspects o ### Recommended Section To Read: - Players or pack developers: - - [Applying Biome Modifiers](#applying-biome-modifiers) - - [Built-in Neoforge Biome Modifiers](#built-in-biome-modifiers) + - [Applying Biome Modifiers](#applying-biome-modifiers) + - [Built-in Neoforge Biome Modifiers](#built-in-biome-modifiers) - Modders doing simple additions or removal biome modifications: - - [Applying Biome Modifiers](#applying-biome-modifiers) - - [Built-in Neoforge Biome Modifiers](#built-in-biome-modifiers) - - [Datagenning Biome Modifiers](#datagenning-biome-modifiers) + - [Applying Biome Modifiers](#applying-biome-modifiers) + - [Built-in Neoforge Biome Modifiers](#built-in-biome-modifiers) + - [Datagenning Biome Modifiers](#datagenning-biome-modifiers) - Modders who want to do custom or complex biome modifications: - - [Applying Biome Modifiers](#applying-biome-modifiers) - - [Creating Custom Biome Modifiers](#creating-custom-biome-modifiers) - - [Datagenning Biome Modifiers](#datagenning-biome-modifiers) + - [Applying Biome Modifiers](#applying-biome-modifiers) + - [Creating Custom Biome Modifiers](#creating-custom-biome-modifiers) + - [Datagenning Biome Modifiers](#datagenning-biome-modifiers) ## Applying Biome Modifiers @@ -39,16 +39,16 @@ These biome modifiers are registered by NeoForge for anyone to use. This biome modifier has no operation and will do no modification. Pack makers and players can use this in a datapack to disable mods' biome modifiers by overriding their biome modifier JSONs with the JSON below. - + ```json5 { - "type": "neoforge:none" + "type": "neoforge:none" } ``` - - + + ```java // Define the ResourceKey for our BiomeModifier. @@ -65,7 +65,7 @@ BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { }); ``` - + ### Add Features @@ -73,27 +73,27 @@ BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { This biome modifier type adds `PlacedFeature`s (such as trees or ores) to biomes so that they can spawn during world generation. The modifier takes in the biome id or tag of the biomes the features are added to, a `PlacedFeature` id or tag to add to the selected biomes, and the [`GenerationStep.Decoration`](#Available-Values-for-Decoration-Steps) the features will be generated within. - + ```json5 { - "type": "neoforge:add_features", - // Can either be a biome id, such as "minecraft:plains", - // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], - // or a biome tag, such as "#c:is_overworld". - "biomes": "#namespace:your_biome_tag", - // Can either be a placed feature id, such as "examplemod:add_features_example", - // or a list of placed feature ids, such as ["examplemod:add_features_example", minecraft:ice_spike", ...], - // or a placed feature tag, such as "#examplemod:placed_feature_tag". - "features": "namespace:your_feature", - // See the GenerationStep.Decoration enum in code for a list of valid enum names. - // The decoration step section further down also has the list of values for reference. - "step": "underground_ores" + "type": "neoforge:add_features", + // Can either be a biome id, such as "minecraft:plains", + // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], + // or a biome tag, such as "#c:is_overworld". + "biomes": "#namespace:your_biome_tag", + // Can either be a placed feature id, such as "examplemod:add_features_example", + // or a list of placed feature ids, such as ["examplemod:add_features_example", minecraft:ice_spike", ...], + // or a placed feature tag, such as "#examplemod:placed_feature_tag". + "features": "namespace:your_feature", + // See the GenerationStep.Decoration enum in code for a list of valid enum names. + // The decoration step section further down also has the list of values for reference. + "step": "underground_ores" } ``` - - + + ```java // Assume we have some PlacedFeature named EXAMPLE_PLACED_FEATURE. @@ -125,7 +125,7 @@ BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { }) ``` - + @@ -140,7 +140,7 @@ Vanilla `PlacedFeature`s can be referenced in biome JSONs or added via biome mod This biome modifier type removes features (such as trees or ores) from biomes so that they will no longer spawn during world generation. The modifier takes in the biome id or tag of the biomes the features are removed from, a `PlacedFeature` id or tag to remove from the selected biomes, and the [`GenerationStep.Decoration`](#Available-Values-for-Decoration-Steps)s that the features will be removed from. - + ```json5 { @@ -161,8 +161,8 @@ This biome modifier type removes features (such as trees or ores) from biomes so } ``` - - + + ```java // Define the ResourceKey for our BiomeModifier. @@ -196,7 +196,7 @@ BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { }); ``` - + @@ -209,35 +209,35 @@ If you are a modder adding a new entity, make sure the entity has a spawn restri ::: - + ```json5 { - "type": "neoforge:add_spawns", - // Can either be a biome id, such as "minecraft:plains", - // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], - // or a biome tag, such as "#c:is_overworld". - "biomes": "#namespace:biome_tag", - // Can be either a single object or a list of objects. - "spawners": [ - { - "type": "namespace:entity_type", // The id of the entity type to spawn - "weight": 100, // int, spawn weight - "minCount": 1, // int, minimum group size - "maxCount": 4 // int, maximum group size - }, - { - "type": "minecraft:ghast", - "weight": 1, - "minCount": 5, - "maxCount": 10 - } - ] + "type": "neoforge:add_spawns", + // Can either be a biome id, such as "minecraft:plains", + // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], + // or a biome tag, such as "#c:is_overworld". + "biomes": "#namespace:biome_tag", + // Can be either a single object or a list of objects. + "spawners": [ + { + "type": "namespace:entity_type", // The id of the entity type to spawn + "weight": 100, // non-negative int, spawn weight + "minCount": 1, // positive int, minimum group size + "maxCount": 4 // positive int, maximum group size + }, + { + "type": "minecraft:ghast", + "weight": 1, + "minCount": 5, + "maxCount": 10 + } + ] } ``` - - + + ```java // Assume we have some EntityType named EXAMPLE_ENTITY. @@ -269,7 +269,7 @@ BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { }); ``` - + @@ -278,24 +278,24 @@ BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { This biome modifier type removes entity spawns from biomes. The modifier takes in the biome id or tag of the biomes the entity spawns are removed from, and the `EntityType` id or tag of the entities to remove. - + ```json5 { - "type": "neoforge:remove_spawns", - // Can either be a biome id, such as "minecraft:plains", - // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], - // or a biome tag, such as "#c:is_overworld". - "biomes": "#namespace:biome_tag", - // Can either be an entity type id, such as "minecraft:ghast", - // or a list of entity type ids, such as ["minecraft:ghast", "minecraft:skeleton", ...], - // or an entity type tag, such as "#minecraft:skeletons". - "entity_types": "#namespace:entitytype_tag" + "type": "neoforge:remove_spawns", + // Can either be a biome id, such as "minecraft:plains", + // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], + // or a biome tag, such as "#c:is_overworld". + "biomes": "#namespace:biome_tag", + // Can either be an entity type id, such as "minecraft:ghast", + // or a list of entity type ids, such as ["minecraft:ghast", "minecraft:skeleton", ...], + // or an entity type tag, such as "#minecraft:skeletons". + "entity_types": "#namespace:entitytype_tag" } ``` - - + + ```java // Define the ResourceKey for our BiomeModifier. @@ -324,7 +324,7 @@ BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { }); ``` - + @@ -339,30 +339,30 @@ If you are a modder adding a new entity, make sure the entity has a spawn restri ::: - + ```json5 { - "type": "neoforge:add_spawn_costs", - // Can either be a biome id, such as "minecraft:plains", - // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], - // or a biome tag, such as "#c:is_overworld". - "biomes": "#namespace:biome_tag", - // Can either be an entity type id, such as "minecraft:ghast", - // or a list of entity type ids, such as ["minecraft:ghast", "minecraft:skeleton", ...], - // or an entity type tag, such as "#minecraft:skeletons". - "entity_types": "#minecraft:skeletons", - "spawn_cost": { - // The energy budget - "energy_budget": 1.0, - // The amount of charge each entity takes up from the budget - "charge": 0.1 - } + "type": "neoforge:add_spawn_costs", + // Can either be a biome id, such as "minecraft:plains", + // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], + // or a biome tag, such as "#c:is_overworld". + "biomes": "#namespace:biome_tag", + // Can either be an entity type id, such as "minecraft:ghast", + // or a list of entity type ids, such as ["minecraft:ghast", "minecraft:skeleton", ...], + // or an entity type tag, such as "#minecraft:skeletons". + "entity_types": "#minecraft:skeletons", + "spawn_cost": { + // The energy budget + "energy_budget": 1.0, + // The amount of charge each entity takes up from the budget + "charge": 0.1 + } } ``` - - + + ```java // Define the ResourceKey for our BiomeModifier. @@ -395,7 +395,7 @@ BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { }); ``` - + @@ -404,24 +404,24 @@ BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { Allows for removing a spawn cost from a biome. Spawn costs are a newer way of making mobs spawn spread out in a biome to reduce clustering. The modifier takes in the biome id or tag of the biomes the spawn costs are removed from, and the `EntityType` id or tag of the entities to remove the spawn cost for. - + ```json5 { - "type": "neoforge:remove_spawn_costs", - // Can either be a biome id, such as "minecraft:plains", - // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], - // or a biome tag, such as "#c:is_overworld". - "biomes": "#namespace:biome_tag", - // Can either be an entity type id, such as "minecraft:ghast", - // or a list of entity type ids, such as ["minecraft:ghast", "minecraft:skeleton", ...], - // or an entity type tag, such as "#minecraft:skeletons". - "entity_types": "#minecraft:skeletons" + "type": "neoforge:remove_spawn_costs", + // Can either be a biome id, such as "minecraft:plains", + // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], + // or a biome tag, such as "#c:is_overworld". + "biomes": "#namespace:biome_tag", + // Can either be an entity type id, such as "minecraft:ghast", + // or a list of entity type ids, such as ["minecraft:ghast", "minecraft:skeleton", ...], + // or an entity type tag, such as "#minecraft:skeletons". + "entity_types": "#minecraft:skeletons" } ``` - - + + ```java // Define the ResourceKey for our BiomeModifier. @@ -450,7 +450,7 @@ BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { }); ``` - + @@ -459,27 +459,24 @@ BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { This biome modifier type allows adding carver caves and ravines to biomes. These are what was used for cave generation before the Caves and Cliffs update. It CANNOT add noise caves to biomes, because noise caves are a part of certain noise-based chunk generator systems and not actually tied to biomes. - + ```json5 -{ - "type": "neoforge:add_carvers", - // Can either be a biome id, such as "minecraft:plains", - // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], - // or a biome tag, such as "#c:is_overworld". - "biomes": "minecraft:plains", - // Can either be a carver id, such as "examplemod:add_carvers_example", - // or a list of carver ids, such as ["examplemod:add_carvers_example", "minecraft:canyon", ...], - // or a carver tag, such as "#examplemod:configured_carver_tag". - "carvers": "examplemod:add_carvers_example", - // See GenerationStep.Carving in code for a list of valid enum names. - // Only "air" and "liquid" are available. - "step": "air" + { + "type": "neoforge:add_carvers", + // Can either be a biome id, such as "minecraft:plains", + // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], + // or a biome tag, such as "#c:is_overworld". + "biomes": "minecraft:plains", + // Can either be a carver id, such as "examplemod:add_carvers_example", + // or a list of carver ids, such as ["examplemod:add_carvers_example", "minecraft:canyon", ...], + // or a carver tag, such as "#examplemod:configured_carver_tag". + "carvers": "examplemod:add_carvers_example" } ``` - - + + ```java // Assume we have some ConfiguredWorldCarver named EXAMPLE_CARVER. @@ -503,15 +500,13 @@ BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { // The biome(s) to generate within HolderSet.direct(biomes.getOrThrow(Biomes.PLAINS)), // The carver(s) to generate within the biomes - HolderSet.direct(carvers.getOrThrow(EXAMPLE_CARVER)), - // The generation step - GenerationStep.Carving.AIR + HolderSet.direct(carvers.getOrThrow(EXAMPLE_CARVER)) ) ); }); ``` - + ### Removing Legacy Carvers @@ -519,32 +514,24 @@ BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { This biome modifier type allows removing carver caves and ravines from biomes. These are what was used for cave generation before the Caves and Cliffs update. It CANNOT remove noise caves from biomes, because noise caves are baked into the dimension's noise settings system and not actually tied to biomes. - + ```json5 { - "type": "neoforge:remove_carvers", - // Can either be a biome id, such as "minecraft:plains", - // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], - // or a biome tag, such as "#c:is_overworld". - "biomes": "minecraft:plains", - // Can either be a carver id, such as "examplemod:add_carvers_example", - // or a list of carver ids, such as ["examplemod:add_carvers_example", "minecraft:canyon", ...], - // or a carver tag, such as "#examplemod:configured_carver_tag". - "carvers": "examplemod:add_carvers_example", - // Can either be a single generation step, such as "air", - // or a list of generation steps, such as ["air", "liquid"]. - // See GenerationStep.Carving for a list of valid enum names. - // Only "air" and "liquid" are available. - "steps": [ - "air", - "liquid" - ] + "type": "neoforge:remove_carvers", + // Can either be a biome id, such as "minecraft:plains", + // or a list of biome ids, such as ["minecraft:plains", "minecraft:badlands", ...], + // or a biome tag, such as "#c:is_overworld". + "biomes": "minecraft:plains", + // Can either be a carver id, such as "examplemod:add_carvers_example", + // or a list of carver ids, such as ["examplemod:add_carvers_example", "minecraft:canyon", ...], + // or a carver tag, such as "#examplemod:configured_carver_tag". + "carvers": "examplemod:add_carvers_example" } ``` - - + + ```java // Define the ResourceKey for our BiomeModifier. @@ -567,18 +554,13 @@ BUILDER.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, bootstrap -> { // The biome(s) to remove from biomes.getOrThrow(Tags.Biomes.IS_OVERWORLD), // The carver(s) to remove from the biomes - HolderSet.direct(carvers.getOrThrow(Carvers.CAVE)), - // The generation steps to remove from - Set.of( - GenerationStep.Carving.AIR, - GenerationStep.Carving.LIQUID - ) + HolderSet.direct(carvers.getOrThrow(Carvers.CAVE)) ) ); }); ``` - + ### Available Values for Decoration Steps diff --git a/neogradle/docs/configuration/index.mdx b/neogradle/docs/configuration/index.md similarity index 100% rename from neogradle/docs/configuration/index.mdx rename to neogradle/docs/configuration/index.md diff --git a/neogradle_versions.json b/neogradle_versions.json index 3f6328c12..441cb05ab 100644 --- a/neogradle_versions.json +++ b/neogradle_versions.json @@ -1,4 +1,4 @@ [ - "6.x", - "5.x" + "6.x", + "5.x" ] diff --git a/src/pages/contributing.mdx b/src/pages/contributing.md similarity index 99% rename from src/pages/contributing.mdx rename to src/pages/contributing.md index 04f4c8e07..878cc8814 100644 --- a/src/pages/contributing.mdx +++ b/src/pages/contributing.md @@ -61,8 +61,6 @@ If the pseudocode is not explanatory enough to understand the concept, then a fu If a change occurs between a minor or patch versions of NeoForge, then relevant changes in the documentation should be split into separate sections or put into tabs. This maintains the accuracy of the information depending on the version the modder is currently developing for. -Tabs must be in an `.mdx` file, not an `.md` file, for proper IDE support. - ````md import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; diff --git a/src/pages/index.md b/src/pages/index.md new file mode 100644 index 000000000..362152449 --- /dev/null +++ b/src/pages/index.md @@ -0,0 +1,33 @@ +import Card from "../theme/Card.tsx"; + +# NeoForged Documentation + +This is the official documentation for [NeoForged], the Minecraft modding API. + +This documentation is _only_ for NeoForged, **this is not a Java tutorial**. + +If you would like to contribute to the docs, read [Contributing to the Docs][contributing]. + +
+
+
+ +
+
+ +
+
+
+ +[NeoForged]: https://neoforged.net +[contributing]: ./contributing diff --git a/src/pages/index.mdx b/src/pages/index.mdx deleted file mode 100644 index e69872017..000000000 --- a/src/pages/index.mdx +++ /dev/null @@ -1,33 +0,0 @@ -import Card from "../theme/Card.tsx"; - -# NeoForged Documentation - -This is the official documentation for [NeoForged], the Minecraft modding API. - -This documentation is _only_ for NeoForged, **this is not a Java tutorial**. - -If you would like to contribute to the docs, read [Contributing to the Docs][contributing]. - -
-
-
- -
-
- -
-
-
- -[NeoForged]: https://neoforged.net -[contributing]: ./contributing diff --git a/versioned_docs/version-1.21.1/advanced/_category_.json b/versioned_docs/version-1.21.1/advanced/_category_.json new file mode 100644 index 000000000..2161114e5 --- /dev/null +++ b/versioned_docs/version-1.21.1/advanced/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Advanced Topics", + "position": 12 +} \ No newline at end of file diff --git a/versioned_docs/version-1.21.1/advanced/accesstransformers.md b/versioned_docs/version-1.21.1/advanced/accesstransformers.md new file mode 100644 index 000000000..f7399e76b --- /dev/null +++ b/versioned_docs/version-1.21.1/advanced/accesstransformers.md @@ -0,0 +1,147 @@ +# Access Transformers + +Access Transformers (ATs for short) allow for widening the visibility and modifying the `final` flags of classes, methods, and fields. They allow modders to access and modify otherwise inaccessible members in classes outside their control. + +The [specification document][specs] can be viewed on the NeoForged GitHub. + +## Adding ATs + +Adding an Access Transformer to your mod project is as simple as adding a single line into your `build.gradle`: + +Access Transformers need to be declared in `build.gradle`. AT files can be specified anywhere as long as they are copied to the `resources` output directory on compilation. + +```groovy +// In build.gradle: +// This block is where your mappings version is also specified +minecraft { + accessTransformers { + file('src/main/resources/META-INF/accesstransformer.cfg') + } +} +``` + +By default, NeoForge will search for `META-INF/accesstransformer.cfg`. If the `build.gradle` specifies access transformers in any other location, then their location needs to be defined within `neoforge.mods.toml`: + +```toml +# In neoforge.mods.toml: +[[accessTransformers]] +## The file is relative to the output directory of the resources, or the root path inside the jar when compiled +## The 'resources' directory represents the root output directory of the resources +file="META-INF/accesstransformer.cfg" +``` + +Additionally, multiple AT files can be specified and will be applied in order. This can be useful for larger mods with multiple packages. + +```groovy +// In build.gradle: +minecraft { + accessTransformers { + file('src/main/resources/accesstransformer_main.cfg') + file('src/additions/resources/accesstransformer_additions.cfg') + } +} +``` + +```toml +# In neoforge.mods.toml +[[accessTransformers]] +file="accesstransformer_main.cfg" + +[[accessTransformers]] +file="accesstransformer_additions.cfg" +``` + +After adding or modifying any Access Transformer, the Gradle project must be refreshed for the transformations to take effect. + +## The Access Transformer Specification + +### Comments + +All text after a `#` until the end of the line will be treated as a comment and will not be parsed. + +### Access Modifiers + +Access modifiers specify to what new member visibility the given target will be transformed to. In decreasing order of visibility: + +- `public` - visible to all classes inside and outside its package +- `protected` - visible only to classes inside the package and subclasses +- `default` - visible only to classes inside the package +- `private` - visible only to inside the class + +A special modifier `+f` and `-f` can be appended to the aforementioned modifiers to either add or remove respectively the `final` modifier, which prevents subclassing, method overriding, or field modification when applied. + +:::danger +Directives only modify the method they directly reference; any overriding methods will not be access-transformed. It is advised to ensure transformed methods do not have non-transformed overrides that restrict the visibility, which will result in the JVM throwing an error. + +Examples of methods that can be safely transformed are `private` methods, `final` methods (or methods in `final` classes), and `static` methods. +::: + +### Targets and Directives + +#### Classes + +To target classes: + +``` + +``` + +Inner classes are denoted by combining the fully qualified name of the outer class and the name of the inner class with a `$` as separator. + +#### Fields + +To target fields: + +``` + +``` + +#### Methods + +Targeting methods require a special syntax to denote the method parameters and return type: + +``` + () +``` + +##### Specifying Types + +Also called "descriptors": see the [Java Virtual Machine Specification, SE 21, sections 4.3.2 and 4.3.3][jvmdescriptors] for more technical details. + +- `B` - `byte`, a signed byte +- `C` - `char`, a Unicode character code point in UTF-16 +- `D` - `double`, a double-precision floating-point value +- `F` - `float`, a single-precision floating-point value +- `I` - `integer`, a 32-bit integer +- `J` - `long`, a 64-bit integer +- `S` - `short`, a signed short +- `Z` - `boolean`, a `true` or `false` value +- `[` - references one dimension of an array + - Example: `[[S` refers to `short[][]` +- `L;` - references a reference type + - Example: `Ljava/lang/String;` refers to `java.lang.String` reference type _(note the use of slashes instead of periods)_ +- `(` - references a method descriptor, parameters should be supplied here or nothing if no parameters are present + - Example: `(I)Z` refers to a method that requires an integer argument and returns a boolean +- `V` - indicates a method returns no value, can only be used at the end of a method descriptor + - Example: `()V` refers to a method that has no arguments and returns nothing + +### Examples + +``` +# Makes public the ByteArrayToKeyFunction interface in Crypt +public net.minecraft.util.Crypt$ByteArrayToKeyFunction + +# Makes protected and removes the final modifier from 'random' in MinecraftServer +protected-f net.minecraft.server.MinecraftServer random + +# Makes public the 'makeExecutor' method in Util, +# accepting a String and returns an ExecutorService +public net.minecraft.Util makeExecutor(Ljava/lang/String;)Ljava/util/concurrent/ExecutorService; + +# Makes public the 'leastMostToIntArray' method in UUIDUtil, +# accepting two longs and returning an int[] +public net.minecraft.core.UUIDUtil leastMostToIntArray(JJ)[I +``` + +[specs]: https://github.com/NeoForged/AccessTransformers/blob/main/FMLAT.md +[jvmdescriptors]: https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-4.html#jvms-4.3.2 diff --git a/versioned_docs/version-1.21.1/advanced/extensibleenums.md b/versioned_docs/version-1.21.1/advanced/extensibleenums.md new file mode 100644 index 000000000..2df255c67 --- /dev/null +++ b/versioned_docs/version-1.21.1/advanced/extensibleenums.md @@ -0,0 +1,149 @@ +# Extensible Enums + +Extensible Enums are an enhancement of specific Vanilla enums to allow new entries to be added. This is done by modifying the compiled bytecode of the enum at runtime to add the elements. + +## `IExtensibleEnum` + +All enums that can have new entries implement the `IExtensibleEnum` interface. This interface acts as a marker to allow the `RuntimeEnumExtender` launch plugin service to know what enums should be transformed. + +:::warning +You should **not** be implementing this interface on your own enums. Use maps or registries instead depending on your usecase. +Enums which are not patched to implement the interface cannot have the interface added to them via mixins or coremods due to the order the transformers run in. +::: + +### Creating an Enum Entry + +To create new enum entries, a JSON file needs to be created and referenced in the `neoforge.mods.toml` with the `enumExtensions` entry of a `[[mods]]` block. The specified path must be relative to the `resources` directory: +```toml +# In neoforge.mods.toml: +[[mods]] +## The file is relative to the output directory of the resources, or the root path inside the jar when compiled +## The 'resources' directory represents the root output directory of the resources +enumExtensions="META-INF/enumextensions.json" +``` + +The definition of the entry consists of the target enum's class name, the new field's name (must be prefixed with the mod ID), the descriptor of the constructor to use for constructing the entry and the parameters to be passed to said constructor. + +```json5 +{ + "entries": [ + { + // The enum class the entry should be added to + "enum": "net/minecraft/world/item/ItemDisplayContext", + // The field name of the new entry, must be prefixed with the mod ID + "name": "EXAMPLEMOD_STANDING", + // The constructor to be used + "constructor": "(ILjava/lang/String;Ljava/lang/String;)V", + // Constant parameters provided directly. + "parameters": [ -1, "examplemod:standing", null ] + }, + { + "enum": "net/minecraft/world/item/Rarity", + "name": "EXAMPLEMOD_CUSTOM", + "constructor": "(ILjava/lang/String;Ljava/util/function/UnaryOperator;)V", + // The parameters to be used, provided as a reference to an EnumProxy field in the given class + "parameters": { + "class": "example/examplemod/MyEnumParams", + "field": "CUSTOM_RARITY_ENUM_PROXY" + } + }, + { + "enum": "net/minecraft/world/damagesource/DamageEffects", + "name": "EXAMPLEMOD_TEST", + "constructor": "(Ljava/lang/String;Ljava/util/function/Supplier;)V", + // The parameters to be used, provided as a reference to a method in the given class + "parameters": { + "class": "example/examplemod/MyEnumParams", + "method": "getTestDamageEffectsParameter" + } + } + ] +} +``` + +```java +public class MyEnumParams { + public static final EnumProxy CUSTOM_RARITY_ENUM_PROXY = new EnumProxy<>( + Rarity.class, -1, "examplemod:custom", (UnaryOperator