Block Ticking
Block ticking lets blocks do things over time — growing crops, decaying leaves, spreading moss, that kind of thing. Each block type can have a TickProcedure attached in its JSON definition, and the server will call it periodically for every instance of that block.
Package Location
Section titled “Package Location”com.hypixel.hytale.server.core.asset.type.blocktick.config.TickProcedure
TickProcedure
Section titled “TickProcedure”The base class all tick procedures extend. It provides a thread-local random number generator and one abstract method you need to implement:
public abstract class TickProcedure { public static final CodecMapCodec<TickProcedure> CODEC = new CodecMapCodec<>("Type"); public static final BuilderCodec<TickProcedure> BASE_CODEC = BuilderCodec.abstractBuilder(TickProcedure.class).build();
protected static final SplittableRandom BASE_RANDOM = new SplittableRandom(); protected static final ThreadLocal<SplittableRandom> RANDOM = ThreadLocal.withInitial(BASE_RANDOM::split);
protected SplittableRandom getRandom() { return RANDOM.get(); }
public abstract BlockTickStrategy onTick( World world, WorldChunk chunk, int x, int y, int z, int blockId );}The CODEC field is a CodecMapCodec — meaning the JSON "Type" key determines which procedure subclass gets deserialized. Procedures register themselves against this codec during plugin setup.
BlockTickStrategy
Section titled “BlockTickStrategy”The return value from onTick tells the tick system what to do next with that block:
| Strategy | Description |
|---|---|
CONTINUE | Keep ticking this block on future ticks |
IGNORED | Tick was ignored (no procedure found, or ticking disabled) |
SLEEP | Stop ticking this block until something wakes it up |
WAIT_FOR_ADJACENT_CHUNK_LOAD | Pause ticking until neighboring chunks are loaded |
Built-in Procedures
Section titled “Built-in Procedures”The BlockTickPlugin registers two built-in procedures:
TickProcedure.CODEC.register("BasicChance", BasicChanceBlockGrowthProcedure.class, BasicChanceBlockGrowthProcedure.CODEC);TickProcedure.CODEC.register("SplitChance", SplitChanceBlockGrowthProcedure.class, SplitChanceBlockGrowthProcedure.CODEC);So in JSON, you use "Type": "BasicChance" or "Type": "SplitChance" — not the full class names.
BasicChance
Section titled “BasicChance”Rolls a random chance each tick. If it succeeds, the block transforms into a different block.
{ "TickProcedure": { "Type": "BasicChance", "NextId": "MyPlugin_MaturePlant", "ChanceMin": 1, "Chance": 10, "NextTicking": false }}| Property | Type | Description |
|---|---|---|
NextId | string | Block ID to transform into |
ChanceMin | int | Minimum threshold for the random roll to succeed |
Chance | int | Upper bound of the random range (rolls 0 to Chance - 1) |
NextTicking | boolean | If true, the new block continues ticking. If false, it sleeps after transforming |
The chance logic works like this: a random int in [0, Chance) is generated, and if it’s less than ChanceMin, the growth triggers. So ChanceMin: 1, Chance: 10 gives a 1-in-10 chance per tick.
SplitChance
Section titled “SplitChance”Extends BasicChance but instead of a single target block, it picks randomly from a weighted map of possible outcomes.
{ "TickProcedure": { "Type": "SplitChance", "NextIds": { "MyPlugin_Crop_Mature_A": 3, "MyPlugin_Crop_Mature_B": 1 }, "ChanceMin": 1, "Data": 10, "NextTicking": false }}| Property | Type | Description |
|---|---|---|
NextIds | object | Map of block IDs to their relative weights |
ChanceMin | int | Minimum threshold for the random roll |
Data | int | Upper bound of the random range (same role as Chance in BasicChance) |
NextTicking | boolean | Whether the resulting block continues ticking |
In the example above, when the growth triggers, there’s a 3/4 chance of becoming Mature_A and a 1/4 chance of Mature_B.
JSON Configuration
Section titled “JSON Configuration”The TickProcedure field goes directly in your block type JSON:
{ "Id": "MyPlugin_GrowingPlant", "DrawType": "Model", "CustomModel": "models/plant_stage1.blockymodel", "TickProcedure": { "Type": "BasicChance", "NextId": "MyPlugin_MaturePlant", "ChanceMin": 1, "Chance": 10, "NextTicking": false }}{ "Id": "MyPlugin_Crop_Stage1", "DrawType": "Model", "CustomModel": "models/crop_stage1.blockymodel", "TickProcedure": { "Type": "BasicChance", "NextId": "MyPlugin_Crop_Stage2", "ChanceMin": 1, "Chance": 8, "NextTicking": true }}{ "Id": "MyPlugin_DecayingBlock", "TickProcedure": { "Type": "BasicChance", "NextId": "Air", "ChanceMin": 1, "Chance": 20, "NextTicking": false }}How Block Ticking Works
Section titled “How Block Ticking Works”The tick system runs as part of the chunk processing pipeline:
-
Discovery — When a chunk is first generated,
BlockTickPluginscans every block in the chunk. Any block with aTickProcedureon itsBlockTypegets flagged as ticking in itsBlockSection. -
PreTick — The
ChunkBlockTickSystem.PreTicksystem runs first, preparing the chunk’s timing state. -
Ticking — The
ChunkBlockTickSystem.Tickingsystem iterates all flagged blocks, looks up theirTickProcedureviaBlockTickPlugin.getTickProcedure(blockId), and callsonTick. The returnedBlockTickStrategydetermines what happens next. -
Error handling — If a tick procedure throws, the system logs a warning and returns
SLEEPfor that block, preventing it from crashing the tick loop.
Ticking can be enabled or disabled per-world via WorldConfig.isBlockTicking().
Custom Tick Procedures
Section titled “Custom Tick Procedures”You can create your own by extending TickProcedure and registering it:
public class SpreadingMossProcedure extends TickProcedure { public static final BuilderCodec<SpreadingMossProcedure> CODEC = BuilderCodec.builder(SpreadingMossProcedure.class, SpreadingMossProcedure::new, TickProcedure.BASE_CODEC) .addField( new KeyedCodec<>("SpreadChance", Codec.INTEGER), (proc, v) -> proc.spreadChance = v, proc -> proc.spreadChance ) .build();
private int spreadChance = 10;
@Override public BlockTickStrategy onTick(World world, WorldChunk chunk, int x, int y, int z, int blockId) { if (getRandom().nextInt(spreadChance) != 0) { return BlockTickStrategy.CONTINUE; }
BlockType stone = BlockType.getAssetMap().get("Rock_Stone"); BlockType mossy = BlockType.getAssetMap().get("MossyStone");
int nx = x + getRandom().nextInt(3) - 1; int nz = z + getRandom().nextInt(3) - 1;
if (world.getBlockType(nx, y, nz) == stone) { world.setBlock(nx, y, nz, mossy); }
return BlockTickStrategy.CONTINUE; }}Then register it in your plugin’s setup():
TickProcedure.CODEC.register("SpreadingMoss", SpreadingMossProcedure.class, SpreadingMossProcedure.CODEC);And use it in JSON:
{ "Id": "MyPlugin_MossyBlock", "TickProcedure": { "Type": "SpreadingMoss", "SpreadChance": 15 }}Random Tick System
Section titled “Random Tick System”The RandomTickPlugin registers a RandomTickSystem that processes random blocks per chunk section, checking BlockType.getRandomTickProcedure() to execute procedures. It introduces two built-in procedures:
| Procedure | Description |
|---|---|
ChangeIntoBlock | Changes a block into a specified target block type |
SpreadTo | Spreads blocks to neighbors (e.g., grass spreading to dirt), with configurable directions, Y range, light level requirements, and revert behavior |
RandomTick Configuration
Section titled “RandomTick Configuration”The RandomTick resource controls tick rates per chunk:
| Field | Default | Description |
|---|---|---|
blocksPerSectionPerTickStable | 1 | Blocks ticked per section for stable ticks (deterministic hash) |
blocksPerSectionPerTickUnstable | 3 | Blocks ticked per section for unstable ticks (random) |
SpreadTo Procedure
Section titled “SpreadTo Procedure”The SpreadTo procedure supports spreading blocks like grass or mycelium:
| Field | Default | Description |
|---|---|---|
SpreadDirections | — | Directions the block can spread |
MinY / MaxY | — | Y range constraints for spreading |
AllowedTag | — | Tag the target block must have to accept spread |
RequireEmptyAboveTarget | — | Whether the block above the target must be empty |
RequiredLightLevel | 6 | Minimum light level for spreading |
RevertBlock | — | Block to revert to when covered |
Related
Section titled “Related”- Block Types — BlockType properties and configuration
- Block States — Per-block instance data