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