diff --git a/docs/advanced/extensibleenums.md b/docs/advanced/extensibleenums.md index 2df255c6..d3532304 100644 --- a/docs/advanced/extensibleenums.md +++ b/docs/advanced/extensibleenums.md @@ -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/index.md b/docs/blockentities/index.md index 251c7713..d8c0c2ca 100644 --- a/docs/blockentities/index.md +++ b/docs/blockentities/index.md @@ -42,7 +42,7 @@ public static final Supplier> MY_BLOCK_ENTITY = B ``` :::note -Remember that the `DeferredRegister` must be [registered][registration] to the mod event bus! +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: @@ -237,6 +237,7 @@ It is important that you do safety checks, as the `BlockEntity` might already be [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/index.md b/docs/blocks/index.md index 8cae724a..2d5353bb 100644 --- a/docs/blocks/index.md +++ b/docs/blocks/index.md @@ -169,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. @@ -176,8 +186,6 @@ public static final DeferredBlock EXAMPLE_BLOCK = BLOCKS.registerBlock( ); ``` -Internally, this will simply call `BLOCKS.register("example_block", registryName -> new Block(BlockBehaviour.Properties.of().setId(ResourceKey.create(Registries.BLOCK, registryName))))` by applying the properties parameter to the provided block factory (which is commonly the constructor). The id is set on the properties. - If you want to use `Block::new`, you can leave out the factory entirely: ```java diff --git a/docs/resources/server/enchantments/index.md b/docs/resources/server/enchantments/index.md index 149334d5..c0084d49 100644 --- a/docs/resources/server/enchantments/index.md +++ b/docs/resources/server/enchantments/index.md @@ -134,15 +134,13 @@ 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 Supplier> INCREMENT = - ENCHANTMENT_COMPONENT_TYPES.register( + ENCHANTMENT_COMPONENT_TYPES.registerComponentType( "increment", - () -> DataComponentType.builder() - .persistent(Increment.CODEC) - .build() + builder -> builder.persistent(Increment.CODEC) ); ``` diff --git a/docs/resources/server/recipes/custom.md b/docs/resources/server/recipes/custom.md index aefaf06f..d3a5d392 100644 --- a/docs/resources/server/recipes/custom.md +++ b/docs/resources/server/recipes/custom.md @@ -470,73 +470,130 @@ public record ClientboundRightClickBlockRecipesPayload( // ... } - // Packet stores data in an instance class. // Present on both server and client to do initial matching. -// Resource listener so it can be reloaded when recipes are. -public class RightClickBlockRecipeInputs extends SimplePreparableReloadListener { - // Only one instance - public static final RightClickBlockRecipeInputs INSTANCE = new RightClickBlockRecipeInputs(); +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; - private RightClickBlockRecipeInputs() {} - - @Override - protected Void prepare(ResourceManager manager, ProfilerFiller filler) {} + 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 - protected abstract void apply(Void dummy, ResourceManager manager, ProfilerFiller filler) { + 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<>(); - server.getRecipeManager().recipeMap().byType(RIGHT_CLICK_BLOCK_TYPE.get()) + 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.copyOf(inputStates); - this.inputItems = Set.copyOf(inputItems); + this.inputStates = Set.unmodifiableSet(inputStates); + this.inputItems = Set.unmodifiableSet(inputItems); } } - // Should be called within the handler for the payload - public void setInputs(Set inputStates, Set> inputItems) { - this.inputStates = inputStates; - this.inputItems = inputItems; - } - public void syncToClient(Stream players) { ClientboundRightClickBlockRecipesPayload payload = new ClientboundRightClickBlockRecipesPayload(this.inputStates, this.inputItems); players.forEach(player -> PacketDistributor.sendToPlayer(player, payload)); } - public boolean test(BlockState state, ItemStack stack) { - return this.inputStates.contains(state) && this.inputItems.contains(stack.getItemHolder()); + @Override + public Set inputStates() { + return this.inputStates; + } + + @Override + public Set> inputItems() { + return this.inputItems; } } -// On the game event bus -@Override -public static void addListener(AddReloadListenerEvent event) { - // Register server reload listener - event.addListener(RightClickBlockRecipeInputs.INSTANCE); +// 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); + } } -// On the game event bus -@SubscribeEvent -public static void datapackSync(OnDatapackSyncEvent event) { - // Send to client - RightClickBlockRecipeInputs.INSTANCE.syncToClient(event.getRelevantPlayers()); +// 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() { + return FMLEnvironment.dist == Dist.CLIENT + ? ClientRightClickBlockRecipes.inputs() + : ServerRightClickBlockRecipes.inputs(); + } } ``` @@ -555,7 +612,7 @@ public static void useItemOnBlock(UseItemOnBlockEvent event) { ItemStack itemStack = event.getItemStack(); // Check if the input can result in a recipe on both sides - if (!RightClickBlockRecipeInputs.INSTANCE.test(blockState, itemStack)) return; + if (!RightClickBlockRecipes.inputs().test(blockState, itemStack)) return; // If so, make sure on server before checking recipe if (!level.isClientSide() && level instanceof ServerLevel serverLevel) { diff --git a/docs/resources/server/recipes/index.md b/docs/resources/server/recipes/index.md index add239b3..22d66884 100644 --- a/docs/resources/server/recipes/index.md +++ b/docs/resources/server/recipes/index.md @@ -1,6 +1,6 @@ # Recipes -Recipes are a datapack registry used as a way to transform a set of objects into other objects within a Minecraft world. Although Minecraft uses this system purely for item transformations, the system is built in a way that allows any kind of objects - blocks, entities, etc. - to be transformed. Almost all recipes use recipe data files; a "recipe" is assumed to be a data-driven recipe in this article unless explicitly stated otherwise. +Recipes are a way to transform a set of objects into other objects within a Minecraft world. Although Minecraft uses this system purely for item transformations, the system is built in a way that allows any kind of objects - blocks, entities, etc. - to be transformed. Almost all recipes use recipe data files; a "recipe" is assumed to be a data-driven recipe in this article unless explicitly stated otherwise. Recipe data files are located at `data//recipe/.json`. For example, the recipe `minecraft:diamond_block` is located at `data/minecraft/recipe/diamond_block.json`. @@ -39,7 +39,7 @@ 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#recipeAccess` or - if you don't have a `ServerLevel` available - `ServerLifecycleHooks.getCurrentServer()#getRecipeManager`. The server does not sync the recipes to the client in their entirety, instead it only sends the `RecipePropertySet`s for restricting inputs on menu slots and the `RecipeDisplayEntry`s for the recipe book. All recipe logic should always run on the server. +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 in their entirety instead it only sends the `RecipePropertySet`s for restricting inputs on menu slots and `RecipeDisplay`s via `RecipeDisplayEntry`s for the recipe book (excluding all recipes where `Recipe#isSpecial` returns true). All recipe logic should always run on the server. The easiest way to get a recipe is by its resource key: