diff --git a/docs/blockentities/ber.md b/docs/blockentities/ber.md index e8aedeaa..d025f848 100644 --- a/docs/blockentities/ber.md +++ b/docs/blockentities/ber.md @@ -7,6 +7,11 @@ To create a BER, create a class that inherits from `BlockEntityRenderer`. It tak ```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. @@ -32,9 +37,24 @@ public static void registerEntityRenderers(EntityRenderersEvent.RegisterRenderer // The block entity type to register the renderer for. MyBlockEntities.MY_BLOCK_ENTITY.get(), // A function of BlockEntityRendererProvider.Context to BlockEntityRenderer. - // You may retrieve and store values from the context in your BER's constructor if needed. - // For example, the context contains entity, item and font renderers. - // If you don't need that, you can simply not use the context and just call new. + MyBlockEntityRenderer::new + ); +} +``` + +In the event that you do not need the BER provider context in your BER, you can also remove the constructor: + +```java +public class MyBlockEntityRenderer implements BlockEntityRenderer { + @Override + public void render( /* ... */ ) { /* ... */ } +} + +// 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() ); } diff --git a/docs/blockentities/container.md b/docs/blockentities/container.md index 69c0e7f6..0ebdeb24 100644 --- a/docs/blockentities/container.md +++ b/docs/blockentities/container.md @@ -30,36 +30,38 @@ public class MyContainer implements Container { // Whether the container is considered empty. @Override public boolean isEmpty() { - return items.stream().allMatch(ItemStack::isEmpty); + return this.items.stream().allMatch(ItemStack::isEmpty); } // Return the item stack in the specified slot. @Override public ItemStack getItem(int slot) { - return items.get(slot); + return this.items.get(slot); } // 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. @Override public ItemStack removeItem(int slot, int amount) { - return ContainerHelper.removeItem(items, slot, amount); + return ContainerHelper.removeItem(this.items, slot, amount); } // Remove all items from the specified slot, returning the stack that was just removed. // We again defer to ContainerHelper here. @Override public ItemStack removeItemNoUpdate(int slot) { - return ContainerHelper.takeItem(items, slot); + return ContainerHelper.takeItem(this.items, slot); } - // Set the given item stack in the given slot. + // 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) { - items.set(slot, stack); + stack.limitSize(this.getMaxStackSize(stack)); + this.items.set(slot, stack); } - // Do something when changes are done to the container. For example, you could call BlockEntity#setChanged here. + // Do something 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() { @@ -141,6 +143,43 @@ public class MyBlockEntity extends BaseContainerBlockEntity { 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! @@ -167,7 +206,7 @@ container.removeItem(2, 16); ``` :::warning -A container may throw an exception if trying to access a slot that is beyond its container size. +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 @@ -176,7 +215,9 @@ Until now, we mainly discussed `Container`s on `BlockEntity`s. However, they can ```java // We use SimpleContainer as the superclass here so we don't have to reimplement the item handling logic ourselves. -// You may of course use a different base implementation of Container (or implement Container yourself) if needed. +// 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; @@ -190,7 +231,7 @@ public class MyBackpackContainer extends SimpleContainer { // 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.copyInfo(this.getItems()); + contents.copyInto(this.getItems()); } // When the contents are changed, we save the data component on the stack. diff --git a/docs/blockentities/index.md b/docs/blockentities/index.md index a78199f5..238e0cbb 100644 --- a/docs/blockentities/index.md +++ b/docs/blockentities/index.md @@ -28,20 +28,19 @@ Registration happens in a similar fashion to entities. We create an instance of 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); - ); +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) +); ``` Now that we have our block entity type, we can use it in place of the `type` variable we left earlier: @@ -54,6 +53,10 @@ 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. +::: + 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 @@ -72,11 +75,12 @@ 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: -:::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. -::: +```java +public static final DeferredBlock MY_ENTITY_BLOCK = + BLOCKS.register("my_entity_block", () -> new MyEntityBlock( /* ... */ )); +``` ## Storing Data @@ -98,16 +102,15 @@ public class MyBlockEntity extends BlockEntity { @Override public void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) { super.loadAdditional(tag, registries); - if (tag.has("value")) { - value = tag.getInt("value"); - } + // 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", value); + tag.putInt("value", this.value); } } ``` @@ -118,7 +121,7 @@ In both methods, it is important that you call super, as that adds basic informa It is expected that Mojang will adapt the [Data Components][datacomponents] system to also work with block entities sometime during the next few updates. Once that happens, both saving to NBT and data attachments will be removed in favor of data components. ::: -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, otherwise the block entity might get skipped during saving. +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. ## Tickers