FindSpawnPos: Let mapgens decide what spawn altitude is suitable
authorparamat <mat.gregory@virginmedia.com>
Thu, 4 Feb 2016 01:03:31 +0000 (01:03 +0000)
committerparamat <mat.gregory@virginmedia.com>
Tue, 9 Feb 2016 07:14:45 +0000 (07:14 +0000)
To avoid spawn search failing in new specialised mapgens
Increase spawn search range to 4000 nodes
Add getSpawnLevelAtPoint() functions to EmergeManager, class Mapgen
and all mapgens
Remove getGroundLevelAtPoint() functions from all mapgens except mgv6
(possibly to be re-added later in the correct form to return actual
ground level)
Make mgvalleys flag names consistent with other mapgens
Remove now unused 'vertical spawn range' setting

19 files changed:
src/defaultsettings.cpp
src/emerge.cpp
src/emerge.h
src/mapgen.h
src/mapgen_flat.cpp
src/mapgen_flat.h
src/mapgen_fractal.cpp
src/mapgen_fractal.h
src/mapgen_singlenode.cpp
src/mapgen_singlenode.h
src/mapgen_v5.cpp
src/mapgen_v5.h
src/mapgen_v6.cpp
src/mapgen_v6.h
src/mapgen_v7.cpp
src/mapgen_v7.h
src/mapgen_valleys.cpp
src/mapgen_valleys.h
src/server.cpp

index ddd7b5fe12f15f2001686ee3eba8ef882f2cc1fb..9578579765d59767eaad9aef05ed959bb2cc560a 100644 (file)
@@ -247,7 +247,6 @@ void set_default_settings(Settings *settings)
        settings->setDefault("default_privs", "interact, shout");
        settings->setDefault("player_transfer_distance", "0");
        settings->setDefault("enable_pvp", "true");
-       settings->setDefault("vertical_spawn_range", "16");
        settings->setDefault("disallow_empty_password", "false");
        settings->setDefault("disable_anticheat", "false");
        settings->setDefault("enable_rollback_recording", "false");
index ccb4c1703554341f0b87a8475514761a0a9baabe..93e8f2b30a92abcbbd81f226b75ae00e8d5bb546 100644 (file)
@@ -334,6 +334,18 @@ v3s16 EmergeManager::getContainingChunk(v3s16 blockpos, s16 chunksize)
 }
 
 
+int EmergeManager::getSpawnLevelAtPoint(v2s16 p)
+{
+       if (m_mapgens.size() == 0 || !m_mapgens[0]) {
+               errorstream << "EmergeManager: getSpawnLevelAtPoint() called"
+                       " before mapgen init" << std::endl;
+               return 0;
+       }
+
+       return m_mapgens[0]->getSpawnLevelAtPoint(p);
+}
+
+
 int EmergeManager::getGroundLevelAtPoint(v2s16 p)
 {
        if (m_mapgens.size() == 0 || !m_mapgens[0]) {
index 02bdf7e672de1bfc4ef58e41dd3f8df5f3bf3aad..825ac1c0fbf490a9204a687a0a2c34e9d1c5cab2 100644 (file)
@@ -136,6 +136,7 @@ public:
 
        // Mapgen helpers methods
        Biome *getBiomeAtPoint(v3s16 p);
+       int getSpawnLevelAtPoint(v2s16 p);
        int getGroundLevelAtPoint(v2s16 p);
        bool isBlockUnderground(v3s16 blockpos);
 
index 9bb7d03b89241a9449c237498731476c73a6e3d6..abc3d2e899af04d3bdc8027edf9ea827014e47d9 100644 (file)
@@ -181,6 +181,13 @@ public:
        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; }
+
 private:
        DISABLE_CLASS_COPY(Mapgen);
 };
index 3b7178dd76922143b79252a22a3cd36ac485f9bb..0d071411d75a5a376511804c90e035f656003ffa 100644 (file)
@@ -192,18 +192,25 @@ void MapgenFlatParams::writeParams(Settings *settings) const
 /////////////////////////////////////////////////////////////////
 
 
