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(); 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 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 diff --git a/src/peep/peep.c b/src/peep/peep.c index 4e65c2d13a..8f19adc948 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; @@ -59,8 +65,8 @@ uint8 gPeepPathFindQueueRideIndex; bool gPeepPathFindSingleChoiceSection; // uint32 gPeepPathFindAltStationNum; static bool _peepPathFindIsStaff; -static uint8 _peepPathFindQueueRideIndex; static sint8 _peepPathFindNumJunctions; +static sint8 _peepPathFindMaxJunctions; static sint32 _peepPathFindTilesChecked; static uint8 _peepPathFindFewestNumSteps; @@ -68,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; @@ -79,14 +88,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 +8425,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 +8458,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 +8501,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 +8546,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 +8616,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; @@ -8619,25 +8639,87 @@ static uint8 peep_pathfind_get_max_number_junctions(rct_peep* peep){ } /** - * Returns the best (smallest) score reachable from Tile x,y,z in direction - * test_edge, within the limits of the search. + * 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; +} + +/** + * 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 * 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 * 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: @@ -8646,249 +8728,432 @@ static uint8 peep_pathfind_get_max_number_junctions(rct_peep* peep){ * 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 (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 + * 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 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) { + uint8 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; - } - - /* If counter (# steps taken) exceeds the limit, the current search - * path ends here */ - if (counter > 200) { - #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); - #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - return score; - } + _peepPathFindTilesChecked--; /* If this is where the search started this is a search loop and the - * current search path ends here */ - if ((_peepPathFindHistory[0].x == (uint8)(x >> 5)) && - (_peepPathFindHistory[0].y == (uint8)(y >> 5)) && - (_peepPathFindHistory[0].z == (uint8)z)) { + * current search path ends here. + * 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)) { #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); + 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 - return score; + return; } + /* 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 gPeepPathFindIgnoreForeignQueues) */ + 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++)); + + /* No map element could be found. + * 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; + } + + /* At this point the map element is found. */ + + 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)) { + 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 this map element is the search goal the current search path ends here. */ + if (new_score == 0) { + /* 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 - log_information("[%03d] Return from %d,%d,%d; At goal; Score: %d\n", 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 score; } + return; } - /* 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; + /* At this point the map element tile is not the goal. */ - /* 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 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 && + 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; + } - 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; + /* At this point the map element is a path. */ + + /* 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. */ + /* So, if the current path is also wide 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 (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; + // 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; } } - - 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) { #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); + 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 score; + return; } - /* 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 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); + 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 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 without updating the parameters (best result so far). */ 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); + 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 - return score; + return; } - /* 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 (thin_count > 2) { - thin_junction = true; - break; - } - prescan_edges &= ~(1 << prescan_edge); - } while ((prescan_edge = bitscanforward(prescan_edges)) != -1); - - /* 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 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); - #endif // defined(DEBUG_LEVEL_2) && DEBUG_LEVEL_2 - return score; + /* 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; + } - /* 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; + bool thin_junction = false; + 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_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. */ + + /* 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 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)) && + (_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 + 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); + _peepPathFindHistory[_peepPathFindNumJunctions].location.y = (uint8)(y >> 5); + _peepPathFindHistory[_peepPathFindNumJunctions].location.z = (uint8)z; + // .direction take is added below. + + _peepPathFindNumJunctions--; } } - /* 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). */ do { edges &= ~(1 << test_edge); - sint8 savedNumJunctions = _peepPathFindNumJunctions; + uint8 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 (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); - 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); + 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 - score = 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; + } + + peep_pathfind_heuristic_search(x, y, height, mapElement, counter, endScore, test_edge, endJunctions, junctionList, directionList, endXYZ, endSteps); _peepPathFindNumJunctions = savedNumJunctions; + + #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 } 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; + return; } /** @@ -8901,13 +9166,10 @@ static uint16 peep_pathfind_heuristic_search(sint16 x, sint16 y, uint8 z, uint8 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. - * 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,40 +9180,11 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) .z = (uint8)(gPeepPathFindGoalPosition.z) }; - uint8 edges = 0xF; - if (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 - * 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. */ - - /* If the peep remembers walking through this junction - * previously while heading for its goal, retrieve the - * directions it has not yet tried. */ - for (int i = 0; i < 4; ++i) { - if (peep->pathfind_history[i].x == x / 32 && - peep->pathfind_history[i].y == y / 32 && - peep->pathfind_history[i].z == z) { - edges = peep->pathfind_history[i].direction & 0xF; - break; - } - } + #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); @@ -8965,6 +9198,45 @@ int peep_pathfind_choose_direction(sint16 x, sint16 y, uint8 z, rct_peep *peep) // 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 (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 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. + * 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 + * directions it has not yet tried. */ + for (int i = 0; i < 4; ++i) { + if (peep->pathfind_history[i].x == x / 32 && + 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; + } + } + } + // Remove any edges that are not permitted edges &= path_get_permitted_edges(dest_map_element); @@ -8978,8 +9250,18 @@ 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] = { 0 }; + uint8 bestDirectionList[16] = { 0 }; + rct_xyz8 bestXYZ; + bestXYZ.x = 0; + bestXYZ.y = 0; + bestXYZ.z = 0; + #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); + 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 @@ -8987,6 +9269,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,40 +9281,96 @@ 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. */ _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 = peep_pathfind_heuristic_search(x, y, height, 0, 0xFFFF, test_edge); + uint16 score = 0xFFFF; + /* 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)); + + 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 - log_verbose("Pathfind test edge: %d score: %d steps: %d\n", test_edge, score, _peepPathFindFewestNumSteps); + if (gPathFindDebug) { + 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 < best_score || (score == best_score && _peepPathFindFewestNumSteps < best_sub)) { + 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; } } + + /* 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 + 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("Pathfind best edge %d with score %d\n", chosen_edge, best_score); + if (gPathFindDebug) { + 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]); + } + log_verbose("End at %d,%d,%d", bestXYZ.x, bestXYZ.y, bestXYZ.z); + } #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 || @@ -9045,29 +9384,46 @@ 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)); - } - - /* 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 defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + if (gPathFindDebug) { + log_verbose("New goal; clearing pf_history."); } + #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 } - /* 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); + 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); + #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; + } + } + + /* 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); + #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; } @@ -9387,6 +9743,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 +9758,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 +9814,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. @@ -9524,7 +9884,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 6ab7555e30..8c35068ab5 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" @@ -999,9 +1000,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 +1013,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 +1034,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 +1055,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 +1077,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) { @@ -1083,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); } 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; } }