You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
435 lines
17 KiB
PHP
435 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* _____ _____ _ _ _____ ______
|
|
* / ____| | __ \ (_) | | | __ \| ____|
|
|
* | | __ ___ _ __ | |__) |_ _ _ _ __ | |_ ___ _ __| |__) | |__
|
|
* | | |_ |/ _ \ '_ \| ___/ _` | | '_ \| __/ _ \ '__| ___/| __|
|
|
* | |__| | __/ | | | | | (_| | | | | | || __/ | | | | |____
|
|
* \_____|\___|_| |_|_| \__,_|_|_| |_|\__\___|_| |_| |______|
|
|
* Pocketmine Generator for Earth and heightmap based generation.
|
|
*
|
|
* Copyright (C) Ad5001 2017
|
|
*
|
|
* @author Ad5001
|
|
* @api 3.0.0
|
|
* @copyright 2017 Ad5001
|
|
* @license NTOSL - View LICENSE.md
|
|
* @version 1.0.0
|
|
* @package GenPainterPE
|
|
*/
|
|
|
|
namespace Ad5001\GenPainterPE\generator;
|
|
|
|
use pocketmine\level\generator\Generator;
|
|
use pocketmine\block\Block;
|
|
use pocketmine\block\BlockFactory;
|
|
use pocketmine\block\CoalOre;
|
|
use pocketmine\block\DiamondOre;
|
|
use pocketmine\block\Dirt;
|
|
use pocketmine\block\GoldOre;
|
|
use pocketmine\block\Gravel;
|
|
use pocketmine\block\IronOre;
|
|
use pocketmine\block\LapisOre;
|
|
use pocketmine\block\RedstoneOre;
|
|
use pocketmine\level\ChunkManager;
|
|
use pocketmine\level\generator\biome\Biome;
|
|
use pocketmine\level\generator\noise\Simplex;
|
|
use pocketmine\level\generator\normal\object\OreType as OreType2;
|
|
use pocketmine\level\generator\object\OreType;
|
|
use pocketmine\level\generator\populator\GroundCover;
|
|
use pocketmine\level\generator\populator\Ore;
|
|
use pocketmine\level\generator\populator\Populator;
|
|
use pocketmine\level\Level;
|
|
use pocketmine\math\Vector3;
|
|
use pocketmine\utils\Random;
|
|
|
|
use Ad5001\GenPainterPE\Main;
|
|
use Ad5001\GenPainterPE\populator\CavePopulator;
|
|
use Ad5001\GenPainterPE\populator\RavinePopulator;
|
|
use Ad5001\GenPainterPE\utils\BiomeSelector;
|
|
|
|
|
|
class GenPainter extends Generator{
|
|
/** Both two here tells the max and min of water. */
|
|
const WATER_HEIGHT = 100;
|
|
const MIN_HEIGHT = 60;
|
|
/** Maximum height for bedrock */
|
|
const BEDROCK_MAX_HEIGHT = 5;
|
|
/** Converts the original picture height to a minecraft height */
|
|
const DEPTH_MULTIPLICATOR = 0.2;
|
|
|
|
/**
|
|
* @var array
|
|
* Coordinates of the start point
|
|
*/
|
|
protected $startPoint = [];
|
|
/** @var resource - Heightmap image resource */
|
|
protected $heightmap;
|
|
|
|
/** @var ChunkManager */
|
|
protected $level;
|
|
/** @var Random */
|
|
protected $random;
|
|
/** @var BiomeSelector */
|
|
protected $selector;
|
|
/** @var int[] */
|
|
protected static $cachedHeights = [];
|
|
/** @var Biome[] - Biomes that are candidates to be choosen to be generated */
|
|
protected $candidatesBiomes = [];
|
|
/** @var Populator[] */
|
|
private $generationPopulators = [];
|
|
/** @var Populator[] */
|
|
private $populators = [];
|
|
|
|
/**
|
|
* Constructs the class
|
|
*
|
|
* @param array $options
|
|
*/
|
|
public function __construct($options = []){
|
|
$this->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)];
|
|
}
|
|
}
|