-int MapgenFlat::getGroundLevelAtPoint(v2s16 p)
+int MapgenFlat::getSpawnLevelAtPoint(v2s16 p)
 {
+       s16 level_at_point = ground_level;
        float n_terrain = NoisePerlin2D(&noise_terrain->np, p.X, p.Y, seed);
+
        if ((spflags & MGFLAT_LAKES) && n_terrain < lake_threshold) {
-               s16 depress = (lake_threshold - n_terrain) * lake_steepness;
-               return ground_level - depress;
+               level_at_point = ground_level -
+                       (lake_threshold - n_terrain) * lake_steepness;
        } else if ((spflags & MGFLAT_HILLS) && n_terrain > hill_threshold) {
-               s16 rise = (n_terrain - hill_threshold) * hill_steepness;
-               return ground_level + rise;
-       } else {
-               return ground_level;
+               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);
+       else if (level_at_point > water_level)
+               return level_at_point;  // Spawn on land
+       else
+               return MAX_MAP_GENERATION_LIMIT;  // Unsuitable spawn point
 }
 
 
index 0e15df781b20f7ef9a3c0215b67c9609b2f7d596..282e4297511482e493583e2770355e368100f0c7 100644 (file)
@@ -102,7 +102,7 @@ public:
        ~MapgenFlat();
 
        virtual void makeChunk(BlockMakeData *data);
-       int getGroundLevelAtPoint(v2s16 p);
+       int getSpawnLevelAtPoint(v2s16 p);
        void calculateNoise();
        s16 generateTerrain();
        MgStoneType generateBiomes(float *heat_map, float *humidity_map);
index 9cb682a911d04402862014af4ea6947ba03f1d2a..8a5c1e2bc8dde8b2d87f80d6c305880f15d6f9a7 100644 (file)
@@ -209,17 +209,28 @@ void MapgenFractalParams::writeParams(Settings *settings) const
 /////////////////////////////////////////////////////////////////
 
 
-int MapgenFractal::getGroundLevelAtPoint(v2s16 p)
+int MapgenFractal::getSpawnLevelAtPoint(v2s16 p)
 {
-       s16 search_start = 128;
-       s16 search_end = -128;
-
-       for (s16 y = search_start; y >= search_end; y--) {
-               if (getFractalAtPoint(p.X, y, p.Y))
-                       return y;
+       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++;
+                       if (air_count == 2)
+                               return y - 2;
+               }
        }
 
-       return -MAX_MAP_GENERATION_LIMIT;
+       return MAX_MAP_GENERATION_LIMIT;  // Unsuitable spawn point
 }
 
 
index 3d4f7ee8ff9620bd95961744627ed77f7d45c3c8..ffb2acfb9ab40530712c1deb94988f7b4ee024ce 100644 (file)
@@ -114,7 +114,7 @@ public:
        ~MapgenFractal();
 
        virtual void makeChunk(BlockMakeData *data);
-       int getGroundLevelAtPoint(v2s16 p);
+       int getSpawnLevelAtPoint(v2s16 p);
        void calculateNoise();
        bool getFractalAtPoint(s16 x, s16 y, s16 z);
        s16 generateTerrain();
index f87115269e3f0771ae47551d1a09f57b1d72d05f..ff985dd34446056bff682535b9ab1950cefc6824 100644 (file)
@@ -99,7 +99,7 @@ void MapgenSinglenode::makeChunk(BlockMakeData *data)
 }
 
 
-int MapgenSinglenode::getGroundLevelAtPoint(v2s16 p)
+int MapgenSinglenode::getSpawnLevelAtPoint(v2s16 p)
 {
        return 0;
 }
index f9c97b50874554718f26152982b9b29a4335583b..2c6496c00693f49139d06d9feb641caa8b769853 100644 (file)
@@ -41,7 +41,7 @@ public:
        ~MapgenSinglenode();
        
        void makeChunk(BlockMakeData *data);
