How-to: Add Map Data Blocks and Map Content
Map data blocks define what happens at specific positions inside a procedurally generated map dimension. This guide covers registering custom blocks, tagging them, and wiring them into the map generation pipeline.
Understand the data flow
When the map chunk generator processes a chunk, it reads NBT-encoded MapDataBlock entries
placed by the structure template. Each entry contains a key string that maps to a registered
MapDataBlock by GUID. The block's processImplementationINTERNAL method is called once, at
which point it can place blocks, spawn entities, store data, etc.
Option A: Extend MapDataBlock directly (hardcoded)
public class MySpawnerBlock extends MapDataBlock {
public static final String SERIALIZER = "my_mod:mob_spawner";
public String mob_id = "minecraft:zombie";
public MySpawnerBlock() {
this.serializer = SERIALIZER;
}
@Override
public String GUID() { return id; }
@Override
public int Weight() { return weight; }
@Override
public ExileRegistryType getExileRegistryType() {
return LibDatabase.MAP_DATA_BLOCK;
}
@Override
public void processImplementationINTERNAL(
String key, BlockPos pos, Level world, CompoundTag nbt, MapBlockCtx ctx) {
EntityType<?> type = ForgeRegistries.ENTITY_TYPES
.getValue(new ResourceLocation(mob_id));
if (type == null) return;
Entity entity = type.create(world);
if (entity != null) {
entity.moveTo(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5);
world.addFreshEntity(entity);
}
}
}
Register using an ExileKeyHolder:
public class MyMapBlockHolder extends ExileKeyHolder<MapDataBlock> {
public MyMapBlockHolder(String modid) {
super(modid, LibDatabase.MAP_DATA_BLOCK);
}
public ExileKey<MapDataBlock, KeyInfo> ZOMBIE_SPAWNER = ExileKey.ofId(
this, "my_mod:zombie_spawner",
x -> {
var block = new MySpawnerBlock();
block.id = x.GUID();
block.mob_id = "minecraft:zombie";
block.weight = 1000;
return block;
}
);
}
Option B: Use the built-in SetBlockMB
SetBlockMB replaces the block at the data block's position with another block. Use it for
terrain decoration, triggers, or traps:
public ExileKey<MapDataBlock, KeyInfo> LAVA_TRAP = ExileKey.ofId(
this, "my_mod:lava_trap",
x -> new SetBlockMB(x.GUID(), "minecraft:lava") // places lava
);
Option C: JSON datapack
MapDataBlock uses a custom-serializer system. Only subclasses that register a serializer string
can be loaded from JSON. SetBlockMB supports JSON out of the box:
src/main/resources/data/my_mod/library_of_exile_map_data_block/lava_trap.json
{
"id": "my_mod:lava_trap",
"serializer": "set_block",
"weight": 500,
"block_id": "minecraft:lava",
"tags": [],
"process_on": "NORMAL"
}
To make your custom subclass JSON-loadable, register its serializer prototype by calling
addToSerializables(serializer) during init.
Adding map content tags
Tags on MapDataBlock are plain strings. Use MapBlockTags constants or define your own:
// Marks this block as eligible to spawn a league event:
block.tags.add(MapBlockTags.CAN_SPAWN_LEAGUE);
// Custom tag:
block.tags.add("my_mod:reward_room");
Processing on reward rooms
The process_on field controls when the block is processed:
| Value | When it runs |
|---|---|
NORMAL |
During standard chunk processing |
REWARD_ROOM |
Only in reward room chunks |
Configuring a map dimension
Register a MapDimensionConfig to declare a new map dimension with your data block as the
default fallback:
MapDimensionConfig.register(
myDimensionInfo,
new MapDimensionConfigDefaults()
.defaultDataBlock("my_mod:zombie_spawner")
.chunkProcessRadius(4)
.chunkSpawnRadius(3)
.despawnIncorrectMobs(true)
.wipeDimensionOnLoad(false)
);
The defaultDataBlock is used when no other data block matches the key string at a position.
Reacting after a data block processes
ExileEvents.PROCESS_DATA_BLOCK.register(new EventConsumer<ExileEvents.OnProcessMapDataBlock>() {
@Override
public void accept(ExileEvents.OnProcessMapDataBlock e) {
// e.dataBlock, e.key, e.pos, e.world, e.nbt
if (e.dataBlock.tags.contains("my_mod:reward_room")) {
// Place additional decorations, etc.
}
}
});
Notes
MAP_DATA_BLOCKisSyncTime.NEVER— map data blocks only exist server-side.MapBlockCtxgives you access toLibMapData, which holds the current map's saved state.- A map data block's GUID must match exactly the
keystring embedded in the structure template NBT for the block to be found and executed.