/******************************************************************** Minecraft: Pocket Edition - Decompilation Project Copyright (C) 2023 iProgramInCpp The following code is licensed under the BSD 1 clause license. SPDX-License-Identifier: BSD-1-Clause ********************************************************************/ #include "RandomLevelSource.hpp" #include "world/level/Level.hpp" #include "world/tile/SandTile.hpp" //#define TEST_CAVES const float RandomLevelSource::SNOW_CUTOFF = 0.5f; const float RandomLevelSource::SNOW_SCALE = 0.3f; float g_timeSpentInPostProcessing = 0; RandomLevelSource::RandomLevelSource(Level* level, TLong seed, int x) : m_random(seed), m_perlinNoise1(&m_random, 16), m_perlinNoise2(&m_random, 16), m_perlinNoise3(&m_random, 8), m_perlinNoise4(&m_random, 4), m_perlinNoise5(&m_random, 4), m_perlinNoise6(&m_random, 10), m_perlinNoise7(&m_random, 16), m_perlinNoise8(&m_random, 8), m_pLevel(level) { field_4 = false; field_19F0 = 1.0f; field_7280 = nullptr; field_7E84 = nullptr; field_7E88 = nullptr; field_7E8C = nullptr; field_7E90 = nullptr; field_7E94 = nullptr; LOG_I("Generating world with seed: %d", seed); for (int a = 0; a < 32; a++) { for (int b = 0; b < 32; b++) { field_9D8[a * 32 + b] = 0; } } field_7280 = new float[1024]; Random random = m_random; LOG_I("random.get : %d", random.genrand_int32() >> 1); } // @BUG: Potential collisions. inline int GetChunkHash(int x, int z) { int v6 = (z & 0x7FFF) | ((x & 0x7FFF) << 16) | (x & 0x80000000), v7; if (z >= 0) v7 = 0; else v7 = 0x8000; return v6 | v7; } LevelChunk* RandomLevelSource::getChunk(int x, int z) { int hashCode = GetChunkHash(x, z); std::map::iterator iter = m_chunks.find(hashCode); if (iter != m_chunks.end()) return iter->second; // have to generate the chunk m_random.init_genrand(341872712 * x + 132899541 * z); TileID* pLevelData = new TileID[32768]; LevelChunk* pChunk = new LevelChunk(m_pLevel, pLevelData, x, z); m_chunks.insert(std::pair(hashCode, pChunk)); Biome** pBiomeBlock = m_pLevel->getBiomeSource()->getBiomeBlock(16 * x, 16 * z, 16, 16); prepareHeights(x, z, pLevelData, nullptr, m_pLevel->getBiomeSource()->field_4); buildSurfaces(x, z, pLevelData, pBiomeBlock); pChunk->recalcHeightmap(); // @NOTE: Java Edition Beta 1.6 uses the m_largeCaveFeature. #ifdef TEST_CAVES m_largeCaveFeature.apply(this, m_pLevel, x, z, pLevelData, 0); #endif return pChunk; } LevelChunk* RandomLevelSource::getChunkDontCreate(int x, int z) { int hashCode = GetChunkHash(x, z); std::map::iterator iter = m_chunks.find(hashCode); if (iter != m_chunks.end()) return iter->second; // have to create the chunk. Create an empty one TileID* pLevelData = new TileID[32768]; memset (pLevelData, 0, sizeof *pLevelData * 32768); LevelChunk* pChunk = new LevelChunk(m_pLevel, pLevelData, x, z); m_chunks.insert(std::pair(hashCode, pChunk)); return pChunk; } float* RandomLevelSource::getHeights(float* fptr, int a3, int a4, int a5, int a6, int a7, int a8) { if (fptr == nullptr) { fptr = new float[a6 * a7 * a8]; } float* bsf4 = m_pLevel->getBiomeSource()->field_4; float* bsf8 = m_pLevel->getBiomeSource()->field_8; constexpr float C_MAGIC_1 = 684.412f; field_7E90 = m_perlinNoise6.getRegion(field_7E90, a3, a5, a6, a8, 1.121f, 1.121f, 0.5f); field_7E94 = m_perlinNoise7.getRegion(field_7E94, a3, a5, a6, a8, 200.0f, 200.0f, 0.5f); field_7E84 = m_perlinNoise3.getRegion(field_7E84, float(a3), float(a4), float(a5), a6, a7, a8, 8.5552f, 4.2776f, 8.5552f); field_7E88 = m_perlinNoise1.getRegion(field_7E88, float(a3), float(a4), float(a5), a6, a7, a8, C_MAGIC_1, C_MAGIC_1, C_MAGIC_1); field_7E8C = m_perlinNoise2.getRegion(field_7E8C, float(a3), float(a4), float(a5), a6, a7, a8, C_MAGIC_1, C_MAGIC_1, C_MAGIC_1); int k1 = 0; int l1 = 0; int i2 = 16 / a6; for (int j2 = 0; j2 < a6; j2++) { int k2 = j2 * i2 + i2 / 2; for (int l2 = 0; l2 < a8; l2++) { int i3 = l2 * i2 + i2 / 2; float d2 = bsf4[k2 * 16 + i3]; float d3 = bsf8[k2 * 16 + i3] * d2; float d4 = 1.0f - d3; d4 *= d4; d4 *= d4; d4 = 1.0f - d4; float d5 = (field_7E90[l1] + 256.0f) / 512.0f; d5 *= d4; if (d5 > 1.0f) d5 = 1.0f; float d6 = field_7E94[l1] / 8000.0f; if (d6 < 0.0f) { d6 = -d6 * 0.3f; } d6 = d6 * 3.0f - 2.0f; if (d6 < 0.0f) { d6 /= 2.0f; if (d6 < -1.0f) d6 = -1.0f; d6 /= 1.4f; d6 /= 2.0f; d5 = 0.0f; } else { if (d6 > 1.0f) d6 = 1.0f; d6 /= 8.0f; } if (d5 < 0.0f) d5 = 0.0f; d5 += 0.5f; d6 = (d6 * (float)a7) / 16.0f; float d7 = (float)a7 / 2.0f + d6 * 4.0f; l1++; for (int j3 = 0; j3 < a7; j3++) { float d8 = 0.0f; float d9 = (((float)j3 - d7) * 12.0f) / d5; if (d9 < 0.0f) { d9 *= 4.0f; } float d10 = field_7E88[k1] / 512.0f; float d11 = field_7E8C[k1] / 512.0f; float d12 = (field_7E84[k1] / 10.0f + 1.0f) / 2.0f; if (d12 < 0.0f) d8 = d10; else if (d12 > 1.0f) d8 = d11; else d8 = d10 + (d11 - d10) * d12; d8 -= d9; if (j3 > a7 - 4) { float d13 = (float)(j3 - (a7 - 4)) / 3.0f; d8 = d8 * (1.0f - d13) + -10.0f * d13; } fptr[k1] = d8; k1++; } } } return fptr; } void RandomLevelSource::prepareHeights(int x, int z, TileID* tiles, void* huh, float* fptr) { field_7280 = getHeights(field_7280, x * 4, 0, z * 4, 5, 17, 5); for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { for (int k = 0; k < 16; k++) { float v24 = field_7280[85 * i + 17 * j + k]; float v23 = field_7280[85 * i + 17 + 17 * j + k]; float v22 = field_7280[85 * i + 85 + 17 * j + k]; float v21 = field_7280[85 * i + 102 + 17 * j + k]; float v20 = (field_7280[85 * i + 1 + 17 * j + k] - v24) * 0.125f; float v19 = (field_7280[85 * i + 18 + 17 * j + k] - v23) * 0.125f; float v18 = (field_7280[85 * i + 86 + 17 * j + k] - v22) * 0.125f; float v17 = (field_7280[85 * i + 103 + 17 * j + k] - v21) * 0.125f; for (int l = 0; l < 8; l++) { float v15 = v24; float v14 = v23; for (int m = 0; m < 4; m++) { int v12 = ((m + 4 * i) << 11) | (j << 9) | (l + 8 * k); float v11 = v15; for (int n = 0; n < 4; n++) { TileID tile = TILE_AIR; if (8 * k + l < 64) { if (fptr[64 * i + 16 * m + 4 * j + n] < 0.5f && 8 * k + l == 63) tile = Tile::ice->m_ID; else tile = Tile::calmWater->m_ID; } if (v11 > 0.0f) tile = Tile::rock->m_ID; tiles[v12] = tile; v12 += 128; v11 = v11 + (v14 - v15) * 0.25f; } v15 = v15 + (v22 - v24) * 0.25f; v14 = v14 + (v21 - v23) * 0.25f; } v24 = v24 + v20; v23 = v23 + v19; v22 = v22 + v18; v21 = v21 + v17; } } } } } void RandomLevelSource::buildSurfaces(int x, int z, TileID* tiles, Biome** biomes) { //return; m_perlinNoise4.getRegion(field_7284, float(x) * 16.0f, float(z) * 16.0f, 0.0f, 16, 16, 1, 1.0f / 32.0f, 1.0f / 32.0f, 1.0f); m_perlinNoise4.getRegion(field_7684, float(x) * 16.0f, 109.01f, float(z) * 16.0f, 16, 1, 16, 1.0f / 32.0f, 1.0f, 1.0f / 32.0f); m_perlinNoise5.getRegion(field_7A84, float(x) * 16.0f, float(z) * 16.0f, 0.0f, 16, 16, 1, 1.0f / 16.0f, 1.0f / 16.0f, 1.0f / 16.0f); // @NOTE: Again, extracted from Java Beta 1.6. Probably accurate constexpr int byte0 = 64; //constexpr float d = 0.03125f; for (int k = 0; k < 16; k++) { for (int l = 0; l < 16; l++) { Biome* pBiome = biomes[k + l * 16]; bool flag = field_7284[k + l * 16] + m_random.nextFloat() * 0.2f > 0.0f; bool flag1 = field_7684[k + l * 16] + m_random.nextFloat() * 0.2f > 3.0f; int i1 = (int)(field_7A84[k + l * 16] / 3.0f + 3.0f + m_random.nextFloat() * 0.25f); int j1 = -1; TileID byte1 = pBiome->field_20; TileID byte2 = pBiome->field_21; for (int k1 = 127; k1 >= 0; k1--) { int l1 = (l * 16 + k) * 128 + k1; if (k1 <= int(0 + m_random.nextInt(5))) { tiles[l1] = Tile::unbreakable->m_ID; continue; } TileID byte3 = tiles[l1]; if (byte3 == 0) { j1 = -1; continue; } if (byte3 != Tile::rock->m_ID) continue; if (j1 == -1) { if (i1 <= 0) { byte1 = 0; byte2 = Tile::rock->m_ID; } else if (k1 >= byte0 - 4 && k1 <= byte0 + 1) { byte1 = pBiome->field_20; byte2 = pBiome->field_21; if (flag1) { byte1 = 0; byte2 = Tile::gravel->m_ID; } if (flag) { byte1 = Tile::sand->m_ID; byte2 = Tile::sand->m_ID; } } if (k1 < byte0 && byte1 == 0) byte1 = Tile::calmWater->m_ID; j1 = i1; if (k1 >= byte0 - 1) tiles[l1] = byte1; else tiles[l1] = byte2; continue; } if (j1 <= 0) continue; j1--; tiles[l1] = byte2; if (j1 == 0 && byte2 == Tile::sand->m_ID) { j1 = m_random.nextInt(4); byte2 = Tile::sandStone->m_ID; } } } } } void RandomLevelSource::postProcess(ChunkSource* src, int x, int z) { m_pLevel->m_bPostProcessing = true; SandTile::instaFall = true; int x16 = x * 16, z16 = z * 16; Biome* pBiome = m_pLevel->getBiomeSource()->getBiome(x16 + 16, z16 + 16); TLong seed = m_pLevel->getSeed(); int xo, yo, zo; m_random.setSeed(seed); TLong x1 = 1 + 2 * (m_random.nextInt() / 2); TLong x2 = 1 + 2 * (m_random.nextInt() / 2); m_random.setSeed((TLong(x) * x1 + TLong(z) * x2) ^ seed); // @NOTE: I can't put the random calls _in_ the argument list - args are evaluated right to left I believe for (int i = 0; i < 10; i++) { xo = m_random.nextInt(16); yo = m_random.nextInt(128); zo = m_random.nextInt(16); ClayFeature(Tile::clay->m_ID, 32).place(m_pLevel, &m_random, x16 + xo, yo, z16 + zo); } // Start of ore generation for (int i = 0; i < 20; i++) { xo = m_random.nextInt(16); yo = m_random.nextInt(128); zo = m_random.nextInt(16); OreFeature(Tile::dirt->m_ID, 32).place(m_pLevel, &m_random, x16 + xo, yo, z16 + zo); } for (int i = 0; i < 10; i++) { xo = m_random.nextInt(16); yo = m_random.nextInt(128); zo = m_random.nextInt(16); OreFeature(Tile::gravel->m_ID, 32).place(m_pLevel, &m_random, x16 + xo, yo, z16 + zo); } for (int i = 0; i < 20; i++) { xo = m_random.nextInt(16); yo = m_random.nextInt(128); zo = m_random.nextInt(16); OreFeature(Tile::coalOre->m_ID, 16).place(m_pLevel, &m_random, x16 + xo, yo, z16 + zo); } for (int i = 0; i < 20; i++) { xo = m_random.nextInt(16); yo = m_random.nextInt(64); zo = m_random.nextInt(16); OreFeature(Tile::ironOre->m_ID, 8).place(m_pLevel, &m_random, x16 + xo, yo, z16 + zo); } for (int i = 0; i < 2; i++) { xo = m_random.nextInt(16); yo = m_random.nextInt(32); zo = m_random.nextInt(16); OreFeature(Tile::goldOre->m_ID, 8).place(m_pLevel, &m_random, x16 + xo, yo, z16 + zo); } for (int i = 0; i < 8; i++) { xo = m_random.nextInt(16); yo = m_random.nextInt(16); zo = m_random.nextInt(16); OreFeature(Tile::redStoneOre->m_ID, 7).place(m_pLevel, &m_random, x16 + xo, yo, z16 + zo); } for (int i = 0; i < 1; i++) { xo = m_random.nextInt(16); yo = m_random.nextInt(16); zo = m_random.nextInt(16); OreFeature(Tile::emeraldOre->m_ID, 7).place(m_pLevel, &m_random, x16 + xo, yo, z16 + zo); } for (int i = 0; i < 1; i++) { xo = m_random.nextInt(16); yo = m_random.nextInt(16) + m_random.nextInt(16); zo = m_random.nextInt(16); OreFeature(Tile::lapisOre->m_ID, 6).place(m_pLevel, &m_random, x16 + xo, yo, z16 + zo); } // End of ore generation // Start of tree generation float t1 = m_perlinNoise8.getValue(float(x16) / 2.0f, float(z16) / 2.0f); float t2 = m_random.nextFloat(); int t3 = int((4.0f + t2 * 4.0f + t1 * 0.125f) / 3.0f); int treeCount = 0; if (m_random.nextInt(10) == 0) treeCount++; if (pBiome == Biome::forest) treeCount += t3 + 2; if (pBiome == Biome::rainForest) treeCount += t3 + 2; if (pBiome == Biome::seasonalForest) treeCount += t3 + 1; if (pBiome == Biome::taiga) treeCount += t3 + 1; if (pBiome == Biome::desert) treeCount -= 20; if (pBiome == Biome::tundra) treeCount -= 20; if (pBiome == Biome::plains) treeCount -= 20; for (int i = 0; i < treeCount; i++) { int rngX = m_random.nextInt(16) + x16 + 8; int rngZ = m_random.nextInt(16) + z16 + 8; int height = m_pLevel->getHeightmap(rngX, rngZ); Feature* pTreeFeature = pBiome->getTreeFeature(&m_random); if (pTreeFeature) { pTreeFeature->init(1.0f, 1.0f, 1.0f); pTreeFeature->place(m_pLevel, &m_random, rngX, height, rngZ); delete pTreeFeature; } } for (int i = 0; i < 2; i++) { int xo = m_random.nextInt(16); int yo = m_random.nextInt(128); int zo = m_random.nextInt(16); FlowerFeature(Tile::flower->m_ID).place(m_pLevel, &m_random, x16 + 8 + xo, yo, z16 + 8 + zo); } if (m_random.nextInt(2) == 0) { int xo = m_random.nextInt(16); int yo = m_random.nextInt(128); int zo = m_random.nextInt(16); FlowerFeature(Tile::rose->m_ID).place(m_pLevel, &m_random, x16 + 8 + xo, yo, z16 + 8 + zo); } if (m_random.nextInt(4) == 0) { int xo = m_random.nextInt(16); int yo = m_random.nextInt(128); int zo = m_random.nextInt(16); FlowerFeature(Tile::mushroom1->m_ID).place(m_pLevel, &m_random, x16 + 8 + xo, yo, z16 + 8 + zo); } if (m_random.nextInt(8) == 0) { int xo = m_random.nextInt(16); int yo = m_random.nextInt(128); int zo = m_random.nextInt(16); FlowerFeature(Tile::mushroom2->m_ID).place(m_pLevel, &m_random, x16 + 8 + xo, yo, z16 + 8 + zo); } for (int i = 0; i < 10; i++) { int xo = m_random.nextInt(16); int yo = m_random.nextInt(128); int zo = m_random.nextInt(16); ReedsFeature().place(m_pLevel, &m_random, x16 + 8 + xo, yo, z16 + 8 + zo); } for (int i = 0; i < 50; i++) { int xo = m_random.nextInt(16); int yo = m_random.nextInt(m_random.nextInt(120) + 8); int zo = m_random.nextInt(16); SpringFeature(Tile::water->m_ID).place(m_pLevel, &m_random, x16 + 8 + xo, yo, z16 + 8 + zo); } for (int i = 0; i < 20; i++) { int xo = m_random.nextInt(16); int yo = m_random.nextInt(m_random.nextInt(m_random.nextInt(112) + 8) + 8); int zo = m_random.nextInt(16); SpringFeature(Tile::lava->m_ID).place(m_pLevel, &m_random, x16 + 8 + xo, yo, z16 + 8 + zo); } float* tempBlock = m_pLevel->getBiomeSource()->getTemperatureBlock(x16 + 8, z16 + 8, 16, 16); for (int j19 = x16 + 8; j19 < x16 + 8 + 16; j19++) { for (int j22 = z16 + 8; j22 < z16 + 8 + 16; j22++) { int i24 = j19 - (x16 + 8); int j25 = j22 - (z16 + 8); int tsb = m_pLevel->getTopSolidBlock(j19, j22); if (SNOW_CUTOFF > (tempBlock[i24 * 16 + j25] - SNOW_SCALE * (float(tsb - 64) / 64.0f))) { if (tsb >= 0 && tsb < C_MAX_Y && m_pLevel->isEmptyTile(j19, tsb, j22)) { if (m_pLevel->getMaterial(j19, tsb - 1, j22)->blocksMotion() && m_pLevel->getMaterial(j19, tsb - 1, j22) != Material::ice) { m_pLevel->setTile(j19, tsb, j22, Tile::topSnow->m_ID); } } } } } SandTile::instaFall = false; m_pLevel->m_bPostProcessing = false; } int RandomLevelSource::tick() { return 0; } bool RandomLevelSource::shouldSave() { return true; } bool RandomLevelSource::hasChunk(int x, int z) { return true; } LevelChunk* RandomLevelSource::create(int x, int z) { return getChunk(x, z); } std::string RandomLevelSource::gatherStats() { return "RandomLevelSource"; }