-       int getGroundLevelAtPoint(v2s16 p);
+       int getSpawnLevelAtPoint(v2s16 p);
 };
 
 struct MapgenFactorySinglenode : public MapgenFactory {
index 06e5f1ca60df77d98de7ea578485e3ed3b98eb4c..0bb3715a85337773a3a5b2ac2d5d64be4ecb42c9 100644 (file)
@@ -171,7 +171,7 @@ void MapgenV5Params::writeParams(Settings *settings) const
 }
 
 
-int MapgenV5::getGroundLevelAtPoint(v2s16 p)
+int MapgenV5::getSpawnLevelAtPoint(v2s16 p)
 {
        //TimeTaker t("getGroundLevelAtPoint", NULL, PRECISION_MICRO);
 
@@ -182,24 +182,25 @@ int MapgenV5::getGroundLevelAtPoint(v2s16 p)
                f *= 1.6;
        float h = NoisePerlin2D(&noise_height->np, p.X, p.Y, seed);
 
-       s16 search_start = 128; // Only bother searching this range, actual
-       s16 search_end = -128;  // ground level is rarely higher or lower.
-
-       for (s16 y = search_start; y >= search_end; y--) {
+       for (s16 y = 128; y >= -128; y--) {
                float n_ground = NoisePerlin3D(&noise_ground->np, p.X, y, p.Y, seed);
-               // If solid
-               if (n_ground * f > y - h) {
+
+               if (n_ground * f > y - h) {  // If solid
                        // If either top 2 nodes of search are solid this is inside a
-                       // mountain or floatland with no space for the player to spawn.
-                       if (y >= search_start - 1)
-                               return MAX_MAP_GENERATION_LIMIT;
-                       else
-                               return y; // Ground below at least 2 nodes of space
+                       // mountain or floatland with possibly no space for the player to spawn.
+                       if (y >= 127) {
+                               return MAX_MAP_GENERATION_LIMIT;  // Unsuitable spawn point
+                       } else {  // Ground below at least 2 nodes of empty space
+                               if (y <= water_level || y > water_level + 16)
+                                       return MAX_MAP_GENERATION_LIMIT;  // Unsuitable spawn point
+                               else
+                                       return y;
+                       }
                }
        }
 
        //printf("getGroundLevelAtPoint: %dus\n", t.stop());
-       return -MAX_MAP_GENERATION_LIMIT;
+       return MAX_MAP_GENERATION_LIMIT;  // Unsuitable spawn position, no ground found
 }
 
 
index 4112bbfa61135db551bba763ac3788eba8913b2e..44a06907da0ac25814a0d89f4bada63d90f3d68b 100644 (file)
@@ -90,7 +90,7 @@ public:
        ~MapgenV5();
 
        virtual void makeChunk(BlockMakeData *data);
-       int getGroundLevelAtPoint(v2s16 p);
+       int getSpawnLevelAtPoint(v2s16 p);
        void calculateNoise();
        int generateBaseTerrain();
        MgStoneType generateBiomes(float *heat_map, float *humidity_map);
index 57d0f59b3401e685a9da5899032c1e0af931d8b2..e24083cffb3b71d5092227633d7de2abf4959e8e 100644 (file)
@@ -318,6 +318,17 @@ int MapgenV6::getGroundLevelAtPoint(v2s16 p)
 }
 
 
+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
+       else
+               return level_at_point;
+}
+
+
 //////////////////////// Noise functions
 
 float MapgenV6::getMudAmount(v2s16 p)
index 612e2322ab32f226dd798062450efc88a4e11738..a55fc6d534083dd458a6f38f26f1b05692c65397 100644 (file)
@@ -129,6 +129,7 @@ public:
 
        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);
index aec00b938c279f704ec74e5a562377f437421f5b..029f56a45a74d3d9b8f38dcbaa7024ab552d065e 100644 (file)
@@ -202,7 +202,7 @@ void MapgenV7Params::writeParams(Settings *settings) const
 ///////////////////////////////////////
 
 
