Butterfly nets can only hold one butterfly at a time, and we can’t do anything with them once caught. We will add a new way to interact with them: moving them to jars so they can be put on display. This adds something we can do with butterflies, as well as another “catch ’em all” element to the mod.
Butterfly Jars
This feature implements butterfly jars. The idea behind them is that we can now move butterflies from a butterfly net into a glass bottle. This bottle can then be placed in the world and we can see the butterfly inside. We can also release butterflies from the bottle, in the same we can from a butterfly net.
Registries
This is the first time we have implemented a new block, so we need to add new registries to support them. Our block will need a block entity, so we will need both a BlockRegistry
and a BlockEntityTypeRegistry
. We will talk about the need for a block entity below.
The BlockRegistry
can be implemented in a similar way to the other registries.
public class BlockRegistry { // An instance of a deferred registry we use to register items. public static final DeferredRegister<Block> INSTANCE = DeferredRegister.create(ForgeRegistries.BLOCKS, ButterfliesMod.MODID); // The bottled butterfly block when it is in the world. public static final RegistryObject<Block> BOTTLED_BUTTERFLY_BLOCK = INSTANCE.register(BottledButterflyBlock.NAME, () -> new BottledButterflyBlock(BlockBehaviour.Properties.copy(Blocks.GLASS) .isRedstoneConductor(BlockRegistry::never) .isSuffocating(BlockRegistry::never) .isValidSpawn(BlockRegistry::never) .isViewBlocking(BlockRegistry::never) .noOcclusion() .sound(SoundType.GLASS) .strength(0.3F))); /** * Helper method for the "never" attribute. * @param blockState The current block state. * @param blockGetter Access to the block. * @param blockPos The block's position. * @param entityType The entity type trying to spawn. * @return Always FALSE. */ private static boolean never(BlockState blockState, BlockGetter blockGetter, BlockPos blockPos, EntityType<?> entityType) { return false; } /** * Helper method for the "never" attribute. * @param blockState The current block state. * @param blockGetter Access to the block. * @param blockPos The block's position. * @return Always FALSE. */ private static boolean never(BlockState blockState, BlockGetter blockGetter, BlockPos blockPos) { return false; } }
We have our DeferredRegister
instance, and a list of blocks to register with the game. We register a BottledButterflyBlock
.
When we register the block we copy the properties from the vanilla glass block, and add a few of our own. We don’t want the block to conduct redstone, suffocate entities, or block visibility. We also don’t want entities spawning on the block.
The never()
methods are copied from vanilla code since they are private and we can’t access them. All they do is give us a shorthand for properties we want to always return false
. We could use reflection to access the private methods, but it’s easier and cleaner to just copy them here.
Since we need to hold extra data in our block, we will also need a block entity, and thus we need a registry for this as well.
public class BlockEntityTypeRegistry { // An instance of a deferred registry we use to register items. public static final DeferredRegister<BlockEntityType<?>> INSTANCE = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, ButterfliesMod.MODID); // The block entity for a bottled butterfly. @SuppressWarnings("ConstantConditions") public static final RegistryObject<BlockEntityType<ButterflyBlockEntity>> BUTTERFLY_BLOCK = INSTANCE.register(ButterflyBlockEntity.NAME, () -> BlockEntityType.Builder.of(ButterflyBlockEntity::new, BlockRegistry.BOTTLED_BUTTERFLY_BLOCK.get()).build(null)); }
This is much simpler than registering a block. In this case we just need to register our block entity and tell it which block we use with it.
Finally we need to initialise these registries in our mod class. Since block entiities and block items both need to know what items are registered, we ensure the BlockRegistry
is initialised first,
// Deferred registries. BlockRegistry.INSTANCE.register(modEventBus); BlockEntityTypeRegistry.INSTANCE.register(modEventBus); EntityTypeRegistry.INSTANCE.register(modEventBus); ItemRegistry.INSTANCE.register(modEventBus);
Block
Since we’re adding a block, we need a Block
class. Since we are using a block entity for this block, we need to inherit from BaseEntityBlock
.
public class BottledButterflyBlock extends BaseEntityBlock { // The name this item is registered under. public static final String NAME = "bottled_butterfly"; /** * Create a butterfly block * @param properties The properties of this block */ public BottledButterflyBlock(Properties properties) { super(properties); } }
We want our block to be shaped like a bottle, so we will need a custom collision shape. This means the block won’t act like a cube as most other blocks do.
// The bottle's "model". private static final VoxelShape SHAPE = Shapes.or( Block.box(5.0, 0.0, 5.0, 10.0, 1.0, 10.0), Block.box(4.0, 1.0, 4.D, 11.0, 2.0, 11.0), Block.box(3.0, 2.0, 3.0, 12.0, 6.0, 12.0), Block.box(4.0, 6.0, 4.D, 11.0, 7.0, 11.0), Block.box(5.0, 7.0, 5.0, 10.0, 8.0, 10.0), Block.box(6.0, 8.0, 6.0, 9.0, 10.0, 9.0), Block.box(5.0, 10.0, 5.0, 10.0, 12.0, 10.0), Block.box(6.0, 12.0, 6.0, 9.0, 13.0, 9.0)); /** * Get the shape of the block. * @param blockState The current block state. * @param blockGetter Access to the block. * @param position The block's position. * @param collisionContext The collision context we are fetching for. * @return The block's bounding box. */ @NotNull @Override @SuppressWarnings("deprecation") public VoxelShape getShape(@NotNull BlockState blockState, @NotNull BlockGetter blockGetter, @NotNull BlockPos position, @NotNull CollisionContext collisionContext) { return SHAPE; }
Later you will see how we create a butterfly entity to exist inside the bottle when it is placed in the world. When we destroy the bottle, we need to make sure that this butterfly entity is destroyed as well.
/** * Ensure we remove the butterfly when the block is destroyed. * @param level Allow access to the current level. * @param position The position of the block. * @param state The current block state. */ @Override public void destroy(@NotNull LevelAccessor level, @NotNull BlockPos position, @NotNull BlockState state) { super.destroy(level, position, state); AABB aabb = new AABB(position); List<Butterfly> butterflies = level.getEntitiesOfClass(Butterfly.class, aabb); for(Butterfly i : butterflies) { i.remove(Entity.RemovalReason.DISCARDED); } }
We also need to ensure that the dropped bottle has the correct NFT tag that represents the butterfly contained within. This means that we can’t just use loot tables as we would for other blocks, and instead we override getDrops()
.
/** * Transfer the NBT data to the drops when the block is destroyed. * @param blockState The current block state. * @param builder The loot drop builder. * @return The loot dropped by this block. */ @NotNull @Override @SuppressWarnings("deprecation") public List<ItemStack> getDrops(@NotNull BlockState blockState, @NotNull LootParams.Builder builder) { BlockEntity blockEntity = builder.getOptionalParameter(LootContextParams.BLOCK_ENTITY); if (blockEntity instanceof ButterflyBlockEntity butterflyBlockEntity) { ItemStack stack = new ItemStack(ItemRegistry.BOTTLED_BUTTERFLY.get()); BottledButterflyItem.setButterfly(stack, butterflyBlockEntity.getEntityId()); List<ItemStack> result = Lists.newArrayList(); result.add(stack); return result; } return super.getDrops(blockState, builder); }
We will look at setItem()
later, but all this does is set the NFT tag for the species of butterfly contained within the bottle.
Block entities can use BlockEntityRenderer
s to render themselves. This can be useful for blocks like chests, that need to animate themselves opening. For this reason the default rendering method for entity blocks is INVISIBLE
. We just want to render it like a normal block, so we need to change this by overriding getRenderShape()
.
/** * Tell this block to render as a normal block. * @param blockState The current block state. * @return Always MODEL. */ @Override @NotNull public RenderShape getRenderShape(@NotNull BlockState blockState) { return RenderShape.MODEL; }
Finally, we need to tell the game what kind of block entity to use. This method essentially acts like a factory, creating a new block entity every time it is called.
/** * Create the block's entity data. * @param position The block's position. * @param blockState The current block state. * @return The new block entity. */ @Nullable @Override public BlockEntity newBlockEntity(@NotNull BlockPos position, @NotNull BlockState blockState) { return new ButterflyBlockEntity(position, blockState); }
Block Entity
Blocks normally only store a small amount of data. Since Minecraft worlds are made up of blocks it makes sense to keep this data small so the game can handle as many blocks as possible.
Some blocks need more data to be able to work properly. An example of this would be a chest, or a barrel, which needs to store information about items contained inside. When a block needs extra data like this, we attach a BlockEntity
to store this data. Our block needs to store the entity ID of a butterfly, so we need a block entity.
public class ButterflyBlockEntity extends BlockEntity { public static final String NAME = "butterfly_block"; /** * Construction * @param position The position of the block that owns this entity. * @param blockState The state of the block that owns this entity. */ public ButterflyBlockEntity(BlockPos position, BlockState blockState) { super(BlockEntityTypeRegistry.BUTTERFLY_BLOCK.get(), position, blockState); } }
We give the entity a place to hold the entity ID, as well as a an accessor and a mutator.
// The ID of the butterfly contained in this block. private String entityId = ""; /** * Accessor for the entity ID. * @return The entity ID. */ public String getEntityId() { return entityId; } /** * Set the entity ID of the butterfly contained in this block. * @param entityId The entity ID of the butterfly contained in this block. */ public void setEntityId(String entityId) { this.entityId = entityId; }
Finally, we save this data using a CompoundTag
so that it gets saved to the world and will still exist next time we load into the game.
/** * Called when loading the entity from NBT data. * @param tag The NBT tags. */ @Override public void load(@NotNull CompoundTag tag) { super.load(tag); if (tag.contains(CompoundTagId.ENTITY_ID)) { this.entityId = tag.getString(CompoundTagId.ENTITY_ID); } } /** * Called when saving the entity to NBT data. * @param tag The NBT tags. */ @Override protected void saveAdditional(@NotNull CompoundTag tag) { super.saveAdditional(tag); tag.putString(CompoundTagId.ENTITY_ID, entityId); }
Block Item
In order for a player to be able to have a block in their inventory, they need an item associated with it. To do this, we inherit from BlockItem
and link it to the block we just created.
public class BottledButterflyItem extends BlockItem implements ButterflyContainerItem { // The name this item is registered under. public static final String NAME = "bottled_butterfly"; /** * Construction * @param properties The properties of the item. */ public BottledButterflyItem(Properties properties) { super(BlockRegistry.BOTTLED_BUTTERFLY_BLOCK.get(), properties); } }
This item and the butterfly net both use similar NBT tags (as do the block and the block entity). To ensure that we are consistent with the tag names, we create a new class that holds the commonly used strings.
public class CompoundTagId { public static final String ENTITY_ID = "EntityId"; public static final String CUSTOM_MODEL_DATA = "CustomModelData"; }
Now we can use these to add a method to set the butterfly’s entity ID when the jar is created.
/** * Set the butterfly contained in the bottle * @param stack The item stack to modify * @param entityId The type of butterfly */ public static void setButterfly(ItemStack stack, String entityId) { CompoundTag tag = stack.getOrCreateTag(); if (!tag.contains(CompoundTagId.CUSTOM_MODEL_DATA) || !tag.contains(CompoundTagId.ENTITY_ID)) { tag.putInt(CompoundTagId.CUSTOM_MODEL_DATA, 1); tag.putString(CompoundTagId.ENTITY_ID, entityId); } }
We also want to modify the name in the same way as the butterfly net, so players can see what butterflies are in the jar. We want to avoid copy-pasting code so we create an interface with a default implementation.
public interface ButterflyContainerItem { /** * Adds some helper text that tells us what butterfly is in the net (if any). * @param stack The item stack. * @param components The current text components. */ default void appendButterflyNameToHoverText(@NotNull ItemStack stack, @NotNull List<Component> components) { CompoundTag tag = stack.getOrCreateTag(); if (tag.contains(CompoundTagId.ENTITY_ID)) { String translatable = "item." + tag.getString(CompoundTagId.ENTITY_ID).replace(':', '.'); MutableComponent newComponent = Component.translatable(translatable); Style style = newComponent.getStyle().withColor(TextColor.fromLegacyFormat(ChatFormatting.DARK_RED)) .withItalic(true); newComponent.setStyle(style); components.add(newComponent); } } }
Now we can modify each classes appendHoverText()
override to use the same code.
/** * Adds some helper text that tells us what butterfly is in the net (if any). * @param stack The item stack. * @param level The current level. * @param components The current text components. * @param tooltipFlag Is this a tooltip? */ @Override public void appendHoverText(@NotNull ItemStack stack, @Nullable Level level, @NotNull List<Component> components, @NotNull TooltipFlag tooltipFlag) { appendButterflyNameToHoverText(stack, components); super.appendHoverText(stack, level, components, tooltipFlag); }
It’s not perfect, since we still have a little boiler plate, but it means we have the code for setting the description all in one place.
We want to be able to release butterflies from bottles as well as place them. To allow players to release butterflies we can use a similar method to the butterfly net, except in this case we leave a glass bottle in the player’s hand rather than an empty net.
/** * Right-clicking with a full bottle will release the net. * @param level The current level. * @param player The player holding the net. * @param hand The player's hand. * @return The result of the action, if any. */ @Override @NotNull public InteractionResultHolder<ItemStack> use(@NotNull Level level, @NotNull Player player, @NotNull InteractionHand hand) { ItemStack stack = player.getItemInHand(hand); CompoundTag tag = stack.getOrCreateTag(); if (tag.contains(CompoundTagId.ENTITY_ID)) { String entityId = tag.getString(CompoundTagId.ENTITY_ID); Butterfly.release(player, entityId, player.blockPosition(), false); player.setItemInHand(hand, new ItemStack(Items.GLASS_BOTTLE)); return InteractionResultHolder.success(stack); } return super.use(level, player, hand); }
Finally we want to be able to place the bottle in the world. When we place a bottle, we want to save the entity ID to the BlockEntity
and spawn a butterfly inside. We can do both of these things by overriding the place()
method.
/** * Placing the item will create an in-world bottle with a butterfly inside. * @param context The context in which the block is being placed. * @return The interaction result. */ @Override @NotNull public InteractionResult place(@NotNull BlockPlaceContext context) { String entityId = null; Player player = context.getPlayer(); if (player != null) { ItemStack stack = player.getItemInHand(context.getHand()); CompoundTag tag = stack.getOrCreateTag(); if (tag.contains(CompoundTagId.ENTITY_ID)) { entityId = tag.getString(CompoundTagId.ENTITY_ID); } } InteractionResult result = super.place(context); if (result == InteractionResult.CONSUME && entityId != null) { Level level = context.getLevel(); BlockPos position = context.getClickedPos(); Butterfly.release(player, entityId, position, true); BlockEntity blockEntity = level.getBlockEntity(position); if (blockEntity instanceof ButterflyBlockEntity butterflyBlockEntity) { butterflyBlockEntity.setEntityId(entityId); } } return result; }
By checking for the NBT tag first, we can avoid a lot of work if there is no butterfly in the jar. In this case, we will just place an empty bottle in the world.
Spawning the Captured Butterfly
To ensure the butterfly we spawn behaves properly, we need to make some small adjustments to the release()
method on the Butterfly
entity. We add a new parameter, placed
which we set to true
if the butterfly is in a jar. Then we need to do two things. First, we don’t edit the butterfly’s location to be in front of the player.
if (!placed) { Vec3 lookAngle = player.getLookAngle(); position = position.offset((int) lookAngle.x, (int) lookAngle.y + 1, (int) lookAngle.z); }
Then near the end of the method we make the butterfly we spawned invulnerable so it can’t be accidentally killed.
if (placed) { butterfly.setInvulnerable(true); }
Now the only way the butterfly will get destroyed is when we destroy the block.
On Crafted Event
When we craft a butterfly jar, we need to transfer the NBT tag from the butterfly net to the jar itself. By default, this doesn’t happen when we craft an item so we need to create some special behaviour for this. To do this we create an event subscriber for the ItemCraftedEvent
on the FORGE
bus.
/** * Transfer butterfly data when moving from a butterfly net to a bottle * @param event The event data */ @SubscribeEvent public static void onItemCraftedEvent(PlayerEvent.ItemCraftedEvent event) { ItemStack craftingItem = event.getCrafting(); if (craftingItem.getItem() == BOTTLED_BUTTERFLY.get()) { Container craftingMatrix = event.getInventory(); for (int i = 0; i < craftingMatrix.getContainerSize(); ++i) { ItemStack recipeItem = craftingMatrix.getItem(i); if (recipeItem.getItem() == BUTTERFLY_NET.get()) { CompoundTag tag = recipeItem.getOrCreateTag(); String entityId = tag.getString(CompoundTagId.ENTITY_ID); BottledButterflyItem.setButterfly(craftingItem, entityId); break; } } } }
First we check that the result of the recipe will be a bottled butterfly. If it is then we pull the entity ID from the butterfly net and use setButterfly()
to add the tag to the resulting item.
The only downside here is that we can create empty butterfly jars if we use an empty net. But that means we get to place down empty bottles, so it’s a feature not a bug.
Block States and Models
The code for this feature is all but complete now. But we still need a model for the jar. Blocks use “blockstates” to define what models they use. Depending on their facing or other state blocks can be set to use different models. For our purposes, we only need one model, so our blockstate JSON is simple.
{ "variants": { "": { "model": "butterflies:block/bottled_butterfly" } } }
The model itself, however is a bit more complex. Based the glass bottle item texture, we can create a 3D model in Minecraft’s JSON format.
{ "parent": "minecraft:block/block", "textures": { "0": "butterflies:block/bottle/blue0", "1": "butterflies:block/bottle/blue1", "2": "butterflies:block/bottle/blue2", "3": "butterflies:block/bottle/blue3", "4": "butterflies:block/bottle/blue4", "5": "butterflies:block/bottle/brown", "6": "butterflies:block/bottle/bluebrown", "particle": "butterflies:block/bottle/blue0" }, "elements": [ { "from": [5, 0, 5], "to": [10, 1, 10], "faces": { "north": {"uv": [5, 0, 10, 1], "texture": "#0"}, "east": {"uv": [5, 1, 10, 2], "texture": "#0"}, "south": {"uv": [5, 2, 10, 3], "texture": "#0"}, "west": {"uv": [5, 3, 10, 4], "texture": "#0"}, "up": {"uv": [5, 5, 0, 0], "texture": "#0"}, "down": {"uv": [5, 5, 0, 10], "texture": "#0"} } }, { "from": [4, 1, 10], "to": [5, 2, 11], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#1"}, "east": {"uv": [0, 1, 1, 2], "texture": "#1"}, "south": {"uv": [1, 0, 2, 1], "texture": "#1"}, "west": {"uv": [1, 1, 2, 2], "texture": "#1"}, "up": {"uv": [1, 3, 0, 2], "texture": "#1"}, "down": {"uv": [3, 0, 2, 1], "texture": "#1"} } }, { "from": [10, 1, 10], "to": [11, 2, 11], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#1"}, "east": {"uv": [0, 1, 1, 2], "texture": "#1"}, "south": {"uv": [1, 0, 2, 1], "texture": "#1"}, "west": {"uv": [1, 1, 2, 2], "texture": "#1"}, "up": {"uv": [1, 3, 0, 2], "texture": "#1"}, "down": {"uv": [3, 0, 2, 1], "texture": "#1"} } }, { "from": [10, 1, 4], "to": [11, 2, 5], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#1"}, "east": {"uv": [0, 1, 1, 2], "texture": "#1"}, "south": {"uv": [1, 0, 2, 1], "texture": "#1"}, "west": {"uv": [1, 1, 2, 2], "texture": "#1"}, "up": {"uv": [1, 3, 0, 2], "texture": "#1"}, "down": {"uv": [3, 0, 2, 1], "texture": "#1"} } }, { "from": [4, 1, 4], "to": [5, 2, 5], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#1"}, "east": {"uv": [0, 1, 1, 2], "texture": "#1"}, "south": {"uv": [1, 0, 2, 1], "texture": "#1"}, "west": {"uv": [1, 1, 2, 2], "texture": "#1"}, "up": {"uv": [1, 3, 0, 2], "texture": "#1"}, "down": {"uv": [3, 0, 2, 1], "texture": "#1"} } }, { "from": [6, 1, 4], "to": [7, 2, 5], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#2"}, "east": {"uv": [0, 1, 1, 2], "texture": "#2"}, "south": {"uv": [1, 0, 2, 1], "texture": "#2"}, "west": {"uv": [1, 1, 2, 2], "texture": "#2"}, "up": {"uv": [1, 3, 0, 2], "texture": "#2"}, "down": {"uv": [3, 0, 2, 1], "texture": "#2"} } }, { "from": [6, 1, 10], "to": [7, 2, 11], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#2"}, "east": {"uv": [0, 1, 1, 2], "texture": "#2"}, "south": {"uv": [1, 0, 2, 1], "texture": "#2"}, "west": {"uv": [1, 1, 2, 2], "texture": "#2"}, "up": {"uv": [1, 3, 0, 2], "texture": "#2"}, "down": {"uv": [3, 0, 2, 1], "texture": "#2"} } }, { "from": [3, 2, 11], "to": [4, 6, 12], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#1"}, "east": {"uv": [0, 1, 1, 2], "texture": "#1"}, "south": {"uv": [1, 0, 2, 1], "texture": "#1"}, "west": {"uv": [1, 1, 2, 2], "texture": "#1"}, "up": {"uv": [1, 3, 0, 2], "texture": "#1"}, "down": {"uv": [3, 0, 2, 1], "texture": "#1"} } }, { "from": [3, 2, 3], "to": [4, 6, 4], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#1"}, "east": {"uv": [0, 1, 1, 2], "texture": "#1"}, "south": {"uv": [1, 0, 2, 1], "texture": "#1"}, "west": {"uv": [1, 1, 2, 2], "texture": "#1"}, "up": {"uv": [1, 3, 0, 2], "texture": "#1"}, "down": {"uv": [3, 0, 2, 1], "texture": "#1"} } }, { "from": [11, 2, 3], "to": [12, 3, 4], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#1"}, "east": {"uv": [0, 1, 1, 2], "texture": "#1"}, "south": {"uv": [1, 0, 2, 1], "texture": "#1"}, "west": {"uv": [1, 1, 2, 2], "texture": "#1"}, "up": {"uv": [1, 3, 0, 2], "texture": "#1"}, "down": {"uv": [3, 0, 2, 1], "texture": "#1"} } }, { "from": [11, 2, 11], "to": [12, 3, 12], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#1"}, "east": {"uv": [0, 1, 1, 2], "texture": "#1"}, "south": {"uv": [1, 0, 2, 1], "texture": "#1"}, "west": {"uv": [1, 1, 2, 2], "texture": "#1"}, "up": {"uv": [1, 3, 0, 2], "texture": "#1"}, "down": {"uv": [3, 0, 2, 1], "texture": "#1"} } }, { "from": [5, 2, 3], "to": [6, 4, 4], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#2"}, "east": {"uv": [0, 1, 1, 2], "texture": "#2"}, "south": {"uv": [1, 0, 2, 1], "texture": "#2"}, "west": {"uv": [1, 1, 2, 2], "texture": "#2"}, "up": {"uv": [1, 3, 0, 2], "texture": "#2"}, "down": {"uv": [3, 0, 2, 1], "texture": "#2"} } }, { "from": [5, 2, 11], "to": [6, 4, 12], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#2"}, "east": {"uv": [0, 1, 1, 2], "texture": "#2"}, "south": {"uv": [1, 0, 2, 1], "texture": "#2"}, "west": {"uv": [1, 1, 2, 2], "texture": "#2"}, "up": {"uv": [1, 3, 0, 2], "texture": "#2"}, "down": {"uv": [3, 0, 2, 1], "texture": "#2"} } }, { "from": [11, 3, 3], "to": [12, 4, 4], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#3"}, "east": {"uv": [0, 1, 1, 2], "texture": "#3"}, "south": {"uv": [1, 0, 2, 1], "texture": "#3"}, "west": {"uv": [1, 1, 2, 2], "texture": "#3"}, "up": {"uv": [1, 3, 0, 2], "texture": "#3"}, "down": {"uv": [3, 0, 2, 1], "texture": "#3"} } }, { "from": [11, 3, 11], "to": [12, 4, 12], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#3"}, "east": {"uv": [0, 1, 1, 2], "texture": "#3"}, "south": {"uv": [1, 0, 2, 1], "texture": "#3"}, "west": {"uv": [1, 1, 2, 2], "texture": "#3"}, "up": {"uv": [1, 3, 0, 2], "texture": "#3"}, "down": {"uv": [3, 0, 2, 1], "texture": "#3"} } }, { "from": [11, 4, 11], "to": [12, 6, 12], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#4"}, "east": {"uv": [0, 1, 1, 2], "texture": "#4"}, "south": {"uv": [1, 0, 2, 1], "texture": "#4"}, "west": {"uv": [1, 1, 2, 2], "texture": "#4"}, "up": {"uv": [1, 3, 0, 2], "texture": "#4"}, "down": {"uv": [3, 0, 2, 1], "texture": "#4"} } }, { "from": [11, 4, 3], "to": [12, 6, 4], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#4"}, "east": {"uv": [0, 1, 1, 2], "texture": "#4"}, "south": {"uv": [1, 0, 2, 1], "texture": "#4"}, "west": {"uv": [1, 1, 2, 2], "texture": "#4"}, "up": {"uv": [1, 3, 0, 2], "texture": "#4"}, "down": {"uv": [3, 0, 2, 1], "texture": "#4"} } }, { "from": [9, 4, 3], "to": [10, 6, 4], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#3"}, "east": {"uv": [0, 1, 1, 2], "texture": "#3"}, "south": {"uv": [1, 0, 2, 1], "texture": "#3"}, "west": {"uv": [1, 1, 2, 2], "texture": "#3"}, "up": {"uv": [1, 3, 0, 2], "texture": "#3"}, "down": {"uv": [3, 0, 2, 1], "texture": "#3"} } }, { "from": [9, 4, 11], "to": [10, 6, 12], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#3"}, "east": {"uv": [0, 1, 1, 2], "texture": "#3"}, "south": {"uv": [1, 0, 2, 1], "texture": "#3"}, "west": {"uv": [1, 1, 2, 2], "texture": "#3"}, "up": {"uv": [1, 3, 0, 2], "texture": "#3"}, "down": {"uv": [3, 0, 2, 1], "texture": "#3"} } }, { "from": [8, 6, 4], "to": [9, 7, 5], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#3"}, "east": {"uv": [0, 1, 1, 2], "texture": "#3"}, "south": {"uv": [1, 0, 2, 1], "texture": "#3"}, "west": {"uv": [1, 1, 2, 2], "texture": "#3"}, "up": {"uv": [1, 3, 0, 2], "texture": "#3"}, "down": {"uv": [3, 0, 2, 1], "texture": "#3"} } }, { "from": [8, 6, 10], "to": [9, 7, 11], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#3"}, "east": {"uv": [0, 1, 1, 2], "texture": "#3"}, "south": {"uv": [1, 0, 2, 1], "texture": "#3"}, "west": {"uv": [1, 1, 2, 2], "texture": "#3"}, "up": {"uv": [1, 3, 0, 2], "texture": "#3"}, "down": {"uv": [3, 0, 2, 1], "texture": "#3"} } }, { "from": [10, 6, 4], "to": [11, 7, 5], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#4"}, "east": {"uv": [0, 1, 1, 2], "texture": "#4"}, "south": {"uv": [1, 0, 2, 1], "texture": "#4"}, "west": {"uv": [1, 1, 2, 2], "texture": "#4"}, "up": {"uv": [1, 3, 0, 2], "texture": "#4"}, "down": {"uv": [3, 0, 2, 1], "texture": "#4"} } }, { "from": [10, 6, 10], "to": [11, 7, 11], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#4"}, "east": {"uv": [0, 1, 1, 2], "texture": "#4"}, "south": {"uv": [1, 0, 2, 1], "texture": "#4"}, "west": {"uv": [1, 1, 2, 2], "texture": "#4"}, "up": {"uv": [1, 3, 0, 2], "texture": "#4"}, "down": {"uv": [3, 0, 2, 1], "texture": "#4"} } }, { "from": [4, 6, 10], "to": [5, 7, 11], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#1"}, "east": {"uv": [0, 1, 1, 2], "texture": "#1"}, "south": {"uv": [1, 0, 2, 1], "texture": "#1"}, "west": {"uv": [1, 1, 2, 2], "texture": "#1"}, "up": {"uv": [1, 3, 0, 2], "texture": "#1"}, "down": {"uv": [3, 0, 2, 1], "texture": "#1"} } }, { "from": [4, 6, 4], "to": [5, 7, 5], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#1"}, "east": {"uv": [0, 1, 1, 2], "texture": "#1"}, "south": {"uv": [1, 0, 2, 1], "texture": "#1"}, "west": {"uv": [1, 1, 2, 2], "texture": "#1"}, "up": {"uv": [1, 3, 0, 2], "texture": "#1"}, "down": {"uv": [3, 0, 2, 1], "texture": "#1"} } }, { "from": [9, 7, 5], "to": [10, 8, 6], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#4"}, "east": {"uv": [0, 1, 1, 2], "texture": "#4"}, "south": {"uv": [1, 0, 2, 1], "texture": "#4"}, "west": {"uv": [1, 1, 2, 2], "texture": "#4"}, "up": {"uv": [1, 3, 0, 2], "texture": "#4"}, "down": {"uv": [3, 0, 2, 1], "texture": "#4"} } }, { "from": [9, 7, 9], "to": [10, 8, 10], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#4"}, "east": {"uv": [0, 1, 1, 2], "texture": "#4"}, "south": {"uv": [1, 0, 2, 1], "texture": "#4"}, "west": {"uv": [1, 1, 2, 2], "texture": "#4"}, "up": {"uv": [1, 3, 0, 2], "texture": "#4"}, "down": {"uv": [3, 0, 2, 1], "texture": "#4"} } }, { "from": [5, 7, 5], "to": [6, 8, 6], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#1"}, "east": {"uv": [0, 1, 1, 2], "texture": "#1"}, "south": {"uv": [1, 0, 2, 1], "texture": "#1"}, "west": {"uv": [1, 1, 2, 2], "texture": "#1"}, "up": {"uv": [1, 3, 0, 2], "texture": "#1"}, "down": {"uv": [3, 0, 2, 1], "texture": "#1"} } }, { "from": [5, 7, 9], "to": [6, 8, 10], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#1"}, "east": {"uv": [0, 1, 1, 2], "texture": "#1"}, "south": {"uv": [1, 0, 2, 1], "texture": "#1"}, "west": {"uv": [1, 1, 2, 2], "texture": "#1"}, "up": {"uv": [1, 3, 0, 2], "texture": "#1"}, "down": {"uv": [3, 0, 2, 1], "texture": "#1"} } }, { "from": [8, 8, 6], "to": [9, 10, 7], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#4"}, "east": {"uv": [0, 1, 1, 2], "texture": "#4"}, "south": {"uv": [1, 0, 2, 1], "texture": "#4"}, "west": {"uv": [1, 1, 2, 2], "texture": "#4"}, "up": {"uv": [1, 3, 0, 2], "texture": "#4"}, "down": {"uv": [3, 0, 2, 1], "texture": "#4"} } }, { "from": [8, 8, 8], "to": [9, 10, 9], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#4"}, "east": {"uv": [0, 1, 1, 2], "texture": "#4"}, "south": {"uv": [1, 0, 2, 1], "texture": "#4"}, "west": {"uv": [1, 1, 2, 2], "texture": "#4"}, "up": {"uv": [1, 3, 0, 2], "texture": "#4"}, "down": {"uv": [3, 0, 2, 1], "texture": "#4"} } }, { "from": [6, 8, 6], "to": [7, 10, 7], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#1"}, "east": {"uv": [0, 1, 1, 2], "texture": "#1"}, "south": {"uv": [1, 0, 2, 1], "texture": "#1"}, "west": {"uv": [1, 1, 2, 2], "texture": "#1"}, "up": {"uv": [1, 3, 0, 2], "texture": "#1"}, "down": {"uv": [3, 0, 2, 1], "texture": "#1"} } }, { "from": [6, 8, 8], "to": [7, 10, 9], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#1"}, "east": {"uv": [0, 1, 1, 2], "texture": "#1"}, "south": {"uv": [1, 0, 2, 1], "texture": "#1"}, "west": {"uv": [1, 1, 2, 2], "texture": "#1"}, "up": {"uv": [1, 3, 0, 2], "texture": "#1"}, "down": {"uv": [3, 0, 2, 1], "texture": "#1"} } }, { "from": [6, 10, 6], "to": [9, 13, 9], "faces": { "north": {"uv": [0, 0, 3, 3], "texture": "#5"}, "east": {"uv": [0, 3, 3, 6], "texture": "#5"}, "south": {"uv": [3, 0, 6, 3], "texture": "#5"}, "west": {"uv": [3, 3, 6, 6], "texture": "#5"}, "up": {"uv": [3, 9, 0, 6], "texture": "#5"}, "down": {"uv": [9, 0, 6, 3], "texture": "#5"} } }, { "from": [5, 10, 5], "to": [6, 12, 6], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#3"}, "east": {"uv": [0, 1, 1, 2], "texture": "#3"}, "south": {"uv": [1, 0, 2, 1], "texture": "#3"}, "west": {"uv": [1, 1, 2, 2], "texture": "#3"}, "up": {"uv": [1, 3, 0, 2], "texture": "#3"}, "down": {"uv": [3, 0, 2, 1], "texture": "#3"} } }, { "from": [5, 10, 9], "to": [6, 12, 10], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#3"}, "east": {"uv": [0, 1, 1, 2], "texture": "#3"}, "south": {"uv": [1, 0, 2, 1], "texture": "#3"}, "west": {"uv": [1, 1, 2, 2], "texture": "#3"}, "up": {"uv": [1, 3, 0, 2], "texture": "#3"}, "down": {"uv": [3, 0, 2, 1], "texture": "#3"} } }, { "from": [9, 10, 5], "to": [10, 11, 6], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#3"}, "east": {"uv": [0, 1, 1, 2], "texture": "#3"}, "south": {"uv": [1, 0, 2, 1], "texture": "#3"}, "west": {"uv": [1, 1, 2, 2], "texture": "#3"}, "up": {"uv": [1, 3, 0, 2], "texture": "#3"}, "down": {"uv": [3, 0, 2, 1], "texture": "#3"} } }, { "from": [9, 10, 9], "to": [10, 11, 10], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#3"}, "east": {"uv": [0, 1, 1, 2], "texture": "#3"}, "south": {"uv": [1, 0, 2, 1], "texture": "#3"}, "west": {"uv": [1, 1, 2, 2], "texture": "#3"}, "up": {"uv": [1, 3, 0, 2], "texture": "#3"}, "down": {"uv": [3, 0, 2, 1], "texture": "#3"} } }, { "from": [9, 11, 5], "to": [10, 12, 6], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#4"}, "east": {"uv": [0, 1, 1, 2], "texture": "#4"}, "south": {"uv": [1, 0, 2, 1], "texture": "#4"}, "west": {"uv": [1, 1, 2, 2], "texture": "#4"}, "up": {"uv": [1, 3, 0, 2], "texture": "#4"}, "down": {"uv": [3, 0, 2, 1], "texture": "#4"} } }, { "from": [9, 11, 9], "to": [10, 12, 10], "faces": { "north": {"uv": [0, 0, 1, 1], "texture": "#4"}, "east": {"uv": [0, 1, 1, 2], "texture": "#4"}, "south": {"uv": [1, 0, 2, 1], "texture": "#4"}, "west": {"uv": [1, 1, 2, 2], "texture": "#4"}, "up": {"uv": [1, 3, 0, 2], "texture": "#4"}, "down": {"uv": [3, 0, 2, 1], "texture": "#4"} } }, { "from": [6, 11, 5], "to": [9, 12, 6], "faces": { "north": {"uv": [0, 0, 3, 1], "texture": "#6"}, "east": {"uv": [3, 0, 4, 1], "texture": "#6"}, "south": {"uv": [0, 1, 3, 2], "texture": "#6"}, "west": {"uv": [3, 1, 4, 2], "texture": "#6"}, "up": {"uv": [3, 3, 0, 2], "texture": "#6"}, "down": {"uv": [3, 3, 0, 4], "texture": "#6"} } }, { "from": [6, 11, 9], "to": [9, 12, 10], "faces": { "north": {"uv": [0, 0, 3, 1], "texture": "#6"}, "east": {"uv": [3, 0, 4, 1], "texture": "#6"}, "south": {"uv": [0, 1, 3, 2], "texture": "#6"}, "west": {"uv": [3, 1, 4, 2], "texture": "#6"}, "up": {"uv": [3, 3, 0, 2], "texture": "#6"}, "down": {"uv": [3, 3, 0, 4], "texture": "#6"} } }, { "from": [9, 11, 6], "to": [10, 12, 9], "faces": { "north": {"uv": [0, 0, 3, 1], "texture": "#6"}, "east": {"uv": [3, 0, 4, 1], "texture": "#6"}, "south": {"uv": [0, 1, 3, 2], "texture": "#6"}, "west": {"uv": [3, 1, 4, 2], "texture": "#6"}, "up": {"uv": [3, 3, 0, 2], "texture": "#6"}, "down": {"uv": [3, 3, 0, 4], "texture": "#6"} } }, { "from": [5, 11, 6], "to": [6, 12, 9], "faces": { "north": {"uv": [0, 0, 3, 1], "texture": "#6"}, "east": {"uv": [3, 0, 4, 1], "texture": "#6"}, "south": {"uv": [0, 1, 3, 2], "texture": "#6"}, "west": {"uv": [3, 1, 4, 2], "texture": "#6"}, "up": {"uv": [3, 3, 0, 2], "texture": "#6"}, "down": {"uv": [3, 3, 0, 4], "texture": "#6"} } } ], "groups": [ { "name": "layer-1", "origin": [0, 0, 0], "color": 0, "children": [0] }, { "name": "layer-2", "origin": [8, 8, 8], "color": 0, "children": [1, 2, 3, 4, 5, 6] }, { "name": "layer-3", "origin": [8, 8, 8], "color": 0, "children": [7, 8, 9, 10, 11, 12] }, { "name": "layer-4", "origin": [8, 8, 8], "color": 0, "children": [13, 14] }, { "name": "layer-4", "origin": [0, 0, 0], "color": 0, "children": [15, 16, 17, 18] }, { "name": "layer-5", "origin": [0, 0, 0], "color": 0, "children": [19, 20, 21, 22, 23, 24] }, { "name": "layer-6", "origin": [0, 0, 0], "color": 0, "children": [25, 26, 27, 28] }, { "name": "layer-7", "origin": [0, 0, 0], "color": 0, "children": [29, 30, 31, 32] }, { "name": "layer-8", "origin": [8, 8, 8], "color": 0, "children": [33, 34, 35, 36, 37] }, { "name": "layer-9", "origin": [0, 0, 0], "color": 0, "children": [38, 39, 40, 41, 42, 43] } ] }
This is a fairly complex file since we use multiple cubes and textures to fully define the block. However, after creating this we now have a good looking glass bottle that can be placed in the world!
Finishing Up
The only thing left now is to add some recipes, advancements, and localisation strings. The recipe we use for this one is a shapeless recipe, taking a butterfly net and a glass bottle.
{ "type": "minecraft:crafting_shapeless", "ingredients": [ { "item": "butterflies:butterfly_net" }, { "item": "minecraft:glass_bottle" } ], "result": { "item": "butterflies:bottled_butterfly" } }
We add an unlock for this recipe based on having the butterfly net and glass bottle. We also add a couple of advancements – one for bottling a butterfly, a second for bottling all butterfly species.
With all this done we are feature complete! We can now decorate our worlds with imprisoned butterflies.
One thought on “Bottling Butterflies in a Jar”