Skip to content

Commit b95a9ee

Browse files
committed
Add support for custom BlockEntity with serialization and inventories.
1 parent f1eec50 commit b95a9ee

File tree

6 files changed

+340
-0
lines changed

6 files changed

+340
-0
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package net.darkhax.bookshelf.api.block;
2+
3+
import net.darkhax.bookshelf.api.block.entity.InventoryBlockEntity;
4+
import net.minecraft.core.BlockPos;
5+
import net.minecraft.world.Containers;
6+
import net.minecraft.world.level.Level;
7+
import net.minecraft.world.level.block.Block;
8+
import net.minecraft.world.level.block.EntityBlock;
9+
import net.minecraft.world.level.block.entity.BlockEntity;
10+
import net.minecraft.world.level.block.state.BlockState;
11+
12+
public abstract class InventoryBlock extends Block implements EntityBlock {
13+
14+
public InventoryBlock(Properties properties) {
15+
16+
super(properties);
17+
}
18+
19+
@Override
20+
public void onRemove(BlockState oldState, Level world, BlockPos pos, BlockState newState, boolean pushed) {
21+
22+
if (!newState.is(oldState.getBlock())) {
23+
24+
final BlockEntity blockEntity = world.getBlockEntity(pos);
25+
26+
if (blockEntity instanceof InventoryBlockEntity invBlock) {
27+
28+
Containers.dropContents(world, pos, invBlock.getInventory());
29+
}
30+
}
31+
32+
super.onRemove(oldState, world, pos, newState, pushed);
33+
}
34+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package net.darkhax.bookshelf.api.block.entity;
2+
3+
import net.darkhax.bookshelf.api.Services;
4+
import net.minecraft.core.BlockPos;
5+
import net.minecraft.core.NonNullList;
6+
import net.minecraft.nbt.CompoundTag;
7+
import net.minecraft.world.Container;
8+
import net.minecraft.world.ContainerHelper;
9+
import net.minecraft.world.item.ItemStack;
10+
import net.minecraft.world.level.block.entity.BlockEntityType;
11+
import net.minecraft.world.level.block.state.BlockState;
12+
13+
/**
14+
* An implementation of BlockEntity that holds an inventory. The inventory is persisted and synced to the client.
15+
*
16+
* @param <T> The type of inventory held by the BlockEntity.
17+
*/
18+
public abstract class InventoryBlockEntity<T extends Container> extends SerializedBlockEntity {
19+
20+
private final T inventory = this.createInventory();
21+
22+
public InventoryBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
23+
24+
super(type, pos, state);
25+
}
26+
27+
public final T getInventory() {
28+
29+
return this.inventory;
30+
}
31+
32+
abstract T createInventory();
33+
34+
@Override
35+
public void readTileData(CompoundTag tag) {
36+
37+
final NonNullList<ItemStack> tempInvStacks = NonNullList.withSize(this.getInventory().getContainerSize(), ItemStack.EMPTY);
38+
ContainerHelper.loadAllItems(tag, tempInvStacks);
39+
Services.INVENTORY_HELPER.fill(this.getInventory(), tempInvStacks);
40+
}
41+
42+
@Override
43+
public void writeTileData(CompoundTag tag) {
44+
45+
ContainerHelper.saveAllItems(tag, Services.INVENTORY_HELPER.toList(this.getInventory()));
46+
}
47+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package net.darkhax.bookshelf.api.block.entity;
2+
3+
import net.minecraft.core.BlockPos;
4+
import net.minecraft.nbt.CompoundTag;
5+
import net.minecraft.network.protocol.Packet;
6+
import net.minecraft.network.protocol.game.ClientGamePacketListener;
7+
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
8+
import net.minecraft.world.level.block.entity.BlockEntity;
9+
import net.minecraft.world.level.block.entity.BlockEntityType;
10+
import net.minecraft.world.level.block.state.BlockState;
11+
12+
/**
13+
* An implementation of BlockEntity that will persist custom data and send this data to clients.
14+
*/
15+
public abstract class SerializedBlockEntity extends BlockEntity {
16+
17+
public SerializedBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
18+
19+
super(type, pos, state);
20+
}
21+
22+
@Override
23+
public void saveAdditional(CompoundTag data) {
24+
25+
super.saveAdditional(data);
26+
this.writeTileData(data);
27+
}
28+
29+
@Override
30+
public void load(CompoundTag data) {
31+
32+
super.load(data);
33+
this.readTileData(data);
34+
}
35+
36+
@Override
37+
public CompoundTag getUpdateTag() {
38+
39+
final CompoundTag updateTag = super.getUpdateTag();
40+
writeTileData(updateTag);
41+
return updateTag;
42+
}
43+
44+
@Override
45+
public Packet<ClientGamePacketListener> getUpdatePacket() {
46+
47+
return ClientboundBlockEntityDataPacket.create(this);
48+
}
49+
50+
abstract void writeTileData(CompoundTag data);
51+
52+
abstract void readTileData(CompoundTag data);
53+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package net.darkhax.bookshelf.api.block.entity;
2+
3+
import net.darkhax.bookshelf.api.function.CachedSupplier;
4+
import net.minecraft.core.BlockPos;
5+
import net.minecraft.core.Direction;
6+
import net.minecraft.world.Container;
7+
import net.minecraft.world.WorldlyContainer;
8+
import net.minecraft.world.entity.player.Player;
9+
import net.minecraft.world.item.Item;
10+
import net.minecraft.world.item.ItemStack;
11+
import net.minecraft.world.level.block.entity.BlockEntityType;
12+
import net.minecraft.world.level.block.state.BlockState;
13+
14+
import javax.annotation.Nullable;
15+
import java.util.Set;
16+
import java.util.stream.IntStream;
17+
18+
/**
19+
* An implementation of InventoryBlockEntity that allows the inventory to be externally accessed by stuff like hoppers.
20+
*
21+
* @param <T> The type of inventory held by the BlockEntity.
22+
*/
23+
public abstract class WorldlyInventoryBlockEntity<T extends Container> extends InventoryBlockEntity<T> implements WorldlyContainer {
24+
25+
private final CachedSupplier<int[]> fallbackSlots = CachedSupplier.cache(() -> IntStream.range(0, getContainerSize()).toArray());
26+
27+
public WorldlyInventoryBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) {
28+
super(type, pos, state);
29+
}
30+
31+
@Override
32+
public int getContainerSize() {
33+
34+
return getInventory().getContainerSize();
35+
}
36+
37+
@Override
38+
public boolean isEmpty() {
39+
40+
return getInventory().isEmpty();
41+
}
42+
43+
@Override
44+
public ItemStack getItem(int slotId) {
45+
46+
return getInventory().getItem(slotId);
47+
}
48+
49+
@Override
50+
public ItemStack removeItem(int slotId, int amount) {
51+
52+
return getInventory().removeItem(slotId, amount);
53+
}
54+
55+
@Override
56+
public ItemStack removeItemNoUpdate(int slotId) {
57+
58+
return getInventory().removeItemNoUpdate(slotId);
59+
}
60+
61+
@Override
62+
public void setItem(int slotId, ItemStack stack) {
63+
64+
getInventory().setItem(slotId, stack);
65+
}
66+
67+
@Override
68+
public int getMaxStackSize() {
69+
70+
return getInventory().getMaxStackSize();
71+
}
72+
73+
@Override
74+
public boolean stillValid(Player player) {
75+
76+
return getInventory().stillValid(player);
77+
}
78+
79+
@Override
80+
public void startOpen(Player player) {
81+
82+
getInventory().startOpen(player);
83+
}
84+
85+
@Override
86+
public void stopOpen(Player player) {
87+
88+
getInventory().stopOpen(player);
89+
}
90+
91+
@Override
92+
public boolean canPlaceItem(int slotId, ItemStack stack) {
93+
94+
return getInventory().canPlaceItem(slotId, stack);
95+
}
96+
97+
@Override
98+
public int countItem(Item item) {
99+
100+
return getInventory().countItem(item);
101+
}
102+
103+
@Override
104+
public boolean hasAnyOf(Set<Item> item) {
105+
106+
return getInventory().hasAnyOf(item);
107+
}
108+
109+
@Override
110+
public void clearContent() {
111+
112+
getInventory().clearContent();
113+
}
114+
115+
@Override
116+
public int[] getSlotsForFace(Direction side) {
117+
118+
if (this.getInventory() instanceof WorldlyContainer worldy) {
119+
120+
return worldy.getSlotsForFace(side);
121+
}
122+
123+
return fallbackSlots.get();
124+
}
125+
126+
@Override
127+
public boolean canPlaceItemThroughFace(int slotId, ItemStack stack, @Nullable Direction side) {
128+
129+
if (this.getInventory() instanceof WorldlyContainer worldly) {
130+
131+
return worldly.canPlaceItemThroughFace(slotId, stack, side);
132+
}
133+
134+
return this.getInventory().canPlaceItem(slotId, stack) && this.getItem(slotId).getCount() < this.getMaxStackSize();
135+
}
136+
137+
@Override
138+
public boolean canTakeItemThroughFace(int slotId, ItemStack stack, Direction side) {
139+
140+
if (this.getInventory() instanceof WorldlyContainer worldly) {
141+
142+
return worldly.canTakeItemThroughFace(slotId, stack, side);
143+
}
144+
145+
return true;
146+
}
147+
}