-int MapgenV7::getGroundLevelAtPoint(v2s16 p)
+int MapgenV7::getSpawnLevelAtPoint(v2s16 p)
 {
        // Base terrain calculation
        s16 y = baseTerrainLevelAtPoint(p.X, p.Y);
@@ -210,22 +210,24 @@ int MapgenV7::getGroundLevelAtPoint(v2s16 p)
        // Ridge/river terrain calculation
        float width = 0.2;
        float uwatern = NoisePerlin2D(&noise_ridge_uwater->np, p.X, p.Y, seed) * 2;
-       // actually computing the depth of the ridge is much more expensive;
-       // if inside a river, simply guess
+       // if inside a river this is an unsuitable spawn point
        if (fabs(uwatern) <= width)
-               return water_level - 10;
+               return MAX_MAP_GENERATION_LIMIT;
 
        // Mountain terrain calculation
-       int iters = 128; // don't even bother iterating more than 128 times..
+       int iters = 128;
        while (iters--) {
-               //current point would have been air
-               if (!getMountainTerrainAtPoint(p.X, y, p.Y))
-                       return y;
-
+               if (!getMountainTerrainAtPoint(p.X, y + 1, p.Y)) {  // Air, y is ground level
+                       if (y <= water_level || y > water_level + 16)
+                               return MAX_MAP_GENERATION_LIMIT;  // Unsuitable spawn point
+                       else
+                               return y;
+               }
                y++;
        }
 
-       return y;
+       // Unsuitable spawn point, no ground surface found
+       return MAX_MAP_GENERATION_LIMIT;
 }
 
 
index 9b483b0410ee62f7b8936ae84b7f19e7f5c43269..82f89387b1aa79ec5adc21ecdb2ea7497a626de6 100644 (file)
@@ -103,7 +103,7 @@ public:
        ~MapgenV7();
 
        virtual void makeChunk(BlockMakeData *data);
-       int getGroundLevelAtPoint(v2s16 p);
+       int getSpawnLevelAtPoint(v2s16 p);
        Biome *getBiomeAtPoint(v3s16 p);
 
        float baseTerrainLevelAtPoint(s16 x, s16 z);
index ceb2c774d988f693ac1f14260b7c5186a012f49a..2f96c339773f673a4461129ac84aa5211d8df921 100644 (file)
@@ -56,8 +56,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 //Profiler *mapgen_profiler = &mapgen_prof;
 
 static FlagDesc flagdesc_mapgen_valleys[] = {
-       {"altitude_chill", MG_VALLEYS_ALT_CHILL},
-       {"humid_rivers",   MG_VALLEYS_HUMID_RIVERS},
+       {"altitude_chill", MGVALLEYS_ALT_CHILL},
+       {"humid_rivers",   MGVALLEYS_HUMID_RIVERS},
        {NULL,             0}
 };
 
