commit 24f9ede380eb06a2879b3d80f8fc3b1d6cbdbe9d Author: Ad5001 Date: Sun Oct 22 10:45:05 2017 +0200 Adding current progress diff --git a/README.md b/README.md new file mode 100644 index 0000000..a16e741 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# RealWorld +Pocketmine Generator for Earth and heightmap based generation. + +# Installation +To support all the features including your own heightmap creation, you may need to install the PHP GD extension. +How to do that? +- For PocketMine Server Manager users, check if the plugin is working, if not, just delete the folder located in <YOUR\_OWN\_FOLDER>/.pocketmine/php and restart PocketMine Server Manager. +- For regular pocketmine user, you can get the following script to install a PHP with GD included . + +You may also need to install https://sourceforge.net/projects/libpng/. \ No newline at end of file diff --git a/heightmap.png b/heightmap.png new file mode 100644 index 0000000..bfdd562 Binary files /dev/null and b/heightmap.png differ diff --git a/plugin.yml b/plugin.yml new file mode 100644 index 0000000..c3edb81 --- /dev/null +++ b/plugin.yml @@ -0,0 +1,9 @@ +--- +name: RealWorld +author: Ad5001 +version: 1.0 +api: [3.0.0-ALPHA9] +main: Ad5001\RealWorld\Main +commands: [] +permissions: [] +... \ No newline at end of file diff --git a/resources/heightmap.jpg b/resources/heightmap.jpg new file mode 100644 index 0000000..24730b0 Binary files /dev/null and b/resources/heightmap.jpg differ diff --git a/resources/heightmap.png b/resources/heightmap.png new file mode 100644 index 0000000..bfdd562 Binary files /dev/null and b/resources/heightmap.png differ diff --git a/src/Ad5001/RealWorld/Main.php b/src/Ad5001/RealWorld/Main.php new file mode 100644 index 0000000..8757c6e --- /dev/null +++ b/src/Ad5001/RealWorld/Main.php @@ -0,0 +1,90 @@ +getDataFolder()); + if(!file_exists($this->getDataFolder() . "heightmap.png")) { // Get default world HeightMap + file_put_contents($this->getDataFolder() . "heightmap.png", $this->getResource("heightmap.png")); + } + $this->getServer()->getPluginManager()->registerEvents($this, $this); + // Register generators + Generator::addGenerator(RealWorld::class, "realworld"); + } + + /** + * Checks when a command is sent. + * + * @param CommandSender $sender + * @param Command $cmd + * @param string $label + * @param array $args + * @return bool + */ + public function onCommand(CommandSender $sender, Command $cmd, string $label, array $args): bool{ + switch($cmd->getName()){ + case "default": + break; + } + return false; + } + + + // /** + // * Checks when a world will start being generated to give it's id to it and start generation + // * + // * @param LevelInitEvent $ev + // * @return void + // */ + // public function onLevelInit(LevelInitEvent $ev){ + // $lvl = $ev->getLevel(); + // $contents = ""; + // if(file_exists($this->getDataFolder() . "worldsids.txt")){ + // $contents = file_get_contents($this->getDataFolder() . "worldsids.txt") . "\n"; + // } + // $contents .= $lvl->getId() . ": " . $lvl->getName(); + // file_put_contents($this->getDataFolder() . "worldsids.txt") + // } + + /** + * Generates all ranges for biomes. + * Default WATER_HEIGHT is 100 + * + * @return void + */ + public static function generateRanges(){ + self::$BIOMES_BY_RANGE = []; + self::$BIOMES_BY_RANGE[Biome::OCEAN] = new Range(RealWorld::MIN_HEIGHT, RealWorld::WATER_HEIGHT); + self::$BIOMES_BY_RANGE[Biome::RIVER] = new Range(RealWorld::WATER_HEIGHT, RealWorld::WATER_HEIGHT + 10); + self::$BIOMES_BY_RANGE[Biome::SWAMP] = new Range(RealWorld::WATER_HEIGHT, RealWorld::WATER_HEIGHT + 10); + self::$BIOMES_BY_RANGE[Biome::DESERT] = new Range(RealWorld::WATER_HEIGHT + 5, RealWorld::WATER_HEIGHT + 31); + self::$BIOMES_BY_RANGE[Biome::ICE_PLAINS] = new Range(RealWorld::WATER_HEIGHT + 10, RealWorld::WATER_HEIGHT + 31); + self::$BIOMES_BY_RANGE[Biome::PLAINS] = new Range(RealWorld::WATER_HEIGHT + 10, RealWorld::WATER_HEIGHT + 31); + self::$BIOMES_BY_RANGE[Biome::FOREST] = new Range(RealWorld::WATER_HEIGHT + 24, RealWorld::WATER_HEIGHT + 47); + self::$BIOMES_BY_RANGE[Biome::BIRCH_FOREST] = new Range(RealWorld::WATER_HEIGHT + 24, RealWorld::WATER_HEIGHT + 47); + self::$BIOMES_BY_RANGE[Biome::TAIGA] = new Range(RealWorld::WATER_HEIGHT + 31, RealWorld::WATER_HEIGHT + 47); + self::$BIOMES_BY_RANGE[Biome::SMALL_MOUNTAINS] = new Range(RealWorld::WATER_HEIGHT + 47, RealWorld::WATER_HEIGHT + 95); + self::$BIOMES_BY_RANGE[Biome::MOUNTAINS] = new Range(RealWorld::WATER_HEIGHT + 95, RealWorld::WATER_HEIGHT + 155); + } +} \ No newline at end of file diff --git a/src/Ad5001/RealWorld/generator/RealWorld.php b/src/Ad5001/RealWorld/generator/RealWorld.php new file mode 100644 index 0000000..6fd0237 --- /dev/null +++ b/src/Ad5001/RealWorld/generator/RealWorld.php @@ -0,0 +1,342 @@ +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; + } +} \ No newline at end of file diff --git a/src/Ad5001/RealWorld/generator/RealWorldLarge.php b/src/Ad5001/RealWorld/generator/RealWorldLarge.php new file mode 100644 index 0000000..0281539 --- /dev/null +++ b/src/Ad5001/RealWorld/generator/RealWorldLarge.php @@ -0,0 +1,22 @@ +randomAmount = $amount; + } + + /** + * Sets the base addition amount + * @param $amount int + */ + public function setBaseAmount(int $amount) { + $this->baseAmount = $amount; + } + + /** + * Returns the amount based on random + * + * @param Random $random + * @return int + */ + public function getAmount(Random $random) { + return $this->baseAmount + $random->nextRange(0, $this->randomAmount + 1); + } + + /** + * Returns base amount + * + * @return int + */ + public function getBaseAmount(): int { + return $this->baseAmount; + } + + /** + * Returns the random additional amount + * + * @return int + */ + public function getRandomAmount(): int { + return $this->randomAmount; + } +} \ No newline at end of file diff --git a/src/Ad5001/RealWorld/populator/CavePopulator.php b/src/Ad5001/RealWorld/populator/CavePopulator.php new file mode 100644 index 0000000..9e28c89 --- /dev/null +++ b/src/Ad5001/RealWorld/populator/CavePopulator.php @@ -0,0 +1,158 @@ +level = $level; + $amount = $this->getAmount($random); + for($i = 0; $i < $amount; $i++) { + $x = $random->nextRange($chunkX << 4, ($chunkX << 4) + 15); + $z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 15); + $y = $random->nextRange(10, $this->getHighestWorkableBlock($x, $z)); + // echo "Generating cave at $x, $y, $z." . PHP_EOL; + $this->generateCave($x, $y, $z, $random); + } + // echo "Finished Populating chunk $chunkX, $chunkZ !" . PHP_EOL; + // Filling water & lava sources randomly + for($i = 0; $i < $random->nextBoundedInt(5) + 3; $i ++) { + $x = $random->nextRange($chunkX << 4, ($chunkX << 4) + 15); + $z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 15); + $y = $random->nextRange(10, $this->getHighestWorkableBlock($x, $z)); + if ($level->getBlockIdAt($x, $y, $z) == Block::STONE && ($level->getBlockIdAt($x + 1, $y, $z) == Block::AIR || $level->getBlockIdAt($x - 1, $y, $z) == Block::AIR || $level->getBlockIdAt($x, $y, $z + 1) == Block::AIR || $level->getBlockIdAt($x, $y, $z - 1) == Block::AIR) && $level->getBlockIdAt($x, $y - 1, $z) !== Block::AIR && $level->getBlockIdAt($x, $y + 1, $z) !== Block::AIR) { + if ($y < 40 && $random->nextBoolean ()) { + $level->setBlockIdAt($x, $y, $z, Block::LAVA); + } else { + $level->setBlockIdAt($x, $y, $z, Block::WATER); + } + } + } + } + + /** + * Gets the top block (y) on an x and z axes + * @param int $x + * @param int $z + */ + 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 or $b === Block::SAND or $b === Block::SNOW_BLOCK or $b === Block::SANDSTONE) { + break; + } elseif ($b !== 0 and $b !== Block::SNOW_LAYER and $b !== Block::WATER) { + return - 1; + } + } + + return ++$y; + } + + /** + * Generates a cave + * + * @param int $x + * @param int $y + * @param int $z + * @param Random $random + * @return void + */ + public function generateCave($x, $y, $z, Random $random) { + $generatedBranches = $random->nextBoundedInt(10) + 1; + foreach($gen = $this->generateBranch($x, $y, $z, 5, 3, 5, $random) as $v3) { + $generatedBranches --; + if ($generatedBranches <= 0) { + $gen->send(self::STOP); + } else { + $gen->send(self::CONTINUE); + } + } + } + + /** + * Generates a cave branch + * + * @param int $x + * @param int $y + * @param int $z + * @param int $length + * @param int $height + * @param int $depth + * @param Random $random + * @yield Vector3 + * @return void + */ + public function generateBranch($x, $y, $z, $length, $height, $depth, Random $random) { + if (! (yield new Vector3($x, $y, $z))) { + for($i = 0; $i <= 4; $i ++) { + BuildingUtils::buildRandom($this->level, new Vector3($x, $y, $z), new Vector3($length - $i, $height - $i, $depth - $i), $random, Block::get(Block::AIR)); + $x += round(($random->nextBoundedInt(round(30 * ($length / 10)) + 1) / 10 - 2)); + $yP = $random->nextRange(-14, 14); + if ($yP > 12) { + $y ++; + } elseif ($yP < - 12) { + $y --; + } + $z += round(($random->nextBoundedInt(round(30 * ($depth / 10)) + 1) / 10 - 1)); + return; + } + } + $repeat = $random->nextBoundedInt(25) + 15; + while($repeat-- > 0) { + BuildingUtils::buildRandom($this->level, new Vector3($x, $y, $z), new Vector3($length, $height, $depth), $random, Block::get(Block::AIR)); + $x += round(($random->nextBoundedInt(round(30 * ($length / 10)) + 1) / 10 - 2)); + $yP = $random->nextRange(- 14, 14); + if ($yP > 12) { + $y ++; + } elseif ($yP < - 12) { + $y --; + } + $z += round(($random->nextBoundedInt(round(30 * ($depth / 10)) + 1) / 10 - 1)); + $height += $random->nextBoundedInt(3) - 1; + $length += $random->nextBoundedInt(3) - 1; + $depth += $random->nextBoundedInt(3) - 1; + if ($height < 3) + $height = 3; + if ($length < 3) + $length = 3; + if ($height < 3) + $height = 3; + if ($height < 7) + $height = 7; + if ($length < 7) + $length = 7; + if ($height < 7) + $height = 7; + if ($random->nextBoundedInt(10) == 0) { + foreach($generator = $this->generateBranch($x, $y, $z, $length, $height, $depth, $random) as $gen) { + if (! (yield $gen)) + $generator->send(self::STOP); + } + } + } + return; + } +} \ No newline at end of file diff --git a/src/Ad5001/RealWorld/populator/RavinePopulator.php b/src/Ad5001/RealWorld/populator/RavinePopulator.php new file mode 100644 index 0000000..05fea7a --- /dev/null +++ b/src/Ad5001/RealWorld/populator/RavinePopulator.php @@ -0,0 +1,125 @@ +level = $level; + $amount = $this->getAmount($random); + if ($amount > 50) { // Only build one per chunk + $depth = $random->nextBoundedInt(60) + 30; // 2Much4U? + $x = $random->nextRange($chunkX << 4, ($chunkX << 4) + 15); + $z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 15); + $y = $random->nextRange(5, $this->getHighestWorkableBlock($x, $z)); + $deffX = $x; + $deffZ = $z; + $height = $random->nextRange(15, 30); + $length = $random->nextRange(5, 12); + for($i = 0; $i < $depth; $i ++) { + $this->buildRavinePart($x, $y, $z, $height, $length, $random); + $diffX = $x - $deffX; + $diffZ = $z - $deffZ; + if ($diffX > $length / 2) + $diffX = $length / 2; + if ($diffX < - $length / 2) + $diffX = - $length / 2; + if ($diffZ > $length / 2) + $diffZ = $length / 2; + if ($diffZ < - $length / 2) + $diffZ = - $length / 2; + if ($length > 10) + $length = 10; + if ($length < 5) + $length = 5; + $x += $random->nextRange(0 + $diffX, 2 + $diffX) - 1; + $y += $random->nextRange(0, 2) - 1; + $z += $random->nextRange(0 + $diffZ, 2 + $diffZ) - 1; + $height += $random->nextRange(0, 2) - 1; + $length += $random->nextRange(0, 2) - 1; + } + } + } + + /* + * 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 or $b === Block::SAND or $b === Block::SNOW_BLOCK or $b === Block::SANDSTONE) { + break; + } elseif ($b !== 0 and $b !== Block::SNOW_LAYER and $b !== Block::WATER) { + return - 1; + } + } + + return ++$y; + } + + /** + * Buidls a ravine part + * + * @param int $x + * @param int $y + * @param int $z + * @param int $height + * @param int $length + * @param Random $random + * @return void + */ + protected function buildRavinePart($x, $y, $z, $height, $length, Random $random) { + $xBounded = 0; + $zBounded = 0; + for($xx = $x - $length; $xx <= $x + $length; $xx ++) { + for($yy = $y; $yy <= $y + $height; $yy ++) { + for($zz = $z - $length; $zz <= $z + $length; $zz ++) { + $oldXB = $xBounded; + $xBounded = $random->nextBoundedInt(self::NOISE * 2) - self::NOISE; + $oldZB = $zBounded; + $zBounded = $random->nextBoundedInt(self::NOISE * 2) - self::NOISE; + if ($xBounded > self::NOISE - 2) { + $xBounded = 1; + } elseif ($xBounded < - self::NOISE + 2) { + $xBounded = -1; + } else { + $xBounded = $oldXB; + } + if ($zBounded > self::NOISE - 2) { + $zBounded = 1; + } elseif ($zBounded < - self::NOISE + 2) { + $zBounded = -1; + } else { + $zBounded = $oldZB; + } + if (abs((abs($xx) - abs($x)) ** 2 + (abs($zz) - abs($z)) ** 2) < ((($length / 2 - $xBounded) + ($length / 2 - $zBounded)) / 2) ** 2 && $y > 0 && ! in_array($this->level->getBlockIdAt(( int) round($xx),(int) round($yy),(int) round($zz)), BuildingUtils::TO_NOT_OVERWRITE) && ! in_array($this->level->getBlockIdAt(( int) round($xx),(int) round($yy + 1),(int) round($zz)), BuildingUtils::TO_NOT_OVERWRITE)) { + $this->level->setBlockIdAt(( int) round($xx),(int) round($yy),(int) round($zz), Block::AIR); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Ad5001/RealWorld/utils/BuildingUtils.php b/src/Ad5001/RealWorld/utils/BuildingUtils.php new file mode 100644 index 0000000..6a7f371 --- /dev/null +++ b/src/Ad5001/RealWorld/utils/BuildingUtils.php @@ -0,0 +1,210 @@ +x; $x >= $pos2->x; $x --) for($y = $pos1->y; $y >= $pos2->y; $y --) for($z = $pos1->z; $z >= $pos2->z; $z --) { + $level->setBlockIdAt($x, $y, $z, $block->getId ()); + $level->setBlockDataAt($x, $y, $z, $block->getDamage ()); + } + } + + + /** + * Fills an area randomly + * + * @param ChunkManager $level + * @param Vector3 $pos1 + * @param Vector3 $pos2 + * @param Block $block + * @param Random $random + * @param int $randMax + * @return void + */ + public static function fillRandom(ChunkManager $level, Vector3 $pos1, Vector3 $pos2, Block $block = null, Random $random = null, $randMax = 3) { + if ($block == null) $block = Block::get(Block::AIR); + list($pos1, $pos2) = self::minmax($pos1, $pos2); + for($x = $pos1->x; $x >= $pos2->x; $x --) for($y = $pos1->y; $y >= $pos2->y; $y --) for($z = $pos1->z; $z >= $pos2->z; $z --) if($random !== null ? $random->nextBoundedInt($randMax) == 0 : rand(0, $randMax) == 0) { + $level->setBlockIdAt($x, $y, $z, $block->getId ()); + $level->setBlockDataAt($x, $y, $z, $block->getDamage ()); + } + } + + /** + * Custom area filling + * + * @param Vector3 $pos1 + * @param Vector3 $pos2 + * @param callable $call + * @param array $params + * @return array + */ + public static function fillCallback(Vector3 $pos1, Vector3 $pos2, callable $call, ...$params) : array { + list($pos1, $pos2) = self::minmax($pos1, $pos2); + $return = []; + for($x = $pos1->x; $x >= $pos2->x; $x --) for($y = $pos1->y; $y >= $pos2->y; $y --) for($z = $pos1->z; $z >= $pos2->z; $z --) { + $return[] = call_user_func($call, new Vector3($x, $y, $z), ...$params); + } + return $return; + } + + /** + * Creates walls + * + * @param ChunkManager $level + * @param Vector3 $pos1 + * @param Vector3 $pos2 + * @param Block $block + * @return void + */ + public static function walls(ChunkManager $level, Vector3 $pos1, Vector3 $pos2, Block $block) { + list($pos1, $pos2) = self::minmax($pos1, $pos2); + for($y = $pos1->y; $y >= $pos2->y; $y --) { + for($x = $pos1->x; $x >= $pos2->x; $x --) { + $level->setBlockIdAt($x, $y, $pos1->z, $block->getId ()); + $level->setBlockDataAt($x, $y, $pos1->z, $block->getDamage ()); + $level->setBlockIdAt($x, $y, $pos2->z, $block->getId ()); + $level->setBlockDataAt($x, $y, $pos2->z, $block->getDamage ()); + } + for($z = $pos1->z; $z >= $pos2->z; $z --) { + $level->setBlockIdAt($pos1->x, $y, $z, $block->getId ()); + $level->setBlockDataAt($pos1->x, $y, $z, $block->getDamage ()); + $level->setBlockIdAt($pos2->x, $y, $z, $block->getId ()); + $level->setBlockDataAt($pos2->x, $y, $z, $block->getDamage ()); + } + } + } + + /** + * Creates the top of a structure + * + * @param ChunkManager $level + * @param Vector3 $pos1 + * @param Vector3 $pos2 + * @param Block $block + * @return void + */ + public static function top(ChunkManager $level, Vector3 $pos1, Vector3 $pos2, Block $block) { + list($pos1, $pos2) = self::minmax($pos1, $pos2); + for($x = $pos1->x; $x >= $pos2->x; $x --) + for($z = $pos1->z; $z >= $pos2->z; $z --) { + $level->setBlockIdAt($x, $pos1->y, $z, $block->getId ()); + $level->setBlockDataAt($x, $pos1->y, $z, $block->getDamage ()); + } + } + + /** + * Creates the corners of the structures. Used for mineshaft "towers" + * + * @param ChunkManager $level + * @param Vector3 $pos1 + * @param Vector3 $pos2 + * @param Block $block + * @return void + */ + public static function corners(ChunkManager $level, Vector3 $pos1, Vector3 $pos2, Block $block) { + list($pos1, $pos2) = self::minmax($pos1, $pos2); + for($y = $pos1->y; $y >= $pos2->y; $y --) { + $level->setBlockIdAt($pos1->x, $y, $pos1->z, $block->getId ()); + $level->setBlockDataAt($pos1->x, $y, $pos1->z, $block->getDamage ()); + $level->setBlockIdAt($pos2->x, $y, $pos1->z, $block->getId ()); + $level->setBlockDataAt($pos2->x, $y, $pos1->z, $block->getDamage ()); + $level->setBlockIdAt($pos1->x, $y, $pos2->z, $block->getId ()); + $level->setBlockDataAt($pos1->x, $y, $pos2->z, $block->getDamage ()); + $level->setBlockIdAt($pos2->x, $y, $pos2->z, $block->getId ()); + $level->setBlockDataAt($pos2->x, $y, $pos2->z, $block->getDamage ()); + } + } + + /** + * Fills the bottom of a structure + * + * @param ChunkManager $level + * @param Vector3 $pos1 + * @param Vector3 $pos2 + * @param Block $block + * @return void + */ + public static function bottom(ChunkManager $level, Vector3 $pos1, Vector3 $pos2, Block $block) { + list($pos1, $pos2) = self::minmax($pos1, $pos2); + for($x = $pos1->x; $x >= $pos2->x; $x --) + for($z = $pos1->z; $z >= $pos2->z; $z --) { + $level->setBlockIdAt($x, $pos2->y, $z, $block->getId ()); + $level->setBlockDataAt($x, $pos2->y, $z, $block->getDamage ()); + } + } + + /** + * Builds a structure randomly based on a circle algorithm. Used in caves and lakes. + * + * @param ChunkManager $level + * @param Vector3 $pos + * @param Vector3 $infos + * @param Random $random + * @param Block $block + * @return void + */ + public static function buildRandom(ChunkManager $level, Vector3 $pos, Vector3 $infos, Random $random, Block $block) { + $xBounded = $random->nextBoundedInt(3) - 1; + $yBounded = $random->nextBoundedInt(3) - 1; + $zBounded = $random->nextBoundedInt(3) - 1; + $pos = $pos->round (); + for($x = $pos->x - ($infos->x / 2); $x <= $pos->x + ($infos->x / 2); $x ++) { + for($y = $pos->y - ($infos->y / 2); $y <= $pos->y + ($infos->y / 2); $y ++) { + for($z = $pos->z - ($infos->z / 2); $z <= $pos->z + ($infos->z / 2); $z ++) { + // if(abs((abs($x) - abs($pos->x)) ** 2 + ($y - $pos->y) ** 2 + (abs($z) - abs($pos->z)) ** 2) < (abs($infos->x / 2 + $xBounded) + abs($infos->y / 2 + $yBounded) + abs($infos->z / 2 + $zBounded)) ** 2 + if (abs((abs($x) - abs($pos->x)) ** 2 + ($y - $pos->y) ** 2 + (abs($z) - abs($pos->z)) ** 2) < ((($infos->x / 2 - $xBounded) + ($infos->y / 2 - $yBounded) + ($infos->z / 2 - $zBounded)) / 3) ** 2 && $y > 0 && ! in_array($level->getBlockIdAt($x, $y, $z), self::TO_NOT_OVERWRITE) && ! in_array($level->getBlockIdAt($x, $y + 1, $z), self::TO_NOT_OVERWRITE)) { + $level->setBlockIdAt($x, $y, $z, $block->getId ()); + $level->setBlockDataAt($x, $y, $z, $block->getDamage ()); + } + } + } + } + } + + /** + * Returns two Vector three, the biggest and lowest ones based on two provided vectors + * + * @param Vector3 $pos1 + * @param Vector3 $pos2 + * @return array + */ + protected static function minmax(Vector3 $pos1, Vector3 $pos2): array { + $v1 = new Vector3(max($pos1->x, $pos2->x), max($pos1->y, $pos2->y), max($pos1->z, $pos2->z)); + $v2 = new Vector3(min($pos1->x, $pos2->x), min($pos1->y, $pos2->y), min($pos1->z, $pos2->z)); + return [ + $v1, + $v2 + ]; + } +} \ No newline at end of file diff --git a/src/Ad5001/RealWorld/utils/Range.php b/src/Ad5001/RealWorld/utils/Range.php new file mode 100644 index 0000000..4135b2f --- /dev/null +++ b/src/Ad5001/RealWorld/utils/Range.php @@ -0,0 +1,32 @@ +from = $from; + $this->to = $to; + } + + /** + * Check if a number is in the range + * + * @param int $toTest + * @return boolean + */ + public function isInRange(int $toTest){ + return $this->from <= $toTest && $toTest < $this->to; + } +} \ No newline at end of file