summaryrefslogtreecommitdiff
path: root/src/mapgen
diff options
context:
space:
mode:
Diffstat (limited to 'src/mapgen')
-rw-r--r--src/mapgen/CMakeLists.txt19
-rw-r--r--src/mapgen/cavegen.cpp882
-rw-r--r--src/mapgen/cavegen.h242
-rw-r--r--src/mapgen/dungeongen.cpp677
-rw-r--r--src/mapgen/dungeongen.h110
-rw-r--r--src/mapgen/mapgen.cpp1140
-rw-r--r--src/mapgen/mapgen.h302
-rw-r--r--src/mapgen/mapgen_carpathian.cpp454
-rw-r--r--src/mapgen/mapgen_carpathian.h102
-rw-r--r--src/mapgen/mapgen_flat.cpp274
-rw-r--r--src/mapgen/mapgen_flat.h76
-rw-r--r--src/mapgen/mapgen_fractal.cpp404
-rw-r--r--src/mapgen/mapgen_fractal.h87
-rw-r--r--src/mapgen/mapgen_singlenode.cpp102
-rw-r--r--src/mapgen/mapgen_singlenode.h49
-rw-r--r--src/mapgen/mapgen_v5.cpp295
-rw-r--r--src/mapgen/mapgen_v5.h74
-rw-r--r--src/mapgen/mapgen_v6.cpp1123
-rw-r--r--src/mapgen/mapgen_v6.h169
-rw-r--r--src/mapgen/mapgen_v7.cpp743
-rw-r--r--src/mapgen/mapgen_v7.h111
-rw-r--r--src/mapgen/mapgen_valleys.cpp743
-rw-r--r--src/mapgen/mapgen_valleys.h134
-rw-r--r--src/mapgen/mg_biome.cpp238
-rw-r--r--src/mapgen/mg_biome.h230
-rw-r--r--src/mapgen/mg_decoration.cpp377
-rw-r--r--src/mapgen/mg_decoration.h136
-rw-r--r--src/mapgen/mg_ore.cpp483
-rw-r--r--src/mapgen/mg_ore.h183
-rw-r--r--src/mapgen/mg_schematic.cpp578
-rw-r--r--src/mapgen/mg_schematic.h147
-rw-r--r--src/mapgen/treegen.cpp872
-rw-r--r--src/mapgen/treegen.h92
33 files changed, 11648 insertions, 0 deletions
diff --git a/src/mapgen/CMakeLists.txt b/src/mapgen/CMakeLists.txt
new file mode 100644
index 000000000..e74bd85db
--- /dev/null
+++ b/src/mapgen/CMakeLists.txt
@@ -0,0 +1,19 @@
+set(mapgen_SRCS
+ ${CMAKE_CURRENT_SOURCE_DIR}/cavegen.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/dungeongen.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_carpathian.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_flat.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_fractal.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_singlenode.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_v5.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_v6.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_v7.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_valleys.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mg_biome.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mg_decoration.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mg_ore.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mg_schematic.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/treegen.cpp
+ PARENT_SCOPE
+)
diff --git a/src/mapgen/cavegen.cpp b/src/mapgen/cavegen.cpp
new file mode 100644
index 000000000..e66634517
--- /dev/null
+++ b/src/mapgen/cavegen.cpp
@@ -0,0 +1,882 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "util/numeric.h"
+#include "map.h"
+#include "mapgen.h"
+#include "mapgen_v5.h"
+#include "mapgen_v6.h"
+#include "mapgen_v7.h"
+#include "mg_biome.h"
+#include "cavegen.h"
+
+static NoiseParams nparams_caveliquids(0, 1, v3f(150.0, 150.0, 150.0), 776, 3, 0.6, 2.0);
+
+
+////
+//// CavesNoiseIntersection
+////
+
+CavesNoiseIntersection::CavesNoiseIntersection(
+ INodeDefManager *nodedef, BiomeManager *biomemgr, v3s16 chunksize,
+ NoiseParams *np_cave1, NoiseParams *np_cave2, s32 seed, float cave_width)
+{
+ assert(nodedef);
+ assert(biomemgr);
+
+ m_ndef = nodedef;
+ m_bmgr = biomemgr;
+
+ m_csize = chunksize;
+ m_cave_width = cave_width;
+
+ m_ystride = m_csize.X;
+ m_zstride_1d = m_csize.X * (m_csize.Y + 1);
+
+ // Noises are created using 1-down overgeneration
+ // A Nx-by-1-by-Nz-sized plane is at the bottom of the desired for
+ // re-carving the solid overtop placed for blocking sunlight
+ noise_cave1 = new Noise(np_cave1, seed, m_csize.X, m_csize.Y + 1, m_csize.Z);
+ noise_cave2 = new Noise(np_cave2, seed, m_csize.X, m_csize.Y + 1, m_csize.Z);
+}
+
+
+CavesNoiseIntersection::~CavesNoiseIntersection()
+{
+ delete noise_cave1;
+ delete noise_cave2;
+}
+
+
+void CavesNoiseIntersection::generateCaves(MMVManip *vm,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap)
+{
+ assert(vm);
+ assert(biomemap);
+
+ noise_cave1->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z);
+ noise_cave2->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z);
+
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 index2d = 0; // Biomemap index
+
+ for (s16 z = nmin.Z; z <= nmax.Z; z++)
+ for (s16 x = nmin.X; x <= nmax.X; x++, index2d++) {
+ bool column_is_open = false; // Is column open to overground
+ bool is_under_river = false; // Is column under river water
+ bool is_under_tunnel = false; // Is tunnel or is under tunnel
+ bool is_top_filler_above = false; // Is top or filler above node
+ // Indexes at column top
+ u32 vi = vm->m_area.index(x, nmax.Y, z);
+ u32 index3d = (z - nmin.Z) * m_zstride_1d + m_csize.Y * m_ystride +
+ (x - nmin.X); // 3D noise index
+ // Biome of column
+ Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index2d]);
+ u16 depth_top = biome->depth_top;
+ u16 base_filler = depth_top + biome->depth_filler;
+ u16 depth_riverbed = biome->depth_riverbed;
+ u16 nplaced = 0;
+ // Don't excavate the overgenerated stone at nmax.Y + 1,
+ // this creates a 'roof' over the tunnel, preventing light in
+ // tunnels at mapchunk borders when generating mapchunks upwards.
+ // This 'roof' is removed when the mapchunk above is generated.
+ for (s16 y = nmax.Y; y >= nmin.Y - 1; y--,
+ index3d -= m_ystride,
+ vm->m_area.add_y(em, vi, -1)) {
+ content_t c = vm->m_data[vi].getContent();
+
+ if (c == CONTENT_AIR || c == biome->c_water_top ||
+ c == biome->c_water) {
+ column_is_open = true;
+ is_top_filler_above = false;
+ continue;
+ }
+
+ if (c == biome->c_river_water) {
+ column_is_open = true;
+ is_under_river = true;
+ is_top_filler_above = false;
+ continue;
+ }
+
+ // Ground
+ float d1 = contour(noise_cave1->result[index3d]);
+ float d2 = contour(noise_cave2->result[index3d]);
+
+ if (d1 * d2 > m_cave_width && m_ndef->get(c).is_ground_content) {
+ // In tunnel and ground content, excavate
+ vm->m_data[vi] = MapNode(CONTENT_AIR);
+ is_under_tunnel = true;
+ // If tunnel roof is top or filler, replace with stone
+ if (is_top_filler_above)
+ vm->m_data[vi + em.X] = MapNode(biome->c_stone);
+ is_top_filler_above = false;
+ } else if (column_is_open && is_under_tunnel &&
+ (c == biome->c_stone || c == biome->c_filler)) {
+ // Tunnel entrance floor, place biome surface nodes
+ if (is_under_river) {
+ if (nplaced < depth_riverbed) {
+ vm->m_data[vi] = MapNode(biome->c_riverbed);
+ is_top_filler_above = true;
+ nplaced++;
+ } else {
+ // Disable top/filler placement
+ column_is_open = false;
+ is_under_river = false;
+ is_under_tunnel = false;
+ }
+ } else if (nplaced < depth_top) {
+ vm->m_data[vi] = MapNode(biome->c_top);
+ is_top_filler_above = true;
+ nplaced++;
+ } else if (nplaced < base_filler) {
+ vm->m_data[vi] = MapNode(biome->c_filler);
+ is_top_filler_above = true;
+ nplaced++;
+ } else {
+ // Disable top/filler placement
+ column_is_open = false;
+ is_under_tunnel = false;
+ }
+ } else {
+ // Not tunnel or tunnel entrance floor
+ // Check node for possible replacing with stone for tunnel roof
+ if (c == biome->c_top || c == biome->c_filler)
+ is_top_filler_above = true;
+
+ column_is_open = false;
+ }
+ }
+ }
+}
+
+
+////
+//// CavernsNoise
+////
+
+CavernsNoise::CavernsNoise(
+ INodeDefManager *nodedef, v3s16 chunksize, NoiseParams *np_cavern,
+ s32 seed, float cavern_limit, float cavern_taper, float cavern_threshold)
+{
+ assert(nodedef);
+
+ m_ndef = nodedef;
+
+ m_csize = chunksize;
+ m_cavern_limit = cavern_limit;
+ m_cavern_taper = cavern_taper;
+ m_cavern_threshold = cavern_threshold;
+
+ m_ystride = m_csize.X;
+ m_zstride_1d = m_csize.X * (m_csize.Y + 1);
+
+ // Noise is created using 1-down overgeneration
+ // A Nx-by-1-by-Nz-sized plane is at the bottom of the desired for
+ // re-carving the solid overtop placed for blocking sunlight
+ noise_cavern = new Noise(np_cavern, seed, m_csize.X, m_csize.Y + 1, m_csize.Z);
+
+ c_water_source = m_ndef->getId("mapgen_water_source");
+ if (c_water_source == CONTENT_IGNORE)
+ c_water_source = CONTENT_AIR;
+
+ c_lava_source = m_ndef->getId("mapgen_lava_source");
+ if (c_lava_source == CONTENT_IGNORE)
+ c_lava_source = CONTENT_AIR;
+}
+
+
+CavernsNoise::~CavernsNoise()
+{
+ delete noise_cavern;
+}
+
+
+bool CavernsNoise::generateCaverns(MMVManip *vm, v3s16 nmin, v3s16 nmax)
+{
+ assert(vm);
+
+ // Calculate noise
+ noise_cavern->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z);
+
+ // Cache cavern_amp values
+ float *cavern_amp = new float[m_csize.Y + 1];
+ u8 cavern_amp_index = 0; // Index zero at column top
+ for (s16 y = nmax.Y; y >= nmin.Y - 1; y--, cavern_amp_index++) {
+ cavern_amp[cavern_amp_index] =
+ MYMIN((m_cavern_limit - y) / (float)m_cavern_taper, 1.0f);
+ }
+
+ //// Place nodes
+ bool near_cavern = false;
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 index2d = 0;
+
+ for (s16 z = nmin.Z; z <= nmax.Z; z++)
+ for (s16 x = nmin.X; x <= nmax.X; x++, index2d++) {
+ // Reset cave_amp index to column top
+ cavern_amp_index = 0;
+ // Initial voxelmanip index at column top
+ u32 vi = vm->m_area.index(x, nmax.Y, z);
+ // Initial 3D noise index at column top
+ u32 index3d = (z - nmin.Z) * m_zstride_1d + m_csize.Y * m_ystride +
+ (x - nmin.X);
+ // Don't excavate the overgenerated stone at node_max.Y + 1,
+ // this creates a 'roof' over the cavern, preventing light in
+ // caverns at mapchunk borders when generating mapchunks upwards.
+ // This 'roof' is excavated when the mapchunk above is generated.
+ for (s16 y = nmax.Y; y >= nmin.Y - 1; y--,
+ index3d -= m_ystride,
+ vm->m_area.add_y(em, vi, -1),
+ cavern_amp_index++) {
+ content_t c = vm->m_data[vi].getContent();
+ float n_absamp_cavern = fabs(noise_cavern->result[index3d]) *
+ cavern_amp[cavern_amp_index];
+ // Disable CavesRandomWalk at a safe distance from caverns
+ // to avoid excessively spreading liquids in caverns.
+ if (n_absamp_cavern > m_cavern_threshold - 0.1f) {
+ near_cavern = true;
+ if (n_absamp_cavern > m_cavern_threshold &&
+ m_ndef->get(c).is_ground_content)
+ vm->m_data[vi] = MapNode(CONTENT_AIR);
+ }
+ }
+ }
+
+ delete[] cavern_amp;
+
+ return near_cavern;
+}
+
+
+////
+//// CavesRandomWalk
+////
+
+CavesRandomWalk::CavesRandomWalk(
+ INodeDefManager *ndef,
+ GenerateNotifier *gennotify,
+ s32 seed,
+ int water_level,
+ content_t water_source,
+ content_t lava_source,
+ int lava_depth)
+{
+ assert(ndef);
+
+ this->ndef = ndef;
+ this->gennotify = gennotify;
+ this->seed = seed;
+ this->water_level = water_level;
+ this->np_caveliquids = &nparams_caveliquids;
+ this->lava_depth = lava_depth;
+
+ c_water_source = water_source;
+ if (c_water_source == CONTENT_IGNORE)
+ c_water_source = ndef->getId("mapgen_water_source");
+ if (c_water_source == CONTENT_IGNORE)
+ c_water_source = CONTENT_AIR;
+
+ c_lava_source = lava_source;
+ if (c_lava_source == CONTENT_IGNORE)
+ c_lava_source = ndef->getId("mapgen_lava_source");
+ if (c_lava_source == CONTENT_IGNORE)
+ c_lava_source = CONTENT_AIR;
+}
+
+
+void CavesRandomWalk::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax,
+ PseudoRandom *ps, bool is_large_cave, int max_stone_height, s16 *heightmap)
+{
+ assert(vm);
+ assert(ps);
+
+ this->vm = vm;
+ this->ps = ps;
+ this->node_min = nmin;
+ this->node_max = nmax;
+ this->heightmap = heightmap;
+ this->large_cave = is_large_cave;
+
+ this->ystride = nmax.X - nmin.X + 1;
+
+ // Set initial parameters from randomness
+ int dswitchint = ps->range(1, 14);
+ flooded = ps->range(1, 2) == 2;
+
+ if (large_cave) {
+ part_max_length_rs = ps->range(2, 4);
+ tunnel_routepoints = ps->range(5, ps->range(15, 30));
+ min_tunnel_diameter = 5;
+ max_tunnel_diameter = ps->range(7, ps->range(8, 24));
+ } else {
+ part_max_length_rs = ps->range(2, 9);
+ tunnel_routepoints = ps->range(10, ps->range(15, 30));
+ min_tunnel_diameter = 2;
+ max_tunnel_diameter = ps->range(2, 6);
+ }
+
+ large_cave_is_flat = (ps->range(0, 1) == 0);
+
+ main_direction = v3f(0, 0, 0);
+
+ // Allowed route area size in nodes
+ ar = node_max - node_min + v3s16(1, 1, 1);
+ // Area starting point in nodes
+ of = node_min;
+
+ // Allow a bit more
+ //(this should be more than the maximum radius of the tunnel)
+ const s16 insure = 10;
+ s16 more = MYMAX(MAP_BLOCKSIZE - max_tunnel_diameter / 2 - insure, 1);
+ ar += v3s16(1, 0, 1) * more * 2;
+ of -= v3s16(1, 0, 1) * more;
+
+ route_y_min = 0;
+ // Allow half a diameter + 7 over stone surface
+ route_y_max = -of.Y + max_stone_y + max_tunnel_diameter / 2 + 7;
+
+ // Limit maximum to area
+ route_y_max = rangelim(route_y_max, 0, ar.Y - 1);
+
+ if (large_cave) {
+ s16 minpos = 0;
+ if (node_min.Y < water_level && node_max.Y > water_level) {
+ minpos = water_level - max_tunnel_diameter / 3 - of.Y;
+ route_y_max = water_level + max_tunnel_diameter / 3 - of.Y;
+ }
+ route_y_min = ps->range(minpos, minpos + max_tunnel_diameter);
+ route_y_min = rangelim(route_y_min, 0, route_y_max);
+ }
+
+ s16 route_start_y_min = route_y_min;
+ s16 route_start_y_max = route_y_max;
+
+ route_start_y_min = rangelim(route_start_y_min, 0, ar.Y - 1);
+ route_start_y_max = rangelim(route_start_y_max, route_start_y_min, ar.Y - 1);
+
+ // Randomize starting position
+ orp.Z = (float)(ps->next() % ar.Z) + 0.5f;
+ orp.Y = (float)(ps->range(route_start_y_min, route_start_y_max)) + 0.5f;
+ orp.X = (float)(ps->next() % ar.X) + 0.5f;
+
+ // Add generation notify begin event
+ if (gennotify) {
+ v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z);
+ GenNotifyType notifytype = large_cave ?
+ GENNOTIFY_LARGECAVE_BEGIN : GENNOTIFY_CAVE_BEGIN;
+ gennotify->addEvent(notifytype, abs_pos);
+ }
+
+ // Generate some tunnel starting from orp
+ for (u16 j = 0; j < tunnel_routepoints; j++)
+ makeTunnel(j % dswitchint == 0);
+
+ // Add generation notify end event
+ if (gennotify) {
+ v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z);
+ GenNotifyType notifytype = large_cave ?
+ GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END;
+ gennotify->addEvent(notifytype, abs_pos);
+ }
+}
+
+
+void CavesRandomWalk::makeTunnel(bool dirswitch)
+{
+ if (dirswitch && !large_cave) {
+ main_direction.Z = ((float)(ps->next() % 20) - (float)10) / 10;
+ main_direction.Y = ((float)(ps->next() % 20) - (float)10) / 30;
+ main_direction.X = ((float)(ps->next() % 20) - (float)10) / 10;
+
+ main_direction *= (float)ps->range(0, 10) / 10;
+ }
+
+ // Randomize size
+ s16 min_d = min_tunnel_diameter;
+ s16 max_d = max_tunnel_diameter;
+ rs = ps->range(min_d, max_d);
+ s16 rs_part_max_length_rs = rs * part_max_length_rs;
+
+ v3s16 maxlen;
+ if (large_cave) {
+ maxlen = v3s16(
+ rs_part_max_length_rs,
+ rs_part_max_length_rs / 2,
+ rs_part_max_length_rs
+ );
+ } else {
+ maxlen = v3s16(
+ rs_part_max_length_rs,
+ ps->range(1, rs_part_max_length_rs),
+ rs_part_max_length_rs
+ );
+ }
+
+ v3f vec;
+ // Jump downward sometimes
+ if (!large_cave && ps->range(0, 12) == 0) {
+ vec.Z = (float)(ps->next() % (maxlen.Z * 1)) - (float)maxlen.Z / 2;
+ vec.Y = (float)(ps->next() % (maxlen.Y * 2)) - (float)maxlen.Y;
+ vec.X = (float)(ps->next() % (maxlen.X * 1)) - (float)maxlen.X / 2;
+ } else {
+ vec.Z = (float)(ps->next() % (maxlen.Z * 1)) - (float)maxlen.Z / 2;
+ vec.Y = (float)(ps->next() % (maxlen.Y * 1)) - (float)maxlen.Y / 2;
+ vec.X = (float)(ps->next() % (maxlen.X * 1)) - (float)maxlen.X / 2;
+ }
+
+ // Do not make caves that are above ground.
+ // It is only necessary to check the startpoint and endpoint.
+ v3s16 p1 = v3s16(orp.X, orp.Y, orp.Z) + of + rs / 2;
+ v3s16 p2 = v3s16(vec.X, vec.Y, vec.Z) + p1;
+ if (isPosAboveSurface(p1) || isPosAboveSurface(p2))
+ return;
+
+ vec += main_direction;
+
+ v3f rp = orp + vec;
+ if (rp.X < 0)
+ rp.X = 0;
+ else if (rp.X >= ar.X)
+ rp.X = ar.X - 1;
+
+ if (rp.Y < route_y_min)
+ rp.Y = route_y_min;
+ else if (rp.Y >= route_y_max)
+ rp.Y = route_y_max - 1;
+
+ if (rp.Z < 0)
+ rp.Z = 0;
+ else if (rp.Z >= ar.Z)
+ rp.Z = ar.Z - 1;
+
+ vec = rp - orp;
+
+ float veclen = vec.getLength();
+ if (veclen < 0.05f)
+ veclen = 1.0f;
+
+ // Every second section is rough
+ bool randomize_xz = (ps->range(1, 2) == 1);
+
+ // Carve routes
+ for (float f = 0.f; f < 1.0f; f += 1.0f / veclen)
+ carveRoute(vec, f, randomize_xz);
+
+ orp = rp;
+}
+
+
+void CavesRandomWalk::carveRoute(v3f vec, float f, bool randomize_xz)
+{
+ MapNode airnode(CONTENT_AIR);
+ MapNode waternode(c_water_source);
+ MapNode lavanode(c_lava_source);
+
+ v3s16 startp(orp.X, orp.Y, orp.Z);
+ startp += of;
+
+ float nval = NoisePerlin3D(np_caveliquids, startp.X,
+ startp.Y, startp.Z, seed);
+ MapNode liquidnode = (nval < 0.40f && node_max.Y < lava_depth) ?
+ lavanode : waternode;
+
+ v3f fp = orp + vec * f;
+ fp.X += 0.1f * ps->range(-10, 10);
+ fp.Z += 0.1f * ps->range(-10, 10);
+ v3s16 cp(fp.X, fp.Y, fp.Z);
+
+ s16 d0 = -rs / 2;
+ s16 d1 = d0 + rs;
+ if (randomize_xz) {
+ d0 += ps->range(-1, 1);
+ d1 += ps->range(-1, 1);
+ }
+
+ bool flat_cave_floor = !large_cave && ps->range(0, 2) == 2;
+
+ for (s16 z0 = d0; z0 <= d1; z0++) {
+ s16 si = rs / 2 - MYMAX(0, abs(z0) - rs / 7 - 1);
+ for (s16 x0 = -si - ps->range(0,1); x0 <= si - 1 + ps->range(0,1); x0++) {
+ s16 maxabsxz = MYMAX(abs(x0), abs(z0));
+
+ s16 si2 = rs / 2 - MYMAX(0, maxabsxz - rs / 7 - 1);
+
+ for (s16 y0 = -si2; y0 <= si2; y0++) {
+ // Make better floors in small caves
+ if (flat_cave_floor && y0 <= -rs / 2 && rs <= 7)
+ continue;
+
+ if (large_cave_is_flat) {
+ // Make large caves not so tall
+ if (rs > 7 && abs(y0) >= rs / 3)
+ continue;
+ }
+
+ v3s16 p(cp.X + x0, cp.Y + y0, cp.Z + z0);
+ p += of;
+
+ if (!vm->m_area.contains(p))
+ continue;
+
+ u32 i = vm->m_area.index(p);
+ content_t c = vm->m_data[i].getContent();
+ if (!ndef->get(c).is_ground_content)
+ continue;
+
+ if (large_cave) {
+ int full_ymin = node_min.Y - MAP_BLOCKSIZE;
+ int full_ymax = node_max.Y + MAP_BLOCKSIZE;
+
+ if (flooded && full_ymin < water_level && full_ymax > water_level)
+ vm->m_data[i] = (p.Y <= water_level) ? waternode : airnode;
+ else if (flooded && full_ymax < water_level)
+ vm->m_data[i] = (p.Y < startp.Y - 4) ? liquidnode : airnode;
+ else
+ vm->m_data[i] = airnode;
+ } else {
+ if (c == CONTENT_IGNORE)
+ continue;
+
+ vm->m_data[i] = airnode;
+ vm->m_flags[i] |= VMANIP_FLAG_CAVE;
+ }
+ }
+ }
+ }
+}
+
+
+inline bool CavesRandomWalk::isPosAboveSurface(v3s16 p)
+{
+ if (heightmap != NULL &&
+ p.Z >= node_min.Z && p.Z <= node_max.Z &&
+ p.X >= node_min.X && p.X <= node_max.X) {
+ u32 index = (p.Z - node_min.Z) * ystride + (p.X - node_min.X);
+ if (heightmap[index] < p.Y)
+ return true;
+ } else if (p.Y > water_level) {
+ return true;
+ }
+
+ return false;
+}
+
+
+////
+//// CavesV6
+////
+
+CavesV6::CavesV6(INodeDefManager *ndef, GenerateNotifier *gennotify,
+ int water_level, content_t water_source, content_t lava_source)
+{
+ assert(ndef);
+
+ this->ndef = ndef;
+ this->gennotify = gennotify;
+ this->water_level = water_level;
+
+ c_water_source = water_source;
+ if (c_water_source == CONTENT_IGNORE)
+ c_water_source = ndef->getId("mapgen_water_source");
+ if (c_water_source == CONTENT_IGNORE)
+ c_water_source = CONTENT_AIR;
+
+ c_lava_source = lava_source;
+ if (c_lava_source == CONTENT_IGNORE)
+ c_lava_source = ndef->getId("mapgen_lava_source");
+ if (c_lava_source == CONTENT_IGNORE)
+ c_lava_source = CONTENT_AIR;
+}
+
+
+void CavesV6::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax,
+ PseudoRandom *ps, PseudoRandom *ps2,
+ bool is_large_cave, int max_stone_height, s16 *heightmap)
+{
+ assert(vm);
+ assert(ps);
+ assert(ps2);
+
+ this->vm = vm;
+ this->ps = ps;
+ this->ps2 = ps2;
+ this->node_min = nmin;
+ this->node_max = nmax;
+ this->heightmap = heightmap;
+ this->large_cave = is_large_cave;
+
+ this->ystride = nmax.X - nmin.X + 1;
+
+ // Set initial parameters from randomness
+ min_tunnel_diameter = 2;
+ max_tunnel_diameter = ps->range(2, 6);
+ int dswitchint = ps->range(1, 14);
+ if (large_cave) {
+ part_max_length_rs = ps->range(2, 4);
+ tunnel_routepoints = ps->range(5, ps->range(15, 30));
+ min_tunnel_diameter = 5;
+ max_tunnel_diameter = ps->range(7, ps->range(8, 24));
+ } else {
+ part_max_length_rs = ps->range(2, 9);
+ tunnel_routepoints = ps->range(10, ps->range(15, 30));
+ }
+ large_cave_is_flat = (ps->range(0, 1) == 0);
+
+ main_direction = v3f(0, 0, 0);
+
+ // Allowed route area size in nodes
+ ar = node_max - node_min + v3s16(1, 1, 1);
+ // Area starting point in nodes
+ of = node_min;
+
+ // Allow a bit more
+ //(this should be more than the maximum radius of the tunnel)
+ const s16 max_spread_amount = MAP_BLOCKSIZE;
+ const s16 insure = 10;
+ s16 more = MYMAX(max_spread_amount - max_tunnel_diameter / 2 - insure, 1);
+ ar += v3s16(1, 0, 1) * more * 2;
+ of -= v3s16(1, 0, 1) * more;
+
+ route_y_min = 0;
+ // Allow half a diameter + 7 over stone surface
+ route_y_max = -of.Y + max_stone_height + max_tunnel_diameter / 2 + 7;
+
+ // Limit maximum to area
+ route_y_max = rangelim(route_y_max, 0, ar.Y - 1);
+
+ if (large_cave) {
+ s16 minpos = 0;
+ if (node_min.Y < water_level && node_max.Y > water_level) {
+ minpos = water_level - max_tunnel_diameter / 3 - of.Y;
+ route_y_max = water_level + max_tunnel_diameter / 3 - of.Y;
+ }
+ route_y_min = ps->range(minpos, minpos + max_tunnel_diameter);
+ route_y_min = rangelim(route_y_min, 0, route_y_max);
+ }
+
+ s16 route_start_y_min = route_y_min;
+ s16 route_start_y_max = route_y_max;
+
+ route_start_y_min = rangelim(route_start_y_min, 0, ar.Y - 1);
+ route_start_y_max = rangelim(route_start_y_max, route_start_y_min, ar.Y - 1);
+
+ // Randomize starting position
+ orp.Z = (float)(ps->next() % ar.Z) + 0.5f;
+ orp.Y = (float)(ps->range(route_start_y_min, route_start_y_max)) + 0.5f;
+ orp.X = (float)(ps->next() % ar.X) + 0.5f;
+
+ // Add generation notify begin event
+ if (gennotify != NULL) {
+ v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z);
+ GenNotifyType notifytype = large_cave ?
+ GENNOTIFY_LARGECAVE_BEGIN : GENNOTIFY_CAVE_BEGIN;
+ gennotify->addEvent(notifytype, abs_pos);
+ }
+
+ // Generate some tunnel starting from orp
+ for (u16 j = 0; j < tunnel_routepoints; j++)
+ makeTunnel(j % dswitchint == 0);
+
+ // Add generation notify end event
+ if (gennotify != NULL) {
+ v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z);
+ GenNotifyType notifytype = large_cave ?
+ GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END;
+ gennotify->addEvent(notifytype, abs_pos);
+ }
+}
+
+
+void CavesV6::makeTunnel(bool dirswitch)
+{
+ if (dirswitch && !large_cave) {
+ main_direction.Z = ((float)(ps->next() % 20) - (float)10) / 10;
+ main_direction.Y = ((float)(ps->next() % 20) - (float)10) / 30;
+ main_direction.X = ((float)(ps->next() % 20) - (float)10) / 10;
+
+ main_direction *= (float)ps->range(0, 10) / 10;
+ }
+
+ // Randomize size
+ s16 min_d = min_tunnel_diameter;
+ s16 max_d = max_tunnel_diameter;
+ rs = ps->range(min_d, max_d);
+ s16 rs_part_max_length_rs = rs * part_max_length_rs;
+
+ v3s16 maxlen;
+ if (large_cave) {
+ maxlen = v3s16(
+ rs_part_max_length_rs,
+ rs_part_max_length_rs / 2,
+ rs_part_max_length_rs
+ );
+ } else {
+ maxlen = v3s16(
+ rs_part_max_length_rs,
+ ps->range(1, rs_part_max_length_rs),
+ rs_part_max_length_rs
+ );
+ }
+
+ v3f vec;
+ vec.Z = (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2;
+ vec.Y = (float)(ps->next() % maxlen.Y) - (float)maxlen.Y / 2;
+ vec.X = (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2;
+
+ // Jump downward sometimes
+ if (!large_cave && ps->range(0, 12) == 0) {
+ vec.Z = (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2;
+ vec.Y = (float)(ps->next() % (maxlen.Y * 2)) - (float)maxlen.Y;
+ vec.X = (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2;
+ }
+
+ // Do not make caves that are entirely above ground, to fix shadow bugs
+ // caused by overgenerated large caves.
+ // It is only necessary to check the startpoint and endpoint.
+ v3s16 p1 = v3s16(orp.X, orp.Y, orp.Z) + of + rs / 2;
+ v3s16 p2 = v3s16(vec.X, vec.Y, vec.Z) + p1;
+
+ // If startpoint and endpoint are above ground, disable placement of nodes
+ // in carveRoute while still running all PseudoRandom calls to ensure caves
+ // are consistent with existing worlds.
+ bool tunnel_above_ground =
+ p1.Y > getSurfaceFromHeightmap(p1) &&
+ p2.Y > getSurfaceFromHeightmap(p2);
+
+ vec += main_direction;
+
+ v3f rp = orp + vec;
+ if (rp.X < 0)
+ rp.X = 0;
+ else if (rp.X >= ar.X)
+ rp.X = ar.X - 1;
+
+ if (rp.Y < route_y_min)
+ rp.Y = route_y_min;
+ else if (rp.Y >= route_y_max)
+ rp.Y = route_y_max - 1;
+
+ if (rp.Z < 0)
+ rp.Z = 0;
+ else if (rp.Z >= ar.Z)
+ rp.Z = ar.Z - 1;
+
+ vec = rp - orp;
+
+ float veclen = vec.getLength();
+ // As odd as it sounds, veclen is *exactly* 0.0 sometimes, causing a FPE
+ if (veclen < 0.05f)
+ veclen = 1.0f;
+
+ // Every second section is rough
+ bool randomize_xz = (ps2->range(1, 2) == 1);
+
+ // Carve routes
+ for (float f = 0.f; f < 1.0f; f += 1.0f / veclen)
+ carveRoute(vec, f, randomize_xz, tunnel_above_ground);
+
+ orp = rp;
+}
+
+
+void CavesV6::carveRoute(v3f vec, float f, bool randomize_xz,
+ bool tunnel_above_ground)
+{
+ MapNode airnode(CONTENT_AIR);
+ MapNode waternode(c_water_source);
+ MapNode lavanode(c_lava_source);
+
+ v3s16 startp(orp.X, orp.Y, orp.Z);
+ startp += of;
+
+ v3f fp = orp + vec * f;
+ fp.X += 0.1f * ps->range(-10, 10);
+ fp.Z += 0.1f * ps->range(-10, 10);
+ v3s16 cp(fp.X, fp.Y, fp.Z);
+
+ s16 d0 = -rs / 2;
+ s16 d1 = d0 + rs;
+ if (randomize_xz) {
+ d0 += ps->range(-1, 1);
+ d1 += ps->range(-1, 1);
+ }
+
+ for (s16 z0 = d0; z0 <= d1; z0++) {
+ s16 si = rs / 2 - MYMAX(0, abs(z0) - rs / 7 - 1);
+ for (s16 x0 = -si - ps->range(0,1); x0 <= si - 1 + ps->range(0,1); x0++) {
+ if (tunnel_above_ground)
+ continue;
+
+ s16 maxabsxz = MYMAX(abs(x0), abs(z0));
+ s16 si2 = rs / 2 - MYMAX(0, maxabsxz - rs / 7 - 1);
+ for (s16 y0 = -si2; y0 <= si2; y0++) {
+ if (large_cave_is_flat) {
+ // Make large caves not so tall
+ if (rs > 7 && abs(y0) >= rs / 3)
+ continue;
+ }
+
+ v3s16 p(cp.X + x0, cp.Y + y0, cp.Z + z0);
+ p += of;
+
+ if (!vm->m_area.contains(p))
+ continue;
+
+ u32 i = vm->m_area.index(p);
+ content_t c = vm->m_data[i].getContent();
+ if (!ndef->get(c).is_ground_content)
+ continue;
+
+ if (large_cave) {
+ int full_ymin = node_min.Y - MAP_BLOCKSIZE;
+ int full_ymax = node_max.Y + MAP_BLOCKSIZE;
+
+ if (full_ymin < water_level && full_ymax > water_level) {
+ vm->m_data[i] = (p.Y <= water_level) ? waternode : airnode;
+ } else if (full_ymax < water_level) {
+ vm->m_data[i] = (p.Y < startp.Y - 2) ? lavanode : airnode;
+ } else {
+ vm->m_data[i] = airnode;
+ }
+ } else {
+ if (c == CONTENT_IGNORE || c == CONTENT_AIR)
+ continue;
+
+ vm->m_data[i] = airnode;
+ vm->m_flags[i] |= VMANIP_FLAG_CAVE;
+ }
+ }
+ }
+ }
+}
+
+
+inline s16 CavesV6::getSurfaceFromHeightmap(v3s16 p)
+{
+ if (heightmap != NULL &&
+ p.Z >= node_min.Z && p.Z <= node_max.Z &&
+ p.X >= node_min.X && p.X <= node_max.X) {
+ u32 index = (p.Z - node_min.Z) * ystride + (p.X - node_min.X);
+ return heightmap[index];
+ }
+
+ return water_level;
+
+}
diff --git a/src/mapgen/cavegen.h b/src/mapgen/cavegen.h
new file mode 100644
index 000000000..ce146e0cd
--- /dev/null
+++ b/src/mapgen/cavegen.h
@@ -0,0 +1,242 @@
+/*
+Minetest
+Copyright (C) 2010-2013 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#define VMANIP_FLAG_CAVE VOXELFLAG_CHECKED1
+
+class GenerateNotifier;
+
+/*
+ CavesNoiseIntersection is a cave digging algorithm that carves smooth,
+ web-like, continuous tunnels at points where the density of the intersection
+ between two separate 3d noises is above a certain value. This value,
+ cave_width, can be modified to set the effective width of these tunnels.
+
+ This algorithm is relatively heavyweight, taking ~80ms to generate an
+ 80x80x80 chunk of map on a modern processor. Use sparingly!
+
+ TODO(hmmmm): Remove dependency on biomes
+ TODO(hmmmm): Find alternative to overgeneration as solution for sunlight issue
+*/
+class CavesNoiseIntersection
+{
+public:
+ CavesNoiseIntersection(INodeDefManager *nodedef, BiomeManager *biomemgr,
+ v3s16 chunksize, NoiseParams *np_cave1, NoiseParams *np_cave2,
+ s32 seed, float cave_width);
+ ~CavesNoiseIntersection();
+
+ void generateCaves(MMVManip *vm, v3s16 nmin, v3s16 nmax, u8 *biomemap);
+
+private:
+ INodeDefManager *m_ndef;
+ BiomeManager *m_bmgr;
+
+ // configurable parameters
+ v3s16 m_csize;
+ float m_cave_width;
+
+ // intermediate state variables
+ u16 m_ystride;
+ u16 m_zstride_1d;
+
+ Noise *noise_cave1;
+ Noise *noise_cave2;
+};
+
+/*
+ CavernsNoise is a cave digging algorithm
+*/
+class CavernsNoise
+{
+public:
+ CavernsNoise(INodeDefManager *nodedef, v3s16 chunksize, NoiseParams *np_cavern,
+ s32 seed, float cavern_limit, float cavern_taper,
+ float cavern_threshold);
+ ~CavernsNoise();
+
+ bool generateCaverns(MMVManip *vm, v3s16 nmin, v3s16 nmax);
+
+private:
+ INodeDefManager *m_ndef;
+
+ // configurable parameters
+ v3s16 m_csize;
+ float m_cavern_limit;
+ float m_cavern_taper;
+ float m_cavern_threshold;
+
+ // intermediate state variables
+ u16 m_ystride;
+ u16 m_zstride_1d;
+
+ Noise *noise_cavern;
+
+ content_t c_water_source;
+ content_t c_lava_source;
+};
+
+/*
+ CavesRandomWalk is an implementation of a cave-digging algorithm that
+ operates on the principle of a "random walk" to approximate the stochiastic
+ activity of cavern development.
+
+ In summary, this algorithm works by carving a randomly sized tunnel in a
+ random direction a random amount of times, randomly varying in width.
+ All randomness here is uniformly distributed; alternative distributions have
+ not yet been implemented.
+
+ This algorithm is very fast, executing in less than 1ms on average for an
+ 80x80x80 chunk of map on a modern processor.
+*/
+class CavesRandomWalk
+{
+public:
+ MMVManip *vm;
+ INodeDefManager *ndef;
+ GenerateNotifier *gennotify;
+ s16 *heightmap;
+
+ // configurable parameters
+ s32 seed;
+ int water_level;
+ int lava_depth;
+ NoiseParams *np_caveliquids;
+
+ // intermediate state variables
+ u16 ystride;
+
+ s16 min_tunnel_diameter;
+ s16 max_tunnel_diameter;
+ u16 tunnel_routepoints;
+ int part_max_length_rs;
+
+ bool large_cave;
+ bool large_cave_is_flat;
+ bool flooded;
+
+ s16 max_stone_y;
+ v3s16 node_min;
+ v3s16 node_max;
+
+ v3f orp; // starting point, relative to caved space
+ v3s16 of; // absolute coordinates of caved space
+ v3s16 ar; // allowed route area
+ s16 rs; // tunnel radius size
+ v3f main_direction;
+
+ s16 route_y_min;
+ s16 route_y_max;
+
+ PseudoRandom *ps;
+
+ content_t c_water_source;
+ content_t c_lava_source;
+
+ // ndef is a mandatory parameter.
+ // If gennotify is NULL, generation events are not logged.
+ CavesRandomWalk(INodeDefManager *ndef, GenerateNotifier *gennotify = NULL,
+ s32 seed = 0, int water_level = 1,
+ content_t water_source = CONTENT_IGNORE,
+ content_t lava_source = CONTENT_IGNORE, int lava_depth = -256);
+
+ // vm and ps are mandatory parameters.
+ // If heightmap is NULL, the surface level at all points is assumed to
+ // be water_level.
+ void makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, PseudoRandom *ps,
+ bool is_large_cave, int max_stone_height, s16 *heightmap);
+
+private:
+ void makeTunnel(bool dirswitch);
+ void carveRoute(v3f vec, float f, bool randomize_xz);
+
+ inline bool isPosAboveSurface(v3s16 p);
+};
+
+/*
+ CavesV6 is the original version of caves used with Mapgen V6.
+
+ Though it uses the same fundamental algorithm as CavesRandomWalk, it is made
+ separate to preserve the exact sequence of PseudoRandom calls - any change
+ to this ordering results in the output being radically different.
+ Because caves in Mapgen V6 are responsible for a large portion of the basic
+ terrain shape, modifying this will break our contract of reverse
+ compatibility for a 'stable' mapgen such as V6.
+
+ tl;dr,
+ *** DO NOT TOUCH THIS CLASS UNLESS YOU KNOW WHAT YOU ARE DOING ***
+*/
+class CavesV6
+{
+public:
+ MMVManip *vm;
+ INodeDefManager *ndef;
+ GenerateNotifier *gennotify;
+ PseudoRandom *ps;
+ PseudoRandom *ps2;
+
+ // configurable parameters
+ s16 *heightmap;
+ content_t c_water_source;
+ content_t c_lava_source;
+ int water_level;
+
+ // intermediate state variables
+ u16 ystride;
+
+ s16 min_tunnel_diameter;
+ s16 max_tunnel_diameter;
+ u16 tunnel_routepoints;
+ int part_max_length_rs;
+
+ bool large_cave;
+ bool large_cave_is_flat;
+
+ v3s16 node_min;
+ v3s16 node_max;
+
+ v3f orp; // starting point, relative to caved space
+ v3s16 of; // absolute coordinates of caved space
+ v3s16 ar; // allowed route area
+ s16 rs; // tunnel radius size
+ v3f main_direction;
+
+ s16 route_y_min;
+ s16 route_y_max;
+
+ // ndef is a mandatory parameter.
+ // If gennotify is NULL, generation events are not logged.
+ CavesV6(INodeDefManager *ndef, GenerateNotifier *gennotify = NULL,
+ int water_level = 1, content_t water_source = CONTENT_IGNORE,
+ content_t lava_source = CONTENT_IGNORE);
+
+ // vm, ps, and ps2 are mandatory parameters.
+ // If heightmap is NULL, the surface level at all points is assumed to
+ // be water_level.
+ void makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, PseudoRandom *ps,
+ PseudoRandom *ps2, bool is_large_cave, int max_stone_height,
+ s16 *heightmap = NULL);
+
+private:
+ void makeTunnel(bool dirswitch);
+ void carveRoute(v3f vec, float f, bool randomize_xz, bool tunnel_above_ground);
+
+ inline s16 getSurfaceFromHeightmap(v3s16 p);
+};
diff --git a/src/mapgen/dungeongen.cpp b/src/mapgen/dungeongen.cpp
new file mode 100644
index 000000000..fa867b398
--- /dev/null
+++ b/src/mapgen/dungeongen.cpp
@@ -0,0 +1,677 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "dungeongen.h"
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "nodedef.h"
+#include "settings.h"
+
+//#define DGEN_USE_TORCHES
+
+NoiseParams nparams_dungeon_density(0.9, 0.5, v3f(500.0, 500.0, 500.0), 0, 2, 0.8, 2.0);
+NoiseParams nparams_dungeon_alt_wall(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0);
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+DungeonGen::DungeonGen(INodeDefManager *ndef,
+ GenerateNotifier *gennotify, DungeonParams *dparams)
+{
+ assert(ndef);
+
+ this->ndef = ndef;
+ this->gennotify = gennotify;
+
+#ifdef DGEN_USE_TORCHES
+ c_torch = ndef->getId("default:torch");
+#endif
+
+ if (dparams) {
+ memcpy(&dp, dparams, sizeof(dp));
+ } else {
+ // Default dungeon parameters
+ dp.seed = 0;
+
+ dp.c_water = ndef->getId("mapgen_water_source");
+ dp.c_river_water = ndef->getId("mapgen_river_water_source");
+ dp.c_wall = ndef->getId("mapgen_cobble");
+ dp.c_alt_wall = ndef->getId("mapgen_mossycobble");
+ dp.c_stair = ndef->getId("mapgen_stair_cobble");
+
+ if (dp.c_river_water == CONTENT_IGNORE)
+ dp.c_river_water = ndef->getId("mapgen_water_source");
+
+ dp.diagonal_dirs = false;
+ dp.only_in_ground = true;
+ dp.holesize = v3s16(1, 2, 1);
+ dp.corridor_len_min = 1;
+ dp.corridor_len_max = 13;
+ dp.room_size_min = v3s16(4, 4, 4);
+ dp.room_size_max = v3s16(8, 6, 8);
+ dp.room_size_large_min = v3s16(8, 8, 8);
+ dp.room_size_large_max = v3s16(16, 16, 16);
+ dp.rooms_min = 2;
+ dp.rooms_max = 16;
+ dp.y_min = -MAX_MAP_GENERATION_LIMIT;
+ dp.y_max = MAX_MAP_GENERATION_LIMIT;
+ dp.notifytype = GENNOTIFY_DUNGEON;
+
+ dp.np_density = nparams_dungeon_density;
+ dp.np_alt_wall = nparams_dungeon_alt_wall;
+ }
+}
+
+
+void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax)
+{
+ assert(vm);
+
+ //TimeTaker t("gen dungeons");
+ if (nmin.Y < dp.y_min || nmax.Y > dp.y_max)
+ return;
+
+ float nval_density = NoisePerlin3D(&dp.np_density, nmin.X, nmin.Y, nmin.Z, dp.seed);
+ if (nval_density < 1.0f)
+ return;
+
+ static const bool preserve_ignore = !g_settings->getBool("projecting_dungeons");
+
+ this->vm = vm;
+ this->blockseed = bseed;
+ random.seed(bseed + 2);
+
+ // Dungeon generator doesn't modify places which have this set
+ vm->clearFlag(VMANIP_FLAG_DUNGEON_INSIDE | VMANIP_FLAG_DUNGEON_PRESERVE);
+
+ if (dp.only_in_ground) {
+ // Set all air and water to be untouchable to make dungeons open to
+ // caves and open air. Optionally set ignore to be untouchable to
+ // prevent protruding dungeons.
+ for (s16 z = nmin.Z; z <= nmax.Z; z++) {
+ for (s16 y = nmin.Y; y <= nmax.Y; y++) {
+ u32 i = vm->m_area.index(nmin.X, y, z);
+ for (s16 x = nmin.X; x <= nmax.X; x++) {
+ content_t c = vm->m_data[i].getContent();
+ if (c == CONTENT_AIR || c == dp.c_water ||
+ (preserve_ignore && c == CONTENT_IGNORE) ||
+ c == dp.c_river_water)
+ vm->m_flags[i] |= VMANIP_FLAG_DUNGEON_PRESERVE;
+ i++;
+ }
+ }
+ }
+ }
+
+ // Add them
+ for (u32 i = 0; i < floor(nval_density); i++)
+ makeDungeon(v3s16(1, 1, 1) * MAP_BLOCKSIZE);
+
+ // Optionally convert some structure to alternative structure
+ if (dp.c_alt_wall == CONTENT_IGNORE)
+ return;
+
+ for (s16 z = nmin.Z; z <= nmax.Z; z++)
+ for (s16 y = nmin.Y; y <= nmax.Y; y++) {
+ u32 i = vm->m_area.index(nmin.X, y, z);
+ for (s16 x = nmin.X; x <= nmax.X; x++) {
+ if (vm->m_data[i].getContent() == dp.c_wall) {
+ if (NoisePerlin3D(&dp.np_alt_wall, x, y, z, blockseed) > 0.0f)
+ vm->m_data[i].setContent(dp.c_alt_wall);
+ }
+ i++;
+ }
+ }
+
+ //printf("== gen dungeons: %dms\n", t.stop());
+}
+
+
+void DungeonGen::makeDungeon(v3s16 start_padding)
+{
+ const v3s16 &areasize = vm->m_area.getExtent();
+ v3s16 roomsize;
+ v3s16 roomplace;
+
+ /*
+ Find place for first room.
+ There is a 1 in 4 chance of the first room being 'large',
+ all other rooms are not 'large'.
+ */
+ bool fits = false;
+ for (u32 i = 0; i < 100 && !fits; i++) {
+ bool is_large_room = ((random.next() & 3) == 1);
+ if (is_large_room) {
+ roomsize.Z = random.range(
+ dp.room_size_large_min.Z, dp.room_size_large_max.Z);
+ roomsize.Y = random.range(
+ dp.room_size_large_min.Y, dp.room_size_large_max.Y);
+ roomsize.X = random.range(
+ dp.room_size_large_min.X, dp.room_size_large_max.X);
+ } else {
+ roomsize.Z = random.range(dp.room_size_min.Z, dp.room_size_max.Z);
+ roomsize.Y = random.range(dp.room_size_min.Y, dp.room_size_max.Y);
+ roomsize.X = random.range(dp.room_size_min.X, dp.room_size_max.X);
+ }
+
+ // start_padding is used to disallow starting the generation of
+ // a dungeon in a neighboring generation chunk
+ roomplace = vm->m_area.MinEdge + start_padding;
+ roomplace.Z += random.range(0, areasize.Z - roomsize.Z - start_padding.Z);
+ roomplace.Y += random.range(0, areasize.Y - roomsize.Y - start_padding.Y);
+ roomplace.X += random.range(0, areasize.X - roomsize.X - start_padding.X);
+
+ /*
+ Check that we're not putting the room to an unknown place,
+ otherwise it might end up floating in the air
+ */
+ fits = true;
+ for (s16 z = 0; z < roomsize.Z; z++)
+ for (s16 y = 0; y < roomsize.Y; y++)
+ for (s16 x = 0; x < roomsize.X; x++) {
+ v3s16 p = roomplace + v3s16(x, y, z);
+ u32 vi = vm->m_area.index(p);
+ if ((vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) ||
+ vm->m_data[vi].getContent() == CONTENT_IGNORE) {
+ fits = false;
+ break;
+ }
+ }
+ }
+ // No place found
+ if (!fits)
+ return;
+
+ /*
+ Stores the center position of the last room made, so that
+ a new corridor can be started from the last room instead of
+ the new room, if chosen so.
+ */
+ v3s16 last_room_center = roomplace + v3s16(roomsize.X / 2, 1, roomsize.Z / 2);
+
+ u32 room_count = random.range(dp.rooms_min, dp.rooms_max);
+ for (u32 i = 0; i < room_count; i++) {
+ // Make a room to the determined place
+ makeRoom(roomsize, roomplace);
+
+ v3s16 room_center = roomplace + v3s16(roomsize.X / 2, 1, roomsize.Z / 2);
+ if (gennotify)
+ gennotify->addEvent(dp.notifytype, room_center);
+
+#ifdef DGEN_USE_TORCHES
+ // Place torch at room center (for testing)
+ vm->m_data[vm->m_area.index(room_center)] = MapNode(c_torch);
+#endif
+
+ // Quit if last room
+ if (i == room_count - 1)
+ break;
+
+ // Determine walker start position
+
+ bool start_in_last_room = (random.range(0, 2) != 0);
+
+ v3s16 walker_start_place;
+
+ if (start_in_last_room) {
+ walker_start_place = last_room_center;
+ } else {
+ walker_start_place = room_center;
+ // Store center of current room as the last one
+ last_room_center = room_center;
+ }
+
+ // Create walker and find a place for a door
+ v3s16 doorplace;
+ v3s16 doordir;
+
+ m_pos = walker_start_place;
+ if (!findPlaceForDoor(doorplace, doordir))
+ return;
+
+ if (random.range(0, 1) == 0)
+ // Make the door
+ makeDoor(doorplace, doordir);
+ else
+ // Don't actually make a door
+ doorplace -= doordir;
+
+ // Make a random corridor starting from the door
+ v3s16 corridor_end;
+ v3s16 corridor_end_dir;
+ makeCorridor(doorplace, doordir, corridor_end, corridor_end_dir);
+
+ // Find a place for a random sized room
+ roomsize.Z = random.range(dp.room_size_min.Z, dp.room_size_max.Z);
+ roomsize.Y = random.range(dp.room_size_min.Y, dp.room_size_max.Y);
+ roomsize.X = random.range(dp.room_size_min.X, dp.room_size_max.X);
+
+ m_pos = corridor_end;
+ m_dir = corridor_end_dir;
+ if (!findPlaceForRoomDoor(roomsize, doorplace, doordir, roomplace))
+ return;
+
+ if (random.range(0, 1) == 0)
+ // Make the door
+ makeDoor(doorplace, doordir);
+ else
+ // Don't actually make a door
+ roomplace -= doordir;
+
+ }
+}
+
+
+void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace)
+{
+ MapNode n_wall(dp.c_wall);
+ MapNode n_air(CONTENT_AIR);
+
+ // Make +-X walls
+ for (s16 z = 0; z < roomsize.Z; z++)
+ for (s16 y = 0; y < roomsize.Y; y++) {
+ {
+ v3s16 p = roomplace + v3s16(0, y, z);
+ if (!vm->m_area.contains(p))
+ continue;
+ u32 vi = vm->m_area.index(p);
+ if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
+ continue;
+ vm->m_data[vi] = n_wall;
+ }
+ {
+ v3s16 p = roomplace + v3s16(roomsize.X - 1, y, z);
+ if (!vm->m_area.contains(p))
+ continue;
+ u32 vi = vm->m_area.index(p);
+ if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
+ continue;
+ vm->m_data[vi] = n_wall;
+ }
+ }
+
+ // Make +-Z walls
+ for (s16 x = 0; x < roomsize.X; x++)
+ for (s16 y = 0; y < roomsize.Y; y++) {
+ {
+ v3s16 p = roomplace + v3s16(x, y, 0);
+ if (!vm->m_area.contains(p))
+ continue;
+ u32 vi = vm->m_area.index(p);
+ if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
+ continue;
+ vm->m_data[vi] = n_wall;
+ }
+ {
+ v3s16 p = roomplace + v3s16(x, y, roomsize.Z - 1);
+ if (!vm->m_area.contains(p))
+ continue;
+ u32 vi = vm->m_area.index(p);
+ if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
+ continue;
+ vm->m_data[vi] = n_wall;
+ }
+ }
+
+ // Make +-Y walls (floor and ceiling)
+ for (s16 z = 0; z < roomsize.Z; z++)
+ for (s16 x = 0; x < roomsize.X; x++) {
+ {
+ v3s16 p = roomplace + v3s16(x, 0, z);
+ if (!vm->m_area.contains(p))
+ continue;
+ u32 vi = vm->m_area.index(p);
+ if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
+ continue;
+ vm->m_data[vi] = n_wall;
+ }
+ {
+ v3s16 p = roomplace + v3s16(x,roomsize. Y - 1, z);
+ if (!vm->m_area.contains(p))
+ continue;
+ u32 vi = vm->m_area.index(p);
+ if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
+ continue;
+ vm->m_data[vi] = n_wall;
+ }
+ }
+
+ // Fill with air
+ for (s16 z = 1; z < roomsize.Z - 1; z++)
+ for (s16 y = 1; y < roomsize.Y - 1; y++)
+ for (s16 x = 1; x < roomsize.X - 1; x++) {
+ v3s16 p = roomplace + v3s16(x, y, z);
+ if (!vm->m_area.contains(p))
+ continue;
+ u32 vi = vm->m_area.index(p);
+ vm->m_flags[vi] |= VMANIP_FLAG_DUNGEON_UNTOUCHABLE;
+ vm->m_data[vi] = n_air;
+ }
+}
+
+
+void DungeonGen::makeFill(v3s16 place, v3s16 size,
+ u8 avoid_flags, MapNode n, u8 or_flags)
+{
+ for (s16 z = 0; z < size.Z; z++)
+ for (s16 y = 0; y < size.Y; y++)
+ for (s16 x = 0; x < size.X; x++) {
+ v3s16 p = place + v3s16(x, y, z);
+ if (!vm->m_area.contains(p))
+ continue;
+ u32 vi = vm->m_area.index(p);
+ if (vm->m_flags[vi] & avoid_flags)
+ continue;
+ vm->m_flags[vi] |= or_flags;
+ vm->m_data[vi] = n;
+ }
+}
+
+
+void DungeonGen::makeHole(v3s16 place)
+{
+ makeFill(place, dp.holesize, 0, MapNode(CONTENT_AIR),
+ VMANIP_FLAG_DUNGEON_INSIDE);
+}
+
+
+void DungeonGen::makeDoor(v3s16 doorplace, v3s16 doordir)
+{
+ makeHole(doorplace);
+
+#ifdef DGEN_USE_TORCHES
+ // Place torch (for testing)
+ vm->m_data[vm->m_area.index(doorplace)] = MapNode(c_torch);
+#endif
+}
+
+
+void DungeonGen::makeCorridor(v3s16 doorplace, v3s16 doordir,
+ v3s16 &result_place, v3s16 &result_dir)
+{
+ makeHole(doorplace);
+ v3s16 p0 = doorplace;
+ v3s16 dir = doordir;
+ u32 length = random.range(dp.corridor_len_min, dp.corridor_len_max);
+ u32 partlength = random.range(dp.corridor_len_min, dp.corridor_len_max);
+ u32 partcount = 0;
+ s16 make_stairs = 0;
+
+ if (random.next() % 2 == 0 && partlength >= 3)
+ make_stairs = random.next() % 2 ? 1 : -1;
+
+ for (u32 i = 0; i < length; i++) {
+ v3s16 p = p0 + dir;
+ if (partcount != 0)
+ p.Y += make_stairs;
+
+ // Check segment of minimum size corridor is in voxelmanip
+ if (vm->m_area.contains(p) && vm->m_area.contains(p + v3s16(0, 1, 0))) {
+ if (make_stairs) {
+ makeFill(p + v3s16(-1, -1, -1),
+ dp.holesize + v3s16(2, 3, 2),
+ VMANIP_FLAG_DUNGEON_UNTOUCHABLE,
+ MapNode(dp.c_wall),
+ 0);
+ makeHole(p);
+ makeHole(p - dir);
+
+ // TODO: fix stairs code so it works 100%
+ // (quite difficult)
+
+ // exclude stairs from the bottom step
+ // exclude stairs from diagonal steps
+ if (((dir.X ^ dir.Z) & 1) &&
+ (((make_stairs == 1) && i != 0) ||
+ ((make_stairs == -1) && i != length - 1))) {
+ // rotate face 180 deg if
+ // making stairs backwards
+ int facedir = dir_to_facedir(dir * make_stairs);
+ v3s16 ps = p;
+ u16 stair_width = (dir.Z != 0) ? dp.holesize.X : dp.holesize.Z;
+ // Stair width direction vector
+ v3s16 swv = (dir.Z != 0) ? v3s16(1, 0, 0) : v3s16(0, 0, 1);
+
+ for (u16 st = 0; st < stair_width; st++) {
+ u32 vi = vm->m_area.index(ps.X - dir.X, ps.Y - 1, ps.Z - dir.Z);
+ if (vm->m_area.contains(ps + v3s16(-dir.X, -1, -dir.Z)) &&
+ vm->m_data[vi].getContent() == dp.c_wall)
+ vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir);
+
+ vi = vm->m_area.index(ps.X, ps.Y, ps.Z);
+ if (vm->m_area.contains(ps) &&
+ vm->m_data[vi].getContent() == dp.c_wall)
+ vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir);
+
+ ps += swv;
+ }
+ }
+ } else {
+ makeFill(p + v3s16(-1, -1, -1),
+ dp.holesize + v3s16(2, 2, 2),
+ VMANIP_FLAG_DUNGEON_UNTOUCHABLE,
+ MapNode(dp.c_wall),
+ 0);
+ makeHole(p);
+ }
+
+ p0 = p;
+ } else {
+ // Can't go here, turn away
+ dir = turn_xz(dir, random.range(0, 1));
+ make_stairs = -make_stairs;
+ partcount = 0;
+ partlength = random.range(1, length);
+ continue;
+ }
+
+ partcount++;
+ if (partcount >= partlength) {
+ partcount = 0;
+
+ dir = random_turn(random, dir);
+
+ partlength = random.range(1, length);
+
+ make_stairs = 0;
+ if (random.next() % 2 == 0 && partlength >= 3)
+ make_stairs = random.next() % 2 ? 1 : -1;
+ }
+ }
+ result_place = p0;
+ result_dir = dir;
+}
+
+
+bool DungeonGen::findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir)
+{
+ for (u32 i = 0; i < 100; i++) {
+ v3s16 p = m_pos + m_dir;
+ v3s16 p1 = p + v3s16(0, 1, 0);
+ if (!vm->m_area.contains(p) || !vm->m_area.contains(p1) || i % 4 == 0) {
+ randomizeDir();
+ continue;
+ }
+ if (vm->getNodeNoExNoEmerge(p).getContent() == dp.c_wall &&
+ vm->getNodeNoExNoEmerge(p1).getContent() == dp.c_wall) {
+ // Found wall, this is a good place!
+ result_place = p;
+ result_dir = m_dir;
+ // Randomize next direction
+ randomizeDir();
+ return true;
+ }
+ /*
+ Determine where to move next
+ */
+ // Jump one up if the actual space is there
+ if (vm->getNodeNoExNoEmerge(p +
+ v3s16(0, 0, 0)).getContent() == dp.c_wall &&
+ vm->getNodeNoExNoEmerge(p +
+ v3s16(0, 1, 0)).getContent() == CONTENT_AIR &&
+ vm->getNodeNoExNoEmerge(p +
+ v3s16(0, 2, 0)).getContent() == CONTENT_AIR)
+ p += v3s16(0,1,0);
+ // Jump one down if the actual space is there
+ if (vm->getNodeNoExNoEmerge(p +
+ v3s16(0, 1, 0)).getContent() == dp.c_wall &&
+ vm->getNodeNoExNoEmerge(p +
+ v3s16(0, 0, 0)).getContent() == CONTENT_AIR &&
+ vm->getNodeNoExNoEmerge(p +
+ v3s16(0, -1, 0)).getContent() == CONTENT_AIR)
+ p += v3s16(0, -1, 0);
+ // Check if walking is now possible
+ if (vm->getNodeNoExNoEmerge(p).getContent() != CONTENT_AIR ||
+ vm->getNodeNoExNoEmerge(p +
+ v3s16(0, 1, 0)).getContent() != CONTENT_AIR) {
+ // Cannot continue walking here
+ randomizeDir();
+ continue;
+ }
+ // Move there
+ m_pos = p;
+ }
+ return false;
+}
+
+
+bool DungeonGen::findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace,
+ v3s16 &result_doordir, v3s16 &result_roomplace)
+{
+ for (s16 trycount = 0; trycount < 30; trycount++) {
+ v3s16 doorplace;
+ v3s16 doordir;
+ bool r = findPlaceForDoor(doorplace, doordir);
+ if (!r)
+ continue;
+ v3s16 roomplace;
+ // X east, Z north, Y up
+ if (doordir == v3s16(1, 0, 0)) // X+
+ roomplace = doorplace +
+ v3s16(0, -1, random.range(-roomsize.Z + 2, -2));
+ if (doordir == v3s16(-1, 0, 0)) // X-
+ roomplace = doorplace +
+ v3s16(-roomsize.X + 1, -1, random.range(-roomsize.Z + 2, -2));
+ if (doordir == v3s16(0, 0, 1)) // Z+
+ roomplace = doorplace +
+ v3s16(random.range(-roomsize.X + 2, -2), -1, 0);
+ if (doordir == v3s16(0, 0, -1)) // Z-
+ roomplace = doorplace +
+ v3s16(random.range(-roomsize.X + 2, -2), -1, -roomsize.Z + 1);
+
+ // Check fit
+ bool fits = true;
+ for (s16 z = 1; z < roomsize.Z - 1; z++)
+ for (s16 y = 1; y < roomsize.Y - 1; y++)
+ for (s16 x = 1; x < roomsize.X - 1; x++) {
+ v3s16 p = roomplace + v3s16(x, y, z);
+ if (!vm->m_area.contains(p)) {
+ fits = false;
+ break;
+ }
+ if (vm->m_flags[vm->m_area.index(p)] & VMANIP_FLAG_DUNGEON_INSIDE) {
+ fits = false;
+ break;
+ }
+ }
+ if (!fits) {
+ // Find new place
+ continue;
+ }
+ result_doorplace = doorplace;
+ result_doordir = doordir;
+ result_roomplace = roomplace;
+ return true;
+ }
+ return false;
+}
+
+
+v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs)
+{
+ // Make diagonal directions somewhat rare
+ if (diagonal_dirs && (random.next() % 4 == 0)) {
+ v3s16 dir;
+ int trycount = 0;
+
+ do {
+ trycount++;
+
+ dir.Z = random.next() % 3 - 1;
+ dir.Y = 0;
+ dir.X = random.next() % 3 - 1;
+ } while ((dir.X == 0 || dir.Z == 0) && trycount < 10);
+
+ return dir;
+ }
+
+ if (random.next() % 2 == 0)
+ return random.next() % 2 ? v3s16(-1, 0, 0) : v3s16(1, 0, 0);
+
+ return random.next() % 2 ? v3s16(0, 0, -1) : v3s16(0, 0, 1);
+}
+
+
+v3s16 turn_xz(v3s16 olddir, int t)
+{
+ v3s16 dir;
+ if (t == 0) {
+ // Turn right
+ dir.X = olddir.Z;
+ dir.Z = -olddir.X;
+ dir.Y = olddir.Y;
+ } else {
+ // Turn left
+ dir.X = -olddir.Z;
+ dir.Z = olddir.X;
+ dir.Y = olddir.Y;
+ }
+ return dir;
+}
+
+
+v3s16 random_turn(PseudoRandom &random, v3s16 olddir)
+{
+ int turn = random.range(0, 2);
+ v3s16 dir;
+ if (turn == 0)
+ // Go straight
+ dir = olddir;
+ else if (turn == 1)
+ // Turn right
+ dir = turn_xz(olddir, 0);
+ else
+ // Turn left
+ dir = turn_xz(olddir, 1);
+ return dir;
+}
+
+
+int dir_to_facedir(v3s16 d)
+{
+ if (abs(d.X) > abs(d.Z))
+ return d.X < 0 ? 3 : 1;
+
+ return d.Z < 0 ? 2 : 0;
+}
diff --git a/src/mapgen/dungeongen.h b/src/mapgen/dungeongen.h
new file mode 100644
index 000000000..6799db79e
--- /dev/null
+++ b/src/mapgen/dungeongen.h
@@ -0,0 +1,110 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "voxel.h"
+#include "noise.h"
+#include "mapgen.h"
+
+#define VMANIP_FLAG_DUNGEON_INSIDE VOXELFLAG_CHECKED1
+#define VMANIP_FLAG_DUNGEON_PRESERVE VOXELFLAG_CHECKED2
+#define VMANIP_FLAG_DUNGEON_UNTOUCHABLE (\
+ VMANIP_FLAG_DUNGEON_INSIDE|VMANIP_FLAG_DUNGEON_PRESERVE)
+
+class MMVManip;
+class INodeDefManager;
+
+v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs);
+v3s16 turn_xz(v3s16 olddir, int t);
+v3s16 random_turn(PseudoRandom &random, v3s16 olddir);
+int dir_to_facedir(v3s16 d);
+
+
+struct DungeonParams {
+ s32 seed;
+
+ content_t c_water;
+ content_t c_river_water;
+ content_t c_wall;
+ content_t c_alt_wall;
+ content_t c_stair;
+
+ bool diagonal_dirs;
+ bool only_in_ground;
+ v3s16 holesize;
+ u16 corridor_len_min;
+ u16 corridor_len_max;
+ v3s16 room_size_min;
+ v3s16 room_size_max;
+ v3s16 room_size_large_min;
+ v3s16 room_size_large_max;
+ u16 rooms_min;
+ u16 rooms_max;
+ s16 y_min;
+ s16 y_max;
+ GenNotifyType notifytype;
+
+ NoiseParams np_density;
+ NoiseParams np_alt_wall;
+};
+
+class DungeonGen {
+public:
+ MMVManip *vm;
+ INodeDefManager *ndef;
+ GenerateNotifier *gennotify;
+
+ u32 blockseed;
+ PseudoRandom random;
+ v3s16 csize;
+
+ content_t c_torch;
+ DungeonParams dp;
+
+ // RoomWalker
+ v3s16 m_pos;
+ v3s16 m_dir;
+
+ DungeonGen(INodeDefManager *ndef,
+ GenerateNotifier *gennotify, DungeonParams *dparams);
+
+ void generate(MMVManip *vm, u32 bseed,
+ v3s16 full_node_min, v3s16 full_node_max);
+
+ void makeDungeon(v3s16 start_padding);
+ void makeRoom(v3s16 roomsize, v3s16 roomplace);
+ void makeCorridor(v3s16 doorplace, v3s16 doordir,
+ v3s16 &result_place, v3s16 &result_dir);
+ void makeDoor(v3s16 doorplace, v3s16 doordir);
+ void makeFill(v3s16 place, v3s16 size, u8 avoid_flags, MapNode n, u8 or_flags);
+ void makeHole(v3s16 place);
+
+ bool findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir);
+ bool findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace,
+ v3s16 &result_doordir, v3s16 &result_roomplace);
+
+ inline void randomizeDir()
+ {
+ m_dir = rand_ortho_dir(random, dp.diagonal_dirs);
+ }
+};
+
+extern NoiseParams nparams_dungeon_density;
+extern NoiseParams nparams_dungeon_alt_wall;
diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp
new file mode 100644
index 000000000..f73e16dd0
--- /dev/null
+++ b/src/mapgen/mapgen.cpp
@@ -0,0 +1,1140 @@
+/*
+Minetest
+Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "gamedef.h"
+#include "mg_biome.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "content_sao.h"
+#include "nodedef.h"
+#include "emerge.h"
+#include "voxelalgorithms.h"
+#include "porting.h"
+#include "profiler.h"
+#include "settings.h"
+#include "treegen.h"
+#include "serialization.h"
+#include "util/serialize.h"
+#include "util/numeric.h"
+#include "filesys.h"
+#include "log.h"
+#include "mapgen_carpathian.h"
+#include "mapgen_flat.h"
+#include "mapgen_fractal.h"
+#include "mapgen_v5.h"
+#include "mapgen_v6.h"
+#include "mapgen_v7.h"
+#include "mapgen_valleys.h"
+#include "mapgen_singlenode.h"
+#include "cavegen.h"
+#include "dungeongen.h"
+
+FlagDesc flagdesc_mapgen[] = {
+ {"caves", MG_CAVES},
+ {"dungeons", MG_DUNGEONS},
+ {"light", MG_LIGHT},
+ {"decorations", MG_DECORATIONS},
+ {NULL, 0}
+};
+
+FlagDesc flagdesc_gennotify[] = {
+ {"dungeon", 1 << GENNOTIFY_DUNGEON},
+ {"temple", 1 << GENNOTIFY_TEMPLE},
+ {"cave_begin", 1 << GENNOTIFY_CAVE_BEGIN},
+ {"cave_end", 1 << GENNOTIFY_CAVE_END},
+ {"large_cave_begin", 1 << GENNOTIFY_LARGECAVE_BEGIN},
+ {"large_cave_end", 1 << GENNOTIFY_LARGECAVE_END},
+ {"decoration", 1 << GENNOTIFY_DECORATION},
+ {NULL, 0}
+};
+
+struct MapgenDesc {
+ const char *name;
+ bool is_user_visible;
+};
+
+////
+//// Built-in mapgens
+////
+
+static MapgenDesc g_reg_mapgens[] = {
+ {"v5", true},
+ {"v6", true},
+ {"v7", true},
+ {"flat", true},
+ {"fractal", true},
+ {"valleys", true},
+ {"singlenode", true},
+ {"carpathian", true},
+};
+
+STATIC_ASSERT(
+ ARRLEN(g_reg_mapgens) == MAPGEN_INVALID,
+ registered_mapgens_is_wrong_size);
+
+////
+//// Mapgen
+////
+
+Mapgen::Mapgen(int mapgenid, MapgenParams *params, EmergeManager *emerge) :
+ gennotify(emerge->gen_notify_on, &emerge->gen_notify_on_deco_ids)
+{
+ id = mapgenid;
+ water_level = params->water_level;
+ mapgen_limit = params->mapgen_limit;
+ flags = params->flags;
+ csize = v3s16(1, 1, 1) * (params->chunksize * MAP_BLOCKSIZE);
+
+ /*
+ We are losing half our entropy by doing this, but it is necessary to
+ preserve reverse compatibility. If the top half of our current 64 bit
+ seeds ever starts getting used, existing worlds will break due to a
+ different hash outcome and no way to differentiate between versions.
+
+ A solution could be to add a new bit to designate that the top half of
+ the seed value should be used, essentially a 1-bit version code, but
+ this would require increasing the total size of a seed to 9 bytes (yuck)
+
+ It's probably okay if this never gets fixed. 4.2 billion possibilities
+ ought to be enough for anyone.
+ */
+ seed = (s32)params->seed;
+
+ ndef = emerge->ndef;
+}
+
+
+MapgenType Mapgen::getMapgenType(const std::string &mgname)
+{
+ for (size_t i = 0; i != ARRLEN(g_reg_mapgens); i++) {
+ if (mgname == g_reg_mapgens[i].name)
+ return (MapgenType)i;
+ }
+
+ return MAPGEN_INVALID;
+}
+
+
+const char *Mapgen::getMapgenName(MapgenType mgtype)
+{
+ size_t index = (size_t)mgtype;
+ if (index == MAPGEN_INVALID || index >= ARRLEN(g_reg_mapgens))
+ return "invalid";
+
+ return g_reg_mapgens[index].name;
+}
+
+
+Mapgen *Mapgen::createMapgen(MapgenType mgtype, int mgid,
+ MapgenParams *params, EmergeManager *emerge)
+{
+ switch (mgtype) {
+ case MAPGEN_CARPATHIAN:
+ return new MapgenCarpathian(mgid, (MapgenCarpathianParams *)params, emerge);
+ case MAPGEN_FLAT:
+ return new MapgenFlat(mgid, (MapgenFlatParams *)params, emerge);
+ case MAPGEN_FRACTAL:
+ return new MapgenFractal(mgid, (MapgenFractalParams *)params, emerge);
+ case MAPGEN_SINGLENODE:
+ return new MapgenSinglenode(mgid, (MapgenSinglenodeParams *)params, emerge);
+ case MAPGEN_V5:
+ return new MapgenV5(mgid, (MapgenV5Params *)params, emerge);
+ case MAPGEN_V6:
+ return new MapgenV6(mgid, (MapgenV6Params *)params, emerge);
+ case MAPGEN_V7:
+ return new MapgenV7(mgid, (MapgenV7Params *)params, emerge);
+ case MAPGEN_VALLEYS:
+ return new MapgenValleys(mgid, (MapgenValleysParams *)params, emerge);
+ default:
+ return NULL;
+ }
+}
+
+
+MapgenParams *Mapgen::createMapgenParams(MapgenType mgtype)
+{
+ switch (mgtype) {
+ case MAPGEN_CARPATHIAN:
+ return new MapgenCarpathianParams;
+ case MAPGEN_FLAT:
+ return new MapgenFlatParams;
+ case MAPGEN_FRACTAL:
+ return new MapgenFractalParams;
+ case MAPGEN_SINGLENODE:
+ return new MapgenSinglenodeParams;
+ case MAPGEN_V5:
+ return new MapgenV5Params;
+ case MAPGEN_V6:
+ return new MapgenV6Params;
+ case MAPGEN_V7:
+ return new MapgenV7Params;
+ case MAPGEN_VALLEYS:
+ return new MapgenValleysParams;
+ default:
+ return NULL;
+ }
+}
+
+
+void Mapgen::getMapgenNames(std::vector<const char *> *mgnames, bool include_hidden)
+{
+ for (u32 i = 0; i != ARRLEN(g_reg_mapgens); i++) {
+ if (include_hidden || g_reg_mapgens[i].is_user_visible)
+ mgnames->push_back(g_reg_mapgens[i].name);
+ }
+}
+
+
+u32 Mapgen::getBlockSeed(v3s16 p, s32 seed)
+{
+ return (u32)seed +
+ p.Z * 38134234 +
+ p.Y * 42123 +
+ p.X * 23;
+}
+
+
+u32 Mapgen::getBlockSeed2(v3s16 p, s32 seed)
+{
+ u32 n = 1619 * p.X + 31337 * p.Y + 52591 * p.Z + 1013 * seed;
+ n = (n >> 13) ^ n;
+ return (n * (n * n * 60493 + 19990303) + 1376312589);
+}
+
+
+// Returns Y one under area minimum if not found
+s16 Mapgen::findGroundLevelFull(v2s16 p2d)
+{
+ const v3s16 &em = vm->m_area.getExtent();
+ s16 y_nodes_max = vm->m_area.MaxEdge.Y;
+ s16 y_nodes_min = vm->m_area.MinEdge.Y;
+ u32 i = vm->m_area.index(p2d.X, y_nodes_max, p2d.Y);
+ s16 y;
+
+ for (y = y_nodes_max; y >= y_nodes_min; y--) {
+ MapNode &n = vm->m_data[i];
+ if (ndef->get(n).walkable)
+ break;
+
+ vm->m_area.add_y(em, i, -1);
+ }
+ return (y >= y_nodes_min) ? y : y_nodes_min - 1;
+}
+
+
+// Returns -MAX_MAP_GENERATION_LIMIT if not found
+s16 Mapgen::findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax)
+{
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 i = vm->m_area.index(p2d.X, ymax, p2d.Y);
+ s16 y;
+
+ for (y = ymax; y >= ymin; y--) {
+ MapNode &n = vm->m_data[i];
+ if (ndef->get(n).walkable)
+ break;
+
+ vm->m_area.add_y(em, i, -1);
+ }
+ return (y >= ymin) ? y : -MAX_MAP_GENERATION_LIMIT;
+}
+
+
+// Returns -MAX_MAP_GENERATION_LIMIT if not found or if ground is found first
+s16 Mapgen::findLiquidSurface(v2s16 p2d, s16 ymin, s16 ymax)
+{
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 i = vm->m_area.index(p2d.X, ymax, p2d.Y);
+ s16 y;
+
+ for (y = ymax; y >= ymin; y--) {
+ MapNode &n = vm->m_data[i];
+ if (ndef->get(n).walkable)
+ return -MAX_MAP_GENERATION_LIMIT;
+
+ if (ndef->get(n).isLiquid())
+ break;
+
+ vm->m_area.add_y(em, i, -1);
+ }
+ return (y >= ymin) ? y : -MAX_MAP_GENERATION_LIMIT;
+}
+
+
+void Mapgen::updateHeightmap(v3s16 nmin, v3s16 nmax)
+{
+ if (!heightmap)
+ return;
+
+ //TimeTaker t("Mapgen::updateHeightmap", NULL, PRECISION_MICRO);
+ int index = 0;
+ for (s16 z = nmin.Z; z <= nmax.Z; z++) {
+ for (s16 x = nmin.X; x <= nmax.X; x++, index++) {
+ s16 y = findGroundLevel(v2s16(x, z), nmin.Y, nmax.Y);
+
+ heightmap[index] = y;
+ }
+ }
+}
+
+
+void Mapgen::getSurfaces(v2s16 p2d, s16 ymin, s16 ymax,
+ s16 *floors, s16 *ceilings, u16 *num_floors, u16 *num_ceilings)
+{
+ u16 floor_i = 0;
+ u16 ceiling_i = 0;
+ const v3s16 &em = vm->m_area.getExtent();
+
+ bool is_walkable = false;
+ u32 vi = vm->m_area.index(p2d.X, ymax, p2d.Y);
+ MapNode mn_max = vm->m_data[vi];
+ bool walkable_above = ndef->get(mn_max).walkable;
+ vm->m_area.add_y(em, vi, -1);
+
+ for (s16 y = ymax - 1; y >= ymin; y--) {
+ MapNode mn = vm->m_data[vi];
+ is_walkable = ndef->get(mn).walkable;
+
+ if (is_walkable && !walkable_above) {
+ floors[floor_i] = y;
+ floor_i++;
+ } else if (!is_walkable && walkable_above) {
+ ceilings[ceiling_i] = y + 1;
+ ceiling_i++;
+ }
+
+ vm->m_area.add_y(em, vi, -1);
+ walkable_above = is_walkable;
+ }
+
+ *num_floors = floor_i;
+ *num_ceilings = ceiling_i;
+}
+
+
+inline bool Mapgen::isLiquidHorizontallyFlowable(u32 vi, v3s16 em)
+{
+ u32 vi_neg_x = vi;
+ vm->m_area.add_x(em, vi_neg_x, -1);
+ if (vm->m_data[vi_neg_x].getContent() != CONTENT_IGNORE) {
+ const ContentFeatures &c_nx = ndef->get(vm->m_data[vi_neg_x]);
+ if (c_nx.floodable && !c_nx.isLiquid())
+ return true;
+ }
+ u32 vi_pos_x = vi;
+ vm->m_area.add_x(em, vi_pos_x, +1);
+ if (vm->m_data[vi_pos_x].getContent() != CONTENT_IGNORE) {
+ const ContentFeatures &c_px = ndef->get(vm->m_data[vi_pos_x]);
+ if (c_px.floodable && !c_px.isLiquid())
+ return true;
+ }
+ u32 vi_neg_z = vi;
+ vm->m_area.add_z(em, vi_neg_z, -1);
+ if (vm->m_data[vi_neg_z].getContent() != CONTENT_IGNORE) {
+ const ContentFeatures &c_nz = ndef->get(vm->m_data[vi_neg_z]);
+ if (c_nz.floodable && !c_nz.isLiquid())
+ return true;
+ }
+ u32 vi_pos_z = vi;
+ vm->m_area.add_z(em, vi_pos_z, +1);
+ if (vm->m_data[vi_pos_z].getContent() != CONTENT_IGNORE) {
+ const ContentFeatures &c_pz = ndef->get(vm->m_data[vi_pos_z]);
+ if (c_pz.floodable && !c_pz.isLiquid())
+ return true;
+ }
+ return false;
+}
+
+void Mapgen::updateLiquid(UniqueQueue<v3s16> *trans_liquid, v3s16 nmin, v3s16 nmax)
+{
+ bool isignored, isliquid, wasignored, wasliquid, waschecked, waspushed;
+ const v3s16 &em = vm->m_area.getExtent();
+
+ for (s16 z = nmin.Z + 1; z <= nmax.Z - 1; z++)
+ for (s16 x = nmin.X + 1; x <= nmax.X - 1; x++) {
+ wasignored = true;
+ wasliquid = false;
+ waschecked = false;
+ waspushed = false;
+
+ u32 vi = vm->m_area.index(x, nmax.Y, z);
+ for (s16 y = nmax.Y; y >= nmin.Y; y--) {
+ isignored = vm->m_data[vi].getContent() == CONTENT_IGNORE;
+ isliquid = ndef->get(vm->m_data[vi]).isLiquid();
+
+ if (isignored || wasignored || isliquid == wasliquid) {
+ // Neither topmost node of liquid column nor topmost node below column
+ waschecked = false;
+ waspushed = false;
+ } else if (isliquid) {
+ // This is the topmost node in the column
+ bool ispushed = false;
+ if (isLiquidHorizontallyFlowable(vi, em)) {
+ trans_liquid->push_back(v3s16(x, y, z));
+ ispushed = true;
+ }
+ // Remember waschecked and waspushed to avoid repeated
+ // checks/pushes in case the column consists of only this node
+ waschecked = true;
+ waspushed = ispushed;
+ } else {
+ // This is the topmost node below a liquid column
+ u32 vi_above = vi;
+ vm->m_area.add_y(em, vi_above, 1);
+ if (!waspushed && (ndef->get(vm->m_data[vi]).floodable ||
+ (!waschecked && isLiquidHorizontallyFlowable(vi_above, em)))) {
+ // Push back the lowest node in the column which is one
+ // node above this one
+ trans_liquid->push_back(v3s16(x, y + 1, z));
+ }
+ }
+
+ wasliquid = isliquid;
+ wasignored = isignored;
+ vm->m_area.add_y(em, vi, -1);
+ }
+ }
+}
+
+
+void Mapgen::setLighting(u8 light, v3s16 nmin, v3s16 nmax)
+{
+ ScopeProfiler sp(g_profiler, "EmergeThread: mapgen lighting update", SPT_AVG);
+ VoxelArea a(nmin, nmax);
+
+ for (int z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) {
+ for (int y = a.MinEdge.Y; y <= a.MaxEdge.Y; y++) {
+ u32 i = vm->m_area.index(a.MinEdge.X, y, z);
+ for (int x = a.MinEdge.X; x <= a.MaxEdge.X; x++, i++)
+ vm->m_data[i].param1 = light;
+ }
+ }
+}
+
+
+void Mapgen::lightSpread(VoxelArea &a, v3s16 p, u8 light)
+{
+ if (light <= 1 || !a.contains(p))
+ return;
+
+ u32 vi = vm->m_area.index(p);
+ MapNode &n = vm->m_data[vi];
+
+ // Decay light in each of the banks separately
+ u8 light_day = light & 0x0F;
+ if (light_day > 0)
+ light_day -= 0x01;
+
+ u8 light_night = light & 0xF0;
+ if (light_night > 0)
+ light_night -= 0x10;
+
+ // Bail out only if we have no more light from either bank to propogate, or
+ // we hit a solid block that light cannot pass through.
+ if ((light_day <= (n.param1 & 0x0F) &&
+ light_night <= (n.param1 & 0xF0)) ||
+ !ndef->get(n).light_propagates)
+ return;
+
+ // Since this recursive function only terminates when there is no light from
+ // either bank left, we need to take the max of both banks into account for
+ // the case where spreading has stopped for one light bank but not the other.
+ light = MYMAX(light_day, n.param1 & 0x0F) |
+ MYMAX(light_night, n.param1 & 0xF0);
+
+ n.param1 = light;
+
+ lightSpread(a, p + v3s16(0, 0, 1), light);
+ lightSpread(a, p + v3s16(0, 1, 0), light);
+ lightSpread(a, p + v3s16(1, 0, 0), light);
+ lightSpread(a, p - v3s16(0, 0, 1), light);
+ lightSpread(a, p - v3s16(0, 1, 0), light);
+ lightSpread(a, p - v3s16(1, 0, 0), light);
+}
+
+
+void Mapgen::calcLighting(v3s16 nmin, v3s16 nmax, v3s16 full_nmin, v3s16 full_nmax,
+ bool propagate_shadow)
+{
+ ScopeProfiler sp(g_profiler, "EmergeThread: mapgen lighting update", SPT_AVG);
+ //TimeTaker t("updateLighting");
+
+ propagateSunlight(nmin, nmax, propagate_shadow);
+ spreadLight(full_nmin, full_nmax);
+
+ //printf("updateLighting: %dms\n", t.stop());
+}
+
+
+void Mapgen::propagateSunlight(v3s16 nmin, v3s16 nmax, bool propagate_shadow)
+{
+ //TimeTaker t("propagateSunlight");
+ VoxelArea a(nmin, nmax);
+ bool block_is_underground = (water_level >= nmax.Y);
+ const v3s16 &em = vm->m_area.getExtent();
+
+ // NOTE: Direct access to the low 4 bits of param1 is okay here because,
+ // by definition, sunlight will never be in the night lightbank.
+
+ for (int z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) {
+ for (int x = a.MinEdge.X; x <= a.MaxEdge.X; x++) {
+ // see if we can get a light value from the overtop
+ u32 i = vm->m_area.index(x, a.MaxEdge.Y + 1, z);
+ if (vm->m_data[i].getContent() == CONTENT_IGNORE) {
+ if (block_is_underground)
+ continue;
+ } else if ((vm->m_data[i].param1 & 0x0F) != LIGHT_SUN &&
+ propagate_shadow) {
+ continue;
+ }
+ vm->m_area.add_y(em, i, -1);
+
+ for (int y = a.MaxEdge.Y; y >= a.MinEdge.Y; y--) {
+ MapNode &n = vm->m_data[i];
+ if (!ndef->get(n).sunlight_propagates)
+ break;
+ n.param1 = LIGHT_SUN;
+ vm->m_area.add_y(em, i, -1);
+ }
+ }
+ }
+ //printf("propagateSunlight: %dms\n", t.stop());
+}
+
+
+void Mapgen::spreadLight(v3s16 nmin, v3s16 nmax)
+{
+ //TimeTaker t("spreadLight");
+ VoxelArea a(nmin, nmax);
+
+ for (int z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) {
+ for (int y = a.MinEdge.Y; y <= a.MaxEdge.Y; y++) {
+ u32 i = vm->m_area.index(a.MinEdge.X, y, z);
+ for (int x = a.MinEdge.X; x <= a.MaxEdge.X; x++, i++) {
+ MapNode &n = vm->m_data[i];
+ if (n.getContent() == CONTENT_IGNORE)
+ continue;
+
+ const ContentFeatures &cf = ndef->get(n);
+ if (!cf.light_propagates)
+ continue;
+
+ // TODO(hmmmmm): Abstract away direct param1 accesses with a
+ // wrapper, but something lighter than MapNode::get/setLight
+
+ u8 light_produced = cf.light_source;
+ if (light_produced)
+ n.param1 = light_produced | (light_produced << 4);
+
+ u8 light = n.param1;
+ if (light) {
+ lightSpread(a, v3s16(x, y, z + 1), light);
+ lightSpread(a, v3s16(x, y + 1, z ), light);
+ lightSpread(a, v3s16(x + 1, y, z ), light);
+ lightSpread(a, v3s16(x, y, z - 1), light);
+ lightSpread(a, v3s16(x, y - 1, z ), light);
+ lightSpread(a, v3s16(x - 1, y, z ), light);
+ }
+ }
+ }
+ }
+
+ //printf("spreadLight: %dms\n", t.stop());
+}
+
+
+////
+//// MapgenBasic
+////
+
+MapgenBasic::MapgenBasic(int mapgenid, MapgenParams *params, EmergeManager *emerge)
+ : Mapgen(mapgenid, params, emerge)
+{
+ this->m_emerge = emerge;
+ this->m_bmgr = emerge->biomemgr;
+
+ //// Here, 'stride' refers to the number of elements needed to skip to index
+ //// an adjacent element for that coordinate in noise/height/biome maps
+ //// (*not* vmanip content map!)
+
+ // Note there is no X stride explicitly defined. Items adjacent in the X
+ // coordinate are assumed to be adjacent in memory as well (i.e. stride of 1).
+
+ // Number of elements to skip to get to the next Y coordinate
+ this->ystride = csize.X;
+
+ // Number of elements to skip to get to the next Z coordinate
+ this->zstride = csize.X * csize.Y;
+
+ // Z-stride value for maps oversized for 1-down overgeneration
+ this->zstride_1d = csize.X * (csize.Y + 1);
+
+ // Z-stride value for maps oversized for 1-up 1-down overgeneration
+ this->zstride_1u1d = csize.X * (csize.Y + 2);
+
+ //// Allocate heightmap
+ this->heightmap = new s16[csize.X * csize.Z];
+
+ //// Initialize biome generator
+ // TODO(hmmmm): should we have a way to disable biomemanager biomes?
+ biomegen = m_bmgr->createBiomeGen(BIOMEGEN_ORIGINAL, params->bparams, csize);
+ biomemap = biomegen->biomemap;
+
+ //// Look up some commonly used content
+ c_stone = ndef->getId("mapgen_stone");
+ c_desert_stone = ndef->getId("mapgen_desert_stone");
+ c_sandstone = ndef->getId("mapgen_sandstone");
+ c_water_source = ndef->getId("mapgen_water_source");
+ c_river_water_source = ndef->getId("mapgen_river_water_source");
+ c_lava_source = ndef->getId("mapgen_lava_source");
+
+ // Fall back to more basic content if not defined
+ // river_water_source cannot fallback to water_source because river water
+ // needs to be non-renewable and have a short flow range.
+ if (c_desert_stone == CONTENT_IGNORE)
+ c_desert_stone = c_stone;
+ if (c_sandstone == CONTENT_IGNORE)
+ c_sandstone = c_stone;
+
+ //// Content used for dungeon generation
+ c_cobble = ndef->getId("mapgen_cobble");
+ c_mossycobble = ndef->getId("mapgen_mossycobble");
+ c_stair_cobble = ndef->getId("mapgen_stair_cobble");
+ c_stair_desert_stone = ndef->getId("mapgen_stair_desert_stone");
+ c_sandstonebrick = ndef->getId("mapgen_sandstonebrick");
+ c_stair_sandstone_block = ndef->getId("mapgen_stair_sandstone_block");
+
+ // Fall back to more basic content if not defined
+ if (c_mossycobble == CONTENT_IGNORE)
+ c_mossycobble = c_cobble;
+ if (c_stair_cobble == CONTENT_IGNORE)
+ c_stair_cobble = c_cobble;
+ if (c_stair_desert_stone == CONTENT_IGNORE)
+ c_stair_desert_stone = c_desert_stone;
+ if (c_sandstonebrick == CONTENT_IGNORE)
+ c_sandstonebrick = c_sandstone;
+ if (c_stair_sandstone_block == CONTENT_IGNORE)
+ c_stair_sandstone_block = c_sandstonebrick;
+}
+
+
+MapgenBasic::~MapgenBasic()
+{
+ delete biomegen;
+ delete []heightmap;
+}
+
+
+void MapgenBasic::generateBiomes(MgStoneType *mgstone_type,
+ content_t *biome_stone)
+{
+ // can't generate biomes without a biome generator!
+ assert(biomegen);
+ assert(biomemap);
+
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 index = 0;
+ MgStoneType stone_type = MGSTONE_OTHER;
+ content_t c_biome_stone = c_stone;
+
+ noise_filler_depth->perlinMap2D(node_min.X, node_min.Z);
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
+ Biome *biome = NULL;
+ u16 depth_top = 0;
+ u16 base_filler = 0;
+ u16 depth_water_top = 0;
+ u16 depth_riverbed = 0;
+ s16 biome_y_min = -MAX_MAP_GENERATION_LIMIT;
+ u32 vi = vm->m_area.index(x, node_max.Y, z);
+
+ // Check node at base of mapchunk above, either a node of a previously
+ // generated mapchunk or if not, a node of overgenerated base terrain.
+ content_t c_above = vm->m_data[vi + em.X].getContent();
+ bool air_above = c_above == CONTENT_AIR;
+ bool river_water_above = c_above == c_river_water_source;
+ bool water_above = c_above == c_water_source || river_water_above;
+
+ biomemap[index] = BIOME_NONE;
+
+ // If there is air or water above enable top/filler placement, otherwise force
+ // nplaced to stone level by setting a number exceeding any possible filler depth.
+ u16 nplaced = (air_above || water_above) ? 0 : U16_MAX;
+
+ for (s16 y = node_max.Y; y >= node_min.Y; y--) {
+ content_t c = vm->m_data[vi].getContent();
+ // Biome is (re)calculated:
+ // 1. At the surface of stone below air or water.
+ // 2. At the surface of water below air.
+ // 3. When stone or water is detected but biome has not yet been calculated.
+ // 4. When stone or water is detected just below a biome's lower limit.
+ bool is_stone_surface = (c == c_stone) &&
+ (air_above || water_above || !biome || y < biome_y_min); // 1, 3, 4
+
+ bool is_water_surface =
+ (c == c_water_source || c == c_river_water_source) &&
+ (air_above || !biome || y < biome_y_min); // 2, 3, 4
+
+ if (is_stone_surface || is_water_surface) {
+ // (Re)calculate biome
+ biome = biomegen->getBiomeAtIndex(index, y);
+
+ if (biomemap[index] == BIOME_NONE && is_stone_surface)
+ biomemap[index] = biome->index;
+
+ depth_top = biome->depth_top;
+ base_filler = MYMAX(depth_top +
+ biome->depth_filler +
+ noise_filler_depth->result[index], 0.0f);
+ depth_water_top = biome->depth_water_top;
+ depth_riverbed = biome->depth_riverbed;
+ biome_y_min = biome->y_min;
+
+ // Detect stone type for dungeons during every biome calculation.
+ // If none detected the last selected biome stone is chosen.
+ if (biome->c_stone == c_stone)
+ stone_type = MGSTONE_STONE;
+ else if (biome->c_stone == c_desert_stone)
+ stone_type = MGSTONE_DESERT_STONE;
+ else if (biome->c_stone == c_sandstone)
+ stone_type = MGSTONE_SANDSTONE;
+
+ c_biome_stone = biome->c_stone;
+ }
+
+ if (c == c_stone) {
+ content_t c_below = vm->m_data[vi - em.X].getContent();
+
+ // If the node below isn't solid, make this node stone, so that
+ // any top/filler nodes above are structurally supported.
+ // This is done by aborting the cycle of top/filler placement
+ // immediately by forcing nplaced to stone level.
+ if (c_below == CONTENT_AIR
+ || c_below == c_water_source
+ || c_below == c_river_water_source)
+ nplaced = U16_MAX;
+
+ if (river_water_above) {
+ if (nplaced < depth_riverbed) {
+ vm->m_data[vi] = MapNode(biome->c_riverbed);
+ nplaced++;
+ } else {
+ nplaced = U16_MAX; // Disable top/filler placement
+ river_water_above = false;
+ }
+ } else if (nplaced < depth_top) {
+ vm->m_data[vi] = MapNode(biome->c_top);
+ nplaced++;
+ } else if (nplaced < base_filler) {
+ vm->m_data[vi] = MapNode(biome->c_filler);
+ nplaced++;
+ } else {
+ vm->m_data[vi] = MapNode(biome->c_stone);
+ nplaced = U16_MAX; // Disable top/filler placement
+ }
+
+ air_above = false;
+ water_above = false;
+ } else if (c == c_water_source) {
+ vm->m_data[vi] = MapNode((y > (s32)(water_level - depth_water_top))
+ ? biome->c_water_top : biome->c_water);
+ nplaced = 0; // Enable top/filler placement for next surface
+ air_above = false;
+ water_above = true;
+ } else if (c == c_river_water_source) {
+ vm->m_data[vi] = MapNode(biome->c_river_water);
+ nplaced = 0; // Enable riverbed placement for next surface
+ air_above = false;
+ water_above = true;
+ river_water_above = true;
+ } else if (c == CONTENT_AIR) {
+ nplaced = 0; // Enable top/filler placement for next surface
+ air_above = true;
+ water_above = false;
+ } else { // Possible various nodes overgenerated from neighbouring mapchunks
+ nplaced = U16_MAX; // Disable top/filler placement
+ air_above = false;
+ water_above = false;
+ }
+
+ vm->m_area.add_y(em, vi, -1);
+ }
+ }
+
+ *mgstone_type = stone_type;
+ *biome_stone = c_biome_stone;
+}
+
+
+void MapgenBasic::dustTopNodes()
+{
+ if (node_max.Y < water_level)
+ return;
+
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 index = 0;
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
+ Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index]);
+
+ if (biome->c_dust == CONTENT_IGNORE)
+ continue;
+
+ u32 vi = vm->m_area.index(x, full_node_max.Y, z);
+ content_t c_full_max = vm->m_data[vi].getContent();
+ s16 y_start;
+
+ if (c_full_max == CONTENT_AIR) {
+ y_start = full_node_max.Y - 1;
+ } else if (c_full_max == CONTENT_IGNORE) {
+ vi = vm->m_area.index(x, node_max.Y + 1, z);
+ content_t c_max = vm->m_data[vi].getContent();
+
+ if (c_max == CONTENT_AIR)
+ y_start = node_max.Y;
+ else
+ continue;
+ } else {
+ continue;
+ }
+
+ vi = vm->m_area.index(x, y_start, z);
+ for (s16 y = y_start; y >= node_min.Y - 1; y--) {
+ if (vm->m_data[vi].getContent() != CONTENT_AIR)
+ break;
+
+ vm->m_area.add_y(em, vi, -1);
+ }
+
+ content_t c = vm->m_data[vi].getContent();
+ if (!ndef->get(c).buildable_to && c != CONTENT_IGNORE && c != biome->c_dust) {
+ vm->m_area.add_y(em, vi, 1);
+ vm->m_data[vi] = MapNode(biome->c_dust);
+ }
+ }
+}
+
+
+void MapgenBasic::generateCaves(s16 max_stone_y, s16 large_cave_depth)
+{
+ if (max_stone_y < node_min.Y)
+ return;
+
+ CavesNoiseIntersection caves_noise(ndef, m_bmgr, csize,
+ &np_cave1, &np_cave2, seed, cave_width);
+
+ caves_noise.generateCaves(vm, node_min, node_max, biomemap);
+
+ if (node_max.Y > large_cave_depth)
+ return;
+
+ PseudoRandom ps(blockseed + 21343);
+ u32 bruises_count = ps.range(0, 2);
+ for (u32 i = 0; i < bruises_count; i++) {
+ CavesRandomWalk cave(ndef, &gennotify, seed, water_level,
+ c_water_source, CONTENT_IGNORE, lava_depth);
+
+ cave.makeCave(vm, node_min, node_max, &ps, true, max_stone_y, heightmap);
+ }
+}
+
+
+bool MapgenBasic::generateCaverns(s16 max_stone_y)
+{
+ if (node_min.Y > max_stone_y || node_min.Y > cavern_limit)
+ return false;
+
+ CavernsNoise caverns_noise(ndef, csize, &np_cavern,
+ seed, cavern_limit, cavern_taper, cavern_threshold);
+
+ return caverns_noise.generateCaverns(vm, node_min, node_max);
+}
+
+
+void MapgenBasic::generateDungeons(s16 max_stone_y,
+ MgStoneType stone_type, content_t biome_stone)
+{
+ if (max_stone_y < node_min.Y)
+ return;
+
+ DungeonParams dp;
+
+ dp.seed = seed;
+ dp.c_water = c_water_source;
+ dp.c_river_water = c_river_water_source;
+
+ dp.only_in_ground = true;
+ dp.corridor_len_min = 1;
+ dp.corridor_len_max = 13;
+ dp.rooms_min = 2;
+ dp.rooms_max = 16;
+ dp.y_min = -MAX_MAP_GENERATION_LIMIT;
+ dp.y_max = MAX_MAP_GENERATION_LIMIT;
+
+ dp.np_density = nparams_dungeon_density;
+ dp.np_alt_wall = nparams_dungeon_alt_wall;
+
+ switch (stone_type) {
+ default:
+ case MGSTONE_STONE:
+ dp.c_wall = c_cobble;
+ dp.c_alt_wall = c_mossycobble;
+ dp.c_stair = c_stair_cobble;
+
+ dp.diagonal_dirs = false;
+ dp.holesize = v3s16(1, 2, 1);
+ dp.room_size_min = v3s16(4, 4, 4);
+ dp.room_size_max = v3s16(8, 6, 8);
+ dp.room_size_large_min = v3s16(8, 8, 8);
+ dp.room_size_large_max = v3s16(16, 16, 16);
+ dp.notifytype = GENNOTIFY_DUNGEON;
+ break;
+ case MGSTONE_DESERT_STONE:
+ dp.c_wall = c_desert_stone;
+ dp.c_alt_wall = CONTENT_IGNORE;
+ dp.c_stair = c_stair_desert_stone;
+
+ dp.diagonal_dirs = true;
+ dp.holesize = v3s16(2, 3, 2);
+ dp.room_size_min = v3s16(6, 9, 6);
+ dp.room_size_max = v3s16(10, 11, 10);
+ dp.room_size_large_min = v3s16(10, 13, 10);
+ dp.room_size_large_max = v3s16(18, 21, 18);
+ dp.notifytype = GENNOTIFY_TEMPLE;
+ break;
+ case MGSTONE_SANDSTONE:
+ dp.c_wall = c_sandstonebrick;
+ dp.c_alt_wall = CONTENT_IGNORE;
+ dp.c_stair = c_stair_sandstone_block;
+
+ dp.diagonal_dirs = false;
+ dp.holesize = v3s16(2, 2, 2);
+ dp.room_size_min = v3s16(6, 4, 6);
+ dp.room_size_max = v3s16(10, 6, 10);
+ dp.room_size_large_min = v3s16(10, 8, 10);
+ dp.room_size_large_max = v3s16(18, 16, 18);
+ dp.notifytype = GENNOTIFY_DUNGEON;
+ break;
+ case MGSTONE_OTHER:
+ dp.c_wall = biome_stone;
+ dp.c_alt_wall = biome_stone;
+ dp.c_stair = biome_stone;
+
+ dp.diagonal_dirs = false;
+ dp.holesize = v3s16(1, 2, 1);
+ dp.room_size_min = v3s16(4, 4, 4);
+ dp.room_size_max = v3s16(8, 6, 8);
+ dp.room_size_large_min = v3s16(8, 8, 8);
+ dp.room_size_large_max = v3s16(16, 16, 16);
+ dp.notifytype = GENNOTIFY_DUNGEON;
+ break;
+ }
+
+ DungeonGen dgen(ndef, &gennotify, &dp);
+ dgen.generate(vm, blockseed, full_node_min, full_node_max);
+}
+
+
+////
+//// GenerateNotifier
+////
+
+GenerateNotifier::GenerateNotifier(u32 notify_on,
+ std::set<u32> *notify_on_deco_ids)
+{
+ m_notify_on = notify_on;
+ m_notify_on_deco_ids = notify_on_deco_ids;
+}
+
+
+void GenerateNotifier::setNotifyOn(u32 notify_on)
+{
+ m_notify_on = notify_on;
+}
+
+
+void GenerateNotifier::setNotifyOnDecoIds(std::set<u32> *notify_on_deco_ids)
+{
+ m_notify_on_deco_ids = notify_on_deco_ids;
+}
+
+
+bool GenerateNotifier::addEvent(GenNotifyType type, v3s16 pos, u32 id)
+{
+ if (!(m_notify_on & (1 << type)))
+ return false;
+
+ if (type == GENNOTIFY_DECORATION &&
+ m_notify_on_deco_ids->find(id) == m_notify_on_deco_ids->end())
+ return false;
+
+ GenNotifyEvent gne;
+ gne.type = type;
+ gne.pos = pos;
+ gne.id = id;
+ m_notify_events.push_back(gne);
+
+ return true;
+}
+
+
+void GenerateNotifier::getEvents(
+ std::map<std::string, std::vector<v3s16> > &event_map,
+ bool peek_events)
+{
+ std::list<GenNotifyEvent>::iterator it;
+
+ for (it = m_notify_events.begin(); it != m_notify_events.end(); ++it) {
+ GenNotifyEvent &gn = *it;
+ std::string name = (gn.type == GENNOTIFY_DECORATION) ?
+ "decoration#"+ itos(gn.id) :
+ flagdesc_gennotify[gn.type].name;
+
+ event_map[name].push_back(gn.pos);
+ }
+
+ if (!peek_events)
+ m_notify_events.clear();
+}
+
+
+////
+//// MapgenParams
+////
+
+
+MapgenParams::~MapgenParams()
+{
+ delete bparams;
+}
+
+
+void MapgenParams::readParams(const Settings *settings)
+{
+ std::string seed_str;
+ const char *seed_name = (settings == g_settings) ? "fixed_map_seed" : "seed";
+
+ if (settings->getNoEx(seed_name, seed_str)) {
+ if (!seed_str.empty())
+ seed = read_seed(seed_str.c_str());
+ else
+ myrand_bytes(&seed, sizeof(seed));
+ }
+
+ std::string mg_name;
+ if (settings->getNoEx("mg_name", mg_name)) {
+ mgtype = Mapgen::getMapgenType(mg_name);
+ if (mgtype == MAPGEN_INVALID)
+ mgtype = MAPGEN_DEFAULT;
+ }
+
+ settings->getS16NoEx("water_level", water_level);
+ settings->getS16NoEx("mapgen_limit", mapgen_limit);
+ settings->getS16NoEx("chunksize", chunksize);
+ settings->getFlagStrNoEx("mg_flags", flags, flagdesc_mapgen);
+
+ delete bparams;
+ bparams = BiomeManager::createBiomeParams(BIOMEGEN_ORIGINAL);
+ if (bparams) {
+ bparams->readParams(settings);
+ bparams->seed = seed;
+ }
+}
+
+
+void MapgenParams::writeParams(Settings *settings) const
+{
+ settings->set("mg_name", Mapgen::getMapgenName(mgtype));
+ settings->setU64("seed", seed);
+ settings->setS16("water_level", water_level);
+ settings->setS16("mapgen_limit", mapgen_limit);
+ settings->setS16("chunksize", chunksize);
+ settings->setFlagStr("mg_flags", flags, flagdesc_mapgen, U32_MAX);
+
+ if (bparams)
+ bparams->writeParams(settings);
+}
+
+// Calculate edges of outermost generated mapchunks (less than
+// 'mapgen_limit'), and corresponding exact limits for SAO entities.
+void MapgenParams::calcMapgenEdges()
+{
+ if (m_mapgen_edges_calculated)
+ return;
+
+ // Central chunk offset, in blocks
+ s16 ccoff_b = -chunksize / 2;
+ // Chunksize, in nodes
+ s32 csize_n = chunksize * MAP_BLOCKSIZE;
+ // Minp/maxp of central chunk, in nodes
+ s16 ccmin = ccoff_b * MAP_BLOCKSIZE;
+ s16 ccmax = ccmin + csize_n - 1;
+ // Fullminp/fullmaxp of central chunk, in nodes
+ s16 ccfmin = ccmin - MAP_BLOCKSIZE;
+ s16 ccfmax = ccmax + MAP_BLOCKSIZE;
+ // Effective mapgen limit, in blocks
+ // Uses same calculation as ServerMap::blockpos_over_mapgen_limit(v3s16 p)
+ s16 mapgen_limit_b = rangelim(mapgen_limit,
+ 0, MAX_MAP_GENERATION_LIMIT) / MAP_BLOCKSIZE;
+ // Effective mapgen limits, in nodes
+ s16 mapgen_limit_min = -mapgen_limit_b * MAP_BLOCKSIZE;
+ s16 mapgen_limit_max = (mapgen_limit_b + 1) * MAP_BLOCKSIZE - 1;
+ // Number of complete chunks from central chunk fullminp/fullmaxp
+ // to effective mapgen limits.
+ s16 numcmin = MYMAX((ccfmin - mapgen_limit_min) / csize_n, 0);
+ s16 numcmax = MYMAX((mapgen_limit_max - ccfmax) / csize_n, 0);
+ // Mapgen edges, in nodes
+ mapgen_edge_min = ccmin - numcmin * csize_n;
+ mapgen_edge_max = ccmax + numcmax * csize_n;
+ // SAO position limits, in Irrlicht units
+ m_sao_limit_min = mapgen_edge_min * BS - 3.0f;
+ m_sao_limit_max = mapgen_edge_max * BS + 3.0f;
+
+ m_mapgen_edges_calculated = true;
+}
+
+
+bool MapgenParams::saoPosOverLimit(const v3f &p)
+{
+ if (!m_mapgen_edges_calculated)
+ calcMapgenEdges();
+
+ return p.X < m_sao_limit_min ||
+ p.X > m_sao_limit_max ||
+ p.Y < m_sao_limit_min ||
+ p.Y > m_sao_limit_max ||
+ p.Z < m_sao_limit_min ||
+ p.Z > m_sao_limit_max;
+}
+
+
+s32 MapgenParams::getSpawnRangeMax()
+{
+ calcMapgenEdges();
+
+ return MYMIN(-mapgen_edge_min, mapgen_edge_max);
+}
diff --git a/src/mapgen/mapgen.h b/src/mapgen/mapgen.h
new file mode 100644
index 000000000..8994fdc00
--- /dev/null
+++ b/src/mapgen/mapgen.h
@@ -0,0 +1,302 @@
+/*
+Minetest
+Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "noise.h"
+#include "nodedef.h"
+#include "util/string.h"
+#include "util/container.h"
+
+#define MAPGEN_DEFAULT MAPGEN_V7
+#define MAPGEN_DEFAULT_NAME "v7"
+
+/////////////////// Mapgen flags
+#define MG_TREES 0x01 // Deprecated. Moved into mgv6 flags
+#define MG_CAVES 0x02
+#define MG_DUNGEONS 0x04
+#define MG_FLAT 0x08 // Deprecated. Moved into mgv6 flags
+#define MG_LIGHT 0x10
+#define MG_DECORATIONS 0x20
+
+typedef u8 biome_t; // copy from mg_biome.h to avoid an unnecessary include
+
+class Settings;
+class MMVManip;
+class INodeDefManager;
+
+extern FlagDesc flagdesc_mapgen[];
+extern FlagDesc flagdesc_gennotify[];
+
+class Biome;
+class BiomeGen;
+struct BiomeParams;
+class BiomeManager;
+class EmergeManager;
+class MapBlock;
+class VoxelManipulator;
+struct BlockMakeData;
+class VoxelArea;
+class Map;
+
+enum MapgenObject {
+ MGOBJ_VMANIP,
+ MGOBJ_HEIGHTMAP,
+ MGOBJ_BIOMEMAP,
+ MGOBJ_HEATMAP,
+ MGOBJ_HUMIDMAP,
+ MGOBJ_GENNOTIFY
+};
+
+enum GenNotifyType {
+ GENNOTIFY_DUNGEON,
+ GENNOTIFY_TEMPLE,
+ GENNOTIFY_CAVE_BEGIN,
+ GENNOTIFY_CAVE_END,
+ GENNOTIFY_LARGECAVE_BEGIN,
+ GENNOTIFY_LARGECAVE_END,
+ GENNOTIFY_DECORATION,
+ NUM_GENNOTIFY_TYPES
+};
+
+enum MgStoneType {
+ MGSTONE_STONE,
+ MGSTONE_DESERT_STONE,
+ MGSTONE_SANDSTONE,
+ MGSTONE_OTHER,
+};
+
+struct GenNotifyEvent {
+ GenNotifyType type;
+ v3s16 pos;
+ u32 id;
+};
+
+class GenerateNotifier {
+public:
+ GenerateNotifier() = default;
+ GenerateNotifier(u32 notify_on, std::set<u32> *notify_on_deco_ids);
+
+ void setNotifyOn(u32 notify_on);
+ void setNotifyOnDecoIds(std::set<u32> *notify_on_deco_ids);
+
+ bool addEvent(GenNotifyType type, v3s16 pos, u32 id=0);
+ void getEvents(std::map<std::string, std::vector<v3s16> > &event_map,
+ bool peek_events=false);
+
+private:
+ u32 m_notify_on = 0;
+ std::set<u32> *m_notify_on_deco_ids;
+ std::list<GenNotifyEvent> m_notify_events;
+};
+
+enum MapgenType {
+ MAPGEN_V5,
+ MAPGEN_V6,
+ MAPGEN_V7,
+ MAPGEN_FLAT,
+ MAPGEN_FRACTAL,
+ MAPGEN_VALLEYS,
+ MAPGEN_SINGLENODE,
+ MAPGEN_CARPATHIAN,
+ MAPGEN_INVALID,
+};
+
+struct MapgenParams {
+ MapgenParams() = default;
+ virtual ~MapgenParams();
+
+ MapgenType mgtype = MAPGEN_DEFAULT;
+ s16 chunksize = 5;
+ u64 seed = 0;
+ s16 water_level = 1;
+ s16 mapgen_limit = MAX_MAP_GENERATION_LIMIT;
+ u32 flags = MG_CAVES | MG_LIGHT | MG_DECORATIONS;
+
+ BiomeParams *bparams = nullptr;
+
+ s16 mapgen_edge_min = -MAX_MAP_GENERATION_LIMIT;
+ s16 mapgen_edge_max = MAX_MAP_GENERATION_LIMIT;
+
+ virtual void readParams(const Settings *settings);
+ virtual void writeParams(Settings *settings) const;
+
+ bool saoPosOverLimit(const v3f &p);
+ s32 getSpawnRangeMax();
+
+private:
+ void calcMapgenEdges();
+
+ float m_sao_limit_min = -MAX_MAP_GENERATION_LIMIT * BS;
+ float m_sao_limit_max = MAX_MAP_GENERATION_LIMIT * BS;
+ bool m_mapgen_edges_calculated = false;
+};
+
+
+/*
+ Generic interface for map generators. All mapgens must inherit this class.
+ If a feature exposed by a public member pointer is not supported by a
+ certain mapgen, it must be set to NULL.
+
+ Apart from makeChunk, getGroundLevelAtPoint, and getSpawnLevelAtPoint, all
+ methods can be used by constructing a Mapgen base class and setting the
+ appropriate public members (e.g. vm, ndef, and so on).
+*/
+class Mapgen {
+public:
+ s32 seed = 0;
+ int water_level = 0;
+ int mapgen_limit = 0;
+ u32 flags = 0;
+ bool generating = false;
+ int id = -1;
+
+ MMVManip *vm = nullptr;
+ INodeDefManager *ndef = nullptr;
+
+ u32 blockseed;
+ s16 *heightmap = nullptr;
+ biome_t *biomemap = nullptr;
+ v3s16 csize;
+
+ BiomeGen *biomegen = nullptr;
+ GenerateNotifier gennotify;
+
+ Mapgen() = default;
+ Mapgen(int mapgenid, MapgenParams *params, EmergeManager *emerge);
+ virtual ~Mapgen() = default;
+ DISABLE_CLASS_COPY(Mapgen);
+
+ virtual MapgenType getType() const { return MAPGEN_INVALID; }
+
+ static u32 getBlockSeed(v3s16 p, s32 seed);
+ static u32 getBlockSeed2(v3s16 p, s32 seed);
+ s16 findGroundLevelFull(v2s16 p2d);
+ s16 findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax);
+ s16 findLiquidSurface(v2s16 p2d, s16 ymin, s16 ymax);
+ void updateHeightmap(v3s16 nmin, v3s16 nmax);
+ void getSurfaces(v2s16 p2d, s16 ymin, s16 ymax,
+ s16 *floors, s16 *ceilings, u16 *num_floors, u16 *num_ceilings);
+
+ void updateLiquid(UniqueQueue<v3s16> *trans_liquid, v3s16 nmin, v3s16 nmax);
+
+ void setLighting(u8 light, v3s16 nmin, v3s16 nmax);
+ void lightSpread(VoxelArea &a, v3s16 p, u8 light);
+ void calcLighting(v3s16 nmin, v3s16 nmax, v3s16 full_nmin, v3s16 full_nmax,
+ bool propagate_shadow = true);
+ void propagateSunlight(v3s16 nmin, v3s16 nmax, bool propagate_shadow);
+ void spreadLight(v3s16 nmin, v3s16 nmax);
+
+ virtual void makeChunk(BlockMakeData *data) {}
+ virtual int getGroundLevelAtPoint(v2s16 p) { return 0; }
+
+ // getSpawnLevelAtPoint() is a function within each mapgen that returns a
+ // suitable y co-ordinate for player spawn ('suitable' usually meaning
+ // within 16 nodes of water_level). If a suitable spawn level cannot be
+ // found at the specified (X, Z) 'MAX_MAP_GENERATION_LIMIT' is returned to
+ // signify this and to cause Server::findSpawnPos() to try another (X, Z).
+ virtual int getSpawnLevelAtPoint(v2s16 p) { return 0; }
+
+ // Mapgen management functions
+ static MapgenType getMapgenType(const std::string &mgname);
+ static const char *getMapgenName(MapgenType mgtype);
+ static Mapgen *createMapgen(MapgenType mgtype, int mgid,
+ MapgenParams *params, EmergeManager *emerge);
+ static MapgenParams *createMapgenParams(MapgenType mgtype);
+ static void getMapgenNames(std::vector<const char *> *mgnames, bool include_hidden);
+
+private:
+ // isLiquidHorizontallyFlowable() is a helper function for updateLiquid()
+ // that checks whether there are floodable nodes without liquid beneath
+ // the node at index vi.
+ inline bool isLiquidHorizontallyFlowable(u32 vi, v3s16 em);
+};
+
+/*
+ MapgenBasic is a Mapgen implementation that handles basic functionality
+ the majority of conventional mapgens will probably want to use, but isn't
+ generic enough to be included as part of the base Mapgen class (such as
+ generating biome terrain over terrain node skeletons, generating caves,
+ dungeons, etc.)
+
+ Inherit MapgenBasic instead of Mapgen to add this basic functionality to
+ your mapgen without having to reimplement it. Feel free to override any of
+ these methods if you desire different or more advanced behavior.
+
+ Note that you must still create your own generateTerrain implementation when
+ inheriting MapgenBasic.
+*/
+class MapgenBasic : public Mapgen {
+public:
+ MapgenBasic(int mapgenid, MapgenParams *params, EmergeManager *emerge);
+ virtual ~MapgenBasic();
+
+ virtual void generateCaves(s16 max_stone_y, s16 large_cave_depth);
+ virtual bool generateCaverns(s16 max_stone_y);
+ virtual void generateDungeons(s16 max_stone_y,
+ MgStoneType stone_type, content_t biome_stone);
+ virtual void generateBiomes(MgStoneType *mgstone_type,
+ content_t *biome_stone);
+ virtual void dustTopNodes();
+
+protected:
+ EmergeManager *m_emerge;
+ BiomeManager *m_bmgr;
+
+ Noise *noise_filler_depth;
+
+ v3s16 node_min;
+ v3s16 node_max;
+ v3s16 full_node_min;
+ v3s16 full_node_max;
+
+ // Content required for generateBiomes
+ content_t c_stone;
+ content_t c_desert_stone;
+ content_t c_sandstone;
+ content_t c_water_source;
+ content_t c_river_water_source;
+ content_t c_lava_source;
+
+ // Content required for generateDungeons
+ content_t c_cobble;
+ content_t c_stair_cobble;
+ content_t c_mossycobble;
+ content_t c_stair_desert_stone;
+ content_t c_sandstonebrick;
+ content_t c_stair_sandstone_block;
+
+ int ystride;
+ int zstride;
+ int zstride_1d;
+ int zstride_1u1d;
+
+ u32 spflags;
+
+ NoiseParams np_cave1;
+ NoiseParams np_cave2;
+ NoiseParams np_cavern;
+ float cave_width;
+ float cavern_limit;
+ float cavern_taper;
+ float cavern_threshold;
+ int lava_depth;
+};
diff --git a/src/mapgen/mapgen_carpathian.cpp b/src/mapgen/mapgen_carpathian.cpp
new file mode 100644
index 000000000..9884ce40b
--- /dev/null
+++ b/src/mapgen/mapgen_carpathian.cpp
@@ -0,0 +1,454 @@
+/*
+Minetest
+Copyright (C) 2010-2016 paramat, Matt Gregory
+Copyright (C) 2010-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2017 vlapsley, Vaughan Lapsley <vlapsley@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+
+#include <cmath>
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "content_sao.h"
+#include "nodedef.h"
+#include "voxelalgorithms.h"
+//#include "profiler.h" // For TimeTaker
+#include "settings.h" // For g_settings
+#include "emerge.h"
+#include "dungeongen.h"
+#include "cavegen.h"
+#include "mg_biome.h"
+#include "mg_ore.h"
+#include "mg_decoration.h"
+#include "mapgen_carpathian.h"
+
+
+FlagDesc flagdesc_mapgen_carpathian[] = {
+ {"caverns", MGCARPATHIAN_CAVERNS},
+ {NULL, 0}
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+MapgenCarpathian::MapgenCarpathian(
+ int mapgenid, MapgenCarpathianParams *params, EmergeManager *emerge)
+ : MapgenBasic(mapgenid, params, emerge)
+{
+ spflags = params->spflags;
+ cave_width = params->cave_width;
+ large_cave_depth = params->large_cave_depth;
+ lava_depth = params->lava_depth;
+ cavern_limit = params->cavern_limit;
+ cavern_taper = params->cavern_taper;
+ cavern_threshold = params->cavern_threshold;
+ grad_wl = 1 - water_level;
+
+ //// 2D Terrain noise
+ noise_base = new Noise(&params->np_base, seed, csize.X, csize.Z);
+ noise_filler_depth = new Noise(&params->np_filler_depth, seed, csize.X, csize.Z);
+ noise_height1 = new Noise(&params->np_height1, seed, csize.X, csize.Z);
+ noise_height2 = new Noise(&params->np_height2, seed, csize.X, csize.Z);
+ noise_height3 = new Noise(&params->np_height3, seed, csize.X, csize.Z);
+ noise_height4 = new Noise(&params->np_height4, seed, csize.X, csize.Z);
+ noise_hills_terrain = new Noise(&params->np_hills_terrain, seed, csize.X, csize.Z);
+ noise_ridge_terrain = new Noise(&params->np_ridge_terrain, seed, csize.X, csize.Z);
+ noise_step_terrain = new Noise(&params->np_step_terrain, seed, csize.X, csize.Z);
+ noise_hills = new Noise(&params->np_hills, seed, csize.X, csize.Z);
+ noise_ridge_mnt = new Noise(&params->np_ridge_mnt, seed, csize.X, csize.Z);
+ noise_step_mnt = new Noise(&params->np_step_mnt, seed, csize.X, csize.Z);
+
+ //// 3D terrain noise
+ // 1 up 1 down overgeneration
+ noise_mnt_var = new Noise(&params->np_mnt_var, seed, csize.X, csize.Y + 2, csize.Z);
+
+ //// Cave noise
+ MapgenBasic::np_cave1 = params->np_cave1;
+ MapgenBasic::np_cave2 = params->np_cave2;
+ MapgenBasic::np_cavern = params->np_cavern;
+}
+
+
+MapgenCarpathian::~MapgenCarpathian()
+{
+ delete noise_base;
+ delete noise_filler_depth;
+ delete noise_height1;
+ delete noise_height2;
+ delete noise_height3;
+ delete noise_height4;
+ delete noise_hills_terrain;
+ delete noise_ridge_terrain;
+ delete noise_step_terrain;
+ delete noise_hills;
+ delete noise_ridge_mnt;
+ delete noise_step_mnt;
+ delete noise_mnt_var;
+}
+
+
+MapgenCarpathianParams::MapgenCarpathianParams():
+ np_base (12, 1, v3f(2557, 2557, 2557), 6538, 4, 0.8, 0.5),
+ np_filler_depth (0, 1, v3f(128, 128, 128), 261, 3, 0.7, 2.0),
+ np_height1 (0, 5, v3f(251, 251, 251), 9613, 5, 0.5, 2.0),
+ np_height2 (0, 5, v3f(383, 383, 383), 1949, 5, 0.5, 2.0),
+ np_height3 (0, 5, v3f(509, 509, 509), 3211, 5, 0.5, 2.0),
+ np_height4 (0, 5, v3f(631, 631, 631), 1583, 5, 0.5, 2.0),
+ np_hills_terrain (1, 1, v3f(1301, 1301, 1301), 1692, 5, 0.5, 2.0),
+ np_ridge_terrain (1, 1, v3f(1889, 1889, 1889), 3568, 5, 0.5, 2.0),
+ np_step_terrain (1, 1, v3f(1889, 1889, 1889), 4157, 5, 0.5, 2.0),
+ np_hills (0, 3, v3f(257, 257, 257), 6604, 6, 0.5, 2.0),
+ np_ridge_mnt (0, 12, v3f(743, 743, 743), 5520, 6, 0.7, 2.0),
+ np_step_mnt (0, 8, v3f(509, 509, 509), 2590, 6, 0.6, 2.0),
+ np_mnt_var (0, 1, v3f(499, 499, 499), 2490, 5, 0.55, 2.0),
+ np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
+ np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0),
+ np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0)
+{
+}
+
+
+void MapgenCarpathianParams::readParams(const Settings *settings)
+{
+ settings->getFlagStrNoEx("mgcarpathian_spflags", spflags, flagdesc_mapgen_carpathian);
+ settings->getFloatNoEx("mgcarpathian_cave_width", cave_width);
+ settings->getS16NoEx("mgcarpathian_large_cave_depth", large_cave_depth);
+ settings->getS16NoEx("mgcarpathian_lava_depth", lava_depth);
+ settings->getS16NoEx("mgcarpathian_cavern_limit", cavern_limit);
+ settings->getS16NoEx("mgcarpathian_cavern_taper", cavern_taper);
+ settings->getFloatNoEx("mgcarpathian_cavern_threshold", cavern_threshold);
+
+ settings->getNoiseParams("mgcarpathian_np_base", np_base);
+ settings->getNoiseParams("mgcarpathian_np_filler_depth", np_filler_depth);
+ settings->getNoiseParams("mgcarpathian_np_height1", np_height1);
+ settings->getNoiseParams("mgcarpathian_np_height2", np_height2);
+ settings->getNoiseParams("mgcarpathian_np_height3", np_height3);
+ settings->getNoiseParams("mgcarpathian_np_height4", np_height4);
+ settings->getNoiseParams("mgcarpathian_np_hills_terrain", np_hills_terrain);
+ settings->getNoiseParams("mgcarpathian_np_ridge_terrain", np_ridge_terrain);
+ settings->getNoiseParams("mgcarpathian_np_step_terrain", np_step_terrain);
+ settings->getNoiseParams("mgcarpathian_np_hills", np_hills);
+ settings->getNoiseParams("mgcarpathian_np_ridge_mnt", np_ridge_mnt);
+ settings->getNoiseParams("mgcarpathian_np_step_mnt", np_step_mnt);
+ settings->getNoiseParams("mgcarpathian_np_mnt_var", np_mnt_var);
+ settings->getNoiseParams("mgcarpathian_np_cave1", np_cave1);
+ settings->getNoiseParams("mgcarpathian_np_cave2", np_cave2);
+ settings->getNoiseParams("mgcarpathian_np_cavern", np_cavern);
+}
+
+
+void MapgenCarpathianParams::writeParams(Settings *settings) const
+{
+ settings->setFlagStr("mgcarpathian_spflags", spflags, flagdesc_mapgen_carpathian, U32_MAX);
+ settings->setFloat("mgcarpathian_cave_width", cave_width);
+ settings->setS16("mgcarpathian_large_cave_depth", large_cave_depth);
+ settings->setS16("mgcarpathian_lava_depth", lava_depth);
+ settings->setS16("mgcarpathian_cavern_limit", cavern_limit);
+ settings->setS16("mgcarpathian_cavern_taper", cavern_taper);
+ settings->setFloat("mgcarpathian_cavern_threshold", cavern_threshold);
+
+ settings->setNoiseParams("mgcarpathian_np_base", np_base);
+ settings->setNoiseParams("mgcarpathian_np_filler_depth", np_filler_depth);
+ settings->setNoiseParams("mgcarpathian_np_height1", np_height1);
+ settings->setNoiseParams("mgcarpathian_np_height2", np_height2);
+ settings->setNoiseParams("mgcarpathian_np_height3", np_height3);
+ settings->setNoiseParams("mgcarpathian_np_height4", np_height4);
+ settings->setNoiseParams("mgcarpathian_np_hills_terrain", np_hills_terrain);
+ settings->setNoiseParams("mgcarpathian_np_ridge_terrain", np_ridge_terrain);
+ settings->setNoiseParams("mgcarpathian_np_step_terrain", np_step_terrain);
+ settings->setNoiseParams("mgcarpathian_np_hills", np_hills);
+ settings->setNoiseParams("mgcarpathian_np_ridge_mnt", np_ridge_mnt);
+ settings->setNoiseParams("mgcarpathian_np_step_mnt", np_step_mnt);
+ settings->setNoiseParams("mgcarpathian_np_mnt_var", np_mnt_var);
+ settings->setNoiseParams("mgcarpathian_np_cave1", np_cave1);
+ settings->setNoiseParams("mgcarpathian_np_cave2", np_cave2);
+ settings->setNoiseParams("mgcarpathian_np_cavern", np_cavern);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+// Lerp function
+inline float MapgenCarpathian::getLerp(float noise1, float noise2, float mod)
+{
+ return noise1 + mod * (noise2 - noise1);
+}
+
+// Steps function
+float MapgenCarpathian::getSteps(float noise)
+{
+ float w = 0.5f;
+ float k = floor(noise / w);
+ float f = (noise - k * w) / w;
+ float s = std::fmin(2.f * f, 1.f);
+ return (k + s) * w;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+void MapgenCarpathian::makeChunk(BlockMakeData *data)
+{
+ // Pre-conditions
+ assert(data->vmanip);
+ assert(data->nodedef);
+ assert(data->blockpos_requested.X >= data->blockpos_min.X &&
+ data->blockpos_requested.Y >= data->blockpos_min.Y &&
+ data->blockpos_requested.Z >= data->blockpos_min.Z);
+ assert(data->blockpos_requested.X <= data->blockpos_max.X &&
+ data->blockpos_requested.Y <= data->blockpos_max.Y &&
+ data->blockpos_requested.Z <= data->blockpos_max.Z);
+
+ this->generating = true;
+ this->vm = data->vmanip;
+ this->ndef = data->nodedef;
+
+ v3s16 blockpos_min = data->blockpos_min;
+ v3s16 blockpos_max = data->blockpos_max;
+ node_min = blockpos_min * MAP_BLOCKSIZE;
+ node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+ full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
+ full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ // Create a block-specific seed
+ blockseed = getBlockSeed2(full_node_min, seed);
+
+ // Generate terrain
+ s16 stone_surface_max_y = generateTerrain();
+
+ // Create heightmap
+ updateHeightmap(node_min, node_max);
+
+ // Init biome generator, place biome-specific nodes, and build biomemap
+ biomegen->calcBiomeNoise(node_min);
+
+ MgStoneType mgstone_type;
+ content_t biome_stone;
+ generateBiomes(&mgstone_type, &biome_stone);
+
+ // Generate caverns, tunnels and classic caves
+ if (flags & MG_CAVES) {
+ bool has_cavern = false;
+ // Generate caverns
+ if (spflags & MGCARPATHIAN_CAVERNS)
+ has_cavern = generateCaverns(stone_surface_max_y);
+ // Generate tunnels and classic caves
+ if (has_cavern)
+ // Disable classic caves in this mapchunk by setting
+ // 'large cave depth' to world base. Avoids excessive liquid in
+ // large caverns and floating blobs of overgenerated liquid.
+ generateCaves(stone_surface_max_y, -MAX_MAP_GENERATION_LIMIT);
+ else
+ generateCaves(stone_surface_max_y, large_cave_depth);
+ }
+
+ // Generate dungeons
+ if (flags & MG_DUNGEONS)
+ generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
+
+ // Generate the registered decorations
+ if (flags & MG_DECORATIONS)
+ m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
+
+ // Generate the registered ores
+ m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
+
+ // Sprinkle some dust on top after everything else was generated
+ dustTopNodes();
+
+ // Update liquids
+ updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
+
+ // Calculate lighting
+ if (flags & MG_LIGHT) {
+ calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
+ full_node_min, full_node_max);
+ }
+
+ this->generating = false;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+int MapgenCarpathian::getSpawnLevelAtPoint(v2s16 p)
+{
+ s16 level_at_point = terrainLevelAtPoint(p.X, p.Y);
+ if (level_at_point <= water_level || level_at_point > water_level + 32)
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+
+ return level_at_point;
+}
+
+
+float MapgenCarpathian::terrainLevelAtPoint(s16 x, s16 z)
+{
+ float ground = NoisePerlin2D(&noise_base->np, x, z, seed);
+ float height1 = NoisePerlin2D(&noise_height1->np, x, z, seed);
+ float height2 = NoisePerlin2D(&noise_height2->np, x, z, seed);
+ float height3 = NoisePerlin2D(&noise_height3->np, x, z, seed);
+ float height4 = NoisePerlin2D(&noise_height4->np, x, z, seed);
+ float hter = NoisePerlin2D(&noise_hills_terrain->np, x, z, seed);
+ float rter = NoisePerlin2D(&noise_ridge_terrain->np, x, z, seed);
+ float ster = NoisePerlin2D(&noise_step_terrain->np, x, z, seed);
+ float n_hills = NoisePerlin2D(&noise_hills->np, x, z, seed);
+ float n_ridge_mnt = NoisePerlin2D(&noise_ridge_mnt->np, x, z, seed);
+ float n_step_mnt = NoisePerlin2D(&noise_step_mnt->np, x, z, seed);
+
+ int height = -MAX_MAP_GENERATION_LIMIT;
+
+ for (s16 y = 1; y <= 30; y++) {
+ float mnt_var = NoisePerlin3D(&noise_mnt_var->np, x, y, z, seed);
+
+ // Gradient & shallow seabed
+ s32 grad = (y < water_level) ? grad_wl + (water_level - y) * 3 : 1 - y;
+
+ // Hill/Mountain height (hilliness)
+ float hill1 = getLerp(height1, height2, mnt_var);
+ float hill2 = getLerp(height3, height4, mnt_var);
+ float hill3 = getLerp(height3, height2, mnt_var);
+ float hill4 = getLerp(height1, height4, mnt_var);
+ float hilliness = std::fmax(std::fmin(hill1, hill2), std::fmin(hill3, hill4));
+
+ // Rolling hills
+ float hill_mnt = hilliness * pow(n_hills, 2.f);
+ float hills = pow(hter, 3.f) * hill_mnt;
+
+ // Ridged mountains
+ float ridge_mnt = hilliness * (1.f - fabs(n_ridge_mnt));
+ float ridged_mountains = pow(rter, 3.f) * ridge_mnt;
+
+ // Step (terraced) mountains
+ float step_mnt = hilliness * getSteps(n_step_mnt);
+ float step_mountains = pow(ster, 3.f) * step_mnt;
+
+ // Final terrain level
+ float mountains = hills + ridged_mountains + step_mountains;
+ float surface_level = ground + mountains + grad;
+
+ if (y > surface_level && height < 0)
+ height = y;
+ }
+
+ return height;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+int MapgenCarpathian::generateTerrain()
+{
+ MapNode mn_air(CONTENT_AIR);
+ MapNode mn_stone(c_stone);
+ MapNode mn_water(c_water_source);
+
+ s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
+ u32 index2d = 0;
+ u32 index3d = 0;
+
+ // Calculate noise for terrain generation
+ noise_base->perlinMap2D(node_min.X, node_min.Z);
+ noise_height1->perlinMap2D(node_min.X, node_min.Z);
+ noise_height2->perlinMap2D(node_min.X, node_min.Z);
+ noise_height3->perlinMap2D(node_min.X, node_min.Z);
+ noise_height4->perlinMap2D(node_min.X, node_min.Z);
+ noise_hills_terrain->perlinMap2D(node_min.X, node_min.Z);
+ noise_ridge_terrain->perlinMap2D(node_min.X, node_min.Z);
+ noise_step_terrain->perlinMap2D(node_min.X, node_min.Z);
+ noise_hills->perlinMap2D(node_min.X, node_min.Z);
+ noise_ridge_mnt->perlinMap2D(node_min.X, node_min.Z);
+ noise_step_mnt->perlinMap2D(node_min.X, node_min.Z);
+ noise_mnt_var->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
+
+ //// Place nodes
+ for (s16 z = node_min.Z; z <= node_max.Z; z++) {
+ for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
+ u32 vi = vm->m_area.index(node_min.X, y, z);
+ for (s16 x = node_min.X; x <= node_max.X;
+ x++, vi++, index2d++, index3d++) {
+ if (vm->m_data[vi].getContent() != CONTENT_IGNORE)
+ continue;
+
+ // Base terrain
+ float ground = noise_base->result[index2d];
+
+ // Gradient & shallow seabed
+ s32 grad = (y < water_level) ? grad_wl + (water_level - y) * 3 : 1 - y;
+
+ // Hill/Mountain height (hilliness)
+ float height1 = noise_height1->result[index2d];
+ float height2 = noise_height2->result[index2d];
+ float height3 = noise_height3->result[index2d];
+ float height4 = noise_height4->result[index2d];
+ float mnt_var = noise_mnt_var->result[index3d];
+ // Combine height noises and apply 3D variation
+ float hill1 = getLerp(height1, height2, mnt_var);
+ float hill2 = getLerp(height3, height4, mnt_var);
+ float hill3 = getLerp(height3, height2, mnt_var);
+ float hill4 = getLerp(height1, height4, mnt_var);
+ // 'hilliness' determines whether hills/mountains are
+ // small or large
+ float hilliness = std::fmax(std::fmin(hill1, hill2), std::fmin(hill3, hill4));
+
+ // Rolling hills
+ float hter = noise_hills_terrain->result[index2d];
+ float n_hills = noise_hills->result[index2d];
+ float hill_mnt = hilliness * pow(n_hills, 2.f);
+ float hills = pow(fabs(hter), 3.f) * hill_mnt;
+
+ // Ridged mountains
+ float rter = noise_ridge_terrain->result[index2d];
+ float n_ridge_mnt = noise_ridge_mnt->result[index2d];
+ float ridge_mnt = hilliness * (1.f - fabs(n_ridge_mnt));
+ float ridged_mountains = pow(fabs(rter), 3.f) * ridge_mnt;
+
+ // Step (terraced) mountains
+ float ster = noise_step_terrain->result[index2d];
+ float n_step_mnt = noise_step_mnt->result[index2d];
+ float step_mnt = hilliness * getSteps(n_step_mnt);
+ float step_mountains = pow(fabs(ster), 3.f) * step_mnt;
+
+ // Final terrain level
+ float mountains = hills + ridged_mountains + step_mountains;
+ float surface_level = ground + mountains + grad;
+
+ if (y < surface_level) {
+ vm->m_data[vi] = mn_stone; // Stone
+ if (y > stone_surface_max_y)
+ stone_surface_max_y = y;
+ } else if (y <= water_level) {
+ vm->m_data[vi] = mn_water; // Sea water
+ } else {
+ vm->m_data[vi] = mn_air; // Air
+ }
+ }
+ index2d -= ystride;
+ }
+ index2d += ystride;
+ }
+
+ return stone_surface_max_y;
+}
diff --git a/src/mapgen/mapgen_carpathian.h b/src/mapgen/mapgen_carpathian.h
new file mode 100644
index 000000000..7dbccde2a
--- /dev/null
+++ b/src/mapgen/mapgen_carpathian.h
@@ -0,0 +1,102 @@
+/*
+Minetest
+Copyright (C) 2010-2016 paramat, Matt Gregory
+Copyright (C) 2010-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2017 vlapsley, Vaughan Lapsley <vlapsley@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "mapgen.h"
+
+///////// Mapgen Carpathian flags
+#define MGCARPATHIAN_CAVERNS 0x01
+
+class BiomeManager;
+
+extern FlagDesc flagdesc_mapgen_carpathian[];
+
+
+struct MapgenCarpathianParams : public MapgenParams
+{
+ u32 spflags = MGCARPATHIAN_CAVERNS;
+ float cave_width = 0.09f;
+ s16 large_cave_depth = -33;
+ s16 lava_depth = -256;
+ s16 cavern_limit = -256;
+ s16 cavern_taper = 256;
+ float cavern_threshold = 0.7f;
+
+ NoiseParams np_base;
+ NoiseParams np_filler_depth;
+ NoiseParams np_height1;
+ NoiseParams np_height2;
+ NoiseParams np_height3;
+ NoiseParams np_height4;
+ NoiseParams np_hills_terrain;
+ NoiseParams np_ridge_terrain;
+ NoiseParams np_step_terrain;
+ NoiseParams np_hills;
+ NoiseParams np_ridge_mnt;
+ NoiseParams np_step_mnt;
+ NoiseParams np_mnt_var;
+ NoiseParams np_cave1;
+ NoiseParams np_cave2;
+ NoiseParams np_cavern;
+
+ MapgenCarpathianParams();
+ ~MapgenCarpathianParams() = default;
+
+ void readParams(const Settings *settings);
+ void writeParams(Settings *settings) const;
+};
+
+class MapgenCarpathian : public MapgenBasic
+{
+public:
+ MapgenCarpathian(int mapgenid, MapgenCarpathianParams *params,
+ EmergeManager *emerge);
+ ~MapgenCarpathian();
+
+ virtual MapgenType getType() const { return MAPGEN_CARPATHIAN; }
+
+ float getSteps(float noise);
+ inline float getLerp(float noise1, float noise2, float mod);
+
+ virtual void makeChunk(BlockMakeData *data);
+ int getSpawnLevelAtPoint(v2s16 p);
+
+private:
+ s16 large_cave_depth;
+ s32 grad_wl;
+
+ Noise *noise_base;
+ Noise *noise_height1;
+ Noise *noise_height2;
+ Noise *noise_height3;
+ Noise *noise_height4;
+ Noise *noise_hills_terrain;
+ Noise *noise_ridge_terrain;
+ Noise *noise_step_terrain;
+ Noise *noise_hills;
+ Noise *noise_ridge_mnt;
+ Noise *noise_step_mnt;
+ Noise *noise_mnt_var;
+
+ float terrainLevelAtPoint(s16 x, s16 z);
+ int generateTerrain();
+};
diff --git a/src/mapgen/mapgen_flat.cpp b/src/mapgen/mapgen_flat.cpp
new file mode 100644
index 000000000..57c20d470
--- /dev/null
+++ b/src/mapgen/mapgen_flat.cpp
@@ -0,0 +1,274 @@
+/*
+Minetest
+Copyright (C) 2015-2017 paramat
+Copyright (C) 2015-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "content_sao.h"
+#include "nodedef.h"
+#include "voxelalgorithms.h"
+//#include "profiler.h" // For TimeTaker
+#include "settings.h" // For g_settings
+#include "emerge.h"
+#include "dungeongen.h"
+#include "cavegen.h"
+#include "mg_biome.h"
+#include "mg_ore.h"
+#include "mg_decoration.h"
+#include "mapgen_flat.h"
+
+
+FlagDesc flagdesc_mapgen_flat[] = {
+ {"lakes", MGFLAT_LAKES},
+ {"hills", MGFLAT_HILLS},
+ {NULL, 0}
+};
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+
+MapgenFlat::MapgenFlat(int mapgenid, MapgenFlatParams *params, EmergeManager *emerge)
+ : MapgenBasic(mapgenid, params, emerge)
+{
+ spflags = params->spflags;
+ ground_level = params->ground_level;
+ large_cave_depth = params->large_cave_depth;
+ lava_depth = params->lava_depth;
+ cave_width = params->cave_width;
+ lake_threshold = params->lake_threshold;
+ lake_steepness = params->lake_steepness;
+ hill_threshold = params->hill_threshold;
+ hill_steepness = params->hill_steepness;
+
+ // 2D noise
+ noise_filler_depth = new Noise(&params->np_filler_depth, seed, csize.X, csize.Z);
+
+ if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS))
+ noise_terrain = new Noise(&params->np_terrain, seed, csize.X, csize.Z);
+ // 3D noise
+ MapgenBasic::np_cave1 = params->np_cave1;
+ MapgenBasic::np_cave2 = params->np_cave2;
+}
+
+
+MapgenFlat::~MapgenFlat()
+{
+ delete noise_filler_depth;
+
+ if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS))
+ delete noise_terrain;
+}
+
+
+MapgenFlatParams::MapgenFlatParams():
+ np_terrain (0, 1, v3f(600, 600, 600), 7244, 5, 0.6, 2.0),
+ np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0),
+ np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
+ np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0)
+{
+}
+
+
+void MapgenFlatParams::readParams(const Settings *settings)
+{
+ settings->getFlagStrNoEx("mgflat_spflags", spflags, flagdesc_mapgen_flat);
+ settings->getS16NoEx("mgflat_ground_level", ground_level);
+ settings->getS16NoEx("mgflat_large_cave_depth", large_cave_depth);
+ settings->getS16NoEx("mgflat_lava_depth", lava_depth);
+ settings->getFloatNoEx("mgflat_cave_width", cave_width);
+ settings->getFloatNoEx("mgflat_lake_threshold", lake_threshold);
+ settings->getFloatNoEx("mgflat_lake_steepness", lake_steepness);
+ settings->getFloatNoEx("mgflat_hill_threshold", hill_threshold);
+ settings->getFloatNoEx("mgflat_hill_steepness", hill_steepness);
+
+ settings->getNoiseParams("mgflat_np_terrain", np_terrain);
+ settings->getNoiseParams("mgflat_np_filler_depth", np_filler_depth);
+ settings->getNoiseParams("mgflat_np_cave1", np_cave1);
+ settings->getNoiseParams("mgflat_np_cave2", np_cave2);
+}
+
+
+void MapgenFlatParams::writeParams(Settings *settings) const
+{
+ settings->setFlagStr("mgflat_spflags", spflags, flagdesc_mapgen_flat, U32_MAX);
+ settings->setS16("mgflat_ground_level", ground_level);
+ settings->setS16("mgflat_large_cave_depth", large_cave_depth);
+ settings->setS16("mgflat_lava_depth", lava_depth);
+ settings->setFloat("mgflat_cave_width", cave_width);
+ settings->setFloat("mgflat_lake_threshold", lake_threshold);
+ settings->setFloat("mgflat_lake_steepness", lake_steepness);
+ settings->setFloat("mgflat_hill_threshold", hill_threshold);
+ settings->setFloat("mgflat_hill_steepness", hill_steepness);
+
+ settings->setNoiseParams("mgflat_np_terrain", np_terrain);
+ settings->setNoiseParams("mgflat_np_filler_depth", np_filler_depth);
+ settings->setNoiseParams("mgflat_np_cave1", np_cave1);
+ settings->setNoiseParams("mgflat_np_cave2", np_cave2);
+}
+
+
+/////////////////////////////////////////////////////////////////
+
+
+int MapgenFlat::getSpawnLevelAtPoint(v2s16 p)
+{
+ s16 level_at_point = ground_level;
+ float n_terrain = 0.0f;
+ if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS))
+ n_terrain = NoisePerlin2D(&noise_terrain->np, p.X, p.Y, seed);
+
+ if ((spflags & MGFLAT_LAKES) && n_terrain < lake_threshold) {
+ level_at_point = ground_level -
+ (lake_threshold - n_terrain) * lake_steepness;
+ } else if ((spflags & MGFLAT_HILLS) && n_terrain > hill_threshold) {
+ level_at_point = ground_level +
+ (n_terrain - hill_threshold) * hill_steepness;
+ }
+
+ if (ground_level < water_level) // Ocean world, allow spawn in water
+ return MYMAX(level_at_point, water_level);
+
+ if (level_at_point > water_level)
+ return level_at_point; // Spawn on land
+
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+}
+
+
+void MapgenFlat::makeChunk(BlockMakeData *data)
+{
+ // Pre-conditions
+ assert(data->vmanip);
+ assert(data->nodedef);
+ assert(data->blockpos_requested.X >= data->blockpos_min.X &&
+ data->blockpos_requested.Y >= data->blockpos_min.Y &&
+ data->blockpos_requested.Z >= data->blockpos_min.Z);
+ assert(data->blockpos_requested.X <= data->blockpos_max.X &&
+ data->blockpos_requested.Y <= data->blockpos_max.Y &&
+ data->blockpos_requested.Z <= data->blockpos_max.Z);
+
+ this->generating = true;
+ this->vm = data->vmanip;
+ this->ndef = data->nodedef;
+ //TimeTaker t("makeChunk");
+
+ v3s16 blockpos_min = data->blockpos_min;
+ v3s16 blockpos_max = data->blockpos_max;
+ node_min = blockpos_min * MAP_BLOCKSIZE;
+ node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+ full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
+ full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ blockseed = getBlockSeed2(full_node_min, seed);
+
+ // Generate base terrain, mountains, and ridges with initial heightmaps
+ s16 stone_surface_max_y = generateTerrain();
+
+ // Create heightmap
+ updateHeightmap(node_min, node_max);
+
+ // Init biome generator, place biome-specific nodes, and build biomemap
+ biomegen->calcBiomeNoise(node_min);
+
+ MgStoneType mgstone_type;
+ content_t biome_stone;
+ generateBiomes(&mgstone_type, &biome_stone);
+
+ if (flags & MG_CAVES)
+ generateCaves(stone_surface_max_y, large_cave_depth);
+
+ if (flags & MG_DUNGEONS)
+ generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
+
+ // Generate the registered decorations
+ if (flags & MG_DECORATIONS)
+ m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
+
+ // Generate the registered ores
+ m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
+
+ // Sprinkle some dust on top after everything else was generated
+ dustTopNodes();
+
+ //printf("makeChunk: %dms\n", t.stop());
+
+ updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
+
+ if (flags & MG_LIGHT)
+ calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
+ full_node_min, full_node_max);
+
+ //setLighting(node_min - v3s16(1, 0, 1) * MAP_BLOCKSIZE,
+ // node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, 0xFF);
+
+ this->generating = false;
+}
+
+
+s16 MapgenFlat::generateTerrain()
+{
+ MapNode n_air(CONTENT_AIR);
+ MapNode n_stone(c_stone);
+ MapNode n_water(c_water_source);
+
+ const v3s16 &em = vm->m_area.getExtent();
+ s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
+ u32 ni2d = 0;
+
+ bool use_noise = (spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS);
+ if (use_noise)
+ noise_terrain->perlinMap2D(node_min.X, node_min.Z);
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, ni2d++) {
+ s16 stone_level = ground_level;
+ float n_terrain = use_noise ? noise_terrain->result[ni2d] : 0.0f;
+
+ if ((spflags & MGFLAT_LAKES) && n_terrain < lake_threshold) {
+ s16 depress = (lake_threshold - n_terrain) * lake_steepness;
+ stone_level = ground_level - depress;
+ } else if ((spflags & MGFLAT_HILLS) && n_terrain > hill_threshold) {
+ s16 rise = (n_terrain - hill_threshold) * hill_steepness;
+ stone_level = ground_level + rise;
+ }
+
+ u32 vi = vm->m_area.index(x, node_min.Y - 1, z);
+ for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
+ if (vm->m_data[vi].getContent() == CONTENT_IGNORE) {
+ if (y <= stone_level) {
+ vm->m_data[vi] = n_stone;
+ if (y > stone_surface_max_y)
+ stone_surface_max_y = y;
+ } else if (y <= water_level) {
+ vm->m_data[vi] = n_water;
+ } else {
+ vm->m_data[vi] = n_air;
+ }
+ }
+ vm->m_area.add_y(em, vi, 1);
+ }
+ }
+
+ return stone_surface_max_y;
+}
diff --git a/src/mapgen/mapgen_flat.h b/src/mapgen/mapgen_flat.h
new file mode 100644
index 000000000..635d40625
--- /dev/null
+++ b/src/mapgen/mapgen_flat.h
@@ -0,0 +1,76 @@
+/*
+Minetest
+Copyright (C) 2015-2017 paramat
+Copyright (C) 2015-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "mapgen.h"
+
+/////// Mapgen Flat flags
+#define MGFLAT_LAKES 0x01
+#define MGFLAT_HILLS 0x02
+
+class BiomeManager;
+
+extern FlagDesc flagdesc_mapgen_flat[];
+
+struct MapgenFlatParams : public MapgenParams
+{
+ u32 spflags = 0;
+ s16 ground_level = 8;
+ s16 large_cave_depth = -33;
+ s16 lava_depth = -256;
+ float cave_width = 0.09f;
+ float lake_threshold = -0.45f;
+ float lake_steepness = 48.0f;
+ float hill_threshold = 0.45f;
+ float hill_steepness = 64.0f;
+ NoiseParams np_terrain;
+ NoiseParams np_filler_depth;
+ NoiseParams np_cave1;
+ NoiseParams np_cave2;
+
+ MapgenFlatParams();
+ ~MapgenFlatParams() = default;
+
+ void readParams(const Settings *settings);
+ void writeParams(Settings *settings) const;
+};
+
+class MapgenFlat : public MapgenBasic
+{
+public:
+ MapgenFlat(int mapgenid, MapgenFlatParams *params, EmergeManager *emerge);
+ ~MapgenFlat();
+
+ virtual MapgenType getType() const { return MAPGEN_FLAT; }
+
+ virtual void makeChunk(BlockMakeData *data);
+ int getSpawnLevelAtPoint(v2s16 p);
+ s16 generateTerrain();
+
+private:
+ s16 ground_level;
+ s16 large_cave_depth;
+ float lake_threshold;
+ float lake_steepness;
+ float hill_threshold;
+ float hill_steepness;
+ Noise *noise_terrain;
+};
diff --git a/src/mapgen/mapgen_fractal.cpp b/src/mapgen/mapgen_fractal.cpp
new file mode 100644
index 000000000..b06885826
--- /dev/null
+++ b/src/mapgen/mapgen_fractal.cpp
@@ -0,0 +1,404 @@
+/*
+Minetest
+Copyright (C) 2015-2017 paramat
+Copyright (C) 2015-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "content_sao.h"
+#include "nodedef.h"
+#include "voxelalgorithms.h"
+//#include "profiler.h" // For TimeTaker
+#include "settings.h" // For g_settings
+#include "emerge.h"
+#include "dungeongen.h"
+#include "cavegen.h"
+#include "mg_biome.h"
+#include "mg_ore.h"
+#include "mg_decoration.h"
+#include "mapgen_fractal.h"
+
+
+FlagDesc flagdesc_mapgen_fractal[] = {
+ {NULL, 0}
+};
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+
+MapgenFractal::MapgenFractal(int mapgenid, MapgenFractalParams *params, EmergeManager *emerge)
+ : MapgenBasic(mapgenid, params, emerge)
+{
+ spflags = params->spflags;
+ cave_width = params->cave_width;
+ large_cave_depth = params->large_cave_depth;
+ lava_depth = params->lava_depth;
+ fractal = params->fractal;
+ iterations = params->iterations;
+ scale = params->scale;
+ offset = params->offset;
+ slice_w = params->slice_w;
+ julia_x = params->julia_x;
+ julia_y = params->julia_y;
+ julia_z = params->julia_z;
+ julia_w = params->julia_w;
+
+ //// 2D terrain noise
+ noise_seabed = new Noise(&params->np_seabed, seed, csize.X, csize.Z);
+ noise_filler_depth = new Noise(&params->np_filler_depth, seed, csize.X, csize.Z);
+
+ MapgenBasic::np_cave1 = params->np_cave1;
+ MapgenBasic::np_cave2 = params->np_cave2;
+
+ formula = fractal / 2 + fractal % 2;
+ julia = fractal % 2 == 0;
+}
+
+
+MapgenFractal::~MapgenFractal()
+{
+ delete noise_seabed;
+ delete noise_filler_depth;
+}
+
+
+MapgenFractalParams::MapgenFractalParams():
+ np_seabed (-14, 9, v3f(600, 600, 600), 41900, 5, 0.6, 2.0),
+ np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0),
+ np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
+ np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0)
+{
+}
+
+
+void MapgenFractalParams::readParams(const Settings *settings)
+{
+ settings->getFlagStrNoEx("mgfractal_spflags", spflags, flagdesc_mapgen_fractal);
+ settings->getFloatNoEx("mgfractal_cave_width", cave_width);
+ settings->getS16NoEx("mgfractal_large_cave_depth", large_cave_depth);
+ settings->getS16NoEx("mgfractal_lava_depth", lava_depth);
+ settings->getU16NoEx("mgfractal_fractal", fractal);
+ settings->getU16NoEx("mgfractal_iterations", iterations);
+ settings->getV3FNoEx("mgfractal_scale", scale);
+ settings->getV3FNoEx("mgfractal_offset", offset);
+ settings->getFloatNoEx("mgfractal_slice_w", slice_w);
+ settings->getFloatNoEx("mgfractal_julia_x", julia_x);
+ settings->getFloatNoEx("mgfractal_julia_y", julia_y);
+ settings->getFloatNoEx("mgfractal_julia_z", julia_z);
+ settings->getFloatNoEx("mgfractal_julia_w", julia_w);
+
+ settings->getNoiseParams("mgfractal_np_seabed", np_seabed);
+ settings->getNoiseParams("mgfractal_np_filler_depth", np_filler_depth);
+ settings->getNoiseParams("mgfractal_np_cave1", np_cave1);
+ settings->getNoiseParams("mgfractal_np_cave2", np_cave2);
+}
+
+
+void MapgenFractalParams::writeParams(Settings *settings) const
+{
+ settings->setFlagStr("mgfractal_spflags", spflags, flagdesc_mapgen_fractal, U32_MAX);
+ settings->setFloat("mgfractal_cave_width", cave_width);
+ settings->setS16("mgfractal_large_cave_depth", large_cave_depth);
+ settings->setS16("mgfractal_lava_depth", lava_depth);
+ settings->setU16("mgfractal_fractal", fractal);
+ settings->setU16("mgfractal_iterations", iterations);
+ settings->setV3F("mgfractal_scale", scale);
+ settings->setV3F("mgfractal_offset", offset);
+ settings->setFloat("mgfractal_slice_w", slice_w);
+ settings->setFloat("mgfractal_julia_x", julia_x);
+ settings->setFloat("mgfractal_julia_y", julia_y);
+ settings->setFloat("mgfractal_julia_z", julia_z);
+ settings->setFloat("mgfractal_julia_w", julia_w);
+
+ settings->setNoiseParams("mgfractal_np_seabed", np_seabed);
+ settings->setNoiseParams("mgfractal_np_filler_depth", np_filler_depth);
+ settings->setNoiseParams("mgfractal_np_cave1", np_cave1);
+ settings->setNoiseParams("mgfractal_np_cave2", np_cave2);
+}
+
+
+/////////////////////////////////////////////////////////////////
+
+
+int MapgenFractal::getSpawnLevelAtPoint(v2s16 p)
+{
+ bool solid_below = false; // Dry solid node is present below to spawn on
+ u8 air_count = 0; // Consecutive air nodes above the dry solid node
+ s16 seabed_level = NoisePerlin2D(&noise_seabed->np, p.X, p.Y, seed);
+ // Seabed can rise above water_level or might be raised to create dry land
+ s16 search_start = MYMAX(seabed_level, water_level + 1);
+ if (seabed_level > water_level)
+ solid_below = true;
+
+ for (s16 y = search_start; y <= search_start + 128; y++) {
+ if (getFractalAtPoint(p.X, y, p.Y)) { // Fractal node
+ solid_below = true;
+ air_count = 0;
+ } else if (solid_below) { // Air above solid node
+ air_count++;
+ // 3 to account for snowblock dust
+ if (air_count == 3)
+ return y - 2;
+ }
+ }
+
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+}
+
+
+void MapgenFractal::makeChunk(BlockMakeData *data)
+{
+ // Pre-conditions
+ assert(data->vmanip);
+ assert(data->nodedef);
+ assert(data->blockpos_requested.X >= data->blockpos_min.X &&
+ data->blockpos_requested.Y >= data->blockpos_min.Y &&
+ data->blockpos_requested.Z >= data->blockpos_min.Z);
+ assert(data->blockpos_requested.X <= data->blockpos_max.X &&
+ data->blockpos_requested.Y <= data->blockpos_max.Y &&
+ data->blockpos_requested.Z <= data->blockpos_max.Z);
+
+ this->generating = true;
+ this->vm = data->vmanip;
+ this->ndef = data->nodedef;
+ //TimeTaker t("makeChunk");
+
+ v3s16 blockpos_min = data->blockpos_min;
+ v3s16 blockpos_max = data->blockpos_max;
+ node_min = blockpos_min * MAP_BLOCKSIZE;
+ node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+ full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
+ full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ blockseed = getBlockSeed2(full_node_min, seed);
+
+ // Generate base terrain, mountains, and ridges with initial heightmaps
+ s16 stone_surface_max_y = generateTerrain();
+
+ // Create heightmap
+ updateHeightmap(node_min, node_max);
+
+ // Init biome generator, place biome-specific nodes, and build biomemap
+ biomegen->calcBiomeNoise(node_min);
+
+ MgStoneType mgstone_type;
+ content_t biome_stone;
+ generateBiomes(&mgstone_type, &biome_stone);
+
+ if (flags & MG_CAVES)
+ generateCaves(stone_surface_max_y, large_cave_depth);
+
+ if (flags & MG_DUNGEONS)
+ generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
+
+ // Generate the registered decorations
+ if (flags & MG_DECORATIONS)
+ m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
+
+ // Generate the registered ores
+ m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
+
+ // Sprinkle some dust on top after everything else was generated
+ dustTopNodes();
+
+ //printf("makeChunk: %dms\n", t.stop());
+
+ updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
+
+ if (flags & MG_LIGHT)
+ calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
+ full_node_min, full_node_max);
+
+ //setLighting(node_min - v3s16(1, 0, 1) * MAP_BLOCKSIZE,
+ // node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, 0xFF);
+
+ this->generating = false;
+}
+
+
+bool MapgenFractal::getFractalAtPoint(s16 x, s16 y, s16 z)
+{
+ float cx, cy, cz, cw, ox, oy, oz, ow;
+
+ if (julia) { // Julia set
+ cx = julia_x;
+ cy = julia_y;
+ cz = julia_z;
+ cw = julia_w;
+ ox = (float)x / scale.X - offset.X;
+ oy = (float)y / scale.Y - offset.Y;
+ oz = (float)z / scale.Z - offset.Z;
+ ow = slice_w;
+ } else { // Mandelbrot set
+ cx = (float)x / scale.X - offset.X;
+ cy = (float)y / scale.Y - offset.Y;
+ cz = (float)z / scale.Z - offset.Z;
+ cw = slice_w;
+ ox = 0.0f;
+ oy = 0.0f;
+ oz = 0.0f;
+ ow = 0.0f;
+ }
+
+ float nx = 0.0f;
+ float ny = 0.0f;
+ float nz = 0.0f;
+ float nw = 0.0f;
+
+ for (u16 iter = 0; iter < iterations; iter++) {
+ switch (formula) {
+ default:
+ case 1: // 4D "Roundy"
+ nx = ox * ox - oy * oy - oz * oz - ow * ow + cx;
+ ny = 2.0f * (ox * oy + oz * ow) + cy;
+ nz = 2.0f * (ox * oz + oy * ow) + cz;
+ nw = 2.0f * (ox * ow + oy * oz) + cw;
+ break;
+ case 2: // 4D "Squarry"
+ nx = ox * ox - oy * oy - oz * oz - ow * ow + cx;
+ ny = 2.0f * (ox * oy + oz * ow) + cy;
+ nz = 2.0f * (ox * oz + oy * ow) + cz;
+ nw = 2.0f * (ox * ow - oy * oz) + cw;
+ break;
+ case 3: // 4D "Mandy Cousin"
+ nx = ox * ox - oy * oy - oz * oz + ow * ow + cx;
+ ny = 2.0f * (ox * oy + oz * ow) + cy;
+ nz = 2.0f * (ox * oz + oy * ow) + cz;
+ nw = 2.0f * (ox * ow + oy * oz) + cw;
+ break;
+ case 4: // 4D "Variation"
+ nx = ox * ox - oy * oy - oz * oz - ow * ow + cx;
+ ny = 2.0f * (ox * oy + oz * ow) + cy;
+ nz = 2.0f * (ox * oz - oy * ow) + cz;
+ nw = 2.0f * (ox * ow + oy * oz) + cw;
+ break;
+ case 5: // 3D "Mandelbrot/Mandelbar"
+ nx = ox * ox - oy * oy - oz * oz + cx;
+ ny = 2.0f * ox * oy + cy;
+ nz = -2.0f * ox * oz + cz;
+ break;
+ case 6: // 3D "Christmas Tree"
+ // Altering the formula here is necessary to avoid division by zero
+ if (fabs(oz) < 0.000000001f) {
+ nx = ox * ox - oy * oy - oz * oz + cx;
+ ny = 2.0f * oy * ox + cy;
+ nz = 4.0f * oz * ox + cz;
+ } else {
+ float a = (2.0f * ox) / (sqrt(oy * oy + oz * oz));
+ nx = ox * ox - oy * oy - oz * oz + cx;
+ ny = a * (oy * oy - oz * oz) + cy;
+ nz = a * 2.0f * oy * oz + cz;
+ }
+ break;
+ case 7: // 3D "Mandelbulb"
+ if (fabs(oy) < 0.000000001f) {
+ nx = ox * ox - oz * oz + cx;
+ ny = cy;
+ nz = -2.0f * oz * sqrt(ox * ox) + cz;
+ } else {
+ float a = 1.0f - (oz * oz) / (ox * ox + oy * oy);
+ nx = (ox * ox - oy * oy) * a + cx;
+ ny = 2.0f * ox * oy * a + cy;
+ nz = -2.0f * oz * sqrt(ox * ox + oy * oy) + cz;
+ }
+ break;
+ case 8: // 3D "Cosine Mandelbulb"
+ if (fabs(oy) < 0.000000001f) {
+ nx = 2.0f * ox * oz + cx;
+ ny = 4.0f * oy * oz + cy;
+ nz = oz * oz - ox * ox - oy * oy + cz;
+ } else {
+ float a = (2.0f * oz) / sqrt(ox * ox + oy * oy);
+ nx = (ox * ox - oy * oy) * a + cx;
+ ny = 2.0f * ox * oy * a + cy;
+ nz = oz * oz - ox * ox - oy * oy + cz;
+ }
+ break;
+ case 9: // 4D "Mandelbulb"
+ float rxy = sqrt(ox * ox + oy * oy);
+ float rxyz = sqrt(ox * ox + oy * oy + oz * oz);
+ if (fabs(ow) < 0.000000001f && fabs(oz) < 0.000000001f) {
+ nx = (ox * ox - oy * oy) + cx;
+ ny = 2.0f * ox * oy + cy;
+ nz = -2.0f * rxy * oz + cz;
+ nw = 2.0f * rxyz * ow + cw;
+ } else {
+ float a = 1.0f - (ow * ow) / (rxyz * rxyz);
+ float b = a * (1.0f - (oz * oz) / (rxy * rxy));
+ nx = (ox * ox - oy * oy) * b + cx;
+ ny = 2.0f * ox * oy * b + cy;
+ nz = -2.0f * rxy * oz * a + cz;
+ nw = 2.0f * rxyz * ow + cw;
+ }
+ break;
+ }
+
+ if (nx * nx + ny * ny + nz * nz + nw * nw > 4.0f)
+ return false;
+
+ ox = nx;
+ oy = ny;
+ oz = nz;
+ ow = nw;
+ }
+
+ return true;
+}
+
+
+s16 MapgenFractal::generateTerrain()
+{
+ MapNode n_air(CONTENT_AIR);
+ MapNode n_stone(c_stone);
+ MapNode n_water(c_water_source);
+
+ s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
+ u32 index2d = 0;
+
+ noise_seabed->perlinMap2D(node_min.X, node_min.Z);
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++) {
+ for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
+ u32 vi = vm->m_area.index(node_min.X, y, z);
+ for (s16 x = node_min.X; x <= node_max.X; x++, vi++, index2d++) {
+ if (vm->m_data[vi].getContent() == CONTENT_IGNORE) {
+ s16 seabed_height = noise_seabed->result[index2d];
+
+ if (y <= seabed_height || getFractalAtPoint(x, y, z)) {
+ vm->m_data[vi] = n_stone;
+ if (y > stone_surface_max_y)
+ stone_surface_max_y = y;
+ } else if (y <= water_level) {
+ vm->m_data[vi] = n_water;
+ } else {
+ vm->m_data[vi] = n_air;
+ }
+ }
+ }
+ index2d -= ystride;
+ }
+ index2d += ystride;
+ }
+
+ return stone_surface_max_y;
+}
diff --git a/src/mapgen/mapgen_fractal.h b/src/mapgen/mapgen_fractal.h
new file mode 100644
index 000000000..be4941581
--- /dev/null
+++ b/src/mapgen/mapgen_fractal.h
@@ -0,0 +1,87 @@
+/*
+Minetest
+Copyright (C) 2015-2017 paramat
+Copyright (C) 2015-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+
+Fractal formulas from http://www.bugman123.com/Hypercomplex/index.html
+by Paul Nylander, and from http://www.fractalforums.com, thank you.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "mapgen.h"
+
+class BiomeManager;
+
+extern FlagDesc flagdesc_mapgen_fractal[];
+
+struct MapgenFractalParams : public MapgenParams
+{
+ u32 spflags = 0;
+ float cave_width = 0.09f;
+ s16 large_cave_depth = -33;
+ s16 lava_depth = -256;
+ u16 fractal = 1;
+ u16 iterations = 11;
+ v3f scale = v3f(4096.0, 1024.0, 4096.0);
+ v3f offset = v3f(1.52, 0.0, 0.0);
+ float slice_w = 0.0f;
+ float julia_x = 0.267f;
+ float julia_y = 0.2f;
+ float julia_z = 0.133f;
+ float julia_w = 0.067f;
+ NoiseParams np_seabed;
+ NoiseParams np_filler_depth;
+ NoiseParams np_cave1;
+ NoiseParams np_cave2;
+
+ MapgenFractalParams();
+ ~MapgenFractalParams() = default;
+
+ void readParams(const Settings *settings);
+ void writeParams(Settings *settings) const;
+};
+
+class MapgenFractal : public MapgenBasic
+{
+public:
+ MapgenFractal(int mapgenid, MapgenFractalParams *params, EmergeManager *emerge);
+ ~MapgenFractal();
+
+ virtual MapgenType getType() const { return MAPGEN_FRACTAL; }
+
+ virtual void makeChunk(BlockMakeData *data);
+ int getSpawnLevelAtPoint(v2s16 p);
+ bool getFractalAtPoint(s16 x, s16 y, s16 z);
+ s16 generateTerrain();
+
+private:
+ u16 formula;
+ bool julia;
+
+ s16 large_cave_depth;
+ u16 fractal;
+ u16 iterations;
+ v3f scale;
+ v3f offset;
+ float slice_w;
+ float julia_x;
+ float julia_y;
+ float julia_z;
+ float julia_w;
+ Noise *noise_seabed;
+};
diff --git a/src/mapgen/mapgen_singlenode.cpp b/src/mapgen/mapgen_singlenode.cpp
new file mode 100644
index 000000000..ee8746b83
--- /dev/null
+++ b/src/mapgen/mapgen_singlenode.cpp
@@ -0,0 +1,102 @@
+/*
+Minetest
+Copyright (C) 2013-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "mapgen_singlenode.h"
+#include "voxel.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "nodedef.h"
+#include "voxelalgorithms.h"
+#include "emerge.h"
+
+
+MapgenSinglenode::MapgenSinglenode(int mapgenid,
+ MapgenParams *params, EmergeManager *emerge)
+ : Mapgen(mapgenid, params, emerge)
+{
+ flags = params->flags;
+
+ INodeDefManager *ndef = emerge->ndef;
+
+ c_node = ndef->getId("mapgen_singlenode");
+ if (c_node == CONTENT_IGNORE)
+ c_node = CONTENT_AIR;
+
+ MapNode n_node(c_node);
+ set_light = (ndef->get(n_node).sunlight_propagates) ? LIGHT_SUN : 0x00;
+}
+
+
+//////////////////////// Map generator
+
+void MapgenSinglenode::makeChunk(BlockMakeData *data)
+{
+ // Pre-conditions
+ assert(data->vmanip);
+ assert(data->nodedef);
+ assert(data->blockpos_requested.X >= data->blockpos_min.X &&
+ data->blockpos_requested.Y >= data->blockpos_min.Y &&
+ data->blockpos_requested.Z >= data->blockpos_min.Z);
+ assert(data->blockpos_requested.X <= data->blockpos_max.X &&
+ data->blockpos_requested.Y <= data->blockpos_max.Y &&
+ data->blockpos_requested.Z <= data->blockpos_max.Z);
+
+ this->generating = true;
+ this->vm = data->vmanip;
+ this->ndef = data->nodedef;
+
+ v3s16 blockpos_min = data->blockpos_min;
+ v3s16 blockpos_max = data->blockpos_max;
+
+ // Area of central chunk
+ v3s16 node_min = blockpos_min * MAP_BLOCKSIZE;
+ v3s16 node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ blockseed = getBlockSeed2(node_min, data->seed);
+
+ MapNode n_node(c_node);
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 y = node_min.Y; y <= node_max.Y; y++) {
+ u32 i = vm->m_area.index(node_min.X, y, z);
+ for (s16 x = node_min.X; x <= node_max.X; x++) {
+ if (vm->m_data[i].getContent() == CONTENT_IGNORE)
+ vm->m_data[i] = n_node;
+ i++;
+ }
+ }
+
+ // Add top and bottom side of water to transforming_liquid queue
+ updateLiquid(&data->transforming_liquid, node_min, node_max);
+
+ // Set lighting
+ if ((flags & MG_LIGHT) && set_light == LIGHT_SUN)
+ setLighting(LIGHT_SUN, node_min, node_max);
+
+ this->generating = false;
+}
+
+
+int MapgenSinglenode::getSpawnLevelAtPoint(v2s16 p)
+{
+ return 0;
+}
diff --git a/src/mapgen/mapgen_singlenode.h b/src/mapgen/mapgen_singlenode.h
new file mode 100644
index 000000000..7678613e5
--- /dev/null
+++ b/src/mapgen/mapgen_singlenode.h
@@ -0,0 +1,49 @@
+/*
+Minetest
+Copyright (C) 2013-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "mapgen.h"
+
+struct MapgenSinglenodeParams : public MapgenParams
+{
+ MapgenSinglenodeParams() = default;
+ ~MapgenSinglenodeParams() = default;
+
+ void readParams(const Settings *settings) {}
+ void writeParams(Settings *settings) const {}
+};
+
+class MapgenSinglenode : public Mapgen
+{
+public:
+ u32 flags;
+ content_t c_node;
+ u8 set_light;
+
+ MapgenSinglenode(int mapgenid, MapgenParams *params, EmergeManager *emerge);
+ ~MapgenSinglenode() = default;
+
+ virtual MapgenType getType() const { return MAPGEN_SINGLENODE; }
+
+ void makeChunk(BlockMakeData *data);
+ int getSpawnLevelAtPoint(v2s16 p);
+};
diff --git a/src/mapgen/mapgen_v5.cpp b/src/mapgen/mapgen_v5.cpp
new file mode 100644
index 000000000..5f5487288
--- /dev/null
+++ b/src/mapgen/mapgen_v5.cpp
@@ -0,0 +1,295 @@
+/*
+Minetest
+Copyright (C) 2014-2017 paramat
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "content_sao.h"
+#include "nodedef.h"
+#include "voxelalgorithms.h"
+//#include "profiler.h" // For TimeTaker
+#include "settings.h" // For g_settings
+#include "emerge.h"
+#include "dungeongen.h"
+#include "cavegen.h"
+#include "mg_biome.h"
+#include "mg_ore.h"
+#include "mg_decoration.h"
+#include "mapgen_v5.h"
+
+
+FlagDesc flagdesc_mapgen_v5[] = {
+ {"caverns", MGV5_CAVERNS},
+ {NULL, 0}
+};
+
+
+MapgenV5::MapgenV5(int mapgenid, MapgenV5Params *params, EmergeManager *emerge)
+ : MapgenBasic(mapgenid, params, emerge)
+{
+ spflags = params->spflags;
+ cave_width = params->cave_width;
+ large_cave_depth = params->large_cave_depth;
+ lava_depth = params->lava_depth;
+ cavern_limit = params->cavern_limit;
+ cavern_taper = params->cavern_taper;
+ cavern_threshold = params->cavern_threshold;
+
+ // Terrain noise
+ noise_filler_depth = new Noise(&params->np_filler_depth, seed, csize.X, csize.Z);
+ noise_factor = new Noise(&params->np_factor, seed, csize.X, csize.Z);
+ noise_height = new Noise(&params->np_height, seed, csize.X, csize.Z);
+
+ // 3D terrain noise
+ // 1-up 1-down overgeneration
+ noise_ground = new Noise(&params->np_ground, seed, csize.X, csize.Y + 2, csize.Z);
+ // 1 down overgeneration
+ MapgenBasic::np_cave1 = params->np_cave1;
+ MapgenBasic::np_cave2 = params->np_cave2;
+ MapgenBasic::np_cavern = params->np_cavern;
+}
+
+
+MapgenV5::~MapgenV5()
+{
+ delete noise_filler_depth;
+ delete noise_factor;
+ delete noise_height;
+ delete noise_ground;
+}
+
+
+MapgenV5Params::MapgenV5Params():
+ np_filler_depth (0, 1, v3f(150, 150, 150), 261, 4, 0.7, 2.0),
+ np_factor (0, 1, v3f(250, 250, 250), 920381, 3, 0.45, 2.0),
+ np_height (0, 10, v3f(250, 250, 250), 84174, 4, 0.5, 2.0),
+ np_ground (0, 40, v3f(80, 80, 80), 983240, 4, 0.55, 2.0, NOISE_FLAG_EASED),
+ np_cave1 (0, 12, v3f(50, 50, 50), 52534, 4, 0.5, 2.0),
+ np_cave2 (0, 12, v3f(50, 50, 50), 10325, 4, 0.5, 2.0),
+ np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0)
+{
+}
+
+
+void MapgenV5Params::readParams(const Settings *settings)
+{
+ settings->getFlagStrNoEx("mgv5_spflags", spflags, flagdesc_mapgen_v5);
+ settings->getFloatNoEx("mgv5_cave_width", cave_width);
+ settings->getS16NoEx("mgv5_large_cave_depth", large_cave_depth);
+ settings->getS16NoEx("mgv5_lava_depth", lava_depth);
+ settings->getS16NoEx("mgv5_cavern_limit", cavern_limit);
+ settings->getS16NoEx("mgv5_cavern_taper", cavern_taper);
+ settings->getFloatNoEx("mgv5_cavern_threshold", cavern_threshold);
+
+ settings->getNoiseParams("mgv5_np_filler_depth", np_filler_depth);
+ settings->getNoiseParams("mgv5_np_factor", np_factor);
+ settings->getNoiseParams("mgv5_np_height", np_height);
+ settings->getNoiseParams("mgv5_np_ground", np_ground);
+ settings->getNoiseParams("mgv5_np_cave1", np_cave1);
+ settings->getNoiseParams("mgv5_np_cave2", np_cave2);
+ settings->getNoiseParams("mgv5_np_cavern", np_cavern);
+}
+
+
+void MapgenV5Params::writeParams(Settings *settings) const
+{
+ settings->setFlagStr("mgv5_spflags", spflags, flagdesc_mapgen_v5, U32_MAX);
+ settings->setFloat("mgv5_cave_width", cave_width);
+ settings->setS16("mgv5_large_cave_depth", large_cave_depth);
+ settings->setS16("mgv5_lava_depth", lava_depth);
+ settings->setS16("mgv5_cavern_limit", cavern_limit);
+ settings->setS16("mgv5_cavern_taper", cavern_taper);
+ settings->setFloat("mgv5_cavern_threshold", cavern_threshold);
+
+ settings->setNoiseParams("mgv5_np_filler_depth", np_filler_depth);
+ settings->setNoiseParams("mgv5_np_factor", np_factor);
+ settings->setNoiseParams("mgv5_np_height", np_height);
+ settings->setNoiseParams("mgv5_np_ground", np_ground);
+ settings->setNoiseParams("mgv5_np_cave1", np_cave1);
+ settings->setNoiseParams("mgv5_np_cave2", np_cave2);
+ settings->setNoiseParams("mgv5_np_cavern", np_cavern);
+}
+
+
+int MapgenV5::getSpawnLevelAtPoint(v2s16 p)
+{
+
+ float f = 0.55 + NoisePerlin2D(&noise_factor->np, p.X, p.Y, seed);
+ if (f < 0.01)
+ f = 0.01;
+ else if (f >= 1.0)
+ f *= 1.6;
+ float h = NoisePerlin2D(&noise_height->np, p.X, p.Y, seed);
+
+ // noise_height 'offset' is the average level of terrain. At least 50% of
+ // terrain will be below this.
+ // Raising the maximum spawn level above 'water_level + 16' is necessary
+ // for when noise_height 'offset' is set much higher than water_level.
+ s16 max_spawn_y = MYMAX(noise_height->np.offset, water_level + 16);
+
+ // Starting spawn search at max_spawn_y + 128 ensures 128 nodes of open
+ // space above spawn position. Avoids spawning in possibly sealed voids.
+ for (s16 y = max_spawn_y + 128; y >= water_level; y--) {
+ float n_ground = NoisePerlin3D(&noise_ground->np, p.X, y, p.Y, seed);
+
+ if (n_ground * f > y - h) { // If solid
+ if (y < water_level || y > max_spawn_y)
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+
+ // y + 2 because y is surface and due to biome 'dust' nodes.
+ return y + 2;
+ }
+ }
+ // Unsuitable spawn position, no ground found
+ return MAX_MAP_GENERATION_LIMIT;
+}
+
+
+void MapgenV5::makeChunk(BlockMakeData *data)
+{
+ // Pre-conditions
+ assert(data->vmanip);
+ assert(data->nodedef);
+ assert(data->blockpos_requested.X >= data->blockpos_min.X &&
+ data->blockpos_requested.Y >= data->blockpos_min.Y &&
+ data->blockpos_requested.Z >= data->blockpos_min.Z);
+ assert(data->blockpos_requested.X <= data->blockpos_max.X &&
+ data->blockpos_requested.Y <= data->blockpos_max.Y &&
+ data->blockpos_requested.Z <= data->blockpos_max.Z);
+
+ this->generating = true;
+ this->vm = data->vmanip;
+ this->ndef = data->nodedef;
+ //TimeTaker t("makeChunk");
+
+ v3s16 blockpos_min = data->blockpos_min;
+ v3s16 blockpos_max = data->blockpos_max;
+ node_min = blockpos_min * MAP_BLOCKSIZE;
+ node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+ full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
+ full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ // Create a block-specific seed
+ blockseed = getBlockSeed2(full_node_min, seed);
+
+ // Generate base terrain
+ s16 stone_surface_max_y = generateBaseTerrain();
+
+ // Create heightmap
+ updateHeightmap(node_min, node_max);
+
+ // Init biome generator, place biome-specific nodes, and build biomemap
+ biomegen->calcBiomeNoise(node_min);
+
+ MgStoneType mgstone_type;
+ content_t biome_stone;
+ generateBiomes(&mgstone_type, &biome_stone);
+
+ // Generate caverns, tunnels and classic caves
+ if (flags & MG_CAVES) {
+ bool near_cavern = false;
+ // Generate caverns
+ if (spflags & MGV5_CAVERNS)
+ near_cavern = generateCaverns(stone_surface_max_y);
+ // Generate tunnels and classic caves
+ if (near_cavern)
+ // Disable classic caves in this mapchunk by setting
+ // 'large cave depth' to world base. Avoids excessive liquid in
+ // large caverns and floating blobs of overgenerated liquid.
+ generateCaves(stone_surface_max_y, -MAX_MAP_GENERATION_LIMIT);
+ else
+ generateCaves(stone_surface_max_y, large_cave_depth);
+ }
+
+ // Generate dungeons and desert temples
+ if (flags & MG_DUNGEONS)
+ generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
+
+ // Generate the registered decorations
+ if (flags & MG_DECORATIONS)
+ m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
+
+ // Generate the registered ores
+ m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
+
+ // Sprinkle some dust on top after everything else was generated
+ dustTopNodes();
+
+ //printf("makeChunk: %dms\n", t.stop());
+
+ // Add top and bottom side of water to transforming_liquid queue
+ updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
+
+ // Calculate lighting
+ if (flags & MG_LIGHT) {
+ calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
+ full_node_min, full_node_max);
+ }
+
+ this->generating = false;
+}
+
+
+int MapgenV5::generateBaseTerrain()
+{
+ u32 index = 0;
+ u32 index2d = 0;
+ int stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
+
+ noise_factor->perlinMap2D(node_min.X, node_min.Z);
+ noise_height->perlinMap2D(node_min.X, node_min.Z);
+ noise_ground->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
+
+ for (s16 z=node_min.Z; z<=node_max.Z; z++) {
+ for (s16 y=node_min.Y - 1; y<=node_max.Y + 1; y++) {
+ u32 vi = vm->m_area.index(node_min.X, y, z);
+ for (s16 x=node_min.X; x<=node_max.X; x++, vi++, index++, index2d++) {
+ if (vm->m_data[vi].getContent() != CONTENT_IGNORE)
+ continue;
+
+ float f = 0.55 + noise_factor->result[index2d];
+ if (f < 0.01)
+ f = 0.01;
+ else if (f >= 1.0)
+ f *= 1.6;
+ float h = noise_height->result[index2d];
+
+ if (noise_ground->result[index] * f < y - h) {
+ if (y <= water_level)
+ vm->m_data[vi] = MapNode(c_water_source);
+ else
+ vm->m_data[vi] = MapNode(CONTENT_AIR);
+ } else {
+ vm->m_data[vi] = MapNode(c_stone);
+ if (y > stone_surface_max_y)
+ stone_surface_max_y = y;
+ }
+ }
+ index2d -= ystride;
+ }
+ index2d += ystride;
+ }
+
+ return stone_surface_max_y;
+}
diff --git a/src/mapgen/mapgen_v5.h b/src/mapgen/mapgen_v5.h
new file mode 100644
index 000000000..44b0a09e7
--- /dev/null
+++ b/src/mapgen/mapgen_v5.h
@@ -0,0 +1,74 @@
+/*
+Minetest
+Copyright (C) 2014-2017 paramat
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "mapgen.h"
+
+///////// Mapgen V5 flags
+#define MGV5_CAVERNS 0x01
+
+class BiomeManager;
+
+extern FlagDesc flagdesc_mapgen_v5[];
+
+struct MapgenV5Params : public MapgenParams
+{
+ u32 spflags = MGV5_CAVERNS;
+ float cave_width = 0.125f;
+ s16 large_cave_depth = -256;
+ s16 lava_depth = -256;
+ s16 cavern_limit = -256;
+ s16 cavern_taper = 256;
+ float cavern_threshold = 0.7f;
+
+ NoiseParams np_filler_depth;
+ NoiseParams np_factor;
+ NoiseParams np_height;
+ NoiseParams np_ground;
+ NoiseParams np_cave1;
+ NoiseParams np_cave2;
+ NoiseParams np_cavern;
+
+ MapgenV5Params();
+ ~MapgenV5Params() = default;
+
+ void readParams(const Settings *settings);
+ void writeParams(Settings *settings) const;
+};
+
+class MapgenV5 : public MapgenBasic
+{
+public:
+ MapgenV5(int mapgenid, MapgenV5Params *params, EmergeManager *emerge);
+ ~MapgenV5();
+
+ virtual MapgenType getType() const { return MAPGEN_V5; }
+
+ virtual void makeChunk(BlockMakeData *data);
+ int getSpawnLevelAtPoint(v2s16 p);
+ int generateBaseTerrain();
+
+private:
+ s16 large_cave_depth;
+ Noise *noise_factor;
+ Noise *noise_height;
+ Noise *noise_ground;
+};
diff --git a/src/mapgen/mapgen_v6.cpp b/src/mapgen/mapgen_v6.cpp
new file mode 100644
index 000000000..6dcf834d2
--- /dev/null
+++ b/src/mapgen/mapgen_v6.cpp
@@ -0,0 +1,1123 @@
+/*
+Minetest
+Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2014-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+//#include "serverobject.h"
+#include "content_sao.h"
+#include "nodedef.h"
+#include "voxelalgorithms.h"
+//#include "profiler.h" // For TimeTaker
+#include "settings.h" // For g_settings
+#include "emerge.h"
+#include "dungeongen.h"
+#include "cavegen.h"
+#include "treegen.h"
+#include "mg_ore.h"
+#include "mg_decoration.h"
+#include "mapgen_v6.h"
+
+
+FlagDesc flagdesc_mapgen_v6[] = {
+ {"jungles", MGV6_JUNGLES},
+ {"biomeblend", MGV6_BIOMEBLEND},
+ {"mudflow", MGV6_MUDFLOW},
+ {"snowbiomes", MGV6_SNOWBIOMES},
+ {"flat", MGV6_FLAT},
+ {"trees", MGV6_TREES},
+ {NULL, 0}
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+
+MapgenV6::MapgenV6(int mapgenid, MapgenV6Params *params, EmergeManager *emerge)
+ : Mapgen(mapgenid, params, emerge)
+{
+ m_emerge = emerge;
+ ystride = csize.X; //////fix this
+
+ heightmap = new s16[csize.X * csize.Z];
+
+ spflags = params->spflags;
+ freq_desert = params->freq_desert;
+ freq_beach = params->freq_beach;
+
+ np_cave = &params->np_cave;
+ np_humidity = &params->np_humidity;
+ np_trees = &params->np_trees;
+ np_apple_trees = &params->np_apple_trees;
+
+ //// Create noise objects
+ noise_terrain_base = new Noise(&params->np_terrain_base, seed, csize.X, csize.Y);
+ noise_terrain_higher = new Noise(&params->np_terrain_higher, seed, csize.X, csize.Y);
+ noise_steepness = new Noise(&params->np_steepness, seed, csize.X, csize.Y);
+ noise_height_select = new Noise(&params->np_height_select, seed, csize.X, csize.Y);
+ noise_mud = new Noise(&params->np_mud, seed, csize.X, csize.Y);
+ noise_beach = new Noise(&params->np_beach, seed, csize.X, csize.Y);
+ noise_biome = new Noise(&params->np_biome, seed,
+ csize.X + 2 * MAP_BLOCKSIZE, csize.Y + 2 * MAP_BLOCKSIZE);
+ noise_humidity = new Noise(&params->np_humidity, seed,
+ csize.X + 2 * MAP_BLOCKSIZE, csize.Y + 2 * MAP_BLOCKSIZE);
+
+ //// Resolve nodes to be used
+ INodeDefManager *ndef = emerge->ndef;
+
+ c_stone = ndef->getId("mapgen_stone");
+ c_dirt = ndef->getId("mapgen_dirt");
+ c_dirt_with_grass = ndef->getId("mapgen_dirt_with_grass");
+ c_sand = ndef->getId("mapgen_sand");
+ c_water_source = ndef->getId("mapgen_water_source");
+ c_lava_source = ndef->getId("mapgen_lava_source");
+ c_gravel = ndef->getId("mapgen_gravel");
+ c_desert_stone = ndef->getId("mapgen_desert_stone");
+ c_desert_sand = ndef->getId("mapgen_desert_sand");
+ c_dirt_with_snow = ndef->getId("mapgen_dirt_with_snow");
+ c_snow = ndef->getId("mapgen_snow");
+ c_snowblock = ndef->getId("mapgen_snowblock");
+ c_ice = ndef->getId("mapgen_ice");
+
+ if (c_gravel == CONTENT_IGNORE)
+ c_gravel = c_stone;
+ if (c_desert_stone == CONTENT_IGNORE)
+ c_desert_stone = c_stone;
+ if (c_desert_sand == CONTENT_IGNORE)
+ c_desert_sand = c_sand;
+ if (c_dirt_with_snow == CONTENT_IGNORE)
+ c_dirt_with_snow = c_dirt_with_grass;
+ if (c_snow == CONTENT_IGNORE)
+ c_snow = CONTENT_AIR;
+ if (c_snowblock == CONTENT_IGNORE)
+ c_snowblock = c_dirt_with_grass;
+ if (c_ice == CONTENT_IGNORE)
+ c_ice = c_water_source;
+
+ c_cobble = ndef->getId("mapgen_cobble");
+ c_mossycobble = ndef->getId("mapgen_mossycobble");
+ c_stair_cobble = ndef->getId("mapgen_stair_cobble");
+ c_stair_desert_stone = ndef->getId("mapgen_stair_desert_stone");
+
+ if (c_mossycobble == CONTENT_IGNORE)
+ c_mossycobble = c_cobble;
+ if (c_stair_cobble == CONTENT_IGNORE)
+ c_stair_cobble = c_cobble;
+ if (c_stair_desert_stone == CONTENT_IGNORE)
+ c_stair_desert_stone = c_desert_stone;
+}
+
+
+MapgenV6::~MapgenV6()
+{
+ delete noise_terrain_base;
+ delete noise_terrain_higher;
+ delete noise_steepness;
+ delete noise_height_select;
+ delete noise_mud;
+ delete noise_beach;
+ delete noise_biome;
+ delete noise_humidity;
+
+ delete[] heightmap;
+}
+
+
+MapgenV6Params::MapgenV6Params():
+ np_terrain_base (-4, 20.0, v3f(250.0, 250.0, 250.0), 82341, 5, 0.6, 2.0),
+ np_terrain_higher (20, 16.0, v3f(500.0, 500.0, 500.0), 85039, 5, 0.6, 2.0),
+ np_steepness (0.85, 0.5, v3f(125.0, 125.0, 125.0), -932, 5, 0.7, 2.0),
+ np_height_select (0, 1.0, v3f(250.0, 250.0, 250.0), 4213, 5, 0.69, 2.0),
+ np_mud (4, 2.0, v3f(200.0, 200.0, 200.0), 91013, 3, 0.55, 2.0),
+ np_beach (0, 1.0, v3f(250.0, 250.0, 250.0), 59420, 3, 0.50, 2.0),
+ np_biome (0, 1.0, v3f(500.0, 500.0, 500.0), 9130, 3, 0.50, 2.0),
+ np_cave (6, 6.0, v3f(250.0, 250.0, 250.0), 34329, 3, 0.50, 2.0),
+ np_humidity (0.5, 0.5, v3f(500.0, 500.0, 500.0), 72384, 3, 0.50, 2.0),
+ np_trees (0, 1.0, v3f(125.0, 125.0, 125.0), 2, 4, 0.66, 2.0),
+ np_apple_trees (0, 1.0, v3f(100.0, 100.0, 100.0), 342902, 3, 0.45, 2.0)
+{
+}
+
+
+void MapgenV6Params::readParams(const Settings *settings)
+{
+ settings->getFlagStrNoEx("mgv6_spflags", spflags, flagdesc_mapgen_v6);
+ settings->getFloatNoEx("mgv6_freq_desert", freq_desert);
+ settings->getFloatNoEx("mgv6_freq_beach", freq_beach);
+
+ settings->getNoiseParams("mgv6_np_terrain_base", np_terrain_base);
+ settings->getNoiseParams("mgv6_np_terrain_higher", np_terrain_higher);
+ settings->getNoiseParams("mgv6_np_steepness", np_steepness);
+ settings->getNoiseParams("mgv6_np_height_select", np_height_select);
+ settings->getNoiseParams("mgv6_np_mud", np_mud);
+ settings->getNoiseParams("mgv6_np_beach", np_beach);
+ settings->getNoiseParams("mgv6_np_biome", np_biome);
+ settings->getNoiseParams("mgv6_np_cave", np_cave);
+ settings->getNoiseParams("mgv6_np_humidity", np_humidity);
+ settings->getNoiseParams("mgv6_np_trees", np_trees);
+ settings->getNoiseParams("mgv6_np_apple_trees", np_apple_trees);
+}
+
+
+void MapgenV6Params::writeParams(Settings *settings) const
+{
+ settings->setFlagStr("mgv6_spflags", spflags, flagdesc_mapgen_v6, U32_MAX);
+ settings->setFloat("mgv6_freq_desert", freq_desert);
+ settings->setFloat("mgv6_freq_beach", freq_beach);
+
+ settings->setNoiseParams("mgv6_np_terrain_base", np_terrain_base);
+ settings->setNoiseParams("mgv6_np_terrain_higher", np_terrain_higher);
+ settings->setNoiseParams("mgv6_np_steepness", np_steepness);
+ settings->setNoiseParams("mgv6_np_height_select", np_height_select);
+ settings->setNoiseParams("mgv6_np_mud", np_mud);
+ settings->setNoiseParams("mgv6_np_beach", np_beach);
+ settings->setNoiseParams("mgv6_np_biome", np_biome);
+ settings->setNoiseParams("mgv6_np_cave", np_cave);
+ settings->setNoiseParams("mgv6_np_humidity", np_humidity);
+ settings->setNoiseParams("mgv6_np_trees", np_trees);
+ settings->setNoiseParams("mgv6_np_apple_trees", np_apple_trees);
+}
+
+
+//////////////////////// Some helper functions for the map generator
+
+// Returns Y one under area minimum if not found
+s16 MapgenV6::find_stone_level(v2s16 p2d)
+{
+ const v3s16 &em = vm->m_area.getExtent();
+ s16 y_nodes_max = vm->m_area.MaxEdge.Y;
+ s16 y_nodes_min = vm->m_area.MinEdge.Y;
+ u32 i = vm->m_area.index(p2d.X, y_nodes_max, p2d.Y);
+ s16 y;
+
+ for (y = y_nodes_max; y >= y_nodes_min; y--) {
+ content_t c = vm->m_data[i].getContent();
+ if (c != CONTENT_IGNORE && (c == c_stone || c == c_desert_stone))
+ break;
+
+ vm->m_area.add_y(em, i, -1);
+ }
+ return (y >= y_nodes_min) ? y : y_nodes_min - 1;
+}
+
+
+// Required by mapgen.h
+bool MapgenV6::block_is_underground(u64 seed, v3s16 blockpos)
+{
+ /*s16 minimum_groundlevel = (s16)get_sector_minimum_ground_level(
+ seed, v2s16(blockpos.X, blockpos.Z));*/
+ // Nah, this is just a heuristic, just return something
+ s16 minimum_groundlevel = water_level;
+
+ if(blockpos.Y * MAP_BLOCKSIZE + MAP_BLOCKSIZE <= minimum_groundlevel)
+ return true;
+
+ return false;
+}
+
+
+//////////////////////// Base terrain height functions
+
+float MapgenV6::baseTerrainLevel(float terrain_base, float terrain_higher,
+ float steepness, float height_select)
+{
+ float base = 1 + terrain_base;
+ float higher = 1 + terrain_higher;
+
+ // Limit higher ground level to at least base
+ if(higher < base)
+ higher = base;
+
+ // Steepness factor of cliffs
+ float b = steepness;
+ b = rangelim(b, 0.0, 1000.0);
+ b = 5 * b * b * b * b * b * b * b;
+ b = rangelim(b, 0.5, 1000.0);
+
+ // Values 1.5...100 give quite horrible looking slopes
+ if (b > 1.5 && b < 100.0)
+ b = (b < 10.0) ? 1.5 : 100.0;
+
+ float a_off = -0.20; // Offset to more low
+ float a = 0.5 + b * (a_off + height_select);
+ a = rangelim(a, 0.0, 1.0); // Limit
+
+ return base * (1.0 - a) + higher * a;
+}
+
+
+float MapgenV6::baseTerrainLevelFromNoise(v2s16 p)
+{
+ if (spflags & MGV6_FLAT)
+ return water_level;
+
+ float terrain_base = NoisePerlin2D_PO(&noise_terrain_base->np,
+ p.X, 0.5, p.Y, 0.5, seed);
+ float terrain_higher = NoisePerlin2D_PO(&noise_terrain_higher->np,
+ p.X, 0.5, p.Y, 0.5, seed);
+ float steepness = NoisePerlin2D_PO(&noise_steepness->np,
+ p.X, 0.5, p.Y, 0.5, seed);
+ float height_select = NoisePerlin2D_PO(&noise_height_select->np,
+ p.X, 0.5, p.Y, 0.5, seed);
+
+ return baseTerrainLevel(terrain_base, terrain_higher,
+ steepness, height_select);
+}
+
+
+float MapgenV6::baseTerrainLevelFromMap(v2s16 p)
+{
+ int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X);
+ return baseTerrainLevelFromMap(index);
+}
+
+
+float MapgenV6::baseTerrainLevelFromMap(int index)
+{
+ if (spflags & MGV6_FLAT)
+ return water_level;
+
+ float terrain_base = noise_terrain_base->result[index];
+ float terrain_higher = noise_terrain_higher->result[index];
+ float steepness = noise_steepness->result[index];
+ float height_select = noise_height_select->result[index];
+
+ return baseTerrainLevel(terrain_base, terrain_higher,
+ steepness, height_select);
+}
+
+
+s16 MapgenV6::find_ground_level_from_noise(u64 seed, v2s16 p2d, s16 precision)
+{
+ return baseTerrainLevelFromNoise(p2d) + MGV6_AVERAGE_MUD_AMOUNT;
+}
+
+
+int MapgenV6::getGroundLevelAtPoint(v2s16 p)
+{
+ return baseTerrainLevelFromNoise(p) + MGV6_AVERAGE_MUD_AMOUNT;
+}
+
+
+int MapgenV6::getSpawnLevelAtPoint(v2s16 p)
+{
+ s16 level_at_point = baseTerrainLevelFromNoise(p) + MGV6_AVERAGE_MUD_AMOUNT;
+ if (level_at_point <= water_level ||
+ level_at_point > water_level + 16)
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+
+ return level_at_point;
+}
+
+
+//////////////////////// Noise functions
+
+float MapgenV6::getMudAmount(v2s16 p)
+{
+ int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X);
+ return getMudAmount(index);
+}
+
+
+bool MapgenV6::getHaveBeach(v2s16 p)
+{
+ int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X);
+ return getHaveBeach(index);
+}
+
+
+BiomeV6Type MapgenV6::getBiome(v2s16 p)
+{
+ int index = (p.Y - full_node_min.Z) * (ystride + 2 * MAP_BLOCKSIZE)
+ + (p.X - full_node_min.X);
+ return getBiome(index, p);
+}
+
+
+float MapgenV6::getHumidity(v2s16 p)
+{
+ /*double noise = noise2d_perlin(
+ 0.5+(float)p.X/500, 0.5+(float)p.Y/500,
+ seed+72384, 4, 0.66);
+ noise = (noise + 1.0)/2.0;*/
+
+ int index = (p.Y - full_node_min.Z) * (ystride + 2 * MAP_BLOCKSIZE)
+ + (p.X - full_node_min.X);
+ float noise = noise_humidity->result[index];
+
+ if (noise < 0.0)
+ noise = 0.0;
+ if (noise > 1.0)
+ noise = 1.0;
+ return noise;
+}
+
+
+float MapgenV6::getTreeAmount(v2s16 p)
+{
+ /*double noise = noise2d_perlin(
+ 0.5+(float)p.X/125, 0.5+(float)p.Y/125,
+ seed+2, 4, 0.66);*/
+
+ float noise = NoisePerlin2D(np_trees, p.X, p.Y, seed);
+ float zeroval = -0.39;
+ if (noise < zeroval)
+ return 0;
+
+ return 0.04 * (noise - zeroval) / (1.0 - zeroval);
+}
+
+
+bool MapgenV6::getHaveAppleTree(v2s16 p)
+{
+ /*is_apple_tree = noise2d_perlin(
+ 0.5+(float)p.X/100, 0.5+(float)p.Z/100,
+ data->seed+342902, 3, 0.45) > 0.2;*/
+
+ float noise = NoisePerlin2D(np_apple_trees, p.X, p.Y, seed);
+
+ return noise > 0.2;
+}
+
+
+float MapgenV6::getMudAmount(int index)
+{
+ if (spflags & MGV6_FLAT)
+ return MGV6_AVERAGE_MUD_AMOUNT;
+
+ /*return ((float)AVERAGE_MUD_AMOUNT + 2.0 * noise2d_perlin(
+ 0.5+(float)p.X/200, 0.5+(float)p.Y/200,
+ seed+91013, 3, 0.55));*/
+
+ return noise_mud->result[index];
+}
+
+
+bool MapgenV6::getHaveBeach(int index)
+{
+ // Determine whether to have sand here
+ /*double sandnoise = noise2d_perlin(
+ 0.2+(float)p2d.X/250, 0.7+(float)p2d.Y/250,
+ seed+59420, 3, 0.50);*/
+
+ float sandnoise = noise_beach->result[index];
+ return (sandnoise > freq_beach);
+}
+
+
+BiomeV6Type MapgenV6::getBiome(int index, v2s16 p)
+{
+ // Just do something very simple as for now
+ /*double d = noise2d_perlin(
+ 0.6+(float)p2d.X/250, 0.2+(float)p2d.Y/250,
+ seed+9130, 3, 0.50);*/
+
+ float d = noise_biome->result[index];
+ float h = noise_humidity->result[index];
+
+ if (spflags & MGV6_SNOWBIOMES) {
+ float blend = (spflags & MGV6_BIOMEBLEND) ? noise2d(p.X, p.Y, seed) / 40 : 0;
+
+ if (d > MGV6_FREQ_HOT + blend) {
+ if (h > MGV6_FREQ_JUNGLE + blend)
+ return BT_JUNGLE;
+
+ return BT_DESERT;
+ }
+
+ if (d < MGV6_FREQ_SNOW + blend) {
+ if (h > MGV6_FREQ_TAIGA + blend)
+ return BT_TAIGA;
+
+ return BT_TUNDRA;
+ }
+
+ return BT_NORMAL;
+ }
+
+ if (d > freq_desert)
+ return BT_DESERT;
+
+ if ((spflags & MGV6_BIOMEBLEND) && (d > freq_desert - 0.10) &&
+ ((noise2d(p.X, p.Y, seed) + 1.0) > (freq_desert - d) * 20.0))
+ return BT_DESERT;
+
+ if ((spflags & MGV6_JUNGLES) && h > 0.75)
+ return BT_JUNGLE;
+
+ return BT_NORMAL;
+
+}
+
+
+u32 MapgenV6::get_blockseed(u64 seed, v3s16 p)
+{
+ s32 x = p.X, y = p.Y, z = p.Z;
+ return (u32)(seed % 0x100000000ULL) + z * 38134234 + y * 42123 + x * 23;
+}
+
+
+//////////////////////// Map generator
+
+void MapgenV6::makeChunk(BlockMakeData *data)
+{
+ // Pre-conditions
+ assert(data->vmanip);
+ assert(data->nodedef);
+ assert(data->blockpos_requested.X >= data->blockpos_min.X &&
+ data->blockpos_requested.Y >= data->blockpos_min.Y &&
+ data->blockpos_requested.Z >= data->blockpos_min.Z);
+ assert(data->blockpos_requested.X <= data->blockpos_max.X &&
+ data->blockpos_requested.Y <= data->blockpos_max.Y &&
+ data->blockpos_requested.Z <= data->blockpos_max.Z);
+
+ this->generating = true;
+ this->vm = data->vmanip;
+ this->ndef = data->nodedef;
+
+ // Hack: use minimum block coords for old code that assumes a single block
+ v3s16 blockpos_min = data->blockpos_min;
+ v3s16 blockpos_max = data->blockpos_max;
+
+ // Area of central chunk
+ node_min = blockpos_min * MAP_BLOCKSIZE;
+ node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ // Full allocated area
+ full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
+ full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ central_area_size = node_max - node_min + v3s16(1, 1, 1);
+ assert(central_area_size.X == central_area_size.Z);
+
+ // Create a block-specific seed
+ blockseed = get_blockseed(data->seed, full_node_min);
+
+ // Make some noise
+ calculateNoise();
+
+ // Maximum height of the stone surface and obstacles.
+ // This is used to guide the cave generation
+ s16 stone_surface_max_y;
+
+ // Generate general ground level to full area
+ stone_surface_max_y = generateGround();
+
+ // Create initial heightmap to limit caves
+ updateHeightmap(node_min, node_max);
+
+ const s16 max_spread_amount = MAP_BLOCKSIZE;
+ // Limit dirt flow area by 1 because mud is flown into neighbors.
+ s16 mudflow_minpos = -max_spread_amount + 1;
+ s16 mudflow_maxpos = central_area_size.X + max_spread_amount - 2;
+
+ // Loop this part, it will make stuff look older and newer nicely
+ const u32 age_loops = 2;
+ for (u32 i_age = 0; i_age < age_loops; i_age++) { // Aging loop
+ // Make caves (this code is relatively horrible)
+ if (flags & MG_CAVES)
+ generateCaves(stone_surface_max_y);
+
+ // Add mud to the central chunk
+ addMud();
+
+ // Flow mud away from steep edges
+ if (spflags & MGV6_MUDFLOW)
+ flowMud(mudflow_minpos, mudflow_maxpos);
+
+ }
+
+ // Update heightmap after mudflow
+ updateHeightmap(node_min, node_max);
+
+ // Add dungeons
+ if ((flags & MG_DUNGEONS) && (stone_surface_max_y >= node_min.Y)) {
+ DungeonParams dp;
+
+ dp.seed = seed;
+ dp.c_water = c_water_source;
+ dp.c_river_water = c_water_source;
+
+ dp.only_in_ground = true;
+ dp.corridor_len_min = 1;
+ dp.corridor_len_max = 13;
+ dp.rooms_min = 2;
+ dp.rooms_max = 16;
+ dp.y_min = -MAX_MAP_GENERATION_LIMIT;
+ dp.y_max = MAX_MAP_GENERATION_LIMIT;
+
+ dp.np_density
+ = NoiseParams(0.9, 0.5, v3f(500.0, 500.0, 500.0), 0, 2, 0.8, 2.0);
+ dp.np_alt_wall
+ = NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0);
+
+ if (getBiome(0, v2s16(node_min.X, node_min.Z)) == BT_DESERT) {
+ dp.c_wall = c_desert_stone;
+ dp.c_alt_wall = CONTENT_IGNORE;
+ dp.c_stair = c_stair_desert_stone;
+
+ dp.diagonal_dirs = true;
+ dp.holesize = v3s16(2, 3, 2);
+ dp.room_size_min = v3s16(6, 9, 6);
+ dp.room_size_max = v3s16(10, 11, 10);
+ dp.room_size_large_min = v3s16(10, 13, 10);
+ dp.room_size_large_max = v3s16(18, 21, 18);
+ dp.notifytype = GENNOTIFY_TEMPLE;
+ } else {
+ dp.c_wall = c_cobble;
+ dp.c_alt_wall = c_mossycobble;
+ dp.c_stair = c_stair_cobble;
+
+ dp.diagonal_dirs = false;
+ dp.holesize = v3s16(1, 2, 1);
+ dp.room_size_min = v3s16(4, 4, 4);
+ dp.room_size_max = v3s16(8, 6, 8);
+ dp.room_size_large_min = v3s16(8, 8, 8);
+ dp.room_size_large_max = v3s16(16, 16, 16);
+ dp.notifytype = GENNOTIFY_DUNGEON;
+ }
+
+ DungeonGen dgen(ndef, &gennotify, &dp);
+ dgen.generate(vm, blockseed, full_node_min, full_node_max);
+ }
+
+ // Add top and bottom side of water to transforming_liquid queue
+ updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
+
+ // Add surface nodes
+ growGrass();
+
+ // Generate some trees, and add grass, if a jungle
+ if (spflags & MGV6_TREES)
+ placeTreesAndJungleGrass();
+
+ // Generate the registered decorations
+ if (flags & MG_DECORATIONS)
+ m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
+
+ // Generate the registered ores
+ m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
+
+ // Calculate lighting
+ if (flags & MG_LIGHT)
+ calcLighting(node_min - v3s16(1, 1, 1) * MAP_BLOCKSIZE,
+ node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE,
+ full_node_min, full_node_max);
+
+ this->generating = false;
+}
+
+
+void MapgenV6::calculateNoise()
+{
+ int x = node_min.X;
+ int z = node_min.Z;
+ int fx = full_node_min.X;
+ int fz = full_node_min.Z;
+
+ if (!(spflags & MGV6_FLAT)) {
+ noise_terrain_base->perlinMap2D_PO(x, 0.5, z, 0.5);
+ noise_terrain_higher->perlinMap2D_PO(x, 0.5, z, 0.5);
+ noise_steepness->perlinMap2D_PO(x, 0.5, z, 0.5);
+ noise_height_select->perlinMap2D_PO(x, 0.5, z, 0.5);
+ noise_mud->perlinMap2D_PO(x, 0.5, z, 0.5);
+ }
+
+ noise_beach->perlinMap2D_PO(x, 0.2, z, 0.7);
+
+ noise_biome->perlinMap2D_PO(fx, 0.6, fz, 0.2);
+ noise_humidity->perlinMap2D_PO(fx, 0.0, fz, 0.0);
+ // Humidity map does not need range limiting 0 to 1,
+ // only humidity at point does
+}
+
+
+int MapgenV6::generateGround()
+{
+ //TimeTaker timer1("Generating ground level");
+ MapNode n_air(CONTENT_AIR), n_water_source(c_water_source);
+ MapNode n_stone(c_stone), n_desert_stone(c_desert_stone);
+ MapNode n_ice(c_ice);
+ int stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
+
+ u32 index = 0;
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
+ // Surface height
+ s16 surface_y = (s16)baseTerrainLevelFromMap(index);
+
+ // Log it
+ if (surface_y > stone_surface_max_y)
+ stone_surface_max_y = surface_y;
+
+ BiomeV6Type bt = getBiome(v2s16(x, z));
+
+ // Fill ground with stone
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 i = vm->m_area.index(x, node_min.Y, z);
+ for (s16 y = node_min.Y; y <= node_max.Y; y++) {
+ if (vm->m_data[i].getContent() == CONTENT_IGNORE) {
+ if (y <= surface_y) {
+ vm->m_data[i] = (y >= MGV6_DESERT_STONE_BASE
+ && bt == BT_DESERT) ?
+ n_desert_stone : n_stone;
+ } else if (y <= water_level) {
+ vm->m_data[i] = (y >= MGV6_ICE_BASE
+ && bt == BT_TUNDRA) ?
+ n_ice : n_water_source;
+ } else {
+ vm->m_data[i] = n_air;
+ }
+ }
+ vm->m_area.add_y(em, i, 1);
+ }
+ }
+
+ return stone_surface_max_y;
+}
+
+
+void MapgenV6::addMud()
+{
+ // 15ms @cs=8
+ //TimeTaker timer1("add mud");
+ MapNode n_dirt(c_dirt), n_gravel(c_gravel);
+ MapNode n_sand(c_sand), n_desert_sand(c_desert_sand);
+ MapNode addnode;
+
+ u32 index = 0;
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
+ // Randomize mud amount
+ s16 mud_add_amount = getMudAmount(index) / 2.0 + 0.5;
+
+ // Find ground level
+ s16 surface_y = find_stone_level(v2s16(x, z)); /////////////////optimize this!
+
+ // Handle area not found
+ if (surface_y == vm->m_area.MinEdge.Y - 1)
+ continue;
+
+ BiomeV6Type bt = getBiome(v2s16(x, z));
+ addnode = (bt == BT_DESERT) ? n_desert_sand : n_dirt;
+
+ if (bt == BT_DESERT && surface_y + mud_add_amount <= water_level + 1) {
+ addnode = n_sand;
+ } else if (mud_add_amount <= 0) {
+ mud_add_amount = 1 - mud_add_amount;
+ addnode = n_gravel;
+ } else if (bt != BT_DESERT && getHaveBeach(index) &&
+ surface_y + mud_add_amount <= water_level + 2) {
+ addnode = n_sand;
+ }
+
+ if ((bt == BT_DESERT || bt == BT_TUNDRA) && surface_y > 20)
+ mud_add_amount = MYMAX(0, mud_add_amount - (surface_y - 20) / 5);
+
+ /* If topmost node is grass, change it to mud. It might be if it was
+ // flown to there from a neighboring chunk and then converted.
+ u32 i = vm->m_area.index(x, surface_y, z);
+ if (vm->m_data[i].getContent() == c_dirt_with_grass)
+ vm->m_data[i] = n_dirt;*/
+
+ // Add mud on ground
+ s16 mudcount = 0;
+ const v3s16 &em = vm->m_area.getExtent();
+ s16 y_start = surface_y + 1;
+ u32 i = vm->m_area.index(x, y_start, z);
+ for (s16 y = y_start; y <= node_max.Y; y++) {
+ if (mudcount >= mud_add_amount)
+ break;
+
+ vm->m_data[i] = addnode;
+ mudcount++;
+
+ vm->m_area.add_y(em, i, 1);
+ }
+ }
+}
+
+
+void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos)
+{
+ // 340ms @cs=8
+ //TimeTaker timer1("flow mud");
+
+ // Iterate a few times
+ for (s16 k = 0; k < 3; k++) {
+ for (s16 z = mudflow_minpos; z <= mudflow_maxpos; z++)
+ for (s16 x = mudflow_minpos; x <= mudflow_maxpos; x++) {
+ // Invert coordinates every 2nd iteration
+ if (k % 2 == 0) {
+ x = mudflow_maxpos - (x - mudflow_minpos);
+ z = mudflow_maxpos - (z - mudflow_minpos);
+ }
+
+ // Node position in 2d
+ v2s16 p2d = v2s16(node_min.X, node_min.Z) + v2s16(x, z);
+
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 i = vm->m_area.index(p2d.X, node_max.Y, p2d.Y);
+ s16 y = node_max.Y;
+
+ while (y >= node_min.Y) {
+
+ for (;; y--) {
+ MapNode *n = NULL;
+ // Find mud
+ for (; y >= node_min.Y; y--) {
+ n = &vm->m_data[i];
+ if (n->getContent() == c_dirt ||
+ n->getContent() == c_dirt_with_grass ||
+ n->getContent() == c_gravel)
+ break;
+
+ vm->m_area.add_y(em, i, -1);
+ }
+
+ // Stop if out of area
+ //if(vmanip.m_area.contains(i) == false)
+ if (y < node_min.Y)
+ break;
+
+ if (n->getContent() == c_dirt ||
+ n->getContent() == c_dirt_with_grass) {
+ // Make it exactly mud
+ n->setContent(c_dirt);
+
+ // Don't flow it if the stuff under it is not mud
+ {
+ u32 i2 = i;
+ vm->m_area.add_y(em, i2, -1);
+ // Cancel if out of area
+ if (!vm->m_area.contains(i2))
+ continue;
+ MapNode *n2 = &vm->m_data[i2];
+ if (n2->getContent() != c_dirt &&
+ n2->getContent() != c_dirt_with_grass)
+ continue;
+ }
+ }
+
+ v3s16 dirs4[4] = {
+ v3s16(0, 0, 1), // back
+ v3s16(1, 0, 0), // right
+ v3s16(0, 0, -1), // front
+ v3s16(-1, 0, 0), // left
+ };
+
+ // Check that upper is walkable. Cancel
+ // dropping if upper keeps it in place.
+ u32 i3 = i;
+ vm->m_area.add_y(em, i3, 1);
+ MapNode *n3 = NULL;
+
+ if (vm->m_area.contains(i3)) {
+ n3 = &vm->m_data[i3];
+ if (ndef->get(*n3).walkable)
+ continue;
+ }
+
+ // Drop mud on side
+ for (const v3s16 &dirp : dirs4) {
+ u32 i2 = i;
+ // Move to side
+ vm->m_area.add_p(em, i2, dirp);
+ // Fail if out of area
+ if (!vm->m_area.contains(i2))
+ continue;
+ // Check that side is air
+ MapNode *n2 = &vm->m_data[i2];
+ if (ndef->get(*n2).walkable)
+ continue;
+ // Check that under side is air
+ vm->m_area.add_y(em, i2, -1);
+ if (!vm->m_area.contains(i2))
+ continue;
+ n2 = &vm->m_data[i2];
+ if (ndef->get(*n2).walkable)
+ continue;
+ // Loop further down until not air
+ bool dropped_to_unknown = false;
+ do {
+ vm->m_area.add_y(em, i2, -1);
+ n2 = &vm->m_data[i2];
+ // if out of known area
+ if (!vm->m_area.contains(i2) ||
+ n2->getContent() == CONTENT_IGNORE) {
+ dropped_to_unknown = true;
+ break;
+ }
+ } while (!ndef->get(*n2).walkable);
+ // Loop one up so that we're in air
+ vm->m_area.add_y(em, i2, 1);
+
+ // Move mud to new place. Outside mapchunk remove
+ // any decorations above removed or placed mud.
+ if (!dropped_to_unknown)
+ moveMud(i, i2, i3, p2d, em);
+
+ // Done
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+
+void MapgenV6::moveMud(u32 remove_index, u32 place_index,
+ u32 above_remove_index, v2s16 pos, v3s16 em)
+{
+ MapNode n_air(CONTENT_AIR);
+ // Copy mud from old place to new place
+ vm->m_data[place_index] = vm->m_data[remove_index];
+ // Set old place to be air
+ vm->m_data[remove_index] = n_air;
+ // Outside the mapchunk decorations may need to be removed if above removed
+ // mud or if half-buried in placed mud. Placed mud is to the side of pos so
+ // use 'pos.X >= node_max.X' etc.
+ if (pos.X >= node_max.X || pos.X <= node_min.X ||
+ pos.Y >= node_max.Z || pos.Y <= node_min.Z) {
+ // 'above remove' node is above removed mud. If it is not air, water or
+ // 'ignore' it is a decoration that needs removing. Also search upwards
+ // to remove a possible stacked decoration.
+ // Check for 'ignore' because stacked decorations can penetrate into
+ // 'ignore' nodes above the mapchunk.
+ while (vm->m_area.contains(above_remove_index) &&
+ vm->m_data[above_remove_index].getContent() != CONTENT_AIR &&
+ vm->m_data[above_remove_index].getContent() != c_water_source &&
+ vm->m_data[above_remove_index].getContent() != CONTENT_IGNORE) {
+ vm->m_data[above_remove_index] = n_air;
+ vm->m_area.add_y(em, above_remove_index, 1);
+ }
+ // Mud placed may have partially-buried a stacked decoration, search
+ // above and remove.
+ vm->m_area.add_y(em, place_index, 1);
+ while (vm->m_area.contains(place_index) &&
+ vm->m_data[place_index].getContent() != CONTENT_AIR &&
+ vm->m_data[place_index].getContent() != c_water_source &&
+ vm->m_data[place_index].getContent() != CONTENT_IGNORE) {
+ vm->m_data[place_index] = n_air;
+ vm->m_area.add_y(em, place_index, 1);
+ }
+ }
+}
+
+
+void MapgenV6::placeTreesAndJungleGrass()
+{
+ //TimeTaker t("placeTrees");
+ if (node_max.Y < water_level)
+ return;
+
+ PseudoRandom grassrandom(blockseed + 53);
+ content_t c_junglegrass = ndef->getId("mapgen_junglegrass");
+ // if we don't have junglegrass, don't place cignore... that's bad
+ if (c_junglegrass == CONTENT_IGNORE)
+ c_junglegrass = CONTENT_AIR;
+ MapNode n_junglegrass(c_junglegrass);
+ const v3s16 &em = vm->m_area.getExtent();
+
+ // Divide area into parts
+ s16 div = 8;
+ s16 sidelen = central_area_size.X / div;
+ double area = sidelen * sidelen;
+
+ // N.B. We must add jungle grass first, since tree leaves will
+ // obstruct the ground, giving us a false ground level
+ for (s16 z0 = 0; z0 < div; z0++)
+ for (s16 x0 = 0; x0 < div; x0++) {
+ // Center position of part of division
+ v2s16 p2d_center(
+ node_min.X + sidelen / 2 + sidelen * x0,
+ node_min.Z + sidelen / 2 + sidelen * z0
+ );
+ // Minimum edge of part of division
+ v2s16 p2d_min(
+ node_min.X + sidelen * x0,
+ node_min.Z + sidelen * z0
+ );
+ // Maximum edge of part of division
+ v2s16 p2d_max(
+ node_min.X + sidelen + sidelen * x0 - 1,
+ node_min.Z + sidelen + sidelen * z0 - 1
+ );
+
+ // Get biome at center position of part of division
+ BiomeV6Type bt = getBiome(p2d_center);
+
+ // Amount of trees
+ u32 tree_count;
+ if (bt == BT_JUNGLE || bt == BT_TAIGA || bt == BT_NORMAL) {
+ tree_count = area * getTreeAmount(p2d_center);
+ if (bt == BT_JUNGLE)
+ tree_count *= 4;
+ } else {
+ tree_count = 0;
+ }
+
+ // Add jungle grass
+ if (bt == BT_JUNGLE) {
+ float humidity = getHumidity(p2d_center);
+ u32 grass_count = 5 * humidity * tree_count;
+ for (u32 i = 0; i < grass_count; i++) {
+ s16 x = grassrandom.range(p2d_min.X, p2d_max.X);
+ s16 z = grassrandom.range(p2d_min.Y, p2d_max.Y);
+ int mapindex = central_area_size.X * (z - node_min.Z)
+ + (x - node_min.X);
+ s16 y = heightmap[mapindex];
+ if (y < water_level)
+ continue;
+
+ u32 vi = vm->m_area.index(x, y, z);
+ // place on dirt_with_grass, since we know it is exposed to sunlight
+ if (vm->m_data[vi].getContent() == c_dirt_with_grass) {
+ vm->m_area.add_y(em, vi, 1);
+ vm->m_data[vi] = n_junglegrass;
+ }
+ }
+ }
+
+ // Put trees in random places on part of division
+ for (u32 i = 0; i < tree_count; i++) {
+ s16 x = myrand_range(p2d_min.X, p2d_max.X);
+ s16 z = myrand_range(p2d_min.Y, p2d_max.Y);
+ int mapindex = central_area_size.X * (z - node_min.Z)
+ + (x - node_min.X);
+ s16 y = heightmap[mapindex];
+ // Don't make a tree under water level
+ // Don't make a tree so high that it doesn't fit
+ if (y < water_level || y > node_max.Y - 6)
+ continue;
+
+ v3s16 p(x, y, z);
+ // Trees grow only on mud and grass
+ {
+ u32 i = vm->m_area.index(p);
+ content_t c = vm->m_data[i].getContent();
+ if (c != c_dirt &&
+ c != c_dirt_with_grass &&
+ c != c_dirt_with_snow)
+ continue;
+ }
+ p.Y++;
+
+ // Make a tree
+ if (bt == BT_JUNGLE) {
+ treegen::make_jungletree(*vm, p, ndef, myrand());
+ } else if (bt == BT_TAIGA) {
+ treegen::make_pine_tree(*vm, p - v3s16(0, 1, 0), ndef, myrand());
+ } else if (bt == BT_NORMAL) {
+ bool is_apple_tree = (myrand_range(0, 3) == 0) &&
+ getHaveAppleTree(v2s16(x, z));
+ treegen::make_tree(*vm, p, is_apple_tree, ndef, myrand());
+ }
+ }
+ }
+ //printf("placeTreesAndJungleGrass: %dms\n", t.stop());
+}
+
+
+void MapgenV6::growGrass() // Add surface nodes
+{
+ MapNode n_dirt_with_grass(c_dirt_with_grass);
+ MapNode n_dirt_with_snow(c_dirt_with_snow);
+ MapNode n_snowblock(c_snowblock);
+ MapNode n_snow(c_snow);
+ const v3s16 &em = vm->m_area.getExtent();
+
+ u32 index = 0;
+ for (s16 z = full_node_min.Z; z <= full_node_max.Z; z++)
+ for (s16 x = full_node_min.X; x <= full_node_max.X; x++, index++) {
+ // Find the lowest surface to which enough light ends up to make
+ // grass grow. Basically just wait until not air and not leaves.
+ s16 surface_y = 0;
+ {
+ u32 i = vm->m_area.index(x, node_max.Y, z);
+ s16 y;
+ // Go to ground level
+ for (y = node_max.Y; y >= full_node_min.Y; y--) {
+ MapNode &n = vm->m_data[i];
+ if (ndef->get(n).param_type != CPT_LIGHT ||
+ ndef->get(n).liquid_type != LIQUID_NONE ||
+ n.getContent() == c_ice)
+ break;
+ vm->m_area.add_y(em, i, -1);
+ }
+ surface_y = (y >= full_node_min.Y) ? y : full_node_min.Y;
+ }
+
+ BiomeV6Type bt = getBiome(index, v2s16(x, z));
+ u32 i = vm->m_area.index(x, surface_y, z);
+ content_t c = vm->m_data[i].getContent();
+ if (surface_y >= water_level - 20) {
+ if (bt == BT_TAIGA && c == c_dirt) {
+ vm->m_data[i] = n_dirt_with_snow;
+ } else if (bt == BT_TUNDRA) {
+ if (c == c_dirt) {
+ vm->m_data[i] = n_snowblock;
+ vm->m_area.add_y(em, i, -1);
+ vm->m_data[i] = n_dirt_with_snow;
+ } else if (c == c_stone && surface_y < node_max.Y) {
+ vm->m_area.add_y(em, i, 1);
+ vm->m_data[i] = n_snowblock;
+ }
+ } else if (c == c_dirt) {
+ vm->m_data[i] = n_dirt_with_grass;
+ }
+ }
+ }
+}
+
+
+void MapgenV6::generateCaves(int max_stone_y)
+{
+ float cave_amount = NoisePerlin2D(np_cave, node_min.X, node_min.Y, seed);
+ int volume_nodes = (node_max.X - node_min.X + 1) *
+ (node_max.Y - node_min.Y + 1) * MAP_BLOCKSIZE;
+ cave_amount = MYMAX(0.0, cave_amount);
+ u32 caves_count = cave_amount * volume_nodes / 50000;
+ u32 bruises_count = 1;
+ PseudoRandom ps(blockseed + 21343);
+ PseudoRandom ps2(blockseed + 1032);
+
+ if (ps.range(1, 6) == 1)
+ bruises_count = ps.range(0, ps.range(0, 2));
+
+ if (getBiome(v2s16(node_min.X, node_min.Z)) == BT_DESERT) {
+ caves_count /= 3;
+ bruises_count /= 3;
+ }
+
+ for (u32 i = 0; i < caves_count + bruises_count; i++) {
+ CavesV6 cave(ndef, &gennotify, water_level, c_water_source, c_lava_source);
+
+ bool large_cave = (i >= caves_count);
+ cave.makeCave(vm, node_min, node_max, &ps, &ps2,
+ large_cave, max_stone_y, heightmap);
+ }
+}
diff --git a/src/mapgen/mapgen_v6.h b/src/mapgen/mapgen_v6.h
new file mode 100644
index 000000000..9c8794217
--- /dev/null
+++ b/src/mapgen/mapgen_v6.h
@@ -0,0 +1,169 @@
+/*
+Minetest
+Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2014-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "mapgen.h"
+#include "noise.h"
+
+#define MGV6_AVERAGE_MUD_AMOUNT 4
+#define MGV6_DESERT_STONE_BASE -32
+#define MGV6_ICE_BASE 0
+#define MGV6_FREQ_HOT 0.4
+#define MGV6_FREQ_SNOW -0.4
+#define MGV6_FREQ_TAIGA 0.5
+#define MGV6_FREQ_JUNGLE 0.5
+
+//////////// Mapgen V6 flags
+#define MGV6_JUNGLES 0x01
+#define MGV6_BIOMEBLEND 0x02
+#define MGV6_MUDFLOW 0x04
+#define MGV6_SNOWBIOMES 0x08
+#define MGV6_FLAT 0x10
+#define MGV6_TREES 0x20
+
+
+extern FlagDesc flagdesc_mapgen_v6[];
+
+
+enum BiomeV6Type
+{
+ BT_NORMAL,
+ BT_DESERT,
+ BT_JUNGLE,
+ BT_TUNDRA,
+ BT_TAIGA,
+};
+
+
+struct MapgenV6Params : public MapgenParams {
+ u32 spflags = MGV6_JUNGLES | MGV6_SNOWBIOMES | MGV6_TREES |
+ MGV6_BIOMEBLEND | MGV6_MUDFLOW;
+ float freq_desert = 0.45f;
+ float freq_beach = 0.15f;
+ NoiseParams np_terrain_base;
+ NoiseParams np_terrain_higher;
+ NoiseParams np_steepness;
+ NoiseParams np_height_select;
+ NoiseParams np_mud;
+ NoiseParams np_beach;
+ NoiseParams np_biome;
+ NoiseParams np_cave;
+ NoiseParams np_humidity;
+ NoiseParams np_trees;
+ NoiseParams np_apple_trees;
+
+ MapgenV6Params();
+ ~MapgenV6Params() = default;
+
+ void readParams(const Settings *settings);
+ void writeParams(Settings *settings) const;
+};
+
+
+class MapgenV6 : public Mapgen {
+public:
+ EmergeManager *m_emerge;
+
+ int ystride;
+ u32 spflags;
+
+ v3s16 node_min;
+ v3s16 node_max;
+ v3s16 full_node_min;
+ v3s16 full_node_max;
+ v3s16 central_area_size;
+
+ Noise *noise_terrain_base;
+ Noise *noise_terrain_higher;
+ Noise *noise_steepness;
+ Noise *noise_height_select;
+ Noise *noise_mud;
+ Noise *noise_beach;
+ Noise *noise_biome;
+ Noise *noise_humidity;
+ NoiseParams *np_cave;
+ NoiseParams *np_humidity;
+ NoiseParams *np_trees;
+ NoiseParams *np_apple_trees;
+ float freq_desert;
+ float freq_beach;
+
+ content_t c_stone;
+ content_t c_dirt;
+ content_t c_dirt_with_grass;
+ content_t c_sand;
+ content_t c_water_source;
+ content_t c_lava_source;
+ content_t c_gravel;
+ content_t c_desert_stone;
+ content_t c_desert_sand;
+ content_t c_dirt_with_snow;
+ content_t c_snow;
+ content_t c_snowblock;
+ content_t c_ice;
+
+ content_t c_cobble;
+ content_t c_mossycobble;
+ content_t c_stair_cobble;
+ content_t c_stair_desert_stone;
+
+ MapgenV6(int mapgenid, MapgenV6Params *params, EmergeManager *emerge);
+ ~MapgenV6();
+
+ virtual MapgenType getType() const { return MAPGEN_V6; }
+
+ void makeChunk(BlockMakeData *data);
+ int getGroundLevelAtPoint(v2s16 p);
+ int getSpawnLevelAtPoint(v2s16 p);
+
+ float baseTerrainLevel(float terrain_base, float terrain_higher,
+ float steepness, float height_select);
+ virtual float baseTerrainLevelFromNoise(v2s16 p);
+ virtual float baseTerrainLevelFromMap(v2s16 p);
+ virtual float baseTerrainLevelFromMap(int index);
+
+ s16 find_stone_level(v2s16 p2d);
+ bool block_is_underground(u64 seed, v3s16 blockpos);
+ s16 find_ground_level_from_noise(u64 seed, v2s16 p2d, s16 precision);
+
+ float getHumidity(v2s16 p);
+ float getTreeAmount(v2s16 p);
+ bool getHaveAppleTree(v2s16 p);
+ float getMudAmount(v2s16 p);
+ virtual float getMudAmount(int index);
+ bool getHaveBeach(v2s16 p);
+ bool getHaveBeach(int index);
+ BiomeV6Type getBiome(v2s16 p);
+ BiomeV6Type getBiome(int index, v2s16 p);
+
+ u32 get_blockseed(u64 seed, v3s16 p);
+
+ virtual void calculateNoise();
+ int generateGround();
+ void addMud();
+ void flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos);
+ void moveMud(u32 remove_index, u32 place_index,
+ u32 above_remove_index, v2s16 pos, v3s16 em);
+ void growGrass();
+ void placeTreesAndJungleGrass();
+ virtual void generateCaves(int max_stone_y);
+};
diff --git a/src/mapgen/mapgen_v7.cpp b/src/mapgen/mapgen_v7.cpp
new file mode 100644
index 000000000..404780158
--- /dev/null
+++ b/src/mapgen/mapgen_v7.cpp
@@ -0,0 +1,743 @@
+/*
+Minetest
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2014-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "content_sao.h"
+#include "nodedef.h"
+#include "voxelalgorithms.h"
+//#include "profiler.h" // For TimeTaker
+#include "settings.h" // For g_settings
+#include "emerge.h"
+#include "dungeongen.h"
+#include "cavegen.h"
+#include "mg_biome.h"
+#include "mg_ore.h"
+#include "mg_decoration.h"
+#include "mapgen_v7.h"
+
+
+FlagDesc flagdesc_mapgen_v7[] = {
+ {"mountains", MGV7_MOUNTAINS},
+ {"ridges", MGV7_RIDGES},
+ {"floatlands", MGV7_FLOATLANDS},
+ {"caverns", MGV7_CAVERNS},
+ {NULL, 0}
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+MapgenV7::MapgenV7(int mapgenid, MapgenV7Params *params, EmergeManager *emerge)
+ : MapgenBasic(mapgenid, params, emerge)
+{
+ spflags = params->spflags;
+ mount_zero_level = params->mount_zero_level;
+ cave_width = params->cave_width;
+ large_cave_depth = params->large_cave_depth;
+ lava_depth = params->lava_depth;
+ float_mount_density = params->float_mount_density;
+ floatland_level = params->floatland_level;
+ shadow_limit = params->shadow_limit;
+ cavern_limit = params->cavern_limit;
+ cavern_taper = params->cavern_taper;
+ cavern_threshold = params->cavern_threshold;
+
+ // This is to avoid a divide-by-zero.
+ // Parameter will be saved to map_meta.txt in limited form.
+ params->float_mount_height = MYMAX(params->float_mount_height, 1.0f);
+ float_mount_height = params->float_mount_height;
+
+ // 2D noise
+ noise_terrain_base = new Noise(&params->np_terrain_base, seed, csize.X, csize.Z);
+ noise_terrain_alt = new Noise(&params->np_terrain_alt, seed, csize.X, csize.Z);
+ noise_terrain_persist = new Noise(&params->np_terrain_persist, seed, csize.X, csize.Z);
+ noise_height_select = new Noise(&params->np_height_select, seed, csize.X, csize.Z);
+ noise_filler_depth = new Noise(&params->np_filler_depth, seed, csize.X, csize.Z);
+
+ if (spflags & MGV7_MOUNTAINS)
+ noise_mount_height = new Noise(&params->np_mount_height, seed, csize.X, csize.Z);
+
+ if (spflags & MGV7_FLOATLANDS) {
+ noise_floatland_base = new Noise(&params->np_floatland_base, seed, csize.X, csize.Z);
+ noise_float_base_height = new Noise(&params->np_float_base_height, seed, csize.X, csize.Z);
+ }
+
+ if (spflags & MGV7_RIDGES) {
+ noise_ridge_uwater = new Noise(&params->np_ridge_uwater, seed, csize.X, csize.Z);
+ // 3D noise, 1-up 1-down overgeneration
+ noise_ridge = new Noise(&params->np_ridge, seed, csize.X, csize.Y + 2, csize.Z);
+ }
+ // 3D noise, 1 up, 1 down overgeneration
+ if ((spflags & MGV7_MOUNTAINS) || (spflags & MGV7_FLOATLANDS))
+ noise_mountain = new Noise(&params->np_mountain, seed, csize.X, csize.Y + 2, csize.Z);
+ // 3D noise, 1 down overgeneration
+ MapgenBasic::np_cave1 = params->np_cave1;
+ MapgenBasic::np_cave2 = params->np_cave2;
+ MapgenBasic::np_cavern = params->np_cavern;
+}
+
+
+MapgenV7::~MapgenV7()
+{
+ delete noise_terrain_base;
+ delete noise_terrain_alt;
+ delete noise_terrain_persist;
+ delete noise_height_select;
+ delete noise_filler_depth;
+
+ if (spflags & MGV7_MOUNTAINS)
+ delete noise_mount_height;
+
+ if (spflags & MGV7_FLOATLANDS) {
+ delete noise_floatland_base;
+ delete noise_float_base_height;
+ }
+
+ if (spflags & MGV7_RIDGES) {
+ delete noise_ridge_uwater;
+ delete noise_ridge;
+ }
+
+ if ((spflags & MGV7_MOUNTAINS) || (spflags & MGV7_FLOATLANDS))
+ delete noise_mountain;
+}
+
+
+MapgenV7Params::MapgenV7Params():
+ np_terrain_base (4, 70, v3f(600, 600, 600), 82341, 5, 0.6, 2.0),
+ np_terrain_alt (4, 25, v3f(600, 600, 600), 5934, 5, 0.6, 2.0),
+ np_terrain_persist (0.6, 0.1, v3f(2000, 2000, 2000), 539, 3, 0.6, 2.0),
+ np_height_select (-8, 16, v3f(500, 500, 500), 4213, 6, 0.7, 2.0),
+ np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0),
+ np_mount_height (256, 112, v3f(1000, 1000, 1000), 72449, 3, 0.6, 2.0),
+ np_ridge_uwater (0, 1, v3f(1000, 1000, 1000), 85039, 5, 0.6, 2.0),
+ np_floatland_base (-0.6, 1.5, v3f(600, 600, 600), 114, 5, 0.6, 2.0),
+ np_float_base_height (48, 24, v3f(300, 300, 300), 907, 4, 0.7, 2.0),
+ np_mountain (-0.6, 1, v3f(250, 350, 250), 5333, 5, 0.63, 2.0),
+ np_ridge (0, 1, v3f(100, 100, 100), 6467, 4, 0.75, 2.0),
+ np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0),
+ np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
+ np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0)
+{
+}
+
+
+void MapgenV7Params::readParams(const Settings *settings)
+{
+ settings->getFlagStrNoEx("mgv7_spflags", spflags, flagdesc_mapgen_v7);
+ settings->getS16NoEx("mgv7_mount_zero_level", mount_zero_level);
+ settings->getFloatNoEx("mgv7_cave_width", cave_width);
+ settings->getS16NoEx("mgv7_large_cave_depth", large_cave_depth);
+ settings->getS16NoEx("mgv7_lava_depth", lava_depth);
+ settings->getFloatNoEx("mgv7_float_mount_density", float_mount_density);
+ settings->getFloatNoEx("mgv7_float_mount_height", float_mount_height);
+ settings->getS16NoEx("mgv7_floatland_level", floatland_level);
+ settings->getS16NoEx("mgv7_shadow_limit", shadow_limit);
+ settings->getS16NoEx("mgv7_cavern_limit", cavern_limit);
+ settings->getS16NoEx("mgv7_cavern_taper", cavern_taper);
+ settings->getFloatNoEx("mgv7_cavern_threshold", cavern_threshold);
+
+ settings->getNoiseParams("mgv7_np_terrain_base", np_terrain_base);
+ settings->getNoiseParams("mgv7_np_terrain_alt", np_terrain_alt);
+ settings->getNoiseParams("mgv7_np_terrain_persist", np_terrain_persist);
+ settings->getNoiseParams("mgv7_np_height_select", np_height_select);
+ settings->getNoiseParams("mgv7_np_filler_depth", np_filler_depth);
+ settings->getNoiseParams("mgv7_np_mount_height", np_mount_height);
+ settings->getNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater);
+ settings->getNoiseParams("mgv7_np_floatland_base", np_floatland_base);
+ settings->getNoiseParams("mgv7_np_float_base_height", np_float_base_height);
+ settings->getNoiseParams("mgv7_np_mountain", np_mountain);
+ settings->getNoiseParams("mgv7_np_ridge", np_ridge);
+ settings->getNoiseParams("mgv7_np_cavern", np_cavern);
+ settings->getNoiseParams("mgv7_np_cave1", np_cave1);
+ settings->getNoiseParams("mgv7_np_cave2", np_cave2);
+}
+
+
+void MapgenV7Params::writeParams(Settings *settings) const
+{
+ settings->setFlagStr("mgv7_spflags", spflags, flagdesc_mapgen_v7, U32_MAX);
+ settings->setS16("mgv7_mount_zero_level", mount_zero_level);
+ settings->setFloat("mgv7_cave_width", cave_width);
+ settings->setS16("mgv7_large_cave_depth", large_cave_depth);
+ settings->setS16("mgv7_lava_depth", lava_depth);
+ settings->setFloat("mgv7_float_mount_density", float_mount_density);
+ settings->setFloat("mgv7_float_mount_height", float_mount_height);
+ settings->setS16("mgv7_floatland_level", floatland_level);
+ settings->setS16("mgv7_shadow_limit", shadow_limit);
+ settings->setS16("mgv7_cavern_limit", cavern_limit);
+ settings->setS16("mgv7_cavern_taper", cavern_taper);
+ settings->setFloat("mgv7_cavern_threshold", cavern_threshold);
+
+ settings->setNoiseParams("mgv7_np_terrain_base", np_terrain_base);
+ settings->setNoiseParams("mgv7_np_terrain_alt", np_terrain_alt);
+ settings->setNoiseParams("mgv7_np_terrain_persist", np_terrain_persist);
+ settings->setNoiseParams("mgv7_np_height_select", np_height_select);
+ settings->setNoiseParams("mgv7_np_filler_depth", np_filler_depth);
+ settings->setNoiseParams("mgv7_np_mount_height", np_mount_height);
+ settings->setNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater);
+ settings->setNoiseParams("mgv7_np_floatland_base", np_floatland_base);
+ settings->setNoiseParams("mgv7_np_float_base_height", np_float_base_height);
+ settings->setNoiseParams("mgv7_np_mountain", np_mountain);
+ settings->setNoiseParams("mgv7_np_ridge", np_ridge);
+ settings->setNoiseParams("mgv7_np_cavern", np_cavern);
+ settings->setNoiseParams("mgv7_np_cave1", np_cave1);
+ settings->setNoiseParams("mgv7_np_cave2", np_cave2);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+int MapgenV7::getSpawnLevelAtPoint(v2s16 p)
+{
+ // If rivers are enabled, first check if in a river
+ if (spflags & MGV7_RIDGES) {
+ float width = 0.2;
+ float uwatern = NoisePerlin2D(&noise_ridge_uwater->np, p.X, p.Y, seed) * 2;
+ if (fabs(uwatern) <= width)
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+ }
+
+ // Terrain noise 'offset' is the average level of that terrain.
+ // At least 50% of terrain will be below the higher of base and alt terrain
+ // 'offset's.
+ // Raising the maximum spawn level above 'water_level + 16' is necessary
+ // for when terrain 'offset's are set much higher than water_level.
+ s16 max_spawn_y = MYMAX(MYMAX(noise_terrain_alt->np.offset,
+ noise_terrain_base->np.offset),
+ water_level + 16);
+ // Base terrain calculation
+ s16 y = baseTerrainLevelAtPoint(p.X, p.Y);
+
+ // If mountains are disabled, terrain level is base terrain level.
+ // Avoids mid-air spawn where mountain terrain would have been.
+ if (!(spflags & MGV7_MOUNTAINS)) {
+ if (y < water_level || y > max_spawn_y)
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+
+ // y + 2 because y is surface level and due to biome 'dust'
+ return y + 2;
+ }
+
+ // Search upwards for first node without mountain terrain
+ int iters = 256;
+ while (iters > 0 && y <= max_spawn_y) {
+ if (!getMountainTerrainAtPoint(p.X, y + 1, p.Y)) {
+ if (y <= water_level || y > max_spawn_y)
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+
+ // y + 1 due to biome 'dust'
+ return y + 1;
+ }
+ y++;
+ iters--;
+ }
+
+ // Unsuitable spawn point
+ return MAX_MAP_GENERATION_LIMIT;
+}
+
+
+void MapgenV7::makeChunk(BlockMakeData *data)
+{
+ // Pre-conditions
+ assert(data->vmanip);
+ assert(data->nodedef);
+ assert(data->blockpos_requested.X >= data->blockpos_min.X &&
+ data->blockpos_requested.Y >= data->blockpos_min.Y &&
+ data->blockpos_requested.Z >= data->blockpos_min.Z);
+ assert(data->blockpos_requested.X <= data->blockpos_max.X &&
+ data->blockpos_requested.Y <= data->blockpos_max.Y &&
+ data->blockpos_requested.Z <= data->blockpos_max.Z);
+
+ this->generating = true;
+ this->vm = data->vmanip;
+ this->ndef = data->nodedef;
+ //TimeTaker t("makeChunk");
+
+ v3s16 blockpos_min = data->blockpos_min;
+ v3s16 blockpos_max = data->blockpos_max;
+ node_min = blockpos_min * MAP_BLOCKSIZE;
+ node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+ full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
+ full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ blockseed = getBlockSeed2(full_node_min, seed);
+
+ // Generate base and mountain terrain
+ // An initial heightmap is no longer created here for use in generateRidgeTerrain()
+ s16 stone_surface_max_y = generateTerrain();
+
+ // Generate rivers
+ if (spflags & MGV7_RIDGES)
+ generateRidgeTerrain();
+
+ // Create heightmap
+ updateHeightmap(node_min, node_max);
+
+ // Init biome generator, place biome-specific nodes, and build biomemap
+ biomegen->calcBiomeNoise(node_min);
+
+ MgStoneType mgstone_type;
+ content_t biome_stone;
+ generateBiomes(&mgstone_type, &biome_stone);
+
+ // Generate caverns, tunnels and classic caves
+ if (flags & MG_CAVES) {
+ bool near_cavern = false;
+ // Generate caverns
+ if (spflags & MGV7_CAVERNS)
+ near_cavern = generateCaverns(stone_surface_max_y);
+ // Generate tunnels and classic caves
+ if (near_cavern)
+ // Disable classic caves in this mapchunk by setting
+ // 'large cave depth' to world base. Avoids excessive liquid in
+ // large caverns and floating blobs of overgenerated liquid.
+ generateCaves(stone_surface_max_y, -MAX_MAP_GENERATION_LIMIT);
+ else
+ generateCaves(stone_surface_max_y, large_cave_depth);
+ }
+
+ // Generate dungeons
+ if (flags & MG_DUNGEONS)
+ generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
+
+ // Generate the registered decorations
+ if (flags & MG_DECORATIONS)
+ m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
+
+ // Generate the registered ores
+ m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
+
+ // Sprinkle some dust on top after everything else was generated
+ dustTopNodes();
+
+ //printf("makeChunk: %dms\n", t.stop());
+
+ // Update liquids
+ updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
+
+ // Calculate lighting
+ // Limit floatland shadow
+ bool propagate_shadow = !((spflags & MGV7_FLOATLANDS) &&
+ node_min.Y <= shadow_limit && node_max.Y >= shadow_limit);
+
+ if (flags & MG_LIGHT)
+ calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
+ full_node_min, full_node_max, propagate_shadow);
+
+ //setLighting(node_min - v3s16(1, 0, 1) * MAP_BLOCKSIZE,
+ // node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, 0xFF);
+
+ this->generating = false;
+}
+
+
+float MapgenV7::baseTerrainLevelAtPoint(s16 x, s16 z)
+{
+ float hselect = NoisePerlin2D(&noise_height_select->np, x, z, seed);
+ hselect = rangelim(hselect, 0.0, 1.0);
+
+ float persist = NoisePerlin2D(&noise_terrain_persist->np, x, z, seed);
+
+ noise_terrain_base->np.persist = persist;
+ float height_base = NoisePerlin2D(&noise_terrain_base->np, x, z, seed);
+
+ noise_terrain_alt->np.persist = persist;
+ float height_alt = NoisePerlin2D(&noise_terrain_alt->np, x, z, seed);
+
+ if (height_alt > height_base)
+ return height_alt;
+
+ return (height_base * hselect) + (height_alt * (1.0 - hselect));
+}
+
+
+float MapgenV7::baseTerrainLevelFromMap(int index)
+{
+ float hselect = rangelim(noise_height_select->result[index], 0.0, 1.0);
+ float height_base = noise_terrain_base->result[index];
+ float height_alt = noise_terrain_alt->result[index];
+
+ if (height_alt > height_base)
+ return height_alt;
+
+ return (height_base * hselect) + (height_alt * (1.0 - hselect));
+}
+
+
+bool MapgenV7::getMountainTerrainAtPoint(s16 x, s16 y, s16 z)
+{
+ float mnt_h_n =
+ MYMAX(NoisePerlin2D(&noise_mount_height->np, x, z, seed), 1.0f);
+ float density_gradient = -((float)(y - mount_zero_level) / mnt_h_n);
+ float mnt_n = NoisePerlin3D(&noise_mountain->np, x, y, z, seed);
+
+ return mnt_n + density_gradient >= 0.0;
+}
+
+
+bool MapgenV7::getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y)
+{
+ float mounthn = MYMAX(noise_mount_height->result[idx_xz], 1.0f);
+ float density_gradient = -((float)(y - mount_zero_level) / mounthn);
+ float mountn = noise_mountain->result[idx_xyz];
+
+ return mountn + density_gradient >= 0.0;
+}
+
+
+bool MapgenV7::getFloatlandMountainFromMap(int idx_xyz, int idx_xz, s16 y)
+{
+ // Make rim 2 nodes thick to match floatland base terrain
+ float density_gradient = (y >= floatland_level) ?
+ -pow((float)(y - floatland_level) / float_mount_height, 0.75f) :
+ -pow((float)(floatland_level - 1 - y) / float_mount_height, 0.75f);
+
+ float floatn = noise_mountain->result[idx_xyz] + float_mount_density;
+
+ return floatn + density_gradient >= 0.0f;
+}
+
+
+void MapgenV7::floatBaseExtentFromMap(s16 *float_base_min, s16 *float_base_max, int idx_xz)
+{
+ // '+1' to avoid a layer of stone at y = MAX_MAP_GENERATION_LIMIT
+ s16 base_min = MAX_MAP_GENERATION_LIMIT + 1;
+ s16 base_max = MAX_MAP_GENERATION_LIMIT;
+
+ float n_base = noise_floatland_base->result[idx_xz];
+ if (n_base > 0.0f) {
+ float n_base_height =
+ MYMAX(noise_float_base_height->result[idx_xz], 1.0f);
+ float amp = n_base * n_base_height;
+ float ridge = n_base_height / 3.0f;
+ base_min = floatland_level - amp / 1.5f;
+
+ if (amp > ridge * 2.0f) {
+ // Lake bed
+ base_max = floatland_level - (amp - ridge * 2.0f) / 2.0f;
+ } else {
+ // Hills and ridges
+ float diff = fabs(amp - ridge) / ridge;
+ // Smooth ridges using the 'smoothstep function'
+ float smooth_diff = diff * diff * (3.0f - 2.0f * diff);
+ base_max = floatland_level + ridge - smooth_diff * ridge;
+ }
+ }
+
+ *float_base_min = base_min;
+ *float_base_max = base_max;
+}
+
+
+int MapgenV7::generateTerrain()
+{
+ MapNode n_air(CONTENT_AIR);
+ MapNode n_stone(c_stone);
+ MapNode n_water(c_water_source);
+
+ //// Calculate noise for terrain generation
+ noise_terrain_persist->perlinMap2D(node_min.X, node_min.Z);
+ float *persistmap = noise_terrain_persist->result;
+
+ noise_terrain_base->perlinMap2D(node_min.X, node_min.Z, persistmap);
+ noise_terrain_alt->perlinMap2D(node_min.X, node_min.Z, persistmap);
+ noise_height_select->perlinMap2D(node_min.X, node_min.Z);
+
+ if ((spflags & MGV7_MOUNTAINS) || (spflags & MGV7_FLOATLANDS)) {
+ noise_mountain->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
+ }
+
+ if (spflags & MGV7_MOUNTAINS) {
+ noise_mount_height->perlinMap2D(node_min.X, node_min.Z);
+ }
+
+ if (spflags & MGV7_FLOATLANDS) {
+ noise_floatland_base->perlinMap2D(node_min.X, node_min.Z);
+ noise_float_base_height->perlinMap2D(node_min.X, node_min.Z);
+ }
+
+ //// Place nodes
+ const v3s16 &em = vm->m_area.getExtent();
+ s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
+ u32 index2d = 0;
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index2d++) {
+ s16 surface_y = baseTerrainLevelFromMap(index2d);
+ if (surface_y > stone_surface_max_y)
+ stone_surface_max_y = surface_y;
+
+ // Get extent of floatland base terrain
+ // '+1' to avoid a layer of stone at y = MAX_MAP_GENERATION_LIMIT
+ s16 float_base_min = MAX_MAP_GENERATION_LIMIT + 1;
+ s16 float_base_max = MAX_MAP_GENERATION_LIMIT;
+ if (spflags & MGV7_FLOATLANDS)
+ floatBaseExtentFromMap(&float_base_min, &float_base_max, index2d);
+
+ u32 vi = vm->m_area.index(x, node_min.Y - 1, z);
+ u32 index3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X);
+
+ for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
+ if (vm->m_data[vi].getContent() == CONTENT_IGNORE) {
+ if (y <= surface_y) {
+ vm->m_data[vi] = n_stone; // Base terrain
+ } else if ((spflags & MGV7_MOUNTAINS) &&
+ getMountainTerrainFromMap(index3d, index2d, y)) {
+ vm->m_data[vi] = n_stone; // Mountain terrain
+ if (y > stone_surface_max_y)
+ stone_surface_max_y = y;
+ } else if ((spflags & MGV7_FLOATLANDS) &&
+ ((y >= float_base_min && y <= float_base_max) ||
+ getFloatlandMountainFromMap(index3d, index2d, y))) {
+ vm->m_data[vi] = n_stone; // Floatland terrain
+ stone_surface_max_y = node_max.Y;
+ } else if (y <= water_level) {
+ vm->m_data[vi] = n_water; // Ground level water
+ } else if ((spflags & MGV7_FLOATLANDS) &&
+ (y >= float_base_max && y <= floatland_level)) {
+ vm->m_data[vi] = n_water; // Floatland water
+ } else {
+ vm->m_data[vi] = n_air;
+ }
+ }
+ vm->m_area.add_y(em, vi, 1);
+ index3d += ystride;
+ }
+ }
+
+ return stone_surface_max_y;
+}
+
+
+void MapgenV7::generateRidgeTerrain()
+{
+ if (node_max.Y < water_level - 16 ||
+ ((spflags & MGV7_FLOATLANDS) && node_max.Y > shadow_limit))
+ return;
+
+ noise_ridge->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
+ noise_ridge_uwater->perlinMap2D(node_min.X, node_min.Z);
+
+ MapNode n_water(c_water_source);
+ MapNode n_air(CONTENT_AIR);
+ u32 index = 0;
+ float width = 0.2;
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
+ u32 vi = vm->m_area.index(node_min.X, y, z);
+ for (s16 x = node_min.X; x <= node_max.X; x++, index++, vi++) {
+ int j = (z - node_min.Z) * csize.X + (x - node_min.X);
+
+ float uwatern = noise_ridge_uwater->result[j] * 2;
+ if (fabs(uwatern) > width)
+ continue;
+
+ float altitude = y - water_level;
+ float height_mod = (altitude + 17) / 2.5;
+ float width_mod = width - fabs(uwatern);
+ float nridge = noise_ridge->result[index] * MYMAX(altitude, 0) / 7.0;
+
+ if (nridge + width_mod * height_mod < 0.6)
+ continue;
+
+ vm->m_data[vi] = (y > water_level) ? n_air : n_water;
+ }
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Code Boneyard
+////
+//// Much of the stuff here has potential to become useful again at some point
+//// in the future, but we don't want it to get lost or forgotten in version
+//// control.
+////
+
+#if 0
+int MapgenV7::generateMountainTerrain(s16 ymax)
+{
+ MapNode n_stone(c_stone);
+ u32 j = 0;
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
+ u32 vi = vm->m_area.index(node_min.X, y, z);
+ for (s16 x = node_min.X; x <= node_max.X; x++) {
+ int index = (z - node_min.Z) * csize.X + (x - node_min.X);
+ content_t c = vm->m_data[vi].getContent();
+
+ if (getMountainTerrainFromMap(j, index, y)
+ && (c == CONTENT_AIR || c == c_water_source)) {
+ vm->m_data[vi] = n_stone;
+ if (y > ymax)
+ ymax = y;
+ }
+
+ vi++;
+ j++;
+ }
+ }
+
+ return ymax;
+}
+#endif
+
+
+#if 0
+void MapgenV7::carveRivers() {
+ MapNode n_air(CONTENT_AIR), n_water_source(c_water_source);
+ MapNode n_stone(c_stone);
+ u32 index = 0;
+
+ int river_depth = 4;
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
+ float terrain_mod = noise_terrain_mod->result[index];
+ NoiseParams *np = noise_terrain_river->np;
+ np.persist = noise_terrain_persist->result[index];
+ float terrain_river = NoisePerlin2DNoTxfm(np, x, z, seed);
+ float height = terrain_river * (1 - abs(terrain_mod)) *
+ noise_terrain_river->np.scale;
+ height = log(height * height); //log(h^3) is pretty interesting for terrain
+
+ s16 y = heightmap[index];
+ if (height < 1.0 && y > river_depth &&
+ y - river_depth >= node_min.Y && y <= node_max.Y) {
+
+ for (s16 ry = y; ry != y - river_depth; ry--) {
+ u32 vi = vm->m_area.index(x, ry, z);
+ vm->m_data[vi] = n_air;
+ }
+
+ u32 vi = vm->m_area.index(x, y - river_depth, z);
+ vm->m_data[vi] = n_water_source;
+ }
+ }
+}
+#endif
+
+
+#if 0
+void MapgenV7::addTopNodes()
+{
+ v3s16 em = vm->m_area.getExtent();
+ s16 ntopnodes;
+ u32 index = 0;
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
+ Biome *biome = bmgr->biomes[biomemap[index]];
+
+ //////////////////// First, add top nodes below the ridge
+ s16 y = ridge_heightmap[index];
+
+ // This cutoff is good enough, but not perfect.
+ // It will cut off potentially placed top nodes at chunk boundaries
+ if (y < node_min.Y)
+ continue;
+ if (y > node_max.Y) {
+ y = node_max.Y; // Let's see if we can still go downward anyway
+ u32 vi = vm->m_area.index(x, y, z);
+ content_t c = vm->m_data[vi].getContent();
+ if (ndef->get(c).walkable)
+ continue;
+ }
+
+ // N.B. It is necessary to search downward since ridge_heightmap[i]
+ // might not be the actual height, just the lowest part in the chunk
+ // where a ridge had been carved
+ u32 i = vm->m_area.index(x, y, z);
+ for (; y >= node_min.Y; y--) {
+ content_t c = vm->m_data[i].getContent();
+ if (ndef->get(c).walkable)
+ break;
+ vm->m_area.add_y(em, i, -1);
+ }
+
+ if (y != node_min.Y - 1 && y >= water_level) {
+ ridge_heightmap[index] = y; //update ridgeheight
+ ntopnodes = biome->top_depth;
+ for (; y <= node_max.Y && ntopnodes; y++) {
+ ntopnodes--;
+ vm->m_data[i] = MapNode(biome->c_top);
+ vm->m_area.add_y(em, i, 1);
+ }
+ // If dirt, grow grass on it.
+ if (y > water_level - 10 &&
+ vm->m_data[i].getContent() == CONTENT_AIR) {
+ vm->m_area.add_y(em, i, -1);
+ if (vm->m_data[i].getContent() == c_dirt)
+ vm->m_data[i] = MapNode(c_dirt_with_grass);
+ }
+ }
+
+ //////////////////// Now, add top nodes on top of the ridge
+ y = heightmap[index];
+ if (y > node_max.Y) {
+ y = node_max.Y; // Let's see if we can still go downward anyway
+ u32 vi = vm->m_area.index(x, y, z);
+ content_t c = vm->m_data[vi].getContent();
+ if (ndef->get(c).walkable)
+ continue;
+ }
+
+ i = vm->m_area.index(x, y, z);
+ for (; y >= node_min.Y; y--) {
+ content_t c = vm->m_data[i].getContent();
+ if (ndef->get(c).walkable)
+ break;
+ vm->m_area.add_y(em, i, -1);
+ }
+
+ if (y != node_min.Y - 1) {
+ ntopnodes = biome->top_depth;
+ // Let's see if we've already added it...
+ if (y == ridge_heightmap[index] + ntopnodes - 1)
+ continue;
+
+ for (; y <= node_max.Y && ntopnodes; y++) {
+ ntopnodes--;
+ vm->m_data[i] = MapNode(biome->c_top);
+ vm->m_area.add_y(em, i, 1);
+ }
+ // If dirt, grow grass on it.
+ if (y > water_level - 10 &&
+ vm->m_data[i].getContent() == CONTENT_AIR) {
+ vm->m_area.add_y(em, i, -1);
+ if (vm->m_data[i].getContent() == c_dirt)
+ vm->m_data[i] = MapNode(c_dirt_with_grass);
+ }
+ }
+ }
+}
+#endif
diff --git a/src/mapgen/mapgen_v7.h b/src/mapgen/mapgen_v7.h
new file mode 100644
index 000000000..6fb7dc4bb
--- /dev/null
+++ b/src/mapgen/mapgen_v7.h
@@ -0,0 +1,111 @@
+/*
+Minetest
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2014-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "mapgen.h"
+
+///////////// Mapgen V7 flags
+#define MGV7_MOUNTAINS 0x01
+#define MGV7_RIDGES 0x02
+#define MGV7_FLOATLANDS 0x04
+#define MGV7_CAVERNS 0x08
+#define MGV7_BIOMEREPEAT 0x10 // Now unused
+
+class BiomeManager;
+
+extern FlagDesc flagdesc_mapgen_v7[];
+
+
+struct MapgenV7Params : public MapgenParams {
+ u32 spflags = MGV7_MOUNTAINS | MGV7_RIDGES | MGV7_CAVERNS;
+ s16 mount_zero_level = 0;
+ float cave_width = 0.09f;
+ s16 large_cave_depth = -33;
+ s16 lava_depth = -256;
+ float float_mount_density = 0.6f;
+ float float_mount_height = 128.0f;
+ s16 floatland_level = 1280;
+ s16 shadow_limit = 1024;
+ s16 cavern_limit = -256;
+ s16 cavern_taper = 256;
+ float cavern_threshold = 0.7f;
+
+ NoiseParams np_terrain_base;
+ NoiseParams np_terrain_alt;
+ NoiseParams np_terrain_persist;
+ NoiseParams np_height_select;
+ NoiseParams np_filler_depth;
+ NoiseParams np_mount_height;
+ NoiseParams np_ridge_uwater;
+ NoiseParams np_floatland_base;
+ NoiseParams np_float_base_height;
+ NoiseParams np_mountain;
+ NoiseParams np_ridge;
+ NoiseParams np_cavern;
+ NoiseParams np_cave1;
+ NoiseParams np_cave2;
+
+ MapgenV7Params();
+ ~MapgenV7Params() = default;
+
+ void readParams(const Settings *settings);
+ void writeParams(Settings *settings) const;
+};
+
+class MapgenV7 : public MapgenBasic {
+public:
+ MapgenV7(int mapgenid, MapgenV7Params *params, EmergeManager *emerge);
+ ~MapgenV7();
+
+ virtual MapgenType getType() const { return MAPGEN_V7; }
+
+ virtual void makeChunk(BlockMakeData *data);
+ int getSpawnLevelAtPoint(v2s16 p);
+
+ float baseTerrainLevelAtPoint(s16 x, s16 z);
+ float baseTerrainLevelFromMap(int index);
+ bool getMountainTerrainAtPoint(s16 x, s16 y, s16 z);
+ bool getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y);
+ bool getFloatlandMountainFromMap(int idx_xyz, int idx_xz, s16 y);
+ void floatBaseExtentFromMap(s16 *float_base_min, s16 *float_base_max, int idx_xz);
+
+ int generateTerrain();
+ void generateRidgeTerrain();
+
+private:
+ s16 mount_zero_level;
+ s16 large_cave_depth;
+ float float_mount_density;
+ float float_mount_height;
+ s16 floatland_level;
+ s16 shadow_limit;
+
+ Noise *noise_terrain_base;
+ Noise *noise_terrain_alt;
+ Noise *noise_terrain_persist;
+ Noise *noise_height_select;
+ Noise *noise_mount_height;
+ Noise *noise_ridge_uwater;
+ Noise *noise_floatland_base;
+ Noise *noise_float_base_height;
+ Noise *noise_mountain;
+ Noise *noise_ridge;
+};
diff --git a/src/mapgen/mapgen_valleys.cpp b/src/mapgen/mapgen_valleys.cpp
new file mode 100644
index 000000000..a13bb4582
--- /dev/null
+++ b/src/mapgen/mapgen_valleys.cpp
@@ -0,0 +1,743 @@
+/*
+Minetest Valleys C
+Copyright (C) 2016-2017 Duane Robertson <duane@duanerobertson.com>
+Copyright (C) 2016-2017 paramat
+
+Based on Valleys Mapgen by Gael de Sailly
+ (https://forum.minetest.net/viewtopic.php?f=9&t=11430)
+and mapgen_v7, mapgen_flat by kwolekr and paramat.
+
+Licensing changed by permission of Gael de Sailly.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "nodedef.h"
+#include "voxelalgorithms.h"
+#include "settings.h" // For g_settings
+#include "emerge.h"
+#include "dungeongen.h"
+#include "mg_biome.h"
+#include "mg_ore.h"
+#include "mg_decoration.h"
+#include "mapgen_valleys.h"
+#include "cavegen.h"
+
+
+//#undef NDEBUG
+//#include "assert.h"
+
+//#include "util/timetaker.h"
+//#include "profiler.h"
+
+
+//static Profiler mapgen_prof;
+//Profiler *mapgen_profiler = &mapgen_prof;
+
+static FlagDesc flagdesc_mapgen_valleys[] = {
+ {"altitude_chill", MGVALLEYS_ALT_CHILL},
+ {"humid_rivers", MGVALLEYS_HUMID_RIVERS},
+ {NULL, 0}
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+MapgenValleys::MapgenValleys(int mapgenid, MapgenValleysParams *params, EmergeManager *emerge)
+ : MapgenBasic(mapgenid, params, emerge)
+{
+ // NOTE: MapgenValleys has a hard dependency on BiomeGenOriginal
+ m_bgen = (BiomeGenOriginal *)biomegen;
+
+ BiomeParamsOriginal *bp = (BiomeParamsOriginal *)params->bparams;
+
+ spflags = params->spflags;
+ altitude_chill = params->altitude_chill;
+ large_cave_depth = params->large_cave_depth;
+ lava_features_lim = rangelim(params->lava_features, 0, 10);
+ massive_cave_depth = params->massive_cave_depth;
+ river_depth_bed = params->river_depth + 1.f;
+ river_size_factor = params->river_size / 100.f;
+ water_features_lim = rangelim(params->water_features, 0, 10);
+ cave_width = params->cave_width;
+
+ //// 2D Terrain noise
+ noise_filler_depth = new Noise(&params->np_filler_depth, seed, csize.X, csize.Z);
+ noise_inter_valley_slope = new Noise(&params->np_inter_valley_slope, seed, csize.X, csize.Z);
+ noise_rivers = new Noise(&params->np_rivers, seed, csize.X, csize.Z);
+ noise_terrain_height = new Noise(&params->np_terrain_height, seed, csize.X, csize.Z);
+ noise_valley_depth = new Noise(&params->np_valley_depth, seed, csize.X, csize.Z);
+ noise_valley_profile = new Noise(&params->np_valley_profile, seed, csize.X, csize.Z);
+
+ //// 3D Terrain noise
+ // 1-up 1-down overgeneration
+ noise_inter_valley_fill = new Noise(&params->np_inter_valley_fill, seed, csize.X, csize.Y + 2, csize.Z);
+ // 1-down overgeneraion
+ noise_cave1 = new Noise(&params->np_cave1, seed, csize.X, csize.Y + 1, csize.Z);
+ noise_cave2 = new Noise(&params->np_cave2, seed, csize.X, csize.Y + 1, csize.Z);
+ noise_massive_caves = new Noise(&params->np_massive_caves, seed, csize.X, csize.Y + 1, csize.Z);
+
+ humid_rivers = (spflags & MGVALLEYS_HUMID_RIVERS);
+ use_altitude_chill = (spflags & MGVALLEYS_ALT_CHILL);
+ humidity_adjust = bp->np_humidity.offset - 50.f;
+
+ // a small chance of overflows if the settings are very high
+ cave_water_max_height = water_level + MYMAX(0, water_features_lim - 4) * 50;
+ lava_max_height = water_level + MYMAX(0, lava_features_lim - 4) * 50;
+
+ tcave_cache = new float[csize.Y + 2];
+}
+
+
+MapgenValleys::~MapgenValleys()
+{
+ delete noise_cave1;
+ delete noise_cave2;
+ delete noise_filler_depth;
+ delete noise_inter_valley_fill;
+ delete noise_inter_valley_slope;
+ delete noise_rivers;
+ delete noise_massive_caves;
+ delete noise_terrain_height;
+ delete noise_valley_depth;
+ delete noise_valley_profile;
+
+ delete[] tcave_cache;
+}
+
+
+MapgenValleysParams::MapgenValleysParams():
+ np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
+ np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0),
+ np_filler_depth (0.f, 1.2f, v3f(256, 256, 256), 1605, 3, 0.5f, 2.f),
+ np_inter_valley_fill (0.f, 1.f, v3f(256, 512, 256), 1993, 6, 0.8f, 2.f),
+ np_inter_valley_slope (0.5f, 0.5f, v3f(128, 128, 128), 746, 1, 1.f, 2.f),
+ np_rivers (0.f, 1.f, v3f(256, 256, 256), -6050, 5, 0.6f, 2.f),
+ np_massive_caves (0.f, 1.f, v3f(768, 256, 768), 59033, 6, 0.63f, 2.f),
+ np_terrain_height (-10.f, 50.f, v3f(1024, 1024, 1024), 5202, 6, 0.4f, 2.f),
+ np_valley_depth (5.f, 4.f, v3f(512, 512, 512), -1914, 1, 1.f, 2.f),
+ np_valley_profile (0.6f, 0.5f, v3f(512, 512, 512), 777, 1, 1.f, 2.f)
+{
+}
+
+
+void MapgenValleysParams::readParams(const Settings *settings)
+{
+ settings->getFlagStrNoEx("mgvalleys_spflags", spflags, flagdesc_mapgen_valleys);
+ settings->getU16NoEx("mgvalleys_altitude_chill", altitude_chill);
+ settings->getS16NoEx("mgvalleys_large_cave_depth", large_cave_depth);
+ settings->getU16NoEx("mgvalleys_lava_features", lava_features);
+ settings->getS16NoEx("mgvalleys_massive_cave_depth", massive_cave_depth);
+ settings->getU16NoEx("mgvalleys_river_depth", river_depth);
+ settings->getU16NoEx("mgvalleys_river_size", river_size);
+ settings->getU16NoEx("mgvalleys_water_features", water_features);
+ settings->getFloatNoEx("mgvalleys_cave_width", cave_width);
+
+ settings->getNoiseParams("mgvalleys_np_cave1", np_cave1);
+ settings->getNoiseParams("mgvalleys_np_cave2", np_cave2);
+ settings->getNoiseParams("mgvalleys_np_filler_depth", np_filler_depth);
+ settings->getNoiseParams("mgvalleys_np_inter_valley_fill", np_inter_valley_fill);
+ settings->getNoiseParams("mgvalleys_np_inter_valley_slope", np_inter_valley_slope);
+ settings->getNoiseParams("mgvalleys_np_rivers", np_rivers);
+ settings->getNoiseParams("mgvalleys_np_massive_caves", np_massive_caves);
+ settings->getNoiseParams("mgvalleys_np_terrain_height", np_terrain_height);
+ settings->getNoiseParams("mgvalleys_np_valley_depth", np_valley_depth);
+ settings->getNoiseParams("mgvalleys_np_valley_profile", np_valley_profile);
+}
+
+
+void MapgenValleysParams::writeParams(Settings *settings) const
+{
+ settings->setFlagStr("mgvalleys_spflags", spflags, flagdesc_mapgen_valleys, U32_MAX);
+ settings->setU16("mgvalleys_altitude_chill", altitude_chill);
+ settings->setS16("mgvalleys_large_cave_depth", large_cave_depth);
+ settings->setU16("mgvalleys_lava_features", lava_features);
+ settings->setS16("mgvalleys_massive_cave_depth", massive_cave_depth);
+ settings->setU16("mgvalleys_river_depth", river_depth);
+ settings->setU16("mgvalleys_river_size", river_size);
+ settings->setU16("mgvalleys_water_features", water_features);
+ settings->setFloat("mgvalleys_cave_width", cave_width);
+
+ settings->setNoiseParams("mgvalleys_np_cave1", np_cave1);
+ settings->setNoiseParams("mgvalleys_np_cave2", np_cave2);
+ settings->setNoiseParams("mgvalleys_np_filler_depth", np_filler_depth);
+ settings->setNoiseParams("mgvalleys_np_inter_valley_fill", np_inter_valley_fill);
+ settings->setNoiseParams("mgvalleys_np_inter_valley_slope", np_inter_valley_slope);
+ settings->setNoiseParams("mgvalleys_np_rivers", np_rivers);
+ settings->setNoiseParams("mgvalleys_np_massive_caves", np_massive_caves);
+ settings->setNoiseParams("mgvalleys_np_terrain_height", np_terrain_height);
+ settings->setNoiseParams("mgvalleys_np_valley_depth", np_valley_depth);
+ settings->setNoiseParams("mgvalleys_np_valley_profile", np_valley_profile);
+}
+
+
+///////////////////////////////////////
+
+
+void MapgenValleys::makeChunk(BlockMakeData *data)
+{
+ // Pre-conditions
+ assert(data->vmanip);
+ assert(data->nodedef);
+ assert(data->blockpos_requested.X >= data->blockpos_min.X &&
+ data->blockpos_requested.Y >= data->blockpos_min.Y &&
+ data->blockpos_requested.Z >= data->blockpos_min.Z);
+ assert(data->blockpos_requested.X <= data->blockpos_max.X &&
+ data->blockpos_requested.Y <= data->blockpos_max.Y &&
+ data->blockpos_requested.Z <= data->blockpos_max.Z);
+
+ this->generating = true;
+ this->vm = data->vmanip;
+ this->ndef = data->nodedef;
+
+ //TimeTaker t("makeChunk");
+
+ v3s16 blockpos_min = data->blockpos_min;
+ v3s16 blockpos_max = data->blockpos_max;
+ node_min = blockpos_min * MAP_BLOCKSIZE;
+ node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+ full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
+ full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ blockseed = getBlockSeed2(full_node_min, seed);
+
+ // Generate biome noises. Note this must be executed strictly before
+ // generateTerrain, because generateTerrain depends on intermediate
+ // biome-related noises.
+ m_bgen->calcBiomeNoise(node_min);
+
+ // Generate noise maps and base terrain height.
+ // Modify heat and humidity maps.
+ calculateNoise();
+
+ // Generate base terrain with initial heightmaps
+ s16 stone_surface_max_y = generateTerrain();
+
+ // Recalculate heightmap
+ updateHeightmap(node_min, node_max);
+
+ // Place biome-specific nodes and build biomemap
+ MgStoneType mgstone_type;
+ content_t biome_stone;
+ generateBiomes(&mgstone_type, &biome_stone);
+
+ // Cave creation.
+ if (flags & MG_CAVES)
+ generateCaves(stone_surface_max_y, large_cave_depth);
+
+ // Dungeon creation
+ if ((flags & MG_DUNGEONS) && node_max.Y < 50)
+ generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
+
+ // Generate the registered decorations
+ if (flags & MG_DECORATIONS)
+ m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
+
+ // Generate the registered ores
+ m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
+
+ // Sprinkle some dust on top after everything else was generated
+ dustTopNodes();
+
+ //TimeTaker tll("liquid_lighting");
+
+ updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
+
+ if (flags & MG_LIGHT)
+ calcLighting(
+ node_min - v3s16(0, 1, 0),
+ node_max + v3s16(0, 1, 0),
+ full_node_min,
+ full_node_max);
+
+ //mapgen_profiler->avg("liquid_lighting", tll.stop() / 1000.f);
+ //mapgen_profiler->avg("makeChunk", t.stop() / 1000.f);
+
+ this->generating = false;
+}
+
+
+// Populate the noise tables and do most of the
+// calculation necessary to determine terrain height.
+void MapgenValleys::calculateNoise()
+{
+ //TimeTaker t("calculateNoise", NULL, PRECISION_MICRO);
+
+ int x = node_min.X;
+ int y = node_min.Y - 1;
+ int z = node_min.Z;
+
+ //TimeTaker tcn("actualNoise");
+
+ noise_inter_valley_slope->perlinMap2D(x, z);
+ noise_rivers->perlinMap2D(x, z);
+ noise_terrain_height->perlinMap2D(x, z);
+ noise_valley_depth->perlinMap2D(x, z);
+ noise_valley_profile->perlinMap2D(x, z);
+
+ noise_inter_valley_fill->perlinMap3D(x, y, z);
+
+ //mapgen_profiler->avg("noisemaps", tcn.stop() / 1000.f);
+
+ float heat_offset = 0.f;
+ float humidity_scale = 1.f;
+
+ // Altitude chill tends to reduce the average heat.
+ if (use_altitude_chill)
+ heat_offset = 5.f;
+
+ // River humidity tends to increase the humidity range.
+ if (humid_rivers) {
+ humidity_scale = 0.8f;
+ }
+
+ for (s32 index = 0; index < csize.X * csize.Z; index++) {
+ m_bgen->heatmap[index] += heat_offset;
+ m_bgen->humidmap[index] *= humidity_scale;
+ }
+
+ TerrainNoise tn;
+
+ u32 index = 0;
+ for (tn.z = node_min.Z; tn.z <= node_max.Z; tn.z++)
+ for (tn.x = node_min.X; tn.x <= node_max.X; tn.x++, index++) {
+ // The parameters that we actually need to generate terrain
+ // are passed by address (and the return value).
+ tn.terrain_height = noise_terrain_height->result[index];
+ // River noise is replaced with base terrain, which
+ // is basically the height of the water table.
+ tn.rivers = &noise_rivers->result[index];
+ // Valley depth noise is replaced with the valley
+ // number that represents the height of terrain
+ // over rivers and is used to determine about
+ // how close a river is for humidity calculation.
+ tn.valley = &noise_valley_depth->result[index];
+ tn.valley_profile = noise_valley_profile->result[index];
+ // Slope noise is replaced by the calculated slope
+ // which is used to get terrain height in the slow
+ // method, to create sharper mountains.
+ tn.slope = &noise_inter_valley_slope->result[index];
+ tn.inter_valley_fill = noise_inter_valley_fill->result[index];
+
+ // This is the actual terrain height.
+ float mount = terrainLevelFromNoise(&tn);
+ noise_terrain_height->result[index] = mount;
+ }
+}
+
+
+// This keeps us from having to maintain two similar sets of
+// complicated code to determine ground level.
+float MapgenValleys::terrainLevelFromNoise(TerrainNoise *tn)
+{
+ // The square function changes the behaviour of this noise:
+ // very often small, and sometimes very high.
+ float valley_d = MYSQUARE(*tn->valley);
+
+ // valley_d is here because terrain is generally higher where valleys
+ // are deep (mountains). base represents the height of the
+ // rivers, most of the surface is above.
+ float base = tn->terrain_height + valley_d;
+
+ // "river" represents the distance from the river, in arbitrary units.
+ float river = fabs(*tn->rivers) - river_size_factor;
+
+ // Use the curve of the function 1-exp(-(x/a)^2) to model valleys.
+ // Making "a" vary (0 < a <= 1) changes the shape of the valleys.
+ // Try it with a geometry software !
+ // (here x = "river" and a = valley_profile).
+ // "valley" represents the height of the terrain, from the rivers.
+ {
+ float t = river / tn->valley_profile;
+ *tn->valley = valley_d * (1.f - exp(- MYSQUARE(t)));
+ }
+
+ // approximate height of the terrain at this point
+ float mount = base + *tn->valley;
+
+ *tn->slope *= *tn->valley;
+
+ // Rivers are placed where "river" is negative, so where the original
+ // noise value is close to zero.
+ // Base ground is returned as rivers since it's basically the water table.
+ *tn->rivers = base;
+ if (river < 0.f) {
+ // Use the the function -sqrt(1-x^2) which models a circle.
+ float depth;
+ {
+ float t = river / river_size_factor + 1;
+ depth = (river_depth_bed * sqrt(MYMAX(0, 1.f - MYSQUARE(t))));
+ }
+
+ // base - depth : height of the bottom of the river
+ // water_level - 3 : don't make rivers below 3 nodes under the surface
+ // We use three because that's as low as the swamp biomes go.
+ // There is no logical equivalent to this using rangelim.
+ mount = MYMIN(MYMAX(base - depth, (float)(water_level - 3)), mount);
+
+ // Slope has no influence on rivers.
+ *tn->slope = 0.f;
+ }
+
+ return mount;
+}
+
+
+// This avoids duplicating the code in terrainLevelFromNoise, adding
+// only the final step of terrain generation without a noise map.
+float MapgenValleys::adjustedTerrainLevelFromNoise(TerrainNoise *tn)
+{
+ float mount = terrainLevelFromNoise(tn);
+ s16 y_start = myround(mount);
+
+ for (s16 y = y_start; y <= y_start + 1000; y++) {
+ float fill = NoisePerlin3D(&noise_inter_valley_fill->np, tn->x, y, tn->z, seed);
+
+ if (fill * *tn->slope < y - mount) {
+ mount = MYMAX(y - 1, mount);
+ break;
+ }
+ }
+
+ return mount;
+}
+
+
+int MapgenValleys::getSpawnLevelAtPoint(v2s16 p)
+{
+ // Check to make sure this isn't a request for a location in a river.
+ float rivers = NoisePerlin2D(&noise_rivers->np, p.X, p.Y, seed);
+ if (fabs(rivers) < river_size_factor)
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+
+ s16 level_at_point = terrainLevelAtPoint(p.X, p.Y);
+ if (level_at_point <= water_level ||
+ level_at_point > water_level + 32)
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+
+ return level_at_point;
+}
+
+
+float MapgenValleys::terrainLevelAtPoint(s16 x, s16 z)
+{
+ TerrainNoise tn;
+
+ float rivers = NoisePerlin2D(&noise_rivers->np, x, z, seed);
+ float valley = NoisePerlin2D(&noise_valley_depth->np, x, z, seed);
+ float inter_valley_slope = NoisePerlin2D(&noise_inter_valley_slope->np, x, z, seed);
+
+ tn.x = x;
+ tn.z = z;
+ tn.terrain_height = NoisePerlin2D(&noise_terrain_height->np, x, z, seed);
+ tn.rivers = &rivers;
+ tn.valley = &valley;
+ tn.valley_profile = NoisePerlin2D(&noise_valley_profile->np, x, z, seed);
+ tn.slope = &inter_valley_slope;
+ tn.inter_valley_fill = 0.f;
+
+ return adjustedTerrainLevelFromNoise(&tn);
+}
+
+
+int MapgenValleys::generateTerrain()
+{
+ // Raising this reduces the rate of evaporation.
+ static const float evaporation = 300.f;
+ // from the lua
+ static const float humidity_dropoff = 4.f;
+ // constant to convert altitude chill (compatible with lua) to heat
+ static const float alt_to_heat = 20.f;
+ // humidity reduction by altitude
+ static const float alt_to_humid = 10.f;
+
+ MapNode n_air(CONTENT_AIR);
+ MapNode n_river_water(c_river_water_source);
+ MapNode n_stone(c_stone);
+ MapNode n_water(c_water_source);
+
+ const v3s16 &em = vm->m_area.getExtent();
+ s16 surface_max_y = -MAX_MAP_GENERATION_LIMIT;
+ u32 index_2d = 0;
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index_2d++) {
+ float river_y = noise_rivers->result[index_2d];
+ float surface_y = noise_terrain_height->result[index_2d];
+ float slope = noise_inter_valley_slope->result[index_2d];
+ float t_heat = m_bgen->heatmap[index_2d];
+
+ heightmap[index_2d] = -MAX_MAP_GENERATION_LIMIT;
+
+ if (surface_y > surface_max_y)
+ surface_max_y = ceil(surface_y);
+
+ if (humid_rivers) {
+ // Derive heat from (base) altitude. This will be most correct
+ // at rivers, since other surface heights may vary below.
+ if (use_altitude_chill && (surface_y > 0.f || river_y > 0.f))
+ t_heat -= alt_to_heat * MYMAX(surface_y, river_y) / altitude_chill;
+
+ // If humidity is low or heat is high, lower the water table.
+ float delta = m_bgen->humidmap[index_2d] - 50.f;
+ if (delta < 0.f) {
+ float t_evap = (t_heat - 32.f) / evaporation;
+ river_y += delta * MYMAX(t_evap, 0.08f);
+ }
+ }
+
+ u32 index_3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X);
+ u32 index_data = vm->m_area.index(x, node_min.Y - 1, z);
+
+ // Mapgens concern themselves with stone and water.
+ for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
+ if (vm->m_data[index_data].getContent() == CONTENT_IGNORE) {
+ float fill = noise_inter_valley_fill->result[index_3d];
+ float surface_delta = (float)y - surface_y;
+ bool river = y + 1 < river_y;
+
+ if (slope * fill > surface_delta) {
+ // ground
+ vm->m_data[index_data] = n_stone;
+ if (y > heightmap[index_2d])
+ heightmap[index_2d] = y;
+ if (y > surface_max_y)
+ surface_max_y = y;
+ } else if (y <= water_level) {
+ // sea
+ vm->m_data[index_data] = n_water;
+ } else if (river) {
+ // river
+ vm->m_data[index_data] = n_river_water;
+ } else { // air
+ vm->m_data[index_data] = n_air;
+ }
+ }
+
+ vm->m_area.add_y(em, index_data, 1);
+ index_3d += ystride;
+ }
+
+ if (heightmap[index_2d] == -MAX_MAP_GENERATION_LIMIT) {
+ s16 surface_y_int = myround(surface_y);
+ if (surface_y_int > node_max.Y + 1 || surface_y_int < node_min.Y - 1) {
+ // If surface_y is outside the chunk, it's good enough.
+ heightmap[index_2d] = surface_y_int;
+ } else {
+ // If the ground is outside of this chunk, but surface_y
+ // is within the chunk, give a value outside.
+ heightmap[index_2d] = node_min.Y - 2;
+ }
+ }
+
+ if (humid_rivers) {
+ // Use base ground (water table) in a riverbed, to
+ // avoid an unnatural rise in humidity.
+ float t_alt = MYMAX(noise_rivers->result[index_2d], (float)heightmap[index_2d]);
+ float humid = m_bgen->humidmap[index_2d];
+ float water_depth = (t_alt - river_y) / humidity_dropoff;
+ humid *= 1.f + pow(0.5f, MYMAX(water_depth, 1.f));
+
+ // Reduce humidity with altitude (ignoring riverbeds).
+ // This is similar to the lua version's seawater adjustment,
+ // but doesn't increase the base humidity, which causes
+ // problems with the default biomes.
+ if (t_alt > 0.f)
+ humid -= alt_to_humid * t_alt / altitude_chill;
+
+ m_bgen->humidmap[index_2d] = humid;
+ }
+
+ // Assign the heat adjusted by any changed altitudes.
+ // The altitude will change about half the time.
+ if (use_altitude_chill) {
+ // ground height ignoring riverbeds
+ float t_alt = MYMAX(noise_rivers->result[index_2d], (float)heightmap[index_2d]);
+ if (humid_rivers && heightmap[index_2d] == (s16)myround(surface_y))
+ // The altitude hasn't changed. Use the first result.
+ m_bgen->heatmap[index_2d] = t_heat;
+ else if (t_alt > 0.f)
+ m_bgen->heatmap[index_2d] -= alt_to_heat * t_alt / altitude_chill;
+ }
+ }
+
+ return surface_max_y;
+}
+
+void MapgenValleys::generateCaves(s16 max_stone_y, s16 large_cave_depth)
+{
+ if (max_stone_y < node_min.Y)
+ return;
+
+ noise_cave1->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
+ noise_cave2->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
+
+ PseudoRandom ps(blockseed + 72202);
+
+ MapNode n_air(CONTENT_AIR);
+ MapNode n_lava(c_lava_source);
+ MapNode n_water(c_river_water_source);
+
+ const v3s16 &em = vm->m_area.getExtent();
+
+ // Cave blend distance near YMIN, YMAX
+ const float massive_cave_blend = 128.f;
+ // noise threshold for massive caves
+ const float massive_cave_threshold = 0.6f;
+ // mct: 1 = small rare caves, 0.5 1/3rd ground volume, 0 = 1/2 ground volume.
+
+ float yblmin = -mapgen_limit + massive_cave_blend * 1.5f;
+ float yblmax = massive_cave_depth - massive_cave_blend * 1.5f;
+ bool made_a_big_one = false;
+
+ // Cache the tcave values as they only vary by altitude.
+ if (node_max.Y <= massive_cave_depth) {
+ noise_massive_caves->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
+
+ for (s16 y = node_min.Y - 1; y <= node_max.Y; y++) {
+ float tcave = massive_cave_threshold;
+
+ if (y < yblmin) {
+ float t = (yblmin - y) / massive_cave_blend;
+ tcave += MYSQUARE(t);
+ } else if (y > yblmax) {
+ float t = (y - yblmax) / massive_cave_blend;
+ tcave += MYSQUARE(t);
+ }
+
+ tcave_cache[y - node_min.Y + 1] = tcave;
+ }
+ }
+
+ // lava_depth varies between one and ten as you approach
+ // the bottom of the world.
+ s16 lava_depth = ceil((lava_max_height - node_min.Y + 1) * 10.f / mapgen_limit);
+ // This allows random lava spawns to be less common at the surface.
+ s16 lava_chance = MYCUBE(lava_features_lim) * lava_depth;
+ // water_depth varies between ten and one on the way down.
+ s16 water_depth = ceil((mapgen_limit - abs(node_min.Y) + 1) * 10.f / mapgen_limit);
+ // This allows random water spawns to be more common at the surface.
+ s16 water_chance = MYCUBE(water_features_lim) * water_depth;
+
+ // Reduce the odds of overflows even further.
+ if (node_max.Y > water_level) {
+ lava_chance /= 3;
+ water_chance /= 3;
+ }
+
+ u32 index_2d = 0;
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index_2d++) {
+ Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index_2d]);
+ bool tunnel_air_above = false;
+ bool is_under_river = false;
+ bool underground = false;
+ u32 index_data = vm->m_area.index(x, node_max.Y, z);
+ u32 index_3d = (z - node_min.Z) * zstride_1d + csize.Y * ystride + (x - node_min.X);
+
+ // Dig caves on down loop to check for air above.
+ // Don't excavate the overgenerated stone at node_max.Y + 1,
+ // this creates a 'roof' over the tunnel, preventing light in
+ // tunnels at mapchunk borders when generating mapchunks upwards.
+ // This 'roof' is removed when the mapchunk above is generated.
+ for (s16 y = node_max.Y; y >= node_min.Y - 1; y--,
+ index_3d -= ystride,
+ vm->m_area.add_y(em, index_data, -1)) {
+
+ float terrain = noise_terrain_height->result[index_2d];
+
+ // Saves some time.
+ if (y > terrain + 10)
+ continue;
+
+ if (y < terrain - 40)
+ underground = true;
+
+ // Dig massive caves.
+ if (node_max.Y <= massive_cave_depth
+ && noise_massive_caves->result[index_3d]
+ > tcave_cache[y - node_min.Y + 1]) {
+ vm->m_data[index_data] = n_air;
+ made_a_big_one = true;
+ continue;
+ }
+
+ content_t c = vm->m_data[index_data].getContent();
+ // Detect river water to place riverbed nodes in tunnels
+ if (c == biome->c_river_water)
+ is_under_river = true;
+
+ float d1 = contour(noise_cave1->result[index_3d]);
+ float d2 = contour(noise_cave2->result[index_3d]);
+
+ if (d1 * d2 > cave_width && ndef->get(c).is_ground_content) {
+ // in a tunnel
+ vm->m_data[index_data] = n_air;
+ tunnel_air_above = true;
+ } else if (c == biome->c_filler || c == biome->c_stone) {
+ if (tunnel_air_above) {
+ // at the tunnel floor
+ s16 sr = ps.range(0, 39);
+ u32 j = index_data;
+ vm->m_area.add_y(em, j, 1);
+
+ if (sr > terrain - y) {
+ // Put biome nodes in tunnels near the surface
+ if (is_under_river)
+ vm->m_data[index_data] = MapNode(biome->c_riverbed);
+ else if (underground)
+ vm->m_data[index_data] = MapNode(biome->c_filler);
+ else
+ vm->m_data[index_data] = MapNode(biome->c_top);
+ } else if (sr < 3 && underground) {
+ sr = abs(ps.next());
+ if (lava_features_lim > 0 && y <= lava_max_height
+ && c == biome->c_stone && sr < lava_chance)
+ vm->m_data[j] = n_lava;
+
+ sr -= lava_chance;
+
+ // If sr < 0 then we should have already placed lava --
+ // don't immediately dump water on it.
+ if (water_features_lim > 0 && y <= cave_water_max_height
+ && sr >= 0 && sr < water_chance)
+ vm->m_data[j] = n_water;
+ }
+ }
+
+ tunnel_air_above = false;
+ underground = true;
+ } else {
+ tunnel_air_above = false;
+ }
+ }
+ }
+
+ if (node_max.Y <= large_cave_depth && !made_a_big_one) {
+ u32 bruises_count = ps.range(0, 2);
+ for (u32 i = 0; i < bruises_count; i++) {
+ CavesRandomWalk cave(ndef, &gennotify, seed, water_level,
+ c_water_source, c_lava_source, lava_max_height);
+
+ cave.makeCave(vm, node_min, node_max, &ps, true, max_stone_y, heightmap);
+ }
+ }
+}
diff --git a/src/mapgen/mapgen_valleys.h b/src/mapgen/mapgen_valleys.h
new file mode 100644
index 000000000..7b5eb187d
--- /dev/null
+++ b/src/mapgen/mapgen_valleys.h
@@ -0,0 +1,134 @@
+/*
+Minetest Valleys C
+Copyright (C) 2016-2017 Duane Robertson <duane@duanerobertson.com>
+Copyright (C) 2016-2017 paramat
+
+Based on Valleys Mapgen by Gael de Sailly
+ (https://forum.minetest.net/viewtopic.php?f=9&t=11430)
+and mapgen_v7 by kwolekr and paramat.
+
+Licensing changed by permission of Gael de Sailly.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "mapgen.h"
+
+////////////// Mapgen Valleys flags
+#define MGVALLEYS_ALT_CHILL 0x01
+#define MGVALLEYS_HUMID_RIVERS 0x02
+
+// Feed only one variable into these.
+#define MYSQUARE(x) (x) * (x)
+#define MYCUBE(x) (x) * (x) * (x)
+
+class BiomeManager;
+class BiomeGenOriginal;
+
+// Global profiler
+//class Profiler;
+//extern Profiler *mapgen_profiler;
+
+
+struct MapgenValleysParams : public MapgenParams {
+ u32 spflags = MGVALLEYS_HUMID_RIVERS | MGVALLEYS_ALT_CHILL;
+ s16 large_cave_depth = -33;
+ s16 massive_cave_depth = -256; // highest altitude of massive caves
+ u16 altitude_chill = 90; // The altitude at which temperature drops by 20C.
+ u16 lava_features = 0; // How often water will occur in caves.
+ u16 river_depth = 4; // How deep to carve river channels.
+ u16 river_size = 5; // How wide to make rivers.
+ u16 water_features = 0; // How often water will occur in caves.
+ float cave_width = 0.09f;
+ NoiseParams np_cave1;
+ NoiseParams np_cave2;
+ NoiseParams np_filler_depth;
+ NoiseParams np_inter_valley_fill;
+ NoiseParams np_inter_valley_slope;
+ NoiseParams np_rivers;
+ NoiseParams np_massive_caves;
+ NoiseParams np_terrain_height;
+ NoiseParams np_valley_depth;
+ NoiseParams np_valley_profile;
+
+ MapgenValleysParams();
+ ~MapgenValleysParams() = default;
+
+ void readParams(const Settings *settings);
+ void writeParams(Settings *settings) const;
+};
+
+struct TerrainNoise {
+ s16 x;
+ s16 z;
+ float terrain_height;
+ float *rivers;
+ float *valley;
+ float valley_profile;
+ float *slope;
+ float inter_valley_fill;
+};
+
+class MapgenValleys : public MapgenBasic {
+public:
+
+ MapgenValleys(int mapgenid, MapgenValleysParams *params, EmergeManager *emerge);
+ ~MapgenValleys();
+
+ virtual MapgenType getType() const { return MAPGEN_VALLEYS; }
+
+ virtual void makeChunk(BlockMakeData *data);
+ int getSpawnLevelAtPoint(v2s16 p);
+
+ s16 large_cave_depth;
+
+private:
+ BiomeGenOriginal *m_bgen;
+
+ bool humid_rivers;
+ bool use_altitude_chill;
+ float humidity_adjust;
+ s16 cave_water_max_height;
+ s16 lava_max_height;
+
+ float altitude_chill;
+ s16 lava_features_lim;
+ s16 massive_cave_depth;
+ float river_depth_bed;
+ float river_size_factor;
+ float *tcave_cache;
+ s16 water_features_lim;
+ Noise *noise_inter_valley_fill;
+ Noise *noise_inter_valley_slope;
+ Noise *noise_rivers;
+ Noise *noise_cave1;
+ Noise *noise_cave2;
+ Noise *noise_massive_caves;
+ Noise *noise_terrain_height;
+ Noise *noise_valley_depth;
+ Noise *noise_valley_profile;
+
+ float terrainLevelAtPoint(s16 x, s16 z);
+
+ void calculateNoise();
+
+ virtual int generateTerrain();
+ float terrainLevelFromNoise(TerrainNoise *tn);
+ float adjustedTerrainLevelFromNoise(TerrainNoise *tn);
+
+ virtual void generateCaves(s16 max_stone_y, s16 large_cave_depth);
+};
diff --git a/src/mapgen/mg_biome.cpp b/src/mapgen/mg_biome.cpp
new file mode 100644
index 000000000..8dbb78e59
--- /dev/null
+++ b/src/mapgen/mg_biome.cpp
@@ -0,0 +1,238 @@
+/*
+Minetest
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2014-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "mg_biome.h"
+#include "mg_decoration.h"
+#include "emerge.h"
+#include "server.h"
+#include "nodedef.h"
+#include "map.h" //for MMVManip
+#include "util/numeric.h"
+#include "porting.h"
+#include "settings.h"
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+BiomeManager::BiomeManager(Server *server) :
+ ObjDefManager(server, OBJDEF_BIOME)
+{
+ m_server = server;
+
+ // Create default biome to be used in case none exist
+ Biome *b = new Biome;
+
+ b->name = "Default";
+ b->flags = 0;
+ b->depth_top = 0;
+ b->depth_filler = -MAX_MAP_GENERATION_LIMIT;
+ b->depth_water_top = 0;
+ b->depth_riverbed = 0;
+ b->y_min = -MAX_MAP_GENERATION_LIMIT;
+ b->y_max = MAX_MAP_GENERATION_LIMIT;
+ b->heat_point = 0.0;
+ b->humidity_point = 0.0;
+
+ b->m_nodenames.emplace_back("mapgen_stone");
+ b->m_nodenames.emplace_back("mapgen_stone");
+ b->m_nodenames.emplace_back("mapgen_stone");
+ b->m_nodenames.emplace_back("mapgen_water_source");
+ b->m_nodenames.emplace_back("mapgen_water_source");
+ b->m_nodenames.emplace_back("mapgen_river_water_source");
+ b->m_nodenames.emplace_back("mapgen_stone");
+ b->m_nodenames.emplace_back("ignore");
+ m_ndef->pendNodeResolve(b);
+
+ add(b);
+}
+
+
+void BiomeManager::clear()
+{
+ EmergeManager *emerge = m_server->getEmergeManager();
+
+ // Remove all dangling references in Decorations
+ DecorationManager *decomgr = emerge->decomgr;
+ for (size_t i = 0; i != decomgr->getNumObjects(); i++) {
+ Decoration *deco = (Decoration *)decomgr->getRaw(i);
+ deco->biomes.clear();
+ }
+
+ // Don't delete the first biome
+ for (size_t i = 1; i < m_objects.size(); i++)
+ delete (Biome *)m_objects[i];
+
+ m_objects.resize(1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+void BiomeParamsOriginal::readParams(const Settings *settings)
+{
+ settings->getNoiseParams("mg_biome_np_heat", np_heat);
+ settings->getNoiseParams("mg_biome_np_heat_blend", np_heat_blend);
+ settings->getNoiseParams("mg_biome_np_humidity", np_humidity);
+ settings->getNoiseParams("mg_biome_np_humidity_blend", np_humidity_blend);
+}
+
+
+void BiomeParamsOriginal::writeParams(Settings *settings) const
+{
+ settings->setNoiseParams("mg_biome_np_heat", np_heat);
+ settings->setNoiseParams("mg_biome_np_heat_blend", np_heat_blend);
+ settings->setNoiseParams("mg_biome_np_humidity", np_humidity);
+ settings->setNoiseParams("mg_biome_np_humidity_blend", np_humidity_blend);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+BiomeGenOriginal::BiomeGenOriginal(BiomeManager *biomemgr,
+ BiomeParamsOriginal *params, v3s16 chunksize)
+{
+ m_bmgr = biomemgr;
+ m_params = params;
+ m_csize = chunksize;
+
+ noise_heat = new Noise(&params->np_heat,
+ params->seed, m_csize.X, m_csize.Z);
+ noise_humidity = new Noise(&params->np_humidity,
+ params->seed, m_csize.X, m_csize.Z);
+ noise_heat_blend = new Noise(&params->np_heat_blend,
+ params->seed, m_csize.X, m_csize.Z);
+ noise_humidity_blend = new Noise(&params->np_humidity_blend,
+ params->seed, m_csize.X, m_csize.Z);
+
+ heatmap = noise_heat->result;
+ humidmap = noise_humidity->result;
+ biomemap = new biome_t[m_csize.X * m_csize.Z];
+}
+
+BiomeGenOriginal::~BiomeGenOriginal()
+{
+ delete []biomemap;
+
+ delete noise_heat;
+ delete noise_humidity;
+ delete noise_heat_blend;
+ delete noise_humidity_blend;
+}
+
+
+Biome *BiomeGenOriginal::calcBiomeAtPoint(v3s16 pos) const
+{
+ float heat =
+ NoisePerlin2D(&m_params->np_heat, pos.X, pos.Z, m_params->seed) +
+ NoisePerlin2D(&m_params->np_heat_blend, pos.X, pos.Z, m_params->seed);
+ float humidity =
+ NoisePerlin2D(&m_params->np_humidity, pos.X, pos.Z, m_params->seed) +
+ NoisePerlin2D(&m_params->np_humidity_blend, pos.X, pos.Z, m_params->seed);
+
+ return calcBiomeFromNoise(heat, humidity, pos.Y);
+}
+
+
+void BiomeGenOriginal::calcBiomeNoise(v3s16 pmin)
+{
+ m_pmin = pmin;
+
+ noise_heat->perlinMap2D(pmin.X, pmin.Z);
+ noise_humidity->perlinMap2D(pmin.X, pmin.Z);
+ noise_heat_blend->perlinMap2D(pmin.X, pmin.Z);
+ noise_humidity_blend->perlinMap2D(pmin.X, pmin.Z);
+
+ for (s32 i = 0; i < m_csize.X * m_csize.Z; i++) {
+ noise_heat->result[i] += noise_heat_blend->result[i];
+ noise_humidity->result[i] += noise_humidity_blend->result[i];
+ }
+}
+
+
+biome_t *BiomeGenOriginal::getBiomes(s16 *heightmap)
+{
+ for (s32 i = 0; i != m_csize.X * m_csize.Z; i++) {
+ Biome *biome = calcBiomeFromNoise(
+ noise_heat->result[i],
+ noise_humidity->result[i],
+ heightmap[i]);
+
+ biomemap[i] = biome->index;
+ }
+
+ return biomemap;
+}
+
+
+Biome *BiomeGenOriginal::getBiomeAtPoint(v3s16 pos) const
+{
+ return getBiomeAtIndex(
+ (pos.Z - m_pmin.Z) * m_csize.X + (pos.X - m_pmin.X),
+ pos.Y);
+}
+
+
+Biome *BiomeGenOriginal::getBiomeAtIndex(size_t index, s16 y) const
+{
+ return calcBiomeFromNoise(
+ noise_heat->result[index],
+ noise_humidity->result[index],
+ y);
+}
+
+
+Biome *BiomeGenOriginal::calcBiomeFromNoise(float heat, float humidity, s16 y) const
+{
+ Biome *b, *biome_closest = NULL;
+ float dist_min = FLT_MAX;
+
+ for (size_t i = 1; i < m_bmgr->getNumObjects(); i++) {
+ b = (Biome *)m_bmgr->getRaw(i);
+ if (!b || y > b->y_max || y < b->y_min)
+ continue;
+
+ float d_heat = heat - b->heat_point;
+ float d_humidity = humidity - b->humidity_point;
+ float dist = (d_heat * d_heat) +
+ (d_humidity * d_humidity);
+ if (dist < dist_min) {
+ dist_min = dist;
+ biome_closest = b;
+ }
+ }
+
+ return biome_closest ? biome_closest : (Biome *)m_bmgr->getRaw(BIOME_NONE);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Biome::resolveNodeNames()
+{
+ getIdFromNrBacklog(&c_top, "mapgen_stone", CONTENT_AIR);
+ getIdFromNrBacklog(&c_filler, "mapgen_stone", CONTENT_AIR);
+ getIdFromNrBacklog(&c_stone, "mapgen_stone", CONTENT_AIR);
+ getIdFromNrBacklog(&c_water_top, "mapgen_water_source", CONTENT_AIR);
+ getIdFromNrBacklog(&c_water, "mapgen_water_source", CONTENT_AIR);
+ getIdFromNrBacklog(&c_river_water, "mapgen_river_water_source", CONTENT_AIR);
+ getIdFromNrBacklog(&c_riverbed, "mapgen_stone", CONTENT_AIR);
+ getIdFromNrBacklog(&c_dust, "ignore", CONTENT_IGNORE);
+}
diff --git a/src/mapgen/mg_biome.h b/src/mapgen/mg_biome.h
new file mode 100644
index 000000000..f45238f28
--- /dev/null
+++ b/src/mapgen/mg_biome.h
@@ -0,0 +1,230 @@
+/*
+Minetest
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2014-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "objdef.h"
+#include "nodedef.h"
+#include "noise.h"
+
+class Server;
+class Settings;
+class BiomeManager;
+
+////
+//// Biome
+////
+
+typedef u8 biome_t;
+
+#define BIOME_NONE ((biome_t)0)
+
+// TODO(hmmmm): Decide whether this is obsolete or will be used in the future
+enum BiomeType {
+ BIOMETYPE_NORMAL,
+ BIOMETYPE_LIQUID,
+ BIOMETYPE_NETHER,
+ BIOMETYPE_AETHER,
+ BIOMETYPE_FLAT,
+};
+
+class Biome : public ObjDef, public NodeResolver {
+public:
+ u32 flags;
+
+ content_t c_top;
+ content_t c_filler;
+ content_t c_stone;
+ content_t c_water_top;
+ content_t c_water;
+ content_t c_river_water;
+ content_t c_riverbed;
+ content_t c_dust;
+
+ s16 depth_top;
+ s16 depth_filler;
+ s16 depth_water_top;
+ s16 depth_riverbed;
+
+ s16 y_min;
+ s16 y_max;
+ float heat_point;
+ float humidity_point;
+
+ virtual void resolveNodeNames();
+};
+
+
+////
+//// BiomeGen
+////
+
+enum BiomeGenType {
+ BIOMEGEN_ORIGINAL,
+};
+
+struct BiomeParams {
+ virtual void readParams(const Settings *settings) = 0;
+ virtual void writeParams(Settings *settings) const = 0;
+ virtual ~BiomeParams() = default;
+
+ s32 seed;
+};
+
+class BiomeGen {
+public:
+ virtual ~BiomeGen() = default;
+
+ virtual BiomeGenType getType() const = 0;
+
+ // Calculates the biome at the exact position provided. This function can
+ // be called at any time, but may be less efficient than the latter methods,
+ // depending on implementation.
+ virtual Biome *calcBiomeAtPoint(v3s16 pos) const = 0;
+
+ // Computes any intermediate results needed for biome generation. Must be
+ // called before using any of: getBiomes, getBiomeAtPoint, or getBiomeAtIndex.
+ // Calling this invalidates the previous results stored in biomemap.
+ virtual void calcBiomeNoise(v3s16 pmin) = 0;
+
+ // Gets all biomes in current chunk using each corresponding element of
+ // heightmap as the y position, then stores the results by biome index in
+ // biomemap (also returned)
+ virtual biome_t *getBiomes(s16 *heightmap) = 0;
+
+ // Gets a single biome at the specified position, which must be contained
+ // in the region formed by m_pmin and (m_pmin + m_csize - 1).
+ virtual Biome *getBiomeAtPoint(v3s16 pos) const = 0;
+
+ // Same as above, but uses a raw numeric index correlating to the (x,z) position.
+ virtual Biome *getBiomeAtIndex(size_t index, s16 y) const = 0;
+
+ // Result of calcBiomes bulk computation.
+ biome_t *biomemap = nullptr;
+
+protected:
+ BiomeManager *m_bmgr = nullptr;
+ v3s16 m_pmin;
+ v3s16 m_csize;
+};
+
+
+////
+//// BiomeGen implementations
+////
+
+//
+// Original biome algorithm (Whittaker's classification + surface height)
+//
+
+struct BiomeParamsOriginal : public BiomeParams {
+ BiomeParamsOriginal() :
+ np_heat(50, 50, v3f(1000.0, 1000.0, 1000.0), 5349, 3, 0.5, 2.0),
+ np_humidity(50, 50, v3f(1000.0, 1000.0, 1000.0), 842, 3, 0.5, 2.0),
+ np_heat_blend(0, 1.5, v3f(8.0, 8.0, 8.0), 13, 2, 1.0, 2.0),
+ np_humidity_blend(0, 1.5, v3f(8.0, 8.0, 8.0), 90003, 2, 1.0, 2.0)
+ {
+ }
+
+ virtual void readParams(const Settings *settings);
+ virtual void writeParams(Settings *settings) const;
+
+ NoiseParams np_heat;
+ NoiseParams np_humidity;
+ NoiseParams np_heat_blend;
+ NoiseParams np_humidity_blend;
+};
+
+class BiomeGenOriginal : public BiomeGen {
+public:
+ BiomeGenOriginal(BiomeManager *biomemgr,
+ BiomeParamsOriginal *params, v3s16 chunksize);
+ virtual ~BiomeGenOriginal();
+
+ BiomeGenType getType() const { return BIOMEGEN_ORIGINAL; }
+
+ Biome *calcBiomeAtPoint(v3s16 pos) const;
+ void calcBiomeNoise(v3s16 pmin);
+
+ biome_t *getBiomes(s16 *heightmap);
+ Biome *getBiomeAtPoint(v3s16 pos) const;
+ Biome *getBiomeAtIndex(size_t index, s16 y) const;
+
+ Biome *calcBiomeFromNoise(float heat, float humidity, s16 y) const;
+
+ float *heatmap;
+ float *humidmap;
+
+private:
+ BiomeParamsOriginal *m_params;
+
+ Noise *noise_heat;
+ Noise *noise_humidity;
+ Noise *noise_heat_blend;
+ Noise *noise_humidity_blend;
+};
+
+
+////
+//// BiomeManager
+////
+
+class BiomeManager : public ObjDefManager {
+public:
+ BiomeManager(Server *server);
+ virtual ~BiomeManager() = default;
+
+ const char *getObjectTitle() const
+ {
+ return "biome";
+ }
+
+ static Biome *create(BiomeType type)
+ {
+ return new Biome;
+ }
+
+ BiomeGen *createBiomeGen(BiomeGenType type, BiomeParams *params, v3s16 chunksize)
+ {
+ switch (type) {
+ case BIOMEGEN_ORIGINAL:
+ return new BiomeGenOriginal(this,
+ (BiomeParamsOriginal *)params, chunksize);
+ default:
+ return NULL;
+ }
+ }
+
+ static BiomeParams *createBiomeParams(BiomeGenType type)
+ {
+ switch (type) {
+ case BIOMEGEN_ORIGINAL:
+ return new BiomeParamsOriginal;
+ default:
+ return NULL;
+ }
+ }
+
+ virtual void clear();
+
+private:
+ Server *m_server;
+
+};
diff --git a/src/mapgen/mg_decoration.cpp b/src/mapgen/mg_decoration.cpp
new file mode 100644
index 000000000..2c2fbc647
--- /dev/null
+++ b/src/mapgen/mg_decoration.cpp
@@ -0,0 +1,377 @@
+/*
+Minetest
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "mg_decoration.h"
+#include "mg_schematic.h"
+#include "mapgen.h"
+#include "noise.h"
+#include "map.h"
+#include "log.h"
+#include "util/numeric.h"
+#include <algorithm>
+
+
+FlagDesc flagdesc_deco[] = {
+ {"place_center_x", DECO_PLACE_CENTER_X},
+ {"place_center_y", DECO_PLACE_CENTER_Y},
+ {"place_center_z", DECO_PLACE_CENTER_Z},
+ {"force_placement", DECO_FORCE_PLACEMENT},
+ {"liquid_surface", DECO_LIQUID_SURFACE},
+ {"all_floors", DECO_ALL_FLOORS},
+ {"all_ceilings", DECO_ALL_CEILINGS},
+ {NULL, 0}
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+DecorationManager::DecorationManager(IGameDef *gamedef) :
+ ObjDefManager(gamedef, OBJDEF_DECORATION)
+{
+}
+
+
+size_t DecorationManager::placeAllDecos(Mapgen *mg, u32 blockseed,
+ v3s16 nmin, v3s16 nmax)
+{
+ size_t nplaced = 0;
+
+ for (size_t i = 0; i != m_objects.size(); i++) {
+ Decoration *deco = (Decoration *)m_objects[i];
+ if (!deco)
+ continue;
+
+ nplaced += deco->placeDeco(mg, blockseed, nmin, nmax);
+ blockseed++;
+ }
+
+ return nplaced;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+void Decoration::resolveNodeNames()
+{
+ getIdsFromNrBacklog(&c_place_on);
+ getIdsFromNrBacklog(&c_spawnby);
+}
+
+
+bool Decoration::canPlaceDecoration(MMVManip *vm, v3s16 p)
+{
+ // Check if the decoration can be placed on this node
+ u32 vi = vm->m_area.index(p);
+ if (!CONTAINS(c_place_on, vm->m_data[vi].getContent()))
+ return false;
+
+ // Don't continue if there are no spawnby constraints
+ if (nspawnby == -1)
+ return true;
+
+ int nneighs = 0;
+ static const v3s16 dirs[16] = {
+ v3s16( 0, 0, 1),
+ v3s16( 0, 0, -1),
+ v3s16( 1, 0, 0),
+ v3s16(-1, 0, 0),
+ v3s16( 1, 0, 1),
+ v3s16(-1, 0, 1),
+ v3s16(-1, 0, -1),
+ v3s16( 1, 0, -1),
+
+ v3s16( 0, 1, 1),
+ v3s16( 0, 1, -1),
+ v3s16( 1, 1, 0),
+ v3s16(-1, 1, 0),
+ v3s16( 1, 1, 1),
+ v3s16(-1, 1, 1),
+ v3s16(-1, 1, -1),
+ v3s16( 1, 1, -1)
+ };
+
+ // Check these 16 neighbouring nodes for enough spawnby nodes
+ for (size_t i = 0; i != ARRLEN(dirs); i++) {
+ u32 index = vm->m_area.index(p + dirs[i]);
+ if (!vm->m_area.contains(index))
+ continue;
+
+ if (CONTAINS(c_spawnby, vm->m_data[index].getContent()))
+ nneighs++;
+ }
+
+ if (nneighs < nspawnby)
+ return false;
+
+ return true;
+}
+
+
+size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax)
+{
+ PcgRandom ps(blockseed + 53);
+ int carea_size = nmax.X - nmin.X + 1;
+
+ // Divide area into parts
+ // If chunksize is changed it may no longer be divisable by sidelen
+ if (carea_size % sidelen)
+ sidelen = carea_size;
+
+ s16 divlen = carea_size / sidelen;
+ int area = sidelen * sidelen;
+
+ for (s16 z0 = 0; z0 < divlen; z0++)
+ for (s16 x0 = 0; x0 < divlen; x0++) {
+ v2s16 p2d_center( // Center position of part of division
+ nmin.X + sidelen / 2 + sidelen * x0,
+ nmin.Z + sidelen / 2 + sidelen * z0
+ );
+ v2s16 p2d_min( // Minimum edge of part of division
+ nmin.X + sidelen * x0,
+ nmin.Z + sidelen * z0
+ );
+ v2s16 p2d_max( // Maximum edge of part of division
+ nmin.X + sidelen + sidelen * x0 - 1,
+ nmin.Z + sidelen + sidelen * z0 - 1
+ );
+
+ // Amount of decorations
+ float nval = (flags & DECO_USE_NOISE) ?
+ NoisePerlin2D(&np, p2d_center.X, p2d_center.Y, mapseed) :
+ fill_ratio;
+ u32 deco_count = 0;
+ float deco_count_f = (float)area * nval;
+ if (deco_count_f >= 1.f) {
+ deco_count = deco_count_f;
+ } else if (deco_count_f > 0.f) {
+ // For low density decorations calculate a chance for 1 decoration
+ if (ps.range(1000) <= deco_count_f * 1000.f)
+ deco_count = 1;
+ }
+
+ for (u32 i = 0; i < deco_count; i++) {
+ s16 x = ps.range(p2d_min.X, p2d_max.X);
+ s16 z = ps.range(p2d_min.Y, p2d_max.Y);
+ int mapindex = carea_size * (z - nmin.Z) + (x - nmin.X);
+
+ if ((flags & DECO_ALL_FLOORS) ||
+ (flags & DECO_ALL_CEILINGS)) {
+ // All-surfaces decorations
+ // Check biome of column
+ if (mg->biomemap && !biomes.empty()) {
+ std::unordered_set<u8>::const_iterator iter =
+ biomes.find(mg->biomemap[mapindex]);
+ if (iter == biomes.end())
+ continue;
+ }
+
+ // Get all floors and ceilings in node column
+ u16 size = (nmax.Y - nmin.Y + 1) / 2;
+ s16 floors[size];
+ s16 ceilings[size];
+ u16 num_floors = 0;
+ u16 num_ceilings = 0;
+
+ mg->getSurfaces(v2s16(x, z), nmin.Y, nmax.Y,
+ floors, ceilings, &num_floors, &num_ceilings);
+
+ if ((flags & DECO_ALL_FLOORS) && num_floors > 0) {
+ // Floor decorations
+ for (u16 fi = 0; fi < num_floors; fi++) {
+ s16 y = floors[fi];
+ if (y < y_min || y > y_max)
+ continue;
+
+ v3s16 pos(x, y, z);
+ if (generate(mg->vm, &ps, pos, false))
+ mg->gennotify.addEvent(
+ GENNOTIFY_DECORATION, pos, index);
+ }
+ }
+
+ if ((flags & DECO_ALL_CEILINGS) && num_ceilings > 0) {
+ // Ceiling decorations
+ for (u16 ci = 0; ci < num_ceilings; ci++) {
+ s16 y = ceilings[ci];
+ if (y < y_min || y > y_max)
+ continue;
+
+ v3s16 pos(x, y, z);
+ if (generate(mg->vm, &ps, pos, true))
+ mg->gennotify.addEvent(
+ GENNOTIFY_DECORATION, pos, index);
+ }
+ }
+ } else { // Heightmap decorations
+ s16 y = -MAX_MAP_GENERATION_LIMIT;
+ if (flags & DECO_LIQUID_SURFACE)
+ y = mg->findLiquidSurface(v2s16(x, z), nmin.Y, nmax.Y);
+ else if (mg->heightmap)
+ y = mg->heightmap[mapindex];
+ else
+ y = mg->findGroundLevel(v2s16(x, z), nmin.Y, nmax.Y);
+
+ if (y < y_min || y > y_max || y < nmin.Y || y > nmax.Y)
+ continue;
+
+ if (mg->biomemap && !biomes.empty()) {
+ std::unordered_set<u8>::const_iterator iter =
+ biomes.find(mg->biomemap[mapindex]);
+ if (iter == biomes.end())
+ continue;
+ }
+
+ v3s16 pos(x, y, z);
+ if (generate(mg->vm, &ps, pos, false))
+ mg->gennotify.addEvent(GENNOTIFY_DECORATION, pos, index);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+void DecoSimple::resolveNodeNames()
+{
+ Decoration::resolveNodeNames();
+ getIdsFromNrBacklog(&c_decos);
+}
+
+
+size_t DecoSimple::generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling)
+{
+ // Don't bother if there aren't any decorations to place
+ if (c_decos.empty())
+ return 0;
+
+ if (!canPlaceDecoration(vm, p))
+ return 0;
+
+ // Check for placement outside the voxelmanip volume
+ if (ceiling) {
+ // Ceiling decorations
+ // 'place offset y' is inverted
+ if (p.Y - place_offset_y - std::max(deco_height, deco_height_max) <
+ vm->m_area.MinEdge.Y)
+ return 0;
+
+ if (p.Y - 1 - place_offset_y > vm->m_area.MaxEdge.Y)
+ return 0;
+
+ } else { // Heightmap and floor decorations
+ if (p.Y + place_offset_y + std::max(deco_height, deco_height_max) >
+ vm->m_area.MaxEdge.Y)
+ return 0;
+
+ if (p.Y + 1 + place_offset_y < vm->m_area.MinEdge.Y)
+ return 0;
+ }
+
+ content_t c_place = c_decos[pr->range(0, c_decos.size() - 1)];
+ s16 height = (deco_height_max > 0) ?
+ pr->range(deco_height, deco_height_max) : deco_height;
+ u8 param2 = (deco_param2_max > 0) ?
+ pr->range(deco_param2, deco_param2_max) : deco_param2;
+ bool force_placement = (flags & DECO_FORCE_PLACEMENT);
+
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 vi = vm->m_area.index(p);
+
+ if (ceiling) {
+ // Ceiling decorations
+ // 'place offset y' is inverted
+ vm->m_area.add_y(em, vi, -place_offset_y);
+
+ for (int i = 0; i < height; i++) {
+ vm->m_area.add_y(em, vi, -1);
+ content_t c = vm->m_data[vi].getContent();
+ if (c != CONTENT_AIR && c != CONTENT_IGNORE && !force_placement)
+ break;
+
+ vm->m_data[vi] = MapNode(c_place, 0, param2);
+ }
+ } else { // Heightmap and floor decorations
+ vm->m_area.add_y(em, vi, place_offset_y);
+
+ for (int i = 0; i < height; i++) {
+ vm->m_area.add_y(em, vi, 1);
+ content_t c = vm->m_data[vi].getContent();
+ if (c != CONTENT_AIR && c != CONTENT_IGNORE && !force_placement)
+ break;
+
+ vm->m_data[vi] = MapNode(c_place, 0, param2);
+ }
+ }
+
+ return 1;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+size_t DecoSchematic::generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling)
+{
+ // Schematic could have been unloaded but not the decoration
+ // In this case generate() does nothing (but doesn't *fail*)
+ if (schematic == NULL)
+ return 0;
+
+ if (!canPlaceDecoration(vm, p))
+ return 0;
+
+ if (flags & DECO_PLACE_CENTER_Y) {
+ p.Y -= (schematic->size.Y - 1) / 2;
+ } else {
+ // Only apply 'place offset y' if not 'deco place center y'
+ if (ceiling)
+ // Shift down so that schematic top layer is level with ceiling
+ // 'place offset y' is inverted
+ p.Y -= (place_offset_y + schematic->size.Y - 1);
+ else
+ p.Y += place_offset_y;
+ }
+
+ // Check schematic top and base are in voxelmanip
+ if (p.Y + schematic->size.Y - 1 > vm->m_area.MaxEdge.Y)
+ return 0;
+
+ if (p.Y < vm->m_area.MinEdge.Y)
+ return 0;
+
+ if (flags & DECO_PLACE_CENTER_X)
+ p.X -= (schematic->size.X - 1) / 2;
+ if (flags & DECO_PLACE_CENTER_Z)
+ p.Z -= (schematic->size.Z - 1) / 2;
+
+ Rotation rot = (rotation == ROTATE_RAND) ?
+ (Rotation)pr->range(ROTATE_0, ROTATE_270) : rotation;
+ bool force_placement = (flags & DECO_FORCE_PLACEMENT);
+
+ schematic->blitToVManip(vm, p, rot, force_placement);
+
+ return 1;
+}
diff --git a/src/mapgen/mg_decoration.h b/src/mapgen/mg_decoration.h
new file mode 100644
index 000000000..1ca632f25
--- /dev/null
+++ b/src/mapgen/mg_decoration.h
@@ -0,0 +1,136 @@
+/*
+Minetest
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <unordered_set>
+#include "objdef.h"
+#include "noise.h"
+#include "nodedef.h"
+
+class Mapgen;
+class MMVManip;
+class PcgRandom;
+class Schematic;
+
+enum DecorationType {
+ DECO_SIMPLE,
+ DECO_SCHEMATIC,
+ DECO_LSYSTEM
+};
+
+#define DECO_PLACE_CENTER_X 0x01
+#define DECO_PLACE_CENTER_Y 0x02
+#define DECO_PLACE_CENTER_Z 0x04
+#define DECO_USE_NOISE 0x08
+#define DECO_FORCE_PLACEMENT 0x10
+#define DECO_LIQUID_SURFACE 0x20
+#define DECO_ALL_FLOORS 0x40
+#define DECO_ALL_CEILINGS 0x80
+
+extern FlagDesc flagdesc_deco[];
+
+
+class Decoration : public ObjDef, public NodeResolver {
+public:
+ Decoration() = default;
+ virtual ~Decoration() = default;
+
+ virtual void resolveNodeNames();
+
+ bool canPlaceDecoration(MMVManip *vm, v3s16 p);
+ size_t placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
+
+ virtual size_t generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling) = 0;
+
+ u32 flags = 0;
+ int mapseed = 0;
+ std::vector<content_t> c_place_on;
+ s16 sidelen = 1;
+ s16 y_min;
+ s16 y_max;
+ float fill_ratio = 0.0f;
+ NoiseParams np;
+ std::vector<content_t> c_spawnby;
+ s16 nspawnby;
+ s16 place_offset_y = 0;
+
+ std::unordered_set<u8> biomes;
+};
+
+
+class DecoSimple : public Decoration {
+public:
+ virtual void resolveNodeNames();
+ virtual size_t generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling);
+
+ std::vector<content_t> c_decos;
+ s16 deco_height;
+ s16 deco_height_max;
+ u8 deco_param2;
+ u8 deco_param2_max;
+};
+
+
+class DecoSchematic : public Decoration {
+public:
+ DecoSchematic() = default;
+
+ virtual size_t generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling);
+
+ Rotation rotation;
+ Schematic *schematic = nullptr;
+};
+
+
+/*
+class DecoLSystem : public Decoration {
+public:
+ virtual void generate(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
+};
+*/
+
+
+class DecorationManager : public ObjDefManager {
+public:
+ DecorationManager(IGameDef *gamedef);
+ virtual ~DecorationManager() = default;
+
+ const char *getObjectTitle() const
+ {
+ return "decoration";
+ }
+
+ static Decoration *create(DecorationType type)
+ {
+ switch (type) {
+ case DECO_SIMPLE:
+ return new DecoSimple;
+ case DECO_SCHEMATIC:
+ return new DecoSchematic;
+ //case DECO_LSYSTEM:
+ // return new DecoLSystem;
+ default:
+ return NULL;
+ }
+ }
+
+ size_t placeAllDecos(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
+};
diff --git a/src/mapgen/mg_ore.cpp b/src/mapgen/mg_ore.cpp
new file mode 100644
index 000000000..979135ed4
--- /dev/null
+++ b/src/mapgen/mg_ore.cpp
@@ -0,0 +1,483 @@
+/*
+Minetest
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "mg_ore.h"
+#include "mapgen.h"
+#include "noise.h"
+#include "map.h"
+#include "log.h"
+#include "util/numeric.h"
+#include <algorithm>
+
+
+FlagDesc flagdesc_ore[] = {
+ {"absheight", OREFLAG_ABSHEIGHT}, // Non-functional
+ {"puff_cliffs", OREFLAG_PUFF_CLIFFS},
+ {"puff_additive_composition", OREFLAG_PUFF_ADDITIVE},
+ {NULL, 0}
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+OreManager::OreManager(IGameDef *gamedef) :
+ ObjDefManager(gamedef, OBJDEF_ORE)
+{
+}
+
+
+size_t OreManager::placeAllOres(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax)
+{
+ size_t nplaced = 0;
+
+ for (size_t i = 0; i != m_objects.size(); i++) {
+ Ore *ore = (Ore *)m_objects[i];
+ if (!ore)
+ continue;
+
+ nplaced += ore->placeOre(mg, blockseed, nmin, nmax);
+ blockseed++;
+ }
+
+ return nplaced;
+}
+
+
+void OreManager::clear()
+{
+ for (ObjDef *object : m_objects) {
+ Ore *ore = (Ore *) object;
+ delete ore;
+ }
+ m_objects.clear();
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+Ore::~Ore()
+{
+ delete noise;
+}
+
+
+void Ore::resolveNodeNames()
+{
+ getIdFromNrBacklog(&c_ore, "", CONTENT_AIR);
+ getIdsFromNrBacklog(&c_wherein);
+}
+
+
+size_t Ore::placeOre(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax)
+{
+ if (nmin.Y > y_max || nmax.Y < y_min)
+ return 0;
+
+ int actual_ymin = MYMAX(nmin.Y, y_min);
+ int actual_ymax = MYMIN(nmax.Y, y_max);
+ if (clust_size >= actual_ymax - actual_ymin + 1)
+ return 0;
+
+ nmin.Y = actual_ymin;
+ nmax.Y = actual_ymax;
+ generate(mg->vm, mg->seed, blockseed, nmin, nmax, mg->biomemap);
+
+ return 1;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+void OreScatter::generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap)
+{
+ PcgRandom pr(blockseed);
+ MapNode n_ore(c_ore, 0, ore_param2);
+
+ u32 sizex = (nmax.X - nmin.X + 1);
+ u32 volume = (nmax.X - nmin.X + 1) *
+ (nmax.Y - nmin.Y + 1) *
+ (nmax.Z - nmin.Z + 1);
+ u32 csize = clust_size;
+ u32 cvolume = csize * csize * csize;
+ u32 nclusters = volume / clust_scarcity;
+
+ for (u32 i = 0; i != nclusters; i++) {
+ int x0 = pr.range(nmin.X, nmax.X - csize + 1);
+ int y0 = pr.range(nmin.Y, nmax.Y - csize + 1);
+ int z0 = pr.range(nmin.Z, nmax.Z - csize + 1);
+
+ if ((flags & OREFLAG_USE_NOISE) &&
+ (NoisePerlin3D(&np, x0, y0, z0, mapseed) < nthresh))
+ continue;
+
+ if (biomemap && !biomes.empty()) {
+ u32 index = sizex * (z0 - nmin.Z) + (x0 - nmin.X);
+ std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]);
+ if (it == biomes.end())
+ continue;
+ }
+
+ for (u32 z1 = 0; z1 != csize; z1++)
+ for (u32 y1 = 0; y1 != csize; y1++)
+ for (u32 x1 = 0; x1 != csize; x1++) {
+ if (pr.range(1, cvolume) > clust_num_ores)
+ continue;
+
+ u32 i = vm->m_area.index(x0 + x1, y0 + y1, z0 + z1);
+ if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
+ continue;
+
+ vm->m_data[i] = n_ore;
+ }
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+void OreSheet::generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap)
+{
+ PcgRandom pr(blockseed + 4234);
+ MapNode n_ore(c_ore, 0, ore_param2);
+
+ u16 max_height = column_height_max;
+ int y_start_min = nmin.Y + max_height;
+ int y_start_max = nmax.Y - max_height;
+
+ int y_start = y_start_min < y_start_max ?
+ pr.range(y_start_min, y_start_max) :
+ (y_start_min + y_start_max) / 2;
+
+ if (!noise) {
+ int sx = nmax.X - nmin.X + 1;
+ int sz = nmax.Z - nmin.Z + 1;
+ noise = new Noise(&np, 0, sx, sz);
+ }
+ noise->seed = mapseed + y_start;
+ noise->perlinMap2D(nmin.X, nmin.Z);
+
+ size_t index = 0;
+ for (int z = nmin.Z; z <= nmax.Z; z++)
+ for (int x = nmin.X; x <= nmax.X; x++, index++) {
+ float noiseval = noise->result[index];
+ if (noiseval < nthresh)
+ continue;
+
+ if (biomemap && !biomes.empty()) {
+ std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]);
+ if (it == biomes.end())
+ continue;
+ }
+
+ u16 height = pr.range(column_height_min, column_height_max);
+ int ymidpoint = y_start + noiseval;
+ int y0 = MYMAX(nmin.Y, ymidpoint - height * (1 - column_midpoint_factor));
+ int y1 = MYMIN(nmax.Y, y0 + height - 1);
+
+ for (int y = y0; y <= y1; y++) {
+ u32 i = vm->m_area.index(x, y, z);
+ if (!vm->m_area.contains(i))
+ continue;
+ if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
+ continue;
+
+ vm->m_data[i] = n_ore;
+ }
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+OrePuff::~OrePuff()
+{
+ delete noise_puff_top;
+ delete noise_puff_bottom;
+}
+
+
+void OrePuff::generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap)
+{
+ PcgRandom pr(blockseed + 4234);
+ MapNode n_ore(c_ore, 0, ore_param2);
+
+ int y_start = pr.range(nmin.Y, nmax.Y);
+
+ if (!noise) {
+ int sx = nmax.X - nmin.X + 1;
+ int sz = nmax.Z - nmin.Z + 1;
+ noise = new Noise(&np, 0, sx, sz);
+ noise_puff_top = new Noise(&np_puff_top, 0, sx, sz);
+ noise_puff_bottom = new Noise(&np_puff_bottom, 0, sx, sz);
+ }
+
+ noise->seed = mapseed + y_start;
+ noise->perlinMap2D(nmin.X, nmin.Z);
+ bool noise_generated = false;
+
+ size_t index = 0;
+ for (int z = nmin.Z; z <= nmax.Z; z++)
+ for (int x = nmin.X; x <= nmax.X; x++, index++) {
+ float noiseval = noise->result[index];
+ if (noiseval < nthresh)
+ continue;
+
+ if (biomemap && !biomes.empty()) {
+ std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]);
+ if (it == biomes.end())
+ continue;
+ }
+
+ if (!noise_generated) {
+ noise_generated = true;
+ noise_puff_top->perlinMap2D(nmin.X, nmin.Z);
+ noise_puff_bottom->perlinMap2D(nmin.X, nmin.Z);
+ }
+
+ float ntop = noise_puff_top->result[index];
+ float nbottom = noise_puff_bottom->result[index];
+
+ if (!(flags & OREFLAG_PUFF_CLIFFS)) {
+ float ndiff = noiseval - nthresh;
+ if (ndiff < 1.0f) {
+ ntop *= ndiff;
+ nbottom *= ndiff;
+ }
+ }
+
+ int ymid = y_start;
+ int y0 = ymid - nbottom;
+ int y1 = ymid + ntop;
+
+ if ((flags & OREFLAG_PUFF_ADDITIVE) && (y0 > y1))
+ SWAP(int, y0, y1);
+
+ for (int y = y0; y <= y1; y++) {
+ u32 i = vm->m_area.index(x, y, z);
+ if (!vm->m_area.contains(i))
+ continue;
+ if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
+ continue;
+
+ vm->m_data[i] = n_ore;
+ }
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+void OreBlob::generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap)
+{
+ PcgRandom pr(blockseed + 2404);
+ MapNode n_ore(c_ore, 0, ore_param2);
+
+ u32 sizex = (nmax.X - nmin.X + 1);
+ u32 volume = (nmax.X - nmin.X + 1) *
+ (nmax.Y - nmin.Y + 1) *
+ (nmax.Z - nmin.Z + 1);
+ u32 csize = clust_size;
+ u32 nblobs = volume / clust_scarcity;
+
+ if (!noise)
+ noise = new Noise(&np, mapseed, csize, csize, csize);
+
+ for (u32 i = 0; i != nblobs; i++) {
+ int x0 = pr.range(nmin.X, nmax.X - csize + 1);
+ int y0 = pr.range(nmin.Y, nmax.Y - csize + 1);
+ int z0 = pr.range(nmin.Z, nmax.Z - csize + 1);
+
+ if (biomemap && !biomes.empty()) {
+ u32 bmapidx = sizex * (z0 - nmin.Z) + (x0 - nmin.X);
+ std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[bmapidx]);
+ if (it == biomes.end())
+ continue;
+ }
+
+ bool noise_generated = false;
+ noise->seed = blockseed + i;
+
+ size_t index = 0;
+ for (u32 z1 = 0; z1 != csize; z1++)
+ for (u32 y1 = 0; y1 != csize; y1++)
+ for (u32 x1 = 0; x1 != csize; x1++, index++) {
+ u32 i = vm->m_area.index(x0 + x1, y0 + y1, z0 + z1);
+ if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
+ continue;
+
+ // Lazily generate noise only if there's a chance of ore being placed
+ // This simple optimization makes calls 6x faster on average
+ if (!noise_generated) {
+ noise_generated = true;
+ noise->perlinMap3D(x0, y0, z0);
+ }
+
+ float noiseval = noise->result[index];
+
+ float xdist = (s32)x1 - (s32)csize / 2;
+ float ydist = (s32)y1 - (s32)csize / 2;
+ float zdist = (s32)z1 - (s32)csize / 2;
+
+ noiseval -= (sqrt(xdist * xdist + ydist * ydist + zdist * zdist) / csize);
+
+ if (noiseval < nthresh)
+ continue;
+
+ vm->m_data[i] = n_ore;
+ }
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+OreVein::~OreVein()
+{
+ delete noise2;
+}
+
+
+void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap)
+{
+ PcgRandom pr(blockseed + 520);
+ MapNode n_ore(c_ore, 0, ore_param2);
+
+ u32 sizex = (nmax.X - nmin.X + 1);
+
+ if (!noise) {
+ int sx = nmax.X - nmin.X + 1;
+ int sy = nmax.Y - nmin.Y + 1;
+ int sz = nmax.Z - nmin.Z + 1;
+ noise = new Noise(&np, mapseed, sx, sy, sz);
+ noise2 = new Noise(&np, mapseed + 436, sx, sy, sz);
+ }
+ bool noise_generated = false;
+
+ size_t index = 0;
+ for (int z = nmin.Z; z <= nmax.Z; z++)
+ for (int y = nmin.Y; y <= nmax.Y; y++)
+ for (int x = nmin.X; x <= nmax.X; x++, index++) {
+ u32 i = vm->m_area.index(x, y, z);
+ if (!vm->m_area.contains(i))
+ continue;
+ if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
+ continue;
+
+ if (biomemap && !biomes.empty()) {
+ u32 bmapidx = sizex * (z - nmin.Z) + (x - nmin.X);
+ std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[bmapidx]);
+ if (it == biomes.end())
+ continue;
+ }
+
+ // Same lazy generation optimization as in OreBlob
+ if (!noise_generated) {
+ noise_generated = true;
+ noise->perlinMap3D(nmin.X, nmin.Y, nmin.Z);
+ noise2->perlinMap3D(nmin.X, nmin.Y, nmin.Z);
+ }
+
+ // randval ranges from -1..1
+ float randval = (float)pr.next() / (pr.RANDOM_RANGE / 2) - 1.f;
+ float noiseval = contour(noise->result[index]);
+ float noiseval2 = contour(noise2->result[index]);
+ if (noiseval * noiseval2 + randval * random_factor < nthresh)
+ continue;
+
+ vm->m_data[i] = n_ore;
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+OreStratum::~OreStratum()
+{
+ delete noise_stratum_thickness;
+}
+
+
+void OreStratum::generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap)
+{
+ PcgRandom pr(blockseed + 4234);
+ MapNode n_ore(c_ore, 0, ore_param2);
+
+ if (flags & OREFLAG_USE_NOISE) {
+ if (!(noise && noise_stratum_thickness)) {
+ int sx = nmax.X - nmin.X + 1;
+ int sz = nmax.Z - nmin.Z + 1;
+ noise = new Noise(&np, 0, sx, sz);
+ noise_stratum_thickness = new Noise(&np_stratum_thickness, 0, sx, sz);
+ }
+ noise->perlinMap2D(nmin.X, nmin.Z);
+ noise_stratum_thickness->perlinMap2D(nmin.X, nmin.Z);
+ }
+
+ size_t index = 0;
+
+ for (int z = nmin.Z; z <= nmax.Z; z++)
+ for (int x = nmin.X; x <= nmax.X; x++, index++) {
+ if (biomemap && !biomes.empty()) {
+ std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]);
+ if (it == biomes.end())
+ continue;
+ }
+
+ int y0;
+ int y1;
+
+ if (flags & OREFLAG_USE_NOISE) {
+ float nmid = noise->result[index];
+ float nhalfthick = noise_stratum_thickness->result[index] / 2.0f;
+ y0 = MYMAX(nmin.Y, nmid - nhalfthick);
+ y1 = MYMIN(nmax.Y, nmid + nhalfthick);
+ } else {
+ y0 = nmin.Y;
+ y1 = nmax.Y;
+ }
+
+ for (int y = y0; y <= y1; y++) {
+ if (pr.range(1, clust_scarcity) != 1)
+ continue;
+
+ u32 i = vm->m_area.index(x, y, z);
+ if (!vm->m_area.contains(i))
+ continue;
+ if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
+ continue;
+
+ vm->m_data[i] = n_ore;
+ }
+ }
+}
diff --git a/src/mapgen/mg_ore.h b/src/mapgen/mg_ore.h
new file mode 100644
index 000000000..e715f348b
--- /dev/null
+++ b/src/mapgen/mg_ore.h
@@ -0,0 +1,183 @@
+/*
+Minetest
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <unordered_set>
+#include "objdef.h"
+#include "noise.h"
+#include "nodedef.h"
+
+class Noise;
+class Mapgen;
+class MMVManip;
+
+/////////////////// Ore generation flags
+
+#define OREFLAG_ABSHEIGHT 0x01 // Non-functional but kept to not break flags
+#define OREFLAG_PUFF_CLIFFS 0x02
+#define OREFLAG_PUFF_ADDITIVE 0x04
+#define OREFLAG_USE_NOISE 0x08
+
+enum OreType {
+ ORE_SCATTER,
+ ORE_SHEET,
+ ORE_PUFF,
+ ORE_BLOB,
+ ORE_VEIN,
+ ORE_STRATUM,
+};
+
+extern FlagDesc flagdesc_ore[];
+
+class Ore : public ObjDef, public NodeResolver {
+public:
+ static const bool NEEDS_NOISE = false;
+
+ content_t c_ore; // the node to place
+ std::vector<content_t> c_wherein; // the nodes to be placed in
+ u32 clust_scarcity; // ore cluster has a 1-in-clust_scarcity chance of appearing at a node
+ s16 clust_num_ores; // how many ore nodes are in a chunk
+ s16 clust_size; // how large (in nodes) a chunk of ore is
+ s16 y_min;
+ s16 y_max;
+ u8 ore_param2; // to set node-specific attributes
+ u32 flags = 0; // attributes for this ore
+ float nthresh; // threshold for noise at which an ore is placed
+ NoiseParams np; // noise for distribution of clusters (NULL for uniform scattering)
+ Noise *noise = nullptr;
+ std::unordered_set<u8> biomes;
+
+ Ore() = default;;
+ virtual ~Ore();
+
+ virtual void resolveNodeNames();
+
+ size_t placeOre(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
+ virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap) = 0;
+};
+
+class OreScatter : public Ore {
+public:
+ static const bool NEEDS_NOISE = false;
+
+ virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap);
+};
+
+class OreSheet : public Ore {
+public:
+ static const bool NEEDS_NOISE = true;
+
+ u16 column_height_min;
+ u16 column_height_max;
+ float column_midpoint_factor;
+
+ virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap);
+};
+
+class OrePuff : public Ore {
+public:
+ static const bool NEEDS_NOISE = true;
+
+ NoiseParams np_puff_top;
+ NoiseParams np_puff_bottom;
+ Noise *noise_puff_top = nullptr;
+ Noise *noise_puff_bottom = nullptr;
+
+ OrePuff() = default;
+ virtual ~OrePuff();
+
+ virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap);
+};
+
+class OreBlob : public Ore {
+public:
+ static const bool NEEDS_NOISE = true;
+
+ virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap);
+};
+
+class OreVein : public Ore {
+public:
+ static const bool NEEDS_NOISE = true;
+
+ float random_factor;
+ Noise *noise2 = nullptr;
+
+ OreVein() = default;
+ virtual ~OreVein();
+
+ virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap);
+};
+
+class OreStratum : public Ore {
+public:
+ static const bool NEEDS_NOISE = false;
+
+ NoiseParams np_stratum_thickness;
+ Noise *noise_stratum_thickness = nullptr;
+
+ OreStratum() = default;
+ virtual ~OreStratum();
+
+ virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap);
+};
+
+class OreManager : public ObjDefManager {
+public:
+ OreManager(IGameDef *gamedef);
+ virtual ~OreManager() = default;
+
+ const char *getObjectTitle() const
+ {
+ return "ore";
+ }
+
+ static Ore *create(OreType type)
+ {
+ switch (type) {
+ case ORE_SCATTER:
+ return new OreScatter;
+ case ORE_SHEET:
+ return new OreSheet;
+ case ORE_PUFF:
+ return new OrePuff;
+ case ORE_BLOB:
+ return new OreBlob;
+ case ORE_VEIN:
+ return new OreVein;
+ case ORE_STRATUM:
+ return new OreStratum;
+ default:
+ return nullptr;
+ }
+ }
+
+ void clear();
+
+ size_t placeAllOres(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
+};
diff --git a/src/mapgen/mg_schematic.cpp b/src/mapgen/mg_schematic.cpp
new file mode 100644
index 000000000..8874abd42
--- /dev/null
+++ b/src/mapgen/mg_schematic.cpp
@@ -0,0 +1,578 @@
+/*
+Minetest
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <fstream>
+#include <typeinfo>
+#include "mg_schematic.h"
+#include "server.h"
+#include "mapgen.h"
+#include "emerge.h"
+#include "map.h"
+#include "mapblock.h"
+#include "log.h"
+#include "util/numeric.h"
+#include "util/serialize.h"
+#include "serialization.h"
+#include "filesys.h"
+#include "voxelalgorithms.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+SchematicManager::SchematicManager(Server *server) :
+ ObjDefManager(server, OBJDEF_SCHEMATIC),
+ m_server(server)
+{
+}
+
+
+void SchematicManager::clear()
+{
+ EmergeManager *emerge = m_server->getEmergeManager();
+
+ // Remove all dangling references in Decorations
+ DecorationManager *decomgr = emerge->decomgr;
+ for (size_t i = 0; i != decomgr->getNumObjects(); i++) {
+ Decoration *deco = (Decoration *)decomgr->getRaw(i);
+
+ try {
+ DecoSchematic *dschem = dynamic_cast<DecoSchematic *>(deco);
+ if (dschem)
+ dschem->schematic = NULL;
+ } catch (const std::bad_cast &) {
+ }
+ }
+
+ ObjDefManager::clear();
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+Schematic::Schematic()
+= default;
+
+
+Schematic::~Schematic()
+{
+ delete []schemdata;
+ delete []slice_probs;
+}
+
+
+void Schematic::resolveNodeNames()
+{
+ getIdsFromNrBacklog(&c_nodes, true, CONTENT_AIR);
+
+ size_t bufsize = size.X * size.Y * size.Z;
+ for (size_t i = 0; i != bufsize; i++) {
+ content_t c_original = schemdata[i].getContent();
+ content_t c_new = c_nodes[c_original];
+ schemdata[i].setContent(c_new);
+ }
+}
+
+
+void Schematic::blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place)
+{
+ sanity_check(m_ndef != NULL);
+
+ int xstride = 1;
+ int ystride = size.X;
+ int zstride = size.X * size.Y;
+
+ s16 sx = size.X;
+ s16 sy = size.Y;
+ s16 sz = size.Z;
+
+ int i_start, i_step_x, i_step_z;
+ switch (rot) {
+ case ROTATE_90:
+ i_start = sx - 1;
+ i_step_x = zstride;
+ i_step_z = -xstride;
+ SWAP(s16, sx, sz);
+ break;
+ case ROTATE_180:
+ i_start = zstride * (sz - 1) + sx - 1;
+ i_step_x = -xstride;
+ i_step_z = -zstride;
+ break;
+ case ROTATE_270:
+ i_start = zstride * (sz - 1);
+ i_step_x = -zstride;
+ i_step_z = xstride;
+ SWAP(s16, sx, sz);
+ break;
+ default:
+ i_start = 0;
+ i_step_x = xstride;
+ i_step_z = zstride;
+ }
+
+ s16 y_map = p.Y;
+ for (s16 y = 0; y != sy; y++) {
+ if ((slice_probs[y] != MTSCHEM_PROB_ALWAYS) &&
+ (slice_probs[y] <= myrand_range(1, MTSCHEM_PROB_ALWAYS)))
+ continue;
+
+ for (s16 z = 0; z != sz; z++) {
+ u32 i = z * i_step_z + y * ystride + i_start;
+ for (s16 x = 0; x != sx; x++, i += i_step_x) {
+ u32 vi = vm->m_area.index(p.X + x, y_map, p.Z + z);
+ if (!vm->m_area.contains(vi))
+ continue;
+
+ if (schemdata[i].getContent() == CONTENT_IGNORE)
+ continue;
+
+ u8 placement_prob = schemdata[i].param1 & MTSCHEM_PROB_MASK;
+ bool force_place_node = schemdata[i].param1 & MTSCHEM_FORCE_PLACE;
+
+ if (placement_prob == MTSCHEM_PROB_NEVER)
+ continue;
+
+ if (!force_place && !force_place_node) {
+ content_t c = vm->m_data[vi].getContent();
+ if (c != CONTENT_AIR && c != CONTENT_IGNORE)
+ continue;
+ }
+
+ if ((placement_prob != MTSCHEM_PROB_ALWAYS) &&
+ (placement_prob <= myrand_range(1, MTSCHEM_PROB_ALWAYS)))
+ continue;
+
+ vm->m_data[vi] = schemdata[i];
+ vm->m_data[vi].param1 = 0;
+
+ if (rot)
+ vm->m_data[vi].rotateAlongYAxis(m_ndef, rot);
+ }
+ }
+ y_map++;
+ }
+}
+
+
+bool Schematic::placeOnVManip(MMVManip *vm, v3s16 p, u32 flags,
+ Rotation rot, bool force_place)
+{
+ assert(vm != NULL);
+ assert(schemdata != NULL);
+ sanity_check(m_ndef != NULL);
+
+ //// Determine effective rotation and effective schematic dimensions
+ if (rot == ROTATE_RAND)
+ rot = (Rotation)myrand_range(ROTATE_0, ROTATE_270);
+
+ v3s16 s = (rot == ROTATE_90 || rot == ROTATE_270) ?
+ v3s16(size.Z, size.Y, size.X) : size;
+
+ //// Adjust placement position if necessary
+ if (flags & DECO_PLACE_CENTER_X)
+ p.X -= (s.X + 1) / 2;
+ if (flags & DECO_PLACE_CENTER_Y)
+ p.Y -= (s.Y + 1) / 2;
+ if (flags & DECO_PLACE_CENTER_Z)
+ p.Z -= (s.Z + 1) / 2;
+
+ blitToVManip(vm, p, rot, force_place);
+
+ return vm->m_area.contains(VoxelArea(p, p + s - v3s16(1,1,1)));
+}
+
+void Schematic::placeOnMap(ServerMap *map, v3s16 p, u32 flags,
+ Rotation rot, bool force_place)
+{
+ std::map<v3s16, MapBlock *> lighting_modified_blocks;
+ std::map<v3s16, MapBlock *> modified_blocks;
+ std::map<v3s16, MapBlock *>::iterator it;
+
+ assert(map != NULL);
+ assert(schemdata != NULL);
+ sanity_check(m_ndef != NULL);
+
+ //// Determine effective rotation and effective schematic dimensions
+ if (rot == ROTATE_RAND)
+ rot = (Rotation)myrand_range(ROTATE_0, ROTATE_270);
+
+ v3s16 s = (rot == ROTATE_90 || rot == ROTATE_270) ?
+ v3s16(size.Z, size.Y, size.X) : size;
+
+ //// Adjust placement position if necessary
+ if (flags & DECO_PLACE_CENTER_X)
+ p.X -= (s.X + 1) / 2;
+ if (flags & DECO_PLACE_CENTER_Y)
+ p.Y -= (s.Y + 1) / 2;
+ if (flags & DECO_PLACE_CENTER_Z)
+ p.Z -= (s.Z + 1) / 2;
+
+ //// Create VManip for effected area, emerge our area, modify area
+ //// inside VManip, then blit back.
+ v3s16 bp1 = getNodeBlockPos(p);
+ v3s16 bp2 = getNodeBlockPos(p + s - v3s16(1,1,1));
+
+ MMVManip vm(map);
+ vm.initialEmerge(bp1, bp2);
+
+ blitToVManip(&vm, p, rot, force_place);
+
+ voxalgo::blit_back_with_light(map, &vm, &modified_blocks);
+
+ //// Carry out post-map-modification actions
+
+ //// Create & dispatch map modification events to observers
+ MapEditEvent event;
+ event.type = MEET_OTHER;
+ for (it = modified_blocks.begin(); it != modified_blocks.end(); ++it)
+ event.modified_blocks.insert(it->first);
+
+ map->dispatchEvent(&event);
+}
+
+
+bool Schematic::deserializeFromMts(std::istream *is,
+ std::vector<std::string> *names)
+{
+ std::istream &ss = *is;
+ content_t cignore = CONTENT_IGNORE;
+ bool have_cignore = false;
+
+ //// Read signature
+ u32 signature = readU32(ss);
+ if (signature != MTSCHEM_FILE_SIGNATURE) {
+ errorstream << __FUNCTION__ << ": invalid schematic "
+ "file" << std::endl;
+ return false;
+ }
+
+ //// Read version
+ u16 version = readU16(ss);
+ if (version > MTSCHEM_FILE_VER_HIGHEST_READ) {
+ errorstream << __FUNCTION__ << ": unsupported schematic "
+ "file version" << std::endl;
+ return false;
+ }
+
+ //// Read size
+ size = readV3S16(ss);
+
+ //// Read Y-slice probability values
+ delete []slice_probs;
+ slice_probs = new u8[size.Y];
+ for (int y = 0; y != size.Y; y++)
+ slice_probs[y] = (version >= 3) ? readU8(ss) : MTSCHEM_PROB_ALWAYS_OLD;
+
+ //// Read node names
+ u16 nidmapcount = readU16(ss);
+ for (int i = 0; i != nidmapcount; i++) {
+ std::string name = deSerializeString(ss);
+
+ // Instances of "ignore" from v1 are converted to air (and instances
+ // are fixed to have MTSCHEM_PROB_NEVER later on).
+ if (name == "ignore") {
+ name = "air";
+ cignore = i;
+ have_cignore = true;
+ }
+
+ names->push_back(name);
+ }
+
+ //// Read node data
+ size_t nodecount = size.X * size.Y * size.Z;
+
+ delete []schemdata;
+ schemdata = new MapNode[nodecount];
+
+ MapNode::deSerializeBulk(ss, SER_FMT_VER_HIGHEST_READ, schemdata,
+ nodecount, 2, 2, true);
+
+ // Fix probability values for nodes that were ignore; removed in v2
+ if (version < 2) {
+ for (size_t i = 0; i != nodecount; i++) {
+ if (schemdata[i].param1 == 0)
+ schemdata[i].param1 = MTSCHEM_PROB_ALWAYS_OLD;
+ if (have_cignore && schemdata[i].getContent() == cignore)
+ schemdata[i].param1 = MTSCHEM_PROB_NEVER;
+ }
+ }
+
+ // Fix probability values for probability range truncation introduced in v4
+ if (version < 4) {
+ for (s16 y = 0; y != size.Y; y++)
+ slice_probs[y] >>= 1;
+ for (size_t i = 0; i != nodecount; i++)
+ schemdata[i].param1 >>= 1;
+ }
+
+ return true;
+}
+
+
+bool Schematic::serializeToMts(std::ostream *os,
+ const std::vector<std::string> &names)
+{
+ std::ostream &ss = *os;
+
+ writeU32(ss, MTSCHEM_FILE_SIGNATURE); // signature
+ writeU16(ss, MTSCHEM_FILE_VER_HIGHEST_WRITE); // version
+ writeV3S16(ss, size); // schematic size
+
+ for (int y = 0; y != size.Y; y++) // Y slice probabilities
+ writeU8(ss, slice_probs[y]);
+
+ writeU16(ss, names.size()); // name count
+ for (size_t i = 0; i != names.size(); i++)
+ ss << serializeString(names[i]); // node names
+
+ // compressed bulk node data
+ MapNode::serializeBulk(ss, SER_FMT_VER_HIGHEST_WRITE,
+ schemdata, size.X * size.Y * size.Z, 2, 2, true);
+
+ return true;
+}
+
+
+bool Schematic::serializeToLua(std::ostream *os,
+ const std::vector<std::string> &names, bool use_comments, u32 indent_spaces)
+{
+ std::ostream &ss = *os;
+
+ std::string indent("\t");
+ if (indent_spaces > 0)
+ indent.assign(indent_spaces, ' ');
+
+ //// Write header
+ {
+ ss << "schematic = {" << std::endl;
+ ss << indent << "size = "
+ << "{x=" << size.X
+ << ", y=" << size.Y
+ << ", z=" << size.Z
+ << "}," << std::endl;
+ }
+
+ //// Write y-slice probabilities
+ {
+ ss << indent << "yslice_prob = {" << std::endl;
+
+ for (u16 y = 0; y != size.Y; y++) {
+ u8 probability = slice_probs[y] & MTSCHEM_PROB_MASK;
+
+ ss << indent << indent << "{"
+ << "ypos=" << y
+ << ", prob=" << (u16)probability * 2
+ << "}," << std::endl;
+ }
+
+ ss << indent << "}," << std::endl;
+ }
+
+ //// Write node data
+ {
+ ss << indent << "data = {" << std::endl;
+
+ u32 i = 0;
+ for (u16 z = 0; z != size.Z; z++)
+ for (u16 y = 0; y != size.Y; y++) {
+ if (use_comments) {
+ ss << std::endl
+ << indent << indent
+ << "-- z=" << z
+ << ", y=" << y << std::endl;
+ }
+
+ for (u16 x = 0; x != size.X; x++, i++) {
+ u8 probability = schemdata[i].param1 & MTSCHEM_PROB_MASK;
+ bool force_place = schemdata[i].param1 & MTSCHEM_FORCE_PLACE;
+
+ ss << indent << indent << "{"
+ << "name=\"" << names[schemdata[i].getContent()]
+ << "\", prob=" << (u16)probability * 2
+ << ", param2=" << (u16)schemdata[i].param2;
+
+ if (force_place)
+ ss << ", force_place=true";
+
+ ss << "}," << std::endl;
+ }
+ }
+
+ ss << indent << "}," << std::endl;
+ }
+
+ ss << "}" << std::endl;
+
+ return true;
+}
+
+
+bool Schematic::loadSchematicFromFile(const std::string &filename,
+ INodeDefManager *ndef, StringMap *replace_names)
+{
+ std::ifstream is(filename.c_str(), std::ios_base::binary);
+ if (!is.good()) {
+ errorstream << __FUNCTION__ << ": unable to open file '"
+ << filename << "'" << std::endl;
+ return false;
+ }
+
+ size_t origsize = m_nodenames.size();
+ if (!deserializeFromMts(&is, &m_nodenames))
+ return false;
+
+ m_nnlistsizes.push_back(m_nodenames.size() - origsize);
+
+ name = filename;
+
+ if (replace_names) {
+ for (size_t i = origsize; i < m_nodenames.size(); i++) {
+ std::string &node_name = m_nodenames[i];
+ StringMap::iterator it = replace_names->find(node_name);
+ if (it != replace_names->end())
+ node_name = it->second;
+ }
+ }
+
+ if (ndef)
+ ndef->pendNodeResolve(this);
+
+ return true;
+}
+
+
+bool Schematic::saveSchematicToFile(const std::string &filename,
+ INodeDefManager *ndef)
+{
+ MapNode *orig_schemdata = schemdata;
+ std::vector<std::string> ndef_nodenames;
+ std::vector<std::string> *names;
+
+ if (m_resolve_done && ndef == NULL)
+ ndef = m_ndef;
+
+ if (ndef) {
+ names = &ndef_nodenames;
+
+ u32 volume = size.X * size.Y * size.Z;
+ schemdata = new MapNode[volume];
+ for (u32 i = 0; i != volume; i++)
+ schemdata[i] = orig_schemdata[i];
+
+ generate_nodelist_and_update_ids(schemdata, volume, names, ndef);
+ } else { // otherwise, use the names we have on hand in the list
+ names = &m_nodenames;
+ }
+
+ std::ostringstream os(std::ios_base::binary);
+ bool status = serializeToMts(&os, *names);
+
+ if (ndef) {
+ delete []schemdata;
+ schemdata = orig_schemdata;
+ }
+
+ if (!status)
+ return false;
+
+ return fs::safeWriteToFile(filename, os.str());
+}
+
+
+bool Schematic::getSchematicFromMap(Map *map, v3s16 p1, v3s16 p2)
+{
+ MMVManip *vm = new MMVManip(map);
+
+ v3s16 bp1 = getNodeBlockPos(p1);
+ v3s16 bp2 = getNodeBlockPos(p2);
+ vm->initialEmerge(bp1, bp2);
+
+ size = p2 - p1 + 1;
+
+ slice_probs = new u8[size.Y];
+ for (s16 y = 0; y != size.Y; y++)
+ slice_probs[y] = MTSCHEM_PROB_ALWAYS;
+
+ schemdata = new MapNode[size.X * size.Y * size.Z];
+
+ u32 i = 0;
+ for (s16 z = p1.Z; z <= p2.Z; z++)
+ for (s16 y = p1.Y; y <= p2.Y; y++) {
+ u32 vi = vm->m_area.index(p1.X, y, z);
+ for (s16 x = p1.X; x <= p2.X; x++, i++, vi++) {
+ schemdata[i] = vm->m_data[vi];
+ schemdata[i].param1 = MTSCHEM_PROB_ALWAYS;
+ }
+ }
+
+ delete vm;
+ return true;
+}
+
+
+void Schematic::applyProbabilities(v3s16 p0,
+ std::vector<std::pair<v3s16, u8> > *plist,
+ std::vector<std::pair<s16, u8> > *splist)
+{
+ for (size_t i = 0; i != plist->size(); i++) {
+ v3s16 p = (*plist)[i].first - p0;
+ int index = p.Z * (size.Y * size.X) + p.Y * size.X + p.X;
+ if (index < size.Z * size.Y * size.X) {
+ u8 prob = (*plist)[i].second;
+ schemdata[index].param1 = prob;
+
+ // trim unnecessary node names from schematic
+ if (prob == MTSCHEM_PROB_NEVER)
+ schemdata[index].setContent(CONTENT_AIR);
+ }
+ }
+
+ for (size_t i = 0; i != splist->size(); i++) {
+ s16 y = (*splist)[i].first - p0.Y;
+ slice_probs[y] = (*splist)[i].second;
+ }
+}
+
+
+void generate_nodelist_and_update_ids(MapNode *nodes, size_t nodecount,
+ std::vector<std::string> *usednodes, INodeDefManager *ndef)
+{
+ std::unordered_map<content_t, content_t> nodeidmap;
+ content_t numids = 0;
+
+ for (size_t i = 0; i != nodecount; i++) {
+ content_t id;
+ content_t c = nodes[i].getContent();
+
+ std::unordered_map<content_t, content_t>::const_iterator it = nodeidmap.find(c);
+ if (it == nodeidmap.end()) {
+ id = numids;
+ numids++;
+
+ usednodes->push_back(ndef->get(c).name);
+ nodeidmap.insert(std::make_pair(c, id));
+ } else {
+ id = it->second;
+ }
+ nodes[i].setContent(id);
+ }
+}
diff --git a/src/mapgen/mg_schematic.h b/src/mapgen/mg_schematic.h
new file mode 100644
index 000000000..069b594f1
--- /dev/null
+++ b/src/mapgen/mg_schematic.h
@@ -0,0 +1,147 @@
+/*
+Minetest
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <map>
+#include "mg_decoration.h"
+#include "util/string.h"
+
+class Map;
+class ServerMap;
+class Mapgen;
+class MMVManip;
+class PseudoRandom;
+class NodeResolver;
+class Server;
+
+/*
+ Minetest Schematic File Format
+
+ All values are stored in big-endian byte order.
+ [u32] signature: 'MTSM'
+ [u16] version: 4
+ [u16] size X
+ [u16] size Y
+ [u16] size Z
+ For each Y:
+ [u8] slice probability value
+ [Name-ID table] Name ID Mapping Table
+ [u16] name-id count
+ For each name-id mapping:
+ [u16] name length
+ [u8[]] name
+ ZLib deflated {
+ For each node in schematic: (for z, y, x)
+ [u16] content
+ For each node in schematic:
+ [u8] param1
+ bit 0-6: probability
+ bit 7: specific node force placement
+ For each node in schematic:
+ [u8] param2
+ }
+
+ Version changes:
+ 1 - Initial version
+ 2 - Fixed messy never/always place; 0 probability is now never, 0xFF is always
+ 3 - Added y-slice probabilities; this allows for variable height structures
+ 4 - Compressed range of node occurence prob., added per-node force placement bit
+*/
+
+//// Schematic constants
+#define MTSCHEM_FILE_SIGNATURE 0x4d54534d // 'MTSM'
+#define MTSCHEM_FILE_VER_HIGHEST_READ 4
+#define MTSCHEM_FILE_VER_HIGHEST_WRITE 4
+
+#define MTSCHEM_PROB_MASK 0x7F
+
+#define MTSCHEM_PROB_NEVER 0x00
+#define MTSCHEM_PROB_ALWAYS 0x7F
+#define MTSCHEM_PROB_ALWAYS_OLD 0xFF
+
+#define MTSCHEM_FORCE_PLACE 0x80
+
+enum SchematicType
+{
+ SCHEMATIC_NORMAL,
+};
+
+enum SchematicFormatType {
+ SCHEM_FMT_HANDLE,
+ SCHEM_FMT_MTS,
+ SCHEM_FMT_LUA,
+};
+
+class Schematic : public ObjDef, public NodeResolver {
+public:
+ Schematic();
+ virtual ~Schematic();
+
+ virtual void resolveNodeNames();
+
+ bool loadSchematicFromFile(const std::string &filename, INodeDefManager *ndef,
+ StringMap *replace_names=NULL);
+ bool saveSchematicToFile(const std::string &filename, INodeDefManager *ndef);
+ bool getSchematicFromMap(Map *map, v3s16 p1, v3s16 p2);
+
+ bool deserializeFromMts(std::istream *is, std::vector<std::string> *names);
+ bool serializeToMts(std::ostream *os, const std::vector<std::string> &names);
+ bool serializeToLua(std::ostream *os, const std::vector<std::string> &names,
+ bool use_comments, u32 indent_spaces);
+
+ void blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place);
+ bool placeOnVManip(MMVManip *vm, v3s16 p, u32 flags, Rotation rot, bool force_place);
+ void placeOnMap(ServerMap *map, v3s16 p, u32 flags, Rotation rot, bool force_place);
+
+ void applyProbabilities(v3s16 p0,
+ std::vector<std::pair<v3s16, u8> > *plist,
+ std::vector<std::pair<s16, u8> > *splist);
+
+ std::vector<content_t> c_nodes;
+ u32 flags = 0;
+ v3s16 size;
+ MapNode *schemdata = nullptr;
+ u8 *slice_probs = nullptr;
+};
+
+class SchematicManager : public ObjDefManager {
+public:
+ SchematicManager(Server *server);
+ virtual ~SchematicManager() = default;
+
+ virtual void clear();
+
+ const char *getObjectTitle() const
+ {
+ return "schematic";
+ }
+
+ static Schematic *create(SchematicType type)
+ {
+ return new Schematic;
+ }
+
+private:
+ Server *m_server;
+};
+
+void generate_nodelist_and_update_ids(MapNode *nodes, size_t nodecount,
+ std::vector<std::string> *usednodes, INodeDefManager *ndef);
diff --git a/src/mapgen/treegen.cpp b/src/mapgen/treegen.cpp
new file mode 100644
index 000000000..9e11b1a06
--- /dev/null
+++ b/src/mapgen/treegen.cpp
@@ -0,0 +1,872 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>,
+ 2012-2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "irr_v3d.h"
+#include <stack>
+#include "util/pointer.h"
+#include "util/numeric.h"
+#include "map.h"
+#include "mapblock.h"
+#include "serverenvironment.h"
+#include "nodedef.h"
+#include "treegen.h"
+#include "voxelalgorithms.h"
+
+namespace treegen
+{
+
+void make_tree(MMVManip &vmanip, v3s16 p0,
+ bool is_apple_tree, INodeDefManager *ndef, s32 seed)
+{
+ /*
+ NOTE: Tree-placing code is currently duplicated in the engine
+ and in games that have saplings; both are deprecated but not
+ replaced yet
+ */
+ MapNode treenode(ndef->getId("mapgen_tree"));
+ MapNode leavesnode(ndef->getId("mapgen_leaves"));
+ MapNode applenode(ndef->getId("mapgen_apple"));
+
+ PseudoRandom pr(seed);
+ s16 trunk_h = pr.range(4, 5);
+ v3s16 p1 = p0;
+ for (s16 ii = 0; ii < trunk_h; ii++) {
+ if (vmanip.m_area.contains(p1)) {
+ u32 vi = vmanip.m_area.index(p1);
+ vmanip.m_data[vi] = treenode;
+ }
+ p1.Y++;
+ }
+
+ // p1 is now the last piece of the trunk
+ p1.Y -= 1;
+
+ VoxelArea leaves_a(v3s16(-2, -1, -2), v3s16(2, 2, 2));
+ Buffer<u8> leaves_d(leaves_a.getVolume());
+ for (s32 i = 0; i < leaves_a.getVolume(); i++)
+ leaves_d[i] = 0;
+
+ // Force leaves at near the end of the trunk
+ s16 d = 1;
+ for (s16 z = -d; z <= d; z++)
+ for (s16 y = -d; y <= d; y++)
+ for (s16 x = -d; x <= d; x++) {
+ leaves_d[leaves_a.index(v3s16(x, y, z))] = 1;
+ }
+
+ // Add leaves randomly
+ for (u32 iii = 0; iii < 7; iii++) {
+ v3s16 p(
+ pr.range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X - d),
+ pr.range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y - d),
+ pr.range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z - d)
+ );
+
+ for (s16 z = 0; z <= d; z++)
+ for (s16 y = 0; y <= d; y++)
+ for (s16 x = 0; x <= d; x++) {
+ leaves_d[leaves_a.index(p + v3s16(x, y, z))] = 1;
+ }
+ }
+
+ // Blit leaves to vmanip
+ for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++)
+ for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) {
+ v3s16 pmin(leaves_a.MinEdge.X, y, z);
+ u32 i = leaves_a.index(pmin);
+ u32 vi = vmanip.m_area.index(pmin + p1);
+ for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) {
+ v3s16 p(x, y, z);
+ if (vmanip.m_area.contains(p + p1) &&
+ (vmanip.m_data[vi].getContent() == CONTENT_AIR ||
+ vmanip.m_data[vi].getContent() == CONTENT_IGNORE)) {
+ if (leaves_d[i] == 1) {
+ bool is_apple = pr.range(0, 99) < 10;
+ if (is_apple_tree && is_apple)
+ vmanip.m_data[vi] = applenode;
+ else
+ vmanip.m_data[vi] = leavesnode;
+ }
+ }
+ vi++;
+ i++;
+ }
+ }
+}
+
+
+// L-System tree LUA spawner
+treegen::error spawn_ltree(ServerEnvironment *env, v3s16 p0,
+ INodeDefManager *ndef, const TreeDef &tree_definition)
+{
+ ServerMap *map = &env->getServerMap();
+ std::map<v3s16, MapBlock*> modified_blocks;
+ MMVManip vmanip(map);
+ v3s16 tree_blockp = getNodeBlockPos(p0);
+ treegen::error e;
+
+ vmanip.initialEmerge(tree_blockp - v3s16(1, 1, 1), tree_blockp + v3s16(1, 3, 1));
+ e = make_ltree(vmanip, p0, ndef, tree_definition);
+ if (e != SUCCESS)
+ return e;
+
+ voxalgo::blit_back_with_light(map, &vmanip, &modified_blocks);
+
+ // Send a MEET_OTHER event
+ MapEditEvent event;
+ event.type = MEET_OTHER;
+ for (auto &modified_block : modified_blocks)
+ event.modified_blocks.insert(modified_block.first);
+ map->dispatchEvent(&event);
+ return SUCCESS;
+}
+
+
+//L-System tree generator
+treegen::error make_ltree(MMVManip &vmanip, v3s16 p0,
+ INodeDefManager *ndef, TreeDef tree_definition)
+{
+ MapNode dirtnode(ndef->getId("mapgen_dirt"));
+ s32 seed;
+ if (tree_definition.explicit_seed)
+ seed = tree_definition.seed + 14002;
+ else
+ seed = p0.X * 2 + p0.Y * 4 + p0.Z; // use the tree position to seed PRNG
+ PseudoRandom ps(seed);
+
+ // chance of inserting abcd rules
+ double prop_a = 9;
+ double prop_b = 8;
+ double prop_c = 7;
+ double prop_d = 6;
+
+ //randomize tree growth level, minimum=2
+ s16 iterations = tree_definition.iterations;
+ if (tree_definition.iterations_random_level > 0)
+ iterations -= ps.range(0, tree_definition.iterations_random_level);
+ if (iterations < 2)
+ iterations = 2;
+
+ s16 MAX_ANGLE_OFFSET = 5;
+ double angle_in_radians = (double)tree_definition.angle * M_PI / 180;
+ double angleOffset_in_radians = (s16)(ps.range(0, 1) % MAX_ANGLE_OFFSET) * M_PI / 180;
+
+ //initialize rotation matrix, position and stacks for branches
+ core::matrix4 rotation;
+ rotation = setRotationAxisRadians(rotation, M_PI / 2, v3f(0, 0, 1));
+ v3f position;
+ position.X = p0.X;
+ position.Y = p0.Y;
+ position.Z = p0.Z;
+ std::stack <core::matrix4> stack_orientation;
+ std::stack <v3f> stack_position;
+
+ //generate axiom
+ std::string axiom = tree_definition.initial_axiom;
+ for (s16 i = 0; i < iterations; i++) {
+ std::string temp;
+ for (s16 j = 0; j < (s16)axiom.size(); j++) {
+ char axiom_char = axiom.at(j);
+ switch (axiom_char) {
+ case 'A':
+ temp += tree_definition.rules_a;
+ break;
+ case 'B':
+ temp += tree_definition.rules_b;
+ break;
+ case 'C':
+ temp += tree_definition.rules_c;
+ break;
+ case 'D':
+ temp += tree_definition.rules_d;
+ break;
+ case 'a':
+ if (prop_a >= ps.range(1, 10))
+ temp += tree_definition.rules_a;
+ break;
+ case 'b':
+ if (prop_b >= ps.range(1, 10))
+ temp += tree_definition.rules_b;
+ break;
+ case 'c':
+ if (prop_c >= ps.range(1, 10))
+ temp += tree_definition.rules_c;
+ break;
+ case 'd':
+ if (prop_d >= ps.range(1, 10))
+ temp += tree_definition.rules_d;
+ break;
+ default:
+ temp += axiom_char;
+ break;
+ }
+ }
+ axiom = temp;
+ }
+
+ //make sure tree is not floating in the air
+ if (tree_definition.trunk_type == "double") {
+ tree_node_placement(
+ vmanip,
+ v3f(position.X + 1, position.Y - 1, position.Z),
+ dirtnode
+ );
+ tree_node_placement(
+ vmanip,
+ v3f(position.X, position.Y - 1, position.Z + 1),
+ dirtnode
+ );
+ tree_node_placement(
+ vmanip,
+ v3f(position.X + 1, position.Y - 1, position.Z + 1),
+ dirtnode
+ );
+ } else if (tree_definition.trunk_type == "crossed") {
+ tree_node_placement(
+ vmanip,
+ v3f(position.X + 1, position.Y - 1, position.Z),
+ dirtnode
+ );
+ tree_node_placement(
+ vmanip,
+ v3f(position.X - 1, position.Y - 1, position.Z),
+ dirtnode
+ );
+ tree_node_placement(
+ vmanip,
+ v3f(position.X, position.Y - 1, position.Z + 1),
+ dirtnode
+ );
+ tree_node_placement(
+ vmanip,
+ v3f(position.X, position.Y - 1, position.Z - 1),
+ dirtnode
+ );
+ }
+
+ /* build tree out of generated axiom
+
+ Key for Special L-System Symbols used in Axioms
+
+ G - move forward one unit with the pen up
+ F - move forward one unit with the pen down drawing trunks and branches
+ f - move forward one unit with the pen down drawing leaves (100% chance)
+ T - move forward one unit with the pen down drawing trunks only
+ R - move forward one unit with the pen down placing fruit
+ A - replace with rules set A
+ B - replace with rules set B
+ C - replace with rules set C
+ D - replace with rules set D
+ a - replace with rules set A, chance 90%
+ b - replace with rules set B, chance 80%
+ c - replace with rules set C, chance 70%
+ d - replace with rules set D, chance 60%
+ + - yaw the turtle right by angle degrees
+ - - yaw the turtle left by angle degrees
+ & - pitch the turtle down by angle degrees
+ ^ - pitch the turtle up by angle degrees
+ / - roll the turtle to the right by angle degrees
+ * - roll the turtle to the left by angle degrees
+ [ - save in stack current state info
+ ] - recover from stack state info
+
+ */
+
+ s16 x,y,z;
+ for (s16 i = 0; i < (s16)axiom.size(); i++) {
+ char axiom_char = axiom.at(i);
+ core::matrix4 temp_rotation;
+ temp_rotation.makeIdentity();
+ v3f dir;
+ switch (axiom_char) {
+ case 'G':
+ dir = v3f(1, 0, 0);
+ dir = transposeMatrix(rotation, dir);
+ position += dir;
+ break;
+ case 'T':
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z),
+ tree_definition
+ );
+ if (tree_definition.trunk_type == "double" &&
+ !tree_definition.thin_branches) {
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X + 1, position.Y, position.Z),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z + 1),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X + 1, position.Y, position.Z + 1),
+ tree_definition
+ );
+ } else if (tree_definition.trunk_type == "crossed" &&
+ !tree_definition.thin_branches) {
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X + 1, position.Y, position.Z),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X - 1, position.Y, position.Z),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z + 1),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z - 1),
+ tree_definition
+ );
+ }
+ dir = v3f(1, 0, 0);
+ dir = transposeMatrix(rotation, dir);
+ position += dir;
+ break;
+ case 'F':
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z),
+ tree_definition
+ );
+ if ((stack_orientation.empty() &&
+ tree_definition.trunk_type == "double") ||
+ (!stack_orientation.empty() &&
+ tree_definition.trunk_type == "double" &&
+ !tree_definition.thin_branches)) {
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X +1 , position.Y, position.Z),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z + 1),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X + 1, position.Y, position.Z + 1),
+ tree_definition
+ );
+ } else if ((stack_orientation.empty() &&
+ tree_definition.trunk_type == "crossed") ||
+ (!stack_orientation.empty() &&
+ tree_definition.trunk_type == "crossed" &&
+ !tree_definition.thin_branches)) {
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X + 1, position.Y, position.Z),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X - 1, position.Y, position.Z),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z + 1),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z - 1),
+ tree_definition
+ );
+ } if (!stack_orientation.empty()) {
+ s16 size = 1;
+ for (x = -size; x <= size; x++)
+ for (y = -size; y <= size; y++)
+ for (z = -size; z <= size; z++) {
+ if (abs(x) == size &&
+ abs(y) == size &&
+ abs(z) == size) {
+ tree_leaves_placement(
+ vmanip,
+ v3f(position.X + x + 1, position.Y + y,
+ position.Z + z),
+ ps.next(),
+ tree_definition
+ );
+ tree_leaves_placement(
+ vmanip,
+ v3f(position.X + x - 1, position.Y + y,
+ position.Z + z),
+ ps.next(),
+ tree_definition
+ );
+ tree_leaves_placement(
+ vmanip,v3f(position.X + x, position.Y + y,
+ position.Z + z + 1),
+ ps.next(),
+ tree_definition
+ );
+ tree_leaves_placement(
+ vmanip,v3f(position.X + x, position.Y + y,
+ position.Z + z - 1),
+ ps.next(),
+ tree_definition
+ );
+ }
+ }
+ }
+ dir = v3f(1, 0, 0);
+ dir = transposeMatrix(rotation, dir);
+ position += dir;
+ break;
+ case 'f':
+ tree_single_leaves_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z),
+ ps.next(),
+ tree_definition
+ );
+ dir = v3f(1, 0, 0);
+ dir = transposeMatrix(rotation, dir);
+ position += dir;
+ break;
+ case 'R':
+ tree_fruit_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z),
+ tree_definition
+ );
+ dir = v3f(1, 0, 0);
+ dir = transposeMatrix(rotation, dir);
+ position += dir;
+ break;
+
+ // turtle orientation commands
+ case '[':
+ stack_orientation.push(rotation);
+ stack_position.push(position);
+ break;
+ case ']':
+ if (stack_orientation.empty())
+ return UNBALANCED_BRACKETS;
+ rotation = stack_orientation.top();
+ stack_orientation.pop();
+ position = stack_position.top();
+ stack_position.pop();
+ break;
+ case '+':
+ temp_rotation.makeIdentity();
+ temp_rotation = setRotationAxisRadians(temp_rotation,
+ angle_in_radians + angleOffset_in_radians, v3f(0, 0, 1));
+ rotation *= temp_rotation;
+ break;
+ case '-':
+ temp_rotation.makeIdentity();
+ temp_rotation = setRotationAxisRadians(temp_rotation,
+ angle_in_radians + angleOffset_in_radians, v3f(0, 0, -1));
+ rotation *= temp_rotation;
+ break;
+ case '&':
+ temp_rotation.makeIdentity();
+ temp_rotation = setRotationAxisRadians(temp_rotation,
+ angle_in_radians + angleOffset_in_radians, v3f(0, 1, 0));
+ rotation *= temp_rotation;
+ break;
+ case '^':
+ temp_rotation.makeIdentity();
+ temp_rotation = setRotationAxisRadians(temp_rotation,
+ angle_in_radians + angleOffset_in_radians, v3f(0, -1, 0));
+ rotation *= temp_rotation;
+ break;
+ case '*':
+ temp_rotation.makeIdentity();
+ temp_rotation = setRotationAxisRadians(temp_rotation,
+ angle_in_radians, v3f(1, 0, 0));
+ rotation *= temp_rotation;
+ break;
+ case '/':
+ temp_rotation.makeIdentity();
+ temp_rotation = setRotationAxisRadians(temp_rotation,
+ angle_in_radians, v3f(-1, 0, 0));
+ rotation *= temp_rotation;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return SUCCESS;
+}
+
+
+void tree_node_placement(MMVManip &vmanip, v3f p0, MapNode node)
+{
+ v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z));
+ if (!vmanip.m_area.contains(p1))
+ return;
+ u32 vi = vmanip.m_area.index(p1);
+ if (vmanip.m_data[vi].getContent() != CONTENT_AIR
+ && vmanip.m_data[vi].getContent() != CONTENT_IGNORE)
+ return;
+ vmanip.m_data[vmanip.m_area.index(p1)] = node;
+}
+
+
+void tree_trunk_placement(MMVManip &vmanip, v3f p0, TreeDef &tree_definition)
+{
+ v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z));
+ if (!vmanip.m_area.contains(p1))
+ return;
+ u32 vi = vmanip.m_area.index(p1);
+ content_t current_node = vmanip.m_data[vi].getContent();
+ if (current_node != CONTENT_AIR && current_node != CONTENT_IGNORE
+ && current_node != tree_definition.leavesnode.getContent()
+ && current_node != tree_definition.leaves2node.getContent()
+ && current_node != tree_definition.fruitnode.getContent())
+ return;
+ vmanip.m_data[vi] = tree_definition.trunknode;
+}
+
+
+void tree_leaves_placement(MMVManip &vmanip, v3f p0,
+ PseudoRandom ps, TreeDef &tree_definition)
+{
+ MapNode leavesnode = tree_definition.leavesnode;
+ if (ps.range(1, 100) > 100 - tree_definition.leaves2_chance)
+ leavesnode = tree_definition.leaves2node;
+ v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z));
+ if (!vmanip.m_area.contains(p1))
+ return;
+ u32 vi = vmanip.m_area.index(p1);
+ if (vmanip.m_data[vi].getContent() != CONTENT_AIR
+ && vmanip.m_data[vi].getContent() != CONTENT_IGNORE)
+ return;
+ if (tree_definition.fruit_chance > 0) {
+ if (ps.range(1, 100) > 100 - tree_definition.fruit_chance)
+ vmanip.m_data[vmanip.m_area.index(p1)] = tree_definition.fruitnode;
+ else
+ vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode;
+ } else if (ps.range(1, 100) > 20) {
+ vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode;
+ }
+}
+
+
+void tree_single_leaves_placement(MMVManip &vmanip, v3f p0,
+ PseudoRandom ps, TreeDef &tree_definition)
+{
+ MapNode leavesnode = tree_definition.leavesnode;
+ if (ps.range(1, 100) > 100 - tree_definition.leaves2_chance)
+ leavesnode = tree_definition.leaves2node;
+ v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z));
+ if (!vmanip.m_area.contains(p1))
+ return;
+ u32 vi = vmanip.m_area.index(p1);
+ if (vmanip.m_data[vi].getContent() != CONTENT_AIR
+ && vmanip.m_data[vi].getContent() != CONTENT_IGNORE)
+ return;
+ vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode;
+}
+
+
+void tree_fruit_placement(MMVManip &vmanip, v3f p0, TreeDef &tree_definition)
+{
+ v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z));
+ if (!vmanip.m_area.contains(p1))
+ return;
+ u32 vi = vmanip.m_area.index(p1);
+ if (vmanip.m_data[vi].getContent() != CONTENT_AIR
+ && vmanip.m_data[vi].getContent() != CONTENT_IGNORE)
+ return;
+ vmanip.m_data[vmanip.m_area.index(p1)] = tree_definition.fruitnode;
+}
+
+
+irr::core::matrix4 setRotationAxisRadians(irr::core::matrix4 M, double angle, v3f axis)
+{
+ double c = cos(angle);
+ double s = sin(angle);
+ double t = 1.0 - c;
+
+ double tx = t * axis.X;
+ double ty = t * axis.Y;
+ double tz = t * axis.Z;
+ double sx = s * axis.X;
+ double sy = s * axis.Y;
+ double sz = s * axis.Z;
+
+ M[0] = tx * axis.X + c;
+ M[1] = tx * axis.Y + sz;
+ M[2] = tx * axis.Z - sy;
+
+ M[4] = ty * axis.X - sz;
+ M[5] = ty * axis.Y + c;
+ M[6] = ty * axis.Z + sx;
+
+ M[8] = tz * axis.X + sy;
+ M[9] = tz * axis.Y - sx;
+ M[10] = tz * axis.Z + c;
+ return M;
+}
+
+
+v3f transposeMatrix(irr::core::matrix4 M, v3f v)
+{
+ v3f translated;
+ double x = M[0] * v.X + M[4] * v.Y + M[8] * v.Z +M[12];
+ double y = M[1] * v.X + M[5] * v.Y + M[9] * v.Z +M[13];
+ double z = M[2] * v.X + M[6] * v.Y + M[10] * v.Z +M[14];
+ translated.X = x;
+ translated.Y = y;
+ translated.Z = z;
+ return translated;
+}
+
+
+void make_jungletree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, s32 seed)
+{
+ /*
+ NOTE: Tree-placing code is currently duplicated in the engine
+ and in games that have saplings; both are deprecated but not
+ replaced yet
+ */
+ content_t c_tree = ndef->getId("mapgen_jungletree");
+ content_t c_leaves = ndef->getId("mapgen_jungleleaves");
+ if (c_tree == CONTENT_IGNORE)
+ c_tree = ndef->getId("mapgen_tree");
+ if (c_leaves == CONTENT_IGNORE)
+ c_leaves = ndef->getId("mapgen_leaves");
+
+ MapNode treenode(c_tree);
+ MapNode leavesnode(c_leaves);
+
+ PseudoRandom pr(seed);
+ for (s16 x= -1; x <= 1; x++)
+ for (s16 z= -1; z <= 1; z++) {
+ if (pr.range(0, 2) == 0)
+ continue;
+ v3s16 p1 = p0 + v3s16(x, 0, z);
+ v3s16 p2 = p0 + v3s16(x, -1, z);
+ u32 vi1 = vmanip.m_area.index(p1);
+ u32 vi2 = vmanip.m_area.index(p2);
+
+ if (vmanip.m_area.contains(p2) &&
+ vmanip.m_data[vi2].getContent() == CONTENT_AIR)
+ vmanip.m_data[vi2] = treenode;
+ else if (vmanip.m_area.contains(p1) &&
+ vmanip.m_data[vi1].getContent() == CONTENT_AIR)
+ vmanip.m_data[vi1] = treenode;
+ }
+ vmanip.m_data[vmanip.m_area.index(p0)] = treenode;
+
+ s16 trunk_h = pr.range(8, 12);
+ v3s16 p1 = p0;
+ for (s16 ii = 0; ii < trunk_h; ii++) {
+ if (vmanip.m_area.contains(p1)) {
+ u32 vi = vmanip.m_area.index(p1);
+ vmanip.m_data[vi] = treenode;
+ }
+ p1.Y++;
+ }
+
+ // p1 is now the last piece of the trunk
+ p1.Y -= 1;
+
+ VoxelArea leaves_a(v3s16(-3, -2, -3), v3s16(3, 2, 3));
+ //SharedPtr<u8> leaves_d(new u8[leaves_a.getVolume()]);
+ Buffer<u8> leaves_d(leaves_a.getVolume());
+ for (s32 i = 0; i < leaves_a.getVolume(); i++)
+ leaves_d[i] = 0;
+
+ // Force leaves at near the end of the trunk
+ s16 d = 1;
+ for (s16 z = -d; z <= d; z++)
+ for (s16 y = -d; y <= d; y++)
+ for (s16 x = -d; x <= d; x++) {
+ leaves_d[leaves_a.index(v3s16(x,y,z))] = 1;
+ }
+
+ // Add leaves randomly
+ for (u32 iii = 0; iii < 30; iii++) {
+ v3s16 p(
+ pr.range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X - d),
+ pr.range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y - d),
+ pr.range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z - d)
+ );
+
+ for (s16 z = 0; z <= d; z++)
+ for (s16 y = 0; y <= d; y++)
+ for (s16 x = 0; x <= d; x++) {
+ leaves_d[leaves_a.index(p + v3s16(x, y, z))] = 1;
+ }
+ }
+
+ // Blit leaves to vmanip
+ for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++)
+ for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) {
+ v3s16 pmin(leaves_a.MinEdge.X, y, z);
+ u32 i = leaves_a.index(pmin);
+ u32 vi = vmanip.m_area.index(pmin + p1);
+ for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) {
+ v3s16 p(x, y, z);
+ if (vmanip.m_area.contains(p + p1) &&
+ (vmanip.m_data[vi].getContent() == CONTENT_AIR ||
+ vmanip.m_data[vi].getContent() == CONTENT_IGNORE)) {
+ if (leaves_d[i] == 1)
+ vmanip.m_data[vi] = leavesnode;
+ }
+ vi++;
+ i++;
+ }
+ }
+}
+
+
+void make_pine_tree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, s32 seed)
+{
+ /*
+ NOTE: Tree-placing code is currently duplicated in the engine
+ and in games that have saplings; both are deprecated but not
+ replaced yet
+ */
+ content_t c_tree = ndef->getId("mapgen_pine_tree");
+ content_t c_leaves = ndef->getId("mapgen_pine_needles");
+ content_t c_snow = ndef->getId("mapgen_snow");
+ if (c_tree == CONTENT_IGNORE)
+ c_tree = ndef->getId("mapgen_tree");
+ if (c_leaves == CONTENT_IGNORE)
+ c_leaves = ndef->getId("mapgen_leaves");
+ if (c_snow == CONTENT_IGNORE)
+ c_snow = CONTENT_AIR;
+
+ MapNode treenode(c_tree);
+ MapNode leavesnode(c_leaves);
+ MapNode snownode(c_snow);
+
+ PseudoRandom pr(seed);
+ u16 trunk_h = pr.range(9, 13);
+ v3s16 p1 = p0;
+ for (u16 ii = 0; ii < trunk_h; ii++) {
+ if (vmanip.m_area.contains(p1)) {
+ u32 vi = vmanip.m_area.index(p1);
+ vmanip.m_data[vi] = treenode;
+ }
+ p1.Y++;
+ }
+
+ // Make p1 the top node of the trunk
+ p1.Y -= 1;
+
+ VoxelArea leaves_a(v3s16(-3, -6, -3), v3s16(3, 3, 3));
+ Buffer<u8> leaves_d(leaves_a.getVolume());
+ for (s32 i = 0; i < leaves_a.getVolume(); i++)
+ leaves_d[i] = 0;
+
+ // Upper branches
+ u16 dev = 3;
+ for (s16 yy = -1; yy <= 1; yy++) {
+ for (s16 zz = -dev; zz <= dev; zz++) {
+ u32 i = leaves_a.index(v3s16(-dev, yy, zz));
+ u32 ia = leaves_a.index(v3s16(-dev, yy+1, zz));
+ for (s16 xx = -dev; xx <= dev; xx++) {
+ if (pr.range(0, 20) <= 19 - dev) {
+ leaves_d[i] = 1;
+ leaves_d[ia] = 2;
+ }
+ i++;
+ ia++;
+ }
+ }
+ dev--;
+ }
+
+ // Centre top nodes
+ leaves_d[leaves_a.index(v3s16(0, 1, 0))] = 1;
+ leaves_d[leaves_a.index(v3s16(0, 2, 0))] = 1;
+ leaves_d[leaves_a.index(v3s16(0, 3, 0))] = 2;
+
+ // Lower branches
+ s16 my = -6;
+ for (u32 iii = 0; iii < 20; iii++) {
+ s16 xi = pr.range(-3, 2);
+ s16 yy = pr.range(-6, -5);
+ s16 zi = pr.range(-3, 2);
+ if (yy > my)
+ my = yy;
+ for (s16 zz = zi; zz <= zi + 1; zz++) {
+ u32 i = leaves_a.index(v3s16(xi, yy, zz));
+ u32 ia = leaves_a.index(v3s16(xi, yy + 1, zz));
+ for (s32 xx = xi; xx <= xi + 1; xx++) {
+ leaves_d[i] = 1;
+ if (leaves_d[ia] == 0)
+ leaves_d[ia] = 2;
+ i++;
+ ia++;
+ }
+ }
+ }
+
+ dev = 2;
+ for (s16 yy = my + 1; yy <= my + 2; yy++) {
+ for (s16 zz = -dev; zz <= dev; zz++) {
+ u32 i = leaves_a.index(v3s16(-dev, yy, zz));
+ u32 ia = leaves_a.index(v3s16(-dev, yy + 1, zz));
+ for (s16 xx = -dev; xx <= dev; xx++) {
+ if (pr.range(0, 20) <= 19 - dev) {
+ leaves_d[i] = 1;
+ leaves_d[ia] = 2;
+ }
+ i++;
+ ia++;
+ }
+ }
+ dev--;
+ }
+
+ // Blit leaves to vmanip
+ for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++)
+ for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) {
+ v3s16 pmin(leaves_a.MinEdge.X, y, z);
+ u32 i = leaves_a.index(pmin);
+ u32 vi = vmanip.m_area.index(pmin + p1);
+ for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) {
+ v3s16 p(x, y, z);
+ if (vmanip.m_area.contains(p + p1) &&
+ (vmanip.m_data[vi].getContent() == CONTENT_AIR ||
+ vmanip.m_data[vi].getContent() == CONTENT_IGNORE ||
+ vmanip.m_data[vi] == snownode)) {
+ if (leaves_d[i] == 1)
+ vmanip.m_data[vi] = leavesnode;
+ else if (leaves_d[i] == 2)
+ vmanip.m_data[vi] = snownode;
+ }
+ vi++;
+ i++;
+ }
+ }
+}
+
+}; // namespace treegen
diff --git a/src/mapgen/treegen.h b/src/mapgen/treegen.h
new file mode 100644
index 000000000..8e5306560
--- /dev/null
+++ b/src/mapgen/treegen.h
@@ -0,0 +1,92 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>,
+ 2012-2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <matrix4.h>
+#include "noise.h"
+
+class MMVManip;
+class INodeDefManager;
+class ServerEnvironment;
+
+
+namespace treegen {
+
+ enum error {
+ SUCCESS,
+ UNBALANCED_BRACKETS
+ };
+
+ struct TreeDef {
+ std::string initial_axiom;
+ std::string rules_a;
+ std::string rules_b;
+ std::string rules_c;
+ std::string rules_d;
+
+ MapNode trunknode;
+ MapNode leavesnode;
+ MapNode leaves2node;
+
+ int leaves2_chance;
+ int angle;
+ int iterations;
+ int iterations_random_level;
+ std::string trunk_type;
+ bool thin_branches;
+ MapNode fruitnode;
+ int fruit_chance;
+ s32 seed;
+ bool explicit_seed;
+ };
+
+ // Add default tree
+ void make_tree(MMVManip &vmanip, v3s16 p0,
+ bool is_apple_tree, INodeDefManager *ndef, s32 seed);
+ // Add jungle tree
+ void make_jungletree(MMVManip &vmanip, v3s16 p0,
+ INodeDefManager *ndef, s32 seed);
+ // Add pine tree
+ void make_pine_tree(MMVManip &vmanip, v3s16 p0,
+ INodeDefManager *ndef, s32 seed);
+
+ // Add L-Systems tree (used by engine)
+ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef,
+ TreeDef tree_definition);
+ // Spawn L-systems tree from LUA
+ treegen::error spawn_ltree (ServerEnvironment *env, v3s16 p0, INodeDefManager *ndef,
+ const TreeDef &tree_definition);
+
+ // L-System tree gen helper functions
+ void tree_node_placement(MMVManip &vmanip, v3f p0,
+ MapNode node);
+ void tree_trunk_placement(MMVManip &vmanip, v3f p0,
+ TreeDef &tree_definition);
+ void tree_leaves_placement(MMVManip &vmanip, v3f p0,
+ PseudoRandom ps, TreeDef &tree_definition);
+ void tree_single_leaves_placement(MMVManip &vmanip, v3f p0,
+ PseudoRandom ps, TreeDef &tree_definition);
+ void tree_fruit_placement(MMVManip &vmanip, v3f p0,
+ TreeDef &tree_definition);
+ irr::core::matrix4 setRotationAxisRadians(irr::core::matrix4 M, double angle, v3f axis);
+
+ v3f transposeMatrix(irr::core::matrix4 M ,v3f v);
+
+}; // namespace treegen