Avoid crash caused by, and improve, 'findSpawnPos()' (#8728)
[oweals/minetest.git] / src / mapgen / mapgen_carpathian.cpp
index 1e12eafc360e962c73f022aec60adde6a6967bb7..12ce07da40ceb2d4db6bab5961330ad8c0dc016a 100644 (file)
@@ -1,8 +1,7 @@
 /*
 Minetest
-Copyright (C) 2017-2018 vlapsley, Vaughan Lapsley <vlapsley@gmail.com>
-Copyright (C) 2010-2018 paramat
-Copyright (C) 2010-2018 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2017-2019 vlapsley, Vaughan Lapsley <vlapsley@gmail.com>
+Copyright (C) 2017-2019 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
@@ -43,6 +42,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 FlagDesc flagdesc_mapgen_carpathian[] = {
        {"caverns", MGCARPATHIAN_CAVERNS},
+       {"rivers",  MGCARPATHIAN_RIVERS},
        {NULL,      0}
 };
 
@@ -50,10 +50,14 @@ FlagDesc flagdesc_mapgen_carpathian[] = {
 ///////////////////////////////////////////////////////////////////////////////
 
 
-MapgenCarpathian::MapgenCarpathian(
-               int mapgenid, MapgenCarpathianParams *params, EmergeManager *emerge)
-       : MapgenBasic(mapgenid, params, emerge)
+MapgenCarpathian::MapgenCarpathian(MapgenCarpathianParams *params, EmergeManager *emerge)
+       : MapgenBasic(MAPGEN_CARPATHIAN, params, emerge)
 {
+       base_level       = params->base_level;
+       river_width      = params->river_width;
+       river_depth      = params->river_depth;
+       valley_width     = params->valley_width;
+
        spflags          = params->spflags;
        cave_width       = params->cave_width;
        large_cave_depth = params->large_cave_depth;
@@ -67,7 +71,6 @@ MapgenCarpathian::MapgenCarpathian(
        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);
@@ -79,6 +82,8 @@ MapgenCarpathian::MapgenCarpathian(
        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);
+       if (spflags & MGCARPATHIAN_RIVERS)
+               noise_rivers    = new Noise(&params->np_rivers,        seed, csize.X, csize.Z);
 
        //// 3D terrain noise
        // 1 up 1 down overgeneration
@@ -88,12 +93,12 @@ MapgenCarpathian::MapgenCarpathian(
        MapgenBasic::np_cave1  = params->np_cave1;
        MapgenBasic::np_cave2  = params->np_cave2;
        MapgenBasic::np_cavern = params->np_cavern;
+       MapgenBasic::np_dungeons = params->np_dungeons;
 }
 
 
 MapgenCarpathian::~MapgenCarpathian()
 {
-       delete noise_base;
        delete noise_filler_depth;
        delete noise_height1;
        delete noise_height2;
@@ -105,27 +110,31 @@ MapgenCarpathian::~MapgenCarpathian()
        delete noise_hills;
        delete noise_ridge_mnt;
        delete noise_step_mnt;
+       if (spflags & MGCARPATHIAN_RIVERS)
+               delete noise_rivers;
+
        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)
+       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_rivers        (0,   1,   v3f(1000, 1000, 1000), 85039, 5, 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),
+       np_dungeons      (0.9, 0.5, v3f(500,  500,  500),  0,     2, 0.8,  2.0)
 {
 }
 
@@ -133,6 +142,12 @@ MapgenCarpathianParams::MapgenCarpathianParams():
 void MapgenCarpathianParams::readParams(const Settings *settings)
 {
        settings->getFlagStrNoEx("mgcarpathian_spflags", spflags, flagdesc_mapgen_carpathian);
+
+       settings->getFloatNoEx("mgcarpathian_base_level",   base_level);
+       settings->getFloatNoEx("mgcarpathian_river_width",  river_width);
+       settings->getFloatNoEx("mgcarpathian_river_depth",  river_depth);
+       settings->getFloatNoEx("mgcarpathian_valley_width", valley_width);
+
        settings->getFloatNoEx("mgcarpathian_cave_width",       cave_width);
        settings->getS16NoEx("mgcarpathian_large_cave_depth",   large_cave_depth);
        settings->getS16NoEx("mgcarpathian_lava_depth",         lava_depth);
@@ -142,7 +157,6 @@ void MapgenCarpathianParams::readParams(const Settings *settings)
        settings->getS16NoEx("mgcarpathian_dungeon_ymin",       dungeon_ymin);
        settings->getS16NoEx("mgcarpathian_dungeon_ymax",       dungeon_ymax);
 
-       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);
@@ -154,16 +168,24 @@ void MapgenCarpathianParams::readParams(const Settings *settings)
        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_rivers",        np_rivers);
        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);
+       settings->getNoiseParams("mgcarpathian_np_dungeons",      np_dungeons);
 }
 
 
 void MapgenCarpathianParams::writeParams(Settings *settings) const
 {
        settings->setFlagStr("mgcarpathian_spflags", spflags, flagdesc_mapgen_carpathian, U32_MAX);
+
+       settings->setFloat("mgcarpathian_base_level",   base_level);
+       settings->setFloat("mgcarpathian_river_width",  river_width);
+       settings->setFloat("mgcarpathian_river_depth",  river_depth);
+       settings->setFloat("mgcarpathian_valley_width", valley_width);
+
        settings->setFloat("mgcarpathian_cave_width",       cave_width);
        settings->setS16("mgcarpathian_large_cave_depth",   large_cave_depth);
        settings->setS16("mgcarpathian_lava_depth",         lava_depth);
@@ -173,7 +195,6 @@ void MapgenCarpathianParams::writeParams(Settings *settings) const
        settings->setS16("mgcarpathian_dungeon_ymin",       dungeon_ymin);
        settings->setS16("mgcarpathian_dungeon_ymax",       dungeon_ymax);
 
-       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);
@@ -185,10 +206,12 @@ void MapgenCarpathianParams::writeParams(Settings *settings) const
        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_rivers",        np_rivers);
        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);
+       settings->setNoiseParams("mgcarpathian_np_dungeons",      np_dungeons);
 }
 
 
@@ -248,42 +271,47 @@ void MapgenCarpathian::makeChunk(BlockMakeData *data)
        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_BIOMES) {
+               biomegen->calcBiomeNoise(node_min);
+               generateBiomes();
+       }
 
-       // Generate caverns, tunnels and classic caves
+       // Generate tunnels, caverns and large randomwalk caves
        if (flags & MG_CAVES) {
-               bool has_cavern = false;
+               // Generate tunnels first as caverns confuse them
+               generateCavesNoiseIntersection(stone_surface_max_y);
+
                // Generate caverns
+               bool near_cavern = false;
                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
+                       near_cavern = generateCavernsNoise(stone_surface_max_y);
+
+               // Generate large randomwalk caves
+               if (near_cavern)
+                       // Disable large randomwalk 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);
+                       generateCavesRandomWalk(stone_surface_max_y,
+                               -MAX_MAP_GENERATION_LIMIT);
                else
-                       generateCaves(stone_surface_max_y, large_cave_depth);
+                       generateCavesRandomWalk(stone_surface_max_y, large_cave_depth);
        }
 
+       // Generate the registered ores
+       m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
+
        // Generate dungeons
        if ((flags & MG_DUNGEONS) && full_node_min.Y >= dungeon_ymin &&
                        full_node_max.Y <= dungeon_ymax)
-               generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
+               generateDungeons(stone_surface_max_y);
 
        // 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();
+       if (flags & MG_BIOMES)
+               dustTopNodes();
 
        // Update liquids
        updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
@@ -303,65 +331,95 @@ void MapgenCarpathian::makeChunk(BlockMakeData *data)
 
 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;
-}
+       // If rivers are enabled, first check if in a river channel
+       if (spflags & MGCARPATHIAN_RIVERS) {
+               float river = std::fabs(NoisePerlin2D(&noise_rivers->np, p.X, p.Y, seed)) -
+                       river_width;
+               if (river < 0.0f)
+                       return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+       }
 
+       float height1 = NoisePerlin2D(&noise_height1->np, p.X, p.Y, seed);
+       float height2 = NoisePerlin2D(&noise_height2->np, p.X, p.Y, seed);
+       float height3 = NoisePerlin2D(&noise_height3->np, p.X, p.Y, seed);
+       float height4 = NoisePerlin2D(&noise_height4->np, p.X, p.Y, seed);
+
+       float hterabs = std::fabs(NoisePerlin2D(&noise_hills_terrain->np, p.X, p.Y, seed));
+       float n_hills = NoisePerlin2D(&noise_hills->np, p.X, p.Y, seed);
+       float hill_mnt = hterabs * hterabs * hterabs * n_hills * n_hills;
+
+       float rterabs = std::fabs(NoisePerlin2D(&noise_ridge_terrain->np, p.X, p.Y, seed));
+       float n_ridge_mnt = NoisePerlin2D(&noise_ridge_mnt->np, p.X, p.Y, seed);
+       float ridge_mnt = rterabs * rterabs * rterabs * (1.0f - std::fabs(n_ridge_mnt));
+
+       float sterabs = std::fabs(NoisePerlin2D(&noise_step_terrain->np, p.X, p.Y, seed));
+       float n_step_mnt = NoisePerlin2D(&noise_step_mnt->np, p.X, p.Y, seed);
+       float step_mnt = sterabs * sterabs * sterabs * getSteps(n_step_mnt);
+
+       float valley = 1.0f;
+       float river = 0.0f;
+
+       if ((spflags & MGCARPATHIAN_RIVERS) && node_max.Y >= water_level - 16) {
+               river = std::fabs(NoisePerlin2D(&noise_rivers->np, p.X, p.Y, seed)) - river_width;
+               if (river <= valley_width) {
+                       // Within river valley
+                       if (river < 0.0f) {
+                               // River channel
+                               valley = river;
+                       } else {
+                               // Valley slopes.
+                               // 0 at river edge, 1 at valley edge.
+                               float riversc = river / valley_width;
+                               // Smoothstep
+                               valley = riversc * riversc * (3.0f - 2.0f * riversc);
+                       }
+               }
+       }
 
-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;
+       bool solid_below = false;
+       u8 cons_non_solid = 0; // consecutive non-solid nodes
 
-               // Hill/Mountain height (hilliness)
+       for (s16 y = water_level; y <= water_level + 32; y++) {
+               float mnt_var = NoisePerlin3D(&noise_mnt_var->np, p.X, y, p.Y, seed);
                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 * std::pow(n_hills, 2.f);
-               float hills = std::pow(hter, 3.f) * hill_mnt;
+               float hilliness = std::fmax(std::fmin(hill1, hill2), std::fmin(hill3, hill4));
+               float hills = hill_mnt * hilliness;
+               float ridged_mountains = ridge_mnt * hilliness;
+               float step_mountains = step_mnt * hilliness;
 
-               // Ridged mountains
-               float ridge_mnt = hilliness * (1.f - std::fabs(n_ridge_mnt));
-               float ridged_mountains = std::pow(rter, 3.f) * ridge_mnt;
-
-               // Step (terraced) mountains
-               float step_mnt = hilliness * getSteps(n_step_mnt);
-               float step_mountains = std::pow(ster, 3.f) * step_mnt;
+               s32 grad = 1 - y;
 
-               // Final terrain level
                float mountains = hills + ridged_mountains + step_mountains;
-               float surface_level = ground + mountains + grad;
+               float surface_level = base_level + mountains + grad;
+
+               if ((spflags & MGCARPATHIAN_RIVERS) && river <= valley_width) {
+                       if (valley < 0.0f) {
+                               // River channel
+                               surface_level = std::fmin(surface_level,
+                                       water_level - std::sqrt(-valley) * river_depth);
+                       } else if (surface_level > water_level) {
+                               // Valley slopes
+                               surface_level = water_level + (surface_level - water_level) * valley;
+                       }
+               }
 
-               if (y > surface_level && height < 0)
-                       height = y;
+               if (y < surface_level) { //TODO '<=' fix from generateTerrain()
+                       // solid node
+                       solid_below = true;
+                       cons_non_solid = 0;
+               } else {
+                       // non-solid node
+                       cons_non_solid++;
+                       if (cons_non_solid == 3 && solid_below)
+                               return y - 1;
+               }
        }
 
-       return height;
+       return MAX_MAP_GENERATION_LIMIT; // No suitable spawn point found
 }
 
 
@@ -375,7 +433,6 @@ int MapgenCarpathian::generateTerrain()
        MapNode mn_water(c_water_source);
 
        // 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);
@@ -388,6 +445,9 @@ int MapgenCarpathian::generateTerrain()
        noise_step_mnt->perlinMap2D(node_min.X, node_min.Z);
        noise_mnt_var->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
 
+       if (spflags & MGCARPATHIAN_RIVERS)
+               noise_rivers->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;
@@ -395,9 +455,6 @@ int MapgenCarpathian::generateTerrain()
 
        for (s16 z = node_min.Z; z <= node_max.Z; z++)
        for (s16 x = node_min.X; x <= node_max.X; x++, index2d++) {
-               // Base terrain
-               float ground = noise_base->result[index2d];
-
                // Hill/Mountain height (hilliness)
                float height1 = noise_height1->result[index2d];
                float height2 = noise_height2->result[index2d];
@@ -413,13 +470,34 @@ int MapgenCarpathian::generateTerrain()
                float rterabs = std::fabs(noise_ridge_terrain->result[index2d]);
                float n_ridge_mnt = noise_ridge_mnt->result[index2d];
                float ridge_mnt = rterabs * rterabs * rterabs *
-                       (1.f - std::fabs(n_ridge_mnt));
+                       (1.0f - std::fabs(n_ridge_mnt));
 
                // Step (terraced) mountains
                float sterabs = std::fabs(noise_step_terrain->result[index2d]);
                float n_step_mnt = noise_step_mnt->result[index2d];
                float step_mnt = sterabs * sterabs * sterabs * getSteps(n_step_mnt);
 
+               // Rivers
+               float valley = 1.0f;
+               float river = 0.0f;
+
+               if ((spflags & MGCARPATHIAN_RIVERS) && node_max.Y >= water_level - 16) {
+                       river = std::fabs(noise_rivers->result[index2d]) - river_width;
+                       if (river <= valley_width) {
+                               // Within river valley
+                               if (river < 0.0f) {
+                                       // River channel
+                                       valley = river;
+                               } else {
+                                       // Valley slopes.
+                                       // 0 at river edge, 1 at valley edge.
+                                       float riversc = river / valley_width;
+                                       // Smoothstep
+                                       valley = riversc * riversc * (3.0f - 2.0f * riversc);
+                               }
+                       }
+               }
+
                // Initialise 3D noise index and voxelmanip index to column base
                u32 index3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X);
                u32 vi = vm->m_area.index(x, node_min.Y - 1, z);
@@ -452,9 +530,22 @@ int MapgenCarpathian::generateTerrain()
 
                        // Final terrain level
                        float mountains = hills + ridged_mountains + step_mountains;
-                       float surface_level = ground + mountains + grad;
+                       float surface_level = base_level + mountains + grad;
+
+                       // Rivers
+                       if ((spflags & MGCARPATHIAN_RIVERS) && node_max.Y >= water_level - 16 &&
+                                       river <= valley_width) {
+                               if (valley < 0.0f) {
+                                       // River channel
+                                       surface_level = std::fmin(surface_level,
+                                               water_level - std::sqrt(-valley) * river_depth);
+                               } else if (surface_level > water_level) {
+                                       // Valley slopes
+                                       surface_level = water_level + (surface_level - water_level) * valley;
+                               }
+                       }
 
-                       if (y < surface_level) {
+                       if (y < surface_level) { //TODO '<='
                                vm->m_data[vi] = mn_stone; // Stone
                                if (y > stone_surface_max_y)
                                        stone_surface_max_y = y;