Exemplo n.º 1
0
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;
}
Exemplo n.º 2
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;
}
Exemplo n.º 4
0
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);
		}
	}
}
Exemplo n.º 5
0
/**
 * 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);
			}
		}
	}
}