]> git.lizzy.rs Git - dragonfireclient.git/blob - src/mapgen/mapgen_v6.cpp
Drop content_sao.{cpp,h}
[dragonfireclient.git] / src / mapgen / mapgen_v6.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2018 celeron55, Perttu Ahola <celeron55@gmail.com>
4 Copyright (C) 2013-2018 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
5 Copyright (C) 2014-2018 paramat
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License along
18 with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22
23 #include <cmath>
24 #include "mapgen.h"
25 #include "voxel.h"
26 #include "noise.h"
27 #include "mapblock.h"
28 #include "mapnode.h"
29 #include "map.h"
30 #include "nodedef.h"
31 #include "voxelalgorithms.h"
32 //#include "profiler.h" // For TimeTaker
33 #include "settings.h" // For g_settings
34 #include "emerge.h"
35 #include "dungeongen.h"
36 #include "cavegen.h"
37 #include "treegen.h"
38 #include "mg_ore.h"
39 #include "mg_decoration.h"
40 #include "mapgen_v6.h"
41
42
43 FlagDesc flagdesc_mapgen_v6[] = {
44         {"jungles",    MGV6_JUNGLES},
45         {"biomeblend", MGV6_BIOMEBLEND},
46         {"mudflow",    MGV6_MUDFLOW},
47         {"snowbiomes", MGV6_SNOWBIOMES},
48         {"flat",       MGV6_FLAT},
49         {"trees",      MGV6_TREES},
50         {NULL,         0}
51 };
52
53
54 /////////////////////////////////////////////////////////////////////////////
55
56
57 MapgenV6::MapgenV6(MapgenV6Params *params, EmergeManager *emerge)
58         : Mapgen(MAPGEN_V6, params, emerge)
59 {
60         m_emerge = emerge;
61         ystride = csize.X;
62
63         heightmap = new s16[csize.X * csize.Z];
64
65         spflags      = params->spflags;
66         freq_desert  = params->freq_desert;
67         freq_beach   = params->freq_beach;
68         dungeon_ymin = params->dungeon_ymin;
69         dungeon_ymax = params->dungeon_ymax;
70
71         np_cave        = &params->np_cave;
72         np_humidity    = &params->np_humidity;
73         np_trees       = &params->np_trees;
74         np_apple_trees = &params->np_apple_trees;
75
76         np_dungeons = NoiseParams(0.9, 0.5, v3f(500.0, 500.0, 500.0), 0, 2, 0.8, 2.0);
77
78         //// Create noise objects
79         noise_terrain_base   = new Noise(&params->np_terrain_base,   seed, csize.X, csize.Y);
80         noise_terrain_higher = new Noise(&params->np_terrain_higher, seed, csize.X, csize.Y);
81         noise_steepness      = new Noise(&params->np_steepness,      seed, csize.X, csize.Y);
82         noise_height_select  = new Noise(&params->np_height_select,  seed, csize.X, csize.Y);
83         noise_mud            = new Noise(&params->np_mud,            seed, csize.X, csize.Y);
84         noise_beach          = new Noise(&params->np_beach,          seed, csize.X, csize.Y);
85         noise_biome          = new Noise(&params->np_biome,          seed,
86                         csize.X + 2 * MAP_BLOCKSIZE, csize.Y + 2 * MAP_BLOCKSIZE);
87         noise_humidity       = new Noise(&params->np_humidity,       seed,
88                         csize.X + 2 * MAP_BLOCKSIZE, csize.Y + 2 * MAP_BLOCKSIZE);
89
90         //// Resolve nodes to be used
91         const NodeDefManager *ndef = emerge->ndef;
92
93         c_stone           = ndef->getId("mapgen_stone");
94         c_dirt            = ndef->getId("mapgen_dirt");
95         c_dirt_with_grass = ndef->getId("mapgen_dirt_with_grass");
96         c_sand            = ndef->getId("mapgen_sand");
97         c_water_source    = ndef->getId("mapgen_water_source");
98         c_lava_source     = ndef->getId("mapgen_lava_source");
99         c_gravel          = ndef->getId("mapgen_gravel");
100         c_desert_stone    = ndef->getId("mapgen_desert_stone");
101         c_desert_sand     = ndef->getId("mapgen_desert_sand");
102         c_dirt_with_snow  = ndef->getId("mapgen_dirt_with_snow");
103         c_snow            = ndef->getId("mapgen_snow");
104         c_snowblock       = ndef->getId("mapgen_snowblock");
105         c_ice             = ndef->getId("mapgen_ice");
106
107         if (c_gravel == CONTENT_IGNORE)
108                 c_gravel = c_stone;
109         if (c_desert_stone == CONTENT_IGNORE)
110                 c_desert_stone = c_stone;
111         if (c_desert_sand == CONTENT_IGNORE)
112                 c_desert_sand = c_sand;
113         if (c_dirt_with_snow == CONTENT_IGNORE)
114                 c_dirt_with_snow = c_dirt_with_grass;
115         if (c_snow == CONTENT_IGNORE)
116                 c_snow = CONTENT_AIR;
117         if (c_snowblock == CONTENT_IGNORE)
118                 c_snowblock = c_dirt_with_grass;
119         if (c_ice == CONTENT_IGNORE)
120                 c_ice = c_water_source;
121
122         c_cobble             = ndef->getId("mapgen_cobble");
123         c_mossycobble        = ndef->getId("mapgen_mossycobble");
124         c_stair_cobble       = ndef->getId("mapgen_stair_cobble");
125         c_stair_desert_stone = ndef->getId("mapgen_stair_desert_stone");
126
127         if (c_mossycobble == CONTENT_IGNORE)
128                 c_mossycobble = c_cobble;
129         if (c_stair_cobble == CONTENT_IGNORE)
130                 c_stair_cobble = c_cobble;
131         if (c_stair_desert_stone == CONTENT_IGNORE)
132                 c_stair_desert_stone = c_desert_stone;
133
134         if (c_stone == CONTENT_IGNORE)
135                 errorstream << "Mapgen v6: Mapgen alias 'mapgen_stone' is invalid!" << std::endl;
136         if (c_dirt == CONTENT_IGNORE)
137                 errorstream << "Mapgen v6: Mapgen alias 'mapgen_dirt' is invalid!" << std::endl;
138         if (c_dirt_with_grass == CONTENT_IGNORE)
139                 errorstream << "Mapgen v6: Mapgen alias 'mapgen_dirt_with_grass' is invalid!" << std::endl;
140         if (c_sand == CONTENT_IGNORE)
141                 errorstream << "Mapgen v6: Mapgen alias 'mapgen_sand' is invalid!" << std::endl;
142         if (c_water_source == CONTENT_IGNORE)
143                 errorstream << "Mapgen v6: Mapgen alias 'mapgen_water_source' is invalid!" << std::endl;
144         if (c_lava_source == CONTENT_IGNORE)
145                 errorstream << "Mapgen v6: Mapgen alias 'mapgen_lava_source' is invalid!" << std::endl;
146         if (c_cobble == CONTENT_IGNORE)
147                 errorstream << "Mapgen v6: Mapgen alias 'mapgen_cobble' is invalid!" << std::endl;
148 }
149
150
151 MapgenV6::~MapgenV6()
152 {
153         delete noise_terrain_base;
154         delete noise_terrain_higher;
155         delete noise_steepness;
156         delete noise_height_select;
157         delete noise_mud;
158         delete noise_beach;
159         delete noise_biome;
160         delete noise_humidity;
161
162         delete[] heightmap;
163 }
164
165
166 MapgenV6Params::MapgenV6Params():
167         np_terrain_base   (-4,   20.0, v3f(250.0, 250.0, 250.0), 82341,  5, 0.6,  2.0),
168         np_terrain_higher (20,   16.0, v3f(500.0, 500.0, 500.0), 85039,  5, 0.6,  2.0),
169         np_steepness      (0.85, 0.5,  v3f(125.0, 125.0, 125.0), -932,   5, 0.7,  2.0),
170         np_height_select  (0,    1.0,  v3f(250.0, 250.0, 250.0), 4213,   5, 0.69, 2.0),
171         np_mud            (4,    2.0,  v3f(200.0, 200.0, 200.0), 91013,  3, 0.55, 2.0),
172         np_beach          (0,    1.0,  v3f(250.0, 250.0, 250.0), 59420,  3, 0.50, 2.0),
173         np_biome          (0,    1.0,  v3f(500.0, 500.0, 500.0), 9130,   3, 0.50, 2.0),
174         np_cave           (6,    6.0,  v3f(250.0, 250.0, 250.0), 34329,  3, 0.50, 2.0),
175         np_humidity       (0.5,  0.5,  v3f(500.0, 500.0, 500.0), 72384,  3, 0.50, 2.0),
176         np_trees          (0,    1.0,  v3f(125.0, 125.0, 125.0), 2,      4, 0.66, 2.0),
177         np_apple_trees    (0,    1.0,  v3f(100.0, 100.0, 100.0), 342902, 3, 0.45, 2.0)
178 {
179 }
180
181
182 void MapgenV6Params::readParams(const Settings *settings)
183 {
184         settings->getFlagStrNoEx("mgv6_spflags", spflags, flagdesc_mapgen_v6);
185         settings->getFloatNoEx("mgv6_freq_desert", freq_desert);
186         settings->getFloatNoEx("mgv6_freq_beach",  freq_beach);
187         settings->getS16NoEx("mgv6_dungeon_ymin",  dungeon_ymin);
188         settings->getS16NoEx("mgv6_dungeon_ymax",  dungeon_ymax);
189
190         settings->getNoiseParams("mgv6_np_terrain_base",   np_terrain_base);
191         settings->getNoiseParams("mgv6_np_terrain_higher", np_terrain_higher);
192         settings->getNoiseParams("mgv6_np_steepness",      np_steepness);
193         settings->getNoiseParams("mgv6_np_height_select",  np_height_select);
194         settings->getNoiseParams("mgv6_np_mud",            np_mud);
195         settings->getNoiseParams("mgv6_np_beach",          np_beach);
196         settings->getNoiseParams("mgv6_np_biome",          np_biome);
197         settings->getNoiseParams("mgv6_np_cave",           np_cave);
198         settings->getNoiseParams("mgv6_np_humidity",       np_humidity);
199         settings->getNoiseParams("mgv6_np_trees",          np_trees);
200         settings->getNoiseParams("mgv6_np_apple_trees",    np_apple_trees);
201 }
202
203
204 void MapgenV6Params::writeParams(Settings *settings) const
205 {
206         settings->setFlagStr("mgv6_spflags", spflags, flagdesc_mapgen_v6);
207         settings->setFloat("mgv6_freq_desert", freq_desert);
208         settings->setFloat("mgv6_freq_beach",  freq_beach);
209         settings->setS16("mgv6_dungeon_ymin",  dungeon_ymin);
210         settings->setS16("mgv6_dungeon_ymax",  dungeon_ymax);
211
212         settings->setNoiseParams("mgv6_np_terrain_base",   np_terrain_base);
213         settings->setNoiseParams("mgv6_np_terrain_higher", np_terrain_higher);
214         settings->setNoiseParams("mgv6_np_steepness",      np_steepness);
215         settings->setNoiseParams("mgv6_np_height_select",  np_height_select);
216         settings->setNoiseParams("mgv6_np_mud",            np_mud);
217         settings->setNoiseParams("mgv6_np_beach",          np_beach);
218         settings->setNoiseParams("mgv6_np_biome",          np_biome);
219         settings->setNoiseParams("mgv6_np_cave",           np_cave);
220         settings->setNoiseParams("mgv6_np_humidity",       np_humidity);
221         settings->setNoiseParams("mgv6_np_trees",          np_trees);
222         settings->setNoiseParams("mgv6_np_apple_trees",    np_apple_trees);
223 }
224
225
226 void MapgenV6Params::setDefaultSettings(Settings *settings)
227 {
228         settings->setDefault("mgv6_spflags", flagdesc_mapgen_v6, MGV6_JUNGLES |
229                 MGV6_SNOWBIOMES | MGV6_TREES | MGV6_BIOMEBLEND | MGV6_MUDFLOW);
230 }
231
232
233 //////////////////////// Some helper functions for the map generator
234
235
236 // Returns Y one under area minimum if not found
237 s16 MapgenV6::find_stone_level(v2s16 p2d)
238 {
239         const v3s16 &em = vm->m_area.getExtent();
240         s16 y_nodes_max = vm->m_area.MaxEdge.Y;
241         s16 y_nodes_min = vm->m_area.MinEdge.Y;
242         u32 i = vm->m_area.index(p2d.X, y_nodes_max, p2d.Y);
243         s16 y;
244
245         for (y = y_nodes_max; y >= y_nodes_min; y--) {
246                 content_t c = vm->m_data[i].getContent();
247                 if (c != CONTENT_IGNORE && (c == c_stone || c == c_desert_stone))
248                         break;
249
250                 VoxelArea::add_y(em, i, -1);
251         }
252         return (y >= y_nodes_min) ? y : y_nodes_min - 1;
253 }
254
255
256 // Required by mapgen.h
257 bool MapgenV6::block_is_underground(u64 seed, v3s16 blockpos)
258 {
259         /*s16 minimum_groundlevel = (s16)get_sector_minimum_ground_level(
260                         seed, v2s16(blockpos.X, blockpos.Z));*/
261         // Nah, this is just a heuristic, just return something
262         s16 minimum_groundlevel = water_level;
263
264         if(blockpos.Y * MAP_BLOCKSIZE + MAP_BLOCKSIZE <= minimum_groundlevel)
265                 return true;
266
267         return false;
268 }
269
270
271 //////////////////////// Base terrain height functions
272
273 float MapgenV6::baseTerrainLevel(float terrain_base, float terrain_higher,
274         float steepness, float height_select)
275 {
276         float base   = 1 + terrain_base;
277         float higher = 1 + terrain_higher;
278
279         // Limit higher ground level to at least base
280         if(higher < base)
281                 higher = base;
282
283         // Steepness factor of cliffs
284         float b = steepness;
285         b = rangelim(b, 0.0, 1000.0);
286         b = 5 * b * b * b * b * b * b * b;
287         b = rangelim(b, 0.5, 1000.0);
288
289         // Values 1.5...100 give quite horrible looking slopes
290         if (b > 1.5 && b < 100.0)
291                 b = (b < 10.0) ? 1.5 : 100.0;
292
293         float a_off = -0.20; // Offset to more low
294         float a = 0.5 + b * (a_off + height_select);
295         a = rangelim(a, 0.0, 1.0); // Limit
296
297         return base * (1.0 - a) + higher * a;
298 }
299
300
301 float MapgenV6::baseTerrainLevelFromNoise(v2s16 p)
302 {
303         if (spflags & MGV6_FLAT)
304                 return water_level;
305
306         float terrain_base   = NoisePerlin2D_PO(&noise_terrain_base->np,
307                                                         p.X, 0.5, p.Y, 0.5, seed);
308         float terrain_higher = NoisePerlin2D_PO(&noise_terrain_higher->np,
309                                                         p.X, 0.5, p.Y, 0.5, seed);
310         float steepness      = NoisePerlin2D_PO(&noise_steepness->np,
311                                                         p.X, 0.5, p.Y, 0.5, seed);
312         float height_select  = NoisePerlin2D_PO(&noise_height_select->np,
313                                                         p.X, 0.5, p.Y, 0.5, seed);
314
315         return baseTerrainLevel(terrain_base, terrain_higher,
316                                                         steepness, height_select);
317 }
318
319
320 float MapgenV6::baseTerrainLevelFromMap(v2s16 p)
321 {
322         int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X);
323         return baseTerrainLevelFromMap(index);
324 }
325
326
327 float MapgenV6::baseTerrainLevelFromMap(int index)
328 {
329         if (spflags & MGV6_FLAT)
330                 return water_level;
331
332         float terrain_base   = noise_terrain_base->result[index];
333         float terrain_higher = noise_terrain_higher->result[index];
334         float steepness      = noise_steepness->result[index];
335         float height_select  = noise_height_select->result[index];
336
337         return baseTerrainLevel(terrain_base, terrain_higher,
338                                                         steepness, height_select);
339 }
340
341
342 s16 MapgenV6::find_ground_level_from_noise(u64 seed, v2s16 p2d, s16 precision)
343 {
344         return baseTerrainLevelFromNoise(p2d) + MGV6_AVERAGE_MUD_AMOUNT;
345 }
346
347
348 int MapgenV6::getGroundLevelAtPoint(v2s16 p)
349 {
350         return baseTerrainLevelFromNoise(p) + MGV6_AVERAGE_MUD_AMOUNT;
351 }
352
353
354 int MapgenV6::getSpawnLevelAtPoint(v2s16 p)
355 {
356         s16 level_at_point = baseTerrainLevelFromNoise(p) + MGV6_AVERAGE_MUD_AMOUNT;
357         if (level_at_point <= water_level ||
358                         level_at_point > water_level + 16)
359                 return MAX_MAP_GENERATION_LIMIT;  // Unsuitable spawn point
360
361         return level_at_point;
362 }
363
364
365 //////////////////////// Noise functions
366
367 float MapgenV6::getMudAmount(v2s16 p)
368 {
369         int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X);
370         return getMudAmount(index);
371 }
372
373
374 bool MapgenV6::getHaveBeach(v2s16 p)
375 {
376         int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X);
377         return getHaveBeach(index);
378 }
379
380
381 BiomeV6Type MapgenV6::getBiome(v2s16 p)
382 {
383         int index = (p.Y - full_node_min.Z) * (ystride + 2 * MAP_BLOCKSIZE)
384                         + (p.X - full_node_min.X);
385         return getBiome(index, p);
386 }
387
388
389 float MapgenV6::getHumidity(v2s16 p)
390 {
391         /*double noise = noise2d_perlin(
392                 0.5+(float)p.X/500, 0.5+(float)p.Y/500,
393                 seed+72384, 4, 0.66);
394         noise = (noise + 1.0)/2.0;*/
395
396         int index = (p.Y - full_node_min.Z) * (ystride + 2 * MAP_BLOCKSIZE)
397                         + (p.X - full_node_min.X);
398         float noise = noise_humidity->result[index];
399
400         if (noise < 0.0)
401                 noise = 0.0;
402         if (noise > 1.0)
403                 noise = 1.0;
404         return noise;
405 }
406
407
408 float MapgenV6::getTreeAmount(v2s16 p)
409 {
410         /*double noise = noise2d_perlin(
411                         0.5+(float)p.X/125, 0.5+(float)p.Y/125,
412                         seed+2, 4, 0.66);*/
413
414         float noise = NoisePerlin2D(np_trees, p.X, p.Y, seed);
415         float zeroval = -0.39;
416         if (noise < zeroval)
417                 return 0;
418
419         return 0.04 * (noise - zeroval) / (1.0 - zeroval);
420 }
421
422
423 bool MapgenV6::getHaveAppleTree(v2s16 p)
424 {
425         /*is_apple_tree = noise2d_perlin(
426                 0.5+(float)p.X/100, 0.5+(float)p.Z/100,
427                 data->seed+342902, 3, 0.45) > 0.2;*/
428
429         float noise = NoisePerlin2D(np_apple_trees, p.X, p.Y, seed);
430
431         return noise > 0.2;
432 }
433
434
435 float MapgenV6::getMudAmount(int index)
436 {
437         if (spflags & MGV6_FLAT)
438                 return MGV6_AVERAGE_MUD_AMOUNT;
439
440         /*return ((float)AVERAGE_MUD_AMOUNT + 2.0 * noise2d_perlin(
441                         0.5+(float)p.X/200, 0.5+(float)p.Y/200,
442                         seed+91013, 3, 0.55));*/
443
444         return noise_mud->result[index];
445 }
446
447
448 bool MapgenV6::getHaveBeach(int index)
449 {
450         // Determine whether to have sand here
451         /*double sandnoise = noise2d_perlin(
452                         0.2+(float)p2d.X/250, 0.7+(float)p2d.Y/250,
453                         seed+59420, 3, 0.50);*/
454
455         float sandnoise = noise_beach->result[index];
456         return (sandnoise > freq_beach);
457 }
458
459
460 BiomeV6Type MapgenV6::getBiome(int index, v2s16 p)
461 {
462         // Just do something very simple as for now
463         /*double d = noise2d_perlin(
464                         0.6+(float)p2d.X/250, 0.2+(float)p2d.Y/250,
465                         seed+9130, 3, 0.50);*/
466
467         float d = noise_biome->result[index];
468         float h = noise_humidity->result[index];
469
470         if (spflags & MGV6_SNOWBIOMES) {
471                 float blend = (spflags & MGV6_BIOMEBLEND) ? noise2d(p.X, p.Y, seed) / 40 : 0;
472
473                 if (d > MGV6_FREQ_HOT + blend) {
474                         if (h > MGV6_FREQ_JUNGLE + blend)
475                                 return BT_JUNGLE;
476
477                         return BT_DESERT;
478                 }
479
480                 if (d < MGV6_FREQ_SNOW + blend) {
481                         if (h > MGV6_FREQ_TAIGA + blend)
482                                 return BT_TAIGA;
483
484                         return BT_TUNDRA;
485                 }
486
487                 return BT_NORMAL;
488         }
489
490         if (d > freq_desert)
491                 return BT_DESERT;
492
493         if ((spflags & MGV6_BIOMEBLEND) && (d > freq_desert - 0.10) &&
494                         ((noise2d(p.X, p.Y, seed) + 1.0) > (freq_desert - d) * 20.0))
495                 return BT_DESERT;
496
497         if ((spflags & MGV6_JUNGLES) && h > 0.75)
498                 return BT_JUNGLE;
499
500         return BT_NORMAL;
501
502 }
503
504
505 u32 MapgenV6::get_blockseed(u64 seed, v3s16 p)
506 {
507         s32 x = p.X, y = p.Y, z = p.Z;
508         return (u32)(seed % 0x100000000ULL) + z * 38134234 + y * 42123 + x * 23;
509 }
510
511
512 //////////////////////// Map generator
513
514 void MapgenV6::makeChunk(BlockMakeData *data)
515 {
516         // Pre-conditions
517         assert(data->vmanip);
518         assert(data->nodedef);
519         assert(data->blockpos_requested.X >= data->blockpos_min.X &&
520                 data->blockpos_requested.Y >= data->blockpos_min.Y &&
521                 data->blockpos_requested.Z >= data->blockpos_min.Z);
522         assert(data->blockpos_requested.X <= data->blockpos_max.X &&
523                 data->blockpos_requested.Y <= data->blockpos_max.Y &&
524                 data->blockpos_requested.Z <= data->blockpos_max.Z);
525
526         this->generating = true;
527         this->vm   = data->vmanip;
528         this->ndef = data->nodedef;
529
530         // Hack: use minimum block coords for old code that assumes a single block
531         v3s16 blockpos_min = data->blockpos_min;
532         v3s16 blockpos_max = data->blockpos_max;
533
534         // Area of central chunk
535         node_min = blockpos_min * MAP_BLOCKSIZE;
536         node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
537
538         // Full allocated area
539         full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
540         full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
541
542         central_area_size = node_max - node_min + v3s16(1, 1, 1);
543         assert(central_area_size.X == central_area_size.Z);
544
545         // Create a block-specific seed
546         blockseed = get_blockseed(data->seed, full_node_min);
547
548         // Make some noise
549         calculateNoise();
550
551         // Maximum height of the stone surface and obstacles.
552         // This is used to guide the cave generation
553         s16 stone_surface_max_y;
554
555         // Generate general ground level to full area
556         stone_surface_max_y = generateGround();
557
558         // Create initial heightmap to limit caves
559         updateHeightmap(node_min, node_max);
560
561         const s16 max_spread_amount = MAP_BLOCKSIZE;
562         // Limit dirt flow area by 1 because mud is flowed into neighbors
563         s16 mudflow_minpos = -max_spread_amount + 1;
564         s16 mudflow_maxpos = central_area_size.X + max_spread_amount - 2;
565
566         // Loop this part, it will make stuff look older and newer nicely
567         const u32 age_loops = 2;
568         for (u32 i_age = 0; i_age < age_loops; i_age++) { // Aging loop
569                 // Make caves (this code is relatively horrible)
570                 if (flags & MG_CAVES)
571                         generateCaves(stone_surface_max_y);
572
573                 // Add mud to the central chunk
574                 addMud();
575
576                 // Flow mud away from steep edges
577                 if (spflags & MGV6_MUDFLOW)
578                         flowMud(mudflow_minpos, mudflow_maxpos);
579
580         }
581
582         // Update heightmap after mudflow
583         updateHeightmap(node_min, node_max);
584
585         // Add dungeons
586         if ((flags & MG_DUNGEONS) && stone_surface_max_y >= node_min.Y &&
587                         full_node_min.Y >= dungeon_ymin && full_node_max.Y <= dungeon_ymax) {
588                 u16 num_dungeons = std::fmax(std::floor(
589                         NoisePerlin3D(&np_dungeons, node_min.X, node_min.Y, node_min.Z, seed)), 0.0f);
590
591                 if (num_dungeons >= 1) {
592                         PseudoRandom ps(blockseed + 4713);
593
594                         DungeonParams dp;
595
596                         dp.seed              = seed;
597                         dp.num_dungeons      = num_dungeons;
598                         dp.only_in_ground    = true;
599                         dp.corridor_len_min  = 1;
600                         dp.corridor_len_max  = 13;
601                         dp.num_rooms         = ps.range(2, 16);
602                         dp.large_room_chance = (ps.range(1, 4) == 1) ? 1 : 0;
603
604                         dp.np_alt_wall
605                                 = NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0);
606
607                         if (getBiome(0, v2s16(node_min.X, node_min.Z)) == BT_DESERT) {
608                                 dp.c_wall              = c_desert_stone;
609                                 dp.c_alt_wall          = CONTENT_IGNORE;
610                                 dp.c_stair             = c_stair_desert_stone;
611
612                                 dp.diagonal_dirs       = true;
613                                 dp.holesize            = v3s16(2, 3, 2);
614                                 dp.room_size_min       = v3s16(6, 9, 6);
615                                 dp.room_size_max       = v3s16(10, 11, 10);
616                                 dp.room_size_large_min = v3s16(10, 13, 10);
617                                 dp.room_size_large_max = v3s16(18, 21, 18);
618                                 dp.notifytype          = GENNOTIFY_TEMPLE;
619                         } else {
620                                 dp.c_wall              = c_cobble;
621                                 dp.c_alt_wall          = c_mossycobble;
622                                 dp.c_stair             = c_stair_cobble;
623
624                                 dp.diagonal_dirs       = false;
625                                 dp.holesize            = v3s16(1, 2, 1);
626                                 dp.room_size_min       = v3s16(4, 4, 4);
627                                 dp.room_size_max       = v3s16(8, 6, 8);
628                                 dp.room_size_large_min = v3s16(8, 8, 8);
629                                 dp.room_size_large_max = v3s16(16, 16, 16);
630                                 dp.notifytype          = GENNOTIFY_DUNGEON;
631                         }
632
633                         DungeonGen dgen(ndef, &gennotify, &dp);
634                         dgen.generate(vm, blockseed, full_node_min, full_node_max);
635                 }
636         }
637
638         // Add top and bottom side of water to transforming_liquid queue
639         updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
640
641         // Add surface nodes
642         growGrass();
643
644         // Generate some trees, and add grass, if a jungle
645         if (spflags & MGV6_TREES)
646                 placeTreesAndJungleGrass();
647
648         // Generate the registered decorations
649         if (flags & MG_DECORATIONS)
650                 m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
651
652         // Generate the registered ores
653         m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
654
655         // Calculate lighting
656         if (flags & MG_LIGHT)
657                 calcLighting(node_min - v3s16(1, 1, 1) * MAP_BLOCKSIZE,
658                         node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE,
659                         full_node_min, full_node_max);
660
661         this->generating = false;
662 }
663
664
665 void MapgenV6::calculateNoise()
666 {
667         int x = node_min.X;
668         int z = node_min.Z;
669         int fx = full_node_min.X;
670         int fz = full_node_min.Z;
671
672         if (!(spflags & MGV6_FLAT)) {
673                 noise_terrain_base->perlinMap2D_PO(x, 0.5, z, 0.5);
674                 noise_terrain_higher->perlinMap2D_PO(x, 0.5, z, 0.5);
675                 noise_steepness->perlinMap2D_PO(x, 0.5, z, 0.5);
676                 noise_height_select->perlinMap2D_PO(x, 0.5, z, 0.5);
677                 noise_mud->perlinMap2D_PO(x, 0.5, z, 0.5);
678         }
679
680         noise_beach->perlinMap2D_PO(x, 0.2, z, 0.7);
681
682         noise_biome->perlinMap2D_PO(fx, 0.6, fz, 0.2);
683         noise_humidity->perlinMap2D_PO(fx, 0.0, fz, 0.0);
684         // Humidity map does not need range limiting 0 to 1,
685         // only humidity at point does
686 }
687
688
689 int MapgenV6::generateGround()
690 {
691         //TimeTaker timer1("Generating ground level");
692         MapNode n_air(CONTENT_AIR), n_water_source(c_water_source);
693         MapNode n_stone(c_stone), n_desert_stone(c_desert_stone);
694         MapNode n_ice(c_ice);
695         int stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
696
697         u32 index = 0;
698         for (s16 z = node_min.Z; z <= node_max.Z; z++)
699         for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
700                 // Surface height
701                 s16 surface_y = (s16)baseTerrainLevelFromMap(index);
702
703                 // Log it
704                 if (surface_y > stone_surface_max_y)
705                         stone_surface_max_y = surface_y;
706
707                 BiomeV6Type bt = getBiome(v2s16(x, z));
708
709                 // Fill ground with stone
710                 const v3s16 &em = vm->m_area.getExtent();
711                 u32 i = vm->m_area.index(x, node_min.Y, z);
712                 for (s16 y = node_min.Y; y <= node_max.Y; y++) {
713                         if (vm->m_data[i].getContent() == CONTENT_IGNORE) {
714                                 if (y <= surface_y) {
715                                         vm->m_data[i] = (y >= MGV6_DESERT_STONE_BASE
716                                                         && bt == BT_DESERT) ?
717                                                 n_desert_stone : n_stone;
718                                 } else if (y <= water_level) {
719                                         vm->m_data[i] = (y >= MGV6_ICE_BASE
720                                                         && bt == BT_TUNDRA) ?
721                                                 n_ice : n_water_source;
722                                 } else {
723                                         vm->m_data[i] = n_air;
724                                 }
725                         }
726                         VoxelArea::add_y(em, i, 1);
727                 }
728         }
729
730         return stone_surface_max_y;
731 }
732
733
734 void MapgenV6::addMud()
735 {
736         // 15ms @cs=8
737         //TimeTaker timer1("add mud");
738         MapNode n_dirt(c_dirt), n_gravel(c_gravel);
739         MapNode n_sand(c_sand), n_desert_sand(c_desert_sand);
740         MapNode addnode;
741
742         u32 index = 0;
743         for (s16 z = node_min.Z; z <= node_max.Z; z++)
744         for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
745                 // Randomize mud amount
746                 s16 mud_add_amount = getMudAmount(index) / 2.0 + 0.5;
747
748                 // Find ground level
749                 s16 surface_y = find_stone_level(v2s16(x, z)); /////////////////optimize this!
750
751                 // Handle area not found
752                 if (surface_y == vm->m_area.MinEdge.Y - 1)
753                         continue;
754
755                 BiomeV6Type bt = getBiome(v2s16(x, z));
756                 addnode = (bt == BT_DESERT) ? n_desert_sand : n_dirt;
757
758                 if (bt == BT_DESERT && surface_y + mud_add_amount <= water_level + 1) {
759                         addnode = n_sand;
760                 } else if (mud_add_amount <= 0) {
761                         mud_add_amount = 1 - mud_add_amount;
762                         addnode = n_gravel;
763                 } else if (bt != BT_DESERT && getHaveBeach(index) &&
764                                 surface_y + mud_add_amount <= water_level + 2) {
765                         addnode = n_sand;
766                 }
767
768                 if ((bt == BT_DESERT || bt == BT_TUNDRA) && surface_y > 20)
769                         mud_add_amount = MYMAX(0, mud_add_amount - (surface_y - 20) / 5);
770
771                 /* If topmost node is grass, change it to mud.  It might be if it was
772                 // flown to there from a neighboring chunk and then converted.
773                 u32 i = vm->m_area.index(x, surface_y, z);
774                 if (vm->m_data[i].getContent() == c_dirt_with_grass)
775                         vm->m_data[i] = n_dirt;*/
776
777                 // Add mud on ground
778                 s16 mudcount = 0;
779                 const v3s16 &em = vm->m_area.getExtent();
780                 s16 y_start = surface_y + 1;
781                 u32 i = vm->m_area.index(x, y_start, z);
782                 for (s16 y = y_start; y <= node_max.Y; y++) {
783                         if (mudcount >= mud_add_amount)
784                                 break;
785
786                         vm->m_data[i] = addnode;
787                         mudcount++;
788
789                         VoxelArea::add_y(em, i, 1);
790                 }
791         }
792 }
793
794
795 void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos)
796 {
797         const v3s16 &em = vm->m_area.getExtent();
798         static const v3s16 dirs4[4] = {
799                 v3s16(0, 0, 1), // Back
800                 v3s16(1, 0, 0), // Right
801                 v3s16(0, 0, -1), // Front
802                 v3s16(-1, 0, 0), // Left
803         };
804         
805         // Iterate twice
806         for (s16 k = 0; k < 2; k++) {
807                 for (s16 z = mudflow_minpos; z <= mudflow_maxpos; z++)
808                 for (s16 x = mudflow_minpos; x <= mudflow_maxpos; x++) {
809                         // Node column position
810                         v2s16 p2d;
811                         // Invert coordinates on second iteration to process columns in
812                         // opposite order, to avoid a directional bias.
813                         if (k == 1)
814                                 p2d = v2s16(node_max.X, node_max.Z) - v2s16(x, z);
815                         else
816                                 p2d = v2s16(node_min.X, node_min.Z) + v2s16(x, z);
817
818                         s16 y = node_max.Y;
819
820                         while (y >= node_min.Y) {
821                                 for (;; y--) {
822                                         u32 i = vm->m_area.index(p2d.X, y, p2d.Y);
823                                         MapNode *n = nullptr;
824
825                                         // Find next mud node in mapchunk column
826                                         for (; y >= node_min.Y; y--) {
827                                                 n = &vm->m_data[i];
828                                                 if (n->getContent() == c_dirt ||
829                                                                 n->getContent() == c_dirt_with_grass ||
830                                                                 n->getContent() == c_gravel)
831                                                         break;
832
833                                                 VoxelArea::add_y(em, i, -1);
834                                         }
835                                         if (y < node_min.Y)
836                                                 // No mud found in mapchunk column, process the next column
837                                                 break;
838
839                                         if (n->getContent() == c_dirt || n->getContent() == c_dirt_with_grass) {
840                                                 // Convert dirt_with_grass to dirt
841                                                 n->setContent(c_dirt);
842                                                 // Don't flow mud if the stuff under it is not mud,
843                                                 // to leave at least 1 node of mud.
844                                                 u32 i2 = i;
845                                                 VoxelArea::add_y(em, i2, -1);
846                                                 MapNode *n2 = &vm->m_data[i2];
847                                                 if (n2->getContent() != c_dirt &&
848                                                                 n2->getContent() != c_dirt_with_grass)
849                                                         // Find next mud node in column
850                                                         continue;
851                                         }
852
853                                         // Check if node above is walkable. If so, cancel
854                                         // flowing as if node above keeps it in place.
855                                         u32 i3 = i;
856                                         VoxelArea::add_y(em, i3, 1);
857                                         MapNode *n3 = &vm->m_data[i3];
858                                         if (ndef->get(*n3).walkable)
859                                                 // Find next mud node in column
860                                                 continue;
861
862                                         // Drop mud on one side
863                                         for (const v3s16 &dirp : dirs4) {
864                                                 u32 i2 = i;
865                                                 // Move to side
866                                                 VoxelArea::add_p(em, i2, dirp);
867                                                 // Check that side is air
868                                                 MapNode *n2 = &vm->m_data[i2];
869                                                 if (ndef->get(*n2).walkable)
870                                                         continue;
871
872                                                 // Check that under side is air
873                                                 VoxelArea::add_y(em, i2, -1);
874                                                 n2 = &vm->m_data[i2];
875                                                 if (ndef->get(*n2).walkable)
876                                                         continue;
877
878                                                 // Loop further down until not air
879                                                 s16 y2 = y - 1; // y of i2
880                                                 bool dropped_to_unknown = false;
881                                                 do {
882                                                         y2--;
883                                                         VoxelArea::add_y(em, i2, -1);
884                                                         n2 = &vm->m_data[i2];
885                                                         // If out of area or in ungenerated world
886                                                         if (y2 < full_node_min.Y || n2->getContent() == CONTENT_IGNORE) {
887                                                                 dropped_to_unknown = true;
888                                                                 break;
889                                                         }
890                                                 } while (!ndef->get(*n2).walkable);
891
892                                                 if (!dropped_to_unknown) {
893                                                         // Move up one so that we're in air
894                                                         VoxelArea::add_y(em, i2, 1);
895                                                         // Move mud to new place, and if outside mapchunk remove
896                                                         // any decorations above removed or placed mud.
897                                                         moveMud(i, i2, i3, p2d, em);
898                                                 }
899                                                 // Done, find next mud node in column
900                                                 break;
901                                         }
902                                 }
903                         }
904                 }
905         }
906 }
907
908
909 void MapgenV6::moveMud(u32 remove_index, u32 place_index,
910         u32 above_remove_index, v2s16 pos, v3s16 em)
911 {
912         MapNode n_air(CONTENT_AIR);
913         // Copy mud from old place to new place
914         vm->m_data[place_index] = vm->m_data[remove_index];
915         // Set old place to be air
916         vm->m_data[remove_index] = n_air;
917         // Outside the mapchunk decorations may need to be removed if above removed
918         // mud or if half-buried in placed mud. Placed mud is to the side of pos so
919         // use 'pos.X >= node_max.X' etc.
920         if (pos.X >= node_max.X || pos.X <= node_min.X ||
921                         pos.Y >= node_max.Z || pos.Y <= node_min.Z) {
922                 // 'above remove' node is above removed mud. If it is not air, water or
923                 // 'ignore' it is a decoration that needs removing. Also search upwards
924                 // to remove a possible stacked decoration.
925                 // Check for 'ignore' because stacked decorations can penetrate into
926                 // 'ignore' nodes above the mapchunk.
927                 while (vm->m_area.contains(above_remove_index) &&
928                                 vm->m_data[above_remove_index].getContent() != CONTENT_AIR &&
929                                 vm->m_data[above_remove_index].getContent() != c_water_source &&
930                                 vm->m_data[above_remove_index].getContent() != CONTENT_IGNORE) {
931                         vm->m_data[above_remove_index] = n_air;
932                         VoxelArea::add_y(em, above_remove_index, 1);
933                 }
934                 // Mud placed may have partially-buried a stacked decoration, search
935                 // above and remove.
936                 VoxelArea::add_y(em, place_index, 1);
937                 while (vm->m_area.contains(place_index) &&
938                                 vm->m_data[place_index].getContent() != CONTENT_AIR &&
939                                 vm->m_data[place_index].getContent() != c_water_source &&
940                                 vm->m_data[place_index].getContent() != CONTENT_IGNORE) {
941                         vm->m_data[place_index] = n_air;
942                         VoxelArea::add_y(em, place_index, 1);
943                 }
944         }
945 }
946
947
948 void MapgenV6::placeTreesAndJungleGrass()
949 {
950         //TimeTaker t("placeTrees");
951         if (node_max.Y < water_level)
952                 return;
953
954         PseudoRandom grassrandom(blockseed + 53);
955         content_t c_junglegrass = ndef->getId("mapgen_junglegrass");
956         // if we don't have junglegrass, don't place cignore... that's bad
957         if (c_junglegrass == CONTENT_IGNORE)
958                 c_junglegrass = CONTENT_AIR;
959         MapNode n_junglegrass(c_junglegrass);
960         const v3s16 &em = vm->m_area.getExtent();
961
962         // Divide area into parts
963         s16 div = 8;
964         s16 sidelen = central_area_size.X / div;
965         double area = sidelen * sidelen;
966
967         // N.B.  We must add jungle grass first, since tree leaves will
968         // obstruct the ground, giving us a false ground level
969         for (s16 z0 = 0; z0 < div; z0++)
970         for (s16 x0 = 0; x0 < div; x0++) {
971                 // Center position of part of division
972                 v2s16 p2d_center(
973                         node_min.X + sidelen / 2 + sidelen * x0,
974                         node_min.Z + sidelen / 2 + sidelen * z0
975                 );
976                 // Minimum edge of part of division
977                 v2s16 p2d_min(
978                         node_min.X + sidelen * x0,
979                         node_min.Z + sidelen * z0
980                 );
981                 // Maximum edge of part of division
982                 v2s16 p2d_max(
983                         node_min.X + sidelen + sidelen * x0 - 1,
984                         node_min.Z + sidelen + sidelen * z0 - 1
985                 );
986
987                 // Get biome at center position of part of division
988                 BiomeV6Type bt = getBiome(p2d_center);
989
990                 // Amount of trees
991                 u32 tree_count;
992                 if (bt == BT_JUNGLE || bt == BT_TAIGA || bt == BT_NORMAL) {
993                         tree_count = area * getTreeAmount(p2d_center);
994                         if (bt == BT_JUNGLE)
995                                 tree_count *= 4;
996                 } else {
997                         tree_count = 0;
998                 }
999
1000                 // Add jungle grass
1001                 if (bt == BT_JUNGLE) {
1002                         float humidity = getHumidity(p2d_center);
1003                         u32 grass_count = 5 * humidity * tree_count;
1004                         for (u32 i = 0; i < grass_count; i++) {
1005                                 s16 x = grassrandom.range(p2d_min.X, p2d_max.X);
1006                                 s16 z = grassrandom.range(p2d_min.Y, p2d_max.Y);
1007                                 int mapindex = central_area_size.X * (z - node_min.Z)
1008                                                                 + (x - node_min.X);
1009                                 s16 y = heightmap[mapindex];
1010                                 if (y < water_level)
1011                                         continue;
1012
1013                                 u32 vi = vm->m_area.index(x, y, z);
1014                                 // place on dirt_with_grass, since we know it is exposed to sunlight
1015                                 if (vm->m_data[vi].getContent() == c_dirt_with_grass) {
1016                                         VoxelArea::add_y(em, vi, 1);
1017                                         vm->m_data[vi] = n_junglegrass;
1018                                 }
1019                         }
1020                 }
1021
1022                 // Put trees in random places on part of division
1023                 for (u32 i = 0; i < tree_count; i++) {
1024                         s16 x = myrand_range(p2d_min.X, p2d_max.X);
1025                         s16 z = myrand_range(p2d_min.Y, p2d_max.Y);
1026                         int mapindex = central_area_size.X * (z - node_min.Z)
1027                                                         + (x - node_min.X);
1028                         s16 y = heightmap[mapindex];
1029                         // Don't make a tree under water level
1030                         // Don't make a tree so high that it doesn't fit
1031                         if (y < water_level || y > node_max.Y - 6)
1032                                 continue;
1033
1034                         v3s16 p(x, y, z);
1035                         // Trees grow only on mud and grass
1036                         {
1037                                 u32 i = vm->m_area.index(p);
1038                                 content_t c = vm->m_data[i].getContent();
1039                                 if (c != c_dirt &&
1040                                                 c != c_dirt_with_grass &&
1041                                                 c != c_dirt_with_snow)
1042                                         continue;
1043                         }
1044                         p.Y++;
1045
1046                         // Make a tree
1047                         if (bt == BT_JUNGLE) {
1048                                 treegen::make_jungletree(*vm, p, ndef, myrand());
1049                         } else if (bt == BT_TAIGA) {
1050                                 treegen::make_pine_tree(*vm, p - v3s16(0, 1, 0), ndef, myrand());
1051                         } else if (bt == BT_NORMAL) {
1052                                 bool is_apple_tree = (myrand_range(0, 3) == 0) &&
1053                                                         getHaveAppleTree(v2s16(x, z));
1054                                 treegen::make_tree(*vm, p, is_apple_tree, ndef, myrand());
1055                         }
1056                 }
1057         }
1058         //printf("placeTreesAndJungleGrass: %dms\n", t.stop());
1059 }
1060
1061
1062 void MapgenV6::growGrass() // Add surface nodes
1063 {
1064         MapNode n_dirt_with_grass(c_dirt_with_grass);
1065         MapNode n_dirt_with_snow(c_dirt_with_snow);
1066         MapNode n_snowblock(c_snowblock);
1067         MapNode n_snow(c_snow);
1068         const v3s16 &em = vm->m_area.getExtent();
1069
1070         u32 index = 0;
1071         for (s16 z = full_node_min.Z; z <= full_node_max.Z; z++)
1072         for (s16 x = full_node_min.X; x <= full_node_max.X; x++, index++) {
1073                 // Find the lowest surface to which enough light ends up to make
1074                 // grass grow.  Basically just wait until not air and not leaves.
1075                 s16 surface_y = 0;
1076                 {
1077                         u32 i = vm->m_area.index(x, node_max.Y, z);
1078                         s16 y;
1079                         // Go to ground level
1080                         for (y = node_max.Y; y >= full_node_min.Y; y--) {
1081                                 MapNode &n = vm->m_data[i];
1082                                 if (ndef->get(n).param_type != CPT_LIGHT ||
1083                                                 ndef->get(n).liquid_type != LIQUID_NONE ||
1084                                                 n.getContent() == c_ice)
1085                                         break;
1086                                 VoxelArea::add_y(em, i, -1);
1087                         }
1088                         surface_y = (y >= full_node_min.Y) ? y : full_node_min.Y;
1089                 }
1090
1091                 BiomeV6Type bt = getBiome(index, v2s16(x, z));
1092                 u32 i = vm->m_area.index(x, surface_y, z);
1093                 content_t c = vm->m_data[i].getContent();
1094                 if (surface_y >= water_level - 20) {
1095                         if (bt == BT_TAIGA && c == c_dirt) {
1096                                 vm->m_data[i] = n_dirt_with_snow;
1097                         } else if (bt == BT_TUNDRA) {
1098                                 if (c == c_dirt) {
1099                                         vm->m_data[i] = n_snowblock;
1100                                         VoxelArea::add_y(em, i, -1);
1101                                         vm->m_data[i] = n_dirt_with_snow;
1102                                 } else if (c == c_stone && surface_y < node_max.Y) {
1103                                         VoxelArea::add_y(em, i, 1);
1104                                         vm->m_data[i] = n_snowblock;
1105                                 }
1106                         } else if (c == c_dirt) {
1107                                 vm->m_data[i] = n_dirt_with_grass;
1108                         }
1109                 }
1110         }
1111 }
1112
1113
1114 void MapgenV6::generateCaves(int max_stone_y)
1115 {
1116         float cave_amount = NoisePerlin2D(np_cave, node_min.X, node_min.Y, seed);
1117         int volume_nodes = (node_max.X - node_min.X + 1) *
1118                                            (node_max.Y - node_min.Y + 1) * MAP_BLOCKSIZE;
1119         cave_amount = MYMAX(0.0, cave_amount);
1120         u32 caves_count = cave_amount * volume_nodes / 50000;
1121         u32 bruises_count = 1;
1122         PseudoRandom ps(blockseed + 21343);
1123         PseudoRandom ps2(blockseed + 1032);
1124
1125         if (ps.range(1, 6) == 1)
1126                 bruises_count = ps.range(0, ps.range(0, 2));
1127
1128         if (getBiome(v2s16(node_min.X, node_min.Z)) == BT_DESERT) {
1129                 caves_count   /= 3;
1130                 bruises_count /= 3;
1131         }
1132
1133         for (u32 i = 0; i < caves_count + bruises_count; i++) {
1134                 CavesV6 cave(ndef, &gennotify, water_level, c_water_source, c_lava_source);
1135
1136                 bool large_cave = (i >= caves_count);
1137                 cave.makeCave(vm, node_min, node_max, &ps, &ps2,
1138                         large_cave, max_stone_y, heightmap);
1139         }
1140 }