From 37923920a07f06d201662d8f1f4e5821efcc7b09 Mon Sep 17 00:00:00 2001 From: Paramat Date: Wed, 7 Aug 2019 22:07:51 +0100 Subject: Avoid crash caused by, and improve, 'findSpawnPos()' (#8728) 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 | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/src/server.cpp b/src/server.cpp index b3a6b4909..7e6208711 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -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) -- cgit v1.2.3