genid = Main::$GENERATOR_IDS++; } /** * Inits the class for the variables. mw create Test 902874635 worldpainter * Executed on the main thread. * $level is actually a level instance. * @param ChunkManager $level * @param Random $random * @return void */ public function init(ChunkManager $level, Random $random) { $this->level = $level; $this->random = $random; $this->random->setSeed($this->level->getSeed()); if($level instanceof Level && isset($this->genid)) { // First init in main thread $this->worldpath = getcwd() . "/worlds/" . $level->getName() . "/"; // Initing folder data @mkdir($this->worldpath . "gendata"); $config = yaml_parse(file_get_contents(getcwd() . "/plugins/GenPainterPE/config.yml")); // Checking heightmap if(!file_exists($this->worldpath . "gendata/heightmap.png")) { copy(getcwd() . "/plugins/GenPainterPE/heightmaps/" . $config["heightmap_name"] . ".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 GenPainterPE AND UNEXEPTED ISSUES MAY OCCUR IF YOU MODIFY ANY OF THESES VALUES."; // Do not modify comment in file for noobs. $spawn = $this->getSpawnsFromImg(); $data["startPoint"] = [ $spawn->x, $spawn->y, $spawn->z ]; $data = array_merge($data, $config); unset($data["heightmap_name"]); file_put_contents($this->worldpath . "gendata/geninfos.json", json_encode($data)); } // Adding symlink to get path later. if(file_exists(getcwd() . "/plugins/GenPainterPE/tmp/" . $this->genid)) unlink(getcwd() . "/plugins/GenPainterPE/tmp/" . $this->genid); symlink($this->worldpath, getcwd() . "/plugins/GenPainterPE/tmp/" . $this->genid); } // Regetting the vars from the symlinked folder $this->worldpath = readlink(getcwd() . "/plugins/GenPainterPE/tmp/" . $this->genid); $this->heightmap = \imagecreatefrompng($this->worldpath . "gendata/heightmap.png"); $this->options = json_decode(file_get_contents($this->worldpath . "gendata/geninfos.json")); $this->startPoint = $this->options->startPoint; // Making selector $this->selector = new BiomeSelector($this->random, function($temperature, $rainfall){ // Checking the nearest candidate biome that have the closest $temperature and $rainfall. $bestBiome = [405001, Biome::getBiome(Biome::OCEAN)]; // Default biome. Should be enough. 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]; } return $bestBiome[1]->getId(); }, 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)); // Populators if($this->options->generate_custom_ground){ $cover = new GroundCover(); $this->generationPopulators[] = $cover; } if($this->options->generate_caves){ $cave = new CavePopulator (); $cave->setBaseAmount(0); $cave->setRandomAmount(2); $this->generationPopulators[] = $cave; $ravine = new RavinePopulator (); $ravine->setBaseAmount(0); $ravine->setRandomAmount(51); $this->generationPopulators[] = $ravine; } if($this->options->generate_ores) { $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->generationPopulators[] = $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 + $z; $height = $this->getHeightFromImg($currentX, $currentZ); $biome = $this->getBiomeFromPos($currentX, $currentZ); $chunk->setBiomeId($x, $z, $biome->getId()); // Building terrain for($y = 0; $y < 256; ++$y) { if($y === 0) { $chunk->setBlockId($x, $y, $z, Block::BEDROCK); } elseif($y <= self::BEDROCK_MAX_HEIGHT && $this->random->nextBoundedInt(2) == 0) { $chunk->setBlockId($x, $y, $z, Block::BEDROCK); } elseif($y <= $height) { $chunk->setBlockId($x, $y, $z, Block::STONE); } elseif($y <= self::WATER_HEIGHT) { $chunk->setBlockId($x, $y, $z, Block::STILL_WATER); } } } } foreach($this->generationPopulators as $populator){ $populator->populate($this->level, $chunkX, $chunkZ, $this->random); } } /** * 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()); // Check if the generator should generate structures. if($this->options->generate_structures) { 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) < 1) Main::generateRanges(); $height = $this->getHeightFromImg($x, $z); // Check if the generator should generate biomes if(!$this->options->generate_biomes) { if($height > self::WATER_HEIGHT) { return Biome::get(Biome::PLAINS); } else { return Biome::get(Biome::OCEAN); } } // 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 = abs($x) % imagesy($this->heightmap); // Getting width px of the world $imgGetDatZ = abs($z) % imagesx($this->heightmap); // Finally, getting the px to determine the height of the top block $imgheight = imagecolorsforindex($this->heightmap, imagecolorat($this->heightmap, $imgGetDatZ, $imgGetDatX))["red"]; // 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)]; 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) . ";" . ($z+1)]; if(isset(self::$cachedHeights[($x) . ";" . ($z-1)])) $surroundValues[] = self::$cachedHeights[($x) . ";" . ($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)]; if(isset(self::$cachedHeights[($x-1) . ";" . ($z-1)])) $surroundValues[] = self::$cachedHeights[($x-1) . ";" . ($z-1)]; $imgValue = $imgheight * self::DEPTH_MULTIPLICATOR + self::WATER_HEIGHT; if($imgheight == 0) { // Calculating presmooth value (to generate $imgheight water value alot smoother and going deeper) if(count($surroundValues) !== 0) { $preSmoothValue = 0; foreach($surroundValues as $v) $preSmoothValue += $v; $preSmoothValue /= count($surroundValues); } else { $preSmoothValue = 100; } $calcDiffValue = ($preSmoothValue - 100) / 2; if(round($calcDiffValue) == -3) $calcDiffValue--; $imgValue += -$this->random->nextBoundedInt(3 + round($calcDiffValue)) - 3 + $calcDiffValue;// Used to make water depth. } // Calculating smooth value // $smoothValue = -1; // foreach($surroundValues as $v) $smoothValue += $v; // $smoothValue += $imgValue * 3; // $smoothValue /= count($surroundValues) + 3; if($imgValue < self::MIN_HEIGHT) $imgValue = self::MIN_HEIGHT; self::$cachedHeights[$x . ";" . $z] = $imgValue; return round($imgValue); // 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 "worldpainter"; } /** * Gives the generators settings. * * @return array */ public function getSettings(): array { return []; } /** * Returns spawn location * * @return Vector3 */ public function getSpawn(): Vector3 { return new Vector3($this->spawnPoint[0], $this->spawnPoint[1], $this->spawnPoint[2]); } /** * Returns a safe spawn location * * @return Vector3 */ public function getSafeSpawn() { return new Vector3($this->spawnPoint[0], $this->spawnPoint[1], $this->spawnPoint[2]); } /* * 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; } /** * Checks the image for a safes spawns (not in water) then saves it. * * @return void */ public function getSpawnsFromImg(): Vector3{ $spawn = new Vector3(128, 128, 128); $found = []; mt_srand($this->random->getSeed()); for($i = 0; $i < 1028; $i++){ // Checking 1028 spots to check for a spawn. If none are found, default to 128 128 128. $x = mt_rand(0, imagesy($this->heightmap) - 1); $z = mt_rand(0, imagesx($this->heightmap) - 1); $imgheight = imagecolorsforindex($this->heightmap, imagecolorat($this->heightmap, $z, $x))["red"]; if($imgheight !== 0){ $imgValue = $imgheight * self::DEPTH_MULTIPLICATOR + self::WATER_HEIGHT + 2; // +2 is here so that the player will not get stuck in a block. $found[] = new Vector3($x, round($imgValue), $z); } } if(count($found) == 0) return $spawn; return $found[mt_rand(0, count($found) - 1)]; } }