mirror of
https://github.com/Cubitect/cubiomes.git
synced 2025-04-03 07:01:21 +08:00
2146 lines
62 KiB
C
2146 lines
62 KiB
C
#include "finders.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
|
|
STRUCT(quad_threadinfo_t)
|
|
{
|
|
int64_t start, end;
|
|
StructureConfig sconf;
|
|
int threadID;
|
|
int quality;
|
|
const char *fnam;
|
|
};
|
|
|
|
|
|
//==============================================================================
|
|
// Globals
|
|
//==============================================================================
|
|
|
|
|
|
Biome biomes[256];
|
|
|
|
|
|
const int64_t lowerBaseBitsQ1[] = // for quad-structure with quality 1
|
|
{
|
|
0x3f18,0x520a,0x751a,0x9a0a
|
|
};
|
|
|
|
const int64_t lowerBaseBitsQ2[] = // for quad-structure with quality 2
|
|
{
|
|
0x0770,0x0775,0x07ad,0x07b2,0x0c3a,0x0c58,0x0cba,0x0cd8,0x0e38,
|
|
0x0e5a,0x0ed8,0x0eda,0x111c,0x1c96,0x2048,0x20e8,0x2248,0x224a,
|
|
0x22c8,0x258d,0x272d,0x2732,0x2739,0x2758,0x275d,0x27c8,0x27c9,
|
|
0x2aa9,0x2c3a,0x2cba,0x2eb8,0x308c,0x3206,0x371a,0x3890,0x3d0a,
|
|
0x3f18,0x4068,0x40ca,0x40e8,0x418a,0x4248,0x426a,0x42ea,0x4732,
|
|
0x4738,0x4739,0x4765,0x4768,0x476a,0x47b0,0x47b5,0x47d4,0x47d9,
|
|
0x47e8,0x4c58,0x4e38,0x4eb8,0x4eda,0x5118,0x520a,0x5618,0x5918,
|
|
0x591d,0x5a08,0x5e18,0x5f1c,0x60ca,0x6739,0x6748,0x6749,0x6758,
|
|
0x6776,0x67b4,0x67b9,0x67c9,0x67d8,0x67dd,0x67ec,0x6c3a,0x6c58,
|
|
0x6cba,0x6d9a,0x6e5a,0x6ed8,0x6eda,0x7108,0x717a,0x751a,0x7618,
|
|
0x791c,0x8068,0x8186,0x8248,0x824a,0x82c8,0x82ea,0x8730,0x8739,
|
|
0x8748,0x8768,0x87b9,0x87c9,0x87ce,0x87d9,0x898d,0x8c3a,0x8cda,
|
|
0x8e38,0x8eb8,0x951e,0x9718,0x9a0a,0xa04a,0xa068,0xa0ca,0xa0e8,
|
|
0xa18a,0xa26a,0xa2e8,0xa2ea,0xa43d,0xa4e1,0xa589,0xa76d,0xa7ac,
|
|
0xa7b1,0xa7ed,0xa85d,0xa86d,0xaa2d,0xb1f8,0xb217,0xb9f8,0xba09,
|
|
0xba17,0xbb0f,0xc54c,0xc6f9,0xc954,0xc9ce,0xd70b,0xd719,0xdc55,
|
|
0xdf0b,0xe1c4,0xe556,0xe589,0xea5d
|
|
};
|
|
|
|
|
|
//==============================================================================
|
|
// Saving & Loading Seeds
|
|
//==============================================================================
|
|
|
|
|
|
int64_t *loadSavedSeeds(const char *fnam, int64_t *scnt)
|
|
{
|
|
FILE *fp = fopen(fnam, "r");
|
|
|
|
int64_t seed;
|
|
int64_t *baseSeeds;
|
|
|
|
if (fp == NULL)
|
|
{
|
|
perror("ERR loadSavedSeeds: ");
|
|
return NULL;
|
|
}
|
|
|
|
*scnt = 0;
|
|
|
|
while (!feof(fp))
|
|
{
|
|
if (fscanf(fp, "%" PRId64, &seed) == 1) (*scnt)++;
|
|
else while (!feof(fp) && fgetc(fp) != '\n');
|
|
}
|
|
|
|
baseSeeds = (int64_t*) calloc(*scnt, sizeof(*baseSeeds));
|
|
|
|
rewind(fp);
|
|
|
|
for (int64_t i = 0; i < *scnt && !feof(fp);)
|
|
{
|
|
if (fscanf(fp, "%" PRId64, &baseSeeds[i]) == 1) i++;
|
|
else while (!feof(fp) && fgetc(fp) != '\n');
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
return baseSeeds;
|
|
}
|
|
|
|
|
|
//==============================================================================
|
|
// Multi-Structure Checks
|
|
//==============================================================================
|
|
|
|
int isQuadFeatureBase(const StructureConfig sconf, const int64_t seed, const int qual)
|
|
{
|
|
// seed offsets for the regions (0,0) to (1,1)
|
|
const int64_t reg00base = sconf.seed;
|
|
const int64_t reg01base = 341873128712 + sconf.seed;
|
|
const int64_t reg10base = 132897987541 + sconf.seed;
|
|
const int64_t reg11base = 341873128712 + 132897987541 + sconf.seed;
|
|
|
|
const int range = sconf.chunkRange;
|
|
const int upper = range - qual - 1;
|
|
const int lower = qual;
|
|
|
|
int64_t s;
|
|
|
|
s = (reg00base + seed) ^ 0x5deece66dLL; // & 0xffffffffffff;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
if ((int)(s >> 17) % range < upper) return 0;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
if ((int)(s >> 17) % range < upper) return 0;
|
|
|
|
s = (reg01base + seed) ^ 0x5deece66dLL; // & 0xffffffffffff;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
if ((int)(s >> 17) % range > lower) return 0;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
if ((int)(s >> 17) % range < upper) return 0;
|
|
|
|
s = (reg10base + seed) ^ 0x5deece66dLL; // & 0xffffffffffff;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
if ((int)(s >> 17) % range < upper) return 0;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
if ((int)(s >> 17) % range > lower) return 0;
|
|
|
|
s = (reg11base + seed) ^ 0x5deece66dLL; // & 0xffffffffffff;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
if ((int)(s >> 17) % range > lower) return 0;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
if ((int)(s >> 17) % range > lower) return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
int isTriFeatureBase(const StructureConfig sconf, const int64_t seed, const int qual)
|
|
{
|
|
// seed offsets for the regions (0,0) to (1,1)
|
|
const int64_t reg00base = sconf.seed;
|
|
const int64_t reg01base = 341873128712 + sconf.seed;
|
|
const int64_t reg10base = 132897987541 + sconf.seed;
|
|
const int64_t reg11base = 341873128712 + 132897987541 + sconf.seed;
|
|
|
|
const int range = sconf.chunkRange;
|
|
const int upper = range - qual - 1;
|
|
const int lower = qual;
|
|
|
|
int64_t s;
|
|
int missing = 0;
|
|
|
|
s = (reg00base + seed) ^ 0x5deece66dLL; // & 0xffffffffffff;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
if ((int)(s >> 17) % range < upper ||
|
|
(int)(((s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff) >> 17) % range < upper)
|
|
{
|
|
missing++;
|
|
}
|
|
|
|
s = (reg01base + seed) ^ 0x5deece66dLL; // & 0xffffffffffff;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
if ((int)(s >> 17) % range > lower ||
|
|
(int)(((s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff) >> 17) % range < upper)
|
|
{
|
|
if (missing) return 0;
|
|
missing++;
|
|
}
|
|
|
|
s = (reg10base + seed) ^ 0x5deece66dLL; // & 0xffffffffffff;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
if ((int)(s >> 17) % range < upper ||
|
|
(int)(((s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff) >> 17) % range > lower)
|
|
{
|
|
if (missing) return 0;
|
|
missing++;
|
|
}
|
|
|
|
s = (reg11base + seed) ^ 0x5deece66dLL; // & 0xffffffffffff;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
if ((int)(s >> 17) % range > lower ||
|
|
(int)(((s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff) >> 17) % range > lower)
|
|
{
|
|
if (missing) return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
int isLargeQuadBase(const StructureConfig sconf, const int64_t seed, const int qual)
|
|
{
|
|
// seed offsets for the regions (0,0) to (1,1)
|
|
const int64_t reg00base = sconf.seed;
|
|
const int64_t reg01base = 341873128712 + sconf.seed;
|
|
const int64_t reg10base = 132897987541 + sconf.seed;
|
|
const int64_t reg11base = 341873128712 + 132897987541 + sconf.seed;
|
|
|
|
// p1 = nextInt(range); p2 = nextInt(range); pos = (p1+p2)>>1
|
|
const int range = sconf.chunkRange;
|
|
const int rmax = (qual << 1) + 1; // p1 <= rmax && p1+p2 <= rmax
|
|
const int rmin2 = (range - qual - 1) << 1; // p1+p2 >= rmin2
|
|
const int rmin1 = rmin2 - range + 1; // p1 >= rmin1
|
|
|
|
int64_t s;
|
|
int p;
|
|
|
|
s = (reg00base + seed) ^ 0x5deece66dLL; // & 0xffffffffffff;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p = (int)(s >> 17) % range;
|
|
if (p < rmin1) return 0;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p += (int)(s >> 17) % range;
|
|
if (p < rmin2) return 0;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p = (int)(s >> 17) % range;
|
|
if (p < rmin1) return 0;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p += (int)(s >> 17) % range;
|
|
if (p < rmin2) return 0;
|
|
|
|
s = (reg01base + seed) ^ 0x5deece66dLL; // & 0xffffffffffff;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p = (int)(s >> 17) % range;
|
|
if (p > rmax) return 0;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p += (int)(s >> 17) % range;
|
|
if (p > rmax) return 0;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p = (int)(s >> 17) % range;
|
|
if (p < rmin1) return 0;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p += (int)(s >> 17) % range;
|
|
if (p < rmin2) return 0;
|
|
|
|
s = (reg10base + seed) ^ 0x5deece66dLL; // & 0xffffffffffff;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p = (int)(s >> 17) % range;
|
|
if (p < rmin1) return 0;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p += (int)(s >> 17) % range;
|
|
if (p < rmin2) return 0;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p = (int)(s >> 17) % range;
|
|
if (p > rmax) return 0;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p += (int)(s >> 17) % range;
|
|
if (p > rmax) return 0;
|
|
|
|
s = (reg11base + seed) ^ 0x5deece66dLL; // & 0xffffffffffff;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p = (int)(s >> 17) % range;
|
|
if (p > rmax) return 0;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p += (int)(s >> 17) % range;
|
|
if (p > rmax) return 0;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p = (int)(s >> 17) % range;
|
|
if (p > rmax) return 0;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p += (int)(s >> 17) % range;
|
|
if (p > rmax) return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
int isLargeTriBase(const StructureConfig sconf, const int64_t seed, const int qual)
|
|
{
|
|
// seed offsets for the regions (0,0) to (1,1)
|
|
const int64_t reg00base = sconf.seed;
|
|
const int64_t reg01base = 341873128712 + sconf.seed;
|
|
const int64_t reg10base = 132897987541 + sconf.seed;
|
|
const int64_t reg11base = 341873128712 + 132897987541 + sconf.seed;
|
|
|
|
// p1 = nextInt(range); p2 = nextInt(range); pos = (p1+p2)>>1
|
|
const int range = sconf.chunkRange;
|
|
const int rmax = (qual << 1) + 1; // p1 <= rmax && p1+p2 <= rmax
|
|
const int rmin2 = (range - qual - 1) << 1; // p1+p2 >= rmin2
|
|
const int rmin1 = rmin2 - range + 1; // p1 >= rmin1
|
|
|
|
int64_t s;
|
|
int p;
|
|
|
|
int incomplete = 0;
|
|
|
|
s = (reg00base + seed) ^ 0x5deece66dLL; // & 0xffffffffffff;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p = (int)(s >> 17) % range;
|
|
if (p < rmin1) goto incomp11;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p += (int)(s >> 17) % range;
|
|
if (p < rmin2) goto incomp11;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p = (int)(s >> 17) % range;
|
|
if (p < rmin1) goto incomp11;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p += (int)(s >> 17) % range;
|
|
if (p < rmin2) goto incomp11;
|
|
|
|
if (0)
|
|
{
|
|
incomp11:
|
|
incomplete = 1;
|
|
}
|
|
|
|
s = (reg01base + seed) ^ 0x5deece66dLL; // & 0xffffffffffff;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p = (int)(s >> 17) % range;
|
|
if (p > rmax) goto incomp01;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p += (int)(s >> 17) % range;
|
|
if (p > rmax) goto incomp01;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p = (int)(s >> 17) % range;
|
|
if (p < rmin1) goto incomp01;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p += (int)(s >> 17) % range;
|
|
if (p < rmin2) goto incomp01;
|
|
|
|
if (0)
|
|
{
|
|
incomp01:
|
|
if (incomplete) return 0;
|
|
incomplete = 2;
|
|
}
|
|
|
|
s = (reg10base + seed) ^ 0x5deece66dLL; // & 0xffffffffffff;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p = (int)(s >> 17) % range;
|
|
if (p < rmin1) goto incomp10;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p += (int)(s >> 17) % range;
|
|
if (p < rmin2) goto incomp10;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p = (int)(s >> 17) % range;
|
|
if (p > rmax) goto incomp10;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p += (int)(s >> 17) % range;
|
|
if (p > rmax) goto incomp10;
|
|
|
|
if (0)
|
|
{
|
|
incomp10:
|
|
if (incomplete) return 0;
|
|
incomplete = 3;
|
|
}
|
|
|
|
s = (reg11base + seed) ^ 0x5deece66dLL; // & 0xffffffffffff;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p = (int)(s >> 17) % range;
|
|
if (p > rmax) goto incomp00;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p += (int)(s >> 17) % range;
|
|
if (p > rmax) goto incomp00;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p = (int)(s >> 17) % range;
|
|
if (p > rmax) goto incomp00;
|
|
s = (s * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
p += (int)(s >> 17) % range;
|
|
if (p > rmax) goto incomp00;
|
|
|
|
if (0)
|
|
{
|
|
incomp00:
|
|
if (incomplete) return 0;
|
|
incomplete = 4;
|
|
}
|
|
|
|
return incomplete ? incomplete : -1;
|
|
}
|
|
|
|
|
|
/* Calls the correct quad-base finder for the structure config, if available.
|
|
* (Exits program otherwise.)
|
|
*/
|
|
int isQuadBase(const StructureConfig sconf, const int64_t seed, const int64_t qual)
|
|
{
|
|
if ((sconf.chunkRange & (sconf.chunkRange-1)) == 0)
|
|
{
|
|
fprintf(stderr,
|
|
"Quad-finder using power of 2 RNG is not implemented yet.\n");
|
|
exit(-1);
|
|
}
|
|
|
|
switch(sconf.properties)
|
|
{
|
|
case 0:
|
|
return isQuadFeatureBase(sconf, seed, qual);
|
|
case LARGE_STRUCT:
|
|
return isLargeQuadBase(sconf, seed, qual);
|
|
default:
|
|
fprintf(stderr,
|
|
"Unknown properties field for structure: 0x%04X\n",
|
|
sconf.properties);
|
|
exit(-1);
|
|
}
|
|
}
|
|
|
|
/* Calls the correct triple-base finder for the structure config, if available.
|
|
* (Exits program otherwise.)
|
|
*/
|
|
int isTriBase(const StructureConfig sconf, const int64_t seed, const int64_t qual)
|
|
{
|
|
if ((sconf.chunkRange & (sconf.chunkRange-1)) == 0)
|
|
{
|
|
fprintf(stderr,
|
|
"Tri-finder using power of 2 RNG is not implemented yet.\n");
|
|
exit(-1);
|
|
}
|
|
|
|
switch(sconf.properties)
|
|
{
|
|
case 0:
|
|
return isTriFeatureBase(sconf, seed, qual);
|
|
case LARGE_STRUCT:
|
|
return isLargeTriBase(sconf, seed, qual);
|
|
default:
|
|
fprintf(stderr,
|
|
"Unknown properties field for structure: 0x%04X\n",
|
|
sconf.properties);
|
|
exit(-1);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Searches for the optimal AFK position given four structures at positions 'p',
|
|
* each of volume (ax,ay,az).
|
|
*
|
|
* Returned is the number of spawning spaces within reach.
|
|
*/
|
|
int countBlocksInSpawnRange(Pos p[4], const int ax, const int ay, const int az)
|
|
{
|
|
int minX = 3e7, minZ = 3e7, maxX = -3e7, maxZ = -3e7;
|
|
int best;
|
|
|
|
|
|
// Find corners
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
if (p[i].x < minX) minX = p[i].x;
|
|
if (p[i].z < minZ) minZ = p[i].z;
|
|
if (p[i].x > maxX) maxX = p[i].x;
|
|
if (p[i].z > maxZ) maxZ = p[i].z;
|
|
}
|
|
|
|
|
|
// assume that the search area is bound by the inner corners
|
|
maxX += ax;
|
|
maxZ += az;
|
|
best = 0;
|
|
|
|
double thsq = 128.0*128.0 - az*az/4.0;
|
|
|
|
for (int x = minX; x < maxX; x++)
|
|
{
|
|
for (int z = minZ; z < maxZ; z++)
|
|
{
|
|
int inrange = 0;
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
double dx = p[i].x - (x+0.5);
|
|
double dz = p[i].z - (z+0.5);
|
|
|
|
for (int px = 0; px < ax; px++)
|
|
{
|
|
for (int pz = 0; pz < az; pz++)
|
|
{
|
|
double ddx = px + dx;
|
|
double ddz = pz + dz;
|
|
inrange += (ddx*ddx + ddz*ddz <= thsq);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (inrange > best)
|
|
{
|
|
best = inrange;
|
|
}
|
|
}
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
#ifdef USE_PTHREAD
|
|
static void *search4QuadBasesThread(void *data)
|
|
#else
|
|
static DWORD WINAPI search4QuadBasesThread(LPVOID data)
|
|
#endif
|
|
{
|
|
quad_threadinfo_t info = *(quad_threadinfo_t*)data;
|
|
|
|
const int64_t start = info.start;
|
|
const int64_t end = info.end;
|
|
const StructureConfig stc = info.sconf;
|
|
|
|
int64_t seed;
|
|
|
|
int64_t *lowerBits;
|
|
int lowerBitsCnt;
|
|
int lowerBitsIdx = 0;
|
|
int i;
|
|
|
|
lowerBits = (int64_t *) malloc(0x10000 * sizeof(int64_t));
|
|
|
|
if (stc.properties == 0 && stc.chunkRange == 24 && info.quality == 1)
|
|
{
|
|
lowerBitsCnt = sizeof(lowerBaseBitsQ1) / sizeof(lowerBaseBitsQ1[0]);
|
|
for (i = 0; i < lowerBitsCnt; i++)
|
|
{
|
|
lowerBits[i] = (lowerBaseBitsQ1[i] - stc.seed) & 0xffff;
|
|
}
|
|
}
|
|
else if (stc.properties == 0 && stc.chunkRange == 24 && info.quality == 2)
|
|
{
|
|
lowerBitsCnt = sizeof(lowerBaseBitsQ2) / sizeof(lowerBaseBitsQ2[0]);
|
|
for (i = 0; i < lowerBitsCnt; i++)
|
|
{
|
|
lowerBits[i] = (lowerBaseBitsQ2[i] - stc.seed) & 0xffff;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
printf("WARN search4QuadBasesThread: "
|
|
"Lower bits for this quality and property have not been defined"
|
|
" => trying all combinations.\n");
|
|
|
|
lowerBitsCnt = 0x10000;
|
|
for (i = 0; i < lowerBitsCnt; i++) lowerBits[i] = i;
|
|
}
|
|
|
|
char fnam[256];
|
|
sprintf(fnam, "%s.part%d", info.fnam, info.threadID);
|
|
|
|
FILE *fp = fopen(fnam, "a+");
|
|
if (fp == NULL)
|
|
{
|
|
fprintf(stderr, "Could not open \"%s\" for writing.\n", fnam);
|
|
free(lowerBits);
|
|
exit(-1);
|
|
}
|
|
|
|
seed = start;
|
|
|
|
// Check the last entry in the file and use it as a starting point if it
|
|
// exists. (I.e. loading the saved progress.)
|
|
int c, nnl = 0;
|
|
char buf[32];
|
|
for (i = 1; i < 32; i++)
|
|
{
|
|
if (fseek(fp, -i, SEEK_END)) break;
|
|
c = fgetc(fp);
|
|
if (c <= 0 || (nnl && c == '\n')) break;
|
|
nnl |= (c != '\n');
|
|
}
|
|
|
|
if (i < 32 && !fseek(fp, 1-i, SEEK_END) && fread(buf, i-1, 1, fp) > 0)
|
|
{
|
|
if (sscanf(buf, "%" PRId64, &seed) == 1)
|
|
{
|
|
while (lowerBits[lowerBitsIdx] <= (seed & 0xffff))
|
|
lowerBitsIdx++;
|
|
|
|
seed = (seed & 0x0000ffffffff0000) + lowerBits[lowerBitsIdx];
|
|
|
|
printf("Thread %d starting from: %" PRId64"\n", info.threadID, seed);
|
|
}
|
|
else
|
|
{
|
|
seed = start;
|
|
}
|
|
}
|
|
|
|
|
|
fseek(fp, 0, SEEK_END);
|
|
|
|
|
|
while (seed < end)
|
|
{
|
|
if (isQuadBase(info.sconf, seed, info.quality))
|
|
{
|
|
fprintf(fp, "%" PRId64"\n", seed);
|
|
fflush(fp);
|
|
//printf("Thread %d: %" PRId64"\n", info.threadID, seed);
|
|
}
|
|
|
|
lowerBitsIdx++;
|
|
if (lowerBitsIdx >= lowerBitsCnt)
|
|
{
|
|
lowerBitsIdx = 0;
|
|
seed += 0x10000;
|
|
}
|
|
seed = (seed & 0x0000ffffffff0000) + lowerBits[lowerBitsIdx];
|
|
}
|
|
|
|
fclose(fp);
|
|
free(lowerBits);
|
|
|
|
#ifdef USE_PTHREAD
|
|
pthread_exit(NULL);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
|
|
void search4QuadBases(const char *fnam, const int threads,
|
|
const StructureConfig structureConfig, const int quality)
|
|
{
|
|
thread_id_t threadID[threads];
|
|
quad_threadinfo_t info[threads];
|
|
int64_t t;
|
|
|
|
for (t = 0; t < threads; t++)
|
|
{
|
|
info[t].threadID = t;
|
|
info[t].start = (t * SEED_BASE_MAX / threads) & 0x0000ffffffff0000;
|
|
info[t].end = ((info[t].start + (SEED_BASE_MAX-1) / threads) & 0x0000ffffffff0000) + 1;
|
|
info[t].fnam = fnam;
|
|
info[t].quality = quality;
|
|
info[t].sconf = structureConfig;
|
|
}
|
|
|
|
|
|
#ifdef USE_PTHREAD
|
|
|
|
for (t = 0; t < threads; t++)
|
|
{
|
|
pthread_create(&threadID[t], NULL, search4QuadBasesThread, (void*)&info[t]);
|
|
}
|
|
|
|
for (t = 0; t < threads; t++)
|
|
{
|
|
pthread_join(threadID[t], NULL);
|
|
}
|
|
|
|
#else
|
|
|
|
for (t = 0; t < threads; t++)
|
|
{
|
|
threadID[t] = CreateThread(NULL, 0, search4QuadBasesThread, (LPVOID)&info[t], 0, NULL);
|
|
}
|
|
|
|
WaitForMultipleObjects(threads, threadID, TRUE, INFINITE);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// merge thread parts
|
|
|
|
char fnamThread[256];
|
|
char buffer[4097];
|
|
FILE *fp = fopen(fnam, "w");
|
|
if (fp == NULL) {
|
|
fprintf(stderr, "Could not open \"%s\" for writing.\n", fnam);
|
|
exit(-1);
|
|
}
|
|
FILE *fpart;
|
|
int n;
|
|
|
|
for (t = 0; t < threads; t++)
|
|
{
|
|
sprintf(fnamThread, "%s.part%d", info[t].fnam, info[t].threadID);
|
|
|
|
fpart = fopen(fnamThread, "r");
|
|
|
|
if (fpart == NULL)
|
|
{
|
|
perror("ERR search4QuadBases: ");
|
|
break;
|
|
}
|
|
|
|
while ((n = fread(buffer, sizeof(char), 4096, fpart)))
|
|
{
|
|
if (!fwrite(buffer, sizeof(char), n, fp))
|
|
{
|
|
perror("ERR search4QuadBases: ");
|
|
fclose(fp);
|
|
fclose(fpart);
|
|
return;
|
|
}
|
|
}
|
|
|
|
fclose(fpart);
|
|
|
|
remove(fnamThread);
|
|
}
|
|
|
|
fclose(fp);
|
|
}
|
|
|
|
|
|
//==============================================================================
|
|
// Finding Structure Positions
|
|
//==============================================================================
|
|
|
|
|
|
Pos getStructurePos(const StructureConfig config, int64_t seed,
|
|
const int regionX, const int regionZ)
|
|
{
|
|
Pos pos;
|
|
|
|
// set seed
|
|
seed = regionX*341873128712 + regionZ*132897987541 + seed + config.seed;
|
|
seed = (seed ^ 0x5deece66dLL);// & ((1LL << 48) - 1);
|
|
|
|
seed = (seed * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
|
|
if (config.chunkRange & (config.chunkRange-1))
|
|
{
|
|
pos.x = (int)(seed >> 17) % config.chunkRange;
|
|
|
|
seed = (seed * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
pos.z = (int)(seed >> 17) % config.chunkRange;
|
|
}
|
|
else
|
|
{
|
|
// Java RNG treats powers of 2 as a special case.
|
|
pos.x = (config.chunkRange * (seed >> 17)) >> 31;
|
|
|
|
seed = (seed * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
pos.z = (config.chunkRange * (seed >> 17)) >> 31;
|
|
}
|
|
|
|
// Structure is positioned at chunk origin but the biome check is performed
|
|
// at block position (9,9) within chunk. [CHECK: maybe (8,8) in 1.7]
|
|
pos.x = ((regionX*config.regionSize + pos.x) << 4) + 9;
|
|
pos.z = ((regionZ*config.regionSize + pos.z) << 4) + 9;
|
|
return pos;
|
|
}
|
|
|
|
Pos getStructureChunkInRegion(const StructureConfig config, int64_t seed,
|
|
const int regionX, const int regionZ)
|
|
{
|
|
/*
|
|
// Vanilla like implementation.
|
|
seed = regionX*341873128712 + regionZ*132897987541 + seed + structureSeed;
|
|
setSeed(&(seed));
|
|
|
|
Pos pos;
|
|
pos.x = nextInt(&seed, 24);
|
|
pos.z = nextInt(&seed, 24);
|
|
*/
|
|
Pos pos;
|
|
|
|
seed = regionX*341873128712 + regionZ*132897987541 + seed + config.seed;
|
|
seed = (seed ^ 0x5deece66dLL);// & ((1LL << 48) - 1);
|
|
|
|
seed = (seed * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
|
|
if (config.chunkRange & (config.chunkRange-1))
|
|
{
|
|
pos.x = (int)(seed >> 17) % config.chunkRange;
|
|
|
|
seed = (seed * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
pos.z = (int)(seed >> 17) % config.chunkRange;
|
|
}
|
|
else
|
|
{
|
|
// Java RNG treats powers of 2 as a special case.
|
|
pos.x = (config.chunkRange * (seed >> 17)) >> 31;
|
|
|
|
seed = (seed * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
pos.z = (config.chunkRange * (seed >> 17)) >> 31;
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
|
|
Pos getLargeStructurePos(StructureConfig config, int64_t seed,
|
|
const int regionX, const int regionZ)
|
|
{
|
|
Pos pos;
|
|
|
|
//TODO: power of two chunk ranges...
|
|
|
|
// set seed
|
|
seed = regionX*341873128712 + regionZ*132897987541 + seed + config.seed;
|
|
seed = (seed ^ 0x5deece66dLL) & ((1LL << 48) - 1);
|
|
|
|
seed = (seed * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
pos.x = (seed >> 17) % config.chunkRange;
|
|
seed = (seed * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
pos.x += (seed >> 17) % config.chunkRange;
|
|
|
|
seed = (seed * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
pos.z = (seed >> 17) % config.chunkRange;
|
|
seed = (seed * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
pos.z += (seed >> 17) % config.chunkRange;
|
|
|
|
pos.x = regionX*config.regionSize + (pos.x >> 1);
|
|
pos.z = regionZ*config.regionSize + (pos.z >> 1);
|
|
pos.x = pos.x*16 + 9;
|
|
pos.z = pos.z*16 + 9;
|
|
return pos;
|
|
}
|
|
|
|
|
|
Pos getLargeStructureChunkInRegion(StructureConfig config, int64_t seed,
|
|
const int regionX, const int regionZ)
|
|
{
|
|
Pos pos;
|
|
|
|
//TODO: power of two chunk ranges...
|
|
|
|
// set seed
|
|
seed = regionX*341873128712 + regionZ*132897987541 + seed + config.seed;
|
|
seed = (seed ^ 0x5deece66dLL) & ((1LL << 48) - 1);
|
|
|
|
seed = (seed * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
pos.x = (seed >> 17) % config.chunkRange;
|
|
seed = (seed * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
pos.x += (seed >> 17) % config.chunkRange;
|
|
|
|
seed = (seed * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
pos.z = (seed >> 17) % config.chunkRange;
|
|
seed = (seed * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
pos.z += (seed >> 17) % config.chunkRange;
|
|
|
|
pos.x >>= 1;
|
|
pos.z >>= 1;
|
|
|
|
return pos;
|
|
}
|
|
|
|
|
|
int isMineshaftChunk(int64_t seed, const int chunkX, const int chunkZ)
|
|
{
|
|
int64_t s = seed;
|
|
setSeed(&s);
|
|
int64_t i = nextLong(&s);
|
|
int64_t j = nextLong(&s);
|
|
s = chunkX * i ^ chunkZ * j ^ seed;
|
|
setSeed(&s);
|
|
return nextDouble(&s) < 0.004;
|
|
}
|
|
|
|
int isTreasureChunk(int64_t seed, const int chunkX, const int chunkZ)
|
|
{
|
|
seed = chunkX*341873128712 + chunkZ*132897987541 + seed + TREASURE_CONFIG.seed;
|
|
setSeed(&seed);
|
|
return nextFloat(&seed) < 0.01;
|
|
}
|
|
|
|
|
|
//==============================================================================
|
|
// Checking Biomes & Biome Helper Functions
|
|
//==============================================================================
|
|
|
|
|
|
int getBiomeAtPos(const LayerStack g, const Pos pos)
|
|
{
|
|
int *map = allocCache(&g.layers[L_VORONOI_ZOOM_1], 1, 1);
|
|
genArea(&g.layers[L_VORONOI_ZOOM_1], map, pos.x, pos.z, 1, 1);
|
|
int biomeID = map[0];
|
|
free(map);
|
|
return biomeID;
|
|
}
|
|
|
|
Pos findBiomePosition(
|
|
const int mcversion,
|
|
const LayerStack g,
|
|
int *cache,
|
|
const int centerX,
|
|
const int centerZ,
|
|
const int range,
|
|
const int *isValid,
|
|
int64_t *seed,
|
|
int *passes
|
|
)
|
|
{
|
|
int x1 = (centerX-range) >> 2;
|
|
int z1 = (centerZ-range) >> 2;
|
|
int x2 = (centerX+range) >> 2;
|
|
int z2 = (centerZ+range) >> 2;
|
|
int width = x2 - x1 + 1;
|
|
int height = z2 - z1 + 1;
|
|
int *map;
|
|
int i, j, found;
|
|
|
|
Layer *layer = &g.layers[L_RIVER_MIX_4];
|
|
Pos out;
|
|
|
|
if (layer->scale != 4)
|
|
{
|
|
printf("WARN findBiomePosition: The generator has unexpected scale %d at layer %d.\n",
|
|
layer->scale, L_RIVER_MIX_4);
|
|
}
|
|
|
|
map = cache ? cache : allocCache(layer, width, height);
|
|
|
|
genArea(layer, map, x1, z1, width, height);
|
|
|
|
out.x = centerX;
|
|
out.z = centerZ;
|
|
found = 0;
|
|
|
|
if (mcversion >= MC_1_13)
|
|
{
|
|
for (i = 0, j = 2; i < width*height; i++)
|
|
{
|
|
if (!isValid[map[i] & 0xff]) continue;
|
|
if ((found == 0 || nextInt(seed, j++) == 0))
|
|
{
|
|
out.x = (x1 + i%width) << 2;
|
|
out.z = (z1 + i/width) << 2;
|
|
found = 1;
|
|
}
|
|
}
|
|
found = j - 2;
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < width*height; i++)
|
|
{
|
|
if (isValid[map[i] & 0xff] &&
|
|
(found == 0 || nextInt(seed, found + 1) == 0))
|
|
{
|
|
out.x = (x1 + i%width) << 2;
|
|
out.z = (z1 + i/width) << 2;
|
|
++found;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (cache == NULL)
|
|
{
|
|
free(map);
|
|
}
|
|
|
|
if (passes != NULL)
|
|
{
|
|
*passes = found;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
|
|
int areBiomesViable(
|
|
const LayerStack g,
|
|
int * cache,
|
|
const int posX,
|
|
const int posZ,
|
|
const int radius,
|
|
const int * isValid
|
|
)
|
|
{
|
|
int x1 = (posX - radius) >> 2;
|
|
int z1 = (posZ - radius) >> 2;
|
|
int x2 = (posX + radius) >> 2;
|
|
int z2 = (posZ + radius) >> 2;
|
|
int width = x2 - x1 + 1;
|
|
int height = z2 - z1 + 1;
|
|
int i;
|
|
int *map;
|
|
|
|
Layer *layer = &g.layers[L_RIVER_MIX_4];
|
|
|
|
if (layer->scale != 4)
|
|
{
|
|
printf("WARN areBiomesViable: The generator has unexpected scale %d at layer %d.\n",
|
|
layer->scale, L_RIVER_MIX_4);
|
|
}
|
|
|
|
map = cache ? cache : allocCache(layer, width, height);
|
|
genArea(layer, map, x1, z1, width, height);
|
|
|
|
for (i = 0; i < width*height; i++)
|
|
{
|
|
if (!isValid[ map[i] & 0xff ])
|
|
{
|
|
if (cache == NULL) free(map);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (cache == NULL) free(map);
|
|
return 1;
|
|
}
|
|
|
|
|
|
int getBiomeRadius(
|
|
const int * map,
|
|
const int mapSide,
|
|
const int * biomes,
|
|
const int bnum,
|
|
const int ignoreMutations)
|
|
{
|
|
int r, i, b;
|
|
int blist[0x100];
|
|
int mask = ignoreMutations ? 0x7f : 0xff;
|
|
int radiusMax = mapSide / 2;
|
|
|
|
if ((mapSide & 1) == 0)
|
|
{
|
|
printf("WARN getBiomeRadius: Side length of the square map should be an odd integer.\n");
|
|
}
|
|
|
|
memset(blist, 0, sizeof(blist));
|
|
|
|
for (r = 1; r < radiusMax; r++)
|
|
{
|
|
for (i = radiusMax-r; i <= radiusMax+r; i++)
|
|
{
|
|
blist[ map[(radiusMax-r) * mapSide+ i] & mask ] = 1;
|
|
blist[ map[(radiusMax+r-1) * mapSide + i] & mask ] = 1;
|
|
blist[ map[mapSide*i + (radiusMax-r)] & mask ] = 1;
|
|
blist[ map[mapSide*i + (radiusMax+r-1)] & mask ] = 1;
|
|
}
|
|
|
|
for (b = 0; b < bnum && blist[biomes[b] & mask]; b++);
|
|
if (b >= bnum)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return r != radiusMax ? r : -1;
|
|
}
|
|
|
|
|
|
|
|
//==============================================================================
|
|
// Finding Strongholds and Spawn
|
|
//==============================================================================
|
|
|
|
|
|
int* getValidStrongholdBiomes()
|
|
{
|
|
static int validStrongholdBiomes[256];
|
|
|
|
if (!validStrongholdBiomes[plains])
|
|
{
|
|
int id;
|
|
for (id = 0; id < 256; id++)
|
|
{
|
|
if (biomeExists(id) && biomes[id].height > 0.0)
|
|
validStrongholdBiomes[id] = 1;
|
|
}
|
|
}
|
|
|
|
return validStrongholdBiomes;
|
|
}
|
|
|
|
|
|
int findStrongholds(const int mcversion, LayerStack *g, int *cache,
|
|
Pos *locations, int64_t worldSeed, int maxSH, const int maxRadius)
|
|
{
|
|
const int *validStrongholdBiomes = getValidStrongholdBiomes();
|
|
int i, x, z;
|
|
double distance;
|
|
|
|
int currentRing = 0;
|
|
int currentCount = 0;
|
|
int perRing = 3;
|
|
|
|
setSeed(&worldSeed);
|
|
double angle = nextDouble(&worldSeed) * PI * 2.0;
|
|
|
|
if (mcversion >= MC_1_9)
|
|
{
|
|
if (maxSH <= 0) maxSH = 128;
|
|
|
|
for (i = 0; i < maxSH; i++)
|
|
{
|
|
distance = (4.0 * 32.0) + (6.0 * currentRing * 32.0) +
|
|
(nextDouble(&worldSeed) - 0.5) * 32 * 2.5;
|
|
|
|
if (maxRadius && distance*16 > maxRadius)
|
|
return i;
|
|
|
|
x = (int)round(cos(angle) * distance);
|
|
z = (int)round(sin(angle) * distance);
|
|
|
|
locations[i] = findBiomePosition(mcversion, *g, cache,
|
|
(x << 4) + 8, (z << 4) + 8, 112, validStrongholdBiomes,
|
|
&worldSeed, NULL);
|
|
|
|
angle += 2 * PI / perRing;
|
|
|
|
currentCount++;
|
|
if (currentCount == perRing)
|
|
{
|
|
// Current ring is complete, move to next ring.
|
|
currentRing++;
|
|
currentCount = 0;
|
|
perRing = perRing + 2*perRing/(currentRing+1);
|
|
if (perRing > 128-i)
|
|
perRing = 128-i;
|
|
angle = angle + nextDouble(&worldSeed) * PI * 2.0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (maxSH <= 0) maxSH = 3;
|
|
|
|
for (i = 0; i < maxSH; i++)
|
|
{
|
|
distance = (1.25 + nextDouble(&worldSeed)) * 32.0;
|
|
|
|
if (maxRadius && distance*16 > maxRadius)
|
|
return i;
|
|
|
|
x = (int)round(cos(angle) * distance);
|
|
z = (int)round(sin(angle) * distance);
|
|
|
|
locations[i] = findBiomePosition(mcversion, *g, cache,
|
|
(x << 4) + 8, (z << 4) + 8, 112, validStrongholdBiomes,
|
|
&worldSeed, NULL);
|
|
|
|
angle += 2 * PI / 3.0;
|
|
}
|
|
}
|
|
|
|
return maxSH;
|
|
}
|
|
|
|
|
|
static double getGrassProbability(int64_t seed, int biome, int x, int z)
|
|
{
|
|
// TODO: Use ChunkGeneratorOverworld.generateHeightmap for better estimate.
|
|
// TODO: Try to determine the actual probabilities and build a statistic.
|
|
switch (biome)
|
|
{
|
|
case plains: return 1.0;
|
|
case mountains: return 0.8; // height dependent
|
|
case forest: return 1.0;
|
|
case taiga: return 1.0;
|
|
case swamp: return 0.6; // height dependent
|
|
case river: return 0.5;
|
|
case beach: return 0.1;
|
|
case wooded_hills: return 1.0;
|
|
case taiga_hills: return 1.0;
|
|
case mountain_edge: return 1.0; // height dependent
|
|
case jungle: return 1.0;
|
|
case jungle_hills: return 1.0;
|
|
case jungle_edge: return 1.0;
|
|
case birch_forest: return 1.0;
|
|
case birch_forest_hills: return 1.0;
|
|
case dark_forest: return 0.9;
|
|
case snowy_taiga: return 0.2; // below trees
|
|
case snowy_taiga_hills: return 0.2; // below trees
|
|
case giant_tree_taiga: return 0.6;
|
|
case giant_tree_taiga_hills: return 0.6;
|
|
case wooded_mountains: return 0.2; // height dependent
|
|
case savanna: return 1.0;
|
|
case savanna_plateau: return 1.0;
|
|
case wooded_badlands_plateau: return 0.1; // height dependent
|
|
case badlands_plateau: return 0.1; // height dependent
|
|
|
|
case sunflower_plains: return 1.0;
|
|
case gravelly_mountains: return 0.2;
|
|
case flower_forest: return 1.0;
|
|
case taiga_mountains: return 1.0;
|
|
case swamp_hills: return 0.9;
|
|
case modified_jungle: return 1.0;
|
|
case modified_jungle_edge: return 1.0;
|
|
case tall_birch_forest: return 1.0;
|
|
case tall_birch_hills: return 1.0;
|
|
case dark_forest_hills: return 0.9;
|
|
case snowy_taiga_mountains: return 0.2;
|
|
case giant_spruce_taiga: return 0.6;
|
|
case giant_spruce_taiga_hills: return 0.6;
|
|
case modified_gravelly_mountains: return 0.2;
|
|
case shattered_savanna: return 1.0;
|
|
case shattered_savanna_plateau: return 1.0;
|
|
case bamboo_jungle: return 0.4;
|
|
case bamboo_jungle_hills: return 0.4;
|
|
// NOTE: in rare circumstances you can get also get grassy islands that are
|
|
// completely in ocean variants...
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
static int canCoordinateBeSpawn(const int64_t seed, LayerStack *g, int *cache, Pos pos)
|
|
{
|
|
int biome = getBiomeAtPos(*g, pos);
|
|
return getGrassProbability(seed, biome, pos.x, pos.z) >= 0.5;
|
|
}
|
|
|
|
static int* getValidSpawnBiomes()
|
|
{
|
|
static int isSpawnBiome[256];
|
|
unsigned int i;
|
|
|
|
if (!isSpawnBiome[biomesToSpawnIn[0]])
|
|
{
|
|
for (i = 0; i < sizeof(biomesToSpawnIn) / sizeof(int); i++)
|
|
{
|
|
isSpawnBiome[ biomesToSpawnIn[i] ] = 1;
|
|
}
|
|
}
|
|
|
|
return isSpawnBiome;
|
|
}
|
|
|
|
|
|
Pos getSpawn(const int mcversion, LayerStack *g, int *cache, int64_t worldSeed)
|
|
{
|
|
const int *isSpawnBiome = getValidSpawnBiomes();
|
|
Pos spawn;
|
|
int found;
|
|
int i;
|
|
|
|
setSeed(&worldSeed);
|
|
spawn = findBiomePosition(mcversion, *g, cache, 0, 0, 256, isSpawnBiome,
|
|
&worldSeed, &found);
|
|
|
|
if (!found)
|
|
{
|
|
//printf("Unable to find spawn biome.\n");
|
|
spawn.x = spawn.z = 8;
|
|
}
|
|
|
|
if (mcversion >= MC_1_13)
|
|
{
|
|
// TODO: The 1.13 section may need further checking!
|
|
int n2 = 0;
|
|
int n3 = 0;
|
|
int n4 = 0;
|
|
int n5 = -1;
|
|
|
|
for (i = 0; i < 1024; i++)
|
|
{
|
|
if (n2 > -16 && n2 <= 16 && n3 > -16 && n3 <= 16)
|
|
{
|
|
int cx = ((spawn.x >> 4) + n2) << 4;
|
|
int cz = ((spawn.z >> 4) + n3) << 4;
|
|
|
|
for (int i2 = cx; i2 <= cx+15; i2++)
|
|
{
|
|
for (int i3 = cz; i3 <= cz+15; i3++)
|
|
{
|
|
Pos pos = {i2, i3};
|
|
if (canCoordinateBeSpawn(worldSeed, g, cache, pos))
|
|
{
|
|
return pos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (n2 == n3 || (n2 < 0 && n2 == - n3) || (n2 > 0 && n2 == 1 - n3))
|
|
{
|
|
int n7 = n4;
|
|
n4 = - n5;
|
|
n5 = n7;
|
|
}
|
|
n2 += n4;
|
|
n3 += n5;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < 1000 && !canCoordinateBeSpawn(worldSeed, g, cache, spawn); i++)
|
|
{
|
|
spawn.x += nextInt(&worldSeed, 64) - nextInt(&worldSeed, 64);
|
|
spawn.z += nextInt(&worldSeed, 64) - nextInt(&worldSeed, 64);
|
|
}
|
|
}
|
|
|
|
return spawn;
|
|
}
|
|
|
|
|
|
Pos estimateSpawn(const int mcversion, LayerStack *g, int *cache, int64_t worldSeed)
|
|
{
|
|
const int *isSpawnBiome = getValidSpawnBiomes();
|
|
Pos spawn;
|
|
int found;
|
|
|
|
setSeed(&worldSeed);
|
|
spawn = findBiomePosition(mcversion, *g, cache, 0, 0, 256, isSpawnBiome,
|
|
&worldSeed, &found);
|
|
|
|
if (!found)
|
|
{
|
|
spawn.x = spawn.z = 8;
|
|
}
|
|
|
|
return spawn;
|
|
}
|
|
|
|
|
|
|
|
//==============================================================================
|
|
// Validating Structure Positions
|
|
//==============================================================================
|
|
|
|
|
|
int isViableFeaturePos(const int structureType, const LayerStack g, int *cache,
|
|
const int blockX, const int blockZ)
|
|
{
|
|
int *map = cache ? cache : allocCache(&g.layers[L_VORONOI_ZOOM_1], 1, 1);
|
|
genArea(&g.layers[L_VORONOI_ZOOM_1], map, blockX, blockZ, 1, 1);
|
|
int biomeID = map[0];
|
|
if (!cache) free(map);
|
|
|
|
switch(structureType)
|
|
{
|
|
case Desert_Pyramid:
|
|
return biomeID == desert || biomeID == desert_hills;
|
|
case Igloo:
|
|
return biomeID == snowy_tundra || biomeID == snowy_taiga;
|
|
case Jungle_Pyramid:
|
|
return biomeID == jungle || biomeID == jungle_hills;
|
|
case Swamp_Hut:
|
|
return biomeID == swamp;
|
|
case Ocean_Ruin:
|
|
case Shipwreck:
|
|
return isOceanic(biomeID);
|
|
default:
|
|
fprintf(stderr, "Structure type is not valid for the scattered feature biome check.\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
int isViableVillagePos(const LayerStack g, int *cache,
|
|
const int blockX, const int blockZ)
|
|
{
|
|
static int isVillageBiome[0x100];
|
|
|
|
if (!isVillageBiome[villageBiomeList[0]])
|
|
{
|
|
unsigned int i;
|
|
for (i = 0; i < sizeof(villageBiomeList) / sizeof(int); i++)
|
|
{
|
|
isVillageBiome[ villageBiomeList[i] ] = 1;
|
|
}
|
|
}
|
|
|
|
return areBiomesViable(g, cache, blockX, blockZ, 0, isVillageBiome);
|
|
}
|
|
|
|
int isViableOceanMonumentPos(const LayerStack g, int *cache,
|
|
const int blockX, const int blockZ)
|
|
{
|
|
static int isWaterBiome[0x100];
|
|
static int isDeepOcean[0x100];
|
|
|
|
if (!isWaterBiome[oceanMonumentBiomeList1[1]])
|
|
{
|
|
unsigned int i;
|
|
for (i = 0; i < sizeof(oceanMonumentBiomeList1) / sizeof(int); i++)
|
|
{
|
|
isWaterBiome[ oceanMonumentBiomeList1[i] ] = 1;
|
|
}
|
|
|
|
for (i = 0; i < sizeof(oceanMonumentBiomeList2) / sizeof(int); i++)
|
|
{
|
|
isDeepOcean[ oceanMonumentBiomeList2[i] ] = 1;
|
|
}
|
|
}
|
|
|
|
return areBiomesViable(g, cache, blockX, blockZ, 16, isDeepOcean) &&
|
|
areBiomesViable(g, cache, blockX, blockZ, 29, isWaterBiome);
|
|
}
|
|
|
|
int isViableMansionPos(const LayerStack g, int *cache,
|
|
const int blockX, const int blockZ)
|
|
{
|
|
static int isMansionBiome[0x100];
|
|
|
|
if (!isMansionBiome[mansionBiomeList[0]])
|
|
{
|
|
unsigned int i;
|
|
for (i = 0; i < sizeof(mansionBiomeList) / sizeof(int); i++)
|
|
{
|
|
isMansionBiome[ mansionBiomeList[i] ] = 1;
|
|
}
|
|
}
|
|
|
|
return areBiomesViable(g, cache, blockX, blockZ, 32, isMansionBiome);
|
|
}
|
|
|
|
|
|
|
|
|
|
//==============================================================================
|
|
// Finding Properties of Structures
|
|
//==============================================================================
|
|
|
|
|
|
int isZombieVillage(const int mcversion, const int64_t worldSeed,
|
|
const int regionX, const int regionZ)
|
|
{
|
|
Pos pos;
|
|
int64_t seed = worldSeed;
|
|
|
|
if (mcversion < MC_1_10)
|
|
{
|
|
printf("Warning: Zombie villages were only introduced in MC 1.10.\n");
|
|
}
|
|
|
|
// get the chunk position of the village
|
|
seed = regionX*341873128712 + regionZ*132897987541 + seed + VILLAGE_CONFIG.seed;
|
|
seed = (seed ^ 0x5deece66dLL);// & ((1LL << 48) - 1);
|
|
|
|
seed = (seed * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
pos.x = (seed >> 17) % VILLAGE_CONFIG.chunkRange;
|
|
|
|
seed = (seed * 0x5deece66dLL + 0xbLL) & 0xffffffffffff;
|
|
pos.z = (seed >> 17) % VILLAGE_CONFIG.chunkRange;
|
|
|
|
pos.x += regionX * VILLAGE_CONFIG.regionSize;
|
|
pos.z += regionZ * VILLAGE_CONFIG.regionSize;
|
|
|
|
// jump to the random number check that determines whether this is village
|
|
// is zombie infested
|
|
int64_t rnd = chunkGenerateRnd(worldSeed, pos.x , pos.z);
|
|
skipNextN(&rnd, mcversion == MC_1_13 ? 10 : 11);
|
|
|
|
return nextInt(&rnd, 50) == 0;
|
|
}
|
|
|
|
|
|
int isBabyZombieVillage(const int mcversion, const int64_t worldSeed,
|
|
const int regionX, const int regionZ)
|
|
{
|
|
if (!isZombieVillage(mcversion, worldSeed, regionX, regionZ))
|
|
return 0;
|
|
|
|
// Whether the zombie is a child or not is dependent on the world random
|
|
// object which is not reset for villages. The last reset is instead
|
|
// performed during the positioning of Mansions.
|
|
int64_t rnd = worldSeed;
|
|
rnd = regionX*341873128712 + regionZ*132897987541 + rnd + MANSION_CONFIG.seed;
|
|
setSeed(&rnd);
|
|
skipNextN(&rnd, 5);
|
|
|
|
int isChild = nextFloat(&rnd) < 0.05;
|
|
//int mountNearbyChicken = nextFloat(&rnd) < 0.05;
|
|
//int spawnNewChicken = nextFloat(&rnd) < 0.05;
|
|
|
|
return isChild;
|
|
}
|
|
|
|
|
|
int64_t getHouseList(const int64_t worldSeed, const int chunkX, const int chunkZ,
|
|
int *out)
|
|
{
|
|
int64_t rnd = chunkGenerateRnd(worldSeed, chunkX, chunkZ);
|
|
skipNextN(&rnd, 1);
|
|
|
|
out[HouseSmall] = nextInt(&rnd, 4 - 2 + 1) + 2;
|
|
out[Church] = nextInt(&rnd, 1 - 0 + 1) + 0;
|
|
out[Library] = nextInt(&rnd, 2 - 0 + 1) + 0;
|
|
out[WoodHut] = nextInt(&rnd, 5 - 2 + 1) + 2;
|
|
out[Butcher] = nextInt(&rnd, 2 - 0 + 1) + 0;
|
|
out[FarmLarge] = nextInt(&rnd, 4 - 1 + 1) + 1;
|
|
out[FarmSmall] = nextInt(&rnd, 4 - 2 + 1) + 2;
|
|
out[Blacksmith] = nextInt(&rnd, 1 - 0 + 1) + 0;
|
|
out[HouseLarge] = nextInt(&rnd, 3 - 0 + 1) + 0;
|
|
|
|
return rnd;
|
|
}
|
|
|
|
//==============================================================================
|
|
// Seed Filters
|
|
//==============================================================================
|
|
|
|
|
|
int64_t filterAllTempCats(
|
|
LayerStack * g,
|
|
int * cache,
|
|
const int64_t * seedsIn,
|
|
int64_t * seedsOut,
|
|
const int64_t seedCnt,
|
|
const int centX,
|
|
const int centZ)
|
|
{
|
|
/* We require all temperature categories, including the special variations
|
|
* in order to get all main biomes. This gives 8 required values:
|
|
* Oceanic, Warm, Lush, Cold, Freezing,
|
|
* Special Warm, Special Lush, Special Cold
|
|
* These categories generate at Layer 13: Edge, Special.
|
|
*
|
|
* Note: The scale at this layer is 1:1024 and each element can "leak" its
|
|
* biome values up to 1024 blocks outwards into the negative coordinates
|
|
* (due to the Zoom layers).
|
|
*
|
|
* The plan is to check if the 3x3 area contains all 8 temperature types.
|
|
* For this, we can check even earlier at Layer 10: Add Island, that each of
|
|
* the Warm, Cold and Freezing categories are present.
|
|
*/
|
|
|
|
/* Edit:
|
|
* All the biomes that are generated by a simple Cold climate can actually
|
|
* be generated later on. So I have commented out the Cold requirements.
|
|
*/
|
|
|
|
const int pX = centX-1, pZ = centZ-1;
|
|
const int sX = 3, sZ = 3;
|
|
int *map;
|
|
|
|
Layer *lFilterSnow = &g->layers[L_ADD_SNOW_1024];
|
|
Layer *lFilterSpecial = &g->layers[L_SPECIAL_1024];
|
|
|
|
map = cache ? cache : allocCache(lFilterSpecial, sX, sZ);
|
|
|
|
// Construct a dummy Edge,Special layer.
|
|
Layer layerSpecial;
|
|
setupLayer(1024, &layerSpecial, NULL, 3, NULL);
|
|
|
|
int64_t sidx, hits, seed;
|
|
int types[9];
|
|
int specialCnt;
|
|
int i, j;
|
|
|
|
hits = 0;
|
|
|
|
for (sidx = 0; sidx < seedCnt; sidx++)
|
|
{
|
|
seed = seedsIn[sidx];
|
|
|
|
/*** Pre-Generation Checks ***/
|
|
|
|
// We require at least 3 special temperature categories which can be
|
|
// tested for without going through the previous layers. (We'll get
|
|
// false positives due to Oceans, but this works fine to rule out some
|
|
// seeds early on.)
|
|
setWorldSeed(&layerSpecial, seed);
|
|
specialCnt = 0;
|
|
for (i = 0; i < sX; i++)
|
|
{
|
|
for (j = 0; j < sZ; j++)
|
|
{
|
|
setChunkSeed(&layerSpecial, (int64_t)(i+pX), (int64_t)(j+pZ));
|
|
if (mcNextInt(&layerSpecial, 13) == 0)
|
|
specialCnt++;
|
|
}
|
|
}
|
|
|
|
if (specialCnt < 3)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/*** Cold/Warm Check ***/
|
|
|
|
// Continue by checking if enough cold and warm categories are present.
|
|
setWorldSeed(lFilterSnow, seed);
|
|
genArea(lFilterSnow, map, pX,pZ, sX,sZ);
|
|
|
|
memset(types, 0, sizeof(types));
|
|
for (i = 0; i < sX*sZ; i++)
|
|
types[map[i]]++;
|
|
|
|
// 1xOcean needs to be present
|
|
// 4xWarm need to turn into Warm, Lush, Special Warm and Special Lush
|
|
// 1xFreezing that needs to stay Freezing
|
|
// 3x(Cold + Freezing) for Cold, Special Cold and Freezing
|
|
if ( types[Ocean] < 1 || types[Warm] < 4 || types[Freezing] < 1 ||
|
|
types[Cold]+types[Freezing] < 2)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/*** Complete Temperature Category Check ***/
|
|
|
|
// Check that all temperature variants are present.
|
|
setWorldSeed(lFilterSpecial, seed);
|
|
genArea(lFilterSpecial, map, pX,pZ, sX,sZ);
|
|
|
|
memset(types, 0, sizeof(types));
|
|
for (i = 0; i < sX*sZ; i++)
|
|
types[ map[i] > 4 ? (map[i]&0xf) + 4 : map[i] ]++;
|
|
|
|
if ( types[Ocean] < 1 || types[Warm] < 1 || types[Lush] < 1 ||
|
|
/*types[Cold] < 1 ||*/ types[Freezing] < 1 ||
|
|
types[Warm+4] < 1 || types[Lush+4] < 1 || types[Cold+4] < 1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
for (i = 0; i < sX*sZ; i++)
|
|
{
|
|
printf("%c%d ", " s"[cache[i] > 4], cache[i]&0xf);
|
|
if (i % sX == sX-1) printf("\n");
|
|
}
|
|
printf("\n");*/
|
|
|
|
// Save the candidate.
|
|
seedsOut[hits] = seed;
|
|
hits++;
|
|
}
|
|
|
|
if (cache == NULL) free(map);
|
|
return hits;
|
|
}
|
|
|
|
|
|
const int majorBiomes[] = {
|
|
ocean, plains, desert, mountains, forest, taiga, swamp,
|
|
snowy_tundra, mushroom_fields, jungle, deep_ocean, birch_forest, dark_forest,
|
|
snowy_taiga, giant_tree_taiga, savanna, wooded_badlands_plateau, badlands_plateau
|
|
};
|
|
|
|
int64_t filterAllMajorBiomes(
|
|
LayerStack * g,
|
|
int * cache,
|
|
const int64_t * seedsIn,
|
|
int64_t * seedsOut,
|
|
const int64_t seedCnt,
|
|
const int pX,
|
|
const int pZ,
|
|
const unsigned int sX,
|
|
const unsigned int sZ)
|
|
{
|
|
Layer *lFilterMushroom = &g->layers[L_ADD_MUSHROOM_256];
|
|
Layer *lFilterBiomes = &g->layers[L_BIOME_256];
|
|
|
|
int *map;
|
|
int64_t sidx, seed, hits;
|
|
unsigned int i, id, hasAll;
|
|
|
|
int types[BIOME_NUM];
|
|
|
|
map = cache ? cache : allocCache(lFilterBiomes, sX, sZ);
|
|
|
|
hits = 0;
|
|
|
|
for (sidx = 0; sidx < seedCnt; sidx++)
|
|
{
|
|
/* We can use the Mushroom layer both to check for mushroom_fields biomes
|
|
* and to make sure all temperature categories are present in the area.
|
|
*/
|
|
seed = seedsIn[sidx];
|
|
setWorldSeed(lFilterMushroom, seed);
|
|
genArea(lFilterMushroom, map, pX,pZ, sX,sZ);
|
|
|
|
memset(types, 0, sizeof(types));
|
|
for (i = 0; i < sX*sZ; i++)
|
|
{
|
|
id = map[i];
|
|
if (id >= BIOME_NUM) id = (id & 0xf) + 4;
|
|
types[id]++;
|
|
}
|
|
|
|
if ( types[Ocean] < 1 || types[Warm] < 1 || types[Lush] < 1 ||
|
|
/* types[Cold] < 1 || */ types[Freezing] < 1 ||
|
|
types[Warm+4] < 1 || types[Lush+4] < 1 || types[Cold+4] < 1 ||
|
|
types[mushroom_fields] < 1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/*** Find all major biomes ***/
|
|
|
|
setWorldSeed(lFilterBiomes, seed);
|
|
genArea(lFilterBiomes, map, pX,pZ, sX,sZ);
|
|
|
|
memset(types, 0, sizeof(types));
|
|
for (i = 0; i < sX*sZ; i++)
|
|
{
|
|
types[map[i]]++;
|
|
}
|
|
|
|
hasAll = 1;
|
|
for (i = 0; i < sizeof(majorBiomes) / sizeof(*majorBiomes); i++)
|
|
{
|
|
// plains, taiga and deep_ocean can be generated in later layers.
|
|
// Also small islands of Forests can be generated in deep_ocean
|
|
// biomes, but we are going to ignore those.
|
|
if (majorBiomes[i] == plains ||
|
|
majorBiomes[i] == taiga ||
|
|
majorBiomes[i] == deep_ocean)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (types[majorBiomes[i]] < 1)
|
|
{
|
|
hasAll = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasAll)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
seedsOut[hits] = seed;
|
|
hits++;
|
|
}
|
|
|
|
if (cache == NULL) free(map);
|
|
return hits;
|
|
}
|
|
|
|
|
|
|
|
BiomeFilter setupBiomeFilter(const int *biomeList, int listLen)
|
|
{
|
|
BiomeFilter bf;
|
|
int i, id;
|
|
|
|
memset(&bf, 0, sizeof(bf));
|
|
|
|
for (i = 0; i < listLen; i++)
|
|
{
|
|
id = biomeList[i] & 0x7f;
|
|
switch (id)
|
|
{
|
|
case mushroom_fields:
|
|
case mushroom_field_shore:
|
|
bf.requireMushroom = 1;
|
|
bf.tempCat |= (1ULL << Oceanic);
|
|
bf.biomesToFind |= (1ULL << id);
|
|
case badlands:
|
|
case wooded_badlands_plateau:
|
|
case badlands_plateau:
|
|
bf.tempCat |= (1ULL << (Warm+Special));
|
|
bf.biomesToFind |= (1ULL << id);
|
|
break;
|
|
case savanna:
|
|
case savanna_plateau:
|
|
bf.tempCat |= (1ULL << Warm);
|
|
bf.biomesToFind |= (1ULL << id);
|
|
break;
|
|
case dark_forest:
|
|
case birch_forest:
|
|
case birch_forest_hills:
|
|
case swamp:
|
|
bf.tempCat |= (1ULL << Lush);
|
|
bf.biomesToFind |= (1ULL << id);
|
|
break;
|
|
case jungle:
|
|
case jungle_hills:
|
|
bf.tempCat |= (1ULL << (Lush+Special));
|
|
bf.biomesToFind |= (1ULL << id);
|
|
break;
|
|
/*case jungleEdge:
|
|
bf.tempCat |= (1ULL << Lush) | (1ULL << Lush+4);
|
|
bf.biomesToFind |= (1ULL << id);
|
|
break;*/
|
|
case giant_tree_taiga:
|
|
case giant_tree_taiga_hills:
|
|
bf.tempCat |= (1ULL << (Cold+Special));
|
|
bf.biomesToFind |= (1ULL << id);
|
|
break;
|
|
case snowy_tundra:
|
|
case snowy_mountains:
|
|
case snowy_taiga:
|
|
case snowy_taiga_hills:
|
|
bf.tempCat |= (1ULL << Freezing);
|
|
bf.biomesToFind |= (1ULL << id);
|
|
break;
|
|
default:
|
|
bf.biomesToFind |= (1ULL << id);
|
|
|
|
if (isOceanic(id))
|
|
{
|
|
if (id != ocean && id != deep_ocean)
|
|
bf.doOceanTypeCheck = 1;
|
|
|
|
if (isShallowOcean(id))
|
|
{
|
|
bf.oceansToFind |= (1ULL << id);
|
|
}
|
|
else
|
|
{
|
|
if (id == deep_warm_ocean)
|
|
bf.oceansToFind |= (1ULL << warm_ocean);
|
|
else if (id == deep_lukewarm_ocean)
|
|
bf.oceansToFind |= (1ULL << lukewarm_ocean);
|
|
else if (id == deep_ocean)
|
|
bf.oceansToFind |= (1ULL << ocean);
|
|
else if (id == deep_cold_ocean)
|
|
bf.oceansToFind |= (1ULL << cold_ocean);
|
|
else if (id == deep_frozen_ocean)
|
|
bf.oceansToFind |= (1ULL << frozen_ocean);
|
|
}
|
|
bf.tempCat |= (1ULL << Oceanic);
|
|
}
|
|
else
|
|
{
|
|
bf.biomesToFind |= (1ULL << id);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < Special; i++)
|
|
{
|
|
if (bf.tempCat & (1ULL << i)) bf.tempNormal++;
|
|
}
|
|
for (i = Special; i < Freezing+Special; i++)
|
|
{
|
|
if (bf.tempCat & (1ULL << i)) bf.tempSpecial++;
|
|
}
|
|
|
|
bf.doTempCheck = (bf.tempSpecial + bf.tempNormal) >= 6;
|
|
bf.doShroomAndTempCheck = bf.requireMushroom && (bf.tempSpecial >= 1 || bf.tempNormal >= 4);
|
|
bf.doMajorBiomeCheck = 1;
|
|
bf.checkBiomePotential = 1;
|
|
bf.doScale4Check = 1;
|
|
|
|
return bf;
|
|
}
|
|
|
|
|
|
|
|
/* Tries to determine if the biomes configured in the filter will generate in
|
|
* this seed within the specified area. The smallest layer scale checked is
|
|
* given by 'minscale'. Lowering this value terminate the search earlier and
|
|
* yield more false positives.
|
|
*/
|
|
int64_t checkForBiomes(
|
|
LayerStack * g,
|
|
int * cache,
|
|
const int64_t seed,
|
|
const int blockX,
|
|
const int blockZ,
|
|
const unsigned int width,
|
|
const unsigned int height,
|
|
const BiomeFilter filter,
|
|
const int minscale)
|
|
{
|
|
Layer *lspecial = &g->layers[L_SPECIAL_1024];
|
|
Layer *lmushroom = &g->layers[L_ADD_MUSHROOM_256];
|
|
Layer *lbiomes = &g->layers[L_BIOME_256];
|
|
Layer *loceantemp = NULL;
|
|
|
|
int *map = cache ? cache : allocCache(&g->layers[L_VORONOI_ZOOM_1], width, height);
|
|
|
|
uint64_t potential, required, modified;
|
|
int64_t ss, cs;
|
|
int id, types[0x100];
|
|
int i, x, z;
|
|
int areaX1024, areaZ1024, areaWidth1024, areaHeight1024;
|
|
int areaX256, areaZ256, areaWidth256, areaHeight256;
|
|
int areaX4, areaZ4, areaWidth4, areaHeight4;
|
|
|
|
// 1:1024 scale
|
|
areaX1024 = blockX >> 10;
|
|
areaZ1024 = blockZ >> 10;
|
|
areaWidth1024 = ((width-1) >> 10) + 2;
|
|
areaHeight1024 = ((height-1) >> 10) + 2;
|
|
|
|
// 1:256 scale
|
|
areaX256 = blockX >> 8;
|
|
areaZ256 = blockZ >> 8;
|
|
areaWidth256 = ((width-1) >> 8) + 2;
|
|
areaHeight256 = ((height-1) >> 8) + 2;
|
|
|
|
|
|
/*** BIOME CHECKS THAT DON'T NEED OTHER LAYERS ***/
|
|
|
|
// Check that there is the necessary minimum of both special and normal
|
|
// temperature categories present.
|
|
if (filter.tempNormal || filter.tempSpecial)
|
|
{
|
|
ss = processWorldSeed(seed, lspecial->baseSeed);
|
|
|
|
types[0] = types[1] = 0;
|
|
for (z = 0; z < areaHeight1024; z++)
|
|
{
|
|
for (x = 0; x < areaWidth1024; x++)
|
|
{
|
|
cs = getChunkSeed(ss, (int64_t)(x + areaX1024), (int64_t)(z + areaZ1024));
|
|
types[(cs >> 24) % 13 == 0]++;
|
|
}
|
|
}
|
|
|
|
if (types[0] < filter.tempNormal || types[1] < filter.tempSpecial)
|
|
{
|
|
goto return_zero;
|
|
}
|
|
}
|
|
|
|
// Check there is a mushroom island, provided there is an ocean.
|
|
if (filter.requireMushroom)
|
|
{
|
|
ss = processWorldSeed(seed, lmushroom->baseSeed);
|
|
|
|
for (z = 0; z < areaHeight256; z++)
|
|
{
|
|
for (x = 0; x < areaWidth256; x++)
|
|
{
|
|
cs = getChunkSeed(ss, (int64_t)(x + areaX256), (int64_t)(z + areaZ256));
|
|
if ((cs >> 24) % 100 == 0)
|
|
{
|
|
goto after_protomushroom;
|
|
}
|
|
}
|
|
}
|
|
|
|
goto return_zero;
|
|
}
|
|
after_protomushroom:
|
|
|
|
if (filter.checkBiomePotential)
|
|
{
|
|
ss = processWorldSeed(seed, lbiomes->baseSeed);
|
|
|
|
potential = 0;
|
|
required = filter.biomesToFind & (
|
|
(1ULL << badlands_plateau) | (1ULL << wooded_badlands_plateau) |
|
|
(1ULL << savanna) | (1ULL << dark_forest) |
|
|
(1ULL << birch_forest) | (1ULL << swamp));
|
|
|
|
for (z = 0; z < areaHeight256; z++)
|
|
{
|
|
for (x = 0; x < areaWidth256; x++)
|
|
{
|
|
cs = getChunkSeed(ss, (int64_t)(x + areaX256), (int64_t)(z + areaZ256));
|
|
cs >>= 24;
|
|
|
|
int cs6 = cs % 6;
|
|
int cs3 = cs6 & 3;
|
|
|
|
if (cs3 == 0) potential |= (1ULL << badlands_plateau);
|
|
else if (cs3 == 1 || cs3 == 2) potential |= (1ULL << wooded_badlands_plateau);
|
|
|
|
if (cs6 == 1) potential |= (1ULL << dark_forest);
|
|
else if (cs6 == 3) potential |= (1ULL << savanna);
|
|
else if (cs6 == 4) potential |= (1ULL << savanna) | (1ULL << birch_forest);
|
|
else if (cs6 == 5) potential |= (1ULL << swamp);
|
|
|
|
if (!((potential & required) ^ required))
|
|
{
|
|
goto after_protobiome;
|
|
}
|
|
}
|
|
}
|
|
|
|
goto return_zero;
|
|
}
|
|
after_protobiome:
|
|
|
|
|
|
/*** BIOME CHECKS ***/
|
|
|
|
if (filter.doTempCheck)
|
|
{
|
|
setWorldSeed(lspecial, seed);
|
|
genArea(lspecial, map, areaX1024, areaZ1024, areaWidth1024, areaHeight1024);
|
|
|
|
potential = 0;
|
|
|
|
for (i = 0; i < areaWidth1024 * areaHeight1024; i++)
|
|
{
|
|
id = map[i];
|
|
if (id >= Special) id = (id & 0xf) + Special;
|
|
potential |= (1ULL << id);
|
|
}
|
|
|
|
if ((potential & filter.tempCat) ^ filter.tempCat)
|
|
{
|
|
goto return_zero;
|
|
}
|
|
}
|
|
|
|
if (minscale > 256) goto return_one;
|
|
|
|
if (filter.doShroomAndTempCheck)
|
|
{
|
|
setWorldSeed(lmushroom, seed);
|
|
genArea(lmushroom, map, areaX256, areaZ256, areaWidth256, areaHeight256);
|
|
|
|
potential = 0;
|
|
|
|
for (i = 0; i < areaWidth256 * areaHeight256; i++)
|
|
{
|
|
id = map[i];
|
|
if (id >= BIOME_NUM) id = (id & 0xf) + Special;
|
|
potential |= (1ULL << id);
|
|
}
|
|
|
|
required = filter.tempCat | (1ULL << mushroom_fields);
|
|
|
|
if ((potential & required) ^ required)
|
|
{
|
|
goto return_zero;
|
|
}
|
|
}
|
|
|
|
if (filter.doOceanTypeCheck)
|
|
{
|
|
loceantemp = &g->layers[L13_OCEAN_TEMP_256];
|
|
setWorldSeed(loceantemp, seed);
|
|
genArea(loceantemp, map, areaX256, areaZ256, areaWidth256, areaHeight256);
|
|
|
|
potential = 0; // ocean potential
|
|
|
|
for (i = 0; i < areaWidth256 * areaHeight256; i++)
|
|
{
|
|
id = map[i];
|
|
if (id == warm_ocean) potential |= (1ULL << warm_ocean) | (1ULL << lukewarm_ocean);
|
|
if (id == lukewarm_ocean) potential |= (1ULL << lukewarm_ocean);
|
|
if (id == ocean) potential |= (1ULL << ocean);
|
|
if (id == cold_ocean) potential |= (1ULL << cold_ocean);
|
|
if (id == frozen_ocean) potential |= (1ULL << frozen_ocean) | (1ULL << cold_ocean);
|
|
}
|
|
|
|
if ((potential & filter.oceansToFind) ^ filter.oceansToFind)
|
|
{
|
|
goto return_zero;
|
|
}
|
|
}
|
|
|
|
if (filter.doMajorBiomeCheck)
|
|
{
|
|
setWorldSeed(lbiomes, seed);
|
|
genArea(lbiomes, map, areaX256, areaZ256, areaWidth256, areaHeight256);
|
|
|
|
// get biomes out of the way that we cannot check for at this layer
|
|
potential = (1ULL << beach) | (1ULL << stone_shore) |
|
|
(1ULL << snowy_beach) | (1ULL << river) | (1ULL << frozen_river);
|
|
|
|
for (i = 0; i < areaWidth256 * areaHeight256; i++)
|
|
{
|
|
id = map[i];
|
|
switch (id)
|
|
{
|
|
case wooded_badlands_plateau:
|
|
case badlands_plateau:
|
|
potential |= (1ULL << id) | (1ULL << badlands) | (1ULL << desert); break;
|
|
case giant_tree_taiga:
|
|
potential |= (1ULL << id) | (1ULL << taiga) | (1ULL << taiga_hills) | (1ULL << giant_tree_taiga_hills); break;
|
|
case desert:
|
|
potential |= (1ULL << id) | (1ULL << wooded_mountains) | (1ULL << desert_hills); break;
|
|
case swamp:
|
|
potential |= (1ULL << id) | (1ULL << jungleEdge) | (1ULL << plains); break;
|
|
case forest:
|
|
potential |= (1ULL << id) | (1ULL << wooded_hills); break;
|
|
case birch_forest:
|
|
potential |= (1ULL << id) | (1ULL << birch_forest_hills); break;
|
|
case dark_forest:
|
|
potential |= (1ULL << id) | (1ULL << plains); break;
|
|
case taiga:
|
|
potential |= (1ULL << id) | (1ULL << taiga_hills); break;
|
|
case snowy_taiga:
|
|
potential |= (1ULL << id) | (1ULL << snowy_taiga_hills); break;
|
|
case plains:
|
|
potential |= (1ULL << id) | (1ULL << wooded_hills) | (1ULL << forest); break;
|
|
case snowy_tundra:
|
|
potential |= (1ULL << id) | (1ULL << snowy_mountains); break;
|
|
case jungle:
|
|
potential |= (1ULL << id) | (1ULL << jungle_hills); break;
|
|
case ocean:
|
|
potential |= (1ULL << id) | (1ULL << deep_ocean);
|
|
// TODO: buffer possible ocean types at this location
|
|
potential |= (1ULL << frozen_ocean) | (1ULL << cold_ocean) | (1ULL << lukewarm_ocean) | (1ULL << warm_ocean); break;
|
|
case mountains:
|
|
potential |= (1ULL << id) | (1ULL << wooded_mountains); break;
|
|
case savanna:
|
|
potential |= (1ULL << id) | (1ULL << savanna_plateau); break;
|
|
case deep_ocean:
|
|
potential |= (1ULL << id) | (1ULL << plains) | (1ULL << forest);
|
|
// TODO: buffer possible ocean types at this location
|
|
potential |= (1ULL << deep_frozen_ocean) | (1ULL << deep_cold_ocean) | (1ULL << deep_lukewarm_ocean); break;
|
|
case mushroom_fields:
|
|
potential |= (1ULL << id) | (1ULL << mushroom_field_shore);
|
|
default:
|
|
potential |= (1ULL << id);
|
|
}
|
|
}
|
|
|
|
if ((potential & filter.biomesToFind) ^ filter.biomesToFind)
|
|
{
|
|
goto return_zero;
|
|
}
|
|
}
|
|
|
|
// TODO: Do a check at the HILLS layer, scale 1:64
|
|
|
|
if (minscale > 4) goto return_one;
|
|
|
|
if (filter.doScale4Check)
|
|
{
|
|
// 1:4 scale
|
|
areaX4 = blockX >> 2;
|
|
areaZ4 = blockZ >> 2;
|
|
areaWidth4 = ((width-1) >> 2) + 2;
|
|
areaHeight4 = ((height-1) >> 2) + 2;
|
|
|
|
applySeed(g, seed);
|
|
if (filter.doOceanTypeCheck)
|
|
genArea(&g->layers[L13_OCEAN_MIX_4], map, areaX4, areaZ4, areaWidth4, areaHeight4);
|
|
else
|
|
genArea(&g->layers[L_RIVER_MIX_4], map, areaX4, areaZ4, areaWidth4, areaHeight4);
|
|
|
|
potential = modified = 0;
|
|
|
|
for (i = 0; i < areaWidth4 * areaHeight4; i++)
|
|
{
|
|
id = map[i];
|
|
if (id >= 128) modified |= (1ULL << (id & 0x7f));
|
|
else potential |= (1ULL << id);
|
|
}
|
|
|
|
required = filter.biomesToFind;
|
|
if ((potential & required) ^ required)
|
|
{
|
|
goto return_zero;
|
|
}
|
|
if ((modified & filter.modifiedToFind) ^ filter.modifiedToFind)
|
|
{
|
|
goto return_zero;
|
|
}
|
|
}
|
|
|
|
|
|
int ret;
|
|
|
|
// clean up and return
|
|
return_one:
|
|
ret = 1;
|
|
|
|
if (0)
|
|
{
|
|
return_zero:
|
|
ret = 0;
|
|
}
|
|
|
|
if (cache == NULL) free(map);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|