GenPainterPE/src/Ad5001/RealWorld/generator/RealWorld.php

342 lines
13 KiB
PHP

<?php
namespace Ad5001\RealWorld\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\biome\BiomeSelector;
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\RealWorld\Main;
use Ad5001\RealWorld\populator\CavePopulator;
use Ad5001\RealWorld\populator\RavinePopulator;
class RealWorld extends Generator{
/** Both two here tells the max and min of water. */
const WATER_HEIGHT = 100;
const MIN_HEIGHT = 60;
/** Converts the original picture height to a minecraft height */
const DEPTH_MULTIPLICATOR = 0.6;
/**
* @var array
* Coordinates of the start point
*/
protected $startPoint = [];
/** @var resource - Heightmap image resource */
protected $heightmap;
/** @var string */
protected $worldpath;
/** @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 = []){}
/**
* Inits the class for the variables. mw create Test 902874635 realworld
* 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) { // 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;
}
}