mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-06 17:43:27 +00:00
1834 lines
58 KiB
C++
1834 lines
58 KiB
C++
/* $Id$ */
|
|
/*
|
|
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
|
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY.
|
|
|
|
See the COPYING file for more details.
|
|
*/
|
|
#include "actions.hpp"
|
|
#include "ai.hpp"
|
|
#include "ai_attack.hpp"
|
|
#include "ai_move.hpp"
|
|
#include "dialogs.hpp"
|
|
#include "game_config.hpp"
|
|
#include "game_events.hpp"
|
|
#include "log.hpp"
|
|
#include "network.hpp"
|
|
#include "pathfind.hpp"
|
|
#include "playlevel.hpp"
|
|
#include "playturn.hpp"
|
|
#include "preferences.hpp"
|
|
#include "replay.hpp"
|
|
#include "show_dialog.hpp"
|
|
#include "statistics.hpp"
|
|
#include "unit_display.hpp"
|
|
#include "util.hpp"
|
|
|
|
#include <iostream>
|
|
|
|
ai_interface* create_ai(const std::string& name, ai_interface::info& info)
|
|
{
|
|
//to add an AI of your own, put
|
|
//if(name == "my_ai")
|
|
// return new my_ai(info);
|
|
//at the top of this function
|
|
|
|
if(name == "sample_ai")
|
|
return new sample_ai(info);
|
|
else if(name == "idle_ai")
|
|
return new idle_ai(info);
|
|
else if(name != "")
|
|
std::cerr << "ERROR: AI not found: '" << name << "'\n";
|
|
|
|
return new ai(info);
|
|
}
|
|
|
|
ai::ai(ai_interface::info& info)
|
|
: ai_interface(info), disp_(info.disp), map_(info.map), gameinfo_(info.gameinfo), units_(info.units),
|
|
teams_(info.teams), team_num_(info.team_num), state_(info.state),
|
|
consider_combat_(true), threats_found_(false)
|
|
{}
|
|
|
|
bool ai::recruit_usage(const std::string& usage)
|
|
{
|
|
user_interact();
|
|
|
|
const int min_gold = 0;
|
|
|
|
log_scope("recruiting troops");
|
|
std::cerr << "recruiting " << usage << "\n";
|
|
|
|
std::vector<std::string> options;
|
|
|
|
//find an available unit that can be recruited, matches the desired
|
|
//usage type, and comes in under budget
|
|
const std::set<std::string>& recruits = current_team().recruits();
|
|
for(std::map<std::string,unit_type>::const_iterator i =
|
|
gameinfo_.unit_types.begin(); i != gameinfo_.unit_types.end(); ++i) {
|
|
|
|
const std::string& name = i->second.name();
|
|
|
|
if(i->second.usage() == usage && recruits.count(name)
|
|
&& current_team().gold() - i->second.cost() > min_gold
|
|
&& not_recommended_units_.count(name) == 0) {
|
|
std::cerr << "recommending '" << name << "'\n";
|
|
options.push_back(name);
|
|
}
|
|
}
|
|
|
|
//from the available options, choose one at random
|
|
if(options.empty() == false) {
|
|
const int option = rand()%options.size();
|
|
return recruit(options[option]);
|
|
}
|
|
|
|
std::cerr << "no available units to recruit that come under the price\n";
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ai_interface::recruit(const std::string& unit_name, location loc)
|
|
{
|
|
const std::set<std::string>& recruits = current_team().recruits();
|
|
const std::set<std::string>::const_iterator i = std::find(recruits.begin(),recruits.end(),unit_name);
|
|
if(i == recruits.end()) {
|
|
return false;
|
|
}
|
|
|
|
const int num = std::distance(recruits.begin(),i);
|
|
|
|
recorder.add_recruit(num,loc);
|
|
replay_undo replay_guard(recorder);
|
|
|
|
game_data::unit_type_map::const_iterator u = info_.gameinfo.unit_types.find(unit_name);
|
|
if(u == info_.gameinfo.unit_types.end()) {
|
|
return false;
|
|
}
|
|
|
|
//check we have enough money
|
|
if(current_team().gold() < u->second.cost()) {
|
|
return false;
|
|
}
|
|
|
|
unit new_unit(&u->second,info_.team_num,true);
|
|
|
|
//see if we can actually recruit (i.e. have enough room etc)
|
|
if(recruit_unit(info_.map,info_.team_num,info_.units,new_unit,loc,preferences::show_ai_moves() ? &info_.disp : NULL).empty()) {
|
|
statistics::recruit_unit(new_unit);
|
|
current_team().spend_gold(u->second.cost());
|
|
|
|
//confirm the transaction - i.e. don't undo recruitment
|
|
replay_guard.confirm_transaction();
|
|
|
|
sync_network();
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void ai_interface::user_interact()
|
|
{
|
|
const int interact_time = 30;
|
|
const int time_since_interact = SDL_GetTicks() - last_interact_;
|
|
if(time_since_interact < interact_time) {
|
|
return;
|
|
}
|
|
|
|
info_.turn_data_.turn_slice();
|
|
info_.turn_data_.send_data();
|
|
info_.disp.draw();
|
|
|
|
last_interact_ = SDL_GetTicks();
|
|
}
|
|
|
|
void ai_interface::diagnostic(const std::string& msg)
|
|
{
|
|
if(game_config::debug) {
|
|
info_.disp.set_diagnostic(msg);
|
|
}
|
|
}
|
|
|
|
void ai_interface::log_message(const std::string& msg)
|
|
{
|
|
if(game_config::debug) {
|
|
info_.disp.add_chat_message("ai",info_.team_num,msg,display::MESSAGE_PUBLIC);
|
|
}
|
|
}
|
|
|
|
team& ai_interface::current_team()
|
|
{
|
|
return info_.teams[info_.team_num-1];
|
|
}
|
|
|
|
const team& ai_interface::current_team() const
|
|
{
|
|
return info_.teams[info_.team_num-1];
|
|
}
|
|
|
|
void ai_interface::sync_network()
|
|
{
|
|
if(network::nconnections() > 0) {
|
|
|
|
//receive data first, and then send data. When we sent the end of
|
|
//the AI's turn, we don't want there to be any chance where we
|
|
//could get data back pertaining to the next turn.
|
|
config cfg;
|
|
while(network::connection res = network::receive_data(cfg)) {
|
|
std::deque<config> backlog;
|
|
info_.turn_data_.process_network_data(cfg,res,backlog);
|
|
cfg.clear();
|
|
}
|
|
|
|
info_.turn_data_.send_data();
|
|
}
|
|
}
|
|
|
|
gamemap::location ai_interface::move_unit(location from, location to, std::map<location,paths>& possible_moves)
|
|
{
|
|
std::cerr << "ai_interface::move_unit " << (from.x+1) << "," << (from.y+1) << " -> " << (to.x+1) << "," << (to.y+1) << "\n";
|
|
//stop the user from issuing any commands while the unit is moving
|
|
const command_disabler disable_commands(&info_.disp);
|
|
|
|
assert(info_.units.find(to) == info_.units.end() || from == to);
|
|
|
|
info_.disp.select_hex(from);
|
|
info_.disp.update_display();
|
|
|
|
log_scope("move_unit");
|
|
unit_map::iterator u_it = info_.units.find(from);
|
|
if(u_it == info_.units.end()) {
|
|
std::cerr << "Could not find unit at " << from.x << ", "
|
|
<< from.y << "\n";
|
|
assert(false);
|
|
return location();
|
|
}
|
|
|
|
if(from == to) {
|
|
std::cerr << "moving unit at " << (from.x+1) << "," << (from.y+1) << " on spot. resetting moves\n";
|
|
u_it->second.set_movement(0);
|
|
return to;
|
|
}
|
|
|
|
const bool show_move = preferences::show_ai_moves();
|
|
|
|
const bool ignore_zocs = u_it->second.type().is_skirmisher();
|
|
const bool teleport = u_it->second.type().teleports();
|
|
paths current_paths(info_.map,info_.state,info_.gameinfo,info_.units,from,info_.teams,ignore_zocs,teleport);
|
|
paths_wiper wiper(info_.disp);
|
|
|
|
if(show_move) {
|
|
if(!info_.disp.fogged(from.x,from.y))
|
|
info_.disp.set_paths(¤t_paths);
|
|
|
|
info_.disp.scroll_to_tiles(from.x,from.y,to.x,to.y);
|
|
}
|
|
|
|
unit current_unit = u_it->second;
|
|
info_.units.erase(u_it);
|
|
|
|
const std::map<location,paths>::iterator p_it = possible_moves.find(from);
|
|
|
|
bool ambushed = false;
|
|
if(p_it != possible_moves.end()) {
|
|
paths& p = p_it->second;
|
|
std::map<location,paths::route>::iterator rt = p.routes.begin();
|
|
for(; rt != p.routes.end(); ++rt) {
|
|
if(rt->first == to) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(rt != p.routes.end()) {
|
|
std::vector<location> steps = rt->second.steps;
|
|
|
|
if(steps.empty() == false) {
|
|
//check if there are any invisible units that we uncover
|
|
for(std::vector<location>::iterator i = steps.begin()+1; i != steps.end(); ++i) {
|
|
location adj[6];
|
|
get_adjacent_tiles(*i,adj);
|
|
size_t n;
|
|
for(n = 0; n != 6; ++n) {
|
|
|
|
//see if there is an enemy unit next to this tile. Note that we don't
|
|
//actually have to check if it's invisible, since it being invisible is
|
|
//the only possibility
|
|
const unit_map::const_iterator invisible_unit = info_.units.find(adj[n]);
|
|
if(!ignore_zocs && invisible_unit != info_.units.end() && invisible_unit->second.stone() == false &&
|
|
current_team().is_enemy(invisible_unit->second.side())) {
|
|
to = *i;
|
|
steps.erase(i,steps.end());
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(n != 6) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
steps.push_back(to); //add the destination to the steps
|
|
|
|
if(show_move) {
|
|
unit_display::move_unit(info_.disp,info_.map,steps,current_unit);
|
|
}
|
|
}
|
|
}
|
|
|
|
current_unit.set_movement(0);
|
|
info_.units.insert(std::pair<location,unit>(to,current_unit));
|
|
if(info_.map.is_village(to)) {
|
|
get_village(to,info_.teams,info_.team_num-1,info_.units);
|
|
}
|
|
|
|
if(show_move) {
|
|
info_.disp.draw_tile(to.x,to.y);
|
|
info_.disp.draw();
|
|
}
|
|
|
|
game_events::fire("moveto",to);
|
|
|
|
if((info_.teams.front().uses_fog() || info_.teams.front().uses_shroud()) && !info_.teams.front().fogged(to.x,to.y)) {
|
|
game_events::fire("sighted",to);
|
|
}
|
|
|
|
recorder.add_movement(from,to);
|
|
|
|
sync_network();
|
|
|
|
return to;
|
|
}
|
|
|
|
bool ai::multistep_move_possible(location from, location to, location via, std::map<location,paths>& possible_moves)
|
|
{
|
|
const unit_map::const_iterator i = units_.find(from);
|
|
if(i != units_.end()) {
|
|
if(from != via && to != via && units_.count(via) == 0) {
|
|
std::cerr << "when seeing if leader can move from " << (from.x+1) << "," << (from.y+1) << " -> " << (to.x+1) << "," << (to.y+1) << " seeing if can detour to keep at " << (via.x+1) << "," << (via.y+1) << "\n";
|
|
const std::map<location,paths>::const_iterator moves = possible_moves.find(from);
|
|
if(moves != possible_moves.end()) {
|
|
|
|
std::cerr << "found leader moves..\n";
|
|
|
|
bool can_make_it = false;
|
|
int move_left = 0;
|
|
|
|
//see if the unit can make it to 'via', and if it can, how much movement it will have
|
|
//left when it gets there.
|
|
const paths::routes_map::const_iterator itor = moves->second.routes.find(via);
|
|
if(itor != moves->second.routes.end()) {
|
|
move_left = itor->second.move_left;
|
|
std::cerr << "can make it to keep with " << move_left << " movement left\n";
|
|
unit temp_unit(i->second);
|
|
temp_unit.set_movement(move_left);
|
|
const temporary_unit_placer unit_placer(units_,via,temp_unit);
|
|
const paths unit_paths(map_,state_,gameinfo_,units_,via,teams_,false,false);
|
|
|
|
std::cerr << "found " << unit_paths.routes.size() << " moves for temp leader\n";
|
|
|
|
//see if this leader could make it back to the keep
|
|
if(unit_paths.routes.count(to) != 0) {
|
|
std::cerr << "can make it back to the keep\n";
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
gamemap::location ai::move_unit(location from, location to, std::map<location,paths>& possible_moves)
|
|
{
|
|
std::map<location,paths> temp_possible_moves;
|
|
std::map<location,paths>* possible_moves_ptr = &possible_moves;
|
|
|
|
const unit_map::const_iterator i = units_.find(from);
|
|
if(i != units_.end() && i->second.can_recruit()) {
|
|
|
|
//if the leader isn't on its keep, and we can move to the keep and still make our planned
|
|
//movement, then try doing that.
|
|
const gamemap::location& start_pos = nearest_keep(i->first);
|
|
|
|
//if we can make it back to the keep and then to our original destination, do so.
|
|
if(multistep_move_possible(from,to,start_pos,possible_moves)) {
|
|
from = ai_interface::move_unit(from,start_pos,possible_moves);
|
|
if(from != start_pos) {
|
|
return from;
|
|
}
|
|
|
|
const unit_map::iterator itor = units_.find(from);
|
|
if(itor != units_.end()) {
|
|
//just set the movement to one less than the maximum possible, since we know we
|
|
//can reach the destination, and we're giong to move there immediately
|
|
itor->second.set_movement(itor->second.total_movement()-1);
|
|
}
|
|
|
|
move_map srcdst, dstsrc;
|
|
calculate_possible_moves(temp_possible_moves,srcdst,dstsrc,false);
|
|
possible_moves_ptr = &temp_possible_moves;
|
|
}
|
|
|
|
do_recruitment();
|
|
}
|
|
|
|
if(units_.count(to) == 0 || from == to) {
|
|
const location res = ai_interface::move_unit(from,to,*possible_moves_ptr);
|
|
if(res != to) {
|
|
//we've been ambushed; find the ambushing unit and attack them
|
|
adjacent_tiles_array locs;
|
|
get_adjacent_tiles(res,locs.data());
|
|
for(adjacent_tiles_array::const_iterator i = locs.begin(); i != locs.end(); ++i) {
|
|
const unit_map::const_iterator itor = units_.find(*i);
|
|
if(itor != units_.end() && current_team().is_enemy(itor->second.side()) &&
|
|
!itor->second.incapacitated()) {
|
|
battle_stats stats;
|
|
const int weapon = choose_weapon(res,itor->first,stats,0);
|
|
attack_enemy(res,itor->first,weapon);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return res;
|
|
} else {
|
|
return from;
|
|
}
|
|
}
|
|
|
|
bool ai::attack_close(const gamemap::location& loc) const
|
|
{
|
|
for(std::set<location>::const_iterator i = attacks_.begin(); i != attacks_.end(); ++i) {
|
|
if(distance_between(*i,loc) < 4) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ai::attack_enemy(const location& attacking_unit, const location& target, int weapon)
|
|
{
|
|
attacks_.insert(attacking_unit);
|
|
ai_interface::attack_enemy(attacking_unit,target,weapon);
|
|
}
|
|
|
|
void ai_interface::calculate_possible_moves(std::map<location,paths>& res, move_map& srcdst, move_map& dstsrc, bool enemy, bool assume_full_movement, const std::set<gamemap::location>* remove_destinations)
|
|
{
|
|
for(std::map<gamemap::location,unit>::iterator un_it = info_.units.begin(); un_it != info_.units.end(); ++un_it) {
|
|
//if we are looking for the movement of enemies, then this unit must be an enemy unit
|
|
//if we are looking for movement of our own units, it must be on our side.
|
|
//if we are assuming full movement, then it may be a unit on our side, or allied
|
|
if(enemy && current_team().is_enemy(un_it->second.side()) == false ||
|
|
!enemy && !assume_full_movement && un_it->second.side() != info_.team_num ||
|
|
!enemy && assume_full_movement && current_team().is_enemy(un_it->second.side())) {
|
|
continue;
|
|
}
|
|
|
|
//discount incapacitated units
|
|
if(un_it->second.incapacitated()) {
|
|
continue;
|
|
}
|
|
|
|
//we can't see where invisible enemy units might move
|
|
if(enemy && un_it->second.invisible(info_.map.underlying_terrain(info_.map.get_terrain(un_it->first)),
|
|
info_.state.get_time_of_day().lawful_bonus,un_it->first,info_.units,info_.teams)) {
|
|
continue;
|
|
}
|
|
|
|
//if it's an enemy unit, reset its moves while we do the calculations
|
|
const unit_movement_resetter move_resetter(un_it->second,enemy || assume_full_movement);
|
|
|
|
//insert the trivial moves of staying on the same location
|
|
if(un_it->second.movement_left() == un_it->second.total_movement()) {
|
|
std::pair<location,location> trivial_mv(un_it->first,un_it->first);
|
|
srcdst.insert(trivial_mv);
|
|
dstsrc.insert(trivial_mv);
|
|
}
|
|
|
|
const bool ignore_zocs = un_it->second.type().is_skirmisher();
|
|
const bool teleports = un_it->second.type().teleports();
|
|
res.insert(std::pair<gamemap::location,paths>(
|
|
un_it->first,paths(info_.map,info_.state,info_.gameinfo,info_.units,
|
|
un_it->first,info_.teams,ignore_zocs,teleports)));
|
|
}
|
|
|
|
for(std::map<location,paths>::iterator m = res.begin(); m != res.end(); ++m) {
|
|
for(paths::routes_map::iterator rtit =
|
|
m->second.routes.begin(); rtit != m->second.routes.end(); ++rtit) {
|
|
const location& src = m->first;
|
|
const location& dst = rtit->first;
|
|
|
|
if(remove_destinations != NULL && remove_destinations->count(dst) != 0) {
|
|
continue;
|
|
}
|
|
|
|
bool friend_owns = false;
|
|
|
|
//don't take friendly villages
|
|
if(!enemy && info_.map.is_village(dst)) {
|
|
for(size_t n = 0; n != info_.teams.size(); ++n) {
|
|
if(info_.teams[n].owns_village(dst)) {
|
|
if(n+1 != info_.team_num && current_team().is_enemy(n+1) == false) {
|
|
friend_owns = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(friend_owns) {
|
|
continue;
|
|
}
|
|
|
|
if(src != dst && info_.units.find(dst) == info_.units.end()) {
|
|
srcdst.insert(std::pair<location,location>(src,dst));
|
|
dstsrc.insert(std::pair<location,location>(dst,src));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ai::remove_unit_from_moves(const gamemap::location& loc, move_map& srcdst, move_map& dstsrc)
|
|
{
|
|
srcdst.erase(loc);
|
|
for(move_map::iterator i = dstsrc.begin(); i != dstsrc.end(); ) {
|
|
if(i->second == loc) {
|
|
dstsrc.erase(i++);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
//a structure to place an item we're trying to protect in
|
|
struct protected_item {
|
|
protected_item(double value, int radius, const gamemap::location& loc) : value(value), radius(radius), loc(loc) {}
|
|
double value;
|
|
int radius;
|
|
gamemap::location loc;
|
|
};
|
|
|
|
}
|
|
|
|
|
|
void ai::find_threats()
|
|
{
|
|
if(threats_found_) {
|
|
return;
|
|
}
|
|
|
|
threats_found_ = true;
|
|
|
|
const config& parms = current_team().ai_parameters();
|
|
|
|
std::vector<protected_item> items;
|
|
|
|
//we want to protect our leader
|
|
const unit_map::const_iterator leader = find_leader(units_,team_num_);
|
|
if(leader != units_.end()) {
|
|
items.push_back(protected_item(lexical_cast_default<double>(parms["protect_leader"],1.0),lexical_cast_default<int>(parms["protect_leader_radius"],20),leader->first));
|
|
}
|
|
|
|
//look for directions to protect a specific location
|
|
const config::child_list& locations = parms.get_children("protect_location");
|
|
for(config::child_list::const_iterator i = locations.begin(); i != locations.end(); ++i) {
|
|
items.push_back(protected_item(lexical_cast_default<double>((**i)["value"],1.0),lexical_cast_default<int>((**i)["radius"],20),location(**i)));
|
|
}
|
|
|
|
//look for directions to protect a unit
|
|
const config::child_list& protected_units = parms.get_children("protect_unit");
|
|
for(config::child_list::const_iterator j = protected_units.begin(); j != protected_units.end(); ++j) {
|
|
|
|
for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
|
|
if(game_events::unit_matches_filter(u,**j)) {
|
|
items.push_back(protected_item(lexical_cast_default<double>((**j)["value"],1.0),lexical_cast_default<int>((**j)["radius"],20),u->first));
|
|
}
|
|
}
|
|
}
|
|
|
|
//iterate over all protected locations, and if enemy units are within the protection radius, set them
|
|
//as hostile targets
|
|
for(std::vector<protected_item>::const_iterator k = items.begin(); k != items.end(); ++k) {
|
|
const protected_item& item = *k;
|
|
|
|
for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
|
|
const int distance = distance_between(u->first,item.loc);
|
|
if(current_team().is_enemy(u->second.side()) && distance < item.radius) {
|
|
add_target(target(u->first,item.value*double(item.radius-distance)/double(item.radius),target::THREAT));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ai::add_target(const target& tgt)
|
|
{
|
|
additional_targets_.push_back(tgt);
|
|
}
|
|
|
|
void ai::play_turn()
|
|
{
|
|
consider_combat_ = true;
|
|
do_move();
|
|
}
|
|
|
|
void ai::do_move()
|
|
{
|
|
log_scope("doing ai move");
|
|
|
|
invalidate_defensive_position_cache();
|
|
|
|
user_interact();
|
|
|
|
typedef paths::route route;
|
|
|
|
typedef std::map<location,paths> moves_map;
|
|
moves_map possible_moves, enemy_possible_moves;
|
|
|
|
move_map srcdst, dstsrc, enemy_srcdst, enemy_dstsrc;
|
|
|
|
calculate_possible_moves(possible_moves,srcdst,dstsrc,false,false,&avoided_locations());
|
|
calculate_possible_moves(enemy_possible_moves,enemy_srcdst,enemy_dstsrc,true);
|
|
|
|
const bool passive_leader = current_team().ai_parameters()["passive_leader"] == "yes";
|
|
|
|
unit_map::iterator leader = find_leader(units_,team_num_);
|
|
if(leader != units_.end() && passive_leader) {
|
|
remove_unit_from_moves(leader->first,srcdst,dstsrc);
|
|
}
|
|
|
|
int ticks = SDL_GetTicks();
|
|
|
|
std::vector<attack_analysis> analysis;
|
|
|
|
AI_DIAGNOSTIC("combat phase");
|
|
|
|
if(consider_combat_) {
|
|
std::cerr << "combat...\n";
|
|
consider_combat_ = do_combat(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc);
|
|
if(consider_combat_) {
|
|
do_move();
|
|
return;
|
|
}
|
|
}
|
|
|
|
move_leader_to_goals(enemy_srcdst,enemy_dstsrc);
|
|
|
|
AI_DIAGNOSTIC("get villages phase");
|
|
|
|
std::cerr << "villages...\n";
|
|
const bool got_village = get_villages(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc,leader);
|
|
if(got_village) {
|
|
do_move();
|
|
return;
|
|
}
|
|
|
|
AI_DIAGNOSTIC("healing phase");
|
|
|
|
std::cerr << "healing...\n";
|
|
const bool healed_unit = get_healing(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc);
|
|
if(healed_unit) {
|
|
do_move();
|
|
return;
|
|
}
|
|
|
|
AI_DIAGNOSTIC("retreat phase");
|
|
|
|
std::cerr << "retreating...\n";
|
|
const bool retreated_unit = retreat_units(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc,leader);
|
|
if(retreated_unit) {
|
|
do_move();
|
|
return;
|
|
}
|
|
|
|
if(leader != units_.end()) {
|
|
remove_unit_from_moves(leader->first,srcdst,dstsrc);
|
|
}
|
|
|
|
find_threats();
|
|
|
|
AI_DIAGNOSTIC("move/targetting phase");
|
|
|
|
const bool met_invisible_unit = move_to_targets(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc,leader);
|
|
if(met_invisible_unit) {
|
|
std::cerr << "met_invisible_unit\n";
|
|
do_move();
|
|
return;
|
|
}
|
|
|
|
std::cerr << "done move to targets\n";
|
|
|
|
AI_DIAGNOSTIC("leader/recruitment phase");
|
|
|
|
//recruitment phase and leader movement phase
|
|
if(leader != units_.end()) {
|
|
|
|
if(!passive_leader) {
|
|
move_leader_to_keep(enemy_dstsrc);
|
|
}
|
|
|
|
do_recruitment();
|
|
|
|
if(!passive_leader) {
|
|
move_leader_after_recruit(enemy_dstsrc);
|
|
}
|
|
}
|
|
|
|
AI_DIAGNOSTIC("");
|
|
|
|
recorder.end_turn();
|
|
sync_network();
|
|
}
|
|
|
|
bool ai::do_combat(std::map<gamemap::location,paths>& possible_moves, const move_map& srcdst, const move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc)
|
|
{
|
|
int ticks = SDL_GetTicks();
|
|
|
|
std::vector<attack_analysis> analysis = analyze_targets(srcdst,dstsrc,enemy_srcdst,enemy_dstsrc);
|
|
|
|
int time_taken = SDL_GetTicks() - ticks;
|
|
std::cerr << "took " << time_taken << " ticks for " << analysis.size() << " positions. Analyzing...\n";
|
|
|
|
ticks = SDL_GetTicks();
|
|
|
|
const int max_sims = 50000;
|
|
int num_sims = analysis.empty() ? 0 : max_sims/analysis.size();
|
|
if(num_sims < 20)
|
|
num_sims = 20;
|
|
if(num_sims > 40)
|
|
num_sims = 40;
|
|
|
|
std::cerr << "simulations: " << num_sims << "\n";
|
|
|
|
const int max_positions = 30000;
|
|
const int skip_num = analysis.size()/max_positions;
|
|
|
|
std::vector<attack_analysis>::iterator choice_it = analysis.end();
|
|
double choice_rating = -1000.0;
|
|
for(std::vector<attack_analysis>::iterator it = analysis.begin(); it != analysis.end(); ++it) {
|
|
if(skip_num > 0 && ((it - analysis.begin())%skip_num) && it->movements.size() > 1)
|
|
continue;
|
|
|
|
const double rating = it->rating(current_team().aggression(),*this);
|
|
std::cerr << "attack option rated at " << rating << " (" << current_team().aggression() << ")\n";
|
|
if(rating > choice_rating) {
|
|
choice_it = it;
|
|
choice_rating = rating;
|
|
}
|
|
}
|
|
|
|
time_taken = SDL_GetTicks() - ticks;
|
|
std::cerr << "analysis took " << time_taken << " ticks\n";
|
|
|
|
if(choice_rating > 0.0) {
|
|
location from = choice_it->movements[0].first;
|
|
location to = choice_it->movements[0].second;
|
|
location target_loc = choice_it->target;
|
|
const int weapon = choice_it->weapons[0];
|
|
|
|
const unit_map::const_iterator tgt = units_.find(target_loc);
|
|
|
|
const bool defender_human = (tgt != units_.end()) ?
|
|
teams_[tgt->second.side()-1].is_human() : false;
|
|
|
|
const location arrived_at = move_unit(from,to,possible_moves);
|
|
if(arrived_at != to) {
|
|
std::cerr << "unit moving to attack has ended up unexpectedly at " << (arrived_at.x+1) << "," << (arrived_at.y+1) << " when moving to "
|
|
<< (to.x+1) << "," << (to.y+1) << " moved from " << (from.x+1) << "," << (from.y+1) << "\n";
|
|
return true;
|
|
}
|
|
|
|
attack_enemy(to,target_loc,weapon);
|
|
|
|
//if this is the only unit in the attack, and the target
|
|
//is still alive, then also summon reinforcements
|
|
if(choice_it->movements.size() == 1 && units_.count(target_loc)) {
|
|
add_target(target(target_loc,3.0,target::BATTLE_AID));
|
|
}
|
|
|
|
return true;
|
|
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void ai_interface::attack_enemy(const location& u, const location& target, int weapon)
|
|
{
|
|
//stop the user from issuing any commands while the unit is attacking
|
|
const command_disabler disable_commands(&info_.disp);
|
|
|
|
if(info_.units.count(u) && info_.units.count(target)) {
|
|
if(info_.units.find(target)->second.stone()) {
|
|
std::cerr << "ERROR: attempt to attack unit that is turned to stone\n";
|
|
return;
|
|
}
|
|
|
|
recorder.add_attack(u,target,weapon);
|
|
game_events::fire("attack",u,target);
|
|
|
|
attack(info_.disp,info_.map,info_.teams,u,target,weapon,info_.units,info_.state,info_.gameinfo,false);
|
|
check_victory(info_.units,info_.teams);
|
|
dialogs::advance_unit(info_.gameinfo,info_.map,info_.units,u,info_.disp,true);
|
|
|
|
const unit_map::const_iterator defender = info_.units.find(target);
|
|
if(defender != info_.units.end()) {
|
|
const size_t defender_team = size_t(defender->second.side()) - 1;
|
|
if(defender_team < info_.teams.size()) {
|
|
dialogs::advance_unit(info_.gameinfo,info_.map,info_.units,target,info_.disp,!info_.teams[defender_team].is_human());
|
|
}
|
|
}
|
|
|
|
sync_network();
|
|
}
|
|
}
|
|
|
|
|
|
std::vector<std::pair<gamemap::location,gamemap::location> > ai::get_village_combinations(std::map<gamemap::location,paths>& possible_moves, const move_map& srcdst, const move_map& dstsrc,
|
|
const move_map& enemy_srcdst, const move_map& enemy_dstsrc, unit_map::const_iterator leader,
|
|
std::set<gamemap::location>& taken_villages, std::set<gamemap::location>& moved_units,
|
|
const std::vector<std::pair<gamemap::location,gamemap::location> >& village_moves,
|
|
std::vector<std::pair<gamemap::location,gamemap::location> >::const_iterator start_at)
|
|
{
|
|
int leader_distance_from_keep = -1;
|
|
|
|
std::vector<std::pair<location,location> > result;
|
|
|
|
for(std::vector<std::pair<location,location> >::const_iterator i = start_at; i != village_moves.end(); ++i) {
|
|
if(taken_villages.count(i->first) || moved_units.count(i->second)) {
|
|
continue;
|
|
}
|
|
|
|
int distance = -1;
|
|
|
|
if(leader != units_.end() && leader->first == i->second) {
|
|
const location& start_pos = nearest_keep(leader->first);;
|
|
distance = distance_between(start_pos,i->first);
|
|
}
|
|
|
|
taken_villages.insert(i->first);
|
|
moved_units.insert(i->second);
|
|
|
|
std::vector<std::pair<location,location> > res = get_village_combinations(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc,leader,
|
|
taken_villages,moved_units,village_moves,i+1);
|
|
|
|
//the result is better if it results in getting more villages, or if it results in the same number of villages,
|
|
//but the leader ends closer to their keep
|
|
const bool result_better = res.size() >= result.size() || res.size()+1 == result.size() && distance != -1 && distance < leader_distance_from_keep;
|
|
if(result_better) {
|
|
result.swap(res);
|
|
result.push_back(*i);
|
|
|
|
if(distance != -1) {
|
|
leader_distance_from_keep = distance;
|
|
}
|
|
}
|
|
|
|
taken_villages.erase(i->first);
|
|
moved_units.erase(i->second);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
bool ai::get_villages(std::map<gamemap::location,paths>& possible_moves, const move_map& srcdst, const move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc, unit_map::const_iterator leader)
|
|
{
|
|
std::cerr << "deciding which villages we want...\n";
|
|
|
|
location start_pos;
|
|
|
|
if(leader != units_.end()) {
|
|
start_pos = nearest_keep(leader->first);
|
|
}
|
|
|
|
std::map<location,double> vulnerability;
|
|
|
|
//we want to build up a list of possible moves we can make that will capture villages.
|
|
//limit the moves to 'max_village_moves' to make sure things don't get out of hand.
|
|
const size_t max_village_moves = 50;
|
|
std::vector<std::pair<location,location> > village_moves;
|
|
for(move_map::const_iterator j = dstsrc.begin(); j != dstsrc.end() && village_moves.size() < max_village_moves; ++j) {
|
|
if(map_.is_village(j->first) == false) {
|
|
continue;
|
|
}
|
|
|
|
bool want_village = true, owned = false;
|
|
for(size_t n = 0; n != teams_.size(); ++n) {
|
|
owned = teams_[n].owns_village(j->first);
|
|
if(owned && !current_team().is_enemy(n+1)) {
|
|
want_village = false;
|
|
}
|
|
|
|
if(owned) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(want_village == false) {
|
|
continue;
|
|
}
|
|
|
|
//if it's a neutral village, and we have no leader, then the village is no use to us,
|
|
//and we don't want it.
|
|
if(!owned && leader == units_.end()) {
|
|
continue;
|
|
}
|
|
|
|
//if we have a decent amount of gold, and the leader can't access the keep this turn if they get this village
|
|
//then don't get this village with them
|
|
if(want_village && leader != units_.end() && current_team().gold() > 20 && leader->first == j->second &&
|
|
leader->first != start_pos && multistep_move_possible(j->second,j->first,start_pos,possible_moves) == false) {
|
|
continue;
|
|
}
|
|
|
|
double threat = 0.0;
|
|
const std::map<location,double>::const_iterator vuln = vulnerability.find(j->first);
|
|
if(vuln != vulnerability.end()) {
|
|
threat = vuln->second;
|
|
} else {
|
|
threat = power_projection(j->first,enemy_srcdst,enemy_dstsrc);
|
|
vulnerability.insert(std::pair<location,double>(j->first,threat));
|
|
}
|
|
|
|
const unit_map::const_iterator u = units_.find(j->second);
|
|
if(u == units_.end() || u->second.is_guardian()) {
|
|
continue;
|
|
}
|
|
|
|
const unit& un = u->second;
|
|
if(un.hitpoints() < (threat*2*un.defense_modifier(map_,map_.get_terrain(j->first)))/100) {
|
|
continue;
|
|
}
|
|
|
|
village_moves.push_back(*j);
|
|
}
|
|
|
|
std::set<location> taken_villages, moved_units;
|
|
const int ticks = SDL_GetTicks();
|
|
std::cerr << "get_villages()..." << village_moves.size() << "\n";
|
|
const std::vector<std::pair<location,location> >& moves = get_village_combinations(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc,leader,
|
|
taken_villages,moved_units,village_moves,village_moves.begin());
|
|
std::cerr << "get_villages() done: " << (SDL_GetTicks() - ticks) << "\n";
|
|
|
|
//move all the units to get villages, however move the leader last, so that the castle will be cleared
|
|
//if it wants to stop to recruit along the way.
|
|
std::pair<location,location> leader_move;
|
|
|
|
int moves_made = 0;
|
|
for(std::vector<std::pair<location,location> >::const_iterator i = moves.begin(); i != moves.end(); ++i) {
|
|
if(leader != units_.end() && leader->first == i->second) {
|
|
leader_move = *i;
|
|
} else {
|
|
if(units_.count(i->first) == 0) {
|
|
const location loc = move_unit(i->second,i->first,possible_moves);
|
|
++moves_made;
|
|
|
|
const unit_map::const_iterator new_unit = units_.find(loc);
|
|
|
|
if(new_unit != units_.end() && power_projection(i->first,enemy_srcdst,enemy_dstsrc) >= new_unit->second.hitpoints()/4) {
|
|
add_target(target(new_unit->first,1.0,target::SUPPORT));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(leader_move.second.valid()) {
|
|
if(units_.count(leader_move.first) == 0) {
|
|
move_unit(leader_move.second,leader_move.first,possible_moves);
|
|
++moves_made;
|
|
}
|
|
}
|
|
|
|
return moves_made > 0 && village_moves.size() == max_village_moves;
|
|
}
|
|
|
|
bool ai::get_healing(std::map<gamemap::location,paths>& possible_moves, const move_map& srcdst, const move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc)
|
|
{
|
|
//find units in need of healing
|
|
unit_map::iterator u_it = units_.begin();
|
|
for(; u_it != units_.end(); ++u_it) {
|
|
unit& u = u_it->second;
|
|
|
|
//if the unit is on our side, has lost as many or more than 1/2 round
|
|
//worth of healing, and doesn't regenerate itself, then try to
|
|
//find a vacant village for it to rest in
|
|
if(u.side() == team_num_ &&
|
|
u.max_hitpoints() - u.hitpoints() >= game_config::cure_amount/2 &&
|
|
!u.type().regenerates()) {
|
|
|
|
//look for the village which is the least vulnerable to enemy attack
|
|
typedef std::multimap<location,location>::const_iterator Itor;
|
|
std::pair<Itor,Itor> it = srcdst.equal_range(u_it->first);
|
|
double best_vulnerability = 100000.0;
|
|
Itor best_loc = it.second;
|
|
while(it.first != it.second) {
|
|
const location& dst = it.first->second;
|
|
if(map_.gives_healing(dst) && (units_.find(dst) == units_.end() || dst == u_it->first)) {
|
|
const double vuln = power_projection(it.first->first,
|
|
enemy_srcdst,enemy_dstsrc);
|
|
std::cerr << "found village with vulnerability: " << vuln << "\n";
|
|
if(vuln < best_vulnerability || best_loc == it.second) {
|
|
best_vulnerability = vuln;
|
|
best_loc = it.first;
|
|
std::cerr << "chose village " << (dst.x+1) << "," << (dst.y+1) << "\n";
|
|
}
|
|
}
|
|
|
|
++it.first;
|
|
}
|
|
|
|
//if we have found an eligible village
|
|
if(best_loc != it.second) {
|
|
const location& src = best_loc->first;
|
|
const location& dst = best_loc->second;
|
|
|
|
std::cerr << "moving unit to village for healing...\n";
|
|
|
|
move_unit(src,dst,possible_moves);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ai::should_retreat(const gamemap::location& loc, const unit_map::const_iterator un, const move_map& srcdst, const move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc) const
|
|
{
|
|
const double caution = current_team().caution();
|
|
if(caution <= 0.0) {
|
|
return false;
|
|
}
|
|
|
|
const double optimal_terrain = best_defensive_position(un->first,dstsrc,srcdst,enemy_dstsrc,enemy_srcdst).chance_to_hit/100.0;
|
|
const double proposed_terrain = un->second.defense_modifier(map_,map_.get_terrain(loc))/100.0;
|
|
|
|
//the 'exposure' is the additional % chance to hit this unit receives from being on a sub-optimal defensive terrain
|
|
const double exposure = proposed_terrain - optimal_terrain;
|
|
|
|
const double our_power = power_projection(loc,srcdst,dstsrc);
|
|
const double their_power = power_projection(loc,enemy_srcdst,enemy_dstsrc);
|
|
return caution*their_power*(1.0+exposure) > our_power;
|
|
}
|
|
|
|
bool ai::retreat_units(std::map<gamemap::location,paths>& possible_moves, const move_map& srcdst, const move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc, unit_map::const_iterator leader)
|
|
{
|
|
//get versions of the move map that assume that all units are at full movement
|
|
std::map<gamemap::location,paths> dummy_possible_moves;
|
|
move_map fullmove_srcdst;
|
|
move_map fullmove_dstsrc;
|
|
calculate_possible_moves(dummy_possible_moves,fullmove_srcdst,fullmove_dstsrc,false,true,&avoided_locations());
|
|
|
|
gamemap::location leader_adj[6];
|
|
if(leader != units_.end()) {
|
|
get_adjacent_tiles(leader->first,leader_adj);
|
|
}
|
|
|
|
for(unit_map::iterator i = units_.begin(); i != units_.end(); ++i) {
|
|
if(i->second.side() == team_num_ && i->second.movement_left() == i->second.total_movement() && unit_map::const_iterator(i) != leader) {
|
|
|
|
//this unit still has movement left, and is a candidate to retreat. We see the amount
|
|
//of power of each side on the situation, and decide whether it should retreat.
|
|
if(should_retreat(i->first,i,fullmove_srcdst,fullmove_dstsrc,enemy_srcdst,enemy_dstsrc)) {
|
|
|
|
bool can_reach_leader = false;
|
|
|
|
//time to retreat. Look for the place where the power balance is most in our favor.
|
|
//If we can't find anywhere where we like the power balance, just try to
|
|
//get to the best defensive hex
|
|
typedef move_map::const_iterator Itor;
|
|
std::pair<Itor,Itor> itors = srcdst.equal_range(i->first);
|
|
gamemap::location best_pos, best_defensive;
|
|
double best_rating = 0.0;
|
|
int best_defensive_rating = 100;
|
|
while(itors.first != itors.second) {
|
|
|
|
if(leader != units_.end() && std::count(leader_adj,leader_adj+6,itors.first->second)) {
|
|
can_reach_leader = true;
|
|
break;
|
|
}
|
|
|
|
//we rate the power balance of a hex based on our power projection compared
|
|
//to theirs, multiplying their power projection by their chance to hit us
|
|
//on the hex we're planning to flee to.
|
|
const gamemap::location& hex = itors.first->second;
|
|
const int defense = i->second.type().movement_type().defense_modifier(map_,map_.get_terrain(hex));
|
|
const double our_power = power_projection(hex,srcdst,dstsrc);
|
|
const double their_power = power_projection(hex,enemy_srcdst,enemy_dstsrc) * double(defense)/100.0;
|
|
const double rating = our_power - their_power;
|
|
if(rating > best_rating) {
|
|
best_pos = hex;
|
|
best_rating = rating;
|
|
}
|
|
|
|
//give a bonus for getting to a village.
|
|
const int modified_defense = defense - (map_.is_village(hex) ? 10 : 0);
|
|
|
|
if(modified_defense < best_defensive_rating) {
|
|
best_defensive_rating = modified_defense;
|
|
best_defensive = hex;
|
|
}
|
|
|
|
++itors.first;
|
|
}
|
|
|
|
//if the unit is in range of its leader, it should never retreat --
|
|
//it has to defend the leader instead
|
|
if(can_reach_leader) {
|
|
continue;
|
|
}
|
|
|
|
if(!best_pos.valid()) {
|
|
best_pos = best_defensive;
|
|
}
|
|
|
|
if(best_pos.valid()) {
|
|
std::cerr << "retreating '" << i->second.type().name() << "' " << i->first.x << "," << i->first.y << " -> " << best_pos.x << "," << best_pos.y << "\n";
|
|
move_unit(i->first,best_pos,possible_moves);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ai::move_to_targets(std::map<gamemap::location,paths>& possible_moves, move_map& srcdst, move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc, unit_map::const_iterator leader)
|
|
{
|
|
std::cerr << "finding targets...\n";
|
|
std::vector<target> targets;
|
|
for(;;) {
|
|
if(targets.empty()) {
|
|
targets = find_targets(leader,enemy_srcdst,enemy_dstsrc);
|
|
targets.insert(targets.end(),additional_targets_.begin(),
|
|
additional_targets_.end());
|
|
if(targets.empty()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::cerr << "choosing move...\n";
|
|
std::pair<location,location> move = choose_move(targets,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc);
|
|
for(std::vector<target>::const_iterator ittg = targets.begin(); ittg != targets.end(); ++ittg) {
|
|
assert(map_.on_board(ittg->loc));
|
|
}
|
|
|
|
if(move.first.valid() == false) {
|
|
break;
|
|
}
|
|
|
|
if(move.second.valid() == false) {
|
|
return true;
|
|
}
|
|
|
|
std::cerr << "move: " << move.first.x << ", " << move.first.y << " - " << move.second.x << ", " << move.second.y << "\n";
|
|
|
|
//search to see if there are any enemy units next
|
|
//to the tile which really should be attacked once the move is done
|
|
gamemap::location adj[6];
|
|
get_adjacent_tiles(move.second,adj);
|
|
battle_stats bat_stats;
|
|
gamemap::location target;
|
|
int weapon = -1;
|
|
for(int n = 0; n != 6; ++n) {
|
|
const unit_map::iterator enemy = find_visible_unit(units_,adj[n],
|
|
map_,
|
|
state_.get_time_of_day().lawful_bonus,
|
|
teams_,current_team());
|
|
|
|
if(enemy != units_.end() &&
|
|
current_team().is_enemy(enemy->second.side()) && enemy->second.stone() == false) {
|
|
target = adj[n];
|
|
weapon = choose_weapon(move.first,target,bat_stats,
|
|
map_[move.second.x][move.second.y]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
const location arrived_at = move_unit(move.first,move.second,possible_moves);
|
|
|
|
//we didn't arrive at our intended destination. We return true, meaning that
|
|
//the AI algorithm should be recalculated from the start.
|
|
if(arrived_at != move.second) {
|
|
std::cerr << "didn't arrive at destination\n";
|
|
return true;
|
|
}
|
|
|
|
const unit_map::const_iterator u_it = units_.find(move.second);
|
|
|
|
//if we're going to attack someone
|
|
if(u_it != units_.end() && u_it->second.stone() == false && weapon != -1) {
|
|
attack_enemy(move.second,target,weapon);
|
|
}
|
|
|
|
//don't allow any other units to move onto the tile our unit
|
|
//just moved onto
|
|
typedef move_map::iterator Itor;
|
|
std::pair<Itor,Itor> del = dstsrc.equal_range(arrived_at);
|
|
dstsrc.erase(del.first,del.second);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int ai::average_resistance_against(const unit_type& a, const unit_type& b) const
|
|
{
|
|
int weighting_sum = 0, defense = 0;
|
|
const std::map<gamemap::TERRAIN,size_t>& terrain = map_.get_weighted_terrain_frequencies();
|
|
for(std::map<gamemap::TERRAIN,size_t>::const_iterator j = terrain.begin(); j != terrain.end(); ++j) {
|
|
defense += a.movement_type().defense_modifier(map_,j->first)*j->second;
|
|
weighting_sum += j->second;
|
|
}
|
|
|
|
defense /= weighting_sum;
|
|
|
|
std::cerr << "average defense of '" << a.name() << "': " << defense << "\n";
|
|
|
|
int sum = 0, weight_sum = 0;
|
|
|
|
const std::vector<attack_type>& attacks = b.attacks();
|
|
for(std::vector<attack_type>::const_iterator i = attacks.begin(); i != attacks.end(); ++i) {
|
|
const int resistance = a.movement_type().resistance_against(*i);
|
|
const int weight = i->damage()*i->num_attacks();
|
|
|
|
sum += defense*resistance*weight;
|
|
weight_sum += weight;
|
|
}
|
|
|
|
return sum/weight_sum;
|
|
}
|
|
|
|
int ai::compare_unit_types(const unit_type& a, const unit_type& b) const
|
|
{
|
|
const int a_effectiveness_vs_b = average_resistance_against(b,a);
|
|
const int b_effectiveness_vs_a = average_resistance_against(a,b);
|
|
|
|
std::cerr << "comparison of '" << a.name() << " vs " << b.name() << ": "
|
|
<< a_effectiveness_vs_b << " - " << b_effectiveness_vs_a << " = "
|
|
<< (a_effectiveness_vs_b - b_effectiveness_vs_a) << "\n";
|
|
return a_effectiveness_vs_b - b_effectiveness_vs_a;
|
|
}
|
|
|
|
void ai::analyze_potential_recruit_combat()
|
|
{
|
|
if(unit_combat_scores_.empty() == false || current_team().ai_parameters()["recruitment_ignore_bad_combat"] == "yes") {
|
|
return;
|
|
}
|
|
|
|
log_scope("analyze_potential_recruit_combat()");
|
|
|
|
//records the best combat analysis for each usage type
|
|
std::map<std::string,int> best_usage;
|
|
|
|
const std::set<std::string>& recruits = current_team().recruits();
|
|
std::set<std::string>::const_iterator i;
|
|
for(i = recruits.begin(); i != recruits.end(); ++i) {
|
|
const game_data::unit_type_map::const_iterator info = gameinfo_.unit_types.find(*i);
|
|
if(info == gameinfo_.unit_types.end() || not_recommended_units_.count(*i)) {
|
|
continue;
|
|
}
|
|
|
|
int score = 0, weighting = 0;
|
|
|
|
for(unit_map::const_iterator j = units_.begin(); j != units_.end(); ++j) {
|
|
if(j->second.can_recruit() || current_team().is_enemy(j->second.side()) == false) {
|
|
continue;
|
|
}
|
|
|
|
weighting += j->second.type().cost();
|
|
score += compare_unit_types(info->second,j->second.type())*j->second.type().cost();
|
|
}
|
|
|
|
if(weighting != 0) {
|
|
score /= weighting;
|
|
}
|
|
|
|
std::cerr << "combat score of '" << *i << "': " << score << "\n";
|
|
unit_combat_scores_[*i] = score;
|
|
|
|
if(best_usage.count(info->second.usage()) == 0 || score > best_usage[info->second.usage()]) {
|
|
best_usage[info->second.usage()] = score;
|
|
}
|
|
}
|
|
|
|
//recommend not to use units of a certain usage type if they have a score more than 1000
|
|
//below the best unit of that usage type
|
|
for(i = recruits.begin(); i != recruits.end(); ++i) {
|
|
const game_data::unit_type_map::const_iterator info = gameinfo_.unit_types.find(*i);
|
|
if(info == gameinfo_.unit_types.end() || not_recommended_units_.count(*i)) {
|
|
continue;
|
|
}
|
|
|
|
if(unit_combat_scores_[*i] + 1000 < best_usage[info->second.usage()]) {
|
|
std::cerr << "recommending not to use '" << *i << "' because of poor combat performance "
|
|
<< unit_combat_scores_[*i] << "/" << best_usage[info->second.usage()] << "\n";
|
|
not_recommended_units_.insert(*i);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct target_comparer_distance {
|
|
target_comparer_distance(const gamemap::location& loc) : loc_(loc) {}
|
|
|
|
bool operator()(const ai::target& a, const ai::target& b) const {
|
|
return distance_between(a.loc,loc_) < distance_between(b.loc,loc_);
|
|
}
|
|
|
|
private:
|
|
gamemap::location loc_;
|
|
};
|
|
|
|
}
|
|
|
|
void ai::analyze_potential_recruit_movements()
|
|
{
|
|
if(unit_movement_scores_.empty() == false || current_team().ai_parameters()["recruitment_ignore_bad_movement"] == "yes") {
|
|
return;
|
|
}
|
|
|
|
const unit_map::const_iterator leader = find_leader(units_,team_num_);
|
|
if(leader == units_.end()) {
|
|
return;
|
|
}
|
|
|
|
const location& start = nearest_keep(leader->first);
|
|
if(map_.on_board(start) == false) {
|
|
return;
|
|
}
|
|
|
|
log_scope("analyze_potential_recruit_movements()");
|
|
|
|
const int max_targets = 5;
|
|
|
|
const move_map srcdst, dstsrc;
|
|
std::vector<target> targets = find_targets(leader,srcdst,dstsrc);
|
|
if(targets.size() > max_targets) {
|
|
std::sort(targets.begin(),targets.end(),target_comparer_distance(start));
|
|
targets.erase(targets.begin()+max_targets,targets.end());
|
|
}
|
|
|
|
const std::set<std::string>& recruits = current_team().recruits();
|
|
|
|
std::cerr << "targets: " << targets.size() << "\n";
|
|
|
|
std::map<std::string,int> best_scores;
|
|
|
|
for(std::set<std::string>::const_iterator i = recruits.begin(); i != recruits.end(); ++i) {
|
|
const game_data::unit_type_map::const_iterator info = gameinfo_.unit_types.find(*i);
|
|
if(info == gameinfo_.unit_types.end()) {
|
|
continue;
|
|
}
|
|
|
|
const unit temp_unit(&info->second,team_num_);
|
|
unit_map units;
|
|
const temporary_unit_placer placer(units,start,temp_unit);
|
|
|
|
int cost = 0;
|
|
int targets_reached = 0;
|
|
int targets_missed = 0;
|
|
|
|
const shortest_path_calculator calc(temp_unit,current_team(),units,teams_,map_,state_);
|
|
for(std::vector<target>::const_iterator t = targets.begin(); t != targets.end(); ++t) {
|
|
std::cerr << "analyzing '" << *i << "' getting to target...\n";
|
|
const paths::route& route = a_star_search(start,t->loc,100.0,calc);
|
|
if(route.steps.empty() == false) {
|
|
std::cerr << "made it: " << route.move_left << "\n";
|
|
cost += route.move_left;
|
|
++targets_reached;
|
|
} else {
|
|
std::cerr << "failed\n";
|
|
++targets_missed;
|
|
}
|
|
}
|
|
|
|
if(targets_reached == 0 || targets_missed >= targets_reached*2) {
|
|
unit_movement_scores_[*i] = 100000;
|
|
not_recommended_units_.insert(*i);
|
|
} else {
|
|
const int average_cost = cost/targets_reached;
|
|
const int score = (average_cost * (targets_reached+targets_missed))/targets_reached;
|
|
unit_movement_scores_[*i] = score;
|
|
|
|
const std::map<std::string,int>::const_iterator current_best = best_scores.find(temp_unit.type().usage());
|
|
if(current_best == best_scores.end() || score < current_best->second) {
|
|
best_scores[temp_unit.type().usage()] = score;
|
|
}
|
|
}
|
|
}
|
|
|
|
for(std::map<std::string,int>::iterator j = unit_movement_scores_.begin(); j != unit_movement_scores_.end(); ++j) {
|
|
const game_data::unit_type_map::const_iterator info = gameinfo_.unit_types.find(j->first);
|
|
if(info == gameinfo_.unit_types.end()) {
|
|
}
|
|
continue;
|
|
|
|
const int best_score = best_scores[info->second.usage()];
|
|
if(best_score > 0) {
|
|
j->second = (j->second*10)/best_score;
|
|
if(j->second > 15) {
|
|
std::cerr << "recommending against recruiting '" << j->first << "' (score: " << j->second << ")\n";
|
|
not_recommended_units_.insert(j->first);
|
|
} else {
|
|
std::cerr << "recommending recruit of '" << j->first << "' (score: " << j->second << ")\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
if(not_recommended_units_.size() == unit_movement_scores_.size()) {
|
|
not_recommended_units_.clear();
|
|
}
|
|
}
|
|
|
|
void ai::do_recruitment()
|
|
{
|
|
const unit_map::const_iterator leader = find_leader(units_,team_num_);
|
|
if(leader == units_.end()) {
|
|
return;
|
|
}
|
|
|
|
const location& start_pos = nearest_keep(leader->first);
|
|
|
|
analyze_potential_recruit_movements();
|
|
analyze_potential_recruit_combat();
|
|
|
|
size_t neutral_villages = 0;
|
|
|
|
//we recruit the initial allocation of scouts based on how many neutral villages
|
|
//there are that are closer to us than to other keeps.
|
|
const std::vector<location>& villages = map_.villages();
|
|
for(std::vector<location>::const_iterator v = villages.begin(); v != villages.end(); ++v) {
|
|
const int owner = village_owner(*v,teams_);
|
|
if(owner == -1) {
|
|
const size_t distance = distance_between(start_pos,*v);
|
|
|
|
bool closest = true;
|
|
for(std::vector<team>::const_iterator i = teams_.begin(); i != teams_.end(); ++i) {
|
|
const int index = i - teams_.begin() + 1;
|
|
const gamemap::location& loc = map_.starting_position(index);
|
|
if(loc != start_pos && distance_between(loc,*v) < distance) {
|
|
closest = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(closest) {
|
|
++neutral_villages;
|
|
}
|
|
}
|
|
}
|
|
|
|
//the villages per scout is for a two-side battle, accounting for all neutral villages
|
|
//on the map. We only look at villages closer to us, so we halve it, making us get
|
|
//twice as many scouts
|
|
const int villages_per_scout = current_team().villages_per_scout()/2;
|
|
|
|
//get scouts depending on how many neutral villages there are
|
|
int scouts_wanted = villages_per_scout > 0 ? neutral_villages/villages_per_scout : 0;
|
|
|
|
std::cerr << "scouts_wanted: " << neutral_villages << "/" << villages_per_scout << " = " << scouts_wanted << "\n";
|
|
|
|
std::map<std::string,int> unit_types;
|
|
|
|
for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
|
|
if(u->second.side() == team_num_) {
|
|
++unit_types[u->second.type().usage()];
|
|
}
|
|
}
|
|
|
|
std::cerr << "we have " << unit_types["scout"] << " scouts already and we want " << scouts_wanted << " in total\n";
|
|
|
|
while(unit_types["scout"] < scouts_wanted) {
|
|
if(recruit_usage("scout") == false)
|
|
break;
|
|
|
|
++unit_types["scout"];
|
|
}
|
|
|
|
const std::vector<std::string>& options = current_team().recruitment_pattern();
|
|
|
|
if(options.empty()) {
|
|
assert(false);
|
|
return;
|
|
}
|
|
|
|
//buy units as long as we have room and can afford it
|
|
while(recruit_usage(options[rand()%options.size()])) {
|
|
}
|
|
}
|
|
|
|
void ai::move_leader_to_goals(const move_map& enemy_srcdst, const move_map& enemy_dstsrc)
|
|
{
|
|
const config* const goal = current_team().ai_parameters().child("leader_goal");
|
|
|
|
if(goal == NULL) {
|
|
AI_LOG("No goal found");
|
|
return;
|
|
}
|
|
|
|
const gamemap::location dst(*goal);
|
|
|
|
const unit_map::iterator leader = find_leader(units_,team_num_);
|
|
if(leader == units_.end() || leader->second.incapacitated()) {
|
|
AI_LOG("leader not found");
|
|
return;
|
|
}
|
|
|
|
AI_LOG("Doing recruitment before goals");
|
|
|
|
do_recruitment();
|
|
|
|
const paths::route route = a_star_search(leader->first,dst,1000.0,shortest_path_calculator(leader->second,current_team(),units_,teams_,map_,state_));
|
|
if(route.steps.empty()) {
|
|
AI_LOG("route empty");
|
|
return;
|
|
}
|
|
|
|
const paths leader_paths(map_,state_,gameinfo_,units_,leader->first,teams_,false,false);
|
|
|
|
std::map<gamemap::location,paths> possible_moves;
|
|
possible_moves.insert(std::pair<gamemap::location,paths>(leader->first,leader_paths));
|
|
|
|
gamemap::location loc;
|
|
for(std::vector<gamemap::location>::const_iterator itor = route.steps.begin(); itor != route.steps.end(); ++itor) {
|
|
if(leader_paths.routes.count(*itor) == 1 && power_projection(*itor,enemy_srcdst,enemy_dstsrc) < double(leader->second.hitpoints()/2)) {
|
|
loc = *itor;
|
|
}
|
|
}
|
|
|
|
if(loc.valid()) {
|
|
AI_LOG("Moving leader to goal");
|
|
move_unit(leader->first,loc,possible_moves);
|
|
}
|
|
}
|
|
|
|
void ai::move_leader_to_keep(const move_map& enemy_dstsrc)
|
|
{
|
|
const unit_map::iterator leader = find_leader(units_,team_num_);
|
|
if(leader == units_.end() || leader->second.incapacitated()) {
|
|
return;
|
|
}
|
|
|
|
//find where the leader can move
|
|
const paths leader_paths(map_,state_,gameinfo_,units_,leader->first,teams_,false,false);
|
|
const gamemap::location& start_pos = nearest_keep(leader->first);
|
|
|
|
std::map<gamemap::location,paths> possible_moves;
|
|
possible_moves.insert(std::pair<gamemap::location,paths>(leader->first,leader_paths));
|
|
|
|
bool leader_moved = false;
|
|
//if the leader is not on his starting location, move him there.
|
|
if(leader->first != start_pos) {
|
|
leader_moved = true;
|
|
const paths::routes_map::const_iterator itor = leader_paths.routes.find(start_pos);
|
|
if(itor != leader_paths.routes.end() && units_.count(start_pos) == 0) {
|
|
move_unit(leader->first,start_pos,possible_moves);
|
|
} else {
|
|
//make a map of the possible locations the leader can move to, ordered by the
|
|
//distance from the keep
|
|
std::multimap<int,gamemap::location> moves_toward_keep;
|
|
|
|
//the leader can't move to his keep, try to move to the closest location to
|
|
//the keep where there are no enemies in range.
|
|
const int current_distance = distance_between(leader->first,start_pos);
|
|
for(paths::routes_map::const_iterator i = leader_paths.routes.begin(); i != leader_paths.routes.end(); ++i) {
|
|
const int new_distance = distance_between(i->first,start_pos);
|
|
if(new_distance < current_distance) {
|
|
moves_toward_keep.insert(std::pair<int,gamemap::location>(new_distance,i->first));
|
|
}
|
|
}
|
|
|
|
//find the first location which we can move to without the threat of enemies
|
|
for(std::multimap<int,gamemap::location>::const_iterator j = moves_toward_keep.begin(); j != moves_toward_keep.end(); ++j) {
|
|
if(enemy_dstsrc.count(j->second) == 0) {
|
|
move_unit(leader->first,j->second,possible_moves);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ai::move_leader_after_recruit(const move_map& enemy_dstsrc)
|
|
{
|
|
std::cerr << "moving leader after recruit...\n";
|
|
|
|
const unit_map::iterator leader = find_leader(units_,team_num_);
|
|
if(leader == units_.end() || leader->second.incapacitated()) {
|
|
return;
|
|
}
|
|
|
|
const paths leader_paths(map_,state_,gameinfo_,units_,leader->first,teams_,false,false);
|
|
|
|
std::map<gamemap::location,paths> possible_moves;
|
|
possible_moves.insert(std::pair<gamemap::location,paths>(leader->first,leader_paths));
|
|
|
|
if(current_team().gold() < 20 && is_accessible(leader->first,enemy_dstsrc) == false) {
|
|
//see if we want to ward any enemy units off from getting our villages
|
|
for(move_map::const_iterator i = enemy_dstsrc.begin(); i != enemy_dstsrc.end(); ++i) {
|
|
|
|
//if this is a village of ours, that an enemy can capture on their turn, and
|
|
//which we might be able to reach in two turns.
|
|
if(map_.is_village(i->first) && current_team().owns_village(i->first) &&
|
|
distance_between(i->first,leader->first) <= leader->second.total_movement()*2) {
|
|
|
|
int current_distance = distance_between(i->first,leader->first);
|
|
location current_loc;
|
|
|
|
for(paths::routes_map::const_iterator j = leader_paths.routes.begin(); j != leader_paths.routes.end(); ++j) {
|
|
const int distance = distance_between(i->first,j->first);
|
|
if(distance < current_distance && is_accessible(j->first,enemy_dstsrc) == false) {
|
|
current_distance = distance;
|
|
current_loc = j->first;
|
|
}
|
|
}
|
|
|
|
//if this location is in range of the village, then we consider it
|
|
if(current_loc.valid()) {
|
|
AI_LOG("considering movement to " + str_cast(current_loc.x+1) + "," + str_cast(current_loc.y+1));
|
|
unit_map temp_units;
|
|
temp_units.insert(std::pair<location,unit>(current_loc,leader->second));
|
|
const paths p(map_,state_,gameinfo_,temp_units,current_loc,teams_,false,false);
|
|
|
|
if(p.routes.count(i->first)) {
|
|
move_unit(leader->first,current_loc,possible_moves);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//see if any friendly leaders can make it to our keep. If they can, then move off it so that they
|
|
//can recruit if they want.
|
|
if(nearest_keep(leader->first) == leader->first) {
|
|
const location keep = leader->first;
|
|
const std::pair<location,unit> temp_leader = *leader;
|
|
units_.erase(leader);
|
|
|
|
bool friend_can_reach_keep = false;
|
|
|
|
std::map<location,paths> friends_possible_moves;
|
|
move_map friends_srcdst, friends_dstsrc;
|
|
calculate_possible_moves(friends_possible_moves,friends_srcdst,friends_dstsrc,false,true);
|
|
for(move_map::const_iterator i = friends_dstsrc.begin(); i != friends_dstsrc.end(); ++i) {
|
|
if(i->first == keep) {
|
|
const unit_map::const_iterator itor = units_.find(i->second);
|
|
if(itor != units_.end() && itor->second.can_recruit()) {
|
|
friend_can_reach_keep = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
units_.insert(temp_leader);
|
|
|
|
if(friend_can_reach_keep) {
|
|
//find a location for our leader to vacate the keep to
|
|
location adj[6];
|
|
get_adjacent_tiles(keep,adj);
|
|
for(size_t n = 0; n != 6; ++n) {
|
|
//vacate to the first location found that is on the board, our leader can move to, and no enemies can reach
|
|
if(map_.on_board(adj[n]) && leader_paths.routes.count(adj[n]) != 0 && is_accessible(adj[n],enemy_dstsrc) == false) {
|
|
move_unit(keep,adj[n],possible_moves);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ai::leader_can_reach_keep() const
|
|
{
|
|
const unit_map::iterator leader = find_leader(units_,team_num_);
|
|
if(leader == units_.end() || leader->second.incapacitated()) {
|
|
return false;
|
|
}
|
|
|
|
const gamemap::location& start_pos = nearest_keep(leader->first);
|
|
if(start_pos.valid() == false) {
|
|
return false;
|
|
}
|
|
|
|
if(leader->first == start_pos) {
|
|
return true;
|
|
}
|
|
|
|
//find where the leader can move
|
|
const paths leader_paths(map_,state_,gameinfo_,units_,leader->first,teams_,false,false);
|
|
|
|
|
|
return leader_paths.routes.count(start_pos) > 0;
|
|
}
|
|
|
|
int ai::rate_terrain(const unit& u, const gamemap::location& loc)
|
|
{
|
|
const gamemap::TERRAIN terrain = map_.get_terrain(loc);
|
|
const int defense = u.defense_modifier(map_,terrain);
|
|
int rating = 100 - defense;
|
|
|
|
const int healing_value = 10;
|
|
const int friendly_village_value = 5;
|
|
const int neutral_village_value = 10;
|
|
const int enemy_village_value = 15;
|
|
|
|
if(map_.gives_healing(terrain) && u.type().regenerates() == false) {
|
|
rating += healing_value;
|
|
}
|
|
|
|
if(map_.is_village(terrain)) {
|
|
const int owner = village_owner(loc,teams_);
|
|
|
|
if(owner+1 == team_num_) {
|
|
rating += friendly_village_value;
|
|
} else if(owner == -1) {
|
|
rating += neutral_village_value;
|
|
} else {
|
|
rating += enemy_village_value;
|
|
}
|
|
}
|
|
|
|
return rating;
|
|
}
|
|
|
|
const ai::defensive_position& ai::best_defensive_position(const gamemap::location& loc,
|
|
const move_map& dstsrc, const move_map& srcdst,
|
|
const move_map& enemy_dstsrc, const move_map& enemy_srcdst) const
|
|
{
|
|
const unit_map::const_iterator itor = units_.find(loc);
|
|
if(itor == units_.end()) {
|
|
static defensive_position pos;
|
|
pos.chance_to_hit = 0;
|
|
pos.vulnerability = pos.support = 0;
|
|
return pos;
|
|
}
|
|
|
|
const std::map<location,defensive_position>::const_iterator position = defensive_position_cache_.find(loc);
|
|
if(position != defensive_position_cache_.end()) {
|
|
return position->second;
|
|
}
|
|
|
|
defensive_position pos;
|
|
pos.chance_to_hit = 100;
|
|
pos.vulnerability = 10000.0;
|
|
pos.support = 0.0;
|
|
|
|
typedef move_map::const_iterator Itor;
|
|
const std::pair<Itor,Itor> itors = srcdst.equal_range(loc);
|
|
for(Itor i = itors.first; i != itors.second; ++i) {
|
|
const int defense = itor->second.defense_modifier(map_,map_.get_terrain(i->second));
|
|
if(defense > pos.chance_to_hit) {
|
|
continue;
|
|
}
|
|
|
|
const double vulnerability = power_projection(i->second,enemy_srcdst,enemy_dstsrc);
|
|
const double support = power_projection(i->second,srcdst,dstsrc);
|
|
|
|
if(defense < pos.chance_to_hit || support - vulnerability > pos.support - pos.vulnerability) {
|
|
pos.loc = i->second;
|
|
pos.chance_to_hit = defense;
|
|
pos.vulnerability = vulnerability;
|
|
pos.support = support;
|
|
}
|
|
}
|
|
|
|
defensive_position_cache_.insert(std::pair<location,defensive_position>(loc,pos));
|
|
return defensive_position_cache_[loc];
|
|
}
|
|
|
|
void ai::invalidate_defensive_position_cache()
|
|
{
|
|
defensive_position_cache_.clear();
|
|
}
|
|
|
|
bool ai::is_accessible(const location& loc, const move_map& dstsrc) const
|
|
{
|
|
gamemap::location adj[6];
|
|
get_adjacent_tiles(loc,adj);
|
|
for(size_t n = 0; n != 6; ++n) {
|
|
if(dstsrc.count(adj[n]) > 0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return dstsrc.count(loc) > 0;
|
|
}
|
|
|
|
|
|
const std::set<gamemap::location>& ai::keeps() const
|
|
{
|
|
if(keeps_.empty()) {
|
|
//generate the list of keeps -- iterate over the entire map and find all keeps
|
|
for(size_t x = 0; x != size_t(map_.x()); ++x) {
|
|
for(size_t y = 0; y != size_t(map_.y()); ++y) {
|
|
const gamemap::location loc(x,y);
|
|
if(map_.is_keep(loc)) {
|
|
gamemap::location adj[6];
|
|
get_adjacent_tiles(loc,adj);
|
|
for(size_t n = 0; n != 6; ++n) {
|
|
if(map_.is_castle(adj[n])) {
|
|
keeps_.insert(loc);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return keeps_;
|
|
}
|
|
|
|
const gamemap::location& ai::nearest_keep(const gamemap::location& loc) const
|
|
{
|
|
const std::set<gamemap::location>& keeps = this->keeps();
|
|
if(keeps.empty()) {
|
|
static const gamemap::location dummy;
|
|
return dummy;
|
|
}
|
|
|
|
const gamemap::location* res = NULL;
|
|
int closest = -1;
|
|
for(std::set<gamemap::location>::const_iterator i = keeps.begin(); i != keeps.end(); ++i) {
|
|
const int distance = distance_between(*i,loc);
|
|
if(res == NULL || distance < closest) {
|
|
closest = distance;
|
|
res = &*i;
|
|
}
|
|
}
|
|
|
|
return *res;
|
|
}
|
|
|
|
const std::set<gamemap::location>& ai::avoided_locations() const
|
|
{
|
|
if(avoid_.empty()) {
|
|
const config::child_list& avoids = current_team().ai_parameters().get_children("avoid");
|
|
for(config::child_list::const_iterator a = avoids.begin(); a != avoids.end(); ++a) {
|
|
|
|
const std::vector<location>& locs = parse_location_range((**a)["x"],(**a)["y"]);
|
|
for(std::vector<location>::const_iterator i = locs.begin(); i != locs.end(); ++i) {
|
|
avoid_.insert(*i);
|
|
}
|
|
}
|
|
|
|
if(avoid_.empty()) {
|
|
avoid_.insert(location());
|
|
}
|
|
}
|
|
|
|
return avoid_;
|
|
} |