Skip to content

Block States

Block states store per-block instance data for blocks that need persistent state (e.g., chests with inventory, doors with open/closed state).

  • BlockState: com.hypixel.hytale.server.core.universe.world.meta.BlockState
  • BlockStateRegistry: com.hypixel.hytale.server.core.universe.world.meta.BlockStateRegistry
BlockState base class
public abstract class BlockState implements Component<ChunkStore> {
public Vector3i getPosition();
public Vector3i getBlockPosition();
public Vector3d getCenteredBlockPosition();
public int getBlockX();
public int getBlockY();
public int getBlockZ();
public WorldChunk getChunk();
public BlockType getBlockType();
public int getRotationIndex();
public boolean initialize(BlockType blockType);
public void onUnload();
public void invalidate();
public void markNeedsSave();
public BsonDocument saveToDocument();
public Component<ChunkStore> clone();
public Ref<ChunkStore> getReference();
public Holder<ChunkStore> toHolder();
public static BlockState ensureState(WorldChunk chunk, int x, int y, int z);
public static BlockState getBlockState(Ref<ChunkStore> ref, ComponentAccessor<ChunkStore> accessor);
public static BlockState getBlockState(Holder<ChunkStore> holder);
}

Block states can implement these interfaces to opt into engine-managed behaviors. The system auto-detects them at registration time.

Makes your block state tick every frame. The engine registers a ticking system for any block state class that implements this.

TickableBlockState
public interface TickableBlockState {
void tick(float dt, int index, ArchetypeChunk<ChunkStore> archetypeChunk,
Store<ChunkStore> store, CommandBuffer<ChunkStore> commandBuffer);
}

Called when the block is destroyed (removed from the world). Useful for cleanup like dropping items.

DestroyableBlockState
public interface DestroyableBlockState {
void onDestroy();
}

Sends/removes packets when a chunk loads or unloads for a player. Used for block states that need client-side representation.

SendableBlockState
public interface SendableBlockState {
void sendTo(List<Packet> packets);
void unloadFrom(List<Packet> packets);
default boolean canPlayerSee(PlayerRef player) { return true; }
}

Register custom block states during plugin setup:

Registering a block state type
@Override
protected void setup() {
// Register a block state type
getBlockStateRegistry().registerBlockState(
MyBlockState.class,
"MyPlugin_CustomState",
MyBlockState.CODEC
);
// With associated data class
getBlockStateRegistry().registerBlockState(
MyBlockState.class,
"MyPlugin_CustomState",
MyBlockState.CODEC,
MyStateData.class,
MyStateData.CODEC
);
}
ChestBlockState.java
public class ChestBlockState extends BlockState {
public static final BuilderCodec<ChestBlockState> CODEC =
BuilderCodec.builder(ChestBlockState.class, ChestBlockState::new)
.inherit(BlockState.BASE_CODEC)
.addField(
new KeyedCodec<>("IsOpen", Codec.BOOLEAN),
(state, open) -> state.isOpen = open,
state -> state.isOpen
)
.build();
private boolean isOpen = false;
private ItemContainer inventory;
@Override
public boolean initialize(BlockType blockType) {
this.inventory = new SimpleItemContainer((short) 27);
return true;
}
public void toggle() {
this.isOpen = !this.isOpen;
markNeedsSave();
}
public boolean isOpen() {
return isOpen;
}
public ItemContainer getInventory() {
return inventory;
}
}

getPosition() returns chunk-local coordinates (0-31 on X/Z). getBlockPosition() returns world coordinates. If you need the center of the block (accounting for rotation), use getCenteredBlockPosition().

Position helpers
BlockState state = chunk.getState(localX, y, localZ);
Vector3i local = state.getPosition(); // chunk-local
Vector3i world = state.getBlockPosition(); // world coordinates
Vector3d center = state.getCenteredBlockPosition(); // world center, adjusted for rotation
int wx = state.getBlockX(); // individual world coordinate accessors
int wy = state.getBlockY();
int wz = state.getBlockZ();
Working with block states
// Get state from chunk
WorldChunk chunk = world.getChunk(chunkX, chunkZ);
BlockState state = chunk.getState(localX, y, localZ);
// Ensure state exists (creates if needed)
BlockState state = BlockState.ensureState(chunk, x, y, z);
// Cast to specific type
if (state instanceof ChestBlockState chestState) {
chestState.toggle();
}

Configure block states in JSON:

Block with state configuration
{
"Id": "MyPlugin_Chest",
"State": {
"Type": "MyPlugin_CustomState",
"Data": {
"DefaultCapacity": 27
}
}
}
MethodWhen Called
initialize(BlockType)When state is first created. Return false to reject creation.
onUnload()When chunk is unloaded
invalidate()When block is invalidated
onDestroy()When block is destroyed (requires DestroyableBlockState)
markNeedsSave()Call when state changes
  1. Minimize usage: Only use block states when truly needed for persistent data
  2. Mark saves: Always call markNeedsSave() when modifying state
  3. Handle null: States may be null if block doesn’t require state
  4. Prepare for removal: This API is deprecated; consider ECS alternatives