bool can_generate(const gamemap& map, const std::vector<team>& teams, const unit_map& units, const unit& u, const map_location& loc) { if (!map.on_board(loc)) { return false; } if (u.movement_cost(map[loc]) == unit_movement_type::UNREACHABLE) { return false; } unit_map::const_iterator it = units.find(loc, false); if (it.valid() && !it->can_stand(u)) { return false; } map_location locs[6]; get_adjacent_tiles(loc, locs); for (int i = 0; i != 6; ++i) { if (!map.on_board(locs[i])) { continue; } if (u.movement_cost(map[locs[i]]) != unit_movement_type::UNREACHABLE) { return true; } } return false; }
// Complexity: O(nr lg(nr) + nr^2), where n = locs.size(), r = radius. // The nr^2 term is bounded by the size of the board. void get_tiles_radius(const gamemap& map, const std::vector<map_location>& locs, size_t radius, std::set<map_location>& result, bool with_border) { // Make sure the provided locations are included. // This would be needed in case some of the provided locations are off-map. // It also allows simpler processing if the radius is zero. // For efficiency, do this first since locs is potentially unsorted. result.insert(locs.begin(), locs.end()); if ( radius != 0 && !locs.empty() ) { const int border = with_border ? map.border_size() : 0; column_ranges collected_tiles; // Collect the hexes within the desired disks into collected_tiles. // This maps each x-value to a set of ranges of y-values that // are covered by the disks around each element of locs. // (So the data size at this point is proportional to the number // of x-values involved, which is O(nr). The lg(nr) factor comes // from the data being sorted.) get_column_ranges(collected_tiles, locs, radius, -border, map.w() + border); // Now that all the tiles have been collected, add them to result. // (There are O(nr^2) hexes to add.) By collecting before adding, each // hex will be processed only once, even when disks overlap. This is // how we can get good performance if there is significant overlap, and // how the work required can be bound by the size of the board. ranges_to_tiles(result, collected_tiles, -border, map.h() + border); } }
boost::optional<std::string> game_board::replace_map(const gamemap & newmap) { boost::optional<std::string> ret = boost::optional<std::string> (); /* Remember the locations where a village is owned by a side. */ std::map<map_location, int> villages; for(const auto& village : map_->villages()) { const int owner = village_owner(village); if(owner != -1) { villages[village] = owner; } } for (unit_map::iterator itor = units_.begin(); itor != units_.end(); ) { if (!newmap.on_board(itor->get_location())) { if (!try_add_unit_to_recall_list(itor->get_location(), itor.get_shared_ptr())) { *ret = std::string("replace_map: Cannot add a unit that would become off-map to the recall list\n"); } units_.erase(itor++); } else { ++itor; } } /* Disown villages that are no longer villages. */ for(const auto& village : villages) { if(!newmap.is_village(village.first)) { teams_[village.second].lose_village(village.first); } } *map_ = newmap; return ret; }
bool move_result::test_route(const unit &un, const team &my_team, const unit_map &units, const std::vector<team> &teams, const gamemap &map, bool) { if (from_== to_) { if (!remove_movement_ || (un.movement_left() == 0) ) { set_error(E_EMPTY_MOVE); return false; } return true; } if (un.movement_left() == 0 ) { set_error(E_EMPTY_MOVE); return false; } if (!to_.valid()) { set_error(E_NO_ROUTE); return false; } const pathfind::shortest_path_calculator calc(un, my_team, units, teams,map); //allowed teleports std::set<map_location> allowed_teleports = pathfind::get_teleport_locations(un, units, my_team, true);//@todo 1.9: see_all -> false //do an A*-search route_ = pathfind::a_star_search(un.get_location(), to_, 10000.0, &calc, map.w(), map.h(), &allowed_teleports); if (route_.steps.empty()) { set_error(E_NO_ROUTE); return false; } return true; }
time_of_day tod_manager::time_of_day_at(const unit_map& units,const map_location& loc, const gamemap& map) const { int lighten = std::max<int>(map.get_terrain_info(map.get_terrain(loc)).light_modification() , 0); int darken = std::min<int>(map.get_terrain_info(map.get_terrain(loc)).light_modification() , 0); time_of_day tod = get_time_of_day(lighten + darken,loc); if(loc.valid()) { map_location locs[7]; locs[0] = loc; get_adjacent_tiles(loc,locs+1); for(int i = 0; i != 7; ++i) { const unit_map::const_iterator itor = units.find(locs[i]); if(itor != units.end() && itor->second.get_ability_bool("illuminates") && !itor->second.incapacitated()) { unit_ability_list illum = itor->second.get_abilities("illuminates"); unit_abilities::effect illum_effect(illum,lighten,false); int mod = illum_effect.get_composite_value(); if(mod + tod.lawful_bonus > illum.highest("max_value").first) { mod = illum.highest("max_value").first - tod.lawful_bonus; } lighten = std::max<int>(mod, lighten); darken = std::min<int>(mod, darken); } } } tod = get_time_of_day(lighten + darken,loc); return tod; }
void team::build(const config& cfg, const gamemap& map, int gold) { gold_ = gold; info_.read(cfg); fog_.set_enabled(cfg["fog"].to_bool()); fog_.read(cfg["fog_data"]); shroud_.set_enabled(cfg["shroud"].to_bool()); shroud_.read(cfg["shroud_data"]); auto_shroud_updates_ = cfg["auto_shroud"].to_bool(auto_shroud_updates_); LOG_NG << "team::team(...): team_name: " << info_.team_name << ", shroud: " << uses_shroud() << ", fog: " << uses_fog() << ".\n"; // Load the WML-cleared fog. const config& fog_override = cfg.child("fog_override"); if(fog_override) { const std::vector<map_location> fog_vector = map.parse_location_range(fog_override["x"], fog_override["y"], true); fog_clearer_.insert(fog_vector.begin(), fog_vector.end()); } // To ensure some minimum starting gold, // gold is the maximum of 'gold' and what is given in the config file gold_ = std::max(gold, info_.gold); if(gold_ != info_.gold) { info_.start_gold = gold; } // Old code was doing: // info_.start_gold = std::to_string(gold) + " (" + info_.start_gold + ")"; // Was it correct? // Load in the villages the side controls at the start for(const config& v : cfg.child_range("village")) { map_location loc(v); if(map.is_village(loc)) { villages_.insert(loc); } else { WRN_NG << "[side] " << current_player() << " [village] points to a non-village location " << loc << std::endl; } } countdown_time_ = cfg["countdown_time"]; action_bonus_count_ = cfg["action_bonus_count"]; planned_actions_.reset(new wb::side_actions()); planned_actions_->set_team_index(info_.side - 1); }
map_location pathfind::find_vacant_tile(const gamemap& map, const unit_map& units, const map_location& loc, pathfind::VACANT_TILE_TYPE vacancy, const unit* pass_check) { if (!map.on_board(loc)) return map_location(); std::set<map_location> pending_tiles_to_check, tiles_checked; pending_tiles_to_check.insert(loc); // Iterate out 50 hexes from loc for (int distance = 0; distance < 50; ++distance) { if (pending_tiles_to_check.empty()) return map_location(); //Copy over the hexes to check and clear the old set std::set<map_location> tiles_checking; tiles_checking.swap(pending_tiles_to_check); //Iterate over all the hexes we need to check foreach (const map_location &loc, tiles_checking) { //If this area is not a castle but should, skip it. if (vacancy == pathfind::VACANT_CASTLE && !map.is_castle(loc)) continue; const bool pass_check_and_unreachable = pass_check && pass_check->movement_cost(map[loc]) == unit_movement_type::UNREACHABLE; //If the unit can't reach the tile and we have searched //an area of at least radius 10 (arbitrary), skip the tile. //Neccessary for cases such as an unreachable //starting hex surrounded by 6 other unreachable hexes, in which case //the algorithm would not even search distance==1 //even if there's a reachable hex for distance==2. if (pass_check_and_unreachable && distance > 10) continue; //If the hex is empty and we do either no pass check or the hex is reachable, return it. if (units.find(loc) == units.end() && !pass_check_and_unreachable) return loc; map_location adjs[6]; get_adjacent_tiles(loc,adjs); foreach (const map_location &loc, adjs) { if (!map.on_board(loc)) continue; // Add the tile to be checked if it hasn't already been and // isn't being checked. if (tiles_checked.find(loc) == tiles_checked.end() && tiles_checking.find(loc) == tiles_checking.end()) { pending_tiles_to_check.insert(loc); } } } tiles_checked.swap(tiles_checking); } return map_location(); }
void get_tiles_radius(gamemap const &map, std::vector<map_location> const &locs, size_t radius, std::set<map_location> &res, xy_pred *pred) { typedef std::set<map_location> location_set; location_set not_visited(locs.begin(), locs.end()), must_visit, filtered_out; ++radius; for(;;) { location_set::const_iterator it = not_visited.begin(), it_end = not_visited.end(); std::copy(it,it_end,std::inserter(res,res.end())); for(; it != it_end; ++it) { map_location adj[6]; get_adjacent_tiles(*it, adj); for(size_t i = 0; i != 6; ++i) { map_location const &loc = adj[i]; if(map.on_board(loc) && !res.count(loc) && !filtered_out.count(loc)) { if(!pred || (*pred)(loc)) { must_visit.insert(loc); } else { filtered_out.insert(loc); } } } } if(--radius == 0 || must_visit.empty()) { break; } not_visited.swap(must_visit); must_visit.clear(); } }
gamemap editor_map::mask_to(const gamemap& target) const { if (target.w() != w() || target.h() != h()) { throw editor_action_exception(_("The size of the target map is different from the current map")); } gamemap mask(target); map_location iter; for (iter.x = -border_size(); iter.x < w() + border_size(); ++iter.x) { for (iter.y = -border_size(); iter.y < h() + border_size(); ++iter.y) { if (target.get_terrain(iter) == get_terrain(iter)) { mask.set_terrain(iter, t_translation::FOGGED); } } } return mask; }
void add_river(river_ptr river, const height_map& heights, gamemap& m, const corner_location& c1, const corner_location& c2) { location adj1[3]; location adj2[3]; std::vector<location> locs; get_adjacent_hexes_to_corner(c1, adj1); get_adjacent_hexes_to_corner(c2, adj2); for(int i = 0; i != 3; ++i) { for(int j = 0; j != 3; ++j) { if(adj1[i] == adj2[j]) { locs.push_back(adj1[i]); } } } assert(locs.size() == 2); DIRECTION dir1 = get_adjacent_direction(locs[0], locs[1]); DIRECTION dir2 = get_adjacent_direction(locs[1], locs[0]); assert(dir1 != NULL_DIRECTION); assert(dir2 != NULL_DIRECTION); tile_ptr t1 = m.get_tile(locs[0]); tile_ptr t2 = m.get_tile(locs[1]); t1->add_river(dir1, river); t2->add_river(dir2, river); }
map_location find_vacant_tile(const gamemap& map, const unit_map& units, const map_location& loc, VACANT_TILE_TYPE vacancy, const unit* pass_check) { std::set<map_location> pending_tiles_to_check; std::set<map_location> tiles_checked; pending_tiles_to_check.insert( loc ); // Iterate out 50 hexes from loc for (int distance = 0; distance < 50; ++distance) { if (pending_tiles_to_check.empty()) return map_location(); //Copy over the hexes to check and clear the old set std::set<map_location> tiles_checking = pending_tiles_to_check; std::set<map_location>::const_iterator tc_itor = tiles_checking.begin(); pending_tiles_to_check.clear(); //Iterate over all the hexes we need to check for ( ; tc_itor != tiles_checking.end(); ++tc_itor ) { //If the unit cannot reach this area or it's not a castle but should, skip it. if ((vacancy == VACANT_CASTLE && !map.is_castle(*tc_itor)) || (pass_check && pass_check->movement_cost(map[*tc_itor]) == unit_movement_type::UNREACHABLE)) continue; //If the hex is empty, return it. if (map.on_board(*tc_itor) && units.find(*tc_itor) == units.end()) return (*tc_itor); map_location adjs[6]; get_adjacent_tiles(*tc_itor,adjs); for (int i = 0; i != 6; ++i) { //Add the tile to be checked if it hasn't already been and isn't already //pending to be checked if (pending_tiles_to_check.find(adjs[i]) == pending_tiles_to_check.end() && tiles_checked.find(adjs[i]) == tiles_checked.end() && tiles_checking.find(adjs[i]) == tiles_checking.end()) { pending_tiles_to_check.insert(adjs[i]); } } } tiles_checked = tiles_checking; } return map_location(); }
bool recruit_result::test_leader_on_keep(const gamemap &map, const unit &my_leader, bool) { if (!map.is_keep(my_leader.get_location())) { set_error(E_LEADER_NOT_ON_KEEP); return false; } return true; }
map_location pathfind::find_vacant_tile(const gamemap& map, const unit_map& units, const map_location& loc, pathfind::VACANT_TILE_TYPE vacancy, const unit* pass_check) { if (!map.on_board(loc)) return map_location(); std::set<map_location> pending_tiles_to_check, tiles_checked; pending_tiles_to_check.insert(loc); // Iterate out 50 hexes from loc for (int distance = 0; distance < 50; ++distance) { if (pending_tiles_to_check.empty()) return map_location(); //Copy over the hexes to check and clear the old set std::set<map_location> tiles_checking; tiles_checking.swap(pending_tiles_to_check); //Iterate over all the hexes we need to check foreach (const map_location &loc, tiles_checking) { //If the unit cannot reach this area or it's not a castle but should, skip it. if ((vacancy == pathfind::VACANT_CASTLE && !map.is_castle(loc)) || (pass_check && pass_check->movement_cost(map[loc]) == unit_movement_type::UNREACHABLE)) continue; //If the hex is empty, return it. if (units.find(loc) == units.end()) return loc; map_location adjs[6]; get_adjacent_tiles(loc,adjs); foreach (const map_location &loc, adjs) { if (!map.on_board(loc)) continue; // Add the tile to be checked if it hasn't already been and // isn't being checked. if (tiles_checked.find(loc) == tiles_checked.end() && tiles_checking.find(loc) == tiles_checking.end()) { pending_tiles_to_check.insert(loc); } } } tiles_checked.swap(tiles_checking); } return map_location(); }
map_location pathfind::find_vacant_tile(const gamemap& map, const std::vector<team>& teams, const unit_map& units, const map_location& loc, const unit* pass_check) { if (!map.on_board(loc)) return map_location(); std::set<map_location> pending_tiles_to_check, tiles_checked; pending_tiles_to_check.insert(loc); // Iterate out 50 hexes from loc for (int distance = 0; distance < 50; ++distance) { //Copy over the hexes to check and clear the old set std::set<map_location> tiles_checking; tiles_checking.swap(pending_tiles_to_check); //Iterate over all the hexes we need to check BOOST_FOREACH (const map_location &loc, tiles_checking) { tiles_checked.insert(loc); // If the unit cannot reach this area or it's not a castle but should, skip it. if (!pass_check || can_generate(map, teams, units, *pass_check, loc)) { // If the hex is empty, return it. if (units.find(loc) == units.end()) { return loc; } } map_location adjs[6]; get_adjacent_tiles(loc, adjs); BOOST_FOREACH (const map_location &loc, adjs) { if (!map.on_board(loc)) continue; // Add the tile to be checked if it hasn't already been and // isn't being checked. if (tiles_checked.find(loc) == tiles_checked.end()) { pending_tiles_to_check.insert(loc); } } } }
void tod_manager::add_time_area(const gamemap & map, const config& cfg) { areas_.push_back(area_time_of_day()); area_time_of_day &area = areas_.back(); area.id = cfg["id"].str(); area.xsrc = cfg["x"].str(); area.ysrc = cfg["y"].str(); area.currentTime = cfg["current_time"].to_int(0); std::vector<map_location> const& locs (map.parse_location_range(area.xsrc, area.ysrc, true)); area.hexes.insert(locs.begin(), locs.end()); time_of_day::parse_times(cfg, area.times); has_tod_bonus_changed_ = true; }
/** * Function that will add to @a result all elements of @a locs, plus all * on-board locations matching @a pred that are connected to elements of * locs by a chain of at most @a radius tiles, each of which matches @a pred. * @a result must be a std::set of locations. */ void get_tiles_radius(gamemap const &map, std::vector<map_location> const &locs, size_t radius, std::set<map_location> &result, bool with_border, xy_pred const &pred) { typedef std::set<map_location> location_set; location_set must_visit, filtered_out; location_set not_visited(locs.begin(), locs.end()); for ( ; radius != 0 && !not_visited.empty(); --radius ) { location_set::const_iterator it = not_visited.begin(); location_set::const_iterator it_end = not_visited.end(); result.insert(it, it_end); for(; it != it_end; ++it) { map_location adj[6]; get_adjacent_tiles(*it, adj); for(size_t i = 0; i != 6; ++i) { map_location const &loc = adj[i]; if ( with_border ? map.on_board_with_border(loc) : map.on_board(loc) ) { if ( !result.count(loc) && !filtered_out.count(loc) ) { if ( pred(loc) ) must_visit.insert(loc); else filtered_out.insert(loc); } } } } not_visited.swap(must_visit); must_visit.clear(); } result.insert(not_visited.begin(), not_visited.end()); }
bool recruit_result::test_suitable_recruit_location(const gamemap &map, const unit_map &units, const unit &my_leader, bool) { recruit_location_ = where_; //if we have not-on-board location, such as null_location, then the caller wants us to recruit on 'any' possible tile. if (!map.on_board(recruit_location_)) { recruit_location_ = pathfind::find_vacant_tile(map, units, my_leader.get_location(), pathfind::VACANT_CASTLE); } if (!can_recruit_on(map, my_leader.get_location(), recruit_location_)) { set_error(E_BAD_RECRUIT_LOCATION); return false; } return true; }
static int placing_score(const config& side, const gamemap& map, const map_location& pos) { int positions = 0, liked = 0; const t_translation::t_list terrain = t_translation::read_list(side["terrain_liked"]); for(int i = pos.x-8; i != pos.x+8; ++i) { for(int j = pos.y-8; j != pos.y+8; ++j) { const map_location pos(i,j); if(map.on_board(pos)) { ++positions; if(std::count(terrain.begin(),terrain.end(),map[pos])) { ++liked; } } } } return (100*liked)/positions; }
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; }
void map_fragment::paste_into(gamemap& map, const map_location& loc) const { for (const tile_info& i : items_) { map.set_terrain(i.offset.vector_sum(loc), i.terrain); } }
const time_of_day tod_manager::get_illuminated_time_of_day(const unit_map & units, const gamemap & map, const map_location& loc, int for_turn) const { // get ToD ignoring illumination time_of_day tod = get_time_of_day(loc, for_turn); if ( map.on_board_with_border(loc) ) { // Now add terrain illumination. const int terrain_light = map.get_terrain_info(loc).light_bonus(tod.lawful_bonus); std::vector<int> mod_list; std::vector<int> max_list; std::vector<int> min_list; int most_add = 0; int most_sub = 0; // Find the "illuminates" effects from units that can affect loc. map_location locs[7]; locs[0] = loc; get_adjacent_tiles(loc,locs+1); for ( size_t i = 0; i != 7; ++i ) { const unit_map::const_iterator itor = units.find(locs[i]); if (itor != units.end() && itor->get_ability_bool("illuminates") && !itor->incapacitated()) { unit_ability_list illum = itor->get_abilities("illuminates"); unit_abilities::effect illum_effect(illum, terrain_light, false); const int unit_mod = illum_effect.get_composite_value(); // Record this value. mod_list.push_back(unit_mod); max_list.push_back(illum.highest("max_value").first); min_list.push_back(illum.lowest("min_value").first); if ( unit_mod > most_add ) most_add = unit_mod; else if ( unit_mod < most_sub ) most_sub = unit_mod; } } const bool net_darker = most_add < -most_sub; // Apply each unit's effect, tracking the best result. int best_result = terrain_light; const int base_light = terrain_light + (net_darker ? most_add : most_sub); for ( size_t i = 0; i != mod_list.size(); ++i ) { int result = bounded_add(base_light, mod_list[i], max_list[i], min_list[i]); if ( net_darker && result < best_result ) best_result = result; else if ( !net_darker && result > best_result ) best_result = result; } // Update the object we will return. tod.bonus_modified = best_result - tod.lawful_bonus; tod.lawful_bonus = best_result; } return tod; }
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); } } }
surface getMinimap(int w, int h, const gamemap &map, const team *vw) { const int scale = 8; DBG_DP << "creating minimap " << int(map.w()*scale*0.75) << "," << int(map.h()*scale) << "\n"; const size_t map_width = map.w()*scale*3/4; const size_t map_height = map.h()*scale; if(map_width == 0 || map_height == 0) { return surface(NULL); } surface minimap(create_neutral_surface(map_width, map_height)); if(minimap == NULL) return surface(NULL); typedef mini_terrain_cache_map cache_map; cache_map *normal_cache = &mini_terrain_cache; cache_map *fog_cache = &mini_fogged_terrain_cache; for(int y = 0; y != map.total_height(); ++y) { for(int x = 0; x != map.total_width(); ++x) { surface surf(NULL); const map_location loc(x,y); if(map.on_board(loc)) { const bool shrouded = vw != NULL && vw->shrouded(loc); // shrouded hex are not considered fogged (no need to fog a black image) const bool fogged = vw != NULL && !shrouded && vw->fogged(loc); const t_translation::t_terrain terrain = shrouded ? t_translation::VOID_TERRAIN : map[loc]; bool need_fogging = false; cache_map* cache = fogged ? fog_cache : normal_cache; cache_map::iterator i = cache->find(terrain); if (fogged && i == cache->end()) { // we don't have the fogged version in cache // try the normal cache and ask fogging the image cache = normal_cache; i = cache->find(terrain); need_fogging = true; } if(i == cache->end()) { surface tile(get_image("terrain/" + map.get_terrain_info(terrain).minimap_image() + ".png",image::HEXED)); if(tile == 0) { utils::string_map symbols; symbols["terrain"] = t_translation::write_terrain_code(terrain); const std::string msg = vgettext("Could not get image for terrain: $terrain.", symbols); VALIDATE(false, msg); } //Compose images of base and overlay if neccessary if(map.get_terrain_info(terrain).is_combined()) { surface overlay(get_image("terrain/" + map.get_terrain_info(terrain).minimap_image_overlay() + ".png", image::HEXED)); if(overlay != 0 && overlay != tile) { surface combined = create_compatible_surface(tile, tile->w, tile->h); SDL_Rect r; r.x = 0; r.y = 0; SDL_BlitSurface(tile, NULL, combined, &r); r.x = std::max(0, (tile->w - overlay->w)/2); r.y = std::max(0, (tile->h - overlay->h)/2); if ((overlay->flags & SDL_RLEACCEL) == 0) { blit_surface(overlay, NULL, combined, &r); } else { WRN_DP << map.get_terrain_info(terrain).minimap_image_overlay() << ".png overlay is RLE-encoded, creating a neutral surface\n"; surface overlay_neutral = make_neutral_surface(overlay); blit_surface(overlay_neutral, NULL, combined, &r); } tile = combined; } } surf = surface(scale_surface_blended(tile,scale,scale)); VALIDATE(surf != NULL, _("Error creating or aquiring an image.")); i = normal_cache->insert(cache_map::value_type(terrain,surf)).first; } surf = i->second; if (need_fogging) { surf = surface(adjust_surface_colour(surf,-50,-50,-50)); fog_cache->insert(cache_map::value_type(terrain,surf)); } VALIDATE(surf != NULL, _("Error creating or aquiring an image.")); // we need a balanced shift up and down of the hexes. // if not, only the bottom half-hexes are clipped // and it looks asymmetrical. // also do 1-pixel shift because the scaling // function seems to do it with its rounding SDL_Rect maprect = {x * scale*3/4 - 1, y*scale + scale/4 * (is_odd(x) ? 1 : -1) - 1, 0, 0}; SDL_BlitSurface(surf, NULL, minimap, &maprect); } } } double wratio = w*1.0 / minimap->w; double hratio = h*1.0 / minimap->h; double ratio = std::min<double>(wratio, hratio); minimap = scale_surface(minimap, static_cast<int>(minimap->w * ratio), static_cast<int>(minimap->h * ratio)); DBG_DP << "done generating minimap\n"; return minimap; }
void attack_analysis::analyze(const gamemap& map, unit_map& units, std::map<std::pair<const unit*, const unit_type*>, battle_context*>& unit_stats_cache, const std::vector<std::pair<unit*, int> >& units2, const std::multimap<map_location, int>& srcdst2, const std::multimap<int, map_location>& dstsrc2, std::map<unit*, std::pair<map_location, unit*>* >& reside_cache, double aggression) { target_value = target->cost(); target_value += 0.5 * (double(target->experience()) / double(target->max_experience())) * target_value; target_starting_damage = target->max_hitpoints() - target->hitpoints(); VALIDATE(!movements.empty(), _("ai::attack_analisis::analyze, movements is empty.")); std::pair<std::pair<unit*, int>, map_location>& m = movements.back(); map_location orig_loc = m.first.first->get_location(); std::pair<map_location, unit*>* up; if (m.first.second < 0) { // We fix up units map to reflect what this would look like. if (m.second == orig_loc) { up = units.get_cookie(m.second, !m.first.first->base()); } else { up = units.extract(orig_loc); up->first = m.second; units.place(up); } } else { // units.add(m.second, m.first.first); std::map<unit*, std::pair<map_location, unit*>* >::iterator cache_itor = reside_cache.find(m.first.first); if (cache_itor == reside_cache.end()) { up = new std::pair<map_location, unit*>(m.second, m.first.first); reside_cache[m.first.first] = up; } else { up = cache_itor->second; } up->first = m.second; units.place(up); } unit_map::node* base = units.get_cookie(m.second, false); bool on_wall = base && base->second->wall(); int att_weapon = -1, def_weapon = -1; bool from_cache = false; battle_context *bc; const unit* src_ptr = up->second; // This cache is only about 99% correct, but speeds up evaluation by about 1000 times. // We recalculate when we actually attack. const unit_type* src_type; if (!src_ptr->packed()) { src_type = src_ptr->type(); } else { src_type = unit_types.find(src_ptr->packee_type_id()); } std::map<std::pair<const unit*, const unit_type*>, battle_context*>::iterator usc; usc = unit_stats_cache.find(std::pair<const unit*, const unit_type*>(target, src_type)); // Just check this attack is valid for this attacking unit (may be modified) if (usc != unit_stats_cache.end() && usc->second->get_attacker_stats().attack_num < static_cast<int>(src_ptr->attacks().size())) { from_cache = true; bc = usc->second; } else { VALIDATE(false, _("ai::attack_analisis::analyze, cannot find unit_type pair in usc.")); } // because save battle_context into usc direct, don't support prev_def. const combatant &att = bc->get_attacker_combatant(NULL); const combatant &def = bc->get_defender_combatant(NULL); // Note we didn't fight at all if defender is already dead. double prob_fought = (1.0 - prob_dead_already); // @todo 1.8 add combatant.prob_killed double prob_killed = def.dead_ - prob_dead_already; prob_dead_already = def.dead_; double prob_died = att.dead_; double prob_survived = (1.0 - prob_died) * prob_fought; double cost = up->second->cost(); const bool on_village = map.is_village(m.second); // Up to double the value of a unit based on experience cost += 0.5 * (double(up->second->experience())/double(up->second->max_experience())) * cost; // We should encourage multi-phase attack. cost /= movements.size(); resources_used += cost; avg_losses += cost * prob_died; // add half of cost for poisoned unit so it might get chance to heal avg_losses += cost * up->second->get_state(unit::STATE_POISONED) /2; // Double reward to emphasize getting onto villages if they survive. if (on_village) { avg_damage_taken -= game_config::poison_amount*2 * prob_survived; } double quality = (double(bc->get_defender_stats().chance_to_hit)/100.0) * cost * (on_village ? 0.5 : 1.0); if (on_wall) { quality *= 0.2; } terrain_quality += quality; double advance_prob = 0.0; // The reward for advancing a unit is to get a 'negative' loss of that unit if (!up->second->advances_to().empty()) { int xp_for_advance = up->second->max_experience() - up->second->experience(); int kill_xp, fight_xp; // See bug #6272... in some cases, unit already has got enough xp to advance, // but hasn't (bug elsewhere?). Can cause divide by zero. if (xp_for_advance <= 0) xp_for_advance = 1; fight_xp = target->level(); kill_xp = fight_xp ? fight_xp * game_config::kill_experience : game_config::kill_experience / 2; if (fight_xp >= xp_for_advance) { advance_prob = prob_fought; avg_losses -= up->second->cost() * prob_fought; } else if (kill_xp >= xp_for_advance) { advance_prob = prob_killed; avg_losses -= up->second->cost() * prob_killed; // The reward for getting a unit closer to advancement // (if it didn't advance) is to get the proportion of // remaining experience needed, and multiply it by // a quarter of the unit cost. // This will cause the AI to heavily favor // getting xp for close-to-advance units. avg_losses -= up->second->cost() * 0.25 * fight_xp * (prob_fought - prob_killed) / xp_for_advance; } else { avg_losses -= up->second->cost() * 0.25 * (kill_xp * prob_killed + fight_xp * (prob_fought - prob_killed)) / xp_for_advance; } // The reward for killing with a unit that plagues // is to get a 'negative' loss of that unit. if (bc->get_attacker_stats().plagues) { avg_losses -= prob_killed * up->second->cost(); } } // If we didn't advance, we took this damage. avg_damage_taken += (up->second->hitpoints() - att.average_hp()) * (1.0 - advance_prob); // // @todo 1.8: attack_prediction.cpp should understand advancement // directly. For each level of attacker def gets 1 xp or // kill_experience. // int fight_xp = up->second->level(); int kill_xp = fight_xp ? fight_xp * game_config::kill_experience : game_config::kill_experience / 2; def_avg_experience += fight_xp * (1.0 - att.dead_) + kill_xp * att.dead_; if (!target->advances_to().empty() && def_avg_experience >= target->max_experience() - target->experience()) { // It's likely to advance: only if we can kill with first blow. if (movements.size() == 1) { chance_to_kill = def.dead_; } // Negative average damage (it will advance). avg_damage_inflicted += target->hitpoints() - target->max_hitpoints(); } else { chance_to_kill += def.dead_; avg_damage_inflicted += target->hitpoints() - def.average_hp(map.gives_healing(target->get_location())); } // Restore the units to their original positions. if (m.first.second < 0) { if (m.second != orig_loc) { units.move(m.second, orig_loc); } } else { units.extract(m.second); // place() will update loc_ of up->second, it is time to be back to orig_loc. up->second->set_location(orig_loc); // delete up; } }
bool editor_map::same_size_as(const gamemap& other) const { return h() == other.h() && w() == other.w(); }
surface getMinimap(int w, int h, const gamemap &map, const team *vw) { const int scale = 8; DBG_DP << "creating minimap " << int(map.w()*scale*0.75) << "," << map.h()*scale << "\n"; const size_t map_width = map.w()*scale*3/4; const size_t map_height = map.h()*scale; if(map_width == 0 || map_height == 0) { return surface(NULL); } surface minimap(create_neutral_surface(map_width, map_height)); if(minimap == NULL) return surface(NULL); typedef mini_terrain_cache_map cache_map; cache_map *normal_cache = &mini_terrain_cache; cache_map *fog_cache = &mini_fogged_terrain_cache; for(int y = 0; y != map.total_height(); ++y) { for(int x = 0; x != map.total_width(); ++x) { surface surf(NULL); const map_location loc(x,y); if(map.on_board(loc)) { const bool shrouded = (vw != NULL && vw->shrouded(loc)); // shrouded hex are not considered fogged (no need to fog a black image) const bool fogged = (vw != NULL && !shrouded && vw->fogged(loc)); const t_translation::t_terrain terrain = shrouded ? t_translation::VOID_TERRAIN : map[loc]; const terrain_type& terrain_info = map.get_terrain_info(terrain); bool need_fogging = false; cache_map* cache = fogged ? fog_cache : normal_cache; cache_map::iterator i = cache->find(terrain); if (fogged && i == cache->end()) { // we don't have the fogged version in cache // try the normal cache and ask fogging the image cache = normal_cache; i = cache->find(terrain); need_fogging = true; } if(i == cache->end()) { std::string base_file = "terrain/" + terrain_info.minimap_image() + ".png"; surface tile = get_image(base_file,image::HEXED); //Compose images of base and overlay if necessary // NOTE we also skip overlay when base is missing (to avoid hiding the error) if(tile != NULL && map.get_terrain_info(terrain).is_combined()) { std::string overlay_file = "terrain/" + terrain_info.minimap_image_overlay() + ".png"; surface overlay = get_image(overlay_file,image::HEXED); if(overlay != NULL && overlay != tile) { surface combined = create_neutral_surface(tile->w, tile->h); SDL_Rect r = create_rect(0,0,0,0); sdl_blit(tile, NULL, combined, &r); r.x = std::max(0, (tile->w - overlay->w)/2); r.y = std::max(0, (tile->h - overlay->h)/2); //blit_surface needs neutral surface surface overlay_neutral = make_neutral_surface(overlay); blit_surface(overlay_neutral, NULL, combined, &r); tile = combined; } } surf = scale_surface_sharp(tile, scale, scale); i = normal_cache->insert(cache_map::value_type(terrain,surf)).first; } surf = i->second; if (need_fogging) { surf = adjust_surface_color(surf,-50,-50,-50); fog_cache->insert(cache_map::value_type(terrain,surf)); } // we need a balanced shift up and down of the hexes. // if not, only the bottom half-hexes are clipped // and it looks asymmetrical. // also do 1-pixel shift because the scaling // function seems to do it with its rounding SDL_Rect maprect = create_rect( x * scale * 3 / 4 - 1 , y * scale + scale / 4 * (is_odd(x) ? 1 : -1) - 1 , 0 , 0); if(surf != NULL) sdl_blit(surf, NULL, minimap, &maprect); } } } double wratio = w*1.0 / minimap->w; double hratio = h*1.0 / minimap->h; double ratio = std::min<double>(wratio, hratio); minimap = scale_surface_sharp(minimap, static_cast<int>(minimap->w * ratio), static_cast<int>(minimap->h * ratio)); DBG_DP << "done generating minimap\n"; return minimap; }
void attack_analysis::analyze(const gamemap& map, unit_map& units, const readonly_context& ai_obj, const move_map& dstsrc, const move_map& srcdst, const move_map& enemy_dstsrc, double aggression) { const unit_map::const_iterator defend_it = units.find(target); assert(defend_it != units.end()); // See if the target is a threat to our leader or an ally's leader. map_location adj[6]; get_adjacent_tiles(target,adj); size_t tile; for(tile = 0; tile != 6; ++tile) { const unit_map::const_iterator leader = units.find(adj[tile]); if(leader != units.end() && leader->can_recruit() && !ai_obj.current_team().is_enemy(leader->side())) { break; } } leader_threat = (tile != 6); uses_leader = false; target_value = defend_it->cost(); target_value += (double(defend_it->experience())/ double(defend_it->max_experience()))*target_value; target_starting_damage = defend_it->max_hitpoints() - defend_it->hitpoints(); // Calculate the 'alternative_terrain_quality' -- the best possible defensive values // the attacking units could hope to achieve if they didn't attack and moved somewhere. // This is used for comparative purposes, to see just how vulnerable the AI is // making itself. alternative_terrain_quality = 0.0; double cost_sum = 0.0; for(size_t i = 0; i != movements.size(); ++i) { const unit_map::const_iterator att = units.find(movements[i].first); const double cost = att->cost(); cost_sum += cost; alternative_terrain_quality += cost*ai_obj.best_defensive_position(movements[i].first,dstsrc,srcdst,enemy_dstsrc).chance_to_hit; } alternative_terrain_quality /= cost_sum*100; avg_damage_inflicted = 0.0; avg_damage_taken = 0.0; resources_used = 0.0; terrain_quality = 0.0; avg_losses = 0.0; chance_to_kill = 0.0; double def_avg_experience = 0.0; double first_chance_kill = 0.0; double prob_dead_already = 0.0; assert(!movements.empty()); std::vector<std::pair<map_location,map_location> >::const_iterator m; battle_context *prev_bc = NULL; const combatant *prev_def = NULL; for (m = movements.begin(); m != movements.end(); ++m) { // We fix up units map to reflect what this would look like. unit_ptr up = units.extract(m->first); up->set_location(m->second); units.insert(up); double m_aggression = aggression; if (up->can_recruit()) { uses_leader = true; // FIXME: suokko's r29531 omitted this line leader_threat = false; m_aggression = ai_obj.get_leader_aggression(); } bool from_cache = false; battle_context *bc; // This cache is only about 99% correct, but speeds up evaluation by about 1000 times. // We recalculate when we actually attack. const readonly_context::unit_stats_cache_t::key_type cache_key = std::make_pair(target, &up->type()); const readonly_context::unit_stats_cache_t::iterator usc = ai_obj.unit_stats_cache().find(cache_key); // Just check this attack is valid for this attacking unit (may be modified) if (usc != ai_obj.unit_stats_cache().end() && usc->second.first.attack_num < static_cast<int>(up->attacks().size())) { from_cache = true; bc = new battle_context(usc->second.first, usc->second.second); } else { bc = new battle_context(units, m->second, target, -1, -1, m_aggression, prev_def); } const combatant &att = bc->get_attacker_combatant(prev_def); const combatant &def = bc->get_defender_combatant(prev_def); delete prev_bc; prev_bc = bc; prev_def = &bc->get_defender_combatant(prev_def); if ( !from_cache ) { ai_obj.unit_stats_cache().insert( std::make_pair(cache_key, std::make_pair(bc->get_attacker_stats(), bc->get_defender_stats()))); } // Note we didn't fight at all if defender is already dead. double prob_fought = (1.0 - prob_dead_already); /** @todo 1.9 add combatant.prob_killed */ double prob_killed = def.hp_dist[0] - prob_dead_already; prob_dead_already = def.hp_dist[0]; double prob_died = att.hp_dist[0]; double prob_survived = (1.0 - prob_died) * prob_fought; double cost = up->cost(); const bool on_village = map.is_village(m->second); // Up to double the value of a unit based on experience cost += (double(up->experience()) / up->max_experience())*cost; resources_used += cost; avg_losses += cost * prob_died; // add half of cost for poisoned unit so it might get chance to heal avg_losses += cost * up->get_state(unit::STATE_POISONED) /2; if (!bc->get_defender_stats().is_poisoned) { avg_damage_inflicted += game_config::poison_amount * 2 * bc->get_defender_combatant().poisoned * (1 - prob_killed); } // Double reward to emphasize getting onto villages if they survive. if (on_village) { avg_damage_taken -= game_config::poison_amount*2 * prob_survived; } terrain_quality += (double(bc->get_defender_stats().chance_to_hit)/100.0)*cost * (on_village ? 0.5 : 1.0); double advance_prob = 0.0; // The reward for advancing a unit is to get a 'negative' loss of that unit if (!up->advances_to().empty()) { int xp_for_advance = up->max_experience() - up->experience(); // See bug #6272... in some cases, unit already has got enough xp to advance, // but hasn't (bug elsewhere?). Can cause divide by zero. if (xp_for_advance <= 0) xp_for_advance = 1; int fight_xp = defend_it->level(); int kill_xp = game_config::kill_xp(fight_xp); if (fight_xp >= xp_for_advance) { advance_prob = prob_fought; avg_losses -= up->cost() * prob_fought; } else if (kill_xp >= xp_for_advance) { advance_prob = prob_killed; avg_losses -= up->cost() * prob_killed; // The reward for getting a unit closer to advancement // (if it didn't advance) is to get the proportion of // remaining experience needed, and multiply it by // a quarter of the unit cost. // This will cause the AI to heavily favor // getting xp for close-to-advance units. avg_losses -= up->cost() * 0.25 * fight_xp * (prob_fought - prob_killed) / xp_for_advance; } else { avg_losses -= up->cost() * 0.25 * (kill_xp * prob_killed + fight_xp * (prob_fought - prob_killed)) / xp_for_advance; } // The reward for killing with a unit that plagues // is to get a 'negative' loss of that unit. if (bc->get_attacker_stats().plagues) { avg_losses -= prob_killed * up->cost(); } } // If we didn't advance, we took this damage. avg_damage_taken += (up->hitpoints() - att.average_hp()) * (1.0 - advance_prob); /** * @todo 1.9: attack_prediction.cpp should understand advancement * directly. For each level of attacker def gets 1 xp or * kill_experience. */ int fight_xp = up->level(); int kill_xp = game_config::kill_xp(fight_xp); def_avg_experience += fight_xp * (1.0 - att.hp_dist[0]) + kill_xp * att.hp_dist[0]; if (m == movements.begin()) { first_chance_kill = def.hp_dist[0]; } } if (!defend_it->advances_to().empty() && def_avg_experience >= defend_it->max_experience() - defend_it->experience()) { // It's likely to advance: only if we can kill with first blow. chance_to_kill = first_chance_kill; // Negative average damage (it will advance). avg_damage_inflicted += defend_it->hitpoints() - defend_it->max_hitpoints(); } else { chance_to_kill = prev_def->hp_dist[0]; avg_damage_inflicted += defend_it->hitpoints() - prev_def->average_hp(map.gives_healing(defend_it->get_location())); } delete prev_bc; terrain_quality /= resources_used; // Restore the units to their original positions. for (m = movements.begin(); m != movements.end(); ++m) { units.move(m->second, m->first); } }
/** * Create a tile info -- the constructor grabs required data from the map */ tile_info(const gamemap& map, const map_location& offset) : offset(offset), terrain(map.get_terrain(offset)) { }
void gamemap::overlay(const gamemap& m, const config& rules_cfg, int xpos, int ypos, bool border) { const config::const_child_itors &rules = rules_cfg.child_range("rule"); int actual_border = (m.border_size() == border_size()) && border ? border_size() : 0; const int xstart = std::max<int>(-actual_border, -xpos - actual_border); const int ystart = std::max<int>(-actual_border, -ypos - actual_border - ((xpos & 1) ? 1 : 0)); const int xend = std::min<int>(m.w() + actual_border, w() + actual_border - xpos); const int yend = std::min<int>(m.h() + actual_border, h() + actual_border - ypos); for(int x1 = xstart; x1 < xend; ++x1) { for(int y1 = ystart; y1 < yend; ++y1) { const int x2 = x1 + xpos; const int y2 = y1 + ypos + ((xpos & 1) && (x1 & 1) ? 1 : 0); const t_translation::t_terrain t = m[x1][y1 + m.border_size_]; const t_translation::t_terrain current = (*this)[x2][y2 + border_size_]; if(t == t_translation::FOGGED || t == t_translation::VOID_TERRAIN) { continue; } // See if there is a matching rule config::const_child_iterator rule = rules.first; for( ; rule != rules.second; ++rule) { static const std::string src_key = "old", dst_key = "new"; const config &cfg = *rule; const t_translation::t_list& src = t_translation::read_list(cfg[src_key]); if(!src.empty() && t_translation::terrain_matches(current, src) == false) { continue; } const t_translation::t_list& dst = t_translation::read_list(cfg[dst_key]); if(!dst.empty() && t_translation::terrain_matches(t, dst) == false) { continue; } break; } if (rule != rules.second) { const config &cfg = *rule; const t_translation::t_list& terrain = t_translation::read_list(cfg["terrain"]); terrain_type_data::tmerge_mode mode = terrain_type_data::BOTH; if (cfg["layer"] == "base") { mode = terrain_type_data::BASE; } else if (cfg["layer"] == "overlay") { mode = terrain_type_data::OVERLAY; } t_translation::t_terrain new_terrain = t; if(!terrain.empty()) { new_terrain = terrain[0]; } if (!cfg["use_old"].to_bool()) { set_terrain(map_location(x2, y2), new_terrain, mode, cfg["replace_if_failed"].to_bool()); } } else { set_terrain(map_location(x2,y2),t); } } } for(const map_location* pos = m.startingPositions_; pos != m.startingPositions_ + sizeof(m.startingPositions_)/sizeof(*m.startingPositions_); ++pos) { if(pos->valid()) { startingPositions_[pos - m.startingPositions_] = *pos; } } }