From 94731c5f5f7bb9d33c3413edb7fa33d66796fc3d Mon Sep 17 00:00:00 2001 From: Nathan Ikola Date: Mon, 3 Aug 2020 22:19:49 -0700 Subject: [PATCH 1/5] Fix #12334: remove goto in footpath_is...map_edge --- src/openrct2/world/Footpath.cpp | 91 ++++++++++++++++----------------- 1 file changed, 44 insertions(+), 47 deletions(-) diff --git a/src/openrct2/world/Footpath.cpp b/src/openrct2/world/Footpath.cpp index c956f40f14..028a4967f0 100644 --- a/src/openrct2/world/Footpath.cpp +++ b/src/openrct2/world/Footpath.cpp @@ -1363,65 +1363,62 @@ static int32_t footpath_is_connected_to_map_edge_recurse( } } } - goto searchFromFootpath; - } while (!(tileElement++)->IsLastForTile()); - return level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : FOOTPATH_SEARCH_INCOMPLETE; + // Exclude direction we came from + targetPos.z = tileElement->GetBaseZ(); + edges &= ~(1 << direction); -searchFromFootpath: - // Exclude direction we came from - targetPos.z = tileElement->GetBaseZ(); - edges &= ~(1 << direction); - - // Find next direction to go - int32_t newDirection{}; - if (!get_next_direction(edges, &newDirection)) - { - return FOOTPATH_SEARCH_INCOMPLETE; - } - direction = newDirection; - - edges &= ~(1 << direction); - if (edges == 0) - { - // Only possible direction to go - if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == direction) + // Find next direction to go + int32_t newDirection{}; + if (!get_next_direction(edges, &newDirection)) { - targetPos.z += PATH_HEIGHT_STEP; - } - return footpath_is_connected_to_map_edge_recurse( - targetPos, direction, flags, level, distanceFromJunction + 1, junctionTolerance); - } - else - { - // We have reached a junction - if (distanceFromJunction != 0) - { - junctionTolerance--; - } - junctionTolerance--; - if (junctionTolerance < 0) - { - return FOOTPATH_SEARCH_TOO_COMPLEX; + return FOOTPATH_SEARCH_INCOMPLETE; } + direction = newDirection; - do + edges &= ~(1 << direction); + if (edges == 0) { - direction = newDirection; - edges &= ~(1 << direction); + // Only possible direction to go if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == direction) { targetPos.z += PATH_HEIGHT_STEP; } - int32_t result = footpath_is_connected_to_map_edge_recurse( - targetPos, direction, flags, level, 0, junctionTolerance); - if (result == FOOTPATH_SEARCH_SUCCESS) + return footpath_is_connected_to_map_edge_recurse( + targetPos, direction, flags, level, distanceFromJunction + 1, junctionTolerance); + } + else + { + // We have reached a junction + if (distanceFromJunction != 0) { - return result; + junctionTolerance--; + } + junctionTolerance--; + if (junctionTolerance < 0) + { + return FOOTPATH_SEARCH_TOO_COMPLEX; } - } while (get_next_direction(edges, &newDirection)); - return FOOTPATH_SEARCH_INCOMPLETE; - } + do + { + direction = newDirection; + edges &= ~(1 << direction); + if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == direction) + { + targetPos.z += PATH_HEIGHT_STEP; + } + int32_t result = footpath_is_connected_to_map_edge_recurse( + targetPos, direction, flags, level, 0, junctionTolerance); + if (result == FOOTPATH_SEARCH_SUCCESS) + { + return result; + } + } while (get_next_direction(edges, &newDirection)); + + return FOOTPATH_SEARCH_INCOMPLETE; + } + } while (!(tileElement++)->IsLastForTile()); + return level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : FOOTPATH_SEARCH_INCOMPLETE; } // TODO: Use GAME_COMMAND_FLAGS From 24a8b4be70d29d28b1adfd48882bb7daff096886 Mon Sep 17 00:00:00 2001 From: Nathan Ikola Date: Mon, 3 Aug 2020 23:10:23 -0700 Subject: [PATCH 2/5] Remove recursion in footpath_is_connected_to... Refactor footpath_is_connected_to_map_edge to be iterative rather than recursive --- src/openrct2/world/Footpath.cpp | 256 ++++++++++++++++++-------------- 1 file changed, 147 insertions(+), 109 deletions(-) diff --git a/src/openrct2/world/Footpath.cpp b/src/openrct2/world/Footpath.cpp index 028a4967f0..6c7c9be6f3 100644 --- a/src/openrct2/world/Footpath.cpp +++ b/src/openrct2/world/Footpath.cpp @@ -1287,137 +1287,175 @@ static bool get_next_direction(int32_t edges, int32_t* direction) * (1 << 5): Unown * (1 << 7): Ignore no entry signs */ -static int32_t footpath_is_connected_to_map_edge_recurse( - const CoordsXYZ& footpathPos, int32_t direction, int32_t flags, int32_t level, int32_t distanceFromJunction, +static int32_t footpath_is_connected_to_map_edge_helper( + CoordsXYZ footpathPos, int32_t direction, int32_t flags, int32_t level, int32_t distanceFromJunction, int32_t junctionTolerance) { - TileElement* tileElement; + // Struct for keeping track of tile state + struct TileState + { + //bool processed = false; + CoordsXYZ footpathPos; + int32_t direction; + int32_t level; + int32_t distanceFromJunction; + int32_t junctionTolerance; + }; + + // Vector of all of the child tile elements for us to explore + std::vector tiles; + TileElement* tileElement = nullptr; + int numPendingTiles = 0; + + // Captures the current state of the variables and stores them for iteration later + auto CaptureCurrentTileState = [&tiles, &footpathPos, &direction, &level, &distanceFromJunction, &junctionTolerance, + &numPendingTiles]() -> void { + tiles.push_back({ footpathPos, direction, level, distanceFromJunction, junctionTolerance }); + ++numPendingTiles; + }; + + // Loads the next tile to visit into our variables + auto LoadNextTileElement = [&tiles, &footpathPos, &direction, &level, &distanceFromJunction, &junctionTolerance, + &numPendingTiles]() -> void { + size_t ii = tiles.size(); + --numPendingTiles; + footpathPos = tiles[ii - 1].footpathPos; + direction = tiles[ii - 1].direction; + level = tiles[ii - 1].level; + distanceFromJunction = tiles[ii - 1].distanceFromJunction; + junctionTolerance = tiles[ii - 1].junctionTolerance; + tiles.pop_back(); + }; + + // Helper method for footpath_is_connected_to_map_edge_helper + // to help make the function more readable + auto SkipTileElement = [](int32_t ste_flags, TileElement* ste_tileElement, int32_t& ste_slopeDirection, + int32_t ste_direction, const CoordsXYZ& ste_targetPos) { + // We are only interested in paths + if (ste_tileElement->GetType() != TILE_ELEMENT_TYPE_PATH) + return true; + + if (ste_tileElement->AsPath()->IsSloped() + && (ste_slopeDirection = ste_tileElement->AsPath()->GetSlopeDirection()) != ste_direction) + { + if (direction_reverse(ste_slopeDirection) != ste_direction) + return true; + if (ste_tileElement->GetBaseZ() + PATH_HEIGHT_STEP != ste_targetPos.z) + return true; + } + else if (ste_tileElement->GetBaseZ() != ste_targetPos.z) + return true; + + if (!(ste_flags & FOOTPATH_CONNECTED_MAP_EDGE_IGNORE_QUEUES)) + if (ste_tileElement->AsPath()->IsQueue()) + return true; + return false; + }; + + // Function to test whether or not the vector has been processed + auto DoneProcessing = [&numPendingTiles]() -> bool { return numPendingTiles == 0; }; + int32_t edges, slopeDirection; - auto targetPos = CoordsXYZ{ CoordsXY{ footpathPos } + CoordsDirectionDelta[direction], footpathPos.z }; - if (++level > 250) - return FOOTPATH_SEARCH_TOO_COMPLEX; + // Capture the current tile state to begin the loop + CaptureCurrentTileState(); - // Check if we are at edge of map - if (targetPos.x < COORDS_XY_STEP || targetPos.y < COORDS_XY_STEP) - return FOOTPATH_SEARCH_SUCCESS; - if (targetPos.x >= gMapSizeUnits || targetPos.y >= gMapSizeUnits) - return FOOTPATH_SEARCH_SUCCESS; - - tileElement = map_get_first_element_at(targetPos); - if (tileElement == nullptr) - return level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : FOOTPATH_SEARCH_INCOMPLETE; - do + // Loop on this until all tiles are processed or we return + while (!DoneProcessing()) { - if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH) - continue; + LoadNextTileElement(); - if (tileElement->AsPath()->IsSloped() && (slopeDirection = tileElement->AsPath()->GetSlopeDirection()) != direction) - { - if (direction_reverse(slopeDirection) != direction) - continue; - if (tileElement->GetBaseZ() + PATH_HEIGHT_STEP != targetPos.z) - continue; - } - else if (tileElement->GetBaseZ() != targetPos.z) - { - continue; - } + CoordsXYZ targetPos = CoordsXYZ{ CoordsXY{ footpathPos } + CoordsDirectionDelta[direction], footpathPos.z }; + if (++level > 250) + return FOOTPATH_SEARCH_TOO_COMPLEX; - if (!(flags & FOOTPATH_CONNECTED_MAP_EDGE_IGNORE_QUEUES)) + // Check if we are at edge of map + if (targetPos.x < COORDS_XY_STEP || targetPos.y < COORDS_XY_STEP) + return FOOTPATH_SEARCH_SUCCESS; + if (targetPos.x >= gMapSizeUnits || targetPos.y >= gMapSizeUnits) + return FOOTPATH_SEARCH_SUCCESS; + + tileElement = map_get_first_element_at(targetPos); + if (tileElement == nullptr) + return level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : FOOTPATH_SEARCH_INCOMPLETE; + + // Loop while there are unvisited TileElements at targetPos + do { - if (tileElement->AsPath()->IsQueue()) + // Skip this tile element if it is not a path + if (SkipTileElement(flags, tileElement, slopeDirection, direction, targetPos)) + continue; + + // Unown the footpath if needed + if (flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN) + footpath_fix_ownership(targetPos); + + edges = tileElement->AsPath()->GetEdges(); + direction = direction_reverse(direction); + if (!(flags & FOOTPATH_CONNECTED_MAP_EDGE_IGNORE_NO_ENTRY)) { - continue; - } - } + // Determine how many elements to check + size_t limit = 0; + if (tileElement[1].GetType() == TILE_ELEMENT_TYPE_BANNER) + limit = 4; + if (tileElement[2].GetType() == TILE_ELEMENT_TYPE_BANNER && tileElement[1].GetType() != TILE_ELEMENT_TYPE_PATH) + limit = 6; - if (flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN) - { - footpath_fix_ownership(targetPos); - } - edges = tileElement->AsPath()->GetEdges(); - direction = direction_reverse(direction); - if (!(flags & FOOTPATH_CONNECTED_MAP_EDGE_IGNORE_NO_ENTRY)) - { - if (tileElement[1].GetType() == TILE_ELEMENT_TYPE_BANNER) - { - for (int32_t i = 1; i < 4; i++) + for (size_t ii(1); ii < limit; ++ii) { - if ((&tileElement[i - 1])->IsLastForTile()) + if ((&tileElement[ii - 1])->IsLastForTile()) break; - if (tileElement[i].GetType() != TILE_ELEMENT_TYPE_BANNER) + if (tileElement[ii].GetType() != TILE_ELEMENT_TYPE_BANNER) break; - edges &= tileElement[i].AsBanner()->GetAllowedEdges(); + edges &= tileElement[ii].AsBanner()->GetAllowedEdges(); } } - if (tileElement[2].GetType() == TILE_ELEMENT_TYPE_BANNER && tileElement[1].GetType() != TILE_ELEMENT_TYPE_PATH) - { - for (int32_t i = 1; i < 6; i++) - { - if ((&tileElement[i - 1])->IsLastForTile()) - break; - if (tileElement[i].GetType() != TILE_ELEMENT_TYPE_BANNER) - break; - edges &= tileElement[i].AsBanner()->GetAllowedEdges(); - } - } - } - // Exclude direction we came from - targetPos.z = tileElement->GetBaseZ(); - edges &= ~(1 << direction); - // Find next direction to go - int32_t newDirection{}; - if (!get_next_direction(edges, &newDirection)) - { - return FOOTPATH_SEARCH_INCOMPLETE; - } - direction = newDirection; + // Exclude the direction we came from + targetPos.z = tileElement->GetBaseZ(); + edges &= ~(1 << direction); - edges &= ~(1 << direction); - if (edges == 0) - { - // Only possible direction to go - if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == direction) - { - targetPos.z += PATH_HEIGHT_STEP; - } - return footpath_is_connected_to_map_edge_recurse( - targetPos, direction, flags, level, distanceFromJunction + 1, junctionTolerance); - } - else - { - // We have reached a junction - if (distanceFromJunction != 0) - { - junctionTolerance--; - } - junctionTolerance--; - if (junctionTolerance < 0) - { - return FOOTPATH_SEARCH_TOO_COMPLEX; - } + // Find next direction to go + if (!get_next_direction(edges, &direction)) + // return FOOTPATH_SEARCH_INCOMPLETE; + continue; - do + edges &= ~(1 << direction); + if (edges == 0) { - direction = newDirection; - edges &= ~(1 << direction); + // Only possible direction to go if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == direction) - { targetPos.z += PATH_HEIGHT_STEP; - } - int32_t result = footpath_is_connected_to_map_edge_recurse( - targetPos, direction, flags, level, 0, junctionTolerance); - if (result == FOOTPATH_SEARCH_SUCCESS) - { - return result; - } - } while (get_next_direction(edges, &newDirection)); - return FOOTPATH_SEARCH_INCOMPLETE; - } - } while (!(tileElement++)->IsLastForTile()); + // Prepare the next iteration + footpathPos = targetPos; + ++distanceFromJunction; + CaptureCurrentTileState(); + } + else + { + // We have reached a junction + --junctionTolerance; + if (distanceFromJunction != 0) + --junctionTolerance; + if (junctionTolerance < 0) + return FOOTPATH_SEARCH_TOO_COMPLEX; + + // Loop until there are no more directions we can go + do + { + edges &= ~(1 << direction); + if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == direction) + targetPos.z += PATH_HEIGHT_STEP; + + // Prepare the next iteration + footpathPos = targetPos; + distanceFromJunction = 0; + CaptureCurrentTileState(); + } while (get_next_direction(edges, &direction)); + } + } while (!(tileElement++)->IsLastForTile()); + } return level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : FOOTPATH_SEARCH_INCOMPLETE; } @@ -1425,7 +1463,7 @@ static int32_t footpath_is_connected_to_map_edge_recurse( int32_t footpath_is_connected_to_map_edge(const CoordsXYZ& footpathPos, int32_t direction, int32_t flags) { flags |= FOOTPATH_CONNECTED_MAP_EDGE_IGNORE_QUEUES; - return footpath_is_connected_to_map_edge_recurse(footpathPos, direction, flags, 0, 0, 16); + return footpath_is_connected_to_map_edge_helper(footpathPos, direction, flags, 0, 0, 16); } bool PathElement::IsSloped() const From 30d65aa4332f97c62ad85d168ccb4a013aef0f1b Mon Sep 17 00:00:00 2001 From: Nathan Ikola Date: Tue, 4 Aug 2020 00:26:59 -0700 Subject: [PATCH 3/5] Fix #12557: no editor hang when looking for edges Fixed a bug that caused the scenario editor to hang when searching for the map edge --- src/openrct2/world/Footpath.cpp | 39 +++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/openrct2/world/Footpath.cpp b/src/openrct2/world/Footpath.cpp index 6c7c9be6f3..7754b31409 100644 --- a/src/openrct2/world/Footpath.cpp +++ b/src/openrct2/world/Footpath.cpp @@ -1294,7 +1294,7 @@ static int32_t footpath_is_connected_to_map_edge_helper( // Struct for keeping track of tile state struct TileState { - //bool processed = false; + bool processed = false; CoordsXYZ footpathPos; int32_t direction; int32_t level; @@ -1310,21 +1310,35 @@ static int32_t footpath_is_connected_to_map_edge_helper( // Captures the current state of the variables and stores them for iteration later auto CaptureCurrentTileState = [&tiles, &footpathPos, &direction, &level, &distanceFromJunction, &junctionTolerance, &numPendingTiles]() -> void { - tiles.push_back({ footpathPos, direction, level, distanceFromJunction, junctionTolerance }); + // Search for an entry of this in our list already + for (size_t ii(0); ii < tiles.size(); ++ii) + if (tiles[ii].footpathPos == footpathPos && tiles[ii].direction == direction) + return; + + // If we get here we did not find it, so insert it + tiles.push_back({ false, footpathPos, direction, level, distanceFromJunction, junctionTolerance }); ++numPendingTiles; }; // Loads the next tile to visit into our variables auto LoadNextTileElement = [&tiles, &footpathPos, &direction, &level, &distanceFromJunction, &junctionTolerance, &numPendingTiles]() -> void { - size_t ii = tiles.size(); - --numPendingTiles; - footpathPos = tiles[ii - 1].footpathPos; - direction = tiles[ii - 1].direction; - level = tiles[ii - 1].level; - distanceFromJunction = tiles[ii - 1].distanceFromJunction; - junctionTolerance = tiles[ii - 1].junctionTolerance; - tiles.pop_back(); + for (size_t ii = tiles.size(); ii > 0; --ii) + { + if (tiles[ii - 1].processed) + continue; + else + { + tiles[ii - 1].processed = true; + --numPendingTiles; + footpathPos = tiles[ii - 1].footpathPos; + direction = tiles[ii - 1].direction; + level = tiles[ii - 1].level; + distanceFromJunction = tiles[ii - 1].distanceFromJunction; + junctionTolerance = tiles[ii - 1].junctionTolerance; + return; + } + } }; // Helper method for footpath_is_connected_to_map_edge_helper @@ -1371,9 +1385,13 @@ static int32_t footpath_is_connected_to_map_edge_helper( // Check if we are at edge of map if (targetPos.x < COORDS_XY_STEP || targetPos.y < COORDS_XY_STEP) + { return FOOTPATH_SEARCH_SUCCESS; + } if (targetPos.x >= gMapSizeUnits || targetPos.y >= gMapSizeUnits) + { return FOOTPATH_SEARCH_SUCCESS; + } tileElement = map_get_first_element_at(targetPos); if (tileElement == nullptr) @@ -1417,7 +1435,6 @@ static int32_t footpath_is_connected_to_map_edge_helper( // Find next direction to go if (!get_next_direction(edges, &direction)) - // return FOOTPATH_SEARCH_INCOMPLETE; continue; edges &= ~(1 << direction); From 6a0d089c84a9194a650387e2d0f1079a370e3af7 Mon Sep 17 00:00:00 2001 From: Nathan Ikola Date: Thu, 6 Aug 2020 12:12:50 -0700 Subject: [PATCH 4/5] Add vanilla RCT2 behavior for unowning paths Implements vanilla RCT2 behavior when unowning paths by bypassing the early returns when the UNOWN flag is set and letting the method continue to unown all connected path tiles --- src/openrct2/world/Footpath.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/openrct2/world/Footpath.cpp b/src/openrct2/world/Footpath.cpp index 7754b31409..81e33f1d15 100644 --- a/src/openrct2/world/Footpath.cpp +++ b/src/openrct2/world/Footpath.cpp @@ -1291,6 +1291,9 @@ static int32_t footpath_is_connected_to_map_edge_helper( CoordsXYZ footpathPos, int32_t direction, int32_t flags, int32_t level, int32_t distanceFromJunction, int32_t junctionTolerance) { + // return value of this function + int32_t returnVal = FOOTPATH_SEARCH_INCOMPLETE; + // Struct for keeping track of tile state struct TileState { @@ -1386,11 +1389,13 @@ static int32_t footpath_is_connected_to_map_edge_helper( // Check if we are at edge of map if (targetPos.x < COORDS_XY_STEP || targetPos.y < COORDS_XY_STEP) { - return FOOTPATH_SEARCH_SUCCESS; + if (!(flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN) || DoneProcessing()) + return FOOTPATH_SEARCH_SUCCESS; } if (targetPos.x >= gMapSizeUnits || targetPos.y >= gMapSizeUnits) { - return FOOTPATH_SEARCH_SUCCESS; + if (!(flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN) || DoneProcessing()) + return FOOTPATH_SEARCH_SUCCESS; } tileElement = map_get_first_element_at(targetPos); @@ -1456,7 +1461,11 @@ static int32_t footpath_is_connected_to_map_edge_helper( if (distanceFromJunction != 0) --junctionTolerance; if (junctionTolerance < 0) - return FOOTPATH_SEARCH_TOO_COMPLEX; + if (!(flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN)) + { + returnVal = FOOTPATH_SEARCH_TOO_COMPLEX; + break; + } // Loop until there are no more directions we can go do @@ -1471,9 +1480,10 @@ static int32_t footpath_is_connected_to_map_edge_helper( CaptureCurrentTileState(); } while (get_next_direction(edges, &direction)); } + break; } while (!(tileElement++)->IsLastForTile()); } - return level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : FOOTPATH_SEARCH_INCOMPLETE; + return level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : returnVal; } // TODO: Use GAME_COMMAND_FLAGS From 7389e10e95c0279ecbef820893464a05c8a63769 Mon Sep 17 00:00:00 2001 From: Nathan Ikola Date: Fri, 9 Oct 2020 15:11:08 -0700 Subject: [PATCH 5/5] Apply change requested by duncanspumpkin Remove or improve comments that do not provide information Use range based for loops when indexes are not necessary Use more informative variable names Load tiles starting from the most recently added tile to better mimic the recursive logic of the original code Operate on TileState objects rather than overwriting local variables to simplify the logic in CaptureCurrentTileState and LoadNextTileElement Use the existing map_is_edge function versus manually checking for the map edge in the code Fix edge culling logic to correctly cull all banner edges Replace incorrect continue with break Remove level, distanceFromJunction, and junctionTolerance from function arguments as they are no longer required --- src/openrct2/world/Footpath.cpp | 186 ++++++++++++++++---------------- 1 file changed, 93 insertions(+), 93 deletions(-) diff --git a/src/openrct2/world/Footpath.cpp b/src/openrct2/world/Footpath.cpp index 81e33f1d15..b5090009f1 100644 --- a/src/openrct2/world/Footpath.cpp +++ b/src/openrct2/world/Footpath.cpp @@ -1287,14 +1287,10 @@ static bool get_next_direction(int32_t edges, int32_t* direction) * (1 << 5): Unown * (1 << 7): Ignore no entry signs */ -static int32_t footpath_is_connected_to_map_edge_helper( - CoordsXYZ footpathPos, int32_t direction, int32_t flags, int32_t level, int32_t distanceFromJunction, - int32_t junctionTolerance) +static int32_t footpath_is_connected_to_map_edge_helper(CoordsXYZ footpathPos, int32_t direction, int32_t flags) { - // return value of this function int32_t returnVal = FOOTPATH_SEARCH_INCOMPLETE; - // Struct for keeping track of tile state struct TileState { bool processed = false; @@ -1310,45 +1306,47 @@ static int32_t footpath_is_connected_to_map_edge_helper( TileElement* tileElement = nullptr; int numPendingTiles = 0; - // Captures the current state of the variables and stores them for iteration later - auto CaptureCurrentTileState = [&tiles, &footpathPos, &direction, &level, &distanceFromJunction, &junctionTolerance, - &numPendingTiles]() -> void { - // Search for an entry of this in our list already - for (size_t ii(0); ii < tiles.size(); ++ii) - if (tiles[ii].footpathPos == footpathPos && tiles[ii].direction == direction) - return; + TileState currentTile = { false, footpathPos, direction, 0, 0, 16 }; - // If we get here we did not find it, so insert it - tiles.push_back({ false, footpathPos, direction, level, distanceFromJunction, junctionTolerance }); + // Captures the current state of the variables and stores them in tiles vector for iteration later + auto CaptureCurrentTileState = [&tiles, &numPendingTiles](TileState t_currentTile) -> void { + // Search for an entry of this tile in our list already + for (const TileState& tile : tiles) + { + if (tile.footpathPos == t_currentTile.footpathPos && tile.direction == t_currentTile.direction) + return; + } + + // If we get here we did not find it, so insert the tile into our list + tiles.push_back(t_currentTile); ++numPendingTiles; }; // Loads the next tile to visit into our variables - auto LoadNextTileElement = [&tiles, &footpathPos, &direction, &level, &distanceFromJunction, &junctionTolerance, - &numPendingTiles]() -> void { - for (size_t ii = tiles.size(); ii > 0; --ii) + auto LoadNextTileElement = [&tiles, &numPendingTiles](TileState& t_currentTile) -> void { + // Do not continue if there are no tiles in the list + if (tiles.size() == 0) + return; + + // Find the next unprocessed tile + for (size_t tileIndex = tiles.size() - 1; tileIndex > 0; --tileIndex) { - if (tiles[ii - 1].processed) + if (tiles[tileIndex].processed) continue; - else - { - tiles[ii - 1].processed = true; - --numPendingTiles; - footpathPos = tiles[ii - 1].footpathPos; - direction = tiles[ii - 1].direction; - level = tiles[ii - 1].level; - distanceFromJunction = tiles[ii - 1].distanceFromJunction; - junctionTolerance = tiles[ii - 1].junctionTolerance; - return; - } + --numPendingTiles; + t_currentTile = tiles[tileIndex]; + tiles[tileIndex].processed = true; + return; } + // Default to tile 0 + --numPendingTiles; + t_currentTile = tiles[0]; + tiles[0].processed = true; }; - // Helper method for footpath_is_connected_to_map_edge_helper - // to help make the function more readable + // Encapsulate the tile skipping logic to make do-while more readable auto SkipTileElement = [](int32_t ste_flags, TileElement* ste_tileElement, int32_t& ste_slopeDirection, int32_t ste_direction, const CoordsXYZ& ste_targetPos) { - // We are only interested in paths if (ste_tileElement->GetType() != TILE_ELEMENT_TYPE_PATH) return true; @@ -1369,44 +1367,37 @@ static int32_t footpath_is_connected_to_map_edge_helper( return false; }; - // Function to test whether or not the vector has been processed - auto DoneProcessing = [&numPendingTiles]() -> bool { return numPendingTiles == 0; }; - int32_t edges, slopeDirection; // Capture the current tile state to begin the loop - CaptureCurrentTileState(); + CaptureCurrentTileState(currentTile); // Loop on this until all tiles are processed or we return - while (!DoneProcessing()) + while (numPendingTiles > 0) { - LoadNextTileElement(); + LoadNextTileElement(currentTile); - CoordsXYZ targetPos = CoordsXYZ{ CoordsXY{ footpathPos } + CoordsDirectionDelta[direction], footpathPos.z }; - if (++level > 250) + CoordsXYZ targetPos = CoordsXYZ{ CoordsXY{ currentTile.footpathPos } + CoordsDirectionDelta[currentTile.direction], + currentTile.footpathPos.z }; + + if (++currentTile.level > 250) return FOOTPATH_SEARCH_TOO_COMPLEX; - // Check if we are at edge of map - if (targetPos.x < COORDS_XY_STEP || targetPos.y < COORDS_XY_STEP) + // Return immediately if we are at the edge of the map and not unowning + // Or if we are unowning and have no tiles left + if ((map_is_edge(targetPos) && !(flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN))) { - if (!(flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN) || DoneProcessing()) - return FOOTPATH_SEARCH_SUCCESS; - } - if (targetPos.x >= gMapSizeUnits || targetPos.y >= gMapSizeUnits) - { - if (!(flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN) || DoneProcessing()) - return FOOTPATH_SEARCH_SUCCESS; + return FOOTPATH_SEARCH_SUCCESS; } tileElement = map_get_first_element_at(targetPos); if (tileElement == nullptr) - return level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : FOOTPATH_SEARCH_INCOMPLETE; + return currentTile.level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : FOOTPATH_SEARCH_INCOMPLETE; // Loop while there are unvisited TileElements at targetPos do { - // Skip this tile element if it is not a path - if (SkipTileElement(flags, tileElement, slopeDirection, direction, targetPos)) + if (SkipTileElement(flags, tileElement, slopeDirection, currentTile.direction, targetPos)) continue; // Unown the footpath if needed @@ -1414,83 +1405,92 @@ static int32_t footpath_is_connected_to_map_edge_helper( footpath_fix_ownership(targetPos); edges = tileElement->AsPath()->GetEdges(); - direction = direction_reverse(direction); + currentTile.direction = direction_reverse(currentTile.direction); if (!(flags & FOOTPATH_CONNECTED_MAP_EDGE_IGNORE_NO_ENTRY)) { - // Determine how many elements to check - size_t limit = 0; - if (tileElement[1].GetType() == TILE_ELEMENT_TYPE_BANNER) - limit = 4; - if (tileElement[2].GetType() == TILE_ELEMENT_TYPE_BANNER && tileElement[1].GetType() != TILE_ELEMENT_TYPE_PATH) - limit = 6; - - for (size_t ii(1); ii < limit; ++ii) + int elementIndex = 1; + // Loop over all elements and cull appropriate edges + do { - if ((&tileElement[ii - 1])->IsLastForTile()) + if (tileElement[elementIndex].GetType() == TILE_ELEMENT_TYPE_PATH) break; - if (tileElement[ii].GetType() != TILE_ELEMENT_TYPE_BANNER) - break; - edges &= tileElement[ii].AsBanner()->GetAllowedEdges(); - } + if (tileElement[elementIndex].GetType() != TILE_ELEMENT_TYPE_BANNER) + { + ++elementIndex; + continue; + } + edges &= tileElement[elementIndex].AsBanner()->GetAllowedEdges(); + ++elementIndex; + } while (!tileElement[elementIndex].IsLastForTile()); } // Exclude the direction we came from targetPos.z = tileElement->GetBaseZ(); - edges &= ~(1 << direction); + edges &= ~(1 << currentTile.direction); - // Find next direction to go - if (!get_next_direction(edges, &direction)) - continue; + if (!get_next_direction(edges, ¤tTile.direction)) + break; - edges &= ~(1 << direction); + edges &= ~(1 << currentTile.direction); if (edges == 0) { // Only possible direction to go - if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == direction) + if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == currentTile.direction) targetPos.z += PATH_HEIGHT_STEP; // Prepare the next iteration - footpathPos = targetPos; - ++distanceFromJunction; - CaptureCurrentTileState(); + currentTile.footpathPos = targetPos; + ++currentTile.distanceFromJunction; + CaptureCurrentTileState(currentTile); } else { // We have reached a junction - --junctionTolerance; - if (distanceFromJunction != 0) - --junctionTolerance; - if (junctionTolerance < 0) - if (!(flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN)) - { - returnVal = FOOTPATH_SEARCH_TOO_COMPLEX; - break; - } + --currentTile.junctionTolerance; + if (currentTile.distanceFromJunction != 0) + { + --currentTile.junctionTolerance; + } + + if (currentTile.junctionTolerance < 0 && !(flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN)) + { + returnVal = FOOTPATH_SEARCH_TOO_COMPLEX; + break; + } // Loop until there are no more directions we can go do { - edges &= ~(1 << direction); - if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == direction) + edges &= ~(1 << currentTile.direction); + if (tileElement->AsPath()->IsSloped() + && tileElement->AsPath()->GetSlopeDirection() == currentTile.direction) + { targetPos.z += PATH_HEIGHT_STEP; + } - // Prepare the next iteration - footpathPos = targetPos; - distanceFromJunction = 0; - CaptureCurrentTileState(); - } while (get_next_direction(edges, &direction)); + // Add each possible path to the list of pending tiles + currentTile.footpathPos = targetPos; + currentTile.distanceFromJunction = 0; + CaptureCurrentTileState(currentTile); + } while (get_next_direction(edges, ¤tTile.direction)); } break; } while (!(tileElement++)->IsLastForTile()); + + // Return success if we have unowned all tiles in our pending list + if ((flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN) && numPendingTiles <= 0) + { + return FOOTPATH_SEARCH_SUCCESS; + } } - return level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : returnVal; + return currentTile.level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : returnVal; } // TODO: Use GAME_COMMAND_FLAGS int32_t footpath_is_connected_to_map_edge(const CoordsXYZ& footpathPos, int32_t direction, int32_t flags) { flags |= FOOTPATH_CONNECTED_MAP_EDGE_IGNORE_QUEUES; - return footpath_is_connected_to_map_edge_helper(footpathPos, direction, flags, 0, 0, 16); + return footpath_is_connected_to_map_edge_helper(footpathPos, direction, flags); } bool PathElement::IsSloped() const