cubiomes/finders.c
Cubitect 89df24c3be Update spawn algorithm for 1.21.2 + cmake + more
* added basic support for cmake (#127)
* renamed Winter Drop version from MC_1_21_3 to MC_1_21_WD
* updated world spawn location for 1.21.2 (cubiomes-viewer #340)
* tweaked mc version to text conversion (#128)
* removed properties field in structure config and added dimension field instead
* moved biome tree selection back to biomenoise.c as it's slightly faster and avoids globals
2024-11-09 21:08:05 +01:00

5620 lines
179 KiB
C

#include "finders.h"
#include "biomes.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <float.h>
#include <math.h>
#define PI 3.14159265358979323846
//==============================================================================
// Finding Structure Positions
//==============================================================================
void setAttemptSeed(uint64_t *s, int cx, int cz)
{
*s ^= (uint64_t)(cx >> 4) ^ ( (uint64_t)(cz >> 4) << 4 );
setSeed(s, *s);
next(s, 31);
}
uint64_t getPopulationSeed(int mc, uint64_t ws, int x, int z)
{
Xoroshiro xr;
uint64_t s;
uint64_t a, b;
if (mc >= MC_1_18)
{
xSetSeed(&xr, ws);
a = xNextLongJ(&xr);
b = xNextLongJ(&xr);
}
else
{
setSeed(&s, ws);
a = nextLong(&s);
b = nextLong(&s);
}
if (mc >= MC_1_13)
{
a |= 1; b |= 1;
}
else
{
a = (int64_t)a / 2 * 2 + 1;
b = (int64_t)b / 2 * 2 + 1;
}
return (x * a + z * b) ^ ws;
}
int getStructureConfig(int structureType, int mc, StructureConfig *sconf)
{
static const StructureConfig
// for desert pyramids, jungle temples, witch huts and igloos prior to 1.13
s_feature = { 14357617, 32, 24, Feature, 0,0},
s_igloo_112 = { 14357617, 32, 24, Igloo, 0,0},
s_swamp_hut_112 = { 14357617, 32, 24, Swamp_Hut, 0,0},
s_desert_pyramid_112 = { 14357617, 32, 24, Desert_Pyramid, 0,0},
s_jungle_temple_112 = { 14357617, 32, 24, Jungle_Pyramid, 0,0},
// ocean features before 1.16
s_ocean_ruin_115 = { 14357621, 16, 8, Ocean_Ruin, 0,0},
s_shipwreck_115 = {165745295, 16, 8, Shipwreck, 0,0},
// 1.13 separated feature seeds by type
s_desert_pyramid = { 14357617, 32, 24, Desert_Pyramid, 0,0},
s_igloo = { 14357618, 32, 24, Igloo, 0,0},
s_jungle_temple = { 14357619, 32, 24, Jungle_Pyramid, 0,0},
s_swamp_hut = { 14357620, 32, 24, Swamp_Hut, 0,0},
s_outpost = {165745296, 32, 24, Outpost, 0,0},
s_village_117 = { 10387312, 32, 24, Village, 0,0},
s_village = { 10387312, 34, 26, Village, 0,0},
s_ocean_ruin = { 14357621, 20, 12, Ocean_Ruin, 0,0},
s_shipwreck = {165745295, 24, 20, Shipwreck, 0,0},
s_monument = { 10387313, 32, 27, Monument, 0,0},
s_mansion = { 10387319, 80, 60, Mansion, 0,0},
s_ruined_portal = { 34222645, 40, 25, Ruined_Portal, 0,0},
s_ruined_portal_n = { 34222645, 40, 25, Ruined_Portal, DIM_NETHER,0},
s_ruined_portal_n_117 = { 34222645, 25, 15, Ruined_Portal_N, DIM_NETHER,0},
s_ancient_city = { 20083232, 24, 16, Ancient_City, 0,0},
s_trail_ruins = { 83469867, 34, 26, Trail_Ruins, 0,0},
s_trial_chambers = { 94251327, 34, 22, Trial_Chambers, 0,0},
s_treasure = { 10387320, 1, 1, Treasure, 0,0},
s_mineshaft = { 0, 1, 1, Mineshaft, 0,0},
s_desert_well_115 = { 30010, 1, 1, Desert_Well, 0, 1.f/1000},
s_desert_well_117 = { 40013, 1, 1, Desert_Well, 0, 1.f/1000},
s_desert_well = { 40002, 1, 1, Desert_Well, 0, 1.f/1000},
s_geode_117 = { 20000, 1, 1, Geode, 0, 1.f/24},
s_geode = { 20002, 1, 1, Geode, 0, 1.f/24},
// nether and end structures
s_fortress_115 = { 0, 16, 8, Fortress, DIM_NETHER,0},
s_fortress = { 30084232, 27, 23, Fortress, DIM_NETHER,0},
s_bastion = { 30084232, 27, 23, Bastion, DIM_NETHER,0},
s_end_city = { 10387313, 20, 9, End_City, DIM_END,0},
// for the scattered return gateways
s_end_gateway_115 = { 30000, 1, 1, End_Gateway, DIM_END, 700},
s_end_gateway_116 = { 40013, 1, 1, End_Gateway, DIM_END, 700},
s_end_gateway_117 = { 40013, 1, 1, End_Gateway, DIM_END, 1.f/700},
s_end_gateway = { 40000, 1, 1, End_Gateway, DIM_END, 1.f/700},
s_end_island_116 = { 0, 1, 1, End_Island, DIM_END, 14},
s_end_island = { 0, 1, 1, End_Island, DIM_END, 1.f/14}
;
switch (structureType)
{
case Feature:
*sconf = s_feature;
return mc <= MC_1_12;
case Desert_Pyramid:
*sconf = mc <= MC_1_12 ? s_desert_pyramid_112 : s_desert_pyramid;
return mc >= MC_1_3;
case Jungle_Pyramid:
*sconf = mc <= MC_1_12 ? s_jungle_temple_112 : s_jungle_temple;
return mc >= MC_1_3;
case Swamp_Hut:
*sconf = mc <= MC_1_12 ? s_swamp_hut_112 : s_swamp_hut;
return mc >= MC_1_4;
case Igloo:
*sconf = mc <= MC_1_12 ? s_igloo_112 : s_igloo;
return mc >= MC_1_9;
case Village:
*sconf = mc <= MC_1_17 ? s_village_117 : s_village;
return mc >= MC_B1_8;
case Ocean_Ruin:
*sconf = mc <= MC_1_15 ? s_ocean_ruin_115 : s_ocean_ruin;
return mc >= MC_1_13;
case Shipwreck:
*sconf = mc <= MC_1_15 ? s_shipwreck_115 : s_shipwreck;
return mc >= MC_1_13;
case Ruined_Portal:
*sconf = s_ruined_portal;
return mc >= MC_1_16_1;
case Ruined_Portal_N:
*sconf = mc <= MC_1_17 ? s_ruined_portal_n_117 : s_ruined_portal_n;
return mc >= MC_1_16_1;
case Monument:
*sconf = s_monument;
return mc >= MC_1_8;
case End_City:
*sconf = s_end_city;
return mc >= MC_1_9;
case Mansion:
*sconf = s_mansion;
return mc >= MC_1_11;
case Outpost:
*sconf = s_outpost;
return mc >= MC_1_14;
case Ancient_City:
*sconf = s_ancient_city;
return mc >= MC_1_19_2;
case Treasure:
*sconf = s_treasure;
return mc >= MC_1_13;
case Mineshaft:
*sconf = s_mineshaft;
return mc >= MC_B1_8;
case Fortress:
*sconf = mc <= MC_1_15 ? s_fortress_115 : s_fortress;
return mc >= MC_1_0;
case Bastion:
*sconf = s_bastion;
return mc >= MC_1_16_1;
case End_Gateway:
if (mc <= MC_1_15) *sconf = s_end_gateway_115;
else if (mc <= MC_1_16) *sconf = s_end_gateway_116;
else if (mc <= MC_1_17) *sconf = s_end_gateway_117;
else *sconf = s_end_gateway;
// 1.11 and 1.12 generate gateways using a random source that passed
// the block filling, making them much more difficult to predict
return mc >= MC_1_13;
case End_Island:
if (mc <= MC_1_16) *sconf = s_end_island_116;
else *sconf = s_end_island;
return mc >= MC_1_13; // we only support decorator features for 1.13+
case Desert_Well:
if (mc <= MC_1_15) *sconf = s_desert_well_115;
else if (mc <= MC_1_17) *sconf = s_desert_well_117;
else *sconf = s_desert_well;
// wells were introduced in 1.2, but we only support decorator features
// for 1.13+
return mc >= MC_1_13;
case Geode:
*sconf = mc <= MC_1_17 ? s_geode_117 : s_geode;
return mc >= MC_1_17;
case Trail_Ruins:
*sconf = s_trail_ruins;
return mc >= MC_1_20;
case Trial_Chambers:
*sconf = s_trial_chambers;
return mc >= MC_1_21_1;
default:
memset(sconf, 0, sizeof(StructureConfig));
return 0;
}
}
// like getFeaturePos(), but modifies the rng seed
static inline
void getRegPos(Pos *p, uint64_t *s, int rx, int rz, StructureConfig sc)
{
setSeed(s, rx*341873128712ULL + rz*132897987541ULL + *s + sc.salt);
p->x = ((uint64_t)rx * sc.regionSize + nextInt(s, sc.chunkRange)) << 4;
p->z = ((uint64_t)rz * sc.regionSize + nextInt(s, sc.chunkRange)) << 4;
}
int getStructurePos(int structureType, int mc, uint64_t seed, int regX, int regZ, Pos *pos)
{
StructureConfig sconf;
#if STRUCT_CONFIG_OVERRIDE
if (!getStructureConfig_override(structureType, mc, &sconf))
#else
if (!getStructureConfig(structureType, mc, &sconf))
#endif
{
return 0;
}
switch (structureType)
{
case Feature:
case Desert_Pyramid:
case Jungle_Pyramid:
case Swamp_Hut:
case Igloo:
case Village:
case Ocean_Ruin:
case Shipwreck:
case Ruined_Portal:
case Ruined_Portal_N:
case Ancient_City:
case Trail_Ruins:
case Trial_Chambers:
*pos = getFeaturePos(sconf, seed, regX, regZ);
return 1;
case Monument:
case Mansion:
*pos = getLargeStructurePos(sconf, seed, regX, regZ);
return 1;
case End_City:
*pos = getLargeStructurePos(sconf, seed, regX, regZ);
return (pos->x*(int64_t)pos->x + pos->z*(int64_t)pos->z) >= 1008*1008LL;
case Outpost:
*pos = getFeaturePos(sconf, seed, regX, regZ);
setAttemptSeed(&seed, (pos->x) >> 4, (pos->z) >> 4);
return nextInt(&seed, 5) == 0;
case Treasure:
pos->x = regX * 16 + 9;
pos->z = regZ * 16 + 9;
seed = regX*341873128712ULL + regZ*132897987541ULL + seed + sconf.salt;
setSeed(&seed, seed);
return nextFloat(&seed) < 0.01;
case Mineshaft:
return getMineshafts(mc, seed, regX, regZ, regX, regZ, pos, 1);
case Fortress:
if (mc >= MC_1_18) {
*pos = getFeaturePos(sconf, seed, regX, regZ);
return 1; // fortresses gen where bastions don't (biome dependent)
} else if (mc >= MC_1_16_1) {
getRegPos(pos, &seed, regX, regZ, sconf);
return nextInt(&seed, 5) < 2;
} else {
setAttemptSeed(&seed, regX * 16, regZ * 16);
int valid = nextInt(&seed, 3) == 0;
pos->x = (regX * 16 + nextInt(&seed, 8) + 4) * 16;
pos->z = (regZ * 16 + nextInt(&seed, 8) + 4) * 16;
return valid;
}
case Bastion:
if (mc >= MC_1_18) {
*pos = getFeaturePos(sconf, seed, regX, regZ);
seed = chunkGenerateRnd(seed, pos->x >> 4, pos->z >> 4);
return nextInt(&seed, 5) >= 2;
} else {
getRegPos(pos, &seed, regX, regZ, sconf);
return nextInt(&seed, 5) >= 2;
}
case End_Gateway:
case End_Island:
case Desert_Well:
case Geode:
// decorator features
pos->x = regX * 16;
pos->z = regZ * 16;
seed = getPopulationSeed(mc, seed, pos->x, pos->z);
if (mc >= MC_1_18)
{
Xoroshiro xr;
xSetSeed(&xr, seed + sconf.salt);
if (xNextFloat(&xr) >= sconf.rarity)
return 0;
pos->x += xNextIntJ(&xr, 16);
pos->z += xNextIntJ(&xr, 16);
}
else
{
setSeed(&seed, seed + sconf.salt);
if (sconf.rarity < 1.0) {
if (nextFloat(&seed) >= sconf.rarity)
return 0;
} else {
if (nextInt(&seed, (int)sconf.rarity) != 0)
return 0;
}
pos->x += nextInt(&seed, 16);
pos->z += nextInt(&seed, 16);
}
return 1;
default:
fprintf(stderr,
"ERR getStructurePos: unsupported structure type %d\n", structureType);
exit(-1);
}
return 0;
}
int getMineshafts(int mc, uint64_t seed, int cx0, int cz0, int cx1, int cz1,
Pos *out, int nout)
{
uint64_t s;
setSeed(&s, seed);
uint64_t a = nextLong(&s);
uint64_t b = nextLong(&s);
int i, j;
int n = 0;
for (i = cx0; i <= cx1; i++)
{
uint64_t aix = i * a ^ seed;
for (j = cz0; j <= cz1; j++)
{
setSeed(&s, aix ^ j * b);
if (mc >= MC_1_13)
{
if unlikely(nextDouble(&s) < 0.004)
{
if (out && n < nout)
{
out[n].x = i * 16;
out[n].z = j * 16;
}
n++;
}
}
else
{
skipNextN(&s, 1);
if unlikely(nextDouble(&s) < 0.004)
{
int d = i;
if (-i > d) d = -i;
if (+j > d) d = +j;
if (-j > d) d = -j;
if (d >= 80 || nextInt(&s, 80) < d)
{
if (out && n < nout)
{
out[n].x = i * 16;
out[n].z = j * 16;
}
n++;
}
}
}
}
}
return n;
}
int getEndIslands(EndIsland islands[2], int mc, uint64_t seed, int chunkX, int chunkZ)
{
StructureConfig sconf;
if (!getStructureConfig(End_Island, mc, &sconf))
return 0;
int x = chunkX * 16;
int z = chunkZ * 16;
uint64_t rng = getPopulationSeed(mc, seed, x, z);
Xoroshiro xr;
float r;
if (mc <= MC_1_16)
{
setSeed(&rng, rng + sconf.salt);
if (nextInt(&rng, (int)sconf.rarity) != 0)
return 0;
islands[0].x = nextInt(&rng, 16) + x;
islands[0].y = nextInt(&rng, 16) + 55;
islands[0].z = nextInt(&rng, 16) + z;
if (nextInt(&rng, 4) != 0)
{
islands[0].r = nextInt(&rng, 3) + 4;
return 1;
}
islands[1].x = nextInt(&rng, 16) + x;
islands[1].y = nextInt(&rng, 16) + 55;
islands[1].z = nextInt(&rng, 16) + z;
islands[0].r = nextInt(&rng, 3) + 4;
for (r = islands[0].r; r > 0.5; r -= nextInt(&rng, 2) + 0.5);
islands[1].r = nextInt(&rng, 3) + 4;
return 2;
}
else if (mc <= MC_1_17)
{
setSeed(&rng, rng + sconf.salt);
if (nextFloat(&rng) >= sconf.rarity)
return 0;
int second = nextInt(&rng, 4) == 0;
islands[0].x = nextInt(&rng, 16) + x;
islands[0].z = nextInt(&rng, 16) + z;
islands[0].y = nextInt(&rng, 16) + 55;
islands[0].r = nextInt(&rng, 3) + 4;
for (r = islands[0].r; r > 0.5; r -= nextInt(&rng, 2) + 0.5);
if (!second)
return 1;
islands[1].x = nextInt(&rng, 16) + x;
islands[1].z = nextInt(&rng, 16) + z;
islands[1].y = nextInt(&rng, 16) + 55;
islands[1].r = nextInt(&rng, 3) + 4;
return 2;
}
else
{
xSetSeed(&xr, rng + sconf.salt);
if (xNextFloat(&xr) >= sconf.rarity)
return 0;
int second = (xNextIntJ(&xr, 4) == 3);
islands[0].x = xNextIntJ(&xr, 16) + x;
islands[0].z = xNextIntJ(&xr, 16) + z;
islands[0].y = xNextIntJ(&xr, 16) + 55;
islands[0].r = xNextIntJ(&xr, 3) + 4;
if (!second)
return 1;
for (r = islands[0].r; r > 0.5; r -= xNextIntJ(&xr, 2) + 0.5);
islands[1].x = xNextIntJ(&xr, 16) + x;
islands[1].z = xNextIntJ(&xr, 16) + z;
islands[1].y = xNextIntJ(&xr, 16) + 55;
islands[1].r = xNextIntJ(&xr, 3) + 4;
return 2;
}
}
static void applyEndIslandHeight(float *y, const EndIsland *island,
int x, int z, int w, int h, int scale)
{
int r = island->r;
int r2 = (r + 1) * (r + 1);
int x0 = floordiv(island->x - r, scale);
int z0 = floordiv(island->z - r, scale);
int x1 = floordiv(island->x + r, scale);
int z1 = floordiv(island->z + r, scale);
int ds = 0;
int i, j;
for (j = z0; j <= z1; j++)
{
if (j < z || j >= z+h)
continue;
int dz = j * scale - island->z + ds;
for (i = x0; i <= x1; i++)
{
if (i < x || i >= x+w)
continue;
int dx = i * scale - island->x + ds;
if (dx*dx + dz*dz > r2)
continue;
int idx = (j - z) * w + (i - x);
if (y[idx] < island->y)
y[idx] = island->y;
}
}
}
int mapEndIslandHeight(float *y, const EndNoise *en, uint64_t seed,
int x, int z, int w, int h, int scale)
{
int rmax = (6 + scale - 1) / scale;
int cx = floordiv(x - rmax, 16 / scale);
int cz = floordiv(z - rmax, 16 / scale);
int cw = floordiv(x + w + rmax, 16 / scale) - cx + 1;
int ch = floordiv(z + h + rmax, 16 / scale) - cz + 1;
int ci, cj;
int *ids = (int*) malloc(sizeof(int) * cw * ch);
mapEndBiome(en, ids, cx, cz, cw, ch);
for (cj = 0; cj < ch; cj++)
{
for (ci = 0; ci < cw; ci++)
{
if (ids[cj*cw + ci] != small_end_islands)
continue;
EndIsland islands[2];
int n = getEndIslands(islands, en->mc, seed, cx+ci, cz+cj);
while (n --> 0)
applyEndIslandHeight(y, islands+n, x, z, w, h, scale);
}
}
free(ids);
return 0;
}
float getEndHeightNoise(const EndNoise *en, int x, int z, int range);
int isEndChunkEmpty(const EndNoise *en, const SurfaceNoise *sn, uint64_t seed,
int chunkX, int chunkZ)
{
int i, j, k;
int x = chunkX * 2;
int z = chunkZ * 2;
double depth[3][3];
float y[256];
// check if small end islands intersect this chunk
for (j = -1; j <= +1; j++)
{
for (i = -1; i <= +1; i++)
{
EndIsland is[2];
int n = getEndIslands(is, en->mc, seed, chunkX+i, chunkZ+j);
while (n --> 0)
{
if (is[n].x + is[n].r <= chunkX*16) continue;
if (is[n].z + is[n].r <= chunkZ*16) continue;
if (is[n].x - is[n].r > chunkX*16 + 15) continue;
if (is[n].z - is[n].r > chunkZ*16 + 15) continue;
int id;
mapEndBiome(en, &id, is[n].x >> 4, is[n].z >> 4, 1, 1);
if (id == small_end_islands)
return 0;
}
}
}
// clamped (32 + 46 - y) / 64.0
static const double upper_drop[] = {
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, // 0-7
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 63./64, // 8-15
62./64, 61./64, 60./64, 59./64, 58./64, 57./64, 56./64, 55./64, // 16-23
54./64, 53./64, 52./64, 51./64, 50./64, 49./64, 48./64, 47./64, // 24-31
46./64 // 32
};
// clamped (y - 1) / 7.0
static const double lower_drop[] = {
0.0, 0.0, 1./7, 2./7, 3./7, 4./7, 5./7, 6./7, // 0-7
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, // 8-15
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, // 16-23
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, // 24-31
1.0, // 32
};
// inverse of clamping func: ( 30 * (1-l) / l + 3000 * (1-u) ) / u
static const double inverse_drop[] = {
1e9, 1e9, 180.0, 75.0, 40.0, 22.5, 12.0, 5.0, // 0-7
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, // 8-14
1000./21, 3000./31, 9000./61, 200.0, // 15-18
};
const double eps = 0.001;
// get the inner depth values and see if they imply blocks in the chunk
for (i = 0; i < 2; i++)
{
for (j = 0; j < 2; j++)
{
depth[i][j] = getEndHeightNoise(en, x+i, z+j, 0) - 8.0f;
for (k = 8; k <= 14; k++)
{
double u = upper_drop[k];
double l = lower_drop[k];
double noise = depth[i][j];
double pivot = inverse_drop[k] - noise;
noise += sampleSurfaceNoiseBetween(sn, x+i, k, z+j, pivot-eps, pivot+eps);
noise = lerp(u, -3000, noise);
noise = lerp(l, -30, noise);
if (noise > 0)
return 0;
}
}
}
// fill in the depth values at the boundaries to neighbouring chunks
for (i = 0; i < 3; i++)
depth[i][2] = getEndHeightNoise(en, x+i, z+2, 0) - 8.0f;
for (j = 0; j < 2; j++)
depth[2][j] = getEndHeightNoise(en, x+2, z+j, 0) - 8.0f;
// see if none of the noise values can generate blocks
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
for (k = 2; k < 18; k++)
{
double u = upper_drop[k];
double l = lower_drop[k];
double noise = depth[i][j];
double pivot = inverse_drop[k] - noise;
noise += sampleSurfaceNoiseBetween(sn, x+i, k, z+j, pivot-eps, pivot+eps);
noise = lerp(u, -3000, noise);
noise = lerp(l, -30, noise);
if (noise > 0)
goto L_check_full;
}
}
}
return 1;
L_check_full:
mapEndSurfaceHeight(y, en, sn, chunkX*16, chunkZ*16, 16, 16, 1, 0);
for (k = 0; k < 256; k++)
if (y[k] != 0) return 0;
return 1;
}
//==============================================================================
// Checking Biomes & Biome Helper Functions
//==============================================================================
static int id_matches(int id, uint64_t b, uint64_t m)
{
return id < 128 ? !!(b & (1ULL << id)) : !!(m & (1ULL << (id-128)));
}
Pos locateBiome(
const Generator *g, int x, int y, int z, int radius,
uint64_t validB, uint64_t validM, uint64_t *rng, int *passes)
{
Pos out = {x, z};
int i, j, found;
found = 0;
if (g->mc >= MC_1_18)
{
x >>= 2;
z >>= 2;
radius >>= 2;
uint64_t dat = 0;
for (j = -radius; j <= radius; j++)
{
for (i = -radius; i <= radius; i++)
{
// emulate order-dependent biome generation MC-241546
//int id = getBiomeAt(g, 4, x+i, y, z+j);
int id = sampleBiomeNoise(&g->bn, NULL, x+i, y, z+j, &dat, 0);
if (!id_matches(id, validB, validM))
continue;
if (found == 0 || nextInt(rng, found+1) == 0)
{
out.x = (x+i) * 4;
out.z = (z+j) * 4;
}
found++;
}
}
}
else
{
int x1 = (x-radius) >> 2;
int z1 = (z-radius) >> 2;
int x2 = (x+radius) >> 2;
int z2 = (z+radius) >> 2;
int width = x2 - x1 + 1;
int height = z2 - z1 + 1;
Range r = {4, x1, z1, width, height, y, 1};
int *ids = allocCache(g, r);
genBiomes(g, ids, r);
if (g->mc >= MC_1_13)
{
for (i = 0, j = 2; i < width*height; i++)
{
if (!id_matches(ids[i], validB, validM))
continue;
if (found == 0 || nextInt(rng, j++) == 0)
{
out.x = (x1 + i%width) * 4;
out.z = (z1 + i/width) * 4;
found = 1;
}
}
found = j - 2;
}
else
{
for (i = 0; i < width*height; i++)
{
if (!id_matches(ids[i], validB, validM))
continue;
if (found == 0 || nextInt(rng, found + 1) == 0)
{
out.x = (x1 + i%width) * 4;
out.z = (z1 + i/width) * 4;
++found;
}
}
}
free(ids);
}
if (passes != NULL)
{
*passes = found;
}
return out;
}
int areBiomesViable(
const Generator *g, int x, int y, int z, int rad,
uint64_t validB, uint64_t validM, int approx)
{
int x1 = (x - rad) >> 2, x2 = (x + rad) >> 2, sx = x2 - x1 + 1;
int z1 = (z - rad) >> 2, z2 = (z + rad) >> 2, sz = z2 - z1 + 1;
int i, j, id, viable = 1;
int *ids = NULL;
// In 1.18+ the area is also checked in y, forming a cube volume.
// However, this function is only used for monuments, which need ocean or
// river, where we can get away with just checking the lowest y for caves.
y = (y - rad) >> 2;
// check corners
Pos corners[4] = { {x1,z1}, {x2,z2}, {x1,z2}, {x2,z1} };
for (i = 0; i < 4; i++)
{
id = getBiomeAt(g, 4, corners[i].x, y, corners[i].z);
if (id < 0 || !id_matches(id, validB, validM))
goto L_no;
}
if (approx >= 1) goto L_yes;
if (g->mc >= MC_1_18)
{
for (i = 0; i < sx; i++)
{
uint64_t dat = 0;
for (j = 0; j < sz; j++)
{
if (g->mc >= MC_1_18)
id = sampleBiomeNoise(&g->bn, NULL, x1+i, y, z1+j, &dat, 0);
else
id = getBiomeAt(g, 4, x1+i, y, z1+j);
if (id < 0 || !id_matches(id, validB, validM))
goto L_no;
}
}
}
else
{
Range r = {4, x1, z1, sx, sz, y, 1};
ids = allocCache(g, r);
if (genBiomes(g, ids, r))
goto L_no;
for (i = 0; i < sx*sz; i++)
{
if (id < 0 || !id_matches(ids[i], validB, validM))
goto L_no;
}
}
if (0) L_yes: viable = 1;
if (0) L_no: viable = 0;
if (ids)
free(ids);
return viable;
}
//==============================================================================
// Finding Strongholds and Spawn
//==============================================================================
int isStrongholdBiome(int mc, int id)
{
if (!isOverworld(mc, id))
return 0;
if (isOceanic(id))
return 0;
switch (id)
{
case plains:
case mushroom_fields:
case taiga_hills:
return mc >= MC_1_7;
case swamp:
return mc <= MC_1_6;
case river:
case frozen_river:
case beach:
case snowy_beach:
case swamp_hills:
return 0;
case mushroom_field_shore:
return mc >= MC_1_13;
case stone_shore:
return mc <= MC_1_17;
case bamboo_jungle:
case bamboo_jungle_hills:
// simulate MC-199298
return mc <= MC_1_15 || mc >= MC_1_18;
case mangrove_swamp:
case deep_dark:
return 0;
default:
return 1;
}
}
Pos initFirstStronghold(StrongholdIter *sh, int mc, uint64_t s48)
{
double dist, angle;
uint64_t rnds;
Pos p;
setSeed(&rnds, s48);
angle = 2.0 * PI * nextDouble(&rnds);
if (mc >= MC_1_9)
dist = (4.0 * 32.0) + (nextDouble(&rnds) - 0.5) * 32 * 2.5;
else
dist = (1.25 + nextDouble(&rnds)) * 32.0;
p.x = ((int)round(cos(angle) * dist) * 16) + 8;
p.z = ((int)round(sin(angle) * dist) * 16) + 8;
if (sh)
{
sh->pos.x = sh->pos.z = 0;
sh->nextapprox = p;
sh->index = 0;
sh->ringnum = 0;
sh->ringmax = 3;
sh->ringidx = 0;
sh->angle = angle;
sh->dist = dist;
sh->rnds = rnds;
sh->mc = mc;
}
return p;
}
int nextStronghold(StrongholdIter *sh, const Generator *g)
{
uint64_t validB = 0, validM = 0;
int i;
for (i = 0; i < 64; i++)
{
if (isStrongholdBiome(sh->mc, i))
validB |= (1ULL << i);
if (isStrongholdBiome(sh->mc, i+128))
validM |= (1ULL << i);
}
if (sh->mc > MC_1_19_2)
{
if (g)
{
uint64_t lbr = sh->rnds;
setSeed(&lbr, nextLong(&sh->rnds));
sh->pos = locateBiome(g, sh->nextapprox.x, 0, sh->nextapprox.z, 112,
validB, validM, &lbr, NULL);
}
else
{
nextLong(&sh->rnds);
sh->pos = sh->nextapprox;
}
}
else if (sh->mc >= MC_B1_8)
{
sh->pos = locateBiome(g, sh->nextapprox.x, 0, sh->nextapprox.z, 112,
validB, validM, &sh->rnds, NULL);
}
else
{
return 0;
}
// staircase is located at (4, 4) in chunk
sh->pos.x = (sh->pos.x & ~15) + 4;
sh->pos.z = (sh->pos.z & ~15) + 4;
sh->ringidx++;
sh->angle += 2 * PI / sh->ringmax;
if (sh->ringidx == sh->ringmax)
{
sh->ringnum++;
sh->ringidx = 0;
sh->ringmax = sh->ringmax + 2*sh->ringmax / (sh->ringnum+1);
if (sh->ringmax > 128-sh->index)
sh->ringmax = 128-sh->index;
sh->angle += nextDouble(&sh->rnds) * PI * 2.0;
}
if (sh->mc >= MC_1_9)
{
sh->dist = (4.0 * 32.0) + (6.0 * sh->ringnum * 32.0) +
(nextDouble(&sh->rnds) - 0.5) * 32 * 2.5;
}
else
{
sh->dist = (1.25 + nextDouble(&sh->rnds)) * 32.0;
}
sh->nextapprox.x = ((int)round(cos(sh->angle) * sh->dist) * 16) + 8;
sh->nextapprox.z = ((int)round(sin(sh->angle) * sh->dist) * 16) + 8;
sh->index++;
return (sh->mc >= MC_1_9 ? 128 : 3) - (sh->index-1);
}
static
uint64_t calcFitness(const Generator *g, int x, int z)
{
int64_t np[6];
uint32_t flags = SAMPLE_NO_DEPTH | SAMPLE_NO_BIOME;
sampleBiomeNoise(&g->bn, np, x>>2, 0, z>>2, NULL, flags);
const int64_t spawn_np[][2] = {
{-10000,10000},{-10000,10000},{-1100,10000},{-10000,10000},{0,0},
{-10000,-1600},{1600,10000} // [6]: weirdness for the second noise point
};
uint64_t ds = 0, ds1 = 0, ds2 = 0;
uint64_t a, b, q, i;
for (i = 0; i < 5; i++)
{
a = +np[i] - (uint64_t)spawn_np[i][1];
b = -np[i] + (uint64_t)spawn_np[i][0];
q = (int64_t)a > 0 ? a : (int64_t)b > 0 ? b : 0;
ds += q * q;
}
a = +np[5] - (uint64_t)spawn_np[5][1];
b = -np[5] + (uint64_t)spawn_np[5][0];
q = (int64_t)a > 0 ? a : (int64_t)b > 0 ? b : 0;
ds1 = ds + q*q;
a = +np[5] - (uint64_t)spawn_np[6][1];
b = -np[5] + (uint64_t)spawn_np[6][0];
q = (int64_t)a > 0 ? a : (int64_t)b > 0 ? b : 0;
ds2 = ds + q*q;
ds = ds1 <= ds2 ? ds1 : ds2;
// apply dependence on distance from origin
a = (int64_t)x*x;
b = (int64_t)z*z;
if (g->mc < MC_1_21_2)
{
double s = (double)(a + b) / (2500 * 2500);
q = (uint64_t)(s*s * 1e8) + ds;
}
else
{
q = ds * (2048LL * 2048LL) + a + b;
}
return q;
}
static
void findFittest(const Generator *g, Pos *pos, uint64_t *fitness, double maxrad, double step)
{
double rad, ang;
Pos p = *pos;
for (rad = step; rad <= maxrad; rad += step)
{
for (ang = 0; ang <= PI*2; ang += step/rad)
{
int x = p.x + (int)(sin(ang) * rad);
int z = p.z + (int)(cos(ang) * rad);
uint64_t fit = calcFitness(g, x, z);
// Then update pos and fitness if combined total is lower/better
if (fit < *fitness)
{
pos->x = x;
pos->z = z;
*fitness = fit;
}
}
}
}
static
Pos findFittestPos(const Generator *g)
{
Pos spawn = {0, 0};
uint64_t fitness = calcFitness(g, 0, 0);
findFittest(g, &spawn, &fitness, 2048.0, 512.0);
findFittest(g, &spawn, &fitness, 512.0, 32.0);
// center of chunk
spawn.x = (spawn.x & ~15) + 8;
spawn.z = (spawn.z & ~15) + 8;
return spawn;
}
// valid spawn biomes up to 1.17
static const uint64_t g_spawn_biomes_17 =
(1ULL << forest) |
(1ULL << plains) |
(1ULL << taiga) |
(1ULL << taiga_hills) |
(1ULL << wooded_hills) |
(1ULL << jungle) |
(1ULL << jungle_hills);
Pos estimateSpawn(const Generator *g, uint64_t *rng)
{
Pos spawn = {0, 0};
if (g->mc <= MC_B1_7)
{
// finds a random sandblock (location is not fixed)
return spawn;
}
else if (g->mc <= MC_1_17)
{
int found;
uint64_t spawn_biomes = g_spawn_biomes_17;
if (g->mc <= MC_1_0)
spawn_biomes = (1ULL << forest) | (1ULL << swamp) |(1ULL << taiga);
uint64_t s;
setSeed(&s, g->seed);
spawn = locateBiome(g, 0, 63, 0, 256, spawn_biomes, 0, &s, &found);
if (!found)
spawn.x = spawn.z = 8;
if (rng)
*rng = s;
}
else
{
spawn = findFittestPos(g);
}
return spawn;
}
Pos getSpawn(const Generator *g)
{
uint64_t rng;
Pos spawn = estimateSpawn(g, &rng);
int i, j, k, u, v, cx0, cz0;
uint32_t ii, jj;
if (g->mc <= MC_B1_7)
return spawn;
SurfaceNoise sn;
initSurfaceNoise(&sn, DIM_OVERWORLD, g->seed);
if (g->mc <= MC_1_12)
{
for (i = 0; i < 1000; i++)
{
float y;
int id, grass = 0;
mapApproxHeight(&y, &id, g, &sn, spawn.x >> 2, spawn.z >> 2, 1, 1);
getBiomeDepthAndScale(id, 0, 0, &grass);
if (grass > 0 && y >= grass)
break;
spawn.x += nextInt(&rng, 64) - nextInt(&rng, 64);
spawn.z += nextInt(&rng, 64) - nextInt(&rng, 64);
}
}
else if (g->mc <= MC_1_17)
{
j = k = u = 0;
v = -1;
for (i = 0; i < 1024; i++)
{
if (j > -16 && j <= 16 && k > -16 && k <= 16)
{
// find server spawn point in chunk
float y[16];
int ids[16];
cx0 = (spawn.x & ~15) + j * 16; // start of chunk
cz0 = (spawn.z & ~15) + k * 16;
mapApproxHeight(y, ids, g, &sn, cx0 >> 2, cz0 >> 2, 4, 4);
for (ii = 0; ii < 4; ii++)
{
for (jj = 0; jj < 4; jj++)
{
int grass = 0;
getBiomeDepthAndScale(ids[jj*4+ii], 0, 0, &grass);
if (grass <= 0 || y[jj*4+ii] < grass)
continue;
spawn.x = cx0 + ii * 4;
spawn.z = cz0 + jj * 4;
return spawn;
}
}
}
if (j == k || (j < 0 && j == -k) || (j > 0 && j == 1 - k))
{
int tmp = u;
u = -v;
v = tmp;
}
j += u;
k += v;
}
// chunk center
spawn.x = (spawn.x & ~15) + 8;
spawn.z = (spawn.z & ~15) + 8;
}
else
{
j = k = u = 0;
v = -1;
for (i = 0; i < 121; i++)
{
if (j >= -5 && j <= 5 && k >= -5 && k <= 5)
{
// find server spawn point in chunk
cx0 = (spawn.x & ~15) + j * 16;
cz0 = (spawn.z & ~15) + k * 16;
for (ii = 0; ii < 4; ii++)
{
for (jj = 0; jj < 4; jj++)
{
float y;
int id;
int x = cx0 + ii * 4;
int z = cz0 + jj * 4;
mapApproxHeight(&y, &id, g, &sn, x >> 2, z >> 2, 1, 1);
if (y > 63 || id == frozen_ocean ||
id == deep_frozen_ocean || id == frozen_river)
{
spawn.x = x;
spawn.z = z;
return spawn;
}
}
}
}
if (j == k || (j < 0 && j == -k) || (j > 0 && j == 1 - k))
{
int tmp = u;
u = -v;
v = tmp;
}
j += u;
k += v;
}
// chunk center
spawn.x = (spawn.x & ~15) + 8;
spawn.z = (spawn.z & ~15) + 8;
}
return spawn;
}
//==============================================================================
// Validating Structure Positions
//==============================================================================
int isViableFeatureBiome(int mc, int structureType, int biomeID)
{
switch (structureType)
{
case Desert_Pyramid:
return biomeID == desert || biomeID == desert_hills;
case Jungle_Pyramid:
return (biomeID == jungle || biomeID == jungle_hills ||
biomeID == bamboo_jungle || biomeID == bamboo_jungle_hills);
case Swamp_Hut:
return biomeID == swamp;
case Igloo:
if (mc <= MC_1_8) return 0;
return biomeID == snowy_tundra || biomeID == snowy_taiga || biomeID == snowy_slopes;
case Ocean_Ruin:
if (mc <= MC_1_12) return 0;
return isOceanic(biomeID);
case Shipwreck:
if (mc <= MC_1_12) return 0;
return isOceanic(biomeID) || biomeID == beach || biomeID == snowy_beach;
case Ruined_Portal:
case Ruined_Portal_N:
return mc >= MC_1_16_1;
case Ancient_City:
if (mc <= MC_1_18) return 0;
return biomeID == deep_dark;
case Trail_Ruins:
if (mc <= MC_1_19) return 0;
else {
switch (biomeID) {
case taiga:
case snowy_taiga:
case old_growth_pine_taiga:
case old_growth_spruce_taiga:
case old_growth_birch_forest:
case jungle:
return 1;
default:
return 0;
}
}
case Trial_Chambers:
if (mc <= MC_1_20) return 0;
return biomeID != deep_dark && isOverworld(mc, biomeID);
case Treasure:
if (mc <= MC_1_12) return 0;
return biomeID == beach || biomeID == snowy_beach;
case Mineshaft:
return isOverworld(mc, biomeID);
case Desert_Well:
return biomeID == desert;
case Monument:
if (mc <= MC_1_7) return 0;
return isDeepOcean(biomeID);
case Outpost:
if (mc <= MC_1_13) return 0;
if (mc >= MC_1_18) {
switch (biomeID) {
case desert:
case plains:
case savanna:
case snowy_plains:
case taiga:
case meadow:
case frozen_peaks:
case jagged_peaks:
case stony_peaks:
case snowy_slopes:
case grove:
case cherry_grove:
return 1;
default:
return 0;
}
}
// fall through
case Village:
if (biomeID == plains || biomeID == desert || biomeID == savanna)
return 1;
if (mc >= MC_1_10 && biomeID == taiga)
return 1;
if (mc >= MC_1_14 && biomeID == snowy_tundra)
return 1;
if (mc >= MC_1_18 && biomeID == meadow)
return 1;
return 0;
case Mansion:
if (mc <= MC_1_10) return 0;
return biomeID == dark_forest || biomeID == dark_forest_hills;
case Fortress:
return (biomeID == nether_wastes || biomeID == soul_sand_valley ||
biomeID == warped_forest || biomeID == crimson_forest ||
biomeID == basalt_deltas);
case Bastion:
if (mc <= MC_1_15) return 0;
return (biomeID == nether_wastes || biomeID == soul_sand_valley ||
biomeID == warped_forest || biomeID == crimson_forest);
case End_City:
if (mc <= MC_1_8) return 0;
return biomeID == end_midlands || biomeID == end_highlands;
case End_Gateway:
if (mc <= MC_1_12) return 0;
return biomeID == end_highlands;
default:
fprintf(stderr,
"isViableFeatureBiome: not implemented for structure type %d.\n",
structureType);
exit(1);
}
return 0;
}
static int mapViableBiome(const Layer * l, int * out, int x, int z, int w, int h)
{
int err = mapBiome(l, out, x, z, w, h);
if unlikely(err != 0)
return err;
int styp = ((const int*) l->data)[0];
int i, j;
for (j = 0; j < h; j++)
{
for (i = 0; i < w; i++)
{
int biomeID = out[i + w*j];
switch (styp)
{
case Desert_Pyramid:
case Desert_Well:
if (biomeID == desert || isMesa(biomeID))
return 0;
break;
case Jungle_Pyramid:
if (biomeID == jungle)
return 0;
break;
case Swamp_Hut:
if (biomeID == swamp)
return 0;
break;
case Igloo:
if (biomeID == snowy_tundra || biomeID == snowy_taiga)
return 0;
break;
case Treasure:
if (isOceanic(biomeID))
return 0;
break;
case Ocean_Ruin:
case Shipwreck:
case Monument:
if (isOceanic(biomeID))
return 0;
break;
case Mansion:
if (biomeID == dark_forest)
return 0;
break;
default:
return 0;
}
}
}
return 1; // required biomes not found: set err status to stop generator
}
static int mapViableShore(const Layer * l, int * out, int x, int z, int w, int h)
{
int err = mapShore(l, out, x, z, w, h);
if unlikely(err != 0)
return err;
int styp = ((const int*) l->data)[0];
int mc = ((const int*) l->data)[1];
int i, j;
for (j = 0; j < h; j++)
{
for (i = 0; i < w; i++)
{
int biomeID = out[i + w*j];
switch (styp)
{
case Desert_Pyramid:
case Jungle_Pyramid:
case Swamp_Hut:
case Igloo:
case Ocean_Ruin:
case Shipwreck:
case Village:
case Monument:
case Mansion:
case Treasure:
case Desert_Well:
if (isViableFeatureBiome(mc, styp, biomeID))
return 0;
break;
default:
return 0;
}
}
}
return 1;
}
static const uint64_t g_monument_biomes2 =
(1ULL << deep_frozen_ocean) |
(1ULL << deep_cold_ocean) |
(1ULL << deep_ocean) |
(1ULL << deep_lukewarm_ocean) |
(1ULL << deep_warm_ocean);
static const uint64_t g_monument_biomes1 =
(1ULL << ocean) |
(1ULL << deep_ocean) |
(1ULL << river) |
(1ULL << frozen_river) |
(1ULL << frozen_ocean) |
(1ULL << deep_frozen_ocean) |
(1ULL << cold_ocean) |
(1ULL << deep_cold_ocean) |
(1ULL << lukewarm_ocean) |
(1ULL << deep_lukewarm_ocean) |
(1ULL << warm_ocean) |
(1ULL << deep_warm_ocean);
int isViableStructurePos(int structureType, Generator *g, int x, int z, uint32_t flags)
{
int approx = 0; // enables approximation levels
int viable = 0;
int64_t chunkX = x >> 4;
int64_t chunkZ = z >> 4;
// Structures are positioned at the chunk origin, but the biome check is
// performed near the middle of the chunk [(9,9) in 1.13, TODO: check 1.7]
// In 1.16 the biome check is always performed at (2,2) with layer scale=4.
int sampleX, sampleZ, sampleY;
int id;
if (g->dim == DIM_NETHER)
{
if (structureType == Fortress && g->mc <= MC_1_17)
return 1;
if (g->mc <= MC_1_15)
return 0;
if (structureType == Ruined_Portal_N)
return 1;
if (structureType == Fortress)
{ // in 1.18+ fortresses generate everywhere, where bastions don't
StructureConfig sc;
if (!getStructureConfig(Fortress, g->mc, &sc))
return 0;
Pos rp = {
floordiv(x, sc.regionSize << 4),
floordiv(z, sc.regionSize << 4)
};
if (!getStructurePos(Bastion, g->mc, g->seed, rp.x, rp.z, &rp))
return 1;
return !isViableStructurePos(Bastion, g, x, z, flags);
}
sampleY = 0;
if (g->mc >= MC_1_18 && structureType == Bastion)
{
StructureVariant sv;
getVariant(&sv, Bastion, g->mc, g->seed, x, z, -1);
sampleX = (chunkX*32 + 2*sv.x + sv.sx-1) / 2 >> 2;
sampleZ = (chunkZ*32 + 2*sv.z + sv.sz-1) / 2 >> 2;
if (g->mc >= MC_1_19_2)
sampleY = 33 >> 2; // nether biomes don't actually vary in Y
}
else
{
sampleX = (chunkX * 4) + 2;
sampleZ = (chunkZ * 4) + 2;
}
id = getBiomeAt(g, 4, sampleX, sampleY, sampleZ);
return isViableFeatureBiome(g->mc, structureType, id);
}
else if (g->dim == DIM_END)
{
switch (structureType)
{
case End_City:
if (g->mc <= MC_1_8) return 0;
break;
case End_Gateway:
if (g->mc <= MC_1_12) return 0;
break;
default:
return 0;
}
// End biomes vary only on a per-chunk scale (1:16)
// voronoi pre-1.15 shouldn't matter for End Cities as the check will
// be near the chunk center
id = getBiomeAt(g, 16, chunkX, 0, chunkZ);
return isViableFeatureBiome(g->mc, structureType, id) ? id : 0;
}
// Overworld
Layer lbiome, lshore, *entry = 0;
int data[2] = { structureType, g->mc };
if (g->mc <= MC_1_17)
{
lbiome = g->ls.layers[L_BIOME_256];
lshore = g->ls.layers[L_SHORE_16];
entry = g->entry;
g->ls.layers[L_BIOME_256].data = (void*) data;
g->ls.layers[L_BIOME_256].getMap = mapViableBiome;
g->ls.layers[L_SHORE_16].data = (void*) data;
g->ls.layers[L_SHORE_16].getMap = mapViableShore;
}
switch (structureType)
{
case Trail_Ruins:
if (g->mc <= MC_1_19) goto L_not_viable;
goto L_feature;
case Ocean_Ruin:
case Shipwreck:
case Treasure:
if (g->mc <= MC_1_12) goto L_not_viable;
goto L_feature;
case Igloo:
if (g->mc <= MC_1_8) goto L_not_viable;
goto L_feature;
case Desert_Pyramid:
case Jungle_Pyramid:
case Swamp_Hut:
L_feature:
if (g->mc <= MC_1_15)
{
g->entry = &g->ls.layers[L_VORONOI_1];
sampleX = chunkX * 16 + 9;
sampleZ = chunkZ * 16 + 9;
}
else
{
if (g->mc <= MC_1_17)
g->entry = &g->ls.layers[L_RIVER_MIX_4];
sampleX = chunkX * 4 + 2;
sampleZ = chunkZ * 4 + 2;
}
id = getBiomeAt(g, 0, sampleX, 319>>2, sampleZ);
if (id < 0 || !isViableFeatureBiome(g->mc, structureType, id))
goto L_not_viable;
goto L_viable;
case Desert_Well:
if (g->mc <= MC_1_15)
{
g->entry = &g->ls.layers[L_VORONOI_1];
sampleX = x;
sampleZ = z;
}
else
{
if (g->mc <= MC_1_17)
g->entry = &g->ls.layers[L_RIVER_MIX_4];
sampleX = x >> 2;
sampleZ = z >> 2;
}
id = getBiomeAt(g, 0, sampleX, 319>>2, sampleZ);
if (id < 0 || !isViableFeatureBiome(g->mc, structureType, id))
goto L_not_viable;
goto L_viable;
case Village:
if (g->mc <= MC_1_17)
{
if (g->mc == MC_1_15)
{ // exclusively in MC_1_15, villages used the same biome check
// as other structures
g->entry = &g->ls.layers[L_VORONOI_1];
sampleX = chunkX * 16 + 9;
sampleZ = chunkZ * 16 + 9;
}
else
{
g->entry = &g->ls.layers[L_RIVER_MIX_4];
sampleX = chunkX * 4 + 2;
sampleZ = chunkZ * 4 + 2;
}
id = getBiomeAt(g, 0, sampleX, 0, sampleZ);
if (id < 0 || !isViableFeatureBiome(g->mc, structureType, id))
goto L_not_viable;
if (flags && (uint32_t) id != flags)
goto L_not_viable;
if (g->mc <= MC_1_9)
{ // before MC_1_10 villages did not spread into invalid biomes,
// which could cause them to fail to generate on the first
// check at block (2, 2) in the starting chunk
sampleX = chunkX * 16 + 2;
sampleZ = chunkZ * 16 + 2;
id = getBiomeAt(g, 1, sampleX, 0, sampleZ);
if (id < 0 || !isViableFeatureBiome(g->mc, structureType, id))
goto L_not_viable;
}
viable = id; // biome for viablility, useful for further analysis
goto L_viable;
}
else
{ // In 1.18 village types are checked separtely...
const int vv[] = { plains, desert, savanna, taiga, snowy_tundra };
size_t i;
for (i = 0; i < sizeof(vv)/sizeof(int); i++) {
if (flags && flags != (uint32_t) vv[i])
continue;
StructureVariant sv;
getVariant(&sv, Village, g->mc, g->seed, x, z, vv[i]);
sampleX = (chunkX*32 + 2*sv.x + sv.sx-1) / 2 >> 2;
sampleZ = (chunkZ*32 + 2*sv.z + sv.sz-1) / 2 >> 2;
sampleY = 319 >> 2;
id = getBiomeAt(g, 0, sampleX, sampleY, sampleZ);
if (id == vv[i] || (id == meadow && vv[i] == plains)) {
viable = vv[i];
goto L_viable;
}
}
goto L_not_viable;
}
case Outpost:
{
if (g->mc <= MC_1_13)
goto L_not_viable;
uint64_t rng = g->seed;
setAttemptSeed(&rng, chunkX, chunkZ);
if (nextInt(&rng, 5) != 0)
goto L_not_viable;
// look for villages within 10 chunks
StructureConfig vilconf;
if (!getStructureConfig(Village, g->mc, &vilconf))
goto L_not_viable;
int cx0 = (chunkX-10), cx1 = (chunkX+10);
int cz0 = (chunkZ-10), cz1 = (chunkZ+10);
int rx0 = floordiv(cx0, vilconf.regionSize);
int rx1 = floordiv(cx1, vilconf.regionSize);
int rz0 = floordiv(cz0, vilconf.regionSize);
int rz1 = floordiv(cz1, vilconf.regionSize);
int rx, rz;
for (rz = rz0; rz <= rz1; rz++)
{
for (rx = rx0; rx <= rx1; rx++)
{
Pos p = getFeaturePos(vilconf, g->seed, rx, rz);
int cx = p.x >> 4, cz = p.z >> 4;
if (cx >= cx0 && cx <= cx1 && cz >= cz0 && cz <= cz1)
{
if (g->mc >= MC_1_16_1)
goto L_not_viable;
if (isViableStructurePos(Village, g, p.x, p.z, 0))
goto L_not_viable;
}
}
}
if (g->mc >= MC_1_18)
{
rng = chunkGenerateRnd(g->seed, chunkX, chunkZ);
switch (nextInt(&rng, 4)) {
case 0: sampleX = +15; sampleZ = +15; break;
case 1: sampleX = -15; sampleZ = +15; break;
case 2: sampleX = -15; sampleZ = -15; break;
case 3: sampleX = +15; sampleZ = -15; break;
default: return 0; // unreachable
}
sampleX = (chunkX * 32 + sampleX) / 2 >> 2;
sampleZ = (chunkZ * 32 + sampleZ) / 2 >> 2;
}
else if (g->mc >= MC_1_16_1)
{
g->entry = &g->ls.layers[L_RIVER_MIX_4];
sampleX = chunkX * 4 + 2;
sampleZ = chunkZ * 4 + 2;
}
else
{
g->entry = &g->ls.layers[L_VORONOI_1];
sampleX = chunkX * 16 + 9;
sampleZ = chunkZ * 16 + 9;
}
id = getBiomeAt(g, 0, sampleX, 319>>2, sampleZ);
if (id < 0 || !isViableFeatureBiome(g->mc, structureType, id))
goto L_not_viable;
goto L_viable;
}
case Monument:
if (g->mc <= MC_1_7)
goto L_not_viable;
else if (g->mc == MC_1_8)
{ // In 1.8 monuments require only a single deep ocean block.
id = getBiomeAt(g, 1, chunkX * 16 + 8, 0, chunkZ * 16 + 8);
if (id < 0 || !isDeepOcean(id))
goto L_not_viable;
}
else if (g->mc <= MC_1_17)
{ // Monuments require two viability checks with the ocean layer
// branch => worth checking for potential deep ocean beforehand.
g->entry = &g->ls.layers[L_SHORE_16];
id = getBiomeAt(g, 0, chunkX, 0, chunkZ);
if (id < 0 || !isDeepOcean(id))
goto L_not_viable;
}
sampleX = chunkX * 16 + 8;
sampleZ = chunkZ * 16 + 8;
if (g->mc >= MC_1_9 && g->mc <= MC_1_17)
{ // check for deep ocean center
if (!areBiomesViable(g, sampleX, 63, sampleZ, 16, g_monument_biomes2, 0, approx))
goto L_not_viable;
}
else if (g->mc >= MC_1_18)
{ // check is done at y level of ocean floor - approx. with y = 36
id = getBiomeAt(g, 4, sampleX>>2, 36>>2, sampleZ>>2);
if (!isDeepOcean(id))
goto L_not_viable;
}
if (areBiomesViable(g, sampleX, 63, sampleZ, 29, g_monument_biomes1, 0, approx))
goto L_viable;
goto L_not_viable;
case Mansion:
if (g->mc <= MC_1_10)
goto L_not_viable;
if (g->mc <= MC_1_17)
{
sampleX = chunkX * 16 + 8;
sampleZ = chunkZ * 16 + 8;
uint64_t b = (1ULL << dark_forest);
uint64_t m = (1ULL << (dark_forest_hills-128));
if (!areBiomesViable(g, sampleX, 0, sampleZ, 32, b, m, approx))
goto L_not_viable;
}
else
{ // In 1.18 the generation gets the minimum surface height among the
// four structure corners (note structure has rotation).
// This minimum height has to be y >= 60. The biome check is done
// at the center position at that height.
// TODO: get surface height
sampleX = chunkX * 16 + 7;
sampleZ = chunkZ * 16 + 7;
id = getBiomeAt(g, 4, sampleX>>2, 319>>2, sampleZ>>2);
if (id < 0 || !isViableFeatureBiome(g->mc, structureType, id))
goto L_not_viable;
}
goto L_viable;
case Ruined_Portal:
case Ruined_Portal_N:
if (g->mc <= MC_1_15)
goto L_not_viable;
goto L_viable;
case Geode:
if (g->mc <= MC_1_16)
goto L_not_viable;
goto L_viable;
case Ancient_City:
if (g->mc <= MC_1_18) goto L_not_viable;
goto L_jigsaw;
case Trial_Chambers:
if (g->mc <= MC_1_20) goto L_not_viable;
L_jigsaw:
{
StructureVariant sv;
getVariant(&sv, structureType, g->mc, g->seed, x, z, -1);
sampleX = (chunkX*32 + 2*sv.x + sv.sx - 1) / 2 >> 2;
sampleZ = (chunkZ*32 + 2*sv.z + sv.sz - 1) / 2 >> 2;
sampleY = sv.y >> 2;
id = getBiomeAt(g, 4, sampleX, sampleY, sampleZ);
}
if (id < 0 || !isViableFeatureBiome(g->mc, structureType, id))
goto L_not_viable;
goto L_viable;
case Mineshaft:
goto L_viable;
default:
fprintf(stderr,
"isViableStructurePos: bad structure type %d or dimension %d\n",
structureType, g->dim);
goto L_not_viable;
}
L_viable:
if (!viable)
viable = 1;
L_not_viable:
if (g->mc <= MC_1_17)
{
g->ls.layers[L_BIOME_256] = lbiome;
g->ls.layers[L_SHORE_16] = lshore;
g->entry = entry;
}
return viable;
}
int isViableStructureTerrain(int structType, Generator *g, int x, int z)
{
int sx, sz;
if (g->mc <= MC_1_17)
return 1;
if (structType == Desert_Pyramid || structType == Jungle_Temple)
{
sx = (structType == Desert_Pyramid ? 21 : 12);
sz = (structType == Desert_Pyramid ? 21 : 15);
}
else if (structType == Mansion)
{
int cx = x >> 4, cz = z >> 4;
uint64_t rng = chunkGenerateRnd(g->seed, cx, cz);
int rot = nextInt(&rng, 4);
sx = 5;
sz = 5;
if (rot == 0) { sx = -5; }
if (rot == 1) { sx = -5; sz = -5; }
if (rot == 2) { sz = -5; }
x = cx * 16 + 7;
z = cz * 16 + 7;
}
else
{
return 1;
}
// approx surface height using depth parameter (0.5 ~ sea level)
double corners[][2] = {
{(x+ 0)/4.0, (z+ 0)/4.0},
{(x+sx)/4.0, (z+sz)/4.0},
{(x+ 0)/4.0, (z+sz)/4.0},
{(x+sx)/4.0, (z+ 0)/4.0},
};
int nptype = g->bn.nptype;
int i, ret = 1;
g->bn.nptype = NP_DEPTH;
for (i = 0; i < 4; i++)
{
double depth = sampleClimatePara(&g->bn, 0, corners[i][0], corners[i][1]);
if (depth < 0.48)
{
ret = 0;
break;
}
}
g->bn.nptype = nptype;
return ret;
}
/* Given bordering noise columns and a fractional position between those,
* determine the surface block height (i.e. where the interpolated noise > 0).
* Note that the noise columns should be of size: ncolxz[ colheight+1 ]
*/
int getSurfaceHeight(
const double ncol00[], const double ncol01[],
const double ncol10[], const double ncol11[],
int colymin, int colymax, int blockspercell, double dx, double dz);
void sampleNoiseColumnEnd(double column[], const SurfaceNoise *sn,
const EndNoise *en, int x, int z, int colymin, int colymax);
int isViableEndCityTerrain(const Generator *g, const SurfaceNoise *sn,
int blockX, int blockZ)
{
const EndNoise *en = &g->en;
int chunkX = blockX >> 4;
int chunkZ = blockZ >> 4;
blockX = chunkX * 16 + 7;
blockZ = chunkZ * 16 + 7;
int cellx = (blockX >> 3);
int cellz = (blockZ >> 3);
enum { y0 = 15, y1 = 18, yn = y1-y0+1 };
double ncol[3][3][yn];
sampleNoiseColumnEnd(ncol[0][0], sn, en, cellx, cellz, y0, y1);
sampleNoiseColumnEnd(ncol[0][1], sn, en, cellx, cellz+1, y0, y1);
sampleNoiseColumnEnd(ncol[1][0], sn, en, cellx+1, cellz, y0, y1);
sampleNoiseColumnEnd(ncol[1][1], sn, en, cellx+1, cellz+1, y0, y1);
int h00, h01, h10, h11;
h00 = getSurfaceHeight(ncol[0][0], ncol[0][1], ncol[1][0], ncol[1][1],
y0, y1, 4, (blockX & 7) / 8.0, (blockZ & 7) / 8.0);
uint64_t cs;
if (en->mc <= MC_1_18)
setSeed(&cs, chunkX + chunkZ * 10387313ULL);
else
cs = chunkGenerateRnd(g->seed, chunkX, chunkZ);
switch (nextInt(&cs, 4))
{
case 0: // (++) 0
sampleNoiseColumnEnd(ncol[0][2], sn, en, cellx+0, cellz+2, y0, y1);
sampleNoiseColumnEnd(ncol[1][2], sn, en, cellx+1, cellz+2, y0, y1);
sampleNoiseColumnEnd(ncol[2][0], sn, en, cellx+2, cellz+0, y0, y1);
sampleNoiseColumnEnd(ncol[2][1], sn, en, cellx+2, cellz+1, y0, y1);
sampleNoiseColumnEnd(ncol[2][2], sn, en, cellx+2, cellz+2, y0, y1);
h01 = getSurfaceHeight(ncol[0][1], ncol[0][2], ncol[1][1], ncol[1][2],
y0, y1, 4, ((blockX ) & 7) / 8.0, ((blockZ + 5) & 7) / 8.0);
h10 = getSurfaceHeight(ncol[1][0], ncol[1][1], ncol[2][0], ncol[2][1],
y0, y1, 4, ((blockX + 5) & 7) / 8.0, ((blockZ ) & 7) / 8.0);
h11 = getSurfaceHeight(ncol[1][1], ncol[1][2], ncol[2][1], ncol[2][2],
y0, y1, 4, ((blockX + 5) & 7) / 8.0, ((blockZ + 5) & 7) / 8.0);
break;
case 1: // (-+) 90
sampleNoiseColumnEnd(ncol[0][2], sn, en, cellx+0, cellz+2, y0, y1);
sampleNoiseColumnEnd(ncol[1][2], sn, en, cellx+1, cellz+2, y0, y1);
h01 = getSurfaceHeight(ncol[0][1], ncol[0][2], ncol[1][1], ncol[1][2],
y0, y1, 4, ((blockX ) & 7) / 8.0, ((blockZ + 5) & 7) / 8.0);
h10 = getSurfaceHeight(ncol[0][0], ncol[0][1], ncol[1][0], ncol[1][1],
y0, y1, 4, ((blockX - 5) & 7) / 8.0, ((blockZ ) & 7) / 8.0);
h11 = getSurfaceHeight(ncol[0][1], ncol[0][2], ncol[1][1], ncol[1][2],
y0, y1, 4, ((blockX - 5) & 7) / 8.0, ((blockZ + 5) & 7) / 8.0);
break;
case 2: // (--) 180
h01 = getSurfaceHeight(ncol[0][0], ncol[0][1], ncol[1][0], ncol[1][1],
y0, y1, 4, ((blockX ) & 7) / 8.0, ((blockZ - 5) & 7) / 8.0);
h10 = getSurfaceHeight(ncol[0][0], ncol[0][1], ncol[1][0], ncol[1][1],
y0, y1, 4, ((blockX - 5) & 7) / 8.0, ((blockZ ) & 7) / 8.0);
h11 = getSurfaceHeight(ncol[0][0], ncol[0][1], ncol[1][0], ncol[1][1],
y0, y1, 4, ((blockX - 5) & 7) / 8.0, ((blockZ - 5) & 7) / 8.0);
break;
case 3: // (+-) 270
sampleNoiseColumnEnd(ncol[2][0], sn, en, cellx+2, cellz+0, y0, y1);
sampleNoiseColumnEnd(ncol[2][1], sn, en, cellx+2, cellz+1, y0, y1);
h01 = getSurfaceHeight(ncol[0][0], ncol[0][1], ncol[1][0], ncol[1][1],
y0, y1, 4, ((blockX ) & 7) / 8.0, ((blockZ - 5) & 7) / 8.0);
h10 = getSurfaceHeight(ncol[1][0], ncol[1][1], ncol[2][0], ncol[2][1],
y0, y1, 4, ((blockX + 5) & 7) / 8.0, ((blockZ ) & 7) / 8.0);
h11 = getSurfaceHeight(ncol[1][0], ncol[1][1], ncol[2][0], ncol[2][1],
y0, y1, 4, ((blockX + 5) & 7) / 8.0, ((blockZ - 5) & 7) / 8.0);
break;
default:
return 0; // error
}
//printf("%d %d %d %d\n", h00, h01, h10, h11);
if (h01 < h00) h00 = h01;
if (h10 < h00) h00 = h10;
if (h11 < h00) h00 = h11;
return h00 >= 60 ? h00 : 0;
}
//==============================================================================
// Finding Properties of Structures
//==============================================================================
STRUCT(PieceEnv)
{
Piece *list;
int *n;
uint64_t *rng;
int *ship;
int y;
int typlast;
int nmax;
int ntyp[PIECE_COUNT];
};
typedef int (piecefunc_t)(PieceEnv *env, Piece *current, int depth);
static piecefunc_t genTower;
static piecefunc_t genBridge;
static piecefunc_t genHouseTower;
static piecefunc_t genFatTower;
int getVariant(StructureVariant *r, int structType, int mc, uint64_t seed,
int x, int z, int biomeID)
{
int t;
char sx, sy, sz;
uint64_t rng = chunkGenerateRnd(seed, x >> 4, z >> 4);
memset(r, 0, sizeof(*r));
r->start = -1;
r->biome = -1;
r->y = 320;
switch (structType)
{
case Village:
if (mc <= MC_1_9)
return 0;
if (!isViableFeatureBiome(mc, Village, biomeID))
return 0;
if (mc <= MC_1_13)
{
skipNextN(&rng, mc == MC_1_13 ? 10 : 11);
r->abandoned = nextInt(&rng, 50) == 0;
return 1;
}
r->biome = biomeID;
r->rotation = nextInt(&rng, 4);
switch (biomeID)
{
case meadow:
r->biome = plains;
// fallthrough
case plains:
t = nextInt(&rng, 204);
if (t < 50) { r->start = 0; sx = 9; sy = 4; sz = 9; } // plains_fountain_01
else if (t < 100) { r->start = 1; sx = 10; sy = 7; sz = 10; } // plains_meeting_point_1
else if (t < 150) { r->start = 2; sx = 8; sy = 5; sz = 15; } // plains_meeting_point_2
else if (t < 200) { r->start = 3; sx = 11; sy = 9; sz = 11; } // plains_meeting_point_3
else if (t < 201) { r->start = 0; sx = 9; sy = 4; sz = 9; r->abandoned = 1; }
else if (t < 202) { r->start = 1; sx = 10; sy = 7; sz = 10; r->abandoned = 1; }
else if (t < 203) { r->start = 2; sx = 8; sy = 5; sz = 15; r->abandoned = 1; }
else if (t < 204) { r->start = 3; sx = 11; sy = 9; sz = 11; r->abandoned = 1; }
else UNREACHABLE();
break;
case desert:
t = nextInt(&rng, 250);
if (t < 98) { r->start = 1; sx = 17; sy = 6; sz = 9; } // desert_meeting_point_1
else if (t < 196) { r->start = 2; sx = 12; sy = 6; sz = 12; } // desert_meeting_point_2
else if (t < 245) { r->start = 3; sx = 15; sy = 6; sz = 15; } // desert_meeting_point_3
else if (t < 247) { r->start = 1; sx = 17; sy = 6; sz = 9; r->abandoned = 1; }
else if (t < 249) { r->start = 2; sx = 12; sy = 6; sz = 12; r->abandoned = 1; }
else if (t < 250) { r->start = 3; sx = 15; sy = 6; sz = 15; r->abandoned = 1; }
else UNREACHABLE();
break;
case savanna:
t = nextInt(&rng, 459);
if (t < 100) { r->start = 1; sx = 14; sy = 5; sz = 12; } // savanna_meeting_point_1
else if (t < 150) { r->start = 2; sx = 11; sy = 6; sz = 11; } // savanna_meeting_point_2
else if (t < 300) { r->start = 3; sx = 9; sy = 6; sz = 11; } // savanna_meeting_point_3
else if (t < 450) { r->start = 4; sx = 9; sy = 6; sz = 9; } // savanna_meeting_point_4
else if (t < 452) { r->start = 1; sx = 14; sy = 5; sz = 12; r->abandoned = 1; }
else if (t < 453) { r->start = 2; sx = 11; sy = 6; sz = 11; r->abandoned = 1; }
else if (t < 456) { r->start = 3; sx = 9; sy = 6; sz = 11; r->abandoned = 1; }
else if (t < 459) { r->start = 4; sx = 9; sy = 6; sz = 9; r->abandoned = 1; }
else UNREACHABLE();
break;
case taiga:
t = nextInt(&rng, 100);
if (t < 49) { r->start = 1; sx = 22; sy = 3; sz = 18; } // taiga_meeting_point_1
else if (t < 98) { r->start = 2; sx = 9; sy = 7; sz = 9; } // taiga_meeting_point_2
else if (t < 99) { r->start = 1; sx = 22; sy = 3; sz = 18; r->abandoned = 1; }
else if (t < 100) { r->start = 2; sx = 9; sy = 7; sz = 9; r->abandoned = 1; }
else UNREACHABLE();
break;
case snowy_tundra:
t = nextInt(&rng, 306);
if (t < 100) { r->start = 1; sx = 12; sy = 8; sz = 8; } // snowy_meeting_point_1
else if (t < 150) { r->start = 2; sx = 11; sy = 5; sz = 9; } // snowy_meeting_point_2
else if (t < 300) { r->start = 3; sx = 7; sy = 7; sz = 7; } // snowy_meeting_point_3
else if (t < 302) { r->start = 1; sx = 12; sy = 8; sz = 8; r->abandoned = 1; }
else if (t < 303) { r->start = 2; sx = 11; sy = 5; sz = 9; r->abandoned = 1; }
else if (t < 306) { r->start = 3; sx = 7; sy = 7; sz = 7; r->abandoned = 1; }
else UNREACHABLE();
break;
default:
sx = sy = sz = 0;
return 0;
}
goto L_rotate_village_bastion;
case Bastion:
r->rotation = nextInt(&rng, 4);
r->start = nextInt(&rng, 4);
if (mc == MC_1_16_1)
{ // swapped in 1.16.1 only
uint8_t tmp = r->start;
r->start = r->rotation;
r->rotation = tmp;
}
switch (r->start)
{
case 0: sx = 46; sy = 24; sz = 46; break; // units/air_base
case 1: sx = 30; sy = 24; sz = 48; break; // hoglin_stable/air_base
case 2: sx = 38; sy = 48; sz = 38; break; // treasure/big_air_full
case 3: sx = 16; sy = 32; sz = 32; break; // bridge/starting_pieces/entrance_base
}
L_rotate_village_bastion:
r->sy = sy;
if (mc >= MC_1_18)
{
switch (r->rotation)
{ // 0:0, 1:cw90, 2:cw180, 3:cw270=ccw90
case 0: r->x = 0; r->z = 0; r->sx = sx; r->sz = sz; break;
case 1: r->x = 1-sz; r->z = 0; r->sx = sz; r->sz = sx; break;
case 2: r->x = 1-sx; r->z = 1-sz; r->sx = sx; r->sz = sz; break;
case 3: r->x = 0; r->z = 1-sx; r->sx = sz; r->sz = sx; break;
}
}
else
{
switch (r->rotation)
{ // 0:0, 1:cw90, 2:cw180, 3:cw270=ccw90
case 0: r->x = 0; r->z = 0; r->sx = sx; r->sz = sz; break;
case 1: r->x = (x<0)-sz; r->z = 0; r->sx = sz; r->sz = sx; break;
case 2: r->x = (x<0)-sx; r->z = (z<0)-sz; r->sx = sx; r->sz = sz; break;
case 3: r->x = 0; r->z = (z<0)-sx; r->sx = sz; r->sz = sx; break;
}
}
return 1;
case Ancient_City:
r->rotation = nextInt(&rng, 4);
r->start = 1 + nextInt(&rng, 3); // city_center_1..3
sx = 18; sy = 31; sz = 41;
switch (r->rotation)
{ // 0:0, 1:cw90, 2:cw180, 3:cw270=ccw90
case 0: x = -(x>0); z = -(z>0); r->sx = sx; r->sz = sz; break;
case 1: x = +(x<0)-sz; z = -(z>0); r->sx = sz; r->sz = sx; break;
case 2: x = +(x<0)-sx; z = +(z<0)-sz; r->sx = sx; r->sz = sz; break;
case 3: x = -(x>0); z = +(z<0)-sx; r->sx = sz; r->sz = sx; break;
}
// note the city_anchor (13, *, 20) is part of the city_center
sx = 13; sz = 20; // city_anchor
switch (r->rotation)
{ // 0:0, 1:cw90, 2:cw180, 3:cw270=ccw90
case 0: r->x = x-sx; r->z = z-sz; break; // 0:0
case 1: r->x = x+sz; r->z = z-sx; break; // 1:cw90
case 2: r->x = x+sx; r->z = z+sz; break; // 2:cw180
case 3: r->x = x-sz; r->z = z+sx; break; // 3:cw270=ccw90
}
r->y = -27;
r->sy = sy;
return 1;
case Ruined_Portal:
case Ruined_Portal_N:
// Ruined portals are split into 7 types that generate independenly
// from one another, each in a certain set of biomes. Together they
// cover each biome once (save for the deep_dark) and have no terrain
// restrictions, so a ruined portal *should* always generate in each
// region. However, in locations with underground biomes, a ruined
// portal can fail to generate, or possibly have two ruined portals
// above one another, because the biome check is done after selecting
// the portal type and generation height, and can therefore vertically
// move into unsupported biomes. Testing for this case requires the
// surface height and is therefore not supported.
{
int cat = getCategory(mc, biomeID);
switch (cat)
{
case desert:
case jungle:
case swamp:
case ocean:
case nether_wastes:
r->biome = cat;
break;
}
if (r->biome == -1)
{
switch (biomeID)
{
case mangrove_swamp:
r->biome = swamp;
break;
case mountains: // windswept_hills
case mountain_edge:
case wooded_mountains: // windswept_forest
case gravelly_mountains: // windswept_gravelly_hills
case modified_gravelly_mountains:
case savanna_plateau:
case shattered_savanna: // windswept_savanna
case shattered_savanna_plateau:
case badlands:
case eroded_badlands:
case wooded_badlands_plateau: // wooded_badlands
case modified_badlands_plateau:
case modified_wooded_badlands_plateau:
case snowy_taiga_mountains:
case taiga_mountains:
case stony_shore:
case meadow:
case frozen_peaks:
case jagged_peaks:
case stony_peaks:
case snowy_slopes:
r->biome = mountains;
break;
}
}
if (r->biome == -1)
r->biome = plains;
if (r->biome == plains || r->biome == mountains)
{
r->underground = nextFloat(&rng) < 0.5f;
if (r->underground)
r->airpocket = 1;
else
r->airpocket = nextFloat(&rng) < 0.5f;
}
else if (r->biome == jungle)
{
r->airpocket = nextFloat(&rng) < 0.5f;
}
}
r->giant = nextFloat(&rng) < 0.05f;
if (r->giant)
{ // ruined_portal/giant_portal_1..3
r->start = 1 + nextInt(&rng, 3);
}
else
{ // ruined_portal/portal_1..10
r->start = 1 + nextInt(&rng, 10);
}
r->rotation = nextInt(&rng, 4);
r->mirror = nextFloat(&rng) < 0.5f;
return 1;
case Monument:
r->x = r->z = -29;
r->sx = r->sz = 58;
return 1;
case Igloo:
if (mc <= MC_1_12)
{
setSeed(&rng, getPopulationSeed(mc, seed, (x>>4) - 1, (z>>4) - 1));
}
r->rotation = nextInt(&rng, 4);
r->basement = nextDouble(&rng) < 0.5;
r->size = nextInt(&rng, 8) + 4;
sx = 7; sy = 5; sz = 8;
r->sy = sy;
switch (r->rotation)
{ // orientation: 0:north, 1:east, 2:south, 3:west
case 0: r->rotation = 0; r->mirror = 0; r->sx = sx; r->sz = sz; break;
case 1: r->rotation = 1; r->mirror = 0; r->sx = sz; r->sz = sx; break;
case 2: r->rotation = 0; r->mirror = 1; r->sx = sx; r->sz = sz; break;
case 3: r->rotation = 1; r->mirror = 1; r->sx = sz; r->sz = sx; break;
}
return 1;
case Desert_Pyramid:
sx = 21; sy = 15; sz = 21;
goto L_rotate_temple;
case Jungle_Temple:
sx = 12; sy = 10; sz = 15;
goto L_rotate_temple;
case Swamp_Hut:
sx = 7; sy = 7; sz = 9;
L_rotate_temple:
r->sy = sy;
if (mc <= MC_1_19)
{
r->sx = sx; r->sz = sz;
return 1;
}
switch (nextInt(&rng, 4))
{ // orientation: 0:north, 1:east, 2:south, 3:west
case 0: r->rotation = 0; r->mirror = 0; r->sx = sx; r->sz = sz; break;
case 1: r->rotation = 1; r->mirror = 0; r->sx = sz; r->sz = sx; break;
case 2: r->rotation = 0; r->mirror = 1; r->sx = sx; r->sz = sz; break;
case 3: r->rotation = 1; r->mirror = 1; r->sx = sz; r->sz = sx; break;
}
return 1;
case Geode:
if (mc >= MC_1_18)
{
StructureConfig sc;
getStructureConfig(Geode, mc, &sc);
Xoroshiro xr;
xSetSeed(&xr, getPopulationSeed(mc, seed, x&~15, z&~15) + sc.salt);
if (xNextFloat(&xr) >= sc.rarity) // rarity chance
return 0;
r->x = xNextIntJ(&xr, 16); // chunk offset X
r->z = xNextIntJ(&xr, 16); // chunk offset Z
r->x -= x & 15; // make offset relative to x and z
r->z -= z & 15;
r->y = xNextIntJ(&xr, 1+30+58) - 58; // Y-level
r->size = xNextIntJ(&xr, 2) + 3; // distribution points
xSkipN(&xr, 2);
r->cracked = xNextFloat(&xr) < 0.95;
// geodes generate somewhat sperical around a set of points with
// offset 4-6 on each coordinate
r->x += 5; r->y += 5; r->z += 5;
}
else
{
StructureConfig sc;
getStructureConfig(Geode, mc, &sc);
setSeed(&rng, getPopulationSeed(mc, seed, x&~15, z&~15) + sc.salt);
if (nextFloat(&rng) >= sc.rarity) // rarity chance
return 0;
r->x = nextInt(&rng, 16); // chunk offset X
r->z = nextInt(&rng, 16); // chunk offset Z
r->x -= x & 15;
r->z -= z & 15;
r->y = nextInt(&rng, 1+46-6) + 6; // Y-level
r->size = nextInt(&rng, 2) + 3;
skipNextN(&rng, 2);
r->cracked = nextFloat(&rng) < 0.95;
r->x += 5; r->y += 5; r->z += 5;
}
return 1;
case Trial_Chambers:
r->y = nextInt(&rng, 1+20) + -40; // Y-level
r->rotation = nextInt(&rng, 4);
r->start = nextInt(&rng, 2); // corridor/end_[12]
r->sx = 19; r->sy = 20; r->sz = 19;
//r->y += -1; // groundLevelData
switch (r->rotation)
{ // 0:0, 1:cw90, 2:cw180, 3:cw270=ccw90
case 0: break;
case 1: r->x = 1-r->sz; r->z = 0; break;
case 2: r->x = 1-r->sx; r->z = 1-r->sz; break;
case 3: r->x = 0; r->z = 1-r->sx; break;
}
return 1;
default:
return 0;
}
}
static
Piece *addEndCityPiece(PieceEnv *env, Piece *prev, int rot, int px, int py, int pz, int typ)
{
static const struct { int sx, sy, sz; const char *name; } info[] = {
{ 9, 3, 9, "base_floor"},
{ 11, 1, 11, "base_roof"},
{ 4, 5, 1, "bridge_end"},
{ 4, 6, 7, "bridge_gentle_stairs"},
{ 4, 5, 3, "bridge_piece"},
{ 4, 6, 3, "bridge_steep_stairs"},
{ 12, 3, 12, "fat_tower_base"},
{ 12, 7, 12, "fat_tower_middle"},
{ 16, 5, 16, "fat_tower_top"},
{ 11, 7, 11, "second_floor_1"},
{ 11, 7, 11, "second_floor_2"},
{ 13, 1, 13, "second_roof"},
{ 12, 23, 28, "ship"},
{ 13, 7, 13, "third_floor_1"},
{ 13, 7, 13, "third_floor_2"},
{ 15, 1, 15, "third_roof"},
{ 6, 6, 6, "tower_base"},
{ 6, 3, 6, "tower_floor"}, // unused
{ 6, 3, 6, "tower_piece"},
{ 8, 4, 8, "tower_top"},
};
Piece *p = env->list + *env->n;
(*env->n)++;
p->name = info[typ].name;
p->rot = rot;
p->depth = 0;
p->type = typ;
p->next = NULL;
Pos3 pos = {px, py, pz};
if (prev)
pos = prev->pos;
p->bb0 = p->bb1 = p->pos = pos;
p->bb1.y += info[typ].sy;
switch (rot)
{
case 0: p->bb1.x += info[typ].sx; p->bb1.z += info[typ].sz; break; // 0
case 1: p->bb0.x -= info[typ].sz; p->bb1.z += info[typ].sx; break; // 90
case 2: p->bb0.x -= info[typ].sx; p->bb0.z -= info[typ].sz; break; // 180
case 3: p->bb1.x += info[typ].sz; p->bb0.z -= info[typ].sx; break; // 270
default: UNREACHABLE();
}
if (prev)
{
int dx = 0, dy = py, dz = 0;
switch (prev->rot)
{
case 0: dx += px; dz += pz; break; // 0
case 1: dx -= pz; dz += px; break; // 90
case 2: dx -= px; dz -= pz; break; // 180
case 3: dx += pz; dz -= px; break; // 270
default: UNREACHABLE();
}
p->pos.x += dx; p->pos.y += dy; p->pos.z += dz;
p->bb0.x += dx; p->bb0.y += dy; p->bb0.z += dz;
p->bb1.x += dx; p->bb1.y += dy; p->bb1.z += dz;
}
return p;
}
static
int genPiecesRecusively(piecefunc_t gen, PieceEnv *env, Piece *current, int depth)
{
if (depth > 8)
return 0;
int i, j, n_local = 0;
PieceEnv env_local = *env;
env_local.list = env->list + *env->n;
env_local.n = &n_local;
if (!gen(&env_local, current, depth))
return 0;
int gendepth = next(env->rng, 32);
for (i = 0; i < n_local; i++)
{
Piece *p = env_local.list + i;
p->depth = gendepth;
for (j = 0; j < *env->n; j++)
{ // check for piece with bounding box collition
Piece *q = env->list + j;
if (q->bb1.x >= p->bb0.x && q->bb0.x <= p->bb1.x &&
q->bb1.z >= p->bb0.z && q->bb0.z <= p->bb1.z &&
q->bb1.y >= p->bb0.y && q->bb0.y <= p->bb1.y)
{
if (current->depth != q->depth)
return 0;
break;
}
}
}
(*env->n) += n_local;
return 1;
}
static
int genTower(PieceEnv *env, Piece *current, int depth)
{
int rot = current->rot;
int x = 3 + nextInt(env->rng, 2);
int z = 3 + nextInt(env->rng, 2);
Piece *base = current;
base = addEndCityPiece(env, base, rot, x, -3, z, TOWER_BASE);
base = addEndCityPiece(env, base, rot, 0, 7, 0, TOWER_PIECE);
Piece *floor = (nextInt(env->rng, 3) == 0 ? base : NULL);
int floorcnt = 1 + nextInt(env->rng, 3);
int i;
for (i = 0; i < floorcnt; i++)
{
base = addEndCityPiece(env, base, rot, 0, 4, 0, TOWER_PIECE);
if (i < floorcnt - 1 && next(env->rng, 1))
floor = base;
}
if (floor)
{
static const int binfo[][4] = {
{0, 1, -1, 0}, // 0
{1, 6, -1, 1}, // 90
{3, 0, -1, 5}, // 270
{2, 5, -1, 6}, // 180
};
for (i = 0; i < 4; i++)
{
if (!next(env->rng, 1))
continue;
int brot = (rot + binfo[i][0]) & 3;
Piece *bridge = addEndCityPiece(env, base, brot,
binfo[i][1], binfo[i][2], binfo[i][3], BRIDGE_END);
genPiecesRecusively(genBridge, env, bridge, depth+1);
}
}
else if (depth != 7)
{
return genPiecesRecusively(genFatTower, env, base, depth+1);
}
addEndCityPiece(env, base, rot, -1, 4, -1, TOWER_TOP);
return 1;
}
static
int genBridge(PieceEnv *env, Piece *current, int depth)
{
int rot = current->rot;
int i, y, floorcnt = 1 + nextInt(env->rng, 4);
Piece *base = current;
base = addEndCityPiece(env, base, rot, 0, 0, -4, BRIDGE_PIECE);
base->depth = -1;
for (i = y = 0; i < floorcnt; i++)
{
if (next(env->rng, 1))
{
base = addEndCityPiece(env, base, rot, 0, y, -4, BRIDGE_PIECE);
y = 0;
continue;
}
if (next(env->rng, 1))
base = addEndCityPiece(env, base, rot, 0, y, -4, BRIDGE_STEEP_STAIRS);
else
base = addEndCityPiece(env, base, rot, 0, y, -8, BRIDGE_GENTLE_STAIRS);
y = 4;
}
if (!*env->ship && nextInt(env->rng, 10 - depth) == 0)
{
int x = -8 + nextInt(env->rng, 8);
int z = -70 + nextInt(env->rng, 10);
base = addEndCityPiece(env, base, rot, x, y, z, END_SHIP);
*env->ship = 1;
}
else
{
env->y = y + 1;
if (!genPiecesRecusively(genHouseTower, env, base, depth+1))
return 0;
}
base = addEndCityPiece(env, base, (rot+2)&3, 4, y, 0, BRIDGE_END);
base->depth = -1;
return 1;
}
static
int genHouseTower(PieceEnv *env, Piece *current, int depth)
{
if (depth > 8) return 0;
int rot = current->rot;
Piece *base = current;
base = addEndCityPiece(env, base, rot, -3, env->y, -11, BASE_FLOOR);
int size = nextInt(env->rng, 3);
if (size == 0)
{
addEndCityPiece(env, base, rot, -1, 4, -1, BASE_ROOF);
return 1;
}
base = addEndCityPiece(env, base, rot, -1, 0, -1, SECOND_FLOOR_2);
if (size == 1)
{
base = addEndCityPiece(env, base, rot, -1, 8, -1, SECOND_ROOF);
}
else
{
base = addEndCityPiece(env, base, rot, -1, 4, -1, THIRD_FLOOR_2);
base = addEndCityPiece(env, base, rot, -1, 8, -1, THIRD_ROOF);
}
genPiecesRecusively(genTower, env, base, depth+1);
return 1;
}
static
int genFatTower(PieceEnv *env, Piece *current, int depth)
{
int rot = current->rot;
int i, j;
Piece *base = current;
base = addEndCityPiece(env, base, rot, -3, 4, -3, FAT_TOWER_BASE);
base = addEndCityPiece(env, base, rot, 0, 4, 0, FAT_TOWER_MIDDLE);
static const int binfo[][4] = {
{0, 4, -1, 0}, // 0
{1, 12, -1, 4}, // 90
{3, 0, -1, 8}, // 270
{2, 8, -1, 12}, // 180
};
for (j = 0; j < 2 && nextInt(env->rng, 3) != 0; j++)
{
base = addEndCityPiece(env, base, rot, 0, 8, 0, FAT_TOWER_MIDDLE);
for (i = 0; i < 4; i++)
{
if (!next(env->rng, 1))
continue;
int brot = (rot + binfo[i][0]) & 3;
Piece *bridge = addEndCityPiece(env, base, brot,
binfo[i][1], binfo[i][2], binfo[i][3], BRIDGE_END);
genPiecesRecusively(genBridge, env, bridge, depth+1);
}
}
addEndCityPiece(env, base, rot, -2, 8, -2, FAT_TOWER_TOP);
return 1;
}
int getEndCityPieces(Piece *list, uint64_t seed, int chunkX, int chunkZ)
{
uint64_t rng = chunkGenerateRnd(seed, chunkX, chunkZ);
int rot = nextInt(&rng, 4);
int ship = 0, n = 0;
PieceEnv env;
memset(&env, 0, sizeof(env));
env.list = list;
env.n = &n;
env.rng = &rng;
env.ship = &ship;
Piece *base = NULL;
int x = chunkX * 16 + 8, z = chunkZ * 16 + 8;
base = addEndCityPiece(&env, base, rot, x, 0, z, BASE_FLOOR);
base = addEndCityPiece(&env, base, rot, -1, 0, -1, SECOND_FLOOR_1);
base = addEndCityPiece(&env, base, rot, -1, 4, -1, THIRD_FLOOR_1);
base = addEndCityPiece(&env, base, rot, -1, 8, -1, THIRD_ROOF);
genPiecesRecusively(genTower, &env, base, 1);
return n;
}
static const struct
{
Pos3 offset, size;
int skip, repeatable, weight, max;
const char *name;
}
fortress_info[] = {
{{ 0, 0,0}, {18, 9,18}, 0, 0, 0, 0, "NeStart"}, // FORTRESS_START
{{-1,-3,0}, { 4, 9,18}, 0, 1,30, 0, "NeBS"}, // BRIDGE_STRAIGHT
{{-8,-3,0}, {18, 9,18}, 0, 0,10, 4, "NeBCr"}, // BRIDGE_CROSSING
{{-2, 0,0}, { 6, 8, 6}, 0, 0,10, 4, "NeRC"}, // BRIDGE_FORTIFIED_CROSSING
{{-2, 0,0}, { 6,10, 6}, 0, 0,10, 3, "NeSR"}, // BRIDGE_STAIRS
{{-2, 0,0}, { 6, 7, 8}, 0, 0, 5, 2, "NeMT"}, // BRIDGE_SPAWNER
{{-5,-3,0}, {12,13,12}, 0, 0, 5, 1, "NeCE"}, // BRIDGE_CORRIDOR_ENTRANCE
{{-1, 0,0}, { 4, 6, 4}, 0, 1,25, 0, "NeSC"}, // CORRIDOR_STRAIGHT
{{-1, 0,0}, { 4, 6, 4}, 0, 0,15, 5, "NeSCSC"}, // CORRIDOR_CROSSING
{{-1, 0,0}, { 4, 6, 4}, 1, 0, 5,10, "NeSCRT"}, // CORRIDOR_TURN_RIGHT
{{-1, 0,0}, { 4, 6, 4}, 1, 0, 5,10, "NeSCLT"}, // CORRIDOR_TURN_LEFT
{{-1,-7,0}, { 4,13, 9}, 0, 1,10, 3, "NeCCS"}, // CORRIDOR_STAIRS
{{-3, 0,0}, { 8, 6, 8}, 0, 0, 7, 2, "NeCTB"}, // CORRIDOR_T_CROSSING
{{-5,-3,0}, {12,13,12}, 0, 0, 5, 2, "NeCSR"}, // CORRIDOR_NETHER_WART
{{-1,-3,0}, { 4, 9, 7}, 1, 0, 0, 0, "NeBEF"}, // FORTRESS_END
};
static
Piece *addFortressPiece(PieceEnv *env, int typ, int x, int y, int z, int depth, int facing, int pending)
{
Pos3 pos = {x, y, z};
Pos3 b0 = pos, b1 = pos;
Pos3 d0 = fortress_info[typ].offset, d1 = fortress_info[typ].size;
b0.y += d0.y;
b1.y += d0.y+d1.y;
switch (facing)
{
case 0: // 0, north
b0.x += d0.x; b0.z += d0.z-d1.z;
b1.x += d0.x+d1.x; b1.z += d0.z;
break;
case 1: // 90, east
b0.x += d0.z; b0.z += d0.x;
b1.x += d0.z+d1.z; b1.z += d0.x+d1.x;
break;
case 2: // 180, south
b0.x += d0.x; b0.z += d0.z;
b1.x += d0.x+d1.x; b1.z += d0.z+d1.z;
break;
case 3: // 270, west
b0.x += d0.z-d1.z; b0.z += d0.x;
b1.x += d0.z; b1.z += d0.x+d1.x;
break;
}
Piece *p = env->list + *env->n;
p->name = fortress_info[typ].name;
p->pos = pos;
p->bb0 = b0;
p->bb1 = b1;
p->rot = facing;
p->depth = depth;
p->type = typ;
p->next = NULL;
int i, n = *env->n;
for (i = 0; i < n; i++)
{
Piece *q = env->list + i;
if (q->bb1.x >= p->bb0.x && q->bb0.x <= p->bb1.x &&
q->bb1.z >= p->bb0.z && q->bb0.z <= p->bb1.z &&
q->bb1.y >= p->bb0.y && q->bb0.y <= p->bb1.y)
{
return NULL; // collision
}
}
// accept the piece and append it to the processing front
skipNextN(env->rng, fortress_info[typ].skip);
//int queue = 0;
if (pending)
{
(*env->n)++;
env->ntyp[typ]++;
if (typ != FORTRESS_END)
env->typlast = typ;
Piece *q = env->list;
while (q->next) {
q = q->next; //queue++;
}
q->next = p;
}
//printf("[%3d] typ=%2d @(%4d %4d %4d) f=%d p=%d queue=%2d rng:%ld\n",
// (*env->n-1), typ, b0.x, b0.y, b0.z, facing, pending, queue, *env->rng);
//fflush(stdout);
return p;
}
static
void extendFortress(PieceEnv *env, Piece *p, int offh, int offv, int turn, int corridor)
{
int x, y, z, t, i;
int depth = p->depth + 1;
int facing = p->rot;
int typ0 = corridor ? CORRIDOR_STRAIGHT : BRIDGE_STRAIGHT;
int typ1 = typ0 + (corridor ? 7 : 6);
int valid = -1;
int weight_tot = 0;
y = p->bb0.y + offv;
if (turn == 0) { // forward
switch (facing) {
case 0: x = p->bb0.x+offh; z = p->bb0.z-1; break;
case 1: x = p->bb1.x+1; z = p->bb0.z+offh; break;
case 2: x = p->bb0.x+offh; z = p->bb1.z+1; break;
case 3: x = p->bb0.x-1; z = p->bb0.z+offh; break;
default: UNREACHABLE();
}
} else if (turn == -1) { // left
if (facing & 1) { x = p->bb0.x+offh; z = p->bb0.z-1; facing = 0; }
else { x = p->bb0.x-1; z = p->bb0.z+offh; facing = 3; }
} else if (turn == +1) { // right
if (facing & 1) { x = p->bb0.x+offh, z = p->bb1.z+1; facing = 2; }
else { x = p->bb1.x+1; z = p->bb0.z+offh; facing = 1; }
} else UNREACHABLE();
if (IABS(x - env->list->bb0.x) > 112 || IABS(z - env->list->bb0.z) > 112)
goto L_end;
for (valid = 0, t = typ0; t < typ1; t++)
{
int max = fortress_info[t].max;
if (max > 0 && env->ntyp[t] >= max)
continue;
if (max > 0)
valid = 1;
weight_tot += fortress_info[t].weight;
}
if (valid == 0 || weight_tot <= 0 || depth > 30)
goto L_end;
for (i = 0; i < 5; i++)
{
int n = nextInt(env->rng, weight_tot);
for (t = typ0; t < typ1; t++)
{
int max = fortress_info[t].max;
if (max > 0 && env->ntyp[t] >= max)
continue;
n -= fortress_info[t].weight;
if (n >= 0)
continue;
if (env->typlast == t && !fortress_info[t].repeatable)
break;
if (addFortressPiece(env, t, x, y, z, depth, facing, 1) != NULL)
return;
}
}
L_end:
addFortressPiece(env, FORTRESS_END, x, y, z, depth, facing, valid >= 0);
}
static
void extendFortressPiece(PieceEnv *env, Piece *p)
{
if (p->type == BRIDGE_STRAIGHT) {
extendFortress(env, p, 1, 3, 0, 0);
} else if (p->type == BRIDGE_CROSSING || p->type == FORTRESS_START) {
extendFortress(env, p, 8, 3, 0, 0);
extendFortress(env, p, 8, 3, -1, 0);
extendFortress(env, p, 8, 3, 1, 0);
} else if (p->type == BRIDGE_FORTIFIED_CROSSING) {
extendFortress(env, p, 2, 0, 0, 0);
extendFortress(env, p, 2, 0, -1, 0);
extendFortress(env, p, 2, 0, 1, 0);
} else if (p->type == BRIDGE_STAIRS) {
extendFortress(env, p, 2, 6, 1, 0);
} else if (p->type == BRIDGE_CORRIDOR_ENTRANCE) {
extendFortress(env, p, 5, 3, 0, 1);
} else if (p->type == CORRIDOR_STRAIGHT) {
extendFortress(env, p, 1, 0, 0, 1);
} else if (p->type == CORRIDOR_CROSSING) {
extendFortress(env, p, 1, 0, 0, 1);
extendFortress(env, p, 1, 0, -1, 1);
extendFortress(env, p, 1, 0, 1, 1);
} else if (p->type == CORRIDOR_TURN_RIGHT) {
extendFortress(env, p, 1, 0, 1, 1);
} else if (p->type == CORRIDOR_TURN_LEFT) {
extendFortress(env, p, 1, 0, -1, 1);
} else if (p->type == CORRIDOR_STAIRS) {
extendFortress(env, p, 1, 0, 0, 1);
} else if (p->type == CORRIDOR_T_CROSSING) {
int h = (p->rot == 0 || p->rot == 3) ? 5 : 1;
extendFortress(env, p, h, 0, -1, nextInt(env->rng, 8) != 0);
extendFortress(env, p, h, 0, 1, nextInt(env->rng, 8) != 0);
} else if (p->type == CORRIDOR_NETHER_WART) {
extendFortress(env, p, 5, 3, 0, 1);
extendFortress(env, p, 5, 11, 0, 1);
}
}
int getFortressPieces(Piece *list, int n, int mc, uint64_t seed, int chunkX, int chunkZ)
{
uint64_t rng = seed;
if (mc <= MC_1_15)
{
setAttemptSeed(&rng, chunkX, chunkZ);
nextInt(&rng, 3);
nextInt(&rng, 8);
nextInt(&rng, 8);
}
else
{
rng = chunkGenerateRnd(seed, chunkX, chunkZ);
}
int count = 1;
PieceEnv env;
memset(&env, 0, sizeof(env));
env.list = list;
env.n = &count;
env.rng = &rng;
env.ntyp[0] = 1;
env.typlast = 0;
env.nmax = n;
Piece *p = list;
Pos3 pos = {chunkX * 16 + 2, 64, chunkZ * 16 + 2};
p->name = fortress_info[0].name;
p->bb0 = p->bb1 = p->pos = pos;
p->bb1.x += fortress_info[0].size.x;
p->bb1.y += fortress_info[0].size.y;
p->bb1.z += fortress_info[0].size.z;
p->rot = nextInt(&rng, 4);
p->depth = 0;
p->type = 0;
p->next = NULL;
extendFortressPiece(&env, p);
while (list->next)
{
Piece *q = list;
int len = 0;
while (q->next)
{
q = q->next;
len++;
}
int i = nextInt(&rng, len);
for (p = list, q = list->next; i-->0; p = q, q = q->next);
p->next = q->next;
q->next = NULL;
extendFortressPiece(&env, q);
}
return count;
}
uint64_t getHouseList(int *out, uint64_t seed, int chunkX, int chunkZ)
{
uint64_t rng = chunkGenerateRnd(seed, chunkX, chunkZ);
skipNextN(&rng, 1);
out[HouseSmall] = nextInt(&rng, 4 - 2 + 1) + 2;
out[Church] = nextInt(&rng, 1 - 0 + 1) + 0;
out[Library] = nextInt(&rng, 2 - 0 + 1) + 0;
out[WoodHut] = nextInt(&rng, 5 - 2 + 1) + 2;
out[Butcher] = nextInt(&rng, 2 - 0 + 1) + 0;
out[FarmLarge] = nextInt(&rng, 4 - 1 + 1) + 1;
out[FarmSmall] = nextInt(&rng, 4 - 2 + 1) + 2;
out[Blacksmith] = nextInt(&rng, 1 - 0 + 1) + 0;
out[HouseLarge] = nextInt(&rng, 3 - 0 + 1) + 0;
return rng;
}
void getFixedEndGateways(int mc, uint64_t seed, Pos src[20])
{
(void) mc;
static const Pos fixed[20] = {
{ 96, 0}, { 91, 29}, { 77, 56}, { 56, 77}, { 29, 91},
{ -1, 96}, {-30, 91}, {-57, 77}, {-78, 56}, {-92, 29},
{-96, -1}, {-92,-30}, {-78,-57}, {-57,-78}, {-30,-92},
{ 0,-96}, { 29,-92}, { 56,-78}, { 77,-57}, { 91,-30},
};
uint8_t order[] = {
19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
};
int i;
uint64_t rng = 0;
setSeed(&rng, seed);
for (i = 0; i < 20; i++)
{
uint8_t j = 19 - nextInt(&rng, 20-i);
uint8_t tmp = order[i];
order[i] = order[j];
order[j] = tmp;
}
for (i = 0; i < 20; i++)
src[i] = fixed[ order[i] ];
}
Pos getLinkedGatewayChunk(const EndNoise *en, const SurfaceNoise *sn, uint64_t seed,
Pos src, Pos *dst)
{
double invr = 1.0 / sqrt(src.x * src.x + src.z * src.z);
double dx = src.x * invr;
double dz = src.z * invr;
double px = dx * 1024;
double pz = dz * 1024;
dx *= 16;
dz *= 16;
int i;
Pos c;
c.x = (int) floor(px) >> 4;
c.z = (int) floor(pz) >> 4;
if (isEndChunkEmpty(en, sn, seed, c.x, c.z))
{ // look forward for the first non-empty chunk
for (i = 0; i < 15; i++)
{
int qx = (int) floor(px += dx) >> 4;
int qz = (int) floor(pz += dz) >> 4;
if (qx == c.x && qz == c.z)
continue;
c.x = qx;
c.z = qz;
if (!isEndChunkEmpty(en, sn, seed, c.x, c.z))
break;
}
}
else
{ // look backward for the last non-empty chunk
for (i = 0; i < 15; i++)
{
int qx = (int) floor(px -= dx) >> 4;
int qz = (int) floor(pz -= dz) >> 4;
if (isEndChunkEmpty(en, sn, seed, qx, qz))
break;
c.x = qx;
c.z = qz;
}
}
if (dst)
{
dst->x = (int) floor(px);
dst->z = (int) floor(pz);
}
return c;
}
Pos getLinkedGatewayPos(const EndNoise *en, const SurfaceNoise *sn, uint64_t seed, Pos src)
{
float y[33*33]; // buffer for [16][16] and [33][33]
int ymin = 0;
int i, j;
Pos dst;
Pos c = getLinkedGatewayChunk(en, sn, seed, src, &dst);
if (en->mc > MC_1_16)
{
// The original java implementation has a bug where the result
// variable for the in-chunk block search is assigned a reference
// to the mutable iterator, which ends up as the last iteration
// position and discards the found location.
dst.x = c.x * 16 + 15;
dst.z = c.z * 16 + 15;
}
else
{
mapEndSurfaceHeight(y, en, sn, c.x*16, c.z*16, 16, 16, 1, 30);
mapEndIslandHeight(y, en, seed, c.x*16, c.z*16, 16, 16, 1);
uint64_t d = 0;
for (j = 0; j < 16; j++)
{
for (i = 0; i < 16; i++)
{
int v = (int) y[j*16 + i];
if (v < 30) continue;
uint64_t dx = 16*c.x + i;
uint64_t dz = 16*c.z + j;
uint64_t dr = dx*dx + dz*dz + v*v;
if (dr > d)
{
d = dr;
dst.x = dx;
dst.z = dz;
}
}
}
// use the previous result to retrieve the minimum y-level we know,
// we can skip generation of surfaces that are lower than this
for (i = 0; i < 16*16; i++)
if (y[i] > ymin)
ymin = (int) floor(y[i]);
}
Pos sp = { dst.x-16, dst.z-16 };
// checking end islands is much cheaper than surface height generation, so
// we can also skip surface generation lower than the highest island around
memset(y, 0, sizeof(float)*33*33);
mapEndIslandHeight(y, en, seed, sp.x, sp.z, 33, 33, 1);
for (i = 0; i < 33*33; i++)
if (y[i] > ymin)
ymin = (int) floor(y[i]);
mapEndSurfaceHeight(y, en, sn, sp.x, sp.z, 33, 33, 1, ymin);
mapEndIslandHeight(y, en, seed, sp.x, sp.z, 33, 33, 1);
float v = -1;
for (i = 0; i < 33; i++)
{
for (j = 0; j < 33; j++)
{
if (y[j*33 + i] <= v)
continue;
v = y[j*33 + i];
dst.x = sp.x + i;
dst.z = sp.z + j;
}
}
return dst;
}
//==============================================================================
// Seed Filters
//==============================================================================
double inverf(double x)
{ // compute the inverse error function via newton's method
double t = x, dt = 1;
while (fabs(dt) > FLT_EPSILON)
{
dt = 0.5 * sqrt(PI) * (erf(t) - x) / exp(-t*t);
t -= dt;
}
return t;
}
void wilson(double n, double p, double z, double *lo, double *hi)
{ // compute the wilson score interval
double s = z * z / n;
double t = 1 / (1 + s);
double w = t * (p + 0.5 * s);
double d = t * z * sqrt( (p*(1-p) + 0.25*s) / n ) + FLT_EPSILON;
*lo = w - d;
*hi = w + d;
}
int monteCarloBiomes(
Generator * g,
Range r,
uint64_t * rng,
double coverage,
double confidence,
int (*eval)(Generator *g, int scale, int x, int y, int z, void*),
void * data
)
{
if (r.sy == 0)
r.sy = 1;
struct touple { int x, y, z; } *buf = 0;
size_t n = (size_t)r.sx*r.sy*r.sz;
// z-score (i.e. probit, or standard deviations) for the confidence
double zscore = sqrt(2.0) * inverf(confidence);
// One standard deviation is approximately given by sqrt(n) elements,
// hence we will take zscore * sqrt(n) as the maximum number of samples
// to reach the desired confidence. This gives us a wilson score interval
// for the upper and lower bound of successes we aim for.
double wn = zscore * sqrt(n);
double wlo, whi;
wilson(wn, coverage, zscore, &wlo, &whi);
// When the number of samples approaches the total number elements,
// we can avoid repeated samples by shuffling a buffer.
// (TODO: adjust for hypergeometric distribution?)
if (n < 4 * wn && n < INT_MAX)
buf = (struct touple*) malloc(n * sizeof(*buf));
if (buf)
{
size_t idx = 0;
int i, k, j;
for (k = 0; k < r.sy; k++)
{
for (j = 0; j < r.sz; j++)
{
for (i = 0; i < r.sx; i++)
{
buf[idx].x = i;
buf[idx].y = k;
buf[idx].z = j;
idx++;
}
}
}
}
size_t i = 0;
double m = 0; // number of samples
double x = 0; // number of successes
int ret = 1;
// iterate over the area in a random order
for (i = 0; i < n; i++)
{
struct touple t;
if (buf)
{
int j = n - i;
int k = nextInt(rng, j);
t = buf[k];
if (k != j-1)
{
buf[k] = buf[j-1];
buf[j-1] = t;
}
}
else
{
t.x = nextInt(rng, r.sx);
t.y = nextInt(rng, r.sy);
t.z = nextInt(rng, r.sz);
}
int status = eval(g, r.scale, r.x+t.x, r.y+t.y, r.z+t.z, data);
if (status == -1)
continue;
else if (status == 0)
;
else if (status == 1)
x += 1.0;
else
{
ret = 0;
break;
}
m += 1.0;
// check if we can abort early with the current confidence interval
double per_m = 1.0 / m;
double lo, hi;
wilson(m, x * per_m, zscore, &lo, &hi);
if (lo - per_m > coverage)
{
ret = 1;
break;
}
if (hi + per_m < coverage)
{
ret = 0;
break;
}
if (hi - lo < whi - wlo)
{ // should occur around i ~ wn
ret = x * per_m > coverage;
break;
}
}
if (buf)
free(buf);
return ret;
}
void setupBiomeFilter(
BiomeFilter *bf,
int mc, uint32_t flags,
const int *required, int requiredLen,
const int *excluded, int excludedLen,
const int *matchany, int matchanyLen)
{
int i, id;
memset(bf, 0, sizeof(*bf));
bf->flags = flags;
// The matchany set is built from the intersection of each member,
// individually treated as a required biome. The search can be aborted with
// a positive result, as soon as any of those biomes is encountered.
for (i = 0; i < matchanyLen; i++)
{
id = matchany[i];
if (id < 128)
bf->biomeToPick |= (1ULL << id);
else
bf->biomeToPickM |= (1ULL << (id-128));
BiomeFilter ibf;
setupBiomeFilter(&ibf, mc, 0, &id, 1, 0, 0, 0, 0);
if (i == 0)
{
bf->tempsToFind = ibf.tempsToFind;
bf->otempToFind = ibf.otempToFind;
bf->majorToFind = ibf.majorToFind;
bf->edgesToFind = ibf.edgesToFind;
bf->raresToFind = ibf.raresToFind;
bf->raresToFindM = ibf.raresToFindM;
bf->shoreToFind = ibf.shoreToFind;
bf->shoreToFindM = ibf.shoreToFindM;
bf->riverToFind = ibf.riverToFind;
bf->riverToFindM = ibf.riverToFindM;
bf->oceanToFind = ibf.oceanToFind;
}
else
{
bf->tempsToFind &= ibf.tempsToFind;
bf->otempToFind &= ibf.otempToFind;
bf->majorToFind &= ibf.majorToFind;
bf->edgesToFind &= ibf.edgesToFind;
bf->raresToFind &= ibf.raresToFind;
bf->raresToFindM &= ibf.raresToFindM;
bf->shoreToFind &= ibf.shoreToFind;
bf->shoreToFindM &= ibf.shoreToFindM;
bf->riverToFind &= ibf.riverToFind;
bf->riverToFindM &= ibf.riverToFindM;
bf->oceanToFind &= ibf.oceanToFind;
}
}
// The excluded set is built by checking which of the biomes from each
// layer have the potential to yield something other than one of the
// excluded biomes.
for (i = 0; i < excludedLen; i++)
{
id = excluded[i];
if (id & ~0xbf) // i.e. not in ranges [0,64),[128,192)
{
fprintf(stderr, "setupBiomeFilter: biomeID=%d not supported.\n", id);
exit(-1);
}
if (id < 128)
bf->biomeToExcl |= (1ULL << id);
else
bf->biomeToExclM |= (1ULL << (id-128));
}
if (excludedLen && mc >= MC_1_7)
{ // TODO: this does not fully work yet...
uint64_t b, m;
int j;
for (j = Oceanic; j <= Freezing+Special; j++)
{
b = m = 0;
int temp = (j <= Freezing) ? j : ((j - Special) | 0xf00);
genPotential(&b, &m, L_SPECIAL_1024, mc, flags, temp);
if ((bf->biomeToExcl & b) || (bf->biomeToExclM & m))
bf->tempsToExcl |= (1ULL << j);
}
for (j = 0; j < 256; j++)
{
if (!isOverworld(mc, j))
continue;
if (j < 128)
{
b = m = 0;
genPotential(&b, &m, L_BIOME_256, mc, flags, j);
if ((~bf->biomeToExcl & b) || (~bf->biomeToExclM & m))
bf->majorToExcl |= (1ULL << j);
}
b = m = 0;
genPotential(&b, &m, L_BIOME_EDGE_64, mc, flags, j);
if ((~bf->biomeToExcl & b) || (~bf->biomeToExclM & m))
{
if (j < 128)
bf->edgesToExcl |= (1ULL << j);
else // bamboo_jungle are mapped onto & 0x3F
bf->edgesToExcl |= (1ULL << (j-128));
}
b = m = 0;
genPotential(&b, &m, L_SUNFLOWER_64, mc, flags, j);
if ((~bf->biomeToExcl & b) || (~bf->biomeToExclM & m))
{
if (j < 128)
bf->raresToExcl |= (1ULL << j);
else
bf->raresToExclM |= (1ULL << (j-128));
}
b = m = 0;
genPotential(&b, &m, L_SHORE_16, mc, flags, j);
if ((~bf->biomeToExcl & b) || (~bf->biomeToExclM & m))
{
if (j < 128)
bf->shoreToExcl |= (1ULL << j);
else
bf->shoreToExclM |= (1ULL << (j-128));
}
b = m = 0;
genPotential(&b, &m, L_RIVER_MIX_4, mc, flags, j);
if ((~bf->biomeToExcl & b) || (~bf->biomeToExclM & m))
{
if (j < 128)
bf->riverToExcl |= (1ULL << j);
else
bf->riverToExclM |= (1ULL << (j-128));
}
}
}
// The required set is built from the biomes that should be present at each
// of the layers. The search can be aborted with a negative result as soon
// as a biome is missing at the corresponding layer.
for (i = 0; i < requiredLen; i++)
{
id = required[i];
if (id & ~0xbf) // i.e. not in ranges [0,64),[128,192)
{
fprintf(stderr, "setupBiomeFilter: biomeID=%d not supported.\n", id);
exit(-1);
}
switch (id)
{
case mushroom_fields:
// mushroom shores can generate with hills and at rivers
bf->raresToFind |= (1ULL << mushroom_fields);
// fall through
case mushroom_field_shore:
bf->tempsToFind |= (1ULL << Oceanic);
bf->majorToFind |= (1ULL << mushroom_fields);
bf->riverToFind |= (1ULL << id);
break;
case badlands_plateau:
case wooded_badlands_plateau:
case badlands:
case eroded_badlands:
case modified_badlands_plateau:
case modified_wooded_badlands_plateau:
bf->tempsToFind |= (1ULL << (Warm+Special));
if (id == badlands_plateau || id == modified_badlands_plateau)
bf->majorToFind |= (1ULL << badlands_plateau);
if (id == wooded_badlands_plateau || id == modified_wooded_badlands_plateau)
bf->majorToFind |= (1ULL << wooded_badlands_plateau);
if (id < 128) {
bf->raresToFind |= (1ULL << id);
bf->riverToFind |= (1ULL << id);
} else {
bf->raresToFindM |= (1ULL << (id-128));
bf->riverToFindM |= (1ULL << (id-128));
}
break;
case jungle:
case jungle_edge:
case jungle_hills:
case modified_jungle:
case modified_jungle_edge:
case bamboo_jungle:
case bamboo_jungle_hills:
bf->tempsToFind |= (1ULL << (Lush+Special));
bf->majorToFind |= (1ULL << jungle);
if (id == bamboo_jungle || id == bamboo_jungle_hills) {
// bamboo%64 are End biomes, so we can reuse the edgesToFind
bf->edgesToFind |= (1ULL << (bamboo_jungle & 0x3f));
bf->raresToFindM |= (1ULL << (id-128));
bf->riverToFindM |= (1ULL << (id-128));
} else if (id == jungle_edge) {
// un-modified jungle_edge can be created at shore layer
bf->riverToFind |= (1ULL << jungle_edge);
} else {
if (id == modified_jungle_edge)
bf->edgesToFind |= (1ULL << jungle_edge);
else
bf->edgesToFind |= (1ULL << jungle);
if (id < 128) {
bf->raresToFind |= (1ULL << id);
bf->riverToFind |= (1ULL << id);
} else {
bf->raresToFindM |= (1ULL << (id-128));
bf->riverToFindM |= (1ULL << (id-128));
}
}
break;
case giant_tree_taiga:
case giant_tree_taiga_hills:
case giant_spruce_taiga:
case giant_spruce_taiga_hills:
bf->tempsToFind |= (1ULL << (Cold+Special));
bf->majorToFind |= (1ULL << giant_tree_taiga);
bf->edgesToFind |= (1ULL << giant_tree_taiga);
if (id < 128) {
bf->raresToFind |= (1ULL << id);
bf->riverToFind |= (1ULL << id);
} else {
bf->raresToFindM |= (1ULL << (id-128));
bf->riverToFindM |= (1ULL << (id-128));
}
break;
case savanna:
case savanna_plateau:
case shattered_savanna:
case shattered_savanna_plateau:
case desert_hills:
case desert_lakes:
bf->tempsToFind |= (1ULL << Warm);
if (id == desert_hills || id == desert_lakes) {
bf->majorToFind |= (1ULL << desert);
bf->edgesToFind |= (1ULL << desert);
} else {
bf->majorToFind |= (1ULL << savanna);
bf->edgesToFind |= (1ULL << savanna);
}
if (id < 128) {
bf->raresToFind |= (1ULL << id);
bf->riverToFind |= (1ULL << id);
} else {
bf->raresToFindM |= (1ULL << (id-128));
bf->riverToFindM |= (1ULL << (id-128));
}
break;
case dark_forest:
case dark_forest_hills:
case birch_forest:
case birch_forest_hills:
case tall_birch_forest:
case tall_birch_hills:
case swamp:
case swamp_hills:
bf->tempsToFind |= (1ULL << Lush);
if (id == dark_forest || id == dark_forest_hills) {
bf->majorToFind |= (1ULL << dark_forest);
bf->edgesToFind |= (1ULL << dark_forest);
}
else if (id == birch_forest || id == birch_forest_hills ||
id == tall_birch_forest || id == tall_birch_hills) {
bf->majorToFind |= (1ULL << birch_forest);
bf->edgesToFind |= (1ULL << birch_forest);
}
else if (id == swamp || id == swamp_hills) {
bf->majorToFind |= (1ULL << swamp);
bf->edgesToFind |= (1ULL << swamp);
}
if (id < 128) {
bf->raresToFind |= (1ULL << id);
bf->riverToFind |= (1ULL << id);
} else {
bf->raresToFindM |= (1ULL << (id-128));
bf->riverToFindM |= (1ULL << (id-128));
}
break;
case snowy_taiga:
case snowy_taiga_hills:
case snowy_taiga_mountains:
case snowy_tundra:
case snowy_mountains:
case ice_spikes:
case frozen_river:
bf->tempsToFind |= (1ULL << Freezing);
if (id == snowy_taiga || id == snowy_taiga_hills ||
id == snowy_taiga_mountains)
bf->edgesToFind |= (1ULL << snowy_taiga);
else
bf->edgesToFind |= (1ULL << snowy_tundra);
if (id == frozen_river) {
bf->raresToFind |= (1ULL << snowy_tundra);
bf->riverToFind |= (1ULL << id);
} else if (id < 128) {
bf->raresToFind |= (1ULL << id);
bf->riverToFind |= (1ULL << id);
} else {
bf->raresToFindM |= (1ULL << (id-128));
bf->riverToFindM |= (1ULL << (id-128));
}
break;
case sunflower_plains:
bf->raresToFindM |= (1ULL << (id-128));
bf->riverToFindM |= (1ULL << (id-128));
break;
case snowy_beach:
bf->tempsToFind |= (1ULL << Freezing);
// fall through
case beach:
case stone_shore:
bf->riverToFind |= (1ULL << id);
break;
case mountains:
bf->majorToFind |= (1ULL << mountains);
// fall through
case wooded_mountains:
bf->raresToFind |= (1ULL << id);
bf->riverToFind |= (1ULL << id);
break;
case gravelly_mountains:
bf->majorToFind |= (1ULL << mountains);
// fall through
case modified_gravelly_mountains:
bf->raresToFindM |= (1ULL << (id-128));
bf->riverToFindM |= (1ULL << (id-128));
break;
case taiga:
case taiga_hills:
bf->edgesToFind |= (1ULL << taiga);
bf->raresToFind |= (1ULL << id);
bf->riverToFind |= (1ULL << id);
break;
case taiga_mountains:
bf->edgesToFind |= (1ULL << taiga);
bf->raresToFindM |= (1ULL << (id-128));
bf->riverToFindM |= (1ULL << (id-128));
break;
case plains:
case forest:
case wooded_hills:
bf->raresToFind |= (1ULL << id);
bf->riverToFind |= (1ULL << id);
break;
case flower_forest:
bf->raresToFindM |= (1ULL << (id-128));
bf->riverToFindM |= (1ULL << (id-128));
break;
case desert: // can generate at shore layer
bf->riverToFind |= (1ULL << id);
break;
default:
if (isOceanic(id)) {
bf->tempsToFind |= (1ULL << Oceanic);
bf->oceanToFind |= (1ULL << id);
if (isShallowOcean(id)) {
if (id != lukewarm_ocean && id != cold_ocean)
bf->otempToFind |= (1ULL << id);
} else {
if (id == deep_warm_ocean)
bf->otempToFind |= (1ULL << warm_ocean);
else if (id == deep_ocean)
bf->otempToFind |= (1ULL << ocean);
else if (id == deep_frozen_ocean)
bf->otempToFind |= (1ULL << frozen_ocean);
if (!(flags & FORCE_OCEAN_VARIANTS)) {
bf->raresToFind |= (1ULL << deep_ocean);
bf->riverToFind |= (1ULL << deep_ocean);
}
}
} else {
if (id < 64)
bf->riverToFind |= (1ULL << id);
else
bf->riverToFindM |= (1ULL << (id-128));
}
break;
}
}
bf->biomeToFind = bf->riverToFind;
bf->biomeToFind &= ~((1ULL << ocean) | (1ULL << deep_ocean));
bf->biomeToFind |= bf->oceanToFind;
bf->biomeToFindM = bf->riverToFindM;
bf->shoreToFind = bf->riverToFind;
bf->shoreToFind &= ~((1ULL << river) | (1ULL << frozen_river));
bf->shoreToFindM = bf->riverToFindM;
bf->specialCnt = 0;
bf->specialCnt += !!(bf->tempsToFind & (1ULL << (Warm+Special)));
bf->specialCnt += !!(bf->tempsToFind & (1ULL << (Lush+Special)));
bf->specialCnt += !!(bf->tempsToFind & (1ULL << (Cold+Special)));
}
typedef struct {
Generator *g;
int *ids;
Range r;
uint32_t flags;
uint64_t b, m;
uint64_t breq, mreq;
uint64_t bexc, mexc;
uint64_t bany, many;
volatile char *stop;
} gdt_info_t;
static int f_graddesc_test(void *data, int x, int z, double p)
{
(void) p;
gdt_info_t *info = (gdt_info_t *) data;
if (info->stop && *info->stop)
return 1;
int idx = (z - info->r.z) * info->r.sx + (x - info->r.x);
if (info->ids[idx] != -1)
return 0;
int id = getBiomeAt(info->g, info->r.scale, x, info->r.y, z);
info->ids[idx] = id;
if (id < 128) info->b |= (1ULL << id);
else info->m |= (1ULL << (id-128));
// check if we know enough to stop
int match_exc = (info->bexc|info->mexc) == 0;
int match_any = (info->bany|info->many) == 0;
int match_req = (info->breq|info->mreq) == 0;
if (!match_exc && ((info->b & info->bexc) || (info->m & info->mexc)))
return 1; // encountered an excluded biome -> stop
match_any |= ((info->b & info->bany) || (info->m & info->many));
match_req |= ((info->b & info->breq) == info->breq &&
(info->m & info->mreq) == info->mreq);
if (match_exc && match_any && match_req)
return 1; // all conditions met -> stop
return 0;
}
int checkForBiomes(
Generator * g,
int * cache,
Range r,
int dim,
uint64_t seed,
const BiomeFilter * filter,
volatile char * stop
)
{
if (stop && *stop)
return 0;
int i, j, k, ret;
if (r.sy == 0)
r.sy = 1;
if (g->mc <= MC_B1_7)
{ // TODO: optimize
int *ids;
if (cache)
ids = cache;
else
ids = allocCache(g, r);
if (g->dim != dim || g->seed != seed)
applySeed(g, dim, seed);
genBiomes(g, ids, r);
uint64_t b = 0;
for (i = 0; i < r.sx*r.sz; i++)
b |= (1ULL << ids[i]);
if (ids != cache)
free(ids);
int match_exc = (filter->biomeToExcl) == 0;
int match_any = (filter->biomeToPick) == 0;
int match_req = (filter->biomeToFind) == 0;
match_exc |= (b & filter->biomeToExcl) == 0;
match_any |= (b & filter->biomeToPick) != 0;
match_req |= (b & filter->biomeToFind) == filter->biomeToFind;
return match_exc && match_any && match_req;
}
if (g->mc <= MC_1_17 && dim == DIM_OVERWORLD)
{
Layer *entry = (Layer*) getLayerForScale(g, r.scale);
ret = checkForBiomesAtLayer(&g->ls, entry, cache, seed,
r.x, r.z, r.sx, r.sz, filter);
if (ret == 0 && r.sy > 1 && cache)
{
for (i = 0; i < r.sy; i++)
{ // overworld has no vertical noise: expanding 2D into 3D
for (j = 0; j < r.sx*r.sz; j++)
cache[i*r.sx*r.sz + j] = cache[j];
}
}
return ret;
}
int *ids, id;
if (cache)
ids = cache;
else
ids = allocCache(g, r);
if (g->dim != dim || g->seed != seed)
{
applySeed(g, dim, seed);
}
gdt_info_t info[1];
info->g = g;
info->ids = ids;
info->r = r;
info->flags = filter->flags;
info->b = info->m = 0;
info->breq = filter->biomeToFind;
info->mreq = filter->biomeToFindM;
info->bexc = filter->biomeToExcl;
info->mexc = filter->biomeToExclM;
info->bany = filter->biomeToPick;
info->many = filter->biomeToPickM;
info->stop = stop;
ret = 0;
memset(ids, -1, r.sx * r.sz * sizeof(int));
int n = r.sx*r.sy*r.sz;
int trials = n;
struct touple { int i, x, y, z; } *buf = NULL;
if (r.scale == 4 && r.sx * r.sz > 64 && dim == DIM_OVERWORLD)
{
// Do a gradient descent to find the min/max of some climate parameters
// and check the biomes along the way. This has a much better chance
// of finding the biomes with exteme climates early.
double tmin, tmax;
int err = 0;
do
{
err = getParaRange(&g->bn.climate[NP_TEMPERATURE], &tmin, &tmax,
r.x, r.z, r.sx, r.sz, info, f_graddesc_test);
if (err) break;
err = getParaRange(&g->bn.climate[NP_HUMIDITY], &tmin, &tmax,
r.x, r.z, r.sx, r.sz, info, f_graddesc_test);
if (err) break;
err = getParaRange(&g->bn.climate[NP_EROSION], &tmin, &tmax,
r.x, r.z, r.sx, r.sz, info, f_graddesc_test);
if (err) break;
//err = getParaRange(&g->bn.climate[NP_CONTINENTALNESS], &tmin, &tmax,
// r.x, r.z, r.sx, r.sz, info, f_graddesc_test);
//if (err) break;
//err = getParaRange(&g->bn.climate[NP_WEIRDNESS], &tmin, &tmax,
// r.x, r.z, r.sx, r.sz, info, f_graddesc_test);
//if (err) break;
}
while (0);
if (err || (stop && *stop) || (filter->flags & BF_APPROX))
goto L_end;
}
// We'll shuffle the coordinates so we'll generate the biomes in a
// stochasitc mannor.
buf = (struct touple*) malloc(n * sizeof(*buf));
id = 0;
for (k = 0; k < r.sy; k++)
{
for (j = 0; j < r.sz; j++)
{
for (i = 0; i < r.sx; i++)
{
buf[id].i = id;
buf[id].x = i;
buf[id].y = k;
buf[id].z = j;
id++;
}
}
}
// Determine a number of trials that gives a decent chance to sample all
// the biomes that are present, assuming a completely random and
// independent biome distribution. (This is actually not at all the case.)
if (filter->flags & BF_APPROX)
{
int t = 400 + (int) sqrt(n);
if (trials > t)
trials = t;
}
for (i = 0; i < trials; i++)
{
struct touple t;
j = n - i;
k = rand() % j;
t = buf[k];
if (k != j-1)
{
buf[k] = buf[j-1];
buf[j-1] = t;
}
if (stop && *stop)
break;
if (t.y == 0 && info->ids[t.i] != -1)
continue;
id = getBiomeAt(g, r.scale, r.x+t.x, r.y+t.y, r.z+t.z);
info->ids[t.i] = id;
if (id < 128) info->b |= (1ULL << id);
else info->m |= (1ULL << (id-128));
// check if we know enough to yield a result
int match_exc = (info->bexc|info->mexc) == 0;
int match_any = (info->bany|info->many) == 0;
int match_req = (info->breq|info->mreq) == 0;
if (!match_exc && ((info->b & info->bexc) || (info->m & info->mexc)))
break; // encountered an excluded biome
match_any |= ((info->b & info->bany) || (info->m & info->many));
match_req |= ((info->b & info->breq) == info->breq &&
(info->m & info->mreq) == info->mreq);
if (match_exc && match_any && match_req)
break; // all conditions met
}
L_end:
if (stop && *stop)
{
ret = 0;
}
else
{ // given the biome set {info.b, info.m} determine if we have a match
int match_exc = (info->bexc|info->mexc) == 0;
int match_any = (info->bany|info->many) == 0;
int match_req = (info->breq|info->mreq) == 0;
match_exc |= ((info->b & info->bexc) || (info->m & info->mexc)) == 0;
match_any |= ((info->b & info->bany) || (info->m & info->many));
match_req |= ((info->b & info->breq) == info->breq &&
(info->m & info->mreq) == info->mreq);
ret = (match_exc && match_any && match_req);
}
if (buf)
free(buf);
if (ids != cache)
free(ids);
return ret;
}
STRUCT(filter_data_t)
{
const BiomeFilter *bf;
int (*map)(const Layer *, int *, int, int, int, int);
};
enum { M_STOP=1, M_DONE=2 };
static int mapFilterSpecial(const Layer * l, int * out, int x, int z, int w, int h)
{
const filter_data_t *f = (const filter_data_t*) l->data;
int i, j;
uint64_t temps;
/// pre-gen checks
int specialcnt = f->bf->specialCnt;
if (specialcnt > 0)
{
uint64_t ss = l->startSeed;
uint64_t cs;
for (j = 0; j < h; j++)
{
for (i = 0; i < w; i++)
{
cs = getChunkSeed(ss, x+i, z+j);
if (mcFirstIsZero(cs, 13))
specialcnt--;
}
}
if (specialcnt > 0)
return M_STOP;
}
int err = f->map(l, out, x, z, w, h);
if unlikely(err != 0)
return err;
temps = 0;
for (j = 0; j < h; j++)
{
for (i = 0; i < w; i++)
{
int id = out[i + w*j];
int isspecial = id & 0xf00;
id &= ~0xf00;
if (isspecial && id != Freezing)
temps |= (1ULL << (id+Special));
else
temps |= (1ULL << id);
}
}
if ((temps & f->bf->tempsToFind) ^ f->bf->tempsToFind)
return M_STOP;
return 0;
}
static int mapFilterMushroom(const Layer * l, int * out, int x, int z, int w, int h)
{
const filter_data_t *f = (const filter_data_t*) l->data;
int i, j;
int err;
if (w*h < 100 && (f->bf->majorToFind & (1ULL << mushroom_fields)))
{
uint64_t ss = l->startSeed;
uint64_t cs;
for (j = 0; j < h; j++)
{
for (i = 0; i < w; i++)
{
cs = getChunkSeed(ss, i+x, j+z);
if (mcFirstIsZero(cs, 100))
goto L_generate;
}
}
return M_STOP;
}
L_generate:
err = f->map(l, out, x, z, w, h);
if unlikely(err != 0)
return err;
if (f->bf->majorToFind & (1ULL << mushroom_fields))
{
for (i = 0; i < w*h; i++)
if (out[i] == mushroom_fields)
return 0;
return M_STOP;
}
return 0;
}
static int mapFilterBiome(const Layer * l, int * out, int x, int z, int w, int h)
{
const filter_data_t *f = (const filter_data_t*) l->data;
int i, j;
uint64_t b;
int err = f->map(l, out, x, z, w, h);
if unlikely(err != 0)
return err;
b = 0;
for (j = 0; j < h; j++)
{
for (i = 0; i < w; i++)
{
int id = out[i + w*j];
b |= (1ULL << id);
}
}
if ((b & f->bf->majorToFind) ^ f->bf->majorToFind)
return M_STOP;
return 0;
}
static int mapFilterOceanTemp(const Layer * l, int * out, int x, int z, int w, int h)
{
const filter_data_t *f = (const filter_data_t*) l->data;
int i, j;
uint64_t b;
int err = f->map(l, out, x, z, w, h);
if unlikely(err != 0)
return err;
b = 0;
for (j = 0; j < h; j++)
{
for (i = 0; i < w; i++)
{
int id = out[i + w*j];
b |= (1ULL << id);
}
}
if ((b & f->bf->otempToFind) ^ f->bf->otempToFind)
return M_STOP;
return 0;
}
static int mapFilterBiomeEdge(const Layer * l, int * out, int x, int z, int w, int h)
{
const filter_data_t *f = (const filter_data_t*) l->data;
uint64_t b;
int i;
int err;
err = f->map(l, out, x, z, w, h);
if unlikely(err != 0)
return err;
b = 0;
for (i = 0; i < w*h; i++)
b |= (1ULL << (out[i] & 0x3f));
if ((b & f->bf->edgesToFind) ^ f->bf->edgesToFind)
return M_STOP;
return 0;
}
static int mapFilterRareBiome(const Layer * l, int * out, int x, int z, int w, int h)
{
const filter_data_t *f = (const filter_data_t*) l->data;
uint64_t b, bm;
int i;
int err;
err = f->map(l, out, x, z, w, h);
if unlikely(err != 0)
return err;
b = 0; bm = 0;
for (i = 0; i < w*h; i++)
{
int id = out[i];
if (id < 128) b |= (1ULL << id);
else bm |= (1ULL << (id-128));
}
if ((b & f->bf->raresToFind) ^ f->bf->raresToFind)
return M_STOP;
if ((bm & f->bf->raresToFindM) ^ f->bf->raresToFindM)
return M_STOP;
return 0;
}
static int mapFilterShore(const Layer * l, int * out, int x, int z, int w, int h)
{
const filter_data_t *f = (const filter_data_t*) l->data;
uint64_t b, bm;
int i;
int err = f->map(l, out, x, z, w, h);
if unlikely(err != 0)
return err;
b = 0; bm = 0;
for (i = 0; i < w*h; i++)
{
int id = out[i];
if (id < 128) b |= (1ULL << id);
else bm |= (1ULL << (id-128));
}
if ((b & f->bf->shoreToFind) ^ f->bf->shoreToFind)
return M_STOP;
if ((bm & f->bf->shoreToFindM) ^ f->bf->shoreToFindM)
return M_STOP;
return 0;
}
static int mapFilterRiverMix(const Layer * l, int * out, int x, int z, int w, int h)
{
const filter_data_t *f = (const filter_data_t*) l->data;
uint64_t b, bm;
int i;
int err = f->map(l, out, x, z, w, h);
if unlikely(err != 0)
return err;
b = 0; bm = 0;
for (i = 0; i < w*h; i++)
{
int id = out[i];
if (id < 128) b |= (1ULL << id);
else bm |= (1ULL << (id-128));
}
if ((b & f->bf->riverToFind) ^ f->bf->riverToFind)
return M_STOP;
if ((bm & f->bf->riverToFindM) ^ f->bf->riverToFindM)
return M_STOP;
return 0;
}
static int mapFilterOceanMix(const Layer * l, int * out, int x, int z, int w, int h)
{
const filter_data_t *f = (const filter_data_t*) l->data;
uint64_t b;
int i;
int err;
if (f->bf->riverToFind)
{
err = l->p->getMap(l->p, out, x, z, w, h); // RiverMix
if (err)
return err;
}
err = f->map(l, out, x, z, w, h);
if unlikely(err != 0)
return err;
b = 0;
for (i = 0; i < w*h; i++)
{
int id = out[i];
if (id < 128) b |= (1ULL << id);
}
if ((b & f->bf->oceanToFind) ^ f->bf->oceanToFind)
return M_STOP;
return 0;
}
static
void swapMap(filter_data_t *fd, const BiomeFilter *bf, Layer *l,
int (*map)(const Layer *, int *, int, int, int, int))
{
fd->bf = bf;
fd->map = l->getMap;
l->data = (void*) fd;
l->getMap = map;
}
static
void restoreMap(filter_data_t *fd, Layer *l)
{
l->getMap = fd->map;
l->data = NULL;
}
static
int testExclusion(Layer *layer, int *cache, int x, int z, const BiomeFilter *bf)
{
int err = layer->getMap(layer, cache, x, z, 1, 1);
if (err)
return 0; // skip, but don't treat error as valid
int id = cache[0];
if (id < 128)
return (bf->biomeToExcl & (1ULL << id)) != 0;
return (bf->biomeToExclM & (1ULL << (id-128))) != 0;
}
int checkForBiomesAtLayer(
LayerStack * g,
Layer * entry,
int * cache,
uint64_t seed,
int x,
int z,
unsigned int w,
unsigned int h,
const BiomeFilter * filter
)
{
Layer *l;
int *ids;
int ret, err;
int memsiz, mem1x1;
if (filter->flags & BF_APPROX) // TODO: protoCheck for 1.6-
{
l = entry;
int i, j;
int bx = x * l->scale;
int bz = z * l->scale;
int bw = w * l->scale;
int bh = h * l->scale;
int x0, z0, x1, z1;
uint64_t ss, cs;
uint64_t potential, required;
int specialcnt = filter->specialCnt;
if (specialcnt > 0)
{
l = &g->layers[L_SPECIAL_1024];
x0 = (bx) / l->scale; if (x < 0) x0--;
z0 = (bz) / l->scale; if (z < 0) z0--;
x1 = (bx + bw) / l->scale; if (x+(int)w >= 0) x1++;
z1 = (bz + bh) / l->scale; if (z+(int)h >= 0) z1++;
ss = getStartSeed(seed, l->layerSalt);
for (j = z0; j <= z1; j++)
{
for (i = x0; i <= x1; i++)
{
cs = getChunkSeed(ss, i, j);
if (mcFirstIsZero(cs, 13))
specialcnt--;
}
}
if (specialcnt > 0)
return 0;
}
l = &g->layers[L_BIOME_256];
x0 = bx / l->scale; if (x < 0) x0--;
z0 = bz / l->scale; if (z < 0) z0--;
x1 = (bx + bw) / l->scale; if (x+(int)w >= 0) x1++;
z1 = (bz + bh) / l->scale; if (z+(int)h >= 0) z1++;
if (filter->majorToFind & (1ULL << mushroom_fields))
{
ss = getStartSeed(seed, g->layers[L_MUSHROOM_256].layerSalt);
for (j = z0; j <= z1; j++)
{
for (i = x0; i <= x1; i++)
{
cs = getChunkSeed(ss, i, j);
if (mcFirstIsZero(cs, 100))
goto L_has_proto_mushroom;
}
}
return 0;
}
L_has_proto_mushroom:
potential = 0;
required = filter->majorToFind & (
(1ULL << badlands_plateau) | (1ULL << wooded_badlands_plateau) |
(1ULL << desert) | (1ULL << savanna) | (1ULL << plains) |
(1ULL << forest) | (1ULL << dark_forest) | (1ULL << mountains) |
(1ULL << birch_forest) | (1ULL << swamp));
ss = getStartSeed(seed, l->layerSalt);
for (j = z0; j <= z1; j++)
{
for (i = x0; i <= x1; i++)
{
cs = getChunkSeed(ss, i, j);
int cs6 = mcFirstInt(cs, 6);
int cs3 = mcFirstInt(cs, 3);
int cs4 = mcFirstInt(cs, 4);
if (cs3) potential |= (1ULL << badlands_plateau);
else potential |= (1ULL << wooded_badlands_plateau);
switch (cs6)
{
case 0: potential |= (1ULL << desert) | (1ULL << forest); break;
case 1: potential |= (1ULL << desert) | (1ULL << dark_forest); break;
case 2: potential |= (1ULL << desert) | (1ULL << mountains); break;
case 3: potential |= (1ULL << savanna) | (1ULL << plains); break;
case 4: potential |= (1ULL << savanna) | (1ULL << birch_forest); break;
case 5: potential |= (1ULL << plains) | (1ULL << swamp); break;
}
if (cs4 == 3) potential |= (1ULL << snowy_taiga);
else potential |= (1ULL << snowy_tundra);
}
}
if ((potential & required) ^ required)
return 0;
}
l = g->layers;
if (cache)
{
memsiz = 0;
ids = cache;
}
else
{
memsiz = getMinLayerCacheSize(entry, w, h);
ids = (int*) calloc(memsiz, sizeof(int));
}
if ((filter->biomeToExcl | filter->biomeToExclM) && w*h > 1)
{
err = 0;
if (memsiz == 0)
memsiz = getMinLayerCacheSize(entry, w, h);
mem1x1 = getMinLayerCacheSize(entry, 1, 1);
if (mem1x1 * 2 < memsiz)
{
setLayerSeed(entry, seed);
err = testExclusion(entry, ids, x+w/2, z+h/2, filter);
}
if (mem1x1 * 5 < memsiz)
{
if (!err) err = testExclusion(entry, ids, x, z, filter);
if (!err) err = testExclusion(entry, ids, x+w-1, z+h-1, filter);
if (!err) err = testExclusion(entry, ids, x, z+h-1, filter);
if (!err) err = testExclusion(entry, ids, x+w-1, z, filter);
}
if (err)
{
if (cache == NULL)
free(ids);
return 0;
}
}
filter_data_t fd[9];
swapMap(fd+0, filter, l+L_OCEAN_MIX_4, mapFilterOceanMix);
swapMap(fd+1, filter, l+L_RIVER_MIX_4, mapFilterRiverMix);
swapMap(fd+2, filter, l+L_SHORE_16, mapFilterShore);
swapMap(fd+3, filter, l+L_SUNFLOWER_64, mapFilterRareBiome);
swapMap(fd+4, filter, l+L_BIOME_EDGE_64, mapFilterBiomeEdge);
swapMap(fd+5, filter, l+L_OCEAN_TEMP_256, mapFilterOceanTemp);
swapMap(fd+6, filter, l+L_BIOME_256, mapFilterBiome);
swapMap(fd+7, filter, l+L_MUSHROOM_256, mapFilterMushroom);
swapMap(fd+8, filter, l+L_SPECIAL_1024, mapFilterSpecial);
ret = 0;
setLayerSeed(entry, seed);
err = entry->getMap(entry, ids, x, z, w, h);
if (err == 0)
{
uint64_t b = 0, m = 0;
unsigned int i;
for (i = 0; i < w*h; i++)
{
int id = ids[i];
if (id < 128) b |= (1ULL << id);
else m |= (1ULL << (id-128));
}
int match_exc = (filter->biomeToExcl|filter->biomeToExclM) == 0;
int match_any = (filter->biomeToPick|filter->biomeToPickM) == 0;
int match_req = (filter->biomeToFind|filter->biomeToFindM) == 0;
match_exc |= ((b & filter->biomeToExcl) || (m & filter->biomeToExclM)) == 0;
match_any |= ((b & filter->biomeToPick) || (m & filter->biomeToPickM));
match_req |= ((b & filter->biomeToFind) == filter->biomeToFind &&
(m & filter->biomeToFindM) == filter->biomeToFindM);
if (match_exc && match_any && match_req)
ret = 1;
}
else if (err == M_STOP)
{ // biome requirements not met
ret = 0;
}
else if (err == M_DONE)
{ // exclusion biomes cannot generate
ret = 2;
}
restoreMap(fd+8, l+L_SPECIAL_1024);
restoreMap(fd+7, l+L_MUSHROOM_256);
restoreMap(fd+6, l+L_BIOME_256);
restoreMap(fd+5, l+L_OCEAN_TEMP_256);
restoreMap(fd+4, l+L_BIOME_EDGE_64);
restoreMap(fd+3, l+L_SUNFLOWER_64);
restoreMap(fd+2, l+L_SHORE_16);
restoreMap(fd+1, l+L_RIVER_MIX_4);
restoreMap(fd+0, l+L_OCEAN_MIX_4);
if (cache == NULL)
free(ids);
return ret;
}
int checkForTemps(LayerStack *g, uint64_t seed, int x, int z, int w, int h, const int tc[9])
{
uint64_t ls = getLayerSalt(3); // L_SPECIAL_1024 layer seed
uint64_t ss = getStartSeed(seed, ls);
int i, j;
int scnt = 0;
if (tc[Special+Warm] > 0) scnt += tc[Special+Warm];
if (tc[Special+Lush] > 0) scnt += tc[Special+Lush];
if (tc[Special+Cold] > 0) scnt += tc[Special+Cold];
if (scnt > 0)
{
for (j = 0; j < h; j++)
{
for (i = 0; i < w; i++)
{
if (mcFirstIsZero(getChunkSeed(ss, x+i, z+j), 13))
scnt--;
}
}
if (scnt > 0)
return 0;
}
Layer *l = &g->layers[L_SPECIAL_1024];
int ccnt[9] = {0};
int *area = (int*) calloc(getMinLayerCacheSize(l, w, h), sizeof(int));
int ret = 1;
setLayerSeed(l, seed);
genArea(l, area, x, z, w, h);
for (i = 0; i < w*h; i++)
{
int id = area[i];
int t = id & 0xff;
if (id != t && t != Freezing)
t += Special;
ccnt[t]++;
}
for (i = 0; i < 9; i++)
{
if (ccnt[i] < tc[i] || (ccnt[i] && tc[i] < 0))
{
ret = 0;
break;
}
}
free(area);
return ret;
}
struct locate_info_t
{
Generator *g;
int *ids;
Range r;
int match, tol;
volatile char *stop;
};
static
int floodFillGen(struct locate_info_t *info, int i, int j, Pos *p)
{
typedef struct { int i, j, d; } entry_t;
entry_t *queue = (entry_t*) malloc(info->r.sx*info->r.sz * sizeof(*queue));
int qn = 1;
queue->i = i;
queue->j = j;
queue->d = 0;
int64_t sumx = 0;
int64_t sumz = 0;
int n = 0;
while (--qn >= 0)
{
if (info->stop && *info->stop)
{
free(queue);
return 0;
}
int d = queue[qn].d;
i = queue[qn].i;
j = queue[qn].j;
int k = j * info->r.sx + i;
int id = info->ids[k];
if (id == INT_MAX)
continue;
info->ids[k] = INT_MAX;
int x = info->r.x + i;
int z = info->r.z + j;
if (info->g->mc >= MC_1_18)
id = getBiomeAt(info->g, info->r.scale, x, info->r.y, z);
if (id == info->match)
{
sumx += x;
sumz += z;
n++;
d = 0;
}
else
{
if (++d >= info->tol)
continue;
}
entry_t next[] = { {i,j-1,d}, {i,j+1,d}, {i-1,j,d}, {i+1,j,d} };
for (k = 0; k < 4; k++)
{
i = next[k].i; j = next[k].j;
if (i < 0 || i >= info->r.sx || j < 0 || j >= info->r.sz)
continue;
if (info->ids[j * info->r.sx + i] == INT_MAX)
continue;
queue[qn++] = next[k];
}
}
free(queue);
if (n)
{
p->x = (int) round((sumx / (double)n + 0.5) * info->r.scale);
p->z = (int) round((sumz / (double)n + 0.5) * info->r.scale);
}
return n;
}
int getBiomeCenters(Pos *pos, int *siz, int nmax, Generator *g, Range r,
int match, int minsiz, int tol, volatile char *stop)
{
if (minsiz <= 0)
minsiz = 1;
int i, j, k, n = 0;
int *ids = (int*) malloc(r.sx*r.sz * sizeof(int));
memset(ids, -1, r.sx*r.sz * sizeof(int));
if (tol <= 0)
tol = 1;
int step = tol;
struct locate_info_t info;
info.g = g;
info.ids = ids;
info.r = r;
info.stop = stop;
info.match = match;
info.tol = tol;
if (g->mc >= MC_1_18)
{
const int *lim = getBiomeParaLimits(g->mc, match);
int para[] = {
NP_TEMPERATURE,
NP_HUMIDITY,
NP_EROSION,
NP_CONTINENTALNESS,
NP_WEIRDNESS,
};
int npara = sizeof(para) / sizeof(para[0]);
if (tol == 1)
step = 1 + floor(sqrt(minsiz) * 0.5);
for (j = 0; j < r.sz; j += step)
{
for (i = 0; i < r.sx; i += step)
{
if (stop && *stop)
break;
for (k = 0; k < npara; k++)
{
const int *plim = lim + 2*para[k];
if (plim[0] == INT_MIN && plim[1] == INT_MAX)
continue;
DoublePerlinNoise *dpn = &g->bn.climate[para[k]];
double px = (r.x+i) * r.scale / 4.0;
double pz = (r.z+j) * r.scale / 4.0;
int p = 10000 * sampleDoublePerlin(dpn, px, 0, pz);
if (p < plim[0] || p > plim[1])
{
ids[j*r.sx + i] = -2;
break;
}
}
}
}
match = -1; // id entries that are still -1 are our candidates
}
else // 1.17-
{
int ts = 32 / r.scale;
if (r.sx + r.sz < 32)
ts = 8;
int tx = (int) floor(r.x / (double)ts);
int tz = (int) floor(r.z / (double)ts);
int tw = (int) ceil((r.x+r.sx) / (double)ts) - tx;
int th = (int) ceil((r.z+r.sz) / (double)ts) - tz;
int ti, tj;
BiomeFilter bf;
setupBiomeFilter(&bf, g->mc, 0, &match, 1, 0, 0, 0, 0);
//applySeed(g, 0, g->seed);
Range tr = { r.scale, 0, 0, ts, ts, 0, 1 };
int *cache = allocCache(g, r);
for (tj = 0; tj < th; tj++)
{
for (ti = 0; ti < tw; ti++)
{
if (stop && *stop)
break;
tr.x = (tx+ti) * ts;
tr.z = (tz+tj) * ts;
if (checkForBiomes(g, cache, tr, DIM_OVERWORLD, g->seed,
&bf, stop) != 1)
{
continue;
}
for (j = 0; j < ts; j++)
{
int jj = tr.z + j - r.z;
if (jj < 0 || jj >= r.sz)
continue;
for (i = 0; i < ts; i++)
{
int ii = tr.x + i - r.x;
if (ii < 0 || ii >= r.sx)
continue;
ids[jj*r.sx + ii] = cache[j*tr.sx + i];
}
}
}
}
free(cache);
}
applySeed(g, DIM_OVERWORLD, g->seed);
for (j = 0; j < r.sz; j += step)
{
for (i = 0; i < r.sx; i += step)
{
if (stop && *stop)
break;
if (ids[j*r.sx + i] != match)
continue;
Pos center;
int area = floodFillGen(&info, i, j, &center);
if (area >= minsiz)
{
pos[n] = center;
if (siz) siz[n] = area;
if (++n >= nmax)
goto L_end;
}
}
}
L_end:
free(ids);
return n;
}
int canBiomeGenerate(int layerId, int mc, uint32_t flags, int id)
{
int dofilter = 0;
if (mc >= MC_1_13)
{
if (layerId == L_OCEAN_TEMP_256)
return isShallowOcean(id);
if ((flags & FORCE_OCEAN_VARIANTS) && isOceanic(id))
return id != deep_warm_ocean;
}
if (dofilter || layerId == L_BIOME_256)
{
dofilter = 1;
if (id >= 64)
return 0;
}
if (dofilter || (layerId == L_BAMBOO_256 && mc >= MC_1_14))
{
dofilter = 1;
switch (id)
{
case jungle_edge:
case wooded_mountains:
case badlands:
return 0;
}
}
if (dofilter || (layerId == L_BIOME_EDGE_64 && mc >= MC_1_7))
{
dofilter = 1;
if (id >= 64 && id != bamboo_jungle)
return 0;
switch (id)
{
case snowy_mountains:
case desert_hills:
case wooded_hills:
case taiga_hills:
case jungle_hills:
case birch_forest_hills:
case snowy_taiga_hills:
case giant_tree_taiga_hills:
case savanna_plateau:
return 0;
}
}
if (dofilter || (layerId == L_ZOOM_64 && mc <= MC_1_0))
{
dofilter = 1;
if (id == mushroom_field_shore)
return 0;
}
if (dofilter || layerId == L_HILLS_64)
{
dofilter = 1;
if (id == frozen_ocean)
return 0;
// sunflower_plains actually generates at Hills layer as well
}
if (dofilter || (layerId == L_ZOOM_16 && mc <= MC_1_6))
{
dofilter = 1;
if (id == mountain_edge)
return 0;
}
if (dofilter || (layerId == L_SUNFLOWER_64 && mc >= MC_1_7))
{
dofilter = 1;
switch (id)
{
case beach:
case stone_shore:
case snowy_beach:
return 0;
case mushroom_field_shore:
if (mc != MC_1_0)
return 0;
break;
}
}
if (dofilter || layerId == L_SHORE_16)
{
dofilter = 1;
if (id == river)
return 0;
}
if (dofilter || (layerId == L_SWAMP_RIVER_16 && mc <= MC_1_6))
{
dofilter = 1;
if (id == frozen_river)
return 0;
}
if (dofilter || layerId == L_RIVER_MIX_4)
{
dofilter = 1;
if (isDeepOcean(id) && id != deep_ocean)
return 0;
if (isShallowOcean(id) && id != ocean)
{
if (mc >= MC_1_7 || id != frozen_ocean)
return 0;
}
}
if (dofilter || (layerId == L_OCEAN_MIX_4 && mc >= MC_1_13))
{
dofilter = 1;
}
if (!dofilter && layerId != L_VORONOI_1)
{
printf("canBiomeGenerate(): unsupported layer (%d) or version (%d)\n",
layerId, mc);
return 0;
}
return isOverworld(mc, id);
}
void getAvailableBiomes(uint64_t *mL, uint64_t *mM, int layerId, int mc, uint32_t flags)
{
*mL = *mM = 0;
int i;
if (mc <= MC_B1_7 || mc >= MC_1_18)
{
for (i = 0; i < 64; i++)
{
if (isOverworld(mc, i))
*mL |= (1ULL << i);
if (isOverworld(mc, i+128))
*mM |= (1ULL << i);
}
}
else if (mc >= MC_1_13 && layerId == L_OCEAN_TEMP_256)
{
*mL =
(1ULL << ocean) |
(1ULL << frozen_ocean) |
(1ULL << warm_ocean) |
(1ULL << lukewarm_ocean) |
(1ULL << cold_ocean);
}
else
{
for (i = 0; i < 64; i++)
{
if (canBiomeGenerate(layerId, mc, i, flags))
*mL |= (1ULL << i);
if (canBiomeGenerate(layerId, mc, i+128, flags))
*mM |= (1ULL << i);
}
}
}
struct _gp_args
{
uint64_t *mL, *mM;
int mc;
uint32_t flags;
};
static void _genPotential(struct _gp_args *a, int layer, int id)
{
int mc = a->mc;
// filter out bad biomes
if (layer >= L_BIOME_256 && !canBiomeGenerate(layer, mc, a->flags, id))
return;
switch (layer)
{
case L_SPECIAL_1024: // biomes added in (L_SPECIAL_1024, L_MUSHROOM_256]
if (mc <= MC_1_6) goto L_bad_layer;
if (id == Oceanic)
_genPotential(a, L_MUSHROOM_256, mushroom_fields);
if ((id & ~0xf00) >= Oceanic && (id & ~0xf00) <= Freezing)
_genPotential(a, L_MUSHROOM_256, id);
break;
case L_MUSHROOM_256: // biomes added in (L_MUSHROOM_256, L_DEEP_OCEAN_256]
if (mc >= MC_1_7) {
if (id == Oceanic)
_genPotential(a, L_DEEP_OCEAN_256, deep_ocean);
if (id == mushroom_fields)
_genPotential(a, L_DEEP_OCEAN_256, id);
if ((id & ~0xf00) >= Oceanic && (id & ~0xf00) <= Freezing)
_genPotential(a, L_DEEP_OCEAN_256, id);
} else { // (L_MUSHROOM_256, L_BIOME_256] for 1.6
if (id == ocean || id == mushroom_fields) {
_genPotential(a, L_BIOME_256, id);
} else {
_genPotential(a, L_BIOME_256, desert);
_genPotential(a, L_BIOME_256, forest);
_genPotential(a, L_BIOME_256, mountains);
_genPotential(a, L_BIOME_256, swamp);
_genPotential(a, L_BIOME_256, plains);
_genPotential(a, L_BIOME_256, taiga);
if (mc >= MC_1_2)
_genPotential(a, L_BIOME_256, jungle);
if (id != plains)
_genPotential(a, L_BIOME_256, snowy_tundra);
}
}
break;
case L_DEEP_OCEAN_256: // biomes added in (L_DEEP_OCEAN_256, L_BIOME_256]
if (mc <= MC_1_6) goto L_bad_layer;
switch (id & ~0xf00)
{
case Warm:
if (id & 0xf00) {
_genPotential(a, L_BIOME_256, badlands_plateau);
_genPotential(a, L_BIOME_256, wooded_badlands_plateau);
} else {
_genPotential(a, L_BIOME_256, desert);
_genPotential(a, L_BIOME_256, savanna);
_genPotential(a, L_BIOME_256, plains);
}
break;
case Lush:
if (id & 0xf00) {
_genPotential(a, L_BIOME_256, jungle);
} else {
_genPotential(a, L_BIOME_256, forest);
_genPotential(a, L_BIOME_256, dark_forest);
_genPotential(a, L_BIOME_256, mountains);
_genPotential(a, L_BIOME_256, plains);
_genPotential(a, L_BIOME_256, birch_forest);
_genPotential(a, L_BIOME_256, swamp);
}
break;
case Cold:
if (id & 0xf00) {
_genPotential(a, L_BIOME_256, giant_tree_taiga);
} else {
_genPotential(a, L_BIOME_256, forest);
_genPotential(a, L_BIOME_256, mountains);
_genPotential(a, L_BIOME_256, taiga);
_genPotential(a, L_BIOME_256, plains);
}
break;
case Freezing:
_genPotential(a, L_BIOME_256, snowy_tundra);
_genPotential(a, L_BIOME_256, snowy_taiga);
break;
default:
id &= ~0xf00;
_genPotential(a, L_BIOME_256, id);
}
break;
case L_BIOME_256: // biomes added in (L_BIOME_256, L_BIOME_EDGE_64]
case L_BAMBOO_256:
case L_ZOOM_64:
if (mc <= MC_1_13 && layer == L_BAMBOO_256) goto L_bad_layer;
if (mc >= MC_1_7) {
if (mc >= MC_1_14 && id == jungle)
_genPotential(a, L_BIOME_EDGE_64, bamboo_jungle);
if (id == wooded_badlands_plateau || id == badlands_plateau)
_genPotential(a, L_BIOME_EDGE_64, badlands);
else if(id == giant_tree_taiga)
_genPotential(a, L_BIOME_EDGE_64, taiga);
else if (id == desert)
_genPotential(a, L_BIOME_EDGE_64, wooded_mountains);
else if (id == swamp) {
_genPotential(a, L_BIOME_EDGE_64, jungle_edge);
_genPotential(a, L_BIOME_EDGE_64, plains);
}
_genPotential(a, L_BIOME_EDGE_64, id);
break;
}
// (L_BIOME_256, L_HILLS_64] for 1.6
// fallthrough
case L_BIOME_EDGE_64: // biomes added in (L_BIOME_EDGE_64, L_HILLS_64]
if (mc <= MC_1_6 && layer == L_BIOME_EDGE_64) goto L_bad_layer;
if (!isShallowOcean(id) && getMutated(mc, id) > 0)
_genPotential(a, L_HILLS_64, getMutated(mc, id));
switch (id)
{
case desert:
_genPotential(a, L_HILLS_64, desert_hills);
break;
case forest:
_genPotential(a, L_HILLS_64, wooded_hills);
break;
case birch_forest:
_genPotential(a, L_HILLS_64, birch_forest_hills);
_genPotential(a, L_HILLS_64, getMutated(mc, birch_forest_hills));
break;
case dark_forest:
_genPotential(a, L_HILLS_64, plains);
_genPotential(a, L_HILLS_64, getMutated(mc, plains));
break;
case taiga:
_genPotential(a, L_HILLS_64, taiga_hills);
break;
case giant_tree_taiga:
_genPotential(a, L_HILLS_64, giant_tree_taiga_hills);
_genPotential(a, L_HILLS_64, getMutated(mc, giant_tree_taiga_hills));
break;
case snowy_taiga:
_genPotential(a, L_HILLS_64, snowy_taiga_hills);
break;
case plains:
if (mc >= MC_1_7)
_genPotential(a, L_HILLS_64, wooded_hills);
_genPotential(a, L_HILLS_64, forest);
_genPotential(a, L_HILLS_64, getMutated(mc, forest));
break;
case snowy_tundra:
_genPotential(a, L_HILLS_64, snowy_mountains);
break;
case jungle:
_genPotential(a, L_HILLS_64, jungle_hills);
break;
case bamboo_jungle:
_genPotential(a, L_HILLS_64, bamboo_jungle_hills);
break;
case ocean:
if (mc >= MC_1_7)
_genPotential(a, L_HILLS_64, deep_ocean);
break;
case mountains:
if (mc >= MC_1_7) {
_genPotential(a, L_HILLS_64, wooded_mountains);
_genPotential(a, L_HILLS_64, getMutated(mc, wooded_mountains));
}
break;
case savanna:
_genPotential(a, L_HILLS_64, savanna_plateau);
_genPotential(a, L_HILLS_64, getMutated(mc, savanna_plateau));
break;
default:
if (areSimilar(mc, id, wooded_badlands_plateau))
{
_genPotential(a, L_HILLS_64, badlands);
_genPotential(a, L_HILLS_64, getMutated(mc, badlands));
}
else if (isDeepOcean(id))
{
_genPotential(a, L_HILLS_64, plains);
_genPotential(a, L_HILLS_64, forest);
_genPotential(a, L_HILLS_64, getMutated(mc, plains));
_genPotential(a, L_HILLS_64, getMutated(mc, forest));
}
}
_genPotential(a, L_HILLS_64, id);
break;
case L_HILLS_64: // biomes added in (L_HILLS_64, L_RARE_BIOME_64]
if (mc <= MC_1_6) { // (L_HILLS_64, L_SHORE_16] for 1.6
if (id == mushroom_fields)
_genPotential(a, L_SHORE_16, mushroom_field_shore);
else if (id == mountains)
_genPotential(a, L_SHORE_16, mountain_edge);
else if (id != ocean && id != river && id != swamp)
_genPotential(a, L_SHORE_16, beach);
_genPotential(a, L_SHORE_16, id);
} else {
if (id == plains)
_genPotential(a, L_SUNFLOWER_64, sunflower_plains);
_genPotential(a, L_SUNFLOWER_64, id);
}
break;
case L_SUNFLOWER_64: // biomes added in (L_SUNFLOWER_64, L_SHORE_16] 1.7+
if (mc <= MC_1_6) goto L_bad_layer;
// fallthrough
case L_ZOOM_16:
if (mc <= MC_1_0 && layer == L_ZOOM_16) {
_genPotential(a, L_SHORE_16, id);
break;
}
if (id == mushroom_fields)
_genPotential(a, L_SHORE_16, mushroom_field_shore);
else if (getCategory(mc, id) == jungle) {
_genPotential(a, L_SHORE_16, beach);
_genPotential(a, L_SHORE_16, jungle_edge);
}
else if (id == mountains || id == wooded_mountains || id == mountain_edge)
_genPotential(a, L_SHORE_16, stone_shore);
else if (isSnowy(id))
_genPotential(a, L_SHORE_16, snowy_beach);
else if (id == badlands || id == wooded_badlands_plateau)
_genPotential(a, L_SHORE_16, desert);
else if (id != ocean && id != deep_ocean && id != river && id != swamp)
_genPotential(a, L_SHORE_16, beach);
_genPotential(a, L_SHORE_16, id);
break;
case L_SHORE_16: // biomes added in (L_SHORE_16, L_RIVER_MIX_4]
case L_SWAMP_RIVER_16:
case L_ZOOM_4:
if (id == snowy_tundra)
_genPotential(a, L_RIVER_MIX_4, frozen_river);
else if (id == mushroom_fields || id == mushroom_field_shore)
_genPotential(a, L_RIVER_MIX_4, mushroom_field_shore);
else if (id != ocean && (mc <= MC_1_6 || !isOceanic(id)))
_genPotential(a, L_RIVER_MIX_4, river);
_genPotential(a, L_RIVER_MIX_4, id);
break;
case L_RIVER_MIX_4: // biomes added in (L_RIVER_MIX_4, L_VORONOI_1]
if (mc >= MC_1_13 && isOceanic(id)) {
if (id == ocean) {
_genPotential(a, L_VORONOI_1, ocean);
_genPotential(a, L_VORONOI_1, warm_ocean);
_genPotential(a, L_VORONOI_1, lukewarm_ocean);
_genPotential(a, L_VORONOI_1, cold_ocean);
_genPotential(a, L_VORONOI_1, frozen_ocean);
} else if (id == deep_ocean) {
_genPotential(a, L_VORONOI_1, deep_ocean);
_genPotential(a, L_VORONOI_1, deep_lukewarm_ocean);
_genPotential(a, L_VORONOI_1, deep_cold_ocean);
_genPotential(a, L_VORONOI_1, deep_frozen_ocean);
}
else break;
}
_genPotential(a, L_VORONOI_1, id);
break;
case L_OCEAN_MIX_4:
if (mc <= MC_1_12) goto L_bad_layer;
// fallthrough
case L_VORONOI_1:
if (id < 128) *a->mL |= 1ULL << id;
else *a->mM |= 1ULL << (id-128);
break;
default:
printf("genPotential() not implemented for layer %d\n", layer);
}
if (0)
{
L_bad_layer:
printf("genPotential() bad layer %d for version\n", layer);
}
}
void genPotential(uint64_t *mL, uint64_t *mM, int layerId, int mc, uint32_t flags, int id)
{
struct _gp_args args = { mL, mM, mc, flags };
_genPotential(&args, layerId, id);
}
double getParaDescent(const DoublePerlinNoise *para, double factor,
int x, int z, int w, int h, int i0, int j0, int maxrad,
int maxiter, double alpha, void *data, int (*func)(void*,int,int,double))
{
/// Do a gradient descent on a grid...
/// To start with, we will just consider a step size of 1 in one axis:
/// Try going in positive x: if gradient is upwards go to negative x
/// then do the same with z - if all 4 directions go upwards then we have
/// found a minimum, otherwise repeat.
/// We can remember and try the direction from the previous cycle first to
/// reduce the number of wrong guesses.
///
/// We can also use a larger step size than 1, as long as we believe that
/// the minimum is not in between. To determine if this is viable, we check
/// the step size of 1 first, and then jump if the gradient appears large
/// enough in that direction.
///
///TODO:
/// The perlin noise can be sampled continuously, so more established
/// minima algorithms can also be considered...
int dirx = 0, dirz = 0, dira;
int k, i, j;
double v, vd, va;
v = factor * sampleDoublePerlin(para, x+i0, 0, z+j0);
if (func)
{
if (func(data, x+i0, z+j0, factor < 0 ? -v : v))
return nan("");
}
i = i0; j = j0;
for (k = 0; k < maxiter; k++)
{
if (dirx == 0) dirx = +1;
if (i+dirx >= 0 && i+dirx < w)
vd = factor * sampleDoublePerlin(para, x+i+dirx, 0, z+j);
else vd = v;
if (vd >= v)
{
dirx *= -1;
if (i+dirx >= 0 && i+dirx < w)
vd = factor * sampleDoublePerlin(para, x+i+dirx, 0, z+j);
else vd = v;
if (vd >= v)
dirx = 0;
}
if (dirx)
{
dira = (int)(dirx * alpha * (v - vd));
if (abs(dira) > 2 && i+dira >= 0 && i+dira < w)
{ // try jumping by more than 1
va = factor * sampleDoublePerlin(para, x+i+dira, 0, z+j);
if (va < vd)
{
i += dira;
v = va;
goto L_x_end;
}
}
v = vd;
i += dirx;
L_x_end:
if (func)
{
if (func(data, x+i, z+j, factor < 0 ? -v : v))
return nan("");
}
}
if (dirz == 0) dirz = +1;
if (j+dirz >= 0 && j+dirz < h)
vd = factor * sampleDoublePerlin(para, x+i, 0, z+j+dirz);
else vd = v;
if (vd >= v)
{
dirz *= -1;
if (j+dirz >= 0 && j+dirz < h)
vd = factor * sampleDoublePerlin(para, x+i, 0, z+j+dirz);
else vd = v;
if (vd >= v)
dirz = 0;
}
if (dirz)
{
dira = (int)(dirz * alpha * (v - vd));
if (abs(dira) > 2 && j+dira >= 0 && j+dira < h)
{ // try jumping by more than 1
va = factor * sampleDoublePerlin(para, x+i, 0, z+j+dira);
if (va < vd)
{
j += dira;
v = va;
goto L_z_end;
}
}
j += dirz;
v = vd;
L_z_end:
if (func)
{
if (func(data, x+i, z+j, factor < 0 ? -v : v))
return nan("");
}
}
if (dirx == 0 && dirz == 0)
{ // this is very likely a fix point
// but there could be a minimum along a diagonal path in rare cases
int c;
for (c = 0; c < 4; c++)
{
dirx = (c & 1) ? -1 : +1;
dirz = (c & 2) ? -1 : +1;
if (i+dirx < 0 || i+dirx >= w || j+dirz < 0 || j+dirz >= h)
continue;
vd = factor * sampleDoublePerlin(para, x+i+dirx, 0, z+j+dirz);
if (vd < v)
{
v = vd;
i += dirx;
j += dirz;
break;
}
}
if (c >= 4)
break;
}
if (abs(i - i0) > maxrad || abs(j - j0) > maxrad)
break; // we have gone too far from the origin
}
return v;
}
int getParaRange(const DoublePerlinNoise *para, double *pmin, double *pmax,
int x, int z, int w, int h, void *data, int (*func)(void*,int,int,double))
{
const double beta = 1.5;
const double factor = 10000;
const double perlin_grad = 2.0 * 1.875; // max perlin noise gradient
double v, lmin, lmax, dr, vdif, small_regime;
char *skip = NULL;
int i, j, step, ii, jj, ww, hh, skipsiz;
int maxrad, maxiter;
int err = 1;
if (pmin) *pmin = DBL_MAX;
if (pmax) *pmax = -DBL_MAX;
lmin = DBL_MAX, lmax = 0;
for (i = 0; i < para->octA.octcnt; i++)
{
double lac = para->octA.octaves[i].lacunarity;
if (lac < lmin) lmin = lac;
if (lac > lmax) lmax = lac;
}
// Sort out the small area cases where we are less likely to improve upon
// checking all positions.
small_regime = 1e3 * sqrt(lmax);
if (w*h < small_regime)
{
for (j = 0; j < h; j++)
{
for (i = 0; i < w; i++)
{
v = factor * sampleDoublePerlin(para, x+i, 0, z+j);
if (func)
{
err = func(data, x+i, z+j, v);
if (err)
return err;
}
if (pmin && v < *pmin) *pmin = v;
if (pmax && v > *pmax) *pmax = v;
}
}
return 0;
}
// Start with the largest noise period to get some bounds for pmin, pmax
step = (int) (0.5 / lmin - FLT_EPSILON) + 1;
dr = lmax / lmin * beta;
for (j = 0; j < h; j += step)
{
for (i = 0; i < w; i += step)
{
if (pmin)
{
v = getParaDescent(para, +factor, x, z, w, h, i, j,
step, step, dr, data, func);
if (v != v) goto L_end;
if (v < *pmin) *pmin = v;
}
if (pmax)
{
v = -getParaDescent(para, -factor, x, z, w, h, i, j,
step, step, dr, data, func);
if (v != v) goto L_end;
if (v > *pmax) *pmax = v;
}
}
}
//(*(double*)data) = -1e9+1; // testing
step = (int) (1.0 / (perlin_grad * lmax + FLT_EPSILON)) + 1;
/// We can determine the maximum contribution we expect from all noise
/// periods for a distance of step. If this does not account for the
/// necessary difference, we can skip that point.
vdif = 0;
for (i = 0; i < para->octA.octcnt; i++)
{
const PerlinNoise *p = para->octA.octaves + i;
double contrib = step * p->lacunarity * 1.0;
if (contrib > 1.0) contrib = 1;
vdif += contrib * p->amplitude;
}
for (i = 0; i < para->octB.octcnt; i++)
{
const double lac_factB = 337.0 / 331.0;
const PerlinNoise *p = para->octB.octaves + i;
double contrib = step * p->lacunarity * lac_factB;
if (contrib > 1.0) contrib = 1;
vdif += contrib * p->amplitude;
}
vdif = fabs(factor * vdif * para->amplitude);
//printf("%g %g %g\n", para->amplitude, 1./lmin, 1./lmax);
//printf("first pass: [%g %g] diff=%g step:%d\n", *pmin, *pmax, vdif, step);
maxrad = step;
maxiter = step*2;
ww = (w+step-1) / step;
hh = (h+step-1) / step;
skipsiz = (ww+1) * (hh+1) * sizeof(*skip);
skip = (char*) malloc(skipsiz);
if (pmin)
{ // look for minima
memset(skip, 0, skipsiz);
for (jj = 0; jj <= hh; jj++)
{
j = jj * step; if (j >= h) j = h-1;
for (ii = 0; ii <= ww; ii++)
{
i = ii * step; if (i >= w) i = w-1;
if (skip[jj*ww+ii]) continue;
v = factor * sampleDoublePerlin(para, x+i, 0, z+j);
if (func)
{
int e = func(data, x+i, z+j, v);
if (e)
{
err = e;
goto L_end;
}
}
// not looking for maxima yet, but update the bounds anyway
if (pmax && v > *pmax) *pmax = v;
dr = beta * (v - *pmin) / vdif;
if (dr > 1.0)
{ // difference is too large -> mark visinity to be skipped
int a, b, r = (int) dr;
for (b = 0; b < r; b++)
{
if (b+jj < 0 || b+jj >= hh) continue;
for (a = -r+1; a < r; a++)
{
if (a+ii < 0 || a+ii >= ww) continue;
skip[(b+jj)*ww + (a+ii)] = 1;
}
}
continue;
}
v = getParaDescent(para, +factor, x, z, w, h, i, j,
maxrad, maxiter, dr, data, func);
if (v != v) goto L_end;
if (v < *pmin) *pmin = v;
}
}
}
if (pmax)
{ // look for maxima
memset(skip, 0, skipsiz);
for (jj = 0; jj <= hh; jj++)
{
j = jj * step; if (j >= h) j = h-1;
for (ii = 0; ii <= ww; ii++)
{
i = ii * step; if (i >= w) i = w-1;
if (skip[jj*ww+ii]) continue;
v = -factor * sampleDoublePerlin(para, x+i, 0, z+j);
if (func)
{
int e = func(data, x+i, z+j, -v);
if (e)
{
err = e;
goto L_end;
}
}
dr = beta * (v + *pmax) / vdif;
if (dr > 1.0)
{ // difference too large -> mark visinity to be skipped
int a, b, r = (int) dr;
for (b = 0; b < r; b++)
{
if (b+jj < 0 || b+jj >= hh) continue;
for (a = -r+1; a < r; a++)
{
if (a+ii < 0 || a+ii >= ww) continue;
skip[(b+jj)*ww + (a+ii)] = 1;
}
}
continue;
}
v = -getParaDescent(para, -factor, x, z, w, h, i, j,
maxrad, maxiter, dr, data, func);
if (v != v) goto L_end;
if (v > *pmax) *pmax = v;
}
}
}
err = 0;
L_end:
if (skip)
free(skip);
return err;
}
#define IMIN INT_MIN
#define IMAX INT_MAX
static const int g_biome_para_range_18[][13] = {
/// biome temperature humidity continental. erosion depth weirdness
{ocean , -1500, 2000, IMIN, IMAX, -4550,-1900, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{plains , -4500, 5500, IMIN, 1000, -1900, IMAX, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{desert , 5500, IMAX, IMIN, IMAX, -1900, IMAX, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{windswept_hills , IMIN, 2000, IMIN, 1000, -1899, IMAX, 4500, 5500, IMIN, IMAX, IMIN, IMAX},
{forest , -4500, 5500, -1000, 3000, -1900, IMAX, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{taiga , IMIN,-1500, 1000, IMAX, -1900, IMAX, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{swamp , -4500, IMAX, IMIN, IMAX, -1100, IMAX, 5500, IMAX, IMIN, IMAX, IMIN, IMAX},
{river , -4500, IMAX, IMIN, IMAX, -1900, IMAX, IMIN, IMAX, IMIN, IMAX, -500, 500},
{frozen_ocean , IMIN,-4501, IMIN, IMAX, -4550,-1900, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{frozen_river , IMIN,-4501, IMIN, IMAX, -1900, IMAX, IMIN, IMAX, IMIN, IMAX, -500, 500},
{snowy_plains , IMIN,-4500, IMIN, 1000, -1900, IMAX, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{mushroom_fields , IMIN, IMAX, IMIN, IMAX, IMIN,-10500, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{beach , -4500, 5500, IMIN, IMAX, -1900,-1100, -2225, IMAX, IMIN, IMAX, IMIN, 2666},
{jungle , 2000, 5500, 1000, IMAX, -1900, IMAX, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{sparse_jungle , 2000, 5500, 1000, 3000, -1899, IMAX, IMIN, IMAX, IMIN, IMAX, -500, IMAX},
{deep_ocean , -1500, 2000, IMIN, IMAX,-10500,-4551, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{stony_shore , IMIN, IMAX, IMIN, IMAX, -1900,-1100, IMIN,-2225, IMIN, IMAX, IMIN, IMAX},
{snowy_beach , IMIN,-4500, IMIN, IMAX, -1900,-1100, -2225, IMAX, IMIN, IMAX, IMIN, 2666},
{birch_forest , -1500, 2000, 1000, 3000, -1900, IMAX, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{dark_forest , -1500, 2000, 3000, IMAX, -1900, IMAX, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{snowy_taiga , IMIN,-4500, -1000, IMAX, -1900, IMAX, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{old_growth_pine_taiga , -4500,-1500, 3000, IMAX, -1899, IMAX, IMIN, IMAX, IMIN, IMAX, -500, IMAX},
{windswept_forest , IMIN, 2000, 1000, IMAX, -1899, IMAX, 4500, 5500, IMIN, IMAX, IMIN, IMAX},
{savanna , 2000, 5500, IMIN,-1000, -1900, IMAX, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{savanna_plateau , 2000, 5500, IMIN,-1000, -1100, IMAX, IMIN, 500, IMIN, IMAX, IMIN, IMAX},
{badlands , 5500, IMAX, IMIN, 1000, -1899, IMAX, IMIN, 500, IMIN, IMAX, IMIN, IMAX},
{wooded_badlands , 5500, IMAX, 1000, IMAX, -1899, IMAX, IMIN, 500, IMIN, IMAX, IMIN, IMAX},
{warm_ocean , 5500, IMAX, IMIN, IMAX,-10500,-1900, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{lukewarm_ocean , 2001, 5500, IMIN, IMAX, -4550,-1900, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{cold_ocean , -4500,-1501, IMIN, IMAX, -4550,-1900, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{deep_lukewarm_ocean , 2001, 5500, IMIN, IMAX,-10500,-4551, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{deep_cold_ocean , -4500,-1501, IMIN, IMAX,-10500,-4551, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{deep_frozen_ocean , IMIN,-4501, IMIN, IMAX,-10500,-4551, IMIN, IMAX, IMIN, IMAX, IMIN, IMAX},
{sunflower_plains , -1500, 2000, IMIN,-3500, -1899, IMAX, IMIN, IMAX, IMIN, IMAX, -500, IMAX},
{windswept_gravelly_hills, IMIN,-1500, IMIN,-1000, -1899, IMAX, 4500, 5500, IMIN, IMAX, IMIN, IMAX},
{flower_forest , -1500, 2000, IMIN,-3500, -1900, IMAX, IMIN, IMAX, IMIN, IMAX, IMIN, -500},
{ice_spikes , IMIN,-4500, IMIN,-3500, -1899, IMAX, IMIN, IMAX, IMIN, IMAX, -500, IMAX},
{old_growth_birch_forest , -1500, 2000, 1000, 3000, -1899, IMAX, IMIN, IMAX, IMIN, IMAX, -500, IMAX},
{old_growth_spruce_taiga , -4500,-1500, 3000, IMAX, -1900, IMAX, IMIN, IMAX, IMIN, IMAX, IMIN, -500},
{windswept_savanna , -1500, IMAX, IMIN, 3000, -1899, 300, 4500, 5500, IMIN, IMAX, 501, IMAX},
{eroded_badlands , 5500, IMAX, IMIN,-1000, -1899, IMAX, IMIN, 500, IMIN, IMAX, IMIN, IMAX},
{bamboo_jungle , 2000, 5500, 3000, IMAX, -1899, IMAX, IMIN, IMAX, IMIN, IMAX, -500, IMAX},
{dripstone_caves , IMIN, IMAX, IMIN, 6999, 3001, IMAX, IMIN, IMAX, 1000, 9500, IMIN, IMAX},
{lush_caves , IMIN, IMAX, 2001, IMAX, IMIN, IMAX, IMIN, IMAX, 1000, 9500, IMIN, IMAX},
{meadow , -4500, 2000, IMIN, 3000, 300, IMAX, -7799, 500, IMIN, IMAX, IMIN, IMAX},
{grove , IMIN, 2000, -1000, IMAX, -1899, IMAX, IMIN,-3750, IMIN, IMAX, IMIN, IMAX},
{snowy_slopes , IMIN, 2000, IMIN,-1000, -1899, IMAX, IMIN,-3750, IMIN, IMAX, IMIN, IMAX},
{jagged_peaks , IMIN, 2000, IMIN, IMAX, -1899, IMAX, IMIN,-3750, IMIN, IMAX, -9333,-4001},
{frozen_peaks , IMIN, 2000, IMIN, IMAX, -1899, IMAX, IMIN,-3750, IMIN, IMAX, 4000, 9333},
{stony_peaks , 2000, 5500, IMIN, IMAX, -1899, IMAX, IMIN,-3750, IMIN, IMAX, -9333, 9333},
{-1,0,0,0,0,0,0,0,0,0,0,0,0}};
static const int g_biome_para_range_19_diff[][13] = {
{eroded_badlands , 5500, IMAX, IMIN,-1000, -1899, IMAX, IMIN, 500, IMIN, IMAX, -500, IMAX},
{grove , IMIN, 2000, -1000, IMAX, -1899, IMAX, IMIN,-3750, IMIN,10499, IMIN, IMAX},
{snowy_slopes , IMIN, 2000, IMIN,-1000, -1899, IMAX, IMIN,-3750, IMIN,10499, IMIN, IMAX},
{jagged_peaks , IMIN, 2000, IMIN, IMAX, -1899, IMAX, IMIN,-3750, IMIN,10499, -9333,-4001},
{deep_dark , IMIN, IMAX, IMIN, IMAX, IMIN, IMAX, IMIN, 1818, 10500, IMAX, IMIN, IMAX},
{mangrove_swamp , 2000, IMAX, IMIN, IMAX, -1100, IMAX, 5500, IMAX, IMIN, IMAX, IMIN, IMAX},
{-1,0,0,0,0,0,0,0,0,0,0,0,0}};
static const int g_biome_para_range_20_diff[][13] = {
{swamp , -4500, 2000, IMIN, IMAX, -1100, IMAX, 5500, IMAX, IMIN, IMAX, IMIN, IMAX},
{grove , IMIN, 2000, -1000, IMAX, -1899, IMAX, IMIN,-3750, IMIN,10500, IMIN, IMAX},
{snowy_slopes , IMIN, 2000, IMIN,-1000, -1899, IMAX, IMIN,-3750, IMIN,10500, IMIN, IMAX},
{jagged_peaks , IMIN, 2000, IMIN, IMAX, -1899, IMAX, IMIN,-3750, IMIN,10500, -9333,-4000},
{frozen_peaks , IMIN, 2000, IMIN, IMAX, -1899, IMAX, IMIN,-3750, IMIN,10500, 4000, 9333},
{stony_peaks , 2000, 5500, IMIN, IMAX, -1899, IMAX, IMIN,-3750, IMIN,10500, -9333, 9333},
{cherry_grove , -4500, 2000, IMIN,-1000, 300, IMAX, -7799, 500, IMIN, IMAX, 2666, IMAX},
{-1,0,0,0,0,0,0,0,0,0,0,0,0}};
static const int g_biome_para_range_21wd_diff[][13] = {
{pale_garden , -1500, 2000, 3000, IMAX, 300, IMAX, -7799, 500, IMIN, IMAX, 2666, IMAX},
{-1,0,0,0,0,0,0,0,0,0,0,0,0}};
/**
* Gets the min/max parameter values within which a biome change can occur.
*/
const int *getBiomeParaExtremes(int mc)
{
if (mc <= MC_B1_7)
{
static const int extremes_beta[] = {
0, 10000,
0, 10000,
0,0, 0,0, 0,0, 0,0,
};
return extremes_beta;
}
if (mc <= MC_1_17)
return NULL;
static const int extremes[] = {
-4501, 5500,
-3500, 6999,
-10500, 300,
-7799, 5500,
1000, 10500, // depth has more dependencies
-9333, 9333,
};
return extremes;
}
/**
* Gets the min/max possible noise parameter values at which the given biome
* can generate. The values are in min/max pairs in order:
* temperature, humidity, continentalness, erosion, depth, weirdness.
*/
const int *getBiomeParaLimits(int mc, int id)
{
if (mc <= MC_1_17)
return NULL;
int i;
if (mc > MC_1_21_2)
{
for (i = 0; g_biome_para_range_21wd_diff[i][0] != -1; i++)
{
if (g_biome_para_range_21wd_diff[i][0] == id)
return &g_biome_para_range_21wd_diff[i][1];
}
}
if (mc > MC_1_19)
{
for (i = 0; g_biome_para_range_20_diff[i][0] != -1; i++)
{
if (g_biome_para_range_20_diff[i][0] == id)
return &g_biome_para_range_20_diff[i][1];
}
}
if (mc > MC_1_18)
{
for (i = 0; g_biome_para_range_19_diff[i][0] != -1; i++)
{
if (g_biome_para_range_19_diff[i][0] == id)
return &g_biome_para_range_19_diff[i][1];
}
}
for (i = 0; g_biome_para_range_18[i][0] != -1; i++)
{
if (g_biome_para_range_18[i][0] == id)
return &g_biome_para_range_18[i][1];
}
return NULL;
}
/**
* Determines which biomes are able to generate given climate parameter limits.
* Possible biomes are marked non-zero in the 'ids'.
*/
void getPossibleBiomesForLimits(char ids[256], int mc, int limits[6][2])
{
int i, j;
memset(ids, 0, 256*sizeof(char));
for (i = 0; i < 256; i++)
{
if (!isOverworld(mc, i))
continue;
const int *bp = getBiomeParaLimits(mc, i);
if (!bp)
continue;
for (j = 0; j < 6; j++)
{
if (limits[j][0] > bp[2*j+1] || limits[j][1] < bp[2*j+0])
break;
}
if (j >= 6)
ids[bp[-1]] = 1;
}
}
int getLargestRec(int match, const int *ids, int sx, int sz, Pos *p0, Pos *p1)
{
typedef struct { int n, j, w; } entry_t;
entry_t *meta = (entry_t*) calloc(sx > sz ? sx : sz, sizeof(*meta));
int i, j, w, m, ret;
ret = m = 0;
for (i = sx-1; i >= 0; i--)
{
for (j = 0; j < sz; j++)
{
if (ids[j*sx + i] == match)
meta[j].n++;
else
meta[j].n = 0;
}
for (w = j = 0; j < sz; j++)
{
int n = meta[j].n;
if (n > w)
{
meta[m].j = j;
meta[m].w = w;
m++;
w = n;
}
if (n == w)
continue;
do
{
entry_t e = meta[--m];
int area = w * (j - e.j);
if (area > ret)
{
p0->x = i; p0->z = e.j;
p1->x = i+w-1; p1->z = j-1;
ret = area;
}
w = e.w;
}
while (n < w);
if ((w = n))
m++;
}
}
free(meta);
return ret;
}