@@ -86,8 +86,8 @@ MapgenValleys::MapgenValleys(int mapgenid, MapgenParams *params, EmergeManager *
        MapgenValleysParams *sp = (MapgenValleysParams *)params->sparams;
        this->spflags = sp->spflags;
 
-       this->humid_rivers       = (spflags & MG_VALLEYS_HUMID_RIVERS);
-       this->use_altitude_chill = (spflags & MG_VALLEYS_ALT_CHILL);
+       this->humid_rivers       = (spflags & MGVALLEYS_HUMID_RIVERS);
+       this->use_altitude_chill = (spflags & MGVALLEYS_ALT_CHILL);
 
        this->altitude_chill     = sp->altitude_chill;
        this->humidity_adjust    = params->np_biome_humidity.offset - 50.f;
@@ -181,7 +181,7 @@ MapgenValleys::~MapgenValleys()
 
 MapgenValleysParams::MapgenValleysParams()
 {
-       spflags = MG_VALLEYS_HUMID_RIVERS | MG_VALLEYS_ALT_CHILL;
+       spflags = MGVALLEYS_HUMID_RIVERS | MGVALLEYS_ALT_CHILL;
 
        altitude_chill     = 90; // The altitude at which temperature drops by 20C.
        large_cave_depth   = -33;
@@ -513,24 +513,19 @@ float MapgenValleys::adjustedTerrainLevelFromNoise(TerrainNoise *tn)
 }
 
 
-int MapgenValleys::getGroundLevelAtPoint(v2s16 p)
+int MapgenValleys::getSpawnLevelAtPoint(v2s16 p)
 {
-       // ***********************************
-       // This method (deliberately) does not
-       // return correct terrain values.
-       // ***********************************
-
-       // Since MT doesn't normally deal with rivers, check
-       // to make sure this isn't a request for a location
-       // in a river.
+       // 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 it's wet, return an unusable number.
        if (fabs(rivers) < river_size_factor)
-               return MAX_MAP_GENERATION_LIMIT;
-
-       // Otherwise, return the real result.
-       return terrainLevelAtPoint(p.X, p.Y);
+               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 + 16)
+               return MAX_MAP_GENERATION_LIMIT;  // Unsuitable spawn point
+       else
+               return level_at_point;
 }
 
 
index ee4052d716b0d974cb731b0da5510c865c343350..6b3eb9cfe87aa478610bf808e97d3b56269d469c 100644 (file)
@@ -30,9 +30,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "mapgen.h"
 
-/////////////////// Mapgen Valleys flags
-#define MG_VALLEYS_ALT_CHILL    0x01
-#define MG_VALLEYS_HUMID_RIVERS 0x02
+////////////// Mapgen Valleys flags
+#define MGVALLEYS_ALT_CHILL    0x01
+#define MGVALLEYS_HUMID_RIVERS 0x02
 
 // Feed only one variable into these.
 #define MYSQUARE(x) (x) * (x)
@@ -96,7 +96,7 @@ public:
        ~MapgenValleys();
 
        virtual void makeChunk(BlockMakeData *data);
-       int getGroundLevelAtPoint(v2s16 p);
+       int getSpawnLevelAtPoint(v2s16 p);
 
        s16 large_cave_depth;
 
index 86096055d1f5e9fd1114dc69686522f81be31b53..572533146555d9d923b17aed3e52297634978773 100644 (file)
@@ -3383,26 +3383,24 @@ v3f Server::findSpawnPos()
                return nodeposf * BS;
        }
 
-       s16 water_level = map.getWaterLevel();
-       s16 vertical_spawn_range = g_settings->getS16("vertical_spawn_range");
        bool is_good = false;
 
        // Try to find a good place a few times
-       for(s32 i = 0; i < 1000 && !is_good; i++) {
+       for(s32 i = 0; i < 4000 && !is_good; i++) {
                s32 range = 1 + i;
                // We're going to try to throw the player to this position
                v2s16 nodepos2d = v2s16(
                                -range + (myrand() % (range * 2)),
                                -range + (myrand() % (range * 2)));
 
-               // Get ground height at point
-               s16 groundheight = map.findGroundLevel(nodepos2d);
-               // Don't go underwater or to high places
-               if (groundheight <= water_level ||
-                               groundheight > water_level + vertical_spawn_range)
+               // Get spawn level at point
+               s16 spawn_level = m_emerge->getSpawnLevelAtPoint(nodepos2d);
+               // Continue if MAX_MAP_GENERATION_LIMIT was returned by
+               // the mapgen to signify an unsuitable spawn position
+               if (spawn_level == MAX_MAP_GENERATION_LIMIT)
                        continue;
 
-               v3s16 nodepos(nodepos2d.X, groundheight, nodepos2d.Y);
+               v3s16 nodepos(nodepos2d.X, spawn_level, nodepos2d.Y);
 
                s32 air_count = 0;
                for (s32 i = 0; i < 10; i++) {