2017-10-22 08:45:05 +00:00
< ? php
2017-10-25 16:09:06 +00:00
/**
* _____ _____ _ _ _____ ______
* / ____ | | __ \ ( _ ) | | | __ \ | ____ |
* | | __ ___ _ __ | | __ ) | _ _ _ _ __ | | _ ___ _ __ | | __ ) | | __
* | | | _ |/ _ \ '_ \| ___/ _` | | ' _ \ | __ / _ \ ' __ | ___ /| __ |
* | | __ | | __ / | | | | | ( _ | | | | | | || __ / | | | | | ____
* \_____ | \___ | _ | | _ | _ | \__ , _ | _ | _ | | _ | \__\___ | _ | | _ | | ______ |
* 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
*/
2017-10-24 22:22:50 +00:00
namespace Ad5001\GenPainterPE\generator ;
2017-10-22 08:45:05 +00:00
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 ;
2017-10-24 22:22:50 +00:00
use Ad5001\GenPainterPE\Main ;
use Ad5001\GenPainterPE\populator\CavePopulator ;
use Ad5001\GenPainterPE\populator\RavinePopulator ;
use Ad5001\GenPainterPE\utils\BiomeSelector ;
2017-10-22 08:45:05 +00:00
2017-10-24 22:22:50 +00:00
class GenPainter extends Generator {
2017-10-22 08:45:05 +00:00
/** Both two here tells the max and min of water. */
const WATER_HEIGHT = 100 ;
const MIN_HEIGHT = 60 ;
2017-10-24 22:22:50 +00:00
/** Maximum height for bedrock */
const BEDROCK_MAX_HEIGHT = 5 ;
2017-10-22 08:45:05 +00:00
/** Converts the original picture height to a minecraft height */
2017-10-24 22:22:50 +00:00
const DEPTH_MULTIPLICATOR = 0.2 ;
2017-10-22 08:45:05 +00:00
/**
* @ 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
*/
2017-10-24 22:22:50 +00:00
public function __construct ( $options = []){
$this -> genid = Main :: $GENERATOR_IDS ++ ;
}
2017-10-22 08:45:05 +00:00
/**
2017-10-24 22:22:50 +00:00
* Inits the class for the variables . mw create Test 902874635 worldpainter
2017-10-22 08:45:05 +00:00
* 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 ());
2017-10-24 22:22:50 +00:00
if ( $level instanceof Level &&
isset ( $this -> genid )) { // First init in main thread
$this -> worldpath = getcwd () . " /worlds/ " . $level -> getName () . " / " ;
2017-10-22 08:45:05 +00:00
// Initing folder data
@ mkdir ( $this -> worldpath . " gendata " );
2017-10-24 22:22:50 +00:00
$config = yaml_parse ( file_get_contents ( getcwd () . " /plugins/GenPainterPE/config.yml " ));
2017-10-22 08:45:05 +00:00
// Checking heightmap
if ( ! file_exists ( $this -> worldpath . " gendata/heightmap.png " )) {
2017-10-24 22:22:50 +00:00
copy ( getcwd () . " /plugins/GenPainterPE/heightmaps/ " . $config [ " heightmap_name " ] . " .png " , $this -> worldpath . " gendata/heightmap.png " );
2017-10-22 08:45:05 +00:00
}
$this -> heightmap = \imagecreatefrompng ( $this -> worldpath . " gendata/heightmap.png " );
// Checking gen infos (startpoint, ...)
if ( ! file_exists ( $this -> worldpath . " gendata/geninfos.json " )) {
$data = [];
2017-10-24 22:22:50 +00:00
$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 ();
2017-10-22 08:45:05 +00:00
$data [ " startPoint " ] = [
2017-10-24 22:22:50 +00:00
$spawn -> x ,
$spawn -> y ,
$spawn -> z
2017-10-22 08:45:05 +00:00
];
2017-10-24 22:22:50 +00:00
$data = array_merge ( $data , $config );
unset ( $data [ " heightmap_name " ]);
2017-10-22 08:45:05 +00:00
file_put_contents ( $this -> worldpath . " gendata/geninfos.json " , json_encode ( $data ));
2017-10-24 22:22:50 +00:00
if ( $this -> getServer () -> getPluginManager () -> getPlugin ( " PSMCore " ) !== null ) \Ad5001\PSMCore\API :: displayNotification ( " GenPainter " , " Generating world " . $level -> getName () . " ... " );
2017-10-22 08:45:05 +00:00
}
2017-10-24 22:22:50 +00:00
// 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 );
2017-10-22 08:45:05 +00:00
}
2017-10-24 22:22:50 +00:00
// 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 ;
2017-10-22 08:45:05 +00:00
// Making selector
$this -> selector = new BiomeSelector ( $this -> random , function ( $temperature , $rainfall ){
// Checking the nearest candidate biome that have the closest $temperature and $rainfall.
2017-10-24 22:22:50 +00:00
$bestBiome = [ 405001 , Biome :: getBiome ( Biome :: OCEAN )]; // Default biome. Should be enough.
2017-10-22 08:45:05 +00:00
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 ];
}
2017-10-24 22:22:50 +00:00
return $bestBiome [ 1 ] -> getId ();
2017-10-22 08:45:05 +00:00
}, 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
2017-10-24 22:22:50 +00:00
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 ;
}
2017-10-22 08:45:05 +00:00
}
/**
* 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 ());
2017-10-24 22:22:50 +00:00
$chunk = $this -> level -> getChunk ( $chunkX , $chunkZ );
2017-10-22 08:45:05 +00:00
for ( $x = 0 ; $x < 16 ; $x ++ ) {
for ( $z = 0 ; $z < 16 ; $z ++ ) {
// Getting biome & height
$currentX = $chunkX * 16 + $x ;
2017-10-24 22:22:50 +00:00
$currentZ = $chunkZ * 16 + $z ;
2017-10-22 08:45:05 +00:00
$height = $this -> getHeightFromImg ( $currentX , $currentZ );
$biome = $this -> getBiomeFromPos ( $currentX , $currentZ );
$chunk -> setBiomeId ( $x , $z , $biome -> getId ());
// Building terrain
2017-10-24 22:22:50 +00:00
for ( $y = 0 ; $y < 256 ; ++ $y ) {
2017-10-22 08:45:05 +00:00
if ( $y === 0 ) {
$chunk -> setBlockId ( $x , $y , $z , Block :: BEDROCK );
2017-10-24 22:22:50 +00:00
} elseif ( $y <= self :: BEDROCK_MAX_HEIGHT && $this -> random -> nextBoundedInt ( 2 ) == 0 ) {
$chunk -> setBlockId ( $x , $y , $z , Block :: BEDROCK );
} elseif ( $y <= $height ) {
2017-10-22 08:45:05 +00:00
$chunk -> setBlockId ( $x , $y , $z , Block :: STONE );
2017-10-24 22:22:50 +00:00
} elseif ( $y <= self :: WATER_HEIGHT ) {
2017-10-22 08:45:05 +00:00
$chunk -> setBlockId ( $x , $y , $z , Block :: STILL_WATER );
}
}
}
}
2017-10-24 22:22:50 +00:00
foreach ( $this -> generationPopulators as $populator ){
$populator -> populate ( $this -> level , $chunkX , $chunkZ , $this -> random );
}
// Logging
echo " Land at " . ( $chunkX * 16 + 7 ) . " , " . ( $chunkZ * 16 + 7 ) .
" with biome " . $this -> getBiomeFromPos ( $chunkZ * 16 + 7 , $chunkZ * 16 + 7 ) -> getName () . " \n " ;
2017-10-22 08:45:05 +00:00
}
/**
* Populates chunks .
*
* @ param int $chunkX
* @ param int $chunkZ
* @ return void
*/
public function populateChunk ( int $chunkX , int $chunkZ ){
2017-10-24 22:22:50 +00:00
$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 );
}
2017-10-22 08:45:05 +00:00
}
/**
* Gets a biome from a pos .
*
* @ param int $x
* @ param int $z
* @ return Biome
*/
public function getBiomeFromPos ( int $x , int $z ) : Biome {
$this -> candidatesBiomes = [];
2017-10-24 22:22:50 +00:00
if ( count ( Main :: $BIOMES_BY_RANGE ) < 1 ) Main :: generateRanges ();
2017-10-22 08:45:05 +00:00
$height = $this -> getHeightFromImg ( $x , $z );
2017-10-24 22:22:50 +00:00
// 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 );
}
}
2017-10-22 08:45:05 +00:00
// 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.
2017-10-24 22:22:50 +00:00
if ( count ( $this -> candidatesBiomes ) == 1 ){
2017-10-22 08:45:05 +00:00
$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
2017-10-24 22:22:50 +00:00
$imgGetDatX = abs ( $x ) % imagesy ( $this -> heightmap );
2017-10-22 08:45:05 +00:00
// Getting width px of the world
2017-10-24 22:22:50 +00:00
$imgGetDatZ = abs ( $z ) % imagesx ( $this -> heightmap );
2017-10-22 08:45:05 +00:00
// Finally, getting the px to determine the height of the top block
2017-10-24 22:22:50 +00:00
$imgheight = imagecolorsforindex ( $this -> heightmap , imagecolorat ( $this -> heightmap , $imgGetDatZ , $imgGetDatX ))[ " red " ]; // Getting height from the red channel.
2017-10-22 08:45:05 +00:00
// 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 )];
2017-10-24 22:22:50 +00:00
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.
}
2017-10-22 08:45:05 +00:00
// Calculating smooth value
2017-10-24 22:22:50 +00:00
// $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
2017-10-22 08:45:05 +00:00
}
/**
* Return the name of the generator
*
* @ return string
*/
public function getName () : string {
2017-10-24 22:22:50 +00:00
return " worldpainter " ;
2017-10-22 08:45:05 +00:00
}
/**
* Gives the generators settings .
*
* @ return array
*/
public function getSettings () : array {
return [];
}
/**
* Returns spawn location
*
* @ return Vector3
*/
public function getSpawn () : Vector3 {
2017-10-24 22:22:50 +00:00
return new Vector3 ( $this -> spawnPoint [ 0 ],
$this -> spawnPoint [ 1 ],
$this -> spawnPoint [ 2 ]);
}
2017-10-22 08:45:05 +00:00
/**
* Returns a safe spawn location
*
* @ return Vector3
*/
public function getSafeSpawn () {
2017-10-24 22:22:50 +00:00
return new Vector3 ( $this -> spawnPoint [ 0 ],
$this -> spawnPoint [ 1 ],
$this -> spawnPoint [ 2 ]);
2017-10-22 08:45:05 +00:00
}
/*
* 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 ;
2017-10-24 22:22:50 +00:00
}
/**
* 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 )];
}
2017-10-22 08:45:05 +00:00
}