Cubiomes is a standalone library, written in C, that mimics the biome and feature generation of Minecraft: Java Edition. It is intended as a powerful tool to devise very fast, custom seed-finding applications and large-scale map viewers with minimal memory usage.
This project is an active fork of Cubiomes by Cubitect. I will add features to it as I need them. Some of the features I have added, I have also opened PRs for. Others are outside the scope of Cubiomes as indicated by Cubitect. For example, he has said that things like chest loot are not within Cubiomes' scope. These features will just be maintained in this repository. You can see all my PRs here.
Below is a list of all the major additions:
- Up-to-date biome generation.
- Ore generation (1.13+).
- Ore vein generation (1.18+).
- Stronghold generation (1.8+).
- Abandoned camp generation (26.3+).
- Structure loot support (1.13+). At the time of writing, the following structures are supported: Bastions (limited), Buried Treasures, Desert Pyramids, End Cities, Igloos, Jungle Temples, Nether Fortresses, Pillager Outposts, Ruined Portals (OW/NE), Shipwrecks, Strongholds and Abandoned Camps.
- Fast Xoroshiro128++ state advancement.
- Canyon/cave carvers (1.13+).
- Terrain generation (1.18+).
- Various bug fixes.
MSVC is not supported for this fork. Please use MinGW, UCRT64, Clang, or GCC.
I have set up the automatic creation of Java bindings based on commits to the master branch. See the java-bindings branch for more information.
If you want to get started without coding, there is a graphical application based on the upstream version of this library.
You should be familiar with the C programming language. A basic understanding of the Minecraft biome generation process would also be helpful.
This section is meant to give you a quick starting point with small example programs if you want to use this library to find your own biome-dependent features.
All terminal commands seen below are for Unix/Linux systems; Windows is similar, barring some minor adjustments.
Let's create a simple program called find_biome_at.c which tests seeds for a Mushroom Fields biome at a predefined location.
// check the biome at a block position
#include "generator.h"
#include <stdio.h>
int main()
{
// Set up a biome generator that reflects the biome generation of
// Minecraft 1.18.
Generator g;
setupGenerator(&g, MC_1_18, 0);
// Seeds are internally represented as unsigned 64-bit integers.
uint64_t seed;
for (seed = 0; ; seed++)
{
// Apply the seed to the generator for the Overworld dimension.
applySeed(&g, DIM_OVERWORLD, seed);
// To get the biome at a single block position, we can use getBiomeAt().
int scale = 1; // scale=1: block coordinates, scale=4: biome coordinates
int x = 0, y = 63, z = 0;
int biomeID = getBiomeAt(&g, scale, x, y, z);
if (biomeID == mushroom_fields)
{
printf("Seed %" PRId64 " has a Mushroom Fields biome at "
"block position (%d, %d).\n", (int64_t) seed, x, z);
break;
}
}
return 0;
}You can compile this code by creating a shared library (libcubiomes.so, libcubiomes.dylib or cubiomes.dll depending on your OS) or an archive (libcubiomes_static.a) using the CMake build script:
$ cd cubiomes
$ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
$ cmake --build buildThen you can compile your program, while linking the archive or shared library, using either of the following commands.
$ gcc find_biome_at.c -Lbuild -Wl,-rpath,build -lcubiomes -O3 -Wall -Wextra -fwrapv -lm # dynamic
$ gcc find_biome_at.c -Lbuild -lcubiomes_static -O3 -Wall -Wextra -fwrapv -lm # staticBoth commands assume that your source code is saved as find_biome_at.c in the Cubiomes working directory. If your makefile is configured to use pthreads, you may also need to add the -lpthread option for the compiler.
The option -fwrapv enforces two's complement for signed integer overflow, which is otherwise undefined behavior. It is not really necessary for this example, but it is a common pitfall when dealing with code that emulates the behavior of Java.
If the archive fails to generate, or the compilation claims the library's functions are undefined or missing, run cmake --build build --target clean to delete the failed archive, then retry the process.
Running the program should output:
$ ./a.out
Seed 262 has a Mushroom Fields biome at block position (0, 0).We can also generate biomes for an area or volume using genBiomes(). This will utilize whatever optimizations are available for the generator, which can be much faster than generating each position individually. (The layered generators for versions up to 1.17 will benefit significantly more from this than the noise-based ones.)
Before we can generate the biomes for an area or volume, we need to define the bounds with a Range structure and allocate the necessary buffer using allocCache(). The Range is described by a scale, position, and size, where each cell inside the Range represents an amount of scale blocks in the horizontal axes. The vertical direction is treated separately and always follows the biome coordinate scaling of 1:4, except for when scale == 1, in which case the vertical scaling is also 1:1.
The only supported values for scale are 1, 4, 16, 64, and (for the Overworld) 256. For versions up to 1.17, the scale is matched to an appropriate biome layer and will influence the biomes that can generate.
// generate an image of the world
#include "generator.h"
#include "util.h"
int main()
{
Generator g;
setupGenerator(&g, MC_1_18, LARGE_BIOMES);
uint64_t seed = 123LL;
applySeed(&g, DIM_OVERWORLD, seed);
Range r;
// 1:16, a.k.a. horizontal chunk scaling
r.scale = 16;
// Define the position and size for a horizontal area:
r.x = -60, r.z = -60; // position (x,z)
r.sx = 120, r.sz = 120; // size (width,height)
// Set the vertical range as a plane near sea level at scale 1:4.
r.y = 15, r.sy = 1;
// Allocate the necessary cache for this range.
int *biomeIds = allocCache(&g, r);
// Generate the area inside biomeIds, indexed as:
// biomeIds[i_y*r.sx*r.sz + i_z*r.sx + i_x]
// where (i_x, i_y, i_z) is a position relative to the range cuboid.
genBiomes(&g, biomeIds, r);
// Map the biomes to an image buffer, with 4 pixels per biome cell.
int pix4cell = 4;
int imgWidth = pix4cell*r.sx, imgHeight = pix4cell*r.sz;
unsigned char biomeColors[256][3];
initBiomeColors(biomeColors);
unsigned char *rgb = (unsigned char *) malloc(3*imgWidth*imgHeight);
biomesToImage(rgb, biomeColors, biomeIds, r.sx, r.sz, pix4cell, 2);
// Save the RGB buffer to a PPM image file.
savePPM("map.ppm", rgb, imgWidth, imgHeight);
// Clean up.
free(biomeIds);
free(rgb);
return 0;
}The generation of structures can usually be regarded as a two-stage process: generation attempts and biome checks. For most structures, Minecraft divides the world into a grid of regions (usually 32x32 chunks) and performs one generation attempt in each. We can use getStructurePos() to get the position of such a generation attempt, and then test whether a structure will actually generate there with isViableStructurePos(); however, this is more expensive to compute (requiring many microseconds instead of nanoseconds).
Note: some structures (in particular desert pyramids, jungle temples, and woodland mansions) in 1.18 no longer depend solely on the biomes and can also fail to generate based on the surface height near the generation attempt. Unfortunately, cubiomes does not provide block-level world generation and cannot check for this, and may therefore yield false positive positions. Support for an approximation of the surface height might be added in the future to improve accuracy.
// find a seed with a certain structure at the origin chunk
#include "finders.h"
#include <stdio.h>
int main()
{
int structType = Outpost;
int mc = MC_1_18;
Generator g;
setupGenerator(&g, mc, 0);
uint64_t lower48;
for (lower48 = 0; ; lower48++)
{
// The structure position depends only on the region coordinates and
// the lower 48-bits of the world seed.
Pos p;
if (!getStructurePos(structType, mc, lower48, 0, 0, &p))
continue;
// Look for a seed with the structure at the origin chunk.
if (p.x >= 16 || p.z >= 16)
continue;
// Look for a full 64-bit seed with viable biomes.
uint64_t upper16;
for (upper16 = 0; upper16 < 0x10000; upper16++)
{
uint64_t seed = lower48 | (upper16 << 48);
applySeed(&g, DIM_OVERWORLD, seed);
if (isViableStructurePos(structType, &g, p.x, p.z, 0))
{
printf("Seed %" PRId64 " has a Pillager Outpost at (%d, %d).\n",
(int64_t) seed, p.x, p.z);
return 0;
}
}
}
}The terrain_features.h API adds chunk-scoped queries for a seed without
loading or modifying a world save:
#include "terrain_features.h"
Generator g;
setupGenerator(&g, MC_1_17_1, 0);
applySeed(&g, DIM_OVERWORLD, 6512112982729996127ULL);
NaturalTreeCandidate trees[64];
NaturalStructure structures[8];
size_t treeCount, structureCount;
getChunkNaturalTreeCandidates(&g, 16, 0, trees, 64, &treeCount);
// Returns FEATURE_PARTIAL because only the documented structure types have
// bounds support.
getChunkNaturalStructures(&g, 16, 0, structures, 8, &structureCount);
uint8_t caveMask[4096]; // 16 * 16 * 128 bits
getChunkCaveCarvingMask(&g, 16, 0, 0, 127,
caveMask, sizeof(caveMask));These queries intentionally expose their accuracy instead of presenting estimated data as final block state:
| Data | Accuracy |
|---|---|
| Structure start and type | Exact for six supported 1.17.1 structure types; the query returns FEATURE_PARTIAL |
| Structure bounds | Approximate starting-template bounds; not the full jigsaw structure |
| First 1.17.1 tree-like vegetation candidate | Exact decorator X/Z and type; approximate Y |
| First accepted 1.21.1 candidate per placed feature | Exact decorator X/Z and type; surface Y comes from the caller |
| Later tree-like vegetation candidates | Approximate because preceding feature generation consumes terrain-dependent random values |
| Cave mask | Exact cave/canyon path RNG and geometry before terrain checks |
The 1.21.1 query follows the vegetation-step Xoroshiro stream, global placed-
feature indexes, a 3x3 chunk surface-biome feature union, and per-candidate
biome placement checks. Jungle bushes, huge mushrooms, mangroves, cherry trees, and
bamboo have distinct candidate types and can be filtered when only ordinary
trees are needed. Tree queries return FEATURE_PARTIAL for biomes whose
vegetation pipeline is not implemented. The cave mask is a
candidate mask, not a final cave_air block map. Vanilla
can reject an entire carving region when it finds fluid, reject individual
materials, place lava at Y=10 and below, and later overwrite blocks. Lakes,
dungeons, and structures can also place cave_air independently. Reproducing
those final states requires block-level terrain generation, which remains out
of scope for cubiomes.
A commonly desired feature is Quad-Witch-Huts or similar multi-structure clusters. To test for these types of seeds, we can look a little deeper into how the generation attempts are determined. Notice that the positions depend only on the structure type, region coordinates, and the lower 48 bits of the seed. Also, once we have found a seed with the desired generation attempts, we can move them around by transforming the 48-bit seed using moveStructure(). This means there is a set of seed bases that can function as a starting point to generate all other seeds with similar structure placement.
The function searchAll48() can be used to find a complete set of 48-bit seed bases for a custom criterion. Given that in general, it can take a very long time to check all 2^48 seeds (days or weeks), the function provides some functionality to save the results to disk which can be loaded again using loadSavedSeeds(). Luckily, it is possible in some cases to reduce the search space even further: for Swamp Huts and structures with a similar structure configuration, there are only a handful of constellations where the structures are close enough together to run simultaneously. Conveniently, these constellations differ uniquely at the lower 20 bits. (This is hard to prove, or at least I haven't found a rigorous proof that doesn't rely on brute forcing.) By specifying a list of lower 20-bit values, we can reduce the search space to the order of 2^28, which can be checked in a reasonable amount of time.
// find seeds with a quad-witch-hut about the origin
#include "quadbase.h"
#include <stdio.h>
int check(uint64_t s48, void *data)
{
const StructureConfig sconf = *(const StructureConfig*) data;
return isQuadBase(sconf, s48 - sconf.salt, 128);
}
int main()
{
int styp = Swamp_Hut;
int mc = MC_1_18;
uint64_t basecnt = 0;
uint64_t *bases = NULL;
int threads = 8;
Generator g;
StructureConfig sconf;
getStructureConfig(styp, mc, &sconf);
printf("Preparing seed bases...\n");
// Get all 48-bit quad-witch-hut bases, but consider only the best 20-bit
// constellations where the structures are the closest together.
int err = searchAll48(&bases, &basecnt, NULL, threads,
low20QuadIdeal, 20, check, &sconf);
if (err || !bases)
{
printf("Failed to generate seed bases.\n");
exit(1);
}
setupGenerator(&g, mc, 0);
uint64_t i;
for (i = 0; i < basecnt; i++)
{
// The quad bases by themselves have structures in regions (0,0)-(1,1)
// so we can move them by -1 regions to have them around the origin.
uint64_t s48 = moveStructure(bases[i] - sconf.salt, -1, -1);
Pos pos[4];
getStructurePos(styp, mc, s48, -1, -1, &pos[0]);
getStructurePos(styp, mc, s48, -1, 0, &pos[1]);
getStructurePos(styp, mc, s48, 0, -1, &pos[2]);
getStructurePos(styp, mc, s48, 0, 0, &pos[3]);
uint64_t high;
for (high = 0; high < 0x10000; high++)
{
uint64_t seed = s48 | (high << 48);
applySeed(&g, DIM_OVERWORLD, seed);
if (isViableStructurePos(styp, &g, pos[0].x, pos[0].z, 0) &&
isViableStructurePos(styp, &g, pos[1].x, pos[1].z, 0) &&
isViableStructurePos(styp, &g, pos[2].x, pos[2].z, 0) &&
isViableStructurePos(styp, &g, pos[3].x, pos[3].z, 0))
{
printf("%" PRId64 "\n", (int64_t) seed);
}
}
}
free(bases);
return 0;
}Strongholds, as well as the world spawn point, actually search until they find a suitable location, rather than checking a single spot like most other structures. This causes them to be particularly slow to find. Furthermore, the positions of strongholds have to be generated in a certain order, which can be done in iteratively with initFirstStronghold() and nextStronghold(). For the world spawn, the exact coordinate is found after a search for a grass or podzol block prior to 1.18, or for any top-solid nonwaterlogged block in 1.18+. This library cannot model individual blocks, so the search relies on heuristics such as biomes and climate-dependent world heights. Alternatively, we can simply use estimateSpawn() and terminate the search after the first biome/climate check under the assumption that grass/a top-solid nonwaterlogged block is nearby.
// find spawn and the first N strongholds
#include "finders.h"
#include <stdio.h>
int main()
{
int mc = MC_1_18;
uint64_t seed = 3055141959546LL;
// Only the first stronghold has a position that can be estimated
// (+/-112 blocks) without biome check.
StrongholdIter sh;
Pos pos = initFirstStronghold(&sh, mc, seed);
printf("Seed: %" PRId64 "\n", (int64_t) seed);
printf("Estimated position of first stronghold: (%d, %d)\n", pos.x, pos.z);
Generator g;
setupGenerator(&g, mc, 0);
applySeed(&g, DIM_OVERWORLD, seed);
pos = getSpawn(&g);
printf("Spawn: (%d, %d)\n", pos.x, pos.z);
int i, N = 12;
for (i = 1; i <= N; i++)
{
if (nextStronghold(&sh, &g) <= 0)
break;
printf("Stronghold #%-3d: (%6d, %6d)\n", i, sh.pos.x, sh.pos.z);
}
return 0;
}