Entities and Points of Interest

Terrain isn't the only thing stored per chunk. Every chunk also has its own entities (mobs, dropped items, minecarts) and its own points of interest (job sites, beds, bells and similar blocks villagers care about). Both are stored separately from the chunk's blocks, in their own region files, but they're looked up the same way: by chunk coordinates, through the Dimension you already opened in the Dimensions and Chunks guide.

1. Reading entities

readEntities(chunkX, chunkZ) gives you an EntityData object, or null if that chunk's entity file doesn't have data for it yet.

EntityData entityData = overworld.readEntities(2, -1);
if (entityData == null) {
    return;
}

for (Entity entity : entityData.entities()) {
    System.out.println(entity.id() + " at " + Arrays.toString(entity.pos()));
}

Each entity comes back wrapped as either an Entity or a LivingEntity. minified-worlds tells them apart by checking whether the entity's NBT has a Health field, which every living entity (mobs, players, animals) has and non living ones (dropped items, minecarts, arrows) don't. LivingEntity extends Entity and adds the extra fields that only make sense for something alive, like health.

Entity fields Common to every entity
id String

The entity's type, like minecraft:zombie.

uuid UUID

The entity's unique id.

pos double[]

The entity's position, as [x, y, z].

rotation float[]

The entity's facing, as [yaw, pitch].

onGround boolean

Whether the entity is currently touching the ground.

LivingEntity fields Extends Entity
health float

The entity's current health.

canPickUpLoot boolean

Whether the entity can pick up dropped items.

persistenceRequired boolean

Whether the entity is exempt from despawning.

Both classes have quite a few more fields than what's listed here, raw() gives you the full NBT if you need something that isn't wrapped yet.

2. Adding an entity

If you want to spawn something into a chunk yourself, build the entity's NBT compound and pass it to addEntity. This adds it both to the in memory list and to the underlying NBT, so it's ready to be saved.

NbtCompound zombieNbt = new NbtCompound();
zombieNbt.setString("id", "minecraft:zombie");
zombieNbt.setFloat("Health", 20.0f);
// ... position, UUID and other fields would normally go here too

Entity zombie = entityData.addEntity(zombieNbt);

Since the Health field is what decides how the entity gets wrapped, adding it before calling addEntity gets you a LivingEntity back instead of a plain Entity.

3. Saving entity changes

Just like chunks, changes to entity data need to be written back explicitly.

overworld.saveEntityData(2, -1, entityData);

This creates the entity region file for that chunk if it doesn't exist yet, so it also works when you're populating a freshly generated chunk that never had any entities before.

4. Reading points of interest

Points of interest, or POIs, are how villagers find beds to sleep in and job site blocks to work at, and how bells know where to summon villagers to. They're read the same way, through readPoi(chunkX, chunkZ), returning PoiData or null.

PoiData poiData = overworld.readPoi(2, -1);
if (poiData == null) {
    return;
}

for (PoiRecord record : poiData.records()) {
    System.out.println(record.type() + " at " + Arrays.toString(record.pos()));
}

Each PoiRecord is small:

pos int[]

The block position of the POI, as [x, y, z].

type String

What kind of POI it is, like minecraft:home or minecraft:armorer.

freeTickets int

How many more villagers can still claim this POI.

POIs inside a chunk are grouped by section Y, the same vertical slices chunks use for blocks. If you only care about one section, pass its Y to records(sectionY) instead of listing everything in the chunk.

List<PoiRecord> recordsInSection = poiData.records(4);

5. Adding a point of interest

addRecord takes a section Y, a block position, the POI type, and how many free tickets it has (this is how many villagers can claim it at once, a bed has one, most job sites have one too). It creates the section entry if it doesn't exist yet.

poiData.addRecord(4, 10, 70, 10, "minecraft:home", 1);
overworld.savePoiData(2, -1, poiData);

That's it

That's the core of minified-worlds: opening a world, reading its settings, walking through dimensions and chunks, and reading or writing the blocks, entities and points of interest inside them. From here, the NbtCompound returned by raw() on most of these wrapper classes is still available whenever you need to reach a field that isn't wrapped yet.