#include "AppHdr.h" #include "mon-pathfind.h" #include "coord.h" #include "directn.h" #include "env.h" #include "los.h" #include "mon-movetarget.h" #include "mon-place.h" #include "mon-util.h" #include "monster.h" #include "random.h" #include "state.h" #include "terrain.h" #include "traps.h" ///////////////////////////////////////////////////////////////////////////// // monster_pathfind // The pathfinding is an implementation of the A* algorithm. Beginning at the // monster position we check all neighbours of a given grid, estimate the // distance needed for any shortest path including this grid and push the // result into a hash. We can then easily access all points with the shortest // distance estimates and then check _their_ neighbours and so on. // The algorithm terminates once we reach the destination since - because // of the sorting of grids by shortest distance in the hash - there can be no // path between start and target that is shorter than the current one. There // could be other paths that have the same length but that has no real impact. // If the hash has been cleared and the start grid has not been encountered, // then there's no path that matches the requirements fed into monster_pathfind. // (These requirements are usually preference of habitat of a specific monster // or a limit of the distance between start and any grid on the path.) int mons_tracking_range(const monster* mon) { int range = 0; switch (mons_intel(mon)) { case I_PLANT: range = 2; break; case I_INSECT: case I_REPTILE: range = 4; break; case I_ANIMAL: range = 5; break; case I_NORMAL: range = LOS_RADIUS; break; default: // Highly intelligent monsters can find their way // anywhere. (range == 0 means no restriction.) break; } if (range) { if (mon->can_cling_to_walls()) range += 4; else if (mons_is_native_in_branch(mon)) range += 3; else if (mons_class_flag(mon->type, M_BLOOD_SCENT)) range++; } if (player_under_penance(GOD_ASHENZARI)) range *= 5; if (mons_foe_is_marked(mon) || mon->has_ench(ENCH_HAUNTING)) range *= 5; return range; } //#define DEBUG_PATHFIND monster_pathfind::monster_pathfind() : mons(NULL), start(), target(), pos(), allow_diagonals(true), traverse_unmapped(false), range(0), min_length(0), max_length(0), dist(), prev(), hash() { } monster_pathfind::~monster_pathfind() { } void monster_pathfind::set_range(int r) { if (r >= 0) range = r; } coord_def monster_pathfind::next_pos(const coord_def &c) const { return c + Compass[prev[c.x][c.y]]; } // The main method in the monster_pathfind class. // Returns true if a path was found, else false. bool monster_pathfind::init_pathfind(const monster* mon, coord_def dest, bool diag, bool msg, bool pass_unmapped) { mons = mon; start = mon->pos(); target = dest; pos = start; allow_diagonals = diag; traverse_unmapped = pass_unmapped; traverse_in_sight = (!crawl_state.game_is_arena() && !crawl_state.game_is_zotdef() && mon->friendly() && mon->is_summoned() && you.see_cell_no_trans(mon->pos())); // Easy enough. :P if (start == target) { if (msg) mpr("The monster is already there!"); return true; } return start_pathfind(msg); } bool monster_pathfind::init_pathfind(coord_def src, coord_def dest, bool diag, bool msg) { start = src; target = dest; pos = start; allow_diagonals = diag; // Easy enough. :P if (start == target) return true; return start_pathfind(msg); } bool monster_pathfind::start_pathfind(bool msg) { // NOTE: We never do any traversable() check for the target square. // This means that even if the target cannot be reached // we may still find a path leading adjacent to this position, which // is desirable if e.g. the player is hovering over deep water // surrounded by shallow water or floor, or if a foe is hiding in // a wall. max_length = min_length = grid_distance(pos, target); for (int i = 0; i < GXM; i++) for (int j = 0; j < GYM; j++) dist[i][j] = INFINITE_DISTANCE; dist[pos.x][pos.y] = 0; bool success = false; do { // Calculate the distance to all neighbours of the current position, // and add them to the hash, if they haven't already been looked at. success = calc_path_to_neighbours(); if (success) return true; // Pull the position with shortest distance estimate to our target grid. success = get_best_position(); if (!success) { if (msg) { mprf("Couldn't find a path from (%d,%d) to (%d,%d).", target.x, target.y, start.x, start.y); } return false; } } while (true); } // Returns true as soon as we encounter the target. bool monster_pathfind::calc_path_to_neighbours() { coord_def npos; int distance, old_dist, total; // For each point, we look at all neighbour points. Check the orthogonals // last, so that, should an orthogonal and a diagonal direction have the // same total travel cost, the orthogonal will be picked first, and thus // zigzagging will be significantly reduced. // // 1 0 3 This means directions are looked at, in order, // \ | / 1, 3, 5, 7 (diagonals) followed by 0, 2, 4, 6 // 6--.--2 (orthogonals). This is achieved by the assignment // / | \ of (dir = 0) once dir has passed 7. // 7 4 5 // // To avoid bias, we'll choose a random 90 degree rotation int rotate = random2(4) * 2; // equal probability of 0,2,4,6 for (int idir = 1; idir < 8; (idir += 2) == 9 && (idir = 0)) { // Skip diagonal movement. if (!allow_diagonals && (idir % 2)) continue; int dir = (idir + rotate) % 8; // apply our random 90 degree rotation npos = pos + Compass[dir]; #ifdef DEBUG_PATHFIND mprf("Looking at neighbour (%d,%d)", npos.x, npos.y); #endif if (!in_bounds(npos)) continue; if (!traversable(npos) && npos != target) continue; // Ignore this grid if it takes us above the allowed distance // away from the target. if (range && estimated_cost(npos) > range) continue; distance = dist[pos.x][pos.y] + travel_cost(npos); old_dist = dist[npos.x][npos.y]; // Also bail out if this would make the path longer than twice the // allowed distance from the target. (This factor may need tuning.) // // This is actually motivated by performance, as pathfinding // in mazes with see-through walls (e.g. plants) can otherwise // soak up a lot of CPU cycles. // // FIXME: This will still happen for monsters with >I_NORMAL // intelligence, since they have range=0 and are therefore // exempt. Should we cap total path length for them, too? if (range && distance > range * 2) continue; #ifdef DEBUG_PATHFIND mprf("old dist: %d, new dist: %d, infinite: %d", old_dist, distance2, INFINITE_DISTANCE); #endif // If the new distance is better than the old one (initialised with // INFINITE), update the position. if (distance < old_dist) { // Calculate new total path length. total = distance + estimated_cost(npos); if (old_dist == INFINITE_DISTANCE) { #ifdef DEBUG_PATHFIND mprf("Adding (%d,%d) to hash (total dist = %d)", npos.x, npos.y, total); #endif add_new_pos(npos, total); if (total > max_length) max_length = total; } else { #ifdef DEBUG_PATHFIND mprf("Improving (%d,%d) to total dist %d", npos.x, npos.y, total); #endif update_pos(npos, total); } // Update distance start->pos. dist[npos.x][npos.y] = distance; // Set backtracking information. // Converts the Compass direction to its counterpart. // 0 1 2 4 5 6 // 7 . 3 ==> 3 . 7 e.g. (3 + 4) % 8 = 7 // 6 5 4 2 1 0 (7 + 4) % 8 = 11 % 8 = 3 prev[npos.x][npos.y] = (dir + 4) % 8; // Are we finished? if (npos == target) { #ifdef DEBUG_PATHFIND mpr("Arrived at target."); #endif return true; } } } return false; } // Starting at known min_length (minimum total estimated path distance), check // the hash for existing vectors, then pick the last entry of the first vector // that matches. Update min_length, if necessary. bool monster_pathfind::get_best_position() { for (int i = min_length; i <= max_length; i++) { if (!hash[i].empty()) { if (i > min_length) min_length = i; vector &vec = hash[i]; // Pick the last position pushed into the vector as it's most // likely to be close to the target. pos = vec[vec.size()-1]; vec.pop_back(); #ifdef DEBUG_PATHFIND mprf("Returning (%d, %d) as best pos with total dist %d.", pos.x, pos.y, min_length); #endif return true; } #ifdef DEBUG_PATHFIND mprf("No positions for path length %d.", i); #endif } // Nothing found? Then there's no path! :( return false; } // Using the prev vector backtrack from start to target to find all steps to // take along the shortest path. vector monster_pathfind::backtrack() { #ifdef DEBUG_PATHFIND mpr("Backtracking..."); #endif vector path; pos = target; path.push_back(pos); if (pos == start) return path; int dir; do { dir = prev[pos.x][pos.y]; pos = pos + Compass[dir]; ASSERT_IN_BOUNDS(pos); #ifdef DEBUG_PATHFIND mprf("prev: (%d, %d), pos: (%d, %d)", Compass[dir].x, Compass[dir].y, pos.x, pos.y); #endif path.insert(path.begin(), pos); if (pos.origin()) break; } while (pos != start); ASSERT(pos == start); return path; } // Reduces the path coordinates to only a couple of key waypoints needed // to reach the target. Waypoints are chosen such that from one waypoint you // can see (and, more importantly, reach) the next one. Note that // can_go_straight() is probably rather too conservative in these estimates. // This is done because Crawl's pathfinding - once a target is in sight and easy // reach - is both very robust and natural, especially if we want to flexibly // avoid plants and other monsters in the way. vector monster_pathfind::calc_waypoints() { vector path = backtrack(); // If no path found, nothing to be done. if (path.empty()) return path; vector waypoints; pos = path[0]; #ifdef DEBUG_PATHFIND mpr("\nWaypoints:"); #endif for (unsigned int i = 1; i < path.size(); i++) { if (can_go_straight(mons, pos, path[i]) && mons_traversable(path[i])) continue; else { pos = path[i-1]; waypoints.push_back(pos); #ifdef DEBUG_PATHFIND mprf("waypoint: (%d, %d)", pos.x, pos.y); #endif } } // Add the actual target to the list of waypoints, so we can later check // whether a tracked enemy has moved too much, in case we have to update // the path. if (pos != path[path.size() - 1]) waypoints.push_back(path[path.size() - 1]); return waypoints; } bool monster_pathfind::traversable(const coord_def& p) { if (!traverse_unmapped && grd(p) == DNGN_UNSEEN) return false; // XXX: Hack to be somewhat consistent with uses of // opc_immob elsewhere in pathfinding. // All of this should eventually be replaced by // giving the monster a proper pathfinding LOS. if (opc_immob(p) == OPC_OPAQUE && grd(p) != DNGN_CLOSED_DOOR && grd(p) != DNGN_SEALED_DOOR) { // XXX: Ugly hack to make thorn hunters use their briars for defensive // cover instead of just pathing around them. if (mons && mons->type == MONS_THORN_HUNTER && monster_at(p) && monster_at(p)->type == MONS_BRIAR_PATCH) { return true; } else if (mons && mons->type == MONS_WANDERING_MUSHROOM && monster_at(p) && monster_at(p)->type == MONS_TOADSTOOL) { return true; } return false; } if (mons) return mons_traversable(p); return feat_has_solid_floor(grd(p)); } // Checks whether a given monster can pass over a certain position, respecting // its preferred habit and capability of flight or opening doors. bool monster_pathfind::mons_traversable(const coord_def& p) { return mons_can_traverse(mons, p, traverse_in_sight) || mons->can_cling_to_walls() && cell_is_clingable(pos) && cell_can_cling_to(pos, p); } int monster_pathfind::travel_cost(coord_def npos) { #ifdef EUCLIDEAN int cost = 1; if (mons) cost = mons_travel_cost(npos); if ((pos - npos).abs() == 2) cost *= 14; else cost *= 10; return cost; #else if (mons) return mons_travel_cost(npos); return 1; #endif } // Assumes that grids that really cannot be entered don't even get here. // (Checked by traversable().) int monster_pathfind::mons_travel_cost(coord_def npos) { ASSERT(grid_distance(pos, npos) <= 1); // Doors need to be opened. if (feat_is_closed_door(grd(npos))) return 2; const monster_type mt = mons_base_type(mons); const bool ground_level = !mons_airborne(mt, -1, false) && !(mons->can_cling_to_walls() && cell_is_clingable(npos)); // Travelling through water, entering or leaving water is more expensive // for non-amphibious monsters, so they'll avoid it where possible. // (The resulting path might not be optimal but it will lead to a path // a monster of such habits is likely to prefer.) if (mons->floundering_at(npos)) return 2; // Try to avoid (known) traps. const trap_def* ptrap = find_trap(npos); if (ptrap) { const bool knows_trap = ptrap->is_known(mons); const trap_type tt = ptrap->type; if (tt == TRAP_ALARM || tt == TRAP_ZOT) { // Your allies take extra precautions to avoid known alarm traps. // Zot traps are considered intraversable. if (knows_trap && mons->friendly()) return 3; // To hostile monsters, these traps are completely harmless. return 1; } // Mechanical traps can be avoided by flying, as can shafts, and // tele traps are never traversable anyway. if (knows_trap && ground_level) return 2; } return 1; } // The estimated cost to reach a grid is simply max(dx, dy). int monster_pathfind::estimated_cost(coord_def p) { return grid_distance(p, target); } void monster_pathfind::add_new_pos(coord_def npos, int total) { hash[total].push_back(npos); } void monster_pathfind::update_pos(coord_def npos, int total) { // Find hash position of old distance and delete it, // then call_add_new_pos. int old_total = dist[npos.x][npos.y] + estimated_cost(npos); vector &vec = hash[old_total]; for (unsigned int i = 0; i < vec.size(); i++) { if (vec[i] == npos) { vec.erase(vec.begin() + i); break; } } add_new_pos(npos, total); }