00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include "ai.hpp"
00021 #include "ai2.hpp"
00022 #include "ai_dfool.hpp"
00023 #ifdef HAVE_PYTHON
00024 #include "ai_python.hpp"
00025 #endif
00026 #include "actions.hpp"
00027 #include "callable_objects.hpp"
00028 #include "dialogs.hpp"
00029 #include "foreach.hpp"
00030 #include "formula_ai.hpp"
00031 #include "game_config.hpp"
00032 #include "game_events.hpp"
00033 #include "game_preferences.hpp"
00034 #include "gettext.hpp"
00035 #include "log.hpp"
00036 #include "menu_events.hpp"
00037 #include "replay.hpp"
00038 #include "statistics.hpp"
00039 #include "unit_display.hpp"
00040 #include "unit.hpp"
00041 #include "playturn.hpp"
00042 #include "wml_exception.hpp"
00043
00044 #include <cassert>
00045
00046 #define LOG_AI LOG_STREAM(info, ai)
00047 #define WRN_AI LOG_STREAM(warn, ai)
00048 #define ERR_AI LOG_STREAM(err, ai)
00049
00050
00051 class idle_ai : public ai_interface {
00052 public:
00053 idle_ai(info& i) : ai_interface(i) {}
00054 void play_turn() {
00055 game_events::fire("ai turn");
00056 }
00057 };
00058
00059
00060 class sample_ai : public ai_interface {
00061 public:
00062 sample_ai(info& i) : ai_interface(i) {}
00063
00064 void play_turn() {
00065 game_events::fire("ai turn");
00066 do_attacks();
00067 get_villages();
00068 do_moves();
00069 do_recruitment();
00070 }
00071
00072 protected:
00073 void do_attacks() {
00074 std::map<location,paths> possible_moves;
00075 move_map srcdst, dstsrc;
00076 calculate_possible_moves(possible_moves,srcdst,dstsrc,false);
00077
00078 for(unit_map::const_iterator i = get_info().units.begin(); i != get_info().units.end(); ++i) {
00079 if(current_team().is_enemy(i->second.side())) {
00080 location adjacent_tiles[6];
00081 get_adjacent_tiles(i->first,adjacent_tiles);
00082
00083 int best_defense = -1;
00084 std::pair<location,location> best_movement;
00085
00086 for(size_t n = 0; n != 6; ++n) {
00087 typedef move_map::const_iterator Itor;
00088 std::pair<Itor,Itor> range = dstsrc.equal_range(adjacent_tiles[n]);
00089 while(range.first != range.second) {
00090 const location& dst = range.first->first;
00091 const location& src = range.first->second;
00092 const unit_map::const_iterator un = get_info().units.find(src);
00093
00094 const t_translation::t_terrain terrain = get_info().map.get_terrain(dst);
00095
00096 const int chance_to_hit = un->second.defense_modifier(terrain);
00097
00098 if(best_defense == -1 || chance_to_hit < best_defense) {
00099 best_defense = chance_to_hit;
00100 best_movement = *range.first;
00101 }
00102
00103 ++range.first;
00104 }
00105 }
00106
00107 if(best_defense != -1) {
00108 move_unit(best_movement.second,best_movement.first,possible_moves);
00109 battle_context bc(get_info().map, get_info().teams,
00110 get_info().units, get_info().state,
00111 best_movement.first,
00112 i->first, -1, -1, current_team().aggression());
00113 attack_enemy(best_movement.first,i->first,
00114 bc.get_attacker_stats().attack_num,
00115 bc.get_defender_stats().attack_num);
00116 do_attacks();
00117 return;
00118 }
00119 }
00120 }
00121 }
00122
00123 void get_villages() {
00124 std::map<location,paths> possible_moves;
00125 move_map srcdst, dstsrc;
00126 calculate_possible_moves(possible_moves,srcdst,dstsrc,false);
00127
00128 for(move_map::const_iterator i = dstsrc.begin(); i != dstsrc.end(); ++i) {
00129 if(get_info().map.is_village(i->first) && current_team().owns_village(i->first) == false) {
00130 move_unit(i->second,i->first,possible_moves);
00131 get_villages();
00132 return;
00133 }
00134 }
00135 }
00136
00137 void do_moves() {
00138 unit_map::const_iterator leader;
00139 for(leader = get_info().units.begin(); leader != get_info().units.end(); ++leader) {
00140 if(leader->second.can_recruit() && current_team().is_enemy(leader->second.side())) {
00141 break;
00142 }
00143 }
00144
00145 if(leader == get_info().units.end())
00146 return;
00147
00148 std::map<location,paths> possible_moves;
00149 move_map srcdst, dstsrc;
00150 calculate_possible_moves(possible_moves,srcdst,dstsrc,false);
00151
00152 int closest_distance = -1;
00153 std::pair<location,location> closest_move;
00154
00155 for(move_map::const_iterator i = dstsrc.begin(); i != dstsrc.end(); ++i) {
00156 const int distance = distance_between(i->first,leader->first);
00157 if(closest_distance == -1 || distance < closest_distance) {
00158 closest_distance = distance;
00159 closest_move = *i;
00160 }
00161 }
00162
00163 if(closest_distance != -1) {
00164 move_unit(closest_move.second,closest_move.first,possible_moves);
00165 do_moves();
00166 }
00167 }
00168
00169 void do_recruitment() {
00170 const std::set<std::string>& options = current_team().recruits();
00171 if (!options.empty()) {
00172 const int choice = (rand()%options.size());
00173 std::set<std::string>::const_iterator i = options.begin();
00174 std::advance(i,choice);
00175
00176 const bool res = recruit(*i);
00177 if(res) {
00178 do_recruitment();
00179 }
00180 }
00181 }
00182 };
00183
00184 std::vector<std::string> get_available_ais()
00185 {
00186 std::vector<std::string> ais;
00187 ais.push_back("default");
00188 ais.push_back("sample_ai");
00189
00190 ais.push_back("dfool_ai");
00191 #ifdef HAVE_PYTHON
00192 std::vector<std::string> scripts = python_ai::get_available_scripts();
00193 ais.insert(ais.end(), scripts.begin(), scripts.end());
00194 #endif
00195 return ais;
00196 }
00197
00198 ai_interface* create_ai(const std::string& name, ai_interface::info& info)
00199 {
00200
00201
00202
00203
00204
00205 if(name == "sample_ai")
00206 return new sample_ai(info);
00207 else if(name == "idle_ai")
00208 return new idle_ai(info);
00209 else if(name == "formula_ai")
00210 return new formula_ai(info);
00211 else if(name == "dfool_ai")
00212 return new dfool::dfool_ai(info);
00213
00214
00215 else if(name == "ai2")
00216 return new ai2(info);
00217 else if(name == "python_ai")
00218 #ifdef HAVE_PYTHON
00219 return new python_ai(info);
00220 #else
00221 {
00222 LOG_STREAM(err, ai) << "No Python AI support available in this Wesnoth build!\n";
00223 return new ai2(info);
00224 }
00225 #endif
00226 else if(name != "") {
00227 LOG_STREAM(err, ai) << "AI not found: '" << name << "'\n";
00228 }
00229
00230 return new ai(info);
00231 }
00232
00233 ai::ai(ai_interface::info& info) :
00234 ai_interface(info),
00235 defensive_position_cache_(),
00236 threats_found_(false),
00237 attacks_(),
00238 disp_(info.disp),
00239 map_(info.map),
00240 units_(info.units),
00241 teams_(info.teams),
00242 team_num_(info.team_num),
00243 state_(info.state),
00244 consider_combat_(true),
00245 additional_targets_(),
00246 unit_movement_scores_(),
00247 not_recommended_units_(),
00248 unit_combat_scores_(),
00249 keeps_(),
00250 avoid_(),
00251 unit_stats_cache_(),
00252 attack_depth_(0)
00253 {}
00254
00255 bool ai::recruit_usage(const std::string& usage)
00256 {
00257 raise_user_interact();
00258
00259 const int min_gold = 0;
00260
00261 log_scope2(ai, "recruiting troops");
00262 LOG_AI << "recruiting '" << usage << "'\n";
00263
00264
00265 unit_type_data::types().build_all(unit_type::HELP_INDEX);
00266
00267 std::vector<std::string> options;
00268 bool found = false;
00269
00270
00271 const std::set<std::string>& recruits = current_team().recruits();
00272 for(std::map<std::string,unit_type>::const_iterator i =
00273 unit_type_data::types().begin(); i != unit_type_data::types().end(); ++i)
00274 {
00275 const std::string& name = i->second.id();
00276
00277 LOG_AI << name << " considered\n";
00278 if (i->second.usage() == usage || usage == "") {
00279 found = true;
00280 LOG_AI << name << " considered for " << usage << " recruitment\n";
00281 if (!recruits.count(name)) {
00282 LOG_AI << name << " rejected, not in recruitment list\n";
00283 continue;
00284 }
00285
00286 if (current_team().gold() - i->second.cost() < min_gold) {
00287 LOG_AI << name << " rejected, cost too high (cost: " << i->second.cost() << ", current gold: " << current_team().gold() <<", min_gold: " << min_gold << ")\n";
00288 continue;
00289 }
00290
00291 if (not_recommended_units_.count(name))
00292 {
00293 LOG_AI << name << " rejected, bad terrain or combat\n";
00294 continue;
00295 }
00296
00297 LOG_AI << "recommending '" << name << "'\n";
00298 options.push_back(name);
00299 }
00300 }
00301
00302
00303 if(options.empty() == false) {
00304 const int option = rand()%options.size();
00305 return recruit(options[option]);
00306 }
00307 if (found) {
00308 LOG_AI << "No available units to recruit that come under the price.\n";
00309 } else {
00310 WRN_AI << "Trying to recruit a: " << usage
00311 << " but no unit of that type (usage=) is available.\n";
00312 }
00313 return false;
00314 }
00315
00316 bool ai_interface::recruit(const std::string& unit_name, location loc)
00317 {
00318 const std::set<std::string>& recruits = current_team().recruits();
00319
00320 const std::set<std::string>::const_iterator i = recruits.find(unit_name);
00321 if(i == recruits.end()) {
00322 return false;
00323 }
00324
00325 const int num = std::distance(recruits.begin(),i);
00326
00327
00328
00329
00330
00331
00332
00333 recorder.add_recruit(num,loc);
00334 replay_undo replay_guard(recorder);
00335
00336 unit_type_data::unit_type_map::const_iterator u = unit_type_data::types().find(unit_name);
00337 if(u == unit_type_data::types().end()) {
00338 return false;
00339 }
00340
00341
00342 if(current_team().gold() < u->second.cost()) {
00343 return false;
00344 }
00345 LOG_AI << "trying recruit: team=" << (info_.team_num) <<
00346 " type=" << unit_name <<
00347 " cost=" << u->second.cost() <<
00348 " loc=(" << loc << ')' <<
00349 " gold=" << (current_team().gold()) <<
00350 " (-> " << (current_team().gold()-u->second.cost()) << ")\n";
00351
00352 unit new_unit(&info_.units,&info_.map,&info_.state,&info_.teams,&u->second,info_.team_num,true);
00353
00354
00355 std::string recruit_err = recruit_unit(info_.map,info_.team_num,info_.units,new_unit,loc,false,preferences::show_ai_moves());
00356 if(recruit_err.empty()) {
00357
00358 statistics::recruit_unit(new_unit);
00359 current_team().spend_gold(u->second.cost());
00360
00361
00362 replay_guard.confirm_transaction();
00363
00364 raise_unit_recruited();
00365 const team_data data = calculate_team_data(current_team(),info_.team_num,info_.units);
00366 LOG_AI <<
00367 "recruit confirmed: team=" << (info_.team_num) <<
00368 " units=" << data.units <<
00369 " gold=" << data.gold <<
00370 ((data.net_income < 0) ? "" : "+") <<
00371 data.net_income << "\n";
00372 recorder.add_checksum_check(loc);
00373 return true;
00374 } else {
00375 const team_data data = calculate_team_data(current_team(),info_.team_num,info_.units);
00376 LOG_AI << recruit_err << "\n";
00377 LOG_AI <<
00378 "recruit UNconfirmed: team=" << (info_.team_num) <<
00379 " units=" << data.units <<
00380 " gold=" << data.gold <<
00381 ((data.net_income < 0) ? "" : "+") <<
00382 data.net_income << "\n";
00383 return false;
00384 }
00385 }
00386
00387 void ai_interface::raise_user_interact()
00388 {
00389 const int interact_time = 30;
00390 const int time_since_interact = SDL_GetTicks() - last_interact_;
00391 if(time_since_interact < interact_time) {
00392 return;
00393 }
00394
00395 user_interact_.notify_observers();
00396
00397 last_interact_ = SDL_GetTicks();
00398 }
00399
00400 void ai_interface::diagnostic(const std::string& msg)
00401 {
00402 if(game_config::debug) {
00403 info_.disp.set_diagnostic(msg);
00404 }
00405 }
00406
00407 void ai_interface::log_message(const std::string& msg)
00408 {
00409 if(game_config::debug) {
00410 info_.disp.add_chat_message(time(NULL), "ai", info_. team_num, msg,
00411 game_display::MESSAGE_PUBLIC, false);
00412 }
00413 }
00414
00415
00416 gamemap::location ai_interface::move_unit(location from, location to,
00417 std::map<location,paths>& possible_moves)
00418 {
00419 const location loc = move_unit_partial(from,to,possible_moves);
00420 const unit_map::iterator u = info_.units.find(loc);
00421 if(u != info_.units.end()) {
00422 if(u->second.movement_left()==u->second.total_movement()) {
00423 u->second.set_movement(0);
00424 u->second.set_state("not_moved","yes");
00425 } else {
00426 u->second.set_movement(0);
00427 }
00428 }
00429
00430 return loc;
00431 }
00432
00433 gamemap::location ai_interface::move_unit_partial(location from, location to,
00434 std::map<location,paths>& possible_moves)
00435 {
00436 LOG_AI << "ai_interface::move_unit " << from << " -> " << to << '\n';
00437
00438
00439 const events::command_disabler disable_commands;
00440
00441 log_scope2(ai, "move_unit");
00442 unit_map::iterator u_it = info_.units.find(from);
00443 if(u_it == info_.units.end()) {
00444 LOG_STREAM(err, ai) << "Could not find unit at " << from << '\n';
00445 assert(false);
00446 return location();
00447 }
00448
00449 if(from == to) {
00450 LOG_AI << "moving unit at " << from << " on spot. resetting moves\n";
00451 return to;
00452 }
00453
00454 const bool show_move = preferences::show_ai_moves();
00455
00456 const std::map<location,paths>::iterator p_it = possible_moves.find(from);
00457
00458 if(p_it != possible_moves.end()) {
00459 paths& p = p_it->second;
00460 std::map<location,paths::route>::iterator rt = p.routes.begin();
00461 for(; rt != p.routes.end(); ++rt) {
00462 if(rt->first == to) {
00463 break;
00464 }
00465 }
00466
00467 if(rt != p.routes.end()) {
00468 u_it->second.set_movement(rt->second.move_left);
00469
00470 std::vector<location> steps = rt->second.steps;
00471
00472 while(steps.empty() == false && (!(info_.units.find(to) == info_.units.end() || from == to))){
00473 LOG_AI << "AI attempting illegal move. Attempting to move onto existing unit\n";
00474 LOG_AI << "\t" << info_.units.find(to)->second.underlying_id() <<" already on " << to << "\n";
00475 LOG_AI <<"\tremoving "<<*(steps.end()-1)<<"\n";
00476 to = *(steps.end()-1);
00477 steps.pop_back();
00478 LOG_AI << "\tresetting to " << from << " -> " << to << '\n';
00479 }
00480
00481 if(steps.size()>1) {
00482 unit_map::const_iterator utest=info_.units.find(*(steps.begin()+1));
00483 if(utest != info_.units.end() && current_team().is_enemy(utest->second.side())){
00484 LOG_STREAM(err, ai) << "AI tried to move onto existing enemy unit at"<<*(steps.begin())<<"\n";
00485
00486 }
00487
00488
00489 for(std::vector<location>::iterator i = steps.begin()+1; i != steps.end(); ++i) {
00490 location adj[6];
00491 get_adjacent_tiles(*i,adj);
00492
00493 size_t n;
00494 for(n = 0; n != 6; ++n) {
00495
00496
00497
00498
00499
00500
00501 const unit_map::const_iterator u = info_.units.find(adj[n]);
00502 if (u != info_.units.end() && u->second.emits_zoc()
00503 && current_team().is_enemy(u->second.side())) {
00504 if (u->second.invisible(adj[n], info_.units, info_.teams)) {
00505 to = *i;
00506 steps.erase(i,steps.end());
00507 break;
00508 } else {
00509 if (!u_it->second.get_ability_bool("skirmisher",*i)){
00510 LOG_STREAM(err, ai) << "AI tried to skirmish with non-skirmisher\n";
00511 LOG_AI << "\tresetting destination from " <<to;
00512 to = *i;
00513 LOG_AI << " to " << to;
00514 steps.erase(i,steps.end());
00515 while(steps.empty() == false && (!(info_.units.find(to) == info_.units.end() || from == to))){
00516 to = *(steps.end()-1);
00517 steps.pop_back();
00518 LOG_AI << "\tresetting to " << from << " -> " << to << '\n';
00519 }
00520
00521 break;
00522 }
00523 }
00524 }
00525 }
00526
00527 if(n != 6) {
00528 u_it->second.set_movement(0);
00529 break;
00530 }
00531 }
00532 }
00533
00534 steps.push_back(to);
00535
00536 if(show_move && unit_display::unit_visible_on_path(steps,
00537 u_it->second, info_.units,info_.teams)) {
00538
00539
00540 info_.disp.display_unit_hex(from);
00541
00542 unit_map::iterator up = info_.units.find(u_it->first);
00543 unit_display::move_unit(steps,up->second,info_.teams);
00544 }
00545 }
00546 }
00547
00548 std::pair<gamemap::location,unit> *p = info_.units.extract(u_it->first);
00549
00550 p->first = to;
00551 info_.units.add(p);
00552 p->second.set_standing(p->first);
00553 if(info_.map.is_village(to)) {
00554
00555 if (!info_.teams[info_.team_num-1].owns_village(to))
00556 info_.units.find(to)->second.set_movement(-1);
00557 get_village(to,info_.disp,info_.teams,info_.team_num-1,info_.units);
00558 }
00559
00560 if(show_move) {
00561 info_.disp.invalidate(to);
00562 info_.disp.draw();
00563 }
00564
00565 recorder.add_movement(from,to);
00566
00567 game_events::fire("moveto",to);
00568
00569 if((info_.teams.front().uses_fog() || info_.teams.front().uses_shroud()) &&
00570 !info_.teams.front().fogged(to)) {
00571 game_events::fire("sighted",to);
00572 }
00573
00574
00575
00576 raise_unit_moved();
00577
00578 return to;
00579 }
00580
00581 bool ai::multistep_move_possible(const location& from,
00582 const location& to, const location& via,
00583 const std::map<location,paths>& possible_moves) const
00584 {
00585 const unit_map::const_iterator i = units_.find(from);
00586 if(i != units_.end()) {
00587 if(from != via && to != via && units_.count(via) == 0) {
00588 LOG_AI << "when seeing if leader can move from "
00589 << from << " -> " << to
00590 << " seeing if can detour to keep at " << via << '\n';
00591 const std::map<location,paths>::const_iterator moves = possible_moves.find(from);
00592 if(moves != possible_moves.end()) {
00593
00594 LOG_AI << "found leader moves..\n";
00595
00596 int move_left = 0;
00597
00598
00599
00600 const paths::routes_map::const_iterator itor = moves->second.routes.find(via);
00601 if(itor != moves->second.routes.end()) {
00602 move_left = itor->second.move_left;
00603 LOG_AI << "can make it to keep with " << move_left << " movement left\n";
00604 unit temp_unit(i->second);
00605 temp_unit.set_movement(move_left);
00606 const temporary_unit_placer unit_placer(units_,via,temp_unit);
00607 const paths unit_paths(map_,units_,via,teams_,false,false,current_team());
00608
00609 LOG_AI << "found " << unit_paths.routes.size() << " moves for temp leader\n";
00610
00611
00612 if(unit_paths.routes.count(to) != 0) {
00613 LOG_AI << "can make it back to the keep\n";
00614 return true;
00615 }
00616 }
00617 }
00618 }
00619 }
00620
00621 return false;
00622 }
00623
00624 gamemap::location ai::move_unit(location from, location to, std::map<location,paths>& possible_moves)
00625 {
00626 std::map<location,paths> temp_possible_moves;
00627 std::map<location,paths>* possible_moves_ptr = &possible_moves;
00628
00629 const unit_map::const_iterator i = units_.find(from);
00630 if(i != units_.end() && i->second.can_recruit()) {
00631
00632
00633
00634 const gamemap::location& start_pos = nearest_keep(i->first);
00635
00636
00637 if(multistep_move_possible(from,to,start_pos,possible_moves)) {
00638 from = ai_interface::move_unit(from,start_pos,possible_moves);
00639 if(from != start_pos) {
00640 return from;
00641 }
00642
00643 const unit_map::iterator itor = units_.find(from);
00644 if(itor != units_.end()) {
00645
00646
00647 itor->second.set_movement(itor->second.total_movement()-1);
00648 }
00649
00650 move_map srcdst, dstsrc;
00651 calculate_possible_moves(temp_possible_moves,srcdst,dstsrc,false);
00652 possible_moves_ptr = &temp_possible_moves;
00653 }
00654
00655 do_recruitment();
00656 }
00657
00658 if(units_.count(to) == 0 || from == to) {
00659 const location res = ai_interface::move_unit(from,to,*possible_moves_ptr);
00660 if(res != to) {
00661
00662 adjacent_tiles_array locs;
00663 get_adjacent_tiles(res,locs.data());
00664 for(adjacent_tiles_array::const_iterator adj_i = locs.begin(); adj_i != locs.end(); ++adj_i) {
00665 const unit_map::const_iterator itor = units_.find(*adj_i);
00666 if(itor != units_.end() && current_team().is_enemy(itor->second.side()) &&
00667 !itor->second.incapacitated()) {
00668 battle_context bc(map_, teams_, units_, state_,
00669 res, *adj_i, -1, -1, current_team().aggression());
00670 attack_enemy(res,itor->first,bc.get_attacker_stats().attack_num,bc.get_defender_stats().attack_num);
00671 break;
00672 }
00673 }
00674 }
00675
00676 return res;
00677 } else {
00678 return from;
00679 }
00680 }
00681
00682 bool ai::attack_close(const gamemap::location& loc) const
00683 {
00684 for(std::set<location>::const_iterator i = attacks_.begin(); i != attacks_.end(); ++i) {
00685 if(distance_between(*i,loc) < 4) {
00686 return true;
00687 }
00688 }
00689
00690 return false;
00691 }
00692
00693 void ai::attack_enemy(const location& attacking_unit, const location& target,
00694 int att_weapon, int def_weapon)
00695 {
00696 attacks_.insert(attacking_unit);
00697 ai_interface::attack_enemy(attacking_unit,target,att_weapon,def_weapon);
00698 }
00699
00700 void ai_interface::calculate_possible_moves(std::map<location,paths>& res, move_map& srcdst,
00701 move_map& dstsrc, bool enemy, bool assume_full_movement,
00702 const std::set<gamemap::location>* remove_destinations) const
00703 {
00704 calculate_moves(info_.units,res,srcdst,dstsrc,enemy,assume_full_movement,remove_destinations);
00705 }
00706
00707 void ai_interface::calculate_moves(const unit_map& units, std::map<location,paths>& res, move_map& srcdst,
00708 move_map& dstsrc, bool enemy, bool assume_full_movement,
00709 const std::set<gamemap::location>* remove_destinations,
00710 bool see_all
00711 ) const
00712 {
00713
00714 for(unit_map::const_iterator un_it = units.begin(); un_it != units.end(); ++un_it) {
00715
00716
00717
00718 if((enemy && current_team().is_enemy(un_it->second.side()) == false) ||
00719 (!enemy && !assume_full_movement && un_it->second.side() != info_.team_num) ||
00720 (!enemy && assume_full_movement && current_team().is_enemy(un_it->second.side()))) {
00721 continue;
00722 }
00723
00724 if(un_it->second.incapacitated()) {
00725 continue;
00726 }
00727
00728
00729 if(enemy && un_it->second.invisible(un_it->first,units,info_.teams) && !see_all) {
00730 continue;
00731 }
00732
00733 unit* held_unit = const_cast<unit*>(&(un_it->second));
00734 const unit_movement_resetter move_resetter(*held_unit,enemy || assume_full_movement);
00735
00736
00737 if(un_it->second.movement_left() == un_it->second.total_movement()) {
00738 std::pair<location,location> trivial_mv(un_it->first,un_it->first);
00739 srcdst.insert(trivial_mv);
00740 dstsrc.insert(trivial_mv);
00741 }
00742 const bool teleports = un_it->second.get_ability_bool("teleport",un_it->first);
00743 res.insert(std::pair<gamemap::location,paths>(
00744 un_it->first,paths(info_.map,units,
00745 un_it->first,info_.teams,false,teleports,
00746 current_team(),0,see_all)));
00747 }
00748
00749 for(std::map<location,paths>::iterator m = res.begin(); m != res.end(); ++m) {
00750 for(paths::routes_map::iterator rtit =
00751 m->second.routes.begin(); rtit != m->second.routes.end(); ++rtit) {
00752 const location& src = m->first;
00753 const location& dst = rtit->first;
00754
00755 if(remove_destinations != NULL && remove_destinations->count(dst) != 0) {
00756 continue;
00757 }
00758
00759 bool friend_owns = false;
00760
00761
00762 if(!enemy && info_.map.is_village(dst)) {
00763 for(size_t n = 0; n != info_.teams.size(); ++n) {
00764 if(info_.teams[n].owns_village(dst)) {
00765 if(n+1 != info_.team_num && current_team().is_enemy(n+1) == false) {
00766 friend_owns = true;
00767 }
00768
00769 break;
00770 }
00771 }
00772 }
00773
00774 if(friend_owns) {
00775 continue;
00776 }
00777
00778 if(src != dst && units.find(dst) == units.end()) {
00779 srcdst.insert(std::pair<location,location>(src,dst));
00780 dstsrc.insert(std::pair<location,location>(dst,src));
00781 }
00782 }
00783 }
00784 }
00785
00786 void ai::remove_unit_from_moves(const gamemap::location& loc, move_map& srcdst, move_map& dstsrc)
00787 {
00788 srcdst.erase(loc);
00789 for(move_map::iterator i = dstsrc.begin(); i != dstsrc.end(); ) {
00790 if(i->second == loc) {
00791 dstsrc.erase(i++);
00792 } else {
00793 ++i;
00794 }
00795 }
00796 }
00797
00798 namespace {
00799
00800
00801 struct protected_item {
00802 protected_item(double value, int radius, const gamemap::location& loc) :
00803 value(value), radius(radius), loc(loc) {}
00804
00805 double value;
00806 int radius;
00807 gamemap::location loc;
00808 };
00809
00810 }
00811
00812
00813 void ai::find_threats()
00814 {
00815 if(threats_found_) {
00816 return;
00817 }
00818
00819 threats_found_ = true;
00820
00821 const config& parms = current_team().ai_parameters();
00822
00823 std::vector<protected_item> items;
00824
00825
00826 const unit_map::const_iterator leader = find_leader(units_,team_num_);
00827 if(leader != units_.end()) {
00828 items.push_back(protected_item(
00829 lexical_cast_default<double>(parms["protect_leader"], 1.0),
00830 lexical_cast_default<int>(parms["protect_leader_radius"], 20),
00831 leader->first));
00832 }
00833
00834
00835 const config::child_list& locations = parms.get_children("protect_location");
00836 for(config::child_list::const_iterator i = locations.begin(); i != locations.end(); ++i) {
00837 items.push_back(protected_item(
00838 lexical_cast_default<double>((**i)["value"], 1.0),
00839 lexical_cast_default<int>((**i)["radius"], 20),
00840 gamemap::location(**i, &get_info().game_state_)));
00841 }
00842
00843
00844 const config::child_list& protected_units = parms.get_children("protect_unit");
00845 for(config::child_list::const_iterator j = protected_units.begin(); j != protected_units.end(); ++j) {
00846
00847 for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
00848 if(game_events::unit_matches_filter(u, *j)) {
00849 items.push_back(protected_item(
00850 lexical_cast_default<double>((**j)["value"], 1.0),
00851 lexical_cast_default<int>((**j)["radius"], 20),
00852 u->first));
00853 }
00854 }
00855 }
00856
00857
00858
00859 for(std::vector<protected_item>::const_iterator k = items.begin(); k != items.end(); ++k) {
00860 const protected_item& item = *k;
00861
00862 for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
00863 const int distance = distance_between(u->first,item.loc);
00864 if(current_team().is_enemy(u->second.side()) && distance < item.radius) {
00865 add_target(target(u->first, item.value * double(item.radius-distance) /
00866 double(item.radius),target::THREAT));
00867 }
00868 }
00869 }
00870 }
00871
00872 void ai::play_turn()
00873 {
00874
00875
00876
00877
00878 try {
00879 consider_combat_ = true;
00880 game_events::fire("ai turn");
00881 do_move();
00882 } catch(std::bad_alloc) {
00883 lg::wml_error << "Memory exhausted - a unit has either a lot of hitpoints or a negative amount.\n";
00884 }
00885 }
00886
00887 void ai::do_move()
00888 {
00889 log_scope2(ai, "doing ai move");
00890
00891 invalidate_defensive_position_cache();
00892
00893 raise_user_interact();
00894
00895 typedef paths::route route;
00896
00897 typedef std::map<location,paths> moves_map;
00898 moves_map possible_moves, enemy_possible_moves;
00899
00900 move_map srcdst, dstsrc, enemy_srcdst, enemy_dstsrc;
00901
00902 calculate_possible_moves(possible_moves,srcdst,dstsrc,false,false,&avoided_locations());
00903 calculate_possible_moves(enemy_possible_moves,enemy_srcdst,enemy_dstsrc,true);
00904
00905 const bool passive_leader = utils::string_bool(current_team().ai_parameters()["passive_leader"]);
00906
00907 if (passive_leader) {
00908 unit_map::iterator leader = find_leader(units_,team_num_);
00909 if(leader != units_.end()) {
00910 remove_unit_from_moves(leader->first,srcdst,dstsrc);
00911 }
00912 }
00913
00914
00915 std::vector<gamemap::location> gotos;
00916
00917 for(unit_map::iterator ui = units_.begin(); ui != units_.end(); ++ui) {
00918 if(ui->second.get_goto() == ui->first) {
00919 ui->second.set_goto(gamemap::location());
00920 } else if(ui->second.side() == team_num_ && map_.on_board(ui->second.get_goto())) {
00921 gotos.push_back(ui->first);
00922 }
00923 }
00924
00925 for(std::vector<gamemap::location>::const_iterator g = gotos.begin(); g != gotos.end(); ++g) {
00926 unit_map::const_iterator ui = units_.find(*g);
00927 int closest_distance = -1;
00928 std::pair<location,location> closest_move;
00929 for(move_map::const_iterator i = dstsrc.begin(); i != dstsrc.end(); ++i) {
00930 if(i->second != ui->first) {
00931 continue;
00932 }
00933 const int distance = distance_between(i->first,ui->second.get_goto());
00934 if(closest_distance == -1 || distance < closest_distance) {
00935 closest_distance = distance;
00936 closest_move = *i;
00937 }
00938 }
00939
00940 if(closest_distance != -1) {
00941 move_unit(ui->first,closest_move.first,possible_moves);
00942 }
00943 }
00944
00945
00946 std::vector<attack_analysis> analysis;
00947
00948 LOG_AI << "combat phase\n";
00949
00950 if(consider_combat_) {
00951 LOG_AI << "combat...\n";
00952 consider_combat_ = do_combat(possible_moves,srcdst,dstsrc,enemy_srcdst,enemy_dstsrc);
00953 if(consider_combat_) {
00954 do_move();
00955 return;
00956 }
00957 }
00958
00959 move_leader_to_goals(enemy_dstsrc);
00960
00961 LOG_AI << "get villages phase\n";
00962
00963
00964 unit_map::iterator leader = find_leader(units_,team_num_);
00965
00966 LOG_AI << "villages...\n";
00967 if(get_villages(possible_moves, dstsrc, enemy_dstsrc, leader)) {
00968 do_move();
00969 return;
00970 }
00971
00972 LOG_AI << "healing...\n";
00973 const bool healed_unit = get_healing(possible_moves,srcdst,enemy_dstsrc);
00974 if(healed_unit) {
00975 do_move();
00976 return;
00977 }
00978
00979 LOG_AI << "retreat phase\n";
00980
00981 LOG_AI << "retreating...\n";
00982
00983 leader = find_leader(units_,team_num_);
00984 const bool retreated_unit = retreat_units(possible_moves,srcdst,dstsrc,enemy_dstsrc,leader);
00985 if(retreated_unit) {
00986 do_move();
00987 return;
00988 }
00989
00990 if(leader != units_.end()) {
00991 remove_unit_from_moves(leader->first,srcdst,dstsrc);
00992 }
00993
00994 find_threats();
00995
00996 LOG_AI << "move/targetting phase\n";
00997
00998 const bool met_invisible_unit = move_to_targets(possible_moves,srcdst,dstsrc,enemy_dstsrc,leader);
00999 if(met_invisible_unit) {
01000 LOG_AI << "met_invisible_unit\n";
01001 do_move();
01002 return;
01003 }
01004
01005 LOG_AI << "done move to targets\n";
01006
01007 LOG_AI << "leader/recruitment phase\n";
01008
01009
01010 if(leader != units_.end()) {
01011
01012 if(!passive_leader) {
01013 move_leader_to_keep(enemy_dstsrc);
01014 }
01015
01016 do_recruitment();
01017
01018 if(!passive_leader) {
01019 move_leader_after_recruit(srcdst,dstsrc,enemy_dstsrc);
01020 }
01021 }
01022 }
01023
01024 bool ai::do_combat(std::map<gamemap::location,paths>& possible_moves, const move_map& srcdst,
01025 const move_map& dstsrc, const move_map& enemy_srcdst, const move_map& enemy_dstsrc)
01026 {
01027 int ticks = SDL_GetTicks();
01028
01029 std::vector<attack_analysis> analysis = analyze_targets(srcdst, dstsrc,
01030 enemy_srcdst, enemy_dstsrc);
01031
01032 int time_taken = SDL_GetTicks() - ticks;
01033 LOG_AI << "took " << time_taken << " ticks for " << analysis.size()
01034 << " positions. Analyzing...\n";
01035
01036 ticks = SDL_GetTicks();
01037
01038 const int max_sims = 50000;
01039 int num_sims = analysis.empty() ? 0 : max_sims/analysis.size();
01040 if(num_sims < 20)
01041 num_sims = 20;
01042 if(num_sims > 40)
01043 num_sims = 40;
01044
01045 LOG_AI << "simulations: " << num_sims << "\n";
01046
01047 const int max_positions = 30000;
01048 const int skip_num = analysis.size()/max_positions;
01049
01050 std::vector<attack_analysis>::iterator choice_it = analysis.end();
01051 double choice_rating = -1000.0;
01052 for(std::vector<attack_analysis>::iterator it = analysis.begin();
01053 it != analysis.end(); ++it) {
01054
01055 if(skip_num > 0 && ((it - analysis.begin())%skip_num) && it->movements.size() > 1)
01056 continue;
01057
01058 const double rating = it->rating(current_team().aggression(),*this);
01059 LOG_AI << "attack option rated at " << rating << " ("
01060 << current_team().aggression() << ")\n";
01061
01062 if(rating > choice_rating) {
01063 choice_it = it;
01064 choice_rating = rating;
01065 }
01066 }
01067
01068 time_taken = SDL_GetTicks() - ticks;
01069 LOG_AI << "analysis took " << time_taken << " ticks\n";
01070
01071 if(choice_rating > 0.0) {
01072 location from = choice_it->movements[0].first;
01073 location to = choice_it->movements[0].second;
01074 location target_loc = choice_it->target;
01075
01076
01077
01078
01079 const location arrived_at = move_unit(from,to,possible_moves);
01080 if(arrived_at != to || units_.find(to) == units_.end()) {
01081 LOG_STREAM(warn, ai) << "unit moving to attack has ended up unexpectedly at "
01082 << arrived_at << " when moving to " << to << " moved from "
01083 << from << '\n';
01084 return true;
01085 }
01086
01087
01088 battle_context bc(map_, teams_, units_, state_,
01089 to, target_loc, -1, -1,
01090 current_team().aggression());
01091 attack_enemy(to, target_loc, bc.get_attacker_stats().attack_num,
01092 bc.get_defender_stats().attack_num);
01093
01094
01095
01096 if(choice_it->movements.size() == 1 && units_.count(target_loc)) {
01097 add_target(target(target_loc,3.0,target::BATTLE_AID));
01098 }
01099
01100 return true;
01101
01102 } else {
01103 return false;
01104 }
01105 }
01106
01107
01108
01109
01110
01111
01112
01113
01114
01115
01116
01117
01118 void ai_interface::attack_enemy(const location u,
01119 const location target, int weapon, int def_weapon)
01120 {
01121
01122 const events::command_disabler disable_commands;
01123
01124 if(info_.units.count(u) && info_.units.count(target)) {
01125 if(info_.units.find(target)->second.incapacitated()) {
01126 LOG_STREAM(err, ai) << "attempt to attack unit that is turned to stone\n";
01127 return;
01128 }
01129 if(!info_.units.find(u)->second.attacks_left()) {
01130 LOG_STREAM(err, ai) << "attempt to attack twice with the same unit\n";
01131 return;
01132 }
01133
01134 if(weapon >= 0) {
01135 recorder.add_attack(u,target,weapon,def_weapon);
01136 }
01137 try {
01138 attack(info_.disp, info_.map, info_.teams, u, target, weapon, def_weapon,
01139 info_.units, info_.state);
01140 }
01141 catch (end_level_exception&)
01142 {
01143 dialogs::advance_unit(info_.map,info_.units,u,info_.disp,true);
01144
01145 const unit_map::const_iterator defender = info_.units.find(target);
01146 if(defender != info_.units.end()) {
01147 const size_t defender_team = size_t(defender->second.side()) - 1;
01148 if(defender_team < info_.teams.size()) {
01149 dialogs::advance_unit(info_.map, info_.units,
01150 target, info_.disp, !info_.teams[defender_team].is_human());
01151 }
01152 }
01153
01154 throw;
01155 }
01156 dialogs::advance_unit(info_.map,info_.units,u,info_.disp,true);
01157
01158 const unit_map::const_iterator defender = info_.units.find(target);
01159 if(defender != info_.units.end()) {
01160 const size_t defender_team = size_t(defender->second.side()) - 1;
01161 if(defender_team < info_.teams.size()) {
01162 dialogs::advance_unit(info_.map, info_.units,
01163 target, info_.disp, !info_.teams[defender_team].is_human());
01164 }
01165 }
01166
01167 check_victory(info_.units,info_.teams, info_.disp);
01168 raise_enemy_attacked();
01169 }
01170 }
01171
01172 bool ai::get_healing(std::map<gamemap::location,paths>& possible_moves,
01173 const move_map& srcdst, const move_map& enemy_dstsrc)
01174 {
01175
01176 unit_map::iterator u_it = units_.begin();
01177 for(; u_it != units_.end(); ++u_it) {
01178 unit& u = u_it->second;
01179
01180
01181
01182
01183 if(u.side() == team_num_ &&
01184 u.max_hitpoints() - u.hitpoints() >= game_config::poison_amount/2 &&
01185 !u.get_ability_bool("regenerate",u_it->first)) {
01186
01187
01188 typedef std::multimap<location,location>::const_iterator Itor;
01189 std::pair<Itor,Itor> it = srcdst.equal_range(u_it->first);
01190 double best_vulnerability = 100000.0;
01191 Itor best_loc = it.second;
01192 while(it.first != it.second) {
01193 const location& dst = it.first->second;
01194 if(map_.gives_healing(dst) && (units_.find(dst) == units_.end() || dst == u_it->first)) {
01195 const double vuln = power_projection(it.first->first, enemy_dstsrc);
01196 LOG_AI << "found village with vulnerability: " << vuln << "\n";
01197 if(vuln < best_vulnerability || best_loc == it.second) {
01198 best_vulnerability = vuln;
01199 best_loc = it.first;
01200 LOG_AI << "chose village " << dst << '\n';
01201 }
01202 }
01203
01204 ++it.first;
01205 }
01206
01207
01208 if(best_loc != it.second) {
01209 const location& src = best_loc->first;
01210 const location& dst = best_loc->second;
01211
01212 LOG_AI << "moving unit to village for healing...\n";
01213
01214 move_unit(src,dst,possible_moves);
01215 return true;
01216 }
01217 }
01218 }
01219
01220 return false;
01221 }
01222
01223 bool ai::should_retreat(const gamemap::location& loc, const unit_map::const_iterator un,
01224 const move_map& srcdst, const move_map& dstsrc, const move_map& enemy_dstsrc,
01225 double caution)
01226 {
01227 if(caution <= 0.0) {
01228 return false;
01229 }
01230
01231 const double optimal_terrain = best_defensive_position(un->first, dstsrc,
01232 srcdst, enemy_dstsrc).chance_to_hit/100.0;
01233 const double proposed_terrain =
01234 un->second.defense_modifier(map_.get_terrain(loc))/100.0;
01235
01236
01237
01238 const double exposure = proposed_terrain - optimal_terrain;
01239
01240 const double our_power = power_projection(loc,dstsrc);
01241 const double their_power = power_projection(loc,enemy_dstsrc);
01242 return caution*their_power*(1.0+exposure) > our_power;
01243 }
01244
01245 bool ai::retreat_units(std::map<gamemap::location,paths>& possible_moves,
01246 const move_map& srcdst, const move_map& dstsrc,
01247 const move_map& enemy_dstsrc, unit_map::const_iterator leader)
01248 {
01249
01250 std::map<gamemap::location,paths> dummy_possible_moves;
01251 move_map fullmove_srcdst;
01252 move_map fullmove_dstsrc;
01253 calculate_possible_moves(dummy_possible_moves, fullmove_srcdst, fullmove_dstsrc,
01254 false, true, &avoided_locations());
01255
01256 gamemap::location leader_adj[6];
01257 if(leader != units_.end()) {
01258 get_adjacent_tiles(leader->first,leader_adj);
01259 }
01260
01261 for(unit_map::iterator i = units_.begin(); i != units_.end(); ++i) {
01262 if(i->second.side() == team_num_ &&
01263 i->second.movement_left() == i->second.total_movement() &&
01264 unit_map::const_iterator(i) != leader &&
01265 !i->second.incapacitated()) {
01266
01267
01268
01269
01270 if(should_retreat(i->first, i, fullmove_srcdst, fullmove_dstsrc,
01271 enemy_dstsrc, current_team().caution())) {
01272
01273 bool can_reach_leader = false;
01274
01275
01276
01277
01278
01279 typedef move_map::const_iterator Itor;
01280 std::pair<Itor,Itor> itors = srcdst.equal_range(i->first);
01281 gamemap::location best_pos, best_defensive(i->first);
01282 double best_rating = 0.0;
01283 int best_defensive_rating = i->second.defense_modifier(map_.get_terrain(i->first))
01284 - (map_.is_village(i->first) ? 10 : 0);
01285 while(itors.first != itors.second) {
01286
01287 if(leader != units_.end() && std::count(leader_adj,
01288 leader_adj + 6, itors.first->second)) {
01289
01290 can_reach_leader = true;
01291 break;
01292 }
01293
01294
01295
01296
01297 const gamemap::location& hex = itors.first->second;
01298 const int defense = i->second.defense_modifier(map_.get_terrain(hex));
01299 const double our_power = power_projection(hex,dstsrc);
01300 const double their_power = power_projection(hex,enemy_dstsrc) * double(defense)/100.0;
01301 const double rating = our_power - their_power;
01302 if(rating > best_rating) {
01303 best_pos = hex;
01304 best_rating = rating;
01305 }
01306
01307
01308 const int modified_defense = defense - (map_.is_village(hex) ? 10 : 0);
01309
01310 if(modified_defense < best_defensive_rating) {
01311 best_defensive_rating = modified_defense;
01312 best_defensive = hex;
01313 }
01314
01315 ++itors.first;
01316 }
01317
01318
01319
01320 if(can_reach_leader) {
01321 continue;
01322 }
01323
01324 if(!best_pos.valid()) {
01325 best_pos = best_defensive;
01326 }
01327
01328
01329 if (best_pos == i->first) {
01330 if (i->second.attacks_left()) {
01331 return desperate_attack(i->first);
01332 }
01333 return false;
01334 }
01335
01336 if(best_pos.valid()) {
01337 LOG_AI << "retreating '" << i->second.type_id() << "' " << i->first
01338 << " -> " << best_pos << '\n';
01339 move_unit(i->first,best_pos,possible_moves);
01340 return true;
01341 }
01342 }
01343 }
01344 }
01345
01346 return false;
01347 }
01348
01349 bool ai::move_to_targets(std::map<gamemap::location, paths>& possible_moves,
01350 move_map& srcdst, move_map& dstsrc, const move_map& enemy_dstsrc,
01351 unit_map::const_iterator leader)
01352 {
01353 LOG_AI << "finding targets...\n";
01354 std::vector<target> targets;
01355 for(;;) {
01356 if(targets.empty()) {
01357 targets = find_targets(leader,enemy_dstsrc);
01358 targets.insert(targets.end(),additional_targets_.begin(),
01359 additional_targets_.end());
01360 if(targets.empty()) {
01361 return false;
01362 }
01363 }
01364
01365 LOG_AI << "choosing move...\n";
01366 std::pair<location,location> move = choose_move(targets, srcdst,
01367 dstsrc, enemy_dstsrc);
01368
01369 for(std::vector<target>::const_iterator ittg = targets.begin();
01370 ittg != targets.end(); ++ittg) {
01371 assert(map_.on_board(ittg->loc));
01372 }
01373
01374 if(move.first.valid() == false) {
01375 break;
01376 }
01377
01378 if(move.second.valid() == false) {
01379 return true;
01380 }
01381
01382 LOG_AI << "move: " << move.first << " -> " << move.second << '\n';
01383
01384 const location arrived_at = move_unit(move.first,move.second,possible_moves);
01385
01386
01387
01388
01389 if(arrived_at != move.second) {
01390 LOG_STREAM(warn, ai) << "didn't arrive at destination\n";
01391 return true;
01392 }
01393
01394 const unit_map::const_iterator u_it = units_.find(arrived_at);
01395
01396 if (u_it == units_.end() || u_it->second.incapacitated()) {
01397 LOG_STREAM(warn, ai) << "stolen or incapacitated\n";
01398 } else {
01399
01400
01401 gamemap::location adj[6];
01402 get_adjacent_tiles(arrived_at,adj);
01403 gamemap::location target;
01404
01405 for(int n = 0; n != 6; ++n) {
01406 const unit_map::iterator enemy = find_visible_unit(units_,adj[n],
01407 map_,
01408 teams_,current_team());
01409
01410 if(enemy != units_.end() &&
01411 current_team().is_enemy(enemy->second.side()) && !enemy->second.incapacitated()) {
01412
01413 battle_context bc(map_, teams_, units_, state_, arrived_at, adj[n], -1, -1, 100.0);
01414 if (bc.get_defender_stats().damage == 0) {
01415 attack_enemy(arrived_at, adj[n], bc.get_attacker_stats().attack_num,
01416 bc.get_defender_stats().attack_num);
01417 break;
01418 }
01419 }
01420 }
01421 }
01422
01423
01424
01425 typedef move_map::iterator Itor;
01426 std::pair<Itor,Itor> del = dstsrc.equal_range(arrived_at);
01427 dstsrc.erase(del.first,del.second);
01428 }
01429
01430 return false;
01431 }
01432
01433 int ai::average_resistance_against(const unit_type& a, const unit_type& b) const
01434 {
01435 int weighting_sum = 0, defense = 0;
01436 const std::map<t_translation::t_terrain, size_t>& terrain =
01437 map_.get_weighted_terrain_frequencies();
01438
01439 for (std::map<t_translation::t_terrain, size_t>::const_iterator j = terrain.begin(),
01440 j_end = terrain.end(); j != j_end; ++j)
01441 {
01442
01443 if (a.movement_type().movement_cost(map_, j->first) < 99) {
01444 defense += a.movement_type().defense_modifier(map_, j->first) * j->second;
01445 weighting_sum += j->second;
01446 }
01447 }
01448
01449 if (weighting_sum == 0) {
01450
01451
01452
01453 for (std::map<t_translation::t_terrain, size_t>::const_iterator jj = terrain.begin(),
01454 jj_end = terrain.end(); jj != jj_end; ++jj)
01455 {
01456 defense += a.movement_type().defense_modifier(map_, jj->first) * jj->second;
01457 weighting_sum += jj->second;
01458 }
01459 }
01460
01461 if(weighting_sum != 0) {
01462 defense /= weighting_sum;
01463 } else {
01464 ERR_AI << "The weighting sum is 0 and is ignored.\n";
01465 }
01466
01467 LOG_AI << "average defense of '" << a.id() << "': " << defense << "\n";
01468
01469 int sum = 0, weight_sum = 0;
01470
01471
01472 bool steadfast = a.has_ability_by_id("steadfast");
01473 bool living = !a.not_living();
01474 const std::vector<attack_type>& attacks = b.attacks();
01475 for (std::vector<attack_type>::const_iterator i = attacks.begin(),
01476 i_end = attacks.end(); i != i_end; ++i)
01477 {
01478 int resistance = a.movement_type().resistance_against(*i);
01479
01480 if (steadfast && resistance < 100)
01481 resistance = maximum<int>(resistance * 2 - 100, 50);
01482
01483 int cth = i->get_special_bool("chance_to_hit", true) ? 70 : defense;
01484 int weight = i->damage() * i->num_attacks();
01485
01486 if (living && cth != 0 && i->get_special_bool("poison", true)) {
01487
01488 int prob = 100;
01489 for (int j = 0; j < i->num_attacks(); ++j)
01490 prob = prob * (100 - cth);
01491
01492 weight += game_config::poison_amount * (100 - prob) / 100;
01493 }
01494 sum += cth * resistance * weight * weight;
01495 weight_sum += weight;
01496 }
01497
01498
01499 sum /= maximum<int>(1,minimum<int>(a.hitpoints(),1000));
01500
01501
01502
01503
01504
01505 if (weight_sum == 0) {
01506 return sum;
01507 }
01508 return sum/weight_sum;
01509 }
01510
01511 int ai::compare_unit_types(const unit_type& a, const unit_type& b) const
01512 {
01513 const int a_effectiveness_vs_b = average_resistance_against(b,a);
01514 const int b_effectiveness_vs_a = average_resistance_against(a,b);
01515
01516 LOG_AI << "comparison of '" << a.id() << " vs " << b.id() << ": "
01517 << a_effectiveness_vs_b << " - " << b_effectiveness_vs_a << " = "
01518 << (a_effectiveness_vs_b - b_effectiveness_vs_a) << "\n";
01519 return a_effectiveness_vs_b - b_effectiveness_vs_a;
01520 }
01521
01522 void ai::analyze_potential_recruit_combat()
01523 {
01524 if(unit_combat_scores_.empty() == false ||
01525 utils::string_bool(current_team().ai_parameters()["recruitment_ignore_bad_combat"])) {
01526 return;
01527 }
01528
01529 log_scope2(ai, "analyze_potential_recruit_combat()");
01530
01531
01532 std::map<std::string,int> best_usage;
01533
01534 const std::set<std::string>& recruits = current_team().recruits();
01535 std::set<std::string>::const_iterator i;
01536 for(i = recruits.begin(); i != recruits.end(); ++i) {
01537 const unit_type_data::unit_type_map::const_iterator info = unit_type_data::types().find(*i);
01538 if(info == unit_type_data::types().end() || not_recommended_units_.count(*i)) {
01539 continue;
01540 }
01541
01542 int score = 0, weighting = 0;
01543
01544 for(unit_map::const_iterator j = units_.begin(); j != units_.end(); ++j) {
01545 if(j->second.can_recruit() || current_team().is_enemy(j->second.side()) == false) {
01546 continue;
01547 }
01548
01549 unit const &un = j->second;
01550 const unit_type_data::unit_type_map::const_iterator enemy_info = unit_type_data::types().find(un.type_id());
01551 VALIDATE((enemy_info != unit_type_data::types().end()), _("Unknown unit type : ") + un.type_id());
01552
01553 int weight = un.cost() * un.hitpoints() / un.max_hitpoints();
01554 weighting += weight;
01555 score += compare_unit_types(info->second, enemy_info->second) * weight;
01556 }
01557
01558 if(weighting != 0) {
01559 score /= weighting;
01560 }
01561
01562 LOG_AI << "combat score of '" << *i << "': " << score << "\n";
01563 unit_combat_scores_[*i] = score;
01564
01565 if(best_usage.count(info->second.usage()) == 0 ||
01566 score > best_usage[info->second.usage()]) {
01567 best_usage[info->second.usage()] = score;
01568 }
01569 }
01570
01571
01572
01573
01574 for(i = recruits.begin(); i != recruits.end(); ++i) {
01575 const unit_type_data::unit_type_map::const_iterator info = unit_type_data::types().find(*i);
01576 if(info == unit_type_data::types().end() || not_recommended_units_.count(*i)) {
01577 continue;
01578 }
01579
01580 if(unit_combat_scores_[*i] + 600 < best_usage[info->second.usage()]) {
01581 LOG_AI << "recommending not to use '" << *i << "' because of poor combat performance "
01582 << unit_combat_scores_[*i] << "/" << best_usage[info->second.usage()] << "\n";
01583 not_recommended_units_.insert(*i);
01584 }
01585 }
01586 }
01587
01588 namespace {
01589
01590 struct target_comparer_distance {
01591 target_comparer_distance(const gamemap::location& loc) : loc_(loc) {}
01592
01593 bool operator()(const ai::target& a, const ai::target& b) const {
01594 return distance_between(a.loc,loc_) < distance_between(b.loc,loc_);
01595 }
01596
01597 private:
01598 gamemap::location loc_;
01599 };
01600
01601 }
01602
01603 void ai::analyze_potential_recruit_movements()
01604 {
01605 if(unit_movement_scores_.empty() == false ||
01606 utils::string_bool(current_team().ai_parameters()["recruitment_ignore_bad_movement"])) {
01607 return;
01608 }
01609
01610 const unit_map::const_iterator leader = find_leader(units_,team_num_);
01611 if(leader == units_.end()) {
01612 return;
01613 }
01614
01615 const location& start = nearest_keep(leader->first);
01616 if(map_.on_board(start) == false) {
01617 return;
01618 }
01619
01620 log_scope2(ai, "analyze_potential_recruit_movements()");
01621
01622 const unsigned int max_targets = 5;
01623
01624 const move_map srcdst, dstsrc;
01625 std::vector<target> targets = find_targets(leader,dstsrc);
01626 if(targets.size() > max_targets) {
01627 std::sort(targets.begin(),targets.end(),target_comparer_distance(start));
01628 targets.erase(targets.begin()+max_targets,targets.end());
01629 }
01630
01631 const std::set<std::string>& recruits = current_team().recruits();
01632
01633 LOG_AI << "targets: " << targets.size() << "\n";
01634
01635 std::map<std::string,int> best_scores;
01636
01637 for(std::set<std::string>::const_iterator i = recruits.begin(); i != recruits.end(); ++i) {
01638 const unit_type_data::unit_type_map::const_iterator info = unit_type_data::types().find(*i);
01639 if(info == unit_type_data::types().end()) {
01640 continue;
01641 }
01642
01643 const unit temp_unit(&get_info().units,&get_info().map,
01644 &get_info().state, &get_info().teams, &info->second, team_num_);
01645
01646
01647
01648
01649
01650 const shortest_path_calculator calc(temp_unit,current_team(),get_info().units,teams_,map_,true,true);
01651
01652 int cost = 0;
01653 int targets_reached = 0;
01654 int targets_missed = 0;
01655
01656 for(std::vector<target>::const_iterator t = targets.begin(); t != targets.end(); ++t) {
01657 LOG_AI << "analyzing '" << *i << "' getting to target...\n";
01658 const paths::route& route = a_star_search(start, t->loc, 100.0, &calc,
01659 get_info().map.w(), get_info().map.h());
01660
01661 if(route.steps.empty() == false) {
01662 LOG_AI << "made it: " << route.move_left << "\n";
01663 cost += route.move_left;
01664 ++targets_reached;
01665 } else {
01666 LOG_AI << "failed\n";
01667 ++targets_missed;
01668 }
01669 }
01670
01671 if(targets_reached == 0 || targets_missed >= targets_reached*2) {
01672 unit_movement_scores_[*i] = 100000;
01673 not_recommended_units_.insert(*i);
01674 } else {
01675 const int average_cost = cost/targets_reached;
01676 const int score = (average_cost * (targets_reached+targets_missed))/targets_reached;
01677 unit_movement_scores_[*i] = score;
01678
01679 const std::map<std::string,int>::const_iterator current_best = best_scores.find(temp_unit.usage());
01680 if(current_best == best_scores.end() || score < current_best->second) {
01681 best_scores[temp_unit.usage()] = score;
01682 }
01683 }
01684 }
01685
01686 for(std::map<std::string,int>::iterator j = unit_movement_scores_.begin();
01687 j != unit_movement_scores_.end(); ++j) {
01688
01689 const unit_type_data::unit_type_map::const_iterator info =
01690 unit_type_data::types().find(j->first);
01691
01692 if(info == unit_type_data::types().end()) {
01693 continue;
01694 }
01695
01696 const int best_score = best_scores[info->second.usage()];
01697 if(best_score > 0) {
01698 j->second = (j->second*10)/best_score;
01699 if(j->second > 15) {
01700 LOG_AI << "recommending against recruiting '" << j->first << "' (score: " << j->second << ")\n";
01701 not_recommended_units_.insert(j->first);
01702 } else {
01703 LOG_AI << "recommending recruit of '" << j->first << "' (score: " << j->second << ")\n";
01704 }
01705 }
01706 }
01707
01708 if(not_recommended_units_.size() == unit_movement_scores_.size()) {
01709 not_recommended_units_.clear();
01710 }
01711 }
01712
01713 void ai::do_recruitment()
01714 {
01715 const unit_map::const_iterator leader = find_leader(units_,team_num_);
01716 if(leader == units_.end()) {
01717 return;
01718 }
01719
01720 const location& start_pos = nearest_keep(leader->first);
01721
01722 analyze_potential_recruit_movements();
01723 analyze_potential_recruit_combat();
01724
01725 size_t neutral_villages = 0;
01726
01727
01728
01729
01730 const std::vector<location>& villages = map_.villages();
01731 for(std::vector<location>::const_iterator v = villages.begin(); v != villages.end(); ++v) {
01732 const int owner = village_owner(*v,teams_);
01733 if(owner == -1) {
01734 const size_t distance = distance_between(start_pos,*v);
01735
01736 bool closest = true;
01737 for(std::vector<team>::const_iterator i = teams_.begin(); i != teams_.end(); ++i) {
01738 const int index = i - teams_.begin() + 1;
01739 const gamemap::location& loc = map_.starting_position(index);
01740 if(loc != start_pos && distance_between(loc,*v) < distance) {
01741 closest = false;
01742 break;
01743 }
01744 }
01745
01746 if(closest) {
01747 ++neutral_villages;
01748 }
01749 }
01750 }
01751
01752
01753
01754
01755
01756 const int villages_per_scout = current_team().villages_per_scout()/2;
01757
01758
01759 int scouts_wanted = villages_per_scout > 0 ? neutral_villages/villages_per_scout : 0;
01760
01761 LOG_AI << "scouts_wanted: " << neutral_villages << "/"
01762 << villages_per_scout << " = " << scouts_wanted << "\n";
01763
01764 std::map<std::string,int> unit_types;
01765
01766 for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
01767 if(u->second.side() == team_num_) {
01768 ++unit_types[u->second.usage()];
01769 }
01770 }
01771
01772 LOG_AI << "we have " << unit_types["scout"] << " scouts already and we want "
01773 << scouts_wanted << " in total\n";
01774
01775 while(unit_types["scout"] < scouts_wanted) {
01776 if(recruit_usage("scout") == false)
01777 break;
01778
01779 ++unit_types["scout"];
01780 }
01781
01782 std::vector<std::string> options = current_team().recruitment_pattern();
01783
01784
01785
01786 if (options.empty()) {
01787 options.push_back("");
01788 }
01789
01790 while(recruit_usage(options[rand()%options.size()])) {
01791 }
01792 }
01793
01794 void ai::move_leader_to_goals( const move_map& enemy_dstsrc)
01795 {
01796 const config* const goal = current_team().ai_parameters().child("leader_goal");
01797
01798 if(goal == NULL) {
01799 LOG_AI << "No goal found\n";
01800 return;
01801 }
01802
01803 const gamemap::location dst(*goal, &get_info().game_state_);
01804 if (!dst.valid()) {
01805 ERR_AI << "Invalid goal\n";
01806 return;
01807 }
01808
01809 const unit_map::iterator leader = find_leader(units_,team_num_);
01810 if(leader == units_.end() || leader->second.incapacitated()) {
01811 WRN_AI << "Leader not found\n";
01812 return;
01813 }
01814
01815 LOG_AI << "Doing recruitment before goals\n";
01816
01817 do_recruitment();
01818
01819 shortest_path_calculator calc(leader->second, current_team(), units_, teams_, map_);
01820 const paths::route route = a_star_search(leader->first, dst, 1000.0, &calc,
01821 get_info().map.w(), get_info().map.h());
01822 if(route.steps.empty()) {
01823 LOG_AI << "route empty";
01824 return;
01825 }
01826
01827 const paths leader_paths(map_, units_, leader->first,
01828 teams_, false, false, current_team());
01829
01830 std::map<gamemap::location,paths> possible_moves;
01831 possible_moves.insert(std::pair<gamemap::location,paths>(leader->first,leader_paths));
01832
01833 gamemap::location loc;
01834 for(std::vector<gamemap::location>::const_iterator itor = route.steps.begin();
01835 itor != route.steps.end(); ++itor) {
01836
01837 if(leader_paths.routes.count(*itor) == 1 &&
01838 power_projection(*itor,enemy_dstsrc) < double(leader->second.hitpoints()/2)) {
01839 loc = *itor;
01840 }
01841 }
01842
01843 if(loc.valid()) {
01844 LOG_AI << "Moving leader to goal\n";
01845 move_unit(leader->first,loc,possible_moves);
01846 }
01847 }
01848
01849 void ai::move_leader_to_keep(const move_map& enemy_dstsrc)
01850 {
01851 const unit_map::iterator leader = find_leader(units_,team_num_);
01852 if(leader == units_.end() || leader->second.incapacitated()) {
01853 return;
01854 }
01855
01856
01857 const paths leader_paths(map_, units_, leader->first,
01858 teams_, false, false, current_team());
01859 const gamemap::location& start_pos = nearest_keep(leader->first);
01860
01861 std::map<gamemap::location,paths> possible_moves;
01862 possible_moves.insert(std::pair<gamemap::location,paths>(leader->first,leader_paths));
01863
01864
01865 if(leader->first != start_pos) {
01866 const paths::routes_map::const_iterator itor = leader_paths.routes.find(start_pos);
01867 if(itor != leader_paths.routes.end() && units_.count(start_pos) == 0) {
01868 move_unit(leader->first,start_pos,possible_moves);
01869 } else {
01870
01871
01872 std::multimap<int,gamemap::location> moves_toward_keep;
01873
01874
01875
01876 const int current_distance = distance_between(leader->first,start_pos);
01877 for(paths::routes_map::const_iterator i = leader_paths.routes.begin();
01878 i != leader_paths.routes.end(); ++i) {
01879
01880 const int new_distance = distance_between(i->first,start_pos);
01881 if(new_distance < current_distance) {
01882 moves_toward_keep.insert(std::pair<int,gamemap::location>(new_distance,i->first));
01883 }
01884 }
01885
01886
01887
01888 for(std::multimap<int,gamemap::location>::const_iterator j = moves_toward_keep.begin();
01889 j != moves_toward_keep.end(); ++j) {
01890
01891 if(enemy_dstsrc.count(j->second) == 0) {
01892 move_unit(leader->first,j->second,possible_moves);
01893 break;
01894 }
01895 }
01896 }
01897 }
01898 }
01899
01900 void ai::move_leader_after_recruit(const move_map& ,
01901 const move_map& , const move_map& enemy_dstsrc)
01902 {
01903 LOG_AI << "moving leader after recruit...\n";
01904
01905 unit_map::iterator leader = find_leader(units_,team_num_);
01906 if(leader == units_.end() || leader->second.incapacitated()) {
01907 return;
01908 }
01909
01910 const paths leader_paths(map_, units_, leader->first,
01911 teams_, false, false, current_team());
01912
01913 std::map<gamemap::location,paths> possible_moves;
01914 possible_moves.insert(std::pair<gamemap::location,paths>(leader->first,leader_paths));
01915
01916 if(current_team().gold() < 20 && is_accessible(leader->first,enemy_dstsrc) == false) {
01917
01918 for(move_map::const_iterator i = enemy_dstsrc.begin(); i != enemy_dstsrc.end(); ++i) {
01919
01920
01921
01922 if(map_.is_village(i->first) && current_team().owns_village(i->first) &&
01923 int(distance_between(i->first,leader->first)) <= leader->second.total_movement()*2) {
01924
01925 int current_distance = distance_between(i->first,leader->first);
01926 location current_loc;
01927
01928 for(paths::routes_map::const_iterator j = leader_paths.routes.begin();
01929 j != leader_paths.routes.end(); ++j) {
01930
01931 const int distance = distance_between(i->first,j->first);
01932 if(distance < current_distance && is_accessible(j->first,enemy_dstsrc) == false) {
01933 current_distance = distance;
01934 current_loc = j->first;
01935 }
01936 }
01937
01938
01939
01940 if(current_loc.valid()) {
01941 LOG_AI << "considering movement to " << str_cast(current_loc.x + 1)
01942 << "," << str_cast(current_loc.y+1);
01943 unit_map temp_units(current_loc,leader->second);
01944 const paths p(map_,temp_units,current_loc,teams_,false,false,current_team());
01945
01946 if(p.routes.count(i->first)) {
01947 move_unit(leader->first,current_loc,possible_moves);
01948 return;
01949 }
01950 }
01951 }
01952 }
01953 }
01954
01955
01956
01957 if(nearest_keep(leader->first) == leader->first) {
01958 const location keep = leader->first;
01959 std::pair<gamemap::location,unit> *temp_leader;
01960
01961 temp_leader = units_.extract(keep);
01962
01963 bool friend_can_reach_keep = false;
01964
01965 std::map<location,paths> friends_possible_moves;
01966 move_map friends_srcdst, friends_dstsrc;
01967 calculate_possible_moves(friends_possible_moves,friends_srcdst,friends_dstsrc,false,true);
01968 for(move_map::const_iterator i = friends_dstsrc.begin(); i != friends_dstsrc.end(); ++i) {
01969 if(i->first == keep) {
01970 const unit_map::const_iterator itor = units_.find(i->second);
01971 if(itor != units_.end() && itor->second.can_recruit()) {
01972 friend_can_reach_keep = true;
01973 break;
01974 }
01975 }
01976 }
01977
01978 units_.add(temp_leader);
01979
01980 if(friend_can_reach_keep) {
01981
01982 location adj[6];
01983 get_adjacent_tiles(keep,adj);
01984 for(size_t n = 0; n != 6; ++n) {
01985
01986
01987 if(map_.on_board(adj[n]) &&
01988 leader_paths.routes.count(adj[n]) != 0 &&
01989 is_accessible(adj[n],enemy_dstsrc) == false) {
01990
01991 move_unit(keep,adj[n],possible_moves);
01992 return;
01993 }
01994 }
01995 }
01996 }
01997
01998
01999 leader = find_leader(units_,team_num_);
02000 if (!leader->second.has_moved() && leader->second.attacks_left()) {
02001 std::map<gamemap::location,paths> dummy_possible_moves;
02002 move_map fullmove_srcdst;
02003 move_map fullmove_dstsrc;
02004 calculate_possible_moves(dummy_possible_moves,fullmove_srcdst,fullmove_dstsrc,false,true,&avoided_locations());
02005
02006 if (should_retreat(leader->first, leader, fullmove_srcdst, fullmove_dstsrc, enemy_dstsrc, 0.5)) {
02007 desperate_attack(leader->first);
02008 }
02009 }
02010 }
02011
02012 bool ai::leader_can_reach_keep()
02013 {
02014 const unit_map::iterator leader = find_leader(units_,team_num_);
02015 if(leader == units_.end() || leader->second.incapacitated()) {
02016 return false;
02017 }
02018
02019 const gamemap::location& start_pos = nearest_keep(leader->first);
02020 if(start_pos.valid() == false) {
02021 return false;
02022 }
02023
02024 if(leader->first == start_pos) {
02025 return true;
02026 }
02027
02028
02029 const paths leader_paths(map_,units_,leader->first,teams_,false,false,current_team());
02030
02031
02032 return leader_paths.routes.count(start_pos) > 0;
02033 }
02034
02035 int ai::rate_terrain(const unit& u, const gamemap::location& loc)
02036 {
02037 const t_translation::t_terrain terrain = map_.get_terrain(loc);
02038 const int defense = u.defense_modifier(terrain);
02039 int rating = 100 - defense;
02040
02041 const int healing_value = 10;
02042 const int friendly_village_value = 5;
02043 const int neutral_village_value = 10;
02044 const int enemy_village_value = 15;
02045
02046 if(map_.gives_healing(terrain) && u.get_ability_bool("regenerates",loc) == false) {
02047 rating += healing_value;
02048 }
02049
02050 if(map_.is_village(terrain)) {
02051 const int owner = village_owner(loc,teams_);
02052
02053 if(owner + 1 == static_cast<int>(team_num_)) {
02054 rating += friendly_village_value;
02055 } else if(owner == -1) {
02056 rating += neutral_village_value;
02057 } else {
02058 rating += enemy_village_value;
02059 }
02060 }
02061
02062 return rating;
02063 }
02064
02065 const ai::defensive_position& ai::best_defensive_position(const gamemap::location& loc,
02066 const move_map& dstsrc, const move_map& srcdst, const move_map& enemy_dstsrc)
02067 {
02068 const unit_map::const_iterator itor = units_.find(loc);
02069 if(itor == units_.end()) {
02070 static defensive_position pos;
02071 pos.chance_to_hit = 0;
02072 pos.vulnerability = pos.support = 0;
02073 return pos;
02074 }
02075
02076 const std::map<location,defensive_position>::const_iterator position =
02077 defensive_position_cache_.find(loc);
02078
02079 if(position != defensive_position_cache_.end()) {
02080 return position->second;
02081 }
02082
02083 defensive_position pos;
02084 pos.chance_to_hit = 100;
02085 pos.vulnerability = 10000.0;
02086 pos.support = 0.0;
02087
02088 typedef move_map::const_iterator Itor;
02089 const std::pair<Itor,Itor> itors = srcdst.equal_range(loc);
02090 for(Itor i = itors.first; i != itors.second; ++i) {
02091 const int defense = itor->second.defense_modifier(map_.get_terrain(i->second));
02092 if(defense > pos.chance_to_hit) {
02093 continue;
02094 }
02095
02096 const double vulnerability = power_projection(i->second,enemy_dstsrc);
02097 const double support = power_projection(i->second,dstsrc);
02098
02099 if(defense < pos.chance_to_hit || support - vulnerability > pos.support - pos.vulnerability) {
02100 pos.loc = i->second;
02101 pos.chance_to_hit = defense;
02102 pos.vulnerability = vulnerability;
02103 pos.support = support;
02104 }
02105 }
02106
02107 defensive_position_cache_.insert(std::pair<location,defensive_position>(loc,pos));
02108 return defensive_position_cache_[loc];
02109 }
02110
02111 bool ai::is_accessible(const location& loc, const move_map& dstsrc) const
02112 {
02113 gamemap::location adj[6];
02114 get_adjacent_tiles(loc,adj);
02115 for(size_t n = 0; n != 6; ++n) {
02116 if(dstsrc.count(adj[n]) > 0) {
02117 return true;
02118 }
02119 }
02120
02121 return dstsrc.count(loc) > 0;
02122 }
02123
02124
02125 const std::set<gamemap::location>& ai::keeps()
02126 {
02127 if(keeps_.empty()) {
02128
02129
02130 for(size_t x = 0; x != size_t(map_.w()); ++x) {
02131 for(size_t y = 0; y != size_t(map_.h()); ++y) {
02132 const gamemap::location loc(x,y);
02133 if(map_.is_keep(loc)) {
02134 gamemap::location adj[6];
02135 get_adjacent_tiles(loc,adj);
02136 for(size_t n = 0; n != 6; ++n) {
02137 if(map_.is_castle(adj[n])) {
02138 keeps_.insert(loc);
02139 break;
02140 }
02141 }
02142 }
02143 }
02144 }
02145 }
02146
02147 return keeps_;
02148 }
02149
02150 const gamemap::location& ai::nearest_keep(const gamemap::location& loc)
02151 {
02152 const std::set<gamemap::location>& keeps = this->keeps();
02153 if(keeps.empty()) {
02154 static const gamemap::location dummy;
02155 return dummy;
02156 }
02157
02158 const gamemap::location* res = NULL;
02159 int closest = -1;
02160 for(std::set<gamemap::location>::const_iterator i = keeps.begin(); i != keeps.end(); ++i) {
02161 const int distance = distance_between(*i,loc);
02162 if(res == NULL || distance < closest) {
02163 closest = distance;
02164 res = &*i;
02165 }
02166 }
02167
02168 return *res;
02169 }
02170
02171 const std::set<gamemap::location>& ai::avoided_locations()
02172 {
02173 if(avoid_.empty()) {
02174 const config::child_list& avoids = current_team().ai_parameters().get_children("avoid");
02175 for(config::child_list::const_iterator a = avoids.begin(); a != avoids.end(); ++a) {
02176
02177 const std::vector<location>& locs = parse_location_range((**a)["x"],(**a)["y"]);
02178 for(std::vector<location>::const_iterator i = locs.begin(); i != locs.end(); ++i) {
02179 avoid_.insert(*i);
02180 }
02181 }
02182
02183 if(avoid_.empty()) {
02184 avoid_.insert(location());
02185 }
02186 }
02187
02188 return avoid_;
02189 }
02190
02191 int ai::attack_depth()
02192 {
02193 if(attack_depth_ > 0) {
02194 return attack_depth_;
02195 }
02196
02197 const config& parms = current_team().ai_parameters();
02198 attack_depth_ = maximum<int>(1,lexical_cast_default<int>(parms["attack_depth"],5));
02199 return attack_depth_;
02200 }
02201
02202 namespace {
02203 template<typename Container>
02204 variant villages_from_set(const Container& villages,
02205 const std::set<gamemap::location>* exclude=NULL) {
02206 std::vector<variant> vars;
02207 foreach(const gamemap::location& loc, villages) {
02208 if(exclude && exclude->count(loc)) {
02209 continue;
02210 }
02211 vars.push_back(variant(new location_callable(loc)));
02212 }
02213
02214 return variant(&vars);
02215 }
02216 }
02217
02218 variant ai_interface::get_value(const std::string& key) const
02219 {
02220 if(key == "turn") {
02221 return variant(get_info().state.turn());
02222 } else if(key == "units") {
02223 std::vector<variant> vars;
02224 for(unit_map::const_iterator i = info_.units.begin(); i != info_.units.end(); ++i) {
02225 vars.push_back(variant(new unit_callable(*i)));
02226 }
02227 return variant(&vars);
02228 } else if(key == "my_units") {
02229 std::vector<variant> vars;
02230 for(unit_map::const_iterator i = info_.units.begin(); i != info_.units.end(); ++i) {
02231 if(i->second.side() == info_.team_num) {
02232 vars.push_back(variant(new unit_callable(*i)));
02233 }
02234 }
02235 return variant(&vars);
02236 } else if(key == "enemy_units") {
02237 std::vector<variant> vars;
02238 for(unit_map::const_iterator i = info_.units.begin(); i != info_.units.end(); ++i) {
02239 if(info_.teams[info_.team_num-1].is_enemy(i->second.side())) {
02240 vars.push_back(variant(new unit_callable(*i)));
02241 }
02242 }
02243 return variant(&vars);
02244 } else if(key == "villages") {
02245 return villages_from_set(info_.map.villages());
02246 } else if(key == "my_villages") {
02247 return villages_from_set(current_team().villages());
02248 } else if(key == "enemy_and_unowned_villages") {
02249 return villages_from_set(info_.map.villages(), ¤t_team().villages());
02250 } else if(key == "map") {
02251 return variant(new gamemap_callable(info_.map));
02252 } else if(key == "teams") {
02253 std::vector<variant> vars;
02254 for(std::vector<team>::const_iterator i = info_.state.teams->begin(); i != info_.state.teams->end(); ++i) {
02255 vars.push_back(variant(new team_callable(*i)));
02256 }
02257 return variant(&vars);
02258 }
02259 return variant();
02260 }
02261
02262 void ai_interface::get_inputs(std::vector<game_logic::formula_input>* inputs) const
02263 {
02264 using game_logic::FORMULA_READ_ONLY;
02265 inputs->push_back(game_logic::formula_input("turn", FORMULA_READ_ONLY));
02266 inputs->push_back(game_logic::formula_input("units", FORMULA_READ_ONLY));
02267 inputs->push_back(game_logic::formula_input("my_units", FORMULA_READ_ONLY));
02268 inputs->push_back(game_logic::formula_input("enemy_units", FORMULA_READ_ONLY));
02269 inputs->push_back(game_logic::formula_input("villages", FORMULA_READ_ONLY));
02270 inputs->push_back(game_logic::formula_input("my_villages", FORMULA_READ_ONLY));
02271 inputs->push_back(game_logic::formula_input("enemy_and_unowned_villages", FORMULA_READ_ONLY));
02272 inputs->push_back(game_logic::formula_input("map", FORMULA_READ_ONLY));
02273 inputs->push_back(game_logic::formula_input("teams", FORMULA_READ_ONLY));
02274 }
02275
02276 variant ai::attack_analysis::get_value(const std::string& key) const
02277 {
02278 using namespace game_logic;
02279 if(key == "target") {
02280 return variant(new location_callable(target));
02281 } else if(key == "movements") {
02282 std::vector<variant> res;
02283 for(size_t n = 0; n != movements.size(); ++n) {
02284 map_formula_callable* item = new map_formula_callable(NULL);
02285 item->add("src", variant(new location_callable(movements[n].first)));
02286 item->add("dst", variant(new location_callable(movements[n].second)));
02287 res.push_back(variant(item));
02288 }
02289
02290 return variant(&res);
02291 } else if(key == "units") {
02292 std::vector<variant> res;
02293 for(size_t n = 0; n != movements.size(); ++n) {
02294 res.push_back(variant(new location_callable(movements[n].first)));
02295 }
02296
02297 return variant(&res);
02298 } else if(key == "target_value") {
02299 return variant(static_cast<int>(target_value*1000));
02300 } else if(key == "avg_losses") {
02301 return variant(static_cast<int>(avg_losses*1000));
02302 } else if(key == "chance_to_kill") {
02303 return variant(static_cast<int>(chance_to_kill*100));
02304 } else if(key == "avg_damage_inflicted") {
02305 return variant(static_cast<int>(avg_damage_inflicted));
02306 } else if(key == "target_starting_damage") {
02307 return variant(target_starting_damage);
02308 } else if(key == "avg_damage_taken") {
02309 return variant(static_cast<int>(avg_damage_taken));
02310 } else if(key == "resources_used") {
02311 return variant(static_cast<int>(resources_used));
02312 } else if(key == "terrain_quality") {
02313 return variant(static_cast<int>(terrain_quality));
02314 } else if(key == "alternative_terrain_quality") {
02315 return variant(static_cast<int>(alternative_terrain_quality));
02316 } else if(key == "vulnerability") {
02317 return variant(static_cast<int>(vulnerability));
02318 } else if(key == "support") {
02319 return variant(static_cast<int>(support));
02320 } else if(key == "leader_threat") {
02321 return variant(leader_threat);
02322 } else if(key == "uses_leader") {
02323 return variant(uses_leader);
02324 } else if(key == "is_surrounded") {
02325 return variant(is_surrounded);
02326 } else {
02327 return variant();
02328 }
02329 }
02330
02331 void ai::attack_analysis::get_inputs(std::vector<game_logic::formula_input>* inputs) const
02332 {
02333 using namespace game_logic;
02334 inputs->push_back(formula_input("target", FORMULA_READ_ONLY));
02335 inputs->push_back(formula_input("movements", FORMULA_READ_ONLY));
02336 inputs->push_back(formula_input("units", FORMULA_READ_ONLY));
02337 inputs->push_back(formula_input("target_value", FORMULA_READ_ONLY));
02338 inputs->push_back(formula_input("avg_losses", FORMULA_READ_ONLY));
02339 inputs->push_back(formula_input("chance_to_kill", FORMULA_READ_ONLY));
02340 inputs->push_back(formula_input("avg_damage_inflicted", FORMULA_READ_ONLY));
02341 inputs->push_back(formula_input("target_starting_damage", FORMULA_READ_ONLY));
02342 inputs->push_back(formula_input("avg_damage_taken", FORMULA_READ_ONLY));
02343 inputs->push_back(formula_input("resources_used", FORMULA_READ_ONLY));
02344 inputs->push_back(formula_input("terrain_quality", FORMULA_READ_ONLY));
02345 inputs->push_back(formula_input("alternative_terrain_quality", FORMULA_READ_ONLY));
02346 inputs->push_back(formula_input("vulnerability", FORMULA_READ_ONLY));
02347 inputs->push_back(formula_input("support", FORMULA_READ_ONLY));
02348 inputs->push_back(formula_input("leader_threat", FORMULA_READ_ONLY));
02349 inputs->push_back(formula_input("uses_leader", FORMULA_READ_ONLY));
02350 inputs->push_back(formula_input("is_surrounded", FORMULA_READ_ONLY));
02351 }