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