level = $level; $this->random = $random; $this->random->setSeed($this->level->getSeed()); if($level instanceof Level) { // First init in main thread $this->worldpath = getcwd() . "/worlds/" . $level->getName() . "/"; // Initing folder data @mkdir($this->worldpath . "gendata"); // Checking heightmap if(!file_exists($this->worldpath . "gendata/heightmap.png")) { copy(getcwd() . "/plugins/RealWorld/heightmap.png", $this->worldpath . "gendata/heightmap.png"); } $this->heightmap = \imagecreatefrompng($this->worldpath . "gendata/heightmap.png"); // Checking gen infos (startpoint, ...) if(!file_exists($this->worldpath . "gendata/geninfos.json")) { $data = []; $data["#"] = "DO NOT MODIFY THIS FILE. IT HAS BEEN GENERATED BY RealWorld AND UNEXEPTED ISSUES MAY OCCUR IF YOU MODIFY ANY OF THESES VALUES."; // Do not modify comment in file for noobs. $data["startPoint"] = [ $random->nextRange(round(-imagesy($this->heightmap)) / 2, round(imagesy($this->heightmap) / 2)), $random->nextRange(round(-imagesx($this->heightmap)) / 2, round(imagesx($this->heightmap) / 2)), ]; file_put_contents($this->worldpath . "gendata/geninfos.json", json_encode($data)); } $options = json_decode(file_get_contents($this->worldpath . "gendata/geninfos.json")); $this->startPoint = $options->startPoint; } else { var_dump($this); } // Making selector $this->selector = new BiomeSelector($this->random, function($temperature, $rainfall){ // Checking the nearest candidate biome that have the closest $temperature and $rainfall. $bestBiome = [PHP_INT_MAX, Biome::getBiome(Biome::OCEAN)]; // Default biome foreach($this->candidatesBiomes as $b){ $diffRainFall = abs($b->getRainfall() - $rainfall); $diffTemperature = abs($b->getTemperature() - $temperature); $diff = $diffRainFall + $diffTemperature; // Total diff. if($diff < $bestBiome[0]) $bestBiome = [$diff, $b]; } }, Biome::getBiome(Biome::OCEAN)); $this->selector->addBiome(Biome::getBiome(Biome::OCEAN)); $this->selector->addBiome(Biome::getBiome(Biome::PLAINS)); $this->selector->addBiome(Biome::getBiome(Biome::DESERT)); $this->selector->addBiome(Biome::getBiome(Biome::MOUNTAINS)); $this->selector->addBiome(Biome::getBiome(Biome::FOREST)); $this->selector->addBiome(Biome::getBiome(Biome::TAIGA)); $this->selector->addBiome(Biome::getBiome(Biome::SWAMP)); $this->selector->addBiome(Biome::getBiome(Biome::RIVER)); $this->selector->addBiome(Biome::getBiome(Biome::ICE_PLAINS)); $this->selector->addBiome(Biome::getBiome(Biome::SMALL_MOUNTAINS)); $this->selector->addBiome(Biome::getBiome(Biome::BIRCH_FOREST)); $this->selector->recalculate(); // Populators $cover = new GroundCover(); $this->generationPopulators[] = $cover; $cave = new CavePopulator (); $cave->setBaseAmount(0); $cave->setRandomAmount(2); $this->generationPopulators[] = $cave; $ravine = new RavinePopulator (); $ravine->setBaseAmount(0); $ravine->setRandomAmount(51); $this->generationPopulators[] = $ravine; $ores = new Ore(); $ores->setOreTypes([ new OreType(BlockFactory::get(Block::COAL_ORE), 20, 16, 0, 128), new OreType(BlockFactory::get(Block::IRON_ORE), 20, 8, 0, 64), new OreType(BlockFactory::get(Block::REDSTONE_ORE), 8, 7, 0, 16), new OreType(BlockFactory::get(Block::LAPIS_ORE), 1, 6, 0, 32), new OreType(BlockFactory::get(Block::GOLD_ORE), 2, 8, 0, 32), new OreType(BlockFactory::get(Block::DIAMOND_ORE), 1, 7, 0, 16), new OreType(BlockFactory::get(Block::DIRT), 20, 32, 0, 128), new OreType(BlockFactory::get(Block::GRAVEL), 10, 16, 0, 128) ]); $this->populators[] = $ores; } /** * Generates a chunk * * @param int $chunkX * @param int $chunkZ * @return void */ public function generateChunk(int $chunkX, int $chunkZ){ $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed()); $chunk = $this->level->getChunk($chunkX, $chunkZ); for($x = 0; $x < 16; $x++) { for($z = 0; $z < 16; $z++) { // Getting biome & height $currentX = $chunkX * 16 + $x; $currentZ = $chunkZ * 16 + $x; $height = $this->getHeightFromImg($currentX, $currentZ); $biome = $this->getBiomeFromPos($currentX, $currentZ); $chunk->setBiomeId($x, $z, $biome->getId()); // Building terrain for($y = 0; $y < 128; ++$y) { if($y === 0) { $chunk->setBlockId($x, $y, $z, Block::BEDROCK); continue; } if($y <= $height) { $chunk->setBlockId($x, $y, $z, Block::STONE); } elseif($y <= $this->waterHeight) { $chunk->setBlockId($x, $y, $z, Block::STILL_WATER); } } } } } /** * Populates chunks. * * @param int $chunkX * @param int $chunkZ * @return void */ public function populateChunk(int $chunkX, int $chunkZ){ $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed()); foreach($this->populators as $populator){ $populator->populate($this->level, $chunkX, $chunkZ, $this->random); } $chunk = $this->level->getChunk($chunkX, $chunkZ); $biome = Biome::getBiome($chunk->getBiomeId(7, 7)); $biome->populateChunk($this->level, $chunkX, $chunkZ, $this->random); } /** * Gets a biome from a pos. * * @param int $x * @param int $z * @return Biome */ public function getBiomeFromPos(int $x, int $z): Biome{ $this->candidatesBiomes = []; if(count(Main::$BIOMES_BY_RANGE) < 0) Main::generateRanges(); $height = $this->getHeightFromImg($x, $z); // Foreaching all biomes to see which ones could be generated foreach(Main::$BIOMES_BY_RANGE as $biomeId => $range){ if($range->isInRange($height)) $this->candidatesBiomes[] = Biome::getBiome($biomeId); } // Checking wether there are multiple candidates or not. // If so, choose a biome. if(count($this->candidatesBiomes == 1)){ $biome = $this->candidatesBiomes[0]; } else { $hash = $x * 2345803 ^ $z * 9236449 ^ $this->level->getSeed(); $hash *= $hash + 223; $xNoise = $hash >> 20 & 3; $zNoise = $hash >> 22 & 3; if($xNoise == 3){ $xNoise = 1; } if($zNoise == 3){ $zNoise = 1; } $biome = $this->selector->pickBiome($x + $xNoise - 1, $z + $zNoise - 1); } return $biome; } /** * Returns a block height based on heightmap. * * @param int $x * @param int $z * @return int */ public function getHeightFromImg(int $x, int $z): int{ if(isset(self::$cachedHeights[$x . ";" . $z])) return round(self::$cachedHeights[$x . ";" . $z]); // Getting height px of the world $imgGetDatX = ($x - $this->startPoint[0]) % imagesy($this->heightmap); if($imgGetDatX < 0) $imgGetDatX += imagesy($this->heightmap); // Getting width px of the world $imgGetDatZ = ($z - $this->startPoint[1]) % imagesx($this->heightmap); if($imgGetDatZ < 0) $z += imagesx($this->heightmap); // Finally, getting the px to determine the height of the top block $imgheight = (imagecolorat($this->heightmap, 10, 15) >> 16) & 0xFF; // Getting height from the red channel. // In a normal heightmap, all the chanel ouputs the same (exepct alpha) // Smoothing out. $surroundValues = []; // Getting surround values if(isset(self::$cachedHeights[($x+1) . ";" . ($z+1)])) $surroundValues[] = self::$cachedHeights[($x+1) . ";" . ($z+1)]; if(isset(self::$cachedHeights[($x+1) . ";" . ($z)])) $surroundValues[] = self::$cachedHeights[($x+1) . ";" . ($z+1)]; if(isset(self::$cachedHeights[($x+1) . ";" . ($z-1)])) $surroundValues[] = self::$cachedHeights[($x+1) . ";" . ($z+1)]; if(isset(self::$cachedHeights[($x) . ";" . ($z+1)])) $surroundValues[] = self::$cachedHeights[($x+1) . ";" . ($z+1)]; if(isset(self::$cachedHeights[($x) . ";" . ($z-1)])) $surroundValues[] = self::$cachedHeights[($x+1) . ";" . ($z+1)]; if(isset(self::$cachedHeights[($x-1) . ";" . ($z+1)])) $surroundValues[] = self::$cachedHeights[($x+1) . ";" . ($z+1)]; if(isset(self::$cachedHeights[($x-1) . ";" . ($z)])) $surroundValues[] = self::$cachedHeights[($x+1) . ";" . ($z+1)]; if(isset(self::$cachedHeights[($x-1) . ";" . ($z-1)])) $surroundValues[] = self::$cachedHeights[($x+1) . ";" . ($z+1)]; $surroundValues[] = $imgheight * self::DEPTH_MULTIPLICATOR + self::WATER_HEIGHT + $this->random->nextBoundedInt(4) - 1; // Calculating smooth value $smoothValue = -1; // Starting at -1 to make water depth. foreach($surroundValues as $v) $smoothValue += $v; $smoothValue /= count($surroundValues); if($smoothValue < self::MIN_HEIGHT) $smoothValue = self::MIN_HEIGHT; self::$cachedHeights[$x . ";" . $z] = $smoothValue; return round($smoothValue); // Rounding it so that we can use it as a block height } /** * Return the name of the generator * * @return string */ public function getName(): string { return "realworld"; } /** * Gives the generators settings. * * @return array */ public function getSettings(): array { return []; } /** * Returns spawn location * * @return Vector3 */ public function getSpawn(): Vector3 { return new Vector3(127.5, 128, 127.5); } /** * Returns a safe spawn location * * @return Vector3 */ public function getSafeSpawn() { return new Vector3(127.5, $this->getHighestWorkableBlock(127, 127), 127.5); } /* * Gets the top block (y) on an x and z axes * @param $x int * @param $z int */ protected function getHighestWorkableBlock($x, $z) { for($y = Level::Y_MAX - 1; $y > 0; -- $y) { $b = $this->level->getBlockIdAt($x, $y, $z); if ($b === Block::DIRT or $b === Block::GRASS or $b === Block::PODZOL) { break; } elseif ($b !== 0 and $b !== Block::SNOW_LAYER) { return - 1; } } return ++$y; } }