Forge/src/main/java/net/darkhax/bookshelf/BookshelfForge.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package net.darkhax.bookshelf;
22

3+
import net.darkhax.bookshelf.api.block.entity.WorldlyInventoryBlockEntity;
34
import net.darkhax.bookshelf.impl.BookshelfCommon;
5+
import net.darkhax.bookshelf.impl.capabilities.SingletonCapabilityProvider;
6+
import net.minecraft.resources.ResourceLocation;
7+
import net.minecraft.world.level.block.entity.BlockEntity;
8+
import net.minecraftforge.common.MinecraftForge;
9+
import net.minecraftforge.event.AttachCapabilitiesEvent;
410
import net.minecraftforge.fml.common.Mod;
511

612
@Mod(Constants.MOD_ID)
@@ -9,5 +15,17 @@ public class BookshelfForge {
915
public BookshelfForge() {
1016

1117
new BookshelfCommon();
18+
19+
MinecraftForge.EVENT_BUS.addGenericListener(BlockEntity.class, this::attachBlockCapabilities);
20+
}
21+
22+
private static final ResourceLocation WORLDLY_CONTAINER_WRAPPER = new ResourceLocation(Constants.MOD_ID, "worldly_container_wrapper");
23+
24+
private void attachBlockCapabilities(AttachCapabilitiesEvent<BlockEntity> event) {
25+
26+
if (event.getObject() instanceof WorldlyInventoryBlockEntity<?> worldly) {
27+
28+
event.addCapability(WORLDLY_CONTAINER_WRAPPER, SingletonCapabilityProvider.of(worldly));
29+
}
1230
}
1331
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package net.darkhax.bookshelf.impl.capabilities;
2+
3+
import net.minecraft.core.Direction;
4+
import net.minecraft.world.WorldlyContainer;
5+
import net.minecraftforge.common.capabilities.Capability;
6+
import net.minecraftforge.common.capabilities.ICapabilityProvider;
7+
import net.minecraftforge.common.util.LazyOptional;
8+
import net.minecraftforge.items.CapabilityItemHandler;
9+
import net.minecraftforge.items.IItemHandler;
10+
import net.minecraftforge.items.wrapper.SidedInvWrapper;
11+
import org.jetbrains.annotations.NotNull;
12+
import org.jetbrains.annotations.Nullable;
13+
14+
public class SingletonCapabilityProvider<T> implements ICapabilityProvider {
15+
16+
private final Capability<T> provided;
17+
private final LazyOptional<T> value;
18+
19+
public SingletonCapabilityProvider(Capability<T> cap, T value) {
20+
21+
this.provided = cap;
22+
this.value = LazyOptional.of(() -> value);
23+
}
24+
25+
@NotNull
26+
@Override
27+
public <R> LazyOptional<R> getCapability(@NotNull Capability<R> requested, @Nullable Direction side) {
28+
29+
return this.provided.orEmpty(requested, value);
30+
}
31+
32+
public static SingletonCapabilityProvider<IItemHandler> of(WorldlyContainer inventory) {
33+
34+
return SingletonCapabilityProvider.of(new SidedInvWrapper(inventory, null));
35+
}
36+
37+
public static SingletonCapabilityProvider<IItemHandler> of(IItemHandler inventory) {
38+
39+
return new SingletonCapabilityProvider<>(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, inventory);
40+
}
41+
}

0 commit comments

Comments
 (0)