Pocketmine Generator for Earth and heightmap based generation.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

GenPainter.php 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. <?php
  2. /**
  3. * _____ _____ _ _ _____ ______
  4. * / ____| | __ \ (_) | | | __ \| ____|
  5. * | | __ ___ _ __ | |__) |_ _ _ _ __ | |_ ___ _ __| |__) | |__
  6. * | | |_ |/ _ \ '_ \| ___/ _` | | '_ \| __/ _ \ '__| ___/| __|
  7. * | |__| | __/ | | | | | (_| | | | | | || __/ | | | | |____
  8. * \_____|\___|_| |_|_| \__,_|_|_| |_|\__\___|_| |_| |______|
  9. * Pocketmine Generator for Earth and heightmap based generation.
  10. *
  11. * Copyright (C) Ad5001 2017
  12. *
  13. * @author Ad5001
  14. * @api 3.0.0
  15. * @copyright 2017 Ad5001
  16. * @license NTOSL - View LICENSE.md
  17. * @version 1.0.0
  18. * @package GenPainterPE
  19. */
  20. namespace Ad5001\GenPainterPE\generator;
  21. use pocketmine\level\generator\Generator;
  22. use pocketmine\block\Block;
  23. use pocketmine\block\BlockFactory;
  24. use pocketmine\block\CoalOre;
  25. use pocketmine\block\DiamondOre;
  26. use pocketmine\block\Dirt;
  27. use pocketmine\block\GoldOre;
  28. use pocketmine\block\Gravel;
  29. use pocketmine\block\IronOre;
  30. use pocketmine\block\LapisOre;
  31. use pocketmine\block\RedstoneOre;
  32. use pocketmine\level\ChunkManager;
  33. use pocketmine\level\generator\biome\Biome;
  34. use pocketmine\level\generator\noise\Simplex;
  35. use pocketmine\level\generator\normal\object\OreType as OreType2;
  36. use pocketmine\level\generator\object\OreType;
  37. use pocketmine\level\generator\populator\GroundCover;
  38. use pocketmine\level\generator\populator\Ore;
  39. use pocketmine\level\generator\populator\Populator;
  40. use pocketmine\level\Level;
  41. use pocketmine\math\Vector3;
  42. use pocketmine\utils\Random;
  43. use Ad5001\GenPainterPE\Main;
  44. use Ad5001\GenPainterPE\populator\CavePopulator;
  45. use Ad5001\GenPainterPE\populator\RavinePopulator;
  46. use Ad5001\GenPainterPE\utils\BiomeSelector;
  47. class GenPainter extends Generator{
  48. /** Both two here tells the max and min of water. */
  49. const WATER_HEIGHT = 100;
  50. const MIN_HEIGHT = 60;
  51. /** Maximum height for bedrock */
  52. const BEDROCK_MAX_HEIGHT = 5;
  53. /** Converts the original picture height to a minecraft height */
  54. const DEPTH_MULTIPLICATOR = 0.2;
  55. /**
  56. * @var array
  57. * Coordinates of the start point
  58. */
  59. protected $startPoint = [];
  60. /** @var resource - Heightmap image resource */
  61. protected $heightmap;
  62. /** @var ChunkManager */
  63. protected $level;
  64. /** @var Random */
  65. protected $random;
  66. /** @var BiomeSelector */
  67. protected $selector;
  68. /** @var int[] */
  69. protected static $cachedHeights = [];
  70. /** @var Biome[] - Biomes that are candidates to be choosen to be generated */
  71. protected $candidatesBiomes = [];
  72. /** @var Populator[] */
  73. private $generationPopulators = [];
  74. /** @var Populator[] */
  75. private $populators = [];
  76. /**
  77. * Constructs the class
  78. *
  79. * @param array $options
  80. */
  81. public function __construct($options = []){
  82. $this->genid = Main::$GENERATOR_IDS++;
  83. }
  84. /**
  85. * Inits the class for the variables. mw create Test 902874635 worldpainter
  86. * Executed on the main thread.
  87. * $level is actually a level instance.
  88. * @param ChunkManager $level
  89. * @param Random $random
  90. * @return void
  91. */
  92. public function init(ChunkManager $level, Random $random) {
  93. $this->level = $level;
  94. $this->random = $random;
  95. $this->random->setSeed($this->level->getSeed());
  96. if($level instanceof Level &&
  97. isset($this->genid)) { // First init in main thread
  98. $this->worldpath = getcwd() . "/worlds/" . $level->getName() . "/";
  99. // Initing folder data
  100. @mkdir($this->worldpath . "gendata");
  101. $config = yaml_parse(file_get_contents(getcwd() . "/plugins/GenPainterPE/config.yml"));
  102. // Checking heightmap
  103. if(!file_exists($this->worldpath . "gendata/heightmap.png")) {
  104. copy(getcwd() . "/plugins/GenPainterPE/heightmaps/" . $config["heightmap_name"] . ".png", $this->worldpath . "gendata/heightmap.png");
  105. }
  106. $this->heightmap = \imagecreatefrompng($this->worldpath . "gendata/heightmap.png");
  107. // Checking gen infos (startpoint, ...)
  108. if(!file_exists($this->worldpath . "gendata/geninfos.json")) {
  109. $data = [];
  110. $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.
  111. $spawn = $this->getSpawnsFromImg();
  112. $data["startPoint"] = [
  113. $spawn->x,
  114. $spawn->y,
  115. $spawn->z
  116. ];
  117. $data = array_merge($data, $config);
  118. unset($data["heightmap_name"]);
  119. file_put_contents($this->worldpath . "gendata/geninfos.json", json_encode($data));
  120. }
  121. // Adding symlink to get path later.
  122. if(file_exists(getcwd() . "/plugins/GenPainterPE/tmp/" . $this->genid)) unlink(getcwd() . "/plugins/GenPainterPE/tmp/" . $this->genid);
  123. symlink($this->worldpath, getcwd() . "/plugins/GenPainterPE/tmp/" . $this->genid);
  124. }
  125. // Regetting the vars from the symlinked folder
  126. $this->worldpath = readlink(getcwd() . "/plugins/GenPainterPE/tmp/" . $this->genid);
  127. $this->heightmap = \imagecreatefrompng($this->worldpath . "gendata/heightmap.png");
  128. $this->options = json_decode(file_get_contents($this->worldpath . "gendata/geninfos.json"));
  129. $this->startPoint = $this->options->startPoint;
  130. // Making selector
  131. $this->selector = new BiomeSelector($this->random, function($temperature, $rainfall){
  132. // Checking the nearest candidate biome that have the closest $temperature and $rainfall.
  133. $bestBiome = [405001, Biome::getBiome(Biome::OCEAN)]; // Default biome. Should be enough.
  134. foreach($this->candidatesBiomes as $b){
  135. $diffRainFall = abs($b->getRainfall() - $rainfall);
  136. $diffTemperature = abs($b->getTemperature() - $temperature);
  137. $diff = $diffRainFall + $diffTemperature; // Total diff.
  138. if($diff < $bestBiome[0]) $bestBiome = [$diff, $b];
  139. }
  140. return $bestBiome[1]->getId();
  141. }, Biome::getBiome(Biome::OCEAN));
  142. $this->selector->addBiome(Biome::getBiome(Biome::OCEAN));
  143. $this->selector->addBiome(Biome::getBiome(Biome::PLAINS));
  144. $this->selector->addBiome(Biome::getBiome(Biome::DESERT));
  145. $this->selector->addBiome(Biome::getBiome(Biome::MOUNTAINS));
  146. $this->selector->addBiome(Biome::getBiome(Biome::FOREST));
  147. $this->selector->addBiome(Biome::getBiome(Biome::TAIGA));
  148. $this->selector->addBiome(Biome::getBiome(Biome::SWAMP));
  149. $this->selector->addBiome(Biome::getBiome(Biome::RIVER));
  150. $this->selector->addBiome(Biome::getBiome(Biome::ICE_PLAINS));
  151. $this->selector->addBiome(Biome::getBiome(Biome::SMALL_MOUNTAINS));
  152. $this->selector->addBiome(Biome::getBiome(Biome::BIRCH_FOREST));
  153. // Populators
  154. if($this->options->generate_custom_ground){
  155. $cover = new GroundCover();
  156. $this->generationPopulators[] = $cover;
  157. }
  158. if($this->options->generate_caves){
  159. $cave = new CavePopulator ();
  160. $cave->setBaseAmount(0);
  161. $cave->setRandomAmount(2);
  162. $this->generationPopulators[] = $cave;
  163. $ravine = new RavinePopulator ();
  164. $ravine->setBaseAmount(0);
  165. $ravine->setRandomAmount(51);
  166. $this->generationPopulators[] = $ravine;
  167. }
  168. if($this->options->generate_ores) {
  169. $ores = new Ore();
  170. $ores->setOreTypes([
  171. new OreType(BlockFactory::get(Block::COAL_ORE), 20, 16, 0, 128),
  172. new OreType(BlockFactory::get(Block::IRON_ORE), 20, 8, 0, 64),
  173. new OreType(BlockFactory::get(Block::REDSTONE_ORE), 8, 7, 0, 16),
  174. new OreType(BlockFactory::get(Block::LAPIS_ORE), 1, 6, 0, 32),
  175. new OreType(BlockFactory::get(Block::GOLD_ORE), 2, 8, 0, 32),
  176. new OreType(BlockFactory::get(Block::DIAMOND_ORE), 1, 7, 0, 16),
  177. new OreType(BlockFactory::get(Block::DIRT), 20, 32, 0, 128),
  178. new OreType(BlockFactory::get(Block::GRAVEL), 10, 16, 0, 128)
  179. ]);
  180. $this->generationPopulators[] = $ores;
  181. }
  182. }
  183. /**
  184. * Generates a chunk
  185. *
  186. * @param int $chunkX
  187. * @param int $chunkZ
  188. * @return void
  189. */
  190. public function generateChunk(int $chunkX, int $chunkZ){
  191. $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed());
  192. $chunk = $this->level->getChunk($chunkX, $chunkZ);
  193. for($x = 0; $x < 16; $x++) {
  194. for($z = 0; $z < 16; $z++) {
  195. // Getting biome & height
  196. $currentX = $chunkX * 16 + $x;
  197. $currentZ = $chunkZ * 16 + $z;
  198. $height = $this->getHeightFromImg($currentX, $currentZ);
  199. $biome = $this->getBiomeFromPos($currentX, $currentZ);
  200. $chunk->setBiomeId($x, $z, $biome->getId());
  201. // Building terrain
  202. for($y = 0; $y < 256; ++$y) {
  203. if($y === 0) {
  204. $chunk->setBlockId($x, $y, $z, Block::BEDROCK);
  205. } elseif($y <= self::BEDROCK_MAX_HEIGHT && $this->random->nextBoundedInt(2) == 0) {
  206. $chunk->setBlockId($x, $y, $z, Block::BEDROCK);
  207. } elseif($y <= $height) {
  208. $chunk->setBlockId($x, $y, $z, Block::STONE);
  209. } elseif($y <= self::WATER_HEIGHT) {
  210. $chunk->setBlockId($x, $y, $z, Block::STILL_WATER);
  211. }
  212. }
  213. }
  214. }
  215. foreach($this->generationPopulators as $populator){
  216. $populator->populate($this->level, $chunkX, $chunkZ, $this->random);
  217. }
  218. }
  219. /**
  220. * Populates chunks.
  221. *
  222. * @param int $chunkX
  223. * @param int $chunkZ
  224. * @return void
  225. */
  226. public function populateChunk(int $chunkX, int $chunkZ){
  227. $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed());
  228. // Check if the generator should generate structures.
  229. if($this->options->generate_structures) {
  230. foreach($this->populators as $populator){
  231. $populator->populate($this->level, $chunkX, $chunkZ, $this->random);
  232. }
  233. $chunk = $this->level->getChunk($chunkX, $chunkZ);
  234. $biome = Biome::getBiome($chunk->getBiomeId(7, 7));
  235. $biome->populateChunk($this->level, $chunkX, $chunkZ, $this->random);
  236. }
  237. }
  238. /**
  239. * Gets a biome from a pos.
  240. *
  241. * @param int $x
  242. * @param int $z
  243. * @return Biome
  244. */
  245. public function getBiomeFromPos(int $x, int $z): Biome{
  246. $this->candidatesBiomes = [];
  247. if(count(Main::$BIOMES_BY_RANGE) < 1) Main::generateRanges();
  248. $height = $this->getHeightFromImg($x, $z);
  249. // Check if the generator should generate biomes
  250. if(!$this->options->generate_biomes) {
  251. if($height > self::WATER_HEIGHT) {
  252. return Biome::get(Biome::PLAINS);
  253. } else {
  254. return Biome::get(Biome::OCEAN);
  255. }
  256. }
  257. // Foreaching all biomes to see which ones could be generated
  258. foreach(Main::$BIOMES_BY_RANGE as $biomeId => $range){
  259. if($range->isInRange($height)) $this->candidatesBiomes[] = Biome::getBiome($biomeId);
  260. }
  261. // Checking wether there are multiple candidates or not.
  262. // If so, choose a biome.
  263. if(count($this->candidatesBiomes) == 1){
  264. $biome = $this->candidatesBiomes[0];
  265. } else {
  266. $hash = $x * 2345803 ^ $z * 9236449 ^ $this->level->getSeed();
  267. $hash *= $hash + 223;
  268. $xNoise = $hash >> 20 & 3;
  269. $zNoise = $hash >> 22 & 3;
  270. if($xNoise == 3){
  271. $xNoise = 1;
  272. }
  273. if($zNoise == 3){
  274. $zNoise = 1;
  275. }
  276. $biome = $this->selector->pickBiome($x + $xNoise - 1, $z + $zNoise - 1);
  277. }
  278. return $biome;
  279. }
  280. /**
  281. * Returns a block height based on heightmap.
  282. *
  283. * @param int $x
  284. * @param int $z
  285. * @return int
  286. */
  287. public function getHeightFromImg(int $x, int $z): int{
  288. if(isset(self::$cachedHeights[$x . ";" . $z])) return round(self::$cachedHeights[$x . ";" . $z]);
  289. // Getting height px of the world
  290. $imgGetDatX = abs($x) % imagesy($this->heightmap);
  291. // Getting width px of the world
  292. $imgGetDatZ = abs($z) % imagesx($this->heightmap);
  293. // Finally, getting the px to determine the height of the top block
  294. $imgheight = imagecolorsforindex($this->heightmap, imagecolorat($this->heightmap, $imgGetDatZ, $imgGetDatX))["red"]; // Getting height from the red channel.
  295. // In a normal heightmap, all the chanel ouputs the same (exepct alpha)
  296. // Smoothing out.
  297. $surroundValues = [];
  298. // Getting surround values
  299. if(isset(self::$cachedHeights[($x+1) . ";" . ($z+1)])) $surroundValues[] = self::$cachedHeights[($x+1) . ";" . ($z+1)];
  300. if(isset(self::$cachedHeights[($x+1) . ";" . ($z)])) $surroundValues[] = self::$cachedHeights[($x+1) . ";" . ($z)];
  301. if(isset(self::$cachedHeights[($x+1) . ";" . ($z-1)])) $surroundValues[] = self::$cachedHeights[($x+1) . ";" . ($z-1)];
  302. if(isset(self::$cachedHeights[($x) . ";" . ($z+1)])) $surroundValues[] = self::$cachedHeights[($x) . ";" . ($z+1)];
  303. if(isset(self::$cachedHeights[($x) . ";" . ($z-1)])) $surroundValues[] = self::$cachedHeights[($x) . ";" . ($z-1)];
  304. if(isset(self::$cachedHeights[($x-1) . ";" . ($z+1)])) $surroundValues[] = self::$cachedHeights[($x-1) . ";" . ($z+1)];
  305. if(isset(self::$cachedHeights[($x-1) . ";" . ($z)])) $surroundValues[] = self::$cachedHeights[($x-1) . ";" . ($z)];
  306. if(isset(self::$cachedHeights[($x-1) . ";" . ($z-1)])) $surroundValues[] = self::$cachedHeights[($x-1) . ";" . ($z-1)];
  307. $imgValue = $imgheight * self::DEPTH_MULTIPLICATOR + self::WATER_HEIGHT;
  308. if($imgheight == 0) {
  309. // Calculating presmooth value (to generate $imgheight water value alot smoother and going deeper)
  310. if(count($surroundValues) !== 0) {
  311. $preSmoothValue = 0;
  312. foreach($surroundValues as $v) $preSmoothValue += $v;
  313. $preSmoothValue /= count($surroundValues);
  314. } else {
  315. $preSmoothValue = 100;
  316. }
  317. $calcDiffValue = ($preSmoothValue - 100) / 2;
  318. if(round($calcDiffValue) == -3) $calcDiffValue--;
  319. $imgValue += -$this->random->nextBoundedInt(3 + round($calcDiffValue)) - 3 + $calcDiffValue;// Used to make water depth.
  320. }
  321. // Calculating smooth value
  322. // $smoothValue = -1;
  323. // foreach($surroundValues as $v) $smoothValue += $v;
  324. // $smoothValue += $imgValue * 3;
  325. // $smoothValue /= count($surroundValues) + 3;
  326. if($imgValue < self::MIN_HEIGHT) $imgValue = self::MIN_HEIGHT;
  327. self::$cachedHeights[$x . ";" . $z] = $imgValue;
  328. return round($imgValue); // Rounding it so that we can use it as a block height
  329. }
  330. /**
  331. * Return the name of the generator
  332. *
  333. * @return string
  334. */
  335. public function getName(): string {
  336. return "worldpainter";
  337. }
  338. /**
  339. * Gives the generators settings.
  340. *
  341. * @return array
  342. */
  343. public function getSettings(): array {
  344. return [];
  345. }
  346. /**
  347. * Returns spawn location
  348. *
  349. * @return Vector3
  350. */
  351. public function getSpawn(): Vector3 {
  352. return new Vector3($this->spawnPoint[0],
  353. $this->spawnPoint[1],
  354. $this->spawnPoint[2]);
  355. }
  356. /**
  357. * Returns a safe spawn location
  358. *
  359. * @return Vector3
  360. */
  361. public function getSafeSpawn() {
  362. return new Vector3($this->spawnPoint[0],
  363. $this->spawnPoint[1],
  364. $this->spawnPoint[2]);
  365. }
  366. /*
  367. * Gets the top block (y) on an x and z axes
  368. * @param $x int
  369. * @param $z int
  370. */
  371. protected function getHighestWorkableBlock($x, $z) {
  372. for($y = Level::Y_MAX - 1; $y > 0; -- $y) {
  373. $b = $this->level->getBlockIdAt($x, $y, $z);
  374. if ($b === Block::DIRT or $b === Block::GRASS or $b === Block::PODZOL) {
  375. break;
  376. } elseif ($b !== 0 and $b !== Block::SNOW_LAYER) {
  377. return - 1;
  378. }
  379. }
  380. return ++$y;
  381. }
  382. /**
  383. * Checks the image for a safes spawns (not in water) then saves it.
  384. *
  385. * @return void
  386. */
  387. public function getSpawnsFromImg(): Vector3{
  388. $spawn = new Vector3(128, 128, 128);
  389. $found = [];
  390. mt_srand($this->random->getSeed());
  391. for($i = 0; $i < 1028; $i++){ // Checking 1028 spots to check for a spawn. If none are found, default to 128 128 128.
  392. $x = mt_rand(0, imagesy($this->heightmap) - 1);
  393. $z = mt_rand(0, imagesx($this->heightmap) - 1);
  394. $imgheight = imagecolorsforindex($this->heightmap, imagecolorat($this->heightmap, $z, $x))["red"];
  395. if($imgheight !== 0){
  396. $imgValue = $imgheight * self::DEPTH_MULTIPLICATOR + self::WATER_HEIGHT + 2; // +2 is here so that the player will not get stuck in a block.
  397. $found[] = new Vector3($x, round($imgValue), $z);
  398. }
  399. }
  400. if(count($found) == 0) return $spawn;
  401. return $found[mt_rand(0, count($found) - 1)];
  402. }
  403. }