double shortest_path_calculator::cost(const map_location& loc, const double so_far) const { assert(map_.on_board(loc)); // loc is shrouded, consider it impassable // NOTE: This is why AI must avoid to use shroud if (viewing_team_.shrouded(loc)) return getNoPathValue(); const t_translation::t_terrain terrain = map_[loc]; int const terrain_cost = unit_.movement_cost(terrain); // Pathfinding heuristic: the cost must be at least 1 VALIDATE(terrain_cost >= 1, _("Terrain with a movement cost less than 1 encountered.")); // total MP is not enough to move on this terrain: impassable if (total_movement_ < terrain_cost) return getNoPathValue(); int other_unit_subcost = 0; if (!ignore_unit_) { const unit *other_unit = get_visible_unit(units_, loc, viewing_team_); // We can't traverse visible enemy and we also prefer empty hexes // (less blocking in multi-turn moves and better when exploring fog, // because we can't stop on a friend) if (other_unit) { if (teams_[unit_.side() - 1].is_enemy(other_unit->side())) return getNoPathValue(); else // This value will be used with the defense_subcost (see below) // The 1 here means: consider occupied hex as a -1% defense // (less important than 10% defense because friends may move) other_unit_subcost = 1; } } // Compute how many movement points are left in the game turn // needed to reach the previous hex. // total_movement_ is not zero, thanks to the pathfinding heuristic int remaining_movement = movement_left_ - static_cast<int>(so_far); if (remaining_movement < 0) remaining_movement = total_movement_ - (-remaining_movement) % total_movement_; // this will sum all different costs of this move int move_cost = 0; // Suppose that we have only 2 remaining MP and want to move onto a hex // costing 3 MP. We don't have enough MP now, so we must end our turn here, // thus spend our remaining MP by waiting (next turn, with full MP, we will // be able to move on that hex) if (remaining_movement < terrain_cost) { move_cost += remaining_movement; remaining_movement = total_movement_; // we consider having full MP now } // check ZoC if (!ignore_unit_ && remaining_movement != terrain_cost && enemy_zoc(units_, teams_, loc, viewing_team_, unit_.side()) && !unit_.get_ability_bool("skirmisher", loc)) { // entering ZoC cost all remaining MP move_cost += remaining_movement; } else { // empty hex, pay only the terrain cost move_cost += terrain_cost; } // We will add a tiny cost based on terrain defense, so the pathfinding // will prefer good terrains between 2 with the same MP cost // Keep in mind that defense_modifier is inverted (= 100 - defense%) const int defense_subcost = ignore_defense_ ? 0 : unit_.defense_modifier(terrain); // We divide subcosts by 100 * 100, because defense is 100-based and // we don't want any impact on move cost for less then 100-steps path // (even ~200 since mean defense is around ~50%) return move_cost + (defense_subcost + other_unit_subcost) / 10000.0; }
marked_route mark_route(const plain_route &rt, const std::vector<map_location>& waypoints, const unit &u, const team &viewing_team, const unit_map &units, const std::vector<team> &teams, const gamemap &map) { marked_route res; if (rt.steps.empty()) return res; res.steps = rt.steps; int turns = 0; int movement = u.movement_left(); const team& unit_team = teams[u.side()-1]; bool zoc = false; std::vector<map_location>::const_iterator i = rt.steps.begin(), w = waypoints.begin(); // TODO fix the name confusion with waypoints and route.waypoints for (; i !=rt.steps.end(); i++) { bool last_step = (i+1 == rt.steps.end()); // move_cost of the next step is irrelevant for the last step assert(last_step || map.on_board(*(i+1))); const int move_cost = last_step ? 0 : u.movement_cost(map[*(i+1)]); bool capture = false; bool pass_here = false; if (w != waypoints.end() && *i == *w) { w++; pass_here = true; } if (last_step || zoc || move_cost > movement) { // check if we stop an a village and so maybe capture it // if it's an enemy unit and a fogged village, we assume a capture // (if he already owns it, we can't know that) // if it's not an enemy, we can always know if he owns the village bool capture = map.is_village(*i) && ( !unit_team.owns_village(*i) || (viewing_team.is_enemy(u.side()) && viewing_team.fogged(*i)) ); ++turns; bool invisible = u.invisible(*i,units,teams,false); res.waypoints[*i] = marked_route::waypoint(turns, pass_here, zoc, capture, invisible); if (last_step) break; // finished and we used dummy move_cost movement = u.total_movement(); if(move_cost > movement) { return res; //we can't reach destination } } else if (pass_here) { bool invisible = u.invisible(*i,units,teams,false); res.waypoints[*i] = marked_route::waypoint(0, pass_here, zoc, false, invisible); } zoc = enemy_zoc(units, teams, *(i + 1), viewing_team,u.side()) && !u.get_ability_bool("skirmisher", *(i+1)); if (zoc || capture) { movement = 0; } else { movement -= move_cost; } } return res; }
pathfind::marked_route pathfind::mark_route(const plain_route &rt, const std::vector<map_location>& waypoints) { marked_route res; if (rt.steps.empty()) return marked_route(); res.route = rt; unit_map::const_iterator it = resources::units->find(rt.steps.front()); if (it == resources::units->end()) return marked_route(); unit const& u = *it; int turns = 0; int movement = u.movement_left(); const team& unit_team = (*resources::teams)[u.side()-1]; bool zoc = false; std::vector<map_location>::const_iterator i = rt.steps.begin(), w = waypoints.begin(); for (; i !=rt.steps.end(); ++i) { bool last_step = (i+1 == rt.steps.end()); // move_cost of the next step is irrelevant for the last step assert(last_step || resources::game_map->on_board(*(i+1))); const int move_cost = last_step ? 0 : u.movement_cost((*resources::game_map)[*(i+1)]); bool pass_here = false; if (w != waypoints.end() && *i == *w) { ++w; pass_here = true; } team const& viewing_team = (*resources::teams)[resources::screen->viewing_team()]; // check if there is a village that we could capture // if it's an enemy unit and a fogged village, we assume it's free // (if he already owns it, we can't know that) // if it's not an enemy, we can always know if he owns the village bool village = resources::game_map->is_village(*i) && ( !unit_team.owns_village(*i) || (viewing_team.is_enemy(u.side()) && viewing_team.fogged(*i)) ); // NOTE if there is a waypoint on a not owned village, then we will always stop there if (last_step || zoc || move_cost > movement || (pass_here && village)) { // check if we stop an a village and so maybe capture it // if it's an enemy unit and a fogged village, we assume a capture // (if he already owns it, we can't know that) // if it's not an enemy, we can always know if he owns the village bool capture = resources::game_map->is_village(*i) && ( !unit_team.owns_village(*i) || (viewing_team.is_enemy(u.side()) && viewing_team.fogged(*i)) ); ++turns; bool invisible = u.invisible(*i,false); res.marks[*i] = marked_route::mark(turns, pass_here, zoc, capture, invisible); if (last_step) break; // finished and we used dummy move_cost movement = u.total_movement(); if(move_cost > movement) { return res; //we can't reach destination } } else if (pass_here) { bool invisible = u.invisible(*i,false); res.marks[*i] = marked_route::mark(0, pass_here, zoc, false, invisible); } zoc = enemy_zoc((*resources::teams), *(i + 1), viewing_team,u.side()) && !u.get_ability_bool("skirmisher", *(i+1)); if (zoc) { movement = 0; } else { movement -= move_cost; } } return res; }
static void find_routes(const gamemap& map, const unit_map& units, const unit& u, const map_location& loc, int move_left, paths::dest_vect &destinations, std::vector<team> const &teams, bool force_ignore_zocs, bool allow_teleport, int turns_left, const team &viewing_team, bool see_all, bool ignore_units) { const team& current_team = teams[u.side() - 1]; std::set<map_location> teleports; if (allow_teleport) { teleports = get_teleport_locations(u, units, viewing_team, see_all, ignore_units); } const int total_movement = u.total_movement(); std::vector<map_location> locs(6 + teleports.size()); std::copy(teleports.begin(), teleports.end(), locs.begin() + 6); search_counter += 2; if (search_counter == 0) search_counter = 2; static std::vector<node> nodes; nodes.resize(map.w() * map.h()); indexer index(map.w(), map.h()); comp node_comp(nodes); int xmin = loc.x, xmax = loc.x, ymin = loc.y, ymax = loc.y, nb_dest = 1; nodes[index(loc)] = node(move_left, turns_left, map_location::null_location, loc); std::vector<int> pq; pq.push_back(index(loc)); while (!pq.empty()) { node& n = nodes[pq.front()]; std::pop_heap(pq.begin(), pq.end(), node_comp); pq.pop_back(); n.in = search_counter; get_adjacent_tiles(n.curr, &locs[0]); for (int i = teleports.count(n.curr) ? locs.size() : 6; i-- > 0; ) { if (!locs[i].valid(map.w(), map.h())) continue; node& next = nodes[index(locs[i])]; bool next_visited = next.in - search_counter <= 1u; // Classic Dijkstra allow to skip chosen nodes (with next.in==search_counter) // But the cost function and hex grid allow to also skip visited nodes: // if next was visited, then we already have a path 'src-..-n2-next' // - n2 was chosen before n, meaning that it is nearer to src. // - the cost of 'n-next' can't be smaller than 'n2-next' because // cost is independent of direction and we don't have more MP at n // (important because more MP may allow to avoid waiting next turn) // Thus, 'src-..-n-next' can't be shorter. if (next_visited) continue; const int move_cost = u.movement_cost(map[locs[i]]); node t = node(n.movement_left, n.turns_left, n.curr, locs[i]); if (t.movement_left < move_cost) { t.movement_left = total_movement; t.turns_left--; } if (t.movement_left < move_cost || t.turns_left < 0) continue; t.movement_left -= move_cost; if (!ignore_units) { const unit *v = get_visible_unit(units, locs[i], viewing_team, see_all); if (v && current_team.is_enemy(v->side())) continue; if (!force_ignore_zocs && t.movement_left > 0 && enemy_zoc(units, teams, locs[i], viewing_team, u.side(), see_all) && !u.get_ability_bool("skirmisher", locs[i])) { t.movement_left = 0; } } ++nb_dest; int x = locs[i].x; if (x < xmin) xmin = x; if (xmax < x) xmax = x; int y = locs[i].y; if (y < ymin) ymin = y; if (ymax < y) ymax = y; bool in_list = next.in == search_counter + 1; t.in = search_counter + 1; next = t; // if already in the priority queue then we just update it, else push it. if (in_list) { // never happen see next_visited above std::push_heap(pq.begin(), std::find(pq.begin(), pq.end(), index(locs[i])) + 1, node_comp); } else { pq.push_back(index(locs[i])); std::push_heap(pq.begin(), pq.end(), node_comp); } } } // Build the routes for every map_location that we reached. // The ordering must be compatible with map_location::operator<. destinations.reserve(nb_dest); for (int x = xmin; x <= xmax; ++x) { for (int y = ymin; y <= ymax; ++y) { const node &n = nodes[index(map_location(x, y))]; if (n.in - search_counter > 1u) continue; paths::step s = { n.curr, n.prev, n.movement_left + n.turns_left * total_movement }; destinations.push_back(s); } } }
/** * Creates a list of routes that a unit can traverse from the provided location. * (This is called when creating pathfind::paths and descendant classes.) * * @param[in] origin The location at which to begin the routes. * @param[in] costs The costs to use for route finding. * @param[in] slowed Whether or not to use the slowed costs. * @param[in] moves_left The number of movement points left for the current turn. * @param[in] max_moves The number of movement points in each future turn. * @param[in] turns_left The number of future turns of movement to calculate. * @param[out] destinations The traversable routes. * @param[out] edges The hexes (possibly off-map) adjacent to those in * destinations. (It is permissible for this to contain * some hexes that are also in destinations.) * * @param[in] teleporter If not NULL, teleportaion will be considered, using * this unit's abilities. * @param[in] current_team If not NULL, enemies of this team can obstruct routes * both by occupying hexes and by exerting zones of control. * In addition, the presence of units can affect * teleportation options. * @param[in] skirmisher If not NULL, use this to determine where ZoC can and * cannot be ignored (due to this unit having or not * having the skirmisher ability). * If NULL, then ignore all zones of control. * (No effect if current_team is NULL). * @param[in] viewing_team If not NULL, use this team's vision when detecting * enemy units and teleport destinations. * If NULL, then "see all". * (No effect if teleporter and current_team are both NULL.) * @param[in] jamming_map The relevant "jamming" of the costs being used * (currently only used with vision costs). */ static void find_routes( const map_location & origin, const movetype::terrain_costs & costs, bool slowed, int moves_left, int max_moves, int turns_left, paths::dest_vect & destinations, std::set<map_location> * edges, const unit * teleporter, const team * current_team, const unit * skirmisher, const team * viewing_team, const std::map<map_location, int> * jamming_map=NULL) { const gamemap& map = *resources::game_map; const bool see_all = viewing_team == NULL; // When see_all is true, the viewing team never matters, but we still // need to supply one to some functions. if ( viewing_team == NULL ) viewing_team = &resources::teams->front(); // Build a teleport map, if needed. const teleport_map teleports = teleporter ? get_teleport_locations(*teleporter, *viewing_team, see_all, current_team == NULL) : teleport_map(); // Since this is called so often, keep memory reserved for the node list. static std::vector<findroute_node> nodes; static unsigned search_counter = 0; // Incrementing search_counter means we ignore results from earlier searches. ++search_counter; // Whenever the counter cycles, trash the contents of nodes and restart at 1. if ( search_counter == 0 ) { nodes.resize(0); search_counter = 1; } // Initialize the nodes for this search. nodes.resize(map.w() * map.h()); findroute_comp node_comp(nodes); findroute_indexer index(map.w(), map.h()); // Used to optimize the final collection of routes. int xmin = origin.x, xmax = origin.x, ymin = origin.y, ymax = origin.y; int nb_dest = 1; // Record the starting location. assert(index(origin) >= 0); nodes[index(origin)] = findroute_node(moves_left, turns_left, map_location::null_location, search_counter); // Begin the search at the starting location. std::vector<int> hexes_to_process(1, index(origin)); // Will be maintained as a heap. while ( !hexes_to_process.empty() ) { // Process the hex closest to the origin. const int cur_index = hexes_to_process.front(); const map_location cur_hex = index(cur_index); const findroute_node& current = nodes[cur_index]; // Remove from the heap. std::pop_heap(hexes_to_process.begin(), hexes_to_process.end(), node_comp); hexes_to_process.pop_back(); // Get the locations adjacent to current. std::vector<map_location> adj_locs(6); get_adjacent_tiles(cur_hex, &adj_locs[0]); if ( teleporter ) { std::set<map_location> allowed_teleports; teleports.get_adjacents(allowed_teleports, cur_hex); adj_locs.insert(adj_locs.end(), allowed_teleports.begin(), allowed_teleports.end()); } for ( int i = adj_locs.size()-1; i >= 0; --i ) { // Get the node associated with this location. const map_location & next_hex = adj_locs[i]; const int next_index = index(next_hex); if ( next_index < 0 ) { // Off the map. if ( edges != NULL ) edges->insert(next_hex); continue; } findroute_node & next = nodes[next_index]; // Skip nodes we have already collected. // (Since no previously checked routes were longer than // the current one, the current route cannot be shorter.) // (Significant difference from classic Dijkstra: we have // vertex weights, not edge weights.) if ( next.search_num == search_counter ) continue; // If we go to next, it will be from current. next.prev = cur_hex; // Calculate the cost of entering next_hex. int cost = costs.cost(map[next_hex], slowed); if ( jamming_map ) { const std::map<map_location, int>::const_iterator jam_it = jamming_map->find(next_hex); if ( jam_it != jamming_map->end() ) cost += jam_it->second; } // Calculate movement remaining after entering next_hex. next.moves_left = current.moves_left - cost; next.turns_left = current.turns_left; if ( next.moves_left < 0 ) { // Have to delay until the next turn. next.turns_left--; next.moves_left = max_moves - cost; } if ( next.moves_left < 0 || next.turns_left < 0 ) { // Either can never enter this hex or out of turns. if ( edges != NULL ) edges->insert(next_hex); continue; } if ( current_team ) { // Account for enemy units. const unit *v = get_visible_unit(next_hex, *viewing_team, see_all); if ( v && current_team->is_enemy(v->side()) ) { // Cannot enter enemy hexes. if ( edges != NULL ) edges->insert(next_hex); continue; } if ( skirmisher && next.moves_left > 0 && enemy_zoc(*current_team, next_hex, *viewing_team, see_all) && !skirmisher->get_ability_bool("skirmisher", next_hex) ) { next.moves_left = 0; } } // Mark next as being collected. next.search_num = search_counter; // Add this node to the heap. hexes_to_process.push_back(next_index); std::push_heap(hexes_to_process.begin(), hexes_to_process.end(), node_comp); // Bookkeeping (for later). ++nb_dest; if ( next_hex.x < xmin ) xmin = next_hex.x; else if ( xmax < next_hex.x ) xmax = next_hex.x; if ( next_hex.y < ymin ) ymin = next_hex.y; else if ( ymax < next_hex.y ) ymax = next_hex.y; }//for (i) }//while (hexes_to_process) // Build the routes for every map_location that we reached. // The ordering must be compatible with map_location::operator<. destinations.reserve(nb_dest); for (int x = xmin; x <= xmax; ++x) { for (int y = ymin; y <= ymax; ++y) { const findroute_node &n = nodes[index(x,y)]; if ( n.search_num == search_counter ) { paths::step s = { map_location(x,y), n.prev, n.moves_left + n.turns_left*max_moves }; destinations.push_back(s); } } } }