🍀 My First Minecraft Mod · Episode 6 of 6 · See All Episodes
🍀 Episode 6 · Capstone · Lucky Crystal Block

Lucky Crystal Block!
The Final Boss

Break the Lucky Crystal Block and anything could happen frogs rain down, lightning strikes, you get free tools, or a Creeper appears! This is your capstone project you've learned everything you need.

🧑 Ages 10+ ⏱️ ~2 Hours 🍀 Fabric 1.21 ✓ Free
Your Progress 0 / 12 Steps
1
🍀
The Plan — What Will Happen?
Design the 5 random events before writing any code
In Progress
🍀
Goal — understand the Lucky Crystal Block concept

The Lucky Crystal Block is a special block that overrides the onBreak() method. When a player breaks it, instead of just dropping items, it picks one of 5 random events. Each event is a case in a switch statement.

The 5 random events

🐸
Frog Rain
Spawn 5 Crystal Frogs around the player
💎
Gem Shower
Drop 16 Crystal Gems at the block location
Lightning!
Strike the block position with lightning
🛡️
Crystal Kit
Give the player their Crystal Pickaxe and Sword
💥
Surprise!
Spawn a Creeper right on the player
ℹ️ How the randomness works we'll use world.getRandom().nextInt(5) which returns a number 0, 1, 2, 3, or 4 — each with equal probability. Each number triggers one event.
💡 This is a capstone project! We're using techniques from all 5 previous episodes: block registration (Ep 2), item drops (Ep 1+2), entity spawning (Ep 5), and custom block behaviour (Ep 2). You've learned all the skills you need!
I understand the Lucky Block will override onBreak() to trigger random events
I know all 5 events we'll implement
2
📝
Create LuckyCrystalBlock.java
Write the block class with the random event logic
Locked
📝
Goal — write the block's brain

Create LuckyCrystalBlock.java. It extends Block and overrides onBreak(). When broken, it picks a random number 0–4 and runs the matching event. All the good stuff happens in this one file!

Create the file

Right-click the crystalmod package → New → Java Class → LuckyCrystalBlock

