Adding Blocks
Blocks in LCE are called tiles. Every block in the game (stone, dirt, furnaces, doors, you name it) is either a subclass or direct instance of the Tile class defined in Minecraft.World/Tile.h. This guide walks you through creating a new tile from scratch, based on how existing tiles work in the source code.
Overview of the Tile System
Section titled “Overview of the Tile System”The base Tile class gives you:
- A numeric ID (
int id) that uniquely identifies the tile in the world - Material (
Material *material) that controls interaction behavior (flammable, solid, etc.) - Properties like destroy speed, explosion resistance, sound type, and light emission
- Render shape constants (
SHAPE_BLOCK,SHAPE_CROSS_TEXTURE,SHAPE_STAIRS, etc.) - Virtual methods for behavior:
tick(),use(),attack(),neighborChanged(),onPlace(),onRemove(), and more
All tiles are stored in the static array Tile::tiles[4096]. The Tile constructor automatically registers itself:
// From Tile::_init()Tile::tiles[id] = this;this->id = id;Step 1: Create a Tile Subclass
Section titled “Step 1: Create a Tile Subclass”Create two new files in Minecraft.World/: a header and an implementation file.
MyCustomTile.h
#pragma once#include "Tile.h"
class Random;class Level;class Player;
class MyCustomTile : public Tile{public: MyCustomTile(int id);
// Override behavior as needed virtual void tick(Level *level, int x, int y, int z, Random *random); virtual bool use(Level *level, int x, int y, int z, shared_ptr<Player> player, int clickedFace, float clickX, float clickY, float clickZ, bool soundOnly = false); virtual int getResource(int data, Random *random, int playerBonusLevel); virtual int getResourceCount(Random *random);};MyCustomTile.cpp
#include "stdafx.h"#include "MyCustomTile.h"#include "net.minecraft.world.item.h"#include "net.minecraft.world.level.h"#include "net.minecraft.world.entity.player.h"
MyCustomTile::MyCustomTile(int id) : Tile(id, Material::stone){ // The Tile constructor calls _init(), which: // - Sets tiles[id] = this // - Defaults: destroySpeed=0, explosionResistance=0 // - Sets soundType = SOUND_NORMAL, friction = 0.6}
void MyCustomTile::tick(Level *level, int x, int y, int z, Random *random){ // Called each tick if setTicking(true) was used during registration. // Example: check neighbors, spawn particles, change state, etc.}
bool MyCustomTile::use(Level *level, int x, int y, int z, shared_ptr<Player> player, int clickedFace, float clickX, float clickY, float clickZ, bool soundOnly){ // Called when the player right-clicks/uses the block. // Return true if the interaction was handled. return false;}
int MyCustomTile::getResource(int data, Random *random, int playerBonusLevel){ // What item ID drops when the block is mined. // Return the tile's own ID to drop itself. return id;}
int MyCustomTile::getResourceCount(Random *random){ // How many items drop. Default is 1. return 1;}The Tile constructor takes up to three parameters:
Tile(int id, Material *material, bool isSolidRender = true);The third parameter isSolidRender controls whether the block counts as opaque for lighting and rendering. Set it to false for transparent or non-full blocks (like glass or fences).
Step 2: Choose a Material
Section titled “Step 2: Choose a Material”The Material class determines the fundamental behavior of a block. Here are the available materials from Material.h:
| Material | Description |
|---|---|
Material::stone | Standard solid block |
Material::wood | Burnable, wooden |
Material::metal | Metal blocks (anvil, iron block) |
Material::glass | Fragile, transparent |
Material::cloth | Soft (wool, carpet) |
Material::sand | Gravity-affected blocks |
Material::dirt | Dirt/gravel type |
Material::plant | Vegetation |
Material::water | Water blocks |
Material::lava | Lava blocks |
Material::leaves | Leaf blocks |
Material::portal | Portal blocks |
Material::fire | Fire |
Pass the material to the Tile constructor:
MyCustomTile::MyCustomTile(int id) : Tile(id, Material::stone)Step 3: Register in Tile::staticCtor()
Section titled “Step 3: Register in Tile::staticCtor()”Open Minecraft.World/Tile.cpp and add your registration inside Tile::staticCtor(). You’ll also need a static pointer declaration in Tile.h.
In Tile.h, add a forward declaration and static pointer:
// Forward declaration near the top of Tile.hclass MyCustomTile;
// Inside the Tile class, with the other static tile pointersstatic MyCustomTile *myCustomTile;In Tile.cpp, add the static definition and registration:
// Static definition (near the other static Tile* definitions)MyCustomTile *Tile::myCustomTile = NULL;
// Inside Tile::staticCtor(), add with the other registrations:Tile::myCustomTile = (MyCustomTile *)(new MyCustomTile(160)) ->setDestroyTime(3.0f) ->setExplodeable(10) ->setSoundType(Tile::SOUND_STONE) ->setTextureName(L"myCustomTile") ->setDescriptionId(IDS_TILE_MY_CUSTOM) ->setUseDescriptionId(IDS_DESC_MY_CUSTOM);Pick an unused ID. See Getting Started for help finding available IDs.
Step 4: Set Properties
Section titled “Step 4: Set Properties”All property setters return Tile*, so you can chain them together in the builder pattern. Here’s what’s available:
Destroy Time and Resistance
Section titled “Destroy Time and Resistance”->setDestroyTime(3.0f) // How long to mine (seconds-ish). 0 = instant break.->setExplodeable(10) // Explosion resistance. Higher = more resistant.->setIndestructible() // Cannot be broken (like bedrock, uses -1.0f internally)Real examples from the source:
- Dirt:
setDestroyTime(0.5f) - Stone:
setDestroyTime(1.5f),setExplodeable(10) - Obsidian:
setDestroyTime(50.0f),setExplodeable(2000) - Bedrock:
setIndestructible(),setExplodeable(6000000)
Sound Type
Section titled “Sound Type”->setSoundType(Tile::SOUND_STONE)Available sound types:
| Constant | Used For |
|---|---|
SOUND_NORMAL | Default / redstone dust |
SOUND_WOOD | Wood, torches, fire |
SOUND_GRAVEL | Gravel, dirt, farmland |
SOUND_GRASS | Grass, leaves, flowers, TNT |
SOUND_STONE | Stone, ores, bricks |
SOUND_METAL | Iron/gold/diamond blocks, rails |
SOUND_GLASS | Glass, ice, portals |
SOUND_CLOTH | Wool, carpet, snow, cactus |
SOUND_SAND | Sand, soul sand |
SOUND_SNOW | Snow |
SOUND_LADDER | Ladders |
SOUND_ANVIL | Anvils |
->setLightEmission(15 / 16.0f) // How much light the block emits (0.0 to 1.0)->setLightBlock(3) // How much light the block absorbs (0-255)Real examples:
- Torch:
setLightEmission(15 / 16.0f) - Glowstone:
setLightEmission(1.0f) - Lava:
setLightEmission(1.0f),setLightBlock(255) - Water:
setLightBlock(3) - Leaves:
setLightBlock(1)
Render Shape
Section titled “Render Shape”Override getRenderShape() in your subclass to return one of the SHAPE_* constants:
int MyCustomTile::getRenderShape(){ return Tile::SHAPE_BLOCK; // Standard full cube}Key shape constants defined in Tile.h:
| Constant | Value | Used For |
|---|---|---|
SHAPE_INVISIBLE | -1 | Air, moving pistons |
SHAPE_BLOCK | 0 | Standard full cube |
SHAPE_CROSS_TEXTURE | 1 | Flowers, saplings, tall grass |
SHAPE_TORCH | 2 | Torches |
SHAPE_FIRE | 3 | Fire |
SHAPE_WATER | 4 | Water/lava |
SHAPE_DOOR | 7 | Doors |
SHAPE_STAIRS | 10 | Stairs |
SHAPE_FENCE | 11 | Fences |
SHAPE_CACTUS | 13 | Cactus |
SHAPE_BED | 14 | Beds |
Other Properties
Section titled “Other Properties”->setTicking(true) // Enable tick() updates for this tile->setNotCollectStatistics() // Exclude from statistics tracking->sendTileData() // Send data bits to clients (for blocks with aux data)->disableMipmap() // Disable texture mipmapping (used for cross-texture plants)->setBaseItemTypeAndMaterial(Item::eBaseItemType_block, Item::eMaterial_stone) // Set creative inventory categoryCollision Shape
Section titled “Collision Shape”Override setShape() to define a non-full bounding box:
// In your constructor or updateDefaultShape():setShape(0.0f, 0.0f, 0.0f, 1.0f, 0.5f, 1.0f); // Half-slab heightThe six parameters are: x0, y0, z0, x1, y1, z1 (in block-space, 0.0 to 1.0).
Step 5: Add Behavior
Section titled “Step 5: Add Behavior”Tick Updates
Section titled “Tick Updates”If you called setTicking(true) during registration, the tick() method will be called periodically:
void MyCustomTile::tick(Level *level, int x, int y, int z, Random *random){ // Check conditions, modify neighbors, etc.}Player Interaction (Right-Click)
Section titled “Player Interaction (Right-Click)”Override use() to handle right-click interactions:
bool MyCustomTile::use(Level *level, int x, int y, int z, shared_ptr<Player> player, int clickedFace, float clickX, float clickY, float clickZ, bool soundOnly){ // Open a GUI, toggle state, etc. // Return true if the interaction was consumed. return true;}Note: the soundOnly parameter is a 4J addition. When true, the game only wants you to play the interaction sound without actually doing anything. Check this flag if your use() has side effects.
Player Attack (Left-Click)
Section titled “Player Attack (Left-Click)”Override attack() for left-click behavior:
void MyCustomTile::attack(Level *level, int x, int y, int z, shared_ptr<Player> player){ // Handle left-click (e.g., note blocks play sound on attack)}Block Drops
Section titled “Block Drops”Control what drops when the block is mined:
int MyCustomTile::getResource(int data, Random *random, int playerBonusLevel){ // Return the item ID to drop. // Return the tile ID itself to drop the block: return id; // Or return a different item, like OreTile does: // if (id == Tile::coalOre_Id) return Item::coal_Id;}
int MyCustomTile::getResourceCount(Random *random){ // How many items drop. Can use random for variable drops. return 1;}For blocks that also drop XP, override spawnResources():
void MyCustomTile::spawnResources(Level *level, int x, int y, int z, int data, float odds, int playerBonus){ Tile::spawnResources(level, x, y, z, data, odds, playerBonus); int xpAmount = Mth::nextInt(level->random, 2, 5); popExperience(level, x, y, z, xpAmount);}Neighbor Updates
Section titled “Neighbor Updates”React when an adjacent block changes:
void MyCustomTile::neighborChanged(Level *level, int x, int y, int z, int type){ // 'type' is the ID of the neighboring tile that changed. // Used by redstone, rails, torches, etc.}Placement and Removal
Section titled “Placement and Removal”void MyCustomTile::onPlace(Level *level, int x, int y, int z){ // Called when the block is placed in the world.}
void MyCustomTile::onRemove(Level *level, int x, int y, int z, int id, int data){ // Called when the block is removed from the world. // 'id' and 'data' are what the block was before removal.}Entity Stepping
Section titled “Entity Stepping”Override stepOn() to react when an entity walks on the block:
void MyCustomTile::stepOn(Level *level, int x, int y, int z, shared_ptr<Entity> entity){ // E.g., FarmTile converts to dirt when stomped on}Placement Data
Section titled “Placement Data”Override getPlacedOnFaceDataValue() to set metadata based on how the block was placed:
int MyCustomTile::getPlacedOnFaceDataValue(Level *level, int x, int y, int z, int face, float clickX, float clickY, float clickZ, int itemValue){ // 'face' is which face of the adjacent block was clicked // Return the data value for the placed block return 0;}Placed-By Context
Section titled “Placed-By Context”Override setPlacedBy() to get the entity that placed the block:
void MyCustomTile::setPlacedBy(Level *level, int x, int y, int z, shared_ptr<Mob> by){ // Used by pistons, skulls, etc. to set facing based on player direction}Step 6: Add the TileItem
Section titled “Step 6: Add the TileItem”For tiles with IDs 0 through 255, the end of Tile::staticCtor() automatically creates a TileItem for any tile that doesn’t already have a custom item:
for (int i = 0; i < 256; i++){ if (Tile::tiles[i] != NULL && Item::items[i] == NULL) { Item::items[i] = new TileItem(i - 256); Tile::tiles[i]->init(); }}If your tile needs special item behavior (multiple sub-types, custom icons, colored variants), register a custom TileItem subclass before this loop runs. The codebase has plenty of examples:
// Wool has colored variantsItem::items[Tile::cloth_Id] = ( new ClothTileItem(Tile::cloth_Id - 256) ) ->setTextureName(L"cloth") ->setDescriptionId(IDS_TILE_CLOTH);
// Logs have tree-type variantsItem::items[Tile::treeTrunk_Id] = ( new TreeTileItem(Tile::treeTrunk_Id - 256, treeTrunk) ) ->setTextureName(L"log") ->setDescriptionId(IDS_TILE_LOG);
// Slabs need special stacking behaviorItem::items[Tile::stoneSlabHalf_Id] = ( new StoneSlabTileItem( Tile::stoneSlabHalf_Id - 256, Tile::stoneSlabHalf, Tile::stoneSlab, false) ) ->setTextureName(L"stoneSlab") ->setDescriptionId(IDS_TILE_STONESLAB);Step 7: Creative Inventory
Section titled “Step 7: Creative Inventory”To make your tile show up in the creative inventory, set its base item type and material during registration:
->setBaseItemTypeAndMaterial(Item::eBaseItemType_block, Item::eMaterial_stone)The eBaseItemType enum controls which tab the item appears on, and eMaterial controls sorting within that tab. Here are the common base item types:
| Type | Usage |
|---|---|
eBaseItemType_block | Solid decorative blocks |
eBaseItemType_structblock | Structural blocks (bricks, nether brick) |
eBaseItemType_structwoodstuff | Wooden planks |
eBaseItemType_stairs | Stair blocks |
eBaseItemType_slab / eBaseItemType_halfslab | Slab blocks |
eBaseItemType_fence | Fences and walls |
eBaseItemType_door | Doors and trapdoors |
eBaseItemType_torch | Torches and light sources |
eBaseItemType_device | Functional blocks (furnace, workbench, enchanting table) |
eBaseItemType_rail | Rail blocks |
eBaseItemType_button | Buttons |
eBaseItemType_pressureplate | Pressure plates |
eBaseItemType_piston | Pistons |
eBaseItemType_chest | Chests |
Block type recipes
Section titled “Block type recipes”Below are guides for every major type of block you can add. Each one builds on the basic tile pattern from above.
Transparent blocks (TransparentTile)
Section titled “Transparent blocks (TransparentTile)”For blocks like glass, ice, or portals that let you see through them.
#include "TransparentTile.h"
class MyGlassTile : public TransparentTile{public: MyGlassTile(int id) : TransparentTile(id, Material::glass, false) // ^material ^allowSame { }};TransparentTile extends Tile and gives you:
allowSameflag: When false, adjacent faces between two of the same block are hidden (like glass panes not rendering where they touch). When true, all faces renderblocksLight(): Returns false by defaultisSolidRender(): Returns falseshouldRenderFace(): Checks theallowSameflag to decide
The constructor’s third parameter is isSolidRender, which defaults to false. The allowSame flag is passed as the third argument to the TransparentTile constructor (not the Tile one).
Overlay transparent blocks (HalfTransparentTile)
Section titled “Overlay transparent blocks (HalfTransparentTile)”For blocks with an overlay texture on top of a base texture (like stained glass or colored ice).
#include "HalfTransparentTile.h"
class MyOverlayTile : public HalfTransparentTile{public: MyOverlayTile(int id) : HalfTransparentTile(id, L"myOverlayTexture", Material::glass, false) // ^overlay texture ^material ^allowSame { }};HalfTransparentTile is a separate class from TransparentTile. Both extend Tile directly. The difference is the overlay texture field (a wstring) that gets registered through registerIcons(). ChunkRebuildData is a friend class so it can access the overlay texture during rendering.
Blocks with persistent data (EntityTile)
Section titled “Blocks with persistent data (EntityTile)”For blocks that need to store data beyond the 4-bit metadata, like chests, furnaces, or signs.
#include "EntityTile.h"#include "TileEntity.h"
class MyStorageTile : public EntityTile{public: MyStorageTile(int id) : EntityTile(id, Material::wood) { }
// Required: create the TileEntity that holds your data virtual shared_ptr<TileEntity> newTileEntity(Level *level) { return make_shared<MyStorageTileEntity>(); }};EntityTile extends Tile and adds:
newTileEntity(Level *): Pure virtual. You must return ashared_ptr<TileEntity>. This is called when the block is placedonPlace(): Automatically registers the TileEntity with the levelonRemove(): Automatically unregisters the TileEntitytriggerEvent(): Forwards block events to the TileEntity
Your TileEntity subclass must implement clone() (a pure virtual added by 4J). It also gets load() and save() for NBT serialization, and optionally tick() for per-tick updates.
The TileEntity has a RenderRemoveStage enum (4J addition) for managing render state:
e_RenderRemoveStageKeep: Normal statee_RenderRemoveStageFlaggedAtChunk: Marked for removal at chunk levele_RenderRemoveStageRemove: Ready to be removed from render
Gravity-affected blocks (HeavyTile)
Section titled “Gravity-affected blocks (HeavyTile)”For blocks that fall when unsupported, like sand and gravel.
#include "HeavyTile.h"
class MyFallingTile : public HeavyTile{public: MyFallingTile(int id) : HeavyTile(id) // uses Material::sand by default { }
// Optional: do something when the block lands virtual void onLand(Level *level, int xt, int yt, int zt, int data) { // e.g., anvils deal damage, concrete powder checks for water }
// Optional: do something while falling virtual void falling(shared_ptr<FallingTile> entity) { // Modify the falling entity (e.g., set damage values) }};HeavyTile extends Tile and provides:
instaFall: A static bool. When true, blocks fall instantly instead of spawning aFallingTileentity (used during world generation)checkSlide(): Called fromonPlace()andneighborChanged(). Checks if the space below is free and starts the fallisFree(): Static method that checks if a position can be fallen throughgetTickDelay(): Override this to control how fast the block starts falling after being unsupported- Two constructor overloads: one takes just an ID (defaults to
Material::sand), the other takes an ID and explicitMaterial
Plant blocks (Bush)
Section titled “Plant blocks (Bush)”For blocks that sit on top of other blocks and break when unsupported, like flowers, saplings, and tall grass.
#include "Bush.h"
class MyPlantTile : public Bush{public: MyPlantTile(int id) : Bush(id) // uses Material::plant by default { }
// Optional: control what blocks this can be placed on virtual bool mayPlaceOn(int tile) { // Default only allows grass and dirt return tile == Tile::grass_Id || tile == Tile::dirt_Id; }};Bush extends Tile and gives you:
mayPlaceOn(tile): Virtual method for controlling valid support blocks. Default is grass/dirtmayPlace(level, x, y, z): ChecksmayPlaceOn()for the block belowcanSurvive(level, x, y, z): Similar tomayPlacebut used for ongoing checkscheckAlive(): Called fromneighborChanged()andtick(). Drops the block ifcanSurvive()failsneighborChanged(): CallscheckAlive()automaticallygetRenderShape(): ReturnsSHAPE_CROSS_TEXTUREby defaultgetAABB(): Returns null (no collision box)isSolidRender(): Returns falseisCubeShaped(): Returns falseblocksLight(): Returns false
Crop blocks (CropTile)
Section titled “Crop blocks (CropTile)”For blocks that grow through stages, like wheat, carrots, and potatoes.
#include "CropTile.h"
class MyCustomCrop : public CropTile{public: MyCustomCrop(int id) : CropTile(id) {}
// What seed item drops virtual int getBaseSeedId() { return Item::mySeeds_Id; }
// What the grown plant drops virtual int getBasePlantId() { return Item::myHarvest_Id; }};CropTile extends Bush and adds:
tick(): Handles growth. Each tick has a chance to advance the growth stage based ongetGrowthSpeed()getGrowthSpeed(): Calculates growth rate based on farmland below, nearby water, and light levelgrowCropsToMax(): Instantly sets the crop to full growth (used by bone meal)getBaseSeedId()/getBasePlantId(): Virtual methods controlling dropsspawnResources(): Drops seeds at any stage, plus the plant item only at full growthmayPlaceOn(): Only allows farmlandgetRenderShape(): ReturnsSHAPE_CROSS_TEXTURE
Crop data values 0-7 represent growth stages, with 7 being fully grown.
Directional blocks (DirectionalTile)
Section titled “Directional blocks (DirectionalTile)”For blocks that face a direction, like beds, pumpkins, fence gates, and repeaters.
#include "DirectionalTile.h"
class MyDirectionalTile : public DirectionalTile{public: MyDirectionalTile(int id) : DirectionalTile(id, Material::wood) { }};DirectionalTile extends Tile and provides:
DIRECTION_MASK = 0x3: Bottom 2 bits of metadata store the direction (0-3)DIRECTION_INV_MASK = 0xC: Upper 2 bits, available for other datagetDirection(data): Static method to extract the direction from metadata
Blocks that use DirectionalTile include BedTile, PumpkinTile, FenceGateTile, DiodeTile (repeaters), and CocoaTile. You’ll typically set the direction in setPlacedBy() based on the player’s facing.
Stair blocks (StairTile)
Section titled “Stair blocks (StairTile)”Stairs delegate almost everything to a base tile. This is an unusual pattern where the stair block wraps another block.
#include "StairTile.h"
// In Tile::staticCtor():Tile::myStairs = (new StairTile(161, Tile::myBaseTile, 0)) ->setDestroyTime(2.0f) ->setSoundType(Tile::SOUND_STONE);StairTile extends Tile and stores a pointer to a base tile and basedata. Nearly every method delegates to the base tile:
getTexture(),getLightColor(),getBrightness(),getExplosionResistance(),getRenderLayer(),getTickDelay()all call the base tile’s versionanimateTick(),attack(),destroy(),handleEntityInside(),onPlace(),onRemove(),stepOn(),tick(),use(),wasExploded(),setPlacedBy()all delegate too- The stair handles its own shape calculation through
setBaseShape(),setStepShape(), andsetInnerPieceShape()
Key constants:
UPSIDEDOWN_BIT = 4: When set in metadata, the stair is upside-downDIR_EAST = 0,DIR_WEST = 1,DIR_SOUTH = 2,DIR_NORTH = 3DEAD_SPACES[8][2]: Array controlling which parts of the stair shape are “dead” (cut away)
Tile is a friend class of StairTile.
Slab blocks (HalfSlabTile)
Section titled “Slab blocks (HalfSlabTile)”Slabs have a special half/full system where two half-slabs combine into a full block.
#include "HalfSlabTile.h"
class MySlabTile : public HalfSlabTile{public: MySlabTile(int id, bool fullSize) : HalfSlabTile(id, fullSize, Material::stone) { }
// Required: return the name for each sub-type virtual int getAuxName(int auxValue) { return IDS_TILE_MYSLAB; }};HalfSlabTile extends Tile and provides:
TYPE_MASK = 7: Bottom 3 bits store the slab variant (up to 8 types)TOP_SLOT_BIT = 8: When set, the slab is in the top half of the blockfullSize: When true, this is the double-slab variantgetAuxName(): Pure virtual. Returns a string ID for each sub-typegetPlacedOnFaceDataValue(): SetsTOP_SLOT_BITwhen placed on the underside of a blockgetSpawnResourcesAuxValue(): Strips theTOP_SLOT_BITfor dropsisHalfSlab(): Static method to check if a tile ID is a slab
You need to register two tiles: the half slab and the full slab. Then register a StoneSlabTileItem (or similar) that handles combining halves.
Liquid blocks (LiquidTile)
Section titled “Liquid blocks (LiquidTile)”For blocks that flow, like water and lava. This is a complex system with two subclasses.
#include "LiquidTileDynamic.h"#include "LiquidTileStatic.h"LiquidTile extends Tile and provides the base for all liquid behavior:
getFlow(): Calculates flow direction as aVec3getDepth()/getRenderedDepth(): Liquid depth from metadatagetHeight(): Static method converting data to rendered heightgetSlopeAngle(): Static method for the visual slopefizz(): Plays the steam sound when lava meets waterhandleEntityInside(): Pushes entities based on flow directiongetTickDelay(): Water ticks every 5, lava every 30 (in the Overworld)
The liquid system has two concrete classes:
LiquidTileDynamic: Active, flowing liquid. Handles spread logic:
trySpreadTo(): Tries to flow into an adjacent blockgetSlopeDistance()/getSpread(): Pathfinding for flow directioncanSpreadTo()/isWaterBlocking(): Checks if flow is blockedsetStatic(): Converts toLiquidTileStaticwhen flow stabilizes- 4J addition:
iterativeTick()with adeque<LiquidTickData>for iterative processing instead of recursive
LiquidTileStatic: Stable liquid at rest:
setDynamic(): Converts back toLiquidTileDynamicwhen disturbedisFlammable(): Checks if lava should ignite nearby blocks
Redstone blocks (RedStoneDustTile)
Section titled “Redstone blocks (RedStoneDustTile)”For blocks that carry redstone signals.
#include "RedStoneDustTile.h"RedStoneDustTile extends Tile directly and provides:
shouldSignalflag: Temporarily disabled during power calculation to prevent feedback loopstoUpdateset:unordered_set<TilePos>tracking positions that need power recalculationupdatePowerStrength(): Two overloads. Propagates signal strength through the wire networkcheckCornerChangeAt(): Handles vertical wire connections around block cornersgetSignal()/getDirectSignal(): Returns signal strength for a given faceisSignalSource(): Returns true (this block is a signal source)shouldConnectTo()/shouldReceivePowerFrom(): Static methods for determining wire connections
Redstone connections use 4 texture variants: TEXTURE_CROSS, TEXTURE_LINE, TEXTURE_CROSS_OVERLAY, TEXTURE_LINE_OVERLAY.
Piston blocks (PistonBaseTile)
Section titled “Piston blocks (PistonBaseTile)”Pistons are among the most complex blocks in the game.
#include "PistonBaseTile.h"PistonBaseTile extends Tile and has:
MAX_PUSH_DEPTH = 12: Maximum number of blocks a piston can pushEXTENDED_BIT = 8: Metadata flag for extended stateisSticky: Whether this is a sticky pistonignoreUpdate(): Thread-local static (TLS) to prevent recursive neighbor updatescheckIfExtend(): Checks if the piston should extend based on redstone signalsgetNeighborSignal(): Checks all faces except the piston face for signalscanPush(): Static method checking if the block chain can be pushedisPushable(): Static method checking if a single block can be pushedcreatePush(): Executes the push, moving up to 12 blocksstopSharingIfServer(): 4J addition for multiplayer block sharingTRIGGER_EXTEND = 0,TRIGGER_CONTRACT = 1: Block event typesgetFacing()/isExtended(): Static helpers for reading metadatagetNewFacing(): Static method to calculate facing from player position
Redstone torch blocks (NotGateTile)
Section titled “Redstone torch blocks (NotGateTile)”The redstone torch has burnout prevention logic.
Key details from the source:
Toggleinner class stores position + game time of each togglerecentToggles: Static map tracking recent toggles per positionRECENT_TOGGLE_TIMER = 60: Ticks before a toggle record expiresMAX_RECENT_TOGGLES = 8: Maximum toggles before burnout
Tripwire blocks (TripWireSourceTile)
Section titled “Tripwire blocks (TripWireSourceTile)”WIRE_DIST_MAX = 42: Maximum tripwire length in blocks- Uses bitmask constants:
MASK_DIR,MASK_ATTACHED,MASK_POWERED calculateState(): Scans the wire in both directions to update state
Rail blocks (RailTile)
Section titled “Rail blocks (RailTile)”Rails have an inner Rail class that manages connections between rail segments:
RAIL_DATA_BIT = 8: Powered rail activation bitRAIL_DIRECTION_MASK = 7: Bottom 3 bits store rail shape (straight, curve, slope)- The inner
Railclass handles connection logic, checking neighbor rails and updating shapes
Complete Example: Adding a Ruby Ore
Section titled “Complete Example: Adding a Ruby Ore”Here’s a full walkthrough based on the existing OreTile pattern.
RubyOreTile.h
#pragma once#include "Tile.h"
class Random;
class RubyOreTile : public Tile{public: RubyOreTile(int id); virtual int getResource(int data, Random *random, int playerBonusLevel); virtual int getResourceCount(Random *random); virtual void spawnResources(Level *level, int x, int y, int z, int data, float odds, int playerBonusLevel);};RubyOreTile.cpp
#include "stdafx.h"#include "RubyOreTile.h"#include "net.minecraft.world.item.h"#include "net.minecraft.world.level.h"
RubyOreTile::RubyOreTile(int id) : Tile(id, Material::stone){}
int RubyOreTile::getResource(int data, Random *random, int playerBonusLevel){ // Drop your custom ruby item (assuming you registered it) return Item::ruby_Id; // You would define this in Item.h}
int RubyOreTile::getResourceCount(Random *random){ return 1;}
void RubyOreTile::spawnResources(Level *level, int x, int y, int z, int data, float odds, int playerBonusLevel){ Tile::spawnResources(level, x, y, z, data, odds, playerBonusLevel); // Drop experience orbs int xpAmount = Mth::nextInt(level->random, 2, 5); popExperience(level, x, y, z, xpAmount);}Registration in Tile.h:
class RubyOreTile;// ...static Tile *rubyOre;static const int rubyOre_Id = 160; // Pick an unused IDRegistration in Tile.cpp:
Tile *Tile::rubyOre = NULL;
// Inside Tile::staticCtor():Tile::rubyOre = (new RubyOreTile(160)) ->setDestroyTime(3.0f) ->setExplodeable(5) ->setSoundType(Tile::SOUND_STONE) ->setTextureName(L"oreRuby") ->setDescriptionId(IDS_TILE_RUBY_ORE) ->setUseDescriptionId(IDS_DESC_RUBY_ORE);Add your new .h and .cpp files to cmake/Sources.cmake, rebuild, and the block will be available in-game.
Related Guides
Section titled “Related Guides”- Getting Started for environment setup and the staticCtor pattern
- Adding Items to create matching items for your blocks
- Blocks Reference for the full Tile class documentation
- Custom World Generation to make your blocks generate in the world