From f3cf23f5d5052af2e30c90f067b8b8b14cf4770e Mon Sep 17 00:00:00 2001 From: zaxcav Date: Wed, 21 Sep 2016 22:21:48 +0200 Subject: [PATCH 01/13] Ignore no-through paths that do not reach the goal in peep path finding. Pathfinding previously picked whichever path got closest to the goal, also when that path is a dead end with no through way to the goal. Adjust the heuristic score comparison to only consider map elements that have reached the goal and search paths that reach one of the search limits, ending on a path tile (i.e. the path continues, from which the goal may still be reachable). Update peep->pathfind_history to only store thin junctions. --- src/peep/peep.c | 724 ++++++++++++++++++++++++++++++------------------ 1 file changed, 456 insertions(+), 268 deletions(-) diff --git a/src/peep/peep.c b/src/peep/peep.c index 91e9524269..fea8a0a6ab 100644 --- a/src/peep/peep.c +++ b/src/peep/peep.c @@ -59,7 +59,6 @@ uint8 gPeepPathFindQueueRideIndex; bool gPeepPathFindSingleChoiceSection; // uint32 gPeepPathFindAltStationNum; static bool _peepPathFindIsStaff; -static uint8 _peepPathFindQueueRideIndex; static sint8 _peepPathFindNumJunctions; static sint32 _peepPathFindTilesChecked; static uint8 _peepPathFindFewestNumSteps; @@ -79,14 +78,18 @@ static uint8 _peepPotentialRides[256]; enum { PATH_SEARCH_DEAD_END, - PATH_SEARCH_RIDE_EXIT, - PATH_SEARCH_RIDE_ENTRANCE, - PATH_SEARCH_JUNCTION, - PATH_SEARCH_PARK_EXIT, - PATH_SEARCH_LIMIT_REACHED, PATH_SEARCH_WIDE, + PATH_SEARCH_THIN, + PATH_SEARCH_JUNCTION, PATH_SEARCH_RIDE_QUEUE, - PATH_SEARCH_OTHER = 10 + PATH_SEARCH_RIDE_ENTRANCE, + PATH_SEARCH_RIDE_EXIT, + PATH_SEARCH_PARK_EXIT, + PATH_SEARCH_SHOP_ENTRANCE, + PATH_SEARCH_LIMIT_REACHED, + PATH_SEARCH_LOOP, + PATH_SEARCH_OTHER, + PATH_SEARCH_FAILED }; enum { @@ -8412,14 +8415,15 @@ static bool is_valid_path_z_and_direction(rct_map_element *mapElement, int curre /** * * Returns: - * 6 - wide path - * 7 - ride queue - * 10 - other + * 1 - PATH_SEARCH_WIDE (path with wide flag set) + * 4 - PATH_SEARCH_RIDE_QUEUE (queue path connected to a ride) + * 11 - PATH_SEARCH_OTHER (other path than the above) + * 12 - PATH_SEARCH_FAILED (no path element found) * * rct2: 0x00694BAE * - * Returns the destination path tile type tile a peep can get to from x,y,z / - * inputMapElement in the given direction following single width paths only. + * Returns the type of the next footpath tile a peep can get to from x,y,z / + * inputMapElement in the given direction. */ static uint8 footpath_element_next_in_direction(sint16 x, sint16 y, sint16 z, rct_map_element *mapElement, uint8 chosenDirection) { @@ -8444,21 +8448,22 @@ static uint8 footpath_element_next_in_direction(sint16 x, sint16 y, sint16 z, rc return PATH_SEARCH_OTHER; } while (!map_element_is_last_for_tile(nextMapElement++)); - return PATH_SEARCH_OTHER; + return PATH_SEARCH_FAILED; } /** * * Returns: - * 0 - dead end? - * 1 - ride exit - * 2 - ride entrance - * 3 - path junction - * 4 - park entrance / exit - * 5 - search limit reached - * 6 - wide path - * 7 - ride queue - * For return values 1, 2 the rideIndex is stored in outRideIndex. + * 0 - PATH_SEARCH_DEAD_END (path is a dead end, i.e. < 2 edges) + * 1 - PATH_SEARCH_WIDE (path with wide flag set) + * 3 - PATH_SEARCH_JUNCTION (path is a junction, i.e. > 2 edges) + * 5 - PATH_SEARCH_RIDE_ENTRANCE (map element is a ride entrance) + * 6 - PATH_SEARCH_RIDE_EXIT (map element is a ride exit) + * 7 - PATH_SEARCH_PARK_EXIT park entrance / exit (map element is a park entrance/exit) + * 8 - PATH_SEARCH_SHOP_ENTRANCE (map element is a shop entrance) + * 9 - PATH_SEARCH_LIMIT_REACHED (search limit reached without reaching path end) + * 12 - PATH_SEARCH_FAILED (no path element found) + * For return values 5, 6 & 8 the rideIndex is stored in outRideIndex. * * rct2: 0x006949B9 * @@ -8486,7 +8491,7 @@ static uint8 footpath_element_dest_in_dir( rct_ride *ride = get_ride(rideIndex); if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_IS_SHOP)) { *outRideIndex = rideIndex; - return PATH_SEARCH_RIDE_ENTRANCE; + return PATH_SEARCH_SHOP_ENTRANCE; } break; case MAP_ELEMENT_TYPE_ENTRANCE: @@ -8531,27 +8536,31 @@ static uint8 footpath_element_dest_in_dir( } return footpath_element_dest_in_dir(x, y, z, mapElement, direction, outRideIndex, level + 1); } + return PATH_SEARCH_DEAD_END; } } while (!map_element_is_last_for_tile(mapElement++)); - return PATH_SEARCH_DEAD_END; + return PATH_SEARCH_FAILED; } /** * Returns: - * 0 - dead end? - * 1 - ride exit - * 2 - ride entrance - * 3 - path junction - * 4 - park entrance / exit - * 5 - search limit reached - * 6 - wide path - * For return values 1, 2 the rideIndex is stored in outRideIndex. + * 0 - PATH_SEARCH_DEAD_END (path is a dead end, i.e. < 2 edges) + * 1 - PATH_SEARCH_WIDE (path with wide flag set) + * 3 - PATH_SEARCH_JUNCTION (path is a junction, i.e. > 2 edges) + * 5 - PATH_SEARCH_RIDE_ENTRANCE (map element is a ride entrance) + * 6 - PATH_SEARCH_RIDE_EXIT (map element is a ride exit) + * 7 - PATH_SEARCH_PARK_EXIT park entrance / exit (map element is a park entrance/exit) + * 8 - PATH_SEARCH_SHOP_ENTRANCE (map element is a shop entrance) + * 9 - PATH_SEARCH_LIMIT_REACHED (search limit reached without reaching path end) + * 12 - PATH_SEARCH_FAILED (no path element found) + * For return values 5, 6 & 8 the rideIndex is stored in outRideIndex. * * rct2: 0x006949A4 * - * Returns the destination path tile type tile a peep can get to from x,y,z / - * inputMapElement in the given direction following single width paths only. + * Returns the destination tile type a peep can get to from x,y,z / + * inputMapElement in the given direction following single width paths only + * and stopping as soon as a path junction is encountered. * Note that a junction is a path with > 2 reachable neighbouring path tiles, * so wide paths have LOTS of junctions. * This is useful for finding out what is at the end of a short single @@ -8597,6 +8606,7 @@ static uint8 peep_pathfind_get_max_number_junctions(rct_peep* peep){ if (peep->type == PEEP_TYPE_STAFF) return 8; + // PEEP_FLAGS_2? It's cleared here but not set anywhere! if ((peep->peep_flags & PEEP_FLAGS_2)){ if ((scenario_rand() & 0xFFFF) <= 7281) peep->peep_flags &= ~PEEP_FLAGS_2; @@ -8618,6 +8628,44 @@ static uint8 peep_pathfind_get_max_number_junctions(rct_peep* peep){ return 5; } +/** + * Returns if the path as xzy is a 'thin' junction. + * A junction is considered 'thin' if it has more than 2 edges + * leading to non-wide path elements; edges leading to non-path elements + * (e.g. ride/shop entrances) or ride queues are not counted, since entrances + * and ride queues coming off a path should not result in the path being + * considered a junction. + */ +static bool path_is_thin_junction(rct_map_element *path, sint16 x, sint16 y, uint8 z) { + uint8 edges = path_get_permitted_edges(path); + + int test_edge = bitscanforward(edges); + if (test_edge == -1) return false; + + bool thin_junction = false; + int thin_count = 0; + do + { + int fp_result = footpath_element_next_in_direction(x, y, z, path, test_edge); + + /* Ignore non-paths (e.g. ride entrances, shops), wide paths + * and ride queues (per ignoreQueues) when counting + * neighbouring tiles. */ + if (fp_result != PATH_SEARCH_FAILED && + fp_result != PATH_SEARCH_WIDE && + fp_result != PATH_SEARCH_RIDE_QUEUE) { + thin_count++; + } + + if (thin_count > 2) { + thin_junction = true; + break; + } + edges &= ~(1 << test_edge); + } while ((test_edge = bitscanforward(edges)) != -1); + return thin_junction; +} + /** * Returns the best (smallest) score reachable from Tile x,y,z in direction * test_edge, within the limits of the search. @@ -8627,6 +8675,14 @@ static uint8 peep_pathfind_get_max_number_junctions(rct_peep* peep){ * of steps. i.e. the search gets as close as possible to the goal in as few * steps as possible. * + * Each tile is checked to determine if the goal is reached. + * When the goal is not reached the search result is only updated at the END + * of each search path (some map element that is not a path or a path at which + * a search limit is reached), NOT at each step along the way. + * This means that the search ignores thin paths that are "no through paths" + * no matter how close to the goal they get, but will follow possible "through + * paths". + * * The implementation is essentially an A* path finding algorithm over the * path layout in xyz; however a best score is tracked via the score parameter * rather than storing scores for each xyz, which means explicit loop detection @@ -8651,244 +8707,358 @@ static uint8 peep_pathfind_get_max_number_junctions(rct_peep* peep){ * - _peepPathFindHistory - the starting point and all junctions navigated * in the current search path - used to detect path loops. * + * The score is only updated when: + * - the goal is reached; + * - a wide tile is encountered with a better search result - the goal may + * still be reachable from here; + * - a junction is encountered with a better search result and + * maxNumJunctions is exceeded - the goal may still be reachable from here; + * - returning from a recursive call if a search limit (i.e. either + * maxNumStep or maxTilesChecked) was reached and the current tile has a + * better search result and the goal may still be reachable from here + * (i.e. not a dead end path tile). + * * rct2: 0x0069A997 */ -static uint16 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 counter, uint16 score, int test_edge) { +static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 counter, uint16 *score, int test_edge) { + uint searchResult = PATH_SEARCH_FAILED; + x += TileDirectionDelta[test_edge].x; y += TileDirectionDelta[test_edge].y; - ++counter; - if (--_peepPathFindTilesChecked < 0) { - #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - log_information("[%03d] Return from %d,%d,%d; TilesChecked < 0; Score: %d\n", counter, x >> 5, y >> 5, z, score); - #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - return score; - } + _peepPathFindTilesChecked--; - /* If counter (# steps taken) exceeds the limit, the current search - * path ends here */ - if (counter > 200) { + /* If the maxNumSteps or maxTilesChecked limits are exceeded the + * search ends here. Return the searchResult with the original score. */ + if (counter > 200 || _peepPathFindTilesChecked < 0) { #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - log_information("[%03d] Return from %d,%d,%d; counter > 200; Score: %d\n", counter, x >> 5, y >> 5, z, score); + log_info("[%03d] Return from %d,%d,%d; Search limit exceeded", counter, x >> 5, y >> 5, z); #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - return score; + searchResult = PATH_SEARCH_LIMIT_REACHED; + return searchResult; } /* If this is where the search started this is a search loop and the - * current search path ends here */ + * current search path ends here. + * Return the searchResult with the original score. */ if ((_peepPathFindHistory[0].x == (uint8)(x >> 5)) && (_peepPathFindHistory[0].y == (uint8)(y >> 5)) && (_peepPathFindHistory[0].z == (uint8)z)) { #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - log_information("[%03d] Return from %d,%d,%d; At start; Score: %d\n", counter, x >> 5, y >> 5, z, score); + log_info("[%03d] Return from %d,%d,%d; At start", counter, x >> 5, y >> 5, z); #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - return score; + searchResult = PATH_SEARCH_LOOP; + return searchResult; } + /* Get the next map element in the direction of test_edge, ignoring + * map elements that are not of interest, which includes wide paths + * and foreign ride queues (depending on gPathFindIgnoreForeignQueues */ + bool found = false; + uint8 rideIndex = 0xFF; + rct_map_element *mapElement = map_get_first_element_at(x / 32, y / 32); + do { + if (mapElement->flags & MAP_ELEMENT_FLAG_GHOST) continue; + + switch (map_element_get_type(mapElement)) { + case MAP_ELEMENT_TYPE_TRACK: + if (z != mapElement->base_height) continue; + /* More details about the mapElement are not needed. + * If in the future it would be useful to know + * whether the map element is a shop, the following + * commented out code will do it. */ + //rideIndex = mapElement->properties.track.ride_index; + //rct_ride *ride = get_ride(rideIndex); + //if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_IS_SHOP)) { + // searchResult = PATH_SEARCH_SHOP_ENTRANCE; + //} else { + searchResult = PATH_SEARCH_OTHER; + //} + found = true; + break; + case MAP_ELEMENT_TYPE_ENTRANCE: + if (z != mapElement->base_height) continue; + int direction; + searchResult = PATH_SEARCH_OTHER; + switch (mapElement->properties.entrance.type) { + case ENTRANCE_TYPE_RIDE_ENTRANCE: + direction = mapElement->type & MAP_ELEMENT_DIRECTION_MASK; + if (direction == test_edge) { + /* The rideIndex will be useful for + * adding transport rides later. */ + uint8 rideIndex = mapElement->properties.entrance.ride_index; + searchResult = PATH_SEARCH_RIDE_ENTRANCE; + } + break; + /* More details for other entrance types are not needed. + * If in the future it would be useful to know + * whether the map element is a ride exit or a park + * entrance/exit, the following commented out code + * will do it. */ + //case ENTRANCE_TYPE_RIDE_EXIT: + // direction = mapElement->type & MAP_ELEMENT_DIRECTION_MASK; + // if (direction == test_edge) { + // searchResult = PATH_SEARCH_RIDE_EXIT; + // } + // break; + //case ENTRANCE_TYPE_PARK_ENTRANCE: + // searchResult = PATH_SEARCH_PARK_EXIT; + } + found = true; + break; + case MAP_ELEMENT_TYPE_PATH: + if (!is_valid_path_z_and_direction(mapElement, z, test_edge)) continue; + + // Path may be sloped, so set z to path base height. + z = mapElement->base_height; + + if (footpath_element_is_wide(mapElement)) { + searchResult = PATH_SEARCH_WIDE; + found = true; + break; + } + + searchResult = PATH_SEARCH_THIN; + + uint8 numEdges = bitcount(path_get_permitted_edges(mapElement)); + + if (numEdges < 2) { + searchResult = PATH_SEARCH_DEAD_END; + } else if (numEdges > 2) { + searchResult = PATH_SEARCH_JUNCTION; + } else { // numEdges == 2 + if (footpath_element_is_queue(mapElement) && mapElement->properties.path.ride_index != gPeepPathFindQueueRideIndex) { + if (gPeepPathFindIgnoreForeignQueues && (mapElement->properties.path.ride_index != 0xFF)) { + // Path is a queue we aren't interested in + /* The rideIndex will be useful for + * adding transport rides later. */ + rideIndex = mapElement->properties.path.ride_index; + searchResult = PATH_SEARCH_RIDE_QUEUE; + } + } + } + found = true; + break; + } + + if (found) { + break; + } + } while (!map_element_is_last_for_tile(mapElement++)); + + if (!found) { + #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + log_info("[%03d] Return from %d,%d,%d; No map element found", counter, x >> 5, y >> 5, z); + #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + return searchResult; // PATH_SEARCH_FAILED + } + + /* At this point the map element is found. */ + + uint height = z; + if (map_element_get_type(mapElement) == MAP_ELEMENT_TYPE_PATH) { + // Adjust height for goal comparison according to the path slope. + if (footpath_element_is_sloped(mapElement)) { + if (footpath_element_get_slope_direction(mapElement) == test_edge) { + height += 2; + } + } + } + + // Calculate the heuristic score of this map element. uint16 x_delta = abs(gPeepPathFindGoalPosition.x - x); uint16 y_delta = abs(gPeepPathFindGoalPosition.y - y); if (x_delta < y_delta) x_delta >>= 4; else y_delta >>= 4; uint16 new_score = x_delta + y_delta; - uint16 z_delta = abs(gPeepPathFindGoalPosition.z - z); + uint16 z_delta = abs(gPeepPathFindGoalPosition.z - height); z_delta <<= 1; new_score += z_delta; - if (new_score < score || (new_score == score && counter < _peepPathFindFewestNumSteps)) { - score = new_score; - _peepPathFindFewestNumSteps = counter; - /* If this tile is the search goal (score == 0) the current - * search path ends here */ - if (score == 0) { - #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - log_information("[%03d] Return from %d,%d,%d; At goal; Score: %d\n", counter, x >> 5, y >> 5, z, score); - #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - return score; + /* If this map element is the search goal the current search path ends here. + * Return the searchResult with the new score. */ + if (new_score == 0) { + if (new_score < *score || counter < _peepPathFindFewestNumSteps) { + _peepPathFindFewestNumSteps = counter; } - } - - /* Get the next tile in the direction of test_edge, ignoring - * tiles that are not of interest, which includes wide tiles - * and foreign ride queues (depending on gPathFindIgnoreForeignQueues */ - bool found = false; - rct_map_element *path = map_get_first_element_at(x / 32, y / 32); - do { - if (map_element_get_type(path) != MAP_ELEMENT_TYPE_PATH) continue; - - /* zaxcav: recommend restructuring these nested conditions - * for logical clarity. */ - /* i.e. as follows: - if (footpath_element_is_sloped(path) { - if (footpath_element_get_slope_direction(path) == test_edge) && (path->base_height != z)) continue; - elseif ((footpath_element_get_slope_direction(path) ^ 2) == test_edge) && (path->base_height + 2 != z)) continue; - } elseif (footpath_element_is_wide(path)) continue; - */ - if (footpath_element_is_sloped(path) && - footpath_element_get_slope_direction(path) != test_edge) { - if ((footpath_element_get_slope_direction(path) ^ 2) != test_edge) continue; - if (path->base_height + 2 != z) continue; - } else { - if (path->base_height != z) continue; - if (footpath_element_is_wide(path)) continue; - } - - if (footpath_element_is_queue(path) && path->properties.path.ride_index != gPeepPathFindQueueRideIndex) { - if (gPeepPathFindIgnoreForeignQueues && (path->properties.path.ride_index != 0xFF)) { - // Path is a queue we aren't interested in - continue; - } - } - - found = true; - break; - } while (!map_element_is_last_for_tile(path++)); - - /* If no tile of interest (for continuing the current search path) - * was found, the current search path ends here. */ - if (!found) { + *score = new_score; #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - log_information("[%03d] Return from %d,%d,%d; Not found; Score: %d\n", counter, x >> 5, y >> 5, z, score); + log_info("[%03d] Return from %d,%d,%d; At goal; Score: %d", counter, x >> 5, y >> 5, z, *score); #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - return score; + return searchResult; } - /* Get all the permitted_edges of the next tile */ - uint8 edges = path_get_permitted_edges(path); - z = path->base_height; + /* At this point the map element tile is not the goal. */ + + /* If this map element is not a path, the search cannot be + * continued, so return the searchResult with the original score. */ + if (searchResult != PATH_SEARCH_DEAD_END && + searchResult != PATH_SEARCH_THIN && + searchResult != PATH_SEARCH_JUNCTION && + searchResult != PATH_SEARCH_WIDE) { + #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + log_info("[%03d] Return from %d,%d,%d; Not a path", counter, x >> 5, y >> 5, z); + #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + return searchResult; + } + + /* At this point the map element is a path. */ + + /* If this is a wide path the search ends here but the goal + * could still be reachable from here. + * If this is the best result so far, return the searchResult with + * the new score. */ + if (searchResult == PATH_SEARCH_WIDE) { + if (new_score < *score || + (new_score == *score && + counter < _peepPathFindFewestNumSteps)) { + *score = new_score; + _peepPathFindFewestNumSteps = counter; + } + #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + log_info("[%03d] Return from %d,%d,%d; Wide path; Score: %d", counter, x >> 5, y >> 5, z, *score); + #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + return searchResult; + } + + /* At this point the map element is a non-wide path.*/ + + /* Get all the permitted_edges of the map element. */ + uint8 edges = path_get_permitted_edges(mapElement); #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - log_information("[%03d] Path %d,%d,%d; 0123:%d%d%d%d; Reverse: %d\n", counter, x >> 5, y >> 5, z, edges & 1, (edges & 2) >> 1, (edges & 4) >> 2, (edges & 8) >> 3, test_edge ^ 2); + log_info("[%03d] Path %d,%d,%d; Edges (0123):%d%d%d%d; Reverse: %d", counter, x >> 5, y >> 5, z, edges & 1, (edges & 2) >> 1, (edges & 4) >> 2, (edges & 8) >> 3, test_edge ^ 2); #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - /* Remove the reverse edge (i.e. the edge back to the previous tile - * in the current search path */ + /* Remove the reverse edge (i.e. the edge back to the previous map element.) */ edges &= ~(1 << (test_edge ^ 2)); test_edge = bitscanforward(edges); - /* If there are no other edges, this is a dead end - the current search - * path ends here. */ + /* If there are no other edges the current search ends here. + * Return the searchResult with the original score. */ if (test_edge == -1) { #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - log_information("[%03d] Return from %d,%d,%d; Dead end; Score: %d\n", counter, x >> 5, y >> 5, z, score); + log_info("[%03d] Return from %d,%d,%d; No more edges/dead end", counter, x >> 5, y >> 5, z); #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - return score; + searchResult = PATH_SEARCH_DEAD_END; // this should be superfluous. + return searchResult; } - /* If there is only a single remaining edge, continue the current - * search path by following it (recursive call). */ - if ((edges & ~(1 << test_edge)) == 0) { - if (footpath_element_is_sloped(path) && - footpath_element_get_slope_direction(path) == test_edge) { - z += 2; - } - _peepPathFindQueueRideIndex = true; - - #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - log_information("[%03d] Recurse from %d,%d,%d direction: %d; Segment; Score: %d\n", counter, x >> 5, y >> 5, z, test_edge, score); - #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - return peep_pathfind_heuristic_search(x, y, z, counter, score, test_edge); - } - - /* At this point it is known that the tile being examined is not - * the goal, not a dead end, and not a single tile wide path. - * It must be a junction of tiles. */ - - /* Determine if this is a 'thin' junction. - * A junction is considered 'thin' if it has more than 2 edges - * leading to non-wide path elements; furthermore, ride queues are not - * counted, since a ride queue coming off a path should not result in - * the path being considered a junction. - * - * A 'thin' junction is considered a junction with respect to - * the search limit _peepPathFindNumJunctions ; junctions that are not - * 'thin' per the above definition are treated like a path segment with - * respect to the search limits. */ - uint8 prescan_edges = path_get_permitted_edges(path); - int prescan_edge = bitscanforward(prescan_edges); bool thin_junction = false; - int thin_count = 0; - do - { - int fp_result = footpath_element_next_in_direction(x, y, z, path, prescan_edge); - if (fp_result != PATH_SEARCH_WIDE && fp_result != PATH_SEARCH_RIDE_QUEUE) { - thin_count++; - } + if (searchResult == PATH_SEARCH_JUNCTION) { + /* Check if this is a thin junction. And perform additional + * necessary checks. */ + thin_junction = path_is_thin_junction(mapElement, x, y, z); - if (thin_count > 2) { - thin_junction = true; - break; - } - prescan_edges &= ~(1 << prescan_edge); - } while ((prescan_edge = bitscanforward(prescan_edges)) != -1); + if (thin_junction == true) { + /* The current search path is passing through a thin + * junction on this map element. Only 'thin' junctions + * are counted towards the junction search limit. */ + --_peepPathFindNumJunctions; - /* The current search path is passing through a thin junction on - * this tile, so decrement the search parameter */ - if (thin_junction == true) { - --_peepPathFindNumJunctions; - - /* If junction search limit is reached, the current search - * path ends here */ - if (_peepPathFindNumJunctions < 0) { - #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - log_information("[%03d] Return from %d,%d,%d; NumJunctions < 0; Score: %d\n", counter, x >> 5, y >> 5, z, score); - #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - return score; - } - - /* Check the pathfind_history to see if this junction has been - * previously passed through in the current search path. - * i.e. this is a loop in the current search path. - * If so, the current search path ends here. */ - for (int junctionNum = _peepPathFindNumJunctions + 1; junctionNum < 16; junctionNum++) { - if ((_peepPathFindHistory[junctionNum].x == (uint8)(x >> 5)) && - (_peepPathFindHistory[junctionNum].y == (uint8)(y >> 5)) && - (_peepPathFindHistory[junctionNum].z == (uint8)z)) { + /* If the junction search limit is reached, the + * current search path ends here. The goal may still + * be reachable from here. + * If this is the best result so far, return the + * searchResult with the new score. */ + if (_peepPathFindNumJunctions < 0) { + if (new_score < *score || + (new_score == *score && + counter < _peepPathFindFewestNumSteps)) { + *score = new_score; + _peepPathFindFewestNumSteps = counter; + } #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - log_information("[%03d] Return from %d,%d,%d; Loop; Score: %d\n", counter, x >> 5, y >> 5, z, score); + log_info("[%03d] Return from %d,%d,%d; NumJunctions < 0; Score: %d", counter, x >> 5, y >> 5, z, *score); #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - return score; + return searchResult; + } + + /* Check the pathfind_history to see if this junction has been + * previously passed through in the current search path. + * i.e. this is a loop in the current search path. + * If so, the current search path ends here. + * Return the searchResult with the original score. */ + for (int junctionNum = _peepPathFindNumJunctions + 1; junctionNum < 16; junctionNum++) { + if ((_peepPathFindHistory[junctionNum].x == (uint8)(x >> 5)) && + (_peepPathFindHistory[junctionNum].y == (uint8)(y >> 5)) && + (_peepPathFindHistory[junctionNum].z == (uint8)z)) { + #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + log_info("[%03d] Return from %d,%d,%d; Loop", counter, x >> 5, y >> 5, z); + #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + searchResult = PATH_SEARCH_LOOP; + return searchResult; + } + } + + /* This junction was NOT previously visited in the current + * search path, so add the junction to the history. + * Note: the last junction that can be visited is not needed + * for future comparisons (since the search path will end when + * a further junction is encountered), so the starting point + * can be stored in _peepPathFindHistory[0]. */ + if (_peepPathFindNumJunctions > 0) { + _peepPathFindHistory[_peepPathFindNumJunctions].x = (uint8)(x >> 5); + _peepPathFindHistory[_peepPathFindNumJunctions].y = (uint8)(y >> 5); + _peepPathFindHistory[_peepPathFindNumJunctions].z = (uint8)z; } } - - /* This junction has NOT previously visited in the current - * search path, so add the junction to the history. - * Note: the last junction that can be visited is not needed - * for future comparisons (since the search path will end when - * a further junction is encountered), so the starting point - * can be stored in _peepPathFindHistory[0]. */ - if (_peepPathFindNumJunctions > 0) { - _peepPathFindHistory[_peepPathFindNumJunctions].x = (uint8)(x >> 5); - _peepPathFindHistory[_peepPathFindNumJunctions].y = (uint8)(y >> 5); - _peepPathFindHistory[_peepPathFindNumJunctions].z = (uint8)z; - } } - /* Continue searching down each edge of the junction (recursive call). - * If any edge returns with a score of 0 the search path ends. */ + /* Continue searching down each remaining edge of the path + * (recursive call). */ + uint8 continueResult; do { edges &= ~(1 << test_edge); sint8 savedNumJunctions = _peepPathFindNumJunctions; uint8 height = z; - _peepPathFindQueueRideIndex = false; - if (footpath_element_is_sloped(path) && - footpath_element_get_slope_direction(path) == test_edge) { + if (footpath_element_is_sloped(mapElement) && + footpath_element_get_slope_direction(mapElement) == test_edge) { height += 2; } #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + if (searchResult == PATH_SEARCH_JUNCTION) { if (thin_junction == true) - log_information("[%03d] Recurse from %d,%d,%d direction: %d; Thin-Junction; Score: %d\n", counter, x >> 5, y >> 5, z, test_edge, score); + log_info("[%03d] Recurse from %d,%d,%d edge: %d; Thin-Junction", counter, x >> 5, y >> 5, z, test_edge); else - log_information("[%03d] Recurse from %d,%d,%d direction: %d; Wide-Junction; Score: %d\n", counter, x >> 5, y >> 5, z, test_edge, score); + log_info("[%03d] Recurse from %d,%d,%d edge: %d; Wide-Junction", counter, x >> 5, y >> 5, z, test_edge); + } else { + log_info("[%03d] Recurse from %d,%d,%d edge: %d; Segment", counter, x >> 5, y >> 5, z, test_edge); + } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - score = peep_pathfind_heuristic_search(x, y, height, counter, score, test_edge); - _peepPathFindNumJunctions = savedNumJunctions; - } while ((test_edge = bitscanforward(edges)) != -1); - #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - log_information("[%03d] Return from %d,%d,%d; Best Junction; Score: %d\n", counter, x >> 5, y >> 5, z, score); - #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - return score; + uint16 savedScore = *score; + uint8 savedSteps = _peepPathFindFewestNumSteps; + continueResult = peep_pathfind_heuristic_search(x, y, height, counter, score, test_edge); + _peepPathFindNumJunctions = savedNumJunctions; + + if (continueResult == PATH_SEARCH_LIMIT_REACHED) { + /* When continueResult is PATH_SEARCH_LIMIT_REACHED the + * search limit was reached without checking another + * map element. This means the last tile checked in + * the current path search is the current map element. + * Update the best search result if appropriate and + * keep searchResult (discarding continueResult). */ + if (new_score < *score || (new_score == *score && counter < _peepPathFindFewestNumSteps)) { + *score = new_score; + _peepPathFindFewestNumSteps = counter; + } + } else { + /* When continueResult is not PATH_SEARCH_LIMIT_REACHED + * it is the search result of the last map element + * checked in the search path. If it has a different + * (i.e. better) search result, update searchResult. */ + if (*score < savedScore || _peepPathFindFewestNumSteps < savedSteps ) { + searchResult = continueResult; + } + } + #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + log_info("[%03d] Return to %d,%d,%d edge: %d; Score: %d", counter, x >> 5, y >> 5, z, test_edge, *score); + #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + + } while ((test_edge = bitscanforward(edges)) != -1); + return searchResult; } /** @@ -8904,10 +9074,7 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) sint8 maxNumJunctions = peep_pathfind_get_max_number_junctions(peep); /* The max number of tiles to check - a whole-search limit. - * Mainly to limit the performance impact of the path finding. - * WARNING: Exceeding this limit can cause path finding problems. - * FUTURE: fix how this limit is applied so all possible directions - * are always searched. */ + * Mainly to limit the performance impact of the path finding. */ sint32 maxTilesChecked = (peep->type == PEEP_TYPE_STAFF) ? 50000 : 15000; // Used to allow walking through no entry banners _peepPathFindIsStaff = (peep->type == PEEP_TYPE_STAFF); @@ -8918,27 +9085,38 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) .z = (uint8)(gPeepPathFindGoalPosition.z) }; + // Get the path element at this location + rct_map_element *dest_map_element = map_get_first_element_at(x / 32, y / 32); + bool found = false; + do { + if (dest_map_element->base_height != z) continue; + if (map_element_get_type(dest_map_element) != MAP_ELEMENT_TYPE_PATH) continue; + found = true; + break; + } while (!map_element_is_last_for_tile(dest_map_element++)); + // Peep is not on a path. + if (!found) return -1; + + /* Determine if the path is a thin junction. + * Only 'thin' junctions are remembered in peep->pathfind_history. */ + bool isThin = path_is_thin_junction(dest_map_element, x, y, z); + uint8 edges = 0xF; - if (peep->pathfind_goal.x == goal.x && + if (isThin && peep->pathfind_goal.x == goal.x && peep->pathfind_goal.y == goal.y && peep->pathfind_goal.z == goal.z ) { /* Use of peep->pathfind_history[]: * When walking to a goal, the peep pathfind_history stores - * the last 4 junctions that the peep walked through. - * For each these 4 junctions the peep remembers those edges it - * has not yet taken. - * If a peep returns to one of the 4 junctions that it + * the last 4 thin junctions that the peep walked through. + * For each of these 4 thin junctions the peep remembers + * those edges it has not yet taken. + * If a peep returns to one of the 4 thin junctions that it * remembers, it will only choose from the directions that it - * did not try yet. i.e. it avoids walking around in circles! - * ASIDE: unlike the (new) loop detection in the search - * heuristic (which considers only loops between 'thin' - * junctions and so handles thin paths and wide paths equally), - * this considers all path junctions - so wide paths make this - * next to useless. - * FUTURE: either update this to work on 'thin' junctions too - * or remove it as obsoleted by the (new) loop detection in the - * search heuristic. */ + * did not try yet. + * This forces to the peep pathfinding to try the "next best" + * direction after trying the "best" direction(s) and finding + * that the goal could not be reached. */ /* If the peep remembers walking through this junction * previously while heading for its goal, retrieve the @@ -8953,18 +9131,6 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) } } - // Get the path element at this location - rct_map_element *dest_map_element = map_get_first_element_at(x / 32, y / 32); - bool found = false; - do { - if (dest_map_element->base_height != z) continue; - if (map_element_get_type(dest_map_element) != MAP_ELEMENT_TYPE_PATH) continue; - found = true; - break; - } while (!map_element_is_last_for_tile(dest_map_element++)); - // Peep is not on a path. - if (!found) return -1; - // Remove any edges that are not permitted edges &= path_get_permitted_edges(dest_map_element); @@ -8979,7 +9145,7 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) uint8 best_sub = 0xFF; #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 - log_verbose("Pathfind start for goal %d,%d,%d from %d,%d,%d\n", goal.x, goal.y, goal.z, x >> 5, y >> 5, z); + log_verbose(stderr, "Pathfind start for goal %d,%d,%d from %d,%d,%d\n", goal.x, goal.y, goal.z, x >> 5, y >> 5, z); #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 /* Call the search heuristic on each edge, keeping track of the @@ -8987,6 +9153,7 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) * or for different edges with equal value, the edge with the * least steps (best_sub). */ int numEdges = bitcount(edges); + bool goalReachable = false; for (int test_edge = chosen_edge; test_edge != -1; test_edge = bitscanforward(edges)) { edges &= ~(1 << test_edge); uint8 height = z; @@ -8998,7 +9165,6 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) } _peepPathFindFewestNumSteps = 255; - _peepPathFindQueueRideIndex = false; /* Divide the maxTilesChecked global search limit * between the remaining edges to ensure the search * covers all of the remaining edges. */ @@ -9015,23 +9181,39 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) _peepPathFindHistory[0].y = (uint8)(y >> 5); _peepPathFindHistory[0].z = (uint8)z; - uint16 score = peep_pathfind_heuristic_search(x, y, height, 0, 0xFFFF, test_edge); + uint16 score = 0xFFFF; + uint8 searchResult = peep_pathfind_heuristic_search(x, y, height, 0, &score, test_edge); #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 - log_verbose("Pathfind test edge: %d score: %d steps: %d\n", test_edge, score, _peepPathFindFewestNumSteps); + log_verbose(stderr,"Pathfind test edge: %d score: %d steps: %d searchResult: %d\n", test_edge, score, _peepPathFindFewestNumSteps, searchResult); #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 - if (score < best_score || (score == best_score && _peepPathFindFewestNumSteps < best_sub)) { - chosen_edge = test_edge; - best_score = score; - best_sub = _peepPathFindFewestNumSteps; + if (score == 0 || + searchResult == PATH_SEARCH_THIN || + searchResult == PATH_SEARCH_JUNCTION || + searchResult == PATH_SEARCH_WIDE) { + if (score < best_score || (score == best_score && _peepPathFindFewestNumSteps < best_sub)) { + chosen_edge = test_edge; + best_score = score; + best_sub = _peepPathFindFewestNumSteps; + } } } + + /* Check if the heuristic search failed. e.g. all connected + * paths are within the search limits and none reaches the + * goal. */ + if (best_score == 0xFFFF) { + #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + log_verbose(stderr, "Pathfind heuristic search failed.\n"); + #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + return -1; + } #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 - log_verbose("Pathfind best edge %d with score %d\n", chosen_edge, best_score); + log_verbose(stderr, "Pathfind best edge %d with score %d\n", chosen_edge, best_score); #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 } - /* This is a new goal for the peep. Store it and reset the peep's + /* If this is a new goal for the peep. Store it and reset the peep's * pathfind_history. */ if (peep->pathfind_goal.direction > 3 || peep->pathfind_goal.x != goal.x || @@ -9047,27 +9229,29 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) memset(peep->pathfind_history, 0xFF, sizeof(peep->pathfind_history)); } - /* Peep remembers this location, so remove the chosen_edge from - * those left to try. */ - for (int i = 0; i < 4; ++i) { - if (peep->pathfind_history[i].x == x >> 5 && - peep->pathfind_history[i].y == y >> 5 && - peep->pathfind_history[i].z == z - ) { - peep->pathfind_history[i].direction &= ~(1 << chosen_edge); - return chosen_edge; + if (isThin) { + for (int i = 0; i < 4; ++i) { + if (peep->pathfind_history[i].x == x >> 5 && + peep->pathfind_history[i].y == y >> 5 && + peep->pathfind_history[i].z == z + ) { + /* Peep remembers this junction, so remove the + * chosen_edge from those left to try. */ + peep->pathfind_history[i].direction &= ~(1 << chosen_edge); + return chosen_edge; + } } - } - /* Peep does not remember this junction, so forget a junction and - * remember this junction. */ - int i = peep->pathfind_goal.direction++; - peep->pathfind_goal.direction &= 3; - peep->pathfind_history[i].x = x >> 5; - peep->pathfind_history[i].y = y >> 5; - peep->pathfind_history[i].z = z; - peep->pathfind_history[i].direction = 0xF; - peep->pathfind_history[i].direction &= ~(1 << chosen_edge); + /* Peep does not remember this junction, so forget a junction + * and remember this junction. */ + int i = peep->pathfind_goal.direction++; + peep->pathfind_goal.direction &= 3; + peep->pathfind_history[i].x = x >> 5; + peep->pathfind_history[i].y = y >> 5; + peep->pathfind_history[i].z = z; + peep->pathfind_history[i].direction = 0xF; + peep->pathfind_history[i].direction &= ~(1 << chosen_edge); + } return chosen_edge; } @@ -9387,6 +9571,9 @@ static int guest_path_finding(rct_peep* peep) return peep_move_one_tile(direction, peep); } + // Peep still has multiple edges to choose from. + + // Peep is outside the park. // loc_694F19: if (peep->outside_of_park != 0){ switch (peep->state) { @@ -9399,7 +9586,7 @@ static int guest_path_finding(rct_peep* peep) } } - /* Peep is inside the park and at a 'thin' junction: + /* Peep is inside the park. * If the peep does not have food, randomly cull the useless directions * (dead ends, ride exits, wide paths) from the edges. * In principle, peeps with food are not paying as much attention to @@ -9455,6 +9642,7 @@ static int guest_path_finding(rct_peep* peep) if (ride->status != RIDE_STATUS_OPEN) return guest_path_find_aimless(peep, edges); + // The ride is open. gPeepPathFindQueueRideIndex = rideIndex; /* Find the ride's closest entrance station to the peep. From fd6652635124eb2395b27bb07bbcee53f99c598d Mon Sep 17 00:00:00 2001 From: zaxcav Date: Wed, 21 Sep 2016 22:23:01 +0200 Subject: [PATCH 02/13] Basic commenting of the staff path finding code. --- src/peep/staff.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/peep/staff.c b/src/peep/staff.c index b93253cfb1..91449026c6 100644 --- a/src/peep/staff.c +++ b/src/peep/staff.c @@ -999,9 +999,12 @@ static uint8 staff_mechanic_direction_path_rand(rct_peep* peep, uint8 pathDirect * rct2: 0x006C0121 */ static uint8 staff_mechanic_direction_path(rct_peep* peep, uint8 validDirections, rct_map_element* pathElement) { + uint8 direction = 0xFF; uint8 pathDirections = pathElement->properties.path.edges & 0xF; if (peep->state != PEEP_STATE_ANSWERING && peep->state != PEEP_STATE_HEADING_TO_INSPECTION) { + /* Mechanic is patrolling, so mask with the valid + * patrol directions */ pathDirections &= validDirections; } @@ -1009,6 +1012,7 @@ static uint8 staff_mechanic_direction_path(rct_peep* peep, uint8 validDirections return staff_mechanic_direction_surface(peep); } + // Check if this is dead end - i.e. only way out is the reverse direction. pathDirections &= ~(1 << (peep->direction ^ (1 << 1))); if (pathDirections == 0) { pathDirections |= (1 << (peep->direction ^ (1 << 1))); @@ -1029,11 +1033,14 @@ static uint8 staff_mechanic_direction_path(rct_peep* peep, uint8 validDirections pathDirections |= (1 << direction); + // Mechanic is heading to ride (either broken down or for inspection). if (peep->state == PEEP_STATE_ANSWERING || peep->state == PEEP_STATE_HEADING_TO_INSPECTION) { rct_ride* ride = get_ride(peep->current_ride); uint8 z = ride->station_heights[peep->current_ride_station]; gPeepPathFindGoalPosition.z = z; + /* Find location of the exit for the target ride station + * or if the ride has no exit, the entrance */ uint16 location = ride->exits[peep->current_ride_station]; if (location == 0xFFFF) { location = ride->entrances[peep->current_ride_station]; @@ -1047,6 +1054,7 @@ static uint8 staff_mechanic_direction_path(rct_peep* peep, uint8 validDirections gPeepPathFindGoalPosition.x = chosenTile.x; gPeepPathFindGoalPosition.y = chosenTile.y; + // Find the exit/entrance map_element bool entranceFound = false; rct_map_element* mapElement = map_get_first_element_at(chosenTile.x / 32, chosenTile.y / 32); do { @@ -1068,12 +1076,15 @@ static uint8 staff_mechanic_direction_path(rct_peep* peep, uint8 validDirections return staff_mechanic_direction_path_rand(peep, pathDirections); } + /* Adjust the peep goal according to the direction of the + * exit/entrance. */ uint8 entranceDirection = map_element_get_direction(mapElement); chosenTile.x -= TileDirectionDelta[entranceDirection].x; chosenTile.y -= TileDirectionDelta[entranceDirection].y; gPeepPathFindGoalPosition.x = chosenTile.x; gPeepPathFindGoalPosition.y = chosenTile.y; + // Peep is about to walk into the target exit/entrance. if (chosenTile.x == peep->next_x && chosenTile.y == peep->next_y && z == peep->next_z) { From 1810e56cc6c1d5c54073a59562f2696f7429d58f Mon Sep 17 00:00:00 2001 From: zaxcav Date: Thu, 22 Sep 2016 13:21:22 +0200 Subject: [PATCH 03/13] Fix #if defined for when compiling on DEBUG on non-Windows platforms. --- src/core/Diagnostics.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/Diagnostics.cpp b/src/core/Diagnostics.cpp index dedbee4745..3f8cbcf87e 100644 --- a/src/core/Diagnostics.cpp +++ b/src/core/Diagnostics.cpp @@ -28,8 +28,8 @@ namespace Debug { void Break() { -#if DEBUG -#if __WINDOWS__ +#if defined(DEBUG) +#if defined(__WINDOWS__) if (IsDebuggerPresent()) { DebugBreak(); From bb5710b4425162a38c30602983d3ac14b43f4a7b Mon Sep 17 00:00:00 2001 From: zaxcav Date: Thu, 22 Sep 2016 15:35:04 +0200 Subject: [PATCH 04/13] Add missing #defines when DEBUG=0. --- src/diagnostic.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/diagnostic.h b/src/diagnostic.h index 5b29f6b719..f862a9a278 100644 --- a/src/diagnostic.h +++ b/src/diagnostic.h @@ -62,6 +62,10 @@ enum { #define DEBUG_LEVEL_3 0 #define DEBUG_LEVEL_2 0 #endif // DEBUG > 1 + #else + #define DEBUG_LEVEL_1 0 + #define DEBUG_LEVEL_2 0 + #define DEBUG_LEVEL_3 0 #endif // DEBUG > 0 #else #define DEBUG_LEVEL_3 0 From 9625a42d82abf8fcc681bccd3657aa8d0a9ec92c Mon Sep 17 00:00:00 2001 From: zaxcav Date: Wed, 28 Sep 2016 15:21:49 +0200 Subject: [PATCH 05/13] Collect search path telemetry in the pathfinding: - Telemetry information collected includes: end location, steps taken, number of junctions walked through, the list of junctions walked through and the directions taken at each of those junctions. - Telemetry collection is not optimised and currently has a negative performance impact. Improve pathfinding debugging framework: - Include collected telemetry information in the debugging messages. - Enable debugging only for specific peeps - for guests, debugging is logged for tracked guest(s) only; for staff, debugging is logged for "Mechanic Debug" only. - Debug messages need a DEBUG build with appropriate log levels enabled: used log levels are VERBOSE and INFO. --- src/peep/peep.c | 451 ++++++++++++++++++++++++++++++++++++++--------- src/peep/peep.h | 6 + src/peep/staff.c | 15 ++ 3 files changed, 386 insertions(+), 86 deletions(-) diff --git a/src/peep/peep.c b/src/peep/peep.c index fea8a0a6ab..a5a6a06a87 100644 --- a/src/peep/peep.c +++ b/src/peep/peep.c @@ -39,6 +39,12 @@ #include "peep.h" #include "staff.h" +#if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 +bool gPathFindDebug = false; +utf8 gPathFindDebugPeepName[256]; +#endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + + uint8 gGuestChangeModifier; uint16 gNumGuestsInPark; uint16 gNumGuestsInParkLastWeek; @@ -60,6 +66,7 @@ bool gPeepPathFindSingleChoiceSection; // uint32 gPeepPathFindAltStationNum; static bool _peepPathFindIsStaff; static sint8 _peepPathFindNumJunctions; +static sint8 _peepPathFindMaxJunctions; static sint32 _peepPathFindTilesChecked; static uint8 _peepPathFindFewestNumSteps; @@ -67,7 +74,10 @@ static uint8 _peepPathFindFewestNumSteps; * The magic number 16 is the largest value returned by * peep_pathfind_get_max_number_junctions() which should eventually * be declared properly. */ -static rct_xyz8 _peepPathFindHistory[16]; +static struct { + rct_xyz8 location; + uint8 direction; +} _peepPathFindHistory[16]; static uint8 _unk_F1AEF0; static uint8 _unk_F1AEF1; @@ -92,6 +102,9 @@ enum { PATH_SEARCH_FAILED }; +// Some text descriptions corresponding to the above enum for understandable debug messages +const char *gPathFindSearchText[] = {"DeadEnd", "Wide", "Thin", "Junction", "RideQueue", "RideEntrance", "RideExit", "ParkEntryExit", "ShopEntrance", "LimitReached", "PathLoop", "Other", "Failed"}; + enum { F1EE18_DESTINATION_REACHED = 1 << 0, F1EE18_OUTSIDE_PARK = 1 << 1, @@ -8702,15 +8715,16 @@ static bool path_is_thin_junction(rct_map_element *path, sint16 x, sint16 y, uin * wide path. This means peeps heading for a destination will only leave * thin paths if walking 1 tile onto a wide path is closer than following * non-wide paths; - * - gPathFindIgnoreForeignQueues + * - gPeepPathFindIgnoreForeignQueues * - gPeepPathFindQueueRideIndex - the ride the peep is heading for - * - _peepPathFindHistory - the starting point and all junctions navigated - * in the current search path - used to detect path loops. + * - _peepPathFindHistory - the search path telemetry consisting of the + * starting point and all thin junctions with directions navigated + * in the current search path - also used to detect path loops. * * The score is only updated when: * - the goal is reached; * - a wide tile is encountered with a better search result - the goal may - * still be reachable from here; + * still be reachable from here; TODO: ignore unless current tile is also wide. * - a junction is encountered with a better search result and * maxNumJunctions is exceeded - the goal may still be reachable from here; * - returning from a recursive call if a search limit (i.e. either @@ -8718,9 +8732,23 @@ static bool path_is_thin_junction(rct_map_element *path, sint16 x, sint16 y, uin * better search result and the goal may still be reachable from here * (i.e. not a dead end path tile). * + * The following variables provide telemetry information on the search path: + * - Variable endXYZ tracks the end location of the search path. + * - Variable endSteps tracks the number of steps to the end of the search path. + * - Variable endJunctions tracks the number of junctions passed through in the + * search path. + * - Variables junctionList and directionList track the junctions and + * corresponding directions of the search path. + * Note: Algorithm optimisations use these variables for the current search path + * and the "best so far" search path at different times. + * The final return values of these variables from the initial call correspond + * to the best search result found. + * Other than debugging purposes, these could potentially be used to visualise + * the pathfinding on the map. + * * rct2: 0x0069A997 */ -static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 counter, uint16 *score, int test_edge) { +static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 counter, uint16 *endScore, int test_edge, uint8 *endJunctions, rct_xyz8 junctionList[16], uint8 directionList[16], rct_xyz8 *endXYZ, uint8 *endSteps) { uint searchResult = PATH_SEARCH_FAILED; x += TileDirectionDelta[test_edge].x; @@ -8733,7 +8761,9 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 c * search ends here. Return the searchResult with the original score. */ if (counter > 200 || _peepPathFindTilesChecked < 0) { #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + if (gPathFindDebug) { log_info("[%03d] Return from %d,%d,%d; Search limit exceeded", counter, x >> 5, y >> 5, z); + } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 searchResult = PATH_SEARCH_LIMIT_REACHED; return searchResult; @@ -8742,11 +8772,13 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 c /* If this is where the search started this is a search loop and the * current search path ends here. * Return the searchResult with the original score. */ - if ((_peepPathFindHistory[0].x == (uint8)(x >> 5)) && - (_peepPathFindHistory[0].y == (uint8)(y >> 5)) && - (_peepPathFindHistory[0].z == (uint8)z)) { + if ((_peepPathFindHistory[0].location.x == (uint8)(x >> 5)) && + (_peepPathFindHistory[0].location.y == (uint8)(y >> 5)) && + (_peepPathFindHistory[0].location.z == (uint8)z)) { #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + if (gPathFindDebug) { log_info("[%03d] Return from %d,%d,%d; At start", counter, x >> 5, y >> 5, z); + } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 searchResult = PATH_SEARCH_LOOP; return searchResult; @@ -8754,7 +8786,7 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 c /* Get the next map element in the direction of test_edge, ignoring * map elements that are not of interest, which includes wide paths - * and foreign ride queues (depending on gPathFindIgnoreForeignQueues */ + * and foreign ride queues (depending on gPeepPathFindIgnoreForeignQueues) */ bool found = false; uint8 rideIndex = 0xFF; rct_map_element *mapElement = map_get_first_element_at(x / 32, y / 32); @@ -8847,9 +8879,13 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 c } } while (!map_element_is_last_for_tile(mapElement++)); + /* No map element could be found. + * Return the searchResult with the original score. */ if (!found) { #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + if (gPathFindDebug) { log_info("[%03d] Return from %d,%d,%d; No map element found", counter, x >> 5, y >> 5, z); + } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 return searchResult; // PATH_SEARCH_FAILED } @@ -8879,12 +8915,25 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 c /* If this map element is the search goal the current search path ends here. * Return the searchResult with the new score. */ if (new_score == 0) { - if (new_score < *score || counter < _peepPathFindFewestNumSteps) { - _peepPathFindFewestNumSteps = counter; + *endScore = new_score; + *endSteps = counter; + // Update the end x,y,z + endXYZ->x = x >> 5; + endXYZ->y = y >> 5; + endXYZ->z = z; + // TODO: Doing the following when the call returns to the previous thin junction (below) should be equivalent to doing it here. i.e. reduce code duplication. + *endJunctions = _peepPathFindMaxJunctions - _peepPathFindNumJunctions; + for (uint8 junctInd = 0; junctInd < *endJunctions; junctInd++) { + uint8 histIdx = _peepPathFindMaxJunctions - junctInd; + junctionList[junctInd].x = _peepPathFindHistory[histIdx].location.x; + junctionList[junctInd].y = _peepPathFindHistory[histIdx].location.y; + junctionList[junctInd].z = _peepPathFindHistory[histIdx].location.z; + directionList[junctInd] = _peepPathFindHistory[histIdx].direction; } - *score = new_score; #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - log_info("[%03d] Return from %d,%d,%d; At goal; Score: %d", counter, x >> 5, y >> 5, z, *score); + if (gPathFindDebug) { + log_info("[%03d] Return from %d,%d,%d; At goal; Score: %d", counter, x >> 5, y >> 5, z, *endScore); + } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 return searchResult; } @@ -8898,7 +8947,9 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 c searchResult != PATH_SEARCH_JUNCTION && searchResult != PATH_SEARCH_WIDE) { #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + if (gPathFindDebug) { log_info("[%03d] Return from %d,%d,%d; Not a path", counter, x >> 5, y >> 5, z); + } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 return searchResult; } @@ -8907,17 +8958,32 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 c /* If this is a wide path the search ends here but the goal * could still be reachable from here. - * If this is the best result so far, return the searchResult with - * the new score. */ + * Return the searchResult with the new score. */ if (searchResult == PATH_SEARCH_WIDE) { - if (new_score < *score || - (new_score == *score && - counter < _peepPathFindFewestNumSteps)) { - *score = new_score; - _peepPathFindFewestNumSteps = counter; + /* Ignore Wide paths as continuing paths UNLESS the current path is also Wide. + * i.e. search across wide paths from a wide path to get onto a thin path, + * thereafter stay on thin paths. */ + if (false) { // TODO: Check that the current tile is also Wide. + *endScore = new_score; + *endSteps = counter; + // Update the end x,y,z + endXYZ->x = x >> 5; + endXYZ->y = y >> 5; + endXYZ->z = z; + // TODO: Doing the following when the call returns to the previous thin junction (below) should be equivalent to doing it here. i.e. reduce code duplication. + *endJunctions = _peepPathFindMaxJunctions - _peepPathFindNumJunctions; + for (uint8 junctInd = 0; junctInd < *endJunctions; junctInd++) { + uint8 histIdx = _peepPathFindMaxJunctions - junctInd; + junctionList[junctInd].x = _peepPathFindHistory[histIdx].location.x; + junctionList[junctInd].y = _peepPathFindHistory[histIdx].location.y; + junctionList[junctInd].z = _peepPathFindHistory[histIdx].location.z; + directionList[junctInd] = _peepPathFindHistory[histIdx].direction; + } } #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - log_info("[%03d] Return from %d,%d,%d; Wide path; Score: %d", counter, x >> 5, y >> 5, z, *score); + if (gPathFindDebug) { + log_info("[%03d] Return from %d,%d,%d; Wide path; Score: %d", counter, x >> 5, y >> 5, z, *endScore); + } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 return searchResult; } @@ -8928,7 +8994,9 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 c uint8 edges = path_get_permitted_edges(mapElement); #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + if (gPathFindDebug) { log_info("[%03d] Path %d,%d,%d; Edges (0123):%d%d%d%d; Reverse: %d", counter, x >> 5, y >> 5, z, edges & 1, (edges & 2) >> 1, (edges & 4) >> 2, (edges & 8) >> 3, test_edge ^ 2); + } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 /* Remove the reverse edge (i.e. the edge back to the previous map element.) */ @@ -8939,7 +9007,9 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 c * Return the searchResult with the original score. */ if (test_edge == -1) { #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + if (gPathFindDebug) { log_info("[%03d] Return from %d,%d,%d; No more edges/dead end", counter, x >> 5, y >> 5, z); + } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 searchResult = PATH_SEARCH_DEAD_END; // this should be superfluous. return searchResult; @@ -8951,26 +9021,35 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 c * necessary checks. */ thin_junction = path_is_thin_junction(mapElement, x, y, z); - if (thin_junction == true) { + if (thin_junction) { /* The current search path is passing through a thin * junction on this map element. Only 'thin' junctions * are counted towards the junction search limit. */ - --_peepPathFindNumJunctions; /* If the junction search limit is reached, the * current search path ends here. The goal may still * be reachable from here. - * If this is the best result so far, return the - * searchResult with the new score. */ - if (_peepPathFindNumJunctions < 0) { - if (new_score < *score || - (new_score == *score && - counter < _peepPathFindFewestNumSteps)) { - *score = new_score; - _peepPathFindFewestNumSteps = counter; + * Return the searchResult with the new score. */ + if (_peepPathFindNumJunctions <= 0) { + *endScore = new_score; + *endSteps = counter; + // Update the end x,y,z + endXYZ->x = x >> 5; + endXYZ->y = y >> 5; + endXYZ->z = z; + // TODO: Doing the following when the call returns to the previous thin junction (below) should be equivalent to doing it here. i.e. reduce code duplication. + *endJunctions = _peepPathFindMaxJunctions - _peepPathFindNumJunctions; + for (uint8 junctInd = 0; junctInd < *endJunctions; junctInd++) { + uint8 histIdx = _peepPathFindMaxJunctions - junctInd; + junctionList[junctInd].x = _peepPathFindHistory[histIdx].location.x; + junctionList[junctInd].y = _peepPathFindHistory[histIdx].location.y; + junctionList[junctInd].z = _peepPathFindHistory[histIdx].location.z; + directionList[junctInd] = _peepPathFindHistory[histIdx].direction; } #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - log_info("[%03d] Return from %d,%d,%d; NumJunctions < 0; Score: %d", counter, x >> 5, y >> 5, z, *score); + if (gPathFindDebug) { + log_info("[%03d] Return from %d,%d,%d; NumJunctions < 0; Score: %d", counter, x >> 5, y >> 5, z, *endScore); + } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 return searchResult; } @@ -8980,12 +9059,14 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 c * i.e. this is a loop in the current search path. * If so, the current search path ends here. * Return the searchResult with the original score. */ - for (int junctionNum = _peepPathFindNumJunctions + 1; junctionNum < 16; junctionNum++) { - if ((_peepPathFindHistory[junctionNum].x == (uint8)(x >> 5)) && - (_peepPathFindHistory[junctionNum].y == (uint8)(y >> 5)) && - (_peepPathFindHistory[junctionNum].z == (uint8)z)) { + for (int junctionNum = _peepPathFindNumJunctions + 1; junctionNum <= _peepPathFindMaxJunctions; junctionNum++) { + if ((_peepPathFindHistory[junctionNum].location.x == (uint8)(x >> 5)) && + (_peepPathFindHistory[junctionNum].location.y == (uint8)(y >> 5)) && + (_peepPathFindHistory[junctionNum].location.z == (uint8)z)) { #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + if (gPathFindDebug) { log_info("[%03d] Return from %d,%d,%d; Loop", counter, x >> 5, y >> 5, z); + } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 searchResult = PATH_SEARCH_LOOP; return searchResult; @@ -8993,44 +9074,76 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 c } /* This junction was NOT previously visited in the current - * search path, so add the junction to the history. - * Note: the last junction that can be visited is not needed - * for future comparisons (since the search path will end when - * a further junction is encountered), so the starting point - * can be stored in _peepPathFindHistory[0]. */ - if (_peepPathFindNumJunctions > 0) { - _peepPathFindHistory[_peepPathFindNumJunctions].x = (uint8)(x >> 5); - _peepPathFindHistory[_peepPathFindNumJunctions].y = (uint8)(y >> 5); - _peepPathFindHistory[_peepPathFindNumJunctions].z = (uint8)z; - } + * search path, so add the junction to the history. */ + _peepPathFindHistory[_peepPathFindNumJunctions].location.x = (uint8)(x >> 5); + _peepPathFindHistory[_peepPathFindNumJunctions].location.y = (uint8)(y >> 5); + _peepPathFindHistory[_peepPathFindNumJunctions].location.z = (uint8)z; + // .direction take is added below. + + _peepPathFindNumJunctions--; } } /* Continue searching down each remaining edge of the path * (recursive call). */ - uint8 continueResult; + + /* Following variables store the best result so far when + * recursing over the multiple edges of a junction, including + * the path finding telemetry for that result (i.e. the search + * path result, end location, thin junctions and directions in + * the search path). + * No initialisation is performed. The first successful + * search result will be the "best so far" by default. */ + uint8 bestSoFarResult; // searchResult + uint16 bestSoFarScore; // score + uint8 bestSoFarSteps; // steps taken + rct_xyz8 bestSoFarXYZ; // end XYZ + uint8 bestSoFarJunctions; // num of thin junctions traversed + rct_xyz8 savedJunctionList[16]; // list of thin junctions traversed + uint8 savedDirectionList[16]; // list of directions taken at each thin junction + + /* Following boolean indicates whether a successful search + * result has been found yet. i.e. when false there is no + * "best so far" result. */ + bool endFound = false; + + /* Now loop through each of the junction edges. */ do { edges &= ~(1 << test_edge); sint8 savedNumJunctions = _peepPathFindNumJunctions; + uint8 height = z; if (footpath_element_is_sloped(mapElement) && footpath_element_get_slope_direction(mapElement) == test_edge) { height += 2; } #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - if (searchResult == PATH_SEARCH_JUNCTION) { - if (thin_junction == true) - log_info("[%03d] Recurse from %d,%d,%d edge: %d; Thin-Junction", counter, x >> 5, y >> 5, z, test_edge); - else - log_info("[%03d] Recurse from %d,%d,%d edge: %d; Wide-Junction", counter, x >> 5, y >> 5, z, test_edge); - } else { - log_info("[%03d] Recurse from %d,%d,%d edge: %d; Segment", counter, x >> 5, y >> 5, z, test_edge); + if (gPathFindDebug) { + if (searchResult == PATH_SEARCH_JUNCTION) { + if (thin_junction) + log_info("[%03d] Recurse from %d,%d,%d edge: %d; Thin-Junction", counter, x >> 5, y >> 5, z, test_edge); + else + log_info("[%03d] Recurse from %d,%d,%d edge: %d; Wide-Junction", counter, x >> 5, y >> 5, z, test_edge); + } else { + log_info("[%03d] Recurse from %d,%d,%d edge: %d; Segment", counter, x >> 5, y >> 5, z, test_edge); + } } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - uint16 savedScore = *score; - uint8 savedSteps = _peepPathFindFewestNumSteps; - continueResult = peep_pathfind_heuristic_search(x, y, height, counter, score, test_edge); + if (thin_junction) { + /* Add the current test_edge to the history. */ + _peepPathFindHistory[_peepPathFindNumJunctions + 1].direction = test_edge; + } + + // Clear the results for the recursive call. + // TODO: This should be unnecessary. Leaving the existing value should not affect the search. Additionally, leaving the telemetry data intact will eliminate the need to restore it from the "best so far" variables at the end. + *endScore = 0xFFFF; + *endJunctions = 0; + memset(junctionList, 0x00, sizeof(junctionList[16])); + memset(directionList, 0x00, sizeof(directionList[16])); + memset(endXYZ, 0x00, sizeof(*endXYZ)); + + uint8 continueResult = peep_pathfind_heuristic_search(x, y, height, counter, endScore, test_edge, endJunctions, junctionList, directionList, endXYZ, endSteps); _peepPathFindNumJunctions = savedNumJunctions; if (continueResult == PATH_SEARCH_LIMIT_REACHED) { @@ -9040,25 +9153,93 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 c * the current path search is the current map element. * Update the best search result if appropriate and * keep searchResult (discarding continueResult). */ - if (new_score < *score || (new_score == *score && counter < _peepPathFindFewestNumSteps)) { - *score = new_score; - _peepPathFindFewestNumSteps = counter; - } - } else { - /* When continueResult is not PATH_SEARCH_LIMIT_REACHED - * it is the search result of the last map element - * checked in the search path. If it has a different - * (i.e. better) search result, update searchResult. */ - if (*score < savedScore || _peepPathFindFewestNumSteps < savedSteps ) { - searchResult = continueResult; + *endScore = new_score; + *endSteps = counter; + // Update the end x,y,z + endXYZ->x = x >> 5; + endXYZ->y = y >> 5; + endXYZ->z = z; + // TODO: Doing the following below should be equivalent to doing it here. i.e. reduce code duplication. + *endJunctions = _peepPathFindMaxJunctions - savedNumJunctions; + for (uint8 junctInd = 0; junctInd < *endJunctions; junctInd++) { + uint8 histIdx = _peepPathFindMaxJunctions - junctInd; + junctionList[junctInd].x = _peepPathFindHistory[histIdx].location.x; + junctionList[junctInd].y = _peepPathFindHistory[histIdx].location.y; + junctionList[junctInd].z = _peepPathFindHistory[histIdx].location.z; + directionList[junctInd] = _peepPathFindHistory[histIdx].direction; } + continueResult = searchResult; } + #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - log_info("[%03d] Return to %d,%d,%d edge: %d; Score: %d", counter, x >> 5, y >> 5, z, test_edge, *score); + if (gPathFindDebug) { + log_info("[%03d] Return to %d,%d,%d edge: %d; Score: %d", counter, x >> 5, y >> 5, z, test_edge, *endScore); + } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + // TODO: Combine the following two conditionals into a single, compound conditional to reduce code duplication. + // The first result is stored in bestSoFar. + if (!endFound && *endScore < 0xFFFF) { + endFound = true; + + bestSoFarScore = *endScore; + bestSoFarSteps = *endSteps; + bestSoFarXYZ.x = endXYZ->x; + bestSoFarXYZ.y = endXYZ->y; + bestSoFarXYZ.z = endXYZ->z; + // TODO: Store the "bestSoFar" telemetry directly into the return parameters (i.e. eliminate the need to store the best so far telemetry and eliminate the need to restore the best so far result at the end) from the global variables (i.e. reduce code duplication). + bestSoFarJunctions = *endJunctions; + for (uint8 junctInd = 0; junctInd < *endJunctions; junctInd++) { + savedJunctionList[junctInd].x = junctionList[junctInd].x; + savedJunctionList[junctInd].y = junctionList[junctInd].y; + savedJunctionList[junctInd].z = junctionList[junctInd].z; + savedDirectionList[junctInd] = directionList[junctInd]; + } + bestSoFarResult = continueResult; + + continue; + } + + if (endFound && *endScore < 0xFFFF && + searchResult == PATH_SEARCH_JUNCTION) { + /* Junction. Check if this edge is the best so far. */ + if (*endScore < bestSoFarScore || (*endScore == bestSoFarScore && *endSteps < bestSoFarSteps )) { + bestSoFarScore = *endScore; + bestSoFarSteps = *endSteps; + bestSoFarXYZ.x = endXYZ->x; + bestSoFarXYZ.y = endXYZ->y; + bestSoFarXYZ.z = endXYZ->z; + // TODO: Store the "bestSoFar" telemetry directly into the return parameters (i.e. eliminate the need to store the best so far telemetry and eliminate the need to restore the best so far result at the end) from the global variables (i.e. reduce code duplication). + bestSoFarJunctions = *endJunctions; + for (uint8 junctInd = 0; junctInd < *endJunctions; junctInd++) { + savedJunctionList[junctInd].x = junctionList[junctInd].x; + savedJunctionList[junctInd].y = junctionList[junctInd].y; + savedJunctionList[junctInd].z = junctionList[junctInd].z; + savedDirectionList[junctInd] = directionList[junctInd]; + } + bestSoFarResult = continueResult; + } + } } while ((test_edge = bitscanforward(edges)) != -1); - return searchResult; + + if (endFound) { + /* Restore the bestSoFar result. */ + *endScore = bestSoFarScore; + *endSteps = bestSoFarSteps; + endXYZ->x = bestSoFarXYZ.x; + endXYZ->y = bestSoFarXYZ.y; + endXYZ->z = bestSoFarXYZ.z; + // TODO: If the "bestSoFar" telemetry is stored directly into the parameters there will be no need to restore it here. + *endJunctions = bestSoFarJunctions; + for (uint8 junctInd = 0; junctInd < *endJunctions; junctInd++) { + junctionList[junctInd].x = savedJunctionList[junctInd].x; + junctionList[junctInd].y = savedJunctionList[junctInd].y; + junctionList[junctInd].z = savedJunctionList[junctInd].z; + directionList[junctInd] = savedDirectionList[junctInd]; + } + return bestSoFarResult; + } + return PATH_SEARCH_FAILED; } /** @@ -9071,7 +9252,7 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 c int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) { // The max number of thin junctions searched - a per-search-path limit. - sint8 maxNumJunctions = peep_pathfind_get_max_number_junctions(peep); + _peepPathFindMaxJunctions = peep_pathfind_get_max_number_junctions(peep); /* The max number of tiles to check - a whole-search limit. * Mainly to limit the performance impact of the path finding. */ @@ -9085,6 +9266,12 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) .z = (uint8)(gPeepPathFindGoalPosition.z) }; + #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + if (gPathFindDebug) { + log_verbose("Choose direction for %s for goal %d,%d,%d from %d,%d,%d", gPathFindDebugPeepName, goal.x, goal.y, goal.z, x >> 5, y >> 5, z); + } + #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + // Get the path element at this location rct_map_element *dest_map_element = map_get_first_element_at(x / 32, y / 32); bool found = false; @@ -9126,6 +9313,11 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) peep->pathfind_history[i].y == y / 32 && peep->pathfind_history[i].z == z) { edges = peep->pathfind_history[i].direction & 0xF; + #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + if (gPathFindDebug) { + log_verbose("Getting untried edges from pf_history for %d,%d,%d: %d", x >> 5, y >> 5, z, edges); + } + #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 break; } } @@ -9144,8 +9336,20 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) uint16 best_score = 0xFFFF; uint8 best_sub = 0xFF; + uint8 bestJunctions = 0; + rct_xyz8 bestJunctionList[16]; + memset(bestJunctionList, 0x0, sizeof(bestJunctionList)); + uint8 bestDirectionList[16]; + memset(bestDirectionList, 0x0, sizeof(bestDirectionList)); + rct_xyz8 bestXYZ; + bestXYZ.x = 0; + bestXYZ.y = 0; + bestXYZ.z = 0; + #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 - log_verbose(stderr, "Pathfind start for goal %d,%d,%d from %d,%d,%d\n", goal.x, goal.y, goal.z, x >> 5, y >> 5, z); + if (gPathFindDebug) { + log_verbose("Pathfind start for goal %d,%d,%d from %d,%d,%d", goal.x, goal.y, goal.z, x >> 5, y >> 5, z); + } #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 /* Call the search heuristic on each edge, keeping track of the @@ -9169,32 +9373,70 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) * between the remaining edges to ensure the search * covers all of the remaining edges. */ _peepPathFindTilesChecked = maxTilesChecked / numEdges; - _peepPathFindNumJunctions = maxNumJunctions; + _peepPathFindNumJunctions = _peepPathFindMaxJunctions; // Initialise _peepPathFindHistory. memset(_peepPathFindHistory, 0xFF, sizeof(_peepPathFindHistory)); /* The pathfinding will only use elements - * 1..maxNumJunctions, so the starting point is - * placed in element 0 */ - _peepPathFindHistory[0].x = (uint8)(x >> 5); - _peepPathFindHistory[0].y = (uint8)(y >> 5); - _peepPathFindHistory[0].z = (uint8)z; + * 1.._peepPathFindMaxJunctions, so the starting point + * is placed in element 0 */ + _peepPathFindHistory[0].location.x = (uint8)(x >> 5); + _peepPathFindHistory[0].location.y = (uint8)(y >> 5); + _peepPathFindHistory[0].location.z = (uint8)z; + _peepPathFindHistory[0].direction = 0xF; uint16 score = 0xFFFF; - uint8 searchResult = peep_pathfind_heuristic_search(x, y, height, 0, &score, test_edge); + /* Variable endXYZ contains the end location of the + * search path. */ + rct_xyz8 endXYZ; + endXYZ.x = 0; + endXYZ.y = 0; + endXYZ.z = 0; + + uint8 endSteps = 255; + + /* Variable bestJunctions is the number of junctions + * pass through in the search path. + * Variables bestJunctionList and bestDirectionList + * contain the junctions and corresponding directions + * of the search path. + * In the future these could be used to visualise the + * pathfinding on the map. */ + uint8 endJunctions = 0; + rct_xyz8 endJunctionList[16]; + memset(endJunctionList, 0x0, sizeof(endJunctionList)); + uint8 endDirectionList[16]; + memset(endDirectionList, 0x0, sizeof(endDirectionList)); + + uint8 searchResult = peep_pathfind_heuristic_search(x, y, height, 0, &score, test_edge, &endJunctions, endJunctionList, endDirectionList, &endXYZ, &endSteps); #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 - log_verbose(stderr,"Pathfind test edge: %d score: %d steps: %d searchResult: %d\n", test_edge, score, _peepPathFindFewestNumSteps, searchResult); + if (gPathFindDebug) { + log_verbose("Pathfind test edge: %d score: %d steps: %d searchResult: %s end: %d,%d,%d junctions: %d", test_edge, score, endSteps, gPathFindSearchText[searchResult], endXYZ.x, endXYZ.y, endXYZ.z, endJunctions); + for (uint8 listIdx = 0; listIdx < endJunctions; listIdx++) { + log_info("Junction#%d %d,%d,%d Direction %d", listIdx + 1, endJunctionList[listIdx].x, endJunctionList[listIdx].y, endJunctionList[listIdx].z, endDirectionList[listIdx]); + } + } #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 if (score == 0 || searchResult == PATH_SEARCH_THIN || searchResult == PATH_SEARCH_JUNCTION || - searchResult == PATH_SEARCH_WIDE) { - if (score < best_score || (score == best_score && _peepPathFindFewestNumSteps < best_sub)) { + (searchResult == PATH_SEARCH_WIDE && false)) { // TODO: Here check that the current tile is also Wide + if (score < best_score || (score == best_score && endSteps < best_sub)) { chosen_edge = test_edge; best_score = score; - best_sub = _peepPathFindFewestNumSteps; + best_sub = endSteps; + bestJunctions = endJunctions; + for (uint8 index = 0; index < endJunctions; index++) { + bestJunctionList[index].x = endJunctionList[index].x; + bestJunctionList[index].y = endJunctionList[index].y; + bestJunctionList[index].z = endJunctionList[index].z; + bestDirectionList[index] = endDirectionList[index]; + } + bestXYZ.x = endXYZ.x; + bestXYZ.y = endXYZ.y; + bestXYZ.z = endXYZ.z; } } } @@ -9204,12 +9446,20 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) * goal. */ if (best_score == 0xFFFF) { #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 - log_verbose(stderr, "Pathfind heuristic search failed.\n"); + if (gPathFindDebug) { + log_verbose("Pathfind heuristic search failed."); + } #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 return -1; } #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 - log_verbose(stderr, "Pathfind best edge %d with score %d\n", chosen_edge, best_score); + if (gPathFindDebug) { + log_verbose("Pathfind best edge %d with score %d", chosen_edge, best_score); + for (uint8 listIdx = 0; listIdx < bestJunctions; listIdx++) { + log_verbose("Junction#%d %d,%d,%d Direction %d", listIdx + 1, bestJunctionList[listIdx].x, bestJunctionList[listIdx].y, bestJunctionList[listIdx].z, bestDirectionList[listIdx]); + } + log_verbose("End at %d,%d,%d", bestXYZ.x, bestXYZ.y, bestXYZ.z); + } #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 } @@ -9227,6 +9477,11 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) // Clear pathfinding history memset(peep->pathfind_history, 0xFF, sizeof(peep->pathfind_history)); + #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + if (gPathFindDebug) { + log_verbose("New goal; clearing pf_history."); + } + #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 } if (isThin) { @@ -9238,6 +9493,11 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) /* Peep remembers this junction, so remove the * chosen_edge from those left to try. */ peep->pathfind_history[i].direction &= ~(1 << chosen_edge); + #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + if (gPathFindDebug) { + log_verbose("Removing edge %d from existing pf_history for %d,%d,%d.", chosen_edge, x >> 5, y >> 5, z); + } + #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 return chosen_edge; } } @@ -9251,6 +9511,11 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) peep->pathfind_history[i].z = z; peep->pathfind_history[i].direction = 0xF; peep->pathfind_history[i].direction &= ~(1 << chosen_edge); + #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + if (gPathFindDebug) { + log_verbose("Storing new pf_history for %d,%d,%d without edge %d.", x >> 5, y >> 5, z, chosen_edge); + } + #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 } return chosen_edge; @@ -9712,7 +9977,21 @@ static int guest_path_finding(rct_peep* peep) gPeepPathFindGoalPosition = (rct_xyz16) { x, y, z }; gPeepPathFindIgnoreForeignQueues = true; + #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + /* Determine if the pathfinding debugging is wanted for this peep. */ + /* For guests, use the existing PEEP_FLAGS_TRACKING flag to + * determine for which guest(s) the pathfinding debugging will + * be output for. */ + format_string(gPathFindDebugPeepName, peep->name_string_idx, &(peep->id)); + gPathFindDebug = peep->peep_flags & PEEP_FLAGS_TRACKING; + #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + direction = peep_pathfind_choose_direction(peep->next_x, peep->next_y, peep->next_z, peep); + + #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + gPathFindDebug = false; + #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + if (direction == -1){ return guest_path_find_aimless(peep, edges); } diff --git a/src/peep/peep.h b/src/peep/peep.h index 911e1f548e..b106fa5c66 100644 --- a/src/peep/peep.h +++ b/src/peep/peep.h @@ -20,6 +20,12 @@ #include "../common.h" #include "../world/map.h" +#if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 +// Some variables used for the path finding debugging. +extern bool gPathFindDebug; +extern utf8 gPathFindDebugPeepName[256]; +#endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + #define PEEP_MAX_THOUGHTS 5 #define PEEP_HUNGER_WARNING_THRESHOLD 25 diff --git a/src/peep/staff.c b/src/peep/staff.c index 91449026c6..27d8423e50 100644 --- a/src/peep/staff.c +++ b/src/peep/staff.c @@ -20,6 +20,7 @@ #include "../interface/viewport.h" #include "../localisation/date.h" #include "../localisation/string_ids.h" +#include "../localisation/localisation.h" #include "../management/finance.h" #include "../util/util.h" #include "../world/sprite.h" @@ -1094,8 +1095,22 @@ static uint8 staff_mechanic_direction_path(rct_peep* peep, uint8 validDirections gPeepPathFindIgnoreForeignQueues = false; gPeepPathFindQueueRideIndex = 255; + #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + /* Determine if the pathfinding debugging is wanted for this peep. */ + /* For staff, there is no tracking button (any other similar + * suitable existing mechanism?), so fall back to a crude + * string comparison with a compile time hardcoded name. */ + format_string(gPathFindDebugPeepName, peep->name_string_idx, &(peep->id)); + + gPathFindDebug = strcmp(gPathFindDebugPeepName, "Mechanic Debug") == 0; + #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + int pathfindDirection = peep_pathfind_choose_direction(peep->next_x, peep->next_y, peep->next_z, peep); + #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + gPathFindDebug = false; + #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + if (pathfindDirection == -1) { return staff_mechanic_direction_path_rand(peep, pathDirections); } From c1b371b510f668e2d6e5e25f7ffc619a095d8c2e Mon Sep 17 00:00:00 2001 From: zaxcav Date: Wed, 28 Sep 2016 21:45:49 +0200 Subject: [PATCH 06/13] Pathfinding now only treats wide path tiles as continuing tiles (and therefore a valid search result) when the current path tile is also wide. Wide tiles are consequently only traversed to reach thin path tiles when in an area of wide path tiles. This fixes peeps getting stuck when the path search limits are all further away from the destination than wide tiles. --- src/peep/peep.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/peep/peep.c b/src/peep/peep.c index a5a6a06a87..c38fcb9946 100644 --- a/src/peep/peep.c +++ b/src/peep/peep.c @@ -8748,7 +8748,7 @@ static bool path_is_thin_junction(rct_map_element *path, sint16 x, sint16 y, uin * * rct2: 0x0069A997 */ -static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 counter, uint16 *endScore, int test_edge, uint8 *endJunctions, rct_xyz8 junctionList[16], uint8 directionList[16], rct_xyz8 *endXYZ, uint8 *endSteps) { +static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, rct_map_element *currentMapElement, uint8 counter, uint16 *endScore, int test_edge, uint8 *endJunctions, rct_xyz8 junctionList[16], uint8 directionList[16], rct_xyz8 *endXYZ, uint8 *endSteps) { uint searchResult = PATH_SEARCH_FAILED; x += TileDirectionDelta[test_edge].x; @@ -8956,14 +8956,16 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 c /* At this point the map element is a path. */ - /* If this is a wide path the search ends here but the goal - * could still be reachable from here. - * Return the searchResult with the new score. */ + /* If this is a wide path the search ends here. */ if (searchResult == PATH_SEARCH_WIDE) { /* Ignore Wide paths as continuing paths UNLESS the current path is also Wide. * i.e. search across wide paths from a wide path to get onto a thin path, * thereafter stay on thin paths. */ - if (false) { // TODO: Check that the current tile is also Wide. + /* So, if the current path is also wide the goal could still + * be reachable from here. + * Return the searchResult with the new score. + * Otherwise, return the original score. */ + if (footpath_element_is_wide(currentMapElement)) { *endScore = new_score; *endSteps = counter; // Update the end x,y,z @@ -9143,7 +9145,7 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 c memset(directionList, 0x00, sizeof(directionList[16])); memset(endXYZ, 0x00, sizeof(*endXYZ)); - uint8 continueResult = peep_pathfind_heuristic_search(x, y, height, counter, endScore, test_edge, endJunctions, junctionList, directionList, endXYZ, endSteps); + uint8 continueResult = peep_pathfind_heuristic_search(x, y, height, mapElement, counter, endScore, test_edge, endJunctions, junctionList, directionList, endXYZ, endSteps); _peepPathFindNumJunctions = savedNumJunctions; if (continueResult == PATH_SEARCH_LIMIT_REACHED) { @@ -9409,7 +9411,7 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) uint8 endDirectionList[16]; memset(endDirectionList, 0x0, sizeof(endDirectionList)); - uint8 searchResult = peep_pathfind_heuristic_search(x, y, height, 0, &score, test_edge, &endJunctions, endJunctionList, endDirectionList, &endXYZ, &endSteps); + uint8 searchResult = peep_pathfind_heuristic_search(x, y, height, dest_map_element, 0, &score, test_edge, &endJunctions, endJunctionList, endDirectionList, &endXYZ, &endSteps); #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 if (gPathFindDebug) { log_verbose("Pathfind test edge: %d score: %d steps: %d searchResult: %s end: %d,%d,%d junctions: %d", test_edge, score, endSteps, gPathFindSearchText[searchResult], endXYZ.x, endXYZ.y, endXYZ.z, endJunctions); @@ -9422,7 +9424,7 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) if (score == 0 || searchResult == PATH_SEARCH_THIN || searchResult == PATH_SEARCH_JUNCTION || - (searchResult == PATH_SEARCH_WIDE && false)) { // TODO: Here check that the current tile is also Wide + searchResult == PATH_SEARCH_WIDE) { if (score < best_score || (score == best_score && endSteps < best_sub)) { chosen_edge = test_edge; best_score = score; From 7ff53057c844d98bb6b3557c6a61454eadf9012d Mon Sep 17 00:00:00 2001 From: zaxcav Date: Mon, 3 Oct 2016 22:17:46 +0200 Subject: [PATCH 07/13] Optimise pathfinding changes. --- src/peep/peep.c | 398 +++++++++++++++++++----------------------------- 1 file changed, 154 insertions(+), 244 deletions(-) diff --git a/src/peep/peep.c b/src/peep/peep.c index c38fcb9946..36fc070da8 100644 --- a/src/peep/peep.c +++ b/src/peep/peep.c @@ -8680,8 +8680,11 @@ static bool path_is_thin_junction(rct_map_element *path, sint16 x, sint16 y, uin } /** - * Returns the best (smallest) score reachable from Tile x,y,z in direction - * test_edge, within the limits of the search. + * Searches for the tile with the best heuristic score within the search limits + * starting from the given tile x,y,z and going in the given direction test_edge. + * The best heuristic score is tracked and returned in the call parameters + * along with the corresponding tile location and search path telemetry + * (junctions passed through and directions taken). * * The primary heuristic used is distance from the goal; the secondary * heuristic used (when the primary heuristic gives equal scores) is the number @@ -8701,12 +8704,25 @@ static bool path_is_thin_junction(rct_map_element *path, sint16 x, sint16 y, uin * rather than storing scores for each xyz, which means explicit loop detection * is necessary to limit the search space. * - * Key parameters that limit the search space are: - * - score - the best heuristic result so far; - * - counter - number of steps walked; - * - _peepPathFindTilesChecked - cumulative number of tiles that can be + * The parameters that hold the best search result so far are: + * - score - the least heuristic distance from the goal + * - endSteps - the least number of steps that achieve the score. + * + * The following parameters provide telemetry information on best search path so far: + * - endXYZ tracks the end location of the search path. + * - endSteps tracks the number of steps to the end of the search path. + * - endJunctions tracks the number of junctions passed through in the + * search path. + * - junctionList[] and directionList[] track the junctions and + * corresponding directions of the search path. + * Other than debugging purposes, these could potentially be used to visualise + * the pathfinding on the map. + * + * The parameters/variables that limit the search space are: + * - counter (param) - number of steps walked in the current search path; + * - _peepPathFindTilesChecked (variable) - cumulative number of tiles that can be * checked in the entire search; - * - _peepPathFindNumJunctions - number of thin junctions that can be + * - _peepPathFindNumJunctions (variable) - number of thin junctions that can be * checked in a single search path; * * Other global variables/state that affect the search space are: @@ -8724,7 +8740,7 @@ static bool path_is_thin_junction(rct_map_element *path, sint16 x, sint16 y, uin * The score is only updated when: * - the goal is reached; * - a wide tile is encountered with a better search result - the goal may - * still be reachable from here; TODO: ignore unless current tile is also wide. + * still be reachable from here (only if the current tile is also wide); * - a junction is encountered with a better search result and * maxNumJunctions is exceeded - the goal may still be reachable from here; * - returning from a recursive call if a search limit (i.e. either @@ -8732,23 +8748,9 @@ static bool path_is_thin_junction(rct_map_element *path, sint16 x, sint16 y, uin * better search result and the goal may still be reachable from here * (i.e. not a dead end path tile). * - * The following variables provide telemetry information on the search path: - * - Variable endXYZ tracks the end location of the search path. - * - Variable endSteps tracks the number of steps to the end of the search path. - * - Variable endJunctions tracks the number of junctions passed through in the - * search path. - * - Variables junctionList and directionList track the junctions and - * corresponding directions of the search path. - * Note: Algorithm optimisations use these variables for the current search path - * and the "best so far" search path at different times. - * The final return values of these variables from the initial call correspond - * to the best search result found. - * Other than debugging purposes, these could potentially be used to visualise - * the pathfinding on the map. - * * rct2: 0x0069A997 */ -static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, rct_map_element *currentMapElement, uint8 counter, uint16 *endScore, int test_edge, uint8 *endJunctions, rct_xyz8 junctionList[16], uint8 directionList[16], rct_xyz8 *endXYZ, uint8 *endSteps) { +static void peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, rct_map_element *currentMapElement, uint8 counter, uint16 *endScore, int test_edge, uint8 *endJunctions, rct_xyz8 junctionList[16], uint8 directionList[16], rct_xyz8 *endXYZ, uint8 *endSteps) { uint searchResult = PATH_SEARCH_FAILED; x += TileDirectionDelta[test_edge].x; @@ -8757,21 +8759,9 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, rct_map ++counter; _peepPathFindTilesChecked--; - /* If the maxNumSteps or maxTilesChecked limits are exceeded the - * search ends here. Return the searchResult with the original score. */ - if (counter > 200 || _peepPathFindTilesChecked < 0) { - #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - if (gPathFindDebug) { - log_info("[%03d] Return from %d,%d,%d; Search limit exceeded", counter, x >> 5, y >> 5, z); - } - #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - searchResult = PATH_SEARCH_LIMIT_REACHED; - return searchResult; - } - /* If this is where the search started this is a search loop and the * current search path ends here. - * Return the searchResult with the original score. */ + * Return without updating the parameters (best result so far). */ if ((_peepPathFindHistory[0].location.x == (uint8)(x >> 5)) && (_peepPathFindHistory[0].location.y == (uint8)(y >> 5)) && (_peepPathFindHistory[0].location.z == (uint8)z)) { @@ -8780,8 +8770,7 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, rct_map log_info("[%03d] Return from %d,%d,%d; At start", counter, x >> 5, y >> 5, z); } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - searchResult = PATH_SEARCH_LOOP; - return searchResult; + return; } /* Get the next map element in the direction of test_edge, ignoring @@ -8880,14 +8869,14 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, rct_map } while (!map_element_is_last_for_tile(mapElement++)); /* No map element could be found. - * Return the searchResult with the original score. */ + * Return without updating the parameters (best result so far). */ if (!found) { #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 if (gPathFindDebug) { log_info("[%03d] Return from %d,%d,%d; No map element found", counter, x >> 5, y >> 5, z); } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - return searchResult; // PATH_SEARCH_FAILED + return; } /* At this point the map element is found. */ @@ -8912,36 +8901,40 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, rct_map z_delta <<= 1; new_score += z_delta; - /* If this map element is the search goal the current search path ends here. - * Return the searchResult with the new score. */ + /* If this map element is the search goal the current search path ends here. */ if (new_score == 0) { - *endScore = new_score; - *endSteps = counter; - // Update the end x,y,z - endXYZ->x = x >> 5; - endXYZ->y = y >> 5; - endXYZ->z = z; - // TODO: Doing the following when the call returns to the previous thin junction (below) should be equivalent to doing it here. i.e. reduce code duplication. - *endJunctions = _peepPathFindMaxJunctions - _peepPathFindNumJunctions; - for (uint8 junctInd = 0; junctInd < *endJunctions; junctInd++) { - uint8 histIdx = _peepPathFindMaxJunctions - junctInd; - junctionList[junctInd].x = _peepPathFindHistory[histIdx].location.x; - junctionList[junctInd].y = _peepPathFindHistory[histIdx].location.y; - junctionList[junctInd].z = _peepPathFindHistory[histIdx].location.z; - directionList[junctInd] = _peepPathFindHistory[histIdx].direction; + /* If the search result is better than the best so far (in the paramaters), + * then update the parameters with this search. */ + if (new_score < *endScore || (new_score == *endScore && counter < *endSteps )) { + // Update the search results + *endScore = new_score; + *endSteps = counter; + // Update the end x,y,z + endXYZ->x = x >> 5; + endXYZ->y = y >> 5; + endXYZ->z = z; + // Update the telemetry + *endJunctions = _peepPathFindMaxJunctions - _peepPathFindNumJunctions; + for (uint8 junctInd = 0; junctInd < *endJunctions; junctInd++) { + uint8 histIdx = _peepPathFindMaxJunctions - junctInd; + junctionList[junctInd].x = _peepPathFindHistory[histIdx].location.x; + junctionList[junctInd].y = _peepPathFindHistory[histIdx].location.y; + junctionList[junctInd].z = _peepPathFindHistory[histIdx].location.z; + directionList[junctInd] = _peepPathFindHistory[histIdx].direction; + } + #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + if (gPathFindDebug) { + log_info("[%03d] Return from %d,%d,%d; At goal; Score: %d", counter, x >> 5, y >> 5, z, *endScore); + } + #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 } - #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - if (gPathFindDebug) { - log_info("[%03d] Return from %d,%d,%d; At goal; Score: %d", counter, x >> 5, y >> 5, z, *endScore); - } - #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - return searchResult; + return; } /* At this point the map element tile is not the goal. */ - /* If this map element is not a path, the search cannot be - * continued, so return the searchResult with the original score. */ + /* If this map element is not a path, the search cannot be continued. + * Return without updating the parameters (best result so far). */ if (searchResult != PATH_SEARCH_DEAD_END && searchResult != PATH_SEARCH_THIN && searchResult != PATH_SEARCH_JUNCTION && @@ -8951,7 +8944,7 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, rct_map log_info("[%03d] Return from %d,%d,%d; Not a path", counter, x >> 5, y >> 5, z); } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - return searchResult; + return; } /* At this point the map element is a path. */ @@ -8963,16 +8956,18 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, rct_map * thereafter stay on thin paths. */ /* So, if the current path is also wide the goal could still * be reachable from here. - * Return the searchResult with the new score. - * Otherwise, return the original score. */ - if (footpath_element_is_wide(currentMapElement)) { + * If the search result is better than the best so far (in the paramaters), + * then update the parameters with this search. */ + if (footpath_element_is_wide(currentMapElement) && + (new_score < *endScore || (new_score == *endScore && counter < *endSteps ))) { + // Update the search results *endScore = new_score; *endSteps = counter; // Update the end x,y,z endXYZ->x = x >> 5; endXYZ->y = y >> 5; endXYZ->z = z; - // TODO: Doing the following when the call returns to the previous thin junction (below) should be equivalent to doing it here. i.e. reduce code duplication. + // Update the telemetry *endJunctions = _peepPathFindMaxJunctions - _peepPathFindNumJunctions; for (uint8 junctInd = 0; junctInd < *endJunctions; junctInd++) { uint8 histIdx = _peepPathFindMaxJunctions - junctInd; @@ -8987,7 +8982,7 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, rct_map log_info("[%03d] Return from %d,%d,%d; Wide path; Score: %d", counter, x >> 5, y >> 5, z, *endScore); } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - return searchResult; + return; } /* At this point the map element is a non-wide path.*/ @@ -9006,15 +9001,47 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, rct_map test_edge = bitscanforward(edges); /* If there are no other edges the current search ends here. - * Return the searchResult with the original score. */ + * Return without updating the parameters (best result so far). */ if (test_edge == -1) { #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 if (gPathFindDebug) { log_info("[%03d] Return from %d,%d,%d; No more edges/dead end", counter, x >> 5, y >> 5, z); } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - searchResult = PATH_SEARCH_DEAD_END; // this should be superfluous. - return searchResult; + return; + } + + /* Check if either of the search limits has been reached: + * - max number of steps or max tiles checked. */ + if (counter >= 200 || _peepPathFindTilesChecked <= 0) { + /* The current search ends here. + * The path continues, so the goal could still be reachable from here. + * If the search result is better than the best so far (in the paramaters), + * then update the parameters with this search. */ + if (new_score < *endScore || (new_score == *endScore && counter < *endSteps )) { + // Update the search results + *endScore = new_score; + *endSteps = counter; + // Update the end x,y,z + endXYZ->x = x >> 5; + endXYZ->y = y >> 5; + endXYZ->z = z; + // Update the telemetry + *endJunctions = _peepPathFindMaxJunctions - _peepPathFindNumJunctions; + for (uint8 junctInd = 0; junctInd < *endJunctions; junctInd++) { + uint8 histIdx = _peepPathFindMaxJunctions - junctInd; + junctionList[junctInd].x = _peepPathFindHistory[histIdx].location.x; + junctionList[junctInd].y = _peepPathFindHistory[histIdx].location.y; + junctionList[junctInd].z = _peepPathFindHistory[histIdx].location.z; + directionList[junctInd] = _peepPathFindHistory[histIdx].direction; + } + } + #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + if (gPathFindDebug) { + log_info("[%03d] Return from %d,%d,%d; Search limit reached", counter, x >> 5, y >> 5, z); + } + #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + return; } bool thin_junction = false; @@ -9028,39 +9055,11 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, rct_map * junction on this map element. Only 'thin' junctions * are counted towards the junction search limit. */ - /* If the junction search limit is reached, the - * current search path ends here. The goal may still - * be reachable from here. - * Return the searchResult with the new score. */ - if (_peepPathFindNumJunctions <= 0) { - *endScore = new_score; - *endSteps = counter; - // Update the end x,y,z - endXYZ->x = x >> 5; - endXYZ->y = y >> 5; - endXYZ->z = z; - // TODO: Doing the following when the call returns to the previous thin junction (below) should be equivalent to doing it here. i.e. reduce code duplication. - *endJunctions = _peepPathFindMaxJunctions - _peepPathFindNumJunctions; - for (uint8 junctInd = 0; junctInd < *endJunctions; junctInd++) { - uint8 histIdx = _peepPathFindMaxJunctions - junctInd; - junctionList[junctInd].x = _peepPathFindHistory[histIdx].location.x; - junctionList[junctInd].y = _peepPathFindHistory[histIdx].location.y; - junctionList[junctInd].z = _peepPathFindHistory[histIdx].location.z; - directionList[junctInd] = _peepPathFindHistory[histIdx].direction; - } - #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - if (gPathFindDebug) { - log_info("[%03d] Return from %d,%d,%d; NumJunctions < 0; Score: %d", counter, x >> 5, y >> 5, z, *endScore); - } - #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - return searchResult; - } - /* Check the pathfind_history to see if this junction has been * previously passed through in the current search path. * i.e. this is a loop in the current search path. * If so, the current search path ends here. - * Return the searchResult with the original score. */ + * Return without updating the parameters (best result so far). */ for (int junctionNum = _peepPathFindNumJunctions + 1; junctionNum <= _peepPathFindMaxJunctions; junctionNum++) { if ((_peepPathFindHistory[junctionNum].location.x == (uint8)(x >> 5)) && (_peepPathFindHistory[junctionNum].location.y == (uint8)(y >> 5)) && @@ -9070,11 +9069,43 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, rct_map log_info("[%03d] Return from %d,%d,%d; Loop", counter, x >> 5, y >> 5, z); } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - searchResult = PATH_SEARCH_LOOP; - return searchResult; + return; } } + /* If the junction search limit is reached, the + * current search path ends here. The goal may still + * be reachable from here. + * If the search result is better than the best so far (in the paramaters), + * then update the parameters with this search. */ + if (_peepPathFindNumJunctions <= 0) { + if (new_score < *endScore || (new_score == *endScore && counter < *endSteps )) { + // Update the search results + *endScore = new_score; + *endSteps = counter; + // Update the end x,y,z + endXYZ->x = x >> 5; + endXYZ->y = y >> 5; + endXYZ->z = z; + // Update the telemetry + *endJunctions = _peepPathFindMaxJunctions; // - _peepPathFindNumJunctions; + for (uint8 junctInd = 0; junctInd < *endJunctions; junctInd++) { + uint8 histIdx = _peepPathFindMaxJunctions - junctInd; + junctionList[junctInd].x = _peepPathFindHistory[histIdx].location.x; + junctionList[junctInd].y = _peepPathFindHistory[histIdx].location.y; + junctionList[junctInd].z = _peepPathFindHistory[histIdx].location.z; + directionList[junctInd] = _peepPathFindHistory[histIdx].direction; + } + } + #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + if (gPathFindDebug) { + log_info("[%03d] Return from %d,%d,%d; NumJunctions < 0; Score: %d", counter, x >> 5, y >> 5, z, *endScore); + } + #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 + return; + } + + /* This junction was NOT previously visited in the current * search path, so add the junction to the history. */ _peepPathFindHistory[_peepPathFindNumJunctions].location.x = (uint8)(x >> 5); @@ -9088,31 +9119,9 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, rct_map /* Continue searching down each remaining edge of the path * (recursive call). */ - - /* Following variables store the best result so far when - * recursing over the multiple edges of a junction, including - * the path finding telemetry for that result (i.e. the search - * path result, end location, thin junctions and directions in - * the search path). - * No initialisation is performed. The first successful - * search result will be the "best so far" by default. */ - uint8 bestSoFarResult; // searchResult - uint16 bestSoFarScore; // score - uint8 bestSoFarSteps; // steps taken - rct_xyz8 bestSoFarXYZ; // end XYZ - uint8 bestSoFarJunctions; // num of thin junctions traversed - rct_xyz8 savedJunctionList[16]; // list of thin junctions traversed - uint8 savedDirectionList[16]; // list of directions taken at each thin junction - - /* Following boolean indicates whether a successful search - * result has been found yet. i.e. when false there is no - * "best so far" result. */ - bool endFound = false; - - /* Now loop through each of the junction edges. */ do { edges &= ~(1 << test_edge); - sint8 savedNumJunctions = _peepPathFindNumJunctions; + uint8 savedNumJunctions = _peepPathFindNumJunctions; uint8 height = z; if (footpath_element_is_sloped(mapElement) && @@ -9137,111 +9146,17 @@ static uint8 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, rct_map _peepPathFindHistory[_peepPathFindNumJunctions + 1].direction = test_edge; } - // Clear the results for the recursive call. - // TODO: This should be unnecessary. Leaving the existing value should not affect the search. Additionally, leaving the telemetry data intact will eliminate the need to restore it from the "best so far" variables at the end. - *endScore = 0xFFFF; - *endJunctions = 0; - memset(junctionList, 0x00, sizeof(junctionList[16])); - memset(directionList, 0x00, sizeof(directionList[16])); - memset(endXYZ, 0x00, sizeof(*endXYZ)); - - uint8 continueResult = peep_pathfind_heuristic_search(x, y, height, mapElement, counter, endScore, test_edge, endJunctions, junctionList, directionList, endXYZ, endSteps); + peep_pathfind_heuristic_search(x, y, height, mapElement, counter, endScore, test_edge, endJunctions, junctionList, directionList, endXYZ, endSteps); _peepPathFindNumJunctions = savedNumJunctions; - if (continueResult == PATH_SEARCH_LIMIT_REACHED) { - /* When continueResult is PATH_SEARCH_LIMIT_REACHED the - * search limit was reached without checking another - * map element. This means the last tile checked in - * the current path search is the current map element. - * Update the best search result if appropriate and - * keep searchResult (discarding continueResult). */ - *endScore = new_score; - *endSteps = counter; - // Update the end x,y,z - endXYZ->x = x >> 5; - endXYZ->y = y >> 5; - endXYZ->z = z; - // TODO: Doing the following below should be equivalent to doing it here. i.e. reduce code duplication. - *endJunctions = _peepPathFindMaxJunctions - savedNumJunctions; - for (uint8 junctInd = 0; junctInd < *endJunctions; junctInd++) { - uint8 histIdx = _peepPathFindMaxJunctions - junctInd; - junctionList[junctInd].x = _peepPathFindHistory[histIdx].location.x; - junctionList[junctInd].y = _peepPathFindHistory[histIdx].location.y; - junctionList[junctInd].z = _peepPathFindHistory[histIdx].location.z; - directionList[junctInd] = _peepPathFindHistory[histIdx].direction; - } - continueResult = searchResult; - } - #if defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 if (gPathFindDebug) { log_info("[%03d] Return to %d,%d,%d edge: %d; Score: %d", counter, x >> 5, y >> 5, z, test_edge, *endScore); } #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - - // TODO: Combine the following two conditionals into a single, compound conditional to reduce code duplication. - // The first result is stored in bestSoFar. - if (!endFound && *endScore < 0xFFFF) { - endFound = true; - - bestSoFarScore = *endScore; - bestSoFarSteps = *endSteps; - bestSoFarXYZ.x = endXYZ->x; - bestSoFarXYZ.y = endXYZ->y; - bestSoFarXYZ.z = endXYZ->z; - // TODO: Store the "bestSoFar" telemetry directly into the return parameters (i.e. eliminate the need to store the best so far telemetry and eliminate the need to restore the best so far result at the end) from the global variables (i.e. reduce code duplication). - bestSoFarJunctions = *endJunctions; - for (uint8 junctInd = 0; junctInd < *endJunctions; junctInd++) { - savedJunctionList[junctInd].x = junctionList[junctInd].x; - savedJunctionList[junctInd].y = junctionList[junctInd].y; - savedJunctionList[junctInd].z = junctionList[junctInd].z; - savedDirectionList[junctInd] = directionList[junctInd]; - } - bestSoFarResult = continueResult; - - continue; - } - - if (endFound && *endScore < 0xFFFF && - searchResult == PATH_SEARCH_JUNCTION) { - /* Junction. Check if this edge is the best so far. */ - if (*endScore < bestSoFarScore || (*endScore == bestSoFarScore && *endSteps < bestSoFarSteps )) { - bestSoFarScore = *endScore; - bestSoFarSteps = *endSteps; - bestSoFarXYZ.x = endXYZ->x; - bestSoFarXYZ.y = endXYZ->y; - bestSoFarXYZ.z = endXYZ->z; - // TODO: Store the "bestSoFar" telemetry directly into the return parameters (i.e. eliminate the need to store the best so far telemetry and eliminate the need to restore the best so far result at the end) from the global variables (i.e. reduce code duplication). - bestSoFarJunctions = *endJunctions; - for (uint8 junctInd = 0; junctInd < *endJunctions; junctInd++) { - savedJunctionList[junctInd].x = junctionList[junctInd].x; - savedJunctionList[junctInd].y = junctionList[junctInd].y; - savedJunctionList[junctInd].z = junctionList[junctInd].z; - savedDirectionList[junctInd] = directionList[junctInd]; - } - bestSoFarResult = continueResult; - } - } } while ((test_edge = bitscanforward(edges)) != -1); - if (endFound) { - /* Restore the bestSoFar result. */ - *endScore = bestSoFarScore; - *endSteps = bestSoFarSteps; - endXYZ->x = bestSoFarXYZ.x; - endXYZ->y = bestSoFarXYZ.y; - endXYZ->z = bestSoFarXYZ.z; - // TODO: If the "bestSoFar" telemetry is stored directly into the parameters there will be no need to restore it here. - *endJunctions = bestSoFarJunctions; - for (uint8 junctInd = 0; junctInd < *endJunctions; junctInd++) { - junctionList[junctInd].x = savedJunctionList[junctInd].x; - junctionList[junctInd].y = savedJunctionList[junctInd].y; - junctionList[junctInd].z = savedJunctionList[junctInd].z; - directionList[junctInd] = savedDirectionList[junctInd]; - } - return bestSoFarResult; - } - return PATH_SEARCH_FAILED; + return; } /** @@ -9411,35 +9326,30 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) uint8 endDirectionList[16]; memset(endDirectionList, 0x0, sizeof(endDirectionList)); - uint8 searchResult = peep_pathfind_heuristic_search(x, y, height, dest_map_element, 0, &score, test_edge, &endJunctions, endJunctionList, endDirectionList, &endXYZ, &endSteps); + peep_pathfind_heuristic_search(x, y, height, dest_map_element, 0, &score, test_edge, &endJunctions, endJunctionList, endDirectionList, &endXYZ, &endSteps); #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 if (gPathFindDebug) { - log_verbose("Pathfind test edge: %d score: %d steps: %d searchResult: %s end: %d,%d,%d junctions: %d", test_edge, score, endSteps, gPathFindSearchText[searchResult], endXYZ.x, endXYZ.y, endXYZ.z, endJunctions); + log_verbose("Pathfind test edge: %d score: %d steps: %d end: %d,%d,%d junctions: %d", test_edge, score, endSteps, endXYZ.x, endXYZ.y, endXYZ.z, endJunctions); for (uint8 listIdx = 0; listIdx < endJunctions; listIdx++) { log_info("Junction#%d %d,%d,%d Direction %d", listIdx + 1, endJunctionList[listIdx].x, endJunctionList[listIdx].y, endJunctionList[listIdx].z, endDirectionList[listIdx]); } } #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 - if (score == 0 || - searchResult == PATH_SEARCH_THIN || - searchResult == PATH_SEARCH_JUNCTION || - searchResult == PATH_SEARCH_WIDE) { - if (score < best_score || (score == best_score && endSteps < best_sub)) { - chosen_edge = test_edge; - best_score = score; - best_sub = endSteps; - bestJunctions = endJunctions; - for (uint8 index = 0; index < endJunctions; index++) { - bestJunctionList[index].x = endJunctionList[index].x; - bestJunctionList[index].y = endJunctionList[index].y; - bestJunctionList[index].z = endJunctionList[index].z; - bestDirectionList[index] = endDirectionList[index]; - } - bestXYZ.x = endXYZ.x; - bestXYZ.y = endXYZ.y; - bestXYZ.z = endXYZ.z; + if (score < best_score || (score == best_score && endSteps < best_sub)) { + chosen_edge = test_edge; + best_score = score; + best_sub = endSteps; + bestJunctions = endJunctions; + for (uint8 index = 0; index < endJunctions; index++) { + bestJunctionList[index].x = endJunctionList[index].x; + bestJunctionList[index].y = endJunctionList[index].y; + bestJunctionList[index].z = endJunctionList[index].z; + bestDirectionList[index] = endDirectionList[index]; } + bestXYZ.x = endXYZ.x; + bestXYZ.y = endXYZ.y; + bestXYZ.z = endXYZ.z; } } @@ -9456,7 +9366,7 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) } #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 if (gPathFindDebug) { - log_verbose("Pathfind best edge %d with score %d", chosen_edge, best_score); + log_verbose("Pathfind best edge %d with score %d steps %d", chosen_edge, best_score, best_sub); for (uint8 listIdx = 0; listIdx < bestJunctions; listIdx++) { log_verbose("Junction#%d %d,%d,%d Direction %d", listIdx + 1, bestJunctionList[listIdx].x, bestJunctionList[listIdx].y, bestJunctionList[listIdx].z, bestDirectionList[listIdx]); } From 2aaf0692f9f32e5a39f9774c266c1a5ac5654399 Mon Sep 17 00:00:00 2001 From: zaxcav Date: Mon, 3 Oct 2016 22:37:32 +0200 Subject: [PATCH 08/13] Remove unused text descriptions of enum. --- src/peep/peep.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/peep/peep.c b/src/peep/peep.c index 36fc070da8..729a6e31ef 100644 --- a/src/peep/peep.c +++ b/src/peep/peep.c @@ -102,9 +102,6 @@ enum { PATH_SEARCH_FAILED }; -// Some text descriptions corresponding to the above enum for understandable debug messages -const char *gPathFindSearchText[] = {"DeadEnd", "Wide", "Thin", "Junction", "RideQueue", "RideEntrance", "RideExit", "ParkEntryExit", "ShopEntrance", "LimitReached", "PathLoop", "Other", "Failed"}; - enum { F1EE18_DESTINATION_REACHED = 1 << 0, F1EE18_OUTSIDE_PARK = 1 << 1, From 533d51489db8e1e82bf0a1374d44ce2475b4af33 Mon Sep 17 00:00:00 2001 From: zaxcav Date: Tue, 4 Oct 2016 12:02:24 +0200 Subject: [PATCH 09/13] Fix typo 'uint' vs 'uint8'. --- src/peep/peep.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/peep/peep.c b/src/peep/peep.c index 729a6e31ef..499f0fe5aa 100644 --- a/src/peep/peep.c +++ b/src/peep/peep.c @@ -8878,7 +8878,7 @@ static void peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, rct_map_ /* At this point the map element is found. */ - uint height = z; + uint8 height = z; if (map_element_get_type(mapElement) == MAP_ELEMENT_TYPE_PATH) { // Adjust height for goal comparison according to the path slope. if (footpath_element_is_sloped(mapElement)) { From 72b4ff4ac8ff4904c427e7442d994f0070318fa1 Mon Sep 17 00:00:00 2001 From: zaxcav Date: Tue, 4 Oct 2016 12:12:21 +0200 Subject: [PATCH 10/13] Fix another typo 'uint' vs 'uint8'. --- src/peep/peep.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/peep/peep.c b/src/peep/peep.c index 499f0fe5aa..9d54e7f35d 100644 --- a/src/peep/peep.c +++ b/src/peep/peep.c @@ -8748,7 +8748,7 @@ static bool path_is_thin_junction(rct_map_element *path, sint16 x, sint16 y, uin * rct2: 0x0069A997 */ static void peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, rct_map_element *currentMapElement, uint8 counter, uint16 *endScore, int test_edge, uint8 *endJunctions, rct_xyz8 junctionList[16], uint8 directionList[16], rct_xyz8 *endXYZ, uint8 *endSteps) { - uint searchResult = PATH_SEARCH_FAILED; + uint8 searchResult = PATH_SEARCH_FAILED; x += TileDirectionDelta[test_edge].x; y += TileDirectionDelta[test_edge].y; From d447223535230d89edce85b6ef70625114592275 Mon Sep 17 00:00:00 2001 From: zaxcav Date: Tue, 4 Oct 2016 12:32:06 +0200 Subject: [PATCH 11/13] Increase network version for pathfinding updates. --- src/network/network.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/network.h b/src/network/network.h index 499be32a5f..da25dfe391 100644 --- a/src/network/network.h +++ b/src/network/network.h @@ -55,7 +55,7 @@ extern "C" { // This define specifies which version of network stream current build uses. // It is used for making sure only compatible builds get connected, even within // single OpenRCT2 version. -#define NETWORK_STREAM_VERSION "13" +#define NETWORK_STREAM_VERSION "14" #define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION #ifdef __cplusplus From bdb8a1590361926ff4048f86f68f13c885291217 Mon Sep 17 00:00:00 2001 From: zaxcav Date: Wed, 5 Oct 2016 13:36:43 +0200 Subject: [PATCH 12/13] Make path wide flag update only update the current tile. Previously updating the path wide flag updated the current tile and reset the wide flag in some neighboring tiles. This could cause glitches in the pathfinding performed over those reset tiles. --- src/world/footpath.c | 81 ++++++++++++++++++++++++++++++++------------ 1 file changed, 59 insertions(+), 22 deletions(-) diff --git a/src/world/footpath.c b/src/world/footpath.c index 54b19f0019..daa36dcddc 100644 --- a/src/world/footpath.c +++ b/src/world/footpath.c @@ -1757,13 +1757,23 @@ void footpath_update_path_wide_flags(int x, int y) return; footpath_clear_wide(x, y); - x += 0x20; - footpath_clear_wide(x, y); - y += 0x20; - footpath_clear_wide(x, y); - x -= 0x20; - footpath_clear_wide(x, y); - y -= 0x20; + /* Rather than clearing the wide flag of the following tiles and + * checking the state of them later, leave them intact and assume + * they were cleared. Consequently only the wide flag for this single + * tile is modified by this update. + * This is important for avoiding glitches in pathfinding that occurs + * between between the batches of updates to the path wide flags. + * Corresponding pathList[] indexes for the following tiles + * are: 2, 3, 4, 5. + * Note: indexes 3, 4, 5 are reset in the current call; + * index 2 is reset in the previous call. */ + //x += 0x20; + //footpath_clear_wide(x, y); + //y += 0x20; + //footpath_clear_wide(x, y); + //x -= 0x20; + //footpath_clear_wide(x, y); + //y -= 0x20; rct_map_element *mapElement = map_get_first_element_at(x / 32, y / 32); do { @@ -1825,20 +1835,30 @@ void footpath_update_path_wide_flags(int x, int y) if (mapElement->properties.path.edges & 2) { F3EFA5 |= 0x8; - if (pathList[3] != NULL) { - if (footpath_element_is_wide(pathList[3])) { - F3EFA5 &= ~0x8; - } - } + /* In the following: + * footpath_element_is_wide(pathList[3]) + * is always false due to the tile update order + * in combination with reset tiles. + * Commented out since it will never occur. */ + //if (pathList[3] != NULL) { + // if (footpath_element_is_wide(pathList[3])) { + // F3EFA5 &= ~0x8; + // } + //} } if (mapElement->properties.path.edges & 4) { F3EFA5 |= 0x20; - if (pathList[5] != NULL) { - if (footpath_element_is_wide(pathList[5])) { - F3EFA5 &= ~0x20; - } - } + /* In the following: + * footpath_element_is_wide(pathList[5]) + * is always false due to the tile update order + * in combination with reset tiles. + * Commented out since it will never occur. */ + //if (pathList[5] != NULL) { + // if (footpath_element_is_wide(pathList[5])) { + // F3EFA5 &= ~0x20; + // } + //} } if ((F3EFA5 & 0x80) && (pathList[7] != NULL) && !(footpath_element_is_wide(pathList[7]))) { @@ -1849,27 +1869,44 @@ void footpath_update_path_wide_flags(int x, int y) F3EFA5 |= 0x1; } + /* In the following: + * footpath_element_is_wide(pathList[5]) + * is always false due to the tile update order + * in combination with reset tiles. + * Short circuit the logic appropriately. */ if ((F3EFA5 & 0x20) && (pathList[6] != NULL) && (!footpath_element_is_wide(pathList[6])) && ((pathList[6]->properties.path.edges & 3) == 3) && // N W - (pathList[5] != NULL) && (!footpath_element_is_wide(pathList[5]))) { + (pathList[5] != NULL) && (true || !footpath_element_is_wide(pathList[5]))) { F3EFA5 |= 0x40; } } - if ((F3EFA5 & 0x8) && (pathList[3] != NULL) && !(pathList[3]->type & 2)) { + /* In the following: + * footpath_element_is_wide(pathList[2]) + * footpath_element_is_wide(pathList[3]) + * are always false due to the tile update order + * in combination with reset tiles. + * Short circuit the logic appropriately. */ + if ((F3EFA5 & 0x8) && (pathList[3] != NULL) && (true || !footpath_element_is_wide(pathList[3]))) { if ((F3EFA5 & 2) && - (pathList[2] != NULL) && (!footpath_element_is_wide(pathList[2])) && + (pathList[2] != NULL) && (true || !footpath_element_is_wide(pathList[2])) && ((pathList[2]->properties.path.edges & 0xC) == 0xC) && (pathList[1] != NULL) && (!footpath_element_is_wide(pathList[1]))) { F3EFA5 |= 0x4; } + /* In the following: + * footpath_element_is_wide(pathList[4]) + * footpath_element_is_wide(pathList[5]) + * are always false due to the tile update order + * in combination with reset tiles. + * Short circuit the logic appropriately. */ if ((F3EFA5 & 0x20) && - (pathList[4] != NULL) && (!footpath_element_is_wide(pathList[4])) && + (pathList[4] != NULL) && (true || !footpath_element_is_wide(pathList[4])) && ((pathList[4]->properties.path.edges & 9) == 9) && - (pathList[5] != NULL) && (!footpath_element_is_wide(pathList[5]))) { + (pathList[5] != NULL) && (true || !footpath_element_is_wide(pathList[5]))) { F3EFA5 |= 0x10; } } From 62ed46d29b755a3e50f848715f811b7ad3c7cdd3 Mon Sep 17 00:00:00 2001 From: zaxcav Date: Fri, 7 Oct 2016 12:32:09 +0200 Subject: [PATCH 13/13] Simplify initialisation of pathfinding telemetry. Code revised per review comments. --- src/peep/peep.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/peep/peep.c b/src/peep/peep.c index 9d54e7f35d..a67404a1ec 100644 --- a/src/peep/peep.c +++ b/src/peep/peep.c @@ -9251,10 +9251,8 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) uint8 best_sub = 0xFF; uint8 bestJunctions = 0; - rct_xyz8 bestJunctionList[16]; - memset(bestJunctionList, 0x0, sizeof(bestJunctionList)); - uint8 bestDirectionList[16]; - memset(bestDirectionList, 0x0, sizeof(bestDirectionList)); + rct_xyz8 bestJunctionList[16] = { 0 }; + uint8 bestDirectionList[16] = { 0 }; rct_xyz8 bestXYZ; bestXYZ.x = 0; bestXYZ.y = 0;