PocketMine-MP 5.37.1 git-cef37e7835c666594588f957a47b27d521c6a58e
Loading...
Searching...
No Matches
Normal.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\world\generator\normal;
25
42use function fmod;
43
44class Normal extends Generator{
45
46 private int $waterHeight = 62;
48 private array $populators = [];
50 private array $generationPopulators = [];
51 private Simplex $noiseBase;
52 private BiomeSelector $selector;
53 private Gaussian $gaussian;
54
58 public function __construct(int $seed, string $preset){
59 parent::__construct($seed, $preset);
60
61 $this->gaussian = new Gaussian(2);
62
63 $this->noiseBase = new Simplex($this->random, 4, 1 / 4, 1 / 32);
64 $this->random->setSeed($this->seed);
65
66 $this->selector = new class($this->random) extends BiomeSelector{
67 protected function lookup(float $temperature, float $rainfall) : int{
68 if($rainfall < 0.25){
69 if($temperature < 0.7){
70 return BiomeIds::OCEAN;
71 }elseif($temperature < 0.85){
72 return BiomeIds::RIVER;
73 }else{
74 return BiomeIds::SWAMPLAND;
75 }
76 }elseif($rainfall < 0.60){
77 if($temperature < 0.25){
78 return BiomeIds::ICE_PLAINS;
79 }elseif($temperature < 0.75){
80 return BiomeIds::PLAINS;
81 }else{
82 return BiomeIds::DESERT;
83 }
84 }elseif($rainfall < 0.80){
85 if($temperature < 0.25){
86 return BiomeIds::TAIGA;
87 }elseif($temperature < 0.75){
88 return BiomeIds::FOREST;
89 }else{
90 return BiomeIds::BIRCH_FOREST;
91 }
92 }else{
93 if($temperature < 0.20){
94 return BiomeIds::EXTREME_HILLS;
95 }elseif($temperature < 0.40){
96 return BiomeIds::EXTREME_HILLS_EDGE;
97 }else{
98 return BiomeIds::RIVER;
99 }
100 }
101 }
102 };
103
104 $this->selector->recalculate();
105
106 $cover = new GroundCover();
107 $this->generationPopulators[] = $cover;
108
109 $ores = new Ore();
110 $stone = VanillaBlocks::STONE();
111 $ores->setOreTypes([
112 new OreType(VanillaBlocks::COAL_ORE(), $stone, 20, 16, 0, 128),
113 new OreType(VanillaBlocks::IRON_ORE(), $stone, 20, 8, 0, 64),
114 new OreType(VanillaBlocks::REDSTONE_ORE(), $stone, 8, 7, 0, 16),
115 new OreType(VanillaBlocks::LAPIS_LAZULI_ORE(), $stone, 1, 6, 0, 32),
116 new OreType(VanillaBlocks::GOLD_ORE(), $stone, 2, 8, 0, 32),
117 new OreType(VanillaBlocks::DIAMOND_ORE(), $stone, 1, 7, 0, 16),
118 new OreType(VanillaBlocks::DIRT(), $stone, 20, 32, 0, 128),
119 new OreType(VanillaBlocks::GRAVEL(), $stone, 10, 16, 0, 128)
120 ]);
121 $this->populators[] = $ores;
122 }
123
124 private function pickBiome(int $x, int $z) : Biome{
125 $hash = $x * 2345803 ^ $z * 9236449 ^ $this->seed;
126 $hash *= $hash + 223;
127 //the above operations may result in a float. This probably wasn't intended, but we need to stick with it to
128 //avoid cliff edges in existing user worlds.
129 //We need to mod this so it doesn't generate an error in PHP 8.5 when we cast it back to an int.
130 $hash = (int) fmod($hash, 2.0 ** 63);
131 $xNoise = $hash >> 20 & 3;
132 $zNoise = $hash >> 22 & 3;
133 if($xNoise === 3){
134 $xNoise = 1;
135 }
136 if($zNoise === 3){
137 $zNoise = 1;
138 }
139
140 return $this->selector->pickBiome($x + $xNoise - 1, $z + $zNoise - 1);
141 }
142
143 public function generateChunk(ChunkManager $world, int $chunkX, int $chunkZ) : void{
144 $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->seed);
145
146 $noise = $this->noiseBase->getFastNoise3D(Chunk::EDGE_LENGTH, 128, Chunk::EDGE_LENGTH, 4, 8, 4, $chunkX * Chunk::EDGE_LENGTH, 0, $chunkZ * Chunk::EDGE_LENGTH);
147
148 //TODO: why don't we just create and set the chunk here directly?
149 $chunk = $world->getChunk($chunkX, $chunkZ) ?? throw new \InvalidArgumentException("Chunk $chunkX $chunkZ does not yet exist");
150
151 $biomeCache = [];
152
153 $bedrock = VanillaBlocks::BEDROCK()->getStateId();
154 $stillWater = VanillaBlocks::WATER()->getStateId();
155 $stone = VanillaBlocks::STONE()->getStateId();
156
157 $baseX = $chunkX * Chunk::EDGE_LENGTH;
158 $baseZ = $chunkZ * Chunk::EDGE_LENGTH;
159 for($x = 0; $x < Chunk::EDGE_LENGTH; ++$x){
160 $absoluteX = $baseX + $x;
161 for($z = 0; $z < Chunk::EDGE_LENGTH; ++$z){
162 $absoluteZ = $baseZ + $z;
163 $minSum = 0;
164 $maxSum = 0;
165 $weightSum = 0;
166
167 $biome = $this->pickBiome($absoluteX, $absoluteZ);
168 for($y = World::Y_MIN; $y < World::Y_MAX; $y++){
169 $chunk->setBiomeId($x, $y, $z, $biome->getId());
170 }
171
172 for($sx = -$this->gaussian->smoothSize; $sx <= $this->gaussian->smoothSize; ++$sx){
173 for($sz = -$this->gaussian->smoothSize; $sz <= $this->gaussian->smoothSize; ++$sz){
174
175 $weight = $this->gaussian->kernel[$sx + $this->gaussian->smoothSize][$sz + $this->gaussian->smoothSize];
176
177 if($sx === 0 && $sz === 0){
178 $adjacent = $biome;
179 }else{
180 $index = World::chunkHash($absoluteX + $sx, $absoluteZ + $sz);
181 if(isset($biomeCache[$index])){
182 $adjacent = $biomeCache[$index];
183 }else{
184 $biomeCache[$index] = $adjacent = $this->pickBiome($absoluteX + $sx, $absoluteZ + $sz);
185 }
186 }
187
188 $minSum += ($adjacent->getMinElevation() - 1) * $weight;
189 $maxSum += $adjacent->getMaxElevation() * $weight;
190
191 $weightSum += $weight;
192 }
193 }
194
195 $minSum /= $weightSum;
196 $maxSum /= $weightSum;
197
198 $smoothHeight = ($maxSum - $minSum) / 2;
199
200 for($y = 0; $y < 128; ++$y){
201 if($y === 0){
202 $chunk->setBlockStateId($x, $y, $z, $bedrock);
203 continue;
204 }
205 $noiseValue = $noise[$x][$z][$y] - 1 / $smoothHeight * ($y - $smoothHeight - $minSum);
206
207 if($noiseValue > 0){
208 $chunk->setBlockStateId($x, $y, $z, $stone);
209 }elseif($y <= $this->waterHeight){
210 $chunk->setBlockStateId($x, $y, $z, $stillWater);
211 }
212 }
213 }
214 }
215
216 foreach($this->generationPopulators as $populator){
217 $populator->populate($world, $chunkX, $chunkZ, $this->random);
218 }
219 }
220
221 public function populateChunk(ChunkManager $world, int $chunkX, int $chunkZ) : void{
222 $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->seed);
223 foreach($this->populators as $populator){
224 $populator->populate($world, $chunkX, $chunkZ, $this->random);
225 }
226
227 $chunk = $world->getChunk($chunkX, $chunkZ);
228 $biome = BiomeRegistry::getInstance()->getBiome($chunk->getBiomeId(7, 7, 7));
229 $biome->populateChunk($world, $chunkX, $chunkZ, $this->random);
230 }
231}
static chunkHash(int $x, int $z)
Definition World.php:385
__construct(int $seed, string $preset)
Definition Normal.php:58