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.
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 classLuckyCrystalBlockextendsBlock {
publicLuckyCrystalBlock(Settings settings) {
super(settings);
}
@Overridepublic voidonBreak(World world, BlockPos pos, BlockState state, PlayerEntity player) {
super.onBreak(world, pos, state, player);
// Only run the events on the server, not the clientif (!world.isClient()) {
ServerWorld serverWorld = (ServerWorld) world;
int event = world.getRandom().nextInt(5); // 0, 1, 2, 3, or 4switch (event) {
case0 -> eventFrogRain(serverWorld, pos, player);
case1 -> eventGemShower(world, pos);
case2 -> eventLightning(serverWorld, pos);
case3 -> eventCrystalKit(player);
case4 -> 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 voideventFrogRain(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 spotdouble 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 voideventGemShower(World world, BlockPos pos) {
ItemStack gems = newItemStack(ModItems.CRYSTAL_GEM, 16);
ItemEntity itemEntity = newItemEntity(
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
⚠️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 voideventCrystalKit(PlayerEntity player) {
player.sendMessage(Text.literal("💎 Crystal Kit! You got the tools!"), false);
// Give a Crystal PickaxeItemStack pickaxe = newItemStack(ModItems.CRYSTAL_PICKAXE);
player.getInventory().insertStack(pickaxe);
// Give a Crystal SwordItemStack sword = newItemStack(ModItems.CRYSTAL_SWORD);
player.getInventory().insertStack(sword);
// Give a stack of Magic Cookies too why not!ItemStack cookies = newItemStack(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 voideventSurprise(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 finalBlock LUCKY_CRYSTAL_BLOCK = registerBlock("lucky_crystal_block",
newLuckyCrystalBlock(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():
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!
ℹ️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!
💡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:
ℹ️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)
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!