mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-06 09:56:06 +00:00
Removed pocketmine subdirectory, map PSR-4 style
This commit is contained in:
186
src/world/generator/Flat.php
Normal file
186
src/world/generator/Flat.php
Normal file
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator;
|
||||
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\world\ChunkManager;
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\generator\object\OreType;
|
||||
use pocketmine\world\generator\populator\Ore;
|
||||
use pocketmine\world\generator\populator\Populator;
|
||||
use function array_map;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function preg_match;
|
||||
use function preg_match_all;
|
||||
|
||||
class Flat extends Generator{
|
||||
/** @var Chunk */
|
||||
private $chunk;
|
||||
/** @var Populator[] */
|
||||
private $populators = [];
|
||||
/** @var int[] */
|
||||
private $structure;
|
||||
/** @var int */
|
||||
private $floorLevel;
|
||||
/** @var int */
|
||||
private $biome;
|
||||
/** @var string */
|
||||
private $preset;
|
||||
|
||||
/**
|
||||
* @param ChunkManager $world
|
||||
* @param int $seed
|
||||
* @param array $options
|
||||
*
|
||||
* @throws InvalidGeneratorOptionsException
|
||||
*/
|
||||
public function __construct(ChunkManager $world, int $seed, array $options = []){
|
||||
parent::__construct($world, $seed, $options);
|
||||
|
||||
if(isset($this->options["preset"]) and $this->options["preset"] != ""){
|
||||
$this->preset = $this->options["preset"];
|
||||
}else{
|
||||
$this->preset = "2;7,2x3,2;1;";
|
||||
//$this->preset = "2;7,59x1,3x3,2;1;spawn(radius=10 block=89),decoration(treecount=80 grasscount=45)";
|
||||
}
|
||||
|
||||
$this->parsePreset();
|
||||
|
||||
if(isset($this->options["decoration"])){
|
||||
$ores = new Ore();
|
||||
$ores->setOreTypes([
|
||||
new OreType(VanillaBlocks::COAL_ORE(), 20, 16, 0, 128),
|
||||
new OreType(VanillaBlocks::IRON_ORE(), 20, 8, 0, 64),
|
||||
new OreType(VanillaBlocks::REDSTONE_ORE(), 8, 7, 0, 16),
|
||||
new OreType(VanillaBlocks::LAPIS_LAZULI_ORE(), 1, 6, 0, 32),
|
||||
new OreType(VanillaBlocks::GOLD_ORE(), 2, 8, 0, 32),
|
||||
new OreType(VanillaBlocks::DIAMOND_ORE(), 1, 7, 0, 16),
|
||||
new OreType(VanillaBlocks::DIRT(), 20, 32, 0, 128),
|
||||
new OreType(VanillaBlocks::GRAVEL(), 10, 16, 0, 128)
|
||||
]);
|
||||
$this->populators[] = $ores;
|
||||
}
|
||||
|
||||
$this->generateBaseChunk();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $layers
|
||||
*
|
||||
* @return int[]
|
||||
* @throws InvalidGeneratorOptionsException
|
||||
*/
|
||||
public static function parseLayers(string $layers) : array{
|
||||
$result = [];
|
||||
$split = array_map('\trim', explode(',', $layers));
|
||||
$y = 0;
|
||||
foreach($split as $line){
|
||||
preg_match('#^(?:(\d+)[x|*])?(.+)$#', $line, $matches);
|
||||
if(count($matches) !== 3){
|
||||
throw new InvalidGeneratorOptionsException("Invalid preset layer \"$line\"");
|
||||
}
|
||||
|
||||
$cnt = $matches[1] !== "" ? (int) $matches[1] : 1;
|
||||
try{
|
||||
$b = ItemFactory::fromString($matches[2])->getBlock();
|
||||
}catch(\InvalidArgumentException $e){
|
||||
throw new InvalidGeneratorOptionsException("Invalid preset layer \"$line\": " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
for($cY = $y, $y += $cnt; $cY < $y; ++$cY){
|
||||
$result[$cY] = $b->getFullId();
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function parsePreset() : void{
|
||||
$preset = explode(";", $this->preset);
|
||||
$blocks = (string) ($preset[1] ?? "");
|
||||
$this->biome = (int) ($preset[2] ?? 1);
|
||||
$options = (string) ($preset[3] ?? "");
|
||||
$this->structure = self::parseLayers($blocks);
|
||||
|
||||
$this->floorLevel = count($this->structure);
|
||||
|
||||
//TODO: more error checking
|
||||
preg_match_all('#(([0-9a-z_]{1,})\(?([0-9a-z_ =:]{0,})\)?),?#', $options, $matches);
|
||||
foreach($matches[2] as $i => $option){
|
||||
$params = true;
|
||||
if($matches[3][$i] !== ""){
|
||||
$params = [];
|
||||
$p = explode(" ", $matches[3][$i]);
|
||||
foreach($p as $k){
|
||||
$k = explode("=", $k);
|
||||
if(isset($k[1])){
|
||||
$params[$k[0]] = $k[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->options[$option] = $params;
|
||||
}
|
||||
}
|
||||
|
||||
protected function generateBaseChunk() : void{
|
||||
$this->chunk = new Chunk(0, 0);
|
||||
$this->chunk->setGenerated();
|
||||
|
||||
for($Z = 0; $Z < 16; ++$Z){
|
||||
for($X = 0; $X < 16; ++$X){
|
||||
$this->chunk->setBiomeId($X, $Z, $this->biome);
|
||||
}
|
||||
}
|
||||
|
||||
$count = count($this->structure);
|
||||
for($sy = 0; $sy < $count; $sy += 16){
|
||||
$subchunk = $this->chunk->getSubChunk($sy >> 4, true);
|
||||
for($y = 0; $y < 16 and isset($this->structure[$y | $sy]); ++$y){
|
||||
$id = $this->structure[$y | $sy];
|
||||
|
||||
for($Z = 0; $Z < 16; ++$Z){
|
||||
for($X = 0; $X < 16; ++$X){
|
||||
$subchunk->setFullBlock($X, $y, $Z, $id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function generateChunk(int $chunkX, int $chunkZ) : void{
|
||||
$chunk = clone $this->chunk;
|
||||
$chunk->setX($chunkX);
|
||||
$chunk->setZ($chunkZ);
|
||||
$this->world->setChunk($chunkX, $chunkZ, $chunk);
|
||||
}
|
||||
|
||||
public function populateChunk(int $chunkX, int $chunkZ) : void{
|
||||
$this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->seed);
|
||||
foreach($this->populators as $populator){
|
||||
$populator->populate($this->world, $chunkX, $chunkZ, $this->random);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
82
src/world/generator/Generator.php
Normal file
82
src/world/generator/Generator.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Noise classes used in world generation
|
||||
*/
|
||||
namespace pocketmine\world\generator;
|
||||
|
||||
use pocketmine\utils\Random;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\ChunkManager;
|
||||
use function preg_match;
|
||||
|
||||
abstract class Generator{
|
||||
|
||||
/**
|
||||
* Converts a string world seed into an integer for use by the generator.
|
||||
*
|
||||
* @param string $seed
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public static function convertSeed(string $seed) : ?int{
|
||||
if($seed === ""){ //empty seed should cause a random seed to be selected - can't use 0 here because 0 is a valid seed
|
||||
$convertedSeed = null;
|
||||
}elseif(preg_match('/^-?\d+$/', $seed) === 1){ //this avoids treating seeds like "404.4" as integer seeds
|
||||
$convertedSeed = (int) $seed;
|
||||
}else{
|
||||
$convertedSeed = Utils::javaStringHash($seed);
|
||||
}
|
||||
|
||||
return $convertedSeed;
|
||||
}
|
||||
|
||||
/** @var ChunkManager */
|
||||
protected $world;
|
||||
/** @var int */
|
||||
protected $seed;
|
||||
/** @var array */
|
||||
protected $options;
|
||||
|
||||
/** @var Random */
|
||||
protected $random;
|
||||
|
||||
/**
|
||||
* @param ChunkManager $world
|
||||
* @param int $seed
|
||||
* @param array $options
|
||||
*
|
||||
* @throws InvalidGeneratorOptionsException
|
||||
*/
|
||||
public function __construct(ChunkManager $world, int $seed, array $options = []){
|
||||
$this->world = $world;
|
||||
$this->seed = $seed;
|
||||
$this->options = $options;
|
||||
$this->random = new Random($seed);
|
||||
}
|
||||
|
||||
abstract public function generateChunk(int $chunkX, int $chunkZ) : void;
|
||||
|
||||
abstract public function populateChunk(int $chunkX, int $chunkZ) : void;
|
||||
}
|
38
src/world/generator/GeneratorChunkManager.php
Normal file
38
src/world/generator/GeneratorChunkManager.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator;
|
||||
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\SimpleChunkManager;
|
||||
use pocketmine\world\World;
|
||||
|
||||
class GeneratorChunkManager extends SimpleChunkManager{
|
||||
|
||||
public function getChunk(int $chunkX, int $chunkZ, bool $create = false) : ?Chunk{
|
||||
if(!isset($this->chunks[World::chunkHash($chunkX, $chunkZ)])){
|
||||
throw new \InvalidArgumentException("Chunk does not exist");
|
||||
}
|
||||
return parent::getChunk($chunkX, $chunkZ, $create);
|
||||
}
|
||||
}
|
115
src/world/generator/GeneratorManager.php
Normal file
115
src/world/generator/GeneratorManager.php
Normal file
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator;
|
||||
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\generator\hell\Nether;
|
||||
use pocketmine\world\generator\normal\Normal;
|
||||
use function array_keys;
|
||||
use function strtolower;
|
||||
|
||||
final class GeneratorManager{
|
||||
/** @var string[] name => classname mapping */
|
||||
private static $list = [];
|
||||
|
||||
/**
|
||||
* Registers the default known generators.
|
||||
*/
|
||||
public static function registerDefaultGenerators() : void{
|
||||
self::addGenerator(Flat::class, "flat");
|
||||
self::addGenerator(Normal::class, "normal");
|
||||
self::addGenerator(Normal::class, "default");
|
||||
self::addGenerator(Nether::class, "hell");
|
||||
self::addGenerator(Nether::class, "nether");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class Fully qualified name of class that extends \pocketmine\world\generator\Generator
|
||||
* @param string $name Alias for this generator type that can be written in configs
|
||||
* @param bool $overwrite Whether to force overwriting any existing registered generator with the same name
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function addGenerator(string $class, string $name, bool $overwrite = false) : void{
|
||||
Utils::testValidInstance($class, Generator::class);
|
||||
|
||||
if(!$overwrite and isset(self::$list[$name = strtolower($name)])){
|
||||
throw new \InvalidArgumentException("Alias \"$name\" is already assigned");
|
||||
}
|
||||
|
||||
self::$list[$name] = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of names for registered generators.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getGeneratorList() : array{
|
||||
return array_keys(self::$list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a class name of a registered Generator matching the given name.
|
||||
*
|
||||
* @param string $name
|
||||
* @param bool $throwOnMissing @deprecated this is for backwards compatibility only
|
||||
*
|
||||
* @return string|Generator Name of class that extends Generator (not an actual Generator object)
|
||||
* @throws \InvalidArgumentException if the generator type isn't registered
|
||||
*/
|
||||
public static function getGenerator(string $name, bool $throwOnMissing = false){
|
||||
if(isset(self::$list[$name = strtolower($name)])){
|
||||
return self::$list[$name];
|
||||
}
|
||||
|
||||
if($throwOnMissing){
|
||||
throw new \InvalidArgumentException("Alias \"$name\" does not map to any known generator");
|
||||
}
|
||||
return Normal::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the registered name of the given Generator class.
|
||||
*
|
||||
* @param string $class Fully qualified name of class that extends \pocketmine\world\generator\Generator
|
||||
*
|
||||
* @return string
|
||||
* @throws \InvalidArgumentException if the class type cannot be matched to a known alias
|
||||
*/
|
||||
public static function getGeneratorName(string $class) : string{
|
||||
Utils::testValidInstance($class, Generator::class);
|
||||
foreach(self::$list as $name => $c){
|
||||
if($c === $class){
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("Generator class $class is not registered");
|
||||
}
|
||||
|
||||
private function __construct(){
|
||||
//NOOP
|
||||
}
|
||||
}
|
62
src/world/generator/GeneratorRegisterTask.php
Normal file
62
src/world/generator/GeneratorRegisterTask.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator;
|
||||
|
||||
use pocketmine\block\BlockFactory;
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\world\biome\Biome;
|
||||
use pocketmine\world\World;
|
||||
use function serialize;
|
||||
use function unserialize;
|
||||
|
||||
class GeneratorRegisterTask extends AsyncTask{
|
||||
|
||||
public $generatorClass;
|
||||
public $settings;
|
||||
public $seed;
|
||||
public $worldId;
|
||||
public $worldHeight = World::Y_MAX;
|
||||
|
||||
public function __construct(World $world, string $generatorClass, array $generatorSettings = []){
|
||||
$this->generatorClass = $generatorClass;
|
||||
$this->settings = serialize($generatorSettings);
|
||||
$this->seed = $world->getSeed();
|
||||
$this->worldId = $world->getId();
|
||||
$this->worldHeight = $world->getWorldHeight();
|
||||
}
|
||||
|
||||
public function onRun() : void{
|
||||
BlockFactory::init();
|
||||
Biome::init();
|
||||
$manager = new GeneratorChunkManager($this->worldHeight);
|
||||
$this->worker->saveToThreadStore("generation.world{$this->worldId}.manager", $manager);
|
||||
|
||||
/**
|
||||
* @var Generator $generator
|
||||
* @see Generator::__construct()
|
||||
*/
|
||||
$generator = new $this->generatorClass($manager, $this->seed, unserialize($this->settings));
|
||||
$this->worker->saveToThreadStore("generation.world{$this->worldId}.generator", $generator);
|
||||
}
|
||||
}
|
41
src/world/generator/GeneratorUnregisterTask.php
Normal file
41
src/world/generator/GeneratorUnregisterTask.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator;
|
||||
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\world\World;
|
||||
|
||||
class GeneratorUnregisterTask extends AsyncTask{
|
||||
|
||||
public $worldId;
|
||||
|
||||
public function __construct(World $world){
|
||||
$this->worldId = $world->getId();
|
||||
}
|
||||
|
||||
public function onRun() : void{
|
||||
$this->worker->removeFromThreadStore("generation.world{$this->worldId}.manager");
|
||||
$this->worker->removeFromThreadStore("generation.world{$this->worldId}.generator");
|
||||
}
|
||||
}
|
28
src/world/generator/InvalidGeneratorOptionsException.php
Normal file
28
src/world/generator/InvalidGeneratorOptionsException.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator;
|
||||
|
||||
class InvalidGeneratorOptionsException extends \UnexpectedValueException{
|
||||
|
||||
}
|
165
src/world/generator/PopulationTask.php
Normal file
165
src/world/generator/PopulationTask.php
Normal file
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator;
|
||||
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\format\io\FastChunkSerializer;
|
||||
use pocketmine\world\SimpleChunkManager;
|
||||
use pocketmine\world\World;
|
||||
|
||||
class PopulationTask extends AsyncTask{
|
||||
private const TLS_KEY_WORLD = "world";
|
||||
|
||||
public $state;
|
||||
public $worldId;
|
||||
public $chunk;
|
||||
|
||||
public $chunk0;
|
||||
public $chunk1;
|
||||
public $chunk2;
|
||||
public $chunk3;
|
||||
//center chunk
|
||||
public $chunk5;
|
||||
public $chunk6;
|
||||
public $chunk7;
|
||||
public $chunk8;
|
||||
|
||||
public function __construct(World $world, Chunk $chunk){
|
||||
$this->state = true;
|
||||
$this->worldId = $world->getId();
|
||||
$this->chunk = FastChunkSerializer::serialize($chunk);
|
||||
|
||||
foreach($world->getAdjacentChunks($chunk->getX(), $chunk->getZ()) as $i => $c){
|
||||
$this->{"chunk$i"} = $c !== null ? FastChunkSerializer::serialize($c) : null;
|
||||
}
|
||||
|
||||
$this->storeLocal(self::TLS_KEY_WORLD, $world);
|
||||
}
|
||||
|
||||
public function onRun() : void{
|
||||
/** @var SimpleChunkManager $manager */
|
||||
$manager = $this->worker->getFromThreadStore("generation.world{$this->worldId}.manager");
|
||||
/** @var Generator $generator */
|
||||
$generator = $this->worker->getFromThreadStore("generation.world{$this->worldId}.generator");
|
||||
if($manager === null or $generator === null){
|
||||
$this->state = false;
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Chunk[] $chunks */
|
||||
$chunks = [];
|
||||
|
||||
$chunk = FastChunkSerializer::deserialize($this->chunk);
|
||||
|
||||
for($i = 0; $i < 9; ++$i){
|
||||
if($i === 4){
|
||||
continue;
|
||||
}
|
||||
$xx = -1 + $i % 3;
|
||||
$zz = -1 + (int) ($i / 3);
|
||||
$ck = $this->{"chunk$i"};
|
||||
if($ck === null){
|
||||
$chunks[$i] = new Chunk($chunk->getX() + $xx, $chunk->getZ() + $zz);
|
||||
}else{
|
||||
$chunks[$i] = FastChunkSerializer::deserialize($ck);
|
||||
}
|
||||
}
|
||||
|
||||
$manager->setChunk($chunk->getX(), $chunk->getZ(), $chunk);
|
||||
if(!$chunk->isGenerated()){
|
||||
$generator->generateChunk($chunk->getX(), $chunk->getZ());
|
||||
$chunk->setGenerated();
|
||||
}
|
||||
|
||||
foreach($chunks as $c){
|
||||
if($c !== null){
|
||||
$manager->setChunk($c->getX(), $c->getZ(), $c);
|
||||
if(!$c->isGenerated()){
|
||||
$generator->generateChunk($c->getX(), $c->getZ());
|
||||
$c = $manager->getChunk($c->getX(), $c->getZ());
|
||||
$c->setGenerated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$generator->populateChunk($chunk->getX(), $chunk->getZ());
|
||||
|
||||
$chunk = $manager->getChunk($chunk->getX(), $chunk->getZ());
|
||||
$chunk->recalculateHeightMap();
|
||||
$chunk->populateSkyLight();
|
||||
$chunk->setLightPopulated();
|
||||
$chunk->setPopulated();
|
||||
$this->chunk = FastChunkSerializer::serialize($chunk);
|
||||
|
||||
$manager->setChunk($chunk->getX(), $chunk->getZ(), null);
|
||||
|
||||
foreach($chunks as $i => $c){
|
||||
if($c !== null){
|
||||
$c = $chunks[$i] = $manager->getChunk($c->getX(), $c->getZ());
|
||||
if(!$c->hasChanged()){
|
||||
$chunks[$i] = null;
|
||||
}
|
||||
}else{
|
||||
//This way non-changed chunks are not set
|
||||
$chunks[$i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
$manager->cleanChunks();
|
||||
|
||||
for($i = 0; $i < 9; ++$i){
|
||||
if($i === 4){
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->{"chunk$i"} = $chunks[$i] !== null ? FastChunkSerializer::serialize($chunks[$i]) : null;
|
||||
}
|
||||
}
|
||||
|
||||
public function onCompletion() : void{
|
||||
/** @var World $world */
|
||||
$world = $this->fetchLocal(self::TLS_KEY_WORLD);
|
||||
if(!$world->isClosed()){
|
||||
if(!$this->state){
|
||||
$world->registerGeneratorToWorker($this->worker->getAsyncWorkerId());
|
||||
}
|
||||
|
||||
$chunk = FastChunkSerializer::deserialize($this->chunk);
|
||||
|
||||
for($i = 0; $i < 9; ++$i){
|
||||
if($i === 4){
|
||||
continue;
|
||||
}
|
||||
$c = $this->{"chunk$i"};
|
||||
if($c !== null){
|
||||
$c = FastChunkSerializer::deserialize($c);
|
||||
$world->generateChunkCallback($c->getX(), $c->getZ(), $this->state ? $c : null);
|
||||
}
|
||||
}
|
||||
|
||||
$world->generateChunkCallback($chunk->getX(), $chunk->getZ(), $this->state ? $chunk : null);
|
||||
}
|
||||
}
|
||||
}
|
90
src/world/generator/biome/BiomeSelector.php
Normal file
90
src/world/generator/biome/BiomeSelector.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\biome;
|
||||
|
||||
use pocketmine\utils\Random;
|
||||
use pocketmine\world\biome\Biome;
|
||||
use pocketmine\world\biome\UnknownBiome;
|
||||
use pocketmine\world\generator\noise\Simplex;
|
||||
|
||||
abstract class BiomeSelector{
|
||||
/** @var Simplex */
|
||||
private $temperature;
|
||||
/** @var Simplex */
|
||||
private $rainfall;
|
||||
|
||||
/** @var Biome[]|\SplFixedArray */
|
||||
private $map = null;
|
||||
|
||||
public function __construct(Random $random){
|
||||
$this->temperature = new Simplex($random, 2, 1 / 16, 1 / 512);
|
||||
$this->rainfall = new Simplex($random, 2, 1 / 16, 1 / 512);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup function called by recalculate() to determine the biome to use for this temperature and rainfall.
|
||||
*
|
||||
* @param float $temperature
|
||||
* @param float $rainfall
|
||||
*
|
||||
* @return int biome ID 0-255
|
||||
*/
|
||||
abstract protected function lookup(float $temperature, float $rainfall) : int;
|
||||
|
||||
public function recalculate() : void{
|
||||
$this->map = new \SplFixedArray(64 * 64);
|
||||
|
||||
for($i = 0; $i < 64; ++$i){
|
||||
for($j = 0; $j < 64; ++$j){
|
||||
$biome = Biome::getBiome($this->lookup($i / 63, $j / 63));
|
||||
if($biome instanceof UnknownBiome){
|
||||
throw new \RuntimeException("Unknown biome returned by selector with ID " . $biome->getId());
|
||||
}
|
||||
$this->map[$i + ($j << 6)] = $biome;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getTemperature($x, $z){
|
||||
return ($this->temperature->noise2D($x, $z, true) + 1) / 2;
|
||||
}
|
||||
|
||||
public function getRainfall($x, $z){
|
||||
return ($this->rainfall->noise2D($x, $z, true) + 1) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: not sure on types here
|
||||
* @param int|float $x
|
||||
* @param int|float $z
|
||||
*
|
||||
* @return Biome
|
||||
*/
|
||||
public function pickBiome($x, $z) : Biome{
|
||||
$temperature = (int) ($this->getTemperature($x, $z) * 63);
|
||||
$rainfall = (int) ($this->getRainfall($x, $z) * 63);
|
||||
|
||||
return $this->map[$temperature + ($rainfall << 6)];
|
||||
}
|
||||
}
|
129
src/world/generator/hell/Nether.php
Normal file
129
src/world/generator/hell/Nether.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\hell;
|
||||
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\world\biome\Biome;
|
||||
use pocketmine\world\ChunkManager;
|
||||
use pocketmine\world\generator\Generator;
|
||||
use pocketmine\world\generator\InvalidGeneratorOptionsException;
|
||||
use pocketmine\world\generator\noise\Simplex;
|
||||
use pocketmine\world\generator\populator\Populator;
|
||||
use function abs;
|
||||
|
||||
class Nether extends Generator{
|
||||
|
||||
/** @var Populator[] */
|
||||
private $populators = [];
|
||||
/** @var int */
|
||||
private $waterHeight = 32;
|
||||
/** @var int */
|
||||
private $emptyHeight = 64;
|
||||
/** @var int */
|
||||
private $emptyAmplitude = 1;
|
||||
/** @var float */
|
||||
private $density = 0.5;
|
||||
|
||||
/** @var Populator[] */
|
||||
private $generationPopulators = [];
|
||||
/** @var Simplex */
|
||||
private $noiseBase;
|
||||
|
||||
/**
|
||||
* @param ChunkManager $world
|
||||
* @param int $seed
|
||||
* @param array $options
|
||||
*
|
||||
* @throws InvalidGeneratorOptionsException
|
||||
*/
|
||||
public function __construct(ChunkManager $world, int $seed, array $options = []){
|
||||
parent::__construct($world, $seed, $options);
|
||||
|
||||
$this->noiseBase = new Simplex($this->random, 4, 1 / 4, 1 / 64);
|
||||
$this->random->setSeed($this->seed);
|
||||
|
||||
/*$ores = new Ore();
|
||||
$ores->setOreTypes([
|
||||
new OreType(new CoalOre(), 20, 16, 0, 128),
|
||||
new OreType(new IronOre(), 20, 8, 0, 64),
|
||||
new OreType(new RedstoneOre(), 8, 7, 0, 16),
|
||||
new OreType(new LapisOre(), 1, 6, 0, 32),
|
||||
new OreType(new GoldOre(), 2, 8, 0, 32),
|
||||
new OreType(new DiamondOre(), 1, 7, 0, 16),
|
||||
new OreType(new Dirt(), 20, 32, 0, 128),
|
||||
new OreType(new Gravel(), 10, 16, 0, 128)
|
||||
]);
|
||||
$this->populators[] = $ores;*/
|
||||
}
|
||||
|
||||
public function generateChunk(int $chunkX, int $chunkZ) : void{
|
||||
$this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->seed);
|
||||
|
||||
$noise = $this->noiseBase->getFastNoise3D(16, 128, 16, 4, 8, 4, $chunkX * 16, 0, $chunkZ * 16);
|
||||
|
||||
$chunk = $this->world->getChunk($chunkX, $chunkZ);
|
||||
|
||||
$bedrock = VanillaBlocks::BEDROCK()->getFullId();
|
||||
$netherrack = VanillaBlocks::NETHERRACK()->getFullId();
|
||||
$stillLava = VanillaBlocks::LAVA()->getFullId();
|
||||
|
||||
for($x = 0; $x < 16; ++$x){
|
||||
for($z = 0; $z < 16; ++$z){
|
||||
|
||||
$biome = Biome::getBiome(Biome::HELL);
|
||||
$chunk->setBiomeId($x, $z, $biome->getId());
|
||||
|
||||
for($y = 0; $y < 128; ++$y){
|
||||
if($y === 0 or $y === 127){
|
||||
$chunk->setFullBlock($x, $y, $z, $bedrock);
|
||||
continue;
|
||||
}
|
||||
$noiseValue = (abs($this->emptyHeight - $y) / $this->emptyHeight) * $this->emptyAmplitude - $noise[$x][$z][$y];
|
||||
$noiseValue -= 1 - $this->density;
|
||||
|
||||
if($noiseValue > 0){
|
||||
$chunk->setFullBlock($x, $y, $z, $netherrack);
|
||||
}elseif($y <= $this->waterHeight){
|
||||
$chunk->setFullBlock($x, $y, $z, $stillLava);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach($this->generationPopulators as $populator){
|
||||
$populator->populate($this->world, $chunkX, $chunkZ, $this->random);
|
||||
}
|
||||
}
|
||||
|
||||
public function populateChunk(int $chunkX, int $chunkZ) : void{
|
||||
$this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->seed);
|
||||
foreach($this->populators as $populator){
|
||||
$populator->populate($this->world, $chunkX, $chunkZ, $this->random);
|
||||
}
|
||||
|
||||
$chunk = $this->world->getChunk($chunkX, $chunkZ);
|
||||
$biome = Biome::getBiome($chunk->getBiomeId(7, 7));
|
||||
$biome->populateChunk($this->world, $chunkX, $chunkZ, $this->random);
|
||||
}
|
||||
}
|
290
src/world/generator/noise/Noise.php
Normal file
290
src/world/generator/noise/Noise.php
Normal file
@ -0,0 +1,290 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Different noise generators for world generation
|
||||
*/
|
||||
namespace pocketmine\world\generator\noise;
|
||||
|
||||
|
||||
use function array_fill;
|
||||
use function assert;
|
||||
|
||||
abstract class Noise{
|
||||
|
||||
|
||||
public static function linearLerp($x, $x1, $x2, $q0, $q1){
|
||||
return (($x2 - $x) / ($x2 - $x1)) * $q0 + (($x - $x1) / ($x2 - $x1)) * $q1;
|
||||
}
|
||||
|
||||
public static function bilinearLerp($x, $y, $q00, $q01, $q10, $q11, $x1, $x2, $y1, $y2){
|
||||
$dx1 = (($x2 - $x) / ($x2 - $x1));
|
||||
$dx2 = (($x - $x1) / ($x2 - $x1));
|
||||
|
||||
return (($y2 - $y) / ($y2 - $y1)) * (
|
||||
$dx1 * $q00 + $dx2 * $q10
|
||||
) + (($y - $y1) / ($y2 - $y1)) * (
|
||||
$dx1 * $q01 + $dx2 * $q11
|
||||
);
|
||||
}
|
||||
|
||||
public static function trilinearLerp($x, $y, $z, $q000, $q001, $q010, $q011, $q100, $q101, $q110, $q111, $x1, $x2, $y1, $y2, $z1, $z2){
|
||||
$dx1 = (($x2 - $x) / ($x2 - $x1));
|
||||
$dx2 = (($x - $x1) / ($x2 - $x1));
|
||||
$dy1 = (($y2 - $y) / ($y2 - $y1));
|
||||
$dy2 = (($y - $y1) / ($y2 - $y1));
|
||||
|
||||
return (($z2 - $z) / ($z2 - $z1)) * (
|
||||
$dy1 * (
|
||||
$dx1 * $q000 + $dx2 * $q100
|
||||
) + $dy2 * (
|
||||
$dx1 * $q001 + $dx2 * $q101
|
||||
)
|
||||
) + (($z - $z1) / ($z2 - $z1)) * (
|
||||
$dy1 * (
|
||||
$dx1 * $q010 + $dx2 * $q110
|
||||
) + $dy2 * (
|
||||
$dx1 * $q011 + $dx2 * $q111
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/** @var float */
|
||||
protected $persistence;
|
||||
/** @var float */
|
||||
protected $expansion;
|
||||
/** @var int */
|
||||
protected $octaves;
|
||||
|
||||
public function __construct(int $octaves, float $persistence, float $expansion){
|
||||
$this->octaves = $octaves;
|
||||
$this->persistence = $persistence;
|
||||
$this->expansion = $expansion;
|
||||
}
|
||||
|
||||
abstract public function getNoise2D($x, $z);
|
||||
|
||||
abstract public function getNoise3D($x, $y, $z);
|
||||
|
||||
public function noise2D($x, $z, $normalized = false){
|
||||
$result = 0;
|
||||
$amp = 1;
|
||||
$freq = 1;
|
||||
$max = 0;
|
||||
|
||||
$x *= $this->expansion;
|
||||
$z *= $this->expansion;
|
||||
|
||||
for($i = 0; $i < $this->octaves; ++$i){
|
||||
$result += $this->getNoise2D($x * $freq, $z * $freq) * $amp;
|
||||
$max += $amp;
|
||||
$freq *= 2;
|
||||
$amp *= $this->persistence;
|
||||
}
|
||||
|
||||
if($normalized === true){
|
||||
$result /= $max;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function noise3D($x, $y, $z, $normalized = false){
|
||||
$result = 0;
|
||||
$amp = 1;
|
||||
$freq = 1;
|
||||
$max = 0;
|
||||
|
||||
$x *= $this->expansion;
|
||||
$y *= $this->expansion;
|
||||
$z *= $this->expansion;
|
||||
|
||||
for($i = 0; $i < $this->octaves; ++$i){
|
||||
$result += $this->getNoise3D($x * $freq, $y * $freq, $z * $freq) * $amp;
|
||||
$max += $amp;
|
||||
$freq *= 2;
|
||||
$amp *= $this->persistence;
|
||||
}
|
||||
|
||||
if($normalized === true){
|
||||
$result /= $max;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param int $xSize
|
||||
* @param int $samplingRate
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @param int $z
|
||||
*
|
||||
* @return \SplFixedArray
|
||||
*/
|
||||
public function getFastNoise1D(int $xSize, int $samplingRate, int $x, int $y, int $z) : \SplFixedArray{
|
||||
if($samplingRate === 0){
|
||||
throw new \InvalidArgumentException("samplingRate cannot be 0");
|
||||
}
|
||||
if($xSize % $samplingRate !== 0){
|
||||
throw new \InvalidArgumentException("xSize % samplingRate must return 0");
|
||||
}
|
||||
|
||||
$noiseArray = new \SplFixedArray($xSize + 1);
|
||||
|
||||
for($xx = 0; $xx <= $xSize; $xx += $samplingRate){
|
||||
$noiseArray[$xx] = $this->noise3D($xx + $x, $y, $z);
|
||||
}
|
||||
|
||||
for($xx = 0; $xx < $xSize; ++$xx){
|
||||
if($xx % $samplingRate !== 0){
|
||||
$nx = (int) ($xx / $samplingRate) * $samplingRate;
|
||||
$noiseArray[$xx] = self::linearLerp($xx, $nx, $nx + $samplingRate, $noiseArray[$nx], $noiseArray[$nx + $samplingRate]);
|
||||
}
|
||||
}
|
||||
|
||||
return $noiseArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $xSize
|
||||
* @param int $zSize
|
||||
* @param int $samplingRate
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @param int $z
|
||||
*
|
||||
* @return \SplFixedArray
|
||||
*/
|
||||
public function getFastNoise2D(int $xSize, int $zSize, int $samplingRate, int $x, int $y, int $z) : \SplFixedArray{
|
||||
assert($samplingRate !== 0, new \InvalidArgumentException("samplingRate cannot be 0"));
|
||||
|
||||
assert($xSize % $samplingRate === 0, new \InvalidArgumentException("xSize % samplingRate must return 0"));
|
||||
assert($zSize % $samplingRate === 0, new \InvalidArgumentException("zSize % samplingRate must return 0"));
|
||||
|
||||
$noiseArray = new \SplFixedArray($xSize + 1);
|
||||
|
||||
for($xx = 0; $xx <= $xSize; $xx += $samplingRate){
|
||||
$noiseArray[$xx] = new \SplFixedArray($zSize + 1);
|
||||
for($zz = 0; $zz <= $zSize; $zz += $samplingRate){
|
||||
$noiseArray[$xx][$zz] = $this->noise3D($x + $xx, $y, $z + $zz);
|
||||
}
|
||||
}
|
||||
|
||||
for($xx = 0; $xx < $xSize; ++$xx){
|
||||
if($xx % $samplingRate !== 0){
|
||||
$noiseArray[$xx] = new \SplFixedArray($zSize + 1);
|
||||
}
|
||||
|
||||
for($zz = 0; $zz < $zSize; ++$zz){
|
||||
if($xx % $samplingRate !== 0 or $zz % $samplingRate !== 0){
|
||||
$nx = (int) ($xx / $samplingRate) * $samplingRate;
|
||||
$nz = (int) ($zz / $samplingRate) * $samplingRate;
|
||||
$noiseArray[$xx][$zz] = Noise::bilinearLerp(
|
||||
$xx, $zz, $noiseArray[$nx][$nz], $noiseArray[$nx][$nz + $samplingRate],
|
||||
$noiseArray[$nx + $samplingRate][$nz], $noiseArray[$nx + $samplingRate][$nz + $samplingRate],
|
||||
$nx, $nx + $samplingRate, $nz, $nz + $samplingRate
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $noiseArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $xSize
|
||||
* @param int $ySize
|
||||
* @param int $zSize
|
||||
* @param int $xSamplingRate
|
||||
* @param int $ySamplingRate
|
||||
* @param int $zSamplingRate
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @param int $z
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFastNoise3D(int $xSize, int $ySize, int $zSize, int $xSamplingRate, int $ySamplingRate, int $zSamplingRate, int $x, int $y, int $z) : array{
|
||||
|
||||
assert($xSamplingRate !== 0, new \InvalidArgumentException("xSamplingRate cannot be 0"));
|
||||
assert($zSamplingRate !== 0, new \InvalidArgumentException("zSamplingRate cannot be 0"));
|
||||
assert($ySamplingRate !== 0, new \InvalidArgumentException("ySamplingRate cannot be 0"));
|
||||
|
||||
assert($xSize % $xSamplingRate === 0, new \InvalidArgumentException("xSize % xSamplingRate must return 0"));
|
||||
assert($zSize % $zSamplingRate === 0, new \InvalidArgumentException("zSize % zSamplingRate must return 0"));
|
||||
assert($ySize % $ySamplingRate === 0, new \InvalidArgumentException("ySize % ySamplingRate must return 0"));
|
||||
|
||||
$noiseArray = array_fill(0, $xSize + 1, array_fill(0, $zSize + 1, []));
|
||||
|
||||
for($xx = 0; $xx <= $xSize; $xx += $xSamplingRate){
|
||||
for($zz = 0; $zz <= $zSize; $zz += $zSamplingRate){
|
||||
for($yy = 0; $yy <= $ySize; $yy += $ySamplingRate){
|
||||
$noiseArray[$xx][$zz][$yy] = $this->noise3D($x + $xx, $y + $yy, $z + $zz, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for($xx = 0; $xx < $xSize; ++$xx){
|
||||
for($zz = 0; $zz < $zSize; ++$zz){
|
||||
for($yy = 0; $yy < $ySize; ++$yy){
|
||||
if($xx % $xSamplingRate !== 0 or $zz % $zSamplingRate !== 0 or $yy % $ySamplingRate !== 0){
|
||||
$nx = (int) ($xx / $xSamplingRate) * $xSamplingRate;
|
||||
$ny = (int) ($yy / $ySamplingRate) * $ySamplingRate;
|
||||
$nz = (int) ($zz / $zSamplingRate) * $zSamplingRate;
|
||||
|
||||
$nnx = $nx + $xSamplingRate;
|
||||
$nny = $ny + $ySamplingRate;
|
||||
$nnz = $nz + $zSamplingRate;
|
||||
|
||||
/**
|
||||
* This code has been manually inlined.
|
||||
* @see Noise::trilinearLerp()
|
||||
*/
|
||||
$dx1 = (($nnx - $xx) / ($nnx - $nx));
|
||||
$dx2 = (($xx - $nx) / ($nnx - $nx));
|
||||
$dy1 = (($nny - $yy) / ($nny - $ny));
|
||||
$dy2 = (($yy - $ny) / ($nny - $ny));
|
||||
|
||||
$noiseArray[$xx][$zz][$yy] = (($nnz - $zz) / ($nnz - $nz)) * (
|
||||
$dy1 * (
|
||||
$dx1 * $noiseArray[$nx][$nz][$ny] + $dx2 * $noiseArray[$nnx][$nz][$ny]
|
||||
) + $dy2 * (
|
||||
$dx1 * $noiseArray[$nx][$nz][$nny] + $dx2 * $noiseArray[$nnx][$nz][$nny]
|
||||
)
|
||||
) + (($zz - $nz) / ($nnz - $nz)) * (
|
||||
$dy1 * (
|
||||
$dx1 * $noiseArray[$nx][$nnz][$ny] + $dx2 * $noiseArray[$nnx][$nnz][$ny]
|
||||
) + $dy2 * (
|
||||
$dx1 * $noiseArray[$nx][$nnz][$nny] + $dx2 * $noiseArray[$nnx][$nnz][$nny]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $noiseArray;
|
||||
}
|
||||
}
|
276
src/world/generator/noise/Simplex.php
Normal file
276
src/world/generator/noise/Simplex.php
Normal file
@ -0,0 +1,276 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\noise;
|
||||
|
||||
use pocketmine\utils\Random;
|
||||
use const M_SQRT3;
|
||||
|
||||
/**
|
||||
* Generates simplex-based noise.
|
||||
*
|
||||
* This is a modified version of the freely published version in the paper by
|
||||
* Stefan Gustavson at
|
||||
* http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
|
||||
*/
|
||||
class Simplex extends Noise{
|
||||
protected const grad3 = [
|
||||
[1, 1, 0], [-1, 1, 0], [1, -1, 0], [-1, -1, 0],
|
||||
[1, 0, 1], [-1, 0, 1], [1, 0, -1], [-1, 0, -1],
|
||||
[0, 1, 1], [0, -1, 1], [0, 1, -1], [0, -1, -1]
|
||||
];
|
||||
|
||||
protected const F2 = 0.5 * (M_SQRT3 - 1);
|
||||
protected const G2 = (3 - M_SQRT3) / 6;
|
||||
protected const G22 = self::G2 * 2.0 - 1;
|
||||
protected const F3 = 1.0 / 3.0;
|
||||
protected const G3 = 1.0 / 6.0;
|
||||
|
||||
/** @var float */
|
||||
protected $offsetX;
|
||||
/** @var float */
|
||||
protected $offsetZ;
|
||||
/** @var float */
|
||||
protected $offsetY;
|
||||
/** @var int[] */
|
||||
protected $perm = [];
|
||||
|
||||
public function __construct(Random $random, int $octaves, float $persistence, float $expansion){
|
||||
parent::__construct($octaves, $persistence, $expansion);
|
||||
|
||||
$this->offsetX = $random->nextFloat() * 256;
|
||||
$this->offsetY = $random->nextFloat() * 256;
|
||||
$this->offsetZ = $random->nextFloat() * 256;
|
||||
|
||||
for($i = 0; $i < 512; ++$i){
|
||||
$this->perm[$i] = 0;
|
||||
}
|
||||
|
||||
for($i = 0; $i < 256; ++$i){
|
||||
$this->perm[$i] = $random->nextBoundedInt(256);
|
||||
}
|
||||
|
||||
for($i = 0; $i < 256; ++$i){
|
||||
$pos = $random->nextBoundedInt(256 - $i) + $i;
|
||||
$old = $this->perm[$i];
|
||||
|
||||
$this->perm[$i] = $this->perm[$pos];
|
||||
$this->perm[$pos] = $old;
|
||||
$this->perm[$i + 256] = $this->perm[$i];
|
||||
}
|
||||
|
||||
//this dummy call is necessary to produce the same RNG state as before latest refactors to this file
|
||||
//previously this value would be used for offsetW
|
||||
//TODO: this really needs to reset the RNG seed to avoid future RNG contamination
|
||||
$random->nextSignedInt();
|
||||
}
|
||||
|
||||
public function getNoise3D($x, $y, $z){
|
||||
$x += $this->offsetX;
|
||||
$y += $this->offsetY;
|
||||
$z += $this->offsetZ;
|
||||
|
||||
// Skew the input space to determine which simplex cell we're in
|
||||
$s = ($x + $y + $z) * self::F3; // Very nice and simple skew factor for 3D
|
||||
$i = (int) ($x + $s);
|
||||
$j = (int) ($y + $s);
|
||||
$k = (int) ($z + $s);
|
||||
$t = ($i + $j + $k) * self::G3;
|
||||
// Unskew the cell origin back to (x,y,z) space
|
||||
$x0 = $x - ($i - $t); // The x,y,z distances from the cell origin
|
||||
$y0 = $y - ($j - $t);
|
||||
$z0 = $z - ($k - $t);
|
||||
|
||||
// For the 3D case, the simplex shape is a slightly irregular tetrahedron.
|
||||
|
||||
// Determine which simplex we are in.
|
||||
if($x0 >= $y0){
|
||||
if($y0 >= $z0){
|
||||
$i1 = 1;
|
||||
$j1 = 0;
|
||||
$k1 = 0;
|
||||
$i2 = 1;
|
||||
$j2 = 1;
|
||||
$k2 = 0;
|
||||
} // X Y Z order
|
||||
elseif($x0 >= $z0){
|
||||
$i1 = 1;
|
||||
$j1 = 0;
|
||||
$k1 = 0;
|
||||
$i2 = 1;
|
||||
$j2 = 0;
|
||||
$k2 = 1;
|
||||
} // X Z Y order
|
||||
else{
|
||||
$i1 = 0;
|
||||
$j1 = 0;
|
||||
$k1 = 1;
|
||||
$i2 = 1;
|
||||
$j2 = 0;
|
||||
$k2 = 1;
|
||||
}
|
||||
// Z X Y order
|
||||
}else{ // x0<y0
|
||||
if($y0 < $z0){
|
||||
$i1 = 0;
|
||||
$j1 = 0;
|
||||
$k1 = 1;
|
||||
$i2 = 0;
|
||||
$j2 = 1;
|
||||
$k2 = 1;
|
||||
} // Z Y X order
|
||||
elseif($x0 < $z0){
|
||||
$i1 = 0;
|
||||
$j1 = 1;
|
||||
$k1 = 0;
|
||||
$i2 = 0;
|
||||
$j2 = 1;
|
||||
$k2 = 1;
|
||||
} // Y Z X order
|
||||
else{
|
||||
$i1 = 0;
|
||||
$j1 = 1;
|
||||
$k1 = 0;
|
||||
$i2 = 1;
|
||||
$j2 = 1;
|
||||
$k2 = 0;
|
||||
}
|
||||
// Y X Z order
|
||||
}
|
||||
|
||||
// A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
|
||||
// a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and
|
||||
// a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where
|
||||
// c = 1/6.
|
||||
$x1 = $x0 - $i1 + self::G3; // Offsets for second corner in (x,y,z) coords
|
||||
$y1 = $y0 - $j1 + self::G3;
|
||||
$z1 = $z0 - $k1 + self::G3;
|
||||
$x2 = $x0 - $i2 + 2.0 * self::G3; // Offsets for third corner in (x,y,z) coords
|
||||
$y2 = $y0 - $j2 + 2.0 * self::G3;
|
||||
$z2 = $z0 - $k2 + 2.0 * self::G3;
|
||||
$x3 = $x0 - 1.0 + 3.0 * self::G3; // Offsets for last corner in (x,y,z) coords
|
||||
$y3 = $y0 - 1.0 + 3.0 * self::G3;
|
||||
$z3 = $z0 - 1.0 + 3.0 * self::G3;
|
||||
|
||||
// Work out the hashed gradient indices of the four simplex corners
|
||||
$ii = $i & 255;
|
||||
$jj = $j & 255;
|
||||
$kk = $k & 255;
|
||||
|
||||
$n = 0;
|
||||
|
||||
// Calculate the contribution from the four corners
|
||||
$t0 = 0.6 - $x0 * $x0 - $y0 * $y0 - $z0 * $z0;
|
||||
if($t0 > 0){
|
||||
$gi0 = self::grad3[$this->perm[$ii + $this->perm[$jj + $this->perm[$kk]]] % 12];
|
||||
$n += $t0 * $t0 * $t0 * $t0 * ($gi0[0] * $x0 + $gi0[1] * $y0 + $gi0[2] * $z0);
|
||||
}
|
||||
|
||||
$t1 = 0.6 - $x1 * $x1 - $y1 * $y1 - $z1 * $z1;
|
||||
if($t1 > 0){
|
||||
$gi1 = self::grad3[$this->perm[$ii + $i1 + $this->perm[$jj + $j1 + $this->perm[$kk + $k1]]] % 12];
|
||||
$n += $t1 * $t1 * $t1 * $t1 * ($gi1[0] * $x1 + $gi1[1] * $y1 + $gi1[2] * $z1);
|
||||
}
|
||||
|
||||
$t2 = 0.6 - $x2 * $x2 - $y2 * $y2 - $z2 * $z2;
|
||||
if($t2 > 0){
|
||||
$gi2 = self::grad3[$this->perm[$ii + $i2 + $this->perm[$jj + $j2 + $this->perm[$kk + $k2]]] % 12];
|
||||
$n += $t2 * $t2 * $t2 * $t2 * ($gi2[0] * $x2 + $gi2[1] * $y2 + $gi2[2] * $z2);
|
||||
}
|
||||
|
||||
$t3 = 0.6 - $x3 * $x3 - $y3 * $y3 - $z3 * $z3;
|
||||
if($t3 > 0){
|
||||
$gi3 = self::grad3[$this->perm[$ii + 1 + $this->perm[$jj + 1 + $this->perm[$kk + 1]]] % 12];
|
||||
$n += $t3 * $t3 * $t3 * $t3 * ($gi3[0] * $x3 + $gi3[1] * $y3 + $gi3[2] * $z3);
|
||||
}
|
||||
|
||||
// Add contributions from each corner to get the noise value.
|
||||
// The result is scaled to stay just inside [-1,1]
|
||||
return 32.0 * $n;
|
||||
}
|
||||
|
||||
public function getNoise2D($x, $y){
|
||||
$x += $this->offsetX;
|
||||
$y += $this->offsetY;
|
||||
|
||||
// Skew the input space to determine which simplex cell we're in
|
||||
$s = ($x + $y) * self::F2; // Hairy factor for 2D
|
||||
$i = (int) ($x + $s);
|
||||
$j = (int) ($y + $s);
|
||||
$t = ($i + $j) * self::G2;
|
||||
// Unskew the cell origin back to (x,y) space
|
||||
$x0 = $x - ($i - $t); // The x,y distances from the cell origin
|
||||
$y0 = $y - ($j - $t);
|
||||
|
||||
// For the 2D case, the simplex shape is an equilateral triangle.
|
||||
|
||||
// Determine which simplex we are in.
|
||||
if($x0 > $y0){
|
||||
$i1 = 1;
|
||||
$j1 = 0;
|
||||
} // lower triangle, XY order: (0,0)->(1,0)->(1,1)
|
||||
else{
|
||||
$i1 = 0;
|
||||
$j1 = 1;
|
||||
}
|
||||
// upper triangle, YX order: (0,0)->(0,1)->(1,1)
|
||||
|
||||
// A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
|
||||
// a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
|
||||
// c = (3-sqrt(3))/6
|
||||
|
||||
$x1 = $x0 - $i1 + self::G2; // Offsets for middle corner in (x,y) unskewed coords
|
||||
$y1 = $y0 - $j1 + self::G2;
|
||||
$x2 = $x0 + self::G22; // Offsets for last corner in (x,y) unskewed coords
|
||||
$y2 = $y0 + self::G22;
|
||||
|
||||
// Work out the hashed gradient indices of the three simplex corners
|
||||
$ii = $i & 255;
|
||||
$jj = $j & 255;
|
||||
|
||||
$n = 0;
|
||||
|
||||
// Calculate the contribution from the three corners
|
||||
$t0 = 0.5 - $x0 * $x0 - $y0 * $y0;
|
||||
if($t0 > 0){
|
||||
$gi0 = self::grad3[$this->perm[$ii + $this->perm[$jj]] % 12];
|
||||
$n += $t0 * $t0 * $t0 * $t0 * ($gi0[0] * $x0 + $gi0[1] * $y0); // (x,y) of grad3 used for 2D gradient
|
||||
}
|
||||
|
||||
$t1 = 0.5 - $x1 * $x1 - $y1 * $y1;
|
||||
if($t1 > 0){
|
||||
$gi1 = self::grad3[$this->perm[$ii + $i1 + $this->perm[$jj + $j1]] % 12];
|
||||
$n += $t1 * $t1 * $t1 * $t1 * ($gi1[0] * $x1 + $gi1[1] * $y1);
|
||||
}
|
||||
|
||||
$t2 = 0.5 - $x2 * $x2 - $y2 * $y2;
|
||||
if($t2 > 0){
|
||||
$gi2 = self::grad3[$this->perm[$ii + 1 + $this->perm[$jj + 1]] % 12];
|
||||
$n += $t2 * $t2 * $t2 * $t2 * ($gi2[0] * $x2 + $gi2[1] * $y2);
|
||||
}
|
||||
|
||||
// Add contributions from each corner to get the noise value.
|
||||
// The result is scaled to return values in the interval [-1,1].
|
||||
return 70.0 * $n;
|
||||
}
|
||||
}
|
247
src/world/generator/normal/Normal.php
Normal file
247
src/world/generator/normal/Normal.php
Normal file
@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\normal;
|
||||
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\world\biome\Biome;
|
||||
use pocketmine\world\ChunkManager;
|
||||
use pocketmine\world\generator\biome\BiomeSelector;
|
||||
use pocketmine\world\generator\Generator;
|
||||
use pocketmine\world\generator\InvalidGeneratorOptionsException;
|
||||
use pocketmine\world\generator\noise\Simplex;
|
||||
use pocketmine\world\generator\object\OreType;
|
||||
use pocketmine\world\generator\populator\GroundCover;
|
||||
use pocketmine\world\generator\populator\Ore;
|
||||
use pocketmine\world\generator\populator\Populator;
|
||||
use pocketmine\world\World;
|
||||
use function exp;
|
||||
|
||||
class Normal extends Generator{
|
||||
|
||||
/** @var Populator[] */
|
||||
private $populators = [];
|
||||
/** @var int */
|
||||
private $waterHeight = 62;
|
||||
|
||||
/** @var Populator[] */
|
||||
private $generationPopulators = [];
|
||||
/** @var Simplex */
|
||||
private $noiseBase;
|
||||
|
||||
/** @var BiomeSelector */
|
||||
private $selector;
|
||||
|
||||
private static $GAUSSIAN_KERNEL = null;
|
||||
private static $SMOOTH_SIZE = 2;
|
||||
|
||||
/**
|
||||
* @param ChunkManager $world
|
||||
* @param int $seed
|
||||
* @param array $options
|
||||
*
|
||||
* @throws InvalidGeneratorOptionsException
|
||||
*/
|
||||
public function __construct(ChunkManager $world, int $seed, array $options = []){
|
||||
parent::__construct($world, $seed, $options);
|
||||
if(self::$GAUSSIAN_KERNEL === null){
|
||||
self::generateKernel();
|
||||
}
|
||||
|
||||
$this->noiseBase = new Simplex($this->random, 4, 1 / 4, 1 / 32);
|
||||
$this->random->setSeed($this->seed);
|
||||
|
||||
$this->selector = new class($this->random) extends BiomeSelector{
|
||||
protected function lookup(float $temperature, float $rainfall) : int{
|
||||
if($rainfall < 0.25){
|
||||
if($temperature < 0.7){
|
||||
return Biome::OCEAN;
|
||||
}elseif($temperature < 0.85){
|
||||
return Biome::RIVER;
|
||||
}else{
|
||||
return Biome::SWAMP;
|
||||
}
|
||||
}elseif($rainfall < 0.60){
|
||||
if($temperature < 0.25){
|
||||
return Biome::ICE_PLAINS;
|
||||
}elseif($temperature < 0.75){
|
||||
return Biome::PLAINS;
|
||||
}else{
|
||||
return Biome::DESERT;
|
||||
}
|
||||
}elseif($rainfall < 0.80){
|
||||
if($temperature < 0.25){
|
||||
return Biome::TAIGA;
|
||||
}elseif($temperature < 0.75){
|
||||
return Biome::FOREST;
|
||||
}else{
|
||||
return Biome::BIRCH_FOREST;
|
||||
}
|
||||
}else{
|
||||
//FIXME: This will always cause River to be used since the rainfall is always greater than 0.8 if we
|
||||
//reached this branch. However I don't think that substituting temperature for rainfall is correct given
|
||||
//that mountain biomes are supposed to be pretty cold.
|
||||
if($rainfall < 0.25){
|
||||
return Biome::MOUNTAINS;
|
||||
}elseif($rainfall < 0.70){
|
||||
return Biome::SMALL_MOUNTAINS;
|
||||
}else{
|
||||
return Biome::RIVER;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$this->selector->recalculate();
|
||||
|
||||
$cover = new GroundCover();
|
||||
$this->generationPopulators[] = $cover;
|
||||
|
||||
$ores = new Ore();
|
||||
$ores->setOreTypes([
|
||||
new OreType(VanillaBlocks::COAL_ORE(), 20, 16, 0, 128),
|
||||
new OreType(VanillaBlocks::IRON_ORE(), 20, 8, 0, 64),
|
||||
new OreType(VanillaBlocks::REDSTONE_ORE(), 8, 7, 0, 16),
|
||||
new OreType(VanillaBlocks::LAPIS_LAZULI_ORE(), 1, 6, 0, 32),
|
||||
new OreType(VanillaBlocks::GOLD_ORE(), 2, 8, 0, 32),
|
||||
new OreType(VanillaBlocks::DIAMOND_ORE(), 1, 7, 0, 16),
|
||||
new OreType(VanillaBlocks::DIRT(), 20, 32, 0, 128),
|
||||
new OreType(VanillaBlocks::GRAVEL(), 10, 16, 0, 128)
|
||||
]);
|
||||
$this->populators[] = $ores;
|
||||
}
|
||||
|
||||
private static function generateKernel() : void{
|
||||
self::$GAUSSIAN_KERNEL = [];
|
||||
|
||||
$bellSize = 1 / self::$SMOOTH_SIZE;
|
||||
$bellHeight = 2 * self::$SMOOTH_SIZE;
|
||||
|
||||
for($sx = -self::$SMOOTH_SIZE; $sx <= self::$SMOOTH_SIZE; ++$sx){
|
||||
self::$GAUSSIAN_KERNEL[$sx + self::$SMOOTH_SIZE] = [];
|
||||
|
||||
for($sz = -self::$SMOOTH_SIZE; $sz <= self::$SMOOTH_SIZE; ++$sz){
|
||||
$bx = $bellSize * $sx;
|
||||
$bz = $bellSize * $sz;
|
||||
self::$GAUSSIAN_KERNEL[$sx + self::$SMOOTH_SIZE][$sz + self::$SMOOTH_SIZE] = $bellHeight * exp(-($bx * $bx + $bz * $bz) / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function pickBiome(int $x, int $z) : Biome{
|
||||
$hash = $x * 2345803 ^ $z * 9236449 ^ $this->seed;
|
||||
$hash *= $hash + 223;
|
||||
$xNoise = $hash >> 20 & 3;
|
||||
$zNoise = $hash >> 22 & 3;
|
||||
if($xNoise == 3){
|
||||
$xNoise = 1;
|
||||
}
|
||||
if($zNoise == 3){
|
||||
$zNoise = 1;
|
||||
}
|
||||
|
||||
return $this->selector->pickBiome($x + $xNoise - 1, $z + $zNoise - 1);
|
||||
}
|
||||
|
||||
public function generateChunk(int $chunkX, int $chunkZ) : void{
|
||||
$this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->seed);
|
||||
|
||||
$noise = $this->noiseBase->getFastNoise3D(16, 128, 16, 4, 8, 4, $chunkX * 16, 0, $chunkZ * 16);
|
||||
|
||||
$chunk = $this->world->getChunk($chunkX, $chunkZ);
|
||||
|
||||
$biomeCache = [];
|
||||
|
||||
$bedrock = VanillaBlocks::BEDROCK()->getFullId();
|
||||
$stillWater = VanillaBlocks::WATER()->getFullId();
|
||||
$stone = VanillaBlocks::STONE()->getFullId();
|
||||
|
||||
for($x = 0; $x < 16; ++$x){
|
||||
for($z = 0; $z < 16; ++$z){
|
||||
$minSum = 0;
|
||||
$maxSum = 0;
|
||||
$weightSum = 0;
|
||||
|
||||
$biome = $this->pickBiome($chunkX * 16 + $x, $chunkZ * 16 + $z);
|
||||
$chunk->setBiomeId($x, $z, $biome->getId());
|
||||
|
||||
for($sx = -self::$SMOOTH_SIZE; $sx <= self::$SMOOTH_SIZE; ++$sx){
|
||||
for($sz = -self::$SMOOTH_SIZE; $sz <= self::$SMOOTH_SIZE; ++$sz){
|
||||
|
||||
$weight = self::$GAUSSIAN_KERNEL[$sx + self::$SMOOTH_SIZE][$sz + self::$SMOOTH_SIZE];
|
||||
|
||||
if($sx === 0 and $sz === 0){
|
||||
$adjacent = $biome;
|
||||
}else{
|
||||
$index = World::chunkHash($chunkX * 16 + $x + $sx, $chunkZ * 16 + $z + $sz);
|
||||
if(isset($biomeCache[$index])){
|
||||
$adjacent = $biomeCache[$index];
|
||||
}else{
|
||||
$biomeCache[$index] = $adjacent = $this->pickBiome($chunkX * 16 + $x + $sx, $chunkZ * 16 + $z + $sz);
|
||||
}
|
||||
}
|
||||
|
||||
$minSum += ($adjacent->getMinElevation() - 1) * $weight;
|
||||
$maxSum += $adjacent->getMaxElevation() * $weight;
|
||||
|
||||
$weightSum += $weight;
|
||||
}
|
||||
}
|
||||
|
||||
$minSum /= $weightSum;
|
||||
$maxSum /= $weightSum;
|
||||
|
||||
$smoothHeight = ($maxSum - $minSum) / 2;
|
||||
|
||||
for($y = 0; $y < 128; ++$y){
|
||||
if($y === 0){
|
||||
$chunk->setFullBlock($x, $y, $z, $bedrock);
|
||||
continue;
|
||||
}
|
||||
$noiseValue = $noise[$x][$z][$y] - 1 / $smoothHeight * ($y - $smoothHeight - $minSum);
|
||||
|
||||
if($noiseValue > 0){
|
||||
$chunk->setFullBlock($x, $y, $z, $stone);
|
||||
}elseif($y <= $this->waterHeight){
|
||||
$chunk->setFullBlock($x, $y, $z, $stillWater);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach($this->generationPopulators as $populator){
|
||||
$populator->populate($this->world, $chunkX, $chunkZ, $this->random);
|
||||
}
|
||||
}
|
||||
|
||||
public function populateChunk(int $chunkX, int $chunkZ) : void{
|
||||
$this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->seed);
|
||||
foreach($this->populators as $populator){
|
||||
$populator->populate($this->world, $chunkX, $chunkZ, $this->random);
|
||||
}
|
||||
|
||||
$chunk = $this->world->getChunk($chunkX, $chunkZ);
|
||||
$biome = Biome::getBiome($chunk->getBiomeId(7, 7));
|
||||
$biome->populateChunk($this->world, $chunkX, $chunkZ, $this->random);
|
||||
}
|
||||
}
|
46
src/world/generator/object/BirchTree.php
Normal file
46
src/world/generator/object/BirchTree.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\object;
|
||||
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\utils\Random;
|
||||
use pocketmine\world\ChunkManager;
|
||||
|
||||
class BirchTree extends Tree{
|
||||
/** @var bool */
|
||||
protected $superBirch = false;
|
||||
|
||||
public function __construct(bool $superBirch = false){
|
||||
parent::__construct(VanillaBlocks::BIRCH_LOG(), VanillaBlocks::BIRCH_LEAVES());
|
||||
$this->superBirch = $superBirch;
|
||||
}
|
||||
|
||||
public function placeObject(ChunkManager $world, int $x, int $y, int $z, Random $random) : void{
|
||||
$this->treeHeight = $random->nextBoundedInt(3) + 5;
|
||||
if($this->superBirch){
|
||||
$this->treeHeight += 5;
|
||||
}
|
||||
parent::placeObject($world, $x, $y, $z, $random);
|
||||
}
|
||||
}
|
33
src/world/generator/object/JungleTree.php
Normal file
33
src/world/generator/object/JungleTree.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\object;
|
||||
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
|
||||
class JungleTree extends Tree{
|
||||
|
||||
public function __construct(){
|
||||
parent::__construct(VanillaBlocks::JUNGLE_LOG(), VanillaBlocks::JUNGLE_LEAVES(), 8);
|
||||
}
|
||||
}
|
40
src/world/generator/object/OakTree.php
Normal file
40
src/world/generator/object/OakTree.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\object;
|
||||
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\utils\Random;
|
||||
use pocketmine\world\ChunkManager;
|
||||
|
||||
class OakTree extends Tree{
|
||||
|
||||
public function __construct(){
|
||||
parent::__construct(VanillaBlocks::OAK_LOG(), VanillaBlocks::OAK_LEAVES());
|
||||
}
|
||||
|
||||
public function placeObject(ChunkManager $world, int $x, int $y, int $z, Random $random) : void{
|
||||
$this->treeHeight = $random->nextBoundedInt(3) + 4;
|
||||
parent::placeObject($world, $x, $y, $z, $random);
|
||||
}
|
||||
}
|
99
src/world/generator/object/Ore.php
Normal file
99
src/world/generator/object/Ore.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\object;
|
||||
|
||||
use pocketmine\block\BlockLegacyIds;
|
||||
use pocketmine\math\VectorMath;
|
||||
use pocketmine\utils\Random;
|
||||
use pocketmine\world\ChunkManager;
|
||||
use function sin;
|
||||
use const M_PI;
|
||||
|
||||
class Ore{
|
||||
/** @var Random */
|
||||
private $random;
|
||||
/** @var OreType */
|
||||
public $type;
|
||||
|
||||
public function __construct(Random $random, OreType $type){
|
||||
$this->type = $type;
|
||||
$this->random = $random;
|
||||
}
|
||||
|
||||
public function getType() : OreType{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function canPlaceObject(ChunkManager $world, int $x, int $y, int $z) : bool{
|
||||
return $world->getBlockAt($x, $y, $z)->getId() === BlockLegacyIds::STONE;
|
||||
}
|
||||
|
||||
public function placeObject(ChunkManager $world, int $x, int $y, int $z) : void{
|
||||
$clusterSize = $this->type->clusterSize;
|
||||
$angle = $this->random->nextFloat() * M_PI;
|
||||
$offset = VectorMath::getDirection2D($angle)->multiply($clusterSize / 8);
|
||||
$x1 = $x + 8 + $offset->x;
|
||||
$x2 = $x + 8 - $offset->x;
|
||||
$z1 = $z + 8 + $offset->y;
|
||||
$z2 = $z + 8 - $offset->y;
|
||||
$y1 = $y + $this->random->nextBoundedInt(3) + 2;
|
||||
$y2 = $y + $this->random->nextBoundedInt(3) + 2;
|
||||
for($count = 0; $count <= $clusterSize; ++$count){
|
||||
$seedX = $x1 + ($x2 - $x1) * $count / $clusterSize;
|
||||
$seedY = $y1 + ($y2 - $y1) * $count / $clusterSize;
|
||||
$seedZ = $z1 + ($z2 - $z1) * $count / $clusterSize;
|
||||
$size = ((sin($count * (M_PI / $clusterSize)) + 1) * $this->random->nextFloat() * $clusterSize / 16 + 1) / 2;
|
||||
|
||||
$startX = (int) ($seedX - $size);
|
||||
$startY = (int) ($seedY - $size);
|
||||
$startZ = (int) ($seedZ - $size);
|
||||
$endX = (int) ($seedX + $size);
|
||||
$endY = (int) ($seedY + $size);
|
||||
$endZ = (int) ($seedZ + $size);
|
||||
|
||||
for($x = $startX; $x <= $endX; ++$x){
|
||||
$sizeX = ($x + 0.5 - $seedX) / $size;
|
||||
$sizeX *= $sizeX;
|
||||
|
||||
if($sizeX < 1){
|
||||
for($y = $startY; $y <= $endY; ++$y){
|
||||
$sizeY = ($y + 0.5 - $seedY) / $size;
|
||||
$sizeY *= $sizeY;
|
||||
|
||||
if($y > 0 and ($sizeX + $sizeY) < 1){
|
||||
for($z = $startZ; $z <= $endZ; ++$z){
|
||||
$sizeZ = ($z + 0.5 - $seedZ) / $size;
|
||||
$sizeZ *= $sizeZ;
|
||||
|
||||
if(($sizeX + $sizeY + $sizeZ) < 1 and $world->getBlockAt($x, $y, $z)->getId() === BlockLegacyIds::STONE){
|
||||
$world->setBlockAt($x, $y, $z, $this->type->material);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
47
src/world/generator/object/OreType.php
Normal file
47
src/world/generator/object/OreType.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\object;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
|
||||
class OreType{
|
||||
/** @var Block */
|
||||
public $material;
|
||||
/** @var int */
|
||||
public $clusterCount;
|
||||
/** @var int */
|
||||
public $clusterSize;
|
||||
/** @var int */
|
||||
public $maxHeight;
|
||||
/** @var int */
|
||||
public $minHeight;
|
||||
|
||||
public function __construct(Block $material, int $clusterCount, int $clusterSize, int $minHeight, int $maxHeight){
|
||||
$this->material = $material;
|
||||
$this->clusterCount = $clusterCount;
|
||||
$this->clusterSize = $clusterSize;
|
||||
$this->maxHeight = $maxHeight;
|
||||
$this->minHeight = $minHeight;
|
||||
}
|
||||
}
|
82
src/world/generator/object/SpruceTree.php
Normal file
82
src/world/generator/object/SpruceTree.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\object;
|
||||
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\utils\Random;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\ChunkManager;
|
||||
use function abs;
|
||||
|
||||
class SpruceTree extends Tree{
|
||||
|
||||
public function __construct(){
|
||||
parent::__construct(VanillaBlocks::SPRUCE_LOG(), VanillaBlocks::SPRUCE_LEAVES(), 10);
|
||||
}
|
||||
|
||||
protected function generateChunkHeight(Random $random) : int{
|
||||
return $this->treeHeight - $random->nextBoundedInt(3);
|
||||
}
|
||||
|
||||
public function placeObject(ChunkManager $world, int $x, int $y, int $z, Random $random) : void{
|
||||
$this->treeHeight = $random->nextBoundedInt(4) + 6;
|
||||
parent::placeObject($world, $x, $y, $z, $random);
|
||||
}
|
||||
|
||||
protected function placeCanopy(int $x, int $y, int $z, Random $random, BlockTransaction $transaction) : void{
|
||||
$topSize = $this->treeHeight - (1 + $random->nextBoundedInt(2));
|
||||
$lRadius = 2 + $random->nextBoundedInt(2);
|
||||
$radius = $random->nextBoundedInt(2);
|
||||
$maxR = 1;
|
||||
$minR = 0;
|
||||
|
||||
for($yy = 0; $yy <= $topSize; ++$yy){
|
||||
$yyy = $y + $this->treeHeight - $yy;
|
||||
|
||||
for($xx = $x - $radius; $xx <= $x + $radius; ++$xx){
|
||||
$xOff = abs($xx - $x);
|
||||
for($zz = $z - $radius; $zz <= $z + $radius; ++$zz){
|
||||
$zOff = abs($zz - $z);
|
||||
if($xOff === $radius and $zOff === $radius and $radius > 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!$transaction->fetchBlockAt($xx, $yyy, $zz)->isSolid()){
|
||||
$transaction->addBlockAt($xx, $yyy, $zz, $this->leafBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($radius >= $maxR){
|
||||
$radius = $minR;
|
||||
$minR = 1;
|
||||
if(++$maxR > $lRadius){
|
||||
$maxR = $lRadius;
|
||||
}
|
||||
}else{
|
||||
++$radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
55
src/world/generator/object/TallGrass.php
Normal file
55
src/world/generator/object/TallGrass.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\object;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\BlockLegacyIds;
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\utils\Random;
|
||||
use pocketmine\world\ChunkManager;
|
||||
use function count;
|
||||
|
||||
class TallGrass{
|
||||
|
||||
public static function growGrass(ChunkManager $world, Vector3 $pos, Random $random, int $count = 15, int $radius = 10) : void{
|
||||
/** @var Block[] $arr */
|
||||
$arr = [
|
||||
VanillaBlocks::DANDELION(),
|
||||
VanillaBlocks::POPPY(),
|
||||
$tallGrass = VanillaBlocks::TALL_GRASS(),
|
||||
$tallGrass,
|
||||
$tallGrass,
|
||||
$tallGrass
|
||||
];
|
||||
$arrC = count($arr) - 1;
|
||||
for($c = 0; $c < $count; ++$c){
|
||||
$x = $random->nextRange($pos->x - $radius, $pos->x + $radius);
|
||||
$z = $random->nextRange($pos->z - $radius, $pos->z + $radius);
|
||||
if($world->getBlockAt($x, $pos->y + 1, $z)->getId() === BlockLegacyIds::AIR and $world->getBlockAt($x, $pos->y, $z)->getId() === BlockLegacyIds::GRASS){
|
||||
$world->setBlockAt($x, $pos->y + 1, $z, $arr[$random->nextRange(0, $arrC)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
154
src/world/generator/object/Tree.php
Normal file
154
src/world/generator/object/Tree.php
Normal file
@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\object;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\Leaves;
|
||||
use pocketmine\block\Sapling;
|
||||
use pocketmine\block\utils\TreeType;
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\utils\Random;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\ChunkManager;
|
||||
use function abs;
|
||||
|
||||
abstract class Tree{
|
||||
/** @var Block */
|
||||
protected $trunkBlock;
|
||||
/** @var Block */
|
||||
protected $leafBlock;
|
||||
|
||||
/** @var int */
|
||||
protected $treeHeight;
|
||||
|
||||
public function __construct(Block $trunkBlock, Block $leafBlock, int $treeHeight = 7){
|
||||
$this->trunkBlock = $trunkBlock;
|
||||
$this->leafBlock = $leafBlock;
|
||||
|
||||
$this->treeHeight = $treeHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ChunkManager $world
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @param int $z
|
||||
* @param Random $random
|
||||
* @param TreeType|null $type default oak
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function growTree(ChunkManager $world, int $x, int $y, int $z, Random $random, ?TreeType $type = null) : void{
|
||||
/** @var null|Tree $tree */
|
||||
$tree = null;
|
||||
$type = $type ?? TreeType::OAK();
|
||||
if($type->equals(TreeType::SPRUCE())){
|
||||
$tree = new SpruceTree();
|
||||
}elseif($type->equals(TreeType::BIRCH())){
|
||||
if($random->nextBoundedInt(39) === 0){
|
||||
$tree = new BirchTree(true);
|
||||
}else{
|
||||
$tree = new BirchTree();
|
||||
}
|
||||
}elseif($type->equals(TreeType::JUNGLE())){
|
||||
$tree = new JungleTree();
|
||||
}elseif($type->equals(TreeType::OAK())){ //default
|
||||
$tree = new OakTree();
|
||||
/*if($random->nextRange(0, 9) === 0){
|
||||
$tree = new BigTree();
|
||||
}else{*/
|
||||
|
||||
//}
|
||||
}
|
||||
|
||||
if($tree !== null and $tree->canPlaceObject($world, $x, $y, $z, $random)){
|
||||
$tree->placeObject($world, $x, $y, $z, $random);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function canPlaceObject(ChunkManager $world, int $x, int $y, int $z, Random $random) : bool{
|
||||
$radiusToCheck = 0;
|
||||
for($yy = 0; $yy < $this->treeHeight + 3; ++$yy){
|
||||
if($yy === 1 or $yy === $this->treeHeight){
|
||||
++$radiusToCheck;
|
||||
}
|
||||
for($xx = -$radiusToCheck; $xx < ($radiusToCheck + 1); ++$xx){
|
||||
for($zz = -$radiusToCheck; $zz < ($radiusToCheck + 1); ++$zz){
|
||||
if(!$this->canOverride($world->getBlockAt($x + $xx, $y + $yy, $z + $zz))){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function placeObject(ChunkManager $world, int $x, int $y, int $z, Random $random) : void{
|
||||
$transaction = new BlockTransaction($world);
|
||||
$this->placeTrunk($x, $y, $z, $random, $this->generateChunkHeight($random), $transaction);
|
||||
$this->placeCanopy($x, $y, $z, $random, $transaction);
|
||||
|
||||
$transaction->apply(); //TODO: handle return value on failure
|
||||
}
|
||||
|
||||
protected function generateChunkHeight(Random $random) : int{
|
||||
return $this->treeHeight - 1;
|
||||
}
|
||||
|
||||
protected function placeTrunk(int $x, int $y, int $z, Random $random, int $trunkHeight, BlockTransaction $transaction) : void{
|
||||
// The base dirt block
|
||||
$transaction->addBlockAt($x, $y - 1, $z, VanillaBlocks::DIRT());
|
||||
|
||||
for($yy = 0; $yy < $trunkHeight; ++$yy){
|
||||
if($this->canOverride($transaction->fetchBlockAt($x, $y + $yy, $z))){
|
||||
$transaction->addBlockAt($x, $y + $yy, $z, $this->trunkBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function placeCanopy(int $x, int $y, int $z, Random $random, BlockTransaction $transaction) : void{
|
||||
for($yy = $y - 3 + $this->treeHeight; $yy <= $y + $this->treeHeight; ++$yy){
|
||||
$yOff = $yy - ($y + $this->treeHeight);
|
||||
$mid = (int) (1 - $yOff / 2);
|
||||
for($xx = $x - $mid; $xx <= $x + $mid; ++$xx){
|
||||
$xOff = abs($xx - $x);
|
||||
for($zz = $z - $mid; $zz <= $z + $mid; ++$zz){
|
||||
$zOff = abs($zz - $z);
|
||||
if($xOff === $mid and $zOff === $mid and ($yOff === 0 or $random->nextBoundedInt(2) === 0)){
|
||||
continue;
|
||||
}
|
||||
if(!$transaction->fetchBlockAt($xx, $yy, $zz)->isSolid()){
|
||||
$transaction->addBlockAt($xx, $yy, $zz, $this->leafBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function canOverride(Block $block) : bool{
|
||||
return $block->canBeReplaced() or $block instanceof Sapling or $block instanceof Leaves;
|
||||
}
|
||||
}
|
72
src/world/generator/populator/GroundCover.php
Normal file
72
src/world/generator/populator/GroundCover.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\populator;
|
||||
|
||||
use pocketmine\block\BlockFactory;
|
||||
use pocketmine\block\BlockLegacyIds;
|
||||
use pocketmine\block\Liquid;
|
||||
use pocketmine\utils\Random;
|
||||
use pocketmine\world\biome\Biome;
|
||||
use pocketmine\world\ChunkManager;
|
||||
use function count;
|
||||
use function min;
|
||||
|
||||
class GroundCover extends Populator{
|
||||
|
||||
public function populate(ChunkManager $world, int $chunkX, int $chunkZ, Random $random) : void{
|
||||
$chunk = $world->getChunk($chunkX, $chunkZ);
|
||||
for($x = 0; $x < 16; ++$x){
|
||||
for($z = 0; $z < 16; ++$z){
|
||||
$biome = Biome::getBiome($chunk->getBiomeId($x, $z));
|
||||
$cover = $biome->getGroundCover();
|
||||
if(count($cover) > 0){
|
||||
$diffY = 0;
|
||||
if(!$cover[0]->isSolid()){
|
||||
$diffY = 1;
|
||||
}
|
||||
|
||||
for($y = 127; $y > 0; --$y){
|
||||
if(!BlockFactory::fromFullBlock($chunk->getFullBlock($x, $y, $z))->isTransparent()){
|
||||
break;
|
||||
}
|
||||
}
|
||||
$startY = min(127, $y + $diffY);
|
||||
$endY = $startY - count($cover);
|
||||
for($y = $startY; $y > $endY and $y >= 0; --$y){
|
||||
$b = $cover[$startY - $y];
|
||||
$id = BlockFactory::fromFullBlock($chunk->getFullBlock($x, $y, $z));
|
||||
if($id->getId() === BlockLegacyIds::AIR and $b->isSolid()){
|
||||
break;
|
||||
}
|
||||
if($b->canBeFlowedInto() and $id instanceof Liquid){
|
||||
continue;
|
||||
}
|
||||
|
||||
$chunk->setFullBlock($x, $y, $z, $b->getFullId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
55
src/world/generator/populator/Ore.php
Normal file
55
src/world/generator/populator/Ore.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\populator;
|
||||
|
||||
use pocketmine\utils\Random;
|
||||
use pocketmine\world\ChunkManager;
|
||||
use pocketmine\world\generator\object\Ore as ObjectOre;
|
||||
use pocketmine\world\generator\object\OreType;
|
||||
|
||||
class Ore extends Populator{
|
||||
/** @var OreType[] */
|
||||
private $oreTypes = [];
|
||||
|
||||
public function populate(ChunkManager $world, int $chunkX, int $chunkZ, Random $random) : void{
|
||||
foreach($this->oreTypes as $type){
|
||||
$ore = new ObjectOre($random, $type);
|
||||
for($i = 0; $i < $ore->type->clusterCount; ++$i){
|
||||
$x = $random->nextRange($chunkX << 4, ($chunkX << 4) + 15);
|
||||
$y = $random->nextRange($ore->type->minHeight, $ore->type->maxHeight);
|
||||
$z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 15);
|
||||
if($ore->canPlaceObject($world, $x, $y, $z)){
|
||||
$ore->placeObject($world, $x, $y, $z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OreType[] $types
|
||||
*/
|
||||
public function setOreTypes(array $types) : void{
|
||||
$this->oreTypes = $types;
|
||||
}
|
||||
}
|
41
src/world/generator/populator/Populator.php
Normal file
41
src/world/generator/populator/Populator.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* All the Object populator classes
|
||||
*/
|
||||
namespace pocketmine\world\generator\populator;
|
||||
|
||||
use pocketmine\utils\Random;
|
||||
use pocketmine\world\ChunkManager;
|
||||
|
||||
abstract class Populator{
|
||||
|
||||
/**
|
||||
* @param ChunkManager $world
|
||||
* @param int $chunkX
|
||||
* @param int $chunkZ
|
||||
* @param Random $random
|
||||
*/
|
||||
abstract public function populate(ChunkManager $world, int $chunkX, int $chunkZ, Random $random) : void;
|
||||
}
|
76
src/world/generator/populator/TallGrass.php
Normal file
76
src/world/generator/populator/TallGrass.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\populator;
|
||||
|
||||
use pocketmine\block\BlockLegacyIds;
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\utils\Random;
|
||||
use pocketmine\world\ChunkManager;
|
||||
|
||||
class TallGrass extends Populator{
|
||||
/** @var ChunkManager */
|
||||
private $world;
|
||||
private $randomAmount;
|
||||
private $baseAmount;
|
||||
|
||||
public function setRandomAmount(int $amount) : void{
|
||||
$this->randomAmount = $amount;
|
||||
}
|
||||
|
||||
public function setBaseAmount(int $amount) : void{
|
||||
$this->baseAmount = $amount;
|
||||
}
|
||||
|
||||
public function populate(ChunkManager $world, int $chunkX, int $chunkZ, Random $random) : void{
|
||||
$this->world = $world;
|
||||
$amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount;
|
||||
|
||||
$block = VanillaBlocks::TALL_GRASS();
|
||||
for($i = 0; $i < $amount; ++$i){
|
||||
$x = $random->nextRange($chunkX * 16, $chunkX * 16 + 15);
|
||||
$z = $random->nextRange($chunkZ * 16, $chunkZ * 16 + 15);
|
||||
$y = $this->getHighestWorkableBlock($x, $z);
|
||||
|
||||
if($y !== -1 and $this->canTallGrassStay($x, $y, $z)){
|
||||
$this->world->setBlockAt($x, $y, $z, $block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function canTallGrassStay(int $x, int $y, int $z) : bool{
|
||||
$b = $this->world->getBlockAt($x, $y, $z)->getId();
|
||||
return ($b === BlockLegacyIds::AIR or $b === BlockLegacyIds::SNOW_LAYER) and $this->world->getBlockAt($x, $y - 1, $z)->getId() === BlockLegacyIds::GRASS;
|
||||
}
|
||||
|
||||
private function getHighestWorkableBlock(int $x, int $z) : int{
|
||||
for($y = 127; $y >= 0; --$y){
|
||||
$b = $this->world->getBlockAt($x, $y, $z)->getId();
|
||||
if($b !== BlockLegacyIds::AIR and $b !== BlockLegacyIds::LEAVES and $b !== BlockLegacyIds::LEAVES2 and $b !== BlockLegacyIds::SNOW_LAYER){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $y === 0 ? -1 : ++$y;
|
||||
}
|
||||
}
|
82
src/world/generator/populator/Tree.php
Normal file
82
src/world/generator/populator/Tree.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\populator;
|
||||
|
||||
use pocketmine\block\BlockLegacyIds;
|
||||
use pocketmine\block\utils\TreeType;
|
||||
use pocketmine\utils\Random;
|
||||
use pocketmine\world\ChunkManager;
|
||||
use pocketmine\world\generator\object\Tree as ObjectTree;
|
||||
|
||||
class Tree extends Populator{
|
||||
/** @var ChunkManager */
|
||||
private $world;
|
||||
private $randomAmount;
|
||||
private $baseAmount;
|
||||
|
||||
/** @var TreeType */
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* @param TreeType|null $type default oak
|
||||
*/
|
||||
public function __construct(?TreeType $type = null){
|
||||
$this->type = $type ?? TreeType::OAK();
|
||||
}
|
||||
|
||||
public function setRandomAmount(int $amount) : void{
|
||||
$this->randomAmount = $amount;
|
||||
}
|
||||
|
||||
public function setBaseAmount(int $amount) : void{
|
||||
$this->baseAmount = $amount;
|
||||
}
|
||||
|
||||
public function populate(ChunkManager $world, int $chunkX, int $chunkZ, Random $random) : void{
|
||||
$this->world = $world;
|
||||
$amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount;
|
||||
for($i = 0; $i < $amount; ++$i){
|
||||
$x = $random->nextRange($chunkX << 4, ($chunkX << 4) + 15);
|
||||
$z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 15);
|
||||
$y = $this->getHighestWorkableBlock($x, $z);
|
||||
if($y === -1){
|
||||
continue;
|
||||
}
|
||||
ObjectTree::growTree($this->world, $x, $y, $z, $random, $this->type);
|
||||
}
|
||||
}
|
||||
|
||||
private function getHighestWorkableBlock(int $x, int $z) : int{
|
||||
for($y = 127; $y > 0; --$y){
|
||||
$b = $this->world->getBlockAt($x, $y, $z)->getId();
|
||||
if($b === BlockLegacyIds::DIRT or $b === BlockLegacyIds::GRASS){
|
||||
break;
|
||||
}elseif($b !== BlockLegacyIds::AIR and $b !== BlockLegacyIds::SNOW_LAYER){
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return ++$y;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user