Avoid crash caused by, and improve, 'findSpawnPos()' (#8728)
authorParamat <paramat@users.noreply.github.com>
Wed, 7 Aug 2019 21:07:51 +0000 (22:07 +0100)
committerGitHub <noreply@github.com>
Wed, 7 Aug 2019 21:07:51 +0000 (22:07 +0100)
Avoid an unsuitable spawn position (which if outside mapgen limits can
cause a crash) if the main 0-3999 loop reaches its end. Fallback to a
spawn at 0,0,0.
Check the mapgen-returned 'spawn_level' value for being outside limits.
When 'air_count' reaches 2, move back down 1 to spawn in the lower
empty node.
If the spawn position is disallowed by 'objectpos_over_limit()', 'break'
from loop instead of 'continue' because positions above are probably
also over limit.
Reset 'air_count' to 0 if an obstruction is found, to make 'air_count'
consecutive empty nodes.
Allow spawn in 'airlike' drawtype nodes such as mod-added vacuum,
alien atmospheres, fog etc.
Add clarifying comments and improve codestyle.

src/server.cpp

index b3a6b49091ef5441c37facba993afe0f4e55c9fc..7e62087115527b66c8a9998242e2c4e6f86a031e 100644 (file)
@@ -3512,52 +3512,71 @@ v3f Server::findSpawnPos()
 {
        ServerMap &map = m_env->getServerMap();
        v3f nodeposf;
-       if (g_settings->getV3FNoEx("static_spawnpoint", nodeposf)) {
+       if (g_settings->getV3FNoEx("static_spawnpoint", nodeposf))
                return nodeposf * BS;
-       }
 
        bool is_good = false;
        // Limit spawn range to mapgen edges (determined by 'mapgen_limit')
        s32 range_max = map.getMapgenParams()->getSpawnRangeMax();
 
        // Try to find a good place a few times
-       for(s32 i = 0; i < 4000 && !is_good; i++) {
+       for (s32 i = 0; i < 4000 && !is_good; i++) {
                s32 range = MYMIN(1 + i, range_max);
                // We're going to try to throw the player to this position
                v2s16 nodepos2d = v2s16(
                        -range + (myrand() % (range * 2)),
                        -range + (myrand() % (range * 2)));
-
                // 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 if MAX_MAP_GENERATION_LIMIT was returned by the mapgen to
+               // signify an unsuitable spawn position, or if outside limits.
+               if (spawn_level >= MAX_MAP_GENERATION_LIMIT ||
+                               spawn_level <= -MAX_MAP_GENERATION_LIMIT)
                        continue;
 
                v3s16 nodepos(nodepos2d.X, spawn_level, nodepos2d.Y);
-
+               // Consecutive empty nodes
                s32 air_count = 0;
-               for (s32 i = 0; i < 10; i++) {
+
+               // Search upwards from 'spawn level' for 2 consecutive empty nodes, to
+               // avoid obstructions in already-generated mapblocks.
+               // In ungenerated mapblocks consisting of 'ignore' nodes, there will be
+               // no obstructions, but mapgen decorations are generated after spawn so
+               // the player may end up inside one.
+               for (s32 i = 0; i < 8; i++) {
                        v3s16 blockpos = getNodeBlockPos(nodepos);
                        map.emergeBlock(blockpos, true);
                        content_t c = map.getNodeNoEx(nodepos).getContent();
-                       if (c == CONTENT_AIR || c == CONTENT_IGNORE) {
+
+                       // In generated mapblocks allow spawn in all 'airlike' drawtype nodes.
+                       // In ungenerated mapblocks allow spawn in 'ignore' nodes.
+                       if (m_nodedef->get(c).drawtype == NDT_AIRLIKE || c == CONTENT_IGNORE) {
                                air_count++;
                                if (air_count >= 2) {
+                                       // Spawn in lower empty node
+                                       nodepos.Y--;
                                        nodeposf = intToFloat(nodepos, BS);
                                        // Don't spawn the player outside map boundaries
                                        if (objectpos_over_limit(nodeposf))
-                                               continue;
+                                               // Exit this loop, positions above are probably over limit
+                                               break;
+
+                                       // Good position found, cause an exit from main loop
                                        is_good = true;
                                        break;
                                }
+                       } else {
+                               air_count = 0;
                        }
                        nodepos.Y++;
                }
        }
 
-       return nodeposf;
+       if (is_good)
+               return nodeposf;
+
+       // No suitable spawn point found, return fallback 0,0,0
+       return v3f(0.0f, 0.0f, 0.0f);
 }
 
 void Server::requestShutdown(const std::string &msg, bool reconnect, float delay)