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.
This commit is contained in:
zaxcav 2016-09-21 22:21:48 +02:00
parent 89d672ba70
commit f3cf23f5d5
1 changed files with 456 additions and 268 deletions

View File

@ -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.