Skip to content

Commit

Permalink
apply feedback by @ChampionAsh5357
Browse files Browse the repository at this point in the history
  • Loading branch information
IchHabeHunger54 committed Sep 16, 2024
1 parent 4aaccd5 commit 9004732
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 36 deletions.
26 changes: 23 additions & 3 deletions docs/blockentities/ber.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<MyBlockEntity> {
// 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.
Expand All @@ -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<MyBlockEntity> {
@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()
);
}
Expand Down
61 changes: 51 additions & 10 deletions docs/blockentities/container.md
Original file line number Diff line number Diff line change
Expand Up @@ -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() {

Expand Down Expand Up @@ -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!
Expand All @@ -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
Expand All @@ -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;
Expand All @@ -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.
Expand Down
49 changes: 26 additions & 23 deletions docs/blockentities/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,19 @@ Registration happens in a similar fashion to entities. We create an instance of
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITY_TYPES =
DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, ExampleMod.MOD_ID);

public static final Supplier<BlockEntityType<MyBlockEntity>> 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 DeferredBlock<Block>s.
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<BlockEntityType<MyBlockEntity>> 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 DeferredBlock<Block>s.
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:
Expand All @@ -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<T extends BlockEntity>`, which is basically a `BiFunction<BlockPos, BlockState, T extends BlockEntity>`. 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
Expand All @@ -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<T extends BlockEntity>`, which is basically a `BiFunction<BlockPos, BlockState, T extends BlockEntity>`. 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<MyEntityBlock> MY_ENTITY_BLOCK =
BLOCKS.register("my_entity_block", () -> new MyEntityBlock( /* ... */ ));
```

## Storing Data

Expand All @@ -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);
}
}
```
Expand All @@ -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

Expand Down

1 comment on commit 9004732

@neoforged-pages-deployments
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploying with Cloudflare Pages

Name Result
Last commit: 900473275e18d69afd64f68363311d039f7c70fc
Status: ✅ Deploy successful!
Preview URL: https://7c6fdb35.neoforged-docs-previews.pages.dev
PR Preview URL: https://pr-160.neoforged-docs-previews.pages.dev

Please sign in to comment.