LuckyCrystalBlock.java imports & class header
package com.example.crystalmod; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.entity.ItemEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.server.world.ServerWorld; import net.minecraft.text.Text; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; public class LuckyCrystalBlock extends Block { public LuckyCrystalBlock(Settings settings) { super(settings); } @Override public void onBreak(World world, BlockPos pos, BlockState state, PlayerEntity player) { super.onBreak(world, pos, state, player); // Only run the events on the server, not the client if (!world.isClient()) { ServerWorld serverWorld = (ServerWorld) world; int event = world.getRandom().nextInt(5); // 0, 1, 2, 3, or 4 switch (event) { case 0 -> eventFrogRain(serverWorld, pos, player); case 1 -> eventGemShower(world, pos); case 2 -> eventLightning(serverWorld, pos); case 3 -> eventCrystalKit(player); case 4 -> eventSurprise(serverWorld, player); } } }
ℹ️ Why !world.isClient()? Minecraft runs game logic twice once on the server (real logic) and once on the client (prediction). We cast to ServerWorld to access server-only methods like entity spawning. Always guard world-changing code with this check!
Created LuckyCrystalBlock.java with the class header and onBreak override
Switch statement with 5 cases is in place (methods will be red — that's OK for now!)
🎓 Teacher Note — Step 2The !world.isClient() guard is essential. Without it, all 5 events fire twice — once on the server and once on the client causing double entity spawns and chaos. Students often remove it thinking it's unnecessary. Also: onBreak fires as the block is being broken, before it's fully removed calling super.onBreak() first is required.
🚨 Stuck? Events aren't triggering when the block is broken
  • Block breaks but nothing happens check ModBlocks.java registers it as new LuckyCrystalBlock(...), not new Block(...). The onBreak override only exists on your custom class.
  • Everything happens twice you're missing the if (!world.isClient()) guard. Add it back wrapping the switch statement.
  • Cannot cast World to ServerWorld the cast is safe because we already checked !isClient(). If IntelliJ warns, you can add @SuppressWarnings("unchecked") above the line.
  • Method not found errors the 5 event methods must all be inside the LuckyCrystalBlock class (before its closing } but after the closing } of onBreak).
3
🐸
Event 1 — Frog Rain
Spawn 5 Crystal Frogs around the player
Locked
🐸
Goal — write eventFrogRain()

Add the eventFrogRain method to LuckyCrystalBlock.java. It spawns 5 Crystal Frogs at slightly random positions around the block.

LuckyCrystalBlock.java — add this method
private void eventFrogRain(ServerWorld world, BlockPos pos, PlayerEntity player) { player.sendMessage(Text.literal("🐸 Frog Rain! The frogs have arrived!"), false); for (int i = 0; i < 5; i++) { CrystalFrogEntity frog = ModEntities.CRYSTAL_FROG.create(world); if (frog != null) { // Spawn each frog at a slightly different spot double offsetX = (world.getRandom().nextDouble() - 0.5) * 6; double offsetZ = (world.getRandom().nextDouble() - 0.5) * 6; frog.refreshPositionAndAngles( pos.getX() + offsetX, pos.getY() + 2, pos.getZ() + offsetZ, 0, 0 ); world.spawnEntity(frog); } } }
💡 nextDouble() - 0.5 gives a number between -0.5 and +0.5. Multiplying by 6 spreads frogs up to 3 blocks in any direction. The + 2 on Y spawns them a bit above the block so they fall down!
Added eventFrogRain() method to LuckyCrystalBlock.java
4
💎
Event 2 — Gem Shower
Drop a pile of Crystal Gems at the block location
Locked
💎
Goal — write eventGemShower()

Drop 16 Crystal Gems as item entities at the block's position. Item entities are the spinning items you see floating on the ground they use ItemEntity.

LuckyCrystalBlock.java add this method
private void eventGemShower(World world, BlockPos pos) { ItemStack gems = new ItemStack(ModItems.CRYSTAL_GEM, 16); ItemEntity itemEntity = new ItemEntity( world, pos.getX() + 0.5, pos.getY() + 1.0, pos.getZ() + 0.5, gems ); // Give the items a little upward velocity so they pop out itemEntity.setVelocity( (world.getRandom().nextDouble() - 0.5) * 0.4, 0.5, (world.getRandom().nextDouble() - 0.5) * 0.4 ); world.spawnEntity(itemEntity); }
ℹ️ new ItemStack(item, count) creates a stack of items. The setVelocity call makes the gem stack fly up and scatter sideways — just like when a creeper explodes near chests!
Added eventGemShower() method to LuckyCrystalBlock.java
5
Event 3 — Lightning Strike
Call down lightning at the block's position
Locked
Goal — write eventLightning()

Summon a LightningBoltEntity at the block location. This is a real lightning bolt it'll set fire to things, transform mobs (pigs into piglins!), and make a big flash.

LuckyCrystalBlock.java — add this import at the top
import net.minecraft.entity.LightningEntity;
LuckyCrystalBlock.java add this method
private void eventLightning(ServerWorld world, BlockPos pos) { LightningEntity lightning = new LightningEntity( EntityType.LIGHTNING_BOLT, world ); lightning.refreshPositionAndAngles( pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0 ); world.spawnEntity(lightning); }
⚠️ Lightning is dangerous! It will set fire to nearby flammable blocks. If you want a "cosmetic only" lightning that doesn't damage, call lightning.setCosmetic(true); before spawning it.
You can also import EntityType from net.minecraft.entity.EntityType to get the LIGHTNING_BOLT type. IntelliJ will offer to add it automatically just press Alt+Enter on the red squiggle!
Added LightningEntity import at the top of the file
Added eventLightning() method to LuckyCrystalBlock.java
6
🛡️
Event 4 Crystal Kit
Give the player a Crystal Pickaxe and Crystal Sword
Locked
🛡️
Goal — write eventCrystalKit()

Directly give items to the player's inventory using player.getInventory().insertStack(). This is a reward event if you're lucky enough to roll this, you get free tools!

LuckyCrystalBlock.java add this method
private void eventCrystalKit(PlayerEntity player) { player.sendMessage(Text.literal("💎 Crystal Kit! You got the tools!"), false); // Give a Crystal Pickaxe ItemStack pickaxe = new ItemStack(ModItems.CRYSTAL_PICKAXE); player.getInventory().insertStack(pickaxe); // Give a Crystal Sword ItemStack sword = new ItemStack(ModItems.CRYSTAL_SWORD); player.getInventory().insertStack(sword); // Give a stack of Magic Cookies too why not! ItemStack cookies = new ItemStack(ModItems.MAGIC_COOKIE, 8); player.getInventory().insertStack(cookies); }
💡 insertStack() puts the item in the first available inventory slot. If the inventory is full, the item goes on the ground instead. You could also use giveItemStack() which does the same thing.
Added eventCrystalKit() method to LuckyCrystalBlock.java
7
💥
Event 5 — Surprise Creeper!
Spawn a Creeper right next to the player
Locked
💥
Goal — write eventSurprise()

The "bad luck" event spawn a fully charged Creeper right next to the player. This is the risky event that makes the Lucky Block exciting!

LuckyCrystalBlock.java add this import at the top
import net.minecraft.entity.mob.CreeperEntity;
LuckyCrystalBlock.java — add this method
private void eventSurprise(ServerWorld world, PlayerEntity player) { player.sendMessage(Text.literal("💥 SURPRISE! Run!!"), false); CreeperEntity creeper = (CreeperEntity) EntityType.CREEPER.create(world); if (creeper != null) { // Spawn 2 blocks in front of the player creeper.refreshPositionAndAngles( player.getX() + 2, player.getY(), player.getZ() + 2, 0, 0 ); world.spawnEntity(creeper); } }
🎮 Want it even scarier? You can make the Creeper already exploding by calling creeper.setFuseSpeed(1); before spawning this makes it start the fuse countdown immediately!
Added CreeperEntity import at the top of the file
Added eventSurprise() method to LuckyCrystalBlock.java
8
📋
Register the Lucky Crystal Block
Add it to ModBlocks.java and CrystalMod.java
Locked
📋
Goal — register the block and its item

Open ModBlocks.java and add the Lucky Crystal Block. It glows (luminance 15 max!) and is a bit harder to mine than the Crystal Block. We want it to feel special.

Add to ModBlocks.java

ModBlocks.java add this field
public static final Block LUCKY_CRYSTAL_BLOCK = registerBlock("lucky_crystal_block", new LuckyCrystalBlock(AbstractBlock.Settings.create() .mapColor(MapColor.MAGENTA) .strength(4.0f, 5.0f) .luminance(state -> 15) // maximum glow! .requiresTool() .sounds(BlockSoundGroup.AMETHYST_BLOCK) ));

Add to ModItems.java — registerModItems()

Add the Lucky Crystal Block item to the creative tab. Open ModItems.java and update registerModItems():

ModItems.java update the entries list
ItemGroupEvents.modifyEntriesEvent(ItemGroups.INGREDIENTS).register(entries -> { entries.add(ModItems.CRYSTAL_GEM); entries.add(ModBlocks.CRYSTAL_BLOCK); entries.add(ModBlocks.LUCKY_CRYSTAL_BLOCK); // <-- new! entries.add(ModItems.MAGIC_COOKIE); entries.add(ModItems.CRYSTAL_PICKAXE); entries.add(ModItems.CRYSTAL_SWORD); entries.add(ModItems.CRYSTAL_FROG_SPAWN_EGG); });

Add language entry

en_us.json — add this line
"block.crystalmod.lucky_crystal_block": "Lucky Crystal Block"
Added LUCKY_CRYSTAL_BLOCK to ModBlocks.java
Added it to the creative tab in ModItems.registerModItems()
Added language entry to en_us.json
9
🎨
Textures & Models
Create the Lucky Crystal Block's glowing look
Locked
🎨
Goal — create all the JSON files and texture

The Lucky Crystal Block needs the same set of files as the Crystal Block from Episode 2: a blockstate, two model files, a texture, and a loot table. We'll skip the loot table this time the block's special events ARE the loot!

Texture

Create assets/crystalmod/textures/block/lucky_crystal_block.png a 16×16 pixel image. Make it look special try a vivid pink/purple with a star or "?" pattern on it!

Blockstate JSON

assets/crystalmod/blockstates/lucky_crystal_block.json
{ "variants": { "": { "model": "crystalmod:block/lucky_crystal_block" } } }

Block model JSON

assets/crystalmod/models/block/lucky_crystal_block.json
{ "parent": "block/cube_all", "textures": { "all": "crystalmod:block/lucky_crystal_block" } }

Item model JSON

assets/crystalmod/models/item/lucky_crystal_block.json
{ "parent": "crystalmod:block/lucky_crystal_block" }

Loot table — drops nothing (events are the reward!)

data/crystalmod/loot_table/blocks/lucky_crystal_block.json
{ "type": "minecraft:block", "pools": [] }
ℹ️ An empty "pools": [] means the block drops nothing. The onBreak events handle what the player gets we don't want both a loot drop AND event rewards!
Created lucky_crystal_block.png texture (16×16)
Created blockstate JSON
Created block model JSON
Created item model JSON
Created empty loot table JSON
10
⚒️
Crafting Recipe
Make the Lucky Crystal Block craftable in-game
Locked
⚒️
Goal — create the crafting recipe

The Lucky Crystal Block is crafted from 8 Crystal Blocks surrounding one Crystal Gem an expensive recipe that matches how rare and powerful it should be!

data/crystalmod/recipe/lucky_crystal_block.json
{ "type": "minecraft:crafting_shaped", "pattern": [ "BBB", "BGG", "BBB" ], "key": { "B": { "item": "crystalmod:crystal_block" }, "G": { "item": "crystalmod:crystal_gem" } }, "result": { "id": "crystalmod:lucky_crystal_block", "count": 1 } }
💡 This recipe uses B for Crystal Block and G for Crystal Gem. The center-right position has the gem, all outer 8 slots have Crystal Blocks. 8 Crystal Blocks = 72 Crystal Gems total that's a proper end-game block!
Want it easier to craft? Change the recipe to just 4 Crystal Gems in a 2×2 shapeless recipe! Just change the type to "minecraft:crafting_shapeless" and use "ingredients" instead of "pattern".
Created lucky_crystal_block.json recipe in the recipe folder
11
⛏️
Mining Tag
Make the Lucky Crystal Block mineable with a pickaxe
Locked
⛏️
Goal — add the Lucky Crystal Block to the pickaxe mining tag

Just like in Episode 2, blocks need to be in the mineable/pickaxe tag to be properly mined with a pickaxe. Open the existing pickaxe tag and add the new block.

Update the pickaxe mining tag

Open data/minecraft/tags/block/mineable/pickaxe.json and add the new block:

data/minecraft/tags/block/mineable/pickaxe.json
{ "replace": false, "values": [ "crystalmod:crystal_block", "crystalmod:lucky_crystal_block" ] }
ℹ️ You created this file back in Episode 4 for the Crystal Block. Now we're just adding the Lucky Crystal Block to the same list. "replace": false means we ADD to the vanilla list rather than replacing it.
Added crystalmod:lucky_crystal_block to the pickaxe.json mining tag
12
▶️
Run & Test Everything!
Launch Minecraft and break the Lucky Crystal Block again and again
Locked
🎊
Goal — test all 5 events and celebrate!

Build the mod and test every event. On average you'll need to break ~10 Lucky Crystal Blocks to see all 5 events. Use /gamemode creative to get infinite blocks!

Build and run

  • 1In IntelliJ: Gradle → Tasks → fabric → runClient
  • 2Create a Creative world
  • 3Open inventory → search for "Lucky Crystal Block"
  • 4Place the block and break it with your Crystal Pickaxe
  • 5Watch for the chat message — it tells you which event fired!
  • 6Try to get all 5 events!
⚠️ onBreak not firing? Make sure the block is registered with LuckyCrystalBlock (not just Block) in ModBlocks.java. The custom class is what adds the onBreak override!
💡 Test all events quickly: Use /gamemode creative so you can place and break blocks infinitely. You can also temporarily change nextInt(5) to nextInt(1) to always trigger the same event while testing!
🌟 CHALLENGE Add a 6th event! Can you add a new case to the switch statement? Ideas: give the player a Speed effect, spawn vanilla frogs, drop a Netherite block, or teleport the player 10 blocks up! Change nextInt(5) to nextInt(6) and add case 5.
Mod builds without errors
Lucky Crystal Block appears in creative inventory with correct texture
Breaking the block triggers random events (see chat messages)
All 5 events work: Frog Rain, Gem Shower, Lightning, Crystal Kit, Surprise Creeper
Crafting recipe works (8 Crystal Blocks + 1 Crystal Gem)
🎊 💎 🐸 ⚡ 🍀 🎊
You're a Minecraft Modder!

You built an entire Minecraft mod from scratch a custom item, a glowing block, a magic food, two powerful tools, a brand new animal mob, and a chaotic lucky block with 5 random events. That's real Java programming. That's real game development. You did it!

💎 Episode 1: Crystal Gem
🪨 Episode 2: Crystal Block
🍪 Episode 3: Magic Cookie
⚒️ Episode 4: Crystal Tools
🐸 Episode 5: Crystal Frog
🍀 Episode 6: Lucky Block
🏆 Back to All Episodes →