From d1e43db58397c77eea1f162404b5b96a3cfe7431 Mon Sep 17 00:00:00 2001 From: IchHabeHunger54 <52419336+IchHabeHunger54@users.noreply.github.com> Date: Wed, 2 Oct 2024 18:59:51 +0200 Subject: [PATCH] Rewrite the Block Entities section (#160) Co-authored-by: Dennis C Co-authored-by: ChampionAsh5357 --- docs/blockentities/ber.md | 125 ++++++-- docs/blockentities/container.md | 312 +++++++++++++++++++ docs/blockentities/index.md | 265 +++++++++++----- docs/items/bewlr.md | 36 --- docs/resources/client/models/bakedmodel.md | 4 +- docs/resources/client/models/index.md | 2 +- docs/resources/client/models/modelloaders.md | 2 +- 7 files changed, 601 insertions(+), 145 deletions(-) create mode 100644 docs/blockentities/container.md delete mode 100644 docs/items/bewlr.md diff --git a/docs/blockentities/ber.md b/docs/blockentities/ber.md index 8d43b4ea..5ef66f2c 100644 --- a/docs/blockentities/ber.md +++ b/docs/blockentities/ber.md @@ -1,44 +1,123 @@ # BlockEntityRenderer -A `BlockEntityRenderer` or `BER` is used to render blocks in a way that cannot be represented with a static baked model (JSON, OBJ, B3D, others). A block entity renderer requires the block to have a `BlockEntity`. +A `BlockEntityRenderer`, often abbreviated as BER, is used to render [blocks][block] in a way that cannot be represented with a [static baked model][model] (JSON, OBJ, others). For example, this could be used to dynamically render container contents of a chest-like block. A block entity renderer requires the block to have a [`BlockEntity`][blockentity], even if the block does not store any data otherwise. -## Creating a BER +To create a BER, create a class that inherits from `BlockEntityRenderer`. It takes a generic argument specifying the block's `BlockEntity` class, which is used as a parameter type in the BER's `render` method. -To create a BER, create a class that inherits from `BlockEntityRenderer`. It takes a generic argument specifying the block's `BlockEntity` class. The generic argument is used in the BER's `render` method. +```java +// Assumes the existence of MyBlockEntity as a subclass of BlockEntity. +public class MyBlockEntityRenderer implements BlockEntityRenderer { + // Add the constructor parameter for the lambda below. You may also use it to get some context + // to be stored in local fields, such as the entity renderer dispatcher, if needed. + public MyBlockEntityRenderer(BlockEntityRendererProvider.Context context) { + } + + // This method is called every frame in order to render the block entity. Parameters are: + // - blockEntity: The block entity instance being rendered. Uses the generic type passed to the super interface. + // - partialTick: The amount of time, in fractions of a tick (0.0 to 1.0), that has passed since the last tick. + // - poseStack: The pose stack to render to. + // - bufferSource: The buffer source to get vertex buffers from. + // - packedLight: The light value of the block entity. + // - packedOverlay: The current overlay value of the block entity, usually OverlayTexture.NO_OVERLAY. + @Override + public void render(MyBlockEntity blockEntity, float partialTick, PoseStack stack, MultiBufferSource bufferSource, int packedLight, int packedOverlay) { + // Do the rendering here. + } +} +``` -Only one BER exists for a given `BlockEntityType`. Therefore, values that are specific to a single instance in the level should be stored in the block entity being passed to the renderer rather than in the BER itself. For example, an integer that increments every frame, if stored in the BER, will increment every frame for every block entity of this type in the level. +Only one BER may exist for a given `BlockEntityType`. Therefore, values that are specific to a single block entity instance should be stored in that block entity instance, rather than the BER itself. -### `render` +When you have created your BER, you must also register it to `EntityRenderersEvent.RegisterRenderers`, an [event] fired on the [mod event bus][eventbus]: -This method is called every frame in order to render the block entity. +```java +@SubscribeEvent +public static void registerEntityRenderers(EntityRenderersEvent.RegisterRenderers event) { + event.registerBlockEntityRenderer( + // The block entity type to register the renderer for. + MyBlockEntities.MY_BLOCK_ENTITY.get(), + // A function of BlockEntityRendererProvider.Context to BlockEntityRenderer. + MyBlockEntityRenderer::new + ); +} +``` -#### Parameters -- `blockEntity`: This is the instance of the block entity being rendered. -- `partialTick`: The amount of time, in fractions of a tick, that has passed since the last full tick. -- `poseStack`: A stack holding four-dimensional matrix entries offset to the current position of the block entity. -- `bufferSource`: A rendering buffer able to access a vertex consumer. -- `combinedLight`: An integer of the current light value on the block entity. -- `combinedOverlay`: An integer set to the current overlay of the block entity, usually `OverlayTexture#NO_OVERLAY` or 655,360. +In the event that you do not need the BER provider context in your BER, you can also remove the constructor: -## Registering a BER +```java +public class MyBlockEntityRenderer implements BlockEntityRenderer { + @Override + public void render( /* ... */ ) { /* ... */ } +} -In order to register a BER, you must subscribe to the `EntityRenderersEvent.RegisterRenderers` [event on the mod event bus][event] and call `#registerBlockEntityRenderer`. +// In your event handler class +@SubscribeEvent +public static void registerEntityRenderers(EntityRenderersEvent.RegisterRenderers event) { + event.registerBlockEntityRenderer(MyBlockEntities.MY_BLOCK_ENTITY.get(), + // Pass the context to an empty (default) constructor call + context -> new MyBlockEntityRenderer() + ); +} +``` -```java -public class MyBlockEntityRenderer implements BlockEntityRenderer { +## `BlockEntityWithoutLevelRenderer` - public MyBlockEntityRenderer(BlockEntityRendererProvider.Context ctx) { - // Do things here +`BlockEntityWithoutLevelRenderer`, colloquially known as BEWLR, is an adaptation of the regular `BlockEntityRenderer` for special [item] rendering (hence "without level", as items do not have level context). Its overall purpose is the same: do special rendering for cases where static models aren't enough. + +To add a BEWLR, create a class that extends `BlockEntityWithoutLevelRenderer` and overrides `#renderByItem`. It also requires some additional constructor setup: + +```java +public class MyBlockEntityWithoutLevelRenderer extends BlockEntityWithoutLevelRenderer { + // We need some boilerplate in the constructor, telling the superclass where to find the central block entity and entity renderers. + public MyBlockEntityWithoutLevelRenderer() { + super(Minecraft.getInstance().getBlockEntityRenderDispatcher(), Minecraft.getInstance().getEntityModels()); + } + + @Override + public void renderByItem(ItemStack stack, ItemDisplayContext transform, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay) { + // Do the rendering here. } +} +``` + +Keep in mind that, like with BERs, there is only one instance of your BEWLR. Stack-specific properties should therefore be stored in the stack, not the BEWLR. + +Unlike BERs, we do not register BEWLRs directly. Instead, we register an instance of `IClientItemExtensions` to the `RegisterClientExtensionsEvent`. `IClientItemExtensions` is an interface that allows us to specify a number of rendering-related behaviors on items, such as (but not limited to) a BEWLR. As such, our implementation of that interface could look like so: - // Implement #render method here and any other logic +```java +public class MyClientItemExtensions implements IClientItemExtensions { + // Cache our BEWLR in a field. + private final MyBlockEntityWithoutLevelRenderer myBEWLR = new MyBlockEntityWithoutLevelRenderer(); + + // Return our BEWLR here. + @Override + public BlockEntityWithoutLevelRenderer getCustomRenderer() { + return myBEWLR; + } } +``` -// In another class using some method to listen to this event +And then, we can register our `IClientItemExtensions` to the event: + +```java @SubscribeEvent -public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) { - event.registerBlockEntityRenderer(MyBlockEntityTypes.MYBE.get(), MyBlockEntityRenderer::new); +public static void registerClientExtensions(RegisterClientExtensionsEvent event) { + event.registerItem( + // The only instance of our IClientItemExtensions, and as such, the only instance of our BEWLR. + new MyClientItemExtensions(), + // A vararg list of items that use this BEWLR. + MyItems.ITEM_1, MyItems.ITEM_2 + ); } ``` +:::info +`IClientItemExtensions` are generally expected to be treated as singletons. Do not construct them outside `RegisterClientExtensionsEvent`! +::: + +[block]: ../blocks/index.md +[blockentity]: index.md [event]: ../concepts/events.md#registering-an-event-handler +[eventbus]: ../concepts/events.md#event-buses +[item]: ../items/index.md +[model]: ../resources/client/models/index.md diff --git a/docs/blockentities/container.md b/docs/blockentities/container.md new file mode 100644 index 00000000..014119d8 --- /dev/null +++ b/docs/blockentities/container.md @@ -0,0 +1,312 @@ +# Containers + +A popular use case of [block entities][blockentity] is to store items of some kind. Some of the most essential [blocks][block] in Minecraft, such as the furnace or the chest, use block entities for this purpose. To store items on something, Minecraft uses `Container`s. + +The `Container` interface defines methods such as `#getItem`, `#setItem` and `#removeItem` that can be used to query and update the container. Since it is an interface, it does not actually contain a backing list or other data structure, that is up to the implementing system. + +Due to this, `Container`s can not only be implemented on block entities, but any other class as well. Notable examples include entity inventories, as well as common modded [items][item] such as backpacks. + +:::warning +NeoForge provides the `ItemStackHandler` class as a replacement for `Container`s in many places. It should be used wherever possible in favor of `Container`, as it allows for cleaner interaction with other `Container`s/`ItemStackHandler`s. + +The main reason this article exists is for reference in vanilla code, or if you are developing mods on multiple loaders. Always use `ItemStackHandler` in your own code if possible! Docs on that are a work in progress. +::: + +## Basic Container Implementation + +Containers can be implemented in any way you like, so long as you satisfy the dictated methods (as with any other interface in Java). However, it is common to use a `NonNullList` with a fixed length as a backing structure. Single-slot containers may also simply use an `ItemStack` field instead. + +For example, a basic implementation of `Container` with a size of 27 slots (one chest) could look like this: + +```java +public class MyContainer implements Container { + private final NonNullList items = NonNullList.withSize( + // The size of the list, i.e. the amount of slots in our container. + 27, + // The default value to be used in place of where you'd use null in normal lists. + ItemStack.EMPTY + ); + + // The amount of slots in our container. + @Override + public int getContainerSize() { + return 27; + } + + // Whether the container is considered empty. + @Override + public boolean isEmpty() { + return this.items.stream().allMatch(ItemStack::isEmpty); + } + + // Return the item stack in the specified slot. + @Override + public ItemStack getItem(int slot) { + return this.items.get(slot); + } + + // Call this when changes are done to the container, i.e. when item stacks are added, modified, or removed. + // For example, you could call BlockEntity#setChanged here. + @Override + public void setChanged() { + + } + + // Remove the specified amount of items from the given slot, returning the stack that was just removed. + // We defer to ContainerHelper here, which does this as expected for us. + // However, we must call #setChanged manually. + @Override + public ItemStack removeItem(int slot, int amount) { + ItemStack stack = ContainerHelper.removeItem(this.items, slot, amount); + this.setChanged(); + return stack; + } + + // Remove all items from the specified slot, returning the stack that was just removed. + // We again defer to ContainerHelper here, and we again have to call #setChanged manually. + @Override + public ItemStack removeItemNoUpdate(int slot) { + ItemStack stack = ContainerHelper.takeItem(this.items, slot); + this.setChanged(); + return stack; + } + + // Set the given item stack in the given slot. Limit to the max stack size of the container first. + @Override + public void setItem(int slot, ItemStack stack) { + stack.limitSize(this.getMaxStackSize(stack)); + this.items.set(slot, stack); + this.setChanged(); + } + + // Whether the container is considered "still valid" for the given player. For example, chests and + // similar blocks check if the player is still within a given distance of the block here. + @Override + public boolean stillValid(Player player) { + return true; + } + + // Clear the internal storage, setting all slots to empty again. + @Override + public void clearContent() { + items.clear(); + this.setChanged(); + } +} +``` + +### `SimpleContainer` + +The `SimpleContainer` class is a basic implementation of a container with some sprinkles on top, such as the ability to add `ContainerListener`s. It can be used if you need a container implementation that doesn't have any special requirements. + +### `BaseContainerBlockEntity` + +The `BaseContainerBlockEntity` class is the base class of many important block entities in Minecraft, such as chests and chest-like blocks, the various furnace types, hoppers, dispensers, droppers, brewing stands and a few others. + +Aside from `Container`, it also implements the `MenuProvider` and `Nameable` interfaces: + +- `Nameable` defines a few methods related to setting (custom) names and, aside from many block entities, is implemented by classes such as `Entity`. This uses the [`Component` system][component]. +- `MenuProvider`, on the other hand, defines the `#createMenu` method, which allows an [`AbstractContainerMenu`][menu] to be constructed from the container. This means that using this class is not desirable if you want a container without an associated GUI, for example in jukeboxes. + +`BaseContainerBlockEntity` bundles all calls we would normally make to our `NonNullList` through two methods `#getItems` and `#setItems`, drastically reducing the amount of boilerplate we need to write. An example implementation of a `BaseContainerBlockEntity` could look like this: + +```java +public class MyBlockEntity extends BaseContainerBlockEntity { + // The container size. This can of course be any value you want. + public static final int SIZE = 9; + // Our item stack list. This is not final due to #setItems existing. + private NonNullList items = NonNullList.withSize(SIZE, ItemStack.EMPTY); + + // The constructor, like before. + public MyBlockEntity(BlockPos pos, BlockState blockState) { + super(MY_BLOCK_ENTITY.get(), pos, blockState); + } + + // The container size, like before. + @Override + public int getContainerSize() { + return SIZE; + } + + // The getter for our item stack list. + @Override + protected NonNullList getItems() { + return items; + } + + // The setter for our item stack list. + @Override + protected void setItems(NonNullList items) { + this.items = items; + } + + // The display name of the menu. Don't forget to add a translation! + @Override + protected Component getDefaultName() { + return Component.translatable("container.examplemod.myblockentity"); + } + + // The menu to create from this container. See below for what to return here. + @Override + protected AbstractContainerMenu createMenu(int containerId, Inventory inventory) { + return null; + } +} +``` + +Keep in mind that this class is a `BlockEntity` and a `Container` at the same time. This means that you can use the class as a supertype for your block entity to get a functioning block entity with a pre-implemented container. + +### `WorldlyContainer` + +`WorldlyContainer` is a sub-interface of `Container` that allows accessing slots of the given `Container` by `Direction`. It is mainly intended for block entities that only expose parts of their container to a particular side. For example, this could be used by a machine that outputs to one side and takes inputs from all other sides, or vice-versa. A simple implementation of the interface could look like this: + +```java +// See BaseContainerBlockEntity methods above. You can of course extend BlockEntity directly +// and implement Container yourself if needed. +public class MyBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer { + // other stuff here + + // Assume that slot 0 is our output and slots 1-8 are our inputs. + // Further assume that we output to the top and take inputs from all other sides. + private static final int[] OUTPUTS = new int[]{0}; + private static final int[] INPUTS = new int[]{1, 2, 3, 4, 5, 6, 7, 8}; + + // Return an array of exposed slot indices based on the passed Direction. + @Override + public int[] getSlotsForFace(Direction side) { + return side == Direction.UP ? OUTPUTS : INPUTS; + } + + // Whether items can be placed through the given side at the given slot. + // For our example, we return true only if we're not inputing from above and are in the index range [1, 8]. + @Override + public boolean canPlaceItemThroughFace(int index, ItemStack itemStack, @Nullable Direction direction) { + return direction != Direction.UP && index > 0 && index < 9; + } + + // Whether items can be taken from the given side and the given slot. + // For our example, we return true only if we're pulling from above and from slot index 0. + @Override + public boolean canTakeItemThroughFace(int index, ItemStack stack, Direction direction) { + return direction == Direction.UP && index == 0; + } +} +``` + +## Using Containers + +Now that we have created containers, let's use them! + +Since there is a considerable overlap between `Container`s and `BlockEntity`s, containers are best retrieved by casting the block entity to `Container` if possible: + +```java +if (blockEntity instanceof Container container) { + // do something with the container +} +``` + +The container can then use the methods we mentioned before, for example: + +```java +// Get the first item in the container. +ItemStack stack = container.getItem(0); + +// Set the first item in the container to dirt. +container.setItem(0, new ItemStack(Items.DIRT)); + +// Removes a quantity of (up to) 16 from the third slot. +container.removeItem(2, 16); +``` + +:::warning +A container may throw an exception if trying to access a slot that is beyond its container size. Alternatively, they may return `ItemStack.EMPTY`, as is the case with (for example) `SimpleContainer`. +::: + +## `Container`s on `ItemStack`s + +Until now, we mainly discussed `Container`s on `BlockEntity`s. However, they can also be applied to [`ItemStack`s][itemstack] using the `minecraft:container` [data component][datacomponent]: + +```java +// We use SimpleContainer as the superclass here so we don't have to reimplement the item handling logic ourselves. +// Due to implementation details of SimpleContainer, this may lead to race conditions if multiple parties +// can access the container at the same time, so we're just going to assume our mod doesn't allow that. +// You may of course use a different implementation of Container (or implement Container yourself) if needed. +public class MyBackpackContainer extends SimpleContainer { + // The item stack this container is for. Passed into and set in the constructor. + private final ItemStack stack; + + public MyBackpackContainer(ItemStack stack) { + // We call super with our desired container size. + super(27); + // Setting the stack field. + this.stack = stack; + // We load the container contents from the data component (if present), which is represented + // by the ItemContainerContents class. If absent, we use ItemContainerContents.EMPTY. + ItemContainerContents contents = stack.getOrDefault(DataComponents.CONTAINER, ItemContainerContents.EMPTY); + // Copy the data component contents into our item stack list. + contents.copyInto(this.getItems()); + } + + // When the contents are changed, we save the data component on the stack. + @Override + public void setChanged() { + super.setChanged(); + this.stack.set(DataComponents.CONTAINER, ItemContainerContents.fromItems(this.getItems())); + } +} +``` + +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. +::: + +## `Container`s on `Entity`s + +`Container`s on `Entity`s are finicky: whether an entity has a container or not cannot be universally determined. It all depends on what entity you are handling, and as such can require a lot of special-casing. + +If you are creating an entity yourself, there is nothing stopping you from implementing `Container` on it directly, though be aware that you will not be able to use superclasses such as `SimpleContainer` (since `Entity` is the superclass). + +### `Container`s on `Mob`s + +`Mob`s do not implement `Container`, but they implement the `EquipmentUser` interface (among others). This interface defines the methods `#setItemSlot(EquipmentSlot, ItemStack)`, `#getItemBySlot(EquipmentSlot)` and `#setDropChance(EquipmentSlot, float)`. While not related to `Container` code-wise, the functionality is quite similar: we associate slots, in this case equipment slots, with `ItemStack`s. + +The most notable difference to `Container` is that there is no list-like order (though `Mob` uses `NonNullList`s in the background). Access does not work through slot indices, but rather through the seven `EquipmentSlot` enum values: `MAINHAND`, `OFFHAND`, `FEET`, `LEGS`, `CHEST`, `HEAD`, and `BODY` (where `BODY` is used for horse and dog armor). + +An example of interaction with the mob's "slots" would look something like this: + +```java +// Get the item stack in the HEAD (helmet) slot. +ItemStack helmet = mob.getItemBySlot(EquipmentSlot.HEAD); + +// Put bedrock into the mob's FEET (boots) slot. +mob.setItemSlot(EquipmentSlot.FEET, new ItemStack(Items.BEDROCK)); + +// Enable that bedrock to always drop if the mob is killed. +mob.setDropChance(EquipmentSlot.FEET, 1f); +``` + +### `InventoryCarrier` + +`InventoryCarrier` is an interface implemented by some living entities, such as villagers. It declares a method `#getInventory`, which returns a `SimpleContainer`. This interface is used by non-player entities that need an actual inventory instead of just the equipment slots provided by `EquipmentUser`. + +### `Container`s on `Player`s (Player Inventory) + +The player's inventory is implemented through the `Inventory` class, a class implementing `Container` as well as the `Nameable` interface mentioned earlier. An instance of that `Inventory` is then stored as a field named `inventory` on the `Player`, accessible via `Player#getInventory`. The inventory can be interacted with like any other container. + +The inventory contents are stored in three `public final NonNullList`s: + +- The `items` list covers the 36 main inventory slots, including the nine hotbar slots (indices 0-8). +- The `armor` list is a list of length 4, containing armor for the `FEET`, `LEGS`, `CHEST`, and `HEAD`, in that order. This list uses `EquipmentSlot` accessors, similar to `Mob`s (see above). +- The `offhand` list contains only the offhand slot, i.e. has a length of 1. + +When iterating over the inventory contents, it is recommended to iterate over `items`, then over `armor` and then over `offhand`, to be consistent with vanilla behavior. + +[block]: ../blocks/index.md +[blockentity]: index.md +[component]: ../resources/client/i18n.md#components +[datacomponent]: ../items/datacomponents.mdx +[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 864e495a..89573ec6 100644 --- a/docs/blockentities/index.md +++ b/docs/blockentities/index.md @@ -1,141 +1,240 @@ # Block Entities -`BlockEntities` are like simplified `Entities` that are bound to a Block. They are used to store dynamic data, execute tick based tasks, and dynamic rendering. Some examples from vanilla Minecraft would be handling of inventories on chests, smelting logic on furnaces, or area effects on beacons. More advanced examples exist in mods, such as quarries, sorting machines, pipes, and displays. +Block entities allow the storage of data on [blocks][block] in cases where [block states][blockstate] are not suitable. This is especially the case for data with a non-finite amount of options, such as inventories. Block entities are stationary and bound to a block, but otherwise share many similarities with entities, hence the name. :::note -`BlockEntities` aren't a solution for everything and they can cause lag when used incorrectly. When possible, try to avoid them. +If you have a finite and reasonably small amount (= a few hundred at most) of possible states for your block, you might want to consider using [block states][blockstate] instead. ::: -## Registering +## Creating and Registering Block Entities -Block Entities are created and removed dynamically and as such are not registry objects on their own. +Like entities and unlike blocks, the `BlockEntity` class represents the block entity instance, not the [registered][registration] singleton object. The singleton is expressed through the `BlockEntityType` class instead. We will need both to create a new block entity. -In order to create a `BlockEntity`, you need to extend the `BlockEntity` class. As such, another object is registered instead to easily create and refer to the *type* of the dynamic object. For a `BlockEntity`, these are known as `BlockEntityType`s. - -A `BlockEntityType` can be [registered][registration] like any other registry object. To construct a `BlockEntityType`, its builder form can be used via `BlockEntityType$Builder#of`. This takes in two arguments: a `BlockEntityType.BlockEntitySupplier` which takes in a `BlockPos` and `BlockState` to create a new instance of the associated `BlockEntity`, and a varargs of `Block`s which this `BlockEntity` can be attached to. Building the `BlockEntityType` is done by calling `BlockEntityType$Builder#build`. This takes in a `Type` which represents the type-safe reference used to refer to this registry object in a `DataFixer`. Since `DataFixer`s are an optional system to use for mods, this can be passed as `null`. +Let's begin by creating our block entity class: ```java -// For some DeferredRegister> REGISTER -public static final Supplier> MY_BE = REGISTER.register("mybe", () -> BlockEntityType.Builder.of(MyBE::new, validBlocks).build(null)); - -// In MyBE, a BlockEntity subclass -public MyBE(BlockPos pos, BlockState state) { - super(MY_BE.get(), pos, state); +public class MyBlockEntity extends BlockEntity { + public MyBlockEntity(BlockPos pos, BlockState state) { + super(type, pos, state); + } } ``` -## Creating a `BlockEntity` - -To create a `BlockEntity` and attach it to a `Block`, the `EntityBlock` interface must be implemented on your `Block` subclass. The method `EntityBlock#newBlockEntity(BlockPos, BlockState)` must be implemented and return a new instance of your `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. -## Storing Data within your `BlockEntity` +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: -In order to save data, override the following two methods: ```java -BlockEntity#saveAdditional(CompoundTag tag, HolderLookup.Provider registries) - -BlockEntity#loadAdditional(CompoundTag tag, HolderLookup.Provider registries) +public static final DeferredRegister> BLOCK_ENTITY_TYPES = + DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, ExampleMod.MOD_ID); + +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 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, MyBlocks.MY_BLOCK_2 + ) + // Build using null; vanilla does some datafixer shenanigans with the parameter that we don't need. + .build(null) +); ``` -These methods are called whenever the `LevelChunk` containing the `BlockEntity` gets loaded from/saved to a tag. -Use them to read and write to the fields in your block entity class. -:::note -Whenever your data changes, you need to call `BlockEntity#setChanged`; otherwise, the `LevelChunk` containing your `BlockEntity` might be skipped while the level is saved. -::: +Now that we have our block entity type, we can use it in place of the `type` variable we left earlier: -:::danger -It is important that you call the `super` methods! +```java +public class MyBlockEntity extends BlockEntity { + public MyBlockEntity(BlockPos pos, BlockState state) { + super(MY_BLOCK_ENTITY.get(), pos, state); + } +} +``` -The tag names `id`, `x`, `y`, `z`, `NeoForgeData` and `neoforge:attachments` are reserved by the `super` methods. +:::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. ::: -## Ticking `BlockEntities` - -If you need a ticking `BlockEntity`, for example to keep track of the progress during a smelting process, another method must be implemented and overridden within `EntityBlock`: `EntityBlock#getTicker(Level, BlockState, BlockEntityType)`. This can implement different tickers depending on which logical side the user is on, or just implement one general ticker. In either case, a `BlockEntityTicker` must be returned. Since this is a functional interface, it can just take in a method representing the ticker instead: +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: ```java -// Inside some Block subclass -@Nullable -@Override -public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { - return type == MyBlockEntityTypes.MYBE.get() ? MyBlockEntity::tick : null; +// The important part is implementing the EntityBlock interface and overriding the #newBlockEntity method. +public class MyEntityBlock extends Block implements EntityBlock { + // Constructor deferring to super. + public MyEntityBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + // Return a new instance of our block entity here. + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return new MyBlockEntity(pos, state); + } } +``` -// Inside MyBlockEntity -public static void tick(Level level, BlockPos pos, BlockState state, MyBlockEntity blockEntity) { - // Do stuff -} +And then, you of course need to use this class as the type in your block registration: + +```java +public static final DeferredBlock MY_BLOCK_1 = + BLOCKS.register("my_block_1", () -> new MyEntityBlock( /* ... */ )); +public static final DeferredBlock MY_BLOCK_2 = + BLOCKS.register("my_block_2", () -> new MyEntityBlock( /* ... */ )); ``` -:::note -This method is called each tick; therefore, you should avoid having complicated calculations in here. If possible, you should make more complex calculations every X ticks. (The amount of ticks in a second may be lower then 20 (twenty) but won't be higher) -::: +## Storing Data -## Synchronizing the Data to the Client +One of the main purposes of `BlockEntity`s is to store data. Data storage on block entities can happen in two ways: directly reading and writing [NBT][nbt], or using [data attachments][dataattachments]. This section will cover reading and writing NBT directly; for data attachments, please refer to the linked article. -There are three ways of syncing data to the client: synchronizing on chunk load, on block updates, and with a custom network message. +:::info +The main purpose of data attachments is, as the name suggests, attaching data to existing block entities, such as those provided by vanilla or other mods. For your own mod's block entities, saving and loading directly to and from NBT is preferred. +::: -### Synchronizing on LevelChunk Load +Data can be read from and written to a `CompoundTag` using the `#loadAdditional` and `#saveAdditional` methods, respectively. These methods are called when the block entity is synced to disk or over the network. -For this you need to override ```java -BlockEntity#getUpdateTag(HolderLookup.Provider registries) - -IBlockEntityExtension#handleUpdateTag(CompoundTag tag, HolderLookup.Provider registries) +public class MyBlockEntity extends BlockEntity { + // This can be any value of any type you want, so long as you can somehow serialize it to NBT. + // We will use an int for the sake of example. + private int value; + + public MyBlockEntity(BlockPos pos, BlockState state) { + super(MY_BLOCK_ENTITY.get(), pos, state); + } + + // Read values from the passed CompoundTag here. + @Override + public void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) { + super.loadAdditional(tag, registries); + // Will default to 0 if absent. See the NBT article for more information. + this.value = tag.getInt("value"); + } + + // Save values into the passed CompoundTag here. + @Override + public void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) { + super.saveAdditional(tag, registries); + tag.putInt("value", this.value); + } +} ``` -The first method collects the data that should be sent to the client while the second one processes that data. If your `BlockEntity` doesn't contain much data, you might be able to use the methods out of the [Storing Data within your `BlockEntity`][storing-data] section. +In both methods, it is important that you call super, as that adds basic information such as the position. The tag names `id`, `x`, `y`, `z`, `NeoForgeData` and `neoforge:attachments` are reserved by the super methods, and as such, you should not use them yourself. -:::caution -Synchronizing excessive/useless data for block entities can lead to network congestion. You should optimize your network usage by sending only the information the client needs when the client needs it. For instance, it is more often than not unnecessary to send the inventory of a block entity in the update tag, as this can be synchronized via its [`AbstractContainerMenu`][menu]. -::: +Of course, you will want to set other values and not just work with defaults. You can do so freely, like with any other field. However, if you want the game to save those changes, you must call `#setChanged()` afterward, which marks the block entity's chunk as dirty (= in need of being saved). If you do not call that method, the block entity might get skipped during saving, as Minecraft's saving system only saves chunks that have been marked as dirty. -### Synchronizing on Block Update +## Tickers -This method is a bit more complicated, but again you just need to override two or three methods. Here is a tiny example implementation of it: +Another very common use of block entities, often in combination with some stored data, is ticking. Ticking means executing some code every game tick. This is done by overriding `EntityBlock#getTicker` and returning a `BlockEntityTicker`, which is basically a consumer with four arguments (level, position, blockstate and block entity), like so: ```java -// In some subclass of BlockEntity -@Override -public CompoundTag getUpdateTag(HolderLookup.Provider registries) { - CompoundTag tag = new CompoundTag(); - //Write your data into the tag - return tag; +// Note: The ticker is defined in the block, not the block entity. However, it is good practice to +// keep the ticking logic in the block entity in some way, for example by defining a static #tick method. +public class MyEntityBlock extends Block implements EntityBlock { + // other stuff here + + @SuppressWarnings("unchecked") // Due to generics, an unchecked cast is necessary here. + @Override + public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { + // You can return different tickers here, depending on whatever factors you want. A common use case would be + // to return different tickers on the client or server, only tick one side to begin with, + // or only return a ticker for some blockstates (e.g. when using a "my machine is working" blockstate property). + return type == MY_BLOCK_ENTITY.get() ? (BlockEntityTicker) MyBlockEntity::tick : null; + } } -@Override -public Packet getUpdatePacket() { - // Will get tag from #getUpdateTag - return ClientboundBlockEntityDataPacket.create(this); +public class MyBlockEntity extends BlockEntity { + // other stuff here + + // The signature of this method matches the signature of the BlockEntityTicker functional interface. + public static void tick(Level level, BlockPos pos, BlockState state, MyBlockEntity blockEntity) { + // Whatever you want to do during ticking. + // For example, you could change a crafting progress value or consume power here. + } } +``` + +Be aware that the `#tick` method is actually called every tick. Due to this, you should avoid doing a lot of complex calculations in here if you can, for example by only calculating things every X ticks, or by caching the results. + +## Syncing -// Can override IBlockEntityExtension#onDataPacket. By default, this will defer to BlockEntity#loadWithComponents. +Block entity logic is usually run on the server. As such, we need to tell the client what we are doing. There are three ways to do just that: on chunk load, on block update, or by using a custom packet. You should generally only sync information when it is necessary, to not needlessly clog up the network. + +### Syncing on Chunk Load + +A chunk is loaded (and by extension, this method is utilized) each time it is read from either network or disk. To send your data here, you need to override the following methods: + +```java +public class MyBlockEntity extends BlockEntity { + // ... + + // Create an update tag here. For block entities with only a few fields, this can just call #saveAdditional. + @Override + public CompoundTag getUpdateTag(HolderLookup.Provider registries) { + CompoundTag tag = new CompoundTag(); + saveAdditional(tag, registries); + return tag; + } + + // Handle a received update tag here. The default implementation calls #loadAdditional here, + // so you do not need to override this method if you don't plan to do anything beyond that. + @Override + public void handleUpdateTag(CompoundTag tag, HolderLookup.Provider registries) { + super.handleUpdateTag(tag, registries); + } +} ``` -The static constructors `ClientboundBlockEntityDataPacket#create` takes: -- The `BlockEntity`. -- An optional function to get the `CompoundTag` from the `BlockEntity` and a `RegistryAccess`. By default, this uses `BlockEntity#getUpdateTag`. +### Syncing on Block Update -Now, to send the packet, an update notification must be given on the server. +This method is used whenever a block update occurs. Block updates must be triggered manually, but are generally processed faster than chunk syncing. ```java -Level#sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) +public class MyBlockEntity extends BlockEntity { + // ... + + // Create an update tag here, like above. + @Override + public CompoundTag getUpdateTag(HolderLookup.Provider registries) { + CompoundTag tag = new CompoundTag(); + saveAdditional(tag, registries); + return tag; + } + + // Return our packet here. This method returning a non-null result tells the game to use this packet for syncing. + @Override + public Packet getUpdatePacket() { + // The packet uses the CompoundTag returned by #getUpdateTag. An alternative overload of #create exists + // that allows you to specify a custom update tag, including the ability to omit data the client might not need. + return ClientboundBlockEntityDataPacket.create(this); + } + + // Optionally: Run some custom logic when the packet is received. + // The super/default implementation forwards to #loadAdditional. + @Override + public void onDataPacket(Connection connection, ClientboundBlockEntityDataPacket packet, HolderLookup.Provider registries) { + super.onDataPacket(connection, packet, registries); + // Do whatever you need to do here. + } +} ``` -- The `pos` should be your `BlockEntity`'s position. -- For `oldState` and `newState`, you can pass the current `BlockState` at that position. -- `flags` is a bitmask that should contain `2`, which will sync the changes to the client. See `Block` for more info as well as the rest of the flags. The flag `2` is equivalent to `Block#UPDATE_CLIENTS`. +To actually send the packet, an update notification must be triggered on the server by calling `Level#sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags)`. The position should be the block entity's position, obtainable via `BlockEntity#getBlockPos`. Both blockstate parameters can be the blockstate at the block entity's position, obtainable via `BlockEntity#getBlockState`. Finally, the `flags` parameter is an update mask, as used in [`Level#setBlock`][setblock]. -### Synchronizing Using a Custom Network Message +### Using a Custom Packet -This way of synchronizing is probably the most complicated but is usually the most optimized, as you can make sure that only the data you need to be synchronized is actually synchronized. You should first check out the [`Networking`][networking] section and especially [`PayloadRegistrar`][payload] before attempting this. Once you've created your custom network message, you can send it to all users that have the `BlockEntity` loaded with `PacketDistrubtor#sendToPlayersTrackingChunk`. +By using a dedicated update packet, you can send packets yourself whenever you need to. This is the most versatile, but also the most complex variant, as it requires setting up a network handler. You can send a packet to all players tracking the block entity by using `PacketDistrubtor#sendToPlayersTrackingChunk`. Please see the [Networking][networking] section for more information. :::caution -It is important that you do safety checks, the `BlockEntity` might already be destroyed/replaced when the message arrives at the player! You should also check if the chunk is loaded (`Level#hasChunkAt(BlockPos)`). +It is important that you do safety checks, as the `BlockEntity` might already be destroyed/replaced when the message arrives at the player. You should also check if the chunk is loaded via `Level#hasChunkAt`. ::: -[registration]: ../concepts/registries.md#methods-for-registering -[storing-data]: #storing-data-within-your-blockentity -[menu]: ../gui/menus.md +[block]: ../blocks/index.md +[blockstate]: ../blocks/states.md +[dataattachments]: ../datastorage/attachments.md +[nbt]: ../datastorage/nbt.md [networking]: ../networking/index.md -[payload]: ../networking/payload.md +[registration]: ../concepts/registries.md#methods-for-registering +[setblock]: ../blocks/states.md#levelsetblock diff --git a/docs/items/bewlr.md b/docs/items/bewlr.md deleted file mode 100644 index c2709c28..00000000 --- a/docs/items/bewlr.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -sidebar_position: 5 ---- -# BlockEntityWithoutLevelRenderer - -`BlockEntityWithoutLevelRenderer` is a method to handle dynamic rendering on items. - -`BlockEntityWithoutLevelRenderer` allows you to render your item using `public void renderByItem(ItemStack itemStack, ItemDisplayContext ctx, PoseStack poseStack, MultiBufferSource bufferSource, int combinedLight, int combinedOverlay)`. - -In order to use an BEWLR, the `Item` must first satisfy the condition that its model returns true for `BakedModel#isCustomRenderer`. If it does not have one, it will use the default `ItemRenderer#getBlockEntityRenderer`. Once that returns true, the Item's BEWLR will be accessed for rendering. - -:::note -`Block`s also render using a BEWLR if `BlockBehaviour#getRenderShape` is set to `RenderShape#ENTITYBLOCK_ANIMATED`. -::: - -To set the BEWLR for an Item, an anonymous instance of `IClientItemExtensions` must be consumed within `Item#initializeClient`. Within the anonymous instance, `IClientItemExtensions#getCustomRenderer` should be overridden to return the instance of your BEWLR: - -```java -// In your item class -@Override -public void initializeClient(Consumer consumer) { - consumer.accept(new IClientItemExtensions() { - - @Override - public BlockEntityWithoutLevelRenderer getCustomRenderer() { - return myBEWLRInstance; - } - }); -} -``` - -:::caution -Each mod should only have one instance of a custom BEWLR. -::: - -That is it, no additional setup is necessary to use a BEWLR. diff --git a/docs/resources/client/models/bakedmodel.md b/docs/resources/client/models/bakedmodel.md index 23a78847..359dafe1 100644 --- a/docs/resources/client/models/bakedmodel.md +++ b/docs/resources/client/models/bakedmodel.md @@ -123,8 +123,9 @@ It is generally encouraged to use a [custom model loader][modelloader] over wrap [ao]: https://en.wikipedia.org/wiki/Ambient_occlusion [ber]: ../../../blockentities/ber.md -[bewlr]: ../../../items/bewlr.md +[bewlr]: ../../../blockentities/ber.md#blockentitywithoutlevelrenderer [blockstate]: ../../../blocks/states.md +[event]: ../../../concepts/events.md [itemoverrides]: #itemoverrides [itemstack]: ../../../items/index.md#itemstacks [modelloader]: modelloaders.md @@ -133,3 +134,4 @@ It is generally encouraged to use a [custom model loader][modelloader] over wrap [perspective]: #perspectives [rendertype]: index.md#render-types [rl]: ../../../misc/resourcelocation.md +[sides]: ../../../concepts/sides.md diff --git a/docs/resources/client/models/index.md b/docs/resources/client/models/index.md index 99c5de3b..079ade3b 100644 --- a/docs/resources/client/models/index.md +++ b/docs/resources/client/models/index.md @@ -303,7 +303,7 @@ public static void registerAdditional(ModelEvent.RegisterAdditional event) { [ao]: https://en.wikipedia.org/wiki/Ambient_occlusion [ber]: ../../../blockentities/ber.md -[bewlr]: ../../../items/bewlr.md +[bewlr]: ../../../blockentities/ber.md#blockentitywithoutlevelrenderer [bsfile]: #blockstate-files [custommodelloader]: modelloaders.md [elements]: #elements diff --git a/docs/resources/client/models/modelloaders.md b/docs/resources/client/models/modelloaders.md index ac8e4cd8..49f5bd80 100644 --- a/docs/resources/client/models/modelloaders.md +++ b/docs/resources/client/models/modelloaders.md @@ -456,7 +456,7 @@ public class MyDynamicModel implements IDynamicBakedModel { [bakedmodel]: bakedmodel.md [ber]: ../../../blockentities/ber.md -[bewlr]: ../../../items/bewlr.md +[bewlr]: ../../../blockentities/ber.md#blockentitywithoutlevelrenderer [composite]: #composite-model [datagen]: ../../index.md#data-generation [elements]: index.md#elements