-
Notifications
You must be signed in to change notification settings - Fork 12
Common Usage Examples: A Teeny Tiny Chest
(NOTE: 2019/06/05 | Look over the above and ensure it still makes sense!)
Let's say you want to make a mod, that includes a single block: A teeny-tiny chest. It's like a chest, but only has 15 slots rather than 27.
So, the basics would probably look something like this:
public class TeenyTinyChest implements ModInitializer {
public static final Identifer CHEST_ID = new Identifier("teeny_tiny_chest:chest");
public static final TeenyTinyChestBlock BLOCK;
public static final BlockEntityType<TeenyTinyChestBlockEntity> BLOCK_ENTITY;
public static final BlockItem ITEM;
static {
BLOCK = new TeenyTinyChestBlock(
FabricBlockSettings.copy(Blocks.CHEST).build()
);
BLOCK_ENTITY = new BlockEntityType<>(
TeenyTinyChestBlockEntity::new,
Collections.singleton(BLOCK),
null
);
ITEM = new BlockItem(BLOCK, new Item.Settings());
}
@Override
public void onInitialize() {
Registry.register(Regsitry.BLOCK, CHEST_ID, BLOCK);
Registry.register(Regsitry.BLOCK_ENTITY, CHEST_ID, BLOCK_ENTITY);
Registry.register(Regsitry.ITEM, CHEST_ID, ITEM);
}
}
public class TeenyTinyChestBlock extends Block implements BlockEntityProvider {
public TeenyTinyChestBlock(Block.Settings settings) {
super(settings);
}
@Override
public BlockEntity createBlockEntity(BlockView view) {
return new TeenyTinyChestBlockEntity();
}
}
public class TeenyTinyChestBlockEntity extends BlockEntity {
public TeenyTinyChestBlockEntity() {
this(TeenyTinyChest.BLOCK_ENTITY);
}
public TeenyTinyChestBlockEntity(BlockEntityType<?> type) {
super(type);
}
}
Which is a good start, but we don't actually store anything yet - so let's get LibBlockAttributes to manage everything. So we'll need to add this at the start of TeenyTinyChestBlockEntity
:
public final FullFixedItemInv inventory = new FullFixedItemInv(15);
So now it's a chest that can store items!
Unfortunately there's still a lot we need to do - like ensure that the inventory saves and loads properly! Otherwise it would be a slower trashcan.
// Also added to TeenyTinyChestBlockEntity
@Override
public CompoundTag toTag(CompoundTag tag) {
// We need this bit otherwise the chest won't read properly
tag = super.toTag(tag);
tag.put("inventory", inventory.toTag());
return tag;
}
@Override
public void fromTag(CompoundTag tag) {
super.fromTag(tag);
inventory.fromTag(tag.getCompound("inventory"));
}
So now it will at least work properly, but it's missing one important aspect: automation! And a GUI. But we'll do the gui a bit later.
Adding this to the block and block entity classes will let pipes, hoppers, and other machines insert and extract items with the inventory:
public class TeenyTinyChestBlock extends Block implements BlockEntityProvider, InventoryProvider {
@Override
public SidedInventory getInventory(BlockState state, World world BlockPos pos) {
BlockEntity be = world.getBlockEntity(pos);
if (be instanceof TeenyTinyChestBlockEntity) {
FixedItemInv inv = ((TeenyTinyChestBlockEntity) be).inventory;
return new SidedInventoryFixedWrapper(inv) {
@Override
public boolean canPlayerUseInv(PlayerEntity player) {
return world.getBlockEntity(pos) == be && player.squaredDistanceTo(new Vec3d(pos)) < 8 * 8;
}
};
}
return null;
}
public class TeenyTinyChestBlockEntity extends BlockEntity implements AttributeProviderBlockEntity {
@Override
public void addAllAttributes(AttributeList<?> to) {
to.offer(inventory);
}
}
And the final bit: a GUI. Most of this is standard fabric gui behaviour, but there is one slight difference: instead of using minecraft's Slot
class for the slots, we need to use SlotFixedItemInv
.
public class TeenyTinyChest implements ModInitializer {
public static final Identifer CHEST_ID = new Identifier("teeny_tiny_chest:chest");
public static final TeenyTinyChestBlock BLOCK;
public static final BlockEntityType<TeenyTinyChestBlockEntity> BLOCK_ENTITY;
public static final BlockItem ITEM;
static {
BLOCK = new TeenyTinyChestBlock(
FabricBlockSettings.copy(Blocks.CHEST).build()
);
BLOCK_ENTITY = new BlockEntityType<>(
TeenyTinyChestBlockEntity::new,
Collections.singleton(BLOCK),
null
);
ITEM = new BlockItem(BLOCK, new Item.Settings());
}
@Override
public void onInitialize() {
Registry.register(Regsitry.BLOCK, CHEST_ID, BLOCK);
Registry.register(Regsitry.BLOCK_ENTITY, CHEST_ID, BLOCK_ENTITY);
Registry.register(Regsitry.ITEM, CHEST_ID, ITEM);
ContainerProviderRegistry.INSTANCE.registerFactory(
CHEST_ID,
TeenyTinyChestContainer.FACTORY
);
}
}
public class TeenyTinyChestClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
ScreenProviderRegistry.INSTANCE.registerFactory(
TeenyTinyChest.CHEST_ID,
TeenyTinyChestScreen.FACTORY
);
}
}
public class TeenyTinyChestContainer extends Container {
public static final ContainerFactory<Container> FACTORY =
(syncId, id, player, buffer) -> {
BlockPos pos = buffer.readBlockPos();
BlockEntity be = player.world.getBlockEntity(pos);
if (be instanceof TeenyTinyChestBlockEntity) {
return new TeenyTinyChestContainer(
syncId,
player,
(TeenyTinyChestBlockEntity) be
);
}
return null;
};
public final TeenyTinyChestBlockEntity blockEntity;
public final PlayerEntity player;
public TeenyTinyChestContainer(
int syncId,
PlayerEntity player,
TeenyTinyChestBlockEntity blockEntity
) {
super(
// Custom containers don't use the vanilla
// ContainerType system, so pass null instead.
null, syncId
);
this.blockEntity = blockEntity;
this.player = player;
addPlayerInventory(71);
// Our teeny-tiny chest has
// 3 rows (y) and 5 colums (x)
for (int y = 0; y < 3; y++) {
for (int x = 0; x < 5; x++) {
addSlot(new SlotFixedItemInv(
this,
blockEntity.inventory,
x + 5 * y,
8 + 18 * x,
18 + 18 * y
));
}
}
}
/**
* Simple method to quickly add a player inventory to any GUI.
*
* @param startY The Y co-ordinate to start the inventory at.
*/
// Perhaps this might be useful for you to copy+paste?
protected void addPlayerInventory(int startY) {
for (int y = 0; y < 3; y++) {
for (int x = 0; x < 9; ++x) {
addSlot(new Slot(player.inventory, x + y * 9 + 9, 8 + x * 18, startY + y * 18));
}
}
for (int x = 0; x < 9; x++) {
addSlot(new Slot(player.inventory, x, 8 + x * 18, startY + 58));
}
}
@Override
public ItemStack transferSlot(PlayerEntity player, int slotIndex) {
// Shift-click logic can get quite long and complicated.
// Instead we'll just abort.
return ItemStack.EMPTY;
}
}
public class TeenyTinyChestScreen extends ContainerScreen<TeenyTinyChestContainer> {
public static final ContainerScreenFactory<TeenyTinyChestContainer> FACTORY
= TeenyTinyChestScreen::new;
public TeenyTinyChestScreen(TeenyTinyChestContainer container) {
super(container, container.player.inventory, TeenyTinyChest.BLOCK.getTextComponent());
containerHeight = 168;
}
// Background rendering and title TODO
}
Whew, that's a lot of code just to display a GUI! Now the only thing left to do is display it in TeenyTinyChestBlock
:
// Add this to TeenyTinyChestBlock
@Override
public boolean activate(
BlockState state,
World world,
BlockPos pos,
PlayerEntity player,
Hand hand,
BlockHitResult hitResult
) {
BlockEntity be = world.getBlockEntity(pos);
if (be instanceof TeenyTinyChestBlockEntity) {
if (world.isClient) {
return true;
}
ContainerProviderRegistry.INSTANCE.openContainer(
TeenyTinyChest.CHEST_ID,
player,
(buffer) -> buffer.writeBlockPos(pos)
);
return true;
}
return super.activate(state, world, pos, player, hand, hitResult);
}